From d1484a868902d8277fffff3843ad4e44de637ffd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Cie=C5=9Blak?= Date: Mon, 22 Jun 2020 15:09:11 +0200 Subject: [PATCH] Use latest Giraffe alpha and Giraffe.ViewEngine --- paket.dependencies | 2 + paket.lock | 181 ++++++++++---------- sample/CsrfSample/CsrfSample.fs | 34 ++-- sample/Saturn.Sample/Saturn.Sample.fs | 4 +- sample/TurbolinksSample/TurbolinksSample.fs | 6 +- sample/TurbolinksSample/Views/App.fs | 89 +++++----- sample/TurbolinksSample/Views/Index.fs | 40 ++--- sample/TurbolinksSample/Views/OtherView.fs | 39 ++--- src/Saturn/CSRF.fs | 43 +++-- src/Saturn/ControllerEndpoint.fs | 1 - src/Saturn/ControllerHelpers.fs | 23 ++- src/Saturn/Diagnostics.fs | 165 +++++++++--------- src/Saturn/Pipelines.fs | 7 +- tests/Saturn.UnitTests/ControllerTests.fs | 137 +++++++-------- 14 files changed, 383 insertions(+), 388 deletions(-) diff --git a/paket.dependencies b/paket.dependencies index 0a142b29..a4f7594a 100644 --- a/paket.dependencies +++ b/paket.dependencies @@ -11,6 +11,8 @@ nuget System.Threading.Tasks.Dataflow >= 4.9 nuget BenchmarkDotNet nuget Giraffe prerelease +nuget Giraffe.ViewEngine + nuget Microsoft.AspNetCore.Authentication.JwtBearer < 3.1 nuget Microsoft.AspNetCore.Authentication.Google < 3.1 nuget Microsoft.AspNetCore.Authentication.OpenIdConnect < 3.1 diff --git a/paket.lock b/paket.lock index b5ddb53c..160ea67f 100644 --- a/paket.lock +++ b/paket.lock @@ -38,25 +38,27 @@ NUGET FSharp.Core (>= 4.3.4) Microsoft.IO.RecyclableMemoryStream (>= 1.2.2) FSharp.Core (4.7.2) - Giraffe (5.0.0-alpha-001) + Giraffe (5.0.0-alpha-002) FSharp.Core (>= 4.7.1) + Giraffe.ViewEngine (>= 1.0) Microsoft.IO.RecyclableMemoryStream (>= 1.3.4) Newtonsoft.Json (>= 12.0.3) System.Text.Json (>= 4.7.2) TaskBuilder.fs (>= 2.1) Utf8Json (>= 1.3.7) + Giraffe.ViewEngine (1.0) + FSharp.Core (>= 4.7.1) Grpc.AspNetCore.Server (2.29) Grpc.Core.Api (>= 2.29) Grpc.Net.Common (>= 2.29) - Grpc.Core.Api (2.29) + Grpc.Core.Api (2.30.0-pre1) System.Memory (>= 4.5.3) - Grpc.Net.Client (2.29) - Grpc.Core.Api (>= 2.29) - Grpc.Net.Common (>= 2.29) + Grpc.Net.Client (2.30.0-pre1) + Grpc.Net.Common (>= 2.30.0-pre1) Microsoft.Extensions.Logging.Abstractions (>= 2.1.1) System.Diagnostics.DiagnosticSource (>= 4.5.1) - Grpc.Net.Common (2.29) - Grpc.Core.Api (>= 2.29) + Grpc.Net.Common (2.30.0-pre1) + Grpc.Core.Api (>= 2.30.0-pre1) Iced (1.7) Microsoft.AspNet.WebApi.Client (5.2.7) Newtonsoft.Json (>= 10.0.1) @@ -317,7 +319,7 @@ NUGET Newtonsoft.Json (12.0.3) Newtonsoft.Json.Bson (1.0.2) Newtonsoft.Json (>= 12.0.1) - NSubstitute (4.2.1) + NSubstitute (4.2.2) Castle.Core (>= 4.4) System.Threading.Tasks.Extensions (>= 4.3) Perfolizer (0.2.1) @@ -628,118 +630,119 @@ STORAGE: NONE RESTRICTION: == netstandard2.0 NUGET remote: https://api.nuget.org/v3/index.json - BlackFox.VsWhere (1.0) + BlackFox.VsWhere (1.1) FSharp.Core (>= 4.2.3) - Fake.Api.GitHub (5.20) + Microsoft.Win32.Registry (>= 4.7) + Fake.Api.GitHub (5.20.1) FSharp.Core (>= 4.7.1) Octokit (>= 0.47) - Fake.Core.CommandLineParsing (5.20) + Fake.Core.CommandLineParsing (5.20.1) FParsec (>= 1.1.1) FSharp.Core (>= 4.7.1) - Fake.Core.Context (5.20) + Fake.Core.Context (5.20.1) FSharp.Core (>= 4.7.1) - Fake.Core.Environment (5.20) + Fake.Core.Environment (5.20.1) FSharp.Core (>= 4.7.1) - Fake.Core.FakeVar (5.20) - Fake.Core.Context (>= 5.20) + Fake.Core.FakeVar (5.20.1) + Fake.Core.Context (>= 5.20.1) FSharp.Core (>= 4.7.1) - Fake.Core.Process (5.20) - Fake.Core.Environment (>= 5.20) - Fake.Core.FakeVar (>= 5.20) - Fake.Core.String (>= 5.20) - Fake.Core.Trace (>= 5.20) - Fake.IO.FileSystem (>= 5.20) + Fake.Core.Process (5.20.1) + Fake.Core.Environment (>= 5.20.1) + Fake.Core.FakeVar (>= 5.20.1) + Fake.Core.String (>= 5.20.1) + Fake.Core.Trace (>= 5.20.1) + Fake.IO.FileSystem (>= 5.20.1) FSharp.Core (>= 4.7.1) System.Collections.Immutable (>= 1.7) - Fake.Core.ReleaseNotes (5.20) - Fake.Core.SemVer (>= 5.20) - Fake.Core.String (>= 5.20) + Fake.Core.ReleaseNotes (5.20.1) + Fake.Core.SemVer (>= 5.20.1) + Fake.Core.String (>= 5.20.1) FSharp.Core (>= 4.7.1) - Fake.Core.SemVer (5.20) + Fake.Core.SemVer (5.20.1) FSharp.Core (>= 4.7.1) - Fake.Core.String (5.20) + Fake.Core.String (5.20.1) FSharp.Core (>= 4.7.1) - Fake.Core.Target (5.20) - Fake.Core.CommandLineParsing (>= 5.20) - Fake.Core.Context (>= 5.20) - Fake.Core.Environment (>= 5.20) - Fake.Core.FakeVar (>= 5.20) - Fake.Core.Process (>= 5.20) - Fake.Core.String (>= 5.20) - Fake.Core.Trace (>= 5.20) + Fake.Core.Target (5.20.1) + Fake.Core.CommandLineParsing (>= 5.20.1) + Fake.Core.Context (>= 5.20.1) + Fake.Core.Environment (>= 5.20.1) + Fake.Core.FakeVar (>= 5.20.1) + Fake.Core.Process (>= 5.20.1) + Fake.Core.String (>= 5.20.1) + Fake.Core.Trace (>= 5.20.1) FSharp.Control.Reactive (>= 4.2) FSharp.Core (>= 4.7.1) - Fake.Core.Tasks (5.20) - Fake.Core.Trace (>= 5.20) + Fake.Core.Tasks (5.20.1) + Fake.Core.Trace (>= 5.20.1) FSharp.Core (>= 4.7.1) - Fake.Core.Trace (5.20) - Fake.Core.Environment (>= 5.20) - Fake.Core.FakeVar (>= 5.20) + Fake.Core.Trace (5.20.1) + Fake.Core.Environment (>= 5.20.1) + Fake.Core.FakeVar (>= 5.20.1) FSharp.Core (>= 4.7.1) - Fake.Core.UserInput (5.20) + Fake.Core.UserInput (5.20.1) FSharp.Core (>= 4.7.1) - Fake.Core.Xml (5.20) - Fake.Core.String (>= 5.20) + Fake.Core.Xml (5.20.1) + Fake.Core.String (>= 5.20.1) FSharp.Core (>= 4.7.1) - Fake.DotNet.AssemblyInfoFile (5.20) - Fake.Core.Environment (>= 5.20) - Fake.Core.String (>= 5.20) - Fake.Core.Trace (>= 5.20) - Fake.IO.FileSystem (>= 5.20) + Fake.DotNet.AssemblyInfoFile (5.20.1) + Fake.Core.Environment (>= 5.20.1) + Fake.Core.String (>= 5.20.1) + Fake.Core.Trace (>= 5.20.1) + Fake.IO.FileSystem (>= 5.20.1) FSharp.Core (>= 4.7.1) - Fake.DotNet.Cli (5.20) - Fake.Core.Environment (>= 5.20) - Fake.Core.Process (>= 5.20) - Fake.Core.String (>= 5.20) - Fake.Core.Trace (>= 5.20) - Fake.DotNet.MSBuild (>= 5.20) - Fake.DotNet.NuGet (>= 5.20) - Fake.IO.FileSystem (>= 5.20) + Fake.DotNet.Cli (5.20.1) + Fake.Core.Environment (>= 5.20.1) + Fake.Core.Process (>= 5.20.1) + Fake.Core.String (>= 5.20.1) + Fake.Core.Trace (>= 5.20.1) + Fake.DotNet.MSBuild (>= 5.20.1) + Fake.DotNet.NuGet (>= 5.20.1) + Fake.IO.FileSystem (>= 5.20.1) FSharp.Core (>= 4.7.1) Mono.Posix.NETStandard (>= 1.0) Newtonsoft.Json (>= 12.0.3) - Fake.DotNet.MSBuild (5.20) + Fake.DotNet.MSBuild (5.20.1) BlackFox.VsWhere (>= 1.0) - Fake.Core.Environment (>= 5.20) - Fake.Core.Process (>= 5.20) - Fake.Core.String (>= 5.20) - Fake.Core.Trace (>= 5.20) - Fake.IO.FileSystem (>= 5.20) + Fake.Core.Environment (>= 5.20.1) + Fake.Core.Process (>= 5.20.1) + Fake.Core.String (>= 5.20.1) + Fake.Core.Trace (>= 5.20.1) + Fake.IO.FileSystem (>= 5.20.1) FSharp.Core (>= 4.7.1) MSBuild.StructuredLogger (>= 2.1.117) - Fake.DotNet.NuGet (5.20) - Fake.Core.Environment (>= 5.20) - Fake.Core.Process (>= 5.20) - Fake.Core.SemVer (>= 5.20) - Fake.Core.String (>= 5.20) - Fake.Core.Tasks (>= 5.20) - Fake.Core.Trace (>= 5.20) - Fake.Core.Xml (>= 5.20) - Fake.IO.FileSystem (>= 5.20) - Fake.Net.Http (>= 5.20) + Fake.DotNet.NuGet (5.20.1) + Fake.Core.Environment (>= 5.20.1) + Fake.Core.Process (>= 5.20.1) + Fake.Core.SemVer (>= 5.20.1) + Fake.Core.String (>= 5.20.1) + Fake.Core.Tasks (>= 5.20.1) + Fake.Core.Trace (>= 5.20.1) + Fake.Core.Xml (>= 5.20.1) + Fake.IO.FileSystem (>= 5.20.1) + Fake.Net.Http (>= 5.20.1) FSharp.Core (>= 4.7.1) Newtonsoft.Json (>= 12.0.3) NuGet.Protocol (>= 5.5.1) - Fake.DotNet.Paket (5.20) - Fake.Core.Process (>= 5.20) - Fake.Core.String (>= 5.20) - Fake.Core.Trace (>= 5.20) - Fake.DotNet.Cli (>= 5.20) - Fake.IO.FileSystem (>= 5.20) + Fake.DotNet.Paket (5.20.1) + Fake.Core.Process (>= 5.20.1) + Fake.Core.String (>= 5.20.1) + Fake.Core.Trace (>= 5.20.1) + Fake.DotNet.Cli (>= 5.20.1) + Fake.IO.FileSystem (>= 5.20.1) FSharp.Core (>= 4.7.1) - Fake.IO.FileSystem (5.20) - Fake.Core.String (>= 5.20) + Fake.IO.FileSystem (5.20.1) + Fake.Core.String (>= 5.20.1) FSharp.Core (>= 4.7.1) - Fake.Net.Http (5.20) - Fake.Core.Trace (>= 5.20) + Fake.Net.Http (5.20.1) + Fake.Core.Trace (>= 5.20.1) FSharp.Core (>= 4.7.1) - Fake.Tools.Git (5.20) - Fake.Core.Environment (>= 5.20) - Fake.Core.Process (>= 5.20) - Fake.Core.SemVer (>= 5.20) - Fake.Core.String (>= 5.20) - Fake.Core.Trace (>= 5.20) - Fake.IO.FileSystem (>= 5.20) + Fake.Tools.Git (5.20.1) + Fake.Core.Environment (>= 5.20.1) + Fake.Core.Process (>= 5.20.1) + Fake.Core.SemVer (>= 5.20.1) + Fake.Core.String (>= 5.20.1) + Fake.Core.Trace (>= 5.20.1) + Fake.IO.FileSystem (>= 5.20.1) FSharp.Core (>= 4.7.1) FParsec (1.1.1) FSharp.Core (>= 4.3.4) @@ -1025,7 +1028,7 @@ STORAGE: SYMLINK RESTRICTION: == netstandard2.0 NUGET remote: https://api.nuget.org/v3/index.json - FSharp.Compiler.Service (36.0.1) + FSharp.Compiler.Service (36.0.3) FSharp.Core (>= 4.6.2) System.Buffers (>= 4.5) System.Collections.Immutable (>= 1.5) diff --git a/sample/CsrfSample/CsrfSample.fs b/sample/CsrfSample/CsrfSample.fs index 2b00b0f5..48e8ade5 100644 --- a/sample/CsrfSample/CsrfSample.fs +++ b/sample/CsrfSample/CsrfSample.fs @@ -4,29 +4,29 @@ open Saturn open Giraffe.ResponseWriters open Microsoft.Extensions.Logging -//TODO -// module Views = -// open Microsoft.AspNetCore.Http -// open Giraffe.GiraffeViewEngine -// open Saturn.CSRF.View.Giraffe - -// let index (ctx: HttpContext) = -// body [] [ -// h1 [] [ -// rawText "Hello from static!" -// ] -// protectedForm ctx [ _action "/form"; _method "post" ] [ -// input [_type "submit"; _value "Yay" ] -// ] -// ] + +module Views = + open Microsoft.AspNetCore.Http + open Giraffe.ViewEngine + open Saturn.CSRF.View.Giraffe + + let index (ctx: HttpContext) = + body [] [ + h1 [] [ + rawText "Hello from static!" + ] + protectedForm ctx [ _action "/form"; _method "post" ] [ + input [_type "submit"; _value "Yay" ] + ] + ] /// There are two routes on this router: one retrieves the token(s) and tells you the form fields/request headers to send the request token on. /// The other requires the token to be present before returning a success message to you. let appRouter = router { pipe_through protectFromForgery - //TODO - // get "/" (fun next ctx -> htmlView (Views.index ctx) next ctx) + + get "/" (fun next ctx -> htmlView (Views.index ctx) next ctx) get "/csrftoken" (fun next ctx -> json (CSRF.getRequestTokens ctx) next ctx) post "/requiresToken" (text "you gave a token!") post "/form" (fun next ctx -> json ctx.Request.Form next ctx) diff --git a/sample/Saturn.Sample/Saturn.Sample.fs b/sample/Saturn.Sample/Saturn.Sample.fs index 37cb5bc7..4f764989 100644 --- a/sample/Saturn.Sample/Saturn.Sample.fs +++ b/sample/Saturn.Sample/Saturn.Sample.fs @@ -112,8 +112,8 @@ let userController = controller { let topRouter = router { pipe_through headerPipe - //TODO - // not_found_handler (SiteMap.page) + + not_found_handler (SiteMap.page) get "/" helloWorld get "/a" helloWorld2 diff --git a/sample/TurbolinksSample/TurbolinksSample.fs b/sample/TurbolinksSample/TurbolinksSample.fs index e07b9b57..95d61b9a 100644 --- a/sample/TurbolinksSample/TurbolinksSample.fs +++ b/sample/TurbolinksSample/TurbolinksSample.fs @@ -11,8 +11,7 @@ let browser = pipeline { } let defaultView = router { - //TODO - // get "/" (htmlView Index.layout) + get "/" (htmlView Index.layout) get "/index.html" (redirectTo false "/") get "/default.html" (redirectTo false "/") } @@ -21,8 +20,7 @@ let browserRouter = router { pipe_through browser forward "" defaultView - //TODO - // forward "/otherView" (htmlView OtherView.layout) + forward "/otherView" (htmlView OtherView.layout) } diff --git a/sample/TurbolinksSample/Views/App.fs b/sample/TurbolinksSample/Views/App.fs index 85665577..26a58de0 100644 --- a/sample/TurbolinksSample/Views/App.fs +++ b/sample/TurbolinksSample/Views/App.fs @@ -1,48 +1,47 @@ module App -//TODO -// open Giraffe.GiraffeViewEngine +open Giraffe.ViewEngine -// let layout (content: XmlNode list) = -// html [_class "has-navbar-fixed-top"] [ -// head [] [ -// meta [_charset "utf-8"] -// meta [_name "viewport"; _content "width=device-width, initial-scale=1" ] -// title [] [encodedText "Hello Saturn + Turbolinks"] -// link [_rel "stylesheet"; _href "https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" ] -// link [_rel "stylesheet"; _href "https://cdnjs.cloudflare.com/ajax/libs/bulma/0.6.1/css/bulma.min.css" ] -// script [_src "https://cdnjs.cloudflare.com/ajax/libs/turbolinks/5.1.1/turbolinks.js"] [] -// ] -// body [] [ -// yield nav [ _class "navbar is-fixed-top has-shadow" ] [ -// div [_class "navbar-brand"] [ -// a [_class "navbar-item"; _href "/"] [ -// img [_src "https://avatars0.githubusercontent.com/u/35305523?s=200"; _width "28"; _height "28"] -// ] -// div [_class "navbar-burger burger"; attr "data-target" "navMenu"] [ -// span [] [] -// span [] [] -// span [] [] -// ] -// ] -// div [_class "navbar-menu"; _id "navMenu"] [ -// div [_class "navbar-start"] [ -// a [_class "navbar-item"; _href "https://github.com/SaturnFramework/Saturn/blob/master/README.md"] [rawText "Getting started"] -// ] -// ] -// ] -// yield! content -// yield footer [_class "footer is-fixed-bottom"] [ -// div [_class "container"] [ -// div [_class "content has-text-centered"] [ -// p [] [ -// rawText "Powered by " -// a [_href "https://github.com/SaturnFramework/Saturn"] [rawText "Saturn"] -// rawText " - F# MVC framework created by " -// a [_href "http://lambdafactory.io"] [rawText "λFactory"] -// ] -// ] -// ] -// ] -// ] -// ] +let layout (content: XmlNode list) = + html [_class "has-navbar-fixed-top"] [ + head [] [ + meta [_charset "utf-8"] + meta [_name "viewport"; _content "width=device-width, initial-scale=1" ] + title [] [encodedText "Hello Saturn + Turbolinks"] + link [_rel "stylesheet"; _href "https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" ] + link [_rel "stylesheet"; _href "https://cdnjs.cloudflare.com/ajax/libs/bulma/0.6.1/css/bulma.min.css" ] + script [_src "https://cdnjs.cloudflare.com/ajax/libs/turbolinks/5.1.1/turbolinks.js"] [] + ] + body [] [ + yield nav [ _class "navbar is-fixed-top has-shadow" ] [ + div [_class "navbar-brand"] [ + a [_class "navbar-item"; _href "/"] [ + img [_src "https://avatars0.githubusercontent.com/u/35305523?s=200"; _width "28"; _height "28"] + ] + div [_class "navbar-burger burger"; attr "data-target" "navMenu"] [ + span [] [] + span [] [] + span [] [] + ] + ] + div [_class "navbar-menu"; _id "navMenu"] [ + div [_class "navbar-start"] [ + a [_class "navbar-item"; _href "https://github.com/SaturnFramework/Saturn/blob/master/README.md"] [rawText "Getting started"] + ] + ] + ] + yield! content + yield footer [_class "footer is-fixed-bottom"] [ + div [_class "container"] [ + div [_class "content has-text-centered"] [ + p [] [ + rawText "Powered by " + a [_href "https://github.com/SaturnFramework/Saturn"] [rawText "Saturn"] + rawText " - F# MVC framework created by " + a [_href "http://lambdafactory.io"] [rawText "λFactory"] + ] + ] + ] + ] + ] + ] diff --git a/sample/TurbolinksSample/Views/Index.fs b/sample/TurbolinksSample/Views/Index.fs index 5ca36127..cc93a1d5 100644 --- a/sample/TurbolinksSample/Views/Index.fs +++ b/sample/TurbolinksSample/Views/Index.fs @@ -1,24 +1,24 @@ module Index -//TODO -// open Giraffe.GiraffeViewEngine -// let index = -// [ -// section [_class "hero is-primary"] [ -// div [_class "hero-body"] [ -// div [_class "container"] [ -// div [_class "columns is-vcentered"] [ -// div [_class "column"] [ -// p [_class "title"] [rawText "Welcome to Saturn!"] -// p [_class "subtitle"] [rawText "Opinionated, web development framework for F# which implements the server-side, functional MVC pattern"] -// ] -// ] -// ] -// ] -// ] -// a [_class "title"; _href "/otherView" ] [rawText "OtherView"] -// ] +open Giraffe.ViewEngine -// let layout = -// App.layout index +let index = + [ + section [_class "hero is-primary"] [ + div [_class "hero-body"] [ + div [_class "container"] [ + div [_class "columns is-vcentered"] [ + div [_class "column"] [ + p [_class "title"] [rawText "Welcome to Saturn!"] + p [_class "subtitle"] [rawText "Opinionated, web development framework for F# which implements the server-side, functional MVC pattern"] + ] + ] + ] + ] + ] + a [_class "title"; _href "/otherView" ] [rawText "OtherView"] + ] + +let layout = + App.layout index diff --git a/sample/TurbolinksSample/Views/OtherView.fs b/sample/TurbolinksSample/Views/OtherView.fs index 9d2f4646..f0a8bcc4 100644 --- a/sample/TurbolinksSample/Views/OtherView.fs +++ b/sample/TurbolinksSample/Views/OtherView.fs @@ -1,25 +1,24 @@ module OtherView -//TODO -// open Giraffe.GiraffeViewEngine +open Giraffe.ViewEngine -// let index = -// [ -// section [_class "hero is-primary"] [ -// div [_class "hero-body"] [ -// div [_class "container"] [ -// div [_class "columns is-vcentered"] [ -// div [_class "column"] [ -// p [_class "title"] [rawText "Welcome to Saturn!"] -// p [_class "subtitle"] [rawText "Opinionated, web development framework for F# which implements the server-side, functional MVC pattern"] -// ] -// ] -// ] -// ] -// ] -// a [_class "title"; _href "/" ] [rawText "Back to main"] -// ] +let index = + [ + section [_class "hero is-primary"] [ + div [_class "hero-body"] [ + div [_class "container"] [ + div [_class "columns is-vcentered"] [ + div [_class "column"] [ + p [_class "title"] [rawText "Welcome to Saturn!"] + p [_class "subtitle"] [rawText "Opinionated, web development framework for F# which implements the server-side, functional MVC pattern"] + ] + ] + ] + ] + ] + a [_class "title"; _href "/" ] [rawText "Back to main"] + ] -// let layout = -// App.layout index +let layout = + App.layout index diff --git a/src/Saturn/CSRF.fs b/src/Saturn/CSRF.fs index 914429c9..c1388aaa 100644 --- a/src/Saturn/CSRF.fs +++ b/src/Saturn/CSRF.fs @@ -96,25 +96,24 @@ or let getRequestTokens (ctx: HttpContext) = ctx.GetService().GetAndStoreTokens(ctx) - //TODO - // /// Contains view helpers for csrf tokens for various view engines. - // module View = - // module Giraffe = - // open Giraffe.GiraffeViewEngine - - // ///Creates a csrf token form input of the kind: - // let csrfTokenInput (ctx: HttpContext) = - // match ctx.GetService() with - // | null -> - // logMissingAntiforgeryFeature ctx - // input [ _name "Missing Antiforgery Feature" - // _value "No Antiforgery Token Available, check your application configuration" - // _type "text" ] - // | antiforgery -> - // let tokens = antiforgery.GetAndStoreTokens(ctx) - // input [ _name tokens.FormFieldName - // _value tokens.RequestToken - // _type "hidden" ] - - // ///View helper for creating a form that implicitly inserts a CSRF token hidden form input. - // let protectedForm ctx attrs children = form attrs (csrfTokenInput ctx :: children) + /// Contains view helpers for csrf tokens for various view engines. + module View = + module Giraffe = + open Giraffe.ViewEngine + + ///Creates a csrf token form input of the kind: + let csrfTokenInput (ctx: HttpContext) = + match ctx.GetService() with + | null -> + logMissingAntiforgeryFeature ctx + input [ _name "Missing Antiforgery Feature" + _value "No Antiforgery Token Available, check your application configuration" + _type "text" ] + | antiforgery -> + let tokens = antiforgery.GetAndStoreTokens(ctx) + input [ _name tokens.FormFieldName + _value tokens.RequestToken + _type "hidden" ] + + ///View helper for creating a form that implicitly inserts a CSRF token hidden form input. + let protectedForm ctx attrs children = form attrs (csrfTokenInput ctx :: children) diff --git a/src/Saturn/ControllerEndpoint.fs b/src/Saturn/ControllerEndpoint.fs index dcb41382..dda8a65e 100644 --- a/src/Saturn/ControllerEndpoint.fs +++ b/src/Saturn/ControllerEndpoint.fs @@ -342,7 +342,6 @@ module Controller = endpoint actionHandler member private x.AddKeyHandler<'Output> state action (actionHandler: HttpContext -> 'Key -> Task<'Output>) = - //TODO: Add version check and error handler let endpoint = x.ActionToIdEndpoint state action let actionHandler : 'Key -> HttpHandler = diff --git a/src/Saturn/ControllerHelpers.fs b/src/Saturn/ControllerHelpers.fs index e7d05708..74d86329 100644 --- a/src/Saturn/ControllerHelpers.fs +++ b/src/Saturn/ControllerHelpers.fs @@ -37,10 +37,10 @@ module ControllerHelpers = let html (ctx: HttpContext) template = ctx.WriteHtmlStringAsync template - //TODO - // ///Returns to the client rendered html template. - // let renderHtml (ctx: HttpContext) template = - // ctx.WriteHtmlStringAsync (Giraffe.GiraffeViewEngine.renderHtmlDocument template) + + ///Returns to the client rendered html template. + let renderHtml (ctx: HttpContext) template = + ctx.WriteHtmlStringAsync (Giraffe.ViewEngine.RenderView.AsString.htmlDocument template) ///Returns to the client static file. let file (ctx: HttpContext) path = @@ -77,9 +77,8 @@ module ControllerHelpers = match typeof<'a>, stringType with | k, Some s when k = typeof && htmlMediaType.IsSubsetOf s -> html ctx (unbox output) | k, Some s when k = typeof && plainMediaType.IsSubsetOf s -> text ctx (unbox output) - //TODO - // | k, _ when k = typeof && mediaTypes |> Seq.exists (htmlMediaType.IsSubsetOf) -> - // renderHtml ctx (unbox<_> output) + | k, _ when k = typeof && mediaTypes |> Seq.exists (htmlMediaType.IsSubsetOf) -> + renderHtml ctx (unbox<_> output) | _ -> if mediaTypes |> Seq.exists (jsonMediaType.IsSubsetOf) then json ctx output elif mediaTypes |> Seq.exists (xmlMediaType.IsSubsetOf) then xml ctx output @@ -91,18 +90,16 @@ module ControllerHelpers = match typeof<'a> with | k when k = typeof && mediaTypes |> Seq.exists (htmlMediaType.IsSubsetOf) -> html ctx (unbox output) | k when k = typeof && mediaTypes |> Seq.exists (plainMediaType.IsSubsetOf) -> text ctx (unbox output) - //TODO - // | k when k = typeof && mediaTypes |> Seq.exists (htmlMediaType.IsSubsetOf) -> - // renderHtml ctx (unbox<_> output) + | k when k = typeof && mediaTypes |> Seq.exists (htmlMediaType.IsSubsetOf) -> + renderHtml ctx (unbox<_> output) | _ -> if mediaTypes |> Seq.exists (jsonMediaType.IsSubsetOf) then json ctx output elif mediaTypes |> Seq.exists (xmlMediaType.IsSubsetOf) then xml ctx output else failwithf "Couldn't recognize any known Content-Type type" | _ -> match typeof<'a> with - //TODO - // | k when k = typeof -> - // renderHtml ctx (unbox<_> output) + | k when k = typeof -> + renderHtml ctx (unbox<_> output) | k when k = typeof -> ctx.WriteTextAsync(unbox output) | _ -> json ctx output diff --git a/src/Saturn/Diagnostics.fs b/src/Saturn/Diagnostics.fs index 2cd033ee..d77ad381 100644 --- a/src/Saturn/Diagnostics.fs +++ b/src/Saturn/Diagnostics.fs @@ -85,87 +85,86 @@ module SiteMap = s.CollectPaths "" None state |> Seq.map (fun (a,b,c) -> {Route = a; Verb =b; Headers = Map.ofList [if c.IsSome then yield ("x-controller-version", c.Value)] }) - //TODO - // open Giraffe.ResponseWriters - // open Giraffe.GiraffeViewEngine - // open Giraffe - - // let page : HttpHandler = - // fun next ctx -> - // let paths = getPaths () - // let generateLink (route: string) = - // let hasB = route.Contains "%b" - // let hasC = route.Contains "%c" - // let hasS = route.Contains "%s" - // let hasI = route.Contains "%i" - // let hasD = route.Contains "%d" - // let hasF = route.Contains "%f" - // let hasO = route.Contains "%O" - // let hasU = route.Contains "%u" - - // let link = route - // let info = "" - // let link, info = if hasB then link.Replace("%b", "true"), info + "`%b` replaced with `true`" else link, info - // let link, info = if hasC then link.Replace("%c", "x"), info + "`%c` replaced with `x`" else link, info - // let link, info = if hasS then link.Replace("%s", "sample"), info + "`%s` replaced with `sample`" else link, info - // let link, info = if hasI then link.Replace("%i", "123"), info + "`%i` replaced with `123`" else link, info - // let link, info = if hasD then link.Replace("%d", "123"), info + "`%d` replaced with `123`" else link, info - // let link, info = if hasF then link.Replace("%f", "123.123"), info + "`%f` replaced with `123.123`" else link, info - // let link, info = if hasU then link.Replace("%u", "123"), info + "`%u` replaced with `123`" else link, info - // let link, info = if hasO then link.Replace("%O", Guid.Empty.ToString()), info + "`%O` replaced with `" + Guid.Empty.ToString() + "`" else link, info - - // let route = if info <> "" then route + " (" + info + ")" else route - // a [_href link] [rawText route] - - - // let index = - // html [_class "has-navbar-fixed-top"] [ - // head [] [ - // meta [_charset "utf-8"] - // meta [_name "viewport"; _content "width=device-width, initial-scale=1" ] - // title [] [encodedText "Routing diagnostic page"] - // link [_rel "stylesheet"; _href "https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" ] - // link [_rel "stylesheet"; _href "https://cdnjs.cloudflare.com/ajax/libs/bulma/0.6.1/css/bulma.min.css" ] - // ] - // body [] [ - // div [_class "container "] [ - // h2 [ _class "title"] [rawText "Routes found in application"] - // table [_class "table is-hoverable is-fullwidth"] [ - // thead [] [ - // tr [] [ - // th [] [rawText "Route"] - // th [] [rawText "Verb"] - // th [] [rawText "Required headers"] - // ] - // ] - // tbody [] [ - // for path in (paths |> Seq.where (fun p -> p.Verb <> "NotFoundHandler")) do - // yield tr [] [ - // td [] [if path.Verb = "GET" then yield generateLink path.Route else yield rawText path.Route] - // td [] [rawText path.Verb] - // td [] (path.Headers |> Seq.map (fun (kv) -> p [] [code [] [rawText kv.Key]; rawText " -> "; rawText kv.Value ] ) |> Seq.toList) - // ] - // ] - // ] - - // h2 [ _class "title"] [rawText "Not Found handlers for following subroutes"] - // table [_class "table is-hoverable is-fullwidth"] [ - // thead [] [ - // tr [] [ - // th [] [rawText "Route"] - // th [] [rawText "Required headers"] - // ] - // ] - // tbody [] [ - // for path in (paths |> Seq.where (fun p -> p.Verb = "NotFoundHandler")) do - // yield tr [] [ - // td [] [rawText path.Route ] - // td [] (path.Headers |> Seq.map (fun (kv) -> p [] [code [] [rawText kv.Key]; rawText " -> "; rawText kv.Value ] ) |> Seq.toList) - // ] - // ] - // ] - // ] - // ] - // ] - // ctx.WriteHtmlStringAsync (Giraffe.GiraffeViewEngine.renderHtmlDocument index) + open Giraffe.ResponseWriters + open Giraffe.ViewEngine + open Giraffe + + let page : HttpHandler = + fun next ctx -> + let paths = getPaths () + let generateLink (route: string) = + let hasB = route.Contains "%b" + let hasC = route.Contains "%c" + let hasS = route.Contains "%s" + let hasI = route.Contains "%i" + let hasD = route.Contains "%d" + let hasF = route.Contains "%f" + let hasO = route.Contains "%O" + let hasU = route.Contains "%u" + + let link = route + let info = "" + let link, info = if hasB then link.Replace("%b", "true"), info + "`%b` replaced with `true`" else link, info + let link, info = if hasC then link.Replace("%c", "x"), info + "`%c` replaced with `x`" else link, info + let link, info = if hasS then link.Replace("%s", "sample"), info + "`%s` replaced with `sample`" else link, info + let link, info = if hasI then link.Replace("%i", "123"), info + "`%i` replaced with `123`" else link, info + let link, info = if hasD then link.Replace("%d", "123"), info + "`%d` replaced with `123`" else link, info + let link, info = if hasF then link.Replace("%f", "123.123"), info + "`%f` replaced with `123.123`" else link, info + let link, info = if hasU then link.Replace("%u", "123"), info + "`%u` replaced with `123`" else link, info + let link, info = if hasO then link.Replace("%O", Guid.Empty.ToString()), info + "`%O` replaced with `" + Guid.Empty.ToString() + "`" else link, info + + let route = if info <> "" then route + " (" + info + ")" else route + a [_href link] [rawText route] + + + let index = + html [_class "has-navbar-fixed-top"] [ + head [] [ + meta [_charset "utf-8"] + meta [_name "viewport"; _content "width=device-width, initial-scale=1" ] + title [] [encodedText "Routing diagnostic page"] + link [_rel "stylesheet"; _href "https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" ] + link [_rel "stylesheet"; _href "https://cdnjs.cloudflare.com/ajax/libs/bulma/0.6.1/css/bulma.min.css" ] + ] + body [] [ + div [_class "container "] [ + h2 [ _class "title"] [rawText "Routes found in application"] + table [_class "table is-hoverable is-fullwidth"] [ + thead [] [ + tr [] [ + th [] [rawText "Route"] + th [] [rawText "Verb"] + th [] [rawText "Required headers"] + ] + ] + tbody [] [ + for path in (paths |> Seq.where (fun p -> p.Verb <> "NotFoundHandler")) do + yield tr [] [ + td [] [if path.Verb = "GET" then yield generateLink path.Route else yield rawText path.Route] + td [] [rawText path.Verb] + td [] (path.Headers |> Seq.map (fun (kv) -> p [] [code [] [rawText kv.Key]; rawText " -> "; rawText kv.Value ] ) |> Seq.toList) + ] + ] + ] + + h2 [ _class "title"] [rawText "Not Found handlers for following subroutes"] + table [_class "table is-hoverable is-fullwidth"] [ + thead [] [ + tr [] [ + th [] [rawText "Route"] + th [] [rawText "Required headers"] + ] + ] + tbody [] [ + for path in (paths |> Seq.where (fun p -> p.Verb = "NotFoundHandler")) do + yield tr [] [ + td [] [rawText path.Route ] + td [] (path.Headers |> Seq.map (fun (kv) -> p [] [code [] [rawText kv.Key]; rawText " -> "; rawText kv.Value ] ) |> Seq.toList) + ] + ] + ] + ] + ] + ] + ctx.WriteHtmlStringAsync (RenderView.AsString.htmlDocument index) diff --git a/src/Saturn/Pipelines.fs b/src/Saturn/Pipelines.fs index a61ee90b..8c625217 100644 --- a/src/Saturn/Pipelines.fs +++ b/src/Saturn/Pipelines.fs @@ -134,10 +134,9 @@ module Pipeline = [] member __.HtmlFile(state, fileName) : HttpHandler = state >=> (htmlFile fileName) - //TODO - // ///`render_html` is a more functional way of generating HTML by composing HTML elements in F# to generate a rich Model-View output. - // [] - // member __.RenderHtml(state, cnt) : HttpHandler = state >=> (htmlView cnt) + ///`render_html` is a more functional way of generating HTML by composing HTML elements in F# to generate a rich Model-View output. + [] + member __.RenderHtml(state, cnt) : HttpHandler = state >=> (htmlView cnt) ///`redirect_to` uses a 302 or 301 (when permanent) HTTP response code to redirect the client to the specified location. It takes in two parameters, a boolean flag denoting whether the redirect should be permanent or not and the location to redirect to. [] diff --git a/tests/Saturn.UnitTests/ControllerTests.fs b/tests/Saturn.UnitTests/ControllerTests.fs index c399b977..66095505 100644 --- a/tests/Saturn.UnitTests/ControllerTests.fs +++ b/tests/Saturn.UnitTests/ControllerTests.fs @@ -182,75 +182,76 @@ let plugTests = ] //---------------------------Rendering tests---------------------------------------- +open Giraffe.ViewEngine +open Microsoft.Extensions.Primitives + +let basicTemplate = + html [] [ + head [] [] + body [] [ + h1 [] [ encodedText "Hello, world!" ] + ] + ] + +let implicitNodeToHtmlTestController = controller { + index (fun _ -> task { return basicTemplate }) +} + +let explicitNodeToHtmlTestController = controller { + index (fun ctx -> task { return! Controller.renderHtml ctx basicTemplate }) +} + +let implicitStringToHtmlTestController = controller { + index (fun _ -> task { return RenderView.AsString.htmlNode basicTemplate}) +} + +[] +let htmlRendererTests = + testList "Controller HTML rendering" [ + testCase "doctype is added to implicit index html" <| fun _ -> + let ctx = getEmptyContext "GET" "/" + ctx.Request.Headers.Add("Accept", StringValues("text/html")) + + let expectedContent = "" + try + let result = implicitNodeToHtmlTestController next ctx |> runTask + match result with + | None -> failtestf "Calling the endpoint did not yield any result" + | Some ctx -> + let body = getBody ctx + Expect.stringStarts (body.ToUpperInvariant()) (expectedContent.ToUpperInvariant()) "Should start with a doctype element" + with ex -> failtestf "failed because %A" ex -//TODO -// let basicTemplate = -// html [] [ -// head [] [] -// body [] [ -// h1 [] [ encodedText "Hello, world!" ] -// ] -// ] - -// let implicitNodeToHtmlTestController = controller { -// index (fun _ -> task { return basicTemplate }) -// } - -// let explicitNodeToHtmlTestController = controller { -// index (fun ctx -> task { return! Controller.renderHtml ctx basicTemplate }) -// } - -// let implicitStringToHtmlTestController = controller { -// index (fun _ -> task { return renderHtmlNode basicTemplate}) -// } - -// [] -// let htmlRendererTests = -// testList "Controller HTML rendering" [ -// testCase "doctype is added to implicit index html" <| fun _ -> -// let ctx = getEmptyContext "GET" "/" -// ctx.Request.Headers.Add("Accept", StringValues("text/html")) - -// let expectedContent = "" -// try -// let result = implicitNodeToHtmlTestController next ctx |> runTask -// match result with -// | None -> failtestf "Calling the endpoint did not yield any result" -// | Some ctx -> -// let body = getBody ctx -// Expect.stringStarts (body.ToUpperInvariant()) (expectedContent.ToUpperInvariant()) "Should start with a doctype element" -// with ex -> failtestf "failed because %A" ex - -// testCase "doctype is added to explicit index html" <| fun _ -> -// let ctx = getEmptyContext "GET" "/" -// ctx.Request.Headers.Add("Accept", StringValues("text/html")) - -// let expectedContent = "" -// try -// let result = explicitNodeToHtmlTestController next ctx |> runTask -// match result with -// | None -> failtestf "Calling the endpoint did not yield any result" -// | Some ctx -> -// let body = getBody ctx -// Expect.stringStarts (body.ToUpperInvariant()) (expectedContent.ToUpperInvariant()) "Should start with a doctype element" -// with ex -> failtestf "failed because %A" ex - -// testCase "doctype is not added to implicit string results" <| fun _ -> -// let ctx = getEmptyContext "GET" "/" - -// let notExpectedContent = "" -// try -// let result = implicitStringToHtmlTestController next ctx |> runTask -// match result with -// | None -> failtestf "Calling the endpoint did not yield any result" -// | Some ctx -> -// let body = getBody ctx -// if body.Contains(notExpectedContent, StringComparison.InvariantCultureIgnoreCase) then -// Tests.failtest "Doctype element was present even though it should not be automatically added to string results." -// else -// () -// with ex -> failtestf "failed because %A" ex -// ] + testCase "doctype is added to explicit index html" <| fun _ -> + let ctx = getEmptyContext "GET" "/" + ctx.Request.Headers.Add("Accept", StringValues("text/html")) + + let expectedContent = "" + try + let result = explicitNodeToHtmlTestController next ctx |> runTask + match result with + | None -> failtestf "Calling the endpoint did not yield any result" + | Some ctx -> + let body = getBody ctx + Expect.stringStarts (body.ToUpperInvariant()) (expectedContent.ToUpperInvariant()) "Should start with a doctype element" + with ex -> failtestf "failed because %A" ex + + testCase "doctype is not added to implicit string results" <| fun _ -> + let ctx = getEmptyContext "GET" "/" + + let notExpectedContent = "" + try + let result = implicitStringToHtmlTestController next ctx |> runTask + match result with + | None -> failtestf "Calling the endpoint did not yield any result" + | Some ctx -> + let body = getBody ctx + if body.Contains(notExpectedContent, StringComparison.InvariantCultureIgnoreCase) then + Tests.failtest "Doctype element was present even though it should not be automatically added to string results." + else + () + with ex -> failtestf "failed because %A" ex + ] //---------------------------Implicit conversion tests---------------------------------------- type Test = {A: string; B: int; C: bool}