mirror of
https://github.com/Smaug123/WoofWare.Myriad
synced 2025-10-13 16:08:40 +00:00
133 lines
4.3 KiB
Markdown
133 lines
4.3 KiB
Markdown
# WoofWare.Myriad.Plugins
|
|
|
|

|
|
|
|
Some helpers in [Myriad](https://github.com/MoiraeSoftware/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 out `jsonParse : JsonNode -> 'T` methods);
|
|
* `RemoveOptions` (to strip `option` modifiers from a type).
|
|
|
|
## `JsonParse`
|
|
|
|
Takes records like this:
|
|
|
|
```fsharp
|
|
[<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:
|
|
|
|
```fsharp
|
|
|
|
/// 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#](https://github.com/dotnet/fsharp/issues/14300).
|
|
|
|
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:
|
|
|
|
```fsharp
|
|
type Foo =
|
|
{
|
|
A : int option
|
|
B : string
|
|
C : float list
|
|
}
|
|
```
|
|
|
|
and stamps out a record like this:
|
|
|
|
```fsharp
|
|
[<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?
|
|
|
|
# Detailed examples
|
|
|
|
See the tests.
|
|
For example, [PureGymDto.fs](./ConsumePlugin/PureGymDto.fs) is a real-world set of DTOs.
|