json-spec-elm 0.4.0.0 → 0.4.0.1
raw patch · 4 files changed
+105/−466 lines, 4 filesPVP ok
version bump matches the API change (PVP)
API changes (from Hackage documentation)
Files
- json-spec-elm.cabal +23/−13
- src/Data/JsonSpec/Elm.hs +78/−19
- test/test-pass.hs +4/−0
- test/test.hs +0/−434
json-spec-elm.cabal view
@@ -1,6 +1,6 @@ cabal-version: 3.0 name: json-spec-elm-version: 0.4.0.0+version: 0.4.0.1 synopsis: Elm code generate for `json-spec`. description: Produce elm types, encoders, and decoders from a@@ -34,7 +34,6 @@ -Wmissing-export-lists -Wmissing-import-lists -Wredundant-constraints- -Wunused-packages library import: dependencies, warnings@@ -44,21 +43,32 @@ -- other-extensions: hs-source-dirs: src default-language: Haskell2010+ ghc-options:+ -Wunused-packages build-depends: , bound >= 2.0.7 && < 2.1 , mtl >= 2.2.2 && < 2.4 -test-suite test+flag compile-elm+ description:+ Set this flag to run the Elm compilation tests, which requires+ Elm to be installed on the system.+ default: False++test-suite compile-elm import: dependencies, warnings- main-is: test.hs- type: exitcode-stdio-1.0 hs-source-dirs: test default-language: Haskell2010- build-depends:- , json-spec-elm- , directory >= 1.3.7.1 && < 1.4- , hspec >= 2.11.1 && < 2.12- , prettyprinter >= 1.7.1 && < 1.8- , process >= 1.6.16.0 && < 1.7- , unordered-containers >= 0.2.19.1 && < 0.3-+ if flag(compile-elm)+ type: exitcode-stdio-1.0+ main-is: test.hs+ build-depends:+ , json-spec-elm+ , directory >= 1.3.7.1 && < 1.4+ , hspec >= 2.11.1 && < 2.12+ , prettyprinter >= 1.7.1 && < 1.8+ , process >= 1.6.16.0 && < 1.7+ , unordered-containers >= 0.2.19.1 && < 0.3+ else+ type: exitcode-stdio-1.0+ main-is: test-pass.hs
src/Data/JsonSpec/Elm.hs view
@@ -1,9 +1,12 @@ {-# LANGUAGE AllowAmbiguousTypes #-} {-# LANGUAGE DataKinds #-}+{-# LANGUAGE DuplicateRecordFields #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE NamedFieldPuns #-}+{-# LANGUAGE OverloadedRecordDot #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE PolyKinds #-} {-# LANGUAGE ScopedTypeVariables #-}@@ -121,8 +124,7 @@ import Language.Elm.Type (Type) import Prelude (Applicative(pure), Bool(False, True), Foldable(foldl, foldr), Functor(fmap), Maybe(Just, Nothing), Monad((>>)),- Semigroup((<>)), Show(show), ($), (++), (.), (<$>), Int, error, fst,- snd, zip)+ Semigroup((<>)), Show(show), ($), (++), (.), (<$>), Int, error, zip) import qualified Data.Char as Char import qualified Data.Set as Set import qualified Data.Text as Text@@ -148,10 +150,46 @@ execWriter $ typeOf @spec >> decoderOf @spec +{-| Describes how a field in a record should be encoded in Elm. -}+data FieldEncoding = FieldEncoding+ { required :: Bool+ {-^+ 'True' if the fields presence is required in the JSON+ object, 'False' if it is not (which implies that the+ field is a @Maybe something@, though that information+ is not tracked here.+ -}+ , jsonField :: Text {-^ The name of the encoded JSON field. -}+ , elmField :: Name.Field {-^ The name of the Elm record field. -}+ , encoderFun :: Expression Void+ {-^+ The Elm function which can decode field value. The+ expression will be a lambda expression that accepts+ an Elm value and produces Elm's representation of a+ JSON value (i.e. @Json.Encode.Value@)+ -}+ }+++{-| Describes how a field in a record should be decoded. -}+data FieldDecoding = FieldDecoding+ { jsonField :: Text {-^ The name of the decoded field in JSON -}+ , decoder :: Expression Void+ {-^+ An Elm expression containing the decoder for the+ field value. I.e. of the Elm type @Json.Decode.Decoder+ something@.+ -}+ }++{-|+ How to define, encode, and decode an Elm record from a list of JSON+ object field specifications.+-} class Record (spec :: [FieldSpec]) where recordDefs :: forall v. Definitions [(Name.Field, Type v)]- recordEncoders :: Definitions [(Bool, Text, Name.Field, Expression Void)]- recordDecoders :: Definitions [(Text, Expression Void)]+ recordEncoders :: Definitions [FieldEncoding]+ recordDecoders :: Definitions [FieldDecoding] instance Record '[] where recordDefs = pure [] recordEncoders = pure []@@ -171,14 +209,23 @@ recordEncoders = do encoder <- encoderOf @spec moreFields <- recordEncoders @more- pure $ (True, sym @name, fieldName (sym @name), encoder) : moreFields+ pure $+ FieldEncoding+ { required = True+ , jsonField = sym @name+ , elmField = fieldName (sym @name)+ , encoderFun = encoder+ }+ : moreFields recordDecoders = do dec <- decoderOf @spec more <- recordDecoders @more pure $- ( sym @name- , "Json.Decode.field" `a` Expr.String (sym @name) `a` dec- ) : more+ FieldDecoding+ { jsonField = sym @name+ , decoder = "Json.Decode.field" `a` Expr.String (sym @name) `a` dec+ }+ : more instance ( HasType spec , KnownSymbol name@@ -194,15 +241,25 @@ recordEncoders = do encoder <- encoderOf @spec moreFields <- recordEncoders @more- pure $ (False, sym @name, fieldName (sym @name), encoder) : moreFields+ pure $+ FieldEncoding+ { required = False+ , jsonField = sym @name+ , elmField = fieldName (sym @name)+ , encoderFun = encoder+ }+ : moreFields recordDecoders = do dec <- decoderOf @spec more <- recordDecoders @more pure $- ( sym @name- , "Json.Decode.maybe"- `a` ("Json.Decode.field" `a` Expr.String (sym @name) `a` dec)- ) : more+ FieldDecoding+ { jsonField = sym @name+ , decoder =+ "Json.Decode.maybe"+ `a` ("Json.Decode.field" `a` Expr.String (sym @name) `a` dec)+ }+ : more {-|@@ -246,7 +303,7 @@ instance (Record fields) => HasType (JsonObject fields) where typeOf = Type.Record <$> recordDefs @fields decoderOf = do- decoders <- recordDecoders @fields+ decodings <- recordDecoders @fields pure $ foldl (\expr decoder ->@@ -256,8 +313,8 @@ lam (\var -> "Json.Decode.map" `a` var `a` (absurd <$> decoder)) ) )- ("Json.Decode.succeed" `a` recordConstructor (fst <$> decoders))- (snd <$> decoders)+ ("Json.Decode.succeed" `a` recordConstructor ((.jsonField) <$> decodings))+ ((.decoder) <$> decodings) encoderOf = do fields <- recordEncoders @fields pure $@@ -272,7 +329,7 @@ Expr.apps "Basics.," [ Expr.String jsonField,- fmap absurd encoder `a`+ fmap absurd encoderFun `a` (Expr.Proj elmField `a` var) ] else@@ -281,11 +338,13 @@ Expr.apps "Basics.," [ Expr.String jsonField,- fmap absurd encoder `a` inner+ fmap absurd encoderFun `a` inner ] ) `a` (Expr.Proj elmField `a` var)- | (required, jsonField, elmField, encoder) <- fields+ | FieldEncoding {required, jsonField, elmField, encoderFun}+ <- fields+ ] ) )
+ test/test-pass.hs view
@@ -0,0 +1,4 @@+module Main (main) where++main :: IO ()+main = pure ()
− test/test.hs
@@ -1,434 +0,0 @@-{-# LANGUAGE DataKinds #-}-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE TypeApplications #-}--module Main (main) where--import Data.Foldable (traverse_)-import Data.HashMap.Strict (HashMap)-import Data.JsonSpec (FieldSpec(Optional, Required),- Specification(JsonArray, JsonDateTime, JsonEither, JsonInt, JsonLet,- JsonNullable, JsonNum, JsonObject, JsonRef, JsonString, JsonTag))-import Data.JsonSpec.Elm (Named, elmDefs)-import Data.Maybe (fromMaybe)-import Data.Proxy (Proxy(Proxy))-import Data.Text (Text)-import Language.Elm.Name (Module)-import Language.Elm.Pretty (modules)-import Prelude (Bool(True), Functor(fmap), Semigroup((<>)), ($), (.),- FilePath, IO, init)-import Prettyprinter (defaultLayoutOptions, layoutPretty)-import Prettyprinter.Render.Text (renderStrict)-import System.Directory (createDirectoryIfMissing)-import System.IO (stderr)-import System.Process (callCommand)-import Test.Hspec (describe, hspec, it, shouldBe)-import qualified Data.HashMap.Strict as HM-import qualified Data.Set as Set-import qualified Data.Text as Text-import qualified Data.Text.IO as TIO---main :: IO ()-main =- hspec $ do- describe "Code generation" $ do- it "works with a complicated schema" $- let- actual :: HashMap Module Text- actual =- fmap ((<> "\n") . renderStrict . layoutPretty defaultLayoutOptions)- . modules- . Set.toList- $ elmDefs (Proxy @TestSpec)-- expected :: HashMap Module Text- expected =- HM.singleton- ["Api", "Data"]- ( Text.unlines- [ "module Api.Data exposing"- , " ( dashboardDecoder"- , " , dashboardEncoder"- , " , inviteDecoder"- , " , inviteEncoder"- , " , Invite(..)"- , " , Dashboard"- , " )"- , ""- , "import Iso8601"- , "import Json.Decode"- , "import Json.Encode"- , "import Time"- , ""- , ""- , "dashboardDecoder : Json.Decode.Decoder Dashboard"- , "dashboardDecoder ="- , " Json.Decode.succeed (\\a b c -> { proposals = a, credits = b, user = c }) |>"- , " Json.Decode.andThen (\\a -> Json.Decode.map a (Json.Decode.field \"proposals\" (Json.Decode.list (Json.Decode.succeed (\\b c -> { key = b"- , " , value = c }) |>"- , " Json.Decode.andThen (\\b -> Json.Decode.map b (Json.Decode.field \"key\" Json.Decode.string)) |>"- , " Json.Decode.andThen (\\b -> Json.Decode.map b (Json.Decode.field \"value\" (Json.Decode.succeed (\\c d e f g h i j -> { name = c"- , " , owner = d"- , " , availability = e"- , " , description = f"- , " , venue = g"- , " , invites = h"- , " , created_at = i"- , " , attachments = j }) |>"- , " Json.Decode.andThen (\\c -> Json.Decode.map c (Json.Decode.field \"name\" Json.Decode.string)) |>"- , " Json.Decode.andThen (\\c -> Json.Decode.map c (Json.Decode.field \"owner\" Json.Decode.string)) |>"- , " Json.Decode.andThen (\\c -> Json.Decode.map c (Json.Decode.field \"availability\" (Json.Decode.list (Json.Decode.succeed (\\d e -> { interval = d"- , " , users = e }) |>"- , " Json.Decode.andThen (\\d -> Json.Decode.map d (Json.Decode.field \"interval\" (Json.Decode.succeed (\\e f -> { startInclusive = e"- , " , endExclusive = f }) |>"- , " Json.Decode.andThen (\\e -> Json.Decode.map e (Json.Decode.field \"startInclusive\" Iso8601.decoder)) |>"- , " Json.Decode.andThen (\\e -> Json.Decode.map e (Json.Decode.field \"endExclusive\" Iso8601.decoder))))) |>"- , " Json.Decode.andThen (\\d -> Json.Decode.map d (Json.Decode.field \"users\" (Json.Decode.list Json.Decode.string))))))) |>"- , " Json.Decode.andThen (\\c -> Json.Decode.map c (Json.Decode.field \"description\" Json.Decode.string)) |>"- , " Json.Decode.andThen (\\c -> Json.Decode.map c (Json.Decode.field \"venue\" Json.Decode.string)) |>"- , " Json.Decode.andThen (\\c -> Json.Decode.map c (Json.Decode.field \"invites\" (Json.Decode.list inviteDecoder))) |>"- , " Json.Decode.andThen (\\c -> Json.Decode.map c (Json.Decode.field \"created-at\" Iso8601.decoder)) |>"- , " Json.Decode.andThen (\\c -> Json.Decode.map c (Json.Decode.field \"attachments\" (Json.Decode.list Json.Decode.string)))))))))) |>"- , " Json.Decode.andThen (\\a -> Json.Decode.map a (Json.Decode.maybe (Json.Decode.field \"credits\" Json.Decode.int))) |>"- , " Json.Decode.andThen (\\a -> Json.Decode.map a (Json.Decode.field \"user\" Json.Decode.string))"- , ""- , ""- , "dashboardEncoder : Dashboard -> Json.Encode.Value"- , "dashboardEncoder a ="- , " Json.Encode.object (List.filterMap identity [ Just (\"proposals\" , Json.Encode.list (\\b -> Json.Encode.object (List.filterMap identity [ Just (\"key\" , Json.Encode.string b.key)"- , " , Just (\"value\" , (\\c -> Json.Encode.object (List.filterMap identity [ Just (\"name\" , Json.Encode.string c.name)"- , " , Just (\"owner\" , Json.Encode.string c.owner)"- , " , Just (\"availability\" , Json.Encode.list (\\d -> Json.Encode.object (List.filterMap identity [ Just (\"interval\" , (\\e -> Json.Encode.object (List.filterMap identity [ Just (\"startInclusive\" , Iso8601.encode e.startInclusive)"- , " , Just (\"endExclusive\" , Iso8601.encode e.endExclusive) ])) d.interval)"- , " , Just (\"users\" , Json.Encode.list Json.Encode.string d.users) ])) c.availability)"- , " , Just (\"description\" , Json.Encode.string c.description)"- , " , Just (\"venue\" , Json.Encode.string c.venue)"- , " , Just (\"invites\" , Json.Encode.list inviteEncoder c.invites)"- , " , Just (\"created-at\" , Iso8601.encode c.created_at)"- , " , Just (\"attachments\" , Json.Encode.list Json.Encode.string c.attachments) ])) b.value) ])) a.proposals)"- , " , Maybe.map (\\b -> (\"credits\" , Json.Encode.int b)) a.credits"- , " , Just (\"user\" , Json.Encode.string a.user) ])"- , ""- , ""- , "inviteDecoder : Json.Decode.Decoder Invite"- , "inviteDecoder ="- , " Json.Decode.oneOf [ Json.Decode.map InviteUser (Json.Decode.succeed (\\a b -> { type_ = a"- , " , username = b }) |>"- , " Json.Decode.andThen (\\a -> Json.Decode.map a (Json.Decode.field \"type\" (Json.Decode.string |>"- , " Json.Decode.andThen (\\b -> if b == \"discord-user\" then"- , " Json.Decode.succeed ()"- , ""- , " else"- , " Json.Decode.fail \"Tag mismatch\")))) |>"- , " Json.Decode.andThen (\\a -> Json.Decode.map a (Json.Decode.field \"username\" Json.Decode.string)))"- , " , Json.Decode.map InviteGuild (Json.Decode.succeed (\\a b -> { type_ = a"- , " , guild = b }) |>"- , " Json.Decode.andThen (\\a -> Json.Decode.map a (Json.Decode.field \"type\" (Json.Decode.string |>"- , " Json.Decode.andThen (\\b -> if b == \"discord-server\" then"- , " Json.Decode.succeed ()"- , ""- , " else"- , " Json.Decode.fail \"Tag mismatch\")))) |>"- , " Json.Decode.andThen (\\a -> Json.Decode.map a (Json.Decode.field \"guild\" (Json.Decode.succeed (\\b c -> { id = b"- , " , name = c }) |>"- , " Json.Decode.andThen (\\b -> Json.Decode.map b (Json.Decode.field \"id\" Json.Decode.string)) |>"- , " Json.Decode.andThen (\\b -> Json.Decode.map b (Json.Decode.field \"name\" Json.Decode.string)))))) ]"- , ""- , ""- , "inviteEncoder : Invite -> Json.Encode.Value"- , "inviteEncoder a ="- , " case a of"- , " InviteUser b ->"- , " (\\c -> Json.Encode.object (List.filterMap identity [ Just (\"type\" , always (Json.Encode.string \"discord-user\") c.type_)"- , " , Just (\"username\" , Json.Encode.string c.username) ])) b"- , ""- , " InviteGuild b ->"- , " (\\c -> Json.Encode.object (List.filterMap identity [ Just (\"type\" , always (Json.Encode.string \"discord-server\") c.type_)"- , " , Just (\"guild\" , (\\d -> Json.Encode.object (List.filterMap identity [ Just (\"id\" , Json.Encode.string d.id)"- , " , Just (\"name\" , Json.Encode.string d.name) ])) c.guild) ])) b"- , ""- , ""- , "type Invite "- , " = InviteUser { type_ : (), username : String }"- , " | InviteGuild { type_ : (), guild : { id : String, name : String } }"- , ""- , ""- , "type alias Dashboard ="- , " { proposals : List { key : String"- , " , value : { name : String"- , " , owner : String"- , " , availability : List { interval : { startInclusive : Time.Posix"- , " , endExclusive : Time.Posix }"- , " , users : List String }"- , " , description : String"- , " , venue : String"- , " , invites : List Invite"- , " , created_at : Time.Posix"- , " , attachments : List String } }"- , " , credits : Maybe Int"- , " , user : String }"- ]- )- in do- TIO.hPutStrLn stderr "==========================================\n\n"- TIO.hPutStrLn stderr (fromMaybe "" (HM.lookup ["Api", "Data"] actual))- TIO.hPutStrLn stderr "\n\n==========================================\n\n"- actual `shouldBe` expected- compileElm actual- it "works with the example schema" $- let- actual :: HashMap Module Text- actual =- fmap ((<> "\n") . renderStrict . layoutPretty defaultLayoutOptions)- . modules- . Set.toList- $ elmDefs (Proxy @ExampleSpec)-- in do- compileElm actual- it "works with nullable values" $- let- actual :: HashMap Module Text- actual =- fmap ((<> "\n") . renderStrict . layoutPretty defaultLayoutOptions)- . modules- . Set.toList- $ elmDefs (Proxy @NullableSpec)-- expected :: HashMap Module Text- expected =- HM.singleton- ["Api", "Data"]- ( Text.unlines- [ "module Api.Data exposing"- , " ( nullableIntDecoder"- , " , nullableIntEncoder"- , " , NullableInt"- , " )"- , ""- , "import Json.Decode"- , "import Json.Encode"- , ""- , ""- , "nullableIntDecoder : Json.Decode.Decoder NullableInt"- , "nullableIntDecoder ="- , " Json.Decode.nullable Json.Decode.int"- , ""- , ""- , "nullableIntEncoder : NullableInt -> Json.Encode.Value"- , "nullableIntEncoder a ="- , " Maybe.withDefault Json.Encode.null (Maybe.map Json.Encode.int a)"- , ""- , ""- , "type alias NullableInt ="- , " Maybe Int"- ]- )- in do- actual `shouldBe` expected- compileElm actual- it "works with optionality" $- let- actual :: HashMap Module Text- actual =- fmap ((<> "\n") . renderStrict . layoutPretty defaultLayoutOptions)- . modules- . Set.toList- $ elmDefs (Proxy @(- JsonLet- '[ '("TestObj", JsonObject '[- Optional "foo" JsonInt,- Optional "bar" (JsonNullable JsonInt),- Required "baz" JsonInt,- Required "qux" (JsonNullable JsonInt)- ])- ]- (JsonRef "TestObj")- ))-- expected :: HashMap Module Text- expected =- HM.singleton- ["Api", "Data"]- ( Text.unlines- [ "module Api.Data exposing"- , " ( testObjDecoder"- , " , testObjEncoder"- , " , TestObj"- , " )"- , ""- , "import Json.Decode"- , "import Json.Encode"- , ""- , ""- , "testObjDecoder : Json.Decode.Decoder TestObj"- , "testObjDecoder ="- , " Json.Decode.succeed (\\a b c d -> { foo = a, bar = b, baz = c, qux = d }) |>"- , " Json.Decode.andThen (\\a -> Json.Decode.map a (Json.Decode.maybe (Json.Decode.field \"foo\" Json.Decode.int))) |>"- , " Json.Decode.andThen (\\a -> Json.Decode.map a (Json.Decode.maybe (Json.Decode.field \"bar\" (Json.Decode.nullable Json.Decode.int)))) |>"- , " Json.Decode.andThen (\\a -> Json.Decode.map a (Json.Decode.field \"baz\" Json.Decode.int)) |>"- , " Json.Decode.andThen (\\a -> Json.Decode.map a (Json.Decode.field \"qux\" (Json.Decode.nullable Json.Decode.int)))"- , ""- , ""- , "testObjEncoder : TestObj -> Json.Encode.Value"- , "testObjEncoder a ="- , " Json.Encode.object (List.filterMap identity [ Maybe.map (\\b -> (\"foo\" , Json.Encode.int b)) a.foo"- , " , Maybe.map (\\b -> (\"bar\" , (\\c -> Maybe.withDefault Json.Encode.null (Maybe.map Json.Encode.int c)) b)) a.bar"- , " , Just (\"baz\" , Json.Encode.int a.baz)"- , " , Just (\"qux\" , (\\b -> Maybe.withDefault Json.Encode.null (Maybe.map Json.Encode.int b)) a.qux) ])"- , ""- , ""- , "type alias TestObj ="- , " { foo : Maybe Int, bar : Maybe (Maybe Int), baz : Int, qux : Maybe Int }"- ]- )- in do- actual `shouldBe` expected- compileElm actual---compileElm :: HashMap Module Text -> IO ()-compileElm code = do- traverse_ writeModule (HM.toList code)- callCommand "(cd elm-test; elm-format src/ --yes)"- callCommand- "(\- \cd elm-test; \- \yes Y | (\- \elm init; \- \elm install rtfeldman/elm-iso8601-date-strings; \- \elm install elm/json; \- \elm install elm/url; \- \elm install elm/time; \- \elm install elm/http\- \); \- \elm make src/Api/Data.elm\- \)"- callCommand "rm -rf elm-test"---{-- This spec is copied from an as-yet uncompleted personal project. I- just used it because it is fairly complex. Probably something known- to be exhaustive is in order.--}-type TestSpec =- JsonLet- '[- '("Dashboard", JsonObject '[- Required "proposals" (JsonArray (- JsonObject '[- Required "key" JsonString,- Required "value" (JsonObject '[- Required "name" JsonString,- Required "owner" JsonString,- Required "availability" (JsonArray (- JsonObject '[- Required "interval" (JsonObject '[- Required "startInclusive" JsonDateTime,- Required "endExclusive" JsonDateTime- ]),- Required "users" (JsonArray JsonString)- ]- )),- Required "description" JsonString,- Required "venue" JsonString,- Required "invites" (- JsonLet '[- '("Invite",- JsonEither- (Named "InviteUser" (JsonObject '[- Required "type" (JsonTag "discord-user"),- Required "username" JsonString- ]))- (Named "InviteGuild" (JsonObject '[- Required "type" (JsonTag "discord-server"),- Required "guild" (JsonObject '[- Required "id" JsonString,- Required "name" JsonString- ])- ]))- )- ]- (JsonArray (JsonRef "Invite"))- ),- Required "created-at" JsonDateTime,- Required "attachments" (JsonArray JsonString)- ])- ]- )),- Optional "credits" JsonInt,- Required "user" JsonString- ])- ] ( JsonRef "Dashboard")---type ExampleSpec =- Named "ExampleType"- ( JsonObject '[- Required "stringField" JsonString,- Required "anonymousObject" (JsonObject '[- Required "floatField" JsonNum,- Required "dateField" JsonDateTime,- Required "sumType1" (- Named "SumTypeWithCustomConstructorNames" (- JsonEither- (- JsonEither- (Named "IntConstructor" JsonInt)- (Named "StringConstructor" JsonString)- )- (Named "FloatConstructor" JsonNum)- )- ),- Required "sumType2" (- Named "SumTypeWithAutomaticConstructorNames" (- JsonEither- ( JsonEither JsonInt JsonString)- JsonNum- )- )- ]),- Required "namedObject" (- Named "NamedElmRecord" (- JsonObject '[- Required "stringField" JsonString,- Required "listOfStrings" (JsonArray JsonString)- ]- )- ),- Required "newtypedObject" (- Named "NewtypedElmRecord" (- Named "Cons" (- JsonObject '[- Required "stringField" JsonString,- Required "listOfStrings" (JsonArray JsonString)- ]- )- )- )- ]- )---type NullableSpec =- Named "NullableInt"- (JsonNullable JsonInt)---writeModule :: (Module, Text) -> IO ()-writeModule (module_, content) = do- createDirectoryIfMissing True dirname- TIO.writeFile filename content- where- pathName :: [Text] -> FilePath- pathName = ("elm-test/src/" <>) . Text.unpack . Text.intercalate "/"-- filename :: FilePath- filename = pathName module_ <> ".elm"-- dirname :: FilePath- dirname = pathName (init module_)--