packages feed

elm-bridge 0.8.5 → 0.8.6

raw patch · 9 files changed

+166/−25 lines, 9 filesPVP ok

version bump matches the API change (PVP)

API changes (from Hackage documentation)

Files

CHANGELOG.md view
@@ -1,3 +1,10 @@+# Unreleased++ * `Either a b` is now generated as Elm's `Result a b` by `defaultAlterations`, with JSON decoders and encoders that match aeson's `{"Left": ..}` / `{"Right": ..}` encoding (#63).+ * Fix decoders for sum type constructors with named fields. Since v0.8.5 they referenced the type name as a constructor, which does not compile in Elm.+ * Fix decoders for newtypes and sum type constructors with fields whose names are Elm reserved words (e.g. `type`).+ * Support Elm 0.19.2. The end-to-end tests now run on Elm 0.19.2 in GitHub Actions, which replaces Travis CI.+ # v0.8.3   * Support Int32 and Int64, from domenkozar
README.md view
@@ -1,7 +1,7 @@ Elm Bridge ===== -[![Build Status](https://travis-ci.org/agrafix/elm-bridge.svg)](https://travis-ci.org/agrafix/elm-bridge)+[![CI](https://github.com/agrafix/elm-bridge/actions/workflows/ci.yml/badge.svg)](https://github.com/agrafix/elm-bridge/actions/workflows/ci.yml)  [![Hackage Deps](https://img.shields.io/hackage-deps/v/elm-bridge.svg)](http://packdeps.haskellers.com/reverse/elm-bridge) @@ -11,7 +11,7 @@  Building the bridge from [Haskell](http://haskell.org) to [Elm](http://elm-lang.org) and back. Define types once, use on both sides and enjoy easy (de)serialisation. Cheers! -This version of the package only supports Elm 0.19. Version 0.5.2 supports Elm 0.18, and Version 0.3.0.2 supports Elm 0.16 and Elm 0.17.+This version of the package only supports Elm 0.19. The generated code works with Elm 0.19.1 and Elm 0.19.2, and the end-to-end tests run on Elm 0.19.2. Version 0.5.2 supports Elm 0.18, and Version 0.3.0.2 supports Elm 0.16 and Elm 0.17.  Note that the [bartavelle/json-helpers](http://package.elm-lang.org/packages/bartavelle/json-helpers/latest/) package, with version >= 1.2.0, is expected by the generated Elm modules. @@ -70,6 +70,8 @@    ] ``` +The default type conversion rules (see `Elm.Module.defaultAlterations`) convert Haskell types that do not exist in Elm. E.g. `Text` becomes `String`, `Map String v` becomes `Dict String v` and `Either a b` becomes `Result a b`.+ Also, there are functions `Elm.Json.stringSerForSimpleAdt` and `Elm.Json.stringParserForSimpleAdt` to generate functions for your non-JSON ADT types.  For more usage examples check the tests or the examples dir.@@ -90,5 +92,15 @@ * `elm install bartavelle/json-helpers`  ## Contribute++To run the tests, you need `stack`, `node`, Elm 0.19.2 and `elm-test` 0.19.2:++```sh+stack test+$(stack path --dist-dir)/build/end-to-end-tests/end-to-end-tests test/current-end-to-end/tests/MyTests.elm+cd test/current-end-to-end && npx elm-test@0.19.2-1+```++The second command generates an Elm module with tests that decode and encode values that aeson produced. The third command compiles and runs these tests.  Pull requests are welcome! Please consider creating an issue beforehand, so we can discuss what you would like to do. Code should be written in a consistent style throughout the project. Avoid whitespace that is sensible to conflicts. (E.g. alignment of `=` signs in functions definitions) Note that by sending a pull request you agree that your contribution can be released under the BSD3 License as part of the `elm-bridge` package or related packages.
elm-bridge.cabal view
@@ -1,5 +1,5 @@ name:                elm-bridge-version:             0.8.5+version:             0.8.6 synopsis:            Derive Elm types and Json code from Haskell types, using aeson's options description:         Building the bridge from Haskell to Elm and back. Define types once,                      and derive the aeson and elm functions at the same time, using any aeson
src/Elm/Json.hs view
@@ -48,6 +48,9 @@       ETyApp (ETyCon (ETCon "Set")) t' -> "decodeSet (" ++ jsonParserForType t' ++ ")"       ETyApp (ETyApp (ETyCon (ETCon "Dict")) (ETyCon (ETCon "String")) ) value -> "Json.Decode.dict (" ++ jsonParserForType value ++ ")"       ETyApp (ETyApp (ETyCon (ETCon "Dict")) key) value -> "decodeMap (" ++ jsonParserForType key ++ ") (" ++ jsonParserForType value ++ ")"+      -- Haskell's Either, encoded by aeson as {"Left": x} or {"Right": y}+      ETyApp (ETyApp (ETyCon (ETCon "Result")) err) ok ->+          "decodeSumObjectWithSingleField \"Result\" (Dict.fromList [(\"Left\", Json.Decode.map Err (" ++ jsonParserForType err ++ ")), (\"Right\", Json.Decode.map Ok (" ++ jsonParserForType ok ++ "))])"       _ ->           case unpackTupleType ty of             [] -> error $ "This should never happen. Failed to unpackTupleType: " ++ show ty@@ -62,13 +65,25 @@                 in "Json.Decode.map" ++ show tupleLen ++ " tuple" ++ show tupleLen ++ " "                     ++ unwords (zipWith (\i t' -> "(Json.Decode.index " ++ show (i :: Int) ++ " (" ++ jsonParserForType t' ++ "))") [0..] xs) -parseRecords :: String -> Bool -> [(String, EType)] -> [String]-parseRecords typename unwrap fields  =+-- | How the decoded fields of a record are assembled into an Elm value.+data RecordConstructor+    = AliasConstructor String   -- ^ A @type alias@ for a record, which has its own constructor function+    | NewtypeConstructor String -- ^ A custom type that wraps a record, e.g. @type T = T { .. }@+    | AnonymousRecord           -- ^ A plain record, e.g. the fields of a sum type constructor++parseRecords :: RecordConstructor -> Bool -> [(String, EType)] -> [String]+parseRecords constructor unwrap fields  =       case fields of         [(_, ftype)] | unwrap -> [ succeed ++ " |> custom (" ++ jsonParserForType' (o ftype) ftype ++ ")" ]         _ -> succeed : map mkField fields     where-        succeed = "   Json.Decode.succeed " ++ typename+        succeed = "   Json.Decode.succeed " ++ constructorExpr+        constructorExpr = case constructor of+          AliasConstructor typename   -> typename+          NewtypeConstructor typename -> mkLambda (typename ++ " " ++ recordExpr)+          AnonymousRecord             -> mkLambda recordExpr+        mkLambda body = "(\\" ++ unwords (map (('p' :) . fst) fields) ++ " -> " ++ body ++ ")"+        recordExpr = "{ " ++ intercalate ", " (map (\(fldName, _) -> fixReserved fldName ++ " = p" ++ fldName) fields) ++ " }"         o fldType = if isOption fldType                       then Root                       else Leaf@@ -94,10 +109,10 @@           , makeName name ++  " ="           , "    " ++ jsonParserForType ty           ]-      ETypeAlias (EAlias name fields _ _ unwrap) -> unlines+      ETypeAlias (EAlias name fields _ newtyping unwrap) -> unlines           ( decoderType name           : (makeName name ++ " =")-          : parseRecords (et_name name) unwrap fields+          : parseRecords (if newtyping then NewtypeConstructor (et_name name) else AliasConstructor (et_name name)) unwrap fields           )       ETypeSum (ESum name opts (SumEncoding' encodingType) _ unarystring) ->             decoderType name ++ "\n" ++@@ -136,7 +151,7 @@             mkDecoder cname (Named args)  =  lazy $ "Json.Decode.map "                                          ++ cname                                          ++ " ("-                                         ++ unwords (parseRecords (et_name name) False args)+                                         ++ unwords (parseRecords AnonymousRecord False args)                                          ++ ")"              mkDecoder cname (Anonymous args) = lazy $ unwords ( decodeFunction@@ -181,6 +196,21 @@       ETyApp (ETyCon (ETCon "Set")) t' -> "(encodeSet " ++ jsonSerForType' omitnull ns t' ++ ")"       ETyApp (ETyApp (ETyCon (ETCon "Dict")) (ETyCon (ETCon "String"))) value -> "(Json.Encode.dict identity (" ++ jsonSerForType' omitnull ns value ++ "))"       ETyApp (ETyApp (ETyCon (ETCon "Dict")) key) value -> "(encodeMap (" ++ jsonSerForType' omitnull ns key ++ ") (" ++ jsonSerForType' omitnull ns value ++ "))"+      -- Haskell's Either, encoded by aeson as {"Left": x} or {"Right": y}.+      -- Elm has no single-line case expression, so the branches go on their+      -- own lines. They are indented past any enclosing layout block, and+      -- deeper for each nested Result, so that Elm parses them correctly.+      ETyApp (ETyApp (ETyCon (ETCon "Result")) err) ok ->+          let (n, rest) = case ns of+                            (x:xs) -> (x, xs)+                            []     -> error "jsonSerForType': ran out of variable names"+              v = show n+              indent = "\n" ++ replicate (24 + 4 * n) ' '+              branch con var key t = indent ++ con ++ " " ++ var ++ " -> Json.Encode.object [(" ++ show key ++ ", " ++ jsonSerForType' omitnull rest t ++ " " ++ var ++ ")]"+          in "(\\r" ++ v ++ " -> case r" ++ v ++ " of"+             ++ branch "Err" ("e" ++ v) "Left" err+             ++ branch "Ok" ("o" ++ v) "Right" ok+             ++ ")"       _ ->           case unpackTupleType ty of             [] -> error $ "This should never happen. Failed to unpackTupleType: " ++ show ty
src/Elm/Module.hs view
@@ -108,6 +108,7 @@  * @Vector@ -> @List@  * @Double@ -> @Float@  * @Tagged t v@ -> @v@+ * @Either a b@ -> @Result a b@ -} defaultAlterations :: ETypeDef -> ETypeDef defaultAlterations = recAlterType defaultTypeAlterations@@ -121,6 +122,7 @@                             ETyApp (ETyCon (ETCon "IntMap")) v              -> checkMap int (defaultTypeAlterations v)                             ETyApp (ETyApp (ETyCon (ETCon "Map")) k) v      -> checkMap (defaultTypeAlterations k) (defaultTypeAlterations v)                             ETyApp (ETyApp (ETyCon (ETCon "Tagged")) _) v   -> defaultTypeAlterations v+                            ETyApp (ETyApp (ETyCon (ETCon "Either")) l) r   -> ETyApp (ETyApp (tc "Result") (defaultTypeAlterations l)) (defaultTypeAlterations r)                             ETyApp x y                                      -> ETyApp (defaultTypeAlterations x) (defaultTypeAlterations y)                             ETyCon (ETCon "Integer")                        -> int                             ETyCon (ETCon "Natural")                        -> int
src/Elm/TyRep.hs view
@@ -1,3 +1,4 @@+{-# LANGUAGE LambdaCase #-} {-| This module defines how the derived Haskell data types are represented. - It is useful for writing type conversion rules. -}@@ -119,15 +120,14 @@ unpackToplevelConstr :: EType -> [EType] unpackToplevelConstr t =     reverse $-    flip unfoldr (Just t) $ \mT ->-        case mT of-          Nothing -> Nothing-          Just t' ->-              case t' of-                ETyApp l r ->-                    Just (r, Just l)-                _ ->-                    Just (t', Nothing)+    flip unfoldr (Just t) $ \case+        Nothing -> Nothing+        Just t' ->+            case t' of+            ETyApp l r ->+                Just (r, Just l)+            _ ->+                Just (t', Nothing)  class IsElmDefinition a where     compileElmDef :: Proxy a -> ETypeDef@@ -179,7 +179,7 @@           | con == typeRepTyCon (typeRep (Proxy :: Proxy [])) &&             args == [typeRep (Proxy :: Proxy Char)]  = ETyCon (ETCon "String")             -- List is special because the constructor name is [] in Haskell and List in elm-          | con == typeRepTyCon (typeRep (Proxy :: Proxy [])) = ETyApp (ETyCon $ ETCon "List") (toElmType' (head args))+          | con == typeRepTyCon (typeRep (Proxy :: Proxy [])), (arg:_) <- args = ETyApp (ETyCon $ ETCon "List") (toElmType' arg)             -- The unit type '()' is a 0-ary tuple.           | isTuple $ tyConName con = foldl ETyApp (ETyTuple $ length args) $ map toElmType' args           | otherwise = typeApplication con args
test/Elm/JsonSpec.hs view
@@ -3,6 +3,7 @@  import           Elm.Derive import           Elm.Json+import           Elm.Module       (defaultAlterations) import           Elm.TyRender import           Elm.TyRep @@ -59,7 +60,17 @@ newtype PhantomC a = PhantomC Int newtype PhantomD a = PhantomD { getPhantomD :: Int } +-- Regression test for issue #65: newtype with record field decoder was broken+newtype APICity = APICity { cityName :: String }++-- Regression test for issue #63: Either is not an Elm type+data WithEither = WithEither { we_result :: Either String Int }+-- Fields whose names are reserved words in Elm get a trailing underscore+newtype NTReserved = NTReserved { ntr_type :: String }+data SumReserved = SumReserved { sr_type :: Int, sr_port :: Int } | SumOther Int+ $(deriveElmDef (defaultOptionsDropLower 2) ''Foo)+$(deriveElmDef (defaultOptionsDropLower 3) ''WithEither) $(deriveElmDef (defaultOptionsDropLower 2) ''Bar) $(deriveElmDef (defaultOptionsDropLower 1) ''TestComp) $(deriveElmDef defaultOptions ''SomeOpts)@@ -77,6 +88,9 @@ $(deriveElmDef defaultOptions ''PhantomB) $(deriveElmDef defaultOptions { unwrapUnaryRecords = False } ''PhantomC) $(deriveElmDef defaultOptions { unwrapUnaryRecords = False } ''PhantomD)+$(deriveElmDef defaultOptions { unwrapUnaryRecords = False } ''APICity)+$(deriveElmDef (defaultOptionsDropLower 4) { unwrapUnaryRecords = False } ''NTReserved)+$(deriveElmDef (defaultOptionsDropLower 3) ''SumReserved)  fooSer :: String fooSer = "jsonEncFoo : Foo -> Value\njsonEncFoo  val =\n   Json.Encode.object\n   [ (\"name\", Json.Encode.string val.name)\n   , (\"blablub\", Json.Encode.int val.blablub)\n   ]\n"@@ -129,8 +143,8 @@     [ "jsonDecBaz : Json.Decode.Decoder a -> Json.Decode.Decoder ( Baz a )"     , "jsonDecBaz localDecoder_a ="     , "    let jsonDecDictBaz = Dict.fromList"-    , "            [ (\"Baz1\", Json.Decode.lazy (\\_ -> Json.Decode.map Baz1 (   Json.Decode.succeed Baz    |> required \"foo\" (Json.Decode.int)    |> required \"qux\" (jsonDecMap (Json.Decode.int) (localDecoder_a)))))"-    , "            , (\"Baz2\", Json.Decode.lazy (\\_ -> Json.Decode.map Baz2 (   Json.Decode.succeed Baz    |> fnullable \"bar\" (Json.Decode.int)    |> required \"str\" (Json.Decode.string))))"+    , "            [ (\"Baz1\", Json.Decode.lazy (\\_ -> Json.Decode.map Baz1 (   Json.Decode.succeed (\\pfoo pqux -> { foo = pfoo, qux = pqux })    |> required \"foo\" (Json.Decode.int)    |> required \"qux\" (jsonDecMap (Json.Decode.int) (localDecoder_a)))))"+    , "            , (\"Baz2\", Json.Decode.lazy (\\_ -> Json.Decode.map Baz2 (   Json.Decode.succeed (\\pbar pstr -> { bar = pbar, str = pstr })    |> fnullable \"bar\" (Json.Decode.int)    |> required \"str\" (Json.Decode.string))))"     , "            , (\"Testing\", Json.Decode.lazy (\\_ -> Json.Decode.map Testing (jsonDecBaz (localDecoder_a))))"     , "            ]"     , "    in  decodeSumObjectWithSingleField  \"Baz\" jsonDecDictBaz"@@ -272,7 +286,7 @@ ntdParse = unlines   [ "jsonDecNTD : Json.Decode.Decoder ( NTD )"   , "jsonDecNTD ="-  , "   Json.Decode.succeed NTD"+  , "   Json.Decode.succeed (\\pgetNtd -> NTD { getNtd = pgetNtd })"   , "   |> required \"getNtd\" (Json.Decode.int)"   ] @@ -298,10 +312,61 @@ phantomDParse = unlines   [ "jsonDecPhantomD : Json.Decode.Decoder a -> Json.Decode.Decoder ( PhantomD a )"   , "jsonDecPhantomD localDecoder_a ="-  , "   Json.Decode.succeed PhantomD"+  , "   Json.Decode.succeed (\\pgetPhantomD -> PhantomD { getPhantomD = pgetPhantomD })"   , "   |> required \"getPhantomD\" (Json.Decode.int)"   ] +-- Regression test for issue #65+apiCityParse :: String+apiCityParse = unlines+  [ "jsonDecAPICity : Json.Decode.Decoder ( APICity )"+  , "jsonDecAPICity ="+  , "   Json.Decode.succeed (\\pcityName -> APICity { cityName = pcityName })"+  , "   |> required \"cityName\" (Json.Decode.string)"+  ]++withEitherType :: String+withEitherType = unlines+  [ "type alias WithEither  ="+  , "   { result: (Result String Int)"+  , "   }"+  ]++withEitherParse :: String+withEitherParse = unlines+  [ "jsonDecWithEither : Json.Decode.Decoder ( WithEither )"+  , "jsonDecWithEither ="+  , "   Json.Decode.succeed WithEither |> custom (decodeSumObjectWithSingleField \"Result\" (Dict.fromList [(\"Left\", Json.Decode.map Err (Json.Decode.string)), (\"Right\", Json.Decode.map Ok (Json.Decode.int))]))"+  ]++withEitherSer :: String+withEitherSer = init $ unlines+  [ "jsonEncWithEither : WithEither -> Value"+  , "jsonEncWithEither  val ="+  , "   (\\r1 -> case r1 of"+  , "                            Err e1 -> Json.Encode.object [(\"Left\", Json.Encode.string e1)]"+  , "                            Ok o1 -> Json.Encode.object [(\"Right\", Json.Encode.int o1)]) val.result"+  ]++ntReservedParse :: String+ntReservedParse = unlines+  [ "jsonDecNTReserved : Json.Decode.Decoder ( NTReserved )"+  , "jsonDecNTReserved ="+  , "   Json.Decode.succeed (\\ptype -> NTReserved { type_ = ptype })"+  , "   |> required \"type\" (Json.Decode.string)"+  ]++sumReservedParse :: String+sumReservedParse = unlines+  [ "jsonDecSumReserved : Json.Decode.Decoder ( SumReserved )"+  , "jsonDecSumReserved ="+  , "    let jsonDecDictSumReserved = Dict.fromList"+  , "            [ (\"SumReserved\", Json.Decode.lazy (\\_ -> Json.Decode.map SumReserved (   Json.Decode.succeed (\\ptype pport -> { type_ = ptype, port_ = pport })    |> required \"type\" (Json.Decode.int)    |> required \"port\" (Json.Decode.int))))"+  , "            , (\"SumOther\", Json.Decode.lazy (\\_ -> Json.Decode.map SumOther (Json.Decode.int)))"+  , "            ]"+  , "    in  decodeSumObjectWithSingleField  \"SumReserved\" jsonDecDictSumReserved"+  ]+ spec :: Spec spec =     describe "json serialisation" $@@ -323,6 +388,10 @@            rPhantomB = compileElmDef (Proxy :: Proxy (PhantomB a))            rPhantomC = compileElmDef (Proxy :: Proxy (PhantomC a))            rPhantomD = compileElmDef (Proxy :: Proxy (PhantomD a))+           rAPICity = compileElmDef (Proxy :: Proxy APICity)+           rWithEither = defaultAlterations (compileElmDef (Proxy :: Proxy WithEither))+           rNTReserved = compileElmDef (Proxy :: Proxy NTReserved)+           rSumReserved = compileElmDef (Proxy :: Proxy SumReserved)        it "should produce the correct ser code" $ do              jsonSerForDef rFoo `shouldBe` fooSer              jsonSerForDef rBar `shouldBe` barSer@@ -360,3 +429,12 @@             jsonParserForDef rPhantomB `shouldBe` phantomBParse             jsonParserForDef rPhantomC `shouldBe` phantomCParse             jsonParserForDef rPhantomD `shouldBe` phantomDParse+       it "should produce the correct parse code for newtypes with record fields (issue #65)" $ do+            jsonParserForDef rAPICity `shouldBe` apiCityParse+       it "should map Either to Result (issue #63)" $ do+            renderElm rWithEither `shouldBe` withEitherType+            jsonParserForDef rWithEither `shouldBe` withEitherParse+            jsonSerForDef rWithEither `shouldBe` withEitherSer+       it "should use valid Elm field names for reserved words in record decoders" $ do+            jsonParserForDef rNTReserved `shouldBe` ntReservedParse+            jsonParserForDef rSumReserved `shouldBe` sumReservedParse
test/Elm/ModuleSpec.hs view
@@ -89,7 +89,7 @@     , "jsonDecQux localDecoder_a ="     , "    let jsonDecDictQux = Dict.fromList"     , "            [ (\"Qux1\", Json.Decode.lazy (\\_ -> Json.Decode.map2 Qux1 (Json.Decode.index 0 (Json.Decode.int)) (Json.Decode.index 1 (Json.Decode.string))))"-    , "            , (\"Qux2\", Json.Decode.lazy (\\_ -> Json.Decode.map Qux2 (   Json.Decode.succeed Qux    |> required \"a\" (Json.Decode.int)    |> required \"test\" (localDecoder_a))))"+    , "            , (\"Qux2\", Json.Decode.lazy (\\_ -> Json.Decode.map Qux2 (   Json.Decode.succeed (\\pa ptest -> { a = pa, test = ptest })    |> required \"a\" (Json.Decode.int)    |> required \"test\" (localDecoder_a))))"     , "            ]"     , "    in  decodeSumObjectWithSingleField  \"Qux\" jsonDecDictQux"     , ""
test/EndToEnd.hs view
@@ -20,6 +20,7 @@ data Record1 a = Record1 { _r1foo :: Int, _r1bar :: Maybe Int, _r1baz :: a, _r1qux :: Maybe a, _r1jmap :: M.Map String Int } deriving Show data Record2 a = Record2 { _r2foo :: Int, _r2bar :: Maybe Int, _r2baz :: a, _r2qux :: Maybe a } deriving Show data RecordNestTuple a = RecordNestTuple (a, (a, a)) deriving Show+data RecordEither a = RecordEither { _rEitherfoo :: Either Int a, _rEitherbar :: Either (Either Int Bool) [Int], _rEitherbaz :: Maybe (Either a Int), _rEitherqux :: [Either Int Int] } deriving Show  data Sum01 a = Sum01A a | Sum01B (Maybe a) | Sum01C a a | Sum01D { _s01foo :: a } | Sum01E { _s01bar :: Int, _s01baz :: Int } deriving Show data Sum02 a = Sum02A a | Sum02B (Maybe a) | Sum02C a a | Sum02D { _s02foo :: a } | Sum02E { _s02bar :: Int, _s02baz :: Int } deriving Show@@ -142,6 +143,10 @@ mkRecordEncodeTest :: (Show a, ToJSON a) => String -> [a] -> String mkRecordEncodeTest = mkEncodeTest "Record" "_r" +-- | Haskell's Either is Elm's Result: rewrite the shown constructors+eitherToResult :: String -> String+eitherToResult = T.unpack . T.replace (T.pack "Left ") (T.pack "Err ") . T.replace (T.pack "Right ") (T.pack "Ok ") . T.pack+ mkSimpleRecordDecodeTest :: (Show a, ToJSON a) => String -> [a] -> String mkSimpleRecordDecodeTest = mkDecodeTest "SimpleRecord" "_s" @@ -158,6 +163,7 @@ $(deriveBoth defaultOptions{ fieldLabelModifier = drop 3, omitNothingFields = False } ''Record1) $(deriveBoth defaultOptions{ fieldLabelModifier = drop 3, omitNothingFields = True  } ''Record2) $(deriveBoth defaultOptions ''RecordNestTuple)+$(deriveBoth defaultOptions{ fieldLabelModifier = drop 8 } ''RecordEither)  $(deriveBoth defaultOptions{ fieldLabelModifier = drop 4, omitNothingFields = False, allNullaryToStringTag = False, sumEncoding = TaggedObject "tag" "content" } ''Sum01) $(deriveBoth defaultOptions{ fieldLabelModifier = drop 4, omitNothingFields = True , allNullaryToStringTag = False, sumEncoding = TaggedObject "tag" "content" } ''Sum02)@@ -208,6 +214,8 @@     , c5 <$> arbitrary <*> arbitrary     ] +instance Arbitrary a => Arbitrary (RecordEither a) where+    arbitrary = RecordEither <$> arbitrary <*> arbitrary <*> fmap Just arbitrary <*> arbitrary instance Arbitrary a => Arbitrary (RecordNestTuple a) where   arbitrary = (\x y z -> RecordNestTuple (x, (y, z))) <$> arbitrary <*> arbitrary <*> arbitrary instance Arbitrary a => Arbitrary (Sum01 a) where arbitrary = arb Sum01A Sum01B Sum01C Sum01D Sum01E@@ -357,10 +365,11 @@     , "    in equal (remix a) (remix b)"     , ""     , ""-    , makeModuleContentWithAlterations (newtypeAliases ["Record1", "Record2", "SimpleRecord01", "SimpleRecord02", "SimpleRecord03", "SimpleRecord04"] . defaultAlterations)+    , makeModuleContentWithAlterations (newtypeAliases ["Record1", "Record2", "RecordEither", "SimpleRecord01", "SimpleRecord02", "SimpleRecord03", "SimpleRecord04"] . defaultAlterations)         [ DefineElm (Proxy :: Proxy (Record1 a))         , DefineElm (Proxy :: Proxy (Record2 a))         , DefineElm (Proxy :: Proxy (RecordNestTuple a))+        , DefineElm (Proxy :: Proxy (RecordEither a))         , DefineElm (Proxy :: Proxy (Sum01 a))         , DefineElm (Proxy :: Proxy (Sum02 a))         , DefineElm (Proxy :: Proxy (Sum03 a))@@ -408,6 +417,7 @@     re01 <- sample' arbitrary :: IO [Record1 [Int]]     re02 <- sample' arbitrary :: IO [Record2 [Int]]     rent <- sample' arbitrary :: IO [RecordNestTuple [Int]]+    reei <- sample' arbitrary :: IO [RecordEither [Int]]     sp01 <- sample' arbitrary :: IO [Simple01 [Int]]     sp02 <- sample' arbitrary :: IO [Simple02 [Int]]     sp03 <- sample' arbitrary :: IO [Simple03 [Int]]@@ -457,6 +467,8 @@                        , mkRecordEncodeTest "2" re02                        , mkRecordDecodeTest "NestTuple" rent                        , mkRecordEncodeTest "NestTuple" rent+                       , eitherToResult (mkRecordDecodeTest "Either" reei)+                       , eitherToResult (mkRecordEncodeTest "Either" reei)                        , mkSimpleEncodeTest "01" sp01                        , mkSimpleEncodeTest "02" sp02                        , mkSimpleEncodeTest "03" sp03