Compare commits

..

1 Commits

Author SHA1 Message Date
Patrick Stevens
0a1783d6ed Support [<BasePath>] (#263) 2024-09-15 17:38:03 +00:00
9 changed files with 602 additions and 90 deletions

View File

@@ -1,5 +1,14 @@
Notable changes are recorded here. Notable changes are recorded here.
# WoofWare.Myriad.Plugins 3.0.1
Semantics of `HttpClient`'s URI component composition changed:
we now implicitly insert `/` characters after `[<BaseAddress>]` and `[<BasePath>]`, so that URI composition doesn't silently drop the last component if you didn't put a slash there.
# WoofWare.Myriad.Plugins 2.3.9
`JsonParse` and `JsonSerialize` now interpret `[<JsonExtensionData>]`, which must be on a `Dictionary<string, _>`; this collects any extra components that were present on the JSON object.
# WoofWare.Myriad.Plugins 2.2.1, WoofWare.Myriad.Plugins.Attributes 3.2.1 # WoofWare.Myriad.Plugins 2.2.1, WoofWare.Myriad.Plugins.Attributes 3.2.1
New generator: `ArgParser`, a basic reflection-free argument parser. New generator: `ArgParser`, a basic reflection-free argument parser.

View File

@@ -29,7 +29,7 @@ module PureGymApi =
let uri = let uri =
System.Uri ( System.Uri (
(match client.BaseAddress with (match client.BaseAddress with
| null -> System.Uri "https://whatnot.com" | null -> System.Uri "https://whatnot.com/"
| v -> v), | v -> v),
System.Uri (("v1/gyms/"), System.UriKind.Relative) System.Uri (("v1/gyms/"), System.UriKind.Relative)
) )
@@ -59,7 +59,7 @@ module PureGymApi =
let uri = let uri =
System.Uri ( System.Uri (
(match client.BaseAddress with (match client.BaseAddress with
| null -> System.Uri "https://whatnot.com" | null -> System.Uri "https://whatnot.com/"
| v -> v), | v -> v),
System.Uri ( System.Uri (
"v1/gyms/{gym_id}/attendance" "v1/gyms/{gym_id}/attendance"
@@ -93,7 +93,7 @@ module PureGymApi =
let uri = let uri =
System.Uri ( System.Uri (
(match client.BaseAddress with (match client.BaseAddress with
| null -> System.Uri "https://whatnot.com" | null -> System.Uri "https://whatnot.com/"
| v -> v), | v -> v),
System.Uri ( System.Uri (
"v1/gyms/{gym_id}/attendance" "v1/gyms/{gym_id}/attendance"
@@ -127,7 +127,7 @@ module PureGymApi =
let uri = let uri =
System.Uri ( System.Uri (
(match client.BaseAddress with (match client.BaseAddress with
| null -> System.Uri "https://whatnot.com" | null -> System.Uri "https://whatnot.com/"
| v -> v), | v -> v),
System.Uri ("v1/member", System.UriKind.Relative) System.Uri ("v1/member", System.UriKind.Relative)
) )
@@ -157,7 +157,7 @@ module PureGymApi =
let uri = let uri =
System.Uri ( System.Uri (
(match client.BaseAddress with (match client.BaseAddress with
| null -> System.Uri "https://whatnot.com" | null -> System.Uri "https://whatnot.com/"
| v -> v), | v -> v),
System.Uri ( System.Uri (
"v1/gyms/{gym}" "v1/gyms/{gym}"
@@ -191,7 +191,7 @@ module PureGymApi =
let uri = let uri =
System.Uri ( System.Uri (
(match client.BaseAddress with (match client.BaseAddress with
| null -> System.Uri "https://whatnot.com" | null -> System.Uri "https://whatnot.com/"
| v -> v), | v -> v),
System.Uri ("v1/member/activity", System.UriKind.Relative) System.Uri ("v1/member/activity", System.UriKind.Relative)
) )
@@ -221,7 +221,7 @@ module PureGymApi =
let uri = let uri =
System.Uri ( System.Uri (
(match client.BaseAddress with (match client.BaseAddress with
| null -> System.Uri "https://whatnot.com" | null -> System.Uri "https://whatnot.com/"
| v -> v), | v -> v),
System.Uri ("some/url", System.UriKind.Relative) System.Uri ("some/url", System.UriKind.Relative)
) )
@@ -251,7 +251,7 @@ module PureGymApi =
let uri = let uri =
System.Uri ( System.Uri (
(match client.BaseAddress with (match client.BaseAddress with
| null -> System.Uri "https://whatnot.com" | null -> System.Uri "https://whatnot.com/"
| v -> v), | v -> v),
System.Uri ("some/url", System.UriKind.Relative) System.Uri ("some/url", System.UriKind.Relative)
) )
@@ -317,7 +317,7 @@ module PureGymApi =
let uri = let uri =
System.Uri ( System.Uri (
(match client.BaseAddress with (match client.BaseAddress with
| null -> System.Uri "https://whatnot.com" | null -> System.Uri "https://whatnot.com/"
| v -> v), | v -> v),
System.Uri ( System.Uri (
("/v2/gymSessions/member" ("/v2/gymSessions/member"
@@ -358,7 +358,7 @@ module PureGymApi =
let uri = let uri =
System.Uri ( System.Uri (
(match client.BaseAddress with (match client.BaseAddress with
| null -> System.Uri "https://whatnot.com" | null -> System.Uri "https://whatnot.com/"
| v -> v), | v -> v),
System.Uri ( System.Uri (
("/v2/gymSessions/member?foo=1" ("/v2/gymSessions/member?foo=1"
@@ -399,7 +399,7 @@ module PureGymApi =
let uri = let uri =
System.Uri ( System.Uri (
(match client.BaseAddress with (match client.BaseAddress with
| null -> System.Uri "https://whatnot.com" | null -> System.Uri "https://whatnot.com/"
| v -> v), | v -> v),
System.Uri ("users/new", System.UriKind.Relative) System.Uri ("users/new", System.UriKind.Relative)
) )
@@ -426,7 +426,7 @@ module PureGymApi =
let uri = let uri =
System.Uri ( System.Uri (
(match client.BaseAddress with (match client.BaseAddress with
| null -> System.Uri "https://whatnot.com" | null -> System.Uri "https://whatnot.com/"
| v -> v), | v -> v),
System.Uri ("users/new", System.UriKind.Relative) System.Uri ("users/new", System.UriKind.Relative)
) )
@@ -453,7 +453,7 @@ module PureGymApi =
let uri = let uri =
System.Uri ( System.Uri (
(match client.BaseAddress with (match client.BaseAddress with
| null -> System.Uri "https://whatnot.com" | null -> System.Uri "https://whatnot.com/"
| v -> v), | v -> v),
System.Uri ("users/new", System.UriKind.Relative) System.Uri ("users/new", System.UriKind.Relative)
) )
@@ -480,7 +480,7 @@ module PureGymApi =
let uri = let uri =
System.Uri ( System.Uri (
(match client.BaseAddress with (match client.BaseAddress with
| null -> System.Uri "https://whatnot.com" | null -> System.Uri "https://whatnot.com/"
| v -> v), | v -> v),
System.Uri ("users/new", System.UriKind.Relative) System.Uri ("users/new", System.UriKind.Relative)
) )
@@ -507,7 +507,7 @@ module PureGymApi =
let uri = let uri =
System.Uri ( System.Uri (
(match client.BaseAddress with (match client.BaseAddress with
| null -> System.Uri "https://whatnot.com" | null -> System.Uri "https://whatnot.com/"
| v -> v), | v -> v),
System.Uri ("users/new", System.UriKind.Relative) System.Uri ("users/new", System.UriKind.Relative)
) )
@@ -534,7 +534,7 @@ module PureGymApi =
let uri = let uri =
System.Uri ( System.Uri (
(match client.BaseAddress with (match client.BaseAddress with
| null -> System.Uri "https://whatnot.com" | null -> System.Uri "https://whatnot.com/"
| v -> v), | v -> v),
System.Uri ("users/new", System.UriKind.Relative) System.Uri ("users/new", System.UriKind.Relative)
) )
@@ -567,7 +567,7 @@ module PureGymApi =
let uri = let uri =
System.Uri ( System.Uri (
(match client.BaseAddress with (match client.BaseAddress with
| null -> System.Uri "https://whatnot.com" | null -> System.Uri "https://whatnot.com/"
| v -> v), | v -> v),
System.Uri ("users/new", System.UriKind.Relative) System.Uri ("users/new", System.UriKind.Relative)
) )
@@ -600,7 +600,7 @@ module PureGymApi =
let uri = let uri =
System.Uri ( System.Uri (
(match client.BaseAddress with (match client.BaseAddress with
| null -> System.Uri "https://whatnot.com" | null -> System.Uri "https://whatnot.com/"
| v -> v), | v -> v),
System.Uri ("users/new", System.UriKind.Relative) System.Uri ("users/new", System.UriKind.Relative)
) )
@@ -633,7 +633,7 @@ module PureGymApi =
let uri = let uri =
System.Uri ( System.Uri (
(match client.BaseAddress with (match client.BaseAddress with
| null -> System.Uri "https://whatnot.com" | null -> System.Uri "https://whatnot.com/"
| v -> v), | v -> v),
System.Uri ("users/new", System.UriKind.Relative) System.Uri ("users/new", System.UriKind.Relative)
) )
@@ -659,7 +659,7 @@ module PureGymApi =
let uri = let uri =
System.Uri ( System.Uri (
(match client.BaseAddress with (match client.BaseAddress with
| null -> System.Uri "https://whatnot.com" | null -> System.Uri "https://whatnot.com/"
| v -> v), | v -> v),
System.Uri ( System.Uri (
"endpoint/{param}" "endpoint/{param}"
@@ -688,7 +688,7 @@ module PureGymApi =
let uri = let uri =
System.Uri ( System.Uri (
(match client.BaseAddress with (match client.BaseAddress with
| null -> System.Uri "https://whatnot.com" | null -> System.Uri "https://whatnot.com/"
| v -> v), | v -> v),
System.Uri ("endpoint", System.UriKind.Relative) System.Uri ("endpoint", System.UriKind.Relative)
) )
@@ -713,7 +713,7 @@ module PureGymApi =
let uri = let uri =
System.Uri ( System.Uri (
(match client.BaseAddress with (match client.BaseAddress with
| null -> System.Uri "https://whatnot.com" | null -> System.Uri "https://whatnot.com/"
| v -> v), | v -> v),
System.Uri ("endpoint", System.UriKind.Relative) System.Uri ("endpoint", System.UriKind.Relative)
) )
@@ -738,7 +738,7 @@ module PureGymApi =
let uri = let uri =
System.Uri ( System.Uri (
(match client.BaseAddress with (match client.BaseAddress with
| null -> System.Uri "https://whatnot.com" | null -> System.Uri "https://whatnot.com/"
| v -> v), | v -> v),
System.Uri ("endpoint", System.UriKind.Relative) System.Uri ("endpoint", System.UriKind.Relative)
) )
@@ -763,7 +763,7 @@ module PureGymApi =
let uri = let uri =
System.Uri ( System.Uri (
(match client.BaseAddress with (match client.BaseAddress with
| null -> System.Uri "https://whatnot.com" | null -> System.Uri "https://whatnot.com/"
| v -> v), | v -> v),
System.Uri ("endpoint", System.UriKind.Relative) System.Uri ("endpoint", System.UriKind.Relative)
) )
@@ -787,7 +787,7 @@ module PureGymApi =
let uri = let uri =
System.Uri ( System.Uri (
(match client.BaseAddress with (match client.BaseAddress with
| null -> System.Uri "https://whatnot.com" | null -> System.Uri "https://whatnot.com/"
| v -> v), | v -> v),
System.Uri ("endpoint", System.UriKind.Relative) System.Uri ("endpoint", System.UriKind.Relative)
) )
@@ -811,7 +811,7 @@ module PureGymApi =
let uri = let uri =
System.Uri ( System.Uri (
(match client.BaseAddress with (match client.BaseAddress with
| null -> System.Uri "https://whatnot.com" | null -> System.Uri "https://whatnot.com/"
| v -> v), | v -> v),
System.Uri ("endpoint", System.UriKind.Relative) System.Uri ("endpoint", System.UriKind.Relative)
) )
@@ -835,7 +835,7 @@ module PureGymApi =
let uri = let uri =
System.Uri ( System.Uri (
(match client.BaseAddress with (match client.BaseAddress with
| null -> System.Uri "https://whatnot.com" | null -> System.Uri "https://whatnot.com/"
| v -> v), | v -> v),
System.Uri ("endpoint", System.UriKind.Relative) System.Uri ("endpoint", System.UriKind.Relative)
) )
@@ -859,7 +859,7 @@ module PureGymApi =
let uri = let uri =
System.Uri ( System.Uri (
(match client.BaseAddress with (match client.BaseAddress with
| null -> System.Uri "https://whatnot.com" | null -> System.Uri "https://whatnot.com/"
| v -> v), | v -> v),
System.Uri ("endpoint", System.UriKind.Relative) System.Uri ("endpoint", System.UriKind.Relative)
) )
@@ -895,7 +895,7 @@ module PureGymApi =
let uri = let uri =
System.Uri ( System.Uri (
(match client.BaseAddress with (match client.BaseAddress with
| null -> System.Uri "https://whatnot.com" | null -> System.Uri "https://whatnot.com/"
| v -> v), | v -> v),
System.Uri ("endpoint", System.UriKind.Relative) System.Uri ("endpoint", System.UriKind.Relative)
) )
@@ -931,7 +931,7 @@ module PureGymApi =
let uri = let uri =
System.Uri ( System.Uri (
(match client.BaseAddress with (match client.BaseAddress with
| null -> System.Uri "https://whatnot.com" | null -> System.Uri "https://whatnot.com/"
| v -> v), | v -> v),
System.Uri ("endpoint", System.UriKind.Relative) System.Uri ("endpoint", System.UriKind.Relative)
) )
@@ -967,7 +967,7 @@ module PureGymApi =
let uri = let uri =
System.Uri ( System.Uri (
(match client.BaseAddress with (match client.BaseAddress with
| null -> System.Uri "https://whatnot.com" | null -> System.Uri "https://whatnot.com/"
| v -> v), | v -> v),
System.Uri ("endpoint", System.UriKind.Relative) System.Uri ("endpoint", System.UriKind.Relative)
) )
@@ -1003,7 +1003,7 @@ module PureGymApi =
let uri = let uri =
System.Uri ( System.Uri (
(match client.BaseAddress with (match client.BaseAddress with
| null -> System.Uri "https://whatnot.com" | null -> System.Uri "https://whatnot.com/"
| v -> v), | v -> v),
System.Uri ("endpoint", System.UriKind.Relative) System.Uri ("endpoint", System.UriKind.Relative)
) )
@@ -1026,7 +1026,7 @@ module PureGymApi =
let uri = let uri =
System.Uri ( System.Uri (
(match client.BaseAddress with (match client.BaseAddress with
| null -> System.Uri "https://whatnot.com" | null -> System.Uri "https://whatnot.com/"
| v -> v), | v -> v),
System.Uri ("endpoint", System.UriKind.Relative) System.Uri ("endpoint", System.UriKind.Relative)
) )
@@ -1116,15 +1116,18 @@ module ApiWithBasePath =
let uri = let uri =
System.Uri ( System.Uri (
(match client.BaseAddress with System.Uri (
| null -> (match client.BaseAddress with
raise ( | null ->
System.ArgumentNullException ( raise (
nameof (client.BaseAddress), System.ArgumentNullException (
"No base address was supplied on the type, and no BaseAddress was on the HttpClient." nameof (client.BaseAddress),
"No base address was supplied on the type, and no BaseAddress was on the HttpClient."
)
) )
) | v -> v),
| v -> v), System.Uri ("foo/", System.UriKind.Relative)
),
System.Uri ( System.Uri (
"endpoint/{param}" "endpoint/{param}"
.Replace ("{param}", parameter.ToString () |> System.Web.HttpUtility.UrlEncode), .Replace ("{param}", parameter.ToString () |> System.Web.HttpUtility.UrlEncode),
@@ -1167,9 +1170,12 @@ module ApiWithBasePathAndAddress =
let uri = let uri =
System.Uri ( System.Uri (
(match client.BaseAddress with System.Uri (
| null -> System.Uri "https://whatnot.com" (match client.BaseAddress with
| v -> v), | null -> System.Uri "https://whatnot.com/thing/"
| v -> v),
System.Uri ("foo/", System.UriKind.Relative)
),
System.Uri ( System.Uri (
"endpoint/{param}" "endpoint/{param}"
.Replace ("{param}", parameter.ToString () |> System.Web.HttpUtility.UrlEncode), .Replace ("{param}", parameter.ToString () |> System.Web.HttpUtility.UrlEncode),
@@ -1200,6 +1206,312 @@ open System.Net
open System.Net.Http open System.Net.Http
open RestEase open RestEase
/// Module for constructing a REST client.
[<CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix) ; RequireQualifiedAccess>]
module ApiWithAbsoluteBasePath =
/// Create a REST client.
let make (client : System.Net.Http.HttpClient) : IApiWithAbsoluteBasePath =
{ new IApiWithAbsoluteBasePath with
member _.GetPathParam (parameter : string, cancellationToken : CancellationToken option) =
async {
let! ct = Async.CancellationToken
let uri =
System.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 ("/foo/", System.UriKind.Relative)
),
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
)
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 = cancellationToken))
}
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 ApiWithAbsoluteBasePathAndAddress =
/// Create a REST client.
let make (client : System.Net.Http.HttpClient) : IApiWithAbsoluteBasePathAndAddress =
{ new IApiWithAbsoluteBasePathAndAddress with
member _.GetPathParam (parameter : string, ct : CancellationToken option) =
async {
let! ct = Async.CancellationToken
let uri =
System.Uri (
System.Uri (
(match client.BaseAddress with
| null -> System.Uri "https://whatnot.com/thing/"
| v -> v),
System.Uri ("/foo/", System.UriKind.Relative)
),
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
)
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 ApiWithBasePathAndAbsoluteEndpoint =
/// Create a REST client.
let make (client : System.Net.Http.HttpClient) : IApiWithBasePathAndAbsoluteEndpoint =
{ new IApiWithBasePathAndAbsoluteEndpoint with
member _.GetPathParam (parameter : string, cancellationToken : CancellationToken option) =
async {
let! ct = Async.CancellationToken
let uri =
System.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 ("foo/", System.UriKind.Relative)
),
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
)
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 = cancellationToken))
}
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 ApiWithBasePathAndAddressAndAbsoluteEndpoint =
/// Create a REST client.
let make (client : System.Net.Http.HttpClient) : IApiWithBasePathAndAddressAndAbsoluteEndpoint =
{ new IApiWithBasePathAndAddressAndAbsoluteEndpoint with
member _.GetPathParam (parameter : string, ct : CancellationToken option) =
async {
let! ct = Async.CancellationToken
let uri =
System.Uri (
System.Uri (
(match client.BaseAddress with
| null -> System.Uri "https://whatnot.com/thing/"
| v -> v),
System.Uri ("foo/", System.UriKind.Relative)
),
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
)
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 ApiWithAbsoluteBasePathAndAbsoluteEndpoint =
/// Create a REST client.
let make (client : System.Net.Http.HttpClient) : IApiWithAbsoluteBasePathAndAbsoluteEndpoint =
{ new IApiWithAbsoluteBasePathAndAbsoluteEndpoint with
member _.GetPathParam (parameter : string, cancellationToken : CancellationToken option) =
async {
let! ct = Async.CancellationToken
let uri =
System.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 ("/foo/", System.UriKind.Relative)
),
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
)
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 = cancellationToken))
}
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 ApiWithAbsoluteBasePathAndAddressAndAbsoluteEndpoint =
/// Create a REST client.
let make (client : System.Net.Http.HttpClient) : IApiWithAbsoluteBasePathAndAddressAndAbsoluteEndpoint =
{ new IApiWithAbsoluteBasePathAndAddressAndAbsoluteEndpoint with
member _.GetPathParam (parameter : string, ct : CancellationToken option) =
async {
let! ct = Async.CancellationToken
let uri =
System.Uri (
System.Uri (
(match client.BaseAddress with
| null -> System.Uri "https://whatnot.com/thing/"
| v -> v),
System.Uri ("/foo/", System.UriKind.Relative)
),
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
)
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. /// Module for constructing a REST client.
[<CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix) ; RequireQualifiedAccess>] [<CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix) ; RequireQualifiedAccess>]
module ApiWithHeaders = module ApiWithHeaders =

View File

@@ -122,8 +122,6 @@ type internal IApiWithoutBaseAddress =
[<Get "endpoint/{param}">] [<Get "endpoint/{param}">]
abstract GetPathParam : [<Path "param">] parameter : string * ?ct : CancellationToken -> Task<string> abstract GetPathParam : [<Path "param">] parameter : string * ?ct : CancellationToken -> Task<string>
// TODO: implement BasePath support
[<WoofWare.Myriad.Plugins.HttpClient>] [<WoofWare.Myriad.Plugins.HttpClient>]
[<BasePath "foo">] [<BasePath "foo">]
type IApiWithBasePath = type IApiWithBasePath =
@@ -132,12 +130,54 @@ type IApiWithBasePath =
abstract GetPathParam : [<Path "param">] parameter : string * ?cancellationToken : CancellationToken -> Task<string> abstract GetPathParam : [<Path "param">] parameter : string * ?cancellationToken : CancellationToken -> Task<string>
[<WoofWare.Myriad.Plugins.HttpClient>] [<WoofWare.Myriad.Plugins.HttpClient>]
[<BaseAddress "https://whatnot.com">] [<BaseAddress "https://whatnot.com/thing">]
[<BasePath "foo">] [<BasePath "foo">]
type IApiWithBasePathAndAddress = type IApiWithBasePathAndAddress =
[<Get "endpoint/{param}">] [<Get "endpoint/{param}">]
abstract GetPathParam : [<Path "param">] parameter : string * ?ct : CancellationToken -> Task<string> abstract GetPathParam : [<Path "param">] parameter : string * ?ct : CancellationToken -> Task<string>
[<WoofWare.Myriad.Plugins.HttpClient>]
[<BasePath "/foo">]
type IApiWithAbsoluteBasePath =
// Example where we use the bundled attributes rather than RestEase's
[<WoofWare.Myriad.Plugins.RestEase.Get "endpoint/{param}">]
abstract GetPathParam : [<Path "param">] parameter : string * ?cancellationToken : CancellationToken -> Task<string>
[<WoofWare.Myriad.Plugins.HttpClient>]
[<BaseAddress "https://whatnot.com/thing">]
[<BasePath "/foo">]
type IApiWithAbsoluteBasePathAndAddress =
[<Get "endpoint/{param}">]
abstract GetPathParam : [<Path "param">] parameter : string * ?ct : CancellationToken -> Task<string>
[<WoofWare.Myriad.Plugins.HttpClient>]
[<BasePath "foo">]
type IApiWithBasePathAndAbsoluteEndpoint =
// Example where we use the bundled attributes rather than RestEase's
[<WoofWare.Myriad.Plugins.RestEase.Get "/endpoint/{param}">]
abstract GetPathParam : [<Path "param">] parameter : string * ?cancellationToken : CancellationToken -> Task<string>
[<WoofWare.Myriad.Plugins.HttpClient>]
[<BaseAddress "https://whatnot.com/thing">]
[<BasePath "foo">]
type IApiWithBasePathAndAddressAndAbsoluteEndpoint =
[<Get "/endpoint/{param}">]
abstract GetPathParam : [<Path "param">] parameter : string * ?ct : CancellationToken -> Task<string>
[<WoofWare.Myriad.Plugins.HttpClient>]
[<BasePath "/foo">]
type IApiWithAbsoluteBasePathAndAbsoluteEndpoint =
// Example where we use the bundled attributes rather than RestEase's
[<WoofWare.Myriad.Plugins.RestEase.Get "/endpoint/{param}">]
abstract GetPathParam : [<Path "param">] parameter : string * ?cancellationToken : CancellationToken -> Task<string>
[<WoofWare.Myriad.Plugins.HttpClient>]
[<BaseAddress "https://whatnot.com/thing">]
[<BasePath "/foo">]
type IApiWithAbsoluteBasePathAndAddressAndAbsoluteEndpoint =
[<Get "/endpoint/{param}">]
abstract GetPathParam : [<Path "param">] parameter : string * ?ct : CancellationToken -> Task<string>
[<WoofWare.Myriad.Plugins.HttpClient>] [<WoofWare.Myriad.Plugins.HttpClient>]
[<Header("Header-Name", "Header-Value")>] [<Header("Header-Name", "Header-Value")>]
type IApiWithHeaders = type IApiWithHeaders =

View File

@@ -45,6 +45,9 @@ module RestEase =
/// Indicates that this interface represents a REST client which accesses an API whose paths are /// Indicates that this interface represents a REST client which accesses an API whose paths are
/// all relative to the given address. /// all relative to the given address.
///
/// We will essentially unconditionally append a slash to this for you, on the grounds that you probably don't
/// intend the base path *itself* to be an endpoint.
type BaseAddressAttribute (addr : string) = type BaseAddressAttribute (addr : string) =
inherit Attribute () inherit Attribute ()
@@ -61,3 +64,21 @@ module RestEase =
inherit Attribute () inherit Attribute ()
new (path : string) = PathAttribute (Some path) new (path : string) = PathAttribute (Some path)
new () = PathAttribute None new () = PathAttribute None
/// Indicates that this argument to a method is passed to the remote API by being serialised into the request
/// body.
type BodyAttribute () =
inherit Attribute ()
/// This is interpolated into every URL, between the BaseAddress and the path specified by e.g. [<Get>].
/// Note that if the [<Get>]-specified path starts with a slash, the BasePath is ignored, because then [<Get>]
/// is considered to be relative to the URL root (i.e. the host part of the BaseAddress).
/// Similarly, if the [<BasePath>] starts with a slash, then any path component of the BaseAddress is ignored.
///
/// We will essentially unconditionally append a slash to this for you, on the grounds that you probably don't
/// intend the base path *itself* to be an endpoint.
///
/// Can contain {placeholders}; hopefully your methods define values for those placeholders with [<Path>]
/// attributes!
type BasePathAttribute (path : string) =
inherit Attribute ()

View File

@@ -49,6 +49,10 @@ WoofWare.Myriad.Plugins.RemoveOptionsAttribute..ctor [constructor]: unit
WoofWare.Myriad.Plugins.RestEase inherit obj WoofWare.Myriad.Plugins.RestEase inherit obj
WoofWare.Myriad.Plugins.RestEase+BaseAddressAttribute inherit System.Attribute WoofWare.Myriad.Plugins.RestEase+BaseAddressAttribute inherit System.Attribute
WoofWare.Myriad.Plugins.RestEase+BaseAddressAttribute..ctor [constructor]: string WoofWare.Myriad.Plugins.RestEase+BaseAddressAttribute..ctor [constructor]: string
WoofWare.Myriad.Plugins.RestEase+BasePathAttribute inherit System.Attribute
WoofWare.Myriad.Plugins.RestEase+BasePathAttribute..ctor [constructor]: string
WoofWare.Myriad.Plugins.RestEase+BodyAttribute inherit System.Attribute
WoofWare.Myriad.Plugins.RestEase+BodyAttribute..ctor [constructor]: unit
WoofWare.Myriad.Plugins.RestEase+DeleteAttribute inherit System.Attribute WoofWare.Myriad.Plugins.RestEase+DeleteAttribute inherit System.Attribute
WoofWare.Myriad.Plugins.RestEase+DeleteAttribute..ctor [constructor]: string WoofWare.Myriad.Plugins.RestEase+DeleteAttribute..ctor [constructor]: string
WoofWare.Myriad.Plugins.RestEase+GetAttribute inherit System.Attribute WoofWare.Myriad.Plugins.RestEase+GetAttribute inherit System.Attribute

View File

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

View File

@@ -9,18 +9,18 @@ open FsUnitTyped
[<TestFixture>] [<TestFixture>]
module TestBasePath = module TestBasePath =
let replyWithUrl (message : HttpRequestMessage) : HttpResponseMessage Async =
async {
message.Method |> shouldEqual HttpMethod.Get
let content = new StringContent (message.RequestUri.ToString ())
let resp = new HttpResponseMessage (HttpStatusCode.OK)
resp.Content <- content
return resp
}
[<Test>] [<Test>]
let ``Base address is respected`` () = let ``Base address is respected`` () =
let proc (message : HttpRequestMessage) : HttpResponseMessage Async = use client = HttpClientMock.makeNoUri replyWithUrl
async {
message.Method |> shouldEqual HttpMethod.Get
let content = new StringContent (message.RequestUri.ToString ())
let resp = new HttpResponseMessage (HttpStatusCode.OK)
resp.Content <- content
return resp
}
use client = HttpClientMock.makeNoUri proc
let api = PureGymApi.make client let api = PureGymApi.make client
let observedUri = api.GetPathParam("param").Result let observedUri = api.GetPathParam("param").Result
@@ -28,38 +28,28 @@ module TestBasePath =
[<Test>] [<Test>]
let ``Without a base address attr but with BaseAddress on client, request goes through`` () = let ``Without a base address attr but with BaseAddress on client, request goes through`` () =
let proc (message : HttpRequestMessage) : HttpResponseMessage Async = use client = HttpClientMock.make (Uri "https://baseaddress.com") replyWithUrl
async {
message.Method |> shouldEqual HttpMethod.Get
let content = new StringContent (message.RequestUri.ToString ())
let resp = new HttpResponseMessage (HttpStatusCode.OK)
resp.Content <- content
return resp
}
use client = HttpClientMock.make (System.Uri "https://baseaddress.com") proc
let api = ApiWithoutBaseAddress.make client let api = ApiWithoutBaseAddress.make client
let observedUri = api.GetPathParam("param").Result let observedUri = api.GetPathParam("param").Result
observedUri |> shouldEqual "https://baseaddress.com/endpoint/param" observedUri |> shouldEqual "https://baseaddress.com/endpoint/param"
[<Test>] [<Test>]
let ``Without a base address attr or BaseAddress on client, request throws`` () = let ``Base address on client takes precedence`` () =
let proc (message : HttpRequestMessage) : HttpResponseMessage Async = use client = HttpClientMock.make (Uri "https://baseaddress.com") replyWithUrl
async { let api = PureGymApi.make client
message.Method |> shouldEqual HttpMethod.Get
let content = new StringContent (message.RequestUri.ToString ())
let resp = new HttpResponseMessage (HttpStatusCode.OK)
resp.Content <- content
return resp
}
use client = HttpClientMock.makeNoUri proc let observedUri = api.GetPathParam("param").Result
observedUri |> shouldEqual "https://baseaddress.com/endpoint/param"
[<Test>]
let ``Without a base address attr or BaseAddress on client, request throws`` () =
use client = HttpClientMock.makeNoUri replyWithUrl
let api = ApiWithoutBaseAddress.make client let api = ApiWithoutBaseAddress.make client
let observedExc = let observedExc =
async { async {
let! result = api.GetPathParam ("param") |> Async.AwaitTask |> Async.Catch let! result = api.GetPathParam "param" |> Async.AwaitTask |> Async.Catch
match result with match result with
| Choice1Of2 _ -> return failwith "test failure" | Choice1Of2 _ -> return failwith "test failure"
@@ -78,3 +68,103 @@ module TestBasePath =
observedExc.Message observedExc.Message
|> shouldEqual |> shouldEqual
"No base address was supplied on the type, and no BaseAddress was on the HttpClient. (Parameter 'BaseAddress')" "No base address was supplied on the type, and no BaseAddress was on the HttpClient. (Parameter 'BaseAddress')"
[<Test>]
let ``Relative base path, no base address, relative attribute`` () : unit =
do
use client = HttpClientMock.makeNoUri replyWithUrl
let api = ApiWithBasePath.make client
let exc =
Assert.Throws<AggregateException> (fun () -> api.GetPathParam("hi").Result |> ignore<string>)
exc.InnerException.Message
|> shouldEqual
"No base address was supplied on the type, and no BaseAddress was on the HttpClient. (Parameter 'BaseAddress')"
use client = HttpClientMock.make (Uri "https://whatnot.com/thing/") replyWithUrl
let api = ApiWithBasePath.make client
let result = api.GetPathParam("hi").Result
result |> shouldEqual "https://whatnot.com/thing/foo/endpoint/hi"
[<Test>]
let ``Relative base path, base address, relative attribute`` () : unit =
use client = HttpClientMock.makeNoUri replyWithUrl
let api = ApiWithBasePathAndAddress.make client
let result = api.GetPathParam("hi").Result
result |> shouldEqual "https://whatnot.com/thing/foo/endpoint/hi"
[<Test>]
let ``Absolute base path, no base address, relative attribute`` () : unit =
do
use client = HttpClientMock.makeNoUri replyWithUrl
let api = ApiWithAbsoluteBasePath.make client
let exc =
Assert.Throws<AggregateException> (fun () -> api.GetPathParam("hi").Result |> ignore<string>)
exc.InnerException.Message
|> shouldEqual
"No base address was supplied on the type, and no BaseAddress was on the HttpClient. (Parameter 'BaseAddress')"
use client = HttpClientMock.make (Uri "https://whatnot.com/thing/") replyWithUrl
let api = ApiWithAbsoluteBasePath.make client
let result = api.GetPathParam("hi").Result
result |> shouldEqual "https://whatnot.com/foo/endpoint/hi"
[<Test>]
let ``Absolute base path, base address, relative attribute`` () : unit =
use client = HttpClientMock.makeNoUri replyWithUrl
let api = ApiWithAbsoluteBasePathAndAddress.make client
let result = api.GetPathParam("hi").Result
result |> shouldEqual "https://whatnot.com/foo/endpoint/hi"
[<Test>]
let ``Relative base path, no base address, absolute attribute`` () : unit =
do
use client = HttpClientMock.makeNoUri replyWithUrl
let api = ApiWithBasePathAndAbsoluteEndpoint.make client
let exc =
Assert.Throws<AggregateException> (fun () -> api.GetPathParam("hi").Result |> ignore<string>)
exc.InnerException.Message
|> shouldEqual
"No base address was supplied on the type, and no BaseAddress was on the HttpClient. (Parameter 'BaseAddress')"
use client = HttpClientMock.make (Uri "https://whatnot.com/thing/") replyWithUrl
let api = ApiWithBasePathAndAbsoluteEndpoint.make client
let result = api.GetPathParam("hi").Result
result |> shouldEqual "https://whatnot.com/endpoint/hi"
[<Test>]
let ``Relative base path, base address, absolute attribute`` () : unit =
use client = HttpClientMock.makeNoUri replyWithUrl
let api = ApiWithBasePathAndAddressAndAbsoluteEndpoint.make client
let result = api.GetPathParam("hi").Result
result |> shouldEqual "https://whatnot.com/endpoint/hi"
[<Test>]
let ``Absolute base path, no base address, absolute attribute`` () : unit =
do
use client = HttpClientMock.makeNoUri replyWithUrl
let api = ApiWithAbsoluteBasePathAndAbsoluteEndpoint.make client
let exc =
Assert.Throws<AggregateException> (fun () -> api.GetPathParam("hi").Result |> ignore<string>)
exc.InnerException.Message
|> shouldEqual
"No base address was supplied on the type, and no BaseAddress was on the HttpClient. (Parameter 'BaseAddress')"
use client = HttpClientMock.make (Uri "https://whatnot.com/thing/") replyWithUrl
let api = ApiWithAbsoluteBasePathAndAbsoluteEndpoint.make client
let result = api.GetPathParam("hi").Result
result |> shouldEqual "https://whatnot.com/endpoint/hi"
[<Test>]
let ``Absolute base path, base address, absolute attribute`` () : unit =
use client = HttpClientMock.makeNoUri replyWithUrl
let api = ApiWithAbsoluteBasePathAndAddressAndAbsoluteEndpoint.make client
let result = api.GetPathParam("hi").Result
result |> shouldEqual "https://whatnot.com/endpoint/hi"

View File

@@ -321,15 +321,33 @@ module internal HttpClientGenerator =
|> SynExpr.createMatch baseAddress |> SynExpr.createMatch baseAddress
|> SynExpr.paren |> SynExpr.paren
let baseAddress =
match info.BasePath with
| None -> baseAddress
| Some basePath ->
[
yield baseAddress
yield
SynExpr.applyFunction
uriIdent
(SynExpr.tuple
[ basePath ; SynExpr.createLongIdent [ "System" ; "UriKind" ; "Relative" ] ])
]
|> SynExpr.tuple
|> SynExpr.applyFunction uriIdent
[ [
baseAddress yield baseAddress
SynExpr.applyFunction
uriIdent yield
(SynExpr.tuple SynExpr.applyFunction
[ uriIdent
requestUriTrailer (SynExpr.tuple
SynExpr.createLongIdent [ "System" ; "UriKind" ; "Relative" ] [
]) requestUriTrailer
SynExpr.createLongIdent [ "System" ; "UriKind" ; "Relative" ]
])
] ]
|> SynExpr.tuple |> SynExpr.tuple
|> SynExpr.applyFunction uriIdent |> SynExpr.applyFunction uriIdent
@@ -647,6 +665,15 @@ module internal HttpClientGenerator =
| _ -> None | _ -> None
) )
let insertTrailingSlash (path : SynExpr) : SynExpr =
match path |> SynExpr.stripOptionalParen with
| SynExpr.Const (SynConst.String (s, _, _), _) ->
if s.EndsWith '/' then
path
else
SynExpr.CreateConst (s + "/")
| _ -> SynExpr.plus (SynExpr.paren path) (SynExpr.CreateConst "/")
let createModule let createModule
(opens : SynOpenDeclTarget list) (opens : SynOpenDeclTarget list)
(ns : LongIdent) (ns : LongIdent)
@@ -676,8 +703,17 @@ module internal HttpClientGenerator =
"Expected constant header parameters to be of the form [<Header (key, value)>], but got more than two args" "Expected constant header parameters to be of the form [<Header (key, value)>], but got more than two args"
) )
let baseAddress = extractBaseAddress interfaceType.Attributes let baseAddress =
let basePath = extractBasePath interfaceType.Attributes extractBaseAddress interfaceType.Attributes
// We artificially insert a trailing slash because this is almost certainly
// not meant to be an endpoint itself.
|> Option.map insertTrailingSlash
let basePath =
extractBasePath interfaceType.Attributes
// We artificially insert a trailing slash because this is almost certainly
// not meant to be an endpoint itself.
|> Option.map insertTrailingSlash
let properties = let properties =
interfaceType.Properties interfaceType.Properties

View File

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