Compare commits

...

32 Commits

Author SHA1 Message Date
Patrick Stevens
9e36986bc7 Fix GitHub releases process (#108) 2024-02-25 11:57:55 +00:00
Patrick Stevens
679c66885d Check out code during GitHub Action tag (#107) 2024-02-25 10:19:53 +00:00
Patrick Stevens
246da41672 GitHub releases (#105) 2024-02-25 10:04:12 +00:00
dependabot[bot]
d07541c2c2 Bump Microsoft.NET.Test.Sdk from 17.8.0 to 17.9.0 (#102)
* Bump Microsoft.NET.Test.Sdk from 17.8.0 to 17.9.0

Bumps [Microsoft.NET.Test.Sdk](https://github.com/microsoft/vstest) from 17.8.0 to 17.9.0.
- [Release notes](https://github.com/microsoft/vstest/releases)
- [Changelog](https://github.com/microsoft/vstest/blob/main/docs/releases.md)
- [Commits](https://github.com/microsoft/vstest/compare/v17.8.0...v17.9.0)

---
updated-dependencies:
- dependency-name: Microsoft.NET.Test.Sdk
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

* Bump ApiSurface from 4.0.28 to 4.0.30

Bumps [ApiSurface](https://github.com/G-Research/ApiSurface) from 4.0.28 to 4.0.30.
- [Commits](https://github.com/G-Research/ApiSurface/commits)

---
updated-dependencies:
- dependency-name: ApiSurface
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

* Bump fsharp-analyzers from 0.24.0 to 0.25.0

Bumps [fsharp-analyzers](https://github.com/ionide/FSharp.Analyzers.SDK) from 0.24.0 to 0.25.0.
- [Release notes](https://github.com/ionide/FSharp.Analyzers.SDK/releases)
- [Changelog](https://github.com/ionide/FSharp.Analyzers.SDK/blob/main/CHANGELOG.md)
- [Commits](https://github.com/ionide/FSharp.Analyzers.SDK/compare/v0.24.0...v0.25.0)

---
updated-dependencies:
- dependency-name: fsharp-analyzers
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

* Bump deps

* Fix

* Bump analysers

* Fix

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Smaug123 <patrick+github@patrickstevens.co.uk>
2024-02-19 18:11:35 +00:00
Patrick Stevens
7b49505064 Absolute bare-bones support for generics in cata (#101) 2024-02-19 00:57:14 +00:00
Patrick Stevens
3209372b5b Add another instance of MyList (#100) 2024-02-18 14:13:34 +00:00
Patrick Stevens
1bbbf4bd06 Fix a bug in the cata (#98) 2024-02-18 14:04:59 +00:00
Patrick Stevens
3ea1c7ab79 Add catamorphism generator (#97) 2024-02-17 23:16:54 +00:00
Patrick Stevens
f55a810608 Allow cancellation token arg to have another name (#96) 2024-02-14 23:17:20 +00:00
Patrick Stevens
afc952241d Add docstring to generated mock (#95) 2024-02-13 23:19:47 +00:00
Patrick Stevens
c3af52596f Permit public mocks (#94) 2024-02-13 19:58:30 +00:00
Patrick Stevens
8bd13c0bb4 Add open statements in generated mocks (#93) 2024-02-13 19:26:50 +00:00
dependabot[bot]
ebd6f980de Bump Microsoft.NET.Test.Sdk from 17.6.0 to 17.9.0 (#91)
* Bump Microsoft.NET.Test.Sdk from 17.6.0 to 17.9.0

Bumps [Microsoft.NET.Test.Sdk](https://github.com/microsoft/vstest) from 17.6.0 to 17.9.0.
- [Release notes](https://github.com/microsoft/vstest/releases)
- [Changelog](https://github.com/microsoft/vstest/blob/main/docs/releases.md)
- [Commits](https://github.com/microsoft/vstest/compare/v17.6.0...v17.9.0)

---
updated-dependencies:
- dependency-name: Microsoft.NET.Test.Sdk
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

* Bump more

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Smaug123 <patrick+github@patrickstevens.co.uk>
2024-02-12 18:11:00 +00:00
Patrick Stevens
690a47488d Stop deps from propagating (#89) 2024-02-07 17:18:22 +00:00
Patrick Stevens
82b40ee559 Fix baffling dependency issue (#88) 2024-02-07 09:40:44 +00:00
Patrick Stevens
5a0a7e0d17 Skip duplicates during push (#87) 2024-02-07 01:37:45 +00:00
Patrick Stevens
7ef393a28d Split attributes into their own assembly (#86) 2024-02-07 01:27:57 +00:00
Patrick Stevens
4e18e8b1bf Accept empty Path attr (#85) 2024-02-06 20:49:51 +00:00
Patrick Stevens
a0fb7ee43a Correctly deal with JsonNull (#84) 2024-02-06 20:42:04 +00:00
Patrick Stevens
3d5cd7374f Document that optional is unsupported (#83) 2024-02-06 18:54:07 +00:00
Patrick Stevens
1215834795 Allow serde of guids (#82) 2024-02-06 18:50:26 +00:00
Patrick Stevens
e453a6f07c Allow general expressions in GET attribute arg (#81) 2024-02-06 18:43:45 +00:00
Patrick Stevens
3dfb89d086 Bump deps (#80) 2024-02-05 20:12:30 +00:00
Patrick Stevens
626f6ef137 Upgrade analyzers (#77) 2024-01-30 09:29:20 +00:00
Patrick Stevens
f803b44311 Implement RestEase variable headers (#76) 2024-01-29 21:24:41 +00:00
dependabot[bot]
5c1841c3d2 Bump fantomas from 6.3.0-alpha-005 to 6.3.0-alpha-007 (#74)
* Bump fantomas from 6.3.0-alpha-005 to 6.3.0-alpha-007

Bumps [fantomas](https://github.com/fsprojects/fantomas) from 6.3.0-alpha-005 to 6.3.0-alpha-007.
- [Release notes](https://github.com/fsprojects/fantomas/releases)
- [Changelog](https://github.com/fsprojects/fantomas/blob/main/CHANGELOG.md)
- [Commits](https://github.com/fsprojects/fantomas/commits)

---
updated-dependencies:
- dependency-name: fantomas
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

* Fix

* Reformat

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Smaug123 <patrick+github@patrickstevens.co.uk>
2024-01-29 19:11:20 +00:00
dependabot[bot]
bea584e3cc Bump NUnit.Analyzers from 3.10.0 to 4.0.0 (#75)
* Bump NUnit.Analyzers from 3.10.0 to 4.0.0

Bumps [NUnit.Analyzers](https://github.com/nunit/nunit.analyzers) from 3.10.0 to 4.0.0.
- [Release notes](https://github.com/nunit/nunit.analyzers/releases)
- [Changelog](https://github.com/nunit/nunit.analyzers/blob/master/CHANGES.txt)
- [Commits](https://github.com/nunit/nunit.analyzers/compare/3.10.0...4.0.0)

---
updated-dependencies:
- dependency-name: NUnit.Analyzers
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

* Fix

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Smaug123 <patrick+github@patrickstevens.co.uk>
2024-01-29 19:02:50 +00:00
Patrick Stevens
f8fdcb805e Support direnv (#73) 2024-01-28 23:39:27 +00:00
Patrick Stevens
0f7724903b Use more general serialisation for body params (#72) 2024-01-28 16:34:01 +00:00
Patrick Stevens
f83ac24a73 Implement JSON serialisation of body params (#71) 2024-01-26 17:54:45 +00:00
Patrick Stevens
ae3840d537 Handle returning RestEase.Response (#70) 2024-01-26 14:50:52 +00:00
Patrick Stevens
aafee9495a JSON serialization (#69) 2024-01-26 10:53:08 +00:00
61 changed files with 6223 additions and 1006 deletions

View File

@@ -3,13 +3,13 @@
"isRoot": true, "isRoot": true,
"tools": { "tools": {
"fantomas": { "fantomas": {
"version": "6.3.0-alpha-005", "version": "6.3.0-alpha-007",
"commands": [ "commands": [
"fantomas" "fantomas"
] ]
}, },
"fsharp-analyzers": { "fsharp-analyzers": {
"version": "0.23.0", "version": "0.25.0",
"commands": [ "commands": [
"fsharp-analyzers" "fsharp-analyzers"
] ]

1
.envrc Normal file
View File

@@ -0,0 +1 @@
use flake

View File

@@ -58,7 +58,7 @@ jobs:
- name: Build project - name: Build project
run: nix develop --command dotnet build ./WoofWare.Myriad.Plugins/WoofWare.Myriad.Plugins.fsproj run: nix develop --command dotnet build ./WoofWare.Myriad.Plugins/WoofWare.Myriad.Plugins.fsproj
- name: Run analyzers - 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: build-nix:
runs-on: ubuntu-latest runs-on: ubuntu-latest
@@ -142,23 +142,37 @@ jobs:
run: nix develop --command dotnet build --no-restore --configuration Release run: nix develop --command dotnet build --no-restore --configuration Release
- name: Pack - name: Pack
run: nix develop --command dotnet pack --configuration Release run: nix develop --command dotnet pack --configuration Release
- name: Upload NuGet artifact - name: Upload NuGet artifact (plugin)
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
with: with:
name: nuget-package name: nuget-package-plugin
path: WoofWare.Myriad.Plugins/bin/Release/WoofWare.Myriad.Plugins.*.nupkg 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: expected-pack:
needs: [nuget-pack] needs: [nuget-pack]
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Download NuGet artifact - name: Download NuGet artifact (plugin)
uses: actions/download-artifact@v4 uses: actions/download-artifact@v4
with: with:
name: nuget-package name: nuget-package-plugin
path: packed-plugin
- name: Check NuGet contents - name: Check NuGet contents
# Verify that there is exactly one nupkg in the artifact that would be NuGet published # 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: all-required-checks-complete:
needs: [check-dotnet-format, check-nix-format, build, build-nix, linkcheck, flake-check, analyzers, nuget-pack, expected-pack] needs: [check-dotnet-format, check-nix-format, build, build-nix, linkcheck, flake-check, analyzers, nuget-pack, expected-pack]
@@ -178,9 +192,39 @@ jobs:
with: with:
extra_nix_config: | extra_nix_config: |
access-tokens = github.com=${{ secrets.GITHUB_TOKEN }} access-tokens = github.com=${{ secrets.GITHUB_TOKEN }}
- name: Download NuGet artifact - name: Download NuGet artifact (plugin)
uses: actions/download-artifact@v4 uses: actions/download-artifact@v4
with: with:
name: nuget-package name: nuget-package-plugin
- name: Publish to NuGet path: packed-plugin
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: 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
View 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
View File

@@ -9,3 +9,4 @@ riderModule.iml
result result
.analyzerpackages/ .analyzerpackages/
analysis.sarif analysis.sarif
.direnv/

6
CHANGELOG.md Normal file
View 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.

View 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>

View File

@@ -35,10 +35,27 @@
<Compile Include="GeneratedVault.fs"> <Compile Include="GeneratedVault.fs">
<MyriadFile>Vault.fs</MyriadFile> <MyriadFile>Vault.fs</MyriadFile>
</Compile> </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>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="RestEase" Version="1.6.4"/> <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"/> <ProjectReference Include="..\WoofWare.Myriad.Plugins\WoofWare.Myriad.Plugins.fsproj"/>
<PackageReference Include="Myriad.Sdk" Version="0.8.3"/> <PackageReference Include="Myriad.Sdk" Version="0.8.3"/>
<PackageReference Include="Myriad.Core" Version="0.8.3"/> <PackageReference Include="Myriad.Core" Version="0.8.3"/>

View 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

View 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

View 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

View File

@@ -4,6 +4,7 @@
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
namespace ConsumePlugin namespace ConsumePlugin
/// Module containing JSON parsing methods for the InnerType type /// Module containing JSON parsing methods for the InnerType type
@@ -123,7 +124,7 @@ namespace ConsumePlugin
/// Module containing JSON parsing extension members for the ToGetExtensionMethod type /// Module containing JSON parsing extension members for the ToGetExtensionMethod type
[<AutoOpen>] [<AutoOpen>]
module ToGetExtensionMethodJsonParseExtension = module ToGetExtensionMethodJsonParseExtension =
///Extension methods for JSON parsing /// Extension methods for JSON parsing
type ToGetExtensionMethod with type ToGetExtensionMethod with
/// Parse from a JSON node. /// Parse from a JSON node.

View File

@@ -5,6 +5,8 @@
namespace SomeNamespace namespace SomeNamespace
open WoofWare.Myriad.Plugins
/// Mock record type for an interface /// Mock record type for an interface
type internal PublicTypeMock = type internal PublicTypeMock =
{ {
@@ -13,6 +15,7 @@ type internal PublicTypeMock =
Mem3 : int * option<System.Threading.CancellationToken> -> string Mem3 : int * option<System.Threading.CancellationToken> -> string
} }
/// An implementation where every method throws.
static member Empty : PublicTypeMock = static member Empty : PublicTypeMock =
{ {
Mem1 = (fun x -> raise (System.NotImplementedException "Unimplemented mock function")) Mem1 = (fun x -> raise (System.NotImplementedException "Unimplemented mock function"))
@@ -26,6 +29,32 @@ type internal PublicTypeMock =
member this.Mem3 (arg_0_0, arg_0_1) = this.Mem3 (arg_0_0, arg_0_1) member this.Mem3 (arg_0_0, arg_0_1) = this.Mem3 (arg_0_0, arg_0_1)
namespace SomeNamespace 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 /// Mock record type for an interface
type internal InternalTypeMock = type internal InternalTypeMock =
{ {
@@ -33,6 +62,7 @@ type internal InternalTypeMock =
Mem2 : string -> int Mem2 : string -> int
} }
/// An implementation where every method throws.
static member Empty : InternalTypeMock = static member Empty : InternalTypeMock =
{ {
Mem1 = (fun x -> raise (System.NotImplementedException "Unimplemented mock function")) Mem1 = (fun x -> raise (System.NotImplementedException "Unimplemented mock function"))
@@ -44,6 +74,8 @@ type internal InternalTypeMock =
member this.Mem2 (arg_0_0) = this.Mem2 (arg_0_0) member this.Mem2 (arg_0_0) = this.Mem2 (arg_0_0)
namespace SomeNamespace namespace SomeNamespace
open WoofWare.Myriad.Plugins
/// Mock record type for an interface /// Mock record type for an interface
type private PrivateTypeMock = type private PrivateTypeMock =
{ {
@@ -51,6 +83,7 @@ type private PrivateTypeMock =
Mem2 : string -> int Mem2 : string -> int
} }
/// An implementation where every method throws.
static member Empty : PrivateTypeMock = static member Empty : PrivateTypeMock =
{ {
Mem1 = (fun x -> raise (System.NotImplementedException "Unimplemented mock function")) Mem1 = (fun x -> raise (System.NotImplementedException "Unimplemented mock function"))
@@ -62,12 +95,36 @@ type private PrivateTypeMock =
member this.Mem2 (arg_0_0) = this.Mem2 (arg_0_0) member this.Mem2 (arg_0_0) = this.Mem2 (arg_0_0)
namespace SomeNamespace 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 /// Mock record type for an interface
type internal VeryPublicTypeMock<'a, 'b> = type internal VeryPublicTypeMock<'a, 'b> =
{ {
Mem1 : 'a -> 'b Mem1 : 'a -> 'b
} }
/// An implementation where every method throws.
static member Empty () : VeryPublicTypeMock<'a, 'b> = static member Empty () : VeryPublicTypeMock<'a, 'b> =
{ {
Mem1 = (fun x -> raise (System.NotImplementedException "Unimplemented mock function")) Mem1 = (fun x -> raise (System.NotImplementedException "Unimplemented mock function"))
@@ -77,6 +134,8 @@ type internal VeryPublicTypeMock<'a, 'b> =
member this.Mem1 (arg_0_0) = this.Mem1 (arg_0_0) member this.Mem1 (arg_0_0) = this.Mem1 (arg_0_0)
namespace SomeNamespace namespace SomeNamespace
open WoofWare.Myriad.Plugins
/// Mock record type for an interface /// Mock record type for an interface
type internal CurriedMock<'a> = type internal CurriedMock<'a> =
{ {
@@ -88,6 +147,7 @@ type internal CurriedMock<'a> =
Mem6 : int * string -> 'a * int -> string Mem6 : int * string -> 'a * int -> string
} }
/// An implementation where every method throws.
static member Empty () : CurriedMock<'a> = static member Empty () : CurriedMock<'a> =
{ {
Mem1 = (fun x -> raise (System.NotImplementedException "Unimplemented mock function")) Mem1 = (fun x -> raise (System.NotImplementedException "Unimplemented mock function"))

View File

@@ -4,6 +4,40 @@
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
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 namespace PureGym
/// Module containing JSON parsing methods for the GymOpeningHours type /// Module containing JSON parsing methods for the GymOpeningHours type
@@ -377,210 +411,212 @@ module Gym =
} }
namespace PureGym namespace PureGym
/// Module containing JSON parsing methods for the Member type /// Module containing JSON parsing extension members for the Member type
[<RequireQualifiedAccess>] [<AutoOpen>]
[<CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix)>] module MemberJsonParseExtension =
module Member = /// Extension methods for JSON parsing
/// Parse from a JSON node. type Member with
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> ()
let SuspendedReason = /// Parse from a JSON node.
(match node.["suspendedReason"] with static member jsonParse (node : System.Text.Json.Nodes.JsonNode) : Member =
| null -> let MemberStatus =
raise ( (match node.["memberStatus"] with
System.Collections.Generic.KeyNotFoundException ( | null ->
sprintf "Required key '%s' not found on JSON object" ("suspendedReason") raise (
System.Collections.Generic.KeyNotFoundException (
sprintf "Required key '%s' not found on JSON object" ("memberStatus")
)
) )
) | v -> v)
| v -> v) .AsValue()
.AsValue() .GetValue<int> ()
.GetValue<int> ()
let MembershipLevel = let SuspendedReason =
(match node.["membershipLevel"] with (match node.["suspendedReason"] with
| null -> | null ->
raise ( raise (
System.Collections.Generic.KeyNotFoundException ( System.Collections.Generic.KeyNotFoundException (
sprintf "Required key '%s' not found on JSON object" ("membershipLevel") sprintf "Required key '%s' not found on JSON object" ("suspendedReason")
)
) )
) | v -> v)
| v -> v) .AsValue()
.AsValue() .GetValue<int> ()
.GetValue<int> ()
let MembershipName = let MembershipLevel =
(match node.["membershipName"] with (match node.["membershipLevel"] with
| null -> | null ->
raise ( raise (
System.Collections.Generic.KeyNotFoundException ( System.Collections.Generic.KeyNotFoundException (
sprintf "Required key '%s' not found on JSON object" ("membershipName") sprintf "Required key '%s' not found on JSON object" ("membershipLevel")
)
) )
) | v -> v)
| v -> v) .AsValue()
.AsValue() .GetValue<int> ()
.GetValue<string> ()
let Postcode = let MembershipName =
(match node.["postCode"] with (match node.["membershipName"] with
| null -> | null ->
raise ( raise (
System.Collections.Generic.KeyNotFoundException ( System.Collections.Generic.KeyNotFoundException (
sprintf "Required key '%s' not found on JSON object" ("postCode") sprintf "Required key '%s' not found on JSON object" ("membershipName")
)
) )
) | v -> v)
| v -> v) .AsValue()
.AsValue() .GetValue<string> ()
.GetValue<string> ()
let MobileNumber = let Postcode =
(match node.["mobileNumber"] with (match node.["postCode"] with
| null -> | null ->
raise ( raise (
System.Collections.Generic.KeyNotFoundException ( System.Collections.Generic.KeyNotFoundException (
sprintf "Required key '%s' not found on JSON object" ("mobileNumber") sprintf "Required key '%s' not found on JSON object" ("postCode")
)
) )
) | v -> v)
| v -> v) .AsValue()
.AsValue() .GetValue<string> ()
.GetValue<string> ()
let DateOfBirth = let MobileNumber =
(match node.["dateofBirth"] with (match node.["mobileNumber"] with
| null -> | null ->
raise ( raise (
System.Collections.Generic.KeyNotFoundException ( System.Collections.Generic.KeyNotFoundException (
sprintf "Required key '%s' not found on JSON object" ("dateofBirth") sprintf "Required key '%s' not found on JSON object" ("mobileNumber")
)
) )
) | v -> v)
| v -> v) .AsValue()
.AsValue() .GetValue<string> ()
.GetValue<string> ()
|> System.DateOnly.Parse
let GymAccessPin = let DateOfBirth =
(match node.["gymAccessPin"] with (match node.["dateofBirth"] with
| null -> | null ->
raise ( raise (
System.Collections.Generic.KeyNotFoundException ( System.Collections.Generic.KeyNotFoundException (
sprintf "Required key '%s' not found on JSON object" ("gymAccessPin") sprintf "Required key '%s' not found on JSON object" ("dateofBirth")
)
) )
) | v -> v)
| v -> v) .AsValue()
.AsValue() .GetValue<string> ()
.GetValue<string> () |> System.DateOnly.Parse
let EmailAddress = let GymAccessPin =
(match node.["emailAddress"] with (match node.["gymAccessPin"] with
| null -> | null ->
raise ( raise (
System.Collections.Generic.KeyNotFoundException ( System.Collections.Generic.KeyNotFoundException (
sprintf "Required key '%s' not found on JSON object" ("emailAddress") sprintf "Required key '%s' not found on JSON object" ("gymAccessPin")
)
) )
) | v -> v)
| v -> v) .AsValue()
.AsValue() .GetValue<string> ()
.GetValue<string> ()
let HomeGymName = let EmailAddress =
(match node.["homeGymName"] with (match node.["emailAddress"] with
| null -> | null ->
raise ( raise (
System.Collections.Generic.KeyNotFoundException ( System.Collections.Generic.KeyNotFoundException (
sprintf "Required key '%s' not found on JSON object" ("homeGymName") sprintf "Required key '%s' not found on JSON object" ("emailAddress")
)
) )
) | v -> v)
| v -> v) .AsValue()
.AsValue() .GetValue<string> ()
.GetValue<string> ()
let HomeGymId = let HomeGymName =
(match node.["homeGymId"] with (match node.["homeGymName"] with
| null -> | null ->
raise ( raise (
System.Collections.Generic.KeyNotFoundException ( System.Collections.Generic.KeyNotFoundException (
sprintf "Required key '%s' not found on JSON object" ("homeGymId") sprintf "Required key '%s' not found on JSON object" ("homeGymName")
)
) )
) | v -> v)
| v -> v) .AsValue()
.AsValue() .GetValue<string> ()
.GetValue<int> ()
let LastName = let HomeGymId =
(match node.["lastName"] with (match node.["homeGymId"] with
| null -> | null ->
raise ( raise (
System.Collections.Generic.KeyNotFoundException ( System.Collections.Generic.KeyNotFoundException (
sprintf "Required key '%s' not found on JSON object" ("lastName") sprintf "Required key '%s' not found on JSON object" ("homeGymId")
)
) )
) | v -> v)
| v -> v) .AsValue()
.AsValue() .GetValue<int> ()
.GetValue<string> ()
let FirstName = let LastName =
(match node.["firstName"] with (match node.["lastName"] with
| null -> | null ->
raise ( raise (
System.Collections.Generic.KeyNotFoundException ( System.Collections.Generic.KeyNotFoundException (
sprintf "Required key '%s' not found on JSON object" ("firstName") sprintf "Required key '%s' not found on JSON object" ("lastName")
)
) )
) | v -> v)
| v -> v) .AsValue()
.AsValue() .GetValue<string> ()
.GetValue<string> ()
let CompoundMemberId = let FirstName =
(match node.["compoundMemberId"] with (match node.["firstName"] with
| null -> | null ->
raise ( raise (
System.Collections.Generic.KeyNotFoundException ( System.Collections.Generic.KeyNotFoundException (
sprintf "Required key '%s' not found on JSON object" ("compoundMemberId") sprintf "Required key '%s' not found on JSON object" ("firstName")
)
) )
) | v -> v)
| v -> v) .AsValue()
.AsValue() .GetValue<string> ()
.GetValue<string> ()
let Id = let CompoundMemberId =
(match node.["id"] with (match node.["compoundMemberId"] with
| null -> | null ->
raise ( raise (
System.Collections.Generic.KeyNotFoundException ( System.Collections.Generic.KeyNotFoundException (
sprintf "Required key '%s' not found on JSON object" ("id") sprintf "Required key '%s' not found on JSON object" ("compoundMemberId")
)
) )
) | v -> v)
| v -> v) .AsValue()
.AsValue() .GetValue<string> ()
.GetValue<int> ()
{ let Id =
Id = Id (match node.["id"] with
CompoundMemberId = CompoundMemberId | null ->
FirstName = FirstName raise (
LastName = LastName System.Collections.Generic.KeyNotFoundException (
HomeGymId = HomeGymId sprintf "Required key '%s' not found on JSON object" ("id")
HomeGymName = HomeGymName )
EmailAddress = EmailAddress )
GymAccessPin = GymAccessPin | v -> v)
DateOfBirth = DateOfBirth .AsValue()
MobileNumber = MobileNumber .GetValue<int> ()
Postcode = Postcode
MembershipName = MembershipName {
MembershipLevel = MembershipLevel Id = Id
SuspendedReason = SuspendedReason CompoundMemberId = CompoundMemberId
MemberStatus = MemberStatus 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 namespace PureGym
/// Module containing JSON parsing methods for the GymAttendance type /// Module containing JSON parsing methods for the GymAttendance type

View File

@@ -5,6 +5,7 @@
namespace PureGym namespace PureGym
open System open System
@@ -31,7 +32,7 @@ module PureGymApi =
(match client.BaseAddress with (match client.BaseAddress with
| null -> System.Uri "https://whatnot.com" | null -> System.Uri "https://whatnot.com"
| v -> v), | v -> v),
System.Uri ("v1/gyms/", System.UriKind.Relative) System.Uri (("v1/gyms/"), System.UriKind.Relative)
) )
let httpMessage = let httpMessage =
@@ -42,13 +43,13 @@ module PureGymApi =
let! response = client.SendAsync (httpMessage, ct) |> Async.AwaitTask let! response = client.SendAsync (httpMessage, ct) |> Async.AwaitTask
let response = response.EnsureSuccessStatusCode () let response = response.EnsureSuccessStatusCode ()
let! stream = response.Content.ReadAsStreamAsync ct |> Async.AwaitTask let! responseStream = response.Content.ReadAsStreamAsync ct |> Async.AwaitTask
let! node = let! jsonNode =
System.Text.Json.Nodes.JsonNode.ParseAsync (stream, cancellationToken = ct) System.Text.Json.Nodes.JsonNode.ParseAsync (responseStream, cancellationToken = ct)
|> Async.AwaitTask |> Async.AwaitTask
return node.AsArray () |> Seq.map (fun elt -> Gym.jsonParse elt) |> List.ofSeq return jsonNode.AsArray () |> Seq.map (fun elt -> Gym.jsonParse elt) |> List.ofSeq
} }
|> (fun a -> Async.StartAsTask (a, ?cancellationToken = ct)) |> (fun a -> Async.StartAsTask (a, ?cancellationToken = ct))
@@ -76,13 +77,13 @@ module PureGymApi =
let! response = client.SendAsync (httpMessage, ct) |> Async.AwaitTask let! response = client.SendAsync (httpMessage, ct) |> Async.AwaitTask
let response = response.EnsureSuccessStatusCode () let response = response.EnsureSuccessStatusCode ()
let! stream = response.Content.ReadAsStreamAsync ct |> Async.AwaitTask let! responseStream = response.Content.ReadAsStreamAsync ct |> Async.AwaitTask
let! node = let! jsonNode =
System.Text.Json.Nodes.JsonNode.ParseAsync (stream, cancellationToken = ct) System.Text.Json.Nodes.JsonNode.ParseAsync (responseStream, cancellationToken = ct)
|> Async.AwaitTask |> Async.AwaitTask
return GymAttendance.jsonParse node return GymAttendance.jsonParse jsonNode
} }
|> (fun a -> Async.StartAsTask (a, ?cancellationToken = ct)) |> (fun a -> Async.StartAsTask (a, ?cancellationToken = ct))
@@ -106,17 +107,17 @@ module PureGymApi =
let! response = client.SendAsync (httpMessage, ct) |> Async.AwaitTask let! response = client.SendAsync (httpMessage, ct) |> Async.AwaitTask
let response = response.EnsureSuccessStatusCode () let response = response.EnsureSuccessStatusCode ()
let! stream = response.Content.ReadAsStreamAsync ct |> Async.AwaitTask let! responseStream = response.Content.ReadAsStreamAsync ct |> Async.AwaitTask
let! node = let! jsonNode =
System.Text.Json.Nodes.JsonNode.ParseAsync (stream, cancellationToken = ct) System.Text.Json.Nodes.JsonNode.ParseAsync (responseStream, cancellationToken = ct)
|> Async.AwaitTask |> Async.AwaitTask
return Member.jsonParse node return Member.jsonParse jsonNode
} }
|> (fun a -> Async.StartAsTask (a, ?cancellationToken = ct)) |> (fun a -> Async.StartAsTask (a, ?cancellationToken = ct))
member _.GetGym (gymId : int, ct : CancellationToken option) = member _.GetGym (gym : int, ct : CancellationToken option) =
async { async {
let! ct = Async.CancellationToken let! ct = Async.CancellationToken
@@ -126,8 +127,8 @@ module PureGymApi =
| null -> System.Uri "https://whatnot.com" | null -> System.Uri "https://whatnot.com"
| v -> v), | v -> v),
System.Uri ( System.Uri (
"v1/gyms/{gym_id}" "v1/gyms/{gym}"
.Replace ("{gym_id}", gymId.ToString () |> System.Web.HttpUtility.UrlEncode), .Replace ("{gym}", gym.ToString () |> System.Web.HttpUtility.UrlEncode),
System.UriKind.Relative System.UriKind.Relative
) )
) )
@@ -140,13 +141,13 @@ module PureGymApi =
let! response = client.SendAsync (httpMessage, ct) |> Async.AwaitTask let! response = client.SendAsync (httpMessage, ct) |> Async.AwaitTask
let response = response.EnsureSuccessStatusCode () let response = response.EnsureSuccessStatusCode ()
let! stream = response.Content.ReadAsStreamAsync ct |> Async.AwaitTask let! responseStream = response.Content.ReadAsStreamAsync ct |> Async.AwaitTask
let! node = let! jsonNode =
System.Text.Json.Nodes.JsonNode.ParseAsync (stream, cancellationToken = ct) System.Text.Json.Nodes.JsonNode.ParseAsync (responseStream, cancellationToken = ct)
|> Async.AwaitTask |> Async.AwaitTask
return Gym.jsonParse node return Gym.jsonParse jsonNode
} }
|> (fun a -> Async.StartAsTask (a, ?cancellationToken = ct)) |> (fun a -> Async.StartAsTask (a, ?cancellationToken = ct))
@@ -170,13 +171,13 @@ module PureGymApi =
let! response = client.SendAsync (httpMessage, ct) |> Async.AwaitTask let! response = client.SendAsync (httpMessage, ct) |> Async.AwaitTask
let response = response.EnsureSuccessStatusCode () let response = response.EnsureSuccessStatusCode ()
let! stream = response.Content.ReadAsStreamAsync ct |> Async.AwaitTask let! responseStream = response.Content.ReadAsStreamAsync ct |> Async.AwaitTask
let! node = let! jsonNode =
System.Text.Json.Nodes.JsonNode.ParseAsync (stream, cancellationToken = ct) System.Text.Json.Nodes.JsonNode.ParseAsync (responseStream, cancellationToken = ct)
|> Async.AwaitTask |> Async.AwaitTask
return MemberActivityDto.jsonParse node return MemberActivityDto.jsonParse jsonNode
} }
|> (fun a -> Async.StartAsTask (a, ?cancellationToken = ct)) |> (fun a -> Async.StartAsTask (a, ?cancellationToken = ct))
@@ -200,13 +201,79 @@ module PureGymApi =
let! response = client.SendAsync (httpMessage, ct) |> Async.AwaitTask let! response = client.SendAsync (httpMessage, ct) |> Async.AwaitTask
let response = response.EnsureSuccessStatusCode () let response = response.EnsureSuccessStatusCode ()
let! stream = response.Content.ReadAsStreamAsync ct |> Async.AwaitTask let! responseStream = response.Content.ReadAsStreamAsync ct |> Async.AwaitTask
let! node = let! jsonNode =
System.Text.Json.Nodes.JsonNode.ParseAsync (stream, cancellationToken = ct) System.Text.Json.Nodes.JsonNode.ParseAsync (responseStream, cancellationToken = ct)
|> Async.AwaitTask |> Async.AwaitTask
return UriThing.jsonParse node return UriThing.jsonParse jsonNode
}
|> (fun a -> Async.StartAsTask (a, ?cancellationToken = ct))
member _.PostStringToString (foo : Map<string, string> option, ct : CancellationToken option) =
async {
let! ct = Async.CancellationToken
let uri =
System.Uri (
(match client.BaseAddress with
| null -> System.Uri "https://whatnot.com"
| v -> v),
System.Uri ("some/url", System.UriKind.Relative)
)
let httpMessage =
new System.Net.Http.HttpRequestMessage (
Method = System.Net.Http.HttpMethod.Post,
RequestUri = uri
)
let queryParams =
new System.Net.Http.StringContent (
foo
|> (fun field ->
match field with
| None -> null :> System.Text.Json.Nodes.JsonNode
| Some field ->
((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<string> value
)
ret
)
field)
:> System.Text.Json.Nodes.JsonNode
)
|> (fun node -> if isNull node then "null" else node.ToJsonString ())
)
do httpMessage.Content <- queryParams
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
match jsonNode with
| null -> None
| v ->
v.AsObject ()
|> Seq.map (fun kvp ->
let key = (kvp.Key)
let value = (kvp.Value).AsValue().GetValue<string> ()
key, value
)
|> Map.ofSeq
|> Some
} }
|> (fun a -> Async.StartAsTask (a, ?cancellationToken = ct)) |> (fun a -> Async.StartAsTask (a, ?cancellationToken = ct))
@@ -237,13 +304,13 @@ module PureGymApi =
let! response = client.SendAsync (httpMessage, ct) |> Async.AwaitTask let! response = client.SendAsync (httpMessage, ct) |> Async.AwaitTask
let response = response.EnsureSuccessStatusCode () let response = response.EnsureSuccessStatusCode ()
let! stream = response.Content.ReadAsStreamAsync ct |> Async.AwaitTask let! responseStream = response.Content.ReadAsStreamAsync ct |> Async.AwaitTask
let! node = let! jsonNode =
System.Text.Json.Nodes.JsonNode.ParseAsync (stream, cancellationToken = ct) System.Text.Json.Nodes.JsonNode.ParseAsync (responseStream, cancellationToken = ct)
|> Async.AwaitTask |> Async.AwaitTask
return Sessions.jsonParse node return Sessions.jsonParse jsonNode
} }
|> (fun a -> Async.StartAsTask (a, ?cancellationToken = ct)) |> (fun a -> Async.StartAsTask (a, ?cancellationToken = ct))
@@ -269,8 +336,8 @@ module PureGymApi =
do httpMessage.Content <- queryParams do httpMessage.Content <- queryParams
let! response = client.SendAsync (httpMessage, ct) |> Async.AwaitTask let! response = client.SendAsync (httpMessage, ct) |> Async.AwaitTask
let response = response.EnsureSuccessStatusCode () let response = response.EnsureSuccessStatusCode ()
let! node = response.Content.ReadAsStringAsync ct |> Async.AwaitTask let! responseString = response.Content.ReadAsStringAsync ct |> Async.AwaitTask
return node return responseString
} }
|> (fun a -> Async.StartAsTask (a, ?cancellationToken = ct)) |> (fun a -> Async.StartAsTask (a, ?cancellationToken = ct))
@@ -296,8 +363,8 @@ module PureGymApi =
do httpMessage.Content <- queryParams do httpMessage.Content <- queryParams
let! response = client.SendAsync (httpMessage, ct) |> Async.AwaitTask let! response = client.SendAsync (httpMessage, ct) |> Async.AwaitTask
let response = response.EnsureSuccessStatusCode () let response = response.EnsureSuccessStatusCode ()
let! node = response.Content.ReadAsStreamAsync ct |> Async.AwaitTask let! responseStream = response.Content.ReadAsStreamAsync ct |> Async.AwaitTask
return node return responseStream
} }
|> (fun a -> Async.StartAsTask (a, ?cancellationToken = ct)) |> (fun a -> Async.StartAsTask (a, ?cancellationToken = ct))
@@ -323,8 +390,8 @@ module PureGymApi =
do httpMessage.Content <- queryParams do httpMessage.Content <- queryParams
let! response = client.SendAsync (httpMessage, ct) |> Async.AwaitTask let! response = client.SendAsync (httpMessage, ct) |> Async.AwaitTask
let response = response.EnsureSuccessStatusCode () let response = response.EnsureSuccessStatusCode ()
let! node = response.Content.ReadAsStreamAsync ct |> Async.AwaitTask let! responseStream = response.Content.ReadAsStreamAsync ct |> Async.AwaitTask
return node return responseStream
} }
|> (fun a -> Async.StartAsTask (a, ?cancellationToken = ct)) |> (fun a -> Async.StartAsTask (a, ?cancellationToken = ct))
@@ -350,8 +417,8 @@ module PureGymApi =
do httpMessage.Content <- queryParams do httpMessage.Content <- queryParams
let! response = client.SendAsync (httpMessage, ct) |> Async.AwaitTask let! response = client.SendAsync (httpMessage, ct) |> Async.AwaitTask
let response = response.EnsureSuccessStatusCode () let response = response.EnsureSuccessStatusCode ()
let! node = response.Content.ReadAsStreamAsync ct |> Async.AwaitTask let! responseStream = response.Content.ReadAsStreamAsync ct |> Async.AwaitTask
return node return responseStream
} }
|> (fun a -> Async.StartAsTask (a, ?cancellationToken = ct)) |> (fun a -> Async.StartAsTask (a, ?cancellationToken = ct))
@@ -377,8 +444,107 @@ module PureGymApi =
do httpMessage.Content <- queryParams do httpMessage.Content <- queryParams
let! response = client.SendAsync (httpMessage, ct) |> Async.AwaitTask let! response = client.SendAsync (httpMessage, ct) |> Async.AwaitTask
let response = response.EnsureSuccessStatusCode () let response = response.EnsureSuccessStatusCode ()
let! node = response.Content.ReadAsStreamAsync ct |> Async.AwaitTask let! responseStream = response.Content.ReadAsStreamAsync ct |> Async.AwaitTask
return node return responseStream
}
|> (fun a -> Async.StartAsTask (a, ?cancellationToken = ct))
member _.CreateUserSerialisedBody (user : PureGym.Member, ct : CancellationToken option) =
async {
let! ct = Async.CancellationToken
let uri =
System.Uri (
(match client.BaseAddress with
| null -> System.Uri "https://whatnot.com"
| v -> v),
System.Uri ("users/new", System.UriKind.Relative)
)
let httpMessage =
new System.Net.Http.HttpRequestMessage (
Method = System.Net.Http.HttpMethod.Post,
RequestUri = uri
)
let queryParams =
new System.Net.Http.StringContent (
user
|> PureGym.Member.toJsonNode
|> (fun node -> if isNull node then "null" else node.ToJsonString ())
)
do httpMessage.Content <- queryParams
let! response = client.SendAsync (httpMessage, ct) |> Async.AwaitTask
let response = response.EnsureSuccessStatusCode ()
let! responseString = response.Content.ReadAsStringAsync ct |> Async.AwaitTask
return responseString
}
|> (fun a -> Async.StartAsTask (a, ?cancellationToken = ct))
member _.CreateUserSerialisedUrlBody (user : Uri, ct : CancellationToken option) =
async {
let! ct = Async.CancellationToken
let uri =
System.Uri (
(match client.BaseAddress with
| null -> System.Uri "https://whatnot.com"
| v -> v),
System.Uri ("users/new", System.UriKind.Relative)
)
let httpMessage =
new System.Net.Http.HttpRequestMessage (
Method = System.Net.Http.HttpMethod.Post,
RequestUri = uri
)
let queryParams =
new System.Net.Http.StringContent (
user
|> System.Text.Json.Nodes.JsonValue.Create<Uri>
|> (fun node -> if isNull node then "null" else node.ToJsonString ())
)
do httpMessage.Content <- queryParams
let! response = client.SendAsync (httpMessage, ct) |> Async.AwaitTask
let response = response.EnsureSuccessStatusCode ()
let! responseString = response.Content.ReadAsStringAsync ct |> Async.AwaitTask
return responseString
}
|> (fun a -> Async.StartAsTask (a, ?cancellationToken = ct))
member _.CreateUserSerialisedIntBody (user : int, ct : CancellationToken option) =
async {
let! ct = Async.CancellationToken
let uri =
System.Uri (
(match client.BaseAddress with
| null -> System.Uri "https://whatnot.com"
| v -> v),
System.Uri ("users/new", System.UriKind.Relative)
)
let httpMessage =
new System.Net.Http.HttpRequestMessage (
Method = System.Net.Http.HttpMethod.Post,
RequestUri = uri
)
let queryParams =
new System.Net.Http.StringContent (
user
|> System.Text.Json.Nodes.JsonValue.Create<int>
|> (fun node -> if isNull node then "null" else node.ToJsonString ())
)
do httpMessage.Content <- queryParams
let! response = client.SendAsync (httpMessage, ct) |> Async.AwaitTask
let response = response.EnsureSuccessStatusCode ()
let! responseString = response.Content.ReadAsStringAsync ct |> Async.AwaitTask
return responseString
} }
|> (fun a -> Async.StartAsTask (a, ?cancellationToken = ct)) |> (fun a -> Async.StartAsTask (a, ?cancellationToken = ct))
@@ -403,8 +569,8 @@ module PureGymApi =
do httpMessage.Content <- user do httpMessage.Content <- user
let! response = client.SendAsync (httpMessage, ct) |> Async.AwaitTask let! response = client.SendAsync (httpMessage, ct) |> Async.AwaitTask
let response = response.EnsureSuccessStatusCode () let response = response.EnsureSuccessStatusCode ()
let! node = response.Content.ReadAsStringAsync ct |> Async.AwaitTask let! responseString = response.Content.ReadAsStringAsync ct |> Async.AwaitTask
return node return responseString
} }
|> (fun a -> Async.StartAsTask (a, ?cancellationToken = ct)) |> (fun a -> Async.StartAsTask (a, ?cancellationToken = ct))
@@ -432,8 +598,8 @@ module PureGymApi =
let! response = client.SendAsync (httpMessage, ct) |> Async.AwaitTask let! response = client.SendAsync (httpMessage, ct) |> Async.AwaitTask
let response = response.EnsureSuccessStatusCode () let response = response.EnsureSuccessStatusCode ()
let! node = response.Content.ReadAsStringAsync ct |> Async.AwaitTask let! responseString = response.Content.ReadAsStringAsync ct |> Async.AwaitTask
return node return responseString
} }
|> (fun a -> Async.StartAsTask (a, ?cancellationToken = ct)) |> (fun a -> Async.StartAsTask (a, ?cancellationToken = ct))
@@ -457,8 +623,8 @@ module PureGymApi =
let! response = client.SendAsync (httpMessage, ct) |> Async.AwaitTask let! response = client.SendAsync (httpMessage, ct) |> Async.AwaitTask
let response = response.EnsureSuccessStatusCode () let response = response.EnsureSuccessStatusCode ()
let! node = response.Content.ReadAsStreamAsync ct |> Async.AwaitTask let! responseStream = response.Content.ReadAsStreamAsync ct |> Async.AwaitTask
return node return responseStream
} }
|> (fun a -> Async.StartAsTask (a, ?cancellationToken = ct)) |> (fun a -> Async.StartAsTask (a, ?cancellationToken = ct))
@@ -482,8 +648,8 @@ module PureGymApi =
let! response = client.SendAsync (httpMessage, ct) |> Async.AwaitTask let! response = client.SendAsync (httpMessage, ct) |> Async.AwaitTask
let response = response.EnsureSuccessStatusCode () let response = response.EnsureSuccessStatusCode ()
let! node = response.Content.ReadAsStreamAsync ct |> Async.AwaitTask let! responseStream = response.Content.ReadAsStreamAsync ct |> Async.AwaitTask
return node return responseStream
} }
|> (fun a -> Async.StartAsTask (a, ?cancellationToken = ct)) |> (fun a -> Async.StartAsTask (a, ?cancellationToken = ct))
@@ -507,8 +673,8 @@ module PureGymApi =
let! response = client.SendAsync (httpMessage, ct) |> Async.AwaitTask let! response = client.SendAsync (httpMessage, ct) |> Async.AwaitTask
let response = response.EnsureSuccessStatusCode () let response = response.EnsureSuccessStatusCode ()
let! node = response.Content.ReadAsStreamAsync ct |> Async.AwaitTask let! responseStream = response.Content.ReadAsStreamAsync ct |> Async.AwaitTask
return node return responseStream
} }
|> (fun a -> Async.StartAsTask (a, ?cancellationToken = ct)) |> (fun a -> Async.StartAsTask (a, ?cancellationToken = ct))
@@ -532,8 +698,7 @@ module PureGymApi =
let! response = client.SendAsync (httpMessage, ct) |> Async.AwaitTask let! response = client.SendAsync (httpMessage, ct) |> Async.AwaitTask
let response = response.EnsureSuccessStatusCode () let response = response.EnsureSuccessStatusCode ()
let node = response return response
return node
} }
|> (fun a -> Async.StartAsTask (a, ?cancellationToken = ct)) |> (fun a -> Async.StartAsTask (a, ?cancellationToken = ct))
@@ -557,8 +722,7 @@ module PureGymApi =
let! response = client.SendAsync (httpMessage, ct) |> Async.AwaitTask let! response = client.SendAsync (httpMessage, ct) |> Async.AwaitTask
let response = response.EnsureSuccessStatusCode () let response = response.EnsureSuccessStatusCode ()
let node = response return response
return node
} }
|> (fun a -> Async.StartAsTask (a, ?cancellationToken = ct)) |> (fun a -> Async.StartAsTask (a, ?cancellationToken = ct))
@@ -582,8 +746,7 @@ module PureGymApi =
let! response = client.SendAsync (httpMessage, ct) |> Async.AwaitTask let! response = client.SendAsync (httpMessage, ct) |> Async.AwaitTask
let response = response.EnsureSuccessStatusCode () let response = response.EnsureSuccessStatusCode ()
let node = response return response
return node
} }
|> (fun a -> Async.StartAsTask (a, ?cancellationToken = ct)) |> (fun a -> Async.StartAsTask (a, ?cancellationToken = ct))
@@ -607,8 +770,151 @@ module PureGymApi =
let! response = client.SendAsync (httpMessage, ct) |> Async.AwaitTask let! response = client.SendAsync (httpMessage, ct) |> Async.AwaitTask
let response = response.EnsureSuccessStatusCode () let response = response.EnsureSuccessStatusCode ()
let node = response return response
return node }
|> (fun a -> Async.StartAsTask (a, ?cancellationToken = ct))
member _.GetResponse (ct : CancellationToken option) =
async {
let! ct = Async.CancellationToken
let uri =
System.Uri (
(match client.BaseAddress with
| null -> System.Uri "https://whatnot.com"
| v -> v),
System.Uri ("endpoint", 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! responseString = response.Content.ReadAsStringAsync ct |> Async.AwaitTask
let! responseStream = response.Content.ReadAsStreamAsync ct |> Async.AwaitTask
let! jsonNode =
System.Text.Json.Nodes.JsonNode.ParseAsync (responseStream, cancellationToken = ct)
|> Async.AwaitTask
return
new RestEase.Response<_> (
responseString,
response,
(fun () -> (MemberActivityDto.jsonParse jsonNode))
)
}
|> (fun a -> Async.StartAsTask (a, ?cancellationToken = ct))
member _.GetResponse' (ct : CancellationToken option) =
async {
let! ct = Async.CancellationToken
let uri =
System.Uri (
(match client.BaseAddress with
| null -> System.Uri "https://whatnot.com"
| v -> v),
System.Uri ("endpoint", 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! responseString = response.Content.ReadAsStringAsync ct |> Async.AwaitTask
let! responseStream = response.Content.ReadAsStreamAsync ct |> Async.AwaitTask
let! jsonNode =
System.Text.Json.Nodes.JsonNode.ParseAsync (responseStream, cancellationToken = ct)
|> Async.AwaitTask
return
new RestEase.Response<_> (
responseString,
response,
(fun () -> (MemberActivityDto.jsonParse jsonNode))
)
}
|> (fun a -> Async.StartAsTask (a, ?cancellationToken = ct))
member _.GetResponse'' (ct : CancellationToken option) =
async {
let! ct = Async.CancellationToken
let uri =
System.Uri (
(match client.BaseAddress with
| null -> System.Uri "https://whatnot.com"
| v -> v),
System.Uri ("endpoint", 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! responseString = response.Content.ReadAsStringAsync ct |> Async.AwaitTask
let! responseStream = response.Content.ReadAsStreamAsync ct |> Async.AwaitTask
let! jsonNode =
System.Text.Json.Nodes.JsonNode.ParseAsync (responseStream, cancellationToken = ct)
|> Async.AwaitTask
return
new RestEase.Response<_> (
responseString,
response,
(fun () -> (MemberActivityDto.jsonParse jsonNode))
)
}
|> (fun a -> Async.StartAsTask (a, ?cancellationToken = ct))
member _.GetResponse''' (ct : CancellationToken option) =
async {
let! ct = Async.CancellationToken
let uri =
System.Uri (
(match client.BaseAddress with
| null -> System.Uri "https://whatnot.com"
| v -> v),
System.Uri ("endpoint", 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! responseString = response.Content.ReadAsStringAsync ct |> Async.AwaitTask
let! responseStream = response.Content.ReadAsStreamAsync ct |> Async.AwaitTask
let! jsonNode =
System.Text.Json.Nodes.JsonNode.ParseAsync (responseStream, cancellationToken = ct)
|> Async.AwaitTask
return
new RestEase.Response<_> (
responseString,
response,
(fun () -> (MemberActivityDto.jsonParse jsonNode))
)
} }
|> (fun a -> Async.StartAsTask (a, ?cancellationToken = ct)) |> (fun a -> Async.StartAsTask (a, ?cancellationToken = ct))
@@ -631,8 +937,7 @@ module PureGymApi =
) )
let! response = client.SendAsync (httpMessage, ct) |> Async.AwaitTask let! response = client.SendAsync (httpMessage, ct) |> Async.AwaitTask
let node = response return response
return node
} }
|> (fun a -> Async.StartAsTask (a, ?cancellationToken = ct)) |> (fun a -> Async.StartAsTask (a, ?cancellationToken = ct))
@@ -656,8 +961,7 @@ module PureGymApi =
let! response = client.SendAsync (httpMessage, ct) |> Async.AwaitTask let! response = client.SendAsync (httpMessage, ct) |> Async.AwaitTask
let response = response.EnsureSuccessStatusCode () let response = response.EnsureSuccessStatusCode ()
let node = response return response
return node
} }
|> (fun a -> Async.StartAsTask (a, ?cancellationToken = ct)) |> (fun a -> Async.StartAsTask (a, ?cancellationToken = ct))
} }
@@ -708,8 +1012,8 @@ module internal ApiWithoutBaseAddress =
let! response = client.SendAsync (httpMessage, ct) |> Async.AwaitTask let! response = client.SendAsync (httpMessage, ct) |> Async.AwaitTask
let response = response.EnsureSuccessStatusCode () let response = response.EnsureSuccessStatusCode ()
let! node = response.Content.ReadAsStringAsync ct |> Async.AwaitTask let! responseString = response.Content.ReadAsStringAsync ct |> Async.AwaitTask
return node return responseString
} }
|> (fun a -> Async.StartAsTask (a, ?cancellationToken = ct)) |> (fun a -> Async.StartAsTask (a, ?cancellationToken = ct))
} }
@@ -730,7 +1034,7 @@ module ApiWithBasePath =
/// Create a REST client. /// Create a REST client.
let make (client : System.Net.Http.HttpClient) : IApiWithBasePath = let make (client : System.Net.Http.HttpClient) : IApiWithBasePath =
{ new IApiWithBasePath with { new IApiWithBasePath with
member _.GetPathParam (parameter : string, ct : CancellationToken option) = member _.GetPathParam (parameter : string, cancellationToken : CancellationToken option) =
async { async {
let! ct = Async.CancellationToken let! ct = Async.CancellationToken
@@ -760,10 +1064,10 @@ module ApiWithBasePath =
let! response = client.SendAsync (httpMessage, ct) |> Async.AwaitTask let! response = client.SendAsync (httpMessage, ct) |> Async.AwaitTask
let response = response.EnsureSuccessStatusCode () let response = response.EnsureSuccessStatusCode ()
let! node = response.Content.ReadAsStringAsync ct |> Async.AwaitTask let! responseString = response.Content.ReadAsStringAsync ct |> Async.AwaitTask
return node return responseString
} }
|> (fun a -> Async.StartAsTask (a, ?cancellationToken = ct)) |> (fun a -> Async.StartAsTask (a, ?cancellationToken = cancellationToken))
} }
namespace PureGym namespace PureGym
@@ -806,8 +1110,71 @@ module ApiWithBasePathAndAddress =
let! response = client.SendAsync (httpMessage, ct) |> Async.AwaitTask let! response = client.SendAsync (httpMessage, ct) |> Async.AwaitTask
let response = response.EnsureSuccessStatusCode () let response = response.EnsureSuccessStatusCode ()
let! node = response.Content.ReadAsStringAsync ct |> Async.AwaitTask let! responseString = response.Content.ReadAsStringAsync ct |> Async.AwaitTask
return node return responseString
}
|> (fun a -> Async.StartAsTask (a, ?cancellationToken = ct))
}
namespace PureGym
open System
open System.Threading
open System.Threading.Tasks
open System.IO
open System.Net
open System.Net.Http
open RestEase
/// Module for constructing a REST client.
[<CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix)>]
[<RequireQualifiedAccess>]
module ApiWithHeaders =
/// Create a REST client. The input functions will be re-evaluated on every HTTP request to obtain the required values for the corresponding header properties.
let make
(someHeader : unit -> string)
(someOtherHeader : unit -> int)
(client : System.Net.Http.HttpClient)
: IApiWithHeaders
=
{ new IApiWithHeaders with
member _.SomeHeader : string = someHeader ()
member _.SomeOtherHeader : int = someOtherHeader ()
member this.GetPathParam (parameter : 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 (
"endpoint/{param}"
.Replace ("{param}", parameter.ToString () |> System.Web.HttpUtility.UrlEncode),
System.UriKind.Relative
)
)
let httpMessage =
new System.Net.Http.HttpRequestMessage (
Method = System.Net.Http.HttpMethod.Get,
RequestUri = uri
)
do httpMessage.Headers.Add ("X-Foo", this.SomeHeader.ToString ())
do httpMessage.Headers.Add ("Authorization", this.SomeOtherHeader.ToString ())
do httpMessage.Headers.Add ("Header-Name", "Header-Value")
let! response = client.SendAsync (httpMessage, ct) |> Async.AwaitTask
let response = response.EnsureSuccessStatusCode ()
let! responseString = response.Content.ReadAsStringAsync ct |> Async.AwaitTask
return responseString
} }
|> (fun a -> Async.StartAsTask (a, ?cancellationToken = ct)) |> (fun a -> Async.StartAsTask (a, ?cancellationToken = ct))
} }

View 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
}

View File

@@ -4,6 +4,7 @@
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
namespace ConsumePlugin namespace ConsumePlugin
/// Module containing JSON parsing methods for the JwtVaultAuthResponse type /// Module containing JSON parsing methods for the JwtVaultAuthResponse type
@@ -461,12 +462,7 @@ module VaultClient =
let make (client : System.Net.Http.HttpClient) : IVaultClient = let make (client : System.Net.Http.HttpClient) : IVaultClient =
{ new IVaultClient with { new IVaultClient with
member _.GetSecret member _.GetSecret
( (jwt : JwtVaultResponse, path : string, mountPoint : string, ct : CancellationToken option)
jwt : JwtVaultResponse,
path : string,
mountPoint : string,
ct : CancellationToken option
)
= =
async { async {
let! ct = Async.CancellationToken let! ct = Async.CancellationToken
@@ -501,13 +497,13 @@ module VaultClient =
let! response = client.SendAsync (httpMessage, ct) |> Async.AwaitTask let! response = client.SendAsync (httpMessage, ct) |> Async.AwaitTask
let response = response.EnsureSuccessStatusCode () let response = response.EnsureSuccessStatusCode ()
let! stream = response.Content.ReadAsStreamAsync ct |> Async.AwaitTask let! responseStream = response.Content.ReadAsStreamAsync ct |> Async.AwaitTask
let! node = let! jsonNode =
System.Text.Json.Nodes.JsonNode.ParseAsync (stream, cancellationToken = ct) System.Text.Json.Nodes.JsonNode.ParseAsync (responseStream, cancellationToken = ct)
|> Async.AwaitTask |> Async.AwaitTask
return JwtSecretResponse.jsonParse node return JwtSecretResponse.jsonParse jsonNode
} }
|> (fun a -> Async.StartAsTask (a, ?cancellationToken = ct)) |> (fun a -> Async.StartAsTask (a, ?cancellationToken = ct))
@@ -537,13 +533,13 @@ module VaultClient =
let! response = client.SendAsync (httpMessage, ct) |> Async.AwaitTask let! response = client.SendAsync (httpMessage, ct) |> Async.AwaitTask
let response = response.EnsureSuccessStatusCode () let response = response.EnsureSuccessStatusCode ()
let! stream = response.Content.ReadAsStreamAsync ct |> Async.AwaitTask let! responseStream = response.Content.ReadAsStreamAsync ct |> Async.AwaitTask
let! node = let! jsonNode =
System.Text.Json.Nodes.JsonNode.ParseAsync (stream, cancellationToken = ct) System.Text.Json.Nodes.JsonNode.ParseAsync (responseStream, cancellationToken = ct)
|> Async.AwaitTask |> Async.AwaitTask
return JwtVaultResponse.jsonParse node return JwtVaultResponse.jsonParse jsonNode
} }
|> (fun a -> Async.StartAsTask (a, ?cancellationToken = ct)) |> (fun a -> Async.StartAsTask (a, ?cancellationToken = ct))
} }

19
ConsumePlugin/List.fs Normal file
View 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
View 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

View File

@@ -8,6 +8,12 @@ type IPublicType =
abstract Mem2 : string -> int abstract Mem2 : string -> int
abstract Mem3 : x : int * ?ct : System.Threading.CancellationToken -> string 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>] [<GenerateMock>]
type internal InternalType = type internal InternalType =
abstract Mem1 : string * int -> unit abstract Mem1 : string * int -> unit
@@ -18,6 +24,11 @@ type private PrivateType =
abstract Mem1 : string * int -> unit abstract Mem1 : string * int -> unit
abstract Mem2 : string -> int abstract Mem2 : string -> int
[<GenerateMock false>]
type private PrivateTypeInternalFalse =
abstract Mem1 : string * int -> unit
abstract Mem2 : string -> int
[<GenerateMock>] [<GenerateMock>]
type VeryPublicType<'a, 'b> = type VeryPublicType<'a, 'b> =
abstract Mem1 : 'a -> 'b abstract Mem1 : 'a -> 'b

View File

@@ -68,7 +68,8 @@ type Gym =
ReopenDate : string ReopenDate : string
} }
[<WoofWare.Myriad.Plugins.JsonParse>] [<WoofWare.Myriad.Plugins.JsonParse true>]
[<WoofWare.Myriad.Plugins.JsonSerialize true>]
type Member = type Member =
{ {
Id : int Id : int

View File

@@ -11,17 +11,17 @@ open RestEase
[<WoofWare.Myriad.Plugins.HttpClient>] [<WoofWare.Myriad.Plugins.HttpClient>]
[<BaseAddress "https://whatnot.com">] [<BaseAddress "https://whatnot.com">]
type IPureGymApi = type IPureGymApi =
[<Get "v1/gyms/">] [<Get("v1/gyms/")>]
abstract GetGyms : ?ct : CancellationToken -> Task<Gym list> abstract GetGyms : ?ct : CancellationToken -> Task<Gym list>
[<Get "v1/gyms/{gym_id}/attendance">] [<Get "v1/gyms/{gym_id}/attendance">]
abstract GetGymAttendance : [<Path "gym_id">] gymId : int * ?ct : CancellationToken -> Task<GymAttendance> abstract GetGymAttendance : [<Path "gym_id">] gymId : int * ?ct : CancellationToken -> Task<GymAttendance>
[<RestEase.GetAttribute "v1/member">] [<RestEase.GetAttribute "v1/member">]
abstract GetMember : ?ct : CancellationToken -> Task<Member> abstract GetMember : ?ct : CancellationToken -> Member Task
[<RestEase.Get "v1/gyms/{gym_id}">] [<RestEase.Get "v1/gyms/{gym}">]
abstract GetGym : [<Path "gym_id">] gymId : int * ?ct : CancellationToken -> Task<Gym> abstract GetGym : [<Path>] gym : int * ?ct : CancellationToken -> Task<Gym>
[<GetAttribute "v1/member/activity">] [<GetAttribute "v1/member/activity">]
abstract GetMemberActivity : ?ct : CancellationToken -> Task<MemberActivityDto> abstract GetMemberActivity : ?ct : CancellationToken -> Task<MemberActivityDto>
@@ -29,6 +29,10 @@ type IPureGymApi =
[<Get "some/url">] [<Get "some/url">]
abstract GetUrl : ?ct : CancellationToken -> Task<UriThing> 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 // We'll use this one to check handling of absolute URIs too
[<Get "/v2/gymSessions/member">] [<Get "/v2/gymSessions/member">]
abstract GetSessions : abstract GetSessions :
@@ -50,6 +54,15 @@ type IPureGymApi =
[<Post "users/new">] [<Post "users/new">]
abstract CreateUserByteArr'' : [<Body>] user : byte array * ?ct : CancellationToken -> Task<Stream> 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">] [<Post "users/new">]
abstract CreateUserHttpContent : abstract CreateUserHttpContent :
[<Body>] user : System.Net.Http.HttpContent * ?ct : CancellationToken -> Task<string> [<Body>] user : System.Net.Http.HttpContent * ?ct : CancellationToken -> Task<string>
@@ -78,6 +91,18 @@ type IPureGymApi =
[<Get "endpoint">] [<Get "endpoint">]
abstract GetResponseMessage''' : ?ct : CancellationToken -> Task<HttpResponseMessage> 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">] [<Get "endpoint">]
[<AllowAnyStatusCode>] [<AllowAnyStatusCode>]
abstract GetWithAnyReturnCode : ?ct : CancellationToken -> Task<HttpResponseMessage> abstract GetWithAnyReturnCode : ?ct : CancellationToken -> Task<HttpResponseMessage>
@@ -96,7 +121,7 @@ type internal IApiWithoutBaseAddress =
[<BasePath "foo">] [<BasePath "foo">]
type IApiWithBasePath = type IApiWithBasePath =
[<Get "endpoint/{param}">] [<Get "endpoint/{param}">]
abstract GetPathParam : [<Path "param">] parameter : string * ?ct : CancellationToken -> Task<string> abstract GetPathParam : [<Path "param">] parameter : string * ?cancellationToken : CancellationToken -> Task<string>
[<WoofWare.Myriad.Plugins.HttpClient>] [<WoofWare.Myriad.Plugins.HttpClient>]
[<BaseAddress "https://whatnot.com">] [<BaseAddress "https://whatnot.com">]
@@ -104,3 +129,15 @@ type IApiWithBasePath =
type IApiWithBasePathAndAddress = type IApiWithBasePathAndAddress =
[<Get "endpoint/{param}">] [<Get "endpoint/{param}">]
abstract GetPathParam : [<Path "param">] parameter : string * ?ct : CancellationToken -> Task<string> 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>

View 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[]
}

781
README.md
View File

@@ -1,318 +1,463 @@
# WoofWare.Myriad.Plugins # WoofWare.Myriad.Plugins
[![NuGet version](https://img.shields.io/nuget/v/WoofWare.Myriad.Plugins.svg?style=flat-square)](https://www.nuget.org/packages/WoofWare.Myriad.Plugins) [![NuGet version](https://img.shields.io/nuget/v/WoofWare.Myriad.Plugins.svg?style=flat-square)](https://www.nuget.org/packages/WoofWare.Myriad.Plugins)
[![GitHub Actions status](https://github.com/Smaug123/WoofWare.Myriad/actions/workflows/dotnet.yaml/badge.svg)](https://github.com/Smaug123/WoofWare.Myriad/actions?query=branch%3Amain) [![GitHub Actions status](https://github.com/Smaug123/WoofWare.Myriad/actions/workflows/dotnet.yaml/badge.svg)](https://github.com/Smaug123/WoofWare.Myriad/actions?query=branch%3Amain)
[![License file](https://img.shields.io/github/license/Smaug123/WoofWare.Myriad)](./LICENSE) [![License file](https://img.shields.io/github/license/Smaug123/WoofWare.Myriad)](./LICENSE)
![Project logo: the face of a cartoon Shiba Inu, staring with powerful cyborg eyes directly at the viewer, with a background of stylised plugs.](./WoofWare.Myriad.Plugins/logo.png) ![Project logo: the face of a cartoon Shiba Inu, staring with powerful cyborg eyes directly at the viewer, with a background of stylised plugs.](./WoofWare.Myriad.Plugins/logo.png)
Some helpers in [Myriad](https://github.com/MoiraeSoftware/myriad/) which might be useful. 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. These are currently somewhat experimental, and I personally am their primary customer.
The `RemoveOptions` generator in particular is extremely half-baked. The `RemoveOptions` generator in particular is extremely half-baked.
Currently implemented: 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,
* `JsonParse` (to stamp out `jsonParse : JsonNode -> 'T` methods); 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;
* `RemoveOptions` (to strip `option` modifiers from a type). 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.
* `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). Currently implemented:
## `JsonParse` * `JsonParse` (to stamp out `jsonParse : JsonNode -> 'T` methods);
* `JsonSerialize` (to stamp out `toJsonNode : 'T -> JsonNode` methods);
Takes records like this: * `RemoveOptions` (to strip `option` modifiers from a type).
* `HttpClient` (to stamp out a [RestEase](https://github.com/canton7/RestEase)-style HTTP client).
```fsharp * `GenerateMock` (to stamp out a record type corresponding to an interface).
[<WoofWare.Myriad.Plugins.JsonParse>] * `CreateCatamorphism` (to stamp out a non-stack-overflowing [catamorphism](https://fsharpforfunandprofit.com/posts/recursive-types-and-folds/) for a discriminated union).
type InnerType =
{ ## `JsonParse`
[<JsonPropertyName "something">]
Thing : string Takes records like this:
}
```fsharp
/// My whatnot [<WoofWare.Myriad.Plugins.JsonParse>]
[<WoofWare.Myriad.Plugins.JsonParse>] type InnerType =
type JsonRecordType = {
{ [<JsonPropertyName "something">]
/// A thing! Thing : string
A : int }
/// Another thing!
B : string /// My whatnot
[<System.Text.Json.Serialization.JsonPropertyName "hi">] [<WoofWare.Myriad.Plugins.JsonParse>]
C : int list type JsonRecordType =
D : InnerType {
} /// A thing!
A : int
``` /// Another thing!
B : string
and stamps out parsing methods like this: [<System.Text.Json.Serialization.JsonPropertyName "hi">]
C : int list
```fsharp D : InnerType
/// Module containing JSON parsing methods for the InnerType type }
[<RequireQualifiedAccess>]
[<CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix)>] ```
module InnerType =
/// Parse from a JSON node. and stamps out parsing methods like this:
let jsonParse (node: System.Text.Json.Nodes.JsonNode) : InnerType =
let Thing = node.["something"].AsValue().GetValue<string>() ```fsharp
{ Thing = Thing } /// Module containing JSON parsing methods for the InnerType type
namespace UsePlugin [<RequireQualifiedAccess>]
[<CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix)>]
/// Module containing JSON parsing methods for the JsonRecordType type module InnerType =
[<RequireQualifiedAccess>] /// Parse from a JSON node.
[<CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix)>] let jsonParse (node: System.Text.Json.Nodes.JsonNode) : InnerType =
module JsonRecordType = let Thing = node.["something"].AsValue().GetValue<string>()
/// Parse from a JSON node. { Thing = Thing }
let jsonParse (node: System.Text.Json.Nodes.JsonNode) : JsonRecordType = namespace UsePlugin
let D = InnerType.jsonParse node.["d"]
/// Module containing JSON parsing methods for the JsonRecordType type
let C = [<RequireQualifiedAccess>]
node.["hi"].AsArray() |> Seq.map (fun elt -> elt.GetValue<int>()) |> List.ofSeq [<CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix)>]
module JsonRecordType =
let B = node.["b"].AsValue().GetValue<string>() /// Parse from a JSON node.
let A = node.["a"].AsValue().GetValue<int>() let jsonParse (node: System.Text.Json.Nodes.JsonNode) : JsonRecordType =
{ A = A; B = B; C = C; D = D } let D = InnerType.jsonParse node.["d"]
```
let C =
### What's the point? node.["hi"].AsArray() |> Seq.map (fun elt -> elt.GetValue<int>()) |> List.ofSeq
`System.Text.Json`, in a `PublishAot` context, relies on C# source generators. let B = node.["b"].AsValue().GetValue<string>()
The default reflection-heavy implementations have the necessary code trimmed away, and result in a runtime exception. let A = node.["a"].AsValue().GetValue<int>()
But C# source generators [are entirely unsupported in F#](https://github.com/dotnet/fsharp/issues/14300). { A = A; B = B; C = C; D = D }
```
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. 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.
### Limitations 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.
This source generator is enough for what I first wanted to use it for.
However, there is *far* more that could be done. ### What's the point?
* Make it possible to give an exact format and cultural info in date and time parsing. `System.Text.Json`, in a `PublishAot` context, relies on C# source generators.
* Make it possible to reject parsing if extra fields are present. The default reflection-heavy implementations have the necessary code trimmed away, and result in a runtime exception.
* Generally support all the `System.Text.Json` attributes. But C# source generators [are entirely unsupported in F#](https://github.com/dotnet/fsharp/issues/14300).
## `RemoveOptions` 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.
Takes a record like this:
### Limitations
```fsharp
type Foo = This source generator is enough for what I first wanted to use it for.
{ However, there is *far* more that could be done.
A : int option
B : string * Make it possible to give an exact format and cultural info in date and time parsing.
C : float list * 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).
and stamps out a record like this:
## `JsonSerialize`
```fsharp
[<RequireQualifiedAccess>] Takes records like this:
module Foo = ```fsharp
type Short = [<WoofWare.Myriad.Plugins.JsonSerialize true>]
{ type InnerTypeWithBoth =
A : int {
B : string [<JsonPropertyName("it's-a-me")>]
C : float list Thing : string
} ReadOnlyDict : IReadOnlyDictionary<string, Uri list>
``` }
```
### What's the point?
and stamps out modules like this:
The motivating example is argument parsing. ```fsharp
An argument parser naturally wants to express "the user did not supply this, so I will provide a default". module InnerTypeWithBoth =
But it's not a very ergonomic experience for the programmer to deal with all these options, let toJsonNode (input : InnerTypeWithBoth) : System.Text.Json.Nodes.JsonNode =
so this Myriad generator stamps out a type *without* any options, let node = System.Text.Json.Nodes.JsonObject ()
and also stamps out an appropriate constructor function.
do
### Limitations node.Add (("it's-a-me"), System.Text.Json.Nodes.JsonValue.Create<string> input.Thing)
This generator is *far* from where I want it, because I haven't really spent any time on it. node.Add (
"ReadOnlyDict",
* It really wants to be able to recurse into the types within the record, to strip options from them. (fun field ->
* It needs some sort of attribute to mark a field as *not* receiving this treatment. let ret = System.Text.Json.Nodes.JsonObject ()
* What do we do about discriminated unions?
for (KeyValue (key, value)) in field do
## `HttpClient` ret.Add (key.ToString (), System.Text.Json.Nodes.JsonValue.Create<Uri> value)
Takes a type like this: ret
) input.Map
```fsharp )
[<WoofWare.Myriad.Plugins.HttpClient>]
type IPureGymApi = node
[<Get "v1/gyms/">] ```
abstract GetGyms : ?ct : CancellationToken -> Task<Gym list>
As in `JsonParse`, you can optionally supply the boolean `true` to the attribute,
[<Get "v1/gyms/{gym_id}/attendance">] which will cause Myriad to stamp out an extension method rather than a module with the same name as the type.
abstract GetGymAttendance : [<Path "gym_id">] gymId : int * ?ct : CancellationToken -> Task<GymAttendance>
The same limitations generally apply to `JsonSerialize` as do to `JsonParse`.
[<Get "v1/member">]
abstract GetMember : ?ct : CancellationToken -> Task<Member> 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).
[<Get "v1/gyms/{gym_id}">] ## `RemoveOptions`
abstract GetGym : [<Path "gym_id">] gymId : int * ?ct : CancellationToken -> Task<Gym>
Takes a record like this:
[<Get "v1/member/activity">]
abstract GetMemberActivity : ?ct : CancellationToken -> Task<MemberActivityDto> ```fsharp
type Foo =
[<Get "v2/gymSessions/member">] {
abstract GetSessions : A : int option
[<Query>] fromDate : DateTime * [<Query>] toDate : DateTime * ?ct : CancellationToken -> Task<Sessions> B : string
``` C : float list
}
and stamps out a type like this: ```
```fsharp and stamps out a record like this:
/// Module for constructing a REST client.
[<CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix)>] ```fsharp
[<RequireQualifiedAccess>] [<RequireQualifiedAccess>]
module PureGymApi = module Foo =
/// Create a REST client. type Short =
let make (client : System.Net.Http.HttpClient) : IPureGymApi = {
{ new IPureGymApi with A : int
member _.GetGyms (ct : CancellationToken option) = B : string
async { C : float list
let! ct = Async.CancellationToken }
```
let httpMessage =
new System.Net.Http.HttpRequestMessage ( ### What's the point?
Method = System.Net.Http.HttpMethod.Get,
RequestUri = System.Uri (client.BaseAddress.ToString () + "v1/gyms/") 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,
let! response = client.SendAsync (httpMessage, ct) |> Async.AwaitTask so this Myriad generator stamps out a type *without* any options,
let response = response.EnsureSuccessStatusCode () and also stamps out an appropriate constructor function.
let! stream = response.Content.ReadAsStreamAsync ct |> Async.AwaitTask
### Limitations
let! node =
System.Text.Json.Nodes.JsonNode.ParseAsync (stream, cancellationToken = ct) This generator is *far* from where I want it, because I haven't really spent any time on it.
|> Async.AwaitTask
* It really wants to be able to recurse into the types within the record, to strip options from them.
return node.AsArray () |> Seq.map (fun elt -> Gym.jsonParse elt) |> List.ofSeq * It needs some sort of attribute to mark a field as *not* receiving this treatment.
} * What do we do about discriminated unions?
|> (fun a -> Async.StartAsTask (a, ?cancellationToken = ct))
## `HttpClient`
// (more methods here)
} Takes a type like this:
```
```fsharp
### What's the point? [<WoofWare.Myriad.Plugins.HttpClient>]
type IPureGymApi =
The motivating example is again ahead-of-time compilation: we wish to avoid the reflection which RestEase does. [<Get "v1/gyms/">]
abstract GetGyms : ?ct : CancellationToken -> Task<Gym list>
### Limitations
[<Get "v1/gyms/{gym_id}/attendance">]
RestEase is complex, and handles a lot of different stuff. abstract GetGymAttendance : [<Path "gym_id">] gymId : int * ?ct : CancellationToken -> Task<GymAttendance>
* If you set the `BaseAddress` on your input `HttpClient`, make sure to end with a trailing slash [<Get "v1/member">]
on any trailing directories (so `"blah/foo/"` rather than `"blah/foo"`). abstract GetMember : ?ct : CancellationToken -> Task<Member>
We combine URIs using `UriKind.Relative`, so without a trailing slash, the last component may be chopped off.
* Parameters are serialised solely with `ToString`, and there's no control over this; [<Get "v1/gyms/{gym_id}">]
nor is there control over encoding in any sense. abstract GetGym : [<Path "gym_id">] gymId : int * ?ct : CancellationToken -> Task<Gym>
* Deserialisation follows the same logic as the `JsonParse` generator,
and it generally assumes you're using types which `JsonParse` is applied to. [<Get "v1/member/activity">]
* Headers are not yet supported. abstract GetMemberActivity : ?ct : CancellationToken -> Task<MemberActivityDto>
* Anonymous parameters are currently forbidden.
[<Get "v2/gymSessions/member">]
There are also some design decisions: abstract GetSessions :
[<Query>] fromDate : DateTime * [<Query>] toDate : DateTime * ?ct : CancellationToken -> Task<Sessions>
* Every function must take an optional `CancellationToken` (which is good practice anyway); ```
so arguments are forced to be tupled.
and stamps out a type like this:
## `GenerateMock`
```fsharp
Takes a type like this: /// Module for constructing a REST client.
[<CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix)>]
```fsharp [<RequireQualifiedAccess>]
[<GenerateMock>] module PureGymApi =
type IPublicType = /// Create a REST client.
abstract Mem1 : string * int -> string list let make (client : System.Net.Http.HttpClient) : IPureGymApi =
abstract Mem2 : string -> int { new IPureGymApi with
``` member _.GetGyms (ct : CancellationToken option) =
async {
and stamps out a type like this: let! ct = Async.CancellationToken
```fsharp let httpMessage =
/// Mock record type for an interface new System.Net.Http.HttpRequestMessage (
type internal PublicTypeMock = Method = System.Net.Http.HttpMethod.Get,
{ RequestUri = System.Uri (client.BaseAddress.ToString () + "v1/gyms/")
Mem1 : string * int -> string list )
Mem2 : string -> int
} let! response = client.SendAsync (httpMessage, ct) |> Async.AwaitTask
let response = response.EnsureSuccessStatusCode ()
static member Empty : PublicTypeMock = let! stream = response.Content.ReadAsStreamAsync ct |> Async.AwaitTask
{
Mem1 = (fun x -> raise (System.NotImplementedException "Unimplemented mock function")) let! node =
Mem2 = (fun x -> raise (System.NotImplementedException "Unimplemented mock function")) System.Text.Json.Nodes.JsonNode.ParseAsync (stream, cancellationToken = ct)
} |> Async.AwaitTask
interface IPublicType with return node.AsArray () |> Seq.map (fun elt -> Gym.jsonParse elt) |> List.ofSeq
member this.Mem1 (arg0, arg1) = this.Mem1 (arg0, arg1) }
member this.Mem2 (arg0) = this.Mem2 (arg0) |> (fun a -> Async.StartAsTask (a, ?cancellationToken = ct))
```
// (more methods here)
### 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. ### What's the point?
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. The motivating example is again ahead-of-time compilation: we wish to avoid the reflection which RestEase does.
### Limitations ### Features
* We currently only support interfaces with tupled arguments. * Variable and constant header values are supported:
* We make the resulting record type at most internal (never public), since this is intended only to be used in tests. see [the definition of `IApiWithHeaders`](./ConsumePlugin/RestApiExample.fs).
You will therefore need an `AssemblyInfo.fs` file [like the one in WoofWare.Myriad's own tests](./ConsumePlugin/AssemblyInfo.fs).
### Limitations
# Detailed examples
RestEase is complex, and handles a lot of different stuff.
See the tests.
For example, [PureGymDto.fs](./ConsumePlugin/PureGymDto.fs) is a real-world set of DTOs. * 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"`).
## How to use 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,
* In your `.fsproj` file, define a helper variable so that subsequent steps don't all have to be kept in sync: and you can't control the serialisation. You can't yet serialise e.g. a primitive type this way (other than `String`);
```xml all body parameters must be types which have a suitable `toJsonNode : 'a -> JsonNode` method.
<PropertyGroup> * Deserialisation follows the same logic as the `JsonParse` generator,
<WoofWareMyriadPluginVersion>1.1.5</WoofWareMyriadPluginVersion> and it generally assumes you're using types which `JsonParse` is applied to.
</PropertyGroup> * Anonymous parameters are currently forbidden.
```
* Take a reference on `WoofWare.Myriad.Plugins`: There are also some design decisions:
```xml
<ItemGroup> * Every function must take an optional `CancellationToken` (which is good practice anyway);
<PackageReference Include="WoofWare.Myriad.Plugins" Version="$(WoofWareMyriadPluginVersion)" /> so arguments are forced to be tupled.
</ItemGroup> * The `[<Optional>]` attribute is not supported and will probably not be supported, because I consider it to be cursed.
```
* Point Myriad to the DLL within the NuGet package which is the source of the plugins: ## `GenerateMock`
```xml
<ItemGroup> Takes a type like this:
<MyriadSdkGenerator Include="$(NuGetPackageRoot)/woofware.myriad.plugins/$(WoofWareMyriadPluginVersion)/lib/net6.0/WoofWare.Myriad.Plugins.dll" />
</ItemGroup> ```fsharp
``` [<GenerateMock>]
type IPublicType =
Now you are ready to start using the generators. abstract Mem1 : string * int -> string list
For example, this specifies that Myriad is to use the contents of `Client.fs` to generate the file `GeneratedClient.fs`: abstract Mem2 : string -> int
```
```xml
<ItemGroup> and stamps out a type like this:
<Compile Include="Client.fs" />
<Compile Include="GeneratedClient.fs"> ```fsharp
<MyriadFile>Client.fs</MyriadFile> /// Mock record type for an interface
</Compile> type internal PublicTypeMock =
</ItemGroup> {
``` Mem1 : string * int -> string list
Mem2 : string -> int
### Myriad Gotchas }
* MsBuild doesn't always realise that it needs to invoke Myriad during rebuild. static member Empty : PublicTypeMock =
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. Mem1 = (fun x -> raise (System.NotImplementedException "Unimplemented mock function"))
* [Fantomas](https://github.com/fsprojects/fantomas), the F# source formatter which powers Myriad, Mem2 = (fun x -> raise (System.NotImplementedException "Unimplemented mock function"))
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. interface IPublicType with
So Myriad's output is formatted without respect to any conventions which may hold in the rest of your repository. member this.Mem1 (arg0, arg1) = this.Mem1 (arg0, arg1)
You should probably add these files to your [fantomasignore](https://github.com/fsprojects/fantomas/blob/a999b77ca5a024fbc3409955faac797e29b39d27/docs/docs/end-users/IgnoreFiles.md) member this.Mem2 (arg0) = this.Mem2 (arg0)
if you use Fantomas to format your repo; ```
the alternative is to manually reformat every time Myriad changes the generated files.
### 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.

View 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 ()

View 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

View File

@@ -0,0 +1,26 @@
namespace WoofWare.Myriad.Plugins.Attributes.Test
open NUnit.Framework
open WoofWare.Myriad.Plugins
open ApiSurface
[<TestFixture>]
module TestSurface =
let assembly = typeof<RemoveOptionsAttribute>.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.Attributes"
*)
[<Test ; Explicit>]
let ``Update API surface`` () =
ApiSurface.writeAssemblyBaseline assembly
[<Test>]
let ``Ensure public API is fully documented`` () =
DocCoverage.assertFullyDocumented assembly

View File

@@ -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.30" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.9.0"/>
<PackageReference Include="NUnit" Version="3.13.3"/>
<PackageReference Include="NUnit3TestAdapter" Version="4.5.0"/>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\WoofWare.Myriad.Plugins.Attributes.fsproj" />
</ItemGroup>
</Project>

View File

@@ -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>

View File

@@ -0,0 +1,7 @@
{
"version": "2.2",
"publicReleaseRefSpec": [
"^refs/heads/main$"
],
"pathFilters": null
}

View File

@@ -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

View File

@@ -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.

View 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

View 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

View File

@@ -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

View File

@@ -4,7 +4,6 @@ open System
open System.IO open System.IO
open System.Net open System.Net
open System.Net.Http open System.Net.Http
open System.Text.Json.Nodes
open NUnit.Framework open NUnit.Framework
open PureGym open PureGym
open FsUnitTyped open FsUnitTyped
@@ -103,3 +102,87 @@ module TestBodyParam =
let buf = Array.zeroCreate 10 let buf = Array.zeroCreate 10
let written = observedContent.ReadAtLeast (buf.AsSpan (), 5, false) let written = observedContent.ReadAtLeast (buf.AsSpan (), 5, false)
buf |> Array.take written |> shouldEqual contents 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\""

View File

@@ -209,10 +209,7 @@ module TestPureGymRestApi =
[<TestCaseSource(nameof sessionsCases)>] [<TestCaseSource(nameof sessionsCases)>]
let ``Test GetSessions`` 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 = let proc (message : HttpRequestMessage) : HttpResponseMessage Async =
async { async {
@@ -260,3 +257,37 @@ module TestPureGymRestApi =
uri.ToString () |> shouldEqual "https://patrick@en.wikipedia.org/wiki/foo" uri.ToString () |> shouldEqual "https://patrick@en.wikipedia.org/wiki/foo"
uri.UserInfo |> shouldEqual "patrick" uri.UserInfo |> shouldEqual "patrick"
uri.Host |> shouldEqual "en.wikipedia.org" 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

View File

@@ -86,3 +86,36 @@ module TestReturnTypes =
| _ -> failwith $"unrecognised case: %s{case}" | _ -> failwith $"unrecognised case: %s{case}"
Object.ReferenceEquals (message, Option.get responseMessage) |> shouldEqual true 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

View File

@@ -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

View 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
)

View File

@@ -6,7 +6,7 @@ open ApiSurface
[<TestFixture>] [<TestFixture>]
module TestSurface = module TestSurface =
let assembly = typeof<RemoveOptionsAttribute>.Assembly let assembly = typeof<RemoveOptionsGenerator>.Assembly
[<Test>] [<Test>]
let ``Ensure API surface has not been modified`` () = ApiSurface.assertIdentical assembly let ``Ensure API surface has not been modified`` () = ApiSurface.assertIdentical assembly

View File

@@ -19,19 +19,26 @@
<Compile Include="TestHttpClient\TestBasePath.fs" /> <Compile Include="TestHttpClient\TestBasePath.fs" />
<Compile Include="TestHttpClient\TestBodyParam.fs" /> <Compile Include="TestHttpClient\TestBodyParam.fs" />
<Compile Include="TestHttpClient\TestVaultClient.fs" /> <Compile Include="TestHttpClient\TestVaultClient.fs" />
<Compile Include="TestHttpClient\TestVariableHeader.fs" />
<Compile Include="TestMockGenerator\TestMockGenerator.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="TestRemoveOptions.fs"/>
<Compile Include="TestSurface.fs"/> <Compile Include="TestSurface.fs"/>
<None Include="../.github/workflows/dotnet.yaml" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="ApiSurface" Version="4.0.25"/> <PackageReference Include="ApiSurface" Version="4.0.30"/>
<PackageReference Include="FsCheck" Version="2.16.6"/> <PackageReference Include="FsCheck" Version="2.16.6"/>
<PackageReference Include="FsUnit" Version="6.0.0"/> <PackageReference Include="FsUnit" Version="6.0.0"/>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0"/> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.9.0"/>
<PackageReference Include="NUnit" Version="4.0.1"/> <PackageReference Include="NUnit" Version="4.0.1"/>
<PackageReference Include="NUnit3TestAdapter" Version="4.5.0"/> <PackageReference Include="NUnit3TestAdapter" Version="4.5.0"/>
<PackageReference Include="NUnit.Analyzers" Version="3.10.0"/>
<PackageReference Include="coverlet.collector" Version="6.0.0"/> <PackageReference Include="coverlet.collector" Version="6.0.0"/>
</ItemGroup> </ItemGroup>

View File

@@ -33,11 +33,29 @@ type internal MemberInfo =
IsMutable : 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 = type internal InterfaceType =
{ {
Attributes : SynAttribute list Attributes : SynAttribute list
Name : LongIdent Name : LongIdent
Members : MemberInfo list Members : MemberInfo list
Properties : PropertyInfo list
Generics : SynTyparDecls option Generics : SynTyparDecls option
Accessibility : SynAccess option Accessibility : SynAccess option
} }
@@ -52,6 +70,30 @@ type internal RecordType =
Accessibility : SynAccess 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>] [<RequireQualifiedAccess>]
module internal AstHelper = module internal AstHelper =
@@ -104,6 +146,12 @@ module internal AstHelper =
true true
| _ -> false | _ -> false
let isResponseIdent (ident : SynLongIdent) : bool =
match ident.LongIdent |> List.map _.idText with
| [ "Response" ]
| [ "RestEase" ; "Response" ] -> true
| _ -> false
let isMapIdent (ident : SynLongIdent) : bool = let isMapIdent (ident : SynLongIdent) : bool =
match ident.LongIdent |> List.map _.idText with match ident.LongIdent |> List.map _.idText with
| [ "Map" ] -> true | [ "Map" ] -> true
@@ -224,6 +272,108 @@ module internal AstHelper =
((toFun (List.map fst inputArgs) inputRet), hasParen) :: args, ret ((toFun (List.map fst inputArgs) inputRet), hasParen) :: args, ret
| _ -> [], ty | _ -> [], 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 ...` /// Assumes that the input type is an ObjectModel, i.e. a `type Foo = member ...`
let parseInterface (interfaceType : SynTypeDefn) : InterfaceType = let parseInterface (interfaceType : SynTypeDefn) : InterfaceType =
let (SynTypeDefn (SynComponentInfo (attrs, typars, _, interfaceName, _, _, accessibility, _), let (SynTypeDefn (SynComponentInfo (attrs, typars, _, interfaceName, _, _, accessibility, _),
@@ -236,110 +386,97 @@ module internal AstHelper =
let attrs = attrs |> List.collect (fun s -> s.Attributes) let attrs = attrs |> List.collect (fun s -> s.Attributes)
let members = let members, properties =
match synTypeDefnRepr with match synTypeDefnRepr with
| SynTypeDefnRepr.ObjectModel (_kind, members, _) -> | SynTypeDefnRepr.ObjectModel (_kind, members, _) ->
members members
|> List.map (fun defn -> |> List.map (fun defn ->
match defn with match defn with
| SynMemberDefn.AbstractSlot (slotSig, flags, _, _) -> | SynMemberDefn.AbstractSlot (slotSig, flags, _, _) -> parseMember slotSig flags
match flags.MemberKind with
| SynMemberKind.Member -> ()
| kind -> failwithf "Unrecognised member kind: %+A" kind
if not flags.IsInstance then
failwith "member was not an instance member"
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 (fun attr -> attr.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
}
)
{
ReturnType = ret
Args = args
Identifier = ident
Attributes = attrs
XmlDoc = Some xmlDoc
Accessibility = accessibility
IsInline = isInline
IsMutable = isMutable
}
| _ -> failwith $"Unrecognised member definition: %+A{defn}" | _ -> failwith $"Unrecognised member definition: %+A{defn}"
) )
| _ -> failwith $"Unrecognised SynTypeDefnRepr for an interface type: %+A{synTypeDefnRepr}" | _ -> failwith $"Unrecognised SynTypeDefnRepr for an interface type: %+A{synTypeDefnRepr}"
|> List.partitionChoice
{ {
Members = members Members = members
Properties = properties
Name = interfaceName Name = interfaceName
Attributes = attrs Attributes = attrs
Generics = typars Generics = typars
Accessibility = accessibility 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>] [<AutoOpen>]
module internal SynTypePatterns = module internal SynTypePatterns =
@@ -362,6 +499,12 @@ module internal SynTypePatterns =
| SynType.Array (1, innerType, _) -> Some innerType | SynType.Array (1, innerType, _) -> Some innerType
| _ -> None | _ -> 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) = let (|DictionaryType|_|) (fieldType : SynType) =
match fieldType with match fieldType with
| SynType.App (SynType.LongIdent ident, _, [ key ; value ], _, _, _, _) when AstHelper.isDictionaryIdent ident -> | SynType.App (SynType.LongIdent ident, _, [ key ; value ], _, _, _, _) when AstHelper.isDictionaryIdent ident ->
@@ -393,7 +536,9 @@ module internal SynTypePatterns =
match fieldType with match fieldType with
| SynType.LongIdent ident -> | SynType.LongIdent ident ->
match ident.LongIdent with 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
| _ -> None | _ -> None
@@ -416,6 +561,15 @@ module internal SynTypePatterns =
| _ -> None | _ -> None
| _ -> 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 = let (|HttpResponseMessage|_|) (fieldType : SynType) : unit option =
match fieldType with match fieldType with
| SynType.LongIdent ident -> | SynType.LongIdent ident ->

File diff suppressed because it is too large Load Diff

View File

@@ -2,26 +2,26 @@ namespace WoofWare.Myriad.Plugins
open System open System
open System.Net.Http open System.Net.Http
open System.Text
open Fantomas.FCS.Syntax open Fantomas.FCS.Syntax
open Fantomas.FCS.SyntaxTrivia
open Fantomas.FCS.Xml open Fantomas.FCS.Xml
open Myriad.Core open Myriad.Core
/// 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 ()
[<RequireQualifiedAccess>] [<RequireQualifiedAccess>]
module internal HttpClientGenerator = module internal HttpClientGenerator =
open Fantomas.FCS.Text.Range open Fantomas.FCS.Text.Range
open Myriad.Core.Ast open Myriad.Core.Ast
[<RequireQualifiedAccess>]
type PathSpec =
| Verbatim of string
| MatchArgName
type HttpAttribute = type HttpAttribute =
// TODO: Format parameter to these attrs // TODO: Format parameter to these attrs
| Query of string option | Query of string option
| Path of string | Path of PathSpec
| Body | Body
type Parameter = type Parameter =
@@ -52,8 +52,8 @@ module internal HttpClientGenerator =
{ {
/// E.g. HttpMethod.Get /// E.g. HttpMethod.Get
HttpMethod : HttpMethod HttpMethod : HttpMethod
/// E.g. "v1/gyms/{gym_id}/attendance" /// E.g. SynExpr.Const "v1/gyms/{gym_id}/attendance"
UrlTemplate : string UrlTemplate : SynExpr
TaskReturnType : SynType TaskReturnType : SynType
Args : Parameter list Args : Parameter list
Identifier : Ident Identifier : Ident
@@ -74,8 +74,8 @@ module internal HttpClientGenerator =
elif m = HttpMethod.Trace then "Trace" elif m = HttpMethod.Trace then "Trace"
else failwith $"Unrecognised method: %+A{m}" else failwith $"Unrecognised method: %+A{m}"
/// E.g. converts `[<Get "blah">]` to (HttpMethod.Get, "blah") /// E.g. converts `[<Get "blah">]` to (HttpMethod.Get, SynExpr.Const "blah")
let extractHttpInformation (attrs : SynAttribute list) : HttpMethod * string = let extractHttpInformation (attrs : SynAttribute list) : HttpMethod * SynExpr =
let matchingAttrs = let matchingAttrs =
attrs attrs
|> List.choose (fun attr -> |> List.choose (fun attr ->
@@ -116,15 +116,25 @@ module internal HttpClientGenerator =
) )
match matchingAttrs with match matchingAttrs with
| [ (meth, arg) ] -> | [ (meth, arg) ] -> meth, arg
match arg with
| SynExpr.Const (SynConst.String (text, SynStringKind.Regular, _), _) -> meth, text
| arg ->
failwith $"Unrecognised AST member in attribute argument. Only regular strings are supported: %+A{arg}"
| [] -> failwith "Required exactly one recognised RestEase attribute on member, but got none" | [] -> failwith "Required exactly one recognised RestEase attribute on member, but got none"
| matchingAttrs -> | matchingAttrs ->
failwith $"Required exactly one recognised RestEase attribute on member, but got %i{matchingAttrs.Length}" failwith $"Required exactly one recognised RestEase attribute on member, but got %i{matchingAttrs.Length}"
/// Get the args associated with the Header attributes within the list.
let extractHeaderInformation (attrs : SynAttribute list) : SynExpr list list =
attrs
|> List.choose (fun attr ->
match attr.TypeName.AsString with
| "Header"
| "RestEase.Header" ->
match attr.ArgExpr with
| SynExpr.Paren (SynExpr.Tuple (_, [ v1 ; v2 ], _, _), _, _, _) ->
Some [ SynExpr.stripOptionalParen v1 ; SynExpr.stripOptionalParen v2 ]
| e -> Some [ SynExpr.stripOptionalParen e ]
| _ -> None
)
let shouldAllowAnyStatusCode (attrs : SynAttribute list) : bool = let shouldAllowAnyStatusCode (attrs : SynAttribute list) : bool =
attrs attrs
|> List.exists (fun attr -> |> List.exists (fun attr ->
@@ -136,7 +146,14 @@ module internal HttpClientGenerator =
| _ -> false | _ -> false
) )
let constructMember (info : MemberInfo) : SynMemberDefn = /// constantHeaders are a list of (headerName, headerValue)
/// variableHeaders are a list of (headerName, selfPropertyToGetValueOf)
let constructMember
(constantHeaders : (SynExpr * SynExpr) list)
(variableHeaders : (SynExpr * Ident) list)
(info : MemberInfo)
: SynMemberDefn
=
let valInfo = let valInfo =
SynValInfo.SynValInfo ( SynValInfo.SynValInfo (
[ [
@@ -166,27 +183,34 @@ module internal HttpClientGenerator =
None None
) )
let args =
info.Args
|> List.map (fun arg ->
let argName =
match arg.Id with
| None -> failwith "TODO: create an arg name"
| Some id -> id
let argType =
if arg.IsOptional then
SynType.CreateApp (
SynType.CreateLongIdent (SynLongIdent.CreateString "option"),
[ arg.Type ],
isPostfix = true
)
else
arg.Type
argName, SynPat.CreateTyped (SynPat.CreateNamed argName, argType)
)
let cancellationTokenArg =
match List.tryLast args with
| None -> failwith $"expected an optional cancellation token as final arg in %s{info.Identifier.idText}"
| Some (arg, _) -> arg
let argPats = let argPats =
let args = let args = args |> List.map snd
info.Args
|> List.map (fun arg ->
let argName =
match arg.Id with
| None -> failwith "TODO: create an arg name"
| Some id -> id
let argType =
if arg.IsOptional then
SynType.CreateApp (
SynType.CreateLongIdent (SynLongIdent.CreateString "option"),
[ arg.Type ],
isPostfix = true
)
else
arg.Type
SynPat.CreateTyped (SynPat.CreateNamed argName, argType)
)
SynPat.Tuple (false, args, List.replicate (args.Length - 1) range0, range0) SynPat.Tuple (false, args, List.replicate (args.Length - 1) range0, range0)
|> SynPat.CreateParen |> SynPat.CreateParen
@@ -194,8 +218,10 @@ module internal HttpClientGenerator =
|> SynArgPats.Pats |> SynArgPats.Pats
let headPat = let headPat =
let thisIdent = if variableHeaders.IsEmpty then "_" else "this"
SynPat.LongIdent ( SynPat.LongIdent (
SynLongIdent.CreateFromLongIdent [ Ident.Create "_" ; info.Identifier ], SynLongIdent.CreateFromLongIdent [ Ident.Create thisIdent ; info.Identifier ],
None, None,
None, None,
argPats, argPats,
@@ -204,23 +230,28 @@ module internal HttpClientGenerator =
) )
let requestUriTrailer = let requestUriTrailer =
(SynExpr.CreateConstString info.UrlTemplate, info.Args) (info.UrlTemplate, info.Args)
||> List.fold (fun template arg -> ||> List.fold (fun template arg ->
(template, arg.Attributes) (template, arg.Attributes)
||> List.fold (fun template attr -> ||> List.fold (fun template attr ->
match attr with match attr with
| HttpAttribute.Path s -> | HttpAttribute.Path spec ->
let varName = let varName =
match arg.Id with match arg.Id with
| None -> failwith "TODO: anonymous args" | None -> failwith "TODO: anonymous args"
| Some id -> id | Some id -> id
let substituteId =
match spec with
| PathSpec.Verbatim s -> s
| PathSpec.MatchArgName -> varName.idText
template template
|> SynExpr.callMethodArg |> SynExpr.callMethodArg
"Replace" "Replace"
(SynExpr.CreateParenedTuple (SynExpr.CreateParenedTuple
[ [
SynExpr.CreateConstString ("{" + s + "}") SynExpr.CreateConstString ("{" + substituteId + "}")
SynExpr.callMethod "ToString" (SynExpr.CreateIdent varName) SynExpr.callMethod "ToString" (SynExpr.CreateIdent varName)
|> SynExpr.pipeThroughFunction ( |> SynExpr.pipeThroughFunction (
SynExpr.CreateLongIdent ( SynExpr.CreateLongIdent (
@@ -362,7 +393,7 @@ module internal HttpClientGenerator =
arg.Attributes arg.Attributes
|> List.choose (fun attr -> |> List.choose (fun attr ->
match attr with match attr with
| Body -> Some arg | HttpAttribute.Body -> Some arg
| _ -> None | _ -> None
) )
) )
@@ -399,15 +430,47 @@ module internal HttpClientGenerator =
let returnExpr = let returnExpr =
match info.TaskReturnType with match info.TaskReturnType with
| HttpResponseMessage | HttpResponseMessage -> SynExpr.CreateIdentString "response"
| String | String -> SynExpr.CreateIdentString "responseString"
| Stream -> SynExpr.CreateIdentString "node" | Stream -> SynExpr.CreateIdentString "responseStream"
| RestEaseResponseType contents ->
let deserialiser =
SynExpr.CreateLambda (
[ SynPat.CreateConst SynConst.Unit ],
SynExpr.CreateParen (
JsonParseGenerator.parseNode
None
JsonParseGenerator.JsonParseOption.None
contents
(SynExpr.CreateIdentString "jsonNode")
)
)
// new RestEase.Response (content : string, response : HttpResponseMessage, deserialiser : unit -> 'T)
SynExpr.New (
false,
SynType.App (
SynType.CreateLongIdent (SynLongIdent.Create [ "RestEase" ; "Response" ]),
Some range0,
[ SynType.Anon range0 ],
[],
Some range0,
false,
range0
),
SynExpr.CreateParenedTuple
[
SynExpr.CreateIdentString "responseString"
SynExpr.CreateIdentString "response"
SynExpr.CreateParen deserialiser
],
range0
)
| retType -> | retType ->
JsonParseGenerator.parseNode JsonParseGenerator.parseNode
None None
JsonParseGenerator.JsonParseOption.None JsonParseGenerator.JsonParseOption.None
retType retType
(SynExpr.CreateIdentString "node") (SynExpr.CreateIdentString "jsonNode")
let handleBodyParams = let handleBodyParams =
match bodyParam with match bodyParam with
@@ -448,10 +511,7 @@ module internal HttpClientGenerator =
) )
) )
] ]
| BodyParamMethods.Serialise _ -> | BodyParamMethods.Serialise ty ->
failwith "We don't yet support serialising Body parameters; use string or Stream instead"
(*
// TODO: this should use JSON instead of ToString
[ [
Let ( Let (
"queryParams", "queryParams",
@@ -460,21 +520,116 @@ module internal HttpClientGenerator =
SynType.CreateLongIdent ( SynType.CreateLongIdent (
SynLongIdent.Create [ "System" ; "Net" ; "Http" ; "StringContent" ] SynLongIdent.Create [ "System" ; "Net" ; "Http" ; "StringContent" ]
), ),
SynExpr.CreateParen (SynExpr.CreateIdent bodyParamName |> SynExpr.toString ty), SynExpr.CreateParen (
SynExpr.CreateIdent bodyParamName
|> SynExpr.pipeThroughFunction (JsonSerializeGenerator.serializeNode ty)
|> SynExpr.pipeThroughFunction (
SynExpr.createLambda
"node"
(SynExpr.ifThenElse
(SynExpr.CreateApp (
SynExpr.CreateIdentString "isNull",
SynExpr.CreateIdentString "node"
))
(SynExpr.CreateApp (
SynExpr.CreateLongIdent (
SynLongIdent.Create [ "node" ; "ToJsonString" ]
),
SynExpr.CreateConst SynConst.Unit
))
(SynExpr.CreateConst (SynConst.CreateString "null")))
)
),
range0 range0
) )
) )
Do ( Do (
SynExpr.LongIdentSet ( SynExpr.LongIdentSet (
SynLongIdent.Create [ "httpMessage" ; "Content" ], SynLongIdent.Create [ "httpMessage" ; "Content" ],
SynExpr.CreateIdentString "queryParams", SynExpr.CreateIdent (Ident.Create "queryParams"),
range0 range0
) )
) )
] ]
*)
let implementation = let implementation =
let responseString =
LetBang (
"responseString",
SynExpr.awaitTask (
SynExpr.CreateApp (
SynExpr.CreateLongIdent (
SynLongIdent.Create [ "response" ; "Content" ; "ReadAsStringAsync" ]
),
SynExpr.CreateIdentString "ct"
)
)
)
let responseStream =
LetBang (
"responseStream",
SynExpr.awaitTask (
SynExpr.CreateApp (
SynExpr.CreateLongIdent (
SynLongIdent.Create [ "response" ; "Content" ; "ReadAsStreamAsync" ]
),
SynExpr.CreateIdentString "ct"
)
)
)
let jsonNode =
LetBang (
"jsonNode",
SynExpr.awaitTask (
SynExpr.CreateApp (
SynExpr.CreateLongIdent (
SynLongIdent.Create [ "System" ; "Text" ; "Json" ; "Nodes" ; "JsonNode" ; "ParseAsync" ]
),
SynExpr.CreateParenedTuple
[
SynExpr.CreateIdentString "responseStream"
SynExpr.equals
(SynExpr.CreateIdentString "cancellationToken")
(SynExpr.CreateIdentString "ct")
]
)
)
)
let setVariableHeaders =
variableHeaders
|> List.map (fun (headerName, callToGetValue) ->
Do (
SynExpr.CreateApp (
SynExpr.CreateLongIdent (SynLongIdent.Create [ "httpMessage" ; "Headers" ; "Add" ]),
SynExpr.CreateParenedTuple
[
headerName
SynExpr.CreateApp (
SynExpr.CreateLongIdent (
SynLongIdent.CreateFromLongIdent
[ Ident.Create "this" ; callToGetValue ; Ident.Create "ToString" ]
),
SynExpr.CreateConst SynConst.Unit
)
]
)
)
)
let setConstantHeaders =
constantHeaders
|> List.map (fun (headerName, headerValue) ->
Do (
SynExpr.CreateApp (
SynExpr.CreateLongIdent (SynLongIdent.Create [ "httpMessage" ; "Headers" ; "Add" ]),
SynExpr.CreateParenedTuple [ headerName ; headerValue ]
)
)
)
[ [
yield LetBang ("ct", SynExpr.CreateLongIdent (SynLongIdent.Create [ "Async" ; "CancellationToken" ])) yield LetBang ("ct", SynExpr.CreateLongIdent (SynLongIdent.Create [ "Async" ; "CancellationToken" ]))
yield Let ("uri", requestUri) yield Let ("uri", requestUri)
@@ -493,6 +648,9 @@ module internal HttpClientGenerator =
yield! handleBodyParams yield! handleBodyParams
yield! setVariableHeaders
yield! setConstantHeaders
yield yield
LetBang ( LetBang (
"response", "response",
@@ -514,69 +672,19 @@ module internal HttpClientGenerator =
) )
) )
match info.TaskReturnType with match info.TaskReturnType with
| HttpResponseMessage -> yield Let ("node", SynExpr.CreateIdentString "response") | HttpResponseMessage -> ()
| String -> | RestEaseResponseType _ ->
yield yield responseString
LetBang ( yield responseStream
"node", yield jsonNode
SynExpr.awaitTask ( | String -> yield responseString
SynExpr.CreateApp ( | Stream -> yield responseStream
SynExpr.CreateLongIdent (
SynLongIdent.Create [ "response" ; "Content" ; "ReadAsStringAsync" ]
),
SynExpr.CreateIdentString "ct"
)
)
)
| Stream ->
yield
LetBang (
"node",
SynExpr.awaitTask (
SynExpr.CreateApp (
SynExpr.CreateLongIdent (
SynLongIdent.Create [ "response" ; "Content" ; "ReadAsStreamAsync" ]
),
SynExpr.CreateIdentString "ct"
)
)
)
| _ -> | _ ->
yield yield responseStream
LetBang ( yield jsonNode
"stream",
SynExpr.awaitTask (
SynExpr.CreateApp (
SynExpr.CreateLongIdent (
SynLongIdent.Create [ "response" ; "Content" ; "ReadAsStreamAsync" ]
),
SynExpr.CreateIdentString "ct"
)
)
)
yield
LetBang (
"node",
SynExpr.awaitTask (
SynExpr.CreateApp (
SynExpr.CreateLongIdent (
SynLongIdent.Create
[ "System" ; "Text" ; "Json" ; "Nodes" ; "JsonNode" ; "ParseAsync" ]
),
SynExpr.CreateParenedTuple
[
SynExpr.CreateIdentString "stream"
SynExpr.equals
(SynExpr.CreateIdentString "cancellationToken")
(SynExpr.CreateIdentString "ct")
]
)
)
)
] ]
|> SynExpr.createCompExpr "async" returnExpr |> SynExpr.createCompExpr "async" returnExpr
|> SynExpr.startAsTask |> SynExpr.startAsTask (SynLongIdent.CreateFromLongIdent [ cancellationTokenArg ])
SynMemberDefn.Member ( SynMemberDefn.Member (
SynBinding.SynBinding ( SynBinding.SynBinding (
@@ -612,7 +720,9 @@ module internal HttpClientGenerator =
| "Path" | "Path"
| "PathAttribute" -> | "PathAttribute" ->
match attr.ArgExpr with match attr.ArgExpr with
| SynExpr.Const (SynConst.String (s, SynStringKind.Regular, _), _) -> Some (HttpAttribute.Path s) | SynExpr.Const (SynConst.String (s, SynStringKind.Regular, _), _) ->
Some (HttpAttribute.Path (PathSpec.Verbatim s))
| SynExpr.Const (SynConst.Unit, _) -> Some (HttpAttribute.Path PathSpec.MatchArgName)
| SynExpr.Const (a, _) -> failwith $"unrecognised constant arg to the Path attribute: %+A{a}" | SynExpr.Const (a, _) -> failwith $"unrecognised constant arg to the Path attribute: %+A{a}"
| _ -> None | _ -> None
| "Body" | "Body"
@@ -654,10 +764,48 @@ module internal HttpClientGenerator =
= =
let interfaceType = AstHelper.parseInterface interfaceType let interfaceType = AstHelper.parseInterface interfaceType
let constantHeaders =
interfaceType.Attributes
|> extractHeaderInformation
|> List.map (fun exprs ->
match exprs with
| [ key ; value ] -> key, value
| [] ->
failwith
"Expected constant header parameters to be of the form [<Header (key, value)>], but got no args"
| [ _ ] ->
failwith
"Expected constant header parameters to be of the form [<Header (key, value)>], but got only one arg"
| _ ->
failwith
"Expected constant header parameters to be of the form [<Header (key, value)>], but got more than two args"
)
let baseAddress = extractBaseAddress interfaceType.Attributes let baseAddress = extractBaseAddress interfaceType.Attributes
let basePath = extractBasePath interfaceType.Attributes let basePath = extractBasePath interfaceType.Attributes
let members = let properties =
interfaceType.Properties
|> List.map (fun pi ->
let headerInfo =
match extractHeaderInformation pi.Attributes with
| [ [ x ] ] -> x
| [ xs ] ->
failwith
"Expected exactly one Header parameter on the member, with exactly one arg; got one Header parameter with non-1-many args"
| [] ->
failwith
"Expected exactly one Header parameter on the member, with exactly one arg; got no Header parameters"
| _ ->
failwith
"Expected exactly one Header parameter on the member, with exactly one arg; got multiple Header parameters"
headerInfo, pi
)
let nonPropertyMembers =
let properties = properties |> List.map (fun (header, pi) -> header, pi.Identifier)
interfaceType.Members interfaceType.Members
|> List.map (fun mem -> |> List.map (fun mem ->
let httpMethod, url = extractHttpInformation mem.Attributes let httpMethod, url = extractHttpInformation mem.Attributes
@@ -704,8 +852,57 @@ module internal HttpClientGenerator =
Accessibility = mem.Accessibility Accessibility = mem.Accessibility
} }
) )
|> List.map (constructMember constantHeaders properties)
let propertyMembers =
properties
|> List.map (fun (_, pi) ->
SynMemberDefn.Member (
SynBinding.SynBinding (
pi.Accessibility,
SynBindingKind.Normal,
pi.IsInline,
false,
[],
PreXmlDoc.Empty,
SynValData.SynValData (
Some
{
IsInstance = true
IsDispatchSlot = false
IsOverrideOrExplicitImpl = true
IsFinal = false
GetterOrSetterIsCompilerGenerated = false
MemberKind = SynMemberKind.Member
},
SynValInfo.SynValInfo ([ [ SynArgInfo.Empty ] ; [] ], SynArgInfo.Empty),
None
),
SynPat.CreateLongIdent (
SynLongIdent.CreateFromLongIdent [ Ident.Create "_" ; pi.Identifier ],
[]
),
Some (SynBindingReturnInfo.Create pi.Type),
SynExpr.CreateApp (
SynExpr.CreateLongIdent (
SynLongIdent.CreateFromLongIdent [ Ident.lowerFirstLetter pi.Identifier ]
),
SynExpr.CreateConst SynConst.Unit
),
range0,
DebugPointAtBinding.Yes range0,
{
LeadingKeyword = SynLeadingKeyword.Member range0
InlineKeyword = if pi.IsInline then Some range0 else None
EqualsRange = Some range0
}
),
range0
)
)
let members = propertyMembers @ nonPropertyMembers
let constructed = members |> List.map constructMember
let docString = PreXmlDoc.Create " Module for constructing a REST client." let docString = PreXmlDoc.Create " Module for constructing a REST client."
let interfaceImpl = let interfaceImpl =
@@ -714,12 +911,35 @@ module internal HttpClientGenerator =
None, None,
Some range0, Some range0,
[], [],
constructed, members,
[], [],
range0, range0,
range0 range0
) )
let headerArgs =
properties
|> List.map (fun (_, pi) ->
SynPat.CreateTyped (
SynPat.CreateNamed (Ident.lowerFirstLetter pi.Identifier),
SynType.CreateFun (SynType.CreateLongIdent "unit", pi.Type)
)
|> SynPat.CreateParen
)
let clientCreationArg =
SynPat.CreateTyped (
SynPat.CreateNamed (Ident.Create "client"),
SynType.CreateLongIdent (SynLongIdent.Create [ "System" ; "Net" ; "Http" ; "HttpClient" ])
)
|> SynPat.CreateParen
let xmlDoc =
if properties.IsEmpty then
" Create a REST client."
else
" Create a REST client. The input functions will be re-evaluated on every HTTP request to obtain the required values for the corresponding header properties."
let createFunc = let createFunc =
SynBinding.SynBinding ( SynBinding.SynBinding (
None, None,
@@ -727,7 +947,7 @@ module internal HttpClientGenerator =
false, false,
false, false,
[], [],
PreXmlDoc.Create " Create a REST client.", PreXmlDoc.Create xmlDoc,
SynValData.SynValData ( SynValData.SynValData (
None, None,
SynValInfo.SynValInfo ( SynValInfo.SynValInfo (
@@ -736,19 +956,7 @@ module internal HttpClientGenerator =
), ),
None None
), ),
SynPat.CreateLongIdent ( SynPat.CreateLongIdent (SynLongIdent.CreateString "make", headerArgs @ [ clientCreationArg ]),
SynLongIdent.CreateString "make",
[
SynPat.CreateParen (
SynPat.CreateTyped (
SynPat.CreateNamed (Ident.Create "client"),
SynType.CreateLongIdent (
SynLongIdent.Create [ "System" ; "Net" ; "Http" ; "HttpClient" ]
)
)
)
]
),
Some ( Some (
SynBindingReturnInfo.Create ( SynBindingReturnInfo.Create (
SynType.LongIdent (SynLongIdent.CreateFromLongIdent interfaceType.Name) SynType.LongIdent (SynLongIdent.CreateFromLongIdent interfaceType.Name)
@@ -764,7 +972,7 @@ module internal HttpClientGenerator =
let moduleName : LongIdent = let moduleName : LongIdent =
List.last interfaceType.Name List.last interfaceType.Name
|> fun ident -> ident.idText |> _.idText
|> fun s -> |> fun s ->
if s.StartsWith 'I' then if s.StartsWith 'I' then
s.[1..] s.[1..]

View 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 ())

View File

@@ -6,13 +6,10 @@ open Fantomas.FCS.SyntaxTrivia
open Fantomas.FCS.Xml open Fantomas.FCS.Xml
open Myriad.Core open Myriad.Core
/// Attribute indicating an interface type for which the "Generate Mock" Myriad type internal GenerateMockOutputSpec =
/// generator should apply during build. {
/// This generator creates a record which implements the interface, IsInternal : bool
/// but where each method is represented as a record field, so you can use }
/// record update syntax to easily specify partially-implemented mock objects.
type GenerateMockAttribute () =
inherit Attribute ()
[<RequireQualifiedAccess>] [<RequireQualifiedAccess>]
module internal InterfaceMockGenerator = module internal InterfaceMockGenerator =
@@ -25,6 +22,7 @@ module internal InterfaceMockGenerator =
| Some id -> id | Some id -> id
let createType let createType
(spec : GenerateMockOutputSpec)
(name : string) (name : string)
(interfaceType : InterfaceType) (interfaceType : InterfaceType)
(xmlDoc : PreXmlDoc) (xmlDoc : PreXmlDoc)
@@ -100,7 +98,7 @@ module internal InterfaceMockGenerator =
false, false,
false, false,
[], [],
PreXmlDoc.Empty, PreXmlDoc.Create " An implementation where every method throws.",
SynValData.SynValData (Some synValData, SynValInfo.Empty, None), SynValData.SynValData (Some synValData, SynValInfo.Empty, None),
constructorIdent, constructorIdent,
Some constructorReturnType, Some constructorReturnType,
@@ -257,13 +255,14 @@ module internal InterfaceMockGenerator =
SynMemberDefn.Interface (interfaceName, Some range0, Some members, range0) SynMemberDefn.Interface (interfaceName, Some range0, Some members, range0)
// TODO: allow an arg to the attribute, specifying a custom visibility
let access = let access =
match interfaceType.Accessibility with match interfaceType.Accessibility, spec.IsInternal with
| Some (SynAccess.Public _) | Some (SynAccess.Public _), true
| Some (SynAccess.Internal _) | None, true -> SynAccess.Internal range0
| None -> SynAccess.Internal range0 | Some (SynAccess.Public _), false -> SynAccess.Public range0
| Some (SynAccess.Private _) -> SynAccess.Private range0 | None, false -> SynAccess.Public range0
| Some (SynAccess.Internal _), _ -> SynAccess.Internal range0
| Some (SynAccess.Private _), _ -> SynAccess.Private range0
let record = let record =
{ {
@@ -312,14 +311,19 @@ module internal InterfaceMockGenerator =
SynFieldTrivia.Zero SynFieldTrivia.Zero
) )
let createRecord (namespaceId : LongIdent) (interfaceType : SynTypeDefn) : SynModuleOrNamespace = let createRecord
(namespaceId : LongIdent)
(opens : SynOpenDeclTarget list)
(interfaceType : SynTypeDefn, spec : GenerateMockOutputSpec)
: SynModuleOrNamespace
=
let interfaceType = AstHelper.parseInterface interfaceType let interfaceType = AstHelper.parseInterface interfaceType
let fields = interfaceType.Members |> List.map constructMember let fields = interfaceType.Members |> List.map constructMember
let docString = PreXmlDoc.Create " Mock record type for an interface" let docString = PreXmlDoc.Create " Mock record type for an interface"
let name = let name =
List.last interfaceType.Name List.last interfaceType.Name
|> fun s -> s.idText |> _.idText
|> fun s -> |> fun s ->
if s.StartsWith 'I' && s.Length > 1 && Char.IsUpper s.[1] then if s.StartsWith 'I' && s.Length > 1 && Char.IsUpper s.[1] then
s.[1..] s.[1..]
@@ -327,9 +331,13 @@ module internal InterfaceMockGenerator =
s s
|> fun s -> s + "Mock" |> fun s -> s + "Mock"
let typeDecl = createType name interfaceType docString fields let typeDecl = createType spec name interfaceType docString fields
SynModuleOrNamespace.CreateNamespace (namespaceId, decls = [ typeDecl ])
SynModuleOrNamespace.CreateNamespace (
namespaceId,
decls = (opens |> List.map SynModuleDecl.CreateOpen) @ [ typeDecl ]
)
/// Myriad generator that creates a record which implements the given interface, /// Myriad generator that creates a record which implements the given interface,
/// but with every field mocked out. /// but with every field mocked out.
@@ -348,15 +356,37 @@ type InterfaceMockGenerator () =
let namespaceAndInterfaces = let namespaceAndInterfaces =
types types
|> List.choose (fun (ns, types) -> |> List.choose (fun (ns, types) ->
match types |> List.filter Ast.hasAttribute<GenerateMockAttribute> with types
| [] -> None |> List.choose (fun typeDef ->
| types -> Some (ns, types) 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 opens = AstHelper.extractOpens ast
let modules = let modules =
namespaceAndInterfaces namespaceAndInterfaces
|> List.collect (fun (ns, records) -> records |> List.map (InterfaceMockGenerator.createRecord ns)) |> List.collect (fun (ns, records) ->
records |> List.map (InterfaceMockGenerator.createRecord ns opens)
)
Output.Ast modules Output.Ast modules

View File

@@ -7,23 +7,6 @@ open Fantomas.FCS.SyntaxTrivia
open Fantomas.FCS.Xml open Fantomas.FCS.Xml
open Myriad.Core open Myriad.Core
/// 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 ()
/// If changing this, *adjust the documentation strings*
static member internal DefaultIsExtensionMethod = false
/// Shorthand for the "isExtensionMethod = false" constructor; see documentation there for details.
new () = JsonParseAttribute JsonParseAttribute.DefaultIsExtensionMethod
type internal JsonParseOutputSpec = type internal JsonParseOutputSpec =
{ {
ExtensionMethods : bool ExtensionMethods : bool
@@ -211,6 +194,12 @@ module internal JsonParseGenerator =
node node
|> asValueGetValue propertyName "string" |> asValueGetValue propertyName "string"
|> SynExpr.pipeThroughFunction (SynExpr.CreateLongIdent (SynLongIdent.Create [ "System" ; "Uri" ])) |> SynExpr.pipeThroughFunction (SynExpr.CreateLongIdent (SynLongIdent.Create [ "System" ; "Uri" ]))
| Guid ->
node
|> asValueGetValue propertyName "string"
|> SynExpr.pipeThroughFunction (
SynExpr.CreateLongIdent (SynLongIdent.Create [ "System" ; "Guid" ; "Parse" ])
)
| DateTime -> | DateTime ->
node node
|> asValueGetValue propertyName "string" |> asValueGetValue propertyName "string"
@@ -533,7 +522,7 @@ module internal JsonParseGenerator =
let containingType = let containingType =
SynTypeDefn.SynTypeDefn ( SynTypeDefn.SynTypeDefn (
SynComponentInfo.Create (typeName, xmldoc = PreXmlDoc.Create "Extension methods for JSON parsing"), SynComponentInfo.Create (typeName, xmldoc = PreXmlDoc.Create " Extension methods for JSON parsing"),
SynTypeDefnRepr.ObjectModel (SynTypeDefnKind.Augmentation range0, [], range0), SynTypeDefnRepr.ObjectModel (SynTypeDefnKind.Augmentation range0, [], range0),
[ mem ], [ mem ],
None, None,
@@ -644,7 +633,7 @@ type JsonParseGenerator () =
| SynExpr.Const (SynConst.Unit, _) -> JsonParseAttribute.DefaultIsExtensionMethod | SynExpr.Const (SynConst.Unit, _) -> JsonParseAttribute.DefaultIsExtensionMethod
| arg -> | arg ->
failwith failwith
$"Unrecognised argument %+A{arg} to [<JsonParseAttribute>]. Literals are not supported. Use `true` or `false` (or unit) only." $"Unrecognised argument %+A{arg} to [<%s{nameof JsonParseAttribute}>]. Literals are not supported. Use `true` or `false` (or unit) only."
let spec = let spec =
{ {

View 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

View 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

View File

@@ -1,17 +1,10 @@
namespace WoofWare.Myriad.Plugins namespace WoofWare.Myriad.Plugins
open System
open Fantomas.FCS.Syntax open Fantomas.FCS.Syntax
open Fantomas.FCS.SyntaxTrivia open Fantomas.FCS.SyntaxTrivia
open Fantomas.FCS.Xml open Fantomas.FCS.Xml
open Myriad.Core open Myriad.Core
/// 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 ()
[<RequireQualifiedAccess>] [<RequireQualifiedAccess>]
module internal RemoveOptionsGenerator = module internal RemoveOptionsGenerator =
open Fantomas.FCS.Text.Range open Fantomas.FCS.Text.Range

View File

@@ -1,17 +1,12 @@
WoofWare.Myriad.Plugins.GenerateMockAttribute inherit System.Attribute WoofWare.Myriad.Plugins.CreateCatamorphismGenerator inherit obj, implements Myriad.Core.IMyriadGenerator
WoofWare.Myriad.Plugins.GenerateMockAttribute..ctor [constructor]: unit WoofWare.Myriad.Plugins.CreateCatamorphismGenerator..ctor [constructor]: unit
WoofWare.Myriad.Plugins.HttpClientAttribute inherit System.Attribute
WoofWare.Myriad.Plugins.HttpClientAttribute..ctor [constructor]: unit
WoofWare.Myriad.Plugins.HttpClientGenerator inherit obj, implements Myriad.Core.IMyriadGenerator WoofWare.Myriad.Plugins.HttpClientGenerator inherit obj, implements Myriad.Core.IMyriadGenerator
WoofWare.Myriad.Plugins.HttpClientGenerator..ctor [constructor]: unit WoofWare.Myriad.Plugins.HttpClientGenerator..ctor [constructor]: unit
WoofWare.Myriad.Plugins.InterfaceMockGenerator inherit obj, implements Myriad.Core.IMyriadGenerator WoofWare.Myriad.Plugins.InterfaceMockGenerator inherit obj, implements Myriad.Core.IMyriadGenerator
WoofWare.Myriad.Plugins.InterfaceMockGenerator..ctor [constructor]: unit WoofWare.Myriad.Plugins.InterfaceMockGenerator..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.JsonParseGenerator inherit obj, implements Myriad.Core.IMyriadGenerator WoofWare.Myriad.Plugins.JsonParseGenerator inherit obj, implements Myriad.Core.IMyriadGenerator
WoofWare.Myriad.Plugins.JsonParseGenerator..ctor [constructor]: unit WoofWare.Myriad.Plugins.JsonParseGenerator..ctor [constructor]: unit
WoofWare.Myriad.Plugins.RemoveOptionsAttribute inherit System.Attribute WoofWare.Myriad.Plugins.JsonSerializeGenerator inherit obj, implements Myriad.Core.IMyriadGenerator
WoofWare.Myriad.Plugins.RemoveOptionsAttribute..ctor [constructor]: unit WoofWare.Myriad.Plugins.JsonSerializeGenerator..ctor [constructor]: unit
WoofWare.Myriad.Plugins.RemoveOptionsGenerator inherit obj, implements Myriad.Core.IMyriadGenerator WoofWare.Myriad.Plugins.RemoveOptionsGenerator inherit obj, implements Myriad.Core.IMyriadGenerator
WoofWare.Myriad.Plugins.RemoveOptionsGenerator..ctor [constructor]: unit WoofWare.Myriad.Plugins.RemoveOptionsGenerator..ctor [constructor]: unit

View File

@@ -180,7 +180,7 @@ module internal SynExpr =
SynExpr.CreateApp (SynExpr.CreateIdent (Ident.Create "reraise"), SynExpr.CreateConst SynConst.Unit) SynExpr.CreateApp (SynExpr.CreateIdent (Ident.Create "reraise"), SynExpr.CreateConst SynConst.Unit)
/// {body} |> fun a -> Async.StartAsTask (a, ?cancellationToken=ct) /// {body} |> fun a -> Async.StartAsTask (a, ?cancellationToken=ct)
let startAsTask (body : SynExpr) = let startAsTask (ct : SynLongIdent) (body : SynExpr) =
let lambda = let lambda =
SynExpr.CreateApp ( SynExpr.CreateApp (
SynExpr.CreateLongIdent (SynLongIdent.Create [ "Async" ; "StartAsTask" ]), SynExpr.CreateLongIdent (SynLongIdent.Create [ "Async" ; "StartAsTask" ]),
@@ -189,7 +189,7 @@ module internal SynExpr =
SynExpr.CreateLongIdent (SynLongIdent.CreateString "a") SynExpr.CreateLongIdent (SynLongIdent.CreateString "a")
equals equals
(SynExpr.LongIdent (true, SynLongIdent.CreateString "cancellationToken", None, range0)) (SynExpr.LongIdent (true, SynLongIdent.CreateString "cancellationToken", None, range0))
(SynExpr.CreateLongIdent (SynLongIdent.CreateString "ct")) (SynExpr.CreateLongIdent ct)
] ]
) )
|> createLambda "a" |> createLambda "a"
@@ -263,6 +263,8 @@ module internal SynExpr =
|> callMethodArg "ToString" (SynExpr.CreateConstString "yyyy-MM-ddTHH:mm:ss") |> callMethodArg "ToString" (SynExpr.CreateConstString "yyyy-MM-ddTHH:mm:ss")
| _ -> callMethod "ToString" ident | _ -> callMethod "ToString" ident
let upcast' (ty : SynType) (e : SynExpr) = SynExpr.Upcast (e, ty, range0)
let synBindingTriviaZero (isMember : bool) = let synBindingTriviaZero (isMember : bool) =
{ {
SynBindingTrivia.EqualsRange = Some range0 SynBindingTrivia.EqualsRange = Some range0
@@ -273,3 +275,39 @@ module internal SynExpr =
else else
SynLeadingKeyword.Let range0 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
)

View 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

View File

@@ -18,19 +18,24 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <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 --> <!-- 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>
<ItemGroup> <ItemGroup>
<Compile Include="List.fs"/>
<Compile Include="Ident.fs" />
<Compile Include="AstHelper.fs"/> <Compile Include="AstHelper.fs"/>
<Compile Include="SynExpr.fs"/> <Compile Include="SynExpr.fs"/>
<Compile Include="SynType.fs"/>
<Compile Include="SynAttribute.fs"/> <Compile Include="SynAttribute.fs"/>
<Compile Include="RemoveOptionsGenerator.fs"/> <Compile Include="RemoveOptionsGenerator.fs"/>
<Compile Include="InterfaceMockGenerator.fs" /> <Compile Include="InterfaceMockGenerator.fs"/>
<Compile Include="JsonSerializeGenerator.fs"/>
<Compile Include="JsonParseGenerator.fs"/> <Compile Include="JsonParseGenerator.fs"/>
<Compile Include="HttpClientGenerator.fs"/> <Compile Include="HttpClientGenerator.fs"/>
<Compile Include="CataGenerator.fs" />
<EmbeddedResource Include="version.json"/> <EmbeddedResource Include="version.json"/>
<EmbeddedResource Include="SurfaceBaseline.txt"/> <EmbeddedResource Include="SurfaceBaseline.txt"/>
<None Include="..\README.md"> <None Include="..\README.md">
@@ -43,4 +48,11 @@
</None> </None>
</ItemGroup> </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> </Project>

View File

@@ -1,5 +1,5 @@
{ {
"version": "1.3", "version": "2.1",
"publicReleaseRefSpec": [ "publicReleaseRefSpec": [
"^refs/heads/main$" "^refs/heads/main$"
], ],

View File

@@ -6,6 +6,10 @@ Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "WoofWare.Myriad.Plugins", "
EndProject EndProject
Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "WoofWare.Myriad.Plugins.Test", "WoofWare.Myriad.Plugins.Test\WoofWare.Myriad.Plugins.Test.fsproj", "{EBFFA5D3-7F74-4824-8795-B6194E6FE0CB}" 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 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 Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU
@@ -24,5 +28,13 @@ Global
{EBFFA5D3-7F74-4824-8795-B6194E6FE0CB}.Debug|Any CPU.Build.0 = 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.ActiveCfg = Release|Any CPU
{EBFFA5D3-7F74-4824-8795-B6194E6FE0CB}.Release|Any CPU.Build.0 = 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 EndGlobalSection
EndGlobal EndGlobal

View File

@@ -10,7 +10,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageDownload Include="G-Research.FSharp.Analyzers" Version="[0.6.0]" /> <PackageDownload Include="G-Research.FSharp.Analyzers" Version="[0.9.3]" />
</ItemGroup> </ItemGroup>
</Project> </Project>

11
flake.lock generated
View File

@@ -20,17 +20,18 @@
}, },
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1703134684, "lastModified": 1706367331,
"narHash": "sha256-SQmng1EnBFLzS7WSRyPM9HgmZP2kLJcPAz+Ug/nug6o=", "narHash": "sha256-AqgkGHRrI6h/8FWuVbnkfFmXr4Bqsr4fV23aISqj/xg=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "d6863cbcbbb80e71cecfc03356db1cda38919523", "rev": "160b762eda6d139ac10ae081f8f78d640dd523eb",
"type": "github" "type": "github"
}, },
"original": { "original": {
"id": "nixpkgs", "owner": "NixOS",
"ref": "nixpkgs-unstable", "ref": "nixpkgs-unstable",
"type": "indirect" "repo": "nixpkgs",
"type": "github"
} }
}, },
"root": { "root": {

View File

@@ -3,7 +3,7 @@
inputs = { inputs = {
flake-utils.url = "github:numtide/flake-utils"; flake-utils.url = "github:numtide/flake-utils";
nixpkgs.url = "nixpkgs/nixpkgs-unstable"; nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
}; };
outputs = { outputs = {
@@ -44,8 +44,8 @@
}; };
in { in {
packages = { packages = {
fantomas = dotnetTool null "fantomas" (builtins.fromJSON (builtins.readFile ./.config/dotnet-tools.json)).tools.fantomas.version "sha256-Jmo7s8JMdQ8SxvNvPnryfE7n24mIgKi5cbgNwcQw3yU="; 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 "sha256-CWMW06ncSs8QkQvxNPNrgn3TAzMU6qCT1k2A3pnGrYQ="; 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 fetchDeps = let
flags = []; flags = [];
runtimeIds = ["win-x64"] ++ map (system: pkgs.dotnetCorePackages.systemToDotnetRid system) dotnet-sdk.meta.platforms; runtimeIds = ["win-x64"] ++ map (system: pkgs.dotnetCorePackages.systemToDotnetRid system) dotnet-sdk.meta.platforms;
@@ -54,8 +54,8 @@
src = ./nix/fetchDeps.sh; src = ./nix/fetchDeps.sh;
pname = pname; pname = pname;
binPath = pkgs.lib.makeBinPath [pkgs.coreutils dotnet-sdk (pkgs.nuget-to-nix.override {inherit dotnet-sdk;})]; 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"]; 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"]; 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; rids = pkgs.lib.concatStringsSep "\" \"" runtimeIds;
packages = dotnet-sdk.packages; packages = dotnet-sdk.packages;
storeSrc = pkgs.srcOnly { storeSrc = pkgs.srcOnly {

View File

@@ -3,18 +3,18 @@
{fetchNuGet}: [ {fetchNuGet}: [
(fetchNuGet { (fetchNuGet {
pname = "fsharp-analyzers"; pname = "fsharp-analyzers";
version = "0.23.0"; version = "0.25.0";
sha256 = "sha256-CWMW06ncSs8QkQvxNPNrgn3TAzMU6qCT1k2A3pnGrYQ="; sha256 = "sha256-njfJYi40jNvrD+mgu9LtQw2Omh8P1SSDThesozH0KQY=";
}) })
(fetchNuGet { (fetchNuGet {
pname = "fantomas"; pname = "fantomas";
version = "6.3.0-alpha-005"; version = "6.3.0-alpha-007";
sha256 = "sha256-Jmo7s8JMdQ8SxvNvPnryfE7n24mIgKi5cbgNwcQw3yU="; sha256 = "sha256-uZw6h6k/DS4BcYtK9cv8TLS0H8MZDO3WBaPPTdtTgu0=";
}) })
(fetchNuGet { (fetchNuGet {
pname = "ApiSurface"; pname = "ApiSurface";
version = "4.0.25"; version = "4.0.30";
sha256 = "0zjq8an9cr0l7wxdmm9n9s3iyq5m0zl4x0h0wmy5cz7am8y15qc4"; sha256 = "0khbp0dx87m4kx1a5b9vgh1pp88vr9w8vpqvxf6afrpcyynwrrcr";
}) })
(fetchNuGet { (fetchNuGet {
pname = "coverlet.collector"; pname = "coverlet.collector";
@@ -36,6 +36,11 @@
version = "2.16.6"; version = "2.16.6";
sha256 = "176rwky6b5rk8dzldiz4068p7m9c5y9ygzbhadrs14jkl94pc56n"; sha256 = "176rwky6b5rk8dzldiz4068p7m9c5y9ygzbhadrs14jkl94pc56n";
}) })
(fetchNuGet {
pname = "FSharp.Core";
version = "4.3.4";
sha256 = "1sg6i4q5nwyzh769g76f6c16876nvdpn83adqjr2y9x6xsiv5p5j";
})
(fetchNuGet { (fetchNuGet {
pname = "FSharp.Core"; pname = "FSharp.Core";
version = "6.0.1"; version = "6.0.1";
@@ -43,8 +48,8 @@
}) })
(fetchNuGet { (fetchNuGet {
pname = "FSharp.Core"; pname = "FSharp.Core";
version = "8.0.100"; version = "8.0.101";
sha256 = "06z3vg8yj7i83x6gmnzl2lka1bp4hzc07h6mrydpilxswnmy2a0l"; sha256 = "0prgcnki6s0rlrfbarrcv50w1bbhaalsyhhw5gsnjs2is7qrjbii";
}) })
(fetchNuGet { (fetchNuGet {
pname = "FsUnit"; pname = "FsUnit";
@@ -53,63 +58,63 @@
}) })
(fetchNuGet { (fetchNuGet {
pname = "Microsoft.AspNetCore.App.Ref"; pname = "Microsoft.AspNetCore.App.Ref";
version = "6.0.25"; version = "6.0.26";
sha256 = "1vrmqn5j6ibwkqasbf7x7n4w5jdclnz3giymiwvym2wa0y5zc59q"; sha256 = "1d8nkz24vsm0iy2xm8y5ak2q1w1p99dxyz0y26acs6sfk2na0vm6";
}) })
(fetchNuGet { (fetchNuGet {
pname = "Microsoft.AspNetCore.App.Ref"; pname = "Microsoft.AspNetCore.App.Ref";
version = "8.0.0"; version = "8.0.1";
sha256 = "0k304yhpm92c46a1fscbzlgvdbhrm9vlbpyfgwp3cafz4f7z7a5y"; sha256 = "0yaaiqq7mi6sclyrb1v0fyncanbx0ifmnnhv9whynqj8439jsdwh";
}) })
(fetchNuGet { (fetchNuGet {
pname = "Microsoft.AspNetCore.App.Runtime.linux-arm64"; pname = "Microsoft.AspNetCore.App.Runtime.linux-arm64";
version = "6.0.25"; version = "6.0.26";
sha256 = "0mgcs4si7mwd0f555s1vg17pf4nqfaijd1pci359l1pgrmv70rrg"; sha256 = "1za8lc52m4z54d68wd64c2nhzy05g3gx171k5cdlx73fbymiys9z";
}) })
(fetchNuGet { (fetchNuGet {
pname = "Microsoft.AspNetCore.App.Runtime.linux-arm64"; pname = "Microsoft.AspNetCore.App.Runtime.linux-arm64";
version = "8.0.0"; version = "8.0.1";
sha256 = "05y1xb5fw8lzvb4si77a5qwfwfz1855crqbphrwky6x9llivbhkx"; sha256 = "0dsdgqg7566qximmjfza4x9if3icy4kskq698ddj5apdia88h2mw";
}) })
(fetchNuGet { (fetchNuGet {
pname = "Microsoft.AspNetCore.App.Runtime.linux-x64"; pname = "Microsoft.AspNetCore.App.Runtime.linux-x64";
version = "6.0.25"; version = "6.0.26";
sha256 = "0wvzhqhlmlbnpa18qp8m3wcrlcgj3ckvp3iv2n7g8vb60c3238aq"; sha256 = "1zpbmz6z8758gwywzg0bac8kx9x39sxxc9j4a4r2jl74l9ssw4vm";
}) })
(fetchNuGet { (fetchNuGet {
pname = "Microsoft.AspNetCore.App.Runtime.linux-x64"; pname = "Microsoft.AspNetCore.App.Runtime.linux-x64";
version = "8.0.0"; version = "8.0.1";
sha256 = "18zdbcb2bn7wy1dp14z5jyqiiwr9rkad1lcb158r5ikjfq1rg5iw"; sha256 = "1gjz379y61ag9whi78qxx09bwkwcznkx2mzypgycibxk61g11da1";
}) })
(fetchNuGet { (fetchNuGet {
pname = "Microsoft.AspNetCore.App.Runtime.osx-arm64"; pname = "Microsoft.AspNetCore.App.Runtime.osx-arm64";
version = "6.0.25"; version = "6.0.26";
sha256 = "1pywgvb8ck1d5aadmijd5s3z6yclchd9pa6dsahijmm55ibplx36"; sha256 = "1i8ydlwjzk7j0mzvn0rpljxfp1h50zwaqalnyvfxai1fwgigzgw5";
}) })
(fetchNuGet { (fetchNuGet {
pname = "Microsoft.AspNetCore.App.Runtime.osx-arm64"; pname = "Microsoft.AspNetCore.App.Runtime.osx-arm64";
version = "8.0.0"; version = "8.0.1";
sha256 = "1nbxzmj6cnccylxis67c54c0ik38ma4rwdvgg6sxd6r04219maqm"; sha256 = "0w3mrs4zdl9mfanl1j81759xwwrzmicsjxn6yfxv5yrxbxzq695n";
}) })
(fetchNuGet { (fetchNuGet {
pname = "Microsoft.AspNetCore.App.Runtime.osx-x64"; pname = "Microsoft.AspNetCore.App.Runtime.osx-x64";
version = "6.0.25"; version = "6.0.26";
sha256 = "1zlf0w7i6r02719dv3nw4jy14sa0rs53i89an5alz5qmywdy3f1d"; sha256 = "02src68hd3213sd1a2ms1my7i92knfmdxclvv90il9cky2zsq8kw";
}) })
(fetchNuGet { (fetchNuGet {
pname = "Microsoft.AspNetCore.App.Runtime.osx-x64"; pname = "Microsoft.AspNetCore.App.Runtime.osx-x64";
version = "8.0.0"; version = "8.0.1";
sha256 = "1wqkbjd1ywv9w397l7rsb89mijc5n0hv7jq9h09xfz6wn9qsp152"; sha256 = "0a9aljr4fy4haq6ndz2y723liv5hbfpss1rn45s88nmgcp27m15m";
}) })
(fetchNuGet { (fetchNuGet {
pname = "Microsoft.AspNetCore.App.Runtime.win-x64"; pname = "Microsoft.AspNetCore.App.Runtime.win-x64";
version = "6.0.25"; version = "6.0.26";
sha256 = "1fbsnm4056cpd4avgpi5sq05m1yd9k4x229ckxpr4q7yc94sncwy"; sha256 = "1gxlmfdkfzmhw9pac5jiv674nn6i1zymcp2hj81irjwhhjk01mf5";
}) })
(fetchNuGet { (fetchNuGet {
pname = "Microsoft.AspNetCore.App.Runtime.win-x64"; pname = "Microsoft.AspNetCore.App.Runtime.win-x64";
version = "8.0.0"; version = "8.0.1";
sha256 = "08vlmswmiyp2nxlr9d77716hk7kz7h9x5bl8wh76xzbj5id1xlb2"; sha256 = "01kzndyqmsvcq49i2jrv7ymfp0l71yxfylv1cy3nhkdbprqz8ipx";
}) })
(fetchNuGet { (fetchNuGet {
pname = "Microsoft.Build.Tasks.Git"; pname = "Microsoft.Build.Tasks.Git";
@@ -118,123 +123,128 @@
}) })
(fetchNuGet { (fetchNuGet {
pname = "Microsoft.CodeCoverage"; pname = "Microsoft.CodeCoverage";
version = "17.8.0"; version = "17.9.0";
sha256 = "173wjadp3gan4x2jfjchngnc4ca4mb95h1sbb28jydfkfw0z1zvj"; sha256 = "1gljgi69k0fz8vy8bn6xlyxabj6q4vls2zza9wz7ng6ix3irm89r";
}) })
(fetchNuGet { (fetchNuGet {
pname = "Microsoft.NET.Test.Sdk"; pname = "Microsoft.NET.Test.Sdk";
version = "17.8.0"; version = "17.9.0";
sha256 = "1syvl3g0hbrcgfi9rq6pld8s8hqqww4dflf1lxn59ccddyyx0gmv"; sha256 = "1lls1fly2gr1n9n1xyl9k33l2v4pwfmylyzkq8v4v5ldnwkl1zdb";
}) })
(fetchNuGet { (fetchNuGet {
pname = "Microsoft.NETCore.App.Host.linux-arm64"; pname = "Microsoft.NETCore.App.Host.linux-arm64";
version = "6.0.25"; version = "6.0.26";
sha256 = "052388yjivzkfllkss0nljbzmjx787jqdjsbb6ls855sp6wh9xfd"; sha256 = "19y6c6v20bgf7x7rrh4rx9y7s5fy8vp5m4j9b6gi1wp4rpb5mza4";
}) })
(fetchNuGet { (fetchNuGet {
pname = "Microsoft.NETCore.App.Host.linux-arm64"; pname = "Microsoft.NETCore.App.Host.linux-arm64";
version = "8.0.0"; version = "8.0.1";
sha256 = "0bpg3v9dnalz7yh7lsgriw9rnm9jx37mqhhvf7snznb3sfk7rgwb"; sha256 = "0dhpdlcdz7adcfh9w01fc867051m35fqaxnvj3fqvqhgcm2n3143";
}) })
(fetchNuGet { (fetchNuGet {
pname = "Microsoft.NETCore.App.Host.linux-x64"; pname = "Microsoft.NETCore.App.Host.linux-x64";
version = "6.0.25"; version = "6.0.26";
sha256 = "103xy6kncjwbbchfnpqvsjpjy92x3dralcg9pw939jp0dwggwarz"; sha256 = "0p7hhidaa3mnyiwnsijwy8578v843x8hh99255s69qwwyld6falv";
}) })
(fetchNuGet { (fetchNuGet {
pname = "Microsoft.NETCore.App.Host.linux-x64"; pname = "Microsoft.NETCore.App.Host.linux-x64";
version = "8.0.0"; version = "8.0.1";
sha256 = "1c7l68bm05d94x5wk1y33mnd4v8m196vyprgrzqnh94yrqy6fkf7"; sha256 = "1aw6mc7zcmzs1grxz2wa9cw9kfj8pz7zpj417xnp1a9n4ix1bxgr";
}) })
(fetchNuGet { (fetchNuGet {
pname = "Microsoft.NETCore.App.Host.osx-arm64"; pname = "Microsoft.NETCore.App.Host.osx-arm64";
version = "6.0.25"; version = "6.0.26";
sha256 = "13m14pdx5xfxky07xgxf6hjd7g9l4k6k40wvp9znhvn27pa0wdxv"; sha256 = "1mq11xsv9g1vsasp6k80y7xlvwi9hrpk5dgm773fvy8538s01gfv";
}) })
(fetchNuGet { (fetchNuGet {
pname = "Microsoft.NETCore.App.Host.osx-arm64"; pname = "Microsoft.NETCore.App.Host.osx-arm64";
version = "8.0.0"; version = "8.0.1";
sha256 = "1hdv825s964vfcgnk94pzhgxnj948f1vdj423jjxpkppcy30fl0m"; sha256 = "1dzg3prng9zfdzz7gcgywjdbwzhwm85j89z0jahynxx4q2dra4b9";
}) })
(fetchNuGet { (fetchNuGet {
pname = "Microsoft.NETCore.App.Host.osx-x64"; pname = "Microsoft.NETCore.App.Host.osx-x64";
version = "6.0.25"; version = "6.0.26";
sha256 = "132pgjhv42mqzx4007sd59bkds0fwsv5xaz07y2yffbn3lzr228k"; sha256 = "1chac9b4424ihrrnlzvc7qz6j4ymfjyv4kzyazzzw19yhymdkh2s";
}) })
(fetchNuGet { (fetchNuGet {
pname = "Microsoft.NETCore.App.Host.osx-x64"; pname = "Microsoft.NETCore.App.Host.osx-x64";
version = "8.0.0"; version = "8.0.1";
sha256 = "0jmzf58vv45j0hqlxq8yalpjwi328vp2mjr3h0pdg0qr143iivnr"; sha256 = "010f8wn15s2kv7yyzgys3pv9i1mxw20hpv1ig2zhybjxs8lpj8jj";
}) })
(fetchNuGet { (fetchNuGet {
pname = "Microsoft.NETCore.App.Host.win-x64"; pname = "Microsoft.NETCore.App.Host.win-x64";
version = "6.0.25"; version = "6.0.26";
sha256 = "039433rm4w37h9qri11v3lrpddpz7zcly9kq8vmk6w1ixzlqwf01"; sha256 = "0i7g9fsqjnbh9rc6807m57r2idg5pkcw6xjfwhnxkcpgqm96258v";
}) })
(fetchNuGet { (fetchNuGet {
pname = "Microsoft.NETCore.App.Host.win-x64"; pname = "Microsoft.NETCore.App.Host.win-x64";
version = "8.0.0"; version = "8.0.1";
sha256 = "1n8yr13df2f6jhxpfazs6rxahfqm18fhjvfm16g5d60c3za1hwnk"; sha256 = "1ssj1cyam3nfidm8q82kvh4i3fzm2lzb3bxw6ck09hwhvwh909z4";
}) })
(fetchNuGet { (fetchNuGet {
pname = "Microsoft.NETCore.App.Ref"; pname = "Microsoft.NETCore.App.Ref";
version = "6.0.25"; version = "6.0.26";
sha256 = "0jfhmfxpx1h4f3axgf60gc8d4cnlvbb853400kag6nk0875hr0x1"; sha256 = "12gb52dhg5h9hgnyqh1zgj2w46paxv2pfh33pphl9ajhrdr7hlsb";
}) })
(fetchNuGet { (fetchNuGet {
pname = "Microsoft.NETCore.App.Ref"; pname = "Microsoft.NETCore.App.Ref";
version = "8.0.0"; version = "8.0.1";
sha256 = "0hyvbh86433764qqqhw9i7ga0ax7bbdmzh77jw58pq0ggm41cff9"; sha256 = "02r4jg4ha0qksix9v6s3cpmvavmz54gkawkxy9bvknw5ynxhhl1l";
}) })
(fetchNuGet { (fetchNuGet {
pname = "Microsoft.NETCore.App.Runtime.linux-arm64"; pname = "Microsoft.NETCore.App.Runtime.linux-arm64";
version = "6.0.25"; version = "6.0.26";
sha256 = "0jpcmva1l8z36r4phz055l7fz9s6z8pv8pqc4ia69mhhgvr0ks7y"; sha256 = "164hfrwqz5dxcbb441lridk4mzcqmarb0b7ckgvqhsvpawyjw88v";
}) })
(fetchNuGet { (fetchNuGet {
pname = "Microsoft.NETCore.App.Runtime.linux-arm64"; pname = "Microsoft.NETCore.App.Runtime.linux-arm64";
version = "8.0.0"; version = "8.0.1";
sha256 = "0gwqmkmr7jy3sjh9gha82amlry41gp8nwswy2iqfw54f28db63n7"; sha256 = "0353whnjgz3sqhzsfrviad3a3db4pk7hl7m4wwppv5mqdg9i9ri5";
}) })
(fetchNuGet { (fetchNuGet {
pname = "Microsoft.NETCore.App.Runtime.linux-x64"; pname = "Microsoft.NETCore.App.Runtime.linux-x64";
version = "6.0.25"; version = "6.0.26";
sha256 = "012jml0bqxbspahf1j4bvvd91pz85hsbcyhq00gxczcazhxpkhz4"; sha256 = "0islayddpnflviqpbq4djc4f3v9nhsa2y76k5x6il3csq5vdw2hq";
}) })
(fetchNuGet { (fetchNuGet {
pname = "Microsoft.NETCore.App.Runtime.linux-x64"; pname = "Microsoft.NETCore.App.Runtime.linux-x64";
version = "8.0.0"; version = "8.0.1";
sha256 = "042cjvnwrrjs3mw5q8q5kinh0cwkks33i3n1vyifaid2jbr3wlc0"; sha256 = "1g5b30f4l8a1zjjr3b8pk9mcqxkxqwa86362f84646xaj4iw3a4d";
}) })
(fetchNuGet { (fetchNuGet {
pname = "Microsoft.NETCore.App.Runtime.osx-arm64"; pname = "Microsoft.NETCore.App.Runtime.osx-arm64";
version = "6.0.25"; version = "6.0.26";
sha256 = "0wgwxpyy1n550sw7npjg69zpxknwn0ay30m2qybvqb5mj857qzxi"; sha256 = "1acn5zw1pxzmcg3c0pbf9hal36fbdh9mvbsiwra7simrk7hzqpdc";
}) })
(fetchNuGet { (fetchNuGet {
pname = "Microsoft.NETCore.App.Runtime.osx-arm64"; pname = "Microsoft.NETCore.App.Runtime.osx-arm64";
version = "8.0.0"; version = "8.0.1";
sha256 = "06ndp4wh1cap01dql3nixka4g56bf6ipmqys7xaxvg4xisf79x8d"; sha256 = "0cdrpdaq5sl3602anfx1p0z0ncx2sjjvl6mgsd6y38g47n7f95jc";
}) })
(fetchNuGet { (fetchNuGet {
pname = "Microsoft.NETCore.App.Runtime.osx-x64"; pname = "Microsoft.NETCore.App.Runtime.osx-x64";
version = "6.0.25"; version = "6.0.26";
sha256 = "08vr7c5bg5x3w35l54z1azif7ysfc2yiyz50ip1dl0mpqywvlswr"; sha256 = "00f9l9dkdz0zv5csaw8fkm6s8ckrj5n9k3ygz12daa22l3bcn6ii";
}) })
(fetchNuGet { (fetchNuGet {
pname = "Microsoft.NETCore.App.Runtime.osx-x64"; pname = "Microsoft.NETCore.App.Runtime.osx-x64";
version = "8.0.0"; version = "8.0.1";
sha256 = "1kh5bnaf6h9mr4swcalrp304625frjiw6mlz1052rxwzsdq98a96"; sha256 = "1fk1flqp6ji0l4c2gvh83ykndpx7a2nkkgrgkgql3c75j1k2v1s9";
}) })
(fetchNuGet { (fetchNuGet {
pname = "Microsoft.NETCore.App.Runtime.win-x64"; pname = "Microsoft.NETCore.App.Runtime.win-x64";
version = "6.0.25"; version = "6.0.26";
sha256 = "03snpmx204xvc9668riisvvdjjgdqhwj7yjp85w5lh8j8ygrqkif"; sha256 = "0i2p356phfc5y6qnr3vyrzjfi1mrbwfb6g85k4q37bbyxjfp7zl9";
}) })
(fetchNuGet { (fetchNuGet {
pname = "Microsoft.NETCore.App.Runtime.win-x64"; pname = "Microsoft.NETCore.App.Runtime.win-x64";
version = "8.0.0"; version = "8.0.1";
sha256 = "054icf5jjnwnswrnv1r05x3pfjvacbz6g3dj8caar1zp53k49rkk"; sha256 = "198576cdkl72xs29zznff9ls763p8pfr0zji7b74dqxd5ga0s3bd";
})
(fetchNuGet {
pname = "Microsoft.NETCore.Platforms";
version = "1.1.0";
sha256 = "08vh1r12g6ykjygq5d3vq09zylgb84l63k49jc4v8faw9g93iqqm";
}) })
(fetchNuGet { (fetchNuGet {
pname = "Microsoft.NETCore.Platforms"; pname = "Microsoft.NETCore.Platforms";
@@ -263,13 +273,13 @@
}) })
(fetchNuGet { (fetchNuGet {
pname = "Microsoft.TestPlatform.ObjectModel"; pname = "Microsoft.TestPlatform.ObjectModel";
version = "17.8.0"; version = "17.9.0";
sha256 = "0b0i7lmkrcfvim8i3l93gwqvkhhhfzd53fqfnygdqvkg6np0cg7m"; sha256 = "1kgsl9w9fganbm9wvlkqgk0ag9hfi58z88rkfybc6kvg78bx89ca";
}) })
(fetchNuGet { (fetchNuGet {
pname = "Microsoft.TestPlatform.TestHost"; pname = "Microsoft.TestPlatform.TestHost";
version = "17.8.0"; version = "17.9.0";
sha256 = "0f5jah93kjkvxwmhwb78lw11m9pkkq9fvf135hpymmmpxqbdh97q"; sha256 = "19ffh31a1jxzn8j69m1vnk5hyfz3dbxmflq77b8x82zybiilh5nl";
}) })
(fetchNuGet { (fetchNuGet {
pname = "Myriad.Core"; pname = "Myriad.Core";
@@ -286,6 +296,16 @@
version = "3.6.133"; version = "3.6.133";
sha256 = "1cdw8krvsnx0n34f7fm5hiiy7bs6h3asvncqcikc0g46l50w2j80"; sha256 = "1cdw8krvsnx0n34f7fm5hiiy7bs6h3asvncqcikc0g46l50w2j80";
}) })
(fetchNuGet {
pname = "NETStandard.Library";
version = "2.0.0";
sha256 = "1bc4ba8ahgk15m8k4nd7x406nhi0kwqzbgjk2dmw52ss553xz7iy";
})
(fetchNuGet {
pname = "NETStandard.Library";
version = "2.0.3";
sha256 = "1fn9fxppfcg4jgypp2pmrpr6awl3qz1xmnri0cygpkwvyx27df1y";
})
(fetchNuGet { (fetchNuGet {
pname = "Newtonsoft.Json"; pname = "Newtonsoft.Json";
version = "13.0.1"; version = "13.0.1";
@@ -298,28 +318,23 @@
}) })
(fetchNuGet { (fetchNuGet {
pname = "NuGet.Common"; pname = "NuGet.Common";
version = "6.8.0"; version = "6.9.1";
sha256 = "0l3ij8iwy7wj6s7f93lzi9168r4wz8zyin6a08iwgk7hvq44cia1"; sha256 = "0ic3d46r9v05pkczpmskw86yzixm6iwshbw0ya8i2957nhhlymw8";
}) })
(fetchNuGet { (fetchNuGet {
pname = "NuGet.Configuration"; pname = "NuGet.Configuration";
version = "6.8.0"; version = "6.9.1";
sha256 = "0x03p408smkmv1gv7pmvsia4lkn0xaj4wfrkl58pjf8bbv51y0yw"; sha256 = "07z4qgbibpg59j2r05ifnqdyqf2xinm33rx7gjyr1f73kzg01m33";
}) })
(fetchNuGet { (fetchNuGet {
pname = "NuGet.Frameworks"; pname = "NuGet.Frameworks";
version = "6.5.0"; version = "6.9.1";
sha256 = "0s37d1p4md0k6d4cy6sq36f2dgkd9qfbzapxhkvi8awwh0vrynhj"; sha256 = "0s3az3ac53icjnmb14hfjcmkvzscvrkm62jgqf48yvsbysyhqm5s";
})
(fetchNuGet {
pname = "NuGet.Frameworks";
version = "6.8.0";
sha256 = "0i2xvhgkjkjr496i3pg8hamwv6505fia45qhn7jg5m01wb3cvsjl";
}) })
(fetchNuGet { (fetchNuGet {
pname = "NuGet.Packaging"; pname = "NuGet.Packaging";
version = "6.8.0"; version = "6.9.1";
sha256 = "031z4s905bxi94h3f0qy4j1b6jxdxgqgpkzqvvpfxch07szxcbim"; sha256 = "0w0arkmzg3qh1brq4vm10zrsjm7nw706ld4y5kqcmvjpd16f4b4y";
}) })
(fetchNuGet { (fetchNuGet {
pname = "NuGet.Protocol"; pname = "NuGet.Protocol";
@@ -328,19 +343,19 @@
}) })
(fetchNuGet { (fetchNuGet {
pname = "NuGet.Versioning"; pname = "NuGet.Versioning";
version = "6.8.0"; version = "6.9.1";
sha256 = "1sd25h46fd12ng780r02q4ijcx1imkb53kj1y2y7cwg5myh537ks"; sha256 = "0xrs82dydy9cgxf0qypr01wawwnq1nf6fc7rwisb4y5v4r259fdm";
})
(fetchNuGet {
pname = "NUnit";
version = "3.13.3";
sha256 = "0wdzfkygqnr73s6lpxg5b1pwaqz9f414fxpvpdmf72bvh4jaqzv6";
}) })
(fetchNuGet { (fetchNuGet {
pname = "NUnit"; pname = "NUnit";
version = "4.0.1"; version = "4.0.1";
sha256 = "0jgiq3dbwli5r70j0bw7021d69r7bhr58s8kphlpjmf7k47l5pcd"; sha256 = "0jgiq3dbwli5r70j0bw7021d69r7bhr58s8kphlpjmf7k47l5pcd";
}) })
(fetchNuGet {
pname = "NUnit.Analyzers";
version = "3.10.0";
sha256 = "1zc6s7lmzw5avrnbbjwyzla9d6bafbpxgv62m4zlqxv14p85md0d";
})
(fetchNuGet { (fetchNuGet {
pname = "NUnit3TestAdapter"; pname = "NUnit3TestAdapter";
version = "4.5.0"; version = "4.5.0";