From 85929d49d549433a1af7ded4f3d3ca6302f968c2 Mon Sep 17 00:00:00 2001 From: Patrick Stevens <3138005+Smaug123@users.noreply.github.com> Date: Mon, 24 Jun 2024 23:23:23 +0100 Subject: [PATCH] Support units of measure in JsonParse (#170) --- .gitignore | 24 +++--- CHANGELOG.md | 5 ++ ConsumePlugin/GeneratedPureGymDto.fs | 1 + ConsumePlugin/PureGymDto.fs | 5 +- WoofWare.Myriad.Plugins.Test/PureGymDtos.fs | 4 +- WoofWare.Myriad.Plugins/JsonParseGenerator.fs | 85 +++++++++++-------- WoofWare.Myriad.Plugins/Measure.fs | 24 ++++++ WoofWare.Myriad.Plugins/SynExpr/SynExpr.fs | 14 +++ WoofWare.Myriad.Plugins/SynExpr/SynType.fs | 11 +++ .../WoofWare.Myriad.Plugins.fsproj | 1 + 10 files changed, 124 insertions(+), 50 deletions(-) create mode 100644 WoofWare.Myriad.Plugins/Measure.fs diff --git a/.gitignore b/.gitignore index 6f8612b..38fb0e1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,12 +1,12 @@ -bin/ -obj/ -/packages/ -riderModule.iml -/_ReSharper.Caches/ -.idea/ -*.sln.DotSettings.user -.DS_Store -result -.analyzerpackages/ -analysis.sarif -.direnv/ +bin/ +obj/ +/packages/ +riderModule.iml +/_ReSharper.Caches/ +.idea/ +*.sln.DotSettings.user +.DS_Store +result +.analyzerpackages/ +analysis.sarif +.direnv/ diff --git a/CHANGELOG.md b/CHANGELOG.md index 7e6e4b1..57e40fe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ Notable changes are recorded here. +# WoofWare.Myriad.Plugins 2.1.45, WoofWare.Myriad.Plugins.Attributes 3.1.7 + +The NuGet packages are now attested to through [GitHub Attestations](https://github.blog/2024-05-02-introducing-artifact-attestations-now-in-public-beta/). +You can run `gh attestation verify ~/.nuget/packages/woofware.myriad.plugins/2.1.45/woofware.myriad.plugins.2.1.45.nupkg -o Smaug123`, for example, to verify with GitHub that the GitHub Actions pipeline on this repository produced a nupkg file with the same hash as the one you were served from NuGet. + # WoofWare.Myriad.Plugins 2.1.33 `JsonParse` can now deserialize the discriminated unions which `JsonSerialize` wrote out. diff --git a/ConsumePlugin/GeneratedPureGymDto.fs b/ConsumePlugin/GeneratedPureGymDto.fs index 34b4c8b..b9931c4 100644 --- a/ConsumePlugin/GeneratedPureGymDto.fs +++ b/ConsumePlugin/GeneratedPureGymDto.fs @@ -148,6 +148,7 @@ module GymLocation = reraise () else reraise () + |> LanguagePrimitives.FloatWithMeasure let arg_0 = try diff --git a/ConsumePlugin/PureGymDto.fs b/ConsumePlugin/PureGymDto.fs index cc21469..57dddea 100644 --- a/ConsumePlugin/PureGymDto.fs +++ b/ConsumePlugin/PureGymDto.fs @@ -19,13 +19,16 @@ type GymAccessOptions = QrCodeAccess : bool } +[] +type measure + [] type GymLocation = { [] Longitude : float [] - Latitude : float + Latitude : float } [] diff --git a/WoofWare.Myriad.Plugins.Test/PureGymDtos.fs b/WoofWare.Myriad.Plugins.Test/PureGymDtos.fs index 0b9711b..433c4f0 100644 --- a/WoofWare.Myriad.Plugins.Test/PureGymDtos.fs +++ b/WoofWare.Myriad.Plugins.Test/PureGymDtos.fs @@ -58,7 +58,7 @@ module PureGymDtos = [ """{"latitude": 1.0, "longitude": 3.0}""", { - GymLocation.Latitude = 1.0 + GymLocation.Latitude = 1.0 Longitude = 3.0 } ] @@ -96,7 +96,7 @@ module PureGymDtos = Location = { Longitude = -0.110252 - Latitude = 51.480401 + Latitude = 51.480401 } TimeZone = "Europe/London" ReopenDate = "2021-04-12T00:00:00+01 Europe/London" diff --git a/WoofWare.Myriad.Plugins/JsonParseGenerator.fs b/WoofWare.Myriad.Plugins/JsonParseGenerator.fs index 565e431..d942716 100644 --- a/WoofWare.Myriad.Plugins/JsonParseGenerator.fs +++ b/WoofWare.Myriad.Plugins/JsonParseGenerator.fs @@ -140,6 +140,47 @@ module internal JsonParseGenerator = failwithf $"Unable to parse the key type %+A{desiredType} of a JSON object. Keys are strings, and this plugin does not know how to convert to that from a string." + let private parseNumberType + (options : JsonParseOption) + (propertyName : SynExpr option) + (node : SynExpr) + (typeName : string) + = + let basic = asValueGetValue propertyName typeName node + + match options.JsonNumberHandlingArg with + | None -> basic + | Some option -> + let cond = + SynExpr.DotGet (SynExpr.createIdent "exc", range0, SynLongIdent.createS "Message", range0) + |> SynExpr.callMethodArg "Contains" (SynExpr.CreateConst "cannot be converted to") + + let handler = + asValueGetValue propertyName "string" node + |> SynExpr.pipeThroughFunction (SynExpr.createLongIdent' (parseFunction typeName)) + |> SynExpr.ifThenElse + (SynExpr.equals + option + (SynExpr.createLongIdent + [ + "System" + "Text" + "Json" + "Serialization" + "JsonNumberHandling" + "AllowReadingFromString" + ])) + SynExpr.reraise + |> SynExpr.ifThenElse cond SynExpr.reraise + + basic + |> SynExpr.pipeThroughTryWith + (SynPat.IsInst ( + SynType.LongIdent (SynLongIdent.createS' [ "System" ; "InvalidOperationException" ]), + range0 + )) + handler + /// Given `node.["town"]`, for example, choose how to obtain a JSON value from it. /// The property name is used in error messages at runtime to show where a JSON /// parse error occurred; supply `None` to indicate "don't validate". @@ -168,41 +209,7 @@ module internal JsonParseGenerator = node |> asValueGetValue propertyName "string" |> SynExpr.pipeThroughFunction (SynExpr.createLongIdent [ "System" ; "DateTime" ; "Parse" ]) - | NumberType typeName -> - let basic = asValueGetValue propertyName typeName node - - match options.JsonNumberHandlingArg with - | None -> basic - | Some option -> - let cond = - SynExpr.DotGet (SynExpr.createIdent "exc", range0, SynLongIdent.createS "Message", range0) - |> SynExpr.callMethodArg "Contains" (SynExpr.CreateConst "cannot be converted to") - - let handler = - asValueGetValue propertyName "string" node - |> SynExpr.pipeThroughFunction (SynExpr.createLongIdent' (parseFunction typeName)) - |> SynExpr.ifThenElse - (SynExpr.equals - option - (SynExpr.createLongIdent - [ - "System" - "Text" - "Json" - "Serialization" - "JsonNumberHandling" - "AllowReadingFromString" - ])) - SynExpr.reraise - |> SynExpr.ifThenElse cond SynExpr.reraise - - basic - |> SynExpr.pipeThroughTryWith - (SynPat.IsInst ( - SynType.LongIdent (SynLongIdent.createS' [ "System" ; "InvalidOperationException" ]), - range0 - )) - handler + | NumberType typeName -> parseNumberType options propertyName node typeName | PrimitiveType typeName -> asValueGetValueIdent propertyName typeName node | OptionType ty -> parseNode None options ty (SynExpr.createIdent "v") @@ -261,6 +268,14 @@ module internal JsonParseGenerator = |> SynExpr.callMethod "ToJsonString" |> SynExpr.paren |> SynExpr.applyFunction (SynExpr.createLongIdent [ "System" ; "Numerics" ; "BigInteger" ; "Parse" ]) + | Measure (_measure, primType) -> + let qualified = + match Primitives.qualifyType primType with + | None -> failwith $"did not recognise type %s{primType} to assign measure" + | Some t -> t + + parseNumberType options propertyName node primType + |> SynExpr.pipeThroughFunction (Measure.getLanguagePrimitivesMeasure qualified) | _ -> // Let's just hope that we've also got our own type annotation! let typeName = diff --git a/WoofWare.Myriad.Plugins/Measure.fs b/WoofWare.Myriad.Plugins/Measure.fs new file mode 100644 index 0000000..7f388da --- /dev/null +++ b/WoofWare.Myriad.Plugins/Measure.fs @@ -0,0 +1,24 @@ +namespace WoofWare.Myriad.Plugins + +open Fantomas.FCS.Syntax + +[] +module internal Measure = + + let getLanguagePrimitivesMeasure (typeName : LongIdent) : SynExpr = + match typeName |> List.map _.idText with + | [ "System" ; "Single" ] -> [ "LanguagePrimitives" ; "Float32WithMeasure" ] + | [ "System" ; "Double" ] -> [ "LanguagePrimitives" ; "FloatWithMeasure" ] + | [ "System" ; "Byte" ] -> [ "LanguagePrimitives" ; "ByteWithMeasure" ] + | [ "System" ; "SByte" ] -> [ "LanguagePrimitives" ; "SByteWithMeasure" ] + | [ "System" ; "Int16" ] -> [ "LanguagePrimitives" ; "Int16WithMeasure" ] + | [ "System" ; "Int32" ] -> [ "LanguagePrimitives" ; "Int32WithMeasure" ] + | [ "System" ; "Int64" ] -> [ "LanguagePrimitives" ; "Int64WithMeasure" ] + | [ "System" ; "UInt16" ] -> [ "LanguagePrimitives" ; "UInt16WithMeasure" ] + | [ "System" ; "UInt32" ] -> [ "LanguagePrimitives" ; "UInt32WithMeasure" ] + | [ "System" ; "UInt64" ] -> [ "LanguagePrimitives" ; "UInt64WithMeasure" ] + | l -> + let l = String.concat "." l + failwith $"unrecognised type for measure: %s{l}" + + |> SynExpr.createLongIdent diff --git a/WoofWare.Myriad.Plugins/SynExpr/SynExpr.fs b/WoofWare.Myriad.Plugins/SynExpr/SynExpr.fs index e631d98..15e1e14 100644 --- a/WoofWare.Myriad.Plugins/SynExpr/SynExpr.fs +++ b/WoofWare.Myriad.Plugins/SynExpr/SynExpr.fs @@ -87,6 +87,20 @@ module internal SynExpr = ) |> applyTo b + /// {a} * {b} + let times (a : SynExpr) (b : SynExpr) = + SynExpr.CreateAppInfix ( + SynExpr.CreateLongIdent ( + SynLongIdent.SynLongIdent ( + Ident.CreateLong "op_Multiply", + [], + [ Some (IdentTrivia.OriginalNotation "*") ] + ) + ), + a + ) + |> applyTo b + let rec stripOptionalParen (expr : SynExpr) : SynExpr = match expr with | SynExpr.Paren (expr, _, _, _) -> stripOptionalParen expr diff --git a/WoofWare.Myriad.Plugins/SynExpr/SynType.fs b/WoofWare.Myriad.Plugins/SynExpr/SynType.fs index 7f273fe..12586cd 100644 --- a/WoofWare.Myriad.Plugins/SynExpr/SynType.fs +++ b/WoofWare.Myriad.Plugins/SynExpr/SynType.fs @@ -197,6 +197,17 @@ module internal SynTypePatterns = | _ -> None | _ -> None + let (|Measure|_|) (fieldType : SynType) : (Ident * string) option = + match fieldType with + | SynType.App (NumberType outer, + _, + [ SynType.LongIdent (SynLongIdent.SynLongIdent ([ ident ], _, _)) ], + _, + _, + _, + _) -> Some (ident, outer) + | _ -> None + let (|DateOnly|_|) (fieldType : SynType) = match fieldType with | SynType.LongIdent (SynLongIdent.SynLongIdent (ident, _, _)) -> diff --git a/WoofWare.Myriad.Plugins/WoofWare.Myriad.Plugins.fsproj b/WoofWare.Myriad.Plugins/WoofWare.Myriad.Plugins.fsproj index b0a90a0..08369d9 100644 --- a/WoofWare.Myriad.Plugins/WoofWare.Myriad.Plugins.fsproj +++ b/WoofWare.Myriad.Plugins/WoofWare.Myriad.Plugins.fsproj @@ -46,6 +46,7 @@ +