Compare commits

...

28 Commits

Author SHA1 Message Date
Smaug123
2c539c13a3 Capturing mock 2025-09-16 23:33:35 +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
48 changed files with 7493 additions and 106 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,14 +287,14 @@ 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
@@ -320,14 +320,14 @@ 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
@@ -356,9 +356,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

@@ -0,0 +1,57 @@
namespace SomeNamespace.CapturingMock
open System
open WoofWare.Myriad.Plugins
[<GenerateCapturingMock>]
type IPublicType =
abstract Mem1 : 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 : 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
[<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,330 @@
//------------------------------------------------------------------------------
// 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
/// Mock record type for an interface
type internal PublicTypeMock =
{
Mem1 : string * int -> string list
Mem1_Calls : ResizeArray<string * int>
Mem2 : string -> int
Mem2_Calls : ResizeArray<string>
Mem3 : int * option<System.Threading.CancellationToken> -> string
Mem3_Calls : ResizeArray<int * System.Threading.CancellationToken>
}
/// An implementation where every non-unit method throws.
static member Empty : PublicTypeMock =
{
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"))
Mem1_Calls = ResizeArray ()
Mem2_Calls = ResizeArray ()
Mem3_Calls = ResizeArray ()
}
interface IPublicType with
member this.Mem1 (arg_0_0, arg_0_1) = this.Mem1 (arg_0_0, arg_0_1)
member this.Mem2 arg_0_0 = this.Mem2 (arg_0_0)
member this.Mem3 (arg_0_0, arg_0_1) = this.Mem3 (arg_0_0, arg_0_1)
namespace SomeNamespace.CapturingMock
open System
open WoofWare.Myriad.Plugins
/// Mock record type for an interface
type public PublicTypeInternalFalseMock =
{
Mem1 : string * int -> string list
Mem1_Calls : ResizeArray<string * int>
Mem2 : string -> int
Mem2_Calls : ResizeArray<string>
Mem3 : int * option<System.Threading.CancellationToken> -> string
Mem3_Calls : ResizeArray<int * System.Threading.CancellationToken>
}
/// An implementation where every non-unit method throws.
static member Empty : PublicTypeInternalFalseMock =
{
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"))
Mem1_Calls = ResizeArray ()
Mem2_Calls = ResizeArray ()
Mem3_Calls = ResizeArray ()
}
interface IPublicTypeInternalFalse with
member this.Mem1 (arg_0_0, arg_0_1) = this.Mem1 (arg_0_0, arg_0_1)
member this.Mem2 arg_0_0 = this.Mem2 (arg_0_0)
member this.Mem3 (arg_0_0, arg_0_1) = this.Mem3 (arg_0_0, arg_0_1)
namespace SomeNamespace.CapturingMock
open System
open WoofWare.Myriad.Plugins
/// Mock record type for an interface
type internal InternalTypeMock =
{
Mem1 : string * int -> unit
Mem1_Calls : ResizeArray<string * int>
Mem2 : string -> int
Mem2_Calls : ResizeArray<string>
}
/// An implementation where every non-unit method throws.
static member Empty : InternalTypeMock =
{
Mem1 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem1"))
Mem2 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem2"))
Mem1_Calls = ResizeArray ()
Mem2_Calls = ResizeArray ()
}
interface InternalType with
member this.Mem1 (arg_0_0, arg_0_1) = this.Mem1 (arg_0_0, arg_0_1)
member this.Mem2 arg_0_0 = this.Mem2 (arg_0_0)
namespace SomeNamespace.CapturingMock
open System
open WoofWare.Myriad.Plugins
/// Mock record type for an interface
type private PrivateTypeMock =
{
Mem1 : string * int -> unit
Mem1_Calls : ResizeArray<string * int>
Mem2 : string -> int
Mem2_Calls : ResizeArray<string>
}
/// An implementation where every non-unit method throws.
static member Empty : PrivateTypeMock =
{
Mem1 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem1"))
Mem2 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem2"))
Mem1_Calls = ResizeArray ()
Mem2_Calls = ResizeArray ()
}
interface PrivateType with
member this.Mem1 (arg_0_0, arg_0_1) = this.Mem1 (arg_0_0, arg_0_1)
member this.Mem2 arg_0_0 = this.Mem2 (arg_0_0)
namespace SomeNamespace.CapturingMock
open System
open WoofWare.Myriad.Plugins
/// Mock record type for an interface
type private PrivateTypeInternalFalseMock =
{
Mem1 : string * int -> unit
Mem1_Calls : ResizeArray<string * int>
Mem2 : string -> int
Mem2_Calls : ResizeArray<string>
}
/// An implementation where every non-unit method throws.
static member Empty : PrivateTypeInternalFalseMock =
{
Mem1 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem1"))
Mem2 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem2"))
Mem1_Calls = ResizeArray ()
Mem2_Calls = ResizeArray ()
}
interface PrivateTypeInternalFalse with
member this.Mem1 (arg_0_0, arg_0_1) = this.Mem1 (arg_0_0, arg_0_1)
member this.Mem2 arg_0_0 = this.Mem2 (arg_0_0)
namespace SomeNamespace.CapturingMock
open System
open WoofWare.Myriad.Plugins
/// Mock record type for an interface
type internal VeryPublicTypeMock<'a, 'b> =
{
Mem1 : 'a -> 'b
Mem1_Calls : ResizeArray<'a>
}
/// An implementation where every non-unit method throws.
static member Empty () : VeryPublicTypeMock<'a, 'b> =
{
Mem1 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem1"))
Mem1_Calls = ResizeArray ()
}
interface VeryPublicType<'a, 'b> with
member this.Mem1 arg_0_0 = this.Mem1 (arg_0_0)
namespace SomeNamespace.CapturingMock
open System
open WoofWare.Myriad.Plugins
/// 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
}
/// Mock record type for an interface
type internal CurriedMock<'a> =
{
Mem1 : int -> 'a -> string
Mem1_Calls : ResizeArray<Mem1Call<'a>>
Mem2 : int * string -> 'a -> string
Mem2_Calls : ResizeArray<Mem2Call<'a>>
Mem3 : (int * string) -> 'a -> string
Mem3_Calls : ResizeArray<Mem3Call<'a>>
Mem4 : (int * string) -> ('a * int) -> string
Mem4_Calls : ResizeArray<Mem4Call<'a>>
Mem5 : int * string -> ('a * int) -> string
Mem5_Calls : ResizeArray<Mem5Call<'a>>
Mem6 : int * string -> 'a * int -> string
Mem6_Calls : ResizeArray<Mem6Call<'a>>
}
/// An implementation where every non-unit method throws.
static member Empty () : CurriedMock<'a> =
{
Mem1 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem1"))
Mem2 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem2"))
Mem3 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem3"))
Mem4 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem4"))
Mem5 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem5"))
Mem6 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem6"))
Mem1_Calls = ResizeArray ()
Mem2_Calls = ResizeArray ()
Mem3_Calls = ResizeArray ()
Mem4_Calls = ResizeArray ()
Mem5_Calls = ResizeArray ()
Mem6_Calls = ResizeArray ()
}
interface Curried<'a> with
member this.Mem1 arg_0_0 arg_1_0 = this.Mem1 (arg_0_0) (arg_1_0)
member this.Mem2 (arg_0_0, arg_0_1) arg_1_0 = this.Mem2 (arg_0_0, arg_0_1) (arg_1_0)
member this.Mem3 ((arg_0_0, arg_0_1)) arg_1_0 = this.Mem3 (arg_0_0, arg_0_1) (arg_1_0)
member this.Mem4 ((arg_0_0, arg_0_1)) ((arg_1_0, arg_1_1)) =
this.Mem4 (arg_0_0, arg_0_1) (arg_1_0, arg_1_1)
member this.Mem5 (arg_0_0, arg_0_1) ((arg_1_0, arg_1_1)) =
this.Mem5 (arg_0_0, arg_0_1) (arg_1_0, arg_1_1)
member this.Mem6 (arg_0_0, arg_0_1) (arg_1_0, arg_1_1) =
this.Mem6 (arg_0_0, arg_0_1) (arg_1_0, arg_1_1)
namespace SomeNamespace.CapturingMock
open System
open WoofWare.Myriad.Plugins
/// Mock record type for an interface
type internal TypeWithInterfaceMock =
{
/// Implementation of IDisposable.Dispose
Dispose : unit -> unit
Mem1 : string option -> string[] Async
Mem1_Calls : ResizeArray<string option>
Mem2 : unit -> string[] Async
Mem2_Calls : ResizeArray<unit>
}
/// An implementation where every non-unit method throws.
static member Empty : TypeWithInterfaceMock =
{
Dispose = (fun () -> ())
Mem1 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem1"))
Mem2 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem2"))
Mem1_Calls = ResizeArray ()
Mem2_Calls = ResizeArray ()
}
interface TypeWithInterface with
member this.Mem1 arg_0_0 = this.Mem1 (arg_0_0)
member this.Mem2 () = this.Mem2 (())
interface System.IDisposable with
member this.Dispose () : unit = this.Dispose ()
namespace SomeNamespace.CapturingMock
open System
open WoofWare.Myriad.Plugins
/// Mock record type for an interface
type internal TypeWithPropertiesMock =
{
/// Implementation of IDisposable.Dispose
Dispose : unit -> unit
Prop1 : unit -> int
Prop1_Calls : ResizeArray<unit>
Prop2 : unit -> unit Async
Prop2_Calls : ResizeArray<unit>
Mem1 : string option -> string[] Async
Mem1_Calls : ResizeArray<string option>
}
/// An implementation where every non-unit method throws.
static member Empty : TypeWithPropertiesMock =
{
Dispose = (fun () -> ())
Prop1 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Prop1"))
Prop2 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Prop2"))
Mem1 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem1"))
Prop1_Calls = ResizeArray ()
Prop2_Calls = ResizeArray ()
Mem1_Calls = ResizeArray ()
}
interface TypeWithProperties with
member this.Mem1 arg_0_0 = this.Mem1 (arg_0_0)
member this.Prop1 = this.Prop1 ()
member this.Prop2 = this.Prop2 ()
interface System.IDisposable with
member this.Dispose () : unit = this.Dispose ()

View File

@@ -0,0 +1,285 @@
//------------------------------------------------------------------------------
// This code was generated by myriad.
// Changes to this file will be lost when the code is regenerated.
//------------------------------------------------------------------------------
namespace SomeNamespace.CapturingMock
open System
/// Mock record type for an interface
type internal PublicTypeNoAttrMock =
{
Mem1 : string * int -> string list
Mem1_Calls : ResizeArray<string * int>
Mem2 : string -> int
Mem2_Calls : ResizeArray<string>
Mem3 : int * option<System.Threading.CancellationToken> -> string
Mem3_Calls : ResizeArray<int * System.Threading.CancellationToken>
}
/// An implementation where every non-unit method throws.
static member Empty : PublicTypeNoAttrMock =
{
Mem1 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem1"))
Mem2 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem2"))
Mem3 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem3"))
Mem1_Calls = ResizeArray ()
Mem2_Calls = ResizeArray ()
Mem3_Calls = ResizeArray ()
}
interface IPublicTypeNoAttr with
member this.Mem1 (arg_0_0, arg_0_1) = this.Mem1 (arg_0_0, arg_0_1)
member this.Mem2 arg_0_0 = this.Mem2 (arg_0_0)
member this.Mem3 (arg_0_0, arg_0_1) = this.Mem3 (arg_0_0, arg_0_1)
namespace SomeNamespace.CapturingMock
open System
/// Mock record type for an interface
type public PublicTypeInternalFalseNoAttrMock =
{
Mem1 : string * int -> string list
Mem1_Calls : ResizeArray<string * int>
Mem2 : string -> int
Mem2_Calls : ResizeArray<string>
Mem3 : int * option<System.Threading.CancellationToken> -> string
Mem3_Calls : ResizeArray<int * System.Threading.CancellationToken>
}
/// An implementation where every non-unit method throws.
static member Empty : PublicTypeInternalFalseNoAttrMock =
{
Mem1 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem1"))
Mem2 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem2"))
Mem3 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem3"))
Mem1_Calls = ResizeArray ()
Mem2_Calls = ResizeArray ()
Mem3_Calls = ResizeArray ()
}
interface IPublicTypeInternalFalseNoAttr with
member this.Mem1 (arg_0_0, arg_0_1) = this.Mem1 (arg_0_0, arg_0_1)
member this.Mem2 arg_0_0 = this.Mem2 (arg_0_0)
member this.Mem3 (arg_0_0, arg_0_1) = this.Mem3 (arg_0_0, arg_0_1)
namespace SomeNamespace.CapturingMock
open System
/// Mock record type for an interface
type internal InternalTypeNoAttrMock =
{
Mem1 : string * int -> unit
Mem1_Calls : ResizeArray<string * int>
Mem2 : string -> int
Mem2_Calls : ResizeArray<string>
}
/// An implementation where every non-unit method throws.
static member Empty : InternalTypeNoAttrMock =
{
Mem1 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem1"))
Mem2 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem2"))
Mem1_Calls = ResizeArray ()
Mem2_Calls = ResizeArray ()
}
interface InternalTypeNoAttr with
member this.Mem1 (arg_0_0, arg_0_1) = this.Mem1 (arg_0_0, arg_0_1)
member this.Mem2 arg_0_0 = this.Mem2 (arg_0_0)
namespace SomeNamespace.CapturingMock
open System
/// Mock record type for an interface
type private PrivateTypeNoAttrMock =
{
Mem1 : string * int -> unit
Mem1_Calls : ResizeArray<string * int>
Mem2 : string -> int
Mem2_Calls : ResizeArray<string>
}
/// An implementation where every non-unit method throws.
static member Empty : PrivateTypeNoAttrMock =
{
Mem1 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem1"))
Mem2 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem2"))
Mem1_Calls = ResizeArray ()
Mem2_Calls = ResizeArray ()
}
interface PrivateTypeNoAttr with
member this.Mem1 (arg_0_0, arg_0_1) = this.Mem1 (arg_0_0, arg_0_1)
member this.Mem2 arg_0_0 = this.Mem2 (arg_0_0)
namespace SomeNamespace.CapturingMock
open System
/// Mock record type for an interface
type private PrivateTypeInternalFalseNoAttrMock =
{
Mem1 : string * int -> unit
Mem1_Calls : ResizeArray<string * int>
Mem2 : string -> int
Mem2_Calls : ResizeArray<string>
}
/// An implementation where every non-unit method throws.
static member Empty : PrivateTypeInternalFalseNoAttrMock =
{
Mem1 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem1"))
Mem2 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem2"))
Mem1_Calls = ResizeArray ()
Mem2_Calls = ResizeArray ()
}
interface PrivateTypeInternalFalseNoAttr with
member this.Mem1 (arg_0_0, arg_0_1) = this.Mem1 (arg_0_0, arg_0_1)
member this.Mem2 arg_0_0 = this.Mem2 (arg_0_0)
namespace SomeNamespace.CapturingMock
open System
/// Mock record type for an interface
type internal VeryPublicTypeNoAttrMock<'a, 'b> =
{
Mem1 : 'a -> 'b
Mem1_Calls : ResizeArray<'a>
}
/// An implementation where every non-unit method throws.
static member Empty () : VeryPublicTypeNoAttrMock<'a, 'b> =
{
Mem1 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem1"))
Mem1_Calls = ResizeArray ()
}
interface VeryPublicTypeNoAttr<'a, 'b> with
member this.Mem1 arg_0_0 = this.Mem1 (arg_0_0)
namespace SomeNamespace.CapturingMock
open System
/// 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
}
/// Mock record type for an interface
type internal CurriedNoAttrMock<'a> =
{
Mem1 : int -> 'a -> string
Mem1_Calls : ResizeArray<Mem1Call<'a>>
Mem2 : int * string -> 'a -> string
Mem2_Calls : ResizeArray<Mem2Call<'a>>
Mem3 : (int * string) -> 'a -> string
Mem3_Calls : ResizeArray<Mem3Call<'a>>
Mem4 : (int * string) -> ('a * int) -> string
Mem4_Calls : ResizeArray<Mem4Call<'a>>
Mem5 : int * string -> ('a * int) -> string
Mem5_Calls : ResizeArray<Mem5Call<'a>>
Mem6 : int * string -> 'a * int -> string
Mem6_Calls : ResizeArray<Mem6Call<'a>>
}
/// An implementation where every non-unit method throws.
static member Empty () : CurriedNoAttrMock<'a> =
{
Mem1 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem1"))
Mem2 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem2"))
Mem3 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem3"))
Mem4 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem4"))
Mem5 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem5"))
Mem6 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem6"))
Mem1_Calls = ResizeArray ()
Mem2_Calls = ResizeArray ()
Mem3_Calls = ResizeArray ()
Mem4_Calls = ResizeArray ()
Mem5_Calls = ResizeArray ()
Mem6_Calls = ResizeArray ()
}
interface CurriedNoAttr<'a> with
member this.Mem1 arg_0_0 arg_1_0 = this.Mem1 (arg_0_0) (arg_1_0)
member this.Mem2 (arg_0_0, arg_0_1) arg_1_0 = this.Mem2 (arg_0_0, arg_0_1) (arg_1_0)
member this.Mem3 ((arg_0_0, arg_0_1)) arg_1_0 = this.Mem3 (arg_0_0, arg_0_1) (arg_1_0)
member this.Mem4 ((arg_0_0, arg_0_1)) ((arg_1_0, arg_1_1)) =
this.Mem4 (arg_0_0, arg_0_1) (arg_1_0, arg_1_1)
member this.Mem5 (arg_0_0, arg_0_1) ((arg_1_0, arg_1_1)) =
this.Mem5 (arg_0_0, arg_0_1) (arg_1_0, arg_1_1)
member this.Mem6 (arg_0_0, arg_0_1) (arg_1_0, arg_1_1) =
this.Mem6 (arg_0_0, arg_0_1) (arg_1_0, arg_1_1)
namespace SomeNamespace.CapturingMock
open System
/// Mock record type for an interface
type internal TypeWithInterfaceNoAttrMock =
{
/// Implementation of IDisposable.Dispose
Dispose : unit -> unit
Mem1 : string option -> string[] Async
Mem1_Calls : ResizeArray<string option>
Mem2 : unit -> string[] Async
Mem2_Calls : ResizeArray<unit>
}
/// An implementation where every non-unit method throws.
static member Empty : TypeWithInterfaceNoAttrMock =
{
Dispose = (fun () -> ())
Mem1 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem1"))
Mem2 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem2"))
Mem1_Calls = ResizeArray ()
Mem2_Calls = ResizeArray ()
}
interface TypeWithInterfaceNoAttr with
member this.Mem1 arg_0_0 = this.Mem1 (arg_0_0)
member this.Mem2 () = this.Mem2 (())
interface System.IDisposable with
member this.Dispose () : unit = this.Dispose ()

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

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

@@ -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.38-alpha" PrivateAssets="all" />
<SourceLinkGitHubHost Include="github.com" ContentUrl="https://raw.githubusercontent.com" />
</ItemGroup>
<PropertyGroup Condition="'$(GITHUB_ACTION)' != ''">
<ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>

View File

@@ -647,3 +647,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,22 @@ 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 captures all calls made to it, which can then be accessed later.
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

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

@@ -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,49 @@
namespace WoofWare.Myriad.Plugins.Test
open System
open SomeNamespace.CapturingMock
open NUnit.Framework
open FsUnitTyped
[<TestFixture>]
module TestMockGenerator =
[<Test>]
let ``Example of use: IPublicType`` () =
let mock : IPublicType =
{ PublicTypeMock.Empty with
Mem1 = fun (s, count) -> List.replicate count s
}
:> _
let _ =
Assert.Throws<NotImplementedException> (fun () -> mock.Mem2 "hi" |> ignore<int>)
mock.Mem1 ("hi", 3) |> shouldEqual [ "hi" ; "hi" ; "hi" ]
[<Test>]
let ``Example of use: curried args`` () =
let mock : Curried<_> =
{ CurriedMock.Empty () with
Mem1 = fun i c -> Array.replicate i c |> String
Mem2 = fun (i, s) c -> String.concat $"%c{c}" (List.replicate i s)
Mem3 = fun (i, s) c -> String.concat $"%c{c}" (List.replicate i s)
}
:> _
mock.Mem1 3 'a' |> shouldEqual "aaa"
mock.Mem2 (3, "hi") 'a' |> shouldEqual "hiahiahi"
mock.Mem3 (3, "hi") 'a' |> shouldEqual "hiahiahi"
[<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

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

@@ -6,7 +6,7 @@ open NUnit.Framework
open FsUnitTyped
[<TestFixture>]
module TestMockGeneratorNoAttr =
module TestCapturingMockGeneratorNoAttr =
[<Test>]
let ``Example of use: IPublicType`` () =

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" />
@@ -27,7 +28,7 @@
<Compile Include="TestHttpClient\TestVaultClient.fs" />
<Compile Include="TestHttpClient\TestVariableHeader.fs" />
<Compile Include="TestMockGenerator\TestMockGenerator.fs" />
<Compile Include="TestMockGenerator\TestMockGeneratorNoAttr.fs" />
<Compile Include="TestMockGenerator\TestCapturingMockGeneratorNoAttr.fs" />
<Compile Include="TestJsonSerialize\TestJsonSerde.fs" />
<Compile Include="TestCataGenerator\TestCataGenerator.fs" />
<Compile Include="TestCataGenerator\TestDirectory.fs" />
@@ -36,24 +37,39 @@
<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="WoofWare.Expect" Version="0.4.2" />
<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>
<ItemGroup>
<Compile Include="TestCapturingMockGenerator\TestCapturingMockGenerator.fs" />
<Compile Include="TestCapturingMockGenerator\TestCapturingMockGeneratorNoAttr.fs" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,561 @@
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 = $"arg%i{i}" |> Ident.create |> Some
Attrs = []
Type =
tupledArg.Args
|> List.map (fun pi -> 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.app "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)
(generics : SynTyparDecls option)
(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 _.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
=
let fields =
interfaceType.Members
|> List.map (constructMember spec interfaceType.Generics)
|> 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
f, extraType, fieldName
)
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
|> List.map (fun (_, _, fieldName) -> SynLongIdent.createS fieldName, failwithNotImplemented fieldName)
let callsArrays =
fields
|> List.map (fun (_field, extraType, fieldName) ->
let name = SynLongIdent.createS $"{fieldName}_Calls"
let init =
match extraType with
| CallField.Original _ ->
SynExpr.createIdent "ResizeArray" |> SynExpr.applyTo (SynExpr.CreateConst ())
| CallField.ArgsObject _ ->
SynExpr.createIdent "ResizeArray" |> SynExpr.applyTo (SynExpr.CreateConst ())
name, init
)
interfaceExtras @ originalMembers @ callsArrays
let staticMemberEmpty =
SynBinding.basic
[ Ident.create "Empty" ]
(if interfaceType.Generics.IsNone then
[]
else
[ 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
|> List.collect (fun (field, callType, fieldName) ->
let callField =
match callType with
| CallField.Original ty ->
{
Attrs = []
Ident = Some (fieldName + "_Calls" |> Ident.create)
Type = SynType.app "ResizeArray" [ ty ]
}
|> SynField.make
| CallField.ArgsObject (name, _, generics) ->
{
Attrs = []
Ident = Some (fieldName + "_Calls" |> Ident.create)
Type =
match generics with
| None -> SynType.named name.idText
| Some generics ->
generics.TyparDecls
|> List.map (fun (SynTyparDecl.SynTyparDecl (_, typar)) -> SynType.var typar)
|> SynType.app name.idText
|> List.singleton
|> SynType.app "ResizeArray"
}
|> SynField.make
[ field ; callField ]
)
extras @ nonExtras
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 =
let tuples =
memberInfo.Args
|> List.mapi (fun i args ->
args.Args
|> List.mapi (fun j arg ->
match arg.Type with
| UnitType -> SynExpr.CreateConst ()
| _ -> SynExpr.createIdent $"arg_%i{i}_%i{j}"
)
|> SynExpr.tuple
)
match tuples |> List.rev with
| [] -> failwith "expected args but got none"
| last :: rest ->
(last, rest)
||> List.fold SynExpr.applyTo
|> SynExpr.applyFunction (
SynExpr.createLongIdent' [ Ident.create "this" ; memberInfo.Identifier ]
)
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 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 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
SynModuleDecl.Types (
[
for _, field, _ in fields do
match field with
| CallField.Original _ -> ()
| CallField.ArgsObject (_, callType, _) -> yield callType
yield 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 typeDecl = createType spec name interfaceType docString
[ yield! opens |> List.map SynModuleDecl.openAny ; 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

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

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

@@ -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": 1757746433,
"narHash": "sha256-fEvTiU4s9lWgW7mYEU/1QUPirgkn+odUBTaindgiziY=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "41da1e3ea8e23e094e5e3eeb1e6b830468a7399e",
"rev": "6d7ec06d6868ac6d94c371458fc2391ded9ff13d",
"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,8 +46,8 @@
},
{
"pname": "FSharp.Core",
"version": "9.0.202",
"hash": "sha256-64Gub0qemmCoMa1tDus6TeTuB1+5sHfE6KD2j4o84mA="
"version": "9.0.303",
"hash": "sha256-AxR6wqodeU23KOTgkUfIgbavgbcSuzD4UBP+tiFydgA="
},
{
"pname": "FSharp.SystemTextJson",
@@ -51,8 +56,8 @@
},
{
"pname": "FsUnit",
"version": "7.0.1",
"hash": "sha256-K85CIdxMeFSHEKZk6heIXp/oFjWAn7dBILKrw49pJUY="
"version": "7.1.1",
"hash": "sha256-UMCEGKxQ4ytjmPuVpiNaAPbi3RQH9gqa61JJIUS/6hg="
},
{
"pname": "Microsoft.ApplicationInsights",
@@ -149,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",
@@ -309,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",
@@ -349,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",
@@ -364,11 +359,6 @@
"version": "4.4.0",
"hash": "sha256-Ri53QmFX8I8UH0x4PikQ1ZA07ZSnBUXStd5rBfGWFOE="
},
{
"pname": "System.Security.Principal.Windows",
"version": "4.5.0",
"hash": "sha256-BkUYNguz0e4NJp1kkW7aJBn3dyH9STwB5N8XqnlCsmY="
},
{
"pname": "System.Text.Json",
"version": "6.0.10",
@@ -391,8 +381,8 @@
},
{
"pname": "WoofWare.Expect",
"version": "0.4.2",
"hash": "sha256-CaVcj9Fo0VSMgfKIukM9WHGufPWHDqMO1D4VYVdJKJk="
"version": "0.8.2",
"hash": "sha256-iqt4FPkUr24/4dnRfh7P5nPNNlaPzaItP6/yrGRrQyo="
},
{
"pname": "WoofWare.Whippet.Fantomas",