mirror of
https://github.com/Smaug123/WoofWare.Myriad
synced 2025-10-05 12:08:46 +00:00
Compare commits
207 Commits
WoofWare.M
...
WoofWare.M
Author | SHA1 | Date | |
---|---|---|---|
|
e6867572b7 | ||
|
5a92d86ad1 | ||
|
fbfd7131f3 | ||
|
ca72b07c33 | ||
|
cdaa46fe00 | ||
|
c70c68b15b | ||
|
17cbaf4a85 | ||
|
e48c5209c7 | ||
|
cc8e3205b1 | ||
|
34cb74dc7b | ||
|
099d14b0b1 | ||
|
96908a5fa6 | ||
|
3e39e187df | ||
|
9f4245341c | ||
|
de58f5ed1f | ||
|
e8571553c4 | ||
|
19761db983 | ||
|
f30a73fd4f | ||
|
b2d64562bf | ||
|
e7e629613e | ||
|
4560138b59 | ||
|
425d5313b4 | ||
|
0fe97da788 | ||
|
f944953384 | ||
|
7930039ad1 | ||
|
8d275f0047 | ||
|
682b12fdb2 | ||
|
325f8634a4 | ||
|
3e5d663544 | ||
|
bb88f80c85 | ||
|
71f26930c6 | ||
|
680728a06e | ||
|
cdc6f2d511 | ||
|
3be487c328 | ||
|
a5f4d169ca | ||
|
ce634efff2 | ||
|
1529dd1fb2 | ||
|
59558b0766 | ||
|
8602894efc | ||
|
51d349b365 | ||
|
120df84bbf | ||
|
603f875a12 | ||
|
2df41555de | ||
|
49e31e52b5 | ||
|
277a186fda | ||
|
129687ec1c | ||
|
c7fea55e28 | ||
|
ded7b32771 | ||
|
b272f8b645 | ||
|
b8d60aec90 | ||
|
0e3510e1e5 | ||
|
8a1edd90d5 | ||
|
74fdd7c0a9 | ||
|
23f55814f9 | ||
|
15c04bb373 | ||
|
a860a93f9c | ||
|
b056af348e | ||
|
b44c8db6e9 | ||
|
7d6a2cea01 | ||
|
d779a602f4 | ||
|
23cd5272fb | ||
|
93538ee6b4 | ||
|
8a29c2f444 | ||
|
1367e00f34 | ||
|
bff08c90cd | ||
|
4136f7fe94 | ||
|
9fe2e3b1fa | ||
|
13a597a365 | ||
|
3acc492f22 | ||
|
eefe64f5a4 | ||
|
93a2d92299 | ||
|
33793e8cbe | ||
|
fa7ef1ffba | ||
|
a25c45dc3a | ||
|
af5f2abdf8 | ||
|
0f89816432 | ||
|
d571da6a22 | ||
|
39a9e94ca5 | ||
|
487f73312a | ||
|
b7aa564306 | ||
|
a34dee0a1c | ||
|
44506f3650 | ||
|
ca7134cfb8 | ||
|
773fd088a7 | ||
|
6e5c0332cd | ||
|
aece186424 | ||
|
827e9aa3ec | ||
|
d59ebdfccb | ||
|
5319a33b7b | ||
|
29b93b2f20 | ||
|
40106d8aa3 | ||
|
4d641b0662 | ||
|
16e6b91548 | ||
|
8488883835 | ||
|
0652744c57 | ||
|
9252979673 | ||
|
1120a3752d | ||
|
7ca6b0c0eb | ||
|
50efb8d9bc | ||
|
93a1b630c8 | ||
|
4482038e8a | ||
|
a41fa9dd37 | ||
|
fc5acc2f58 | ||
|
0a1783d6ed | ||
|
9a3ebbf28f | ||
|
e22525c200 | ||
|
09b7109c84 | ||
|
693b95106a | ||
|
49ecfbf5e5 | ||
|
5748ac3d5b | ||
|
913959a740 | ||
|
93ffc065cd | ||
|
d14efba7e7 | ||
|
f5cf0b79dd | ||
|
029e3746bb | ||
|
8ae749c529 | ||
|
e4cbab3209 | ||
|
bdce82fb7a | ||
|
8f9f933971 | ||
|
3a55ba1242 | ||
|
047b2eda99 | ||
|
2220f88053 | ||
|
86b938c81e | ||
|
1832a57bdf | ||
|
38f4821fa4 | ||
|
70aaf8c408 | ||
|
417ca45c37 | ||
|
569b3cc553 | ||
|
20226b9da9 | ||
|
f800e53bff | ||
|
5358f5da0e | ||
|
a868b8c08e | ||
|
a4f945a3ee | ||
|
8434730ba7 | ||
|
811026996c | ||
|
25b2b160bb | ||
|
4679474604 | ||
|
e16e241785 | ||
|
a52e4a46b0 | ||
|
f40a368948 | ||
|
adaee61fbf | ||
|
d388660bfe | ||
|
d0e9ba0efd | ||
|
d7d6c57910 | ||
|
98e52743f5 | ||
|
896696e002 | ||
|
654f760f3a | ||
|
31bd9e22f2 | ||
|
b7a240bbb9 | ||
|
ebbe10ad81 | ||
|
8f9af9af67 | ||
|
2c7cd91cbc | ||
|
ffaa373da9 | ||
|
9f8459a7d3 | ||
|
362542d5ee | ||
|
18309becbd | ||
|
e96803e303 | ||
|
b53b410feb | ||
|
398cd04a2a | ||
|
434c042510 | ||
|
c590db2a65 | ||
|
6a81513a93 | ||
|
ba31689145 | ||
|
85929d49d5 | ||
|
db4694f6e7 | ||
|
669eccbdef | ||
|
1bb87e55da | ||
|
4901e7cdf4 | ||
|
68bd4bc1fd | ||
|
8da0fd01fe | ||
|
18c7a2e920 | ||
|
f371ee59fe | ||
|
f8296e54bc | ||
|
adf497c5db | ||
|
04ecbe6002 | ||
|
7b14e52e9d | ||
|
8e47f39efc | ||
|
6942ba42b9 | ||
|
b98080690d | ||
|
81b7e5361d | ||
|
94b88a4143 | ||
|
ed3ffecb52 | ||
|
c696dcf31f | ||
|
d5bb2726d3 | ||
|
f17290d0f1 | ||
|
35cd94cba1 | ||
|
1b3eb03380 | ||
|
b846ce08a3 | ||
|
4b9f63d374 | ||
|
b9ba07a8a7 | ||
|
e80ed51498 | ||
|
61b07ad802 | ||
|
59369bcb94 | ||
|
072169e4e3 | ||
|
91136a25ab | ||
|
c51038448a | ||
|
09780efb07 | ||
|
f562271c12 | ||
|
e3081c3136 | ||
|
232d2ba5ec | ||
|
f7458f521e | ||
|
bfc25a672b | ||
|
af7fcb3028 | ||
|
91853b1fff | ||
|
1144e93c1c | ||
|
d899d77ae2 | ||
|
a2ad430b2f |
@@ -3,13 +3,13 @@
|
||||
"isRoot": true,
|
||||
"tools": {
|
||||
"fantomas": {
|
||||
"version": "6.3.0-alpha-007",
|
||||
"version": "7.0.2",
|
||||
"commands": [
|
||||
"fantomas"
|
||||
]
|
||||
},
|
||||
"fsharp-analyzers": {
|
||||
"version": "0.25.0",
|
||||
"version": "0.31.0",
|
||||
"commands": [
|
||||
"fsharp-analyzers"
|
||||
]
|
||||
|
@@ -2,7 +2,6 @@ root=true
|
||||
|
||||
[*]
|
||||
charset=utf-8
|
||||
end_of_line=crlf
|
||||
trim_trailing_whitespace=true
|
||||
insert_final_newline=true
|
||||
indent_style=space
|
||||
|
1
.fantomasignore
Normal file
1
.fantomasignore
Normal file
@@ -0,0 +1 @@
|
||||
.direnv/
|
10
.gitattributes
vendored
10
.gitattributes
vendored
@@ -1,5 +1,5 @@
|
||||
* eol=auto
|
||||
*.sh text eol=lf
|
||||
*.yaml text
|
||||
*.nix text eol=lf
|
||||
hooks/pre-push text eol=lf
|
||||
* eol=auto
|
||||
*.sh text eol=lf
|
||||
*.yaml text
|
||||
*.nix text eol=lf
|
||||
hooks/pre-push text eol=lf
|
||||
|
215
.github/workflows/dotnet.yaml
vendored
215
.github/workflows/dotnet.yaml
vendored
@@ -1,3 +1,4 @@
|
||||
# yaml-language-server: $schema=https://raw.githubusercontent.com/SchemaStore/schemastore/master/src/schemas/json/github-workflow.json
|
||||
name: .NET
|
||||
|
||||
on:
|
||||
@@ -28,7 +29,7 @@ jobs:
|
||||
with:
|
||||
fetch-depth: 0 # so that NerdBank.GitVersioning has access to history
|
||||
- name: Install Nix
|
||||
uses: cachix/install-nix-action@v25
|
||||
uses: cachix/install-nix-action@v31
|
||||
with:
|
||||
extra_nix_config: |
|
||||
access-tokens = github.com=${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -49,7 +50,7 @@ jobs:
|
||||
with:
|
||||
fetch-depth: 0 # so that NerdBank.GitVersioning has access to history
|
||||
- name: Install Nix
|
||||
uses: cachix/install-nix-action@v25
|
||||
uses: cachix/install-nix-action@v31
|
||||
with:
|
||||
extra_nix_config: |
|
||||
access-tokens = github.com=${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -66,12 +67,14 @@ jobs:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: Install Nix
|
||||
uses: cachix/install-nix-action@v25
|
||||
uses: cachix/install-nix-action@v31
|
||||
with:
|
||||
extra_nix_config: |
|
||||
access-tokens = github.com=${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Build
|
||||
run: nix build
|
||||
- name: Reproducibility check
|
||||
run: nix build --rebuild
|
||||
|
||||
check-dotnet-format:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -79,20 +82,41 @@ jobs:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: Install Nix
|
||||
uses: cachix/install-nix-action@v25
|
||||
uses: cachix/install-nix-action@v31
|
||||
with:
|
||||
extra_nix_config: |
|
||||
access-tokens = github.com=${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Run Fantomas
|
||||
run: nix run .#fantomas -- --check .
|
||||
|
||||
check-accurate-generations:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0 # so that NerdBank.GitVersioning has access to history
|
||||
- name: Install Nix
|
||||
uses: cachix/install-nix-action@v31
|
||||
with:
|
||||
extra_nix_config: |
|
||||
access-tokens = github.com=${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Whitespace change
|
||||
run: "echo ' ' >> ConsumePlugin/List.fs"
|
||||
- name: Generate code
|
||||
run: nix develop --command dotnet build
|
||||
- name: Run Fantomas
|
||||
run: nix run .#fantomas -- .
|
||||
- name: Verify there is no diff
|
||||
run: git diff --name-only --no-color --exit-code
|
||||
|
||||
check-nix-format:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: Install Nix
|
||||
uses: cachix/install-nix-action@v25
|
||||
uses: cachix/install-nix-action@v31
|
||||
with:
|
||||
extra_nix_config: |
|
||||
access-tokens = github.com=${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -105,7 +129,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@master
|
||||
- name: Install Nix
|
||||
uses: cachix/install-nix-action@v25
|
||||
uses: cachix/install-nix-action@v31
|
||||
with:
|
||||
extra_nix_config: |
|
||||
access-tokens = github.com=${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -118,7 +142,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@master
|
||||
- name: Install Nix
|
||||
uses: cachix/install-nix-action@v25
|
||||
uses: cachix/install-nix-action@v31
|
||||
with:
|
||||
extra_nix_config: |
|
||||
access-tokens = github.com=${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -132,7 +156,7 @@ jobs:
|
||||
with:
|
||||
fetch-depth: 0 # so that NerdBank.GitVersioning has access to history
|
||||
- name: Install Nix
|
||||
uses: cachix/install-nix-action@v25
|
||||
uses: cachix/install-nix-action@v31
|
||||
with:
|
||||
extra_nix_config: |
|
||||
access-tokens = github.com=${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -174,40 +198,157 @@ jobs:
|
||||
# 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
|
||||
|
||||
github-release-dry-run:
|
||||
strategy:
|
||||
matrix:
|
||||
artifact:
|
||||
- nuget-package-plugin
|
||||
- nuget-package-attribute
|
||||
runs-on: ubuntu-latest
|
||||
needs: [nuget-pack]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Download NuGet artifact
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: ${{ matrix.artifact }}
|
||||
- name: Compute package path
|
||||
id: compute-path
|
||||
run: |
|
||||
find . -maxdepth 1 -type f -name 'WoofWare.Myriad.*.nupkg' -exec sh -c 'echo "output=$(basename "$1")" >> $GITHUB_OUTPUT' shell {} \;
|
||||
- name: Compute tag name
|
||||
id: compute-tag
|
||||
env:
|
||||
NUPKG_PATH: ${{ steps.compute-path.outputs.output }}
|
||||
run: echo "output=$(basename "$NUPKG_PATH" .nupkg)" >> $GITHUB_OUTPUT
|
||||
- name: Tag and release
|
||||
uses: G-Research/common-actions/github-release@19d7281a0f9f83e13c78f99a610dbc80fc59ba3b
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
target-commitish: ${{ github.sha }}
|
||||
tag: ${{ steps.compute-tag.outputs.output }}
|
||||
binary-contents: ${{ steps.compute-path.outputs.output }}
|
||||
dry-run: true
|
||||
|
||||
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, check-accurate-generations, build, build-nix, linkcheck, flake-check, analyzers, nuget-pack, expected-pack, github-release-dry-run]
|
||||
if: ${{ always() }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- run: echo "All required checks complete."
|
||||
- uses: G-Research/common-actions/check-required-lite@2b7dc49cb14f3344fbe6019c14a31165e258c059
|
||||
with:
|
||||
needs-context: ${{ toJSON(needs) }}
|
||||
|
||||
nuget-publish:
|
||||
attestation-attribute:
|
||||
runs-on: ubuntu-latest
|
||||
needs: [all-required-checks-complete]
|
||||
if: ${{ !github.event.repository.fork && github.ref == 'refs/heads/main' }}
|
||||
permissions:
|
||||
id-token: write
|
||||
attestations: write
|
||||
contents: read
|
||||
steps:
|
||||
- name: Download NuGet artifact
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: nuget-package-attribute
|
||||
path: packed
|
||||
- name: Attest Build Provenance
|
||||
uses: actions/attest-build-provenance@e8998f949152b193b063cb0ec769d69d929409be # v2.4.0
|
||||
with:
|
||||
subject-path: "packed/*.nupkg"
|
||||
|
||||
attestation-plugin:
|
||||
runs-on: ubuntu-latest
|
||||
needs: [all-required-checks-complete]
|
||||
if: ${{ !github.event.repository.fork && github.ref == 'refs/heads/main' }}
|
||||
permissions:
|
||||
id-token: write
|
||||
attestations: write
|
||||
contents: read
|
||||
steps:
|
||||
- name: Download NuGet artifact
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: nuget-package-plugin
|
||||
path: packed
|
||||
- name: Attest Build Provenance
|
||||
uses: actions/attest-build-provenance@e8998f949152b193b063cb0ec769d69d929409be # v2.4.0
|
||||
with:
|
||||
subject-path: "packed/*.nupkg"
|
||||
|
||||
nuget-publish-attribute:
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ !github.event.repository.fork && github.ref == 'refs/heads/main' }}
|
||||
needs: [all-required-checks-complete]
|
||||
environment: main-deploy
|
||||
permissions:
|
||||
id-token: write
|
||||
attestations: write
|
||||
contents: read
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install Nix
|
||||
uses: cachix/install-nix-action@v25
|
||||
uses: cachix/install-nix-action@v31
|
||||
with:
|
||||
extra_nix_config: |
|
||||
access-tokens = github.com=${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Download NuGet artifact (plugin)
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: nuget-package-plugin
|
||||
path: packed-plugin
|
||||
- name: Publish to NuGet (plugin)
|
||||
run: nix develop --command dotnet nuget push "packed-plugin/WoofWare.Myriad.Plugins.*.nupkg" --api-key ${{ secrets.NUGET_API_KEY }} --source https://api.nuget.org/v3/index.json --skip-duplicate
|
||||
- name: Download NuGet artifact (attribute)
|
||||
- name: Download NuGet artifact
|
||||
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
|
||||
path: packed
|
||||
- name: Identify `dotnet`
|
||||
id: dotnet-identify
|
||||
run: nix develop --command bash -c 'echo "dotnet=$(which dotnet)" >> $GITHUB_OUTPUT'
|
||||
- name: Publish to NuGet
|
||||
id: publish-success
|
||||
uses: G-Research/common-actions/publish-nuget@2b7dc49cb14f3344fbe6019c14a31165e258c059
|
||||
with:
|
||||
package-name: WoofWare.Myriad.Plugins.Attributes
|
||||
nuget-key: ${{ secrets.NUGET_API_KEY }}
|
||||
nupkg-dir: packed/
|
||||
dotnet: ${{ steps.dotnet-identify.outputs.dotnet }}
|
||||
|
||||
github-release-plugin:
|
||||
nuget-publish-plugin:
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ !github.event.repository.fork && github.ref == 'refs/heads/main' }}
|
||||
needs: [all-required-checks-complete]
|
||||
environment: main-deploy
|
||||
permissions:
|
||||
id-token: write
|
||||
attestations: write
|
||||
contents: read
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install Nix
|
||||
uses: cachix/install-nix-action@v31
|
||||
with:
|
||||
extra_nix_config: |
|
||||
access-tokens = github.com=${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Download NuGet artifact
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: nuget-package-plugin
|
||||
path: packed
|
||||
- name: Identify `dotnet`
|
||||
id: dotnet-identify
|
||||
run: nix develop --command bash -c 'echo "dotnet=$(which dotnet)" >> $GITHUB_OUTPUT'
|
||||
- name: Publish to NuGet
|
||||
id: publish-success
|
||||
uses: G-Research/common-actions/publish-nuget@2b7dc49cb14f3344fbe6019c14a31165e258c059
|
||||
with:
|
||||
package-name: WoofWare.Myriad.Plugins
|
||||
nuget-key: ${{ secrets.NUGET_API_KEY }}
|
||||
nupkg-dir: packed/
|
||||
dotnet: ${{ steps.dotnet-identify.outputs.dotnet }}
|
||||
|
||||
github-release:
|
||||
strategy:
|
||||
matrix:
|
||||
artifact:
|
||||
- nuget-package-attribute
|
||||
- nuget-package-plugin
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ !github.event.repository.fork && github.ref == 'refs/heads/main' }}
|
||||
needs: [all-required-checks-complete]
|
||||
@@ -216,15 +357,23 @@ jobs:
|
||||
contents: write
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Download NuGet artifact (plugin)
|
||||
- name: Download NuGet artifact
|
||||
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
|
||||
name: ${{ matrix.artifact }}
|
||||
- name: Compute package path
|
||||
id: compute-path
|
||||
run: |
|
||||
find . -maxdepth 1 -type f -name 'WoofWare.Myriad.*.nupkg' -exec sh -c 'echo "output=$(basename "$1")" >> $GITHUB_OUTPUT' shell {} \;
|
||||
- name: Compute tag name
|
||||
id: compute-tag
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: sh .github/workflows/tag.sh
|
||||
NUPKG_PATH: ${{ steps.compute-path.outputs.output }}
|
||||
run: echo "output=$(basename "$NUPKG_PATH" .nupkg)" >> $GITHUB_OUTPUT
|
||||
- name: Tag and release
|
||||
uses: G-Research/common-actions/github-release@19d7281a0f9f83e13c78f99a610dbc80fc59ba3b
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
target-commitish: ${{ github.sha }}
|
||||
tag: ${{ steps.compute-tag.outputs.output }}
|
||||
binary-contents: ${{ steps.compute-path.outputs.output }}
|
||||
|
57
.github/workflows/flake_update.yaml
vendored
Normal file
57
.github/workflows/flake_update.yaml
vendored
Normal file
@@ -0,0 +1,57 @@
|
||||
# yaml-language-server: $schema=https://raw.githubusercontent.com/SchemaStore/schemastore/master/src/schemas/json/github-workflow.json
|
||||
name: Weekly Nix Flake Update
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 0 * * 0' # Runs at 00:00 every Sunday
|
||||
workflow_dispatch: # Allows manual triggering
|
||||
|
||||
jobs:
|
||||
update-nix-flake:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install Nix
|
||||
uses: DeterminateSystems/nix-installer-action@main
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Update Nix flake
|
||||
run: 'nix flake update'
|
||||
|
||||
- name: Build fetch-deps
|
||||
run: 'nix build ".#default.fetch-deps"'
|
||||
|
||||
- name: Run fetch-deps
|
||||
run: |
|
||||
set -o pipefail
|
||||
./result nix/deps.json
|
||||
|
||||
- name: Format
|
||||
run: 'nix develop --command alejandra .'
|
||||
|
||||
- name: Create token
|
||||
id: generate-token
|
||||
uses: actions/create-github-app-token@v2
|
||||
with:
|
||||
# https://github.com/actions/create-github-app-token/issues/136
|
||||
app-id: ${{ secrets.APP_ID }}
|
||||
private-key: ${{ secrets.APP_PRIVATE_KEY }}
|
||||
|
||||
- name: Raise pull request
|
||||
uses: Smaug123/commit-action@d34807f26cb52c7a05bbd80efe9f964cdf29bc87
|
||||
id: cpr
|
||||
with:
|
||||
bearer-token: ${{ steps.generate-token.outputs.token }}
|
||||
pr-title: "Upgrade Nix flake and deps"
|
||||
branch-name: "auto-pr"
|
||||
|
||||
- name: Enable Pull Request Automerge
|
||||
if: ${{ steps.cpr.outputs.pull-request-number }}
|
||||
uses: peter-evans/enable-pull-request-automerge@v3
|
||||
with:
|
||||
token: ${{ steps.generate-token.outputs.token }}
|
||||
pull-request-number: ${{ steps.cpr.outputs.pull-request-number }}
|
||||
merge-method: squash
|
17
.github/workflows/tag.sh
vendored
17
.github/workflows/tag.sh
vendored
@@ -1,17 +0,0 @@
|
||||
#!/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}'
|
26
.gitignore
vendored
26
.gitignore
vendored
@@ -1,12 +1,14 @@
|
||||
bin/
|
||||
obj/
|
||||
/packages/
|
||||
riderModule.iml
|
||||
/_ReSharper.Caches/
|
||||
.idea/
|
||||
*.sln.DotSettings.user
|
||||
.DS_Store
|
||||
result
|
||||
.analyzerpackages/
|
||||
analysis.sarif
|
||||
.direnv/
|
||||
bin/
|
||||
obj/
|
||||
/packages/
|
||||
riderModule.iml
|
||||
/_ReSharper.Caches/
|
||||
.idea/
|
||||
*.sln.DotSettings.user
|
||||
.DS_Store
|
||||
result
|
||||
.analyzerpackages/
|
||||
analysis.sarif
|
||||
.direnv/
|
||||
.venv/
|
||||
.vs/
|
||||
|
63
CHANGELOG.md
63
CHANGELOG.md
@@ -1,6 +1,67 @@
|
||||
Notable changes are recorded here.
|
||||
|
||||
# WoofWare.Myriad.Plugins 1.4 -> 2.0
|
||||
# WoofWare.Myriad.Plugins 7.0.1
|
||||
|
||||
All generators should now be compatible with `<Nullable>enable</Nullable>`.
|
||||
|
||||
**Please test the results and let me know of unexpected failures.**
|
||||
There are a number of heuristics in this code, because:
|
||||
|
||||
* `System.Text.Json.Nodes` is an unfathomably weird API which simply requires us to make educated guesses about whether a user-provided type is supposed to be nullable, despite this being irrelevant to the operation of `System.Text.Json`;
|
||||
* Some types (like `Uri` and `String`) have `ToString` methods which can't return `null`, but in general `Object.ToString` can of course return `null`, and as far as I can tell there is simply no way to know from the source alone whether a given type will have a nullable `ToString`.
|
||||
|
||||
# WoofWare.Myriad.Plugins 6.0.1
|
||||
|
||||
The `ArgParser` generator's type signatures have changed.
|
||||
The `parse'` method no longer takes `getEnvironmentVariable : string -> string`; it's now `getEnvironmentVariable : string -> string option`.
|
||||
This is to permit satisfying the `<Nullable>enable</Nullable>` compiler setting.
|
||||
If you're calling `parse'`, give it `Environment.GetEnvironmentVariable >> Option.ofObj` instead.
|
||||
|
||||
# WoofWare.Myriad.Plugins 5.0.1
|
||||
|
||||
We now enforce non-nullability on more types during JSON parse.
|
||||
We have always expected you to consume nullable types wrapped in an `option`, but now we enforce this in more cases by throwing `ArgumentNullException`.
|
||||
|
||||
# WoofWare.Myriad.Plugins 3.0.1
|
||||
|
||||
Semantics of `HttpClient`'s URI component composition changed:
|
||||
we now implicitly insert `/` characters after `[<BaseAddress>]` and `[<BasePath>]`, so that URI composition doesn't silently drop the last component if you didn't put a slash there.
|
||||
|
||||
# WoofWare.Myriad.Plugins 2.3.9
|
||||
|
||||
`JsonParse` and `JsonSerialize` now interpret `[<JsonExtensionData>]`, which must be on a `Dictionary<string, _>`; this collects any extra components that were present on the JSON object.
|
||||
|
||||
# WoofWare.Myriad.Plugins 2.2.1, WoofWare.Myriad.Plugins.Attributes 3.2.1
|
||||
|
||||
New generator: `ArgParser`, a basic reflection-free argument parser.
|
||||
|
||||
# WoofWare.Myriad.Plugins 2.1.45, WoofWare.Myriad.Plugins.Attributes 3.1.7
|
||||
|
||||
The NuGet packages are now attested to through [GitHub Attestations](https://github.blog/2024-05-02-introducing-artifact-attestations-now-in-public-beta/).
|
||||
You can run `gh attestation verify ~/.nuget/packages/woofware.myriad.plugins/2.1.45/woofware.myriad.plugins.2.1.45.nupkg -o Smaug123`, for example, to verify with GitHub that the GitHub Actions pipeline on this repository produced a nupkg file with the same hash as the one you were served from NuGet.
|
||||
|
||||
# WoofWare.Myriad.Plugins 2.1.33
|
||||
|
||||
`JsonParse` can now deserialize the discriminated unions which `JsonSerialize` wrote out.
|
||||
|
||||
# WoofWare.Myriad.Plugins 2.1.32, WoofWare.Myriad.Plugins.Attributes 3.1.4
|
||||
|
||||
`JsonSerialize` can now serialize many discriminated unions.
|
||||
(This operation is inherently opinionated, because JSON does not model discriminated unions.)
|
||||
|
||||
# WoofWare.Myriad.Plugins 2.1.20, WoofWare.Myriad.Plugins.Attributes 3.0.1
|
||||
|
||||
We now bundle copies of the RestEase attributes in `WoofWare.Myriad.Plugins.Attributes`, in case you don't want to take a dependency on RestEase.
|
||||
|
||||
# WoofWare.Myriad.Plugins 2.1.15
|
||||
|
||||
The `GenerateMock` generator now permits a limited amount of inheritance in the record we're mocking out (specifically, `IDisposable`).
|
||||
|
||||
# WoofWare.Myriad.Plugins 2.1.8
|
||||
|
||||
No change to the packages, but this is when we started creating and tagging GitHub releases, which are a better source of truth than this file.
|
||||
|
||||
# WoofWare.Myriad.Plugins 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.
|
||||
|
237
ConsumePlugin/Args.fs
Normal file
237
ConsumePlugin/Args.fs
Normal file
@@ -0,0 +1,237 @@
|
||||
namespace ConsumePlugin
|
||||
|
||||
open System
|
||||
open System.IO
|
||||
open WoofWare.Myriad.Plugins
|
||||
|
||||
[<ArgParser>]
|
||||
type BasicNoPositionals =
|
||||
{
|
||||
Foo : int
|
||||
Bar : string
|
||||
Baz : bool
|
||||
Rest : int list
|
||||
}
|
||||
|
||||
[<ArgParser>]
|
||||
type Basic =
|
||||
{
|
||||
[<ArgumentHelpText "This is a foo!">]
|
||||
Foo : int
|
||||
Bar : string
|
||||
Baz : bool
|
||||
[<ArgumentHelpText "Here's where the rest of the args go">]
|
||||
[<PositionalArgs>]
|
||||
Rest : string list
|
||||
}
|
||||
|
||||
[<ArgParser>]
|
||||
type BasicWithIntPositionals =
|
||||
{
|
||||
Foo : int
|
||||
Bar : string
|
||||
Baz : bool
|
||||
[<PositionalArgs>]
|
||||
Rest : int list
|
||||
}
|
||||
|
||||
[<ArgParser>]
|
||||
type LoadsOfTypes =
|
||||
{
|
||||
Foo : int
|
||||
Bar : string
|
||||
Baz : bool
|
||||
SomeFile : FileInfo
|
||||
SomeDirectory : DirectoryInfo
|
||||
SomeList : DirectoryInfo list
|
||||
OptionalThingWithNoDefault : int option
|
||||
[<PositionalArgs>]
|
||||
Positionals : int list
|
||||
[<ArgumentDefaultFunction>]
|
||||
OptionalThing : Choice<bool, bool>
|
||||
[<ArgumentDefaultFunction>]
|
||||
AnotherOptionalThing : Choice<int, int>
|
||||
[<ArgumentDefaultEnvironmentVariable "CONSUMEPLUGIN_THINGS">]
|
||||
YetAnotherOptionalThing : Choice<string, string>
|
||||
}
|
||||
|
||||
static member DefaultOptionalThing () = true
|
||||
|
||||
static member DefaultAnotherOptionalThing () = 3
|
||||
|
||||
[<ArgParser>]
|
||||
type LoadsOfTypesNoPositionals =
|
||||
{
|
||||
Foo : int
|
||||
Bar : string
|
||||
Baz : bool
|
||||
SomeFile : FileInfo
|
||||
SomeDirectory : DirectoryInfo
|
||||
SomeList : DirectoryInfo list
|
||||
OptionalThingWithNoDefault : int option
|
||||
[<ArgumentDefaultFunction>]
|
||||
OptionalThing : Choice<bool, bool>
|
||||
[<ArgumentDefaultFunction>]
|
||||
AnotherOptionalThing : Choice<int, int>
|
||||
[<ArgumentDefaultEnvironmentVariable "CONSUMEPLUGIN_THINGS">]
|
||||
YetAnotherOptionalThing : Choice<string, string>
|
||||
}
|
||||
|
||||
static member DefaultOptionalThing () = false
|
||||
|
||||
static member DefaultAnotherOptionalThing () = 3
|
||||
|
||||
[<ArgParser true>]
|
||||
type DatesAndTimes =
|
||||
{
|
||||
Plain : TimeSpan
|
||||
[<InvariantCulture>]
|
||||
Invariant : TimeSpan
|
||||
[<ParseExact @"hh\:mm\:ss">]
|
||||
[<ArgumentHelpText "An exact time please">]
|
||||
Exact : TimeSpan
|
||||
[<InvariantCulture ; ParseExact @"hh\:mm\:ss">]
|
||||
InvariantExact : TimeSpan
|
||||
}
|
||||
|
||||
type ChildRecord =
|
||||
{
|
||||
Thing1 : int
|
||||
Thing2 : string
|
||||
}
|
||||
|
||||
[<ArgParser true>]
|
||||
type ParentRecord =
|
||||
{
|
||||
Child : ChildRecord
|
||||
AndAnother : bool
|
||||
}
|
||||
|
||||
type ChildRecordWithPositional =
|
||||
{
|
||||
Thing1 : int
|
||||
[<PositionalArgs>]
|
||||
Thing2 : Uri list
|
||||
}
|
||||
|
||||
[<ArgParser true>]
|
||||
type ParentRecordChildPos =
|
||||
{
|
||||
Child : ChildRecordWithPositional
|
||||
AndAnother : bool
|
||||
}
|
||||
|
||||
[<ArgParser true>]
|
||||
type ParentRecordSelfPos =
|
||||
{
|
||||
Child : ChildRecord
|
||||
[<PositionalArgs>]
|
||||
AndAnother : bool list
|
||||
}
|
||||
|
||||
[<ArgParser true>]
|
||||
type ChoicePositionals =
|
||||
{
|
||||
[<PositionalArgs>]
|
||||
Args : Choice<string, string> list
|
||||
}
|
||||
|
||||
[<ArgParser true>]
|
||||
type ContainsBoolEnvVar =
|
||||
{
|
||||
[<ArgumentDefaultEnvironmentVariable "CONSUMEPLUGIN_THINGS">]
|
||||
BoolVar : Choice<bool, bool>
|
||||
}
|
||||
|
||||
[<RequireQualifiedAccess>]
|
||||
module Consts =
|
||||
[<Literal>]
|
||||
let FALSE = false
|
||||
|
||||
[<Literal>]
|
||||
let TRUE = true
|
||||
|
||||
type DryRunMode =
|
||||
| [<ArgumentFlag(Consts.FALSE)>] Wet
|
||||
| [<ArgumentFlag true>] Dry
|
||||
|
||||
[<ArgParser true>]
|
||||
type WithFlagDu =
|
||||
{
|
||||
DryRun : DryRunMode
|
||||
}
|
||||
|
||||
[<ArgParser true>]
|
||||
type ContainsFlagEnvVar =
|
||||
{
|
||||
// This phrasing is odd, but it's for a test. Nobody's really going to have `--dry-run`
|
||||
// controlled by an env var!
|
||||
[<ArgumentDefaultEnvironmentVariable "CONSUMEPLUGIN_THINGS">]
|
||||
DryRun : Choice<DryRunMode, DryRunMode>
|
||||
}
|
||||
|
||||
[<ArgParser true>]
|
||||
type ContainsFlagDefaultValue =
|
||||
{
|
||||
[<ArgumentDefaultFunction>]
|
||||
DryRun : Choice<DryRunMode, DryRunMode>
|
||||
}
|
||||
|
||||
static member DefaultDryRun () = DryRunMode.Wet
|
||||
|
||||
[<ArgParser true>]
|
||||
type ManyLongForms =
|
||||
{
|
||||
[<ArgumentLongForm "do-something-else">]
|
||||
[<ArgumentLongForm "anotherarg">]
|
||||
DoTheThing : string
|
||||
|
||||
[<ArgumentLongForm "turn-it-on">]
|
||||
[<ArgumentLongForm "dont-turn-it-off">]
|
||||
SomeFlag : bool
|
||||
}
|
||||
|
||||
[<RequireQualifiedAccess>]
|
||||
type private IrrelevantDu =
|
||||
| Foo
|
||||
| Bar
|
||||
|
||||
[<ArgParser true>]
|
||||
type FlagsIntoPositionalArgs =
|
||||
{
|
||||
A : string
|
||||
[<PositionalArgs true>]
|
||||
GrabEverything : string list
|
||||
}
|
||||
|
||||
[<ArgParser true>]
|
||||
type FlagsIntoPositionalArgsChoice =
|
||||
{
|
||||
A : string
|
||||
[<PositionalArgs true>]
|
||||
GrabEverything : Choice<string, string> list
|
||||
}
|
||||
|
||||
[<ArgParser true>]
|
||||
type FlagsIntoPositionalArgsInt =
|
||||
{
|
||||
A : string
|
||||
[<PositionalArgs true>]
|
||||
GrabEverything : int list
|
||||
}
|
||||
|
||||
[<ArgParser true>]
|
||||
type FlagsIntoPositionalArgsIntChoice =
|
||||
{
|
||||
A : string
|
||||
[<PositionalArgs true>]
|
||||
GrabEverything : Choice<int, int> list
|
||||
}
|
||||
|
||||
[<ArgParser true>]
|
||||
type FlagsIntoPositionalArgs' =
|
||||
{
|
||||
A : string
|
||||
[<PositionalArgs false>]
|
||||
DontGrabEverything : string list
|
||||
}
|
@@ -1,8 +1,10 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<IsPackable>false</IsPackable>
|
||||
<OtherFlags>--reflectionfree $(OtherFlags)</OtherFlags>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<MyriadSdkGenerator Include="$(MSBuildThisFileDirectory)..\WoofWare.Myriad.Plugins\bin\$(Configuration)\net6.0\WoofWare.Myriad.Plugins.dll"/>
|
||||
@@ -31,6 +33,20 @@
|
||||
<Compile Include="GeneratedMock.fs">
|
||||
<MyriadFile>MockExample.fs</MyriadFile>
|
||||
</Compile>
|
||||
<Compile Include="MockExampleNoAttributes.fs" />
|
||||
<Compile Include="GeneratedMockNoAttributes.fs">
|
||||
<MyriadFile>MockExampleNoAttributes.fs</MyriadFile>
|
||||
<MyriadParams>
|
||||
<IPublicTypeNoAttr>GenerateMock</IPublicTypeNoAttr>
|
||||
<IPublicTypeInternalFalseNoAttr>GenerateMock(false)</IPublicTypeInternalFalseNoAttr>
|
||||
<InternalTypeNoAttr>GenerateMock</InternalTypeNoAttr>
|
||||
<PrivateTypeNoAttr>GenerateMock</PrivateTypeNoAttr>
|
||||
<PrivateTypeInternalFalseNoAttr>GenerateMock(false)</PrivateTypeInternalFalseNoAttr>
|
||||
<VeryPublicTypeNoAttr>GenerateMock</VeryPublicTypeNoAttr>
|
||||
<CurriedNoAttr>GenerateMock</CurriedNoAttr>
|
||||
<TypeWithInterfaceNoAttr>GenerateMock</TypeWithInterfaceNoAttr>
|
||||
</MyriadParams>
|
||||
</Compile>
|
||||
<Compile Include="Vault.fs" />
|
||||
<Compile Include="GeneratedVault.fs">
|
||||
<MyriadFile>Vault.fs</MyriadFile>
|
||||
@@ -51,14 +67,28 @@
|
||||
<Compile Include="ListCata.fs">
|
||||
<MyriadFile>List.fs</MyriadFile>
|
||||
</Compile>
|
||||
<Compile Include="Args.fs" />
|
||||
<Compile Include="GeneratedArgs.fs">
|
||||
<MyriadFile>Args.fs</MyriadFile>
|
||||
</Compile>
|
||||
<None Include="swagger-gitea.json" />
|
||||
<Compile Include="GeneratedSwaggerGitea.fs">
|
||||
<MyriadFile>swagger-gitea.json</MyriadFile>
|
||||
<MyriadParams>
|
||||
<GenerateMockInternal>true</GenerateMockInternal>
|
||||
<ClassName>Gitea</ClassName>
|
||||
</MyriadParams>
|
||||
</Compile>
|
||||
<Compile Include="Generated2SwaggerGitea.fs">
|
||||
<MyriadFile>GeneratedSwaggerGitea.fs</MyriadFile>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="RestEase" Version="1.6.4"/>
|
||||
<ProjectReference Include="..\WoofWare.Myriad.Plugins.Attributes\WoofWare.Myriad.Plugins.Attributes.fsproj" />
|
||||
<ProjectReference Include="..\WoofWare.Myriad.Plugins\WoofWare.Myriad.Plugins.fsproj"/>
|
||||
<PackageReference Include="Myriad.Sdk" Version="0.8.3"/>
|
||||
<PackageReference Include="Myriad.Core" Version="0.8.3"/>
|
||||
<ProjectReference Include="..\WoofWare.Myriad.Plugins\WoofWare.Myriad.Plugins.fsproj" PrivateAssets="all" />
|
||||
<PackageReference Include="Myriad.Sdk" Version="0.8.3" PrivateAssets="all" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
@@ -30,6 +30,12 @@ type ChocolateType =
|
||||
| Milk
|
||||
| SeventyPercent
|
||||
|
||||
override this.ToString () =
|
||||
match this with
|
||||
| ChocolateType.Dark -> "Dark"
|
||||
| ChocolateType.Milk -> "Milk"
|
||||
| ChocolateType.SeventyPercent -> "SeventyPercent"
|
||||
|
||||
type Chocolate =
|
||||
{
|
||||
chocType : ChocolateType
|
||||
@@ -43,6 +49,12 @@ type WrappingPaperStyle =
|
||||
| HappyHolidays
|
||||
| SolidColor
|
||||
|
||||
override this.ToString () =
|
||||
match this with
|
||||
| WrappingPaperStyle.HappyBirthday -> "HappyBirthday"
|
||||
| WrappingPaperStyle.HappyHolidays -> "HappyHolidays"
|
||||
| WrappingPaperStyle.SolidColor -> "SolidColor"
|
||||
|
||||
[<CreateCatamorphism "GiftCata">]
|
||||
type Gift =
|
||||
| Book of Book
|
||||
|
60051
ConsumePlugin/Generated2SwaggerGitea.fs
Normal file
60051
ConsumePlugin/Generated2SwaggerGitea.fs
Normal file
File diff suppressed because it is too large
Load Diff
4347
ConsumePlugin/GeneratedArgs.fs
Normal file
4347
ConsumePlugin/GeneratedArgs.fs
Normal file
File diff suppressed because it is too large
Load Diff
@@ -92,13 +92,13 @@ module TreeCata =
|
||||
let arg0_0 = treeStack.[treeStack.Count - 1]
|
||||
treeStack.RemoveAt (treeStack.Count - 1)
|
||||
cata.TreeBuilder.Parent arg0_0 |> treeBuilderStack.Add
|
||||
| Instruction.Tree_Pair (arg2_0) ->
|
||||
| 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) ->
|
||||
| Instruction.Tree_Sequential arg0_0 ->
|
||||
let arg0_0_len = arg0_0
|
||||
|
||||
let arg0_0 =
|
||||
|
@@ -31,7 +31,7 @@ module FileSystemItemCata =
|
||||
[<RequireQualifiedAccess>]
|
||||
type private Instruction =
|
||||
| Process__FileSystemItem of FileSystemItem
|
||||
| FileSystemItem_Directory of string * int * int
|
||||
| FileSystemItem_Directory of name : string * dirSize : int * contents : int
|
||||
|
||||
let private loop (cata : FileSystemCata<'FileSystemItem>) (instructions : ResizeArray<Instruction>) =
|
||||
let fileSystemItemStack = ResizeArray<'FileSystemItem> ()
|
||||
@@ -106,7 +106,7 @@ module GiftCata =
|
||||
| Process__Gift of Gift
|
||||
| Gift_Wrapped of WrappingPaperStyle
|
||||
| Gift_Boxed
|
||||
| Gift_WithACard of string
|
||||
| Gift_WithACard of message : string
|
||||
|
||||
let private loop (cata : GiftCata<'Gift>) (instructions : ResizeArray<Instruction>) =
|
||||
let giftStack = ResizeArray<'Gift> ()
|
||||
@@ -129,7 +129,7 @@ module GiftCata =
|
||||
| Gift.WithACard (arg0_0, message) ->
|
||||
instructions.Add (Instruction.Gift_WithACard (message))
|
||||
instructions.Add (Instruction.Process__Gift arg0_0)
|
||||
| Instruction.Gift_Wrapped (arg1_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
|
||||
@@ -137,7 +137,7 @@ module GiftCata =
|
||||
let arg0_0 = giftStack.[giftStack.Count - 1]
|
||||
giftStack.RemoveAt (giftStack.Count - 1)
|
||||
cata.Gift.Boxed arg0_0 |> giftStack.Add
|
||||
| Instruction.Gift_WithACard (message) ->
|
||||
| Instruction.Gift_WithACard message ->
|
||||
let arg0_0 = giftStack.[giftStack.Count - 1]
|
||||
giftStack.RemoveAt (giftStack.Count - 1)
|
||||
cata.Gift.WithACard arg0_0 message |> giftStack.Add
|
||||
|
@@ -4,123 +4,241 @@
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
|
||||
namespace ConsumePlugin
|
||||
|
||||
open System.Text.Json.Serialization
|
||||
|
||||
/// Module containing JSON serializing methods for the InternalTypeNotExtensionSerial type
|
||||
[<RequireQualifiedAccess ; CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix)>]
|
||||
module internal InternalTypeNotExtensionSerial =
|
||||
/// Serialize to a JSON node
|
||||
let toJsonNode (input : InternalTypeNotExtensionSerial) : System.Text.Json.Nodes.JsonNode =
|
||||
let node = System.Text.Json.Nodes.JsonObject ()
|
||||
|
||||
do
|
||||
node.Add (
|
||||
(Literals.something),
|
||||
(input.InternalThing2
|
||||
|> (fun field ->
|
||||
let field = System.Text.Json.Nodes.JsonValue.Create<string> field
|
||||
|
||||
(match field with
|
||||
| null ->
|
||||
raise (
|
||||
System.ArgumentNullException
|
||||
"Expected type string to be non-null, but received a null value when serialising"
|
||||
)
|
||||
| field -> field)
|
||||
))
|
||||
)
|
||||
|
||||
node :> _
|
||||
namespace ConsumePlugin
|
||||
|
||||
open System.Text.Json.Serialization
|
||||
|
||||
/// Module containing JSON serializing extension members for the InternalTypeExtension type
|
||||
[<AutoOpen>]
|
||||
module internal InternalTypeExtensionJsonSerializeExtension =
|
||||
/// Extension methods for JSON parsing
|
||||
type InternalTypeExtension with
|
||||
|
||||
/// Serialize to a JSON node
|
||||
static member toJsonNode (input : InternalTypeExtension) : System.Text.Json.Nodes.JsonNode =
|
||||
let node = System.Text.Json.Nodes.JsonObject ()
|
||||
|
||||
do
|
||||
node.Add (
|
||||
(Literals.something),
|
||||
(input.ExternalThing
|
||||
|> (fun field ->
|
||||
let field = System.Text.Json.Nodes.JsonValue.Create<string> field
|
||||
|
||||
(match field with
|
||||
| null ->
|
||||
raise (
|
||||
System.ArgumentNullException
|
||||
"Expected type string to be non-null, but received a null value when serialising"
|
||||
)
|
||||
| field -> field)
|
||||
))
|
||||
)
|
||||
|
||||
node :> _
|
||||
|
||||
namespace ConsumePlugin
|
||||
|
||||
/// Module containing JSON parsing methods for the InnerType type
|
||||
[<RequireQualifiedAccess>]
|
||||
[<CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix)>]
|
||||
[<RequireQualifiedAccess ; CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix)>]
|
||||
module InnerType =
|
||||
/// Parse from a JSON node.
|
||||
let jsonParse (node : System.Text.Json.Nodes.JsonNode) : InnerType =
|
||||
let Thing =
|
||||
(match node.[(Literals.something)] with
|
||||
| null ->
|
||||
raise (
|
||||
System.Collections.Generic.KeyNotFoundException (
|
||||
sprintf "Required key '%s' not found on JSON object" ((Literals.something))
|
||||
)
|
||||
)
|
||||
| v -> v)
|
||||
.AsValue()
|
||||
.GetValue<string> ()
|
||||
let arg_0 =
|
||||
match node.[(Literals.something)] |> Option.ofObj with
|
||||
| None ->
|
||||
raise (
|
||||
System.Collections.Generic.KeyNotFoundException (
|
||||
sprintf "Required key '%s' not found on JSON object" ((Literals.something))
|
||||
)
|
||||
)
|
||||
| Some node -> node.AsValue().GetValue<System.String> ()
|
||||
|
||||
{
|
||||
Thing = Thing
|
||||
Thing = arg_0
|
||||
}
|
||||
namespace ConsumePlugin
|
||||
|
||||
/// Module containing JSON parsing methods for the JsonRecordType type
|
||||
[<RequireQualifiedAccess>]
|
||||
[<CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix)>]
|
||||
[<RequireQualifiedAccess ; CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix)>]
|
||||
module JsonRecordType =
|
||||
/// Parse from a JSON node.
|
||||
let jsonParse (node : System.Text.Json.Nodes.JsonNode) : JsonRecordType =
|
||||
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 =
|
||||
InnerType.jsonParse (
|
||||
match node.["d"] with
|
||||
| null ->
|
||||
raise (
|
||||
System.Collections.Generic.KeyNotFoundException (
|
||||
sprintf "Required key '%s' not found on JSON object" ("d")
|
||||
)
|
||||
let arg_5 =
|
||||
match node.["f"] |> Option.ofObj with
|
||||
| None ->
|
||||
raise (
|
||||
System.Collections.Generic.KeyNotFoundException (
|
||||
sprintf "Required key '%s' not found on JSON object" ("f")
|
||||
)
|
||||
| v -> v
|
||||
)
|
||||
)
|
||||
| Some node ->
|
||||
node.AsArray ()
|
||||
|> Seq.map (fun elt ->
|
||||
(match elt with
|
||||
| null ->
|
||||
raise (
|
||||
System.ArgumentNullException
|
||||
"Expected element of array (element type int32) to be non-null, but found a null element"
|
||||
)
|
||||
| elt -> elt.AsValue().GetValue<System.Int32> ())
|
||||
)
|
||||
|> Array.ofSeq
|
||||
|
||||
let C =
|
||||
(match node.["hi"] with
|
||||
| null ->
|
||||
raise (
|
||||
System.Collections.Generic.KeyNotFoundException (
|
||||
sprintf "Required key '%s' not found on JSON object" ("hi")
|
||||
)
|
||||
)
|
||||
| v -> v)
|
||||
.AsArray ()
|
||||
|> Seq.map (fun elt -> elt.AsValue().GetValue<int> ())
|
||||
|> List.ofSeq
|
||||
let arg_4 =
|
||||
match node.["e"] |> Option.ofObj with
|
||||
| None ->
|
||||
raise (
|
||||
System.Collections.Generic.KeyNotFoundException (
|
||||
sprintf "Required key '%s' not found on JSON object" ("e")
|
||||
)
|
||||
)
|
||||
| Some node ->
|
||||
node.AsArray ()
|
||||
|> Seq.map (fun elt ->
|
||||
(match elt with
|
||||
| null ->
|
||||
raise (
|
||||
System.ArgumentNullException
|
||||
"Expected element of array (element type string) to be non-null, but found a null element"
|
||||
)
|
||||
| elt -> elt.AsValue().GetValue<System.String> ())
|
||||
)
|
||||
|> Array.ofSeq
|
||||
|
||||
let B =
|
||||
(match node.["another-thing"] with
|
||||
| null ->
|
||||
raise (
|
||||
System.Collections.Generic.KeyNotFoundException (
|
||||
sprintf "Required key '%s' not found on JSON object" ("another-thing")
|
||||
)
|
||||
)
|
||||
| v -> v)
|
||||
.AsValue()
|
||||
.GetValue<string> ()
|
||||
let arg_3 =
|
||||
match node.["d"] |> Option.ofObj with
|
||||
| None ->
|
||||
raise (
|
||||
System.Collections.Generic.KeyNotFoundException (
|
||||
sprintf "Required key '%s' not found on JSON object" ("d")
|
||||
)
|
||||
)
|
||||
| Some node -> InnerType.jsonParse node
|
||||
|
||||
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> ()
|
||||
let arg_2 =
|
||||
match node.["hi"] |> Option.ofObj with
|
||||
| None ->
|
||||
raise (
|
||||
System.Collections.Generic.KeyNotFoundException (
|
||||
sprintf "Required key '%s' not found on JSON object" ("hi")
|
||||
)
|
||||
)
|
||||
| Some node ->
|
||||
node.AsArray ()
|
||||
|> Seq.map (fun elt ->
|
||||
(match elt with
|
||||
| null ->
|
||||
raise (
|
||||
System.ArgumentNullException
|
||||
"Expected element of array (element type int32) to be non-null, but found a null element"
|
||||
)
|
||||
| elt -> elt.AsValue().GetValue<System.Int32> ())
|
||||
)
|
||||
|> List.ofSeq
|
||||
|
||||
let arg_1 =
|
||||
match node.["another-thing"] |> Option.ofObj with
|
||||
| None ->
|
||||
raise (
|
||||
System.Collections.Generic.KeyNotFoundException (
|
||||
sprintf "Required key '%s' not found on JSON object" ("another-thing")
|
||||
)
|
||||
)
|
||||
| Some node -> node.AsValue().GetValue<System.String> ()
|
||||
|
||||
let arg_0 =
|
||||
match node.["a"] |> Option.ofObj with
|
||||
| None ->
|
||||
raise (
|
||||
System.Collections.Generic.KeyNotFoundException (
|
||||
sprintf "Required key '%s' not found on JSON object" ("a")
|
||||
)
|
||||
)
|
||||
| Some node -> node.AsValue().GetValue<System.Int32> ()
|
||||
|
||||
{
|
||||
A = A
|
||||
B = B
|
||||
C = C
|
||||
D = D
|
||||
E = E
|
||||
F = F
|
||||
A = arg_0
|
||||
B = arg_1
|
||||
C = arg_2
|
||||
D = arg_3
|
||||
E = arg_4
|
||||
F = arg_5
|
||||
}
|
||||
namespace ConsumePlugin
|
||||
|
||||
/// Module containing JSON parsing methods for the InternalTypeNotExtension type
|
||||
[<RequireQualifiedAccess ; CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix)>]
|
||||
module internal InternalTypeNotExtension =
|
||||
/// Parse from a JSON node.
|
||||
let jsonParse (node : System.Text.Json.Nodes.JsonNode) : InternalTypeNotExtension =
|
||||
let arg_0 =
|
||||
match node.[(Literals.something)] |> Option.ofObj with
|
||||
| None ->
|
||||
raise (
|
||||
System.Collections.Generic.KeyNotFoundException (
|
||||
sprintf "Required key '%s' not found on JSON object" ((Literals.something))
|
||||
)
|
||||
)
|
||||
| Some node -> node.AsValue().GetValue<System.String> ()
|
||||
|
||||
{
|
||||
InternalThing = arg_0
|
||||
}
|
||||
namespace ConsumePlugin
|
||||
|
||||
/// Module containing JSON parsing extension members for the InternalTypeExtension type
|
||||
[<AutoOpen>]
|
||||
module internal InternalTypeExtensionJsonParseExtension =
|
||||
/// Extension methods for JSON parsing
|
||||
type InternalTypeExtension with
|
||||
|
||||
/// Parse from a JSON node.
|
||||
static member jsonParse (node : System.Text.Json.Nodes.JsonNode) : InternalTypeExtension =
|
||||
let arg_0 =
|
||||
match node.[(Literals.something)] |> Option.ofObj with
|
||||
| None ->
|
||||
raise (
|
||||
System.Collections.Generic.KeyNotFoundException (
|
||||
sprintf "Required key '%s' not found on JSON object" ((Literals.something))
|
||||
)
|
||||
)
|
||||
| Some node -> node.AsValue().GetValue<System.String> ()
|
||||
|
||||
{
|
||||
ExternalThing = arg_0
|
||||
}
|
||||
namespace ConsumePlugin
|
||||
|
||||
/// Module containing JSON parsing extension members for the ToGetExtensionMethod type
|
||||
[<AutoOpen>]
|
||||
module ToGetExtensionMethodJsonParseExtension =
|
||||
@@ -129,58 +247,236 @@ module ToGetExtensionMethodJsonParseExtension =
|
||||
|
||||
/// Parse from a JSON node.
|
||||
static member jsonParse (node : System.Text.Json.Nodes.JsonNode) : ToGetExtensionMethod =
|
||||
let Sailor =
|
||||
(match node.["sailor"] with
|
||||
| null ->
|
||||
raise (
|
||||
System.Collections.Generic.KeyNotFoundException (
|
||||
sprintf "Required key '%s' not found on JSON object" ("sailor")
|
||||
)
|
||||
)
|
||||
| v -> v)
|
||||
.AsValue()
|
||||
.GetValue<float> ()
|
||||
let arg_20 =
|
||||
match node.["whiskey"] |> Option.ofObj with
|
||||
| None ->
|
||||
raise (
|
||||
System.Collections.Generic.KeyNotFoundException (
|
||||
sprintf "Required key '%s' not found on JSON object" ("whiskey")
|
||||
)
|
||||
)
|
||||
| Some node -> System.Numerics.BigInteger.Parse (node.ToJsonString ())
|
||||
|
||||
let Soldier =
|
||||
(match node.["soldier"] with
|
||||
| null ->
|
||||
raise (
|
||||
System.Collections.Generic.KeyNotFoundException (
|
||||
sprintf "Required key '%s' not found on JSON object" ("soldier")
|
||||
)
|
||||
)
|
||||
| v -> v)
|
||||
.AsValue()
|
||||
.GetValue<string> ()
|
||||
|> System.Uri
|
||||
let arg_19 =
|
||||
match node.["victor"] |> Option.ofObj with
|
||||
| None ->
|
||||
raise (
|
||||
System.Collections.Generic.KeyNotFoundException (
|
||||
sprintf "Required key '%s' not found on JSON object" ("victor")
|
||||
)
|
||||
)
|
||||
| Some node -> node.AsValue().GetValue<System.Char> ()
|
||||
|
||||
let Tailor =
|
||||
(match node.["tailor"] with
|
||||
| null ->
|
||||
raise (
|
||||
System.Collections.Generic.KeyNotFoundException (
|
||||
sprintf "Required key '%s' not found on JSON object" ("tailor")
|
||||
)
|
||||
)
|
||||
| v -> v)
|
||||
.AsValue()
|
||||
.GetValue<int> ()
|
||||
let arg_18 =
|
||||
match node.["uniform"] |> Option.ofObj with
|
||||
| None ->
|
||||
raise (
|
||||
System.Collections.Generic.KeyNotFoundException (
|
||||
sprintf "Required key '%s' not found on JSON object" ("uniform")
|
||||
)
|
||||
)
|
||||
| Some node -> node.AsValue().GetValue<System.Decimal> ()
|
||||
|
||||
let Tinker =
|
||||
(match node.["tinker"] with
|
||||
| null ->
|
||||
raise (
|
||||
System.Collections.Generic.KeyNotFoundException (
|
||||
sprintf "Required key '%s' not found on JSON object" ("tinker")
|
||||
)
|
||||
)
|
||||
| v -> v)
|
||||
.AsValue()
|
||||
.GetValue<string> ()
|
||||
let arg_17 =
|
||||
match node.["tango"] |> Option.ofObj with
|
||||
| None ->
|
||||
raise (
|
||||
System.Collections.Generic.KeyNotFoundException (
|
||||
sprintf "Required key '%s' not found on JSON object" ("tango")
|
||||
)
|
||||
)
|
||||
| Some node -> node.AsValue().GetValue<System.SByte> ()
|
||||
|
||||
let arg_16 =
|
||||
match node.["quebec"] |> Option.ofObj with
|
||||
| None ->
|
||||
raise (
|
||||
System.Collections.Generic.KeyNotFoundException (
|
||||
sprintf "Required key '%s' not found on JSON object" ("quebec")
|
||||
)
|
||||
)
|
||||
| Some node -> node.AsValue().GetValue<System.Byte> ()
|
||||
|
||||
let arg_15 =
|
||||
match node.["papa"] |> Option.ofObj with
|
||||
| None ->
|
||||
raise (
|
||||
System.Collections.Generic.KeyNotFoundException (
|
||||
sprintf "Required key '%s' not found on JSON object" ("papa")
|
||||
)
|
||||
)
|
||||
| Some node -> node.AsValue().GetValue<System.Byte> ()
|
||||
|
||||
let arg_14 =
|
||||
match node.["oscar"] |> Option.ofObj with
|
||||
| None ->
|
||||
raise (
|
||||
System.Collections.Generic.KeyNotFoundException (
|
||||
sprintf "Required key '%s' not found on JSON object" ("oscar")
|
||||
)
|
||||
)
|
||||
| Some node -> node.AsValue().GetValue<System.SByte> ()
|
||||
|
||||
let arg_13 =
|
||||
match node.["november"] |> Option.ofObj with
|
||||
| None ->
|
||||
raise (
|
||||
System.Collections.Generic.KeyNotFoundException (
|
||||
sprintf "Required key '%s' not found on JSON object" ("november")
|
||||
)
|
||||
)
|
||||
| Some node -> node.AsValue().GetValue<System.UInt16> ()
|
||||
|
||||
let arg_12 =
|
||||
match node.["mike"] |> Option.ofObj with
|
||||
| None ->
|
||||
raise (
|
||||
System.Collections.Generic.KeyNotFoundException (
|
||||
sprintf "Required key '%s' not found on JSON object" ("mike")
|
||||
)
|
||||
)
|
||||
| Some node -> node.AsValue().GetValue<System.Int16> ()
|
||||
|
||||
let arg_11 =
|
||||
match node.["lima"] |> Option.ofObj with
|
||||
| None ->
|
||||
raise (
|
||||
System.Collections.Generic.KeyNotFoundException (
|
||||
sprintf "Required key '%s' not found on JSON object" ("lima")
|
||||
)
|
||||
)
|
||||
| Some node -> node.AsValue().GetValue<System.UInt32> ()
|
||||
|
||||
let arg_10 =
|
||||
match node.["kilo"] |> Option.ofObj with
|
||||
| None ->
|
||||
raise (
|
||||
System.Collections.Generic.KeyNotFoundException (
|
||||
sprintf "Required key '%s' not found on JSON object" ("kilo")
|
||||
)
|
||||
)
|
||||
| Some node -> node.AsValue().GetValue<System.Int32> ()
|
||||
|
||||
let arg_9 =
|
||||
match node.["juliette"] |> Option.ofObj with
|
||||
| None ->
|
||||
raise (
|
||||
System.Collections.Generic.KeyNotFoundException (
|
||||
sprintf "Required key '%s' not found on JSON object" ("juliette")
|
||||
)
|
||||
)
|
||||
| Some node -> node.AsValue().GetValue<System.UInt32> ()
|
||||
|
||||
let arg_8 =
|
||||
match node.["india"] |> Option.ofObj with
|
||||
| None ->
|
||||
raise (
|
||||
System.Collections.Generic.KeyNotFoundException (
|
||||
sprintf "Required key '%s' not found on JSON object" ("india")
|
||||
)
|
||||
)
|
||||
| Some node -> node.AsValue().GetValue<System.Int32> ()
|
||||
|
||||
let arg_7 =
|
||||
match node.["hotel"] |> Option.ofObj with
|
||||
| None ->
|
||||
raise (
|
||||
System.Collections.Generic.KeyNotFoundException (
|
||||
sprintf "Required key '%s' not found on JSON object" ("hotel")
|
||||
)
|
||||
)
|
||||
| Some node -> node.AsValue().GetValue<System.UInt64> ()
|
||||
|
||||
let arg_6 =
|
||||
match node.["golf"] |> Option.ofObj with
|
||||
| None ->
|
||||
raise (
|
||||
System.Collections.Generic.KeyNotFoundException (
|
||||
sprintf "Required key '%s' not found on JSON object" ("golf")
|
||||
)
|
||||
)
|
||||
| Some node -> node.AsValue().GetValue<System.Int64> ()
|
||||
|
||||
let arg_5 =
|
||||
match node.["foxtrot"] |> Option.ofObj with
|
||||
| None ->
|
||||
raise (
|
||||
System.Collections.Generic.KeyNotFoundException (
|
||||
sprintf "Required key '%s' not found on JSON object" ("foxtrot")
|
||||
)
|
||||
)
|
||||
| Some node -> node.AsValue().GetValue<System.Double> ()
|
||||
|
||||
let arg_4 =
|
||||
match node.["echo"] |> Option.ofObj with
|
||||
| None ->
|
||||
raise (
|
||||
System.Collections.Generic.KeyNotFoundException (
|
||||
sprintf "Required key '%s' not found on JSON object" ("echo")
|
||||
)
|
||||
)
|
||||
| Some node -> node.AsValue().GetValue<System.Single> ()
|
||||
|
||||
let arg_3 =
|
||||
match node.["delta"] |> Option.ofObj with
|
||||
| None ->
|
||||
raise (
|
||||
System.Collections.Generic.KeyNotFoundException (
|
||||
sprintf "Required key '%s' not found on JSON object" ("delta")
|
||||
)
|
||||
)
|
||||
| Some node -> node.AsValue().GetValue<System.Single> ()
|
||||
|
||||
let arg_2 =
|
||||
match node.["charlie"] |> Option.ofObj with
|
||||
| None ->
|
||||
raise (
|
||||
System.Collections.Generic.KeyNotFoundException (
|
||||
sprintf "Required key '%s' not found on JSON object" ("charlie")
|
||||
)
|
||||
)
|
||||
| Some node -> node.AsValue().GetValue<System.Double> ()
|
||||
|
||||
let arg_1 =
|
||||
match node.["bravo"] |> Option.ofObj with
|
||||
| None ->
|
||||
raise (
|
||||
System.Collections.Generic.KeyNotFoundException (
|
||||
sprintf "Required key '%s' not found on JSON object" ("bravo")
|
||||
)
|
||||
)
|
||||
| Some node -> node.AsValue().GetValue<string> () |> System.Uri
|
||||
|
||||
let arg_0 =
|
||||
match node.["alpha"] |> Option.ofObj with
|
||||
| None ->
|
||||
raise (
|
||||
System.Collections.Generic.KeyNotFoundException (
|
||||
sprintf "Required key '%s' not found on JSON object" ("alpha")
|
||||
)
|
||||
)
|
||||
| Some node -> node.AsValue().GetValue<System.String> ()
|
||||
|
||||
{
|
||||
Tinker = Tinker
|
||||
Tailor = Tailor
|
||||
Soldier = Soldier
|
||||
Sailor = Sailor
|
||||
Alpha = arg_0
|
||||
Bravo = arg_1
|
||||
Charlie = arg_2
|
||||
Delta = arg_3
|
||||
Echo = arg_4
|
||||
Foxtrot = arg_5
|
||||
Golf = arg_6
|
||||
Hotel = arg_7
|
||||
India = arg_8
|
||||
Juliette = arg_9
|
||||
Kilo = arg_10
|
||||
Lima = arg_11
|
||||
Mike = arg_12
|
||||
November = arg_13
|
||||
Oscar = arg_14
|
||||
Papa = arg_15
|
||||
Quebec = arg_16
|
||||
Tango = arg_17
|
||||
Uniform = arg_18
|
||||
Victor = arg_19
|
||||
Whiskey = arg_20
|
||||
}
|
||||
|
@@ -5,6 +5,7 @@
|
||||
|
||||
namespace SomeNamespace
|
||||
|
||||
open System
|
||||
open WoofWare.Myriad.Plugins
|
||||
|
||||
/// Mock record type for an interface
|
||||
@@ -18,17 +19,18 @@ type internal PublicTypeMock =
|
||||
/// An implementation where every method throws.
|
||||
static member Empty : PublicTypeMock =
|
||||
{
|
||||
Mem1 = (fun x -> raise (System.NotImplementedException "Unimplemented mock function"))
|
||||
Mem2 = (fun x -> raise (System.NotImplementedException "Unimplemented mock function"))
|
||||
Mem3 = (fun x -> raise (System.NotImplementedException "Unimplemented mock function"))
|
||||
Mem1 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem1"))
|
||||
Mem2 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem2"))
|
||||
Mem3 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem3"))
|
||||
}
|
||||
|
||||
interface IPublicType with
|
||||
member this.Mem1 (arg_0_0, arg_0_1) = this.Mem1 (arg_0_0, arg_0_1)
|
||||
member this.Mem2 (arg_0_0) = this.Mem2 (arg_0_0)
|
||||
member this.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 System
|
||||
open WoofWare.Myriad.Plugins
|
||||
|
||||
/// Mock record type for an interface
|
||||
@@ -42,17 +44,18 @@ type public PublicTypeInternalFalseMock =
|
||||
/// 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"))
|
||||
Mem1 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem1"))
|
||||
Mem2 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem2"))
|
||||
Mem3 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem3"))
|
||||
}
|
||||
|
||||
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.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 System
|
||||
open WoofWare.Myriad.Plugins
|
||||
|
||||
/// Mock record type for an interface
|
||||
@@ -65,15 +68,16 @@ type internal InternalTypeMock =
|
||||
/// An implementation where every method throws.
|
||||
static member Empty : InternalTypeMock =
|
||||
{
|
||||
Mem1 = (fun x -> raise (System.NotImplementedException "Unimplemented mock function"))
|
||||
Mem2 = (fun x -> raise (System.NotImplementedException "Unimplemented mock function"))
|
||||
Mem1 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem1"))
|
||||
Mem2 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem2"))
|
||||
}
|
||||
|
||||
interface InternalType with
|
||||
member this.Mem1 (arg_0_0, arg_0_1) = this.Mem1 (arg_0_0, arg_0_1)
|
||||
member this.Mem2 (arg_0_0) = this.Mem2 (arg_0_0)
|
||||
member this.Mem2 arg_0_0 = this.Mem2 (arg_0_0)
|
||||
namespace SomeNamespace
|
||||
|
||||
open System
|
||||
open WoofWare.Myriad.Plugins
|
||||
|
||||
/// Mock record type for an interface
|
||||
@@ -86,15 +90,16 @@ type private PrivateTypeMock =
|
||||
/// An implementation where every method throws.
|
||||
static member Empty : PrivateTypeMock =
|
||||
{
|
||||
Mem1 = (fun x -> raise (System.NotImplementedException "Unimplemented mock function"))
|
||||
Mem2 = (fun x -> raise (System.NotImplementedException "Unimplemented mock function"))
|
||||
Mem1 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem1"))
|
||||
Mem2 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem2"))
|
||||
}
|
||||
|
||||
interface PrivateType with
|
||||
member this.Mem1 (arg_0_0, arg_0_1) = this.Mem1 (arg_0_0, arg_0_1)
|
||||
member this.Mem2 (arg_0_0) = this.Mem2 (arg_0_0)
|
||||
member this.Mem2 arg_0_0 = this.Mem2 (arg_0_0)
|
||||
namespace SomeNamespace
|
||||
|
||||
open System
|
||||
open WoofWare.Myriad.Plugins
|
||||
|
||||
/// Mock record type for an interface
|
||||
@@ -107,15 +112,16 @@ type private PrivateTypeInternalFalseMock =
|
||||
/// 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"))
|
||||
Mem1 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem1"))
|
||||
Mem2 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem2"))
|
||||
}
|
||||
|
||||
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)
|
||||
member this.Mem2 arg_0_0 = this.Mem2 (arg_0_0)
|
||||
namespace SomeNamespace
|
||||
|
||||
open System
|
||||
open WoofWare.Myriad.Plugins
|
||||
|
||||
/// Mock record type for an interface
|
||||
@@ -127,13 +133,14 @@ type internal VeryPublicTypeMock<'a, 'b> =
|
||||
/// An implementation where every method throws.
|
||||
static member Empty () : VeryPublicTypeMock<'a, 'b> =
|
||||
{
|
||||
Mem1 = (fun x -> raise (System.NotImplementedException "Unimplemented mock function"))
|
||||
Mem1 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem1"))
|
||||
}
|
||||
|
||||
interface VeryPublicType<'a, 'b> with
|
||||
member this.Mem1 (arg_0_0) = this.Mem1 (arg_0_0)
|
||||
member this.Mem1 arg_0_0 = this.Mem1 (arg_0_0)
|
||||
namespace SomeNamespace
|
||||
|
||||
open System
|
||||
open WoofWare.Myriad.Plugins
|
||||
|
||||
/// Mock record type for an interface
|
||||
@@ -150,18 +157,18 @@ type internal CurriedMock<'a> =
|
||||
/// An implementation where every method throws.
|
||||
static member Empty () : CurriedMock<'a> =
|
||||
{
|
||||
Mem1 = (fun x -> raise (System.NotImplementedException "Unimplemented mock function"))
|
||||
Mem2 = (fun x -> raise (System.NotImplementedException "Unimplemented mock function"))
|
||||
Mem3 = (fun x -> raise (System.NotImplementedException "Unimplemented mock function"))
|
||||
Mem4 = (fun x -> raise (System.NotImplementedException "Unimplemented mock function"))
|
||||
Mem5 = (fun x -> raise (System.NotImplementedException "Unimplemented mock function"))
|
||||
Mem6 = (fun x -> raise (System.NotImplementedException "Unimplemented mock function"))
|
||||
Mem1 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem1"))
|
||||
Mem2 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem2"))
|
||||
Mem3 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem3"))
|
||||
Mem4 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem4"))
|
||||
Mem5 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem5"))
|
||||
Mem6 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem6"))
|
||||
}
|
||||
|
||||
interface Curried<'a> with
|
||||
member this.Mem1 (arg_0_0) (arg_1_0) = this.Mem1 (arg_0_0) (arg_1_0)
|
||||
member this.Mem2 (arg_0_0, arg_0_1) (arg_1_0) = this.Mem2 (arg_0_0, arg_0_1) (arg_1_0)
|
||||
member this.Mem3 ((arg_0_0, arg_0_1)) (arg_1_0) = this.Mem3 (arg_0_0, arg_0_1) (arg_1_0)
|
||||
member this.Mem1 arg_0_0 arg_1_0 = this.Mem1 (arg_0_0) (arg_1_0)
|
||||
member this.Mem2 (arg_0_0, arg_0_1) arg_1_0 = this.Mem2 (arg_0_0, arg_0_1) (arg_1_0)
|
||||
member this.Mem3 ((arg_0_0, arg_0_1)) arg_1_0 = this.Mem3 (arg_0_0, arg_0_1) (arg_1_0)
|
||||
|
||||
member this.Mem4 ((arg_0_0, arg_0_1)) ((arg_1_0, arg_1_1)) =
|
||||
this.Mem4 (arg_0_0, arg_0_1) (arg_1_0, arg_1_1)
|
||||
@@ -171,3 +178,62 @@ type internal CurriedMock<'a> =
|
||||
|
||||
member this.Mem6 (arg_0_0, arg_0_1) (arg_1_0, arg_1_1) =
|
||||
this.Mem6 (arg_0_0, arg_0_1) (arg_1_0, arg_1_1)
|
||||
namespace SomeNamespace
|
||||
|
||||
open System
|
||||
open WoofWare.Myriad.Plugins
|
||||
|
||||
/// Mock record type for an interface
|
||||
type internal TypeWithInterfaceMock =
|
||||
{
|
||||
/// Implementation of IDisposable.Dispose
|
||||
Dispose : unit -> unit
|
||||
Mem1 : string option -> string[] Async
|
||||
Mem2 : unit -> string[] Async
|
||||
}
|
||||
|
||||
/// An implementation where every method throws.
|
||||
static member Empty : TypeWithInterfaceMock =
|
||||
{
|
||||
Dispose = (fun () -> ())
|
||||
Mem1 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem1"))
|
||||
Mem2 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem2"))
|
||||
}
|
||||
|
||||
interface TypeWithInterface with
|
||||
member this.Mem1 arg_0_0 = this.Mem1 (arg_0_0)
|
||||
member this.Mem2 () = this.Mem2 (())
|
||||
|
||||
interface System.IDisposable with
|
||||
member this.Dispose () : unit = this.Dispose ()
|
||||
namespace SomeNamespace
|
||||
|
||||
open System
|
||||
open WoofWare.Myriad.Plugins
|
||||
|
||||
/// Mock record type for an interface
|
||||
type internal TypeWithPropertiesMock =
|
||||
{
|
||||
/// Implementation of IDisposable.Dispose
|
||||
Dispose : unit -> unit
|
||||
Prop1 : unit -> int
|
||||
Prop2 : unit -> unit Async
|
||||
Mem1 : string option -> string[] Async
|
||||
}
|
||||
|
||||
/// An implementation where every method throws.
|
||||
static member Empty : TypeWithPropertiesMock =
|
||||
{
|
||||
Dispose = (fun () -> ())
|
||||
Prop1 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Prop1"))
|
||||
Prop2 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Prop2"))
|
||||
Mem1 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem1"))
|
||||
}
|
||||
|
||||
interface TypeWithProperties with
|
||||
member this.Mem1 arg_0_0 = this.Mem1 (arg_0_0)
|
||||
member this.Prop1 = this.Prop1 ()
|
||||
member this.Prop2 = this.Prop2 ()
|
||||
|
||||
interface System.IDisposable with
|
||||
member this.Dispose () : unit = this.Dispose ()
|
||||
|
200
ConsumePlugin/GeneratedMockNoAttributes.fs
Normal file
200
ConsumePlugin/GeneratedMockNoAttributes.fs
Normal file
@@ -0,0 +1,200 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// This code was generated by myriad.
|
||||
// Changes to this file will be lost when the code is regenerated.
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace SomeNamespace
|
||||
|
||||
open System
|
||||
|
||||
/// Mock record type for an interface
|
||||
type internal PublicTypeNoAttrMock =
|
||||
{
|
||||
Mem1 : string * int -> string list
|
||||
Mem2 : string -> int
|
||||
Mem3 : int * option<System.Threading.CancellationToken> -> string
|
||||
}
|
||||
|
||||
/// An implementation where every method throws.
|
||||
static member Empty : PublicTypeNoAttrMock =
|
||||
{
|
||||
Mem1 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem1"))
|
||||
Mem2 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem2"))
|
||||
Mem3 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem3"))
|
||||
}
|
||||
|
||||
interface IPublicTypeNoAttr 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 System
|
||||
|
||||
/// Mock record type for an interface
|
||||
type public PublicTypeInternalFalseNoAttrMock =
|
||||
{
|
||||
Mem1 : string * int -> string list
|
||||
Mem2 : string -> int
|
||||
Mem3 : int * option<System.Threading.CancellationToken> -> string
|
||||
}
|
||||
|
||||
/// An implementation where every method throws.
|
||||
static member Empty : PublicTypeInternalFalseNoAttrMock =
|
||||
{
|
||||
Mem1 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem1"))
|
||||
Mem2 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem2"))
|
||||
Mem3 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem3"))
|
||||
}
|
||||
|
||||
interface IPublicTypeInternalFalseNoAttr 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 System
|
||||
|
||||
/// Mock record type for an interface
|
||||
type internal InternalTypeNoAttrMock =
|
||||
{
|
||||
Mem1 : string * int -> unit
|
||||
Mem2 : string -> int
|
||||
}
|
||||
|
||||
/// An implementation where every method throws.
|
||||
static member Empty : InternalTypeNoAttrMock =
|
||||
{
|
||||
Mem1 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem1"))
|
||||
Mem2 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem2"))
|
||||
}
|
||||
|
||||
interface InternalTypeNoAttr 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 System
|
||||
|
||||
/// Mock record type for an interface
|
||||
type private PrivateTypeNoAttrMock =
|
||||
{
|
||||
Mem1 : string * int -> unit
|
||||
Mem2 : string -> int
|
||||
}
|
||||
|
||||
/// An implementation where every method throws.
|
||||
static member Empty : PrivateTypeNoAttrMock =
|
||||
{
|
||||
Mem1 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem1"))
|
||||
Mem2 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem2"))
|
||||
}
|
||||
|
||||
interface PrivateTypeNoAttr 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 System
|
||||
|
||||
/// Mock record type for an interface
|
||||
type private PrivateTypeInternalFalseNoAttrMock =
|
||||
{
|
||||
Mem1 : string * int -> unit
|
||||
Mem2 : string -> int
|
||||
}
|
||||
|
||||
/// An implementation where every method throws.
|
||||
static member Empty : PrivateTypeInternalFalseNoAttrMock =
|
||||
{
|
||||
Mem1 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem1"))
|
||||
Mem2 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem2"))
|
||||
}
|
||||
|
||||
interface PrivateTypeInternalFalseNoAttr 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 System
|
||||
|
||||
/// Mock record type for an interface
|
||||
type internal VeryPublicTypeNoAttrMock<'a, 'b> =
|
||||
{
|
||||
Mem1 : 'a -> 'b
|
||||
}
|
||||
|
||||
/// An implementation where every method throws.
|
||||
static member Empty () : VeryPublicTypeNoAttrMock<'a, 'b> =
|
||||
{
|
||||
Mem1 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem1"))
|
||||
}
|
||||
|
||||
interface VeryPublicTypeNoAttr<'a, 'b> with
|
||||
member this.Mem1 arg_0_0 = this.Mem1 (arg_0_0)
|
||||
namespace SomeNamespace
|
||||
|
||||
open System
|
||||
|
||||
/// Mock record type for an interface
|
||||
type internal CurriedNoAttrMock<'a> =
|
||||
{
|
||||
Mem1 : int -> 'a -> string
|
||||
Mem2 : int * string -> 'a -> string
|
||||
Mem3 : (int * string) -> 'a -> string
|
||||
Mem4 : (int * string) -> ('a * int) -> string
|
||||
Mem5 : int * string -> ('a * int) -> string
|
||||
Mem6 : int * string -> 'a * int -> string
|
||||
}
|
||||
|
||||
/// An implementation where every method throws.
|
||||
static member Empty () : CurriedNoAttrMock<'a> =
|
||||
{
|
||||
Mem1 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem1"))
|
||||
Mem2 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem2"))
|
||||
Mem3 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem3"))
|
||||
Mem4 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem4"))
|
||||
Mem5 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem5"))
|
||||
Mem6 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem6"))
|
||||
}
|
||||
|
||||
interface CurriedNoAttr<'a> with
|
||||
member this.Mem1 arg_0_0 arg_1_0 = this.Mem1 (arg_0_0) (arg_1_0)
|
||||
member this.Mem2 (arg_0_0, arg_0_1) arg_1_0 = this.Mem2 (arg_0_0, arg_0_1) (arg_1_0)
|
||||
member this.Mem3 ((arg_0_0, arg_0_1)) arg_1_0 = this.Mem3 (arg_0_0, arg_0_1) (arg_1_0)
|
||||
|
||||
member this.Mem4 ((arg_0_0, arg_0_1)) ((arg_1_0, arg_1_1)) =
|
||||
this.Mem4 (arg_0_0, arg_0_1) (arg_1_0, arg_1_1)
|
||||
|
||||
member this.Mem5 (arg_0_0, arg_0_1) ((arg_1_0, arg_1_1)) =
|
||||
this.Mem5 (arg_0_0, arg_0_1) (arg_1_0, arg_1_1)
|
||||
|
||||
member this.Mem6 (arg_0_0, arg_0_1) (arg_1_0, arg_1_1) =
|
||||
this.Mem6 (arg_0_0, arg_0_1) (arg_1_0, arg_1_1)
|
||||
namespace SomeNamespace
|
||||
|
||||
open System
|
||||
|
||||
/// Mock record type for an interface
|
||||
type internal TypeWithInterfaceNoAttrMock =
|
||||
{
|
||||
/// Implementation of IDisposable.Dispose
|
||||
Dispose : unit -> unit
|
||||
Mem1 : string option -> string[] Async
|
||||
Mem2 : unit -> string[] Async
|
||||
}
|
||||
|
||||
/// An implementation where every method throws.
|
||||
static member Empty : TypeWithInterfaceNoAttrMock =
|
||||
{
|
||||
Dispose = (fun () -> ())
|
||||
Mem1 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem1"))
|
||||
Mem2 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem2"))
|
||||
}
|
||||
|
||||
interface TypeWithInterfaceNoAttr with
|
||||
member this.Mem1 arg_0_0 = this.Mem1 (arg_0_0)
|
||||
member this.Mem2 () = this.Mem2 (())
|
||||
|
||||
interface System.IDisposable with
|
||||
member this.Dispose () : unit = this.Dispose ()
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
6432
ConsumePlugin/GeneratedSwaggerGitea.fs
Normal file
6432
ConsumePlugin/GeneratedSwaggerGitea.fs
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -29,13 +29,52 @@ type JsonRecordType =
|
||||
F : int[]
|
||||
}
|
||||
|
||||
[<WoofWare.Myriad.Plugins.JsonParse>]
|
||||
type internal InternalTypeNotExtension =
|
||||
{
|
||||
[<JsonPropertyName(Literals.something)>]
|
||||
InternalThing : string
|
||||
}
|
||||
|
||||
[<WoofWare.Myriad.Plugins.JsonSerialize>]
|
||||
type internal InternalTypeNotExtensionSerial =
|
||||
{
|
||||
[<JsonPropertyName(Literals.something)>]
|
||||
InternalThing2 : string
|
||||
}
|
||||
|
||||
[<WoofWare.Myriad.Plugins.JsonParse true>]
|
||||
[<WoofWare.Myriad.Plugins.JsonSerialize true>]
|
||||
type internal InternalTypeExtension =
|
||||
{
|
||||
[<JsonPropertyName(Literals.something)>]
|
||||
ExternalThing : string
|
||||
}
|
||||
|
||||
[<WoofWare.Myriad.Plugins.JsonParse true>]
|
||||
type ToGetExtensionMethod =
|
||||
{
|
||||
Tinker : string
|
||||
Tailor : int
|
||||
Soldier : System.Uri
|
||||
Sailor : float
|
||||
Alpha : string
|
||||
Bravo : System.Uri
|
||||
Charlie : float
|
||||
Delta : float32
|
||||
Echo : single
|
||||
Foxtrot : double
|
||||
Golf : int64
|
||||
Hotel : uint64
|
||||
India : int
|
||||
Juliette : uint
|
||||
Kilo : int32
|
||||
Lima : uint32
|
||||
Mike : int16
|
||||
November : uint16
|
||||
Oscar : int8
|
||||
Papa : uint8
|
||||
Quebec : byte
|
||||
Tango : sbyte
|
||||
Uniform : decimal
|
||||
Victor : char
|
||||
Whiskey : bigint
|
||||
}
|
||||
|
||||
[<RequireQualifiedAccess>]
|
||||
|
@@ -31,7 +31,7 @@ module MyListCata =
|
||||
[<RequireQualifiedAccess>]
|
||||
type private Instruction<'a> =
|
||||
| Process__MyList of MyList<'a>
|
||||
| MyList_Cons of 'a
|
||||
| MyList_Cons of head : 'a
|
||||
|
||||
let private loop (cata : MyListCata<'a, 'MyList>) (instructions : ResizeArray<Instruction<'a>>) =
|
||||
let myListStack = ResizeArray<'MyList> ()
|
||||
@@ -50,7 +50,7 @@ module MyListCata =
|
||||
}) ->
|
||||
instructions.Add (Instruction.MyList_Cons (head))
|
||||
instructions.Add (Instruction.Process__MyList tail)
|
||||
| Instruction.MyList_Cons (head) ->
|
||||
| Instruction.MyList_Cons head ->
|
||||
let tail = myListStack.[myListStack.Count - 1]
|
||||
myListStack.RemoveAt (myListStack.Count - 1)
|
||||
cata.MyList.Cons head tail |> myListStack.Add
|
||||
@@ -103,7 +103,7 @@ module MyList2Cata =
|
||||
| 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) ->
|
||||
| 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
|
||||
|
@@ -1,5 +1,6 @@
|
||||
namespace SomeNamespace
|
||||
|
||||
open System
|
||||
open WoofWare.Myriad.Plugins
|
||||
|
||||
[<GenerateMock>]
|
||||
@@ -41,3 +42,16 @@ type Curried<'a> =
|
||||
abstract Mem4 : (int * string) -> ('a * int) -> string
|
||||
abstract Mem5 : x : int * string -> ('a * int) -> string
|
||||
abstract Mem6 : int * string -> y : 'a * int -> string
|
||||
|
||||
[<GenerateMock>]
|
||||
type TypeWithInterface =
|
||||
inherit IDisposable
|
||||
abstract Mem1 : string option -> string[] Async
|
||||
abstract Mem2 : unit -> string[] Async
|
||||
|
||||
[<GenerateMock>]
|
||||
type TypeWithProperties =
|
||||
inherit IDisposable
|
||||
abstract Mem1 : string option -> string[] Async
|
||||
abstract Prop1 : int
|
||||
abstract Prop2 : unit Async
|
||||
|
41
ConsumePlugin/MockExampleNoAttributes.fs
Normal file
41
ConsumePlugin/MockExampleNoAttributes.fs
Normal file
@@ -0,0 +1,41 @@
|
||||
namespace SomeNamespace
|
||||
|
||||
open System
|
||||
|
||||
type IPublicTypeNoAttr =
|
||||
abstract Mem1 : string * int -> string list
|
||||
abstract Mem2 : string -> int
|
||||
abstract Mem3 : x : int * ?ct : System.Threading.CancellationToken -> string
|
||||
|
||||
type IPublicTypeInternalFalseNoAttr =
|
||||
abstract Mem1 : string * int -> string list
|
||||
abstract Mem2 : string -> int
|
||||
abstract Mem3 : x : int * ?ct : System.Threading.CancellationToken -> string
|
||||
|
||||
type internal InternalTypeNoAttr =
|
||||
abstract Mem1 : string * int -> unit
|
||||
abstract Mem2 : string -> int
|
||||
|
||||
type private PrivateTypeNoAttr =
|
||||
abstract Mem1 : string * int -> unit
|
||||
abstract Mem2 : string -> int
|
||||
|
||||
type private PrivateTypeInternalFalseNoAttr =
|
||||
abstract Mem1 : string * int -> unit
|
||||
abstract Mem2 : string -> int
|
||||
|
||||
type VeryPublicTypeNoAttr<'a, 'b> =
|
||||
abstract Mem1 : 'a -> 'b
|
||||
|
||||
type CurriedNoAttr<'a> =
|
||||
abstract Mem1 : int -> 'a -> string
|
||||
abstract Mem2 : int * string -> 'a -> string
|
||||
abstract Mem3 : (int * string) -> 'a -> string
|
||||
abstract Mem4 : (int * string) -> ('a * int) -> string
|
||||
abstract Mem5 : x : int * string -> ('a * int) -> string
|
||||
abstract Mem6 : int * string -> y : 'a * int -> string
|
||||
|
||||
type TypeWithInterfaceNoAttr =
|
||||
inherit IDisposable
|
||||
abstract Mem1 : string option -> string[] Async
|
||||
abstract Mem2 : unit -> string[] Async
|
@@ -19,13 +19,16 @@ type GymAccessOptions =
|
||||
QrCodeAccess : bool
|
||||
}
|
||||
|
||||
[<Measure>]
|
||||
type measure
|
||||
|
||||
[<WoofWare.Myriad.Plugins.JsonParse>]
|
||||
type GymLocation =
|
||||
{
|
||||
[<JsonNumberHandling(JsonNumberHandling.AllowReadingFromString)>]
|
||||
Longitude : float
|
||||
[<JsonNumberHandling(JsonNumberHandling.AllowReadingFromString)>]
|
||||
Latitude : float
|
||||
Latitude : float<measure>
|
||||
}
|
||||
|
||||
[<WoofWare.Myriad.Plugins.JsonParse>]
|
||||
|
@@ -1,9 +1,5 @@
|
||||
namespace ConsumePlugin
|
||||
|
||||
type ParseState =
|
||||
| AwaitingKey
|
||||
| AwaitingValue of string
|
||||
|
||||
/// My whatnot
|
||||
[<WoofWare.Myriad.Plugins.RemoveOptions>]
|
||||
type RecordType =
|
||||
|
@@ -17,6 +17,9 @@ type IPureGymApi =
|
||||
[<Get "v1/gyms/{gym_id}/attendance">]
|
||||
abstract GetGymAttendance : [<Path "gym_id">] gymId : int * ?ct : CancellationToken -> Task<GymAttendance>
|
||||
|
||||
[<Get "v1/gyms/{gym_id}/attendance">]
|
||||
abstract GetGymAttendance' : [<Path("gym_id")>] gymId : int * ?ct : CancellationToken -> Task<GymAttendance>
|
||||
|
||||
[<RestEase.GetAttribute "v1/member">]
|
||||
abstract GetMember : ?ct : CancellationToken -> Member Task
|
||||
|
||||
@@ -38,6 +41,10 @@ type IPureGymApi =
|
||||
abstract GetSessions :
|
||||
[<Query>] fromDate : DateOnly * [<Query>] toDate : DateOnly * ?ct : CancellationToken -> Task<Sessions>
|
||||
|
||||
[<Get "/v2/gymSessions/member?foo=1">]
|
||||
abstract GetSessionsWithQuery :
|
||||
[<Query>] fromDate : DateOnly * [<Query>] toDate : DateOnly * ?ct : CancellationToken -> Task<Sessions>
|
||||
|
||||
// An example from RestEase's own docs
|
||||
[<Post "users/new">]
|
||||
abstract CreateUserString : [<Body>] user : string * ?ct : CancellationToken -> Task<string>
|
||||
@@ -115,21 +122,62 @@ type internal IApiWithoutBaseAddress =
|
||||
[<Get "endpoint/{param}">]
|
||||
abstract GetPathParam : [<Path "param">] parameter : string * ?ct : CancellationToken -> Task<string>
|
||||
|
||||
// TODO: implement BasePath support
|
||||
|
||||
[<WoofWare.Myriad.Plugins.HttpClient>]
|
||||
[<BasePath "foo">]
|
||||
type IApiWithBasePath =
|
||||
[<Get "endpoint/{param}">]
|
||||
// Example where we use the bundled attributes rather than RestEase's
|
||||
[<WoofWare.Myriad.Plugins.RestEase.Get "endpoint/{param}">]
|
||||
abstract GetPathParam : [<Path "param">] parameter : string * ?cancellationToken : CancellationToken -> Task<string>
|
||||
|
||||
[<WoofWare.Myriad.Plugins.HttpClient>]
|
||||
[<BaseAddress "https://whatnot.com">]
|
||||
[<BaseAddress "https://whatnot.com/thing">]
|
||||
[<BasePath "foo">]
|
||||
type IApiWithBasePathAndAddress =
|
||||
[<Get "endpoint/{param}">]
|
||||
abstract GetPathParam : [<Path "param">] parameter : string * ?ct : CancellationToken -> Task<string>
|
||||
|
||||
[<WoofWare.Myriad.Plugins.HttpClient>]
|
||||
[<BasePath "/foo">]
|
||||
type IApiWithAbsoluteBasePath =
|
||||
// Example where we use the bundled attributes rather than RestEase's
|
||||
[<WoofWare.Myriad.Plugins.RestEase.Get "endpoint/{param}">]
|
||||
abstract GetPathParam : [<Path "param">] parameter : string * ?cancellationToken : CancellationToken -> Task<string>
|
||||
|
||||
[<WoofWare.Myriad.Plugins.HttpClient>]
|
||||
[<BaseAddress "https://whatnot.com/thing">]
|
||||
[<BasePath "/foo">]
|
||||
type IApiWithAbsoluteBasePathAndAddress =
|
||||
[<Get "endpoint/{param}">]
|
||||
abstract GetPathParam : [<Path "param">] parameter : string * ?ct : CancellationToken -> Task<string>
|
||||
|
||||
[<WoofWare.Myriad.Plugins.HttpClient>]
|
||||
[<BasePath "foo">]
|
||||
type IApiWithBasePathAndAbsoluteEndpoint =
|
||||
// Example where we use the bundled attributes rather than RestEase's
|
||||
[<WoofWare.Myriad.Plugins.RestEase.Get "/endpoint/{param}">]
|
||||
abstract GetPathParam : [<Path "param">] parameter : string * ?cancellationToken : CancellationToken -> Task<string>
|
||||
|
||||
[<WoofWare.Myriad.Plugins.HttpClient>]
|
||||
[<BaseAddress "https://whatnot.com/thing">]
|
||||
[<BasePath "foo">]
|
||||
type IApiWithBasePathAndAddressAndAbsoluteEndpoint =
|
||||
[<Get "/endpoint/{param}">]
|
||||
abstract GetPathParam : [<Path "param">] parameter : string * ?ct : CancellationToken -> Task<string>
|
||||
|
||||
[<WoofWare.Myriad.Plugins.HttpClient>]
|
||||
[<BasePath "/foo">]
|
||||
type IApiWithAbsoluteBasePathAndAbsoluteEndpoint =
|
||||
// Example where we use the bundled attributes rather than RestEase's
|
||||
[<WoofWare.Myriad.Plugins.RestEase.Get "/endpoint/{param}">]
|
||||
abstract GetPathParam : [<Path "param">] parameter : string * ?cancellationToken : CancellationToken -> Task<string>
|
||||
|
||||
[<WoofWare.Myriad.Plugins.HttpClient>]
|
||||
[<BaseAddress "https://whatnot.com/thing">]
|
||||
[<BasePath "/foo">]
|
||||
type IApiWithAbsoluteBasePathAndAddressAndAbsoluteEndpoint =
|
||||
[<Get "/endpoint/{param}">]
|
||||
abstract GetPathParam : [<Path "param">] parameter : string * ?ct : CancellationToken -> Task<string>
|
||||
|
||||
[<WoofWare.Myriad.Plugins.HttpClient>]
|
||||
[<Header("Header-Name", "Header-Value")>]
|
||||
type IApiWithHeaders =
|
||||
@@ -140,4 +188,18 @@ type IApiWithHeaders =
|
||||
abstract SomeOtherHeader : int
|
||||
|
||||
[<Get "endpoint/{param}">]
|
||||
[<Header("Something-Else", "val")>]
|
||||
abstract GetPathParam : [<Path "param">] parameter : string * ?ct : CancellationToken -> Task<string>
|
||||
|
||||
[<WoofWare.Myriad.Plugins.HttpClient>]
|
||||
[<WoofWare.Myriad.Plugins.RestEase.Header("Header-Name", "Header-Value")>]
|
||||
type IApiWithHeaders2 =
|
||||
[<WoofWare.Myriad.Plugins.RestEase.Header "X-Foo">]
|
||||
abstract SomeHeader : string
|
||||
|
||||
[<WoofWare.Myriad.Plugins.RestEase.Header "Authorization">]
|
||||
abstract SomeOtherHeader : int
|
||||
|
||||
[<Get "endpoint/{param}">]
|
||||
abstract GetPathParam :
|
||||
[<WoofWare.Myriad.Plugins.RestEase.Path "param">] parameter : string * ?ct : CancellationToken -> Task<string>
|
||||
|
@@ -16,6 +16,15 @@ type InnerTypeWithBoth =
|
||||
ConcreteDict : Dictionary<string, InnerTypeWithBoth>
|
||||
}
|
||||
|
||||
[<WoofWare.Myriad.Plugins.JsonParse true>]
|
||||
[<WoofWare.Myriad.Plugins.JsonSerialize true>]
|
||||
type SomeEnum =
|
||||
| Blah = 1
|
||||
| Thing = 0
|
||||
|
||||
[<Measure>]
|
||||
type measure
|
||||
|
||||
[<WoofWare.Myriad.Plugins.JsonParse true>]
|
||||
[<WoofWare.Myriad.Plugins.JsonSerialize true>]
|
||||
type JsonRecordTypeWithBoth =
|
||||
@@ -25,5 +34,61 @@ type JsonRecordTypeWithBoth =
|
||||
C : int list
|
||||
D : InnerTypeWithBoth
|
||||
E : string array
|
||||
F : int[]
|
||||
Arr : int[]
|
||||
Byte : byte<measure>
|
||||
Sbyte : sbyte<measure>
|
||||
I : int<measure>
|
||||
I32 : int32<measure>
|
||||
I64 : int64<measure>
|
||||
U : uint<measure>
|
||||
U32 : uint32<measure>
|
||||
U64 : uint64<measure>
|
||||
F : float<measure>
|
||||
F32 : float32<measure>
|
||||
Single : single<measure>
|
||||
IntMeasureOption : int<measure> option
|
||||
IntMeasureNullable : int<measure> Nullable
|
||||
Enum : SomeEnum
|
||||
Timestamp : DateTimeOffset
|
||||
Unit : unit
|
||||
}
|
||||
|
||||
[<WoofWare.Myriad.Plugins.JsonSerialize true>]
|
||||
[<WoofWare.Myriad.Plugins.JsonParse true>]
|
||||
type FirstDu =
|
||||
| EmptyCase
|
||||
| Case1 of data : string
|
||||
| Case2 of record : JsonRecordTypeWithBoth * i : int
|
||||
|
||||
[<WoofWare.Myriad.Plugins.JsonParse true>]
|
||||
[<WoofWare.Myriad.Plugins.JsonSerialize true>]
|
||||
type HeaderAndValue =
|
||||
{
|
||||
Header : string
|
||||
Value : string
|
||||
}
|
||||
|
||||
[<WoofWare.Myriad.Plugins.JsonSerialize true>]
|
||||
[<WoofWare.Myriad.Plugins.JsonParse true>]
|
||||
type Foo =
|
||||
{
|
||||
Message : HeaderAndValue option
|
||||
}
|
||||
|
||||
[<WoofWare.Myriad.Plugins.JsonSerialize true>]
|
||||
[<WoofWare.Myriad.Plugins.JsonParse true>]
|
||||
type CollectRemaining =
|
||||
{
|
||||
Message : HeaderAndValue option
|
||||
[<JsonExtensionData>]
|
||||
Rest : Dictionary<string, System.Text.Json.Nodes.JsonNode>
|
||||
}
|
||||
|
||||
[<WoofWare.Myriad.Plugins.JsonSerialize true>]
|
||||
[<WoofWare.Myriad.Plugins.JsonParse true>]
|
||||
type OuterCollectRemaining =
|
||||
{
|
||||
[<JsonExtensionData>]
|
||||
Others : Dictionary<string, int>
|
||||
Remaining : CollectRemaining
|
||||
}
|
||||
|
@@ -76,3 +76,33 @@ type IVaultClient =
|
||||
|
||||
[<Get "v1/auth/jwt/login">]
|
||||
abstract GetJwt : role : string * jwt : string * ?ct : CancellationToken -> Task<JwtVaultResponse>
|
||||
|
||||
[<WoofWare.Myriad.Plugins.HttpClient false>]
|
||||
type IVaultClientNonExtensionMethod =
|
||||
[<Get "v1/{mountPoint}/{path}">]
|
||||
abstract GetSecret :
|
||||
jwt : JwtVaultResponse *
|
||||
[<Path "path">] path : string *
|
||||
[<Path "mountPoint">] mountPoint : string *
|
||||
?ct : CancellationToken ->
|
||||
Task<JwtSecretResponse>
|
||||
|
||||
[<Get "v1/auth/jwt/login">]
|
||||
abstract GetJwt : role : string * jwt : string * ?ct : CancellationToken -> Task<JwtVaultResponse>
|
||||
|
||||
[<WoofWare.Myriad.Plugins.HttpClient(true)>]
|
||||
type IVaultClientExtensionMethod =
|
||||
[<Get "v1/{mountPoint}/{path}">]
|
||||
abstract GetSecret :
|
||||
jwt : JwtVaultResponse *
|
||||
[<Path "path">] path : string *
|
||||
[<Path "mountPoint">] mountPoint : string *
|
||||
?ct : CancellationToken ->
|
||||
Task<JwtSecretResponse>
|
||||
|
||||
[<Get "v1/auth/jwt/login">]
|
||||
abstract GetJwt : role : string * jwt : string * ?ct : CancellationToken -> Task<JwtVaultResponse>
|
||||
|
||||
[<RequireQualifiedAccess>]
|
||||
type VaultClientExtensionMethod =
|
||||
static member thisClashes = 99
|
||||
|
21054
ConsumePlugin/swagger-gitea.json
Normal file
21054
ConsumePlugin/swagger-gitea.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -10,19 +10,10 @@
|
||||
<WarnOn>FS3388,FS3559</WarnOn>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Nerdbank.GitVersioning" Version="3.6.133" PrivateAssets="all"/>
|
||||
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="8.0.0" PrivateAssets="All"/>
|
||||
<PackageReference Include="Nerdbank.GitVersioning" Version="3.8.38-alpha" PrivateAssets="all"/>
|
||||
<SourceLinkGitHubHost Include="github.com" ContentUrl="https://raw.githubusercontent.com"/>
|
||||
</ItemGroup>
|
||||
<!--
|
||||
SourceLink doesn't support F# deterministic builds out of the box,
|
||||
so tell SourceLink that our source root is going to be remapped.
|
||||
-->
|
||||
<Target Name="MapSourceRoot" BeforeTargets="_GenerateSourceLinkFile" Condition="'$(SourceRootMappedPathsFeatureSupported)' != 'true'">
|
||||
<ItemGroup>
|
||||
<SourceRoot Update="@(SourceRoot)">
|
||||
<MappedPath>Z:\CheckoutRoot\WoofWare.Myriad\</MappedPath>
|
||||
</SourceRoot>
|
||||
</ItemGroup>
|
||||
</Target>
|
||||
<PropertyGroup Condition="'$(GITHUB_ACTION)' != ''">
|
||||
<ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
|
210
README.md
210
README.md
@@ -8,23 +8,22 @@
|
||||
|
||||
Some helpers in [Myriad](https://github.com/MoiraeSoftware/myriad/) which might be useful.
|
||||
|
||||
These are currently somewhat experimental, and I personally am their primary customer.
|
||||
The `RemoveOptions` generator in particular is extremely half-baked.
|
||||
Currently implemented:
|
||||
|
||||
* `JsonParse` (to stamp out `jsonParse : JsonNode -> 'T` methods).
|
||||
* `JsonSerialize` (to stamp out `toJsonNode : 'T -> JsonNode` methods).
|
||||
* `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, like a compile-time [Foq](https://github.com/fsprojects/Foq)).
|
||||
* `ArgParser` (to stamp out a basic argument parser).
|
||||
* `SwaggerClient` (to stamp out an HTTP client for a Swagger API).
|
||||
* `CreateCatamorphism` (to stamp out a non-stack-overflowing [catamorphism](https://fsharpforfunandprofit.com/posts/recursive-types-and-folds/) for a discriminated union).
|
||||
* `RemoveOptions` (to strip `option` modifiers from a type) - this one is particularly half-baked!
|
||||
|
||||
If you would like to ensure that your particular use-case remains unbroken, please do contribute tests to this repository.
|
||||
The `ConsumePlugin` assembly contains a number of invocations of these source generators,
|
||||
so you just need to add copies of your types to that assembly to ensure that I will at least notice if I break the build;
|
||||
and if you add tests to `WoofWare.Myriad.Plugins.Test` then I will also notice if I break the runtime semantics of the generated code.
|
||||
|
||||
Currently implemented:
|
||||
|
||||
* `JsonParse` (to stamp out `jsonParse : JsonNode -> 'T` methods);
|
||||
* `JsonSerialize` (to stamp out `toJsonNode : 'T -> JsonNode` methods);
|
||||
* `RemoveOptions` (to strip `option` modifiers from a type).
|
||||
* `HttpClient` (to stamp out a [RestEase](https://github.com/canton7/RestEase)-style HTTP client).
|
||||
* `GenerateMock` (to stamp out a record type corresponding to an interface).
|
||||
* `CreateCatamorphism` (to stamp out a non-stack-overflowing [catamorphism](https://fsharpforfunandprofit.com/posts/recursive-types-and-folds/) for a discriminated union).
|
||||
|
||||
## `JsonParse`
|
||||
|
||||
Takes records like this:
|
||||
@@ -137,12 +136,15 @@ module InnerTypeWithBoth =
|
||||
ret.Add (key.ToString (), System.Text.Json.Nodes.JsonValue.Create<Uri> value)
|
||||
|
||||
ret
|
||||
) input.Map
|
||||
) input.ReadOnlyDict
|
||||
)
|
||||
|
||||
node
|
||||
```
|
||||
|
||||
Also includes an *opinionated* serializer for discriminated unions.
|
||||
(Any such serializer must be opinionated, because JSON does not natively model DUs.)
|
||||
|
||||
As in `JsonParse`, you can optionally supply the boolean `true` to the attribute,
|
||||
which will cause Myriad to stamp out an extension method rather than a module with the same name as the type.
|
||||
|
||||
@@ -150,6 +152,160 @@ The same limitations generally apply to `JsonSerialize` as do to `JsonParse`.
|
||||
|
||||
For an example of using both `JsonParse` and `JsonSerialize` together with complex types, see [the type definitions](./ConsumePlugin/SerializationAndDeserialization.fs) and [tests](./WoofWare.Myriad.Plugins.Test/TestJsonSerialize/TestJsonSerde.fs).
|
||||
|
||||
## `ArgParser`
|
||||
|
||||
Takes a record like this:
|
||||
|
||||
```fsharp
|
||||
type DryRunMode =
|
||||
| [<ArgumentFlag true> Dry
|
||||
| [<ArgumentFlag false> Wet
|
||||
|
||||
[<ArgParser>]
|
||||
type Foo =
|
||||
{
|
||||
[<ArgumentHelpText "Enable the frobnicator">]
|
||||
SomeFlag : bool
|
||||
A : int option
|
||||
[<ArgumentDefaultFunction>]
|
||||
B : Choice<int, int>
|
||||
[<ArgumentDefaultEnvironmentVariable "MY_ENV_VAR">]
|
||||
BWithEnv : Choice<int, int>
|
||||
[<ArgumentDefaultFunction>]
|
||||
DryRun : DryRunMode
|
||||
[<ArgumentLongForm "longer-form-replaces-c">]
|
||||
C : float list
|
||||
// optionally:
|
||||
[<PositionalArgs>]
|
||||
Rest : string list // or e.g. `int list` if you want them parsed into a type too
|
||||
}
|
||||
static member DefaultB () = 4
|
||||
static member DefaultDryRun () = DryRunMode.Wet
|
||||
```
|
||||
|
||||
and stamps out a basic `parse` method of this signature:
|
||||
|
||||
```fsharp
|
||||
[<RequireQualifiedAccess>]
|
||||
module Foo =
|
||||
// in case you want to test it
|
||||
let parse' (getEnvVar : string -> string) (args : string list) : Foo = ...
|
||||
// the one we expect you actually want to use
|
||||
let parse (args : string list) : Foo = ...
|
||||
```
|
||||
|
||||
Default arguments are handled as `Choice<'a, 'a>`:
|
||||
you get a `Choice1Of2` if the user provided the input, or a `Choice2Of2` if the parser filled in your specified default value.
|
||||
|
||||
You can control `TimeSpan` and friends with the `[<InvariantCulture>]` and `[<ParseExact @"hh\:mm\:ss">]` attributes.
|
||||
|
||||
You can generate extension methods for the type, instead of a module with the type's name, using `[<ArgParser (* isExtensionMethod = *) true>]`.
|
||||
|
||||
If `--help` appears in a position where the parser is expecting a key (e.g. in the first position, or after a `--foo=bar`), the parser fails with help text.
|
||||
The parser also makes a limited effort to supply help text when encountering an invalid parse.
|
||||
|
||||
### What's the point?
|
||||
|
||||
I got fed up of waiting for us to find time to rewrite the in-house one at work.
|
||||
That one has a bunch of nice compositional properties, which my version lacks:
|
||||
I can basically only deal with primitive types, and e.g. you can't stack records and discriminated unions inside each other.
|
||||
|
||||
But I *do* want an F#-native argument parser suitable for AOT-compilation.
|
||||
|
||||
Why not [Argu](https://fsprojects.github.io/Argu/)?
|
||||
Answer: I got annoyed with having to construct my records by hand even after Argu returned and said the parsing was all "done".
|
||||
|
||||
### Limitations
|
||||
|
||||
This is very bare-bones, but do raise GitHub issues if you like (or if you find cases where the parser does the wrong thing).
|
||||
|
||||
* Help is signalled by throwing an exception, so you'll get an unsightly stack trace and a nonzero exit code.
|
||||
* Help doesn't take into account any arguments the user has entered. Ideally you'd get contextual information like an identification of which args the user has supplied at the point where the parse failed or help was requested.
|
||||
* I don't handle very many types, and in particular a real arg parser would handle DUs and records with nesting.
|
||||
* I don't try very hard to find a valid parse. It may well be possible to find a case where I fail to parse despite there existing a valid parse.
|
||||
* There's no subcommand support (you'll have to do that yourself).
|
||||
|
||||
It should work fine if you just want to compose a few primitive types, though.
|
||||
|
||||
## `SwaggerClient`
|
||||
|
||||
Takes a JSON-schema definition of a [Swagger API](https://swagger.io/), and stamps out a client like this:
|
||||
|
||||
```fsharp
|
||||
/// A type which was defined in the Swagger spec
|
||||
[<JsonParse true ; JsonSerialize true>]
|
||||
type SwaggerType1 =
|
||||
{
|
||||
[<System.Text.Json.Serialization.JsonExtensionData>]
|
||||
AdditionalProperties : System.Collections.Generic.Dictionary<string, System.Text.Json.Nodes.JsonNode>
|
||||
Message : string
|
||||
}
|
||||
|
||||
/// Documentation from the Swagger spec
|
||||
[<HttpClient false ; RestEase.BasePath "/api/v1">]
|
||||
type IGitea =
|
||||
/// Returns the Person actor for a user
|
||||
[<RestEase.Get "/activitypub/user/{username}">]
|
||||
abstract ActivitypubPerson :
|
||||
[<RestEase.Path "username">] username : string * ?ct : System.Threading.CancellationToken ->
|
||||
ActivityPub System.Threading.Tasks.Task
|
||||
```
|
||||
|
||||
Notice that we automatically decorate the type with our `[<HttpClient>]` attribute, so if you choose to do so, you can chain another Myriad generated file off this one and you'll get a RestEase-style client stamped out.
|
||||
(See below, searching on the string `"Generated2SwaggerGitea.fs"`, for an example.)
|
||||
|
||||
You don't need to `Content Include` or `EmbeddedResource Include` the JSON schema.
|
||||
`None Include` will do; we only need the source to be available at build time.
|
||||
|
||||
You *do* need to include the following configuration:
|
||||
|
||||
```xml
|
||||
<Compile Include="GeneratedClient.fs">
|
||||
<!-- This bit is normal: -->
|
||||
<MyriadFile>swagger.json</MyriadFile>
|
||||
<!-- This bit is new and required! -->
|
||||
<MyriadParams>
|
||||
<ClassName>GiteaClient</ClassName>
|
||||
<!-- Optionally: -->
|
||||
<GenerateMock>true</GenerateMock>
|
||||
</MyriadParams>
|
||||
</Compile>
|
||||
```
|
||||
|
||||
The `<ClassName />` key tells us what to name the resulting interface (it gets an `I` prepended for you).
|
||||
You can optionally also set `<GenerateMockVisibility>v</GenerateMockVisibility>` to add the `[<GenerateMock>]` attribute to the type
|
||||
(where `v` should be `internal` or `public`, indicating "resulting mock type is internal" vs "is public"),
|
||||
so that the following manoeuvre will result in a generated mock:
|
||||
|
||||
```xml
|
||||
<None Include="swagger-gitea.json" />
|
||||
<Compile Include="GeneratedSwaggerGitea.fs">
|
||||
<MyriadFile>swagger-gitea.json</MyriadFile>
|
||||
<MyriadParams>
|
||||
<GenerateMockVisibility>public</GenerateMockVisibility>
|
||||
<ClassName>Gitea</ClassName>
|
||||
</MyriadParams>
|
||||
</Compile>
|
||||
<Compile Include="Generated2SwaggerGitea.fs">
|
||||
<MyriadFile>GeneratedSwaggerGitea.fs</MyriadFile>
|
||||
</Compile>
|
||||
```
|
||||
|
||||
(Note that you do have to create the `GeneratedSwaggerGitea.fs` file manually before code generation happens. Myriad will throw if that file isn't there, because `Generated2SwaggerGitea.fs` depends on it so Myriad wants to compute its hash. Just make an empty file.)
|
||||
|
||||
### What's the point?
|
||||
|
||||
[`SwaggerProvider`](https://github.com/fsprojects/SwaggerProvider) is *absolutely magical*, but it's kind of witchcraft.
|
||||
I fear no man, but that thing… it scares me.
|
||||
|
||||
Also, builds using `SwaggerProvider` appear to be inherently nondeterministic, even if the data source doesn't change.
|
||||
|
||||
## Limitations
|
||||
|
||||
Swagger API specs appear to be pretty cowboy in the wild.
|
||||
I try to cope with invalid schemas I have seen, but I can't guarantee I do so correctly.
|
||||
Definitely do perform integration tests and let me know of weird specs you encounter, and bits of the (very extensive) Swagger spec I have omitted!
|
||||
|
||||
## `RemoveOptions`
|
||||
|
||||
Takes a record like this:
|
||||
@@ -448,6 +604,36 @@ For example, this specifies that Myriad is to use the contents of `Client.fs` to
|
||||
</ItemGroup>
|
||||
```
|
||||
|
||||
## Alternative use without the attributes
|
||||
|
||||
You can avoid taking a reference on the `WoofWare.Myriad.Plugins.Attributes` assembly, instead putting all the configuration into the project file.
|
||||
This is implemented for everything except the SwaggerClientGenerator.
|
||||
|
||||
```xml
|
||||
<Project>
|
||||
<ItemGroup>
|
||||
<Compile Include="Client.fs" />
|
||||
<Compile Include="GeneratedClient.fs">
|
||||
<MyriadFile>Client.fs</MyriadFile>
|
||||
<MyriadParams>
|
||||
<MyTypeName1>GenerateMock(false)!JsonParse</MyTypeName1>
|
||||
<SomeOtherTypeName>GenerateMock</SomeOtherTypeName>
|
||||
</MyriadParams>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="WoofWare.Myriad.Plugins" Version="$(WoofWareMyriadPluginVersion)" PrivateAssets="all" />
|
||||
<PackageReference Include="Myriad.Sdk" Version="0.8.3" PrivateAssets="all" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
```
|
||||
|
||||
That is, you specify a `!`-delimited list of the attributes you *would* apply to the type.
|
||||
Supply "arguments" to the attribute name in the project file as you would to the attribute itself.
|
||||
|
||||
(Yes, this is indeed incredibly cumbersome, and you're not interested in the reasons it's all so mad!
|
||||
I'm hopefully going to get round to writing a more powerful source generation system which won't have these limitations.)
|
||||
|
||||
### Myriad Gotchas
|
||||
|
||||
* MsBuild doesn't always realise that it needs to invoke Myriad during rebuild.
|
||||
|
102
WoofWare.Myriad.Plugins.Attributes/ArgParserAttributes.fs
Normal file
102
WoofWare.Myriad.Plugins.Attributes/ArgParserAttributes.fs
Normal file
@@ -0,0 +1,102 @@
|
||||
namespace WoofWare.Myriad.Plugins
|
||||
|
||||
open System
|
||||
|
||||
/// Attribute indicating a record type to which the "build arg parser" Myriad
|
||||
/// generator should apply during build.
|
||||
///
|
||||
/// 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 ArgParserAttribute (isExtensionMethod : bool) =
|
||||
inherit Attribute ()
|
||||
|
||||
/// The default value of `isExtensionMethod`, the optional argument to the ArgParserAttribute constructor.
|
||||
static member DefaultIsExtensionMethod = false
|
||||
|
||||
/// Shorthand for the "isExtensionMethod = false" constructor; see documentation there for details.
|
||||
new () = ArgParserAttribute ArgParserAttribute.DefaultIsExtensionMethod
|
||||
|
||||
/// Attribute indicating that this field shall accumulate all unmatched args,
|
||||
/// as well as any that appear after a bare `--`.
|
||||
///
|
||||
/// Set `includeFlagLike = true` to include args that begin `--` in the
|
||||
/// positional args.
|
||||
/// (By default, `includeFlagLike = false` and we throw when encountering
|
||||
/// an argument which looks like a flag but which we don't recognise.)
|
||||
/// We will still interpret `--help` as requesting help, unless it comes after
|
||||
/// a standalone `--` separator.
|
||||
///
|
||||
/// If the type of the PositionalArgs field is `Choice<'a, 'a>`, then we will
|
||||
/// tell you whether each arg came before or after a standalone `--` separator.
|
||||
/// For example, `MyApp foo bar -- baz` with PositionalArgs of `Choice<string, string>`
|
||||
/// would yield `Choice1Of2 foo, Choice1Of2 bar, Choice2Of2 baz`.
|
||||
type PositionalArgsAttribute (includeFlagLike : bool) =
|
||||
inherit Attribute ()
|
||||
|
||||
/// The default value of `isExtensionMethod`, the optional argument to the ArgParserAttribute constructor.
|
||||
static member DefaultIncludeFlagLike = false
|
||||
|
||||
/// Shorthand for the "includeFlagLike = false" constructor; see documentation there for details.
|
||||
new () = PositionalArgsAttribute PositionalArgsAttribute.DefaultIncludeFlagLike
|
||||
|
||||
/// Attribute indicating that this field shall have a default value derived
|
||||
/// from calling an appropriately named static method on the type.
|
||||
///
|
||||
/// This attribute can only be placed on fields of type `Choice<_, _>` where both type parameters
|
||||
/// are the same.
|
||||
/// After a successful parse, the value is Choice1Of2 if the user supplied an input,
|
||||
/// or Choice2Of2 if the input was obtained by calling the default function.
|
||||
///
|
||||
/// The static method we call for field `FieldName : 'a` is `DefaultFieldName : unit -> 'a`.
|
||||
type ArgumentDefaultFunctionAttribute () =
|
||||
inherit Attribute ()
|
||||
|
||||
/// Attribute indicating that this field shall have a default value derived
|
||||
/// from an environment variable (whose name you give in the attribute constructor).
|
||||
///
|
||||
/// This attribute can only be placed on fields of type `Choice<_, _>` where both type parameters
|
||||
/// are the same.
|
||||
/// After a successful parse, the value is Choice1Of2 if the user supplied an input,
|
||||
/// or Choice2Of2 if the input was obtained by pulling a value from `Environment.GetEnvironmentVariable`.
|
||||
type ArgumentDefaultEnvironmentVariableAttribute (envVar : string) =
|
||||
inherit Attribute ()
|
||||
|
||||
/// Attribute indicating that this field shall have the given help text, when `--help` is invoked
|
||||
/// or when a parse error causes us to print help text.
|
||||
type ArgumentHelpTextAttribute (helpText : string) =
|
||||
inherit Attribute ()
|
||||
|
||||
/// Attribute indicating that this field should be parsed with a ParseExact method on its type.
|
||||
/// For example, on a TimeSpan field, with [<ArgumentParseExact @"hh\:mm\:ss">], we will call
|
||||
/// `TimeSpan.ParseExact (s, @"hh\:mm\:ss", CultureInfo.CurrentCulture).
|
||||
type ParseExactAttribute (format : string) =
|
||||
inherit Attribute ()
|
||||
|
||||
/// Attribute indicating that this field should be parsed in the invariant culture, rather than the
|
||||
/// default current culture.
|
||||
/// For example, on a TimeSpan field, with [<InvariantCulture>] and [<ArgumentParseExact @"hh\:mm\:ss">], we will call
|
||||
/// `TimeSpan.ParseExact (s, @"hh\:mm\:ss", CultureInfo.InvariantCulture).
|
||||
type InvariantCultureAttribute () =
|
||||
inherit Attribute ()
|
||||
|
||||
/// Attribute placed on a field of a two-case no-data discriminated union, indicating that this is "basically a bool".
|
||||
/// For example: `type DryRun = | [<ArgumentFlag true>] Dry | [<ArgumentFlag false>] Wet`
|
||||
/// A record with `{ DryRun : DryRun }` will then be parsed like `{ DryRun : bool }` (so the user supplies `--dry-run`),
|
||||
/// but that you get this strongly-typed value directly in the code (so you `match args.DryRun with | DryRun.Dry ...`).
|
||||
///
|
||||
/// You must put this attribute on both cases of the discriminated union, with opposite values in each case.
|
||||
type ArgumentFlagAttribute (flagValue : bool) =
|
||||
inherit Attribute ()
|
||||
|
||||
/// Attribute placed on a field of a record to specify a different long form from the default. If you place this
|
||||
/// attribute, you won't get the default: ArgFoo would normally be expressed as `--arg-foo`, but if you instead
|
||||
/// say `[<ArgumentLongForm "thingy-blah">]` or `[<ArgumentLongForm "thingy">]`, you instead use `--thingy-blah`
|
||||
/// or `--thingy` respectively.
|
||||
///
|
||||
/// You can place this argument multiple times.
|
||||
///
|
||||
/// Omit the initial `--` that you expect the user to type.
|
||||
[<AttributeUsage(AttributeTargets.Field, AllowMultiple = true)>]
|
||||
type ArgumentLongForm (s : string) =
|
||||
inherit Attribute ()
|
@@ -60,8 +60,17 @@ type JsonParseAttribute (isExtensionMethod : bool) =
|
||||
/// 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 () =
|
||||
///
|
||||
/// 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 HttpClientAttribute (isExtensionMethod : bool) =
|
||||
inherit Attribute ()
|
||||
/// The default value of `isExtensionMethod`, the optional argument to the HttpClientAttribute constructor.
|
||||
static member DefaultIsExtensionMethod = false
|
||||
|
||||
/// Shorthand for the "isExtensionMethod = false" constructor; see documentation there for details.
|
||||
new () = HttpClientAttribute HttpClientAttribute.DefaultIsExtensionMethod
|
||||
|
||||
/// Attribute indicating a DU type to which the "create catamorphism" Myriad
|
||||
/// generator should apply during build.
|
||||
|
84
WoofWare.Myriad.Plugins.Attributes/RestEase.fs
Normal file
84
WoofWare.Myriad.Plugins.Attributes/RestEase.fs
Normal file
@@ -0,0 +1,84 @@
|
||||
namespace WoofWare.Myriad.Plugins
|
||||
|
||||
open System
|
||||
|
||||
/// Module containing duplicates of the supported RestEase attributes, in case you don't want
|
||||
/// to take a dependency on RestEase.
|
||||
[<RequireQualifiedAccess>]
|
||||
module RestEase =
|
||||
/// Indicates that a method represents an HTTP Get query to the specified endpoint.
|
||||
type GetAttribute (path : string) =
|
||||
inherit Attribute ()
|
||||
|
||||
/// Indicates that a method represents an HTTP Post query to the specified endpoint.
|
||||
type PostAttribute (path : string) =
|
||||
inherit Attribute ()
|
||||
|
||||
/// Indicates that a method represents an HTTP Delete query to the specified endpoint.
|
||||
type DeleteAttribute (path : string) =
|
||||
inherit Attribute ()
|
||||
|
||||
/// Indicates that a method represents an HTTP Head query to the specified endpoint.
|
||||
type HeadAttribute (path : string) =
|
||||
inherit Attribute ()
|
||||
|
||||
/// Indicates that a method represents an HTTP Options query to the specified endpoint.
|
||||
type OptionsAttribute (path : string) =
|
||||
inherit Attribute ()
|
||||
|
||||
/// Indicates that a method represents an HTTP Put query to the specified endpoint.
|
||||
type PutAttribute (path : string) =
|
||||
inherit Attribute ()
|
||||
|
||||
/// Indicates that a method represents an HTTP Patch query to the specified endpoint.
|
||||
type PatchAttribute (path : string) =
|
||||
inherit Attribute ()
|
||||
|
||||
/// Indicates that a method represents an HTTP Trace query to the specified endpoint.
|
||||
type TraceAttribute (path : string) =
|
||||
inherit Attribute ()
|
||||
|
||||
/// Indicates that this argument to a method is interpolated into the HTTP request at runtime
|
||||
/// by setting a query parameter (with the given name) to the value of the annotated argument.
|
||||
type QueryAttribute (paramName : string) =
|
||||
inherit Attribute ()
|
||||
|
||||
/// Indicates that this interface represents a REST client which accesses an API whose paths are
|
||||
/// all relative to the given address.
|
||||
///
|
||||
/// We will essentially unconditionally append a slash to this for you, on the grounds that you probably don't
|
||||
/// intend the base path *itself* to be an endpoint.
|
||||
type BaseAddressAttribute (addr : string) =
|
||||
inherit Attribute ()
|
||||
|
||||
/// Indicates that this interface member causes the interface to set a header with the given name,
|
||||
/// whose value is obtained whenever required by a fresh call to the interface member.
|
||||
type HeaderAttribute (header : string, value : string option) =
|
||||
inherit Attribute ()
|
||||
new (header : string) = HeaderAttribute (header, None)
|
||||
new (header : string, value : string) = HeaderAttribute (header, Some value)
|
||||
|
||||
/// Indicates that this argument to a method is interpolated into the request path at runtime
|
||||
/// by writing it into the templated string that specifies the HTTP query e.g. in the `[<Get "/foo/{template}">]`.
|
||||
type PathAttribute (path : string option) =
|
||||
inherit Attribute ()
|
||||
new (path : string) = PathAttribute (Some path)
|
||||
new () = PathAttribute None
|
||||
|
||||
/// Indicates that this argument to a method is passed to the remote API by being serialised into the request
|
||||
/// body.
|
||||
type BodyAttribute () =
|
||||
inherit Attribute ()
|
||||
|
||||
/// This is interpolated into every URL, between the BaseAddress and the path specified by e.g. [<Get>].
|
||||
/// Note that if the [<Get>]-specified path starts with a slash, the BasePath is ignored, because then [<Get>]
|
||||
/// is considered to be relative to the URL root (i.e. the host part of the BaseAddress).
|
||||
/// Similarly, if the [<BasePath>] starts with a slash, then any path component of the BaseAddress is ignored.
|
||||
///
|
||||
/// We will essentially unconditionally append a slash to this for you, on the grounds that you probably don't
|
||||
/// intend the base path *itself* to be an endpoint.
|
||||
///
|
||||
/// Can contain {placeholders}; hopefully your methods define values for those placeholders with [<Path>]
|
||||
/// attributes!
|
||||
type BasePathAttribute (path : string) =
|
||||
inherit Attribute ()
|
@@ -1,3 +1,18 @@
|
||||
WoofWare.Myriad.Plugins.ArgParserAttribute inherit System.Attribute
|
||||
WoofWare.Myriad.Plugins.ArgParserAttribute..ctor [constructor]: bool
|
||||
WoofWare.Myriad.Plugins.ArgParserAttribute..ctor [constructor]: unit
|
||||
WoofWare.Myriad.Plugins.ArgParserAttribute.DefaultIsExtensionMethod [static property]: [read-only] bool
|
||||
WoofWare.Myriad.Plugins.ArgParserAttribute.get_DefaultIsExtensionMethod [static method]: unit -> bool
|
||||
WoofWare.Myriad.Plugins.ArgumentDefaultEnvironmentVariableAttribute inherit System.Attribute
|
||||
WoofWare.Myriad.Plugins.ArgumentDefaultEnvironmentVariableAttribute..ctor [constructor]: string
|
||||
WoofWare.Myriad.Plugins.ArgumentDefaultFunctionAttribute inherit System.Attribute
|
||||
WoofWare.Myriad.Plugins.ArgumentDefaultFunctionAttribute..ctor [constructor]: unit
|
||||
WoofWare.Myriad.Plugins.ArgumentFlagAttribute inherit System.Attribute
|
||||
WoofWare.Myriad.Plugins.ArgumentFlagAttribute..ctor [constructor]: bool
|
||||
WoofWare.Myriad.Plugins.ArgumentHelpTextAttribute inherit System.Attribute
|
||||
WoofWare.Myriad.Plugins.ArgumentHelpTextAttribute..ctor [constructor]: string
|
||||
WoofWare.Myriad.Plugins.ArgumentLongForm inherit System.Attribute
|
||||
WoofWare.Myriad.Plugins.ArgumentLongForm..ctor [constructor]: string
|
||||
WoofWare.Myriad.Plugins.CreateCatamorphismAttribute inherit System.Attribute
|
||||
WoofWare.Myriad.Plugins.CreateCatamorphismAttribute..ctor [constructor]: string
|
||||
WoofWare.Myriad.Plugins.GenerateMockAttribute inherit System.Attribute
|
||||
@@ -6,7 +21,12 @@ 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]: bool
|
||||
WoofWare.Myriad.Plugins.HttpClientAttribute..ctor [constructor]: unit
|
||||
WoofWare.Myriad.Plugins.HttpClientAttribute.DefaultIsExtensionMethod [static property]: [read-only] bool
|
||||
WoofWare.Myriad.Plugins.HttpClientAttribute.get_DefaultIsExtensionMethod [static method]: unit -> bool
|
||||
WoofWare.Myriad.Plugins.InvariantCultureAttribute inherit System.Attribute
|
||||
WoofWare.Myriad.Plugins.InvariantCultureAttribute..ctor [constructor]: unit
|
||||
WoofWare.Myriad.Plugins.JsonParseAttribute inherit System.Attribute
|
||||
WoofWare.Myriad.Plugins.JsonParseAttribute..ctor [constructor]: bool
|
||||
WoofWare.Myriad.Plugins.JsonParseAttribute..ctor [constructor]: unit
|
||||
@@ -17,5 +37,45 @@ 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.ParseExactAttribute inherit System.Attribute
|
||||
WoofWare.Myriad.Plugins.ParseExactAttribute..ctor [constructor]: string
|
||||
WoofWare.Myriad.Plugins.PositionalArgsAttribute inherit System.Attribute
|
||||
WoofWare.Myriad.Plugins.PositionalArgsAttribute..ctor [constructor]: bool
|
||||
WoofWare.Myriad.Plugins.PositionalArgsAttribute..ctor [constructor]: unit
|
||||
WoofWare.Myriad.Plugins.PositionalArgsAttribute.DefaultIncludeFlagLike [static property]: [read-only] bool
|
||||
WoofWare.Myriad.Plugins.PositionalArgsAttribute.get_DefaultIncludeFlagLike [static method]: unit -> bool
|
||||
WoofWare.Myriad.Plugins.RemoveOptionsAttribute inherit System.Attribute
|
||||
WoofWare.Myriad.Plugins.RemoveOptionsAttribute..ctor [constructor]: unit
|
||||
WoofWare.Myriad.Plugins.RemoveOptionsAttribute..ctor [constructor]: unit
|
||||
WoofWare.Myriad.Plugins.RestEase inherit obj
|
||||
WoofWare.Myriad.Plugins.RestEase+BaseAddressAttribute inherit System.Attribute
|
||||
WoofWare.Myriad.Plugins.RestEase+BaseAddressAttribute..ctor [constructor]: string
|
||||
WoofWare.Myriad.Plugins.RestEase+BasePathAttribute inherit System.Attribute
|
||||
WoofWare.Myriad.Plugins.RestEase+BasePathAttribute..ctor [constructor]: string
|
||||
WoofWare.Myriad.Plugins.RestEase+BodyAttribute inherit System.Attribute
|
||||
WoofWare.Myriad.Plugins.RestEase+BodyAttribute..ctor [constructor]: unit
|
||||
WoofWare.Myriad.Plugins.RestEase+DeleteAttribute inherit System.Attribute
|
||||
WoofWare.Myriad.Plugins.RestEase+DeleteAttribute..ctor [constructor]: string
|
||||
WoofWare.Myriad.Plugins.RestEase+GetAttribute inherit System.Attribute
|
||||
WoofWare.Myriad.Plugins.RestEase+GetAttribute..ctor [constructor]: string
|
||||
WoofWare.Myriad.Plugins.RestEase+HeadAttribute inherit System.Attribute
|
||||
WoofWare.Myriad.Plugins.RestEase+HeadAttribute..ctor [constructor]: string
|
||||
WoofWare.Myriad.Plugins.RestEase+HeaderAttribute inherit System.Attribute
|
||||
WoofWare.Myriad.Plugins.RestEase+HeaderAttribute..ctor [constructor]: (string, string option)
|
||||
WoofWare.Myriad.Plugins.RestEase+HeaderAttribute..ctor [constructor]: (string, string)
|
||||
WoofWare.Myriad.Plugins.RestEase+HeaderAttribute..ctor [constructor]: string
|
||||
WoofWare.Myriad.Plugins.RestEase+OptionsAttribute inherit System.Attribute
|
||||
WoofWare.Myriad.Plugins.RestEase+OptionsAttribute..ctor [constructor]: string
|
||||
WoofWare.Myriad.Plugins.RestEase+PatchAttribute inherit System.Attribute
|
||||
WoofWare.Myriad.Plugins.RestEase+PatchAttribute..ctor [constructor]: string
|
||||
WoofWare.Myriad.Plugins.RestEase+PathAttribute inherit System.Attribute
|
||||
WoofWare.Myriad.Plugins.RestEase+PathAttribute..ctor [constructor]: string
|
||||
WoofWare.Myriad.Plugins.RestEase+PathAttribute..ctor [constructor]: string option
|
||||
WoofWare.Myriad.Plugins.RestEase+PathAttribute..ctor [constructor]: unit
|
||||
WoofWare.Myriad.Plugins.RestEase+PostAttribute inherit System.Attribute
|
||||
WoofWare.Myriad.Plugins.RestEase+PostAttribute..ctor [constructor]: string
|
||||
WoofWare.Myriad.Plugins.RestEase+PutAttribute inherit System.Attribute
|
||||
WoofWare.Myriad.Plugins.RestEase+PutAttribute..ctor [constructor]: string
|
||||
WoofWare.Myriad.Plugins.RestEase+QueryAttribute inherit System.Attribute
|
||||
WoofWare.Myriad.Plugins.RestEase+QueryAttribute..ctor [constructor]: string
|
||||
WoofWare.Myriad.Plugins.RestEase+TraceAttribute inherit System.Attribute
|
||||
WoofWare.Myriad.Plugins.RestEase+TraceAttribute..ctor [constructor]: string
|
@@ -11,11 +11,9 @@ module TestSurface =
|
||||
[<Test>]
|
||||
let ``Ensure API surface has not been modified`` () = ApiSurface.assertIdentical assembly
|
||||
|
||||
(*
|
||||
[<Test>]
|
||||
let ``Check version against remote`` () =
|
||||
MonotonicVersion.validate assembly "WoofWare.Myriad.Plugins.Attributes"
|
||||
*)
|
||||
|
||||
[<Test ; Explicit>]
|
||||
let ``Update API surface`` () =
|
||||
|
@@ -1,10 +1,15 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
|
||||
<IsPackable>false</IsPackable>
|
||||
<IsTestProject>true</IsTestProject>
|
||||
<IsPackable>false</IsPackable>
|
||||
<IsTestProject>true</IsTestProject>
|
||||
<!--
|
||||
Known high severity vulnerability
|
||||
I have not yet seen a single instance where I care about this warning
|
||||
-->
|
||||
<NoWarn>$(NoWarn),NU1903</NoWarn>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@@ -12,10 +17,10 @@
|
||||
</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"/>
|
||||
<PackageReference Include="ApiSurface" Version="4.1.21" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1"/>
|
||||
<PackageReference Include="NUnit" Version="4.3.2"/>
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="5.0.0"/>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
@@ -19,6 +19,8 @@
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Include="Attributes.fs"/>
|
||||
<Compile Include="ArgParserAttributes.fs" />
|
||||
<Compile Include="RestEase.fs" />
|
||||
<EmbeddedResource Include="version.json"/>
|
||||
<EmbeddedResource Include="SurfaceBaseline.txt"/>
|
||||
<None Include="..\README.md">
|
||||
|
@@ -1,7 +1,15 @@
|
||||
{
|
||||
"version": "2.2",
|
||||
"version": "3.6",
|
||||
"publicReleaseRefSpec": [
|
||||
"^refs/heads/main$"
|
||||
],
|
||||
"pathFilters": null
|
||||
}
|
||||
"pathFilters": [
|
||||
":/README.md",
|
||||
":/LICENSE",
|
||||
":/WoofWare.Myriad.Plugins/logo.png",
|
||||
":/Directory.Build.props",
|
||||
":/global.json",
|
||||
"./",
|
||||
":^Test"
|
||||
]
|
||||
}
|
@@ -58,7 +58,7 @@ module PureGymDtos =
|
||||
[
|
||||
"""{"latitude": 1.0, "longitude": 3.0}""",
|
||||
{
|
||||
GymLocation.Latitude = 1.0
|
||||
GymLocation.Latitude = 1.0<measure>
|
||||
Longitude = 3.0
|
||||
}
|
||||
]
|
||||
@@ -96,7 +96,7 @@ module PureGymDtos =
|
||||
Location =
|
||||
{
|
||||
Longitude = -0.110252
|
||||
Latitude = 51.480401
|
||||
Latitude = 51.480401<measure>
|
||||
}
|
||||
TimeZone = "Europe/London"
|
||||
ReopenDate = "2021-04-12T00:00:00+01 Europe/London"
|
||||
|
706
WoofWare.Myriad.Plugins.Test/TestArgParser/TestArgParser.fs
Normal file
706
WoofWare.Myriad.Plugins.Test/TestArgParser/TestArgParser.fs
Normal file
@@ -0,0 +1,706 @@
|
||||
namespace WoofWare.Myriad.Plugins.Test
|
||||
|
||||
open System
|
||||
open System.Threading
|
||||
open NUnit.Framework
|
||||
open FsUnitTyped
|
||||
open ConsumePlugin
|
||||
open FsCheck
|
||||
|
||||
[<TestFixture>]
|
||||
module TestArgParser =
|
||||
|
||||
[<TestCase true>]
|
||||
[<TestCase false>]
|
||||
let ``Positionals get parsed: they don't have to be strings`` (sep : bool) =
|
||||
let getEnvVar (_ : string) = failwith "should not call"
|
||||
|
||||
let property
|
||||
(fooSep : bool)
|
||||
(barSep : bool)
|
||||
(bazSep : bool)
|
||||
(pos0 : int list)
|
||||
(pos1 : int list)
|
||||
(pos2 : int list)
|
||||
(pos3 : int list)
|
||||
(pos4 : int list)
|
||||
=
|
||||
let args =
|
||||
[
|
||||
yield! pos0 |> List.map string<int>
|
||||
if fooSep then
|
||||
yield "--foo=3"
|
||||
else
|
||||
yield "--foo"
|
||||
yield "3"
|
||||
yield! pos1 |> List.map string<int>
|
||||
if barSep then
|
||||
yield "--bar=4"
|
||||
else
|
||||
yield "--bar"
|
||||
yield "4"
|
||||
yield! pos2 |> List.map string<int>
|
||||
if bazSep then
|
||||
yield "--baz=true"
|
||||
else
|
||||
yield "--baz"
|
||||
yield "true"
|
||||
yield! pos3 |> List.map string<int>
|
||||
if sep then
|
||||
yield "--"
|
||||
yield! pos4 |> List.map string<int>
|
||||
]
|
||||
|
||||
BasicWithIntPositionals.parse' getEnvVar args
|
||||
|> shouldEqual
|
||||
{
|
||||
Foo = 3
|
||||
Bar = "4"
|
||||
Baz = true
|
||||
Rest = pos0 @ pos1 @ pos2 @ pos3 @ pos4
|
||||
}
|
||||
|
||||
Check.QuickThrowOnFailure property
|
||||
|
||||
[<Test>]
|
||||
let ``Arg-like thing appearing before double dash`` () =
|
||||
let envCalls = ref 0
|
||||
|
||||
let getEnvVar (_ : string) =
|
||||
Interlocked.Increment envCalls |> ignore<int>
|
||||
None
|
||||
|
||||
let args = [ "--foo=3" ; "--non-existent" ; "--bar=4" ; "--baz=true" ]
|
||||
|
||||
let exc =
|
||||
Assert.Throws<exn> (fun () -> Basic.parse' getEnvVar args |> ignore<Basic>)
|
||||
|
||||
envCalls.Value |> shouldEqual 0
|
||||
|
||||
exc.Message
|
||||
|> shouldEqual
|
||||
"""Unable to process supplied arg --non-existent. Help text follows.
|
||||
--foo int32 : This is a foo!
|
||||
--bar string
|
||||
--baz bool
|
||||
--rest string (positional args) (can be repeated) : Here's where the rest of the args go"""
|
||||
|
||||
[<Test>]
|
||||
let ``Can supply positional args with key`` () =
|
||||
let envCalls = ref 0
|
||||
|
||||
let getEnvVar (_ : string) =
|
||||
Interlocked.Increment envCalls |> ignore<int>
|
||||
None
|
||||
|
||||
let property (args : (int * bool) list) (afterDoubleDash : int list option) =
|
||||
let flatArgs =
|
||||
args
|
||||
|> List.collect (fun (value, sep) ->
|
||||
if sep then
|
||||
[ $"--rest=%i{value}" ]
|
||||
else
|
||||
[ "--rest" ; string<int> value ]
|
||||
)
|
||||
|> fun l -> l @ [ "--foo=3" ; "--bar=4" ; "--baz=true" ]
|
||||
|
||||
let flatArgs, expected =
|
||||
match afterDoubleDash with
|
||||
| None -> flatArgs, List.map fst args
|
||||
| Some rest -> flatArgs @ [ "--" ] @ (List.map string<int> rest), List.map fst args @ rest
|
||||
|
||||
BasicWithIntPositionals.parse' getEnvVar flatArgs
|
||||
|> shouldEqual
|
||||
{
|
||||
Foo = 3
|
||||
Bar = "4"
|
||||
Baz = true
|
||||
Rest = expected
|
||||
}
|
||||
|
||||
Check.QuickThrowOnFailure property
|
||||
envCalls.Value |> shouldEqual 0
|
||||
|
||||
[<Test>]
|
||||
let ``Consume multiple occurrences of required arg`` () =
|
||||
let envCalls = ref 0
|
||||
|
||||
let getEnvVar (_ : string) =
|
||||
Interlocked.Increment envCalls |> ignore<int>
|
||||
None
|
||||
|
||||
let args = [ "--foo=3" ; "--rest" ; "7" ; "--bar=4" ; "--baz=true" ; "--rest=8" ]
|
||||
|
||||
let result = BasicNoPositionals.parse' getEnvVar args
|
||||
|
||||
envCalls.Value |> shouldEqual 0
|
||||
|
||||
result
|
||||
|> shouldEqual
|
||||
{
|
||||
Foo = 3
|
||||
Bar = "4"
|
||||
Baz = true
|
||||
Rest = [ 7 ; 8 ]
|
||||
}
|
||||
|
||||
[<Test>]
|
||||
let ``Gracefully handle invalid multiple occurrences of required arg`` () =
|
||||
let envCalls = ref 0
|
||||
|
||||
let getEnvVar (_ : string) =
|
||||
Interlocked.Increment envCalls |> ignore<int>
|
||||
None
|
||||
|
||||
let args = [ "--foo=3" ; "--foo" ; "9" ; "--bar=4" ; "--baz=true" ; "--baz=false" ]
|
||||
|
||||
let exc =
|
||||
Assert.Throws<exn> (fun () -> Basic.parse' getEnvVar args |> ignore<Basic>)
|
||||
|
||||
envCalls.Value |> shouldEqual 0
|
||||
|
||||
exc.Message
|
||||
|> shouldEqual
|
||||
"""Errors during parse!
|
||||
Argument '--foo' was supplied multiple times: 3 and 9
|
||||
Argument '--baz' was supplied multiple times: True and false"""
|
||||
|
||||
[<Test>]
|
||||
let ``Args appearing after double dash are positional`` () =
|
||||
let envCalls = ref 0
|
||||
|
||||
let getEnvVar (_ : string) =
|
||||
Interlocked.Increment envCalls |> ignore<int>
|
||||
None
|
||||
|
||||
let args = [ "--" ; "--foo=3" ; "--bar=4" ; "--baz=true" ]
|
||||
|
||||
let exc =
|
||||
Assert.Throws<exn> (fun () -> Basic.parse' getEnvVar args |> ignore<Basic>)
|
||||
|
||||
exc.Message
|
||||
|> shouldEqual
|
||||
"""Errors during parse!
|
||||
Required argument '--foo' received no value
|
||||
Required argument '--bar' received no value
|
||||
Required argument '--baz' received no value"""
|
||||
|
||||
envCalls.Value |> shouldEqual 0
|
||||
|
||||
[<Test>]
|
||||
let ``Help text`` () =
|
||||
let getEnvVar (s : string) =
|
||||
s |> shouldEqual "CONSUMEPLUGIN_THINGS"
|
||||
Some "hi!"
|
||||
|
||||
let exc =
|
||||
Assert.Throws<exn> (fun () -> Basic.parse' getEnvVar [ "--help" ] |> ignore<Basic>)
|
||||
|
||||
exc.Message
|
||||
|> shouldEqual
|
||||
"""Help text requested.
|
||||
--foo int32 : This is a foo!
|
||||
--bar string
|
||||
--baz bool
|
||||
--rest string (positional args) (can be repeated) : Here's where the rest of the args go"""
|
||||
|
||||
[<Test>]
|
||||
let ``Help text, with default values`` () =
|
||||
let envVars = ref 0
|
||||
|
||||
let getEnvVar (_ : string) =
|
||||
Interlocked.Increment envVars |> ignore<int>
|
||||
None
|
||||
|
||||
let exc =
|
||||
Assert.Throws<exn> (fun () -> LoadsOfTypes.parse' getEnvVar [ "--help" ] |> ignore<LoadsOfTypes>)
|
||||
|
||||
exc.Message
|
||||
|> shouldEqual
|
||||
"""Help text requested.
|
||||
--foo int32
|
||||
--bar string
|
||||
--baz bool
|
||||
--some-file FileInfo
|
||||
--some-directory DirectoryInfo
|
||||
--some-list DirectoryInfo (can be repeated)
|
||||
--optional-thing-with-no-default int32 (optional)
|
||||
--optional-thing bool (default value: True)
|
||||
--another-optional-thing int32 (default value: 3)
|
||||
--yet-another-optional-thing string (default value populated from env var CONSUMEPLUGIN_THINGS)
|
||||
--positionals int32 (positional args) (can be repeated)"""
|
||||
|
||||
envVars.Value |> shouldEqual 0
|
||||
|
||||
[<Test>]
|
||||
let ``Default values`` () =
|
||||
let getEnvVar (s : string) =
|
||||
s |> shouldEqual "CONSUMEPLUGIN_THINGS"
|
||||
Some "hi!"
|
||||
|
||||
let args =
|
||||
[
|
||||
"--foo"
|
||||
"3"
|
||||
"--bar=some string"
|
||||
"--baz"
|
||||
"--some-file=/path/to/file"
|
||||
"--some-directory"
|
||||
"/a/dir"
|
||||
"--another-optional-thing"
|
||||
"3000"
|
||||
]
|
||||
|
||||
let result = LoadsOfTypes.parse' getEnvVar args
|
||||
|
||||
result.OptionalThing |> shouldEqual (Choice2Of2 true)
|
||||
result.OptionalThingWithNoDefault |> shouldEqual None
|
||||
result.AnotherOptionalThing |> shouldEqual (Choice1Of2 3000)
|
||||
result.YetAnotherOptionalThing |> shouldEqual (Choice2Of2 "hi!")
|
||||
|
||||
[<Test>]
|
||||
let ``ParseExact and help`` () =
|
||||
let count = ref 0
|
||||
|
||||
let getEnvVar (_ : string) =
|
||||
Interlocked.Increment count |> ignore<int>
|
||||
None
|
||||
|
||||
let exc =
|
||||
Assert.Throws<exn> (fun () -> DatesAndTimes.parse' getEnvVar [ "--help" ] |> ignore<DatesAndTimes>)
|
||||
|
||||
exc.Message
|
||||
|> shouldEqual
|
||||
@"Help text requested.
|
||||
--plain TimeSpan
|
||||
--invariant TimeSpan
|
||||
--exact TimeSpan : An exact time please [Parse format (.NET): hh\:mm\:ss]
|
||||
--invariant-exact TimeSpan : [Parse format (.NET): hh\:mm\:ss]"
|
||||
|
||||
count.Value |> shouldEqual 0
|
||||
|
||||
[<Test>]
|
||||
let rec ``TimeSpans and their attributes`` () =
|
||||
let count = ref 0
|
||||
|
||||
let getEnvVar (_ : string) =
|
||||
Interlocked.Increment count |> ignore<int>
|
||||
None
|
||||
|
||||
let parsed =
|
||||
DatesAndTimes.parse'
|
||||
getEnvVar
|
||||
[
|
||||
"--exact=11:34:00"
|
||||
"--plain=1"
|
||||
"--invariant=23:59"
|
||||
"--invariant-exact=23:59:00"
|
||||
]
|
||||
|
||||
parsed.Plain |> shouldEqual (TimeSpan (1, 0, 0, 0))
|
||||
parsed.Invariant |> shouldEqual (TimeSpan (23, 59, 00))
|
||||
parsed.Exact |> shouldEqual (TimeSpan (11, 34, 00))
|
||||
parsed.InvariantExact |> shouldEqual (TimeSpan (23, 59, 00))
|
||||
|
||||
let exc =
|
||||
Assert.Throws<exn> (fun () ->
|
||||
DatesAndTimes.parse'
|
||||
getEnvVar
|
||||
[
|
||||
"--exact=11:34:00"
|
||||
"--plain=1"
|
||||
"--invariant=23:59"
|
||||
"--invariant-exact=23:59"
|
||||
]
|
||||
|> ignore<DatesAndTimes>
|
||||
)
|
||||
|
||||
exc.Message
|
||||
|> shouldEqual
|
||||
"""Errors during parse!
|
||||
Input string was not in a correct format. (at arg --invariant-exact=23:59)
|
||||
Required argument '--invariant-exact' received no value"""
|
||||
|
||||
let exc =
|
||||
Assert.Throws<exn> (fun () ->
|
||||
DatesAndTimes.parse'
|
||||
getEnvVar
|
||||
[
|
||||
"--exact=11:34"
|
||||
"--plain=1"
|
||||
"--invariant=23:59"
|
||||
"--invariant-exact=23:59:00"
|
||||
]
|
||||
|> ignore<DatesAndTimes>
|
||||
)
|
||||
|
||||
exc.Message
|
||||
|> shouldEqual
|
||||
"""Errors during parse!
|
||||
Input string was not in a correct format. (at arg --exact=11:34)
|
||||
Required argument '--exact' received no value"""
|
||||
|
||||
count.Value |> shouldEqual 0
|
||||
|
||||
[<Test>]
|
||||
let ``Can consume stacked record without positionals`` () =
|
||||
let getEnvVar (_ : string) = failwith "should not call"
|
||||
|
||||
let parsed =
|
||||
ParentRecord.parse' getEnvVar [ "--and-another=true" ; "--thing1=9" ; "--thing2=a thing!" ]
|
||||
|
||||
parsed
|
||||
|> shouldEqual
|
||||
{
|
||||
Child =
|
||||
{
|
||||
Thing1 = 9
|
||||
Thing2 = "a thing!"
|
||||
}
|
||||
AndAnother = true
|
||||
}
|
||||
|
||||
[<Test>]
|
||||
let ``Can consume stacked record, child has positionals`` () =
|
||||
let getEnvVar (_ : string) = failwith "should not call"
|
||||
|
||||
let parsed =
|
||||
ParentRecordChildPos.parse'
|
||||
getEnvVar
|
||||
[
|
||||
"--and-another=true"
|
||||
"--thing1=9"
|
||||
"--thing2=https://example.com"
|
||||
"--thing2=http://example.com"
|
||||
]
|
||||
|
||||
parsed.AndAnother |> shouldEqual true
|
||||
parsed.Child.Thing1 |> shouldEqual 9
|
||||
|
||||
parsed.Child.Thing2
|
||||
|> List.map (fun (x : Uri) -> x.ToString ())
|
||||
|> shouldEqual [ "https://example.com/" ; "http://example.com/" ]
|
||||
|
||||
[<Test>]
|
||||
let ``Can consume stacked record, child has no positionals, parent has positionals`` () =
|
||||
let getEnvVar (_ : string) = failwith "should not call"
|
||||
|
||||
let parsed =
|
||||
ParentRecordSelfPos.parse'
|
||||
getEnvVar
|
||||
[
|
||||
"--and-another=true"
|
||||
"--and-another=false"
|
||||
"--and-another=true"
|
||||
"--thing1=9"
|
||||
"--thing2=some"
|
||||
]
|
||||
|
||||
parsed
|
||||
|> shouldEqual
|
||||
{
|
||||
Child =
|
||||
{
|
||||
Thing1 = 9
|
||||
Thing2 = "some"
|
||||
}
|
||||
AndAnother = [ true ; false ; true ]
|
||||
}
|
||||
|
||||
[<Test>]
|
||||
let ``Help text for stacked records`` () =
|
||||
let getEnvVar (_ : string) = failwith "should not call"
|
||||
|
||||
let exc =
|
||||
Assert.Throws<exn> (fun () ->
|
||||
ParentRecordSelfPos.parse' getEnvVar [ "--help" ] |> ignore<ParentRecordSelfPos>
|
||||
)
|
||||
|
||||
exc.Message
|
||||
|> shouldEqual
|
||||
"""Help text requested.
|
||||
--thing1 int32
|
||||
--thing2 string
|
||||
--and-another bool (positional args) (can be repeated)"""
|
||||
|
||||
[<Test>]
|
||||
let ``Positionals are tagged with Choice`` () =
|
||||
let getEnvVar (_ : string) = failwith "should not call"
|
||||
|
||||
ChoicePositionals.parse' getEnvVar [ "a" ; "b" ; "--" ; "--c" ; "--help" ]
|
||||
|> shouldEqual
|
||||
{
|
||||
Args = [ Choice1Of2 "a" ; Choice1Of2 "b" ; Choice2Of2 "--c" ; Choice2Of2 "--help" ]
|
||||
}
|
||||
|
||||
let boolCases =
|
||||
[
|
||||
"1", true
|
||||
"0", false
|
||||
"true", true
|
||||
"false", false
|
||||
"TRUE", true
|
||||
"FALSE", false
|
||||
]
|
||||
|> List.map TestCaseData
|
||||
|
||||
[<TestCaseSource(nameof (boolCases))>]
|
||||
let ``Bool env vars can be populated`` (envValue : string, boolValue : bool) =
|
||||
let getEnvVar (s : string) =
|
||||
s |> shouldEqual "CONSUMEPLUGIN_THINGS"
|
||||
Some envValue
|
||||
|
||||
ContainsBoolEnvVar.parse' getEnvVar []
|
||||
|> shouldEqual
|
||||
{
|
||||
BoolVar = Choice2Of2 boolValue
|
||||
}
|
||||
|
||||
[<Test>]
|
||||
let ``Bools can be treated with arity 0`` () =
|
||||
let getEnvVar (_ : string) = failwith "do not call"
|
||||
|
||||
ContainsBoolEnvVar.parse' getEnvVar [ "--bool-var" ]
|
||||
|> shouldEqual
|
||||
{
|
||||
BoolVar = Choice1Of2 true
|
||||
}
|
||||
|
||||
[<TestCaseSource(nameof boolCases)>]
|
||||
let ``Flag DUs can be parsed from env var`` (envValue : string, boolValue : bool) =
|
||||
let getEnvVar (s : string) =
|
||||
s |> shouldEqual "CONSUMEPLUGIN_THINGS"
|
||||
Some envValue
|
||||
|
||||
let boolValue = if boolValue then DryRunMode.Dry else DryRunMode.Wet
|
||||
|
||||
ContainsFlagEnvVar.parse' getEnvVar []
|
||||
|> shouldEqual
|
||||
{
|
||||
DryRun = Choice2Of2 boolValue
|
||||
}
|
||||
|
||||
let dryRunData =
|
||||
[
|
||||
[ "--dry-run" ], DryRunMode.Dry
|
||||
[ "--dry-run" ; "true" ], DryRunMode.Dry
|
||||
[ "--dry-run=true" ], DryRunMode.Dry
|
||||
[ "--dry-run" ; "True" ], DryRunMode.Dry
|
||||
[ "--dry-run=True" ], DryRunMode.Dry
|
||||
[ "--dry-run" ; "false" ], DryRunMode.Wet
|
||||
[ "--dry-run=false" ], DryRunMode.Wet
|
||||
[ "--dry-run" ; "False" ], DryRunMode.Wet
|
||||
[ "--dry-run=False" ], DryRunMode.Wet
|
||||
]
|
||||
|> List.map TestCaseData
|
||||
|
||||
[<TestCaseSource(nameof dryRunData)>]
|
||||
let ``Flag DUs can be parsed`` (args : string list, expected : DryRunMode) =
|
||||
let getEnvVar (_ : string) = failwith "do not call"
|
||||
|
||||
ContainsFlagEnvVar.parse' getEnvVar args
|
||||
|> shouldEqual
|
||||
{
|
||||
DryRun = Choice1Of2 expected
|
||||
}
|
||||
|
||||
[<TestCaseSource(nameof dryRunData)>]
|
||||
let ``Flag DUs can be parsed, ArgumentDefaultFunction`` (args : string list, expected : DryRunMode) =
|
||||
let getEnvVar (_ : string) = failwith "do not call"
|
||||
|
||||
ContainsFlagDefaultValue.parse' getEnvVar args
|
||||
|> shouldEqual
|
||||
{
|
||||
DryRun = Choice1Of2 expected
|
||||
}
|
||||
|
||||
[<Test>]
|
||||
let ``Flag DUs can be given a default value`` () =
|
||||
let getEnvVar (_ : string) = failwith "do not call"
|
||||
|
||||
ContainsFlagDefaultValue.parse' getEnvVar []
|
||||
|> shouldEqual
|
||||
{
|
||||
DryRun = Choice2Of2 DryRunMode.Wet
|
||||
}
|
||||
|
||||
[<Test>]
|
||||
let ``Help text for flag DU`` () =
|
||||
let getEnvVar (_ : string) = failwith "do not call"
|
||||
|
||||
let exc =
|
||||
Assert.Throws<exn> (fun () ->
|
||||
ContainsFlagDefaultValue.parse' getEnvVar [ "--help" ]
|
||||
|> ignore<ContainsFlagDefaultValue>
|
||||
)
|
||||
|
||||
exc.Message
|
||||
|> shouldEqual
|
||||
"""Help text requested.
|
||||
--dry-run bool (default value: false)"""
|
||||
|
||||
[<Test>]
|
||||
let ``Help text for flag DU, non default`` () =
|
||||
let getEnvVar (_ : string) = failwith "do not call"
|
||||
|
||||
let exc =
|
||||
Assert.Throws<exn> (fun () -> WithFlagDu.parse' getEnvVar [ "--help" ] |> ignore<WithFlagDu>)
|
||||
|
||||
exc.Message
|
||||
|> shouldEqual
|
||||
"""Help text requested.
|
||||
--dry-run bool"""
|
||||
|
||||
let longFormCases =
|
||||
let doTheThing =
|
||||
[
|
||||
[ "--do-something-else=foo" ]
|
||||
[ "--anotherarg=foo" ]
|
||||
[ "--do-something-else" ; "foo" ]
|
||||
[ "--anotherarg" ; "foo" ]
|
||||
]
|
||||
|
||||
let someFlag =
|
||||
[
|
||||
[ "--turn-it-on" ], true
|
||||
[ "--dont-turn-it-off" ], true
|
||||
[ "--turn-it-on=true" ], true
|
||||
[ "--dont-turn-it-off=true" ], true
|
||||
[ "--turn-it-on=false" ], false
|
||||
[ "--dont-turn-it-off=false" ], false
|
||||
[ "--turn-it-on" ; "true" ], true
|
||||
[ "--dont-turn-it-off" ; "true" ], true
|
||||
[ "--turn-it-on" ; "false" ], false
|
||||
[ "--dont-turn-it-off" ; "false" ], false
|
||||
]
|
||||
|
||||
List.allPairs doTheThing someFlag
|
||||
|> List.map (fun (doTheThing, (someFlag, someFlagResult)) ->
|
||||
let args = doTheThing @ someFlag
|
||||
|
||||
let expected =
|
||||
{
|
||||
DoTheThing = "foo"
|
||||
SomeFlag = someFlagResult
|
||||
}
|
||||
|
||||
args, expected
|
||||
)
|
||||
|> List.map TestCaseData
|
||||
|
||||
[<TestCaseSource(nameof longFormCases)>]
|
||||
let ``Long-form args`` (args : string list, expected : ManyLongForms) =
|
||||
let getEnvVar (_ : string) = failwith "do not call"
|
||||
|
||||
ManyLongForms.parse' getEnvVar args |> shouldEqual expected
|
||||
|
||||
[<Test>]
|
||||
let ``Long-form args can't be referred to by their original name`` () =
|
||||
let getEnvVar (_ : string) = failwith "do not call"
|
||||
|
||||
let exc =
|
||||
Assert.Throws<exn> (fun () ->
|
||||
ManyLongForms.parse' getEnvVar [ "--do-the-thing=foo" ] |> ignore<ManyLongForms>
|
||||
)
|
||||
|
||||
exc.Message
|
||||
|> shouldEqual """Unable to process argument --do-the-thing=foo as key --do-the-thing and value foo"""
|
||||
|
||||
[<Test>]
|
||||
let ``Long-form args help text`` () =
|
||||
let getEnvVar (_ : string) = failwith "do not call"
|
||||
|
||||
let exc =
|
||||
Assert.Throws<exn> (fun () -> ManyLongForms.parse' getEnvVar [ "--help" ] |> ignore<ManyLongForms>)
|
||||
|
||||
exc.Message
|
||||
|> shouldEqual
|
||||
"""Help text requested.
|
||||
--do-something-else / --anotherarg string
|
||||
--turn-it-on / --dont-turn-it-off bool"""
|
||||
|
||||
[<Test>]
|
||||
let ``Can collect *all* non-help args into positional args with includeFlagLike`` () =
|
||||
let getEnvVar (_ : string) = failwith "do not call"
|
||||
|
||||
FlagsIntoPositionalArgs.parse' getEnvVar [ "--a" ; "foo" ; "--b=false" ; "--c" ; "hi" ; "--" ; "--help" ]
|
||||
|> shouldEqual
|
||||
{
|
||||
A = "foo"
|
||||
GrabEverything = [ "--b=false" ; "--c" ; "hi" ; "--help" ]
|
||||
}
|
||||
|
||||
// Users might consider this eccentric!
|
||||
// But we're only a simple arg parser; we don't look around to see whether this is "almost"
|
||||
// a valid parse.
|
||||
FlagsIntoPositionalArgs.parse' getEnvVar [ "--a" ; "--b=false" ; "--c" ; "hi" ; "--" ; "--help" ]
|
||||
|> shouldEqual
|
||||
{
|
||||
A = "--b=false"
|
||||
GrabEverything = [ "--c" ; "hi" ; "--help" ]
|
||||
}
|
||||
|
||||
[<Test>]
|
||||
let ``Can collect non-help args into positional args with Choice`` () =
|
||||
let getEnvVar (_ : string) = failwith "do not call"
|
||||
|
||||
FlagsIntoPositionalArgsChoice.parse' getEnvVar [ "--a" ; "foo" ; "--b=false" ; "--c" ; "hi" ; "--" ; "--help" ]
|
||||
|> shouldEqual
|
||||
{
|
||||
A = "foo"
|
||||
GrabEverything =
|
||||
[
|
||||
Choice1Of2 "--b=false"
|
||||
Choice1Of2 "--c"
|
||||
Choice1Of2 "hi"
|
||||
Choice2Of2 "--help"
|
||||
]
|
||||
}
|
||||
|
||||
[<Test>]
|
||||
let ``Can collect non-help args into positional args, and we parse on the way`` () =
|
||||
let getEnvVar (_ : string) = failwith "do not call"
|
||||
|
||||
FlagsIntoPositionalArgsInt.parse' getEnvVar [ "3" ; "--a" ; "foo" ; "5" ; "--" ; "98" ]
|
||||
|> shouldEqual
|
||||
{
|
||||
A = "foo"
|
||||
GrabEverything = [ 3 ; 5 ; 98 ]
|
||||
}
|
||||
|
||||
[<Test>]
|
||||
let ``Can collect non-help args into positional args with Choice, and we parse on the way`` () =
|
||||
let getEnvVar (_ : string) = failwith "do not call"
|
||||
|
||||
FlagsIntoPositionalArgsIntChoice.parse' getEnvVar [ "3" ; "--a" ; "foo" ; "5" ; "--" ; "98" ]
|
||||
|> shouldEqual
|
||||
{
|
||||
A = "foo"
|
||||
GrabEverything = [ Choice1Of2 3 ; Choice1Of2 5 ; Choice2Of2 98 ]
|
||||
}
|
||||
|
||||
[<Test>]
|
||||
let ``Can refuse to collect non-help args with PositionalArgs false`` () =
|
||||
let getEnvVar (_ : string) = failwith "do not call"
|
||||
|
||||
let exc =
|
||||
Assert.Throws<exn> (fun () ->
|
||||
FlagsIntoPositionalArgs'.parse'
|
||||
getEnvVar
|
||||
[ "--a" ; "foo" ; "--b=false" ; "--c" ; "hi" ; "--" ; "--help" ]
|
||||
|> ignore<FlagsIntoPositionalArgs'>
|
||||
)
|
||||
|
||||
exc.Message
|
||||
|> shouldEqual """Unable to process argument --b=false as key --b and value false"""
|
||||
|
||||
let exc =
|
||||
Assert.Throws<exn> (fun () ->
|
||||
FlagsIntoPositionalArgs'.parse' getEnvVar [ "--a" ; "--b=false" ; "--c=hi" ; "--" ; "--help" ]
|
||||
|> ignore<FlagsIntoPositionalArgs'>
|
||||
)
|
||||
|
||||
// Again perhaps eccentric!
|
||||
// Again, we don't try to detect that the user has missed out the desired argument to `--a`.
|
||||
exc.Message
|
||||
|> shouldEqual """Unable to process argument --c=hi as key --c and value hi"""
|
@@ -43,7 +43,7 @@ module TestGift =
|
||||
member _.WithACard g message =
|
||||
$"%s{g} with a card saying '%s{message}'"
|
||||
|
||||
member _.Wrapped g paper = $"%s{g} wrapped in %A{paper} paper"
|
||||
member _.Wrapped g paper = $"%s{g} wrapped in %O{paper} paper"
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -9,18 +9,18 @@ open FsUnitTyped
|
||||
|
||||
[<TestFixture>]
|
||||
module TestBasePath =
|
||||
let replyWithUrl (message : HttpRequestMessage) : HttpResponseMessage Async =
|
||||
async {
|
||||
message.Method |> shouldEqual HttpMethod.Get
|
||||
let content = new StringContent (message.RequestUri.ToString ())
|
||||
let resp = new HttpResponseMessage (HttpStatusCode.OK)
|
||||
resp.Content <- content
|
||||
return resp
|
||||
}
|
||||
|
||||
[<Test>]
|
||||
let ``Base address is respected`` () =
|
||||
let proc (message : HttpRequestMessage) : HttpResponseMessage Async =
|
||||
async {
|
||||
message.Method |> shouldEqual HttpMethod.Get
|
||||
let content = new StringContent (message.RequestUri.ToString ())
|
||||
let resp = new HttpResponseMessage (HttpStatusCode.OK)
|
||||
resp.Content <- content
|
||||
return resp
|
||||
}
|
||||
|
||||
use client = HttpClientMock.makeNoUri proc
|
||||
use client = HttpClientMock.makeNoUri replyWithUrl
|
||||
let api = PureGymApi.make client
|
||||
|
||||
let observedUri = api.GetPathParam("param").Result
|
||||
@@ -28,38 +28,28 @@ module TestBasePath =
|
||||
|
||||
[<Test>]
|
||||
let ``Without a base address attr but with BaseAddress on client, request goes through`` () =
|
||||
let proc (message : HttpRequestMessage) : HttpResponseMessage Async =
|
||||
async {
|
||||
message.Method |> shouldEqual HttpMethod.Get
|
||||
let content = new StringContent (message.RequestUri.ToString ())
|
||||
let resp = new HttpResponseMessage (HttpStatusCode.OK)
|
||||
resp.Content <- content
|
||||
return resp
|
||||
}
|
||||
|
||||
use client = HttpClientMock.make (System.Uri "https://baseaddress.com") proc
|
||||
use client = HttpClientMock.make (Uri "https://baseaddress.com") replyWithUrl
|
||||
let api = ApiWithoutBaseAddress.make client
|
||||
|
||||
let observedUri = api.GetPathParam("param").Result
|
||||
observedUri |> shouldEqual "https://baseaddress.com/endpoint/param"
|
||||
|
||||
[<Test>]
|
||||
let ``Without a base address attr or BaseAddress on client, request throws`` () =
|
||||
let proc (message : HttpRequestMessage) : HttpResponseMessage Async =
|
||||
async {
|
||||
message.Method |> shouldEqual HttpMethod.Get
|
||||
let content = new StringContent (message.RequestUri.ToString ())
|
||||
let resp = new HttpResponseMessage (HttpStatusCode.OK)
|
||||
resp.Content <- content
|
||||
return resp
|
||||
}
|
||||
let ``Base address on client takes precedence`` () =
|
||||
use client = HttpClientMock.make (Uri "https://baseaddress.com") replyWithUrl
|
||||
let api = PureGymApi.make client
|
||||
|
||||
use client = HttpClientMock.makeNoUri proc
|
||||
let observedUri = api.GetPathParam("param").Result
|
||||
observedUri |> shouldEqual "https://baseaddress.com/endpoint/param"
|
||||
|
||||
[<Test>]
|
||||
let ``Without a base address attr or BaseAddress on client, request throws`` () =
|
||||
use client = HttpClientMock.makeNoUri replyWithUrl
|
||||
let api = ApiWithoutBaseAddress.make client
|
||||
|
||||
let observedExc =
|
||||
async {
|
||||
let! result = api.GetPathParam ("param") |> Async.AwaitTask |> Async.Catch
|
||||
let! result = api.GetPathParam "param" |> Async.AwaitTask |> Async.Catch
|
||||
|
||||
match result with
|
||||
| Choice1Of2 _ -> return failwith "test failure"
|
||||
@@ -78,3 +68,103 @@ module TestBasePath =
|
||||
observedExc.Message
|
||||
|> shouldEqual
|
||||
"No base address was supplied on the type, and no BaseAddress was on the HttpClient. (Parameter 'BaseAddress')"
|
||||
|
||||
[<Test>]
|
||||
let ``Relative base path, no base address, relative attribute`` () : unit =
|
||||
do
|
||||
use client = HttpClientMock.makeNoUri replyWithUrl
|
||||
let api = ApiWithBasePath.make client
|
||||
|
||||
let exc =
|
||||
Assert.Throws<AggregateException> (fun () -> api.GetPathParam("hi").Result |> ignore<string>)
|
||||
|
||||
exc.InnerException.Message
|
||||
|> shouldEqual
|
||||
"No base address was supplied on the type, and no BaseAddress was on the HttpClient. (Parameter 'BaseAddress')"
|
||||
|
||||
use client = HttpClientMock.make (Uri "https://whatnot.com/thing/") replyWithUrl
|
||||
let api = ApiWithBasePath.make client
|
||||
let result = api.GetPathParam("hi").Result
|
||||
result |> shouldEqual "https://whatnot.com/thing/foo/endpoint/hi"
|
||||
|
||||
[<Test>]
|
||||
let ``Relative base path, base address, relative attribute`` () : unit =
|
||||
use client = HttpClientMock.makeNoUri replyWithUrl
|
||||
let api = ApiWithBasePathAndAddress.make client
|
||||
let result = api.GetPathParam("hi").Result
|
||||
result |> shouldEqual "https://whatnot.com/thing/foo/endpoint/hi"
|
||||
|
||||
[<Test>]
|
||||
let ``Absolute base path, no base address, relative attribute`` () : unit =
|
||||
do
|
||||
use client = HttpClientMock.makeNoUri replyWithUrl
|
||||
let api = ApiWithAbsoluteBasePath.make client
|
||||
|
||||
let exc =
|
||||
Assert.Throws<AggregateException> (fun () -> api.GetPathParam("hi").Result |> ignore<string>)
|
||||
|
||||
exc.InnerException.Message
|
||||
|> shouldEqual
|
||||
"No base address was supplied on the type, and no BaseAddress was on the HttpClient. (Parameter 'BaseAddress')"
|
||||
|
||||
use client = HttpClientMock.make (Uri "https://whatnot.com/thing/") replyWithUrl
|
||||
let api = ApiWithAbsoluteBasePath.make client
|
||||
let result = api.GetPathParam("hi").Result
|
||||
result |> shouldEqual "https://whatnot.com/foo/endpoint/hi"
|
||||
|
||||
[<Test>]
|
||||
let ``Absolute base path, base address, relative attribute`` () : unit =
|
||||
use client = HttpClientMock.makeNoUri replyWithUrl
|
||||
let api = ApiWithAbsoluteBasePathAndAddress.make client
|
||||
let result = api.GetPathParam("hi").Result
|
||||
result |> shouldEqual "https://whatnot.com/foo/endpoint/hi"
|
||||
|
||||
[<Test>]
|
||||
let ``Relative base path, no base address, absolute attribute`` () : unit =
|
||||
do
|
||||
use client = HttpClientMock.makeNoUri replyWithUrl
|
||||
let api = ApiWithBasePathAndAbsoluteEndpoint.make client
|
||||
|
||||
let exc =
|
||||
Assert.Throws<AggregateException> (fun () -> api.GetPathParam("hi").Result |> ignore<string>)
|
||||
|
||||
exc.InnerException.Message
|
||||
|> shouldEqual
|
||||
"No base address was supplied on the type, and no BaseAddress was on the HttpClient. (Parameter 'BaseAddress')"
|
||||
|
||||
use client = HttpClientMock.make (Uri "https://whatnot.com/thing/") replyWithUrl
|
||||
let api = ApiWithBasePathAndAbsoluteEndpoint.make client
|
||||
let result = api.GetPathParam("hi").Result
|
||||
result |> shouldEqual "https://whatnot.com/endpoint/hi"
|
||||
|
||||
[<Test>]
|
||||
let ``Relative base path, base address, absolute attribute`` () : unit =
|
||||
use client = HttpClientMock.makeNoUri replyWithUrl
|
||||
let api = ApiWithBasePathAndAddressAndAbsoluteEndpoint.make client
|
||||
let result = api.GetPathParam("hi").Result
|
||||
result |> shouldEqual "https://whatnot.com/endpoint/hi"
|
||||
|
||||
[<Test>]
|
||||
let ``Absolute base path, no base address, absolute attribute`` () : unit =
|
||||
do
|
||||
use client = HttpClientMock.makeNoUri replyWithUrl
|
||||
let api = ApiWithAbsoluteBasePathAndAbsoluteEndpoint.make client
|
||||
|
||||
let exc =
|
||||
Assert.Throws<AggregateException> (fun () -> api.GetPathParam("hi").Result |> ignore<string>)
|
||||
|
||||
exc.InnerException.Message
|
||||
|> shouldEqual
|
||||
"No base address was supplied on the type, and no BaseAddress was on the HttpClient. (Parameter 'BaseAddress')"
|
||||
|
||||
use client = HttpClientMock.make (Uri "https://whatnot.com/thing/") replyWithUrl
|
||||
let api = ApiWithAbsoluteBasePathAndAbsoluteEndpoint.make client
|
||||
let result = api.GetPathParam("hi").Result
|
||||
result |> shouldEqual "https://whatnot.com/endpoint/hi"
|
||||
|
||||
[<Test>]
|
||||
let ``Absolute base path, base address, absolute attribute`` () : unit =
|
||||
use client = HttpClientMock.makeNoUri replyWithUrl
|
||||
let api = ApiWithAbsoluteBasePathAndAddressAndAbsoluteEndpoint.make client
|
||||
let result = api.GetPathParam("hi").Result
|
||||
result |> shouldEqual "https://whatnot.com/endpoint/hi"
|
||||
|
@@ -33,4 +33,4 @@ module TestPathParam =
|
||||
let api = PureGymApi.make client
|
||||
|
||||
api.GetPathParam("hello/world?(hi)").Result
|
||||
|> shouldEqual "hello%2fworld%3f(hi)"
|
||||
|> shouldEqual "hello%2Fworld%3F%28hi%29"
|
||||
|
@@ -89,6 +89,7 @@ module TestPureGymRestApi =
|
||||
let api = PureGymApi.make client
|
||||
|
||||
api.GetGymAttendance(requestedGym).Result |> shouldEqual expected
|
||||
api.GetGymAttendance'(requestedGym).Result |> shouldEqual expected
|
||||
|
||||
let memberCases =
|
||||
PureGymDtos.memberCases |> List.allPairs baseUris |> List.map TestCaseData
|
||||
@@ -234,6 +235,33 @@ module TestPureGymRestApi =
|
||||
|
||||
api.GetSessions(startDate, endDate).Result |> shouldEqual expected
|
||||
|
||||
[<TestCaseSource(nameof sessionsCases)>]
|
||||
let ``Test GetSessionsWithQuery``
|
||||
(baseUri : Uri, (startDate : DateOnly, (endDate : DateOnly, (json : string, expected : Sessions))))
|
||||
=
|
||||
let proc (message : HttpRequestMessage) : HttpResponseMessage Async =
|
||||
async {
|
||||
message.Method |> shouldEqual HttpMethod.Get
|
||||
|
||||
// This one is specified as being absolute, in its attribute on the IPureGymApi type
|
||||
let expectedUri =
|
||||
let fromDate = dateOnlyToString startDate
|
||||
let toDate = dateOnlyToString endDate
|
||||
$"https://example.com/v2/gymSessions/member?foo=1&fromDate=%s{fromDate}&toDate=%s{toDate}"
|
||||
|
||||
message.RequestUri.ToString () |> shouldEqual expectedUri
|
||||
|
||||
let content = new StringContent (json)
|
||||
let resp = new HttpResponseMessage (HttpStatusCode.OK)
|
||||
resp.Content <- content
|
||||
return resp
|
||||
}
|
||||
|
||||
use client = HttpClientMock.make baseUri proc
|
||||
let api = PureGymApi.make client
|
||||
|
||||
api.GetSessionsWithQuery(startDate, endDate).Result |> shouldEqual expected
|
||||
|
||||
[<Test>]
|
||||
let ``URI example`` () =
|
||||
let proc (message : HttpRequestMessage) : HttpResponseMessage Async =
|
||||
|
@@ -52,7 +52,13 @@ module TestVariableHeader =
|
||||
|
||||
api.GetPathParam("param").Result.Split "\n"
|
||||
|> Array.sort
|
||||
|> shouldEqual [| "Authorization: -99" ; "Header-Name: Header-Value" ; "X-Foo: 11" |]
|
||||
|> shouldEqual
|
||||
[|
|
||||
"Authorization: -99"
|
||||
"Header-Name: Header-Value"
|
||||
"Something-Else: val"
|
||||
"X-Foo: 11"
|
||||
|]
|
||||
|
||||
someHeaderCount.Value |> shouldEqual 11
|
||||
someOtherHeaderCount.Value |> shouldEqual -99
|
||||
@@ -98,11 +104,23 @@ module TestVariableHeader =
|
||||
|
||||
api.GetPathParam("param").Result.Split "\n"
|
||||
|> Array.sort
|
||||
|> shouldEqual [| "Authorization: -99" ; "Header-Name: Header-Value" ; "X-Foo: 11" |]
|
||||
|> shouldEqual
|
||||
[|
|
||||
"Authorization: -99"
|
||||
"Header-Name: Header-Value"
|
||||
"Something-Else: val"
|
||||
"X-Foo: 11"
|
||||
|]
|
||||
|
||||
api.GetPathParam("param").Result.Split "\n"
|
||||
|> Array.sort
|
||||
|> shouldEqual [| "Authorization: -98" ; "Header-Name: Header-Value" ; "X-Foo: 12" |]
|
||||
|> shouldEqual
|
||||
[|
|
||||
"Authorization: -98"
|
||||
"Header-Name: Header-Value"
|
||||
"Something-Else: val"
|
||||
"X-Foo: 12"
|
||||
|]
|
||||
|
||||
someHeaderCount.Value |> shouldEqual 12
|
||||
someOtherHeaderCount.Value |> shouldEqual -98
|
||||
|
@@ -87,8 +87,10 @@ module TestVaultClient =
|
||||
}
|
||||
}"""
|
||||
|
||||
[<Test>]
|
||||
let ``URI example`` () =
|
||||
[<TestCase 1>]
|
||||
[<TestCase 2>]
|
||||
[<TestCase 3>]
|
||||
let ``URI example`` (vaultClientId : int) =
|
||||
let proc (message : HttpRequestMessage) : HttpResponseMessage Async =
|
||||
async {
|
||||
message.Method |> shouldEqual HttpMethod.Get
|
||||
@@ -112,10 +114,25 @@ module TestVaultClient =
|
||||
}
|
||||
|
||||
use client = HttpClientMock.make (Uri "https://my-vault.com") proc
|
||||
let api = VaultClient.make client
|
||||
|
||||
let vaultResponse = api.GetJwt("role", "jwt").Result
|
||||
let value = api.GetSecret(vaultResponse, "path", "mount").Result
|
||||
let value =
|
||||
match vaultClientId with
|
||||
| 1 ->
|
||||
let api = VaultClient.make client
|
||||
let vaultResponse = api.GetJwt("role", "jwt").Result
|
||||
let value = api.GetSecret(vaultResponse, "path", "mount").Result
|
||||
value
|
||||
| 2 ->
|
||||
let api = VaultClientNonExtensionMethod.make client
|
||||
let vaultResponse = api.GetJwt("role", "jwt").Result
|
||||
let value = api.GetSecret(vaultResponse, "path", "mount").Result
|
||||
value
|
||||
| 3 ->
|
||||
let api = VaultClientExtensionMethod.make client
|
||||
let vaultResponse = api.GetJwt("role", "jwt").Result
|
||||
let value = api.GetSecret(vaultResponse, "path", "mount").Result
|
||||
value
|
||||
| _ -> failwith $"Unrecognised ID: %i{vaultClientId}"
|
||||
|
||||
value.Data
|
||||
|> Seq.toList
|
||||
@@ -168,3 +185,5 @@ module TestVaultClient =
|
||||
"key8_1", "https://example.com/data8/1"
|
||||
"key8_2", "https://example.com/data8/2"
|
||||
]
|
||||
|
||||
let _canSeePastExtensionMethod = VaultClientExtensionMethod.thisClashes
|
||||
|
@@ -1,6 +1,7 @@
|
||||
namespace WoofWare.Myriad.Plugins.Test
|
||||
|
||||
open System
|
||||
open System.Numerics
|
||||
open System.Text.Json.Nodes
|
||||
open ConsumePlugin
|
||||
open NUnit.Framework
|
||||
@@ -12,15 +13,62 @@ module TestExtensionMethod =
|
||||
[<Test>]
|
||||
let ``Parse via extension method`` () =
|
||||
let json =
|
||||
"""{"tinker": "job", "tailor": 3, "soldier": "https://example.com", "sailor": 3.1}"""
|
||||
"""{
|
||||
"alpha": "hello!",
|
||||
"bravo": "https://example.com",
|
||||
"charlie": 0.3341,
|
||||
"delta": 110033.4,
|
||||
"echo": -0.000993,
|
||||
"foxtrot": -999999999999,
|
||||
"golf": -123456789101112,
|
||||
"hotel": 18446744073709551615,
|
||||
"india": 99884,
|
||||
"juliette": 12223334,
|
||||
"kilo": -2147483642,
|
||||
"lima": 4294967293,
|
||||
"mike": -32767,
|
||||
"november": 65533,
|
||||
"oscar": -125,
|
||||
"papa": 253,
|
||||
"quebec": 254,
|
||||
"tango": -3,
|
||||
"uniform": 1004443.300988393349583009,
|
||||
"victor": "x",
|
||||
"whiskey": 123456123456123456123456123456123456123456
|
||||
}"""
|
||||
|> JsonNode.Parse
|
||||
|
||||
let expected =
|
||||
{
|
||||
Tinker = "job"
|
||||
Tailor = 3
|
||||
Soldier = Uri "https://example.com"
|
||||
Sailor = 3.1
|
||||
Alpha = "hello!"
|
||||
Bravo = Uri "https://example.com"
|
||||
Charlie = 0.3341
|
||||
Delta = 110033.4f
|
||||
Echo = -0.000993f
|
||||
Foxtrot = -999999999999.0
|
||||
Golf = -123456789101112L
|
||||
Hotel = 18446744073709551615UL
|
||||
India = 99884
|
||||
Juliette = 12223334u
|
||||
Kilo = -2147483642
|
||||
Lima = 4294967293u
|
||||
Mike = -32767s
|
||||
November = 65533us
|
||||
Oscar = -125y
|
||||
Papa = 253uy
|
||||
Quebec = 254uy
|
||||
Tango = -3y
|
||||
Uniform = 1004443.300988393349583009m
|
||||
Victor = 'x'
|
||||
Whiskey =
|
||||
let mutable i = BigInteger 0
|
||||
|
||||
for _ = 0 to 6 do
|
||||
i <- i * BigInteger 1000000 + BigInteger 123456
|
||||
|
||||
i
|
||||
}
|
||||
|
||||
ToGetExtensionMethod.jsonParse json |> shouldEqual expected
|
||||
let actual = ToGetExtensionMethod.jsonParse json
|
||||
|
||||
actual |> shouldEqual expected
|
||||
|
@@ -7,6 +7,8 @@ open FsUnitTyped
|
||||
|
||||
[<TestFixture>]
|
||||
module TestJsonParse =
|
||||
let _canSeePastExtensionMethod = ToGetExtensionMethod.thisModuleWouldClash
|
||||
|
||||
[<Test>]
|
||||
let ``Single example`` () =
|
||||
let s =
|
||||
@@ -47,3 +49,15 @@ module TestJsonParse =
|
||||
|
||||
let actual = s |> JsonNode.Parse |> InnerType.jsonParse
|
||||
actual |> shouldEqual expected
|
||||
|
||||
[<TestCase("thing", SomeEnum.Thing)>]
|
||||
[<TestCase("Thing", SomeEnum.Thing)>]
|
||||
[<TestCase("THING", SomeEnum.Thing)>]
|
||||
[<TestCase("blah", SomeEnum.Blah)>]
|
||||
[<TestCase("Blah", SomeEnum.Blah)>]
|
||||
[<TestCase("BLAH", SomeEnum.Blah)>]
|
||||
let ``Can deserialise enum`` (str : string, expected : SomeEnum) =
|
||||
sprintf "\"%s\"" str
|
||||
|> JsonNode.Parse
|
||||
|> SomeEnum.jsonParse
|
||||
|> shouldEqual expected
|
||||
|
@@ -2,10 +2,9 @@ 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 FsCheck.FSharp
|
||||
open Microsoft.FSharp.Reflection
|
||||
open NUnit.Framework
|
||||
open FsCheck
|
||||
open FsUnitTyped
|
||||
@@ -16,21 +15,21 @@ module TestJsonSerde =
|
||||
|
||||
let uriGen : Gen<Uri> =
|
||||
gen {
|
||||
let! suffix = Arb.generate<int>
|
||||
let! suffix = ArbMap.generate<int> ArbMap.defaults
|
||||
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! guid = ArbMap.generate<Guid> ArbMap.defaults
|
||||
let! mapKeys = Gen.listOf (ArbMap.generate<NonNull<string>> ArbMap.defaults)
|
||||
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>>
|
||||
Gen.listOf (ArbMap.generate<NonNull<string>> ArbMap.defaults)
|
||||
else
|
||||
Gen.constant []
|
||||
|
||||
@@ -51,13 +50,16 @@ module TestJsonSerde =
|
||||
|> List.map KeyValuePair
|
||||
|> Dictionary
|
||||
|
||||
let! readOnlyDictKeys = Gen.listOf Arb.generate<NonNull<string>>
|
||||
let! readOnlyDictKeys = Gen.listOf (ArbMap.generate<NonNull<string>> ArbMap.defaults)
|
||||
let readOnlyDictKeys = readOnlyDictKeys |> List.map _.Get |> List.distinct
|
||||
let! readOnlyDictValues = Gen.listOfLength readOnlyDictKeys.Length (Gen.listOf Arb.generate<char>)
|
||||
|
||||
let! readOnlyDictValues =
|
||||
Gen.listOfLength readOnlyDictKeys.Length (Gen.listOf (ArbMap.generate<char> ArbMap.defaults))
|
||||
|
||||
let readOnlyDict = List.zip readOnlyDictKeys readOnlyDictValues |> readOnlyDict
|
||||
|
||||
let! dictKeys = Gen.listOf uriGen
|
||||
let! dictValues = Gen.listOfLength dictKeys.Length Arb.generate<bool>
|
||||
let! dictValues = Gen.listOfLength dictKeys.Length (ArbMap.generate<bool> ArbMap.defaults)
|
||||
let dict = List.zip dictKeys dictValues |> dict
|
||||
|
||||
return
|
||||
@@ -72,13 +74,38 @@ module TestJsonSerde =
|
||||
|
||||
let outerGen : Gen<JsonRecordTypeWithBoth> =
|
||||
gen {
|
||||
let! a = Arb.generate<int>
|
||||
let! b = Arb.generate<NonNull<string>>
|
||||
let! c = Gen.listOf Arb.generate<int>
|
||||
let! a = ArbMap.generate<int> ArbMap.defaults
|
||||
let! b = ArbMap.generate<NonNull<string>> ArbMap.defaults
|
||||
let! c = Gen.listOf (ArbMap.generate<int> ArbMap.defaults)
|
||||
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>
|
||||
let! e = Gen.arrayOf (ArbMap.generate<NonNull<string>> ArbMap.defaults)
|
||||
let! arr = Gen.arrayOf (ArbMap.generate<int> ArbMap.defaults)
|
||||
let! byte = ArbMap.generate ArbMap.defaults
|
||||
let! sbyte = ArbMap.generate ArbMap.defaults
|
||||
let! i = ArbMap.generate ArbMap.defaults
|
||||
let! i32 = ArbMap.generate ArbMap.defaults
|
||||
let! i64 = ArbMap.generate ArbMap.defaults
|
||||
let! u = ArbMap.generate ArbMap.defaults
|
||||
let! u32 = ArbMap.generate ArbMap.defaults
|
||||
let! u64 = ArbMap.generate ArbMap.defaults
|
||||
|
||||
let! f =
|
||||
ArbMap.generate ArbMap.defaults
|
||||
|> Gen.filter (fun s -> Double.IsFinite (s / 1.0<measure>))
|
||||
|
||||
let! f32 =
|
||||
ArbMap.generate ArbMap.defaults
|
||||
|> Gen.filter (fun s -> Single.IsFinite (s / 1.0f<measure>))
|
||||
|
||||
let! single =
|
||||
ArbMap.generate ArbMap.defaults
|
||||
|> Gen.filter (fun s -> Single.IsFinite (s / 1.0f<measure>))
|
||||
|
||||
let! intMeasureOption = ArbMap.generate ArbMap.defaults
|
||||
let! intMeasureNullable = ArbMap.generate ArbMap.defaults
|
||||
let! someEnum = Gen.choose (0, 1)
|
||||
let! timestamp = ArbMap.generate ArbMap.defaults
|
||||
|
||||
return
|
||||
{
|
||||
@@ -87,7 +114,23 @@ module TestJsonSerde =
|
||||
C = c
|
||||
D = d
|
||||
E = e |> Array.map _.Get
|
||||
Arr = arr
|
||||
Byte = byte
|
||||
Sbyte = sbyte
|
||||
I = i
|
||||
I32 = i32
|
||||
I64 = i64
|
||||
U = u
|
||||
U32 = u32
|
||||
U64 = u64
|
||||
F = f
|
||||
F32 = f32
|
||||
Single = single
|
||||
IntMeasureOption = intMeasureOption
|
||||
IntMeasureNullable = intMeasureNullable
|
||||
Enum = enum<SomeEnum> someEnum
|
||||
Timestamp = timestamp
|
||||
Unit = ()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -105,6 +148,82 @@ module TestJsonSerde =
|
||||
|
||||
property |> Prop.forAll (Arb.fromGen outerGen) |> Check.QuickThrowOnFailure
|
||||
|
||||
[<Test>]
|
||||
let ``Single example of big record`` () =
|
||||
let guid = Guid.Parse "dfe24db5-9f8d-447b-8463-4c0bcf1166d5"
|
||||
|
||||
let data =
|
||||
{
|
||||
A = 3
|
||||
B = "hello!"
|
||||
C = [ 1 ; -9 ]
|
||||
D =
|
||||
{
|
||||
Thing = guid
|
||||
Map = Map.ofList []
|
||||
ReadOnlyDict = readOnlyDict []
|
||||
Dict = dict []
|
||||
ConcreteDict = Dictionary ()
|
||||
}
|
||||
E = [| "I'm-a-string" |]
|
||||
Arr = [| -18883 ; 9100 |]
|
||||
Byte = 87uy<measure>
|
||||
Sbyte = 89y<measure>
|
||||
I = 199993345<measure>
|
||||
I32 = -485832<measure>
|
||||
I64 = -13458625689L<measure>
|
||||
U = 458582u<measure>
|
||||
U32 = 857362147u<measure>
|
||||
U64 = 1234567892123414596UL<measure>
|
||||
F = 8833345667.1<measure>
|
||||
F32 = 1000.98f<measure>
|
||||
Single = 0.334f<measure>
|
||||
IntMeasureOption = Some 981<measure>
|
||||
IntMeasureNullable = Nullable -883<measure>
|
||||
Enum = enum<SomeEnum> 1
|
||||
Timestamp = DateTimeOffset (2024, 07, 01, 17, 54, 00, TimeSpan.FromHours 1.0)
|
||||
Unit = ()
|
||||
}
|
||||
|
||||
let expected =
|
||||
"""{
|
||||
"a": 3,
|
||||
"b": "hello!",
|
||||
"c": [1, -9],
|
||||
"d": {
|
||||
"it\u0027s-a-me": "dfe24db5-9f8d-447b-8463-4c0bcf1166d5",
|
||||
"map": {},
|
||||
"readOnlyDict": {},
|
||||
"dict": {},
|
||||
"concreteDict": {}
|
||||
},
|
||||
"e": ["I\u0027m-a-string"],
|
||||
"arr": [-18883, 9100],
|
||||
"byte": 87,
|
||||
"sbyte": 89,
|
||||
"i": 199993345,
|
||||
"i32": -485832,
|
||||
"i64": -13458625689,
|
||||
"u": 458582,
|
||||
"u32": 857362147,
|
||||
"u64": 1234567892123414596,
|
||||
"f": 8833345667.1,
|
||||
"f32": 1000.98,
|
||||
"single": 0.334,
|
||||
"intMeasureOption": 981,
|
||||
"intMeasureNullable": -883,
|
||||
"enum": 1,
|
||||
"timestamp": "2024-07-01T17:54:00.0000000\u002B01:00",
|
||||
"unit": {}
|
||||
}
|
||||
"""
|
||||
|> fun s -> s.ToCharArray ()
|
||||
|> Array.filter (fun c -> not (Char.IsWhiteSpace c))
|
||||
|> fun s -> new String (s)
|
||||
|
||||
JsonRecordTypeWithBoth.toJsonNode(data).ToJsonString () |> shouldEqual expected
|
||||
JsonRecordTypeWithBoth.jsonParse (JsonNode.Parse expected) |> shouldEqual data
|
||||
|
||||
[<Test>]
|
||||
let ``Guids are treated just like strings`` () =
|
||||
let guidStr = "b1e7496e-6e79-4158-8579-a01de355d3b2"
|
||||
@@ -124,3 +243,246 @@ module TestJsonSerde =
|
||||
|> shouldEqual (
|
||||
sprintf """{"it\u0027s-a-me":"%s","map":{},"readOnlyDict":{},"dict":{},"concreteDict":{}}""" guidStr
|
||||
)
|
||||
|
||||
type Generators =
|
||||
static member TestCase () =
|
||||
{ new Arbitrary<InnerTypeWithBoth>() with
|
||||
override x.Generator = innerGen 5
|
||||
}
|
||||
|
||||
let sanitiseInner (r : InnerTypeWithBoth) : InnerTypeWithBoth =
|
||||
{
|
||||
Thing = r.Thing
|
||||
Map = r.Map
|
||||
ReadOnlyDict = r.ReadOnlyDict
|
||||
Dict = r.Dict
|
||||
ConcreteDict = r.ConcreteDict
|
||||
}
|
||||
|
||||
let sanitiseRec (r : JsonRecordTypeWithBoth) : JsonRecordTypeWithBoth =
|
||||
{ r with
|
||||
B = if isNull r.B then "<null>" else r.B
|
||||
C =
|
||||
if Object.ReferenceEquals (r.C, (null : obj)) then
|
||||
[]
|
||||
else
|
||||
r.C
|
||||
D = sanitiseInner r.D
|
||||
E = if isNull r.E then [||] else r.E
|
||||
Arr =
|
||||
if Object.ReferenceEquals (r.Arr, (null : obj)) then
|
||||
[||]
|
||||
else
|
||||
r.Arr
|
||||
}
|
||||
|
||||
let duGen =
|
||||
gen {
|
||||
let! case = Gen.choose (0, 2)
|
||||
|
||||
match case with
|
||||
| 0 -> return FirstDu.EmptyCase
|
||||
| 1 ->
|
||||
let! s = ArbMap.generate<NonNull<string>> ArbMap.defaults
|
||||
return FirstDu.Case1 s.Get
|
||||
| 2 ->
|
||||
let! i = ArbMap.generate<int> ArbMap.defaults
|
||||
let! record = outerGen
|
||||
return FirstDu.Case2 (record, i)
|
||||
| _ -> return failwith $"unexpected: %i{case}"
|
||||
}
|
||||
|
||||
[<Test>]
|
||||
let ``Discriminated union works`` () =
|
||||
let property (du : FirstDu) : unit =
|
||||
du
|
||||
|> FirstDu.toJsonNode
|
||||
|> fun s -> s.ToJsonString ()
|
||||
|> JsonNode.Parse
|
||||
|> FirstDu.jsonParse
|
||||
|> shouldEqual du
|
||||
|
||||
property |> Prop.forAll (Arb.fromGen duGen) |> Check.QuickThrowOnFailure
|
||||
|
||||
[<Test>]
|
||||
let ``DU generator covers all cases`` () =
|
||||
let cases = FSharpType.GetUnionCases typeof<FirstDu>
|
||||
let counts = Array.zeroCreate<int> cases.Length
|
||||
|
||||
let decompose = FSharpValue.PreComputeUnionTagReader typeof<FirstDu>
|
||||
|
||||
let mutable i = 0
|
||||
|
||||
let property (du : FirstDu) =
|
||||
let tag = decompose du
|
||||
counts.[tag] <- counts.[tag] + 1
|
||||
i <- i + 1
|
||||
true
|
||||
|
||||
Check.One (Config.Quick, Prop.forAll (Arb.fromGen duGen) property)
|
||||
|
||||
for i in counts do
|
||||
i |> shouldBeGreaterThan 0
|
||||
|
||||
let dict<'a, 'b when 'a : equality> (xs : ('a * 'b) seq) : Dictionary<'a, 'b> =
|
||||
let result = Dictionary ()
|
||||
|
||||
for k, v in xs do
|
||||
result.Add (k, v)
|
||||
|
||||
result
|
||||
|
||||
let inline makeJsonArr< ^t, ^u when ^u : (static member op_Implicit : ^t -> JsonNode) and ^u :> JsonNode>
|
||||
(arr : ^t seq)
|
||||
: JsonNode
|
||||
=
|
||||
let result = JsonArray ()
|
||||
|
||||
for a in arr do
|
||||
result.Add a
|
||||
|
||||
result :> JsonNode
|
||||
|
||||
let normalise (d : Dictionary<'a, 'b>) : ('a * 'b) list =
|
||||
d |> Seq.map (fun (KeyValue (a, b)) -> a, b) |> Seq.toList |> List.sortBy fst
|
||||
|
||||
[<Test>]
|
||||
let ``Can collect extension data`` () =
|
||||
let str =
|
||||
"""{
|
||||
"message": { "header": "hi", "value": "bye" },
|
||||
"something": 3,
|
||||
"arr": ["egg", "toast"],
|
||||
"str": "whatnot"
|
||||
}"""
|
||||
|> JsonNode.Parse
|
||||
|
||||
let expected =
|
||||
{
|
||||
Rest =
|
||||
[
|
||||
"something", JsonNode.op_Implicit 3
|
||||
"arr", makeJsonArr [| "egg" ; "toast" |]
|
||||
"str", JsonNode.op_Implicit "whatnot"
|
||||
]
|
||||
|> dict
|
||||
Message =
|
||||
Some
|
||||
{
|
||||
Header = "hi"
|
||||
Value = "bye"
|
||||
}
|
||||
}
|
||||
|
||||
let actual = CollectRemaining.jsonParse str
|
||||
|
||||
actual.Message |> shouldEqual expected.Message
|
||||
|
||||
normalise actual.Rest
|
||||
|> List.map (fun (k, v) -> k, v.ToJsonString ())
|
||||
|> shouldEqual (normalise expected.Rest |> List.map (fun (k, v) -> k, v.ToJsonString ()))
|
||||
|
||||
[<Test>]
|
||||
let ``Can write out extension data`` () =
|
||||
let expected =
|
||||
"""{"message":{"header":"hi","value":"bye"},"something":3,"arr":["egg","toast"],"str":"whatnot"}"""
|
||||
|
||||
let toWrite =
|
||||
{
|
||||
Rest =
|
||||
[
|
||||
"something", JsonNode.op_Implicit 3
|
||||
"arr", makeJsonArr [| "egg" ; "toast" |]
|
||||
"str", JsonNode.op_Implicit "whatnot"
|
||||
]
|
||||
|> dict
|
||||
Message =
|
||||
Some
|
||||
{
|
||||
Header = "hi"
|
||||
Value = "bye"
|
||||
}
|
||||
}
|
||||
|
||||
let actual = CollectRemaining.toJsonNode toWrite |> fun s -> s.ToJsonString ()
|
||||
|
||||
actual |> shouldEqual expected
|
||||
|
||||
[<Test>]
|
||||
let ``Can collect extension data, nested`` () =
|
||||
let str =
|
||||
"""{
|
||||
"thing": 99,
|
||||
"baz": -123,
|
||||
"remaining": {
|
||||
"message": { "header": "hi", "value": "bye" },
|
||||
"something": 3,
|
||||
"arr": ["egg", "toast"],
|
||||
"str": "whatnot"
|
||||
}
|
||||
}"""
|
||||
|> JsonNode.Parse
|
||||
|
||||
let expected : OuterCollectRemaining =
|
||||
{
|
||||
Remaining =
|
||||
{
|
||||
Message =
|
||||
Some
|
||||
{
|
||||
Header = "hi"
|
||||
Value = "bye"
|
||||
}
|
||||
Rest =
|
||||
[
|
||||
"something", JsonNode.op_Implicit 3
|
||||
"arr", makeJsonArr [| "egg" ; "toast" |]
|
||||
"str", JsonNode.op_Implicit "whatnot"
|
||||
]
|
||||
|> dict
|
||||
}
|
||||
Others = [ "thing", 99 ; "baz", -123 ] |> dict
|
||||
}
|
||||
|
||||
let actual = OuterCollectRemaining.jsonParse str
|
||||
|
||||
normalise actual.Others |> shouldEqual (normalise expected.Others)
|
||||
|
||||
let actual = actual.Remaining
|
||||
let expected = expected.Remaining
|
||||
|
||||
actual.Message |> shouldEqual expected.Message
|
||||
|
||||
normalise actual.Rest
|
||||
|> List.map (fun (k, v) -> k, v.ToJsonString ())
|
||||
|> shouldEqual (normalise expected.Rest |> List.map (fun (k, v) -> k, v.ToJsonString ()))
|
||||
|
||||
[<Test>]
|
||||
let ``Can write out extension data, nested`` () =
|
||||
let expected =
|
||||
"""{"thing":99,"baz":-123,"remaining":{"message":{"header":"hi","value":"bye"},"something":3,"arr":["egg","toast"],"str":"whatnot"}}"""
|
||||
|
||||
let toWrite : OuterCollectRemaining =
|
||||
{
|
||||
Others = [ "thing", 99 ; "baz", -123 ] |> dict
|
||||
Remaining =
|
||||
{
|
||||
Rest =
|
||||
[
|
||||
"something", JsonNode.op_Implicit 3
|
||||
"arr", makeJsonArr [| "egg" ; "toast" |]
|
||||
"str", JsonNode.op_Implicit "whatnot"
|
||||
]
|
||||
|> dict
|
||||
Message =
|
||||
Some
|
||||
{
|
||||
Header = "hi"
|
||||
Value = "bye"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let actual = OuterCollectRemaining.toJsonNode toWrite |> fun s -> s.ToJsonString ()
|
||||
|
||||
actual |> shouldEqual expected
|
||||
|
@@ -34,3 +34,16 @@ module TestMockGenerator =
|
||||
mock.Mem1 3 'a' |> shouldEqual "aaa"
|
||||
mock.Mem2 (3, "hi") 'a' |> shouldEqual "hiahiahi"
|
||||
mock.Mem3 (3, "hi") 'a' |> shouldEqual "hiahiahi"
|
||||
|
||||
[<Test>]
|
||||
let ``Example of use: properties`` () =
|
||||
let mock : TypeWithProperties =
|
||||
{ TypeWithPropertiesMock.Empty with
|
||||
Mem1 = fun i -> async { return Option.toArray i }
|
||||
Prop1 = fun () -> 44
|
||||
}
|
||||
:> _
|
||||
|
||||
mock.Mem1 (Some "hi") |> Async.RunSynchronously |> shouldEqual [| "hi" |]
|
||||
|
||||
mock.Prop1 |> shouldEqual 44
|
||||
|
@@ -0,0 +1,36 @@
|
||||
namespace WoofWare.Myriad.Plugins.Test
|
||||
|
||||
open System
|
||||
open SomeNamespace
|
||||
open NUnit.Framework
|
||||
open FsUnitTyped
|
||||
|
||||
[<TestFixture>]
|
||||
module TestMockGeneratorNoAttr =
|
||||
|
||||
[<Test>]
|
||||
let ``Example of use: IPublicType`` () =
|
||||
let mock : IPublicTypeNoAttr =
|
||||
{ PublicTypeNoAttrMock.Empty with
|
||||
Mem1 = fun (s, count) -> List.replicate count s
|
||||
}
|
||||
:> _
|
||||
|
||||
let _ =
|
||||
Assert.Throws<NotImplementedException> (fun () -> mock.Mem2 "hi" |> ignore<int>)
|
||||
|
||||
mock.Mem1 ("hi", 3) |> shouldEqual [ "hi" ; "hi" ; "hi" ]
|
||||
|
||||
[<Test>]
|
||||
let ``Example of use: curried args`` () =
|
||||
let mock : CurriedNoAttr<_> =
|
||||
{ CurriedNoAttrMock.Empty () with
|
||||
Mem1 = fun i c -> Array.replicate i c |> String
|
||||
Mem2 = fun (i, s) c -> String.concat $"%c{c}" (List.replicate i s)
|
||||
Mem3 = fun (i, s) c -> String.concat $"%c{c}" (List.replicate i s)
|
||||
}
|
||||
:> _
|
||||
|
||||
mock.Mem1 3 'a' |> shouldEqual "aaa"
|
||||
mock.Mem2 (3, "hi") 'a' |> shouldEqual "hiahiahi"
|
||||
mock.Mem3 (3, "hi") 'a' |> shouldEqual "hiahiahi"
|
@@ -12,7 +12,8 @@ module TestSurface =
|
||||
let ``Ensure API surface has not been modified`` () = ApiSurface.assertIdentical assembly
|
||||
|
||||
[<Test>]
|
||||
let ``Check version against remote`` () =
|
||||
// https://github.com/nunit/nunit3-vs-adapter/issues/876
|
||||
let CheckVersionAgainstRemote () =
|
||||
MonotonicVersion.validate assembly "WoofWare.Myriad.Plugins"
|
||||
|
||||
[<Test ; Explicit>]
|
||||
|
84
WoofWare.Myriad.Plugins.Test/TestSwagger/TestSwaggerParse.fs
Normal file
84
WoofWare.Myriad.Plugins.Test/TestSwagger/TestSwaggerParse.fs
Normal file
@@ -0,0 +1,84 @@
|
||||
namespace WoofWare.Myriad.Plugins.Test
|
||||
|
||||
open System.Text.Json.Nodes
|
||||
open NUnit.Framework
|
||||
open FsUnitTyped
|
||||
open WoofWare.Myriad.Plugins.SwaggerV2
|
||||
|
||||
[<TestFixture>]
|
||||
module TestSwaggerParse =
|
||||
[<Test>]
|
||||
let ``Can parse parameters`` () : unit =
|
||||
let s =
|
||||
"""{
|
||||
"tags": [
|
||||
"organization"
|
||||
],
|
||||
"summary": "Check if a user is a member of an organization",
|
||||
"operationId": "orgIsMember",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "name of the organization",
|
||||
"name": "org",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "username of the user",
|
||||
"name": "username",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"204": {
|
||||
"description": "user is a member"
|
||||
},
|
||||
"303": {
|
||||
"description": "redirection to /orgs/{org}/public_members/{username}"
|
||||
},
|
||||
"404": {
|
||||
"description": "user is not a member"
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
|> JsonNode.Parse
|
||||
|
||||
s.AsObject ()
|
||||
|> SwaggerEndpoint.Parse
|
||||
|> shouldEqual
|
||||
{
|
||||
Consumes = None
|
||||
Produces = None
|
||||
Tags = [ "organization" ]
|
||||
Summary = "Check if a user is a member of an organization"
|
||||
OperationId = OperationId "orgIsMember"
|
||||
Parameters =
|
||||
[
|
||||
{
|
||||
Type = Definition.String
|
||||
Description = Some "name of the organization"
|
||||
Name = "org"
|
||||
In = ParameterIn.Path "org"
|
||||
Required = Some true
|
||||
}
|
||||
{
|
||||
Type = Definition.String
|
||||
Description = Some "username of the user"
|
||||
Name = "username"
|
||||
In = ParameterIn.Path "username"
|
||||
Required = Some true
|
||||
}
|
||||
]
|
||||
|> Some
|
||||
Responses =
|
||||
[
|
||||
204, Definition.Unspecified
|
||||
303, Definition.Unspecified
|
||||
404, Definition.Unspecified
|
||||
]
|
||||
|> Map.ofList
|
||||
}
|
@@ -1,9 +1,15 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<IsPackable>false</IsPackable>
|
||||
<IsTestProject>true</IsTestProject>
|
||||
<!--
|
||||
Known high severity vulnerability
|
||||
I have not yet seen a single instance where I care about this warning
|
||||
-->
|
||||
<NoWarn>$(NoWarn),NU1903</NoWarn>
|
||||
<TestingPlatformDotnetTestSupport>true</TestingPlatformDotnetTestSupport>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@@ -21,25 +27,27 @@
|
||||
<Compile Include="TestHttpClient\TestVaultClient.fs" />
|
||||
<Compile Include="TestHttpClient\TestVariableHeader.fs" />
|
||||
<Compile Include="TestMockGenerator\TestMockGenerator.fs" />
|
||||
<Compile Include="TestMockGenerator\TestMockGeneratorNoAttr.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="TestArgParser\TestArgParser.fs" />
|
||||
<Compile Include="TestSwagger\TestSwaggerParse.fs" />
|
||||
<Compile Include="TestRemoveOptions.fs"/>
|
||||
<Compile Include="TestSurface.fs"/>
|
||||
<None Include="../.github/workflows/dotnet.yaml" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="ApiSurface" Version="4.0.30"/>
|
||||
<PackageReference Include="FsCheck" Version="2.16.6"/>
|
||||
<PackageReference Include="FsUnit" Version="6.0.0"/>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.9.0"/>
|
||||
<PackageReference Include="NUnit" Version="4.0.1"/>
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="4.5.0"/>
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.0"/>
|
||||
<PackageReference Include="ApiSurface" Version="4.1.21"/>
|
||||
<PackageReference Include="FsCheck" Version="3.3.0"/>
|
||||
<PackageReference Include="FsUnit" Version="7.0.1"/>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1"/>
|
||||
<PackageReference Include="NUnit" Version="4.3.2"/>
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="5.0.0"/>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
201
WoofWare.Myriad.Plugins/ApacheLicence.txt
Normal file
201
WoofWare.Myriad.Plugins/ApacheLicence.txt
Normal file
@@ -0,0 +1,201 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
1824
WoofWare.Myriad.Plugins/ArgParserGenerator.fs
Normal file
1824
WoofWare.Myriad.Plugins/ArgParserGenerator.fs
Normal file
File diff suppressed because it is too large
Load Diff
6
WoofWare.Myriad.Plugins/AssemblyInfo.fs
Normal file
6
WoofWare.Myriad.Plugins/AssemblyInfo.fs
Normal file
@@ -0,0 +1,6 @@
|
||||
module internal WoofWare.Myriad.Plugins.AssemblyInfo
|
||||
|
||||
open System.Runtime.CompilerServices
|
||||
|
||||
[<assembly : InternalsVisibleTo("WoofWare.Myriad.Plugins.Test")>]
|
||||
do ()
|
@@ -1,74 +1,8 @@
|
||||
namespace WoofWare.Myriad.Plugins
|
||||
|
||||
open Fantomas.FCS.Syntax
|
||||
open Fantomas.FCS.SyntaxTrivia
|
||||
open Fantomas.FCS.Text.Range
|
||||
open Fantomas.FCS.Xml
|
||||
open Myriad.Core.AstExtensions
|
||||
|
||||
type internal ParameterInfo =
|
||||
{
|
||||
Attributes : SynAttribute list
|
||||
IsOptional : bool
|
||||
Id : Ident option
|
||||
Type : SynType
|
||||
}
|
||||
|
||||
type internal TupledArg =
|
||||
{
|
||||
HasParen : bool
|
||||
Args : ParameterInfo list
|
||||
}
|
||||
|
||||
type internal MemberInfo =
|
||||
{
|
||||
ReturnType : SynType
|
||||
Accessibility : SynAccess option
|
||||
/// Each element of this list is a list of args in a tuple, or just one arg if not a tuple.
|
||||
Args : TupledArg list
|
||||
Identifier : Ident
|
||||
Attributes : SynAttribute list
|
||||
XmlDoc : PreXmlDoc option
|
||||
IsInline : bool
|
||||
IsMutable : bool
|
||||
}
|
||||
|
||||
[<RequireQualifiedAccess>]
|
||||
type internal PropertyAccessors =
|
||||
| Get
|
||||
| Set
|
||||
| GetSet
|
||||
|
||||
type internal PropertyInfo =
|
||||
{
|
||||
Type : SynType
|
||||
Accessibility : SynAccess option
|
||||
Attributes : SynAttribute list
|
||||
XmlDoc : PreXmlDoc option
|
||||
Accessors : PropertyAccessors
|
||||
IsInline : bool
|
||||
Identifier : Ident
|
||||
}
|
||||
|
||||
type internal InterfaceType =
|
||||
{
|
||||
Attributes : SynAttribute list
|
||||
Name : LongIdent
|
||||
Members : MemberInfo list
|
||||
Properties : PropertyInfo list
|
||||
Generics : SynTyparDecls option
|
||||
Accessibility : SynAccess option
|
||||
}
|
||||
|
||||
type internal RecordType =
|
||||
{
|
||||
Name : Ident
|
||||
Fields : SynField seq
|
||||
Members : SynMemberDefns option
|
||||
XmlDoc : PreXmlDoc option
|
||||
Generics : SynTyparDecls option
|
||||
Accessibility : SynAccess option
|
||||
}
|
||||
open WoofWare.Whippet.Fantomas
|
||||
|
||||
/// Anything that is part of an ADT.
|
||||
/// A record is a product of stuff; this type represents one of those stuffs.
|
||||
@@ -97,89 +31,23 @@ type internal AdtProduct =
|
||||
[<RequireQualifiedAccess>]
|
||||
module internal AstHelper =
|
||||
|
||||
let instantiateRecord (fields : (RecordFieldName * SynExpr option) list) : SynExpr =
|
||||
let fields =
|
||||
fields
|
||||
|> List.map (fun (rfn, synExpr) -> SynExprRecordField (rfn, Some range0, synExpr, None))
|
||||
|
||||
SynExpr.Record (None, None, fields, range0)
|
||||
let isEnum (SynTypeDefn.SynTypeDefn (_, repr, _, _, _, _)) : bool =
|
||||
match repr with
|
||||
| SynTypeDefnRepr.Simple (SynTypeDefnSimpleRepr.Enum _, _) -> true
|
||||
| _ -> false
|
||||
|
||||
let defineRecordType (record : RecordType) : SynTypeDefn =
|
||||
let repr =
|
||||
SynTypeDefnRepr.Simple (SynTypeDefnSimpleRepr.Record (None, Seq.toList record.Fields, range0), range0)
|
||||
|
||||
let name =
|
||||
SynComponentInfo.Create (
|
||||
[ record.Name ],
|
||||
?xmldoc = record.XmlDoc,
|
||||
?parameters = record.Generics,
|
||||
access = record.Accessibility
|
||||
)
|
||||
SynComponentInfo.create record.Name
|
||||
|> SynComponentInfo.setAccessibility record.TypeAccessibility
|
||||
|> match record.XmlDoc with
|
||||
| None -> id
|
||||
| Some doc -> SynComponentInfo.withDocString doc
|
||||
|> SynComponentInfo.setGenerics record.Generics
|
||||
|
||||
let trivia : SynTypeDefnTrivia =
|
||||
{
|
||||
LeadingKeyword = SynTypeDefnLeadingKeyword.Type range0
|
||||
EqualsRange = Some range0
|
||||
WithKeyword = Some range0
|
||||
}
|
||||
|
||||
SynTypeDefn (name, repr, defaultArg record.Members SynMemberDefns.Empty, None, range0, trivia)
|
||||
|
||||
let isOptionIdent (ident : SynLongIdent) : bool =
|
||||
match ident.LongIdent with
|
||||
| [ i ] when System.String.Equals (i.idText, "option", System.StringComparison.OrdinalIgnoreCase) -> true
|
||||
// TODO: consider Microsoft.FSharp.Option or whatever it is
|
||||
| _ -> false
|
||||
|
||||
let isListIdent (ident : SynLongIdent) : bool =
|
||||
match ident.LongIdent with
|
||||
| [ i ] when System.String.Equals (i.idText, "list", System.StringComparison.OrdinalIgnoreCase) -> true
|
||||
// TODO: consider FSharpList or whatever it is
|
||||
| _ -> false
|
||||
|
||||
let isArrayIdent (ident : SynLongIdent) : bool =
|
||||
match ident.LongIdent with
|
||||
| [ i ] when
|
||||
System.String.Equals (i.idText, "array", System.StringComparison.OrdinalIgnoreCase)
|
||||
|| System.String.Equals (i.idText, "[]", System.StringComparison.Ordinal)
|
||||
->
|
||||
true
|
||||
| _ -> false
|
||||
|
||||
let isResponseIdent (ident : SynLongIdent) : bool =
|
||||
match ident.LongIdent |> List.map _.idText with
|
||||
| [ "Response" ]
|
||||
| [ "RestEase" ; "Response" ] -> true
|
||||
| _ -> false
|
||||
|
||||
let isMapIdent (ident : SynLongIdent) : bool =
|
||||
match ident.LongIdent |> List.map _.idText with
|
||||
| [ "Map" ] -> true
|
||||
| _ -> false
|
||||
|
||||
let isReadOnlyDictionaryIdent (ident : SynLongIdent) : bool =
|
||||
match ident.LongIdent |> List.map _.idText with
|
||||
| [ "IReadOnlyDictionary" ]
|
||||
| [ "Generic" ; "IReadOnlyDictionary" ]
|
||||
| [ "Collections" ; "Generic" ; "IReadOnlyDictionary" ]
|
||||
| [ "System" ; "Collections" ; "Generic" ; "IReadOnlyDictionary" ] -> true
|
||||
| _ -> false
|
||||
|
||||
let isDictionaryIdent (ident : SynLongIdent) : bool =
|
||||
match ident.LongIdent |> List.map _.idText with
|
||||
| [ "Dictionary" ]
|
||||
| [ "Generic" ; "Dictionary" ]
|
||||
| [ "Collections" ; "Generic" ; "Dictionary" ]
|
||||
| [ "System" ; "Collections" ; "Generic" ; "Dictionary" ] -> true
|
||||
| _ -> false
|
||||
|
||||
let isIDictionaryIdent (ident : SynLongIdent) : bool =
|
||||
match ident.LongIdent |> List.map _.idText with
|
||||
| [ "IDictionary" ]
|
||||
| [ "Generic" ; "IDictionary" ]
|
||||
| [ "Collections" ; "Generic" ; "IDictionary" ]
|
||||
| [ "System" ; "Collections" ; "Generic" ; "IDictionary" ] -> true
|
||||
| _ -> false
|
||||
SynTypeDefnRepr.recordWithAccess record.ImplAccessibility (Seq.toList record.Fields)
|
||||
|> SynTypeDefn.create name
|
||||
|> SynTypeDefn.withMemberDefns (defaultArg record.Members SynMemberDefns.Empty)
|
||||
|
||||
let rec private extractOpensFromDecl (moduleDecls : SynModuleDecl list) : SynOpenDeclTarget list =
|
||||
moduleDecls
|
||||
@@ -201,12 +69,12 @@ module internal AstHelper =
|
||||
| SynType.Paren (inner, _) ->
|
||||
let result, _ = convertSigParam inner
|
||||
result, true
|
||||
| SynType.LongIdent ident ->
|
||||
| SynType.LongIdent (SynLongIdent.SynLongIdent (ident, _, _)) ->
|
||||
{
|
||||
Attributes = []
|
||||
IsOptional = false
|
||||
Id = None
|
||||
Type = SynType.CreateLongIdent ident
|
||||
Type = SynType.createLongIdent ident
|
||||
},
|
||||
false
|
||||
| SynType.SignatureParameter (attrs, opt, id, usedType, _) ->
|
||||
@@ -224,7 +92,7 @@ module internal AstHelper =
|
||||
Attributes = []
|
||||
IsOptional = false
|
||||
Id = None
|
||||
Type = SynType.Var (typar, range0)
|
||||
Type = SynType.var typar
|
||||
},
|
||||
false
|
||||
| _ -> failwithf "expected SignatureParameter, got: %+A" ty
|
||||
@@ -253,10 +121,6 @@ module internal AstHelper =
|
||||
}
|
||||
| _ -> failwithf "Didn't have alternating type-and-star in interface member definition: %+A" tupleType
|
||||
|
||||
let toFun (inputs : SynType list) (ret : SynType) : SynType =
|
||||
(ret, List.rev inputs)
|
||||
||> List.fold (fun ty input -> SynType.CreateFun (input, ty))
|
||||
|
||||
/// Returns the args (where these are tuple types if curried) in order, and the return type.
|
||||
let rec getType (ty : SynType) : (SynType * bool) list * SynType =
|
||||
match ty with
|
||||
@@ -269,7 +133,7 @@ module internal AstHelper =
|
||||
| SynType.Paren (argType, _) -> getType argType, true
|
||||
| _ -> getType argType, false
|
||||
|
||||
((toFun (List.map fst inputArgs) inputRet), hasParen) :: args, ret
|
||||
((SynType.toFun (List.map fst inputArgs) inputRet), hasParen) :: args, ret
|
||||
| _ -> [], ty
|
||||
|
||||
let private parseMember (slotSig : SynValSig) (flags : SynMemberFlags) : Choice<MemberInfo, PropertyInfo> =
|
||||
@@ -326,7 +190,7 @@ module internal AstHelper =
|
||||
Attributes = []
|
||||
IsOptional = false
|
||||
Id = None
|
||||
Type = SynType.CreateLongIdent (SynLongIdent.CreateFromLongIdent ident)
|
||||
Type = SynType.createLongIdent ident
|
||||
}
|
||||
|> List.singleton
|
||||
}
|
||||
@@ -338,11 +202,22 @@ module internal AstHelper =
|
||||
Attributes = []
|
||||
IsOptional = false
|
||||
Id = None
|
||||
Type = SynType.Var (typar, range0)
|
||||
Type = SynType.var typar
|
||||
}
|
||||
|> List.singleton
|
||||
}
|
||||
| arg ->
|
||||
{
|
||||
HasParen = false
|
||||
Args =
|
||||
{
|
||||
Attributes = []
|
||||
IsOptional = false
|
||||
Id = None
|
||||
Type = arg
|
||||
}
|
||||
|> List.singleton
|
||||
}
|
||||
| _ -> failwith $"Unrecognised args in interface method declaration: %+A{args}"
|
||||
|> fun ty ->
|
||||
{ ty with
|
||||
HasParen = ty.HasParen || hasParen
|
||||
@@ -386,22 +261,26 @@ module internal AstHelper =
|
||||
|
||||
let attrs = attrs |> List.collect (fun s -> s.Attributes)
|
||||
|
||||
let members, properties =
|
||||
let members, inherits =
|
||||
match synTypeDefnRepr with
|
||||
| SynTypeDefnRepr.ObjectModel (_kind, members, _) ->
|
||||
members
|
||||
|> List.map (fun defn ->
|
||||
match defn with
|
||||
| SynMemberDefn.AbstractSlot (slotSig, flags, _, _) -> parseMember slotSig flags
|
||||
| SynMemberDefn.AbstractSlot (slotSig, flags, _, _) -> Choice1Of2 (parseMember slotSig flags)
|
||||
| SynMemberDefn.Inherit (baseType, _asIdent, _) -> Choice2Of2 baseType
|
||||
| _ -> failwith $"Unrecognised member definition: %+A{defn}"
|
||||
)
|
||||
| _ -> failwith $"Unrecognised SynTypeDefnRepr for an interface type: %+A{synTypeDefnRepr}"
|
||||
|> List.partitionChoice
|
||||
|
||||
let members, properties = members |> List.partitionChoice
|
||||
|
||||
{
|
||||
Members = members
|
||||
Properties = properties
|
||||
Name = interfaceName
|
||||
Inherits = inherits
|
||||
Attributes = attrs
|
||||
Generics = typars
|
||||
Accessibility = accessibility
|
||||
@@ -477,176 +356,3 @@ module internal AstHelper =
|
||||
}
|
||||
)
|
||||
| _ -> failwithf "Failed to get record elements for type that was: %+A" repr
|
||||
|
||||
[<AutoOpen>]
|
||||
module internal SynTypePatterns =
|
||||
let (|OptionType|_|) (fieldType : SynType) =
|
||||
match fieldType with
|
||||
| SynType.App (SynType.LongIdent ident, _, [ innerType ], _, _, _, _) when AstHelper.isOptionIdent ident ->
|
||||
Some innerType
|
||||
| _ -> None
|
||||
|
||||
let (|ListType|_|) (fieldType : SynType) =
|
||||
match fieldType with
|
||||
| SynType.App (SynType.LongIdent ident, _, [ innerType ], _, _, _, _) when AstHelper.isListIdent ident ->
|
||||
Some innerType
|
||||
| _ -> None
|
||||
|
||||
let (|ArrayType|_|) (fieldType : SynType) =
|
||||
match fieldType with
|
||||
| SynType.App (SynType.LongIdent ident, _, [ innerType ], _, _, _, _) when AstHelper.isArrayIdent ident ->
|
||||
Some innerType
|
||||
| SynType.Array (1, innerType, _) -> Some innerType
|
||||
| _ -> None
|
||||
|
||||
let (|RestEaseResponseType|_|) (fieldType : SynType) =
|
||||
match fieldType with
|
||||
| SynType.App (SynType.LongIdent ident, _, [ innerType ], _, _, _, _) when AstHelper.isResponseIdent ident ->
|
||||
Some innerType
|
||||
| _ -> None
|
||||
|
||||
let (|DictionaryType|_|) (fieldType : SynType) =
|
||||
match fieldType with
|
||||
| SynType.App (SynType.LongIdent ident, _, [ key ; value ], _, _, _, _) when AstHelper.isDictionaryIdent ident ->
|
||||
Some (key, value)
|
||||
| _ -> None
|
||||
|
||||
let (|IDictionaryType|_|) (fieldType : SynType) =
|
||||
match fieldType with
|
||||
| SynType.App (SynType.LongIdent ident, _, [ key ; value ], _, _, _, _) when AstHelper.isIDictionaryIdent ident ->
|
||||
Some (key, value)
|
||||
| _ -> None
|
||||
|
||||
let (|IReadOnlyDictionaryType|_|) (fieldType : SynType) =
|
||||
match fieldType with
|
||||
| SynType.App (SynType.LongIdent ident, _, [ key ; value ], _, _, _, _) when
|
||||
AstHelper.isReadOnlyDictionaryIdent ident
|
||||
->
|
||||
Some (key, value)
|
||||
| _ -> None
|
||||
|
||||
let (|MapType|_|) (fieldType : SynType) =
|
||||
match fieldType with
|
||||
| SynType.App (SynType.LongIdent ident, _, [ key ; value ], _, _, _, _) when AstHelper.isMapIdent ident ->
|
||||
Some (key, value)
|
||||
| _ -> None
|
||||
|
||||
/// Returns the string name of the type.
|
||||
let (|PrimitiveType|_|) (fieldType : SynType) =
|
||||
match fieldType with
|
||||
| SynType.LongIdent ident ->
|
||||
match ident.LongIdent with
|
||||
| [ i ] ->
|
||||
[ "string" ; "float" ; "int" ; "bool" ; "char" ]
|
||||
|> List.tryFind (fun s -> s = i.idText)
|
||||
| _ -> None
|
||||
| _ -> None
|
||||
|
||||
let (|String|_|) (fieldType : SynType) : unit option =
|
||||
match fieldType with
|
||||
| SynType.LongIdent ident ->
|
||||
match ident.LongIdent with
|
||||
| [ i ] ->
|
||||
[ "string" ]
|
||||
|> List.tryFind (fun s -> s = i.idText)
|
||||
|> Option.map ignore<string>
|
||||
| _ -> None
|
||||
| _ -> None
|
||||
|
||||
let (|Byte|_|) (fieldType : SynType) : unit option =
|
||||
match fieldType with
|
||||
| SynType.LongIdent ident ->
|
||||
match ident.LongIdent with
|
||||
| [ i ] -> [ "byte" ] |> List.tryFind (fun s -> s = i.idText) |> Option.map ignore<string>
|
||||
| _ -> None
|
||||
| _ -> None
|
||||
|
||||
let (|Guid|_|) (fieldType : SynType) : unit option =
|
||||
match fieldType with
|
||||
| SynType.LongIdent ident ->
|
||||
match ident.LongIdent |> List.map (fun i -> i.idText) with
|
||||
| [ "System" ; "Guid" ]
|
||||
| [ "Guid" ] -> Some ()
|
||||
| _ -> None
|
||||
| _ -> None
|
||||
|
||||
let (|HttpResponseMessage|_|) (fieldType : SynType) : unit option =
|
||||
match fieldType with
|
||||
| SynType.LongIdent ident ->
|
||||
match ident.LongIdent |> List.map (fun i -> i.idText) with
|
||||
| [ "System" ; "Net" ; "Http" ; "HttpResponseMessage" ]
|
||||
| [ "Net" ; "Http" ; "HttpResponseMessage" ]
|
||||
| [ "Http" ; "HttpResponseMessage" ]
|
||||
| [ "HttpResponseMessage" ] -> Some ()
|
||||
| _ -> None
|
||||
| _ -> None
|
||||
|
||||
let (|HttpContent|_|) (fieldType : SynType) : unit option =
|
||||
match fieldType with
|
||||
| SynType.LongIdent ident ->
|
||||
match ident.LongIdent |> List.map (fun i -> i.idText) with
|
||||
| [ "System" ; "Net" ; "Http" ; "HttpContent" ]
|
||||
| [ "Net" ; "Http" ; "HttpContent" ]
|
||||
| [ "Http" ; "HttpContent" ]
|
||||
| [ "HttpContent" ] -> Some ()
|
||||
| _ -> None
|
||||
| _ -> None
|
||||
|
||||
let (|Stream|_|) (fieldType : SynType) : unit option =
|
||||
match fieldType with
|
||||
| SynType.LongIdent ident ->
|
||||
match ident.LongIdent |> List.map (fun i -> i.idText) with
|
||||
| [ "System" ; "IO" ; "Stream" ]
|
||||
| [ "IO" ; "Stream" ]
|
||||
| [ "Stream" ] -> Some ()
|
||||
| _ -> None
|
||||
| _ -> None
|
||||
|
||||
let (|NumberType|_|) (fieldType : SynType) =
|
||||
match fieldType with
|
||||
| SynType.LongIdent ident ->
|
||||
match ident.LongIdent with
|
||||
| [ i ] -> [ "string" ; "float" ; "int" ; "bool" ] |> List.tryFind (fun s -> s = i.idText)
|
||||
| _ -> None
|
||||
| _ -> None
|
||||
|
||||
let (|DateOnly|_|) (fieldType : SynType) =
|
||||
match fieldType with
|
||||
| SynType.LongIdent (SynLongIdent.SynLongIdent (ident, _, _)) ->
|
||||
match ident |> List.map (fun i -> i.idText) with
|
||||
| [ "System" ; "DateOnly" ]
|
||||
| [ "DateOnly" ] -> Some ()
|
||||
| _ -> None
|
||||
| _ -> None
|
||||
|
||||
let (|DateTime|_|) (fieldType : SynType) =
|
||||
match fieldType with
|
||||
| SynType.LongIdent (SynLongIdent.SynLongIdent (ident, _, _)) ->
|
||||
match ident |> List.map (fun i -> i.idText) with
|
||||
| [ "System" ; "DateTime" ]
|
||||
| [ "DateTime" ] -> Some ()
|
||||
| _ -> None
|
||||
| _ -> None
|
||||
|
||||
let (|Uri|_|) (fieldType : SynType) =
|
||||
match fieldType with
|
||||
| SynType.LongIdent (SynLongIdent.SynLongIdent (ident, _, _)) ->
|
||||
match ident |> List.map (fun i -> i.idText) with
|
||||
| [ "System" ; "Uri" ]
|
||||
| [ "Uri" ] -> Some ()
|
||||
| _ -> None
|
||||
| _ -> None
|
||||
|
||||
let (|Task|_|) (fieldType : SynType) : SynType option =
|
||||
match fieldType with
|
||||
| SynType.App (SynType.LongIdent (SynLongIdent.SynLongIdent (ident, _, _)), _, args, _, _, _, _) ->
|
||||
match ident |> List.map (fun i -> i.idText) with
|
||||
| [ "Task" ]
|
||||
| [ "Tasks" ; "Task" ]
|
||||
| [ "Threading" ; "Tasks" ; "Task" ]
|
||||
| [ "System" ; "Threading" ; "Tasks" ; "Task" ] ->
|
||||
match args with
|
||||
| [ arg ] -> Some arg
|
||||
| _ -> failwithf "Expected Task to be applied to exactly one arg, but got: %+A" args
|
||||
| _ -> None
|
||||
| _ -> None
|
||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
66
WoofWare.Myriad.Plugins/HttpMethod.fs
Normal file
66
WoofWare.Myriad.Plugins/HttpMethod.fs
Normal file
@@ -0,0 +1,66 @@
|
||||
namespace WoofWare.Myriad.Plugins
|
||||
|
||||
open System
|
||||
|
||||
/// An HTTP method. This is System.Net.Http.HttpMethod, but
|
||||
/// a proper discriminated union.
|
||||
type HttpMethod =
|
||||
/// HTTP Get
|
||||
| Get
|
||||
/// HTTP Post
|
||||
| Post
|
||||
/// HTTP Delete
|
||||
| Delete
|
||||
/// HTTP Patch
|
||||
| Patch
|
||||
/// HTTP Options
|
||||
| Options
|
||||
/// HTTP Head
|
||||
| Head
|
||||
/// HTTP Put
|
||||
| Put
|
||||
/// HTTP Trace
|
||||
| Trace
|
||||
|
||||
/// Convert to the standard library's enum type.
|
||||
member this.ToDotNet () : System.Net.Http.HttpMethod =
|
||||
match this with
|
||||
| HttpMethod.Get -> System.Net.Http.HttpMethod.Get
|
||||
| HttpMethod.Post -> System.Net.Http.HttpMethod.Post
|
||||
| HttpMethod.Delete -> System.Net.Http.HttpMethod.Delete
|
||||
| HttpMethod.Patch -> System.Net.Http.HttpMethod.Patch
|
||||
| HttpMethod.Options -> System.Net.Http.HttpMethod.Options
|
||||
| HttpMethod.Head -> System.Net.Http.HttpMethod.Head
|
||||
| HttpMethod.Put -> System.Net.Http.HttpMethod.Put
|
||||
| HttpMethod.Trace -> System.Net.Http.HttpMethod.Trace
|
||||
|
||||
/// Human-readable string representation.
|
||||
override this.ToString () : string =
|
||||
match this with
|
||||
| HttpMethod.Get -> "Get"
|
||||
| HttpMethod.Post -> "Post"
|
||||
| HttpMethod.Delete -> "Delete"
|
||||
| HttpMethod.Patch -> "Patch"
|
||||
| HttpMethod.Options -> "Options"
|
||||
| HttpMethod.Head -> "Head"
|
||||
| HttpMethod.Put -> "Put"
|
||||
| HttpMethod.Trace -> "Trace"
|
||||
|
||||
/// Throws on invalid inputs.
|
||||
static member Parse (s : string) : HttpMethod =
|
||||
if String.Equals (s, "get", StringComparison.OrdinalIgnoreCase) then
|
||||
HttpMethod.Get
|
||||
elif String.Equals (s, "post", StringComparison.OrdinalIgnoreCase) then
|
||||
HttpMethod.Post
|
||||
elif String.Equals (s, "patch", StringComparison.OrdinalIgnoreCase) then
|
||||
HttpMethod.Patch
|
||||
elif String.Equals (s, "delete", StringComparison.OrdinalIgnoreCase) then
|
||||
HttpMethod.Delete
|
||||
elif String.Equals (s, "head", StringComparison.OrdinalIgnoreCase) then
|
||||
HttpMethod.Head
|
||||
elif String.Equals (s, "options", StringComparison.OrdinalIgnoreCase) then
|
||||
HttpMethod.Options
|
||||
elif String.Equals (s, "put", StringComparison.OrdinalIgnoreCase) then
|
||||
HttpMethod.Put
|
||||
else
|
||||
failwith $"Unrecognised method: %s{s}"
|
@@ -1,14 +0,0 @@
|
||||
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 ())
|
@@ -2,9 +2,8 @@ namespace WoofWare.Myriad.Plugins
|
||||
|
||||
open System
|
||||
open Fantomas.FCS.Syntax
|
||||
open Fantomas.FCS.SyntaxTrivia
|
||||
open Fantomas.FCS.Xml
|
||||
open Myriad.Core
|
||||
open WoofWare.Whippet.Fantomas
|
||||
|
||||
type internal GenerateMockOutputSpec =
|
||||
{
|
||||
@@ -14,13 +13,15 @@ type internal GenerateMockOutputSpec =
|
||||
[<RequireQualifiedAccess>]
|
||||
module internal InterfaceMockGenerator =
|
||||
open Fantomas.FCS.Text.Range
|
||||
open Myriad.Core.Ast
|
||||
|
||||
let private getName (SynField (_, _, id, _, _, _, _, _, _)) =
|
||||
match id with
|
||||
| None -> failwith "Expected record field to have a name, but it was somehow anonymous"
|
||||
| Some id -> id
|
||||
|
||||
[<RequireQualifiedAccess>]
|
||||
type private KnownInheritance = | IDisposable
|
||||
|
||||
let createType
|
||||
(spec : GenerateMockOutputSpec)
|
||||
(name : string)
|
||||
@@ -29,157 +30,106 @@ module internal InterfaceMockGenerator =
|
||||
(fields : SynField list)
|
||||
: SynModuleDecl
|
||||
=
|
||||
let synValData =
|
||||
{
|
||||
SynMemberFlags.IsInstance = false
|
||||
SynMemberFlags.IsDispatchSlot = false
|
||||
SynMemberFlags.IsOverrideOrExplicitImpl = false
|
||||
SynMemberFlags.IsFinal = false
|
||||
SynMemberFlags.GetterOrSetterIsCompilerGenerated = false
|
||||
SynMemberFlags.MemberKind = SynMemberKind.Member
|
||||
}
|
||||
|
||||
let failwithFun =
|
||||
SynExpr.createLambda
|
||||
"x"
|
||||
(SynExpr.CreateApp (
|
||||
SynExpr.CreateIdentString "raise",
|
||||
SynExpr.CreateParen (
|
||||
SynExpr.CreateApp (
|
||||
SynExpr.CreateLongIdent (SynLongIdent.Create [ "System" ; "NotImplementedException" ]),
|
||||
SynExpr.CreateConstString "Unimplemented mock function"
|
||||
)
|
||||
)
|
||||
))
|
||||
|
||||
let constructorIdent =
|
||||
let generics =
|
||||
interfaceType.Generics
|
||||
|> Option.map (fun generics -> SynValTyparDecls (Some generics, false))
|
||||
|
||||
SynPat.LongIdent (
|
||||
SynLongIdent.CreateString "Empty",
|
||||
None,
|
||||
None, // no generics on the "Empty", only on the return type
|
||||
SynArgPats.Pats (
|
||||
if generics.IsNone then
|
||||
[]
|
||||
else
|
||||
[ SynPat.CreateParen (SynPat.CreateConst SynConst.Unit) ]
|
||||
),
|
||||
None,
|
||||
range0
|
||||
let inherits =
|
||||
interfaceType.Inherits
|
||||
|> Seq.map (fun ty ->
|
||||
match ty with
|
||||
| SynType.LongIdent (SynLongIdent.SynLongIdent (name, _, _)) ->
|
||||
match name |> List.map _.idText with
|
||||
| [] -> failwith "Unexpected empty identifier in inheritance declaration"
|
||||
| [ "IDisposable" ]
|
||||
| [ "System" ; "IDisposable" ] -> KnownInheritance.IDisposable
|
||||
| _ -> failwithf "Unrecognised inheritance identifier: %+A" name
|
||||
| x -> failwithf "Unrecognised type in inheritance: %+A" x
|
||||
)
|
||||
|> Set.ofSeq
|
||||
|
||||
let failwithFun (SynField (_, _, idOpt, _, _, _, _, _, _)) =
|
||||
let failString =
|
||||
match idOpt with
|
||||
| None -> SynExpr.CreateConst "Unimplemented mock function"
|
||||
| Some ident -> SynExpr.CreateConst $"Unimplemented mock function: %s{ident.idText}"
|
||||
|
||||
SynExpr.createLongIdent [ "System" ; "NotImplementedException" ]
|
||||
|> SynExpr.applyTo failString
|
||||
|> SynExpr.paren
|
||||
|> SynExpr.applyFunction (SynExpr.createIdent "raise")
|
||||
|> SynExpr.createLambda "_"
|
||||
|
||||
let constructorReturnType =
|
||||
match interfaceType.Generics with
|
||||
| None -> SynType.CreateLongIdent name
|
||||
| None -> SynType.createLongIdent' [ name ]
|
||||
| Some generics ->
|
||||
let generics =
|
||||
generics.TyparDecls
|
||||
|> List.map (fun (SynTyparDecl (_, typar)) -> SynType.Var (typar, range0))
|
||||
|
||||
SynType.App (
|
||||
SynType.CreateLongIdent name,
|
||||
Some range0,
|
||||
generics,
|
||||
List.replicate (generics.Length - 1) range0,
|
||||
Some range0,
|
||||
false,
|
||||
range0
|
||||
)
|
||||
|> SynBindingReturnInfo.Create
|
||||
let generics =
|
||||
generics.TyparDecls
|
||||
|> List.map (fun (SynTyparDecl (_, typar)) -> SynType.var typar)
|
||||
|
||||
SynType.app name generics
|
||||
|
||||
let constructorFields =
|
||||
let extras =
|
||||
if inherits.Contains KnownInheritance.IDisposable then
|
||||
let unitFun = SynExpr.createThunk (SynExpr.CreateConst ())
|
||||
|
||||
[ SynLongIdent.createS "Dispose", unitFun ]
|
||||
else
|
||||
[]
|
||||
|
||||
let nonExtras =
|
||||
fields
|
||||
|> List.map (fun field -> SynLongIdent.createI (getName field), failwithFun field)
|
||||
|
||||
extras @ nonExtras
|
||||
|
||||
let constructor =
|
||||
SynMemberDefn.Member (
|
||||
SynBinding.SynBinding (
|
||||
None,
|
||||
SynBindingKind.Normal,
|
||||
false,
|
||||
false,
|
||||
[],
|
||||
PreXmlDoc.Create " An implementation where every method throws.",
|
||||
SynValData.SynValData (Some synValData, SynValInfo.Empty, None),
|
||||
constructorIdent,
|
||||
Some constructorReturnType,
|
||||
AstHelper.instantiateRecord (
|
||||
fields
|
||||
|> List.map (fun field ->
|
||||
((SynLongIdent.CreateFromLongIdent [ getName field ], true), Some failwithFun)
|
||||
)
|
||||
),
|
||||
range0,
|
||||
DebugPointAtBinding.Yes range0,
|
||||
{ SynExpr.synBindingTriviaZero true with
|
||||
LeadingKeyword = SynLeadingKeyword.StaticMember (range0, range0)
|
||||
SynBinding.basic
|
||||
[ Ident.create "Empty" ]
|
||||
(if interfaceType.Generics.IsNone then
|
||||
[]
|
||||
else
|
||||
[ SynPat.unit ])
|
||||
(SynExpr.createRecord None constructorFields)
|
||||
|> SynBinding.withXmlDoc (PreXmlDoc.create "An implementation where every method throws.")
|
||||
|> SynBinding.withReturnAnnotation constructorReturnType
|
||||
|> SynMemberDefn.staticMember
|
||||
|
||||
let fields =
|
||||
let extras =
|
||||
if inherits.Contains KnownInheritance.IDisposable then
|
||||
{
|
||||
Attrs = []
|
||||
Ident = Some (Ident.create "Dispose")
|
||||
Type = SynType.funFromDomain SynType.unit SynType.unit
|
||||
}
|
||||
),
|
||||
range0
|
||||
)
|
||||
|> SynField.make
|
||||
|> SynField.withDocString (PreXmlDoc.create "Implementation of IDisposable.Dispose")
|
||||
|> List.singleton
|
||||
else
|
||||
[]
|
||||
|
||||
extras @ fields
|
||||
|
||||
let interfaceMembers =
|
||||
let members =
|
||||
interfaceType.Members
|
||||
|> List.map (fun memberInfo ->
|
||||
|
||||
let synValData =
|
||||
SynValData.SynValData (
|
||||
Some
|
||||
{
|
||||
IsInstance = true
|
||||
IsDispatchSlot = false
|
||||
IsOverrideOrExplicitImpl = true
|
||||
IsFinal = false
|
||||
GetterOrSetterIsCompilerGenerated = false
|
||||
MemberKind = SynMemberKind.Member
|
||||
},
|
||||
valInfo =
|
||||
SynValInfo.SynValInfo (
|
||||
curriedArgInfos =
|
||||
[
|
||||
yield
|
||||
[
|
||||
SynArgInfo.SynArgInfo (
|
||||
attributes = [],
|
||||
optional = false,
|
||||
ident = None
|
||||
)
|
||||
]
|
||||
yield!
|
||||
memberInfo.Args
|
||||
|> List.mapi (fun i arg ->
|
||||
arg.Args
|
||||
|> List.mapi (fun j arg ->
|
||||
SynArgInfo.CreateIdString $"arg_%i{i}_%i{j}"
|
||||
)
|
||||
)
|
||||
],
|
||||
returnInfo =
|
||||
SynArgInfo.SynArgInfo (attributes = [], optional = false, ident = None)
|
||||
),
|
||||
thisIdOpt = None
|
||||
)
|
||||
|
||||
let headArgs =
|
||||
memberInfo.Args
|
||||
|> List.mapi (fun i tupledArgs ->
|
||||
let args =
|
||||
tupledArgs.Args
|
||||
|> List.mapi (fun j _ -> SynPat.CreateNamed (Ident.Create $"arg_%i{i}_%i{j}"))
|
||||
|> List.mapi (fun j ty ->
|
||||
match ty.Type with
|
||||
| UnitType -> SynPat.unit
|
||||
| _ -> SynPat.named $"arg_%i{i}_%i{j}"
|
||||
)
|
||||
|
||||
SynPat.Tuple (false, args, List.replicate (args.Length - 1) range0, range0)
|
||||
|> SynPat.CreateParen
|
||||
|> fun i -> if tupledArgs.HasParen then SynPat.Paren (i, range0) else i
|
||||
)
|
||||
|
||||
let headPat =
|
||||
SynPat.LongIdent (
|
||||
SynLongIdent.CreateFromLongIdent [ Ident.Create "this" ; memberInfo.Identifier ],
|
||||
None,
|
||||
None,
|
||||
SynArgPats.Pats headArgs,
|
||||
None,
|
||||
range0
|
||||
match args with
|
||||
| [] -> failwith "somehow got no args at all"
|
||||
| [ arg ] -> arg
|
||||
| args -> SynPat.tuple args
|
||||
|> fun i -> if tupledArgs.HasParen then SynPat.paren i else i
|
||||
)
|
||||
|
||||
let body =
|
||||
@@ -187,8 +137,12 @@ module internal InterfaceMockGenerator =
|
||||
memberInfo.Args
|
||||
|> List.mapi (fun i args ->
|
||||
args.Args
|
||||
|> List.mapi (fun j args -> SynExpr.CreateIdentString $"arg_%i{i}_%i{j}")
|
||||
|> SynExpr.CreateParenedTuple
|
||||
|> List.mapi (fun j arg ->
|
||||
match arg.Type with
|
||||
| UnitType -> SynExpr.CreateConst ()
|
||||
| _ -> SynExpr.createIdent $"arg_%i{i}_%i{j}"
|
||||
)
|
||||
|> SynExpr.tuple
|
||||
)
|
||||
|
||||
match tuples |> List.rev with
|
||||
@@ -196,42 +150,26 @@ module internal InterfaceMockGenerator =
|
||||
| last :: rest ->
|
||||
|
||||
(last, rest)
|
||||
||> List.fold (fun trail next -> SynExpr.CreateApp (next, trail))
|
||||
|> fun args ->
|
||||
SynExpr.CreateApp (
|
||||
SynExpr.CreateLongIdent (
|
||||
SynLongIdent.CreateFromLongIdent [ Ident.Create "this" ; memberInfo.Identifier ]
|
||||
),
|
||||
args
|
||||
)
|
||||
||> List.fold SynExpr.applyTo
|
||||
|> SynExpr.applyFunction (
|
||||
SynExpr.createLongIdent' [ Ident.create "this" ; memberInfo.Identifier ]
|
||||
)
|
||||
|
||||
SynMemberDefn.Member (
|
||||
SynBinding.SynBinding (
|
||||
None,
|
||||
SynBindingKind.Normal,
|
||||
false,
|
||||
false,
|
||||
[],
|
||||
PreXmlDoc.Empty,
|
||||
synValData,
|
||||
headPat,
|
||||
None,
|
||||
body,
|
||||
range0,
|
||||
DebugPointAtBinding.Yes range0,
|
||||
{
|
||||
LeadingKeyword = SynLeadingKeyword.Member range0
|
||||
InlineKeyword = None
|
||||
EqualsRange = Some range0
|
||||
}
|
||||
),
|
||||
range0
|
||||
)
|
||||
SynBinding.basic [ Ident.create "this" ; memberInfo.Identifier ] headArgs body
|
||||
|> SynMemberDefn.memberImplementation
|
||||
)
|
||||
|
||||
let properties =
|
||||
interfaceType.Properties
|
||||
|> List.map (fun pi ->
|
||||
SynExpr.createLongIdent' [ Ident.create "this" ; pi.Identifier ]
|
||||
|> SynExpr.applyTo (SynExpr.CreateConst ())
|
||||
|> SynBinding.basic [ Ident.create "this" ; pi.Identifier ] []
|
||||
|> SynMemberDefn.memberImplementation
|
||||
)
|
||||
|
||||
let interfaceName =
|
||||
let baseName =
|
||||
SynType.CreateLongIdent (SynLongIdent.CreateFromLongIdent interfaceType.Name)
|
||||
let baseName = SynType.createLongIdent interfaceType.Name
|
||||
|
||||
match interfaceType.Generics with
|
||||
| None -> baseName
|
||||
@@ -241,19 +179,11 @@ module internal InterfaceMockGenerator =
|
||||
| SynTyparDecls.PostfixList (decls, _, _) -> decls
|
||||
| SynTyparDecls.PrefixList (decls, _) -> decls
|
||||
| SynTyparDecls.SinglePrefix (decl, _) -> [ decl ]
|
||||
|> List.map (fun (SynTyparDecl (_, typar)) -> SynType.Var (typar, range0))
|
||||
|> List.map (fun (SynTyparDecl (_, typar)) -> SynType.var typar)
|
||||
|
||||
SynType.App (
|
||||
baseName,
|
||||
Some range0,
|
||||
generics,
|
||||
List.replicate (generics.Length - 1) range0,
|
||||
Some range0,
|
||||
false,
|
||||
range0
|
||||
)
|
||||
SynType.app' baseName generics
|
||||
|
||||
SynMemberDefn.Interface (interfaceName, Some range0, Some members, range0)
|
||||
SynMemberDefn.Interface (interfaceName, Some range0, Some (members @ properties), range0)
|
||||
|
||||
let access =
|
||||
match interfaceType.Accessibility, spec.IsInternal with
|
||||
@@ -264,14 +194,37 @@ module internal InterfaceMockGenerator =
|
||||
| Some (SynAccess.Internal _), _ -> SynAccess.Internal range0
|
||||
| Some (SynAccess.Private _), _ -> SynAccess.Private range0
|
||||
|
||||
let extraInterfaces =
|
||||
inherits
|
||||
|> Seq.map (fun inheritance ->
|
||||
match inheritance with
|
||||
| KnownInheritance.IDisposable ->
|
||||
let mem =
|
||||
SynExpr.createLongIdent [ "this" ; "Dispose" ]
|
||||
|> SynExpr.applyTo (SynExpr.CreateConst ())
|
||||
|> SynBinding.basic [ Ident.create "this" ; Ident.create "Dispose" ] [ SynPat.unit ]
|
||||
|> SynBinding.withReturnAnnotation SynType.unit
|
||||
|> SynMemberDefn.memberImplementation
|
||||
|
||||
SynMemberDefn.Interface (
|
||||
SynType.createLongIdent' [ "System" ; "IDisposable" ],
|
||||
Some range0,
|
||||
Some [ mem ],
|
||||
range0
|
||||
)
|
||||
)
|
||||
|> Seq.toList
|
||||
|
||||
let record =
|
||||
{
|
||||
Name = Ident.Create name
|
||||
Name = Ident.create name
|
||||
Fields = fields
|
||||
Members = Some [ constructor ; interfaceMembers ]
|
||||
Members = Some ([ constructor ; interfaceMembers ] @ extraInterfaces)
|
||||
XmlDoc = Some xmlDoc
|
||||
Generics = interfaceType.Generics
|
||||
Accessibility = Some access
|
||||
TypeAccessibility = Some access
|
||||
ImplAccessibility = None
|
||||
Attributes = []
|
||||
}
|
||||
|
||||
let typeDecl = AstHelper.defineRecordType record
|
||||
@@ -280,36 +233,38 @@ module internal InterfaceMockGenerator =
|
||||
|
||||
let private buildType (x : ParameterInfo) : SynType =
|
||||
if x.IsOptional then
|
||||
SynType.App (SynType.CreateLongIdent "option", Some range0, [ x.Type ], [], Some range0, false, range0)
|
||||
SynType.app "option" [ x.Type ]
|
||||
else
|
||||
x.Type
|
||||
|
||||
let private constructMemberSinglePlace (tuple : TupledArg) : SynType =
|
||||
match tuple.Args |> List.rev |> List.map buildType with
|
||||
| [] -> failwith "no-arg functions not supported yet"
|
||||
| [ x ] -> x
|
||||
| last :: rest ->
|
||||
([ SynTupleTypeSegment.Type last ], rest)
|
||||
||> List.fold (fun ty nextArg -> SynTupleTypeSegment.Type nextArg :: SynTupleTypeSegment.Star range0 :: ty)
|
||||
|> fun segs -> SynType.Tuple (false, segs, range0)
|
||||
|> fun ty -> if tuple.HasParen then SynType.Paren (ty, range0) else ty
|
||||
tuple.Args
|
||||
|> List.map buildType
|
||||
|> SynType.tupleNoParen
|
||||
|> Option.defaultWith (fun () -> failwith "no-arg functions not supported yet")
|
||||
|> if tuple.HasParen then SynType.paren else id
|
||||
|
||||
let constructMember (mem : MemberInfo) : SynField =
|
||||
let inputType = mem.Args |> List.map constructMemberSinglePlace
|
||||
|
||||
let funcType = AstHelper.toFun inputType mem.ReturnType
|
||||
let funcType = SynType.toFun inputType mem.ReturnType
|
||||
|
||||
SynField.SynField (
|
||||
[],
|
||||
false,
|
||||
Some mem.Identifier,
|
||||
funcType,
|
||||
false,
|
||||
mem.XmlDoc |> Option.defaultValue PreXmlDoc.Empty,
|
||||
None,
|
||||
range0,
|
||||
SynFieldTrivia.Zero
|
||||
)
|
||||
{
|
||||
Type = funcType
|
||||
Attrs = []
|
||||
Ident = Some mem.Identifier
|
||||
}
|
||||
|> SynField.make
|
||||
|> SynField.withDocString (mem.XmlDoc |> Option.defaultValue PreXmlDoc.Empty)
|
||||
|
||||
let constructProperty (prop : PropertyInfo) : SynField =
|
||||
{
|
||||
Attrs = []
|
||||
Ident = Some prop.Identifier
|
||||
Type = SynType.toFun [ SynType.unit ] prop.Type
|
||||
}
|
||||
|> SynField.make
|
||||
|> SynField.withDocString (prop.XmlDoc |> Option.defaultValue PreXmlDoc.Empty)
|
||||
|
||||
let createRecord
|
||||
(namespaceId : LongIdent)
|
||||
@@ -318,26 +273,30 @@ module internal InterfaceMockGenerator =
|
||||
: SynModuleOrNamespace
|
||||
=
|
||||
let interfaceType = AstHelper.parseInterface interfaceType
|
||||
let fields = interfaceType.Members |> List.map constructMember
|
||||
let docString = PreXmlDoc.Create " Mock record type for an interface"
|
||||
|
||||
let fields =
|
||||
interfaceType.Members
|
||||
|> List.map constructMember
|
||||
|> List.append (interfaceType.Properties |> List.map constructProperty)
|
||||
|
||||
let docString = PreXmlDoc.create "Mock record type for an interface"
|
||||
|
||||
let name =
|
||||
List.last interfaceType.Name
|
||||
|> _.idText
|
||||
|> fun s ->
|
||||
if s.StartsWith 'I' && s.Length > 1 && Char.IsUpper s.[1] then
|
||||
s.[1..]
|
||||
s.Substring 1
|
||||
else
|
||||
s
|
||||
|> fun s -> s + "Mock"
|
||||
|
||||
let typeDecl = createType spec name interfaceType docString fields
|
||||
|
||||
[ yield! opens |> List.map SynModuleDecl.openAny ; yield typeDecl ]
|
||||
|> SynModuleOrNamespace.createNamespace namespaceId
|
||||
|
||||
SynModuleOrNamespace.CreateNamespace (
|
||||
namespaceId,
|
||||
decls = (opens |> List.map SynModuleDecl.CreateOpen) @ [ typeDecl ]
|
||||
)
|
||||
open Myriad.Core
|
||||
|
||||
/// Myriad generator that creates a record which implements the given interface,
|
||||
/// but with every field mocked out.
|
||||
@@ -348,18 +307,42 @@ type InterfaceMockGenerator () =
|
||||
member _.ValidInputExtensions = [ ".fs" ]
|
||||
|
||||
member _.Generate (context : GeneratorContext) =
|
||||
let targetedTypes =
|
||||
MyriadParamParser.render context.AdditionalParameters
|
||||
|> Map.map (fun _ v -> v.Split '!' |> Array.toList |> List.map DesiredGenerator.Parse)
|
||||
|
||||
let ast, _ =
|
||||
Ast.fromFilename context.InputFilename |> Async.RunSynchronously |> Array.head
|
||||
|
||||
let types = Ast.extractTypeDefn ast
|
||||
let types = Ast.getTypes ast
|
||||
|
||||
let namespaceAndInterfaces =
|
||||
types
|
||||
|> List.choose (fun (ns, types) ->
|
||||
types
|
||||
|> List.choose (fun typeDef ->
|
||||
match Ast.getAttribute<GenerateMockAttribute> typeDef with
|
||||
| None -> None
|
||||
match SynTypeDefn.getAttribute typeof<GenerateMockAttribute>.Name typeDef with
|
||||
| None ->
|
||||
let name = SynTypeDefn.getName typeDef |> List.map _.idText |> String.concat "."
|
||||
|
||||
match Map.tryFind name targetedTypes with
|
||||
| Some desired ->
|
||||
desired
|
||||
|> List.tryPick (fun generator ->
|
||||
match generator with
|
||||
| DesiredGenerator.InterfaceMock arg ->
|
||||
let spec =
|
||||
{
|
||||
IsInternal =
|
||||
arg
|
||||
|> Option.defaultValue GenerateMockAttribute.DefaultIsInternal
|
||||
}
|
||||
|
||||
Some (typeDef, spec)
|
||||
| _ -> None
|
||||
)
|
||||
| _ -> None
|
||||
|
||||
| Some attr ->
|
||||
let arg =
|
||||
match SynExpr.stripOptionalParen attr.ArgExpr with
|
||||
|
48
WoofWare.Myriad.Plugins/JsonHelpers.fs
Normal file
48
WoofWare.Myriad.Plugins/JsonHelpers.fs
Normal file
@@ -0,0 +1,48 @@
|
||||
namespace WoofWare.Myriad.Plugins
|
||||
|
||||
open System.Text.Json.Nodes
|
||||
|
||||
[<AutoOpen>]
|
||||
module internal JsonHelpers =
|
||||
let inline asString (n : JsonNode) (key : string) : string =
|
||||
match n.[key] with
|
||||
| null -> failwith $"Expected node to have a key '%s{key}', but it did not: %s{n.ToJsonString ()}"
|
||||
| s -> s.GetValue<string> ()
|
||||
|
||||
[<RequiresExplicitTypeArguments>]
|
||||
let inline asOpt<'ret> (n : JsonNode) (key : string) : 'ret option =
|
||||
match n.[key] with
|
||||
| null -> None
|
||||
| s -> s.GetValue<'ret> () |> Some
|
||||
|
||||
let inline asObj (n : JsonNode) (key : string) : JsonObject =
|
||||
match n.[key] with
|
||||
| null -> failwith $"Expected node to have a key '%s{key}', but it did not: %s{n.ToJsonString ()}"
|
||||
| o -> o.AsObject ()
|
||||
|
||||
let inline asObjOpt (n : JsonNode) (key : string) : JsonObject option =
|
||||
match n.[key] with
|
||||
| null -> None
|
||||
| o -> o.AsObject () |> Some
|
||||
|
||||
let inline asArr (n : JsonNode) (key : string) : JsonArray =
|
||||
match n.[key] with
|
||||
| null -> failwith $"Expected node to have a key '%s{key}', but it did not: %s{n.ToJsonString ()}"
|
||||
| o -> o.AsArray ()
|
||||
|
||||
let inline asArrOpt (n : JsonNode) (key : string) : JsonArray option =
|
||||
match n.[key] with
|
||||
| null -> None
|
||||
| o -> o.AsArray () |> Some
|
||||
|
||||
[<RequiresExplicitTypeArguments>]
|
||||
let inline asArr'<'v> (n : JsonNode) (key : string) : 'v list =
|
||||
match n.[key] with
|
||||
| null -> failwith $"Expected node to have a key '%s{key}', but it did not: %s{n.ToJsonString ()}"
|
||||
| o -> o.AsArray () |> Seq.map (fun v -> v.GetValue<'v> ()) |> Seq.toList
|
||||
|
||||
[<RequiresExplicitTypeArguments>]
|
||||
let inline asArrOpt'<'v> (n : JsonNode) (key : string) : 'v list option =
|
||||
match n.[key] with
|
||||
| null -> None
|
||||
| o -> o.AsArray () |> Seq.map (fun v -> v.GetValue<'v> ()) |> Seq.toList |> Some
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -12,3 +12,12 @@ module private List =
|
||||
)
|
||||
|
||||
List.rev xs, List.rev ys
|
||||
|
||||
let allSome<'a> (l : 'a option list) : 'a list option =
|
||||
let rec go acc (l : 'a option list) =
|
||||
match l with
|
||||
| [] -> Some (List.rev acc)
|
||||
| None :: _ -> None
|
||||
| Some head :: tail -> go (head :: acc) tail
|
||||
|
||||
go [] l
|
||||
|
24
WoofWare.Myriad.Plugins/Measure.fs
Normal file
24
WoofWare.Myriad.Plugins/Measure.fs
Normal file
@@ -0,0 +1,24 @@
|
||||
namespace WoofWare.Myriad.Plugins
|
||||
|
||||
open Fantomas.FCS.Syntax
|
||||
open WoofWare.Whippet.Fantomas
|
||||
|
||||
[<RequireQualifiedAccess>]
|
||||
module internal Measure =
|
||||
|
||||
let getLanguagePrimitivesMeasure (typeName : LongIdent) : SynExpr =
|
||||
match typeName |> List.map _.idText with
|
||||
| [ "System" ; "Single" ] -> [ "LanguagePrimitives" ; "Float32WithMeasure" ]
|
||||
| [ "System" ; "Double" ] -> [ "LanguagePrimitives" ; "FloatWithMeasure" ]
|
||||
| [ "System" ; "Byte" ] -> [ "LanguagePrimitives" ; "ByteWithMeasure" ]
|
||||
| [ "System" ; "SByte" ] -> [ "LanguagePrimitives" ; "SByteWithMeasure" ]
|
||||
| [ "System" ; "Int16" ] -> [ "LanguagePrimitives" ; "Int16WithMeasure" ]
|
||||
| [ "System" ; "Int32" ] -> [ "LanguagePrimitives" ; "Int32WithMeasure" ]
|
||||
| [ "System" ; "Int64" ] -> [ "LanguagePrimitives" ; "Int64WithMeasure" ]
|
||||
| [ "System" ; "UInt16" ] -> [ "LanguagePrimitives" ; "UInt16WithMeasure" ]
|
||||
| [ "System" ; "UInt32" ] -> [ "LanguagePrimitives" ; "UInt32WithMeasure" ]
|
||||
| [ "System" ; "UInt64" ] -> [ "LanguagePrimitives" ; "UInt64WithMeasure" ]
|
||||
| l ->
|
||||
let l = String.concat "." l
|
||||
failwith $"unrecognised type for measure: %s{l}"
|
||||
|> SynExpr.createLongIdent
|
42
WoofWare.Myriad.Plugins/MyriadParamParser.fs
Normal file
42
WoofWare.Myriad.Plugins/MyriadParamParser.fs
Normal file
@@ -0,0 +1,42 @@
|
||||
namespace WoofWare.Myriad.Plugins
|
||||
|
||||
open System.Collections.Generic
|
||||
|
||||
[<RequireQualifiedAccess>]
|
||||
module internal MyriadParamParser =
|
||||
(*
|
||||
An apparent bug in Myriad's argument parsing means that this:
|
||||
|
||||
<MyriadParams>
|
||||
<Foo>bar</Foo>
|
||||
<Baz>quux</Baz>
|
||||
</MyriadParams>
|
||||
|
||||
leads to this:
|
||||
|
||||
Foo = "bar;Baz=quux"
|
||||
|
||||
I'm not going to put effort into fixing Myriad, though, because I want
|
||||
to build something much more powerful instead.
|
||||
*)
|
||||
|
||||
/// Call this with `context.AdditionalParameters`.
|
||||
let render (pars : IDictionary<string, string>) : Map<string, string> =
|
||||
match pars.Count with
|
||||
| 0 -> Map.empty
|
||||
| 1 ->
|
||||
let (KeyValue (key, value)) = pars |> Seq.exactlyOne
|
||||
|
||||
match value.Split ';' |> Seq.toList with
|
||||
| [] -> failwith "LOGIC ERROR"
|
||||
| value :: rest ->
|
||||
rest
|
||||
|> Seq.map (fun v ->
|
||||
let split = v.Split '='
|
||||
split.[0], String.concat "=" split.[1..]
|
||||
)
|
||||
|> Seq.append (Seq.singleton (key, value))
|
||||
|> Map.ofSeq
|
||||
| _ ->
|
||||
// assume the Myriad bug is fixed!
|
||||
pars |> Seq.map (fun (KeyValue (k, v)) -> k, v) |> Map.ofSeq
|
504
WoofWare.Myriad.Plugins/OpenApi3.fs
Normal file
504
WoofWare.Myriad.Plugins/OpenApi3.fs
Normal file
@@ -0,0 +1,504 @@
|
||||
module internal WoofWare.Myriad.Plugins.OpenApi3
|
||||
|
||||
open System
|
||||
open System.Text.Json.Nodes
|
||||
|
||||
type ExternalDocumentation =
|
||||
{
|
||||
/// A short description of the target documentation, possibly in CommonMark.
|
||||
Description : string option
|
||||
/// The URL for the target documentation.
|
||||
Url : Uri
|
||||
}
|
||||
|
||||
type Schema = | Schema of unit
|
||||
|
||||
type Example =
|
||||
{
|
||||
/// Short description for the example.
|
||||
Summary : string option
|
||||
/// Long description for the example, possibly CommonMark.
|
||||
Description : string option
|
||||
Value : Choice<JsonNode, Uri> option
|
||||
}
|
||||
|
||||
type Reference =
|
||||
{
|
||||
/// The reference string.
|
||||
Ref : string
|
||||
}
|
||||
|
||||
type Tag =
|
||||
{
|
||||
/// The name of the tag.
|
||||
Name : string
|
||||
/// A short description for the tag, possibly CommonMark.
|
||||
Description : string option
|
||||
/// Additional external documentation for this tag.
|
||||
ExternalDocs : ExternalDocumentation option
|
||||
}
|
||||
|
||||
type ServerVariable =
|
||||
{
|
||||
/// An enumeration of string values to be used if the substitution options are from a limited set.
|
||||
Enum : string list option
|
||||
/// The default value to use for substitution, and to send, if an alternate value is not supplied.
|
||||
/// Unlike the Schema Object’s default, this value MUST be provided by the consumer.
|
||||
Default : string
|
||||
/// An optional description for the server variable, possibly in CommonMark.
|
||||
Description : string option
|
||||
}
|
||||
|
||||
type Server =
|
||||
{
|
||||
/// A URL to the target host.
|
||||
/// This URL supports Server Variables and MAY be relative, to indicate that the host location is relative to the location where the OpenAPI document is being served.
|
||||
/// Variable substitutions will be made when a variable is named in {brackets}.
|
||||
Url : Uri
|
||||
/// Describes the host designated by the URL, possibly with CommonMark.
|
||||
Description : string option
|
||||
/// Used for substituting in the Url.
|
||||
Variables : Map<string, ServerVariable> option
|
||||
}
|
||||
|
||||
type StringFormat =
|
||||
/// base64-encoded characters
|
||||
| Byte
|
||||
/// any sequence of octets
|
||||
| Binary
|
||||
/// As defined by full-date - RFC3339 Section 5.6
|
||||
| Date
|
||||
/// As defined by date-time - RFC3339 Section 5.6
|
||||
| DateTime
|
||||
/// A hint to UIs to obscure input
|
||||
| Password
|
||||
| Verbatim of string
|
||||
|
||||
type IntegerFormat =
|
||||
/// Signed 32 bits
|
||||
| Int32
|
||||
/// Signed 64 bits
|
||||
| Int64
|
||||
| Verbatim of string
|
||||
|
||||
type NumberFormat =
|
||||
| Float
|
||||
| Double
|
||||
| Verbatim of string
|
||||
|
||||
type DataFormat =
|
||||
| Integer of IntegerFormat option
|
||||
| Number of NumberFormat option
|
||||
| String of StringFormat option
|
||||
| Boolean of format : string option
|
||||
|
||||
type Contact =
|
||||
{
|
||||
/// The identifying name of the contact person/organization.
|
||||
Name : string option
|
||||
/// The URL pointing to the contact information.
|
||||
Url : Uri option
|
||||
/// This MUST be in email address format.
|
||||
Email : string option
|
||||
}
|
||||
|
||||
type License =
|
||||
{
|
||||
/// The license name used for the API.
|
||||
Name : string
|
||||
/// A URL to the license used for the API.
|
||||
Url : Uri option
|
||||
}
|
||||
|
||||
type OpenApiInfo =
|
||||
{
|
||||
/// Title of the application
|
||||
Title : string
|
||||
/// Short description of the application, might be in CommonMark
|
||||
Description : string option
|
||||
/// Link to the ToS of the application
|
||||
TermsOfService : Uri option
|
||||
/// The contact information for the exposed API.
|
||||
Contact : Contact option
|
||||
/// The license information for the exposed API.
|
||||
License : License option
|
||||
/// The version of the OpenAPI document (which is distinct from the OpenAPI Specification version or the API implementation version).
|
||||
Version : string
|
||||
}
|
||||
|
||||
type Encoding =
|
||||
{
|
||||
/// The Content-Type for encoding a specific property.
|
||||
/// Default value depends on the property type:
|
||||
/// for string with format being binary – application/octet-stream;
|
||||
/// for other primitive types – text/plain;
|
||||
/// for object - application/json;
|
||||
/// for array – the default is defined based on the inner type.
|
||||
/// The value can be a specific media type (e.g. application/json), a wildcard media type (e.g. image/*), or a comma-separated list of the two types.
|
||||
ContentType : string option
|
||||
/// A map allowing additional information to be provided as headers, for example Content-Disposition.
|
||||
/// Content-Type is described separately and SHALL be ignored in this section.
|
||||
/// This property SHALL be ignored if the request body media type is not a multipart.
|
||||
Headers : Map<string, Choice<Header, Reference>> option
|
||||
/// Describes how a specific property value will be serialized depending on its type.
|
||||
/// See Parameter Object for details on the style property.
|
||||
/// The behavior follows the same values as query parameters, including default values.
|
||||
/// This property SHALL be ignored if the request body media type is not application/x-www-form-urlencoded.
|
||||
Style : string option
|
||||
/// When this is true, property values of type array or object generate separate parameters for each value of the array, or key-value-pair of the map.
|
||||
/// For other types of properties this property has no effect.
|
||||
/// When style is form, the default value is true.
|
||||
/// For all other styles, the default value is false.
|
||||
/// This property SHALL be ignored if the request body media type is not application/x-www-form-urlencoded.
|
||||
Explode : bool option
|
||||
/// Determines whether the parameter value SHOULD allow reserved characters, as defined by [RFC3986] Section 2.2 :/?#[]@!$&'()*+,;=
|
||||
/// to be included without percent-encoding.
|
||||
/// The default value is false.
|
||||
/// This property SHALL be ignored if the request body media type is not application/x-www-form-urlencoded.
|
||||
AllowReserved : bool option
|
||||
}
|
||||
|
||||
and MediaType =
|
||||
{
|
||||
/// The schema defining the type used for the request body.
|
||||
Schema : Choice<Schema, Reference> option
|
||||
Example : Choice<JsonNode, Map<string, Choice<Example, Reference>>> option
|
||||
/// A map between a property name and its encoding information.
|
||||
/// The key, being the property name, MUST exist in the schema as a property.
|
||||
/// The encoding object SHALL only apply to requestBody objects when the media type is multipart or application/x-www-form-urlencoded.
|
||||
Encoding : Map<string, Encoding> option
|
||||
}
|
||||
|
||||
/// The Header Object basically follows the structure of the Parameter Object.
|
||||
/// All traits that are affected by the location MUST be applicable to a location of header (for example, style).
|
||||
and Header =
|
||||
{
|
||||
/// A brief description of the header, possibly CommonMark.
|
||||
Description : string option
|
||||
/// Determines whether this header is mandatory.
|
||||
/// If the header location is “path”, this property is REQUIRED and its value MUST be true.
|
||||
/// Otherwise, the property MAY be included and its default value is false.
|
||||
Required : bool option
|
||||
/// Specifies that a header is deprecated and SHOULD be transitioned out of usage.
|
||||
Deprecated : bool option
|
||||
/// Sets the ability to pass empty-valued headers.
|
||||
/// This is valid only for query headers and allows sending a header with an empty value.
|
||||
/// Default value is false.
|
||||
/// If style is used, and if behavior is n/a (cannot be serialized), the value of allowEmptyValue SHALL be ignored.
|
||||
AllowEmptyValue : bool option
|
||||
/// Describes how the header value will be serialized depending on the type of the header value.
|
||||
/// Default values (based on value of in): for query - form; for path - simple; for header - simple; for cookie - form.
|
||||
Style : string option
|
||||
/// When this is true, header values of type array or object generate separate headers for each value of the array or key-value pair of the map.
|
||||
/// For other types of headers this property has no effect.
|
||||
/// When style is form, the default value is true.
|
||||
/// For all other styles, the default value is false.
|
||||
Explode : bool option
|
||||
/// Determines whether the header value SHOULD allow reserved characters, as defined by [RFC3986] Section 2.2 :/?#[]@!$&'()*+,;=
|
||||
/// to be included without percent-encoding.
|
||||
/// This property only applies to headers with an in value of query.
|
||||
/// The default value is false.
|
||||
AllowReserved : bool option
|
||||
/// The schema defining the type used for the header.
|
||||
Schema : Choice<Schema, Reference> option
|
||||
Example : Choice<JsonNode, Map<string, Choice<Example, Reference>>> option
|
||||
/// A map containing the representations for the header.
|
||||
/// The key is the media type and the value describes it.
|
||||
/// The map MUST only contain one entry.
|
||||
Content : Map<string, MediaType> option
|
||||
}
|
||||
|
||||
type LinkOperation =
|
||||
/// A relative or absolute reference to an OAS operation.
|
||||
/// This field is mutually exclusive of the operationId field, and MUST point to an Operation Object.
|
||||
/// Relative operationRef values MAY be used to locate an existing Operation Object in the OpenAPI definition.
|
||||
| Ref of string
|
||||
/// The name of an existing, resolvable OAS operation, as defined with a unique operationId.
|
||||
/// This field is mutually exclusive of the operationRef field.
|
||||
| Id of string
|
||||
|
||||
type RuntimeExpression = | RuntimeExpression of unit
|
||||
|
||||
type Link =
|
||||
{
|
||||
/// A relative or absolute reference to an OAS operation.
|
||||
/// This field is mutually exclusive of the operationId field, and MUST point to an Operation Object. Relative operationRef values MAY be used to locate an existing Operation Object in the OpenAPI definition.
|
||||
Operation : LinkOperation option
|
||||
/// A map representing parameters to pass to an operation as specified with operationId or identified via operationRef.
|
||||
/// The key is the parameter name to be used, whereas the value can be a constant or an expression to be evaluated and passed to the linked operation.
|
||||
/// The parameter name can be qualified using the parameter location [{in}.]{name} for operations that use the same parameter name in different locations (e.g. path.id).
|
||||
Parameters : Map<string, Choice<JsonNode, RuntimeExpression>> option
|
||||
/// A literal value or {expression} to use as a request body when calling the target operation.
|
||||
RequestBody : Choice<JsonNode, RuntimeExpression> option
|
||||
/// A description of the link, possibly CommonMark.
|
||||
Description : string option
|
||||
/// A server object to be used by the target operation.
|
||||
Server : Server option
|
||||
}
|
||||
|
||||
type Response =
|
||||
{
|
||||
/// A short description of the response, possibly CommonMark.
|
||||
Description : string
|
||||
/// Maps a header name to its definition.
|
||||
/// [RFC7230] Page 22 states header names are case insensitive.
|
||||
/// If a response header is defined with the name "Content-Type", it SHALL be ignored.
|
||||
Headers : Map<string, Choice<Header, Reference>> option
|
||||
/// A map containing descriptions of potential response payloads.
|
||||
/// The key is a media type or media type range, see [RFC7231] Appendix D, and the value describes it.
|
||||
/// For responses that match multiple keys, only the most specific key is applicable. e.g. text/plain overrides text/*
|
||||
Content : Map<string, MediaType> option
|
||||
/// A map of operations links that can be followed from the response.
|
||||
/// The key of the map is a short name for the link, following the naming constraints of the names for Component Objects.
|
||||
Links : Map<string, Choice<Link, Reference>> option
|
||||
}
|
||||
|
||||
type Responses =
|
||||
{
|
||||
/// The documentation of responses other than the ones declared for specific HTTP response codes.
|
||||
/// Use this field to cover undeclared responses.
|
||||
Default : Choice<Response, Reference> option
|
||||
/// Map from HTTP status code to expected response.
|
||||
/// The keys are allowed to be "2XX" for example, hence being strings and not ints.
|
||||
Patterns : Map<string, Choice<Response, Reference>> option
|
||||
}
|
||||
|
||||
type SecuritySchemeIn =
|
||||
| Query
|
||||
| Header
|
||||
| Cookie
|
||||
|
||||
type OauthFlow =
|
||||
{
|
||||
/// The authorization URL to be used for this flow.
|
||||
AuthorizationUrl : Uri
|
||||
/// The token URL to be used for this flow.
|
||||
TokenUrl : Uri
|
||||
/// The URL to be used for obtaining refresh tokens.
|
||||
RefreshUrl : Uri option
|
||||
/// The available scopes for the OAuth2 security scheme. A map between the scope name and a short description for it.
|
||||
Scopes : Map<string, string>
|
||||
}
|
||||
|
||||
type SecurityRequirement =
|
||||
{
|
||||
/// Each name MUST correspond to a security scheme which is declared in the Security Schemes under the Components Object.
|
||||
/// If the security scheme is of type "oauth2" or "openIdConnect", then the value is a list of scope names required for the execution.
|
||||
/// For other security scheme types, the array MUST be empty.
|
||||
Fields : Map<string, string list> option
|
||||
}
|
||||
|
||||
type OauthFlows =
|
||||
{
|
||||
/// Configuration for the OAuth Implicit flow
|
||||
Implicit : OauthFlow
|
||||
/// Configuration for the OAuth Resource Owner Password flow
|
||||
Password : OauthFlow
|
||||
/// Configuration for the OAuth Client Credentials flow.
|
||||
ClientCredentials : OauthFlow
|
||||
/// Configuration for the OAuth Authorization Code flow.
|
||||
AuthorizationCode : OauthFlow
|
||||
}
|
||||
|
||||
type SecurityScheme =
|
||||
| ApiKey of description : string option * name : string * inValue : SecuritySchemeIn
|
||||
| Http of description : string option * scheme : string * bearerFormat : string option
|
||||
| Oauth2 of description : string option * OauthFlows
|
||||
| OpenIdConnect of description : string option * url : Uri
|
||||
|
||||
type ParameterIn =
|
||||
/// Used together with Path Templating, where the parameter value is actually part of the operation’s URL.
|
||||
/// This does not include the host or base path of the API.
|
||||
/// For example, in /items/{itemId}, the path parameter is itemId.
|
||||
| Path
|
||||
/// Custom headers that are expected as part of the request.
|
||||
/// Note that [RFC7230] Page 22 states header names are case insensitive.
|
||||
| Header
|
||||
/// Parameters that are appended to the URL. For example, in /items?id=###, the query parameter is id.
|
||||
| Query
|
||||
/// Used to pass a specific cookie value to the API.
|
||||
| Cookie
|
||||
|
||||
/// A unique parameter is defined by a combination of a name and location.
|
||||
type Parameter =
|
||||
{
|
||||
/// Name of the parameter, case sensitive.
|
||||
/// If in is "path", the name field MUST correspond to the associated path segment from the path field in the Paths Object.
|
||||
/// See Path Templating for further information.
|
||||
/// If in is "header" and the name field is "Accept", "Content-Type" or "Authorization", the parameter definition SHALL be ignored.
|
||||
/// For all other cases, the name corresponds to the parameter name used by the in property.
|
||||
Name : string
|
||||
/// The location of the parameter.
|
||||
In : ParameterIn
|
||||
/// A brief description of the parameter, possibly CommonMark.
|
||||
Description : string option
|
||||
/// Determines whether this parameter is mandatory.
|
||||
/// If the parameter location is “path”, this property is REQUIRED and its value MUST be true.
|
||||
/// Otherwise, the property MAY be included and its default value is false.
|
||||
Required : bool option
|
||||
/// Specifies that a parameter is deprecated and SHOULD be transitioned out of usage.
|
||||
Deprecated : bool option
|
||||
/// Sets the ability to pass empty-valued parameters.
|
||||
/// This is valid only for query parameters and allows sending a parameter with an empty value.
|
||||
/// Default value is false.
|
||||
/// If style is used, and if behavior is n/a (cannot be serialized), the value of allowEmptyValue SHALL be ignored.
|
||||
AllowEmptyValue : bool option
|
||||
/// Describes how the parameter value will be serialized depending on the type of the parameter value.
|
||||
/// Default values (based on value of in): for query - form; for path - simple; for header - simple; for cookie - form.
|
||||
Style : string option
|
||||
/// When this is true, parameter values of type array or object generate separate parameters for each value of the array or key-value pair of the map.
|
||||
/// For other types of parameters this property has no effect.
|
||||
/// When style is form, the default value is true.
|
||||
/// For all other styles, the default value is false.
|
||||
Explode : bool option
|
||||
/// Determines whether the parameter value SHOULD allow reserved characters, as defined by [RFC3986] Section 2.2 :/?#[]@!$&'()*+,;=
|
||||
/// to be included without percent-encoding.
|
||||
/// This property only applies to parameters with an in value of query.
|
||||
/// The default value is false.
|
||||
AllowReserved : bool option
|
||||
/// The schema defining the type used for the parameter.
|
||||
Schema : Choice<Schema, Reference> option
|
||||
Example : Choice<JsonNode, Map<string, Choice<Example, Reference>>> option
|
||||
/// A map containing the representations for the parameter.
|
||||
/// The key is the media type and the value describes it.
|
||||
/// The map MUST only contain one entry.
|
||||
Content : Map<string, MediaType> option
|
||||
}
|
||||
|
||||
type RequestBody =
|
||||
{
|
||||
/// A brief description of the request body. This could contain examples of use.
|
||||
/// Possibly CommonMark.
|
||||
Description : string option
|
||||
/// The content of the request body.
|
||||
/// The key is a media type or media type range, see [RFC7231] Appendix D, and the value describes it.
|
||||
/// For requests that match multiple keys, only the most specific key is applicable. e.g. text/plain overrides text/*
|
||||
Content : Map<string, MediaType>
|
||||
/// Determines if the request body is required in the request. Defaults to false.
|
||||
Required : bool option
|
||||
}
|
||||
|
||||
type Callback =
|
||||
{
|
||||
/// For the semantics of the keys, see https://spec.openapis.org/oas/v3.0.0#key-expression
|
||||
Patterns : Map<string, PathItem> option
|
||||
}
|
||||
|
||||
and Operation =
|
||||
{
|
||||
/// A list of tags for API documentation control.
|
||||
/// Tags can be used for logical grouping of operations by resources or any other qualifier.
|
||||
Tags : string list option
|
||||
/// A short summary of what the operation does.
|
||||
Summary : string option
|
||||
/// A verbose explanation of the operation behavior, possibly in CommonMark.
|
||||
Description : string option
|
||||
/// Additional external documentation for this operation.
|
||||
ExternalDocs : ExternalDocumentation
|
||||
/// Unique string used to identify the operation.
|
||||
/// The id MUST be unique among all operations described in the API.
|
||||
/// Tools and libraries MAY use the operationId to uniquely identify an operation, therefore,
|
||||
/// it is RECOMMENDED to follow common programming naming conventions.
|
||||
OperationId : string option
|
||||
/// A list of parameters that are applicable for this operation.
|
||||
/// If a parameter is already defined at the Path Item, the new definition will override it but can never remove it.
|
||||
/// The list MUST NOT include duplicated parameters.
|
||||
/// A unique parameter is defined by a combination of a name and location.
|
||||
/// The list can use the Reference Object to link to parameters that are defined at the OpenAPI Object’s components/parameters.
|
||||
Parameters : Choice<Parameter, Reference> list option
|
||||
/// The request body applicable for this operation.
|
||||
/// The requestBody is only supported in HTTP methods where the HTTP 1.1 specification [RFC7231] Section 4.3.1 has explicitly defined semantics for request bodies.
|
||||
/// In other cases where the HTTP spec is vague, requestBody SHALL be ignored by consumers.
|
||||
RequestBody : Choice<RequestBody, Reference> option
|
||||
/// The list of possible responses as they are returned from executing this operation.
|
||||
Responses : Responses
|
||||
/// A map of possible out-of band callbacks related to the parent operation.
|
||||
/// The key is a unique identifier for the Callback Object.
|
||||
/// Each value in the map is a Callback Object that describes a request that may be initiated by the API provider and the expected responses.
|
||||
/// The key value used to identify the callback object is an expression, evaluated at runtime, that identifies a URL to use for the callback operation.
|
||||
Callbacks : Map<string, Choice<Callback, Reference>> option
|
||||
/// Default value is "false".
|
||||
Deprecated : bool option
|
||||
/// A declaration of which security mechanisms can be used for this operation.
|
||||
/// The list of values includes alternative security requirement objects that can be used.
|
||||
/// Only one of the security requirement objects need to be satisfied to authorize a request.
|
||||
/// This definition overrides any declared top-level security.
|
||||
/// To remove a top-level security declaration, an empty array can be used.
|
||||
Security : SecurityRequirement list option
|
||||
/// An alternative server array to service this operation.
|
||||
/// If an alternative server object is specified at the Path Item Object or Root level, it will be overridden by this value.
|
||||
Servers : Server list option
|
||||
}
|
||||
|
||||
and PathItem =
|
||||
{
|
||||
/// Allows for an external definition of this path item.
|
||||
/// The referenced structure MUST be in the format of a Path Item Object.
|
||||
/// If there are conflicts between the referenced definition and this Path Item’s definition, the behavior is undefined.
|
||||
Ref : string option
|
||||
/// A string summary, intended to apply to all operations in this path.
|
||||
Summary : string option
|
||||
/// A string description, intended to apply to all operations in this path, possibly in CommonMark
|
||||
Description : string option
|
||||
/// A definition of a GET operation on this path.
|
||||
Get : Operation option
|
||||
/// A definition of a PUT operation on this path.
|
||||
Put : Operation option
|
||||
/// A definition of a POST operation on this path.
|
||||
Post : Operation option
|
||||
/// A definition of a DELETE operation on this path.
|
||||
Delete : Operation option
|
||||
/// A definition of an OPTIONS operation on this path.
|
||||
Options : Operation option
|
||||
/// A definition of a HEAD operation on this path.
|
||||
Head : Operation option
|
||||
/// A definition of a PATCH operation on this path.
|
||||
Patch : Operation option
|
||||
/// A definition of a TRACE operation on this path.
|
||||
Trace : Operation option
|
||||
/// An alternative server array to service all operations in this path.
|
||||
Servers : Server list option
|
||||
/// A list of parameters that are applicable for all the operations described under this path.
|
||||
/// These parameters can be overridden at the operation level, but cannot be removed there.
|
||||
/// The list MUST NOT include duplicated parameters.
|
||||
/// A unique parameter is defined by a combination of a name and location.
|
||||
/// The list can use the Reference Object to link to parameters that are defined at the OpenAPI Object’s components/parameters.
|
||||
Parameters : Choice<Parameter, Reference> list option
|
||||
}
|
||||
|
||||
type Paths =
|
||||
{
|
||||
/// A relative path to an individual endpoint.
|
||||
/// The field name MUST begin with a slash.
|
||||
/// The path is appended (no relative URL resolution) to the expanded URL from the Server Object’s url field in order to construct the full URL.
|
||||
/// Path templating is allowed.
|
||||
/// When matching URLs, concrete (non-templated) paths would be matched before their templated counterparts.
|
||||
/// Templated paths with the same hierarchy but different templated names MUST NOT exist as they are identical.
|
||||
/// In case of ambiguous matching, it’s up to the tooling to decide which one to use.
|
||||
Fields : Map<string, PathItem> option
|
||||
}
|
||||
|
||||
type Components =
|
||||
{
|
||||
Schemas : Map<string, Choice<Schema, Reference>>
|
||||
Responses : Map<string, Choice<Response, Reference>>
|
||||
Parameters : Map<string, Choice<Parameter, Reference>>
|
||||
Examples : Map<string, Choice<Example, Reference>>
|
||||
RequestBodies : Map<string, Choice<RequestBody, Reference>>
|
||||
Headers : Map<string, Choice<Header, Reference>>
|
||||
SecuritySchemes : Map<string, Choice<SecurityScheme, Reference>>
|
||||
Links : Map<string, Choice<Link, Reference>>
|
||||
Callbacks : Map<string, Choice<Callback, Reference>>
|
||||
}
|
||||
|
||||
type OpenApiSpec =
|
||||
{
|
||||
OpenApi : Version
|
||||
Info : OpenApiInfo
|
||||
Servers : Server list option
|
||||
Paths : Paths
|
||||
Components : Components option
|
||||
Security : SecurityRequirement list option
|
||||
Tags : Tag list option
|
||||
ExternalDocs : ExternalDocumentation option
|
||||
}
|
23
WoofWare.Myriad.Plugins/Parameters.fs
Normal file
23
WoofWare.Myriad.Plugins/Parameters.fs
Normal file
@@ -0,0 +1,23 @@
|
||||
namespace WoofWare.Myriad.Plugins
|
||||
|
||||
type internal DesiredGenerator =
|
||||
| InterfaceMock of isInternal : bool option
|
||||
| JsonParse of extensionMethod : bool option
|
||||
| JsonSerialize of extensionMethod : bool option
|
||||
| HttpClient of extensionMethod : bool option
|
||||
|
||||
static member Parse (s : string) =
|
||||
match s with
|
||||
| "GenerateMock" -> DesiredGenerator.InterfaceMock None
|
||||
| "GenerateMock(true)" -> DesiredGenerator.InterfaceMock (Some true)
|
||||
| "GenerateMock(false)" -> DesiredGenerator.InterfaceMock (Some false)
|
||||
| "JsonParse" -> DesiredGenerator.JsonParse None
|
||||
| "JsonParse(true)" -> DesiredGenerator.JsonParse (Some true)
|
||||
| "JsonParse(false)" -> DesiredGenerator.JsonParse (Some false)
|
||||
| "JsonSerialize" -> DesiredGenerator.JsonSerialize None
|
||||
| "JsonSerialize(true)" -> DesiredGenerator.JsonSerialize (Some true)
|
||||
| "JsonSerialize(false)" -> DesiredGenerator.JsonSerialize (Some false)
|
||||
| "HttpClient" -> DesiredGenerator.HttpClient None
|
||||
| "HttpClient(true)" -> DesiredGenerator.HttpClient (Some true)
|
||||
| "HttpClient(false)" -> DesiredGenerator.HttpClient (Some false)
|
||||
| _ -> failwith $"Failed to parse as a generator specification: %s{s}"
|
@@ -1,14 +1,13 @@
|
||||
namespace WoofWare.Myriad.Plugins
|
||||
|
||||
open System
|
||||
open Fantomas.FCS.Syntax
|
||||
open Fantomas.FCS.SyntaxTrivia
|
||||
open Fantomas.FCS.Xml
|
||||
open Myriad.Core
|
||||
open WoofWare.Whippet.Fantomas
|
||||
|
||||
[<RequireQualifiedAccess>]
|
||||
module internal RemoveOptionsGenerator =
|
||||
open Fantomas.FCS.Text.Range
|
||||
open Myriad.Core.Ast
|
||||
|
||||
let private removeOption (s : SynField) : SynField =
|
||||
let (SynField.SynField (synAttributeLists,
|
||||
@@ -39,15 +38,15 @@ module internal RemoveOptionsGenerator =
|
||||
trivia
|
||||
)
|
||||
|
||||
// TODO: this option seems a bit odd
|
||||
let createType
|
||||
(xmlDoc : PreXmlDoc option)
|
||||
(accessibility : SynAccess option)
|
||||
(generics : SynTyparDecls option)
|
||||
(fields : SynField list)
|
||||
: SynModuleDecl
|
||||
=
|
||||
let fields : SynField list = fields |> List.map removeOption
|
||||
let name = Ident.Create "Short"
|
||||
let name = Ident.create "Short"
|
||||
|
||||
let record =
|
||||
{
|
||||
@@ -56,138 +55,85 @@ module internal RemoveOptionsGenerator =
|
||||
Members = None
|
||||
XmlDoc = xmlDoc
|
||||
Generics = generics
|
||||
Accessibility = accessibility
|
||||
TypeAccessibility = accessibility
|
||||
ImplAccessibility = None
|
||||
Attributes = []
|
||||
}
|
||||
|
||||
let typeDecl = AstHelper.defineRecordType record
|
||||
let typeDecl = RecordType.ToAst record
|
||||
|
||||
SynModuleDecl.Types ([ typeDecl ], range0)
|
||||
|
||||
let createMaker (withOptionsType : LongIdent) (withoutOptionsType : LongIdent) (fields : SynField list) =
|
||||
let xmlDoc = PreXmlDoc.Create " Remove the optional members of the input."
|
||||
let createMaker (withOptionsType : LongIdent) (withoutOptionsType : Ident) (fields : SynFieldData<Ident> list) =
|
||||
let xmlDoc = PreXmlDoc.create "Remove the optional members of the input."
|
||||
|
||||
let returnInfo =
|
||||
SynBindingReturnInfo.Create (SynType.LongIdent (SynLongIdent.CreateFromLongIdent withOptionsType))
|
||||
|
||||
let inputArg = Ident.Create "input"
|
||||
let functionName = Ident.Create "shorten"
|
||||
|
||||
let inputVal =
|
||||
SynValData.SynValData (
|
||||
None,
|
||||
SynValInfo.SynValInfo ([ [ SynArgInfo.CreateId functionName ] ], SynArgInfo.Empty),
|
||||
Some inputArg
|
||||
)
|
||||
let inputArg = Ident.create "input"
|
||||
let functionName = Ident.create "shorten"
|
||||
|
||||
let body =
|
||||
fields
|
||||
|> List.map (fun (SynField (_, _, id, fieldType, _, _, _, _, _)) ->
|
||||
let id =
|
||||
match id with
|
||||
| None -> failwith "Expected record field to have an identifying name"
|
||||
| Some id -> id
|
||||
|
||||
|> List.map (fun fieldData ->
|
||||
let accessor =
|
||||
SynExpr.LongIdent (false, SynLongIdent ([ inputArg ; id ], [ range0 ], []), None, range0)
|
||||
SynExpr.LongIdent (
|
||||
false,
|
||||
SynLongIdent ([ inputArg ; fieldData.Ident ], [ range0 ], []),
|
||||
None,
|
||||
range0
|
||||
)
|
||||
|
||||
let body =
|
||||
match fieldType with
|
||||
match fieldData.Type with
|
||||
| OptionType _ ->
|
||||
SynExpr.CreateApp (
|
||||
SynExpr.CreateAppInfix (
|
||||
SynExpr.LongIdent (
|
||||
false,
|
||||
SynLongIdent.SynLongIdent (
|
||||
[ Ident.Create "op_PipeRight" ],
|
||||
[],
|
||||
[ Some (IdentTrivia.OriginalNotation "|>") ]
|
||||
),
|
||||
None,
|
||||
range0
|
||||
),
|
||||
accessor
|
||||
),
|
||||
SynExpr.CreateApp (
|
||||
SynExpr.CreateLongIdent (SynLongIdent.CreateString "Option.defaultWith"),
|
||||
SynExpr.CreateLongIdent (
|
||||
SynLongIdent.CreateFromLongIdent (
|
||||
withoutOptionsType @ [ Ident.Create (sprintf "Default%s" id.idText) ]
|
||||
)
|
||||
)
|
||||
)
|
||||
accessor
|
||||
|> SynExpr.pipeThroughFunction (
|
||||
SynExpr.applyFunction
|
||||
(SynExpr.createLongIdent [ "Option" ; "defaultWith" ])
|
||||
(SynExpr.createLongIdent' (
|
||||
[ withoutOptionsType ]
|
||||
@ [ Ident.create (sprintf "Default%s" fieldData.Ident.idText) ]
|
||||
))
|
||||
)
|
||||
| _ -> accessor
|
||||
|
||||
(SynLongIdent.CreateFromLongIdent [ id ], true), Some body
|
||||
SynLongIdent.createI fieldData.Ident, body
|
||||
)
|
||||
|> AstHelper.instantiateRecord
|
||||
|> SynExpr.createRecord None
|
||||
|
||||
let pattern =
|
||||
SynPat.LongIdent (
|
||||
SynLongIdent.CreateFromLongIdent [ functionName ],
|
||||
None,
|
||||
None,
|
||||
SynArgPats.Pats
|
||||
[
|
||||
SynPat.CreateTyped (
|
||||
SynPat.CreateNamed inputArg,
|
||||
SynType.LongIdent (SynLongIdent.CreateFromLongIdent withoutOptionsType)
|
||||
)
|
||||
|> SynPat.CreateParen
|
||||
],
|
||||
None,
|
||||
range0
|
||||
)
|
||||
SynBinding.basic
|
||||
[ functionName ]
|
||||
[
|
||||
SynPat.named inputArg.idText
|
||||
|> SynPat.annotateType (SynType.LongIdent (SynLongIdent.createI withoutOptionsType))
|
||||
]
|
||||
body
|
||||
|> SynBinding.withXmlDoc xmlDoc
|
||||
|> SynBinding.withReturnAnnotation (SynType.LongIdent (SynLongIdent.create withOptionsType))
|
||||
|> SynModuleDecl.createLet
|
||||
|
||||
let binding =
|
||||
SynBinding.Let (
|
||||
isInline = false,
|
||||
isMutable = false,
|
||||
xmldoc = xmlDoc,
|
||||
returnInfo = returnInfo,
|
||||
expr = body,
|
||||
valData = inputVal,
|
||||
pattern = pattern
|
||||
)
|
||||
let createRecordModule (namespaceId : LongIdent) (typeDefn : RecordType) =
|
||||
let fieldData = typeDefn.Fields |> List.map SynField.extractWithIdent
|
||||
|
||||
SynModuleDecl.CreateLet [ binding ]
|
||||
let decls =
|
||||
[
|
||||
createType typeDefn.XmlDoc typeDefn.TypeAccessibility typeDefn.Generics typeDefn.Fields
|
||||
createMaker [ Ident.create "Short" ] typeDefn.Name fieldData
|
||||
]
|
||||
|
||||
let createRecordModule (namespaceId : LongIdent) (typeDefn : SynTypeDefn) =
|
||||
let (SynTypeDefn (synComponentInfo, synTypeDefnRepr, _members, _implicitCtor, _, _)) =
|
||||
typeDefn
|
||||
let xmlDoc =
|
||||
sprintf "Module containing an option-truncated version of the %s type" typeDefn.Name.idText
|
||||
|> PreXmlDoc.create
|
||||
|
||||
let (SynComponentInfo (_attributes, typeParams, _constraints, recordId, doc, _preferPostfix, _access, _)) =
|
||||
synComponentInfo
|
||||
let info =
|
||||
SynComponentInfo.create typeDefn.Name
|
||||
|> SynComponentInfo.withDocString xmlDoc
|
||||
|> SynComponentInfo.addAttributes [ SynAttribute.compilationRepresentation ]
|
||||
|> SynComponentInfo.addAttributes [ SynAttribute.requireQualifiedAccess ]
|
||||
|
||||
match synTypeDefnRepr with
|
||||
| SynTypeDefnRepr.Simple (SynTypeDefnSimpleRepr.Record (accessibility, recordFields, _recordRange), _) ->
|
||||
SynModuleDecl.nestedModule info decls
|
||||
|> List.singleton
|
||||
|> SynModuleOrNamespace.createNamespace namespaceId
|
||||
|
||||
let decls =
|
||||
[
|
||||
createType (Some doc) accessibility typeParams recordFields
|
||||
createMaker [ Ident.Create "Short" ] recordId recordFields
|
||||
]
|
||||
|
||||
let attributes =
|
||||
[
|
||||
SynAttributeList.Create (SynAttribute.RequireQualifiedAccess ())
|
||||
SynAttributeList.Create SynAttribute.compilationRepresentation
|
||||
]
|
||||
|
||||
let xmlDoc =
|
||||
recordId
|
||||
|> Seq.map (fun i -> i.idText)
|
||||
|> String.concat "."
|
||||
|> sprintf " Module containing an option-truncated version of the %s type"
|
||||
|> PreXmlDoc.Create
|
||||
|
||||
let info =
|
||||
SynComponentInfo.Create (recordId, attributes = attributes, xmldoc = xmlDoc)
|
||||
|
||||
let mdl = SynModuleDecl.CreateNestedModule (info, decls)
|
||||
|
||||
SynModuleOrNamespace.CreateNamespace (namespaceId, decls = [ mdl ])
|
||||
| _ -> failwithf "Not a record type"
|
||||
open Myriad.Core
|
||||
|
||||
/// Myriad generator that stamps out a record with option types stripped
|
||||
/// from the fields at the top level.
|
||||
@@ -201,24 +147,31 @@ type RemoveOptionsGenerator () =
|
||||
let ast, _ =
|
||||
Ast.fromFilename context.InputFilename |> Async.RunSynchronously |> Array.head
|
||||
|
||||
let records = Ast.extractRecords ast
|
||||
let records = Ast.getRecords ast
|
||||
|
||||
let namespaceAndRecords =
|
||||
records
|
||||
|> List.choose (fun (ns, types) ->
|
||||
match types |> List.filter Ast.hasAttribute<RemoveOptionsAttribute> with
|
||||
| [] -> None
|
||||
| types -> Some (ns, types)
|
||||
|> List.collect (fun (ns, ty) ->
|
||||
ty
|
||||
|> List.filter (fun record ->
|
||||
record.Attributes
|
||||
|> List.exists (fun attr ->
|
||||
attr.TypeName.LongIdent
|
||||
|> List.last
|
||||
|> _.idText
|
||||
|> fun s ->
|
||||
if s.EndsWith ("Attribute", StringComparison.Ordinal) then
|
||||
s
|
||||
else
|
||||
$"%s{s}Attribute"
|
||||
|> (=) typeof<RemoveOptionsAttribute>.Name
|
||||
)
|
||||
)
|
||||
|> List.map (fun ty -> ns, ty)
|
||||
)
|
||||
|
||||
let modules =
|
||||
namespaceAndRecords
|
||||
|> List.collect (fun (ns, records) ->
|
||||
records
|
||||
|> List.map (fun record ->
|
||||
let recordModule = RemoveOptionsGenerator.createRecordModule ns record
|
||||
recordModule
|
||||
)
|
||||
)
|
||||
|> List.map (fun (ns, record) -> RemoveOptionsGenerator.createRecordModule ns record)
|
||||
|
||||
Output.Ast modules
|
||||
|
@@ -1,7 +1,56 @@
|
||||
WoofWare.Myriad.Plugins.ArgParserGenerator inherit obj, implements Myriad.Core.IMyriadGenerator
|
||||
WoofWare.Myriad.Plugins.ArgParserGenerator..ctor [constructor]: unit
|
||||
WoofWare.Myriad.Plugins.CreateCatamorphismGenerator inherit obj, implements Myriad.Core.IMyriadGenerator
|
||||
WoofWare.Myriad.Plugins.CreateCatamorphismGenerator..ctor [constructor]: unit
|
||||
WoofWare.Myriad.Plugins.HttpClientGenerator inherit obj, implements Myriad.Core.IMyriadGenerator
|
||||
WoofWare.Myriad.Plugins.HttpClientGenerator..ctor [constructor]: unit
|
||||
WoofWare.Myriad.Plugins.HttpMethod inherit obj, implements WoofWare.Myriad.Plugins.HttpMethod System.IEquatable, System.Collections.IStructuralEquatable, WoofWare.Myriad.Plugins.HttpMethod System.IComparable, System.IComparable, System.Collections.IStructuralComparable - union type with 8 cases
|
||||
WoofWare.Myriad.Plugins.HttpMethod+Tags inherit obj
|
||||
WoofWare.Myriad.Plugins.HttpMethod+Tags.Delete [static field]: int = 2
|
||||
WoofWare.Myriad.Plugins.HttpMethod+Tags.Get [static field]: int = 0
|
||||
WoofWare.Myriad.Plugins.HttpMethod+Tags.Head [static field]: int = 5
|
||||
WoofWare.Myriad.Plugins.HttpMethod+Tags.Options [static field]: int = 4
|
||||
WoofWare.Myriad.Plugins.HttpMethod+Tags.Patch [static field]: int = 3
|
||||
WoofWare.Myriad.Plugins.HttpMethod+Tags.Post [static field]: int = 1
|
||||
WoofWare.Myriad.Plugins.HttpMethod+Tags.Put [static field]: int = 6
|
||||
WoofWare.Myriad.Plugins.HttpMethod+Tags.Trace [static field]: int = 7
|
||||
WoofWare.Myriad.Plugins.HttpMethod.Delete [static property]: [read-only] WoofWare.Myriad.Plugins.HttpMethod
|
||||
WoofWare.Myriad.Plugins.HttpMethod.Equals [method]: (WoofWare.Myriad.Plugins.HttpMethod, System.Collections.IEqualityComparer) -> bool
|
||||
WoofWare.Myriad.Plugins.HttpMethod.Get [static property]: [read-only] WoofWare.Myriad.Plugins.HttpMethod
|
||||
WoofWare.Myriad.Plugins.HttpMethod.get_Delete [static method]: unit -> WoofWare.Myriad.Plugins.HttpMethod
|
||||
WoofWare.Myriad.Plugins.HttpMethod.get_Get [static method]: unit -> WoofWare.Myriad.Plugins.HttpMethod
|
||||
WoofWare.Myriad.Plugins.HttpMethod.get_Head [static method]: unit -> WoofWare.Myriad.Plugins.HttpMethod
|
||||
WoofWare.Myriad.Plugins.HttpMethod.get_IsDelete [method]: unit -> bool
|
||||
WoofWare.Myriad.Plugins.HttpMethod.get_IsGet [method]: unit -> bool
|
||||
WoofWare.Myriad.Plugins.HttpMethod.get_IsHead [method]: unit -> bool
|
||||
WoofWare.Myriad.Plugins.HttpMethod.get_IsOptions [method]: unit -> bool
|
||||
WoofWare.Myriad.Plugins.HttpMethod.get_IsPatch [method]: unit -> bool
|
||||
WoofWare.Myriad.Plugins.HttpMethod.get_IsPost [method]: unit -> bool
|
||||
WoofWare.Myriad.Plugins.HttpMethod.get_IsPut [method]: unit -> bool
|
||||
WoofWare.Myriad.Plugins.HttpMethod.get_IsTrace [method]: unit -> bool
|
||||
WoofWare.Myriad.Plugins.HttpMethod.get_Options [static method]: unit -> WoofWare.Myriad.Plugins.HttpMethod
|
||||
WoofWare.Myriad.Plugins.HttpMethod.get_Patch [static method]: unit -> WoofWare.Myriad.Plugins.HttpMethod
|
||||
WoofWare.Myriad.Plugins.HttpMethod.get_Post [static method]: unit -> WoofWare.Myriad.Plugins.HttpMethod
|
||||
WoofWare.Myriad.Plugins.HttpMethod.get_Put [static method]: unit -> WoofWare.Myriad.Plugins.HttpMethod
|
||||
WoofWare.Myriad.Plugins.HttpMethod.get_Tag [method]: unit -> int
|
||||
WoofWare.Myriad.Plugins.HttpMethod.get_Trace [static method]: unit -> WoofWare.Myriad.Plugins.HttpMethod
|
||||
WoofWare.Myriad.Plugins.HttpMethod.Head [static property]: [read-only] WoofWare.Myriad.Plugins.HttpMethod
|
||||
WoofWare.Myriad.Plugins.HttpMethod.IsDelete [property]: [read-only] bool
|
||||
WoofWare.Myriad.Plugins.HttpMethod.IsGet [property]: [read-only] bool
|
||||
WoofWare.Myriad.Plugins.HttpMethod.IsHead [property]: [read-only] bool
|
||||
WoofWare.Myriad.Plugins.HttpMethod.IsOptions [property]: [read-only] bool
|
||||
WoofWare.Myriad.Plugins.HttpMethod.IsPatch [property]: [read-only] bool
|
||||
WoofWare.Myriad.Plugins.HttpMethod.IsPost [property]: [read-only] bool
|
||||
WoofWare.Myriad.Plugins.HttpMethod.IsPut [property]: [read-only] bool
|
||||
WoofWare.Myriad.Plugins.HttpMethod.IsTrace [property]: [read-only] bool
|
||||
WoofWare.Myriad.Plugins.HttpMethod.Options [static property]: [read-only] WoofWare.Myriad.Plugins.HttpMethod
|
||||
WoofWare.Myriad.Plugins.HttpMethod.Parse [static method]: string -> WoofWare.Myriad.Plugins.HttpMethod
|
||||
WoofWare.Myriad.Plugins.HttpMethod.Patch [static property]: [read-only] WoofWare.Myriad.Plugins.HttpMethod
|
||||
WoofWare.Myriad.Plugins.HttpMethod.Post [static property]: [read-only] WoofWare.Myriad.Plugins.HttpMethod
|
||||
WoofWare.Myriad.Plugins.HttpMethod.Put [static property]: [read-only] WoofWare.Myriad.Plugins.HttpMethod
|
||||
WoofWare.Myriad.Plugins.HttpMethod.Tag [property]: [read-only] int
|
||||
WoofWare.Myriad.Plugins.HttpMethod.ToDotNet [method]: unit -> System.Net.Http.HttpMethod
|
||||
WoofWare.Myriad.Plugins.HttpMethod.Trace [static property]: [read-only] WoofWare.Myriad.Plugins.HttpMethod
|
||||
WoofWare.Myriad.Plugins.InterfaceMockGenerator inherit obj, implements Myriad.Core.IMyriadGenerator
|
||||
WoofWare.Myriad.Plugins.InterfaceMockGenerator..ctor [constructor]: unit
|
||||
WoofWare.Myriad.Plugins.JsonParseGenerator inherit obj, implements Myriad.Core.IMyriadGenerator
|
||||
@@ -9,4 +58,6 @@ WoofWare.Myriad.Plugins.JsonParseGenerator..ctor [constructor]: unit
|
||||
WoofWare.Myriad.Plugins.JsonSerializeGenerator inherit obj, implements Myriad.Core.IMyriadGenerator
|
||||
WoofWare.Myriad.Plugins.JsonSerializeGenerator..ctor [constructor]: unit
|
||||
WoofWare.Myriad.Plugins.RemoveOptionsGenerator inherit obj, implements Myriad.Core.IMyriadGenerator
|
||||
WoofWare.Myriad.Plugins.RemoveOptionsGenerator..ctor [constructor]: unit
|
||||
WoofWare.Myriad.Plugins.RemoveOptionsGenerator..ctor [constructor]: unit
|
||||
WoofWare.Myriad.Plugins.SwaggerClientGenerator inherit obj, implements Myriad.Core.IMyriadGenerator
|
||||
WoofWare.Myriad.Plugins.SwaggerClientGenerator..ctor [constructor]: unit
|
735
WoofWare.Myriad.Plugins/SwaggerClientGenerator.fs
Normal file
735
WoofWare.Myriad.Plugins/SwaggerClientGenerator.fs
Normal file
@@ -0,0 +1,735 @@
|
||||
namespace WoofWare.Myriad.Plugins
|
||||
|
||||
open System.Collections.Generic
|
||||
open System.Threading
|
||||
open Fantomas.FCS.Syntax
|
||||
open Fantomas.FCS.Xml
|
||||
open Fantomas.FCS.Text.Range
|
||||
open WoofWare.Whippet.Fantomas
|
||||
|
||||
type internal SwaggerClientConfig =
|
||||
{
|
||||
/// Additionally create a mock with `InterfaceMockGenerator`, with the given boolean arg.
|
||||
/// (`None` means "no mock".)
|
||||
CreateMock : bool option
|
||||
ClassName : string
|
||||
}
|
||||
|
||||
type internal Produces =
|
||||
// TODO: this will cope with decoding JSON, plain text, etc
|
||||
| Produces of string
|
||||
| OctetStream
|
||||
|
||||
type internal Endpoint =
|
||||
{
|
||||
DocString : PreXmlDoc
|
||||
Produces : Produces
|
||||
ReturnType : SwaggerV2.Definition
|
||||
Method : WoofWare.Myriad.Plugins.HttpMethod
|
||||
Operation : SwaggerV2.OperationId
|
||||
Parameters : SwaggerV2.SwaggerParameter list
|
||||
Endpoint : string
|
||||
}
|
||||
|
||||
type internal TypeEntry =
|
||||
{
|
||||
/// If we had to define a type for this, here it is.
|
||||
FSharpDefinition : SynTypeDefn option
|
||||
/// SynType you use in e.g. a type annotation to refer to this type in F# code.
|
||||
Signature : SynType
|
||||
}
|
||||
|
||||
type internal Types =
|
||||
{
|
||||
ByHandle : IReadOnlyDictionary<string, TypeEntry>
|
||||
ByDefinition : IReadOnlyDictionary<SwaggerV2.Definition, TypeEntry>
|
||||
}
|
||||
|
||||
[<RequireQualifiedAccess>]
|
||||
module internal SwaggerClientGenerator =
|
||||
|
||||
let internal log (_ : string) = ()
|
||||
|
||||
let renderType (types : Types) (defn : SwaggerV2.Definition) : SynType option =
|
||||
match types.ByDefinition.TryGetValue defn with
|
||||
| true, v -> Some v.Signature
|
||||
| false, _ ->
|
||||
|
||||
match defn with
|
||||
| SwaggerV2.Definition.Handle h ->
|
||||
match types.ByHandle.TryGetValue h with
|
||||
| false, _ -> None
|
||||
| true, v -> Some v.Signature
|
||||
| SwaggerV2.Definition.Object _ -> failwith "should not hit"
|
||||
| SwaggerV2.Definition.Array _ -> failwith "should not hit"
|
||||
| SwaggerV2.Definition.Unspecified -> failwith "should not hit"
|
||||
| SwaggerV2.Definition.String -> SynType.string |> Some
|
||||
| SwaggerV2.Definition.Boolean -> SynType.bool |> Some
|
||||
| SwaggerV2.Definition.Integer _ -> SynType.int |> Some
|
||||
| SwaggerV2.Definition.File -> SynType.createLongIdent' [ "System" ; "IO" ; "Stream" ] |> Some
|
||||
|
||||
/// Returns None if we lacked the information required to do this.
|
||||
/// bigCache is a map of e.g. {"securityDefinition": {Defn : F# type}}.
|
||||
let rec defnToType
|
||||
(anonymousTypeCount : int ref)
|
||||
(handlesMap : Dictionary<string, TypeEntry>)
|
||||
(bigCache : Dictionary<string, Dictionary<SwaggerV2.Definition, TypeEntry>>)
|
||||
(thisKey : string)
|
||||
(typeName : string option)
|
||||
(d : SwaggerV2.Definition)
|
||||
: TypeEntry option
|
||||
=
|
||||
let cache =
|
||||
match bigCache.TryGetValue thisKey with
|
||||
| false, _ ->
|
||||
let d = Dictionary ()
|
||||
bigCache.Add (thisKey, d)
|
||||
d
|
||||
| true, d -> d
|
||||
|
||||
let handleKey =
|
||||
match typeName with
|
||||
| None -> None
|
||||
| Some typeName -> $"#/%s{thisKey}/%s{typeName}" |> Some
|
||||
|
||||
match handleKey with
|
||||
| Some hk when handlesMap.ContainsKey hk ->
|
||||
let result = handlesMap.[hk]
|
||||
cache.[d] <- result
|
||||
Some result
|
||||
|
||||
| _ ->
|
||||
|
||||
match cache.TryGetValue d with
|
||||
| true, v ->
|
||||
match handleKey with
|
||||
| None -> ()
|
||||
| Some key -> handlesMap.Add (key, v)
|
||||
|
||||
Some v
|
||||
| false, _ ->
|
||||
|
||||
let result =
|
||||
match d with
|
||||
| SwaggerV2.Definition.Object obj ->
|
||||
let requiredFields = obj.Required |> Option.defaultValue [] |> Set.ofList
|
||||
|
||||
let namedProperties =
|
||||
obj.Properties
|
||||
|> Option.map Seq.cast
|
||||
|> Option.defaultValue Seq.empty
|
||||
|> Seq.map (fun (KeyValue (fieldName, defn)) ->
|
||||
// TODO this is a horrible hack and is incomplete, e.g. if we contain an array of ourself
|
||||
// Special case for when this is a reference to this very type
|
||||
let isOurself =
|
||||
match defn with
|
||||
| SwaggerV2.Definition.Handle h ->
|
||||
match h.Split '/' with
|
||||
| [| "#" ; location ; ty |] when location = thisKey && Some ty = typeName ->
|
||||
SynType.named ty |> Some
|
||||
| _ -> None
|
||||
| _ -> None
|
||||
|
||||
let jsonPropertyName =
|
||||
SynExpr.CreateConst (fieldName : string)
|
||||
|> SynAttribute.create (
|
||||
SynLongIdent.createS'
|
||||
[ "System" ; "Text" ; "Json" ; "Serialization" ; "JsonPropertyName" ]
|
||||
)
|
||||
|
||||
match isOurself with
|
||||
| Some alreadyDone ->
|
||||
let ty =
|
||||
if Set.contains fieldName requiredFields then
|
||||
alreadyDone
|
||||
else
|
||||
SynType.option alreadyDone
|
||||
|
||||
{
|
||||
Attrs = [ jsonPropertyName ]
|
||||
Type = ty
|
||||
Ident = Some (Ident.createSanitisedTypeName fieldName)
|
||||
}
|
||||
|> SynField.make
|
||||
|> Some
|
||||
| None ->
|
||||
|
||||
let defn' = defnToType anonymousTypeCount handlesMap bigCache thisKey None defn
|
||||
|
||||
match defn' with
|
||||
| None -> None
|
||||
| Some defn' ->
|
||||
let ty =
|
||||
if Set.contains fieldName requiredFields then
|
||||
defn'.Signature
|
||||
else
|
||||
defn'.Signature |> SynType.option
|
||||
|
||||
{
|
||||
Attrs = [ jsonPropertyName ]
|
||||
Ident = Ident.createSanitisedTypeName fieldName |> Some
|
||||
Type = ty
|
||||
}
|
||||
|> SynField.make
|
||||
|> Some
|
||||
)
|
||||
|> Seq.toList
|
||||
|
||||
let additionalProperties =
|
||||
match obj.AdditionalProperties with
|
||||
| None ->
|
||||
{
|
||||
Attrs =
|
||||
[
|
||||
SynAttribute.create
|
||||
(SynLongIdent.createS'
|
||||
[ "System" ; "Text" ; "Json" ; "Serialization" ; "JsonExtensionData" ])
|
||||
(SynExpr.CreateConst ())
|
||||
]
|
||||
Ident = Ident.create "AdditionalProperties" |> Some
|
||||
Type =
|
||||
SynType.app'
|
||||
(SynType.createLongIdent' [ "System" ; "Collections" ; "Generic" ; "Dictionary" ])
|
||||
[
|
||||
SynType.string
|
||||
SynType.createLongIdent' [ "System" ; "Text" ; "Json" ; "Nodes" ; "JsonNode" ]
|
||||
]
|
||||
}
|
||||
|> SynField.make
|
||||
|> List.singleton
|
||||
|> Some
|
||||
| Some SwaggerV2.AdditionalProperties.Never -> Some []
|
||||
| Some (SwaggerV2.AdditionalProperties.Constrained defn) ->
|
||||
let defn' = defnToType anonymousTypeCount handlesMap bigCache thisKey None defn
|
||||
|
||||
match defn' with
|
||||
| None -> None
|
||||
| Some defn' ->
|
||||
{
|
||||
Attrs =
|
||||
[
|
||||
SynAttribute.create
|
||||
(SynLongIdent.createS'
|
||||
[ "System" ; "Text" ; "Json" ; "Serialization" ; "JsonExtensionData" ])
|
||||
(SynExpr.CreateConst ())
|
||||
]
|
||||
Ident = Ident.create "AdditionalProperties" |> Some
|
||||
Type =
|
||||
SynType.app'
|
||||
(SynType.createLongIdent'
|
||||
[ "System" ; "Collections" ; "Generic" ; "Dictionary" ])
|
||||
[ SynType.string ; defn'.Signature ]
|
||||
}
|
||||
|> SynField.make
|
||||
|> List.singleton
|
||||
|> Some
|
||||
|
||||
match additionalProperties with
|
||||
| None -> None
|
||||
| Some additionalProperties ->
|
||||
|
||||
match List.allSome namedProperties with
|
||||
| None -> None
|
||||
| Some namedProperties ->
|
||||
|
||||
let fSharpTypeName =
|
||||
match typeName with
|
||||
| None -> $"Type%i{Interlocked.Increment anonymousTypeCount}"
|
||||
| Some typeName -> typeName
|
||||
|
||||
let properties = additionalProperties @ namedProperties
|
||||
|
||||
let properties =
|
||||
if properties.IsEmpty then
|
||||
// sigh, they didn't give us any properties at all; let's make one up
|
||||
{
|
||||
Attrs = []
|
||||
Ident = Some (Ident.create "_SchemaUnspecified")
|
||||
Type = SynType.obj
|
||||
}
|
||||
|> SynField.make
|
||||
|> List.singleton
|
||||
else
|
||||
properties
|
||||
|
||||
let defn =
|
||||
let sci =
|
||||
SynComponentInfo.create (Ident.createSanitisedTypeName fSharpTypeName)
|
||||
|> SynComponentInfo.addAttributes
|
||||
[
|
||||
SynAttribute.create (SynLongIdent.createS' [ "JsonParse" ]) (SynExpr.CreateConst true)
|
||||
SynAttribute.create
|
||||
(SynLongIdent.createS' [ "JsonSerialize" ])
|
||||
(SynExpr.CreateConst true)
|
||||
]
|
||||
|> fun sci ->
|
||||
match obj.Description with
|
||||
| None -> sci
|
||||
| Some doc -> sci |> SynComponentInfo.withDocString (PreXmlDoc.create doc)
|
||||
|
||||
properties |> SynTypeDefnRepr.record |> SynTypeDefn.create sci
|
||||
|
||||
let defn =
|
||||
{
|
||||
Signature = SynType.named fSharpTypeName
|
||||
FSharpDefinition = Some defn
|
||||
}
|
||||
|
||||
defn |> Some
|
||||
|
||||
| SwaggerV2.Definition.Array elt ->
|
||||
let child = defnToType anonymousTypeCount handlesMap bigCache thisKey None elt.Items
|
||||
|
||||
match child with
|
||||
| None -> None
|
||||
| Some child ->
|
||||
let defn =
|
||||
{
|
||||
Signature = SynType.list child.Signature
|
||||
FSharpDefinition = None
|
||||
}
|
||||
|
||||
Some defn
|
||||
| SwaggerV2.Definition.String ->
|
||||
{
|
||||
Signature = SynType.string
|
||||
FSharpDefinition = None
|
||||
}
|
||||
|> Some
|
||||
| SwaggerV2.Definition.Boolean ->
|
||||
{
|
||||
Signature = SynType.bool
|
||||
FSharpDefinition = None
|
||||
}
|
||||
|> Some
|
||||
| SwaggerV2.Definition.Unspecified ->
|
||||
{
|
||||
Signature = SynType.unit
|
||||
FSharpDefinition = None
|
||||
}
|
||||
|> Some
|
||||
| SwaggerV2.Definition.Integer _ ->
|
||||
{
|
||||
Signature = SynType.createLongIdent' [ "int" ]
|
||||
FSharpDefinition = None
|
||||
}
|
||||
|> Some
|
||||
| SwaggerV2.Definition.File ->
|
||||
{
|
||||
Signature = SynType.createLongIdent' [ "System" ; "IO" ; "Stream" ]
|
||||
FSharpDefinition = None
|
||||
}
|
||||
|> Some
|
||||
| SwaggerV2.Definition.Handle s ->
|
||||
let split = s.Split '/' |> List.ofArray
|
||||
|
||||
match split with
|
||||
| [ "#" ; _location ; _handle ] ->
|
||||
match handlesMap.TryGetValue s with
|
||||
| false, _ -> None
|
||||
| true, computed ->
|
||||
let defn =
|
||||
{
|
||||
FSharpDefinition = None
|
||||
Signature = computed.Signature
|
||||
}
|
||||
|
||||
defn |> Some
|
||||
| _ -> failwith $"we don't know how to deal with object handle %s{s}"
|
||||
|
||||
match result with
|
||||
| None -> None
|
||||
| Some result ->
|
||||
|
||||
match handleKey with
|
||||
| None -> ()
|
||||
| Some handleKey -> handlesMap.Add (handleKey, result)
|
||||
|
||||
cache.Add (d, result)
|
||||
Some result
|
||||
|
||||
let instantiateRequiredTypes (types : Types) : SynModuleDecl =
|
||||
types.ByDefinition
|
||||
|> Seq.choose (fun (KeyValue (_defn, typeEntry)) -> typeEntry.FSharpDefinition)
|
||||
|> Seq.toList
|
||||
|> SynModuleDecl.createTypes
|
||||
|
||||
type private IsIn =
|
||||
| Path of str : string
|
||||
| Query of str : string
|
||||
| Body
|
||||
|
||||
let computeType
|
||||
(options : SwaggerClientConfig)
|
||||
(basePath : string)
|
||||
(types : Types)
|
||||
(clientDocString : PreXmlDoc)
|
||||
(endpoints : Endpoint list)
|
||||
: SynModuleDecl list
|
||||
=
|
||||
endpoints
|
||||
|> List.choose (fun ep ->
|
||||
let name = (Ident.createSanitisedTypeName (ep.Operation.ToString ())).idText
|
||||
|
||||
match renderType types ep.ReturnType with
|
||||
| None ->
|
||||
log $"Skipping %O{ep.Operation}: Couldn't render return type: %O{ep.ReturnType}"
|
||||
None
|
||||
| Some returnType ->
|
||||
|
||||
let pars =
|
||||
ep.Parameters
|
||||
|> List.map (fun par ->
|
||||
let inParam =
|
||||
match par.In with
|
||||
| SwaggerV2.ParameterIn.Unrecognised (f, name) ->
|
||||
log
|
||||
$"Skipping %O{ep.Operation} at %s{ep.Endpoint}: unrecognised In parameter %s{f} with name %s{name}"
|
||||
|
||||
None
|
||||
| SwaggerV2.ParameterIn.Body -> Some IsIn.Body
|
||||
| SwaggerV2.ParameterIn.Query name -> Some (IsIn.Query name)
|
||||
| SwaggerV2.ParameterIn.Path name -> Some (IsIn.Path name)
|
||||
|
||||
match inParam with
|
||||
| None -> None
|
||||
| Some inParam ->
|
||||
|
||||
match renderType types par.Type with
|
||||
| None ->
|
||||
// Couldn't render the return type
|
||||
// failwith "Did not have a type here"
|
||||
log $"Skipping %O{ep.Operation}: Couldn't render parameter: %O{par.Type}"
|
||||
None
|
||||
| Some v -> Some (Ident.createSanitisedParamName par.Name, inParam, v)
|
||||
)
|
||||
|> List.allSome
|
||||
|
||||
match pars with
|
||||
| None -> None
|
||||
| Some pars ->
|
||||
|
||||
let arity =
|
||||
SynValInfo.SynValInfo (
|
||||
[
|
||||
ep.Parameters
|
||||
|> List.map (fun par ->
|
||||
let name = par.Name |> Ident.create |> Some
|
||||
SynArgInfo.SynArgInfo ([], false, name)
|
||||
)
|
||||
|> fun l -> l @ [ SynArgInfo.SynArgInfo ([], true, Some (Ident.create "ct")) ]
|
||||
],
|
||||
SynArgInfo.SynArgInfo ([], false, None)
|
||||
)
|
||||
|
||||
let domain =
|
||||
let ctParam =
|
||||
SynType.signatureParamOfType
|
||||
[]
|
||||
(SynType.createLongIdent' [ "System" ; "Threading" ; "CancellationToken" ])
|
||||
true
|
||||
(Some (Ident.create "ct"))
|
||||
|
||||
let argParams =
|
||||
pars
|
||||
|> List.map (fun (ident, isIn, t) ->
|
||||
let attr : SynAttribute list =
|
||||
match isIn with
|
||||
| IsIn.Path name ->
|
||||
SynAttribute.create
|
||||
(SynLongIdent.createS' [ "RestEase" ; "Path" ])
|
||||
(SynExpr.CreateConst name)
|
||||
|> List.singleton
|
||||
| IsIn.Query name ->
|
||||
SynAttribute.create
|
||||
(SynLongIdent.createS' [ "RestEase" ; "Query" ])
|
||||
(SynExpr.CreateConst name)
|
||||
|> List.singleton
|
||||
| IsIn.Body ->
|
||||
SynAttribute.create
|
||||
(SynLongIdent.createS' [ "RestEase" ; "Body" ])
|
||||
(SynExpr.CreateConst ())
|
||||
|> List.singleton
|
||||
|
||||
SynType.signatureParamOfType attr t false (Some ident)
|
||||
)
|
||||
|
||||
SynType.tupleNoParen (argParams @ [ ctParam ]) |> Option.get
|
||||
|
||||
let attrs =
|
||||
[
|
||||
SynAttribute.create
|
||||
(SynLongIdent.createS' [ "RestEase" ; ep.Method.ToString () ])
|
||||
// Gitea, at least, starts with a `/`, which `Uri` then takes to indicate an absolute path.
|
||||
(SynExpr.CreateConst (ep.Endpoint.TrimStart '/'))
|
||||
|
||||
match ep.Produces with
|
||||
| Produces.Produces contentType ->
|
||||
SynAttribute.create
|
||||
(SynLongIdent.createS' [ "RestEase" ; "Header" ])
|
||||
// Gitea, at least, starts with a `/`, which `Uri` then takes to indicate an absolute path.
|
||||
(SynExpr.tuple [ SynExpr.CreateConst "Content-Type" ; SynExpr.CreateConst contentType ])
|
||||
| Produces.OctetStream ->
|
||||
SynAttribute.create
|
||||
(SynLongIdent.createS' [ "RestEase" ; "Header" ])
|
||||
// Gitea, at least, starts with a `/`, which `Uri` then takes to indicate an absolute path.
|
||||
(SynExpr.tuple
|
||||
[
|
||||
SynExpr.CreateConst "Content-Type"
|
||||
SynExpr.CreateConst "application/octet-stream"
|
||||
])
|
||||
]
|
||||
|
||||
returnType
|
||||
|> SynType.task
|
||||
|> SynType.toFun [ domain ]
|
||||
|> SynMemberDefn.abstractMember attrs (SynIdent.createS name) None arity ep.DocString
|
||||
|> Some
|
||||
)
|
||||
|> SynTypeDefnRepr.interfaceType
|
||||
|> SynTypeDefn.create (
|
||||
let attrs =
|
||||
[
|
||||
yield SynAttribute.create (SynLongIdent.createS' [ "HttpClient" ]) (SynExpr.CreateConst false)
|
||||
yield
|
||||
SynAttribute.create
|
||||
(SynLongIdent.createS' [ "RestEase" ; "BasePath" ])
|
||||
(SynExpr.CreateConst basePath)
|
||||
match options.CreateMock with
|
||||
| None -> ()
|
||||
| Some createMockValue ->
|
||||
yield
|
||||
SynAttribute.create
|
||||
(SynLongIdent.createS' [ "GenerateMock" ])
|
||||
(SynExpr.CreateConst createMockValue)
|
||||
]
|
||||
|
||||
SynComponentInfo.create (Ident.create ("I" + options.ClassName))
|
||||
|> SynComponentInfo.withDocString clientDocString
|
||||
|> SynComponentInfo.addAttributes attrs
|
||||
)
|
||||
|> List.singleton
|
||||
|> SynModuleDecl.createTypes
|
||||
|> List.singleton
|
||||
|
||||
open Myriad.Core
|
||||
open System.IO
|
||||
|
||||
[<RequireQualifiedAccess>]
|
||||
module internal SwaggerV2Generator =
|
||||
let generate (pars : Map<string, string>) (contents : SwaggerV2.SwaggerV2) : Output =
|
||||
let scheme =
|
||||
let preferred = SwaggerV2.Scheme "https"
|
||||
|
||||
if List.isEmpty contents.Schemes then
|
||||
failwith "no schemes specified in API spec!"
|
||||
|
||||
if List.contains preferred contents.Schemes then
|
||||
preferred
|
||||
else
|
||||
List.head contents.Schemes
|
||||
|
||||
let clientDocstring = contents.Info.Description |> PreXmlDoc.create
|
||||
|
||||
let basePath = contents.BasePath
|
||||
|
||||
let typeDefs =
|
||||
let bigCache = Dictionary<_, Dictionary<_, _>> ()
|
||||
|
||||
let countAll () =
|
||||
(0, bigCache) ||> Seq.fold (fun count (KeyValue (_, v)) -> count + v.Count)
|
||||
|
||||
let byHandle = Dictionary ()
|
||||
let anonymousTypeCount = ref 0
|
||||
|
||||
let rec go (contents : ((string * SwaggerV2.Definition) * string) list) =
|
||||
let lastRound = countAll ()
|
||||
|
||||
contents
|
||||
|> List.filter (fun ((name, defn), defnClass) ->
|
||||
let doIt =
|
||||
SwaggerClientGenerator.defnToType
|
||||
anonymousTypeCount
|
||||
byHandle
|
||||
bigCache
|
||||
defnClass
|
||||
(Some name)
|
||||
defn
|
||||
|
||||
match doIt with
|
||||
| None -> true
|
||||
| Some _ -> false
|
||||
)
|
||||
|> fun remaining ->
|
||||
if not remaining.IsEmpty then
|
||||
let currentCount = countAll ()
|
||||
|
||||
if currentCount = lastRound then
|
||||
for (name, remaining), kind in remaining do
|
||||
SwaggerClientGenerator.log $"Remaining: %s{name} (%s{kind})"
|
||||
|
||||
SwaggerClientGenerator.log "--------"
|
||||
|
||||
for KeyValue (handle, defn) in byHandle do
|
||||
SwaggerClientGenerator.log $"Known: %s{handle} %O{defn}"
|
||||
|
||||
// TODO: ohh noooooo the Gitea spec is genuinely circular,
|
||||
// it's impossible to construct a Repository type
|
||||
// we're going to have to somehow detect this case and break the cycle
|
||||
// by artificially making a property optional
|
||||
// :sob: Gitea why are you like this
|
||||
// failwith "Made no further progress rendering types"
|
||||
()
|
||||
else
|
||||
go remaining
|
||||
|
||||
seq {
|
||||
for defnClass in [ "definitions" ; "responses" ] do
|
||||
match defnClass with
|
||||
| "definitions" ->
|
||||
for KeyValue (k, v) in contents.Definitions do
|
||||
yield (k, v), defnClass
|
||||
| "responses" ->
|
||||
for KeyValue (k, v) in contents.Responses do
|
||||
yield (k, v.Schema), defnClass
|
||||
| _ -> failwith "oh no"
|
||||
}
|
||||
|> Seq.toList
|
||||
|> go
|
||||
|
||||
let result = Dictionary ()
|
||||
|
||||
for KeyValue (_container, types) in bigCache do
|
||||
for KeyValue (defn, rendered) in types do
|
||||
result.TryAdd (defn, rendered) |> ignore<bool>
|
||||
|
||||
{
|
||||
ByHandle = byHandle
|
||||
ByDefinition = result :> IReadOnlyDictionary<_, _>
|
||||
}
|
||||
|
||||
let summary =
|
||||
contents.Paths
|
||||
|> Seq.collect (fun (KeyValue (path, endpoints)) ->
|
||||
endpoints
|
||||
|> Seq.choose (fun (KeyValue (method, endpoint)) ->
|
||||
let docstring = endpoint.Summary |> PreXmlDoc.create
|
||||
|
||||
let produces =
|
||||
match endpoint.Produces with
|
||||
| None -> Produces.Produces "json"
|
||||
| Some [] -> failwith $"API specified empty Produces: %s{path} (%O{method})"
|
||||
| Some [ SwaggerV2.MimeType "application/octet-stream" ] -> Produces.OctetStream
|
||||
| Some [ SwaggerV2.MimeType "application/json" ] -> Produces.Produces "json"
|
||||
| Some [ SwaggerV2.MimeType (StartsWith "text/" t) ] -> Produces.Produces t
|
||||
| Some [ SwaggerV2.MimeType s ] ->
|
||||
failwithf
|
||||
$"we don't support non-JSON Produces right now, got: %s{s} (%s{path} %O{method})"
|
||||
| Some (_ :: _) ->
|
||||
failwith $"we don't support multiple Produces right now, at %s{path} (%O{method})"
|
||||
|
||||
let returnType =
|
||||
endpoint.Responses
|
||||
|> Seq.choose (fun (KeyValue (response, defn)) ->
|
||||
if 200 <= response && response < 300 then
|
||||
Some defn
|
||||
else
|
||||
None
|
||||
)
|
||||
|> Seq.toList
|
||||
|
||||
let returnType =
|
||||
match returnType with
|
||||
| [ t ] -> Some t
|
||||
| [] -> failwith $"got no successful response results, %s{path} %O{method}"
|
||||
| _ ->
|
||||
SwaggerClientGenerator.log
|
||||
$"Ignoring %s{path} %O{method} due to multiple success responses"
|
||||
// can't be bothered to work out how to deal with multiple success
|
||||
// results right now
|
||||
None
|
||||
|
||||
match returnType with
|
||||
| None -> None
|
||||
| Some returnType ->
|
||||
|
||||
{
|
||||
Method = method
|
||||
Produces = produces
|
||||
DocString = docstring
|
||||
ReturnType = returnType
|
||||
Operation = endpoint.OperationId
|
||||
Parameters = endpoint.Parameters |> Option.defaultValue []
|
||||
Endpoint = path
|
||||
}
|
||||
|> Some
|
||||
)
|
||||
|> Seq.toList
|
||||
)
|
||||
|> Seq.toList
|
||||
|
||||
let config =
|
||||
let createMock =
|
||||
match Map.tryFind "GENERATEMOCKVISIBILITY" pars with
|
||||
| None -> None
|
||||
| Some v ->
|
||||
match v.ToLowerInvariant () with
|
||||
| "internal" -> Some true
|
||||
| "public" -> Some false
|
||||
| _ ->
|
||||
failwith
|
||||
$"Expected GenerateMockVisibility parameter to be 'internal' or 'public', but was: '%s{v.ToLowerInvariant ()}'"
|
||||
|
||||
let className =
|
||||
match Map.tryFind "CLASSNAME" pars with
|
||||
| None -> failwith "You must supply the <ClassName /> parameter in <MyriadParams />."
|
||||
| Some v -> v
|
||||
|
||||
{
|
||||
CreateMock = createMock
|
||||
ClassName = className
|
||||
}
|
||||
|
||||
let ty =
|
||||
SwaggerClientGenerator.computeType config basePath typeDefs clientDocstring summary
|
||||
|
||||
[
|
||||
yield
|
||||
SynModuleDecl.Open (
|
||||
SynOpenDeclTarget.ModuleOrNamespace (
|
||||
SynLongIdent.createS' [ "WoofWare" ; "Myriad" ; "Plugins" ],
|
||||
range0
|
||||
),
|
||||
range0
|
||||
)
|
||||
yield SwaggerClientGenerator.instantiateRequiredTypes typeDefs
|
||||
yield! ty
|
||||
]
|
||||
|> SynModuleOrNamespace.createNamespace [ Ident.create config.ClassName ]
|
||||
|> List.singleton
|
||||
|> Output.Ast
|
||||
|
||||
/// Myriad generator that stamps out an interface and class to access a Swagger-specified API.
|
||||
[<MyriadGenerator("swagger-client")>]
|
||||
type SwaggerClientGenerator () =
|
||||
|
||||
interface IMyriadGenerator with
|
||||
member _.ValidInputExtensions = [ ".json" ]
|
||||
|
||||
member _.Generate (context : GeneratorContext) =
|
||||
let pars = MyriadParamParser.render context.AdditionalParameters
|
||||
|
||||
let pars =
|
||||
pars
|
||||
|> Map.toSeq
|
||||
|> Seq.map (fun (k, v) -> k.ToUpperInvariant (), v)
|
||||
|> Map.ofSeq
|
||||
|
||||
if pars.IsEmpty then
|
||||
failwith "No parameters given. You must supply the <ClassName /> parameter in <MyriadParams />."
|
||||
|
||||
let contents = File.ReadAllText context.InputFilename |> SwaggerV2.parse
|
||||
|
||||
match contents with
|
||||
| Ok contents -> SwaggerV2Generator.generate pars contents
|
||||
| Error node -> failwith "Input was not a Swagger 2 spec"
|
480
WoofWare.Myriad.Plugins/SwaggerV2.fs
Normal file
480
WoofWare.Myriad.Plugins/SwaggerV2.fs
Normal file
@@ -0,0 +1,480 @@
|
||||
module internal WoofWare.Myriad.Plugins.SwaggerV2
|
||||
|
||||
open System
|
||||
open System.Text.Json.Nodes
|
||||
|
||||
/// A MIME type, like "application/json"
|
||||
type MimeType =
|
||||
/// A MIME type, like "application/json"
|
||||
| MimeType of string
|
||||
|
||||
/// A URL scheme, like "https"
|
||||
type Scheme =
|
||||
/// A URL scheme, like "https"
|
||||
| Scheme of string
|
||||
|
||||
/// "Licence information for the exposed API", whatever that means.
|
||||
type SwaggerLicense =
|
||||
{
|
||||
/// "The license name used for the API", whatever that means.
|
||||
Name : string
|
||||
/// Link to the license used. Mutually exclusive with `Identifier`.
|
||||
Url : Uri option
|
||||
/// SPDX license identifier. Mutually exclusive with `Url`.
|
||||
Identifier : string option
|
||||
}
|
||||
|
||||
/// Render a JsonObject into the strongly-typed version, performing sanity
|
||||
/// checks and throwing on input that can't be parsed.
|
||||
static member Parse (node : JsonObject) : SwaggerLicense =
|
||||
let name = asString node "name"
|
||||
let url = asOpt<string> node "url" |> Option.map Uri
|
||||
let identifier = asOpt<string> node "identifier"
|
||||
|
||||
match url, identifier with
|
||||
| Some _, Some _ -> failwith "Invalid license spec: cannot supply both URL and identifier"
|
||||
| _, _ -> ()
|
||||
|
||||
{
|
||||
Name = name
|
||||
Url = url
|
||||
Identifier = identifier
|
||||
}
|
||||
|
||||
/// Overall information about the API described by this Swagger spec.
|
||||
type SwaggerInfo =
|
||||
{
|
||||
/// Human-readable description of what this Swagger API is for.
|
||||
/// Supports GitHub-flavoured markdown, apparently.
|
||||
Description : string
|
||||
/// Human-readable title of the service to which this is an API.
|
||||
Title : string
|
||||
/// The license applying to this schema. It's very unclear what this means.
|
||||
/// The spec just says:
|
||||
/// "Licence information for the exposed API"
|
||||
License : SwaggerLicense
|
||||
/// The version of this API (not the version of Swagger or the file defining the API!).
|
||||
/// Strictly speaking this can be anything, but I am assuming it's roughly
|
||||
/// SemVer.
|
||||
Version : Version
|
||||
}
|
||||
|
||||
/// Render a JsonObject into the strongly-typed version, performing sanity
|
||||
/// checks and throwing on input that can't be parsed.
|
||||
static member Parse (node : JsonObject) : SwaggerInfo =
|
||||
let description = asString node "description"
|
||||
let title = asString node "title"
|
||||
let version = asString node "version" |> Version.Parse
|
||||
let license = asObj node "license" |> SwaggerLicense.Parse
|
||||
|
||||
{
|
||||
Description = description
|
||||
Title = title
|
||||
License = license
|
||||
Version = version
|
||||
}
|
||||
|
||||
/// An "optional unique string used to describe an operation".
|
||||
/// If present, these are assumed to be unique among all operations described
|
||||
/// in the API.
|
||||
type OperationId =
|
||||
/// An "optional unique string used to describe an operation".
|
||||
/// If present, these are assumed to be unique among all operations described
|
||||
/// in the API.
|
||||
| OperationId of string
|
||||
|
||||
/// Round-trip string representation.
|
||||
override this.ToString () =
|
||||
match this with
|
||||
| OperationId.OperationId s -> s
|
||||
|
||||
/// Constraints on the `additionalProperties` (in the JSON schema sense).
|
||||
/// "Additional properties" are properties of a JSON object which were not
|
||||
/// listed in the schema.
|
||||
type AdditionalProperties =
|
||||
/// No additional properties are allowed: all properties must have been
|
||||
/// mentioned in the schema.
|
||||
| Never
|
||||
/// Additional properties are permitted, but if they exist, they must
|
||||
/// match this schema definition.
|
||||
| Constrained of Definition
|
||||
|
||||
/// The Swagger schema lets you define types. An ObjectTypeDefinition
|
||||
/// is specifically the information about types defined as `"type": "object"`.
|
||||
and ObjectTypeDefinition =
|
||||
{
|
||||
/// Human-readable description of the purpose of this type.
|
||||
Description : string option
|
||||
/// Fields which any object must have to satisfy this type.
|
||||
Properties : Map<string, Definition> option
|
||||
/// Extra properties in the type description. In Gitea, these are
|
||||
/// (for example) "x-go-package":"code.gitea.io/gitea/modules/structs".
|
||||
Extras : Map<string, JsonNode>
|
||||
/// List of fields which are required; all other fields are optional.
|
||||
Required : string list option
|
||||
/// Constraints, if any, placed on fields which are not mentioned in
|
||||
/// the schema. If absent, there are no constraints.
|
||||
AdditionalProperties : AdditionalProperties option
|
||||
/// Example of an object which satisfies this schema.
|
||||
Example : JsonObject option
|
||||
}
|
||||
|
||||
/// Render a JsonObject into the strongly-typed version, performing sanity
|
||||
/// checks and throwing on input that can't be parsed.
|
||||
static member Parse (node : JsonObject) : ObjectTypeDefinition =
|
||||
let description =
|
||||
match asOpt<string> node "description", asOpt<string> node "title" with
|
||||
| None, None -> None
|
||||
| Some v, None
|
||||
| None, Some v -> Some v
|
||||
| Some v1, Some v2 -> failwith "both description and title were given"
|
||||
|
||||
let additionalProperties =
|
||||
match node.["additionalProperties"] with
|
||||
| null -> None
|
||||
| :? JsonValue as p ->
|
||||
if not (p.GetValue<bool> ()) then
|
||||
Some AdditionalProperties.Never
|
||||
else
|
||||
failwith $"additionalProperties should be 'false' or an object, but was: %s{p.ToJsonString ()}"
|
||||
| p ->
|
||||
let p = p.AsObject ()
|
||||
Definition.Parse p |> AdditionalProperties.Constrained |> Some
|
||||
|
||||
let properties =
|
||||
match node.["properties"] with
|
||||
| null -> None
|
||||
| p ->
|
||||
p.AsObject ()
|
||||
|> Seq.map (fun (KeyValue (key, value)) ->
|
||||
let value = value.AsObject ()
|
||||
key, Definition.Parse value
|
||||
)
|
||||
|> Map.ofSeq
|
||||
|> Some
|
||||
|
||||
let example =
|
||||
match node.["example"] with
|
||||
| null -> None
|
||||
| :? JsonObject as o -> Some o
|
||||
| _ ->
|
||||
// Gitea returns a stringified and malformed JSON object here.
|
||||
// Don't throw; just omit.
|
||||
None
|
||||
|
||||
let required = asArrOpt'<string> node "required"
|
||||
|
||||
let extras =
|
||||
node.AsObject ()
|
||||
|> Seq.choose (fun (KeyValue (key, value)) ->
|
||||
match key with
|
||||
| "type"
|
||||
| "description"
|
||||
| "title"
|
||||
| "additionalProperties"
|
||||
| "example"
|
||||
| "required"
|
||||
| "properties" -> None
|
||||
| _ -> Some (key, value)
|
||||
)
|
||||
|> Map.ofSeq
|
||||
|
||||
{
|
||||
Description = description
|
||||
Properties = properties
|
||||
AdditionalProperties = additionalProperties
|
||||
Required = required
|
||||
Extras = extras
|
||||
Example = example
|
||||
}
|
||||
|
||||
/// The Swagger schema lets you define types. An ArrayTypeDefinition
|
||||
/// is specifically the information about types defined as `"type": "array"`.
|
||||
and ArrayTypeDefinition =
|
||||
{
|
||||
/// The type is `'a array`; this field describes `'a`.
|
||||
Items : Definition
|
||||
}
|
||||
|
||||
/// Render a JsonNode into the strongly-typed version, performing sanity
|
||||
/// checks and throwing on input that can't be parsed.
|
||||
static member Parse (n : JsonNode) : ArrayTypeDefinition =
|
||||
let items = asObj n "items" |> Definition.Parse
|
||||
|
||||
{
|
||||
Items = items
|
||||
}
|
||||
|
||||
/// Any definition of a type in the Swagger document. This is basically any
|
||||
/// information associated with the `"type": "blah"` field.
|
||||
and Definition =
|
||||
/// For example, if `"$ref": "#/responses/Blah", then this is "#/responses/Blah".
|
||||
| Handle of string
|
||||
/// A type definition with "type": "object".
|
||||
| Object of ObjectTypeDefinition
|
||||
/// A type definition with "type": "array".
|
||||
| Array of ArrayTypeDefinition
|
||||
/// A type definition with "type": "string".
|
||||
| String
|
||||
/// A type definition with "type": "boolean".
|
||||
| Boolean
|
||||
/// A response without a body has no "schema" specified.
|
||||
| Unspecified
|
||||
/// A type definition with "type": "integer".
|
||||
/// The format is an optional hint which could be e.g. "int64" or "int32".
|
||||
/// https://swagger.io/docs/specification/data-models/data-types/#numbers
|
||||
| Integer of format : string option
|
||||
/// Not a JSON schema type, but a Swagger 2.0 type.
|
||||
| File
|
||||
|
||||
/// Render a JsonObject into this strongly-typed specification.
|
||||
static member Parse (n : JsonObject) : Definition =
|
||||
match n.["$ref"] |> Option.ofObj with
|
||||
| Some ref -> Definition.Handle (ref.GetValue<string> ())
|
||||
| None ->
|
||||
|
||||
let ty = asOpt<string> n "type"
|
||||
|
||||
match ty with
|
||||
| None -> Definition.Unspecified
|
||||
| Some "object" -> ObjectTypeDefinition.Parse n |> Definition.Object
|
||||
| Some "array" -> ArrayTypeDefinition.Parse n |> Definition.Array
|
||||
| Some "string" -> Definition.String
|
||||
| Some "boolean" -> Definition.Boolean
|
||||
| Some "file" -> Definition.File
|
||||
| Some "integer" ->
|
||||
let format = asOpt<string> n "format"
|
||||
Definition.Integer format
|
||||
| Some ty -> failwith $"Unrecognised type: %s{ty}"
|
||||
|
||||
/// REST APIs allow their parameters to be passed in various ways. This describes
|
||||
/// how one single parameter is passed.
|
||||
type ParameterIn =
|
||||
/// The parameter is interpolated into the path, e.g. "/foo/{blah}".
|
||||
/// The "name" is what we replace in the path: e.g. "/foo/{person}" would
|
||||
/// have a name of "person".
|
||||
| Path of name : string
|
||||
/// The parameter is appended to the URL's query params, e.g. "?<name>=blah"
|
||||
| Query of name : string
|
||||
/// The parameter is passed in the body of the HTTP request.
|
||||
| Body
|
||||
/// Some spec that WoofWare.Myriad doesn't support.
|
||||
| Unrecognised of op : string * name : string
|
||||
|
||||
/// Description of a single input parameter to an endpoint.
|
||||
type SwaggerParameter =
|
||||
{
|
||||
/// The type schema to which this parameter must conform.
|
||||
Type : Definition
|
||||
/// Optional human-readable description of this parameter.
|
||||
Description : string option
|
||||
/// How this parameter is passed.
|
||||
In : ParameterIn
|
||||
/// Name of this parameter. For most `In` values, this name is the
|
||||
/// name of the parameter as supplied to the API at runtime, and in WoofWare's
|
||||
/// strongly-typed domain types this information is also contained in the `In` field.
|
||||
/// For `Body` parameters, this is purely for dev-time information.
|
||||
Name : string
|
||||
/// Whether this parameter is required for validation to succeed.
|
||||
/// I think this defaults to "no".
|
||||
Required : bool option
|
||||
}
|
||||
|
||||
/// Render a JsonObject into this strongly-typed specification.
|
||||
static member Parse (node : JsonObject) : SwaggerParameter =
|
||||
let ty =
|
||||
match asObjOpt node "schema" with
|
||||
| None -> Definition.Parse node
|
||||
| Some node -> Definition.Parse node
|
||||
|
||||
let description = asOpt<string> node "description"
|
||||
let name = asString node "name"
|
||||
|
||||
let paramIn =
|
||||
match asString node "in" with
|
||||
| "path" -> ParameterIn.Path name
|
||||
| "query" -> ParameterIn.Query name
|
||||
| "body" -> ParameterIn.Body
|
||||
| f -> ParameterIn.Unrecognised (f, name)
|
||||
|
||||
let required = asOpt<bool> node "required"
|
||||
|
||||
{
|
||||
Type = ty
|
||||
Description = description
|
||||
In = paramIn
|
||||
Name = name
|
||||
Required = required
|
||||
}
|
||||
|
||||
/// An "endpoint" is basically a single HTTP verb, applied to some path.
|
||||
type SwaggerEndpoint =
|
||||
{
|
||||
/// The MIME types we should send our request body in.
|
||||
/// This overrides (does not extend) any global definitions on the spec itself.
|
||||
Consumes : MimeType list option
|
||||
/// The MIME types we should expect to receive in response to this request.
|
||||
/// This overrides (does not extend) any global definitions on the spec itself.
|
||||
Produces : MimeType list option
|
||||
/// Arbitrary list of [tags](https://swagger.io/docs/specification/2-0/grouping-operations-with-tags/).
|
||||
Tags : string list
|
||||
/// Human-readable description of the endpoint.
|
||||
Summary : string
|
||||
/// Arbitrary identifier of this endpoint; this must be unique across *all* endpoints
|
||||
/// in this entire spec.
|
||||
OperationId : OperationId
|
||||
/// Parameters that must be supplied at HTTP-request-time to the endpoint.
|
||||
/// (Each parameter knows how it needs to be supplied: e.g. if it's a query parameter or
|
||||
/// if it's interpolated into the path.)
|
||||
Parameters : SwaggerParameter list option
|
||||
/// Map of HTTP response code to the type that we expect to receive in the body if we
|
||||
/// get that response code back.
|
||||
Responses : Map<int, Definition>
|
||||
}
|
||||
|
||||
/// Render a JsonObject into this strongly-typed specification.
|
||||
static member Parse (r : JsonObject) : SwaggerEndpoint =
|
||||
let produces = asArrOpt'<string> r "produces" |> Option.map (List.map MimeType)
|
||||
let consumes = asArrOpt'<string> r "consumes" |> Option.map (List.map MimeType)
|
||||
let tags = asArr'<string> r "tags"
|
||||
let summary = asString r "summary"
|
||||
let operationId = asString r "operationId" |> OperationId
|
||||
|
||||
let responses =
|
||||
asObj r "responses"
|
||||
|> Seq.map (fun (KeyValue (key, value)) ->
|
||||
let value = value.AsObject ()
|
||||
Int32.Parse key, Definition.Parse value
|
||||
)
|
||||
|> Map.ofSeq
|
||||
|
||||
let parameters =
|
||||
asArrOpt r "parameters"
|
||||
|> Option.map (fun pars ->
|
||||
pars
|
||||
|> Seq.map (fun par -> par.AsObject () |> SwaggerParameter.Parse)
|
||||
|> Seq.toList
|
||||
)
|
||||
|
||||
{
|
||||
Produces = produces
|
||||
Consumes = consumes
|
||||
Tags = tags
|
||||
Summary = summary
|
||||
OperationId = operationId
|
||||
Parameters = parameters
|
||||
Responses = responses
|
||||
}
|
||||
|
||||
/// Specifies the form a response to an endpoint will take if it's complying with this spec.
|
||||
type Response =
|
||||
{
|
||||
/// Human-readable description.
|
||||
Description : string
|
||||
/// Specification of the type to which responses will conform under this spec.
|
||||
Schema : Definition
|
||||
}
|
||||
|
||||
/// Render a JsonObject into this strongly-typed specification.
|
||||
static member Parse (r : JsonObject) : Response =
|
||||
let desc = asString r "description"
|
||||
|
||||
let schema =
|
||||
match asObjOpt r "schema" with
|
||||
| None -> Definition.Unspecified
|
||||
| Some s -> Definition.Parse s
|
||||
|
||||
{
|
||||
Description = desc
|
||||
Schema = schema
|
||||
}
|
||||
|
||||
/// A Swagger API specification.
|
||||
type SwaggerV2 =
|
||||
{
|
||||
/// Global collection of MIME types which any endpoint expects to consume its inputs in.
|
||||
/// This may be overridden on any individual endpoint by that endpoint.
|
||||
Consumes : MimeType list
|
||||
/// Global collection of MIME types which any endpoint will produce.
|
||||
/// This may be overridden on any individual endpoint by that endpoint.
|
||||
Produces : MimeType list
|
||||
/// HTTP or HTTPS, for example. Indicates which scheme to access the API on.
|
||||
Schemes : Scheme list
|
||||
/// The version of OpenAPI this specification is written against.
|
||||
/// (As of this writing, we only support 2.0.)
|
||||
Swagger : Version
|
||||
/// General information about this API.
|
||||
Info : SwaggerInfo
|
||||
/// Path under the URI host, which should be prefixed (with trailing slash if necessary)
|
||||
/// to all requests.
|
||||
BasePath : string
|
||||
/// Map from relative path to "what is served at that path".
|
||||
Paths : Map<string, Map<HttpMethod, SwaggerEndpoint>>
|
||||
/// Types defined in the schema. Requests may use these definitions just like in any other JSON schema.
|
||||
/// Key is a domain type name, e.g. "APIError".
|
||||
Definitions : Map<string, Definition>
|
||||
/// Types of each response.
|
||||
/// Key is a domain type name, e.g. "AccessToken".
|
||||
Responses : Map<string, Response>
|
||||
}
|
||||
|
||||
/// Parse a JSON-schema-based specification of a Swagger 2.0 API and
|
||||
/// build the strongly-typed version. Throws on invalid inputs; returns Error if the input is JSON but has a Swagger
|
||||
/// version that is not in the 2.0 series.
|
||||
let parse (s : string) : Result<SwaggerV2, JsonNode> =
|
||||
let node = JsonNode.Parse s
|
||||
let swagger = asString node "swagger" |> Version.Parse
|
||||
|
||||
if swagger.Major <> 2 then
|
||||
Error node
|
||||
else
|
||||
|
||||
let consumes = asArr'<string> node "consumes" |> List.map MimeType
|
||||
let produces = asArr'<string> node "produces" |> List.map MimeType
|
||||
let schemes = asArr'<string> node "schemes" |> List.map Scheme
|
||||
let info = asObj node "info" |> SwaggerInfo.Parse
|
||||
let basePath = asString node "basePath"
|
||||
|
||||
let definitions =
|
||||
asObj node "definitions"
|
||||
|> Seq.map (fun (KeyValue (key, value)) ->
|
||||
let value = value.AsObject ()
|
||||
key, Definition.Parse value
|
||||
)
|
||||
|> Map.ofSeq
|
||||
|
||||
let paths =
|
||||
asObj node "paths"
|
||||
|> Seq.map (fun (KeyValue (key, value)) ->
|
||||
let contents =
|
||||
value.AsObject ()
|
||||
|> Seq.map (fun (KeyValue (endpoint, contents)) ->
|
||||
let contents = contents.AsObject ()
|
||||
HttpMethod.Parse endpoint, SwaggerEndpoint.Parse contents
|
||||
)
|
||||
|> Map.ofSeq
|
||||
|
||||
key, contents
|
||||
)
|
||||
|> Map.ofSeq
|
||||
|
||||
let responses =
|
||||
asObj node "responses"
|
||||
|> Seq.map (fun (KeyValue (key, value)) ->
|
||||
let value = value.AsObject ()
|
||||
key, Response.Parse value
|
||||
)
|
||||
|> Map.ofSeq
|
||||
|
||||
{
|
||||
Consumes = consumes
|
||||
Produces = produces
|
||||
Schemes = schemes
|
||||
Swagger = swagger
|
||||
Info = info
|
||||
BasePath = basePath
|
||||
Paths = paths
|
||||
Definitions = definitions
|
||||
Responses = responses
|
||||
}
|
||||
|> Ok
|
@@ -1,31 +0,0 @@
|
||||
namespace WoofWare.Myriad.Plugins
|
||||
|
||||
open Fantomas.FCS.Syntax
|
||||
open Fantomas.FCS.Text.Range
|
||||
open Myriad.Core
|
||||
|
||||
[<RequireQualifiedAccess>]
|
||||
module internal SynAttribute =
|
||||
let internal compilationRepresentation : SynAttribute =
|
||||
{
|
||||
TypeName = SynLongIdent.CreateString "CompilationRepresentation"
|
||||
ArgExpr =
|
||||
SynExpr.CreateLongIdent (
|
||||
false,
|
||||
SynLongIdent.Create [ "CompilationRepresentationFlags" ; "ModuleSuffix" ],
|
||||
None
|
||||
)
|
||||
|> SynExpr.CreateParen
|
||||
Target = None
|
||||
AppliesToGetterAndSetter = false
|
||||
Range = range0
|
||||
}
|
||||
|
||||
let internal autoOpen : SynAttribute =
|
||||
{
|
||||
TypeName = SynLongIdent.CreateString "AutoOpen"
|
||||
ArgExpr = SynExpr.CreateConst SynConst.Unit
|
||||
Target = None
|
||||
AppliesToGetterAndSetter = false
|
||||
Range = range0
|
||||
}
|
@@ -1,313 +0,0 @@
|
||||
namespace WoofWare.Myriad.Plugins
|
||||
|
||||
open Fantomas.FCS.Syntax
|
||||
open Fantomas.FCS.SyntaxTrivia
|
||||
open Myriad.Core
|
||||
open Myriad.Core.Ast
|
||||
open Fantomas.FCS.Text.Range
|
||||
|
||||
type internal CompExprBinding =
|
||||
| LetBang of varName : string * rhs : SynExpr
|
||||
| Let of varName : string * rhs : SynExpr
|
||||
| Use of varName : string * rhs : SynExpr
|
||||
| Do of body : SynExpr
|
||||
|
||||
[<RequireQualifiedAccess>]
|
||||
module internal SynExpr =
|
||||
|
||||
/// {expr} |> {func}
|
||||
let pipeThroughFunction (func : SynExpr) (expr : SynExpr) : SynExpr =
|
||||
SynExpr.CreateApp (
|
||||
SynExpr.CreateAppInfix (
|
||||
SynExpr.CreateLongIdent (
|
||||
SynLongIdent.SynLongIdent (
|
||||
[ Ident.Create "op_PipeRight" ],
|
||||
[],
|
||||
[ Some (IdentTrivia.OriginalNotation "|>") ]
|
||||
)
|
||||
),
|
||||
expr
|
||||
),
|
||||
func
|
||||
)
|
||||
|
||||
/// if {cond} then {trueBranch} else {falseBranch}
|
||||
/// Note that this function puts the trueBranch last, for pipelining convenience:
|
||||
/// we assume that the `else` branch is more like an error case and is less interesting.
|
||||
let ifThenElse (cond : SynExpr) (falseBranch : SynExpr) (trueBranch : SynExpr) : SynExpr =
|
||||
SynExpr.IfThenElse (
|
||||
cond,
|
||||
trueBranch,
|
||||
Some falseBranch,
|
||||
DebugPointAtBinding.Yes range0,
|
||||
false,
|
||||
range0,
|
||||
{
|
||||
IfKeyword = range0
|
||||
IsElif = false
|
||||
ThenKeyword = range0
|
||||
ElseKeyword = Some range0
|
||||
IfToThenRange = range0
|
||||
}
|
||||
)
|
||||
|
||||
/// try {body} with | {exc} as exc -> {handler}
|
||||
let pipeThroughTryWith (exc : SynPat) (handler : SynExpr) (body : SynExpr) : SynExpr =
|
||||
let clause =
|
||||
SynMatchClause.Create (SynPat.As (exc, SynPat.CreateNamed (Ident.Create "exc"), range0), None, handler)
|
||||
|
||||
SynExpr.TryWith (
|
||||
body,
|
||||
[ clause ],
|
||||
range0,
|
||||
DebugPointAtTry.Yes range0,
|
||||
DebugPointAtWith.Yes range0,
|
||||
{
|
||||
TryKeyword = range0
|
||||
TryToWithRange = range0
|
||||
WithKeyword = range0
|
||||
WithToEndRange = range0
|
||||
}
|
||||
)
|
||||
|
||||
/// {a} = {b}
|
||||
let equals (a : SynExpr) (b : SynExpr) =
|
||||
SynExpr.CreateApp (
|
||||
SynExpr.CreateAppInfix (
|
||||
SynExpr.CreateLongIdent (
|
||||
SynLongIdent.SynLongIdent (
|
||||
Ident.CreateLong "op_Equality",
|
||||
[],
|
||||
[ Some (IdentTrivia.OriginalNotation "=") ]
|
||||
)
|
||||
),
|
||||
a
|
||||
),
|
||||
b
|
||||
)
|
||||
|
||||
/// {a} + {b}
|
||||
let plus (a : SynExpr) (b : SynExpr) =
|
||||
SynExpr.CreateApp (
|
||||
SynExpr.CreateAppInfix (
|
||||
SynExpr.CreateLongIdent (
|
||||
SynLongIdent.SynLongIdent (
|
||||
Ident.CreateLong "op_Addition",
|
||||
[],
|
||||
[ Some (IdentTrivia.OriginalNotation "+") ]
|
||||
)
|
||||
),
|
||||
a
|
||||
),
|
||||
b
|
||||
)
|
||||
|
||||
let rec stripOptionalParen (expr : SynExpr) : SynExpr =
|
||||
match expr with
|
||||
| SynExpr.Paren (expr, _, _, _) -> stripOptionalParen expr
|
||||
| expr -> expr
|
||||
|
||||
/// Given e.g. "byte", returns "System.Byte".
|
||||
let qualifyPrimitiveType (typeName : string) : LongIdent =
|
||||
match typeName with
|
||||
| "float32" -> [ "System" ; "Single" ]
|
||||
| "float" -> [ "System" ; "Double" ]
|
||||
| "byte"
|
||||
| "uint8" -> [ "System" ; "Byte" ]
|
||||
| "sbyte" -> [ "System" ; "SByte" ]
|
||||
| "int16" -> [ "System" ; "Int16" ]
|
||||
| "int" -> [ "System" ; "Int32" ]
|
||||
| "int64" -> [ "System" ; "Int64" ]
|
||||
| "uint16" -> [ "System" ; "UInt16" ]
|
||||
| "uint"
|
||||
| "uint32" -> [ "System" ; "UInt32" ]
|
||||
| "uint64" -> [ "System" ; "UInt64" ]
|
||||
| _ -> failwith $"Unable to identify a parsing function `string -> %s{typeName}`"
|
||||
|> List.map Ident.Create
|
||||
|
||||
/// {obj}.{meth} {arg}
|
||||
let callMethodArg (meth : string) (arg : SynExpr) (obj : SynExpr) : SynExpr =
|
||||
SynExpr.CreateApp (
|
||||
SynExpr.DotGet (
|
||||
obj,
|
||||
range0,
|
||||
SynLongIdent.SynLongIdent (id = [ Ident.Create meth ], dotRanges = [], trivia = [ None ]),
|
||||
range0
|
||||
),
|
||||
arg
|
||||
)
|
||||
|
||||
/// {obj}.{meth}()
|
||||
let callMethod (meth : string) (obj : SynExpr) : SynExpr =
|
||||
callMethodArg meth (SynExpr.CreateConst SynConst.Unit) obj
|
||||
|
||||
/// {obj}.{meth}<ty>()
|
||||
let callGenericMethod (meth : string) (ty : string) (obj : SynExpr) : SynExpr =
|
||||
SynExpr.CreateApp (
|
||||
SynExpr.TypeApp (
|
||||
SynExpr.DotGet (obj, range0, SynLongIdent.Create [ meth ], range0),
|
||||
range0,
|
||||
[ SynType.CreateLongIdent ty ],
|
||||
[],
|
||||
Some range0,
|
||||
range0,
|
||||
range0
|
||||
),
|
||||
SynExpr.CreateConst SynConst.Unit
|
||||
)
|
||||
|
||||
let index (property : SynExpr) (obj : SynExpr) : SynExpr =
|
||||
SynExpr.DotIndexedGet (obj, property, range0, range0)
|
||||
|
||||
/// (fun {varName} -> {body})
|
||||
let createLambda (varName : string) (body : SynExpr) : SynExpr =
|
||||
let parsedDataPat = [ SynPat.CreateNamed (Ident.Create varName) ]
|
||||
|
||||
SynExpr.Lambda (
|
||||
false,
|
||||
false,
|
||||
SynSimplePats.Create [ SynSimplePat.CreateId (Ident.Create varName) ],
|
||||
body,
|
||||
Some (parsedDataPat, body),
|
||||
range0,
|
||||
{
|
||||
ArrowRange = Some range0
|
||||
}
|
||||
)
|
||||
|> SynExpr.CreateParen
|
||||
|
||||
let reraise : SynExpr =
|
||||
SynExpr.CreateApp (SynExpr.CreateIdent (Ident.Create "reraise"), SynExpr.CreateConst SynConst.Unit)
|
||||
|
||||
/// {body} |> fun a -> Async.StartAsTask (a, ?cancellationToken=ct)
|
||||
let startAsTask (ct : SynLongIdent) (body : SynExpr) =
|
||||
let lambda =
|
||||
SynExpr.CreateApp (
|
||||
SynExpr.CreateLongIdent (SynLongIdent.Create [ "Async" ; "StartAsTask" ]),
|
||||
SynExpr.CreateParenedTuple
|
||||
[
|
||||
SynExpr.CreateLongIdent (SynLongIdent.CreateString "a")
|
||||
equals
|
||||
(SynExpr.LongIdent (true, SynLongIdent.CreateString "cancellationToken", None, range0))
|
||||
(SynExpr.CreateLongIdent ct)
|
||||
]
|
||||
)
|
||||
|> createLambda "a"
|
||||
|
||||
pipeThroughFunction lambda body
|
||||
|
||||
/// {compExpr} { {lets} ; return {ret} }
|
||||
let createCompExpr (compExpr : string) (retBody : SynExpr) (lets : CompExprBinding list) : SynExpr =
|
||||
let retStatement = SynExpr.YieldOrReturn ((false, true), retBody, range0)
|
||||
|
||||
let contents : SynExpr =
|
||||
(retStatement, List.rev lets)
|
||||
||> List.fold (fun state binding ->
|
||||
match binding with
|
||||
| LetBang (lhs, rhs) ->
|
||||
SynExpr.LetOrUseBang (
|
||||
DebugPointAtBinding.Yes range0,
|
||||
false,
|
||||
true,
|
||||
SynPat.CreateNamed (Ident.Create lhs),
|
||||
rhs,
|
||||
[],
|
||||
state,
|
||||
range0,
|
||||
{
|
||||
EqualsRange = Some range0
|
||||
}
|
||||
)
|
||||
| Let (lhs, rhs) ->
|
||||
SynExpr.LetOrUse (
|
||||
false,
|
||||
false,
|
||||
[ SynBinding.Let (pattern = SynPat.CreateNamed (Ident.Create lhs), expr = rhs) ],
|
||||
state,
|
||||
range0,
|
||||
{
|
||||
SynExprLetOrUseTrivia.InKeyword = None
|
||||
}
|
||||
)
|
||||
| Use (lhs, rhs) ->
|
||||
SynExpr.LetOrUse (
|
||||
false,
|
||||
true,
|
||||
[ SynBinding.Let (pattern = SynPat.CreateNamed (Ident.Create lhs), expr = rhs) ],
|
||||
state,
|
||||
range0,
|
||||
{
|
||||
SynExprLetOrUseTrivia.InKeyword = None
|
||||
}
|
||||
)
|
||||
| Do body -> SynExpr.CreateSequential [ SynExpr.Do (body, range0) ; state ]
|
||||
)
|
||||
|
||||
SynExpr.CreateApp (
|
||||
SynExpr.CreateIdent (Ident.Create compExpr),
|
||||
SynExpr.ComputationExpr (false, contents, range0)
|
||||
)
|
||||
|
||||
/// {expr} |> Async.AwaitTask
|
||||
let awaitTask (expr : SynExpr) : SynExpr =
|
||||
expr
|
||||
|> pipeThroughFunction (SynExpr.CreateLongIdent (SynLongIdent.Create [ "Async" ; "AwaitTask" ]))
|
||||
|
||||
/// {ident}.ToString ()
|
||||
/// with special casing for some types like DateTime
|
||||
let toString (ty : SynType) (ident : SynExpr) =
|
||||
match ty with
|
||||
| DateOnly -> ident |> callMethodArg "ToString" (SynExpr.CreateConstString "yyyy-MM-dd")
|
||||
| DateTime ->
|
||||
ident
|
||||
|> callMethodArg "ToString" (SynExpr.CreateConstString "yyyy-MM-ddTHH:mm:ss")
|
||||
| _ -> callMethod "ToString" ident
|
||||
|
||||
let upcast' (ty : SynType) (e : SynExpr) = SynExpr.Upcast (e, ty, range0)
|
||||
|
||||
let synBindingTriviaZero (isMember : bool) =
|
||||
{
|
||||
SynBindingTrivia.EqualsRange = Some range0
|
||||
InlineKeyword = None
|
||||
LeadingKeyword =
|
||||
if isMember then
|
||||
SynLeadingKeyword.Member range0
|
||||
else
|
||||
SynLeadingKeyword.Let range0
|
||||
}
|
||||
|
||||
/// {ident} - {rhs}
|
||||
let minus (ident : SynLongIdent) (rhs : SynExpr) : SynExpr =
|
||||
SynExpr.CreateApp (
|
||||
SynExpr.CreateAppInfix (
|
||||
SynExpr.CreateLongIdent (
|
||||
SynLongIdent.SynLongIdent (
|
||||
[ Ident.Create "op_Subtraction" ],
|
||||
[],
|
||||
[ Some (IdentTrivia.OriginalNotation "-") ]
|
||||
)
|
||||
),
|
||||
SynExpr.CreateLongIdent ident
|
||||
),
|
||||
rhs
|
||||
)
|
||||
|
||||
/// {ident} - {n}
|
||||
let minusN (ident : SynLongIdent) (n : int) : SynExpr =
|
||||
minus ident (SynExpr.CreateConst (SynConst.Int32 n))
|
||||
|
||||
/// {y} > {x}
|
||||
let greaterThan (x : SynExpr) (y : SynExpr) : SynExpr =
|
||||
SynExpr.CreateApp (
|
||||
SynExpr.CreateAppInfix (
|
||||
SynExpr.CreateLongIdent (
|
||||
SynLongIdent.SynLongIdent (
|
||||
[ Ident.Create "op_GreaterThan" ],
|
||||
[],
|
||||
[ Some (IdentTrivia.OriginalNotation ">") ]
|
||||
)
|
||||
),
|
||||
y
|
||||
),
|
||||
x
|
||||
)
|
@@ -1,10 +0,0 @@
|
||||
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
|
11
WoofWare.Myriad.Plugins/Text.fs
Normal file
11
WoofWare.Myriad.Plugins/Text.fs
Normal file
@@ -0,0 +1,11 @@
|
||||
namespace WoofWare.Myriad.Plugins
|
||||
|
||||
open System
|
||||
|
||||
[<AutoOpen>]
|
||||
module internal Text =
|
||||
let (|StartsWith|_|) (prefix : string) (s : string) : string option =
|
||||
if s.StartsWith (prefix, StringComparison.Ordinal) then
|
||||
Some (s.Substring prefix.Length)
|
||||
else
|
||||
None
|
@@ -1,4 +1,4 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
@@ -15,27 +15,39 @@
|
||||
<WarnOn>FS3559</WarnOn>
|
||||
<PackageId>WoofWare.Myriad.Plugins</PackageId>
|
||||
<PackageIcon>logo.png</PackageIcon>
|
||||
<NoWarn>NU5118</NoWarn>
|
||||
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Myriad.Core" Version="0.8.3" PrivateAssets="all"/>
|
||||
<PackageReference Include="Myriad.Core" Version="0.8.3" />
|
||||
<PackageReference Include="TypeEquality" Version="0.3.0" />
|
||||
<PackageReference Include="WoofWare.Whippet.Fantomas" Version="0.6.3" />
|
||||
<!-- the lowest version allowed by Myriad.Core -->
|
||||
<PackageReference Update="FSharp.Core" Version="6.0.1" PrivateAssets="all"/>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Include="AssemblyInfo.fs" />
|
||||
<Compile Include="List.fs"/>
|
||||
<Compile Include="Ident.fs" />
|
||||
<Compile Include="AstHelper.fs"/>
|
||||
<Compile Include="SynExpr.fs"/>
|
||||
<Compile Include="SynType.fs"/>
|
||||
<Compile Include="SynAttribute.fs"/>
|
||||
<Compile Include="Text.fs" />
|
||||
<Compile Include="Measure.fs" />
|
||||
<Compile Include="AstHelper.fs" />
|
||||
<Compile Include="Parameters.fs" />
|
||||
<Compile Include="RemoveOptionsGenerator.fs"/>
|
||||
<Compile Include="MyriadParamParser.fs" />
|
||||
<Compile Include="InterfaceMockGenerator.fs"/>
|
||||
<Compile Include="JsonSerializeGenerator.fs"/>
|
||||
<Compile Include="JsonParseGenerator.fs"/>
|
||||
<Compile Include="HttpClientGenerator.fs"/>
|
||||
<Compile Include="CataGenerator.fs" />
|
||||
<Compile Include="ArgParserGenerator.fs" />
|
||||
<Compile Include="JsonHelpers.fs" />
|
||||
<Compile Include="HttpMethod.fs" />
|
||||
<Compile Include="SwaggerV2.fs" />
|
||||
<Compile Include="OpenApi3.fs" />
|
||||
<Compile Include="SwaggerClientGenerator.fs" />
|
||||
<None Include="ApacheLicence.txt" />
|
||||
<EmbeddedResource Include="version.json"/>
|
||||
<EmbeddedResource Include="SurfaceBaseline.txt"/>
|
||||
<None Include="..\README.md">
|
||||
@@ -52,7 +64,7 @@
|
||||
<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)"/>
|
||||
<None Include="$(OutputPath)\*.dll" Pack="true" PackagePath="lib\$(TargetFramework)"/>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
@@ -1,7 +1,14 @@
|
||||
{
|
||||
"version": "2.1",
|
||||
"version": "8.0",
|
||||
"publicReleaseRefSpec": [
|
||||
"^refs/heads/main$"
|
||||
],
|
||||
"pathFilters": null
|
||||
"pathFilters": [
|
||||
"./",
|
||||
":/WoofWare.Myriad.Plugins.Attributes",
|
||||
":^/WoofWare.Myriad.Plugins.Attributes/Test",
|
||||
":/global.json",
|
||||
":/README.md",
|
||||
":/Directory.Build.props"
|
||||
]
|
||||
}
|
@@ -10,7 +10,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageDownload Include="G-Research.FSharp.Analyzers" Version="[0.9.3]" />
|
||||
<PackageDownload Include="G-Research.FSharp.Analyzers" Version="[0.15.0]" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
12
flake.lock
generated
12
flake.lock
generated
@@ -5,11 +5,11 @@
|
||||
"systems": "systems"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1701680307,
|
||||
"narHash": "sha256-kAuep2h5ajznlPMD9rnQyffWG8EM/C73lejGofXvdM8=",
|
||||
"lastModified": 1731533236,
|
||||
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "4022d587cbbfd70fe950c1e2083a02621806a725",
|
||||
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -20,11 +20,11 @@
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1706367331,
|
||||
"narHash": "sha256-AqgkGHRrI6h/8FWuVbnkfFmXr4Bqsr4fV23aISqj/xg=",
|
||||
"lastModified": 1749903597,
|
||||
"narHash": "sha256-jp0D4vzBcRKwNZwfY4BcWHemLGUs4JrS3X9w5k/JYDA=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "160b762eda6d139ac10ae081f8f78d640dd523eb",
|
||||
"rev": "41da1e3ea8e23e094e5e3eeb1e6b830468a7399e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
70
flake.nix
70
flake.nix
@@ -7,7 +7,6 @@
|
||||
};
|
||||
|
||||
outputs = {
|
||||
self,
|
||||
nixpkgs,
|
||||
flake-utils,
|
||||
...
|
||||
@@ -15,10 +14,10 @@
|
||||
flake-utils.lib.eachDefaultSystem (system: let
|
||||
pkgs = nixpkgs.legacyPackages.${system};
|
||||
pname = "WoofWare.Myriad.Plugins";
|
||||
dotnet-sdk = pkgs.dotnet-sdk_8;
|
||||
dotnet-runtime = pkgs.dotnetCorePackages.runtime_8_0;
|
||||
dotnet-sdk = pkgs.dotnetCorePackages.sdk_9_0;
|
||||
dotnet-runtime = pkgs.dotnetCorePackages.runtime_9_0;
|
||||
version = "0.1";
|
||||
dotnetTool = dllOverride: toolName: toolVersion: sha256:
|
||||
dotnetTool = dllOverride: toolName: toolVersion: hash:
|
||||
pkgs.stdenvNoCC.mkDerivation rec {
|
||||
name = toolName;
|
||||
version = toolVersion;
|
||||
@@ -26,64 +25,43 @@
|
||||
src = pkgs.fetchNuGet {
|
||||
pname = name;
|
||||
version = version;
|
||||
sha256 = sha256;
|
||||
installPhase = ''mkdir -p $out/bin && cp -r tools/net6.0/any/* $out/bin'';
|
||||
hash = hash;
|
||||
installPhase = ''mkdir -p $out/bin && cp -r tools/net*/any/* $out/bin'';
|
||||
};
|
||||
installPhase = let
|
||||
dll =
|
||||
if isNull dllOverride
|
||||
then name
|
||||
else dllOverride;
|
||||
in ''
|
||||
runHook preInstall
|
||||
mkdir -p "$out/lib"
|
||||
cp -r ./bin/* "$out/lib"
|
||||
makeWrapper "${dotnet-runtime}/bin/dotnet" "$out/bin/${name}" --add-flags "$out/lib/${dll}.dll"
|
||||
runHook postInstall
|
||||
'';
|
||||
in
|
||||
# fsharp-analyzers requires the .NET SDK at runtime, so we use that instead of dotnet-runtime.
|
||||
''
|
||||
runHook preInstall
|
||||
mkdir -p "$out/lib"
|
||||
cp -r ./bin/* "$out/lib"
|
||||
makeWrapper "${dotnet-sdk}/bin/dotnet" "$out/bin/${name}" --set DOTNET_HOST_PATH "${dotnet-sdk}/bin/dotnet" --add-flags "$out/lib/${dll}.dll"
|
||||
runHook postInstall
|
||||
'';
|
||||
};
|
||||
in {
|
||||
packages = {
|
||||
fantomas = dotnetTool null "fantomas" (builtins.fromJSON (builtins.readFile ./.config/dotnet-tools.json)).tools.fantomas.version (builtins.head (builtins.filter (elem: elem.pname == "fantomas") ((import ./nix/deps.nix) {fetchNuGet = x: x;}))).sha256;
|
||||
fsharp-analyzers = dotnetTool "FSharp.Analyzers.Cli" "fsharp-analyzers" (builtins.fromJSON (builtins.readFile ./.config/dotnet-tools.json)).tools.fsharp-analyzers.version (builtins.head (builtins.filter (elem: elem.pname == "fsharp-analyzers") ((import ./nix/deps.nix) {fetchNuGet = x: x;}))).sha256;
|
||||
fetchDeps = let
|
||||
flags = [];
|
||||
runtimeIds = ["win-x64"] ++ map (system: pkgs.dotnetCorePackages.systemToDotnetRid system) dotnet-sdk.meta.platforms;
|
||||
in
|
||||
pkgs.writeShellScriptBin "fetch-${pname}-deps" (builtins.readFile (pkgs.substituteAll {
|
||||
src = ./nix/fetchDeps.sh;
|
||||
pname = pname;
|
||||
binPath = pkgs.lib.makeBinPath [pkgs.coreutils dotnet-sdk (pkgs.nuget-to-nix.override {inherit dotnet-sdk;})];
|
||||
projectFiles = toString ["./WoofWare.Myriad.Plugins/WoofWare.Myriad.Plugins.fsproj" "./ConsumePlugin/ConsumePlugin.fsproj" "./WoofWare.Myriad.Plugins.Attributes/WoofWare.Myriad.Plugins.Attributes.fsproj"];
|
||||
testProjectFiles = ["./WoofWare.Myriad.Plugins.Test/WoofWare.Myriad.Plugins.Test.fsproj" "./WoofWare.Myriad.Plugins.Attributes/Test/Woofware.Myriad.Plugins.Attributes.Test.fsproj"];
|
||||
rids = pkgs.lib.concatStringsSep "\" \"" runtimeIds;
|
||||
packages = dotnet-sdk.packages;
|
||||
storeSrc = pkgs.srcOnly {
|
||||
src = ./.;
|
||||
pname = pname;
|
||||
version = version;
|
||||
};
|
||||
}));
|
||||
packages = let
|
||||
deps = builtins.fromJSON (builtins.readFile ./nix/deps.json);
|
||||
in {
|
||||
fantomas = dotnetTool null "fantomas" (builtins.fromJSON (builtins.readFile ./.config/dotnet-tools.json)).tools.fantomas.version (builtins.head (builtins.filter (elem: elem.pname == "fantomas") deps)).hash;
|
||||
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") deps)).hash;
|
||||
default = pkgs.buildDotnetModule {
|
||||
pname = pname;
|
||||
inherit pname version dotnet-sdk dotnet-runtime;
|
||||
name = "WoofWare.Myriad.Plugins";
|
||||
version = version;
|
||||
src = ./.;
|
||||
projectFile = "./WoofWare.Myriad.Plugins/WoofWare.Myriad.Plugins.fsproj";
|
||||
nugetDeps = ./nix/deps.nix;
|
||||
testProjectFile = "./WoofWare.Myriad.Plugins.Test/WoofWare.Myriad.Plugins.Test.fsproj";
|
||||
disabledTests = ["WoofWare.Myriad.Plugins.Test.TestSurface.CheckVersionAgainstRemote"];
|
||||
nugetDeps = ./nix/deps.json; # `nix build .#default.fetch-deps && ./result nix/deps.json`
|
||||
doCheck = true;
|
||||
dotnet-sdk = dotnet-sdk;
|
||||
dotnet-runtime = dotnet-runtime;
|
||||
};
|
||||
};
|
||||
devShell = pkgs.mkShell {
|
||||
buildInputs = with pkgs; [
|
||||
(with dotnetCorePackages;
|
||||
combinePackages [
|
||||
dotnet-sdk_8
|
||||
dotnetPackages.Nuget
|
||||
])
|
||||
];
|
||||
buildInputs = [dotnet-sdk];
|
||||
packages = [
|
||||
pkgs.alejandra
|
||||
pkgs.nodePackages.markdown-link-check
|
||||
|
12
global.json
12
global.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"sdk": {
|
||||
"version": "8.0.100",
|
||||
"rollForward": "latestFeature"
|
||||
}
|
||||
}
|
||||
{
|
||||
"sdk": {
|
||||
"version": "9.0.100",
|
||||
"rollForward": "latestMajor"
|
||||
}
|
||||
}
|
||||
|
382
nix/deps.json
Normal file
382
nix/deps.json
Normal file
@@ -0,0 +1,382 @@
|
||||
[
|
||||
{
|
||||
"pname": "ApiSurface",
|
||||
"version": "4.1.21",
|
||||
"hash": "sha256-v2adBYoE9NZPaQR3u2qq9r/9PxAM/wqi2Uiky0xGq+E="
|
||||
},
|
||||
{
|
||||
"pname": "fantomas",
|
||||
"version": "7.0.2",
|
||||
"hash": "sha256-BAaENIm/ksTiXrUImRgKoIXTGIlgsX7ch6ayoFjhJXA="
|
||||
},
|
||||
{
|
||||
"pname": "Fantomas.Core",
|
||||
"version": "6.1.1",
|
||||
"hash": "sha256-FcTLHQFvKkQY/kV08jhhy/St/+FmXpp3epp/R3zUXMA="
|
||||
},
|
||||
{
|
||||
"pname": "Fantomas.FCS",
|
||||
"version": "6.1.1",
|
||||
"hash": "sha256-NuZ8msPEHYA8T3EYREB28F1RcNgUU8V54eg2+UttYxw="
|
||||
},
|
||||
{
|
||||
"pname": "FsCheck",
|
||||
"version": "3.3.0",
|
||||
"hash": "sha256-TFDR/uAGv4OqrMX8/reQ4faaAhH9hxTHr1T/YkNPCCU="
|
||||
},
|
||||
{
|
||||
"pname": "fsharp-analyzers",
|
||||
"version": "0.31.0",
|
||||
"hash": "sha256-PoAvaXbXsmvVw870UsnqdD20HoBHO7u4bzoaz5DXfzM="
|
||||
},
|
||||
{
|
||||
"pname": "FSharp.Core",
|
||||
"version": "4.3.4",
|
||||
"hash": "sha256-styyo+6mJy+yxE0NZG/b1hxkAjPOnJfMgd9zWzCJ5uk="
|
||||
},
|
||||
{
|
||||
"pname": "FSharp.Core",
|
||||
"version": "6.0.1",
|
||||
"hash": "sha256-Ehsgt3nCJijpaVuJguC1TPVEKSkJd6PSc07D2ZQSemI="
|
||||
},
|
||||
{
|
||||
"pname": "FSharp.Core",
|
||||
"version": "9.0.202",
|
||||
"hash": "sha256-64Gub0qemmCoMa1tDus6TeTuB1+5sHfE6KD2j4o84mA="
|
||||
},
|
||||
{
|
||||
"pname": "FsUnit",
|
||||
"version": "7.0.1",
|
||||
"hash": "sha256-K85CIdxMeFSHEKZk6heIXp/oFjWAn7dBILKrw49pJUY="
|
||||
},
|
||||
{
|
||||
"pname": "Microsoft.ApplicationInsights",
|
||||
"version": "2.22.0",
|
||||
"hash": "sha256-mUQ63atpT00r49ca50uZu2YCiLg3yd6r3HzTryqcuEA="
|
||||
},
|
||||
{
|
||||
"pname": "Microsoft.AspNetCore.App.Ref",
|
||||
"version": "6.0.36",
|
||||
"hash": "sha256-9jDkWbjw/nd8yqdzVTagCuqr6owJ/DUMi4BlUZT4hWU="
|
||||
},
|
||||
{
|
||||
"pname": "Microsoft.AspNetCore.App.Runtime.linux-arm64",
|
||||
"version": "6.0.36",
|
||||
"hash": "sha256-JQULJyF0ivLoUU1JaFfK/HHg+/qzpN7V2RR2Cc+WlQ4="
|
||||
},
|
||||
{
|
||||
"pname": "Microsoft.AspNetCore.App.Runtime.linux-x64",
|
||||
"version": "6.0.36",
|
||||
"hash": "sha256-zUsVIpV481vMLAXaLEEUpEMA9/f1HGOnvaQnaWdzlyY="
|
||||
},
|
||||
{
|
||||
"pname": "Microsoft.AspNetCore.App.Runtime.osx-arm64",
|
||||
"version": "6.0.36",
|
||||
"hash": "sha256-2seqZcz0JeUjkzh3QcGa9TcJ4LUafpFjTRk+Nm8T6T0="
|
||||
},
|
||||
{
|
||||
"pname": "Microsoft.AspNetCore.App.Runtime.osx-x64",
|
||||
"version": "6.0.36",
|
||||
"hash": "sha256-yxLafxiBKkvfkDggPk0P9YZIHBkDJOsFTO7/V9mEHuU="
|
||||
},
|
||||
{
|
||||
"pname": "Microsoft.CodeCoverage",
|
||||
"version": "17.14.1",
|
||||
"hash": "sha256-f8QytG8GvRoP47rO2KEmnDLxIpyesaq26TFjDdW40Gs="
|
||||
},
|
||||
{
|
||||
"pname": "Microsoft.NET.Test.Sdk",
|
||||
"version": "17.14.1",
|
||||
"hash": "sha256-mZUzDFvFp7x1nKrcnRd0hhbNu5g8EQYt8SKnRgdhT/A="
|
||||
},
|
||||
{
|
||||
"pname": "Microsoft.NETCore.App.Host.linux-arm64",
|
||||
"version": "6.0.36",
|
||||
"hash": "sha256-9lC/LYnthYhjkWWz2kkFCvlA5LJOv11jdt59SDnpdy0="
|
||||
},
|
||||
{
|
||||
"pname": "Microsoft.NETCore.App.Host.linux-x64",
|
||||
"version": "6.0.36",
|
||||
"hash": "sha256-VFRDzx7LJuvI5yzKdGmw/31NYVbwHWPKQvueQt5xc10="
|
||||
},
|
||||
{
|
||||
"pname": "Microsoft.NETCore.App.Host.osx-arm64",
|
||||
"version": "6.0.36",
|
||||
"hash": "sha256-DaSWwYACJGolEBuMhzDVCj/rQTdDt061xCVi+gyQnuo="
|
||||
},
|
||||
{
|
||||
"pname": "Microsoft.NETCore.App.Host.osx-x64",
|
||||
"version": "6.0.36",
|
||||
"hash": "sha256-FrRny9EI6HKCKQbu6mcLj5w4ooSRrODD4Vj2ZMGnMd4="
|
||||
},
|
||||
{
|
||||
"pname": "Microsoft.NETCore.App.Ref",
|
||||
"version": "6.0.36",
|
||||
"hash": "sha256-9LZgVoIFF8qNyUu8kdJrYGLutMF/cL2K82HN2ywwlx8="
|
||||
},
|
||||
{
|
||||
"pname": "Microsoft.NETCore.App.Runtime.linux-arm64",
|
||||
"version": "6.0.36",
|
||||
"hash": "sha256-k3rxvUhCEU0pVH8KgEMtkPiSOibn+nBh+0zT2xIfId8="
|
||||
},
|
||||
{
|
||||
"pname": "Microsoft.NETCore.App.Runtime.linux-x64",
|
||||
"version": "6.0.36",
|
||||
"hash": "sha256-U8wJ2snSDFqeAgDVLXjnniidC7Cr5aJ1/h/BMSlyu0c="
|
||||
},
|
||||
{
|
||||
"pname": "Microsoft.NETCore.App.Runtime.osx-arm64",
|
||||
"version": "6.0.36",
|
||||
"hash": "sha256-UfLcrL2Gj/OLz0s92Oo+OCJeDpZFAcQLPLiSNND8D5Y="
|
||||
},
|
||||
{
|
||||
"pname": "Microsoft.NETCore.App.Runtime.osx-x64",
|
||||
"version": "6.0.36",
|
||||
"hash": "sha256-0xIJYFzxdMcnCj3wzkFRQZSnQcPHzPHMzePRIOA3oJs="
|
||||
},
|
||||
{
|
||||
"pname": "Microsoft.NETCore.Platforms",
|
||||
"version": "1.1.0",
|
||||
"hash": "sha256-FeM40ktcObQJk4nMYShB61H/E8B7tIKfl9ObJ0IOcCM="
|
||||
},
|
||||
{
|
||||
"pname": "Microsoft.NETCore.Platforms",
|
||||
"version": "1.1.1",
|
||||
"hash": "sha256-8hLiUKvy/YirCWlFwzdejD2Db3DaXhHxT7GSZx/znJg="
|
||||
},
|
||||
{
|
||||
"pname": "Microsoft.NETCore.Platforms",
|
||||
"version": "2.0.0",
|
||||
"hash": "sha256-IEvBk6wUXSdyCnkj6tHahOJv290tVVT8tyemYcR0Yro="
|
||||
},
|
||||
{
|
||||
"pname": "Microsoft.NETCore.Targets",
|
||||
"version": "1.1.0",
|
||||
"hash": "sha256-0AqQ2gMS8iNlYkrD+BxtIg7cXMnr9xZHtKAuN4bjfaQ="
|
||||
},
|
||||
{
|
||||
"pname": "Microsoft.NETCore.Targets",
|
||||
"version": "1.1.3",
|
||||
"hash": "sha256-WLsf1NuUfRWyr7C7Rl9jiua9jximnVvzy6nk2D2bVRc="
|
||||
},
|
||||
{
|
||||
"pname": "Microsoft.Testing.Extensions.Telemetry",
|
||||
"version": "1.5.3",
|
||||
"hash": "sha256-bIXwPSa3jkr2b6xINOqMUs6/uj/r4oVFM7xq3uVIZDU="
|
||||
},
|
||||
{
|
||||
"pname": "Microsoft.Testing.Extensions.TrxReport.Abstractions",
|
||||
"version": "1.5.3",
|
||||
"hash": "sha256-IfMRfcyaIKEMRtx326ICKtinDBEfGw/Sv8ZHawJ96Yc="
|
||||
},
|
||||
{
|
||||
"pname": "Microsoft.Testing.Extensions.VSTestBridge",
|
||||
"version": "1.5.3",
|
||||
"hash": "sha256-XpM/yFjhLSsuzyDV+xKubs4V1zVVYiV05E0+N4S1h0g="
|
||||
},
|
||||
{
|
||||
"pname": "Microsoft.Testing.Platform",
|
||||
"version": "1.5.3",
|
||||
"hash": "sha256-y61Iih6w5D79dmrj2V675mcaeIiHoj1HSa1FRit2BLM="
|
||||
},
|
||||
{
|
||||
"pname": "Microsoft.Testing.Platform.MSBuild",
|
||||
"version": "1.5.3",
|
||||
"hash": "sha256-YspvjE5Jfi587TAfsvfDVJXNrFOkx1B3y1CKV6m7YLY="
|
||||
},
|
||||
{
|
||||
"pname": "Microsoft.TestPlatform.ObjectModel",
|
||||
"version": "17.12.0",
|
||||
"hash": "sha256-3XBHBSuCxggAIlHXmKNQNlPqMqwFlM952Av6RrLw1/w="
|
||||
},
|
||||
{
|
||||
"pname": "Microsoft.TestPlatform.ObjectModel",
|
||||
"version": "17.14.1",
|
||||
"hash": "sha256-QMf6O+w0IT+16Mrzo7wn+N20f3L1/mDhs/qjmEo1rYs="
|
||||
},
|
||||
{
|
||||
"pname": "Microsoft.TestPlatform.TestHost",
|
||||
"version": "17.14.1",
|
||||
"hash": "sha256-1cxHWcvHRD7orQ3EEEPPxVGEkTpxom1/zoICC9SInJs="
|
||||
},
|
||||
{
|
||||
"pname": "Myriad.Core",
|
||||
"version": "0.8.3",
|
||||
"hash": "sha256-vBOxfq8QriX/yUtaXN69rEQaY/psRNJWxqATLidrt2g="
|
||||
},
|
||||
{
|
||||
"pname": "Myriad.Sdk",
|
||||
"version": "0.8.3",
|
||||
"hash": "sha256-7O397WKhskKOvE3MkJT37BvxorDWngDR6gTUogtDZ2M="
|
||||
},
|
||||
{
|
||||
"pname": "Nerdbank.GitVersioning",
|
||||
"version": "3.8.38-alpha",
|
||||
"hash": "sha256-gPMrVbjOZxXoofczF/pn6eVkLhjVSJIyQrLO2oljrDc="
|
||||
},
|
||||
{
|
||||
"pname": "NETStandard.Library",
|
||||
"version": "2.0.3",
|
||||
"hash": "sha256-Prh2RPebz/s8AzHb2sPHg3Jl8s31inv9k+Qxd293ybo="
|
||||
},
|
||||
{
|
||||
"pname": "Newtonsoft.Json",
|
||||
"version": "13.0.1",
|
||||
"hash": "sha256-K2tSVW4n4beRPzPu3rlVaBEMdGvWSv/3Q1fxaDh4Mjo="
|
||||
},
|
||||
{
|
||||
"pname": "Newtonsoft.Json",
|
||||
"version": "13.0.3",
|
||||
"hash": "sha256-hy/BieY4qxBWVVsDqqOPaLy1QobiIapkbrESm6v2PHc="
|
||||
},
|
||||
{
|
||||
"pname": "NuGet.Common",
|
||||
"version": "6.14.0",
|
||||
"hash": "sha256-jDOwt3veI1GSG8CfBnf2+dJxD3E/Nmlc+vHtD4J76Ms="
|
||||
},
|
||||
{
|
||||
"pname": "NuGet.Configuration",
|
||||
"version": "6.14.0",
|
||||
"hash": "sha256-1PN9s6fhCw3wd2260U6hQ4vG3jIvcG8GIn1oQgxMXA0="
|
||||
},
|
||||
{
|
||||
"pname": "NuGet.Frameworks",
|
||||
"version": "6.14.0",
|
||||
"hash": "sha256-3ViM3R1ucQMEL2hQYsivT86kI6veMQK2xDsiAcFcVQk="
|
||||
},
|
||||
{
|
||||
"pname": "NuGet.Packaging",
|
||||
"version": "6.14.0",
|
||||
"hash": "sha256-Yafbnxs3maj55bJ1oKQiZ0QkntFUzXdhorL94YEUOhY="
|
||||
},
|
||||
{
|
||||
"pname": "NuGet.Protocol",
|
||||
"version": "6.14.0",
|
||||
"hash": "sha256-uLDKfs+QN1MdnuQtTES8qfNzzsmYKM6XB9pwJc4G+eo="
|
||||
},
|
||||
{
|
||||
"pname": "NuGet.Versioning",
|
||||
"version": "6.14.0",
|
||||
"hash": "sha256-DqdOJgsphKxSvqB8b60zNPCaiLfbiu3WnUJ/90feLrY="
|
||||
},
|
||||
{
|
||||
"pname": "NUnit",
|
||||
"version": "4.3.2",
|
||||
"hash": "sha256-0RWe8uFoxYp6qhPlDDEghOMcKJgyw2ybvEoAqBLebeE="
|
||||
},
|
||||
{
|
||||
"pname": "NUnit3TestAdapter",
|
||||
"version": "5.0.0",
|
||||
"hash": "sha256-7jZM4qAbIzne3AcdFfMbvbgogqpxvVe6q2S7Ls8xQy0="
|
||||
},
|
||||
{
|
||||
"pname": "RestEase",
|
||||
"version": "1.6.4",
|
||||
"hash": "sha256-FFmqFwlHhIln46k56Z8KM1G+xuPEh/bceKCQnJcdcdc="
|
||||
},
|
||||
{
|
||||
"pname": "runtime.any.System.Runtime",
|
||||
"version": "4.3.0",
|
||||
"hash": "sha256-qwhNXBaJ1DtDkuRacgHwnZmOZ1u9q7N8j0cWOLYOELM="
|
||||
},
|
||||
{
|
||||
"pname": "runtime.native.System",
|
||||
"version": "4.3.0",
|
||||
"hash": "sha256-ZBZaodnjvLXATWpXXakFgcy6P+gjhshFXmglrL5xD5Y="
|
||||
},
|
||||
{
|
||||
"pname": "runtime.unix.System.Private.Uri",
|
||||
"version": "4.3.0",
|
||||
"hash": "sha256-c5tXWhE/fYbJVl9rXs0uHh3pTsg44YD1dJvyOA0WoMs="
|
||||
},
|
||||
{
|
||||
"pname": "System.Collections.Immutable",
|
||||
"version": "8.0.0",
|
||||
"hash": "sha256-F7OVjKNwpqbUh8lTidbqJWYi476nsq9n+6k0+QVRo3w="
|
||||
},
|
||||
{
|
||||
"pname": "System.Diagnostics.DiagnosticSource",
|
||||
"version": "5.0.0",
|
||||
"hash": "sha256-6mW3N6FvcdNH/pB58pl+pFSCGWgyaP4hfVtC/SMWDV4="
|
||||
},
|
||||
{
|
||||
"pname": "System.Diagnostics.DiagnosticSource",
|
||||
"version": "7.0.0",
|
||||
"hash": "sha256-9Wk8cHSkjKtqkN6xW7KnXoQVtF/VNbKeBq79WqDesMs="
|
||||
},
|
||||
{
|
||||
"pname": "System.Formats.Asn1",
|
||||
"version": "6.0.0",
|
||||
"hash": "sha256-KaMHgIRBF7Nf3VwOo+gJS1DcD+41cJDPWFh+TDQ8ee8="
|
||||
},
|
||||
{
|
||||
"pname": "System.IO.Abstractions",
|
||||
"version": "4.2.13",
|
||||
"hash": "sha256-nkC/PiqE6+c1HJ2yTwg3x+qdBh844Z8n3ERWDW8k6Gg="
|
||||
},
|
||||
{
|
||||
"pname": "System.IO.FileSystem.AccessControl",
|
||||
"version": "4.5.0",
|
||||
"hash": "sha256-ck44YBQ0M+2Im5dw0VjBgFD1s0XuY54cujrodjjSBL8="
|
||||
},
|
||||
{
|
||||
"pname": "System.Memory",
|
||||
"version": "4.5.5",
|
||||
"hash": "sha256-EPQ9o1Kin7KzGI5O3U3PUQAZTItSbk9h/i4rViN3WiI="
|
||||
},
|
||||
{
|
||||
"pname": "System.Private.Uri",
|
||||
"version": "4.3.0",
|
||||
"hash": "sha256-fVfgcoP4AVN1E5wHZbKBIOPYZ/xBeSIdsNF+bdukIRM="
|
||||
},
|
||||
{
|
||||
"pname": "System.Reflection.Metadata",
|
||||
"version": "8.0.0",
|
||||
"hash": "sha256-dQGC30JauIDWNWXMrSNOJncVa1umR1sijazYwUDdSIE="
|
||||
},
|
||||
{
|
||||
"pname": "System.Runtime",
|
||||
"version": "4.3.1",
|
||||
"hash": "sha256-R9T68AzS1PJJ7v6ARz9vo88pKL1dWqLOANg4pkQjkA0="
|
||||
},
|
||||
{
|
||||
"pname": "System.Runtime.CompilerServices.Unsafe",
|
||||
"version": "6.0.0",
|
||||
"hash": "sha256-bEG1PnDp7uKYz/OgLOWs3RWwQSVYm+AnPwVmAmcgp2I="
|
||||
},
|
||||
{
|
||||
"pname": "System.Security.AccessControl",
|
||||
"version": "4.5.0",
|
||||
"hash": "sha256-AFsKPb/nTk2/mqH/PYpaoI8PLsiKKimaXf+7Mb5VfPM="
|
||||
},
|
||||
{
|
||||
"pname": "System.Security.Cryptography.Pkcs",
|
||||
"version": "6.0.4",
|
||||
"hash": "sha256-2e0aRybote+OR66bHaNiYpF//4fCiaO3zbR2e9GABUI="
|
||||
},
|
||||
{
|
||||
"pname": "System.Security.Cryptography.ProtectedData",
|
||||
"version": "4.4.0",
|
||||
"hash": "sha256-Ri53QmFX8I8UH0x4PikQ1ZA07ZSnBUXStd5rBfGWFOE="
|
||||
},
|
||||
{
|
||||
"pname": "System.Security.Principal.Windows",
|
||||
"version": "4.5.0",
|
||||
"hash": "sha256-BkUYNguz0e4NJp1kkW7aJBn3dyH9STwB5N8XqnlCsmY="
|
||||
},
|
||||
{
|
||||
"pname": "System.Text.Json",
|
||||
"version": "8.0.5",
|
||||
"hash": "sha256-yKxo54w5odWT6nPruUVsaX53oPRe+gKzGvLnnxtwP68="
|
||||
},
|
||||
{
|
||||
"pname": "TypeEquality",
|
||||
"version": "0.3.0",
|
||||
"hash": "sha256-V50xAOzzyUJrY+MYPRxtnqW5MVeATXCes89wPprv1r4="
|
||||
},
|
||||
{
|
||||
"pname": "WoofWare.Whippet.Fantomas",
|
||||
"version": "0.6.3",
|
||||
"hash": "sha256-FkW/HtVp8/HE2k6d7yFpnJcnP3FNNe9kGlkoIWmNgDw="
|
||||
}
|
||||
]
|
459
nix/deps.nix
459
nix/deps.nix
@@ -1,459 +0,0 @@
|
||||
# This file was automatically generated by passthru.fetch-deps.
|
||||
# Please don't edit it manually, your changes might get overwritten!
|
||||
{fetchNuGet}: [
|
||||
(fetchNuGet {
|
||||
pname = "fsharp-analyzers";
|
||||
version = "0.25.0";
|
||||
sha256 = "sha256-njfJYi40jNvrD+mgu9LtQw2Omh8P1SSDThesozH0KQY=";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "fantomas";
|
||||
version = "6.3.0-alpha-007";
|
||||
sha256 = "sha256-uZw6h6k/DS4BcYtK9cv8TLS0H8MZDO3WBaPPTdtTgu0=";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "ApiSurface";
|
||||
version = "4.0.30";
|
||||
sha256 = "0khbp0dx87m4kx1a5b9vgh1pp88vr9w8vpqvxf6afrpcyynwrrcr";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "coverlet.collector";
|
||||
version = "6.0.0";
|
||||
sha256 = "12j34vrkmph8lspbafnqmfnj2qvysz1jcrks2khw798s6dwv0j90";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "Fantomas.Core";
|
||||
version = "6.1.1";
|
||||
sha256 = "1h2wsiy4fzwsg9vrlpk6w7zsvx6bc4wg4x25zqc48akg04fwpi0m";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "Fantomas.FCS";
|
||||
version = "6.1.1";
|
||||
sha256 = "0733dm5zjdp8w5wwalqlv1q52pghfr04863i9wy807f4qfd7rrin";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "FsCheck";
|
||||
version = "2.16.6";
|
||||
sha256 = "176rwky6b5rk8dzldiz4068p7m9c5y9ygzbhadrs14jkl94pc56n";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "FSharp.Core";
|
||||
version = "4.3.4";
|
||||
sha256 = "1sg6i4q5nwyzh769g76f6c16876nvdpn83adqjr2y9x6xsiv5p5j";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "FSharp.Core";
|
||||
version = "6.0.1";
|
||||
sha256 = "0qks2aadkhsffg9a6xq954ll9xacnph852avd7ljh9n2g6vj06qj";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "FSharp.Core";
|
||||
version = "8.0.101";
|
||||
sha256 = "0prgcnki6s0rlrfbarrcv50w1bbhaalsyhhw5gsnjs2is7qrjbii";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "FsUnit";
|
||||
version = "6.0.0";
|
||||
sha256 = "18q3p0z155znwj1l0qq3vq9nh9wl2i4mlfx4pmrnia4czr0xdkmb";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "Microsoft.AspNetCore.App.Ref";
|
||||
version = "6.0.26";
|
||||
sha256 = "1d8nkz24vsm0iy2xm8y5ak2q1w1p99dxyz0y26acs6sfk2na0vm6";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "Microsoft.AspNetCore.App.Ref";
|
||||
version = "8.0.1";
|
||||
sha256 = "0yaaiqq7mi6sclyrb1v0fyncanbx0ifmnnhv9whynqj8439jsdwh";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "Microsoft.AspNetCore.App.Runtime.linux-arm64";
|
||||
version = "6.0.26";
|
||||
sha256 = "1za8lc52m4z54d68wd64c2nhzy05g3gx171k5cdlx73fbymiys9z";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "Microsoft.AspNetCore.App.Runtime.linux-arm64";
|
||||
version = "8.0.1";
|
||||
sha256 = "0dsdgqg7566qximmjfza4x9if3icy4kskq698ddj5apdia88h2mw";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "Microsoft.AspNetCore.App.Runtime.linux-x64";
|
||||
version = "6.0.26";
|
||||
sha256 = "1zpbmz6z8758gwywzg0bac8kx9x39sxxc9j4a4r2jl74l9ssw4vm";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "Microsoft.AspNetCore.App.Runtime.linux-x64";
|
||||
version = "8.0.1";
|
||||
sha256 = "1gjz379y61ag9whi78qxx09bwkwcznkx2mzypgycibxk61g11da1";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "Microsoft.AspNetCore.App.Runtime.osx-arm64";
|
||||
version = "6.0.26";
|
||||
sha256 = "1i8ydlwjzk7j0mzvn0rpljxfp1h50zwaqalnyvfxai1fwgigzgw5";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "Microsoft.AspNetCore.App.Runtime.osx-arm64";
|
||||
version = "8.0.1";
|
||||
sha256 = "0w3mrs4zdl9mfanl1j81759xwwrzmicsjxn6yfxv5yrxbxzq695n";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "Microsoft.AspNetCore.App.Runtime.osx-x64";
|
||||
version = "6.0.26";
|
||||
sha256 = "02src68hd3213sd1a2ms1my7i92knfmdxclvv90il9cky2zsq8kw";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "Microsoft.AspNetCore.App.Runtime.osx-x64";
|
||||
version = "8.0.1";
|
||||
sha256 = "0a9aljr4fy4haq6ndz2y723liv5hbfpss1rn45s88nmgcp27m15m";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "Microsoft.AspNetCore.App.Runtime.win-x64";
|
||||
version = "6.0.26";
|
||||
sha256 = "1gxlmfdkfzmhw9pac5jiv674nn6i1zymcp2hj81irjwhhjk01mf5";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "Microsoft.AspNetCore.App.Runtime.win-x64";
|
||||
version = "8.0.1";
|
||||
sha256 = "01kzndyqmsvcq49i2jrv7ymfp0l71yxfylv1cy3nhkdbprqz8ipx";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "Microsoft.Build.Tasks.Git";
|
||||
version = "8.0.0";
|
||||
sha256 = "0055f69q3hbagqp8gl3nk0vfn4qyqyxsxyy7pd0g7wm3z28byzmx";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "Microsoft.CodeCoverage";
|
||||
version = "17.9.0";
|
||||
sha256 = "1gljgi69k0fz8vy8bn6xlyxabj6q4vls2zza9wz7ng6ix3irm89r";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "Microsoft.NET.Test.Sdk";
|
||||
version = "17.9.0";
|
||||
sha256 = "1lls1fly2gr1n9n1xyl9k33l2v4pwfmylyzkq8v4v5ldnwkl1zdb";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "Microsoft.NETCore.App.Host.linux-arm64";
|
||||
version = "6.0.26";
|
||||
sha256 = "19y6c6v20bgf7x7rrh4rx9y7s5fy8vp5m4j9b6gi1wp4rpb5mza4";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "Microsoft.NETCore.App.Host.linux-arm64";
|
||||
version = "8.0.1";
|
||||
sha256 = "0dhpdlcdz7adcfh9w01fc867051m35fqaxnvj3fqvqhgcm2n3143";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "Microsoft.NETCore.App.Host.linux-x64";
|
||||
version = "6.0.26";
|
||||
sha256 = "0p7hhidaa3mnyiwnsijwy8578v843x8hh99255s69qwwyld6falv";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "Microsoft.NETCore.App.Host.linux-x64";
|
||||
version = "8.0.1";
|
||||
sha256 = "1aw6mc7zcmzs1grxz2wa9cw9kfj8pz7zpj417xnp1a9n4ix1bxgr";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "Microsoft.NETCore.App.Host.osx-arm64";
|
||||
version = "6.0.26";
|
||||
sha256 = "1mq11xsv9g1vsasp6k80y7xlvwi9hrpk5dgm773fvy8538s01gfv";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "Microsoft.NETCore.App.Host.osx-arm64";
|
||||
version = "8.0.1";
|
||||
sha256 = "1dzg3prng9zfdzz7gcgywjdbwzhwm85j89z0jahynxx4q2dra4b9";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "Microsoft.NETCore.App.Host.osx-x64";
|
||||
version = "6.0.26";
|
||||
sha256 = "1chac9b4424ihrrnlzvc7qz6j4ymfjyv4kzyazzzw19yhymdkh2s";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "Microsoft.NETCore.App.Host.osx-x64";
|
||||
version = "8.0.1";
|
||||
sha256 = "010f8wn15s2kv7yyzgys3pv9i1mxw20hpv1ig2zhybjxs8lpj8jj";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "Microsoft.NETCore.App.Host.win-x64";
|
||||
version = "6.0.26";
|
||||
sha256 = "0i7g9fsqjnbh9rc6807m57r2idg5pkcw6xjfwhnxkcpgqm96258v";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "Microsoft.NETCore.App.Host.win-x64";
|
||||
version = "8.0.1";
|
||||
sha256 = "1ssj1cyam3nfidm8q82kvh4i3fzm2lzb3bxw6ck09hwhvwh909z4";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "Microsoft.NETCore.App.Ref";
|
||||
version = "6.0.26";
|
||||
sha256 = "12gb52dhg5h9hgnyqh1zgj2w46paxv2pfh33pphl9ajhrdr7hlsb";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "Microsoft.NETCore.App.Ref";
|
||||
version = "8.0.1";
|
||||
sha256 = "02r4jg4ha0qksix9v6s3cpmvavmz54gkawkxy9bvknw5ynxhhl1l";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "Microsoft.NETCore.App.Runtime.linux-arm64";
|
||||
version = "6.0.26";
|
||||
sha256 = "164hfrwqz5dxcbb441lridk4mzcqmarb0b7ckgvqhsvpawyjw88v";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "Microsoft.NETCore.App.Runtime.linux-arm64";
|
||||
version = "8.0.1";
|
||||
sha256 = "0353whnjgz3sqhzsfrviad3a3db4pk7hl7m4wwppv5mqdg9i9ri5";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "Microsoft.NETCore.App.Runtime.linux-x64";
|
||||
version = "6.0.26";
|
||||
sha256 = "0islayddpnflviqpbq4djc4f3v9nhsa2y76k5x6il3csq5vdw2hq";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "Microsoft.NETCore.App.Runtime.linux-x64";
|
||||
version = "8.0.1";
|
||||
sha256 = "1g5b30f4l8a1zjjr3b8pk9mcqxkxqwa86362f84646xaj4iw3a4d";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "Microsoft.NETCore.App.Runtime.osx-arm64";
|
||||
version = "6.0.26";
|
||||
sha256 = "1acn5zw1pxzmcg3c0pbf9hal36fbdh9mvbsiwra7simrk7hzqpdc";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "Microsoft.NETCore.App.Runtime.osx-arm64";
|
||||
version = "8.0.1";
|
||||
sha256 = "0cdrpdaq5sl3602anfx1p0z0ncx2sjjvl6mgsd6y38g47n7f95jc";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "Microsoft.NETCore.App.Runtime.osx-x64";
|
||||
version = "6.0.26";
|
||||
sha256 = "00f9l9dkdz0zv5csaw8fkm6s8ckrj5n9k3ygz12daa22l3bcn6ii";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "Microsoft.NETCore.App.Runtime.osx-x64";
|
||||
version = "8.0.1";
|
||||
sha256 = "1fk1flqp6ji0l4c2gvh83ykndpx7a2nkkgrgkgql3c75j1k2v1s9";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "Microsoft.NETCore.App.Runtime.win-x64";
|
||||
version = "6.0.26";
|
||||
sha256 = "0i2p356phfc5y6qnr3vyrzjfi1mrbwfb6g85k4q37bbyxjfp7zl9";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "Microsoft.NETCore.App.Runtime.win-x64";
|
||||
version = "8.0.1";
|
||||
sha256 = "198576cdkl72xs29zznff9ls763p8pfr0zji7b74dqxd5ga0s3bd";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "Microsoft.NETCore.Platforms";
|
||||
version = "1.1.0";
|
||||
sha256 = "08vh1r12g6ykjygq5d3vq09zylgb84l63k49jc4v8faw9g93iqqm";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "Microsoft.NETCore.Platforms";
|
||||
version = "1.1.1";
|
||||
sha256 = "164wycgng4mi9zqi2pnsf1pq6gccbqvw6ib916mqizgjmd8f44pj";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "Microsoft.NETCore.Platforms";
|
||||
version = "2.0.0";
|
||||
sha256 = "1fk2fk2639i7nzy58m9dvpdnzql4vb8yl8vr19r2fp8lmj9w2jr0";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "Microsoft.NETCore.Targets";
|
||||
version = "1.1.3";
|
||||
sha256 = "05smkcyxir59rgrmp7d6327vvrlacdgldfxhmyr1azclvga1zfsq";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "Microsoft.SourceLink.Common";
|
||||
version = "8.0.0";
|
||||
sha256 = "0xrr8yd34ij7dqnyddkp2awfmf9qn3c89xmw2f3npaa4wnajmx81";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "Microsoft.SourceLink.GitHub";
|
||||
version = "8.0.0";
|
||||
sha256 = "1gdx7n45wwia3yvang3ls92sk3wrymqcx9p349j8wba2lyjf9m44";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "Microsoft.TestPlatform.ObjectModel";
|
||||
version = "17.9.0";
|
||||
sha256 = "1kgsl9w9fganbm9wvlkqgk0ag9hfi58z88rkfybc6kvg78bx89ca";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "Microsoft.TestPlatform.TestHost";
|
||||
version = "17.9.0";
|
||||
sha256 = "19ffh31a1jxzn8j69m1vnk5hyfz3dbxmflq77b8x82zybiilh5nl";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "Myriad.Core";
|
||||
version = "0.8.3";
|
||||
sha256 = "0s5pdckjw4x0qrbd4i3cz9iili5cppg5qnjbr7zjbbhhmxzb24xw";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "Myriad.Sdk";
|
||||
version = "0.8.3";
|
||||
sha256 = "0qv78c5s5m04xb8h17nnn2ig26zcyya91k2dpj745cm1cbnzvvgc";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "Nerdbank.GitVersioning";
|
||||
version = "3.6.133";
|
||||
sha256 = "1cdw8krvsnx0n34f7fm5hiiy7bs6h3asvncqcikc0g46l50w2j80";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "NETStandard.Library";
|
||||
version = "2.0.0";
|
||||
sha256 = "1bc4ba8ahgk15m8k4nd7x406nhi0kwqzbgjk2dmw52ss553xz7iy";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "NETStandard.Library";
|
||||
version = "2.0.3";
|
||||
sha256 = "1fn9fxppfcg4jgypp2pmrpr6awl3qz1xmnri0cygpkwvyx27df1y";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "Newtonsoft.Json";
|
||||
version = "13.0.1";
|
||||
sha256 = "0fijg0w6iwap8gvzyjnndds0q4b8anwxxvik7y8vgq97dram4srb";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "Newtonsoft.Json";
|
||||
version = "13.0.3";
|
||||
sha256 = "0xrwysmrn4midrjal8g2hr1bbg38iyisl0svamb11arqws4w2bw7";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "NuGet.Common";
|
||||
version = "6.9.1";
|
||||
sha256 = "0ic3d46r9v05pkczpmskw86yzixm6iwshbw0ya8i2957nhhlymw8";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "NuGet.Configuration";
|
||||
version = "6.9.1";
|
||||
sha256 = "07z4qgbibpg59j2r05ifnqdyqf2xinm33rx7gjyr1f73kzg01m33";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "NuGet.Frameworks";
|
||||
version = "6.9.1";
|
||||
sha256 = "0s3az3ac53icjnmb14hfjcmkvzscvrkm62jgqf48yvsbysyhqm5s";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "NuGet.Packaging";
|
||||
version = "6.9.1";
|
||||
sha256 = "0w0arkmzg3qh1brq4vm10zrsjm7nw706ld4y5kqcmvjpd16f4b4y";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "NuGet.Protocol";
|
||||
version = "6.7.0";
|
||||
sha256 = "1v5ibnq2mp801vw68zyj169hkj3xm7h55824i33n1jxxj2vs3vbk";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "NuGet.Versioning";
|
||||
version = "6.9.1";
|
||||
sha256 = "0xrs82dydy9cgxf0qypr01wawwnq1nf6fc7rwisb4y5v4r259fdm";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "NUnit";
|
||||
version = "3.13.3";
|
||||
sha256 = "0wdzfkygqnr73s6lpxg5b1pwaqz9f414fxpvpdmf72bvh4jaqzv6";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "NUnit";
|
||||
version = "4.0.1";
|
||||
sha256 = "0jgiq3dbwli5r70j0bw7021d69r7bhr58s8kphlpjmf7k47l5pcd";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "NUnit3TestAdapter";
|
||||
version = "4.5.0";
|
||||
sha256 = "1srx1629s0k1kmf02nmz251q07vj6pv58mdafcr5dr0bbn1fh78i";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "RestEase";
|
||||
version = "1.6.4";
|
||||
sha256 = "1mvi3nbrr450g3fgd1y4wg3bwl9k1agyjfd9wdkqk12714bsln8l";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "runtime.any.System.Runtime";
|
||||
version = "4.3.0";
|
||||
sha256 = "1cqh1sv3h5j7ixyb7axxbdkqx6cxy00p4np4j91kpm492rf4s25b";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "runtime.native.System";
|
||||
version = "4.3.0";
|
||||
sha256 = "15hgf6zaq9b8br2wi1i3x0zvmk410nlmsmva9p0bbg73v6hml5k4";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "runtime.unix.System.Private.Uri";
|
||||
version = "4.3.0";
|
||||
sha256 = "1jx02q6kiwlvfksq1q9qr17fj78y5v6mwsszav4qcz9z25d5g6vk";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "System.Diagnostics.DiagnosticSource";
|
||||
version = "7.0.0";
|
||||
sha256 = "1jxhvsh5mzdf0sgb4dfmbys1b12ylyr5pcfyj1map354fiq3qsgm";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "System.Formats.Asn1";
|
||||
version = "6.0.0";
|
||||
sha256 = "1vvr7hs4qzjqb37r0w1mxq7xql2b17la63jwvmgv65s1hj00g8r9";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "System.IO.Abstractions";
|
||||
version = "4.2.13";
|
||||
sha256 = "0s784iphsmj4vhkrzq9q3w39vsn76w44zclx3hsygsw458zbyh4y";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "System.IO.FileSystem.AccessControl";
|
||||
version = "4.5.0";
|
||||
sha256 = "1gq4s8w7ds1sp8f9wqzf8nrzal40q5cd2w4pkf4fscrl2ih3hkkj";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "System.Memory";
|
||||
version = "4.5.5";
|
||||
sha256 = "08jsfwimcarfzrhlyvjjid61j02irx6xsklf32rv57x2aaikvx0h";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "System.Private.Uri";
|
||||
version = "4.3.0";
|
||||
sha256 = "04r1lkdnsznin0fj4ya1zikxiqr0h6r6a1ww2dsm60gqhdrf0mvx";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "System.Reflection.Metadata";
|
||||
version = "1.6.0";
|
||||
sha256 = "1wdbavrrkajy7qbdblpbpbalbdl48q3h34cchz24gvdgyrlf15r4";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "System.Runtime";
|
||||
version = "4.3.1";
|
||||
sha256 = "03ch4d2acf6q037a4njxpll2kkx3dwzlg07yxr4z5m6j1kqgmm27";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "System.Runtime.CompilerServices.Unsafe";
|
||||
version = "6.0.0";
|
||||
sha256 = "0qm741kh4rh57wky16sq4m0v05fxmkjjr87krycf5vp9f0zbahbc";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "System.Security.AccessControl";
|
||||
version = "4.5.0";
|
||||
sha256 = "1wvwanz33fzzbnd2jalar0p0z3x0ba53vzx1kazlskp7pwyhlnq0";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "System.Security.Cryptography.Pkcs";
|
||||
version = "6.0.4";
|
||||
sha256 = "0hh5h38pnxmlrnvs72f2hzzpz4b2caiiv6xf8y7fzdg84r3imvfr";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "System.Security.Cryptography.ProtectedData";
|
||||
version = "4.4.0";
|
||||
sha256 = "1q8ljvqhasyynp94a1d7jknk946m20lkwy2c3wa8zw2pc517fbj6";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "System.Security.Principal.Windows";
|
||||
version = "4.5.0";
|
||||
sha256 = "0rmj89wsl5yzwh0kqjgx45vzf694v9p92r4x4q6yxldk1cv1hi86";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "System.Text.Encodings.Web";
|
||||
version = "6.0.0";
|
||||
sha256 = "06n9ql3fmhpjl32g3492sj181zjml5dlcc5l76xq2h38c4f87sai";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "System.Text.Json";
|
||||
version = "6.0.0";
|
||||
sha256 = "1si2my1g0q0qv1hiqnji4xh9wd05qavxnzj9dwgs23iqvgjky0gl";
|
||||
})
|
||||
]
|
@@ -1,73 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# This file was adapted from
|
||||
# https://github.com/NixOS/nixpkgs/blob/b981d811453ab84fb3ea593a9b33b960f1ab9147/pkgs/build-support/dotnet/build-dotnet-module/default.nix#L173
|
||||
set -euo pipefail
|
||||
export PATH="@binPath@"
|
||||
for arg in "$@"; do
|
||||
case "$arg" in
|
||||
--keep-sources|-k)
|
||||
keepSources=1
|
||||
shift
|
||||
;;
|
||||
--help|-h)
|
||||
echo "usage: $0 [--keep-sources] [--help] <output path>"
|
||||
echo " <output path> The path to write the lockfile to. A temporary file is used if this is not set"
|
||||
echo " --keep-sources Don't remove temporary directories upon exit, useful for debugging"
|
||||
echo " --help Show this help message"
|
||||
exit
|
||||
;;
|
||||
esac
|
||||
done
|
||||
tmp=$(mktemp -td "@pname@-tmp-XXXXXX")
|
||||
export tmp
|
||||
HOME=$tmp/home
|
||||
exitTrap() {
|
||||
test -n "${ranTrap-}" && return
|
||||
ranTrap=1
|
||||
if test -n "${keepSources-}"; then
|
||||
echo -e "Path to the source: $tmp/src\nPath to the fake home: $tmp/home"
|
||||
else
|
||||
rm -rf "$tmp"
|
||||
fi
|
||||
# Since mktemp is used this will be empty if the script didnt succesfully complete
|
||||
if ! test -s "$depsFile"; then
|
||||
rm -rf "$depsFile"
|
||||
fi
|
||||
}
|
||||
trap exitTrap EXIT INT TERM
|
||||
dotnetRestore() {
|
||||
local -r project="${1-}"
|
||||
local -r rid="$2"
|
||||
dotnet restore "${project-}" \
|
||||
-p:ContinuousIntegrationBuild=true \
|
||||
-p:Deterministic=true \
|
||||
--packages "$tmp/nuget_pkgs" \
|
||||
--runtime "$rid" \
|
||||
--no-cache \
|
||||
--force
|
||||
}
|
||||
declare -a projectFiles=( @projectFiles@ )
|
||||
declare -a testProjectFiles=( @testProjectFiles@ )
|
||||
export DOTNET_NOLOGO=1
|
||||
export DOTNET_CLI_TELEMETRY_OPTOUT=1
|
||||
depsFile=$(realpath "${1:-$(mktemp -t "@pname@-deps-XXXXXX.nix")}")
|
||||
mkdir -p "$tmp/nuget_pkgs"
|
||||
storeSrc="@storeSrc@"
|
||||
src="$tmp/src"
|
||||
cp -rT "$storeSrc" "$src"
|
||||
chmod -R +w "$src"
|
||||
cd "$src"
|
||||
echo "Restoring project..."
|
||||
rids=("@rids@")
|
||||
for rid in "${rids[@]}"; do
|
||||
(( ${#projectFiles[@]} == 0 )) && dotnetRestore "" "$rid"
|
||||
for project in "${projectFiles[@]-}" "${testProjectFiles[@]-}"; do
|
||||
dotnetRestore "$project" "$rid"
|
||||
done
|
||||
done
|
||||
echo "Successfully restored project"
|
||||
echo "Writing lockfile..."
|
||||
echo -e "# This file was automatically generated by passthru.fetch-deps.\n# Please don't edit it manually, your changes might get overwritten!\n" > "$depsFile"
|
||||
nuget-to-nix "$tmp/nuget_pkgs" "@packages@" >> "$depsFile"
|
||||
echo "Successfully wrote lockfile to $depsFile"
|
Reference in New Issue
Block a user