mirror of
https://github.com/Smaug123/WoofWare.Myriad
synced 2025-10-23 21:08:39 +00:00
Compare commits
48 Commits
63a45f14d8
...
WoofWare.M
Author | SHA1 | Date | |
---|---|---|---|
|
1144e93c1c | ||
|
d899d77ae2 | ||
|
a2ad430b2f | ||
|
9e36986bc7 | ||
|
679c66885d | ||
|
246da41672 | ||
|
d07541c2c2 | ||
|
7b49505064 | ||
|
3209372b5b | ||
|
1bbbf4bd06 | ||
|
3ea1c7ab79 | ||
|
f55a810608 | ||
|
afc952241d | ||
|
c3af52596f | ||
|
8bd13c0bb4 | ||
|
ebd6f980de | ||
|
690a47488d | ||
|
82b40ee559 | ||
|
5a0a7e0d17 | ||
|
7ef393a28d | ||
|
4e18e8b1bf | ||
|
a0fb7ee43a | ||
|
3d5cd7374f | ||
|
1215834795 | ||
|
e453a6f07c | ||
|
3dfb89d086 | ||
|
626f6ef137 | ||
|
f803b44311 | ||
|
5c1841c3d2 | ||
|
bea584e3cc | ||
|
f8fdcb805e | ||
|
0f7724903b | ||
|
f83ac24a73 | ||
|
ae3840d537 | ||
|
aafee9495a | ||
|
515ea306a2 | ||
|
268a2f6f52 | ||
|
0b25100f00 | ||
|
41e9e4f82c | ||
|
948fbfbc84 | ||
|
ad2eeaaa4f | ||
|
7b3bd32323 | ||
|
ff2c08d54f | ||
|
ed0e4da0a3 | ||
|
79d7502f3f | ||
|
dd7e004e36 | ||
|
4c55bbed22 | ||
|
0d231c5200 |
@@ -3,16 +3,16 @@
|
||||
"isRoot": true,
|
||||
"tools": {
|
||||
"fantomas": {
|
||||
"version": "6.3.0-alpha-005",
|
||||
"version": "6.3.0-alpha-008",
|
||||
"commands": [
|
||||
"fantomas"
|
||||
]
|
||||
},
|
||||
"fsharp-analyzers": {
|
||||
"version": "0.22.0",
|
||||
"version": "0.25.0",
|
||||
"commands": [
|
||||
"fsharp-analyzers"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -2,7 +2,6 @@ root=true
|
||||
|
||||
[*]
|
||||
charset=utf-8
|
||||
end_of_line=crlf
|
||||
trim_trailing_whitespace=true
|
||||
insert_final_newline=true
|
||||
indent_style=space
|
||||
|
10
.gitattributes
vendored
10
.gitattributes
vendored
@@ -1,5 +1,5 @@
|
||||
* eol=auto
|
||||
*.sh text eol=lf
|
||||
*.yaml text
|
||||
*.nix text eol=lf
|
||||
hooks/pre-push text eol=lf
|
||||
* eol=auto
|
||||
*.sh text eol=lf
|
||||
*.yaml text
|
||||
*.nix text eol=lf
|
||||
hooks/pre-push text eol=lf
|
||||
|
2
.github/dependabot.yml
vendored
2
.github/dependabot.yml
vendored
@@ -7,7 +7,7 @@ updates:
|
||||
interval: "weekly"
|
||||
|
||||
- package-ecosystem: "nuget"
|
||||
directory: "/ApiSurface"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
ignore:
|
||||
|
82
.github/workflows/dotnet.yaml
vendored
82
.github/workflows/dotnet.yaml
vendored
@@ -28,7 +28,7 @@ jobs:
|
||||
with:
|
||||
fetch-depth: 0 # so that NerdBank.GitVersioning has access to history
|
||||
- name: Install Nix
|
||||
uses: cachix/install-nix-action@v24
|
||||
uses: cachix/install-nix-action@v25
|
||||
with:
|
||||
extra_nix_config: |
|
||||
access-tokens = github.com=${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -49,7 +49,7 @@ jobs:
|
||||
with:
|
||||
fetch-depth: 0 # so that NerdBank.GitVersioning has access to history
|
||||
- name: Install Nix
|
||||
uses: cachix/install-nix-action@v24
|
||||
uses: cachix/install-nix-action@v25
|
||||
with:
|
||||
extra_nix_config: |
|
||||
access-tokens = github.com=${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -58,7 +58,7 @@ jobs:
|
||||
- name: Build project
|
||||
run: nix develop --command dotnet build ./WoofWare.Myriad.Plugins/WoofWare.Myriad.Plugins.fsproj
|
||||
- name: Run analyzers
|
||||
run: nix run .#fsharp-analyzers -- --project ./WoofWare.Myriad.Plugins/WoofWare.Myriad.Plugins.fsproj --analyzers-path ./.analyzerpackages/g-research.fsharp.analyzers/0.6.0/ --verbosity detailed --report ./analysis.sarif --treat-as-error GRA-STRING-001 GRA-STRING-002 GRA-STRING-003 GRA-UNIONCASE-001 GRA-INTERPOLATED-001 GRA-TYPE-ANNOTATE-001 GRA-VIRTUALCALL-001 GRA-IMMUTABLECOLLECTIONEQUALITY-001 GRA-JSONOPTS-001 GRA-LOGARGFUNCFULLAPP-001
|
||||
run: nix run .#fsharp-analyzers -- --project ./WoofWare.Myriad.Plugins/WoofWare.Myriad.Plugins.fsproj --analyzers-path ./.analyzerpackages/g-research.fsharp.analyzers/*/ --verbosity detailed --report ./analysis.sarif --treat-as-error GRA-STRING-001 GRA-STRING-002 GRA-STRING-003 GRA-UNIONCASE-001 GRA-INTERPOLATED-001 GRA-TYPE-ANNOTATE-001 GRA-VIRTUALCALL-001 GRA-IMMUTABLECOLLECTIONEQUALITY-001 GRA-JSONOPTS-001 GRA-LOGARGFUNCFULLAPP-001 GRA-DISPBEFOREASYNC-001 --exclude-analyzers PartialAppAnalyzer
|
||||
|
||||
build-nix:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -66,7 +66,7 @@ jobs:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: Install Nix
|
||||
uses: cachix/install-nix-action@v24
|
||||
uses: cachix/install-nix-action@v25
|
||||
with:
|
||||
extra_nix_config: |
|
||||
access-tokens = github.com=${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -79,7 +79,7 @@ jobs:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: Install Nix
|
||||
uses: cachix/install-nix-action@v24
|
||||
uses: cachix/install-nix-action@v25
|
||||
with:
|
||||
extra_nix_config: |
|
||||
access-tokens = github.com=${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -92,7 +92,7 @@ jobs:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: Install Nix
|
||||
uses: cachix/install-nix-action@v24
|
||||
uses: cachix/install-nix-action@v25
|
||||
with:
|
||||
extra_nix_config: |
|
||||
access-tokens = github.com=${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -105,7 +105,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@master
|
||||
- name: Install Nix
|
||||
uses: cachix/install-nix-action@v24
|
||||
uses: cachix/install-nix-action@v25
|
||||
with:
|
||||
extra_nix_config: |
|
||||
access-tokens = github.com=${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -118,7 +118,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@master
|
||||
- name: Install Nix
|
||||
uses: cachix/install-nix-action@v24
|
||||
uses: cachix/install-nix-action@v25
|
||||
with:
|
||||
extra_nix_config: |
|
||||
access-tokens = github.com=${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -132,7 +132,7 @@ jobs:
|
||||
with:
|
||||
fetch-depth: 0 # so that NerdBank.GitVersioning has access to history
|
||||
- name: Install Nix
|
||||
uses: cachix/install-nix-action@v24
|
||||
uses: cachix/install-nix-action@v25
|
||||
with:
|
||||
extra_nix_config: |
|
||||
access-tokens = github.com=${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -142,23 +142,37 @@ jobs:
|
||||
run: nix develop --command dotnet build --no-restore --configuration Release
|
||||
- name: Pack
|
||||
run: nix develop --command dotnet pack --configuration Release
|
||||
- name: Upload NuGet artifact
|
||||
- name: Upload NuGet artifact (plugin)
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: nuget-package
|
||||
name: nuget-package-plugin
|
||||
path: WoofWare.Myriad.Plugins/bin/Release/WoofWare.Myriad.Plugins.*.nupkg
|
||||
- name: Upload NuGet artifact (attributes)
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: nuget-package-attribute
|
||||
path: WoofWare.Myriad.Plugins.Attributes/bin/Release/WoofWare.Myriad.Plugins.Attributes.*.nupkg
|
||||
|
||||
expected-pack:
|
||||
needs: [nuget-pack]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Download NuGet artifact
|
||||
- name: Download NuGet artifact (plugin)
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: nuget-package
|
||||
name: nuget-package-plugin
|
||||
path: packed-plugin
|
||||
- name: Check NuGet contents
|
||||
# Verify that there is exactly one nupkg in the artifact that would be NuGet published
|
||||
run: if [[ $(find . -maxdepth 1 -name 'WoofWare.Myriad.Plugins.*.nupkg' -printf c | wc -c) -ne "1" ]]; then exit 1; fi
|
||||
run: if [[ $(find packed-plugin -maxdepth 1 -name 'WoofWare.Myriad.Plugins.*.nupkg' -printf c | wc -c) -ne "1" ]]; then exit 1; fi
|
||||
- name: Download NuGet artifact (attributes)
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: nuget-package-attribute
|
||||
path: packed-attribute
|
||||
- name: Check NuGet contents
|
||||
# Verify that there is exactly one nupkg in the artifact that would be NuGet published
|
||||
run: if [[ $(find packed-attribute -maxdepth 1 -name 'WoofWare.Myriad.Plugins.Attributes.*.nupkg' -printf c | wc -c) -ne "1" ]]; then exit 1; fi
|
||||
|
||||
all-required-checks-complete:
|
||||
needs: [check-dotnet-format, check-nix-format, build, build-nix, linkcheck, flake-check, analyzers, nuget-pack, expected-pack]
|
||||
@@ -174,13 +188,43 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install Nix
|
||||
uses: cachix/install-nix-action@v24
|
||||
uses: cachix/install-nix-action@v25
|
||||
with:
|
||||
extra_nix_config: |
|
||||
access-tokens = github.com=${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Download NuGet artifact
|
||||
- name: Download NuGet artifact (plugin)
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: nuget-package
|
||||
- name: Publish to NuGet
|
||||
run: nix develop --command dotnet nuget push "WoofWare.Myriad.Plugins.*.nupkg" --api-key ${{ secrets.NUGET_API_KEY }} --source https://api.nuget.org/v3/index.json
|
||||
name: nuget-package-plugin
|
||||
path: packed-plugin
|
||||
- name: Publish to NuGet (plugin)
|
||||
run: nix develop --command dotnet nuget push "packed-plugin/WoofWare.Myriad.Plugins.*.nupkg" --api-key ${{ secrets.NUGET_API_KEY }} --source https://api.nuget.org/v3/index.json --skip-duplicate
|
||||
- name: Download NuGet artifact (attribute)
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: nuget-package-attribute
|
||||
path: packed-attribute
|
||||
- name: Publish to NuGet (attribute)
|
||||
run: nix develop --command dotnet nuget push "packed-attribute/WoofWare.Myriad.Plugins.Attributes.*.nupkg" --api-key ${{ secrets.NUGET_API_KEY }} --source https://api.nuget.org/v3/index.json --skip-duplicate
|
||||
|
||||
github-release-plugin:
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ !github.event.repository.fork && github.ref == 'refs/heads/main' }}
|
||||
needs: [all-required-checks-complete]
|
||||
environment: main-deploy
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Download NuGet artifact (plugin)
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: nuget-package-plugin
|
||||
- name: Download NuGet artifact (attribute)
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: nuget-package-attribute
|
||||
- name: Tag and release plugin
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: sh .github/workflows/tag.sh
|
||||
|
17
.github/workflows/tag.sh
vendored
Normal file
17
.github/workflows/tag.sh
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
#!/bin/sh
|
||||
|
||||
find . -maxdepth 1 -type f -name '*.nupkg' -exec sh -c 'tag=$(basename "$1" .nupkg); git tag "$tag"; git push origin "$tag"' shell {} \;
|
||||
|
||||
export TAG
|
||||
TAG=$(find . -maxdepth 1 -type f -name 'WoofWare.Myriad.Plugins.*.nupkg' -exec sh -c 'basename "$1" .nupkg' shell {} \; | grep -v Attributes)
|
||||
|
||||
case "$TAG" in
|
||||
*"
|
||||
"*)
|
||||
echo "Error: TAG contains a newline; multiple plugins found."
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
# target_commitish empty indicates the repo default branch
|
||||
curl -L -X POST -H "Accept: application/vnd.github+json" -H "Authorization: Bearer $GITHUB_TOKEN" -H "X-GitHub-Api-Version: 2022-11-28" https://api.github.com/repos/Smaug123/WoofWare.Myriad/releases -d '{"tag_name":"'"$TAG"'","target_commitish":"","name":"'"$TAG"'","draft":false,"prerelease":false,"generate_release_notes":false}'
|
1
.gitignore
vendored
1
.gitignore
vendored
@@ -9,3 +9,4 @@ riderModule.iml
|
||||
result
|
||||
.analyzerpackages/
|
||||
analysis.sarif
|
||||
.direnv/
|
||||
|
6
CHANGELOG.md
Normal file
6
CHANGELOG.md
Normal file
@@ -0,0 +1,6 @@
|
||||
Notable changes are recorded here.
|
||||
|
||||
# WoofWare.Myriad.Plugins 1.4 -> 2.0
|
||||
|
||||
This transition split the attributes (e.g. `[<JsonParseAttribute>]`) into their own assembly, WoofWare.Myriad.Plugins.Attributes.
|
||||
The new assembly has minimal dependencies, so you may safely use it from your own code.
|
5
ConsumePlugin/AssemblyInfo.fs
Normal file
5
ConsumePlugin/AssemblyInfo.fs
Normal file
@@ -0,0 +1,5 @@
|
||||
namespace ConsumePlugin.AssemblyInfo
|
||||
|
||||
[<assembly : System.Runtime.CompilerServices.InternalsVisibleTo("WoofWare.Myriad.Plugins.Test")>]
|
||||
|
||||
do ()
|
22
ConsumePlugin/Catamorphism.fs
Normal file
22
ConsumePlugin/Catamorphism.fs
Normal file
@@ -0,0 +1,22 @@
|
||||
namespace ConsumePlugin
|
||||
|
||||
open WoofWare.Myriad.Plugins
|
||||
|
||||
type Const<'a> =
|
||||
| Verbatim of 'a
|
||||
| String of string
|
||||
|
||||
type PairOpKind =
|
||||
| NormalSeq
|
||||
| ThenDoSeq
|
||||
|
||||
[<CreateCatamorphism "TreeCata">]
|
||||
type Tree<'a, 'b> =
|
||||
| Const of Const<'a> * 'b
|
||||
| Pair of Tree<'a, 'b> * Tree<'a, 'b> * PairOpKind
|
||||
| Sequential of Tree<'a, 'b> list
|
||||
| Builder of Tree<'a, 'b> * TreeBuilder<'b, 'a>
|
||||
|
||||
and TreeBuilder<'b, 'a> =
|
||||
| Child of TreeBuilder<'b, 'a>
|
||||
| Parent of Tree<'a, 'b>
|
@@ -10,29 +10,52 @@
|
||||
|
||||
<ItemGroup>
|
||||
<None Include="myriad.toml"/>
|
||||
<Compile Include="AssemblyInfo.fs" />
|
||||
<Compile Include="RecordFile.fs"/>
|
||||
<Compile Include="GeneratedRecord.fs"> <!--1-->
|
||||
<MyriadFile>RecordFile.fs</MyriadFile> <!--2-->
|
||||
<Compile Include="GeneratedRecord.fs">
|
||||
<MyriadFile>RecordFile.fs</MyriadFile>
|
||||
</Compile>
|
||||
<Compile Include="JsonRecord.fs"/>
|
||||
<Compile Include="GeneratedJson.fs"> <!--1-->
|
||||
<MyriadFile>JsonRecord.fs</MyriadFile> <!--2-->
|
||||
<Compile Include="GeneratedJson.fs">
|
||||
<MyriadFile>JsonRecord.fs</MyriadFile>
|
||||
</Compile>
|
||||
<Compile Include="PureGymDto.fs"/>
|
||||
<Compile Include="GeneratedPureGymDto.fs">
|
||||
<MyriadFile>PureGymDto.fs</MyriadFile> <!--2-->
|
||||
<MyriadFile>PureGymDto.fs</MyriadFile>
|
||||
</Compile>
|
||||
<Compile Include="RestApiExample.fs"/>
|
||||
<Compile Include="GeneratedRestClient.fs">
|
||||
<MyriadFile>RestApiExample.fs</MyriadFile> <!--2-->
|
||||
<MyriadFile>RestApiExample.fs</MyriadFile>
|
||||
</Compile>
|
||||
<Compile Include="MockExample.fs"/>
|
||||
<Compile Include="GeneratedMock.fs">
|
||||
<MyriadFile>MockExample.fs</MyriadFile>
|
||||
</Compile>
|
||||
<Compile Include="Vault.fs" />
|
||||
<Compile Include="GeneratedVault.fs">
|
||||
<MyriadFile>Vault.fs</MyriadFile>
|
||||
</Compile>
|
||||
<Compile Include="SerializationAndDeserialization.fs" />
|
||||
<Compile Include="GeneratedSerde.fs">
|
||||
<MyriadFile>SerializationAndDeserialization.fs</MyriadFile>
|
||||
</Compile>
|
||||
<Compile Include="Catamorphism.fs" />
|
||||
<Compile Include="GeneratedCatamorphism.fs">
|
||||
<MyriadFile>Catamorphism.fs</MyriadFile>
|
||||
</Compile>
|
||||
<Compile Include="FSharpForFunAndProfitCata.fs" />
|
||||
<Compile Include="GeneratedFileSystem.fs">
|
||||
<MyriadFile>FSharpForFunAndProfitCata.fs</MyriadFile>
|
||||
</Compile>
|
||||
<Compile Include="List.fs" />
|
||||
<Compile Include="ListCata.fs">
|
||||
<MyriadFile>List.fs</MyriadFile>
|
||||
</Compile>
|
||||
<None Include="..\runmyriad.sh">
|
||||
<Link>runmyriad.sh</Link>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="RestEase" Version="1.6.4"/>
|
||||
<ProjectReference Include="..\WoofWare.Myriad.Plugins.Attributes\WoofWare.Myriad.Plugins.Attributes.fsproj" />
|
||||
<ProjectReference Include="..\WoofWare.Myriad.Plugins\WoofWare.Myriad.Plugins.fsproj"/>
|
||||
<PackageReference Include="Myriad.Sdk" Version="0.8.3"/>
|
||||
<PackageReference Include="Myriad.Core" Version="0.8.3"/>
|
||||
|
52
ConsumePlugin/FSharpForFunAndProfitCata.fs
Normal file
52
ConsumePlugin/FSharpForFunAndProfitCata.fs
Normal file
@@ -0,0 +1,52 @@
|
||||
namespace ConsumePlugin
|
||||
|
||||
open WoofWare.Myriad.Plugins
|
||||
|
||||
type File =
|
||||
{
|
||||
Name : string
|
||||
FileSize : int
|
||||
}
|
||||
|
||||
type Directory =
|
||||
{
|
||||
Name : string
|
||||
DirSize : int
|
||||
Contents : FileSystemItem list
|
||||
}
|
||||
|
||||
and [<CreateCatamorphism "FileSystemCata">] FileSystemItem =
|
||||
| Directory of Directory
|
||||
| File of File
|
||||
|
||||
type Book =
|
||||
{
|
||||
title : string
|
||||
price : decimal
|
||||
}
|
||||
|
||||
type ChocolateType =
|
||||
| Dark
|
||||
| Milk
|
||||
| SeventyPercent
|
||||
|
||||
type Chocolate =
|
||||
{
|
||||
chocType : ChocolateType
|
||||
price : decimal
|
||||
}
|
||||
|
||||
override this.ToString () = this.chocType.ToString ()
|
||||
|
||||
type WrappingPaperStyle =
|
||||
| HappyBirthday
|
||||
| HappyHolidays
|
||||
| SolidColor
|
||||
|
||||
[<CreateCatamorphism "GiftCata">]
|
||||
type Gift =
|
||||
| Book of Book
|
||||
| Chocolate of Chocolate
|
||||
| Wrapped of Gift * WrappingPaperStyle
|
||||
| Boxed of Gift
|
||||
| WithACard of Gift * message : string
|
138
ConsumePlugin/GeneratedCatamorphism.fs
Normal file
138
ConsumePlugin/GeneratedCatamorphism.fs
Normal file
@@ -0,0 +1,138 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// This code was generated by myriad.
|
||||
// Changes to this file will be lost when the code is regenerated.
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
namespace ConsumePlugin
|
||||
|
||||
open WoofWare.Myriad.Plugins
|
||||
|
||||
/// Description of how to combine cases during a fold
|
||||
type TreeBuilderCataCase<'b, 'a, 'TreeBuilder, 'Tree> =
|
||||
/// How to operate on the Child case
|
||||
abstract Child : 'TreeBuilder -> 'TreeBuilder
|
||||
/// How to operate on the Parent case
|
||||
abstract Parent : 'Tree -> 'TreeBuilder
|
||||
|
||||
/// Description of how to combine cases during a fold
|
||||
type TreeCataCase<'a, 'b, 'TreeBuilder, 'Tree> =
|
||||
/// How to operate on the Const case
|
||||
abstract Const : Const<'a> -> 'b -> 'Tree
|
||||
/// How to operate on the Pair case
|
||||
abstract Pair : 'Tree -> 'Tree -> PairOpKind -> 'Tree
|
||||
/// How to operate on the Sequential case
|
||||
abstract Sequential : 'Tree list -> 'Tree
|
||||
/// How to operate on the Builder case
|
||||
abstract Builder : 'Tree -> 'TreeBuilder -> 'Tree
|
||||
|
||||
/// Specifies how to perform a fold (catamorphism) over the type Tree and its friends.
|
||||
type TreeCata<'b, 'a, 'TreeBuilder, 'Tree> =
|
||||
{
|
||||
/// How to perform a fold (catamorphism) over the type TreeBuilder
|
||||
TreeBuilder : TreeBuilderCataCase<'b, 'a, 'TreeBuilder, 'Tree>
|
||||
/// How to perform a fold (catamorphism) over the type Tree
|
||||
Tree : TreeCataCase<'a, 'b, 'TreeBuilder, 'Tree>
|
||||
}
|
||||
|
||||
/// Methods to perform a catamorphism over the type Tree
|
||||
[<RequireQualifiedAccess>]
|
||||
module TreeCata =
|
||||
[<RequireQualifiedAccess>]
|
||||
type private Instruction<'b, 'a> =
|
||||
| Process__TreeBuilder of TreeBuilder<'b, 'a>
|
||||
| Process__Tree of Tree<'a, 'b>
|
||||
| TreeBuilder_Child
|
||||
| TreeBuilder_Parent
|
||||
| Tree_Pair of PairOpKind
|
||||
| Tree_Sequential of int
|
||||
| Tree_Builder
|
||||
|
||||
let private loop (cata : TreeCata<'b, 'a, 'TreeBuilder, 'Tree>) (instructions : ResizeArray<Instruction<'b, 'a>>) =
|
||||
let treeStack = ResizeArray<'Tree> ()
|
||||
let treeBuilderStack = ResizeArray<'TreeBuilder> ()
|
||||
|
||||
while instructions.Count > 0 do
|
||||
let currentInstruction = instructions.[instructions.Count - 1]
|
||||
instructions.RemoveAt (instructions.Count - 1)
|
||||
|
||||
match currentInstruction with
|
||||
| Instruction.Process__TreeBuilder x ->
|
||||
match x with
|
||||
| TreeBuilder.Child (arg0_0) ->
|
||||
instructions.Add Instruction.TreeBuilder_Child
|
||||
instructions.Add (Instruction.Process__TreeBuilder arg0_0)
|
||||
| TreeBuilder.Parent (arg0_0) ->
|
||||
instructions.Add Instruction.TreeBuilder_Parent
|
||||
instructions.Add (Instruction.Process__Tree arg0_0)
|
||||
| Instruction.Process__Tree x ->
|
||||
match x with
|
||||
| Tree.Const (arg0_0, arg1_0) -> cata.Tree.Const arg0_0 arg1_0 |> treeStack.Add
|
||||
| Tree.Pair (arg0_0, arg1_0, arg2_0) ->
|
||||
instructions.Add (Instruction.Tree_Pair (arg2_0))
|
||||
instructions.Add (Instruction.Process__Tree arg0_0)
|
||||
instructions.Add (Instruction.Process__Tree arg1_0)
|
||||
| Tree.Sequential (arg0_0) ->
|
||||
instructions.Add (Instruction.Tree_Sequential ((List.length arg0_0)))
|
||||
|
||||
for elt in arg0_0 do
|
||||
instructions.Add (Instruction.Process__Tree elt)
|
||||
| Tree.Builder (arg0_0, arg1_0) ->
|
||||
instructions.Add Instruction.Tree_Builder
|
||||
instructions.Add (Instruction.Process__Tree arg0_0)
|
||||
instructions.Add (Instruction.Process__TreeBuilder arg1_0)
|
||||
| Instruction.TreeBuilder_Child ->
|
||||
let arg0_0 = treeBuilderStack.[treeBuilderStack.Count - 1]
|
||||
treeBuilderStack.RemoveAt (treeBuilderStack.Count - 1)
|
||||
cata.TreeBuilder.Child arg0_0 |> treeBuilderStack.Add
|
||||
| Instruction.TreeBuilder_Parent ->
|
||||
let arg0_0 = treeStack.[treeStack.Count - 1]
|
||||
treeStack.RemoveAt (treeStack.Count - 1)
|
||||
cata.TreeBuilder.Parent arg0_0 |> treeBuilderStack.Add
|
||||
| Instruction.Tree_Pair (arg2_0) ->
|
||||
let arg0_0 = treeStack.[treeStack.Count - 1]
|
||||
treeStack.RemoveAt (treeStack.Count - 1)
|
||||
let arg1_0 = treeStack.[treeStack.Count - 1]
|
||||
treeStack.RemoveAt (treeStack.Count - 1)
|
||||
cata.Tree.Pair arg0_0 arg1_0 arg2_0 |> treeStack.Add
|
||||
| Instruction.Tree_Sequential (arg0_0) ->
|
||||
let arg0_0_len = arg0_0
|
||||
|
||||
let arg0_0 =
|
||||
seq {
|
||||
for i = treeStack.Count - 1 downto treeStack.Count - arg0_0 do
|
||||
yield treeStack.[i]
|
||||
}
|
||||
|> Seq.toList
|
||||
|
||||
treeStack.RemoveRange (treeStack.Count - arg0_0_len, arg0_0_len)
|
||||
cata.Tree.Sequential arg0_0 |> treeStack.Add
|
||||
| Instruction.Tree_Builder ->
|
||||
let arg0_0 = treeStack.[treeStack.Count - 1]
|
||||
treeStack.RemoveAt (treeStack.Count - 1)
|
||||
let arg1_0 = treeBuilderStack.[treeBuilderStack.Count - 1]
|
||||
treeBuilderStack.RemoveAt (treeBuilderStack.Count - 1)
|
||||
cata.Tree.Builder arg0_0 arg1_0 |> treeStack.Add
|
||||
|
||||
treeBuilderStack, treeStack
|
||||
|
||||
/// Execute the catamorphism.
|
||||
let runTreeBuilder
|
||||
(cata : TreeCata<'b, 'a, 'TreeBuilderRet, 'TreeRet>)
|
||||
(x : TreeBuilder<'b, 'a>)
|
||||
: 'TreeBuilderRet
|
||||
=
|
||||
let instructions = ResizeArray ()
|
||||
instructions.Add (Instruction.Process__TreeBuilder x)
|
||||
let treeBuilderRetStack, treeRetStack = loop cata instructions
|
||||
Seq.exactlyOne treeBuilderRetStack
|
||||
|
||||
/// Execute the catamorphism.
|
||||
let runTree (cata : TreeCata<'b, 'a, 'TreeBuilderRet, 'TreeRet>) (x : Tree<'a, 'b>) : 'TreeRet =
|
||||
let instructions = ResizeArray ()
|
||||
instructions.Add (Instruction.Process__Tree x)
|
||||
let treeBuilderRetStack, treeRetStack = loop cata instructions
|
||||
Seq.exactlyOne treeRetStack
|
152
ConsumePlugin/GeneratedFileSystem.fs
Normal file
152
ConsumePlugin/GeneratedFileSystem.fs
Normal file
@@ -0,0 +1,152 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// This code was generated by myriad.
|
||||
// Changes to this file will be lost when the code is regenerated.
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
namespace ConsumePlugin
|
||||
|
||||
open WoofWare.Myriad.Plugins
|
||||
|
||||
/// Description of how to combine cases during a fold
|
||||
type FileSystemItemCataCase<'FileSystemItem> =
|
||||
/// How to operate on the Directory case
|
||||
abstract Directory : name : string -> dirSize : int -> contents : 'FileSystemItem list -> 'FileSystemItem
|
||||
/// How to operate on the File case
|
||||
abstract File : File -> 'FileSystemItem
|
||||
|
||||
/// Specifies how to perform a fold (catamorphism) over the type FileSystemItem and its friends.
|
||||
type FileSystemCata<'FileSystemItem> =
|
||||
{
|
||||
/// How to perform a fold (catamorphism) over the type FileSystemItem
|
||||
FileSystemItem : FileSystemItemCataCase<'FileSystemItem>
|
||||
}
|
||||
|
||||
/// Methods to perform a catamorphism over the type FileSystemItem
|
||||
[<RequireQualifiedAccess>]
|
||||
module FileSystemItemCata =
|
||||
[<RequireQualifiedAccess>]
|
||||
type private Instruction =
|
||||
| Process__FileSystemItem of FileSystemItem
|
||||
| FileSystemItem_Directory of string * int * int
|
||||
|
||||
let private loop (cata : FileSystemCata<'FileSystemItem>) (instructions : ResizeArray<Instruction>) =
|
||||
let fileSystemItemStack = ResizeArray<'FileSystemItem> ()
|
||||
|
||||
while instructions.Count > 0 do
|
||||
let currentInstruction = instructions.[instructions.Count - 1]
|
||||
instructions.RemoveAt (instructions.Count - 1)
|
||||
|
||||
match currentInstruction with
|
||||
| Instruction.Process__FileSystemItem x ->
|
||||
match x with
|
||||
| FileSystemItem.Directory ({
|
||||
Name = name
|
||||
DirSize = dirSize
|
||||
Contents = contents
|
||||
}) ->
|
||||
instructions.Add (Instruction.FileSystemItem_Directory (name, dirSize, (List.length contents)))
|
||||
|
||||
for elt in contents do
|
||||
instructions.Add (Instruction.Process__FileSystemItem elt)
|
||||
| FileSystemItem.File (arg0_0) -> cata.FileSystemItem.File arg0_0 |> fileSystemItemStack.Add
|
||||
| Instruction.FileSystemItem_Directory (name, dirSize, contents) ->
|
||||
let contents_len = contents
|
||||
|
||||
let contents =
|
||||
seq {
|
||||
for i = fileSystemItemStack.Count - 1 downto fileSystemItemStack.Count - contents do
|
||||
yield fileSystemItemStack.[i]
|
||||
}
|
||||
|> Seq.toList
|
||||
|
||||
fileSystemItemStack.RemoveRange (fileSystemItemStack.Count - contents_len, contents_len)
|
||||
cata.FileSystemItem.Directory name dirSize contents |> fileSystemItemStack.Add
|
||||
|
||||
fileSystemItemStack
|
||||
|
||||
/// Execute the catamorphism.
|
||||
let runFileSystemItem (cata : FileSystemCata<'FileSystemItemRet>) (x : FileSystemItem) : 'FileSystemItemRet =
|
||||
let instructions = ResizeArray ()
|
||||
instructions.Add (Instruction.Process__FileSystemItem x)
|
||||
let fileSystemItemRetStack = loop cata instructions
|
||||
Seq.exactlyOne fileSystemItemRetStack
|
||||
namespace ConsumePlugin
|
||||
|
||||
open WoofWare.Myriad.Plugins
|
||||
|
||||
/// Description of how to combine cases during a fold
|
||||
type GiftCataCase<'Gift> =
|
||||
/// How to operate on the Book case
|
||||
abstract Book : Book -> 'Gift
|
||||
/// How to operate on the Chocolate case
|
||||
abstract Chocolate : Chocolate -> 'Gift
|
||||
/// How to operate on the Wrapped case
|
||||
abstract Wrapped : 'Gift -> WrappingPaperStyle -> 'Gift
|
||||
/// How to operate on the Boxed case
|
||||
abstract Boxed : 'Gift -> 'Gift
|
||||
/// How to operate on the WithACard case
|
||||
abstract WithACard : 'Gift -> message : string -> 'Gift
|
||||
|
||||
/// Specifies how to perform a fold (catamorphism) over the type Gift and its friends.
|
||||
type GiftCata<'Gift> =
|
||||
{
|
||||
/// How to perform a fold (catamorphism) over the type Gift
|
||||
Gift : GiftCataCase<'Gift>
|
||||
}
|
||||
|
||||
/// Methods to perform a catamorphism over the type Gift
|
||||
[<RequireQualifiedAccess>]
|
||||
module GiftCata =
|
||||
[<RequireQualifiedAccess>]
|
||||
type private Instruction =
|
||||
| Process__Gift of Gift
|
||||
| Gift_Wrapped of WrappingPaperStyle
|
||||
| Gift_Boxed
|
||||
| Gift_WithACard of string
|
||||
|
||||
let private loop (cata : GiftCata<'Gift>) (instructions : ResizeArray<Instruction>) =
|
||||
let giftStack = ResizeArray<'Gift> ()
|
||||
|
||||
while instructions.Count > 0 do
|
||||
let currentInstruction = instructions.[instructions.Count - 1]
|
||||
instructions.RemoveAt (instructions.Count - 1)
|
||||
|
||||
match currentInstruction with
|
||||
| Instruction.Process__Gift x ->
|
||||
match x with
|
||||
| Gift.Book (arg0_0) -> cata.Gift.Book arg0_0 |> giftStack.Add
|
||||
| Gift.Chocolate (arg0_0) -> cata.Gift.Chocolate arg0_0 |> giftStack.Add
|
||||
| Gift.Wrapped (arg0_0, arg1_0) ->
|
||||
instructions.Add (Instruction.Gift_Wrapped (arg1_0))
|
||||
instructions.Add (Instruction.Process__Gift arg0_0)
|
||||
| Gift.Boxed (arg0_0) ->
|
||||
instructions.Add Instruction.Gift_Boxed
|
||||
instructions.Add (Instruction.Process__Gift arg0_0)
|
||||
| Gift.WithACard (arg0_0, message) ->
|
||||
instructions.Add (Instruction.Gift_WithACard (message))
|
||||
instructions.Add (Instruction.Process__Gift arg0_0)
|
||||
| Instruction.Gift_Wrapped (arg1_0) ->
|
||||
let arg0_0 = giftStack.[giftStack.Count - 1]
|
||||
giftStack.RemoveAt (giftStack.Count - 1)
|
||||
cata.Gift.Wrapped arg0_0 arg1_0 |> giftStack.Add
|
||||
| Instruction.Gift_Boxed ->
|
||||
let arg0_0 = giftStack.[giftStack.Count - 1]
|
||||
giftStack.RemoveAt (giftStack.Count - 1)
|
||||
cata.Gift.Boxed arg0_0 |> giftStack.Add
|
||||
| Instruction.Gift_WithACard (message) ->
|
||||
let arg0_0 = giftStack.[giftStack.Count - 1]
|
||||
giftStack.RemoveAt (giftStack.Count - 1)
|
||||
cata.Gift.WithACard arg0_0 message |> giftStack.Add
|
||||
|
||||
giftStack
|
||||
|
||||
/// Execute the catamorphism.
|
||||
let runGift (cata : GiftCata<'GiftRet>) (x : Gift) : 'GiftRet =
|
||||
let instructions = ResizeArray ()
|
||||
instructions.Add (Instruction.Process__Gift x)
|
||||
let giftRetStack = loop cata instructions
|
||||
Seq.exactlyOne giftRetStack
|
@@ -3,6 +3,8 @@
|
||||
// Changes to this file will be lost when the code is regenerated.
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
|
||||
|
||||
namespace ConsumePlugin
|
||||
|
||||
/// Module containing JSON parsing methods for the InnerType type
|
||||
@@ -60,7 +62,17 @@ module JsonRecordType =
|
||||
|> Seq.map (fun elt -> elt.AsValue().GetValue<string> ())
|
||||
|> Array.ofSeq
|
||||
|
||||
let D = InnerType.jsonParse node.["d"]
|
||||
let D =
|
||||
InnerType.jsonParse (
|
||||
match node.["d"] with
|
||||
| null ->
|
||||
raise (
|
||||
System.Collections.Generic.KeyNotFoundException (
|
||||
sprintf "Required key '%s' not found on JSON object" ("d")
|
||||
)
|
||||
)
|
||||
| v -> v
|
||||
)
|
||||
|
||||
let C =
|
||||
(match node.["hi"] with
|
||||
@@ -107,3 +119,68 @@ module JsonRecordType =
|
||||
E = E
|
||||
F = F
|
||||
}
|
||||
namespace ConsumePlugin
|
||||
|
||||
/// Module containing JSON parsing extension members for the ToGetExtensionMethod type
|
||||
[<AutoOpen>]
|
||||
module ToGetExtensionMethodJsonParseExtension =
|
||||
/// Extension methods for JSON parsing
|
||||
type ToGetExtensionMethod with
|
||||
|
||||
/// Parse from a JSON node.
|
||||
static member jsonParse (node : System.Text.Json.Nodes.JsonNode) : ToGetExtensionMethod =
|
||||
let Sailor =
|
||||
(match node.["sailor"] with
|
||||
| null ->
|
||||
raise (
|
||||
System.Collections.Generic.KeyNotFoundException (
|
||||
sprintf "Required key '%s' not found on JSON object" ("sailor")
|
||||
)
|
||||
)
|
||||
| v -> v)
|
||||
.AsValue()
|
||||
.GetValue<float> ()
|
||||
|
||||
let Soldier =
|
||||
(match node.["soldier"] with
|
||||
| null ->
|
||||
raise (
|
||||
System.Collections.Generic.KeyNotFoundException (
|
||||
sprintf "Required key '%s' not found on JSON object" ("soldier")
|
||||
)
|
||||
)
|
||||
| v -> v)
|
||||
.AsValue()
|
||||
.GetValue<string> ()
|
||||
|> System.Uri
|
||||
|
||||
let Tailor =
|
||||
(match node.["tailor"] with
|
||||
| null ->
|
||||
raise (
|
||||
System.Collections.Generic.KeyNotFoundException (
|
||||
sprintf "Required key '%s' not found on JSON object" ("tailor")
|
||||
)
|
||||
)
|
||||
| v -> v)
|
||||
.AsValue()
|
||||
.GetValue<int> ()
|
||||
|
||||
let Tinker =
|
||||
(match node.["tinker"] with
|
||||
| null ->
|
||||
raise (
|
||||
System.Collections.Generic.KeyNotFoundException (
|
||||
sprintf "Required key '%s' not found on JSON object" ("tinker")
|
||||
)
|
||||
)
|
||||
| v -> v)
|
||||
.AsValue()
|
||||
.GetValue<string> ()
|
||||
|
||||
{
|
||||
Tinker = Tinker
|
||||
Tailor = Tailor
|
||||
Soldier = Soldier
|
||||
Sailor = Sailor
|
||||
}
|
||||
|
173
ConsumePlugin/GeneratedMock.fs
Normal file
173
ConsumePlugin/GeneratedMock.fs
Normal file
@@ -0,0 +1,173 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// This code was generated by myriad.
|
||||
// Changes to this file will be lost when the code is regenerated.
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace SomeNamespace
|
||||
|
||||
open WoofWare.Myriad.Plugins
|
||||
|
||||
/// Mock record type for an interface
|
||||
type internal PublicTypeMock =
|
||||
{
|
||||
Mem1 : string * int -> string list
|
||||
Mem2 : string -> int
|
||||
Mem3 : int * option<System.Threading.CancellationToken> -> string
|
||||
}
|
||||
|
||||
/// An implementation where every method throws.
|
||||
static member Empty : PublicTypeMock =
|
||||
{
|
||||
Mem1 = (fun x -> raise (System.NotImplementedException "Unimplemented mock function"))
|
||||
Mem2 = (fun x -> raise (System.NotImplementedException "Unimplemented mock function"))
|
||||
Mem3 = (fun x -> raise (System.NotImplementedException "Unimplemented mock function"))
|
||||
}
|
||||
|
||||
interface IPublicType with
|
||||
member this.Mem1 (arg_0_0, arg_0_1) = this.Mem1 (arg_0_0, arg_0_1)
|
||||
member this.Mem2 (arg_0_0) = this.Mem2 (arg_0_0)
|
||||
member this.Mem3 (arg_0_0, arg_0_1) = this.Mem3 (arg_0_0, arg_0_1)
|
||||
namespace SomeNamespace
|
||||
|
||||
open WoofWare.Myriad.Plugins
|
||||
|
||||
/// Mock record type for an interface
|
||||
type public PublicTypeInternalFalseMock =
|
||||
{
|
||||
Mem1 : string * int -> string list
|
||||
Mem2 : string -> int
|
||||
Mem3 : int * option<System.Threading.CancellationToken> -> string
|
||||
}
|
||||
|
||||
/// An implementation where every method throws.
|
||||
static member Empty : PublicTypeInternalFalseMock =
|
||||
{
|
||||
Mem1 = (fun x -> raise (System.NotImplementedException "Unimplemented mock function"))
|
||||
Mem2 = (fun x -> raise (System.NotImplementedException "Unimplemented mock function"))
|
||||
Mem3 = (fun x -> raise (System.NotImplementedException "Unimplemented mock function"))
|
||||
}
|
||||
|
||||
interface IPublicTypeInternalFalse with
|
||||
member this.Mem1 (arg_0_0, arg_0_1) = this.Mem1 (arg_0_0, arg_0_1)
|
||||
member this.Mem2 (arg_0_0) = this.Mem2 (arg_0_0)
|
||||
member this.Mem3 (arg_0_0, arg_0_1) = this.Mem3 (arg_0_0, arg_0_1)
|
||||
namespace SomeNamespace
|
||||
|
||||
open WoofWare.Myriad.Plugins
|
||||
|
||||
/// Mock record type for an interface
|
||||
type internal InternalTypeMock =
|
||||
{
|
||||
Mem1 : string * int -> unit
|
||||
Mem2 : string -> int
|
||||
}
|
||||
|
||||
/// An implementation where every method throws.
|
||||
static member Empty : InternalTypeMock =
|
||||
{
|
||||
Mem1 = (fun x -> raise (System.NotImplementedException "Unimplemented mock function"))
|
||||
Mem2 = (fun x -> raise (System.NotImplementedException "Unimplemented mock function"))
|
||||
}
|
||||
|
||||
interface InternalType with
|
||||
member this.Mem1 (arg_0_0, arg_0_1) = this.Mem1 (arg_0_0, arg_0_1)
|
||||
member this.Mem2 (arg_0_0) = this.Mem2 (arg_0_0)
|
||||
namespace SomeNamespace
|
||||
|
||||
open WoofWare.Myriad.Plugins
|
||||
|
||||
/// Mock record type for an interface
|
||||
type private PrivateTypeMock =
|
||||
{
|
||||
Mem1 : string * int -> unit
|
||||
Mem2 : string -> int
|
||||
}
|
||||
|
||||
/// An implementation where every method throws.
|
||||
static member Empty : PrivateTypeMock =
|
||||
{
|
||||
Mem1 = (fun x -> raise (System.NotImplementedException "Unimplemented mock function"))
|
||||
Mem2 = (fun x -> raise (System.NotImplementedException "Unimplemented mock function"))
|
||||
}
|
||||
|
||||
interface PrivateType with
|
||||
member this.Mem1 (arg_0_0, arg_0_1) = this.Mem1 (arg_0_0, arg_0_1)
|
||||
member this.Mem2 (arg_0_0) = this.Mem2 (arg_0_0)
|
||||
namespace SomeNamespace
|
||||
|
||||
open WoofWare.Myriad.Plugins
|
||||
|
||||
/// Mock record type for an interface
|
||||
type private PrivateTypeInternalFalseMock =
|
||||
{
|
||||
Mem1 : string * int -> unit
|
||||
Mem2 : string -> int
|
||||
}
|
||||
|
||||
/// An implementation where every method throws.
|
||||
static member Empty : PrivateTypeInternalFalseMock =
|
||||
{
|
||||
Mem1 = (fun x -> raise (System.NotImplementedException "Unimplemented mock function"))
|
||||
Mem2 = (fun x -> raise (System.NotImplementedException "Unimplemented mock function"))
|
||||
}
|
||||
|
||||
interface PrivateTypeInternalFalse with
|
||||
member this.Mem1 (arg_0_0, arg_0_1) = this.Mem1 (arg_0_0, arg_0_1)
|
||||
member this.Mem2 (arg_0_0) = this.Mem2 (arg_0_0)
|
||||
namespace SomeNamespace
|
||||
|
||||
open WoofWare.Myriad.Plugins
|
||||
|
||||
/// Mock record type for an interface
|
||||
type internal VeryPublicTypeMock<'a, 'b> =
|
||||
{
|
||||
Mem1 : 'a -> 'b
|
||||
}
|
||||
|
||||
/// An implementation where every method throws.
|
||||
static member Empty () : VeryPublicTypeMock<'a, 'b> =
|
||||
{
|
||||
Mem1 = (fun x -> raise (System.NotImplementedException "Unimplemented mock function"))
|
||||
}
|
||||
|
||||
interface VeryPublicType<'a, 'b> with
|
||||
member this.Mem1 (arg_0_0) = this.Mem1 (arg_0_0)
|
||||
namespace SomeNamespace
|
||||
|
||||
open WoofWare.Myriad.Plugins
|
||||
|
||||
/// Mock record type for an interface
|
||||
type internal CurriedMock<'a> =
|
||||
{
|
||||
Mem1 : int -> 'a -> string
|
||||
Mem2 : int * string -> 'a -> string
|
||||
Mem3 : (int * string) -> 'a -> string
|
||||
Mem4 : (int * string) -> ('a * int) -> string
|
||||
Mem5 : int * string -> ('a * int) -> string
|
||||
Mem6 : int * string -> 'a * int -> string
|
||||
}
|
||||
|
||||
/// An implementation where every method throws.
|
||||
static member Empty () : CurriedMock<'a> =
|
||||
{
|
||||
Mem1 = (fun x -> raise (System.NotImplementedException "Unimplemented mock function"))
|
||||
Mem2 = (fun x -> raise (System.NotImplementedException "Unimplemented mock function"))
|
||||
Mem3 = (fun x -> raise (System.NotImplementedException "Unimplemented mock function"))
|
||||
Mem4 = (fun x -> raise (System.NotImplementedException "Unimplemented mock function"))
|
||||
Mem5 = (fun x -> raise (System.NotImplementedException "Unimplemented mock function"))
|
||||
Mem6 = (fun x -> raise (System.NotImplementedException "Unimplemented mock function"))
|
||||
}
|
||||
|
||||
interface Curried<'a> with
|
||||
member this.Mem1 (arg_0_0) (arg_1_0) = this.Mem1 (arg_0_0) (arg_1_0)
|
||||
member this.Mem2 (arg_0_0, arg_0_1) (arg_1_0) = this.Mem2 (arg_0_0, arg_0_1) (arg_1_0)
|
||||
member this.Mem3 ((arg_0_0, arg_0_1)) (arg_1_0) = this.Mem3 (arg_0_0, arg_0_1) (arg_1_0)
|
||||
|
||||
member this.Mem4 ((arg_0_0, arg_0_1)) ((arg_1_0, arg_1_1)) =
|
||||
this.Mem4 (arg_0_0, arg_0_1) (arg_1_0, arg_1_1)
|
||||
|
||||
member this.Mem5 (arg_0_0, arg_0_1) ((arg_1_0, arg_1_1)) =
|
||||
this.Mem5 (arg_0_0, arg_0_1) (arg_1_0, arg_1_1)
|
||||
|
||||
member this.Mem6 (arg_0_0, arg_0_1) (arg_1_0, arg_1_1) =
|
||||
this.Mem6 (arg_0_0, arg_0_1) (arg_1_0, arg_1_1)
|
@@ -3,6 +3,41 @@
|
||||
// Changes to this file will be lost when the code is regenerated.
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
|
||||
namespace PureGym
|
||||
|
||||
open System
|
||||
open System.Text.Json.Serialization
|
||||
|
||||
/// Module containing JSON serializing extension members for the Member type
|
||||
[<AutoOpen>]
|
||||
module MemberJsonSerializeExtension =
|
||||
/// Extension methods for JSON parsing
|
||||
type Member with
|
||||
|
||||
/// Serialize to a JSON node
|
||||
static member toJsonNode (input : Member) : System.Text.Json.Nodes.JsonNode =
|
||||
let node = System.Text.Json.Nodes.JsonObject ()
|
||||
|
||||
do
|
||||
node.Add ("id", System.Text.Json.Nodes.JsonValue.Create<int> input.Id)
|
||||
node.Add ("compoundMemberId", System.Text.Json.Nodes.JsonValue.Create<string> input.CompoundMemberId)
|
||||
node.Add ("firstName", System.Text.Json.Nodes.JsonValue.Create<string> input.FirstName)
|
||||
node.Add ("lastName", System.Text.Json.Nodes.JsonValue.Create<string> input.LastName)
|
||||
node.Add ("homeGymId", System.Text.Json.Nodes.JsonValue.Create<int> input.HomeGymId)
|
||||
node.Add ("homeGymName", System.Text.Json.Nodes.JsonValue.Create<string> input.HomeGymName)
|
||||
node.Add ("emailAddress", System.Text.Json.Nodes.JsonValue.Create<string> input.EmailAddress)
|
||||
node.Add ("gymAccessPin", System.Text.Json.Nodes.JsonValue.Create<string> input.GymAccessPin)
|
||||
node.Add ("dateofBirth", System.Text.Json.Nodes.JsonValue.Create<DateOnly> input.DateOfBirth)
|
||||
node.Add ("mobileNumber", System.Text.Json.Nodes.JsonValue.Create<string> input.MobileNumber)
|
||||
node.Add ("postCode", System.Text.Json.Nodes.JsonValue.Create<string> input.Postcode)
|
||||
node.Add ("membershipName", System.Text.Json.Nodes.JsonValue.Create<string> input.MembershipName)
|
||||
node.Add ("membershipLevel", System.Text.Json.Nodes.JsonValue.Create<int> input.MembershipLevel)
|
||||
node.Add ("suspendedReason", System.Text.Json.Nodes.JsonValue.Create<int> input.SuspendedReason)
|
||||
node.Add ("memberStatus", System.Text.Json.Nodes.JsonValue.Create<int> input.MemberStatus)
|
||||
|
||||
node :> _
|
||||
|
||||
namespace PureGym
|
||||
|
||||
/// Module containing JSON parsing methods for the GymOpeningHours type
|
||||
@@ -253,9 +288,41 @@ module Gym =
|
||||
.AsValue()
|
||||
.GetValue<string> ()
|
||||
|
||||
let Location = GymLocation.jsonParse node.["location"]
|
||||
let AccessOptions = GymAccessOptions.jsonParse node.["accessOptions"]
|
||||
let GymOpeningHours = GymOpeningHours.jsonParse node.["gymOpeningHours"]
|
||||
let Location =
|
||||
GymLocation.jsonParse (
|
||||
match node.["location"] with
|
||||
| null ->
|
||||
raise (
|
||||
System.Collections.Generic.KeyNotFoundException (
|
||||
sprintf "Required key '%s' not found on JSON object" ("location")
|
||||
)
|
||||
)
|
||||
| v -> v
|
||||
)
|
||||
|
||||
let AccessOptions =
|
||||
GymAccessOptions.jsonParse (
|
||||
match node.["accessOptions"] with
|
||||
| null ->
|
||||
raise (
|
||||
System.Collections.Generic.KeyNotFoundException (
|
||||
sprintf "Required key '%s' not found on JSON object" ("accessOptions")
|
||||
)
|
||||
)
|
||||
| v -> v
|
||||
)
|
||||
|
||||
let GymOpeningHours =
|
||||
GymOpeningHours.jsonParse (
|
||||
match node.["gymOpeningHours"] with
|
||||
| null ->
|
||||
raise (
|
||||
System.Collections.Generic.KeyNotFoundException (
|
||||
sprintf "Required key '%s' not found on JSON object" ("gymOpeningHours")
|
||||
)
|
||||
)
|
||||
| v -> v
|
||||
)
|
||||
|
||||
let EmailAddress =
|
||||
(match node.["emailAddress"] with
|
||||
@@ -281,7 +348,17 @@ module Gym =
|
||||
.AsValue()
|
||||
.GetValue<string> ()
|
||||
|
||||
let Address = GymAddress.jsonParse node.["address"]
|
||||
let Address =
|
||||
GymAddress.jsonParse (
|
||||
match node.["address"] with
|
||||
| null ->
|
||||
raise (
|
||||
System.Collections.Generic.KeyNotFoundException (
|
||||
sprintf "Required key '%s' not found on JSON object" ("address")
|
||||
)
|
||||
)
|
||||
| v -> v
|
||||
)
|
||||
|
||||
let Status =
|
||||
(match node.["status"] with
|
||||
@@ -334,210 +411,212 @@ module Gym =
|
||||
}
|
||||
namespace PureGym
|
||||
|
||||
/// Module containing JSON parsing methods for the Member type
|
||||
[<RequireQualifiedAccess>]
|
||||
[<CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix)>]
|
||||
module Member =
|
||||
/// Parse from a JSON node.
|
||||
let jsonParse (node : System.Text.Json.Nodes.JsonNode) : Member =
|
||||
let MemberStatus =
|
||||
(match node.["memberStatus"] with
|
||||
| null ->
|
||||
raise (
|
||||
System.Collections.Generic.KeyNotFoundException (
|
||||
sprintf "Required key '%s' not found on JSON object" ("memberStatus")
|
||||
)
|
||||
)
|
||||
| v -> v)
|
||||
.AsValue()
|
||||
.GetValue<int> ()
|
||||
/// Module containing JSON parsing extension members for the Member type
|
||||
[<AutoOpen>]
|
||||
module MemberJsonParseExtension =
|
||||
/// Extension methods for JSON parsing
|
||||
type Member with
|
||||
|
||||
let SuspendedReason =
|
||||
(match node.["suspendedReason"] with
|
||||
| null ->
|
||||
raise (
|
||||
System.Collections.Generic.KeyNotFoundException (
|
||||
sprintf "Required key '%s' not found on JSON object" ("suspendedReason")
|
||||
/// Parse from a JSON node.
|
||||
static member jsonParse (node : System.Text.Json.Nodes.JsonNode) : Member =
|
||||
let MemberStatus =
|
||||
(match node.["memberStatus"] with
|
||||
| null ->
|
||||
raise (
|
||||
System.Collections.Generic.KeyNotFoundException (
|
||||
sprintf "Required key '%s' not found on JSON object" ("memberStatus")
|
||||
)
|
||||
)
|
||||
)
|
||||
| v -> v)
|
||||
.AsValue()
|
||||
.GetValue<int> ()
|
||||
| v -> v)
|
||||
.AsValue()
|
||||
.GetValue<int> ()
|
||||
|
||||
let MembershipLevel =
|
||||
(match node.["membershipLevel"] with
|
||||
| null ->
|
||||
raise (
|
||||
System.Collections.Generic.KeyNotFoundException (
|
||||
sprintf "Required key '%s' not found on JSON object" ("membershipLevel")
|
||||
let SuspendedReason =
|
||||
(match node.["suspendedReason"] with
|
||||
| null ->
|
||||
raise (
|
||||
System.Collections.Generic.KeyNotFoundException (
|
||||
sprintf "Required key '%s' not found on JSON object" ("suspendedReason")
|
||||
)
|
||||
)
|
||||
)
|
||||
| v -> v)
|
||||
.AsValue()
|
||||
.GetValue<int> ()
|
||||
| v -> v)
|
||||
.AsValue()
|
||||
.GetValue<int> ()
|
||||
|
||||
let MembershipName =
|
||||
(match node.["membershipName"] with
|
||||
| null ->
|
||||
raise (
|
||||
System.Collections.Generic.KeyNotFoundException (
|
||||
sprintf "Required key '%s' not found on JSON object" ("membershipName")
|
||||
let MembershipLevel =
|
||||
(match node.["membershipLevel"] with
|
||||
| null ->
|
||||
raise (
|
||||
System.Collections.Generic.KeyNotFoundException (
|
||||
sprintf "Required key '%s' not found on JSON object" ("membershipLevel")
|
||||
)
|
||||
)
|
||||
)
|
||||
| v -> v)
|
||||
.AsValue()
|
||||
.GetValue<string> ()
|
||||
| v -> v)
|
||||
.AsValue()
|
||||
.GetValue<int> ()
|
||||
|
||||
let Postcode =
|
||||
(match node.["postCode"] with
|
||||
| null ->
|
||||
raise (
|
||||
System.Collections.Generic.KeyNotFoundException (
|
||||
sprintf "Required key '%s' not found on JSON object" ("postCode")
|
||||
let MembershipName =
|
||||
(match node.["membershipName"] with
|
||||
| null ->
|
||||
raise (
|
||||
System.Collections.Generic.KeyNotFoundException (
|
||||
sprintf "Required key '%s' not found on JSON object" ("membershipName")
|
||||
)
|
||||
)
|
||||
)
|
||||
| v -> v)
|
||||
.AsValue()
|
||||
.GetValue<string> ()
|
||||
| v -> v)
|
||||
.AsValue()
|
||||
.GetValue<string> ()
|
||||
|
||||
let MobileNumber =
|
||||
(match node.["mobileNumber"] with
|
||||
| null ->
|
||||
raise (
|
||||
System.Collections.Generic.KeyNotFoundException (
|
||||
sprintf "Required key '%s' not found on JSON object" ("mobileNumber")
|
||||
let Postcode =
|
||||
(match node.["postCode"] with
|
||||
| null ->
|
||||
raise (
|
||||
System.Collections.Generic.KeyNotFoundException (
|
||||
sprintf "Required key '%s' not found on JSON object" ("postCode")
|
||||
)
|
||||
)
|
||||
)
|
||||
| v -> v)
|
||||
.AsValue()
|
||||
.GetValue<string> ()
|
||||
| v -> v)
|
||||
.AsValue()
|
||||
.GetValue<string> ()
|
||||
|
||||
let DateOfBirth =
|
||||
(match node.["dateofBirth"] with
|
||||
| null ->
|
||||
raise (
|
||||
System.Collections.Generic.KeyNotFoundException (
|
||||
sprintf "Required key '%s' not found on JSON object" ("dateofBirth")
|
||||
let MobileNumber =
|
||||
(match node.["mobileNumber"] with
|
||||
| null ->
|
||||
raise (
|
||||
System.Collections.Generic.KeyNotFoundException (
|
||||
sprintf "Required key '%s' not found on JSON object" ("mobileNumber")
|
||||
)
|
||||
)
|
||||
)
|
||||
| v -> v)
|
||||
.AsValue()
|
||||
.GetValue<string> ()
|
||||
|> System.DateOnly.Parse
|
||||
| v -> v)
|
||||
.AsValue()
|
||||
.GetValue<string> ()
|
||||
|
||||
let GymAccessPin =
|
||||
(match node.["gymAccessPin"] with
|
||||
| null ->
|
||||
raise (
|
||||
System.Collections.Generic.KeyNotFoundException (
|
||||
sprintf "Required key '%s' not found on JSON object" ("gymAccessPin")
|
||||
let DateOfBirth =
|
||||
(match node.["dateofBirth"] with
|
||||
| null ->
|
||||
raise (
|
||||
System.Collections.Generic.KeyNotFoundException (
|
||||
sprintf "Required key '%s' not found on JSON object" ("dateofBirth")
|
||||
)
|
||||
)
|
||||
)
|
||||
| v -> v)
|
||||
.AsValue()
|
||||
.GetValue<string> ()
|
||||
| v -> v)
|
||||
.AsValue()
|
||||
.GetValue<string> ()
|
||||
|> System.DateOnly.Parse
|
||||
|
||||
let EmailAddress =
|
||||
(match node.["emailAddress"] with
|
||||
| null ->
|
||||
raise (
|
||||
System.Collections.Generic.KeyNotFoundException (
|
||||
sprintf "Required key '%s' not found on JSON object" ("emailAddress")
|
||||
let GymAccessPin =
|
||||
(match node.["gymAccessPin"] with
|
||||
| null ->
|
||||
raise (
|
||||
System.Collections.Generic.KeyNotFoundException (
|
||||
sprintf "Required key '%s' not found on JSON object" ("gymAccessPin")
|
||||
)
|
||||
)
|
||||
)
|
||||
| v -> v)
|
||||
.AsValue()
|
||||
.GetValue<string> ()
|
||||
| v -> v)
|
||||
.AsValue()
|
||||
.GetValue<string> ()
|
||||
|
||||
let HomeGymName =
|
||||
(match node.["homeGymName"] with
|
||||
| null ->
|
||||
raise (
|
||||
System.Collections.Generic.KeyNotFoundException (
|
||||
sprintf "Required key '%s' not found on JSON object" ("homeGymName")
|
||||
let EmailAddress =
|
||||
(match node.["emailAddress"] with
|
||||
| null ->
|
||||
raise (
|
||||
System.Collections.Generic.KeyNotFoundException (
|
||||
sprintf "Required key '%s' not found on JSON object" ("emailAddress")
|
||||
)
|
||||
)
|
||||
)
|
||||
| v -> v)
|
||||
.AsValue()
|
||||
.GetValue<string> ()
|
||||
| v -> v)
|
||||
.AsValue()
|
||||
.GetValue<string> ()
|
||||
|
||||
let HomeGymId =
|
||||
(match node.["homeGymId"] with
|
||||
| null ->
|
||||
raise (
|
||||
System.Collections.Generic.KeyNotFoundException (
|
||||
sprintf "Required key '%s' not found on JSON object" ("homeGymId")
|
||||
let HomeGymName =
|
||||
(match node.["homeGymName"] with
|
||||
| null ->
|
||||
raise (
|
||||
System.Collections.Generic.KeyNotFoundException (
|
||||
sprintf "Required key '%s' not found on JSON object" ("homeGymName")
|
||||
)
|
||||
)
|
||||
)
|
||||
| v -> v)
|
||||
.AsValue()
|
||||
.GetValue<int> ()
|
||||
| v -> v)
|
||||
.AsValue()
|
||||
.GetValue<string> ()
|
||||
|
||||
let LastName =
|
||||
(match node.["lastName"] with
|
||||
| null ->
|
||||
raise (
|
||||
System.Collections.Generic.KeyNotFoundException (
|
||||
sprintf "Required key '%s' not found on JSON object" ("lastName")
|
||||
let HomeGymId =
|
||||
(match node.["homeGymId"] with
|
||||
| null ->
|
||||
raise (
|
||||
System.Collections.Generic.KeyNotFoundException (
|
||||
sprintf "Required key '%s' not found on JSON object" ("homeGymId")
|
||||
)
|
||||
)
|
||||
)
|
||||
| v -> v)
|
||||
.AsValue()
|
||||
.GetValue<string> ()
|
||||
| v -> v)
|
||||
.AsValue()
|
||||
.GetValue<int> ()
|
||||
|
||||
let FirstName =
|
||||
(match node.["firstName"] with
|
||||
| null ->
|
||||
raise (
|
||||
System.Collections.Generic.KeyNotFoundException (
|
||||
sprintf "Required key '%s' not found on JSON object" ("firstName")
|
||||
let LastName =
|
||||
(match node.["lastName"] with
|
||||
| null ->
|
||||
raise (
|
||||
System.Collections.Generic.KeyNotFoundException (
|
||||
sprintf "Required key '%s' not found on JSON object" ("lastName")
|
||||
)
|
||||
)
|
||||
)
|
||||
| v -> v)
|
||||
.AsValue()
|
||||
.GetValue<string> ()
|
||||
| v -> v)
|
||||
.AsValue()
|
||||
.GetValue<string> ()
|
||||
|
||||
let CompoundMemberId =
|
||||
(match node.["compoundMemberId"] with
|
||||
| null ->
|
||||
raise (
|
||||
System.Collections.Generic.KeyNotFoundException (
|
||||
sprintf "Required key '%s' not found on JSON object" ("compoundMemberId")
|
||||
let FirstName =
|
||||
(match node.["firstName"] with
|
||||
| null ->
|
||||
raise (
|
||||
System.Collections.Generic.KeyNotFoundException (
|
||||
sprintf "Required key '%s' not found on JSON object" ("firstName")
|
||||
)
|
||||
)
|
||||
)
|
||||
| v -> v)
|
||||
.AsValue()
|
||||
.GetValue<string> ()
|
||||
| v -> v)
|
||||
.AsValue()
|
||||
.GetValue<string> ()
|
||||
|
||||
let Id =
|
||||
(match node.["id"] with
|
||||
| null ->
|
||||
raise (
|
||||
System.Collections.Generic.KeyNotFoundException (
|
||||
sprintf "Required key '%s' not found on JSON object" ("id")
|
||||
let CompoundMemberId =
|
||||
(match node.["compoundMemberId"] with
|
||||
| null ->
|
||||
raise (
|
||||
System.Collections.Generic.KeyNotFoundException (
|
||||
sprintf "Required key '%s' not found on JSON object" ("compoundMemberId")
|
||||
)
|
||||
)
|
||||
)
|
||||
| v -> v)
|
||||
.AsValue()
|
||||
.GetValue<int> ()
|
||||
| v -> v)
|
||||
.AsValue()
|
||||
.GetValue<string> ()
|
||||
|
||||
{
|
||||
Id = Id
|
||||
CompoundMemberId = CompoundMemberId
|
||||
FirstName = FirstName
|
||||
LastName = LastName
|
||||
HomeGymId = HomeGymId
|
||||
HomeGymName = HomeGymName
|
||||
EmailAddress = EmailAddress
|
||||
GymAccessPin = GymAccessPin
|
||||
DateOfBirth = DateOfBirth
|
||||
MobileNumber = MobileNumber
|
||||
Postcode = Postcode
|
||||
MembershipName = MembershipName
|
||||
MembershipLevel = MembershipLevel
|
||||
SuspendedReason = SuspendedReason
|
||||
MemberStatus = MemberStatus
|
||||
}
|
||||
let Id =
|
||||
(match node.["id"] with
|
||||
| null ->
|
||||
raise (
|
||||
System.Collections.Generic.KeyNotFoundException (
|
||||
sprintf "Required key '%s' not found on JSON object" ("id")
|
||||
)
|
||||
)
|
||||
| v -> v)
|
||||
.AsValue()
|
||||
.GetValue<int> ()
|
||||
|
||||
{
|
||||
Id = Id
|
||||
CompoundMemberId = CompoundMemberId
|
||||
FirstName = FirstName
|
||||
LastName = LastName
|
||||
HomeGymId = HomeGymId
|
||||
HomeGymName = HomeGymName
|
||||
EmailAddress = EmailAddress
|
||||
GymAccessPin = GymAccessPin
|
||||
DateOfBirth = DateOfBirth
|
||||
MobileNumber = MobileNumber
|
||||
Postcode = Postcode
|
||||
MembershipName = MembershipName
|
||||
MembershipLevel = MembershipLevel
|
||||
SuspendedReason = SuspendedReason
|
||||
MemberStatus = MemberStatus
|
||||
}
|
||||
namespace PureGym
|
||||
|
||||
/// Module containing JSON parsing methods for the GymAttendance type
|
||||
@@ -856,7 +935,17 @@ namespace PureGym
|
||||
module Visit =
|
||||
/// Parse from a JSON node.
|
||||
let jsonParse (node : System.Text.Json.Nodes.JsonNode) : Visit =
|
||||
let Gym = VisitGym.jsonParse node.["Gym"]
|
||||
let Gym =
|
||||
VisitGym.jsonParse (
|
||||
match node.["Gym"] with
|
||||
| null ->
|
||||
raise (
|
||||
System.Collections.Generic.KeyNotFoundException (
|
||||
sprintf "Required key '%s' not found on JSON object" ("Gym")
|
||||
)
|
||||
)
|
||||
| v -> v
|
||||
)
|
||||
|
||||
let Duration =
|
||||
(match node.["Duration"] with
|
||||
@@ -909,8 +998,29 @@ namespace PureGym
|
||||
module SessionsSummary =
|
||||
/// Parse from a JSON node.
|
||||
let jsonParse (node : System.Text.Json.Nodes.JsonNode) : SessionsSummary =
|
||||
let ThisWeek = SessionsAggregate.jsonParse node.["ThisWeek"]
|
||||
let Total = SessionsAggregate.jsonParse node.["Total"]
|
||||
let ThisWeek =
|
||||
SessionsAggregate.jsonParse (
|
||||
match node.["ThisWeek"] with
|
||||
| null ->
|
||||
raise (
|
||||
System.Collections.Generic.KeyNotFoundException (
|
||||
sprintf "Required key '%s' not found on JSON object" ("ThisWeek")
|
||||
)
|
||||
)
|
||||
| v -> v
|
||||
)
|
||||
|
||||
let Total =
|
||||
SessionsAggregate.jsonParse (
|
||||
match node.["Total"] with
|
||||
| null ->
|
||||
raise (
|
||||
System.Collections.Generic.KeyNotFoundException (
|
||||
sprintf "Required key '%s' not found on JSON object" ("Total")
|
||||
)
|
||||
)
|
||||
| v -> v
|
||||
)
|
||||
|
||||
{
|
||||
Total = Total
|
||||
@@ -937,9 +1047,43 @@ module Sessions =
|
||||
|> Seq.map (fun elt -> Visit.jsonParse elt)
|
||||
|> List.ofSeq
|
||||
|
||||
let Summary = SessionsSummary.jsonParse node.["Summary"]
|
||||
let Summary =
|
||||
SessionsSummary.jsonParse (
|
||||
match node.["Summary"] with
|
||||
| null ->
|
||||
raise (
|
||||
System.Collections.Generic.KeyNotFoundException (
|
||||
sprintf "Required key '%s' not found on JSON object" ("Summary")
|
||||
)
|
||||
)
|
||||
| v -> v
|
||||
)
|
||||
|
||||
{
|
||||
Summary = Summary
|
||||
Visits = Visits
|
||||
}
|
||||
namespace PureGym
|
||||
|
||||
/// Module containing JSON parsing methods for the UriThing type
|
||||
[<RequireQualifiedAccess>]
|
||||
[<CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix)>]
|
||||
module UriThing =
|
||||
/// Parse from a JSON node.
|
||||
let jsonParse (node : System.Text.Json.Nodes.JsonNode) : UriThing =
|
||||
let SomeUri =
|
||||
(match node.["someUri"] with
|
||||
| null ->
|
||||
raise (
|
||||
System.Collections.Generic.KeyNotFoundException (
|
||||
sprintf "Required key '%s' not found on JSON object" ("someUri")
|
||||
)
|
||||
)
|
||||
| v -> v)
|
||||
.AsValue()
|
||||
.GetValue<string> ()
|
||||
|> System.Uri
|
||||
|
||||
{
|
||||
SomeUri = SomeUri
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
349
ConsumePlugin/GeneratedSerde.fs
Normal file
349
ConsumePlugin/GeneratedSerde.fs
Normal file
@@ -0,0 +1,349 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// This code was generated by myriad.
|
||||
// Changes to this file will be lost when the code is regenerated.
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
|
||||
namespace ConsumePlugin
|
||||
|
||||
open System
|
||||
open System.Collections.Generic
|
||||
open System.Text.Json.Serialization
|
||||
|
||||
/// Module containing JSON serializing extension members for the InnerTypeWithBoth type
|
||||
[<AutoOpen>]
|
||||
module InnerTypeWithBothJsonSerializeExtension =
|
||||
/// Extension methods for JSON parsing
|
||||
type InnerTypeWithBoth with
|
||||
|
||||
/// Serialize to a JSON node
|
||||
static member toJsonNode (input : InnerTypeWithBoth) : System.Text.Json.Nodes.JsonNode =
|
||||
let node = System.Text.Json.Nodes.JsonObject ()
|
||||
|
||||
do
|
||||
node.Add (("it's-a-me"), System.Text.Json.Nodes.JsonValue.Create<Guid> input.Thing)
|
||||
|
||||
node.Add (
|
||||
"map",
|
||||
(fun field ->
|
||||
let ret = System.Text.Json.Nodes.JsonObject ()
|
||||
|
||||
for (KeyValue (key, value)) in field do
|
||||
ret.Add (key.ToString (), System.Text.Json.Nodes.JsonValue.Create<Uri> value)
|
||||
|
||||
ret
|
||||
)
|
||||
input.Map
|
||||
)
|
||||
|
||||
node.Add (
|
||||
"readOnlyDict",
|
||||
(fun field ->
|
||||
let ret = System.Text.Json.Nodes.JsonObject ()
|
||||
|
||||
for (KeyValue (key, value)) in field do
|
||||
ret.Add (
|
||||
key.ToString (),
|
||||
(fun field ->
|
||||
let arr = System.Text.Json.Nodes.JsonArray ()
|
||||
|
||||
for mem in field do
|
||||
arr.Add (System.Text.Json.Nodes.JsonValue.Create<char> mem)
|
||||
|
||||
arr
|
||||
)
|
||||
value
|
||||
)
|
||||
|
||||
ret
|
||||
)
|
||||
input.ReadOnlyDict
|
||||
)
|
||||
|
||||
node.Add (
|
||||
"dict",
|
||||
(fun field ->
|
||||
let ret = System.Text.Json.Nodes.JsonObject ()
|
||||
|
||||
for (KeyValue (key, value)) in field do
|
||||
ret.Add (key.ToString (), System.Text.Json.Nodes.JsonValue.Create<bool> value)
|
||||
|
||||
ret
|
||||
)
|
||||
input.Dict
|
||||
)
|
||||
|
||||
node.Add (
|
||||
"concreteDict",
|
||||
(fun field ->
|
||||
let ret = System.Text.Json.Nodes.JsonObject ()
|
||||
|
||||
for (KeyValue (key, value)) in field do
|
||||
ret.Add (key.ToString (), InnerTypeWithBoth.toJsonNode value)
|
||||
|
||||
ret
|
||||
)
|
||||
input.ConcreteDict
|
||||
)
|
||||
|
||||
node :> _
|
||||
namespace ConsumePlugin
|
||||
|
||||
open System
|
||||
open System.Collections.Generic
|
||||
open System.Text.Json.Serialization
|
||||
|
||||
/// Module containing JSON serializing extension members for the JsonRecordTypeWithBoth type
|
||||
[<AutoOpen>]
|
||||
module JsonRecordTypeWithBothJsonSerializeExtension =
|
||||
/// Extension methods for JSON parsing
|
||||
type JsonRecordTypeWithBoth with
|
||||
|
||||
/// Serialize to a JSON node
|
||||
static member toJsonNode (input : JsonRecordTypeWithBoth) : System.Text.Json.Nodes.JsonNode =
|
||||
let node = System.Text.Json.Nodes.JsonObject ()
|
||||
|
||||
do
|
||||
node.Add ("a", System.Text.Json.Nodes.JsonValue.Create<int> input.A)
|
||||
node.Add ("b", System.Text.Json.Nodes.JsonValue.Create<string> input.B)
|
||||
|
||||
node.Add (
|
||||
"c",
|
||||
(fun field ->
|
||||
let arr = System.Text.Json.Nodes.JsonArray ()
|
||||
|
||||
for mem in field do
|
||||
arr.Add (System.Text.Json.Nodes.JsonValue.Create<int> mem)
|
||||
|
||||
arr
|
||||
)
|
||||
input.C
|
||||
)
|
||||
|
||||
node.Add ("d", InnerTypeWithBoth.toJsonNode input.D)
|
||||
|
||||
node.Add (
|
||||
"e",
|
||||
(fun field ->
|
||||
let arr = System.Text.Json.Nodes.JsonArray ()
|
||||
|
||||
for mem in field do
|
||||
arr.Add (System.Text.Json.Nodes.JsonValue.Create<string> mem)
|
||||
|
||||
arr
|
||||
)
|
||||
input.E
|
||||
)
|
||||
|
||||
node.Add (
|
||||
"f",
|
||||
(fun field ->
|
||||
let arr = System.Text.Json.Nodes.JsonArray ()
|
||||
|
||||
for mem in field do
|
||||
arr.Add (System.Text.Json.Nodes.JsonValue.Create<int> mem)
|
||||
|
||||
arr
|
||||
)
|
||||
input.F
|
||||
)
|
||||
|
||||
node :> _
|
||||
|
||||
namespace ConsumePlugin
|
||||
|
||||
/// Module containing JSON parsing extension members for the InnerTypeWithBoth type
|
||||
[<AutoOpen>]
|
||||
module InnerTypeWithBothJsonParseExtension =
|
||||
/// Extension methods for JSON parsing
|
||||
type InnerTypeWithBoth with
|
||||
|
||||
/// Parse from a JSON node.
|
||||
static member jsonParse (node : System.Text.Json.Nodes.JsonNode) : InnerTypeWithBoth =
|
||||
let ConcreteDict =
|
||||
(match node.["concreteDict"] with
|
||||
| null ->
|
||||
raise (
|
||||
System.Collections.Generic.KeyNotFoundException (
|
||||
sprintf "Required key '%s' not found on JSON object" ("concreteDict")
|
||||
)
|
||||
)
|
||||
| v -> v)
|
||||
.AsObject ()
|
||||
|> Seq.map (fun kvp ->
|
||||
let key = (kvp.Key)
|
||||
let value = InnerTypeWithBoth.jsonParse (kvp.Value)
|
||||
key, value
|
||||
)
|
||||
|> Seq.map System.Collections.Generic.KeyValuePair
|
||||
|> System.Collections.Generic.Dictionary
|
||||
|
||||
let Dict =
|
||||
(match node.["dict"] with
|
||||
| null ->
|
||||
raise (
|
||||
System.Collections.Generic.KeyNotFoundException (
|
||||
sprintf "Required key '%s' not found on JSON object" ("dict")
|
||||
)
|
||||
)
|
||||
| v -> v)
|
||||
.AsObject ()
|
||||
|> Seq.map (fun kvp ->
|
||||
let key = (kvp.Key) |> System.Uri
|
||||
let value = (kvp.Value).AsValue().GetValue<bool> ()
|
||||
key, value
|
||||
)
|
||||
|> dict
|
||||
|
||||
let ReadOnlyDict =
|
||||
(match node.["readOnlyDict"] with
|
||||
| null ->
|
||||
raise (
|
||||
System.Collections.Generic.KeyNotFoundException (
|
||||
sprintf "Required key '%s' not found on JSON object" ("readOnlyDict")
|
||||
)
|
||||
)
|
||||
| v -> v)
|
||||
.AsObject ()
|
||||
|> Seq.map (fun kvp ->
|
||||
let key = (kvp.Key)
|
||||
|
||||
let value =
|
||||
(kvp.Value).AsArray ()
|
||||
|> Seq.map (fun elt -> elt.AsValue().GetValue<char> ())
|
||||
|> List.ofSeq
|
||||
|
||||
key, value
|
||||
)
|
||||
|> readOnlyDict
|
||||
|
||||
let Map =
|
||||
(match node.["map"] with
|
||||
| null ->
|
||||
raise (
|
||||
System.Collections.Generic.KeyNotFoundException (
|
||||
sprintf "Required key '%s' not found on JSON object" ("map")
|
||||
)
|
||||
)
|
||||
| v -> v)
|
||||
.AsObject ()
|
||||
|> Seq.map (fun kvp ->
|
||||
let key = (kvp.Key)
|
||||
let value = (kvp.Value).AsValue().GetValue<string> () |> System.Uri
|
||||
key, value
|
||||
)
|
||||
|> Map.ofSeq
|
||||
|
||||
let Thing =
|
||||
(match node.[("it's-a-me")] with
|
||||
| null ->
|
||||
raise (
|
||||
System.Collections.Generic.KeyNotFoundException (
|
||||
sprintf "Required key '%s' not found on JSON object" (("it's-a-me"))
|
||||
)
|
||||
)
|
||||
| v -> v)
|
||||
.AsValue()
|
||||
.GetValue<string> ()
|
||||
|> System.Guid.Parse
|
||||
|
||||
{
|
||||
Thing = Thing
|
||||
Map = Map
|
||||
ReadOnlyDict = ReadOnlyDict
|
||||
Dict = Dict
|
||||
ConcreteDict = ConcreteDict
|
||||
}
|
||||
namespace ConsumePlugin
|
||||
|
||||
/// Module containing JSON parsing extension members for the JsonRecordTypeWithBoth type
|
||||
[<AutoOpen>]
|
||||
module JsonRecordTypeWithBothJsonParseExtension =
|
||||
/// Extension methods for JSON parsing
|
||||
type JsonRecordTypeWithBoth with
|
||||
|
||||
/// Parse from a JSON node.
|
||||
static member jsonParse (node : System.Text.Json.Nodes.JsonNode) : JsonRecordTypeWithBoth =
|
||||
let F =
|
||||
(match node.["f"] with
|
||||
| null ->
|
||||
raise (
|
||||
System.Collections.Generic.KeyNotFoundException (
|
||||
sprintf "Required key '%s' not found on JSON object" ("f")
|
||||
)
|
||||
)
|
||||
| v -> v)
|
||||
.AsArray ()
|
||||
|> Seq.map (fun elt -> elt.AsValue().GetValue<int> ())
|
||||
|> Array.ofSeq
|
||||
|
||||
let E =
|
||||
(match node.["e"] with
|
||||
| null ->
|
||||
raise (
|
||||
System.Collections.Generic.KeyNotFoundException (
|
||||
sprintf "Required key '%s' not found on JSON object" ("e")
|
||||
)
|
||||
)
|
||||
| v -> v)
|
||||
.AsArray ()
|
||||
|> Seq.map (fun elt -> elt.AsValue().GetValue<string> ())
|
||||
|> Array.ofSeq
|
||||
|
||||
let D =
|
||||
InnerTypeWithBoth.jsonParse (
|
||||
match node.["d"] with
|
||||
| null ->
|
||||
raise (
|
||||
System.Collections.Generic.KeyNotFoundException (
|
||||
sprintf "Required key '%s' not found on JSON object" ("d")
|
||||
)
|
||||
)
|
||||
| v -> v
|
||||
)
|
||||
|
||||
let C =
|
||||
(match node.["c"] with
|
||||
| null ->
|
||||
raise (
|
||||
System.Collections.Generic.KeyNotFoundException (
|
||||
sprintf "Required key '%s' not found on JSON object" ("c")
|
||||
)
|
||||
)
|
||||
| v -> v)
|
||||
.AsArray ()
|
||||
|> Seq.map (fun elt -> elt.AsValue().GetValue<int> ())
|
||||
|> List.ofSeq
|
||||
|
||||
let B =
|
||||
(match node.["b"] with
|
||||
| null ->
|
||||
raise (
|
||||
System.Collections.Generic.KeyNotFoundException (
|
||||
sprintf "Required key '%s' not found on JSON object" ("b")
|
||||
)
|
||||
)
|
||||
| v -> v)
|
||||
.AsValue()
|
||||
.GetValue<string> ()
|
||||
|
||||
let A =
|
||||
(match node.["a"] with
|
||||
| null ->
|
||||
raise (
|
||||
System.Collections.Generic.KeyNotFoundException (
|
||||
sprintf "Required key '%s' not found on JSON object" ("a")
|
||||
)
|
||||
)
|
||||
| v -> v)
|
||||
.AsValue()
|
||||
.GetValue<int> ()
|
||||
|
||||
{
|
||||
A = A
|
||||
B = B
|
||||
C = C
|
||||
D = D
|
||||
E = E
|
||||
F = F
|
||||
}
|
545
ConsumePlugin/GeneratedVault.fs
Normal file
545
ConsumePlugin/GeneratedVault.fs
Normal file
@@ -0,0 +1,545 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// This code was generated by myriad.
|
||||
// Changes to this file will be lost when the code is regenerated.
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
|
||||
|
||||
namespace ConsumePlugin
|
||||
|
||||
/// Module containing JSON parsing methods for the JwtVaultAuthResponse type
|
||||
[<RequireQualifiedAccess>]
|
||||
[<CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix)>]
|
||||
module JwtVaultAuthResponse =
|
||||
/// Parse from a JSON node.
|
||||
let jsonParse (node : System.Text.Json.Nodes.JsonNode) : JwtVaultAuthResponse =
|
||||
let NumUses =
|
||||
(match node.["num_uses"] with
|
||||
| null ->
|
||||
raise (
|
||||
System.Collections.Generic.KeyNotFoundException (
|
||||
sprintf "Required key '%s' not found on JSON object" ("num_uses")
|
||||
)
|
||||
)
|
||||
| v -> v)
|
||||
.AsValue()
|
||||
.GetValue<int> ()
|
||||
|
||||
let Orphan =
|
||||
(match node.["orphan"] with
|
||||
| null ->
|
||||
raise (
|
||||
System.Collections.Generic.KeyNotFoundException (
|
||||
sprintf "Required key '%s' not found on JSON object" ("orphan")
|
||||
)
|
||||
)
|
||||
| v -> v)
|
||||
.AsValue()
|
||||
.GetValue<bool> ()
|
||||
|
||||
let EntityId =
|
||||
(match node.["entity_id"] with
|
||||
| null ->
|
||||
raise (
|
||||
System.Collections.Generic.KeyNotFoundException (
|
||||
sprintf "Required key '%s' not found on JSON object" ("entity_id")
|
||||
)
|
||||
)
|
||||
| v -> v)
|
||||
.AsValue()
|
||||
.GetValue<string> ()
|
||||
|
||||
let TokenType =
|
||||
(match node.["token_type"] with
|
||||
| null ->
|
||||
raise (
|
||||
System.Collections.Generic.KeyNotFoundException (
|
||||
sprintf "Required key '%s' not found on JSON object" ("token_type")
|
||||
)
|
||||
)
|
||||
| v -> v)
|
||||
.AsValue()
|
||||
.GetValue<string> ()
|
||||
|
||||
let Renewable =
|
||||
(match node.["renewable"] with
|
||||
| null ->
|
||||
raise (
|
||||
System.Collections.Generic.KeyNotFoundException (
|
||||
sprintf "Required key '%s' not found on JSON object" ("renewable")
|
||||
)
|
||||
)
|
||||
| v -> v)
|
||||
.AsValue()
|
||||
.GetValue<bool> ()
|
||||
|
||||
let LeaseDuration =
|
||||
(match node.["lease_duration"] with
|
||||
| null ->
|
||||
raise (
|
||||
System.Collections.Generic.KeyNotFoundException (
|
||||
sprintf "Required key '%s' not found on JSON object" ("lease_duration")
|
||||
)
|
||||
)
|
||||
| v -> v)
|
||||
.AsValue()
|
||||
.GetValue<int> ()
|
||||
|
||||
let IdentityPolicies =
|
||||
(match node.["identity_policies"] with
|
||||
| null ->
|
||||
raise (
|
||||
System.Collections.Generic.KeyNotFoundException (
|
||||
sprintf "Required key '%s' not found on JSON object" ("identity_policies")
|
||||
)
|
||||
)
|
||||
| v -> v)
|
||||
.AsArray ()
|
||||
|> Seq.map (fun elt -> elt.AsValue().GetValue<string> ())
|
||||
|> List.ofSeq
|
||||
|
||||
let TokenPolicies =
|
||||
(match node.["token_policies"] with
|
||||
| null ->
|
||||
raise (
|
||||
System.Collections.Generic.KeyNotFoundException (
|
||||
sprintf "Required key '%s' not found on JSON object" ("token_policies")
|
||||
)
|
||||
)
|
||||
| v -> v)
|
||||
.AsArray ()
|
||||
|> Seq.map (fun elt -> elt.AsValue().GetValue<string> ())
|
||||
|> List.ofSeq
|
||||
|
||||
let Policies =
|
||||
(match node.["policies"] with
|
||||
| null ->
|
||||
raise (
|
||||
System.Collections.Generic.KeyNotFoundException (
|
||||
sprintf "Required key '%s' not found on JSON object" ("policies")
|
||||
)
|
||||
)
|
||||
| v -> v)
|
||||
.AsArray ()
|
||||
|> Seq.map (fun elt -> elt.AsValue().GetValue<string> ())
|
||||
|> List.ofSeq
|
||||
|
||||
let Accessor =
|
||||
(match node.["accessor"] with
|
||||
| null ->
|
||||
raise (
|
||||
System.Collections.Generic.KeyNotFoundException (
|
||||
sprintf "Required key '%s' not found on JSON object" ("accessor")
|
||||
)
|
||||
)
|
||||
| v -> v)
|
||||
.AsValue()
|
||||
.GetValue<string> ()
|
||||
|
||||
let ClientToken =
|
||||
(match node.["client_token"] with
|
||||
| null ->
|
||||
raise (
|
||||
System.Collections.Generic.KeyNotFoundException (
|
||||
sprintf "Required key '%s' not found on JSON object" ("client_token")
|
||||
)
|
||||
)
|
||||
| v -> v)
|
||||
.AsValue()
|
||||
.GetValue<string> ()
|
||||
|
||||
{
|
||||
ClientToken = ClientToken
|
||||
Accessor = Accessor
|
||||
Policies = Policies
|
||||
TokenPolicies = TokenPolicies
|
||||
IdentityPolicies = IdentityPolicies
|
||||
LeaseDuration = LeaseDuration
|
||||
Renewable = Renewable
|
||||
TokenType = TokenType
|
||||
EntityId = EntityId
|
||||
Orphan = Orphan
|
||||
NumUses = NumUses
|
||||
}
|
||||
namespace ConsumePlugin
|
||||
|
||||
/// Module containing JSON parsing methods for the JwtVaultResponse type
|
||||
[<RequireQualifiedAccess>]
|
||||
[<CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix)>]
|
||||
module JwtVaultResponse =
|
||||
/// Parse from a JSON node.
|
||||
let jsonParse (node : System.Text.Json.Nodes.JsonNode) : JwtVaultResponse =
|
||||
let Auth =
|
||||
JwtVaultAuthResponse.jsonParse (
|
||||
match node.["auth"] with
|
||||
| null ->
|
||||
raise (
|
||||
System.Collections.Generic.KeyNotFoundException (
|
||||
sprintf "Required key '%s' not found on JSON object" ("auth")
|
||||
)
|
||||
)
|
||||
| v -> v
|
||||
)
|
||||
|
||||
let LeaseDuration =
|
||||
(match node.["lease_duration"] with
|
||||
| null ->
|
||||
raise (
|
||||
System.Collections.Generic.KeyNotFoundException (
|
||||
sprintf "Required key '%s' not found on JSON object" ("lease_duration")
|
||||
)
|
||||
)
|
||||
| v -> v)
|
||||
.AsValue()
|
||||
.GetValue<int> ()
|
||||
|
||||
let Renewable =
|
||||
(match node.["renewable"] with
|
||||
| null ->
|
||||
raise (
|
||||
System.Collections.Generic.KeyNotFoundException (
|
||||
sprintf "Required key '%s' not found on JSON object" ("renewable")
|
||||
)
|
||||
)
|
||||
| v -> v)
|
||||
.AsValue()
|
||||
.GetValue<bool> ()
|
||||
|
||||
let LeaseId =
|
||||
(match node.["lease_id"] with
|
||||
| null ->
|
||||
raise (
|
||||
System.Collections.Generic.KeyNotFoundException (
|
||||
sprintf "Required key '%s' not found on JSON object" ("lease_id")
|
||||
)
|
||||
)
|
||||
| v -> v)
|
||||
.AsValue()
|
||||
.GetValue<string> ()
|
||||
|
||||
let RequestId =
|
||||
(match node.["request_id"] with
|
||||
| null ->
|
||||
raise (
|
||||
System.Collections.Generic.KeyNotFoundException (
|
||||
sprintf "Required key '%s' not found on JSON object" ("request_id")
|
||||
)
|
||||
)
|
||||
| v -> v)
|
||||
.AsValue()
|
||||
.GetValue<string> ()
|
||||
|
||||
{
|
||||
RequestId = RequestId
|
||||
LeaseId = LeaseId
|
||||
Renewable = Renewable
|
||||
LeaseDuration = LeaseDuration
|
||||
Auth = Auth
|
||||
}
|
||||
namespace ConsumePlugin
|
||||
|
||||
/// Module containing JSON parsing methods for the JwtSecretResponse type
|
||||
[<RequireQualifiedAccess>]
|
||||
[<CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix)>]
|
||||
module JwtSecretResponse =
|
||||
/// Parse from a JSON node.
|
||||
let jsonParse (node : System.Text.Json.Nodes.JsonNode) : JwtSecretResponse =
|
||||
let Data8 =
|
||||
(match node.["data8"] with
|
||||
| null ->
|
||||
raise (
|
||||
System.Collections.Generic.KeyNotFoundException (
|
||||
sprintf "Required key '%s' not found on JSON object" ("data8")
|
||||
)
|
||||
)
|
||||
| v -> v)
|
||||
.AsObject ()
|
||||
|> Seq.map (fun kvp ->
|
||||
let key = (kvp.Key)
|
||||
let value = (kvp.Value).AsValue().GetValue<string> () |> System.Uri
|
||||
key, value
|
||||
)
|
||||
|> Seq.map System.Collections.Generic.KeyValuePair
|
||||
|> System.Collections.Generic.Dictionary
|
||||
|
||||
let Data7 =
|
||||
(match node.["data7"] with
|
||||
| null ->
|
||||
raise (
|
||||
System.Collections.Generic.KeyNotFoundException (
|
||||
sprintf "Required key '%s' not found on JSON object" ("data7")
|
||||
)
|
||||
)
|
||||
| v -> v)
|
||||
.AsObject ()
|
||||
|> Seq.map (fun kvp ->
|
||||
let key = (kvp.Key)
|
||||
let value = (kvp.Value).AsValue().GetValue<int> ()
|
||||
key, value
|
||||
)
|
||||
|> Map.ofSeq
|
||||
|
||||
let Data6 =
|
||||
(match node.["data6"] with
|
||||
| null ->
|
||||
raise (
|
||||
System.Collections.Generic.KeyNotFoundException (
|
||||
sprintf "Required key '%s' not found on JSON object" ("data6")
|
||||
)
|
||||
)
|
||||
| v -> v)
|
||||
.AsObject ()
|
||||
|> Seq.map (fun kvp ->
|
||||
let key = (kvp.Key) |> System.Uri
|
||||
let value = (kvp.Value).AsValue().GetValue<string> ()
|
||||
key, value
|
||||
)
|
||||
|> dict
|
||||
|
||||
let Data5 =
|
||||
(match node.["data5"] with
|
||||
| null ->
|
||||
raise (
|
||||
System.Collections.Generic.KeyNotFoundException (
|
||||
sprintf "Required key '%s' not found on JSON object" ("data5")
|
||||
)
|
||||
)
|
||||
| v -> v)
|
||||
.AsObject ()
|
||||
|> Seq.map (fun kvp ->
|
||||
let key = (kvp.Key) |> System.Uri
|
||||
let value = (kvp.Value).AsValue().GetValue<string> ()
|
||||
key, value
|
||||
)
|
||||
|> readOnlyDict
|
||||
|
||||
let Data4 =
|
||||
(match node.["data4"] with
|
||||
| null ->
|
||||
raise (
|
||||
System.Collections.Generic.KeyNotFoundException (
|
||||
sprintf "Required key '%s' not found on JSON object" ("data4")
|
||||
)
|
||||
)
|
||||
| v -> v)
|
||||
.AsObject ()
|
||||
|> Seq.map (fun kvp ->
|
||||
let key = (kvp.Key)
|
||||
let value = (kvp.Value).AsValue().GetValue<string> ()
|
||||
key, value
|
||||
)
|
||||
|> Map.ofSeq
|
||||
|
||||
let Data3 =
|
||||
(match node.["data3"] with
|
||||
| null ->
|
||||
raise (
|
||||
System.Collections.Generic.KeyNotFoundException (
|
||||
sprintf "Required key '%s' not found on JSON object" ("data3")
|
||||
)
|
||||
)
|
||||
| v -> v)
|
||||
.AsObject ()
|
||||
|> Seq.map (fun kvp ->
|
||||
let key = (kvp.Key)
|
||||
let value = (kvp.Value).AsValue().GetValue<string> ()
|
||||
key, value
|
||||
)
|
||||
|> Seq.map System.Collections.Generic.KeyValuePair
|
||||
|> System.Collections.Generic.Dictionary
|
||||
|
||||
let Data2 =
|
||||
(match node.["data2"] with
|
||||
| null ->
|
||||
raise (
|
||||
System.Collections.Generic.KeyNotFoundException (
|
||||
sprintf "Required key '%s' not found on JSON object" ("data2")
|
||||
)
|
||||
)
|
||||
| v -> v)
|
||||
.AsObject ()
|
||||
|> Seq.map (fun kvp ->
|
||||
let key = (kvp.Key)
|
||||
let value = (kvp.Value).AsValue().GetValue<string> ()
|
||||
key, value
|
||||
)
|
||||
|> dict
|
||||
|
||||
let Data =
|
||||
(match node.["data"] with
|
||||
| null ->
|
||||
raise (
|
||||
System.Collections.Generic.KeyNotFoundException (
|
||||
sprintf "Required key '%s' not found on JSON object" ("data")
|
||||
)
|
||||
)
|
||||
| v -> v)
|
||||
.AsObject ()
|
||||
|> Seq.map (fun kvp ->
|
||||
let key = (kvp.Key)
|
||||
let value = (kvp.Value).AsValue().GetValue<string> ()
|
||||
key, value
|
||||
)
|
||||
|> readOnlyDict
|
||||
|
||||
let LeaseDuration =
|
||||
(match node.["lease_duration"] with
|
||||
| null ->
|
||||
raise (
|
||||
System.Collections.Generic.KeyNotFoundException (
|
||||
sprintf "Required key '%s' not found on JSON object" ("lease_duration")
|
||||
)
|
||||
)
|
||||
| v -> v)
|
||||
.AsValue()
|
||||
.GetValue<int> ()
|
||||
|
||||
let Renewable =
|
||||
(match node.["renewable"] with
|
||||
| null ->
|
||||
raise (
|
||||
System.Collections.Generic.KeyNotFoundException (
|
||||
sprintf "Required key '%s' not found on JSON object" ("renewable")
|
||||
)
|
||||
)
|
||||
| v -> v)
|
||||
.AsValue()
|
||||
.GetValue<bool> ()
|
||||
|
||||
let LeaseId =
|
||||
(match node.["lease_id"] with
|
||||
| null ->
|
||||
raise (
|
||||
System.Collections.Generic.KeyNotFoundException (
|
||||
sprintf "Required key '%s' not found on JSON object" ("lease_id")
|
||||
)
|
||||
)
|
||||
| v -> v)
|
||||
.AsValue()
|
||||
.GetValue<string> ()
|
||||
|
||||
let RequestId =
|
||||
(match node.["request_id"] with
|
||||
| null ->
|
||||
raise (
|
||||
System.Collections.Generic.KeyNotFoundException (
|
||||
sprintf "Required key '%s' not found on JSON object" ("request_id")
|
||||
)
|
||||
)
|
||||
| v -> v)
|
||||
.AsValue()
|
||||
.GetValue<string> ()
|
||||
|
||||
{
|
||||
RequestId = RequestId
|
||||
LeaseId = LeaseId
|
||||
Renewable = Renewable
|
||||
LeaseDuration = LeaseDuration
|
||||
Data = Data
|
||||
Data2 = Data2
|
||||
Data3 = Data3
|
||||
Data4 = Data4
|
||||
Data5 = Data5
|
||||
Data6 = Data6
|
||||
Data7 = Data7
|
||||
Data8 = Data8
|
||||
}
|
||||
|
||||
namespace ConsumePlugin
|
||||
|
||||
open System
|
||||
open System.Collections.Generic
|
||||
open System.Text.Json.Serialization
|
||||
open System.Threading
|
||||
open System.Threading.Tasks
|
||||
open RestEase
|
||||
|
||||
/// Module for constructing a REST client.
|
||||
[<CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix)>]
|
||||
[<RequireQualifiedAccess>]
|
||||
module VaultClient =
|
||||
/// Create a REST client.
|
||||
let make (client : System.Net.Http.HttpClient) : IVaultClient =
|
||||
{ new IVaultClient with
|
||||
member _.GetSecret
|
||||
(jwt : JwtVaultResponse, path : string, mountPoint : string, ct : CancellationToken option)
|
||||
=
|
||||
async {
|
||||
let! ct = Async.CancellationToken
|
||||
|
||||
let uri =
|
||||
System.Uri (
|
||||
(match client.BaseAddress with
|
||||
| null ->
|
||||
raise (
|
||||
System.ArgumentNullException (
|
||||
nameof (client.BaseAddress),
|
||||
"No base address was supplied on the type, and no BaseAddress was on the HttpClient."
|
||||
)
|
||||
)
|
||||
| v -> v),
|
||||
System.Uri (
|
||||
"v1/{mountPoint}/{path}"
|
||||
.Replace("{path}", path.ToString () |> System.Web.HttpUtility.UrlEncode)
|
||||
.Replace (
|
||||
"{mountPoint}",
|
||||
mountPoint.ToString () |> System.Web.HttpUtility.UrlEncode
|
||||
),
|
||||
System.UriKind.Relative
|
||||
)
|
||||
)
|
||||
|
||||
let httpMessage =
|
||||
new System.Net.Http.HttpRequestMessage (
|
||||
Method = System.Net.Http.HttpMethod.Get,
|
||||
RequestUri = uri
|
||||
)
|
||||
|
||||
let! response = client.SendAsync (httpMessage, ct) |> Async.AwaitTask
|
||||
let response = response.EnsureSuccessStatusCode ()
|
||||
let! responseStream = response.Content.ReadAsStreamAsync ct |> Async.AwaitTask
|
||||
|
||||
let! jsonNode =
|
||||
System.Text.Json.Nodes.JsonNode.ParseAsync (responseStream, cancellationToken = ct)
|
||||
|> Async.AwaitTask
|
||||
|
||||
return JwtSecretResponse.jsonParse jsonNode
|
||||
}
|
||||
|> (fun a -> Async.StartAsTask (a, ?cancellationToken = ct))
|
||||
|
||||
member _.GetJwt (role : string, jwt : string, ct : CancellationToken option) =
|
||||
async {
|
||||
let! ct = Async.CancellationToken
|
||||
|
||||
let uri =
|
||||
System.Uri (
|
||||
(match client.BaseAddress with
|
||||
| null ->
|
||||
raise (
|
||||
System.ArgumentNullException (
|
||||
nameof (client.BaseAddress),
|
||||
"No base address was supplied on the type, and no BaseAddress was on the HttpClient."
|
||||
)
|
||||
)
|
||||
| v -> v),
|
||||
System.Uri ("v1/auth/jwt/login", System.UriKind.Relative)
|
||||
)
|
||||
|
||||
let httpMessage =
|
||||
new System.Net.Http.HttpRequestMessage (
|
||||
Method = System.Net.Http.HttpMethod.Get,
|
||||
RequestUri = uri
|
||||
)
|
||||
|
||||
let! response = client.SendAsync (httpMessage, ct) |> Async.AwaitTask
|
||||
let response = response.EnsureSuccessStatusCode ()
|
||||
let! responseStream = response.Content.ReadAsStreamAsync ct |> Async.AwaitTask
|
||||
|
||||
let! jsonNode =
|
||||
System.Text.Json.Nodes.JsonNode.ParseAsync (responseStream, cancellationToken = ct)
|
||||
|> Async.AwaitTask
|
||||
|
||||
return JwtVaultResponse.jsonParse jsonNode
|
||||
}
|
||||
|> (fun a -> Async.StartAsTask (a, ?cancellationToken = ct))
|
||||
}
|
@@ -28,3 +28,16 @@ type JsonRecordType =
|
||||
E : string array
|
||||
F : int[]
|
||||
}
|
||||
|
||||
[<WoofWare.Myriad.Plugins.JsonParse true>]
|
||||
type ToGetExtensionMethod =
|
||||
{
|
||||
Tinker : string
|
||||
Tailor : int
|
||||
Soldier : System.Uri
|
||||
Sailor : float
|
||||
}
|
||||
|
||||
[<RequireQualifiedAccess>]
|
||||
module ToGetExtensionMethod =
|
||||
let thisModuleWouldClash = 3
|
||||
|
19
ConsumePlugin/List.fs
Normal file
19
ConsumePlugin/List.fs
Normal file
@@ -0,0 +1,19 @@
|
||||
namespace ConsumePlugin
|
||||
|
||||
open WoofWare.Myriad.Plugins
|
||||
|
||||
[<CreateCatamorphism "MyListCata">]
|
||||
type MyList<'a> =
|
||||
| Nil
|
||||
| Cons of ConsCase<'a>
|
||||
|
||||
and ConsCase<'a> =
|
||||
{
|
||||
Head : 'a
|
||||
Tail : MyList<'a>
|
||||
}
|
||||
|
||||
[<CreateCatamorphism "MyList2Cata">]
|
||||
type MyList2<'a> =
|
||||
| Nil
|
||||
| Cons of 'a * MyList2<'a>
|
118
ConsumePlugin/ListCata.fs
Normal file
118
ConsumePlugin/ListCata.fs
Normal file
@@ -0,0 +1,118 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// This code was generated by myriad.
|
||||
// Changes to this file will be lost when the code is regenerated.
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
namespace ConsumePlugin
|
||||
|
||||
open WoofWare.Myriad.Plugins
|
||||
|
||||
/// Description of how to combine cases during a fold
|
||||
type MyListCataCase<'a, 'MyList> =
|
||||
/// How to operate on the Nil case
|
||||
abstract Nil : 'MyList
|
||||
/// How to operate on the Cons case
|
||||
abstract Cons : head : 'a -> tail : 'MyList -> 'MyList
|
||||
|
||||
/// Specifies how to perform a fold (catamorphism) over the type MyList and its friends.
|
||||
type MyListCata<'a, 'MyList> =
|
||||
{
|
||||
/// How to perform a fold (catamorphism) over the type MyList
|
||||
MyList : MyListCataCase<'a, 'MyList>
|
||||
}
|
||||
|
||||
/// Methods to perform a catamorphism over the type MyList
|
||||
[<RequireQualifiedAccess>]
|
||||
module MyListCata =
|
||||
[<RequireQualifiedAccess>]
|
||||
type private Instruction<'a> =
|
||||
| Process__MyList of MyList<'a>
|
||||
| MyList_Cons of 'a
|
||||
|
||||
let private loop (cata : MyListCata<'a, 'MyList>) (instructions : ResizeArray<Instruction<'a>>) =
|
||||
let myListStack = ResizeArray<'MyList> ()
|
||||
|
||||
while instructions.Count > 0 do
|
||||
let currentInstruction = instructions.[instructions.Count - 1]
|
||||
instructions.RemoveAt (instructions.Count - 1)
|
||||
|
||||
match currentInstruction with
|
||||
| Instruction.Process__MyList x ->
|
||||
match x with
|
||||
| MyList.Nil -> cata.MyList.Nil |> myListStack.Add
|
||||
| MyList.Cons ({
|
||||
Head = head
|
||||
Tail = tail
|
||||
}) ->
|
||||
instructions.Add (Instruction.MyList_Cons (head))
|
||||
instructions.Add (Instruction.Process__MyList tail)
|
||||
| Instruction.MyList_Cons (head) ->
|
||||
let tail = myListStack.[myListStack.Count - 1]
|
||||
myListStack.RemoveAt (myListStack.Count - 1)
|
||||
cata.MyList.Cons head tail |> myListStack.Add
|
||||
|
||||
myListStack
|
||||
|
||||
/// Execute the catamorphism.
|
||||
let runMyList (cata : MyListCata<'a, 'MyListRet>) (x : MyList<'a>) : 'MyListRet =
|
||||
let instructions = ResizeArray ()
|
||||
instructions.Add (Instruction.Process__MyList x)
|
||||
let myListRetStack = loop cata instructions
|
||||
Seq.exactlyOne myListRetStack
|
||||
namespace ConsumePlugin
|
||||
|
||||
open WoofWare.Myriad.Plugins
|
||||
|
||||
/// Description of how to combine cases during a fold
|
||||
type MyList2CataCase<'a, 'MyList2> =
|
||||
/// How to operate on the Nil case
|
||||
abstract Nil : 'MyList2
|
||||
/// How to operate on the Cons case
|
||||
abstract Cons : 'a -> 'MyList2 -> 'MyList2
|
||||
|
||||
/// Specifies how to perform a fold (catamorphism) over the type MyList2 and its friends.
|
||||
type MyList2Cata<'a, 'MyList2> =
|
||||
{
|
||||
/// How to perform a fold (catamorphism) over the type MyList2
|
||||
MyList2 : MyList2CataCase<'a, 'MyList2>
|
||||
}
|
||||
|
||||
/// Methods to perform a catamorphism over the type MyList2
|
||||
[<RequireQualifiedAccess>]
|
||||
module MyList2Cata =
|
||||
[<RequireQualifiedAccess>]
|
||||
type private Instruction<'a> =
|
||||
| Process__MyList2 of MyList2<'a>
|
||||
| MyList2_Cons of 'a
|
||||
|
||||
let private loop (cata : MyList2Cata<'a, 'MyList2>) (instructions : ResizeArray<Instruction<'a>>) =
|
||||
let myList2Stack = ResizeArray<'MyList2> ()
|
||||
|
||||
while instructions.Count > 0 do
|
||||
let currentInstruction = instructions.[instructions.Count - 1]
|
||||
instructions.RemoveAt (instructions.Count - 1)
|
||||
|
||||
match currentInstruction with
|
||||
| Instruction.Process__MyList2 x ->
|
||||
match x with
|
||||
| MyList2.Nil -> cata.MyList2.Nil |> myList2Stack.Add
|
||||
| MyList2.Cons (arg0_0, arg1_0) ->
|
||||
instructions.Add (Instruction.MyList2_Cons (arg0_0))
|
||||
instructions.Add (Instruction.Process__MyList2 arg1_0)
|
||||
| Instruction.MyList2_Cons (arg0_0) ->
|
||||
let arg1_0 = myList2Stack.[myList2Stack.Count - 1]
|
||||
myList2Stack.RemoveAt (myList2Stack.Count - 1)
|
||||
cata.MyList2.Cons arg0_0 arg1_0 |> myList2Stack.Add
|
||||
|
||||
myList2Stack
|
||||
|
||||
/// Execute the catamorphism.
|
||||
let runMyList2 (cata : MyList2Cata<'a, 'MyList2Ret>) (x : MyList2<'a>) : 'MyList2Ret =
|
||||
let instructions = ResizeArray ()
|
||||
instructions.Add (Instruction.Process__MyList2 x)
|
||||
let myList2RetStack = loop cata instructions
|
||||
Seq.exactlyOne myList2RetStack
|
43
ConsumePlugin/MockExample.fs
Normal file
43
ConsumePlugin/MockExample.fs
Normal file
@@ -0,0 +1,43 @@
|
||||
namespace SomeNamespace
|
||||
|
||||
open WoofWare.Myriad.Plugins
|
||||
|
||||
[<GenerateMock>]
|
||||
type IPublicType =
|
||||
abstract Mem1 : string * int -> string list
|
||||
abstract Mem2 : string -> int
|
||||
abstract Mem3 : x : int * ?ct : System.Threading.CancellationToken -> string
|
||||
|
||||
[<GenerateMock false>]
|
||||
type IPublicTypeInternalFalse =
|
||||
abstract Mem1 : string * int -> string list
|
||||
abstract Mem2 : string -> int
|
||||
abstract Mem3 : x : int * ?ct : System.Threading.CancellationToken -> string
|
||||
|
||||
[<GenerateMock>]
|
||||
type internal InternalType =
|
||||
abstract Mem1 : string * int -> unit
|
||||
abstract Mem2 : string -> int
|
||||
|
||||
[<GenerateMock>]
|
||||
type private PrivateType =
|
||||
abstract Mem1 : string * int -> unit
|
||||
abstract Mem2 : string -> int
|
||||
|
||||
[<GenerateMock false>]
|
||||
type private PrivateTypeInternalFalse =
|
||||
abstract Mem1 : string * int -> unit
|
||||
abstract Mem2 : string -> int
|
||||
|
||||
[<GenerateMock>]
|
||||
type VeryPublicType<'a, 'b> =
|
||||
abstract Mem1 : 'a -> 'b
|
||||
|
||||
[<GenerateMock>]
|
||||
type Curried<'a> =
|
||||
abstract Mem1 : int -> 'a -> string
|
||||
abstract Mem2 : int * string -> 'a -> string
|
||||
abstract Mem3 : (int * string) -> 'a -> string
|
||||
abstract Mem4 : (int * string) -> ('a * int) -> string
|
||||
abstract Mem5 : x : int * string -> ('a * int) -> string
|
||||
abstract Mem6 : int * string -> y : 'a * int -> string
|
@@ -68,7 +68,8 @@ type Gym =
|
||||
ReopenDate : string
|
||||
}
|
||||
|
||||
[<WoofWare.Myriad.Plugins.JsonParse>]
|
||||
[<WoofWare.Myriad.Plugins.JsonParse true>]
|
||||
[<WoofWare.Myriad.Plugins.JsonSerialize true>]
|
||||
type Member =
|
||||
{
|
||||
Id : int
|
||||
@@ -177,3 +178,9 @@ type Sessions =
|
||||
[<JsonPropertyName "Visits">]
|
||||
Visits : Visit list
|
||||
}
|
||||
|
||||
[<WoofWare.Myriad.Plugins.JsonParse>]
|
||||
type UriThing =
|
||||
{
|
||||
SomeUri : Uri
|
||||
}
|
||||
|
@@ -9,27 +9,64 @@ open System.Net.Http
|
||||
open RestEase
|
||||
|
||||
[<WoofWare.Myriad.Plugins.HttpClient>]
|
||||
[<BaseAddress "https://whatnot.com">]
|
||||
type IPureGymApi =
|
||||
[<Get "v1/gyms/">]
|
||||
[<Get("v1/gyms/")>]
|
||||
abstract GetGyms : ?ct : CancellationToken -> Task<Gym list>
|
||||
|
||||
[<Get "v1/gyms/{gym_id}/attendance">]
|
||||
abstract GetGymAttendance : [<Path "gym_id">] gymId : int * ?ct : CancellationToken -> Task<GymAttendance>
|
||||
|
||||
[<RestEase.GetAttribute "v1/member">]
|
||||
abstract GetMember : ?ct : CancellationToken -> Task<Member>
|
||||
abstract GetMember : ?ct : CancellationToken -> Member Task
|
||||
|
||||
[<RestEase.Get "v1/gyms/{gym_id}">]
|
||||
abstract GetGym : [<Path "gym_id">] gymId : int * ?ct : CancellationToken -> Task<Gym>
|
||||
[<RestEase.Get "v1/gyms/{gym}">]
|
||||
abstract GetGym : [<Path>] gym : int * ?ct : CancellationToken -> Task<Gym>
|
||||
|
||||
[<GetAttribute "v1/member/activity">]
|
||||
abstract GetMemberActivity : ?ct : CancellationToken -> Task<MemberActivityDto>
|
||||
|
||||
[<Get "some/url">]
|
||||
abstract GetUrl : ?ct : CancellationToken -> Task<UriThing>
|
||||
|
||||
[<Post "some/url">]
|
||||
abstract PostStringToString :
|
||||
[<Body>] foo : Map<string, string> option * ?ct : CancellationToken -> Task<Map<string, string> option>
|
||||
|
||||
// We'll use this one to check handling of absolute URIs too
|
||||
[<Get "/v2/gymSessions/member">]
|
||||
abstract GetSessions :
|
||||
[<Query>] fromDate : DateOnly * [<Query>] toDate : DateOnly * ?ct : CancellationToken -> Task<Sessions>
|
||||
|
||||
// An example from RestEase's own docs
|
||||
[<Post "users/new">]
|
||||
abstract CreateUserString : [<Body>] user : string * ?ct : CancellationToken -> Task<string>
|
||||
|
||||
[<Post "users/new">]
|
||||
abstract CreateUserStream : [<Body>] user : System.IO.Stream * ?ct : CancellationToken -> Task<Stream>
|
||||
|
||||
[<Post "users/new">]
|
||||
abstract CreateUserByteArr : [<Body>] user : byte[] * ?ct : CancellationToken -> Task<Stream>
|
||||
|
||||
[<Post "users/new">]
|
||||
abstract CreateUserByteArr' : [<Body>] user : array<byte> * ?ct : CancellationToken -> Task<Stream>
|
||||
|
||||
[<Post "users/new">]
|
||||
abstract CreateUserByteArr'' : [<Body>] user : byte array * ?ct : CancellationToken -> Task<Stream>
|
||||
|
||||
[<Post "users/new">]
|
||||
abstract CreateUserSerialisedBody : [<Body>] user : PureGym.Member * ?ct : CancellationToken -> Task<string>
|
||||
|
||||
[<Post "users/new">]
|
||||
abstract CreateUserSerialisedUrlBody : [<Body>] user : Uri * ?ct : CancellationToken -> Task<string>
|
||||
|
||||
[<Post "users/new">]
|
||||
abstract CreateUserSerialisedIntBody : [<Body>] user : int * ?ct : CancellationToken -> Task<string>
|
||||
|
||||
[<Post "users/new">]
|
||||
abstract CreateUserHttpContent :
|
||||
[<Body>] user : System.Net.Http.HttpContent * ?ct : CancellationToken -> Task<string>
|
||||
|
||||
[<Get "endpoint/{param}">]
|
||||
abstract GetPathParam : [<Path "param">] parameter : string * ?ct : CancellationToken -> Task<string>
|
||||
|
||||
@@ -54,9 +91,53 @@ type IPureGymApi =
|
||||
[<Get "endpoint">]
|
||||
abstract GetResponseMessage''' : ?ct : CancellationToken -> Task<HttpResponseMessage>
|
||||
|
||||
[<Get "endpoint">]
|
||||
abstract GetResponse : ?ct : CancellationToken -> Task<Response<MemberActivityDto>>
|
||||
|
||||
[<Get "endpoint">]
|
||||
abstract GetResponse' : ?ct : CancellationToken -> Task<RestEase.Response<MemberActivityDto>>
|
||||
|
||||
[<Get "endpoint">]
|
||||
abstract GetResponse'' : ?ct : CancellationToken -> Task<MemberActivityDto Response>
|
||||
|
||||
[<Get "endpoint">]
|
||||
abstract GetResponse''' : ?ct : CancellationToken -> Task<MemberActivityDto RestEase.Response>
|
||||
|
||||
[<Get "endpoint">]
|
||||
[<AllowAnyStatusCode>]
|
||||
abstract GetWithAnyReturnCode : ?ct : CancellationToken -> Task<HttpResponseMessage>
|
||||
|
||||
[<Get "endpoint">]
|
||||
abstract GetWithoutAnyReturnCode : ?ct : CancellationToken -> Task<HttpResponseMessage>
|
||||
|
||||
[<WoofWare.Myriad.Plugins.HttpClient>]
|
||||
type internal IApiWithoutBaseAddress =
|
||||
[<Get "endpoint/{param}">]
|
||||
abstract GetPathParam : [<Path "param">] parameter : string * ?ct : CancellationToken -> Task<string>
|
||||
|
||||
// TODO: implement BasePath support
|
||||
|
||||
[<WoofWare.Myriad.Plugins.HttpClient>]
|
||||
[<BasePath "foo">]
|
||||
type IApiWithBasePath =
|
||||
[<Get "endpoint/{param}">]
|
||||
abstract GetPathParam : [<Path "param">] parameter : string * ?cancellationToken : CancellationToken -> Task<string>
|
||||
|
||||
[<WoofWare.Myriad.Plugins.HttpClient>]
|
||||
[<BaseAddress "https://whatnot.com">]
|
||||
[<BasePath "foo">]
|
||||
type IApiWithBasePathAndAddress =
|
||||
[<Get "endpoint/{param}">]
|
||||
abstract GetPathParam : [<Path "param">] parameter : string * ?ct : CancellationToken -> Task<string>
|
||||
|
||||
[<WoofWare.Myriad.Plugins.HttpClient>]
|
||||
[<Header("Header-Name", "Header-Value")>]
|
||||
type IApiWithHeaders =
|
||||
[<Header "X-Foo">]
|
||||
abstract SomeHeader : string
|
||||
|
||||
[<Header "Authorization">]
|
||||
abstract SomeOtherHeader : int
|
||||
|
||||
[<Get "endpoint/{param}">]
|
||||
abstract GetPathParam : [<Path "param">] parameter : string * ?ct : CancellationToken -> Task<string>
|
||||
|
29
ConsumePlugin/SerializationAndDeserialization.fs
Normal file
29
ConsumePlugin/SerializationAndDeserialization.fs
Normal file
@@ -0,0 +1,29 @@
|
||||
namespace ConsumePlugin
|
||||
|
||||
open System
|
||||
open System.Collections.Generic
|
||||
open System.Text.Json.Serialization
|
||||
|
||||
[<WoofWare.Myriad.Plugins.JsonParse true>]
|
||||
[<WoofWare.Myriad.Plugins.JsonSerialize true>]
|
||||
type InnerTypeWithBoth =
|
||||
{
|
||||
[<JsonPropertyName("it's-a-me")>]
|
||||
Thing : Guid
|
||||
Map : Map<string, Uri>
|
||||
ReadOnlyDict : IReadOnlyDictionary<string, char list>
|
||||
Dict : IDictionary<Uri, bool>
|
||||
ConcreteDict : Dictionary<string, InnerTypeWithBoth>
|
||||
}
|
||||
|
||||
[<WoofWare.Myriad.Plugins.JsonParse true>]
|
||||
[<WoofWare.Myriad.Plugins.JsonSerialize true>]
|
||||
type JsonRecordTypeWithBoth =
|
||||
{
|
||||
A : int
|
||||
B : string
|
||||
C : int list
|
||||
D : InnerTypeWithBoth
|
||||
E : string array
|
||||
F : int[]
|
||||
}
|
78
ConsumePlugin/Vault.fs
Normal file
78
ConsumePlugin/Vault.fs
Normal file
@@ -0,0 +1,78 @@
|
||||
namespace ConsumePlugin
|
||||
|
||||
open System
|
||||
open System.Collections.Generic
|
||||
open System.Text.Json.Serialization
|
||||
open System.Threading
|
||||
open System.Threading.Tasks
|
||||
open RestEase
|
||||
|
||||
[<WoofWare.Myriad.Plugins.JsonParse>]
|
||||
type JwtVaultAuthResponse =
|
||||
{
|
||||
[<JsonPropertyName "client_token">]
|
||||
ClientToken : string
|
||||
Accessor : string
|
||||
Policies : string list
|
||||
[<JsonPropertyName "token_policies">]
|
||||
TokenPolicies : string list
|
||||
[<JsonPropertyName "identity_policies">]
|
||||
IdentityPolicies : string list
|
||||
[<JsonPropertyName "lease_duration">]
|
||||
LeaseDuration : int
|
||||
Renewable : bool
|
||||
[<JsonPropertyName "token_type">]
|
||||
TokenType : string
|
||||
[<JsonPropertyName "entity_id">]
|
||||
EntityId : string
|
||||
Orphan : bool
|
||||
[<JsonPropertyName "num_uses">]
|
||||
NumUses : int
|
||||
}
|
||||
|
||||
[<WoofWare.Myriad.Plugins.JsonParse>]
|
||||
type JwtVaultResponse =
|
||||
{
|
||||
[<JsonPropertyName "request_id">]
|
||||
RequestId : string
|
||||
[<JsonPropertyName "lease_id">]
|
||||
LeaseId : string
|
||||
Renewable : bool
|
||||
[<JsonPropertyName "lease_duration">]
|
||||
LeaseDuration : int
|
||||
Auth : JwtVaultAuthResponse
|
||||
}
|
||||
|
||||
[<WoofWare.Myriad.Plugins.JsonParse>]
|
||||
type JwtSecretResponse =
|
||||
{
|
||||
[<JsonPropertyName "request_id">]
|
||||
RequestId : string
|
||||
[<JsonPropertyName "lease_id">]
|
||||
LeaseId : string
|
||||
Renewable : bool
|
||||
[<JsonPropertyName "lease_duration">]
|
||||
LeaseDuration : int
|
||||
Data : IReadOnlyDictionary<string, string>
|
||||
// These ones aren't actually part of the Vault response, but are here for tests
|
||||
Data2 : IDictionary<string, string>
|
||||
Data3 : Dictionary<string, string>
|
||||
Data4 : Map<string, string>
|
||||
Data5 : IReadOnlyDictionary<System.Uri, string>
|
||||
Data6 : IDictionary<Uri, string>
|
||||
Data7 : Map<string, int>
|
||||
Data8 : Dictionary<string, Uri>
|
||||
}
|
||||
|
||||
[<WoofWare.Myriad.Plugins.HttpClient>]
|
||||
type IVaultClient =
|
||||
[<Get "v1/{mountPoint}/{path}">]
|
||||
abstract GetSecret :
|
||||
jwt : JwtVaultResponse *
|
||||
[<Path "path">] path : string *
|
||||
[<Path "mountPoint">] mountPoint : string *
|
||||
?ct : CancellationToken ->
|
||||
Task<JwtSecretResponse>
|
||||
|
||||
[<Get "v1/auth/jwt/login">]
|
||||
abstract GetJwt : role : string * jwt : string * ?ct : CancellationToken -> Task<JwtVaultResponse>
|
@@ -6,12 +6,12 @@
|
||||
<DisableImplicitLibraryPacksFolder>true</DisableImplicitLibraryPacksFolder>
|
||||
<DisableImplicitNuGetFallbackFolder>true</DisableImplicitNuGetFallbackFolder>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<WarnOn>FS3559</WarnOn>
|
||||
<DebugType>embedded</DebugType>
|
||||
<WarnOn>FS3388,FS3559</WarnOn>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Nerdbank.GitVersioning" Version="3.6.128" PrivateAssets="all"/>
|
||||
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1" PrivateAssets="All"/>
|
||||
<PackageReference Include="Nerdbank.GitVersioning" Version="3.6.133" PrivateAssets="all"/>
|
||||
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="8.0.0" PrivateAssets="All"/>
|
||||
<SourceLinkGitHubHost Include="github.com" ContentUrl="https://raw.githubusercontent.com"/>
|
||||
</ItemGroup>
|
||||
<!--
|
||||
|
@@ -1,38 +0,0 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<IsPackable>false</IsPackable>
|
||||
<IsTestProject>true</IsTestProject>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Include="HttpClient.fs"/>
|
||||
<Compile Include="TestPathParam.fs" />
|
||||
<Compile Include="TestReturnTypes.fs" />
|
||||
<Compile Include="TestAllowAnyStatusCode.fs" />
|
||||
<Compile Include="TestSurface.fs"/>
|
||||
<Compile Include="TestRemoveOptions.fs"/>
|
||||
<Compile Include="TestJsonParse.fs"/>
|
||||
<Compile Include="PureGymDtos.fs"/>
|
||||
<Compile Include="TestPureGymJson.fs"/>
|
||||
<Compile Include="TestPureGymRestApi.fs" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="ApiSurface" Version="4.0.25"/>
|
||||
<PackageReference Include="FsCheck" Version="2.16.6"/>
|
||||
<PackageReference Include="FsUnit" Version="5.6.1"/>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.5.0"/>
|
||||
<PackageReference Include="NUnit" Version="3.14.0"/>
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="4.4.2"/>
|
||||
<PackageReference Include="NUnit.Analyzers" Version="3.6.1"/>
|
||||
<PackageReference Include="coverlet.collector" Version="3.2.0"/>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\WoofWare.Myriad.Plugins\WoofWare.Myriad.Plugins.fsproj"/>
|
||||
<ProjectReference Include="..\ConsumePlugin\ConsumePlugin.fsproj"/>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
735
README.md
735
README.md
@@ -1,272 +1,463 @@
|
||||
# WoofWare.Myriad.Plugins
|
||||
|
||||
[](https://www.nuget.org/packages/WoofWare.Myriad.Plugins)
|
||||
[](https://github.com/Smaug123/WoofWare.Myriad/actions?query=branch%3Amain)
|
||||
[](./LICENSE)
|
||||
|
||||

|
||||
|
||||
Some helpers in [Myriad](https://github.com/MoiraeSoftware/myriad/) which might be useful.
|
||||
|
||||
These are currently somewhat experimental, and I personally am their primary customer.
|
||||
The `RemoveOptions` generator in particular is extremely half-baked.
|
||||
|
||||
Currently implemented:
|
||||
|
||||
* `JsonParse` (to stamp out `jsonParse : JsonNode -> 'T` methods);
|
||||
* `RemoveOptions` (to strip `option` modifiers from a type).
|
||||
* `HttpClient` (to stamp out a [RestEase](https://github.com/canton7/RestEase)-style HTTP client).
|
||||
|
||||
## `JsonParse`
|
||||
|
||||
Takes records like this:
|
||||
|
||||
```fsharp
|
||||
[<WoofWare.Myriad.Plugins.JsonParse>]
|
||||
type InnerType =
|
||||
{
|
||||
[<JsonPropertyName "something">]
|
||||
Thing : string
|
||||
}
|
||||
|
||||
/// My whatnot
|
||||
[<WoofWare.Myriad.Plugins.JsonParse>]
|
||||
type JsonRecordType =
|
||||
{
|
||||
/// A thing!
|
||||
A : int
|
||||
/// Another thing!
|
||||
B : string
|
||||
[<System.Text.Json.Serialization.JsonPropertyName "hi">]
|
||||
C : int list
|
||||
D : InnerType
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
and stamps out parsing methods like this:
|
||||
|
||||
```fsharp
|
||||
/// Module containing JSON parsing methods for the InnerType type
|
||||
[<RequireQualifiedAccess>]
|
||||
[<CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix)>]
|
||||
module InnerType =
|
||||
/// Parse from a JSON node.
|
||||
let jsonParse (node: System.Text.Json.Nodes.JsonNode) : InnerType =
|
||||
let Thing = node.["something"].AsValue().GetValue<string>()
|
||||
{ Thing = Thing }
|
||||
namespace UsePlugin
|
||||
|
||||
/// Module containing JSON parsing methods for the JsonRecordType type
|
||||
[<RequireQualifiedAccess>]
|
||||
[<CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix)>]
|
||||
module JsonRecordType =
|
||||
/// Parse from a JSON node.
|
||||
let jsonParse (node: System.Text.Json.Nodes.JsonNode) : JsonRecordType =
|
||||
let D = InnerType.jsonParse node.["d"]
|
||||
|
||||
let C =
|
||||
node.["hi"].AsArray() |> Seq.map (fun elt -> elt.GetValue<int>()) |> List.ofSeq
|
||||
|
||||
let B = node.["b"].AsValue().GetValue<string>()
|
||||
let A = node.["a"].AsValue().GetValue<int>()
|
||||
{ A = A; B = B; C = C; D = D }
|
||||
```
|
||||
|
||||
### What's the point?
|
||||
|
||||
`System.Text.Json`, in a `PublishAot` context, relies on C# source generators.
|
||||
The default reflection-heavy implementations have the necessary code trimmed away, and result in a runtime exception.
|
||||
But C# source generators [are entirely unsupported in F#](https://github.com/dotnet/fsharp/issues/14300).
|
||||
|
||||
This Myriad generator expects you to use `System.Text.Json` to construct a `JsonNode`,
|
||||
and then the generator takes over to construct a strongly-typed object.
|
||||
|
||||
### Limitations
|
||||
|
||||
This source generator is enough for what I first wanted to use it for.
|
||||
However, there is *far* more that could be done.
|
||||
|
||||
* Make it possible to give an exact format and cultural info in date and time parsing.
|
||||
* Make it possible to reject parsing if extra fields are present.
|
||||
* Generally support all the `System.Text.Json` attributes.
|
||||
|
||||
## `RemoveOptions`
|
||||
|
||||
Takes a record like this:
|
||||
|
||||
```fsharp
|
||||
type Foo =
|
||||
{
|
||||
A : int option
|
||||
B : string
|
||||
C : float list
|
||||
}
|
||||
```
|
||||
|
||||
and stamps out a record like this:
|
||||
|
||||
```fsharp
|
||||
[<RequireQualifiedAccess>]
|
||||
module Foo =
|
||||
type Short =
|
||||
{
|
||||
A : int
|
||||
B : string
|
||||
C : float list
|
||||
}
|
||||
```
|
||||
|
||||
### What's the point?
|
||||
|
||||
The motivating example is argument parsing.
|
||||
An argument parser naturally wants to express "the user did not supply this, so I will provide a default".
|
||||
But it's not a very ergonomic experience for the programmer to deal with all these options,
|
||||
so this Myriad generator stamps out a type *without* any options,
|
||||
and also stamps out an appropriate constructor function.
|
||||
|
||||
### Limitations
|
||||
|
||||
This generator is *far* from where I want it, because I haven't really spent any time on it.
|
||||
|
||||
* It really wants to be able to recurse into the types within the record, to strip options from them.
|
||||
* It needs some sort of attribute to mark a field as *not* receiving this treatment.
|
||||
* What do we do about discriminated unions?
|
||||
|
||||
## `HttpClient`
|
||||
|
||||
Takes a type like this:
|
||||
|
||||
```fsharp
|
||||
[<WoofWare.Myriad.Plugins.HttpClient>]
|
||||
type IPureGymApi =
|
||||
[<Get "v1/gyms/">]
|
||||
abstract GetGyms : ?ct : CancellationToken -> Task<Gym list>
|
||||
|
||||
[<Get "v1/gyms/{gym_id}/attendance">]
|
||||
abstract GetGymAttendance : [<Path "gym_id">] gymId : int * ?ct : CancellationToken -> Task<GymAttendance>
|
||||
|
||||
[<Get "v1/member">]
|
||||
abstract GetMember : ?ct : CancellationToken -> Task<Member>
|
||||
|
||||
[<Get "v1/gyms/{gym_id}">]
|
||||
abstract GetGym : [<Path "gym_id">] gymId : int * ?ct : CancellationToken -> Task<Gym>
|
||||
|
||||
[<Get "v1/member/activity">]
|
||||
abstract GetMemberActivity : ?ct : CancellationToken -> Task<MemberActivityDto>
|
||||
|
||||
[<Get "v2/gymSessions/member">]
|
||||
abstract GetSessions :
|
||||
[<Query>] fromDate : DateTime * [<Query>] toDate : DateTime * ?ct : CancellationToken -> Task<Sessions>
|
||||
```
|
||||
|
||||
and stamps out a type like this:
|
||||
|
||||
```fsharp
|
||||
/// Module for constructing a REST client.
|
||||
[<CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix)>]
|
||||
[<RequireQualifiedAccess>]
|
||||
module PureGymApi =
|
||||
/// Create a REST client.
|
||||
let make (client : System.Net.Http.HttpClient) : IPureGymApi =
|
||||
{ new IPureGymApi with
|
||||
member _.GetGyms (ct : CancellationToken option) =
|
||||
async {
|
||||
let! ct = Async.CancellationToken
|
||||
|
||||
let httpMessage =
|
||||
new System.Net.Http.HttpRequestMessage (
|
||||
Method = System.Net.Http.HttpMethod.Get,
|
||||
RequestUri = System.Uri (client.BaseAddress.ToString () + "v1/gyms/")
|
||||
)
|
||||
|
||||
let! response = client.SendAsync (httpMessage, ct) |> Async.AwaitTask
|
||||
let response = response.EnsureSuccessStatusCode ()
|
||||
let! stream = response.Content.ReadAsStreamAsync ct |> Async.AwaitTask
|
||||
|
||||
let! node =
|
||||
System.Text.Json.Nodes.JsonNode.ParseAsync (stream, cancellationToken = ct)
|
||||
|> Async.AwaitTask
|
||||
|
||||
return node.AsArray () |> Seq.map (fun elt -> Gym.jsonParse elt) |> List.ofSeq
|
||||
}
|
||||
|> (fun a -> Async.StartAsTask (a, ?cancellationToken = ct))
|
||||
|
||||
// (more methods here)
|
||||
}
|
||||
```
|
||||
|
||||
### What's the point?
|
||||
|
||||
The motivating example is again ahead-of-time compilation: we wish to avoid the reflection which RestEase does.
|
||||
|
||||
### Limitations
|
||||
|
||||
RestEase is complex, and handles a lot of different stuff.
|
||||
|
||||
* As of this writing, `[<Body>]` is explicitly unsupported (it throws with a TODO).
|
||||
* Parameters are serialised solely with `ToString`, and there's no control over this;
|
||||
nor is there control over encoding in any sense.
|
||||
* Deserialisation follows the same logic as the `JsonParse` generator,
|
||||
and it generally assumes you're using types which `JsonParse` is applied to.
|
||||
* Headers are not yet supported.
|
||||
* You have to specify the `BaseAddress` on the input client yourself, and you can't have the same client talking to a
|
||||
different `BaseAddress` this way unless you manually set it before making any different request.
|
||||
* I haven't yet worked out how to integrate this with a mocked HTTP client; you can always mock up an `HttpClient`,
|
||||
but I prefer to use a mock which defines a single member `SendAsync`.
|
||||
* Anonymous parameters are currently forbidden.
|
||||
* Every function must take an optional `CancellationToken` (which is good practice anyway);
|
||||
so arguments are forced to be tupled.
|
||||
This is a won't-fix for as long as F# requires tupled arguments if any of the args are optional.
|
||||
|
||||
# Detailed examples
|
||||
|
||||
See the tests.
|
||||
For example, [PureGymDto.fs](./ConsumePlugin/PureGymDto.fs) is a real-world set of DTOs.
|
||||
|
||||
## How to use
|
||||
|
||||
* In your `.fsproj` file, define a helper variable so that subsequent steps don't all have to be kept in sync:
|
||||
```xml
|
||||
<PropertyGroup>
|
||||
<WoofWareMyriadPluginVersion>1.1.5</WoofWareMyriadPluginVersion>
|
||||
</PropertyGroup>
|
||||
```
|
||||
* Take a reference on `WoofWare.Myriad.Plugins`:
|
||||
```xml
|
||||
<ItemGroup>
|
||||
<PackageReference Include="WoofWare.Myriad.Plugins" Version="$(WoofWareMyriadPluginVersion)" />
|
||||
</ItemGroup>
|
||||
```
|
||||
* Point Myriad to the DLL within the NuGet package which is the source of the plugins:
|
||||
```xml
|
||||
<ItemGroup>
|
||||
<MyriadSdkGenerator Include="$(NuGetPackageRoot)/woofware.myriad.plugins/$(WoofWareMyriadPluginVersion)/lib/net6.0/WoofWare.Myriad.Plugins.dll" />
|
||||
</ItemGroup>
|
||||
```
|
||||
|
||||
Now you are ready to start using the generators.
|
||||
For example, this specifies that Myriad is to use the contents of `Client.fs` to generate the file `GeneratedClient.fs`:
|
||||
|
||||
```xml
|
||||
<ItemGroup>
|
||||
<Compile Include="Client.fs" />
|
||||
<Compile Include="GeneratedClient.fs">
|
||||
<MyriadFile>Client.fs</MyriadFile>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
```
|
||||
|
||||
### Myriad Gotchas
|
||||
|
||||
* MsBuild doesn't always realise that it needs to invoke Myriad during rebuild.
|
||||
You can always save a whitespace change to the source file (e.g. `Client.fs` above),
|
||||
and MsBuild will then execute Myriad during the next build.
|
||||
* [Fantomas](https://github.com/fsprojects/fantomas), the F# source formatter which powers Myriad,
|
||||
is customisable with [editorconfig](https://editorconfig.org/),
|
||||
but it [does not easily expose](https://github.com/fsprojects/fantomas/issues/3031) this customisation
|
||||
except through the standalone Fantomas client.
|
||||
So Myriad's output is formatted without respect to any conventions which may hold in the rest of your repository.
|
||||
You should probably add these files to your [fantomasignore](https://github.com/fsprojects/fantomas/blob/a999b77ca5a024fbc3409955faac797e29b39d27/docs/docs/end-users/IgnoreFiles.md)
|
||||
if you use Fantomas to format your repo;
|
||||
the alternative is to manually reformat every time Myriad changes the generated files.
|
||||
# WoofWare.Myriad.Plugins
|
||||
|
||||
[](https://www.nuget.org/packages/WoofWare.Myriad.Plugins)
|
||||
[](https://github.com/Smaug123/WoofWare.Myriad/actions?query=branch%3Amain)
|
||||
[](./LICENSE)
|
||||
|
||||

|
||||
|
||||
Some helpers in [Myriad](https://github.com/MoiraeSoftware/myriad/) which might be useful.
|
||||
|
||||
These are currently somewhat experimental, and I personally am their primary customer.
|
||||
The `RemoveOptions` generator in particular is extremely half-baked.
|
||||
|
||||
If you would like to ensure that your particular use-case remains unbroken, please do contribute tests to this repository.
|
||||
The `ConsumePlugin` assembly contains a number of invocations of these source generators,
|
||||
so you just need to add copies of your types to that assembly to ensure that I will at least notice if I break the build;
|
||||
and if you add tests to `WoofWare.Myriad.Plugins.Test` then I will also notice if I break the runtime semantics of the generated code.
|
||||
|
||||
Currently implemented:
|
||||
|
||||
* `JsonParse` (to stamp out `jsonParse : JsonNode -> 'T` methods);
|
||||
* `JsonSerialize` (to stamp out `toJsonNode : 'T -> JsonNode` methods);
|
||||
* `RemoveOptions` (to strip `option` modifiers from a type).
|
||||
* `HttpClient` (to stamp out a [RestEase](https://github.com/canton7/RestEase)-style HTTP client).
|
||||
* `GenerateMock` (to stamp out a record type corresponding to an interface).
|
||||
* `CreateCatamorphism` (to stamp out a non-stack-overflowing [catamorphism](https://fsharpforfunandprofit.com/posts/recursive-types-and-folds/) for a discriminated union).
|
||||
|
||||
## `JsonParse`
|
||||
|
||||
Takes records like this:
|
||||
|
||||
```fsharp
|
||||
[<WoofWare.Myriad.Plugins.JsonParse>]
|
||||
type InnerType =
|
||||
{
|
||||
[<JsonPropertyName "something">]
|
||||
Thing : string
|
||||
}
|
||||
|
||||
/// My whatnot
|
||||
[<WoofWare.Myriad.Plugins.JsonParse>]
|
||||
type JsonRecordType =
|
||||
{
|
||||
/// A thing!
|
||||
A : int
|
||||
/// Another thing!
|
||||
B : string
|
||||
[<System.Text.Json.Serialization.JsonPropertyName "hi">]
|
||||
C : int list
|
||||
D : InnerType
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
and stamps out parsing methods like this:
|
||||
|
||||
```fsharp
|
||||
/// Module containing JSON parsing methods for the InnerType type
|
||||
[<RequireQualifiedAccess>]
|
||||
[<CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix)>]
|
||||
module InnerType =
|
||||
/// Parse from a JSON node.
|
||||
let jsonParse (node: System.Text.Json.Nodes.JsonNode) : InnerType =
|
||||
let Thing = node.["something"].AsValue().GetValue<string>()
|
||||
{ Thing = Thing }
|
||||
namespace UsePlugin
|
||||
|
||||
/// Module containing JSON parsing methods for the JsonRecordType type
|
||||
[<RequireQualifiedAccess>]
|
||||
[<CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix)>]
|
||||
module JsonRecordType =
|
||||
/// Parse from a JSON node.
|
||||
let jsonParse (node: System.Text.Json.Nodes.JsonNode) : JsonRecordType =
|
||||
let D = InnerType.jsonParse node.["d"]
|
||||
|
||||
let C =
|
||||
node.["hi"].AsArray() |> Seq.map (fun elt -> elt.GetValue<int>()) |> List.ofSeq
|
||||
|
||||
let B = node.["b"].AsValue().GetValue<string>()
|
||||
let A = node.["a"].AsValue().GetValue<int>()
|
||||
{ A = A; B = B; C = C; D = D }
|
||||
```
|
||||
|
||||
You can optionally supply the boolean `true` to the attribute,
|
||||
which will cause Myriad to stamp out an extension method rather than a module with the same name as the type.
|
||||
This is useful if you want to reuse the type name as a module name yourself,
|
||||
or if you want to apply multiple source generators which each want to use the module name.
|
||||
|
||||
### What's the point?
|
||||
|
||||
`System.Text.Json`, in a `PublishAot` context, relies on C# source generators.
|
||||
The default reflection-heavy implementations have the necessary code trimmed away, and result in a runtime exception.
|
||||
But C# source generators [are entirely unsupported in F#](https://github.com/dotnet/fsharp/issues/14300).
|
||||
|
||||
This Myriad generator expects you to use `System.Text.Json` to construct a `JsonNode`,
|
||||
and then the generator takes over to construct a strongly-typed object.
|
||||
|
||||
### Limitations
|
||||
|
||||
This source generator is enough for what I first wanted to use it for.
|
||||
However, there is *far* more that could be done.
|
||||
|
||||
* Make it possible to give an exact format and cultural info in date and time parsing.
|
||||
* Make it possible to reject parsing if extra fields are present.
|
||||
* Generally support all the `System.Text.Json` attributes.
|
||||
|
||||
For an example of using both `JsonParse` and `JsonSerialize` together with complex types, see [the type definitions](./ConsumePlugin/SerializationAndDeserialization.fs) and [tests](./WoofWare.Myriad.Plugins.Test/TestJsonSerialize/TestJsonSerde.fs).
|
||||
|
||||
## `JsonSerialize`
|
||||
|
||||
Takes records like this:
|
||||
```fsharp
|
||||
[<WoofWare.Myriad.Plugins.JsonSerialize true>]
|
||||
type InnerTypeWithBoth =
|
||||
{
|
||||
[<JsonPropertyName("it's-a-me")>]
|
||||
Thing : string
|
||||
ReadOnlyDict : IReadOnlyDictionary<string, Uri list>
|
||||
}
|
||||
```
|
||||
|
||||
and stamps out modules like this:
|
||||
```fsharp
|
||||
module InnerTypeWithBoth =
|
||||
let toJsonNode (input : InnerTypeWithBoth) : System.Text.Json.Nodes.JsonNode =
|
||||
let node = System.Text.Json.Nodes.JsonObject ()
|
||||
|
||||
do
|
||||
node.Add (("it's-a-me"), System.Text.Json.Nodes.JsonValue.Create<string> input.Thing)
|
||||
|
||||
node.Add (
|
||||
"ReadOnlyDict",
|
||||
(fun field ->
|
||||
let ret = System.Text.Json.Nodes.JsonObject ()
|
||||
|
||||
for (KeyValue (key, value)) in field do
|
||||
ret.Add (key.ToString (), System.Text.Json.Nodes.JsonValue.Create<Uri> value)
|
||||
|
||||
ret
|
||||
) input.Map
|
||||
)
|
||||
|
||||
node
|
||||
```
|
||||
|
||||
As in `JsonParse`, you can optionally supply the boolean `true` to the attribute,
|
||||
which will cause Myriad to stamp out an extension method rather than a module with the same name as the type.
|
||||
|
||||
The same limitations generally apply to `JsonSerialize` as do to `JsonParse`.
|
||||
|
||||
For an example of using both `JsonParse` and `JsonSerialize` together with complex types, see [the type definitions](./ConsumePlugin/SerializationAndDeserialization.fs) and [tests](./WoofWare.Myriad.Plugins.Test/TestJsonSerialize/TestJsonSerde.fs).
|
||||
|
||||
## `RemoveOptions`
|
||||
|
||||
Takes a record like this:
|
||||
|
||||
```fsharp
|
||||
type Foo =
|
||||
{
|
||||
A : int option
|
||||
B : string
|
||||
C : float list
|
||||
}
|
||||
```
|
||||
|
||||
and stamps out a record like this:
|
||||
|
||||
```fsharp
|
||||
[<RequireQualifiedAccess>]
|
||||
module Foo =
|
||||
type Short =
|
||||
{
|
||||
A : int
|
||||
B : string
|
||||
C : float list
|
||||
}
|
||||
```
|
||||
|
||||
### What's the point?
|
||||
|
||||
The motivating example is argument parsing.
|
||||
An argument parser naturally wants to express "the user did not supply this, so I will provide a default".
|
||||
But it's not a very ergonomic experience for the programmer to deal with all these options,
|
||||
so this Myriad generator stamps out a type *without* any options,
|
||||
and also stamps out an appropriate constructor function.
|
||||
|
||||
### Limitations
|
||||
|
||||
This generator is *far* from where I want it, because I haven't really spent any time on it.
|
||||
|
||||
* It really wants to be able to recurse into the types within the record, to strip options from them.
|
||||
* It needs some sort of attribute to mark a field as *not* receiving this treatment.
|
||||
* What do we do about discriminated unions?
|
||||
|
||||
## `HttpClient`
|
||||
|
||||
Takes a type like this:
|
||||
|
||||
```fsharp
|
||||
[<WoofWare.Myriad.Plugins.HttpClient>]
|
||||
type IPureGymApi =
|
||||
[<Get "v1/gyms/">]
|
||||
abstract GetGyms : ?ct : CancellationToken -> Task<Gym list>
|
||||
|
||||
[<Get "v1/gyms/{gym_id}/attendance">]
|
||||
abstract GetGymAttendance : [<Path "gym_id">] gymId : int * ?ct : CancellationToken -> Task<GymAttendance>
|
||||
|
||||
[<Get "v1/member">]
|
||||
abstract GetMember : ?ct : CancellationToken -> Task<Member>
|
||||
|
||||
[<Get "v1/gyms/{gym_id}">]
|
||||
abstract GetGym : [<Path "gym_id">] gymId : int * ?ct : CancellationToken -> Task<Gym>
|
||||
|
||||
[<Get "v1/member/activity">]
|
||||
abstract GetMemberActivity : ?ct : CancellationToken -> Task<MemberActivityDto>
|
||||
|
||||
[<Get "v2/gymSessions/member">]
|
||||
abstract GetSessions :
|
||||
[<Query>] fromDate : DateTime * [<Query>] toDate : DateTime * ?ct : CancellationToken -> Task<Sessions>
|
||||
```
|
||||
|
||||
and stamps out a type like this:
|
||||
|
||||
```fsharp
|
||||
/// Module for constructing a REST client.
|
||||
[<CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix)>]
|
||||
[<RequireQualifiedAccess>]
|
||||
module PureGymApi =
|
||||
/// Create a REST client.
|
||||
let make (client : System.Net.Http.HttpClient) : IPureGymApi =
|
||||
{ new IPureGymApi with
|
||||
member _.GetGyms (ct : CancellationToken option) =
|
||||
async {
|
||||
let! ct = Async.CancellationToken
|
||||
|
||||
let httpMessage =
|
||||
new System.Net.Http.HttpRequestMessage (
|
||||
Method = System.Net.Http.HttpMethod.Get,
|
||||
RequestUri = System.Uri (client.BaseAddress.ToString () + "v1/gyms/")
|
||||
)
|
||||
|
||||
let! response = client.SendAsync (httpMessage, ct) |> Async.AwaitTask
|
||||
let response = response.EnsureSuccessStatusCode ()
|
||||
let! stream = response.Content.ReadAsStreamAsync ct |> Async.AwaitTask
|
||||
|
||||
let! node =
|
||||
System.Text.Json.Nodes.JsonNode.ParseAsync (stream, cancellationToken = ct)
|
||||
|> Async.AwaitTask
|
||||
|
||||
return node.AsArray () |> Seq.map (fun elt -> Gym.jsonParse elt) |> List.ofSeq
|
||||
}
|
||||
|> (fun a -> Async.StartAsTask (a, ?cancellationToken = ct))
|
||||
|
||||
// (more methods here)
|
||||
}
|
||||
```
|
||||
|
||||
### What's the point?
|
||||
|
||||
The motivating example is again ahead-of-time compilation: we wish to avoid the reflection which RestEase does.
|
||||
|
||||
### Features
|
||||
|
||||
* Variable and constant header values are supported:
|
||||
see [the definition of `IApiWithHeaders`](./ConsumePlugin/RestApiExample.fs).
|
||||
|
||||
### Limitations
|
||||
|
||||
RestEase is complex, and handles a lot of different stuff.
|
||||
|
||||
* If you set the `BaseAddress` on your input `HttpClient`, make sure to end with a trailing slash
|
||||
on any trailing directories (so `"blah/foo/"` rather than `"blah/foo"`).
|
||||
We combine URIs using `UriKind.Relative`, so without a trailing slash, the last component may be chopped off.
|
||||
* Parameters are serialised naively with `toJsonNode` as though the `JsonSerialize` generator were applied,
|
||||
and you can't control the serialisation. You can't yet serialise e.g. a primitive type this way (other than `String`);
|
||||
all body parameters must be types which have a suitable `toJsonNode : 'a -> JsonNode` method.
|
||||
* Deserialisation follows the same logic as the `JsonParse` generator,
|
||||
and it generally assumes you're using types which `JsonParse` is applied to.
|
||||
* Anonymous parameters are currently forbidden.
|
||||
|
||||
There are also some design decisions:
|
||||
|
||||
* Every function must take an optional `CancellationToken` (which is good practice anyway);
|
||||
so arguments are forced to be tupled.
|
||||
* The `[<Optional>]` attribute is not supported and will probably not be supported, because I consider it to be cursed.
|
||||
|
||||
## `GenerateMock`
|
||||
|
||||
Takes a type like this:
|
||||
|
||||
```fsharp
|
||||
[<GenerateMock>]
|
||||
type IPublicType =
|
||||
abstract Mem1 : string * int -> string list
|
||||
abstract Mem2 : string -> int
|
||||
```
|
||||
|
||||
and stamps out a type like this:
|
||||
|
||||
```fsharp
|
||||
/// Mock record type for an interface
|
||||
type internal PublicTypeMock =
|
||||
{
|
||||
Mem1 : string * int -> string list
|
||||
Mem2 : string -> int
|
||||
}
|
||||
|
||||
static member Empty : PublicTypeMock =
|
||||
{
|
||||
Mem1 = (fun x -> raise (System.NotImplementedException "Unimplemented mock function"))
|
||||
Mem2 = (fun x -> raise (System.NotImplementedException "Unimplemented mock function"))
|
||||
}
|
||||
|
||||
interface IPublicType with
|
||||
member this.Mem1 (arg0, arg1) = this.Mem1 (arg0, arg1)
|
||||
member this.Mem2 (arg0) = this.Mem2 (arg0)
|
||||
```
|
||||
|
||||
### What's the point?
|
||||
|
||||
Reflective mocking libraries like [Foq](https://github.com/fsprojects/Foq) in my experience are a rich source of flaky tests.
|
||||
The [Grug-brained developer](https://grugbrain.dev/) would prefer to do this without reflection, and this reduces the rate of strange one-in-ten-thousand "failed to generate IL" errors.
|
||||
But since F# does not let you partially update an interface definition, we instead stamp out a record,
|
||||
thereby allowing the programmer to use F#'s record-update syntax.
|
||||
|
||||
### Features
|
||||
|
||||
* You may supply an `isInternal : bool` argument to the attribute. By default, we make the resulting record type at most internal (never public), since this is intended only to be used in tests; but you can instead make it public with `[<GenerateMock false>]`.
|
||||
|
||||
## `CreateCatamorphism`
|
||||
|
||||
Takes a collection of mutually recursive discriminated unions:
|
||||
|
||||
```fsharp
|
||||
[<CreateCatamorphism "MyCata">]
|
||||
type Expr =
|
||||
| Const of Const
|
||||
| Pair of Expr * Expr * PairOpKind
|
||||
| Sequential of Expr list
|
||||
| Builder of Expr * ExprBuilder
|
||||
|
||||
and ExprBuilder =
|
||||
| Child of ExprBuilder
|
||||
| Parent of Expr
|
||||
```
|
||||
|
||||
and stamps out a type like this:
|
||||
```fsharp
|
||||
type ExprCata<'Expr, 'ExprBuilder> =
|
||||
abstract Const : Const -> 'Expr
|
||||
abstract Pair : 'Expr -> 'Expr -> PairOpKind -> 'Expr
|
||||
abstract Sequential : 'Expr list -> 'Expr
|
||||
abstract Builder : 'Expr -> 'ExprBuilder -> 'Expr
|
||||
|
||||
type ExprBuilderCata<'Expr, 'ExprBuilder> =
|
||||
abstract Child : 'ExprBuilder -> 'ExprBuilder
|
||||
abstract Parent : 'Expr -> 'ExprBuilder
|
||||
|
||||
type MyCata<'Expr, 'ExprBuilder> =
|
||||
{
|
||||
Expr : ExprCata<'Expr, 'ExprBuilder>
|
||||
ExprBuilder : ExprBuilderCata<'Expr, 'ExprBuilder>
|
||||
}
|
||||
|
||||
[<RequireQualifiedAccess>]
|
||||
module ExprCata =
|
||||
let runExpr (cata : MyCata<'ExprRet, 'ExprBuilderRet>) (x : Expr) : 'ExprRet =
|
||||
failwith "this is implemented"
|
||||
|
||||
let runExprBuilder (cata : MyCata<'ExprRet, 'ExprBuilderRet>) (x : ExprBuilder) : 'ExprBuilderRet =
|
||||
failwith "this is implemented"
|
||||
```
|
||||
|
||||
### What's the point?
|
||||
Recursing over a tree is not easy to get right, especially if you want to avoid stack overflows.
|
||||
Instead of writing the recursion many times, it's better to do it once,
|
||||
and then each time you only plug in what you want to do.
|
||||
|
||||
### Features
|
||||
|
||||
* Mutually recursive DUs are supported (as in the example above).
|
||||
Every DU in a recursive `type Foo... and Bar...` knot will be given an appropriate cata, as long as any one of those DUs has the `[<CreateCatamorphism>]` attribute.
|
||||
* There is *limited* support for records and for lists.
|
||||
* There is *extremely brittle* support for generics in the DUs you are cata'ing over.
|
||||
It is based on the names of the generic parameters, so you must ensure that generic parameters with the same name have the same meaning across the various cases in your recursive knot of DUs.
|
||||
(If you overstep the bounds of what this generator can do, you will get compile-time errors, e.g. with generics being constrained to each other's values.)
|
||||
See the [List tests](./WoofWare.Myriad.Plugins.Test/TestCataGenerator/TestMyList2.fs) for an example, where we re-implement `FSharpList<'a>`.
|
||||
|
||||
### Limitations
|
||||
|
||||
**I am not at all convinced of the correctness of this generator**, and I know it is very incomplete (in the sense that there are many possible DUs you could write for which the generator will bail out).
|
||||
I *strongly* recommend implementing the identity catamorphism for your type and using property-based tests ([as I do](./WoofWare.Myriad.Plugins.Test/TestCataGenerator/TestDirectory.fs)) to assert that the correct thing happens.
|
||||
Feel free to raise GitHub issues with code I can copy-paste to reproduce a case where the wrong thing happens (though I can't promise to look at them).
|
||||
|
||||
* This is a particularly half-baked generator which has so far seen no real-world use.
|
||||
It likely has a bunch of [80/20](https://en.wikipedia.org/wiki/Pareto_principle) low-hanging fruit remaining, but it also likely has impossible problems to solve which I don't know about yet.
|
||||
* Only a very few kinds of DU field are currently implemented.
|
||||
For example, this generator can't see through an interface (e.g. the kind of interface one would use to implement the [crate pattern](https://www.patrickstevens.co.uk/posts/2021-10-19-crates/) to represent a [GADT](https://en.wikipedia.org/wiki/Generalized_algebraic_data_type)),
|
||||
so the generated cata will simply grant you access to the interface (rather than attempting to descend into it to discover recursive references).
|
||||
You can't nest lists deeply. All sorts of other cases are unaddressed.
|
||||
* This generator does not try to solve the "exponential diamond dependency" problem.
|
||||
If you have a case of the form `type Expr = | Branch of Expr * Expr`, the cata will walk into both `Expr`s separately.
|
||||
If the `Expr`s happen to be equal, the cata will nevertheless traverse them individually (that is, it will traverse the same `Expr` twice).
|
||||
Your type may represent a [DAG](https://en.wikipedia.org/wiki/Directed_acyclic_graph), but we will always effectively expand it into a tree of paths and operate on each of the exponentially-many paths.
|
||||
|
||||
# Detailed examples
|
||||
|
||||
See the tests.
|
||||
For example, [PureGymDto.fs](./ConsumePlugin/PureGymDto.fs) is a real-world set of DTOs.
|
||||
|
||||
## How to use
|
||||
|
||||
* In your `.fsproj` file, define a helper variable so that subsequent steps don't all have to be kept in sync:
|
||||
```xml
|
||||
<PropertyGroup>
|
||||
<WoofWareMyriadPluginVersion>2.0.1</WoofWareMyriadPluginVersion>
|
||||
</PropertyGroup>
|
||||
```
|
||||
* Take a reference on `WoofWare.Myriad.Plugins.Attributes` (which has no other dependencies), to obtain access to the attributes which the generator will recognise:
|
||||
```xml
|
||||
<ItemGroup>
|
||||
<PackageReference Include="WoofWare.Myriad.Plugins.Attributes" Version="2.0.2" />
|
||||
</ItemGroup>
|
||||
```
|
||||
* Take a reference (with private assets, to prevent these from propagating to your own assembly) on `WoofWare.Myriad.Plugins`, to obtain the plugins which Myriad will run, and on `Myriad.Sdk`, to obtain the Myriad binary itself:
|
||||
```xml
|
||||
<ItemGroup>
|
||||
<PackageReference Include="WoofWare.Myriad.Plugins" Version="$(WoofWareMyriadPluginVersion)" PrivateAssets="all" />
|
||||
<PackageReference Include="Myriad.Sdk" Version="0.8.3" PrivateAssets="all" />
|
||||
</ItemGroup>
|
||||
```
|
||||
* Point Myriad to the DLL within the NuGet package which is the source of the plugins:
|
||||
```xml
|
||||
<ItemGroup>
|
||||
<MyriadSdkGenerator Include="$(NuGetPackageRoot)/woofware.myriad.plugins/$(WoofWareMyriadPluginVersion)/lib/net6.0/WoofWare.Myriad.Plugins.dll" />
|
||||
</ItemGroup>
|
||||
```
|
||||
|
||||
Now you are ready to start using the generators.
|
||||
For example, this specifies that Myriad is to use the contents of `Client.fs` to generate the file `GeneratedClient.fs`:
|
||||
|
||||
```xml
|
||||
<ItemGroup>
|
||||
<Compile Include="Client.fs" />
|
||||
<Compile Include="GeneratedClient.fs">
|
||||
<MyriadFile>Client.fs</MyriadFile>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
```
|
||||
|
||||
### Myriad Gotchas
|
||||
|
||||
* MsBuild doesn't always realise that it needs to invoke Myriad during rebuild.
|
||||
You can always save a whitespace change to the source file (e.g. `Client.fs` above),
|
||||
and MsBuild will then execute Myriad during the next build.
|
||||
* [Fantomas](https://github.com/fsprojects/fantomas), the F# source formatter which powers Myriad,
|
||||
is customisable with [editorconfig](https://editorconfig.org/),
|
||||
but it [does not easily expose](https://github.com/fsprojects/fantomas/issues/3031) this customisation
|
||||
except through the standalone Fantomas client.
|
||||
So Myriad's output is formatted without respect to any conventions which may hold in the rest of your repository.
|
||||
You should probably add these files to your [fantomasignore](https://github.com/fsprojects/fantomas/blob/a999b77ca5a024fbc3409955faac797e29b39d27/docs/docs/end-users/IgnoreFiles.md)
|
||||
if you use Fantomas to format your repo;
|
||||
the alternative is to manually reformat every time Myriad changes the generated files.
|
||||
|
72
WoofWare.Myriad.Plugins.Attributes/Attributes.fs
Normal file
72
WoofWare.Myriad.Plugins.Attributes/Attributes.fs
Normal file
@@ -0,0 +1,72 @@
|
||||
namespace WoofWare.Myriad.Plugins
|
||||
|
||||
open System
|
||||
|
||||
/// Attribute indicating a record type to which the "Remove Options" Myriad
|
||||
/// generator should apply during build.
|
||||
/// The purpose of this generator is to strip the `option` modifier from types.
|
||||
type RemoveOptionsAttribute () =
|
||||
inherit Attribute ()
|
||||
|
||||
/// Attribute indicating an interface type for which the "Generate Mock" Myriad
|
||||
/// generator should apply during build.
|
||||
/// This generator creates a record which implements the interface,
|
||||
/// but where each method is represented as a record field, so you can use
|
||||
/// record update syntax to easily specify partially-implemented mock objects.
|
||||
/// You may optionally specify `isInternal = false` to get a mock with the public visibility modifier.
|
||||
type GenerateMockAttribute (isInternal : bool) =
|
||||
inherit Attribute ()
|
||||
/// The default value of `isInternal`, the optional argument to the GenerateMockAttribute constructor.
|
||||
static member DefaultIsInternal = true
|
||||
|
||||
/// Shorthand for the "isExtensionMethod = false" constructor; see documentation there for details.
|
||||
new () = GenerateMockAttribute GenerateMockAttribute.DefaultIsInternal
|
||||
|
||||
/// Attribute indicating a record type to which the "Add JSON serializer" Myriad
|
||||
/// generator should apply during build.
|
||||
/// The purpose of this generator is to create methods (possibly extension methods) of the form
|
||||
/// `{TypeName}.toJsonNode : {TypeName} -> System.Text.Json.Nodes.JsonNode`.
|
||||
///
|
||||
/// If you supply isExtensionMethod = true, you will get extension methods.
|
||||
/// These can only be consumed from F#, but the benefit is that they don't use up the module name
|
||||
/// (since by default we create a module called "{TypeName}").
|
||||
type JsonSerializeAttribute (isExtensionMethod : bool) =
|
||||
inherit Attribute ()
|
||||
|
||||
/// The default value of `isExtensionMethod`, the optional argument to the JsonSerializeAttribute constructor.
|
||||
static member DefaultIsExtensionMethod = false
|
||||
|
||||
/// Shorthand for the "isExtensionMethod = false" constructor; see documentation there for details.
|
||||
new () = JsonSerializeAttribute JsonSerializeAttribute.DefaultIsExtensionMethod
|
||||
|
||||
/// Attribute indicating a record type to which the "Add JSON parse" Myriad
|
||||
/// generator should apply during build.
|
||||
/// The purpose of this generator is to create methods (possibly extension methods) of the form
|
||||
/// `{TypeName}.jsonParse : System.Text.Json.Nodes.JsonNode -> {TypeName}`.
|
||||
///
|
||||
/// If you supply isExtensionMethod = true, you will get extension methods.
|
||||
/// These can only be consumed from F#, but the benefit is that they don't use up the module name
|
||||
/// (since by default we create a module called "{TypeName}").
|
||||
type JsonParseAttribute (isExtensionMethod : bool) =
|
||||
inherit Attribute ()
|
||||
|
||||
/// The default value of `isExtensionMethod`, the optional argument to the JsonParseAttribute constructor.
|
||||
static member DefaultIsExtensionMethod = false
|
||||
|
||||
/// Shorthand for the "isExtensionMethod = false" constructor; see documentation there for details.
|
||||
new () = JsonParseAttribute JsonParseAttribute.DefaultIsExtensionMethod
|
||||
|
||||
/// Attribute indicating a record type to which the "create HTTP client" Myriad
|
||||
/// generator should apply during build.
|
||||
/// This generator is intended to replicate much of the functionality of RestEase,
|
||||
/// i.e. to stamp out HTTP REST clients from interfaces defining the API.
|
||||
type HttpClientAttribute () =
|
||||
inherit Attribute ()
|
||||
|
||||
/// Attribute indicating a DU type to which the "create catamorphism" Myriad
|
||||
/// generator should apply during build.
|
||||
/// Supply the `typeName` for the name of the record type we will generate, which contains
|
||||
/// all the catas required; for example, "MyThing" would generate:
|
||||
/// type MyThing<'a, 'b> = { Du1 : Du1Cata<'a, 'b> ; Du2 : Du2Cata<'a, 'b> }.
|
||||
type CreateCatamorphismAttribute (typeName : string) =
|
||||
inherit Attribute ()
|
21
WoofWare.Myriad.Plugins.Attributes/SurfaceBaseline.txt
Normal file
21
WoofWare.Myriad.Plugins.Attributes/SurfaceBaseline.txt
Normal file
@@ -0,0 +1,21 @@
|
||||
WoofWare.Myriad.Plugins.CreateCatamorphismAttribute inherit System.Attribute
|
||||
WoofWare.Myriad.Plugins.CreateCatamorphismAttribute..ctor [constructor]: string
|
||||
WoofWare.Myriad.Plugins.GenerateMockAttribute inherit System.Attribute
|
||||
WoofWare.Myriad.Plugins.GenerateMockAttribute..ctor [constructor]: bool
|
||||
WoofWare.Myriad.Plugins.GenerateMockAttribute..ctor [constructor]: unit
|
||||
WoofWare.Myriad.Plugins.GenerateMockAttribute.DefaultIsInternal [static property]: [read-only] bool
|
||||
WoofWare.Myriad.Plugins.GenerateMockAttribute.get_DefaultIsInternal [static method]: unit -> bool
|
||||
WoofWare.Myriad.Plugins.HttpClientAttribute inherit System.Attribute
|
||||
WoofWare.Myriad.Plugins.HttpClientAttribute..ctor [constructor]: unit
|
||||
WoofWare.Myriad.Plugins.JsonParseAttribute inherit System.Attribute
|
||||
WoofWare.Myriad.Plugins.JsonParseAttribute..ctor [constructor]: bool
|
||||
WoofWare.Myriad.Plugins.JsonParseAttribute..ctor [constructor]: unit
|
||||
WoofWare.Myriad.Plugins.JsonParseAttribute.DefaultIsExtensionMethod [static property]: [read-only] bool
|
||||
WoofWare.Myriad.Plugins.JsonParseAttribute.get_DefaultIsExtensionMethod [static method]: unit -> bool
|
||||
WoofWare.Myriad.Plugins.JsonSerializeAttribute inherit System.Attribute
|
||||
WoofWare.Myriad.Plugins.JsonSerializeAttribute..ctor [constructor]: bool
|
||||
WoofWare.Myriad.Plugins.JsonSerializeAttribute..ctor [constructor]: unit
|
||||
WoofWare.Myriad.Plugins.JsonSerializeAttribute.DefaultIsExtensionMethod [static property]: [read-only] bool
|
||||
WoofWare.Myriad.Plugins.JsonSerializeAttribute.get_DefaultIsExtensionMethod [static method]: unit -> bool
|
||||
WoofWare.Myriad.Plugins.RemoveOptionsAttribute inherit System.Attribute
|
||||
WoofWare.Myriad.Plugins.RemoveOptionsAttribute..ctor [constructor]: unit
|
@@ -1,4 +1,4 @@
|
||||
namespace MyriadPlugin.Test
|
||||
namespace WoofWare.Myriad.Plugins.Attributes.Test
|
||||
|
||||
open NUnit.Framework
|
||||
open WoofWare.Myriad.Plugins
|
||||
@@ -11,9 +11,11 @@ module TestSurface =
|
||||
[<Test>]
|
||||
let ``Ensure API surface has not been modified`` () = ApiSurface.assertIdentical assembly
|
||||
|
||||
(*
|
||||
[<Test>]
|
||||
let ``Check version against remote`` () =
|
||||
MonotonicVersion.validate assembly "WoofWare.Myriad.Plugins"
|
||||
MonotonicVersion.validate assembly "WoofWare.Myriad.Plugins.Attributes"
|
||||
*)
|
||||
|
||||
[<Test ; Explicit>]
|
||||
let ``Update API surface`` () =
|
@@ -0,0 +1,25 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
|
||||
<IsPackable>false</IsPackable>
|
||||
<IsTestProject>true</IsTestProject>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Include="TestSurface.fs" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="ApiSurface" Version="4.0.33" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.9.0"/>
|
||||
<PackageReference Include="NUnit" Version="4.1.0"/>
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="4.5.0"/>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\WoofWare.Myriad.Plugins.Attributes.fsproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
@@ -0,0 +1,38 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<Authors>Patrick Stevens</Authors>
|
||||
<Copyright>Copyright (c) Patrick Stevens 2024</Copyright>
|
||||
<Description>Attributes to accompany the WoofWare.Myriad.Plugins source generator, so that you need take no runtime dependencies to use them.</Description>
|
||||
<RepositoryType>git</RepositoryType>
|
||||
<RepositoryUrl>https://github.com/Smaug123/WoofWare.Myriad</RepositoryUrl>
|
||||
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
||||
<PackageReadmeFile>README.md</PackageReadmeFile>
|
||||
<PackageTags>myriad;fsharp;source-generator;source-gen;json</PackageTags>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<WarnOn>FS3559</WarnOn>
|
||||
<PackageId>WoofWare.Myriad.Plugins.Attributes</PackageId>
|
||||
<PackageIcon>logo.png</PackageIcon>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Include="Attributes.fs"/>
|
||||
<EmbeddedResource Include="version.json"/>
|
||||
<EmbeddedResource Include="SurfaceBaseline.txt"/>
|
||||
<None Include="..\README.md">
|
||||
<Pack>True</Pack>
|
||||
<PackagePath>\</PackagePath>
|
||||
</None>
|
||||
<None Include="../WoofWare.Myriad.Plugins/logo.png">
|
||||
<Pack>True</Pack>
|
||||
<PackagePath>\</PackagePath>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Update="FSharp.Core" Version="4.3.4"/>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
7
WoofWare.Myriad.Plugins.Attributes/version.json
Normal file
7
WoofWare.Myriad.Plugins.Attributes/version.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"version": "2.2",
|
||||
"publicReleaseRefSpec": [
|
||||
"^refs/heads/main$"
|
||||
],
|
||||
"pathFilters": null
|
||||
}
|
@@ -1,4 +1,4 @@
|
||||
namespace MyriadPlugin.Test
|
||||
namespace WoofWare.Myriad.Plugins.Test
|
||||
|
||||
open System.Net.Http
|
||||
|
||||
@@ -11,7 +11,11 @@ type HttpClientMock (result : HttpRequestMessage -> Async<HttpResponseMessage>)
|
||||
|
||||
[<RequireQualifiedAccess>]
|
||||
module HttpClientMock =
|
||||
let make (baseUrl : System.Uri) (handler : HttpRequestMessage -> Async<HttpResponseMessage>) =
|
||||
let makeNoUri (handler : HttpRequestMessage -> Async<HttpResponseMessage>) =
|
||||
let result = new HttpClientMock (handler)
|
||||
result
|
||||
|
||||
let make (baseUrl : System.Uri) (handler : HttpRequestMessage -> Async<HttpResponseMessage>) =
|
||||
let result = makeNoUri handler
|
||||
result.BaseAddress <- baseUrl
|
||||
result
|
@@ -1,4 +1,4 @@
|
||||
namespace MyriadPlugin.Test
|
||||
namespace WoofWare.Myriad.Plugins.Test
|
||||
|
||||
open PureGym
|
||||
open System
|
@@ -0,0 +1,47 @@
|
||||
namespace WoofWare.Myriad.Plugins.Test
|
||||
|
||||
open System.Threading
|
||||
open NUnit.Framework
|
||||
open FsUnitTyped
|
||||
open ConsumePlugin
|
||||
open FsCheck
|
||||
|
||||
[<TestFixture>]
|
||||
module TestCataGenerator =
|
||||
let idCata<'a, 'b> : TreeCata<'a, 'b, _, _> =
|
||||
{
|
||||
Tree =
|
||||
{ new TreeCataCase<_, _, _, _> with
|
||||
member _.Const x y = Const (x, y)
|
||||
member _.Pair x y z = Pair (x, y, z)
|
||||
member _.Sequential xs = Sequential xs
|
||||
member _.Builder x b = Builder (x, b)
|
||||
}
|
||||
TreeBuilder =
|
||||
{ new TreeBuilderCataCase<_, _, _, _> with
|
||||
member _.Child x = Child x
|
||||
member _.Parent x = Parent x
|
||||
}
|
||||
}
|
||||
|
||||
[<Test>]
|
||||
let ``Example`` () =
|
||||
let x =
|
||||
Tree.Pair (Tree.Const (Const.Verbatim 0, "hi"), Tree.Const (Const.String "", "bye"), PairOpKind.ThenDoSeq)
|
||||
|
||||
TreeCata.runTree idCata x |> shouldEqual x
|
||||
|
||||
|
||||
[<Test>]
|
||||
let ``Cata works`` () =
|
||||
let builderCases = ref 0
|
||||
|
||||
let property (x : Tree<int, string>) =
|
||||
match x with
|
||||
| Tree.Builder _ -> Interlocked.Increment builderCases |> ignore
|
||||
| _ -> ()
|
||||
|
||||
TreeCata.runTree idCata x = x
|
||||
|
||||
Check.QuickThrowOnFailure property
|
||||
builderCases.Value |> shouldBeGreaterThan 10
|
@@ -0,0 +1,37 @@
|
||||
namespace WoofWare.Myriad.Plugins.Test
|
||||
|
||||
open NUnit.Framework
|
||||
open ConsumePlugin
|
||||
open FsCheck
|
||||
|
||||
[<TestFixture>]
|
||||
module TestDirectory =
|
||||
let idCata : FileSystemCata<_> =
|
||||
{
|
||||
FileSystemItem =
|
||||
{ new FileSystemItemCataCase<_> with
|
||||
member _.File file = FileSystemItem.File file
|
||||
|
||||
member _.Directory name dirSize results =
|
||||
FileSystemItem.Directory
|
||||
{
|
||||
Name = name
|
||||
DirSize = dirSize
|
||||
Contents = results
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Note: this file is preserved as an example of writing an identity cata.
|
||||
// Don't add anything else to this file, because that will muddy the example.
|
||||
|
||||
[<Test>]
|
||||
let ``Cata works`` () =
|
||||
let property (x : FileSystemItem) =
|
||||
FileSystemItemCata.runFileSystemItem idCata x = x
|
||||
|
||||
Check.QuickThrowOnFailure property
|
||||
|
||||
// Note: this file is preserved as an example of writing an identity cata.
|
||||
// Don't add anything else to this file, because that will muddy the example.
|
99
WoofWare.Myriad.Plugins.Test/TestCataGenerator/TestGift.fs
Normal file
99
WoofWare.Myriad.Plugins.Test/TestCataGenerator/TestGift.fs
Normal file
@@ -0,0 +1,99 @@
|
||||
namespace WoofWare.Myriad.Plugins.Test
|
||||
|
||||
open NUnit.Framework
|
||||
open ConsumePlugin
|
||||
open FsCheck
|
||||
open FsUnitTyped
|
||||
|
||||
[<TestFixture>]
|
||||
module TestGift =
|
||||
|
||||
let idCata : GiftCata<_> =
|
||||
{
|
||||
Gift =
|
||||
{ new GiftCataCase<_> with
|
||||
member _.Book b = Gift.Book b
|
||||
member _.Boxed g = Gift.Boxed g
|
||||
member _.Chocolate g = Gift.Chocolate g
|
||||
member _.WithACard g message = Gift.WithACard (g, message)
|
||||
member _.Wrapped g paper = Gift.Wrapped (g, paper)
|
||||
}
|
||||
}
|
||||
|
||||
let totalCostCata : GiftCata<_> =
|
||||
{
|
||||
Gift =
|
||||
{ new GiftCataCase<_> with
|
||||
member _.Book b = b.price
|
||||
member _.Boxed g = g + 1.0m
|
||||
member _.Chocolate c = c.price
|
||||
member _.WithACard g message = g + 2.0m
|
||||
member _.Wrapped g paper = g + 0.5m
|
||||
}
|
||||
}
|
||||
|
||||
let descriptionCata : GiftCata<_> =
|
||||
{
|
||||
Gift =
|
||||
{ new GiftCataCase<_> with
|
||||
member _.Book b = b.title
|
||||
member _.Boxed g = $"%s{g} in a box"
|
||||
member _.Chocolate c = $"%O{c} chocolate"
|
||||
|
||||
member _.WithACard g message =
|
||||
$"%s{g} with a card saying '%s{message}'"
|
||||
|
||||
member _.Wrapped g paper = $"%s{g} wrapped in %A{paper} paper"
|
||||
}
|
||||
}
|
||||
|
||||
[<Test>]
|
||||
let ``Cata works`` () =
|
||||
let property (x : Gift) = GiftCata.runGift idCata x = x
|
||||
|
||||
Check.QuickThrowOnFailure property
|
||||
|
||||
[<Test>]
|
||||
let ``Example from docs`` () =
|
||||
let wolfHall =
|
||||
{
|
||||
title = "Wolf Hall"
|
||||
price = 20m
|
||||
}
|
||||
|
||||
let yummyChoc =
|
||||
{
|
||||
chocType = SeventyPercent
|
||||
price = 5m
|
||||
}
|
||||
|
||||
let birthdayPresent =
|
||||
WithACard (Wrapped (Book wolfHall, HappyBirthday), "Happy Birthday")
|
||||
|
||||
let christmasPresent = Wrapped (Boxed (Chocolate yummyChoc), HappyHolidays)
|
||||
|
||||
GiftCata.runGift totalCostCata birthdayPresent |> shouldEqual 22.5m
|
||||
|
||||
GiftCata.runGift descriptionCata christmasPresent
|
||||
|> shouldEqual "SeventyPercent chocolate in a box wrapped in HappyHolidays paper"
|
||||
|
||||
let deeplyNestedBox depth =
|
||||
let rec loop depth boxSoFar =
|
||||
match depth with
|
||||
| 0 -> boxSoFar
|
||||
| n -> loop (n - 1) (Boxed boxSoFar)
|
||||
|
||||
loop depth (Book wolfHall)
|
||||
|
||||
deeplyNestedBox 10 |> GiftCata.runGift totalCostCata |> shouldEqual 30.0M
|
||||
deeplyNestedBox 100 |> GiftCata.runGift totalCostCata |> shouldEqual 120.0M
|
||||
deeplyNestedBox 1000 |> GiftCata.runGift totalCostCata |> shouldEqual 1020.0M
|
||||
deeplyNestedBox 10000 |> GiftCata.runGift totalCostCata |> shouldEqual 10020.0M
|
||||
|
||||
deeplyNestedBox 100000
|
||||
|> GiftCata.runGift totalCostCata
|
||||
|> shouldEqual 100020.0M
|
||||
|
||||
deeplyNestedBox 1000000
|
||||
|> GiftCata.runGift totalCostCata
|
||||
|> shouldEqual 1000020.0M
|
77
WoofWare.Myriad.Plugins.Test/TestCataGenerator/TestMyList.fs
Normal file
77
WoofWare.Myriad.Plugins.Test/TestCataGenerator/TestMyList.fs
Normal file
@@ -0,0 +1,77 @@
|
||||
namespace WoofWare.Myriad.Plugins.Test
|
||||
|
||||
open NUnit.Framework
|
||||
open FsCheck
|
||||
open FsUnitTyped
|
||||
open ConsumePlugin
|
||||
|
||||
[<TestFixture>]
|
||||
module TestMyList =
|
||||
|
||||
let idCata<'a> : MyListCata<'a, _> =
|
||||
{
|
||||
MyList =
|
||||
{ new MyListCataCase<'a, _> with
|
||||
member _.Nil = MyList.Nil
|
||||
|
||||
member _.Cons head tail =
|
||||
MyList.Cons
|
||||
{
|
||||
Head = head
|
||||
Tail = tail
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[<Test>]
|
||||
let ``Cata works`` () =
|
||||
let property (x : MyList<int>) = MyListCata.runMyList idCata x = x
|
||||
|
||||
Check.QuickThrowOnFailure property
|
||||
|
||||
let toListCata<'a> =
|
||||
{
|
||||
MyList =
|
||||
{ new MyListCataCase<'a, 'a list> with
|
||||
member _.Nil = []
|
||||
member _.Cons (head : 'a) (tail : 'a list) = head :: tail
|
||||
}
|
||||
}
|
||||
|
||||
let toListViaCata<'a> (l : MyList<'a>) : 'a list = MyListCata.runMyList toListCata l
|
||||
|
||||
|
||||
[<Test>]
|
||||
let ``Example of a fold converting to a new data structure`` () =
|
||||
let rec toListNaive (l : MyList<int>) : int list =
|
||||
match l with
|
||||
| MyList.Nil -> []
|
||||
| MyList.Cons consCell -> consCell.Head :: toListNaive consCell.Tail
|
||||
|
||||
Check.QuickThrowOnFailure (fun l -> toListNaive l = toListViaCata l)
|
||||
|
||||
[<Test>]
|
||||
let ``Example of equivalence with FoldBack`` () =
|
||||
let baseCase = 0L
|
||||
let atLeaf (head : int) (tail : int64) : int64 = int64 head + tail
|
||||
|
||||
let sumCata =
|
||||
{
|
||||
MyList =
|
||||
{ new MyListCataCase<int, int64> with
|
||||
member _.Nil = baseCase
|
||||
member _.Cons (head : int) (tail : int64) = atLeaf head tail
|
||||
}
|
||||
}
|
||||
|
||||
let viaCata (l : MyList<int>) : int64 = MyListCata.runMyList sumCata l
|
||||
|
||||
let viaFold (l : MyList<int>) : int64 =
|
||||
// choose your favourite "to list" method - here I use the cata
|
||||
// but that could have been done naively
|
||||
(toListViaCata l, baseCase)
|
||||
||> List.foldBack (fun elt state -> atLeaf elt state)
|
||||
|
||||
let property (l : MyList<int>) = viaCata l = viaFold l
|
||||
|
||||
Check.QuickThrowOnFailure property
|
@@ -0,0 +1,25 @@
|
||||
namespace WoofWare.Myriad.Plugins.Test
|
||||
|
||||
open NUnit.Framework
|
||||
open FsCheck
|
||||
open FsUnitTyped
|
||||
open ConsumePlugin
|
||||
|
||||
[<TestFixture>]
|
||||
module TestMyList2 =
|
||||
|
||||
let idCata<'a> : MyList2Cata<'a, _> =
|
||||
{
|
||||
MyList2 =
|
||||
{ new MyList2CataCase<'a, _> with
|
||||
member _.Nil = MyList2.Nil
|
||||
|
||||
member _.Cons (head : 'a) (tail : MyList2<'a>) = MyList2.Cons (head, tail)
|
||||
}
|
||||
}
|
||||
|
||||
[<Test>]
|
||||
let ``Cata works`` () =
|
||||
let property (x : MyList2<int>) = MyList2Cata.runMyList2 idCata x = x
|
||||
|
||||
Check.QuickThrowOnFailure property
|
@@ -1,4 +1,4 @@
|
||||
namespace MyriadPlugin.Test
|
||||
namespace WoofWare.Myriad.Plugins.Test
|
||||
|
||||
open System
|
||||
open System.Net
|
80
WoofWare.Myriad.Plugins.Test/TestHttpClient/TestBasePath.fs
Normal file
80
WoofWare.Myriad.Plugins.Test/TestHttpClient/TestBasePath.fs
Normal file
@@ -0,0 +1,80 @@
|
||||
namespace WoofWare.Myriad.Plugins.Test
|
||||
|
||||
open System
|
||||
open System.Net
|
||||
open System.Net.Http
|
||||
open NUnit.Framework
|
||||
open PureGym
|
||||
open FsUnitTyped
|
||||
|
||||
[<TestFixture>]
|
||||
module TestBasePath =
|
||||
[<Test>]
|
||||
let ``Base address is respected`` () =
|
||||
let proc (message : HttpRequestMessage) : HttpResponseMessage Async =
|
||||
async {
|
||||
message.Method |> shouldEqual HttpMethod.Get
|
||||
let content = new StringContent (message.RequestUri.ToString ())
|
||||
let resp = new HttpResponseMessage (HttpStatusCode.OK)
|
||||
resp.Content <- content
|
||||
return resp
|
||||
}
|
||||
|
||||
use client = HttpClientMock.makeNoUri proc
|
||||
let api = PureGymApi.make client
|
||||
|
||||
let observedUri = api.GetPathParam("param").Result
|
||||
observedUri |> shouldEqual "https://whatnot.com/endpoint/param"
|
||||
|
||||
[<Test>]
|
||||
let ``Without a base address attr but with BaseAddress on client, request goes through`` () =
|
||||
let proc (message : HttpRequestMessage) : HttpResponseMessage Async =
|
||||
async {
|
||||
message.Method |> shouldEqual HttpMethod.Get
|
||||
let content = new StringContent (message.RequestUri.ToString ())
|
||||
let resp = new HttpResponseMessage (HttpStatusCode.OK)
|
||||
resp.Content <- content
|
||||
return resp
|
||||
}
|
||||
|
||||
use client = HttpClientMock.make (System.Uri "https://baseaddress.com") proc
|
||||
let api = ApiWithoutBaseAddress.make client
|
||||
|
||||
let observedUri = api.GetPathParam("param").Result
|
||||
observedUri |> shouldEqual "https://baseaddress.com/endpoint/param"
|
||||
|
||||
[<Test>]
|
||||
let ``Without a base address attr or BaseAddress on client, request throws`` () =
|
||||
let proc (message : HttpRequestMessage) : HttpResponseMessage Async =
|
||||
async {
|
||||
message.Method |> shouldEqual HttpMethod.Get
|
||||
let content = new StringContent (message.RequestUri.ToString ())
|
||||
let resp = new HttpResponseMessage (HttpStatusCode.OK)
|
||||
resp.Content <- content
|
||||
return resp
|
||||
}
|
||||
|
||||
use client = HttpClientMock.makeNoUri proc
|
||||
let api = ApiWithoutBaseAddress.make client
|
||||
|
||||
let observedExc =
|
||||
async {
|
||||
let! result = api.GetPathParam ("param") |> Async.AwaitTask |> Async.Catch
|
||||
|
||||
match result with
|
||||
| Choice1Of2 _ -> return failwith "test failure"
|
||||
| Choice2Of2 exc -> return exc
|
||||
}
|
||||
|> Async.RunSynchronously
|
||||
|
||||
let observedExc =
|
||||
match observedExc with
|
||||
| :? AggregateException as exc ->
|
||||
match exc.InnerException with
|
||||
| :? ArgumentNullException as exc -> exc
|
||||
| _ -> failwith "test failure"
|
||||
| _ -> failwith "test failure"
|
||||
|
||||
observedExc.Message
|
||||
|> shouldEqual
|
||||
"No base address was supplied on the type, and no BaseAddress was on the HttpClient. (Parameter 'BaseAddress')"
|
188
WoofWare.Myriad.Plugins.Test/TestHttpClient/TestBodyParam.fs
Normal file
188
WoofWare.Myriad.Plugins.Test/TestHttpClient/TestBodyParam.fs
Normal file
@@ -0,0 +1,188 @@
|
||||
namespace WoofWare.Myriad.Plugins.Test
|
||||
|
||||
open System
|
||||
open System.IO
|
||||
open System.Net
|
||||
open System.Net.Http
|
||||
open NUnit.Framework
|
||||
open PureGym
|
||||
open FsUnitTyped
|
||||
|
||||
[<TestFixture>]
|
||||
module TestBodyParam =
|
||||
|
||||
[<Test>]
|
||||
let ``Body param of string`` () =
|
||||
let proc (message : HttpRequestMessage) : HttpResponseMessage Async =
|
||||
async {
|
||||
message.Method |> shouldEqual HttpMethod.Post
|
||||
let! content = message.Content.ReadAsStringAsync () |> Async.AwaitTask
|
||||
let content = new StringContent (content)
|
||||
let resp = new HttpResponseMessage (HttpStatusCode.OK)
|
||||
resp.Content <- content
|
||||
return resp
|
||||
}
|
||||
|
||||
use client = HttpClientMock.make (Uri "https://example.com") proc
|
||||
let api = PureGymApi.make client
|
||||
|
||||
let observedUri = api.CreateUserString("username?not!url%encoded").Result
|
||||
observedUri |> shouldEqual "username?not!url%encoded"
|
||||
|
||||
[<Test>]
|
||||
let ``Body param of stream`` () =
|
||||
let proc (message : HttpRequestMessage) : HttpResponseMessage Async =
|
||||
async {
|
||||
message.Method |> shouldEqual HttpMethod.Post
|
||||
let! content = message.Content.ReadAsStreamAsync () |> Async.AwaitTask
|
||||
let content = new StreamContent (content)
|
||||
let resp = new HttpResponseMessage (HttpStatusCode.OK)
|
||||
resp.Content <- content
|
||||
return resp
|
||||
}
|
||||
|
||||
let contents = [| 1uy ; 2uy ; 3uy ; 4uy |]
|
||||
|
||||
use client = HttpClientMock.make (Uri "https://example.com") proc
|
||||
let api = PureGymApi.make client
|
||||
|
||||
use stream = new MemoryStream (contents)
|
||||
let observedContent = api.CreateUserStream(stream).Result
|
||||
let buf = Array.zeroCreate 10
|
||||
let written = observedContent.ReadAtLeast (buf.AsSpan (), 5, false)
|
||||
buf |> Array.take written |> shouldEqual contents
|
||||
|
||||
[<Test>]
|
||||
let ``Body param of HttpContent`` () =
|
||||
let mutable observedContent = None
|
||||
|
||||
let proc (message : HttpRequestMessage) : HttpResponseMessage Async =
|
||||
async {
|
||||
message.Method |> shouldEqual HttpMethod.Post
|
||||
let resp = new HttpResponseMessage (HttpStatusCode.OK)
|
||||
observedContent <- Some message.Content
|
||||
resp.Content <- new StringContent ("oh hi")
|
||||
return resp
|
||||
}
|
||||
|
||||
use client = HttpClientMock.make (Uri "https://example.com") proc
|
||||
let api = PureGymApi.make client
|
||||
|
||||
use content = new StringContent ("hello!")
|
||||
|
||||
api.CreateUserHttpContent(content).Result |> shouldEqual "oh hi"
|
||||
Object.ReferenceEquals (Option.get observedContent, content) |> shouldEqual true
|
||||
|
||||
[<TestCase "ByteArr">]
|
||||
[<TestCase "ByteArr'">]
|
||||
[<TestCase "ByteArr''">]
|
||||
let ``Body param of byte arr`` (case : string) =
|
||||
let proc (message : HttpRequestMessage) : HttpResponseMessage Async =
|
||||
async {
|
||||
message.Method |> shouldEqual HttpMethod.Post
|
||||
let! content = message.Content.ReadAsStreamAsync () |> Async.AwaitTask
|
||||
let content = new StreamContent (content)
|
||||
let resp = new HttpResponseMessage (HttpStatusCode.OK)
|
||||
resp.Content <- content
|
||||
return resp
|
||||
}
|
||||
|
||||
use client = HttpClientMock.make (Uri "https://example.com") proc
|
||||
let api = PureGymApi.make client
|
||||
|
||||
let contents = [| 1uy ; 2uy ; 3uy ; 4uy |]
|
||||
|
||||
let observedContent =
|
||||
match case with
|
||||
| "ByteArr" -> api.CreateUserByteArr(contents).Result
|
||||
| "ByteArr'" -> api.CreateUserByteArr'(contents).Result
|
||||
| "ByteArr''" -> api.CreateUserByteArr''(contents).Result
|
||||
| _ -> failwith $"Unrecognised case: %s{case}"
|
||||
|
||||
let buf = Array.zeroCreate 10
|
||||
let written = observedContent.ReadAtLeast (buf.AsSpan (), 5, false)
|
||||
buf |> Array.take written |> shouldEqual contents
|
||||
|
||||
[<Test>]
|
||||
let ``Body param of serialised thing`` () =
|
||||
let proc (message : HttpRequestMessage) : HttpResponseMessage Async =
|
||||
async {
|
||||
message.Method |> shouldEqual HttpMethod.Post
|
||||
let! content = message.Content.ReadAsStringAsync () |> Async.AwaitTask
|
||||
let content = new StringContent ("Done! " + content)
|
||||
let resp = new HttpResponseMessage (HttpStatusCode.OK)
|
||||
resp.Content <- content
|
||||
return resp
|
||||
}
|
||||
|
||||
use client = HttpClientMock.make (Uri "https://example.com") proc
|
||||
let api = PureGymApi.make client
|
||||
|
||||
let expected =
|
||||
{
|
||||
Id = 3
|
||||
CompoundMemberId = "compound!"
|
||||
FirstName = "Patrick"
|
||||
LastName = "Stevens"
|
||||
HomeGymId = 100
|
||||
HomeGymName = "Big Boy Gym"
|
||||
EmailAddress = "woof@ware"
|
||||
GymAccessPin = "l3tm31n"
|
||||
// To the reader: what's the significance of this date?
|
||||
// answer rot13: ghevatpbzchgnovyvglragfpurvqhatfceboyrzcncre
|
||||
DateOfBirth = DateOnly (1936, 05, 28)
|
||||
MobileNumber = "+44-GHOST-BUSTERS"
|
||||
Postcode = "W1A 111"
|
||||
MembershipName = "mario"
|
||||
MembershipLevel = 4
|
||||
SuspendedReason = 1090
|
||||
MemberStatus = -3
|
||||
}
|
||||
|
||||
let result = api.CreateUserSerialisedBody(expected).Result
|
||||
|
||||
result.StartsWith ("Done! ", StringComparison.Ordinal) |> shouldEqual true
|
||||
let result = result.[6..]
|
||||
|
||||
result
|
||||
|> System.Text.Json.Nodes.JsonNode.Parse
|
||||
|> PureGym.Member.jsonParse
|
||||
|> shouldEqual expected
|
||||
|
||||
[<Test>]
|
||||
let ``Body param of primitive: int`` () =
|
||||
let proc (message : HttpRequestMessage) : HttpResponseMessage Async =
|
||||
async {
|
||||
message.Method |> shouldEqual HttpMethod.Post
|
||||
let! content = message.Content.ReadAsStringAsync () |> Async.AwaitTask
|
||||
let content = new StringContent ("Done! " + content)
|
||||
let resp = new HttpResponseMessage (HttpStatusCode.OK)
|
||||
resp.Content <- content
|
||||
return resp
|
||||
}
|
||||
|
||||
use client = HttpClientMock.make (Uri "https://example.com") proc
|
||||
let api = PureGymApi.make client
|
||||
|
||||
let result = api.CreateUserSerialisedIntBody(3).Result
|
||||
|
||||
result |> shouldEqual "Done! 3"
|
||||
|
||||
[<Test>]
|
||||
let ``Body param of primitive: Uri`` () =
|
||||
let proc (message : HttpRequestMessage) : HttpResponseMessage Async =
|
||||
async {
|
||||
message.Method |> shouldEqual HttpMethod.Post
|
||||
let! content = message.Content.ReadAsStringAsync () |> Async.AwaitTask
|
||||
let content = new StringContent ("Done! " + content)
|
||||
let resp = new HttpResponseMessage (HttpStatusCode.OK)
|
||||
resp.Content <- content
|
||||
return resp
|
||||
}
|
||||
|
||||
use client = HttpClientMock.make (Uri "https://example.com") proc
|
||||
let api = PureGymApi.make client
|
||||
|
||||
let result = api.CreateUserSerialisedUrlBody(Uri "https://mything.com/blah").Result
|
||||
|
||||
result |> shouldEqual "Done! \"https://mything.com/blah\""
|
@@ -1,4 +1,4 @@
|
||||
namespace MyriadPlugin.Test
|
||||
namespace WoofWare.Myriad.Plugins.Test
|
||||
|
||||
open System
|
||||
open System.Net
|
@@ -1,4 +1,4 @@
|
||||
namespace MyriadPlugin.Test
|
||||
namespace WoofWare.Myriad.Plugins.Test
|
||||
|
||||
open System
|
||||
open System.Net
|
||||
@@ -209,10 +209,7 @@ module TestPureGymRestApi =
|
||||
|
||||
[<TestCaseSource(nameof sessionsCases)>]
|
||||
let ``Test GetSessions``
|
||||
(
|
||||
baseUri : Uri,
|
||||
(startDate : DateOnly, (endDate : DateOnly, (json : string, expected : Sessions)))
|
||||
)
|
||||
(baseUri : Uri, (startDate : DateOnly, (endDate : DateOnly, (json : string, expected : Sessions))))
|
||||
=
|
||||
let proc (message : HttpRequestMessage) : HttpResponseMessage Async =
|
||||
async {
|
||||
@@ -236,3 +233,61 @@ module TestPureGymRestApi =
|
||||
let api = PureGymApi.make client
|
||||
|
||||
api.GetSessions(startDate, endDate).Result |> shouldEqual expected
|
||||
|
||||
[<Test>]
|
||||
let ``URI example`` () =
|
||||
let proc (message : HttpRequestMessage) : HttpResponseMessage Async =
|
||||
async {
|
||||
message.Method |> shouldEqual HttpMethod.Get
|
||||
|
||||
message.RequestUri.ToString () |> shouldEqual "https://whatnot.com/some/url"
|
||||
|
||||
let content =
|
||||
new StringContent ("""{"someUri": "https://patrick@en.wikipedia.org/wiki/foo"}""")
|
||||
|
||||
let resp = new HttpResponseMessage (HttpStatusCode.OK)
|
||||
resp.Content <- content
|
||||
return resp
|
||||
}
|
||||
|
||||
use client = HttpClientMock.makeNoUri proc
|
||||
let api = PureGymApi.make client
|
||||
|
||||
let uri = api.GetUrl().Result.SomeUri
|
||||
uri.ToString () |> shouldEqual "https://patrick@en.wikipedia.org/wiki/foo"
|
||||
uri.UserInfo |> shouldEqual "patrick"
|
||||
uri.Host |> shouldEqual "en.wikipedia.org"
|
||||
|
||||
[<TestCase false>]
|
||||
[<TestCase true>]
|
||||
let ``Map<string, string> option example`` (isSome : bool) =
|
||||
let proc (message : HttpRequestMessage) : HttpResponseMessage Async =
|
||||
async {
|
||||
message.Method |> shouldEqual HttpMethod.Post
|
||||
|
||||
message.RequestUri.ToString () |> shouldEqual "https://whatnot.com/some/url"
|
||||
let! content = message.Content.ReadAsStringAsync () |> Async.AwaitTask
|
||||
|
||||
if isSome then
|
||||
content |> shouldEqual """{"hi":"bye"}"""
|
||||
else
|
||||
content |> shouldEqual "null"
|
||||
|
||||
let content = new StringContent (content)
|
||||
|
||||
let resp = new HttpResponseMessage (HttpStatusCode.OK)
|
||||
resp.Content <- content
|
||||
return resp
|
||||
}
|
||||
|
||||
use client = HttpClientMock.makeNoUri proc
|
||||
let api = PureGymApi.make client
|
||||
|
||||
let expected =
|
||||
if isSome then
|
||||
[ "hi", "bye" ] |> Map.ofList |> Some
|
||||
else
|
||||
None
|
||||
|
||||
let actual = api.PostStringToString(expected).Result
|
||||
actual |> shouldEqual expected
|
@@ -1,4 +1,4 @@
|
||||
namespace MyriadPlugin.Test
|
||||
namespace WoofWare.Myriad.Plugins.Test
|
||||
|
||||
open System
|
||||
open System.IO
|
||||
@@ -54,8 +54,8 @@ module TestReturnTypes =
|
||||
| _ -> failwith $"unrecognised case: %s{case}"
|
||||
|
||||
let buf = Array.zeroCreate 10
|
||||
stream.Read (buf, 0, 10) |> shouldEqual 4
|
||||
Array.take 4 buf |> shouldEqual result
|
||||
let written = stream.ReadAtLeast (buf.AsSpan (), 10, false)
|
||||
Array.take written buf |> shouldEqual result
|
||||
|
||||
[<TestCase "GetResponseMessage">]
|
||||
[<TestCase "GetResponseMessage'">]
|
||||
@@ -86,3 +86,36 @@ module TestReturnTypes =
|
||||
| _ -> failwith $"unrecognised case: %s{case}"
|
||||
|
||||
Object.ReferenceEquals (message, Option.get responseMessage) |> shouldEqual true
|
||||
|
||||
[<TestCase "Task<Response>">]
|
||||
[<TestCase "Task<RestEase.Response>">]
|
||||
[<TestCase "RestEase.Response Task">]
|
||||
[<TestCase "RestEase.Response Task">]
|
||||
let ``Response return`` (case : string) =
|
||||
for json, memberDto in PureGymDtos.memberActivityDtoCases do
|
||||
let mutable responseMessage = None
|
||||
|
||||
let proc (message : HttpRequestMessage) : HttpResponseMessage Async =
|
||||
async {
|
||||
message.Method |> shouldEqual HttpMethod.Get
|
||||
let content = new StringContent (json)
|
||||
let resp = new HttpResponseMessage (HttpStatusCode.OK)
|
||||
resp.Content <- content
|
||||
responseMessage <- Some resp
|
||||
return resp
|
||||
}
|
||||
|
||||
use client = HttpClientMock.make (Uri "https://example.com") proc
|
||||
let api = PureGymApi.make client
|
||||
|
||||
let response =
|
||||
match case with
|
||||
| "Task<Response>" -> api.GetResponse().Result
|
||||
| "Task<RestEase.Response>" -> api.GetResponse'().Result
|
||||
| "Response Task" -> api.GetResponse''().Result
|
||||
| "RestEase.Response Task" -> api.GetResponse'''().Result
|
||||
| _ -> failwith $"unrecognised case: %s{case}"
|
||||
|
||||
response.ResponseMessage |> shouldEqual (Option.get responseMessage)
|
||||
response.StringContent |> shouldEqual json
|
||||
response.GetContent () |> shouldEqual memberDto
|
@@ -0,0 +1,108 @@
|
||||
namespace WoofWare.Myriad.Plugins.Test
|
||||
|
||||
open System
|
||||
open System.Net
|
||||
open System.Net.Http
|
||||
open System.Threading
|
||||
open NUnit.Framework
|
||||
open FsUnitTyped
|
||||
open PureGym
|
||||
|
||||
[<TestFixture>]
|
||||
module TestVariableHeader =
|
||||
|
||||
[<Test>]
|
||||
let ``Headers are set`` () : unit =
|
||||
let proc (message : HttpRequestMessage) : HttpResponseMessage Async =
|
||||
async {
|
||||
message.Method |> shouldEqual HttpMethod.Get
|
||||
|
||||
message.RequestUri.ToString ()
|
||||
|> shouldEqual "https://example.com/endpoint/param"
|
||||
|
||||
let headers =
|
||||
[
|
||||
for h in message.Headers do
|
||||
yield $"%s{h.Key}: %s{Seq.exactlyOne h.Value}"
|
||||
]
|
||||
|> String.concat "\n"
|
||||
|
||||
let content = new StringContent (headers)
|
||||
let resp = new HttpResponseMessage (HttpStatusCode.OK)
|
||||
resp.Content <- content
|
||||
return resp
|
||||
}
|
||||
|
||||
use client = HttpClientMock.make (Uri "https://example.com") proc
|
||||
|
||||
let someHeaderCount = ref 10
|
||||
|
||||
let someHeader () =
|
||||
(Interlocked.Increment someHeaderCount : int).ToString ()
|
||||
|
||||
let someOtherHeaderCount = ref -100
|
||||
|
||||
let someOtherHeader () =
|
||||
Interlocked.Increment someOtherHeaderCount
|
||||
|
||||
let api = ApiWithHeaders.make someHeader someOtherHeader client
|
||||
|
||||
someHeaderCount.Value |> shouldEqual 10
|
||||
someOtherHeaderCount.Value |> shouldEqual -100
|
||||
|
||||
api.GetPathParam("param").Result.Split "\n"
|
||||
|> Array.sort
|
||||
|> shouldEqual [| "Authorization: -99" ; "Header-Name: Header-Value" ; "X-Foo: 11" |]
|
||||
|
||||
someHeaderCount.Value |> shouldEqual 11
|
||||
someOtherHeaderCount.Value |> shouldEqual -99
|
||||
|
||||
[<Test>]
|
||||
let ``Headers get re-evaluated every time`` () : unit =
|
||||
let proc (message : HttpRequestMessage) : HttpResponseMessage Async =
|
||||
async {
|
||||
message.Method |> shouldEqual HttpMethod.Get
|
||||
|
||||
message.RequestUri.ToString ()
|
||||
|> shouldEqual "https://example.com/endpoint/param"
|
||||
|
||||
let headers =
|
||||
[
|
||||
for h in message.Headers do
|
||||
yield $"%s{h.Key}: %s{Seq.exactlyOne h.Value}"
|
||||
]
|
||||
|> String.concat "\n"
|
||||
|
||||
let content = new StringContent (headers)
|
||||
let resp = new HttpResponseMessage (HttpStatusCode.OK)
|
||||
resp.Content <- content
|
||||
return resp
|
||||
}
|
||||
|
||||
use client = HttpClientMock.make (Uri "https://example.com") proc
|
||||
|
||||
let someHeaderCount = ref 10
|
||||
|
||||
let someHeader () =
|
||||
(Interlocked.Increment someHeaderCount : int).ToString ()
|
||||
|
||||
let someOtherHeaderCount = ref -100
|
||||
|
||||
let someOtherHeader () =
|
||||
Interlocked.Increment someOtherHeaderCount
|
||||
|
||||
let api = ApiWithHeaders.make someHeader someOtherHeader client
|
||||
|
||||
someHeaderCount.Value |> shouldEqual 10
|
||||
someOtherHeaderCount.Value |> shouldEqual -100
|
||||
|
||||
api.GetPathParam("param").Result.Split "\n"
|
||||
|> Array.sort
|
||||
|> shouldEqual [| "Authorization: -99" ; "Header-Name: Header-Value" ; "X-Foo: 11" |]
|
||||
|
||||
api.GetPathParam("param").Result.Split "\n"
|
||||
|> Array.sort
|
||||
|> shouldEqual [| "Authorization: -98" ; "Header-Name: Header-Value" ; "X-Foo: 12" |]
|
||||
|
||||
someHeaderCount.Value |> shouldEqual 12
|
||||
someOtherHeaderCount.Value |> shouldEqual -98
|
170
WoofWare.Myriad.Plugins.Test/TestHttpClient/TestVaultClient.fs
Normal file
170
WoofWare.Myriad.Plugins.Test/TestHttpClient/TestVaultClient.fs
Normal file
@@ -0,0 +1,170 @@
|
||||
namespace WoofWare.Myriad.Plugins.Test
|
||||
|
||||
open System
|
||||
open System.Net
|
||||
open System.Net.Http
|
||||
open NUnit.Framework
|
||||
open FsUnitTyped
|
||||
open ConsumePlugin
|
||||
|
||||
[<TestFixture>]
|
||||
module TestVaultClient =
|
||||
|
||||
let exampleVaultKeyResponseString =
|
||||
"""{
|
||||
"request_id": "e2470000-0000-0000-0000-000000001f47",
|
||||
"lease_id": "",
|
||||
"renewable": false,
|
||||
"lease_duration": 0,
|
||||
"data": {
|
||||
"key1_1": "value1_1",
|
||||
"key1_2": "value1_2"
|
||||
},
|
||||
"data2": {
|
||||
"key2_1": "value2_1",
|
||||
"key2_2": "value2_2"
|
||||
},
|
||||
"data3": {
|
||||
"key3_1": "value3_1",
|
||||
"key3_2": "value3_2"
|
||||
},
|
||||
"data4": {
|
||||
"key4_1": "value4_1",
|
||||
"key4_2": "value4_2"
|
||||
},
|
||||
"data5": {
|
||||
"https://example.com/data5/1": "value5_1",
|
||||
"https://example.com/data5/2": "value5_2"
|
||||
},
|
||||
"data6": {
|
||||
"https://example.com/data6/1": "value6_1",
|
||||
"https://example.com/data6/2": "value6_2"
|
||||
},
|
||||
"data7": {
|
||||
"key7_1": 71,
|
||||
"key7_2": 72
|
||||
},
|
||||
"data8": {
|
||||
"key8_1": "https://example.com/data8/1",
|
||||
"key8_2": "https://example.com/data8/2"
|
||||
}
|
||||
}"""
|
||||
|
||||
let exampleVaultJwtResponseString =
|
||||
"""{
|
||||
"request_id": "80000000-0000-0000-0000-00000000000d",
|
||||
"lease_id": "",
|
||||
"renewable": false,
|
||||
"lease_duration": 0,
|
||||
"data": null,
|
||||
"wrap_info": null,
|
||||
"warnings": null,
|
||||
"auth": {
|
||||
"client_token": "redacted_client_token",
|
||||
"accessor": "redacted_accessor",
|
||||
"policies": [
|
||||
"policy1",
|
||||
"default"
|
||||
],
|
||||
"identity_policies": [
|
||||
"identity-policy",
|
||||
"default-2"
|
||||
],
|
||||
"token_policies": [
|
||||
"token-policy",
|
||||
"default-3"
|
||||
],
|
||||
"metadata": {
|
||||
"role": "some-role"
|
||||
},
|
||||
"lease_duration": 43200,
|
||||
"renewable": true,
|
||||
"entity_id": "20000000-0000-0000-0000-000000000007",
|
||||
"token_type": "service",
|
||||
"orphan": true,
|
||||
"mfa_requirement": null,
|
||||
"num_uses": 0
|
||||
}
|
||||
}"""
|
||||
|
||||
[<Test>]
|
||||
let ``URI example`` () =
|
||||
let proc (message : HttpRequestMessage) : HttpResponseMessage Async =
|
||||
async {
|
||||
message.Method |> shouldEqual HttpMethod.Get
|
||||
|
||||
let requestUri = message.RequestUri.ToString ()
|
||||
|
||||
match requestUri with
|
||||
| "https://my-vault.com/v1/auth/jwt/login" ->
|
||||
let content = new StringContent (exampleVaultJwtResponseString)
|
||||
|
||||
let resp = new HttpResponseMessage (HttpStatusCode.OK)
|
||||
resp.Content <- content
|
||||
return resp
|
||||
| "https://my-vault.com/v1/mount/path" ->
|
||||
let content = new StringContent (exampleVaultKeyResponseString)
|
||||
|
||||
let resp = new HttpResponseMessage (HttpStatusCode.OK)
|
||||
resp.Content <- content
|
||||
return resp
|
||||
| _ -> return failwith $"bad URI: %s{requestUri}"
|
||||
}
|
||||
|
||||
use client = HttpClientMock.make (Uri "https://my-vault.com") proc
|
||||
let api = VaultClient.make client
|
||||
|
||||
let vaultResponse = api.GetJwt("role", "jwt").Result
|
||||
let value = api.GetSecret(vaultResponse, "path", "mount").Result
|
||||
|
||||
value.Data
|
||||
|> Seq.toList
|
||||
|> List.map (fun (KeyValue (k, v)) -> k, v)
|
||||
|> shouldEqual [ "key1_1", "value1_1" ; "key1_2", "value1_2" ]
|
||||
|
||||
value.Data2
|
||||
|> Seq.toList
|
||||
|> List.map (fun (KeyValue (k, v)) -> k, v)
|
||||
|> shouldEqual [ "key2_1", "value2_1" ; "key2_2", "value2_2" ]
|
||||
|
||||
value.Data3
|
||||
|> Seq.toList
|
||||
|> List.map (fun (KeyValue (k, v)) -> k, v)
|
||||
|> shouldEqual [ "key3_1", "value3_1" ; "key3_2", "value3_2" ]
|
||||
|
||||
value.Data4
|
||||
|> Seq.toList
|
||||
|> List.map (fun (KeyValue (k, v)) -> k, v)
|
||||
|> shouldEqual [ "key4_1", "value4_1" ; "key4_2", "value4_2" ]
|
||||
|
||||
value.Data5
|
||||
|> Seq.toList
|
||||
|> List.map (fun (KeyValue (k, v)) -> (k : Uri).ToString (), v)
|
||||
|> shouldEqual
|
||||
[
|
||||
"https://example.com/data5/1", "value5_1"
|
||||
"https://example.com/data5/2", "value5_2"
|
||||
]
|
||||
|
||||
value.Data6
|
||||
|> Seq.toList
|
||||
|> List.map (fun (KeyValue (k, v)) -> (k : Uri).ToString (), v)
|
||||
|> shouldEqual
|
||||
[
|
||||
"https://example.com/data6/1", "value6_1"
|
||||
"https://example.com/data6/2", "value6_2"
|
||||
]
|
||||
|
||||
value.Data7
|
||||
|> Seq.toList
|
||||
|> List.map (fun (KeyValue (k, v)) -> k, v)
|
||||
|> shouldEqual [ "key7_1", 71 ; "key7_2", 72 ]
|
||||
|
||||
value.Data8
|
||||
|> Seq.toList
|
||||
|> List.map (fun (KeyValue (k, v)) -> k, (v : Uri).ToString ())
|
||||
|> shouldEqual
|
||||
[
|
||||
"key8_1", "https://example.com/data8/1"
|
||||
"key8_2", "https://example.com/data8/2"
|
||||
]
|
@@ -0,0 +1,26 @@
|
||||
namespace WoofWare.Myriad.Plugins.Test
|
||||
|
||||
open System
|
||||
open System.Text.Json.Nodes
|
||||
open ConsumePlugin
|
||||
open NUnit.Framework
|
||||
open FsUnitTyped
|
||||
|
||||
[<TestFixture>]
|
||||
module TestExtensionMethod =
|
||||
|
||||
[<Test>]
|
||||
let ``Parse via extension method`` () =
|
||||
let json =
|
||||
"""{"tinker": "job", "tailor": 3, "soldier": "https://example.com", "sailor": 3.1}"""
|
||||
|> JsonNode.Parse
|
||||
|
||||
let expected =
|
||||
{
|
||||
Tinker = "job"
|
||||
Tailor = 3
|
||||
Soldier = Uri "https://example.com"
|
||||
Sailor = 3.1
|
||||
}
|
||||
|
||||
ToGetExtensionMethod.jsonParse json |> shouldEqual expected
|
@@ -1,4 +1,4 @@
|
||||
namespace MyriadPlugin.Test
|
||||
namespace WoofWare.Myriad.Plugins.Test
|
||||
|
||||
open System.Text.Json.Nodes
|
||||
open ConsumePlugin
|
||||
@@ -32,3 +32,18 @@ module TestJsonParse =
|
||||
|
||||
let actual = s |> JsonNode.Parse |> JsonRecordType.jsonParse
|
||||
actual |> shouldEqual expected
|
||||
|
||||
[<Test>]
|
||||
let ``Inner example`` () =
|
||||
let s =
|
||||
"""{
|
||||
"something": "oh hi"
|
||||
}"""
|
||||
|
||||
let expected =
|
||||
{
|
||||
Thing = "oh hi"
|
||||
}
|
||||
|
||||
let actual = s |> JsonNode.Parse |> InnerType.jsonParse
|
||||
actual |> shouldEqual expected
|
@@ -1,4 +1,4 @@
|
||||
namespace MyriadPlugin.Test
|
||||
namespace WoofWare.Myriad.Plugins.Test
|
||||
|
||||
open System
|
||||
open System.Text.Json.Nodes
|
126
WoofWare.Myriad.Plugins.Test/TestJsonSerialize/TestJsonSerde.fs
Normal file
126
WoofWare.Myriad.Plugins.Test/TestJsonSerialize/TestJsonSerde.fs
Normal file
@@ -0,0 +1,126 @@
|
||||
namespace WoofWare.Myriad.Plugins.Test
|
||||
|
||||
open System
|
||||
open System.Collections.Generic
|
||||
open System.IO
|
||||
open System.Text
|
||||
open System.Text.Json
|
||||
open System.Text.Json.Nodes
|
||||
open NUnit.Framework
|
||||
open FsCheck
|
||||
open FsUnitTyped
|
||||
open ConsumePlugin
|
||||
|
||||
[<TestFixture>]
|
||||
module TestJsonSerde =
|
||||
|
||||
let uriGen : Gen<Uri> =
|
||||
gen {
|
||||
let! suffix = Arb.generate<int>
|
||||
return Uri $"https://example.com/%i{suffix}"
|
||||
}
|
||||
|
||||
let rec innerGen (count : int) : Gen<InnerTypeWithBoth> =
|
||||
gen {
|
||||
let! guid = Arb.generate<Guid>
|
||||
let! mapKeys = Gen.listOf Arb.generate<NonNull<string>>
|
||||
let mapKeys = mapKeys |> List.map _.Get |> List.distinct
|
||||
let! mapValues = Gen.listOfLength mapKeys.Length uriGen
|
||||
let map = List.zip mapKeys mapValues |> Map.ofList
|
||||
|
||||
let! concreteDictKeys =
|
||||
if count > 0 then
|
||||
Gen.listOf Arb.generate<NonNull<string>>
|
||||
else
|
||||
Gen.constant []
|
||||
|
||||
let concreteDictKeys =
|
||||
concreteDictKeys
|
||||
|> List.map _.Get
|
||||
|> List.distinct
|
||||
|> fun x -> List.take (min 3 x.Length) x
|
||||
|
||||
let! concreteDictValues =
|
||||
if count > 0 then
|
||||
Gen.listOfLength concreteDictKeys.Length (innerGen (count - 1))
|
||||
else
|
||||
Gen.constant []
|
||||
|
||||
let concreteDict =
|
||||
List.zip concreteDictKeys concreteDictValues
|
||||
|> List.map KeyValuePair
|
||||
|> Dictionary
|
||||
|
||||
let! readOnlyDictKeys = Gen.listOf Arb.generate<NonNull<string>>
|
||||
let readOnlyDictKeys = readOnlyDictKeys |> List.map _.Get |> List.distinct
|
||||
let! readOnlyDictValues = Gen.listOfLength readOnlyDictKeys.Length (Gen.listOf Arb.generate<char>)
|
||||
let readOnlyDict = List.zip readOnlyDictKeys readOnlyDictValues |> readOnlyDict
|
||||
|
||||
let! dictKeys = Gen.listOf uriGen
|
||||
let! dictValues = Gen.listOfLength dictKeys.Length Arb.generate<bool>
|
||||
let dict = List.zip dictKeys dictValues |> dict
|
||||
|
||||
return
|
||||
{
|
||||
Thing = guid
|
||||
Map = map
|
||||
ReadOnlyDict = readOnlyDict
|
||||
Dict = dict
|
||||
ConcreteDict = concreteDict
|
||||
}
|
||||
}
|
||||
|
||||
let outerGen : Gen<JsonRecordTypeWithBoth> =
|
||||
gen {
|
||||
let! a = Arb.generate<int>
|
||||
let! b = Arb.generate<NonNull<string>>
|
||||
let! c = Gen.listOf Arb.generate<int>
|
||||
let! depth = Gen.choose (0, 2)
|
||||
let! d = innerGen depth
|
||||
let! e = Gen.arrayOf Arb.generate<NonNull<string>>
|
||||
let! f = Gen.arrayOf Arb.generate<int>
|
||||
|
||||
return
|
||||
{
|
||||
A = a
|
||||
B = b.Get
|
||||
C = c
|
||||
D = d
|
||||
E = e |> Array.map _.Get
|
||||
F = f
|
||||
}
|
||||
}
|
||||
|
||||
[<Test>]
|
||||
let ``It just works`` () =
|
||||
let property (o : JsonRecordTypeWithBoth) : bool =
|
||||
o
|
||||
|> JsonRecordTypeWithBoth.toJsonNode
|
||||
|> fun s -> s.ToJsonString ()
|
||||
|> JsonNode.Parse
|
||||
|> JsonRecordTypeWithBoth.jsonParse
|
||||
|> shouldEqual o
|
||||
|
||||
true
|
||||
|
||||
property |> Prop.forAll (Arb.fromGen outerGen) |> Check.QuickThrowOnFailure
|
||||
|
||||
[<Test>]
|
||||
let ``Guids are treated just like strings`` () =
|
||||
let guidStr = "b1e7496e-6e79-4158-8579-a01de355d3b2"
|
||||
let guid = Guid.Parse guidStr
|
||||
|
||||
let node =
|
||||
{
|
||||
Thing = guid
|
||||
Map = Map.empty
|
||||
ReadOnlyDict = readOnlyDict []
|
||||
Dict = dict []
|
||||
ConcreteDict = Dictionary ()
|
||||
}
|
||||
|> InnerTypeWithBoth.toJsonNode
|
||||
|
||||
node.ToJsonString ()
|
||||
|> shouldEqual (
|
||||
sprintf """{"it\u0027s-a-me":"%s","map":{},"readOnlyDict":{},"dict":{},"concreteDict":{}}""" guidStr
|
||||
)
|
@@ -0,0 +1,36 @@
|
||||
namespace WoofWare.Myriad.Plugins.Test
|
||||
|
||||
open System
|
||||
open SomeNamespace
|
||||
open NUnit.Framework
|
||||
open FsUnitTyped
|
||||
|
||||
[<TestFixture>]
|
||||
module TestMockGenerator =
|
||||
|
||||
[<Test>]
|
||||
let ``Example of use: IPublicType`` () =
|
||||
let mock : IPublicType =
|
||||
{ PublicTypeMock.Empty with
|
||||
Mem1 = fun (s, count) -> List.replicate count s
|
||||
}
|
||||
:> _
|
||||
|
||||
let _ =
|
||||
Assert.Throws<NotImplementedException> (fun () -> mock.Mem2 "hi" |> ignore<int>)
|
||||
|
||||
mock.Mem1 ("hi", 3) |> shouldEqual [ "hi" ; "hi" ; "hi" ]
|
||||
|
||||
[<Test>]
|
||||
let ``Example of use: curried args`` () =
|
||||
let mock : Curried<_> =
|
||||
{ CurriedMock.Empty () with
|
||||
Mem1 = fun i c -> Array.replicate i c |> String
|
||||
Mem2 = fun (i, s) c -> String.concat $"%c{c}" (List.replicate i s)
|
||||
Mem3 = fun (i, s) c -> String.concat $"%c{c}" (List.replicate i s)
|
||||
}
|
||||
:> _
|
||||
|
||||
mock.Mem1 3 'a' |> shouldEqual "aaa"
|
||||
mock.Mem2 (3, "hi") 'a' |> shouldEqual "hiahiahi"
|
||||
mock.Mem3 (3, "hi") 'a' |> shouldEqual "hiahiahi"
|
@@ -1,4 +1,4 @@
|
||||
namespace MyriadPlugin.Test
|
||||
namespace WoofWare.Myriad.Plugins.Test
|
||||
|
||||
open FsCheck
|
||||
open ConsumePlugin
|
24
WoofWare.Myriad.Plugins.Test/TestSurface.fs
Normal file
24
WoofWare.Myriad.Plugins.Test/TestSurface.fs
Normal file
@@ -0,0 +1,24 @@
|
||||
namespace WoofWare.Myriad.Plugins.Test
|
||||
|
||||
open NUnit.Framework
|
||||
open WoofWare.Myriad.Plugins
|
||||
open ApiSurface
|
||||
|
||||
[<TestFixture>]
|
||||
module TestSurface =
|
||||
let assembly = typeof<RemoveOptionsGenerator>.Assembly
|
||||
|
||||
[<Test>]
|
||||
let ``Ensure API surface has not been modified`` () = ApiSurface.assertIdentical assembly
|
||||
|
||||
[<Test>]
|
||||
let ``Check version against remote`` () =
|
||||
MonotonicVersion.validate assembly "WoofWare.Myriad.Plugins"
|
||||
|
||||
[<Test ; Explicit>]
|
||||
let ``Update API surface`` () =
|
||||
ApiSurface.writeAssemblyBaseline assembly
|
||||
|
||||
[<Test>]
|
||||
let ``Ensure public API is fully documented`` () =
|
||||
DocCoverage.assertFullyDocumented assembly
|
@@ -0,0 +1,49 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<IsPackable>false</IsPackable>
|
||||
<IsTestProject>true</IsTestProject>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Include="HttpClient.fs"/>
|
||||
<Compile Include="PureGymDtos.fs"/>
|
||||
<Compile Include="TestJsonParse\TestJsonParse.fs" />
|
||||
<Compile Include="TestJsonParse\TestPureGymJson.fs" />
|
||||
<Compile Include="TestJsonParse\TestExtensionMethod.fs" />
|
||||
<Compile Include="TestHttpClient\TestPureGymRestApi.fs" />
|
||||
<Compile Include="TestHttpClient\TestPathParam.fs" />
|
||||
<Compile Include="TestHttpClient\TestReturnTypes.fs" />
|
||||
<Compile Include="TestHttpClient\TestAllowAnyStatusCode.fs" />
|
||||
<Compile Include="TestHttpClient\TestBasePath.fs" />
|
||||
<Compile Include="TestHttpClient\TestBodyParam.fs" />
|
||||
<Compile Include="TestHttpClient\TestVaultClient.fs" />
|
||||
<Compile Include="TestHttpClient\TestVariableHeader.fs" />
|
||||
<Compile Include="TestMockGenerator\TestMockGenerator.fs" />
|
||||
<Compile Include="TestJsonSerialize\TestJsonSerde.fs" />
|
||||
<Compile Include="TestCataGenerator\TestCataGenerator.fs" />
|
||||
<Compile Include="TestCataGenerator\TestDirectory.fs" />
|
||||
<Compile Include="TestCataGenerator\TestGift.fs" />
|
||||
<Compile Include="TestCataGenerator\TestMyList.fs" />
|
||||
<Compile Include="TestCataGenerator\TestMyList2.fs" />
|
||||
<Compile Include="TestRemoveOptions.fs"/>
|
||||
<Compile Include="TestSurface.fs"/>
|
||||
<None Include="../.github/workflows/dotnet.yaml" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="ApiSurface" Version="4.0.33"/>
|
||||
<PackageReference Include="FsCheck" Version="2.16.6"/>
|
||||
<PackageReference Include="FsUnit" Version="6.0.0"/>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.9.0"/>
|
||||
<PackageReference Include="NUnit" Version="4.1.0"/>
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="4.5.0"/>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\WoofWare.Myriad.Plugins\WoofWare.Myriad.Plugins.fsproj"/>
|
||||
<ProjectReference Include="..\ConsumePlugin\ConsumePlugin.fsproj"/>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
@@ -6,26 +6,115 @@ open Fantomas.FCS.Text.Range
|
||||
open Fantomas.FCS.Xml
|
||||
open Myriad.Core.AstExtensions
|
||||
|
||||
type internal ParameterInfo =
|
||||
{
|
||||
Attributes : SynAttribute list
|
||||
IsOptional : bool
|
||||
Id : Ident option
|
||||
Type : SynType
|
||||
}
|
||||
|
||||
type internal TupledArg =
|
||||
{
|
||||
HasParen : bool
|
||||
Args : ParameterInfo list
|
||||
}
|
||||
|
||||
type internal MemberInfo =
|
||||
{
|
||||
ReturnType : SynType
|
||||
Accessibility : SynAccess option
|
||||
/// Each element of this list is a list of args in a tuple, or just one arg if not a tuple.
|
||||
Args : TupledArg list
|
||||
Identifier : Ident
|
||||
Attributes : SynAttribute list
|
||||
XmlDoc : PreXmlDoc option
|
||||
IsInline : bool
|
||||
IsMutable : bool
|
||||
}
|
||||
|
||||
[<RequireQualifiedAccess>]
|
||||
type internal PropertyAccessors =
|
||||
| Get
|
||||
| Set
|
||||
| GetSet
|
||||
|
||||
type internal PropertyInfo =
|
||||
{
|
||||
Type : SynType
|
||||
Accessibility : SynAccess option
|
||||
Attributes : SynAttribute list
|
||||
XmlDoc : PreXmlDoc option
|
||||
Accessors : PropertyAccessors
|
||||
IsInline : bool
|
||||
Identifier : Ident
|
||||
}
|
||||
|
||||
type internal InterfaceType =
|
||||
{
|
||||
Attributes : SynAttribute list
|
||||
Name : LongIdent
|
||||
Members : MemberInfo list
|
||||
Properties : PropertyInfo list
|
||||
Generics : SynTyparDecls option
|
||||
Accessibility : SynAccess option
|
||||
}
|
||||
|
||||
type internal RecordType =
|
||||
{
|
||||
Name : Ident
|
||||
Fields : SynField seq
|
||||
Members : SynMemberDefns option
|
||||
XmlDoc : PreXmlDoc option
|
||||
Generics : SynTyparDecls option
|
||||
Accessibility : SynAccess option
|
||||
}
|
||||
|
||||
/// Anything that is part of an ADT.
|
||||
/// A record is a product of stuff; this type represents one of those stuffs.
|
||||
type internal AdtNode =
|
||||
{
|
||||
Type : SynType
|
||||
Name : Ident option
|
||||
/// An ordered list, so you can look up any given generic within `this.Type`
|
||||
/// to discover what its index is in the parent DU which defined it.
|
||||
GenericsOfParent : SynTyparDecl list
|
||||
}
|
||||
|
||||
/// A DU is a sum of products (e.g. `type Thing = Foo of a * b`);
|
||||
/// similarly a record is a product.
|
||||
/// This type represents a product in that sense.
|
||||
type internal AdtProduct =
|
||||
{
|
||||
Name : SynIdent
|
||||
Fields : AdtNode list
|
||||
/// This AdtProduct represents a product in which there might be
|
||||
/// some bound type parameters. This field lists the bound
|
||||
/// type parameters in the order they appeared on the parent type.
|
||||
Generics : SynTyparDecl list
|
||||
}
|
||||
|
||||
[<RequireQualifiedAccess>]
|
||||
module internal AstHelper =
|
||||
|
||||
let constructRecord (fields : (RecordFieldName * SynExpr option) list) : SynExpr =
|
||||
let instantiateRecord (fields : (RecordFieldName * SynExpr option) list) : SynExpr =
|
||||
let fields =
|
||||
fields
|
||||
|> List.map (fun (rfn, synExpr) -> SynExprRecordField (rfn, Some range0, synExpr, None))
|
||||
|
||||
SynExpr.Record (None, None, fields, range0)
|
||||
|
||||
let private createRecordType
|
||||
(
|
||||
name : Ident,
|
||||
repr : SynTypeDefnRepr,
|
||||
members : SynMemberDefns,
|
||||
xmldoc : PreXmlDoc
|
||||
)
|
||||
: SynTypeDefn
|
||||
=
|
||||
let name = SynComponentInfo.Create ([ name ], xmldoc = xmldoc)
|
||||
let defineRecordType (record : RecordType) : SynTypeDefn =
|
||||
let repr =
|
||||
SynTypeDefnRepr.Simple (SynTypeDefnSimpleRepr.Record (None, Seq.toList record.Fields, range0), range0)
|
||||
|
||||
let name =
|
||||
SynComponentInfo.Create (
|
||||
[ record.Name ],
|
||||
?xmldoc = record.XmlDoc,
|
||||
?parameters = record.Generics,
|
||||
access = record.Accessibility
|
||||
)
|
||||
|
||||
let trivia : SynTypeDefnTrivia =
|
||||
{
|
||||
@@ -34,21 +123,7 @@ module internal AstHelper =
|
||||
WithKeyword = Some range0
|
||||
}
|
||||
|
||||
SynTypeDefn (name, repr, members, None, range0, trivia)
|
||||
|
||||
let defineRecordType
|
||||
(
|
||||
name : Ident,
|
||||
fields : SynField seq,
|
||||
members : SynMemberDefns option,
|
||||
xmldoc : PreXmlDoc option
|
||||
)
|
||||
: SynTypeDefn
|
||||
=
|
||||
let repr =
|
||||
SynTypeDefnRepr.Simple (SynTypeDefnSimpleRepr.Record (None, Seq.toList fields, range0), range0)
|
||||
|
||||
createRecordType (name, repr, defaultArg members SynMemberDefns.Empty, defaultArg xmldoc PreXmlDoc.Empty)
|
||||
SynTypeDefn (name, repr, defaultArg record.Members SynMemberDefns.Empty, None, range0, trivia)
|
||||
|
||||
let isOptionIdent (ident : SynLongIdent) : bool =
|
||||
match ident.LongIdent with
|
||||
@@ -69,12 +144,340 @@ module internal AstHelper =
|
||||
|| System.String.Equals (i.idText, "[]", System.StringComparison.Ordinal)
|
||||
->
|
||||
true
|
||||
// TODO: consider FSharpList or whatever it is
|
||||
| [ i ] ->
|
||||
printfn $"Not array: %s{i.idText}"
|
||||
false
|
||||
| _ -> false
|
||||
|
||||
let isResponseIdent (ident : SynLongIdent) : bool =
|
||||
match ident.LongIdent |> List.map _.idText with
|
||||
| [ "Response" ]
|
||||
| [ "RestEase" ; "Response" ] -> true
|
||||
| _ -> false
|
||||
|
||||
let isMapIdent (ident : SynLongIdent) : bool =
|
||||
match ident.LongIdent |> List.map _.idText with
|
||||
| [ "Map" ] -> true
|
||||
| _ -> false
|
||||
|
||||
let isReadOnlyDictionaryIdent (ident : SynLongIdent) : bool =
|
||||
match ident.LongIdent |> List.map _.idText with
|
||||
| [ "IReadOnlyDictionary" ]
|
||||
| [ "Generic" ; "IReadOnlyDictionary" ]
|
||||
| [ "Collections" ; "Generic" ; "IReadOnlyDictionary" ]
|
||||
| [ "System" ; "Collections" ; "Generic" ; "IReadOnlyDictionary" ] -> true
|
||||
| _ -> false
|
||||
|
||||
let isDictionaryIdent (ident : SynLongIdent) : bool =
|
||||
match ident.LongIdent |> List.map _.idText with
|
||||
| [ "Dictionary" ]
|
||||
| [ "Generic" ; "Dictionary" ]
|
||||
| [ "Collections" ; "Generic" ; "Dictionary" ]
|
||||
| [ "System" ; "Collections" ; "Generic" ; "Dictionary" ] -> true
|
||||
| _ -> false
|
||||
|
||||
let isIDictionaryIdent (ident : SynLongIdent) : bool =
|
||||
match ident.LongIdent |> List.map _.idText with
|
||||
| [ "IDictionary" ]
|
||||
| [ "Generic" ; "IDictionary" ]
|
||||
| [ "Collections" ; "Generic" ; "IDictionary" ]
|
||||
| [ "System" ; "Collections" ; "Generic" ; "IDictionary" ] -> true
|
||||
| _ -> false
|
||||
|
||||
let rec private extractOpensFromDecl (moduleDecls : SynModuleDecl list) : SynOpenDeclTarget list =
|
||||
moduleDecls
|
||||
|> List.choose (fun moduleDecl ->
|
||||
match moduleDecl with
|
||||
| SynModuleDecl.Open (target, _) -> Some target
|
||||
| _ -> None
|
||||
)
|
||||
|
||||
let extractOpens (ast : ParsedInput) : SynOpenDeclTarget list =
|
||||
match ast with
|
||||
| ParsedInput.ImplFile (ParsedImplFileInput (_, _, _, _, _, modules, _, _, _)) ->
|
||||
modules
|
||||
|> List.collect (fun (SynModuleOrNamespace (_, _, _, decls, _, _, _, _, _)) -> extractOpensFromDecl decls)
|
||||
| _ -> []
|
||||
|
||||
let rec convertSigParam (ty : SynType) : ParameterInfo * bool =
|
||||
match ty with
|
||||
| SynType.Paren (inner, _) ->
|
||||
let result, _ = convertSigParam inner
|
||||
result, true
|
||||
| SynType.LongIdent ident ->
|
||||
{
|
||||
Attributes = []
|
||||
IsOptional = false
|
||||
Id = None
|
||||
Type = SynType.CreateLongIdent ident
|
||||
},
|
||||
false
|
||||
| SynType.SignatureParameter (attrs, opt, id, usedType, _) ->
|
||||
let attrs = attrs |> List.collect (fun attrs -> attrs.Attributes)
|
||||
|
||||
{
|
||||
Attributes = attrs
|
||||
IsOptional = opt
|
||||
Id = id
|
||||
Type = usedType
|
||||
},
|
||||
false
|
||||
| SynType.Var (typar, _) ->
|
||||
{
|
||||
Attributes = []
|
||||
IsOptional = false
|
||||
Id = None
|
||||
Type = SynType.Var (typar, range0)
|
||||
},
|
||||
false
|
||||
| _ -> failwithf "expected SignatureParameter, got: %+A" ty
|
||||
|
||||
let rec extractTupledTypes (tupleType : SynTupleTypeSegment list) : TupledArg =
|
||||
match tupleType with
|
||||
| [] ->
|
||||
{
|
||||
HasParen = false
|
||||
Args = []
|
||||
}
|
||||
| [ SynTupleTypeSegment.Type param ] ->
|
||||
let converted, hasParen = convertSigParam param
|
||||
|
||||
{
|
||||
HasParen = hasParen
|
||||
Args = [ converted ]
|
||||
}
|
||||
| SynTupleTypeSegment.Type param :: SynTupleTypeSegment.Star _ :: rest ->
|
||||
let rest = extractTupledTypes rest
|
||||
let converted, _ = convertSigParam param
|
||||
|
||||
{
|
||||
HasParen = false
|
||||
Args = converted :: rest.Args
|
||||
}
|
||||
| _ -> failwithf "Didn't have alternating type-and-star in interface member definition: %+A" tupleType
|
||||
|
||||
let toFun (inputs : SynType list) (ret : SynType) : SynType =
|
||||
(ret, List.rev inputs)
|
||||
||> List.fold (fun ty input -> SynType.CreateFun (input, ty))
|
||||
|
||||
/// Returns the args (where these are tuple types if curried) in order, and the return type.
|
||||
let rec getType (ty : SynType) : (SynType * bool) list * SynType =
|
||||
match ty with
|
||||
| SynType.Paren (ty, _) -> getType ty
|
||||
| SynType.Fun (argType, returnType, _, _) ->
|
||||
let args, ret = getType returnType
|
||||
// TODO this code is clearly wrong
|
||||
let (inputArgs, inputRet), hasParen =
|
||||
match argType with
|
||||
| SynType.Paren (argType, _) -> getType argType, true
|
||||
| _ -> getType argType, false
|
||||
|
||||
((toFun (List.map fst inputArgs) inputRet), hasParen) :: args, ret
|
||||
| _ -> [], ty
|
||||
|
||||
let private parseMember (slotSig : SynValSig) (flags : SynMemberFlags) : Choice<MemberInfo, PropertyInfo> =
|
||||
if not flags.IsInstance then
|
||||
failwith "member was not an instance member"
|
||||
|
||||
let propertyAccessors =
|
||||
match flags.MemberKind with
|
||||
| SynMemberKind.Member -> None
|
||||
| SynMemberKind.PropertyGet -> Some PropertyAccessors.Get
|
||||
| SynMemberKind.PropertySet -> Some PropertyAccessors.Set
|
||||
| SynMemberKind.PropertyGetSet -> Some PropertyAccessors.GetSet
|
||||
| kind -> failwithf "Unrecognised member kind: %+A" kind
|
||||
|
||||
match slotSig with
|
||||
| SynValSig (attrs,
|
||||
SynIdent.SynIdent (ident, _),
|
||||
_typeParams,
|
||||
synType,
|
||||
_arity,
|
||||
isInline,
|
||||
isMutable,
|
||||
xmlDoc,
|
||||
accessibility,
|
||||
synExpr,
|
||||
_,
|
||||
_) ->
|
||||
|
||||
match synExpr with
|
||||
| Some _ -> failwith "literal members are not supported"
|
||||
| None -> ()
|
||||
|
||||
let attrs = attrs |> List.collect _.Attributes
|
||||
|
||||
let args, ret = getType synType
|
||||
|
||||
let args =
|
||||
args
|
||||
|> List.map (fun (args, hasParen) ->
|
||||
match args with
|
||||
| SynType.Tuple (false, path, _) -> extractTupledTypes path
|
||||
| SynType.SignatureParameter _ ->
|
||||
let arg, hasParen = convertSigParam args
|
||||
|
||||
{
|
||||
HasParen = hasParen
|
||||
Args = [ arg ]
|
||||
}
|
||||
| SynType.LongIdent (SynLongIdent (ident, _, _)) ->
|
||||
{
|
||||
HasParen = false
|
||||
Args =
|
||||
{
|
||||
Attributes = []
|
||||
IsOptional = false
|
||||
Id = None
|
||||
Type = SynType.CreateLongIdent (SynLongIdent.CreateFromLongIdent ident)
|
||||
}
|
||||
|> List.singleton
|
||||
}
|
||||
| SynType.Var (typar, _) ->
|
||||
{
|
||||
HasParen = false
|
||||
Args =
|
||||
{
|
||||
Attributes = []
|
||||
IsOptional = false
|
||||
Id = None
|
||||
Type = SynType.Var (typar, range0)
|
||||
}
|
||||
|> List.singleton
|
||||
}
|
||||
| _ -> failwith $"Unrecognised args in interface method declaration: %+A{args}"
|
||||
|> fun ty ->
|
||||
{ ty with
|
||||
HasParen = ty.HasParen || hasParen
|
||||
}
|
||||
)
|
||||
|
||||
match propertyAccessors with
|
||||
| None ->
|
||||
{
|
||||
ReturnType = ret
|
||||
Args = args
|
||||
Identifier = ident
|
||||
Attributes = attrs
|
||||
XmlDoc = Some xmlDoc
|
||||
Accessibility = accessibility
|
||||
IsInline = isInline
|
||||
IsMutable = isMutable
|
||||
}
|
||||
|> Choice1Of2
|
||||
| Some accessors ->
|
||||
{
|
||||
Type = ret
|
||||
Accessibility = accessibility
|
||||
Attributes = attrs
|
||||
XmlDoc = Some xmlDoc
|
||||
Accessors = accessors
|
||||
IsInline = isInline
|
||||
Identifier = ident
|
||||
}
|
||||
|> Choice2Of2
|
||||
|
||||
/// Assumes that the input type is an ObjectModel, i.e. a `type Foo = member ...`
|
||||
let parseInterface (interfaceType : SynTypeDefn) : InterfaceType =
|
||||
let (SynTypeDefn (SynComponentInfo (attrs, typars, _, interfaceName, _, _, accessibility, _),
|
||||
synTypeDefnRepr,
|
||||
_,
|
||||
_,
|
||||
_,
|
||||
_)) =
|
||||
interfaceType
|
||||
|
||||
let attrs = attrs |> List.collect (fun s -> s.Attributes)
|
||||
|
||||
let members, properties =
|
||||
match synTypeDefnRepr with
|
||||
| SynTypeDefnRepr.ObjectModel (_kind, members, _) ->
|
||||
members
|
||||
|> List.map (fun defn ->
|
||||
match defn with
|
||||
| SynMemberDefn.AbstractSlot (slotSig, flags, _, _) -> parseMember slotSig flags
|
||||
| _ -> failwith $"Unrecognised member definition: %+A{defn}"
|
||||
)
|
||||
| _ -> failwith $"Unrecognised SynTypeDefnRepr for an interface type: %+A{synTypeDefnRepr}"
|
||||
|> List.partitionChoice
|
||||
|
||||
{
|
||||
Members = members
|
||||
Properties = properties
|
||||
Name = interfaceName
|
||||
Attributes = attrs
|
||||
Generics = typars
|
||||
Accessibility = accessibility
|
||||
}
|
||||
|
||||
let getUnionCases
|
||||
(SynTypeDefn.SynTypeDefn (info, repr, _, _, _, _))
|
||||
: AdtProduct list * SynTyparDecl list * SynAccess option
|
||||
=
|
||||
let typars, access =
|
||||
match info with
|
||||
| SynComponentInfo (_, typars, _, _, _, _, access, _) -> typars, access
|
||||
|
||||
let typars =
|
||||
match typars with
|
||||
| None -> []
|
||||
| Some (SynTyparDecls.PrefixList (decls, _)) -> decls
|
||||
| Some (SynTyparDecls.SinglePrefix (l, _)) -> [ l ]
|
||||
| Some (SynTyparDecls.PostfixList (decls, constraints, _)) ->
|
||||
if not constraints.IsEmpty then
|
||||
failwith "Constrained type parameters not currently supported"
|
||||
|
||||
decls
|
||||
|
||||
match repr with
|
||||
| SynTypeDefnRepr.Simple (SynTypeDefnSimpleRepr.Union (_, cases, _), _) ->
|
||||
let cases =
|
||||
cases
|
||||
|> List.map (fun (SynUnionCase.SynUnionCase (_, ident, kind, _, _, _, _)) ->
|
||||
match kind with
|
||||
| SynUnionCaseKind.FullType _ -> failwith "FullType union cases not supported"
|
||||
| SynUnionCaseKind.Fields fields ->
|
||||
{
|
||||
Name = ident
|
||||
Fields =
|
||||
fields
|
||||
|> List.map (fun (SynField.SynField (_, _, id, ty, _, _, _, _, _)) ->
|
||||
{
|
||||
Type = ty
|
||||
Name = id
|
||||
GenericsOfParent = typars
|
||||
}
|
||||
)
|
||||
Generics = typars
|
||||
}
|
||||
)
|
||||
|
||||
cases, typars, access
|
||||
| _ -> failwithf "Failed to get union cases for type that was: %+A" repr
|
||||
|
||||
let getRecordFields (SynTypeDefn.SynTypeDefn (typeInfo, repr, _, _, _, _)) : AdtNode list =
|
||||
let (SynComponentInfo.SynComponentInfo (typeParams = typars)) = typeInfo
|
||||
|
||||
let typars =
|
||||
match typars with
|
||||
| None -> []
|
||||
| Some (SynTyparDecls.PrefixList (decls, _)) -> decls
|
||||
| Some (SynTyparDecls.SinglePrefix (l, _)) -> [ l ]
|
||||
| Some (SynTyparDecls.PostfixList (decls, constraints, _)) ->
|
||||
if not constraints.IsEmpty then
|
||||
failwith "Constrained type parameters not currently supported"
|
||||
|
||||
decls
|
||||
|
||||
match repr with
|
||||
| SynTypeDefnRepr.Simple (SynTypeDefnSimpleRepr.Record (_, fields, _), _) ->
|
||||
fields
|
||||
|> List.map (fun (SynField.SynField (_, _, ident, ty, _, _, _, _, _)) ->
|
||||
{
|
||||
Name = ident
|
||||
Type = ty
|
||||
GenericsOfParent = typars
|
||||
}
|
||||
)
|
||||
| _ -> failwithf "Failed to get record elements for type that was: %+A" repr
|
||||
|
||||
[<AutoOpen>]
|
||||
module internal SynTypePatterns =
|
||||
let (|OptionType|_|) (fieldType : SynType) =
|
||||
@@ -96,12 +499,46 @@ module internal SynTypePatterns =
|
||||
| SynType.Array (1, innerType, _) -> Some innerType
|
||||
| _ -> None
|
||||
|
||||
let (|RestEaseResponseType|_|) (fieldType : SynType) =
|
||||
match fieldType with
|
||||
| SynType.App (SynType.LongIdent ident, _, [ innerType ], _, _, _, _) when AstHelper.isResponseIdent ident ->
|
||||
Some innerType
|
||||
| _ -> None
|
||||
|
||||
let (|DictionaryType|_|) (fieldType : SynType) =
|
||||
match fieldType with
|
||||
| SynType.App (SynType.LongIdent ident, _, [ key ; value ], _, _, _, _) when AstHelper.isDictionaryIdent ident ->
|
||||
Some (key, value)
|
||||
| _ -> None
|
||||
|
||||
let (|IDictionaryType|_|) (fieldType : SynType) =
|
||||
match fieldType with
|
||||
| SynType.App (SynType.LongIdent ident, _, [ key ; value ], _, _, _, _) when AstHelper.isIDictionaryIdent ident ->
|
||||
Some (key, value)
|
||||
| _ -> None
|
||||
|
||||
let (|IReadOnlyDictionaryType|_|) (fieldType : SynType) =
|
||||
match fieldType with
|
||||
| SynType.App (SynType.LongIdent ident, _, [ key ; value ], _, _, _, _) when
|
||||
AstHelper.isReadOnlyDictionaryIdent ident
|
||||
->
|
||||
Some (key, value)
|
||||
| _ -> None
|
||||
|
||||
let (|MapType|_|) (fieldType : SynType) =
|
||||
match fieldType with
|
||||
| SynType.App (SynType.LongIdent ident, _, [ key ; value ], _, _, _, _) when AstHelper.isMapIdent ident ->
|
||||
Some (key, value)
|
||||
| _ -> None
|
||||
|
||||
/// Returns the string name of the type.
|
||||
let (|PrimitiveType|_|) (fieldType : SynType) =
|
||||
match fieldType with
|
||||
| SynType.LongIdent ident ->
|
||||
match ident.LongIdent with
|
||||
| [ i ] -> [ "string" ; "float" ; "int" ; "bool" ] |> List.tryFind (fun s -> s = i.idText)
|
||||
| [ i ] ->
|
||||
[ "string" ; "float" ; "int" ; "bool" ; "char" ]
|
||||
|> List.tryFind (fun s -> s = i.idText)
|
||||
| _ -> None
|
||||
| _ -> None
|
||||
|
||||
@@ -116,6 +553,23 @@ module internal SynTypePatterns =
|
||||
| _ -> None
|
||||
| _ -> None
|
||||
|
||||
let (|Byte|_|) (fieldType : SynType) : unit option =
|
||||
match fieldType with
|
||||
| SynType.LongIdent ident ->
|
||||
match ident.LongIdent with
|
||||
| [ i ] -> [ "byte" ] |> List.tryFind (fun s -> s = i.idText) |> Option.map ignore<string>
|
||||
| _ -> None
|
||||
| _ -> None
|
||||
|
||||
let (|Guid|_|) (fieldType : SynType) : unit option =
|
||||
match fieldType with
|
||||
| SynType.LongIdent ident ->
|
||||
match ident.LongIdent |> List.map (fun i -> i.idText) with
|
||||
| [ "System" ; "Guid" ]
|
||||
| [ "Guid" ] -> Some ()
|
||||
| _ -> None
|
||||
| _ -> None
|
||||
|
||||
let (|HttpResponseMessage|_|) (fieldType : SynType) : unit option =
|
||||
match fieldType with
|
||||
| SynType.LongIdent ident ->
|
||||
@@ -127,6 +581,17 @@ module internal SynTypePatterns =
|
||||
| _ -> None
|
||||
| _ -> None
|
||||
|
||||
let (|HttpContent|_|) (fieldType : SynType) : unit option =
|
||||
match fieldType with
|
||||
| SynType.LongIdent ident ->
|
||||
match ident.LongIdent |> List.map (fun i -> i.idText) with
|
||||
| [ "System" ; "Net" ; "Http" ; "HttpContent" ]
|
||||
| [ "Net" ; "Http" ; "HttpContent" ]
|
||||
| [ "Http" ; "HttpContent" ]
|
||||
| [ "HttpContent" ] -> Some ()
|
||||
| _ -> None
|
||||
| _ -> None
|
||||
|
||||
let (|Stream|_|) (fieldType : SynType) : unit option =
|
||||
match fieldType with
|
||||
| SynType.LongIdent ident ->
|
||||
@@ -163,6 +628,15 @@ module internal SynTypePatterns =
|
||||
| _ -> None
|
||||
| _ -> None
|
||||
|
||||
let (|Uri|_|) (fieldType : SynType) =
|
||||
match fieldType with
|
||||
| SynType.LongIdent (SynLongIdent.SynLongIdent (ident, _, _)) ->
|
||||
match ident |> List.map (fun i -> i.idText) with
|
||||
| [ "System" ; "Uri" ]
|
||||
| [ "Uri" ] -> Some ()
|
||||
| _ -> None
|
||||
| _ -> None
|
||||
|
||||
let (|Task|_|) (fieldType : SynType) : SynType option =
|
||||
match fieldType with
|
||||
| SynType.App (SynType.LongIdent (SynLongIdent.SynLongIdent (ident, _, _)), _, args, _, _, _, _) ->
|
||||
|
1724
WoofWare.Myriad.Plugins/CataGenerator.fs
Normal file
1724
WoofWare.Myriad.Plugins/CataGenerator.fs
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
14
WoofWare.Myriad.Plugins/Ident.fs
Normal file
14
WoofWare.Myriad.Plugins/Ident.fs
Normal file
@@ -0,0 +1,14 @@
|
||||
namespace WoofWare.Myriad.Plugins
|
||||
|
||||
open System
|
||||
open System.Text
|
||||
open Fantomas.FCS.Syntax
|
||||
open Myriad.Core
|
||||
|
||||
[<RequireQualifiedAccess>]
|
||||
module internal Ident =
|
||||
let lowerFirstLetter (x : Ident) : Ident =
|
||||
let result = StringBuilder x.idText.Length
|
||||
result.Append (Char.ToLowerInvariant x.idText.[0]) |> ignore
|
||||
result.Append x.idText.[1..] |> ignore
|
||||
Ident.Create ((result : StringBuilder).ToString ())
|
392
WoofWare.Myriad.Plugins/InterfaceMockGenerator.fs
Normal file
392
WoofWare.Myriad.Plugins/InterfaceMockGenerator.fs
Normal file
@@ -0,0 +1,392 @@
|
||||
namespace WoofWare.Myriad.Plugins
|
||||
|
||||
open System
|
||||
open Fantomas.FCS.Syntax
|
||||
open Fantomas.FCS.SyntaxTrivia
|
||||
open Fantomas.FCS.Xml
|
||||
open Myriad.Core
|
||||
|
||||
type internal GenerateMockOutputSpec =
|
||||
{
|
||||
IsInternal : bool
|
||||
}
|
||||
|
||||
[<RequireQualifiedAccess>]
|
||||
module internal InterfaceMockGenerator =
|
||||
open Fantomas.FCS.Text.Range
|
||||
open Myriad.Core.Ast
|
||||
|
||||
let private getName (SynField (_, _, id, _, _, _, _, _, _)) =
|
||||
match id with
|
||||
| None -> failwith "Expected record field to have a name, but it was somehow anonymous"
|
||||
| Some id -> id
|
||||
|
||||
let createType
|
||||
(spec : GenerateMockOutputSpec)
|
||||
(name : string)
|
||||
(interfaceType : InterfaceType)
|
||||
(xmlDoc : PreXmlDoc)
|
||||
(fields : SynField list)
|
||||
: SynModuleDecl
|
||||
=
|
||||
let synValData =
|
||||
{
|
||||
SynMemberFlags.IsInstance = false
|
||||
SynMemberFlags.IsDispatchSlot = false
|
||||
SynMemberFlags.IsOverrideOrExplicitImpl = false
|
||||
SynMemberFlags.IsFinal = false
|
||||
SynMemberFlags.GetterOrSetterIsCompilerGenerated = false
|
||||
SynMemberFlags.MemberKind = SynMemberKind.Member
|
||||
}
|
||||
|
||||
let failwithFun =
|
||||
SynExpr.createLambda
|
||||
"x"
|
||||
(SynExpr.CreateApp (
|
||||
SynExpr.CreateIdentString "raise",
|
||||
SynExpr.CreateParen (
|
||||
SynExpr.CreateApp (
|
||||
SynExpr.CreateLongIdent (SynLongIdent.Create [ "System" ; "NotImplementedException" ]),
|
||||
SynExpr.CreateConstString "Unimplemented mock function"
|
||||
)
|
||||
)
|
||||
))
|
||||
|
||||
let constructorIdent =
|
||||
let generics =
|
||||
interfaceType.Generics
|
||||
|> Option.map (fun generics -> SynValTyparDecls (Some generics, false))
|
||||
|
||||
SynPat.LongIdent (
|
||||
SynLongIdent.CreateString "Empty",
|
||||
None,
|
||||
None, // no generics on the "Empty", only on the return type
|
||||
SynArgPats.Pats (
|
||||
if generics.IsNone then
|
||||
[]
|
||||
else
|
||||
[ SynPat.CreateParen (SynPat.CreateConst SynConst.Unit) ]
|
||||
),
|
||||
None,
|
||||
range0
|
||||
)
|
||||
|
||||
let constructorReturnType =
|
||||
match interfaceType.Generics with
|
||||
| None -> SynType.CreateLongIdent name
|
||||
| Some generics ->
|
||||
let generics =
|
||||
generics.TyparDecls
|
||||
|> List.map (fun (SynTyparDecl (_, typar)) -> SynType.Var (typar, range0))
|
||||
|
||||
SynType.App (
|
||||
SynType.CreateLongIdent name,
|
||||
Some range0,
|
||||
generics,
|
||||
List.replicate (generics.Length - 1) range0,
|
||||
Some range0,
|
||||
false,
|
||||
range0
|
||||
)
|
||||
|> SynBindingReturnInfo.Create
|
||||
|
||||
let constructor =
|
||||
SynMemberDefn.Member (
|
||||
SynBinding.SynBinding (
|
||||
None,
|
||||
SynBindingKind.Normal,
|
||||
false,
|
||||
false,
|
||||
[],
|
||||
PreXmlDoc.Create " An implementation where every method throws.",
|
||||
SynValData.SynValData (Some synValData, SynValInfo.Empty, None),
|
||||
constructorIdent,
|
||||
Some constructorReturnType,
|
||||
AstHelper.instantiateRecord (
|
||||
fields
|
||||
|> List.map (fun field ->
|
||||
((SynLongIdent.CreateFromLongIdent [ getName field ], true), Some failwithFun)
|
||||
)
|
||||
),
|
||||
range0,
|
||||
DebugPointAtBinding.Yes range0,
|
||||
{ SynExpr.synBindingTriviaZero true with
|
||||
LeadingKeyword = SynLeadingKeyword.StaticMember (range0, range0)
|
||||
}
|
||||
),
|
||||
range0
|
||||
)
|
||||
|
||||
let interfaceMembers =
|
||||
let members =
|
||||
interfaceType.Members
|
||||
|> List.map (fun memberInfo ->
|
||||
|
||||
let synValData =
|
||||
SynValData.SynValData (
|
||||
Some
|
||||
{
|
||||
IsInstance = true
|
||||
IsDispatchSlot = false
|
||||
IsOverrideOrExplicitImpl = true
|
||||
IsFinal = false
|
||||
GetterOrSetterIsCompilerGenerated = false
|
||||
MemberKind = SynMemberKind.Member
|
||||
},
|
||||
valInfo =
|
||||
SynValInfo.SynValInfo (
|
||||
curriedArgInfos =
|
||||
[
|
||||
yield
|
||||
[
|
||||
SynArgInfo.SynArgInfo (
|
||||
attributes = [],
|
||||
optional = false,
|
||||
ident = None
|
||||
)
|
||||
]
|
||||
yield!
|
||||
memberInfo.Args
|
||||
|> List.mapi (fun i arg ->
|
||||
arg.Args
|
||||
|> List.mapi (fun j arg ->
|
||||
SynArgInfo.CreateIdString $"arg_%i{i}_%i{j}"
|
||||
)
|
||||
)
|
||||
],
|
||||
returnInfo =
|
||||
SynArgInfo.SynArgInfo (attributes = [], optional = false, ident = None)
|
||||
),
|
||||
thisIdOpt = None
|
||||
)
|
||||
|
||||
let headArgs =
|
||||
memberInfo.Args
|
||||
|> List.mapi (fun i tupledArgs ->
|
||||
let args =
|
||||
tupledArgs.Args
|
||||
|> List.mapi (fun j _ -> SynPat.CreateNamed (Ident.Create $"arg_%i{i}_%i{j}"))
|
||||
|
||||
SynPat.Tuple (false, args, List.replicate (args.Length - 1) range0, range0)
|
||||
|> SynPat.CreateParen
|
||||
|> fun i -> if tupledArgs.HasParen then SynPat.Paren (i, range0) else i
|
||||
)
|
||||
|
||||
let headPat =
|
||||
SynPat.LongIdent (
|
||||
SynLongIdent.CreateFromLongIdent [ Ident.Create "this" ; memberInfo.Identifier ],
|
||||
None,
|
||||
None,
|
||||
SynArgPats.Pats headArgs,
|
||||
None,
|
||||
range0
|
||||
)
|
||||
|
||||
let body =
|
||||
let tuples =
|
||||
memberInfo.Args
|
||||
|> List.mapi (fun i args ->
|
||||
args.Args
|
||||
|> List.mapi (fun j args -> SynExpr.CreateIdentString $"arg_%i{i}_%i{j}")
|
||||
|> SynExpr.CreateParenedTuple
|
||||
)
|
||||
|
||||
match tuples |> List.rev with
|
||||
| [] -> failwith "expected args but got none"
|
||||
| last :: rest ->
|
||||
|
||||
(last, rest)
|
||||
||> List.fold (fun trail next -> SynExpr.CreateApp (next, trail))
|
||||
|> fun args ->
|
||||
SynExpr.CreateApp (
|
||||
SynExpr.CreateLongIdent (
|
||||
SynLongIdent.CreateFromLongIdent [ Ident.Create "this" ; memberInfo.Identifier ]
|
||||
),
|
||||
args
|
||||
)
|
||||
|
||||
SynMemberDefn.Member (
|
||||
SynBinding.SynBinding (
|
||||
None,
|
||||
SynBindingKind.Normal,
|
||||
false,
|
||||
false,
|
||||
[],
|
||||
PreXmlDoc.Empty,
|
||||
synValData,
|
||||
headPat,
|
||||
None,
|
||||
body,
|
||||
range0,
|
||||
DebugPointAtBinding.Yes range0,
|
||||
{
|
||||
LeadingKeyword = SynLeadingKeyword.Member range0
|
||||
InlineKeyword = None
|
||||
EqualsRange = Some range0
|
||||
}
|
||||
),
|
||||
range0
|
||||
)
|
||||
)
|
||||
|
||||
let interfaceName =
|
||||
let baseName =
|
||||
SynType.CreateLongIdent (SynLongIdent.CreateFromLongIdent interfaceType.Name)
|
||||
|
||||
match interfaceType.Generics with
|
||||
| None -> baseName
|
||||
| Some generics ->
|
||||
let generics =
|
||||
match generics with
|
||||
| SynTyparDecls.PostfixList (decls, _, _) -> decls
|
||||
| SynTyparDecls.PrefixList (decls, _) -> decls
|
||||
| SynTyparDecls.SinglePrefix (decl, _) -> [ decl ]
|
||||
|> List.map (fun (SynTyparDecl (_, typar)) -> SynType.Var (typar, range0))
|
||||
|
||||
SynType.App (
|
||||
baseName,
|
||||
Some range0,
|
||||
generics,
|
||||
List.replicate (generics.Length - 1) range0,
|
||||
Some range0,
|
||||
false,
|
||||
range0
|
||||
)
|
||||
|
||||
SynMemberDefn.Interface (interfaceName, Some range0, Some members, range0)
|
||||
|
||||
let access =
|
||||
match interfaceType.Accessibility, spec.IsInternal with
|
||||
| Some (SynAccess.Public _), true
|
||||
| None, true -> SynAccess.Internal range0
|
||||
| Some (SynAccess.Public _), false -> SynAccess.Public range0
|
||||
| None, false -> SynAccess.Public range0
|
||||
| Some (SynAccess.Internal _), _ -> SynAccess.Internal range0
|
||||
| Some (SynAccess.Private _), _ -> SynAccess.Private range0
|
||||
|
||||
let record =
|
||||
{
|
||||
Name = Ident.Create name
|
||||
Fields = fields
|
||||
Members = Some [ constructor ; interfaceMembers ]
|
||||
XmlDoc = Some xmlDoc
|
||||
Generics = interfaceType.Generics
|
||||
Accessibility = Some access
|
||||
}
|
||||
|
||||
let typeDecl = AstHelper.defineRecordType record
|
||||
|
||||
SynModuleDecl.Types ([ typeDecl ], range0)
|
||||
|
||||
let private buildType (x : ParameterInfo) : SynType =
|
||||
if x.IsOptional then
|
||||
SynType.App (SynType.CreateLongIdent "option", Some range0, [ x.Type ], [], Some range0, false, range0)
|
||||
else
|
||||
x.Type
|
||||
|
||||
let private constructMemberSinglePlace (tuple : TupledArg) : SynType =
|
||||
match tuple.Args |> List.rev |> List.map buildType with
|
||||
| [] -> failwith "no-arg functions not supported yet"
|
||||
| [ x ] -> x
|
||||
| last :: rest ->
|
||||
([ SynTupleTypeSegment.Type last ], rest)
|
||||
||> List.fold (fun ty nextArg -> SynTupleTypeSegment.Type nextArg :: SynTupleTypeSegment.Star range0 :: ty)
|
||||
|> fun segs -> SynType.Tuple (false, segs, range0)
|
||||
|> fun ty -> if tuple.HasParen then SynType.Paren (ty, range0) else ty
|
||||
|
||||
let constructMember (mem : MemberInfo) : SynField =
|
||||
let inputType = mem.Args |> List.map constructMemberSinglePlace
|
||||
|
||||
let funcType = AstHelper.toFun inputType mem.ReturnType
|
||||
|
||||
SynField.SynField (
|
||||
[],
|
||||
false,
|
||||
Some mem.Identifier,
|
||||
funcType,
|
||||
false,
|
||||
mem.XmlDoc |> Option.defaultValue PreXmlDoc.Empty,
|
||||
None,
|
||||
range0,
|
||||
SynFieldTrivia.Zero
|
||||
)
|
||||
|
||||
let createRecord
|
||||
(namespaceId : LongIdent)
|
||||
(opens : SynOpenDeclTarget list)
|
||||
(interfaceType : SynTypeDefn, spec : GenerateMockOutputSpec)
|
||||
: SynModuleOrNamespace
|
||||
=
|
||||
let interfaceType = AstHelper.parseInterface interfaceType
|
||||
let fields = interfaceType.Members |> List.map constructMember
|
||||
let docString = PreXmlDoc.Create " Mock record type for an interface"
|
||||
|
||||
let name =
|
||||
List.last interfaceType.Name
|
||||
|> _.idText
|
||||
|> fun s ->
|
||||
if s.StartsWith 'I' && s.Length > 1 && Char.IsUpper s.[1] then
|
||||
s.[1..]
|
||||
else
|
||||
s
|
||||
|> fun s -> s + "Mock"
|
||||
|
||||
let typeDecl = createType spec name interfaceType docString fields
|
||||
|
||||
|
||||
SynModuleOrNamespace.CreateNamespace (
|
||||
namespaceId,
|
||||
decls = (opens |> List.map SynModuleDecl.CreateOpen) @ [ typeDecl ]
|
||||
)
|
||||
|
||||
/// Myriad generator that creates a record which implements the given interface,
|
||||
/// but with every field mocked out.
|
||||
[<MyriadGenerator("interface-mock")>]
|
||||
type InterfaceMockGenerator () =
|
||||
|
||||
interface IMyriadGenerator with
|
||||
member _.ValidInputExtensions = [ ".fs" ]
|
||||
|
||||
member _.Generate (context : GeneratorContext) =
|
||||
let ast, _ =
|
||||
Ast.fromFilename context.InputFilename |> Async.RunSynchronously |> Array.head
|
||||
|
||||
let types = Ast.extractTypeDefn ast
|
||||
|
||||
let namespaceAndInterfaces =
|
||||
types
|
||||
|> List.choose (fun (ns, types) ->
|
||||
types
|
||||
|> List.choose (fun typeDef ->
|
||||
match Ast.getAttribute<GenerateMockAttribute> typeDef with
|
||||
| None -> None
|
||||
| Some attr ->
|
||||
let arg =
|
||||
match SynExpr.stripOptionalParen attr.ArgExpr with
|
||||
| SynExpr.Const (SynConst.Bool value, _) -> value
|
||||
| SynExpr.Const (SynConst.Unit, _) -> GenerateMockAttribute.DefaultIsInternal
|
||||
| arg ->
|
||||
failwith
|
||||
$"Unrecognised argument %+A{arg} to [<%s{nameof GenerateMockAttribute}>]. Literals are not supported. Use `true` or `false` (or unit) only."
|
||||
|
||||
let spec =
|
||||
{
|
||||
IsInternal = arg
|
||||
}
|
||||
|
||||
Some (typeDef, spec)
|
||||
)
|
||||
|> function
|
||||
| [] -> None
|
||||
| ty -> Some (ns, ty)
|
||||
)
|
||||
|
||||
let opens = AstHelper.extractOpens ast
|
||||
|
||||
let modules =
|
||||
namespaceAndInterfaces
|
||||
|> List.collect (fun (ns, records) ->
|
||||
records |> List.map (InterfaceMockGenerator.createRecord ns opens)
|
||||
)
|
||||
|
||||
Output.Ast modules
|
@@ -7,10 +7,10 @@ open Fantomas.FCS.SyntaxTrivia
|
||||
open Fantomas.FCS.Xml
|
||||
open Myriad.Core
|
||||
|
||||
/// Attribute indicating a record type to which the "Add JSON parse" Myriad
|
||||
/// generator should apply during build.
|
||||
type JsonParseAttribute () =
|
||||
inherit Attribute ()
|
||||
type internal JsonParseOutputSpec =
|
||||
{
|
||||
ExtensionMethods : bool
|
||||
}
|
||||
|
||||
[<RequireQualifiedAccess>]
|
||||
module internal JsonParseGenerator =
|
||||
@@ -68,6 +68,14 @@ module internal JsonParseGenerator =
|
||||
|> SynExpr.callMethod "AsValue"
|
||||
|> SynExpr.callGenericMethod "GetValue" typeName
|
||||
|
||||
/// {node}.AsObject()
|
||||
/// If `propertyName` is Some, uses `assertNotNull {node}` instead of `{node}`.
|
||||
let asObject (propertyName : SynExpr option) (node : SynExpr) : SynExpr =
|
||||
match propertyName with
|
||||
| None -> node
|
||||
| Some propertyName -> assertNotNull propertyName node
|
||||
|> SynExpr.callMethod "AsObject"
|
||||
|
||||
/// {type}.jsonParse {node}
|
||||
let typeJsonParse (typeName : LongIdent) (node : SynExpr) : SynExpr =
|
||||
SynExpr.CreateApp (
|
||||
@@ -116,6 +124,54 @@ module internal JsonParseGenerator =
|
||||
let parseFunction (typeName : string) : LongIdent =
|
||||
List.append (SynExpr.qualifyPrimitiveType typeName) [ Ident.Create "Parse" ]
|
||||
|
||||
/// fun kvp -> let key = {key(kvp)} in let value = {value(kvp)} in (key, value))
|
||||
/// The inputs will be fed with appropriate SynExprs to apply them to the `kvp.Key` and `kvp.Value` args.
|
||||
let dictionaryMapper (key : SynExpr -> SynExpr) (value : SynExpr -> SynExpr) : SynExpr =
|
||||
let keyArg =
|
||||
SynExpr.CreateLongIdent (SynLongIdent.Create [ "kvp" ; "Key" ])
|
||||
|> SynExpr.CreateParen
|
||||
|
||||
let valueArg =
|
||||
SynExpr.CreateLongIdent (SynLongIdent.Create [ "kvp" ; "Value" ])
|
||||
|> SynExpr.CreateParen
|
||||
|
||||
SynExpr.LetOrUse (
|
||||
false,
|
||||
false,
|
||||
[
|
||||
SynBinding.Let (pattern = SynPat.CreateNamed (Ident.Create "key"), expr = key keyArg)
|
||||
],
|
||||
SynExpr.LetOrUse (
|
||||
false,
|
||||
false,
|
||||
[
|
||||
SynBinding.Let (pattern = SynPat.CreateNamed (Ident.Create "value"), expr = value valueArg)
|
||||
],
|
||||
SynExpr.CreateTuple [ SynExpr.CreateIdentString "key" ; SynExpr.CreateIdentString "value" ],
|
||||
range0,
|
||||
{
|
||||
InKeyword = None
|
||||
}
|
||||
),
|
||||
range0,
|
||||
{
|
||||
InKeyword = None
|
||||
}
|
||||
)
|
||||
|> SynExpr.createLambda "kvp"
|
||||
|
||||
/// A conforming JSON object has only strings as keys. But it would be reasonable to allow the user
|
||||
/// to parse these as URIs, for example.
|
||||
let parseKeyString (desiredType : SynType) (key : SynExpr) : SynExpr =
|
||||
match desiredType with
|
||||
| String -> key
|
||||
| Uri ->
|
||||
key
|
||||
|> SynExpr.pipeThroughFunction (SynExpr.CreateLongIdent (SynLongIdent.Create [ "System" ; "Uri" ]))
|
||||
| _ ->
|
||||
failwithf
|
||||
$"Unable to parse the key type %+A{desiredType} of a JSON object. Keys are strings, and this plugin does not know how to convert to that from a string."
|
||||
|
||||
/// Given `node.["town"]`, for example, choose how to obtain a JSON value from it.
|
||||
/// The property name is used in error messages at runtime to show where a JSON
|
||||
/// parse error occurred; supply `None` to indicate "don't validate".
|
||||
@@ -134,6 +190,16 @@ module internal JsonParseGenerator =
|
||||
|> SynExpr.pipeThroughFunction (
|
||||
SynExpr.CreateLongIdent (SynLongIdent.Create [ "System" ; "DateOnly" ; "Parse" ])
|
||||
)
|
||||
| Uri ->
|
||||
node
|
||||
|> asValueGetValue propertyName "string"
|
||||
|> SynExpr.pipeThroughFunction (SynExpr.CreateLongIdent (SynLongIdent.Create [ "System" ; "Uri" ]))
|
||||
| Guid ->
|
||||
node
|
||||
|> asValueGetValue propertyName "string"
|
||||
|> SynExpr.pipeThroughFunction (
|
||||
SynExpr.CreateLongIdent (SynLongIdent.Create [ "System" ; "Guid" ; "Parse" ])
|
||||
)
|
||||
| DateTime ->
|
||||
node
|
||||
|> asValueGetValue propertyName "string"
|
||||
@@ -196,6 +262,56 @@ module internal JsonParseGenerator =
|
||||
| ArrayType ty ->
|
||||
parseNode None options ty (SynExpr.CreateLongIdent (SynLongIdent.CreateString "elt"))
|
||||
|> asArrayMapped propertyName "Array" node
|
||||
| IDictionaryType (keyType, valueType) ->
|
||||
node
|
||||
|> asObject propertyName
|
||||
|> SynExpr.pipeThroughFunction (
|
||||
SynExpr.CreateApp (
|
||||
SynExpr.CreateLongIdent (SynLongIdent.Create [ "Seq" ; "map" ]),
|
||||
dictionaryMapper (parseKeyString keyType) (parseNode None options valueType)
|
||||
)
|
||||
)
|
||||
|> SynExpr.pipeThroughFunction (SynExpr.CreateLongIdent (SynLongIdent.Create [ "dict" ]))
|
||||
| DictionaryType (keyType, valueType) ->
|
||||
node
|
||||
|> asObject propertyName
|
||||
|> SynExpr.pipeThroughFunction (
|
||||
SynExpr.CreateApp (
|
||||
SynExpr.CreateLongIdent (SynLongIdent.Create [ "Seq" ; "map" ]),
|
||||
dictionaryMapper (parseKeyString keyType) (parseNode None options valueType)
|
||||
)
|
||||
)
|
||||
|> SynExpr.pipeThroughFunction (
|
||||
SynExpr.CreateApp (
|
||||
SynExpr.CreateLongIdent (SynLongIdent.Create [ "Seq" ; "map" ]),
|
||||
SynExpr.CreateLongIdent (
|
||||
SynLongIdent.Create [ "System" ; "Collections" ; "Generic" ; "KeyValuePair" ]
|
||||
)
|
||||
)
|
||||
)
|
||||
|> SynExpr.pipeThroughFunction (
|
||||
SynExpr.CreateLongIdent (SynLongIdent.Create [ "System" ; "Collections" ; "Generic" ; "Dictionary" ])
|
||||
)
|
||||
| IReadOnlyDictionaryType (keyType, valueType) ->
|
||||
node
|
||||
|> asObject propertyName
|
||||
|> SynExpr.pipeThroughFunction (
|
||||
SynExpr.CreateApp (
|
||||
SynExpr.CreateLongIdent (SynLongIdent.Create [ "Seq" ; "map" ]),
|
||||
dictionaryMapper (parseKeyString keyType) (parseNode None options valueType)
|
||||
)
|
||||
)
|
||||
|> SynExpr.pipeThroughFunction (SynExpr.CreateLongIdent (SynLongIdent.Create [ "readOnlyDict" ]))
|
||||
| MapType (keyType, valueType) ->
|
||||
node
|
||||
|> asObject propertyName
|
||||
|> SynExpr.pipeThroughFunction (
|
||||
SynExpr.CreateApp (
|
||||
SynExpr.CreateLongIdent (SynLongIdent.Create [ "Seq" ; "map" ]),
|
||||
dictionaryMapper (parseKeyString keyType) (parseNode None options valueType)
|
||||
)
|
||||
)
|
||||
|> SynExpr.pipeThroughFunction (SynExpr.CreateLongIdent (SynLongIdent.Create [ "Map" ; "ofSeq" ]))
|
||||
| _ ->
|
||||
// Let's just hope that we've also got our own type annotation!
|
||||
let typeName =
|
||||
@@ -203,7 +319,10 @@ module internal JsonParseGenerator =
|
||||
| SynType.LongIdent ident -> ident.LongIdent
|
||||
| _ -> failwith $"Unrecognised type: %+A{fieldType}"
|
||||
|
||||
typeJsonParse typeName node
|
||||
match propertyName with
|
||||
| None -> node
|
||||
| Some propertyName -> assertNotNull propertyName node
|
||||
|> typeJsonParse typeName
|
||||
|
||||
/// propertyName is probably a string literal, but it could be a [<Literal>] variable
|
||||
/// The result of this function is the body of a let-binding (not including the LHS of that let-binding).
|
||||
@@ -221,7 +340,7 @@ module internal JsonParseGenerator =
|
||||
| [ _ ; "JsonNumberHandling" ; "Serialization" ; "Json" ; "Text" ; "System" ] -> true
|
||||
| _ -> false
|
||||
|
||||
let createMaker (typeName : LongIdent) (fields : SynField list) =
|
||||
let createMaker (spec : JsonParseOutputSpec) (typeName : LongIdent) (fields : SynField list) =
|
||||
let xmlDoc = PreXmlDoc.Create " Parse from a JSON node."
|
||||
|
||||
let returnInfo =
|
||||
@@ -231,10 +350,26 @@ module internal JsonParseGenerator =
|
||||
let functionName = Ident.Create "jsonParse"
|
||||
|
||||
let inputVal =
|
||||
let memberFlags =
|
||||
if spec.ExtensionMethods then
|
||||
{
|
||||
SynMemberFlags.IsInstance = false
|
||||
SynMemberFlags.IsDispatchSlot = false
|
||||
SynMemberFlags.IsOverrideOrExplicitImpl = false
|
||||
SynMemberFlags.IsFinal = false
|
||||
SynMemberFlags.GetterOrSetterIsCompilerGenerated = false
|
||||
SynMemberFlags.MemberKind = SynMemberKind.Member
|
||||
}
|
||||
|> Some
|
||||
else
|
||||
None
|
||||
|
||||
let thisIdOpt = if spec.ExtensionMethods then None else Some inputArg
|
||||
|
||||
SynValData.SynValData (
|
||||
None,
|
||||
memberFlags,
|
||||
SynValInfo.SynValInfo ([ [ SynArgInfo.CreateId functionName ] ], SynArgInfo.Empty),
|
||||
Some inputArg
|
||||
thisIdOpt
|
||||
)
|
||||
|
||||
let assignments =
|
||||
@@ -325,7 +460,7 @@ module internal JsonParseGenerator =
|
||||
(SynLongIdent.CreateFromLongIdent [ id ], true),
|
||||
Some (SynExpr.CreateLongIdent (SynLongIdent.CreateFromLongIdent [ id ]))
|
||||
)
|
||||
|> AstHelper.constructRecord
|
||||
|> AstHelper.instantiateRecord
|
||||
|
||||
let assignments =
|
||||
(finalConstruction, assignments)
|
||||
@@ -361,20 +496,60 @@ module internal JsonParseGenerator =
|
||||
range0
|
||||
)
|
||||
|
||||
let binding =
|
||||
SynBinding.Let (
|
||||
isInline = false,
|
||||
isMutable = false,
|
||||
xmldoc = xmlDoc,
|
||||
returnInfo = returnInfo,
|
||||
expr = assignments,
|
||||
valData = inputVal,
|
||||
pattern = pattern
|
||||
)
|
||||
if spec.ExtensionMethods then
|
||||
let binding =
|
||||
SynBinding.SynBinding (
|
||||
None,
|
||||
SynBindingKind.Normal,
|
||||
false,
|
||||
false,
|
||||
[],
|
||||
xmlDoc,
|
||||
inputVal,
|
||||
pattern,
|
||||
Some returnInfo,
|
||||
assignments,
|
||||
range0,
|
||||
DebugPointAtBinding.NoneAtInvisible,
|
||||
{
|
||||
LeadingKeyword = SynLeadingKeyword.StaticMember (range0, range0)
|
||||
InlineKeyword = None
|
||||
EqualsRange = Some range0
|
||||
}
|
||||
)
|
||||
|
||||
SynModuleDecl.CreateLet [ binding ]
|
||||
let mem = SynMemberDefn.Member (binding, range0)
|
||||
|
||||
let createRecordModule (namespaceId : LongIdent) (typeDefn : SynTypeDefn) =
|
||||
let containingType =
|
||||
SynTypeDefn.SynTypeDefn (
|
||||
SynComponentInfo.Create (typeName, xmldoc = PreXmlDoc.Create " Extension methods for JSON parsing"),
|
||||
SynTypeDefnRepr.ObjectModel (SynTypeDefnKind.Augmentation range0, [], range0),
|
||||
[ mem ],
|
||||
None,
|
||||
range0,
|
||||
{
|
||||
LeadingKeyword = SynTypeDefnLeadingKeyword.Type range0
|
||||
EqualsRange = None
|
||||
WithKeyword = None
|
||||
}
|
||||
)
|
||||
|
||||
SynModuleDecl.Types ([ containingType ], range0)
|
||||
else
|
||||
let binding =
|
||||
SynBinding.Let (
|
||||
isInline = false,
|
||||
isMutable = false,
|
||||
xmldoc = xmlDoc,
|
||||
returnInfo = returnInfo,
|
||||
expr = assignments,
|
||||
valData = inputVal,
|
||||
pattern = pattern
|
||||
)
|
||||
|
||||
SynModuleDecl.CreateLet [ binding ]
|
||||
|
||||
let createRecordModule (namespaceId : LongIdent) (spec : JsonParseOutputSpec) (typeDefn : SynTypeDefn) =
|
||||
let (SynTypeDefn (synComponentInfo, synTypeDefnRepr, _members, _implicitCtor, _, _)) =
|
||||
typeDefn
|
||||
|
||||
@@ -384,30 +559,54 @@ module internal JsonParseGenerator =
|
||||
match synTypeDefnRepr with
|
||||
| SynTypeDefnRepr.Simple (SynTypeDefnSimpleRepr.Record (_accessibility, recordFields, _recordRange), _) ->
|
||||
|
||||
let decls = [ createMaker recordId recordFields ]
|
||||
let decls = [ createMaker spec recordId recordFields ]
|
||||
|
||||
let attributes =
|
||||
[
|
||||
SynAttributeList.Create (SynAttribute.RequireQualifiedAccess ())
|
||||
SynAttributeList.Create SynAttribute.compilationRepresentation
|
||||
]
|
||||
if spec.ExtensionMethods then
|
||||
[ SynAttributeList.Create SynAttribute.autoOpen ]
|
||||
else
|
||||
[
|
||||
SynAttributeList.Create (SynAttribute.RequireQualifiedAccess ())
|
||||
SynAttributeList.Create SynAttribute.compilationRepresentation
|
||||
]
|
||||
|
||||
let xmlDoc =
|
||||
recordId
|
||||
|> Seq.map (fun i -> i.idText)
|
||||
|> String.concat "."
|
||||
|> sprintf " Module containing JSON parsing methods for the %s type"
|
||||
let fullyQualified = recordId |> Seq.map (fun i -> i.idText) |> String.concat "."
|
||||
|
||||
let description =
|
||||
if spec.ExtensionMethods then
|
||||
"extension members"
|
||||
else
|
||||
"methods"
|
||||
|
||||
$" Module containing JSON parsing %s{description} for the %s{fullyQualified} type"
|
||||
|> PreXmlDoc.Create
|
||||
|
||||
let moduleName =
|
||||
if spec.ExtensionMethods then
|
||||
match recordId with
|
||||
| [] -> failwith "unexpectedly got an empty identifier for record name"
|
||||
| recordId ->
|
||||
let expanded =
|
||||
List.last recordId
|
||||
|> fun i -> i.idText
|
||||
|> fun s -> s + "JsonParseExtension"
|
||||
|> Ident.Create
|
||||
|
||||
List.take (List.length recordId - 1) recordId @ [ expanded ]
|
||||
else
|
||||
recordId
|
||||
|
||||
let info =
|
||||
SynComponentInfo.Create (recordId, attributes = attributes, xmldoc = xmlDoc)
|
||||
SynComponentInfo.Create (moduleName, attributes = attributes, xmldoc = xmlDoc)
|
||||
|
||||
let mdl = SynModuleDecl.CreateNestedModule (info, decls)
|
||||
|
||||
SynModuleOrNamespace.CreateNamespace (namespaceId, decls = [ mdl ])
|
||||
| _ -> failwithf "Not a record type"
|
||||
|
||||
/// Myriad generator that provides a JSON parse function for a record type.
|
||||
/// Myriad generator that provides a method (possibly an extension method) for a record type,
|
||||
/// containing a JSON parse function.
|
||||
[<MyriadGenerator("json-parse")>]
|
||||
type JsonParseGenerator () =
|
||||
|
||||
@@ -423,17 +622,37 @@ type JsonParseGenerator () =
|
||||
let namespaceAndRecords =
|
||||
records
|
||||
|> List.choose (fun (ns, types) ->
|
||||
match types |> List.filter Ast.hasAttribute<JsonParseAttribute> with
|
||||
| [] -> None
|
||||
| types -> Some (ns, types)
|
||||
types
|
||||
|> List.choose (fun typeDef ->
|
||||
match Ast.getAttribute<JsonParseAttribute> typeDef with
|
||||
| None -> None
|
||||
| Some attr ->
|
||||
let arg =
|
||||
match SynExpr.stripOptionalParen attr.ArgExpr with
|
||||
| SynExpr.Const (SynConst.Bool value, _) -> value
|
||||
| SynExpr.Const (SynConst.Unit, _) -> JsonParseAttribute.DefaultIsExtensionMethod
|
||||
| arg ->
|
||||
failwith
|
||||
$"Unrecognised argument %+A{arg} to [<%s{nameof JsonParseAttribute}>]. Literals are not supported. Use `true` or `false` (or unit) only."
|
||||
|
||||
let spec =
|
||||
{
|
||||
ExtensionMethods = arg
|
||||
}
|
||||
|
||||
Some (typeDef, spec)
|
||||
)
|
||||
|> function
|
||||
| [] -> None
|
||||
| ty -> Some (ns, ty)
|
||||
)
|
||||
|
||||
let modules =
|
||||
namespaceAndRecords
|
||||
|> List.collect (fun (ns, records) ->
|
||||
records
|
||||
|> List.map (fun record ->
|
||||
let recordModule = JsonParseGenerator.createRecordModule ns record
|
||||
|> List.map (fun (record, spec) ->
|
||||
let recordModule = JsonParseGenerator.createRecordModule ns spec record
|
||||
recordModule
|
||||
)
|
||||
)
|
||||
|
527
WoofWare.Myriad.Plugins/JsonSerializeGenerator.fs
Normal file
527
WoofWare.Myriad.Plugins/JsonSerializeGenerator.fs
Normal file
@@ -0,0 +1,527 @@
|
||||
namespace WoofWare.Myriad.Plugins
|
||||
|
||||
open System
|
||||
open System.Text
|
||||
open Fantomas.FCS.Syntax
|
||||
open Fantomas.FCS.SyntaxTrivia
|
||||
open Fantomas.FCS.Xml
|
||||
open Myriad.Core
|
||||
|
||||
type internal JsonSerializeOutputSpec =
|
||||
{
|
||||
ExtensionMethods : bool
|
||||
}
|
||||
|
||||
[<RequireQualifiedAccess>]
|
||||
module internal JsonSerializeGenerator =
|
||||
open Fantomas.FCS.Text.Range
|
||||
open Myriad.Core.Ast
|
||||
|
||||
/// Given `input.Ident`, for example, choose how to add it to the ambient `node`.
|
||||
/// The result is a line like `(fun ident -> InnerType.toJsonNode ident)` or `(fun ident -> JsonValue.Create ident)`.
|
||||
let rec serializeNode (fieldType : SynType) : SynExpr =
|
||||
// TODO: serialization format for DateTime etc
|
||||
match fieldType with
|
||||
| DateOnly
|
||||
| DateTime
|
||||
| NumberType _
|
||||
| PrimitiveType _
|
||||
| Guid
|
||||
| Uri ->
|
||||
// JsonValue.Create<type>
|
||||
SynExpr.TypeApp (
|
||||
SynExpr.CreateLongIdent (
|
||||
SynLongIdent.Create [ "System" ; "Text" ; "Json" ; "Nodes" ; "JsonValue" ; "Create" ]
|
||||
),
|
||||
range0,
|
||||
[ fieldType ],
|
||||
[],
|
||||
Some range0,
|
||||
range0,
|
||||
range0
|
||||
)
|
||||
| OptionType ty ->
|
||||
// fun field -> match field with | None -> JsonValue.Create null | Some v -> {serializeNode ty} field
|
||||
SynExpr.CreateMatch (
|
||||
SynExpr.CreateIdentString "field",
|
||||
[
|
||||
SynMatchClause.Create (
|
||||
SynPat.CreateLongIdent (SynLongIdent.CreateString "None", []),
|
||||
None,
|
||||
// The absolutely galaxy-brained implementation of JsonValue has `JsonValue.Parse "null"`
|
||||
// identically equal to null. We have to work around this later, but we might as well just
|
||||
// be efficient here and whip up the null directly.
|
||||
SynExpr.CreateNull
|
||||
|> SynExpr.upcast' (
|
||||
SynType.CreateLongIdent (
|
||||
SynLongIdent.Create [ "System" ; "Text" ; "Json" ; "Nodes" ; "JsonNode" ]
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
SynMatchClause.Create (
|
||||
SynPat.CreateLongIdent (
|
||||
SynLongIdent.CreateString "Some",
|
||||
[ SynPat.CreateNamed (Ident.Create "field") ]
|
||||
),
|
||||
None,
|
||||
SynExpr.CreateApp (serializeNode ty, SynExpr.CreateIdentString "field")
|
||||
|> SynExpr.CreateParen
|
||||
|> SynExpr.upcast' (
|
||||
SynType.CreateLongIdent (
|
||||
SynLongIdent.Create [ "System" ; "Text" ; "Json" ; "Nodes" ; "JsonNode" ]
|
||||
)
|
||||
)
|
||||
)
|
||||
]
|
||||
)
|
||||
|> SynExpr.createLambda "field"
|
||||
| ArrayType ty
|
||||
| ListType ty ->
|
||||
// fun field ->
|
||||
// let arr = JsonArray ()
|
||||
// for mem in field do arr.Add ({serializeNode} mem)
|
||||
// arr
|
||||
SynExpr.LetOrUse (
|
||||
false,
|
||||
false,
|
||||
[
|
||||
SynBinding.Let (
|
||||
pattern = SynPat.CreateNamed (Ident.Create "arr"),
|
||||
expr =
|
||||
SynExpr.CreateApp (
|
||||
SynExpr.CreateLongIdent (
|
||||
SynLongIdent.Create [ "System" ; "Text" ; "Json" ; "Nodes" ; "JsonArray" ]
|
||||
),
|
||||
SynExpr.CreateConst SynConst.Unit
|
||||
)
|
||||
)
|
||||
],
|
||||
SynExpr.CreateSequential
|
||||
[
|
||||
SynExpr.ForEach (
|
||||
DebugPointAtFor.Yes range0,
|
||||
DebugPointAtInOrTo.Yes range0,
|
||||
SeqExprOnly.SeqExprOnly false,
|
||||
true,
|
||||
SynPat.CreateNamed (Ident.Create "mem"),
|
||||
SynExpr.CreateIdent (Ident.Create "field"),
|
||||
SynExpr.CreateApp (
|
||||
SynExpr.CreateLongIdent (SynLongIdent.Create [ "arr" ; "Add" ]),
|
||||
SynExpr.CreateParen (
|
||||
SynExpr.CreateApp (serializeNode ty, SynExpr.CreateIdentString "mem")
|
||||
)
|
||||
),
|
||||
range0
|
||||
)
|
||||
SynExpr.CreateIdentString "arr"
|
||||
],
|
||||
range0,
|
||||
{
|
||||
InKeyword = None
|
||||
}
|
||||
)
|
||||
|> SynExpr.createLambda "field"
|
||||
| IDictionaryType (keyType, valueType)
|
||||
| DictionaryType (keyType, valueType)
|
||||
| IReadOnlyDictionaryType (keyType, valueType)
|
||||
| MapType (keyType, valueType) ->
|
||||
// fun field ->
|
||||
// let ret = JsonObject ()
|
||||
// for (KeyValue(key, value)) in field do
|
||||
// ret.Add (key.ToString (), {serializeNode} value)
|
||||
// ret
|
||||
SynExpr.LetOrUse (
|
||||
false,
|
||||
false,
|
||||
[
|
||||
SynBinding.Let (
|
||||
pattern = SynPat.CreateNamed (Ident.Create "ret"),
|
||||
expr =
|
||||
SynExpr.CreateApp (
|
||||
SynExpr.CreateLongIdent (
|
||||
SynLongIdent.Create [ "System" ; "Text" ; "Json" ; "Nodes" ; "JsonObject" ]
|
||||
),
|
||||
SynExpr.CreateConst SynConst.Unit
|
||||
)
|
||||
)
|
||||
],
|
||||
SynExpr.CreateSequential
|
||||
[
|
||||
SynExpr.ForEach (
|
||||
DebugPointAtFor.Yes range0,
|
||||
DebugPointAtInOrTo.Yes range0,
|
||||
SeqExprOnly.SeqExprOnly false,
|
||||
true,
|
||||
SynPat.CreateParen (
|
||||
SynPat.CreateLongIdent (
|
||||
SynLongIdent.CreateString "KeyValue",
|
||||
[
|
||||
SynPat.CreateParen (
|
||||
SynPat.Tuple (
|
||||
false,
|
||||
[
|
||||
SynPat.CreateNamed (Ident.Create "key")
|
||||
SynPat.CreateNamed (Ident.Create "value")
|
||||
],
|
||||
[ range0 ],
|
||||
range0
|
||||
)
|
||||
)
|
||||
]
|
||||
)
|
||||
),
|
||||
SynExpr.CreateIdent (Ident.Create "field"),
|
||||
SynExpr.CreateApp (
|
||||
SynExpr.CreateLongIdent (SynLongIdent.Create [ "ret" ; "Add" ]),
|
||||
SynExpr.CreateParenedTuple
|
||||
[
|
||||
SynExpr.CreateApp (
|
||||
SynExpr.CreateLongIdent (SynLongIdent.Create [ "key" ; "ToString" ]),
|
||||
SynExpr.CreateConst SynConst.Unit
|
||||
)
|
||||
SynExpr.CreateApp (serializeNode valueType, SynExpr.CreateIdentString "value")
|
||||
]
|
||||
),
|
||||
range0
|
||||
)
|
||||
SynExpr.CreateIdentString "ret"
|
||||
],
|
||||
range0,
|
||||
{
|
||||
InKeyword = None
|
||||
}
|
||||
)
|
||||
|> SynExpr.createLambda "field"
|
||||
| _ ->
|
||||
// {type}.toJsonNode
|
||||
let typeName =
|
||||
match fieldType with
|
||||
| SynType.LongIdent ident -> ident.LongIdent
|
||||
| _ -> failwith $"Unrecognised type: %+A{fieldType}"
|
||||
|
||||
SynExpr.CreateLongIdent (SynLongIdent.CreateFromLongIdent (typeName @ [ Ident.Create "toJsonNode" ]))
|
||||
|
||||
/// propertyName is probably a string literal, but it could be a [<Literal>] variable
|
||||
/// `node.Add ({propertyName}, {toJsonNode})`
|
||||
let createSerializeRhs (propertyName : SynExpr) (fieldId : Ident) (fieldType : SynType) : SynExpr =
|
||||
let func = SynExpr.CreateLongIdent (SynLongIdent.Create [ "node" ; "Add" ])
|
||||
|
||||
let args =
|
||||
SynExpr.CreateParenedTuple
|
||||
[
|
||||
propertyName
|
||||
SynExpr.CreateApp (
|
||||
serializeNode fieldType,
|
||||
SynExpr.CreateLongIdent (SynLongIdent.CreateFromLongIdent [ Ident.Create "input" ; fieldId ])
|
||||
)
|
||||
]
|
||||
|
||||
SynExpr.CreateApp (func, args)
|
||||
|
||||
let createMaker (spec : JsonSerializeOutputSpec) (typeName : LongIdent) (fields : SynField list) =
|
||||
let xmlDoc = PreXmlDoc.Create " Serialize to a JSON node"
|
||||
|
||||
let returnInfo =
|
||||
SynBindingReturnInfo.Create (
|
||||
SynType.LongIdent (SynLongIdent.Create [ "System" ; "Text" ; "Json" ; "Nodes" ; "JsonNode" ])
|
||||
)
|
||||
|
||||
let inputArg = Ident.Create "input"
|
||||
let functionName = Ident.Create "toJsonNode"
|
||||
|
||||
let inputVal =
|
||||
let memberFlags =
|
||||
if spec.ExtensionMethods then
|
||||
{
|
||||
SynMemberFlags.IsInstance = false
|
||||
SynMemberFlags.IsDispatchSlot = false
|
||||
SynMemberFlags.IsOverrideOrExplicitImpl = false
|
||||
SynMemberFlags.IsFinal = false
|
||||
SynMemberFlags.GetterOrSetterIsCompilerGenerated = false
|
||||
SynMemberFlags.MemberKind = SynMemberKind.Member
|
||||
}
|
||||
|> Some
|
||||
else
|
||||
None
|
||||
|
||||
let thisIdOpt = if spec.ExtensionMethods then None else Some inputArg
|
||||
|
||||
SynValData.SynValData (
|
||||
memberFlags,
|
||||
SynValInfo.SynValInfo ([ [ SynArgInfo.CreateId functionName ] ], SynArgInfo.Empty),
|
||||
thisIdOpt
|
||||
)
|
||||
|
||||
let assignments =
|
||||
fields
|
||||
|> List.map (fun (SynField (attrs, _, id, fieldType, _, _, _, _, _)) ->
|
||||
let id =
|
||||
match id with
|
||||
| None -> failwith "didn't get an ID on field"
|
||||
| Some id -> id
|
||||
|
||||
let attrs = attrs |> List.collect (fun l -> l.Attributes)
|
||||
|
||||
let propertyNameAttr =
|
||||
attrs
|
||||
|> List.tryFind (fun attr ->
|
||||
attr.TypeName.AsString.EndsWith ("JsonPropertyName", StringComparison.Ordinal)
|
||||
)
|
||||
|
||||
let propertyName =
|
||||
match propertyNameAttr with
|
||||
| None ->
|
||||
let sb = StringBuilder id.idText.Length
|
||||
sb.Append (Char.ToLowerInvariant id.idText.[0]) |> ignore
|
||||
|
||||
if id.idText.Length > 1 then
|
||||
sb.Append id.idText.[1..] |> ignore
|
||||
|
||||
sb.ToString () |> SynConst.CreateString |> SynExpr.CreateConst
|
||||
| Some name -> name.ArgExpr
|
||||
|
||||
let pattern =
|
||||
SynPat.LongIdent (
|
||||
SynLongIdent.CreateFromLongIdent [ id ],
|
||||
None,
|
||||
None,
|
||||
SynArgPats.Empty,
|
||||
None,
|
||||
range0
|
||||
)
|
||||
|
||||
createSerializeRhs propertyName id fieldType
|
||||
)
|
||||
|
||||
let finalConstruction =
|
||||
fields
|
||||
|> List.map (fun (SynField (_, _, id, _, _, _, _, _, _)) ->
|
||||
let id =
|
||||
match id with
|
||||
| None -> failwith "Expected record field to have an identifying name"
|
||||
| Some id -> id
|
||||
|
||||
(SynLongIdent.CreateFromLongIdent [ id ], true),
|
||||
Some (SynExpr.CreateLongIdent (SynLongIdent.CreateFromLongIdent [ id ]))
|
||||
)
|
||||
|> AstHelper.instantiateRecord
|
||||
|
||||
let assignments = assignments |> SynExpr.CreateSequential
|
||||
|
||||
let assignments =
|
||||
SynExpr.LetOrUse (
|
||||
false,
|
||||
false,
|
||||
[
|
||||
SynBinding.Let (
|
||||
pattern = SynPat.CreateNamed (Ident.Create "node"),
|
||||
expr =
|
||||
SynExpr.CreateApp (
|
||||
SynExpr.CreateLongIdent (
|
||||
SynLongIdent.Create [ "System" ; "Text" ; "Json" ; "Nodes" ; "JsonObject" ]
|
||||
),
|
||||
SynExpr.CreateConst SynConst.Unit
|
||||
)
|
||||
)
|
||||
],
|
||||
SynExpr.CreateSequential
|
||||
[
|
||||
SynExpr.Do (assignments, range0)
|
||||
SynExpr.Upcast (SynExpr.CreateIdentString "node", SynType.Anon range0, range0)
|
||||
],
|
||||
range0,
|
||||
{
|
||||
InKeyword = None
|
||||
}
|
||||
)
|
||||
|
||||
let pattern =
|
||||
SynPat.LongIdent (
|
||||
SynLongIdent.CreateFromLongIdent [ functionName ],
|
||||
None,
|
||||
None,
|
||||
SynArgPats.Pats
|
||||
[
|
||||
SynPat.CreateTyped (
|
||||
SynPat.CreateNamed inputArg,
|
||||
SynType.LongIdent (SynLongIdent.CreateFromLongIdent typeName)
|
||||
)
|
||||
|> SynPat.CreateParen
|
||||
],
|
||||
None,
|
||||
range0
|
||||
)
|
||||
|
||||
if spec.ExtensionMethods then
|
||||
let binding =
|
||||
SynBinding.SynBinding (
|
||||
None,
|
||||
SynBindingKind.Normal,
|
||||
false,
|
||||
false,
|
||||
[],
|
||||
xmlDoc,
|
||||
inputVal,
|
||||
pattern,
|
||||
Some returnInfo,
|
||||
assignments,
|
||||
range0,
|
||||
DebugPointAtBinding.NoneAtInvisible,
|
||||
{
|
||||
LeadingKeyword = SynLeadingKeyword.StaticMember (range0, range0)
|
||||
InlineKeyword = None
|
||||
EqualsRange = Some range0
|
||||
}
|
||||
)
|
||||
|
||||
let mem = SynMemberDefn.Member (binding, range0)
|
||||
|
||||
let containingType =
|
||||
SynTypeDefn.SynTypeDefn (
|
||||
SynComponentInfo.Create (typeName, xmldoc = PreXmlDoc.Create " Extension methods for JSON parsing"),
|
||||
SynTypeDefnRepr.ObjectModel (SynTypeDefnKind.Augmentation range0, [], range0),
|
||||
[ mem ],
|
||||
None,
|
||||
range0,
|
||||
{
|
||||
LeadingKeyword = SynTypeDefnLeadingKeyword.Type range0
|
||||
EqualsRange = None
|
||||
WithKeyword = None
|
||||
}
|
||||
)
|
||||
|
||||
SynModuleDecl.Types ([ containingType ], range0)
|
||||
else
|
||||
let binding =
|
||||
SynBinding.Let (
|
||||
isInline = false,
|
||||
isMutable = false,
|
||||
xmldoc = xmlDoc,
|
||||
returnInfo = returnInfo,
|
||||
expr = assignments,
|
||||
valData = inputVal,
|
||||
pattern = pattern
|
||||
)
|
||||
|
||||
SynModuleDecl.CreateLet [ binding ]
|
||||
|
||||
let createRecordModule
|
||||
(namespaceId : LongIdent)
|
||||
(opens : SynOpenDeclTarget list)
|
||||
(spec : JsonSerializeOutputSpec)
|
||||
(typeDefn : SynTypeDefn)
|
||||
=
|
||||
let (SynTypeDefn (synComponentInfo, synTypeDefnRepr, _members, _implicitCtor, _, _)) =
|
||||
typeDefn
|
||||
|
||||
let (SynComponentInfo (_attributes, _typeParams, _constraints, recordId, _, _preferPostfix, _access, _)) =
|
||||
synComponentInfo
|
||||
|
||||
match synTypeDefnRepr with
|
||||
| SynTypeDefnRepr.Simple (SynTypeDefnSimpleRepr.Record (_accessibility, recordFields, _recordRange), _) ->
|
||||
|
||||
let decls = [ createMaker spec recordId recordFields ]
|
||||
|
||||
let attributes =
|
||||
if spec.ExtensionMethods then
|
||||
[ SynAttributeList.Create SynAttribute.autoOpen ]
|
||||
else
|
||||
[
|
||||
SynAttributeList.Create (SynAttribute.RequireQualifiedAccess ())
|
||||
SynAttributeList.Create SynAttribute.compilationRepresentation
|
||||
]
|
||||
|
||||
let xmlDoc =
|
||||
let fullyQualified = recordId |> Seq.map (fun i -> i.idText) |> String.concat "."
|
||||
|
||||
let description =
|
||||
if spec.ExtensionMethods then
|
||||
"extension members"
|
||||
else
|
||||
"methods"
|
||||
|
||||
$" Module containing JSON serializing %s{description} for the %s{fullyQualified} type"
|
||||
|> PreXmlDoc.Create
|
||||
|
||||
let moduleName =
|
||||
if spec.ExtensionMethods then
|
||||
match recordId with
|
||||
| [] -> failwith "unexpectedly got an empty identifier for record name"
|
||||
| recordId ->
|
||||
let expanded =
|
||||
List.last recordId
|
||||
|> fun i -> i.idText
|
||||
|> fun s -> s + "JsonSerializeExtension"
|
||||
|> Ident.Create
|
||||
|
||||
List.take (List.length recordId - 1) recordId @ [ expanded ]
|
||||
else
|
||||
recordId
|
||||
|
||||
let info =
|
||||
SynComponentInfo.Create (moduleName, attributes = attributes, xmldoc = xmlDoc)
|
||||
|
||||
let mdl = SynModuleDecl.CreateNestedModule (info, decls)
|
||||
|
||||
SynModuleOrNamespace.CreateNamespace (
|
||||
namespaceId,
|
||||
decls = (opens |> List.map SynModuleDecl.CreateOpen) @ [ mdl ]
|
||||
)
|
||||
| _ -> failwithf "Not a record type"
|
||||
|
||||
/// Myriad generator that provides a method (possibly an extension method) for a record type,
|
||||
/// containing a JSON serialization function.
|
||||
[<MyriadGenerator("json-serialize")>]
|
||||
type JsonSerializeGenerator () =
|
||||
|
||||
interface IMyriadGenerator with
|
||||
member _.ValidInputExtensions = [ ".fs" ]
|
||||
|
||||
member _.Generate (context : GeneratorContext) =
|
||||
let ast, _ =
|
||||
Ast.fromFilename context.InputFilename |> Async.RunSynchronously |> Array.head
|
||||
|
||||
let records = Ast.extractRecords ast
|
||||
|
||||
let namespaceAndRecords =
|
||||
records
|
||||
|> List.choose (fun (ns, types) ->
|
||||
types
|
||||
|> List.choose (fun typeDef ->
|
||||
match Ast.getAttribute<JsonSerializeAttribute> typeDef with
|
||||
| None -> None
|
||||
| Some attr ->
|
||||
let arg =
|
||||
match SynExpr.stripOptionalParen attr.ArgExpr with
|
||||
| SynExpr.Const (SynConst.Bool value, _) -> value
|
||||
| SynExpr.Const (SynConst.Unit, _) -> JsonSerializeAttribute.DefaultIsExtensionMethod
|
||||
| arg ->
|
||||
failwith
|
||||
$"Unrecognised argument %+A{arg} to [<%s{nameof JsonSerializeAttribute}>]. Literals are not supported. Use `true` or `false` (or unit) only."
|
||||
|
||||
let spec =
|
||||
{
|
||||
ExtensionMethods = arg
|
||||
}
|
||||
|
||||
Some (typeDef, spec)
|
||||
)
|
||||
|> function
|
||||
| [] -> None
|
||||
| ty -> Some (ns, ty)
|
||||
)
|
||||
|
||||
let opens = AstHelper.extractOpens ast
|
||||
|
||||
let modules =
|
||||
namespaceAndRecords
|
||||
|> List.collect (fun (ns, records) ->
|
||||
records
|
||||
|> List.map (fun (record, spec) ->
|
||||
let recordModule = JsonSerializeGenerator.createRecordModule ns opens spec record
|
||||
recordModule
|
||||
)
|
||||
)
|
||||
|
||||
Output.Ast modules
|
14
WoofWare.Myriad.Plugins/List.fs
Normal file
14
WoofWare.Myriad.Plugins/List.fs
Normal file
@@ -0,0 +1,14 @@
|
||||
namespace WoofWare.Myriad.Plugins
|
||||
|
||||
[<RequireQualifiedAccess>]
|
||||
module private List =
|
||||
let partitionChoice<'a, 'b> (xs : Choice<'a, 'b> list) : 'a list * 'b list =
|
||||
let xs, ys =
|
||||
(([], []), xs)
|
||||
||> List.fold (fun (xs, ys) v ->
|
||||
match v with
|
||||
| Choice1Of2 x -> x :: xs, ys
|
||||
| Choice2Of2 y -> xs, y :: ys
|
||||
)
|
||||
|
||||
List.rev xs, List.rev ys
|
@@ -1,16 +1,10 @@
|
||||
namespace WoofWare.Myriad.Plugins
|
||||
|
||||
open System
|
||||
open Fantomas.FCS.Syntax
|
||||
open Fantomas.FCS.SyntaxTrivia
|
||||
open Fantomas.FCS.Xml
|
||||
open Myriad.Core
|
||||
|
||||
/// Attribute indicating a record type to which the "Remove Options" Myriad
|
||||
/// generator should apply during build.
|
||||
type RemoveOptionsAttribute () =
|
||||
inherit Attribute ()
|
||||
|
||||
[<RequireQualifiedAccess>]
|
||||
module internal RemoveOptionsGenerator =
|
||||
open Fantomas.FCS.Text.Range
|
||||
@@ -46,14 +40,26 @@ module internal RemoveOptionsGenerator =
|
||||
)
|
||||
|
||||
// TODO: this option seems a bit odd
|
||||
let createType (xmlDoc : PreXmlDoc option) (fields : SynField list) =
|
||||
let createType
|
||||
(xmlDoc : PreXmlDoc option)
|
||||
(accessibility : SynAccess option)
|
||||
(generics : SynTyparDecls option)
|
||||
(fields : SynField list)
|
||||
=
|
||||
let fields : SynField list = fields |> List.map removeOption
|
||||
let name = Ident.Create "Short"
|
||||
|
||||
let typeDecl : SynTypeDefn =
|
||||
match xmlDoc with
|
||||
| None -> AstHelper.defineRecordType (name, fields, None, None)
|
||||
| Some xmlDoc -> AstHelper.defineRecordType (name, fields, None, Some xmlDoc)
|
||||
let record =
|
||||
{
|
||||
Name = name
|
||||
Fields = fields
|
||||
Members = None
|
||||
XmlDoc = xmlDoc
|
||||
Generics = generics
|
||||
Accessibility = accessibility
|
||||
}
|
||||
|
||||
let typeDecl = AstHelper.defineRecordType record
|
||||
|
||||
SynModuleDecl.Types ([ typeDecl ], range0)
|
||||
|
||||
@@ -114,7 +120,7 @@ module internal RemoveOptionsGenerator =
|
||||
|
||||
(SynLongIdent.CreateFromLongIdent [ id ], true), Some body
|
||||
)
|
||||
|> AstHelper.constructRecord
|
||||
|> AstHelper.instantiateRecord
|
||||
|
||||
let pattern =
|
||||
SynPat.LongIdent (
|
||||
@@ -150,15 +156,15 @@ module internal RemoveOptionsGenerator =
|
||||
let (SynTypeDefn (synComponentInfo, synTypeDefnRepr, _members, _implicitCtor, _, _)) =
|
||||
typeDefn
|
||||
|
||||
let (SynComponentInfo (_attributes, _typeParams, _constraints, recordId, doc, _preferPostfix, _access, _)) =
|
||||
let (SynComponentInfo (_attributes, typeParams, _constraints, recordId, doc, _preferPostfix, _access, _)) =
|
||||
synComponentInfo
|
||||
|
||||
match synTypeDefnRepr with
|
||||
| SynTypeDefnRepr.Simple (SynTypeDefnSimpleRepr.Record (_accessibility, recordFields, _recordRange), _) ->
|
||||
| SynTypeDefnRepr.Simple (SynTypeDefnSimpleRepr.Record (accessibility, recordFields, _recordRange), _) ->
|
||||
|
||||
let decls =
|
||||
[
|
||||
createType (Some doc) recordFields
|
||||
createType (Some doc) accessibility typeParams recordFields
|
||||
createMaker [ Ident.Create "Short" ] recordId recordFields
|
||||
]
|
||||
|
||||
|
@@ -1,12 +1,12 @@
|
||||
WoofWare.Myriad.Plugins.HttpClientAttribute inherit System.Attribute
|
||||
WoofWare.Myriad.Plugins.HttpClientAttribute..ctor [constructor]: unit
|
||||
WoofWare.Myriad.Plugins.CreateCatamorphismGenerator inherit obj, implements Myriad.Core.IMyriadGenerator
|
||||
WoofWare.Myriad.Plugins.CreateCatamorphismGenerator..ctor [constructor]: unit
|
||||
WoofWare.Myriad.Plugins.HttpClientGenerator inherit obj, implements Myriad.Core.IMyriadGenerator
|
||||
WoofWare.Myriad.Plugins.HttpClientGenerator..ctor [constructor]: unit
|
||||
WoofWare.Myriad.Plugins.JsonParseAttribute inherit System.Attribute
|
||||
WoofWare.Myriad.Plugins.JsonParseAttribute..ctor [constructor]: unit
|
||||
WoofWare.Myriad.Plugins.InterfaceMockGenerator inherit obj, implements Myriad.Core.IMyriadGenerator
|
||||
WoofWare.Myriad.Plugins.InterfaceMockGenerator..ctor [constructor]: unit
|
||||
WoofWare.Myriad.Plugins.JsonParseGenerator inherit obj, implements Myriad.Core.IMyriadGenerator
|
||||
WoofWare.Myriad.Plugins.JsonParseGenerator..ctor [constructor]: unit
|
||||
WoofWare.Myriad.Plugins.RemoveOptionsAttribute inherit System.Attribute
|
||||
WoofWare.Myriad.Plugins.RemoveOptionsAttribute..ctor [constructor]: unit
|
||||
WoofWare.Myriad.Plugins.JsonSerializeGenerator inherit obj, implements Myriad.Core.IMyriadGenerator
|
||||
WoofWare.Myriad.Plugins.JsonSerializeGenerator..ctor [constructor]: unit
|
||||
WoofWare.Myriad.Plugins.RemoveOptionsGenerator inherit obj, implements Myriad.Core.IMyriadGenerator
|
||||
WoofWare.Myriad.Plugins.RemoveOptionsGenerator..ctor [constructor]: unit
|
@@ -20,3 +20,12 @@ module internal SynAttribute =
|
||||
AppliesToGetterAndSetter = false
|
||||
Range = range0
|
||||
}
|
||||
|
||||
let internal autoOpen : SynAttribute =
|
||||
{
|
||||
TypeName = SynLongIdent.CreateString "AutoOpen"
|
||||
ArgExpr = SynExpr.CreateConst SynConst.Unit
|
||||
Target = None
|
||||
AppliesToGetterAndSetter = false
|
||||
Range = range0
|
||||
}
|
||||
|
@@ -102,9 +102,9 @@ module internal SynExpr =
|
||||
b
|
||||
)
|
||||
|
||||
let stripOptionalParen (expr : SynExpr) : SynExpr =
|
||||
let rec stripOptionalParen (expr : SynExpr) : SynExpr =
|
||||
match expr with
|
||||
| SynExpr.Paren (expr, _, _, _) -> expr
|
||||
| SynExpr.Paren (expr, _, _, _) -> stripOptionalParen expr
|
||||
| expr -> expr
|
||||
|
||||
/// Given e.g. "byte", returns "System.Byte".
|
||||
@@ -180,7 +180,7 @@ module internal SynExpr =
|
||||
SynExpr.CreateApp (SynExpr.CreateIdent (Ident.Create "reraise"), SynExpr.CreateConst SynConst.Unit)
|
||||
|
||||
/// {body} |> fun a -> Async.StartAsTask (a, ?cancellationToken=ct)
|
||||
let startAsTask (body : SynExpr) =
|
||||
let startAsTask (ct : SynLongIdent) (body : SynExpr) =
|
||||
let lambda =
|
||||
SynExpr.CreateApp (
|
||||
SynExpr.CreateLongIdent (SynLongIdent.Create [ "Async" ; "StartAsTask" ]),
|
||||
@@ -189,7 +189,7 @@ module internal SynExpr =
|
||||
SynExpr.CreateLongIdent (SynLongIdent.CreateString "a")
|
||||
equals
|
||||
(SynExpr.LongIdent (true, SynLongIdent.CreateString "cancellationToken", None, range0))
|
||||
(SynExpr.CreateLongIdent (SynLongIdent.CreateString "ct"))
|
||||
(SynExpr.CreateLongIdent ct)
|
||||
]
|
||||
)
|
||||
|> createLambda "a"
|
||||
@@ -240,7 +240,7 @@ module internal SynExpr =
|
||||
SynExprLetOrUseTrivia.InKeyword = None
|
||||
}
|
||||
)
|
||||
| Do body -> SynExpr.Do (body, range0)
|
||||
| Do body -> SynExpr.CreateSequential [ SynExpr.Do (body, range0) ; state ]
|
||||
)
|
||||
|
||||
SynExpr.CreateApp (
|
||||
@@ -252,3 +252,62 @@ module internal SynExpr =
|
||||
let awaitTask (expr : SynExpr) : SynExpr =
|
||||
expr
|
||||
|> pipeThroughFunction (SynExpr.CreateLongIdent (SynLongIdent.Create [ "Async" ; "AwaitTask" ]))
|
||||
|
||||
/// {ident}.ToString ()
|
||||
/// with special casing for some types like DateTime
|
||||
let toString (ty : SynType) (ident : SynExpr) =
|
||||
match ty with
|
||||
| DateOnly -> ident |> callMethodArg "ToString" (SynExpr.CreateConstString "yyyy-MM-dd")
|
||||
| DateTime ->
|
||||
ident
|
||||
|> callMethodArg "ToString" (SynExpr.CreateConstString "yyyy-MM-ddTHH:mm:ss")
|
||||
| _ -> callMethod "ToString" ident
|
||||
|
||||
let upcast' (ty : SynType) (e : SynExpr) = SynExpr.Upcast (e, ty, range0)
|
||||
|
||||
let synBindingTriviaZero (isMember : bool) =
|
||||
{
|
||||
SynBindingTrivia.EqualsRange = Some range0
|
||||
InlineKeyword = None
|
||||
LeadingKeyword =
|
||||
if isMember then
|
||||
SynLeadingKeyword.Member range0
|
||||
else
|
||||
SynLeadingKeyword.Let range0
|
||||
}
|
||||
|
||||
/// {ident} - {rhs}
|
||||
let minus (ident : SynLongIdent) (rhs : SynExpr) : SynExpr =
|
||||
SynExpr.CreateApp (
|
||||
SynExpr.CreateAppInfix (
|
||||
SynExpr.CreateLongIdent (
|
||||
SynLongIdent.SynLongIdent (
|
||||
[ Ident.Create "op_Subtraction" ],
|
||||
[],
|
||||
[ Some (IdentTrivia.OriginalNotation "-") ]
|
||||
)
|
||||
),
|
||||
SynExpr.CreateLongIdent ident
|
||||
),
|
||||
rhs
|
||||
)
|
||||
|
||||
/// {ident} - {n}
|
||||
let minusN (ident : SynLongIdent) (n : int) : SynExpr =
|
||||
minus ident (SynExpr.CreateConst (SynConst.Int32 n))
|
||||
|
||||
/// {y} > {x}
|
||||
let greaterThan (x : SynExpr) (y : SynExpr) : SynExpr =
|
||||
SynExpr.CreateApp (
|
||||
SynExpr.CreateAppInfix (
|
||||
SynExpr.CreateLongIdent (
|
||||
SynLongIdent.SynLongIdent (
|
||||
[ Ident.Create "op_GreaterThan" ],
|
||||
[],
|
||||
[ Some (IdentTrivia.OriginalNotation ">") ]
|
||||
)
|
||||
),
|
||||
y
|
||||
),
|
||||
x
|
||||
)
|
||||
|
10
WoofWare.Myriad.Plugins/SynType.fs
Normal file
10
WoofWare.Myriad.Plugins/SynType.fs
Normal file
@@ -0,0 +1,10 @@
|
||||
namespace WoofWare.Myriad.Plugins
|
||||
|
||||
open Fantomas.FCS.Syntax
|
||||
|
||||
[<RequireQualifiedAccess>]
|
||||
module internal SynType =
|
||||
let rec stripOptionalParen (ty : SynType) : SynType =
|
||||
match ty with
|
||||
| SynType.Paren (ty, _) -> stripOptionalParen ty
|
||||
| ty -> ty
|
@@ -18,18 +18,24 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Myriad.Core" Version="0.8.3"/>
|
||||
<PackageReference Include="Myriad.Core" Version="0.8.3" PrivateAssets="all"/>
|
||||
<!-- the lowest version allowed by Myriad.Core -->
|
||||
<PackageReference Update="FSharp.Core" Version="6.0.1"/>
|
||||
<PackageReference Update="FSharp.Core" Version="6.0.1" PrivateAssets="all"/>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Include="List.fs"/>
|
||||
<Compile Include="Ident.fs" />
|
||||
<Compile Include="AstHelper.fs"/>
|
||||
<Compile Include="SynExpr.fs"/>
|
||||
<Compile Include="SynType.fs"/>
|
||||
<Compile Include="SynAttribute.fs"/>
|
||||
<Compile Include="RemoveOptionsGenerator.fs"/>
|
||||
<Compile Include="InterfaceMockGenerator.fs"/>
|
||||
<Compile Include="JsonSerializeGenerator.fs"/>
|
||||
<Compile Include="JsonParseGenerator.fs"/>
|
||||
<Compile Include="HttpClientGenerator.fs"/>
|
||||
<Compile Include="CataGenerator.fs" />
|
||||
<EmbeddedResource Include="version.json"/>
|
||||
<EmbeddedResource Include="SurfaceBaseline.txt"/>
|
||||
<None Include="..\README.md">
|
||||
@@ -42,4 +48,11 @@
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\WoofWare.Myriad.Plugins.Attributes\WoofWare.Myriad.Plugins.Attributes.fsproj"/>
|
||||
<!-- NuGet is such a clown package manager! Get the DLLs into the Nupkg artefact, I have no idea why this is needed,
|
||||
but without this line, we don't get any dependency at all packaged into the resulting artefact. -->
|
||||
<None Include="$(OutputPath)\WoofWare.Myriad.Plugins.Attributes.dll" Pack="true" PackagePath="lib\$(TargetFramework)"/>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"version": "1.1",
|
||||
"version": "2.1",
|
||||
"publicReleaseRefSpec": [
|
||||
"^refs/heads/main$"
|
||||
],
|
||||
|
@@ -4,7 +4,11 @@ Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "ConsumePlugin", "ConsumePlu
|
||||
EndProject
|
||||
Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "WoofWare.Myriad.Plugins", "WoofWare.Myriad.Plugins\WoofWare.Myriad.Plugins.fsproj", "{DB86C53B-4090-4791-884B-024C5759855F}"
|
||||
EndProject
|
||||
Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "MyriadPlugin.Test", "MyriadPlugin.Test\MyriadPlugin.Test.fsproj", "{13370CA7-2A80-4B4D-8DEB-F1AA77F206C4}"
|
||||
Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "WoofWare.Myriad.Plugins.Test", "WoofWare.Myriad.Plugins.Test\WoofWare.Myriad.Plugins.Test.fsproj", "{EBFFA5D3-7F74-4824-8795-B6194E6FE0CB}"
|
||||
EndProject
|
||||
Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "WoofWare.Myriad.Plugins.Attributes", "WoofWare.Myriad.Plugins.Attributes\WoofWare.Myriad.Plugins.Attributes.fsproj", "{17548737-9BAB-4B1E-B680-76D47C343AAC}"
|
||||
EndProject
|
||||
Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "WoofWare.Myriad.Plugins.Attributes.Test", "WoofWare.Myriad.Plugins.Attributes\Test\WoofWare.Myriad.Plugins.Attributes.Test.fsproj", "{26DC0C94-85F2-45B4-8FA1-1B27201F7AFB}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
@@ -20,9 +24,17 @@ Global
|
||||
{DB86C53B-4090-4791-884B-024C5759855F}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{DB86C53B-4090-4791-884B-024C5759855F}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{DB86C53B-4090-4791-884B-024C5759855F}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{13370CA7-2A80-4B4D-8DEB-F1AA77F206C4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{13370CA7-2A80-4B4D-8DEB-F1AA77F206C4}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{13370CA7-2A80-4B4D-8DEB-F1AA77F206C4}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{13370CA7-2A80-4B4D-8DEB-F1AA77F206C4}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{EBFFA5D3-7F74-4824-8795-B6194E6FE0CB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{EBFFA5D3-7F74-4824-8795-B6194E6FE0CB}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{EBFFA5D3-7F74-4824-8795-B6194E6FE0CB}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{EBFFA5D3-7F74-4824-8795-B6194E6FE0CB}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{17548737-9BAB-4B1E-B680-76D47C343AAC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{17548737-9BAB-4B1E-B680-76D47C343AAC}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{17548737-9BAB-4B1E-B680-76D47C343AAC}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{17548737-9BAB-4B1E-B680-76D47C343AAC}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{26DC0C94-85F2-45B4-8FA1-1B27201F7AFB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{26DC0C94-85F2-45B4-8FA1-1B27201F7AFB}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{26DC0C94-85F2-45B4-8FA1-1B27201F7AFB}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{26DC0C94-85F2-45B4-8FA1-1B27201F7AFB}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
|
@@ -10,7 +10,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageDownload Include="G-Research.FSharp.Analyzers" Version="[0.6.0]" />
|
||||
<PackageDownload Include="G-Research.FSharp.Analyzers" Version="[0.9.3]" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
11
flake.lock
generated
11
flake.lock
generated
@@ -20,17 +20,18 @@
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1703134684,
|
||||
"narHash": "sha256-SQmng1EnBFLzS7WSRyPM9HgmZP2kLJcPAz+Ug/nug6o=",
|
||||
"lastModified": 1706367331,
|
||||
"narHash": "sha256-AqgkGHRrI6h/8FWuVbnkfFmXr4Bqsr4fV23aISqj/xg=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "d6863cbcbbb80e71cecfc03356db1cda38919523",
|
||||
"rev": "160b762eda6d139ac10ae081f8f78d640dd523eb",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"id": "nixpkgs",
|
||||
"owner": "NixOS",
|
||||
"ref": "nixpkgs-unstable",
|
||||
"type": "indirect"
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
|
10
flake.nix
10
flake.nix
@@ -3,7 +3,7 @@
|
||||
|
||||
inputs = {
|
||||
flake-utils.url = "github:numtide/flake-utils";
|
||||
nixpkgs.url = "nixpkgs/nixpkgs-unstable";
|
||||
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
|
||||
};
|
||||
|
||||
outputs = {
|
||||
@@ -44,8 +44,8 @@
|
||||
};
|
||||
in {
|
||||
packages = {
|
||||
fantomas = dotnetTool null "fantomas" (builtins.fromJSON (builtins.readFile ./.config/dotnet-tools.json)).tools.fantomas.version "sha256-Jmo7s8JMdQ8SxvNvPnryfE7n24mIgKi5cbgNwcQw3yU=";
|
||||
fsharp-analyzers = dotnetTool "FSharp.Analyzers.Cli" "fsharp-analyzers" (builtins.fromJSON (builtins.readFile ./.config/dotnet-tools.json)).tools.fsharp-analyzers.version "sha256-wDS7aE4VI718iwU8xUm0aCOYIcFpMuqWu9+H5d+8XAA=";
|
||||
fantomas = dotnetTool null "fantomas" (builtins.fromJSON (builtins.readFile ./.config/dotnet-tools.json)).tools.fantomas.version (builtins.head (builtins.filter (elem: elem.pname == "fantomas") ((import ./nix/deps.nix) {fetchNuGet = x: x;}))).sha256;
|
||||
fsharp-analyzers = dotnetTool "FSharp.Analyzers.Cli" "fsharp-analyzers" (builtins.fromJSON (builtins.readFile ./.config/dotnet-tools.json)).tools.fsharp-analyzers.version (builtins.head (builtins.filter (elem: elem.pname == "fsharp-analyzers") ((import ./nix/deps.nix) {fetchNuGet = x: x;}))).sha256;
|
||||
fetchDeps = let
|
||||
flags = [];
|
||||
runtimeIds = ["win-x64"] ++ map (system: pkgs.dotnetCorePackages.systemToDotnetRid system) dotnet-sdk.meta.platforms;
|
||||
@@ -54,8 +54,8 @@
|
||||
src = ./nix/fetchDeps.sh;
|
||||
pname = pname;
|
||||
binPath = pkgs.lib.makeBinPath [pkgs.coreutils dotnet-sdk (pkgs.nuget-to-nix.override {inherit dotnet-sdk;})];
|
||||
projectFiles = toString ["./WoofWare.Myriad.Plugins/WoofWare.Myriad.Plugins.fsproj" "./ConsumePlugin/ConsumePlugin.fsproj"];
|
||||
testProjectFiles = ["./WoofWare.Myriad.Plugins.Test/WoofWare.Myriad.Plugins.Test.fsproj"];
|
||||
projectFiles = toString ["./WoofWare.Myriad.Plugins/WoofWare.Myriad.Plugins.fsproj" "./ConsumePlugin/ConsumePlugin.fsproj" "./WoofWare.Myriad.Plugins.Attributes/WoofWare.Myriad.Plugins.Attributes.fsproj"];
|
||||
testProjectFiles = ["./WoofWare.Myriad.Plugins.Test/WoofWare.Myriad.Plugins.Test.fsproj" "./WoofWare.Myriad.Plugins.Attributes/Test/Woofware.Myriad.Plugins.Attributes.Test.fsproj"];
|
||||
rids = pkgs.lib.concatStringsSep "\" \"" runtimeIds;
|
||||
packages = dotnet-sdk.packages;
|
||||
storeSrc = pkgs.srcOnly {
|
||||
|
245
nix/deps.nix
245
nix/deps.nix
@@ -3,23 +3,18 @@
|
||||
{fetchNuGet}: [
|
||||
(fetchNuGet {
|
||||
pname = "fsharp-analyzers";
|
||||
version = "0.22.0";
|
||||
sha256 = "sha256-wDS7aE4VI718iwU8xUm0aCOYIcFpMuqWu9+H5d+8XAA=";
|
||||
version = "0.25.0";
|
||||
sha256 = "sha256-njfJYi40jNvrD+mgu9LtQw2Omh8P1SSDThesozH0KQY=";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "fantomas";
|
||||
version = "6.3.0-alpha-005";
|
||||
sha256 = "sha256-Jmo7s8JMdQ8SxvNvPnryfE7n24mIgKi5cbgNwcQw3yU=";
|
||||
version = "6.3.0-alpha-008";
|
||||
sha256 = "sha256-rI/4upuj8JBy2C9gl2lwI/JXmBD7UHKxCoSpd+bstRw=";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "ApiSurface";
|
||||
version = "4.0.25";
|
||||
sha256 = "0zjq8an9cr0l7wxdmm9n9s3iyq5m0zl4x0h0wmy5cz7am8y15qc4";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "coverlet.collector";
|
||||
version = "3.2.0";
|
||||
sha256 = "1qxpv8v10p5wn162lzdm193gdl6c5f81zadj8h889dprlnj3g8yr";
|
||||
version = "4.0.33";
|
||||
sha256 = "0mmsa5gxfd3bbgacip0c1hljwd958zcx1012qdh033sx6nfz3v36";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "Fantomas.Core";
|
||||
@@ -36,6 +31,11 @@
|
||||
version = "2.16.6";
|
||||
sha256 = "176rwky6b5rk8dzldiz4068p7m9c5y9ygzbhadrs14jkl94pc56n";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "FSharp.Core";
|
||||
version = "4.3.4";
|
||||
sha256 = "1sg6i4q5nwyzh769g76f6c16876nvdpn83adqjr2y9x6xsiv5p5j";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "FSharp.Core";
|
||||
version = "6.0.1";
|
||||
@@ -43,198 +43,198 @@
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "FSharp.Core";
|
||||
version = "8.0.100";
|
||||
sha256 = "06z3vg8yj7i83x6gmnzl2lka1bp4hzc07h6mrydpilxswnmy2a0l";
|
||||
version = "8.0.101";
|
||||
sha256 = "0prgcnki6s0rlrfbarrcv50w1bbhaalsyhhw5gsnjs2is7qrjbii";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "FsUnit";
|
||||
version = "5.6.1";
|
||||
sha256 = "1zffn9dm2c44v8qjzwfg6y3psydiv2bn3n305rf7mc57cmm4ygv3";
|
||||
version = "6.0.0";
|
||||
sha256 = "18q3p0z155znwj1l0qq3vq9nh9wl2i4mlfx4pmrnia4czr0xdkmb";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "Microsoft.AspNetCore.App.Ref";
|
||||
version = "6.0.25";
|
||||
sha256 = "1vrmqn5j6ibwkqasbf7x7n4w5jdclnz3giymiwvym2wa0y5zc59q";
|
||||
version = "6.0.26";
|
||||
sha256 = "1d8nkz24vsm0iy2xm8y5ak2q1w1p99dxyz0y26acs6sfk2na0vm6";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "Microsoft.AspNetCore.App.Ref";
|
||||
version = "8.0.0";
|
||||
sha256 = "0k304yhpm92c46a1fscbzlgvdbhrm9vlbpyfgwp3cafz4f7z7a5y";
|
||||
version = "8.0.1";
|
||||
sha256 = "0yaaiqq7mi6sclyrb1v0fyncanbx0ifmnnhv9whynqj8439jsdwh";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "Microsoft.AspNetCore.App.Runtime.linux-arm64";
|
||||
version = "6.0.25";
|
||||
sha256 = "0mgcs4si7mwd0f555s1vg17pf4nqfaijd1pci359l1pgrmv70rrg";
|
||||
version = "6.0.26";
|
||||
sha256 = "1za8lc52m4z54d68wd64c2nhzy05g3gx171k5cdlx73fbymiys9z";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "Microsoft.AspNetCore.App.Runtime.linux-arm64";
|
||||
version = "8.0.0";
|
||||
sha256 = "05y1xb5fw8lzvb4si77a5qwfwfz1855crqbphrwky6x9llivbhkx";
|
||||
version = "8.0.1";
|
||||
sha256 = "0dsdgqg7566qximmjfza4x9if3icy4kskq698ddj5apdia88h2mw";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "Microsoft.AspNetCore.App.Runtime.linux-x64";
|
||||
version = "6.0.25";
|
||||
sha256 = "0wvzhqhlmlbnpa18qp8m3wcrlcgj3ckvp3iv2n7g8vb60c3238aq";
|
||||
version = "6.0.26";
|
||||
sha256 = "1zpbmz6z8758gwywzg0bac8kx9x39sxxc9j4a4r2jl74l9ssw4vm";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "Microsoft.AspNetCore.App.Runtime.linux-x64";
|
||||
version = "8.0.0";
|
||||
sha256 = "18zdbcb2bn7wy1dp14z5jyqiiwr9rkad1lcb158r5ikjfq1rg5iw";
|
||||
version = "8.0.1";
|
||||
sha256 = "1gjz379y61ag9whi78qxx09bwkwcznkx2mzypgycibxk61g11da1";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "Microsoft.AspNetCore.App.Runtime.osx-arm64";
|
||||
version = "6.0.25";
|
||||
sha256 = "1pywgvb8ck1d5aadmijd5s3z6yclchd9pa6dsahijmm55ibplx36";
|
||||
version = "6.0.26";
|
||||
sha256 = "1i8ydlwjzk7j0mzvn0rpljxfp1h50zwaqalnyvfxai1fwgigzgw5";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "Microsoft.AspNetCore.App.Runtime.osx-arm64";
|
||||
version = "8.0.0";
|
||||
sha256 = "1nbxzmj6cnccylxis67c54c0ik38ma4rwdvgg6sxd6r04219maqm";
|
||||
version = "8.0.1";
|
||||
sha256 = "0w3mrs4zdl9mfanl1j81759xwwrzmicsjxn6yfxv5yrxbxzq695n";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "Microsoft.AspNetCore.App.Runtime.osx-x64";
|
||||
version = "6.0.25";
|
||||
sha256 = "1zlf0w7i6r02719dv3nw4jy14sa0rs53i89an5alz5qmywdy3f1d";
|
||||
version = "6.0.26";
|
||||
sha256 = "02src68hd3213sd1a2ms1my7i92knfmdxclvv90il9cky2zsq8kw";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "Microsoft.AspNetCore.App.Runtime.osx-x64";
|
||||
version = "8.0.0";
|
||||
sha256 = "1wqkbjd1ywv9w397l7rsb89mijc5n0hv7jq9h09xfz6wn9qsp152";
|
||||
version = "8.0.1";
|
||||
sha256 = "0a9aljr4fy4haq6ndz2y723liv5hbfpss1rn45s88nmgcp27m15m";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "Microsoft.AspNetCore.App.Runtime.win-x64";
|
||||
version = "6.0.25";
|
||||
sha256 = "1fbsnm4056cpd4avgpi5sq05m1yd9k4x229ckxpr4q7yc94sncwy";
|
||||
version = "6.0.26";
|
||||
sha256 = "1gxlmfdkfzmhw9pac5jiv674nn6i1zymcp2hj81irjwhhjk01mf5";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "Microsoft.AspNetCore.App.Runtime.win-x64";
|
||||
version = "8.0.0";
|
||||
sha256 = "08vlmswmiyp2nxlr9d77716hk7kz7h9x5bl8wh76xzbj5id1xlb2";
|
||||
version = "8.0.1";
|
||||
sha256 = "01kzndyqmsvcq49i2jrv7ymfp0l71yxfylv1cy3nhkdbprqz8ipx";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "Microsoft.Build.Tasks.Git";
|
||||
version = "1.1.1";
|
||||
sha256 = "1bb5p4zlnfn88skkvymxfsn0jybqncl4356hwnic9jxdq2d4fz1w";
|
||||
version = "8.0.0";
|
||||
sha256 = "0055f69q3hbagqp8gl3nk0vfn4qyqyxsxyy7pd0g7wm3z28byzmx";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "Microsoft.CodeCoverage";
|
||||
version = "17.5.0";
|
||||
sha256 = "0briw00gb5bz9k9kx00p6ghq47w501db7gb6ig5zzmz9hb8lw4a4";
|
||||
version = "17.9.0";
|
||||
sha256 = "1gljgi69k0fz8vy8bn6xlyxabj6q4vls2zza9wz7ng6ix3irm89r";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "Microsoft.NET.Test.Sdk";
|
||||
version = "17.5.0";
|
||||
sha256 = "00gz2i8kx4mlq1ywj3imvf7wc6qzh0bsnynhw06z0mgyha1a21jy";
|
||||
version = "17.9.0";
|
||||
sha256 = "1lls1fly2gr1n9n1xyl9k33l2v4pwfmylyzkq8v4v5ldnwkl1zdb";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "Microsoft.NETCore.App.Host.linux-arm64";
|
||||
version = "6.0.25";
|
||||
sha256 = "052388yjivzkfllkss0nljbzmjx787jqdjsbb6ls855sp6wh9xfd";
|
||||
version = "6.0.26";
|
||||
sha256 = "19y6c6v20bgf7x7rrh4rx9y7s5fy8vp5m4j9b6gi1wp4rpb5mza4";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "Microsoft.NETCore.App.Host.linux-arm64";
|
||||
version = "8.0.0";
|
||||
sha256 = "0bpg3v9dnalz7yh7lsgriw9rnm9jx37mqhhvf7snznb3sfk7rgwb";
|
||||
version = "8.0.1";
|
||||
sha256 = "0dhpdlcdz7adcfh9w01fc867051m35fqaxnvj3fqvqhgcm2n3143";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "Microsoft.NETCore.App.Host.linux-x64";
|
||||
version = "6.0.25";
|
||||
sha256 = "103xy6kncjwbbchfnpqvsjpjy92x3dralcg9pw939jp0dwggwarz";
|
||||
version = "6.0.26";
|
||||
sha256 = "0p7hhidaa3mnyiwnsijwy8578v843x8hh99255s69qwwyld6falv";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "Microsoft.NETCore.App.Host.linux-x64";
|
||||
version = "8.0.0";
|
||||
sha256 = "1c7l68bm05d94x5wk1y33mnd4v8m196vyprgrzqnh94yrqy6fkf7";
|
||||
version = "8.0.1";
|
||||
sha256 = "1aw6mc7zcmzs1grxz2wa9cw9kfj8pz7zpj417xnp1a9n4ix1bxgr";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "Microsoft.NETCore.App.Host.osx-arm64";
|
||||
version = "6.0.25";
|
||||
sha256 = "13m14pdx5xfxky07xgxf6hjd7g9l4k6k40wvp9znhvn27pa0wdxv";
|
||||
version = "6.0.26";
|
||||
sha256 = "1mq11xsv9g1vsasp6k80y7xlvwi9hrpk5dgm773fvy8538s01gfv";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "Microsoft.NETCore.App.Host.osx-arm64";
|
||||
version = "8.0.0";
|
||||
sha256 = "1hdv825s964vfcgnk94pzhgxnj948f1vdj423jjxpkppcy30fl0m";
|
||||
version = "8.0.1";
|
||||
sha256 = "1dzg3prng9zfdzz7gcgywjdbwzhwm85j89z0jahynxx4q2dra4b9";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "Microsoft.NETCore.App.Host.osx-x64";
|
||||
version = "6.0.25";
|
||||
sha256 = "132pgjhv42mqzx4007sd59bkds0fwsv5xaz07y2yffbn3lzr228k";
|
||||
version = "6.0.26";
|
||||
sha256 = "1chac9b4424ihrrnlzvc7qz6j4ymfjyv4kzyazzzw19yhymdkh2s";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "Microsoft.NETCore.App.Host.osx-x64";
|
||||
version = "8.0.0";
|
||||
sha256 = "0jmzf58vv45j0hqlxq8yalpjwi328vp2mjr3h0pdg0qr143iivnr";
|
||||
version = "8.0.1";
|
||||
sha256 = "010f8wn15s2kv7yyzgys3pv9i1mxw20hpv1ig2zhybjxs8lpj8jj";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "Microsoft.NETCore.App.Host.win-x64";
|
||||
version = "6.0.25";
|
||||
sha256 = "039433rm4w37h9qri11v3lrpddpz7zcly9kq8vmk6w1ixzlqwf01";
|
||||
version = "6.0.26";
|
||||
sha256 = "0i7g9fsqjnbh9rc6807m57r2idg5pkcw6xjfwhnxkcpgqm96258v";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "Microsoft.NETCore.App.Host.win-x64";
|
||||
version = "8.0.0";
|
||||
sha256 = "1n8yr13df2f6jhxpfazs6rxahfqm18fhjvfm16g5d60c3za1hwnk";
|
||||
version = "8.0.1";
|
||||
sha256 = "1ssj1cyam3nfidm8q82kvh4i3fzm2lzb3bxw6ck09hwhvwh909z4";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "Microsoft.NETCore.App.Ref";
|
||||
version = "6.0.25";
|
||||
sha256 = "0jfhmfxpx1h4f3axgf60gc8d4cnlvbb853400kag6nk0875hr0x1";
|
||||
version = "6.0.26";
|
||||
sha256 = "12gb52dhg5h9hgnyqh1zgj2w46paxv2pfh33pphl9ajhrdr7hlsb";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "Microsoft.NETCore.App.Ref";
|
||||
version = "8.0.0";
|
||||
sha256 = "0hyvbh86433764qqqhw9i7ga0ax7bbdmzh77jw58pq0ggm41cff9";
|
||||
version = "8.0.1";
|
||||
sha256 = "02r4jg4ha0qksix9v6s3cpmvavmz54gkawkxy9bvknw5ynxhhl1l";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "Microsoft.NETCore.App.Runtime.linux-arm64";
|
||||
version = "6.0.25";
|
||||
sha256 = "0jpcmva1l8z36r4phz055l7fz9s6z8pv8pqc4ia69mhhgvr0ks7y";
|
||||
version = "6.0.26";
|
||||
sha256 = "164hfrwqz5dxcbb441lridk4mzcqmarb0b7ckgvqhsvpawyjw88v";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "Microsoft.NETCore.App.Runtime.linux-arm64";
|
||||
version = "8.0.0";
|
||||
sha256 = "0gwqmkmr7jy3sjh9gha82amlry41gp8nwswy2iqfw54f28db63n7";
|
||||
version = "8.0.1";
|
||||
sha256 = "0353whnjgz3sqhzsfrviad3a3db4pk7hl7m4wwppv5mqdg9i9ri5";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "Microsoft.NETCore.App.Runtime.linux-x64";
|
||||
version = "6.0.25";
|
||||
sha256 = "012jml0bqxbspahf1j4bvvd91pz85hsbcyhq00gxczcazhxpkhz4";
|
||||
version = "6.0.26";
|
||||
sha256 = "0islayddpnflviqpbq4djc4f3v9nhsa2y76k5x6il3csq5vdw2hq";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "Microsoft.NETCore.App.Runtime.linux-x64";
|
||||
version = "8.0.0";
|
||||
sha256 = "042cjvnwrrjs3mw5q8q5kinh0cwkks33i3n1vyifaid2jbr3wlc0";
|
||||
version = "8.0.1";
|
||||
sha256 = "1g5b30f4l8a1zjjr3b8pk9mcqxkxqwa86362f84646xaj4iw3a4d";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "Microsoft.NETCore.App.Runtime.osx-arm64";
|
||||
version = "6.0.25";
|
||||
sha256 = "0wgwxpyy1n550sw7npjg69zpxknwn0ay30m2qybvqb5mj857qzxi";
|
||||
version = "6.0.26";
|
||||
sha256 = "1acn5zw1pxzmcg3c0pbf9hal36fbdh9mvbsiwra7simrk7hzqpdc";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "Microsoft.NETCore.App.Runtime.osx-arm64";
|
||||
version = "8.0.0";
|
||||
sha256 = "06ndp4wh1cap01dql3nixka4g56bf6ipmqys7xaxvg4xisf79x8d";
|
||||
version = "8.0.1";
|
||||
sha256 = "0cdrpdaq5sl3602anfx1p0z0ncx2sjjvl6mgsd6y38g47n7f95jc";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "Microsoft.NETCore.App.Runtime.osx-x64";
|
||||
version = "6.0.25";
|
||||
sha256 = "08vr7c5bg5x3w35l54z1azif7ysfc2yiyz50ip1dl0mpqywvlswr";
|
||||
version = "6.0.26";
|
||||
sha256 = "00f9l9dkdz0zv5csaw8fkm6s8ckrj5n9k3ygz12daa22l3bcn6ii";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "Microsoft.NETCore.App.Runtime.osx-x64";
|
||||
version = "8.0.0";
|
||||
sha256 = "1kh5bnaf6h9mr4swcalrp304625frjiw6mlz1052rxwzsdq98a96";
|
||||
version = "8.0.1";
|
||||
sha256 = "1fk1flqp6ji0l4c2gvh83ykndpx7a2nkkgrgkgql3c75j1k2v1s9";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "Microsoft.NETCore.App.Runtime.win-x64";
|
||||
version = "6.0.25";
|
||||
sha256 = "03snpmx204xvc9668riisvvdjjgdqhwj7yjp85w5lh8j8ygrqkif";
|
||||
version = "6.0.26";
|
||||
sha256 = "0i2p356phfc5y6qnr3vyrzjfi1mrbwfb6g85k4q37bbyxjfp7zl9";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "Microsoft.NETCore.App.Runtime.win-x64";
|
||||
version = "8.0.0";
|
||||
sha256 = "054icf5jjnwnswrnv1r05x3pfjvacbz6g3dj8caar1zp53k49rkk";
|
||||
version = "8.0.1";
|
||||
sha256 = "198576cdkl72xs29zznff9ls763p8pfr0zji7b74dqxd5ga0s3bd";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "Microsoft.NETCore.Platforms";
|
||||
@@ -258,23 +258,23 @@
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "Microsoft.SourceLink.Common";
|
||||
version = "1.1.1";
|
||||
sha256 = "0xkdqs7az2cprar7jzjlgjpd64l6f8ixcmwmpkdm03fyb4s5m0bg";
|
||||
version = "8.0.0";
|
||||
sha256 = "0xrr8yd34ij7dqnyddkp2awfmf9qn3c89xmw2f3npaa4wnajmx81";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "Microsoft.SourceLink.GitHub";
|
||||
version = "1.1.1";
|
||||
sha256 = "099y35f2npvva3jk1zp8hn0vb9pwm2l0ivjasdly6y2idv53s5yy";
|
||||
version = "8.0.0";
|
||||
sha256 = "1gdx7n45wwia3yvang3ls92sk3wrymqcx9p349j8wba2lyjf9m44";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "Microsoft.TestPlatform.ObjectModel";
|
||||
version = "17.5.0";
|
||||
sha256 = "0qkjyf3ky6xpjg5is2sdsawm99ka7fzgid2bvpglwmmawqgm8gls";
|
||||
version = "17.9.0";
|
||||
sha256 = "1kgsl9w9fganbm9wvlkqgk0ag9hfi58z88rkfybc6kvg78bx89ca";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "Microsoft.TestPlatform.TestHost";
|
||||
version = "17.5.0";
|
||||
sha256 = "17g0k3r5n8grba8kg4nghjyhnq9w8v0w6c2nkyyygvfh8k8x9wh3";
|
||||
version = "17.9.0";
|
||||
sha256 = "19ffh31a1jxzn8j69m1vnk5hyfz3dbxmflq77b8x82zybiilh5nl";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "Myriad.Core";
|
||||
@@ -288,13 +288,13 @@
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "Nerdbank.GitVersioning";
|
||||
version = "3.6.128";
|
||||
sha256 = "1ip5qlhssfhx7q6gjnx7syvwc9m1bf4ikd17z5cbn9l257465hrj";
|
||||
version = "3.6.133";
|
||||
sha256 = "1cdw8krvsnx0n34f7fm5hiiy7bs6h3asvncqcikc0g46l50w2j80";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "NETStandard.Library";
|
||||
version = "2.0.0";
|
||||
sha256 = "1bc4ba8ahgk15m8k4nd7x406nhi0kwqzbgjk2dmw52ss553xz7iy";
|
||||
version = "2.0.3";
|
||||
sha256 = "1fn9fxppfcg4jgypp2pmrpr6awl3qz1xmnri0cygpkwvyx27df1y";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "Newtonsoft.Json";
|
||||
@@ -308,28 +308,23 @@
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "NuGet.Common";
|
||||
version = "6.8.0";
|
||||
sha256 = "0l3ij8iwy7wj6s7f93lzi9168r4wz8zyin6a08iwgk7hvq44cia1";
|
||||
version = "6.9.1";
|
||||
sha256 = "0ic3d46r9v05pkczpmskw86yzixm6iwshbw0ya8i2957nhhlymw8";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "NuGet.Configuration";
|
||||
version = "6.8.0";
|
||||
sha256 = "0x03p408smkmv1gv7pmvsia4lkn0xaj4wfrkl58pjf8bbv51y0yw";
|
||||
version = "6.9.1";
|
||||
sha256 = "07z4qgbibpg59j2r05ifnqdyqf2xinm33rx7gjyr1f73kzg01m33";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "NuGet.Frameworks";
|
||||
version = "5.11.0";
|
||||
sha256 = "0wv26gq39hfqw9md32amr5771s73f5zn1z9vs4y77cgynxr73s4z";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "NuGet.Frameworks";
|
||||
version = "6.8.0";
|
||||
sha256 = "0i2xvhgkjkjr496i3pg8hamwv6505fia45qhn7jg5m01wb3cvsjl";
|
||||
version = "6.9.1";
|
||||
sha256 = "0s3az3ac53icjnmb14hfjcmkvzscvrkm62jgqf48yvsbysyhqm5s";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "NuGet.Packaging";
|
||||
version = "6.8.0";
|
||||
sha256 = "031z4s905bxi94h3f0qy4j1b6jxdxgqgpkzqvvpfxch07szxcbim";
|
||||
version = "6.9.1";
|
||||
sha256 = "0w0arkmzg3qh1brq4vm10zrsjm7nw706ld4y5kqcmvjpd16f4b4y";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "NuGet.Protocol";
|
||||
@@ -338,23 +333,23 @@
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "NuGet.Versioning";
|
||||
version = "6.8.0";
|
||||
sha256 = "1sd25h46fd12ng780r02q4ijcx1imkb53kj1y2y7cwg5myh537ks";
|
||||
version = "6.9.1";
|
||||
sha256 = "0xrs82dydy9cgxf0qypr01wawwnq1nf6fc7rwisb4y5v4r259fdm";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "NUnit";
|
||||
version = "3.14.0";
|
||||
sha256 = "19p8911lrfds1k9rv47jk1bbn665s0pvghkd06gzbg78j6mzzqqa";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "NUnit.Analyzers";
|
||||
version = "3.6.1";
|
||||
sha256 = "16dw5375k2wyhiw9x387y7pjgq6zms30y036qb8z7idx4lxw9yi9";
|
||||
version = "4.1.0";
|
||||
sha256 = "0fj6xwgqaxq3mrai86bklclfmjkzf038mrslwfqf4ignaz9f7g5j";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "NUnit3TestAdapter";
|
||||
version = "4.4.2";
|
||||
sha256 = "1n2jlc16vjdd81cb1by4qbp75sq73zsjz5w3zc61ssmbdci1q2ri";
|
||||
version = "4.5.0";
|
||||
sha256 = "1srx1629s0k1kmf02nmz251q07vj6pv58mdafcr5dr0bbn1fh78i";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "RestEase";
|
||||
version = "1.6.4";
|
||||
sha256 = "1mvi3nbrr450g3fgd1y4wg3bwl9k1agyjfd9wdkqk12714bsln8l";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "runtime.any.System.Runtime";
|
||||
|
Reference in New Issue
Block a user