WoofWare.Myriad.Plugins
1.1.3
See the version list below for details.
dotnet add package WoofWare.Myriad.Plugins --version 1.1.3
NuGet\Install-Package WoofWare.Myriad.Plugins -Version 1.1.3
<PackageReference Include="WoofWare.Myriad.Plugins" Version="1.1.3" />
paket add WoofWare.Myriad.Plugins --version 1.1.3
#r "nuget: WoofWare.Myriad.Plugins, 1.1.3"
// Install WoofWare.Myriad.Plugins as a Cake Addin #addin nuget:?package=WoofWare.Myriad.Plugins&version=1.1.3 // Install WoofWare.Myriad.Plugins as a Cake Tool #tool nuget:?package=WoofWare.Myriad.Plugins&version=1.1.3
WoofWare.Myriad.Plugins
Some helpers in Myriad which might be useful.
These are currently somewhat experimental, and I personally am their primary customer.
The RemoveOptions
generator in particular is extremely half-baked.
Currently implemented:
JsonParse
(to stamp outjsonParse : JsonNode -> 'T
methods);RemoveOptions
(to stripoption
modifiers from a type).HttpClient
(to stamp out a RestEase-style HTTP client).
JsonParse
Takes records like this:
[<MyriadPlugin.JsonParse>]
type InnerType =
{
[<JsonPropertyName "something">]
Thing : string
}
/// My whatnot
[<MyriadPlugin.JsonParse>]
type JsonRecordType =
{
/// A thing!
A : int
/// Another thing!
B : string
[<System.Text.Json.Serialization.JsonPropertyName "hi">]
C : int list
D : InnerType
}
and stamps out parsing methods like this:
/// Module containing JSON parsing methods for the InnerType type
[<RequireQualifiedAccess>]
[<CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix)>]
module InnerType =
/// Parse from a JSON node.
let jsonParse (node: System.Text.Json.Nodes.JsonNode) : InnerType =
let Thing = node.["something"].AsValue().GetValue<string>()
{ Thing = Thing }
namespace UsePlugin
/// Module containing JSON parsing methods for the JsonRecordType type
[<RequireQualifiedAccess>]
[<CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix)>]
module JsonRecordType =
/// Parse from a JSON node.
let jsonParse (node: System.Text.Json.Nodes.JsonNode) : JsonRecordType =
let D = InnerType.jsonParse node.["d"]
let C =
node.["hi"].AsArray() |> Seq.map (fun elt -> elt.GetValue<int>()) |> List.ofSeq
let B = node.["b"].AsValue().GetValue<string>()
let A = node.["a"].AsValue().GetValue<int>()
{ A = A; B = B; C = C; D = D }
What's the point?
System.Text.Json
, in a PublishAot
context, relies on C# source generators.
The default reflection-heavy implementations have the necessary code trimmed away, and result in a runtime exception.
But C# source generators are entirely unsupported in F#.
This Myriad generator expects you to use System.Text.Json
to construct a JsonNode
, and then the generator takes over to construct a strongly-typed object.
Limitations
This source generator is enough for what I first wanted to use it for. However, there is far more that could be done.
- Make it possible to give an exact format and cultural info in date and time parsing.
- Make it possible to reject parsing if extra fields are present.
- Rather than just throwing
NullReferenceException
, print out the field name that failed. - Generally support all the
System.Text.Json
attributes.
RemoveOptions
Takes a record like this:
type Foo =
{
A : int option
B : string
C : float list
}
and stamps out a record like this:
[<RequireQualifiedAccess>]
module Foo =
type Short =
{
A : int
B : string
C : float list
}
What's the point?
The motivating example is argument parsing. An argument parser naturally wants to express "the user did not supply this, so I will provide a default". But it's not a very ergonomic experience for the programmer to deal with all these options, so this Myriad generator stamps out a type without any options, and also stamps out an appropriate constructor function.
Limitations
This generator is far from where I want it, because I haven't really spent any time on it.
- It really wants to be able to recurse into the types within the record, to strip options from them.
- It needs some sort of attribute to mark a field as not receiving this treatment.
- What do we do about discriminated unions?
HttpClient
Takes a type like this:
[<WoofWare.Myriad.Plugins.HttpClient>]
type IPureGymApi =
[<Get "v1/gyms/">]
abstract GetGyms : ?ct : CancellationToken -> Task<Gym list>
[<Get "v1/gyms/{gym_id}/attendance">]
abstract GetGymAttendance : [<Path "gym_id">] gymId : int * ?ct : CancellationToken -> Task<GymAttendance>
[<Get "v1/member">]
abstract GetMember : ?ct : CancellationToken -> Task<Member>
[<Get "v1/gyms/{gym_id}">]
abstract GetGym : [<Path "gym_id">] gymId : int * ?ct : CancellationToken -> Task<Gym>
[<Get "v1/member/activity">]
abstract GetMemberActivity : ?ct : CancellationToken -> Task<MemberActivityDto>
[<Get "v2/gymSessions/member">]
abstract GetSessions :
[<Query>] fromDate : DateTime * [<Query>] toDate : DateTime * ?ct : CancellationToken -> Task<Sessions>
and stamps out a type like this:
/// Module for constructing a REST client.
[<CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix)>]
[<RequireQualifiedAccess>]
module PureGymApi =
/// Create a REST client.
let make (client : System.Net.Http.HttpClient) : IPureGymApi =
{ new IPureGymApi with
member _.GetGyms (ct : CancellationToken option) =
async {
let! ct = Async.CancellationToken
let httpMessage =
new System.Net.Http.HttpRequestMessage (
Method = System.Net.Http.HttpMethod.Get,
RequestUri = System.Uri (client.BaseAddress.ToString () + "v1/gyms/")
)
let! response = client.SendAsync (httpMessage, ct) |> Async.AwaitTask
let response = response.EnsureSuccessStatusCode ()
let! stream = response.Content.ReadAsStreamAsync ct |> Async.AwaitTask
let! node =
System.Text.Json.Nodes.JsonNode.ParseAsync (stream, cancellationToken = ct)
|> Async.AwaitTask
return node.AsArray () |> Seq.map (fun elt -> Gym.jsonParse elt) |> List.ofSeq
}
|> (fun a -> Async.StartAsTask (a, ?cancellationToken = ct))
// (more methods here)
}
What's the point?
The motivating example is again ahead-of-time compilation: we wish to avoid the reflection which RestEase does.
Limitations
RestEase is complex, and handles a lot of different stuff.
- As of this writing,
[<Body>]
is explicitly unsupported (it throws with a TODO). - Parameters are serialised solely with
ToString
, and there's no control over this; nor is there control over encoding in any sense. - Deserialisation follows the same logic as the
JsonParse
generator, and it generally assumes you're using types whichJsonParse
is applied to. - Headers are not yet supported.
- I haven't yet worked out how to integrate this with a mocked HTTP client; you can always mock up an
HttpClient
, but I prefer to use a mock which defines a single memberSendAsync
. - Anonymous parameters are currently forbidden.
- Every function must take an optional
CancellationToken
(which is good practice anyway); so arguments are forced to be tupled. This is a won't-fix for as long as F# requires tupled arguments if any of the args are optional.
Detailed examples
See the tests. For example, PureGymDto.fs is a real-world set of DTOs.
Product | Versions Compatible and additional computed target framework versions. |
---|---|
.NET | net6.0 is compatible. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 was computed. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. |
-
net6.0
- FSharp.Core (>= 6.0.1)
- Myriad.Core (>= 0.8.3)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
Version | Downloads | Last updated |
---|---|---|
4.0.7 | 102 | 10/21/2024 |
4.0.6 | 304 | 10/14/2024 |
4.0.5 | 128 | 10/7/2024 |
4.0.4 | 78 | 10/6/2024 |
4.0.3 | 64 | 10/6/2024 |
4.0.2 | 68 | 10/6/2024 |
4.0.1 | 67 | 10/6/2024 |
3.1.4 | 77 | 10/3/2024 |
3.1.3 | 79 | 10/2/2024 |
3.1.2 | 72 | 10/2/2024 |
3.1.1 | 316 | 9/19/2024 |
3.0.2 | 82 | 9/19/2024 |
3.0.1 | 186 | 9/15/2024 |
2.3.10 | 112 | 9/15/2024 |
2.3.9 | 113 | 9/15/2024 |
2.3.8 | 114 | 9/14/2024 |
2.3.7 | 108 | 9/13/2024 |
2.3.6 | 127 | 9/12/2024 |
2.3.5 | 98 | 9/11/2024 |
2.3.4 | 121 | 9/10/2024 |
2.3.3 | 99 | 9/7/2024 |
2.3.2 | 114 | 9/5/2024 |
2.3.1 | 116 | 9/4/2024 |
2.2.7 | 108 | 9/4/2024 |
2.2.6 | 110 | 9/4/2024 |
2.2.5 | 109 | 9/4/2024 |
2.2.4 | 116 | 9/4/2024 |
2.2.3 | 116 | 9/3/2024 |
2.2.2 | 98 | 9/2/2024 |
2.2.1 | 385 | 8/26/2024 |
2.1.58 | 133 | 8/25/2024 |
2.1.57 | 228 | 8/13/2024 |
2.1.56 | 200 | 8/12/2024 |
2.1.55 | 88 | 8/4/2024 |
2.1.54 | 82 | 8/4/2024 |
2.1.53 | 323 | 7/8/2024 |
2.1.52 | 330 | 7/1/2024 |
2.1.51 | 126 | 7/1/2024 |
2.1.50 | 108 | 7/1/2024 |
2.1.49 | 121 | 6/27/2024 |
2.1.48 | 106 | 6/27/2024 |
2.1.47 | 120 | 6/24/2024 |
2.1.46 | 115 | 6/24/2024 |
2.1.45 | 496 | 6/17/2024 |
2.1.44 | 406 | 6/15/2024 |
2.1.43 | 97 | 6/10/2024 |
2.1.42 | 250 | 6/10/2024 |
2.1.41 | 96 | 6/9/2024 |
2.1.40 | 453 | 6/4/2024 |
2.1.39 | 99 | 6/3/2024 |
2.1.38 | 109 | 6/1/2024 |
2.1.37 | 85 | 5/31/2024 |
2.1.36 | 96 | 5/31/2024 |
2.1.35 | 100 | 5/31/2024 |
2.1.34 | 108 | 5/30/2024 |
2.1.33 | 104 | 5/30/2024 |
2.1.32 | 103 | 5/30/2024 |
2.1.31 | 114 | 5/30/2024 |
2.1.30 | 101 | 5/30/2024 |
2.1.29 | 104 | 5/28/2024 |
2.1.28 | 96 | 5/27/2024 |
2.1.27 | 97 | 5/24/2024 |
2.1.26 | 99 | 5/24/2024 |
2.1.25 | 96 | 5/24/2024 |
2.1.24 | 112 | 5/20/2024 |
2.1.23 | 98 | 5/20/2024 |
2.1.22 | 128 | 5/6/2024 |
2.1.21 | 103 | 4/30/2024 |
2.1.20 | 115 | 4/29/2024 |
2.1.19 | 101 | 4/29/2024 |
2.1.18 | 105 | 4/22/2024 |
2.1.17 | 102 | 4/17/2024 |
2.1.16 | 103 | 4/16/2024 |
2.1.15 | 109 | 4/16/2024 |
2.1.14 | 109 | 4/15/2024 |
2.1.13 | 125 | 3/19/2024 |
2.1.12 | 114 | 3/11/2024 |
2.1.11 | 108 | 3/4/2024 |
2.1.10 | 129 | 2/26/2024 |
2.1.9 | 123 | 2/26/2024 |
2.1.8 | 113 | 2/25/2024 |
2.1.7 | 119 | 2/25/2024 |
2.1.6 | 111 | 2/25/2024 |
2.1.5 | 105 | 2/19/2024 |
2.1.4 | 110 | 2/19/2024 |
2.1.3 | 102 | 2/18/2024 |
2.1.2 | 98 | 2/18/2024 |
2.1.1 | 107 | 2/17/2024 |
2.0.9 | 104 | 2/14/2024 |
2.0.8 | 118 | 2/13/2024 |
2.0.7 | 105 | 2/13/2024 |
2.0.6 | 105 | 2/13/2024 |
2.0.5 | 123 | 2/12/2024 |
2.0.4 | 105 | 2/7/2024 |
2.0.3 | 94 | 2/7/2024 |
2.0.2 | 97 | 2/7/2024 |
2.0.1 | 115 | 2/7/2024 |
1.4.15 | 116 | 2/6/2024 |
1.4.14 | 114 | 2/6/2024 |
1.4.13 | 96 | 2/6/2024 |
1.4.12 | 101 | 2/6/2024 |
1.4.11 | 120 | 2/6/2024 |
1.4.10 | 96 | 2/5/2024 |
1.4.9 | 106 | 1/30/2024 |
1.4.8 | 117 | 1/29/2024 |
1.4.7 | 98 | 1/29/2024 |
1.4.6 | 100 | 1/29/2024 |
1.4.5 | 100 | 1/28/2024 |
1.4.4 | 88 | 1/28/2024 |
1.4.3 | 107 | 1/26/2024 |
1.4.2 | 102 | 1/26/2024 |
1.4.1 | 86 | 1/26/2024 |
1.3.5 | 90 | 1/25/2024 |
1.3.4 | 119 | 1/15/2024 |
1.3.3 | 112 | 1/15/2024 |
1.3.2 | 125 | 1/8/2024 |
1.3.1 | 122 | 1/8/2024 |
1.2.3 | 122 | 1/3/2024 |
1.2.2 | 123 | 12/31/2023 |
1.2.1 | 128 | 12/30/2023 |
1.1.15 | 136 | 12/30/2023 |
1.1.14 | 136 | 12/30/2023 |
1.1.13 | 133 | 12/30/2023 |
1.1.12 | 130 | 12/30/2023 |
1.1.11 | 114 | 12/30/2023 |
1.1.10 | 137 | 12/29/2023 |
1.1.9 | 130 | 12/29/2023 |
1.1.8 | 129 | 12/29/2023 |
1.1.7 | 136 | 12/29/2023 |
1.1.6 | 141 | 12/29/2023 |
1.1.5 | 135 | 12/29/2023 |
1.1.4 | 129 | 12/29/2023 |
1.1.3 | 117 | 12/29/2023 |
1.1.2 | 121 | 12/28/2023 |
1.1.1 | 121 | 12/28/2023 |
1.0.6 | 123 | 12/28/2023 |
1.0.5 | 121 | 12/28/2023 |
1.0.4 | 125 | 12/27/2023 |