Compare commits

...

34 Commits

Author SHA1 Message Date
Patrick Stevens
9d8cef8fdc Switch to trusted publishing (#431) 2025-10-03 09:37:32 +00:00
Patrick Stevens
1721ad1ac0 Unconditional function for empty generating mock (#430) 2025-09-30 21:52:59 +00:00
dependabot[bot]
857bde0ba9 Bump Nerdbank.GitVersioning from 3.8.38-alpha to 3.8.118 (#428)
* Bump Nerdbank.GitVersioning from 3.8.38-alpha to 3.8.118

---
updated-dependencies:
- dependency-name: Nerdbank.GitVersioning
  dependency-version: 3.8.118
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

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

* Deps

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Smaug123 <3138005+Smaug123@users.noreply.github.com>
2025-09-29 23:52:31 +01:00
patrick-conscriptus[bot]
d10f608941 Automated commit (#427)
Co-authored-by: patrick-conscriptus[bot] <175414948+patrick-conscriptus[bot]@users.noreply.github.com>
2025-09-28 01:31:56 +00:00
patrick-conscriptus[bot]
46effedfc4 Automated commit (#426)
Co-authored-by: patrick-conscriptus[bot] <175414948+patrick-conscriptus[bot]@users.noreply.github.com>
2025-09-21 01:31:20 +00:00
Patrick Stevens
1b85182b9d GenerateCapturingMock that captures calls made to it (#425) 2025-09-18 15:42:10 +01:00
patrick-conscriptus[bot]
738e0c1f1b Automated commit (#421)
Co-authored-by: patrick-conscriptus[bot] <175414948+patrick-conscriptus[bot]@users.noreply.github.com>
2025-09-14 01:29:14 +00:00
Patrick Stevens
8e415ea679 Bump ApiSurface (#420) 2025-09-08 20:40:27 +00:00
dependabot[bot]
b7427b523a Bump WoofWare.Expect from 0.8.1 to 0.8.2 (#419)
* Bump WoofWare.Expect from 0.8.1 to 0.8.2

---
updated-dependencies:
- dependency-name: WoofWare.Expect
  dependency-version: 0.8.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

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

* Deps

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Smaug123 <3138005+Smaug123@users.noreply.github.com>
2025-09-08 17:45:48 +00:00
dependabot[bot]
d4891dfa29 Bump actions/attest-build-provenance from 2.4.0 to 3.0.0 (#418) 2025-09-08 12:05:17 +01:00
patrick-conscriptus[bot]
3e51eb764e Automated commit (#417)
Co-authored-by: patrick-conscriptus[bot] <175414948+patrick-conscriptus[bot]@users.noreply.github.com>
2025-09-07 01:29:59 +00:00
patrick-conscriptus[bot]
a704e3b959 Automated commit (#416)
Co-authored-by: patrick-conscriptus[bot] <175414948+patrick-conscriptus[bot]@users.noreply.github.com>
2025-08-31 01:31:28 +00:00
dependabot[bot]
e1e7198cc4 Bump FsCheck from 3.3.0 to 3.3.1 (#415)
* Bump FsCheck from 3.3.0 to 3.3.1

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

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

* Deps

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Smaug123 <3138005+Smaug123@users.noreply.github.com>
2025-08-26 08:03:52 +01:00
dependabot[bot]
dd55b3dcbc Bump actions/checkout from 4 to 5 (#414)
Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 5.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-25 20:35:12 +01:00
patrick-conscriptus[bot]
7483658b41 Automated commit (#413)
Co-authored-by: patrick-conscriptus[bot] <175414948+patrick-conscriptus[bot]@users.noreply.github.com>
2025-08-24 01:45:02 +00:00
patrick-conscriptus[bot]
fd7c513bb3 Automated commit (#412)
Co-authored-by: patrick-conscriptus[bot] <175414948+patrick-conscriptus[bot]@users.noreply.github.com>
2025-08-17 01:48:28 +00:00
dependabot[bot]
aca60f1b6a Bump actions/download-artifact from 4 to 5 (#411) 2025-08-11 16:29:40 +01:00
patrick-conscriptus[bot]
1715975fa3 Automated commit (#410)
Co-authored-by: patrick-conscriptus[bot] <175414948+patrick-conscriptus[bot]@users.noreply.github.com>
2025-08-10 01:54:13 +00:00
dependabot[bot]
628f87d67f Bump fsharp-analyzers and WoofWare.Expect (#409)
* Bump fsharp-analyzers and WoofWare.Expect

Bumps fsharp-analyzers from 0.32.0 to 0.32.1
Bumps WoofWare.Expect from 0.6.2 to 0.8.1

---
updated-dependencies:
- dependency-name: fsharp-analyzers
  dependency-version: 0.32.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: WoofWare.Expect
  dependency-version: 0.8.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

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

* Deps

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Smaug123 <3138005+Smaug123@users.noreply.github.com>
2025-08-04 17:46:58 +00:00
patrick-conscriptus[bot]
0374bc00f2 Automated commit (#408)
Co-authored-by: patrick-conscriptus[bot] <175414948+patrick-conscriptus[bot]@users.noreply.github.com>
2025-08-03 01:57:07 +00:00
dependabot[bot]
69f1112a52 Bump WoofWare.Expect from 0.5.1 to 0.6.2 (#406) 2025-07-28 17:20:03 +00:00
Patrick Stevens
bc69a7a364 Add to envrc (#407) 2025-07-28 17:15:00 +00:00
patrick-conscriptus[bot]
72f3800826 Automated commit (#405)
Co-authored-by: patrick-conscriptus[bot] <175414948+patrick-conscriptus[bot]@users.noreply.github.com>
2025-07-27 01:55:41 +00:00
patrick-conscriptus[bot]
88500f0043 Automated commit (#404)
Co-authored-by: patrick-conscriptus[bot] <175414948+patrick-conscriptus[bot]@users.noreply.github.com>
2025-07-20 01:54:59 +00:00
dependabot[bot]
6491fc68f4 Bump ApiSurface to 4.1.22 (#403)
* Bump ApiSurface to 4.1.22

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

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

* Deps

* Deps

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Smaug123 <3138005+Smaug123@users.noreply.github.com>
2025-07-14 21:07:28 +00:00
patrick-conscriptus[bot]
57af5ffa1a Automated commit (#402)
Co-authored-by: patrick-conscriptus[bot] <175414948+patrick-conscriptus[bot]@users.noreply.github.com>
2025-07-13 01:53:59 +00:00
dependabot[bot]
7f20ed9761 Bump WoofWare.Expect from 0.4.5 to 0.5.1 (#401)
* Bump WoofWare.Expect from 0.4.5 to 0.5.1

---
updated-dependencies:
- dependency-name: WoofWare.Expect
  dependency-version: 0.5.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

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

* Deps

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Smaug123 <3138005+Smaug123@users.noreply.github.com>
2025-07-07 21:14:55 +01:00
patrick-conscriptus[bot]
c5c5bbd02a Automated commit (#400)
Co-authored-by: patrick-conscriptus[bot] <175414948+patrick-conscriptus[bot]@users.noreply.github.com>
2025-07-06 01:49:30 +00:00
dependabot[bot]
470b073884 Bump FsUnit from 7.0.1 to 7.1.1 (#399)
* Bump FsUnit from 7.0.1 to 7.1.1

---
updated-dependencies:
- dependency-name: FsUnit
  dependency-version: 7.1.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

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

* Deps

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Smaug123 <3138005+Smaug123@users.noreply.github.com>
2025-06-30 18:12:05 +00:00
patrick-conscriptus[bot]
3b9fa7f3b8 Automated commit (#398)
Co-authored-by: patrick-conscriptus[bot] <175414948+patrick-conscriptus[bot]@users.noreply.github.com>
2025-06-29 01:50:32 +00:00
dependabot[bot]
510c4da2ca Bump WoofWare.Expect from 0.4.2 to 0.4.5 (#397)
* Bump WoofWare.Expect from 0.4.2 to 0.4.5

---
updated-dependencies:
- dependency-name: WoofWare.Expect
  dependency-version: 0.4.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

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

* Deps

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Smaug123 <3138005+Smaug123@users.noreply.github.com>
2025-06-23 23:03:31 +00:00
patrick-conscriptus[bot]
d0d5fa4040 Automated commit (#396)
Co-authored-by: patrick-conscriptus[bot] <175414948+patrick-conscriptus[bot]@users.noreply.github.com>
2025-06-22 01:48:42 +00:00
Patrick Stevens
4236b26189 Parse OpenAPI 3 files (#392) 2025-06-18 18:00:56 +00:00
Patrick Stevens
4fe4e3f277 Add JSON headers automatically to Body in HTTP client (#395) 2025-06-18 15:46:14 +00:00
55 changed files with 8600 additions and 145 deletions

View File

@@ -3,13 +3,13 @@
"isRoot": true,
"tools": {
"fantomas": {
"version": "7.0.2",
"version": "7.0.3",
"commands": [
"fantomas"
]
},
"fsharp-analyzers": {
"version": "0.31.0",
"version": "0.32.1",
"commands": [
"fsharp-analyzers"
]

22
.envrc
View File

@@ -1 +1,23 @@
use flake
DOTNET_PATH=$(readlink "$(which dotnet)")
SETTINGS_FILE=$(find . -maxdepth 1 -type f -name '*.sln.DotSettings.user')
MSBUILD=$(realpath "$(find "$(dirname "$DOTNET_PATH")/../share/dotnet/sdk" -maxdepth 2 -type f -name MSBuild.dll)")
if [ -f "$SETTINGS_FILE" ] ; then
xmlstarlet ed --inplace \
-N wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation" \
-N x="http://schemas.microsoft.com/winfx/2006/xaml" \
-N s="clr-namespace:System;assembly=mscorlib" \
-N ss="urn:shemas-jetbrains-com:settings-storage-xaml" \
--update "//s:String[@x:Key='/Default/Environment/Hierarchy/Build/BuildTool/DotNetCliExePath/@EntryValue']" \
--value "$(realpath "$(dirname "$DOTNET_PATH")/../share/dotnet/dotnet")" \
"$SETTINGS_FILE"
xmlstarlet ed --inplace \
-N wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation" \
-N x="http://schemas.microsoft.com/winfx/2006/xaml" \
-N s="clr-namespace:System;assembly=mscorlib" \
-N ss="urn:shemas-jetbrains-com:settings-storage-xaml" \
--update "//s:String[@x:Key='/Default/Environment/Hierarchy/Build/BuildTool/CustomBuildToolPath/@EntryValue']" \
--value "$MSBUILD" \
"$SETTINGS_FILE"
fi

View File

@@ -25,7 +25,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
with:
fetch-depth: 0 # so that NerdBank.GitVersioning has access to history
- name: Install Nix
@@ -46,7 +46,7 @@ jobs:
security-events: write
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v5
with:
fetch-depth: 0 # so that NerdBank.GitVersioning has access to history
- name: Install Nix
@@ -65,7 +65,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v5
- name: Install Nix
uses: cachix/install-nix-action@v31
with:
@@ -80,7 +80,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v5
- name: Install Nix
uses: cachix/install-nix-action@v31
with:
@@ -93,7 +93,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v5
with:
fetch-depth: 0 # so that NerdBank.GitVersioning has access to history
- name: Install Nix
@@ -114,7 +114,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v5
- name: Install Nix
uses: cachix/install-nix-action@v31
with:
@@ -152,7 +152,7 @@ jobs:
nuget-pack:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
with:
fetch-depth: 0 # so that NerdBank.GitVersioning has access to history
- name: Install Nix
@@ -182,7 +182,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Download NuGet artifact (plugin)
uses: actions/download-artifact@v4
uses: actions/download-artifact@v5
with:
name: nuget-package-plugin
path: packed-plugin
@@ -190,7 +190,7 @@ jobs:
# Verify that there is exactly one nupkg in the artifact that would be NuGet published
run: if [[ $(find packed-plugin -maxdepth 1 -name 'WoofWare.Myriad.Plugins.*.nupkg' -printf c | wc -c) -ne "1" ]]; then exit 1; fi
- name: Download NuGet artifact (attributes)
uses: actions/download-artifact@v4
uses: actions/download-artifact@v5
with:
name: nuget-package-attribute
path: packed-attribute
@@ -207,9 +207,9 @@ jobs:
runs-on: ubuntu-latest
needs: [nuget-pack]
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
- name: Download NuGet artifact
uses: actions/download-artifact@v4
uses: actions/download-artifact@v5
with:
name: ${{ matrix.artifact }}
- name: Compute package path
@@ -249,12 +249,12 @@ jobs:
contents: read
steps:
- name: Download NuGet artifact
uses: actions/download-artifact@v4
uses: actions/download-artifact@v5
with:
name: nuget-package-attribute
path: packed
- name: Attest Build Provenance
uses: actions/attest-build-provenance@e8998f949152b193b063cb0ec769d69d929409be # v2.4.0
uses: actions/attest-build-provenance@977bb373ede98d70efdf65b84cb5f73e068dcc2a # v3.0.0
with:
subject-path: "packed/*.nupkg"
@@ -268,12 +268,12 @@ jobs:
contents: read
steps:
- name: Download NuGet artifact
uses: actions/download-artifact@v4
uses: actions/download-artifact@v5
with:
name: nuget-package-plugin
path: packed
- name: Attest Build Provenance
uses: actions/attest-build-provenance@e8998f949152b193b063cb0ec769d69d929409be # v2.4.0
uses: actions/attest-build-provenance@977bb373ede98d70efdf65b84cb5f73e068dcc2a # v3.0.0
with:
subject-path: "packed/*.nupkg"
@@ -287,26 +287,31 @@ jobs:
attestations: write
contents: read
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
- 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
uses: actions/download-artifact@v5
with:
name: nuget-package-attribute
path: packed
- name: Identify `dotnet`
id: dotnet-identify
run: nix develop --command bash -c 'echo "dotnet=$(which dotnet)" >> $GITHUB_OUTPUT'
- name: Obtain NuGet key
uses: NuGet/login@d22cc5f58ff5b88bf9bd452535b4335137e24544
id: login
with:
user: ${{ secrets.NUGET_USER }}
- 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 }}
nuget-key: ${{ steps.login.outputs.NUGET_API_KEY }}
nupkg-dir: packed/
dotnet: ${{ steps.dotnet-identify.outputs.dotnet }}
@@ -320,26 +325,31 @@ jobs:
attestations: write
contents: read
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
- 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
uses: actions/download-artifact@v5
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: Obtain NuGet key
uses: NuGet/login@d22cc5f58ff5b88bf9bd452535b4335137e24544
id: login
with:
user: ${{ secrets.NUGET_USER }}
- 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 }}
nuget-key: ${{ steps.login.outputs.NUGET_API_KEY }}
nupkg-dir: packed/
dotnet: ${{ steps.dotnet-identify.outputs.dotnet }}
@@ -356,9 +366,9 @@ jobs:
permissions:
contents: write
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
- name: Download NuGet artifact
uses: actions/download-artifact@v4
uses: actions/download-artifact@v5
with:
name: ${{ matrix.artifact }}
- name: Compute package path

View File

@@ -11,7 +11,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Check out repository
uses: actions/checkout@v4
uses: actions/checkout@v5
- name: Install Nix
uses: DeterminateSystems/nix-installer-action@main

View File

@@ -1,5 +1,14 @@
Notable changes are recorded here.
# WoofWare.Myriad.Plugins 8.1.1
Adds `GenerateCapturingMock`, which is `GenerateMock` but additionally records the calls made to each function.
# WoofWare.Myriad.Plugins 8.0.3
The RestEase-style HTTP client generator now automatically adds the `application/json` content type header to requests which are POSTing a body that is known to be JSON-serialised.
You can override this by setting the `[<RestEase.Header ("Content-Type", "desired content type")>]` header manually on any affected member.
# WoofWare.Myriad.Plugins 7.0.1
All generators should now be compatible with `<Nullable>enable</Nullable>`.

View File

@@ -0,0 +1,57 @@
namespace SomeNamespace.CapturingMock
open System
open WoofWare.Myriad.Plugins
[<GenerateCapturingMock>]
type IPublicType =
abstract Mem1 : foo : string * int -> string list
abstract Mem2 : string -> int
abstract Mem3 : x : int * ?ct : System.Threading.CancellationToken -> string
[<GenerateCapturingMock false>]
type IPublicTypeInternalFalse =
abstract Mem1 : string * int -> string list
abstract Mem2 : string -> int
abstract Mem3 : x : int * ?ct : System.Threading.CancellationToken -> string
[<GenerateCapturingMock>]
type internal InternalType =
abstract Mem1 : string * int -> unit
abstract Mem2 : string -> int
[<GenerateCapturingMock>]
type private PrivateType =
abstract Mem1 : string * int -> unit
abstract Mem2 : string -> int
[<GenerateCapturingMock false>]
type private PrivateTypeInternalFalse =
abstract Mem1 : string * int -> unit
abstract Mem2 : string -> int
[<GenerateCapturingMock>]
type VeryPublicType<'a, 'b> =
abstract Mem1 : 'a -> 'b
[<GenerateCapturingMock>]
type Curried<'a> =
abstract Mem1 : bar : int -> 'a -> string
abstract Mem2 : int * string -> baz : 'a -> string
abstract Mem3 : quux : (int * string) -> flurb : '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
[<GenerateCapturingMock>]
type TypeWithInterface =
inherit IDisposable
abstract Mem1 : string option -> string[] Async
abstract Mem2 : unit -> string[] Async
[<GenerateCapturingMock>]
type TypeWithProperties =
inherit IDisposable
abstract Mem1 : string option -> string[] Async
abstract Prop1 : int
abstract Prop2 : unit Async

View File

@@ -0,0 +1,41 @@
namespace SomeNamespace.CapturingMock
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

View File

@@ -33,6 +33,10 @@
<Compile Include="GeneratedMock.fs">
<MyriadFile>MockExample.fs</MyriadFile>
</Compile>
<Compile Include="CapturingMockExample.fs" />
<Compile Include="GeneratedCapturingMock.fs">
<MyriadFile>CapturingMockExample.fs</MyriadFile>
</Compile>
<Compile Include="MockExampleNoAttributes.fs" />
<Compile Include="GeneratedMockNoAttributes.fs">
<MyriadFile>MockExampleNoAttributes.fs</MyriadFile>
@@ -47,6 +51,20 @@
<TypeWithInterfaceNoAttr>GenerateMock</TypeWithInterfaceNoAttr>
</MyriadParams>
</Compile>
<Compile Include="CapturingMockExampleNoAttributes.fs" />
<Compile Include="GeneratedCapturingMockNoAttributes.fs">
<MyriadFile>CapturingMockExampleNoAttributes.fs</MyriadFile>
<MyriadParams>
<IPublicTypeNoAttr>GenerateCapturingMock</IPublicTypeNoAttr>
<IPublicTypeInternalFalseNoAttr>GenerateCapturingMock(false)</IPublicTypeInternalFalseNoAttr>
<InternalTypeNoAttr>GenerateCapturingMock</InternalTypeNoAttr>
<PrivateTypeNoAttr>GenerateCapturingMock</PrivateTypeNoAttr>
<PrivateTypeInternalFalseNoAttr>GenerateCapturingMock(false)</PrivateTypeInternalFalseNoAttr>
<VeryPublicTypeNoAttr>GenerateCapturingMock</VeryPublicTypeNoAttr>
<CurriedNoAttr>GenerateCapturingMock</CurriedNoAttr>
<TypeWithInterfaceNoAttr>GenerateCapturingMock</TypeWithInterfaceNoAttr>
</MyriadParams>
</Compile>
<Compile Include="Vault.fs" />
<Compile Include="GeneratedVault.fs">
<MyriadFile>Vault.fs</MyriadFile>

View File

@@ -4,6 +4,7 @@
//------------------------------------------------------------------------------
namespace Gitea
open WoofWare.Myriad.Plugins

View File

@@ -8,6 +8,7 @@
namespace ConsumePlugin
open System

View File

@@ -0,0 +1,562 @@
//------------------------------------------------------------------------------
// This code was generated by myriad.
// Changes to this file will be lost when the code is regenerated.
//------------------------------------------------------------------------------
namespace SomeNamespace.CapturingMock
open System
open WoofWare.Myriad.Plugins
[<RequireQualifiedAccess>]
module internal PublicTypeMockCalls =
/// All the calls made to a PublicTypeMock mock
type internal Calls =
{
Mem1 : ResizeArray<string * int>
Mem2 : ResizeArray<string>
Mem3 : ResizeArray<int * System.Threading.CancellationToken option>
}
/// A fresh calls object which has not yet had any calls made.
static member Empty () : Calls =
{
Mem1 = ResizeArray ()
Mem2 = ResizeArray ()
Mem3 = ResizeArray ()
}
/// Mock record type for an interface
type internal PublicTypeMock =
{
Calls : PublicTypeMockCalls.Calls
Mem1 : string * int -> string list
Mem2 : string -> int
Mem3 : int * System.Threading.CancellationToken option -> string
}
/// An implementation where every non-unit method throws.
static member Empty () : PublicTypeMock =
{
Calls = PublicTypeMockCalls.Calls.Empty ()
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) =
lock this.Calls.Mem1 (fun _ -> this.Calls.Mem1.Add (arg_0_0, arg_0_1))
this.Mem1 (arg_0_0, arg_0_1)
member this.Mem2 arg_0_0 =
lock this.Calls.Mem2 (fun _ -> this.Calls.Mem2.Add (arg_0_0))
this.Mem2 (arg_0_0)
member this.Mem3 (arg_0_0, arg_0_1) =
lock this.Calls.Mem3 (fun _ -> this.Calls.Mem3.Add (arg_0_0, arg_0_1))
this.Mem3 (arg_0_0, arg_0_1)
namespace SomeNamespace.CapturingMock
open System
open WoofWare.Myriad.Plugins
[<RequireQualifiedAccess>]
module public PublicTypeInternalFalseMockCalls =
/// All the calls made to a PublicTypeInternalFalseMock mock
type public Calls =
{
Mem1 : ResizeArray<string * int>
Mem2 : ResizeArray<string>
Mem3 : ResizeArray<int * System.Threading.CancellationToken option>
}
/// A fresh calls object which has not yet had any calls made.
static member Empty () : Calls =
{
Mem1 = ResizeArray ()
Mem2 = ResizeArray ()
Mem3 = ResizeArray ()
}
/// Mock record type for an interface
type public PublicTypeInternalFalseMock =
{
Calls : PublicTypeInternalFalseMockCalls.Calls
Mem1 : string * int -> string list
Mem2 : string -> int
Mem3 : int * System.Threading.CancellationToken option -> string
}
/// An implementation where every non-unit method throws.
static member Empty () : PublicTypeInternalFalseMock =
{
Calls = PublicTypeInternalFalseMockCalls.Calls.Empty ()
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) =
lock this.Calls.Mem1 (fun _ -> this.Calls.Mem1.Add (arg_0_0, arg_0_1))
this.Mem1 (arg_0_0, arg_0_1)
member this.Mem2 arg_0_0 =
lock this.Calls.Mem2 (fun _ -> this.Calls.Mem2.Add (arg_0_0))
this.Mem2 (arg_0_0)
member this.Mem3 (arg_0_0, arg_0_1) =
lock this.Calls.Mem3 (fun _ -> this.Calls.Mem3.Add (arg_0_0, arg_0_1))
this.Mem3 (arg_0_0, arg_0_1)
namespace SomeNamespace.CapturingMock
open System
open WoofWare.Myriad.Plugins
[<RequireQualifiedAccess>]
module internal InternalTypeMockCalls =
/// All the calls made to a InternalTypeMock mock
type internal Calls =
{
Mem1 : ResizeArray<string * int>
Mem2 : ResizeArray<string>
}
/// A fresh calls object which has not yet had any calls made.
static member Empty () : Calls =
{
Mem1 = ResizeArray ()
Mem2 = ResizeArray ()
}
/// Mock record type for an interface
type internal InternalTypeMock =
{
Calls : InternalTypeMockCalls.Calls
Mem1 : string * int -> unit
Mem2 : string -> int
}
/// An implementation where every non-unit method throws.
static member Empty () : InternalTypeMock =
{
Calls = InternalTypeMockCalls.Calls.Empty ()
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) =
lock this.Calls.Mem1 (fun _ -> this.Calls.Mem1.Add (arg_0_0, arg_0_1))
this.Mem1 (arg_0_0, arg_0_1)
member this.Mem2 arg_0_0 =
lock this.Calls.Mem2 (fun _ -> this.Calls.Mem2.Add (arg_0_0))
this.Mem2 (arg_0_0)
namespace SomeNamespace.CapturingMock
open System
open WoofWare.Myriad.Plugins
[<RequireQualifiedAccess>]
module internal PrivateTypeMockCalls =
/// All the calls made to a PrivateTypeMock mock
type internal Calls =
{
Mem1 : ResizeArray<string * int>
Mem2 : ResizeArray<string>
}
/// A fresh calls object which has not yet had any calls made.
static member Empty () : Calls =
{
Mem1 = ResizeArray ()
Mem2 = ResizeArray ()
}
/// Mock record type for an interface
type private PrivateTypeMock =
{
Calls : PrivateTypeMockCalls.Calls
Mem1 : string * int -> unit
Mem2 : string -> int
}
/// An implementation where every non-unit method throws.
static member Empty () : PrivateTypeMock =
{
Calls = PrivateTypeMockCalls.Calls.Empty ()
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) =
lock this.Calls.Mem1 (fun _ -> this.Calls.Mem1.Add (arg_0_0, arg_0_1))
this.Mem1 (arg_0_0, arg_0_1)
member this.Mem2 arg_0_0 =
lock this.Calls.Mem2 (fun _ -> this.Calls.Mem2.Add (arg_0_0))
this.Mem2 (arg_0_0)
namespace SomeNamespace.CapturingMock
open System
open WoofWare.Myriad.Plugins
[<RequireQualifiedAccess>]
module internal PrivateTypeInternalFalseMockCalls =
/// All the calls made to a PrivateTypeInternalFalseMock mock
type internal Calls =
{
Mem1 : ResizeArray<string * int>
Mem2 : ResizeArray<string>
}
/// A fresh calls object which has not yet had any calls made.
static member Empty () : Calls =
{
Mem1 = ResizeArray ()
Mem2 = ResizeArray ()
}
/// Mock record type for an interface
type private PrivateTypeInternalFalseMock =
{
Calls : PrivateTypeInternalFalseMockCalls.Calls
Mem1 : string * int -> unit
Mem2 : string -> int
}
/// An implementation where every non-unit method throws.
static member Empty () : PrivateTypeInternalFalseMock =
{
Calls = PrivateTypeInternalFalseMockCalls.Calls.Empty ()
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) =
lock this.Calls.Mem1 (fun _ -> this.Calls.Mem1.Add (arg_0_0, arg_0_1))
this.Mem1 (arg_0_0, arg_0_1)
member this.Mem2 arg_0_0 =
lock this.Calls.Mem2 (fun _ -> this.Calls.Mem2.Add (arg_0_0))
this.Mem2 (arg_0_0)
namespace SomeNamespace.CapturingMock
open System
open WoofWare.Myriad.Plugins
[<RequireQualifiedAccess>]
module internal VeryPublicTypeMockCalls =
/// All the calls made to a VeryPublicTypeMock mock
type internal Calls<'a, 'b> =
{
Mem1 : ResizeArray<'a>
}
/// A fresh calls object which has not yet had any calls made.
static member Empty () : Calls<'a, 'b> =
{
Mem1 = ResizeArray ()
}
/// Mock record type for an interface
type internal VeryPublicTypeMock<'a, 'b> =
{
Calls : VeryPublicTypeMockCalls.Calls<'a, 'b>
Mem1 : 'a -> 'b
}
/// An implementation where every non-unit method throws.
static member Empty () : VeryPublicTypeMock<'a, 'b> =
{
Calls = VeryPublicTypeMockCalls.Calls.Empty ()
Mem1 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem1"))
}
interface VeryPublicType<'a, 'b> with
member this.Mem1 arg_0_0 =
lock this.Calls.Mem1 (fun _ -> this.Calls.Mem1.Add (arg_0_0))
this.Mem1 (arg_0_0)
namespace SomeNamespace.CapturingMock
open System
open WoofWare.Myriad.Plugins
[<RequireQualifiedAccess>]
module internal CurriedMockCalls =
/// A single call to the Mem1 method
type internal Mem1Call<'a> =
{
bar : int
Arg1 : 'a
}
/// A single call to the Mem2 method
type internal Mem2Call<'a> =
{
Arg0 : int * string
baz : 'a
}
/// A single call to the Mem3 method
type internal Mem3Call<'a> =
{
quux : (int * string)
flurb : 'a
}
/// A single call to the Mem4 method
type internal Mem4Call<'a> =
{
Arg0 : int * string
Arg1 : 'a * int
}
/// A single call to the Mem5 method
type internal Mem5Call<'a> =
{
Arg0 : int * string
Arg1 : 'a * int
}
/// A single call to the Mem6 method
type internal Mem6Call<'a> =
{
Arg0 : int * string
Arg1 : 'a * int
}
/// All the calls made to a CurriedMock mock
type internal Calls<'a> =
{
Mem1 : ResizeArray<Mem1Call<'a>>
Mem2 : ResizeArray<Mem2Call<'a>>
Mem3 : ResizeArray<Mem3Call<'a>>
Mem4 : ResizeArray<Mem4Call<'a>>
Mem5 : ResizeArray<Mem5Call<'a>>
Mem6 : ResizeArray<Mem6Call<'a>>
}
/// A fresh calls object which has not yet had any calls made.
static member Empty () : Calls<'a> =
{
Mem1 = ResizeArray ()
Mem2 = ResizeArray ()
Mem3 = ResizeArray ()
Mem4 = ResizeArray ()
Mem5 = ResizeArray ()
Mem6 = ResizeArray ()
}
/// Mock record type for an interface
type internal CurriedMock<'a> =
{
Calls : CurriedMockCalls.Calls<'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 non-unit method throws.
static member Empty () : CurriedMock<'a> =
{
Calls = CurriedMockCalls.Calls.Empty ()
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 =
lock
this.Calls.Mem1
(fun _ ->
this.Calls.Mem1.Add
{
bar = arg_0_0
Arg1 = arg_1_0
}
)
this.Mem1 (arg_0_0) (arg_1_0)
member this.Mem2 (arg_0_0, arg_0_1) arg_1_0 =
lock
this.Calls.Mem2
(fun _ ->
this.Calls.Mem2.Add
{
Arg0 = arg_0_0, arg_0_1
baz = arg_1_0
}
)
this.Mem2 (arg_0_0, arg_0_1) (arg_1_0)
member this.Mem3 arg_0_0 arg_1_0 =
lock
this.Calls.Mem3
(fun _ ->
this.Calls.Mem3.Add
{
quux = arg_0_0
flurb = arg_1_0
}
)
this.Mem3 (arg_0_0) (arg_1_0)
member this.Mem4 ((arg_0_0, arg_0_1)) ((arg_1_0, arg_1_1)) =
lock
this.Calls.Mem4
(fun _ ->
this.Calls.Mem4.Add
{
Arg0 = arg_0_0, arg_0_1
Arg1 = 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)) =
lock
this.Calls.Mem5
(fun _ ->
this.Calls.Mem5.Add
{
Arg0 = arg_0_0, arg_0_1
Arg1 = 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) =
lock
this.Calls.Mem6
(fun _ ->
this.Calls.Mem6.Add
{
Arg0 = arg_0_0, arg_0_1
Arg1 = arg_1_0, arg_1_1
}
)
this.Mem6 (arg_0_0, arg_0_1) (arg_1_0, arg_1_1)
namespace SomeNamespace.CapturingMock
open System
open WoofWare.Myriad.Plugins
[<RequireQualifiedAccess>]
module internal TypeWithInterfaceMockCalls =
/// All the calls made to a TypeWithInterfaceMock mock
type internal Calls =
{
Mem1 : ResizeArray<string option>
Mem2 : ResizeArray<unit>
}
/// A fresh calls object which has not yet had any calls made.
static member Empty () : Calls =
{
Mem1 = ResizeArray ()
Mem2 = ResizeArray ()
}
/// Mock record type for an interface
type internal TypeWithInterfaceMock =
{
Calls : TypeWithInterfaceMockCalls.Calls
/// Implementation of IDisposable.Dispose
Dispose : unit -> unit
Mem1 : string option -> string[] Async
Mem2 : unit -> string[] Async
}
/// An implementation where every non-unit method throws.
static member Empty () : TypeWithInterfaceMock =
{
Calls = TypeWithInterfaceMockCalls.Calls.Empty ()
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 =
lock this.Calls.Mem1 (fun _ -> this.Calls.Mem1.Add (arg_0_0))
this.Mem1 (arg_0_0)
member this.Mem2 () =
lock this.Calls.Mem2 (fun _ -> this.Calls.Mem2.Add (()))
this.Mem2 (())
interface System.IDisposable with
member this.Dispose () : unit = this.Dispose ()
namespace SomeNamespace.CapturingMock
open System
open WoofWare.Myriad.Plugins
[<RequireQualifiedAccess>]
module internal TypeWithPropertiesMockCalls =
/// All the calls made to a TypeWithPropertiesMock mock
type internal Calls =
{
Mem1 : ResizeArray<string option>
Prop1 : ResizeArray<unit>
Prop2 : ResizeArray<unit>
}
/// A fresh calls object which has not yet had any calls made.
static member Empty () : Calls =
{
Mem1 = ResizeArray ()
Prop1 = ResizeArray ()
Prop2 = ResizeArray ()
}
/// Mock record type for an interface
type internal TypeWithPropertiesMock =
{
Calls : TypeWithPropertiesMockCalls.Calls
/// Implementation of IDisposable.Dispose
Dispose : unit -> unit
Mem1 : string option -> string[] Async
Prop1 : unit -> int
Prop2 : unit -> unit Async
}
/// An implementation where every non-unit method throws.
static member Empty () : TypeWithPropertiesMock =
{
Calls = TypeWithPropertiesMockCalls.Calls.Empty ()
Dispose = (fun () -> ())
Mem1 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem1"))
Prop1 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Prop1"))
Prop2 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Prop2"))
}
interface TypeWithProperties with
member this.Mem1 arg_0_0 =
lock this.Calls.Mem1 (fun _ -> this.Calls.Mem1.Add (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 ()

View File

@@ -0,0 +1,500 @@
//------------------------------------------------------------------------------
// This code was generated by myriad.
// Changes to this file will be lost when the code is regenerated.
//------------------------------------------------------------------------------
namespace SomeNamespace.CapturingMock
open System
[<RequireQualifiedAccess>]
module internal PublicTypeNoAttrMockCalls =
/// All the calls made to a PublicTypeNoAttrMock mock
type internal Calls =
{
Mem1 : ResizeArray<string * int>
Mem2 : ResizeArray<string>
Mem3 : ResizeArray<int * System.Threading.CancellationToken option>
}
/// A fresh calls object which has not yet had any calls made.
static member Empty () : Calls =
{
Mem1 = ResizeArray ()
Mem2 = ResizeArray ()
Mem3 = ResizeArray ()
}
/// Mock record type for an interface
type internal PublicTypeNoAttrMock =
{
Calls : PublicTypeNoAttrMockCalls.Calls
Mem1 : string * int -> string list
Mem2 : string -> int
Mem3 : int * System.Threading.CancellationToken option -> string
}
/// An implementation where every non-unit method throws.
static member Empty () : PublicTypeNoAttrMock =
{
Calls = PublicTypeNoAttrMockCalls.Calls.Empty ()
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) =
lock this.Calls.Mem1 (fun _ -> this.Calls.Mem1.Add (arg_0_0, arg_0_1))
this.Mem1 (arg_0_0, arg_0_1)
member this.Mem2 arg_0_0 =
lock this.Calls.Mem2 (fun _ -> this.Calls.Mem2.Add (arg_0_0))
this.Mem2 (arg_0_0)
member this.Mem3 (arg_0_0, arg_0_1) =
lock this.Calls.Mem3 (fun _ -> this.Calls.Mem3.Add (arg_0_0, arg_0_1))
this.Mem3 (arg_0_0, arg_0_1)
namespace SomeNamespace.CapturingMock
open System
[<RequireQualifiedAccess>]
module public PublicTypeInternalFalseNoAttrMockCalls =
/// All the calls made to a PublicTypeInternalFalseNoAttrMock mock
type public Calls =
{
Mem1 : ResizeArray<string * int>
Mem2 : ResizeArray<string>
Mem3 : ResizeArray<int * System.Threading.CancellationToken option>
}
/// A fresh calls object which has not yet had any calls made.
static member Empty () : Calls =
{
Mem1 = ResizeArray ()
Mem2 = ResizeArray ()
Mem3 = ResizeArray ()
}
/// Mock record type for an interface
type public PublicTypeInternalFalseNoAttrMock =
{
Calls : PublicTypeInternalFalseNoAttrMockCalls.Calls
Mem1 : string * int -> string list
Mem2 : string -> int
Mem3 : int * System.Threading.CancellationToken option -> string
}
/// An implementation where every non-unit method throws.
static member Empty () : PublicTypeInternalFalseNoAttrMock =
{
Calls = PublicTypeInternalFalseNoAttrMockCalls.Calls.Empty ()
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) =
lock this.Calls.Mem1 (fun _ -> this.Calls.Mem1.Add (arg_0_0, arg_0_1))
this.Mem1 (arg_0_0, arg_0_1)
member this.Mem2 arg_0_0 =
lock this.Calls.Mem2 (fun _ -> this.Calls.Mem2.Add (arg_0_0))
this.Mem2 (arg_0_0)
member this.Mem3 (arg_0_0, arg_0_1) =
lock this.Calls.Mem3 (fun _ -> this.Calls.Mem3.Add (arg_0_0, arg_0_1))
this.Mem3 (arg_0_0, arg_0_1)
namespace SomeNamespace.CapturingMock
open System
[<RequireQualifiedAccess>]
module internal InternalTypeNoAttrMockCalls =
/// All the calls made to a InternalTypeNoAttrMock mock
type internal Calls =
{
Mem1 : ResizeArray<string * int>
Mem2 : ResizeArray<string>
}
/// A fresh calls object which has not yet had any calls made.
static member Empty () : Calls =
{
Mem1 = ResizeArray ()
Mem2 = ResizeArray ()
}
/// Mock record type for an interface
type internal InternalTypeNoAttrMock =
{
Calls : InternalTypeNoAttrMockCalls.Calls
Mem1 : string * int -> unit
Mem2 : string -> int
}
/// An implementation where every non-unit method throws.
static member Empty () : InternalTypeNoAttrMock =
{
Calls = InternalTypeNoAttrMockCalls.Calls.Empty ()
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) =
lock this.Calls.Mem1 (fun _ -> this.Calls.Mem1.Add (arg_0_0, arg_0_1))
this.Mem1 (arg_0_0, arg_0_1)
member this.Mem2 arg_0_0 =
lock this.Calls.Mem2 (fun _ -> this.Calls.Mem2.Add (arg_0_0))
this.Mem2 (arg_0_0)
namespace SomeNamespace.CapturingMock
open System
[<RequireQualifiedAccess>]
module internal PrivateTypeNoAttrMockCalls =
/// All the calls made to a PrivateTypeNoAttrMock mock
type internal Calls =
{
Mem1 : ResizeArray<string * int>
Mem2 : ResizeArray<string>
}
/// A fresh calls object which has not yet had any calls made.
static member Empty () : Calls =
{
Mem1 = ResizeArray ()
Mem2 = ResizeArray ()
}
/// Mock record type for an interface
type private PrivateTypeNoAttrMock =
{
Calls : PrivateTypeNoAttrMockCalls.Calls
Mem1 : string * int -> unit
Mem2 : string -> int
}
/// An implementation where every non-unit method throws.
static member Empty () : PrivateTypeNoAttrMock =
{
Calls = PrivateTypeNoAttrMockCalls.Calls.Empty ()
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) =
lock this.Calls.Mem1 (fun _ -> this.Calls.Mem1.Add (arg_0_0, arg_0_1))
this.Mem1 (arg_0_0, arg_0_1)
member this.Mem2 arg_0_0 =
lock this.Calls.Mem2 (fun _ -> this.Calls.Mem2.Add (arg_0_0))
this.Mem2 (arg_0_0)
namespace SomeNamespace.CapturingMock
open System
[<RequireQualifiedAccess>]
module internal PrivateTypeInternalFalseNoAttrMockCalls =
/// All the calls made to a PrivateTypeInternalFalseNoAttrMock mock
type internal Calls =
{
Mem1 : ResizeArray<string * int>
Mem2 : ResizeArray<string>
}
/// A fresh calls object which has not yet had any calls made.
static member Empty () : Calls =
{
Mem1 = ResizeArray ()
Mem2 = ResizeArray ()
}
/// Mock record type for an interface
type private PrivateTypeInternalFalseNoAttrMock =
{
Calls : PrivateTypeInternalFalseNoAttrMockCalls.Calls
Mem1 : string * int -> unit
Mem2 : string -> int
}
/// An implementation where every non-unit method throws.
static member Empty () : PrivateTypeInternalFalseNoAttrMock =
{
Calls = PrivateTypeInternalFalseNoAttrMockCalls.Calls.Empty ()
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) =
lock this.Calls.Mem1 (fun _ -> this.Calls.Mem1.Add (arg_0_0, arg_0_1))
this.Mem1 (arg_0_0, arg_0_1)
member this.Mem2 arg_0_0 =
lock this.Calls.Mem2 (fun _ -> this.Calls.Mem2.Add (arg_0_0))
this.Mem2 (arg_0_0)
namespace SomeNamespace.CapturingMock
open System
[<RequireQualifiedAccess>]
module internal VeryPublicTypeNoAttrMockCalls =
/// All the calls made to a VeryPublicTypeNoAttrMock mock
type internal Calls<'a, 'b> =
{
Mem1 : ResizeArray<'a>
}
/// A fresh calls object which has not yet had any calls made.
static member Empty () : Calls<'a, 'b> =
{
Mem1 = ResizeArray ()
}
/// Mock record type for an interface
type internal VeryPublicTypeNoAttrMock<'a, 'b> =
{
Calls : VeryPublicTypeNoAttrMockCalls.Calls<'a, 'b>
Mem1 : 'a -> 'b
}
/// An implementation where every non-unit method throws.
static member Empty () : VeryPublicTypeNoAttrMock<'a, 'b> =
{
Calls = VeryPublicTypeNoAttrMockCalls.Calls.Empty ()
Mem1 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem1"))
}
interface VeryPublicTypeNoAttr<'a, 'b> with
member this.Mem1 arg_0_0 =
lock this.Calls.Mem1 (fun _ -> this.Calls.Mem1.Add (arg_0_0))
this.Mem1 (arg_0_0)
namespace SomeNamespace.CapturingMock
open System
[<RequireQualifiedAccess>]
module internal CurriedNoAttrMockCalls =
/// A single call to the Mem1 method
type internal Mem1Call<'a> =
{
Arg0 : int
Arg1 : 'a
}
/// A single call to the Mem2 method
type internal Mem2Call<'a> =
{
Arg0 : int * string
Arg1 : 'a
}
/// A single call to the Mem3 method
type internal Mem3Call<'a> =
{
Arg0 : int * string
Arg1 : 'a
}
/// A single call to the Mem4 method
type internal Mem4Call<'a> =
{
Arg0 : int * string
Arg1 : 'a * int
}
/// A single call to the Mem5 method
type internal Mem5Call<'a> =
{
Arg0 : int * string
Arg1 : 'a * int
}
/// A single call to the Mem6 method
type internal Mem6Call<'a> =
{
Arg0 : int * string
Arg1 : 'a * int
}
/// All the calls made to a CurriedNoAttrMock mock
type internal Calls<'a> =
{
Mem1 : ResizeArray<Mem1Call<'a>>
Mem2 : ResizeArray<Mem2Call<'a>>
Mem3 : ResizeArray<Mem3Call<'a>>
Mem4 : ResizeArray<Mem4Call<'a>>
Mem5 : ResizeArray<Mem5Call<'a>>
Mem6 : ResizeArray<Mem6Call<'a>>
}
/// A fresh calls object which has not yet had any calls made.
static member Empty () : Calls<'a> =
{
Mem1 = ResizeArray ()
Mem2 = ResizeArray ()
Mem3 = ResizeArray ()
Mem4 = ResizeArray ()
Mem5 = ResizeArray ()
Mem6 = ResizeArray ()
}
/// Mock record type for an interface
type internal CurriedNoAttrMock<'a> =
{
Calls : CurriedNoAttrMockCalls.Calls<'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 non-unit method throws.
static member Empty () : CurriedNoAttrMock<'a> =
{
Calls = CurriedNoAttrMockCalls.Calls.Empty ()
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 =
lock
this.Calls.Mem1
(fun _ ->
this.Calls.Mem1.Add
{
Arg0 = arg_0_0
Arg1 = arg_1_0
}
)
this.Mem1 (arg_0_0) (arg_1_0)
member this.Mem2 (arg_0_0, arg_0_1) arg_1_0 =
lock
this.Calls.Mem2
(fun _ ->
this.Calls.Mem2.Add
{
Arg0 = arg_0_0, arg_0_1
Arg1 = 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 =
lock
this.Calls.Mem3
(fun _ ->
this.Calls.Mem3.Add
{
Arg0 = arg_0_0, arg_0_1
Arg1 = 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)) =
lock
this.Calls.Mem4
(fun _ ->
this.Calls.Mem4.Add
{
Arg0 = arg_0_0, arg_0_1
Arg1 = 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)) =
lock
this.Calls.Mem5
(fun _ ->
this.Calls.Mem5.Add
{
Arg0 = arg_0_0, arg_0_1
Arg1 = 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) =
lock
this.Calls.Mem6
(fun _ ->
this.Calls.Mem6.Add
{
Arg0 = arg_0_0, arg_0_1
Arg1 = arg_1_0, arg_1_1
}
)
this.Mem6 (arg_0_0, arg_0_1) (arg_1_0, arg_1_1)
namespace SomeNamespace.CapturingMock
open System
[<RequireQualifiedAccess>]
module internal TypeWithInterfaceNoAttrMockCalls =
/// All the calls made to a TypeWithInterfaceNoAttrMock mock
type internal Calls =
{
Mem1 : ResizeArray<string option>
Mem2 : ResizeArray<unit>
}
/// A fresh calls object which has not yet had any calls made.
static member Empty () : Calls =
{
Mem1 = ResizeArray ()
Mem2 = ResizeArray ()
}
/// Mock record type for an interface
type internal TypeWithInterfaceNoAttrMock =
{
Calls : TypeWithInterfaceNoAttrMockCalls.Calls
/// Implementation of IDisposable.Dispose
Dispose : unit -> unit
Mem1 : string option -> string[] Async
Mem2 : unit -> string[] Async
}
/// An implementation where every non-unit method throws.
static member Empty () : TypeWithInterfaceNoAttrMock =
{
Calls = TypeWithInterfaceNoAttrMockCalls.Calls.Empty ()
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 =
lock this.Calls.Mem1 (fun _ -> this.Calls.Mem1.Add (arg_0_0))
this.Mem1 (arg_0_0)
member this.Mem2 () =
lock this.Calls.Mem2 (fun _ -> this.Calls.Mem2.Add (()))
this.Mem2 (())
interface System.IDisposable with
member this.Dispose () : unit = this.Dispose ()

View File

@@ -7,6 +7,7 @@
namespace ConsumePlugin
open WoofWare.Myriad.Plugins

View File

@@ -7,6 +7,7 @@
namespace ConsumePlugin
open WoofWare.Myriad.Plugins

View File

@@ -4,6 +4,7 @@
//------------------------------------------------------------------------------
namespace ConsumePlugin
open System.Text.Json.Serialization

View File

@@ -4,6 +4,7 @@
//------------------------------------------------------------------------------
namespace PureGym
open System

View File

@@ -6,6 +6,7 @@
namespace PureGym
open System
@@ -375,7 +376,9 @@ module PureGymApi =
match node with
| None -> "null"
| Some node -> node.ToJsonString ()
)
),
null,
"application/json"
)
do httpMessage.Content <- queryParams
@@ -667,7 +670,9 @@ module PureGymApi =
let queryParams =
new System.Net.Http.StringContent (
user |> PureGym.Member.toJsonNode |> (fun node -> node.ToJsonString ())
user |> PureGym.Member.toJsonNode |> (fun node -> node.ToJsonString ()),
null,
"application/json"
)
do httpMessage.Content <- queryParams
@@ -710,7 +715,9 @@ module PureGymApi =
)
| field -> field)
)
|> (fun node -> node.ToJsonString ())
|> (fun node -> node.ToJsonString ()),
null,
"application/json"
)
do httpMessage.Content <- queryParams
@@ -753,7 +760,9 @@ module PureGymApi =
)
| field -> field)
)
|> (fun node -> node.ToJsonString ())
|> (fun node -> node.ToJsonString ()),
null,
"application/json"
)
do httpMessage.Content <- queryParams
@@ -1801,3 +1810,174 @@ module ApiWithHeaders2 =
}
|> (fun a -> Async.StartAsTask (a, ?cancellationToken = ct))
}
namespace PureGym
open System
open System.Threading
open System.Threading.Tasks
open System.IO
open System.Net
open System.Net.Http
open RestEase
/// Module for constructing a REST client.
[<CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix) ; RequireQualifiedAccess>]
module ClientWithJsonBody =
/// Create a REST client.
let make (client : System.Net.Http.HttpClient) : IClientWithJsonBody =
{ new IClientWithJsonBody with
member _.GetPathParam (parameter : string, mem : PureGym.Member, ct : CancellationToken option) =
async {
let! ct = Async.CancellationToken
let uri =
System.Uri (
(match client.BaseAddress with
| null ->
raise (
System.ArgumentNullException (
nameof (client.BaseAddress),
"No base address was supplied on the type, and no BaseAddress was on the HttpClient."
)
)
| v -> v),
System.Uri (
"endpoint/{param}"
.Replace ("{param}", parameter.ToString () |> System.Uri.EscapeDataString),
System.UriKind.Relative
)
)
let httpMessage =
new System.Net.Http.HttpRequestMessage (
Method = System.Net.Http.HttpMethod.Post,
RequestUri = uri
)
let queryParams =
new System.Net.Http.StringContent (
mem |> PureGym.Member.toJsonNode |> (fun node -> node.ToJsonString ()),
null,
"application/json"
)
do httpMessage.Content <- queryParams
let! response = client.SendAsync (httpMessage, ct) |> Async.AwaitTask
let response = response.EnsureSuccessStatusCode ()
let! responseString = response.Content.ReadAsStringAsync ct |> Async.AwaitTask
return responseString
}
|> (fun a -> Async.StartAsTask (a, ?cancellationToken = ct))
}
namespace PureGym
open System
open System.Threading
open System.Threading.Tasks
open System.IO
open System.Net
open System.Net.Http
open RestEase
/// Module for constructing a REST client.
[<CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix) ; RequireQualifiedAccess>]
module ClientWithJsonBodyOverridden =
/// Create a REST client.
let make (client : System.Net.Http.HttpClient) : IClientWithJsonBodyOverridden =
{ new IClientWithJsonBodyOverridden with
member _.GetPathParam (parameter : string, mem : PureGym.Member, ct : CancellationToken option) =
async {
let! ct = Async.CancellationToken
let uri =
System.Uri (
(match client.BaseAddress with
| null ->
raise (
System.ArgumentNullException (
nameof (client.BaseAddress),
"No base address was supplied on the type, and no BaseAddress was on the HttpClient."
)
)
| v -> v),
System.Uri (
"endpoint/{param}"
.Replace ("{param}", parameter.ToString () |> System.Uri.EscapeDataString),
System.UriKind.Relative
)
)
let httpMessage =
new System.Net.Http.HttpRequestMessage (
Method = System.Net.Http.HttpMethod.Post,
RequestUri = uri
)
let queryParams =
new System.Net.Http.StringContent (
mem |> PureGym.Member.toJsonNode |> (fun node -> node.ToJsonString ()),
null,
"application/ecmascript"
)
do httpMessage.Content <- queryParams
let! response = client.SendAsync (httpMessage, ct) |> Async.AwaitTask
let response = response.EnsureSuccessStatusCode ()
let! responseString = response.Content.ReadAsStringAsync ct |> Async.AwaitTask
return responseString
}
|> (fun a -> Async.StartAsTask (a, ?cancellationToken = ct))
}
namespace PureGym
open System
open System.Threading
open System.Threading.Tasks
open System.IO
open System.Net
open System.Net.Http
open RestEase
/// Module for constructing a REST client.
[<CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix) ; RequireQualifiedAccess>]
module ClientWithStringBody =
/// Create a REST client.
let make (client : System.Net.Http.HttpClient) : IClientWithStringBody =
{ new IClientWithStringBody with
member _.GetPathParam (parameter : string, mem : string, ct : CancellationToken option) =
async {
let! ct = Async.CancellationToken
let uri =
System.Uri (
(match client.BaseAddress with
| null ->
raise (
System.ArgumentNullException (
nameof (client.BaseAddress),
"No base address was supplied on the type, and no BaseAddress was on the HttpClient."
)
)
| v -> v),
System.Uri (
"endpoint/{param}"
.Replace ("{param}", parameter.ToString () |> System.Uri.EscapeDataString),
System.UriKind.Relative
)
)
let httpMessage =
new System.Net.Http.HttpRequestMessage (
Method = System.Net.Http.HttpMethod.Post,
RequestUri = uri
)
let queryParams = new System.Net.Http.StringContent (mem)
do httpMessage.Content <- queryParams
let! response = client.SendAsync (httpMessage, ct) |> Async.AwaitTask
let response = response.EnsureSuccessStatusCode ()
let! responseString = response.Content.ReadAsStringAsync ct |> Async.AwaitTask
return responseString
}
|> (fun a -> Async.StartAsTask (a, ?cancellationToken = ct))
}

View File

@@ -4,6 +4,7 @@
//------------------------------------------------------------------------------
namespace ConsumePlugin
open System

View File

@@ -9,6 +9,7 @@
namespace Gitea
open WoofWare.Myriad.Plugins

View File

@@ -5,6 +5,7 @@
namespace ConsumePlugin
/// Module containing JSON parsing methods for the JwtVaultAuthResponse type

View File

@@ -7,6 +7,7 @@
namespace ConsumePlugin
open WoofWare.Myriad.Plugins

View File

@@ -203,3 +203,35 @@ type IApiWithHeaders2 =
[<Get "endpoint/{param}">]
abstract GetPathParam :
[<WoofWare.Myriad.Plugins.RestEase.Path "param">] parameter : string * ?ct : CancellationToken -> Task<string>
[<WoofWare.Myriad.Plugins.HttpClient>]
type IClientWithJsonBody =
// As a POST request of a JSON-serialised body, we automatically set Content-Type: application/json.
[<Post "endpoint/{param}">]
abstract GetPathParam :
[<RestEase.Path "param">] parameter : string *
[<WoofWare.Myriad.Plugins.RestEase.Body>] mem : PureGym.Member *
?ct : CancellationToken ->
Task<string>
[<WoofWare.Myriad.Plugins.HttpClient>]
type IClientWithJsonBodyOverridden =
// As a POST request of a JSON-serialised body, we *would* automatically set Content-Type: application/json,
// but this method has overridden it.
[<Post "endpoint/{param}">]
[<Header("Content-Type", "application/ecmascript")>]
abstract GetPathParam :
[<RestEase.Path "param">] parameter : string *
[<WoofWare.Myriad.Plugins.RestEase.Body>] mem : PureGym.Member *
?ct : CancellationToken ->
Task<string>
[<WoofWare.Myriad.Plugins.HttpClient>]
type IClientWithStringBody =
// As a POST request of a bare string body, we don't override the Content-Type.
[<Post "endpoint/{param}">]
abstract GetPathParam :
[<RestEase.Path "param">] parameter : string *
[<WoofWare.Myriad.Plugins.RestEase.Body>] mem : string *
?ct : CancellationToken ->
Task<string>

View File

@@ -10,8 +10,8 @@
<WarnOn>FS3388,FS3559</WarnOn>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Nerdbank.GitVersioning" Version="3.8.38-alpha" PrivateAssets="all"/>
<SourceLinkGitHubHost Include="github.com" ContentUrl="https://raw.githubusercontent.com"/>
<PackageReference Include="Nerdbank.GitVersioning" Version="3.8.118" PrivateAssets="all" />
<SourceLinkGitHubHost Include="github.com" ContentUrl="https://raw.githubusercontent.com" />
</ItemGroup>
<PropertyGroup Condition="'$(GITHUB_ACTION)' != ''">
<ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>

View File

@@ -13,7 +13,7 @@ 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)).
* `GenerateMock` and `GenerateCapturingMock` (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).
@@ -440,9 +440,9 @@ There are also some design decisions:
so arguments are forced to be tupled.
* The `[<Optional>]` attribute is not supported and will probably not be supported, because I consider it to be cursed.
## `GenerateMock`
## `GenerateMock` and `GenerateCapturingMock`
Takes a type like this:
`GenerateMock` takes a type like this:
```fsharp
[<GenerateMock>]
@@ -472,6 +472,59 @@ type internal PublicTypeMock =
member this.Mem2 (arg0) = this.Mem2 (arg0)
```
`GenerateCapturingMock` additionally captures the calls made to each function (except for `Dispose`).
It takes a type like this:
```fsharp
[<GenerateCapturingMock>]
type IPublicType =
abstract Mem1 : string * int -> thing : bool -> string list
abstract Mem2 : baz : string -> unit -> int
```
and stamps out types like this:
```fsharp
[<RequireQualifiedAccess>]
module internal PublicTypeCalls =
type internal Mem1Call =
{
Arg0 : string * int
thing : bool
}
type internal Calls =
{
Mem1 : ResizeArray<Mem1Call>
Mem2 : ResizeArray<string>
}
static member Empty () = { Mem1 = ResizeArray () ; Mem2 = ResizeArray () }
/// Mock record type for an interface
type internal PublicTypeMock =
{
Mem1 : string * int -> bool -> string list
Mem2 : string -> int
Calls : PublicTypeCalls.Calls
}
static member Empty : PublicTypeMock =
{
Mem1 = (fun x -> raise (System.NotImplementedException "Unimplemented mock function"))
Mem2 = (fun x -> raise (System.NotImplementedException "Unimplemented mock function"))
Calls = PublicTypeMockCalls.Calls.Empty ()
}
interface IPublicType with
member this.Mem1 (arg0, arg1) =
lock this.Calls.Mem1 (fun () -> this.Calls.Mem1.Add { Arg0 = arg0 ; thing = arg1 })
this.Mem1 (arg0, arg1)
member this.Mem2 (arg0) =
lock this.Calls.Mem2 (fun () -> this.Calls.Mem2.Add arg0)
this.Mem2 (arg0)
```
### What's the point?
Reflective mocking libraries like [Foq](https://github.com/fsprojects/Foq) in my experience are a rich source of flaky tests.
@@ -483,6 +536,36 @@ thereby allowing the programmer to use F#'s record-update syntax.
* You may supply an `isInternal : bool` argument to the attribute. By default, we make the resulting record type at most internal (never public), since this is intended only to be used in tests; but you can instead make it public with `[<GenerateMock false>]`.
### Gotchas (GenerateCapturingMock)
We use the same name for the record field as the implementing interface member:
```fsharp
type FooMock =
{
Field : string -> unit
}
interface IFoo with
member _.Field x = ...
```
If you have an object of type `FooMock` in scope, you'll get the *record field*, not the *interface member*.
You need to cast it to `IFoo` before using it (or pass it into a function which takes an `IFoo`):
```fsharp
let thing = FooMock.Empty () // of type FooMock
thing.Field "hello" // the wrong one! bypasses the IFoo implementation which captures calls
// correct:
let thing' = FooMock.Empty ()
let thing = thing' :> IFoo
thing.Field "hello" // the right one: this call does get recorded in the mock, because this is the interface member
// also correct, but beware because it leaves the chance of the above footgun lying around for later:
let thing = FooMock.Empty () // of type FooMock
doTheThing thing // where doTheThing : IFoo -> unit
```
## `CreateCatamorphism`
Takes a collection of mutually recursive discriminated unions:
@@ -569,13 +652,13 @@ For example, [PureGymDto.fs](./ConsumePlugin/PureGymDto.fs) is a real-world set
* In your `.fsproj` file, define a helper variable so that subsequent steps don't all have to be kept in sync:
```xml
<PropertyGroup>
<WoofWareMyriadPluginVersion>2.0.1</WoofWareMyriadPluginVersion>
<WoofWareMyriadPluginVersion>9.0.1</WoofWareMyriadPluginVersion>
</PropertyGroup>
```
* Take a reference on `WoofWare.Myriad.Plugins.Attributes` (which has no other dependencies), to obtain access to the attributes which the generator will recognise:
```xml
<ItemGroup>
<PackageReference Include="WoofWare.Myriad.Plugins.Attributes" Version="2.0.2" />
<PackageReference Include="WoofWare.Myriad.Plugins.Attributes" Version="3.7.2" />
</ItemGroup>
```
* Take a reference (with private assets, to prevent these from propagating to your own assembly) on `WoofWare.Myriad.Plugins`, to obtain the plugins which Myriad will run, and on `Myriad.Sdk`, to obtain the Myriad binary itself:
@@ -647,3 +730,7 @@ I'm hopefully going to get round to writing a more powerful source generation sy
You should probably add these files to your [fantomasignore](https://github.com/fsprojects/fantomas/blob/a999b77ca5a024fbc3409955faac797e29b39d27/docs/docs/end-users/IgnoreFiles.md)
if you use Fantomas to format your repo;
the alternative is to manually reformat every time Myriad changes the generated files.
# Licence
The code is MIT-licenced, except for the Swagger API examples in WoofWare.Myriad.Plugins.Test, which are [CC-BY 4.0](https://creativecommons.org/licenses/by/4.0/), copyright 2023 by the OpenAPI Initiative, and obtained from https://learn.openapis.org/examples/ with no changes made.

View File

@@ -14,6 +14,9 @@ type RemoveOptionsAttribute () =
/// but where each method is represented as a record field, so you can use
/// record update syntax to easily specify partially-implemented mock objects.
/// You may optionally specify `isInternal = false` to get a mock with the public visibility modifier.
///
/// The default implementation of each field throws (except for default implementations of IDisposable, which are
/// no-ops).
type GenerateMockAttribute (isInternal : bool) =
inherit Attribute ()
/// The default value of `isInternal`, the optional argument to the GenerateMockAttribute constructor.
@@ -22,6 +25,26 @@ type GenerateMockAttribute (isInternal : bool) =
/// Shorthand for the "isExtensionMethod = false" constructor; see documentation there for details.
new () = GenerateMockAttribute GenerateMockAttribute.DefaultIsInternal
/// Attribute indicating an interface type for which the "Generate Capturing Mock" Myriad
/// generator should apply during build.
/// This generator creates a record which implements the interface,
/// but where each method is represented as a record field, so you can use
/// record update syntax to easily specify partially-implemented mock objects.
/// You may optionally specify `isInternal = false` to get a mock with the public visibility modifier.
///
/// The default implementation of each field throws.
///
/// The generated interface methods capture all calls made to them, before passing through to the relevant
/// field of the mock record; the calls can be accessed later through the `Calls` field of the generated
/// mock record.
type GenerateCapturingMockAttribute (isInternal : bool) =
inherit Attribute ()
/// The default value of `isInternal`, the optional argument to the GenerateCapturingMockAttribute constructor.
static member DefaultIsInternal = true
/// Shorthand for the "isExtensionMethod = false" constructor; see documentation there for details.
new () = GenerateCapturingMockAttribute GenerateCapturingMockAttribute.DefaultIsInternal
/// Attribute indicating a record type to which the "Add JSON serializer" Myriad
/// generator should apply during build.
/// The purpose of this generator is to create methods (possibly extension methods) of the form

View File

@@ -15,6 +15,11 @@ 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.GenerateCapturingMockAttribute inherit System.Attribute
WoofWare.Myriad.Plugins.GenerateCapturingMockAttribute..ctor [constructor]: bool
WoofWare.Myriad.Plugins.GenerateCapturingMockAttribute..ctor [constructor]: unit
WoofWare.Myriad.Plugins.GenerateCapturingMockAttribute.DefaultIsInternal [static property]: [read-only] bool
WoofWare.Myriad.Plugins.GenerateCapturingMockAttribute.get_DefaultIsInternal [static method]: unit -> bool
WoofWare.Myriad.Plugins.GenerateMockAttribute inherit System.Attribute
WoofWare.Myriad.Plugins.GenerateMockAttribute..ctor [constructor]: bool
WoofWare.Myriad.Plugins.GenerateMockAttribute..ctor [constructor]: unit

View File

@@ -17,7 +17,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="ApiSurface" Version="4.1.21" />
<PackageReference Include="ApiSurface" Version="5.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"/>

View File

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

View File

@@ -0,0 +1,22 @@
namespace WoofWare.Myriad.Plugins.Test
open System
open System.IO
open System.Reflection
[<RequireQualifiedAccess>]
module Assembly =
let getEmbeddedResource (assembly : Assembly) (name : string) : string =
let names = assembly.GetManifestResourceNames ()
let names =
names |> Seq.filter (fun s -> s.EndsWith (name, StringComparison.Ordinal))
use s =
names
|> Seq.exactlyOne
|> assembly.GetManifestResourceStream
|> fun s -> new StreamReader (s)
s.ReadToEnd ()

View File

@@ -0,0 +1,72 @@
namespace WoofWare.Myriad.Plugins.Test
open System
open SomeNamespace.CapturingMock
open NUnit.Framework
open FsUnitTyped
[<TestFixture>]
module TestCapturingMockGenerator =
[<Test>]
let ``Example of use: IPublicType`` () =
let mock : IPublicType =
{ PublicTypeMock.Empty () with
Mem1 = fun (s, count) -> List.replicate count s
}
:> _
let _ =
Assert.Throws<NotImplementedException> (fun () -> mock.Mem2 "hi" |> ignore<int>)
mock.Mem1 ("hi", 3) |> shouldEqual [ "hi" ; "hi" ; "hi" ]
[<Test>]
let ``Example of use: curried args`` () =
let mock : Curried<_> =
{ CurriedMock.Empty () with
Mem1 = fun i c -> Array.replicate i c |> String
Mem2 = fun (i, s) c -> String.concat $"%c{c}" (List.replicate i s)
Mem3 = fun (i, s) c -> String.concat $"%c{c}" (List.replicate i s)
}
:> _
mock.Mem1 3 'a' |> shouldEqual "aaa"
mock.Mem2 (3, "hi") 'a' |> shouldEqual "hiahiahi"
mock.Mem3 (3, "hi") 'a' |> shouldEqual "hiahiahi"
[<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
[<Test>]
let ``Example of curried use`` () =
let mock' =
{ CurriedMock<string>.Empty () with
Mem1 =
fun x y ->
x |> shouldEqual 3
y |> shouldEqual "hello"
"it's me"
}
let mock = mock' :> Curried<_>
mock.Mem1 3 "hello" |> shouldEqual "it's me"
lock mock'.Calls.Mem1 (fun () -> Seq.toList mock'.Calls.Mem1)
|> List.exactlyOne
|> shouldEqual
{
bar = 3
Arg1 = "hello"
}

View File

@@ -0,0 +1,36 @@
namespace WoofWare.Myriad.Plugins.Test
open System
open SomeNamespace.CapturingMock
open NUnit.Framework
open FsUnitTyped
[<TestFixture>]
module TestCapturingMockGeneratorNoAttr =
[<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"

View File

@@ -3,10 +3,12 @@ namespace WoofWare.Myriad.Plugins.Test
open System
open System.Net
open System.Net.Http
open System.Text.Json.Nodes
open System.Threading
open NUnit.Framework
open FsUnitTyped
open PureGym
open WoofWare.Expect
[<TestFixture>]
module TestVariableHeader =
@@ -50,15 +52,17 @@ module TestVariableHeader =
someHeaderCount.Value |> shouldEqual 10
someOtherHeaderCount.Value |> shouldEqual -100
api.GetPathParam("param").Result.Split "\n"
|> Array.sort
|> shouldEqual
[|
"Authorization: -99"
"Header-Name: Header-Value"
"Something-Else: val"
"X-Foo: 11"
|]
expect {
snapshotJson
@"[
""Authorization: -99"",
""Header-Name: Header-Value"",
""Something-Else: val"",
""X-Foo: 11""
]"
return api.GetPathParam("param").Result.Split "\n" |> Array.sort
}
someHeaderCount.Value |> shouldEqual 11
someOtherHeaderCount.Value |> shouldEqual -99
@@ -102,25 +106,156 @@ module TestVariableHeader =
someHeaderCount.Value |> shouldEqual 10
someOtherHeaderCount.Value |> shouldEqual -100
api.GetPathParam("param").Result.Split "\n"
|> Array.sort
|> shouldEqual
[|
"Authorization: -99"
"Header-Name: Header-Value"
"Something-Else: val"
"X-Foo: 11"
|]
expect {
snapshotJson
@"[
""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"
"Something-Else: val"
"X-Foo: 12"
|]
return api.GetPathParam("param").Result.Split "\n" |> Array.sort
}
expect {
snapshotJson
@"[
""Authorization: -98"",
""Header-Name: Header-Value"",
""Something-Else: val"",
""X-Foo: 12""
]"
return api.GetPathParam("param").Result.Split "\n" |> Array.sort
}
someHeaderCount.Value |> shouldEqual 12
someOtherHeaderCount.Value |> shouldEqual -98
let pureGymMember =
{
Id = 3
CompoundMemberId = "compound"
FirstName = "Patrick"
LastName = "Stevens"
HomeGymId = 1223
HomeGymName = "Arnie's Temple o' Gainz"
EmailAddress = "patrick@home"
GymAccessPin = "1234"
DateOfBirth = DateOnly (1992, 03, 04)
MobileNumber = "number"
Postcode = "postcode"
MembershipName = "member"
MembershipLevel = 9999
SuspendedReason = -1
MemberStatus = 100
}
[<Test>]
let ``Content-Type header is automatically inserted`` () =
let proc (message : HttpRequestMessage) : HttpResponseMessage Async =
async {
message.Method |> shouldEqual HttpMethod.Post
message.RequestUri.ToString ()
|> shouldEqual "https://example.com/endpoint/param"
let headers =
[
for h in message.Content.Headers do
yield $"%s{h.Key}: %s{Seq.exactlyOne h.Value}"
]
|> String.concat "\n"
let! ct = Async.CancellationToken
let! content = message.Content.ReadAsStringAsync ct |> Async.AwaitTask
content |> JsonNode.Parse |> Member.jsonParse |> shouldEqual pureGymMember
let content = new StringContent (headers)
let resp = new HttpResponseMessage (HttpStatusCode.OK)
resp.Content <- content
return resp
}
use client = HttpClientMock.make (Uri "https://example.com") proc
let api = ClientWithJsonBody.make client
let result = api.GetPathParam ("param", pureGymMember) |> _.Result
expect {
snapshot @"Content-Type: application/json; charset=utf-8"
return result
}
[<Test>]
let ``Content-Type header is respected if overridden`` () =
let proc (message : HttpRequestMessage) : HttpResponseMessage Async =
async {
message.Method |> shouldEqual HttpMethod.Post
message.RequestUri.ToString ()
|> shouldEqual "https://example.com/endpoint/param"
let headers =
[
for h in message.Content.Headers do
yield $"%s{h.Key}: %s{Seq.exactlyOne h.Value}"
]
|> String.concat "\n"
let! ct = Async.CancellationToken
let! content = message.Content.ReadAsStringAsync ct |> Async.AwaitTask
content |> JsonNode.Parse |> Member.jsonParse |> shouldEqual pureGymMember
let content = new StringContent (headers)
let resp = new HttpResponseMessage (HttpStatusCode.OK)
resp.Content <- content
return resp
}
use client = HttpClientMock.make (Uri "https://example.com") proc
let api = ClientWithJsonBodyOverridden.make client
let result = api.GetPathParam ("param", pureGymMember) |> _.Result
expect {
snapshot @"Content-Type: application/ecmascript; charset=utf-8"
return result
}
[<Test>]
let ``Content-Type header is the default for strings`` () =
let proc (message : HttpRequestMessage) : HttpResponseMessage Async =
async {
message.Method |> shouldEqual HttpMethod.Post
message.RequestUri.ToString ()
|> shouldEqual "https://example.com/endpoint/param"
let headers =
[
for h in message.Content.Headers do
yield $"%s{h.Key}: %s{Seq.exactlyOne h.Value}"
]
|> String.concat "\n"
let! ct = Async.CancellationToken
let! content = message.Content.ReadAsStringAsync ct |> Async.AwaitTask
content |> shouldEqual "hello!"
let content = new StringContent (headers)
let resp = new HttpResponseMessage (HttpStatusCode.OK)
resp.Content <- content
return resp
}
use client = HttpClientMock.make (Uri "https://example.com") proc
let api = ClientWithStringBody.make client
let result = api.GetPathParam ("param", "hello!") |> _.Result
expect {
snapshot @"Content-Type: text/plain; charset=utf-8"
return result
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,192 @@
{
"openapi": "3.0.0",
"info": {
"title": "Simple API overview",
"version": "2.0.0"
},
"paths": {
"/": {
"get": {
"operationId": "listVersionsv2",
"summary": "List API versions",
"responses": {
"200": {
"description": "200 response",
"content": {
"application/json": {
"examples": {
"foo": {
"value": {
"versions": [
{
"status": "CURRENT",
"updated": "2011-01-21T11:33:21Z",
"id": "v2.0",
"links": [
{
"href": "http://127.0.0.1:8774/v2/",
"rel": "self"
}
]
},
{
"status": "EXPERIMENTAL",
"updated": "2013-07-23T11:33:21Z",
"id": "v3.0",
"links": [
{
"href": "http://127.0.0.1:8774/v3/",
"rel": "self"
}
]
}
]
}
}
}
}
}
},
"300": {
"description": "300 response",
"content": {
"application/json": {
"examples": {
"foo": {
"value": {
"versions": [
{
"status": "CURRENT",
"updated": "2011-01-21T11:33:21Z",
"id": "v2.0",
"links": [
{
"href": "http://127.0.0.1:8774/v2/",
"rel": "self"
}
]
},
{
"status": "EXPERIMENTAL",
"updated": "2013-07-23T11:33:21Z",
"id": "v3.0",
"links": [
{
"href": "http://127.0.0.1:8774/v3/",
"rel": "self"
}
]
}
]
}
}
}
}
}
}
}
}
},
"/v2": {
"get": {
"operationId": "getVersionDetailsv2",
"summary": "Show API version details",
"responses": {
"200": {
"description": "200 response",
"content": {
"application/json": {
"examples": {
"foo": {
"value": {
"version": {
"status": "CURRENT",
"updated": "2011-01-21T11:33:21Z",
"media-types": [
{
"base": "application/xml",
"type": "application/vnd.openstack.compute+xml;version=2"
},
{
"base": "application/json",
"type": "application/vnd.openstack.compute+json;version=2"
}
],
"id": "v2.0",
"links": [
{
"href": "http://127.0.0.1:8774/v2/",
"rel": "self"
},
{
"href": "http://docs.openstack.org/api/openstack-compute/2/os-compute-devguide-2.pdf",
"type": "application/pdf",
"rel": "describedby"
},
{
"href": "http://docs.openstack.org/api/openstack-compute/2/wadl/os-compute-2.wadl",
"type": "application/vnd.sun.wadl+xml",
"rel": "describedby"
},
{
"href": "http://docs.openstack.org/api/openstack-compute/2/wadl/os-compute-2.wadl",
"type": "application/vnd.sun.wadl+xml",
"rel": "describedby"
}
]
}
}
}
}
}
}
},
"203": {
"description": "203 response",
"content": {
"application/json": {
"examples": {
"foo": {
"value": {
"version": {
"status": "CURRENT",
"updated": "2011-01-21T11:33:21Z",
"media-types": [
{
"base": "application/xml",
"type": "application/vnd.openstack.compute+xml;version=2"
},
{
"base": "application/json",
"type": "application/vnd.openstack.compute+json;version=2"
}
],
"id": "v2.0",
"links": [
{
"href": "http://23.253.228.211:8774/v2/",
"rel": "self"
},
{
"href": "http://docs.openstack.org/api/openstack-compute/2/os-compute-devguide-2.pdf",
"type": "application/pdf",
"rel": "describedby"
},
{
"href": "http://docs.openstack.org/api/openstack-compute/2/wadl/os-compute-2.wadl",
"type": "application/vnd.sun.wadl+xml",
"rel": "describedby"
}
]
}
}
}
}
}
}
}
}
}
}
}
}

View File

@@ -0,0 +1,82 @@
{
"openapi": "3.0.0",
"info": {
"title": "Callback Example",
"version": "1.0.0"
},
"paths": {
"/streams": {
"post": {
"description": "subscribes a client to receive out-of-band data",
"parameters": [
{
"name": "callbackUrl",
"in": "query",
"required": true,
"description": "the location where data will be sent. Must be network accessible\nby the source server\n",
"schema": {
"type": "string",
"format": "uri",
"example": "https://tonys-server.com"
}
}
],
"responses": {
"201": {
"description": "subscription successfully created",
"content": {
"application/json": {
"schema": {
"description": "subscription information",
"required": ["subscriptionId"],
"properties": {
"subscriptionId": {
"description": "this unique identifier allows management of the subscription",
"type": "string",
"example": "2531329f-fb09-4ef7-887e-84e648214436"
}
}
}
}
}
}
},
"callbacks": {
"onData": {
"{$request.query.callbackUrl}/data": {
"post": {
"requestBody": {
"description": "subscription payload",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"timestamp": {
"type": "string",
"format": "date-time"
},
"userData": {
"type": "string"
}
}
}
}
}
},
"responses": {
"202": {
"description": "Your server implementation should return this HTTP status code\nif the data was received successfully\n"
},
"204": {
"description": "Your server should return this HTTP status code if no longer interested\nin further updates\n"
}
}
}
}
}
}
}
}
}
}

View File

@@ -0,0 +1,319 @@
{
"openapi": "3.0.0",
"info": {
"title": "Link Example",
"version": "1.0.0"
},
"paths": {
"/2.0/users/{username}": {
"get": {
"operationId": "getUserByName",
"parameters": [
{
"name": "username",
"in": "path",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "The User",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/user"
}
}
},
"links": {
"userRepositories": {
"$ref": "#/components/links/UserRepositories"
}
}
}
}
}
},
"/2.0/repositories/{username}": {
"get": {
"operationId": "getRepositoriesByOwner",
"parameters": [
{
"name": "username",
"in": "path",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "repositories owned by the supplied user",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/repository"
}
}
}
},
"links": {
"userRepository": {
"$ref": "#/components/links/UserRepository"
}
}
}
}
}
},
"/2.0/repositories/{username}/{slug}": {
"get": {
"operationId": "getRepository",
"parameters": [
{
"name": "username",
"in": "path",
"required": true,
"schema": {
"type": "string"
}
},
{
"name": "slug",
"in": "path",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "The repository",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/repository"
}
}
},
"links": {
"repositoryPullRequests": {
"$ref": "#/components/links/RepositoryPullRequests"
}
}
}
}
}
},
"/2.0/repositories/{username}/{slug}/pullrequests": {
"get": {
"operationId": "getPullRequestsByRepository",
"parameters": [
{
"name": "username",
"in": "path",
"required": true,
"schema": {
"type": "string"
}
},
{
"name": "slug",
"in": "path",
"required": true,
"schema": {
"type": "string"
}
},
{
"name": "state",
"in": "query",
"schema": {
"type": "string",
"enum": ["open", "merged", "declined"]
}
}
],
"responses": {
"200": {
"description": "an array of pull request objects",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/pullrequest"
}
}
}
}
}
}
}
},
"/2.0/repositories/{username}/{slug}/pullrequests/{pid}": {
"get": {
"operationId": "getPullRequestsById",
"parameters": [
{
"name": "username",
"in": "path",
"required": true,
"schema": {
"type": "string"
}
},
{
"name": "slug",
"in": "path",
"required": true,
"schema": {
"type": "string"
}
},
{
"name": "pid",
"in": "path",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "a pull request object",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/pullrequest"
}
}
},
"links": {
"pullRequestMerge": {
"$ref": "#/components/links/PullRequestMerge"
}
}
}
}
}
},
"/2.0/repositories/{username}/{slug}/pullrequests/{pid}/merge": {
"post": {
"operationId": "mergePullRequest",
"parameters": [
{
"name": "username",
"in": "path",
"required": true,
"schema": {
"type": "string"
}
},
{
"name": "slug",
"in": "path",
"required": true,
"schema": {
"type": "string"
}
},
{
"name": "pid",
"in": "path",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"204": {
"description": "the PR was successfully merged"
}
}
}
}
},
"components": {
"links": {
"UserRepositories": {
"operationId": "getRepositoriesByOwner",
"parameters": {
"username": "$response.body#/username"
}
},
"UserRepository": {
"operationId": "getRepository",
"parameters": {
"username": "$response.body#/owner/username",
"slug": "$response.body#/slug"
}
},
"RepositoryPullRequests": {
"operationId": "getPullRequestsByRepository",
"parameters": {
"username": "$response.body#/owner/username",
"slug": "$response.body#/slug"
}
},
"PullRequestMerge": {
"operationId": "mergePullRequest",
"parameters": {
"username": "$response.body#/author/username",
"slug": "$response.body#/repository/slug",
"pid": "$response.body#/id"
}
}
},
"schemas": {
"user": {
"type": "object",
"properties": {
"username": {
"type": "string"
},
"uuid": {
"type": "string"
}
}
},
"repository": {
"type": "object",
"properties": {
"slug": {
"type": "string"
},
"owner": {
"$ref": "#/components/schemas/user"
}
}
},
"pullrequest": {
"type": "object",
"properties": {
"id": {
"type": "integer"
},
"title": {
"type": "string"
},
"repository": {
"$ref": "#/components/schemas/repository"
},
"author": {
"$ref": "#/components/schemas/user"
}
}
}
}
}
}

View File

@@ -0,0 +1,28 @@
{
"openapi": "3.1.0",
"info": {
"title": "Non-oAuth Scopes example",
"version": "1.0.0"
},
"paths": {
"/users": {
"get": {
"security": [
{
"bearerAuth": ["read:users", "public"]
}
]
}
}
},
"components": {
"securitySchemes": {
"bearerAuth": {
"type": "http",
"scheme": "bearer",
"bearerFormat": "jwt",
"description": "note: non-oauth scopes are not defined at the securityScheme level"
}
}
}
}

View File

@@ -0,0 +1,235 @@
{
"openapi": "3.0.0",
"info": {
"version": "1.0.0",
"title": "Swagger Petstore",
"description": "A sample API that uses a petstore as an example to demonstrate features in the OpenAPI 3.0 specification",
"termsOfService": "http://swagger.io/terms/",
"contact": {
"name": "Swagger API Team",
"email": "apiteam@swagger.io",
"url": "http://swagger.io"
},
"license": {
"name": "Apache 2.0",
"url": "https://www.apache.org/licenses/LICENSE-2.0.html"
}
},
"servers": [
{
"url": "https://petstore.swagger.io/v2"
}
],
"paths": {
"/pets": {
"get": {
"description": "Returns all pets from the system that the user has access to\nNam sed condimentum est. Maecenas tempor sagittis sapien, nec rhoncus sem sagittis sit amet. Aenean at gravida augue, ac iaculis sem. Curabitur odio lorem, ornare eget elementum nec, cursus id lectus. Duis mi turpis, pulvinar ac eros ac, tincidunt varius justo. In hac habitasse platea dictumst. Integer at adipiscing ante, a sagittis ligula. Aenean pharetra tempor ante molestie imperdiet. Vivamus id aliquam diam. Cras quis velit non tortor eleifend sagittis. Praesent at enim pharetra urna volutpat venenatis eget eget mauris. In eleifend fermentum facilisis. Praesent enim enim, gravida ac sodales sed, placerat id erat. Suspendisse lacus dolor, consectetur non augue vel, vehicula interdum libero. Morbi euismod sagittis libero sed lacinia.\n\nSed tempus felis lobortis leo pulvinar rutrum. Nam mattis velit nisl, eu condimentum ligula luctus nec. Phasellus semper velit eget aliquet faucibus. In a mattis elit. Phasellus vel urna viverra, condimentum lorem id, rhoncus nibh. Ut pellentesque posuere elementum. Sed a varius odio. Morbi rhoncus ligula libero, vel eleifend nunc tristique vitae. Fusce et sem dui. Aenean nec scelerisque tortor. Fusce malesuada accumsan magna vel tempus. Quisque mollis felis eu dolor tristique, sit amet auctor felis gravida. Sed libero lorem, molestie sed nisl in, accumsan tempor nisi. Fusce sollicitudin massa ut lacinia mattis. Sed vel eleifend lorem. Pellentesque vitae felis pretium, pulvinar elit eu, euismod sapien.\n",
"operationId": "findPets",
"parameters": [
{
"name": "tags",
"in": "query",
"description": "tags to filter by",
"required": false,
"style": "form",
"schema": {
"type": "array",
"items": {
"type": "string"
}
}
},
{
"name": "limit",
"in": "query",
"description": "maximum number of results to return",
"required": false,
"schema": {
"type": "integer",
"format": "int32"
}
}
],
"responses": {
"200": {
"description": "pet response",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/Pet"
}
}
}
}
},
"default": {
"description": "unexpected error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
},
"post": {
"description": "Creates a new pet in the store. Duplicates are allowed",
"operationId": "addPet",
"requestBody": {
"description": "Pet to add to the store",
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/NewPet"
}
}
}
},
"responses": {
"200": {
"description": "pet response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Pet"
}
}
}
},
"default": {
"description": "unexpected error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
}
},
"/pets/{id}": {
"get": {
"description": "Returns a user based on a single ID, if the user does not have access to the pet",
"operationId": "find pet by id",
"parameters": [
{
"name": "id",
"in": "path",
"description": "ID of pet to fetch",
"required": true,
"schema": {
"type": "integer",
"format": "int64"
}
}
],
"responses": {
"200": {
"description": "pet response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Pet"
}
}
}
},
"default": {
"description": "unexpected error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
},
"delete": {
"description": "deletes a single pet based on the ID supplied",
"operationId": "deletePet",
"parameters": [
{
"name": "id",
"in": "path",
"description": "ID of pet to delete",
"required": true,
"schema": {
"type": "integer",
"format": "int64"
}
}
],
"responses": {
"204": {
"description": "pet deleted"
},
"default": {
"description": "unexpected error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
}
}
},
"components": {
"schemas": {
"Pet": {
"allOf": [
{
"$ref": "#/components/schemas/NewPet"
},
{
"type": "object",
"required": ["id"],
"properties": {
"id": {
"type": "integer",
"format": "int64"
}
}
}
]
},
"NewPet": {
"type": "object",
"required": ["name"],
"properties": {
"name": {
"type": "string"
},
"tag": {
"type": "string"
}
}
},
"Error": {
"type": "object",
"required": ["code", "message"],
"properties": {
"code": {
"type": "integer",
"format": "int32"
},
"message": {
"type": "string"
}
}
}
}
}
}

View File

@@ -0,0 +1,177 @@
{
"openapi": "3.0.0",
"info": {
"version": "1.0.0",
"title": "Swagger Petstore",
"license": {
"name": "MIT"
}
},
"servers": [
{
"url": "http://petstore.swagger.io/v1"
}
],
"paths": {
"/pets": {
"get": {
"summary": "List all pets",
"operationId": "listPets",
"tags": ["pets"],
"parameters": [
{
"name": "limit",
"in": "query",
"description": "How many items to return at one time (max 100)",
"required": false,
"schema": {
"type": "integer",
"maximum": 100,
"format": "int32"
}
}
],
"responses": {
"200": {
"description": "A paged array of pets",
"headers": {
"x-next": {
"description": "A link to the next page of responses",
"schema": {
"type": "string"
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Pets"
}
}
}
},
"default": {
"description": "unexpected error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
},
"post": {
"summary": "Create a pet",
"operationId": "createPets",
"tags": ["pets"],
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Pet"
}
}
},
"required": true
},
"responses": {
"201": {
"description": "Null response"
},
"default": {
"description": "unexpected error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
}
},
"/pets/{petId}": {
"get": {
"summary": "Info for a specific pet",
"operationId": "showPetById",
"tags": ["pets"],
"parameters": [
{
"name": "petId",
"in": "path",
"required": true,
"description": "The id of the pet to retrieve",
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "Expected response to a valid request",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Pet"
}
}
}
},
"default": {
"description": "unexpected error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
}
}
},
"components": {
"schemas": {
"Pet": {
"type": "object",
"required": ["id", "name"],
"properties": {
"id": {
"type": "integer",
"format": "int64"
},
"name": {
"type": "string"
},
"tag": {
"type": "string"
}
}
},
"Pets": {
"type": "array",
"maxItems": 100,
"items": {
"$ref": "#/components/schemas/Pet"
}
},
"Error": {
"type": "object",
"required": ["code", "message"],
"properties": {
"code": {
"type": "integer",
"format": "int32"
},
"message": {
"type": "string"
}
}
}
}
}
}

View File

@@ -0,0 +1,261 @@
{
"openapi": "3.1.0",
"info": {
"title": "Tic Tac Toe",
"description": "This API allows writing down marks on a Tic Tac Toe board\nand requesting the state of the board or of individual squares.\n",
"version": "1.0.0"
},
"tags": [
{
"name": "Gameplay"
}
],
"paths": {
"/board": {
"get": {
"summary": "Get the whole board",
"description": "Retrieves the current state of the board and the winner.",
"tags": ["Gameplay"],
"operationId": "get-board",
"responses": {
"200": {
"description": "OK",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/status"
}
}
}
}
},
"security": [
{
"defaultApiKey": []
},
{
"app2AppOauth": ["board:read"]
}
]
}
},
"/board/{row}/{column}": {
"parameters": [
{
"$ref": "#/components/parameters/rowParam"
},
{
"$ref": "#/components/parameters/columnParam"
}
],
"get": {
"summary": "Get a single board square",
"description": "Retrieves the requested square.",
"tags": ["Gameplay"],
"operationId": "get-square",
"responses": {
"200": {
"description": "OK",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/mark"
}
}
}
},
"400": {
"description": "The provided parameters are incorrect",
"content": {
"text/html": {
"schema": {
"$ref": "#/components/schemas/errorMessage"
},
"example": "Illegal coordinates"
}
}
}
},
"security": [
{
"bearerHttpAuthentication": []
},
{
"user2AppOauth": ["board:read"]
}
]
},
"put": {
"summary": "Set a single board square",
"description": "Places a mark on the board and retrieves the whole board and the winner (if any).",
"tags": ["Gameplay"],
"operationId": "put-square",
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/mark"
}
}
}
},
"responses": {
"200": {
"description": "OK",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/status"
}
}
}
},
"400": {
"description": "The provided parameters are incorrect",
"content": {
"text/html": {
"schema": {
"$ref": "#/components/schemas/errorMessage"
},
"examples": {
"illegalCoordinates": {
"value": "Illegal coordinates."
},
"notEmpty": {
"value": "Square is not empty."
},
"invalidMark": {
"value": "Invalid Mark (X or O)."
}
}
}
}
}
},
"security": [
{
"bearerHttpAuthentication": []
},
{
"user2AppOauth": ["board:write"]
}
]
}
}
},
"components": {
"parameters": {
"rowParam": {
"description": "Board row (vertical coordinate)",
"name": "row",
"in": "path",
"required": true,
"schema": {
"$ref": "#/components/schemas/coordinate"
}
},
"columnParam": {
"description": "Board column (horizontal coordinate)",
"name": "column",
"in": "path",
"required": true,
"schema": {
"$ref": "#/components/schemas/coordinate"
}
}
},
"schemas": {
"errorMessage": {
"type": "string",
"maxLength": 256,
"description": "A text message describing an error"
},
"coordinate": {
"type": "integer",
"minimum": 1,
"maximum": 3,
"example": 1
},
"mark": {
"type": "string",
"enum": [".", "X", "O"],
"description": "Possible values for a board square. `.` means empty square.",
"example": "."
},
"board": {
"type": "array",
"maxItems": 3,
"minItems": 3,
"items": {
"type": "array",
"maxItems": 3,
"minItems": 3,
"items": {
"$ref": "#/components/schemas/mark"
}
}
},
"winner": {
"type": "string",
"enum": [".", "X", "O"],
"description": "Winner of the game. `.` means nobody has won yet.",
"example": "."
},
"status": {
"type": "object",
"properties": {
"winner": {
"$ref": "#/components/schemas/winner"
},
"board": {
"$ref": "#/components/schemas/board"
}
}
}
},
"securitySchemes": {
"defaultApiKey": {
"description": "API key provided in console",
"type": "apiKey",
"name": "api-key",
"in": "header"
},
"basicHttpAuthentication": {
"description": "Basic HTTP Authentication",
"type": "http",
"scheme": "Basic"
},
"bearerHttpAuthentication": {
"description": "Bearer token using a JWT",
"type": "http",
"scheme": "Bearer",
"bearerFormat": "JWT"
},
"app2AppOauth": {
"type": "oauth2",
"flows": {
"clientCredentials": {
"tokenUrl": "https://learn.openapis.org/oauth/2.0/token",
"scopes": {
"board:read": "Read the board"
}
}
}
},
"user2AppOauth": {
"type": "oauth2",
"flows": {
"authorizationCode": {
"authorizationUrl": "https://learn.openapis.org/oauth/2.0/auth",
"tokenUrl": "https://learn.openapis.org/oauth/2.0/token",
"scopes": {
"board:read": "Read the board",
"board:write": "Write to the board"
}
}
}
}
}
}
}

