From 4a0650b4e217885aa890a9c6eda49e5d5365232c Mon Sep 17 00:00:00 2001 From: Nicolas Perriault Date: Fri, 20 Sep 2024 07:55:09 +0200 Subject: [PATCH] fix: decode and validate all optionals. --- src/Data/Common/DecodeUtils.elm | 12 ++++++++ src/Data/Food/Process.elm | 5 ++-- src/Data/Food/Query.elm | 7 +++-- src/Data/Textile/Query.elm | 51 ++++++++++++++------------------- 4 files changed, 41 insertions(+), 34 deletions(-) create mode 100644 src/Data/Common/DecodeUtils.elm diff --git a/src/Data/Common/DecodeUtils.elm b/src/Data/Common/DecodeUtils.elm new file mode 100644 index 000000000..221d8b782 --- /dev/null +++ b/src/Data/Common/DecodeUtils.elm @@ -0,0 +1,12 @@ +module Data.Common.DecodeUtils exposing (strictOptional) + +import Json.Decode exposing (Decoder) +import Json.Decode.Extra as DE + + +{-| A stricter Decode.maybe using Json.Decode.Extra's optionalField here because we want +a failure when a Maybe decoded field value is invalid. +-} +strictOptional : String -> Decoder a -> Decoder (Maybe a -> b) -> Decoder b +strictOptional field decoder = + DE.andMap (DE.optionalField field decoder) diff --git a/src/Data/Food/Process.elm b/src/Data/Food/Process.elm index a2bee15d6..b2c366ea9 100644 --- a/src/Data/Food/Process.elm +++ b/src/Data/Food/Process.elm @@ -17,6 +17,7 @@ module Data.Food.Process exposing , nameToString ) +import Data.Common.DecodeUtils as DU import Data.Impact as Impact exposing (Impacts) import Json.Decode as Decode exposing (Decoder) import Json.Decode.Extra as DE @@ -184,8 +185,8 @@ decodeProcess : Decoder Impact.Impacts -> Decoder Process decodeProcess impactsDecoder = Decode.succeed Process |> Pipe.required "category" decodeCategory - |> Pipe.optional "comment" (Decode.maybe Decode.string) Nothing - |> Pipe.optional "displayName" (Decode.maybe Decode.string) Nothing + |> DU.strictOptional "comment" Decode.string + |> DU.strictOptional "displayName" Decode.string |> Pipe.required "id" Decode.string |> Pipe.required "identifier" decodeIdentifier |> Pipe.required "impacts" impactsDecoder diff --git a/src/Data/Food/Query.elm b/src/Data/Food/Query.elm index 360643e40..be6ce2cfb 100644 --- a/src/Data/Food/Query.elm +++ b/src/Data/Food/Query.elm @@ -24,6 +24,7 @@ module Data.Food.Query exposing ) import Base64 +import Data.Common.DecodeUtils as DU import Data.Country as Country import Data.Food.Ingredient as Ingredient import Data.Food.Preparation as Preparation @@ -102,11 +103,11 @@ buildApiQuery clientUrl query = decode : Decoder Query decode = Decode.succeed Query - |> Pipe.optional "distribution" (Decode.maybe Retail.decode) Nothing + |> DU.strictOptional "distribution" Retail.decode |> Pipe.required "ingredients" (Decode.list decodeIngredient) |> Pipe.optional "packaging" (Decode.list decodeProcess) [] |> Pipe.optional "preparation" (Decode.list Preparation.decodeId) [] - |> Pipe.optional "transform" (Decode.maybe decodeProcess) Nothing + |> DU.strictOptional "transform" decodeProcess decodePlaneTransport : Decoder Ingredient.PlaneTransport @@ -153,7 +154,7 @@ decodeProcess = decodeIngredient : Decoder IngredientQuery decodeIngredient = Decode.succeed IngredientQuery - |> Pipe.optional "country" (Decode.maybe Country.decodeCode) Nothing + |> DU.strictOptional "country" Country.decodeCode |> Pipe.required "id" Ingredient.decodeId |> Pipe.required "mass" decodeMassInGrams |> Pipe.optional "byPlane" decodePlaneTransport Ingredient.PlaneNotApplicable diff --git a/src/Data/Textile/Query.elm b/src/Data/Textile/Query.elm index 3a31853b5..4f5dfc7f6 100644 --- a/src/Data/Textile/Query.elm +++ b/src/Data/Textile/Query.elm @@ -24,6 +24,7 @@ module Data.Textile.Query exposing ) import Base64 +import Data.Common.DecodeUtils as DU import Data.Country as Country import Data.Split as Split exposing (Split) import Data.Textile.DyeingMedium as DyeingMedium exposing (DyeingMedium) @@ -37,7 +38,6 @@ import Data.Textile.Product as Product exposing (Product) import Data.Textile.Step.Label as Label exposing (Label) import Data.Unit as Unit import Json.Decode as Decode exposing (Decoder) -import Json.Decode.Extra as DE import Json.Decode.Pipeline as Pipe import Json.Encode as Encode import List.Extra as LE @@ -111,46 +111,39 @@ buildApiQuery clientUrl query = decode : Decoder Query decode = Decode.succeed Query - |> strictOptional "airTransportRatio" Split.decodeFloat - |> strictOptional "business" Economics.decodeBusiness - |> strictOptional "countryDyeing" Country.decodeCode - |> strictOptional "countryFabric" Country.decodeCode - |> strictOptional "countryMaking" Country.decodeCode - |> strictOptional "countrySpinning" Country.decodeCode + |> DU.strictOptional "airTransportRatio" Split.decodeFloat + |> DU.strictOptional "business" Economics.decodeBusiness + |> DU.strictOptional "countryDyeing" Country.decodeCode + |> DU.strictOptional "countryFabric" Country.decodeCode + |> DU.strictOptional "countryMaking" Country.decodeCode + |> DU.strictOptional "countrySpinning" Country.decodeCode |> Pipe.optional "disabledSteps" (Decode.list Label.decodeFromCode) [] - |> strictOptional "dyeingMedium" DyeingMedium.decode - |> strictOptional "fabricProcess" Fabric.decode - |> strictOptional "fading" Decode.bool - |> strictOptional "makingComplexity" MakingComplexity.decode - |> strictOptional "makingDeadStock" Split.decodeFloat - |> strictOptional "makingWaste" Split.decodeFloat + |> DU.strictOptional "dyeingMedium" DyeingMedium.decode + |> DU.strictOptional "fabricProcess" Fabric.decode + |> DU.strictOptional "fading" Decode.bool + |> DU.strictOptional "makingComplexity" MakingComplexity.decode + |> DU.strictOptional "makingDeadStock" Split.decodeFloat + |> DU.strictOptional "makingWaste" Split.decodeFloat |> Pipe.required "mass" (Decode.map Mass.kilograms Decode.float) |> Pipe.required "materials" (Decode.list decodeMaterialQuery) - |> strictOptional "numberOfReferences" Decode.int - |> strictOptional "physicalDurability" Unit.decodePhysicalDurability - |> strictOptional "price" Economics.decodePrice - |> strictOptional "printing" Printing.decode + |> DU.strictOptional "numberOfReferences" Decode.int + |> DU.strictOptional "physicalDurability" Unit.decodePhysicalDurability + |> DU.strictOptional "price" Economics.decodePrice + |> DU.strictOptional "printing" Printing.decode |> Pipe.required "product" (Decode.map Product.Id Decode.string) - |> strictOptional "surfaceMass" Unit.decodeSurfaceMass - |> strictOptional "traceability" Decode.bool + |> DU.strictOptional "surfaceMass" Unit.decodeSurfaceMass + |> DU.strictOptional "traceability" Decode.bool |> Pipe.optional "upcycled" Decode.bool False - |> strictOptional "yarnSize" Unit.decodeYarnSize - - -strictOptional : String -> Decoder a -> Decoder (Maybe a -> b) -> Decoder b -strictOptional field decoder = - -- Note: Using Json.Decode.Extra's optionalField here because we want - -- a failure when a Maybe decoded field value is invalid - DE.andMap (DE.optionalField field decoder) + |> DU.strictOptional "yarnSize" Unit.decodeYarnSize decodeMaterialQuery : Decoder MaterialQuery decodeMaterialQuery = Decode.succeed MaterialQuery - |> strictOptional "country" Country.decodeCode + |> DU.strictOptional "country" Country.decodeCode |> Pipe.required "id" (Decode.map Material.Id Decode.string) |> Pipe.required "share" Split.decodeFloat - |> strictOptional "spinning" Spinning.decode + |> DU.strictOptional "spinning" Spinning.decode encode : Query -> Encode.Value