Compare commits

...

14 Commits

Author SHA1 Message Date
dependabot[bot]
072169e4e3 Bump ApiSurface from 4.0.36 to 4.0.39 (#132)
* Bump ApiSurface from 4.0.36 to 4.0.39

Bumps [ApiSurface](https://github.com/G-Research/ApiSurface) from 4.0.36 to 4.0.39.
- [Release notes](https://github.com/G-Research/ApiSurface/releases)
- [Commits](https://github.com/G-Research/ApiSurface/compare/ApiSurface.4.0.36...ApiSurface.4.0.39)

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

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

* Bump 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>
2024-05-06 13:59:29 +01:00
Patrick Stevens
91136a25ab Enable query params in Get request endpoint (#131) 2024-04-30 19:03:20 +00:00
Patrick Stevens
c51038448a Be more forgiving about the source of the attributes (#129) 2024-04-29 20:46:14 +01:00
Patrick Stevens
09780efb07 Add RestEase attributes (#128) 2024-04-29 17:51:26 +01:00
dependabot[bot]
f562271c12 Bump fantomas from 6.3.3 to 6.3.4 (#126)
* Bump ApiSurface from 4.0.33 to 4.0.36

Bumps [ApiSurface](https://github.com/G-Research/ApiSurface) from 4.0.33 to 4.0.36.
- [Release notes](https://github.com/G-Research/ApiSurface/releases)
- [Commits](https://github.com/G-Research/ApiSurface/compare/ApiSurface.4.0.33...ApiSurface.4.0.36)

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

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

* Bump fantomas from 6.3.3 to 6.3.4

Bumps [fantomas](https://github.com/fsprojects/fantomas) from 6.3.3 to 6.3.4.
- [Release notes](https://github.com/fsprojects/fantomas/releases)
- [Changelog](https://github.com/fsprojects/fantomas/blob/main/CHANGELOG.md)
- [Commits](https://github.com/fsprojects/fantomas/compare/v6.3.3...v6.3.4)

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

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

* Drive-by

* Fix 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>
2024-04-22 23:36:02 +01:00
Patrick Stevens
e3081c3136 Deal with unit type in generated mock (#124) 2024-04-17 08:44:31 +01:00
Patrick Stevens
232d2ba5ec Relax arg checking strictness (#123) 2024-04-16 22:47:06 +01:00
Patrick Stevens
f7458f521e Track inheritance in GenerateMock (#122) 2024-04-16 22:23:32 +01:00
dependabot[bot]
bfc25a672b Bump fantomas from 6.3.0 to 6.3.3 (#120)
* Bump fantomas from 6.3.0 to 6.3.3

Bumps [fantomas](https://github.com/fsprojects/fantomas) from 6.3.0 to 6.3.3.
- [Release notes](https://github.com/fsprojects/fantomas/releases)
- [Changelog](https://github.com/fsprojects/fantomas/blob/main/CHANGELOG.md)
- [Commits](https://github.com/fsprojects/fantomas/compare/v6.3.0...v6.3.3)

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

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

* Fix dep

---------

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>
2024-04-15 21:23:09 +00:00
dependabot[bot]
af7fcb3028 Bump fantomas from 6.3.0-alpha-008 to 6.3.0 (#118)
* Bump fantomas from 6.3.0-alpha-008 to 6.3.0

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

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-19 23:07:38 +00:00
dependabot[bot]
91853b1fff Bump cachix/install-nix-action from 25 to 26 (#116) 2024-03-11 10:10:04 +00:00
dependabot[bot]
1144e93c1c Bump ApiSurface from 4.0.30 to 4.0.33 (#115)
* Bump NUnit from 3.13.3 to 4.1.0

Bumps [NUnit](https://github.com/nunit/nunit) from 3.13.3 to 4.1.0.
- [Release notes](https://github.com/nunit/nunit/releases)
- [Changelog](https://github.com/nunit/nunit/blob/master/CHANGES.md)
- [Commits](https://github.com/nunit/nunit/compare/v3.13.3...4.1.0)

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

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

* Fix deps

* Bump NUnit from 4.0.1 to 4.1.0

Bumps [NUnit](https://github.com/nunit/nunit) from 4.0.1 to 4.1.0.
- [Release notes](https://github.com/nunit/nunit/releases)
- [Changelog](https://github.com/nunit/nunit/blob/master/CHANGES.md)
- [Commits](https://github.com/nunit/nunit/compare/v4.0.1...4.1.0)

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

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

* Bump fantomas from 6.3.0-alpha-007 to 6.3.0-alpha-008

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

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

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

* Bump ApiSurface from 4.0.30 to 4.0.33

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

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

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

* Bump lots of 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 <patrick+github@patrickstevens.co.uk>
2024-03-04 19:43:53 +00:00
dependabot[bot]
d899d77ae2 Bump NUnit from 3.13.3 to 4.1.0 (#110)
* Bump NUnit from 3.13.3 to 4.1.0

Bumps [NUnit](https://github.com/nunit/nunit) from 3.13.3 to 4.1.0.
- [Release notes](https://github.com/nunit/nunit/releases)
- [Changelog](https://github.com/nunit/nunit/blob/master/CHANGES.md)
- [Commits](https://github.com/nunit/nunit/compare/v3.13.3...4.1.0)

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

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

* Fix 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 <patrick+github@patrickstevens.co.uk>
2024-02-26 19:00:19 +00:00
Patrick Stevens
a2ad430b2f Fix end-of-line config (#109) 2024-02-26 18:33:29 +00:00
21 changed files with 593 additions and 80 deletions

View File

@@ -3,7 +3,7 @@
"isRoot": true,
"tools": {
"fantomas": {
"version": "6.3.0-alpha-007",
"version": "6.3.4",
"commands": [
"fantomas"
]

View File

@@ -2,7 +2,6 @@ root=true
[*]
charset=utf-8
end_of_line=crlf
trim_trailing_whitespace=true
insert_final_newline=true
indent_style=space

10
.gitattributes vendored
View File

@@ -1,5 +1,5 @@
* eol=auto
*.sh text eol=lf
*.yaml text
*.nix text eol=lf
hooks/pre-push text eol=lf
* eol=auto
*.sh text eol=lf
*.yaml text
*.nix text eol=lf
hooks/pre-push text eol=lf

View File

@@ -28,7 +28,7 @@ jobs:
with:
fetch-depth: 0 # so that NerdBank.GitVersioning has access to history
- name: Install Nix
uses: cachix/install-nix-action@v25
uses: cachix/install-nix-action@v26
with:
extra_nix_config: |
access-tokens = github.com=${{ secrets.GITHUB_TOKEN }}
@@ -49,7 +49,7 @@ jobs:
with:
fetch-depth: 0 # so that NerdBank.GitVersioning has access to history
- name: Install Nix
uses: cachix/install-nix-action@v25
uses: cachix/install-nix-action@v26
with:
extra_nix_config: |
access-tokens = github.com=${{ secrets.GITHUB_TOKEN }}
@@ -66,7 +66,7 @@ jobs:
- name: Checkout
uses: actions/checkout@v4
- name: Install Nix
uses: cachix/install-nix-action@v25
uses: cachix/install-nix-action@v26
with:
extra_nix_config: |
access-tokens = github.com=${{ secrets.GITHUB_TOKEN }}
@@ -79,7 +79,7 @@ jobs:
- name: Checkout
uses: actions/checkout@v4
- name: Install Nix
uses: cachix/install-nix-action@v25
uses: cachix/install-nix-action@v26
with:
extra_nix_config: |
access-tokens = github.com=${{ secrets.GITHUB_TOKEN }}
@@ -92,7 +92,7 @@ jobs:
- name: Checkout
uses: actions/checkout@v4
- name: Install Nix
uses: cachix/install-nix-action@v25
uses: cachix/install-nix-action@v26
with:
extra_nix_config: |
access-tokens = github.com=${{ secrets.GITHUB_TOKEN }}
@@ -105,7 +105,7 @@ jobs:
steps:
- uses: actions/checkout@master
- name: Install Nix
uses: cachix/install-nix-action@v25
uses: cachix/install-nix-action@v26
with:
extra_nix_config: |
access-tokens = github.com=${{ secrets.GITHUB_TOKEN }}
@@ -118,7 +118,7 @@ jobs:
steps:
- uses: actions/checkout@master
- name: Install Nix
uses: cachix/install-nix-action@v25
uses: cachix/install-nix-action@v26
with:
extra_nix_config: |
access-tokens = github.com=${{ secrets.GITHUB_TOKEN }}
@@ -132,7 +132,7 @@ jobs:
with:
fetch-depth: 0 # so that NerdBank.GitVersioning has access to history
- name: Install Nix
uses: cachix/install-nix-action@v25
uses: cachix/install-nix-action@v26
with:
extra_nix_config: |
access-tokens = github.com=${{ secrets.GITHUB_TOKEN }}
@@ -188,7 +188,7 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Install Nix
uses: cachix/install-nix-action@v25
uses: cachix/install-nix-action@v26
with:
extra_nix_config: |
access-tokens = github.com=${{ secrets.GITHUB_TOKEN }}

View File

@@ -5,6 +5,7 @@
namespace SomeNamespace
open System
open WoofWare.Myriad.Plugins
/// Mock record type for an interface
@@ -25,10 +26,11 @@ type internal PublicTypeMock =
interface IPublicType with
member this.Mem1 (arg_0_0, arg_0_1) = this.Mem1 (arg_0_0, arg_0_1)
member this.Mem2 (arg_0_0) = this.Mem2 (arg_0_0)
member this.Mem2 arg_0_0 = this.Mem2 (arg_0_0)
member this.Mem3 (arg_0_0, arg_0_1) = this.Mem3 (arg_0_0, arg_0_1)
namespace SomeNamespace
open System
open WoofWare.Myriad.Plugins
/// Mock record type for an interface
@@ -49,10 +51,11 @@ type public PublicTypeInternalFalseMock =
interface IPublicTypeInternalFalse with
member this.Mem1 (arg_0_0, arg_0_1) = this.Mem1 (arg_0_0, arg_0_1)
member this.Mem2 (arg_0_0) = this.Mem2 (arg_0_0)
member this.Mem2 arg_0_0 = this.Mem2 (arg_0_0)
member this.Mem3 (arg_0_0, arg_0_1) = this.Mem3 (arg_0_0, arg_0_1)
namespace SomeNamespace
open System
open WoofWare.Myriad.Plugins
/// Mock record type for an interface
@@ -71,9 +74,10 @@ type internal InternalTypeMock =
interface InternalType with
member this.Mem1 (arg_0_0, arg_0_1) = this.Mem1 (arg_0_0, arg_0_1)
member this.Mem2 (arg_0_0) = this.Mem2 (arg_0_0)
member this.Mem2 arg_0_0 = this.Mem2 (arg_0_0)
namespace SomeNamespace
open System
open WoofWare.Myriad.Plugins
/// Mock record type for an interface
@@ -92,9 +96,10 @@ type private PrivateTypeMock =
interface PrivateType with
member this.Mem1 (arg_0_0, arg_0_1) = this.Mem1 (arg_0_0, arg_0_1)
member this.Mem2 (arg_0_0) = this.Mem2 (arg_0_0)
member this.Mem2 arg_0_0 = this.Mem2 (arg_0_0)
namespace SomeNamespace
open System
open WoofWare.Myriad.Plugins
/// Mock record type for an interface
@@ -113,9 +118,10 @@ type private PrivateTypeInternalFalseMock =
interface PrivateTypeInternalFalse with
member this.Mem1 (arg_0_0, arg_0_1) = this.Mem1 (arg_0_0, arg_0_1)
member this.Mem2 (arg_0_0) = this.Mem2 (arg_0_0)
member this.Mem2 arg_0_0 = this.Mem2 (arg_0_0)
namespace SomeNamespace
open System
open WoofWare.Myriad.Plugins
/// Mock record type for an interface
@@ -131,9 +137,10 @@ type internal VeryPublicTypeMock<'a, 'b> =
}
interface VeryPublicType<'a, 'b> with
member this.Mem1 (arg_0_0) = this.Mem1 (arg_0_0)
member this.Mem1 arg_0_0 = this.Mem1 (arg_0_0)
namespace SomeNamespace
open System
open WoofWare.Myriad.Plugins
/// Mock record type for an interface
@@ -159,9 +166,9 @@ type internal CurriedMock<'a> =
}
interface Curried<'a> with
member this.Mem1 (arg_0_0) (arg_1_0) = this.Mem1 (arg_0_0) (arg_1_0)
member this.Mem2 (arg_0_0, arg_0_1) (arg_1_0) = this.Mem2 (arg_0_0, arg_0_1) (arg_1_0)
member this.Mem3 ((arg_0_0, arg_0_1)) (arg_1_0) = this.Mem3 (arg_0_0, arg_0_1) (arg_1_0)
member this.Mem1 arg_0_0 arg_1_0 = this.Mem1 (arg_0_0) (arg_1_0)
member this.Mem2 (arg_0_0, arg_0_1) arg_1_0 = this.Mem2 (arg_0_0, arg_0_1) (arg_1_0)
member this.Mem3 ((arg_0_0, arg_0_1)) arg_1_0 = this.Mem3 (arg_0_0, arg_0_1) (arg_1_0)
member this.Mem4 ((arg_0_0, arg_0_1)) ((arg_1_0, arg_1_1)) =
this.Mem4 (arg_0_0, arg_0_1) (arg_1_0, arg_1_1)
@@ -171,3 +178,31 @@ type internal CurriedMock<'a> =
member this.Mem6 (arg_0_0, arg_0_1) (arg_1_0, arg_1_1) =
this.Mem6 (arg_0_0, arg_0_1) (arg_1_0, arg_1_1)
namespace SomeNamespace
open System
open WoofWare.Myriad.Plugins
/// Mock record type for an interface
type internal TypeWithInterfaceMock =
{
/// Implementation of IDisposable.Dispose
Dispose : unit -> unit
Mem1 : string option -> string[] Async
Mem2 : unit -> string[] Async
}
/// An implementation where every method throws.
static member Empty : TypeWithInterfaceMock =
{
Dispose = (fun _ -> ())
Mem1 = (fun x -> raise (System.NotImplementedException "Unimplemented mock function"))
Mem2 = (fun x -> raise (System.NotImplementedException "Unimplemented mock function"))
}
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 ()

View File

@@ -288,7 +288,52 @@ module PureGymApi =
| v -> v),
System.Uri (
("/v2/gymSessions/member"
+ "?fromDate="
+ (if "/v2/gymSessions/member".IndexOf (char 63) > 0 then
"&"
else
"?")
+ "fromDate="
+ ((fromDate.ToString "yyyy-MM-dd") |> System.Web.HttpUtility.UrlEncode)
+ "&toDate="
+ ((toDate.ToString "yyyy-MM-dd") |> System.Web.HttpUtility.UrlEncode)),
System.UriKind.Relative
)
)
let httpMessage =
new System.Net.Http.HttpRequestMessage (
Method = System.Net.Http.HttpMethod.Get,
RequestUri = uri
)
let! response = client.SendAsync (httpMessage, ct) |> Async.AwaitTask
let response = response.EnsureSuccessStatusCode ()
let! responseStream = response.Content.ReadAsStreamAsync ct |> Async.AwaitTask
let! jsonNode =
System.Text.Json.Nodes.JsonNode.ParseAsync (responseStream, cancellationToken = ct)
|> Async.AwaitTask
return Sessions.jsonParse jsonNode
}
|> (fun a -> Async.StartAsTask (a, ?cancellationToken = ct))
member _.GetSessionsWithQuery (fromDate : DateOnly, toDate : DateOnly, ct : CancellationToken option) =
async {
let! ct = Async.CancellationToken
let uri =
System.Uri (
(match client.BaseAddress with
| null -> System.Uri "https://whatnot.com"
| v -> v),
System.Uri (
("/v2/gymSessions/member?foo=1"
+ (if "/v2/gymSessions/member?foo=1".IndexOf (char 63) > 0 then
"&"
else
"?")
+ "fromDate="
+ ((fromDate.ToString "yyyy-MM-dd") |> System.Web.HttpUtility.UrlEncode)
+ "&toDate="
+ ((toDate.ToString "yyyy-MM-dd") |> System.Web.HttpUtility.UrlEncode)),
@@ -1140,6 +1185,69 @@ module ApiWithHeaders =
member _.SomeHeader : string = someHeader ()
member _.SomeOtherHeader : int = someOtherHeader ()
member this.GetPathParam (parameter : string, ct : CancellationToken option) =
async {
let! ct = Async.CancellationToken
let uri =
System.Uri (
(match client.BaseAddress with
| null ->
raise (
System.ArgumentNullException (
nameof (client.BaseAddress),
"No base address was supplied on the type, and no BaseAddress was on the HttpClient."
)
)
| v -> v),
System.Uri (
"endpoint/{param}"
.Replace ("{param}", parameter.ToString () |> System.Web.HttpUtility.UrlEncode),
System.UriKind.Relative
)
)
let httpMessage =
new System.Net.Http.HttpRequestMessage (
Method = System.Net.Http.HttpMethod.Get,
RequestUri = uri
)
do httpMessage.Headers.Add ("X-Foo", this.SomeHeader.ToString ())
do httpMessage.Headers.Add ("Authorization", this.SomeOtherHeader.ToString ())
do httpMessage.Headers.Add ("Header-Name", "Header-Value")
let! response = client.SendAsync (httpMessage, ct) |> Async.AwaitTask
let response = response.EnsureSuccessStatusCode ()
let! responseString = response.Content.ReadAsStringAsync ct |> Async.AwaitTask
return responseString
}
|> (fun a -> Async.StartAsTask (a, ?cancellationToken = ct))
}
namespace PureGym
open System
open System.Threading
open System.Threading.Tasks
open System.IO
open System.Net
open System.Net.Http
open RestEase
/// Module for constructing a REST client.
[<CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix)>]
[<RequireQualifiedAccess>]
module ApiWithHeaders2 =
/// Create a REST client. The input functions will be re-evaluated on every HTTP request to obtain the required values for the corresponding header properties.
let make
(someHeader : unit -> string)
(someOtherHeader : unit -> int)
(client : System.Net.Http.HttpClient)
: IApiWithHeaders2
=
{ new IApiWithHeaders2 with
member _.SomeHeader : string = someHeader ()
member _.SomeOtherHeader : int = someOtherHeader ()
member this.GetPathParam (parameter : string, ct : CancellationToken option) =
async {
let! ct = Async.CancellationToken

View File

@@ -1,5 +1,6 @@
namespace SomeNamespace
open System
open WoofWare.Myriad.Plugins
[<GenerateMock>]
@@ -41,3 +42,9 @@ type Curried<'a> =
abstract Mem4 : (int * string) -> ('a * int) -> string
abstract Mem5 : x : int * string -> ('a * int) -> string
abstract Mem6 : int * string -> y : 'a * int -> string
[<GenerateMock>]
type TypeWithInterface =
inherit IDisposable
abstract Mem1 : string option -> string[] Async
abstract Mem2 : unit -> string[] Async

View File

@@ -38,6 +38,10 @@ type IPureGymApi =
abstract GetSessions :
[<Query>] fromDate : DateOnly * [<Query>] toDate : DateOnly * ?ct : CancellationToken -> Task<Sessions>
[<Get "/v2/gymSessions/member?foo=1">]
abstract GetSessionsWithQuery :
[<Query>] fromDate : DateOnly * [<Query>] toDate : DateOnly * ?ct : CancellationToken -> Task<Sessions>
// An example from RestEase's own docs
[<Post "users/new">]
abstract CreateUserString : [<Body>] user : string * ?ct : CancellationToken -> Task<string>
@@ -120,7 +124,8 @@ type internal IApiWithoutBaseAddress =
[<WoofWare.Myriad.Plugins.HttpClient>]
[<BasePath "foo">]
type IApiWithBasePath =
[<Get "endpoint/{param}">]
// Example where we use the bundled attributes rather than RestEase's
[<WoofWare.Myriad.Plugins.RestEase.Get "endpoint/{param}">]
abstract GetPathParam : [<Path "param">] parameter : string * ?cancellationToken : CancellationToken -> Task<string>
[<WoofWare.Myriad.Plugins.HttpClient>]
@@ -141,3 +146,16 @@ type IApiWithHeaders =
[<Get "endpoint/{param}">]
abstract GetPathParam : [<Path "param">] parameter : string * ?ct : CancellationToken -> Task<string>
[<WoofWare.Myriad.Plugins.HttpClient>]
[<WoofWare.Myriad.Plugins.RestEase.Header("Header-Name", "Header-Value")>]
type IApiWithHeaders2 =
[<WoofWare.Myriad.Plugins.RestEase.Header "X-Foo">]
abstract SomeHeader : string
[<WoofWare.Myriad.Plugins.RestEase.Header "Authorization">]
abstract SomeOtherHeader : int
[<Get "endpoint/{param}">]
abstract GetPathParam :
[<WoofWare.Myriad.Plugins.RestEase.Path "param">] parameter : string * ?ct : CancellationToken -> Task<string>

View File

@@ -0,0 +1,63 @@
namespace WoofWare.Myriad.Plugins
open System
/// Module containing duplicates of the supported RestEase attributes, in case you don't want
/// to take a dependency on RestEase.
[<RequireQualifiedAccess>]
module RestEase =
/// Indicates that a method represents an HTTP Get query to the specified endpoint.
type GetAttribute (path : string) =
inherit Attribute ()
/// Indicates that a method represents an HTTP Post query to the specified endpoint.
type PostAttribute (path : string) =
inherit Attribute ()
/// Indicates that a method represents an HTTP Delete query to the specified endpoint.
type DeleteAttribute (path : string) =
inherit Attribute ()
/// Indicates that a method represents an HTTP Head query to the specified endpoint.
type HeadAttribute (path : string) =
inherit Attribute ()
/// Indicates that a method represents an HTTP Options query to the specified endpoint.
type OptionsAttribute (path : string) =
inherit Attribute ()
/// Indicates that a method represents an HTTP Put query to the specified endpoint.
type PutAttribute (path : string) =
inherit Attribute ()
/// Indicates that a method represents an HTTP Patch query to the specified endpoint.
type PatchAttribute (path : string) =
inherit Attribute ()
/// Indicates that a method represents an HTTP Trace query to the specified endpoint.
type TraceAttribute (path : string) =
inherit Attribute ()
/// Indicates that this argument to a method is interpolated into the HTTP request at runtime
/// by setting a query parameter (with the given name) to the value of the annotated argument.
type QueryAttribute (paramName : string) =
inherit Attribute ()
/// Indicates that this interface represents a REST client which accesses an API whose paths are
/// all relative to the given address.
type BaseAddressAttribute (addr : string) =
inherit Attribute ()
/// Indicates that this interface member causes the interface to set a header with the given name,
/// whose value is obtained whenever required by a fresh call to the interface member.
type HeaderAttribute (header : string, value : string option) =
inherit Attribute ()
new (header : string) = HeaderAttribute (header, None)
new (header : string, value : string) = HeaderAttribute (header, Some value)
/// Indicates that this argument to a method is interpolated into the request path at runtime
/// by writing it into the templated string that specifies the HTTP query e.g. in the `[<Get "/foo/{template}">]`.
type PathAttribute (path : string option) =
inherit Attribute ()
new (path : string) = PathAttribute (Some path)
new () = PathAttribute None

View File

@@ -18,4 +18,33 @@ WoofWare.Myriad.Plugins.JsonSerializeAttribute..ctor [constructor]: unit
WoofWare.Myriad.Plugins.JsonSerializeAttribute.DefaultIsExtensionMethod [static property]: [read-only] bool
WoofWare.Myriad.Plugins.JsonSerializeAttribute.get_DefaultIsExtensionMethod [static method]: unit -> bool
WoofWare.Myriad.Plugins.RemoveOptionsAttribute inherit System.Attribute
WoofWare.Myriad.Plugins.RemoveOptionsAttribute..ctor [constructor]: unit
WoofWare.Myriad.Plugins.RemoveOptionsAttribute..ctor [constructor]: unit
WoofWare.Myriad.Plugins.RestEase inherit obj
WoofWare.Myriad.Plugins.RestEase+BaseAddressAttribute inherit System.Attribute
WoofWare.Myriad.Plugins.RestEase+BaseAddressAttribute..ctor [constructor]: string
WoofWare.Myriad.Plugins.RestEase+DeleteAttribute inherit System.Attribute
WoofWare.Myriad.Plugins.RestEase+DeleteAttribute..ctor [constructor]: string
WoofWare.Myriad.Plugins.RestEase+GetAttribute inherit System.Attribute
WoofWare.Myriad.Plugins.RestEase+GetAttribute..ctor [constructor]: string
WoofWare.Myriad.Plugins.RestEase+HeadAttribute inherit System.Attribute
WoofWare.Myriad.Plugins.RestEase+HeadAttribute..ctor [constructor]: string
WoofWare.Myriad.Plugins.RestEase+HeaderAttribute inherit System.Attribute
WoofWare.Myriad.Plugins.RestEase+HeaderAttribute..ctor [constructor]: (string, string option)
WoofWare.Myriad.Plugins.RestEase+HeaderAttribute..ctor [constructor]: (string, string)
WoofWare.Myriad.Plugins.RestEase+HeaderAttribute..ctor [constructor]: string
WoofWare.Myriad.Plugins.RestEase+OptionsAttribute inherit System.Attribute
WoofWare.Myriad.Plugins.RestEase+OptionsAttribute..ctor [constructor]: string
WoofWare.Myriad.Plugins.RestEase+PatchAttribute inherit System.Attribute
WoofWare.Myriad.Plugins.RestEase+PatchAttribute..ctor [constructor]: string
WoofWare.Myriad.Plugins.RestEase+PathAttribute inherit System.Attribute
WoofWare.Myriad.Plugins.RestEase+PathAttribute..ctor [constructor]: string
WoofWare.Myriad.Plugins.RestEase+PathAttribute..ctor [constructor]: string option
WoofWare.Myriad.Plugins.RestEase+PathAttribute..ctor [constructor]: unit
WoofWare.Myriad.Plugins.RestEase+PostAttribute inherit System.Attribute
WoofWare.Myriad.Plugins.RestEase+PostAttribute..ctor [constructor]: string
WoofWare.Myriad.Plugins.RestEase+PutAttribute inherit System.Attribute
WoofWare.Myriad.Plugins.RestEase+PutAttribute..ctor [constructor]: string
WoofWare.Myriad.Plugins.RestEase+QueryAttribute inherit System.Attribute
WoofWare.Myriad.Plugins.RestEase+QueryAttribute..ctor [constructor]: string
WoofWare.Myriad.Plugins.RestEase+TraceAttribute inherit System.Attribute
WoofWare.Myriad.Plugins.RestEase+TraceAttribute..ctor [constructor]: string

View File

@@ -11,11 +11,9 @@ module TestSurface =
[<Test>]
let ``Ensure API surface has not been modified`` () = ApiSurface.assertIdentical assembly
(*
[<Test>]
let ``Check version against remote`` () =
MonotonicVersion.validate assembly "WoofWare.Myriad.Plugins.Attributes"
*)
[<Test ; Explicit>]
let ``Update API surface`` () =

View File

@@ -12,9 +12,9 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="ApiSurface" Version="4.0.30" />
<PackageReference Include="ApiSurface" Version="4.0.39" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.9.0"/>
<PackageReference Include="NUnit" Version="3.13.3"/>
<PackageReference Include="NUnit" Version="4.1.0"/>
<PackageReference Include="NUnit3TestAdapter" Version="4.5.0"/>
</ItemGroup>

View File

@@ -19,6 +19,7 @@
<ItemGroup>
<Compile Include="Attributes.fs"/>
<Compile Include="RestEase.fs" />
<EmbeddedResource Include="version.json"/>
<EmbeddedResource Include="SurfaceBaseline.txt"/>
<None Include="..\README.md">

View File

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

View File

@@ -234,6 +234,33 @@ module TestPureGymRestApi =
api.GetSessions(startDate, endDate).Result |> shouldEqual expected
[<TestCaseSource(nameof sessionsCases)>]
let ``Test GetSessionsWithQuery``
(baseUri : Uri, (startDate : DateOnly, (endDate : DateOnly, (json : string, expected : Sessions))))
=
let proc (message : HttpRequestMessage) : HttpResponseMessage Async =
async {
message.Method |> shouldEqual HttpMethod.Get
// This one is specified as being absolute, in its attribute on the IPureGymApi type
let expectedUri =
let fromDate = dateOnlyToString startDate
let toDate = dateOnlyToString endDate
$"https://example.com/v2/gymSessions/member?foo=1&fromDate=%s{fromDate}&toDate=%s{toDate}"
message.RequestUri.ToString () |> shouldEqual expectedUri
let content = new StringContent (json)
let resp = new HttpResponseMessage (HttpStatusCode.OK)
resp.Content <- content
return resp
}
use client = HttpClientMock.make baseUri proc
let api = PureGymApi.make client
api.GetSessionsWithQuery(startDate, endDate).Result |> shouldEqual expected
[<Test>]
let ``URI example`` () =
let proc (message : HttpRequestMessage) : HttpResponseMessage Async =

View File

@@ -33,13 +33,12 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="ApiSurface" Version="4.0.30"/>
<PackageReference Include="ApiSurface" Version="4.0.39"/>
<PackageReference Include="FsCheck" Version="2.16.6"/>
<PackageReference Include="FsUnit" Version="6.0.0"/>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.9.0"/>
<PackageReference Include="NUnit" Version="4.0.1"/>
<PackageReference Include="NUnit" Version="4.1.0"/>
<PackageReference Include="NUnit3TestAdapter" Version="4.5.0"/>
<PackageReference Include="coverlet.collector" Version="6.0.0"/>
</ItemGroup>
<ItemGroup>

View File

@@ -54,6 +54,7 @@ type internal InterfaceType =
{
Attributes : SynAttribute list
Name : LongIdent
Inherits : SynType list
Members : MemberInfo list
Properties : PropertyInfo list
Generics : SynTyparDecls option
@@ -131,6 +132,11 @@ module internal AstHelper =
// TODO: consider Microsoft.FSharp.Option or whatever it is
| _ -> false
let isUnitIdent (ident : SynLongIdent) : bool =
match ident.LongIdent with
| [ i ] when System.String.Equals (i.idText, "unit", System.StringComparison.OrdinalIgnoreCase) -> true
| _ -> false
let isListIdent (ident : SynLongIdent) : bool =
match ident.LongIdent with
| [ i ] when System.String.Equals (i.idText, "list", System.StringComparison.OrdinalIgnoreCase) -> true
@@ -342,7 +348,18 @@ module internal AstHelper =
}
|> List.singleton
}
| _ -> failwith $"Unrecognised args in interface method declaration: %+A{args}"
| arg ->
{
HasParen = false
Args =
{
Attributes = []
IsOptional = false
Id = None
Type = arg
}
|> List.singleton
}
|> fun ty ->
{ ty with
HasParen = ty.HasParen || hasParen
@@ -386,22 +403,26 @@ module internal AstHelper =
let attrs = attrs |> List.collect (fun s -> s.Attributes)
let members, properties =
let members, inherits =
match synTypeDefnRepr with
| SynTypeDefnRepr.ObjectModel (_kind, members, _) ->
members
|> List.map (fun defn ->
match defn with
| SynMemberDefn.AbstractSlot (slotSig, flags, _, _) -> parseMember slotSig flags
| SynMemberDefn.AbstractSlot (slotSig, flags, _, _) -> Choice1Of2 (parseMember slotSig flags)
| SynMemberDefn.Inherit (baseType, _asIdent, _) -> Choice2Of2 baseType
| _ -> failwith $"Unrecognised member definition: %+A{defn}"
)
| _ -> failwith $"Unrecognised SynTypeDefnRepr for an interface type: %+A{synTypeDefnRepr}"
|> List.partitionChoice
let members, properties = members |> List.partitionChoice
{
Members = members
Properties = properties
Name = interfaceName
Inherits = inherits
Attributes = attrs
Generics = typars
Accessibility = accessibility
@@ -486,6 +507,11 @@ module internal SynTypePatterns =
Some innerType
| _ -> None
let (|UnitType|_|) (fieldType : SynType) : unit option =
match fieldType with
| SynType.LongIdent ident when AstHelper.isUnitIdent ident -> Some ()
| _ -> None
let (|ListType|_|) (fieldType : SynType) =
match fieldType with
| SynType.App (SynType.LongIdent ident, _, [ innerType ], _, _, _, _) when AstHelper.isListIdent ident ->

View File

@@ -1,8 +1,6 @@
namespace WoofWare.Myriad.Plugins
open System
open System.Net.Http
open System.Text
open Fantomas.FCS.Syntax
open Fantomas.FCS.SyntaxTrivia
open Fantomas.FCS.Xml
@@ -82,34 +80,50 @@ module internal HttpClientGenerator =
match attr.TypeName.AsString with
| "Get"
| "GetAttribute"
| "WoofWare.Myriad.Plugins.RestEase.Get"
| "WoofWare.Myriad.Plugins.RestEase.GetAttribute"
| "RestEase.Get"
| "RestEase.GetAttribute" -> Some (HttpMethod.Get, attr.ArgExpr)
| "Post"
| "PostAttribute"
| "WoofWare.Myriad.Plugins.RestEase.Post"
| "WoofWare.Myriad.Plugins.RestEase.PostAttribute"
| "RestEase.Post"
| "RestEase.PostAttribute" -> Some (HttpMethod.Post, attr.ArgExpr)
| "Put"
| "PutAttribute"
| "WoofWare.Myriad.Plugins.RestEase.Put"
| "WoofWare.Myriad.Plugins.RestEase.PutAttribute"
| "RestEase.Put"
| "RestEase.PutAttribute" -> Some (HttpMethod.Put, attr.ArgExpr)
| "Delete"
| "DeleteAttribute"
| "WoofWare.Myriad.Plugins.RestEase.Delete"
| "WoofWare.Myriad.Plugins.RestEase.DeleteAttribute"
| "RestEase.Delete"
| "RestEase.DeleteAttribute" -> Some (HttpMethod.Delete, attr.ArgExpr)
| "Head"
| "HeadAttribute"
| "WoofWare.Myriad.Plugins.RestEase.Head"
| "WoofWare.Myriad.Plugins.RestEase.HeadAttribute"
| "RestEase.Head"
| "RestEase.HeadAttribute" -> Some (HttpMethod.Head, attr.ArgExpr)
| "Options"
| "OptionsAttribute"
| "WoofWare.Myriad.Plugins.RestEase.Options"
| "WoofWare.Myriad.Plugins.RestEase.OptionsAttribute"
| "RestEase.Options"
| "RestEase.OptionsAttribute" -> Some (HttpMethod.Options, attr.ArgExpr)
| "Patch"
| "PatchAttribute"
| "WoofWare.Myriad.Plugins.RestEase.Patch"
| "WoofWare.Myriad.Plugins.RestEase.PatchAttribute"
| "RestEase.Patch"
| "RestEase.PatchAttribute" -> Some (HttpMethod.Patch, attr.ArgExpr)
| "Trace"
| "TraceAttribute"
| "WoofWare.Myriad.Plugins.RestEase.Trace"
| "WoofWare.Myriad.Plugins.RestEase.TraceAttribute"
| "RestEase.Trace"
| "RestEase.TraceAttribute" -> Some (HttpMethod.Trace, attr.ArgExpr)
| _ -> None
@@ -127,7 +141,8 @@ module internal HttpClientGenerator =
|> List.choose (fun attr ->
match attr.TypeName.AsString with
| "Header"
| "RestEase.Header" ->
| "RestEase.Header"
| "WoofWare.Myriad.Plugins.RestEase.Header" ->
match attr.ArgExpr with
| SynExpr.Paren (SynExpr.Tuple (_, [ v1 ; v2 ], _, _), _, _, _) ->
Some [ SynExpr.stripOptionalParen v1 ; SynExpr.stripOptionalParen v2 ]
@@ -293,6 +308,27 @@ module internal HttpClientGenerator =
| None -> failwith "Unable to get parameter variable name from anonymous parameter"
| Some id -> id
let urlSeparator =
// apparent Myriad bug: `IndexOf '?'` gets formatted as `IndexOf ?` which is clearly wrong
let questionMark =
SynExpr.CreateParen (
SynExpr.CreateApp (
SynExpr.CreateIdentString "char",
SynExpr.CreateConst (SynConst.Int32 63)
)
)
let containsQuestion =
info.UrlTemplate
|> SynExpr.callMethodArg "IndexOf" questionMark
|> SynExpr.greaterThanOrEqual (SynExpr.CreateConst (SynConst.Int32 0))
SynExpr.ifThenElse
containsQuestion
(SynExpr.CreateConst (SynConst.CreateString "?"))
(SynExpr.CreateConst (SynConst.CreateString "&"))
|> SynExpr.CreateParen
let prefix =
SynExpr.CreateIdent firstValueId
|> SynExpr.toString firstValue.Type
@@ -301,7 +337,7 @@ module internal HttpClientGenerator =
SynExpr.CreateLongIdent (SynLongIdent.Create [ "System" ; "Web" ; "HttpUtility" ; "UrlEncode" ])
)
|> SynExpr.CreateParen
|> SynExpr.plus (SynExpr.CreateConstString ("?" + firstKey + "="))
|> SynExpr.plus (SynExpr.plus urlSeparator (SynExpr.CreateConstString (firstKey + "=")))
(prefix, queryParams)
||> List.fold (fun uri (paramKey, paramValue) ->
@@ -709,6 +745,10 @@ module internal HttpClientGenerator =
attrs
|> List.choose (fun attr ->
match attr.TypeName.AsString with
| "RestEase.Query"
| "RestEase.QueryAttribute"
| "WoofWare.Myriad.Plugins.RestEase.Query"
| "WoofWare.Myriad.Plugins.RestEase.QueryAttribute"
| "Query"
| "QueryAttribute" ->
match attr.ArgExpr with
@@ -717,6 +757,10 @@ module internal HttpClientGenerator =
Some (HttpAttribute.Query (Some s))
| SynExpr.Const (a, _) -> failwith $"unrecognised constant arg to the Query attribute: %+A{a}"
| _ -> None
| "RestEase.Path"
| "RestEase.PathAttribute"
| "WoofWare.Myriad.Plugins.RestEase.Path"
| "WoofWare.Myriad.Plugins.RestEase.PathAttribute"
| "Path"
| "PathAttribute" ->
match attr.ArgExpr with
@@ -725,6 +769,10 @@ module internal HttpClientGenerator =
| SynExpr.Const (SynConst.Unit, _) -> Some (HttpAttribute.Path PathSpec.MatchArgName)
| SynExpr.Const (a, _) -> failwith $"unrecognised constant arg to the Path attribute: %+A{a}"
| _ -> None
| "RestEase.Body"
| "RestEase.BodyAttribute"
| "WoofWare.Myriad.Plugins.RestEase.Body"
| "WoofWare.Myriad.Plugins.RestEase.BodyAttribute"
| "Body"
| "BodyAttribute" ->
match attr.ArgExpr with
@@ -740,8 +788,10 @@ module internal HttpClientGenerator =
match attr.TypeName.AsString with
| "BasePath"
| "RestEase.BasePath"
| "WoofWare.Myriad.Plugins.RestEase.BasePath"
| "BasePathAttribute"
| "RestEase.BasePathAttribute" -> Some attr.ArgExpr
| "RestEase.BasePathAttribute"
| "WoofWare.Myriad.Plugins.RestEase.BasePathAttribute" -> Some attr.ArgExpr
| _ -> None
)
@@ -751,8 +801,10 @@ module internal HttpClientGenerator =
match attr.TypeName.AsString with
| "BaseAddress"
| "RestEase.BaseAddress"
| "WoofWare.Myriad.Plugins.RestEase.BaseAddress"
| "BaseAddressAttribute"
| "RestEase.BaseAddressAttribute" -> Some attr.ArgExpr
| "RestEase.BaseAddressAttribute"
| "WoofWare.Myriad.Plugins.RestEase.BaseAddressAttribute" -> Some attr.ArgExpr
| _ -> None
)
@@ -764,6 +816,10 @@ module internal HttpClientGenerator =
=
let interfaceType = AstHelper.parseInterface interfaceType
if not (List.isEmpty interfaceType.Inherits) then
failwith
"HttpClientGenerator does not support inheritance. Remove the `inherit` keyword if you want to use this generator."
let constantHeaders =
interfaceType.Attributes
|> extractHeaderInformation

View File

@@ -21,6 +21,9 @@ module internal InterfaceMockGenerator =
| None -> failwith "Expected record field to have a name, but it was somehow anonymous"
| Some id -> id
[<RequireQualifiedAccess>]
type private KnownInheritance = | IDisposable
let createType
(spec : GenerateMockOutputSpec)
(name : string)
@@ -29,6 +32,20 @@ module internal InterfaceMockGenerator =
(fields : SynField list)
: SynModuleDecl
=
let inherits =
interfaceType.Inherits
|> Seq.map (fun ty ->
match ty with
| SynType.LongIdent (SynLongIdent.SynLongIdent (name, _, _)) ->
match name |> List.map _.idText with
| [] -> failwith "Unexpected empty identifier in inheritance declaration"
| [ "IDisposable" ]
| [ "System" ; "IDisposable" ] -> KnownInheritance.IDisposable
| _ -> failwithf "Unrecognised inheritance identifier: %+A" name
| x -> failwithf "Unrecognised type in inheritance: %+A" x
)
|> Set.ofSeq
let synValData =
{
SynMemberFlags.IsInstance = false
@@ -90,6 +107,23 @@ module internal InterfaceMockGenerator =
)
|> SynBindingReturnInfo.Create
let constructorFields =
let extras =
if inherits.Contains KnownInheritance.IDisposable then
let unitFun = SynExpr.createLambda "_" SynExpr.CreateUnit
[
(SynLongIdent.CreateFromLongIdent [ Ident.Create "Dispose" ], true), Some unitFun
]
else
[]
let nonExtras =
fields
|> List.map (fun field -> (SynLongIdent.CreateFromLongIdent [ getName field ], true), Some failwithFun)
extras @ nonExtras
let constructor =
SynMemberDefn.Member (
SynBinding.SynBinding (
@@ -102,12 +136,7 @@ module internal InterfaceMockGenerator =
SynValData.SynValData (Some synValData, SynValInfo.Empty, None),
constructorIdent,
Some constructorReturnType,
AstHelper.instantiateRecord (
fields
|> List.map (fun field ->
((SynLongIdent.CreateFromLongIdent [ getName field ], true), Some failwithFun)
)
),
AstHelper.instantiateRecord constructorFields,
range0,
DebugPointAtBinding.Yes range0,
{ SynExpr.synBindingTriviaZero true with
@@ -117,6 +146,21 @@ module internal InterfaceMockGenerator =
range0
)
let fields =
let extras =
if inherits.Contains KnownInheritance.IDisposable then
[
SynField.Create (
SynType.CreateFun (SynType.CreateUnit, SynType.CreateUnit),
Ident.Create "Dispose",
xmldoc = PreXmlDoc.Create " Implementation of IDisposable.Dispose"
)
]
else
[]
extras @ fields
let interfaceMembers =
let members =
interfaceType.Members
@@ -150,7 +194,9 @@ module internal InterfaceMockGenerator =
|> List.mapi (fun i arg ->
arg.Args
|> List.mapi (fun j arg ->
SynArgInfo.CreateIdString $"arg_%i{i}_%i{j}"
match arg.Type with
| UnitType -> SynArgInfo.SynArgInfo ([], false, None)
| _ -> SynArgInfo.CreateIdString $"arg_%i{i}_%i{j}"
)
)
],
@@ -165,10 +211,18 @@ module internal InterfaceMockGenerator =
|> List.mapi (fun i tupledArgs ->
let args =
tupledArgs.Args
|> List.mapi (fun j _ -> SynPat.CreateNamed (Ident.Create $"arg_%i{i}_%i{j}"))
|> List.mapi (fun j ty ->
match ty.Type with
| UnitType -> SynPat.Const (SynConst.Unit, range0)
| _ -> SynPat.CreateNamed (Ident.Create $"arg_%i{i}_%i{j}")
)
SynPat.Tuple (false, args, List.replicate (args.Length - 1) range0, range0)
|> SynPat.CreateParen
match args with
| [] -> failwith "somehow got no args at all"
| [ arg ] -> arg
| args ->
SynPat.Tuple (false, args, List.replicate (args.Length - 1) range0, range0)
|> SynPat.CreateParen
|> fun i -> if tupledArgs.HasParen then SynPat.Paren (i, range0) else i
)
@@ -187,7 +241,11 @@ module internal InterfaceMockGenerator =
memberInfo.Args
|> List.mapi (fun i args ->
args.Args
|> List.mapi (fun j args -> SynExpr.CreateIdentString $"arg_%i{i}_%i{j}")
|> List.mapi (fun j arg ->
match arg.Type with
| UnitType -> SynExpr.CreateConst SynConst.Unit
| _ -> SynExpr.CreateIdentString $"arg_%i{i}_%i{j}"
)
|> SynExpr.CreateParenedTuple
)
@@ -264,11 +322,100 @@ module internal InterfaceMockGenerator =
| Some (SynAccess.Internal _), _ -> SynAccess.Internal range0
| Some (SynAccess.Private _), _ -> SynAccess.Private range0
let extraInterfaces =
inherits
|> Seq.map (fun inheritance ->
match inheritance with
| KnownInheritance.IDisposable ->
let valData =
SynValData.SynValData (
Some
{
IsInstance = true
IsDispatchSlot = false
IsOverrideOrExplicitImpl = true
IsFinal = false
GetterOrSetterIsCompilerGenerated = false
MemberKind = SynMemberKind.Member
},
valInfo =
SynValInfo.SynValInfo (
curriedArgInfos =
[
yield
[
SynArgInfo.SynArgInfo (
attributes = [],
optional = false,
ident = None
)
]
],
returnInfo =
SynArgInfo.SynArgInfo (attributes = [], optional = false, ident = None)
),
thisIdOpt = None
)
let headArgs = [ SynPat.Const (SynConst.Unit, range0) ]
let headPat =
SynPat.LongIdent (
SynLongIdent.CreateFromLongIdent [ Ident.Create "this" ; Ident.Create "Dispose" ],
None,
None,
SynArgPats.Pats headArgs,
None,
range0
)
let binding =
SynBinding.SynBinding (
None,
SynBindingKind.Normal,
false,
false,
[],
PreXmlDoc.Empty,
valData,
headPat,
Some (
SynBindingReturnInfo.SynBindingReturnInfo (
SynType.Unit (),
range0,
[],
SynBindingReturnInfoTrivia.Zero
)
),
SynExpr.CreateApp (
SynExpr.CreateLongIdent (SynLongIdent.Create [ "this" ; "Dispose" ]),
SynExpr.CreateUnit
),
range0,
DebugPointAtBinding.Yes range0,
{
LeadingKeyword = SynLeadingKeyword.Member range0
InlineKeyword = None
EqualsRange = Some range0
}
)
let mem = SynMemberDefn.Member (binding, range0)
SynMemberDefn.Interface (
SynType.CreateLongIdent (SynLongIdent.Create [ "System" ; "IDisposable" ]),
Some range0,
Some [ mem ],
range0
)
)
|> Seq.toList
let record =
{
Name = Ident.Create name
Fields = fields
Members = Some [ constructor ; interfaceMembers ]
Members = Some ([ constructor ; interfaceMembers ] @ extraInterfaces)
XmlDoc = Some xmlDoc
Generics = interfaceType.Generics
Accessibility = Some access
@@ -333,7 +480,6 @@ module internal InterfaceMockGenerator =
let typeDecl = createType spec name interfaceType docString fields
SynModuleOrNamespace.CreateNamespace (
namespaceId,
decls = (opens |> List.map SynModuleDecl.CreateOpen) @ [ typeDecl ]

View File

@@ -311,3 +311,19 @@ module internal SynExpr =
),
x
)
/// {y} >= {x}
let greaterThanOrEqual (x : SynExpr) (y : SynExpr) : SynExpr =
SynExpr.CreateApp (
SynExpr.CreateAppInfix (
SynExpr.CreateLongIdent (
SynLongIdent.SynLongIdent (
[ Ident.Create "op_GreaterThanOrEqual" ],
[],
[ Some (IdentTrivia.OriginalNotation ">=") ]
)
),
y
),
x
)

View File

@@ -8,18 +8,13 @@
})
(fetchNuGet {
pname = "fantomas";
version = "6.3.0-alpha-007";
sha256 = "sha256-uZw6h6k/DS4BcYtK9cv8TLS0H8MZDO3WBaPPTdtTgu0=";
version = "6.3.4";
sha256 = "sha256-1aWqZynBkQoznenGoP0sbf1PcUXAbcHiWyECuv89xa0=";
})
(fetchNuGet {
pname = "ApiSurface";
version = "4.0.30";
sha256 = "0khbp0dx87m4kx1a5b9vgh1pp88vr9w8vpqvxf6afrpcyynwrrcr";
})
(fetchNuGet {
pname = "coverlet.collector";
version = "6.0.0";
sha256 = "12j34vrkmph8lspbafnqmfnj2qvysz1jcrks2khw798s6dwv0j90";
version = "4.0.39";
sha256 = "sha256-I4K5nJbltsfL/1r+KPTIo2wUd30zsCC2pkrnIRnsRHM=";
})
(fetchNuGet {
pname = "Fantomas.Core";
@@ -296,11 +291,6 @@
version = "3.6.133";
sha256 = "1cdw8krvsnx0n34f7fm5hiiy7bs6h3asvncqcikc0g46l50w2j80";
})
(fetchNuGet {
pname = "NETStandard.Library";
version = "2.0.0";
sha256 = "1bc4ba8ahgk15m8k4nd7x406nhi0kwqzbgjk2dmw52ss553xz7iy";
})
(fetchNuGet {
pname = "NETStandard.Library";
version = "2.0.3";
@@ -348,13 +338,8 @@
})
(fetchNuGet {
pname = "NUnit";
version = "3.13.3";
sha256 = "0wdzfkygqnr73s6lpxg5b1pwaqz9f414fxpvpdmf72bvh4jaqzv6";
})
(fetchNuGet {
pname = "NUnit";
version = "4.0.1";
sha256 = "0jgiq3dbwli5r70j0bw7021d69r7bhr58s8kphlpjmf7k47l5pcd";
version = "4.1.0";
sha256 = "0fj6xwgqaxq3mrai86bklclfmjkzf038mrslwfqf4ignaz9f7g5j";
})
(fetchNuGet {
pname = "NUnit3TestAdapter";