View File

@@ -0,0 +1,241 @@
{
"openapi": "3.0.1",
"servers": [
{
"url": "{scheme}://developer.uspto.gov/ds-api",
"variables": {
"scheme": {
"description": "The Data Set API is accessible via https and http",
"enum": ["https", "http"],
"default": "https"
}
}
}
],
"info": {
"description": "The Data Set API (DSAPI) allows the public users to discover and search USPTO exported data sets. This is a generic API that allows USPTO users to make any CSV based data files searchable through API. With the help of GET call, it returns the list of data fields that are searchable. With the help of POST call, data can be fetched based on the filters on the field names. Please note that POST call is used to search the actual data. The reason for the POST call is that it allows users to specify any complex search criteria without worry about the GET size limitations as well as encoding of the input parameters.",
"version": "1.0.0",
"title": "USPTO Data Set API",
"contact": {
"name": "Open Data Portal",
"url": "https://developer.uspto.gov",
"email": "developer@uspto.gov"
}
},
"tags": [
{
"name": "metadata",
"description": "Find out about the data sets"
},
{
"name": "search",
"description": "Search a data set"
}
],
"paths": {
"/": {
"get": {
"tags": ["metadata"],
"operationId": "list-data-sets",
"summary": "List available data sets",
"responses": {
"200": {
"description": "Returns a list of data sets",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/dataSetList"
},
"example": {
"total": 2,
"apis": [
{
"apiKey": "oa_citations",
"apiVersionNumber": "v1",
"apiUrl": "https://developer.uspto.gov/ds-api/oa_citations/v1/fields",
"apiDocumentationUrl": "https://developer.uspto.gov/ds-api-docs/index.html?url=https://developer.uspto.gov/ds-api/swagger/docs/oa_citations.json"
},
{
"apiKey": "cancer_moonshot",
"apiVersionNumber": "v1",
"apiUrl": "https://developer.uspto.gov/ds-api/cancer_moonshot/v1/fields",
"apiDocumentationUrl": "https://developer.uspto.gov/ds-api-docs/index.html?url=https://developer.uspto.gov/ds-api/swagger/docs/cancer_moonshot.json"
}
]
}
}
}
}
}
}
},
"/{dataset}/{version}/fields": {
"get": {
"tags": ["metadata"],
"summary": "Provides the general information about the API and the list of fields that can be used to query the dataset.",
"description": "This GET API returns the list of all the searchable field names that are in the oa_citations. Please see the 'fields' attribute which returns an array of field names. Each field or a combination of fields can be searched using the syntax options shown below.",
"operationId": "list-searchable-fields",
"parameters": [
{
"name": "dataset",
"in": "path",
"description": "Name of the dataset.",
"required": true,
"example": "oa_citations",
"schema": {
"type": "string"
}
},
{
"name": "version",
"in": "path",
"description": "Version of the dataset.",
"required": true,
"example": "v1",
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "The dataset API for the given version is found and it is accessible to consume.",
"content": {
"application/json": {
"schema": {
"type": "string"
}
}
}
},
"404": {
"description": "The combination of dataset name and version is not found in the system or it is not published yet to be consumed by public.",
"content": {
"application/json": {
"schema": {
"type": "string"
}
}
}
}
}
}
},
"/{dataset}/{version}/records": {
"post": {
"tags": ["search"],
"summary": "Provides search capability for the data set with the given search criteria.",
"description": "This API is based on Solr/Lucene Search. The data is indexed using SOLR. This GET API returns the list of all the searchable field names that are in the Solr Index. Please see the 'fields' attribute which returns an array of field names. Each field or a combination of fields can be searched using the Solr/Lucene Syntax. Please refer https://lucene.apache.org/core/3_6_2/queryparsersyntax.html#Overview for the query syntax. List of field names that are searchable can be determined using above GET api.",
"operationId": "perform-search",
"parameters": [
{
"name": "version",
"in": "path",
"description": "Version of the dataset.",
"required": true,
"schema": {
"type": "string",
"default": "v1"
}
},
{
"name": "dataset",
"in": "path",
"description": "Name of the dataset. In this case, the default value is oa_citations",
"required": true,
"schema": {
"type": "string",
"default": "oa_citations"
}
}
],
"responses": {
"200": {
"description": "successful operation",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"type": "object",
"additionalProperties": {
"type": "object"
}
}
}
}
}
},
"404": {
"description": "No matching record found for the given criteria."
}
},
"requestBody": {
"content": {
"application/x-www-form-urlencoded": {
"schema": {
"type": "object",
"properties": {
"criteria": {
"description": "Uses Lucene Query Syntax in the format of propertyName:value, propertyName:[num1 TO num2] and date range format: propertyName:[yyyyMMdd TO yyyyMMdd]. In the response please see the 'docs' element which has the list of record objects. Each record structure would consist of all the fields and their corresponding values.",
"type": "string",
"default": "*:*"
},
"start": {
"description": "Starting record number. Default value is 0.",
"type": "integer",
"default": 0
},
"rows": {
"description": "Specify number of rows to be returned. If you run the search with default values, in the response you will see 'numFound' attribute which will tell the number of records available in the dataset.",
"type": "integer",
"default": 100
}
},
"required": ["criteria"]
}
}
}
}
}
}
},
"components": {
"schemas": {
"dataSetList": {
"type": "object",
"properties": {
"total": {
"type": "integer"
},
"apis": {
"type": "array",
"items": {
"type": "object",
"properties": {
"apiKey": {
"type": "string",
"description": "To be used as a dataset parameter value"
},
"apiVersionNumber": {
"type": "string",
"description": "To be used as a version parameter value"
},
"apiUrl": {
"type": "string",
"format": "uriref",
"description": "The URL describing the dataset's fields"
},
"apiDocumentationUrl": {
"type": "string",
"format": "uriref",
"description": "A URL to the API console for each API"
}
}
}
}
}
}
}
}
}

