├── tools
├── paket.references
└── tools.csproj
├── .paket
├── paket.exe
├── paket.exe.config
├── paket.targets
└── Paket.Restore.targets
├── sample
├── paket.references
├── App.fsproj
└── Program.fs
├── src
└── Giraffe.JsonTherapy
│ ├── paket.references
│ ├── AssemblyInfo.fs
│ ├── Giraffe.JsonTherapy.fsproj
│ └── JsonTherapy.fs
├── tests
└── Giraffe.JsonTherapy.Tests
│ ├── paket.references
│ ├── Giraffe.JsonTherapy.Tests.fsproj
│ ├── Main.fs
│ ├── AssemblyInfo.fs
│ └── Tests.fs
├── RELEASE_NOTES.md
├── appveyor.yml
├── .config
└── dotnet-tools.json
├── .travis.yml
├── .editorconfig
├── paket.dependencies
├── .github
├── ISSUE_TEMPLATE.md
└── PULL_REQUEST_TEMPLATE.md
├── LICENSE.md
├── .vscode
└── launch.json
├── Giraffe.JsonTherapy.sln
├── .gitignore
└── README.md
/tools/paket.references:
--------------------------------------------------------------------------------
1 | dotnet-sourcelink
2 |
--------------------------------------------------------------------------------
/.paket/paket.exe:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Zaid-Ajaj/Giraffe.JsonTherapy/HEAD/.paket/paket.exe
--------------------------------------------------------------------------------
/sample/paket.references:
--------------------------------------------------------------------------------
1 | FSharp.Core
2 | Giraffe
3 | Microsoft.AspNetCore.Server.Kestrel
4 | Newtonsoft.Json
--------------------------------------------------------------------------------
/src/Giraffe.JsonTherapy/paket.references:
--------------------------------------------------------------------------------
1 | FSharp.Core
2 | SourceLink.Create.CommandLine
3 | Giraffe
4 | Newtonsoft.Json
5 |
--------------------------------------------------------------------------------
/tests/Giraffe.JsonTherapy.Tests/paket.references:
--------------------------------------------------------------------------------
1 | Expecto
2 | FSharp.Core
3 | dotnet-mono
4 | Microsoft.AspNetCore.Http
5 | Microsoft.AspNetCore.TestHost
--------------------------------------------------------------------------------
/RELEASE_NOTES.md:
--------------------------------------------------------------------------------
1 | #### 2.0.0 - 2021-06-03
2 | * Update to Giraffe v5 and net5
3 |
4 | #### 1.1.0 - 2019-01-28
5 | * Update nuget description
6 |
7 | #### 1.0.0 - 2019-01-28
8 | * Initial release
9 |
--------------------------------------------------------------------------------
/appveyor.yml:
--------------------------------------------------------------------------------
1 | init:
2 | - git config --global core.autocrlf input
3 | build_script:
4 | - cmd: build.cmd
5 | test: off
6 | version: 0.0.1.{build}
7 | artifacts:
8 | - path: bin
9 | name: bin
10 |
--------------------------------------------------------------------------------
/.paket/paket.exe.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/tools/tools.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netcoreapp2.0
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.config/dotnet-tools.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": 1,
3 | "isRoot": true,
4 | "tools": {
5 | "paket": {
6 | "version": "6.0.0-beta4",
7 | "commands": [
8 | "paket"
9 | ]
10 | }
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/sample/App.fsproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | net5.0
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: csharp
2 | sudo: required
3 | dist: trusty
4 |
5 | dotnet: 2.1.300
6 | mono:
7 | - 5.4.1
8 | - latest # => "stable release"
9 | - alpha
10 | - beta
11 | - weekly # => "latest commits"
12 | os:
13 | - linux
14 |
15 | addons:
16 | apt:
17 | packages:
18 | - dotnet-sharedframework-microsoft.netcore.app-1.1.2
19 |
20 | script:
21 | - ./build.sh
22 |
23 | matrix:
24 | fast_finish: true
25 | allow_failures:
26 | - mono: latest
27 | - mono: alpha
28 | - mono: beta
29 | - mono: weekly
30 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # EditorConfig is awesome:
2 | http://EditorConfig.org
3 |
4 | # top-most EditorConfig file
5 | root = true
6 |
7 | # Default settings:
8 | # A newline ending every file
9 | # Use 4 spaces as indentation
10 | [*]
11 | insert_final_newline = true
12 | indent_style = space
13 | indent_size = 4
14 |
15 | [*.{fs,fsi,fsx,config}]
16 | charset = utf-8
17 | trim_trailing_whitespace = true
18 |
19 | [paket.*]
20 | trim_trailing_whitespace = true
21 | indent_size = 2
22 |
23 | [*.paket.references]
24 | trim_trailing_whitespace = true
25 | indent_size = 2
26 |
--------------------------------------------------------------------------------
/tests/Giraffe.JsonTherapy.Tests/Giraffe.JsonTherapy.Tests.fsproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | net5.0
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/paket.dependencies:
--------------------------------------------------------------------------------
1 | source https://api.nuget.org/v3/index.json
2 | storage: none
3 | clitool dotnet-mono 0.5.4
4 | clitool dotnet-sourcelink 2.8.0
5 | nuget Argu
6 | nuget FSharp.Core
7 | nuget Expecto
8 | nuget Serilog
9 | nuget Serilog.Sinks.Console
10 | nuget Serilog.Sinks.TestCorrelator
11 | nuget SourceLink.Create.CommandLine 2.8.0 copy_local: true
12 | nuget Giraffe
13 | nuget Microsoft.AspNetCore.Server.Kestrel
14 | nuget Microsoft.AspNetCore.Hosting
15 | nuget Microsoft.AspNetCore.TestHost
16 | nuget Microsoft.AspNetCore.Http
17 | nuget Newtonsoft.Json
18 |
19 | group Build
20 | framework: >= net45
21 | source https://api.nuget.org/v3/index.json
22 | nuget FAKE
23 | github fsharp/FAKE modules/Octokit/Octokit.fsx
24 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | ## Description
2 |
3 | Please insert a description of your problem or question.
4 |
5 | ## Error messages, screenshots
6 |
7 | Please add any error logs or screenshots if available.
8 |
9 | ## Failing test, failing github repo, or reproduction steps
10 |
11 | Please add either a failing test, a github repo of the problem or detailed reproduction steps.
12 |
13 | ## Expected Behavior
14 |
15 | Please define what you would expect the behavior to be like.
16 |
17 | ## Known workarounds
18 |
19 | Please provide a description of any known workarounds.
20 |
21 | ## Other information
22 |
23 | * Operating System:
24 | - [ ] windows [insert version here]
25 | - [ ] macOs [insert version]
26 | - [ ] linux [insert flavor/version here]
27 | * Platform
28 | - [ ] dotnet core
29 | - [ ] dotnet full
30 | - [ ] mono
31 | * Branch or release version:
32 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) [year] [fullname]
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/src/Giraffe.JsonTherapy/AssemblyInfo.fs:
--------------------------------------------------------------------------------
1 | // Auto-Generated by FAKE; do not edit
2 | namespace System
3 | open System.Reflection
4 |
5 | []
6 | []
7 | []
8 | []
9 | []
10 | []
11 | []
12 | []
13 | do ()
14 |
15 | module internal AssemblyVersionInformation =
16 | let [] AssemblyTitle = "Giraffe.JsonTherapy"
17 | let [] AssemblyProduct = "Giraffe.JsonTherapy"
18 | let [] AssemblyVersion = "2.0.0"
19 | let [] AssemblyMetadata_ReleaseDate = "2021-06-03T00:00:00.0000000"
20 | let [] AssemblyFileVersion = "2.0.0"
21 | let [] AssemblyInformationalVersion = "2.0.0"
22 | let [] AssemblyMetadata_ReleaseChannel = "release"
23 | let [] AssemblyMetadata_GitHash = "4f637925b64e30adbf7250ffb793e87ae274b407"
24 |
--------------------------------------------------------------------------------
/tests/Giraffe.JsonTherapy.Tests/Main.fs:
--------------------------------------------------------------------------------
1 | module ExpectoTemplate
2 | open Expecto
3 | open System.Reflection
4 |
5 |
6 | module AssemblyInfo =
7 |
8 | let metaDataValue (mda : AssemblyMetadataAttribute) = mda.Value
9 | let getMetaDataAttribute (assembly : Assembly) key =
10 | assembly.GetCustomAttributes(typedefof)
11 | |> Seq.cast
12 | |> Seq.find(fun x -> x.Key = key)
13 |
14 | let getReleaseDate assembly =
15 | "ReleaseDate"
16 | |> getMetaDataAttribute assembly
17 | |> metaDataValue
18 |
19 | let getGitHash assembly =
20 | "GitHash"
21 | |> getMetaDataAttribute assembly
22 | |> metaDataValue
23 |
24 | []
25 | let main argv =
26 | if argv |> Seq.contains ("--version") then
27 | let assembly = Assembly.GetEntryAssembly()
28 | let name = assembly.GetName()
29 | let version = assembly.GetName().Version
30 | let releaseDate = AssemblyInfo.getReleaseDate assembly
31 | let githash = AssemblyInfo.getGitHash assembly
32 | printfn "%s - %A - %s - %s" name.Name version releaseDate githash
33 |
34 | Tests.runTestsInAssembly defaultConfig argv
--------------------------------------------------------------------------------
/sample/Program.fs:
--------------------------------------------------------------------------------
1 | open System
2 | open System.Threading.Tasks
3 | open Giraffe
4 | open Giraffe.JsonTherapy
5 | open FSharp.Control.Tasks
6 | open Microsoft.AspNetCore.Http
7 | open Microsoft.AspNetCore.Hosting
8 | open Microsoft.AspNetCore.Builder
9 | open Microsoft.AspNetCore.Server.Kestrel
10 | open Microsoft.Extensions.Logging
11 | open Microsoft.Extensions.DependencyInjection
12 |
13 | type Maybe<'a> =
14 | | Nothing
15 | | Just of 'a
16 |
17 | type Rec = { First: string; Job: Maybe }
18 |
19 | let webApp : HttpHandler =
20 | choose [ GET >=> route "/index" >=> text "Index"
21 | POST >=> route "/echo" >=> text "Echo" ]
22 |
23 | type Startup() =
24 | member __.ConfigureServices (services : IServiceCollection) =
25 | // Register default Giraffe dependencies
26 | services.AddGiraffe() |> ignore
27 |
28 | member __.Configure (app : IApplicationBuilder)
29 | (env : IHostingEnvironment)
30 | (loggerFactory : ILoggerFactory) =
31 | // Add Giraffe to the ASP.NET Core pipeline
32 | app.UseGiraffe webApp
33 |
34 | []
35 | let main _ =
36 | WebHostBuilder()
37 | .UseKestrel()
38 | .UseStartup()
39 | .Build()
40 | .Run()
41 | 0
42 |
--------------------------------------------------------------------------------
/tests/Giraffe.JsonTherapy.Tests/AssemblyInfo.fs:
--------------------------------------------------------------------------------
1 | // Auto-Generated by FAKE; do not edit
2 | namespace System
3 | open System.Reflection
4 |
5 | []
6 | []
7 | []
8 | []
9 | []
10 | []
11 | []
12 | []
13 | do ()
14 |
15 | module internal AssemblyVersionInformation =
16 | let [] AssemblyTitle = "Giraffe.JsonTherapy.Tests"
17 | let [] AssemblyProduct = "Giraffe.JsonTherapy"
18 | let [] AssemblyVersion = "2.0.0"
19 | let [] AssemblyMetadata_ReleaseDate = "2021-06-03T00:00:00.0000000"
20 | let [] AssemblyFileVersion = "2.0.0"
21 | let [] AssemblyInformationalVersion = "2.0.0"
22 | let [] AssemblyMetadata_ReleaseChannel = "release"
23 | let [] AssemblyMetadata_GitHash = "4f637925b64e30adbf7250ffb793e87ae274b407"
24 |
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | ## Proposed Changes
2 |
3 | Describe the big picture of your changes here to communicate to the maintainers why we should accept this pull request. If it fixes a bug or resolves a feature request, be sure to link to that issue.
4 |
5 | ## Types of changes
6 |
7 | What types of changes does your code introduce to suave_serilog?
8 | _Put an `x` in the boxes that apply_
9 |
10 | - [ ] Bugfix (non-breaking change which fixes an issue)
11 | - [ ] New feature (non-breaking change which adds functionality)
12 | - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
13 |
14 |
15 | ## Checklist
16 |
17 | _Put an `x` in the boxes that apply. You can also fill these out after creating the PR. If you're unsure about any of them, don't hesitate to ask. We're here to help! This is simply a reminder of what we are going to look for before merging your code._
18 |
19 | - [ ] Build and tests pass locally
20 | - [ ] I have added tests that prove my fix is effective or that my feature works (if appropriate)
21 | - [ ] I have added necessary documentation (if appropriate)
22 |
23 | ## Further comments
24 |
25 | If this is a relatively large or complex change, kick off the discussion by explaining why you chose the solution you did and what alternatives you considered, etc...
26 |
--------------------------------------------------------------------------------
/src/Giraffe.JsonTherapy/Giraffe.JsonTherapy.fsproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net5.0
5 | true
6 |
7 |
8 | Giraffe.JsonTherapy
9 | Alternative JSON model binding mechanism for Giraffe apps without definting intermediate types
10 | f#, fsharp, giraffe, json
11 | https://github.com/zaid-ajaj/Giraffe.JsonTherapy
12 | https://github.com/zaid-ajaj/Giraffe.JsonTherapy/blob/master/LICENSE
13 | false
14 | git
15 | Zaid Ajaj
16 | https://github.com/zaid-ajaj/Giraffe.JsonTherapy
17 |
18 |
19 | true
20 | true
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | // Use IntelliSense to learn about possible attributes.
3 | // Hover to view descriptions of existing attributes.
4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5 | "version": "0.2.0",
6 | "configurations": [
7 | {
8 | "name": ".NET Core Launch (console)",
9 | "type": "coreclr",
10 | "request": "launch",
11 | "program": "${workspaceFolder}/tests/Suave.Serilog.Tests/bin/Debug/netstandard2.0/Suave.Serilog.Tests.dll",
12 | "args": [],
13 | "cwd": "${workspaceFolder}",
14 | "console": "internalConsole",
15 | "stopAtEntry": false,
16 | "internalConsoleOptions": "openOnSessionStart"
17 | },
18 | {
19 | "name": ".NET Core Launch (web)",
20 | "type": "coreclr",
21 | "request": "launch",
22 | "preLaunchTask": "build",
23 | "program": "${workspaceFolder}/bin/Debug//.dll",
24 | "args": [],
25 | "cwd": "${workspaceFolder}",
26 | "stopAtEntry": false,
27 | "internalConsoleOptions": "openOnSessionStart",
28 | "launchBrowser": {
29 | "enabled": true,
30 | "args": "${auto-detect-url}",
31 | "windows": {
32 | "command": "cmd.exe",
33 | "args": "/C start ${auto-detect-url}"
34 | },
35 | "osx": {
36 | "command": "open"
37 | },
38 | "linux": {
39 | "command": "xdg-open"
40 | }
41 | },
42 | "env": {
43 | "ASPNETCORE_ENVIRONMENT": "Development"
44 | },
45 | "sourceFileMap": {
46 | "/Views": "${workspaceFolder}/Views"
47 | }
48 | },
49 | {
50 | "name": ".NET Core Attach",
51 | "type": "coreclr",
52 | "request": "attach",
53 | "processId": "${command:pickProcess}"
54 | }
55 | ]
56 | }
--------------------------------------------------------------------------------
/.paket/paket.targets:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | true
7 | $(MSBuildThisFileDirectory)
8 | $(MSBuildThisFileDirectory)..\
9 | /Library/Frameworks/Mono.framework/Commands/mono
10 | mono
11 |
12 |
13 |
14 |
15 | $(PaketRootPath)paket.exe
16 | $(PaketToolsPath)paket.exe
17 | "$(PaketExePath)"
18 | $(MonoPath) --runtime=v4.0.30319 "$(PaketExePath)"
19 |
20 |
21 |
22 |
23 |
24 | $(MSBuildProjectFullPath).paket.references
25 |
26 |
27 |
28 |
29 | $(MSBuildProjectDirectory)\$(MSBuildProjectName).paket.references
30 |
31 |
32 |
33 |
34 | $(MSBuildProjectDirectory)\paket.references
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 | $(MSBuildProjectDirectory)\$(MSBuildProjectName).paket.references
47 | $(MSBuildProjectDirectory)\paket.references
48 | $(MSBuildStartupDirectory)\paket.references
49 | $(MSBuildProjectFullPath).paket.references
50 | $(PaketCommand) restore --references-files "$(PaketReferences)"
51 |
52 | RestorePackages; $(BuildDependsOn);
53 |
54 |
55 |
61 |
62 |
63 |
--------------------------------------------------------------------------------
/Giraffe.JsonTherapy.sln:
--------------------------------------------------------------------------------
1 | Microsoft Visual Studio Solution File, Format Version 12.00
2 | # Visual Studio 15
3 | VisualStudioVersion = 15.0.26124.0
4 | MinimumVisualStudioVersion = 15.0.26124.0
5 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{C397A34C-84F1-49E7-AEBC-2F9F2B196216}"
6 | EndProject
7 | Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "Giraffe.JsonTherapy", "src\Giraffe.JsonTherapy\Giraffe.JsonTherapy.fsproj", "{5D30E174-2538-47AC-8443-318C8C5DC2C9}"
8 | EndProject
9 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{ACBEE43C-7A88-4FB1-9B06-DB064D22B29F}"
10 | EndProject
11 | Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "Giraffe.JsonTherapy.Tests", "tests\Giraffe.JsonTherapy.Tests\Giraffe.JsonTherapy.Tests.fsproj", "{1CA2E092-2320-451D-A4F0-9ED7C7C528CA}"
12 | EndProject
13 | Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "App", "sample\App.fsproj", "{F0C5FD47-AEFF-44A8-BB24-9F38A3CECCB9}"
14 | EndProject
15 | Global
16 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
17 | Debug|Any CPU = Debug|Any CPU
18 | Debug|x64 = Debug|x64
19 | Debug|x86 = Debug|x86
20 | Release|Any CPU = Release|Any CPU
21 | Release|x64 = Release|x64
22 | Release|x86 = Release|x86
23 | EndGlobalSection
24 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
25 | {5D30E174-2538-47AC-8443-318C8C5DC2C9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
26 | {5D30E174-2538-47AC-8443-318C8C5DC2C9}.Debug|Any CPU.Build.0 = Debug|Any CPU
27 | {5D30E174-2538-47AC-8443-318C8C5DC2C9}.Debug|x64.ActiveCfg = Debug|Any CPU
28 | {5D30E174-2538-47AC-8443-318C8C5DC2C9}.Debug|x64.Build.0 = Debug|Any CPU
29 | {5D30E174-2538-47AC-8443-318C8C5DC2C9}.Debug|x86.ActiveCfg = Debug|Any CPU
30 | {5D30E174-2538-47AC-8443-318C8C5DC2C9}.Debug|x86.Build.0 = Debug|Any CPU
31 | {5D30E174-2538-47AC-8443-318C8C5DC2C9}.Release|Any CPU.ActiveCfg = Release|Any CPU
32 | {5D30E174-2538-47AC-8443-318C8C5DC2C9}.Release|Any CPU.Build.0 = Release|Any CPU
33 | {5D30E174-2538-47AC-8443-318C8C5DC2C9}.Release|x64.ActiveCfg = Release|Any CPU
34 | {5D30E174-2538-47AC-8443-318C8C5DC2C9}.Release|x64.Build.0 = Release|Any CPU
35 | {5D30E174-2538-47AC-8443-318C8C5DC2C9}.Release|x86.ActiveCfg = Release|Any CPU
36 | {5D30E174-2538-47AC-8443-318C8C5DC2C9}.Release|x86.Build.0 = Release|Any CPU
37 | {1CA2E092-2320-451D-A4F0-9ED7C7C528CA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
38 | {1CA2E092-2320-451D-A4F0-9ED7C7C528CA}.Debug|Any CPU.Build.0 = Debug|Any CPU
39 | {1CA2E092-2320-451D-A4F0-9ED7C7C528CA}.Debug|x64.ActiveCfg = Debug|Any CPU
40 | {1CA2E092-2320-451D-A4F0-9ED7C7C528CA}.Debug|x64.Build.0 = Debug|Any CPU
41 | {1CA2E092-2320-451D-A4F0-9ED7C7C528CA}.Debug|x86.ActiveCfg = Debug|Any CPU
42 | {1CA2E092-2320-451D-A4F0-9ED7C7C528CA}.Debug|x86.Build.0 = Debug|Any CPU
43 | {1CA2E092-2320-451D-A4F0-9ED7C7C528CA}.Release|Any CPU.ActiveCfg = Release|Any CPU
44 | {1CA2E092-2320-451D-A4F0-9ED7C7C528CA}.Release|Any CPU.Build.0 = Release|Any CPU
45 | {1CA2E092-2320-451D-A4F0-9ED7C7C528CA}.Release|x64.ActiveCfg = Release|Any CPU
46 | {1CA2E092-2320-451D-A4F0-9ED7C7C528CA}.Release|x64.Build.0 = Release|Any CPU
47 | {1CA2E092-2320-451D-A4F0-9ED7C7C528CA}.Release|x86.ActiveCfg = Release|Any CPU
48 | {1CA2E092-2320-451D-A4F0-9ED7C7C528CA}.Release|x86.Build.0 = Release|Any CPU
49 | {F0C5FD47-AEFF-44A8-BB24-9F38A3CECCB9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
50 | {F0C5FD47-AEFF-44A8-BB24-9F38A3CECCB9}.Debug|Any CPU.Build.0 = Debug|Any CPU
51 | {F0C5FD47-AEFF-44A8-BB24-9F38A3CECCB9}.Debug|x64.ActiveCfg = Debug|Any CPU
52 | {F0C5FD47-AEFF-44A8-BB24-9F38A3CECCB9}.Debug|x64.Build.0 = Debug|Any CPU
53 | {F0C5FD47-AEFF-44A8-BB24-9F38A3CECCB9}.Debug|x86.ActiveCfg = Debug|Any CPU
54 | {F0C5FD47-AEFF-44A8-BB24-9F38A3CECCB9}.Debug|x86.Build.0 = Debug|Any CPU
55 | {F0C5FD47-AEFF-44A8-BB24-9F38A3CECCB9}.Release|Any CPU.ActiveCfg = Release|Any CPU
56 | {F0C5FD47-AEFF-44A8-BB24-9F38A3CECCB9}.Release|Any CPU.Build.0 = Release|Any CPU
57 | {F0C5FD47-AEFF-44A8-BB24-9F38A3CECCB9}.Release|x64.ActiveCfg = Release|Any CPU
58 | {F0C5FD47-AEFF-44A8-BB24-9F38A3CECCB9}.Release|x64.Build.0 = Release|Any CPU
59 | {F0C5FD47-AEFF-44A8-BB24-9F38A3CECCB9}.Release|x86.ActiveCfg = Release|Any CPU
60 | {F0C5FD47-AEFF-44A8-BB24-9F38A3CECCB9}.Release|x86.Build.0 = Release|Any CPU
61 | EndGlobalSection
62 | GlobalSection(SolutionProperties) = preSolution
63 | HideSolutionNode = FALSE
64 | EndGlobalSection
65 | GlobalSection(NestedProjects) = preSolution
66 | {5D30E174-2538-47AC-8443-318C8C5DC2C9} = {C397A34C-84F1-49E7-AEBC-2F9F2B196216}
67 | {1CA2E092-2320-451D-A4F0-9ED7C7C528CA} = {ACBEE43C-7A88-4FB1-9B06-DB064D22B29F}
68 | EndGlobalSection
69 | GlobalSection(ExtensibilityGlobals) = postSolution
70 | SolutionGuid = {C9C98179-6748-4A52-B335-9E34076E5958}
71 | EndGlobalSection
72 | EndGlobal
73 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 |
4 | # User-specific files
5 | *.suo
6 | *.user
7 | *.userosscache
8 | *.sln.docstates
9 |
10 | # User-specific files (MonoDevelop/Xamarin Studio)
11 | *.userprefs
12 |
13 | # Build results
14 | [Dd]ebug/
15 | [Dd]ebugPublic/
16 | [Rr]elease/
17 | [Rr]eleases/
18 | x64/
19 | x86/
20 | bld/
21 | [Bb]in/
22 | [Oo]bj/
23 | [Ll]og/
24 |
25 | # Visual Studio 2015 cache/options directory
26 | .vs/
27 | # Uncomment if you have tasks that create the project's static files in wwwroot
28 | #wwwroot/
29 |
30 | # MSTest test Results
31 | [Tt]est[Rr]esult*/
32 | [Bb]uild[Ll]og.*
33 |
34 | # NUNIT
35 | *.VisualState.xml
36 | TestResult.xml
37 |
38 | # Build Results of an ATL Project
39 | [Dd]ebugPS/
40 | [Rr]eleasePS/
41 | dlldata.c
42 |
43 | # DNX
44 | project.lock.json
45 | artifacts/
46 |
47 | *_i.c
48 | *_p.c
49 | *_i.h
50 | *.ilk
51 | *.meta
52 | *.obj
53 | *.pch
54 | *.pdb
55 | *.pgc
56 | *.pgd
57 | *.rsp
58 | *.sbr
59 | *.tlb
60 | *.tli
61 | *.tlh
62 | *.tmp
63 | *.tmp_proj
64 | *.log
65 | *.vspscc
66 | *.vssscc
67 | .builds
68 | *.pidb
69 | *.svclog
70 | *.scc
71 |
72 | # Chutzpah Test files
73 | _Chutzpah*
74 |
75 | # Visual C++ cache files
76 | ipch/
77 | *.aps
78 | *.ncb
79 | *.opendb
80 | *.opensdf
81 | *.sdf
82 | *.cachefile
83 | *.VC.db
84 | *.VC.VC.opendb
85 |
86 | # Visual Studio profiler
87 | *.psess
88 | *.vsp
89 | *.vspx
90 | *.sap
91 |
92 | # TFS 2012 Local Workspace
93 | $tf/
94 |
95 | # Guidance Automation Toolkit
96 | *.gpState
97 |
98 | # ReSharper is a .NET coding add-in
99 | _ReSharper*/
100 | *.[Rr]e[Ss]harper
101 | *.DotSettings.user
102 |
103 | # JustCode is a .NET coding add-in
104 | .JustCode
105 |
106 | # TeamCity is a build add-in
107 | _TeamCity*
108 |
109 | # DotCover is a Code Coverage Tool
110 | *.dotCover
111 |
112 | # NCrunch
113 | _NCrunch_*
114 | .*crunch*.local.xml
115 | nCrunchTemp_*
116 |
117 | # MightyMoose
118 | *.mm.*
119 | AutoTest.Net/
120 |
121 | # Web workbench (sass)
122 | .sass-cache/
123 |
124 | # Installshield output folder
125 | [Ee]xpress/
126 |
127 | # DocProject is a documentation generator add-in
128 | DocProject/buildhelp/
129 | DocProject/Help/*.HxT
130 | DocProject/Help/*.HxC
131 | DocProject/Help/*.hhc
132 | DocProject/Help/*.hhk
133 | DocProject/Help/*.hhp
134 | DocProject/Help/Html2
135 | DocProject/Help/html
136 |
137 | # Click-Once directory
138 | publish/
139 |
140 | # Publish Web Output
141 | *.[Pp]ublish.xml
142 | *.azurePubxml
143 | # TODO: Comment the next line if you want to checkin your web deploy settings
144 | # but database connection strings (with potential passwords) will be unencrypted
145 | *.pubxml
146 | *.publishproj
147 |
148 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
149 | # checkin your Azure Web App publish settings, but sensitive information contained
150 | # in these scripts will be unencrypted
151 | PublishScripts/
152 |
153 | # NuGet Packages
154 | *.nupkg
155 | packages/
156 | # The packages folder can be ignored because of Package Restore
157 | **/packages/*
158 | # except build/, which is used as an MSBuild target.
159 | !**/packages/build/
160 | # Uncomment if necessary however generally it will be regenerated when needed
161 | #!**/packages/repositories.config
162 | # NuGet v3's project.json files produces more ignoreable files
163 | *.nuget.props
164 | *.nuget.targets
165 |
166 | # Microsoft Azure Build Output
167 | csx/
168 | *.build.csdef
169 |
170 | # Microsoft Azure Emulator
171 | ecf/
172 | rcf/
173 |
174 | # Windows Store app package directories and files
175 | AppPackages/
176 | BundleArtifacts/
177 | Package.StoreAssociation.xml
178 | _pkginfo.txt
179 |
180 | # Visual Studio cache files
181 | # files ending in .cache can be ignored
182 | *.[Cc]ache
183 | # but keep track of directories ending in .cache
184 | !*.[Cc]ache/
185 |
186 | # Others
187 | ClientBin/
188 | ~$*
189 | *~
190 | *.dbmdl
191 | *.dbproj.schemaview
192 | *.pfx
193 | *.publishsettings
194 | node_modules/
195 | orleans.codegen.cs
196 |
197 | # Since there are multiple workflows, uncomment next line to ignore bower_components
198 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
199 | #bower_components/
200 |
201 | # RIA/Silverlight projects
202 | Generated_Code/
203 |
204 | # Backup & report files from converting an old project file
205 | # to a newer Visual Studio version. Backup files are not needed,
206 | # because we have git ;-)
207 | _UpgradeReport_Files/
208 | Backup*/
209 | UpgradeLog*.XML
210 | UpgradeLog*.htm
211 |
212 | # SQL Server files
213 | *.mdf
214 | *.ldf
215 |
216 | # Business Intelligence projects
217 | *.rdl.data
218 | *.bim.layout
219 | *.bim_*.settings
220 |
221 | # Microsoft Fakes
222 | FakesAssemblies/
223 |
224 | # GhostDoc plugin setting file
225 | *.GhostDoc.xml
226 |
227 | # Node.js Tools for Visual Studio
228 | .ntvs_analysis.dat
229 |
230 | # Visual Studio 6 build log
231 | *.plg
232 |
233 | # Visual Studio 6 workspace options file
234 | *.opt
235 |
236 | # Visual Studio LightSwitch build output
237 | **/*.HTMLClient/GeneratedArtifacts
238 | **/*.DesktopClient/GeneratedArtifacts
239 | **/*.DesktopClient/ModelManifest.xml
240 | **/*.Server/GeneratedArtifacts
241 | **/*.Server/ModelManifest.xml
242 | _Pvt_Extensions
243 |
244 | # Paket dependency manager
245 | paket-files/
246 |
247 | # FAKE - F# Make
248 | .fake/
249 |
250 | # JetBrains Rider
251 | .idea/
252 | *.sln.iml
253 |
254 | TestResults.xml
255 |
256 | dist/
257 | .DS_Store
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Giraffe.JsonTherapy [](https://travis-ci.org/Zaid-Ajaj/Giraffe.JsonTherapy) [](https://www.nuget.org/packages/Giraffe.JsonTherapy)
2 |
3 | Simply extract JSON values from HTTP requests for [Giraffe](https://github.com/giraffe-fsharp/Giraffe) in a type-safe manner without defining intermediate types or decoders. Many times the input JSON is so simple that you just want to get the values so this library is great for rapid developement!
4 |
5 | # Install
6 | ```bash
7 | # using nuget client
8 | dotnet add package Giraffe.JsonTherapy
9 | # using Paket
10 | .paket/paket.exe add Giraffe.JsonTherapy --project path/to/Your.fsproj
11 | ```
12 |
13 | The library code is actually only a single-file: `Jsontherapy.fs` so you can add it manually to your project and modify however you want.
14 |
15 | ## Basic Usage
16 |
17 | Namespace `Giraffe.JsonTherapy` is opened in all examples below
18 |
19 | The library has two functions:
20 | - `Json.parts`
21 | - `Json.manyParts`
22 |
23 | `Json.parts` allows you to specify the path of the JSON properties and use them directly. For example, given the following JSON for a Todo item:
24 | ```json
25 | {
26 | "id": 1,
27 | "description": "Learn F#",
28 | "complete": true
29 | }
30 | ```
31 | where both `description` and `complete` are *optional*. You want to update a an existing Todo so you can read the *values* directly as follows:
32 | ```fs
33 | let webApp =
34 | PUT
35 | >=> route "/todo/update"
36 | >=> Json.parts("id", "description", "complete",
37 | // id: int -> required
38 | // desc: string option -> optional
39 | // complete: bool option -> optional
40 | fun id desc complete ->
41 | match desc, complete with
42 | // input JSON => { id, desc, complete }
43 | | Some desc, Some complete -> updateDescAndComplete id desc complete
44 | // input JSON => { id, complete }
45 | | None, Some complete -> updateJustComplete id complete
46 | // input JSON => { id, desc }
47 | | Some desc, None -> updateJustDescription id desc
48 | // input JSON => { id }
49 | | None, None -> setStatusCode 400 >=> text "Nothing to update"
50 | ```
51 | the first arguments (up to 8, the function is overloaded) of `Json.parts` are the names/paths of the JSON properties that you want to read. The last argument is a function that returns a `HttpHandler`.
52 |
53 | Because `desc` and `complete` has been infered (from usage) as optionals, then they can be omitted from the JSON whereas the `id` is infered to be `int` which means it is required to be in the input JSON:
54 |
55 | ```bash
56 | PUT /todo/update
57 |
58 | { }
59 | # 400 Bad Request { "message": "cannot find value at path 'id' in the JSON" }
60 |
61 | { id: 1 }
62 | # 400 Bad Request "Nothing to update"
63 |
64 | { id: 1, description: "updated description" }
65 | # 200 OK "Updating just description"
66 |
67 | { id: 1, complete: false }
68 | # 200 OK "Updating just complete"
69 |
70 | { id: 1, complete: false, description: "updated description" }
71 | # 200 OK "Updating both description and complete"
72 | ```
73 |
74 | ## Nested properties
75 |
76 | `Json.parts` can read nested properties by the path of the nested JSON property that you want to read. Suppose you have this JSON:
77 | ```json
78 | {
79 | "player": {
80 | "id": 1,
81 | "name": "john"
82 | },
83 | "role": "goal keeper"
84 | }
85 | ```
86 | You can read all the values as follows:
87 | ```fs
88 | POST
89 | >=> route "/player"
90 | >=> Json.parts("player.id", "player.name", "role",
91 | fun id name role ->
92 | text (sprintf "Player(%s, %d) is a %s" name id role))
93 | ```
94 | then call the end point:
95 | ```bash
96 | POST /player
97 | { "player": { "id": 1, "name": "john" }, "role": "goal keeper" }
98 |
99 | # 200 OK "Player(john, 1) is a goal keeper
100 | ```
101 |
102 | ## Arrays of objects
103 | Using `Json.manyParts` you extract the properties from objects in an array of such object in JSON, for example:
104 | ```json
105 | [
106 | { "product": "apple", "price": 1.50 },
107 | { "product": "banana", "price": 3.45 }
108 | ]
109 | ```
110 | You can read the parts as follows:
111 | ```fs
112 | POST
113 | >=> route "/total-price"
114 | >=> Json.manyParts("price",
115 | fun prices ->
116 | prices
117 | |> List.sum
118 | |> sprintf "%.2f"
119 | |> text)
120 | ```
121 | The last argument of `Json.manyParts` is a function that takes in a list of the extracted values from the input JSON value, in the last example we had one value extracted so the value `prices` was infered to be of type `float list`.
122 |
123 | ## Multiple parts from arrays of objects
124 | You can also extract multiple parts from the array objects and map them as a tuple in the last argument:
125 | ```fs
126 | POST
127 | >=> route "/product-names"
128 | >=> Json.manyParts("product", "price",
129 | fun products ->
130 | products
131 | |> List.map (fun (name, price) -> name)
132 | |> String.concat ", "
133 | |> text)
134 | ```
135 | then
136 | ```bash
137 | POST /product-names
138 | [
139 | { "product": "apple", "price": 1.50 },
140 | { "product": "banana", "price": 3.45 }
141 | ]
142 |
143 | # OK 20O "apple, banana"
144 | ```
145 |
146 | See the tests for examples, you can also open an issue if you have questions. Missing something? PR's are very much welcome!
147 |
148 | ## Builds
149 |
150 | 
151 |
152 |
153 | ### Building
154 |
155 |
156 | Make sure the following **requirements** are installed in your system:
157 |
158 | * [dotnet SDK](https://www.microsoft.com/net/download/core) 2.0 or higher
159 | * [Mono](http://www.mono-project.com/) if you're on Linux or macOS.
160 |
161 | ```
162 | > build.cmd // on windows
163 | $ ./build.sh // on unix
164 | ```
165 |
166 | ### Watch Tests
167 |
168 | The `WatchTests` target will use [dotnet-watch](https://github.com/aspnet/Docs/blob/master/aspnetcore/tutorials/dotnet-watch.md) to watch for changes in your lib or tests and re-run your tests on all `TargetFrameworks`
169 |
170 | ```
171 | ./build.sh WatchTests
172 | ```
173 |
--------------------------------------------------------------------------------
/tests/Giraffe.JsonTherapy.Tests/Tests.fs:
--------------------------------------------------------------------------------
1 | module Tests
2 |
3 | open Expecto
4 | open Giraffe
5 | open Giraffe.JsonTherapy
6 | open System
7 | open System.IO
8 | open System.Linq
9 | open System.Net.Http
10 | open System.Collections.Generic
11 | open Microsoft.AspNetCore.Hosting
12 | open Microsoft.AspNetCore.TestHost
13 | open Microsoft.AspNetCore.Builder
14 | open Microsoft.Extensions.DependencyInjection
15 | open System.Net
16 |
17 | let updateDescription (id: int) (desc: string) =
18 | text "Updating just the description"
19 |
20 | let updateComplete (id: int) (complete: bool) =
21 | text "updating todo complete"
22 |
23 | let updateDescAndComplete (id: int) (desc: string) (complete: bool) =
24 | text "updating description and complete"
25 |
26 | let testWebApp : HttpHandler =
27 | choose [
28 | GET >=> route "/" >=> text "Index"
29 | PUT
30 | >=> route "/todo/update"
31 | >=> Json.parts("id", "description", "complete",
32 | // id: int -> required
33 | // desc: string option -> optional
34 | // complete: bool option -> optional
35 | fun id desc complete ->
36 | match desc, complete with
37 | // input JSON => { id, desc, complete }
38 | | Some desc, Some complete -> updateDescAndComplete id desc complete
39 | // input JSON => { id, complete }
40 | | None, Some complete -> updateComplete id complete
41 | // input JSON => { id, desc }
42 | | Some desc, None -> updateDescription id desc
43 | // input JSON => { id }
44 | | None, None -> setStatusCode 400 >=> text "Nothing to update"
45 | )
46 |
47 | setStatusCode 404 >=> text "Not Found"
48 | ]
49 |
50 | let pass() = Expect.isTrue true "Passed"
51 | let fail() = Expect.isTrue false "Failed"
52 |
53 | let rnd = System.Random()
54 |
55 | let appBuilder (app: IApplicationBuilder) =
56 | app.UseGiraffe testWebApp
57 |
58 | let configureServices (services: IServiceCollection) =
59 | services.AddGiraffe()
60 | |> ignore
61 |
62 | let createHost() =
63 | WebHostBuilder()
64 | .UseContentRoot(Directory.GetCurrentDirectory())
65 | .Configure(Action (appBuilder))
66 | .ConfigureServices(Action configureServices)
67 |
68 | let withClientFor (webApp: HttpHandler) (map: HttpClient -> unit) =
69 | let host =
70 | WebHostBuilder()
71 | .UseContentRoot(Directory.GetCurrentDirectory())
72 | .Configure(Action (fun app -> app.UseGiraffe webApp))
73 | .ConfigureServices(Action configureServices)
74 |
75 | use server = new TestServer(host)
76 | use client = server.CreateClient()
77 | map client
78 |
79 | let runTask task =
80 | task
81 | |> Async.AwaitTask
82 | |> Async.RunSynchronously
83 |
84 | let httpGet (path : string) (client : HttpClient) =
85 | path
86 | |> client.GetAsync
87 | |> runTask
88 |
89 | let put (path: string) (content: string) (client: HttpClient) =
90 | let httpContent = new StringContent(content)
91 | client.PutAsync(path, httpContent)
92 | |> runTask
93 |
94 | let post (path: string) (content: string) (client: HttpClient) =
95 | let httpContent = new StringContent(content)
96 | client.PostAsync(path, httpContent)
97 | |> runTask
98 |
99 | let isStatus (code : HttpStatusCode) (response : HttpResponseMessage) =
100 | Expect.equal response.StatusCode code "Status code is wrong"
101 | response
102 |
103 | let ensureSuccess (response : HttpResponseMessage) =
104 | if not response.IsSuccessStatusCode
105 | then response.Content.ReadAsStringAsync() |> runTask |> failwithf "%A"
106 | else response
107 |
108 | let readText (response : HttpResponseMessage) =
109 | response.Content.ReadAsStringAsync()
110 | |> runTask
111 |
112 | let readTextEqual content (response : HttpResponseMessage) =
113 | response.Content.ReadAsStringAsync()
114 | |> runTask
115 | |> fun result -> Expect.equal result content "The expected and actual response content are not equal"
116 |
117 |
118 | []
119 | let tests =
120 | testList "Giraffe.JsonTherapy" [
121 | testCase "Nested properties can be extracted from JSON" <| fun _ ->
122 | let inputJson = """ { "player": { "id": 1, "name": "john" } } """
123 | let playerId = "player.id"
124 | let playerName = "player.name"
125 | let json = Extensions.parse inputJson
126 | match Extensions.readPath (Extensions.getJPath playerId) json with
127 | | Some (JNumber 1.0) -> pass()
128 | | otherwise -> fail()
129 |
130 | match Extensions.readPath (Extensions.getJPath playerName) json with
131 | | Some (JString "john") -> pass()
132 | | otherwise -> fail()
133 |
134 | testCase "Root path / returns 'Index' as text" <| fun _ ->
135 | use server = new TestServer(createHost())
136 | use client = server.CreateClient()
137 |
138 | client
139 | |> httpGet "/"
140 | |> isStatus HttpStatusCode.OK
141 | |> readTextEqual "Index"
142 |
143 | testCase "Unknown path returns status 404 not found " <| fun _ ->
144 | use server = new TestServer(createHost())
145 | use client = server.CreateClient()
146 |
147 | client
148 | |> httpGet "/non-existent-path"
149 | |> isStatus HttpStatusCode.NotFound
150 | |> readTextEqual "Not Found"
151 |
152 | testCase "Basic use cases" <| fun _ ->
153 | use server = new TestServer(createHost())
154 | use client = server.CreateClient()
155 |
156 | client
157 | |> put "/todo/update" "{ \"id\": 1 }"
158 | |> isStatus HttpStatusCode.BadRequest
159 | |> readTextEqual "Nothing to update"
160 |
161 | client
162 | |> put "/todo/update" "{ \"id\": 1, \"description\": \"description\" }"
163 | |> ensureSuccess
164 | |> readTextEqual "Updating just the description"
165 |
166 | client
167 | |> put "/todo/update" "{ \"id\": 1, \"complete\": true }"
168 | |> ensureSuccess
169 | |> readTextEqual "updating todo complete"
170 |
171 | client
172 | |> put "/todo/update" "{ \"id\": 1, \"complete\": true, \"description\": \"description\" }"
173 | |> ensureSuccess
174 | |> readTextEqual "updating description and complete"
175 |
176 | testCase "Nested properties can be extracted" <| fun _ ->
177 | let webApp =
178 | POST
179 | >=> route "/extract"
180 | >=> Json.parts("player.id", "player.name", "role",
181 | fun id name role -> text (sprintf "Player(%d, %s) is a %s" id name role))
182 |
183 | withClientFor webApp <| fun client ->
184 | let inputJson = """ { "player": { "id": 1, "name": "john" }, "role": "goal keeper" } """
185 | client
186 | |> post "/extract" inputJson
187 | |> readTextEqual "Player(1, john) is a goal keeper"
188 |
189 | testCase "Json.manyParts works for single paramters" <| fun _ ->
190 | let webApp =
191 | POST
192 | >=> route "/extract"
193 | >=> Json.manyParts("value", List.sum >> sprintf "%d" >> text)
194 |
195 | withClientFor webApp (post "/extract" "[{ \"value\": 10 }, { \"value\": 5 }]" >> readTextEqual "15")
196 |
197 | testCase "Json.manyParts works for single paramters with nested properties" <| fun _ ->
198 | let webApp =
199 | POST
200 | >=> route "/extract"
201 | >=> Json.manyParts("value.role", String.concat ", " >> text)
202 |
203 | withClientFor webApp (post "/extract" "[{ \"value\": { \"role\": \"admin\" } }, { \"value\": { \"role\": \"user\" } }]" >> readTextEqual "admin, user")
204 |
205 | testCase "Json.parts works with simple use cases" <| fun _ ->
206 | let webApp =
207 | POST
208 | >=> route "/extract"
209 | >=> Json.parts("value", "id", fun value id -> text (sprintf "Value(%s) = %d" value id))
210 |
211 | withClientFor webApp (post "/extract" "{ \"value\": \"one\", \"id\": 1 }" >> readTextEqual "Value(one) = 1")
212 |
213 | testCase "Json.manyParts works with multi parameters" <| fun _ ->
214 | let webApp =
215 | POST
216 | >=> route "/extract"
217 | >=> Json.manyParts("value", "id", function
218 | | [ ("hello", 1); ("there", 2) ] -> text "pass"
219 | | otheriwse -> failwith "does not work")
220 |
221 | withClientFor webApp (post "/extract" "[{ \"value\": \"hello\", \"id\": 1 }, { \"value\": \"there\", \"id\": 2 }]" >> readTextEqual "pass")
222 |
223 | testCase "Reading floats works" <| fun _ ->
224 | let webApp =
225 | POST
226 | >=> route "/extract"
227 | >=> Json.manyParts("price",
228 | fun prices ->
229 | prices
230 | |> List.sum
231 | |> sprintf "%.2f"
232 | |> text)
233 |
234 | withClientFor webApp <| fun client ->
235 | client
236 | |> post "/extract" "[{\"price\":1.5 },{\"price\":3.5}]"
237 | |> readTextEqual "5.00"
238 |
239 | testCase "Null string in JSON becomes None" <| fun _ ->
240 | let webApp =
241 | POST
242 | >=> route "/extract"
243 | >=> Json.parts("name",
244 | function
245 | | None -> text "Name was null or missing"
246 | | Some name -> text name)
247 |
248 | withClientFor webApp (post "/extract" "{ \"name\": null }" >> readTextEqual "Name was null or missing")
249 | withClientFor webApp (post "/extract" "{ \"name\": \"non-empty\" }" >> readTextEqual "non-empty")
250 | withClientFor webApp (post "/extract" "{ }" >> readTextEqual "Name was null or missing" )
251 |
252 | testCase "array of strings can be extracted" <| fun _ ->
253 | let webApp = Json.parts("roles", Seq.ofArray >> String.concat ", " >> text)
254 | withClientFor webApp (post "/" "{ \"roles\": [\"one\", \"two\"] }" >> readTextEqual "one, two")
255 |
256 | testCase "list of strings can be extracted" <| fun _ ->
257 | let webApp = Json.parts("roles", Seq.ofList >> String.concat ", " >> text)
258 | withClientFor webApp (post "/" "{ \"roles\": [\"one\", \"two\"] }" >> readTextEqual "one, two")
259 |
260 | testCase "list of ints can be extracted" <| fun _ ->
261 | let webApp = Json.parts("numbers", Seq.ofList >> Seq.sum >> sprintf "%d" >> text)
262 | withClientFor webApp (post "/" "{ \"numbers\": [1,2,3,4,5] }" >> readTextEqual "15")
263 | ]
264 |
--------------------------------------------------------------------------------
/.paket/Paket.Restore.targets:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | $(MSBuildAllProjects);$(MSBuildThisFileFullPath)
8 |
9 | $(MSBuildVersion)
10 | 15.0.0
11 | false
12 | true
13 |
14 | true
15 | $(MSBuildThisFileDirectory)
16 | $(MSBuildThisFileDirectory)..\
17 | $(PaketRootPath)paket-files\paket.restore.cached
18 | $(PaketRootPath)paket.lock
19 | classic
20 | proj
21 | assembly
22 | native
23 | /Library/Frameworks/Mono.framework/Commands/mono
24 | mono
25 |
26 |
27 | $(PaketRootPath)paket.bootstrapper.exe
28 | $(PaketToolsPath)paket.bootstrapper.exe
29 | $([System.IO.Path]::GetDirectoryName("$(PaketBootStrapperExePath)"))\
30 |
31 | "$(PaketBootStrapperExePath)"
32 | $(MonoPath) --runtime=v4.0.30319 "$(PaketBootStrapperExePath)"
33 |
34 |
35 |
36 |
37 | true
38 | true
39 |
40 |
41 | True
42 |
43 |
44 | False
45 |
46 | $(BaseIntermediateOutputPath.TrimEnd('\').TrimEnd('\/'))
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 | $(PaketRootPath)paket
56 | $(PaketToolsPath)paket
57 |
58 |
59 |
60 |
61 |
62 | $(PaketRootPath)paket.exe
63 | $(PaketToolsPath)paket.exe
64 |
65 |
66 |
67 |
68 |
69 | <_DotnetToolsJson Condition="Exists('$(PaketRootPath)/.config/dotnet-tools.json')">$([System.IO.File]::ReadAllText("$(PaketRootPath)/.config/dotnet-tools.json"))
70 | <_ConfigContainsPaket Condition=" '$(_DotnetToolsJson)' != ''">$(_DotnetToolsJson.Contains('"paket"'))
71 | <_ConfigContainsPaket Condition=" '$(_ConfigContainsPaket)' == ''">false
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 | <_PaketCommand>dotnet paket
83 |
84 |
85 |
86 |
87 |
88 | $(PaketToolsPath)paket
89 | $(PaketBootStrapperExeDir)paket
90 |
91 |
92 | paket
93 |
94 |
95 |
96 |
97 | <_PaketExeExtension>$([System.IO.Path]::GetExtension("$(PaketExePath)"))
98 | <_PaketCommand Condition=" '$(_PaketCommand)' == '' AND '$(_PaketExeExtension)' == '.dll' ">dotnet "$(PaketExePath)"
99 | <_PaketCommand Condition=" '$(_PaketCommand)' == '' AND '$(OS)' != 'Windows_NT' AND '$(_PaketExeExtension)' == '.exe' ">$(MonoPath) --runtime=v4.0.30319 "$(PaketExePath)"
100 | <_PaketCommand Condition=" '$(_PaketCommand)' == '' ">"$(PaketExePath)"
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 | true
122 | $(NoWarn);NU1603;NU1604;NU1605;NU1608
123 | false
124 | true
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 | $([System.IO.File]::ReadAllText('$(PaketRestoreCacheFile)'))
134 |
135 |
136 |
137 |
138 |
139 |
141 | $([System.Text.RegularExpressions.Regex]::Split(`%(Identity)`, `": "`)[0].Replace(`"`, ``).Replace(` `, ``))
142 | $([System.Text.RegularExpressions.Regex]::Split(`%(Identity)`, `": "`)[1].Replace(`"`, ``).Replace(` `, ``))
143 |
144 |
145 |
146 |
147 | %(PaketRestoreCachedKeyValue.Value)
148 | %(PaketRestoreCachedKeyValue.Value)
149 |
150 |
151 |
152 |
153 | true
154 | false
155 | true
156 |
157 |
158 |
162 |
163 | true
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 | $(PaketIntermediateOutputPath)\$(MSBuildProjectFile).paket.references.cached
183 |
184 | $(MSBuildProjectFullPath).paket.references
185 |
186 | $(MSBuildProjectDirectory)\$(MSBuildProjectName).paket.references
187 |
188 | $(MSBuildProjectDirectory)\paket.references
189 |
190 | false
191 | true
192 | true
193 | references-file-or-cache-not-found
194 |
195 |
196 |
197 |
198 | $([System.IO.File]::ReadAllText('$(PaketReferencesCachedFilePath)'))
199 | $([System.IO.File]::ReadAllText('$(PaketOriginalReferencesFilePath)'))
200 | references-file
201 | false
202 |
203 |
204 |
205 |
206 | false
207 |
208 |
209 |
210 |
211 | true
212 | target-framework '$(TargetFramework)' or '$(TargetFrameworks)' files @(PaketResolvedFilePaths)
213 |
214 |
215 |
216 |
217 |
218 |
219 |
220 |
221 |
222 |
223 | false
224 | true
225 |
226 |
227 |
228 |
229 |
230 |
231 |
232 |
233 |
234 |
235 | $([System.String]::Copy('%(PaketReferencesFileLines.Identity)').Split(',').Length)
236 | $([System.String]::Copy('%(PaketReferencesFileLines.Identity)').Split(',')[0])
237 | $([System.String]::Copy('%(PaketReferencesFileLines.Identity)').Split(',')[1])
238 | $([System.String]::Copy('%(PaketReferencesFileLines.Identity)').Split(',')[4])
239 | $([System.String]::Copy('%(PaketReferencesFileLines.Identity)').Split(',')[5])
240 |
241 |
242 | %(PaketReferencesFileLinesInfo.PackageVersion)
243 | All
244 | runtime
245 | runtime
246 | true
247 | true
248 |
249 |
250 |
251 |
252 | $(PaketIntermediateOutputPath)/$(MSBuildProjectFile).paket.clitools
253 |
254 |
255 |
256 |
257 |
258 |
259 |
260 |
261 | $([System.String]::Copy('%(PaketCliToolFileLines.Identity)').Split(',')[0])
262 | $([System.String]::Copy('%(PaketCliToolFileLines.Identity)').Split(',')[1])
263 |
264 |
265 | %(PaketCliToolFileLinesInfo.PackageVersion)
266 |
267 |
268 |
269 |
273 |
274 |
275 |
276 |
277 |
278 | false
279 |
280 |
281 |
282 |
283 |
284 | <_NuspecFilesNewLocation Include="$(PaketIntermediateOutputPath)\$(Configuration)\*.nuspec"/>
285 |
286 |
287 |
288 |
289 |
290 | $(MSBuildProjectDirectory)/$(MSBuildProjectFile)
291 | true
292 | false
293 | true
294 | false
295 | true
296 | false
297 | true
298 | false
299 | true
300 | $(PaketIntermediateOutputPath)\$(Configuration)
301 | $(PaketIntermediateOutputPath)
302 |
303 |
304 |
305 | <_NuspecFiles Include="$(AdjustedNuspecOutputPath)\*.$(PackageVersion.Split(`+`)[0]).nuspec"/>
306 |
307 |
308 |
309 |
310 |
311 |
312 |
313 |
314 |
315 |
316 |
317 |
363 |
364 |
407 |
408 |
450 |
451 |
492 |
493 |
494 |
495 |
--------------------------------------------------------------------------------
/src/Giraffe.JsonTherapy/JsonTherapy.fs:
--------------------------------------------------------------------------------
1 | namespace Giraffe.JsonTherapy
2 |
3 | open System
4 | open Giraffe
5 | open Microsoft.AspNetCore.Http
6 | open Newtonsoft.Json.Linq
7 | open System.Collections.Generic
8 | open System.IO
9 | open Microsoft.Extensions.Primitives
10 | open System.Threading.Tasks
11 | open FSharp.Control.Tasks
12 |
13 | []
14 | module Types =
15 |
16 | /// A type representing Javascript Object Notation
17 | type Json =
18 | | JNumber of float
19 | | JString of string
20 | | JBool of bool
21 | | JNull
22 | | JArray of Json list
23 | | JObject of Map
24 |
25 | []
26 | module Extensions =
27 | open Newtonsoft.Json
28 |
29 | let internal request (requestMap : HttpRequest -> Task) : HttpHandler =
30 | fun (next : HttpFunc) (ctx : HttpContext) ->
31 | task {
32 | let! createdHandler = requestMap ctx.Request
33 | return! createdHandler next ctx
34 | }
35 |
36 | let internal isOption (typeInfo: Type) = typeInfo.FullName.StartsWith("Microsoft.FSharp.Core.FSharpOption`1")
37 |
38 | /// Parses the input string as structured JSON
39 | let parse (input: string) =
40 | let settings = JsonSerializerSettings(DateParseHandling = DateParseHandling.None)
41 | let token = JsonConvert.DeserializeObject(input, settings)
42 | let rec fromJToken (token: JToken) =
43 | match token.Type with
44 | | JTokenType.Float -> JNumber (token.Value())
45 | | JTokenType.Integer -> JNumber (token.Value())
46 | | JTokenType.Boolean -> JBool (token.Value())
47 | | JTokenType.String -> JString (token.Value())
48 | | JTokenType.Guid -> JString (token.Value().ToString())
49 | | JTokenType.Null -> JNull
50 | | JTokenType.Array ->
51 | token.Values()
52 | |> Seq.map fromJToken
53 | |> List.ofSeq
54 | |> Json.JArray
55 | | JTokenType.Object ->
56 | token.Value>()
57 | |> Seq.map (fun pair -> pair.Key, fromJToken pair.Value)
58 | |> List.ofSeq
59 | |> Map.ofList
60 | |> Json.JObject
61 | | _ -> failwithf "JSON token type '%s' was not recognised" (token.Type.ToString())
62 |
63 | fromJToken token
64 |
65 | let rec convertJToken = function
66 | | JNull -> JValue.CreateNull() :> JToken
67 | | JBool value -> JToken.op_Implicit(value)
68 | | JString value -> JToken.op_Implicit(value)
69 | | JNumber value -> JToken.op_Implicit(value)
70 | | JArray values ->
71 | let output = Newtonsoft.Json.Linq.JArray()
72 | for value in values do
73 | output.Add(convertJToken value)
74 | output :> JToken
75 | | JObject dict ->
76 | let output = Newtonsoft.Json.Linq.JObject()
77 | for (key, value) in Map.toSeq dict do
78 | output.Add(JProperty(key, convertJToken value))
79 | output :> JToken
80 |
81 | /// Tries to parse the input string as structured data
82 | let tryParse input =
83 | try Ok (parse input)
84 | with | ex -> Error ex.Message
85 |
86 | let getJPath (inputPath: string) = List.ofArray (inputPath.Split('.'))
87 |
88 | let rec readPath (keys: string list) (input: Json) =
89 | match keys, input with
90 | | [ ], _ -> None
91 | | [ key ], JObject dict -> Map.tryFind key dict
92 | | firstKey :: rest, JObject dict ->
93 | match Map.tryFind firstKey dict with
94 | | Some (JObject nextDict) -> readPath rest (JObject nextDict)
95 | | _ -> None
96 | | _ -> None
97 |
98 | let parseInt (input: string) : Option =
99 | match Int32.TryParse(input) with
100 | | true, value -> Some value
101 | | _, _ -> None
102 |
103 | let parseFloat (input: string) : Option =
104 | match Double.TryParse input with
105 | | true, value -> Some value
106 | | _ -> None
107 |
108 | let parseGuid (input: string) : Option =
109 | match Guid.TryParse input with
110 | | true, value -> Some value
111 | | _ -> None
112 |
113 | let extractValue (inputPath: string) (inputJson: Json) (typeInfo: Type) : Result =
114 | match typeInfo.FullName, readPath (getJPath inputPath) inputJson with
115 | | "System.Boolean", Some (JBool value) -> Ok (box value)
116 | | "System.Boolean", Some (JNumber 1.0) -> Ok (box true)
117 | | "System.Boolean", Some (JNumber 0.0) -> Ok (box false)
118 | | "System.Boolean", Some (JString ("true"|"True")) -> Ok (box true)
119 | | "System.Boolean", Some (JString ("false"|"False")) -> Ok (box false)
120 | | "System.Int32", Some (JNumber value) -> Ok (box (int (Math.Floor value)))
121 | | "System.Int32", Some (JString value) ->
122 | match parseInt value with
123 | | Some number -> Ok (box number)
124 | | None -> Error (sprintf "Could not parse value at path '%s' as an integer" inputPath)
125 | | "System.Double", Some (JNumber value) -> Ok (box value)
126 | | "System.Double", Some (JString value) ->
127 | match parseFloat value with
128 | | Some number -> Ok (box number)
129 | | None -> Error (sprintf "Could not parse value at path '%s' as a number" inputPath)
130 | | "System.String", Some (JString value) -> Ok (box value)
131 | | "System.String", Some (JNumber value) -> Ok (box (string value))
132 | | "System.String", Some (JNull) -> Error (sprintf "String value at path '%s' was null" inputPath)
133 | | "System.Guid", Some (JString value) ->
134 | match parseGuid value with
135 | | Some value -> Ok (box value)
136 | | None -> Error (sprintf "Could not parse value at path '%s' as valid GUID" inputPath)
137 | | "Giraffe.JsonTherapy.Types.Json", jsonValue -> Ok (box jsonValue)
138 | | name, None when not (isOption typeInfo) ->
139 | Error (sprintf "No value was found at path '%s' within the JSON" inputPath)
140 | | name, Some value when not (isOption typeInfo) ->
141 | // try parse the value automatically
142 | let originalJToken = convertJToken value
143 | Ok (originalJToken.ToObject(typeInfo))
144 | // no value was found for an optional value <-> return None
145 | | name, None when isOption typeInfo -> Ok (box None)
146 | | name, Some value when isOption typeInfo ->
147 | // option has one generic argument
148 | let innerType = typeInfo.GetGenericArguments().[0]
149 | match innerType.FullName, value with
150 | | "System.Boolean", JNull -> Ok (box Option.None)
151 | | "System.Boolean", JBool value -> Ok (box (Some value))
152 | | "System.Boolean", (JString ("true"|"True") | JNumber 1.0) -> Ok (box (Some true))
153 | | "System.Boolean", (JString ("false"|"False") | JNumber 0.0) -> Ok (box (Some false))
154 | | "System.Int32", JNull -> Ok (box Option.None)
155 | | "System.Int32", JNumber n -> Ok (box (Some (int (Math.Floor(n)))))
156 | | "System.Int32", JString value ->
157 | match parseInt value with
158 | | Some number -> Ok (box (Some number))
159 | | None -> Error (sprintf "Could not parse value at path '%s' as an integer" inputPath)
160 | | "System.String", JString value -> Ok (box (Some value))
161 | | "System.String", JNumber value -> Ok (box (Some (string value)))
162 | | "System.String", JNull -> Ok (box Option.None)
163 | | "System.Double", JNull -> Ok (box Option.None)
164 | | "System.Double", JNumber value -> Ok (box (Some value))
165 | | "System.Double", JString value ->
166 | match parseFloat value with
167 | | Some value -> Ok (box (Some value))
168 | | None -> Error (sprintf "Could not parse value at path '%s' as a number" inputPath)
169 | | "System.Guid", JNull -> Ok (box Option.None)
170 | | "System.Guid", JString value ->
171 | match parseGuid value with
172 | | Some value -> Ok (box (Some value))
173 | | None -> Error (sprintf "Could not parse value at path '%s' as valid GUID" inputPath)
174 | | "Giraffe.QueryReader.Types.Json", jsonValue -> Ok (box (Some jsonValue))
175 | | name, anyJson ->
176 | try
177 | let originalToken = convertJToken anyJson
178 | Ok (box (Some (originalToken.ToObject(innerType))))
179 | with
180 | | ex ->
181 | let originalToken = convertJToken anyJson
182 | let stringified = originalToken.ToString()
183 | Error (sprintf "Could not convert %s to type %s using default deserializer" stringified name)
184 | | name, None -> Error (sprintf "Could not parse value at path '%s' as type %s" inputPath name)
185 | | name, Some anyJson ->
186 | try
187 | let originalToken = convertJToken anyJson
188 | Ok (box (Some (originalToken.ToObject(typeInfo))))
189 | with
190 | | ex ->
191 | let originalToken = convertJToken anyJson
192 | let stringified = originalToken.ToString()
193 | Error (sprintf "Could not convert %s to type %s using default deserializer" stringified name)
194 |
195 | let badRequest (msg: string) =
196 | setStatusCode 400
197 | >=> json (dict [ "message", msg ])
198 |
199 | type Json() =
200 | static member parts<'t>(path: string, mapper: 't -> HttpHandler) =
201 | Extensions.request <| fun req ->
202 | task {
203 | use reader = new StreamReader(req.Body)
204 | let! content = reader.ReadToEndAsync()
205 | let inputJson = Extensions.parse content
206 | let typeInfo = typeof<'t>
207 | match Extensions.extractValue path inputJson typeInfo with
208 | | Error errorMsg -> return Extensions.badRequest errorMsg
209 | | Ok value -> return mapper (unbox<'t> value)
210 | }
211 |
212 |
213 | static member manyParts<'t>(path: string, mapper: 't list -> HttpHandler) =
214 | Extensions.request <| fun req -> task {
215 | use reader = new StreamReader(req.Body)
216 | let! content = reader.ReadToEndAsync()
217 | let inputJson = Extensions.parse content
218 | let typeInfo = typeof<'t>
219 | match inputJson with
220 | | JArray values ->
221 | let result =
222 | values
223 | |> List.choose (fun value ->
224 | match Extensions.extractValue path value typeInfo with
225 | | Error erroMsg -> None
226 | | Ok part -> Some (unbox<'t> part))
227 | |> mapper
228 | return result
229 |
230 | | otherwise -> return Extensions.badRequest "Expected input as JSON array"
231 | }
232 |
233 | static member manyParts<'t, 'u>(fstPath: string, sndPath: string, mapper: ('t * 'u) list -> HttpHandler) =
234 | Extensions.request <| fun req -> task {
235 | use reader = new StreamReader(req.Body)
236 | let! content = reader.ReadToEndAsync()
237 | let inputJson = Extensions.parse content
238 | let fstType = typeof<'t>
239 | let sndType = typeof<'u>
240 | match inputJson with
241 | | JArray values ->
242 | return
243 | values
244 | |> List.choose (fun value ->
245 | match Extensions.extractValue fstPath value fstType with
246 | | Error erroMsg -> None
247 | | Ok first ->
248 | match Extensions.extractValue sndPath value sndType with
249 | | Error errorMsg -> None
250 | | Ok second -> Some (unbox<'t> first, unbox<'u> second))
251 | |> mapper
252 |
253 | | otherwise -> return Extensions.badRequest "Expected input as JSON array"
254 | }
255 |
256 | static member manyParts<'t, 'u, 'v>(fstPath: string, sndPath: string, thirdPath: string, mapper: ('t * 'u * 'v) list -> HttpHandler) =
257 | Extensions.request <| fun req -> task {
258 | use reader = new StreamReader(req.Body)
259 | let! content = reader.ReadToEndAsync()
260 | let inputJson = Extensions.parse content
261 | let fstType = typeof<'t>
262 | let sndType = typeof<'u>
263 | let thirdType = typeof<'v>
264 | match inputJson with
265 | | JArray values ->
266 | return
267 | values
268 | |> List.choose (fun value ->
269 | match Extensions.extractValue fstPath value fstType with
270 | | Error erroMsg -> None
271 | | Ok first ->
272 | match Extensions.extractValue sndPath value sndType with
273 | | Error errorMsg -> None
274 | | Ok second ->
275 | match Extensions.extractValue thirdPath value thirdType with
276 | | Error errMsg -> None
277 | | Ok third -> Some (unbox<'t> first, unbox<'u> second, unbox<'v> third))
278 | |> mapper
279 |
280 | | otherwise -> return Extensions.badRequest "Expected input as JSON array"
281 | }
282 |
283 | static member manyParts<'t, 'u, 'v, 'q>(fstPath: string, sndPath: string, thirdPath: string, forthPath: string, mapper: ('t * 'u * 'v * 'q) list -> HttpHandler) =
284 | Extensions.request <| fun req -> task {
285 | use reader = new StreamReader(req.Body)
286 | let! content = reader.ReadToEndAsync()
287 | let inputJson = Extensions.parse content
288 | let fstType = typeof<'t>
289 | let sndType = typeof<'u>
290 | let thirdType = typeof<'v>
291 | let forthType = typeof<'q>
292 | match inputJson with
293 | | JArray values ->
294 | return
295 | values
296 | |> List.choose (fun value ->
297 | match Extensions.extractValue fstPath value fstType with
298 | | Error erroMsg -> None
299 | | Ok first ->
300 | match Extensions.extractValue sndPath value sndType with
301 | | Error errorMsg -> None
302 | | Ok second ->
303 | match Extensions.extractValue thirdPath value thirdType with
304 | | Error errMsg -> None
305 | | Ok third ->
306 | match Extensions.extractValue forthPath value forthType with
307 | | Error _ -> None
308 | | Ok forth -> Some (unbox<'t> first, unbox<'u> second, unbox<'v> third, unbox<'q> forth))
309 | |> mapper
310 |
311 | | otherwise -> return Extensions.badRequest "Expected input as JSON array"
312 | }
313 |
314 | static member manyParts<'t, 'u, 'v, 'q, 'w>(fstPath: string, sndPath: string, thirdPath: string, forthPath: string, fifthPath: string, mapper: ('t * 'u * 'v * 'q * 'w) list -> HttpHandler) =
315 | Extensions.request <| fun req -> task {
316 | use reader = new StreamReader(req.Body)
317 | let! content = reader.ReadToEndAsync()
318 | let inputJson = Extensions.parse content
319 | let fstType = typeof<'t>
320 | let sndType = typeof<'u>
321 | let thirdType = typeof<'v>
322 | let forthType = typeof<'q>
323 | let fifthType = typeof<'w>
324 | match inputJson with
325 | | JArray values ->
326 | return
327 | values
328 | |> List.choose (fun value ->
329 | match Extensions.extractValue fstPath value fstType with
330 | | Error erroMsg -> None
331 | | Ok first ->
332 | match Extensions.extractValue sndPath value sndType with
333 | | Error errorMsg -> None
334 | | Ok second ->
335 | match Extensions.extractValue thirdPath value thirdType with
336 | | Error errMsg -> None
337 | | Ok third ->
338 | match Extensions.extractValue forthPath value forthType with
339 | | Error _ -> None
340 | | Ok forth ->
341 | match Extensions.extractValue fifthPath value fifthType with
342 | | Error _ -> None
343 | | Ok fifth -> Some (unbox<'t> first, unbox<'u> second, unbox<'v> third, unbox<'q> forth, unbox<'w> fifth))
344 |
345 | |> mapper
346 |
347 | | otherwise -> return Extensions.badRequest "Expected input as JSON array"
348 | }
349 |
350 | static member manyParts<'t, 'u, 'v, 'q, 'w, 'z>(fstPath: string, sndPath: string, thirdPath: string, forthPath: string, fifthPath: string, sixthPath: string, mapper: ('t * 'u * 'v * 'q * 'w * 'z) list -> HttpHandler) =
351 | Extensions.request <| fun req -> task {
352 | use reader = new StreamReader(req.Body)
353 | let! content = reader.ReadToEndAsync()
354 | let inputJson = Extensions.parse content
355 | let fstType = typeof<'t>
356 | let sndType = typeof<'u>
357 | let thirdType = typeof<'v>
358 | let forthType = typeof<'q>
359 | let fifthType = typeof<'w>
360 | let sixthType = typeof<'z>
361 | match inputJson with
362 | | JArray values ->
363 | return
364 | values
365 | |> List.choose (fun value ->
366 | match Extensions.extractValue fstPath value fstType with
367 | | Error erroMsg -> None
368 | | Ok first ->
369 | match Extensions.extractValue sndPath value sndType with
370 | | Error errorMsg -> None
371 | | Ok second ->
372 | match Extensions.extractValue thirdPath value thirdType with
373 | | Error errMsg -> None
374 | | Ok third ->
375 | match Extensions.extractValue forthPath value forthType with
376 | | Error _ -> None
377 | | Ok forth ->
378 | match Extensions.extractValue fifthPath value fifthType with
379 | | Error _ -> None
380 | | Ok fifth ->
381 | match Extensions.extractValue sixthPath value sixthType with
382 | | Error _ -> None
383 | | Ok sixth -> Some (unbox<'t> first, unbox<'u> second, unbox<'v> third, unbox<'q> forth, unbox<'w> fifth, unbox<'z> sixth))
384 | |> mapper
385 |
386 | | otherwise -> return Extensions.badRequest "Expected input as JSON array"
387 | }
388 |
389 | static member manyParts<'t, 'u, 'v, 'q, 'w, 'z, 'p>(fstPath: string, sndPath: string, thirdPath: string, forthPath: string, fifthPath: string, sixthPath: string, seventhPath: string, mapper: ('t * 'u * 'v * 'q * 'w * 'z * 'p) list -> HttpHandler) =
390 | Extensions.request <| fun req -> task {
391 | use reader = new StreamReader(req.Body)
392 | let! content = reader.ReadToEndAsync()
393 | let inputJson = Extensions.parse content
394 | let fstType = typeof<'t>
395 | let sndType = typeof<'u>
396 | let thirdType = typeof<'v>
397 | let forthType = typeof<'q>
398 | let fifthType = typeof<'w>
399 | let sixthType = typeof<'z>
400 | let seventhType = typeof<'p>
401 | match inputJson with
402 | | JArray values ->
403 | return
404 | values
405 | |> List.choose (fun value ->
406 | match Extensions.extractValue fstPath value fstType with
407 | | Error erroMsg -> None
408 | | Ok first ->
409 | match Extensions.extractValue sndPath value sndType with
410 | | Error errorMsg -> None
411 | | Ok second ->
412 | match Extensions.extractValue thirdPath value thirdType with
413 | | Error errMsg -> None
414 | | Ok third ->
415 | match Extensions.extractValue forthPath value forthType with
416 | | Error _ -> None
417 | | Ok forth ->
418 | match Extensions.extractValue fifthPath value fifthType with
419 | | Error _ -> None
420 | | Ok fifth ->
421 | match Extensions.extractValue sixthPath value sixthType with
422 | | Error _ -> None
423 | | Ok sixth ->
424 | match Extensions.extractValue seventhPath value seventhType with
425 | | Error _ -> None
426 | | Ok seventh -> Some (unbox<'t> first, unbox<'u> second, unbox<'v> third, unbox<'q> forth, unbox<'w> fifth, unbox<'z> sixth, unbox<'p> seventh))
427 | |> mapper
428 | | otherwise ->
429 | return Extensions.badRequest "Expected input as JSON array"
430 | }
431 |
432 | static member manyParts<'t, 'u, 'v, 'q, 'w, 'z, 'p, 'r>(fstPath: string, sndPath: string, thirdPath: string, forthPath: string, fifthPath: string, sixthPath: string, seventhPath: string, eighthPath: string, mapper: ('t * 'u * 'v * 'q * 'w * 'z * 'p * 'r) list -> HttpHandler) =
433 | Extensions.request <| fun req -> task {
434 | use reader = new StreamReader(req.Body)
435 | let! content = reader.ReadToEndAsync()
436 | let inputJson = Extensions.parse content
437 | let fstType = typeof<'t>
438 | let sndType = typeof<'u>
439 | let thirdType = typeof<'v>
440 | let forthType = typeof<'q>
441 | let fifthType = typeof<'w>
442 | let sixthType = typeof<'z>
443 | let seventhType = typeof<'p>
444 | let eighthType = typeof<'r>
445 | match inputJson with
446 | | JArray values ->
447 | return
448 | values
449 | |> List.choose (fun value ->
450 | match Extensions.extractValue fstPath value fstType with
451 | | Error erroMsg -> None
452 | | Ok first ->
453 | match Extensions.extractValue sndPath value sndType with
454 | | Error errorMsg -> None
455 | | Ok second ->
456 | match Extensions.extractValue thirdPath value thirdType with
457 | | Error errMsg -> None
458 | | Ok third ->
459 | match Extensions.extractValue forthPath value forthType with
460 | | Error _ -> None
461 | | Ok forth ->
462 | match Extensions.extractValue fifthPath value fifthType with
463 | | Error _ -> None
464 | | Ok fifth ->
465 | match Extensions.extractValue sixthPath value sixthType with
466 | | Error _ -> None
467 | | Ok sixth ->
468 | match Extensions.extractValue seventhPath value seventhType with
469 | | Error _ -> None
470 | | Ok seventh ->
471 | match Extensions.extractValue eighthPath value eighthType with
472 | | Error _ -> None
473 | | Ok eighth -> Some (unbox<'t> first, unbox<'u> second, unbox<'v> third, unbox<'q> forth, unbox<'w> fifth, unbox<'z> sixth, unbox<'p> seventh, unbox<'r> eighth))
474 | |> mapper
475 | | otherwise ->
476 | return Extensions.badRequest "Expected input as JSON array"
477 | }
478 | static member parts<'t, 'u>(fstPath: string, sndPath: string, mapper: 't -> 'u -> HttpHandler) =
479 | Extensions.request <| fun req -> task {
480 | use reader = new StreamReader(req.Body)
481 | let! content = reader.ReadToEndAsync()
482 | let inputJson = Extensions.parse content
483 | let first = typeof<'t>
484 | let second = typeof<'u>
485 | match Extensions.extractValue fstPath inputJson first with
486 | | Error errorMsg -> return Extensions.badRequest errorMsg
487 | | Ok first ->
488 | match Extensions.extractValue sndPath inputJson second with
489 | | Error errorMsg -> return Extensions.badRequest errorMsg
490 | | Ok second -> return mapper (unbox<'t> first) (unbox<'u> second)
491 | }
492 |
493 | static member parts<'t, 'u, 'v>(fstPath: string, sndPath: string, thirdPath: string, mapper: 't -> 'u -> 'v -> HttpHandler) =
494 | Extensions.request <| fun req -> task {
495 | use reader = new StreamReader(req.Body)
496 | let! content = reader.ReadToEndAsync()
497 | let inputJson = Extensions.parse content
498 | let firstType = typeof<'t>
499 | let secondType = typeof<'u>
500 | let thirdType = typeof<'v>
501 | match Extensions.extractValue fstPath inputJson firstType with
502 | | Error errorMsg -> return Extensions.badRequest errorMsg
503 | | Ok first ->
504 | match Extensions.extractValue sndPath inputJson secondType with
505 | | Error errorMsg -> return Extensions.badRequest errorMsg
506 | | Ok second ->
507 | match Extensions.extractValue thirdPath inputJson thirdType with
508 | | Error errorMsg -> return Extensions.badRequest errorMsg
509 | | Ok third -> return mapper (unbox<'t> first) (unbox<'u> second) (unbox<'v> third)
510 | }
511 | static member parts<'t, 'u, 'v, 'w>(fstPath: string, sndPath: string, thirdPath: string, forthPath: string, mapper: 't -> 'u -> 'v -> 'w -> HttpHandler) =
512 | Extensions.request <| fun req -> task {
513 | use reader = new StreamReader(req.Body)
514 | let! content = reader.ReadToEndAsync()
515 | let inputJson = Extensions.parse content
516 | let firstType = typeof<'t>
517 | let secondType = typeof<'u>
518 | let thirdType = typeof<'v>
519 | let forthType = typeof<'w>
520 | match Extensions.extractValue fstPath inputJson firstType with
521 | | Error errorMsg -> return Extensions.badRequest errorMsg
522 | | Ok first ->
523 | match Extensions.extractValue sndPath inputJson secondType with
524 | | Error errorMsg -> return Extensions.badRequest errorMsg
525 | | Ok second ->
526 | match Extensions.extractValue thirdPath inputJson thirdType with
527 | | Error errorMsg -> return Extensions.badRequest errorMsg
528 | | Ok third ->
529 | match Extensions.extractValue forthPath inputJson forthType with
530 | | Error errorMsg -> return Extensions.badRequest errorMsg
531 | | Ok forth -> return mapper (unbox<'t> first) (unbox<'u> second) (unbox<'v> third) (unbox<'w> forth)
532 | }
533 |
534 | static member parts<'t, 'u, 'v, 'w, 'q>(fstPath: string, sndPath: string, thirdPath: string, forthPath: string, fifthPath: string, mapper: 't -> 'u -> 'v -> 'w -> 'q -> HttpHandler) =
535 | Extensions.request <| fun req -> task {
536 | use reader = new StreamReader(req.Body)
537 | let! content = reader.ReadToEndAsync()
538 | let inputJson = Extensions.parse content
539 | let firstType = typeof<'t>
540 | let secondType = typeof<'u>
541 | let thirdType = typeof<'v>
542 | let forthType = typeof<'w>
543 | let fifthType = typeof<'q>
544 | match Extensions.extractValue fstPath inputJson firstType with
545 | | Error errorMsg -> return Extensions.badRequest errorMsg
546 | | Ok first ->
547 | match Extensions.extractValue sndPath inputJson secondType with
548 | | Error errorMsg -> return Extensions.badRequest errorMsg
549 | | Ok second ->
550 | match Extensions.extractValue thirdPath inputJson thirdType with
551 | | Error errorMsg -> return Extensions.badRequest errorMsg
552 | | Ok third ->
553 | match Extensions.extractValue forthPath inputJson forthType with
554 | | Error errorMsg -> return Extensions.badRequest errorMsg
555 | | Ok forth ->
556 | match Extensions.extractValue fifthPath inputJson fifthType with
557 | | Error errorMsg -> return Extensions.badRequest errorMsg
558 | | Ok fifth -> return mapper (unbox<'t> first) (unbox<'u> second) (unbox<'v> third) (unbox<'w> forth) (unbox<'q> fifth)
559 | }
560 |
561 | static member parts<'t, 'u, 'v, 'w, 'q, 'y>(fstPath: string, sndPath: string, thirdPath: string, forthPath: string, fifthPath: string, sixthPath: string, mapper: 't -> 'u -> 'v -> 'w -> 'q -> 'y -> HttpHandler) =
562 | Extensions.request <| fun req -> task {
563 | use reader = new StreamReader(req.Body)
564 | let! content = reader.ReadToEndAsync()
565 | let inputJson = Extensions.parse content
566 | let firstType = typeof<'t>
567 | let secondType = typeof<'u>
568 | let thirdType = typeof<'v>
569 | let forthType = typeof<'w>
570 | let fifthType = typeof<'q>
571 | let sixthType = typeof<'y>
572 | match Extensions.extractValue fstPath inputJson firstType with
573 | | Error errorMsg -> return Extensions.badRequest errorMsg
574 | | Ok first ->
575 | match Extensions.extractValue sndPath inputJson secondType with
576 | | Error errorMsg -> return Extensions.badRequest errorMsg
577 | | Ok second ->
578 | match Extensions.extractValue thirdPath inputJson thirdType with
579 | | Error errorMsg -> return Extensions.badRequest errorMsg
580 | | Ok third ->
581 | match Extensions.extractValue forthPath inputJson forthType with
582 | | Error errorMsg -> return Extensions.badRequest errorMsg
583 | | Ok forth ->
584 | match Extensions.extractValue fifthPath inputJson fifthType with
585 | | Error errorMsg -> return Extensions.badRequest errorMsg
586 | | Ok fifth ->
587 | match Extensions.extractValue sixthPath inputJson sixthType with
588 | | Error errorMsg -> return Extensions.badRequest errorMsg
589 | | Ok sixth -> return mapper (unbox<'t> first) (unbox<'u> second) (unbox<'v> third) (unbox<'w> forth) (unbox<'q> fifth) (unbox<'y> sixth)
590 | }
591 | static member parts<'t, 'u, 'v, 'w, 'q, 'y, 'r>(fstPath: string, sndPath: string, thirdPath: string, forthPath: string, fifthPath: string, sixthPath: string, seventhPath: string, mapper: 't -> 'u -> 'v -> 'w -> 'q -> 'y -> 'r -> HttpHandler) =
592 | Extensions.request <| fun req -> task {
593 | use reader = new StreamReader(req.Body)
594 | let! content = reader.ReadToEndAsync()
595 | let inputJson = Extensions.parse content
596 | let firstType = typeof<'t>
597 | let secondType = typeof<'u>
598 | let thirdType = typeof<'v>
599 | let forthType = typeof<'w>
600 | let fifthType = typeof<'q>
601 | let sixthType = typeof<'y>
602 | let seventhType = typeof<'r>
603 | match Extensions.extractValue fstPath inputJson firstType with
604 | | Error errorMsg -> return Extensions.badRequest errorMsg
605 | | Ok first ->
606 | match Extensions.extractValue sndPath inputJson secondType with
607 | | Error errorMsg -> return Extensions.badRequest errorMsg
608 | | Ok second ->
609 | match Extensions.extractValue thirdPath inputJson thirdType with
610 | | Error errorMsg -> return Extensions.badRequest errorMsg
611 | | Ok third ->
612 | match Extensions.extractValue forthPath inputJson forthType with
613 | | Error errorMsg -> return Extensions.badRequest errorMsg
614 | | Ok forth ->
615 | match Extensions.extractValue fifthPath inputJson fifthType with
616 | | Error errorMsg -> return Extensions.badRequest errorMsg
617 | | Ok fifth ->
618 | match Extensions.extractValue sixthPath inputJson sixthType with
619 | | Error errorMsg -> return Extensions.badRequest errorMsg
620 | | Ok sixth ->
621 | match Extensions.extractValue seventhPath inputJson seventhType with
622 | | Error errorMsg -> return Extensions.badRequest errorMsg
623 | | Ok seventh -> return mapper (unbox<'t> first) (unbox<'u> second) (unbox<'v> third) (unbox<'w> forth) (unbox<'q> fifth) (unbox<'y> sixth) (unbox<'r> seventh)
624 | }
625 | static member parts<'t, 'u, 'v, 'w, 'q, 'y, 'r, 'z>(fstPath: string, sndPath: string, thirdPath: string, forthPath: string, fifthPath: string, sixthPath: string, seventhPath: string, eighthPath: string, mapper: 't -> 'u -> 'v -> 'w -> 'q -> 'y -> 'r -> 'z -> HttpHandler) =
626 | Extensions.request <| fun req -> task {
627 | use reader = new StreamReader(req.Body)
628 | let! content = reader.ReadToEndAsync()
629 | let inputJson = Extensions.parse content
630 | let firstType = typeof<'t>
631 | let secondType = typeof<'u>
632 | let thirdType = typeof<'v>
633 | let forthType = typeof<'w>
634 | let fifthType = typeof<'q>
635 | let sixthType = typeof<'y>
636 | let seventhType = typeof<'r>
637 | let eighthType = typeof<'z>
638 | match Extensions.extractValue fstPath inputJson firstType with
639 | | Error errorMsg -> return Extensions.badRequest errorMsg
640 | | Ok first ->
641 | match Extensions.extractValue sndPath inputJson secondType with
642 | | Error errorMsg -> return Extensions.badRequest errorMsg
643 | | Ok second ->
644 | match Extensions.extractValue thirdPath inputJson thirdType with
645 | | Error errorMsg -> return Extensions.badRequest errorMsg
646 | | Ok third ->
647 | match Extensions.extractValue forthPath inputJson forthType with
648 | | Error errorMsg -> return Extensions.badRequest errorMsg
649 | | Ok forth ->
650 | match Extensions.extractValue fifthPath inputJson fifthType with
651 | | Error errorMsg -> return Extensions.badRequest errorMsg
652 | | Ok fifth ->
653 | match Extensions.extractValue sixthPath inputJson sixthType with
654 | | Error errorMsg -> return Extensions.badRequest errorMsg
655 | | Ok sixth ->
656 | match Extensions.extractValue seventhPath inputJson seventhType with
657 | | Error errorMsg -> return Extensions.badRequest errorMsg
658 | | Ok seventh ->
659 | match Extensions.extractValue eighthPath inputJson eighthType with
660 | | Error errorMsg -> return Extensions.badRequest errorMsg
661 | | Ok eighth -> return mapper (unbox<'t> first) (unbox<'u> second) (unbox<'v> third) (unbox<'w> forth) (unbox<'q> fifth) (unbox<'y> sixth) (unbox<'r> seventh) (unbox<'z> eighth)
662 | }
663 |
--------------------------------------------------------------------------------