Compare commits

...

148 Commits

Author SHA1 Message Date
Patrick Stevens
0fe97da788 Have arg parser take string -> string option (#368) 2025-04-21 09:07:58 +00:00
Patrick Stevens
f944953384 Enforce non-nullability in more cases during JSON parse (#367) 2025-04-20 17:20:22 +00:00
Patrick Stevens
7930039ad1 Revert "Some fixes to nullability (#365)" (#366)
This reverts commit 8d275f0047.
2025-04-20 17:02:40 +00:00
Patrick Stevens
8d275f0047 Some fixes to nullability (#365) 2025-04-20 16:26:45 +00:00
patrick-conscriptus[bot]
682b12fdb2 Automated commit (#363)
Co-authored-by: patrick-conscriptus[bot] <175414948+patrick-conscriptus[bot]@users.noreply.github.com>
2025-04-20 01:31:43 +00:00
dependabot[bot]
325f8634a4 Bump FSharp.Core and WoofWare.Whippet.Fantomas (#361)
* Bump FsCheck and FSharp.Core

Bumps [FsCheck](https://github.com/Fscheck/fscheck) and [FSharp.Core](https://github.com/dotnet/fsharp). These dependencies needed to be updated together.

Updates `FsCheck` from 3.1.0 to 3.2.0
- [Release notes](https://github.com/Fscheck/fscheck/releases)
- [Changelog](https://github.com/fscheck/FsCheck/blob/master/FsCheck%20Release%20Notes.md)
- [Commits](https://github.com/Fscheck/fscheck/compare/3.1.0...3.2.0)

Updates `FSharp.Core` from 4.3.4 to 5.0.2
- [Release notes](https://github.com/dotnet/fsharp/releases)
- [Changelog](https://github.com/dotnet/fsharp/blob/main/release-notes.md)
- [Commits](https://github.com/dotnet/fsharp/commits)

---
updated-dependencies:
- dependency-name: FsCheck
  dependency-version: 3.2.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
- dependency-name: FSharp.Core
  dependency-version: 5.0.2
  dependency-type: direct:production
  update-type: version-update:semver-major
...

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

* Bump FSharp.Core and WoofWare.Whippet.Fantomas

Bumps [FSharp.Core](https://github.com/dotnet/fsharp) and [WoofWare.Whippet.Fantomas](https://github.com/Smaug123/WoofWare.Whippet). These dependencies needed to be updated together.

Updates `FSharp.Core` from 4.3.4 to 6.0.1
- [Release notes](https://github.com/dotnet/fsharp/releases)
- [Changelog](https://github.com/dotnet/fsharp/blob/main/release-notes.md)
- [Commits](https://github.com/dotnet/fsharp/commits)

Updates `WoofWare.Whippet.Fantomas` from 0.3.2 to 0.5.1
- [Commits](https://github.com/Smaug123/WoofWare.Whippet/commits)

---
updated-dependencies:
- dependency-name: FSharp.Core
  dependency-version: 6.0.1
  dependency-type: direct:production
  update-type: version-update:semver-major
- dependency-name: WoofWare.Whippet.Fantomas
  dependency-version: 0.5.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

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

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Smaug123 <3138005+Smaug123@users.noreply.github.com>
2025-04-14 22:05:23 +00:00
Patrick Stevens
3e5d663544 Better docs on PositionalArgsAttribute (#362) 2025-04-14 23:01:26 +01:00
patrick-conscriptus[bot]
bb88f80c85 Automated commit (#358)
Co-authored-by: patrick-conscriptus[bot] <175414948+patrick-conscriptus[bot]@users.noreply.github.com>
2025-04-13 02:53:09 +00:00
dependabot[bot]
71f26930c6 Bump actions/create-github-app-token from 1 to 2 (#357) 2025-04-07 17:19:12 +01:00
patrick-conscriptus[bot]
680728a06e Automated commit (#356)
Co-authored-by: patrick-conscriptus[bot] <175414948+patrick-conscriptus[bot]@users.noreply.github.com>
2025-04-06 01:29:18 +00:00
dependabot[bot]
cdc6f2d511 Bump fsharp-analyzers from 0.29.1 to 0.30.0 (#355)
* Bump fsharp-analyzers from 0.29.1 to 0.30.0

Bumps [fsharp-analyzers](https://github.com/ionide/FSharp.Analyzers.SDK) from 0.29.1 to 0.30.0.
- [Release notes](https://github.com/ionide/FSharp.Analyzers.SDK/releases)
- [Changelog](https://github.com/ionide/FSharp.Analyzers.SDK/blob/main/CHANGELOG.md)
- [Commits](https://github.com/ionide/FSharp.Analyzers.SDK/compare/v0.29.1...v0.30.0)

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

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

* Deps

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Smaug123 <3138005+Smaug123@users.noreply.github.com>
2025-04-01 08:23:43 +00:00
patrick-conscriptus[bot]
3be487c328 Automated commit (#354)
Co-authored-by: patrick-conscriptus[bot] <175414948+patrick-conscriptus[bot]@users.noreply.github.com>
2025-03-30 01:30:44 +00:00
dependabot[bot]
a5f4d169ca Bump FSharp.Core and WoofWare.Whippet.Fantomas (#352)
* Bump FSharp.Core and WoofWare.Whippet.Fantomas

Bumps [FSharp.Core](https://github.com/dotnet/fsharp) and [WoofWare.Whippet.Fantomas](https://github.com/Smaug123/WoofWare.Whippet). These dependencies needed to be updated together.

Updates `FSharp.Core` from 4.3.4 to 6.0.1
- [Release notes](https://github.com/dotnet/fsharp/releases)
- [Changelog](https://github.com/dotnet/fsharp/blob/main/release-notes.md)
- [Commits](https://github.com/dotnet/fsharp/commits)

Updates `WoofWare.Whippet.Fantomas` from 0.3.1 to 0.3.2
- [Commits](https://github.com/Smaug123/WoofWare.Whippet/commits)

---
updated-dependencies:
- dependency-name: FSharp.Core
  dependency-type: direct:production
  update-type: version-update:semver-major
- dependency-name: WoofWare.Whippet.Fantomas
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

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

* Bump ApiSurface and FSharp.Core

Bumps [ApiSurface](https://github.com/G-Research/ApiSurface) and [FSharp.Core](https://github.com/Microsoft/visualfsharp). These dependencies needed to be updated together.

Updates `ApiSurface` from 4.1.17 to 4.1.20
- [Release notes](https://github.com/G-Research/ApiSurface/releases)
- [Commits](https://github.com/G-Research/ApiSurface/compare/ApiSurface.4.1.17...ApiSurface.4.1.20)

Updates `FSharp.Core` from 4.3.4 to 4.3.4
- [Release notes](https://github.com/Microsoft/visualfsharp/releases)
- [Changelog](https://github.com/dotnet/fsharp/blob/main/release-notes.md)
- [Commits](https://github.com/Microsoft/visualfsharp/commits)

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

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

* Deps

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Smaug123 <3138005+Smaug123@users.noreply.github.com>
2025-03-24 19:38:01 +00:00
patrick-conscriptus[bot]
ce634efff2 Automated commit (#351)
Co-authored-by: patrick-conscriptus[bot] <175414948+patrick-conscriptus[bot]@users.noreply.github.com>
2025-03-23 01:28:44 +00:00
Patrick Stevens
1529dd1fb2 Bump NBGV (#350) 2025-03-21 14:33:32 +00:00
dependabot[bot]
59558b0766 Bump cachix/install-nix-action from 30 to 31 (#349)
Bumps [cachix/install-nix-action](https://github.com/cachix/install-nix-action) from 30 to 31.
- [Release notes](https://github.com/cachix/install-nix-action/releases)
- [Commits](https://github.com/cachix/install-nix-action/compare/v30...v31)

---
updated-dependencies:
- dependency-name: cachix/install-nix-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-17 19:56:20 +00:00
dependabot[bot]
8602894efc Bump fsharp-analyzers from 0.29.0 to 0.29.1 (#348)
* Bump fsharp-analyzers from 0.29.0 to 0.29.1

Bumps [fsharp-analyzers](https://github.com/ionide/FSharp.Analyzers.SDK) from 0.29.0 to 0.29.1.
- [Release notes](https://github.com/ionide/FSharp.Analyzers.SDK/releases)
- [Changelog](https://github.com/ionide/FSharp.Analyzers.SDK/blob/main/CHANGELOG.md)
- [Commits](https://github.com/ionide/FSharp.Analyzers.SDK/compare/v0.29.0...v0.29.1)

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

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

* Deps

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Smaug123 <3138005+Smaug123@users.noreply.github.com>
2025-03-10 18:41:51 +00:00
dependabot[bot]
51d349b365 Bump actions/attest-build-provenance from 2.2.2 to 2.2.3 (#347) 2025-03-10 12:13:39 +00:00
dependabot[bot]
120df84bbf Bump actions/attest-build-provenance from 2.2.0 to 2.2.2 (#345) 2025-03-03 11:24:42 +00:00
dependabot[bot]
603f875a12 Bump ApiSurface and FSharp.Core (#341)
* Automated commit

* Bump Microsoft.NET.Test.Sdk from 17.12.0 to 17.13.0

Bumps [Microsoft.NET.Test.Sdk](https://github.com/microsoft/vstest) from 17.12.0 to 17.13.0.
- [Release notes](https://github.com/microsoft/vstest/releases)
- [Changelog](https://github.com/microsoft/vstest/blob/main/docs/releases.md)
- [Commits](https://github.com/microsoft/vstest/compare/v17.12.0...v17.13.0)

---
updated-dependencies:
- dependency-name: Microsoft.NET.Test.Sdk
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

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

* Bump ApiSurface and FSharp.Core

Bumps [ApiSurface](https://github.com/G-Research/ApiSurface) and [FSharp.Core](https://github.com/Microsoft/visualfsharp). These dependencies needed to be updated together.

Updates `ApiSurface` from 4.1.16 to 4.1.17
- [Release notes](https://github.com/G-Research/ApiSurface/releases)
- [Commits](https://github.com/G-Research/ApiSurface/compare/ApiSurface.4.1.16...ApiSurface.4.1.17)

Updates `FSharp.Core` from 4.3.4 to 4.3.4
- [Release notes](https://github.com/Microsoft/visualfsharp/releases)
- [Changelog](https://github.com/dotnet/fsharp/blob/main/release-notes.md)
- [Commits](https://github.com/Microsoft/visualfsharp/commits)

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

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

* Bump fantomas from 7.0.0 to 7.0.1

Bumps [fantomas](https://github.com/fsprojects/fantomas) from 7.0.0 to 7.0.1.
- [Release notes](https://github.com/fsprojects/fantomas/releases)
- [Changelog](https://github.com/fsprojects/fantomas/blob/main/CHANGELOG.md)
- [Commits](https://github.com/fsprojects/fantomas/compare/v7.0.0...v7.0.1)

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

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

* Bump NUnit3TestAdapter from 4.6.0 to 5.0.0

Bumps [NUnit3TestAdapter](https://github.com/nunit/nunit3-vs-adapter) from 4.6.0 to 5.0.0.
- [Release notes](https://github.com/nunit/nunit3-vs-adapter/releases)
- [Commits](https://github.com/nunit/nunit3-vs-adapter/compare/V4.6.0...V5.0.0)

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

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

* Deps

* Suppress build warning

* Revert "Merge remote-tracking branch 'origin/auto_pr2025_02_23-01_20_56_533960' into dependabot/nuget/multi-be38daf731"

This reverts commit d2bb029b2b, reversing
changes made to 7a2fe4a014.

* Deps

* Revert

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: patrick-conscriptus[bot] <175414948+patrick-conscriptus[bot]@users.noreply.github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Smaug123 <3138005+Smaug123@users.noreply.github.com>
2025-02-25 00:28:26 +00:00
dependabot[bot]
2df41555de Bump fsharp-analyzers from 0.28.0 to 0.29.0, and FsUnit (#338)
* Bump fsharp-analyzers from 0.28.0 to 0.29.0

Bumps [fsharp-analyzers](https://github.com/ionide/FSharp.Analyzers.SDK) from 0.28.0 to 0.29.0.
- [Release notes](https://github.com/ionide/FSharp.Analyzers.SDK/releases)
- [Changelog](https://github.com/ionide/FSharp.Analyzers.SDK/blob/main/CHANGELOG.md)
- [Commits](https://github.com/ionide/FSharp.Analyzers.SDK/compare/v0.28.0...v0.29.0)

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

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

* Bump analysers

* Deps

* Upgrade

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Smaug123 <3138005+Smaug123@users.noreply.github.com>
2025-02-20 10:44:45 +00:00
patrick-conscriptus[bot]
49e31e52b5 Automated commit (#337)
Co-authored-by: patrick-conscriptus[bot] <175414948+patrick-conscriptus[bot]@users.noreply.github.com>
2025-02-16 01:25:03 +00:00
Patrick Stevens
277a186fda Allow properties in mocked interfaces (#336) 2025-02-12 23:53:15 +00:00
patrick-conscriptus[bot]
129687ec1c Automated commit (#334)
Co-authored-by: patrick-conscriptus[bot] <175414948+patrick-conscriptus[bot]@users.noreply.github.com>
2025-02-09 01:22:26 +00:00
dependabot[bot]
c7fea55e28 Bump FsCheck from 3.0.1 to 3.1.0 (#333)
* Bump FsCheck from 3.0.1 to 3.1.0

Bumps [FsCheck](https://github.com/Fscheck/fscheck) from 3.0.1 to 3.1.0.
- [Release notes](https://github.com/Fscheck/fscheck/releases)
- [Changelog](https://github.com/fscheck/FsCheck/blob/master/FsCheck%20Release%20Notes.md)
- [Commits](https://github.com/Fscheck/fscheck/compare/3.0.1...3.1.0)

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

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

* Deps

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Smaug123 <3138005+Smaug123@users.noreply.github.com>
2025-02-03 17:28:07 +00:00
patrick-conscriptus[bot]
ded7b32771 Automated commit (#332)
Co-authored-by: patrick-conscriptus[bot] <175414948+patrick-conscriptus[bot]@users.noreply.github.com>
2025-02-02 01:21:17 +00:00
dependabot[bot]
b272f8b645 Bump FsCheck from 3.0.0 to 3.0.1 (#330)
* Bump FsCheck from 3.0.0 to 3.0.1

Bumps [FsCheck](https://github.com/Fscheck/fscheck) from 3.0.0 to 3.0.1.
- [Release notes](https://github.com/Fscheck/fscheck/releases)
- [Changelog](https://github.com/fscheck/FsCheck/blob/master/FsCheck%20Release%20Notes.md)
- [Commits](https://github.com/Fscheck/fscheck/compare/3.0.0...3.0.1)

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

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

* Deps

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Smaug123 <3138005+Smaug123@users.noreply.github.com>
2025-01-29 00:49:28 +00:00
dependabot[bot]
b8d60aec90 Bump actions/attest-build-provenance from 2.1.0 to 2.2.0 (#331) 2025-01-27 11:36:25 +00:00
patrick-conscriptus[bot]
0e3510e1e5 Automated commit (#329)
Co-authored-by: patrick-conscriptus[bot] <175414948+patrick-conscriptus[bot]@users.noreply.github.com>
2025-01-26 01:19:46 +00:00
patrick-conscriptus[bot]
8a1edd90d5 Automated commit (#328)
Co-authored-by: patrick-conscriptus[bot] <175414948+patrick-conscriptus[bot]@users.noreply.github.com>
2025-01-19 01:22:30 +00:00
dependabot[bot]
74fdd7c0a9 Bump fantomas from 6.3.16 to 7.0.0 (#326)
* Bump fantomas from 6.3.16 to 7.0.0

Bumps [fantomas](https://github.com/fsprojects/fantomas) from 6.3.16 to 7.0.0.
- [Release notes](https://github.com/fsprojects/fantomas/releases)
- [Changelog](https://github.com/fsprojects/fantomas/blob/main/CHANGELOG.md)
- [Commits](https://github.com/fsprojects/fantomas/compare/v6.3.16...v7.0.0)

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

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

* Deps

* Format

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Smaug123 <3138005+Smaug123@users.noreply.github.com>
2025-01-14 10:35:58 +00:00
dependabot[bot]
23f55814f9 Bump ApiSurface from 4.1.15 to 4.1.16 (#325)
* Bump FsCheck from 2.16.6 to 3.0.0

Bumps [FsCheck](https://github.com/Fscheck/fscheck) from 2.16.6 to 3.0.0.
- [Release notes](https://github.com/Fscheck/fscheck/releases)
- [Changelog](https://github.com/fscheck/FsCheck/blob/master/FsCheck%20Release%20Notes.md)
- [Commits](https://github.com/Fscheck/fscheck/compare/2.16.6...3.0.0)

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

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

* Bump ApiSurface from 4.1.15 to 4.1.16

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

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

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

* Bump Nerdbank.GitVersioning from 3.7.112 to 3.7.115

Bumps [Nerdbank.GitVersioning](https://github.com/dotnet/Nerdbank.GitVersioning) from 3.7.112 to 3.7.115.
- [Release notes](https://github.com/dotnet/Nerdbank.GitVersioning/releases)
- [Commits](https://github.com/dotnet/Nerdbank.GitVersioning/commits)

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

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

* Lockfile

* Fix tests

* Fix

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Smaug123 <3138005+Smaug123@users.noreply.github.com>
2025-01-14 01:16:20 +00:00
Patrick Stevens
15c04bb373 Use GR's GitHub release action (#323) 2025-01-13 09:34:27 +00:00
patrick-conscriptus[bot]
a860a93f9c Automated commit (#322)
Co-authored-by: patrick-conscriptus[bot] <175414948+patrick-conscriptus[bot]@users.noreply.github.com>
2025-01-12 01:25:56 +00:00
patrick-conscriptus[bot]
b056af348e Automated commit (#321)
Co-authored-by: patrick-conscriptus[bot] <175414948+patrick-conscriptus[bot]@users.noreply.github.com>
2025-01-05 01:24:38 +00:00
dependabot[bot]
b44c8db6e9 Bump NUnit from 4.3.1 to 4.3.2 (#320)
* Bump NUnit from 4.3.1 to 4.3.2

Bumps [NUnit](https://github.com/nunit/nunit) from 4.3.1 to 4.3.2.
- [Release notes](https://github.com/nunit/nunit/releases)
- [Changelog](https://github.com/nunit/nunit/blob/main/CHANGES.md)
- [Commits](https://github.com/nunit/nunit/compare/4.3.1...4.3.2)

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

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

* Fix

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Smaug123 <3138005+Smaug123@users.noreply.github.com>
2024-12-30 22:43:17 +00:00
patrick-conscriptus[bot]
7d6a2cea01 Automated commit (#319)
Co-authored-by: patrick-conscriptus[bot] <175414948+patrick-conscriptus[bot]@users.noreply.github.com>
2024-12-29 01:25:23 +00:00
Patrick Stevens
d779a602f4 Fix flake update after recent nixpkgs bump (#318) 2024-12-24 19:49:30 +00:00
Patrick Stevens
23cd5272fb Use non-deprecated nixpkgs methods (#317) 2024-12-24 19:32:39 +00:00
dependabot[bot]
93538ee6b4 Bump NUnit from 4.3.0 to 4.3.1 (#315)
* Bump NUnit from 4.3.0 to 4.3.1

Bumps [NUnit](https://github.com/nunit/nunit) from 4.3.0 to 4.3.1.
- [Release notes](https://github.com/nunit/nunit/releases)
- [Changelog](https://github.com/nunit/nunit/blob/main/CHANGES.md)
- [Commits](https://github.com/nunit/nunit/compare/4.3.0...4.3.1)

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

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

* Bump ApiSurface from 4.1.14 to 4.1.15

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

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

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

* Deps

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Smaug123 <3138005+Smaug123@users.noreply.github.com>
2024-12-23 11:45:33 +00:00
patrick-conscriptus[bot]
8a29c2f444 Automated commit (#314)
Co-authored-by: patrick-conscriptus[bot] <175414948+patrick-conscriptus[bot]@users.noreply.github.com>
2024-12-22 01:24:49 +00:00
dependabot[bot]
1367e00f34 Bump Nerdbank.GitVersioning from 3.6.146 to 3.7.112 (#311)
* Bump NUnit from 4.2.2 to 4.3.0

Bumps [NUnit](https://github.com/nunit/nunit) from 4.2.2 to 4.3.0.
- [Release notes](https://github.com/nunit/nunit/releases)
- [Changelog](https://github.com/nunit/nunit/blob/main/CHANGES.md)
- [Commits](https://github.com/nunit/nunit/compare/4.2.2...4.3.0)

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

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

* Bump Nerdbank.GitVersioning from 3.6.146 to 3.7.112

Bumps [Nerdbank.GitVersioning](https://github.com/dotnet/Nerdbank.GitVersioning) from 3.6.146 to 3.7.112.
- [Release notes](https://github.com/dotnet/Nerdbank.GitVersioning/releases)
- [Commits](https://github.com/dotnet/Nerdbank.GitVersioning/compare/v3.6.146...v3.7.112)

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

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

* Bump ApiSurface from 4.1.12 to 4.1.14

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

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

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

* deps

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Smaug123 <3138005+Smaug123@users.noreply.github.com>
2024-12-16 11:21:31 +00:00
dependabot[bot]
bff08c90cd Bump actions/attest-build-provenance from 2.0.1 to 2.1.0 (#313)
Bumps [actions/attest-build-provenance](https://github.com/actions/attest-build-provenance) from 2.0.1 to 2.1.0.
- [Release notes](https://github.com/actions/attest-build-provenance/releases)
- [Changelog](https://github.com/actions/attest-build-provenance/blob/main/RELEASE.md)
- [Commits](c4fbc64884...7668571508)

---
updated-dependencies:
- dependency-name: actions/attest-build-provenance
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-16 10:55:25 +00:00
Patrick Stevens
4136f7fe94 Update to net9 SDK (#302) 2024-12-15 22:13:30 +00:00
patrick-conscriptus[bot]
9fe2e3b1fa Automated commit (#309)
Co-authored-by: patrick-conscriptus[bot] <175414948+patrick-conscriptus[bot]@users.noreply.github.com>
2024-12-15 01:31:17 +00:00
dependabot[bot]
13a597a365 Bump actions/attest-build-provenance from 1.4.4 to 2.0.1 (#308) 2024-12-09 13:45:39 +00:00
patrick-conscriptus[bot]
3acc492f22 Automated commit (#307)
Co-authored-by: patrick-conscriptus[bot] <175414948+patrick-conscriptus[bot]@users.noreply.github.com>
2024-12-08 01:32:11 +00:00
patrick-conscriptus[bot]
eefe64f5a4 Automated commit (#306)
Co-authored-by: patrick-conscriptus[bot] <175414948+patrick-conscriptus[bot]@users.noreply.github.com>
2024-12-01 01:44:12 +00:00
dependabot[bot]
93a2d92299 Bump fsharp-analyzers from 0.27.0 to 0.28.0 (#305)
* Bump ApiSurface from 4.1.11 to 4.1.12

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

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

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

* Bump Microsoft.NET.Test.Sdk from 17.11.1 to 17.12.0

Bumps [Microsoft.NET.Test.Sdk](https://github.com/microsoft/vstest) from 17.11.1 to 17.12.0.
- [Release notes](https://github.com/microsoft/vstest/releases)
- [Changelog](https://github.com/microsoft/vstest/blob/main/docs/releases.md)
- [Commits](https://github.com/microsoft/vstest/compare/v17.11.1...v17.12.0)

---
updated-dependencies:
- dependency-name: Microsoft.NET.Test.Sdk
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

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

* Bump fsharp-analyzers from 0.27.0 to 0.28.0

Bumps [fsharp-analyzers](https://github.com/ionide/FSharp.Analyzers.SDK) from 0.27.0 to 0.28.0.
- [Release notes](https://github.com/ionide/FSharp.Analyzers.SDK/releases)
- [Changelog](https://github.com/ionide/FSharp.Analyzers.SDK/blob/main/CHANGELOG.md)
- [Commits](https://github.com/ionide/FSharp.Analyzers.SDK/compare/v0.27.0...v0.28.0)

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

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

* Deps

* Fix flake

* Fix analysers

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Smaug123 <3138005+Smaug123@users.noreply.github.com>
2024-11-25 16:38:14 +00:00
patrick-conscriptus[bot]
33793e8cbe Automated commit (#301)
Co-authored-by: patrick-conscriptus[bot] <175414948+patrick-conscriptus[bot]@users.noreply.github.com>
2024-11-24 01:28:15 +00:00
dependabot[bot]
fa7ef1ffba Bump ApiSurface from 4.1.8 to 4.1.11 (#300)
* Bump ApiSurface from 4.1.8 to 4.1.11

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

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

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

* Deps

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Smaug123 <3138005+Smaug123@users.noreply.github.com>
2024-11-18 20:07:55 +00:00
patrick-conscriptus[bot]
a25c45dc3a Automated commit (#299)
Co-authored-by: patrick-conscriptus[bot] <175414948+patrick-conscriptus[bot]@users.noreply.github.com>
2024-11-17 01:26:53 +00:00
dependabot[bot]
af5f2abdf8 Bump actions/attest-build-provenance from 1.4.3 to 1.4.4 (#298)
Bumps [actions/attest-build-provenance](https://github.com/actions/attest-build-provenance) from 1.4.3 to 1.4.4.
- [Release notes](https://github.com/actions/attest-build-provenance/releases)
- [Changelog](https://github.com/actions/attest-build-provenance/blob/main/RELEASE.md)
- [Commits](1c608d11d6...ef244123eb)

---
updated-dependencies:
- dependency-name: actions/attest-build-provenance
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-11 11:07:51 +00:00
patrick-conscriptus[bot]
0f89816432 Automated commit (#297)
Co-authored-by: patrick-conscriptus[bot] <175414948+patrick-conscriptus[bot]@users.noreply.github.com>
2024-11-10 01:22:22 +00:00
dependabot[bot]
d571da6a22 Bump fantomas from 6.3.15 to 6.3.16 (#296)
* Bump fantomas from 6.3.15 to 6.3.16

Bumps [fantomas](https://github.com/fsprojects/fantomas) from 6.3.15 to 6.3.16.
- [Release notes](https://github.com/fsprojects/fantomas/releases)
- [Changelog](https://github.com/fsprojects/fantomas/blob/main/CHANGELOG.md)
- [Commits](https://github.com/fsprojects/fantomas/compare/v6.3.15...v6.3.16)

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

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

* Deps

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Smaug123 <3138005+Smaug123@users.noreply.github.com>
2024-11-04 17:42:25 +00:00
patrick-conscriptus[bot]
39a9e94ca5 Automated commit (#295)
Co-authored-by: patrick-conscriptus[bot] <175414948+patrick-conscriptus[bot]@users.noreply.github.com>
2024-11-03 01:24:04 +00:00
patrick-conscriptus[bot]
487f73312a Automated commit (#294)
Co-authored-by: patrick-conscriptus[bot] <175414948+patrick-conscriptus[bot]@users.noreply.github.com>
2024-10-27 01:24:19 +00:00
dependabot[bot]
b7aa564306 Bump Nerdbank.GitVersioning from 3.6.143 to 3.6.146 (#292)
* Bump Nerdbank.GitVersioning from 3.6.143 to 3.6.146

Bumps [Nerdbank.GitVersioning](https://github.com/dotnet/Nerdbank.GitVersioning) from 3.6.143 to 3.6.146.
- [Release notes](https://github.com/dotnet/Nerdbank.GitVersioning/releases)
- [Commits](https://github.com/dotnet/Nerdbank.GitVersioning/commits)

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

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

* Bump ApiSurface from 4.1.7 to 4.1.8

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

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

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

* Deps

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Smaug123 <3138005+Smaug123@users.noreply.github.com>
2024-10-21 19:29:38 +01:00
patrick-conscriptus[bot]
a34dee0a1c Automated commit (#291)
Co-authored-by: patrick-conscriptus[bot] <175414948+patrick-conscriptus[bot]@users.noreply.github.com>
2024-10-20 01:24:50 +00:00
dependabot[bot]
44506f3650 Bump WoofWare.Whippet.Fantomas and FSharp.Core (#289)
* Bump WoofWare.Whippet.Fantomas and FSharp.Core

Bumps [WoofWare.Whippet.Fantomas](https://github.com/Smaug123/WoofWare.Whippet) and [FSharp.Core](https://github.com/dotnet/fsharp). These dependencies needed to be updated together.

Updates `WoofWare.Whippet.Fantomas` from 0.2.1 to 0.3.1
- [Commits](https://github.com/Smaug123/WoofWare.Whippet/commits)

Updates `FSharp.Core` from 4.3.4 to 6.0.1
- [Release notes](https://github.com/dotnet/fsharp/releases)
- [Changelog](https://github.com/dotnet/fsharp/blob/main/release-notes.md)
- [Commits](https://github.com/dotnet/fsharp/commits)

---
updated-dependencies:
- dependency-name: WoofWare.Whippet.Fantomas
  dependency-type: direct:production
  update-type: version-update:semver-minor
- dependency-name: FSharp.Core
  dependency-type: direct:production
  update-type: version-update:semver-major
...

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

* Deps

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Smaug123 <3138005+Smaug123@users.noreply.github.com>
2024-10-14 11:39:41 +00:00
dependabot[bot]
ca7134cfb8 Bump ApiSurface from 4.1.6 to 4.1.7 (#290)
* Bump ApiSurface from 4.1.6 to 4.1.7

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

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

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

* Fix deps

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Smaug123 <3138005+Smaug123@users.noreply.github.com>
2024-10-14 11:30:54 +00:00
patrick-conscriptus[bot]
773fd088a7 Automated commit (#288)
Co-authored-by: patrick-conscriptus[bot] <175414948+patrick-conscriptus[bot]@users.noreply.github.com>
2024-10-13 01:23:21 +00:00
dependabot[bot]
6e5c0332cd Bump ApiSurface from 4.1.5 to 4.1.6 (#286)
* Bump ApiSurface from 4.1.5 to 4.1.6

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

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

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

* deps

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Smaug123 <3138005+Smaug123@users.noreply.github.com>
2024-10-07 21:12:40 +01:00
Patrick Stevens
aece186424 Fix NuGet packaging (#287) 2024-10-07 20:38:49 +01:00
dependabot[bot]
827e9aa3ec Bump cachix/install-nix-action from 29 to 30 (#285)
Bumps [cachix/install-nix-action](https://github.com/cachix/install-nix-action) from 29 to 30.
- [Release notes](https://github.com/cachix/install-nix-action/releases)
- [Commits](https://github.com/cachix/install-nix-action/compare/v29...v30)

---
updated-dependencies:
- dependency-name: cachix/install-nix-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-07 12:30:06 +01:00
Patrick Stevens
d59ebdfccb Use more of the Whippet Fantomas client lib (#284) 2024-10-06 21:22:24 +00:00
Patrick Stevens
5319a33b7b Move a useful function (#283) 2024-10-06 21:15:41 +00:00
Patrick Stevens
29b93b2f20 Pull in AST-manipulating function from Myriad (#281) 2024-10-06 21:08:32 +00:00
Patrick Stevens
40106d8aa3 Use WoofWare.Whippet.Fantomas rather than Myriad.Core (#280) 2024-10-06 20:22:06 +00:00
patrick-conscriptus[bot]
4d641b0662 Automated commit (#279)
Co-authored-by: patrick-conscriptus[bot] <175414948+patrick-conscriptus[bot]@users.noreply.github.com>
2024-10-06 01:23:42 +00:00
Patrick Stevens
16e6b91548 Use EscapeDataString instead of UrlEncode (#278) 2024-10-03 15:10:39 +00:00
Patrick Stevens
8488883835 Remove more of Myriad.Core (#276) 2024-10-02 20:38:00 +00:00
Patrick Stevens
0652744c57 Allow using fsproj annotations instead of attributes (#275) 2024-10-02 19:30:21 +00:00
dependabot[bot]
9252979673 Bump cachix/install-nix-action from V28 to 29 (#273) 2024-09-30 13:53:25 +01:00
patrick-conscriptus[bot]
1120a3752d Automated commit (#272)
Co-authored-by: patrick-conscriptus[bot] <175414948+patrick-conscriptus[bot]@users.noreply.github.com>
2024-09-29 01:26:14 +00:00
patrick-conscriptus[bot]
7ca6b0c0eb Automated commit (#271)
Co-authored-by: patrick-conscriptus[bot] <175414948+patrick-conscriptus[bot]@users.noreply.github.com>
2024-09-22 01:25:31 +00:00
Patrick Stevens
50efb8d9bc Bump flake (#270) 2024-09-20 19:44:24 +01:00
Patrick Stevens
93a1b630c8 Add HTTP Swagger client generator (#250) 2024-09-19 17:21:09 +01:00
Patrick Stevens
4482038e8a Support individual per-method headers (#268) 2024-09-19 17:08:38 +01:00
dependabot[bot]
a41fa9dd37 Bump FsUnit from 6.0.0 to 6.0.1 (#265)
* Bump fantomas from 6.3.12 to 6.3.15

Bumps [fantomas](https://github.com/fsprojects/fantomas) from 6.3.12 to 6.3.15.
- [Release notes](https://github.com/fsprojects/fantomas/releases)
- [Changelog](https://github.com/fsprojects/fantomas/blob/main/CHANGELOG.md)
- [Commits](https://github.com/fsprojects/fantomas/compare/v6.3.12...v6.3.15)

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

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

* Bump FsUnit from 6.0.0 to 6.0.1

Bumps [FsUnit](https://github.com/fsprojects/FsUnit) from 6.0.0 to 6.0.1.
- [Release notes](https://github.com/fsprojects/FsUnit/releases)
- [Changelog](https://github.com/fsprojects/FsUnit/blob/master/RELEASE_NOTES.md)
- [Commits](https://github.com/fsprojects/FsUnit/compare/6.0.0...6.0.1)

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

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

* Deps

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Smaug123 <3138005+Smaug123@users.noreply.github.com>
2024-09-16 13:56:13 +00:00
dependabot[bot]
fc5acc2f58 Bump cachix/install-nix-action from V27 to 28 (#266) 2024-09-16 12:12:30 +01:00
Patrick Stevens
0a1783d6ed Support [<BasePath>] (#263) 2024-09-15 17:38:03 +00:00
Patrick Stevens
9a3ebbf28f Cope with unit type in JSON (#262) 2024-09-15 14:37:50 +00:00
Patrick Stevens
e22525c200 Interpret JsonExtensionData (#261) 2024-09-15 11:13:22 +01:00
Patrick Stevens
09b7109c84 Extract some utilities from http-client branch (#260) 2024-09-14 22:02:32 +00:00
Patrick Stevens
693b95106a Also pipe through parser in PositionalArgs true (#259) 2024-09-13 16:11:53 +00:00
Patrick Stevens
49ecfbf5e5 Fix includeFlagLike when arg doesn't have an equals (#257) 2024-09-12 22:10:08 +00:00
Patrick Stevens
5748ac3d5b Allow consuming *all* args as positionals, not just ones which look like --foo (#255) 2024-09-11 19:00:04 +00:00
Patrick Stevens
913959a740 Make arg parser more AOT-friendly (#253) 2024-09-10 22:16:43 +01:00
dependabot[bot]
93ffc065cd Bump Microsoft.NET.Test.Sdk from 17.11.0 to 17.11.1 (#248)
* Bump Microsoft.NET.Test.Sdk from 17.11.0 to 17.11.1

Bumps [Microsoft.NET.Test.Sdk](https://github.com/microsoft/vstest) from 17.11.0 to 17.11.1.
- [Release notes](https://github.com/microsoft/vstest/releases)
- [Changelog](https://github.com/microsoft/vstest/blob/main/docs/releases.md)
- [Commits](https://github.com/microsoft/vstest/compare/v17.11.0...v17.11.1)

---
updated-dependencies:
- dependency-name: Microsoft.NET.Test.Sdk
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

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

* Bump fantomas from 6.3.11 to 6.3.12

Bumps [fantomas](https://github.com/fsprojects/fantomas) from 6.3.11 to 6.3.12.
- [Release notes](https://github.com/fsprojects/fantomas/releases)
- [Changelog](https://github.com/fsprojects/fantomas/blob/main/CHANGELOG.md)
- [Commits](https://github.com/fsprojects/fantomas/compare/v6.3.11...v6.3.12)

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

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

* Deps

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Smaug123 <3138005+Smaug123@users.noreply.github.com>
2024-09-09 19:33:39 +00:00
dependabot[bot]
d14efba7e7 Bump actions/attest-build-provenance from 1.4.2 to 1.4.3 (#247) 2024-09-09 12:42:12 +01:00
patrick-conscriptus[bot]
f5cf0b79dd Automated commit (#246)
Co-authored-by: patrick-conscriptus[bot] <175414948+patrick-conscriptus[bot]@users.noreply.github.com>
2024-09-08 01:20:34 +00:00
Patrick Stevens
029e3746bb Fix record/union impl accessibility (#245) 2024-09-07 08:49:28 +00:00
Patrick Stevens
8ae749c529 Add ArgumentLongForm (#244) 2024-09-05 21:26:52 +01:00
Patrick Stevens
e4cbab3209 Implement [<ArgumentFlag>] for two-case DUs (#242) 2024-09-04 21:48:36 +00:00
Patrick Stevens
bdce82fb7a Handle env vars with value 0 and 1 for bools (#241) 2024-09-04 21:06:22 +01:00
Patrick Stevens
8f9f933971 Handle URI in arg parser (#240) 2024-09-04 20:20:51 +01:00
Patrick Stevens
3a55ba1242 Allow positional args to be Choice<'a, 'a> to indicate whether they came before any positional marker (#238) 2024-09-04 20:09:40 +01:00
Patrick Stevens
047b2eda99 Throw on duplicated args (#237) 2024-09-04 08:39:19 +01:00
Patrick Stevens
2220f88053 Compose records in arg parser (#234) 2024-09-03 23:04:23 +00:00
dependabot[bot]
86b938c81e Bump Nerdbank.GitVersioning from 3.6.141 to 3.6.143 (#231) 2024-09-02 20:05:18 +01:00
patrick-conscriptus[bot]
1832a57bdf Automated commit (#230)
Co-authored-by: patrick-conscriptus[bot] <175414948+patrick-conscriptus[bot]@users.noreply.github.com>
2024-09-01 01:25:31 +00:00
Patrick Stevens
38f4821fa4 Switch to centrally managed actions (#229) 2024-08-28 16:51:36 +00:00
dependabot[bot]
70aaf8c408 Bump actions/attest-build-provenance from 1.4.1 to 1.4.2 (#221)
* Bump actions/attest-build-provenance from 1.4.1 to 1.4.2

Bumps [actions/attest-build-provenance](https://github.com/actions/attest-build-provenance) from 1.4.1 to 1.4.2.
- [Release notes](https://github.com/actions/attest-build-provenance/releases)
- [Changelog](https://github.com/actions/attest-build-provenance/blob/main/RELEASE.md)
- [Commits](310b0a4a3b...6149ea5740)

---
updated-dependencies:
- dependency-name: actions/attest-build-provenance
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

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

* Bump Microsoft.NET.Test.Sdk from 17.10.0 to 17.11.0

Bumps [Microsoft.NET.Test.Sdk](https://github.com/microsoft/vstest) from 17.10.0 to 17.11.0.
- [Release notes](https://github.com/microsoft/vstest/releases)
- [Changelog](https://github.com/microsoft/vstest/blob/main/docs/releases.md)
- [Commits](https://github.com/microsoft/vstest/compare/v17.10.0...v17.11.0)

---
updated-dependencies:
- dependency-name: Microsoft.NET.Test.Sdk
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

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

* Bump ApiSurface from 4.0.44 to 4.1.2

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

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

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

* Bump NUnit from 4.1.0 to 4.2.1

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

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

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

* Bump fsharp-analyzers from 0.26.1 to 0.27.0

Bumps [fsharp-analyzers](https://github.com/ionide/FSharp.Analyzers.SDK) from 0.26.1 to 0.27.0.
- [Release notes](https://github.com/ionide/FSharp.Analyzers.SDK/releases)
- [Changelog](https://github.com/ionide/FSharp.Analyzers.SDK/blob/main/CHANGELOG.md)
- [Commits](https://github.com/ionide/FSharp.Analyzers.SDK/compare/v0.26.1...v0.27.0)

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

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

* Bump fantomas from 6.3.10 to 6.3.11

Bumps [fantomas](https://github.com/fsprojects/fantomas) from 6.3.10 to 6.3.11.
- [Release notes](https://github.com/fsprojects/fantomas/releases)
- [Changelog](https://github.com/fsprojects/fantomas/blob/main/CHANGELOG.md)
- [Commits](https://github.com/fsprojects/fantomas/compare/v6.3.10...v6.3.11)

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

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

* Deps

* Bump analyzers too

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Smaug123 <3138005+Smaug123@users.noreply.github.com>
2024-08-26 11:07:27 +00:00
Patrick Stevens
417ca45c37 Basic arg parser (#216) 2024-08-26 11:54:35 +01:00
Patrick Stevens
569b3cc553 Pull out general changes from ArgParser PR (#217) 2024-08-25 19:23:23 +00:00
patrick-conscriptus[bot]
20226b9da9 Automated commit (#215)
Co-authored-by: patrick-conscriptus[bot] <175414948+patrick-conscriptus[bot]@users.noreply.github.com>
2024-08-25 01:17:27 +00:00
dependabot[bot]
f800e53bff Bump ApiSurface from 4.0.43 to 4.0.44 (#214)
* Bump ApiSurface from 4.0.43 to 4.0.44

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

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

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

* Fix deps

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Smaug123 <3138005+Smaug123@users.noreply.github.com>
2024-08-19 20:22:38 +01:00
patrick-conscriptus[bot]
5358f5da0e Automated commit (#213)
Co-authored-by: patrick-conscriptus[bot] <175414948+patrick-conscriptus[bot]@users.noreply.github.com>
2024-08-18 08:29:45 +00:00
Patrick Stevens
a868b8c08e No-op change to test the pipeline (#212) 2024-08-13 22:06:54 +00:00
Patrick Stevens
a4f945a3ee Upgrade all-required-checks-complete (#211) 2024-08-13 23:03:27 +01:00
Patrick Stevens
8434730ba7 Migrate to dedicated publish action (#210) 2024-08-13 21:54:14 +00:00
dependabot[bot]
811026996c Bump fsharp-analyzers from 0.26.0 to 0.26.1 (#208)
* Bump Nerdbank.GitVersioning from 3.6.139 to 3.6.141

Bumps [Nerdbank.GitVersioning](https://github.com/dotnet/Nerdbank.GitVersioning) from 3.6.139 to 3.6.141.
- [Release notes](https://github.com/dotnet/Nerdbank.GitVersioning/releases)
- [Commits](https://github.com/dotnet/Nerdbank.GitVersioning/compare/v3.6.139...v3.6.141)

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

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

* Bump fsharp-analyzers from 0.26.0 to 0.26.1

Bumps [fsharp-analyzers](https://github.com/ionide/FSharp.Analyzers.SDK) from 0.26.0 to 0.26.1.
- [Release notes](https://github.com/ionide/FSharp.Analyzers.SDK/releases)
- [Changelog](https://github.com/ionide/FSharp.Analyzers.SDK/blob/main/CHANGELOG.md)
- [Commits](https://github.com/ionide/FSharp.Analyzers.SDK/compare/v0.26.0...v0.26.1)

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

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

* Bump deps

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Smaug123 <3138005+Smaug123@users.noreply.github.com>
2024-08-12 11:35:03 +00:00
dependabot[bot]
25b2b160bb Bump actions/attest-build-provenance from 1.4.0 to 1.4.1 (#206) 2024-08-12 11:27:09 +01:00
patrick-conscriptus[bot]
4679474604 Automated commit (#205)
Co-authored-by: patrick-conscriptus[bot] <175414948+patrick-conscriptus[bot]@users.noreply.github.com>
2024-08-11 01:16:52 +00:00
dependabot[bot]
e16e241785 Bump actions/attest-build-provenance from 1.3.3 to 1.4.0 (#204) 2024-08-05 13:01:06 +01:00
Patrick Stevens
a52e4a46b0 Properly exclude test project (#203) 2024-08-04 19:42:15 +01:00
dependabot[bot]
f40a368948 Bump NUnit3TestAdapter from 4.5.0 to 4.6.0 (#201)
* Bump NUnit3TestAdapter from 4.5.0 to 4.6.0

Bumps [NUnit3TestAdapter](https://github.com/nunit/nunit3-vs-adapter) from 4.5.0 to 4.6.0.
- [Release notes](https://github.com/nunit/nunit3-vs-adapter/releases)
- [Commits](https://github.com/nunit/nunit3-vs-adapter/compare/V4.5.0...V4.6.0)

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

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

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Smaug123 <3138005+Smaug123@users.noreply.github.com>
2024-08-04 19:07:03 +01:00
patrick-conscriptus[bot]
adaee61fbf Automated commit (#202)
Co-authored-by: patrick-conscriptus[bot] <175414948+patrick-conscriptus[bot]@users.noreply.github.com>
2024-08-04 01:15:45 +00:00
patrick-conscriptus[bot]
d388660bfe Automated commit (#200)
Co-authored-by: patrick-conscriptus[bot] <175414948+patrick-conscriptus[bot]@users.noreply.github.com>
2024-07-28 01:14:36 +00:00
patrick-conscriptus[bot]
d0e9ba0efd Automated commit (#199)
Co-authored-by: patrick-conscriptus[bot] <175414948+patrick-conscriptus[bot]@users.noreply.github.com>
2024-07-21 01:14:26 +00:00
dependabot[bot]
d7d6c57910 Bump fantomas from 6.3.9 to 6.3.10 (#198)
Bumps fantomas from 6.3.9 to 6.3.10.

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-15 17:44:58 +00:00
dependabot[bot]
98e52743f5 Bump actions/attest-build-provenance from 1.3.2 to 1.3.3 (#197) 2024-07-15 11:45:54 +01:00
patrick-conscriptus[bot]
896696e002 Automated commit (#196)
Co-authored-by: patrick-conscriptus[bot] <175414948+patrick-conscriptus[bot]@users.noreply.github.com>
2024-07-14 01:14:38 +00:00
Patrick Stevens
654f760f3a Abstract away the required-checks feature (#194) 2024-07-12 20:02:04 +00:00
Patrick Stevens
31bd9e22f2 Shrink flake-updating action (#193) 2024-07-12 19:44:13 +01:00
Patrick Stevens
b7a240bbb9 Early out of flake update if there are no changes (#192) 2024-07-12 16:46:04 +00:00
Patrick Stevens
ebbe10ad81 Fix PR-raising logic (#191) 2024-07-12 16:36:56 +00:00
patrick-conscriptus[bot]
8f9af9af67 Upgrade Nix flake and deps (#190)
* Automated commit

---------

Co-authored-by: patrick-conscriptus[bot] <175414948+patrick-conscriptus[bot]@users.noreply.github.com>
Co-authored-by: Smaug123 <3138005+Smaug123@users.noreply.github.com>
2024-07-12 16:32:21 +00:00
Patrick Stevens
2c7cd91cbc Fix default branch name bit of update workflow (#189) 2024-07-12 16:22:07 +00:00
Patrick Stevens
ffaa373da9 Fix flake update workflow (#184) 2024-07-12 16:07:31 +00:00
Patrick Stevens
9f8459a7d3 Add flake update workflow (#183) 2024-07-12 12:12:44 +01:00
Patrick Stevens
362542d5ee Make required status check actually required (#182) 2024-07-12 11:59:42 +01:00
Patrick Stevens
18309becbd Add reproducibility check (#181) 2024-07-11 00:39:31 +01:00
dependabot[bot]
e96803e303 Bump ApiSurface from 4.0.42 to 4.0.43 (#180)
* Bump ApiSurface from 4.0.42 to 4.0.43

Bumps ApiSurface from 4.0.42 to 4.0.43.

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

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

* Deps

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Smaug123 <3138005+Smaug123@users.noreply.github.com>
2024-07-08 19:06:18 +01:00
dependabot[bot]
b53b410feb Bump ApiSurface from 4.0.41 to 4.0.42 (#176)
* Bump ApiSurface from 4.0.41 to 4.0.42

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

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

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

* Deps

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Smaug123 <3138005+Smaug123@users.noreply.github.com>
2024-07-01 18:21:26 +01:00
Patrick Stevens
398cd04a2a Support DateTimeOffset in JSON generators (#179) 2024-07-01 18:08:09 +01:00
Patrick Stevens
434c042510 Omit upcasts where possible (#178) 2024-07-01 17:45:36 +01:00
Patrick Stevens
c590db2a65 JSON enums (#175) 2024-06-27 21:23:06 +01:00
Patrick Stevens
6a81513a93 Add nullable support to JSON generators (#174) 2024-06-27 08:40:58 +01:00
Patrick Stevens
ba31689145 Also allow serialising units of measure (#171) 2024-06-25 00:04:56 +01:00
Patrick Stevens
85929d49d5 Support units of measure in JsonParse (#170) 2024-06-24 23:23:23 +01:00
dependabot[bot]
db4694f6e7 Bump actions/attest-build-provenance from 1.0.0 to 1.3.2 (#169)
Bumps [actions/attest-build-provenance](https://github.com/actions/attest-build-provenance) from 1.0.0 to 1.3.2.
- [Release notes](https://github.com/actions/attest-build-provenance/releases)
- [Changelog](https://github.com/actions/attest-build-provenance/blob/main/RELEASE.md)
- [Commits](897ed5eab6...bdd51370e0)

---
updated-dependencies:
- dependency-name: actions/attest-build-provenance
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-24 18:55:05 +01:00
Patrick Stevens
669eccbdef Nudge README to bump the pipeline (#168) 2024-06-17 23:17:34 +01:00
Patrick Stevens
1bb87e55da Attest contents of packages (#167) 2024-06-17 23:08:36 +01:00
Patrick Stevens
4901e7cdf4 Add visibility modifiers in JsonParse/Serialize (#165) 2024-06-15 21:03:59 +01:00
dependabot[bot]
68bd4bc1fd Bump fantomas from 6.3.7 to 6.3.9 (#162)
* Bump fantomas from 6.3.7 to 6.3.9

Bumps [fantomas](https://github.com/fsprojects/fantomas) from 6.3.7 to 6.3.9.
- [Release notes](https://github.com/fsprojects/fantomas/releases)
- [Changelog](https://github.com/fsprojects/fantomas/blob/main/CHANGELOG.md)
- [Commits](https://github.com/fsprojects/fantomas/compare/v6.3.7...v6.3.9)

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

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

* Deps

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Smaug123 <3138005+Smaug123@users.noreply.github.com>
2024-06-10 18:04:02 +01:00
dependabot[bot]
8da0fd01fe Bump Nerdbank.GitVersioning from 3.6.133 to 3.6.139 (#164)
* Bump ApiSurface from 4.0.40 to 4.0.41

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

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

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

* Bump Nerdbank.GitVersioning from 3.6.133 to 3.6.139

Bumps [Nerdbank.GitVersioning](https://github.com/dotnet/Nerdbank.GitVersioning) from 3.6.133 to 3.6.139.
- [Release notes](https://github.com/dotnet/Nerdbank.GitVersioning/releases)
- [Commits](https://github.com/dotnet/Nerdbank.GitVersioning/compare/v3.6.133...v3.6.139)

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

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

* Deps

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Smaug123 <3138005+Smaug123@users.noreply.github.com>
2024-06-10 12:25:03 +01:00
96 changed files with 84011 additions and 2796 deletions

View File

@@ -3,13 +3,13 @@
"isRoot": true,
"tools": {
"fantomas": {
"version": "6.3.7",
"version": "7.0.1",
"commands": [
"fantomas"
]
},
"fsharp-analyzers": {
"version": "0.26.0",
"version": "0.30.0",
"commands": [
"fsharp-analyzers"
]

1
.fantomasignore Normal file
View File

@@ -0,0 +1 @@
.direnv/

View File

@@ -29,7 +29,7 @@ jobs:
with:
fetch-depth: 0 # so that NerdBank.GitVersioning has access to history
- name: Install Nix
uses: cachix/install-nix-action@V27
uses: cachix/install-nix-action@v31
with:
extra_nix_config: |
access-tokens = github.com=${{ secrets.GITHUB_TOKEN }}
@@ -50,7 +50,7 @@ jobs:
with:
fetch-depth: 0 # so that NerdBank.GitVersioning has access to history
- name: Install Nix
uses: cachix/install-nix-action@V27
uses: cachix/install-nix-action@v31
with:
extra_nix_config: |
access-tokens = github.com=${{ secrets.GITHUB_TOKEN }}
@@ -67,12 +67,14 @@ jobs:
- name: Checkout
uses: actions/checkout@v4
- name: Install Nix
uses: cachix/install-nix-action@V27
uses: cachix/install-nix-action@v31
with:
extra_nix_config: |
access-tokens = github.com=${{ secrets.GITHUB_TOKEN }}
- name: Build
run: nix build
- name: Reproducibility check
run: nix build --rebuild
check-dotnet-format:
runs-on: ubuntu-latest
@@ -80,7 +82,7 @@ jobs:
- name: Checkout
uses: actions/checkout@v4
- name: Install Nix
uses: cachix/install-nix-action@V27
uses: cachix/install-nix-action@v31
with:
extra_nix_config: |
access-tokens = github.com=${{ secrets.GITHUB_TOKEN }}
@@ -95,7 +97,7 @@ jobs:
with:
fetch-depth: 0 # so that NerdBank.GitVersioning has access to history
- name: Install Nix
uses: cachix/install-nix-action@V27
uses: cachix/install-nix-action@v31
with:
extra_nix_config: |
access-tokens = github.com=${{ secrets.GITHUB_TOKEN }}
@@ -114,7 +116,7 @@ jobs:
- name: Checkout
uses: actions/checkout@v4
- name: Install Nix
uses: cachix/install-nix-action@V27
uses: cachix/install-nix-action@v31
with:
extra_nix_config: |
access-tokens = github.com=${{ secrets.GITHUB_TOKEN }}
@@ -127,7 +129,7 @@ jobs:
steps:
- uses: actions/checkout@master
- name: Install Nix
uses: cachix/install-nix-action@V27
uses: cachix/install-nix-action@v31
with:
extra_nix_config: |
access-tokens = github.com=${{ secrets.GITHUB_TOKEN }}
@@ -140,7 +142,7 @@ jobs:
steps:
- uses: actions/checkout@master
- name: Install Nix
uses: cachix/install-nix-action@V27
uses: cachix/install-nix-action@v31
with:
extra_nix_config: |
access-tokens = github.com=${{ secrets.GITHUB_TOKEN }}
@@ -154,7 +156,7 @@ jobs:
with:
fetch-depth: 0 # so that NerdBank.GitVersioning has access to history
- name: Install Nix
uses: cachix/install-nix-action@V27
uses: cachix/install-nix-action@v31
with:
extra_nix_config: |
access-tokens = github.com=${{ secrets.GITHUB_TOKEN }}
@@ -196,59 +198,157 @@ jobs:
# Verify that there is exactly one nupkg in the artifact that would be NuGet published
run: if [[ $(find packed-attribute -maxdepth 1 -name 'WoofWare.Myriad.Plugins.Attributes.*.nupkg' -printf c | wc -c) -ne "1" ]]; then exit 1; fi
github-release-plugin-dry-run:
needs: [nuget-pack]
github-release-dry-run:
strategy:
matrix:
artifact:
- nuget-package-plugin
- nuget-package-attribute
runs-on: ubuntu-latest
needs: [nuget-pack]
steps:
- uses: actions/checkout@v4
- name: Download NuGet artifact (plugin)
- name: Download NuGet artifact
uses: actions/download-artifact@v4
with:
name: nuget-package-plugin
- name: Download NuGet artifact (attribute)
name: ${{ matrix.artifact }}
- name: Compute package path
id: compute-path
run: |
find . -maxdepth 1 -type f -name 'WoofWare.Myriad.*.nupkg' -exec sh -c 'echo "output=$(basename "$1")" >> $GITHUB_OUTPUT' shell {} \;
- name: Compute tag name
id: compute-tag
env:
NUPKG_PATH: ${{ steps.compute-path.outputs.output }}
run: echo "output=$(basename "$NUPKG_PATH" .nupkg)" >> $GITHUB_OUTPUT
- name: Tag and release
uses: G-Research/common-actions/github-release@19d7281a0f9f83e13c78f99a610dbc80fc59ba3b
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
target-commitish: ${{ github.sha }}
tag: ${{ steps.compute-tag.outputs.output }}
binary-contents: ${{ steps.compute-path.outputs.output }}
dry-run: true
all-required-checks-complete:
needs: [check-dotnet-format, check-nix-format, check-accurate-generations, build, build-nix, linkcheck, flake-check, analyzers, nuget-pack, expected-pack, github-release-dry-run]
if: ${{ always() }}
runs-on: ubuntu-latest
steps:
- uses: G-Research/common-actions/check-required-lite@2b7dc49cb14f3344fbe6019c14a31165e258c059
with:
needs-context: ${{ toJSON(needs) }}
attestation-attribute:
runs-on: ubuntu-latest
needs: [all-required-checks-complete]
if: ${{ !github.event.repository.fork && github.ref == 'refs/heads/main' }}
permissions:
id-token: write
attestations: write
contents: read
steps:
- name: Download NuGet artifact
uses: actions/download-artifact@v4
with:
name: nuget-package-attribute
- name: Tag and release plugin
env:
DRY_RUN: 1
GITHUB_TOKEN: mock-token
run: sh .github/workflows/tag.sh
path: packed
- name: Attest Build Provenance
uses: actions/attest-build-provenance@c074443f1aee8d4aeeae555aebba3282517141b2 # v2.2.3
with:
subject-path: "packed/*.nupkg"
all-required-checks-complete:
needs: [check-dotnet-format, check-nix-format, check-accurate-generations, build, build-nix, linkcheck, flake-check, analyzers, nuget-pack, expected-pack, github-release-plugin-dry-run]
attestation-plugin:
runs-on: ubuntu-latest
needs: [all-required-checks-complete]
if: ${{ !github.event.repository.fork && github.ref == 'refs/heads/main' }}
permissions:
id-token: write
attestations: write
contents: read
steps:
- run: echo "All required checks complete."
- name: Download NuGet artifact
uses: actions/download-artifact@v4
with:
name: nuget-package-plugin
path: packed
- name: Attest Build Provenance
uses: actions/attest-build-provenance@c074443f1aee8d4aeeae555aebba3282517141b2 # v2.2.3
with:
subject-path: "packed/*.nupkg"
nuget-publish:
nuget-publish-attribute:
runs-on: ubuntu-latest
if: ${{ !github.event.repository.fork && github.ref == 'refs/heads/main' }}
needs: [all-required-checks-complete]
environment: main-deploy
permissions:
id-token: write
attestations: write
contents: read
steps:
- uses: actions/checkout@v4
- name: Install Nix
uses: cachix/install-nix-action@V27
uses: cachix/install-nix-action@v31
with:
extra_nix_config: |
access-tokens = github.com=${{ secrets.GITHUB_TOKEN }}
- name: Download NuGet artifact (plugin)
uses: actions/download-artifact@v4
with:
name: nuget-package-plugin
path: packed-plugin
- name: Publish to NuGet (plugin)
run: nix develop --command dotnet nuget push "packed-plugin/WoofWare.Myriad.Plugins.*.nupkg" --api-key ${{ secrets.NUGET_API_KEY }} --source https://api.nuget.org/v3/index.json --skip-duplicate
- name: Download NuGet artifact (attribute)
- name: Download NuGet artifact
uses: actions/download-artifact@v4
with:
name: nuget-package-attribute
path: packed-attribute
- name: Publish to NuGet (attribute)
run: nix develop --command dotnet nuget push "packed-attribute/WoofWare.Myriad.Plugins.Attributes.*.nupkg" --api-key ${{ secrets.NUGET_API_KEY }} --source https://api.nuget.org/v3/index.json --skip-duplicate
path: packed
- name: Identify `dotnet`
id: dotnet-identify
run: nix develop --command bash -c 'echo "dotnet=$(which dotnet)" >> $GITHUB_OUTPUT'
- name: Publish to NuGet
id: publish-success
uses: G-Research/common-actions/publish-nuget@2b7dc49cb14f3344fbe6019c14a31165e258c059
with:
package-name: WoofWare.Myriad.Plugins.Attributes
nuget-key: ${{ secrets.NUGET_API_KEY }}
nupkg-dir: packed/
dotnet: ${{ steps.dotnet-identify.outputs.dotnet }}
github-release-plugin:
nuget-publish-plugin:
runs-on: ubuntu-latest
if: ${{ !github.event.repository.fork && github.ref == 'refs/heads/main' }}
needs: [all-required-checks-complete]
environment: main-deploy
permissions:
id-token: write
attestations: write
contents: read
steps:
- uses: actions/checkout@v4
- name: Install Nix
uses: cachix/install-nix-action@v31
with:
extra_nix_config: |
access-tokens = github.com=${{ secrets.GITHUB_TOKEN }}
- name: Download NuGet artifact
uses: actions/download-artifact@v4
with:
name: nuget-package-plugin
path: packed
- name: Identify `dotnet`
id: dotnet-identify
run: nix develop --command bash -c 'echo "dotnet=$(which dotnet)" >> $GITHUB_OUTPUT'
- name: Publish to NuGet
id: publish-success
uses: G-Research/common-actions/publish-nuget@2b7dc49cb14f3344fbe6019c14a31165e258c059
with:
package-name: WoofWare.Myriad.Plugins
nuget-key: ${{ secrets.NUGET_API_KEY }}
nupkg-dir: packed/
dotnet: ${{ steps.dotnet-identify.outputs.dotnet }}
github-release:
strategy:
matrix:
artifact:
- nuget-package-attribute
- nuget-package-plugin
runs-on: ubuntu-latest
if: ${{ !github.event.repository.fork && github.ref == 'refs/heads/main' }}
needs: [all-required-checks-complete]
@@ -257,15 +357,23 @@ jobs:
contents: write
steps:
- uses: actions/checkout@v4
- name: Download NuGet artifact (plugin)
- name: Download NuGet artifact
uses: actions/download-artifact@v4
with:
name: nuget-package-plugin
- name: Download NuGet artifact (attribute)
uses: actions/download-artifact@v4
with:
name: nuget-package-attribute
- name: Tag and release plugin
name: ${{ matrix.artifact }}
- name: Compute package path
id: compute-path
run: |
find . -maxdepth 1 -type f -name 'WoofWare.Myriad.*.nupkg' -exec sh -c 'echo "output=$(basename "$1")" >> $GITHUB_OUTPUT' shell {} \;
- name: Compute tag name
id: compute-tag
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: sh .github/workflows/tag.sh
NUPKG_PATH: ${{ steps.compute-path.outputs.output }}
run: echo "output=$(basename "$NUPKG_PATH" .nupkg)" >> $GITHUB_OUTPUT
- name: Tag and release
uses: G-Research/common-actions/github-release@19d7281a0f9f83e13c78f99a610dbc80fc59ba3b
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
target-commitish: ${{ github.sha }}
tag: ${{ steps.compute-tag.outputs.output }}
binary-contents: ${{ steps.compute-path.outputs.output }}

56
.github/workflows/flake_update.yaml vendored Normal file
View File

@@ -0,0 +1,56 @@
# yaml-language-server: $schema=https://raw.githubusercontent.com/SchemaStore/schemastore/master/src/schemas/json/github-workflow.json
name: Weekly Nix Flake Update
on:
schedule:
- cron: '0 0 * * 0' # Runs at 00:00 every Sunday
workflow_dispatch: # Allows manual triggering
jobs:
update-nix-flake:
runs-on: ubuntu-latest
steps:
- name: Check out repository
uses: actions/checkout@v4
- name: Install Nix
uses: DeterminateSystems/nix-installer-action@main
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
- name: Update Nix flake
run: 'nix flake update'
- name: Build fetch-deps
run: 'nix build ".#default.fetch-deps"'
- name: Run fetch-deps
run: |
set -o pipefail
./result nix/deps.json
- name: Format
run: 'nix develop --command alejandra .'
- name: Create token
id: generate-token
uses: actions/create-github-app-token@v2
with:
# https://github.com/actions/create-github-app-token/issues/136
app-id: ${{ secrets.APP_ID }}
private-key: ${{ secrets.APP_PRIVATE_KEY }}
- name: Raise pull request
uses: Smaug123/commit-action@cc25e6d80a796c49669dda4a0aa36c54c573983d
id: cpr
with:
bearer-token: ${{ steps.generate-token.outputs.token }}
pr-title: "Upgrade Nix flake and deps"
- name: Enable Pull Request Automerge
if: ${{ steps.cpr.outputs.pull-request-number }}
uses: peter-evans/enable-pull-request-automerge@v3
with:
token: ${{ steps.generate-token.outputs.token }}
pull-request-number: ${{ steps.cpr.outputs.pull-request-number }}
merge-method: squash

View File

@@ -1,120 +0,0 @@
#!/bin/bash
echo "Dry-run? $DRY_RUN!"
find . -maxdepth 1 -type f ! -name "$(printf "*\n*")" -name '*.nupkg' | while IFS= read -r file
do
tag=$(basename "$file" .nupkg)
git tag "$tag"
${DRY_RUN:+echo} git push origin "$tag"
done
export TAG
TAG=$(find . -maxdepth 1 -type f -name 'WoofWare.Myriad.Plugins.*.nupkg' -exec sh -c 'basename "$1" .nupkg' shell {} \; | grep -v Attributes)
case "$TAG" in
*"
"*)
echo "Error: TAG contains a newline; multiple plugins found."
exit 1
;;
esac
# target_commitish empty indicates the repo default branch
curl_body='{"tag_name":"'"$TAG"'","target_commitish":"","name":"'"$TAG"'","draft":false,"prerelease":false,"generate_release_notes":false}'
echo "cURL body: $curl_body"
failed_output=$(cat <<'EOF'
{
"message": "Validation Failed",
"errors": [
{
"resource": "Release",
"code": "already_exists",
"field": "tag_name"
}
],
"documentation_url": "https://docs.github.com/rest/releases/releases#create-a-release"
}
EOF
)
success_output=$(cat <<'EOF'
{
"url": "https://api.github.com/repos/Smaug123/WoofWare.Myriad/releases/158152116",
"assets_url": "https://api.github.com/repos/Smaug123/WoofWare.Myriad/releases/158152116/assets",
"upload_url": "https://uploads.github.com/repos/Smaug123/WoofWare.Myriad/releases/158152116/assets{?name,label}",
"html_url": "https://github.com/Smaug123/WoofWare.Myriad/releases/tag/WoofWare.Myriad.Plugins.2.1.30",
"id": 158152116,
"author": {
"login": "github-actions[bot]",
"id": 41898282,
"node_id": "MDM6Qm90NDE4OTgyODI=",
"avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4",
"gravatar_id": "",
"url": "https://api.github.com/users/github-actions%5Bbot%5D",
"html_url": "https://github.com/apps/github-actions",
"followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers",
"following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}",
"gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}",
"starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions",
"organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs",
"repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos",
"events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}",
"received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events",
"type": "Bot",
"site_admin": false
},
"node_id": "RE_kwDOJfksgc4JbTW0",
"tag_name": "WoofWare.Myriad.Plugins.2.1.30",
"target_commitish": "main",
"name": "WoofWare.Myriad.Plugins.2.1.30",
"draft": false,
"prerelease": false,
"created_at": "2024-05-30T11:00:55Z",
"published_at": "2024-05-30T11:03:02Z",
"assets": [
],
"tarball_url": "https://api.github.com/repos/Smaug123/WoofWare.Myriad/tarball/WoofWare.Myriad.Plugins.2.1.30",
"zipball_url": "https://api.github.com/repos/Smaug123/WoofWare.Myriad/zipball/WoofWare.Myriad.Plugins.2.1.30",
"body": null
}
EOF
)
HANDLE_OUTPUT=''
handle_error() {
ERROR_OUTPUT="$1"
exit_message=$(echo "$ERROR_OUTPUT" | jq -r --exit-status 'if .errors | length == 1 then .errors[0].code else null end')
if [ "$exit_message" = "already_exists" ] ; then
HANDLE_OUTPUT="Did not create GitHub release because it already exists at this version."
else
echo "Unexpected error output from curl: $(cat curl_output.json)"
echo "JQ output: $(exit_message)"
exit 2
fi
}
run_tests() {
handle_error "$failed_output"
if [ "$HANDLE_OUTPUT" != "Did not create GitHub release because it already exists at this version." ]; then
echo "Bad output from handler: $HANDLE_OUTPUT"
exit 3
fi
HANDLE_OUTPUT=''
echo "Tests passed."
}
run_tests
if [ "$DRY_RUN" != 1 ] ; then
if curl --fail-with-body -L -X POST -H "Accept: application/vnd.github+json" -H "Authorization: Bearer $GITHUB_TOKEN" -H "X-GitHub-Api-Version: 2022-11-28" https://api.github.com/repos/Smaug123/WoofWare.Myriad/releases -d "$curl_body" > curl_output.json; then
echo "Curl succeeded."
else
handle_error "$(cat curl_output.json)"
echo "$HANDLE_OUTPUT"
fi
fi

26
.gitignore vendored
View File

@@ -1,12 +1,14 @@
bin/
obj/
/packages/
riderModule.iml
/_ReSharper.Caches/
.idea/
*.sln.DotSettings.user
.DS_Store
result
.analyzerpackages/
analysis.sarif
.direnv/
bin/
obj/
/packages/
riderModule.iml
/_ReSharper.Caches/
.idea/
*.sln.DotSettings.user
.DS_Store
result
.analyzerpackages/
analysis.sarif
.direnv/
.venv/
.vs/

View File

@@ -1,5 +1,35 @@
Notable changes are recorded here.
# WoofWare.Myriad.Plugins 6.0.1
The `ArgParser` generator's type signatures have changed.
The `parse'` method no longer takes `getEnvironmentVariable : string -> string`; it's now `getEnvironmentVariable : string -> string option`.
This is to permit satisfying the `<Nullable>enable</Nullable>` compiler setting.
If you're calling `parse'`, give it `Environment.GetEnvironmentVariable >> Option.ofObj` instead.
# WoofWare.Myriad.Plugins 5.0.1
We now enforce non-nullability on more types during JSON parse.
We have always expected you to consume nullable types wrapped in an `option`, but now we enforce this in more cases by throwing `ArgumentNullException`.
# 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
The NuGet packages are now attested to through [GitHub Attestations](https://github.blog/2024-05-02-introducing-artifact-attestations-now-in-public-beta/).
You can run `gh attestation verify ~/.nuget/packages/woofware.myriad.plugins/2.1.45/woofware.myriad.plugins.2.1.45.nupkg -o Smaug123`, for example, to verify with GitHub that the GitHub Actions pipeline on this repository produced a nupkg file with the same hash as the one you were served from NuGet.
# WoofWare.Myriad.Plugins 2.1.33
`JsonParse` can now deserialize the discriminated unions which `JsonSerialize` wrote out.

237
ConsumePlugin/Args.fs Normal file
View File

@@ -0,0 +1,237 @@
namespace ConsumePlugin
open System
open System.IO
open WoofWare.Myriad.Plugins
[<ArgParser>]
type BasicNoPositionals =
{
Foo : int
Bar : string
Baz : bool
Rest : int list
}
[<ArgParser>]
type Basic =
{
[<ArgumentHelpText "This is a foo!">]
Foo : int
Bar : string
Baz : bool
[<ArgumentHelpText "Here's where the rest of the args go">]
[<PositionalArgs>]
Rest : string list
}
[<ArgParser>]
type BasicWithIntPositionals =
{
Foo : int
Bar : string
Baz : bool
[<PositionalArgs>]
Rest : int list
}
[<ArgParser>]
type LoadsOfTypes =
{
Foo : int
Bar : string
Baz : bool
SomeFile : FileInfo
SomeDirectory : DirectoryInfo
SomeList : DirectoryInfo list
OptionalThingWithNoDefault : int option
[<PositionalArgs>]
Positionals : int list
[<ArgumentDefaultFunction>]
OptionalThing : Choice<bool, bool>
[<ArgumentDefaultFunction>]
AnotherOptionalThing : Choice<int, int>
[<ArgumentDefaultEnvironmentVariable "CONSUMEPLUGIN_THINGS">]
YetAnotherOptionalThing : Choice<string, string>
}
static member DefaultOptionalThing () = true
static member DefaultAnotherOptionalThing () = 3
[<ArgParser>]
type LoadsOfTypesNoPositionals =
{
Foo : int
Bar : string
Baz : bool
SomeFile : FileInfo
SomeDirectory : DirectoryInfo
SomeList : DirectoryInfo list
OptionalThingWithNoDefault : int option
[<ArgumentDefaultFunction>]
OptionalThing : Choice<bool, bool>
[<ArgumentDefaultFunction>]
AnotherOptionalThing : Choice<int, int>
[<ArgumentDefaultEnvironmentVariable "CONSUMEPLUGIN_THINGS">]
YetAnotherOptionalThing : Choice<string, string>
}
static member DefaultOptionalThing () = false
static member DefaultAnotherOptionalThing () = 3
[<ArgParser true>]
type DatesAndTimes =
{
Plain : TimeSpan
[<InvariantCulture>]
Invariant : TimeSpan
[<ParseExact @"hh\:mm\:ss">]
[<ArgumentHelpText "An exact time please">]
Exact : TimeSpan
[<InvariantCulture ; ParseExact @"hh\:mm\:ss">]
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
}

View File

@@ -1,8 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net9.0</TargetFramework>
<IsPackable>false</IsPackable>
<OtherFlags>--reflectionfree $(OtherFlags)</OtherFlags>
</PropertyGroup>
<ItemGroup>
<MyriadSdkGenerator Include="$(MSBuildThisFileDirectory)..\WoofWare.Myriad.Plugins\bin\$(Configuration)\net6.0\WoofWare.Myriad.Plugins.dll"/>
@@ -31,6 +32,20 @@
<Compile Include="GeneratedMock.fs">
<MyriadFile>MockExample.fs</MyriadFile>
</Compile>
<Compile Include="MockExampleNoAttributes.fs" />
<Compile Include="GeneratedMockNoAttributes.fs">
<MyriadFile>MockExampleNoAttributes.fs</MyriadFile>
<MyriadParams>
<IPublicTypeNoAttr>GenerateMock</IPublicTypeNoAttr>
<IPublicTypeInternalFalseNoAttr>GenerateMock(false)</IPublicTypeInternalFalseNoAttr>
<InternalTypeNoAttr>GenerateMock</InternalTypeNoAttr>
<PrivateTypeNoAttr>GenerateMock</PrivateTypeNoAttr>
<PrivateTypeInternalFalseNoAttr>GenerateMock(false)</PrivateTypeInternalFalseNoAttr>
<VeryPublicTypeNoAttr>GenerateMock</VeryPublicTypeNoAttr>
<CurriedNoAttr>GenerateMock</CurriedNoAttr>
<TypeWithInterfaceNoAttr>GenerateMock</TypeWithInterfaceNoAttr>
</MyriadParams>
</Compile>
<Compile Include="Vault.fs" />
<Compile Include="GeneratedVault.fs">
<MyriadFile>Vault.fs</MyriadFile>
@@ -51,14 +66,28 @@
<Compile Include="ListCata.fs">
<MyriadFile>List.fs</MyriadFile>
</Compile>
<Compile Include="Args.fs" />
<Compile Include="GeneratedArgs.fs">
<MyriadFile>Args.fs</MyriadFile>
</Compile>
<None Include="swagger-gitea.json" />
<Compile Include="GeneratedSwaggerGitea.fs">
<MyriadFile>swagger-gitea.json</MyriadFile>
<MyriadParams>
<GenerateMockInternal>true</GenerateMockInternal>
<ClassName>Gitea</ClassName>
</MyriadParams>
</Compile>
<Compile Include="Generated2SwaggerGitea.fs">
<MyriadFile>GeneratedSwaggerGitea.fs</MyriadFile>
</Compile>
</ItemGroup>
<ItemGroup>
<PackageReference Include="RestEase" Version="1.6.4"/>
<ProjectReference Include="..\WoofWare.Myriad.Plugins.Attributes\WoofWare.Myriad.Plugins.Attributes.fsproj" />
<ProjectReference Include="..\WoofWare.Myriad.Plugins\WoofWare.Myriad.Plugins.fsproj"/>
<PackageReference Include="Myriad.Sdk" Version="0.8.3"/>
<PackageReference Include="Myriad.Core" Version="0.8.3"/>
<ProjectReference Include="..\WoofWare.Myriad.Plugins\WoofWare.Myriad.Plugins.fsproj" PrivateAssets="all" />
<PackageReference Include="Myriad.Sdk" Version="0.8.3" PrivateAssets="all" />
</ItemGroup>
</Project>

View File

@@ -30,6 +30,12 @@ type ChocolateType =
| Milk
| SeventyPercent
override this.ToString () =
match this with
| ChocolateType.Dark -> "Dark"
| ChocolateType.Milk -> "Milk"
| ChocolateType.SeventyPercent -> "SeventyPercent"
type Chocolate =
{
chocType : ChocolateType
@@ -43,6 +49,12 @@ type WrappingPaperStyle =
| HappyHolidays
| SolidColor
override this.ToString () =
match this with
| WrappingPaperStyle.HappyBirthday -> "HappyBirthday"
| WrappingPaperStyle.HappyHolidays -> "HappyHolidays"
| WrappingPaperStyle.SolidColor -> "SolidColor"
[<CreateCatamorphism "GiftCata">]
type Gift =
| Book of Book

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -31,7 +31,7 @@ module FileSystemItemCata =
[<RequireQualifiedAccess>]
type private Instruction =
| Process__FileSystemItem of FileSystemItem
| FileSystemItem_Directory of string * int * int
| FileSystemItem_Directory of name : string * dirSize : int * contents : int
let private loop (cata : FileSystemCata<'FileSystemItem>) (instructions : ResizeArray<Instruction>) =
let fileSystemItemStack = ResizeArray<'FileSystemItem> ()
@@ -106,7 +106,7 @@ module GiftCata =
| Process__Gift of Gift
| Gift_Wrapped of WrappingPaperStyle
| Gift_Boxed
| Gift_WithACard of string
| Gift_WithACard of message : string
let private loop (cata : GiftCata<'Gift>) (instructions : ResizeArray<Instruction>) =
let giftStack = ResizeArray<'Gift> ()

View File

@@ -4,6 +4,33 @@
//------------------------------------------------------------------------------
namespace ConsumePlugin
open System.Text.Json.Serialization
/// Module containing JSON serializing methods for the InternalTypeNotExtensionSerial type
[<RequireQualifiedAccess ; CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix)>]
module internal InternalTypeNotExtensionSerial =
/// Serialize to a JSON node
let toJsonNode (input : InternalTypeNotExtensionSerial) : System.Text.Json.Nodes.JsonNode =
let node = System.Text.Json.Nodes.JsonObject ()
do node.Add ((Literals.something), (input.InternalThing2 |> System.Text.Json.Nodes.JsonValue.Create<string>))
node :> _
namespace ConsumePlugin
open System.Text.Json.Serialization
/// Module containing JSON serializing extension members for the InternalTypeExtension type
[<AutoOpen>]
module internal InternalTypeExtensionJsonSerializeExtension =
/// Extension methods for JSON parsing
type InternalTypeExtension with
/// Serialize to a JSON node
static member toJsonNode (input : InternalTypeExtension) : System.Text.Json.Nodes.JsonNode =
let node = System.Text.Json.Nodes.JsonObject ()
do node.Add ((Literals.something), (input.ExternalThing |> System.Text.Json.Nodes.JsonValue.Create<string>))
node :> _
namespace ConsumePlugin
@@ -22,7 +49,7 @@ module InnerType =
)
| v -> v)
.AsValue()
.GetValue<string> ()
.GetValue<System.String> ()
{
Thing = arg_0
@@ -44,7 +71,11 @@ module JsonRecordType =
)
| v -> v)
.AsArray ()
|> Seq.map (fun elt -> elt.AsValue().GetValue<int> ())
|> Seq.map (fun elt ->
(match elt with
| null -> raise (System.ArgumentNullException ())
| elt -> elt.AsValue().GetValue<System.Int32> ())
)
|> Array.ofSeq
let arg_4 =
@@ -57,7 +88,11 @@ module JsonRecordType =
)
| v -> v)
.AsArray ()
|> Seq.map (fun elt -> elt.AsValue().GetValue<string> ())
|> Seq.map (fun elt ->
(match elt with
| null -> raise (System.ArgumentNullException ())
| elt -> elt.AsValue().GetValue<System.String> ())
)
|> Array.ofSeq
let arg_3 =
@@ -82,7 +117,11 @@ module JsonRecordType =
)
| v -> v)
.AsArray ()
|> Seq.map (fun elt -> elt.AsValue().GetValue<int> ())
|> Seq.map (fun elt ->
(match elt with
| null -> raise (System.ArgumentNullException ())
| elt -> elt.AsValue().GetValue<System.Int32> ())
)
|> List.ofSeq
let arg_1 =
@@ -95,7 +134,7 @@ module JsonRecordType =
)
| v -> v)
.AsValue()
.GetValue<string> ()
.GetValue<System.String> ()
let arg_0 =
(match node.["a"] with
@@ -107,7 +146,7 @@ module JsonRecordType =
)
| v -> v)
.AsValue()
.GetValue<int> ()
.GetValue<System.Int32> ()
{
A = arg_0
@@ -119,6 +158,53 @@ module JsonRecordType =
}
namespace ConsumePlugin
/// Module containing JSON parsing methods for the InternalTypeNotExtension type
[<RequireQualifiedAccess ; CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix)>]
module internal InternalTypeNotExtension =
/// Parse from a JSON node.
let jsonParse (node : System.Text.Json.Nodes.JsonNode) : InternalTypeNotExtension =
let arg_0 =
(match node.[(Literals.something)] with
| null ->
raise (
System.Collections.Generic.KeyNotFoundException (
sprintf "Required key '%s' not found on JSON object" ((Literals.something))
)
)
| v -> v)
.AsValue()
.GetValue<System.String> ()
{
InternalThing = arg_0
}
namespace ConsumePlugin
/// Module containing JSON parsing extension members for the InternalTypeExtension type
[<AutoOpen>]
module internal InternalTypeExtensionJsonParseExtension =
/// Extension methods for JSON parsing
type InternalTypeExtension with
/// Parse from a JSON node.
static member jsonParse (node : System.Text.Json.Nodes.JsonNode) : InternalTypeExtension =
let arg_0 =
(match node.[(Literals.something)] with
| null ->
raise (
System.Collections.Generic.KeyNotFoundException (
sprintf "Required key '%s' not found on JSON object" ((Literals.something))
)
)
| v -> v)
.AsValue()
.GetValue<System.String> ()
{
ExternalThing = arg_0
}
namespace ConsumePlugin
/// Module containing JSON parsing extension members for the ToGetExtensionMethod type
[<AutoOpen>]
module ToGetExtensionMethodJsonParseExtension =
@@ -271,7 +357,7 @@ module ToGetExtensionMethodJsonParseExtension =
)
| v -> v)
.AsValue()
.GetValue<int> ()
.GetValue<System.Int32> ()
let arg_7 =
(match node.["hotel"] with
@@ -343,7 +429,7 @@ module ToGetExtensionMethodJsonParseExtension =
)
| v -> v)
.AsValue()
.GetValue<float> ()
.GetValue<System.Double> ()
let arg_1 =
(match node.["bravo"] with
@@ -368,7 +454,7 @@ module ToGetExtensionMethodJsonParseExtension =
)
| v -> v)
.AsValue()
.GetValue<string> ()
.GetValue<System.String> ()
{
Alpha = arg_0

View File

@@ -206,3 +206,34 @@ type internal TypeWithInterfaceMock =
interface System.IDisposable with
member this.Dispose () : unit = this.Dispose ()
namespace SomeNamespace
open System
open WoofWare.Myriad.Plugins
/// Mock record type for an interface
type internal TypeWithPropertiesMock =
{
/// Implementation of IDisposable.Dispose
Dispose : unit -> unit
Prop1 : unit -> int
Prop2 : unit -> unit Async
Mem1 : string option -> string[] Async
}
/// An implementation where every method throws.
static member Empty : TypeWithPropertiesMock =
{
Dispose = (fun () -> ())
Prop1 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Prop1"))
Prop2 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Prop2"))
Mem1 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem1"))
}
interface TypeWithProperties with
member this.Mem1 arg_0_0 = this.Mem1 (arg_0_0)
member this.Prop1 = this.Prop1 ()
member this.Prop2 = this.Prop2 ()
interface System.IDisposable with
member this.Dispose () : unit = this.Dispose ()

View File

@@ -0,0 +1,200 @@
//------------------------------------------------------------------------------
// This code was generated by myriad.
// Changes to this file will be lost when the code is regenerated.
//------------------------------------------------------------------------------
namespace SomeNamespace
open System
/// Mock record type for an interface
type internal PublicTypeNoAttrMock =
{
Mem1 : string * int -> string list
Mem2 : string -> int
Mem3 : int * option<System.Threading.CancellationToken> -> string
}
/// An implementation where every method throws.
static member Empty : PublicTypeNoAttrMock =
{
Mem1 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem1"))
Mem2 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem2"))
Mem3 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem3"))
}
interface IPublicTypeNoAttr with
member this.Mem1 (arg_0_0, arg_0_1) = this.Mem1 (arg_0_0, arg_0_1)
member this.Mem2 arg_0_0 = this.Mem2 (arg_0_0)
member this.Mem3 (arg_0_0, arg_0_1) = this.Mem3 (arg_0_0, arg_0_1)
namespace SomeNamespace
open System
/// Mock record type for an interface
type public PublicTypeInternalFalseNoAttrMock =
{
Mem1 : string * int -> string list
Mem2 : string -> int
Mem3 : int * option<System.Threading.CancellationToken> -> string
}
/// An implementation where every method throws.
static member Empty : PublicTypeInternalFalseNoAttrMock =
{
Mem1 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem1"))
Mem2 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem2"))
Mem3 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem3"))
}
interface IPublicTypeInternalFalseNoAttr with
member this.Mem1 (arg_0_0, arg_0_1) = this.Mem1 (arg_0_0, arg_0_1)
member this.Mem2 arg_0_0 = this.Mem2 (arg_0_0)
member this.Mem3 (arg_0_0, arg_0_1) = this.Mem3 (arg_0_0, arg_0_1)
namespace SomeNamespace
open System
/// Mock record type for an interface
type internal InternalTypeNoAttrMock =
{
Mem1 : string * int -> unit
Mem2 : string -> int
}
/// An implementation where every method throws.
static member Empty : InternalTypeNoAttrMock =
{
Mem1 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem1"))
Mem2 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem2"))
}
interface InternalTypeNoAttr with
member this.Mem1 (arg_0_0, arg_0_1) = this.Mem1 (arg_0_0, arg_0_1)
member this.Mem2 arg_0_0 = this.Mem2 (arg_0_0)
namespace SomeNamespace
open System
/// Mock record type for an interface
type private PrivateTypeNoAttrMock =
{
Mem1 : string * int -> unit
Mem2 : string -> int
}
/// An implementation where every method throws.
static member Empty : PrivateTypeNoAttrMock =
{
Mem1 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem1"))
Mem2 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem2"))
}
interface PrivateTypeNoAttr with
member this.Mem1 (arg_0_0, arg_0_1) = this.Mem1 (arg_0_0, arg_0_1)
member this.Mem2 arg_0_0 = this.Mem2 (arg_0_0)
namespace SomeNamespace
open System
/// Mock record type for an interface
type private PrivateTypeInternalFalseNoAttrMock =
{
Mem1 : string * int -> unit
Mem2 : string -> int
}
/// An implementation where every method throws.
static member Empty : PrivateTypeInternalFalseNoAttrMock =
{
Mem1 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem1"))
Mem2 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem2"))
}
interface PrivateTypeInternalFalseNoAttr with
member this.Mem1 (arg_0_0, arg_0_1) = this.Mem1 (arg_0_0, arg_0_1)
member this.Mem2 arg_0_0 = this.Mem2 (arg_0_0)
namespace SomeNamespace
open System
/// Mock record type for an interface
type internal VeryPublicTypeNoAttrMock<'a, 'b> =
{
Mem1 : 'a -> 'b
}
/// An implementation where every method throws.
static member Empty () : VeryPublicTypeNoAttrMock<'a, 'b> =
{
Mem1 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem1"))
}
interface VeryPublicTypeNoAttr<'a, 'b> with
member this.Mem1 arg_0_0 = this.Mem1 (arg_0_0)
namespace SomeNamespace
open System
/// Mock record type for an interface
type internal CurriedNoAttrMock<'a> =
{
Mem1 : int -> 'a -> string
Mem2 : int * string -> 'a -> string
Mem3 : (int * string) -> 'a -> string
Mem4 : (int * string) -> ('a * int) -> string
Mem5 : int * string -> ('a * int) -> string
Mem6 : int * string -> 'a * int -> string
}
/// An implementation where every method throws.
static member Empty () : CurriedNoAttrMock<'a> =
{
Mem1 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem1"))
Mem2 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem2"))
Mem3 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem3"))
Mem4 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem4"))
Mem5 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem5"))
Mem6 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem6"))
}
interface CurriedNoAttr<'a> with
member this.Mem1 arg_0_0 arg_1_0 = this.Mem1 (arg_0_0) (arg_1_0)
member this.Mem2 (arg_0_0, arg_0_1) arg_1_0 = this.Mem2 (arg_0_0, arg_0_1) (arg_1_0)
member this.Mem3 ((arg_0_0, arg_0_1)) arg_1_0 = this.Mem3 (arg_0_0, arg_0_1) (arg_1_0)
member this.Mem4 ((arg_0_0, arg_0_1)) ((arg_1_0, arg_1_1)) =
this.Mem4 (arg_0_0, arg_0_1) (arg_1_0, arg_1_1)
member this.Mem5 (arg_0_0, arg_0_1) ((arg_1_0, arg_1_1)) =
this.Mem5 (arg_0_0, arg_0_1) (arg_1_0, arg_1_1)
member this.Mem6 (arg_0_0, arg_0_1) (arg_1_0, arg_1_1) =
this.Mem6 (arg_0_0, arg_0_1) (arg_1_0, arg_1_1)
namespace SomeNamespace
open System
/// Mock record type for an interface
type internal TypeWithInterfaceNoAttrMock =
{
/// Implementation of IDisposable.Dispose
Dispose : unit -> unit
Mem1 : string option -> string[] Async
Mem2 : unit -> string[] Async
}
/// An implementation where every method throws.
static member Empty : TypeWithInterfaceNoAttrMock =
{
Dispose = (fun () -> ())
Mem1 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem1"))
Mem2 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem2"))
}
interface TypeWithInterfaceNoAttr with
member this.Mem1 arg_0_0 = this.Mem1 (arg_0_0)
member this.Mem2 () = this.Mem2 (())
interface System.IDisposable with
member this.Dispose () : unit = this.Dispose ()

View File

@@ -20,21 +20,26 @@ module MemberJsonSerializeExtension =
let node = System.Text.Json.Nodes.JsonObject ()
do
node.Add ("id", System.Text.Json.Nodes.JsonValue.Create<int> input.Id)
node.Add ("compoundMemberId", System.Text.Json.Nodes.JsonValue.Create<string> input.CompoundMemberId)
node.Add ("firstName", System.Text.Json.Nodes.JsonValue.Create<string> input.FirstName)
node.Add ("lastName", System.Text.Json.Nodes.JsonValue.Create<string> input.LastName)
node.Add ("homeGymId", System.Text.Json.Nodes.JsonValue.Create<int> input.HomeGymId)
node.Add ("homeGymName", System.Text.Json.Nodes.JsonValue.Create<string> input.HomeGymName)
node.Add ("emailAddress", System.Text.Json.Nodes.JsonValue.Create<string> input.EmailAddress)
node.Add ("gymAccessPin", System.Text.Json.Nodes.JsonValue.Create<string> input.GymAccessPin)
node.Add ("dateofBirth", System.Text.Json.Nodes.JsonValue.Create<DateOnly> input.DateOfBirth)
node.Add ("mobileNumber", System.Text.Json.Nodes.JsonValue.Create<string> input.MobileNumber)
node.Add ("postCode", System.Text.Json.Nodes.JsonValue.Create<string> input.Postcode)
node.Add ("membershipName", System.Text.Json.Nodes.JsonValue.Create<string> input.MembershipName)
node.Add ("membershipLevel", System.Text.Json.Nodes.JsonValue.Create<int> input.MembershipLevel)
node.Add ("suspendedReason", System.Text.Json.Nodes.JsonValue.Create<int> input.SuspendedReason)
node.Add ("memberStatus", System.Text.Json.Nodes.JsonValue.Create<int> input.MemberStatus)
node.Add ("id", (input.Id |> System.Text.Json.Nodes.JsonValue.Create<int>))
node.Add (
"compoundMemberId",
(input.CompoundMemberId |> System.Text.Json.Nodes.JsonValue.Create<string>)
)
node.Add ("firstName", (input.FirstName |> System.Text.Json.Nodes.JsonValue.Create<string>))
node.Add ("lastName", (input.LastName |> System.Text.Json.Nodes.JsonValue.Create<string>))
node.Add ("homeGymId", (input.HomeGymId |> System.Text.Json.Nodes.JsonValue.Create<int>))
node.Add ("homeGymName", (input.HomeGymName |> System.Text.Json.Nodes.JsonValue.Create<string>))
node.Add ("emailAddress", (input.EmailAddress |> System.Text.Json.Nodes.JsonValue.Create<string>))
node.Add ("gymAccessPin", (input.GymAccessPin |> System.Text.Json.Nodes.JsonValue.Create<string>))
node.Add ("dateofBirth", (input.DateOfBirth |> System.Text.Json.Nodes.JsonValue.Create<DateOnly>))
node.Add ("mobileNumber", (input.MobileNumber |> System.Text.Json.Nodes.JsonValue.Create<string>))
node.Add ("postCode", (input.Postcode |> System.Text.Json.Nodes.JsonValue.Create<string>))
node.Add ("membershipName", (input.MembershipName |> System.Text.Json.Nodes.JsonValue.Create<string>))
node.Add ("membershipLevel", (input.MembershipLevel |> System.Text.Json.Nodes.JsonValue.Create<int>))
node.Add ("suspendedReason", (input.SuspendedReason |> System.Text.Json.Nodes.JsonValue.Create<int>))
node.Add ("memberStatus", (input.MemberStatus |> System.Text.Json.Nodes.JsonValue.Create<int>))
node :> _
@@ -55,7 +60,11 @@ module GymOpeningHours =
)
| v -> v)
.AsArray ()
|> Seq.map (fun elt -> elt.AsValue().GetValue<string> ())
|> Seq.map (fun elt ->
(match elt with
| null -> raise (System.ArgumentNullException ())
| elt -> elt.AsValue().GetValue<System.String> ())
)
|> List.ofSeq
let arg_0 =
@@ -68,7 +77,7 @@ module GymOpeningHours =
)
| v -> v)
.AsValue()
.GetValue<bool> ()
.GetValue<System.Boolean> ()
{
IsAlwaysOpen = arg_0
@@ -91,7 +100,7 @@ module GymAccessOptions =
)
| v -> v)
.AsValue()
.GetValue<bool> ()
.GetValue<System.Boolean> ()
let arg_0 =
(match node.["pinAccess"] with
@@ -103,7 +112,7 @@ module GymAccessOptions =
)
| v -> v)
.AsValue()
.GetValue<bool> ()
.GetValue<System.Boolean> ()
{
PinAccess = arg_0
@@ -127,7 +136,7 @@ module GymLocation =
)
| v -> v)
.AsValue()
.GetValue<float> ()
.GetValue<System.Double> ()
with :? System.InvalidOperationException as exc ->
if exc.Message.Contains "cannot be converted to" then
if
@@ -148,6 +157,7 @@ module GymLocation =
reraise ()
else
reraise ()
|> LanguagePrimitives.FloatWithMeasure
let arg_0 =
try
@@ -160,7 +170,7 @@ module GymLocation =
)
| v -> v)
.AsValue()
.GetValue<float> ()
.GetValue<System.Double> ()
with :? System.InvalidOperationException as exc ->
if exc.Message.Contains "cannot be converted to" then
if
@@ -203,12 +213,12 @@ module GymAddress =
)
| v -> v)
.AsValue()
.GetValue<string> ()
.GetValue<System.String> ()
let arg_4 =
match node.["county"] with
| null -> None
| v -> v.AsValue().GetValue<string> () |> Some
| v -> v.AsValue().GetValue<System.String> () |> Some
let arg_3 =
(match node.["town"] with
@@ -220,17 +230,17 @@ module GymAddress =
)
| v -> v)
.AsValue()
.GetValue<string> ()
.GetValue<System.String> ()
let arg_2 =
match node.["addressLine3"] with
| null -> None
| v -> v.AsValue().GetValue<string> () |> Some
| v -> v.AsValue().GetValue<System.String> () |> Some
let arg_1 =
match node.["addressLine2"] with
| null -> None
| v -> v.AsValue().GetValue<string> () |> Some
| v -> v.AsValue().GetValue<System.String> () |> Some
let arg_0 =
(match node.["addressLine1"] with
@@ -242,7 +252,7 @@ module GymAddress =
)
| v -> v)
.AsValue()
.GetValue<string> ()
.GetValue<System.String> ()
{
AddressLine1 = arg_0
@@ -269,7 +279,7 @@ module Gym =
)
| v -> v)
.AsValue()
.GetValue<string> ()
.GetValue<System.String> ()
let arg_9 =
(match node.["timeZone"] with
@@ -281,7 +291,7 @@ module Gym =
)
| v -> v)
.AsValue()
.GetValue<string> ()
.GetValue<System.String> ()
let arg_8 =
GymLocation.jsonParse (
@@ -329,7 +339,7 @@ module Gym =
)
| v -> v)
.AsValue()
.GetValue<string> ()
.GetValue<System.String> ()
let arg_4 =
(match node.["phoneNumber"] with
@@ -341,7 +351,7 @@ module Gym =
)
| v -> v)
.AsValue()
.GetValue<string> ()
.GetValue<System.String> ()
let arg_3 =
GymAddress.jsonParse (
@@ -365,7 +375,7 @@ module Gym =
)
| v -> v)
.AsValue()
.GetValue<int> ()
.GetValue<System.Int32> ()
let arg_1 =
(match node.["id"] with
@@ -377,7 +387,7 @@ module Gym =
)
| v -> v)
.AsValue()
.GetValue<int> ()
.GetValue<System.Int32> ()
let arg_0 =
(match node.["name"] with
@@ -389,7 +399,7 @@ module Gym =
)
| v -> v)
.AsValue()
.GetValue<string> ()
.GetValue<System.String> ()
{
Name = arg_0
@@ -424,7 +434,7 @@ module MemberJsonParseExtension =
)
| v -> v)
.AsValue()
.GetValue<int> ()
.GetValue<System.Int32> ()
let arg_13 =
(match node.["suspendedReason"] with
@@ -436,7 +446,7 @@ module MemberJsonParseExtension =
)
| v -> v)
.AsValue()
.GetValue<int> ()
.GetValue<System.Int32> ()
let arg_12 =
(match node.["membershipLevel"] with
@@ -448,7 +458,7 @@ module MemberJsonParseExtension =
)
| v -> v)
.AsValue()
.GetValue<int> ()
.GetValue<System.Int32> ()
let arg_11 =
(match node.["membershipName"] with
@@ -460,7 +470,7 @@ module MemberJsonParseExtension =
)
| v -> v)
.AsValue()
.GetValue<string> ()
.GetValue<System.String> ()
let arg_10 =
(match node.["postCode"] with
@@ -472,7 +482,7 @@ module MemberJsonParseExtension =
)
| v -> v)
.AsValue()
.GetValue<string> ()
.GetValue<System.String> ()
let arg_9 =
(match node.["mobileNumber"] with
@@ -484,7 +494,7 @@ module MemberJsonParseExtension =
)
| v -> v)
.AsValue()
.GetValue<string> ()
.GetValue<System.String> ()
let arg_8 =
(match node.["dateofBirth"] with
@@ -509,7 +519,7 @@ module MemberJsonParseExtension =
)
| v -> v)
.AsValue()
.GetValue<string> ()
.GetValue<System.String> ()
let arg_6 =
(match node.["emailAddress"] with
@@ -521,7 +531,7 @@ module MemberJsonParseExtension =
)
| v -> v)
.AsValue()
.GetValue<string> ()
.GetValue<System.String> ()
let arg_5 =
(match node.["homeGymName"] with
@@ -533,7 +543,7 @@ module MemberJsonParseExtension =
)
| v -> v)
.AsValue()
.GetValue<string> ()
.GetValue<System.String> ()
let arg_4 =
(match node.["homeGymId"] with
@@ -545,7 +555,7 @@ module MemberJsonParseExtension =
)
| v -> v)
.AsValue()
.GetValue<int> ()
.GetValue<System.Int32> ()
let arg_3 =
(match node.["lastName"] with
@@ -557,7 +567,7 @@ module MemberJsonParseExtension =
)
| v -> v)
.AsValue()
.GetValue<string> ()
.GetValue<System.String> ()
let arg_2 =
(match node.["firstName"] with
@@ -569,7 +579,7 @@ module MemberJsonParseExtension =
)
| v -> v)
.AsValue()
.GetValue<string> ()
.GetValue<System.String> ()
let arg_1 =
(match node.["compoundMemberId"] with
@@ -581,7 +591,7 @@ module MemberJsonParseExtension =
)
| v -> v)
.AsValue()
.GetValue<string> ()
.GetValue<System.String> ()
let arg_0 =
(match node.["id"] with
@@ -593,7 +603,7 @@ module MemberJsonParseExtension =
)
| v -> v)
.AsValue()
.GetValue<int> ()
.GetValue<System.Int32> ()
{
Id = arg_0
@@ -629,7 +639,7 @@ module GymAttendance =
)
| v -> v)
.AsValue()
.GetValue<int> ()
.GetValue<System.Int32> ()
let arg_7 =
(match node.["lastRefreshedPeopleInClasses"] with
@@ -680,12 +690,12 @@ module GymAttendance =
)
| v -> v)
.AsValue()
.GetValue<bool> ()
.GetValue<System.Boolean> ()
let arg_3 =
match node.["totalPeopleSuffix"] with
| null -> None
| v -> v.AsValue().GetValue<string> () |> Some
| v -> v.AsValue().GetValue<System.String> () |> Some
let arg_2 =
(match node.["totalPeopleInClasses"] with
@@ -697,7 +707,7 @@ module GymAttendance =
)
| v -> v)
.AsValue()
.GetValue<int> ()
.GetValue<System.Int32> ()
let arg_1 =
(match node.["totalPeopleInGym"] with
@@ -709,7 +719,7 @@ module GymAttendance =
)
| v -> v)
.AsValue()
.GetValue<int> ()
.GetValue<System.Int32> ()
let arg_0 =
(match node.["description"] with
@@ -721,7 +731,7 @@ module GymAttendance =
)
| v -> v)
.AsValue()
.GetValue<string> ()
.GetValue<System.String> ()
{
Description = arg_0
@@ -764,7 +774,7 @@ module MemberActivityDto =
)
| v -> v)
.AsValue()
.GetValue<bool> ()
.GetValue<System.Boolean> ()
let arg_3 =
(match node.["totalClasses"] with
@@ -776,7 +786,7 @@ module MemberActivityDto =
)
| v -> v)
.AsValue()
.GetValue<int> ()
.GetValue<System.Int32> ()
let arg_2 =
(match node.["totalVisits"] with
@@ -788,7 +798,7 @@ module MemberActivityDto =
)
| v -> v)
.AsValue()
.GetValue<int> ()
.GetValue<System.Int32> ()
let arg_1 =
(match node.["averageDuration"] with
@@ -800,7 +810,7 @@ module MemberActivityDto =
)
| v -> v)
.AsValue()
.GetValue<int> ()
.GetValue<System.Int32> ()
let arg_0 =
(match node.["totalDuration"] with
@@ -812,7 +822,7 @@ module MemberActivityDto =
)
| v -> v)
.AsValue()
.GetValue<int> ()
.GetValue<System.Int32> ()
{
TotalDuration = arg_0
@@ -839,7 +849,7 @@ module SessionsAggregate =
)
| v -> v)
.AsValue()
.GetValue<int> ()
.GetValue<System.Int32> ()
let arg_1 =
(match node.["Visits"] with
@@ -851,7 +861,7 @@ module SessionsAggregate =
)
| v -> v)
.AsValue()
.GetValue<int> ()
.GetValue<System.Int32> ()
let arg_0 =
(match node.["Activities"] with
@@ -863,7 +873,7 @@ module SessionsAggregate =
)
| v -> v)
.AsValue()
.GetValue<int> ()
.GetValue<System.Int32> ()
{
Activities = arg_0
@@ -887,7 +897,7 @@ module VisitGym =
)
| v -> v)
.AsValue()
.GetValue<string> ()
.GetValue<System.String> ()
let arg_1 =
(match node.["Name"] with
@@ -899,7 +909,7 @@ module VisitGym =
)
| v -> v)
.AsValue()
.GetValue<string> ()
.GetValue<System.String> ()
let arg_0 =
(match node.["Id"] with
@@ -911,7 +921,7 @@ module VisitGym =
)
| v -> v)
.AsValue()
.GetValue<int> ()
.GetValue<System.Int32> ()
{
Id = arg_0
@@ -947,7 +957,7 @@ module Visit =
)
| v -> v)
.AsValue()
.GetValue<int> ()
.GetValue<System.Int32> ()
let arg_1 =
(match node.["StartTime"] with
@@ -972,7 +982,7 @@ module Visit =
)
| v -> v)
.AsValue()
.GetValue<bool> ()
.GetValue<System.Boolean> ()
{
IsDurationEstimated = arg_0
@@ -1032,7 +1042,11 @@ module Sessions =
)
| v -> v)
.AsArray ()
|> Seq.map (fun elt -> Visit.jsonParse elt)
|> Seq.map (fun elt ->
(match elt with
| null -> raise (System.ArgumentNullException ())
| elt -> Visit.jsonParse elt)
)
|> List.ofSeq
let arg_0 =

View File

@@ -29,7 +29,7 @@ module PureGymApi =
let uri =
System.Uri (
(match client.BaseAddress with
| null -> System.Uri "https://whatnot.com"
| null -> System.Uri "https://whatnot.com/"
| v -> v),
System.Uri (("v1/gyms/"), System.UriKind.Relative)
)
@@ -48,7 +48,14 @@ module PureGymApi =
System.Text.Json.Nodes.JsonNode.ParseAsync (responseStream, cancellationToken = ct)
|> Async.AwaitTask
return jsonNode.AsArray () |> Seq.map (fun elt -> Gym.jsonParse elt) |> List.ofSeq
return
jsonNode.AsArray ()
|> Seq.map (fun elt ->
(match elt with
| null -> raise (System.ArgumentNullException ())
| elt -> Gym.jsonParse elt)
)
|> List.ofSeq
}
|> (fun a -> Async.StartAsTask (a, ?cancellationToken = ct))
@@ -59,11 +66,11 @@ module PureGymApi =
let uri =
System.Uri (
(match client.BaseAddress with
| null -> System.Uri "https://whatnot.com"
| null -> System.Uri "https://whatnot.com/"
| v -> v),
System.Uri (
"v1/gyms/{gym_id}/attendance"
.Replace ("{gym_id}", gymId.ToString () |> System.Web.HttpUtility.UrlEncode),
.Replace ("{gym_id}", gymId.ToString () |> System.Uri.EscapeDataString),
System.UriKind.Relative
)
)
@@ -93,11 +100,11 @@ module PureGymApi =
let uri =
System.Uri (
(match client.BaseAddress with
| null -> System.Uri "https://whatnot.com"
| null -> System.Uri "https://whatnot.com/"
| v -> v),
System.Uri (
"v1/gyms/{gym_id}/attendance"
.Replace ("{gym_id}", gymId.ToString () |> System.Web.HttpUtility.UrlEncode),
.Replace ("{gym_id}", gymId.ToString () |> System.Uri.EscapeDataString),
System.UriKind.Relative
)
)
@@ -127,7 +134,7 @@ module PureGymApi =
let uri =
System.Uri (
(match client.BaseAddress with
| null -> System.Uri "https://whatnot.com"
| null -> System.Uri "https://whatnot.com/"
| v -> v),
System.Uri ("v1/member", System.UriKind.Relative)
)
@@ -157,11 +164,10 @@ module PureGymApi =
let uri =
System.Uri (
(match client.BaseAddress with
| null -> System.Uri "https://whatnot.com"
| null -> System.Uri "https://whatnot.com/"
| v -> v),
System.Uri (
"v1/gyms/{gym}"
.Replace ("{gym}", gym.ToString () |> System.Web.HttpUtility.UrlEncode),
"v1/gyms/{gym}".Replace ("{gym}", gym.ToString () |> System.Uri.EscapeDataString),
System.UriKind.Relative
)
)
@@ -191,7 +197,7 @@ module PureGymApi =
let uri =
System.Uri (
(match client.BaseAddress with
| null -> System.Uri "https://whatnot.com"
| null -> System.Uri "https://whatnot.com/"
| v -> v),
System.Uri ("v1/member/activity", System.UriKind.Relative)
)
@@ -221,7 +227,7 @@ module PureGymApi =
let uri =
System.Uri (
(match client.BaseAddress with
| null -> System.Uri "https://whatnot.com"
| null -> System.Uri "https://whatnot.com/"
| v -> v),
System.Uri ("some/url", System.UriKind.Relative)
)
@@ -251,7 +257,7 @@ module PureGymApi =
let uri =
System.Uri (
(match client.BaseAddress with
| null -> System.Uri "https://whatnot.com"
| null -> System.Uri "https://whatnot.com/"
| v -> v),
System.Uri ("some/url", System.UriKind.Relative)
)
@@ -302,7 +308,7 @@ module PureGymApi =
v.AsObject ()
|> Seq.map (fun kvp ->
let key = (kvp.Key)
let value = (kvp.Value).AsValue().GetValue<string> ()
let value = (kvp.Value).AsValue().GetValue<System.String> ()
key, value
)
|> Map.ofSeq
@@ -317,7 +323,7 @@ module PureGymApi =
let uri =
System.Uri (
(match client.BaseAddress with
| null -> System.Uri "https://whatnot.com"
| null -> System.Uri "https://whatnot.com/"
| v -> v),
System.Uri (
("/v2/gymSessions/member"
@@ -326,9 +332,9 @@ module PureGymApi =
else
"?")
+ "fromDate="
+ ((fromDate.ToString "yyyy-MM-dd") |> System.Web.HttpUtility.UrlEncode)
+ ((fromDate.ToString "yyyy-MM-dd") |> System.Uri.EscapeDataString)
+ "&toDate="
+ ((toDate.ToString "yyyy-MM-dd") |> System.Web.HttpUtility.UrlEncode)),
+ ((toDate.ToString "yyyy-MM-dd") |> System.Uri.EscapeDataString)),
System.UriKind.Relative
)
)
@@ -358,7 +364,7 @@ module PureGymApi =
let uri =
System.Uri (
(match client.BaseAddress with
| null -> System.Uri "https://whatnot.com"
| null -> System.Uri "https://whatnot.com/"
| v -> v),
System.Uri (
("/v2/gymSessions/member?foo=1"
@@ -367,9 +373,9 @@ module PureGymApi =
else
"?")
+ "fromDate="
+ ((fromDate.ToString "yyyy-MM-dd") |> System.Web.HttpUtility.UrlEncode)
+ ((fromDate.ToString "yyyy-MM-dd") |> System.Uri.EscapeDataString)
+ "&toDate="
+ ((toDate.ToString "yyyy-MM-dd") |> System.Web.HttpUtility.UrlEncode)),
+ ((toDate.ToString "yyyy-MM-dd") |> System.Uri.EscapeDataString)),
System.UriKind.Relative
)
)
@@ -399,7 +405,7 @@ module PureGymApi =
let uri =
System.Uri (
(match client.BaseAddress with
| null -> System.Uri "https://whatnot.com"
| null -> System.Uri "https://whatnot.com/"
| v -> v),
System.Uri ("users/new", System.UriKind.Relative)
)
@@ -426,7 +432,7 @@ module PureGymApi =
let uri =
System.Uri (
(match client.BaseAddress with
| null -> System.Uri "https://whatnot.com"
| null -> System.Uri "https://whatnot.com/"
| v -> v),
System.Uri ("users/new", System.UriKind.Relative)
)
@@ -453,7 +459,7 @@ module PureGymApi =
let uri =
System.Uri (
(match client.BaseAddress with
| null -> System.Uri "https://whatnot.com"
| null -> System.Uri "https://whatnot.com/"
| v -> v),
System.Uri ("users/new", System.UriKind.Relative)
)
@@ -480,7 +486,7 @@ module PureGymApi =
let uri =
System.Uri (
(match client.BaseAddress with
| null -> System.Uri "https://whatnot.com"
| null -> System.Uri "https://whatnot.com/"
| v -> v),
System.Uri ("users/new", System.UriKind.Relative)
)
@@ -507,7 +513,7 @@ module PureGymApi =
let uri =
System.Uri (
(match client.BaseAddress with
| null -> System.Uri "https://whatnot.com"
| null -> System.Uri "https://whatnot.com/"
| v -> v),
System.Uri ("users/new", System.UriKind.Relative)
)
@@ -534,7 +540,7 @@ module PureGymApi =
let uri =
System.Uri (
(match client.BaseAddress with
| null -> System.Uri "https://whatnot.com"
| null -> System.Uri "https://whatnot.com/"
| v -> v),
System.Uri ("users/new", System.UriKind.Relative)
)
@@ -567,7 +573,7 @@ module PureGymApi =
let uri =
System.Uri (
(match client.BaseAddress with
| null -> System.Uri "https://whatnot.com"
| null -> System.Uri "https://whatnot.com/"
| v -> v),
System.Uri ("users/new", System.UriKind.Relative)
)
@@ -600,7 +606,7 @@ module PureGymApi =
let uri =
System.Uri (
(match client.BaseAddress with
| null -> System.Uri "https://whatnot.com"
| null -> System.Uri "https://whatnot.com/"
| v -> v),
System.Uri ("users/new", System.UriKind.Relative)
)
@@ -633,7 +639,7 @@ module PureGymApi =
let uri =
System.Uri (
(match client.BaseAddress with
| null -> System.Uri "https://whatnot.com"
| null -> System.Uri "https://whatnot.com/"
| v -> v),
System.Uri ("users/new", System.UriKind.Relative)
)
@@ -659,11 +665,11 @@ module PureGymApi =
let uri =
System.Uri (
(match client.BaseAddress with
| null -> System.Uri "https://whatnot.com"
| null -> System.Uri "https://whatnot.com/"
| v -> v),
System.Uri (
"endpoint/{param}"
.Replace ("{param}", parameter.ToString () |> System.Web.HttpUtility.UrlEncode),
.Replace ("{param}", parameter.ToString () |> System.Uri.EscapeDataString),
System.UriKind.Relative
)
)
@@ -688,7 +694,7 @@ module PureGymApi =
let uri =
System.Uri (
(match client.BaseAddress with
| null -> System.Uri "https://whatnot.com"
| null -> System.Uri "https://whatnot.com/"
| v -> v),
System.Uri ("endpoint", System.UriKind.Relative)
)
@@ -713,7 +719,7 @@ module PureGymApi =
let uri =
System.Uri (
(match client.BaseAddress with
| null -> System.Uri "https://whatnot.com"
| null -> System.Uri "https://whatnot.com/"
| v -> v),
System.Uri ("endpoint", System.UriKind.Relative)
)
@@ -738,7 +744,7 @@ module PureGymApi =
let uri =
System.Uri (
(match client.BaseAddress with
| null -> System.Uri "https://whatnot.com"
| null -> System.Uri "https://whatnot.com/"
| v -> v),
System.Uri ("endpoint", System.UriKind.Relative)
)
@@ -763,7 +769,7 @@ module PureGymApi =
let uri =
System.Uri (
(match client.BaseAddress with
| null -> System.Uri "https://whatnot.com"
| null -> System.Uri "https://whatnot.com/"
| v -> v),
System.Uri ("endpoint", System.UriKind.Relative)
)
@@ -787,7 +793,7 @@ module PureGymApi =
let uri =
System.Uri (
(match client.BaseAddress with
| null -> System.Uri "https://whatnot.com"
| null -> System.Uri "https://whatnot.com/"
| v -> v),
System.Uri ("endpoint", System.UriKind.Relative)
)
@@ -811,7 +817,7 @@ module PureGymApi =
let uri =
System.Uri (
(match client.BaseAddress with
| null -> System.Uri "https://whatnot.com"
| null -> System.Uri "https://whatnot.com/"
| v -> v),
System.Uri ("endpoint", System.UriKind.Relative)
)
@@ -835,7 +841,7 @@ module PureGymApi =
let uri =
System.Uri (
(match client.BaseAddress with
| null -> System.Uri "https://whatnot.com"
| null -> System.Uri "https://whatnot.com/"
| v -> v),
System.Uri ("endpoint", System.UriKind.Relative)
)
@@ -859,7 +865,7 @@ module PureGymApi =
let uri =
System.Uri (
(match client.BaseAddress with
| null -> System.Uri "https://whatnot.com"
| null -> System.Uri "https://whatnot.com/"
| v -> v),
System.Uri ("endpoint", System.UriKind.Relative)
)
@@ -895,7 +901,7 @@ module PureGymApi =
let uri =
System.Uri (
(match client.BaseAddress with
| null -> System.Uri "https://whatnot.com"
| null -> System.Uri "https://whatnot.com/"
| v -> v),
System.Uri ("endpoint", System.UriKind.Relative)
)
@@ -931,7 +937,7 @@ module PureGymApi =
let uri =
System.Uri (
(match client.BaseAddress with
| null -> System.Uri "https://whatnot.com"
| null -> System.Uri "https://whatnot.com/"
| v -> v),
System.Uri ("endpoint", System.UriKind.Relative)
)
@@ -967,7 +973,7 @@ module PureGymApi =
let uri =
System.Uri (
(match client.BaseAddress with
| null -> System.Uri "https://whatnot.com"
| null -> System.Uri "https://whatnot.com/"
| v -> v),
System.Uri ("endpoint", System.UriKind.Relative)
)
@@ -1003,7 +1009,7 @@ module PureGymApi =
let uri =
System.Uri (
(match client.BaseAddress with
| null -> System.Uri "https://whatnot.com"
| null -> System.Uri "https://whatnot.com/"
| v -> v),
System.Uri ("endpoint", System.UriKind.Relative)
)
@@ -1026,7 +1032,7 @@ module PureGymApi =
let uri =
System.Uri (
(match client.BaseAddress with
| null -> System.Uri "https://whatnot.com"
| null -> System.Uri "https://whatnot.com/"
| v -> v),
System.Uri ("endpoint", System.UriKind.Relative)
)
@@ -1076,7 +1082,7 @@ module internal ApiWithoutBaseAddress =
| v -> v),
System.Uri (
"endpoint/{param}"
.Replace ("{param}", parameter.ToString () |> System.Web.HttpUtility.UrlEncode),
.Replace ("{param}", parameter.ToString () |> System.Uri.EscapeDataString),
System.UriKind.Relative
)
)
@@ -1116,18 +1122,21 @@ module ApiWithBasePath =
let uri =
System.Uri (
(match client.BaseAddress with
| null ->
raise (
System.ArgumentNullException (
nameof (client.BaseAddress),
"No base address was supplied on the type, and no BaseAddress was on the HttpClient."
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),
| v -> v),
System.Uri ("foo/", System.UriKind.Relative)
),
System.Uri (
"endpoint/{param}"
.Replace ("{param}", parameter.ToString () |> System.Web.HttpUtility.UrlEncode),
.Replace ("{param}", parameter.ToString () |> System.Uri.EscapeDataString),
System.UriKind.Relative
)
)
@@ -1167,12 +1176,321 @@ module ApiWithBasePathAndAddress =
let uri =
System.Uri (
(match client.BaseAddress with
| null -> System.Uri "https://whatnot.com"
| v -> v),
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),
.Replace ("{param}", parameter.ToString () |> System.Uri.EscapeDataString),
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 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.Uri.EscapeDataString),
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.Uri.EscapeDataString),
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.Uri.EscapeDataString),
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.Uri.EscapeDataString),
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.Uri.EscapeDataString),
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.Uri.EscapeDataString),
System.UriKind.Relative
)
)
@@ -1231,7 +1549,7 @@ module ApiWithHeaders =
| v -> v),
System.Uri (
"endpoint/{param}"
.Replace ("{param}", parameter.ToString () |> System.Web.HttpUtility.UrlEncode),
.Replace ("{param}", parameter.ToString () |> System.Uri.EscapeDataString),
System.UriKind.Relative
)
)
@@ -1245,6 +1563,7 @@ module ApiWithHeaders =
do httpMessage.Headers.Add ("X-Foo", this.SomeHeader.ToString ())
do httpMessage.Headers.Add ("Authorization", this.SomeOtherHeader.ToString ())
do httpMessage.Headers.Add ("Header-Name", "Header-Value")
do httpMessage.Headers.Add ("Something-Else", "val")
let! response = client.SendAsync (httpMessage, ct) |> Async.AwaitTask
let response = response.EnsureSuccessStatusCode ()
let! responseString = response.Content.ReadAsStringAsync ct |> Async.AwaitTask
@@ -1293,7 +1612,7 @@ module ApiWithHeaders2 =
| v -> v),
System.Uri (
"endpoint/{param}"
.Replace ("{param}", parameter.ToString () |> System.Web.HttpUtility.UrlEncode),
.Replace ("{param}", parameter.ToString () |> System.Uri.EscapeDataString),
System.UriKind.Relative
)
)

View File

@@ -21,69 +21,69 @@ module InnerTypeWithBothJsonSerializeExtension =
let node = System.Text.Json.Nodes.JsonObject ()
do
node.Add (("it's-a-me"), System.Text.Json.Nodes.JsonValue.Create<Guid> input.Thing)
node.Add (("it's-a-me"), (input.Thing |> System.Text.Json.Nodes.JsonValue.Create<Guid>))
node.Add (
"map",
(fun field ->
let ret = System.Text.Json.Nodes.JsonObject ()
(input.Map
|> (fun field ->
let ret = System.Text.Json.Nodes.JsonObject ()
for (KeyValue (key, value)) in field do
ret.Add (key.ToString (), System.Text.Json.Nodes.JsonValue.Create<Uri> value)
for (KeyValue (key, value)) in field do
ret.Add (key.ToString (), System.Text.Json.Nodes.JsonValue.Create<Uri> value)
ret
)
input.Map
ret
))
)
node.Add (
"readOnlyDict",
(fun field ->
let ret = System.Text.Json.Nodes.JsonObject ()
(input.ReadOnlyDict
|> (fun field ->
let ret = System.Text.Json.Nodes.JsonObject ()
for (KeyValue (key, value)) in field do
ret.Add (
key.ToString (),
(fun field ->
let arr = System.Text.Json.Nodes.JsonArray ()
for (KeyValue (key, value)) in field do
ret.Add (
key.ToString (),
(fun field ->
let arr = System.Text.Json.Nodes.JsonArray ()
for mem in field do
arr.Add (System.Text.Json.Nodes.JsonValue.Create<char> mem)
for mem in field do
arr.Add (System.Text.Json.Nodes.JsonValue.Create<char> mem)
arr
)
value
)
arr
)
value
)
ret
)
input.ReadOnlyDict
ret
))
)
node.Add (
"dict",
(fun field ->
let ret = System.Text.Json.Nodes.JsonObject ()
(input.Dict
|> (fun field ->
let ret = System.Text.Json.Nodes.JsonObject ()
for (KeyValue (key, value)) in field do
ret.Add (key.ToString (), System.Text.Json.Nodes.JsonValue.Create<bool> value)
for (KeyValue (key, value)) in field do
ret.Add (key.ToString (), System.Text.Json.Nodes.JsonValue.Create<bool> value)
ret
)
input.Dict
ret
))
)
node.Add (
"concreteDict",
(fun field ->
let ret = System.Text.Json.Nodes.JsonObject ()
(input.ConcreteDict
|> (fun field ->
let ret = System.Text.Json.Nodes.JsonObject ()
for (KeyValue (key, value)) in field do
ret.Add (key.ToString (), InnerTypeWithBoth.toJsonNode value)
for (KeyValue (key, value)) in field do
ret.Add (key.ToString (), InnerTypeWithBoth.toJsonNode value)
ret
)
input.ConcreteDict
ret
))
)
node :> _
@@ -93,6 +93,24 @@ open System
open System.Collections.Generic
open System.Text.Json.Serialization
/// Module containing JSON serializing extension members for the SomeEnum type
[<AutoOpen>]
module SomeEnumJsonSerializeExtension =
/// Extension methods for JSON parsing
type SomeEnum with
/// Serialize to a JSON node
static member toJsonNode (input : SomeEnum) : System.Text.Json.Nodes.JsonNode =
match input with
| SomeEnum.Blah -> System.Text.Json.Nodes.JsonValue.Create 1
| SomeEnum.Thing -> System.Text.Json.Nodes.JsonValue.Create 0
| v -> failwith (sprintf "Unrecognised value for enum: %O" v)
namespace ConsumePlugin
open System
open System.Collections.Generic
open System.Text.Json.Serialization
/// Module containing JSON serializing extension members for the JsonRecordTypeWithBoth type
[<AutoOpen>]
module JsonRecordTypeWithBothJsonSerializeExtension =
@@ -104,50 +122,96 @@ module JsonRecordTypeWithBothJsonSerializeExtension =
let node = System.Text.Json.Nodes.JsonObject ()
do
node.Add ("a", System.Text.Json.Nodes.JsonValue.Create<int> input.A)
node.Add ("b", System.Text.Json.Nodes.JsonValue.Create<string> input.B)
node.Add ("a", (input.A |> System.Text.Json.Nodes.JsonValue.Create<int>))
node.Add ("b", (input.B |> System.Text.Json.Nodes.JsonValue.Create<string>))
node.Add (
"c",
(fun field ->
let arr = System.Text.Json.Nodes.JsonArray ()
(input.C
|> (fun field ->
let arr = System.Text.Json.Nodes.JsonArray ()
for mem in field do
arr.Add (System.Text.Json.Nodes.JsonValue.Create<int> mem)
for mem in field do
arr.Add (System.Text.Json.Nodes.JsonValue.Create<int> mem)
arr
)
input.C
arr
))
)
node.Add ("d", InnerTypeWithBoth.toJsonNode input.D)
node.Add ("d", (input.D |> InnerTypeWithBoth.toJsonNode))
node.Add (
"e",
(fun field ->
let arr = System.Text.Json.Nodes.JsonArray ()
(input.E
|> (fun field ->
let arr = System.Text.Json.Nodes.JsonArray ()
for mem in field do
arr.Add (System.Text.Json.Nodes.JsonValue.Create<string> mem)
for mem in field do
arr.Add (System.Text.Json.Nodes.JsonValue.Create<string> mem)
arr
)
input.E
arr
))
)
node.Add (
"f",
(fun field ->
let arr = System.Text.Json.Nodes.JsonArray ()
"arr",
(input.Arr
|> (fun field ->
let arr = System.Text.Json.Nodes.JsonArray ()
for mem in field do
arr.Add (System.Text.Json.Nodes.JsonValue.Create<int> mem)
for mem in field do
arr.Add (System.Text.Json.Nodes.JsonValue.Create<int> mem)
arr
)
input.F
arr
))
)
node.Add ("byte", (input.Byte |> System.Text.Json.Nodes.JsonValue.Create<byte<measure>>))
node.Add ("sbyte", (input.Sbyte |> System.Text.Json.Nodes.JsonValue.Create<sbyte<measure>>))
node.Add ("i", (input.I |> System.Text.Json.Nodes.JsonValue.Create<int<measure>>))
node.Add ("i32", (input.I32 |> System.Text.Json.Nodes.JsonValue.Create<int32<measure>>))
node.Add ("i64", (input.I64 |> System.Text.Json.Nodes.JsonValue.Create<int64<measure>>))
node.Add ("u", (input.U |> System.Text.Json.Nodes.JsonValue.Create<uint<measure>>))
node.Add ("u32", (input.U32 |> System.Text.Json.Nodes.JsonValue.Create<uint32<measure>>))
node.Add ("u64", (input.U64 |> System.Text.Json.Nodes.JsonValue.Create<uint64<measure>>))
node.Add ("f", (input.F |> System.Text.Json.Nodes.JsonValue.Create<float<measure>>))
node.Add ("f32", (input.F32 |> System.Text.Json.Nodes.JsonValue.Create<float32<measure>>))
node.Add ("single", (input.Single |> System.Text.Json.Nodes.JsonValue.Create<single<measure>>))
node.Add (
"intMeasureOption",
(input.IntMeasureOption
|> (fun field ->
match field with
| None -> null :> System.Text.Json.Nodes.JsonNode
| Some field ->
(System.Text.Json.Nodes.JsonValue.Create<int<measure>> field)
:> System.Text.Json.Nodes.JsonNode
))
)
node.Add (
"intMeasureNullable",
(input.IntMeasureNullable
|> (fun field ->
if field.HasValue then
System.Text.Json.Nodes.JsonValue.Create<int<measure>> field.Value
:> System.Text.Json.Nodes.JsonNode
else
null :> System.Text.Json.Nodes.JsonNode
))
)
node.Add ("enum", (input.Enum |> SomeEnum.toJsonNode))
node.Add (
"timestamp",
(input.Timestamp
|> (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 :> _
namespace ConsumePlugin
@@ -180,6 +244,109 @@ module FirstDuJsonSerializeExtension =
node.Add ("data", dataNode)
node :> _
namespace ConsumePlugin
open System
open System.Collections.Generic
open System.Text.Json.Serialization
/// Module containing JSON serializing extension members for the HeaderAndValue type
[<AutoOpen>]
module HeaderAndValueJsonSerializeExtension =
/// Extension methods for JSON parsing
type HeaderAndValue with
/// Serialize to a JSON node
static member toJsonNode (input : HeaderAndValue) : System.Text.Json.Nodes.JsonNode =
let node = System.Text.Json.Nodes.JsonObject ()
do
node.Add ("header", (input.Header |> System.Text.Json.Nodes.JsonValue.Create<string>))
node.Add ("value", (input.Value |> System.Text.Json.Nodes.JsonValue.Create<string>))
node :> _
namespace ConsumePlugin
open System
open System.Collections.Generic
open System.Text.Json.Serialization
/// Module containing JSON serializing extension members for the Foo type
[<AutoOpen>]
module FooJsonSerializeExtension =
/// Extension methods for JSON parsing
type Foo with
/// Serialize to a JSON node
static member toJsonNode (input : Foo) : 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
))
)
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
@@ -221,7 +388,7 @@ module InnerTypeWithBothJsonParseExtension =
.AsObject ()
|> Seq.map (fun kvp ->
let key = (kvp.Key) |> System.Uri
let value = (kvp.Value).AsValue().GetValue<bool> ()
let value = (kvp.Value).AsValue().GetValue<System.Boolean> ()
key, value
)
|> dict
@@ -241,7 +408,11 @@ module InnerTypeWithBothJsonParseExtension =
let value =
(kvp.Value).AsArray ()
|> Seq.map (fun elt -> elt.AsValue().GetValue<System.Char> ())
|> Seq.map (fun elt ->
(match elt with
| null -> raise (System.ArgumentNullException ())
| elt -> elt.AsValue().GetValue<System.Char> ())
)
|> List.ofSeq
key, value
@@ -287,6 +458,24 @@ module InnerTypeWithBothJsonParseExtension =
}
namespace ConsumePlugin
/// Module containing JSON parsing extension members for the SomeEnum type
[<AutoOpen>]
module SomeEnumJsonParseExtension =
/// Extension methods for JSON parsing
type SomeEnum with
/// Parse from a JSON node.
static member jsonParse (node : System.Text.Json.Nodes.JsonNode) : SomeEnum =
match node.GetValueKind () with
| System.Text.Json.JsonValueKind.Number -> node.AsValue().GetValue<int> () |> enum<SomeEnum>
| System.Text.Json.JsonValueKind.String ->
match node.AsValue().GetValue<string>().ToLowerInvariant () with
| "blah" -> SomeEnum.Blah
| "thing" -> SomeEnum.Thing
| v -> failwith ("Unrecognised value for enum: %i" + v)
| _ -> failwith ("Unrecognised kind for enum of type: " + "SomeEnum")
namespace ConsumePlugin
/// Module containing JSON parsing extension members for the JsonRecordTypeWithBoth type
[<AutoOpen>]
module JsonRecordTypeWithBothJsonParseExtension =
@@ -295,7 +484,76 @@ module JsonRecordTypeWithBothJsonParseExtension =
/// Parse from a JSON node.
static member jsonParse (node : System.Text.Json.Nodes.JsonNode) : JsonRecordTypeWithBoth =
let arg_5 =
let arg_21 = ()
let arg_20 =
(match node.["timestamp"] with
| null ->
raise (
System.Collections.Generic.KeyNotFoundException (
sprintf "Required key '%s' not found on JSON object" ("timestamp")
)
)
| v -> v)
.AsValue()
.GetValue<string> ()
|> System.DateTimeOffset.Parse
let arg_19 =
SomeEnum.jsonParse (
match node.["enum"] with
| null ->
raise (
System.Collections.Generic.KeyNotFoundException (
sprintf "Required key '%s' not found on JSON object" ("enum")
)
)
| v -> v
)
let arg_18 =
match node.["intMeasureNullable"] with
| null -> System.Nullable ()
| v ->
v.AsValue().GetValue<System.Int32> ()
|> LanguagePrimitives.Int32WithMeasure
|> System.Nullable
let arg_17 =
match node.["intMeasureOption"] with
| null -> None
| v ->
v.AsValue().GetValue<System.Int32> ()
|> LanguagePrimitives.Int32WithMeasure
|> Some
let arg_16 =
(match node.["single"] with
| null ->
raise (
System.Collections.Generic.KeyNotFoundException (
sprintf "Required key '%s' not found on JSON object" ("single")
)
)
| v -> v)
.AsValue()
.GetValue<System.Single> ()
|> LanguagePrimitives.Float32WithMeasure
let arg_15 =
(match node.["f32"] with
| null ->
raise (
System.Collections.Generic.KeyNotFoundException (
sprintf "Required key '%s' not found on JSON object" ("f32")
)
)
| v -> v)
.AsValue()
.GetValue<System.Single> ()
|> LanguagePrimitives.Float32WithMeasure
let arg_14 =
(match node.["f"] with
| null ->
raise (
@@ -303,9 +561,130 @@ module JsonRecordTypeWithBothJsonParseExtension =
sprintf "Required key '%s' not found on JSON object" ("f")
)
)
| v -> v)
.AsValue()
.GetValue<System.Double> ()
|> LanguagePrimitives.FloatWithMeasure
let arg_13 =
(match node.["u64"] with
| null ->
raise (
System.Collections.Generic.KeyNotFoundException (
sprintf "Required key '%s' not found on JSON object" ("u64")
)
)
| v -> v)
.AsValue()
.GetValue<System.UInt64> ()
|> LanguagePrimitives.UInt64WithMeasure
let arg_12 =
(match node.["u32"] with
| null ->
raise (
System.Collections.Generic.KeyNotFoundException (
sprintf "Required key '%s' not found on JSON object" ("u32")
)
)
| v -> v)
.AsValue()
.GetValue<System.UInt32> ()
|> LanguagePrimitives.UInt32WithMeasure
let arg_11 =
(match node.["u"] with
| null ->
raise (
System.Collections.Generic.KeyNotFoundException (
sprintf "Required key '%s' not found on JSON object" ("u")
)
)
| v -> v)
.AsValue()
.GetValue<System.UInt32> ()
|> LanguagePrimitives.UInt32WithMeasure
let arg_10 =
(match node.["i64"] with
| null ->
raise (
System.Collections.Generic.KeyNotFoundException (
sprintf "Required key '%s' not found on JSON object" ("i64")
)
)
| v -> v)
.AsValue()
.GetValue<System.Int64> ()
|> LanguagePrimitives.Int64WithMeasure
let arg_9 =
(match node.["i32"] with
| null ->
raise (
System.Collections.Generic.KeyNotFoundException (
sprintf "Required key '%s' not found on JSON object" ("i32")
)
)
| v -> v)
.AsValue()
.GetValue<System.Int32> ()
|> LanguagePrimitives.Int32WithMeasure
let arg_8 =
(match node.["i"] with
| null ->
raise (
System.Collections.Generic.KeyNotFoundException (
sprintf "Required key '%s' not found on JSON object" ("i")
)
)
| v -> v)
.AsValue()
.GetValue<System.Int32> ()
|> LanguagePrimitives.Int32WithMeasure
let arg_7 =
(match node.["sbyte"] with
| null ->
raise (
System.Collections.Generic.KeyNotFoundException (
sprintf "Required key '%s' not found on JSON object" ("sbyte")
)
)
| v -> v)
.AsValue()
.GetValue<System.SByte> ()
|> LanguagePrimitives.SByteWithMeasure
let arg_6 =
(match node.["byte"] with
| null ->
raise (
System.Collections.Generic.KeyNotFoundException (
sprintf "Required key '%s' not found on JSON object" ("byte")
)
)
| v -> v)
.AsValue()
.GetValue<System.Byte> ()
|> LanguagePrimitives.ByteWithMeasure
let arg_5 =
(match node.["arr"] with
| null ->
raise (
System.Collections.Generic.KeyNotFoundException (
sprintf "Required key '%s' not found on JSON object" ("arr")
)
)
| v -> v)
.AsArray ()
|> Seq.map (fun elt -> elt.AsValue().GetValue<int> ())
|> Seq.map (fun elt ->
(match elt with
| null -> raise (System.ArgumentNullException ())
| elt -> elt.AsValue().GetValue<System.Int32> ())
)
|> Array.ofSeq
let arg_4 =
@@ -318,7 +697,11 @@ module JsonRecordTypeWithBothJsonParseExtension =
)
| v -> v)
.AsArray ()
|> Seq.map (fun elt -> elt.AsValue().GetValue<string> ())
|> Seq.map (fun elt ->
(match elt with
| null -> raise (System.ArgumentNullException ())
| elt -> elt.AsValue().GetValue<System.String> ())
)
|> Array.ofSeq
let arg_3 =
@@ -343,7 +726,11 @@ module JsonRecordTypeWithBothJsonParseExtension =
)
| v -> v)
.AsArray ()
|> Seq.map (fun elt -> elt.AsValue().GetValue<int> ())
|> Seq.map (fun elt ->
(match elt with
| null -> raise (System.ArgumentNullException ())
| elt -> elt.AsValue().GetValue<System.Int32> ())
)
|> List.ofSeq
let arg_1 =
@@ -356,7 +743,7 @@ module JsonRecordTypeWithBothJsonParseExtension =
)
| v -> v)
.AsValue()
.GetValue<string> ()
.GetValue<System.String> ()
let arg_0 =
(match node.["a"] with
@@ -368,7 +755,7 @@ module JsonRecordTypeWithBothJsonParseExtension =
)
| v -> v)
.AsValue()
.GetValue<int> ()
.GetValue<System.Int32> ()
{
A = arg_0
@@ -376,7 +763,23 @@ module JsonRecordTypeWithBothJsonParseExtension =
C = arg_2
D = arg_3
E = arg_4
F = arg_5
Arr = arg_5
Byte = arg_6
Sbyte = arg_7
I = arg_8
I32 = arg_9
I64 = arg_10
U = arg_11
U32 = arg_12
U64 = arg_13
F = arg_14
F32 = arg_15
Single = arg_16
IntMeasureOption = arg_17
IntMeasureNullable = arg_18
Enum = arg_19
Timestamp = arg_20
Unit = arg_21
}
namespace ConsumePlugin
@@ -422,7 +825,7 @@ module FirstDuJsonParseExtension =
)
| v -> v)
.AsValue()
.GetValue<string> ()
.GetValue<System.String> ()
)
| "case2" ->
let node =
@@ -455,6 +858,142 @@ module FirstDuJsonParseExtension =
)
| v -> v)
.AsValue()
.GetValue<int> ()
.GetValue<System.Int32> ()
)
| v -> failwith ("Unrecognised 'type' field value: " + v)
namespace ConsumePlugin
/// Module containing JSON parsing extension members for the HeaderAndValue type
[<AutoOpen>]
module HeaderAndValueJsonParseExtension =
/// Extension methods for JSON parsing
type HeaderAndValue with
/// Parse from a JSON node.
static member jsonParse (node : System.Text.Json.Nodes.JsonNode) : HeaderAndValue =
let arg_1 =
(match node.["value"] with
| null ->
raise (
System.Collections.Generic.KeyNotFoundException (
sprintf "Required key '%s' not found on JSON object" ("value")
)
)
| v -> v)
.AsValue()
.GetValue<System.String> ()
let arg_0 =
(match node.["header"] with
| null ->
raise (
System.Collections.Generic.KeyNotFoundException (
sprintf "Required key '%s' not found on JSON object" ("header")
)
)
| v -> v)
.AsValue()
.GetValue<System.String> ()
{
Header = arg_0
Value = arg_1
}
namespace ConsumePlugin
/// Module containing JSON parsing extension members for the Foo type
[<AutoOpen>]
module FooJsonParseExtension =
/// Extension methods for JSON parsing
type Foo with
/// Parse from a JSON node.
static member jsonParse (node : System.Text.Json.Nodes.JsonNode) : Foo =
let arg_0 =
match node.["message"] with
| null -> None
| v -> HeaderAndValue.jsonParse v |> Some
{
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
}

File diff suppressed because it is too large Load Diff

View File

@@ -22,7 +22,7 @@ module JwtVaultAuthResponse =
)
| v -> v)
.AsValue()
.GetValue<int> ()
.GetValue<System.Int32> ()
let arg_9 =
(match node.["orphan"] with
@@ -34,7 +34,7 @@ module JwtVaultAuthResponse =
)
| v -> v)
.AsValue()
.GetValue<bool> ()
.GetValue<System.Boolean> ()
let arg_8 =
(match node.["entity_id"] with
@@ -46,7 +46,7 @@ module JwtVaultAuthResponse =
)
| v -> v)
.AsValue()
.GetValue<string> ()
.GetValue<System.String> ()
let arg_7 =
(match node.["token_type"] with
@@ -58,7 +58,7 @@ module JwtVaultAuthResponse =
)
| v -> v)
.AsValue()
.GetValue<string> ()
.GetValue<System.String> ()
let arg_6 =
(match node.["renewable"] with
@@ -70,7 +70,7 @@ module JwtVaultAuthResponse =
)
| v -> v)
.AsValue()
.GetValue<bool> ()
.GetValue<System.Boolean> ()
let arg_5 =
(match node.["lease_duration"] with
@@ -82,7 +82,7 @@ module JwtVaultAuthResponse =
)
| v -> v)
.AsValue()
.GetValue<int> ()
.GetValue<System.Int32> ()
let arg_4 =
(match node.["identity_policies"] with
@@ -94,7 +94,11 @@ module JwtVaultAuthResponse =
)
| v -> v)
.AsArray ()
|> Seq.map (fun elt -> elt.AsValue().GetValue<string> ())
|> Seq.map (fun elt ->
(match elt with
| null -> raise (System.ArgumentNullException ())
| elt -> elt.AsValue().GetValue<System.String> ())
)
|> List.ofSeq
let arg_3 =
@@ -107,7 +111,11 @@ module JwtVaultAuthResponse =
)
| v -> v)
.AsArray ()
|> Seq.map (fun elt -> elt.AsValue().GetValue<string> ())
|> Seq.map (fun elt ->
(match elt with
| null -> raise (System.ArgumentNullException ())
| elt -> elt.AsValue().GetValue<System.String> ())
)
|> List.ofSeq
let arg_2 =
@@ -120,7 +128,11 @@ module JwtVaultAuthResponse =
)
| v -> v)
.AsArray ()
|> Seq.map (fun elt -> elt.AsValue().GetValue<string> ())
|> Seq.map (fun elt ->
(match elt with
| null -> raise (System.ArgumentNullException ())
| elt -> elt.AsValue().GetValue<System.String> ())
)
|> List.ofSeq
let arg_1 =
@@ -133,7 +145,7 @@ module JwtVaultAuthResponse =
)
| v -> v)
.AsValue()
.GetValue<string> ()
.GetValue<System.String> ()
let arg_0 =
(match node.["client_token"] with
@@ -145,7 +157,7 @@ module JwtVaultAuthResponse =
)
| v -> v)
.AsValue()
.GetValue<string> ()
.GetValue<System.String> ()
{
ClientToken = arg_0
@@ -189,7 +201,7 @@ module JwtVaultResponse =
)
| v -> v)
.AsValue()
.GetValue<int> ()
.GetValue<System.Int32> ()
let arg_2 =
(match node.["renewable"] with
@@ -201,7 +213,7 @@ module JwtVaultResponse =
)
| v -> v)
.AsValue()
.GetValue<bool> ()
.GetValue<System.Boolean> ()
let arg_1 =
(match node.["lease_id"] with
@@ -213,7 +225,7 @@ module JwtVaultResponse =
)
| v -> v)
.AsValue()
.GetValue<string> ()
.GetValue<System.String> ()
let arg_0 =
(match node.["request_id"] with
@@ -225,7 +237,7 @@ module JwtVaultResponse =
)
| v -> v)
.AsValue()
.GetValue<string> ()
.GetValue<System.String> ()
{
RequestId = arg_0
@@ -271,7 +283,7 @@ module JwtSecretResponse =
.AsObject ()
|> Seq.map (fun kvp ->
let key = (kvp.Key)
let value = (kvp.Value).AsValue().GetValue<int> ()
let value = (kvp.Value).AsValue().GetValue<System.Int32> ()
key, value
)
|> Map.ofSeq
@@ -288,7 +300,7 @@ module JwtSecretResponse =
.AsObject ()
|> Seq.map (fun kvp ->
let key = (kvp.Key) |> System.Uri
let value = (kvp.Value).AsValue().GetValue<string> ()
let value = (kvp.Value).AsValue().GetValue<System.String> ()
key, value
)
|> dict
@@ -305,7 +317,7 @@ module JwtSecretResponse =
.AsObject ()
|> Seq.map (fun kvp ->
let key = (kvp.Key) |> System.Uri
let value = (kvp.Value).AsValue().GetValue<string> ()
let value = (kvp.Value).AsValue().GetValue<System.String> ()
key, value
)
|> readOnlyDict
@@ -322,7 +334,7 @@ module JwtSecretResponse =
.AsObject ()
|> Seq.map (fun kvp ->
let key = (kvp.Key)
let value = (kvp.Value).AsValue().GetValue<string> ()
let value = (kvp.Value).AsValue().GetValue<System.String> ()
key, value
)
|> Map.ofSeq
@@ -339,7 +351,7 @@ module JwtSecretResponse =
.AsObject ()
|> Seq.map (fun kvp ->
let key = (kvp.Key)
let value = (kvp.Value).AsValue().GetValue<string> ()
let value = (kvp.Value).AsValue().GetValue<System.String> ()
key, value
)
|> Seq.map System.Collections.Generic.KeyValuePair
@@ -357,7 +369,7 @@ module JwtSecretResponse =
.AsObject ()
|> Seq.map (fun kvp ->
let key = (kvp.Key)
let value = (kvp.Value).AsValue().GetValue<string> ()
let value = (kvp.Value).AsValue().GetValue<System.String> ()
key, value
)
|> dict
@@ -374,7 +386,7 @@ module JwtSecretResponse =
.AsObject ()
|> Seq.map (fun kvp ->
let key = (kvp.Key)
let value = (kvp.Value).AsValue().GetValue<string> ()
let value = (kvp.Value).AsValue().GetValue<System.String> ()
key, value
)
|> readOnlyDict
@@ -389,7 +401,7 @@ module JwtSecretResponse =
)
| v -> v)
.AsValue()
.GetValue<int> ()
.GetValue<System.Int32> ()
let arg_2 =
(match node.["renewable"] with
@@ -401,7 +413,7 @@ module JwtSecretResponse =
)
| v -> v)
.AsValue()
.GetValue<bool> ()
.GetValue<System.Boolean> ()
let arg_1 =
(match node.["lease_id"] with
@@ -413,7 +425,7 @@ module JwtSecretResponse =
)
| v -> v)
.AsValue()
.GetValue<string> ()
.GetValue<System.String> ()
let arg_0 =
(match node.["request_id"] with
@@ -425,7 +437,7 @@ module JwtSecretResponse =
)
| v -> v)
.AsValue()
.GetValue<string> ()
.GetValue<System.String> ()
{
RequestId = arg_0
@@ -476,11 +488,8 @@ module VaultClient =
| v -> v),
System.Uri (
"v1/{mountPoint}/{path}"
.Replace("{path}", path.ToString () |> System.Web.HttpUtility.UrlEncode)
.Replace (
"{mountPoint}",
mountPoint.ToString () |> System.Web.HttpUtility.UrlEncode
),
.Replace("{path}", path.ToString () |> System.Uri.EscapeDataString)
.Replace ("{mountPoint}", mountPoint.ToString () |> System.Uri.EscapeDataString),
System.UriKind.Relative
)
)
@@ -573,11 +582,8 @@ module VaultClientNonExtensionMethod =
| v -> v),
System.Uri (
"v1/{mountPoint}/{path}"
.Replace("{path}", path.ToString () |> System.Web.HttpUtility.UrlEncode)
.Replace (
"{mountPoint}",
mountPoint.ToString () |> System.Web.HttpUtility.UrlEncode
),
.Replace("{path}", path.ToString () |> System.Uri.EscapeDataString)
.Replace ("{mountPoint}", mountPoint.ToString () |> System.Uri.EscapeDataString),
System.UriKind.Relative
)
)
@@ -673,11 +679,8 @@ module VaultClientExtensionMethodHttpClientExtension =
| v -> v),
System.Uri (
"v1/{mountPoint}/{path}"
.Replace("{path}", path.ToString () |> System.Web.HttpUtility.UrlEncode)
.Replace (
"{mountPoint}",
mountPoint.ToString () |> System.Web.HttpUtility.UrlEncode
),
.Replace("{path}", path.ToString () |> System.Uri.EscapeDataString)
.Replace ("{mountPoint}", mountPoint.ToString () |> System.Uri.EscapeDataString),
System.UriKind.Relative
)
)

View File

@@ -29,6 +29,28 @@ type JsonRecordType =
F : int[]
}
[<WoofWare.Myriad.Plugins.JsonParse>]
type internal InternalTypeNotExtension =
{
[<JsonPropertyName(Literals.something)>]
InternalThing : string
}
[<WoofWare.Myriad.Plugins.JsonSerialize>]
type internal InternalTypeNotExtensionSerial =
{
[<JsonPropertyName(Literals.something)>]
InternalThing2 : string
}
[<WoofWare.Myriad.Plugins.JsonParse true>]
[<WoofWare.Myriad.Plugins.JsonSerialize true>]
type internal InternalTypeExtension =
{
[<JsonPropertyName(Literals.something)>]
ExternalThing : string
}
[<WoofWare.Myriad.Plugins.JsonParse true>]
type ToGetExtensionMethod =
{

View File

@@ -31,7 +31,7 @@ module MyListCata =
[<RequireQualifiedAccess>]
type private Instruction<'a> =
| Process__MyList of MyList<'a>
| MyList_Cons of 'a
| MyList_Cons of head : 'a
let private loop (cata : MyListCata<'a, 'MyList>) (instructions : ResizeArray<Instruction<'a>>) =
let myListStack = ResizeArray<'MyList> ()

View File

@@ -48,3 +48,10 @@ type TypeWithInterface =
inherit IDisposable
abstract Mem1 : string option -> string[] Async
abstract Mem2 : unit -> string[] Async
[<GenerateMock>]
type TypeWithProperties =
inherit IDisposable
abstract Mem1 : string option -> string[] Async
abstract Prop1 : int
abstract Prop2 : unit Async

View File

@@ -0,0 +1,41 @@
namespace SomeNamespace
open System
type IPublicTypeNoAttr =
abstract Mem1 : string * int -> string list
abstract Mem2 : string -> int
abstract Mem3 : x : int * ?ct : System.Threading.CancellationToken -> string
type IPublicTypeInternalFalseNoAttr =
abstract Mem1 : string * int -> string list
abstract Mem2 : string -> int
abstract Mem3 : x : int * ?ct : System.Threading.CancellationToken -> string
type internal InternalTypeNoAttr =
abstract Mem1 : string * int -> unit
abstract Mem2 : string -> int
type private PrivateTypeNoAttr =
abstract Mem1 : string * int -> unit
abstract Mem2 : string -> int
type private PrivateTypeInternalFalseNoAttr =
abstract Mem1 : string * int -> unit
abstract Mem2 : string -> int
type VeryPublicTypeNoAttr<'a, 'b> =
abstract Mem1 : 'a -> 'b
type CurriedNoAttr<'a> =
abstract Mem1 : int -> 'a -> string
abstract Mem2 : int * string -> 'a -> string
abstract Mem3 : (int * string) -> 'a -> string
abstract Mem4 : (int * string) -> ('a * int) -> string
abstract Mem5 : x : int * string -> ('a * int) -> string
abstract Mem6 : int * string -> y : 'a * int -> string
type TypeWithInterfaceNoAttr =
inherit IDisposable
abstract Mem1 : string option -> string[] Async
abstract Mem2 : unit -> string[] Async

View File

@@ -19,13 +19,16 @@ type GymAccessOptions =
QrCodeAccess : bool
}
[<Measure>]
type measure
[<WoofWare.Myriad.Plugins.JsonParse>]
type GymLocation =
{
[<JsonNumberHandling(JsonNumberHandling.AllowReadingFromString)>]
Longitude : float
[<JsonNumberHandling(JsonNumberHandling.AllowReadingFromString)>]
Latitude : float
Latitude : float<measure>
}
[<WoofWare.Myriad.Plugins.JsonParse>]

View File

@@ -1,9 +1,5 @@
namespace ConsumePlugin
type ParseState =
| AwaitingKey
| AwaitingValue of string
/// My whatnot
[<WoofWare.Myriad.Plugins.RemoveOptions>]
type RecordType =

View File

@@ -122,8 +122,6 @@ type internal IApiWithoutBaseAddress =
[<Get "endpoint/{param}">]
abstract GetPathParam : [<Path "param">] parameter : string * ?ct : CancellationToken -> Task<string>
// TODO: implement BasePath support
[<WoofWare.Myriad.Plugins.HttpClient>]
[<BasePath "foo">]
type IApiWithBasePath =
@@ -132,12 +130,54 @@ type IApiWithBasePath =
abstract GetPathParam : [<Path "param">] parameter : string * ?cancellationToken : CancellationToken -> Task<string>
[<WoofWare.Myriad.Plugins.HttpClient>]
[<BaseAddress "https://whatnot.com">]
[<BaseAddress "https://whatnot.com/thing">]
[<BasePath "foo">]
type IApiWithBasePathAndAddress =
[<Get "endpoint/{param}">]
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>]
[<Header("Header-Name", "Header-Value")>]
type IApiWithHeaders =
@@ -148,6 +188,7 @@ type IApiWithHeaders =
abstract SomeOtherHeader : int
[<Get "endpoint/{param}">]
[<Header("Something-Else", "val")>]
abstract GetPathParam : [<Path "param">] parameter : string * ?ct : CancellationToken -> Task<string>
[<WoofWare.Myriad.Plugins.HttpClient>]

View File

@@ -16,6 +16,15 @@ type InnerTypeWithBoth =
ConcreteDict : Dictionary<string, InnerTypeWithBoth>
}
[<WoofWare.Myriad.Plugins.JsonParse true>]
[<WoofWare.Myriad.Plugins.JsonSerialize true>]
type SomeEnum =
| Blah = 1
| Thing = 0
[<Measure>]
type measure
[<WoofWare.Myriad.Plugins.JsonParse true>]
[<WoofWare.Myriad.Plugins.JsonSerialize true>]
type JsonRecordTypeWithBoth =
@@ -25,7 +34,23 @@ type JsonRecordTypeWithBoth =
C : int list
D : InnerTypeWithBoth
E : string array
F : int[]
Arr : int[]
Byte : byte<measure>
Sbyte : sbyte<measure>
I : int<measure>
I32 : int32<measure>
I64 : int64<measure>
U : uint<measure>
U32 : uint32<measure>
U64 : uint64<measure>
F : float<measure>
F32 : float32<measure>
Single : single<measure>
IntMeasureOption : int<measure> option
IntMeasureNullable : int<measure> Nullable
Enum : SomeEnum
Timestamp : DateTimeOffset
Unit : unit
}
[<WoofWare.Myriad.Plugins.JsonSerialize true>]
@@ -34,3 +59,36 @@ type FirstDu =
| EmptyCase
| Case1 of data : string
| Case2 of record : JsonRecordTypeWithBoth * i : int
[<WoofWare.Myriad.Plugins.JsonParse true>]
[<WoofWare.Myriad.Plugins.JsonSerialize true>]
type HeaderAndValue =
{
Header : string
Value : string
}
[<WoofWare.Myriad.Plugins.JsonSerialize true>]
[<WoofWare.Myriad.Plugins.JsonParse true>]
type Foo =
{
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
}

File diff suppressed because it is too large Load Diff

View File

@@ -10,7 +10,7 @@
<WarnOn>FS3388,FS3559</WarnOn>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Nerdbank.GitVersioning" Version="3.6.133" PrivateAssets="all"/>
<PackageReference Include="Nerdbank.GitVersioning" Version="3.8.38-alpha" PrivateAssets="all"/>
<SourceLinkGitHubHost Include="github.com" ContentUrl="https://raw.githubusercontent.com"/>
</ItemGroup>
<PropertyGroup Condition="'$(GITHUB_ACTION)' != ''">

205
README.md
View File

@@ -8,23 +8,22 @@
Some helpers in [Myriad](https://github.com/MoiraeSoftware/myriad/) which might be useful.
These are currently somewhat experimental, and I personally am their primary customer.
The `RemoveOptions` generator in particular is extremely half-baked.
Currently implemented:
* `JsonParse` (to stamp out `jsonParse : JsonNode -> 'T` methods).
* `JsonSerialize` (to stamp out `toJsonNode : 'T -> JsonNode` methods).
* `HttpClient` (to stamp out a [RestEase](https://github.com/canton7/RestEase)-style HTTP client).
* `GenerateMock` (to stamp out a record type corresponding to an interface, like a compile-time [Foq](https://github.com/fsprojects/Foq)).
* `ArgParser` (to stamp out a basic argument parser).
* `SwaggerClient` (to stamp out an HTTP client for a Swagger API).
* `CreateCatamorphism` (to stamp out a non-stack-overflowing [catamorphism](https://fsharpforfunandprofit.com/posts/recursive-types-and-folds/) for a discriminated union).
* `RemoveOptions` (to strip `option` modifiers from a type) - this one is particularly half-baked!
If you would like to ensure that your particular use-case remains unbroken, please do contribute tests to this repository.
The `ConsumePlugin` assembly contains a number of invocations of these source generators,
so you just need to add copies of your types to that assembly to ensure that I will at least notice if I break the build;
and if you add tests to `WoofWare.Myriad.Plugins.Test` then I will also notice if I break the runtime semantics of the generated code.
Currently implemented:
* `JsonParse` (to stamp out `jsonParse : JsonNode -> 'T` methods);
* `JsonSerialize` (to stamp out `toJsonNode : 'T -> JsonNode` methods);
* `RemoveOptions` (to strip `option` modifiers from a type).
* `HttpClient` (to stamp out a [RestEase](https://github.com/canton7/RestEase)-style HTTP client).
* `GenerateMock` (to stamp out a record type corresponding to an interface).
* `CreateCatamorphism` (to stamp out a non-stack-overflowing [catamorphism](https://fsharpforfunandprofit.com/posts/recursive-types-and-folds/) for a discriminated union).
## `JsonParse`
Takes records like this:
@@ -153,6 +152,160 @@ The same limitations generally apply to `JsonSerialize` as do to `JsonParse`.
For an example of using both `JsonParse` and `JsonSerialize` together with complex types, see [the type definitions](./ConsumePlugin/SerializationAndDeserialization.fs) and [tests](./WoofWare.Myriad.Plugins.Test/TestJsonSerialize/TestJsonSerde.fs).
## `ArgParser`
Takes a record like this:
```fsharp
type DryRunMode =
| [<ArgumentFlag true> Dry
| [<ArgumentFlag false> Wet
[<ArgParser>]
type Foo =
{
[<ArgumentHelpText "Enable the frobnicator">]
SomeFlag : bool
A : int option
[<ArgumentDefaultFunction>]
B : Choice<int, int>
[<ArgumentDefaultEnvironmentVariable "MY_ENV_VAR">]
BWithEnv : Choice<int, int>
[<ArgumentDefaultFunction>]
DryRun : DryRunMode
[<ArgumentLongForm "longer-form-replaces-c">]
C : float list
// optionally:
[<PositionalArgs>]
Rest : string list // or e.g. `int list` if you want them parsed into a type too
}
static member DefaultB () = 4
static member DefaultDryRun () = DryRunMode.Wet
```
and stamps out a basic `parse` method of this signature:
```fsharp
[<RequireQualifiedAccess>]
module Foo =
// in case you want to test it
let parse' (getEnvVar : string -> string) (args : string list) : Foo = ...
// the one we expect you actually want to use
let parse (args : string list) : Foo = ...
```
Default arguments are handled as `Choice<'a, 'a>`:
you get a `Choice1Of2` if the user provided the input, or a `Choice2Of2` if the parser filled in your specified default value.
You can control `TimeSpan` and friends with the `[<InvariantCulture>]` and `[<ParseExact @"hh\:mm\:ss">]` attributes.
You can generate extension methods for the type, instead of a module with the type's name, using `[<ArgParser (* isExtensionMethod = *) true>]`.
If `--help` appears in a position where the parser is expecting a key (e.g. in the first position, or after a `--foo=bar`), the parser fails with help text.
The parser also makes a limited effort to supply help text when encountering an invalid parse.
### What's the point?
I got fed up of waiting for us to find time to rewrite the in-house one at work.
That one has a bunch of nice compositional properties, which my version lacks:
I can basically only deal with primitive types, and e.g. you can't stack records and discriminated unions inside each other.
But I *do* want an F#-native argument parser suitable for AOT-compilation.
Why not [Argu](https://fsprojects.github.io/Argu/)?
Answer: I got annoyed with having to construct my records by hand even after Argu returned and said the parsing was all "done".
### Limitations
This is very bare-bones, but do raise GitHub issues if you like (or if you find cases where the parser does the wrong thing).
* Help is signalled by throwing an exception, so you'll get an unsightly stack trace and a nonzero exit code.
* Help doesn't take into account any arguments the user has entered. Ideally you'd get contextual information like an identification of which args the user has supplied at the point where the parse failed or help was requested.
* I don't handle very many types, and in particular a real arg parser would handle DUs and records with nesting.
* I don't try very hard to find a valid parse. It may well be possible to find a case where I fail to parse despite there existing a valid parse.
* There's no subcommand support (you'll have to do that yourself).
It should work fine if you just want to compose a few primitive types, though.
## `SwaggerClient`
Takes a JSON-schema definition of a [Swagger API](https://swagger.io/), and stamps out a client like this:
```fsharp
/// A type which was defined in the Swagger spec
[<JsonParse true ; JsonSerialize true>]
type SwaggerType1 =
{
[<System.Text.Json.Serialization.JsonExtensionData>]
AdditionalProperties : System.Collections.Generic.Dictionary<string, System.Text.Json.Nodes.JsonNode>
Message : string
}
/// Documentation from the Swagger spec
[<HttpClient false ; RestEase.BasePath "/api/v1">]
type IGitea =
/// Returns the Person actor for a user
[<RestEase.Get "/activitypub/user/{username}">]
abstract ActivitypubPerson :
[<RestEase.Path "username">] username : string * ?ct : System.Threading.CancellationToken ->
ActivityPub System.Threading.Tasks.Task
```
Notice that we automatically decorate the type with our `[<HttpClient>]` attribute, so if you choose to do so, you can chain another Myriad generated file off this one and you'll get a RestEase-style client stamped out.
(See below, searching on the string `"Generated2SwaggerGitea.fs"`, for an example.)
You don't need to `Content Include` or `EmbeddedResource Include` the JSON schema.
`None Include` will do; we only need the source to be available at build time.
You *do* need to include the following configuration:
```xml
<Compile Include="GeneratedClient.fs">
<!-- This bit is normal: -->
<MyriadFile>swagger.json</MyriadFile>
<!-- This bit is new and required! -->
<MyriadParams>
<ClassName>GiteaClient</ClassName>
<!-- Optionally: -->
<GenerateMock>true</GenerateMock>
</MyriadParams>
</Compile>
```
The `<ClassName />` key tells us what to name the resulting interface (it gets an `I` prepended for you).
You can optionally also set `<GenerateMockVisibility>v</GenerateMockVisibility>` to add the `[<GenerateMock>]` attribute to the type
(where `v` should be `internal` or `public`, indicating "resulting mock type is internal" vs "is public"),
so that the following manoeuvre will result in a generated mock:
```xml
<None Include="swagger-gitea.json" />
<Compile Include="GeneratedSwaggerGitea.fs">
<MyriadFile>swagger-gitea.json</MyriadFile>
<MyriadParams>
<GenerateMockVisibility>public</GenerateMockVisibility>
<ClassName>Gitea</ClassName>
</MyriadParams>
</Compile>
<Compile Include="Generated2SwaggerGitea.fs">
<MyriadFile>GeneratedSwaggerGitea.fs</MyriadFile>
</Compile>
```
(Note that you do have to create the `GeneratedSwaggerGitea.fs` file manually before code generation happens. Myriad will throw if that file isn't there, because `Generated2SwaggerGitea.fs` depends on it so Myriad wants to compute its hash. Just make an empty file.)
### What's the point?
[`SwaggerProvider`](https://github.com/fsprojects/SwaggerProvider) is *absolutely magical*, but it's kind of witchcraft.
I fear no man, but that thing… it scares me.
Also, builds using `SwaggerProvider` appear to be inherently nondeterministic, even if the data source doesn't change.
## Limitations
Swagger API specs appear to be pretty cowboy in the wild.
I try to cope with invalid schemas I have seen, but I can't guarantee I do so correctly.
Definitely do perform integration tests and let me know of weird specs you encounter, and bits of the (very extensive) Swagger spec I have omitted!
## `RemoveOptions`
Takes a record like this:
@@ -451,6 +604,36 @@ For example, this specifies that Myriad is to use the contents of `Client.fs` to
</ItemGroup>
```
## Alternative use without the attributes
You can avoid taking a reference on the `WoofWare.Myriad.Plugins.Attributes` assembly, instead putting all the configuration into the project file.
This is implemented for everything except the SwaggerClientGenerator.
```xml
<Project>
<ItemGroup>
<Compile Include="Client.fs" />
<Compile Include="GeneratedClient.fs">
<MyriadFile>Client.fs</MyriadFile>
<MyriadParams>
<MyTypeName1>GenerateMock(false)!JsonParse</MyTypeName1>
<SomeOtherTypeName>GenerateMock</SomeOtherTypeName>
</MyriadParams>
</Compile>
</ItemGroup>
<ItemGroup>
<PackageReference Include="WoofWare.Myriad.Plugins" Version="$(WoofWareMyriadPluginVersion)" PrivateAssets="all" />
<PackageReference Include="Myriad.Sdk" Version="0.8.3" PrivateAssets="all" />
</ItemGroup>
</Project>
```
That is, you specify a `!`-delimited list of the attributes you *would* apply to the type.
Supply "arguments" to the attribute name in the project file as you would to the attribute itself.
(Yes, this is indeed incredibly cumbersome, and you're not interested in the reasons it's all so mad!
I'm hopefully going to get round to writing a more powerful source generation system which won't have these limitations.)
### Myriad Gotchas
* MsBuild doesn't always realise that it needs to invoke Myriad during rebuild.

View File

@@ -0,0 +1,102 @@
namespace WoofWare.Myriad.Plugins
open System
/// Attribute indicating a record type to which the "build arg parser" Myriad
/// generator should apply during build.
///
/// If you supply isExtensionMethod = true, you will get extension methods.
/// These can only be consumed from F#, but the benefit is that they don't use up the module name
/// (since by default we create a module called "{TypeName}").
type ArgParserAttribute (isExtensionMethod : bool) =
inherit Attribute ()
/// The default value of `isExtensionMethod`, the optional argument to the ArgParserAttribute constructor.
static member DefaultIsExtensionMethod = false
/// Shorthand for the "isExtensionMethod = false" constructor; see documentation there for details.
new () = ArgParserAttribute ArgParserAttribute.DefaultIsExtensionMethod
/// Attribute indicating that this field shall accumulate all unmatched args,
/// as well as any that appear after a bare `--`.
///
/// 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.
///
/// If the type of the PositionalArgs field is `Choice<'a, 'a>`, then we will
/// tell you whether each arg came before or after a standalone `--` separator.
/// For example, `MyApp foo bar -- baz` with PositionalArgs of `Choice<string, string>`
/// would yield `Choice1Of2 foo, Choice1Of2 bar, Choice2Of2 baz`.
type PositionalArgsAttribute (includeFlagLike : bool) =
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
/// from calling an appropriately named static method on the type.
///
/// This attribute can only be placed on fields of type `Choice<_, _>` where both type parameters
/// are the same.
/// After a successful parse, the value is Choice1Of2 if the user supplied an input,
/// or Choice2Of2 if the input was obtained by calling the default function.
///
/// The static method we call for field `FieldName : 'a` is `DefaultFieldName : unit -> 'a`.
type ArgumentDefaultFunctionAttribute () =
inherit Attribute ()
/// Attribute indicating that this field shall have a default value derived
/// from an environment variable (whose name you give in the attribute constructor).
///
/// This attribute can only be placed on fields of type `Choice<_, _>` where both type parameters
/// are the same.
/// After a successful parse, the value is Choice1Of2 if the user supplied an input,
/// or Choice2Of2 if the input was obtained by pulling a value from `Environment.GetEnvironmentVariable`.
type ArgumentDefaultEnvironmentVariableAttribute (envVar : string) =
inherit Attribute ()
/// Attribute indicating that this field shall have the given help text, when `--help` is invoked
/// or when a parse error causes us to print help text.
type ArgumentHelpTextAttribute (helpText : string) =
inherit Attribute ()
/// Attribute indicating that this field should be parsed with a ParseExact method on its type.
/// For example, on a TimeSpan field, with [<ArgumentParseExact @"hh\:mm\:ss">], we will call
/// `TimeSpan.ParseExact (s, @"hh\:mm\:ss", CultureInfo.CurrentCulture).
type ParseExactAttribute (format : string) =
inherit Attribute ()
/// Attribute indicating that this field should be parsed in the invariant culture, rather than the
/// default current culture.
/// For example, on a TimeSpan field, with [<InvariantCulture>] and [<ArgumentParseExact @"hh\:mm\:ss">], we will call
/// `TimeSpan.ParseExact (s, @"hh\:mm\:ss", CultureInfo.InvariantCulture).
type InvariantCultureAttribute () =
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 ()

View File

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

View File

@@ -1,3 +1,18 @@
WoofWare.Myriad.Plugins.ArgParserAttribute inherit System.Attribute
WoofWare.Myriad.Plugins.ArgParserAttribute..ctor [constructor]: bool
WoofWare.Myriad.Plugins.ArgParserAttribute..ctor [constructor]: unit
WoofWare.Myriad.Plugins.ArgParserAttribute.DefaultIsExtensionMethod [static property]: [read-only] bool
WoofWare.Myriad.Plugins.ArgParserAttribute.get_DefaultIsExtensionMethod [static method]: unit -> bool
WoofWare.Myriad.Plugins.ArgumentDefaultEnvironmentVariableAttribute inherit System.Attribute
WoofWare.Myriad.Plugins.ArgumentDefaultEnvironmentVariableAttribute..ctor [constructor]: string
WoofWare.Myriad.Plugins.ArgumentDefaultFunctionAttribute inherit System.Attribute
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..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..ctor [constructor]: string
WoofWare.Myriad.Plugins.GenerateMockAttribute inherit System.Attribute
@@ -10,6 +25,8 @@ WoofWare.Myriad.Plugins.HttpClientAttribute..ctor [constructor]: bool
WoofWare.Myriad.Plugins.HttpClientAttribute..ctor [constructor]: unit
WoofWare.Myriad.Plugins.HttpClientAttribute.DefaultIsExtensionMethod [static property]: [read-only] bool
WoofWare.Myriad.Plugins.HttpClientAttribute.get_DefaultIsExtensionMethod [static method]: unit -> bool
WoofWare.Myriad.Plugins.InvariantCultureAttribute inherit System.Attribute
WoofWare.Myriad.Plugins.InvariantCultureAttribute..ctor [constructor]: unit
WoofWare.Myriad.Plugins.JsonParseAttribute inherit System.Attribute
WoofWare.Myriad.Plugins.JsonParseAttribute..ctor [constructor]: bool
WoofWare.Myriad.Plugins.JsonParseAttribute..ctor [constructor]: unit
@@ -20,11 +37,22 @@ WoofWare.Myriad.Plugins.JsonSerializeAttribute..ctor [constructor]: bool
WoofWare.Myriad.Plugins.JsonSerializeAttribute..ctor [constructor]: unit
WoofWare.Myriad.Plugins.JsonSerializeAttribute.DefaultIsExtensionMethod [static property]: [read-only] bool
WoofWare.Myriad.Plugins.JsonSerializeAttribute.get_DefaultIsExtensionMethod [static method]: unit -> bool
WoofWare.Myriad.Plugins.ParseExactAttribute inherit System.Attribute
WoofWare.Myriad.Plugins.ParseExactAttribute..ctor [constructor]: string
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.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..ctor [constructor]: unit
WoofWare.Myriad.Plugins.RestEase inherit obj
WoofWare.Myriad.Plugins.RestEase+BaseAddressAttribute inherit System.Attribute
WoofWare.Myriad.Plugins.RestEase+BaseAddressAttribute..ctor [constructor]: string
WoofWare.Myriad.Plugins.RestEase+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..ctor [constructor]: string
WoofWare.Myriad.Plugins.RestEase+GetAttribute inherit System.Attribute

View File

@@ -1,10 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net9.0</TargetFramework>
<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
<IsPackable>false</IsPackable>
<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>
<ItemGroup>
@@ -12,10 +17,10 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="ApiSurface" Version="4.0.40" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.10.0"/>
<PackageReference Include="NUnit" Version="4.1.0"/>
<PackageReference Include="NUnit3TestAdapter" Version="4.5.0"/>
<PackageReference Include="ApiSurface" Version="4.1.20" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.13.0"/>
<PackageReference Include="NUnit" Version="4.3.2"/>
<PackageReference Include="NUnit3TestAdapter" Version="5.0.0"/>
</ItemGroup>
<ItemGroup>

View File

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

View File

@@ -1,5 +1,5 @@
{
"version": "3.1",
"version": "3.6",
"publicReleaseRefSpec": [
"^refs/heads/main$"
],
@@ -10,6 +10,6 @@
":/Directory.Build.props",
":/global.json",
"./",
"^./Test"
":^Test"
]
}
}

View File

@@ -58,7 +58,7 @@ module PureGymDtos =
[
"""{"latitude": 1.0, "longitude": 3.0}""",
{
GymLocation.Latitude = 1.0
GymLocation.Latitude = 1.0<measure>
Longitude = 3.0
}
]
@@ -96,7 +96,7 @@ module PureGymDtos =
Location =
{
Longitude = -0.110252
Latitude = 51.480401
Latitude = 51.480401<measure>
}
TimeZone = "Europe/London"
ReopenDate = "2021-04-12T00:00:00+01 Europe/London"

View File

@@ -0,0 +1,706 @@
namespace WoofWare.Myriad.Plugins.Test
open System
open System.Threading
open NUnit.Framework
open FsUnitTyped
open ConsumePlugin
open FsCheck
[<TestFixture>]
module TestArgParser =
[<TestCase true>]
[<TestCase false>]
let ``Positionals get parsed: they don't have to be strings`` (sep : bool) =
let getEnvVar (_ : string) = failwith "should not call"
let property
(fooSep : bool)
(barSep : bool)
(bazSep : bool)
(pos0 : int list)
(pos1 : int list)
(pos2 : int list)
(pos3 : int list)
(pos4 : int list)
=
let args =
[
yield! pos0 |> List.map string<int>
if fooSep then
yield "--foo=3"
else
yield "--foo"
yield "3"
yield! pos1 |> List.map string<int>
if barSep then
yield "--bar=4"
else
yield "--bar"
yield "4"
yield! pos2 |> List.map string<int>
if bazSep then
yield "--baz=true"
else
yield "--baz"
yield "true"
yield! pos3 |> List.map string<int>
if sep then
yield "--"
yield! pos4 |> List.map string<int>
]
BasicWithIntPositionals.parse' getEnvVar args
|> shouldEqual
{
Foo = 3
Bar = "4"
Baz = true
Rest = pos0 @ pos1 @ pos2 @ pos3 @ pos4
}
Check.QuickThrowOnFailure property
[<Test>]
let ``Arg-like thing appearing before double dash`` () =
let envCalls = ref 0
let getEnvVar (_ : string) =
Interlocked.Increment envCalls |> ignore<int>
None
let args = [ "--foo=3" ; "--non-existent" ; "--bar=4" ; "--baz=true" ]
let exc =
Assert.Throws<exn> (fun () -> Basic.parse' getEnvVar args |> ignore<Basic>)
envCalls.Value |> shouldEqual 0
exc.Message
|> shouldEqual
"""Unable to process supplied arg --non-existent. Help text follows.
--foo int32 : This is a foo!
--bar string
--baz bool
--rest string (positional args) (can be repeated) : Here's where the rest of the args go"""
[<Test>]
let ``Can supply positional args with key`` () =
let envCalls = ref 0
let getEnvVar (_ : string) =
Interlocked.Increment envCalls |> ignore<int>
None
let property (args : (int * bool) list) (afterDoubleDash : int list option) =
let flatArgs =
args
|> List.collect (fun (value, sep) ->
if sep then
[ $"--rest=%i{value}" ]
else
[ "--rest" ; string<int> value ]
)
|> fun l -> l @ [ "--foo=3" ; "--bar=4" ; "--baz=true" ]
let flatArgs, expected =
match afterDoubleDash with
| None -> flatArgs, List.map fst args
| Some rest -> flatArgs @ [ "--" ] @ (List.map string<int> rest), List.map fst args @ rest
BasicWithIntPositionals.parse' getEnvVar flatArgs
|> shouldEqual
{
Foo = 3
Bar = "4"
Baz = true
Rest = expected
}
Check.QuickThrowOnFailure property
envCalls.Value |> shouldEqual 0
[<Test>]
let ``Consume multiple occurrences of required arg`` () =
let envCalls = ref 0
let getEnvVar (_ : string) =
Interlocked.Increment envCalls |> ignore<int>
None
let args = [ "--foo=3" ; "--rest" ; "7" ; "--bar=4" ; "--baz=true" ; "--rest=8" ]
let result = BasicNoPositionals.parse' getEnvVar args
envCalls.Value |> shouldEqual 0
result
|> shouldEqual
{
Foo = 3
Bar = "4"
Baz = true
Rest = [ 7 ; 8 ]
}
[<Test>]
let ``Gracefully handle invalid multiple occurrences of required arg`` () =
let envCalls = ref 0
let getEnvVar (_ : string) =
Interlocked.Increment envCalls |> ignore<int>
None
let args = [ "--foo=3" ; "--foo" ; "9" ; "--bar=4" ; "--baz=true" ; "--baz=false" ]
let exc =
Assert.Throws<exn> (fun () -> Basic.parse' getEnvVar args |> ignore<Basic>)
envCalls.Value |> shouldEqual 0
exc.Message
|> shouldEqual
"""Errors during parse!
Argument '--foo' was supplied multiple times: 3 and 9
Argument '--baz' was supplied multiple times: True and false"""
[<Test>]
let ``Args appearing after double dash are positional`` () =
let envCalls = ref 0
let getEnvVar (_ : string) =
Interlocked.Increment envCalls |> ignore<int>
None
let args = [ "--" ; "--foo=3" ; "--bar=4" ; "--baz=true" ]
let exc =
Assert.Throws<exn> (fun () -> Basic.parse' getEnvVar args |> ignore<Basic>)
exc.Message
|> shouldEqual
"""Errors during parse!
Required argument '--foo' received no value
Required argument '--bar' received no value
Required argument '--baz' received no value"""
envCalls.Value |> shouldEqual 0
[<Test>]
let ``Help text`` () =
let getEnvVar (s : string) =
s |> shouldEqual "CONSUMEPLUGIN_THINGS"
Some "hi!"
let exc =
Assert.Throws<exn> (fun () -> Basic.parse' getEnvVar [ "--help" ] |> ignore<Basic>)
exc.Message
|> shouldEqual
"""Help text requested.
--foo int32 : This is a foo!
--bar string
--baz bool
--rest string (positional args) (can be repeated) : Here's where the rest of the args go"""
[<Test>]
let ``Help text, with default values`` () =
let envVars = ref 0
let getEnvVar (_ : string) =
Interlocked.Increment envVars |> ignore<int>
None
let exc =
Assert.Throws<exn> (fun () -> LoadsOfTypes.parse' getEnvVar [ "--help" ] |> ignore<LoadsOfTypes>)
exc.Message
|> shouldEqual
"""Help text requested.
--foo int32
--bar string
--baz bool
--some-file FileInfo
--some-directory DirectoryInfo
--some-list DirectoryInfo (can be repeated)
--optional-thing-with-no-default int32 (optional)
--optional-thing bool (default value: True)
--another-optional-thing int32 (default value: 3)
--yet-another-optional-thing string (default value populated from env var CONSUMEPLUGIN_THINGS)
--positionals int32 (positional args) (can be repeated)"""
envVars.Value |> shouldEqual 0
[<Test>]
let ``Default values`` () =
let getEnvVar (s : string) =
s |> shouldEqual "CONSUMEPLUGIN_THINGS"
Some "hi!"
let args =
[
"--foo"
"3"
"--bar=some string"
"--baz"
"--some-file=/path/to/file"
"--some-directory"
"/a/dir"
"--another-optional-thing"
"3000"
]
let result = LoadsOfTypes.parse' getEnvVar args
result.OptionalThing |> shouldEqual (Choice2Of2 true)
result.OptionalThingWithNoDefault |> shouldEqual None
result.AnotherOptionalThing |> shouldEqual (Choice1Of2 3000)
result.YetAnotherOptionalThing |> shouldEqual (Choice2Of2 "hi!")
[<Test>]
let ``ParseExact and help`` () =
let count = ref 0
let getEnvVar (_ : string) =
Interlocked.Increment count |> ignore<int>
None
let exc =
Assert.Throws<exn> (fun () -> DatesAndTimes.parse' getEnvVar [ "--help" ] |> ignore<DatesAndTimes>)
exc.Message
|> shouldEqual
@"Help text requested.
--plain TimeSpan
--invariant TimeSpan
--exact TimeSpan : An exact time please [Parse format (.NET): hh\:mm\:ss]
--invariant-exact TimeSpan : [Parse format (.NET): hh\:mm\:ss]"
count.Value |> shouldEqual 0
[<Test>]
let rec ``TimeSpans and their attributes`` () =
let count = ref 0
let getEnvVar (_ : string) =
Interlocked.Increment count |> ignore<int>
None
let parsed =
DatesAndTimes.parse'
getEnvVar
[
"--exact=11:34:00"
"--plain=1"
"--invariant=23:59"
"--invariant-exact=23:59:00"
]
parsed.Plain |> shouldEqual (TimeSpan (1, 0, 0, 0))
parsed.Invariant |> shouldEqual (TimeSpan (23, 59, 00))
parsed.Exact |> shouldEqual (TimeSpan (11, 34, 00))
parsed.InvariantExact |> shouldEqual (TimeSpan (23, 59, 00))
let exc =
Assert.Throws<exn> (fun () ->
DatesAndTimes.parse'
getEnvVar
[
"--exact=11:34:00"
"--plain=1"
"--invariant=23:59"
"--invariant-exact=23:59"
]
|> ignore<DatesAndTimes>
)
exc.Message
|> shouldEqual
"""Errors during parse!
Input string was not in a correct format. (at arg --invariant-exact=23:59)
Required argument '--invariant-exact' received no value"""
let exc =
Assert.Throws<exn> (fun () ->
DatesAndTimes.parse'
getEnvVar
[
"--exact=11:34"
"--plain=1"
"--invariant=23:59"
"--invariant-exact=23:59:00"
]
|> ignore<DatesAndTimes>
)
exc.Message
|> shouldEqual
"""Errors during parse!
Input string was not in a correct format. (at arg --exact=11:34)
Required argument '--exact' received no value"""
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"
Some 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"
Some 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"""

View File

@@ -43,7 +43,7 @@ module TestGift =
member _.WithACard g message =
$"%s{g} with a card saying '%s{message}'"
member _.Wrapped g paper = $"%s{g} wrapped in %A{paper} paper"
member _.Wrapped g paper = $"%s{g} wrapped in %O{paper} paper"
}
}

View File

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

View File

@@ -33,4 +33,4 @@ module TestPathParam =
let api = PureGymApi.make client
api.GetPathParam("hello/world?(hi)").Result
|> shouldEqual "hello%2fworld%3f(hi)"
|> shouldEqual "hello%2Fworld%3F%28hi%29"

View File

@@ -52,7 +52,13 @@ module TestVariableHeader =
api.GetPathParam("param").Result.Split "\n"
|> Array.sort
|> shouldEqual [| "Authorization: -99" ; "Header-Name: Header-Value" ; "X-Foo: 11" |]
|> shouldEqual
[|
"Authorization: -99"
"Header-Name: Header-Value"
"Something-Else: val"
"X-Foo: 11"
|]
someHeaderCount.Value |> shouldEqual 11
someOtherHeaderCount.Value |> shouldEqual -99
@@ -98,11 +104,23 @@ module TestVariableHeader =
api.GetPathParam("param").Result.Split "\n"
|> Array.sort
|> shouldEqual [| "Authorization: -99" ; "Header-Name: Header-Value" ; "X-Foo: 11" |]
|> shouldEqual
[|
"Authorization: -99"
"Header-Name: Header-Value"
"Something-Else: val"
"X-Foo: 11"
|]
api.GetPathParam("param").Result.Split "\n"
|> Array.sort
|> shouldEqual [| "Authorization: -98" ; "Header-Name: Header-Value" ; "X-Foo: 12" |]
|> shouldEqual
[|
"Authorization: -98"
"Header-Name: Header-Value"
"Something-Else: val"
"X-Foo: 12"
|]
someHeaderCount.Value |> shouldEqual 12
someOtherHeaderCount.Value |> shouldEqual -98

View File

@@ -49,3 +49,15 @@ module TestJsonParse =
let actual = s |> JsonNode.Parse |> InnerType.jsonParse
actual |> shouldEqual expected
[<TestCase("thing", SomeEnum.Thing)>]
[<TestCase("Thing", SomeEnum.Thing)>]
[<TestCase("THING", SomeEnum.Thing)>]
[<TestCase("blah", SomeEnum.Blah)>]
[<TestCase("Blah", SomeEnum.Blah)>]
[<TestCase("BLAH", SomeEnum.Blah)>]
let ``Can deserialise enum`` (str : string, expected : SomeEnum) =
sprintf "\"%s\"" str
|> JsonNode.Parse
|> SomeEnum.jsonParse
|> shouldEqual expected

View File

@@ -3,7 +3,7 @@ namespace WoofWare.Myriad.Plugins.Test
open System
open System.Collections.Generic
open System.Text.Json.Nodes
open FsCheck.Random
open FsCheck.FSharp
open Microsoft.FSharp.Reflection
open NUnit.Framework
open FsCheck
@@ -15,21 +15,21 @@ module TestJsonSerde =
let uriGen : Gen<Uri> =
gen {
let! suffix = Arb.generate<int>
let! suffix = ArbMap.generate<int> ArbMap.defaults
return Uri $"https://example.com/%i{suffix}"
}
let rec innerGen (count : int) : Gen<InnerTypeWithBoth> =
gen {
let! guid = Arb.generate<Guid>
let! mapKeys = Gen.listOf Arb.generate<NonNull<string>>
let! guid = ArbMap.generate<Guid> ArbMap.defaults
let! mapKeys = Gen.listOf (ArbMap.generate<NonNull<string>> ArbMap.defaults)
let mapKeys = mapKeys |> List.map _.Get |> List.distinct
let! mapValues = Gen.listOfLength mapKeys.Length uriGen
let map = List.zip mapKeys mapValues |> Map.ofList
let! concreteDictKeys =
if count > 0 then
Gen.listOf Arb.generate<NonNull<string>>
Gen.listOf (ArbMap.generate<NonNull<string>> ArbMap.defaults)
else
Gen.constant []
@@ -50,13 +50,16 @@ module TestJsonSerde =
|> List.map KeyValuePair
|> Dictionary
let! readOnlyDictKeys = Gen.listOf Arb.generate<NonNull<string>>
let! readOnlyDictKeys = Gen.listOf (ArbMap.generate<NonNull<string>> ArbMap.defaults)
let readOnlyDictKeys = readOnlyDictKeys |> List.map _.Get |> List.distinct
let! readOnlyDictValues = Gen.listOfLength readOnlyDictKeys.Length (Gen.listOf Arb.generate<char>)
let! readOnlyDictValues =
Gen.listOfLength readOnlyDictKeys.Length (Gen.listOf (ArbMap.generate<char> ArbMap.defaults))
let readOnlyDict = List.zip readOnlyDictKeys readOnlyDictValues |> readOnlyDict
let! dictKeys = Gen.listOf uriGen
let! dictValues = Gen.listOfLength dictKeys.Length Arb.generate<bool>
let! dictValues = Gen.listOfLength dictKeys.Length (ArbMap.generate<bool> ArbMap.defaults)
let dict = List.zip dictKeys dictValues |> dict
return
@@ -71,13 +74,38 @@ module TestJsonSerde =
let outerGen : Gen<JsonRecordTypeWithBoth> =
gen {
let! a = Arb.generate<int>
let! b = Arb.generate<NonNull<string>>
let! c = Gen.listOf Arb.generate<int>
let! a = ArbMap.generate<int> ArbMap.defaults
let! b = ArbMap.generate<NonNull<string>> ArbMap.defaults
let! c = Gen.listOf (ArbMap.generate<int> ArbMap.defaults)
let! depth = Gen.choose (0, 2)
let! d = innerGen depth
let! e = Gen.arrayOf Arb.generate<NonNull<string>>
let! f = Gen.arrayOf Arb.generate<int>
let! e = Gen.arrayOf (ArbMap.generate<NonNull<string>> ArbMap.defaults)
let! arr = Gen.arrayOf (ArbMap.generate<int> ArbMap.defaults)
let! byte = ArbMap.generate ArbMap.defaults
let! sbyte = ArbMap.generate ArbMap.defaults
let! i = ArbMap.generate ArbMap.defaults
let! i32 = ArbMap.generate ArbMap.defaults
let! i64 = ArbMap.generate ArbMap.defaults
let! u = ArbMap.generate ArbMap.defaults
let! u32 = ArbMap.generate ArbMap.defaults
let! u64 = ArbMap.generate ArbMap.defaults
let! f =
ArbMap.generate ArbMap.defaults
|> Gen.filter (fun s -> Double.IsFinite (s / 1.0<measure>))
let! f32 =
ArbMap.generate ArbMap.defaults
|> Gen.filter (fun s -> Single.IsFinite (s / 1.0f<measure>))
let! single =
ArbMap.generate ArbMap.defaults
|> Gen.filter (fun s -> Single.IsFinite (s / 1.0f<measure>))
let! intMeasureOption = ArbMap.generate ArbMap.defaults
let! intMeasureNullable = ArbMap.generate ArbMap.defaults
let! someEnum = Gen.choose (0, 1)
let! timestamp = ArbMap.generate ArbMap.defaults
return
{
@@ -86,7 +114,23 @@ module TestJsonSerde =
C = c
D = d
E = e |> Array.map _.Get
Arr = arr
Byte = byte
Sbyte = sbyte
I = i
I32 = i32
I64 = i64
U = u
U32 = u32
U64 = u64
F = f
F32 = f32
Single = single
IntMeasureOption = intMeasureOption
IntMeasureNullable = intMeasureNullable
Enum = enum<SomeEnum> someEnum
Timestamp = timestamp
Unit = ()
}
}
@@ -104,6 +148,82 @@ module TestJsonSerde =
property |> Prop.forAll (Arb.fromGen outerGen) |> Check.QuickThrowOnFailure
[<Test>]
let ``Single example of big record`` () =
let guid = Guid.Parse "dfe24db5-9f8d-447b-8463-4c0bcf1166d5"
let data =
{
A = 3
B = "hello!"
C = [ 1 ; -9 ]
D =
{
Thing = guid
Map = Map.ofList []
ReadOnlyDict = readOnlyDict []
Dict = dict []
ConcreteDict = Dictionary ()
}
E = [| "I'm-a-string" |]
Arr = [| -18883 ; 9100 |]
Byte = 87uy<measure>
Sbyte = 89y<measure>
I = 199993345<measure>
I32 = -485832<measure>
I64 = -13458625689L<measure>
U = 458582u<measure>
U32 = 857362147u<measure>
U64 = 1234567892123414596UL<measure>
F = 8833345667.1<measure>
F32 = 1000.98f<measure>
Single = 0.334f<measure>
IntMeasureOption = Some 981<measure>
IntMeasureNullable = Nullable -883<measure>
Enum = enum<SomeEnum> 1
Timestamp = DateTimeOffset (2024, 07, 01, 17, 54, 00, TimeSpan.FromHours 1.0)
Unit = ()
}
let expected =
"""{
"a": 3,
"b": "hello!",
"c": [1, -9],
"d": {
"it\u0027s-a-me": "dfe24db5-9f8d-447b-8463-4c0bcf1166d5",
"map": {},
"readOnlyDict": {},
"dict": {},
"concreteDict": {}
},
"e": ["I\u0027m-a-string"],
"arr": [-18883, 9100],
"byte": 87,
"sbyte": 89,
"i": 199993345,
"i32": -485832,
"i64": -13458625689,
"u": 458582,
"u32": 857362147,
"u64": 1234567892123414596,
"f": 8833345667.1,
"f32": 1000.98,
"single": 0.334,
"intMeasureOption": 981,
"intMeasureNullable": -883,
"enum": 1,
"timestamp": "2024-07-01T17:54:00.0000000\u002B01:00",
"unit": {}
}
"""
|> fun s -> s.ToCharArray ()
|> Array.filter (fun c -> not (Char.IsWhiteSpace c))
|> fun s -> new String (s)
JsonRecordTypeWithBoth.toJsonNode(data).ToJsonString () |> shouldEqual expected
JsonRecordTypeWithBoth.jsonParse (JsonNode.Parse expected) |> shouldEqual data
[<Test>]
let ``Guids are treated just like strings`` () =
let guidStr = "b1e7496e-6e79-4158-8579-a01de355d3b2"
@@ -140,8 +260,7 @@ module TestJsonSerde =
}
let sanitiseRec (r : JsonRecordTypeWithBoth) : JsonRecordTypeWithBoth =
{
A = r.A
{ r with
B = if isNull r.B then "<null>" else r.B
C =
if Object.ReferenceEquals (r.C, (null : obj)) then
@@ -150,11 +269,11 @@ module TestJsonSerde =
r.C
D = sanitiseInner r.D
E = if isNull r.E then [||] else r.E
F =
if Object.ReferenceEquals (r.F, (null : obj)) then
Arr =
if Object.ReferenceEquals (r.Arr, (null : obj)) then
[||]
else
r.F
r.Arr
}
let duGen =
@@ -164,10 +283,10 @@ module TestJsonSerde =
match case with
| 0 -> return FirstDu.EmptyCase
| 1 ->
let! s = Arb.generate<NonNull<string>>
let! s = ArbMap.generate<NonNull<string>> ArbMap.defaults
return FirstDu.Case1 s.Get
| 2 ->
let! i = Arb.generate<int>
let! i = ArbMap.generate<int> ArbMap.defaults
let! record = outerGen
return FirstDu.Case2 (record, i)
| _ -> return failwith $"unexpected: %i{case}"
@@ -187,7 +306,6 @@ module TestJsonSerde =
[<Test>]
let ``DU generator covers all cases`` () =
let rand = Random ()
let cases = FSharpType.GetUnionCases typeof<FirstDu>
let counts = Array.zeroCreate<int> cases.Length
@@ -195,11 +313,176 @@ module TestJsonSerde =
let mutable i = 0
while i < 10_000 && Array.exists (fun i -> i = 0) counts do
let du = Gen.eval 10 (StdGen.StdGen (rand.Next (), rand.Next ())) duGen
let property (du : FirstDu) =
let tag = decompose du
counts.[tag] <- counts.[tag] + 1
i <- i + 1
true
Check.One (Config.Quick, Prop.forAll (Arb.fromGen duGen) property)
for i in counts do
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

View File

@@ -34,3 +34,16 @@ module TestMockGenerator =
mock.Mem1 3 'a' |> shouldEqual "aaa"
mock.Mem2 (3, "hi") 'a' |> shouldEqual "hiahiahi"
mock.Mem3 (3, "hi") 'a' |> shouldEqual "hiahiahi"
[<Test>]
let ``Example of use: properties`` () =
let mock : TypeWithProperties =
{ TypeWithPropertiesMock.Empty with
Mem1 = fun i -> async { return Option.toArray i }
Prop1 = fun () -> 44
}
:> _
mock.Mem1 (Some "hi") |> Async.RunSynchronously |> shouldEqual [| "hi" |]
mock.Prop1 |> shouldEqual 44

View File

@@ -0,0 +1,36 @@
namespace WoofWare.Myriad.Plugins.Test
open System
open SomeNamespace
open NUnit.Framework
open FsUnitTyped
[<TestFixture>]
module TestMockGeneratorNoAttr =
[<Test>]
let ``Example of use: IPublicType`` () =
let mock : IPublicTypeNoAttr =
{ PublicTypeNoAttrMock.Empty with
Mem1 = fun (s, count) -> List.replicate count s
}
:> _
let _ =
Assert.Throws<NotImplementedException> (fun () -> mock.Mem2 "hi" |> ignore<int>)
mock.Mem1 ("hi", 3) |> shouldEqual [ "hi" ; "hi" ; "hi" ]
[<Test>]
let ``Example of use: curried args`` () =
let mock : CurriedNoAttr<_> =
{ CurriedNoAttrMock.Empty () with
Mem1 = fun i c -> Array.replicate i c |> String
Mem2 = fun (i, s) c -> String.concat $"%c{c}" (List.replicate i s)
Mem3 = fun (i, s) c -> String.concat $"%c{c}" (List.replicate i s)
}
:> _
mock.Mem1 3 'a' |> shouldEqual "aaa"
mock.Mem2 (3, "hi") 'a' |> shouldEqual "hiahiahi"
mock.Mem3 (3, "hi") 'a' |> shouldEqual "hiahiahi"

View File

@@ -0,0 +1,84 @@
namespace WoofWare.Myriad.Plugins.Test
open System.Text.Json.Nodes
open NUnit.Framework
open FsUnitTyped
open WoofWare.Myriad.Plugins
[<TestFixture>]
module TestSwaggerParse =
[<Test>]
let ``Can parse parameters`` () : unit =
let s =
"""{
"tags": [
"organization"
],
"summary": "Check if a user is a member of an organization",
"operationId": "orgIsMember",
"parameters": [
{
"type": "string",
"description": "name of the organization",
"name": "org",
"in": "path",
"required": true
},
{
"type": "string",
"description": "username of the user",
"name": "username",
"in": "path",
"required": true
}
],
"responses": {
"204": {
"description": "user is a member"
},
"303": {
"description": "redirection to /orgs/{org}/public_members/{username}"
},
"404": {
"description": "user is not a member"
}
}
}
"""
|> JsonNode.Parse
s.AsObject ()
|> SwaggerEndpoint.Parse
|> shouldEqual
{
Consumes = None
Produces = None
Tags = [ "organization" ]
Summary = "Check if a user is a member of an organization"
OperationId = OperationId "orgIsMember"
Parameters =
[
{
Type = Definition.String
Description = Some "name of the organization"
Name = "org"
In = ParameterIn.Path "org"
Required = Some true
}
{
Type = Definition.String
Description = Some "username of the user"
Name = "username"
In = ParameterIn.Path "username"
Required = Some true
}
]
|> Some
Responses =
[
204, Definition.Unspecified
303, Definition.Unspecified
404, Definition.Unspecified
]
|> Map.ofList
}

View File

@@ -1,9 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net9.0</TargetFramework>
<IsPackable>false</IsPackable>
<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>
<TestingPlatformDotnetTestSupport>true</TestingPlatformDotnetTestSupport>
</PropertyGroup>
<ItemGroup>
@@ -21,24 +27,27 @@
<Compile Include="TestHttpClient\TestVaultClient.fs" />
<Compile Include="TestHttpClient\TestVariableHeader.fs" />
<Compile Include="TestMockGenerator\TestMockGenerator.fs" />
<Compile Include="TestMockGenerator\TestMockGeneratorNoAttr.fs" />
<Compile Include="TestJsonSerialize\TestJsonSerde.fs" />
<Compile Include="TestCataGenerator\TestCataGenerator.fs" />
<Compile Include="TestCataGenerator\TestDirectory.fs" />
<Compile Include="TestCataGenerator\TestGift.fs" />
<Compile Include="TestCataGenerator\TestMyList.fs" />
<Compile Include="TestCataGenerator\TestMyList2.fs" />
<Compile Include="TestArgParser\TestArgParser.fs" />
<Compile Include="TestSwagger\TestSwaggerParse.fs" />
<Compile Include="TestRemoveOptions.fs"/>
<Compile Include="TestSurface.fs"/>
<None Include="../.github/workflows/dotnet.yaml" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="ApiSurface" Version="4.0.40"/>
<PackageReference Include="FsCheck" Version="2.16.6"/>
<PackageReference Include="FsUnit" Version="6.0.0"/>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.10.0"/>
<PackageReference Include="NUnit" Version="4.1.0"/>
<PackageReference Include="NUnit3TestAdapter" Version="4.5.0"/>
<PackageReference Include="ApiSurface" Version="4.1.20"/>
<PackageReference Include="FsCheck" Version="3.2.0"/>
<PackageReference Include="FsUnit" Version="7.0.1"/>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.13.0"/>
<PackageReference Include="NUnit" Version="4.3.2"/>
<PackageReference Include="NUnit3TestAdapter" Version="5.0.0"/>
</ItemGroup>
<ItemGroup>

View 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.

File diff suppressed because it is too large Load Diff

View File

@@ -2,72 +2,7 @@ namespace WoofWare.Myriad.Plugins
open Fantomas.FCS.Syntax
open Fantomas.FCS.Text.Range
open Fantomas.FCS.Xml
type internal ParameterInfo =
{
Attributes : SynAttribute list
IsOptional : bool
Id : Ident option
Type : SynType
}
type internal TupledArg =
{
HasParen : bool
Args : ParameterInfo list
}
type internal MemberInfo =
{
ReturnType : SynType
Accessibility : SynAccess option
/// Each element of this list is a list of args in a tuple, or just one arg if not a tuple.
Args : TupledArg list
Identifier : Ident
Attributes : SynAttribute list
XmlDoc : PreXmlDoc option
IsInline : bool
IsMutable : bool
}
[<RequireQualifiedAccess>]
type internal PropertyAccessors =
| Get
| Set
| GetSet
type internal PropertyInfo =
{
Type : SynType
Accessibility : SynAccess option
Attributes : SynAttribute list
XmlDoc : PreXmlDoc option
Accessors : PropertyAccessors
IsInline : bool
Identifier : Ident
}
type internal InterfaceType =
{
Attributes : SynAttribute list
Name : LongIdent
Inherits : SynType list
Members : MemberInfo list
Properties : PropertyInfo list
Generics : SynTyparDecls option
Accessibility : SynAccess option
}
type internal RecordType =
{
Name : Ident
Fields : SynField seq
Members : SynMemberDefns option
XmlDoc : PreXmlDoc option
Generics : SynTyparDecls option
Accessibility : SynAccess option
}
open WoofWare.Whippet.Fantomas
/// Anything that is part of an ADT.
/// A record is a product of stuff; this type represents one of those stuffs.
@@ -96,23 +31,21 @@ type internal AdtProduct =
[<RequireQualifiedAccess>]
module internal AstHelper =
let instantiateRecord (fields : (RecordFieldName * SynExpr option) list) : SynExpr =
let fields =
fields
|> List.map (fun (rfn, synExpr) -> SynExprRecordField (rfn, Some range0, synExpr, None))
SynExpr.Record (None, None, fields, range0)
let isEnum (SynTypeDefn.SynTypeDefn (_, repr, _, _, _, _)) : bool =
match repr with
| SynTypeDefnRepr.Simple (SynTypeDefnSimpleRepr.Enum _, _) -> true
| _ -> false
let defineRecordType (record : RecordType) : SynTypeDefn =
let name =
SynComponentInfo.create record.Name
|> SynComponentInfo.setAccessibility record.Accessibility
|> SynComponentInfo.setAccessibility record.TypeAccessibility
|> match record.XmlDoc with
| None -> id
| Some doc -> SynComponentInfo.withDocString doc
|> SynComponentInfo.setGenerics record.Generics
SynTypeDefnRepr.record (Seq.toList record.Fields)
SynTypeDefnRepr.recordWithAccess record.ImplAccessibility (Seq.toList record.Fields)
|> SynTypeDefn.create name
|> SynTypeDefn.withMemberDefns (defaultArg record.Members SynMemberDefns.Empty)

View File

@@ -3,12 +3,11 @@ namespace WoofWare.Myriad.Plugins
open Fantomas.FCS.Syntax
open Fantomas.FCS.SyntaxTrivia
open Fantomas.FCS.Xml
open Myriad.Core
open WoofWare.Whippet.Fantomas
[<RequireQualifiedAccess>]
module internal CataGenerator =
open Fantomas.FCS.Text.Range
open Myriad.Core.Ast
/// The user-provided DU contains cases, each of which contains fields.
/// We have a hard-coded set of things we know how to deal with as field contents.
@@ -175,20 +174,14 @@ module internal CataGenerator =
|> SynExpr.applyFunction (SynExpr.createLongIdent [ "Seq" ; "exactlyOne" ])
|> SynExpr.createLet
[
SynBinding.Let (
valData = SynValData.SynValData (None, SynValInfo.Empty, None),
pattern =
SynPat.tupleNoParen (
allArtificialTyparNames
|> List.map (fun (t : Ident) ->
SynPat.namedI (Ident.create (t.idText + "Stack") |> Ident.lowerFirstLetter)
)
),
expr =
SynExpr.applyFunction
(SynExpr.applyFunction (SynExpr.createIdent "loop") (SynExpr.createIdent "cata"))
(SynExpr.createIdent "instructions")
)
SynBinding.basicTuple
(allArtificialTyparNames
|> List.map (fun (t : Ident) ->
SynPat.namedI (Ident.create (t.idText + "Stack") |> Ident.lowerFirstLetter)
))
(SynExpr.applyFunction
(SynExpr.applyFunction (SynExpr.createIdent "loop") (SynExpr.createIdent "cata"))
(SynExpr.createIdent "instructions"))
]
]
|> SynExpr.sequential
@@ -463,18 +456,39 @@ module internal CataGenerator =
{
SynFieldData.Type = field.Type
Attrs = []
Ident = None
Ident = field.Name
}
|> SynField.make
)
SynUnionCase.Create (unionCase.Name, fields)
{
Name = unionCase.Name
XmlDoc = None
Access = None
Attributes = []
Fields = fields
}
|> SynUnionCase.create
)
let casesFromCases =
recursiveCases analysis
|> List.map (fun case ->
SynUnionCase.Create (case.Name, case.Fields |> List.map (fun field -> SynField.Create field.Type))
{
UnionCase.Name = case.Name
XmlDoc = None
Access = None
Attributes = []
Fields =
case.Fields
|> List.map (fun field ->
{
SynFieldData.Type = field.Type
Attrs = []
Ident = field.Name
}
)
}
|> SynUnionCase.create
)
let cases = casesFromProcess @ casesFromCases
@@ -539,8 +553,8 @@ module internal CataGenerator =
|> List.map (fun case ->
let arity =
SynValInfo.SynValInfo (
case.Fields |> List.map (fun field -> [ SynArgInfo.Empty ]),
SynArgInfo.Empty
case.Fields |> List.map (fun field -> [ SynArgInfo.empty ]),
SynArgInfo.empty
)
(SynType.var generics.[analysis.GenericName.idText], List.rev case.FlattenedFields)
@@ -564,11 +578,12 @@ module internal CataGenerator =
let domain =
field.FieldName
|> Option.map Ident.lowerFirstLetter
|> SynType.signatureParamOfType place
|> SynType.signatureParamOfType [] place false
acc |> SynType.funFromDomain domain
)
|> SynMemberDefn.abstractMember
[]
case.CataMethodIdent
None
arity
@@ -851,14 +866,12 @@ module internal CataGenerator =
else
[]
SynMatchClause.create
(SynPat.CreateLongIdent (SynLongIdent.create unionCase.Match, matchLhs))
matchBody
SynMatchClause.create (SynPat.identWithArgs unionCase.Match (SynArgPats.create matchLhs)) matchBody
)
SynExpr.createMatch (SynExpr.createIdent "x") matchCases
|> SynMatchClause.create (
SynPat.identWithArgs analysis.AssociatedProcessInstruction (SynArgPats.create [ Ident.create "x" ])
SynPat.identWithArgs analysis.AssociatedProcessInstruction (SynArgPats.createNamed [ "x" ])
)
/// Create the state-machine matches which deal with receiving the instruction
@@ -896,8 +909,8 @@ module internal CataGenerator =
|> Seq.mapi (fun i x -> (i, x))
|> Seq.choose (fun (i, case) ->
match case.Description with
| FieldDescription.NonRecursive _ -> case.ArgName |> Some
| FieldDescription.ListSelf _ -> case.ArgName |> Some
| FieldDescription.NonRecursive _ -> case.ArgName |> SynPat.namedI |> Some
| FieldDescription.ListSelf _ -> case.ArgName |> SynPat.namedI |> Some
| FieldDescription.Self _ -> None
)
|> Seq.toList
@@ -1058,7 +1071,7 @@ module internal CataGenerator =
(SynExpr.CreateConst 0)
(SynExpr.createLongIdent [ "instructions" ; "Count" ]))
body
SynExpr.CreateTuple (
SynExpr.tupleNoParen (
analysis
|> List.map (fun unionAnalysis -> [ unionAnalysis.StackName ] |> SynExpr.createLongIdent')
)
@@ -1070,17 +1083,11 @@ module internal CataGenerator =
body
|> SynExpr.createLet
[
SynExpr.TypeApp (
SynExpr.createIdent "ResizeArray",
range0,
(SynExpr.createIdent "ResizeArray")
|> SynExpr.typeApp
[
SynType.var (SynTypar.SynTypar (unionCase.GenericName, TyparStaticReq.None, false))
],
[],
Some range0,
range0,
range0
)
]
|> SynExpr.applyTo (SynExpr.CreateConst ())
|> SynBinding.basic [ unionCase.StackName ] []
]
@@ -1106,9 +1113,9 @@ module internal CataGenerator =
let moduleName = parentName + "Cata" |> Ident.create
let modInfo =
SynComponentInfo.create (parentName + "Cata" |> Ident.create)
SynComponentInfo.create moduleName
|> SynComponentInfo.withDocString (
PreXmlDoc.Create $" Methods to perform a catamorphism over the type %s{parentName}"
PreXmlDoc.create $"Methods to perform a catamorphism over the type %s{parentName}"
)
|> SynComponentInfo.addAttributes [ SynAttribute.requireQualifiedAccess ]
@@ -1155,7 +1162,7 @@ module internal CataGenerator =
[
for openStatement in opens do
yield SynModuleDecl.CreateOpen openStatement
yield SynModuleDecl.openAny openStatement
yield! cataStructures
yield cataRecord
yield
@@ -1167,53 +1174,30 @@ module internal CataGenerator =
]
|> SynModuleOrNamespace.createNamespace ns
let generate (context : GeneratorContext) : Output =
let ast, _ =
Ast.fromFilename context.InputFilename |> Async.RunSynchronously |> Array.head
/// This function comes from Myriad, and is therefore derived from an Apache 2.0-licenced work.
/// https://github.com/MoiraeSoftware/myriad/blob/3c9818faabf9d508c10c28d5ecd26e66fafb48a1/src/Myriad.Core/Ast.fs#L160
/// A copy of the Apache 2.0 licence is at ApacheLicence.txt.
let groupedTypeDefns (ast : ParsedInput) : (LongIdent * SynTypeDefn list) list =
let rec extractTypes (moduleDecls : SynModuleDecl list) (ns : LongIdent) =
[
for moduleDecl in moduleDecls do
match moduleDecl with
| SynModuleDecl.Types (types, _) -> yield (ns, types)
| SynModuleDecl.NestedModule (SynComponentInfo (_, _, _, longId, _, _, _, _), _, decls, _, _, _) ->
let combined = longId |> List.append ns
yield! (extractTypes decls combined)
| _ -> ()
]
let types = Ast.extractTypeDefn ast
[
match ast with
| ParsedInput.ImplFile (ParsedImplFileInput (_, _, _, _, _, modules, _, _, _)) ->
for SynModuleOrNamespace (namespaceId, _, _, moduleDecls, _, _, _, _, _) in modules do
yield! extractTypes moduleDecls namespaceId
| _ -> ()
]
let opens = AstHelper.extractOpens ast
let namespaceAndTypes =
types
|> List.choose (fun (ns, types) ->
let typeWithAttr =
types
|> List.tryPick (fun ty ->
match Ast.getAttribute<CreateCatamorphismAttribute> ty with
| None -> None
| Some attr -> Some (attr.ArgExpr, ty)
)
match typeWithAttr with
| Some taggedType ->
let unions, records, others =
(([], [], []), types)
||> List.fold (fun
(unions, records, others)
(SynTypeDefn.SynTypeDefn (_, repr, _, _, _, _) as ty) ->
match repr with
| SynTypeDefnRepr.Simple (SynTypeDefnSimpleRepr.Union _, _) ->
ty :: unions, records, others
| SynTypeDefnRepr.Simple (SynTypeDefnSimpleRepr.Record _, _) ->
unions, ty :: records, others
| _ -> unions, records, ty :: others
)
if not others.IsEmpty then
failwith
$"Error: all types recursively defined together with a CreateCatamorphism type must be discriminated unions or records. %+A{others}"
Some (ns, taggedType, unions, records)
| _ -> None
)
let modules =
namespaceAndTypes
|> List.map (fun (ns, taggedType, unions, records) -> createModule opens ns taggedType unions records)
Output.Ast modules
open Myriad.Core
/// Myriad generator that provides a catamorphism for an algebraic data type.
[<MyriadGenerator("create-catamorphism")>]
@@ -1222,4 +1206,52 @@ type CreateCatamorphismGenerator () =
interface IMyriadGenerator with
member _.ValidInputExtensions = [ ".fs" ]
member _.Generate (context : GeneratorContext) = CataGenerator.generate context
member _.Generate (context : GeneratorContext) =
let ast, _ =
Ast.fromFilename context.InputFilename |> Async.RunSynchronously |> Array.head
let types = CataGenerator.groupedTypeDefns ast
let opens = AstHelper.extractOpens ast
let namespaceAndTypes =
types
|> List.choose (fun (ns, types) ->
let typeWithAttr =
types
|> List.tryPick (fun ty ->
match SynTypeDefn.getAttribute typeof<CreateCatamorphismAttribute>.Name ty with
| None -> None
| Some attr -> Some (attr.ArgExpr, ty)
)
match typeWithAttr with
| Some taggedType ->
let unions, records, others =
(([], [], []), types)
||> List.fold (fun
(unions, records, others)
(SynTypeDefn.SynTypeDefn (_, repr, _, _, _, _) as ty) ->
match repr with
| SynTypeDefnRepr.Simple (SynTypeDefnSimpleRepr.Union _, _) ->
ty :: unions, records, others
| SynTypeDefnRepr.Simple (SynTypeDefnSimpleRepr.Record _, _) ->
unions, ty :: records, others
| _ -> unions, records, ty :: others
)
if not others.IsEmpty then
failwith
$"Error: all types recursively defined together with a CreateCatamorphism type must be discriminated unions or records. %+A{others}"
Some (ns, taggedType, unions, records)
| _ -> None
)
let modules =
namespaceAndTypes
|> List.map (fun (ns, taggedType, unions, records) ->
CataGenerator.createModule opens ns taggedType unions records
)
Output.Ast modules

View File

@@ -1,7 +1,9 @@
namespace WoofWare.Myriad.Plugins
open System.IO
open System.Net.Http
open Fantomas.FCS.Syntax
open WoofWare.Whippet.Fantomas
type internal HttpClientGeneratorOutputSpec =
{
@@ -12,6 +14,17 @@ type internal HttpClientGeneratorOutputSpec =
module internal HttpClientGenerator =
open Fantomas.FCS.Text.Range
let outputFile = FileInfo "/tmp/output.txt"
// do
// use _ = File.Create outputFile.FullName
// ()
let log (line : string) =
// use w = outputFile.AppendText ()
// w.WriteLine line
()
[<RequireQualifiedAccess>]
type PathSpec =
| Verbatim of string
@@ -60,6 +73,9 @@ module internal HttpClientGenerator =
BaseAddress : SynExpr option
BasePath : SynExpr option
Accessibility : SynAccess option
/// Headers which apply *only* to this endpoint.
/// For example, SynConst "Authorization" and SynConst "token BLAH".
Headers : (SynExpr * SynExpr) list
}
let httpMethodString (m : HttpMethod) : string =
@@ -219,7 +235,7 @@ module internal HttpClientGenerator =
SynExpr.CreateConst ("{" + substituteId + "}")
SynExpr.callMethod "ToString" (SynExpr.createIdent' varName)
|> SynExpr.pipeThroughFunction (
SynExpr.createLongIdent [ "System" ; "Web" ; "HttpUtility" ; "UrlEncode" ]
SynExpr.createLongIdent [ "System" ; "Uri" ; "EscapeDataString" ]
)
])
| _ -> template
@@ -257,11 +273,7 @@ module internal HttpClientGenerator =
| Some id -> id
let urlSeparator =
// apparent Myriad bug: `IndexOf '?'` gets formatted as `IndexOf ?` which is clearly wrong
let questionMark =
SynExpr.CreateConst 63
|> SynExpr.applyFunction (SynExpr.createIdent "char")
|> SynExpr.paren
let questionMark = SynExpr.CreateConst '?'
let containsQuestion =
info.UrlTemplate
@@ -275,9 +287,7 @@ module internal HttpClientGenerator =
SynExpr.createIdent' firstValueId
|> SynExpr.toString firstValue.Type
|> SynExpr.paren
|> SynExpr.pipeThroughFunction (
SynExpr.createLongIdent [ "System" ; "Web" ; "HttpUtility" ; "UrlEncode" ]
)
|> SynExpr.pipeThroughFunction (SynExpr.createLongIdent [ "System" ; "Uri" ; "EscapeDataString" ])
|> SynExpr.paren
|> SynExpr.plus (SynExpr.plus urlSeparator (SynExpr.CreateConst (firstKey + "=")))
@@ -290,9 +300,7 @@ module internal HttpClientGenerator =
SynExpr.toString paramValue.Type (SynExpr.createIdent' paramValueId)
|> SynExpr.paren
|> SynExpr.pipeThroughFunction (
SynExpr.createLongIdent [ "System" ; "Web" ; "HttpUtility" ; "UrlEncode" ]
)
|> SynExpr.pipeThroughFunction (SynExpr.createLongIdent [ "System" ; "Uri" ; "EscapeDataString" ])
|> SynExpr.paren
|> SynExpr.plus (SynExpr.plus uri (SynExpr.CreateConst ("&" + paramKey + "=")))
)
@@ -325,15 +333,33 @@ module internal HttpClientGenerator =
|> SynExpr.createMatch baseAddress
|> 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
SynExpr.applyFunction
uriIdent
(SynExpr.tuple
[
requestUriTrailer
SynExpr.createLongIdent [ "System" ; "UriKind" ; "Relative" ]
])
yield baseAddress
yield
SynExpr.applyFunction
uriIdent
(SynExpr.tuple
[
requestUriTrailer
SynExpr.createLongIdent [ "System" ; "UriKind" ; "Relative" ]
])
]
|> SynExpr.tuple
|> SynExpr.applyFunction uriIdent
@@ -408,14 +434,54 @@ module internal HttpClientGenerator =
retType
(SynExpr.createIdent "jsonNode")
let contentTypeHeader, memberHeaders =
info.Headers
|> List.partition (fun (headerName, headerValue) ->
match headerName |> SynExpr.stripOptionalParen with
| SynExpr.Const (SynConst.String ("Content-Type", _, _), _) -> true
| _ -> false
)
let contentTypeHeader =
match contentTypeHeader with
| [] -> None
| [ _, ct ] -> Some (SynExpr.stripOptionalParen ct)
| _ -> failwith "Unexpectedly got multiple Content-Type headers"
let createStringContent (contents : SynExpr) =
SynExpr.createNew
(SynType.createLongIdent' [ "System" ; "Net" ; "Http" ; "StringContent" ])
(SynExpr.tupleNoParen
[
yield contents
match contentTypeHeader with
| None -> ()
| Some ch ->
yield SynExpr.createNull ()
// Sigh, Gitea in particular passes "json" here
match ch with
| SynExpr.Const (SynConst.String ("json", _, _), _) ->
yield SynExpr.CreateConst "application/json"
| SynExpr.Const (SynConst.String ("html", _, _), _) -> yield SynExpr.CreateConst "text/html"
| _ -> yield ch
])
let handleBodyParams =
match bodyParam with
| None -> []
| Some (bodyParamType, bodyParamName) ->
match bodyParamType with
| BodyParamMethods.StreamContent
| BodyParamMethods.ByteArrayContent
| BodyParamMethods.StringContent ->
[
Let ("queryParams", createStringContent (SynExpr.createIdent' bodyParamName))
Do (
SynExpr.assign
(SynLongIdent.createS' [ "httpMessage" ; "Content" ])
(SynExpr.createIdent "queryParams")
)
]
| BodyParamMethods.StreamContent
| BodyParamMethods.ByteArrayContent ->
[
Let (
"queryParams",
@@ -425,50 +491,44 @@ module internal HttpClientGenerator =
(SynExpr.createIdent' bodyParamName)
)
Do (
SynExpr.LongIdentSet (
SynLongIdent.createS' [ "httpMessage" ; "Content" ],
SynExpr.createIdent "queryParams",
range0
)
SynExpr.assign
(SynLongIdent.createS' [ "httpMessage" ; "Content" ])
(SynExpr.createIdent "queryParams")
)
]
| BodyParamMethods.HttpContent ->
[
Do (
SynExpr.LongIdentSet (
SynLongIdent.createS' [ "httpMessage" ; "Content" ],
SynExpr.createIdent' bodyParamName,
range0
)
SynExpr.assign
(SynLongIdent.createS' [ "httpMessage" ; "Content" ])
(SynExpr.createIdent' bodyParamName)
)
]
| BodyParamMethods.Serialise ty ->
[
Let (
"queryParams",
SynExpr.createNew
(SynType.createLongIdent' [ "System" ; "Net" ; "Http" ; "StringContent" ])
(SynExpr.createIdent' bodyParamName
|> SynExpr.pipeThroughFunction (JsonSerializeGenerator.serializeNode ty)
|> SynExpr.pipeThroughFunction (
SynExpr.createLambda
"node"
(SynExpr.ifThenElse
(SynExpr.applyFunction
(SynExpr.createIdent "isNull")
(SynExpr.createIdent "node"))
(SynExpr.applyFunction
(SynExpr.createLongIdent [ "node" ; "ToJsonString" ])
(SynExpr.CreateConst ()))
(SynExpr.CreateConst "null"))
))
createStringContent (
SynExpr.createIdent' bodyParamName
|> SynExpr.pipeThroughFunction (fst (JsonSerializeGenerator.serializeNode ty))
|> SynExpr.pipeThroughFunction (
SynExpr.createLambda
"node"
(SynExpr.ifThenElse
(SynExpr.applyFunction
(SynExpr.createIdent "isNull")
(SynExpr.createIdent "node"))
(SynExpr.applyFunction
(SynExpr.createLongIdent [ "node" ; "ToJsonString" ])
(SynExpr.CreateConst ()))
(SynExpr.CreateConst "null"))
)
)
)
Do (
SynExpr.LongIdentSet (
SynLongIdent.createS' [ "httpMessage" ; "Content" ],
SynExpr.createIdent "queryParams",
range0
)
SynExpr.assign
(SynLongIdent.createS' [ "httpMessage" ; "Content" ])
(SynExpr.createIdent "queryParams")
)
]
@@ -532,6 +592,16 @@ module internal HttpClientGenerator =
|> Do
)
let setMemberHeaders =
memberHeaders
|> List.map (fun (headerName, headerValue) ->
// Best-effort: assume this is a message header.
SynExpr.applyFunction
(SynExpr.createLongIdent [ "httpMessage" ; "Headers" ; "Add" ])
(SynExpr.tuple [ headerName ; headerValue ])
|> Do
)
[
yield LetBang ("ct", SynExpr.createLongIdent [ "Async" ; "CancellationToken" ])
yield Let ("uri", requestUri)
@@ -547,6 +617,7 @@ module internal HttpClientGenerator =
yield! setVariableHeaders
yield! setConstantHeaders
yield! setMemberHeaders
yield
LetBang (
@@ -573,6 +644,9 @@ module internal HttpClientGenerator =
yield jsonNode
| String -> yield responseString
| Stream -> yield responseStream
| UnitType ->
// What we're returning doesn't depend on the content, so don't bother!
()
| _ ->
yield responseStream
yield jsonNode
@@ -657,6 +731,15 @@ module internal HttpClientGenerator =
| _ -> 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
(opens : SynOpenDeclTarget list)
(ns : LongIdent)
@@ -686,8 +769,17 @@ module internal HttpClientGenerator =
"Expected constant header parameters to be of the form [<Header (key, value)>], but got more than two args"
)
let baseAddress = extractBaseAddress interfaceType.Attributes
let basePath = extractBasePath interfaceType.Attributes
let baseAddress =
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 =
interfaceType.Properties
@@ -695,7 +787,7 @@ module internal HttpClientGenerator =
let headerInfo =
match extractHeaderInformation pi.Attributes with
| [ [ x ] ] -> x
| [ xs ] ->
| [ _ ] ->
failwith
"Expected exactly one Header parameter on the member, with exactly one arg; got one Header parameter with non-1-many args"
| [] ->
@@ -715,6 +807,16 @@ module internal HttpClientGenerator =
|> List.map (fun mem ->
let httpMethod, url = extractHttpInformation mem.Attributes
let specificHeaders =
extractHeaderInformation mem.Attributes
|> List.map (fun l ->
match l with
| [ x ; y ] -> x, y
| _ ->
failwith
$"Expected Header attribute on member %s{mem.Identifier.idText} to have exactly two arguments."
)
let shouldEnsureSuccess = not (shouldAllowAnyStatusCode mem.Attributes)
let returnType =
@@ -755,6 +857,7 @@ module internal HttpClientGenerator =
BaseAddress = baseAddress
BasePath = basePath
Accessibility = mem.Accessibility
Headers = specificHeaders
}
)
|> List.map (constructMember constantHeaders properties)
@@ -843,7 +946,7 @@ module internal HttpClientGenerator =
|> SynTypeDefn.create componentInfo
|> SynTypeDefn.withMemberDefns [ binding ]
SynModuleDecl.Types ([ containingType ], range0)
SynModuleDecl.createTypes [ containingType ]
else
SynBinding.basic [ Ident.create "make" ] (headerArgs @ [ clientCreationArg ]) interfaceImpl
@@ -886,10 +989,14 @@ type HttpClientGenerator () =
member _.ValidInputExtensions = [ ".fs" ]
member _.Generate (context : GeneratorContext) =
let targetedTypes =
MyriadParamParser.render context.AdditionalParameters
|> Map.map (fun _ v -> v.Split '!' |> Array.toList |> List.map DesiredGenerator.Parse)
let ast, _ =
Ast.fromFilename context.InputFilename |> Async.RunSynchronously |> Array.head
let types = Ast.extractTypeDefn ast
let types = Ast.getTypes ast
let opens = AstHelper.extractOpens ast
@@ -898,13 +1005,33 @@ type HttpClientGenerator () =
|> List.choose (fun (ns, types) ->
types
|> List.choose (fun typeDef ->
match Ast.getAttribute<HttpClientAttribute> typeDef with
| None -> None
match SynTypeDefn.getAttribute typeof<HttpClientAttribute>.Name typeDef with
| None ->
let name = SynTypeDefn.getName typeDef |> List.map _.idText |> String.concat "."
match Map.tryFind name targetedTypes with
| Some desired ->
desired
|> List.tryPick (fun generator ->
match generator with
| DesiredGenerator.HttpClient arg ->
let spec =
{
ExtensionMethods =
arg
|> Option.defaultValue
HttpClientAttribute.DefaultIsExtensionMethod
}
Some (typeDef, spec)
| _ -> None
)
| _ -> None
| Some attr ->
let arg =
match SynExpr.stripOptionalParen attr.ArgExpr with
| SynExpr.Const (SynConst.Bool value, _) -> value
| SynExpr.Const (SynConst.Unit, _) -> JsonParseAttribute.DefaultIsExtensionMethod
| SynExpr.Const (SynConst.Unit, _) -> HttpClientAttribute.DefaultIsExtensionMethod
| arg ->
failwith
$"Unrecognised argument %+A{arg} to [<%s{nameof HttpClientAttribute}>]. Literals are not supported. Use `true` or `false` (or unit) only."

View File

@@ -3,6 +3,7 @@ namespace WoofWare.Myriad.Plugins
open System
open Fantomas.FCS.Syntax
open Fantomas.FCS.Xml
open WoofWare.Whippet.Fantomas
type internal GenerateMockOutputSpec =
{
@@ -71,13 +72,13 @@ module internal InterfaceMockGenerator =
if inherits.Contains KnownInheritance.IDisposable then
let unitFun = SynExpr.createThunk (SynExpr.CreateConst ())
[ (SynLongIdent.createS "Dispose", true), Some unitFun ]
[ SynLongIdent.createS "Dispose", unitFun ]
else
[]
let nonExtras =
fields
|> List.map (fun field -> (SynLongIdent.createI (getName field), true), Some (failwithFun field))
|> List.map (fun field -> SynLongIdent.createI (getName field), failwithFun field)
extras @ nonExtras
@@ -88,7 +89,7 @@ module internal InterfaceMockGenerator =
[]
else
[ SynPat.unit ])
(AstHelper.instantiateRecord constructorFields)
(SynExpr.createRecord None constructorFields)
|> SynBinding.withXmlDoc (PreXmlDoc.create "An implementation where every method throws.")
|> SynBinding.withReturnAnnotation constructorReturnType
|> SynMemberDefn.staticMember
@@ -158,6 +159,15 @@ module internal InterfaceMockGenerator =
|> SynMemberDefn.memberImplementation
)
let properties =
interfaceType.Properties
|> List.map (fun pi ->
SynExpr.createLongIdent' [ Ident.create "this" ; pi.Identifier ]
|> SynExpr.applyTo (SynExpr.CreateConst ())
|> SynBinding.basic [ Ident.create "this" ; pi.Identifier ] []
|> SynMemberDefn.memberImplementation
)
let interfaceName =
let baseName = SynType.createLongIdent interfaceType.Name
@@ -173,7 +183,7 @@ module internal InterfaceMockGenerator =
SynType.app' baseName generics
SynMemberDefn.Interface (interfaceName, Some range0, Some members, range0)
SynMemberDefn.Interface (interfaceName, Some range0, Some (members @ properties), range0)
let access =
match interfaceType.Accessibility, spec.IsInternal with
@@ -212,7 +222,9 @@ module internal InterfaceMockGenerator =
Members = Some ([ constructor ; interfaceMembers ] @ extraInterfaces)
XmlDoc = Some xmlDoc
Generics = interfaceType.Generics
Accessibility = Some access
TypeAccessibility = Some access
ImplAccessibility = None
Attributes = []
}
let typeDecl = AstHelper.defineRecordType record
@@ -226,14 +238,11 @@ module internal InterfaceMockGenerator =
x.Type
let private constructMemberSinglePlace (tuple : TupledArg) : SynType =
match tuple.Args |> List.rev |> List.map buildType with
| [] -> failwith "no-arg functions not supported yet"
| [ x ] -> x
| last :: rest ->
([ SynTupleTypeSegment.Type last ], rest)
||> 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
tuple.Args
|> List.map buildType
|> SynType.tupleNoParen
|> Option.defaultWith (fun () -> failwith "no-arg functions not supported yet")
|> if tuple.HasParen then SynType.paren else id
let constructMember (mem : MemberInfo) : SynField =
let inputType = mem.Args |> List.map constructMemberSinglePlace
@@ -248,6 +257,15 @@ module internal InterfaceMockGenerator =
|> SynField.make
|> SynField.withDocString (mem.XmlDoc |> Option.defaultValue PreXmlDoc.Empty)
let constructProperty (prop : PropertyInfo) : SynField =
{
Attrs = []
Ident = Some prop.Identifier
Type = SynType.toFun [ SynType.unit ] prop.Type
}
|> SynField.make
|> SynField.withDocString (prop.XmlDoc |> Option.defaultValue PreXmlDoc.Empty)
let createRecord
(namespaceId : LongIdent)
(opens : SynOpenDeclTarget list)
@@ -255,7 +273,12 @@ module internal InterfaceMockGenerator =
: SynModuleOrNamespace
=
let interfaceType = AstHelper.parseInterface interfaceType
let fields = interfaceType.Members |> List.map constructMember
let fields =
interfaceType.Members
|> List.map constructMember
|> List.append (interfaceType.Properties |> List.map constructProperty)
let docString = PreXmlDoc.create "Mock record type for an interface"
let name =
@@ -284,18 +307,42 @@ type InterfaceMockGenerator () =
member _.ValidInputExtensions = [ ".fs" ]
member _.Generate (context : GeneratorContext) =
let targetedTypes =
MyriadParamParser.render context.AdditionalParameters
|> Map.map (fun _ v -> v.Split '!' |> Array.toList |> List.map DesiredGenerator.Parse)
let ast, _ =
Ast.fromFilename context.InputFilename |> Async.RunSynchronously |> Array.head
let types = Ast.extractTypeDefn ast
let types = Ast.getTypes ast
let namespaceAndInterfaces =
types
|> List.choose (fun (ns, types) ->
types
|> List.choose (fun typeDef ->
match Ast.getAttribute<GenerateMockAttribute> typeDef with
| None -> None
match SynTypeDefn.getAttribute typeof<GenerateMockAttribute>.Name typeDef with
| None ->
let name = SynTypeDefn.getName typeDef |> List.map _.idText |> String.concat "."
match Map.tryFind name targetedTypes with
| Some desired ->
desired
|> List.tryPick (fun generator ->
match generator with
| DesiredGenerator.InterfaceMock arg ->
let spec =
{
IsInternal =
arg
|> Option.defaultValue GenerateMockAttribute.DefaultIsInternal
}
Some (typeDef, spec)
| _ -> None
)
| _ -> None
| Some attr ->
let arg =
match SynExpr.stripOptionalParen attr.ArgExpr with

View File

@@ -4,6 +4,7 @@ open System
open System.Text
open Fantomas.FCS.Syntax
open Fantomas.FCS.SyntaxTrivia
open WoofWare.Whippet.Fantomas
type internal JsonParseOutputSpec =
{
@@ -24,8 +25,8 @@ module internal JsonParseGenerator =
JsonNumberHandlingArg = None
}
/// (match {indexed} with | null -> raise (System.Collections.Generic.KeyNotFoundException ()) | v -> v)
let assertNotNull (propertyName : SynExpr) (indexed : SynExpr) =
/// (match {indexed} with | null -> raise (System.Collections.Generic.KeyNotFoundException ({propertyName} not found)) | v -> v)
let assertPropertyExists (propertyName : SynExpr) (indexed : SynExpr) =
let raiseExpr =
SynExpr.applyFunction
(SynExpr.createIdent "sprintf")
@@ -45,28 +46,42 @@ module internal JsonParseGenerator =
|> SynExpr.createMatch indexed
|> SynExpr.paren
let assertNotNull (boundIdent : Ident) (body : SynExpr) : SynExpr =
let raiseExpr =
SynExpr.CreateConst ()
|> SynExpr.applyFunction (SynExpr.createLongIdent [ "System" ; "ArgumentNullException" ])
|> SynExpr.paren
|> SynExpr.applyFunction (SynExpr.createIdent "raise")
[
SynMatchClause.create SynPat.createNull raiseExpr
SynMatchClause.create (SynPat.namedI boundIdent) body
]
|> SynExpr.createMatch (SynExpr.createIdent' boundIdent)
|> SynExpr.paren
/// {node}.AsValue().GetValue<{typeName}> ()
/// If `propertyName` is Some, uses `assertNotNull {node}` instead of `{node}`.
/// If `propertyName` is Some, uses `assertPropertyExists {node}` instead of `{node}`.
let asValueGetValue (propertyName : SynExpr option) (typeName : string) (node : SynExpr) : SynExpr =
match propertyName with
| None -> node
| Some propertyName -> assertNotNull propertyName node
| Some propertyName -> assertPropertyExists propertyName node
|> SynExpr.callMethod "AsValue"
|> SynExpr.callGenericMethod' "GetValue" typeName
let asValueGetValueIdent (propertyName : SynExpr option) (typeName : LongIdent) (node : SynExpr) : SynExpr =
match propertyName with
| None -> node
| Some propertyName -> assertNotNull propertyName node
| Some propertyName -> assertPropertyExists propertyName node
|> SynExpr.callMethod "AsValue"
|> SynExpr.callGenericMethod "GetValue" typeName
|> SynExpr.callGenericMethod (SynLongIdent.createS "GetValue") [ SynType.createLongIdent typeName ]
/// {node}.AsObject()
/// If `propertyName` is Some, uses `assertNotNull {node}` instead of `{node}`.
/// If `propertyName` is Some, uses `assertPropertyExists {node}` instead of `{node}`.
let asObject (propertyName : SynExpr option) (node : SynExpr) : SynExpr =
match propertyName with
| None -> node
| Some propertyName -> assertNotNull propertyName node
| Some propertyName -> assertPropertyExists propertyName node
|> SynExpr.callMethod "AsObject"
/// {type}.jsonParse {node}
@@ -76,8 +91,8 @@ module internal JsonParseGenerator =
/// collectionType is e.g. "List"; we'll be calling `ofSeq` on it.
/// body is the body of a lambda which takes a parameter `elt`.
/// {assertNotNull node}.AsArray()
/// |> Seq.map (fun elt -> {body})
/// {assertPropertyExists node}.AsArray()
/// |> Seq.map (fun elt -> {assertNotNull} {body})
/// |> {collectionType}.ofSeq
let asArrayMapped
(propertyName : SynExpr option)
@@ -88,32 +103,17 @@ module internal JsonParseGenerator =
=
match propertyName with
| None -> node
| Some propertyName -> assertNotNull propertyName node
| Some propertyName -> assertPropertyExists propertyName node
|> SynExpr.callMethod "AsArray"
|> SynExpr.pipeThroughFunction (
SynExpr.applyFunction (SynExpr.createLongIdent [ "Seq" ; "map" ]) (SynExpr.createLambda "elt" body)
SynExpr.applyFunction
(SynExpr.createLongIdent [ "Seq" ; "map" ])
(SynExpr.createLambda "elt" (assertNotNull (Ident.create "elt") body))
)
|> SynExpr.pipeThroughFunction (SynExpr.createLongIdent [ collectionType ; "ofSeq" ])
/// match {node} with | null -> None | v -> {body} |> Some
/// Use the variable `v` to get access to the `Some`.
let createParseLineOption (node : SynExpr) (body : SynExpr) : SynExpr =
let body = SynExpr.pipeThroughFunction (SynExpr.createIdent "Some") body
[
SynMatchClause.create SynPat.createNull (SynExpr.createIdent "None")
SynMatchClause.create (SynPat.named "v") body
]
|> SynExpr.createMatch node
/// Given e.g. "float", returns "System.Double.Parse"
let parseFunction (typeName : string) : LongIdent =
let qualified =
match Primitives.qualifyType typeName with
| Some x -> x
| None -> failwith $"Could not recognise type %s{typeName} as a primitive."
List.append qualified [ Ident.create "Parse" ]
let dotParse (typeName : LongIdent) : LongIdent =
List.append typeName [ Ident.create "Parse" ]
/// fun kvp -> let key = {key(kvp)} in let value = {value(kvp)} in (key, value))
/// The inputs will be fed with appropriate SynExprs to apply them to the `kvp.Key` and `kvp.Value` args.
@@ -140,6 +140,47 @@ module internal JsonParseGenerator =
failwithf
$"Unable to parse the key type %+A{desiredType} of a JSON object. Keys are strings, and this plugin does not know how to convert to that from a string."
let private parseNumberType
(options : JsonParseOption)
(propertyName : SynExpr option)
(node : SynExpr)
(typeName : LongIdent)
=
let basic = asValueGetValueIdent propertyName typeName node
match options.JsonNumberHandlingArg with
| None -> basic
| Some option ->
let cond =
SynExpr.DotGet (SynExpr.createIdent "exc", range0, SynLongIdent.createS "Message", range0)
|> SynExpr.callMethodArg "Contains" (SynExpr.CreateConst "cannot be converted to")
let handler =
asValueGetValue propertyName "string" node
|> SynExpr.pipeThroughFunction (SynExpr.createLongIdent' (typeName |> dotParse))
|> SynExpr.ifThenElse
(SynExpr.equals
option
(SynExpr.createLongIdent
[
"System"
"Text"
"Json"
"Serialization"
"JsonNumberHandling"
"AllowReadingFromString"
]))
SynExpr.reraise
|> SynExpr.ifThenElse cond SynExpr.reraise
basic
|> SynExpr.pipeThroughTryWith
(SynPat.IsInst (
SynType.LongIdent (SynLongIdent.createS' [ "System" ; "InvalidOperationException" ]),
range0
))
handler
/// Given `node.["town"]`, for example, choose how to obtain a JSON value from it.
/// The property name is used in error messages at runtime to show where a JSON
/// parse error occurred; supply `None` to indicate "don't validate".
@@ -152,61 +193,54 @@ module internal JsonParseGenerator =
=
// TODO: parsing format for DateTime etc
match fieldType with
// Struct types
| DateOnly ->
node
|> asValueGetValue propertyName "string"
|> SynExpr.pipeThroughFunction (SynExpr.createLongIdent [ "System" ; "DateOnly" ; "Parse" ])
| Uri ->
node
|> asValueGetValue propertyName "string"
|> SynExpr.pipeThroughFunction (SynExpr.createLongIdent [ "System" ; "Uri" ])
| Guid ->
node
|> asValueGetValue propertyName "string"
|> SynExpr.pipeThroughFunction (SynExpr.createLongIdent [ "System" ; "Guid" ; "Parse" ])
| DateTime ->
node
|> asValueGetValue propertyName "string"
|> SynExpr.pipeThroughFunction (SynExpr.createLongIdent [ "System" ; "DateTime" ; "Parse" ])
| NumberType typeName ->
let basic = asValueGetValue propertyName typeName node
match options.JsonNumberHandlingArg with
| None -> basic
| Some option ->
let cond =
SynExpr.DotGet (SynExpr.createIdent "exc", range0, SynLongIdent.createS "Message", range0)
|> SynExpr.callMethodArg "Contains" (SynExpr.CreateConst "cannot be converted to")
let handler =
asValueGetValue propertyName "string" node
|> SynExpr.pipeThroughFunction (SynExpr.createLongIdent' (parseFunction typeName))
|> SynExpr.ifThenElse
(SynExpr.equals
option
(SynExpr.createLongIdent
[
"System"
"Text"
"Json"
"Serialization"
"JsonNumberHandling"
"AllowReadingFromString"
]))
SynExpr.reraise
|> SynExpr.ifThenElse cond SynExpr.reraise
basic
|> SynExpr.pipeThroughTryWith
(SynPat.IsInst (
SynType.LongIdent (SynLongIdent.createS' [ "System" ; "InvalidOperationException" ]),
range0
))
handler
| NumberType typeName -> parseNumberType options propertyName node typeName
| Guid ->
node
|> asValueGetValue propertyName "string"
|> SynExpr.pipeThroughFunction (SynExpr.createLongIdent [ "System" ; "Guid" ; "Parse" ])
// Reference types
| Uri ->
node
|> asValueGetValue propertyName "string"
|> SynExpr.pipeThroughFunction (SynExpr.createLongIdent [ "System" ; "Uri" ])
| DateTimeOffset ->
node
|> asValueGetValue propertyName "string"
|> SynExpr.pipeThroughFunction (SynExpr.createLongIdent [ "System" ; "DateTimeOffset" ; "Parse" ])
| PrimitiveType typeName -> asValueGetValueIdent propertyName typeName node
| OptionType ty ->
parseNode None options ty (SynExpr.createIdent "v")
|> createParseLineOption node
let someClause =
parseNode None options ty (SynExpr.createIdent "v")
|> SynExpr.pipeThroughFunction (SynExpr.createIdent "Some")
|> SynMatchClause.create (SynPat.named "v")
[
SynMatchClause.create SynPat.createNull (SynExpr.createIdent "None")
someClause
]
|> SynExpr.createMatch node
| NullableType ty ->
let someClause =
parseNode None options ty (SynExpr.createIdent "v")
|> SynExpr.pipeThroughFunction (SynExpr.createLongIdent [ "System" ; "Nullable" ])
|> SynMatchClause.create (SynPat.named "v")
[
SynMatchClause.create
SynPat.createNull
(SynExpr.applyFunction (SynExpr.createLongIdent [ "System" ; "Nullable" ]) (SynExpr.CreateConst ()))
someClause
]
|> SynExpr.createMatch node
| ListType ty ->
parseNode None options ty (SynExpr.createIdent "elt")
|> asArrayMapped propertyName "List" node
@@ -261,6 +295,11 @@ module internal JsonParseGenerator =
|> SynExpr.callMethod "ToJsonString"
|> SynExpr.paren
|> SynExpr.applyFunction (SynExpr.createLongIdent [ "System" ; "Numerics" ; "BigInteger" ; "Parse" ])
| Measure (_measure, primType) ->
parseNumberType options propertyName node primType
|> SynExpr.pipeThroughFunction (Measure.getLanguagePrimitivesMeasure primType)
| JsonNode -> node
| UnitType -> SynExpr.CreateConst ()
| _ ->
// Let's just hope that we've also got our own type annotation!
let typeName =
@@ -270,7 +309,7 @@ module internal JsonParseGenerator =
match propertyName with
| None -> node
| Some propertyName -> assertNotNull propertyName node
| Some propertyName -> assertPropertyExists propertyName node
|> typeJsonParse typeName
/// propertyName is probably a string literal, but it could be a [<Literal>] variable
@@ -330,10 +369,7 @@ module internal JsonParseGenerator =
let getParseOptions (fieldAttrs : SynAttribute list) =
(JsonParseOption.None, fieldAttrs)
||> List.fold (fun options attr ->
if
(SynLongIdent.toString attr.TypeName)
.EndsWith ("JsonNumberHandling", StringComparison.Ordinal)
then
if (SynLongIdent.toString attr.TypeName).EndsWith ("JsonNumberHandling", StringComparison.Ordinal) then
let qualifiedEnumValue =
match SynExpr.stripOptionalParen attr.ArgExpr with
| SynExpr.LongIdent (_, SynLongIdent (ident, _, _), _, _) when isJsonNumberHandling ident ->
@@ -357,17 +393,20 @@ module internal JsonParseGenerator =
)
let createRecordMaker (spec : JsonParseOutputSpec) (fields : SynFieldData<Ident> list) =
let assignments =
let propertyFields =
fields
|> List.mapi (fun i fieldData ->
|> List.map (fun fieldData ->
let propertyNameAttr =
fieldData.Attrs
|> List.tryFind (fun attr ->
(SynLongIdent.toString attr.TypeName)
.EndsWith ("JsonPropertyName", StringComparison.Ordinal)
(SynLongIdent.toString attr.TypeName).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 =
match propertyNameAttr with
@@ -383,16 +422,83 @@ module internal JsonParseGenerator =
sb.ToString () |> SynExpr.CreateConst
| 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
|> SynBinding.basic [ Ident.create $"arg_%i{i}" ] []
|> SynBinding.basic [ accIdent ] []
)
let finalConstruction =
fields
|> List.mapi (fun i fieldData ->
(SynLongIdent.createI fieldData.Ident, true), Some (SynExpr.createIdent $"arg_%i{i}")
)
|> AstHelper.instantiateRecord
|> List.mapi (fun i fieldData -> SynLongIdent.createI fieldData.Ident, SynExpr.createIdent $"arg_%i{i}")
|> SynExpr.createRecord None
(finalConstruction, assignments)
||> List.fold (fun final assignment -> SynExpr.createLet [ assignment ] final)
@@ -400,11 +506,11 @@ module internal JsonParseGenerator =
let createUnionMaker (spec : JsonParseOutputSpec) (typeName : LongIdent) (fields : UnionCase<Ident> list) =
fields
|> List.map (fun case ->
let propertyName = JsonSerializeGenerator.getPropertyName case.Ident case.Attrs
let propertyName = JsonSerializeGenerator.getPropertyName case.Name case.Attributes
let body =
if case.Fields.IsEmpty then
SynExpr.createLongIdent' (typeName @ [ case.Ident ])
SynExpr.createLongIdent' (typeName @ [ case.Name ])
else
case.Fields
|> List.map (fun field ->
@@ -413,11 +519,11 @@ module internal JsonParseGenerator =
createParseRhs options propertyName field.Type
)
|> SynExpr.tuple
|> SynExpr.applyFunction (SynExpr.createLongIdent' (typeName @ [ case.Ident ]))
|> SynExpr.applyFunction (SynExpr.createLongIdent' (typeName @ [ case.Name ]))
|> SynExpr.createLet
[
SynExpr.index (SynExpr.CreateConst "data") (SynExpr.createIdent "node")
|> assertNotNull (SynExpr.CreateConst "data")
|> assertPropertyExists (SynExpr.CreateConst "data")
|> SynBinding.basic [ Ident.create "node" ] []
]
@@ -465,20 +571,71 @@ module internal JsonParseGenerator =
SynExpr.createIdent "node"
|> SynExpr.index property
|> assertNotNull property
|> assertPropertyExists property
|> SynExpr.pipeThroughFunction (
SynExpr.createLambda
"v"
(SynExpr.callGenericMethod "GetValue" [ Ident.create "string" ] (SynExpr.createIdent "v"))
SynExpr.createLambda "v" (SynExpr.callGenericMethod' "GetValue" "string" (SynExpr.createIdent "v"))
)
|> SynBinding.basic [ Ident.create "ty" ] []
]
let createEnumMaker
(spec : JsonParseOutputSpec)
(typeName : LongIdent)
(fields : (Ident * SynExpr) list)
: SynExpr
=
let numberKind =
[ "System" ; "Text" ; "Json" ; "JsonValueKind" ; "Number" ]
|> List.map Ident.create
let stringKind =
[ "System" ; "Text" ; "Json" ; "JsonValueKind" ; "String" ]
|> List.map Ident.create
let fail =
SynExpr.plus
(SynExpr.CreateConst "Unrecognised kind for enum of type: ")
(SynExpr.CreateConst (typeName |> List.map _.idText |> String.concat "."))
|> SynExpr.paren
|> SynExpr.applyFunction (SynExpr.createIdent "failwith")
let failString =
SynExpr.plus (SynExpr.CreateConst "Unrecognised value for enum: %i") (SynExpr.createIdent "v")
|> SynExpr.paren
|> SynExpr.applyFunction (SynExpr.createIdent "failwith")
let parseString =
fields
|> List.map (fun (ident, _) ->
SynMatchClause.create
(SynPat.createConst (
SynConst.String (ident.idText.ToLowerInvariant (), SynStringKind.Regular, range0)
))
(SynExpr.createLongIdent' (typeName @ [ ident ]))
)
|> fun l -> l @ [ SynMatchClause.create (SynPat.named "v") failString ]
|> SynExpr.createMatch (
asValueGetValue None "string" (SynExpr.createIdent "node")
|> SynExpr.callMethod "ToLowerInvariant"
)
[
SynMatchClause.create
(SynPat.identWithArgs numberKind (SynArgPats.create []))
(asValueGetValue None "int" (SynExpr.createIdent "node")
|> SynExpr.pipeThroughFunction (
SynExpr.typeApp [ SynType.createLongIdent typeName ] (SynExpr.createIdent "enum")
))
SynMatchClause.create (SynPat.identWithArgs stringKind (SynArgPats.create [])) parseString
SynMatchClause.create (SynPat.named "_") fail
]
|> SynExpr.createMatch (SynExpr.callMethod "GetValueKind" (SynExpr.createIdent "node"))
let createModule (namespaceId : LongIdent) (spec : JsonParseOutputSpec) (typeDefn : SynTypeDefn) =
let (SynTypeDefn (synComponentInfo, synTypeDefnRepr, _members, _implicitCtor, _, _)) =
typeDefn
let (SynComponentInfo (_attributes, _typeParams, _constraints, ident, _, _preferPostfix, _access, _)) =
let (SynComponentInfo (_attributes, _typeParams, _constraints, ident, _, _preferPostfix, access, _)) =
synComponentInfo
let attributes =
@@ -517,6 +674,7 @@ module internal JsonParseGenerator =
let info =
SynComponentInfo.createLong moduleName
|> SynComponentInfo.withDocString xmlDoc
|> SynComponentInfo.setAccessibility access
|> SynComponentInfo.addAttributes attributes
let decl =
@@ -530,9 +688,16 @@ module internal JsonParseGenerator =
| Some i -> i
cases
|> List.map SynUnionCase.extract
|> List.map UnionCase.ofSynUnionCase
|> List.map (UnionCase.mapIdentFields optionGet)
|> createUnionMaker spec ident
| SynTypeDefnRepr.Simple (SynTypeDefnSimpleRepr.Enum (cases, _range), _) ->
cases
|> List.map (fun c ->
match c with
| SynEnumCase.SynEnumCase (_, SynIdent.SynIdent (ident, _), value, _, _, _) -> ident, value
)
|> createEnumMaker spec ident
| _ -> failwithf "Not a record or union type"
[ scaffolding spec ident decl ]
@@ -551,28 +716,54 @@ type JsonParseGenerator () =
member _.ValidInputExtensions = [ ".fs" ]
member _.Generate (context : GeneratorContext) =
let targetedTypes =
MyriadParamParser.render context.AdditionalParameters
|> Map.map (fun _ v -> v.Split '!' |> Array.toList |> List.map DesiredGenerator.Parse)
let ast, _ =
Ast.fromFilename context.InputFilename |> Async.RunSynchronously |> Array.head
let recordsAndUnions =
Ast.extractTypeDefn ast
let relevantTypes =
Ast.getTypes ast
|> List.map (fun (name, defns) ->
defns
|> List.choose (fun defn ->
if Ast.isRecord defn then Some defn
elif Ast.isDu defn then Some defn
if SynTypeDefn.isRecord defn then Some defn
elif SynTypeDefn.isDu defn then Some defn
elif SynTypeDefn.isEnum defn then Some defn
else None
)
|> fun defns -> name, defns
)
let namespaceAndTypes =
recordsAndUnions
relevantTypes
|> List.choose (fun (ns, types) ->
types
|> List.choose (fun typeDef ->
match Ast.getAttribute<JsonParseAttribute> typeDef with
| None -> None
match SynTypeDefn.getAttribute typeof<JsonParseAttribute>.Name typeDef with
| None ->
let name = SynTypeDefn.getName typeDef |> List.map _.idText |> String.concat "."
match Map.tryFind name targetedTypes with
| Some desired ->
desired
|> List.tryPick (fun generator ->
match generator with
| DesiredGenerator.JsonParse arg ->
let spec =
{
ExtensionMethods =
arg
|> Option.defaultValue
JsonParseAttribute.DefaultIsExtensionMethod
}
Some (typeDef, spec)
| _ -> None
)
| _ -> None
| Some attr ->
let arg =
match SynExpr.stripOptionalParen attr.ArgExpr with

View File

@@ -3,6 +3,7 @@ namespace WoofWare.Myriad.Plugins
open System
open System.Text
open Fantomas.FCS.Syntax
open WoofWare.Whippet.Fantomas
type internal JsonSerializeOutputSpec =
{
@@ -13,48 +14,71 @@ type internal JsonSerializeOutputSpec =
module internal JsonSerializeGenerator =
open Fantomas.FCS.Text.Range
// The absolutely galaxy-brained implementation of JsonValue has `JsonValue.Parse "null"`
// identically equal to null. We have to work around this later, but we might as well just
// be efficient here and whip up the null directly.
let private jsonNull () =
SynExpr.createNull ()
|> SynExpr.upcast' (SynType.createLongIdent' [ "System" ; "Text" ; "Json" ; "Nodes" ; "JsonNode" ])
/// Given `input.Ident`, for example, choose how to add it to the ambient `node`.
/// The result is a line like `(fun ident -> InnerType.toJsonNode ident)` or `(fun ident -> JsonValue.Create ident)`.
let rec serializeNode (fieldType : SynType) : SynExpr =
/// Returns also a bool which is true if the resulting SynExpr represents something of type JsonNode.
let rec serializeNode (fieldType : SynType) : SynExpr * bool =
// TODO: serialization format for DateTime etc
match fieldType with
| DateOnly
| DateTime
| NumberType _
| Measure _
| PrimitiveType _
| Guid
| Uri ->
// JsonValue.Create<type>
SynExpr.TypeApp (
SynExpr.createLongIdent [ "System" ; "Text" ; "Json" ; "Nodes" ; "JsonValue" ; "Create" ],
range0,
[ fieldType ],
[],
Some range0,
range0,
range0
)
SynExpr.createLongIdent [ "System" ; "Text" ; "Json" ; "Nodes" ; "JsonValue" ; "Create" ]
|> SynExpr.typeApp [ fieldType ]
|> fun e -> e, false
| DateTimeOffset ->
// fun field -> field.ToString("o") |> JsonValue.Create<string>
let create =
SynExpr.createLongIdent [ "System" ; "Text" ; "Json" ; "Nodes" ; "JsonValue" ; "Create" ]
|> SynExpr.typeApp [ SynType.named "string" ]
SynExpr.createIdent "field"
|> SynExpr.callMethodArg "ToString" (SynExpr.CreateConst "o")
|> SynExpr.pipeThroughFunction create
|> SynExpr.createLambda "field"
|> fun e -> e, false
| NullableType ty ->
// fun field -> if field.HasValue then {serializeNode ty} field.Value else JsonValue.Create null
let inner, innerIsJsonNode = serializeNode ty
SynExpr.applyFunction inner (SynExpr.createLongIdent [ "field" ; "Value" ])
|> SynExpr.upcast' (SynType.createLongIdent' [ "System" ; "Text" ; "Json" ; "Nodes" ; "JsonNode" ])
|> SynExpr.ifThenElse (SynExpr.createLongIdent [ "field" ; "HasValue" ]) (jsonNull ())
|> SynExpr.createLambda "field"
|> fun e -> e, innerIsJsonNode
| OptionType ty ->
// fun field -> match field with | None -> JsonValue.Create null | Some v -> {serializeNode ty} field
let noneClause =
// The absolutely galaxy-brained implementation of JsonValue has `JsonValue.Parse "null"`
// identically equal to null. We have to work around this later, but we might as well just
// be efficient here and whip up the null directly.
SynExpr.createNull ()
|> SynExpr.upcast' (SynType.createLongIdent' [ "System" ; "Text" ; "Json" ; "Nodes" ; "JsonNode" ])
|> SynMatchClause.create (SynPat.named "None")
let noneClause = jsonNull () |> SynMatchClause.create (SynPat.named "None")
let someClause =
SynExpr.applyFunction (serializeNode ty) (SynExpr.createIdent "field")
|> SynExpr.paren
|> SynExpr.upcast' (SynType.createLongIdent' [ "System" ; "Text" ; "Json" ; "Nodes" ; "JsonNode" ])
|> SynMatchClause.create (
SynPat.identWithArgs [ Ident.create "Some" ] (SynArgPats.create [ Ident.create "field" ])
)
let inner, innerIsJsonNode = serializeNode ty
let target = SynExpr.applyFunction inner (SynExpr.createIdent "field")
if innerIsJsonNode then
target
else
target
|> SynExpr.paren
|> SynExpr.upcast' (SynType.createLongIdent' [ "System" ; "Text" ; "Json" ; "Nodes" ; "JsonNode" ])
|> SynMatchClause.create (SynPat.nameWithArgs "Some" [ SynPat.named "field" ])
[ noneClause ; someClause ]
|> SynExpr.createMatch (SynExpr.createIdent "field")
|> SynExpr.createLambda "field"
|> fun e -> e, true
| ArrayType ty
| ListType ty ->
// fun field ->
@@ -71,7 +95,7 @@ module internal JsonSerializeGenerator =
SynExpr.createIdent "field",
SynExpr.applyFunction
(SynExpr.createLongIdent [ "arr" ; "Add" ])
(SynExpr.paren (SynExpr.applyFunction (serializeNode ty) (SynExpr.createIdent "mem"))),
(SynExpr.paren (SynExpr.applyFunction (fst (serializeNode ty)) (SynExpr.createIdent "mem"))),
range0
)
SynExpr.createIdent "arr"
@@ -84,6 +108,7 @@ module internal JsonSerializeGenerator =
|> SynBinding.basic [ Ident.create "arr" ] []
]
|> SynExpr.createLambda "field"
|> fun e -> e, false
| IDictionaryType (_keyType, valueType)
| DictionaryType (_keyType, valueType)
| IReadOnlyDictionaryType (_keyType, valueType)
@@ -99,11 +124,7 @@ module internal JsonSerializeGenerator =
DebugPointAtInOrTo.Yes range0,
SeqExprOnly.SeqExprOnly false,
true,
SynPat.paren (
SynPat.identWithArgs
[ Ident.create "KeyValue" ]
(SynArgPats.create [ Ident.create "key" ; Ident.create "value" ])
),
SynPat.paren (SynPat.nameWithArgs "KeyValue" [ SynPat.named "key" ; SynPat.named "value" ]),
SynExpr.createIdent "field",
SynExpr.applyFunction
(SynExpr.createLongIdent [ "ret" ; "Add" ])
@@ -111,7 +132,7 @@ module internal JsonSerializeGenerator =
[
SynExpr.createLongIdent [ "key" ; "ToString" ]
|> SynExpr.applyTo (SynExpr.CreateConst ())
SynExpr.applyFunction (serializeNode valueType) (SynExpr.createIdent "value")
SynExpr.applyFunction (fst (serializeNode valueType)) (SynExpr.createIdent "value")
]),
range0
)
@@ -125,6 +146,14 @@ module internal JsonSerializeGenerator =
|> SynBinding.basic [ Ident.create "ret" ] []
]
|> SynExpr.createLambda "field"
|> fun e -> e, false
| JsonNode -> SynExpr.createIdent "id", true
| UnitType ->
SynExpr.createLambda
"value"
(SynExpr.createLongIdent [ "System" ; "Text" ; "Json" ; "Nodes" ; "JsonObject" ]
|> SynExpr.applyTo (SynExpr.CreateConst ())),
false
| _ ->
// {type}.toJsonNode
let typeName =
@@ -132,16 +161,17 @@ module internal JsonSerializeGenerator =
| SynType.LongIdent ident -> ident.LongIdent
| _ -> failwith $"Unrecognised type: %+A{fieldType}"
SynExpr.createLongIdent' (typeName @ [ Ident.create "toJsonNode" ])
SynExpr.createLongIdent' (typeName @ [ Ident.create "toJsonNode" ]), true
/// propertyName is probably a string literal, but it could be a [<Literal>] variable
/// `node.Add ({propertyName}, {toJsonNode})`
let createSerializeRhsRecord (propertyName : SynExpr) (fieldId : Ident) (fieldType : SynType) : SynExpr =
[
propertyName
SynExpr.applyFunction
(serializeNode fieldType)
SynExpr.pipeThroughFunction
(fst (serializeNode fieldType))
(SynExpr.createLongIdent' [ Ident.create "input" ; fieldId ])
|> SynExpr.paren
]
|> SynExpr.tuple
|> SynExpr.applyFunction (SynExpr.createLongIdent [ "node" ; "Add" ])
@@ -150,8 +180,7 @@ module internal JsonSerializeGenerator =
let propertyNameAttr =
attrs
|> List.tryFind (fun attr ->
(SynLongIdent.toString attr.TypeName)
.EndsWith ("JsonPropertyName", StringComparison.Ordinal)
(SynLongIdent.toString attr.TypeName).EndsWith ("JsonPropertyName", StringComparison.Ordinal)
)
match propertyNameAttr with
@@ -165,6 +194,13 @@ module internal JsonSerializeGenerator =
sb.ToString () |> SynExpr.CreateConst
| 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.
///
/// That is, we give you access to a `JsonObject` called `node`,
@@ -228,34 +264,56 @@ module internal JsonSerializeGenerator =
|> SynBinding.withXmlDoc xmlDoc
|> SynModuleDecl.createLet
let recordModule (spec : JsonSerializeOutputSpec) (typeName : LongIdent) (fields : SynField list) =
let inputArg = Ident.create "input"
let recordModule (spec : JsonSerializeOutputSpec) (_typeName : LongIdent) (fields : SynField list) =
let fields = fields |> List.map SynField.extractWithIdent
fields
|> List.map (fun fieldData ->
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
|> fun expr -> SynExpr.Do (expr, range0)
|> scaffolding spec typeName inputArg
let unionModule (spec : JsonSerializeOutputSpec) (typeName : LongIdent) (cases : SynUnionCase list) =
let inputArg = Ident.create "input"
let fields = cases |> List.map SynUnionCase.extract
let fields = cases |> List.map UnionCase.ofSynUnionCase
fields
|> 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 _ -> Ident.create $"arg%i{i}")
let caseNames = unionCase.Fields |> List.mapi (fun i _ -> $"arg%i{i}")
let argPats = SynArgPats.create caseNames
let argPats = SynArgPats.createNamed caseNames
let pattern =
SynPat.LongIdent (
SynLongIdent.create (typeName @ [ unionCase.Ident ]),
SynLongIdent.create (typeName @ [ unionCase.Name ]),
None,
None,
argPats,
@@ -285,7 +343,7 @@ module internal JsonSerializeGenerator =
let propertyName = getPropertyName (Option.get fieldData.Ident) fieldData.Attrs
let node =
SynExpr.applyFunction (serializeNode fieldData.Type) (SynExpr.createIdent' caseName)
SynExpr.applyFunction (fst (serializeNode fieldData.Type)) (SynExpr.createIdent caseName)
[ propertyName ; node ]
|> SynExpr.tuple
@@ -312,7 +370,68 @@ module internal JsonSerializeGenerator =
SynMatchClause.create pattern action
)
|> SynExpr.createMatch (SynExpr.createIdent' inputArg)
|> scaffolding spec typeName inputArg
let enumModule
(spec : JsonSerializeOutputSpec)
(typeName : LongIdent)
(cases : (Ident * SynExpr) list)
: SynModuleDecl
=
let fail =
SynExpr.CreateConst "Unrecognised value for enum: %O"
|> SynExpr.applyFunction (SynExpr.createIdent "sprintf")
|> SynExpr.applyTo (SynExpr.createIdent "v")
|> SynExpr.paren
|> SynExpr.applyFunction (SynExpr.createIdent "failwith")
let body =
cases
|> List.map (fun (caseName, value) ->
value
|> SynExpr.applyFunction (
SynExpr.createLongIdent [ "System" ; "Text" ; "Json" ; "Nodes" ; "JsonValue" ; "Create" ]
)
|> SynMatchClause.create (SynPat.identWithArgs (typeName @ [ caseName ]) (SynArgPats.create []))
)
|> fun l -> l @ [ SynMatchClause.create (SynPat.named "v") fail ]
|> SynExpr.createMatch (SynExpr.createIdent "input")
let xmlDoc = PreXmlDoc.create "Serialize to a JSON node"
let returnInfo =
SynLongIdent.createS' [ "System" ; "Text" ; "Json" ; "Nodes" ; "JsonNode" ]
|> SynType.LongIdent
let functionName = Ident.create "toJsonNode"
let pattern =
SynPat.named "input"
|> SynPat.annotateType (SynType.LongIdent (SynLongIdent.create typeName))
if spec.ExtensionMethods then
let componentInfo =
SynComponentInfo.createLong typeName
|> SynComponentInfo.withDocString (PreXmlDoc.create "Extension methods for JSON parsing")
let memberDef =
body
|> SynBinding.basic [ functionName ] [ pattern ]
|> SynBinding.withXmlDoc xmlDoc
|> SynBinding.withReturnAnnotation returnInfo
|> SynMemberDefn.staticMember
let containingType =
SynTypeDefnRepr.augmentation ()
|> SynTypeDefn.create componentInfo
|> SynTypeDefn.withMemberDefns [ memberDef ]
SynModuleDecl.Types ([ containingType ], range0)
else
body
|> SynBinding.basic [ functionName ] [ pattern ]
|> SynBinding.withReturnAnnotation returnInfo
|> SynBinding.withXmlDoc xmlDoc
|> SynModuleDecl.createLet
let createModule
(namespaceId : LongIdent)
@@ -323,7 +442,7 @@ module internal JsonSerializeGenerator =
let (SynTypeDefn (synComponentInfo, synTypeDefnRepr, _members, _implicitCtor, _, _)) =
typeDefn
let (SynComponentInfo (_attributes, _typeParams, _constraints, ident, _, _preferPostfix, _access, _)) =
let (SynComponentInfo (_attributes, _typeParams, _constraints, ident, _, _preferPostfix, access, _)) =
synComponentInfo
let attributes =
@@ -362,19 +481,29 @@ module internal JsonSerializeGenerator =
let info =
SynComponentInfo.createLong moduleName
|> SynComponentInfo.addAttributes attributes
|> SynComponentInfo.setAccessibility access
|> SynComponentInfo.withDocString xmlDoc
let decls =
match synTypeDefnRepr with
| SynTypeDefnRepr.Simple (SynTypeDefnSimpleRepr.Record (_accessibility, recordFields, _range), _) ->
[ recordModule spec ident recordFields ]
recordModule spec ident recordFields
|> scaffolding spec ident (Ident.create "input")
| SynTypeDefnRepr.Simple (SynTypeDefnSimpleRepr.Union (_accessibility, unionFields, _range), _) ->
[ unionModule spec ident unionFields ]
| _ -> failwithf "Only record types currently supported."
unionModule spec ident unionFields
|> scaffolding spec ident (Ident.create "input")
| SynTypeDefnRepr.Simple (SynTypeDefnSimpleRepr.Enum (cases, _range), _) ->
cases
|> List.map (fun c ->
match c with
| SynEnumCase.SynEnumCase (_, SynIdent.SynIdent (ident, _), value, _, _, _) -> ident, value
)
|> enumModule spec ident
| ty -> failwithf "Unsupported type: got %O" ty
[
yield! opens |> List.map SynModuleDecl.openAny
yield SynModuleDecl.nestedModule info decls
yield decls |> List.singleton |> SynModuleDecl.nestedModule info
]
|> SynModuleOrNamespace.createNamespace namespaceId
@@ -389,28 +518,54 @@ type JsonSerializeGenerator () =
member _.ValidInputExtensions = [ ".fs" ]
member _.Generate (context : GeneratorContext) =
let targetedTypes =
MyriadParamParser.render context.AdditionalParameters
|> Map.map (fun _ v -> v.Split '!' |> Array.toList |> List.map DesiredGenerator.Parse)
let ast, _ =
Ast.fromFilename context.InputFilename |> Async.RunSynchronously |> Array.head
let recordsAndUnions =
Ast.extractTypeDefn ast
let relevantTypes =
Ast.getTypes ast
|> List.map (fun (name, defns) ->
defns
|> List.choose (fun defn ->
if Ast.isRecord defn then Some defn
elif Ast.isDu defn then Some defn
if SynTypeDefn.isRecord defn then Some defn
elif SynTypeDefn.isDu defn then Some defn
elif SynTypeDefn.isEnum defn then Some defn
else None
)
|> fun defns -> name, defns
)
let namespaceAndTypes =
recordsAndUnions
relevantTypes
|> List.choose (fun (ns, types) ->
types
|> List.choose (fun typeDef ->
match Ast.getAttribute<JsonSerializeAttribute> typeDef with
| None -> None
match SynTypeDefn.getAttribute typeof<JsonSerializeAttribute>.Name typeDef with
| None ->
let name = SynTypeDefn.getName typeDef |> List.map _.idText |> String.concat "."
match Map.tryFind name targetedTypes with
| Some desired ->
desired
|> List.tryPick (fun generator ->
match generator with
| DesiredGenerator.JsonSerialize arg ->
let spec =
{
ExtensionMethods =
arg
|> Option.defaultValue
JsonSerializeAttribute.DefaultIsExtensionMethod
}
Some (typeDef, spec)
| _ -> None
)
| _ -> None
| Some attr ->
let arg =
match SynExpr.stripOptionalParen attr.ArgExpr with

View File

@@ -12,3 +12,12 @@ module private List =
)
List.rev xs, List.rev ys
let allSome<'a> (l : 'a option list) : 'a list option =
let rec go acc (l : 'a option list) =
match l with
| [] -> Some (List.rev acc)
| None :: _ -> None
| Some head :: tail -> go (head :: acc) tail
go [] l

View File

@@ -0,0 +1,24 @@
namespace WoofWare.Myriad.Plugins
open Fantomas.FCS.Syntax
open WoofWare.Whippet.Fantomas
[<RequireQualifiedAccess>]
module internal Measure =
let getLanguagePrimitivesMeasure (typeName : LongIdent) : SynExpr =
match typeName |> List.map _.idText with
| [ "System" ; "Single" ] -> [ "LanguagePrimitives" ; "Float32WithMeasure" ]
| [ "System" ; "Double" ] -> [ "LanguagePrimitives" ; "FloatWithMeasure" ]
| [ "System" ; "Byte" ] -> [ "LanguagePrimitives" ; "ByteWithMeasure" ]
| [ "System" ; "SByte" ] -> [ "LanguagePrimitives" ; "SByteWithMeasure" ]
| [ "System" ; "Int16" ] -> [ "LanguagePrimitives" ; "Int16WithMeasure" ]
| [ "System" ; "Int32" ] -> [ "LanguagePrimitives" ; "Int32WithMeasure" ]
| [ "System" ; "Int64" ] -> [ "LanguagePrimitives" ; "Int64WithMeasure" ]
| [ "System" ; "UInt16" ] -> [ "LanguagePrimitives" ; "UInt16WithMeasure" ]
| [ "System" ; "UInt32" ] -> [ "LanguagePrimitives" ; "UInt32WithMeasure" ]
| [ "System" ; "UInt64" ] -> [ "LanguagePrimitives" ; "UInt64WithMeasure" ]
| l ->
let l = String.concat "." l
failwith $"unrecognised type for measure: %s{l}"
|> SynExpr.createLongIdent

View File

@@ -0,0 +1,42 @@
namespace WoofWare.Myriad.Plugins
open System.Collections.Generic
[<RequireQualifiedAccess>]
module internal MyriadParamParser =
(*
An apparent bug in Myriad's argument parsing means that this:
<MyriadParams>
<Foo>bar</Foo>
<Baz>quux</Baz>
</MyriadParams>
leads to this:
Foo = "bar;Baz=quux"
I'm not going to put effort into fixing Myriad, though, because I want
to build something much more powerful instead.
*)
/// Call this with `context.AdditionalParameters`.
let render (pars : IDictionary<string, string>) : Map<string, string> =
match pars.Count with
| 0 -> Map.empty
| 1 ->
let (KeyValue (key, value)) = pars |> Seq.exactlyOne
match value.Split ';' |> Seq.toList with
| [] -> failwith "LOGIC ERROR"
| value :: rest ->
rest
|> Seq.map (fun v ->
let split = v.Split '='
split.[0], String.concat "=" split.[1..]
)
|> Seq.append (Seq.singleton (key, value))
|> Map.ofSeq
| _ ->
// assume the Myriad bug is fixed!
pars |> Seq.map (fun (KeyValue (k, v)) -> k, v) |> Map.ofSeq

View File

@@ -0,0 +1,23 @@
namespace WoofWare.Myriad.Plugins
type internal DesiredGenerator =
| InterfaceMock of isInternal : bool option
| JsonParse of extensionMethod : bool option
| JsonSerialize of extensionMethod : bool option
| HttpClient of extensionMethod : bool option
static member Parse (s : string) =
match s with
| "GenerateMock" -> DesiredGenerator.InterfaceMock None
| "GenerateMock(true)" -> DesiredGenerator.InterfaceMock (Some true)
| "GenerateMock(false)" -> DesiredGenerator.InterfaceMock (Some false)
| "JsonParse" -> DesiredGenerator.JsonParse None
| "JsonParse(true)" -> DesiredGenerator.JsonParse (Some true)
| "JsonParse(false)" -> DesiredGenerator.JsonParse (Some false)
| "JsonSerialize" -> DesiredGenerator.JsonSerialize None
| "JsonSerialize(true)" -> DesiredGenerator.JsonSerialize (Some true)
| "JsonSerialize(false)" -> DesiredGenerator.JsonSerialize (Some false)
| "HttpClient" -> DesiredGenerator.HttpClient None
| "HttpClient(true)" -> DesiredGenerator.HttpClient (Some true)
| "HttpClient(false)" -> DesiredGenerator.HttpClient (Some false)
| _ -> failwith $"Failed to parse as a generator specification: %s{s}"

View File

@@ -1,30 +0,0 @@
namespace WoofWare.Myriad.Plugins
open Fantomas.FCS.Syntax
open Fantomas.FCS.Text.Range
[<RequireQualifiedAccess>]
module internal Primitives =
/// Given e.g. "byte", returns "System.Byte".
let qualifyType (typeName : string) : LongIdent option =
match typeName with
| "float32"
| "single" -> [ "System" ; "Single" ] |> Some
| "float"
| "double" -> [ "System" ; "Double" ] |> Some
| "byte"
| "uint8" -> [ "System" ; "Byte" ] |> Some
| "sbyte"
| "int8" -> [ "System" ; "SByte" ] |> Some
| "int16" -> [ "System" ; "Int16" ] |> Some
| "int"
| "int32" -> [ "System" ; "Int32" ] |> Some
| "int64" -> [ "System" ; "Int64" ] |> Some
| "uint16" -> [ "System" ; "UInt16" ] |> Some
| "uint"
| "uint32" -> [ "System" ; "UInt32" ] |> Some
| "uint64" -> [ "System" ; "UInt64" ] |> Some
| "char" -> [ "System" ; "Char" ] |> Some
| "decimal" -> [ "System" ; "Decimal" ] |> Some
| _ -> None
|> Option.map (List.map (fun i -> (Ident (i, range0))))

View File

@@ -1,7 +1,9 @@
namespace WoofWare.Myriad.Plugins
open System
open Fantomas.FCS.Syntax
open Fantomas.FCS.Xml
open WoofWare.Whippet.Fantomas
[<RequireQualifiedAccess>]
module internal RemoveOptionsGenerator =
@@ -36,12 +38,12 @@ module internal RemoveOptionsGenerator =
trivia
)
// TODO: this option seems a bit odd
let createType
(xmlDoc : PreXmlDoc option)
(accessibility : SynAccess option)
(generics : SynTyparDecls option)
(fields : SynField list)
: SynModuleDecl
=
let fields : SynField list = fields |> List.map removeOption
let name = Ident.create "Short"
@@ -53,14 +55,16 @@ module internal RemoveOptionsGenerator =
Members = None
XmlDoc = xmlDoc
Generics = generics
Accessibility = accessibility
TypeAccessibility = accessibility
ImplAccessibility = None
Attributes = []
}
let typeDecl = AstHelper.defineRecordType record
let typeDecl = RecordType.ToAst record
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 inputArg = Ident.create "input"
@@ -85,61 +89,49 @@ module internal RemoveOptionsGenerator =
SynExpr.applyFunction
(SynExpr.createLongIdent [ "Option" ; "defaultWith" ])
(SynExpr.createLongIdent' (
withoutOptionsType
[ withoutOptionsType ]
@ [ Ident.create (sprintf "Default%s" fieldData.Ident.idText) ]
))
)
| _ -> accessor
(SynLongIdent.createI fieldData.Ident, true), Some body
SynLongIdent.createI fieldData.Ident, body
)
|> AstHelper.instantiateRecord
|> SynExpr.createRecord None
SynBinding.basic
[ functionName ]
[
SynPat.named inputArg.idText
|> SynPat.annotateType (SynType.LongIdent (SynLongIdent.create withoutOptionsType))
|> SynPat.annotateType (SynType.LongIdent (SynLongIdent.createI withoutOptionsType))
]
body
|> SynBinding.withXmlDoc xmlDoc
|> SynBinding.withReturnAnnotation (SynType.LongIdent (SynLongIdent.create withOptionsType))
|> SynModuleDecl.createLet
let createRecordModule (namespaceId : LongIdent) (typeDefn : SynTypeDefn) =
let (SynTypeDefn (synComponentInfo, synTypeDefnRepr, _members, _implicitCtor, _, _)) =
typeDefn
let createRecordModule (namespaceId : LongIdent) (typeDefn : RecordType) =
let fieldData = typeDefn.Fields |> List.map SynField.extractWithIdent
let (SynComponentInfo (_attributes, typeParams, _constraints, recordId, doc, _preferPostfix, _access, _)) =
synComponentInfo
let decls =
[
createType typeDefn.XmlDoc typeDefn.TypeAccessibility typeDefn.Generics typeDefn.Fields
createMaker [ Ident.create "Short" ] typeDefn.Name fieldData
]
match synTypeDefnRepr with
| SynTypeDefnRepr.Simple (SynTypeDefnSimpleRepr.Record (accessibility, fields, _range), _) ->
let fieldData = fields |> List.map SynField.extractWithIdent
let xmlDoc =
sprintf "Module containing an option-truncated version of the %s type" typeDefn.Name.idText
|> PreXmlDoc.create
let decls =
[
createType (Some doc) accessibility typeParams fields
createMaker [ Ident.create "Short" ] recordId fieldData
]
let info =
SynComponentInfo.create typeDefn.Name
|> SynComponentInfo.withDocString xmlDoc
|> SynComponentInfo.addAttributes [ SynAttribute.compilationRepresentation ]
|> SynComponentInfo.addAttributes [ SynAttribute.requireQualifiedAccess ]
let xmlDoc =
recordId
|> Seq.map (fun i -> i.idText)
|> 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"
SynModuleDecl.nestedModule info decls
|> List.singleton
|> SynModuleOrNamespace.createNamespace namespaceId
open Myriad.Core
@@ -155,24 +147,31 @@ type RemoveOptionsGenerator () =
let ast, _ =
Ast.fromFilename context.InputFilename |> Async.RunSynchronously |> Array.head
let records = Ast.extractRecords ast
let records = Ast.getRecords ast
let namespaceAndRecords =
records
|> List.choose (fun (ns, types) ->
match types |> List.filter Ast.hasAttribute<RemoveOptionsAttribute> with
| [] -> None
| types -> Some (ns, types)
|> List.collect (fun (ns, ty) ->
ty
|> List.filter (fun record ->
record.Attributes
|> List.exists (fun attr ->
attr.TypeName.LongIdent
|> List.last
|> _.idText
|> fun s ->
if s.EndsWith ("Attribute", StringComparison.Ordinal) then
s
else
$"%s{s}Attribute"
|> (=) typeof<RemoveOptionsAttribute>.Name
)
)
|> List.map (fun ty -> ns, ty)
)
let modules =
namespaceAndRecords
|> List.collect (fun (ns, records) ->
records
|> List.map (fun record ->
let recordModule = RemoveOptionsGenerator.createRecordModule ns record
recordModule
)
)
|> List.map (fun (ns, record) -> RemoveOptionsGenerator.createRecordModule ns record)
Output.Ast modules

View File

@@ -1,12 +1,295 @@
WoofWare.Myriad.Plugins.AdditionalProperties inherit obj, implements WoofWare.Myriad.Plugins.AdditionalProperties System.IEquatable, System.Collections.IStructuralEquatable - union type with 2 cases
WoofWare.Myriad.Plugins.AdditionalProperties+Constrained inherit WoofWare.Myriad.Plugins.AdditionalProperties
WoofWare.Myriad.Plugins.AdditionalProperties+Constrained.get_Item [method]: unit -> WoofWare.Myriad.Plugins.Definition
WoofWare.Myriad.Plugins.AdditionalProperties+Constrained.Item [property]: [read-only] WoofWare.Myriad.Plugins.Definition
WoofWare.Myriad.Plugins.AdditionalProperties+Tags inherit obj
WoofWare.Myriad.Plugins.AdditionalProperties+Tags.Constrained [static field]: int = 1
WoofWare.Myriad.Plugins.AdditionalProperties+Tags.Never [static field]: int = 0
WoofWare.Myriad.Plugins.AdditionalProperties.Equals [method]: (WoofWare.Myriad.Plugins.AdditionalProperties, System.Collections.IEqualityComparer) -> bool
WoofWare.Myriad.Plugins.AdditionalProperties.get_IsConstrained [method]: unit -> bool
WoofWare.Myriad.Plugins.AdditionalProperties.get_IsNever [method]: unit -> bool
WoofWare.Myriad.Plugins.AdditionalProperties.get_Never [static method]: unit -> WoofWare.Myriad.Plugins.AdditionalProperties
WoofWare.Myriad.Plugins.AdditionalProperties.get_Tag [method]: unit -> int
WoofWare.Myriad.Plugins.AdditionalProperties.IsConstrained [property]: [read-only] bool
WoofWare.Myriad.Plugins.AdditionalProperties.IsNever [property]: [read-only] bool
WoofWare.Myriad.Plugins.AdditionalProperties.Never [static property]: [read-only] WoofWare.Myriad.Plugins.AdditionalProperties
WoofWare.Myriad.Plugins.AdditionalProperties.NewConstrained [static method]: WoofWare.Myriad.Plugins.Definition -> WoofWare.Myriad.Plugins.AdditionalProperties
WoofWare.Myriad.Plugins.AdditionalProperties.Tag [property]: [read-only] int
WoofWare.Myriad.Plugins.ArgParserGenerator inherit obj, implements Myriad.Core.IMyriadGenerator
WoofWare.Myriad.Plugins.ArgParserGenerator..ctor [constructor]: unit
WoofWare.Myriad.Plugins.ArrayTypeDefinition inherit obj, implements WoofWare.Myriad.Plugins.ArrayTypeDefinition System.IEquatable, System.Collections.IStructuralEquatable
WoofWare.Myriad.Plugins.ArrayTypeDefinition..ctor [constructor]: WoofWare.Myriad.Plugins.Definition
WoofWare.Myriad.Plugins.ArrayTypeDefinition.Equals [method]: (WoofWare.Myriad.Plugins.ArrayTypeDefinition, System.Collections.IEqualityComparer) -> bool
WoofWare.Myriad.Plugins.ArrayTypeDefinition.get_Items [method]: unit -> WoofWare.Myriad.Plugins.Definition
WoofWare.Myriad.Plugins.ArrayTypeDefinition.Items [property]: [read-only] WoofWare.Myriad.Plugins.Definition
WoofWare.Myriad.Plugins.ArrayTypeDefinition.Parse [static method]: System.Text.Json.Nodes.JsonNode -> WoofWare.Myriad.Plugins.ArrayTypeDefinition
WoofWare.Myriad.Plugins.CreateCatamorphismGenerator inherit obj, implements Myriad.Core.IMyriadGenerator
WoofWare.Myriad.Plugins.CreateCatamorphismGenerator..ctor [constructor]: unit
WoofWare.Myriad.Plugins.Definition inherit obj, implements WoofWare.Myriad.Plugins.Definition System.IEquatable, System.Collections.IStructuralEquatable - union type with 8 cases
WoofWare.Myriad.Plugins.Definition+Array inherit WoofWare.Myriad.Plugins.Definition
WoofWare.Myriad.Plugins.Definition+Array.get_Item [method]: unit -> WoofWare.Myriad.Plugins.ArrayTypeDefinition
WoofWare.Myriad.Plugins.Definition+Array.Item [property]: [read-only] WoofWare.Myriad.Plugins.ArrayTypeDefinition
WoofWare.Myriad.Plugins.Definition+Handle inherit WoofWare.Myriad.Plugins.Definition
WoofWare.Myriad.Plugins.Definition+Handle.get_Item [method]: unit -> string
WoofWare.Myriad.Plugins.Definition+Handle.Item [property]: [read-only] string
WoofWare.Myriad.Plugins.Definition+Integer inherit WoofWare.Myriad.Plugins.Definition
WoofWare.Myriad.Plugins.Definition+Integer.format [property]: [read-only] string option
WoofWare.Myriad.Plugins.Definition+Integer.get_format [method]: unit -> string option
WoofWare.Myriad.Plugins.Definition+Object inherit WoofWare.Myriad.Plugins.Definition
WoofWare.Myriad.Plugins.Definition+Object.get_Item [method]: unit -> WoofWare.Myriad.Plugins.ObjectTypeDefinition
WoofWare.Myriad.Plugins.Definition+Object.Item [property]: [read-only] WoofWare.Myriad.Plugins.ObjectTypeDefinition
WoofWare.Myriad.Plugins.Definition+Tags inherit obj
WoofWare.Myriad.Plugins.Definition+Tags.Array [static field]: int = 2
WoofWare.Myriad.Plugins.Definition+Tags.Boolean [static field]: int = 4
WoofWare.Myriad.Plugins.Definition+Tags.File [static field]: int = 7
WoofWare.Myriad.Plugins.Definition+Tags.Handle [static field]: int = 0
WoofWare.Myriad.Plugins.Definition+Tags.Integer [static field]: int = 6
WoofWare.Myriad.Plugins.Definition+Tags.Object [static field]: int = 1
WoofWare.Myriad.Plugins.Definition+Tags.String [static field]: int = 3
WoofWare.Myriad.Plugins.Definition+Tags.Unspecified [static field]: int = 5
WoofWare.Myriad.Plugins.Definition.Boolean [static property]: [read-only] WoofWare.Myriad.Plugins.Definition
WoofWare.Myriad.Plugins.Definition.Equals [method]: (WoofWare.Myriad.Plugins.Definition, System.Collections.IEqualityComparer) -> bool
WoofWare.Myriad.Plugins.Definition.File [static property]: [read-only] WoofWare.Myriad.Plugins.Definition
WoofWare.Myriad.Plugins.Definition.get_Boolean [static method]: unit -> WoofWare.Myriad.Plugins.Definition
WoofWare.Myriad.Plugins.Definition.get_File [static method]: unit -> WoofWare.Myriad.Plugins.Definition
WoofWare.Myriad.Plugins.Definition.get_IsArray [method]: unit -> bool
WoofWare.Myriad.Plugins.Definition.get_IsBoolean [method]: unit -> bool
WoofWare.Myriad.Plugins.Definition.get_IsFile [method]: unit -> bool
WoofWare.Myriad.Plugins.Definition.get_IsHandle [method]: unit -> bool
WoofWare.Myriad.Plugins.Definition.get_IsInteger [method]: unit -> bool
WoofWare.Myriad.Plugins.Definition.get_IsObject [method]: unit -> bool
WoofWare.Myriad.Plugins.Definition.get_IsString [method]: unit -> bool
WoofWare.Myriad.Plugins.Definition.get_IsUnspecified [method]: unit -> bool
WoofWare.Myriad.Plugins.Definition.get_String [static method]: unit -> WoofWare.Myriad.Plugins.Definition
WoofWare.Myriad.Plugins.Definition.get_Tag [method]: unit -> int
WoofWare.Myriad.Plugins.Definition.get_Unspecified [static method]: unit -> WoofWare.Myriad.Plugins.Definition
WoofWare.Myriad.Plugins.Definition.IsArray [property]: [read-only] bool
WoofWare.Myriad.Plugins.Definition.IsBoolean [property]: [read-only] bool
WoofWare.Myriad.Plugins.Definition.IsFile [property]: [read-only] bool
WoofWare.Myriad.Plugins.Definition.IsHandle [property]: [read-only] bool
WoofWare.Myriad.Plugins.Definition.IsInteger [property]: [read-only] bool
WoofWare.Myriad.Plugins.Definition.IsObject [property]: [read-only] bool
WoofWare.Myriad.Plugins.Definition.IsString [property]: [read-only] bool
WoofWare.Myriad.Plugins.Definition.IsUnspecified [property]: [read-only] bool
WoofWare.Myriad.Plugins.Definition.NewArray [static method]: WoofWare.Myriad.Plugins.ArrayTypeDefinition -> WoofWare.Myriad.Plugins.Definition
WoofWare.Myriad.Plugins.Definition.NewHandle [static method]: string -> WoofWare.Myriad.Plugins.Definition
WoofWare.Myriad.Plugins.Definition.NewInteger [static method]: string option -> WoofWare.Myriad.Plugins.Definition
WoofWare.Myriad.Plugins.Definition.NewObject [static method]: WoofWare.Myriad.Plugins.ObjectTypeDefinition -> WoofWare.Myriad.Plugins.Definition
WoofWare.Myriad.Plugins.Definition.Parse [static method]: System.Text.Json.Nodes.JsonObject -> WoofWare.Myriad.Plugins.Definition
WoofWare.Myriad.Plugins.Definition.String [static property]: [read-only] WoofWare.Myriad.Plugins.Definition
WoofWare.Myriad.Plugins.Definition.Tag [property]: [read-only] int
WoofWare.Myriad.Plugins.Definition.Unspecified [static property]: [read-only] WoofWare.Myriad.Plugins.Definition
WoofWare.Myriad.Plugins.HttpClientGenerator inherit obj, implements Myriad.Core.IMyriadGenerator
WoofWare.Myriad.Plugins.HttpClientGenerator..ctor [constructor]: unit
WoofWare.Myriad.Plugins.HttpMethod inherit obj, implements WoofWare.Myriad.Plugins.HttpMethod System.IEquatable, System.Collections.IStructuralEquatable, WoofWare.Myriad.Plugins.HttpMethod System.IComparable, System.IComparable, System.Collections.IStructuralComparable - union type with 8 cases
WoofWare.Myriad.Plugins.HttpMethod+Tags inherit obj
WoofWare.Myriad.Plugins.HttpMethod+Tags.Delete [static field]: int = 2
WoofWare.Myriad.Plugins.HttpMethod+Tags.Get [static field]: int = 0
WoofWare.Myriad.Plugins.HttpMethod+Tags.Head [static field]: int = 5
WoofWare.Myriad.Plugins.HttpMethod+Tags.Options [static field]: int = 4
WoofWare.Myriad.Plugins.HttpMethod+Tags.Patch [static field]: int = 3
WoofWare.Myriad.Plugins.HttpMethod+Tags.Post [static field]: int = 1
WoofWare.Myriad.Plugins.HttpMethod+Tags.Put [static field]: int = 6
WoofWare.Myriad.Plugins.HttpMethod+Tags.Trace [static field]: int = 7
WoofWare.Myriad.Plugins.HttpMethod.Delete [static property]: [read-only] WoofWare.Myriad.Plugins.HttpMethod
WoofWare.Myriad.Plugins.HttpMethod.Equals [method]: (WoofWare.Myriad.Plugins.HttpMethod, System.Collections.IEqualityComparer) -> bool
WoofWare.Myriad.Plugins.HttpMethod.Get [static property]: [read-only] WoofWare.Myriad.Plugins.HttpMethod
WoofWare.Myriad.Plugins.HttpMethod.get_Delete [static method]: unit -> WoofWare.Myriad.Plugins.HttpMethod
WoofWare.Myriad.Plugins.HttpMethod.get_Get [static method]: unit -> WoofWare.Myriad.Plugins.HttpMethod
WoofWare.Myriad.Plugins.HttpMethod.get_Head [static method]: unit -> WoofWare.Myriad.Plugins.HttpMethod
WoofWare.Myriad.Plugins.HttpMethod.get_IsDelete [method]: unit -> bool
WoofWare.Myriad.Plugins.HttpMethod.get_IsGet [method]: unit -> bool
WoofWare.Myriad.Plugins.HttpMethod.get_IsHead [method]: unit -> bool
WoofWare.Myriad.Plugins.HttpMethod.get_IsOptions [method]: unit -> bool
WoofWare.Myriad.Plugins.HttpMethod.get_IsPatch [method]: unit -> bool
WoofWare.Myriad.Plugins.HttpMethod.get_IsPost [method]: unit -> bool
WoofWare.Myriad.Plugins.HttpMethod.get_IsPut [method]: unit -> bool
WoofWare.Myriad.Plugins.HttpMethod.get_IsTrace [method]: unit -> bool
WoofWare.Myriad.Plugins.HttpMethod.get_Options [static method]: unit -> WoofWare.Myriad.Plugins.HttpMethod
WoofWare.Myriad.Plugins.HttpMethod.get_Patch [static method]: unit -> WoofWare.Myriad.Plugins.HttpMethod
WoofWare.Myriad.Plugins.HttpMethod.get_Post [static method]: unit -> WoofWare.Myriad.Plugins.HttpMethod
WoofWare.Myriad.Plugins.HttpMethod.get_Put [static method]: unit -> WoofWare.Myriad.Plugins.HttpMethod
WoofWare.Myriad.Plugins.HttpMethod.get_Tag [method]: unit -> int
WoofWare.Myriad.Plugins.HttpMethod.get_Trace [static method]: unit -> WoofWare.Myriad.Plugins.HttpMethod
WoofWare.Myriad.Plugins.HttpMethod.Head [static property]: [read-only] WoofWare.Myriad.Plugins.HttpMethod
WoofWare.Myriad.Plugins.HttpMethod.IsDelete [property]: [read-only] bool
WoofWare.Myriad.Plugins.HttpMethod.IsGet [property]: [read-only] bool
WoofWare.Myriad.Plugins.HttpMethod.IsHead [property]: [read-only] bool
WoofWare.Myriad.Plugins.HttpMethod.IsOptions [property]: [read-only] bool
WoofWare.Myriad.Plugins.HttpMethod.IsPatch [property]: [read-only] bool
WoofWare.Myriad.Plugins.HttpMethod.IsPost [property]: [read-only] bool
WoofWare.Myriad.Plugins.HttpMethod.IsPut [property]: [read-only] bool
WoofWare.Myriad.Plugins.HttpMethod.IsTrace [property]: [read-only] bool
WoofWare.Myriad.Plugins.HttpMethod.Options [static property]: [read-only] WoofWare.Myriad.Plugins.HttpMethod
WoofWare.Myriad.Plugins.HttpMethod.Parse [static method]: string -> WoofWare.Myriad.Plugins.HttpMethod
WoofWare.Myriad.Plugins.HttpMethod.Patch [static property]: [read-only] WoofWare.Myriad.Plugins.HttpMethod
WoofWare.Myriad.Plugins.HttpMethod.Post [static property]: [read-only] WoofWare.Myriad.Plugins.HttpMethod
WoofWare.Myriad.Plugins.HttpMethod.Put [static property]: [read-only] WoofWare.Myriad.Plugins.HttpMethod
WoofWare.Myriad.Plugins.HttpMethod.Tag [property]: [read-only] int
WoofWare.Myriad.Plugins.HttpMethod.ToDotNet [method]: unit -> System.Net.Http.HttpMethod
WoofWare.Myriad.Plugins.HttpMethod.Trace [static property]: [read-only] WoofWare.Myriad.Plugins.HttpMethod
WoofWare.Myriad.Plugins.InterfaceMockGenerator inherit obj, implements Myriad.Core.IMyriadGenerator
WoofWare.Myriad.Plugins.InterfaceMockGenerator..ctor [constructor]: unit
WoofWare.Myriad.Plugins.JsonParseGenerator inherit obj, implements Myriad.Core.IMyriadGenerator
WoofWare.Myriad.Plugins.JsonParseGenerator..ctor [constructor]: unit
WoofWare.Myriad.Plugins.JsonSerializeGenerator inherit obj, implements Myriad.Core.IMyriadGenerator
WoofWare.Myriad.Plugins.JsonSerializeGenerator..ctor [constructor]: unit
WoofWare.Myriad.Plugins.MimeType inherit obj, implements WoofWare.Myriad.Plugins.MimeType System.IEquatable, System.Collections.IStructuralEquatable, WoofWare.Myriad.Plugins.MimeType System.IComparable, System.IComparable, System.Collections.IStructuralComparable - union type with 1 cases
WoofWare.Myriad.Plugins.MimeType.Equals [method]: (WoofWare.Myriad.Plugins.MimeType, System.Collections.IEqualityComparer) -> bool
WoofWare.Myriad.Plugins.MimeType.get_Item [method]: unit -> string
WoofWare.Myriad.Plugins.MimeType.get_Tag [method]: unit -> int
WoofWare.Myriad.Plugins.MimeType.Item [property]: [read-only] string
WoofWare.Myriad.Plugins.MimeType.NewMimeType [static method]: string -> WoofWare.Myriad.Plugins.MimeType
WoofWare.Myriad.Plugins.MimeType.Tag [property]: [read-only] int
WoofWare.Myriad.Plugins.ObjectTypeDefinition inherit obj, implements WoofWare.Myriad.Plugins.ObjectTypeDefinition System.IEquatable, System.Collections.IStructuralEquatable
WoofWare.Myriad.Plugins.ObjectTypeDefinition..ctor [constructor]: (string option, Map<string, WoofWare.Myriad.Plugins.Definition> option, Map<string, System.Text.Json.Nodes.JsonNode>, string list option, WoofWare.Myriad.Plugins.AdditionalProperties option, System.Text.Json.Nodes.JsonObject option)
WoofWare.Myriad.Plugins.ObjectTypeDefinition.AdditionalProperties [property]: [read-only] WoofWare.Myriad.Plugins.AdditionalProperties option
WoofWare.Myriad.Plugins.ObjectTypeDefinition.Description [property]: [read-only] string option
WoofWare.Myriad.Plugins.ObjectTypeDefinition.Equals [method]: (WoofWare.Myriad.Plugins.ObjectTypeDefinition, System.Collections.IEqualityComparer) -> bool
WoofWare.Myriad.Plugins.ObjectTypeDefinition.Example [property]: [read-only] System.Text.Json.Nodes.JsonObject option
WoofWare.Myriad.Plugins.ObjectTypeDefinition.Extras [property]: [read-only] Map<string, System.Text.Json.Nodes.JsonNode>
WoofWare.Myriad.Plugins.ObjectTypeDefinition.get_AdditionalProperties [method]: unit -> WoofWare.Myriad.Plugins.AdditionalProperties option
WoofWare.Myriad.Plugins.ObjectTypeDefinition.get_Description [method]: unit -> string option
WoofWare.Myriad.Plugins.ObjectTypeDefinition.get_Example [method]: unit -> System.Text.Json.Nodes.JsonObject option
WoofWare.Myriad.Plugins.ObjectTypeDefinition.get_Extras [method]: unit -> Map<string, System.Text.Json.Nodes.JsonNode>
WoofWare.Myriad.Plugins.ObjectTypeDefinition.get_Properties [method]: unit -> Map<string, WoofWare.Myriad.Plugins.Definition> option
WoofWare.Myriad.Plugins.ObjectTypeDefinition.get_Required [method]: unit -> string list option
WoofWare.Myriad.Plugins.ObjectTypeDefinition.Parse [static method]: System.Text.Json.Nodes.JsonObject -> WoofWare.Myriad.Plugins.ObjectTypeDefinition
WoofWare.Myriad.Plugins.ObjectTypeDefinition.Properties [property]: [read-only] Map<string, WoofWare.Myriad.Plugins.Definition> option
WoofWare.Myriad.Plugins.ObjectTypeDefinition.Required [property]: [read-only] string list option
WoofWare.Myriad.Plugins.OperationId inherit obj, implements WoofWare.Myriad.Plugins.OperationId System.IEquatable, System.Collections.IStructuralEquatable, WoofWare.Myriad.Plugins.OperationId System.IComparable, System.IComparable, System.Collections.IStructuralComparable - union type with 1 cases
WoofWare.Myriad.Plugins.OperationId.Equals [method]: (WoofWare.Myriad.Plugins.OperationId, System.Collections.IEqualityComparer) -> bool
WoofWare.Myriad.Plugins.OperationId.get_Item [method]: unit -> string
WoofWare.Myriad.Plugins.OperationId.get_Tag [method]: unit -> int
WoofWare.Myriad.Plugins.OperationId.Item [property]: [read-only] string
WoofWare.Myriad.Plugins.OperationId.NewOperationId [static method]: string -> WoofWare.Myriad.Plugins.OperationId
WoofWare.Myriad.Plugins.OperationId.Tag [property]: [read-only] int
WoofWare.Myriad.Plugins.ParameterIn inherit obj, implements WoofWare.Myriad.Plugins.ParameterIn System.IEquatable, System.Collections.IStructuralEquatable, WoofWare.Myriad.Plugins.ParameterIn System.IComparable, System.IComparable, System.Collections.IStructuralComparable - union type with 4 cases
WoofWare.Myriad.Plugins.ParameterIn+Path inherit WoofWare.Myriad.Plugins.ParameterIn
WoofWare.Myriad.Plugins.ParameterIn+Path.get_name [method]: unit -> string
WoofWare.Myriad.Plugins.ParameterIn+Path.name [property]: [read-only] string
WoofWare.Myriad.Plugins.ParameterIn+Query inherit WoofWare.Myriad.Plugins.ParameterIn
WoofWare.Myriad.Plugins.ParameterIn+Query.get_name [method]: unit -> string
WoofWare.Myriad.Plugins.ParameterIn+Query.name [property]: [read-only] string
WoofWare.Myriad.Plugins.ParameterIn+Tags inherit obj
WoofWare.Myriad.Plugins.ParameterIn+Tags.Body [static field]: int = 2
WoofWare.Myriad.Plugins.ParameterIn+Tags.Path [static field]: int = 0
WoofWare.Myriad.Plugins.ParameterIn+Tags.Query [static field]: int = 1
WoofWare.Myriad.Plugins.ParameterIn+Tags.Unrecognised [static field]: int = 3
WoofWare.Myriad.Plugins.ParameterIn+Unrecognised inherit WoofWare.Myriad.Plugins.ParameterIn
WoofWare.Myriad.Plugins.ParameterIn+Unrecognised.get_name [method]: unit -> string
WoofWare.Myriad.Plugins.ParameterIn+Unrecognised.get_op [method]: unit -> string
WoofWare.Myriad.Plugins.ParameterIn+Unrecognised.name [property]: [read-only] string
WoofWare.Myriad.Plugins.ParameterIn+Unrecognised.op [property]: [read-only] string
WoofWare.Myriad.Plugins.ParameterIn.Body [static property]: [read-only] WoofWare.Myriad.Plugins.ParameterIn
WoofWare.Myriad.Plugins.ParameterIn.Equals [method]: (WoofWare.Myriad.Plugins.ParameterIn, System.Collections.IEqualityComparer) -> bool
WoofWare.Myriad.Plugins.ParameterIn.get_Body [static method]: unit -> WoofWare.Myriad.Plugins.ParameterIn
WoofWare.Myriad.Plugins.ParameterIn.get_IsBody [method]: unit -> bool
WoofWare.Myriad.Plugins.ParameterIn.get_IsPath [method]: unit -> bool
WoofWare.Myriad.Plugins.ParameterIn.get_IsQuery [method]: unit -> bool
WoofWare.Myriad.Plugins.ParameterIn.get_IsUnrecognised [method]: unit -> bool
WoofWare.Myriad.Plugins.ParameterIn.get_Tag [method]: unit -> int
WoofWare.Myriad.Plugins.ParameterIn.IsBody [property]: [read-only] bool
WoofWare.Myriad.Plugins.ParameterIn.IsPath [property]: [read-only] bool
WoofWare.Myriad.Plugins.ParameterIn.IsQuery [property]: [read-only] bool
WoofWare.Myriad.Plugins.ParameterIn.IsUnrecognised [property]: [read-only] bool
WoofWare.Myriad.Plugins.ParameterIn.NewPath [static method]: string -> WoofWare.Myriad.Plugins.ParameterIn
WoofWare.Myriad.Plugins.ParameterIn.NewQuery [static method]: string -> WoofWare.Myriad.Plugins.ParameterIn
WoofWare.Myriad.Plugins.ParameterIn.NewUnrecognised [static method]: (string, string) -> WoofWare.Myriad.Plugins.ParameterIn
WoofWare.Myriad.Plugins.ParameterIn.Tag [property]: [read-only] int
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.Response inherit obj, implements WoofWare.Myriad.Plugins.Response System.IEquatable, System.Collections.IStructuralEquatable
WoofWare.Myriad.Plugins.Response..ctor [constructor]: (string, WoofWare.Myriad.Plugins.Definition)
WoofWare.Myriad.Plugins.Response.Description [property]: [read-only] string
WoofWare.Myriad.Plugins.Response.Equals [method]: (WoofWare.Myriad.Plugins.Response, System.Collections.IEqualityComparer) -> bool
WoofWare.Myriad.Plugins.Response.get_Description [method]: unit -> string
WoofWare.Myriad.Plugins.Response.get_Schema [method]: unit -> WoofWare.Myriad.Plugins.Definition
WoofWare.Myriad.Plugins.Response.Parse [static method]: System.Text.Json.Nodes.JsonObject -> WoofWare.Myriad.Plugins.Response
WoofWare.Myriad.Plugins.Response.Schema [property]: [read-only] WoofWare.Myriad.Plugins.Definition
WoofWare.Myriad.Plugins.Scheme inherit obj, implements WoofWare.Myriad.Plugins.Scheme System.IEquatable, System.Collections.IStructuralEquatable, WoofWare.Myriad.Plugins.Scheme System.IComparable, System.IComparable, System.Collections.IStructuralComparable - union type with 1 cases
WoofWare.Myriad.Plugins.Scheme.Equals [method]: (WoofWare.Myriad.Plugins.Scheme, System.Collections.IEqualityComparer) -> bool
WoofWare.Myriad.Plugins.Scheme.get_Item [method]: unit -> string
WoofWare.Myriad.Plugins.Scheme.get_Tag [method]: unit -> int
WoofWare.Myriad.Plugins.Scheme.Item [property]: [read-only] string
WoofWare.Myriad.Plugins.Scheme.NewScheme [static method]: string -> WoofWare.Myriad.Plugins.Scheme
WoofWare.Myriad.Plugins.Scheme.Tag [property]: [read-only] int
WoofWare.Myriad.Plugins.Swagger inherit obj, implements WoofWare.Myriad.Plugins.Swagger System.IEquatable, System.Collections.IStructuralEquatable
WoofWare.Myriad.Plugins.Swagger..ctor [constructor]: (WoofWare.Myriad.Plugins.MimeType list, WoofWare.Myriad.Plugins.MimeType list, WoofWare.Myriad.Plugins.Scheme list, System.Version, WoofWare.Myriad.Plugins.SwaggerInfo, string, Map<string, Map<WoofWare.Myriad.Plugins.HttpMethod, WoofWare.Myriad.Plugins.SwaggerEndpoint>>, Map<string, WoofWare.Myriad.Plugins.Definition>, Map<string, WoofWare.Myriad.Plugins.Response>)
WoofWare.Myriad.Plugins.Swagger.BasePath [property]: [read-only] string
WoofWare.Myriad.Plugins.Swagger.Consumes [property]: [read-only] WoofWare.Myriad.Plugins.MimeType list
WoofWare.Myriad.Plugins.Swagger.Definitions [property]: [read-only] Map<string, WoofWare.Myriad.Plugins.Definition>
WoofWare.Myriad.Plugins.Swagger.Equals [method]: (WoofWare.Myriad.Plugins.Swagger, System.Collections.IEqualityComparer) -> bool
WoofWare.Myriad.Plugins.Swagger.get_BasePath [method]: unit -> string
WoofWare.Myriad.Plugins.Swagger.get_Consumes [method]: unit -> WoofWare.Myriad.Plugins.MimeType list
WoofWare.Myriad.Plugins.Swagger.get_Definitions [method]: unit -> Map<string, WoofWare.Myriad.Plugins.Definition>
WoofWare.Myriad.Plugins.Swagger.get_Info [method]: unit -> WoofWare.Myriad.Plugins.SwaggerInfo
WoofWare.Myriad.Plugins.Swagger.get_Paths [method]: unit -> Map<string, Map<WoofWare.Myriad.Plugins.HttpMethod, WoofWare.Myriad.Plugins.SwaggerEndpoint>>
WoofWare.Myriad.Plugins.Swagger.get_Produces [method]: unit -> WoofWare.Myriad.Plugins.MimeType list
WoofWare.Myriad.Plugins.Swagger.get_Responses [method]: unit -> Map<string, WoofWare.Myriad.Plugins.Response>
WoofWare.Myriad.Plugins.Swagger.get_Schemes [method]: unit -> WoofWare.Myriad.Plugins.Scheme list
WoofWare.Myriad.Plugins.Swagger.get_Swagger [method]: unit -> System.Version
WoofWare.Myriad.Plugins.Swagger.Info [property]: [read-only] WoofWare.Myriad.Plugins.SwaggerInfo
WoofWare.Myriad.Plugins.Swagger.Paths [property]: [read-only] Map<string, Map<WoofWare.Myriad.Plugins.HttpMethod, WoofWare.Myriad.Plugins.SwaggerEndpoint>>
WoofWare.Myriad.Plugins.Swagger.Produces [property]: [read-only] WoofWare.Myriad.Plugins.MimeType list
WoofWare.Myriad.Plugins.Swagger.Responses [property]: [read-only] Map<string, WoofWare.Myriad.Plugins.Response>
WoofWare.Myriad.Plugins.Swagger.Schemes [property]: [read-only] WoofWare.Myriad.Plugins.Scheme list
WoofWare.Myriad.Plugins.Swagger.Swagger [property]: [read-only] System.Version
WoofWare.Myriad.Plugins.SwaggerClientGenerator inherit obj, implements Myriad.Core.IMyriadGenerator
WoofWare.Myriad.Plugins.SwaggerClientGenerator..ctor [constructor]: unit
WoofWare.Myriad.Plugins.SwaggerEndpoint inherit obj, implements WoofWare.Myriad.Plugins.SwaggerEndpoint System.IEquatable, System.Collections.IStructuralEquatable
WoofWare.Myriad.Plugins.SwaggerEndpoint..ctor [constructor]: (WoofWare.Myriad.Plugins.MimeType list option, WoofWare.Myriad.Plugins.MimeType list option, string list, string, WoofWare.Myriad.Plugins.OperationId, WoofWare.Myriad.Plugins.SwaggerParameter list option, Map<int, WoofWare.Myriad.Plugins.Definition>)
WoofWare.Myriad.Plugins.SwaggerEndpoint.Consumes [property]: [read-only] WoofWare.Myriad.Plugins.MimeType list option
WoofWare.Myriad.Plugins.SwaggerEndpoint.Equals [method]: (WoofWare.Myriad.Plugins.SwaggerEndpoint, System.Collections.IEqualityComparer) -> bool
WoofWare.Myriad.Plugins.SwaggerEndpoint.get_Consumes [method]: unit -> WoofWare.Myriad.Plugins.MimeType list option
WoofWare.Myriad.Plugins.SwaggerEndpoint.get_OperationId [method]: unit -> WoofWare.Myriad.Plugins.OperationId
WoofWare.Myriad.Plugins.SwaggerEndpoint.get_Parameters [method]: unit -> WoofWare.Myriad.Plugins.SwaggerParameter list option
WoofWare.Myriad.Plugins.SwaggerEndpoint.get_Produces [method]: unit -> WoofWare.Myriad.Plugins.MimeType list option
WoofWare.Myriad.Plugins.SwaggerEndpoint.get_Responses [method]: unit -> Map<int, WoofWare.Myriad.Plugins.Definition>
WoofWare.Myriad.Plugins.SwaggerEndpoint.get_Summary [method]: unit -> string
WoofWare.Myriad.Plugins.SwaggerEndpoint.get_Tags [method]: unit -> string list
WoofWare.Myriad.Plugins.SwaggerEndpoint.OperationId [property]: [read-only] WoofWare.Myriad.Plugins.OperationId
WoofWare.Myriad.Plugins.SwaggerEndpoint.Parameters [property]: [read-only] WoofWare.Myriad.Plugins.SwaggerParameter list option
WoofWare.Myriad.Plugins.SwaggerEndpoint.Parse [static method]: System.Text.Json.Nodes.JsonObject -> WoofWare.Myriad.Plugins.SwaggerEndpoint
WoofWare.Myriad.Plugins.SwaggerEndpoint.Produces [property]: [read-only] WoofWare.Myriad.Plugins.MimeType list option
WoofWare.Myriad.Plugins.SwaggerEndpoint.Responses [property]: [read-only] Map<int, WoofWare.Myriad.Plugins.Definition>
WoofWare.Myriad.Plugins.SwaggerEndpoint.Summary [property]: [read-only] string
WoofWare.Myriad.Plugins.SwaggerEndpoint.Tags [property]: [read-only] string list
WoofWare.Myriad.Plugins.SwaggerInfo inherit obj, implements WoofWare.Myriad.Plugins.SwaggerInfo System.IEquatable, System.Collections.IStructuralEquatable
WoofWare.Myriad.Plugins.SwaggerInfo..ctor [constructor]: (string, string, WoofWare.Myriad.Plugins.SwaggerLicense, System.Version)
WoofWare.Myriad.Plugins.SwaggerInfo.Description [property]: [read-only] string
WoofWare.Myriad.Plugins.SwaggerInfo.Equals [method]: (WoofWare.Myriad.Plugins.SwaggerInfo, System.Collections.IEqualityComparer) -> bool
WoofWare.Myriad.Plugins.SwaggerInfo.get_Description [method]: unit -> string
WoofWare.Myriad.Plugins.SwaggerInfo.get_License [method]: unit -> WoofWare.Myriad.Plugins.SwaggerLicense
WoofWare.Myriad.Plugins.SwaggerInfo.get_Title [method]: unit -> string
WoofWare.Myriad.Plugins.SwaggerInfo.get_Version [method]: unit -> System.Version
WoofWare.Myriad.Plugins.SwaggerInfo.License [property]: [read-only] WoofWare.Myriad.Plugins.SwaggerLicense
WoofWare.Myriad.Plugins.SwaggerInfo.Parse [static method]: System.Text.Json.Nodes.JsonObject -> WoofWare.Myriad.Plugins.SwaggerInfo
WoofWare.Myriad.Plugins.SwaggerInfo.Title [property]: [read-only] string
WoofWare.Myriad.Plugins.SwaggerInfo.Version [property]: [read-only] System.Version
WoofWare.Myriad.Plugins.SwaggerLicense inherit obj, implements WoofWare.Myriad.Plugins.SwaggerLicense System.IEquatable, System.Collections.IStructuralEquatable
WoofWare.Myriad.Plugins.SwaggerLicense..ctor [constructor]: (string, System.Uri option, string option)
WoofWare.Myriad.Plugins.SwaggerLicense.Equals [method]: (WoofWare.Myriad.Plugins.SwaggerLicense, System.Collections.IEqualityComparer) -> bool
WoofWare.Myriad.Plugins.SwaggerLicense.get_Identifier [method]: unit -> string option
WoofWare.Myriad.Plugins.SwaggerLicense.get_Name [method]: unit -> string
WoofWare.Myriad.Plugins.SwaggerLicense.get_Url [method]: unit -> System.Uri option
WoofWare.Myriad.Plugins.SwaggerLicense.Identifier [property]: [read-only] string option
WoofWare.Myriad.Plugins.SwaggerLicense.Name [property]: [read-only] string
WoofWare.Myriad.Plugins.SwaggerLicense.Parse [static method]: System.Text.Json.Nodes.JsonObject -> WoofWare.Myriad.Plugins.SwaggerLicense
WoofWare.Myriad.Plugins.SwaggerLicense.Url [property]: [read-only] System.Uri option
WoofWare.Myriad.Plugins.SwaggerModule inherit obj
WoofWare.Myriad.Plugins.SwaggerModule.parse [static method]: string -> WoofWare.Myriad.Plugins.Swagger
WoofWare.Myriad.Plugins.SwaggerParameter inherit obj, implements WoofWare.Myriad.Plugins.SwaggerParameter System.IEquatable, System.Collections.IStructuralEquatable
WoofWare.Myriad.Plugins.SwaggerParameter..ctor [constructor]: (WoofWare.Myriad.Plugins.Definition, string option, WoofWare.Myriad.Plugins.ParameterIn, string, bool option)
WoofWare.Myriad.Plugins.SwaggerParameter.Description [property]: [read-only] string option
WoofWare.Myriad.Plugins.SwaggerParameter.Equals [method]: (WoofWare.Myriad.Plugins.SwaggerParameter, System.Collections.IEqualityComparer) -> bool
WoofWare.Myriad.Plugins.SwaggerParameter.get_Description [method]: unit -> string option
WoofWare.Myriad.Plugins.SwaggerParameter.get_In [method]: unit -> WoofWare.Myriad.Plugins.ParameterIn
WoofWare.Myriad.Plugins.SwaggerParameter.get_Name [method]: unit -> string
WoofWare.Myriad.Plugins.SwaggerParameter.get_Required [method]: unit -> bool option
WoofWare.Myriad.Plugins.SwaggerParameter.get_Type [method]: unit -> WoofWare.Myriad.Plugins.Definition
WoofWare.Myriad.Plugins.SwaggerParameter.In [property]: [read-only] WoofWare.Myriad.Plugins.ParameterIn
WoofWare.Myriad.Plugins.SwaggerParameter.Name [property]: [read-only] string
WoofWare.Myriad.Plugins.SwaggerParameter.Parse [static method]: System.Text.Json.Nodes.JsonObject -> WoofWare.Myriad.Plugins.SwaggerParameter
WoofWare.Myriad.Plugins.SwaggerParameter.Required [property]: [read-only] bool option
WoofWare.Myriad.Plugins.SwaggerParameter.Type [property]: [read-only] WoofWare.Myriad.Plugins.Definition

View File

@@ -0,0 +1,576 @@
namespace WoofWare.Myriad.Plugins
open System
open System.Text.Json.Nodes
[<AutoOpen>]
module internal JsonHelpers =
let inline asString (n : JsonNode) (key : string) : string =
match n.[key] with
| null -> failwith $"Expected node to have a key '%s{key}', but it did not: %s{n.ToJsonString ()}"
| s -> s.GetValue<string> ()
[<RequiresExplicitTypeArguments>]
let inline asOpt<'ret> (n : JsonNode) (key : string) : 'ret option =
match n.[key] with
| null -> None
| s -> s.GetValue<'ret> () |> Some
let inline asObj (n : JsonNode) (key : string) : JsonObject =
match n.[key] with
| null -> failwith $"Expected node to have a key '%s{key}', but it did not: %s{n.ToJsonString ()}"
| o -> o.AsObject ()
let inline asObjOpt (n : JsonNode) (key : string) : JsonObject option =
match n.[key] with
| null -> None
| o -> o.AsObject () |> Some
let inline asArr (n : JsonNode) (key : string) : JsonArray =
match n.[key] with
| null -> failwith $"Expected node to have a key '%s{key}', but it did not: %s{n.ToJsonString ()}"
| o -> o.AsArray ()
let inline asArrOpt (n : JsonNode) (key : string) : JsonArray option =
match n.[key] with
| null -> None
| o -> o.AsArray () |> Some
[<RequiresExplicitTypeArguments>]
let inline asArr'<'v> (n : JsonNode) (key : string) : 'v list =
match n.[key] with
| null -> failwith $"Expected node to have a key '%s{key}', but it did not: %s{n.ToJsonString ()}"
| o -> o.AsArray () |> Seq.map (fun v -> v.GetValue<'v> ()) |> Seq.toList
[<RequiresExplicitTypeArguments>]
let inline asArrOpt'<'v> (n : JsonNode) (key : string) : 'v list option =
match n.[key] with
| null -> None
| o -> o.AsArray () |> Seq.map (fun v -> v.GetValue<'v> ()) |> Seq.toList |> Some
/// A MIME type, like "application/json"
type MimeType =
/// A MIME type, like "application/json"
| MimeType of string
/// A URL scheme, like "https"
type Scheme =
/// A URL scheme, like "https"
| Scheme of string
/// "Licence information for the exposed API", whatever that means.
type SwaggerLicense =
{
/// "The license name used for the API", whatever that means.
Name : string
/// Link to the license used. Mutually exclusive with `Identifier`.
Url : Uri option
/// SPDX license identifier. Mutually exclusive with `Url`.
Identifier : string option
}
/// Render a JsonObject into the strongly-typed version, performing sanity
/// checks and throwing on input that can't be parsed.
static member Parse (node : JsonObject) : SwaggerLicense =
let name = asString node "name"
let url = asOpt<string> node "url" |> Option.map Uri
let identifier = asOpt<string> node "identifier"
match url, identifier with
| Some _, Some _ -> failwith "Invalid license spec: cannot supply both URL and identifier"
| _, _ -> ()
{
Name = name
Url = url
Identifier = identifier
}
/// Overall information about the API described by this Swagger spec.
type SwaggerInfo =
{
/// Human-readable description of what this Swagger API is for.
/// Supports GitHub-flavoured markdown, apparently.
Description : string
/// Human-readable title of the service to which this is an API.
Title : string
/// The license applying to this schema. It's very unclear what this means.
/// The spec just says:
/// "Licence information for the exposed API"
License : SwaggerLicense
/// The version of this API (not the version of Swagger or the file defining the API!).
/// Strictly speaking this can be anything, but I am assuming it's roughly
/// SemVer.
Version : Version
}
/// Render a JsonObject into the strongly-typed version, performing sanity
/// checks and throwing on input that can't be parsed.
static member Parse (node : JsonObject) : SwaggerInfo =
let description = asString node "description"
let title = asString node "title"
let version = asString node "version" |> Version.Parse
let license = asObj node "license" |> SwaggerLicense.Parse
{
Description = description
Title = title
License = license
Version = version
}
/// An "optional unique string used to describe an operation".
/// If present, these are assumed to be unique among all operations described
/// in the API.
type OperationId =
/// An "optional unique string used to describe an operation".
/// If present, these are assumed to be unique among all operations described
/// in the API.
| OperationId of string
/// Round-trip string representation.
override this.ToString () =
match this with
| OperationId.OperationId s -> s
/// Constraints on the `additionalProperties` (in the JSON schema sense).
/// "Additional properties" are properties of a JSON object which were not
/// listed in the schema.
type AdditionalProperties =
/// No additional properties are allowed: all properties must have been
/// mentioned in the schema.
| Never
/// Additional properties are permitted, but if they exist, they must
/// match this schema definition.
| Constrained of Definition
/// The Swagger schema lets you define types. An ObjectTypeDefinition
/// is specifically the information about types defined as `"type": "object"`.
and ObjectTypeDefinition =
{
/// Human-readable description of the purpose of this type.
Description : string option
/// Fields which any object must have to satisfy this type.
Properties : Map<string, Definition> option
/// Extra properties in the type description. In Gitea, these are
/// (for example) "x-go-package":"code.gitea.io/gitea/modules/structs".
Extras : Map<string, JsonNode>
/// List of fields which are required; all other fields are optional.
Required : string list option
/// Constraints, if any, placed on fields which are not mentioned in
/// the schema. If absent, there are no constraints.
AdditionalProperties : AdditionalProperties option
/// Example of an object which satisfies this schema.
Example : JsonObject option
}
/// Render a JsonObject into the strongly-typed version, performing sanity
/// checks and throwing on input that can't be parsed.
static member Parse (node : JsonObject) : ObjectTypeDefinition =
let description =
match asOpt<string> node "description", asOpt<string> node "title" with
| None, None -> None
| Some v, None
| None, Some v -> Some v
| Some v1, Some v2 -> failwith "both description and title were given"
let additionalProperties =
match node.["additionalProperties"] with
| null -> None
| :? JsonValue as p ->
if not (p.GetValue<bool> ()) then
Some AdditionalProperties.Never
else
failwith $"additionalProperties should be 'false' or an object, but was: %s{p.ToJsonString ()}"
| p ->
let p = p.AsObject ()
Definition.Parse p |> AdditionalProperties.Constrained |> Some
let properties =
match node.["properties"] with
| null -> None
| p ->
p.AsObject ()
|> Seq.map (fun (KeyValue (key, value)) ->
let value = value.AsObject ()
key, Definition.Parse value
)
|> Map.ofSeq
|> Some
let example = asObjOpt node "example"
let required = asArrOpt'<string> node "required"
let extras =
node.AsObject ()
|> Seq.choose (fun (KeyValue (key, value)) ->
match key with
| "type"
| "description"
| "title"
| "additionalProperties"
| "example"
| "required"
| "properties" -> None
| _ -> Some (key, value)
)
|> Map.ofSeq
{
Description = description
Properties = properties
AdditionalProperties = additionalProperties
Required = required
Extras = extras
Example = example
}
/// The Swagger schema lets you define types. An ArrayTypeDefinition
/// is specifically the information about types defined as `"type": "array"`.
and ArrayTypeDefinition =
{
/// The type is `'a array`; this field describes `'a`.
Items : Definition
}
/// Render a JsonNode into the strongly-typed version, performing sanity
/// checks and throwing on input that can't be parsed.
static member Parse (n : JsonNode) : ArrayTypeDefinition =
let items = asObj n "items" |> Definition.Parse
{
Items = items
}
/// Any definition of a type in the Swagger document. This is basically any
/// information associated with the `"type": "blah"` field.
and Definition =
/// For example, if `"$ref": "#/responses/Blah", then this is "#/responses/Blah".
| Handle of string
/// A type definition with "type": "object".
| Object of ObjectTypeDefinition
/// A type definition with "type": "array".
| Array of ArrayTypeDefinition
/// A type definition with "type": "string".
| String
/// A type definition with "type": "boolean".
| Boolean
/// A response without a body has no "schema" specified.
| Unspecified
/// A type definition with "type": "integer".
/// The format is an optional hint which could be e.g. "int64" or "int32".
/// https://swagger.io/docs/specification/data-models/data-types/#numbers
| Integer of format : string option
/// Not a JSON schema type, but a Swagger 2.0 type.
| File
/// Render a JsonObject into this strongly-typed specification.
static member Parse (n : JsonObject) : Definition =
match n.["$ref"] |> Option.ofObj with
| Some ref -> Definition.Handle (ref.GetValue<string> ())
| None ->
let ty = asOpt<string> n "type"
match ty with
| None -> Definition.Unspecified
| Some "object" -> ObjectTypeDefinition.Parse n |> Definition.Object
| Some "array" -> ArrayTypeDefinition.Parse n |> Definition.Array
| Some "string" -> Definition.String
| Some "boolean" -> Definition.Boolean
| Some "file" -> Definition.File
| Some "integer" ->
let format = asOpt<string> n "format"
Definition.Integer format
| Some ty -> failwith $"Unrecognised type: %s{ty}"
/// REST APIs allow their parameters to be passed in various ways. This describes
/// how one single parameter is passed.
type ParameterIn =
/// The parameter is interpolated into the path, e.g. "/foo/{blah}".
/// The "name" is what we replace in the path: e.g. "/foo/{person}" would
/// have a name of "person".
| Path of name : string
/// The parameter is appended to the URL's query params, e.g. "?<name>=blah"
| Query of name : string
/// The parameter is passed in the body of the HTTP request.
| Body
/// Some spec that WoofWare.Myriad doesn't support.
| Unrecognised of op : string * name : string
/// Description of a single input parameter to an endpoint.
type SwaggerParameter =
{
/// The type schema to which this parameter must conform.
Type : Definition
/// Optional human-readable description of this parameter.
Description : string option
/// How this parameter is passed.
In : ParameterIn
/// Name of this parameter. For most `In` values, this name is the
/// name of the parameter as supplied to the API at runtime, and in WoofWare's
/// strongly-typed domain types this information is also contained in the `In` field.
/// For `Body` parameters, this is purely for dev-time information.
Name : string
/// Whether this parameter is required for validation to succeed.
/// I think this defaults to "no".
Required : bool option
}
/// Render a JsonObject into this strongly-typed specification.
static member Parse (node : JsonObject) : SwaggerParameter =
let ty =
match asObjOpt node "schema" with
| None -> Definition.Parse node
| Some node -> Definition.Parse node
let description = asOpt<string> node "description"
let name = asString node "name"
let paramIn =
match asString node "in" with
| "path" -> ParameterIn.Path name
| "query" -> ParameterIn.Query name
| "body" -> ParameterIn.Body
| f -> ParameterIn.Unrecognised (f, name)
let required = asOpt<bool> node "required"
{
Type = ty
Description = description
In = paramIn
Name = name
Required = required
}
/// An "endpoint" is basically a single HTTP verb, applied to some path.
type SwaggerEndpoint =
{
/// The MIME types we should send our request body in.
/// This overrides (does not extend) any global definitions on the spec itself.
Consumes : MimeType list option
/// The MIME types we should expect to receive in response to this request.
/// This overrides (does not extend) any global definitions on the spec itself.
Produces : MimeType list option
/// Arbitrary list of [tags](https://swagger.io/docs/specification/2-0/grouping-operations-with-tags/).
Tags : string list
/// Human-readable description of the endpoint.
Summary : string
/// Arbitrary identifier of this endpoint; this must be unique across *all* endpoints
/// in this entire spec.
OperationId : OperationId
/// Parameters that must be supplied at HTTP-request-time to the endpoint.
/// (Each parameter knows how it needs to be supplied: e.g. if it's a query parameter or
/// if it's interpolated into the path.)
Parameters : SwaggerParameter list option
/// Map of HTTP response code to the type that we expect to receive in the body if we
/// get that response code back.
Responses : Map<int, Definition>
}
/// Render a JsonObject into this strongly-typed specification.
static member Parse (r : JsonObject) : SwaggerEndpoint =
let produces = asArrOpt'<string> r "produces" |> Option.map (List.map MimeType)
let consumes = asArrOpt'<string> r "consumes" |> Option.map (List.map MimeType)
let tags = asArr'<string> r "tags"
let summary = asString r "summary"
let operationId = asString r "operationId" |> OperationId
let responses =
asObj r "responses"
|> Seq.map (fun (KeyValue (key, value)) ->
let value = value.AsObject ()
Int32.Parse key, Definition.Parse value
)
|> Map.ofSeq
let parameters =
asArrOpt r "parameters"
|> Option.map (fun pars ->
pars
|> Seq.map (fun par -> par.AsObject () |> SwaggerParameter.Parse)
|> Seq.toList
)
{
Produces = produces
Consumes = consumes
Tags = tags
Summary = summary
OperationId = operationId
Parameters = parameters
Responses = responses
}
/// Specifies the form a response to an endpoint will take if it's complying with this spec.
type Response =
{
/// Human-readable description.
Description : string
/// Specification of the type to which responses will conform under this spec.
Schema : Definition
}
/// Render a JsonObject into this strongly-typed specification.
static member Parse (r : JsonObject) : Response =
let desc = asString r "description"
let schema =
match asObjOpt r "schema" with
| None -> Definition.Unspecified
| Some s -> Definition.Parse s
{
Description = desc
Schema = schema
}
/// An HTTP method. This is System.Net.Http.HttpMethod, but
/// a proper discriminated union.
type HttpMethod =
/// HTTP Get
| Get
/// HTTP Post
| Post
/// HTTP Delete
| Delete
/// HTTP Patch
| Patch
/// HTTP Options
| Options
/// HTTP Head
| Head
/// HTTP Put
| Put
/// HTTP Trace
| Trace
/// Convert to the standard library's enum type.
member this.ToDotNet () : System.Net.Http.HttpMethod =
match this with
| HttpMethod.Get -> System.Net.Http.HttpMethod.Get
| HttpMethod.Post -> System.Net.Http.HttpMethod.Post
| HttpMethod.Delete -> System.Net.Http.HttpMethod.Delete
| HttpMethod.Patch -> System.Net.Http.HttpMethod.Patch
| HttpMethod.Options -> System.Net.Http.HttpMethod.Options
| HttpMethod.Head -> System.Net.Http.HttpMethod.Head
| HttpMethod.Put -> System.Net.Http.HttpMethod.Put
| HttpMethod.Trace -> System.Net.Http.HttpMethod.Trace
/// Human-readable string representation.
override this.ToString () : string =
match this with
| HttpMethod.Get -> "Get"
| HttpMethod.Post -> "Post"
| HttpMethod.Delete -> "Delete"
| HttpMethod.Patch -> "Post"
| HttpMethod.Options -> "Options"
| HttpMethod.Head -> "Head"
| HttpMethod.Put -> "Put"
| HttpMethod.Trace -> "Trace"
/// Throws on invalid inputs.
static member Parse (s : string) : HttpMethod =
if String.Equals (s, "get", StringComparison.OrdinalIgnoreCase) then
HttpMethod.Get
elif String.Equals (s, "post", StringComparison.OrdinalIgnoreCase) then
HttpMethod.Post
elif String.Equals (s, "patch", StringComparison.OrdinalIgnoreCase) then
HttpMethod.Patch
elif String.Equals (s, "delete", StringComparison.OrdinalIgnoreCase) then
HttpMethod.Delete
elif String.Equals (s, "head", StringComparison.OrdinalIgnoreCase) then
HttpMethod.Head
elif String.Equals (s, "options", StringComparison.OrdinalIgnoreCase) then
HttpMethod.Options
elif String.Equals (s, "put", StringComparison.OrdinalIgnoreCase) then
HttpMethod.Put
else
failwith $"Unrecognised method: %s{s}"
/// A Swagger API specification.
type Swagger =
{
/// Global collection of MIME types which any endpoint expects to consume its inputs in.
/// This may be overridden on any individual endpoint by that endpoint.
Consumes : MimeType list
/// Global collection of MIME types which any endpoint will produce.
/// This may be overridden on any individual endpoint by that endpoint.
Produces : MimeType list
/// HTTP or HTTPS, for example. Indicates which scheme to access the API on.
Schemes : Scheme list
/// The version of OpenAPI this specification is written against.
/// (As of this writing, we only support 2.0.)
Swagger : Version
/// General information about this API.
Info : SwaggerInfo
/// Path under the URI host, which should be prefixed (with trailing slash if necessary)
/// to all requests.
BasePath : string
/// Map from relative path to "what is served at that path".
Paths : Map<string, Map<HttpMethod, SwaggerEndpoint>>
/// Types defined in the schema. Requests may use these definitions just like in any other JSON schema.
/// Key is a domain type name, e.g. "APIError".
Definitions : Map<string, Definition>
/// Types of each response.
/// Key is a domain type name, e.g. "AccessToken".
Responses : Map<string, Response>
}
[<RequireQualifiedAccess>]
module Swagger =
/// Parse a JSON-schema-based specification of a Swagger 2.0 API and
/// build the strongly-typed version. Throws on invalid inputs.
let parse (s : string) : Swagger =
let node = JsonNode.Parse s
let consumes = asArr'<string> node "consumes" |> List.map MimeType
let produces = asArr'<string> node "produces" |> List.map MimeType
let schemes = asArr'<string> node "schemes" |> List.map Scheme
let swagger = asString node "swagger" |> Version.Parse
let info = asObj node "info" |> SwaggerInfo.Parse
let basePath = asString node "basePath"
let definitions =
asObj node "definitions"
|> Seq.map (fun (KeyValue (key, value)) ->
let value = value.AsObject ()
key, Definition.Parse value
)
|> Map.ofSeq
let paths =
asObj node "paths"
|> Seq.map (fun (KeyValue (key, value)) ->
let contents =
value.AsObject ()
|> Seq.map (fun (KeyValue (endpoint, contents)) ->
let contents = contents.AsObject ()
HttpMethod.Parse endpoint, SwaggerEndpoint.Parse contents
)
|> Map.ofSeq
key, contents
)
|> Map.ofSeq
let responses =
asObj node "responses"
|> Seq.map (fun (KeyValue (key, value)) ->
let value = value.AsObject ()
key, Response.Parse value
)
|> Map.ofSeq
{
Consumes = consumes
Produces = produces
Schemes = schemes
Swagger = swagger
Info = info
BasePath = basePath
Paths = paths
Definitions = definitions
Responses = responses
}

View File

@@ -0,0 +1,725 @@
namespace WoofWare.Myriad.Plugins
open System.Collections.Generic
open System.IO
open System.Threading
open Fantomas.FCS.Syntax
open Fantomas.FCS.Xml
open Fantomas.FCS.Text.Range
open WoofWare.Whippet.Fantomas
type internal SwaggerClientConfig =
{
/// Additionally create a mock with `InterfaceMockGenerator`, with the given boolean arg.
/// (`None` means "no mock".)
CreateMock : bool option
ClassName : string
}
type internal Produces =
// TODO: this will cope with decoding JSON, plain text, etc
| Produces of string
type internal Endpoint =
{
DocString : PreXmlDoc
Produces : Produces
ReturnType : Definition
Method : WoofWare.Myriad.Plugins.HttpMethod
Operation : OperationId
Parameters : SwaggerParameter list
Endpoint : string
}
type internal TypeEntry =
{
/// If we had to define a type for this, here it is.
FSharpDefinition : SynTypeDefn option
/// SynType you use in e.g. a type annotation to refer to this type in F# code.
Signature : SynType
}
type internal Types =
{
ByHandle : IReadOnlyDictionary<string, TypeEntry>
ByDefinition : IReadOnlyDictionary<Definition, TypeEntry>
}
[<RequireQualifiedAccess>]
module internal SwaggerClientGenerator =
let outputFile = FileInfo "/tmp/output.txt"
// do
// use _ = File.Create outputFile.FullName
// ()
let log (line : string) =
// use w = outputFile.AppendText ()
// w.WriteLine line
()
let renderType (types : Types) (defn : Definition) : SynType option =
match types.ByDefinition.TryGetValue defn with
| true, v -> Some v.Signature
| false, _ ->
match defn with
| Definition.Handle h ->
match types.ByHandle.TryGetValue h with
| false, _ -> None
| true, v -> Some v.Signature
| Definition.Object _ -> failwith "should not hit"
| Definition.Array _ -> failwith "should not hit"
| Definition.Unspecified -> failwith "should not hit"
| Definition.String -> SynType.string |> Some
| Definition.Boolean -> SynType.bool |> Some
| Definition.Integer _ -> SynType.int |> Some
| Definition.File -> SynType.createLongIdent' [ "System" ; "IO" ; "Stream" ] |> Some
/// Returns None if we lacked the information required to do this.
/// bigCache is a map of e.g. {"securityDefinition": {Defn : F# type}}.
let rec defnToType
(anonymousTypeCount : int ref)
(handlesMap : Dictionary<string, TypeEntry>)
(bigCache : Dictionary<string, Dictionary<Definition, TypeEntry>>)
(thisKey : string)
(typeName : string option)
(d : Definition)
: TypeEntry option
=
let cache =
match bigCache.TryGetValue thisKey with
| false, _ ->
let d = Dictionary ()
bigCache.Add (thisKey, d)
d
| true, d -> d
let handleKey =
match typeName with
| None -> None
| Some typeName -> $"#/%s{thisKey}/%s{typeName}" |> Some
match handleKey with
| Some hk when handlesMap.ContainsKey hk ->
let result = handlesMap.[hk]
cache.[d] <- result
Some result
| _ ->
match cache.TryGetValue d with
| true, v ->
match handleKey with
| None -> ()
| Some key -> handlesMap.Add (key, v)
Some v
| false, _ ->
let result =
match d with
| Definition.Object obj ->
let requiredFields = obj.Required |> Option.defaultValue [] |> Set.ofList
let namedProperties =
obj.Properties
|> Option.map Seq.cast
|> Option.defaultValue Seq.empty
|> Seq.map (fun (KeyValue (fieldName, defn)) ->
// TODO this is a horrible hack and is incomplete, e.g. if we contain an array of ourself
// Special case for when this is a reference to this very type
let isOurself =
match defn with
| Definition.Handle h ->
match h.Split '/' with
| [| "#" ; location ; ty |] when location = thisKey && Some ty = typeName ->
SynType.named ty |> Some
| _ -> None
| _ -> None
let jsonPropertyName =
SynExpr.CreateConst (fieldName : string)
|> SynAttribute.create (
SynLongIdent.createS'
[ "System" ; "Text" ; "Json" ; "Serialization" ; "JsonPropertyName" ]
)
match isOurself with
| Some alreadyDone ->
let ty =
if Set.contains fieldName requiredFields then
alreadyDone
else
SynType.option alreadyDone
{
Attrs = [ jsonPropertyName ]
Type = ty
Ident = Some (Ident.createSanitisedTypeName fieldName)
}
|> SynField.make
|> Some
| None ->
let defn' = defnToType anonymousTypeCount handlesMap bigCache thisKey None defn
match defn' with
| None -> None
| Some defn' ->
let ty =
if Set.contains fieldName requiredFields then
defn'.Signature
else
defn'.Signature |> SynType.option
{
Attrs = [ jsonPropertyName ]
Ident = Ident.createSanitisedTypeName fieldName |> Some
Type = ty
}
|> SynField.make
|> Some
)
|> Seq.toList
let additionalProperties =
match obj.AdditionalProperties with
| None ->
{
Attrs =
[
SynAttribute.create
(SynLongIdent.createS'
[ "System" ; "Text" ; "Json" ; "Serialization" ; "JsonExtensionData" ])
(SynExpr.CreateConst ())
]
Ident = Ident.create "AdditionalProperties" |> Some
Type =
SynType.app'
(SynType.createLongIdent' [ "System" ; "Collections" ; "Generic" ; "Dictionary" ])
[
SynType.string
SynType.createLongIdent' [ "System" ; "Text" ; "Json" ; "Nodes" ; "JsonNode" ]
]
}
|> SynField.make
|> List.singleton
|> Some
| Some AdditionalProperties.Never -> Some []
| Some (AdditionalProperties.Constrained defn) ->
let defn' = defnToType anonymousTypeCount handlesMap bigCache thisKey None defn
match defn' with
| None -> None
| Some defn' ->
{
Attrs =
[
SynAttribute.create
(SynLongIdent.createS'
[ "System" ; "Text" ; "Json" ; "Serialization" ; "JsonExtensionData" ])
(SynExpr.CreateConst ())
]
Ident = Ident.create "AdditionalProperties" |> Some
Type =
SynType.app'
(SynType.createLongIdent'
[ "System" ; "Collections" ; "Generic" ; "Dictionary" ])
[ SynType.string ; defn'.Signature ]
}
|> SynField.make
|> List.singleton
|> Some
match additionalProperties with
| None -> None
| Some additionalProperties ->
match List.allSome namedProperties with
| None -> None
| Some namedProperties ->
let fSharpTypeName =
match typeName with
| None -> $"Type%i{Interlocked.Increment anonymousTypeCount}"
| Some typeName -> typeName
let properties = additionalProperties @ namedProperties
let properties =
if properties.IsEmpty then
// sigh, they didn't give us any properties at all; let's make one up
{
Attrs = []
Ident = Some (Ident.create "_SchemaUnspecified")
Type = SynType.obj
}
|> SynField.make
|> List.singleton
else
properties
let defn =
let sci =
SynComponentInfo.create (Ident.createSanitisedTypeName fSharpTypeName)
|> SynComponentInfo.addAttributes
[
SynAttribute.create (SynLongIdent.createS' [ "JsonParse" ]) (SynExpr.CreateConst true)
SynAttribute.create
(SynLongIdent.createS' [ "JsonSerialize" ])
(SynExpr.CreateConst true)
]
|> fun sci ->
match obj.Description with
| None -> sci
| Some doc -> sci |> SynComponentInfo.withDocString (PreXmlDoc.create doc)
properties |> SynTypeDefnRepr.record |> SynTypeDefn.create sci
let defn =
{
Signature = SynType.named fSharpTypeName
FSharpDefinition = Some defn
}
defn |> Some
| Definition.Array elt ->
let child = defnToType anonymousTypeCount handlesMap bigCache thisKey None elt.Items
match child with
| None -> None
| Some child ->
let defn =
{
Signature = SynType.list child.Signature
FSharpDefinition = None
}
Some defn
| Definition.String ->
{
Signature = SynType.string
FSharpDefinition = None
}
|> Some
| Definition.Boolean ->
{
Signature = SynType.bool
FSharpDefinition = None
}
|> Some
| Definition.Unspecified ->
{
Signature = SynType.unit
FSharpDefinition = None
}
|> Some
| Definition.Integer _ ->
{
Signature = SynType.createLongIdent' [ "int" ]
FSharpDefinition = None
}
|> Some
| Definition.File ->
{
Signature = SynType.createLongIdent' [ "System" ; "IO" ; "Stream" ]
FSharpDefinition = None
}
|> Some
| Definition.Handle s ->
let split = s.Split '/' |> List.ofArray
match split with
| [ "#" ; _location ; _handle ] ->
match handlesMap.TryGetValue s with
| false, _ -> None
| true, computed ->
let defn =
{
FSharpDefinition = None
Signature = computed.Signature
}
defn |> Some
| _ -> failwith $"we don't know how to deal with object handle %s{s}"
match result with
| None -> None
| Some result ->
match handleKey with
| None -> ()
| Some handleKey -> handlesMap.Add (handleKey, result)
cache.Add (d, result)
Some result
let instantiateRequiredTypes (types : Types) : SynModuleDecl =
types.ByDefinition
|> Seq.choose (fun (KeyValue (_defn, typeEntry)) -> typeEntry.FSharpDefinition)
|> Seq.toList
|> SynModuleDecl.createTypes
type private IsIn =
| Path of str : string
| Query of str : string
| Body
let computeType
(options : SwaggerClientConfig)
(basePath : string)
(types : Types)
(clientDocString : PreXmlDoc)
(endpoints : Endpoint list)
: SynModuleDecl list
=
endpoints
|> List.choose (fun ep ->
let name = (Ident.createSanitisedTypeName (ep.Operation.ToString ())).idText
match renderType types ep.ReturnType with
| None ->
log $"Skipping %O{ep.Operation}: Couldn't render return type: %O{ep.ReturnType}"
None
| Some returnType ->
let pars =
ep.Parameters
|> List.map (fun par ->
let inParam =
match par.In with
| ParameterIn.Unrecognised (f, name) ->
log
$"Skipping %O{ep.Operation} at %s{ep.Endpoint}: unrecognised In parameter %s{f} with name %s{name}"
None
| ParameterIn.Body -> Some IsIn.Body
| ParameterIn.Query name -> Some (IsIn.Query name)
| ParameterIn.Path name -> Some (IsIn.Path name)
match inParam with
| None -> None
| Some inParam ->
match renderType types par.Type with
| None ->
// Couldn't render the return type
// failwith "Did not have a type here"
log $"Skipping %O{ep.Operation}: Couldn't render parameter: %O{par.Type}"
None
| Some v -> Some (Ident.createSanitisedParamName par.Name, inParam, v)
)
|> List.allSome
match pars with
| None -> None
| Some pars ->
let arity =
SynValInfo.SynValInfo (
[
ep.Parameters
|> List.map (fun par ->
let name = par.Name |> Ident.create |> Some
SynArgInfo.SynArgInfo ([], false, name)
)
|> fun l -> l @ [ SynArgInfo.SynArgInfo ([], true, Some (Ident.create "ct")) ]
],
SynArgInfo.SynArgInfo ([], false, None)
)
let domain =
let ctParam =
SynType.signatureParamOfType
[]
(SynType.createLongIdent' [ "System" ; "Threading" ; "CancellationToken" ])
true
(Some (Ident.create "ct"))
let argParams =
pars
|> List.map (fun (ident, isIn, t) ->
let attr : SynAttribute list =
match isIn with
| IsIn.Path name ->
SynAttribute.create
(SynLongIdent.createS' [ "RestEase" ; "Path" ])
(SynExpr.CreateConst name)
|> List.singleton
| IsIn.Query name ->
SynAttribute.create
(SynLongIdent.createS' [ "RestEase" ; "Query" ])
(SynExpr.CreateConst name)
|> List.singleton
| IsIn.Body ->
SynAttribute.create
(SynLongIdent.createS' [ "RestEase" ; "Body" ])
(SynExpr.CreateConst ())
|> List.singleton
SynType.signatureParamOfType attr t false (Some ident)
)
SynType.tupleNoParen (argParams @ [ ctParam ]) |> Option.get
let attrs =
[
SynAttribute.create
(SynLongIdent.createS' [ "RestEase" ; ep.Method.ToString () ])
// Gitea, at least, starts with a `/`, which `Uri` then takes to indicate an absolute path.
(SynExpr.CreateConst (ep.Endpoint.TrimStart '/'))
match ep.Produces with
| Produces.Produces contentType ->
SynAttribute.create
(SynLongIdent.createS' [ "RestEase" ; "Header" ])
// Gitea, at least, starts with a `/`, which `Uri` then takes to indicate an absolute path.
(SynExpr.tuple [ SynExpr.CreateConst "Content-Type" ; SynExpr.CreateConst contentType ])
]
returnType
|> SynType.task
|> SynType.toFun [ domain ]
|> SynMemberDefn.abstractMember attrs (SynIdent.createS name) None arity ep.DocString
|> Some
)
|> SynTypeDefnRepr.interfaceType
|> SynTypeDefn.create (
let attrs =
[
yield SynAttribute.create (SynLongIdent.createS' [ "HttpClient" ]) (SynExpr.CreateConst false)
yield
SynAttribute.create
(SynLongIdent.createS' [ "RestEase" ; "BasePath" ])
(SynExpr.CreateConst basePath)
match options.CreateMock with
| None -> ()
| Some createMockValue ->
yield
SynAttribute.create
(SynLongIdent.createS' [ "GenerateMock" ])
(SynExpr.CreateConst createMockValue)
]
SynComponentInfo.create (Ident.create ("I" + options.ClassName))
|> SynComponentInfo.withDocString clientDocString
|> SynComponentInfo.addAttributes attrs
)
|> List.singleton
|> SynModuleDecl.createTypes
|> List.singleton
open Myriad.Core
/// Myriad generator that stamps out an interface and class to access a Swagger-specified API.
[<MyriadGenerator("swagger-client")>]
type SwaggerClientGenerator () =
interface IMyriadGenerator with
member _.ValidInputExtensions = [ ".json" ]
member _.Generate (context : GeneratorContext) =
let contents = File.ReadAllText context.InputFilename |> Swagger.parse
let scheme =
let preferred = Scheme "https"
if List.isEmpty contents.Schemes then
failwith "no schemes specified in API spec!"
if List.contains preferred contents.Schemes then
preferred
else
List.head contents.Schemes
let clientDocstring = contents.Info.Description |> PreXmlDoc.create
let basePath = contents.BasePath
let typeDefs =
let bigCache = Dictionary<_, Dictionary<_, _>> ()
let countAll () =
(0, bigCache) ||> Seq.fold (fun count (KeyValue (_, v)) -> count + v.Count)
let byHandle = Dictionary ()
let anonymousTypeCount = ref 0
let rec go (contents : ((string * Definition) * string) list) =
let lastRound = countAll ()
contents
|> List.filter (fun ((name, defn), defnClass) ->
let doIt =
SwaggerClientGenerator.defnToType
anonymousTypeCount
byHandle
bigCache
defnClass
(Some name)
defn
match doIt with
| None -> true
| Some _ -> false
)
|> fun remaining ->
if not remaining.IsEmpty then
let currentCount = countAll ()
if currentCount = lastRound then
for (name, remaining), kind in remaining do
SwaggerClientGenerator.log $"Remaining: %s{name} (%s{kind})"
SwaggerClientGenerator.log "--------"
for KeyValue (handle, defn) in byHandle do
SwaggerClientGenerator.log $"Known: %s{handle} %O{defn}"
// TODO: ohh noooooo the Gitea spec is genuinely circular,
// it's impossible to construct a Repository type
// we're going to have to somehow detect this case and break the cycle
// by artificially making a property optional
// :sob: Gitea why are you like this
// failwith "Made no further progress rendering types"
()
else
go remaining
seq {
for defnClass in [ "definitions" ; "responses" ] do
match defnClass with
| "definitions" ->
for KeyValue (k, v) in contents.Definitions do
yield (k, v), defnClass
| "responses" ->
for KeyValue (k, v) in contents.Responses do
yield (k, v.Schema), defnClass
| _ -> failwith "oh no"
}
|> Seq.toList
|> go
let result = Dictionary ()
for KeyValue (_container, types) in bigCache do
for KeyValue (defn, rendered) in types do
result.TryAdd (defn, rendered) |> ignore<bool>
{
ByHandle = byHandle
ByDefinition = result :> IReadOnlyDictionary<_, _>
}
let summary =
contents.Paths
|> Seq.collect (fun (KeyValue (path, endpoints)) ->
endpoints
|> Seq.choose (fun (KeyValue (method, endpoint)) ->
let docstring = endpoint.Summary |> PreXmlDoc.create
let produces =
match endpoint.Produces with
| None -> Produces "json"
| Some [] -> failwith $"API specified empty Produces: %s{path} (%O{method})"
| Some [ MimeType "application/json" ] -> Produces "json"
| Some [ MimeType (StartsWith "text/" t) ] -> Produces t
| Some [ MimeType s ] ->
failwithf
$"we don't support non-JSON Produces right now, got: %s{s} (%s{path} %O{method})"
| Some (_ :: _) ->
failwith $"we don't support multiple Produces right now, at %s{path} (%O{method})"
let returnType =
endpoint.Responses
|> Seq.choose (fun (KeyValue (response, defn)) ->
if 200 <= response && response < 300 then
Some defn
else
None
)
|> Seq.toList
let returnType =
match returnType with
| [ t ] -> Some t
| [] -> failwith $"got no successful response results, %s{path} %O{method}"
| _ ->
SwaggerClientGenerator.log
$"Ignoring %s{path} %O{method} due to multiple success responses"
// can't be bothered to work out how to deal with multiple success
// results right now
None
match returnType with
| None -> None
| Some returnType ->
{
Method = method
Produces = produces
DocString = docstring
ReturnType = returnType
Operation = endpoint.OperationId
Parameters = endpoint.Parameters |> Option.defaultValue []
Endpoint = path
}
|> Some
)
|> Seq.toList
)
|> Seq.toList
let config =
let pars = MyriadParamParser.render context.AdditionalParameters
let pars =
pars
|> Map.toSeq
|> Seq.map (fun (k, v) -> k.ToUpperInvariant (), v)
|> Map.ofSeq
if pars.IsEmpty then
failwith "No parameters given. You must supply the <ClassName /> parameter in <MyriadParams />."
let createMock =
match Map.tryFind "GENERATEMOCKVISIBILITY" pars with
| None -> None
| Some v ->
match v.ToLowerInvariant () with
| "internal" -> Some true
| "public" -> Some false
| _ ->
failwith
$"Expected GenerateMockVisibility parameter to be 'internal' or 'public', but was: '%s{v.ToLowerInvariant ()}'"
let className =
match Map.tryFind "CLASSNAME" pars with
| None -> failwith "You must supply the <ClassName /> parameter in <MyriadParams />."
| Some v -> v
{
CreateMock = createMock
ClassName = className
}
let ty =
SwaggerClientGenerator.computeType config basePath typeDefs clientDocstring summary
[
yield
SynModuleDecl.Open (
SynOpenDeclTarget.ModuleOrNamespace (
SynLongIdent.createS' [ "WoofWare" ; "Myriad" ; "Plugins" ],
range0
),
range0
)
yield SwaggerClientGenerator.instantiateRequiredTypes typeDefs
yield! ty
]
|> SynModuleOrNamespace.createNamespace [ Ident.create config.ClassName ]
|> List.singleton
|> Output.Ast

View File

@@ -1,49 +0,0 @@
namespace WoofWare.Myriad.Plugins
open Fantomas.FCS.Syntax
type internal CompExprBinding =
| LetBang of varName : string * rhs : SynExpr
| Let of varName : string * rhs : SynExpr
| Use of varName : string * rhs : SynExpr
| Do of body : SynExpr
(*
Potential API!
type internal CompExprBindings =
private
{
/// These are stored in reverse.
Bindings : CompExprBinding list
CompExprName : string
}
[<RequireQualifiedAccess>]
module internal CompExprBindings =
let make (name : string) : CompExprBindings =
{
Bindings = []
CompExprName = name
}
let thenDo (body : SynExpr) (bindings : CompExprBindings) =
{ bindings with
Bindings = (Do body :: bindings.Bindings)
}
let thenLet (varName : string) (value : SynExpr) (bindings : CompExprBindings) =
{ bindings with
Bindings = (Let (varName, value) :: bindings.Bindings)
}
let thenLetBang (varName : string) (value : SynExpr) (bindings : CompExprBindings) =
{ bindings with
Bindings = (LetBang (varName, value) :: bindings.Bindings)
}
let thenUse (varName : string) (value : SynExpr) (bindings : CompExprBindings) =
{ bindings with
Bindings = (LetBang (varName, value) :: bindings.Bindings)
}
*)

View File

@@ -1,16 +0,0 @@
namespace WoofWare.Myriad.Plugins
open System
open System.Text
open Fantomas.FCS.Syntax
open Fantomas.FCS.Text.Range
[<RequireQualifiedAccess>]
module internal Ident =
let inline create (s : string) = Ident (s, range0)
let lowerFirstLetter (x : Ident) : Ident =
let result = StringBuilder x.idText.Length
result.Append (Char.ToLowerInvariant x.idText.[0]) |> ignore
result.Append x.idText.[1..] |> ignore
create ((result : StringBuilder).ToString ())

View File

@@ -1,9 +0,0 @@
namespace WoofWare.Myriad.Plugins
open Fantomas.FCS.Xml
open Fantomas.FCS.Text.Range
[<RequireQualifiedAccess>]
module internal PreXmlDoc =
let create (s : string) : PreXmlDoc =
PreXmlDoc.Create ([| " " + s |], range0)

View File

@@ -1,16 +0,0 @@
namespace WoofWare.Myriad.Plugins
open Fantomas.FCS.Syntax
[<RequireQualifiedAccess>]
module internal SynArgPats =
let create (caseNames : Ident list) : SynArgPats =
match caseNames.Length with
| 0 -> SynArgPats.Pats []
| 1 -> [ SynPat.named caseNames.[0].idText ] |> SynArgPats.Pats
| _ ->
caseNames
|> List.map (fun i -> SynPat.named i.idText)
|> SynPat.tuple
|> List.singleton
|> SynArgPats.Pats

View File

@@ -1,36 +0,0 @@
namespace WoofWare.Myriad.Plugins
open Fantomas.FCS.Syntax
open Fantomas.FCS.Text.Range
[<RequireQualifiedAccess>]
module internal SynAttribute =
let internal compilationRepresentation : SynAttribute =
{
TypeName = SynLongIdent.createS "CompilationRepresentation"
ArgExpr =
[ "CompilationRepresentationFlags" ; "ModuleSuffix" ]
|> SynExpr.createLongIdent
|> SynExpr.paren
Target = None
AppliesToGetterAndSetter = false
Range = range0
}
let internal requireQualifiedAccess : SynAttribute =
{
TypeName = SynLongIdent.createS "RequireQualifiedAccess"
ArgExpr = SynExpr.CreateConst ()
Target = None
AppliesToGetterAndSetter = false
Range = range0
}
let internal autoOpen : SynAttribute =
{
TypeName = SynLongIdent.createS "AutoOpen"
ArgExpr = SynExpr.CreateConst ()
Target = None
AppliesToGetterAndSetter = false
Range = range0
}

View File

@@ -1,204 +0,0 @@
namespace WoofWare.Myriad.Plugins
open Fantomas.FCS.Syntax
open Fantomas.FCS.SyntaxTrivia
open Fantomas.FCS.Xml
open Fantomas.FCS.Text.Range
[<RequireQualifiedAccess>]
module internal SynBinding =
let rec private stripParen (pat : SynPat) =
match pat with
| SynPat.Paren (p, _) -> stripParen p
| _ -> pat
let rec private getName (pat : SynPat) : Ident option =
match stripParen pat with
| SynPat.Named (SynIdent.SynIdent (name, _), _, _, _) -> Some name
| SynPat.Typed (pat, _, _) -> getName pat
| SynPat.LongIdent (SynLongIdent.SynLongIdent (longIdent, _, _), _, _, _, _, _) ->
match longIdent with
| [ x ] -> Some x
| _ -> failwithf "got long ident %O ; can only get the name of a long ident with one component" longIdent
| _ -> None
let private getArgInfo (pat : SynPat) : SynArgInfo list =
// TODO: this only copes with one layer of tupling
match stripParen pat with
| SynPat.Tuple (_, pats, _, _) -> pats |> List.map (fun pat -> SynArgInfo.SynArgInfo ([], false, getName pat))
| pat -> [ SynArgInfo.SynArgInfo (SynAttributes.Empty, false, getName pat) ]
let triviaZero (isMember : bool) =
{
SynBindingTrivia.EqualsRange = Some range0
InlineKeyword = None
LeadingKeyword =
if isMember then
SynLeadingKeyword.Member range0
else
SynLeadingKeyword.Let range0
}
let basic (name : LongIdent) (args : SynPat list) (body : SynExpr) : SynBinding =
let valInfo : SynValInfo =
args
|> List.map getArgInfo
|> fun x -> SynValInfo.SynValInfo (x, SynArgInfo.SynArgInfo ([], false, None))
SynBinding.SynBinding (
None,
SynBindingKind.Normal,
false,
false,
[],
PreXmlDoc.Empty,
SynValData.SynValData (None, valInfo, None),
SynPat.identWithArgs name (SynArgPats.Pats args),
None,
body,
range0,
DebugPointAtBinding.Yes range0,
triviaZero false
)
let withAccessibility (acc : SynAccess option) (binding : SynBinding) : SynBinding =
match binding with
| SynBinding (_, kind, inl, mut, attrs, xml, valData, headPat, returnInfo, expr, range, debugPoint, trivia) ->
let headPat =
match headPat with
| SynPat.LongIdent (ident, extra, options, argPats, _, range) ->
SynPat.LongIdent (ident, extra, options, argPats, acc, range)
| _ -> failwithf "unrecognised head pattern: %O" headPat
SynBinding (acc, kind, inl, mut, attrs, xml, valData, headPat, returnInfo, expr, range, debugPoint, trivia)
let withXmlDoc (doc : PreXmlDoc) (binding : SynBinding) : SynBinding =
match binding with
| SynBinding (acc, kind, inl, mut, attrs, _, valData, headPat, returnInfo, expr, range, debugPoint, trivia) ->
SynBinding (acc, kind, inl, mut, attrs, doc, valData, headPat, returnInfo, expr, range, debugPoint, trivia)
let withReturnAnnotation (ty : SynType) (binding : SynBinding) : SynBinding =
match binding with
| SynBinding (acc, kind, inl, mut, attrs, doc, valData, headPat, _, expr, range, debugPoint, trivia) ->
let retInfo =
SynBindingReturnInfo.SynBindingReturnInfo (
ty,
range0,
[],
{
ColonRange = Some range0
}
)
SynBinding (
acc,
kind,
inl,
mut,
attrs,
doc,
valData,
headPat,
Some retInfo,
expr,
range,
debugPoint,
trivia
)
let inline makeInline (binding : SynBinding) : SynBinding =
match binding with
| SynBinding (acc, kind, _, mut, attrs, doc, valData, headPat, ret, expr, range, debugPoint, trivia) ->
SynBinding (
acc,
kind,
true,
mut,
attrs,
doc,
valData,
headPat,
ret,
expr,
range,
debugPoint,
{ trivia with
InlineKeyword = Some range0
}
)
let inline makeNotInline (binding : SynBinding) : SynBinding =
match binding with
| SynBinding (acc, kind, _, mut, attrs, doc, valData, headPat, ret, expr, range, debugPoint, trivia) ->
SynBinding (
acc,
kind,
false,
mut,
attrs,
doc,
valData,
headPat,
ret,
expr,
range,
debugPoint,
{ trivia with
InlineKeyword = None
}
)
let inline setInline (isInline : bool) (binding : SynBinding) : SynBinding =
if isInline then
makeInline binding
else
makeNotInline binding
let makeStaticMember (binding : SynBinding) : SynBinding =
let memberFlags =
{
SynMemberFlags.IsInstance = false
SynMemberFlags.IsDispatchSlot = false
SynMemberFlags.IsOverrideOrExplicitImpl = false
SynMemberFlags.IsFinal = false
SynMemberFlags.GetterOrSetterIsCompilerGenerated = false
SynMemberFlags.MemberKind = SynMemberKind.Member
}
match binding with
| SynBinding (acc, kind, inl, mut, attrs, doc, valData, headPat, ret, expr, range, debugPoint, trivia) ->
let valData =
match valData with
| SynValData.SynValData (_, valInfo, _) -> SynValData.SynValData (Some memberFlags, valInfo, None)
let trivia =
{ trivia with
LeadingKeyword = SynLeadingKeyword.StaticMember (range0, range0)
}
SynBinding (acc, kind, inl, mut, attrs, doc, valData, headPat, ret, expr, range, debugPoint, trivia)
let makeInstanceMember (binding : SynBinding) : SynBinding =
let memberFlags =
{
SynMemberFlags.IsInstance = true
SynMemberFlags.IsDispatchSlot = false
SynMemberFlags.IsOverrideOrExplicitImpl = true
SynMemberFlags.IsFinal = false
SynMemberFlags.GetterOrSetterIsCompilerGenerated = false
SynMemberFlags.MemberKind = SynMemberKind.Member
}
match binding with
| SynBinding (acc, kind, inl, mut, attrs, doc, valData, headPat, ret, expr, range, debugPoint, trivia) ->
let valData =
match valData with
| SynValData.SynValData (_, valInfo, _) -> SynValData.SynValData (Some memberFlags, valInfo, None)
let trivia =
{ trivia with
LeadingKeyword = SynLeadingKeyword.Member range0
}
SynBinding (acc, kind, inl, mut, attrs, doc, valData, headPat, ret, expr, range, debugPoint, trivia)

View File

@@ -1,50 +0,0 @@
namespace WoofWare.Myriad.Plugins
open Fantomas.FCS.Syntax
open Fantomas.FCS.Xml
open Fantomas.FCS.Text.Range
[<RequireQualifiedAccess>]
module internal SynComponentInfo =
let inline createLong (name : LongIdent) =
SynComponentInfo.SynComponentInfo ([], None, [], name, PreXmlDoc.Empty, false, None, range0)
let inline create (name : Ident) = createLong [ name ]
let inline withDocString (doc : PreXmlDoc) (i : SynComponentInfo) : SynComponentInfo =
match i with
| SynComponentInfo.SynComponentInfo (attrs, typars, constraints, name, _, postfix, access, range) ->
SynComponentInfo (attrs, typars, constraints, name, doc, postfix, access, range)
let inline setGenerics (typars : SynTyparDecls option) (i : SynComponentInfo) : SynComponentInfo =
match i with
| SynComponentInfo.SynComponentInfo (attrs, _, constraints, name, doc, postfix, access, range) ->
SynComponentInfo (attrs, typars, constraints, name, doc, postfix, access, range)
let inline withGenerics (typars : SynTyparDecl list) (i : SynComponentInfo) : SynComponentInfo =
let inner =
if typars.IsEmpty then
None
else
Some (SynTyparDecls.PostfixList (typars, [], range0))
setGenerics inner i
let inline setAccessibility (acc : SynAccess option) (i : SynComponentInfo) : SynComponentInfo =
match i with
| SynComponentInfo.SynComponentInfo (attrs, typars, constraints, name, doc, postfix, _, range) ->
SynComponentInfo.SynComponentInfo (attrs, typars, constraints, name, doc, postfix, acc, range)
let inline withAccessibility (acc : SynAccess) (i : SynComponentInfo) : SynComponentInfo =
setAccessibility (Some acc) i
let inline addAttributes (attrs : SynAttribute list) (i : SynComponentInfo) : SynComponentInfo =
match i with
| SynComponentInfo.SynComponentInfo (oldAttrs, typars, constraints, name, doc, postfix, acc, range) ->
let attrs =
{
SynAttributeList.Attributes = attrs
SynAttributeList.Range = range0
}
SynComponentInfo.SynComponentInfo ((attrs :: oldAttrs), typars, constraints, name, doc, postfix, acc, range)

View File

@@ -1,300 +0,0 @@
namespace WoofWare.Myriad.Plugins
open Fantomas.FCS.Syntax
open Fantomas.FCS.SyntaxTrivia
open Myriad.Core
open Fantomas.FCS.Text.Range
[<AutoOpen>]
module internal SynExprExtensions =
type SynExpr with
static member CreateConst (s : string) : SynExpr =
SynExpr.Const (SynConst.String (s, SynStringKind.Regular, range0), range0)
static member CreateConst () : SynExpr = SynExpr.Const (SynConst.Unit, range0)
static member CreateConst (i : int32) : SynExpr =
SynExpr.Const (SynConst.Int32 i, range0)
[<RequireQualifiedAccess>]
module internal SynExpr =
/// {f} {x}
let applyFunction (f : SynExpr) (x : SynExpr) : SynExpr = SynExpr.CreateApp (f, x)
/// {f} {x}
let inline applyTo (x : SynExpr) (f : SynExpr) : SynExpr = applyFunction f x
/// {expr} |> {func}
let pipeThroughFunction (func : SynExpr) (expr : SynExpr) : SynExpr =
SynExpr.CreateAppInfix (SynExpr.CreateLongIdent SynLongIdent.pipe, expr)
|> applyTo func
/// if {cond} then {trueBranch} else {falseBranch}
/// Note that this function puts the trueBranch last, for pipelining convenience:
/// we assume that the `else` branch is more like an error case and is less interesting.
let ifThenElse (cond : SynExpr) (falseBranch : SynExpr) (trueBranch : SynExpr) : SynExpr =
SynExpr.IfThenElse (
cond,
trueBranch,
Some falseBranch,
DebugPointAtBinding.Yes range0,
false,
range0,
{
IfKeyword = range0
IsElif = false
ThenKeyword = range0
ElseKeyword = Some range0
IfToThenRange = range0
}
)
/// try {body} with | {exc} as exc -> {handler}
let pipeThroughTryWith (exc : SynPat) (handler : SynExpr) (body : SynExpr) : SynExpr =
let clause =
SynMatchClause.create (SynPat.As (exc, SynPat.named "exc", range0)) handler
SynExpr.TryWith (
body,
[ clause ],
range0,
DebugPointAtTry.Yes range0,
DebugPointAtWith.Yes range0,
{
TryKeyword = range0
TryToWithRange = range0
WithKeyword = range0
WithToEndRange = range0
}
)
/// {a} = {b}
let equals (a : SynExpr) (b : SynExpr) =
SynExpr.CreateAppInfix (SynExpr.CreateLongIdent SynLongIdent.eq, a) |> applyTo b
/// {a} + {b}
let plus (a : SynExpr) (b : SynExpr) =
SynExpr.CreateAppInfix (
SynExpr.CreateLongIdent (
SynLongIdent.SynLongIdent (
Ident.CreateLong "op_Addition",
[],
[ Some (IdentTrivia.OriginalNotation "+") ]
)
),
a
)
|> applyTo b
let rec stripOptionalParen (expr : SynExpr) : SynExpr =
match expr with
| SynExpr.Paren (expr, _, _, _) -> stripOptionalParen expr
| expr -> expr
/// {obj}.{meth} {arg}
let callMethodArg (meth : string) (arg : SynExpr) (obj : SynExpr) : SynExpr =
SynExpr.DotGet (
obj,
range0,
SynLongIdent.SynLongIdent (id = [ Ident.create meth ], dotRanges = [], trivia = [ None ]),
range0
)
|> applyTo arg
/// {obj}.{meth}()
let callMethod (meth : string) (obj : SynExpr) : SynExpr =
callMethodArg meth (SynExpr.CreateConst ()) obj
let callGenericMethod (meth : string) (ty : LongIdent) (obj : SynExpr) : SynExpr =
SynExpr.TypeApp (
SynExpr.DotGet (obj, range0, SynLongIdent.createS meth, range0),
range0,
[ SynType.LongIdent (SynLongIdent.create ty) ],
[],
Some range0,
range0,
range0
)
|> applyTo (SynExpr.CreateConst ())
/// {obj}.{meth}<ty>()
let callGenericMethod' (meth : string) (ty : string) (obj : SynExpr) : SynExpr =
SynExpr.TypeApp (
SynExpr.DotGet (obj, range0, SynLongIdent.createS meth, range0),
range0,
[ SynType.createLongIdent' [ ty ] ],
[],
Some range0,
range0,
range0
)
|> applyTo (SynExpr.CreateConst ())
let inline index (property : SynExpr) (obj : SynExpr) : SynExpr =
SynExpr.DotIndexedGet (obj, property, range0, range0)
let inline paren (e : SynExpr) : SynExpr =
SynExpr.Paren (e, range0, Some range0, range0)
/// (fun {varName} -> {body})
let createLambda (varName : string) (body : SynExpr) : SynExpr =
let parsedDataPat = [ SynPat.named varName ]
SynExpr.Lambda (
false,
false,
SynSimplePats.Create [ SynSimplePat.CreateId (Ident.Create varName) ],
body,
Some (parsedDataPat, body),
range0,
{
ArrowRange = Some range0
}
)
|> paren
let createThunk (body : SynExpr) : SynExpr =
SynExpr.Lambda (
false,
false,
SynSimplePats.Create [],
body,
Some ([ SynPat.unit ], body),
range0,
{
ArrowRange = Some range0
}
)
|> paren
let inline createIdent (s : string) : SynExpr = SynExpr.Ident (Ident (s, range0))
let inline createIdent' (i : Ident) : SynExpr = SynExpr.Ident i
let inline createLongIdent' (ident : Ident list) : SynExpr =
SynExpr.LongIdent (false, SynLongIdent.create ident, None, range0)
let inline createLongIdent (ident : string list) : SynExpr =
createLongIdent' (ident |> List.map Ident.create)
let tupleNoParen (args : SynExpr list) : SynExpr =
SynExpr.Tuple (false, args, List.replicate (args.Length - 1) range0, range0)
let inline tuple (args : SynExpr list) = args |> tupleNoParen |> paren
/// {body} |> fun a -> Async.StartAsTask (a, ?cancellationToken=ct)
let startAsTask (ct : Ident) (body : SynExpr) =
let lambda =
[
createIdent "a"
equals
(SynExpr.LongIdent (true, SynLongIdent.createS "cancellationToken", None, range0))
(createIdent' ct)
]
|> tuple
|> applyFunction (createLongIdent [ "Async" ; "StartAsTask" ])
|> createLambda "a"
pipeThroughFunction lambda body
let inline createLet (bindings : SynBinding list) (body : SynExpr) : SynExpr =
SynExpr.LetOrUse (false, false, bindings, body, range0, SynExprLetOrUseTrivia.empty)
let inline createMatch (matchOn : SynExpr) (cases : SynMatchClause list) : SynExpr =
SynExpr.Match (
DebugPointAtBinding.Yes range0,
matchOn,
cases,
range0,
{
MatchKeyword = range0
WithKeyword = range0
}
)
let typeAnnotate (ty : SynType) (expr : SynExpr) : SynExpr = SynExpr.Typed (expr, ty, range0)
let inline createNew (ty : SynType) (args : SynExpr) : SynExpr =
SynExpr.New (false, ty, paren args, range0)
let inline createWhile (cond : SynExpr) (body : SynExpr) : SynExpr =
SynExpr.While (DebugPointAtWhile.Yes range0, cond, body, range0)
let inline createNull () : SynExpr = SynExpr.Null range0
let reraise : SynExpr = createIdent "reraise" |> applyTo (SynExpr.CreateConst ())
let sequential (exprs : SynExpr list) : SynExpr =
exprs
|> List.reduce (fun a b -> SynExpr.Sequential (DebugPointAtSequential.SuppressNeither, false, a, b, range0))
/// {compExpr} { {lets} ; return {ret} }
let createCompExpr (compExpr : string) (retBody : SynExpr) (lets : CompExprBinding list) : SynExpr =
let retStatement = SynExpr.YieldOrReturn ((false, true), retBody, range0)
let contents : SynExpr =
(retStatement, List.rev lets)
||> List.fold (fun state binding ->
match binding with
| LetBang (lhs, rhs) ->
SynExpr.LetOrUseBang (
DebugPointAtBinding.Yes range0,
false,
true,
SynPat.named lhs,
rhs,
[],
state,
range0,
{
EqualsRange = Some range0
}
)
| Let (lhs, rhs) -> createLet [ SynBinding.basic [ Ident.create lhs ] [] rhs ] state
| Use (lhs, rhs) ->
SynExpr.LetOrUse (
false,
true,
[ SynBinding.basic [ Ident.create lhs ] [] rhs ],
state,
range0,
{
SynExprLetOrUseTrivia.InKeyword = None
}
)
| Do body -> sequential [ SynExpr.Do (body, range0) ; state ]
)
applyFunction (createIdent compExpr) (SynExpr.ComputationExpr (false, contents, range0))
/// {expr} |> Async.AwaitTask
let awaitTask (expr : SynExpr) : SynExpr =
expr |> pipeThroughFunction (createLongIdent [ "Async" ; "AwaitTask" ])
/// {ident}.ToString ()
/// with special casing for some types like DateTime
let toString (ty : SynType) (ident : SynExpr) =
match ty with
| DateOnly -> ident |> callMethodArg "ToString" (SynExpr.CreateConst "yyyy-MM-dd")
| DateTime -> ident |> callMethodArg "ToString" (SynExpr.CreateConst "yyyy-MM-ddTHH:mm:ss")
| _ -> callMethod "ToString" ident
let upcast' (ty : SynType) (e : SynExpr) = SynExpr.Upcast (e, ty, range0)
/// {ident} - {rhs}
let minus (ident : SynLongIdent) (rhs : SynExpr) : SynExpr =
SynExpr.CreateAppInfix (SynExpr.CreateLongIdent SynLongIdent.sub, SynExpr.CreateLongIdent ident)
|> applyTo rhs
/// {ident} - {n}
let minusN (ident : SynLongIdent) (n : int) : SynExpr = minus ident (SynExpr.CreateConst n)
/// {y} > {x}
let greaterThan (x : SynExpr) (y : SynExpr) : SynExpr =
SynExpr.CreateAppInfix (SynExpr.CreateLongIdent SynLongIdent.ge, y) |> applyTo x
/// {y} >= {x}
let greaterThanOrEqual (x : SynExpr) (y : SynExpr) : SynExpr =
SynExpr.CreateAppInfix (SynExpr.CreateLongIdent SynLongIdent.geq, y)
|> applyTo x

View File

@@ -1,10 +0,0 @@
namespace WoofWare.Myriad.Plugins
open Fantomas.FCS.SyntaxTrivia
[<RequireQualifiedAccess>]
module internal SynExprLetOrUseTrivia =
let empty : SynExprLetOrUseTrivia =
{
InKeyword = None
}

View File

@@ -1,69 +0,0 @@
namespace WoofWare.Myriad.Plugins
open Fantomas.FCS.Text.Range
open Fantomas.FCS.Syntax
open Fantomas.FCS.SyntaxTrivia
open Fantomas.FCS.Xml
type internal SynFieldData<'Ident> =
{
Attrs : SynAttribute list
Ident : 'Ident
Type : SynType
}
[<RequireQualifiedAccess>]
module internal SynField =
/// Get the useful information out of a SynField.
let extract (SynField (attrs, _, id, fieldType, _, _, _, _, _)) : SynFieldData<Ident option> =
{
Attrs = attrs |> List.collect (fun l -> l.Attributes)
Ident = id
Type = fieldType
}
let mapIdent<'a, 'b> (f : 'a -> 'b) (x : SynFieldData<'a>) : SynFieldData<'b> =
let ident = f x.Ident
{
Attrs = x.Attrs
Ident = ident
Type = x.Type
}
/// Throws if the field has no identifier.
let extractWithIdent (f : SynField) : SynFieldData<Ident> =
f
|> extract
|> mapIdent (fun ident ->
match ident with
| None -> failwith "expected field identifier to have a value, but it did not"
| Some i -> i
)
let make (data : SynFieldData<Ident option>) : SynField =
let attrs : SynAttributeList list =
data.Attrs
|> List.map (fun l ->
{
Attributes = [ l ]
Range = range0
}
)
SynField.SynField (
attrs,
false,
data.Ident,
data.Type,
false,
PreXmlDoc.Empty,
None,
range0,
SynFieldTrivia.Zero
)
let withDocString (doc : PreXmlDoc) (f : SynField) : SynField =
match f with
| SynField (attributes, isStatic, idOpt, fieldType, isMutable, _, accessibility, range, trivia) ->
SynField (attributes, isStatic, idOpt, fieldType, isMutable, doc, accessibility, range, trivia)

View File

@@ -1,106 +0,0 @@
namespace WoofWare.Myriad.Plugins
open Fantomas.FCS.SyntaxTrivia
open Fantomas.FCS.Text.Range
open Fantomas.FCS.Syntax
[<RequireQualifiedAccess>]
module internal SynLongIdent =
let geq =
SynLongIdent.SynLongIdent (
[ Ident.create "op_GreaterThanOrEqual" ],
[],
[ Some (IdentTrivia.OriginalNotation ">=") ]
)
let ge =
SynLongIdent.SynLongIdent ([ Ident.create "op_GreaterThan" ], [], [ Some (IdentTrivia.OriginalNotation ">") ])
let sub =
SynLongIdent.SynLongIdent ([ Ident.create "op_Subtraction" ], [], [ Some (IdentTrivia.OriginalNotation "-") ])
let eq =
SynLongIdent.SynLongIdent ([ Ident.create "op_Equality" ], [], [ Some (IdentTrivia.OriginalNotation "=") ])
let pipe =
SynLongIdent.SynLongIdent ([ Ident.create "op_PipeRight" ], [], [ Some (IdentTrivia.OriginalNotation "|>") ])
let toString (sli : SynLongIdent) : string =
sli.LongIdent |> List.map _.idText |> String.concat "."
let create (ident : LongIdent) : SynLongIdent =
let commas =
match ident with
| [] -> []
| _ :: commas -> commas |> List.map (fun _ -> range0)
SynLongIdent.SynLongIdent (ident, commas, List.replicate ident.Length None)
let inline createI (i : Ident) : SynLongIdent = create [ i ]
let inline createS (s : string) : SynLongIdent = createI (Ident (s, range0))
let inline createS' (s : string list) : SynLongIdent =
create (s |> List.map (fun i -> Ident (i, range0)))
let isUnit (ident : SynLongIdent) : bool =
match ident.LongIdent with
| [ i ] when System.String.Equals (i.idText, "unit", System.StringComparison.OrdinalIgnoreCase) -> true
| _ -> false
let isList (ident : SynLongIdent) : bool =
match ident.LongIdent with
| [ i ] when System.String.Equals (i.idText, "list", System.StringComparison.OrdinalIgnoreCase) -> true
// TODO: consider FSharpList or whatever it is
| _ -> false
let isArray (ident : SynLongIdent) : bool =
match ident.LongIdent with
| [ i ] when
System.String.Equals (i.idText, "array", System.StringComparison.OrdinalIgnoreCase)
|| System.String.Equals (i.idText, "[]", System.StringComparison.Ordinal)
->
true
| _ -> false
let isOption (ident : SynLongIdent) : bool =
match ident.LongIdent with
| [ i ] when System.String.Equals (i.idText, "option", System.StringComparison.OrdinalIgnoreCase) -> true
// TODO: consider Microsoft.FSharp.Option or whatever it is
| _ -> false
let isResponse (ident : SynLongIdent) : bool =
match ident.LongIdent |> List.map _.idText with
| [ "Response" ]
| [ "RestEase" ; "Response" ] -> true
| _ -> false
let isMap (ident : SynLongIdent) : bool =
match ident.LongIdent |> List.map _.idText with
| [ "Map" ] -> true
| _ -> false
let isReadOnlyDictionary (ident : SynLongIdent) : bool =
match ident.LongIdent |> List.map _.idText with
| [ "IReadOnlyDictionary" ]
| [ "Generic" ; "IReadOnlyDictionary" ]
| [ "Collections" ; "Generic" ; "IReadOnlyDictionary" ]
| [ "System" ; "Collections" ; "Generic" ; "IReadOnlyDictionary" ] -> true
| _ -> false
let isDictionary (ident : SynLongIdent) : bool =
match ident.LongIdent |> List.map _.idText with
| [ "Dictionary" ]
| [ "Generic" ; "Dictionary" ]
| [ "Collections" ; "Generic" ; "Dictionary" ]
| [ "System" ; "Collections" ; "Generic" ; "Dictionary" ] -> true
| _ -> false
let isIDictionary (ident : SynLongIdent) : bool =
match ident.LongIdent |> List.map _.idText with
| [ "IDictionary" ]
| [ "Generic" ; "IDictionary" ]
| [ "Collections" ; "Generic" ; "IDictionary" ]
| [ "System" ; "Collections" ; "Generic" ; "IDictionary" ] -> true
| _ -> false

View File

@@ -1,24 +0,0 @@
namespace WoofWare.Myriad.Plugins
open Fantomas.FCS.Syntax
open Fantomas.FCS.Text.Range
[<RequireQualifiedAccess>]
module internal SynMatchClause =
let create (lhs : SynPat) (rhs : SynExpr) : SynMatchClause =
SynMatchClause.SynMatchClause (
lhs,
None,
rhs,
range0,
DebugPointAtTarget.Yes,
{
ArrowRange = Some range0
BarRange = Some range0
}
)
let withWhere (where : SynExpr) (m : SynMatchClause) : SynMatchClause =
match m with
| SynMatchClause (synPat, _, resultExpr, range, debugPointAtTarget, synMatchClauseTrivia) ->
SynMatchClause (synPat, Some where, resultExpr, range, debugPointAtTarget, synMatchClauseTrivia)

View File

@@ -1,65 +0,0 @@
namespace WoofWare.Myriad.Plugins
open Fantomas.FCS.Syntax
open Fantomas.FCS.SyntaxTrivia
open Fantomas.FCS.Text.Range
open Fantomas.FCS.Xml
[<RequireQualifiedAccess>]
module internal SynMemberDefn =
let private interfaceMemberSlotFlags =
{
SynMemberFlags.IsInstance = true
SynMemberFlags.IsDispatchSlot = true
SynMemberFlags.IsOverrideOrExplicitImpl = false
SynMemberFlags.IsFinal = false
SynMemberFlags.GetterOrSetterIsCompilerGenerated = false
SynMemberFlags.MemberKind = SynMemberKind.Member
}
let abstractMember
(ident : SynIdent)
(typars : SynTyparDecls option)
(arity : SynValInfo)
(xmlDoc : PreXmlDoc)
(returnType : SynType)
: SynMemberDefn
=
let slot =
SynValSig.SynValSig (
[],
ident,
SynValTyparDecls.SynValTyparDecls (typars, true),
returnType,
arity,
false,
false,
xmlDoc,
None,
None,
range0,
{
EqualsRange = None
WithKeyword = None
InlineKeyword = None
LeadingKeyword = SynLeadingKeyword.Abstract range0
}
)
SynMemberDefn.AbstractSlot (
slot,
interfaceMemberSlotFlags,
range0,
{
GetSetKeywords = None
}
)
let staticMember (binding : SynBinding) : SynMemberDefn =
let binding = SynBinding.makeStaticMember binding
SynMemberDefn.Member (binding, range0)
let memberImplementation (binding : SynBinding) : SynMemberDefn =
let binding = SynBinding.makeInstanceMember binding
SynMemberDefn.Member (binding, range0)

View File

@@ -1,28 +0,0 @@
namespace WoofWare.Myriad.Plugins
open Fantomas.FCS.Syntax
open Fantomas.FCS.SyntaxTrivia
open Fantomas.FCS.Text.Range
[<RequireQualifiedAccess>]
module internal SynModuleDecl =
let inline openAny (ident : SynOpenDeclTarget) : SynModuleDecl = SynModuleDecl.Open (ident, range0)
let inline createLets (bindings : SynBinding list) : SynModuleDecl =
SynModuleDecl.Let (false, bindings, range0)
let inline createLet (binding : SynBinding) : SynModuleDecl = createLets [ binding ]
let nestedModule (info : SynComponentInfo) (decls : SynModuleDecl list) : SynModuleDecl =
SynModuleDecl.NestedModule (
info,
false,
decls,
false,
range0,
{
ModuleKeyword = Some range0
EqualsRange = Some range0
}
)

View File

@@ -1,24 +0,0 @@
namespace WoofWare.Myriad.Plugins
open Fantomas.FCS.Syntax
open Fantomas.FCS.SyntaxTrivia
open Fantomas.FCS.Xml
open Fantomas.FCS.Text.Range
[<RequireQualifiedAccess>]
module internal SynModuleOrNamespace =
let createNamespace (name : LongIdent) (decls : SynModuleDecl list) =
SynModuleOrNamespace.SynModuleOrNamespace (
name,
false,
SynModuleOrNamespaceKind.DeclaredNamespace,
decls,
PreXmlDoc.Empty,
[],
None,
range0,
{
LeadingKeyword = SynModuleOrNamespaceLeadingKeyword.Namespace range0
}
)

View File

@@ -1,35 +0,0 @@
namespace WoofWare.Myriad.Plugins
open Fantomas.FCS.Syntax
open Fantomas.FCS.Text.Range
[<RequireQualifiedAccess>]
module internal SynPat =
let inline paren (pat : SynPat) : SynPat = SynPat.Paren (pat, range0)
let inline annotateTypeNoParen (ty : SynType) (pat : SynPat) = SynPat.Typed (pat, ty, range0)
let inline annotateType (ty : SynType) (pat : SynPat) = paren (annotateTypeNoParen ty pat)
let inline named (s : string) : SynPat =
SynPat.Named (SynIdent.SynIdent (Ident (s, range0), None), false, None, range0)
let inline namedI (i : Ident) : SynPat =
SynPat.Named (SynIdent.SynIdent (i, None), false, None, range0)
let inline identWithArgs (i : LongIdent) (args : SynArgPats) : SynPat =
SynPat.LongIdent (SynLongIdent.create i, None, None, args, None, range0)
let inline tupleNoParen (elements : SynPat list) : SynPat =
match elements with
| [] -> failwith "Can't tuple no elements in a pattern"
| [ p ] -> p
| elements -> SynPat.Tuple (false, elements, List.replicate (elements.Length - 1) range0, range0)
let inline tuple (elements : SynPat list) : SynPat = tupleNoParen elements |> paren
let inline createConst (c : SynConst) = SynPat.Const (c, range0)
let unit = createConst SynConst.Unit
let createNull = SynPat.Null range0

View File

@@ -1,239 +0,0 @@
namespace WoofWare.Myriad.Plugins
open Fantomas.FCS.Syntax
open Fantomas.FCS.Text.Range
[<RequireQualifiedAccess>]
module internal SynType =
let rec stripOptionalParen (ty : SynType) : SynType =
match ty with
| SynType.Paren (ty, _) -> stripOptionalParen ty
| ty -> ty
let inline createLongIdent (ident : LongIdent) : SynType =
SynType.LongIdent (SynLongIdent.create ident)
let inline createLongIdent' (ident : string list) : SynType =
SynType.LongIdent (SynLongIdent.createS' ident)
let inline named (name : string) = createLongIdent' [ name ]
let inline app' (name : SynType) (args : SynType list) : SynType =
if args.IsEmpty then
failwith "Type cannot be applied to no arguments"
SynType.App (name, Some range0, args, List.replicate (args.Length - 1) range0, Some range0, false, range0)
let inline app (name : string) (args : SynType list) : SynType = app' (named name) args
let inline appPostfix (name : string) (arg : SynType) : SynType =
SynType.App (named name, None, [ arg ], [], None, true, range0)
let inline funFromDomain (domain : SynType) (range : SynType) : SynType =
SynType.Fun (
domain,
range,
range0,
{
ArrowRange = range0
}
)
let inline signatureParamOfType (ty : SynType) (name : Ident option) : SynType =
SynType.SignatureParameter ([], false, name, ty, range0)
let inline var (ty : SynTypar) : SynType = SynType.Var (ty, range0)
let unit : SynType = named "unit"
let int : SynType = named "int"
/// Given ['a1, 'a2] and 'ret, returns 'a1 -> 'a2 -> 'ret.
let toFun (inputs : SynType list) (ret : SynType) : SynType =
(ret, List.rev inputs) ||> List.fold (fun ty input -> funFromDomain input ty)
[<AutoOpen>]
module internal SynTypePatterns =
let (|OptionType|_|) (fieldType : SynType) =
match fieldType with
| SynType.App (SynType.LongIdent ident, _, [ innerType ], _, _, _, _) when SynLongIdent.isOption ident ->
Some innerType
| _ -> None
let (|UnitType|_|) (fieldType : SynType) : unit option =
match fieldType with
| SynType.LongIdent ident when SynLongIdent.isUnit ident -> Some ()
| _ -> None
let (|ListType|_|) (fieldType : SynType) =
match fieldType with
| SynType.App (SynType.LongIdent ident, _, [ innerType ], _, _, _, _) when SynLongIdent.isList ident ->
Some innerType
| _ -> None
let (|ArrayType|_|) (fieldType : SynType) =
match fieldType with
| SynType.App (SynType.LongIdent ident, _, [ innerType ], _, _, _, _) when SynLongIdent.isArray ident ->
Some innerType
| SynType.Array (1, innerType, _) -> Some innerType
| _ -> None
let (|RestEaseResponseType|_|) (fieldType : SynType) =
match fieldType with
| SynType.App (SynType.LongIdent ident, _, [ innerType ], _, _, _, _) when SynLongIdent.isResponse ident ->
Some innerType
| _ -> None
let (|DictionaryType|_|) (fieldType : SynType) =
match fieldType with
| SynType.App (SynType.LongIdent ident, _, [ key ; value ], _, _, _, _) when SynLongIdent.isDictionary ident ->
Some (key, value)
| _ -> None
let (|IDictionaryType|_|) (fieldType : SynType) =
match fieldType with
| SynType.App (SynType.LongIdent ident, _, [ key ; value ], _, _, _, _) when SynLongIdent.isIDictionary ident ->
Some (key, value)
| _ -> None
let (|IReadOnlyDictionaryType|_|) (fieldType : SynType) =
match fieldType with
| SynType.App (SynType.LongIdent ident, _, [ key ; value ], _, _, _, _) when
SynLongIdent.isReadOnlyDictionary ident
->
Some (key, value)
| _ -> None
let (|MapType|_|) (fieldType : SynType) =
match fieldType with
| SynType.App (SynType.LongIdent ident, _, [ key ; value ], _, _, _, _) when SynLongIdent.isMap ident ->
Some (key, value)
| _ -> None
let (|BigInt|_|) (fieldType : SynType) : unit option =
match fieldType with
| SynType.LongIdent ident ->
match ident.LongIdent |> List.map _.idText with
| [ "bigint" ]
| [ "BigInteger" ]
| [ "Numerics" ; "BigInteger" ]
| [ "System" ; "Numerics" ; "BigInteger" ] -> Some ()
| _ -> None
| _ -> None
/// Returns the type, qualified as in e.g. `System.Boolean`.
let (|PrimitiveType|_|) (fieldType : SynType) : LongIdent option =
match fieldType with
| SynType.LongIdent ident ->
match ident.LongIdent with
| [ i ] -> Primitives.qualifyType i.idText
| _ -> None
| _ -> None
let (|String|_|) (fieldType : SynType) : unit option =
match fieldType with
| SynType.LongIdent ident ->
match ident.LongIdent with
| [ i ] ->
[ "string" ]
|> List.tryFind (fun s -> s = i.idText)
|> Option.map ignore<string>
| _ -> None
| _ -> None
let (|Byte|_|) (fieldType : SynType) : unit option =
match fieldType with
| SynType.LongIdent ident ->
match ident.LongIdent with
| [ i ] -> [ "byte" ] |> List.tryFind (fun s -> s = i.idText) |> Option.map ignore<string>
| _ -> None
| _ -> None
let (|Guid|_|) (fieldType : SynType) : unit option =
match fieldType with
| SynType.LongIdent ident ->
match ident.LongIdent |> List.map (fun i -> i.idText) with
| [ "System" ; "Guid" ]
| [ "Guid" ] -> Some ()
| _ -> None
| _ -> None
let (|HttpResponseMessage|_|) (fieldType : SynType) : unit option =
match fieldType with
| SynType.LongIdent ident ->
match ident.LongIdent |> List.map (fun i -> i.idText) with
| [ "System" ; "Net" ; "Http" ; "HttpResponseMessage" ]
| [ "Net" ; "Http" ; "HttpResponseMessage" ]
| [ "Http" ; "HttpResponseMessage" ]
| [ "HttpResponseMessage" ] -> Some ()
| _ -> None
| _ -> None
let (|HttpContent|_|) (fieldType : SynType) : unit option =
match fieldType with
| SynType.LongIdent ident ->
match ident.LongIdent |> List.map (fun i -> i.idText) with
| [ "System" ; "Net" ; "Http" ; "HttpContent" ]
| [ "Net" ; "Http" ; "HttpContent" ]
| [ "Http" ; "HttpContent" ]
| [ "HttpContent" ] -> Some ()
| _ -> None
| _ -> None
let (|Stream|_|) (fieldType : SynType) : unit option =
match fieldType with
| SynType.LongIdent ident ->
match ident.LongIdent |> List.map (fun i -> i.idText) with
| [ "System" ; "IO" ; "Stream" ]
| [ "IO" ; "Stream" ]
| [ "Stream" ] -> Some ()
| _ -> None
| _ -> None
let (|NumberType|_|) (fieldType : SynType) =
match fieldType with
| SynType.LongIdent ident ->
match ident.LongIdent with
| [ i ] -> [ "string" ; "float" ; "int" ; "bool" ] |> List.tryFind (fun s -> s = i.idText)
| _ -> None
| _ -> None
let (|DateOnly|_|) (fieldType : SynType) =
match fieldType with
| SynType.LongIdent (SynLongIdent.SynLongIdent (ident, _, _)) ->
match ident |> List.map (fun i -> i.idText) with
| [ "System" ; "DateOnly" ]
| [ "DateOnly" ] -> Some ()
| _ -> None
| _ -> None
let (|DateTime|_|) (fieldType : SynType) =
match fieldType with
| SynType.LongIdent (SynLongIdent.SynLongIdent (ident, _, _)) ->
match ident |> List.map (fun i -> i.idText) with
| [ "System" ; "DateTime" ]
| [ "DateTime" ] -> Some ()
| _ -> None
| _ -> None
let (|Uri|_|) (fieldType : SynType) =
match fieldType with
| SynType.LongIdent (SynLongIdent.SynLongIdent (ident, _, _)) ->
match ident |> List.map (fun i -> i.idText) with
| [ "System" ; "Uri" ]
| [ "Uri" ] -> Some ()
| _ -> None
| _ -> None
let (|Task|_|) (fieldType : SynType) : SynType option =
match fieldType with
| SynType.App (SynType.LongIdent (SynLongIdent.SynLongIdent (ident, _, _)), _, args, _, _, _, _) ->
match ident |> List.map (fun i -> i.idText) with
| [ "Task" ]
| [ "Tasks" ; "Task" ]
| [ "Threading" ; "Tasks" ; "Task" ]
| [ "System" ; "Threading" ; "Tasks" ; "Task" ] ->
match args with
| [ arg ] -> Some arg
| _ -> failwithf "Expected Task to be applied to exactly one arg, but got: %+A" args
| _ -> None
| _ -> None

View File

@@ -1,27 +0,0 @@
namespace WoofWare.Myriad.Plugins
open Fantomas.FCS.Syntax
open Fantomas.FCS.SyntaxTrivia
open Fantomas.FCS.Text.Range
[<RequireQualifiedAccess>]
module internal SynTypeDefn =
let inline create (componentInfo : SynComponentInfo) (repr : SynTypeDefnRepr) : SynTypeDefn =
SynTypeDefn.SynTypeDefn (
componentInfo,
repr,
[],
None,
range0,
{
LeadingKeyword = SynTypeDefnLeadingKeyword.Type range0
EqualsRange = Some range0
WithKeyword = None
}
)
let inline withMemberDefns (members : SynMemberDefn list) (r : SynTypeDefn) : SynTypeDefn =
match r with
| SynTypeDefn (typeInfo, typeRepr, _, ctor, range, trivia) ->
SynTypeDefn.SynTypeDefn (typeInfo, typeRepr, members, ctor, range, trivia)

View File

@@ -1,20 +0,0 @@
namespace WoofWare.Myriad.Plugins
open Fantomas.FCS.Syntax
open Fantomas.FCS.Text.Range
[<RequireQualifiedAccess>]
module internal SynTypeDefnRepr =
let inline interfaceType (mems : SynMemberDefns) : SynTypeDefnRepr =
SynTypeDefnRepr.ObjectModel (SynTypeDefnKind.Unspecified, mems, range0)
/// Indicates the body of a `type Foo with {body}` extension type declaration.
let inline augmentation () : SynTypeDefnRepr =
SynTypeDefnRepr.ObjectModel (SynTypeDefnKind.Augmentation range0, [], range0)
let inline union (cases : SynUnionCase list) : SynTypeDefnRepr =
SynTypeDefnRepr.Simple (SynTypeDefnSimpleRepr.Union (None, cases, range0), range0)
let inline record (fields : SynField list) : SynTypeDefnRepr =
SynTypeDefnRepr.Simple (SynTypeDefnSimpleRepr.Record (None, fields, range0), range0)

View File

@@ -1,41 +0,0 @@
namespace WoofWare.Myriad.Plugins
open Fantomas.FCS.Syntax
type internal UnionCase<'Ident> =
{
Fields : SynFieldData<'Ident> list
Attrs : SynAttribute list
Ident : Ident
}
[<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>]
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
}

View File

@@ -0,0 +1,11 @@
namespace WoofWare.Myriad.Plugins
open System
[<AutoOpen>]
module internal Text =
let (|StartsWith|_|) (prefix : string) (s : string) : string option =
if s.StartsWith (prefix, StringComparison.Ordinal) then
Some (s.Substring prefix.Length)
else
None

View File

@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
@@ -15,44 +15,35 @@
<WarnOn>FS3559</WarnOn>
<PackageId>WoofWare.Myriad.Plugins</PackageId>
<PackageIcon>logo.png</PackageIcon>
<NoWarn>NU5118</NoWarn>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Myriad.Core" Version="0.8.3" PrivateAssets="all"/>
<PackageReference Include="Myriad.Core" Version="0.8.3" />
<PackageReference Include="TypeEquality" Version="0.3.0" />
<PackageReference Include="WoofWare.Whippet.Fantomas" Version="0.6.1" />
<!-- the lowest version allowed by Myriad.Core -->
<PackageReference Update="FSharp.Core" Version="6.0.1" PrivateAssets="all"/>
</ItemGroup>
<ItemGroup>
<Compile Include="List.fs"/>
<Compile Include="Primitives.fs" />
<Compile Include="SynExpr\PreXmlDoc.fs" />
<Compile Include="SynExpr\Ident.fs" />
<Compile Include="SynExpr\SynLongIdent.fs" />
<Compile Include="SynExpr\SynExprLetOrUseTrivia.fs" />
<Compile Include="SynExpr\SynPat.fs" />
<Compile Include="SynExpr\SynBinding.fs" />
<Compile Include="SynExpr\SynType.fs" />
<Compile Include="SynExpr\SynMatchClause.fs" />
<Compile Include="SynExpr\CompExpr.fs" />
<Compile Include="SynExpr\SynExpr.fs" />
<Compile Include="SynExpr\SynArgPats.fs" />
<Compile Include="SynExpr\SynField.fs" />
<Compile Include="SynExpr\SynUnionCase.fs" />
<Compile Include="SynExpr\SynTypeDefnRepr.fs" />
<Compile Include="SynExpr\SynTypeDefn.fs" />
<Compile Include="SynExpr\SynComponentInfo.fs" />
<Compile Include="SynExpr\SynMemberDefn.fs" />
<Compile Include="SynExpr\SynAttribute.fs" />
<Compile Include="SynExpr\SynModuleDecl.fs" />
<Compile Include="SynExpr\SynModuleOrNamespace.fs" />
<Compile Include="Text.fs" />
<Compile Include="Measure.fs" />
<Compile Include="AstHelper.fs" />
<Compile Include="Parameters.fs" />
<Compile Include="RemoveOptionsGenerator.fs"/>
<Compile Include="MyriadParamParser.fs" />
<Compile Include="InterfaceMockGenerator.fs"/>
<Compile Include="JsonSerializeGenerator.fs"/>
<Compile Include="JsonParseGenerator.fs"/>
<Compile Include="HttpClientGenerator.fs"/>
<Compile Include="CataGenerator.fs" />
<Compile Include="ArgParserGenerator.fs" />
<Compile Include="Swagger.fs" />
<Compile Include="SwaggerClientGenerator.fs" />
<None Include="ApacheLicence.txt" />
<EmbeddedResource Include="version.json"/>
<EmbeddedResource Include="SurfaceBaseline.txt"/>
<None Include="..\README.md">
@@ -69,7 +60,7 @@
<ProjectReference Include="..\WoofWare.Myriad.Plugins.Attributes\WoofWare.Myriad.Plugins.Attributes.fsproj"/>
<!-- NuGet is such a clown package manager! Get the DLLs into the Nupkg artefact, I have no idea why this is needed,
but without this line, we don't get any dependency at all packaged into the resulting artefact. -->
<None Include="$(OutputPath)\WoofWare.Myriad.Plugins.Attributes.dll" Pack="true" PackagePath="lib\$(TargetFramework)"/>
<None Include="$(OutputPath)\*.dll" Pack="true" PackagePath="lib\$(TargetFramework)"/>
</ItemGroup>
</Project>

View File

@@ -1,13 +1,14 @@
{
"version": "2.1",
"version": "6.0",
"publicReleaseRefSpec": [
"^refs/heads/main$"
],
"pathFilters": [
":/",
":^WoofWare.Myriad.Plugins.Test/",
":^WoofWare.Myriad.Plugins.Attributes/Test/",
":^/.github/",
":^/CHANGELOG.md"
"./",
":/WoofWare.Myriad.Plugins.Attributes",
":^/WoofWare.Myriad.Plugins.Attributes/Test",
":/global.json",
":/README.md",
":/Directory.Build.props"
]
}

View File

@@ -10,7 +10,7 @@
</PropertyGroup>
<ItemGroup>
<PackageDownload Include="G-Research.FSharp.Analyzers" Version="[0.10.0]" />
<PackageDownload Include="G-Research.FSharp.Analyzers" Version="[0.14.0]" />
</ItemGroup>
</Project>

12
flake.lock generated
View File

@@ -5,11 +5,11 @@
"systems": "systems"
},
"locked": {
"lastModified": 1701680307,
"narHash": "sha256-kAuep2h5ajznlPMD9rnQyffWG8EM/C73lejGofXvdM8=",
"lastModified": 1731533236,
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "4022d587cbbfd70fe950c1e2083a02621806a725",
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
"type": "github"
},
"original": {
@@ -20,11 +20,11 @@
},
"nixpkgs": {
"locked": {
"lastModified": 1706367331,
"narHash": "sha256-AqgkGHRrI6h/8FWuVbnkfFmXr4Bqsr4fV23aISqj/xg=",
"lastModified": 1744868846,
"narHash": "sha256-5RJTdUHDmj12Qsv7XOhuospjAjATNiTMElplWnJE9Hs=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "160b762eda6d139ac10ae081f8f78d640dd523eb",
"rev": "ebe4301cbd8f81c4f8d3244b3632338bbeb6d49c",
"type": "github"
},
"original": {

View File

@@ -14,10 +14,10 @@
flake-utils.lib.eachDefaultSystem (system: let
pkgs = nixpkgs.legacyPackages.${system};
pname = "WoofWare.Myriad.Plugins";
dotnet-sdk = pkgs.dotnet-sdk_8;
dotnet-runtime = pkgs.dotnetCorePackages.runtime_8_0;
dotnet-sdk = pkgs.dotnetCorePackages.sdk_9_0;
dotnet-runtime = pkgs.dotnetCorePackages.runtime_9_0;
version = "0.1";
dotnetTool = dllOverride: toolName: toolVersion: sha256:
dotnetTool = dllOverride: toolName: toolVersion: hash:
pkgs.stdenvNoCC.mkDerivation rec {
name = toolName;
version = toolVersion;
@@ -25,26 +25,30 @@
src = pkgs.fetchNuGet {
pname = name;
version = version;
sha256 = sha256;
installPhase = ''mkdir -p $out/bin && cp -r tools/net6.0/any/* $out/bin'';
hash = hash;
installPhase = ''mkdir -p $out/bin && cp -r tools/net*/any/* $out/bin'';
};
installPhase = let
dll =
if isNull dllOverride
then name
else dllOverride;
in ''
runHook preInstall
mkdir -p "$out/lib"
cp -r ./bin/* "$out/lib"
makeWrapper "${dotnet-runtime}/bin/dotnet" "$out/bin/${name}" --add-flags "$out/lib/${dll}.dll"
runHook postInstall
'';
in
# fsharp-analyzers requires the .NET SDK at runtime, so we use that instead of dotnet-runtime.
''
runHook preInstall
mkdir -p "$out/lib"
cp -r ./bin/* "$out/lib"
makeWrapper "${dotnet-sdk}/bin/dotnet" "$out/bin/${name}" --set DOTNET_HOST_PATH "${dotnet-sdk}/bin/dotnet" --add-flags "$out/lib/${dll}.dll"
runHook postInstall
'';
};
in {
packages = {
fantomas = dotnetTool null "fantomas" (builtins.fromJSON (builtins.readFile ./.config/dotnet-tools.json)).tools.fantomas.version (builtins.head (builtins.filter (elem: elem.pname == "fantomas") ((import ./nix/deps.nix) {fetchNuGet = x: x;}))).sha256;
fsharp-analyzers = dotnetTool "FSharp.Analyzers.Cli" "fsharp-analyzers" (builtins.fromJSON (builtins.readFile ./.config/dotnet-tools.json)).tools.fsharp-analyzers.version (builtins.head (builtins.filter (elem: elem.pname == "fsharp-analyzers") ((import ./nix/deps.nix) {fetchNuGet = x: x;}))).sha256;
packages = let
deps = builtins.fromJSON (builtins.readFile ./nix/deps.json);
in {
fantomas = dotnetTool null "fantomas" (builtins.fromJSON (builtins.readFile ./.config/dotnet-tools.json)).tools.fantomas.version (builtins.head (builtins.filter (elem: elem.pname == "fantomas") deps)).hash;
fsharp-analyzers = dotnetTool "FSharp.Analyzers.Cli" "fsharp-analyzers" (builtins.fromJSON (builtins.readFile ./.config/dotnet-tools.json)).tools.fsharp-analyzers.version (builtins.head (builtins.filter (elem: elem.pname == "fsharp-analyzers") deps)).hash;
default = pkgs.buildDotnetModule {
inherit pname version dotnet-sdk dotnet-runtime;
name = "WoofWare.Myriad.Plugins";
@@ -52,7 +56,7 @@
projectFile = "./WoofWare.Myriad.Plugins/WoofWare.Myriad.Plugins.fsproj";
testProjectFile = "./WoofWare.Myriad.Plugins.Test/WoofWare.Myriad.Plugins.Test.fsproj";
disabledTests = ["WoofWare.Myriad.Plugins.Test.TestSurface.CheckVersionAgainstRemote"];
nugetDeps = ./nix/deps.nix; # `nix build .#default.passthru.fetch-deps && ./result` and put the result here
nugetDeps = ./nix/deps.json; # `nix build .#default.fetch-deps && ./result nix/deps.json`
doCheck = true;
};
};

View File

@@ -1,6 +1,6 @@
{
"sdk": {
"version": "8.0.100",
"rollForward": "latestFeature"
}
}
{
"sdk": {
"version": "9.0.100",
"rollForward": "latestMajor"
}
}

387
nix/deps.json Normal file
View File

@@ -0,0 +1,387 @@
[
{
"pname": "ApiSurface",
"version": "4.1.20",
"hash": "sha256-koWgO9FC9ax+Ij56ug8kxeyknl0yhLqnNLOUdxtqqo4="
},
{
"pname": "fantomas",
"version": "7.0.1",
"hash": "sha256-2aGD6Kjh83gmssRqqZ/Uihi7VbNqNUelX4otIfCuhTI="
},
{
"pname": "Fantomas.Core",
"version": "6.1.1",
"hash": "sha256-FcTLHQFvKkQY/kV08jhhy/St/+FmXpp3epp/R3zUXMA="
},
{
"pname": "Fantomas.FCS",
"version": "6.1.1",
"hash": "sha256-NuZ8msPEHYA8T3EYREB28F1RcNgUU8V54eg2+UttYxw="
},
{
"pname": "FsCheck",
"version": "3.2.0",
"hash": "sha256-ksZ4vLgWwyQOzFuK2BczdtDtWWYmedG7UBAg4pYuI8g="
},
{
"pname": "fsharp-analyzers",
"version": "0.30.0",
"hash": "sha256-7oaSwpHAU1opzpz4szLU/gDaJC/ww9eiFkPu0nr4Mj4="
},
{
"pname": "FSharp.Core",
"version": "4.3.4",
"hash": "sha256-styyo+6mJy+yxE0NZG/b1hxkAjPOnJfMgd9zWzCJ5uk="
},
{
"pname": "FSharp.Core",
"version": "6.0.1",
"hash": "sha256-Ehsgt3nCJijpaVuJguC1TPVEKSkJd6PSc07D2ZQSemI="
},
{
"pname": "FSharp.Core",
"version": "9.0.202",
"hash": "sha256-64Gub0qemmCoMa1tDus6TeTuB1+5sHfE6KD2j4o84mA="
},
{
"pname": "FsUnit",
"version": "7.0.1",
"hash": "sha256-K85CIdxMeFSHEKZk6heIXp/oFjWAn7dBILKrw49pJUY="
},
{
"pname": "Microsoft.ApplicationInsights",
"version": "2.22.0",
"hash": "sha256-mUQ63atpT00r49ca50uZu2YCiLg3yd6r3HzTryqcuEA="
},
{
"pname": "Microsoft.AspNetCore.App.Ref",
"version": "6.0.36",
"hash": "sha256-9jDkWbjw/nd8yqdzVTagCuqr6owJ/DUMi4BlUZT4hWU="
},
{
"pname": "Microsoft.AspNetCore.App.Runtime.linux-arm64",
"version": "6.0.36",
"hash": "sha256-JQULJyF0ivLoUU1JaFfK/HHg+/qzpN7V2RR2Cc+WlQ4="
},
{
"pname": "Microsoft.AspNetCore.App.Runtime.linux-x64",
"version": "6.0.36",
"hash": "sha256-zUsVIpV481vMLAXaLEEUpEMA9/f1HGOnvaQnaWdzlyY="
},
{
"pname": "Microsoft.AspNetCore.App.Runtime.osx-arm64",
"version": "6.0.36",
"hash": "sha256-2seqZcz0JeUjkzh3QcGa9TcJ4LUafpFjTRk+Nm8T6T0="
},
{
"pname": "Microsoft.AspNetCore.App.Runtime.osx-x64",
"version": "6.0.36",
"hash": "sha256-yxLafxiBKkvfkDggPk0P9YZIHBkDJOsFTO7/V9mEHuU="
},
{
"pname": "Microsoft.CodeCoverage",
"version": "17.13.0",
"hash": "sha256-GKrIxeyQo5Az1mztfQgea1kGtJwonnNOrXK/0ULfu8o="
},
{
"pname": "Microsoft.NET.Test.Sdk",
"version": "17.13.0",
"hash": "sha256-sc2wvyV8cGm1FrNP2GGHEI584RCvRPu15erYCsgw5QY="
},
{
"pname": "Microsoft.NETCore.App.Host.linux-arm64",
"version": "6.0.36",
"hash": "sha256-9lC/LYnthYhjkWWz2kkFCvlA5LJOv11jdt59SDnpdy0="
},
{
"pname": "Microsoft.NETCore.App.Host.linux-x64",
"version": "6.0.36",
"hash": "sha256-VFRDzx7LJuvI5yzKdGmw/31NYVbwHWPKQvueQt5xc10="
},
{
"pname": "Microsoft.NETCore.App.Host.osx-arm64",
"version": "6.0.36",
"hash": "sha256-DaSWwYACJGolEBuMhzDVCj/rQTdDt061xCVi+gyQnuo="
},
{
"pname": "Microsoft.NETCore.App.Host.osx-x64",
"version": "6.0.36",
"hash": "sha256-FrRny9EI6HKCKQbu6mcLj5w4ooSRrODD4Vj2ZMGnMd4="
},
{
"pname": "Microsoft.NETCore.App.Ref",
"version": "6.0.36",
"hash": "sha256-9LZgVoIFF8qNyUu8kdJrYGLutMF/cL2K82HN2ywwlx8="
},
{
"pname": "Microsoft.NETCore.App.Runtime.linux-arm64",
"version": "6.0.36",
"hash": "sha256-k3rxvUhCEU0pVH8KgEMtkPiSOibn+nBh+0zT2xIfId8="
},
{
"pname": "Microsoft.NETCore.App.Runtime.linux-x64",
"version": "6.0.36",
"hash": "sha256-U8wJ2snSDFqeAgDVLXjnniidC7Cr5aJ1/h/BMSlyu0c="
},
{
"pname": "Microsoft.NETCore.App.Runtime.osx-arm64",
"version": "6.0.36",
"hash": "sha256-UfLcrL2Gj/OLz0s92Oo+OCJeDpZFAcQLPLiSNND8D5Y="
},
{
"pname": "Microsoft.NETCore.App.Runtime.osx-x64",
"version": "6.0.36",
"hash": "sha256-0xIJYFzxdMcnCj3wzkFRQZSnQcPHzPHMzePRIOA3oJs="
},
{
"pname": "Microsoft.NETCore.Platforms",
"version": "1.1.0",
"hash": "sha256-FeM40ktcObQJk4nMYShB61H/E8B7tIKfl9ObJ0IOcCM="
},
{
"pname": "Microsoft.NETCore.Platforms",
"version": "1.1.1",
"hash": "sha256-8hLiUKvy/YirCWlFwzdejD2Db3DaXhHxT7GSZx/znJg="
},
{
"pname": "Microsoft.NETCore.Platforms",
"version": "2.0.0",
"hash": "sha256-IEvBk6wUXSdyCnkj6tHahOJv290tVVT8tyemYcR0Yro="
},
{
"pname": "Microsoft.NETCore.Targets",
"version": "1.1.0",
"hash": "sha256-0AqQ2gMS8iNlYkrD+BxtIg7cXMnr9xZHtKAuN4bjfaQ="
},
{
"pname": "Microsoft.NETCore.Targets",
"version": "1.1.3",
"hash": "sha256-WLsf1NuUfRWyr7C7Rl9jiua9jximnVvzy6nk2D2bVRc="
},
{
"pname": "Microsoft.Testing.Extensions.Telemetry",
"version": "1.5.3",
"hash": "sha256-bIXwPSa3jkr2b6xINOqMUs6/uj/r4oVFM7xq3uVIZDU="
},
{
"pname": "Microsoft.Testing.Extensions.TrxReport.Abstractions",
"version": "1.5.3",
"hash": "sha256-IfMRfcyaIKEMRtx326ICKtinDBEfGw/Sv8ZHawJ96Yc="
},
{
"pname": "Microsoft.Testing.Extensions.VSTestBridge",
"version": "1.5.3",
"hash": "sha256-XpM/yFjhLSsuzyDV+xKubs4V1zVVYiV05E0+N4S1h0g="
},
{
"pname": "Microsoft.Testing.Platform",
"version": "1.5.3",
"hash": "sha256-y61Iih6w5D79dmrj2V675mcaeIiHoj1HSa1FRit2BLM="
},
{
"pname": "Microsoft.Testing.Platform.MSBuild",
"version": "1.5.3",
"hash": "sha256-YspvjE5Jfi587TAfsvfDVJXNrFOkx1B3y1CKV6m7YLY="
},
{
"pname": "Microsoft.TestPlatform.ObjectModel",
"version": "17.12.0",
"hash": "sha256-3XBHBSuCxggAIlHXmKNQNlPqMqwFlM952Av6RrLw1/w="
},
{
"pname": "Microsoft.TestPlatform.ObjectModel",
"version": "17.13.0",
"hash": "sha256-6S0fjfj8vA+h6dJVNwLi6oZhYDO/I/6hBZaq2VTW+Uk="
},
{
"pname": "Microsoft.TestPlatform.TestHost",
"version": "17.13.0",
"hash": "sha256-L/CJzou7dhmShUgXq3aXL3CaLTJll17Q+JY2DBdUUpo="
},
{
"pname": "Myriad.Core",
"version": "0.8.3",
"hash": "sha256-vBOxfq8QriX/yUtaXN69rEQaY/psRNJWxqATLidrt2g="
},
{
"pname": "Myriad.Sdk",
"version": "0.8.3",
"hash": "sha256-7O397WKhskKOvE3MkJT37BvxorDWngDR6gTUogtDZ2M="
},
{
"pname": "Nerdbank.GitVersioning",
"version": "3.8.38-alpha",
"hash": "sha256-gPMrVbjOZxXoofczF/pn6eVkLhjVSJIyQrLO2oljrDc="
},
{
"pname": "NETStandard.Library",
"version": "2.0.3",
"hash": "sha256-Prh2RPebz/s8AzHb2sPHg3Jl8s31inv9k+Qxd293ybo="
},
{
"pname": "Newtonsoft.Json",
"version": "13.0.1",
"hash": "sha256-K2tSVW4n4beRPzPu3rlVaBEMdGvWSv/3Q1fxaDh4Mjo="
},
{
"pname": "Newtonsoft.Json",
"version": "13.0.3",
"hash": "sha256-hy/BieY4qxBWVVsDqqOPaLy1QobiIapkbrESm6v2PHc="
},
{
"pname": "NuGet.Common",
"version": "6.13.2",
"hash": "sha256-ASLa/Jigg5Eop0ZrXPl98RW2rxnJRC7pbbxhuV74hFw="
},
{
"pname": "NuGet.Configuration",
"version": "6.13.2",
"hash": "sha256-z8VW1YdRDanyyRTDYRvRkSv/XPR3c/hMM1y8cNNjx0Y="
},
{
"pname": "NuGet.Frameworks",
"version": "6.13.2",
"hash": "sha256-caDyc+WgYOo43AUTjtbP0MyvYDb6JweEKDdIul61Cac="
},
{
"pname": "NuGet.Packaging",
"version": "6.13.2",
"hash": "sha256-lhO+SFwIYZ4aPHxIGm5ubkkE2a5Ve2xgtroRbNh7hpw="
},
{
"pname": "NuGet.Protocol",
"version": "6.13.2",
"hash": "sha256-5lnAHHZjy7A4vgv65AeBAs64mSNpuoUjxW3HnrMpuzY="
},
{
"pname": "NuGet.Versioning",
"version": "6.13.2",
"hash": "sha256-gmpyBpKnt+GHqgx/2uFKp+J2csbxEAy1E7WdVT117sw="
},
{
"pname": "NUnit",
"version": "4.3.2",
"hash": "sha256-0RWe8uFoxYp6qhPlDDEghOMcKJgyw2ybvEoAqBLebeE="
},
{
"pname": "NUnit3TestAdapter",
"version": "5.0.0",
"hash": "sha256-7jZM4qAbIzne3AcdFfMbvbgogqpxvVe6q2S7Ls8xQy0="
},
{
"pname": "RestEase",
"version": "1.6.4",
"hash": "sha256-FFmqFwlHhIln46k56Z8KM1G+xuPEh/bceKCQnJcdcdc="
},
{
"pname": "runtime.any.System.Runtime",
"version": "4.3.0",
"hash": "sha256-qwhNXBaJ1DtDkuRacgHwnZmOZ1u9q7N8j0cWOLYOELM="
},
{
"pname": "runtime.native.System",
"version": "4.3.0",
"hash": "sha256-ZBZaodnjvLXATWpXXakFgcy6P+gjhshFXmglrL5xD5Y="
},
{
"pname": "runtime.unix.System.Private.Uri",
"version": "4.3.0",
"hash": "sha256-c5tXWhE/fYbJVl9rXs0uHh3pTsg44YD1dJvyOA0WoMs="
},
{
"pname": "System.Collections.Immutable",
"version": "8.0.0",
"hash": "sha256-F7OVjKNwpqbUh8lTidbqJWYi476nsq9n+6k0+QVRo3w="
},
{
"pname": "System.Diagnostics.DiagnosticSource",
"version": "5.0.0",
"hash": "sha256-6mW3N6FvcdNH/pB58pl+pFSCGWgyaP4hfVtC/SMWDV4="
},
{
"pname": "System.Diagnostics.DiagnosticSource",
"version": "7.0.0",
"hash": "sha256-9Wk8cHSkjKtqkN6xW7KnXoQVtF/VNbKeBq79WqDesMs="
},
{
"pname": "System.Formats.Asn1",
"version": "6.0.0",
"hash": "sha256-KaMHgIRBF7Nf3VwOo+gJS1DcD+41cJDPWFh+TDQ8ee8="
},
{
"pname": "System.Formats.Asn1",
"version": "8.0.1",
"hash": "sha256-may/Wg+esmm1N14kQTG4ESMBi+GQKPp0ZrrBo/o6OXM="
},
{
"pname": "System.IO.Abstractions",
"version": "4.2.13",
"hash": "sha256-nkC/PiqE6+c1HJ2yTwg3x+qdBh844Z8n3ERWDW8k6Gg="
},
{
"pname": "System.IO.FileSystem.AccessControl",
"version": "4.5.0",
"hash": "sha256-ck44YBQ0M+2Im5dw0VjBgFD1s0XuY54cujrodjjSBL8="
},
{
"pname": "System.Memory",
"version": "4.5.5",
"hash": "sha256-EPQ9o1Kin7KzGI5O3U3PUQAZTItSbk9h/i4rViN3WiI="
},
{
"pname": "System.Private.Uri",
"version": "4.3.0",
"hash": "sha256-fVfgcoP4AVN1E5wHZbKBIOPYZ/xBeSIdsNF+bdukIRM="
},
{
"pname": "System.Reflection.Metadata",
"version": "1.6.0",
"hash": "sha256-JJfgaPav7UfEh4yRAQdGhLZF1brr0tUWPl6qmfNWq/E="
},
{
"pname": "System.Runtime",
"version": "4.3.1",
"hash": "sha256-R9T68AzS1PJJ7v6ARz9vo88pKL1dWqLOANg4pkQjkA0="
},
{
"pname": "System.Runtime.CompilerServices.Unsafe",
"version": "6.0.0",
"hash": "sha256-bEG1PnDp7uKYz/OgLOWs3RWwQSVYm+AnPwVmAmcgp2I="
},
{
"pname": "System.Security.AccessControl",
"version": "4.5.0",
"hash": "sha256-AFsKPb/nTk2/mqH/PYpaoI8PLsiKKimaXf+7Mb5VfPM="
},
{
"pname": "System.Security.Cryptography.Pkcs",
"version": "6.0.4",
"hash": "sha256-2e0aRybote+OR66bHaNiYpF//4fCiaO3zbR2e9GABUI="
},
{
"pname": "System.Security.Cryptography.ProtectedData",
"version": "4.4.0",
"hash": "sha256-Ri53QmFX8I8UH0x4PikQ1ZA07ZSnBUXStd5rBfGWFOE="
},
{
"pname": "System.Security.Principal.Windows",
"version": "4.5.0",
"hash": "sha256-BkUYNguz0e4NJp1kkW7aJBn3dyH9STwB5N8XqnlCsmY="
},
{
"pname": "System.Text.Json",
"version": "8.0.5",
"hash": "sha256-yKxo54w5odWT6nPruUVsaX53oPRe+gKzGvLnnxtwP68="
},
{
"pname": "TypeEquality",
"version": "0.3.0",
"hash": "sha256-V50xAOzzyUJrY+MYPRxtnqW5MVeATXCes89wPprv1r4="
},
{
"pname": "WoofWare.Whippet.Fantomas",
"version": "0.6.1",
"hash": "sha256-UTK+WbHR83zbMZ+2BzecIrKvp/5ZppHToIZ/oHTIF1k="
}
]

View File

@@ -1,344 +0,0 @@
# This file was automatically generated by passthru.fetch-deps.
# Please dont edit it manually, your changes might get overwritten!
{fetchNuGet}: [
(fetchNuGet {
pname = "ApiSurface";
version = "4.0.40";
sha256 = "1c9z0b6minlripwrjmv4yd5w8zj4lcpak4x41izh7ygx8kgmbvx0";
})
(fetchNuGet {
pname = "fantomas";
version = "6.3.7";
sha256 = "1z1a5bw7vwz6g8nvfgkvx66jnm4hmvn62vbyq0as60nw0jlvaidl";
})
(fetchNuGet {
pname = "Fantomas.Core";
version = "6.1.1";
sha256 = "1h2wsiy4fzwsg9vrlpk6w7zsvx6bc4wg4x25zqc48akg04fwpi0m";
})
(fetchNuGet {
pname = "Fantomas.FCS";
version = "6.1.1";
sha256 = "0733dm5zjdp8w5wwalqlv1q52pghfr04863i9wy807f4qfd7rrin";
})
(fetchNuGet {
pname = "FsCheck";
version = "2.16.6";
sha256 = "176rwky6b5rk8dzldiz4068p7m9c5y9ygzbhadrs14jkl94pc56n";
})
(fetchNuGet {
pname = "fsharp-analyzers";
version = "0.26.0";
sha256 = "0xgv5kvbwfdvcp6s8x7xagbbi4s3mqa4ixni6pazqvyflbgnah7b";
})
(fetchNuGet {
pname = "FSharp.Core";
version = "4.3.4";
sha256 = "1sg6i4q5nwyzh769g76f6c16876nvdpn83adqjr2y9x6xsiv5p5j";
})
(fetchNuGet {
pname = "FSharp.Core";
version = "6.0.1";
sha256 = "0qks2aadkhsffg9a6xq954ll9xacnph852avd7ljh9n2g6vj06qj";
})
(fetchNuGet {
pname = "FSharp.Core";
version = "8.0.101";
sha256 = "0prgcnki6s0rlrfbarrcv50w1bbhaalsyhhw5gsnjs2is7qrjbii";
})
(fetchNuGet {
pname = "FsUnit";
version = "6.0.0";
sha256 = "18q3p0z155znwj1l0qq3vq9nh9wl2i4mlfx4pmrnia4czr0xdkmb";
})
(fetchNuGet {
pname = "Microsoft.AspNetCore.App.Ref";
version = "6.0.26";
sha256 = "1d8nkz24vsm0iy2xm8y5ak2q1w1p99dxyz0y26acs6sfk2na0vm6";
})
(fetchNuGet {
pname = "Microsoft.AspNetCore.App.Runtime.linux-arm64";
version = "6.0.26";
sha256 = "1za8lc52m4z54d68wd64c2nhzy05g3gx171k5cdlx73fbymiys9z";
})
(fetchNuGet {
pname = "Microsoft.AspNetCore.App.Runtime.linux-x64";
version = "6.0.26";
sha256 = "1zpbmz6z8758gwywzg0bac8kx9x39sxxc9j4a4r2jl74l9ssw4vm";
})
(fetchNuGet {
pname = "Microsoft.AspNetCore.App.Runtime.osx-arm64";
version = "6.0.26";
sha256 = "1i8ydlwjzk7j0mzvn0rpljxfp1h50zwaqalnyvfxai1fwgigzgw5";
})
(fetchNuGet {
pname = "Microsoft.AspNetCore.App.Runtime.osx-x64";
version = "6.0.26";
sha256 = "02src68hd3213sd1a2ms1my7i92knfmdxclvv90il9cky2zsq8kw";
})
(fetchNuGet {
pname = "Microsoft.Build.Tasks.Git";
version = "8.0.0";
sha256 = "0055f69q3hbagqp8gl3nk0vfn4qyqyxsxyy7pd0g7wm3z28byzmx";
})
(fetchNuGet {
pname = "Microsoft.CodeCoverage";
version = "17.10.0";
sha256 = "0s0v7jmrq85n356xv7zixvwa4z94fszjcr5vll8x4im1a2lp00f9";
})
(fetchNuGet {
pname = "Microsoft.NET.Test.Sdk";
version = "17.10.0";
sha256 = "13g8fwl09li8fc71nk13dgkb7gahd4qhamyg2xby7am63nlchhdf";
})
(fetchNuGet {
pname = "Microsoft.NETCore.App.Host.linux-arm64";
version = "6.0.26";
sha256 = "19y6c6v20bgf7x7rrh4rx9y7s5fy8vp5m4j9b6gi1wp4rpb5mza4";
})
(fetchNuGet {
pname = "Microsoft.NETCore.App.Host.linux-x64";
version = "6.0.26";
sha256 = "0p7hhidaa3mnyiwnsijwy8578v843x8hh99255s69qwwyld6falv";
})
(fetchNuGet {
pname = "Microsoft.NETCore.App.Host.osx-arm64";
version = "6.0.26";
sha256 = "1mq11xsv9g1vsasp6k80y7xlvwi9hrpk5dgm773fvy8538s01gfv";
})
(fetchNuGet {
pname = "Microsoft.NETCore.App.Host.osx-x64";
version = "6.0.26";
sha256 = "1chac9b4424ihrrnlzvc7qz6j4ymfjyv4kzyazzzw19yhymdkh2s";
})
(fetchNuGet {
pname = "Microsoft.NETCore.App.Ref";
version = "6.0.26";
sha256 = "12gb52dhg5h9hgnyqh1zgj2w46paxv2pfh33pphl9ajhrdr7hlsb";
})
(fetchNuGet {
pname = "Microsoft.NETCore.App.Runtime.linux-arm64";
version = "6.0.26";
sha256 = "164hfrwqz5dxcbb441lridk4mzcqmarb0b7ckgvqhsvpawyjw88v";
})
(fetchNuGet {
pname = "Microsoft.NETCore.App.Runtime.linux-x64";
version = "6.0.26";
sha256 = "0islayddpnflviqpbq4djc4f3v9nhsa2y76k5x6il3csq5vdw2hq";
})
(fetchNuGet {
pname = "Microsoft.NETCore.App.Runtime.osx-arm64";
version = "6.0.26";
sha256 = "1acn5zw1pxzmcg3c0pbf9hal36fbdh9mvbsiwra7simrk7hzqpdc";
})
(fetchNuGet {
pname = "Microsoft.NETCore.App.Runtime.osx-x64";
version = "6.0.26";
sha256 = "00f9l9dkdz0zv5csaw8fkm6s8ckrj5n9k3ygz12daa22l3bcn6ii";
})
(fetchNuGet {
pname = "Microsoft.NETCore.Platforms";
version = "1.1.0";
sha256 = "08vh1r12g6ykjygq5d3vq09zylgb84l63k49jc4v8faw9g93iqqm";
})
(fetchNuGet {
pname = "Microsoft.NETCore.Platforms";
version = "1.1.1";
sha256 = "164wycgng4mi9zqi2pnsf1pq6gccbqvw6ib916mqizgjmd8f44pj";
})
(fetchNuGet {
pname = "Microsoft.NETCore.Platforms";
version = "2.0.0";
sha256 = "1fk2fk2639i7nzy58m9dvpdnzql4vb8yl8vr19r2fp8lmj9w2jr0";
})
(fetchNuGet {
pname = "Microsoft.NETCore.Targets";
version = "1.1.3";
sha256 = "05smkcyxir59rgrmp7d6327vvrlacdgldfxhmyr1azclvga1zfsq";
})
(fetchNuGet {
pname = "Microsoft.SourceLink.Common";
version = "8.0.0";
sha256 = "0xrr8yd34ij7dqnyddkp2awfmf9qn3c89xmw2f3npaa4wnajmx81";
})
(fetchNuGet {
pname = "Microsoft.SourceLink.GitHub";
version = "8.0.0";
sha256 = "1gdx7n45wwia3yvang3ls92sk3wrymqcx9p349j8wba2lyjf9m44";
})
(fetchNuGet {
pname = "Microsoft.TestPlatform.ObjectModel";
version = "17.10.0";
sha256 = "07j69cw8r39533w4p39mnj00kahazz38760in3jfc45kmlcdb26x";
})
(fetchNuGet {
pname = "Microsoft.TestPlatform.TestHost";
version = "17.10.0";
sha256 = "1bl471s7fx9jycr0cc8rylwf34mrvlg9qn1an6l86nisavfcyb7v";
})
(fetchNuGet {
pname = "Myriad.Core";
version = "0.8.3";
sha256 = "0s5pdckjw4x0qrbd4i3cz9iili5cppg5qnjbr7zjbbhhmxzb24xw";
})
(fetchNuGet {
pname = "Myriad.Sdk";
version = "0.8.3";
sha256 = "0qv78c5s5m04xb8h17nnn2ig26zcyya91k2dpj745cm1cbnzvvgc";
})
(fetchNuGet {
pname = "Nerdbank.GitVersioning";
version = "3.6.133";
sha256 = "1cdw8krvsnx0n34f7fm5hiiy7bs6h3asvncqcikc0g46l50w2j80";
})
(fetchNuGet {
pname = "NETStandard.Library";
version = "2.0.3";
sha256 = "1fn9fxppfcg4jgypp2pmrpr6awl3qz1xmnri0cygpkwvyx27df1y";
})
(fetchNuGet {
pname = "Newtonsoft.Json";
version = "13.0.1";
sha256 = "0fijg0w6iwap8gvzyjnndds0q4b8anwxxvik7y8vgq97dram4srb";
})
(fetchNuGet {
pname = "Newtonsoft.Json";
version = "13.0.3";
sha256 = "0xrwysmrn4midrjal8g2hr1bbg38iyisl0svamb11arqws4w2bw7";
})
(fetchNuGet {
pname = "NuGet.Common";
version = "6.10.0";
sha256 = "0nizrnilmlcqbm945293h8q3wfqfchb4xi8g50x4kjn0rbpd1kbh";
})
(fetchNuGet {
pname = "NuGet.Configuration";
version = "6.10.0";
sha256 = "1aqaknaawnqx4mnvx9qw73wvj48jjzv0d78dzwl7m9zjlrl9myhz";
})
(fetchNuGet {
pname = "NuGet.Frameworks";
version = "6.10.0";
sha256 = "0hrd8y31zx9a0wps49czw0qgbrakb49zn3abfgylc9xrq990zkqk";
})
(fetchNuGet {
pname = "NuGet.Packaging";
version = "6.10.0";
sha256 = "18s53cvrf51lihmaqqdf48p2qi6ky1l48jv0hvbp76cxwdg7rba4";
})
(fetchNuGet {
pname = "NuGet.Protocol";
version = "6.10.0";
sha256 = "0hmv4q0ks9i34mfgpb13l01la9v3jjllfh1qd3aqv105xrqrdxac";
})
(fetchNuGet {
pname = "NuGet.Versioning";
version = "6.10.0";
sha256 = "1x19njx4x0sw9fz8y5fibi15xfsrw5avir0cx0599yd7p3ykik5g";
})
(fetchNuGet {
pname = "NUnit";
version = "4.1.0";
sha256 = "0fj6xwgqaxq3mrai86bklclfmjkzf038mrslwfqf4ignaz9f7g5j";
})
(fetchNuGet {
pname = "NUnit3TestAdapter";
version = "4.5.0";
sha256 = "1srx1629s0k1kmf02nmz251q07vj6pv58mdafcr5dr0bbn1fh78i";
})
(fetchNuGet {
pname = "RestEase";
version = "1.6.4";
sha256 = "1mvi3nbrr450g3fgd1y4wg3bwl9k1agyjfd9wdkqk12714bsln8l";
})
(fetchNuGet {
pname = "runtime.any.System.Runtime";
version = "4.3.0";
sha256 = "1cqh1sv3h5j7ixyb7axxbdkqx6cxy00p4np4j91kpm492rf4s25b";
})
(fetchNuGet {
pname = "runtime.native.System";
version = "4.3.0";
sha256 = "15hgf6zaq9b8br2wi1i3x0zvmk410nlmsmva9p0bbg73v6hml5k4";
})
(fetchNuGet {
pname = "runtime.unix.System.Private.Uri";
version = "4.3.0";
sha256 = "1jx02q6kiwlvfksq1q9qr17fj78y5v6mwsszav4qcz9z25d5g6vk";
})
(fetchNuGet {
pname = "System.Diagnostics.DiagnosticSource";
version = "7.0.0";
sha256 = "1jxhvsh5mzdf0sgb4dfmbys1b12ylyr5pcfyj1map354fiq3qsgm";
})
(fetchNuGet {
pname = "System.Formats.Asn1";
version = "6.0.0";
sha256 = "1vvr7hs4qzjqb37r0w1mxq7xql2b17la63jwvmgv65s1hj00g8r9";
})
(fetchNuGet {
pname = "System.IO.Abstractions";
version = "4.2.13";
sha256 = "0s784iphsmj4vhkrzq9q3w39vsn76w44zclx3hsygsw458zbyh4y";
})
(fetchNuGet {
pname = "System.IO.FileSystem.AccessControl";
version = "4.5.0";
sha256 = "1gq4s8w7ds1sp8f9wqzf8nrzal40q5cd2w4pkf4fscrl2ih3hkkj";
})
(fetchNuGet {
pname = "System.Memory";
version = "4.5.5";
sha256 = "08jsfwimcarfzrhlyvjjid61j02irx6xsklf32rv57x2aaikvx0h";
})
(fetchNuGet {
pname = "System.Private.Uri";
version = "4.3.0";
sha256 = "04r1lkdnsznin0fj4ya1zikxiqr0h6r6a1ww2dsm60gqhdrf0mvx";
})
(fetchNuGet {
pname = "System.Reflection.Metadata";
version = "1.6.0";
sha256 = "1wdbavrrkajy7qbdblpbpbalbdl48q3h34cchz24gvdgyrlf15r4";
})
(fetchNuGet {
pname = "System.Runtime";
version = "4.3.1";
sha256 = "03ch4d2acf6q037a4njxpll2kkx3dwzlg07yxr4z5m6j1kqgmm27";
})
(fetchNuGet {
pname = "System.Runtime.CompilerServices.Unsafe";
version = "6.0.0";
sha256 = "0qm741kh4rh57wky16sq4m0v05fxmkjjr87krycf5vp9f0zbahbc";
})
(fetchNuGet {
pname = "System.Security.AccessControl";
version = "4.5.0";
sha256 = "1wvwanz33fzzbnd2jalar0p0z3x0ba53vzx1kazlskp7pwyhlnq0";
})
(fetchNuGet {
pname = "System.Security.Cryptography.Pkcs";
version = "6.0.4";
sha256 = "0hh5h38pnxmlrnvs72f2hzzpz4b2caiiv6xf8y7fzdg84r3imvfr";
})
(fetchNuGet {
pname = "System.Security.Cryptography.ProtectedData";
version = "4.4.0";
sha256 = "1q8ljvqhasyynp94a1d7jknk946m20lkwy2c3wa8zw2pc517fbj6";
})
(fetchNuGet {
pname = "System.Security.Principal.Windows";
version = "4.5.0";
sha256 = "0rmj89wsl5yzwh0kqjgx45vzf694v9p92r4x4q6yxldk1cv1hi86";
})
(fetchNuGet {
pname = "System.Text.Encodings.Web";
version = "7.0.0";
sha256 = "1151hbyrcf8kyg1jz8k9awpbic98lwz9x129rg7zk1wrs6vjlpxl";
})
(fetchNuGet {
pname = "System.Text.Json";
version = "7.0.3";
sha256 = "0zjrnc9lshagm6kdb9bdh45dmlnkpwcpyssa896sda93ngbmj8k9";
})
]