View File

@@ -0,0 +1,47 @@
{
"openapi": "3.1.0",
"info": {
"title": "Webhook Example",
"version": "1.0.0"
},
"webhooks": {
"newPet": {
"post": {
"requestBody": {
"description": "Information about a new pet in the system",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Pet"
}
}
}
},
"responses": {
"200": {
"description": "Return a 200 status to indicate that the data was received successfully"
}
}
}
}
},
"components": {
"schemas": {
"Pet": {
"required": ["id", "name"],
"properties": {
"id": {
"type": "integer",
"format": "int64"
},
"name": {
"type": "string"
},
"tag": {
"type": "string"
}
}
}
}
}
}

View File

@@ -13,8 +13,9 @@
</PropertyGroup>
<ItemGroup>
<Compile Include="HttpClient.fs"/>
<Compile Include="PureGymDtos.fs"/>
<Compile Include="Assembly.fs" />
<Compile Include="HttpClient.fs" />
<Compile Include="PureGymDtos.fs" />
<Compile Include="TestJsonParse\TestJsonParse.fs" />
<Compile Include="TestJsonParse\TestPureGymJson.fs" />
<Compile Include="TestJsonParse\TestExtensionMethod.fs" />
@@ -28,6 +29,8 @@
<Compile Include="TestHttpClient\TestVariableHeader.fs" />
<Compile Include="TestMockGenerator\TestMockGenerator.fs" />
<Compile Include="TestMockGenerator\TestMockGeneratorNoAttr.fs" />
<Compile Include="TestCapturingMockGenerator\TestCapturingMockGenerator.fs" />
<Compile Include="TestCapturingMockGenerator\TestCapturingMockGeneratorNoAttr.fs" />
<Compile Include="TestJsonSerialize\TestJsonSerde.fs" />
<Compile Include="TestCataGenerator\TestCataGenerator.fs" />
<Compile Include="TestCataGenerator\TestDirectory.fs" />
@@ -36,23 +39,34 @@
<Compile Include="TestCataGenerator\TestMyList2.fs" />
<Compile Include="TestArgParser\TestArgParser.fs" />
<Compile Include="TestSwagger\TestSwaggerParse.fs" />
<Compile Include="TestRemoveOptions.fs"/>
<Compile Include="TestSurface.fs"/>
<Compile Include="TestSwagger\TestOpenApi3Parse.fs" />
<EmbeddedResource Include="TestSwagger\api-with-examples.json" />
<EmbeddedResource Include="TestSwagger\callback-example.json" />
<EmbeddedResource Include="TestSwagger\link-example.json" />
<EmbeddedResource Include="TestSwagger\non-oauth-scopes.json" />
<EmbeddedResource Include="TestSwagger\petstore.json" />
<EmbeddedResource Include="TestSwagger\petstore-expanded.json" />
<EmbeddedResource Include="TestSwagger\tictactoe.json" />
<EmbeddedResource Include="TestSwagger\uspto.json" />
<EmbeddedResource Include="TestSwagger\webhook-example.json" />
<Compile Include="TestRemoveOptions.fs" />
<Compile Include="TestSurface.fs" />
<None Include="../.github/workflows/dotnet.yaml" />
</ItemGroup>
<ItemGroup>
<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"/>
<PackageReference Include="ApiSurface" Version="5.0.1" />
<PackageReference Include="FsCheck" Version="3.3.1" />
<PackageReference Include="FsUnit" Version="7.1.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" />
<PackageReference Include="WoofWare.Expect" Version="0.8.2" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\WoofWare.Myriad.Plugins\WoofWare.Myriad.Plugins.fsproj"/>
<ProjectReference Include="..\ConsumePlugin\ConsumePlugin.fsproj"/>
<ProjectReference Include="..\WoofWare.Myriad.Plugins\WoofWare.Myriad.Plugins.fsproj" />
<ProjectReference Include="..\ConsumePlugin\ConsumePlugin.fsproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,677 @@
namespace WoofWare.Myriad.Plugins
open System
open Fantomas.FCS.Syntax
open Fantomas.FCS.Xml
open WoofWare.Whippet.Fantomas
type internal CapturingInterfaceMockOutputSpec =
{
IsInternal : bool
}
type private CallField =
| ArgsObject of Ident * SynTypeDefn * SynTyparDecls option
| Original of SynType
[<RequireQualifiedAccess>]
module internal CapturingInterfaceMockGenerator =
open Fantomas.FCS.Text.Range
[<RequireQualifiedAccess>]
type private KnownInheritance = | IDisposable
/// Expects the input `args` list to have more than one element.
let private createTypeForArgs
(spec : CapturingInterfaceMockOutputSpec)
(memberName : Ident)
(generics : SynTyparDecls option)
(args : TupledArg list)
: Ident * SynTypeDefn
=
let name = memberName.idText + "Call" |> Ident.create
let access =
if spec.IsInternal then
SynAccess.Internal range0
else
SynAccess.Public range0
let recordFields =
args
|> List.mapi (fun i tupledArg ->
{
SynFieldData.Ident =
match tupledArg.Args with
| [ arg ] -> arg.Id
| _ -> None
|> Option.defaultValue (Ident.create $"Arg%i{i}")
|> Some
Attrs = []
Type =
tupledArg.Args
|> List.map (fun pi ->
if pi.IsOptional then
pi.Type |> SynType.appPostfix "option"
else
pi.Type
)
|> SynType.tupleNoParen
|> Option.get
}
|> SynField.make
)
let record =
{
Name = name
Fields = recordFields
Members = None
XmlDoc = Some (PreXmlDoc.create $"A single call to the %s{memberName.idText} method")
Generics = generics
TypeAccessibility = Some access
ImplAccessibility = None
Attributes = []
}
let typeDecl = AstHelper.defineRecordType record
name, typeDecl
let private buildType (x : ParameterInfo) : SynType =
if x.IsOptional then
SynType.appPostfix "option" x.Type
else
x.Type
let private constructMemberSinglePlace (tuple : TupledArg) : SynType =
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 rec private collectGenerics' (ty : SynType) : Ident list =
match ty with
| SynType.Var (typar = SynTypar (ident = typar)) -> [ typar ]
| SynType.HashConstraint (innerType = ty)
| SynType.WithGlobalConstraints (typeName = ty)
| SynType.Paren (innerType = ty)
| SynType.MeasurePower (baseMeasure = ty)
| SynType.SignatureParameter (usedType = ty)
| SynType.Array (elementType = ty) -> collectGenerics' ty
| SynType.StaticConstant _
| SynType.StaticConstantNamed _
| SynType.StaticConstantExpr _
| SynType.FromParseError _
| SynType.Anon _
| SynType.LongIdent _ -> []
| SynType.LongIdentApp (typeArgs = tys)
| SynType.App (typeArgs = tys) -> tys |> List.collect collectGenerics'
| SynType.Tuple (path = path) ->
path
|> List.collect (fun seg ->
match seg with
| SynTupleTypeSegment.Type ty -> collectGenerics' ty
| SynTupleTypeSegment.Star _
| SynTupleTypeSegment.Slash _ -> []
)
| SynType.AnonRecd (fields = fields) -> fields |> List.collect (fun (_, ty) -> collectGenerics' ty)
| SynType.Fun (argType = t1 ; returnType = t2)
| SynType.Or (lhsType = t1 ; rhsType = t2) -> collectGenerics' t1 @ collectGenerics' t2
let private collectGenerics (ty : SynType) =
collectGenerics' ty |> List.distinctBy _.idText
/// Builds the record field for the mock object, and also if applicable a type representing a single call to
/// that object (packaging up the args of the call).
let private constructMember (spec : CapturingInterfaceMockOutputSpec) (mem : MemberInfo) : SynField * CallField =
let inputType = mem.Args |> List.map constructMemberSinglePlace
let funcType = SynType.toFun inputType mem.ReturnType
let field =
{
Type = funcType
Attrs = []
Ident = Some mem.Identifier
}
|> SynField.make
|> SynField.withDocString (mem.XmlDoc |> Option.defaultValue PreXmlDoc.Empty)
let argsType =
match mem.Args with
| [] -> failwith "expected args in member"
| [ ty ] ->
ty.Args
|> List.map (fun pi ->
if pi.IsOptional then
SynType.appPostfix "option" pi.Type
else
pi.Type
)
|> SynType.tupleNoParen
|> Option.get
|> CallField.Original
| args ->
let genericsUsed =
args
|> List.collect (fun arg -> arg.Args |> List.map _.Type |> List.collect collectGenerics)
|> List.distinctBy _.idText
let genericsUsed =
match genericsUsed with
| [] -> None
| genericsUsed ->
genericsUsed
|> List.map (fun i ->
SynTyparDecl.SynTyparDecl ([], SynTypar.SynTypar (i, TyparStaticReq.None, false))
)
|> fun l -> SynTyparDecls.PostfixList (l, [], range0)
|> Some
let name, defn = createTypeForArgs spec mem.Identifier genericsUsed args
CallField.ArgsObject (name, defn, genericsUsed)
field, argsType
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 createType
(spec : CapturingInterfaceMockOutputSpec)
(name : string)
(interfaceType : InterfaceType)
(xmlDoc : PreXmlDoc)
: SynModuleDecl option * SynModuleDecl
=
let fields =
interfaceType.Members
|> List.map (constructMember spec)
|> List.append (
interfaceType.Properties
|> List.map constructProperty
|> List.map (Tuple.withRight (CallField.Original SynType.unit))
)
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
// TODO: for each field, if there are multiple arguments to the member, stamp out a new type to represent them;
// then store that type name in this list alongside the field name
let fields =
fields
|> List.map (fun (SynField (idOpt = idOpt) as f, extraType) ->
let fieldName =
match idOpt with
| None -> failwith $"unexpectedly got a field with no identifier: %O{f}"
| Some idOpt -> idOpt.idText
fieldName, (f, extraType)
)
|> Map.ofList
let failwithNotImplemented (fieldName : string) =
let failString = SynExpr.CreateConst $"Unimplemented mock function: %s{fieldName}"
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 ]
| Some generics ->
let generics =
generics.TyparDecls
|> List.map (fun (SynTyparDecl (_, typar)) -> SynType.var typar)
SynType.app name generics
let emptyRecordFieldInstantiations =
let interfaceExtras =
if inherits.Contains KnownInheritance.IDisposable then
let unitFun = SynExpr.createThunk (SynExpr.CreateConst ())
[ SynLongIdent.createS "Dispose", unitFun ]
else
[]
let originalMembers =
fields
|> Map.toList
|> List.map (fun (fieldName, _) -> SynLongIdent.createS fieldName, failwithNotImplemented fieldName)
let callsObject =
SynLongIdent.createS "Calls",
SynExpr.applyFunction
(SynExpr.createLongIdent [ $"%s{name}Calls" ; "Calls" ; "Empty" ])
(SynExpr.CreateConst ())
callsObject :: interfaceExtras @ originalMembers
let staticMemberEmpty =
SynBinding.basic
[ Ident.create "Empty" ]
[ SynPat.unit ]
(SynExpr.createRecord None emptyRecordFieldInstantiations)
|> SynBinding.withXmlDoc (PreXmlDoc.create "An implementation where every non-unit method throws.")
|> SynBinding.withReturnAnnotation constructorReturnType
|> SynMemberDefn.staticMember
let recordFields =
let extras =
if inherits.Contains KnownInheritance.IDisposable then
{
Attrs = []
Ident = Some (Ident.create "Dispose")
Type = SynType.funFromDomain SynType.unit SynType.unit
}
|> SynField.make
|> SynField.withDocString (PreXmlDoc.create "Implementation of IDisposable.Dispose")
|> List.singleton
else
[]
let nonExtras =
fields |> Map.toSeq |> Seq.map (fun (_, (field, _)) -> field) |> Seq.toList
let calls =
let ty =
match interfaceType.Generics with
| None -> SynType.createLongIdent' [ $"%s{name}Calls" ; "Calls" ]
| Some generics ->
generics.TyparDecls
|> List.map (fun (SynTyparDecl (_, typar)) -> SynType.var typar)
|> SynType.app' (SynType.createLongIdent' [ $"%s{name}Calls" ; "Calls" ])
{
Attrs = []
Ident = Ident.create "Calls" |> Some
Type = ty
}
|> SynField.make
calls :: extras @ nonExtras
let access =
match interfaceType.Accessibility, spec.IsInternal with
| Some (SynAccess.Public _), true
| None, true -> SynAccess.Internal range0
| Some (SynAccess.Public _), false -> SynAccess.Public range0
| None, false -> SynAccess.Public range0
| Some (SynAccess.Internal _), _ -> SynAccess.Internal range0
| Some (SynAccess.Private _), _ -> SynAccess.Private range0
let accessAtLeastInternal =
match access with
| SynAccess.Private _ -> SynAccess.Internal range0
| access -> access
let callsObject =
let fields' =
fields
|> Map.toSeq
|> Seq.map (fun (fieldName, (_, callType)) ->
match callType with
| CallField.Original ty ->
{
Attrs = []
Ident = Some (fieldName |> Ident.create)
Type = SynType.app "ResizeArray" [ ty ]
}
|> SynField.make
| CallField.ArgsObject (argsObjectName, _, generics) ->
{
Attrs = []
Ident = Some (fieldName |> Ident.create)
Type =
match generics with
| None -> SynType.named argsObjectName.idText
| Some generics ->
generics.TyparDecls
|> List.map (fun (SynTyparDecl.SynTyparDecl (_, typar)) -> SynType.var typar)
|> SynType.app' (SynType.createLongIdent' [ argsObjectName.idText ])
|> List.singleton
|> SynType.app "ResizeArray"
}
|> SynField.make
)
|> Seq.toList
let emptyMember =
let returnType =
match interfaceType.Generics with
| None -> SynType.named "Calls"
| Some generics ->
let generics =
match generics with
| SynTyparDecls.PostfixList (decls = decls)
| SynTyparDecls.PrefixList (decls = decls) -> decls
| SynTyparDecls.SinglePrefix (decl = decl) -> [ decl ]
|> List.map (fun (SynTyparDecl.SynTyparDecl (_, typar)) -> SynType.var typar)
SynType.app "Calls" generics
fields
|> Map.toSeq
|> Seq.map (fun (name, _) ->
SynLongIdent.createS name,
SynExpr.applyFunction (SynExpr.createIdent "ResizeArray") (SynExpr.CreateConst ())
)
|> Seq.toList
|> SynExpr.createRecord None
|> SynBinding.basic [ Ident.create "Empty" ] [ SynPat.unit ]
|> SynBinding.withXmlDoc (PreXmlDoc.create "A fresh calls object which has not yet had any calls made.")
|> SynBinding.withReturnAnnotation returnType
|> SynMemberDefn.staticMember
{
RecordType.Name = Ident.create "Calls"
Fields = fields'
Members = Some [ emptyMember ]
XmlDoc = PreXmlDoc.create $"All the calls made to a %s{name} mock" |> Some
Generics = interfaceType.Generics
TypeAccessibility = Some accessAtLeastInternal
ImplAccessibility = None
Attributes = [ SynAttribute.requireQualifiedAccess ]
}
|> AstHelper.defineRecordType
let interfaceMembers =
let members =
interfaceType.Members
|> List.map (fun memberInfo ->
let headArgs =
memberInfo.Args
|> List.mapi (fun i tupledArgs ->
let args =
tupledArgs.Args
|> List.mapi (fun j ty ->
match ty.Type with
| UnitType -> SynPat.unit
| _ -> SynPat.named $"arg_%i{i}_%i{j}"
)
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, addToCalls =
let tupleContents =
memberInfo.Args
|> List.mapi (fun i args ->
args.Args
|> List.mapi (fun j arg ->
match arg.Type with
| UnitType -> SynExpr.CreateConst (), arg.Id
| _ -> SynExpr.createIdent $"arg_%i{i}_%i{j}", arg.Id
)
)
let tuples = tupleContents |> List.map (List.map fst >> SynExpr.tuple)
match tuples |> List.rev with
| [] -> failwith "expected args but got none"
| last :: rest ->
let tuples = (last, rest) ||> List.fold SynExpr.applyTo
let body =
tuples
|> SynExpr.applyFunction (
SynExpr.createLongIdent' [ Ident.create "this" ; memberInfo.Identifier ]
)
let addToCalls =
match Map.tryFind memberInfo.Identifier.idText fields with
| None ->
failwith
$"unexpectedly looking up a nonexistent field %s{memberInfo.Identifier.idText}"
| Some (_, result) ->
match result with
| CallField.Original _ -> tuples
| CallField.ArgsObject _ ->
tupleContents
|> List.mapi (fun i fields ->
match fields with
| [ contents, Some ident ] -> SynLongIdent.create [ ident ], contents
| [ contents, None ] -> SynLongIdent.createS $"Arg%i{i}", contents
| _ ->
SynLongIdent.createS $"Arg%i{i}",
SynExpr.tupleNoParen (fields |> List.map fst)
)
|> SynExpr.createRecord None
|> SynExpr.applyFunction (
SynExpr.createLongIdent [ "this" ; "Calls" ; memberInfo.Identifier.idText ; "Add" ]
)
|> SynExpr.createLambda "_"
|> SynExpr.applyFunction (
SynExpr.createIdent "lock"
|> SynExpr.applyTo (
SynExpr.createLongIdent [ "this" ; "Calls" ; memberInfo.Identifier.idText ]
)
)
body, addToCalls
let body = [ addToCalls ; body ] |> SynExpr.sequential
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 interfaceType.Name
match interfaceType.Generics with
| None -> baseName
| Some generics ->
let generics =
match generics with
| SynTyparDecls.PostfixList (decls, _, _) -> decls
| SynTyparDecls.PrefixList (decls, _) -> decls
| SynTyparDecls.SinglePrefix (decl, _) -> [ decl ]
|> List.map (fun (SynTyparDecl (_, typar)) -> SynType.var typar)
SynType.app' baseName generics
SynMemberDefn.Interface (interfaceName, Some range0, Some (members @ properties), 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
Fields = recordFields
Members = Some ([ staticMemberEmpty ; interfaceMembers ] @ extraInterfaces)
XmlDoc = Some xmlDoc
Generics = interfaceType.Generics
TypeAccessibility = Some access
ImplAccessibility = None
Attributes = []
}
let typeDecl = AstHelper.defineRecordType record
let callsModule =
let types =
fields
|> Map.toSeq
|> Seq.choose (fun (_, (_, field)) ->
match field with
| CallField.Original _ -> None
| CallField.ArgsObject (_, callType, _) -> Some (SynModuleDecl.Types ([ callType ], range0))
)
|> Seq.toList
types @ [ SynModuleDecl.Types ([ callsObject ], range0) ]
|> SynModuleDecl.nestedModule (
SynComponentInfo.create (Ident.create $"%s{name}Calls")
|> SynComponentInfo.withAccessibility accessAtLeastInternal
|> SynComponentInfo.addAttributes [ SynAttribute.requireQualifiedAccess ]
)
|> Some
(callsModule, SynModuleDecl.Types ([ typeDecl ], range0))
let createRecord
(namespaceId : LongIdent)
(opens : SynOpenDeclTarget list)
(interfaceType : SynTypeDefn, spec : CapturingInterfaceMockOutputSpec)
: SynModuleOrNamespace
=
let interfaceType = AstHelper.parseInterface interfaceType
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.Substring 1
else
s
|> fun s -> s + "Mock"
let callsTypes, typeDecl = createType spec name interfaceType docString
[
yield! opens |> List.map SynModuleDecl.openAny
match callsTypes with
| None -> ()
| Some c -> yield c
yield typeDecl
]
|> SynModuleOrNamespace.createNamespace namespaceId
open Myriad.Core
/// Myriad generator that creates a record which implements the given interface,
/// but with every field mocked out.
[<MyriadGenerator("capturing-interface-mock")>]
type CapturingInterfaceMockGenerator () =
interface IMyriadGenerator with
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.getTypes ast
let namespaceAndInterfaces =
types
|> List.choose (fun (ns, types) ->
types
|> List.choose (fun typeDef ->
match SynTypeDefn.getAttribute typeof<GenerateCapturingMockAttribute>.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.CapturingInterfaceMock arg ->
let spec =
{
IsInternal =
arg
|> Option.defaultValue
GenerateCapturingMockAttribute.DefaultIsInternal
}
Some (typeDef, spec)
| _ -> None
)
| _ -> None
| Some attr ->
let arg =
match SynExpr.stripOptionalParen attr.ArgExpr with
| SynExpr.Const (SynConst.Bool value, _) -> value
| SynExpr.Const (SynConst.Unit, _) -> GenerateCapturingMockAttribute.DefaultIsInternal
| arg ->
failwith
$"Unrecognised argument %+A{arg} to [<%s{nameof GenerateCapturingMockAttribute}>]. Literals are not supported. Use `true` or `false` (or unit) only."
let spec =
{
IsInternal = arg
}
Some (typeDef, spec)
)
|> function
| [] -> None
| ty -> Some (ns, ty)
)
let opens = AstHelper.extractOpens ast
let modules =
namespaceAndInterfaces
|> List.collect (fun (ns, records) ->
records |> List.map (CapturingInterfaceMockGenerator.createRecord ns opens)
)
Output.Ast modules

View File

@@ -453,15 +453,20 @@ module internal HttpClientGenerator =
let contentTypeHeader, memberHeaders =
info.Headers
|> List.partition (fun (headerName, headerValue) ->
|> List.partition (fun (headerName, _headerValue) ->
match headerName |> SynExpr.stripOptionalParen with
| SynExpr.Const (SynConst.String ("Content-Type", _, _), _) -> true
| SynExpr.Const (SynConst.String (s, _, _), _) ->
System.String.Equals (s, "Content-Type", System.StringComparison.OrdinalIgnoreCase)
| _ -> false
)
let contentTypeHeader =
match contentTypeHeader with
| [] -> None
| [] ->
// Set application/json if we *know* we're sending JSON
match bodyParam with
| Some (BodyParamMethods.Serialise _, _) -> Some (SynExpr.CreateConst "application/json")
| _ -> None
| [ _, ct ] -> Some (SynExpr.stripOptionalParen ct)
| _ -> failwith "Unexpectedly got multiple Content-Type headers"

File diff suppressed because it is too large Load Diff

View File

@@ -2,6 +2,7 @@ namespace WoofWare.Myriad.Plugins
type internal DesiredGenerator =
| InterfaceMock of isInternal : bool option
| CapturingInterfaceMock of isInternal : bool option
| JsonParse of extensionMethod : bool option
| JsonSerialize of extensionMethod : bool option
| HttpClient of extensionMethod : bool option
@@ -11,6 +12,9 @@ type internal DesiredGenerator =
| "GenerateMock" -> DesiredGenerator.InterfaceMock None
| "GenerateMock(true)" -> DesiredGenerator.InterfaceMock (Some true)
| "GenerateMock(false)" -> DesiredGenerator.InterfaceMock (Some false)
| "GenerateCapturingMock" -> DesiredGenerator.CapturingInterfaceMock None
| "GenerateCapturingMock(true)" -> DesiredGenerator.CapturingInterfaceMock (Some true)
| "GenerateCapturingMock(false)" -> DesiredGenerator.CapturingInterfaceMock (Some false)
| "JsonParse" -> DesiredGenerator.JsonParse None
| "JsonParse(true)" -> DesiredGenerator.JsonParse (Some true)
| "JsonParse(false)" -> DesiredGenerator.JsonParse (Some false)

View File

@@ -1,5 +1,7 @@
WoofWare.Myriad.Plugins.ArgParserGenerator inherit obj, implements Myriad.Core.IMyriadGenerator
WoofWare.Myriad.Plugins.ArgParserGenerator..ctor [constructor]: unit
WoofWare.Myriad.Plugins.CapturingInterfaceMockGenerator inherit obj, implements Myriad.Core.IMyriadGenerator
WoofWare.Myriad.Plugins.CapturingInterfaceMockGenerator..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

View File

@@ -0,0 +1,6 @@
namespace WoofWare.Myriad.Plugins
[<RequireQualifiedAccess>]
module internal Tuple =
let withLeft left right = left, right
let withRight right left = left, right

View File

@@ -30,6 +30,7 @@
<ItemGroup>
<Compile Include="AssemblyInfo.fs" />
<Compile Include="List.fs"/>
<Compile Include="Tuple.fs" />
<Compile Include="Text.fs" />
<Compile Include="Measure.fs" />
<Compile Include="AstHelper.fs" />
@@ -37,6 +38,7 @@
<Compile Include="RemoveOptionsGenerator.fs"/>
<Compile Include="MyriadParamParser.fs" />
<Compile Include="InterfaceMockGenerator.fs"/>
<Compile Include="CapturingInterfaceMockGenerator.fs" />
<Compile Include="JsonSerializeGenerator.fs"/>
<Compile Include="JsonParseGenerator.fs"/>
<Compile Include="HttpClientGenerator.fs"/>

View File

@@ -1,5 +1,5 @@
{
"version": "8.0",
"version": "9.0",
"publicReleaseRefSpec": [
"^refs/heads/main$"
],
@@ -11,4 +11,4 @@
":/README.md",
":/Directory.Build.props"
]
}
}

