mirror of
https://github.com/Smaug123/WoofWare.Myriad
synced 2025-10-09 14:08:39 +00:00
Compare commits
19 Commits
WoofWare.M
...
WoofWare.M
Author | SHA1 | Date | |
---|---|---|---|
|
0a1783d6ed | ||
|
9a3ebbf28f | ||
|
e22525c200 | ||
|
09b7109c84 | ||
|
693b95106a | ||
|
49ecfbf5e5 | ||
|
5748ac3d5b | ||
|
913959a740 | ||
|
93ffc065cd | ||
|
d14efba7e7 | ||
|
f5cf0b79dd | ||
|
029e3746bb | ||
|
8ae749c529 | ||
|
e4cbab3209 | ||
|
bdce82fb7a | ||
|
8f9f933971 | ||
|
3a55ba1242 | ||
|
047b2eda99 | ||
|
2220f88053 |
@@ -3,7 +3,7 @@
|
|||||||
"isRoot": true,
|
"isRoot": true,
|
||||||
"tools": {
|
"tools": {
|
||||||
"fantomas": {
|
"fantomas": {
|
||||||
"version": "6.3.11",
|
"version": "6.3.12",
|
||||||
"commands": [
|
"commands": [
|
||||||
"fantomas"
|
"fantomas"
|
||||||
]
|
]
|
||||||
|
1
.fantomasignore
Normal file
1
.fantomasignore
Normal file
@@ -0,0 +1 @@
|
|||||||
|
.direnv/
|
4
.github/workflows/dotnet.yaml
vendored
4
.github/workflows/dotnet.yaml
vendored
@@ -241,7 +241,7 @@ jobs:
|
|||||||
name: nuget-package-attribute
|
name: nuget-package-attribute
|
||||||
path: packed
|
path: packed
|
||||||
- name: Attest Build Provenance
|
- name: Attest Build Provenance
|
||||||
uses: actions/attest-build-provenance@6149ea5740be74af77f260b9db67e633f6b0a9a1 # v1.4.2
|
uses: actions/attest-build-provenance@1c608d11d69870c2092266b3f9a6f3abbf17002c # v1.4.3
|
||||||
with:
|
with:
|
||||||
subject-path: "packed/*.nupkg"
|
subject-path: "packed/*.nupkg"
|
||||||
|
|
||||||
@@ -260,7 +260,7 @@ jobs:
|
|||||||
name: nuget-package-plugin
|
name: nuget-package-plugin
|
||||||
path: packed
|
path: packed
|
||||||
- name: Attest Build Provenance
|
- name: Attest Build Provenance
|
||||||
uses: actions/attest-build-provenance@6149ea5740be74af77f260b9db67e633f6b0a9a1 # v1.4.2
|
uses: actions/attest-build-provenance@1c608d11d69870c2092266b3f9a6f3abbf17002c # v1.4.3
|
||||||
with:
|
with:
|
||||||
subject-path: "packed/*.nupkg"
|
subject-path: "packed/*.nupkg"
|
||||||
|
|
||||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@@ -11,3 +11,4 @@ result
|
|||||||
analysis.sarif
|
analysis.sarif
|
||||||
.direnv/
|
.direnv/
|
||||||
.venv/
|
.venv/
|
||||||
|
.vs/
|
||||||
|
13
CHANGELOG.md
13
CHANGELOG.md
@@ -1,5 +1,18 @@
|
|||||||
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
|
||||||
|
|
||||||
|
New generator: `ArgParser`, a basic reflection-free argument parser.
|
||||||
|
|
||||||
# WoofWare.Myriad.Plugins 2.1.45, WoofWare.Myriad.Plugins.Attributes 3.1.7
|
# WoofWare.Myriad.Plugins 2.1.45, WoofWare.Myriad.Plugins.Attributes 3.1.7
|
||||||
|
|
||||||
The NuGet packages are now attested to through [GitHub Attestations](https://github.blog/2024-05-02-introducing-artifact-attestations-now-in-public-beta/).
|
The NuGet packages are now attested to through [GitHub Attestations](https://github.blog/2024-05-02-introducing-artifact-attestations-now-in-public-beta/).
|
||||||
|
@@ -93,3 +93,145 @@ type DatesAndTimes =
|
|||||||
[<InvariantCulture ; ParseExact @"hh\:mm\:ss">]
|
[<InvariantCulture ; ParseExact @"hh\:mm\:ss">]
|
||||||
InvariantExact : TimeSpan
|
InvariantExact : TimeSpan
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ChildRecord =
|
||||||
|
{
|
||||||
|
Thing1 : int
|
||||||
|
Thing2 : string
|
||||||
|
}
|
||||||
|
|
||||||
|
[<ArgParser true>]
|
||||||
|
type ParentRecord =
|
||||||
|
{
|
||||||
|
Child : ChildRecord
|
||||||
|
AndAnother : bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type ChildRecordWithPositional =
|
||||||
|
{
|
||||||
|
Thing1 : int
|
||||||
|
[<PositionalArgs>]
|
||||||
|
Thing2 : Uri list
|
||||||
|
}
|
||||||
|
|
||||||
|
[<ArgParser true>]
|
||||||
|
type ParentRecordChildPos =
|
||||||
|
{
|
||||||
|
Child : ChildRecordWithPositional
|
||||||
|
AndAnother : bool
|
||||||
|
}
|
||||||
|
|
||||||
|
[<ArgParser true>]
|
||||||
|
type ParentRecordSelfPos =
|
||||||
|
{
|
||||||
|
Child : ChildRecord
|
||||||
|
[<PositionalArgs>]
|
||||||
|
AndAnother : bool list
|
||||||
|
}
|
||||||
|
|
||||||
|
[<ArgParser true>]
|
||||||
|
type ChoicePositionals =
|
||||||
|
{
|
||||||
|
[<PositionalArgs>]
|
||||||
|
Args : Choice<string, string> list
|
||||||
|
}
|
||||||
|
|
||||||
|
[<ArgParser true>]
|
||||||
|
type ContainsBoolEnvVar =
|
||||||
|
{
|
||||||
|
[<ArgumentDefaultEnvironmentVariable "CONSUMEPLUGIN_THINGS">]
|
||||||
|
BoolVar : Choice<bool, bool>
|
||||||
|
}
|
||||||
|
|
||||||
|
[<RequireQualifiedAccess>]
|
||||||
|
module Consts =
|
||||||
|
[<Literal>]
|
||||||
|
let FALSE = false
|
||||||
|
|
||||||
|
[<Literal>]
|
||||||
|
let TRUE = true
|
||||||
|
|
||||||
|
type DryRunMode =
|
||||||
|
| [<ArgumentFlag(Consts.FALSE)>] Wet
|
||||||
|
| [<ArgumentFlag true>] Dry
|
||||||
|
|
||||||
|
[<ArgParser true>]
|
||||||
|
type WithFlagDu =
|
||||||
|
{
|
||||||
|
DryRun : DryRunMode
|
||||||
|
}
|
||||||
|
|
||||||
|
[<ArgParser true>]
|
||||||
|
type ContainsFlagEnvVar =
|
||||||
|
{
|
||||||
|
// This phrasing is odd, but it's for a test. Nobody's really going to have `--dry-run`
|
||||||
|
// controlled by an env var!
|
||||||
|
[<ArgumentDefaultEnvironmentVariable "CONSUMEPLUGIN_THINGS">]
|
||||||
|
DryRun : Choice<DryRunMode, DryRunMode>
|
||||||
|
}
|
||||||
|
|
||||||
|
[<ArgParser true>]
|
||||||
|
type ContainsFlagDefaultValue =
|
||||||
|
{
|
||||||
|
[<ArgumentDefaultFunction>]
|
||||||
|
DryRun : Choice<DryRunMode, DryRunMode>
|
||||||
|
}
|
||||||
|
|
||||||
|
static member DefaultDryRun () = DryRunMode.Wet
|
||||||
|
|
||||||
|
[<ArgParser true>]
|
||||||
|
type ManyLongForms =
|
||||||
|
{
|
||||||
|
[<ArgumentLongForm "do-something-else">]
|
||||||
|
[<ArgumentLongForm "anotherarg">]
|
||||||
|
DoTheThing : string
|
||||||
|
|
||||||
|
[<ArgumentLongForm "turn-it-on">]
|
||||||
|
[<ArgumentLongForm "dont-turn-it-off">]
|
||||||
|
SomeFlag : bool
|
||||||
|
}
|
||||||
|
|
||||||
|
[<RequireQualifiedAccess>]
|
||||||
|
type private IrrelevantDu =
|
||||||
|
| Foo
|
||||||
|
| Bar
|
||||||
|
|
||||||
|
[<ArgParser true>]
|
||||||
|
type FlagsIntoPositionalArgs =
|
||||||
|
{
|
||||||
|
A : string
|
||||||
|
[<PositionalArgs true>]
|
||||||
|
GrabEverything : string list
|
||||||
|
}
|
||||||
|
|
||||||
|
[<ArgParser true>]
|
||||||
|
type FlagsIntoPositionalArgsChoice =
|
||||||
|
{
|
||||||
|
A : string
|
||||||
|
[<PositionalArgs true>]
|
||||||
|
GrabEverything : Choice<string, string> list
|
||||||
|
}
|
||||||
|
|
||||||
|
[<ArgParser true>]
|
||||||
|
type FlagsIntoPositionalArgsInt =
|
||||||
|
{
|
||||||
|
A : string
|
||||||
|
[<PositionalArgs true>]
|
||||||
|
GrabEverything : int list
|
||||||
|
}
|
||||||
|
|
||||||
|
[<ArgParser true>]
|
||||||
|
type FlagsIntoPositionalArgsIntChoice =
|
||||||
|
{
|
||||||
|
A : string
|
||||||
|
[<PositionalArgs true>]
|
||||||
|
GrabEverything : Choice<int, int> list
|
||||||
|
}
|
||||||
|
|
||||||
|
[<ArgParser true>]
|
||||||
|
type FlagsIntoPositionalArgs' =
|
||||||
|
{
|
||||||
|
A : string
|
||||||
|
[<PositionalArgs false>]
|
||||||
|
DontGrabEverything : string list
|
||||||
|
}
|
||||||
|
File diff suppressed because it is too large
Load Diff
@@ -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 =
|
||||||
|
@@ -210,6 +210,8 @@ module JsonRecordTypeWithBothJsonSerializeExtension =
|
|||||||
|> (fun field -> field.ToString "o" |> System.Text.Json.Nodes.JsonValue.Create<string>))
|
|> (fun field -> field.ToString "o" |> System.Text.Json.Nodes.JsonValue.Create<string>))
|
||||||
)
|
)
|
||||||
|
|
||||||
|
node.Add ("unit", (input.Unit |> (fun value -> System.Text.Json.Nodes.JsonObject ())))
|
||||||
|
|
||||||
node :> _
|
node :> _
|
||||||
namespace ConsumePlugin
|
namespace ConsumePlugin
|
||||||
|
|
||||||
@@ -291,6 +293,60 @@ module FooJsonSerializeExtension =
|
|||||||
)
|
)
|
||||||
|
|
||||||
node :> _
|
node :> _
|
||||||
|
namespace ConsumePlugin
|
||||||
|
|
||||||
|
open System
|
||||||
|
open System.Collections.Generic
|
||||||
|
open System.Text.Json.Serialization
|
||||||
|
|
||||||
|
/// Module containing JSON serializing extension members for the CollectRemaining type
|
||||||
|
[<AutoOpen>]
|
||||||
|
module CollectRemainingJsonSerializeExtension =
|
||||||
|
/// Extension methods for JSON parsing
|
||||||
|
type CollectRemaining with
|
||||||
|
|
||||||
|
/// Serialize to a JSON node
|
||||||
|
static member toJsonNode (input : CollectRemaining) : System.Text.Json.Nodes.JsonNode =
|
||||||
|
let node = System.Text.Json.Nodes.JsonObject ()
|
||||||
|
|
||||||
|
do
|
||||||
|
node.Add (
|
||||||
|
"message",
|
||||||
|
(input.Message
|
||||||
|
|> (fun field ->
|
||||||
|
match field with
|
||||||
|
| None -> null :> System.Text.Json.Nodes.JsonNode
|
||||||
|
| Some field -> HeaderAndValue.toJsonNode field
|
||||||
|
))
|
||||||
|
)
|
||||||
|
|
||||||
|
for KeyValue (key, value) in input.Rest do
|
||||||
|
node.Add (key, id value)
|
||||||
|
|
||||||
|
node :> _
|
||||||
|
namespace ConsumePlugin
|
||||||
|
|
||||||
|
open System
|
||||||
|
open System.Collections.Generic
|
||||||
|
open System.Text.Json.Serialization
|
||||||
|
|
||||||
|
/// Module containing JSON serializing extension members for the OuterCollectRemaining type
|
||||||
|
[<AutoOpen>]
|
||||||
|
module OuterCollectRemainingJsonSerializeExtension =
|
||||||
|
/// Extension methods for JSON parsing
|
||||||
|
type OuterCollectRemaining with
|
||||||
|
|
||||||
|
/// Serialize to a JSON node
|
||||||
|
static member toJsonNode (input : OuterCollectRemaining) : System.Text.Json.Nodes.JsonNode =
|
||||||
|
let node = System.Text.Json.Nodes.JsonObject ()
|
||||||
|
|
||||||
|
do
|
||||||
|
for KeyValue (key, value) in input.Others do
|
||||||
|
node.Add (key, System.Text.Json.Nodes.JsonValue.Create<int> value)
|
||||||
|
|
||||||
|
node.Add ("remaining", (input.Remaining |> CollectRemaining.toJsonNode))
|
||||||
|
|
||||||
|
node :> _
|
||||||
|
|
||||||
namespace ConsumePlugin
|
namespace ConsumePlugin
|
||||||
|
|
||||||
@@ -424,6 +480,8 @@ module JsonRecordTypeWithBothJsonParseExtension =
|
|||||||
|
|
||||||
/// Parse from a JSON node.
|
/// Parse from a JSON node.
|
||||||
static member jsonParse (node : System.Text.Json.Nodes.JsonNode) : JsonRecordTypeWithBoth =
|
static member jsonParse (node : System.Text.Json.Nodes.JsonNode) : JsonRecordTypeWithBoth =
|
||||||
|
let arg_21 = ()
|
||||||
|
|
||||||
let arg_20 =
|
let arg_20 =
|
||||||
(match node.["timestamp"] with
|
(match node.["timestamp"] with
|
||||||
| null ->
|
| null ->
|
||||||
@@ -705,6 +763,7 @@ module JsonRecordTypeWithBothJsonParseExtension =
|
|||||||
IntMeasureNullable = arg_18
|
IntMeasureNullable = arg_18
|
||||||
Enum = arg_19
|
Enum = arg_19
|
||||||
Timestamp = arg_20
|
Timestamp = arg_20
|
||||||
|
Unit = arg_21
|
||||||
}
|
}
|
||||||
namespace ConsumePlugin
|
namespace ConsumePlugin
|
||||||
|
|
||||||
@@ -842,3 +901,83 @@ module FooJsonParseExtension =
|
|||||||
{
|
{
|
||||||
Message = arg_0
|
Message = arg_0
|
||||||
}
|
}
|
||||||
|
namespace ConsumePlugin
|
||||||
|
|
||||||
|
/// Module containing JSON parsing extension members for the CollectRemaining type
|
||||||
|
[<AutoOpen>]
|
||||||
|
module CollectRemainingJsonParseExtension =
|
||||||
|
/// Extension methods for JSON parsing
|
||||||
|
type CollectRemaining with
|
||||||
|
|
||||||
|
/// Parse from a JSON node.
|
||||||
|
static member jsonParse (node : System.Text.Json.Nodes.JsonNode) : CollectRemaining =
|
||||||
|
let arg_1 =
|
||||||
|
let result =
|
||||||
|
System.Collections.Generic.Dictionary<string, System.Text.Json.Nodes.JsonNode> ()
|
||||||
|
|
||||||
|
let node = node.AsObject ()
|
||||||
|
|
||||||
|
for KeyValue (key, value) in node do
|
||||||
|
if key = "message" then () else result.Add (key, node.[key])
|
||||||
|
|
||||||
|
result
|
||||||
|
|
||||||
|
let arg_0 =
|
||||||
|
match node.["message"] with
|
||||||
|
| null -> None
|
||||||
|
| v -> HeaderAndValue.jsonParse v |> Some
|
||||||
|
|
||||||
|
{
|
||||||
|
Message = arg_0
|
||||||
|
Rest = arg_1
|
||||||
|
}
|
||||||
|
namespace ConsumePlugin
|
||||||
|
|
||||||
|
/// Module containing JSON parsing extension members for the OuterCollectRemaining type
|
||||||
|
[<AutoOpen>]
|
||||||
|
module OuterCollectRemainingJsonParseExtension =
|
||||||
|
/// Extension methods for JSON parsing
|
||||||
|
type OuterCollectRemaining with
|
||||||
|
|
||||||
|
/// Parse from a JSON node.
|
||||||
|
static member jsonParse (node : System.Text.Json.Nodes.JsonNode) : OuterCollectRemaining =
|
||||||
|
let arg_1 =
|
||||||
|
CollectRemaining.jsonParse (
|
||||||
|
match node.["remaining"] with
|
||||||
|
| null ->
|
||||||
|
raise (
|
||||||
|
System.Collections.Generic.KeyNotFoundException (
|
||||||
|
sprintf "Required key '%s' not found on JSON object" ("remaining")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
| v -> v
|
||||||
|
)
|
||||||
|
|
||||||
|
let arg_0 =
|
||||||
|
let result = System.Collections.Generic.Dictionary<string, int> ()
|
||||||
|
let node = node.AsObject ()
|
||||||
|
|
||||||
|
for KeyValue (key, value) in node do
|
||||||
|
if key = "remaining" then
|
||||||
|
()
|
||||||
|
else
|
||||||
|
result.Add (
|
||||||
|
key,
|
||||||
|
(match node.[key] with
|
||||||
|
| null ->
|
||||||
|
raise (
|
||||||
|
System.Collections.Generic.KeyNotFoundException (
|
||||||
|
sprintf "Required key '%s' not found on JSON object" (key)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
| v -> v)
|
||||||
|
.AsValue()
|
||||||
|
.GetValue<System.Int32> ()
|
||||||
|
)
|
||||||
|
|
||||||
|
result
|
||||||
|
|
||||||
|
{
|
||||||
|
Others = arg_0
|
||||||
|
Remaining = arg_1
|
||||||
|
}
|
||||||
|
@@ -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 =
|
||||||
|
@@ -50,6 +50,7 @@ type JsonRecordTypeWithBoth =
|
|||||||
IntMeasureNullable : int<measure> Nullable
|
IntMeasureNullable : int<measure> Nullable
|
||||||
Enum : SomeEnum
|
Enum : SomeEnum
|
||||||
Timestamp : DateTimeOffset
|
Timestamp : DateTimeOffset
|
||||||
|
Unit : unit
|
||||||
}
|
}
|
||||||
|
|
||||||
[<WoofWare.Myriad.Plugins.JsonSerialize true>]
|
[<WoofWare.Myriad.Plugins.JsonSerialize true>]
|
||||||
@@ -73,3 +74,21 @@ type Foo =
|
|||||||
{
|
{
|
||||||
Message : HeaderAndValue option
|
Message : HeaderAndValue option
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[<WoofWare.Myriad.Plugins.JsonSerialize true>]
|
||||||
|
[<WoofWare.Myriad.Plugins.JsonParse true>]
|
||||||
|
type CollectRemaining =
|
||||||
|
{
|
||||||
|
Message : HeaderAndValue option
|
||||||
|
[<JsonExtensionData>]
|
||||||
|
Rest : Dictionary<string, System.Text.Json.Nodes.JsonNode>
|
||||||
|
}
|
||||||
|
|
||||||
|
[<WoofWare.Myriad.Plugins.JsonSerialize true>]
|
||||||
|
[<WoofWare.Myriad.Plugins.JsonParse true>]
|
||||||
|
type OuterCollectRemaining =
|
||||||
|
{
|
||||||
|
[<JsonExtensionData>]
|
||||||
|
Others : Dictionary<string, int>
|
||||||
|
Remaining : CollectRemaining
|
||||||
|
}
|
||||||
|
@@ -19,9 +19,22 @@ type ArgParserAttribute (isExtensionMethod : bool) =
|
|||||||
|
|
||||||
/// Attribute indicating that this field shall accumulate all unmatched args,
|
/// Attribute indicating that this field shall accumulate all unmatched args,
|
||||||
/// as well as any that appear after a bare `--`.
|
/// as well as any that appear after a bare `--`.
|
||||||
type PositionalArgsAttribute () =
|
///
|
||||||
|
/// Set `includeFlagLike = true` to include args that begin `--` in the
|
||||||
|
/// positional args.
|
||||||
|
/// (By default, `includeFlagLike = false` and we throw when encountering
|
||||||
|
/// an argument which looks like a flag but which we don't recognise.)
|
||||||
|
/// We will still interpret `--help` as requesting help, unless it comes after
|
||||||
|
/// a standalone `--` separator.
|
||||||
|
type PositionalArgsAttribute (includeFlagLike : bool) =
|
||||||
inherit Attribute ()
|
inherit Attribute ()
|
||||||
|
|
||||||
|
/// The default value of `isExtensionMethod`, the optional argument to the ArgParserAttribute constructor.
|
||||||
|
static member DefaultIncludeFlagLike = false
|
||||||
|
|
||||||
|
/// Shorthand for the "includeFlagLike = false" constructor; see documentation there for details.
|
||||||
|
new () = PositionalArgsAttribute PositionalArgsAttribute.DefaultIncludeFlagLike
|
||||||
|
|
||||||
/// Attribute indicating that this field shall have a default value derived
|
/// Attribute indicating that this field shall have a default value derived
|
||||||
/// from calling an appropriately named static method on the type.
|
/// from calling an appropriately named static method on the type.
|
||||||
///
|
///
|
||||||
@@ -61,3 +74,24 @@ type ParseExactAttribute (format : string) =
|
|||||||
/// `TimeSpan.ParseExact (s, @"hh\:mm\:ss", CultureInfo.InvariantCulture).
|
/// `TimeSpan.ParseExact (s, @"hh\:mm\:ss", CultureInfo.InvariantCulture).
|
||||||
type InvariantCultureAttribute () =
|
type InvariantCultureAttribute () =
|
||||||
inherit Attribute ()
|
inherit Attribute ()
|
||||||
|
|
||||||
|
/// Attribute placed on a field of a two-case no-data discriminated union, indicating that this is "basically a bool".
|
||||||
|
/// For example: `type DryRun = | [<ArgumentFlag true>] Dry | [<ArgumentFlag false>] Wet`
|
||||||
|
/// A record with `{ DryRun : DryRun }` will then be parsed like `{ DryRun : bool }` (so the user supplies `--dry-run`),
|
||||||
|
/// but that you get this strongly-typed value directly in the code (so you `match args.DryRun with | DryRun.Dry ...`).
|
||||||
|
///
|
||||||
|
/// You must put this attribute on both cases of the discriminated union, with opposite values in each case.
|
||||||
|
type ArgumentFlagAttribute (flagValue : bool) =
|
||||||
|
inherit Attribute ()
|
||||||
|
|
||||||
|
/// Attribute placed on a field of a record to specify a different long form from the default. If you place this
|
||||||
|
/// attribute, you won't get the default: ArgFoo would normally be expressed as `--arg-foo`, but if you instead
|
||||||
|
/// say `[<ArgumentLongForm "thingy-blah">]` or `[<ArgumentLongForm "thingy">]`, you instead use `--thingy-blah`
|
||||||
|
/// or `--thingy` respectively.
|
||||||
|
///
|
||||||
|
/// You can place this argument multiple times.
|
||||||
|
///
|
||||||
|
/// Omit the initial `--` that you expect the user to type.
|
||||||
|
[<AttributeUsage(AttributeTargets.Field, AllowMultiple = true)>]
|
||||||
|
type ArgumentLongForm (s : string) =
|
||||||
|
inherit Attribute ()
|
||||||
|
@@ -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 ()
|
||||||
|
@@ -7,8 +7,12 @@ WoofWare.Myriad.Plugins.ArgumentDefaultEnvironmentVariableAttribute inherit Syst
|
|||||||
WoofWare.Myriad.Plugins.ArgumentDefaultEnvironmentVariableAttribute..ctor [constructor]: string
|
WoofWare.Myriad.Plugins.ArgumentDefaultEnvironmentVariableAttribute..ctor [constructor]: string
|
||||||
WoofWare.Myriad.Plugins.ArgumentDefaultFunctionAttribute inherit System.Attribute
|
WoofWare.Myriad.Plugins.ArgumentDefaultFunctionAttribute inherit System.Attribute
|
||||||
WoofWare.Myriad.Plugins.ArgumentDefaultFunctionAttribute..ctor [constructor]: unit
|
WoofWare.Myriad.Plugins.ArgumentDefaultFunctionAttribute..ctor [constructor]: unit
|
||||||
|
WoofWare.Myriad.Plugins.ArgumentFlagAttribute inherit System.Attribute
|
||||||
|
WoofWare.Myriad.Plugins.ArgumentFlagAttribute..ctor [constructor]: bool
|
||||||
WoofWare.Myriad.Plugins.ArgumentHelpTextAttribute inherit System.Attribute
|
WoofWare.Myriad.Plugins.ArgumentHelpTextAttribute inherit System.Attribute
|
||||||
WoofWare.Myriad.Plugins.ArgumentHelpTextAttribute..ctor [constructor]: string
|
WoofWare.Myriad.Plugins.ArgumentHelpTextAttribute..ctor [constructor]: string
|
||||||
|
WoofWare.Myriad.Plugins.ArgumentLongForm inherit System.Attribute
|
||||||
|
WoofWare.Myriad.Plugins.ArgumentLongForm..ctor [constructor]: string
|
||||||
WoofWare.Myriad.Plugins.CreateCatamorphismAttribute inherit System.Attribute
|
WoofWare.Myriad.Plugins.CreateCatamorphismAttribute inherit System.Attribute
|
||||||
WoofWare.Myriad.Plugins.CreateCatamorphismAttribute..ctor [constructor]: string
|
WoofWare.Myriad.Plugins.CreateCatamorphismAttribute..ctor [constructor]: string
|
||||||
WoofWare.Myriad.Plugins.GenerateMockAttribute inherit System.Attribute
|
WoofWare.Myriad.Plugins.GenerateMockAttribute inherit System.Attribute
|
||||||
@@ -36,12 +40,19 @@ WoofWare.Myriad.Plugins.JsonSerializeAttribute.get_DefaultIsExtensionMethod [sta
|
|||||||
WoofWare.Myriad.Plugins.ParseExactAttribute inherit System.Attribute
|
WoofWare.Myriad.Plugins.ParseExactAttribute inherit System.Attribute
|
||||||
WoofWare.Myriad.Plugins.ParseExactAttribute..ctor [constructor]: string
|
WoofWare.Myriad.Plugins.ParseExactAttribute..ctor [constructor]: string
|
||||||
WoofWare.Myriad.Plugins.PositionalArgsAttribute inherit System.Attribute
|
WoofWare.Myriad.Plugins.PositionalArgsAttribute inherit System.Attribute
|
||||||
|
WoofWare.Myriad.Plugins.PositionalArgsAttribute..ctor [constructor]: bool
|
||||||
WoofWare.Myriad.Plugins.PositionalArgsAttribute..ctor [constructor]: unit
|
WoofWare.Myriad.Plugins.PositionalArgsAttribute..ctor [constructor]: unit
|
||||||
|
WoofWare.Myriad.Plugins.PositionalArgsAttribute.DefaultIncludeFlagLike [static property]: [read-only] bool
|
||||||
|
WoofWare.Myriad.Plugins.PositionalArgsAttribute.get_DefaultIncludeFlagLike [static method]: unit -> bool
|
||||||
WoofWare.Myriad.Plugins.RemoveOptionsAttribute inherit System.Attribute
|
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 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
|
||||||
|
@@ -1,10 +1,15 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
|
||||||
<IsPackable>false</IsPackable>
|
<IsPackable>false</IsPackable>
|
||||||
<IsTestProject>true</IsTestProject>
|
<IsTestProject>true</IsTestProject>
|
||||||
|
<!--
|
||||||
|
Known high severity vulnerability
|
||||||
|
I have not yet seen a single instance where I care about this warning
|
||||||
|
-->
|
||||||
|
<NoWarn>$(NoWarn),NU1903</NoWarn>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
@@ -13,7 +18,7 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="ApiSurface" Version="4.1.5" />
|
<PackageReference Include="ApiSurface" Version="4.1.5" />
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.0"/>
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.1"/>
|
||||||
<PackageReference Include="NUnit" Version="4.2.2"/>
|
<PackageReference Include="NUnit" Version="4.2.2"/>
|
||||||
<PackageReference Include="NUnit3TestAdapter" Version="4.6.0"/>
|
<PackageReference Include="NUnit3TestAdapter" Version="4.6.0"/>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"version": "3.2",
|
"version": "3.6",
|
||||||
"publicReleaseRefSpec": [
|
"publicReleaseRefSpec": [
|
||||||
"^refs/heads/main$"
|
"^refs/heads/main$"
|
||||||
],
|
],
|
||||||
@@ -12,4 +12,4 @@
|
|||||||
"./",
|
"./",
|
||||||
":^Test"
|
":^Test"
|
||||||
]
|
]
|
||||||
}
|
}
|
@@ -341,3 +341,366 @@ Input string was not in a correct format. (at arg --exact=11:34)
|
|||||||
Required argument '--exact' received no value"""
|
Required argument '--exact' received no value"""
|
||||||
|
|
||||||
count.Value |> shouldEqual 0
|
count.Value |> shouldEqual 0
|
||||||
|
|
||||||
|
[<Test>]
|
||||||
|
let ``Can consume stacked record without positionals`` () =
|
||||||
|
let getEnvVar (_ : string) = failwith "should not call"
|
||||||
|
|
||||||
|
let parsed =
|
||||||
|
ParentRecord.parse' getEnvVar [ "--and-another=true" ; "--thing1=9" ; "--thing2=a thing!" ]
|
||||||
|
|
||||||
|
parsed
|
||||||
|
|> shouldEqual
|
||||||
|
{
|
||||||
|
Child =
|
||||||
|
{
|
||||||
|
Thing1 = 9
|
||||||
|
Thing2 = "a thing!"
|
||||||
|
}
|
||||||
|
AndAnother = true
|
||||||
|
}
|
||||||
|
|
||||||
|
[<Test>]
|
||||||
|
let ``Can consume stacked record, child has positionals`` () =
|
||||||
|
let getEnvVar (_ : string) = failwith "should not call"
|
||||||
|
|
||||||
|
let parsed =
|
||||||
|
ParentRecordChildPos.parse'
|
||||||
|
getEnvVar
|
||||||
|
[
|
||||||
|
"--and-another=true"
|
||||||
|
"--thing1=9"
|
||||||
|
"--thing2=https://example.com"
|
||||||
|
"--thing2=http://example.com"
|
||||||
|
]
|
||||||
|
|
||||||
|
parsed.AndAnother |> shouldEqual true
|
||||||
|
parsed.Child.Thing1 |> shouldEqual 9
|
||||||
|
|
||||||
|
parsed.Child.Thing2
|
||||||
|
|> List.map (fun (x : Uri) -> x.ToString ())
|
||||||
|
|> shouldEqual [ "https://example.com/" ; "http://example.com/" ]
|
||||||
|
|
||||||
|
[<Test>]
|
||||||
|
let ``Can consume stacked record, child has no positionals, parent has positionals`` () =
|
||||||
|
let getEnvVar (_ : string) = failwith "should not call"
|
||||||
|
|
||||||
|
let parsed =
|
||||||
|
ParentRecordSelfPos.parse'
|
||||||
|
getEnvVar
|
||||||
|
[
|
||||||
|
"--and-another=true"
|
||||||
|
"--and-another=false"
|
||||||
|
"--and-another=true"
|
||||||
|
"--thing1=9"
|
||||||
|
"--thing2=some"
|
||||||
|
]
|
||||||
|
|
||||||
|
parsed
|
||||||
|
|> shouldEqual
|
||||||
|
{
|
||||||
|
Child =
|
||||||
|
{
|
||||||
|
Thing1 = 9
|
||||||
|
Thing2 = "some"
|
||||||
|
}
|
||||||
|
AndAnother = [ true ; false ; true ]
|
||||||
|
}
|
||||||
|
|
||||||
|
[<Test>]
|
||||||
|
let ``Help text for stacked records`` () =
|
||||||
|
let getEnvVar (_ : string) = failwith "should not call"
|
||||||
|
|
||||||
|
let exc =
|
||||||
|
Assert.Throws<exn> (fun () ->
|
||||||
|
ParentRecordSelfPos.parse' getEnvVar [ "--help" ] |> ignore<ParentRecordSelfPos>
|
||||||
|
)
|
||||||
|
|
||||||
|
exc.Message
|
||||||
|
|> shouldEqual
|
||||||
|
"""Help text requested.
|
||||||
|
--thing1 int32
|
||||||
|
--thing2 string
|
||||||
|
--and-another bool (positional args) (can be repeated)"""
|
||||||
|
|
||||||
|
[<Test>]
|
||||||
|
let ``Positionals are tagged with Choice`` () =
|
||||||
|
let getEnvVar (_ : string) = failwith "should not call"
|
||||||
|
|
||||||
|
ChoicePositionals.parse' getEnvVar [ "a" ; "b" ; "--" ; "--c" ; "--help" ]
|
||||||
|
|> shouldEqual
|
||||||
|
{
|
||||||
|
Args = [ Choice1Of2 "a" ; Choice1Of2 "b" ; Choice2Of2 "--c" ; Choice2Of2 "--help" ]
|
||||||
|
}
|
||||||
|
|
||||||
|
let boolCases =
|
||||||
|
[
|
||||||
|
"1", true
|
||||||
|
"0", false
|
||||||
|
"true", true
|
||||||
|
"false", false
|
||||||
|
"TRUE", true
|
||||||
|
"FALSE", false
|
||||||
|
]
|
||||||
|
|> List.map TestCaseData
|
||||||
|
|
||||||
|
[<TestCaseSource(nameof (boolCases))>]
|
||||||
|
let ``Bool env vars can be populated`` (envValue : string, boolValue : bool) =
|
||||||
|
let getEnvVar (s : string) =
|
||||||
|
s |> shouldEqual "CONSUMEPLUGIN_THINGS"
|
||||||
|
envValue
|
||||||
|
|
||||||
|
ContainsBoolEnvVar.parse' getEnvVar []
|
||||||
|
|> shouldEqual
|
||||||
|
{
|
||||||
|
BoolVar = Choice2Of2 boolValue
|
||||||
|
}
|
||||||
|
|
||||||
|
[<Test>]
|
||||||
|
let ``Bools can be treated with arity 0`` () =
|
||||||
|
let getEnvVar (_ : string) = failwith "do not call"
|
||||||
|
|
||||||
|
ContainsBoolEnvVar.parse' getEnvVar [ "--bool-var" ]
|
||||||
|
|> shouldEqual
|
||||||
|
{
|
||||||
|
BoolVar = Choice1Of2 true
|
||||||
|
}
|
||||||
|
|
||||||
|
[<TestCaseSource(nameof boolCases)>]
|
||||||
|
let ``Flag DUs can be parsed from env var`` (envValue : string, boolValue : bool) =
|
||||||
|
let getEnvVar (s : string) =
|
||||||
|
s |> shouldEqual "CONSUMEPLUGIN_THINGS"
|
||||||
|
envValue
|
||||||
|
|
||||||
|
let boolValue = if boolValue then DryRunMode.Dry else DryRunMode.Wet
|
||||||
|
|
||||||
|
ContainsFlagEnvVar.parse' getEnvVar []
|
||||||
|
|> shouldEqual
|
||||||
|
{
|
||||||
|
DryRun = Choice2Of2 boolValue
|
||||||
|
}
|
||||||
|
|
||||||
|
let dryRunData =
|
||||||
|
[
|
||||||
|
[ "--dry-run" ], DryRunMode.Dry
|
||||||
|
[ "--dry-run" ; "true" ], DryRunMode.Dry
|
||||||
|
[ "--dry-run=true" ], DryRunMode.Dry
|
||||||
|
[ "--dry-run" ; "True" ], DryRunMode.Dry
|
||||||
|
[ "--dry-run=True" ], DryRunMode.Dry
|
||||||
|
[ "--dry-run" ; "false" ], DryRunMode.Wet
|
||||||
|
[ "--dry-run=false" ], DryRunMode.Wet
|
||||||
|
[ "--dry-run" ; "False" ], DryRunMode.Wet
|
||||||
|
[ "--dry-run=False" ], DryRunMode.Wet
|
||||||
|
]
|
||||||
|
|> List.map TestCaseData
|
||||||
|
|
||||||
|
[<TestCaseSource(nameof dryRunData)>]
|
||||||
|
let ``Flag DUs can be parsed`` (args : string list, expected : DryRunMode) =
|
||||||
|
let getEnvVar (_ : string) = failwith "do not call"
|
||||||
|
|
||||||
|
ContainsFlagEnvVar.parse' getEnvVar args
|
||||||
|
|> shouldEqual
|
||||||
|
{
|
||||||
|
DryRun = Choice1Of2 expected
|
||||||
|
}
|
||||||
|
|
||||||
|
[<TestCaseSource(nameof dryRunData)>]
|
||||||
|
let ``Flag DUs can be parsed, ArgumentDefaultFunction`` (args : string list, expected : DryRunMode) =
|
||||||
|
let getEnvVar (_ : string) = failwith "do not call"
|
||||||
|
|
||||||
|
ContainsFlagDefaultValue.parse' getEnvVar args
|
||||||
|
|> shouldEqual
|
||||||
|
{
|
||||||
|
DryRun = Choice1Of2 expected
|
||||||
|
}
|
||||||
|
|
||||||
|
[<Test>]
|
||||||
|
let ``Flag DUs can be given a default value`` () =
|
||||||
|
let getEnvVar (_ : string) = failwith "do not call"
|
||||||
|
|
||||||
|
ContainsFlagDefaultValue.parse' getEnvVar []
|
||||||
|
|> shouldEqual
|
||||||
|
{
|
||||||
|
DryRun = Choice2Of2 DryRunMode.Wet
|
||||||
|
}
|
||||||
|
|
||||||
|
[<Test>]
|
||||||
|
let ``Help text for flag DU`` () =
|
||||||
|
let getEnvVar (_ : string) = failwith "do not call"
|
||||||
|
|
||||||
|
let exc =
|
||||||
|
Assert.Throws<exn> (fun () ->
|
||||||
|
ContainsFlagDefaultValue.parse' getEnvVar [ "--help" ]
|
||||||
|
|> ignore<ContainsFlagDefaultValue>
|
||||||
|
)
|
||||||
|
|
||||||
|
exc.Message
|
||||||
|
|> shouldEqual
|
||||||
|
"""Help text requested.
|
||||||
|
--dry-run bool (default value: false)"""
|
||||||
|
|
||||||
|
[<Test>]
|
||||||
|
let ``Help text for flag DU, non default`` () =
|
||||||
|
let getEnvVar (_ : string) = failwith "do not call"
|
||||||
|
|
||||||
|
let exc =
|
||||||
|
Assert.Throws<exn> (fun () -> WithFlagDu.parse' getEnvVar [ "--help" ] |> ignore<WithFlagDu>)
|
||||||
|
|
||||||
|
exc.Message
|
||||||
|
|> shouldEqual
|
||||||
|
"""Help text requested.
|
||||||
|
--dry-run bool"""
|
||||||
|
|
||||||
|
let longFormCases =
|
||||||
|
let doTheThing =
|
||||||
|
[
|
||||||
|
[ "--do-something-else=foo" ]
|
||||||
|
[ "--anotherarg=foo" ]
|
||||||
|
[ "--do-something-else" ; "foo" ]
|
||||||
|
[ "--anotherarg" ; "foo" ]
|
||||||
|
]
|
||||||
|
|
||||||
|
let someFlag =
|
||||||
|
[
|
||||||
|
[ "--turn-it-on" ], true
|
||||||
|
[ "--dont-turn-it-off" ], true
|
||||||
|
[ "--turn-it-on=true" ], true
|
||||||
|
[ "--dont-turn-it-off=true" ], true
|
||||||
|
[ "--turn-it-on=false" ], false
|
||||||
|
[ "--dont-turn-it-off=false" ], false
|
||||||
|
[ "--turn-it-on" ; "true" ], true
|
||||||
|
[ "--dont-turn-it-off" ; "true" ], true
|
||||||
|
[ "--turn-it-on" ; "false" ], false
|
||||||
|
[ "--dont-turn-it-off" ; "false" ], false
|
||||||
|
]
|
||||||
|
|
||||||
|
List.allPairs doTheThing someFlag
|
||||||
|
|> List.map (fun (doTheThing, (someFlag, someFlagResult)) ->
|
||||||
|
let args = doTheThing @ someFlag
|
||||||
|
|
||||||
|
let expected =
|
||||||
|
{
|
||||||
|
DoTheThing = "foo"
|
||||||
|
SomeFlag = someFlagResult
|
||||||
|
}
|
||||||
|
|
||||||
|
args, expected
|
||||||
|
)
|
||||||
|
|> List.map TestCaseData
|
||||||
|
|
||||||
|
[<TestCaseSource(nameof longFormCases)>]
|
||||||
|
let ``Long-form args`` (args : string list, expected : ManyLongForms) =
|
||||||
|
let getEnvVar (_ : string) = failwith "do not call"
|
||||||
|
|
||||||
|
ManyLongForms.parse' getEnvVar args |> shouldEqual expected
|
||||||
|
|
||||||
|
[<Test>]
|
||||||
|
let ``Long-form args can't be referred to by their original name`` () =
|
||||||
|
let getEnvVar (_ : string) = failwith "do not call"
|
||||||
|
|
||||||
|
let exc =
|
||||||
|
Assert.Throws<exn> (fun () ->
|
||||||
|
ManyLongForms.parse' getEnvVar [ "--do-the-thing=foo" ] |> ignore<ManyLongForms>
|
||||||
|
)
|
||||||
|
|
||||||
|
exc.Message
|
||||||
|
|> shouldEqual """Unable to process argument --do-the-thing=foo as key --do-the-thing and value foo"""
|
||||||
|
|
||||||
|
[<Test>]
|
||||||
|
let ``Long-form args help text`` () =
|
||||||
|
let getEnvVar (_ : string) = failwith "do not call"
|
||||||
|
|
||||||
|
let exc =
|
||||||
|
Assert.Throws<exn> (fun () -> ManyLongForms.parse' getEnvVar [ "--help" ] |> ignore<ManyLongForms>)
|
||||||
|
|
||||||
|
exc.Message
|
||||||
|
|> shouldEqual
|
||||||
|
"""Help text requested.
|
||||||
|
--do-something-else / --anotherarg string
|
||||||
|
--turn-it-on / --dont-turn-it-off bool"""
|
||||||
|
|
||||||
|
[<Test>]
|
||||||
|
let ``Can collect *all* non-help args into positional args with includeFlagLike`` () =
|
||||||
|
let getEnvVar (_ : string) = failwith "do not call"
|
||||||
|
|
||||||
|
FlagsIntoPositionalArgs.parse' getEnvVar [ "--a" ; "foo" ; "--b=false" ; "--c" ; "hi" ; "--" ; "--help" ]
|
||||||
|
|> shouldEqual
|
||||||
|
{
|
||||||
|
A = "foo"
|
||||||
|
GrabEverything = [ "--b=false" ; "--c" ; "hi" ; "--help" ]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Users might consider this eccentric!
|
||||||
|
// But we're only a simple arg parser; we don't look around to see whether this is "almost"
|
||||||
|
// a valid parse.
|
||||||
|
FlagsIntoPositionalArgs.parse' getEnvVar [ "--a" ; "--b=false" ; "--c" ; "hi" ; "--" ; "--help" ]
|
||||||
|
|> shouldEqual
|
||||||
|
{
|
||||||
|
A = "--b=false"
|
||||||
|
GrabEverything = [ "--c" ; "hi" ; "--help" ]
|
||||||
|
}
|
||||||
|
|
||||||
|
[<Test>]
|
||||||
|
let ``Can collect non-help args into positional args with Choice`` () =
|
||||||
|
let getEnvVar (_ : string) = failwith "do not call"
|
||||||
|
|
||||||
|
FlagsIntoPositionalArgsChoice.parse' getEnvVar [ "--a" ; "foo" ; "--b=false" ; "--c" ; "hi" ; "--" ; "--help" ]
|
||||||
|
|> shouldEqual
|
||||||
|
{
|
||||||
|
A = "foo"
|
||||||
|
GrabEverything =
|
||||||
|
[
|
||||||
|
Choice1Of2 "--b=false"
|
||||||
|
Choice1Of2 "--c"
|
||||||
|
Choice1Of2 "hi"
|
||||||
|
Choice2Of2 "--help"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
[<Test>]
|
||||||
|
let ``Can collect non-help args into positional args, and we parse on the way`` () =
|
||||||
|
let getEnvVar (_ : string) = failwith "do not call"
|
||||||
|
|
||||||
|
FlagsIntoPositionalArgsInt.parse' getEnvVar [ "3" ; "--a" ; "foo" ; "5" ; "--" ; "98" ]
|
||||||
|
|> shouldEqual
|
||||||
|
{
|
||||||
|
A = "foo"
|
||||||
|
GrabEverything = [ 3 ; 5 ; 98 ]
|
||||||
|
}
|
||||||
|
|
||||||
|
[<Test>]
|
||||||
|
let ``Can collect non-help args into positional args with Choice, and we parse on the way`` () =
|
||||||
|
let getEnvVar (_ : string) = failwith "do not call"
|
||||||
|
|
||||||
|
FlagsIntoPositionalArgsIntChoice.parse' getEnvVar [ "3" ; "--a" ; "foo" ; "5" ; "--" ; "98" ]
|
||||||
|
|> shouldEqual
|
||||||
|
{
|
||||||
|
A = "foo"
|
||||||
|
GrabEverything = [ Choice1Of2 3 ; Choice1Of2 5 ; Choice2Of2 98 ]
|
||||||
|
}
|
||||||
|
|
||||||
|
[<Test>]
|
||||||
|
let ``Can refuse to collect non-help args with PositionalArgs false`` () =
|
||||||
|
let getEnvVar (_ : string) = failwith "do not call"
|
||||||
|
|
||||||
|
let exc =
|
||||||
|
Assert.Throws<exn> (fun () ->
|
||||||
|
FlagsIntoPositionalArgs'.parse'
|
||||||
|
getEnvVar
|
||||||
|
[ "--a" ; "foo" ; "--b=false" ; "--c" ; "hi" ; "--" ; "--help" ]
|
||||||
|
|> ignore<FlagsIntoPositionalArgs'>
|
||||||
|
)
|
||||||
|
|
||||||
|
exc.Message
|
||||||
|
|> shouldEqual """Unable to process argument --b=false as key --b and value false"""
|
||||||
|
|
||||||
|
let exc =
|
||||||
|
Assert.Throws<exn> (fun () ->
|
||||||
|
FlagsIntoPositionalArgs'.parse' getEnvVar [ "--a" ; "--b=false" ; "--c=hi" ; "--" ; "--help" ]
|
||||||
|
|> ignore<FlagsIntoPositionalArgs'>
|
||||||
|
)
|
||||||
|
|
||||||
|
// Again perhaps eccentric!
|
||||||
|
// Again, we don't try to detect that the user has missed out the desired argument to `--a`.
|
||||||
|
exc.Message
|
||||||
|
|> shouldEqual """Unable to process argument --c=hi as key --c and value hi"""
|
||||||
|
@@ -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"
|
||||||
|
@@ -117,6 +117,7 @@ module TestJsonSerde =
|
|||||||
IntMeasureNullable = intMeasureNullable
|
IntMeasureNullable = intMeasureNullable
|
||||||
Enum = enum<SomeEnum> someEnum
|
Enum = enum<SomeEnum> someEnum
|
||||||
Timestamp = timestamp
|
Timestamp = timestamp
|
||||||
|
Unit = ()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -168,6 +169,7 @@ module TestJsonSerde =
|
|||||||
IntMeasureNullable = Nullable -883<measure>
|
IntMeasureNullable = Nullable -883<measure>
|
||||||
Enum = enum<SomeEnum> 1
|
Enum = enum<SomeEnum> 1
|
||||||
Timestamp = DateTimeOffset (2024, 07, 01, 17, 54, 00, TimeSpan.FromHours 1.0)
|
Timestamp = DateTimeOffset (2024, 07, 01, 17, 54, 00, TimeSpan.FromHours 1.0)
|
||||||
|
Unit = ()
|
||||||
}
|
}
|
||||||
|
|
||||||
let expected =
|
let expected =
|
||||||
@@ -198,7 +200,8 @@ module TestJsonSerde =
|
|||||||
"intMeasureOption": 981,
|
"intMeasureOption": 981,
|
||||||
"intMeasureNullable": -883,
|
"intMeasureNullable": -883,
|
||||||
"enum": 1,
|
"enum": 1,
|
||||||
"timestamp": "2024-07-01T17:54:00.0000000\u002B01:00"
|
"timestamp": "2024-07-01T17:54:00.0000000\u002B01:00",
|
||||||
|
"unit": {}
|
||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
|> fun s -> s.ToCharArray ()
|
|> fun s -> s.ToCharArray ()
|
||||||
@@ -306,3 +309,166 @@ module TestJsonSerde =
|
|||||||
|
|
||||||
for i in counts do
|
for i in counts do
|
||||||
i |> shouldBeGreaterThan 0
|
i |> shouldBeGreaterThan 0
|
||||||
|
|
||||||
|
let dict<'a, 'b when 'a : equality> (xs : ('a * 'b) seq) : Dictionary<'a, 'b> =
|
||||||
|
let result = Dictionary ()
|
||||||
|
|
||||||
|
for k, v in xs do
|
||||||
|
result.Add (k, v)
|
||||||
|
|
||||||
|
result
|
||||||
|
|
||||||
|
let inline makeJsonArr< ^t, ^u when ^u : (static member op_Implicit : ^t -> JsonNode) and ^u :> JsonNode>
|
||||||
|
(arr : ^t seq)
|
||||||
|
: JsonNode
|
||||||
|
=
|
||||||
|
let result = JsonArray ()
|
||||||
|
|
||||||
|
for a in arr do
|
||||||
|
result.Add a
|
||||||
|
|
||||||
|
result :> JsonNode
|
||||||
|
|
||||||
|
let normalise (d : Dictionary<'a, 'b>) : ('a * 'b) list =
|
||||||
|
d |> Seq.map (fun (KeyValue (a, b)) -> a, b) |> Seq.toList |> List.sortBy fst
|
||||||
|
|
||||||
|
[<Test>]
|
||||||
|
let ``Can collect extension data`` () =
|
||||||
|
let str =
|
||||||
|
"""{
|
||||||
|
"message": { "header": "hi", "value": "bye" },
|
||||||
|
"something": 3,
|
||||||
|
"arr": ["egg", "toast"],
|
||||||
|
"str": "whatnot"
|
||||||
|
}"""
|
||||||
|
|> JsonNode.Parse
|
||||||
|
|
||||||
|
let expected =
|
||||||
|
{
|
||||||
|
Rest =
|
||||||
|
[
|
||||||
|
"something", JsonNode.op_Implicit 3
|
||||||
|
"arr", makeJsonArr [| "egg" ; "toast" |]
|
||||||
|
"str", JsonNode.op_Implicit "whatnot"
|
||||||
|
]
|
||||||
|
|> dict
|
||||||
|
Message =
|
||||||
|
Some
|
||||||
|
{
|
||||||
|
Header = "hi"
|
||||||
|
Value = "bye"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let actual = CollectRemaining.jsonParse str
|
||||||
|
|
||||||
|
actual.Message |> shouldEqual expected.Message
|
||||||
|
|
||||||
|
normalise actual.Rest
|
||||||
|
|> List.map (fun (k, v) -> k, v.ToJsonString ())
|
||||||
|
|> shouldEqual (normalise expected.Rest |> List.map (fun (k, v) -> k, v.ToJsonString ()))
|
||||||
|
|
||||||
|
[<Test>]
|
||||||
|
let ``Can write out extension data`` () =
|
||||||
|
let expected =
|
||||||
|
"""{"message":{"header":"hi","value":"bye"},"something":3,"arr":["egg","toast"],"str":"whatnot"}"""
|
||||||
|
|
||||||
|
let toWrite =
|
||||||
|
{
|
||||||
|
Rest =
|
||||||
|
[
|
||||||
|
"something", JsonNode.op_Implicit 3
|
||||||
|
"arr", makeJsonArr [| "egg" ; "toast" |]
|
||||||
|
"str", JsonNode.op_Implicit "whatnot"
|
||||||
|
]
|
||||||
|
|> dict
|
||||||
|
Message =
|
||||||
|
Some
|
||||||
|
{
|
||||||
|
Header = "hi"
|
||||||
|
Value = "bye"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let actual = CollectRemaining.toJsonNode toWrite |> fun s -> s.ToJsonString ()
|
||||||
|
|
||||||
|
actual |> shouldEqual expected
|
||||||
|
|
||||||
|
[<Test>]
|
||||||
|
let ``Can collect extension data, nested`` () =
|
||||||
|
let str =
|
||||||
|
"""{
|
||||||
|
"thing": 99,
|
||||||
|
"baz": -123,
|
||||||
|
"remaining": {
|
||||||
|
"message": { "header": "hi", "value": "bye" },
|
||||||
|
"something": 3,
|
||||||
|
"arr": ["egg", "toast"],
|
||||||
|
"str": "whatnot"
|
||||||
|
}
|
||||||
|
}"""
|
||||||
|
|> JsonNode.Parse
|
||||||
|
|
||||||
|
let expected : OuterCollectRemaining =
|
||||||
|
{
|
||||||
|
Remaining =
|
||||||
|
{
|
||||||
|
Message =
|
||||||
|
Some
|
||||||
|
{
|
||||||
|
Header = "hi"
|
||||||
|
Value = "bye"
|
||||||
|
}
|
||||||
|
Rest =
|
||||||
|
[
|
||||||
|
"something", JsonNode.op_Implicit 3
|
||||||
|
"arr", makeJsonArr [| "egg" ; "toast" |]
|
||||||
|
"str", JsonNode.op_Implicit "whatnot"
|
||||||
|
]
|
||||||
|
|> dict
|
||||||
|
}
|
||||||
|
Others = [ "thing", 99 ; "baz", -123 ] |> dict
|
||||||
|
}
|
||||||
|
|
||||||
|
let actual = OuterCollectRemaining.jsonParse str
|
||||||
|
|
||||||
|
normalise actual.Others |> shouldEqual (normalise expected.Others)
|
||||||
|
|
||||||
|
let actual = actual.Remaining
|
||||||
|
let expected = expected.Remaining
|
||||||
|
|
||||||
|
actual.Message |> shouldEqual expected.Message
|
||||||
|
|
||||||
|
normalise actual.Rest
|
||||||
|
|> List.map (fun (k, v) -> k, v.ToJsonString ())
|
||||||
|
|> shouldEqual (normalise expected.Rest |> List.map (fun (k, v) -> k, v.ToJsonString ()))
|
||||||
|
|
||||||
|
[<Test>]
|
||||||
|
let ``Can write out extension data, nested`` () =
|
||||||
|
let expected =
|
||||||
|
"""{"thing":99,"baz":-123,"remaining":{"message":{"header":"hi","value":"bye"},"something":3,"arr":["egg","toast"],"str":"whatnot"}}"""
|
||||||
|
|
||||||
|
let toWrite : OuterCollectRemaining =
|
||||||
|
{
|
||||||
|
Others = [ "thing", 99 ; "baz", -123 ] |> dict
|
||||||
|
Remaining =
|
||||||
|
{
|
||||||
|
Rest =
|
||||||
|
[
|
||||||
|
"something", JsonNode.op_Implicit 3
|
||||||
|
"arr", makeJsonArr [| "egg" ; "toast" |]
|
||||||
|
"str", JsonNode.op_Implicit "whatnot"
|
||||||
|
]
|
||||||
|
|> dict
|
||||||
|
Message =
|
||||||
|
Some
|
||||||
|
{
|
||||||
|
Header = "hi"
|
||||||
|
Value = "bye"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let actual = OuterCollectRemaining.toJsonNode toWrite |> fun s -> s.ToJsonString ()
|
||||||
|
|
||||||
|
actual |> shouldEqual expected
|
||||||
|
@@ -4,6 +4,11 @@
|
|||||||
<TargetFramework>net8.0</TargetFramework>
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
<IsPackable>false</IsPackable>
|
<IsPackable>false</IsPackable>
|
||||||
<IsTestProject>true</IsTestProject>
|
<IsTestProject>true</IsTestProject>
|
||||||
|
<!--
|
||||||
|
Known high severity vulnerability
|
||||||
|
I have not yet seen a single instance where I care about this warning
|
||||||
|
-->
|
||||||
|
<NoWarn>$(NoWarn),NU1903</NoWarn>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
@@ -37,7 +42,7 @@
|
|||||||
<PackageReference Include="ApiSurface" Version="4.1.5"/>
|
<PackageReference Include="ApiSurface" Version="4.1.5"/>
|
||||||
<PackageReference Include="FsCheck" Version="2.16.6"/>
|
<PackageReference Include="FsCheck" Version="2.16.6"/>
|
||||||
<PackageReference Include="FsUnit" Version="6.0.0"/>
|
<PackageReference Include="FsUnit" Version="6.0.0"/>
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.0"/>
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.1"/>
|
||||||
<PackageReference Include="NUnit" Version="4.2.2"/>
|
<PackageReference Include="NUnit" Version="4.2.2"/>
|
||||||
<PackageReference Include="NUnit3TestAdapter" Version="4.6.0"/>
|
<PackageReference Include="NUnit3TestAdapter" Version="4.6.0"/>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
File diff suppressed because it is too large
Load Diff
@@ -67,41 +67,109 @@ type internal RecordType =
|
|||||||
Members : SynMemberDefns option
|
Members : SynMemberDefns option
|
||||||
XmlDoc : PreXmlDoc option
|
XmlDoc : PreXmlDoc option
|
||||||
Generics : SynTyparDecls option
|
Generics : SynTyparDecls option
|
||||||
Accessibility : SynAccess option
|
TypeAccessibility : SynAccess option
|
||||||
|
ImplAccessibility : SynAccess option
|
||||||
Attributes : SynAttribute list
|
Attributes : SynAttribute list
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parse from the AST.
|
/// Parse from the AST.
|
||||||
static member OfRecord (record : SynTypeDefn) : RecordType =
|
static member OfRecord
|
||||||
let sci, sdr, smd, smdo =
|
(sci : SynComponentInfo)
|
||||||
match record with
|
(smd : SynMemberDefns)
|
||||||
| SynTypeDefn.SynTypeDefn (sci, sdr, smd, smdo, _, _) -> sci, sdr, smd, smdo
|
(access : SynAccess option)
|
||||||
|
(recordFields : SynField list)
|
||||||
let synAccessOption, recordFields =
|
: RecordType
|
||||||
match sdr with
|
=
|
||||||
| SynTypeDefnRepr.Simple (SynTypeDefnSimpleRepr.Record (sa, fields, _), _) -> sa, fields
|
|
||||||
| _ -> failwith $"expected a record; got: %+A{record}"
|
|
||||||
|
|
||||||
match sci with
|
match sci with
|
||||||
| SynComponentInfo.SynComponentInfo (attrs, typars, _, longId, doc, _, access, _) ->
|
| SynComponentInfo.SynComponentInfo (attrs, typars, _, longId, doc, _, implAccess, _) ->
|
||||||
if access <> synAccessOption then
|
|
||||||
failwith
|
|
||||||
$"TODO what's happened, two different accessibility modifiers: %O{access} and %O{synAccessOption}"
|
|
||||||
|
|
||||||
match smdo with
|
|
||||||
| Some v -> failwith $"TODO what's happened, got a synMemberDefn of %O{v}"
|
|
||||||
| None -> ()
|
|
||||||
|
|
||||||
{
|
{
|
||||||
Name = List.last longId
|
Name = List.last longId
|
||||||
Fields = recordFields
|
Fields = recordFields
|
||||||
Members = if smd.IsEmpty then None else Some smd
|
Members = if smd.IsEmpty then None else Some smd
|
||||||
XmlDoc = if doc.IsEmpty then None else Some doc
|
XmlDoc = if doc.IsEmpty then None else Some doc
|
||||||
Generics = typars
|
Generics = typars
|
||||||
Accessibility = synAccessOption
|
ImplAccessibility = implAccess
|
||||||
|
TypeAccessibility = access
|
||||||
Attributes = attrs |> List.collect (fun l -> l.Attributes)
|
Attributes = attrs |> List.collect (fun l -> l.Attributes)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Methods for manipulating UnionCase.
|
||||||
|
[<RequireQualifiedAccess>]
|
||||||
|
module UnionCase =
|
||||||
|
/// Construct our structured `UnionCase` from an FCS `SynUnionCase`: extract everything
|
||||||
|
/// we care about from the AST representation.
|
||||||
|
let ofSynUnionCase (case : SynUnionCase) : UnionCase<Ident option> =
|
||||||
|
match case with
|
||||||
|
| SynUnionCase.SynUnionCase (attributes, ident, caseType, xmlDoc, access, _, _) ->
|
||||||
|
|
||||||
|
let ident =
|
||||||
|
match ident with
|
||||||
|
| SynIdent.SynIdent (ident, _) -> ident
|
||||||
|
|
||||||
|
let fields =
|
||||||
|
match caseType with
|
||||||
|
| SynUnionCaseKind.Fields cases -> cases
|
||||||
|
| SynUnionCaseKind.FullType _ -> failwith "unexpected FullType union"
|
||||||
|
|
||||||
|
{
|
||||||
|
Name = ident
|
||||||
|
XmlDoc = if xmlDoc.IsEmpty then None else Some xmlDoc
|
||||||
|
Access = access
|
||||||
|
Attributes = attributes |> List.collect (fun t -> t.Attributes)
|
||||||
|
Fields = fields |> List.map SynField.extract
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Functorial `map`.
|
||||||
|
let mapIdentFields<'a, 'b> (f : 'a -> 'b) (unionCase : UnionCase<'a>) : UnionCase<'b> =
|
||||||
|
{
|
||||||
|
Attributes = unionCase.Attributes
|
||||||
|
Name = unionCase.Name
|
||||||
|
Access = unionCase.Access
|
||||||
|
XmlDoc = unionCase.XmlDoc
|
||||||
|
Fields = unionCase.Fields |> List.map (SynField.mapIdent f)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Everything you need to know about a discriminated union definition.
|
||||||
|
type internal UnionType =
|
||||||
|
{
|
||||||
|
/// The name of the DU: for example, `type Foo = | Blah` has this being `Foo`.
|
||||||
|
Name : Ident
|
||||||
|
/// Any additional members which are not union cases.
|
||||||
|
Members : SynMemberDefns option
|
||||||
|
/// Any docstring associated with the DU itself (not its cases).
|
||||||
|
XmlDoc : PreXmlDoc option
|
||||||
|
/// Generic type parameters this DU takes: `type Foo<'a> = | ...`.
|
||||||
|
Generics : SynTyparDecls option
|
||||||
|
/// Attributes of the DU (not its cases): `[<Attr>] type Foo = | ...`
|
||||||
|
Attributes : SynAttribute list
|
||||||
|
/// Accessibility modifier of the DU: `type private Foo = ...`
|
||||||
|
TypeAccessibility : SynAccess option
|
||||||
|
/// Accessibility modifier of the DU's implementation: `type Foo = private | ...`
|
||||||
|
ImplAccessibility : SynAccess option
|
||||||
|
/// The actual DU cases themselves.
|
||||||
|
Cases : UnionCase<Ident option> list
|
||||||
|
}
|
||||||
|
|
||||||
|
static member OfUnion
|
||||||
|
(sci : SynComponentInfo)
|
||||||
|
(smd : SynMemberDefns)
|
||||||
|
(access : SynAccess option)
|
||||||
|
(cases : SynUnionCase list)
|
||||||
|
: UnionType
|
||||||
|
=
|
||||||
|
match sci with
|
||||||
|
| SynComponentInfo.SynComponentInfo (attrs, typars, _, longId, doc, _, implAccess, _) ->
|
||||||
|
{
|
||||||
|
Name = List.last longId
|
||||||
|
Members = if smd.IsEmpty then None else Some smd
|
||||||
|
XmlDoc = if doc.IsEmpty then None else Some doc
|
||||||
|
Generics = typars
|
||||||
|
Attributes = attrs |> List.collect (fun l -> l.Attributes)
|
||||||
|
TypeAccessibility = access
|
||||||
|
ImplAccessibility = implAccess
|
||||||
|
Cases = cases |> List.map UnionCase.ofSynUnionCase
|
||||||
|
}
|
||||||
|
|
||||||
/// Anything that is part of an ADT.
|
/// Anything that is part of an ADT.
|
||||||
/// A record is a product of stuff; this type represents one of those stuffs.
|
/// A record is a product of stuff; this type represents one of those stuffs.
|
||||||
type internal AdtNode =
|
type internal AdtNode =
|
||||||
@@ -144,13 +212,13 @@ module internal AstHelper =
|
|||||||
let defineRecordType (record : RecordType) : SynTypeDefn =
|
let defineRecordType (record : RecordType) : SynTypeDefn =
|
||||||
let name =
|
let name =
|
||||||
SynComponentInfo.create record.Name
|
SynComponentInfo.create record.Name
|
||||||
|> SynComponentInfo.setAccessibility record.Accessibility
|
|> SynComponentInfo.setAccessibility record.TypeAccessibility
|
||||||
|> match record.XmlDoc with
|
|> match record.XmlDoc with
|
||||||
| None -> id
|
| None -> id
|
||||||
| Some doc -> SynComponentInfo.withDocString doc
|
| Some doc -> SynComponentInfo.withDocString doc
|
||||||
|> SynComponentInfo.setGenerics record.Generics
|
|> SynComponentInfo.setGenerics record.Generics
|
||||||
|
|
||||||
SynTypeDefnRepr.record (Seq.toList record.Fields)
|
SynTypeDefnRepr.recordWithAccess record.ImplAccessibility (Seq.toList record.Fields)
|
||||||
|> SynTypeDefn.create name
|
|> SynTypeDefn.create name
|
||||||
|> SynTypeDefn.withMemberDefns (defaultArg record.Members SynMemberDefns.Empty)
|
|> SynTypeDefn.withMemberDefns (defaultArg record.Members SynMemberDefns.Empty)
|
||||||
|
|
||||||
|
@@ -564,11 +564,12 @@ module internal CataGenerator =
|
|||||||
let domain =
|
let domain =
|
||||||
field.FieldName
|
field.FieldName
|
||||||
|> Option.map Ident.lowerFirstLetter
|
|> Option.map Ident.lowerFirstLetter
|
||||||
|> SynType.signatureParamOfType place
|
|> SynType.signatureParamOfType [] place false
|
||||||
|
|
||||||
acc |> SynType.funFromDomain domain
|
acc |> SynType.funFromDomain domain
|
||||||
)
|
)
|
||||||
|> SynMemberDefn.abstractMember
|
|> SynMemberDefn.abstractMember
|
||||||
|
[]
|
||||||
case.CataMethodIdent
|
case.CataMethodIdent
|
||||||
None
|
None
|
||||||
arity
|
arity
|
||||||
|
@@ -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
|
||||||
|
@@ -212,7 +212,8 @@ module internal InterfaceMockGenerator =
|
|||||||
Members = Some ([ constructor ; interfaceMembers ] @ extraInterfaces)
|
Members = Some ([ constructor ; interfaceMembers ] @ extraInterfaces)
|
||||||
XmlDoc = Some xmlDoc
|
XmlDoc = Some xmlDoc
|
||||||
Generics = interfaceType.Generics
|
Generics = interfaceType.Generics
|
||||||
Accessibility = Some access
|
TypeAccessibility = Some access
|
||||||
|
ImplAccessibility = None
|
||||||
Attributes = []
|
Attributes = []
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -227,14 +228,11 @@ module internal InterfaceMockGenerator =
|
|||||||
x.Type
|
x.Type
|
||||||
|
|
||||||
let private constructMemberSinglePlace (tuple : TupledArg) : SynType =
|
let private constructMemberSinglePlace (tuple : TupledArg) : SynType =
|
||||||
match tuple.Args |> List.rev |> List.map buildType with
|
tuple.Args
|
||||||
| [] -> failwith "no-arg functions not supported yet"
|
|> List.map buildType
|
||||||
| [ x ] -> x
|
|> SynType.tupleNoParen
|
||||||
| last :: rest ->
|
|> Option.defaultWith (fun () -> failwith "no-arg functions not supported yet")
|
||||||
([ SynTupleTypeSegment.Type last ], rest)
|
|> if tuple.HasParen then SynType.paren else id
|
||||||
||> List.fold (fun ty nextArg -> SynTupleTypeSegment.Type nextArg :: SynTupleTypeSegment.Star range0 :: ty)
|
|
||||||
|> fun segs -> SynType.Tuple (false, segs, range0)
|
|
||||||
|> fun ty -> if tuple.HasParen then SynType.Paren (ty, range0) else ty
|
|
||||||
|
|
||||||
let constructMember (mem : MemberInfo) : SynField =
|
let constructMember (mem : MemberInfo) : SynField =
|
||||||
let inputType = mem.Args |> List.map constructMemberSinglePlace
|
let inputType = mem.Args |> List.map constructMemberSinglePlace
|
||||||
|
@@ -59,7 +59,7 @@ module internal JsonParseGenerator =
|
|||||||
| None -> node
|
| None -> node
|
||||||
| Some propertyName -> assertNotNull propertyName node
|
| Some propertyName -> assertNotNull propertyName node
|
||||||
|> SynExpr.callMethod "AsValue"
|
|> SynExpr.callMethod "AsValue"
|
||||||
|> SynExpr.callGenericMethod "GetValue" typeName
|
|> SynExpr.callGenericMethod (SynLongIdent.createS "GetValue") [ SynType.createLongIdent typeName ]
|
||||||
|
|
||||||
/// {node}.AsObject()
|
/// {node}.AsObject()
|
||||||
/// If `propertyName` is Some, uses `assertNotNull {node}` instead of `{node}`.
|
/// If `propertyName` is Some, uses `assertNotNull {node}` instead of `{node}`.
|
||||||
@@ -279,6 +279,8 @@ module internal JsonParseGenerator =
|
|||||||
| Measure (_measure, primType) ->
|
| Measure (_measure, primType) ->
|
||||||
parseNumberType options propertyName node primType
|
parseNumberType options propertyName node primType
|
||||||
|> SynExpr.pipeThroughFunction (Measure.getLanguagePrimitivesMeasure primType)
|
|> SynExpr.pipeThroughFunction (Measure.getLanguagePrimitivesMeasure primType)
|
||||||
|
| JsonNode -> node
|
||||||
|
| Unit -> SynExpr.CreateConst ()
|
||||||
| _ ->
|
| _ ->
|
||||||
// Let's just hope that we've also got our own type annotation!
|
// Let's just hope that we've also got our own type annotation!
|
||||||
let typeName =
|
let typeName =
|
||||||
@@ -375,9 +377,9 @@ module internal JsonParseGenerator =
|
|||||||
)
|
)
|
||||||
|
|
||||||
let createRecordMaker (spec : JsonParseOutputSpec) (fields : SynFieldData<Ident> list) =
|
let createRecordMaker (spec : JsonParseOutputSpec) (fields : SynFieldData<Ident> list) =
|
||||||
let assignments =
|
let propertyFields =
|
||||||
fields
|
fields
|
||||||
|> List.mapi (fun i fieldData ->
|
|> List.map (fun fieldData ->
|
||||||
let propertyNameAttr =
|
let propertyNameAttr =
|
||||||
fieldData.Attrs
|
fieldData.Attrs
|
||||||
|> List.tryFind (fun attr ->
|
|> List.tryFind (fun attr ->
|
||||||
@@ -385,7 +387,12 @@ module internal JsonParseGenerator =
|
|||||||
.EndsWith ("JsonPropertyName", StringComparison.Ordinal)
|
.EndsWith ("JsonPropertyName", StringComparison.Ordinal)
|
||||||
)
|
)
|
||||||
|
|
||||||
let options = getParseOptions fieldData.Attrs
|
let extensionDataAttr =
|
||||||
|
fieldData.Attrs
|
||||||
|
|> List.tryFind (fun attr ->
|
||||||
|
(SynLongIdent.toString attr.TypeName)
|
||||||
|
.EndsWith ("JsonExtensionData", StringComparison.Ordinal)
|
||||||
|
)
|
||||||
|
|
||||||
let propertyName =
|
let propertyName =
|
||||||
match propertyNameAttr with
|
match propertyNameAttr with
|
||||||
@@ -401,8 +408,77 @@ module internal JsonParseGenerator =
|
|||||||
sb.ToString () |> SynExpr.CreateConst
|
sb.ToString () |> SynExpr.CreateConst
|
||||||
| Some name -> name.ArgExpr
|
| Some name -> name.ArgExpr
|
||||||
|
|
||||||
|
propertyName, extensionDataAttr
|
||||||
|
)
|
||||||
|
|
||||||
|
let namedPropertyFields =
|
||||||
|
propertyFields
|
||||||
|
|> List.choose (fun (name, extension) ->
|
||||||
|
match extension with
|
||||||
|
| Some _ -> None
|
||||||
|
| None -> Some name
|
||||||
|
)
|
||||||
|
|
||||||
|
let isNamedPropertyField =
|
||||||
|
match namedPropertyFields with
|
||||||
|
| [] -> SynExpr.CreateConst false
|
||||||
|
| _ ->
|
||||||
|
namedPropertyFields
|
||||||
|
|> List.map (fun fieldName -> SynExpr.equals (SynExpr.createIdent "key") fieldName)
|
||||||
|
|> List.reduce SynExpr.booleanOr
|
||||||
|
|
||||||
|
let assignments =
|
||||||
|
List.zip fields propertyFields
|
||||||
|
|> List.mapi (fun i (fieldData, (propertyName, extensionDataAttr)) ->
|
||||||
|
let options = getParseOptions fieldData.Attrs
|
||||||
|
|
||||||
|
let accIdent = Ident.create $"arg_%i{i}"
|
||||||
|
|
||||||
|
match extensionDataAttr with
|
||||||
|
| Some _ ->
|
||||||
|
// Can't go through the usual parse logic here, because that will try and identify the node that's
|
||||||
|
// been labelled. The whole point of JsonExtensionData is that there is no such node!
|
||||||
|
let valType =
|
||||||
|
match fieldData.Type with
|
||||||
|
| DictionaryType (String, v) -> v
|
||||||
|
| _ -> failwith "Expected JsonExtensionData to be Dictionary<string, _>"
|
||||||
|
|
||||||
|
SynExpr.ifThenElse
|
||||||
|
isNamedPropertyField
|
||||||
|
(SynExpr.callMethodArg
|
||||||
|
"Add"
|
||||||
|
(SynExpr.tuple
|
||||||
|
[
|
||||||
|
SynExpr.createIdent "key"
|
||||||
|
createParseRhs options (SynExpr.createIdent "key") valType
|
||||||
|
])
|
||||||
|
(SynExpr.createIdent "result"))
|
||||||
|
(SynExpr.CreateConst ())
|
||||||
|
|> SynExpr.createForEach
|
||||||
|
(SynPat.nameWithArgs "KeyValue" [ SynPat.named "key" ; SynPat.named "value" ])
|
||||||
|
(SynExpr.createIdent "node")
|
||||||
|
|> fun forEach -> [ forEach ; SynExpr.createIdent "result" ]
|
||||||
|
|> SynExpr.sequential
|
||||||
|
|> SynExpr.createLet
|
||||||
|
[
|
||||||
|
SynBinding.basic
|
||||||
|
[ Ident.create "result" ]
|
||||||
|
[]
|
||||||
|
(SynExpr.typeApp
|
||||||
|
[ SynType.string ; valType ]
|
||||||
|
(SynExpr.createLongIdent [ "System" ; "Collections" ; "Generic" ; "Dictionary" ])
|
||||||
|
|> SynExpr.applyTo (SynExpr.CreateConst ()))
|
||||||
|
|
||||||
|
SynBinding.basic
|
||||||
|
[ Ident.create "node" ]
|
||||||
|
[]
|
||||||
|
(SynExpr.createIdent "node" |> SynExpr.callMethod "AsObject")
|
||||||
|
]
|
||||||
|
|> SynBinding.basic [ accIdent ] []
|
||||||
|
| None ->
|
||||||
|
|
||||||
createParseRhs options propertyName fieldData.Type
|
createParseRhs options propertyName fieldData.Type
|
||||||
|> SynBinding.basic [ Ident.create $"arg_%i{i}" ] []
|
|> SynBinding.basic [ accIdent ] []
|
||||||
)
|
)
|
||||||
|
|
||||||
let finalConstruction =
|
let finalConstruction =
|
||||||
@@ -416,11 +492,11 @@ module internal JsonParseGenerator =
|
|||||||
let createUnionMaker (spec : JsonParseOutputSpec) (typeName : LongIdent) (fields : UnionCase<Ident> list) =
|
let createUnionMaker (spec : JsonParseOutputSpec) (typeName : LongIdent) (fields : UnionCase<Ident> list) =
|
||||||
fields
|
fields
|
||||||
|> List.map (fun case ->
|
|> List.map (fun case ->
|
||||||
let propertyName = JsonSerializeGenerator.getPropertyName case.Ident case.Attrs
|
let propertyName = JsonSerializeGenerator.getPropertyName case.Name case.Attributes
|
||||||
|
|
||||||
let body =
|
let body =
|
||||||
if case.Fields.IsEmpty then
|
if case.Fields.IsEmpty then
|
||||||
SynExpr.createLongIdent' (typeName @ [ case.Ident ])
|
SynExpr.createLongIdent' (typeName @ [ case.Name ])
|
||||||
else
|
else
|
||||||
case.Fields
|
case.Fields
|
||||||
|> List.map (fun field ->
|
|> List.map (fun field ->
|
||||||
@@ -429,7 +505,7 @@ module internal JsonParseGenerator =
|
|||||||
createParseRhs options propertyName field.Type
|
createParseRhs options propertyName field.Type
|
||||||
)
|
)
|
||||||
|> SynExpr.tuple
|
|> SynExpr.tuple
|
||||||
|> SynExpr.applyFunction (SynExpr.createLongIdent' (typeName @ [ case.Ident ]))
|
|> SynExpr.applyFunction (SynExpr.createLongIdent' (typeName @ [ case.Name ]))
|
||||||
|> SynExpr.createLet
|
|> SynExpr.createLet
|
||||||
[
|
[
|
||||||
SynExpr.index (SynExpr.CreateConst "data") (SynExpr.createIdent "node")
|
SynExpr.index (SynExpr.CreateConst "data") (SynExpr.createIdent "node")
|
||||||
@@ -483,9 +559,7 @@ module internal JsonParseGenerator =
|
|||||||
|> SynExpr.index property
|
|> SynExpr.index property
|
||||||
|> assertNotNull property
|
|> assertNotNull property
|
||||||
|> SynExpr.pipeThroughFunction (
|
|> SynExpr.pipeThroughFunction (
|
||||||
SynExpr.createLambda
|
SynExpr.createLambda "v" (SynExpr.callGenericMethod' "GetValue" "string" (SynExpr.createIdent "v"))
|
||||||
"v"
|
|
||||||
(SynExpr.callGenericMethod "GetValue" [ Ident.create "string" ] (SynExpr.createIdent "v"))
|
|
||||||
)
|
)
|
||||||
|> SynBinding.basic [ Ident.create "ty" ] []
|
|> SynBinding.basic [ Ident.create "ty" ] []
|
||||||
]
|
]
|
||||||
@@ -600,7 +674,7 @@ module internal JsonParseGenerator =
|
|||||||
| Some i -> i
|
| Some i -> i
|
||||||
|
|
||||||
cases
|
cases
|
||||||
|> List.map SynUnionCase.extract
|
|> List.map UnionCase.ofSynUnionCase
|
||||||
|> List.map (UnionCase.mapIdentFields optionGet)
|
|> List.map (UnionCase.mapIdentFields optionGet)
|
||||||
|> createUnionMaker spec ident
|
|> createUnionMaker spec ident
|
||||||
| SynTypeDefnRepr.Simple (SynTypeDefnSimpleRepr.Enum (cases, _range), _) ->
|
| SynTypeDefnRepr.Simple (SynTypeDefnSimpleRepr.Enum (cases, _range), _) ->
|
||||||
|
@@ -146,6 +146,13 @@ module internal JsonSerializeGenerator =
|
|||||||
]
|
]
|
||||||
|> SynExpr.createLambda "field"
|
|> SynExpr.createLambda "field"
|
||||||
|> fun e -> e, false
|
|> fun e -> e, false
|
||||||
|
| JsonNode -> SynExpr.createIdent "id", true
|
||||||
|
| Unit ->
|
||||||
|
SynExpr.createLambda
|
||||||
|
"value"
|
||||||
|
(SynExpr.createLongIdent [ "System" ; "Text" ; "Json" ; "Nodes" ; "JsonObject" ]
|
||||||
|
|> SynExpr.applyTo (SynExpr.CreateConst ())),
|
||||||
|
false
|
||||||
| _ ->
|
| _ ->
|
||||||
// {type}.toJsonNode
|
// {type}.toJsonNode
|
||||||
let typeName =
|
let typeName =
|
||||||
@@ -187,6 +194,14 @@ module internal JsonSerializeGenerator =
|
|||||||
sb.ToString () |> SynExpr.CreateConst
|
sb.ToString () |> SynExpr.CreateConst
|
||||||
| Some name -> name.ArgExpr
|
| Some name -> name.ArgExpr
|
||||||
|
|
||||||
|
let getIsJsonExtension (attrs : SynAttribute list) : bool =
|
||||||
|
attrs
|
||||||
|
|> List.tryFind (fun attr ->
|
||||||
|
(SynLongIdent.toString attr.TypeName)
|
||||||
|
.EndsWith ("JsonExtensionData", StringComparison.Ordinal)
|
||||||
|
)
|
||||||
|
|> Option.isSome
|
||||||
|
|
||||||
/// `populateNode` will be inserted before we return the `node` variable.
|
/// `populateNode` will be inserted before we return the `node` variable.
|
||||||
///
|
///
|
||||||
/// That is, we give you access to a `JsonObject` called `node`,
|
/// That is, we give you access to a `JsonObject` called `node`,
|
||||||
@@ -256,18 +271,42 @@ module internal JsonSerializeGenerator =
|
|||||||
fields
|
fields
|
||||||
|> List.map (fun fieldData ->
|
|> List.map (fun fieldData ->
|
||||||
let propertyName = getPropertyName fieldData.Ident fieldData.Attrs
|
let propertyName = getPropertyName fieldData.Ident fieldData.Attrs
|
||||||
createSerializeRhsRecord propertyName fieldData.Ident fieldData.Type
|
let isJsonExtension = getIsJsonExtension fieldData.Attrs
|
||||||
|
|
||||||
|
if isJsonExtension then
|
||||||
|
let valType =
|
||||||
|
match fieldData.Type with
|
||||||
|
| DictionaryType (String, v) -> v
|
||||||
|
| _ -> failwith "Expected JsonExtensionData to be a Dictionary<string, something>"
|
||||||
|
|
||||||
|
let serialise = fst (serializeNode valType)
|
||||||
|
|
||||||
|
SynExpr.createIdent "node"
|
||||||
|
|> SynExpr.callMethodArg
|
||||||
|
"Add"
|
||||||
|
(SynExpr.tuple
|
||||||
|
[
|
||||||
|
SynExpr.createIdent "key"
|
||||||
|
SynExpr.applyFunction serialise (SynExpr.createIdent "value")
|
||||||
|
])
|
||||||
|
|> SynExpr.createForEach
|
||||||
|
(SynPat.identWithArgs
|
||||||
|
[ Ident.create "KeyValue" ]
|
||||||
|
(SynArgPats.create [ SynPat.named "key" ; SynPat.named "value" ]))
|
||||||
|
(SynExpr.createLongIdent' [ Ident.create "input" ; fieldData.Ident ])
|
||||||
|
else
|
||||||
|
createSerializeRhsRecord propertyName fieldData.Ident fieldData.Type
|
||||||
)
|
)
|
||||||
|> SynExpr.sequential
|
|> SynExpr.sequential
|
||||||
|> fun expr -> SynExpr.Do (expr, range0)
|
|> fun expr -> SynExpr.Do (expr, range0)
|
||||||
|
|
||||||
let unionModule (spec : JsonSerializeOutputSpec) (typeName : LongIdent) (cases : SynUnionCase list) =
|
let unionModule (spec : JsonSerializeOutputSpec) (typeName : LongIdent) (cases : SynUnionCase list) =
|
||||||
let inputArg = Ident.create "input"
|
let inputArg = Ident.create "input"
|
||||||
let fields = cases |> List.map SynUnionCase.extract
|
let fields = cases |> List.map UnionCase.ofSynUnionCase
|
||||||
|
|
||||||
fields
|
fields
|
||||||
|> List.map (fun unionCase ->
|
|> List.map (fun unionCase ->
|
||||||
let propertyName = getPropertyName unionCase.Ident unionCase.Attrs
|
let propertyName = getPropertyName unionCase.Name unionCase.Attributes
|
||||||
|
|
||||||
let caseNames = unionCase.Fields |> List.mapi (fun i _ -> $"arg%i{i}")
|
let caseNames = unionCase.Fields |> List.mapi (fun i _ -> $"arg%i{i}")
|
||||||
|
|
||||||
@@ -275,7 +314,7 @@ module internal JsonSerializeGenerator =
|
|||||||
|
|
||||||
let pattern =
|
let pattern =
|
||||||
SynPat.LongIdent (
|
SynPat.LongIdent (
|
||||||
SynLongIdent.create (typeName @ [ unionCase.Ident ]),
|
SynLongIdent.create (typeName @ [ unionCase.Name ]),
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
argPats,
|
argPats,
|
||||||
|
@@ -36,7 +36,6 @@ module internal RemoveOptionsGenerator =
|
|||||||
trivia
|
trivia
|
||||||
)
|
)
|
||||||
|
|
||||||
// TODO: this option seems a bit odd
|
|
||||||
let createType
|
let createType
|
||||||
(xmlDoc : PreXmlDoc option)
|
(xmlDoc : PreXmlDoc option)
|
||||||
(accessibility : SynAccess option)
|
(accessibility : SynAccess option)
|
||||||
@@ -54,7 +53,8 @@ module internal RemoveOptionsGenerator =
|
|||||||
Members = None
|
Members = None
|
||||||
XmlDoc = xmlDoc
|
XmlDoc = xmlDoc
|
||||||
Generics = generics
|
Generics = generics
|
||||||
Accessibility = accessibility
|
TypeAccessibility = accessibility
|
||||||
|
ImplAccessibility = None
|
||||||
Attributes = []
|
Attributes = []
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -62,7 +62,7 @@ module internal RemoveOptionsGenerator =
|
|||||||
|
|
||||||
SynModuleDecl.Types ([ typeDecl ], range0)
|
SynModuleDecl.Types ([ typeDecl ], range0)
|
||||||
|
|
||||||
let createMaker (withOptionsType : LongIdent) (withoutOptionsType : LongIdent) (fields : SynFieldData<Ident> list) =
|
let createMaker (withOptionsType : LongIdent) (withoutOptionsType : Ident) (fields : SynFieldData<Ident> list) =
|
||||||
let xmlDoc = PreXmlDoc.create "Remove the optional members of the input."
|
let xmlDoc = PreXmlDoc.create "Remove the optional members of the input."
|
||||||
|
|
||||||
let inputArg = Ident.create "input"
|
let inputArg = Ident.create "input"
|
||||||
@@ -87,7 +87,7 @@ module internal RemoveOptionsGenerator =
|
|||||||
SynExpr.applyFunction
|
SynExpr.applyFunction
|
||||||
(SynExpr.createLongIdent [ "Option" ; "defaultWith" ])
|
(SynExpr.createLongIdent [ "Option" ; "defaultWith" ])
|
||||||
(SynExpr.createLongIdent' (
|
(SynExpr.createLongIdent' (
|
||||||
withoutOptionsType
|
[ withoutOptionsType ]
|
||||||
@ [ Ident.create (sprintf "Default%s" fieldData.Ident.idText) ]
|
@ [ Ident.create (sprintf "Default%s" fieldData.Ident.idText) ]
|
||||||
))
|
))
|
||||||
)
|
)
|
||||||
@@ -101,47 +101,35 @@ module internal RemoveOptionsGenerator =
|
|||||||
[ functionName ]
|
[ functionName ]
|
||||||
[
|
[
|
||||||
SynPat.named inputArg.idText
|
SynPat.named inputArg.idText
|
||||||
|> SynPat.annotateType (SynType.LongIdent (SynLongIdent.create withoutOptionsType))
|
|> SynPat.annotateType (SynType.LongIdent (SynLongIdent.createI withoutOptionsType))
|
||||||
]
|
]
|
||||||
body
|
body
|
||||||
|> SynBinding.withXmlDoc xmlDoc
|
|> SynBinding.withXmlDoc xmlDoc
|
||||||
|> SynBinding.withReturnAnnotation (SynType.LongIdent (SynLongIdent.create withOptionsType))
|
|> SynBinding.withReturnAnnotation (SynType.LongIdent (SynLongIdent.create withOptionsType))
|
||||||
|> SynModuleDecl.createLet
|
|> SynModuleDecl.createLet
|
||||||
|
|
||||||
let createRecordModule (namespaceId : LongIdent) (typeDefn : SynTypeDefn) =
|
let createRecordModule (namespaceId : LongIdent) (typeDefn : RecordType) =
|
||||||
let (SynTypeDefn (synComponentInfo, synTypeDefnRepr, _members, _implicitCtor, _, _)) =
|
let fieldData = typeDefn.Fields |> List.map SynField.extractWithIdent
|
||||||
typeDefn
|
|
||||||
|
|
||||||
let (SynComponentInfo (_attributes, typeParams, _constraints, recordId, doc, _preferPostfix, _access, _)) =
|
let decls =
|
||||||
synComponentInfo
|
[
|
||||||
|
createType typeDefn.XmlDoc typeDefn.TypeAccessibility typeDefn.Generics typeDefn.Fields
|
||||||
|
createMaker [ Ident.create "Short" ] typeDefn.Name fieldData
|
||||||
|
]
|
||||||
|
|
||||||
match synTypeDefnRepr with
|
let xmlDoc =
|
||||||
| SynTypeDefnRepr.Simple (SynTypeDefnSimpleRepr.Record (accessibility, fields, _range), _) ->
|
sprintf "Module containing an option-truncated version of the %s type" typeDefn.Name.idText
|
||||||
let fieldData = fields |> List.map SynField.extractWithIdent
|
|> PreXmlDoc.create
|
||||||
|
|
||||||
let decls =
|
let info =
|
||||||
[
|
SynComponentInfo.create typeDefn.Name
|
||||||
createType (Some doc) accessibility typeParams fields
|
|> SynComponentInfo.withDocString xmlDoc
|
||||||
createMaker [ Ident.create "Short" ] recordId fieldData
|
|> SynComponentInfo.addAttributes [ SynAttribute.compilationRepresentation ]
|
||||||
]
|
|> SynComponentInfo.addAttributes [ SynAttribute.requireQualifiedAccess ]
|
||||||
|
|
||||||
let xmlDoc =
|
SynModuleDecl.nestedModule info decls
|
||||||
recordId
|
|> List.singleton
|
||||||
|> Seq.map (fun i -> i.idText)
|
|> SynModuleOrNamespace.createNamespace namespaceId
|
||||||
|> String.concat "."
|
|
||||||
|> sprintf "Module containing an option-truncated version of the %s type"
|
|
||||||
|> PreXmlDoc.create
|
|
||||||
|
|
||||||
let info =
|
|
||||||
SynComponentInfo.createLong recordId
|
|
||||||
|> SynComponentInfo.withDocString xmlDoc
|
|
||||||
|> SynComponentInfo.addAttributes [ SynAttribute.compilationRepresentation ]
|
|
||||||
|> SynComponentInfo.addAttributes [ SynAttribute.requireQualifiedAccess ]
|
|
||||||
|
|
||||||
SynModuleDecl.nestedModule info decls
|
|
||||||
|> List.singleton
|
|
||||||
|> SynModuleOrNamespace.createNamespace namespaceId
|
|
||||||
| _ -> failwithf "Not a record type"
|
|
||||||
|
|
||||||
open Myriad.Core
|
open Myriad.Core
|
||||||
|
|
||||||
@@ -164,7 +152,24 @@ type RemoveOptionsGenerator () =
|
|||||||
|> List.choose (fun (ns, types) ->
|
|> List.choose (fun (ns, types) ->
|
||||||
match types |> List.filter Ast.hasAttribute<RemoveOptionsAttribute> with
|
match types |> List.filter Ast.hasAttribute<RemoveOptionsAttribute> with
|
||||||
| [] -> None
|
| [] -> None
|
||||||
| types -> Some (ns, types)
|
| types ->
|
||||||
|
let types =
|
||||||
|
types
|
||||||
|
|> List.map (fun ty ->
|
||||||
|
match ty with
|
||||||
|
| SynTypeDefn.SynTypeDefn (sci,
|
||||||
|
SynTypeDefnRepr.Simple (SynTypeDefnSimpleRepr.Record (access,
|
||||||
|
fields,
|
||||||
|
_),
|
||||||
|
_),
|
||||||
|
smd,
|
||||||
|
smdo,
|
||||||
|
_,
|
||||||
|
_) -> RecordType.OfRecord sci smd access fields
|
||||||
|
| _ -> failwith "unexpectedly not a record"
|
||||||
|
)
|
||||||
|
|
||||||
|
Some (ns, types)
|
||||||
)
|
)
|
||||||
|
|
||||||
let modules =
|
let modules =
|
||||||
|
@@ -11,4 +11,27 @@ WoofWare.Myriad.Plugins.JsonParseGenerator..ctor [constructor]: unit
|
|||||||
WoofWare.Myriad.Plugins.JsonSerializeGenerator inherit obj, implements Myriad.Core.IMyriadGenerator
|
WoofWare.Myriad.Plugins.JsonSerializeGenerator inherit obj, implements Myriad.Core.IMyriadGenerator
|
||||||
WoofWare.Myriad.Plugins.JsonSerializeGenerator..ctor [constructor]: unit
|
WoofWare.Myriad.Plugins.JsonSerializeGenerator..ctor [constructor]: unit
|
||||||
WoofWare.Myriad.Plugins.RemoveOptionsGenerator inherit obj, implements Myriad.Core.IMyriadGenerator
|
WoofWare.Myriad.Plugins.RemoveOptionsGenerator inherit obj, implements Myriad.Core.IMyriadGenerator
|
||||||
WoofWare.Myriad.Plugins.RemoveOptionsGenerator..ctor [constructor]: unit
|
WoofWare.Myriad.Plugins.RemoveOptionsGenerator..ctor [constructor]: unit
|
||||||
|
WoofWare.Myriad.Plugins.SynFieldData`1 inherit obj
|
||||||
|
WoofWare.Myriad.Plugins.SynFieldData`1..ctor [constructor]: (Fantomas.FCS.Syntax.SynAttribute list, 'Ident, Fantomas.FCS.Syntax.SynType)
|
||||||
|
WoofWare.Myriad.Plugins.SynFieldData`1.Attrs [property]: [read-only] Fantomas.FCS.Syntax.SynAttribute list
|
||||||
|
WoofWare.Myriad.Plugins.SynFieldData`1.get_Attrs [method]: unit -> Fantomas.FCS.Syntax.SynAttribute list
|
||||||
|
WoofWare.Myriad.Plugins.SynFieldData`1.get_Ident [method]: unit -> 'Ident
|
||||||
|
WoofWare.Myriad.Plugins.SynFieldData`1.get_Type [method]: unit -> Fantomas.FCS.Syntax.SynType
|
||||||
|
WoofWare.Myriad.Plugins.SynFieldData`1.Ident [property]: [read-only] 'Ident
|
||||||
|
WoofWare.Myriad.Plugins.SynFieldData`1.Type [property]: [read-only] Fantomas.FCS.Syntax.SynType
|
||||||
|
WoofWare.Myriad.Plugins.UnionCase inherit obj
|
||||||
|
WoofWare.Myriad.Plugins.UnionCase.mapIdentFields [static method]: ('a -> 'b) -> 'a WoofWare.Myriad.Plugins.UnionCase -> 'b WoofWare.Myriad.Plugins.UnionCase
|
||||||
|
WoofWare.Myriad.Plugins.UnionCase.ofSynUnionCase [static method]: Fantomas.FCS.Syntax.SynUnionCase -> Fantomas.FCS.Syntax.Ident option WoofWare.Myriad.Plugins.UnionCase
|
||||||
|
WoofWare.Myriad.Plugins.UnionCase`1 inherit obj
|
||||||
|
WoofWare.Myriad.Plugins.UnionCase`1..ctor [constructor]: (Fantomas.FCS.Syntax.Ident, Fantomas.FCS.Xml.PreXmlDoc option, Fantomas.FCS.Syntax.SynAccess option, Fantomas.FCS.Syntax.SynAttribute list, 'ident WoofWare.Myriad.Plugins.SynFieldData list)
|
||||||
|
WoofWare.Myriad.Plugins.UnionCase`1.Access [property]: [read-only] Fantomas.FCS.Syntax.SynAccess option
|
||||||
|
WoofWare.Myriad.Plugins.UnionCase`1.Attributes [property]: [read-only] Fantomas.FCS.Syntax.SynAttribute list
|
||||||
|
WoofWare.Myriad.Plugins.UnionCase`1.Fields [property]: [read-only] 'ident WoofWare.Myriad.Plugins.SynFieldData list
|
||||||
|
WoofWare.Myriad.Plugins.UnionCase`1.get_Access [method]: unit -> Fantomas.FCS.Syntax.SynAccess option
|
||||||
|
WoofWare.Myriad.Plugins.UnionCase`1.get_Attributes [method]: unit -> Fantomas.FCS.Syntax.SynAttribute list
|
||||||
|
WoofWare.Myriad.Plugins.UnionCase`1.get_Fields [method]: unit -> 'ident WoofWare.Myriad.Plugins.SynFieldData list
|
||||||
|
WoofWare.Myriad.Plugins.UnionCase`1.get_Name [method]: unit -> Fantomas.FCS.Syntax.Ident
|
||||||
|
WoofWare.Myriad.Plugins.UnionCase`1.get_XmlDoc [method]: unit -> Fantomas.FCS.Xml.PreXmlDoc option
|
||||||
|
WoofWare.Myriad.Plugins.UnionCase`1.Name [property]: [read-only] Fantomas.FCS.Syntax.Ident
|
||||||
|
WoofWare.Myriad.Plugins.UnionCase`1.XmlDoc [property]: [read-only] Fantomas.FCS.Xml.PreXmlDoc option
|
@@ -2,6 +2,7 @@ namespace WoofWare.Myriad.Plugins
|
|||||||
|
|
||||||
open System
|
open System
|
||||||
open System.Text
|
open System.Text
|
||||||
|
open System.Text.RegularExpressions
|
||||||
open Fantomas.FCS.Syntax
|
open Fantomas.FCS.Syntax
|
||||||
open Fantomas.FCS.Text.Range
|
open Fantomas.FCS.Text.Range
|
||||||
|
|
||||||
@@ -9,6 +10,54 @@ open Fantomas.FCS.Text.Range
|
|||||||
module internal Ident =
|
module internal Ident =
|
||||||
let inline create (s : string) = Ident (s, range0)
|
let inline create (s : string) = Ident (s, range0)
|
||||||
|
|
||||||
|
/// Fantomas bug, perhaps? "type" is not rendered as ``type``, although the ASTs are identical
|
||||||
|
/// apart from the ranges?
|
||||||
|
/// Awful hack: here is a function that does this sort of thing.
|
||||||
|
let createSanitisedParamName (s : string) =
|
||||||
|
match s with
|
||||||
|
| "type" -> create "type'"
|
||||||
|
| "private" -> create "private'"
|
||||||
|
| _ ->
|
||||||
|
|
||||||
|
let result = StringBuilder ()
|
||||||
|
|
||||||
|
for i = 0 to s.Length - 1 do
|
||||||
|
if Char.IsLetter s.[i] then
|
||||||
|
result.Append s.[i] |> ignore<StringBuilder>
|
||||||
|
elif Char.IsNumber s.[i] then
|
||||||
|
if result.Length > 0 then
|
||||||
|
result.Append s.[i] |> ignore<StringBuilder>
|
||||||
|
elif s.[i] = '_' || s.[i] = '-' then
|
||||||
|
result.Append '_' |> ignore<StringBuilder>
|
||||||
|
else
|
||||||
|
failwith $"could not convert to ident: %s{s}"
|
||||||
|
|
||||||
|
create (result.ToString ())
|
||||||
|
|
||||||
|
let private alnum = Regex @"^[a-zA-Z][a-zA-Z0-9]*$"
|
||||||
|
|
||||||
|
let createSanitisedTypeName (s : string) =
|
||||||
|
let result = StringBuilder ()
|
||||||
|
let mutable capitalize = true
|
||||||
|
|
||||||
|
for i = 0 to s.Length - 1 do
|
||||||
|
if Char.IsLetter s.[i] then
|
||||||
|
if capitalize then
|
||||||
|
result.Append (Char.ToUpperInvariant s.[i]) |> ignore<StringBuilder>
|
||||||
|
capitalize <- false
|
||||||
|
else
|
||||||
|
result.Append s.[i] |> ignore<StringBuilder>
|
||||||
|
elif Char.IsNumber s.[i] then
|
||||||
|
if result.Length > 0 then
|
||||||
|
result.Append s.[i] |> ignore<StringBuilder>
|
||||||
|
elif s.[i] = '_' then
|
||||||
|
capitalize <- true
|
||||||
|
|
||||||
|
if result.Length = 0 then
|
||||||
|
failwith $"String %s{s} was not suitable as a type identifier"
|
||||||
|
|
||||||
|
Ident (result.ToString (), range0)
|
||||||
|
|
||||||
let lowerFirstLetter (x : Ident) : Ident =
|
let lowerFirstLetter (x : Ident) : Ident =
|
||||||
let result = StringBuilder x.idText.Length
|
let result = StringBuilder x.idText.Length
|
||||||
result.Append (Char.ToLowerInvariant x.idText.[0]) |> ignore
|
result.Append (Char.ToLowerInvariant x.idText.[0]) |> ignore
|
||||||
|
@@ -6,7 +6,12 @@ open Fantomas.FCS.Text.Range
|
|||||||
[<RequireQualifiedAccess>]
|
[<RequireQualifiedAccess>]
|
||||||
module internal PreXmlDoc =
|
module internal PreXmlDoc =
|
||||||
let create (s : string) : PreXmlDoc =
|
let create (s : string) : PreXmlDoc =
|
||||||
PreXmlDoc.Create ([| " " + s |], range0)
|
let s = s.Split "\n"
|
||||||
|
|
||||||
|
for i = 0 to s.Length - 1 do
|
||||||
|
s.[i] <- " " + s.[i]
|
||||||
|
|
||||||
|
PreXmlDoc.Create (s, range0)
|
||||||
|
|
||||||
let create' (s : string seq) : PreXmlDoc =
|
let create' (s : string seq) : PreXmlDoc =
|
||||||
PreXmlDoc.Create (Array.ofSeq s, range0)
|
PreXmlDoc.Create (Array.ofSeq s, range0)
|
||||||
|
@@ -9,12 +9,12 @@ module internal SynArgPats =
|
|||||||
match caseNames.Length with
|
match caseNames.Length with
|
||||||
| 0 -> SynArgPats.Pats []
|
| 0 -> SynArgPats.Pats []
|
||||||
| 1 ->
|
| 1 ->
|
||||||
SynPat.Named (SynIdent.SynIdent (Ident.create caseNames.[0], None), false, None, range0)
|
SynPat.Named (SynIdent.createS caseNames.[0], false, None, range0)
|
||||||
|> List.singleton
|
|> List.singleton
|
||||||
|> SynArgPats.Pats
|
|> SynArgPats.Pats
|
||||||
| len ->
|
| len ->
|
||||||
caseNames
|
caseNames
|
||||||
|> List.map (fun name -> SynPat.Named (SynIdent.SynIdent (Ident.create name, None), false, None, range0))
|
|> List.map (fun name -> SynPat.Named (SynIdent.createS name, false, None, range0))
|
||||||
|> fun t -> SynPat.Tuple (false, t, List.replicate (len - 1) range0, range0)
|
|> fun t -> SynPat.Tuple (false, t, List.replicate (len - 1) range0, range0)
|
||||||
|> fun t -> SynPat.Paren (t, range0)
|
|> fun t -> SynPat.Paren (t, range0)
|
||||||
|> List.singleton
|
|> List.singleton
|
||||||
|
@@ -5,32 +5,23 @@ open Fantomas.FCS.Text.Range
|
|||||||
|
|
||||||
[<RequireQualifiedAccess>]
|
[<RequireQualifiedAccess>]
|
||||||
module internal SynAttribute =
|
module internal SynAttribute =
|
||||||
let internal compilationRepresentation : SynAttribute =
|
let inline create (typeName : SynLongIdent) (arg : SynExpr) : SynAttribute =
|
||||||
{
|
{
|
||||||
TypeName = SynLongIdent.createS "CompilationRepresentation"
|
TypeName = typeName
|
||||||
ArgExpr =
|
ArgExpr = arg
|
||||||
[ "CompilationRepresentationFlags" ; "ModuleSuffix" ]
|
|
||||||
|> SynExpr.createLongIdent
|
|
||||||
|> SynExpr.paren
|
|
||||||
Target = None
|
Target = None
|
||||||
AppliesToGetterAndSetter = false
|
AppliesToGetterAndSetter = false
|
||||||
Range = range0
|
Range = range0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let internal compilationRepresentation : SynAttribute =
|
||||||
|
[ "CompilationRepresentationFlags" ; "ModuleSuffix" ]
|
||||||
|
|> SynExpr.createLongIdent
|
||||||
|
|> SynExpr.paren
|
||||||
|
|> create (SynLongIdent.createS "CompilationRepresentation")
|
||||||
|
|
||||||
let internal requireQualifiedAccess : SynAttribute =
|
let internal requireQualifiedAccess : SynAttribute =
|
||||||
{
|
create (SynLongIdent.createS "RequireQualifiedAccess") (SynExpr.CreateConst ())
|
||||||
TypeName = SynLongIdent.createS "RequireQualifiedAccess"
|
|
||||||
ArgExpr = SynExpr.CreateConst ()
|
|
||||||
Target = None
|
|
||||||
AppliesToGetterAndSetter = false
|
|
||||||
Range = range0
|
|
||||||
}
|
|
||||||
|
|
||||||
let internal autoOpen : SynAttribute =
|
let internal autoOpen : SynAttribute =
|
||||||
{
|
create (SynLongIdent.createS "AutoOpen") (SynExpr.CreateConst ())
|
||||||
TypeName = SynLongIdent.createS "AutoOpen"
|
|
||||||
ArgExpr = SynExpr.CreateConst ()
|
|
||||||
Target = None
|
|
||||||
AppliesToGetterAndSetter = false
|
|
||||||
Range = range0
|
|
||||||
}
|
|
||||||
|
@@ -80,6 +80,16 @@ module internal SynExpr =
|
|||||||
let equals (a : SynExpr) (b : SynExpr) =
|
let equals (a : SynExpr) (b : SynExpr) =
|
||||||
SynExpr.CreateAppInfix (SynExpr.CreateLongIdent SynLongIdent.eq, a) |> applyTo b
|
SynExpr.CreateAppInfix (SynExpr.CreateLongIdent SynLongIdent.eq, a) |> applyTo b
|
||||||
|
|
||||||
|
/// {a} && {b}
|
||||||
|
let booleanAnd (a : SynExpr) (b : SynExpr) =
|
||||||
|
SynExpr.CreateAppInfix (SynExpr.CreateLongIdent SynLongIdent.booleanAnd, a)
|
||||||
|
|> applyTo b
|
||||||
|
|
||||||
|
/// {a} || {b}
|
||||||
|
let booleanOr (a : SynExpr) (b : SynExpr) =
|
||||||
|
SynExpr.CreateAppInfix (SynExpr.CreateLongIdent SynLongIdent.booleanOr, a)
|
||||||
|
|> applyTo b
|
||||||
|
|
||||||
/// {a} + {b}
|
/// {a} + {b}
|
||||||
let plus (a : SynExpr) (b : SynExpr) =
|
let plus (a : SynExpr) (b : SynExpr) =
|
||||||
SynExpr.CreateAppInfix (
|
SynExpr.CreateAppInfix (
|
||||||
@@ -131,16 +141,15 @@ module internal SynExpr =
|
|||||||
let typeApp (types : SynType list) (operand : SynExpr) =
|
let typeApp (types : SynType list) (operand : SynExpr) =
|
||||||
SynExpr.TypeApp (operand, range0, types, List.replicate (types.Length - 1) range0, Some range0, range0, range0)
|
SynExpr.TypeApp (operand, range0, types, List.replicate (types.Length - 1) range0, Some range0, range0, range0)
|
||||||
|
|
||||||
let callGenericMethod (meth : string) (ty : LongIdent) (obj : SynExpr) : SynExpr =
|
/// {obj}.{meth}<types,...>()
|
||||||
SynExpr.DotGet (obj, range0, SynLongIdent.createS meth, range0)
|
let callGenericMethod (meth : SynLongIdent) (types : SynType list) (obj : SynExpr) : SynExpr =
|
||||||
|> typeApp [ SynType.LongIdent (SynLongIdent.create ty) ]
|
SynExpr.DotGet (obj, range0, meth, range0)
|
||||||
|
|> typeApp types
|
||||||
|> applyTo (SynExpr.CreateConst ())
|
|> applyTo (SynExpr.CreateConst ())
|
||||||
|
|
||||||
/// {obj}.{meth}<ty>()
|
/// {obj}.{meth}<ty>()
|
||||||
let callGenericMethod' (meth : string) (ty : string) (obj : SynExpr) : SynExpr =
|
let callGenericMethod' (meth : string) (ty : string) (obj : SynExpr) : SynExpr =
|
||||||
SynExpr.DotGet (obj, range0, SynLongIdent.createS meth, range0)
|
callGenericMethod (SynLongIdent.createS meth) [ SynType.createLongIdent' [ ty ] ] obj
|
||||||
|> typeApp [ SynType.createLongIdent' [ ty ] ]
|
|
||||||
|> applyTo (SynExpr.CreateConst ())
|
|
||||||
|
|
||||||
let inline index (property : SynExpr) (obj : SynExpr) : SynExpr =
|
let inline index (property : SynExpr) (obj : SynExpr) : SynExpr =
|
||||||
SynExpr.DotIndexedGet (obj, property, range0, range0)
|
SynExpr.DotIndexedGet (obj, property, range0, range0)
|
||||||
@@ -232,6 +241,8 @@ module internal SynExpr =
|
|||||||
let inline createLet (bindings : SynBinding list) (body : SynExpr) : SynExpr =
|
let inline createLet (bindings : SynBinding list) (body : SynExpr) : SynExpr =
|
||||||
SynExpr.LetOrUse (false, false, bindings, body, range0, SynExprLetOrUseTrivia.empty)
|
SynExpr.LetOrUse (false, false, bindings, body, range0, SynExprLetOrUseTrivia.empty)
|
||||||
|
|
||||||
|
let inline createDo (body : SynExpr) : SynExpr = SynExpr.Do (body, range0)
|
||||||
|
|
||||||
let inline createMatch (matchOn : SynExpr) (cases : SynMatchClause list) : SynExpr =
|
let inline createMatch (matchOn : SynExpr) (cases : SynMatchClause list) : SynExpr =
|
||||||
SynExpr.Match (
|
SynExpr.Match (
|
||||||
DebugPointAtBinding.Yes range0,
|
DebugPointAtBinding.Yes range0,
|
||||||
|
@@ -5,10 +5,17 @@ open Fantomas.FCS.Syntax
|
|||||||
open Fantomas.FCS.SyntaxTrivia
|
open Fantomas.FCS.SyntaxTrivia
|
||||||
open Fantomas.FCS.Xml
|
open Fantomas.FCS.Xml
|
||||||
|
|
||||||
type internal SynFieldData<'Ident> =
|
/// The data needed to reconstitute a single piece of data within a union field, or a single record field.
|
||||||
|
/// This is generic on whether the field is identified. For example, in `type Foo = Blah of int`, the `int`
|
||||||
|
/// field is not identified; whereas in `type Foo = Blah of baz : int`, it is identified.
|
||||||
|
type SynFieldData<'Ident> =
|
||||||
{
|
{
|
||||||
|
/// Attributes on this field. I think you can only get these if this is a *record* field.
|
||||||
Attrs : SynAttribute list
|
Attrs : SynAttribute list
|
||||||
|
/// The identifier of this field (see docstring for SynFieldData).
|
||||||
Ident : 'Ident
|
Ident : 'Ident
|
||||||
|
/// The type of the data contained in this field. For example, `type Foo = { Blah : int }`
|
||||||
|
/// has this being `int`.
|
||||||
Type : SynType
|
Type : SynType
|
||||||
}
|
}
|
||||||
|
|
||||||
|
10
WoofWare.Myriad.Plugins/SynExpr/SynIdent.fs
Normal file
10
WoofWare.Myriad.Plugins/SynExpr/SynIdent.fs
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
namespace WoofWare.Myriad.Plugins
|
||||||
|
|
||||||
|
open Fantomas.FCS.Syntax
|
||||||
|
|
||||||
|
[<RequireQualifiedAccess>]
|
||||||
|
module internal SynIdent =
|
||||||
|
let inline createI (i : Ident) : SynIdent = SynIdent.SynIdent (i, None)
|
||||||
|
|
||||||
|
let inline createS (i : string) : SynIdent =
|
||||||
|
SynIdent.SynIdent (Ident.create i, None)
|
@@ -33,6 +33,12 @@ module internal SynLongIdent =
|
|||||||
let eq =
|
let eq =
|
||||||
SynLongIdent.SynLongIdent ([ Ident.create "op_Equality" ], [], [ Some (IdentTrivia.OriginalNotation "=") ])
|
SynLongIdent.SynLongIdent ([ Ident.create "op_Equality" ], [], [ Some (IdentTrivia.OriginalNotation "=") ])
|
||||||
|
|
||||||
|
let booleanAnd =
|
||||||
|
SynLongIdent.SynLongIdent ([ Ident.create "op_BooleanAnd" ], [], [ Some (IdentTrivia.OriginalNotation "&&") ])
|
||||||
|
|
||||||
|
let booleanOr =
|
||||||
|
SynLongIdent.SynLongIdent ([ Ident.create "op_BooleanOr" ], [], [ Some (IdentTrivia.OriginalNotation "||") ])
|
||||||
|
|
||||||
let pipe =
|
let pipe =
|
||||||
SynLongIdent.SynLongIdent ([ Ident.create "op_PipeRight" ], [], [ Some (IdentTrivia.OriginalNotation "|>") ])
|
SynLongIdent.SynLongIdent ([ Ident.create "op_PipeRight" ], [], [ Some (IdentTrivia.OriginalNotation "|>") ])
|
||||||
|
|
||||||
|
@@ -17,8 +17,8 @@ module internal SynMemberDefn =
|
|||||||
SynMemberFlags.MemberKind = SynMemberKind.Member
|
SynMemberFlags.MemberKind = SynMemberKind.Member
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
let abstractMember
|
let abstractMember
|
||||||
|
(attrs : SynAttribute list)
|
||||||
(ident : SynIdent)
|
(ident : SynIdent)
|
||||||
(typars : SynTyparDecls option)
|
(typars : SynTyparDecls option)
|
||||||
(arity : SynValInfo)
|
(arity : SynValInfo)
|
||||||
@@ -28,7 +28,13 @@ module internal SynMemberDefn =
|
|||||||
=
|
=
|
||||||
let slot =
|
let slot =
|
||||||
SynValSig.SynValSig (
|
SynValSig.SynValSig (
|
||||||
[],
|
attrs
|
||||||
|
|> List.map (fun attr ->
|
||||||
|
{
|
||||||
|
Attributes = [ attr ]
|
||||||
|
Range = range0
|
||||||
|
}
|
||||||
|
),
|
||||||
ident,
|
ident,
|
||||||
SynValTyparDecls.SynValTyparDecls (typars, true),
|
SynValTyparDecls.SynValTyparDecls (typars, true),
|
||||||
returnType,
|
returnType,
|
||||||
|
@@ -181,6 +181,29 @@ module internal SynTypePatterns =
|
|||||||
_) -> Some (ident, outer)
|
_) -> Some (ident, outer)
|
||||||
| _ -> None
|
| _ -> None
|
||||||
|
|
||||||
|
let (|JsonNode|_|) (fieldType : SynType) : unit option =
|
||||||
|
match fieldType with
|
||||||
|
| SynType.LongIdent (SynLongIdent.SynLongIdent (ident, _, _)) ->
|
||||||
|
match ident |> List.map (fun i -> i.idText) with
|
||||||
|
| [ "System" ; "Text" ; "Json" ; "Nodes" ; "JsonNode" ]
|
||||||
|
| [ "Text" ; "Json" ; "Nodes" ; "JsonNode" ]
|
||||||
|
| [ "Json" ; "Nodes" ; "JsonNode" ]
|
||||||
|
| [ "Nodes" ; "JsonNode" ]
|
||||||
|
| [ "JsonNode" ] -> Some ()
|
||||||
|
| _ -> None
|
||||||
|
| _ -> None
|
||||||
|
|
||||||
|
let (|Unit|_|) (fieldType : SynType) : unit option =
|
||||||
|
match fieldType with
|
||||||
|
| SynType.LongIdent (SynLongIdent.SynLongIdent (ident, _, _)) ->
|
||||||
|
match ident |> List.map (fun i -> i.idText.ToLowerInvariant ()) with
|
||||||
|
| [ "microsoft" ; "fsharp" ; "core" ; "unit" ]
|
||||||
|
| [ "fsharp" ; "core" ; "unit" ]
|
||||||
|
| [ "core" ; "unit" ]
|
||||||
|
| [ "unit" ] -> Some ()
|
||||||
|
| _ -> None
|
||||||
|
| _ -> None
|
||||||
|
|
||||||
let (|DateOnly|_|) (fieldType : SynType) =
|
let (|DateOnly|_|) (fieldType : SynType) =
|
||||||
match fieldType with
|
match fieldType with
|
||||||
| SynType.LongIdent (SynLongIdent.SynLongIdent (ident, _, _)) ->
|
| SynType.LongIdent (SynLongIdent.SynLongIdent (ident, _, _)) ->
|
||||||
@@ -267,6 +290,8 @@ module internal SynType =
|
|||||||
| SynType.Paren (ty, _) -> stripOptionalParen ty
|
| SynType.Paren (ty, _) -> stripOptionalParen ty
|
||||||
| ty -> ty
|
| ty -> ty
|
||||||
|
|
||||||
|
let inline paren (ty : SynType) : SynType = SynType.Paren (ty, range0)
|
||||||
|
|
||||||
let inline createLongIdent (ident : LongIdent) : SynType =
|
let inline createLongIdent (ident : LongIdent) : SynType =
|
||||||
SynType.LongIdent (SynLongIdent.create ident)
|
SynType.LongIdent (SynLongIdent.create ident)
|
||||||
|
|
||||||
@@ -283,6 +308,17 @@ module internal SynType =
|
|||||||
|
|
||||||
let inline app (name : string) (args : SynType list) : SynType = app' (named name) args
|
let inline app (name : string) (args : SynType list) : SynType = app' (named name) args
|
||||||
|
|
||||||
|
/// Returns None if the input list was empty.
|
||||||
|
let inline tupleNoParen (ty : SynType list) : SynType option =
|
||||||
|
match List.rev ty with
|
||||||
|
| [] -> None
|
||||||
|
| [ t ] -> Some t
|
||||||
|
| t :: rest ->
|
||||||
|
([ SynTupleTypeSegment.Type t ], rest)
|
||||||
|
||> List.fold (fun ty nextArg -> SynTupleTypeSegment.Type nextArg :: SynTupleTypeSegment.Star range0 :: ty)
|
||||||
|
|> fun segs -> SynType.Tuple (false, segs, range0)
|
||||||
|
|> Some
|
||||||
|
|
||||||
let inline appPostfix (name : string) (arg : SynType) : SynType =
|
let inline appPostfix (name : string) (arg : SynType) : SynType =
|
||||||
SynType.App (named name, None, [ arg ], [], None, true, range0)
|
SynType.App (named name, None, [ arg ], [], None, true, range0)
|
||||||
|
|
||||||
@@ -299,16 +335,54 @@ module internal SynType =
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
let inline signatureParamOfType (ty : SynType) (name : Ident option) : SynType =
|
let inline signatureParamOfType
|
||||||
SynType.SignatureParameter ([], false, name, ty, range0)
|
(attrs : SynAttribute list)
|
||||||
|
(ty : SynType)
|
||||||
|
(optional : bool)
|
||||||
|
(name : Ident option)
|
||||||
|
: SynType
|
||||||
|
=
|
||||||
|
SynType.SignatureParameter (
|
||||||
|
attrs
|
||||||
|
|> List.map (fun attr ->
|
||||||
|
{
|
||||||
|
Attributes = [ attr ]
|
||||||
|
Range = range0
|
||||||
|
}
|
||||||
|
),
|
||||||
|
optional,
|
||||||
|
name,
|
||||||
|
ty,
|
||||||
|
range0
|
||||||
|
)
|
||||||
|
|
||||||
let inline var (ty : SynTypar) : SynType = SynType.Var (ty, range0)
|
let inline var (ty : SynTypar) : SynType = SynType.Var (ty, range0)
|
||||||
|
|
||||||
let unit : SynType = named "unit"
|
let unit : SynType = named "unit"
|
||||||
|
let obj : SynType = named "obj"
|
||||||
|
let bool : SynType = named "bool"
|
||||||
let int : SynType = named "int"
|
let int : SynType = named "int"
|
||||||
|
let array (elt : SynType) : SynType = SynType.Array (1, elt, range0)
|
||||||
|
|
||||||
|
let list (elt : SynType) : SynType =
|
||||||
|
SynType.App (named "list", None, [ elt ], [], None, true, range0)
|
||||||
|
|
||||||
|
let option (elt : SynType) : SynType =
|
||||||
|
SynType.App (named "option", None, [ elt ], [], None, true, range0)
|
||||||
|
|
||||||
let anon : SynType = SynType.Anon range0
|
let anon : SynType = SynType.Anon range0
|
||||||
|
|
||||||
|
let task (elt : SynType) : SynType =
|
||||||
|
SynType.App (
|
||||||
|
createLongIdent' [ "System" ; "Threading" ; "Tasks" ; "Task" ],
|
||||||
|
None,
|
||||||
|
[ elt ],
|
||||||
|
[],
|
||||||
|
None,
|
||||||
|
true,
|
||||||
|
range0
|
||||||
|
)
|
||||||
|
|
||||||
let string : SynType = named "string"
|
let string : SynType = named "string"
|
||||||
|
|
||||||
/// Given ['a1, 'a2] and 'ret, returns 'a1 -> 'a2 -> 'ret.
|
/// Given ['a1, 'a2] and 'ret, returns 'a1 -> 'a2 -> 'ret.
|
||||||
@@ -363,6 +437,7 @@ module internal SynType =
|
|||||||
| DateTimeOffset -> "DateTimeOffset"
|
| DateTimeOffset -> "DateTimeOffset"
|
||||||
| DateOnly -> "DateOnly"
|
| DateOnly -> "DateOnly"
|
||||||
| TimeSpan -> "TimeSpan"
|
| TimeSpan -> "TimeSpan"
|
||||||
|
| SynType.LongIdent (SynLongIdent.SynLongIdent (ident, _, _)) -> ident |> List.map _.idText |> String.concat "."
|
||||||
| ty -> failwithf "could not compute human-readable string for type: %O" ty
|
| ty -> failwithf "could not compute human-readable string for type: %O" ty
|
||||||
|
|
||||||
/// Guess whether the types are equal. We err on the side of saying "no, they're different".
|
/// Guess whether the types are equal. We err on the side of saying "no, they're different".
|
||||||
@@ -454,4 +529,11 @@ module internal SynType =
|
|||||||
match ty2 with
|
match ty2 with
|
||||||
| DateOnly -> true
|
| DateOnly -> true
|
||||||
| _ -> false
|
| _ -> false
|
||||||
| _ -> false
|
| _ ->
|
||||||
|
|
||||||
|
match ty1, ty2 with
|
||||||
|
| SynType.LongIdent (SynLongIdent (ident1, _, _)), SynType.LongIdent (SynLongIdent (ident2, _, _)) ->
|
||||||
|
let ident1 = ident1 |> List.map _.idText
|
||||||
|
let ident2 = ident2 |> List.map _.idText
|
||||||
|
ident1 = ident2
|
||||||
|
| _, _ -> false
|
||||||
|
@@ -13,8 +13,12 @@ module internal SynTypeDefnRepr =
|
|||||||
let inline augmentation () : SynTypeDefnRepr =
|
let inline augmentation () : SynTypeDefnRepr =
|
||||||
SynTypeDefnRepr.ObjectModel (SynTypeDefnKind.Augmentation range0, [], range0)
|
SynTypeDefnRepr.ObjectModel (SynTypeDefnKind.Augmentation range0, [], range0)
|
||||||
|
|
||||||
let inline union (cases : SynUnionCase list) : SynTypeDefnRepr =
|
let inline unionWithAccess (implAccess : SynAccess option) (cases : SynUnionCase list) : SynTypeDefnRepr =
|
||||||
SynTypeDefnRepr.Simple (SynTypeDefnSimpleRepr.Union (None, cases, range0), range0)
|
SynTypeDefnRepr.Simple (SynTypeDefnSimpleRepr.Union (implAccess, cases, range0), range0)
|
||||||
|
|
||||||
let inline record (fields : SynField list) : SynTypeDefnRepr =
|
let inline union (cases : SynUnionCase list) : SynTypeDefnRepr = unionWithAccess None cases
|
||||||
SynTypeDefnRepr.Simple (SynTypeDefnSimpleRepr.Record (None, fields, range0), range0)
|
|
||||||
|
let inline recordWithAccess (implAccess : SynAccess option) (fields : SynField list) : SynTypeDefnRepr =
|
||||||
|
SynTypeDefnRepr.Simple (SynTypeDefnSimpleRepr.Record (implAccess, fields, range0), range0)
|
||||||
|
|
||||||
|
let inline record (fields : SynField list) : SynTypeDefnRepr = recordWithAccess None fields
|
||||||
|
@@ -5,44 +5,24 @@ open Fantomas.FCS.Text.Range
|
|||||||
open Fantomas.FCS.Xml
|
open Fantomas.FCS.Xml
|
||||||
open Fantomas.FCS.SyntaxTrivia
|
open Fantomas.FCS.SyntaxTrivia
|
||||||
|
|
||||||
type internal UnionCase<'Ident> =
|
/// Represents everything you need to know about a union case.
|
||||||
|
/// This is generic on whether each field of this case must be named.
|
||||||
|
type UnionCase<'ident> =
|
||||||
{
|
{
|
||||||
Fields : SynFieldData<'Ident> list
|
/// The name of the case: e.g. `| Foo of blah` has this being `Foo`.
|
||||||
Attrs : SynAttribute list
|
Name : Ident
|
||||||
Ident : Ident
|
/// Any docstring associated with this case.
|
||||||
|
XmlDoc : PreXmlDoc option
|
||||||
|
/// Any accessibility modifier: e.g. `type Foo = private | Blah`.
|
||||||
|
Access : SynAccess option
|
||||||
|
/// Attributes on the case: for example, `| [<Attr>] Foo of blah`.
|
||||||
|
Attributes : SynAttribute list
|
||||||
|
/// The data contained within the case: for example, `[blah]` in `| Foo of blah`.
|
||||||
|
Fields : SynFieldData<'ident> list
|
||||||
}
|
}
|
||||||
|
|
||||||
[<RequireQualifiedAccess>]
|
|
||||||
module internal UnionCase =
|
|
||||||
let mapIdentFields<'a, 'b> (f : 'a -> 'b) (unionCase : UnionCase<'a>) : UnionCase<'b> =
|
|
||||||
{
|
|
||||||
Fields = unionCase.Fields |> List.map (SynField.mapIdent f)
|
|
||||||
Attrs = unionCase.Attrs
|
|
||||||
Ident = unionCase.Ident
|
|
||||||
}
|
|
||||||
|
|
||||||
[<RequireQualifiedAccess>]
|
[<RequireQualifiedAccess>]
|
||||||
module internal SynUnionCase =
|
module internal SynUnionCase =
|
||||||
let extract (SynUnionCase (attrs, id, caseType, _, _, _, _)) : UnionCase<Ident option> =
|
|
||||||
match caseType with
|
|
||||||
| SynUnionCaseKind.FullType _ -> failwith "WoofWare.Myriad does not support FullType union cases."
|
|
||||||
| SynUnionCaseKind.Fields fields ->
|
|
||||||
|
|
||||||
let fields = fields |> List.map SynField.extract
|
|
||||||
|
|
||||||
let id =
|
|
||||||
match id with
|
|
||||||
| SynIdent.SynIdent (ident, _) -> ident
|
|
||||||
|
|
||||||
// As far as I can tell, there's no way to get any attributes here? :shrug:
|
|
||||||
let attrs = attrs |> List.collect (fun l -> l.Attributes)
|
|
||||||
|
|
||||||
{
|
|
||||||
Fields = fields
|
|
||||||
Attrs = attrs
|
|
||||||
Ident = id
|
|
||||||
}
|
|
||||||
|
|
||||||
let create (case : UnionCase<Ident>) : SynUnionCase =
|
let create (case : UnionCase<Ident>) : SynUnionCase =
|
||||||
let fields =
|
let fields =
|
||||||
case.Fields
|
case.Fields
|
||||||
@@ -63,11 +43,11 @@ module internal SynUnionCase =
|
|||||||
)
|
)
|
||||||
|
|
||||||
SynUnionCase.SynUnionCase (
|
SynUnionCase.SynUnionCase (
|
||||||
SynAttributes.ofAttrs case.Attrs,
|
SynAttributes.ofAttrs case.Attributes,
|
||||||
SynIdent.SynIdent (case.Ident, None),
|
SynIdent.createI case.Name,
|
||||||
SynUnionCaseKind.Fields fields,
|
SynUnionCaseKind.Fields fields,
|
||||||
PreXmlDoc.Empty,
|
case.XmlDoc |> Option.defaultValue PreXmlDoc.Empty,
|
||||||
None,
|
case.Access,
|
||||||
range0,
|
range0,
|
||||||
{
|
{
|
||||||
BarRange = Some range0
|
BarRange = Some range0
|
||||||
|
18
WoofWare.Myriad.Plugins/Teq.fs
Normal file
18
WoofWare.Myriad.Plugins/Teq.fs
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
namespace WoofWare.Myriad.Plugins
|
||||||
|
|
||||||
|
// Extracted from https://github.com/G-Research/TypeEquality
|
||||||
|
// which is Apache-2.0 licenced. See `TeqLicence.txt`.
|
||||||
|
// We inline this code because Myriad doesn't seem to reliably load package references in the generator.
|
||||||
|
// I have reformatted a little, and stripped out all the code I don't use.
|
||||||
|
|
||||||
|
type internal Teq<'a, 'b> = private | Teq of ('a -> 'b) * ('b -> 'a)
|
||||||
|
|
||||||
|
[<RequireQualifiedAccess>]
|
||||||
|
module internal Teq =
|
||||||
|
|
||||||
|
let refl<'a> : Teq<'a, 'a> = Teq (id, id)
|
||||||
|
let cast (Teq (f, _)) a = f a
|
||||||
|
|
||||||
|
[<RequireQualifiedAccess>]
|
||||||
|
module Cong =
|
||||||
|
let believeMe<'a, 'b, 'a2, 'b2> (_ : Teq<'a, 'b>) : Teq<'a2, 'b2> = unbox <| (refl : Teq<'a2, 'a2>)
|
201
WoofWare.Myriad.Plugins/TeqLicence.txt
Normal file
201
WoofWare.Myriad.Plugins/TeqLicence.txt
Normal file
@@ -0,0 +1,201 @@
|
|||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
APPENDIX: How to apply the Apache License to your work.
|
||||||
|
|
||||||
|
To apply the Apache License to your work, attach the following
|
||||||
|
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||||
|
replaced with your own identifying information. (Don't include
|
||||||
|
the brackets!) The text should be enclosed in the appropriate
|
||||||
|
comment syntax for the file format. We also recommend that a
|
||||||
|
file or class name and description of purpose be included on the
|
||||||
|
same "printed page" as the copyright notice for easier
|
||||||
|
identification within third-party archives.
|
||||||
|
|
||||||
|
Copyright [yyyy] [name of copyright owner]
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
@@ -25,10 +25,12 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Compile Include="List.fs"/>
|
<Compile Include="List.fs"/>
|
||||||
|
<Compile Include="Teq.fs" />
|
||||||
<Compile Include="Primitives.fs" />
|
<Compile Include="Primitives.fs" />
|
||||||
<Compile Include="SynExpr\SynAttributes.fs" />
|
<Compile Include="SynExpr\SynAttributes.fs" />
|
||||||
<Compile Include="SynExpr\PreXmlDoc.fs" />
|
<Compile Include="SynExpr\PreXmlDoc.fs" />
|
||||||
<Compile Include="SynExpr\Ident.fs" />
|
<Compile Include="SynExpr\Ident.fs" />
|
||||||
|
<Compile Include="SynExpr\SynIdent.fs" />
|
||||||
<Compile Include="SynExpr\SynLongIdent.fs" />
|
<Compile Include="SynExpr\SynLongIdent.fs" />
|
||||||
<Compile Include="SynExpr\SynExprLetOrUseTrivia.fs" />
|
<Compile Include="SynExpr\SynExprLetOrUseTrivia.fs" />
|
||||||
<Compile Include="SynExpr\SynArgPats.fs" />
|
<Compile Include="SynExpr\SynArgPats.fs" />
|
||||||
@@ -56,6 +58,7 @@
|
|||||||
<Compile Include="HttpClientGenerator.fs"/>
|
<Compile Include="HttpClientGenerator.fs"/>
|
||||||
<Compile Include="CataGenerator.fs" />
|
<Compile Include="CataGenerator.fs" />
|
||||||
<Compile Include="ArgParserGenerator.fs" />
|
<Compile Include="ArgParserGenerator.fs" />
|
||||||
|
<None Include="TeqLicence.txt" />
|
||||||
<EmbeddedResource Include="version.json"/>
|
<EmbeddedResource Include="version.json"/>
|
||||||
<EmbeddedResource Include="SurfaceBaseline.txt"/>
|
<EmbeddedResource Include="SurfaceBaseline.txt"/>
|
||||||
<None Include="..\README.md">
|
<None Include="..\README.md">
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"version": "2.2",
|
"version": "3.0",
|
||||||
"publicReleaseRefSpec": [
|
"publicReleaseRefSpec": [
|
||||||
"^refs/heads/main$"
|
"^refs/heads/main$"
|
||||||
],
|
],
|
||||||
|
6
flake.lock
generated
6
flake.lock
generated
@@ -20,11 +20,11 @@
|
|||||||
},
|
},
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1725099143,
|
"lastModified": 1725534445,
|
||||||
"narHash": "sha256-CHgumPZaC7z+WYx72WgaLt2XF0yUVzJS60rO4GZ7ytY=",
|
"narHash": "sha256-Yd0FK9SkWy+ZPuNqUgmVPXokxDgMJoGuNpMEtkfcf84=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "5629520edecb69630a3f4d17d3d33fc96c13f6fe",
|
"rev": "9bb1e7571aadf31ddb4af77fc64b2d59580f9a39",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"sdk": {
|
"sdk": {
|
||||||
"version": "8.0.100",
|
"version": "8.0.100",
|
||||||
"rollForward": "latestFeature"
|
"rollForward": "latestMajor"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
20
nix/deps.nix
20
nix/deps.nix
@@ -8,8 +8,8 @@
|
|||||||
})
|
})
|
||||||
(fetchNuGet {
|
(fetchNuGet {
|
||||||
pname = "fantomas";
|
pname = "fantomas";
|
||||||
version = "6.3.11";
|
version = "6.3.12";
|
||||||
hash = "sha256-11bHGEAZTNtdp2pTg5zqLrQiyI/j/AT7GGL/2CR4+dw=";
|
hash = "sha256-LFZn2cO72FlsmLI0vTLz52Bn4XBeGILTOr8rz/EuXeg=";
|
||||||
})
|
})
|
||||||
(fetchNuGet {
|
(fetchNuGet {
|
||||||
pname = "Fantomas.Core";
|
pname = "Fantomas.Core";
|
||||||
@@ -78,13 +78,13 @@
|
|||||||
})
|
})
|
||||||
(fetchNuGet {
|
(fetchNuGet {
|
||||||
pname = "Microsoft.CodeCoverage";
|
pname = "Microsoft.CodeCoverage";
|
||||||
version = "17.11.0";
|
version = "17.11.1";
|
||||||
hash = "sha256-XglInnx5GePUYHG7n2NLX+WfK7kJnornsWOW/5FnOXE=";
|
hash = "sha256-1dLlK3NGh88PuFYZiYpT+izA96etxhU3BSgixDgdtGA=";
|
||||||
})
|
})
|
||||||
(fetchNuGet {
|
(fetchNuGet {
|
||||||
pname = "Microsoft.NET.Test.Sdk";
|
pname = "Microsoft.NET.Test.Sdk";
|
||||||
version = "17.11.0";
|
version = "17.11.1";
|
||||||
hash = "sha256-WjyA78+PG9ZloWTt9Hf1ek3VVj2FfJ9fAjqklnN+fWw=";
|
hash = "sha256-0JUEucQ2lzaPgkrjm/NFLBTbqU1dfhvhN3Tl3moE6mI=";
|
||||||
})
|
})
|
||||||
(fetchNuGet {
|
(fetchNuGet {
|
||||||
pname = "Microsoft.NETCore.App.Host.linux-arm64";
|
pname = "Microsoft.NETCore.App.Host.linux-arm64";
|
||||||
@@ -153,13 +153,13 @@
|
|||||||
})
|
})
|
||||||
(fetchNuGet {
|
(fetchNuGet {
|
||||||
pname = "Microsoft.TestPlatform.ObjectModel";
|
pname = "Microsoft.TestPlatform.ObjectModel";
|
||||||
version = "17.11.0";
|
version = "17.11.1";
|
||||||
hash = "sha256-mCI3MCV6nyrGLrBat5VvK5LrXTEKlsdp9NkpZyJYwVg=";
|
hash = "sha256-5vX+vCzFY3S7xfMVIv8OlMMFtdedW9UIJzc0WEc+vm4=";
|
||||||
})
|
})
|
||||||
(fetchNuGet {
|
(fetchNuGet {
|
||||||
pname = "Microsoft.TestPlatform.TestHost";
|
pname = "Microsoft.TestPlatform.TestHost";
|
||||||
version = "17.11.0";
|
version = "17.11.1";
|
||||||
hash = "sha256-gViDLobza22kuLvB4JdlGtbANqwBHRwf1wLmIHMw9Eo=";
|
hash = "sha256-wSkY0H1fQAq0H3LcKT4u7Y5RzhAAPa6yueVN84g8HxU=";
|
||||||
})
|
})
|
||||||
(fetchNuGet {
|
(fetchNuGet {
|
||||||
pname = "Myriad.Core";
|
pname = "Myriad.Core";
|
||||||
|
Reference in New Issue
Block a user