mirror of
				https://github.com/Smaug123/WoofWare.Myriad
				synced 2025-10-29 23:59:01 +00:00 
			
		
		
		
	Compare commits
	
		
			18 Commits
		
	
	
		
			eddf7bac18
			...
			WoofWare.M
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | 4b9f63d374 | ||
|  | b9ba07a8a7 | ||
|  | e80ed51498 | ||
|  | 61b07ad802 | ||
|  | 59369bcb94 | ||
|  | 072169e4e3 | ||
|  | 91136a25ab | ||
|  | c51038448a | ||
|  | 09780efb07 | ||
|  | f562271c12 | ||
|  | e3081c3136 | ||
|  | 232d2ba5ec | ||
|  | f7458f521e | ||
|  | bfc25a672b | ||
|  | af7fcb3028 | ||
|  | 91853b1fff | ||
|  | 1144e93c1c | ||
|  | d899d77ae2 | 
| @@ -3,13 +3,13 @@ | ||||
|   "isRoot": true, | ||||
|   "tools": { | ||||
|     "fantomas": { | ||||
|       "version": "6.3.0-alpha-007", | ||||
|       "version": "6.3.4", | ||||
|       "commands": [ | ||||
|         "fantomas" | ||||
|       ] | ||||
|     }, | ||||
|     "fsharp-analyzers": { | ||||
|       "version": "0.25.0", | ||||
|       "version": "0.26.0", | ||||
|       "commands": [ | ||||
|         "fsharp-analyzers" | ||||
|       ] | ||||
|   | ||||
							
								
								
									
										18
									
								
								.github/workflows/dotnet.yaml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										18
									
								
								.github/workflows/dotnet.yaml
									
									
									
									
										vendored
									
									
								
							| @@ -28,7 +28,7 @@ jobs: | ||||
|       with: | ||||
|         fetch-depth: 0 # so that NerdBank.GitVersioning has access to history | ||||
|     - name: Install Nix | ||||
|       uses: cachix/install-nix-action@v25 | ||||
|       uses: cachix/install-nix-action@V27 | ||||
|       with: | ||||
|         extra_nix_config: | | ||||
|           access-tokens = github.com=${{ secrets.GITHUB_TOKEN }} | ||||
| @@ -49,7 +49,7 @@ jobs: | ||||
|         with: | ||||
|           fetch-depth: 0 # so that NerdBank.GitVersioning has access to history | ||||
|       - name: Install Nix | ||||
|         uses: cachix/install-nix-action@v25 | ||||
|         uses: cachix/install-nix-action@V27 | ||||
|         with: | ||||
|           extra_nix_config: | | ||||
|             access-tokens = github.com=${{ secrets.GITHUB_TOKEN }} | ||||
| @@ -66,7 +66,7 @@ jobs: | ||||
|       - name: Checkout | ||||
|         uses: actions/checkout@v4 | ||||
|       - name: Install Nix | ||||
|         uses: cachix/install-nix-action@v25 | ||||
|         uses: cachix/install-nix-action@V27 | ||||
|         with: | ||||
|           extra_nix_config: | | ||||
|             access-tokens = github.com=${{ secrets.GITHUB_TOKEN }} | ||||
| @@ -79,7 +79,7 @@ jobs: | ||||
|       - name: Checkout | ||||
|         uses: actions/checkout@v4 | ||||
|       - name: Install Nix | ||||
|         uses: cachix/install-nix-action@v25 | ||||
|         uses: cachix/install-nix-action@V27 | ||||
|         with: | ||||
|           extra_nix_config: | | ||||
|             access-tokens = github.com=${{ secrets.GITHUB_TOKEN }} | ||||
| @@ -92,7 +92,7 @@ jobs: | ||||
|       - name: Checkout | ||||
|         uses: actions/checkout@v4 | ||||
|       - name: Install Nix | ||||
|         uses: cachix/install-nix-action@v25 | ||||
|         uses: cachix/install-nix-action@V27 | ||||
|         with: | ||||
|           extra_nix_config: | | ||||
|             access-tokens = github.com=${{ secrets.GITHUB_TOKEN }} | ||||
| @@ -105,7 +105,7 @@ jobs: | ||||
|     steps: | ||||
|       - uses: actions/checkout@master | ||||
|       - name: Install Nix | ||||
|         uses: cachix/install-nix-action@v25 | ||||
|         uses: cachix/install-nix-action@V27 | ||||
|         with: | ||||
|           extra_nix_config: | | ||||
|             access-tokens = github.com=${{ secrets.GITHUB_TOKEN }} | ||||
| @@ -118,7 +118,7 @@ jobs: | ||||
|     steps: | ||||
|       - uses: actions/checkout@master | ||||
|       - name: Install Nix | ||||
|         uses: cachix/install-nix-action@v25 | ||||
|         uses: cachix/install-nix-action@V27 | ||||
|         with: | ||||
|           extra_nix_config: | | ||||
|             access-tokens = github.com=${{ secrets.GITHUB_TOKEN }} | ||||
| @@ -132,7 +132,7 @@ jobs: | ||||
|       with: | ||||
|         fetch-depth: 0 # so that NerdBank.GitVersioning has access to history | ||||
|     - name: Install Nix | ||||
|       uses: cachix/install-nix-action@v25 | ||||
|       uses: cachix/install-nix-action@V27 | ||||
|       with: | ||||
|         extra_nix_config: | | ||||
|           access-tokens = github.com=${{ secrets.GITHUB_TOKEN }} | ||||
| @@ -188,7 +188,7 @@ jobs: | ||||
|     steps: | ||||
|       - uses: actions/checkout@v4 | ||||
|       - name: Install Nix | ||||
|         uses: cachix/install-nix-action@v25 | ||||
|         uses: cachix/install-nix-action@V27 | ||||
|         with: | ||||
|           extra_nix_config: | | ||||
|             access-tokens = github.com=${{ secrets.GITHUB_TOKEN }} | ||||
|   | ||||
							
								
								
									
										14
									
								
								CHANGELOG.md
									
									
									
									
									
								
							
							
						
						
									
										14
									
								
								CHANGELOG.md
									
									
									
									
									
								
							| @@ -1,6 +1,18 @@ | ||||
| Notable changes are recorded here. | ||||
|  | ||||
| # WoofWare.Myriad.Plugins 1.4 -> 2.0 | ||||
| # WoofWare.Myriad.Plugins 2.1.20, WoofWare.Myriad.Plugins.Attributes 3.0.1 | ||||
|  | ||||
| We now bundle copies of the RestEase attributes in `WoofWare.Myriad.Plugins.Attributes`, in case you don't want to take a dependency on RestEase. | ||||
|  | ||||
| # WoofWare.Myriad.Plugins 2.1.15 | ||||
|  | ||||
| The `GenerateMock` generator now permits a limited amount of inheritance in the record we're mocking out (specifically, `IDisposable`). | ||||
|  | ||||
| # WoofWare.Myriad.Plugins 2.1.8 | ||||
|  | ||||
| No change to the packages, but this is when we started creating and tagging GitHub releases, which are a better source of truth than this file. | ||||
|  | ||||
| # WoofWare.Myriad.Plugins 2.0 | ||||
|  | ||||
| This transition split the attributes (e.g. `[<JsonParseAttribute>]`) into their own assembly, WoofWare.Myriad.Plugins.Attributes. | ||||
| The new assembly has minimal dependencies, so you may safely use it from your own code. | ||||
|   | ||||
| @@ -129,24 +129,230 @@ module ToGetExtensionMethodJsonParseExtension = | ||||
|  | ||||
|         /// Parse from a JSON node. | ||||
|         static member jsonParse (node : System.Text.Json.Nodes.JsonNode) : ToGetExtensionMethod = | ||||
|             let Sailor = | ||||
|                 (match node.["sailor"] with | ||||
|             let Whiskey = System.Numerics.BigInteger.Parse (node.["whiskey"].ToJsonString ()) | ||||
|  | ||||
|             let Victor = | ||||
|                 (match node.["victor"] with | ||||
|                  | null -> | ||||
|                      raise ( | ||||
|                          System.Collections.Generic.KeyNotFoundException ( | ||||
|                              sprintf "Required key '%s' not found on JSON object" ("sailor") | ||||
|                              sprintf "Required key '%s' not found on JSON object" ("victor") | ||||
|                          ) | ||||
|                      ) | ||||
|                  | v -> v) | ||||
|                     .AsValue() | ||||
|                     .GetValue<System.Char> () | ||||
|  | ||||
|             let Uniform = | ||||
|                 (match node.["uniform"] with | ||||
|                  | null -> | ||||
|                      raise ( | ||||
|                          System.Collections.Generic.KeyNotFoundException ( | ||||
|                              sprintf "Required key '%s' not found on JSON object" ("uniform") | ||||
|                          ) | ||||
|                      ) | ||||
|                  | v -> v) | ||||
|                     .AsValue() | ||||
|                     .GetValue<System.Decimal> () | ||||
|  | ||||
|             let Tango = | ||||
|                 (match node.["tango"] with | ||||
|                  | null -> | ||||
|                      raise ( | ||||
|                          System.Collections.Generic.KeyNotFoundException ( | ||||
|                              sprintf "Required key '%s' not found on JSON object" ("tango") | ||||
|                          ) | ||||
|                      ) | ||||
|                  | v -> v) | ||||
|                     .AsValue() | ||||
|                     .GetValue<System.SByte> () | ||||
|  | ||||
|             let Quebec = | ||||
|                 (match node.["quebec"] with | ||||
|                  | null -> | ||||
|                      raise ( | ||||
|                          System.Collections.Generic.KeyNotFoundException ( | ||||
|                              sprintf "Required key '%s' not found on JSON object" ("quebec") | ||||
|                          ) | ||||
|                      ) | ||||
|                  | v -> v) | ||||
|                     .AsValue() | ||||
|                     .GetValue<System.Byte> () | ||||
|  | ||||
|             let Papa = | ||||
|                 (match node.["papa"] with | ||||
|                  | null -> | ||||
|                      raise ( | ||||
|                          System.Collections.Generic.KeyNotFoundException ( | ||||
|                              sprintf "Required key '%s' not found on JSON object" ("papa") | ||||
|                          ) | ||||
|                      ) | ||||
|                  | v -> v) | ||||
|                     .AsValue() | ||||
|                     .GetValue<System.Byte> () | ||||
|  | ||||
|             let Oscar = | ||||
|                 (match node.["oscar"] with | ||||
|                  | null -> | ||||
|                      raise ( | ||||
|                          System.Collections.Generic.KeyNotFoundException ( | ||||
|                              sprintf "Required key '%s' not found on JSON object" ("oscar") | ||||
|                          ) | ||||
|                      ) | ||||
|                  | v -> v) | ||||
|                     .AsValue() | ||||
|                     .GetValue<System.SByte> () | ||||
|  | ||||
|             let November = | ||||
|                 (match node.["november"] with | ||||
|                  | null -> | ||||
|                      raise ( | ||||
|                          System.Collections.Generic.KeyNotFoundException ( | ||||
|                              sprintf "Required key '%s' not found on JSON object" ("november") | ||||
|                          ) | ||||
|                      ) | ||||
|                  | v -> v) | ||||
|                     .AsValue() | ||||
|                     .GetValue<System.UInt16> () | ||||
|  | ||||
|             let Mike = | ||||
|                 (match node.["mike"] with | ||||
|                  | null -> | ||||
|                      raise ( | ||||
|                          System.Collections.Generic.KeyNotFoundException ( | ||||
|                              sprintf "Required key '%s' not found on JSON object" ("mike") | ||||
|                          ) | ||||
|                      ) | ||||
|                  | v -> v) | ||||
|                     .AsValue() | ||||
|                     .GetValue<System.Int16> () | ||||
|  | ||||
|             let Lima = | ||||
|                 (match node.["lima"] with | ||||
|                  | null -> | ||||
|                      raise ( | ||||
|                          System.Collections.Generic.KeyNotFoundException ( | ||||
|                              sprintf "Required key '%s' not found on JSON object" ("lima") | ||||
|                          ) | ||||
|                      ) | ||||
|                  | v -> v) | ||||
|                     .AsValue() | ||||
|                     .GetValue<System.UInt32> () | ||||
|  | ||||
|             let Kilo = | ||||
|                 (match node.["kilo"] with | ||||
|                  | null -> | ||||
|                      raise ( | ||||
|                          System.Collections.Generic.KeyNotFoundException ( | ||||
|                              sprintf "Required key '%s' not found on JSON object" ("kilo") | ||||
|                          ) | ||||
|                      ) | ||||
|                  | v -> v) | ||||
|                     .AsValue() | ||||
|                     .GetValue<System.Int32> () | ||||
|  | ||||
|             let Juliette = | ||||
|                 (match node.["juliette"] with | ||||
|                  | null -> | ||||
|                      raise ( | ||||
|                          System.Collections.Generic.KeyNotFoundException ( | ||||
|                              sprintf "Required key '%s' not found on JSON object" ("juliette") | ||||
|                          ) | ||||
|                      ) | ||||
|                  | v -> v) | ||||
|                     .AsValue() | ||||
|                     .GetValue<System.UInt32> () | ||||
|  | ||||
|             let India = | ||||
|                 (match node.["india"] with | ||||
|                  | null -> | ||||
|                      raise ( | ||||
|                          System.Collections.Generic.KeyNotFoundException ( | ||||
|                              sprintf "Required key '%s' not found on JSON object" ("india") | ||||
|                          ) | ||||
|                      ) | ||||
|                  | v -> v) | ||||
|                     .AsValue() | ||||
|                     .GetValue<int> () | ||||
|  | ||||
|             let Hotel = | ||||
|                 (match node.["hotel"] with | ||||
|                  | null -> | ||||
|                      raise ( | ||||
|                          System.Collections.Generic.KeyNotFoundException ( | ||||
|                              sprintf "Required key '%s' not found on JSON object" ("hotel") | ||||
|                          ) | ||||
|                      ) | ||||
|                  | v -> v) | ||||
|                     .AsValue() | ||||
|                     .GetValue<System.UInt64> () | ||||
|  | ||||
|             let Golf = | ||||
|                 (match node.["golf"] with | ||||
|                  | null -> | ||||
|                      raise ( | ||||
|                          System.Collections.Generic.KeyNotFoundException ( | ||||
|                              sprintf "Required key '%s' not found on JSON object" ("golf") | ||||
|                          ) | ||||
|                      ) | ||||
|                  | v -> v) | ||||
|                     .AsValue() | ||||
|                     .GetValue<System.Int64> () | ||||
|  | ||||
|             let Foxtrot = | ||||
|                 (match node.["foxtrot"] with | ||||
|                  | null -> | ||||
|                      raise ( | ||||
|                          System.Collections.Generic.KeyNotFoundException ( | ||||
|                              sprintf "Required key '%s' not found on JSON object" ("foxtrot") | ||||
|                          ) | ||||
|                      ) | ||||
|                  | v -> v) | ||||
|                     .AsValue() | ||||
|                     .GetValue<System.Double> () | ||||
|  | ||||
|             let Echo = | ||||
|                 (match node.["echo"] with | ||||
|                  | null -> | ||||
|                      raise ( | ||||
|                          System.Collections.Generic.KeyNotFoundException ( | ||||
|                              sprintf "Required key '%s' not found on JSON object" ("echo") | ||||
|                          ) | ||||
|                      ) | ||||
|                  | v -> v) | ||||
|                     .AsValue() | ||||
|                     .GetValue<System.Single> () | ||||
|  | ||||
|             let Delta = | ||||
|                 (match node.["delta"] with | ||||
|                  | null -> | ||||
|                      raise ( | ||||
|                          System.Collections.Generic.KeyNotFoundException ( | ||||
|                              sprintf "Required key '%s' not found on JSON object" ("delta") | ||||
|                          ) | ||||
|                      ) | ||||
|                  | v -> v) | ||||
|                     .AsValue() | ||||
|                     .GetValue<System.Single> () | ||||
|  | ||||
|             let Charlie = | ||||
|                 (match node.["charlie"] with | ||||
|                  | null -> | ||||
|                      raise ( | ||||
|                          System.Collections.Generic.KeyNotFoundException ( | ||||
|                              sprintf "Required key '%s' not found on JSON object" ("charlie") | ||||
|                          ) | ||||
|                      ) | ||||
|                  | v -> v) | ||||
|                     .AsValue() | ||||
|                     .GetValue<float> () | ||||
|  | ||||
|             let Soldier = | ||||
|                 (match node.["soldier"] with | ||||
|             let Bravo = | ||||
|                 (match node.["bravo"] with | ||||
|                  | null -> | ||||
|                      raise ( | ||||
|                          System.Collections.Generic.KeyNotFoundException ( | ||||
|                              sprintf "Required key '%s' not found on JSON object" ("soldier") | ||||
|                              sprintf "Required key '%s' not found on JSON object" ("bravo") | ||||
|                          ) | ||||
|                      ) | ||||
|                  | v -> v) | ||||
| @@ -154,24 +360,12 @@ module ToGetExtensionMethodJsonParseExtension = | ||||
|                     .GetValue<string> () | ||||
|                 |> System.Uri | ||||
|  | ||||
|             let Tailor = | ||||
|                 (match node.["tailor"] with | ||||
|             let Alpha = | ||||
|                 (match node.["alpha"] with | ||||
|                  | null -> | ||||
|                      raise ( | ||||
|                          System.Collections.Generic.KeyNotFoundException ( | ||||
|                              sprintf "Required key '%s' not found on JSON object" ("tailor") | ||||
|                          ) | ||||
|                      ) | ||||
|                  | v -> v) | ||||
|                     .AsValue() | ||||
|                     .GetValue<int> () | ||||
|  | ||||
|             let Tinker = | ||||
|                 (match node.["tinker"] with | ||||
|                  | null -> | ||||
|                      raise ( | ||||
|                          System.Collections.Generic.KeyNotFoundException ( | ||||
|                              sprintf "Required key '%s' not found on JSON object" ("tinker") | ||||
|                              sprintf "Required key '%s' not found on JSON object" ("alpha") | ||||
|                          ) | ||||
|                      ) | ||||
|                  | v -> v) | ||||
| @@ -179,8 +373,25 @@ module ToGetExtensionMethodJsonParseExtension = | ||||
|                     .GetValue<string> () | ||||
|  | ||||
|             { | ||||
|                 Tinker = Tinker | ||||
|                 Tailor = Tailor | ||||
|                 Soldier = Soldier | ||||
|                 Sailor = Sailor | ||||
|                 Alpha = Alpha | ||||
|                 Bravo = Bravo | ||||
|                 Charlie = Charlie | ||||
|                 Delta = Delta | ||||
|                 Echo = Echo | ||||
|                 Foxtrot = Foxtrot | ||||
|                 Golf = Golf | ||||
|                 Hotel = Hotel | ||||
|                 India = India | ||||
|                 Juliette = Juliette | ||||
|                 Kilo = Kilo | ||||
|                 Lima = Lima | ||||
|                 Mike = Mike | ||||
|                 November = November | ||||
|                 Oscar = Oscar | ||||
|                 Papa = Papa | ||||
|                 Quebec = Quebec | ||||
|                 Tango = Tango | ||||
|                 Uniform = Uniform | ||||
|                 Victor = Victor | ||||
|                 Whiskey = Whiskey | ||||
|             } | ||||
|   | ||||
| @@ -5,6 +5,7 @@ | ||||
|  | ||||
| namespace SomeNamespace | ||||
|  | ||||
| open System | ||||
| open WoofWare.Myriad.Plugins | ||||
|  | ||||
| /// Mock record type for an interface | ||||
| @@ -25,10 +26,11 @@ type internal PublicTypeMock = | ||||
|  | ||||
|     interface IPublicType 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.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 | ||||
| open WoofWare.Myriad.Plugins | ||||
|  | ||||
| /// Mock record type for an interface | ||||
| @@ -49,10 +51,11 @@ type public PublicTypeInternalFalseMock = | ||||
|  | ||||
|     interface IPublicTypeInternalFalse 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.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 | ||||
| open WoofWare.Myriad.Plugins | ||||
|  | ||||
| /// Mock record type for an interface | ||||
| @@ -71,9 +74,10 @@ type internal InternalTypeMock = | ||||
|  | ||||
|     interface InternalType 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.Mem2 arg_0_0 = this.Mem2 (arg_0_0) | ||||
| namespace SomeNamespace | ||||
|  | ||||
| open System | ||||
| open WoofWare.Myriad.Plugins | ||||
|  | ||||
| /// Mock record type for an interface | ||||
| @@ -92,9 +96,10 @@ type private PrivateTypeMock = | ||||
|  | ||||
|     interface PrivateType 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.Mem2 arg_0_0 = this.Mem2 (arg_0_0) | ||||
| namespace SomeNamespace | ||||
|  | ||||
| open System | ||||
| open WoofWare.Myriad.Plugins | ||||
|  | ||||
| /// Mock record type for an interface | ||||
| @@ -113,9 +118,10 @@ type private PrivateTypeInternalFalseMock = | ||||
|  | ||||
|     interface PrivateTypeInternalFalse 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.Mem2 arg_0_0 = this.Mem2 (arg_0_0) | ||||
| namespace SomeNamespace | ||||
|  | ||||
| open System | ||||
| open WoofWare.Myriad.Plugins | ||||
|  | ||||
| /// Mock record type for an interface | ||||
| @@ -131,9 +137,10 @@ type internal VeryPublicTypeMock<'a, 'b> = | ||||
|         } | ||||
|  | ||||
|     interface VeryPublicType<'a, 'b> with | ||||
|         member this.Mem1 (arg_0_0) = this.Mem1 (arg_0_0) | ||||
|         member this.Mem1 arg_0_0 = this.Mem1 (arg_0_0) | ||||
| namespace SomeNamespace | ||||
|  | ||||
| open System | ||||
| open WoofWare.Myriad.Plugins | ||||
|  | ||||
| /// Mock record type for an interface | ||||
| @@ -159,9 +166,9 @@ type internal CurriedMock<'a> = | ||||
|         } | ||||
|  | ||||
|     interface Curried<'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.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) | ||||
| @@ -171,3 +178,31 @@ type internal CurriedMock<'a> = | ||||
|  | ||||
|         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 | ||||
| open WoofWare.Myriad.Plugins | ||||
|  | ||||
| /// Mock record type for an interface | ||||
| type internal TypeWithInterfaceMock = | ||||
|     { | ||||
|         /// 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 : TypeWithInterfaceMock = | ||||
|         { | ||||
|             Dispose = (fun _ -> ()) | ||||
|             Mem1 = (fun x -> raise (System.NotImplementedException "Unimplemented mock function")) | ||||
|             Mem2 = (fun x -> raise (System.NotImplementedException "Unimplemented mock function")) | ||||
|         } | ||||
|  | ||||
|     interface TypeWithInterface 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 () | ||||
|   | ||||
| @@ -87,6 +87,40 @@ module PureGymApi = | ||||
|                 } | ||||
|                 |> (fun a -> Async.StartAsTask (a, ?cancellationToken = ct)) | ||||
|  | ||||
|             member _.GetGymAttendance' (gymId : int, ct : CancellationToken option) = | ||||
|                 async { | ||||
|                     let! ct = Async.CancellationToken | ||||
|  | ||||
|                     let uri = | ||||
|                         System.Uri ( | ||||
|                             (match client.BaseAddress with | ||||
|                              | null -> System.Uri "https://whatnot.com" | ||||
|                              | v -> v), | ||||
|                             System.Uri ( | ||||
|                                 "v1/gyms/{gym_id}/attendance" | ||||
|                                     .Replace ("{gym_id}", gymId.ToString () |> System.Web.HttpUtility.UrlEncode), | ||||
|                                 System.UriKind.Relative | ||||
|                             ) | ||||
|                         ) | ||||
|  | ||||
|                     let httpMessage = | ||||
|                         new System.Net.Http.HttpRequestMessage ( | ||||
|                             Method = System.Net.Http.HttpMethod.Get, | ||||
|                             RequestUri = uri | ||||
|                         ) | ||||
|  | ||||
|                     let! response = client.SendAsync (httpMessage, ct) |> Async.AwaitTask | ||||
|                     let response = response.EnsureSuccessStatusCode () | ||||
|                     let! responseStream = response.Content.ReadAsStreamAsync ct |> Async.AwaitTask | ||||
|  | ||||
|                     let! jsonNode = | ||||
|                         System.Text.Json.Nodes.JsonNode.ParseAsync (responseStream, cancellationToken = ct) | ||||
|                         |> Async.AwaitTask | ||||
|  | ||||
|                     return GymAttendance.jsonParse jsonNode | ||||
|                 } | ||||
|                 |> (fun a -> Async.StartAsTask (a, ?cancellationToken = ct)) | ||||
|  | ||||
|             member _.GetMember (ct : CancellationToken option) = | ||||
|                 async { | ||||
|                     let! ct = Async.CancellationToken | ||||
| @@ -288,7 +322,52 @@ module PureGymApi = | ||||
|                              | v -> v), | ||||
|                             System.Uri ( | ||||
|                                 ("/v2/gymSessions/member" | ||||
|                                  + "?fromDate=" | ||||
|                                  + (if "/v2/gymSessions/member".IndexOf (char 63) >= 0 then | ||||
|                                         "&" | ||||
|                                     else | ||||
|                                         "?") | ||||
|                                  + "fromDate=" | ||||
|                                  + ((fromDate.ToString "yyyy-MM-dd") |> System.Web.HttpUtility.UrlEncode) | ||||
|                                  + "&toDate=" | ||||
|                                  + ((toDate.ToString "yyyy-MM-dd") |> System.Web.HttpUtility.UrlEncode)), | ||||
|                                 System.UriKind.Relative | ||||
|                             ) | ||||
|                         ) | ||||
|  | ||||
|                     let httpMessage = | ||||
|                         new System.Net.Http.HttpRequestMessage ( | ||||
|                             Method = System.Net.Http.HttpMethod.Get, | ||||
|                             RequestUri = uri | ||||
|                         ) | ||||
|  | ||||
|                     let! response = client.SendAsync (httpMessage, ct) |> Async.AwaitTask | ||||
|                     let response = response.EnsureSuccessStatusCode () | ||||
|                     let! responseStream = response.Content.ReadAsStreamAsync ct |> Async.AwaitTask | ||||
|  | ||||
|                     let! jsonNode = | ||||
|                         System.Text.Json.Nodes.JsonNode.ParseAsync (responseStream, cancellationToken = ct) | ||||
|                         |> Async.AwaitTask | ||||
|  | ||||
|                     return Sessions.jsonParse jsonNode | ||||
|                 } | ||||
|                 |> (fun a -> Async.StartAsTask (a, ?cancellationToken = ct)) | ||||
|  | ||||
|             member _.GetSessionsWithQuery (fromDate : DateOnly, toDate : DateOnly, ct : CancellationToken option) = | ||||
|                 async { | ||||
|                     let! ct = Async.CancellationToken | ||||
|  | ||||
|                     let uri = | ||||
|                         System.Uri ( | ||||
|                             (match client.BaseAddress with | ||||
|                              | null -> System.Uri "https://whatnot.com" | ||||
|                              | v -> v), | ||||
|                             System.Uri ( | ||||
|                                 ("/v2/gymSessions/member?foo=1" | ||||
|                                  + (if "/v2/gymSessions/member?foo=1".IndexOf (char 63) >= 0 then | ||||
|                                         "&" | ||||
|                                     else | ||||
|                                         "?") | ||||
|                                  + "fromDate=" | ||||
|                                  + ((fromDate.ToString "yyyy-MM-dd") |> System.Web.HttpUtility.UrlEncode) | ||||
|                                  + "&toDate=" | ||||
|                                  + ((toDate.ToString "yyyy-MM-dd") |> System.Web.HttpUtility.UrlEncode)), | ||||
| @@ -1140,6 +1219,69 @@ module ApiWithHeaders = | ||||
|             member _.SomeHeader : string = someHeader () | ||||
|             member _.SomeOtherHeader : int = someOtherHeader () | ||||
|  | ||||
|             member this.GetPathParam (parameter : string, ct : CancellationToken option) = | ||||
|                 async { | ||||
|                     let! ct = Async.CancellationToken | ||||
|  | ||||
|                     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." | ||||
|                                      ) | ||||
|                                  ) | ||||
|                              | v -> v), | ||||
|                             System.Uri ( | ||||
|                                 "endpoint/{param}" | ||||
|                                     .Replace ("{param}", parameter.ToString () |> System.Web.HttpUtility.UrlEncode), | ||||
|                                 System.UriKind.Relative | ||||
|                             ) | ||||
|                         ) | ||||
|  | ||||
|                     let httpMessage = | ||||
|                         new System.Net.Http.HttpRequestMessage ( | ||||
|                             Method = System.Net.Http.HttpMethod.Get, | ||||
|                             RequestUri = uri | ||||
|                         ) | ||||
|  | ||||
|                     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") | ||||
|                     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 ApiWithHeaders2 = | ||||
|     /// Create a REST client. The input functions will be re-evaluated on every HTTP request to obtain the required values for the corresponding header properties. | ||||
|     let make | ||||
|         (someHeader : unit -> string) | ||||
|         (someOtherHeader : unit -> int) | ||||
|         (client : System.Net.Http.HttpClient) | ||||
|         : IApiWithHeaders2 | ||||
|         = | ||||
|         { new IApiWithHeaders2 with | ||||
|             member _.SomeHeader : string = someHeader () | ||||
|             member _.SomeOtherHeader : int = someOtherHeader () | ||||
|  | ||||
|             member this.GetPathParam (parameter : string, ct : CancellationToken option) = | ||||
|                 async { | ||||
|                     let! ct = Async.CancellationToken | ||||
|   | ||||
| @@ -210,7 +210,7 @@ module InnerTypeWithBothJsonParseExtension = | ||||
|  | ||||
|                     let value = | ||||
|                         (kvp.Value).AsArray () | ||||
|                         |> Seq.map (fun elt -> elt.AsValue().GetValue<char> ()) | ||||
|                         |> Seq.map (fun elt -> elt.AsValue().GetValue<System.Char> ()) | ||||
|                         |> List.ofSeq | ||||
|  | ||||
|                     key, value | ||||
|   | ||||
| @@ -543,3 +543,201 @@ module VaultClient = | ||||
|                 } | ||||
|                 |> (fun a -> Async.StartAsTask (a, ?cancellationToken = ct)) | ||||
|         } | ||||
| namespace ConsumePlugin | ||||
|  | ||||
| open System | ||||
| open System.Collections.Generic | ||||
| open System.Text.Json.Serialization | ||||
| open System.Threading | ||||
| open System.Threading.Tasks | ||||
| open RestEase | ||||
|  | ||||
| /// Module for constructing a REST client. | ||||
| [<CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix)>] | ||||
| [<RequireQualifiedAccess>] | ||||
| module VaultClientNonExtensionMethod = | ||||
|     /// Create a REST client. | ||||
|     let make (client : System.Net.Http.HttpClient) : IVaultClientNonExtensionMethod = | ||||
|         { new IVaultClientNonExtensionMethod with | ||||
|             member _.GetSecret | ||||
|                 (jwt : JwtVaultResponse, path : string, mountPoint : string, ct : CancellationToken option) | ||||
|                 = | ||||
|                 async { | ||||
|                     let! ct = Async.CancellationToken | ||||
|  | ||||
|                     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." | ||||
|                                      ) | ||||
|                                  ) | ||||
|                              | v -> v), | ||||
|                             System.Uri ( | ||||
|                                 "v1/{mountPoint}/{path}" | ||||
|                                     .Replace("{path}", path.ToString () |> System.Web.HttpUtility.UrlEncode) | ||||
|                                     .Replace ( | ||||
|                                         "{mountPoint}", | ||||
|                                         mountPoint.ToString () |> System.Web.HttpUtility.UrlEncode | ||||
|                                     ), | ||||
|                                 System.UriKind.Relative | ||||
|                             ) | ||||
|                         ) | ||||
|  | ||||
|                     let httpMessage = | ||||
|                         new System.Net.Http.HttpRequestMessage ( | ||||
|                             Method = System.Net.Http.HttpMethod.Get, | ||||
|                             RequestUri = uri | ||||
|                         ) | ||||
|  | ||||
|                     let! response = client.SendAsync (httpMessage, ct) |> Async.AwaitTask | ||||
|                     let response = response.EnsureSuccessStatusCode () | ||||
|                     let! responseStream = response.Content.ReadAsStreamAsync ct |> Async.AwaitTask | ||||
|  | ||||
|                     let! jsonNode = | ||||
|                         System.Text.Json.Nodes.JsonNode.ParseAsync (responseStream, cancellationToken = ct) | ||||
|                         |> Async.AwaitTask | ||||
|  | ||||
|                     return JwtSecretResponse.jsonParse jsonNode | ||||
|                 } | ||||
|                 |> (fun a -> Async.StartAsTask (a, ?cancellationToken = ct)) | ||||
|  | ||||
|             member _.GetJwt (role : string, jwt : string, ct : CancellationToken option) = | ||||
|                 async { | ||||
|                     let! ct = Async.CancellationToken | ||||
|  | ||||
|                     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." | ||||
|                                      ) | ||||
|                                  ) | ||||
|                              | v -> v), | ||||
|                             System.Uri ("v1/auth/jwt/login", 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! responseStream = response.Content.ReadAsStreamAsync ct |> Async.AwaitTask | ||||
|  | ||||
|                     let! jsonNode = | ||||
|                         System.Text.Json.Nodes.JsonNode.ParseAsync (responseStream, cancellationToken = ct) | ||||
|                         |> Async.AwaitTask | ||||
|  | ||||
|                     return JwtVaultResponse.jsonParse jsonNode | ||||
|                 } | ||||
|                 |> (fun a -> Async.StartAsTask (a, ?cancellationToken = ct)) | ||||
|         } | ||||
| namespace ConsumePlugin | ||||
|  | ||||
| open System | ||||
| open System.Collections.Generic | ||||
| open System.Text.Json.Serialization | ||||
| open System.Threading | ||||
| open System.Threading.Tasks | ||||
| open RestEase | ||||
|  | ||||
| /// Extension methods for constructing a REST client. | ||||
| [<AutoOpen>] | ||||
| module VaultClientExtensionMethodHttpClientExtension = | ||||
|     /// Extension methods for HTTP clients | ||||
|     type VaultClientExtensionMethod with | ||||
|  | ||||
|         /// Create a REST client. | ||||
|         static member make (client : System.Net.Http.HttpClient) : IVaultClientExtensionMethod = | ||||
|             { new IVaultClientExtensionMethod with | ||||
|                 member _.GetSecret | ||||
|                     (jwt : JwtVaultResponse, path : string, mountPoint : string, ct : CancellationToken option) | ||||
|                     = | ||||
|                     async { | ||||
|                         let! ct = Async.CancellationToken | ||||
|  | ||||
|                         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." | ||||
|                                          ) | ||||
|                                      ) | ||||
|                                  | v -> v), | ||||
|                                 System.Uri ( | ||||
|                                     "v1/{mountPoint}/{path}" | ||||
|                                         .Replace("{path}", path.ToString () |> System.Web.HttpUtility.UrlEncode) | ||||
|                                         .Replace ( | ||||
|                                             "{mountPoint}", | ||||
|                                             mountPoint.ToString () |> System.Web.HttpUtility.UrlEncode | ||||
|                                         ), | ||||
|                                     System.UriKind.Relative | ||||
|                                 ) | ||||
|                             ) | ||||
|  | ||||
|                         let httpMessage = | ||||
|                             new System.Net.Http.HttpRequestMessage ( | ||||
|                                 Method = System.Net.Http.HttpMethod.Get, | ||||
|                                 RequestUri = uri | ||||
|                             ) | ||||
|  | ||||
|                         let! response = client.SendAsync (httpMessage, ct) |> Async.AwaitTask | ||||
|                         let response = response.EnsureSuccessStatusCode () | ||||
|                         let! responseStream = response.Content.ReadAsStreamAsync ct |> Async.AwaitTask | ||||
|  | ||||
|                         let! jsonNode = | ||||
|                             System.Text.Json.Nodes.JsonNode.ParseAsync (responseStream, cancellationToken = ct) | ||||
|                             |> Async.AwaitTask | ||||
|  | ||||
|                         return JwtSecretResponse.jsonParse jsonNode | ||||
|                     } | ||||
|                     |> (fun a -> Async.StartAsTask (a, ?cancellationToken = ct)) | ||||
|  | ||||
|                 member _.GetJwt (role : string, jwt : string, ct : CancellationToken option) = | ||||
|                     async { | ||||
|                         let! ct = Async.CancellationToken | ||||
|  | ||||
|                         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." | ||||
|                                          ) | ||||
|                                      ) | ||||
|                                  | v -> v), | ||||
|                                 System.Uri ("v1/auth/jwt/login", 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! responseStream = response.Content.ReadAsStreamAsync ct |> Async.AwaitTask | ||||
|  | ||||
|                         let! jsonNode = | ||||
|                             System.Text.Json.Nodes.JsonNode.ParseAsync (responseStream, cancellationToken = ct) | ||||
|                             |> Async.AwaitTask | ||||
|  | ||||
|                         return JwtVaultResponse.jsonParse jsonNode | ||||
|                     } | ||||
|                     |> (fun a -> Async.StartAsTask (a, ?cancellationToken = ct)) | ||||
|             } | ||||
|   | ||||
| @@ -32,10 +32,27 @@ type JsonRecordType = | ||||
| [<WoofWare.Myriad.Plugins.JsonParse true>] | ||||
| type ToGetExtensionMethod = | ||||
|     { | ||||
|         Tinker : string | ||||
|         Tailor : int | ||||
|         Soldier : System.Uri | ||||
|         Sailor : float | ||||
|         Alpha : string | ||||
|         Bravo : System.Uri | ||||
|         Charlie : float | ||||
|         Delta : float32 | ||||
|         Echo : single | ||||
|         Foxtrot : double | ||||
|         Golf : int64 | ||||
|         Hotel : uint64 | ||||
|         India : int | ||||
|         Juliette : uint | ||||
|         Kilo : int32 | ||||
|         Lima : uint32 | ||||
|         Mike : int16 | ||||
|         November : uint16 | ||||
|         Oscar : int8 | ||||
|         Papa : uint8 | ||||
|         Quebec : byte | ||||
|         Tango : sbyte | ||||
|         Uniform : decimal | ||||
|         Victor : char | ||||
|         Whiskey : bigint | ||||
|     } | ||||
|  | ||||
| [<RequireQualifiedAccess>] | ||||
|   | ||||
| @@ -1,5 +1,6 @@ | ||||
| namespace SomeNamespace | ||||
|  | ||||
| open System | ||||
| open WoofWare.Myriad.Plugins | ||||
|  | ||||
| [<GenerateMock>] | ||||
| @@ -41,3 +42,9 @@ type Curried<'a> = | ||||
|     abstract Mem4 : (int * string) -> ('a * int) -> string | ||||
|     abstract Mem5 : x : int * string -> ('a * int) -> string | ||||
|     abstract Mem6 : int * string -> y : 'a * int -> string | ||||
|  | ||||
| [<GenerateMock>] | ||||
| type TypeWithInterface = | ||||
|     inherit IDisposable | ||||
|     abstract Mem1 : string option -> string[] Async | ||||
|     abstract Mem2 : unit -> string[] Async | ||||
|   | ||||
| @@ -17,6 +17,9 @@ type IPureGymApi = | ||||
|     [<Get "v1/gyms/{gym_id}/attendance">] | ||||
|     abstract GetGymAttendance : [<Path "gym_id">] gymId : int * ?ct : CancellationToken -> Task<GymAttendance> | ||||
|  | ||||
|     [<Get "v1/gyms/{gym_id}/attendance">] | ||||
|     abstract GetGymAttendance' : [<Path("gym_id")>] gymId : int * ?ct : CancellationToken -> Task<GymAttendance> | ||||
|  | ||||
|     [<RestEase.GetAttribute "v1/member">] | ||||
|     abstract GetMember : ?ct : CancellationToken -> Member Task | ||||
|  | ||||
| @@ -38,6 +41,10 @@ type IPureGymApi = | ||||
|     abstract GetSessions : | ||||
|         [<Query>] fromDate : DateOnly * [<Query>] toDate : DateOnly * ?ct : CancellationToken -> Task<Sessions> | ||||
|  | ||||
|     [<Get "/v2/gymSessions/member?foo=1">] | ||||
|     abstract GetSessionsWithQuery : | ||||
|         [<Query>] fromDate : DateOnly * [<Query>] toDate : DateOnly * ?ct : CancellationToken -> Task<Sessions> | ||||
|  | ||||
|     // An example from RestEase's own docs | ||||
|     [<Post "users/new">] | ||||
|     abstract CreateUserString : [<Body>] user : string * ?ct : CancellationToken -> Task<string> | ||||
| @@ -120,7 +127,8 @@ type internal IApiWithoutBaseAddress = | ||||
| [<WoofWare.Myriad.Plugins.HttpClient>] | ||||
| [<BasePath "foo">] | ||||
| type IApiWithBasePath = | ||||
|     [<Get "endpoint/{param}">] | ||||
|     // 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>] | ||||
| @@ -141,3 +149,16 @@ type IApiWithHeaders = | ||||
|  | ||||
|     [<Get "endpoint/{param}">] | ||||
|     abstract GetPathParam : [<Path "param">] parameter : string * ?ct : CancellationToken -> Task<string> | ||||
|  | ||||
| [<WoofWare.Myriad.Plugins.HttpClient>] | ||||
| [<WoofWare.Myriad.Plugins.RestEase.Header("Header-Name", "Header-Value")>] | ||||
| type IApiWithHeaders2 = | ||||
|     [<WoofWare.Myriad.Plugins.RestEase.Header "X-Foo">] | ||||
|     abstract SomeHeader : string | ||||
|  | ||||
|     [<WoofWare.Myriad.Plugins.RestEase.Header "Authorization">] | ||||
|     abstract SomeOtherHeader : int | ||||
|  | ||||
|     [<Get "endpoint/{param}">] | ||||
|     abstract GetPathParam : | ||||
|         [<WoofWare.Myriad.Plugins.RestEase.Path "param">] parameter : string * ?ct : CancellationToken -> Task<string> | ||||
|   | ||||
| @@ -76,3 +76,33 @@ type IVaultClient = | ||||
|  | ||||
|     [<Get "v1/auth/jwt/login">] | ||||
|     abstract GetJwt : role : string * jwt : string * ?ct : CancellationToken -> Task<JwtVaultResponse> | ||||
|  | ||||
| [<WoofWare.Myriad.Plugins.HttpClient false>] | ||||
| type IVaultClientNonExtensionMethod = | ||||
|     [<Get "v1/{mountPoint}/{path}">] | ||||
|     abstract GetSecret : | ||||
|         jwt : JwtVaultResponse * | ||||
|         [<Path "path">] path : string * | ||||
|         [<Path "mountPoint">] mountPoint : string * | ||||
|         ?ct : CancellationToken -> | ||||
|             Task<JwtSecretResponse> | ||||
|  | ||||
|     [<Get "v1/auth/jwt/login">] | ||||
|     abstract GetJwt : role : string * jwt : string * ?ct : CancellationToken -> Task<JwtVaultResponse> | ||||
|  | ||||
| [<WoofWare.Myriad.Plugins.HttpClient(true)>] | ||||
| type IVaultClientExtensionMethod = | ||||
|     [<Get "v1/{mountPoint}/{path}">] | ||||
|     abstract GetSecret : | ||||
|         jwt : JwtVaultResponse * | ||||
|         [<Path "path">] path : string * | ||||
|         [<Path "mountPoint">] mountPoint : string * | ||||
|         ?ct : CancellationToken -> | ||||
|             Task<JwtSecretResponse> | ||||
|  | ||||
|     [<Get "v1/auth/jwt/login">] | ||||
|     abstract GetJwt : role : string * jwt : string * ?ct : CancellationToken -> Task<JwtVaultResponse> | ||||
|  | ||||
| [<RequireQualifiedAccess>] | ||||
| type VaultClientExtensionMethod = | ||||
|     static member thisClashes = 99 | ||||
|   | ||||
| @@ -60,8 +60,17 @@ type JsonParseAttribute (isExtensionMethod : bool) = | ||||
| /// generator should apply during build. | ||||
| /// This generator is intended to replicate much of the functionality of RestEase, | ||||
| /// i.e. to stamp out HTTP REST clients from interfaces defining the API. | ||||
| type HttpClientAttribute () = | ||||
| /// | ||||
| /// 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 HttpClientAttribute (isExtensionMethod : bool) = | ||||
|     inherit Attribute () | ||||
|     /// The default value of `isExtensionMethod`, the optional argument to the HttpClientAttribute constructor. | ||||
|     static member DefaultIsExtensionMethod = false | ||||
|  | ||||
|     /// Shorthand for the "isExtensionMethod = false" constructor; see documentation there for details. | ||||
|     new () = HttpClientAttribute HttpClientAttribute.DefaultIsExtensionMethod | ||||
|  | ||||
| /// Attribute indicating a DU type to which the "create catamorphism" Myriad | ||||
| /// generator should apply during build. | ||||
|   | ||||
							
								
								
									
										63
									
								
								WoofWare.Myriad.Plugins.Attributes/RestEase.fs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										63
									
								
								WoofWare.Myriad.Plugins.Attributes/RestEase.fs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,63 @@ | ||||
| namespace WoofWare.Myriad.Plugins | ||||
|  | ||||
| open System | ||||
|  | ||||
| /// Module containing duplicates of the supported RestEase attributes, in case you don't want | ||||
| /// to take a dependency on RestEase. | ||||
| [<RequireQualifiedAccess>] | ||||
| module RestEase = | ||||
|     /// Indicates that a method represents an HTTP Get query to the specified endpoint. | ||||
|     type GetAttribute (path : string) = | ||||
|         inherit Attribute () | ||||
|  | ||||
|     /// Indicates that a method represents an HTTP Post query to the specified endpoint. | ||||
|     type PostAttribute (path : string) = | ||||
|         inherit Attribute () | ||||
|  | ||||
|     /// Indicates that a method represents an HTTP Delete query to the specified endpoint. | ||||
|     type DeleteAttribute (path : string) = | ||||
|         inherit Attribute () | ||||
|  | ||||
|     /// Indicates that a method represents an HTTP Head query to the specified endpoint. | ||||
|     type HeadAttribute (path : string) = | ||||
|         inherit Attribute () | ||||
|  | ||||
|     /// Indicates that a method represents an HTTP Options query to the specified endpoint. | ||||
|     type OptionsAttribute (path : string) = | ||||
|         inherit Attribute () | ||||
|  | ||||
|     /// Indicates that a method represents an HTTP Put query to the specified endpoint. | ||||
|     type PutAttribute (path : string) = | ||||
|         inherit Attribute () | ||||
|  | ||||
|     /// Indicates that a method represents an HTTP Patch query to the specified endpoint. | ||||
|     type PatchAttribute (path : string) = | ||||
|         inherit Attribute () | ||||
|  | ||||
|     /// Indicates that a method represents an HTTP Trace query to the specified endpoint. | ||||
|     type TraceAttribute (path : string) = | ||||
|         inherit Attribute () | ||||
|  | ||||
|     /// Indicates that this argument to a method is interpolated into the HTTP request at runtime | ||||
|     /// by setting a query parameter (with the given name) to the value of the annotated argument. | ||||
|     type QueryAttribute (paramName : string) = | ||||
|         inherit Attribute () | ||||
|  | ||||
|     /// Indicates that this interface represents a REST client which accesses an API whose paths are | ||||
|     /// all relative to the given address. | ||||
|     type BaseAddressAttribute (addr : string) = | ||||
|         inherit Attribute () | ||||
|  | ||||
|     /// Indicates that this interface member causes the interface to set a header with the given name, | ||||
|     /// whose value is obtained whenever required by a fresh call to the interface member. | ||||
|     type HeaderAttribute (header : string, value : string option) = | ||||
|         inherit Attribute () | ||||
|         new (header : string) = HeaderAttribute (header, None) | ||||
|         new (header : string, value : string) = HeaderAttribute (header, Some value) | ||||
|  | ||||
|     /// Indicates that this argument to a method is interpolated into the request path at runtime | ||||
|     /// by writing it into the templated string that specifies the HTTP query e.g. in the `[<Get "/foo/{template}">]`. | ||||
|     type PathAttribute (path : string option) = | ||||
|         inherit Attribute () | ||||
|         new (path : string) = PathAttribute (Some path) | ||||
|         new () = PathAttribute None | ||||
| @@ -6,7 +6,10 @@ WoofWare.Myriad.Plugins.GenerateMockAttribute..ctor [constructor]: unit | ||||
| WoofWare.Myriad.Plugins.GenerateMockAttribute.DefaultIsInternal [static property]: [read-only] bool | ||||
| WoofWare.Myriad.Plugins.GenerateMockAttribute.get_DefaultIsInternal [static method]: unit -> bool | ||||
| WoofWare.Myriad.Plugins.HttpClientAttribute inherit System.Attribute | ||||
| 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.JsonParseAttribute inherit System.Attribute | ||||
| WoofWare.Myriad.Plugins.JsonParseAttribute..ctor [constructor]: bool | ||||
| WoofWare.Myriad.Plugins.JsonParseAttribute..ctor [constructor]: unit | ||||
| @@ -18,4 +21,33 @@ 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.RemoveOptionsAttribute inherit System.Attribute | ||||
| WoofWare.Myriad.Plugins.RemoveOptionsAttribute..ctor [constructor]: unit | ||||
| WoofWare.Myriad.Plugins.RemoveOptionsAttribute..ctor [constructor]: unit | ||||
| WoofWare.Myriad.Plugins.RestEase inherit obj | ||||
| WoofWare.Myriad.Plugins.RestEase+BaseAddressAttribute inherit System.Attribute | ||||
| WoofWare.Myriad.Plugins.RestEase+BaseAddressAttribute..ctor [constructor]: string | ||||
| WoofWare.Myriad.Plugins.RestEase+DeleteAttribute inherit System.Attribute | ||||
| WoofWare.Myriad.Plugins.RestEase+DeleteAttribute..ctor [constructor]: string | ||||
| WoofWare.Myriad.Plugins.RestEase+GetAttribute inherit System.Attribute | ||||
| WoofWare.Myriad.Plugins.RestEase+GetAttribute..ctor [constructor]: string | ||||
| WoofWare.Myriad.Plugins.RestEase+HeadAttribute inherit System.Attribute | ||||
| WoofWare.Myriad.Plugins.RestEase+HeadAttribute..ctor [constructor]: string | ||||
| WoofWare.Myriad.Plugins.RestEase+HeaderAttribute inherit System.Attribute | ||||
| WoofWare.Myriad.Plugins.RestEase+HeaderAttribute..ctor [constructor]: (string, string option) | ||||
| WoofWare.Myriad.Plugins.RestEase+HeaderAttribute..ctor [constructor]: (string, string) | ||||
| WoofWare.Myriad.Plugins.RestEase+HeaderAttribute..ctor [constructor]: string | ||||
| WoofWare.Myriad.Plugins.RestEase+OptionsAttribute inherit System.Attribute | ||||
| WoofWare.Myriad.Plugins.RestEase+OptionsAttribute..ctor [constructor]: string | ||||
| WoofWare.Myriad.Plugins.RestEase+PatchAttribute inherit System.Attribute | ||||
| WoofWare.Myriad.Plugins.RestEase+PatchAttribute..ctor [constructor]: string | ||||
| WoofWare.Myriad.Plugins.RestEase+PathAttribute inherit System.Attribute | ||||
| WoofWare.Myriad.Plugins.RestEase+PathAttribute..ctor [constructor]: string | ||||
| WoofWare.Myriad.Plugins.RestEase+PathAttribute..ctor [constructor]: string option | ||||
| WoofWare.Myriad.Plugins.RestEase+PathAttribute..ctor [constructor]: unit | ||||
| WoofWare.Myriad.Plugins.RestEase+PostAttribute inherit System.Attribute | ||||
| WoofWare.Myriad.Plugins.RestEase+PostAttribute..ctor [constructor]: string | ||||
| WoofWare.Myriad.Plugins.RestEase+PutAttribute inherit System.Attribute | ||||
| WoofWare.Myriad.Plugins.RestEase+PutAttribute..ctor [constructor]: string | ||||
| WoofWare.Myriad.Plugins.RestEase+QueryAttribute inherit System.Attribute | ||||
| WoofWare.Myriad.Plugins.RestEase+QueryAttribute..ctor [constructor]: string | ||||
| WoofWare.Myriad.Plugins.RestEase+TraceAttribute inherit System.Attribute | ||||
| WoofWare.Myriad.Plugins.RestEase+TraceAttribute..ctor [constructor]: string | ||||
| @@ -11,11 +11,9 @@ module TestSurface = | ||||
|     [<Test>] | ||||
|     let ``Ensure API surface has not been modified`` () = ApiSurface.assertIdentical assembly | ||||
|  | ||||
|     (* | ||||
|     [<Test>] | ||||
|     let ``Check version against remote`` () = | ||||
|         MonotonicVersion.validate assembly "WoofWare.Myriad.Plugins.Attributes" | ||||
|     *) | ||||
|  | ||||
|     [<Test ; Explicit>] | ||||
|     let ``Update API surface`` () = | ||||
|   | ||||
| @@ -12,9 +12,9 @@ | ||||
|     </ItemGroup> | ||||
|  | ||||
|     <ItemGroup> | ||||
|         <PackageReference Include="ApiSurface" Version="4.0.30" /> | ||||
|         <PackageReference Include="ApiSurface" Version="4.0.39" /> | ||||
|         <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.9.0"/> | ||||
|         <PackageReference Include="NUnit" Version="3.13.3"/> | ||||
|         <PackageReference Include="NUnit" Version="4.1.0"/> | ||||
|         <PackageReference Include="NUnit3TestAdapter" Version="4.5.0"/> | ||||
|     </ItemGroup> | ||||
|  | ||||
|   | ||||
| @@ -19,6 +19,7 @@ | ||||
|  | ||||
|   <ItemGroup> | ||||
|     <Compile Include="Attributes.fs"/> | ||||
|     <Compile Include="RestEase.fs" /> | ||||
|     <EmbeddedResource Include="version.json"/> | ||||
|     <EmbeddedResource Include="SurfaceBaseline.txt"/> | ||||
|     <None Include="..\README.md"> | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| { | ||||
|   "version": "2.2", | ||||
|   "version": "3.1", | ||||
|   "publicReleaseRefSpec": [ | ||||
|     "^refs/heads/main$" | ||||
|   ], | ||||
|   "pathFilters": null | ||||
| } | ||||
| } | ||||
| @@ -89,6 +89,7 @@ module TestPureGymRestApi = | ||||
|         let api = PureGymApi.make client | ||||
|  | ||||
|         api.GetGymAttendance(requestedGym).Result |> shouldEqual expected | ||||
|         api.GetGymAttendance'(requestedGym).Result |> shouldEqual expected | ||||
|  | ||||
|     let memberCases = | ||||
|         PureGymDtos.memberCases |> List.allPairs baseUris |> List.map TestCaseData | ||||
| @@ -234,6 +235,33 @@ module TestPureGymRestApi = | ||||
|  | ||||
|         api.GetSessions(startDate, endDate).Result |> shouldEqual expected | ||||
|  | ||||
|     [<TestCaseSource(nameof sessionsCases)>] | ||||
|     let ``Test GetSessionsWithQuery`` | ||||
|         (baseUri : Uri, (startDate : DateOnly, (endDate : DateOnly, (json : string, expected : Sessions)))) | ||||
|         = | ||||
|         let proc (message : HttpRequestMessage) : HttpResponseMessage Async = | ||||
|             async { | ||||
|                 message.Method |> shouldEqual HttpMethod.Get | ||||
|  | ||||
|                 // This one is specified as being absolute, in its attribute on the IPureGymApi type | ||||
|                 let expectedUri = | ||||
|                     let fromDate = dateOnlyToString startDate | ||||
|                     let toDate = dateOnlyToString endDate | ||||
|                     $"https://example.com/v2/gymSessions/member?foo=1&fromDate=%s{fromDate}&toDate=%s{toDate}" | ||||
|  | ||||
|                 message.RequestUri.ToString () |> shouldEqual expectedUri | ||||
|  | ||||
|                 let content = new StringContent (json) | ||||
|                 let resp = new HttpResponseMessage (HttpStatusCode.OK) | ||||
|                 resp.Content <- content | ||||
|                 return resp | ||||
|             } | ||||
|  | ||||
|         use client = HttpClientMock.make baseUri proc | ||||
|         let api = PureGymApi.make client | ||||
|  | ||||
|         api.GetSessionsWithQuery(startDate, endDate).Result |> shouldEqual expected | ||||
|  | ||||
|     [<Test>] | ||||
|     let ``URI example`` () = | ||||
|         let proc (message : HttpRequestMessage) : HttpResponseMessage Async = | ||||
|   | ||||
| @@ -87,8 +87,10 @@ module TestVaultClient = | ||||
|     } | ||||
| }""" | ||||
|  | ||||
|     [<Test>] | ||||
|     let ``URI example`` () = | ||||
|     [<TestCase 1>] | ||||
|     [<TestCase 2>] | ||||
|     [<TestCase 3>] | ||||
|     let ``URI example`` (vaultClientId : int) = | ||||
|         let proc (message : HttpRequestMessage) : HttpResponseMessage Async = | ||||
|             async { | ||||
|                 message.Method |> shouldEqual HttpMethod.Get | ||||
| @@ -112,10 +114,25 @@ module TestVaultClient = | ||||
|             } | ||||
|  | ||||
|         use client = HttpClientMock.make (Uri "https://my-vault.com") proc | ||||
|         let api = VaultClient.make client | ||||
|  | ||||
|         let vaultResponse = api.GetJwt("role", "jwt").Result | ||||
|         let value = api.GetSecret(vaultResponse, "path", "mount").Result | ||||
|         let value = | ||||
|             match vaultClientId with | ||||
|             | 1 -> | ||||
|                 let api = VaultClient.make client | ||||
|                 let vaultResponse = api.GetJwt("role", "jwt").Result | ||||
|                 let value = api.GetSecret(vaultResponse, "path", "mount").Result | ||||
|                 value | ||||
|             | 2 -> | ||||
|                 let api = VaultClientNonExtensionMethod.make client | ||||
|                 let vaultResponse = api.GetJwt("role", "jwt").Result | ||||
|                 let value = api.GetSecret(vaultResponse, "path", "mount").Result | ||||
|                 value | ||||
|             | 3 -> | ||||
|                 let api = VaultClientExtensionMethod.make client | ||||
|                 let vaultResponse = api.GetJwt("role", "jwt").Result | ||||
|                 let value = api.GetSecret(vaultResponse, "path", "mount").Result | ||||
|                 value | ||||
|             | _ -> failwith $"Unrecognised ID: %i{vaultClientId}" | ||||
|  | ||||
|         value.Data | ||||
|         |> Seq.toList | ||||
| @@ -168,3 +185,5 @@ module TestVaultClient = | ||||
|                 "key8_1", "https://example.com/data8/1" | ||||
|                 "key8_2", "https://example.com/data8/2" | ||||
|             ] | ||||
|  | ||||
|     let _canSeePastExtensionMethod = VaultClientExtensionMethod.thisClashes | ||||
|   | ||||
| @@ -1,6 +1,7 @@ | ||||
| namespace WoofWare.Myriad.Plugins.Test | ||||
|  | ||||
| open System | ||||
| open System.Numerics | ||||
| open System.Text.Json.Nodes | ||||
| open ConsumePlugin | ||||
| open NUnit.Framework | ||||
| @@ -12,15 +13,62 @@ module TestExtensionMethod = | ||||
|     [<Test>] | ||||
|     let ``Parse via extension method`` () = | ||||
|         let json = | ||||
|             """{"tinker": "job", "tailor": 3, "soldier": "https://example.com", "sailor": 3.1}""" | ||||
|             """{ | ||||
|     "alpha": "hello!", | ||||
|     "bravo": "https://example.com", | ||||
|     "charlie": 0.3341, | ||||
|     "delta": 110033.4, | ||||
|     "echo": -0.000993, | ||||
|     "foxtrot": -999999999999, | ||||
|     "golf": -123456789101112, | ||||
|     "hotel": 18446744073709551615, | ||||
|     "india": 99884, | ||||
|     "juliette": 12223334, | ||||
|     "kilo": -2147483642, | ||||
|     "lima": 4294967293, | ||||
|     "mike": -32767, | ||||
|     "november": 65533, | ||||
|     "oscar": -125, | ||||
|     "papa": 253, | ||||
|     "quebec": 254, | ||||
|     "tango": -3, | ||||
|     "uniform": 1004443.300988393349583009, | ||||
|     "victor": "x", | ||||
|     "whiskey": 123456123456123456123456123456123456123456 | ||||
| }""" | ||||
|             |> JsonNode.Parse | ||||
|  | ||||
|         let expected = | ||||
|             { | ||||
|                 Tinker = "job" | ||||
|                 Tailor = 3 | ||||
|                 Soldier = Uri "https://example.com" | ||||
|                 Sailor = 3.1 | ||||
|                 Alpha = "hello!" | ||||
|                 Bravo = Uri "https://example.com" | ||||
|                 Charlie = 0.3341 | ||||
|                 Delta = 110033.4f | ||||
|                 Echo = -0.000993f | ||||
|                 Foxtrot = -999999999999.0 | ||||
|                 Golf = -123456789101112L | ||||
|                 Hotel = 18446744073709551615UL | ||||
|                 India = 99884 | ||||
|                 Juliette = 12223334u | ||||
|                 Kilo = -2147483642 | ||||
|                 Lima = 4294967293u | ||||
|                 Mike = -32767s | ||||
|                 November = 65533us | ||||
|                 Oscar = -125y | ||||
|                 Papa = 253uy | ||||
|                 Quebec = 254uy | ||||
|                 Tango = -3y | ||||
|                 Uniform = 1004443.300988393349583009m | ||||
|                 Victor = 'x' | ||||
|                 Whiskey = | ||||
|                     let mutable i = BigInteger 0 | ||||
|  | ||||
|                     for _ = 0 to 6 do | ||||
|                         i <- i * BigInteger 1000000 + BigInteger 123456 | ||||
|  | ||||
|                     i | ||||
|             } | ||||
|  | ||||
|         ToGetExtensionMethod.jsonParse json |> shouldEqual expected | ||||
|         let actual = ToGetExtensionMethod.jsonParse json | ||||
|  | ||||
|         actual |> shouldEqual expected | ||||
|   | ||||
| @@ -7,6 +7,8 @@ open FsUnitTyped | ||||
|  | ||||
| [<TestFixture>] | ||||
| module TestJsonParse = | ||||
|     let _canSeePastExtensionMethod = ToGetExtensionMethod.thisModuleWouldClash | ||||
|  | ||||
|     [<Test>] | ||||
|     let ``Single example`` () = | ||||
|         let s = | ||||
|   | ||||
| @@ -33,13 +33,12 @@ | ||||
|   </ItemGroup> | ||||
|  | ||||
|   <ItemGroup> | ||||
|     <PackageReference Include="ApiSurface" Version="4.0.30"/> | ||||
|     <PackageReference Include="ApiSurface" Version="4.0.39"/> | ||||
|     <PackageReference Include="FsCheck" Version="2.16.6"/> | ||||
|     <PackageReference Include="FsUnit" Version="6.0.0"/> | ||||
|     <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.9.0"/> | ||||
|     <PackageReference Include="NUnit" Version="4.0.1"/> | ||||
|     <PackageReference Include="NUnit" Version="4.1.0"/> | ||||
|     <PackageReference Include="NUnit3TestAdapter" Version="4.5.0"/> | ||||
|     <PackageReference Include="coverlet.collector" Version="6.0.0"/> | ||||
|   </ItemGroup> | ||||
|  | ||||
|   <ItemGroup> | ||||
|   | ||||
| @@ -54,6 +54,7 @@ type internal InterfaceType = | ||||
|     { | ||||
|         Attributes : SynAttribute list | ||||
|         Name : LongIdent | ||||
|         Inherits : SynType list | ||||
|         Members : MemberInfo list | ||||
|         Properties : PropertyInfo list | ||||
|         Generics : SynTyparDecls option | ||||
| @@ -97,6 +98,30 @@ type internal AdtProduct = | ||||
| [<RequireQualifiedAccess>] | ||||
| module internal AstHelper = | ||||
|  | ||||
|     /// Given e.g. "byte", returns "System.Byte". | ||||
|     let qualifyPrimitiveType (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 Ident.Create) | ||||
|  | ||||
|     let instantiateRecord (fields : (RecordFieldName * SynExpr option) list) : SynExpr = | ||||
|         let fields = | ||||
|             fields | ||||
| @@ -131,6 +156,11 @@ module internal AstHelper = | ||||
|         // TODO: consider Microsoft.FSharp.Option or whatever it is | ||||
|         | _ -> false | ||||
|  | ||||
|     let isUnitIdent (ident : SynLongIdent) : bool = | ||||
|         match ident.LongIdent with | ||||
|         | [ i ] when System.String.Equals (i.idText, "unit", System.StringComparison.OrdinalIgnoreCase) -> true | ||||
|         | _ -> false | ||||
|  | ||||
|     let isListIdent (ident : SynLongIdent) : bool = | ||||
|         match ident.LongIdent with | ||||
|         | [ i ] when System.String.Equals (i.idText, "list", System.StringComparison.OrdinalIgnoreCase) -> true | ||||
| @@ -342,7 +372,18 @@ module internal AstHelper = | ||||
|                                 } | ||||
|                                 |> List.singleton | ||||
|                         } | ||||
|                     | _ -> failwith $"Unrecognised args in interface method declaration: %+A{args}" | ||||
|                     | arg -> | ||||
|                         { | ||||
|                             HasParen = false | ||||
|                             Args = | ||||
|                                 { | ||||
|                                     Attributes = [] | ||||
|                                     IsOptional = false | ||||
|                                     Id = None | ||||
|                                     Type = arg | ||||
|                                 } | ||||
|                                 |> List.singleton | ||||
|                         } | ||||
|                     |> fun ty -> | ||||
|                         { ty with | ||||
|                             HasParen = ty.HasParen || hasParen | ||||
| @@ -386,22 +427,26 @@ module internal AstHelper = | ||||
|  | ||||
|         let attrs = attrs |> List.collect (fun s -> s.Attributes) | ||||
|  | ||||
|         let members, properties = | ||||
|         let members, inherits = | ||||
|             match synTypeDefnRepr with | ||||
|             | SynTypeDefnRepr.ObjectModel (_kind, members, _) -> | ||||
|                 members | ||||
|                 |> List.map (fun defn -> | ||||
|                     match defn with | ||||
|                     | SynMemberDefn.AbstractSlot (slotSig, flags, _, _) -> parseMember slotSig flags | ||||
|                     | SynMemberDefn.AbstractSlot (slotSig, flags, _, _) -> Choice1Of2 (parseMember slotSig flags) | ||||
|                     | SynMemberDefn.Inherit (baseType, _asIdent, _) -> Choice2Of2 baseType | ||||
|                     | _ -> failwith $"Unrecognised member definition: %+A{defn}" | ||||
|                 ) | ||||
|             | _ -> failwith $"Unrecognised SynTypeDefnRepr for an interface type: %+A{synTypeDefnRepr}" | ||||
|             |> List.partitionChoice | ||||
|  | ||||
|         let members, properties = members |> List.partitionChoice | ||||
|  | ||||
|         { | ||||
|             Members = members | ||||
|             Properties = properties | ||||
|             Name = interfaceName | ||||
|             Inherits = inherits | ||||
|             Attributes = attrs | ||||
|             Generics = typars | ||||
|             Accessibility = accessibility | ||||
| @@ -486,6 +531,11 @@ module internal SynTypePatterns = | ||||
|             Some innerType | ||||
|         | _ -> None | ||||
|  | ||||
|     let (|UnitType|_|) (fieldType : SynType) : unit option = | ||||
|         match fieldType with | ||||
|         | SynType.LongIdent ident when AstHelper.isUnitIdent ident -> Some () | ||||
|         | _ -> None | ||||
|  | ||||
|     let (|ListType|_|) (fieldType : SynType) = | ||||
|         match fieldType with | ||||
|         | SynType.App (SynType.LongIdent ident, _, [ innerType ], _, _, _, _) when AstHelper.isListIdent ident -> | ||||
| @@ -531,14 +581,23 @@ module internal SynTypePatterns = | ||||
|             Some (key, value) | ||||
|         | _ -> None | ||||
|  | ||||
|     /// Returns the string name of the type. | ||||
|     let (|PrimitiveType|_|) (fieldType : SynType) = | ||||
|     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 ] -> | ||||
|                 [ "string" ; "float" ; "int" ; "bool" ; "char" ] | ||||
|                 |> List.tryFind (fun s -> s = i.idText) | ||||
|             | [ i ] -> AstHelper.qualifyPrimitiveType i.idText | ||||
|             | _ -> None | ||||
|         | _ -> None | ||||
|  | ||||
|   | ||||
| @@ -1,13 +1,16 @@ | ||||
| namespace WoofWare.Myriad.Plugins | ||||
|  | ||||
| open System | ||||
| open System.Net.Http | ||||
| open System.Text | ||||
| open Fantomas.FCS.Syntax | ||||
| open Fantomas.FCS.SyntaxTrivia | ||||
| open Fantomas.FCS.Xml | ||||
| open Myriad.Core | ||||
|  | ||||
| type internal HttpClientGeneratorOutputSpec = | ||||
|     { | ||||
|         ExtensionMethods : bool | ||||
|     } | ||||
|  | ||||
| [<RequireQualifiedAccess>] | ||||
| module internal HttpClientGenerator = | ||||
|     open Fantomas.FCS.Text.Range | ||||
| @@ -82,34 +85,50 @@ module internal HttpClientGenerator = | ||||
|                 match attr.TypeName.AsString with | ||||
|                 | "Get" | ||||
|                 | "GetAttribute" | ||||
|                 | "WoofWare.Myriad.Plugins.RestEase.Get" | ||||
|                 | "WoofWare.Myriad.Plugins.RestEase.GetAttribute" | ||||
|                 | "RestEase.Get" | ||||
|                 | "RestEase.GetAttribute" -> Some (HttpMethod.Get, attr.ArgExpr) | ||||
|                 | "Post" | ||||
|                 | "PostAttribute" | ||||
|                 | "WoofWare.Myriad.Plugins.RestEase.Post" | ||||
|                 | "WoofWare.Myriad.Plugins.RestEase.PostAttribute" | ||||
|                 | "RestEase.Post" | ||||
|                 | "RestEase.PostAttribute" -> Some (HttpMethod.Post, attr.ArgExpr) | ||||
|                 | "Put" | ||||
|                 | "PutAttribute" | ||||
|                 | "WoofWare.Myriad.Plugins.RestEase.Put" | ||||
|                 | "WoofWare.Myriad.Plugins.RestEase.PutAttribute" | ||||
|                 | "RestEase.Put" | ||||
|                 | "RestEase.PutAttribute" -> Some (HttpMethod.Put, attr.ArgExpr) | ||||
|                 | "Delete" | ||||
|                 | "DeleteAttribute" | ||||
|                 | "WoofWare.Myriad.Plugins.RestEase.Delete" | ||||
|                 | "WoofWare.Myriad.Plugins.RestEase.DeleteAttribute" | ||||
|                 | "RestEase.Delete" | ||||
|                 | "RestEase.DeleteAttribute" -> Some (HttpMethod.Delete, attr.ArgExpr) | ||||
|                 | "Head" | ||||
|                 | "HeadAttribute" | ||||
|                 | "WoofWare.Myriad.Plugins.RestEase.Head" | ||||
|                 | "WoofWare.Myriad.Plugins.RestEase.HeadAttribute" | ||||
|                 | "RestEase.Head" | ||||
|                 | "RestEase.HeadAttribute" -> Some (HttpMethod.Head, attr.ArgExpr) | ||||
|                 | "Options" | ||||
|                 | "OptionsAttribute" | ||||
|                 | "WoofWare.Myriad.Plugins.RestEase.Options" | ||||
|                 | "WoofWare.Myriad.Plugins.RestEase.OptionsAttribute" | ||||
|                 | "RestEase.Options" | ||||
|                 | "RestEase.OptionsAttribute" -> Some (HttpMethod.Options, attr.ArgExpr) | ||||
|                 | "Patch" | ||||
|                 | "PatchAttribute" | ||||
|                 | "WoofWare.Myriad.Plugins.RestEase.Patch" | ||||
|                 | "WoofWare.Myriad.Plugins.RestEase.PatchAttribute" | ||||
|                 | "RestEase.Patch" | ||||
|                 | "RestEase.PatchAttribute" -> Some (HttpMethod.Patch, attr.ArgExpr) | ||||
|                 | "Trace" | ||||
|                 | "TraceAttribute" | ||||
|                 | "WoofWare.Myriad.Plugins.RestEase.Trace" | ||||
|                 | "WoofWare.Myriad.Plugins.RestEase.TraceAttribute" | ||||
|                 | "RestEase.Trace" | ||||
|                 | "RestEase.TraceAttribute" -> Some (HttpMethod.Trace, attr.ArgExpr) | ||||
|                 | _ -> None | ||||
| @@ -127,7 +146,8 @@ module internal HttpClientGenerator = | ||||
|         |> List.choose (fun attr -> | ||||
|             match attr.TypeName.AsString with | ||||
|             | "Header" | ||||
|             | "RestEase.Header" -> | ||||
|             | "RestEase.Header" | ||||
|             | "WoofWare.Myriad.Plugins.RestEase.Header" -> | ||||
|                 match attr.ArgExpr with | ||||
|                 | SynExpr.Paren (SynExpr.Tuple (_, [ v1 ; v2 ], _, _), _, _, _) -> | ||||
|                     Some [ SynExpr.stripOptionalParen v1 ; SynExpr.stripOptionalParen v2 ] | ||||
| @@ -293,6 +313,27 @@ module internal HttpClientGenerator = | ||||
|                     | None -> failwith "Unable to get parameter variable name from anonymous parameter" | ||||
|                     | Some id -> id | ||||
|  | ||||
|                 let urlSeparator = | ||||
|                     // apparent Myriad bug: `IndexOf '?'` gets formatted as `IndexOf ?` which is clearly wrong | ||||
|                     let questionMark = | ||||
|                         SynExpr.CreateParen ( | ||||
|                             SynExpr.CreateApp ( | ||||
|                                 SynExpr.CreateIdentString "char", | ||||
|                                 SynExpr.CreateConst (SynConst.Int32 63) | ||||
|                             ) | ||||
|                         ) | ||||
|  | ||||
|                     let containsQuestion = | ||||
|                         info.UrlTemplate | ||||
|                         |> SynExpr.callMethodArg "IndexOf" questionMark | ||||
|                         |> SynExpr.greaterThanOrEqual (SynExpr.CreateConst (SynConst.Int32 0)) | ||||
|  | ||||
|                     SynExpr.ifThenElse | ||||
|                         containsQuestion | ||||
|                         (SynExpr.CreateConst (SynConst.CreateString "?")) | ||||
|                         (SynExpr.CreateConst (SynConst.CreateString "&")) | ||||
|                     |> SynExpr.CreateParen | ||||
|  | ||||
|                 let prefix = | ||||
|                     SynExpr.CreateIdent firstValueId | ||||
|                     |> SynExpr.toString firstValue.Type | ||||
| @@ -301,7 +342,7 @@ module internal HttpClientGenerator = | ||||
|                         SynExpr.CreateLongIdent (SynLongIdent.Create [ "System" ; "Web" ; "HttpUtility" ; "UrlEncode" ]) | ||||
|                     ) | ||||
|                     |> SynExpr.CreateParen | ||||
|                     |> SynExpr.plus (SynExpr.CreateConstString ("?" + firstKey + "=")) | ||||
|                     |> SynExpr.plus (SynExpr.plus urlSeparator (SynExpr.CreateConstString (firstKey + "="))) | ||||
|  | ||||
|                 (prefix, queryParams) | ||||
|                 ||> List.fold (fun uri (paramKey, paramValue) -> | ||||
| @@ -709,6 +750,10 @@ module internal HttpClientGenerator = | ||||
|         attrs | ||||
|         |> List.choose (fun attr -> | ||||
|             match attr.TypeName.AsString with | ||||
|             | "RestEase.Query" | ||||
|             | "RestEase.QueryAttribute" | ||||
|             | "WoofWare.Myriad.Plugins.RestEase.Query" | ||||
|             | "WoofWare.Myriad.Plugins.RestEase.QueryAttribute" | ||||
|             | "Query" | ||||
|             | "QueryAttribute" -> | ||||
|                 match attr.ArgExpr with | ||||
| @@ -717,14 +762,22 @@ module internal HttpClientGenerator = | ||||
|                     Some (HttpAttribute.Query (Some s)) | ||||
|                 | SynExpr.Const (a, _) -> failwith $"unrecognised constant arg to the Query attribute: %+A{a}" | ||||
|                 | _ -> None | ||||
|             | "RestEase.Path" | ||||
|             | "RestEase.PathAttribute" | ||||
|             | "WoofWare.Myriad.Plugins.RestEase.Path" | ||||
|             | "WoofWare.Myriad.Plugins.RestEase.PathAttribute" | ||||
|             | "Path" | ||||
|             | "PathAttribute" -> | ||||
|                 match attr.ArgExpr with | ||||
|                 match attr.ArgExpr |> SynExpr.stripOptionalParen with | ||||
|                 | SynExpr.Const (SynConst.String (s, SynStringKind.Regular, _), _) -> | ||||
|                     Some (HttpAttribute.Path (PathSpec.Verbatim s)) | ||||
|                 | SynExpr.Const (SynConst.Unit, _) -> Some (HttpAttribute.Path PathSpec.MatchArgName) | ||||
|                 | SynExpr.Const (a, _) -> failwith $"unrecognised constant arg to the Path attribute: %+A{a}" | ||||
|                 | _ -> None | ||||
|             | "RestEase.Body" | ||||
|             | "RestEase.BodyAttribute" | ||||
|             | "WoofWare.Myriad.Plugins.RestEase.Body" | ||||
|             | "WoofWare.Myriad.Plugins.RestEase.BodyAttribute" | ||||
|             | "Body" | ||||
|             | "BodyAttribute" -> | ||||
|                 match attr.ArgExpr with | ||||
| @@ -740,8 +793,10 @@ module internal HttpClientGenerator = | ||||
|             match attr.TypeName.AsString with | ||||
|             | "BasePath" | ||||
|             | "RestEase.BasePath" | ||||
|             | "WoofWare.Myriad.Plugins.RestEase.BasePath" | ||||
|             | "BasePathAttribute" | ||||
|             | "RestEase.BasePathAttribute" -> Some attr.ArgExpr | ||||
|             | "RestEase.BasePathAttribute" | ||||
|             | "WoofWare.Myriad.Plugins.RestEase.BasePathAttribute" -> Some attr.ArgExpr | ||||
|             | _ -> None | ||||
|         ) | ||||
|  | ||||
| @@ -751,19 +806,25 @@ module internal HttpClientGenerator = | ||||
|             match attr.TypeName.AsString with | ||||
|             | "BaseAddress" | ||||
|             | "RestEase.BaseAddress" | ||||
|             | "WoofWare.Myriad.Plugins.RestEase.BaseAddress" | ||||
|             | "BaseAddressAttribute" | ||||
|             | "RestEase.BaseAddressAttribute" -> Some attr.ArgExpr | ||||
|             | "RestEase.BaseAddressAttribute" | ||||
|             | "WoofWare.Myriad.Plugins.RestEase.BaseAddressAttribute" -> Some attr.ArgExpr | ||||
|             | _ -> None | ||||
|         ) | ||||
|  | ||||
|     let createModule | ||||
|         (opens : SynOpenDeclTarget list) | ||||
|         (ns : LongIdent) | ||||
|         (interfaceType : SynTypeDefn) | ||||
|         (interfaceType : SynTypeDefn, spec : HttpClientGeneratorOutputSpec) | ||||
|         : SynModuleOrNamespace | ||||
|         = | ||||
|         let interfaceType = AstHelper.parseInterface interfaceType | ||||
|  | ||||
|         if not (List.isEmpty interfaceType.Inherits) then | ||||
|             failwith | ||||
|                 "HttpClientGenerator does not support inheritance. Remove the `inherit` keyword if you want to use this generator." | ||||
|  | ||||
|         let constantHeaders = | ||||
|             interfaceType.Attributes | ||||
|             |> extractHeaderInformation | ||||
| @@ -903,7 +964,13 @@ module internal HttpClientGenerator = | ||||
|  | ||||
|         let members = propertyMembers @ nonPropertyMembers | ||||
|  | ||||
|         let docString = PreXmlDoc.Create " Module for constructing a REST client." | ||||
|         let docString = | ||||
|             (if spec.ExtensionMethods then | ||||
|                  "Extension methods" | ||||
|              else | ||||
|                  "Module") | ||||
|             |> sprintf " %s for constructing a REST client." | ||||
|             |> PreXmlDoc.Create | ||||
|  | ||||
|         let interfaceImpl = | ||||
|             SynExpr.ObjExpr ( | ||||
| @@ -939,38 +1006,38 @@ module internal HttpClientGenerator = | ||||
|                 " Create a REST client." | ||||
|             else | ||||
|                 " Create a REST client. The input functions will be re-evaluated on every HTTP request to obtain the required values for the corresponding header properties." | ||||
|             |> PreXmlDoc.Create | ||||
|  | ||||
|         let createFunc = | ||||
|             SynBinding.SynBinding ( | ||||
|                 None, | ||||
|                 SynBindingKind.Normal, | ||||
|                 false, | ||||
|                 false, | ||||
|                 [], | ||||
|                 PreXmlDoc.Create xmlDoc, | ||||
|                 SynValData.SynValData ( | ||||
|                     None, | ||||
|                     SynValInfo.SynValInfo ( | ||||
|                         [ [ SynArgInfo.SynArgInfo ([], false, Some (Ident.Create "client")) ] ], | ||||
|                         SynArgInfo.Empty | ||||
|                     ), | ||||
|         let functionName = Ident.Create "client" | ||||
|  | ||||
|         let valData = | ||||
|             let memberFlags = | ||||
|                 if spec.ExtensionMethods then | ||||
|                     { | ||||
|                         SynMemberFlags.IsInstance = false | ||||
|                         SynMemberFlags.IsDispatchSlot = false | ||||
|                         SynMemberFlags.IsOverrideOrExplicitImpl = false | ||||
|                         SynMemberFlags.IsFinal = false | ||||
|                         SynMemberFlags.GetterOrSetterIsCompilerGenerated = false | ||||
|                         SynMemberFlags.MemberKind = SynMemberKind.Member | ||||
|                     } | ||||
|                     |> Some | ||||
|                 else | ||||
|                     None | ||||
|                 ), | ||||
|                 SynPat.CreateLongIdent (SynLongIdent.CreateString "make", headerArgs @ [ clientCreationArg ]), | ||||
|                 Some ( | ||||
|                     SynBindingReturnInfo.Create ( | ||||
|                         SynType.LongIdent (SynLongIdent.CreateFromLongIdent interfaceType.Name) | ||||
|                     ) | ||||
|                 ), | ||||
|                 interfaceImpl, | ||||
|                 range0, | ||||
|                 DebugPointAtBinding.NoneAtLet, | ||||
|                 SynExpr.synBindingTriviaZero false | ||||
|             ) | ||||
|             |> List.singleton | ||||
|             |> SynModuleDecl.CreateLet | ||||
|  | ||||
|         let moduleName : LongIdent = | ||||
|             SynValData.SynValData ( | ||||
|                 memberFlags, | ||||
|                 SynValInfo.SynValInfo ([ [ SynArgInfo.SynArgInfo ([], false, Some functionName) ] ], SynArgInfo.Empty), | ||||
|                 None | ||||
|             ) | ||||
|  | ||||
|         let pattern = | ||||
|             SynPat.CreateLongIdent (SynLongIdent.CreateString "make", headerArgs @ [ clientCreationArg ]) | ||||
|  | ||||
|         let returnInfo = | ||||
|             SynBindingReturnInfo.Create (SynType.LongIdent (SynLongIdent.CreateFromLongIdent interfaceType.Name)) | ||||
|  | ||||
|         let nameWithoutLeadingI = | ||||
|             List.last interfaceType.Name | ||||
|             |> _.idText | ||||
|             |> fun s -> | ||||
| @@ -978,14 +1045,84 @@ module internal HttpClientGenerator = | ||||
|                     s.[1..] | ||||
|                 else | ||||
|                     failwith $"Expected interface type to start with 'I', but was: %s{s}" | ||||
|             |> Ident.Create | ||||
|             |> List.singleton | ||||
|  | ||||
|         let createFunc = | ||||
|             if spec.ExtensionMethods then | ||||
|                 let binding = | ||||
|                     SynBinding.SynBinding ( | ||||
|                         None, | ||||
|                         SynBindingKind.Normal, | ||||
|                         false, | ||||
|                         false, | ||||
|                         [], | ||||
|                         xmlDoc, | ||||
|                         valData, | ||||
|                         pattern, | ||||
|                         Some returnInfo, | ||||
|                         interfaceImpl, | ||||
|                         range0, | ||||
|                         DebugPointAtBinding.NoneAtInvisible, | ||||
|                         { | ||||
|                             LeadingKeyword = SynLeadingKeyword.StaticMember (range0, range0) | ||||
|                             InlineKeyword = None | ||||
|                             EqualsRange = Some range0 | ||||
|                         } | ||||
|                     ) | ||||
|  | ||||
|                 let mem = SynMemberDefn.Member (binding, range0) | ||||
|  | ||||
|                 let containingType = | ||||
|                     SynTypeDefn.SynTypeDefn ( | ||||
|                         SynComponentInfo.Create ( | ||||
|                             [ Ident.Create nameWithoutLeadingI ], | ||||
|                             xmldoc = PreXmlDoc.Create " Extension methods for HTTP clients" | ||||
|                         ), | ||||
|                         SynTypeDefnRepr.ObjectModel (SynTypeDefnKind.Augmentation range0, [], range0), | ||||
|                         [ mem ], | ||||
|                         None, | ||||
|                         range0, | ||||
|                         { | ||||
|                             LeadingKeyword = SynTypeDefnLeadingKeyword.Type range0 | ||||
|                             EqualsRange = None | ||||
|                             WithKeyword = None | ||||
|                         } | ||||
|                     ) | ||||
|  | ||||
|                 SynModuleDecl.Types ([ containingType ], range0) | ||||
|  | ||||
|             else | ||||
|                 SynBinding.SynBinding ( | ||||
|                     None, | ||||
|                     SynBindingKind.Normal, | ||||
|                     false, | ||||
|                     false, | ||||
|                     [], | ||||
|                     xmlDoc, | ||||
|                     valData, | ||||
|                     pattern, | ||||
|                     Some returnInfo, | ||||
|                     interfaceImpl, | ||||
|                     range0, | ||||
|                     DebugPointAtBinding.NoneAtLet, | ||||
|                     SynExpr.synBindingTriviaZero false | ||||
|                 ) | ||||
|                 |> List.singleton | ||||
|                 |> SynModuleDecl.CreateLet | ||||
|  | ||||
|         let moduleName : LongIdent = | ||||
|             if spec.ExtensionMethods then | ||||
|                 [ Ident.Create (nameWithoutLeadingI + "HttpClientExtension") ] | ||||
|             else | ||||
|                 [ Ident.Create nameWithoutLeadingI ] | ||||
|  | ||||
|         let attribs = | ||||
|             [ | ||||
|                 SynAttributeList.Create SynAttribute.compilationRepresentation | ||||
|                 SynAttributeList.Create (SynAttribute.RequireQualifiedAccess ()) | ||||
|             ] | ||||
|             if spec.ExtensionMethods then | ||||
|                 [ SynAttributeList.Create SynAttribute.autoOpen ] | ||||
|             else | ||||
|                 [ | ||||
|                     SynAttributeList.Create SynAttribute.compilationRepresentation | ||||
|                     SynAttributeList.Create (SynAttribute.RequireQualifiedAccess ()) | ||||
|                 ] | ||||
|  | ||||
|         let modInfo = | ||||
|             SynComponentInfo.Create ( | ||||
| @@ -1023,9 +1160,29 @@ type HttpClientGenerator () = | ||||
|             let namespaceAndTypes = | ||||
|                 types | ||||
|                 |> List.choose (fun (ns, types) -> | ||||
|                     match types |> List.filter Ast.hasAttribute<HttpClientAttribute> with | ||||
|                     | [] -> None | ||||
|                     | types -> Some (ns, types) | ||||
|                     types | ||||
|                     |> List.choose (fun typeDef -> | ||||
|                         match Ast.getAttribute<HttpClientAttribute> typeDef with | ||||
|                         | None -> None | ||||
|                         | Some attr -> | ||||
|                             let arg = | ||||
|                                 match SynExpr.stripOptionalParen attr.ArgExpr with | ||||
|                                 | SynExpr.Const (SynConst.Bool value, _) -> value | ||||
|                                 | SynExpr.Const (SynConst.Unit, _) -> JsonParseAttribute.DefaultIsExtensionMethod | ||||
|                                 | arg -> | ||||
|                                     failwith | ||||
|                                         $"Unrecognised argument %+A{arg} to [<%s{nameof HttpClientAttribute}>]. Literals are not supported. Use `true` or `false` (or unit) only." | ||||
|  | ||||
|                             let spec = | ||||
|                                 { | ||||
|                                     ExtensionMethods = arg | ||||
|                                 } | ||||
|  | ||||
|                             Some (typeDef, spec) | ||||
|                     ) | ||||
|                     |> function | ||||
|                         | [] -> None | ||||
|                         | ty -> Some (ns, ty) | ||||
|                 ) | ||||
|  | ||||
|             let modules = | ||||
|   | ||||
| @@ -21,6 +21,9 @@ module internal InterfaceMockGenerator = | ||||
|         | None -> failwith "Expected record field to have a name, but it was somehow anonymous" | ||||
|         | Some id -> id | ||||
|  | ||||
|     [<RequireQualifiedAccess>] | ||||
|     type private KnownInheritance = | IDisposable | ||||
|  | ||||
|     let createType | ||||
|         (spec : GenerateMockOutputSpec) | ||||
|         (name : string) | ||||
| @@ -29,6 +32,20 @@ module internal InterfaceMockGenerator = | ||||
|         (fields : SynField list) | ||||
|         : SynModuleDecl | ||||
|         = | ||||
|         let inherits = | ||||
|             interfaceType.Inherits | ||||
|             |> Seq.map (fun ty -> | ||||
|                 match ty with | ||||
|                 | SynType.LongIdent (SynLongIdent.SynLongIdent (name, _, _)) -> | ||||
|                     match name |> List.map _.idText with | ||||
|                     | [] -> failwith "Unexpected empty identifier in inheritance declaration" | ||||
|                     | [ "IDisposable" ] | ||||
|                     | [ "System" ; "IDisposable" ] -> KnownInheritance.IDisposable | ||||
|                     | _ -> failwithf "Unrecognised inheritance identifier: %+A" name | ||||
|                 | x -> failwithf "Unrecognised type in inheritance: %+A" x | ||||
|             ) | ||||
|             |> Set.ofSeq | ||||
|  | ||||
|         let synValData = | ||||
|             { | ||||
|                 SynMemberFlags.IsInstance = false | ||||
| @@ -90,6 +107,23 @@ module internal InterfaceMockGenerator = | ||||
|                 ) | ||||
|             |> SynBindingReturnInfo.Create | ||||
|  | ||||
|         let constructorFields = | ||||
|             let extras = | ||||
|                 if inherits.Contains KnownInheritance.IDisposable then | ||||
|                     let unitFun = SynExpr.createLambda "_" SynExpr.CreateUnit | ||||
|  | ||||
|                     [ | ||||
|                         (SynLongIdent.CreateFromLongIdent [ Ident.Create "Dispose" ], true), Some unitFun | ||||
|                     ] | ||||
|                 else | ||||
|                     [] | ||||
|  | ||||
|             let nonExtras = | ||||
|                 fields | ||||
|                 |> List.map (fun field -> (SynLongIdent.CreateFromLongIdent [ getName field ], true), Some failwithFun) | ||||
|  | ||||
|             extras @ nonExtras | ||||
|  | ||||
|         let constructor = | ||||
|             SynMemberDefn.Member ( | ||||
|                 SynBinding.SynBinding ( | ||||
| @@ -102,12 +136,7 @@ module internal InterfaceMockGenerator = | ||||
|                     SynValData.SynValData (Some synValData, SynValInfo.Empty, None), | ||||
|                     constructorIdent, | ||||
|                     Some constructorReturnType, | ||||
|                     AstHelper.instantiateRecord ( | ||||
|                         fields | ||||
|                         |> List.map (fun field -> | ||||
|                             ((SynLongIdent.CreateFromLongIdent [ getName field ], true), Some failwithFun) | ||||
|                         ) | ||||
|                     ), | ||||
|                     AstHelper.instantiateRecord constructorFields, | ||||
|                     range0, | ||||
|                     DebugPointAtBinding.Yes range0, | ||||
|                     { SynExpr.synBindingTriviaZero true with | ||||
| @@ -117,6 +146,21 @@ module internal InterfaceMockGenerator = | ||||
|                 range0 | ||||
|             ) | ||||
|  | ||||
|         let fields = | ||||
|             let extras = | ||||
|                 if inherits.Contains KnownInheritance.IDisposable then | ||||
|                     [ | ||||
|                         SynField.Create ( | ||||
|                             SynType.CreateFun (SynType.CreateUnit, SynType.CreateUnit), | ||||
|                             Ident.Create "Dispose", | ||||
|                             xmldoc = PreXmlDoc.Create " Implementation of IDisposable.Dispose" | ||||
|                         ) | ||||
|                     ] | ||||
|                 else | ||||
|                     [] | ||||
|  | ||||
|             extras @ fields | ||||
|  | ||||
|         let interfaceMembers = | ||||
|             let members = | ||||
|                 interfaceType.Members | ||||
| @@ -150,7 +194,9 @@ module internal InterfaceMockGenerator = | ||||
|                                                 |> List.mapi (fun i arg -> | ||||
|                                                     arg.Args | ||||
|                                                     |> List.mapi (fun j arg -> | ||||
|                                                         SynArgInfo.CreateIdString $"arg_%i{i}_%i{j}" | ||||
|                                                         match arg.Type with | ||||
|                                                         | UnitType -> SynArgInfo.SynArgInfo ([], false, None) | ||||
|                                                         | _ -> SynArgInfo.CreateIdString $"arg_%i{i}_%i{j}" | ||||
|                                                     ) | ||||
|                                                 ) | ||||
|                                         ], | ||||
| @@ -165,10 +211,18 @@ module internal InterfaceMockGenerator = | ||||
|                         |> List.mapi (fun i tupledArgs -> | ||||
|                             let args = | ||||
|                                 tupledArgs.Args | ||||
|                                 |> List.mapi (fun j _ -> SynPat.CreateNamed (Ident.Create $"arg_%i{i}_%i{j}")) | ||||
|                                 |> List.mapi (fun j ty -> | ||||
|                                     match ty.Type with | ||||
|                                     | UnitType -> SynPat.Const (SynConst.Unit, range0) | ||||
|                                     | _ -> SynPat.CreateNamed (Ident.Create $"arg_%i{i}_%i{j}") | ||||
|                                 ) | ||||
|  | ||||
|                             SynPat.Tuple (false, args, List.replicate (args.Length - 1) range0, range0) | ||||
|                             |> SynPat.CreateParen | ||||
|                             match args with | ||||
|                             | [] -> failwith "somehow got no args at all" | ||||
|                             | [ arg ] -> arg | ||||
|                             | args -> | ||||
|                                 SynPat.Tuple (false, args, List.replicate (args.Length - 1) range0, range0) | ||||
|                                 |> SynPat.CreateParen | ||||
|                             |> fun i -> if tupledArgs.HasParen then SynPat.Paren (i, range0) else i | ||||
|                         ) | ||||
|  | ||||
| @@ -187,7 +241,11 @@ module internal InterfaceMockGenerator = | ||||
|                             memberInfo.Args | ||||
|                             |> List.mapi (fun i args -> | ||||
|                                 args.Args | ||||
|                                 |> List.mapi (fun j args -> SynExpr.CreateIdentString $"arg_%i{i}_%i{j}") | ||||
|                                 |> List.mapi (fun j arg -> | ||||
|                                     match arg.Type with | ||||
|                                     | UnitType -> SynExpr.CreateConst SynConst.Unit | ||||
|                                     | _ -> SynExpr.CreateIdentString $"arg_%i{i}_%i{j}" | ||||
|                                 ) | ||||
|                                 |> SynExpr.CreateParenedTuple | ||||
|                             ) | ||||
|  | ||||
| @@ -264,11 +322,100 @@ module internal InterfaceMockGenerator = | ||||
|             | Some (SynAccess.Internal _), _ -> SynAccess.Internal range0 | ||||
|             | Some (SynAccess.Private _), _ -> SynAccess.Private range0 | ||||
|  | ||||
|         let extraInterfaces = | ||||
|             inherits | ||||
|             |> Seq.map (fun inheritance -> | ||||
|                 match inheritance with | ||||
|                 | KnownInheritance.IDisposable -> | ||||
|                     let valData = | ||||
|                         SynValData.SynValData ( | ||||
|                             Some | ||||
|                                 { | ||||
|                                     IsInstance = true | ||||
|                                     IsDispatchSlot = false | ||||
|                                     IsOverrideOrExplicitImpl = true | ||||
|                                     IsFinal = false | ||||
|                                     GetterOrSetterIsCompilerGenerated = false | ||||
|                                     MemberKind = SynMemberKind.Member | ||||
|                                 }, | ||||
|                             valInfo = | ||||
|                                 SynValInfo.SynValInfo ( | ||||
|                                     curriedArgInfos = | ||||
|                                         [ | ||||
|                                             yield | ||||
|                                                 [ | ||||
|                                                     SynArgInfo.SynArgInfo ( | ||||
|                                                         attributes = [], | ||||
|                                                         optional = false, | ||||
|                                                         ident = None | ||||
|                                                     ) | ||||
|                                                 ] | ||||
|                                         ], | ||||
|                                     returnInfo = | ||||
|                                         SynArgInfo.SynArgInfo (attributes = [], optional = false, ident = None) | ||||
|                                 ), | ||||
|                             thisIdOpt = None | ||||
|                         ) | ||||
|  | ||||
|                     let headArgs = [ SynPat.Const (SynConst.Unit, range0) ] | ||||
|  | ||||
|                     let headPat = | ||||
|                         SynPat.LongIdent ( | ||||
|                             SynLongIdent.CreateFromLongIdent [ Ident.Create "this" ; Ident.Create "Dispose" ], | ||||
|                             None, | ||||
|                             None, | ||||
|                             SynArgPats.Pats headArgs, | ||||
|                             None, | ||||
|                             range0 | ||||
|                         ) | ||||
|  | ||||
|                     let binding = | ||||
|                         SynBinding.SynBinding ( | ||||
|                             None, | ||||
|                             SynBindingKind.Normal, | ||||
|                             false, | ||||
|                             false, | ||||
|                             [], | ||||
|                             PreXmlDoc.Empty, | ||||
|                             valData, | ||||
|                             headPat, | ||||
|                             Some ( | ||||
|                                 SynBindingReturnInfo.SynBindingReturnInfo ( | ||||
|                                     SynType.Unit (), | ||||
|                                     range0, | ||||
|                                     [], | ||||
|                                     SynBindingReturnInfoTrivia.Zero | ||||
|                                 ) | ||||
|                             ), | ||||
|                             SynExpr.CreateApp ( | ||||
|                                 SynExpr.CreateLongIdent (SynLongIdent.Create [ "this" ; "Dispose" ]), | ||||
|                                 SynExpr.CreateUnit | ||||
|                             ), | ||||
|                             range0, | ||||
|                             DebugPointAtBinding.Yes range0, | ||||
|                             { | ||||
|                                 LeadingKeyword = SynLeadingKeyword.Member range0 | ||||
|                                 InlineKeyword = None | ||||
|                                 EqualsRange = Some range0 | ||||
|                             } | ||||
|                         ) | ||||
|  | ||||
|                     let mem = SynMemberDefn.Member (binding, range0) | ||||
|  | ||||
|                     SynMemberDefn.Interface ( | ||||
|                         SynType.CreateLongIdent (SynLongIdent.Create [ "System" ; "IDisposable" ]), | ||||
|                         Some range0, | ||||
|                         Some [ mem ], | ||||
|                         range0 | ||||
|                     ) | ||||
|             ) | ||||
|             |> Seq.toList | ||||
|  | ||||
|         let record = | ||||
|             { | ||||
|                 Name = Ident.Create name | ||||
|                 Fields = fields | ||||
|                 Members = Some [ constructor ; interfaceMembers ] | ||||
|                 Members = Some ([ constructor ; interfaceMembers ] @ extraInterfaces) | ||||
|                 XmlDoc = Some xmlDoc | ||||
|                 Generics = interfaceType.Generics | ||||
|                 Accessibility = Some access | ||||
| @@ -333,7 +480,6 @@ module internal InterfaceMockGenerator = | ||||
|  | ||||
|         let typeDecl = createType spec name interfaceType docString fields | ||||
|  | ||||
|  | ||||
|         SynModuleOrNamespace.CreateNamespace ( | ||||
|             namespaceId, | ||||
|             decls = (opens |> List.map SynModuleDecl.CreateOpen) @ [ typeDecl ] | ||||
|   | ||||
| @@ -62,6 +62,13 @@ module internal JsonParseGenerator = | ||||
|     /// {node}.AsValue().GetValue<{typeName}> () | ||||
|     /// If `propertyName` is Some, uses `assertNotNull {node}` instead of `{node}`. | ||||
|     let asValueGetValue (propertyName : SynExpr option) (typeName : string) (node : SynExpr) : SynExpr = | ||||
|         match propertyName with | ||||
|         | None -> node | ||||
|         | Some propertyName -> assertNotNull 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 | ||||
| @@ -122,7 +129,12 @@ module internal JsonParseGenerator = | ||||
|  | ||||
|     /// Given e.g. "float", returns "System.Double.Parse" | ||||
|     let parseFunction (typeName : string) : LongIdent = | ||||
|         List.append (SynExpr.qualifyPrimitiveType typeName) [ Ident.Create "Parse" ] | ||||
|         let qualified = | ||||
|             match AstHelper.qualifyPrimitiveType typeName with | ||||
|             | Some x -> x | ||||
|             | None -> failwith $"Could not recognise type %s{typeName} as a primitive." | ||||
|  | ||||
|         List.append qualified [ 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. | ||||
| @@ -252,7 +264,7 @@ module internal JsonParseGenerator = | ||||
|                         range0 | ||||
|                     )) | ||||
|                     handler | ||||
|         | PrimitiveType typeName -> asValueGetValue propertyName typeName node | ||||
|         | PrimitiveType typeName -> asValueGetValueIdent propertyName typeName node | ||||
|         | OptionType ty -> | ||||
|             parseNode None options ty (SynExpr.CreateIdentString "v") | ||||
|             |> createParseLineOption node | ||||
| @@ -312,6 +324,11 @@ module internal JsonParseGenerator = | ||||
|                 ) | ||||
|             ) | ||||
|             |> SynExpr.pipeThroughFunction (SynExpr.CreateLongIdent (SynLongIdent.Create [ "Map" ; "ofSeq" ])) | ||||
|         | BigInt -> | ||||
|             SynExpr.CreateApp ( | ||||
|                 SynExpr.CreateLongIdent (SynLongIdent.Create [ "System" ; "Numerics" ; "BigInteger" ; "Parse" ]), | ||||
|                 SynExpr.CreateParen (node |> SynExpr.callMethod "ToJsonString") | ||||
|             ) | ||||
|         | _ -> | ||||
|             // Let's just hope that we've also got our own type annotation! | ||||
|             let typeName = | ||||
|   | ||||
| @@ -107,24 +107,6 @@ module internal SynExpr = | ||||
|         | SynExpr.Paren (expr, _, _, _) -> stripOptionalParen expr | ||||
|         | expr -> expr | ||||
|  | ||||
|     /// Given e.g. "byte", returns "System.Byte". | ||||
|     let qualifyPrimitiveType (typeName : string) : LongIdent = | ||||
|         match typeName with | ||||
|         | "float32" -> [ "System" ; "Single" ] | ||||
|         | "float" -> [ "System" ; "Double" ] | ||||
|         | "byte" | ||||
|         | "uint8" -> [ "System" ; "Byte" ] | ||||
|         | "sbyte" -> [ "System" ; "SByte" ] | ||||
|         | "int16" -> [ "System" ; "Int16" ] | ||||
|         | "int" -> [ "System" ; "Int32" ] | ||||
|         | "int64" -> [ "System" ; "Int64" ] | ||||
|         | "uint16" -> [ "System" ; "UInt16" ] | ||||
|         | "uint" | ||||
|         | "uint32" -> [ "System" ; "UInt32" ] | ||||
|         | "uint64" -> [ "System" ; "UInt64" ] | ||||
|         | _ -> failwith $"Unable to identify a parsing function `string -> %s{typeName}`" | ||||
|         |> List.map Ident.Create | ||||
|  | ||||
|     /// {obj}.{meth} {arg} | ||||
|     let callMethodArg (meth : string) (arg : SynExpr) (obj : SynExpr) : SynExpr = | ||||
|         SynExpr.CreateApp ( | ||||
| @@ -141,8 +123,22 @@ module internal SynExpr = | ||||
|     let callMethod (meth : string) (obj : SynExpr) : SynExpr = | ||||
|         callMethodArg meth (SynExpr.CreateConst SynConst.Unit) obj | ||||
|  | ||||
|     let callGenericMethod (meth : string) (ty : LongIdent) (obj : SynExpr) : SynExpr = | ||||
|         SynExpr.CreateApp ( | ||||
|             SynExpr.TypeApp ( | ||||
|                 SynExpr.DotGet (obj, range0, SynLongIdent.Create [ meth ], range0), | ||||
|                 range0, | ||||
|                 [ SynType.LongIdent (SynLongIdent.CreateFromLongIdent ty) ], | ||||
|                 [], | ||||
|                 Some range0, | ||||
|                 range0, | ||||
|                 range0 | ||||
|             ), | ||||
|             SynExpr.CreateConst SynConst.Unit | ||||
|         ) | ||||
|  | ||||
|     /// {obj}.{meth}<ty>() | ||||
|     let callGenericMethod (meth : string) (ty : string) (obj : SynExpr) : SynExpr = | ||||
|     let callGenericMethod' (meth : string) (ty : string) (obj : SynExpr) : SynExpr = | ||||
|         SynExpr.CreateApp ( | ||||
|             SynExpr.TypeApp ( | ||||
|                 SynExpr.DotGet (obj, range0, SynLongIdent.Create [ meth ], range0), | ||||
| @@ -311,3 +307,19 @@ module internal SynExpr = | ||||
|             ), | ||||
|             x | ||||
|         ) | ||||
|  | ||||
|     /// {y} >= {x} | ||||
|     let greaterThanOrEqual (x : SynExpr) (y : SynExpr) : SynExpr = | ||||
|         SynExpr.CreateApp ( | ||||
|             SynExpr.CreateAppInfix ( | ||||
|                 SynExpr.CreateLongIdent ( | ||||
|                     SynLongIdent.SynLongIdent ( | ||||
|                         [ Ident.Create "op_GreaterThanOrEqual" ], | ||||
|                         [], | ||||
|                         [ Some (IdentTrivia.OriginalNotation ">=") ] | ||||
|                     ) | ||||
|                 ), | ||||
|                 y | ||||
|             ), | ||||
|             x | ||||
|         ) | ||||
|   | ||||
| @@ -27,7 +27,7 @@ | ||||
|     <Compile Include="List.fs"/> | ||||
|     <Compile Include="Ident.fs" /> | ||||
|     <Compile Include="AstHelper.fs"/> | ||||
|     <Compile Include="SynExpr.fs"/> | ||||
|     <Compile Include="SynExpr.fs" /> | ||||
|     <Compile Include="SynType.fs"/> | ||||
|     <Compile Include="SynAttribute.fs"/> | ||||
|     <Compile Include="RemoveOptionsGenerator.fs"/> | ||||
|   | ||||
| @@ -10,7 +10,7 @@ | ||||
|   </PropertyGroup> | ||||
|  | ||||
|   <ItemGroup> | ||||
|     <PackageDownload Include="G-Research.FSharp.Analyzers" Version="[0.9.3]" /> | ||||
|     <PackageDownload Include="G-Research.FSharp.Analyzers" Version="[0.10.0]" /> | ||||
|   </ItemGroup> | ||||
|  | ||||
| </Project> | ||||
|   | ||||
							
								
								
									
										31
									
								
								nix/deps.nix
									
									
									
									
									
								
							
							
						
						
									
										31
									
								
								nix/deps.nix
									
									
									
									
									
								
							| @@ -3,23 +3,18 @@ | ||||
| {fetchNuGet}: [ | ||||
|   (fetchNuGet { | ||||
|     pname = "fsharp-analyzers"; | ||||
|     version = "0.25.0"; | ||||
|     sha256 = "sha256-njfJYi40jNvrD+mgu9LtQw2Omh8P1SSDThesozH0KQY="; | ||||
|     version = "0.26.0"; | ||||
|     sha256 = "sha256-60Bl36LOb/zVNdH2SBSuQ5O41lP9dKTNZbs5vvYs+3U="; | ||||
|   }) | ||||
|   (fetchNuGet { | ||||
|     pname = "fantomas"; | ||||
|     version = "6.3.0-alpha-007"; | ||||
|     sha256 = "sha256-uZw6h6k/DS4BcYtK9cv8TLS0H8MZDO3WBaPPTdtTgu0="; | ||||
|     version = "6.3.4"; | ||||
|     sha256 = "sha256-1aWqZynBkQoznenGoP0sbf1PcUXAbcHiWyECuv89xa0="; | ||||
|   }) | ||||
|   (fetchNuGet { | ||||
|     pname = "ApiSurface"; | ||||
|     version = "4.0.30"; | ||||
|     sha256 = "0khbp0dx87m4kx1a5b9vgh1pp88vr9w8vpqvxf6afrpcyynwrrcr"; | ||||
|   }) | ||||
|   (fetchNuGet { | ||||
|     pname = "coverlet.collector"; | ||||
|     version = "6.0.0"; | ||||
|     sha256 = "12j34vrkmph8lspbafnqmfnj2qvysz1jcrks2khw798s6dwv0j90"; | ||||
|     version = "4.0.39"; | ||||
|     sha256 = "sha256-I4K5nJbltsfL/1r+KPTIo2wUd30zsCC2pkrnIRnsRHM="; | ||||
|   }) | ||||
|   (fetchNuGet { | ||||
|     pname = "Fantomas.Core"; | ||||
| @@ -296,11 +291,6 @@ | ||||
|     version = "3.6.133"; | ||||
|     sha256 = "1cdw8krvsnx0n34f7fm5hiiy7bs6h3asvncqcikc0g46l50w2j80"; | ||||
|   }) | ||||
|   (fetchNuGet { | ||||
|     pname = "NETStandard.Library"; | ||||
|     version = "2.0.0"; | ||||
|     sha256 = "1bc4ba8ahgk15m8k4nd7x406nhi0kwqzbgjk2dmw52ss553xz7iy"; | ||||
|   }) | ||||
|   (fetchNuGet { | ||||
|     pname = "NETStandard.Library"; | ||||
|     version = "2.0.3"; | ||||
| @@ -348,13 +338,8 @@ | ||||
|   }) | ||||
|   (fetchNuGet { | ||||
|     pname = "NUnit"; | ||||
|     version = "3.13.3"; | ||||
|     sha256 = "0wdzfkygqnr73s6lpxg5b1pwaqz9f414fxpvpdmf72bvh4jaqzv6"; | ||||
|   }) | ||||
|   (fetchNuGet { | ||||
|     pname = "NUnit"; | ||||
|     version = "4.0.1"; | ||||
|     sha256 = "0jgiq3dbwli5r70j0bw7021d69r7bhr58s8kphlpjmf7k47l5pcd"; | ||||
|     version = "4.1.0"; | ||||
|     sha256 = "0fj6xwgqaxq3mrai86bklclfmjkzf038mrslwfqf4ignaz9f7g5j"; | ||||
|   }) | ||||
|   (fetchNuGet { | ||||
|     pname = "NUnit3TestAdapter"; | ||||
|   | ||||
		Reference in New Issue
	
	Block a user