View File

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

6
flake.lock generated
View File

@@ -20,11 +20,11 @@
},
"nixpkgs": {
"locked": {
"lastModified": 1749903597,
"narHash": "sha256-jp0D4vzBcRKwNZwfY4BcWHemLGUs4JrS3X9w5k/JYDA=",
"lastModified": 1758976413,
"narHash": "sha256-hEIDTaIqvW1NMfaNgz6pjhZPZKTmACJmXxGr/H6isIg=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "41da1e3ea8e23e094e5e3eeb1e6b830468a7399e",
"rev": "e3a3b32cc234f1683258d36c6232f150d57df015",
"type": "github"
},
"original": {

View File

@@ -66,6 +66,7 @@
pkgs.alejandra
pkgs.nodePackages.markdown-link-check
pkgs.shellcheck
pkgs.xmlstarlet
];
};
});

View File

@@ -1,13 +1,13 @@
[
{
"pname": "ApiSurface",
"version": "4.1.21",
"hash": "sha256-v2adBYoE9NZPaQR3u2qq9r/9PxAM/wqi2Uiky0xGq+E="
"version": "5.0.1",
"hash": "sha256-0GMXEMFgWbbE2OGxW+6h4zGgQHg+IZy1aI13Dn97xSU="
},
{
"pname": "fantomas",
"version": "7.0.2",
"hash": "sha256-BAaENIm/ksTiXrUImRgKoIXTGIlgsX7ch6ayoFjhJXA="
"version": "7.0.3",
"hash": "sha256-0XlfV7SxXPDnk/CjkUesJSaH0cxlNHJ+Jj86zNUhkNA="
},
{
"pname": "Fantomas.Core",
@@ -19,15 +19,20 @@
"version": "6.1.1",
"hash": "sha256-NuZ8msPEHYA8T3EYREB28F1RcNgUU8V54eg2+UttYxw="
},
{
"pname": "Fantomas.FCS",
"version": "7.0.3",
"hash": "sha256-BmCUq+ZQ3b25nrMBTc5tcxdO2soryEjNx9Fn/FJpi1c="
},
{
"pname": "FsCheck",
"version": "3.3.0",
"hash": "sha256-TFDR/uAGv4OqrMX8/reQ4faaAhH9hxTHr1T/YkNPCCU="
"version": "3.3.1",
"hash": "sha256-k65ksdOSOGz+meRUUND+yuqJtm5ChaKuaxmRIdKzx2Y="
},
{
"pname": "fsharp-analyzers",
"version": "0.31.0",
"hash": "sha256-PoAvaXbXsmvVw870UsnqdD20HoBHO7u4bzoaz5DXfzM="
"version": "0.32.1",
"hash": "sha256-le6rPnAF7cKGBZ2w8H2u9glK+6rT2ZjiAVnrkH2IhrM="
},
{
"pname": "FSharp.Core",
@@ -41,13 +46,18 @@
},
{
"pname": "FSharp.Core",
"version": "9.0.202",
"hash": "sha256-64Gub0qemmCoMa1tDus6TeTuB1+5sHfE6KD2j4o84mA="
"version": "9.0.303",
"hash": "sha256-AxR6wqodeU23KOTgkUfIgbavgbcSuzD4UBP+tiFydgA="
},
{
"pname": "FSharp.SystemTextJson",
"version": "1.4.36",
"hash": "sha256-zZEhjP0mdc5E3fBPS4/lqD7sxoaoT5SOspP546RWYdc="
},
{
"pname": "FsUnit",
"version": "7.0.1",
"hash": "sha256-K85CIdxMeFSHEKZk6heIXp/oFjWAn7dBILKrw49pJUY="
"version": "7.1.1",
"hash": "sha256-UMCEGKxQ4ytjmPuVpiNaAPbi3RQH9gqa61JJIUS/6hg="
},
{
"pname": "Microsoft.ApplicationInsights",
@@ -144,11 +154,6 @@
"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",
@@ -211,8 +216,8 @@
},
{
"pname": "Nerdbank.GitVersioning",
"version": "3.8.38-alpha",
"hash": "sha256-gPMrVbjOZxXoofczF/pn6eVkLhjVSJIyQrLO2oljrDc="
"version": "3.8.118",
"hash": "sha256-Hmyy0ZKOmwN4zIhI4+MqoN8geZNc1sd033aZJ6APrO8="
},
{
"pname": "NETStandard.Library",
@@ -304,26 +309,26 @@
"version": "7.0.0",
"hash": "sha256-9Wk8cHSkjKtqkN6xW7KnXoQVtF/VNbKeBq79WqDesMs="
},
{
"pname": "System.Diagnostics.DiagnosticSource",
"version": "8.0.1",
"hash": "sha256-zmwHjcJgKcbkkwepH038QhcnsWMJcHys+PEbFGC0Jgo="
},
{
"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.Memory",
"version": "4.6.0",
"hash": "sha256-OhAEKzUM6eEaH99DcGaMz2pFLG/q/N4KVWqqiBYUOFo="
},
{
"pname": "System.Private.Uri",
"version": "4.3.0",
@@ -344,11 +349,6 @@
"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",
@@ -360,20 +360,30 @@
"hash": "sha256-Ri53QmFX8I8UH0x4PikQ1ZA07ZSnBUXStd5rBfGWFOE="
},
{
"pname": "System.Security.Principal.Windows",
"version": "4.5.0",
"hash": "sha256-BkUYNguz0e4NJp1kkW7aJBn3dyH9STwB5N8XqnlCsmY="
"pname": "System.Text.Json",
"version": "6.0.10",
"hash": "sha256-UijYh0dxFjFinMPSTJob96oaRkNm+Wsa+7Ffg6mRnsc="
},
{
"pname": "System.Text.Json",
"version": "8.0.5",
"hash": "sha256-yKxo54w5odWT6nPruUVsaX53oPRe+gKzGvLnnxtwP68="
},
{
"pname": "System.Text.Json",
"version": "9.0.0",
"hash": "sha256-aM5Dh4okLnDv940zmoFAzRmqZre83uQBtGOImJpoIqk="
},
{
"pname": "TypeEquality",
"version": "0.3.0",
"hash": "sha256-V50xAOzzyUJrY+MYPRxtnqW5MVeATXCes89wPprv1r4="
},
{
"pname": "WoofWare.Expect",
"version": "0.8.2",
"hash": "sha256-iqt4FPkUr24/4dnRfh7P5nPNNlaPzaItP6/yrGRrQyo="
},
{
"pname": "WoofWare.Whippet.Fantomas",
"version": "0.6.3",