packages feed

aeson-schemas 1.0.3 → 1.1.0

raw patch · 29 files changed

+546/−82 lines, 29 filesdep +QuickCheckdep +tasty-hunitdep +tasty-quickcheckdep ~aesondep ~tastydep ~template-haskellPVP ok

version bump matches the API change (PVP)

Dependencies added: QuickCheck, tasty-hunit, tasty-quickcheck

Dependency ranges changed: aeson, tasty, template-haskell

API changes (from Hackage documentation)

+ Data.Aeson.Schema.Internal: SchemaUnion :: [SchemaType] -> SchemaType
+ Data.Aeson.Schema.Internal: instance (Data.Aeson.Schema.Utils.TypeFamilies.All Data.Aeson.Schema.Internal.IsSchemaType schemas, Data.Typeable.Internal.Typeable schemas, GHC.Show.Show (Data.Aeson.Schema.Internal.SchemaResult ('Data.Aeson.Schema.Internal.SchemaUnion schemas)), Data.Aeson.Types.FromJSON.FromJSON (Data.Aeson.Schema.Internal.SchemaResult ('Data.Aeson.Schema.Internal.SchemaUnion schemas))) => Data.Aeson.Schema.Internal.IsSchemaType ('Data.Aeson.Schema.Internal.SchemaUnion schemas)
+ Data.Aeson.Schema.Show: SchemaUnion :: [SchemaType] -> SchemaType
+ Data.Aeson.Schema.TH: genToJSONEnum :: Name -> Q [Dec]
+ Data.Aeson.Schema.Utils.Sum: [Here] :: forall x xs. x -> SumType (x : xs)
+ Data.Aeson.Schema.Utils.Sum: [There] :: forall x xs. SumType xs -> SumType (x : xs)
+ Data.Aeson.Schema.Utils.Sum: data SumType (types :: [Type])
+ Data.Aeson.Schema.Utils.Sum: fromSumType :: (IsInRange n types, 'Just result ~ GetIndex n types, FromSumType n types result) => proxy n -> SumType types -> Maybe result
+ Data.Aeson.Schema.Utils.Sum: instance (Data.Aeson.Schema.Utils.Sum.FromSumType (n GHC.TypeNats.- 1) xs x, 'GHC.Maybe.Just x Data.Type.Equality.~ Data.Aeson.Schema.Utils.Sum.GetIndex (n GHC.TypeNats.- 1) xs) => Data.Aeson.Schema.Utils.Sum.FromSumType n (_x : xs) x
+ Data.Aeson.Schema.Utils.Sum: instance (Data.Aeson.Types.FromJSON.FromJSON x, Data.Aeson.Types.FromJSON.FromJSON (Data.Aeson.Schema.Utils.Sum.SumType xs)) => Data.Aeson.Types.FromJSON.FromJSON (Data.Aeson.Schema.Utils.Sum.SumType (x : xs))
+ Data.Aeson.Schema.Utils.Sum: instance (GHC.Classes.Eq x, GHC.Classes.Eq (Data.Aeson.Schema.Utils.Sum.SumType xs)) => GHC.Classes.Eq (Data.Aeson.Schema.Utils.Sum.SumType (x : xs))
+ Data.Aeson.Schema.Utils.Sum: instance (GHC.Classes.Ord x, GHC.Classes.Ord (Data.Aeson.Schema.Utils.Sum.SumType xs)) => GHC.Classes.Ord (Data.Aeson.Schema.Utils.Sum.SumType (x : xs))
+ Data.Aeson.Schema.Utils.Sum: instance (GHC.Show.Show x, GHC.Show.Show (Data.Aeson.Schema.Utils.Sum.SumType xs)) => GHC.Show.Show (Data.Aeson.Schema.Utils.Sum.SumType (x : xs))
+ Data.Aeson.Schema.Utils.Sum: instance Data.Aeson.Schema.Utils.Sum.FromSumType 0 (x : xs) x
+ Data.Aeson.Schema.Utils.Sum: instance Data.Aeson.Types.FromJSON.FromJSON (Data.Aeson.Schema.Utils.Sum.SumType '[])
+ Data.Aeson.Schema.Utils.Sum: instance GHC.Classes.Eq (Data.Aeson.Schema.Utils.Sum.SumType '[])
+ Data.Aeson.Schema.Utils.Sum: instance GHC.Classes.Ord (Data.Aeson.Schema.Utils.Sum.SumType '[])
+ Data.Aeson.Schema.Utils.Sum: instance GHC.Show.Show (Data.Aeson.Schema.Utils.Sum.SumType '[])
+ Data.Aeson.Schema.Utils.TypeFamilies: type family All (f :: a -> Constraint) (xs :: [a]) :: Constraint

Files

CHANGELOG.md view
@@ -1,5 +1,11 @@ ## Upcoming +## 1.1.0++New features:+    * Added support for unions+    * Added `ToJSON` instance for enums generated with `mkEnum`+ ## 1.0.3  Support GHC 8.8
aeson-schemas.cabal view
@@ -4,10 +4,10 @@ -- -- see: https://github.com/sol/hpack ----- hash: c2bdf380b742a87fa9d09969246278d74fefaa1c713dc1b131df3e2016de3fa0+-- hash: 79cd47bc31fb343c075854b35187f50f082ad4bc23f5bc14a9e514cc8633c9f2  name:           aeson-schemas-version:        1.0.3+version:        1.1.0 synopsis:       Easily consume JSON data on-demand with type-safety description:    Parse JSON data easily and safely without defining new data types. Useful                 for deeply nested JSON data, which is difficult to parse using the default@@ -82,13 +82,22 @@     test/goldens/schema_def_obj.golden     test/goldens/schema_def_shadow.golden     test/goldens/schema_def_text.golden+    test/goldens/schema_def_union.golden+    test/goldens/schema_def_union_grouped.golden     test/goldens/schema_def_unknown_type.golden     test/goldens/text.golden+    test/goldens/union.golden+    test/goldens/union_0.golden+    test/goldens/union_0_a.golden+    test/goldens/union_1.golden+    test/goldens/union_2.golden     test/goldens/unwrap_schema.golden     test/goldens/unwrap_schema_bad_bang.golden+    test/goldens/unwrap_schema_bad_branch.golden     test/goldens/unwrap_schema_bad_key.golden     test/goldens/unwrap_schema_bad_list.golden     test/goldens/unwrap_schema_bad_question.golden+    test/goldens/unwrap_schema_branch_out_of_bounds.golden     test/goldens/unwrap_schema_nested_list.golden     test/goldens/unwrap_schema_nested_object.golden @@ -102,6 +111,8 @@       Data.Aeson.Schema.Internal       Data.Aeson.Schema.Show       Data.Aeson.Schema.TH+      Data.Aeson.Schema.Utils.Sum+      Data.Aeson.Schema.Utils.TypeFamilies   other-modules:       Data.Aeson.Schema.TH.Enum       Data.Aeson.Schema.TH.Get@@ -117,8 +128,8 @@       aeson >=1.1.2.0 && <1.5     , base >=4.9 && <5     , bytestring >=0.10.8.1 && <0.11-    , first-class-families >=0.3.0.0 && <0.6.1-    , megaparsec >=6.0.0 && <7.1+    , first-class-families >=0.3.0.0 && <0.9+    , megaparsec >=6.0.0 && <9     , template-haskell >=2.12.0.0 && <2.16     , text >=1.2.2.2 && <1.3     , unordered-containers >=0.2.8.0 && <0.3@@ -133,23 +144,28 @@   main-is: Main.hs   other-modules:       AllTypes+      Enums       Nested       Schema+      SumType       Util       Paths_aeson_schemas   hs-source-dirs:       test   ghc-options: -Wall   build-depends:-      aeson >=1.1.2.0 && <1.5+      QuickCheck >=2.7 && <3+    , aeson >=1.1.2.0 && <1.5     , aeson-schemas     , base >=4.9 && <5     , bytestring >=0.10.8.1 && <0.11-    , first-class-families >=0.3.0.0 && <0.6.1-    , megaparsec >=6.0.0 && <7.1+    , first-class-families >=0.3.0.0 && <0.9+    , megaparsec >=6.0.0 && <9     , raw-strings-qq >=1.1 && <1.2     , tasty >=0.11.3 && <1.3     , tasty-golden >=2.3.1.2 && <2.4+    , tasty-hunit >=0.9 && <0.11+    , tasty-quickcheck >=0.9 && <0.11     , template-haskell >=2.12.0.0 && <2.16     , text >=1.2.2.2 && <1.3     , th-test-utils >=1.0.0 && <1.1
src/Data/Aeson/Schema/Internal.hs view
@@ -44,12 +44,14 @@ import Data.Text (Text) import qualified Data.Text as Text import Data.Typeable (Typeable, splitTyConApp, tyConName, typeRep, typeRepTyCon)-import Fcf (type (<=<), type (=<<), Eval, Find, FromMaybe, Fst, Snd, TyEq)+import Fcf (type (=<<), Eval, FromMaybe, Lookup) import GHC.Exts (toList) import GHC.TypeLits     (ErrorMessage(..), KnownSymbol, Symbol, TypeError, symbolVal)  import qualified Data.Aeson.Schema.Show as SchemaShow+import Data.Aeson.Schema.Utils.Sum (SumType)+import Data.Aeson.Schema.Utils.TypeFamilies (All)  {- Schema-validated JSON object -} @@ -83,6 +85,7 @@   | SchemaMaybe SchemaType   | SchemaList SchemaType   | SchemaObject [(Symbol, SchemaType)]+  | SchemaUnion [SchemaType] -- ^ @since v1.1.0  -- | Convert 'SchemaType' into 'SchemaShow.SchemaType'. toSchemaTypeShow :: forall (a :: SchemaType). Typeable a => SchemaShow.SchemaType@@ -96,16 +99,24 @@       ("'SchemaCustom", [inner]) -> SchemaShow.SchemaCustom $ typeRepName inner       ("'SchemaMaybe", [inner]) -> SchemaShow.SchemaMaybe $ cast inner       ("'SchemaList", [inner]) -> SchemaShow.SchemaList $ cast inner-      ("'SchemaObject", [pairs]) -> SchemaShow.SchemaObject $ getSchemaObjectPairs pairs+      ("'SchemaObject", [pairs]) -> SchemaShow.SchemaObject $ map getSchemaObjectPair $ typeRepToList pairs+      ("'SchemaUnion", [schemas]) -> SchemaShow.SchemaUnion $ map cast $ typeRepToList schemas       _ -> error $ "Unknown schema type: " ++ show tyRep-    getSchemaObjectPairs tyRep = case splitTypeRep tyRep of++    getSchemaObjectPair tyRep =+      let (key, val) = typeRepToPair tyRep+          key' = tail . init . typeRepName $ key -- strip leading + trailing quote+      in (key', cast val)++    typeRepToPair tyRep = case splitTypeRep tyRep of+      ("'(,)", [a, b]) -> (a, b)+      _ -> error $ "Unknown pair: " ++ show tyRep++    typeRepToList tyRep = case splitTypeRep tyRep of       ("'[]", []) -> []-      ("':", [x, rest]) -> case splitTypeRep x of-        ("'(,)", [key, val]) ->-          let key' = tail . init . typeRepName $ key -- strip leading + trailing quote-          in (key', cast val) : getSchemaObjectPairs rest-        _ -> error $ "Unknown pair: " ++ show x+      ("':", [x, rest]) -> x : typeRepToList rest       _ -> error $ "Unknown list: " ++ show tyRep+     splitTypeRep = first tyConName . splitTyConApp     typeRepName = tyConName . typeRepTyCon @@ -125,7 +136,12 @@   SchemaResult ('SchemaMaybe inner) = Maybe (SchemaResult inner)   SchemaResult ('SchemaList inner) = [SchemaResult inner]   SchemaResult ('SchemaObject inner) = Object ('SchemaObject inner)+  SchemaResult ('SchemaUnion schemas) = SumType (SchemaResultList schemas) +type family SchemaResultList (xs :: [SchemaType]) where+  SchemaResultList '[] = '[]+  SchemaResultList (x ': xs) = SchemaResult x ': SchemaResultList xs+ -- | A type-class for types that can be parsed from JSON for an associated schema type. class Typeable schema => IsSchemaType (schema :: SchemaType) where   parseValue :: [Text] -> Value -> Parser (SchemaResult schema)@@ -193,6 +209,13 @@         in maybe (show dynValue) show $ fromDynamic @(SchemaResult inner) dynValue       pair = "\"" ++ key ++ "\": " ++ value +instance+  ( All IsSchemaType schemas+  , Typeable schemas+  , Show (SchemaResult ('SchemaUnion schemas))+  , FromJSON (SchemaResult ('SchemaUnion schemas))+  ) => IsSchemaType ('SchemaUnion schemas)+ -- | A helper for creating fail messages when parsing a schema. parseFail :: forall (schema :: SchemaType) m a. (MonadFail m, Typeable schema) => [Text] -> Value -> m a parseFail path value = fail $ msg ++ ": " ++ ellipses 200 (show value)@@ -209,14 +232,14 @@ -- | The type-level function that return the schema of the given key in a 'SchemaObject'. type family LookupSchema (key :: Symbol) (schema :: SchemaType) :: SchemaType where   LookupSchema key ('SchemaObject schema) = Eval-    ( Snd-    =<< FromMaybe (TypeError-      (     'Text "Key '"-      ':<>: 'Text key-      ':<>: 'Text "' does not exist in the following schema:"-      ':$$: 'ShowType schema-      ))-    =<< Find (TyEq key <=< Fst) schema+    ( FromMaybe (TypeError+        (     'Text "Key '"+        ':<>: 'Text key+        ':<>: 'Text "' does not exist in the following schema:"+        ':$$: 'ShowType schema+        )+      )+      =<< Lookup key schema     )   LookupSchema key schema = TypeError     (     'Text "Attempted to lookup key '"
src/Data/Aeson/Schema/Show.hs view
@@ -25,6 +25,7 @@   | SchemaMaybe SchemaType   | SchemaList SchemaType   | SchemaObject [(String, SchemaType)]+  | SchemaUnion [SchemaType]   deriving (Show)  -- | Pretty show the given SchemaType.@@ -38,6 +39,7 @@   SchemaMaybe inner -> "SchemaMaybe " ++ showSchemaType' inner   SchemaList inner -> "SchemaList " ++ showSchemaType' inner   SchemaObject _ -> "SchemaObject " ++ showSchemaType' schema+  SchemaUnion _ -> "SchemaUnion " ++ showSchemaType' schema   where     showSchemaType' = \case       SchemaBool -> "Bool"@@ -47,5 +49,8 @@       SchemaCustom s -> s       SchemaMaybe inner -> "Maybe " ++ showSchemaType' inner       SchemaList inner -> "List " ++ showSchemaType' inner-      SchemaObject pairs -> "{" ++ intercalate ", " (map showPair pairs) ++ "}"+      SchemaObject pairs -> "{" ++ mapJoin showPair ", " pairs ++ "}"+      SchemaUnion schemas -> "( " ++ mapJoin showSchemaType' " | " schemas ++ " )"     showPair (key, inner) = "\"" ++ key ++ "\": " ++ showSchemaType' inner++    mapJoin f delim = intercalate delim . map f
src/Data/Aeson/Schema/TH.hs view
@@ -30,6 +30,7 @@   -- * Helpers for Enum types   , mkEnum   , genFromJSONEnum+  , genToJSONEnum   ) where  import Data.Aeson.Schema.TH.Enum
src/Data/Aeson/Schema/TH/Enum.hs view
@@ -11,15 +11,17 @@  module Data.Aeson.Schema.TH.Enum   ( genFromJSONEnum+  , genToJSONEnum   , mkEnum   ) where  import Control.Monad (forM, unless)-import Data.Aeson (FromJSON(..), Value(..))+import Data.Aeson (FromJSON(..), ToJSON(..), Value(..)) import Data.Char (toLower) import Data.Maybe (mapMaybe) import qualified Data.Text as Text import Language.Haskell.TH+import Language.Haskell.TH.Syntax (lift)  -- | Make an enum type with the given constructors, that can be parsed from JSON. --@@ -27,18 +29,19 @@ -- case-insensitive. -- -- @--- mkEnum "State" ["OPEN", "CLOSED"]+-- mkEnum \"State" [\"OPEN", \"CLOSED"] ----- main = print---   [ decode \"open" :: Maybe State---   , decode \"OPEN" :: Maybe State---   , decode \"closed" :: Maybe State---   , decode \"CLOSED" :: Maybe State---   ]+-- -- generates equivalent of:+-- --   data State = OPEN | CLOSED deriving (...)+-- --   genFromJSONEnum ''State+-- --   genToJSONEnum ''State -- @ mkEnum :: String -> [String] -> Q [Dec]-mkEnum name vals = do-  (:) <$> dataDec <*> mkFromJSON name' vals'+mkEnum name vals = concat <$> sequence+  [ (:[]) <$> dataDec+  , mkFromJSON name' vals'+  , mkToJSON name' vals'+  ]   where     name' = mkName name     vals' = map mkName vals@@ -60,18 +63,53 @@ -- case-insensitive. -- -- @--- data State = OPEN | CLOSED deriving (Show,Enum)+-- data State = Open | CLOSED deriving (Show,Enum) -- genFromJSONEnum ''State ----- main = print---   [ decode \"open" :: Maybe State---   , decode \"OPEN" :: Maybe State---   , decode \"closed" :: Maybe State---   , decode \"CLOSED" :: Maybe State+-- -- outputs:+-- --   Just Open+-- --   Just Open+-- --   Just CLOSED+-- --   Just CLOSED+-- main = mapM_ print+--   [ decodeState \"open"+--   , decodeState \"OPEN"+--   , decodeState \"closed"+--   , decodeState \"CLOSED" --   ]+--   where+--     decodeState :: String -> Maybe State+--     decodeState = decode . show -- @ genFromJSONEnum :: Name -> Q [Dec]-genFromJSONEnum name = do+genFromJSONEnum name = getEnumConstructors name >>= mkFromJSON name++-- | Generate an instance of 'ToJSON' for the given data type.+--+-- Prefer using 'mkEnum'; this function is useful for data types in which you want greater control+-- over the actual data type.+--+-- The 'ToJSON' instance will encode the enum as a string matching the constructor name.+--+-- @+-- data State = Open | CLOSED deriving (Show,Enum)+-- genToJSONEnum ''State+--+-- -- outputs:+-- --   \"Open"+-- --   \"CLOSED"+-- main = mapM_ print+--   [ encode Open+--   , encode CLOSED+--   ]+-- @+genToJSONEnum :: Name -> Q [Dec]+genToJSONEnum name = getEnumConstructors name >>= mkToJSON name++{- Helpers -}++getEnumConstructors :: Name -> Q [Name]+getEnumConstructors name = do   -- check if 'name' is an Enum   ClassI _ instances <- reify ''Enum   let instanceNames = flip mapMaybe instances $ \case@@ -80,16 +118,12 @@   unless (name `elem` instanceNames) $ fail $ "Not an Enum type: " ++ show name    -- extract constructor names-  cons <- reify name >>= \case+  reify name >>= \case     TyConI (DataD _ _ _ _ cons _) -> forM cons $ \case       NormalC con [] -> return con       con -> fail $ "Invalid constructor: " ++ show con     info -> fail $ "Invalid data type: " ++ show info -  mkFromJSON name cons--{- Helpers -}- mkFromJSON :: Name -> [Name] -> Q [Dec] mkFromJSON name cons = do   let toPattern = litP . stringL . map toLower . nameBase@@ -108,3 +142,12 @@     badParse =       let prefix = litE $ stringL $ "Bad " ++ nameBase name ++ ": "       in [| fail . ($prefix ++) . show |]++mkToJSON :: Name -> [Name] -> Q [Dec]+mkToJSON name cons =+  [d|+    instance ToJSON $(conT name) where+      toJSON = $(lamCaseE $ map encodeConstructor cons)+    |]+  where+    encodeConstructor con = match (conP con []) (normalB [| String $ Text.pack $(lift $ nameBase con) |]) []
src/Data/Aeson/Schema/TH/Get.hs view
@@ -15,6 +15,7 @@  import Control.Monad (unless, (>=>)) import qualified Data.Maybe as Maybe+import Data.Proxy (Proxy(..)) import GHC.Stack (HasCallStack) import Language.Haskell.TH import Language.Haskell.TH.Quote (QuasiQuoter(..))@@ -23,6 +24,7 @@ import Data.Aeson.Schema.Internal (getKey) import Data.Aeson.Schema.TH.Parse (GetterExp(..), getterExp, parse) import Data.Aeson.Schema.TH.Utils (GetterOperation(..), showGetterOps)+import Data.Aeson.Schema.Utils.Sum (fromSumType)  -- | Defines a QuasiQuoter for extracting JSON data. --@@ -69,6 +71,13 @@ --     * @x[]!@ unwraps all 'Just' values in @x@ (and errors if any 'Nothing' values exist in @x@) -- -- * @x?@ follows the same rules as @x[]@ except it's only valid if @x@ is a 'Maybe'.+--+-- * @x\@#@ is only valid if @x@ is a 'SumType'. If the sum type contains a value at the given+--   branch (e.g. @x\@0@ for @Here v@), return 'Just' that value, otherwise 'Nothing'. (added in+--   v1.1.0)+--+--   e.g. with the schema @{ a: Int | Bool }@, calling @[get| .a\@0 |]@ will return @Maybe Int@ if+--   the sum type contains an 'Int'. get :: QuasiQuoter get = QuasiQuoter   { quoteExp = parse getterExp >=> generateGetterExp@@ -102,12 +111,15 @@             checkLast label = unless (null ops) $ fail $ label ++ " operation MUST be last."             fromJustMsg = startDisplay ++ showGetterOps (reverse history)         in case op of-          GetterKey key     -> applyToNext' $ Right $ appTypeE [| getKey |] (litT $ strTyLit key)-          GetterList elems  -> checkLast ".[*]" >> applyToEach' listE elems-          GetterTuple elems -> checkLast ".(*)" >> applyToEach' tupE elems-          GetterBang        -> applyToNext' $ Right [| fromJust $(lift fromJustMsg) |]-          GetterMapMaybe    -> applyToNext' $ Left [| (<$?>) |]-          GetterMapList     -> applyToNext' $ Left [| (<$:>) |]+          GetterKey key       -> applyToNext' $ Right $ appTypeE [| getKey |] (litT $ strTyLit key)+          GetterList elems    -> checkLast ".[*]" >> applyToEach' listE elems+          GetterTuple elems   -> checkLast ".(*)" >> applyToEach' tupE elems+          GetterBang          -> applyToNext' $ Right [| fromJust $(lift fromJustMsg) |]+          GetterMapMaybe      -> applyToNext' $ Left [| (<$?>) |]+          GetterMapList       -> applyToNext' $ Left [| (<$:>) |]+          GetterBranch branch ->+            let branchTyLit = litT $ numTyLit $ fromIntegral branch+            in applyToNext' $ Right [| fromSumType (Proxy :: Proxy $branchTyLit) |]  -- | fromJust with helpful error message fromJust :: HasCallStack => String -> Maybe a -> a
src/Data/Aeson/Schema/TH/Parse.hs view
@@ -45,6 +45,7 @@   [ lexeme "!" $> GetterBang   , lexeme "[]" $> GetterMapList   , lexeme "?" $> GetterMapMaybe+  , lexeme "@" *> (GetterBranch . read <$> some digitChar)   , optional (lexeme ".") *> choice       [ GetterKey <$> jsonKey       , fmap GetterList $ between (lexeme "[") (lexeme "]") $ some parseGetterOp `sepBy1` lexeme ","@@ -53,14 +54,23 @@   ]  parseSchemaDef :: Parser SchemaDef-parseSchemaDef = choice-  [ between (lexeme "{") (lexeme "}") $ SchemaDefObj <$> parseSchemaDefObjItems-  , lexeme "Maybe" *> (SchemaDefMaybe <$> parseSchemaDef)-  , lexeme "List" *> (SchemaDefList <$> parseSchemaDef)-  , SchemaDefType <$> identifier upperChar-  , SchemaDefInclude <$> parseSchemaReference-  ]+parseSchemaDef = parseSchemaDefWithUnions   where+    parseSchemaDefWithUnions =+      let parseSchemaUnion [] = error "Parsed no schema definitions" -- should not happen; sepBy1 returns one or more+          parseSchemaUnion [schemaDef'] = schemaDef'+          parseSchemaUnion schemaDefs = SchemaDefUnion schemaDefs+      in fmap parseSchemaUnion $ parseSchemaDefWithoutUnions `sepBy1` lexeme "|"++    parseSchemaDefWithoutUnions = choice+      [ between (lexeme "{") (lexeme "}") $ SchemaDefObj <$> parseSchemaDefObjItems+      , between (lexeme "(") (lexeme ")") parseSchemaDefWithUnions+      , lexeme "Maybe" *> (SchemaDefMaybe <$> parseSchemaDefWithoutUnions)+      , lexeme "List" *> (SchemaDefList <$> parseSchemaDefWithoutUnions)+      , SchemaDefType <$> identifier upperChar+      , SchemaDefInclude <$> parseSchemaReference+      ] <* space -- allow any trailing spaces+     parseSchemaDefObjItems = parseSchemaDefObjItem `sepEndBy1` lexeme ","     parseSchemaDefObjItem = choice       [ SchemaDefObjPair <$> parseSchemaDefPair@@ -69,7 +79,7 @@     parseSchemaDefPair = do       key <- jsonKey       lexeme ":"-      value <- parseSchemaDef+      value <- parseSchemaDefWithUnions       return (key, value)     parseSchemaReference = char '#' *> namespacedIdentifier upperChar @@ -96,7 +106,7 @@ jsonKey = some $ noneOf $ " " ++ schemaChars ++ getChars   where     -- characters that cause ambiguity when parsing 'get' expressions-    getChars = "!?[](),."+    getChars = "!?[](),.@"     -- characters that should not indicate the start of a key when parsing 'schema' definitions     schemaChars = ":{}#" @@ -108,6 +118,7 @@   | SchemaDefList SchemaDef   | SchemaDefInclude String   | SchemaDefObj [SchemaDefObjItem]+  | SchemaDefUnion [SchemaDef]   deriving (Show)  data SchemaDefObjItem
src/Data/Aeson/Schema/TH/Schema.hs view
@@ -17,6 +17,7 @@ module Data.Aeson.Schema.TH.Schema (schema) where  import Control.Monad ((<=<), (>=>))+import Data.Bifunctor (second) import qualified Data.HashMap.Strict as HashMap import Data.Maybe (mapMaybe) import Language.Haskell.TH@@ -24,7 +25,8 @@  import Data.Aeson.Schema.Internal (SchemaType(..)) import Data.Aeson.Schema.TH.Parse-import Data.Aeson.Schema.TH.Utils (fromTypeList, toTypeList)+import Data.Aeson.Schema.TH.Utils+    (schemaPairsToTypeQ, typeQListToTypeQ, typeToSchemaPairs)  -- | Defines a QuasiQuoter for writing schemas. --@@ -60,6 +62,10 @@ -- * @Maybe \<schema\>@ and @List \<schema\>@ correspond to @Maybe@ and @[]@, containing values --   specified by the provided schema (no parentheses needed). --+-- * @\<schema1\> | \<schema2\>@ corresponds to a JSON value that matches one of the given schemas.+--   When extracted from an 'Data.Aeson.Schema.Object', it deserializes into a+--   'Data.Aeson.Schema.Utils.Sum.JSONSum' object. (added in v1.1.0)+-- -- * Any other uppercase identifier corresponds to the respective type in scope -- requires a --   FromJSON instance. --@@ -79,7 +85,7 @@ generateSchemaObject :: [SchemaDefObjItem] -> TypeQ generateSchemaObject items = [t| 'SchemaObject $(fromItems items) |]   where-    fromItems = toTypeList <=< resolveParts . concat <=< mapM toParts+    fromItems = schemaPairsToTypeQ <=< resolveParts . concat <=< mapM toParts  generateSchema :: SchemaDef -> TypeQ generateSchema = \case@@ -92,6 +98,7 @@   SchemaDefList inner    -> [t| 'SchemaList $(generateSchema inner) |]   SchemaDefInclude other -> getType other   SchemaDefObj items     -> generateSchemaObject items+  SchemaDefUnion schemas -> [t| 'SchemaUnion $(typeQListToTypeQ $ map generateSchema schemas) |]  {- Helpers -} @@ -113,7 +120,7 @@     name <- getName other     reify name >>= \case       TyConI (TySynD _ _ (AppT (PromotedT ty) inner)) | ty == 'SchemaObject ->-        tagAs Imported <$> fromTypeList inner+        pure . tagAs Imported . map (second pure) $ typeToSchemaPairs inner       _ -> fail $ "'" ++ show name ++ "' is not a SchemaObject"   where     tagAs source = map $ \(k,v) -> (k,v,source)
src/Data/Aeson/Schema/TH/Unwrap.hs view
@@ -44,6 +44,8 @@ -- * @x?@ is the same as @x!@. -- -- * @x[]@ is only valid if @x@ is a @[a]@ type. Returns @a@, the type contained in the list.+--+-- * @x\@#@ is only valid if @x@ is a @SumType@. Returns the type at that branch in the sum type. unwrap :: QuasiQuoter unwrap = QuasiQuoter   { quoteExp = error "Cannot use `unwrap` for Exp"
src/Data/Aeson/Schema/TH/Utils.hs view
@@ -13,7 +13,7 @@ module Data.Aeson.Schema.TH.Utils where  import Control.Monad ((>=>))-import Data.Bifunctor (second)+import Data.Bifunctor (bimap, second) import Data.List (intercalate) import Data.Text (Text) import Language.Haskell.TH@@ -38,31 +38,42 @@         | name == 'SchemaMaybe -> SchemaShow.SchemaMaybe $ fromSchemaType inner         | name == 'SchemaList -> SchemaShow.SchemaList $ fromSchemaType inner         | name == 'SchemaObject -> SchemaShow.SchemaObject $ fromPairs inner+        | name == 'SchemaUnion -> SchemaShow.SchemaUnion $ map fromSchemaType $ typeToList inner       ty -> error $ "Unknown type: " ++ show ty-    fromPairs pairs = map (second fromSchemaType) $ fromTypeList' pairs -fromTypeList' :: Type -> [(String, Type)]-fromTypeList' = \case+    fromPairs pairs = map (second fromSchemaType) $ typeToSchemaPairs pairs++typeToList :: Type -> [Type]+typeToList = \case   PromotedNilT -> []-  AppT (AppT PromotedConsT x) xs -> fromTypeTuple x : fromTypeList' xs-  SigT ty _ -> fromTypeList' ty+  AppT (AppT PromotedConsT x) xs -> x : typeToList xs+  SigT ty _ -> typeToList ty   ty -> error $ "Not a type-level list: " ++ show ty-  where-    fromTypeTuple = \case-      AppT (AppT (PromotedTupleT 2) (LitT (StrTyLit k))) v -> (k, stripSigs v)-      SigT ty _ -> fromTypeTuple ty-      x -> error $ "Not a type-level tuple: " ++ show x -fromTypeList :: Type -> Q [(String, TypeQ)]-fromTypeList = pure . map (second pure) . fromTypeList'+typeToPair :: Type -> (Type, Type)+typeToPair = \case+  AppT (AppT (PromotedTupleT 2) a) b -> (a, b)+  SigT ty _ -> typeToPair ty+  ty -> error $ "Not a type-level pair: " ++ show ty -toTypeList :: [(String, TypeQ)] -> TypeQ-toTypeList = foldr (consT . pairT) promotedNilT+typeToSchemaPairs :: Type -> [(String, Type)]+typeToSchemaPairs = map (bimap toTypeStr stripSigs . typeToPair) . typeToList   where-    pairT (k, v) = [t| '( $(litT $ strTyLit k), $v) |]+    toTypeStr = \case+      LitT (StrTyLit k) -> k+      x -> error $ "Not a type-level string: " ++ show x++typeQListToTypeQ :: [TypeQ] -> TypeQ+typeQListToTypeQ = foldr consT promotedNilT+  where     -- nb. https://stackoverflow.com/a/34457936     consT x xs = appT (appT promotedConsT x) xs +schemaPairsToTypeQ :: [(String, TypeQ)] -> TypeQ+schemaPairsToTypeQ = typeQListToTypeQ . map pairT+  where+    pairT (k, v) = [t| '( $(litT $ strTyLit k), $v) |]+ -- | Strip all kind signatures from the given type. stripSigs :: Type -> Type stripSigs = \case@@ -92,6 +103,7 @@         | ty == 'SchemaMaybe -> [t| Maybe $(fromSchemaType inner) |]         | ty == 'SchemaList -> [t| [$(fromSchemaType inner)] |]         | ty == 'SchemaObject -> [t| Object $(pure schema) |]+        | ty == 'SchemaUnion -> [t| SchemaResult $(pure schema) |]       PromotedT ty         | ty == 'SchemaBool -> [t| Bool |]         | ty == 'SchemaInt -> [t| Int |]@@ -123,6 +135,12 @@       GetterMapMaybe -> fail $ "Cannot use `?` operator on schema: " ++ showSchemaType schema       GetterMapList | ty == 'SchemaList -> withFunctor (pure ListT) $ unwrapType' ops inner       GetterMapList -> fail $ "Cannot use `[]` operator on schema: " ++ showSchemaType schema+      GetterBranch branch | ty == 'SchemaUnion ->+        let subTypes = typeToList inner+        in if branch >= length subTypes+          then fail $ "Branch out of bounds for schema: " ++ showSchemaType schema+          else unwrapType' ops $ subTypes !! branch+      GetterBranch _ -> fail $ "Cannot use `@` operator on schema: " ++ showSchemaType schema   -- allow starting from (Object schema)   AppT (ConT ty) inner | ty == ''Object -> unwrapType' (op:ops) inner   schema -> fail $ unlines ["Cannot get type:", show schema, show op]@@ -148,6 +166,7 @@   | GetterBang   | GetterMapList   | GetterMapMaybe+  | GetterBranch Int   deriving (Show,Lift)  showGetterOps :: GetterOps -> String@@ -160,3 +179,4 @@       GetterBang -> "!"       GetterMapList -> "[]"       GetterMapMaybe -> "?"+      GetterBranch x -> '@' : show x
+ src/Data/Aeson/Schema/Utils/Sum.hs view
@@ -0,0 +1,130 @@+{-|+Module      :  Data.Aeson.Schema.Utils.Sum+Maintainer  :  Brandon Chinn <brandon@leapyear.io>+Stability   :  experimental+Portability :  portable++The 'SumType' data type that represents a sum type consisting of types+specified in a type-level list.+-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE ExplicitNamespaces #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE GADTs #-}+{-# LANGUAGE KindSignatures #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE StandaloneDeriving #-}+{-# LANGUAGE TypeApplications #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE TypeOperators #-}+{-# LANGUAGE UndecidableInstances #-}++module Data.Aeson.Schema.Utils.Sum+  ( SumType(..)+  , fromSumType+  ) where++import Control.Applicative ((<|>))+import Data.Aeson (FromJSON(..))+import Data.Kind (Constraint, Type)+import Data.Proxy (Proxy(..))+import GHC.TypeLits (type (-), ErrorMessage(..), Nat, TypeError)++-- | Represents a sum type.+--+-- Loads the first type that successfully parses the JSON value.+--+-- Example:+--+-- @+-- data Owl = Owl+-- data Cat = Cat+-- data Toad = Toad+-- type Animal = SumType '[Owl, Cat, Toad]+--+-- Here Owl                         :: Animal+-- There (Here Cat)                 :: Animal+-- There (There (Here Toad))        :: Animal+--+-- {- Fails at compile-time+-- Here True                        :: Animal+-- Here Cat                         :: Animal+-- There (Here Owl)                 :: Animal+-- There (There (There (Here Owl))) :: Animal+-- -}+-- @+data SumType (types :: [Type]) where+  Here  :: forall x xs. x          -> SumType (x ': xs)+  There :: forall x xs. SumType xs -> SumType (x ': xs)++deriving instance (Show x, Show (SumType xs)) => Show (SumType (x ': xs))+instance Show (SumType '[]) where+  show = error "impossible"++deriving instance (Eq x, Eq (SumType xs)) => Eq (SumType (x ': xs))+instance Eq (SumType '[]) where+  (==) = error "impossible"++deriving instance (Ord x, Ord (SumType xs)) => Ord (SumType (x ': xs))+instance Ord (SumType '[]) where+  compare = error "impossible"++instance (FromJSON x, FromJSON (SumType xs)) => FromJSON (SumType (x ': xs)) where+  parseJSON v = (Here <$> parseJSON v) <|> (There <$> parseJSON v)++instance FromJSON (SumType '[]) where+  parseJSON _ = fail "Could not parse sum type"++{- Extracting sum type branches -}++class FromSumType (n :: Nat) (types :: [Type]) (x :: Type) where+  fromSumType' :: 'Just x ~ GetIndex n types => proxy1 n -> SumType types -> Maybe x++instance {-# OVERLAPPING #-} FromSumType 0 (x ': xs) x where+  fromSumType' _ = \case+    Here x -> Just x+    There _ -> Nothing++instance {-# OVERLAPPABLE #-}+  ( FromSumType (n - 1) xs x+  , 'Just x ~ GetIndex (n - 1) xs+  ) => FromSumType n (_x ': xs) x where+  fromSumType' _ = \case+    Here _ -> Nothing+    There xs -> fromSumType' (Proxy @(n - 1)) xs++fromSumType+  :: ( IsInRange n types+     , 'Just result ~ GetIndex n types+     , FromSumType n types result+     )+  => proxy n -> SumType types -> Maybe result+fromSumType = fromSumType'++{- Helpers -}++type family IsInRange (n :: Nat) (xs :: [Type]) :: Constraint where+  IsInRange n xs = IsInRange'+    ( TypeError+      (     'Text "Index "+      ':<>: 'ShowType n+      ':<>: 'Text " does not exist in list: "+      ':<>: 'ShowType xs+      )+    )+    n+    xs++type family IsInRange' typeErr (n :: Nat) (xs :: [Type]) :: Constraint where+  IsInRange' typeErr _ '[] = typeErr+  IsInRange' _ 0 (_ ': _) = ()+  IsInRange' typeErr n (_ ': xs) = IsInRange' typeErr (n - 1) xs++type family GetIndex (n :: Nat) (types :: [Type]) :: Maybe Type where+  GetIndex 0 (x ': xs) = 'Just x+  GetIndex _ '[] = 'Nothing+  GetIndex n (_ ': xs) = GetIndex (n - 1) xs
+ src/Data/Aeson/Schema/Utils/TypeFamilies.hs view
@@ -0,0 +1,23 @@+{-|+Module      :  Data.Aeson.Schema.Utils.TypeFamilies+Maintainer  :  Brandon Chinn <brandon@leapyear.io>+Stability   :  experimental+Portability :  portable++Utilities for working with type families.+-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE KindSignatures #-}+{-# LANGUAGE PolyKinds #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE TypeOperators #-}++module Data.Aeson.Schema.Utils.TypeFamilies+  ( All+  ) where++import Data.Kind (Constraint)++type family All (f :: a -> Constraint) (xs :: [a]) :: Constraint where+  All _ '[] = ()+  All f (x ': xs) = (f x, All f xs)
test/AllTypes.hs view
@@ -60,6 +60,9 @@     },     nonexistent: Maybe Text,     // future_key: Int,+    union: List (+        { a: Int } | List Bool | Text+    ),   } |] 
+ test/Enums.hs view
@@ -0,0 +1,13 @@+{-# LANGUAGE TemplateHaskell #-}++module Enums (State(..), Color(..)) where++import Data.Aeson.Schema.TH (genFromJSONEnum, genToJSONEnum, mkEnum)++mkEnum "State" ["OPEN", "CLOSED"]++data Color = Red | LightBlue | Yellow | DarkGreen | Black | JustABitOffWhite+  deriving (Show, Eq, Enum)++genFromJSONEnum ''Color+genToJSONEnum ''Color
test/Main.hs view
@@ -1,23 +1,34 @@ {-# LANGUAGE CPP #-} {-# LANGUAGE DataKinds #-} {-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE QuasiQuotes #-}+{-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TypeApplications #-} {-# LANGUAGE TypeFamilies #-} +import Data.Aeson (ToJSON, decode, eitherDecode, encode) import Data.Aeson.Schema (Object, get, unwrap)+import Data.Aeson.Schema.Utils.Sum (SumType(..), fromSumType) import qualified Data.ByteString.Lazy.Char8 as ByteString+import Data.Char (toLower, toUpper) import Data.Maybe (fromMaybe)+import Data.Proxy (Proxy(..)) import qualified Data.Text as Text import Language.Haskell.TH.TestUtils (tryQErr') import Test.Tasty (TestTree, defaultMain, testGroup) import Test.Tasty.Golden (goldenVsString)+import Test.Tasty.HUnit (testCase, (@?=))+import Test.Tasty.QuickCheck+    (arbitrary, elements, infiniteListOf, oneof, testProperty, (===)) import Text.RawString.QQ (r)  import qualified AllTypes+import Enums import qualified Nested import Schema+import SumType import Util  allTypes :: Object AllTypes.Schema@@ -32,6 +43,8 @@   , testUnwrapSchema   , testSchemaDef   , testMkGetter+  , testEnumTH+  , testSumType   ]  goldens' :: String -> String -> TestTree@@ -74,6 +87,11 @@   , goldens "list_maybeBool"           [get| allTypes.list[].maybeBool      |]   , goldens "list_maybeInt"            [get| allTypes.list[].maybeInt       |]   , goldens "nonexistent"              [get| allTypes.nonexistent           |]+  , goldens "union"                    [get| allTypes.union                 |]+  , goldens "union_0"                  [get| allTypes.union[]@0             |]+  , goldens "union_0_a"                [get| allTypes.union[]@0?.a          |]+  , goldens "union_1"                  [get| allTypes.union[]@1             |]+  , goldens "union_2"                  [get| allTypes.union[]@2             |]   -- bad 'get' expressions   , goldens' "maybeListNull_bang" $(getError [get| (AllTypes.result).maybeListNull! |]) #if MIN_VERSION_megaparsec(7,0,0)@@ -127,6 +145,8 @@   , goldens' "unwrap_schema_bad_question" $(tryQErr' $ showUnwrap "(AllTypes.Schema).list[]?")   , goldens' "unwrap_schema_bad_list" $(tryQErr' $ showUnwrap "(AllTypes.Schema).list[][]")   , goldens' "unwrap_schema_bad_key" $(tryQErr' $ showUnwrap "(AllTypes.Schema).list.a")+  , goldens' "unwrap_schema_bad_branch" $(tryQErr' $ showUnwrap "(AllTypes.Schema).list@0")+  , goldens' "unwrap_schema_branch_out_of_bounds" $(tryQErr' $ showUnwrap "(AllTypes.Schema).union[]@10")   ]  testSchemaDef :: TestTree@@ -144,6 +164,8 @@   , goldens' "schema_def_import_user" $(showSchema [r| { user: #UserSchema } |])   , goldens' "schema_def_extend" $(showSchema [r| { a: Int, #(Schema.MySchema) } |])   , goldens' "schema_def_shadow" $(showSchema [r| { extra: Bool, #(Schema.MySchema) } |])+  , goldens' "schema_def_union" $(showSchema [r| { a: List Int | Text } |])+  , goldens' "schema_def_union_grouped" $(showSchema [r| { a: List (Int | Text) } |])   -- bad schema definitions   , goldens' "schema_def_duplicate" $(tryQErr' $ showSchema [r| { a: Int, a: Bool } |])   , goldens' "schema_def_duplicate_extend" $(tryQErr' $ showSchema [r| { #MySchema, #MySchema2 } |])@@ -163,3 +185,108 @@      getType :: AllTypes.ListItem -> Text.Text     getType = [get| .type |]++testEnumTH :: TestTree+testEnumTH = testGroup "Test the Enum TH helpers"+  -- State+  [ testProperty "mkEnum decode is case insensitive" $ do+      (val, enumVal) <- elements+        [ ("OPEN", OPEN)+        , ("CLOSED", CLOSED)+        ]+      casedVal <- randomlyCased val+      return $ decode (ByteString.pack $ show casedVal) === Just enumVal+  , testCase "mkEnum encode keeps case of constructor" $ do+      encode OPEN @?= "\"OPEN\""+      encode CLOSED @?= "\"CLOSED\""+  , testProperty "mkEnum: (fromJust . decode . encode) === id" $ do+      enumVal <- elements [OPEN, CLOSED]+      return $ (decode . encode) enumVal === Just enumVal++  -- Color+  , testProperty "genFromJSONEnum decode is case insensitive" $ do+      (val, enumVal) <- elements+        [ ("Red", Red)+        , ("LightBlue", LightBlue)+        , ("Yellow", Yellow)+        , ("DarkGreen", DarkGreen)+        , ("Black", Black)+        , ("JustABitOffWhite", JustABitOffWhite)+        ]+      casedVal <- randomlyCased val+      return $ decode (ByteString.pack $ show casedVal) === Just enumVal+  , testCase "genToJSONEnum encode keeps case of constructor" $ do+      encode Red @?= "\"Red\""+      encode LightBlue @?= "\"LightBlue\""+      encode Yellow @?= "\"Yellow\""+      encode DarkGreen @?= "\"DarkGreen\""+      encode Black @?= "\"Black\""+      encode JustABitOffWhite @?= "\"JustABitOffWhite\""+  , testProperty "genFromJSONEnum + genToJSONEnum: (fromJust . decode . encode) === id" $ do+      enumVal <- elements+        [ Red+        , LightBlue+        , Yellow+        , DarkGreen+        , Black+        , JustABitOffWhite+        ]+      return $ (decode . encode) enumVal === Just enumVal+  ]+  where+    randomlyCased s = do+      caseFuncs <- infiniteListOf $ elements [toLower, toUpper]+      return $ zipWith ($) caseFuncs s++testSumType :: TestTree+testSumType = testGroup "Test the SumType helper"+  [ testCase "Sanity checks" $+      let values =+            [ Here True+            , Here False+            , There (Here 1)+            , There (Here 10)+            , There (There (Here []))+            , There (There (Here ["a"]))+            ] :: [SpecialJSON]+      in values @?= values+  , testGroup "Decode SumType"+    [ testProperty "branch 1" $ \(b :: Bool) ->+        toSpecialJSON b === Right (Here b)+    , testProperty "branch 2" $ \(x :: Int) ->+        toSpecialJSON x === Right (There (Here x))+    , testProperty "branch 3" $ \(l :: [String]) ->+        toSpecialJSON l === Right (There (There (Here l)))+    , testCase "invalid SumType" $+        toSpecialJSON [True] @?= Left "Error in $: Could not parse sum type"+    ]+  , testGroup "fromSumType"+    [ testProperty "branch 0 valid" $ \b ->+        fromSumType (Proxy @0) (Here b :: SpecialJSON) === Just b+    , testProperty "branch 0 invalid" $ do+        value <- oneof+          [ There . Here <$> arbitrary+          , There . There . Here <$> arbitrary+          ]+        return $ fromSumType (Proxy @0) (value :: SpecialJSON) === Nothing+    , testProperty "branch 1 valid" $ \x ->+        fromSumType (Proxy @1) (There (Here x) :: SpecialJSON) === Just x+    , testProperty "branch 1 invalid" $ do+        value <- oneof+          [ Here <$> arbitrary+          , There . There . Here <$> arbitrary+          ]+        return $ fromSumType (Proxy @1) (value :: SpecialJSON) === Nothing+    , testProperty "branch 2 valid" $ \l ->+        fromSumType (Proxy @2) (There (There (Here l)) :: SpecialJSON) === Just l+    , testProperty "branch 2 invalid" $ do+        value <- oneof+          [ Here <$> arbitrary+          , There . Here <$> arbitrary+          ]+        return $ fromSumType (Proxy @2) (value :: SpecialJSON) === Nothing+    ]+  ]+  where+    toSpecialJSON :: ToJSON a => a -> Either String SpecialJSON+    toSpecialJSON = eitherDecode . encode
+ test/SumType.hs view
@@ -0,0 +1,7 @@+{-# LANGUAGE DataKinds #-}++module SumType where++import Data.Aeson.Schema.Utils.Sum (SumType(..))++type SpecialJSON = SumType '[Bool, Int, [String]]
test/all_types.json view
@@ -32,5 +32,11 @@             "type": "null",             "maybeNull": null         }+    ],+    "union": [+        "Hello",+        { "a": 1 },+        [true, false],+        "World!"     ] }
test/goldens/get_empty.golden view
@@ -3,4 +3,4 @@ 1 | <empty line>   | ^ unexpected end of input-expecting "[]", '!', '(', '.', '?', '[', lowercase letter, or white space+expecting "[]", '!', '(', '.', '?', '@', '[', lowercase letter, or white space
test/goldens/get_just_start.golden view
@@ -3,4 +3,4 @@ 1 | allTypes   |         ^ unexpected end of input-expecting "[]", '!', ''', '(', '.', '?', '[', or alphanumeric character+expecting "[]", '!', ''', '(', '.', '?', '@', '[', or alphanumeric character
+ test/goldens/schema_def_union.golden view
@@ -0,0 +1,1 @@+SchemaObject {"a": ( List Int | Text )}
+ test/goldens/schema_def_union_grouped.golden view
@@ -0,0 +1,1 @@+SchemaObject {"a": List ( Int | Text )}
+ test/goldens/union.golden view
@@ -0,0 +1,1 @@+[There (There (Here "Hello")),Here {"a": 1},There (Here [True,False]),There (There (Here "World!"))]
+ test/goldens/union_0.golden view
@@ -0,0 +1,1 @@+[Nothing,Just {"a": 1},Nothing,Nothing]
+ test/goldens/union_0_a.golden view
@@ -0,0 +1,1 @@+[Nothing,Just 1,Nothing,Nothing]
+ test/goldens/union_1.golden view
@@ -0,0 +1,1 @@+[Nothing,Nothing,Just [True,False],Nothing]
+ test/goldens/union_2.golden view
@@ -0,0 +1,1 @@+[Just "Hello",Nothing,Nothing,Just "World!"]
+ test/goldens/unwrap_schema_bad_branch.golden view
@@ -0,0 +1,1 @@+Cannot use `@` operator on schema: SchemaList {"type": Text, "maybeBool": Maybe Bool, "maybeInt": Maybe Int, "maybeNull": Maybe Bool}
+ test/goldens/unwrap_schema_branch_out_of_bounds.golden view
@@ -0,0 +1,1 @@+Branch out of bounds for schema: SchemaUnion ( {"a": Int} | List Bool | Text )