From 40cc2641697665c8e5499e184b4f32b98a5827b4 Mon Sep 17 00:00:00 2001 From: Martin Stewart Date: Wed, 8 May 2024 17:37:09 +0200 Subject: [PATCH 1/8] Add miniBill codecs --- src/Internal/Builtin/MiniBillCodec.elm | 112 +++++++++++++++++++++++++ src/NoDebug/TodoItForMe.elm | 2 + 2 files changed, 114 insertions(+) create mode 100644 src/Internal/Builtin/MiniBillCodec.elm diff --git a/src/Internal/Builtin/MiniBillCodec.elm b/src/Internal/Builtin/MiniBillCodec.elm new file mode 100644 index 0000000..cf92cb6 --- /dev/null +++ b/src/Internal/Builtin/MiniBillCodec.elm @@ -0,0 +1,112 @@ +module Internal.Builtin.MiniBillCodec exposing (codeGen) + +import CodeGenerator exposing (CodeGenerator) +import Elm.CodeGen as CG +import ResolvedType +import String.Extra +import TypePattern exposing (TypePattern(..)) + + +val = + CG.fqVal [ "Codec" ] + + +fn1 name arg = + CG.apply [ CG.fqVal [ "Codec" ] name, arg ] + + +codeGen : CodeGenerator +codeGen = + CodeGenerator.define + { id = "miniBill/elm-codec/Codec" + , dependency = "miniBill/elm-codec" + , typePattern = Typed [ "Codec" ] "Codec" [ GenericType "e", Target ] + , makeName = \name -> String.Extra.decapitalize name ++ "Codec" + } + [ CodeGenerator.int (val "int") + , CodeGenerator.float (val "float") + , CodeGenerator.string (val "string") + , CodeGenerator.list (fn1 "list") + , CodeGenerator.maybe (fn1 "maybe") + , CodeGenerator.dict (\key value -> CG.apply [ val "dict", key, value ]) + , CodeGenerator.unit (val "unit") + , CodeGenerator.tuple (\arg1 arg2 -> CG.apply [ val "tuple", arg1, arg2 ]) + , CodeGenerator.triple (\arg1 arg2 arg3 -> CG.apply [ val "tuple", arg1, arg2, arg3 ]) + , CodeGenerator.customType + (\ctors exprs -> + CG.pipe + (CG.apply + [ val "custom" + , CG.lambda (List.map (\( ctorRef, _ ) -> CG.varPattern (String.Extra.decapitalize ctorRef.name ++ "Encoder")) ctors ++ [ CG.varPattern "value" ]) + (ctors + |> List.map + (\( ctorRef, arguments ) -> + ( CG.fqNamedPattern ctorRef.modulePath ctorRef.name (thingsToPatterns arguments) + , CG.apply (CG.val (String.Extra.decapitalize ctorRef.name ++ "Encoder") :: thingsToValues arguments) + ) + ) + |> CG.caseExpr (CG.val "value") + ) + ] + ) + (List.map Tuple.second exprs ++ [ val "buildCustom" ]) + ) + , CodeGenerator.combiner + (\t fn exprs -> + case t of + ResolvedType.Opaque _ args -> + Just <| CG.apply ([ val ("variant" ++ String.fromInt (List.length args)), fn ] ++ exprs) + + ResolvedType.AnonymousRecord _ fields -> + CG.pipe + (CG.apply [ val "object", fn ]) + (List.map2 + (\( field, _ ) expr -> + CG.apply + [ val "field" + , CG.string field + , CG.accessFun ("." ++ field) + , expr + ] + ) + fields + exprs + ++ [ val "buildObject" ] + ) + |> Just + + ResolvedType.TypeAlias _ _ (ResolvedType.AnonymousRecord _ fields) -> + CG.pipe (CG.apply [ val "object", fn ]) + (List.map2 + (\( field, _ ) expr -> + CG.apply + [ val "field" + , CG.string field + , CG.accessFun ("." ++ field) + , expr + ] + ) + fields + exprs + ++ [ val "buildObject" ] + ) + |> Just + + _ -> + Nothing + ) + , CodeGenerator.lambdaBreaker + (\expr -> + fn1 "lazy" (CG.lambda [ CG.unitPattern ] expr) + ) + ] + + +thingsToPatterns : List a -> List CG.Pattern +thingsToPatterns = + List.indexedMap (\i _ -> CG.varPattern ("arg" ++ String.fromInt i)) + + +thingsToValues : List a -> List CG.Expression +thingsToValues = + List.indexedMap (\i _ -> CG.val ("arg" ++ String.fromInt i)) diff --git a/src/NoDebug/TodoItForMe.elm b/src/NoDebug/TodoItForMe.elm index 24c53c4..dec3a3d 100644 --- a/src/NoDebug/TodoItForMe.elm +++ b/src/NoDebug/TodoItForMe.elm @@ -26,6 +26,7 @@ import Internal.Builtin.Fuzzer import Internal.Builtin.JsonDecoder import Internal.Builtin.JsonEncoder import Internal.Builtin.ListAllVariants +import Internal.Builtin.MiniBillCodec import Internal.Builtin.Random import Internal.Builtin.ToString import Internal.CodeGenTodo exposing (CodeGenTodo) @@ -80,6 +81,7 @@ rule generators = , Internal.Builtin.JsonEncoder.codeGen , Internal.Builtin.JsonDecoder.codeGen , Internal.Builtin.Codec.codeGen + , Internal.Builtin.MiniBillCodec.codeGen , Internal.Builtin.Fuzzer.codeGen , Internal.Builtin.ListAllVariants.codeGen , Internal.Builtin.ToString.codeGen From 8855410e8b80020b22f2952744981adbb4750829 Mon Sep 17 00:00:00 2001 From: Martin Stewart Date: Wed, 8 May 2024 17:40:35 +0200 Subject: [PATCH 2/8] Fix mistake --- src/Internal/Builtin/MiniBillCodec.elm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Internal/Builtin/MiniBillCodec.elm b/src/Internal/Builtin/MiniBillCodec.elm index cf92cb6..2456664 100644 --- a/src/Internal/Builtin/MiniBillCodec.elm +++ b/src/Internal/Builtin/MiniBillCodec.elm @@ -20,7 +20,7 @@ codeGen = CodeGenerator.define { id = "miniBill/elm-codec/Codec" , dependency = "miniBill/elm-codec" - , typePattern = Typed [ "Codec" ] "Codec" [ GenericType "e", Target ] + , typePattern = Typed [ "Codec" ] "Codec" [ Target ] , makeName = \name -> String.Extra.decapitalize name ++ "Codec" } [ CodeGenerator.int (val "int") From 7198a783039c9d241f4011f5c38e78ac6ab8ff37 Mon Sep 17 00:00:00 2001 From: Martin Stewart Date: Wed, 8 May 2024 17:43:05 +0200 Subject: [PATCH 3/8] Add missing variant parameter --- src/Internal/Builtin/MiniBillCodec.elm | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/Internal/Builtin/MiniBillCodec.elm b/src/Internal/Builtin/MiniBillCodec.elm index 2456664..942d3e4 100644 --- a/src/Internal/Builtin/MiniBillCodec.elm +++ b/src/Internal/Builtin/MiniBillCodec.elm @@ -54,8 +54,15 @@ codeGen = , CodeGenerator.combiner (\t fn exprs -> case t of - ResolvedType.Opaque _ args -> - Just <| CG.apply ([ val ("variant" ++ String.fromInt (List.length args)), fn ] ++ exprs) + ResolvedType.Opaque reference args -> + CG.apply + ([ val ("variant" ++ String.fromInt (List.length args)) + , CG.string reference.name + , fn + ] + ++ exprs + ) + |> Just ResolvedType.AnonymousRecord _ fields -> CG.pipe From e904f196e5ee21487accb5e4fa3e8ce186cb3e28 Mon Sep 17 00:00:00 2001 From: Martin Stewart Date: Wed, 8 May 2024 18:56:50 +0200 Subject: [PATCH 4/8] Work on adding tests --- src/Internal/Builtin/MiniBillCodec.elm | 2 +- tests/MiniBillCodecCodeGenTest.elm | 608 +++++++++++++++++++++++++ 2 files changed, 609 insertions(+), 1 deletion(-) create mode 100644 tests/MiniBillCodecCodeGenTest.elm diff --git a/src/Internal/Builtin/MiniBillCodec.elm b/src/Internal/Builtin/MiniBillCodec.elm index 942d3e4..c87b041 100644 --- a/src/Internal/Builtin/MiniBillCodec.elm +++ b/src/Internal/Builtin/MiniBillCodec.elm @@ -27,7 +27,7 @@ codeGen = , CodeGenerator.float (val "float") , CodeGenerator.string (val "string") , CodeGenerator.list (fn1 "list") - , CodeGenerator.maybe (fn1 "maybe") + , CodeGenerator.maybe (fn1 "nullable") , CodeGenerator.dict (\key value -> CG.apply [ val "dict", key, value ]) , CodeGenerator.unit (val "unit") , CodeGenerator.tuple (\arg1 arg2 -> CG.apply [ val "tuple", arg1, arg2 ]) diff --git a/tests/MiniBillCodecCodeGenTest.elm b/tests/MiniBillCodecCodeGenTest.elm new file mode 100644 index 0000000..a4c2024 --- /dev/null +++ b/tests/MiniBillCodecCodeGenTest.elm @@ -0,0 +1,608 @@ +module MiniBillCodecCodeGenTest exposing (suite) + +import CodeGenerator.Test exposing (FakeDependency, codeGenTest) +import Test exposing (Test, describe) + + +elmCodec : FakeDependency +elmCodec = + CodeGenerator.Test.fakeDependency + { name = "miniBill/elm-codec" + , dependencies = [] + , modules = + [ { name = "Codec" + , values = + [ ( "decoder", "Codec.Codec a -> Json.Decode.Decoder a" ) + , ( "decodeString", "Codec.Codec a -> String -> Result Json.Decode.Error a" ) + , ( "decodeValue", "Codec.Codec a -> Json.Decode.Value -> Result Json.Decode.Error a" ) + , ( "encoder", "Codec.Codec a -> a -> Json.Decode.Value" ) + , ( "encodeToString", "Int -> Codec.Codec a -> a -> String" ) + , ( "encodeToValue", "Codec.Codec a -> a -> Json.Decode.Value" ) + , ( "build", "(a -> Json.Decode.Value) -> Json.Decode.Decoder a -> Codec.Codec a" ) + , ( "string", "Codec.Codec String" ) + , ( "bool", "Codec.Codec Bool" ) + , ( "int", "Codec.Codec Int" ) + , ( "float", "Codec.Codec Float" ) + , ( "char", "Codec.Codec Char" ) + , ( "enum", "Codec.Codec a -> List.List ( a, b ) -> Codec.Codec b" ) + , ( "composite", "((b -> Json.Decode.Value) -> (a -> Json.Decode.Value)) -> (Json.Decode.Decoder b -> Json.Decode.Decoder a) -> Codec.Codec b -> Codec.Codec a" ) + , ( "maybe", "Codec.Codec a -> Codec.Codec (Maybe.Maybe a)" ) + , ( "nullable", "Codec.Codec a -> Codec.Codec (Maybe.Maybe a)" ) + , ( "list", "Codec.Codec a -> Codec.Codec (List.List a)" ) + , ( "array", "Codec.Codec a -> Codec.Codec (Array.Array a)" ) + , ( "dict", "Codec.Codec a -> Codec.Codec (Dict.Dict String a)" ) + , ( "set", "Codec.Codec comparable -> Codec.Codec (Set.Set comparable)" ) + , ( "tuple", "Codec.Codec a -> Codec.Codec b -> Codec.Codec ( a, b )" ) + , ( "triple", "Codec.Codec a -> Codec.Codec b -> Codec.Codec c -> Codec.Codec ( a, b, c )" ) + , ( "result", "Codec.Codec error -> Codec.Codec value -> Codec.Codec (Result.Result error value)" ) + , ( "object", "b -> Codec.ObjectCodec a b" ) + , ( "field", "String -> (a -> f) -> Codec.Codec f -> Codec.ObjectCodec a (f -> b) -> Codec.ObjectCodec a b" ) + , ( "maybeField", "String -> (a -> Maybe.Maybe f) -> Codec.Codec f -> Codec.ObjectCodec a (Maybe.Maybe f -> b) -> Codec.ObjectCodec a b" ) + , ( "optionalField", "String -> (a -> Maybe.Maybe f) -> Codec.Codec f -> Codec.ObjectCodec a (Maybe.Maybe f -> b) -> Codec.ObjectCodec a b" ) + , ( "optionalNullableField", "String -> (a -> Maybe.Maybe f) -> Codec.Codec f -> Codec.ObjectCodec a (Maybe.Maybe f -> b) -> Codec.ObjectCodec a b" ) + , ( "nullableField", "String -> (a -> Maybe.Maybe f) -> Codec.Codec f -> Codec.ObjectCodec a (Maybe.Maybe f -> b) -> Codec.ObjectCodec a b" ) + , ( "buildObject", "Codec.ObjectCodec a a -> Codec.Codec a" ) + , ( "custom", "match -> Codec.CustomCodec match value" ) + , ( "variant", "String -> ((List.List Json.Decode.Value -> Json.Decode.Value) -> a) -> Json.Decode.Decoder v -> Codec.CustomCodec (a -> b) v -> Codec.CustomCodec b v" ) + , ( "variant0", "String -> v -> Codec.CustomCodec (Json.Decode.Value -> a) v -> Codec.CustomCodec a v" ) + , ( "variant1", "String -> (a -> v) -> Codec.Codec a -> Codec.CustomCodec ((a -> Json.Decode.Value) -> b) v -> Codec.CustomCodec b v" ) + , ( "variant2", "String -> (a -> b -> v) -> Codec.Codec a -> Codec.Codec b -> Codec.CustomCodec ((a -> b -> Json.Decode.Value) -> c) v -> Codec.CustomCodec c v" ) + , ( "variant3", "String -> (a -> b -> c -> v) -> Codec.Codec a -> Codec.Codec b -> Codec.Codec c -> Codec.CustomCodec ((a -> b -> c -> Json.Decode.Value) -> partial) v -> Codec.CustomCodec partial v" ) + , ( "variant4", "String -> (a -> b -> c -> d -> v) -> Codec.Codec a -> Codec.Codec b -> Codec.Codec c -> Codec.Codec d -> Codec.CustomCodec ((a -> b -> c -> d -> Json.Decode.Value) -> partial) v -> Codec.CustomCodec partial v" ) + , ( "variant5", "String -> (a -> b -> c -> d -> e -> v) -> Codec.Codec a -> Codec.Codec b -> Codec.Codec c -> Codec.Codec d -> Codec.Codec e -> Codec.CustomCodec ((a -> b -> c -> d -> e -> Json.Decode.Value) -> partial) v -> Codec.CustomCodec partial v" ) + , ( "variant6", "String -> (a -> b -> c -> d -> e -> f -> v) -> Codec.Codec a -> Codec.Codec b -> Codec.Codec c -> Codec.Codec d -> Codec.Codec e -> Codec.Codec f -> Codec.CustomCodec ((a -> b -> c -> d -> e -> f -> Json.Decode.Value) -> partial) v -> Codec.CustomCodec partial v" ) + , ( "variant7", "String -> (a -> b -> c -> d -> e -> f -> g -> v) -> Codec.Codec a -> Codec.Codec b -> Codec.Codec c -> Codec.Codec d -> Codec.Codec e -> Codec.Codec f -> Codec.Codec g -> Codec.CustomCodec ((a -> b -> c -> d -> e -> f -> g -> Json.Decode.Value) -> partial) v -> Codec.CustomCodec partial v" ) + , ( "variant8", "String -> (a -> b -> c -> d -> e -> f -> g -> h -> v) -> Codec.Codec a -> Codec.Codec b -> Codec.Codec c -> Codec.Codec d -> Codec.Codec e -> Codec.Codec f -> Codec.Codec g -> Codec.Codec h -> Codec.CustomCodec ((a -> b -> c -> d -> e -> f -> g -> h -> Json.Decode.Value) -> partial) v -> Codec.CustomCodec partial v" ) + , ( "buildCustom", "Codec.CustomCodec (a -> Json.Decode.Value) a -> Codec.Codec a" ) + , ( "oneOf", "Codec.Codec a -> List.List (Codec.Codec a) -> Codec.Codec a" ) + , ( "map", "(a -> b) -> (b -> a) -> Codec.Codec a -> Codec.Codec b" ) + , ( "fail", "String -> Codec.Codec a" ) + , ( "andThen", "(a -> Codec.Codec b) -> (b -> a) -> Codec.Codec a -> Codec.Codec b" ) + , ( "recursive", "(Codec.Codec a -> Codec.Codec a) -> Codec.Codec a" ) + , ( "succeed", "a -> Codec.Codec a" ) + , ( "constant", "a -> Codec.Codec a" ) + , ( "lazy", "(() -> Codec.Codec a) -> Codec.Codec a" ) + , ( "value", "Codec.Codec Json.Decode.Value" ) + ] + } + ] + } + + +suite : Test +suite = + describe "Codec code gen todo" + [ codeGenTest "record codec" [ elmCodec ] [] [ """module A exposing (..) + +import Codec exposing (Codec) + +type alias A = { fieldA : Int, fieldB : String, fieldC : B } +type alias B = { fieldD : Float } + +codec : Codec A +codec = + Debug.todo "" +""" ] """module A exposing (..) + +import Codec exposing (Codec) + +type alias A = { fieldA : Int, fieldB : String, fieldC : B } +type alias B = { fieldD : Float } + +codec : Codec A +codec = + Codec.object A + |> Codec.field "fieldA" .fieldA Codec.int + |> Codec.field "fieldB" .fieldB Codec.string + |> Codec.field "fieldC" .fieldC bCodec + |> Codec.buildObject + +bCodec : Codec B +bCodec = + Codec.object B |> Codec.field "fieldD" .fieldD Codec.float |> Codec.buildObject +""" + , codeGenTest "qualified codec" [ elmCodec ] [] [ """module A exposing (..) + +import Codec exposing (Codec) + +type alias A = { fieldA : Int } + +codec : Codec.Codec A +codec = + Debug.todo "" +""" ] """module A exposing (..) + +import Codec exposing (Codec) + +type alias A = { fieldA : Int } + +codec : Codec.Codec A +codec = + Codec.object A |> Codec.field "fieldA" .fieldA Codec.int |> Codec.buildObject +""" + , codeGenTest "custom type codec" + [ elmCodec ] + [] + [ """module A exposing (..) + +import Codec exposing (Codec) + +type MyType + = VariantA + | VariantB Int + | VariantC String String + +codec : Codec MyType +codec = + Debug.todo "" +""" ] + """module A exposing (..) + +import Codec exposing (Codec) + +type MyType + = VariantA + | VariantB Int + | VariantC String String + +codec : Codec MyType +codec = + Codec.custom + (\\variantAEncoder variantBEncoder variantCEncoder value -> + case value of + VariantA -> + variantAEncoder + + VariantB arg0 -> + variantBEncoder arg0 + + VariantC arg0 arg1 -> + variantCEncoder arg0 arg1 + ) + |> Codec.variant0 "VariantA" VariantA + |> Codec.variant1 "VariantB" VariantB Codec.int + |> Codec.variant2 "VariantC" VariantC Codec.string Codec.string + |> Codec.buildCustom +""" + , codeGenTest "custom type in another module" + [ elmCodec ] + [] + [ """module A exposing (..) + +import Codec exposing (Codec) +import OtherModule + +codec : Codec OtherModule.MyType +codec = + Debug.todo "" +""" + , """module OtherModule exposing (..) + +type MyType + = VariantA + | VariantB Int + | VariantC String +""" + ] + """module A exposing (..) + +import Codec exposing (Codec) +import OtherModule + +codec : Codec OtherModule.MyType +codec = + Codec.custom + (\\variantAEncoder variantBEncoder variantCEncoder value -> + case value of + OtherModule.VariantA -> + variantAEncoder + + OtherModule.VariantB arg0 -> + variantBEncoder arg0 + + OtherModule.VariantC arg0 -> + variantCEncoder arg0 + ) + |> Codec.variant0 "VariantA" OtherModule.VariantA + |> Codec.variant1 "VariantB" OtherModule.VariantB Codec.int + |> Codec.variant1 "VariantC" OtherModule.VariantC Codec.string + |> Codec.buildCustom +""" + , codeGenTest "reuse existing codec in another module" + [ elmCodec ] + [] + [ """module A exposing (..) + +import Codec exposing (Codec) +import OtherModule exposing (MyType) + +type alias A = { field : MyType } + +codec : Codec A +codec = + Debug.todo "" +""" + , """module OtherModule exposing (..) + +import Codec exposing (Codec) + +type MyType + = VariantA + | VariantB Int + | VariantC String + +codec : Codec MyType +codec = + Codec.custom + (\\a b c value -> + case value of + VariantA -> + a + VariantB data0 -> + b data0 + VariantC data0 -> + c data0 + ) + |> Codec.variant0 "VariantA" VariantA + |> Codec.variant1 "VariantB" VariantB Codec.int + |> Codec.variant1 "VariantC" VariantC Codec.string + |> Codec.buildCustom""" + ] + """module A exposing (..) + +import Codec exposing (Codec) +import OtherModule exposing (MyType) + +type alias A = { field : MyType } + +codec : Codec A +codec = + Codec.object A |> Codec.field "field" .field OtherModule.codec |> Codec.buildObject +""" + , codeGenTest "maybe codec" [ elmCodec ] [] [ """module A exposing (..) + +import Codec exposing (Codec) + +type alias MyType = { fieldA : Maybe Int } + +codec : Codec MyType +codec = + Debug.todo "" +""" ] """module A exposing (..) + +import Codec exposing (Codec) + +type alias MyType = { fieldA : Maybe Int } + +codec : Codec MyType +codec = + Codec.object MyType |> Codec.field "fieldA" .fieldA (Codec.nullable Codec.int) |> Codec.buildObject +""" + , codeGenTest "dict codec" [ elmCodec ] [] [ """module A exposing (..) + +import Codec exposing (Codec) +import Dict exposing (Dict) + +type alias MyType = + { fieldA : Dict Int String + } + +codec : Codec MyType +codec = + Debug.todo "" +""" ] """module A exposing (..) + +import Codec exposing (Codec) +import Dict exposing (Dict) + +type alias MyType = + { fieldA : Dict Int String + } + +codec : Codec MyType +codec = + Codec.object MyType |> Codec.field "fieldA" .fieldA (Codec.dict Codec.int Codec.string) |> Codec.buildObject +""" + , codeGenTest "nested record codec" + [ elmCodec ] + [] + [ """module A exposing (..) + +import Codec exposing (Codec) + +type alias MyType = { fieldA : { field0 : Int, field1 : () } } + +codec : Codec MyType +codec = + Debug.todo "" +""" ] + """module A exposing (..) + +import Codec exposing (Codec) + +type alias MyType = { fieldA : { field0 : Int, field1 : () } } + +codec : Codec MyType +codec = + Codec.object MyType + |> Codec.field + "fieldA" + .fieldA + (Codec.object (\\field0 field1 -> { field0 = field0, field1 = field1 }) + |> Codec.field "field0" .field0 Codec.int + |> Codec.field "field1" .field1 Codec.unit + |> Codec.buildObject + ) + |> Codec.buildObject +""" + , codeGenTest "add nested codec" [ elmCodec ] [] [ """module A exposing (..) + +import Codec exposing (Codec) + +type alias MyType = { fieldA : MyOtherType } + +type alias MyOtherType = { fieldB : Int } + +codec : Codec MyType +codec = + Debug.todo "" +""" ] """module A exposing (..) + +import Codec exposing (Codec) + +type alias MyType = { fieldA : MyOtherType } + +type alias MyOtherType = { fieldB : Int } + +codec : Codec MyType +codec = + Codec.object MyType |> Codec.field "fieldA" .fieldA myOtherTypeCodec |> Codec.buildObject + +myOtherTypeCodec : Codec MyOtherType +myOtherTypeCodec = + Codec.object MyOtherType |> Codec.field "fieldB" .fieldB Codec.int |> Codec.buildObject +""" + , codeGenTest "nested codec in another module" + [ elmCodec ] + [] + [ """module A exposing (..) + +import Codec exposing (Codec) +import B exposing (B) + +type alias A = { field : B } + +codec : Codec A +codec = + Debug.todo "" +""" + , """module B exposing (..) + +type alias B = { field : Int }""" + ] + """module A exposing (..) + +import Codec exposing (Codec) +import B exposing (B) + +type alias A = { field : B } + +codec : Codec A +codec = + Codec.object A |> Codec.field "field" .field bCodec |> Codec.buildObject + +bCodec : Codec B +bCodec = + Codec.object B |> Codec.field "field" .field Codec.int |> Codec.buildObject +""" + , codeGenTest "twice nested codec in another module" + [ elmCodec ] + [] + [ """module A exposing (..) + +import Codec exposing (Codec) +import B exposing (B) + +type alias A = { field : B } + +codec : Codec A +codec = + Debug.todo "" +""" + , """module B exposing (..) + +type alias B = { field1 : B2 } + +type alias B2 = { field2 : Int }""" + ] + """module A exposing (..) + +import Codec exposing (Codec) +import B exposing (B) + +type alias A = { field : B } + +codec : Codec A +codec = + Codec.object A |> Codec.field "field" .field bCodec |> Codec.buildObject + +bCodec : Codec B +bCodec = + Codec.object B |> Codec.field "field1" .field1 b2Codec |> Codec.buildObject + +b2Codec : Codec B.B2 +b2Codec = + Codec.object B.B2 |> Codec.field "field2" .field2 Codec.int |> Codec.buildObject +""" + , codeGenTest "nested codec in list" + [ elmCodec ] + [] + [ """module A exposing (..) + +import Codec exposing (Codec) +import B exposing (B) + +type alias A = { field : List B } + +codec : Codec A +codec = + Debug.todo "" +""" + , """module B exposing (..) + +type alias B = { field1 : Int }""" + ] + """module A exposing (..) + +import Codec exposing (Codec) +import B exposing (B) + +type alias A = { field : List B } + +codec : Codec A +codec = + Codec.object A |> Codec.field "field" .field (Codec.list bCodec) |> Codec.buildObject + +bCodec : Codec B +bCodec = + Codec.object B |> Codec.field "field1" .field1 Codec.int |> Codec.buildObject +""" + , -- This is not supported. In fact there are 2 features here that are still TODO: + -- 1. Support for generics + -- 2. Support for recursive datatypes + codeGenTest "codec with type parameter" [ elmCodec ] [] [ """module A exposing (..) + +import Codec exposing (Codec) + +type Tree a + = Node (Tree a) + | Leaf a + +codec : Codec a -> Codec (Tree a) +codec codecA = + Debug.todo "" +""" ] """module A exposing (..) + +import Codec exposing (Codec) + +type Tree a + = Node (Tree a) + | Leaf a + +codec : Codec a -> Codec (Tree a) +codec codecA = + Codec.custom + (\\nodeEncoder leafEncoder value -> + case value of + Node arg0 -> + nodeEncoder arg0 + + Leaf arg0 -> + leafEncoder arg0 + ) + |> Codec.variant1 "Node" Node (Codec.lazy (\\() -> codec codecA)) + |> Codec.variant1 "Leaf" Leaf codecA + |> Codec.buildCustom +""" + , codeGenTest "Add codec for type inside tuple" [ elmCodec ] [] [ """module Schema exposing (..) + +import Codec exposing (Codec) + +type alias A = + { a : ( B, Int ) } + +type alias B = { b : Int } + +codec : Codec A +codec = + Debug.todo "" +""" ] """module Schema exposing (..) + +import Codec exposing (Codec) + +type alias A = + { a : ( B, Int ) } + +type alias B = { b : Int } + +codec : Codec A +codec = + Codec.object A |> Codec.field "a" .a (Codec.tuple bCodec Codec.int) |> Codec.buildObject + +bCodec : Codec B +bCodec = + Codec.object B |> Codec.field "b" .b Codec.int |> Codec.buildObject +""" + , codeGenTest "Add codec for type inside nested record" [ elmCodec ] [] [ """module Schema exposing (..) + +import Codec exposing (Codec) + +type alias A = + { a : { b : B } } + +type alias B = { b : Int } + +codec : Codec A +codec = + Debug.todo "" +""" ] """module Schema exposing (..) + +import Codec exposing (Codec) + +type alias A = + { a : { b : B } } + +type alias B = { b : Int } + +codec : Codec A +codec = + Codec.object A + |> Codec.field "a" .a (Codec.object (\\b -> { b = b }) |> Codec.field "b" .b bCodec |> Codec.buildObject) + |> Codec.buildObject + +bCodec : Codec B +bCodec = + Codec.object B |> Codec.field "b" .b Codec.int |> Codec.buildObject +""" + , codeGenTest "CaseId.codec not found regression test" + [ elmCodec ] + [] + [ """module Route exposing (..) +import Codec exposing (Codec) +import CaseId exposing (CaseId) + +type Route + = MyPagesRoute (Maybe.Maybe CaseId) + +routeCodec : Codec Route +routeCodec = + Debug.todo "" +""" + , """module CaseId exposing (CaseId, codec) +import Codec exposing (Codec) +type CaseId = CaseId String + +codec : Codec CaseId +codec = + Codec.string |> Codec.map fromString toString +""" + ] + """module Route exposing (..) +import Codec exposing (Codec) +import CaseId exposing (CaseId) + +type Route + = MyPagesRoute (Maybe.Maybe CaseId) + +routeCodec : Codec Route +routeCodec = + Codec.custom + (\\myPagesRouteEncoder value -> + case value of + MyPagesRoute arg0 -> + myPagesRouteEncoder arg0 + ) + |> Codec.variant1 "MyPagesRoute" MyPagesRoute (Codec.nullable CaseId.codec) + |> Codec.buildCustom +""" + ] From d3610a0df4a7c28a0fe3fa5bdff47983ee4fcce3 Mon Sep 17 00:00:00 2001 From: Martin Stewart Date: Wed, 8 May 2024 19:01:30 +0200 Subject: [PATCH 5/8] Update readme --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index 6afb979..57867f9 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,11 @@ This rule isn't limited to generating toString functions for custom types though Required package: [MartinSStewart/elm-serialize] Type features supported: **All\*** +### `Codec.Codec e MyType` + +Required package: [miniBill/elm-codec] +Type features supported: **All\*** + ### `Csv.Decode.Decoder MyType` Required package: [BrianHicks/elm-csv] From 2fd0e1e31f40f55f7ec6f7e128d093ed9d565dcc Mon Sep 17 00:00:00 2001 From: Martin Stewart Date: Wed, 8 May 2024 19:39:11 +0200 Subject: [PATCH 6/8] Fix dict --- src/Internal/Builtin/MiniBillCodec.elm | 10 +++++++++- tests/MiniBillCodecCodeGenTest.elm | 7 ++++++- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/Internal/Builtin/MiniBillCodec.elm b/src/Internal/Builtin/MiniBillCodec.elm index c87b041..59ad66a 100644 --- a/src/Internal/Builtin/MiniBillCodec.elm +++ b/src/Internal/Builtin/MiniBillCodec.elm @@ -28,7 +28,15 @@ codeGen = , CodeGenerator.string (val "string") , CodeGenerator.list (fn1 "list") , CodeGenerator.maybe (fn1 "nullable") - , CodeGenerator.dict (\key value -> CG.apply [ val "dict", key, value ]) + , CodeGenerator.dict + (\key value -> + CG.apply + [ val "map" + , CG.fqVal [ "Dict" ] "fromList" + , CG.fqVal [ "Dict" ] "toList" + , CG.apply [ val "list", CG.apply [ val "tuple", key, value ] ] + ] + ) , CodeGenerator.unit (val "unit") , CodeGenerator.tuple (\arg1 arg2 -> CG.apply [ val "tuple", arg1, arg2 ]) , CodeGenerator.triple (\arg1 arg2 arg3 -> CG.apply [ val "tuple", arg1, arg2, arg3 ]) diff --git a/tests/MiniBillCodecCodeGenTest.elm b/tests/MiniBillCodecCodeGenTest.elm index a4c2024..a4dc1df 100644 --- a/tests/MiniBillCodecCodeGenTest.elm +++ b/tests/MiniBillCodecCodeGenTest.elm @@ -301,7 +301,12 @@ type alias MyType = codec : Codec MyType codec = - Codec.object MyType |> Codec.field "fieldA" .fieldA (Codec.dict Codec.int Codec.string) |> Codec.buildObject + Codec.object MyType + |> Codec.field + "fieldA" + .fieldA + (Codec.map Dict.fromList Dict.toList (Codec.list (Codec.tuple Codec.int Codec.string))) + |> Codec.buildObject """ , codeGenTest "nested record codec" [ elmCodec ] From c2beb99e3545f1353481467c02b8fbb49f334001 Mon Sep 17 00:00:00 2001 From: Martin Stewart Date: Sat, 11 May 2024 18:33:05 +0200 Subject: [PATCH 7/8] Fix markdown reference --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 57867f9..db2dd4d 100644 --- a/README.md +++ b/README.md @@ -87,6 +87,7 @@ unless there is a `Decoder Color` available in the project or dependencies). \*\* By Enum we mean a custom type where none of the constructors take any arguments. (i.e. `type Semaphore = Red | Yellow | Green`). [MartinSStewart/elm-serialize]: https://package.elm-lang.org/packages/MartinSStewart/elm-serialize/latest/ +[miniBill/elm-codec]: https://package.elm-lang.org/packages/miniBill/elm-codec/latest/ [BrianHicks/elm-csv]: https://package.elm-lang.org/packages/BrianHicks/elm-csv/latest/ [elm/core]: https://package.elm-lang.org/packages/elm/core/latest/ [elm-explorations/test]: https://package.elm-lang.org/packages/elm-explorations/test/latest/ From ab7c86bb8125df99375e44302ff41c3ee9c3f382 Mon Sep 17 00:00:00 2001 From: Jakub Hampl Date: Mon, 13 May 2024 09:30:13 +0100 Subject: [PATCH 8/8] Fixes lazy code for the blessed implementations hack --- src/Internal/Builtin/MiniBillCodec.elm | 2 +- src/Internal/DependencyScanner.elm | 8 ++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/Internal/Builtin/MiniBillCodec.elm b/src/Internal/Builtin/MiniBillCodec.elm index 59ad66a..c4ff9ab 100644 --- a/src/Internal/Builtin/MiniBillCodec.elm +++ b/src/Internal/Builtin/MiniBillCodec.elm @@ -27,7 +27,7 @@ codeGen = , CodeGenerator.float (val "float") , CodeGenerator.string (val "string") , CodeGenerator.list (fn1 "list") - , CodeGenerator.maybe (fn1 "nullable") + , CodeGenerator.use "Codec.nullable" , CodeGenerator.dict (\key value -> CG.apply diff --git a/src/Internal/DependencyScanner.elm b/src/Internal/DependencyScanner.elm index 2082981..1b4d41e 100644 --- a/src/Internal/DependencyScanner.elm +++ b/src/Internal/DependencyScanner.elm @@ -7,6 +7,7 @@ import Elm.Syntax.ModuleName exposing (ModuleName) import Elm.Type as T exposing (Type) import Internal.CodeGenerator exposing (ConfiguredCodeGenerator, ExistingFunctionProvider) import List.Extra +import Maybe.Extra import ResolvedType as RT exposing (ResolvedType) import Review.Project.Dependency as Dependency exposing (Dependency) import TypePattern as TP exposing (TypePattern) @@ -105,8 +106,11 @@ heuristicRejectIfMultiplePatternsForSameType codeGens providers = Dict.get provider.codeGenId codeGens |> Maybe.andThen (\codeGen -> - List.Extra.find (\ref -> ref.modulePath == provider.moduleName && ref.name == provider.functionName) codeGen.blessedImplementations - |> Maybe.map (always provider) + List.Extra.find + (\{ moduleName, functionName } -> + Maybe.Extra.isJust (List.Extra.find (\ref -> ref.modulePath == moduleName && ref.name == functionName) codeGen.blessedImplementations) + ) + (provider :: rest) ) )