packages feed

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 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_)--