packages feed

aeson-schemas 1.1.0 → 1.2.0

raw patch · 21 files changed

+269/−76 lines, 21 filesdep ~megaparsecPVP ok

version bump matches the API change (PVP)

Dependency ranges changed: megaparsec

API changes (from Hackage documentation)

- Data.Aeson.Schema.Internal: instance (GHC.TypeLits.KnownSymbol key, Data.Aeson.Schema.Internal.IsSchemaType inner, GHC.Show.Show (Data.Aeson.Schema.Internal.SchemaResult inner), Data.Typeable.Internal.Typeable (Data.Aeson.Schema.Internal.SchemaResult inner), Data.Aeson.Schema.Internal.IsSchemaObject ('Data.Aeson.Schema.Internal.SchemaObject rest), Data.Typeable.Internal.Typeable rest) => Data.Aeson.Schema.Internal.IsSchemaType ('Data.Aeson.Schema.Internal.SchemaObject ('(key, inner) : rest))
+ Data.Aeson.Schema.Internal: NormalKey :: Symbol -> SchemaKey
+ Data.Aeson.Schema.Internal: PhantomKey :: Symbol -> SchemaKey
+ Data.Aeson.Schema.Internal: SchemaTry :: SchemaType -> SchemaType
+ Data.Aeson.Schema.Internal: class (Typeable schemaKey, KnownSymbol (FromSchemaKey schemaKey)) => KnownSchemaKey (schemaKey :: SchemaKey)
+ Data.Aeson.Schema.Internal: data SchemaKey
+ Data.Aeson.Schema.Internal: data UnSchemaKey :: SchemaKey -> Exp Symbol
+ Data.Aeson.Schema.Internal: fromSchemaKey :: forall schemaKey. KnownSymbol (FromSchemaKey schemaKey) => Text
+ Data.Aeson.Schema.Internal: getContext :: KnownSchemaKey schemaKey => HashMap Text Value -> Value
+ Data.Aeson.Schema.Internal: instance (Data.Aeson.Schema.Internal.IsSchemaType inner, GHC.Show.Show (Data.Aeson.Schema.Internal.SchemaResult inner)) => Data.Aeson.Schema.Internal.IsSchemaType ('Data.Aeson.Schema.Internal.SchemaTry inner)
+ Data.Aeson.Schema.Internal: instance (Data.Aeson.Schema.Internal.KnownSchemaKey schemaKey, Data.Aeson.Schema.Internal.IsSchemaType inner, GHC.Show.Show (Data.Aeson.Schema.Internal.SchemaResult inner), Data.Typeable.Internal.Typeable (Data.Aeson.Schema.Internal.SchemaResult inner), Data.Aeson.Schema.Internal.IsSchemaObject ('Data.Aeson.Schema.Internal.SchemaObject rest), Data.Typeable.Internal.Typeable rest) => Data.Aeson.Schema.Internal.IsSchemaType ('Data.Aeson.Schema.Internal.SchemaObject ('(schemaKey, inner) : rest))
+ Data.Aeson.Schema.Internal: instance GHC.TypeLits.KnownSymbol key => Data.Aeson.Schema.Internal.KnownSchemaKey ('Data.Aeson.Schema.Internal.NormalKey key)
+ Data.Aeson.Schema.Internal: instance GHC.TypeLits.KnownSymbol key => Data.Aeson.Schema.Internal.KnownSchemaKey ('Data.Aeson.Schema.Internal.PhantomKey key)
+ Data.Aeson.Schema.Internal: type Lookup a = Map Snd <=< Find (TyEq a <=< Fst)
+ Data.Aeson.Schema.Key: NormalKey :: String -> SchemaKey
+ Data.Aeson.Schema.Key: PhantomKey :: String -> SchemaKey
+ Data.Aeson.Schema.Key: data SchemaKey
+ Data.Aeson.Schema.Key: fromSchemaKey :: SchemaKey -> String
+ Data.Aeson.Schema.Key: instance GHC.Show.Show Data.Aeson.Schema.Key.SchemaKey
+ Data.Aeson.Schema.Show: NormalKey :: String -> SchemaKey
+ Data.Aeson.Schema.Show: PhantomKey :: String -> SchemaKey
+ Data.Aeson.Schema.Show: SchemaTry :: SchemaType -> SchemaType
+ Data.Aeson.Schema.Show: data SchemaKey
- Data.Aeson.Schema.Internal: SchemaObject :: [(Symbol, SchemaType)] -> SchemaType
+ Data.Aeson.Schema.Internal: SchemaObject :: [(SchemaKey, SchemaType)] -> SchemaType
- Data.Aeson.Schema.Show: SchemaObject :: [(String, SchemaType)] -> SchemaType
+ Data.Aeson.Schema.Show: SchemaObject :: [(SchemaKey, SchemaType)] -> SchemaType

Files

CHANGELOG.md view
@@ -1,10 +1,18 @@ ## Upcoming +## 1.2.0++New features:++* Add support for phantom keys+* Add support for `Try` schemas+ ## 1.1.0  New features:-    * Added support for unions-    * Added `ToJSON` instance for enums generated with `mkEnum`++* Added support for unions+* Added `ToJSON` instance for enums generated with `mkEnum`  ## 1.0.3 
aeson-schemas.cabal view
@@ -4,10 +4,10 @@ -- -- see: https://github.com/sol/hpack ----- hash: 79cd47bc31fb343c075854b35187f50f082ad4bc23f5bc14a9e514cc8633c9f2+-- hash: f264efe1e9e5d79645f971635649d0dea5c3dedf8e8aee3513f201dd9386a438  name:           aeson-schemas-version:        1.1.0+version:        1.2.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@@ -63,6 +63,7 @@     test/goldens/maybeObjNull.golden     test/goldens/maybeObjNull_text.golden     test/goldens/nonexistent.golden+    test/goldens/phantom.golden     test/goldens/README_Quickstart.golden     test/goldens/scalar.golden     test/goldens/schema_def_bool.golden@@ -70,6 +71,7 @@     test/goldens/schema_def_double.golden     test/goldens/schema_def_duplicate.golden     test/goldens/schema_def_duplicate_extend.golden+    test/goldens/schema_def_duplicate_phantom.golden     test/goldens/schema_def_extend.golden     test/goldens/schema_def_import_user.golden     test/goldens/schema_def_int.golden@@ -78,6 +80,7 @@     test/goldens/schema_def_list_obj.golden     test/goldens/schema_def_maybe.golden     test/goldens/schema_def_maybe_obj.golden+    test/goldens/schema_def_nonobject_phantom.golden     test/goldens/schema_def_not_object.golden     test/goldens/schema_def_obj.golden     test/goldens/schema_def_shadow.golden@@ -86,6 +89,12 @@     test/goldens/schema_def_union_grouped.golden     test/goldens/schema_def_unknown_type.golden     test/goldens/text.golden+    test/goldens/tryObj.golden+    test/goldens/tryObj_a.golden+    test/goldens/tryObj_bang.golden+    test/goldens/tryObj_bang_a.golden+    test/goldens/tryObjNull.golden+    test/goldens/tryObjNull_a.golden     test/goldens/union.golden     test/goldens/union_0.golden     test/goldens/union_0_a.golden@@ -109,6 +118,7 @@   exposed-modules:       Data.Aeson.Schema       Data.Aeson.Schema.Internal+      Data.Aeson.Schema.Key       Data.Aeson.Schema.Show       Data.Aeson.Schema.TH       Data.Aeson.Schema.Utils.Sum@@ -125,12 +135,12 @@       src   ghc-options: -Wall   build-depends:-      aeson >=1.1.2.0 && <1.5+      aeson >=1.1.2.0 && <1.6     , base >=4.9 && <5     , bytestring >=0.10.8.1 && <0.11     , first-class-families >=0.3.0.0 && <0.9     , megaparsec >=6.0.0 && <9-    , template-haskell >=2.12.0.0 && <2.16+    , template-haskell >=2.12.0.0 && <2.17     , text >=1.2.2.2 && <1.3     , unordered-containers >=0.2.8.0 && <0.3   if impl(ghc >= 8.0)@@ -155,18 +165,18 @@   ghc-options: -Wall   build-depends:       QuickCheck >=2.7 && <3-    , aeson >=1.1.2.0 && <1.5+    , aeson >=1.1.2.0 && <1.6     , aeson-schemas     , base >=4.9 && <5     , bytestring >=0.10.8.1 && <0.11     , 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 >=0.11.3 && <1.4     , 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+    , template-haskell >=2.12.0.0 && <2.17     , text >=1.2.2.2 && <1.3     , th-test-utils >=1.0.0 && <1.1     , unordered-containers >=0.2.8.0 && <0.3
src/Data/Aeson/Schema/Internal.hs view
@@ -44,7 +44,8 @@ import Data.Text (Text) import qualified Data.Text as Text import Data.Typeable (Typeable, splitTyConApp, tyConName, typeRep, typeRepTyCon)-import Fcf (type (=<<), Eval, FromMaybe, Lookup)+import Fcf (type (<=<), type (=<<))+import qualified Fcf import GHC.Exts (toList) import GHC.TypeLits     (ErrorMessage(..), KnownSymbol, Symbol, TypeError, symbolVal)@@ -83,8 +84,9 @@   | SchemaText   | SchemaCustom Type   | SchemaMaybe SchemaType+  | SchemaTry SchemaType   | SchemaList SchemaType-  | SchemaObject [(Symbol, SchemaType)]+  | SchemaObject [(SchemaKey, SchemaType)]   | SchemaUnion [SchemaType] -- ^ @since v1.1.0  -- | Convert 'SchemaType' into 'SchemaShow.SchemaType'.@@ -98,6 +100,7 @@       ("'SchemaText", _) -> SchemaShow.SchemaText       ("'SchemaCustom", [inner]) -> SchemaShow.SchemaCustom $ typeRepName inner       ("'SchemaMaybe", [inner]) -> SchemaShow.SchemaMaybe $ cast inner+      ("'SchemaTry", [inner]) -> SchemaShow.SchemaTry $ cast inner       ("'SchemaList", [inner]) -> SchemaShow.SchemaList $ cast inner       ("'SchemaObject", [pairs]) -> SchemaShow.SchemaObject $ map getSchemaObjectPair $ typeRepToList pairs       ("'SchemaUnion", [schemas]) -> SchemaShow.SchemaUnion $ map cast $ typeRepToList schemas@@ -105,8 +108,12 @@      getSchemaObjectPair tyRep =       let (key, val) = typeRepToPair tyRep-          key' = tail . init . typeRepName $ key -- strip leading + trailing quote-      in (key', cast val)+          fromTypeRep = tail . init . typeRepName -- strip leading + trailing quote+          schemaKey = case splitTypeRep key of+            ("'NormalKey", [key']) -> SchemaShow.NormalKey $ fromTypeRep key'+            ("'PhantomKey", [key']) -> SchemaShow.PhantomKey $ fromTypeRep key'+            _ -> error $ "Unknown schema key: " ++ show key+      in (schemaKey, cast val)      typeRepToPair tyRep = case splitTypeRep tyRep of       ("'(,)", [a, b]) -> (a, b)@@ -124,6 +131,30 @@ showSchema :: forall (a :: SchemaType). Typeable a => String showSchema = SchemaShow.showSchemaType $ toSchemaTypeShow @a +-- | The type-level analogue of 'Data.Aeson.Schema.Key.SchemaKey'.+data SchemaKey+  = NormalKey Symbol+  | PhantomKey Symbol++type family FromSchemaKey (schemaKey :: SchemaKey) where+  FromSchemaKey ('NormalKey key) = key+  FromSchemaKey ('PhantomKey key) = key++fromSchemaKey :: forall schemaKey. KnownSymbol (FromSchemaKey schemaKey) => Text+fromSchemaKey = Text.pack $ symbolVal $ Proxy @(FromSchemaKey schemaKey)++class+  ( Typeable schemaKey+  , KnownSymbol (FromSchemaKey schemaKey)+  ) => KnownSchemaKey (schemaKey :: SchemaKey) where+  getContext :: HashMap Text Value -> Value++instance KnownSymbol key => KnownSchemaKey ('NormalKey key) where+  getContext = fromMaybe Null . HashMap.lookup (fromSchemaKey @('NormalKey key))++instance KnownSymbol key => KnownSchemaKey ('PhantomKey key) where+  getContext = Object+ {- Conversions from schema types into Haskell types -}  -- | A type family mapping SchemaType to the corresponding Haskell type.@@ -134,6 +165,7 @@   SchemaResult 'SchemaText = Text   SchemaResult ('SchemaCustom inner) = inner   SchemaResult ('SchemaMaybe inner) = Maybe (SchemaResult inner)+  SchemaResult ('SchemaTry inner) = Maybe (SchemaResult inner)   SchemaResult ('SchemaList inner) = [SchemaResult inner]   SchemaResult ('SchemaObject inner) = Object ('SchemaObject inner)   SchemaResult ('SchemaUnion schemas) = SumType (SchemaResultList schemas)@@ -167,6 +199,11 @@     Null -> return Nothing     value -> (Just <$> parseValue @inner path value) +instance (IsSchemaType inner, Show (SchemaResult inner)) => IsSchemaType ('SchemaTry inner) where+  parseValue path = wrapTry . parseValue @inner path+    where+      wrapTry parser = (Just <$> parser) <|> pure Nothing+ instance (IsSchemaType inner, Show (SchemaResult inner)) => IsSchemaType ('SchemaList inner) where   parseValue path value = case value of     Array a -> traverse (parseValue @inner path) (toList a)@@ -180,34 +217,34 @@   showValue _ = "{}"  instance-  ( KnownSymbol key+  ( KnownSchemaKey schemaKey   , IsSchemaType inner   , Show (SchemaResult inner)   , Typeable (SchemaResult inner)   , IsSchemaObject ('SchemaObject rest)   , Typeable rest-  ) => IsSchemaType ('SchemaObject ('(key, inner) ': rest)) where+  ) => IsSchemaType ('SchemaObject ('(schemaKey, inner) ': rest)) where   parseValue path value = case value of     Object o -> do-      let key = Text.pack $ symbolVal $ Proxy @key-          innerVal = fromMaybe Null $ HashMap.lookup key o+      let key = fromSchemaKey @schemaKey+          innerVal = getContext @schemaKey o        inner <- parseValue @inner (key:path) innerVal       UnsafeObject rest <- parseValue @('SchemaObject rest) path value        return $ UnsafeObject $ HashMap.insert key (toDyn inner) rest-    _ -> parseFail @('SchemaObject ('(key, inner) ': rest)) path value+    _ -> parseFail @('SchemaObject ('(schemaKey, inner) ': rest)) path value    showValue (UnsafeObject hm) = case showValue @('SchemaObject rest) (UnsafeObject hm) of     "{}" -> "{" ++ pair ++ "}"     '{':s -> "{" ++ pair ++ ", " ++ s     s -> error $ "Unknown result when showing Object: " ++ s     where-      key = symbolVal $ Proxy @key+      key = fromSchemaKey @schemaKey       value =-        let dynValue = hm ! Text.pack key+        let dynValue = hm ! key         in maybe (show dynValue) show $ fromDynamic @(SchemaResult inner) dynValue-      pair = "\"" ++ key ++ "\": " ++ value+      pair = show key ++ ": " ++ value  instance   ( All IsSchemaType schemas@@ -229,17 +266,24 @@  {- Lookups within SchemaObject -} +data UnSchemaKey :: SchemaKey -> Fcf.Exp Symbol+type instance Fcf.Eval (UnSchemaKey ('NormalKey key)) = Fcf.Eval (Fcf.Pure key)+type instance Fcf.Eval (UnSchemaKey ('PhantomKey key)) = Fcf.Eval (Fcf.Pure key)++-- first-class-families-0.3.0.1 doesn't support partially applying Lookup+type Lookup a = Fcf.Map Fcf.Snd <=< Fcf.Find (Fcf.TyEq a <=< Fcf.Fst)+ -- | 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-    ( FromMaybe (TypeError+  LookupSchema key ('SchemaObject schema) = Fcf.Eval+    ( Fcf.FromMaybe (TypeError         (     'Text "Key '"         ':<>: 'Text key         ':<>: 'Text "' does not exist in the following schema:"         ':$$: 'ShowType schema         )       )-      =<< Lookup key schema+      =<< Lookup key =<< Fcf.Map (Fcf.Bimap UnSchemaKey Fcf.Pure) schema     )   LookupSchema key schema = TypeError     (     'Text "Attempted to lookup key '"
+ src/Data/Aeson/Schema/Key.hs view
@@ -0,0 +1,25 @@+{-|+Module      :  Data.Aeson.Schema.Key+Maintainer  :  Brandon Chinn <brandon@leapyear.io>+Stability   :  experimental+Portability :  portable++Defines a SchemaKey.+-}++module Data.Aeson.Schema.Key+  ( SchemaKey(..)+  , fromSchemaKey+  ) where++-- | A key in a JSON object schema.+data SchemaKey+  = NormalKey String+  | PhantomKey String+    -- ^ A key that doesn't actually exist in the object, but whose content should be parsed from+    -- the current object.+  deriving (Show)++fromSchemaKey :: SchemaKey -> String+fromSchemaKey (NormalKey key) = key+fromSchemaKey (PhantomKey key) = key
src/Data/Aeson/Schema/Show.hs view
@@ -11,10 +11,14 @@ module Data.Aeson.Schema.Show   ( SchemaType(..)   , showSchemaType+    -- * Re-exports+  , SchemaKey(..)   ) where  import Data.List (intercalate) +import Data.Aeson.Schema.Key (SchemaKey(..), fromSchemaKey)+ -- | 'Data.Aeson.Schema.Internal.SchemaType', but for printing. data SchemaType   = SchemaBool@@ -23,8 +27,9 @@   | SchemaText   | SchemaCustom String   | SchemaMaybe SchemaType+  | SchemaTry SchemaType   | SchemaList SchemaType-  | SchemaObject [(String, SchemaType)]+  | SchemaObject [(SchemaKey, SchemaType)]   | SchemaUnion [SchemaType]   deriving (Show) @@ -37,6 +42,7 @@   SchemaText -> "SchemaText"   SchemaCustom s -> "SchemaCustom " ++ s   SchemaMaybe inner -> "SchemaMaybe " ++ showSchemaType' inner+  SchemaTry inner -> "SchemaTry " ++ showSchemaType' inner   SchemaList inner -> "SchemaList " ++ showSchemaType' inner   SchemaObject _ -> "SchemaObject " ++ showSchemaType' schema   SchemaUnion _ -> "SchemaUnion " ++ showSchemaType' schema@@ -48,9 +54,10 @@       SchemaText -> "Text"       SchemaCustom s -> s       SchemaMaybe inner -> "Maybe " ++ showSchemaType' inner+      SchemaTry inner -> "Try " ++ showSchemaType' inner       SchemaList inner -> "List " ++ showSchemaType' inner       SchemaObject pairs -> "{" ++ mapJoin showPair ", " pairs ++ "}"       SchemaUnion schemas -> "( " ++ mapJoin showSchemaType' " | " schemas ++ " )"-    showPair (key, inner) = "\"" ++ key ++ "\": " ++ showSchemaType' inner+    showPair (key, inner) = "\"" ++ fromSchemaKey key ++ "\": " ++ showSchemaType' inner      mapJoin f delim = intercalate delim . map f
src/Data/Aeson/Schema/TH/Parse.hs view
@@ -66,6 +66,7 @@       [ between (lexeme "{") (lexeme "}") $ SchemaDefObj <$> parseSchemaDefObjItems       , between (lexeme "(") (lexeme ")") parseSchemaDefWithUnions       , lexeme "Maybe" *> (SchemaDefMaybe <$> parseSchemaDefWithoutUnions)+      , lexeme "Try" *> (SchemaDefTry <$> parseSchemaDefWithoutUnions)       , lexeme "List" *> (SchemaDefList <$> parseSchemaDefWithoutUnions)       , SchemaDefType <$> identifier upperChar       , SchemaDefInclude <$> parseSchemaReference@@ -77,7 +78,10 @@       , SchemaDefObjExtend <$> parseSchemaReference       ] <* space -- allow any trailing spaces     parseSchemaDefPair = do-      key <- jsonKey+      key <- choice+        [ SchemaDefObjKeyNormal <$> jsonKey+        , SchemaDefObjKeyPhantom <$> between (lexeme "[") (lexeme "]") jsonKey+        ]       lexeme ":"       value <- parseSchemaDefWithUnions       return (key, value)@@ -115,6 +119,7 @@ data SchemaDef   = SchemaDefType String   | SchemaDefMaybe SchemaDef+  | SchemaDefTry SchemaDef   | SchemaDefList SchemaDef   | SchemaDefInclude String   | SchemaDefObj [SchemaDefObjItem]@@ -122,8 +127,13 @@   deriving (Show)  data SchemaDefObjItem-  = SchemaDefObjPair (String, SchemaDef)+  = SchemaDefObjPair (SchemaDefObjKey, SchemaDef)   | SchemaDefObjExtend String+  deriving (Show)++data SchemaDefObjKey+  = SchemaDefObjKeyNormal String+  | SchemaDefObjKeyPhantom String   deriving (Show)  schemaDef :: Parser SchemaDef
src/Data/Aeson/Schema/TH/Schema.hs view
@@ -16,7 +16,7 @@  module Data.Aeson.Schema.TH.Schema (schema) where -import Control.Monad ((<=<), (>=>))+import Control.Monad (unless, (<=<), (>=>)) import Data.Bifunctor (second) import qualified Data.HashMap.Strict as HashMap import Data.Maybe (mapMaybe)@@ -24,9 +24,11 @@ import Language.Haskell.TH.Quote (QuasiQuoter(..))  import Data.Aeson.Schema.Internal (SchemaType(..))+import Data.Aeson.Schema.Key (SchemaKey(..), fromSchemaKey)+import qualified Data.Aeson.Schema.Show as SchemaShow import Data.Aeson.Schema.TH.Parse import Data.Aeson.Schema.TH.Utils-    (schemaPairsToTypeQ, typeQListToTypeQ, typeToSchemaPairs)+    (parseSchemaType, schemaPairsToTypeQ, typeQListToTypeQ, typeToSchemaPairs)  -- | Defines a QuasiQuoter for writing schemas. --@@ -62,12 +64,20 @@ -- * @Maybe \<schema\>@ and @List \<schema\>@ correspond to @Maybe@ and @[]@, containing values --   specified by the provided schema (no parentheses needed). --+-- * @Try \<schema\>@ correspond to @Maybe@, where the value will be @Just@ if the given schema+--   successfully parses the value, or @Nothing@ otherwise.+--+-- * Any other uppercase identifier corresponds to the respective type in scope -- requires a+--   FromJSON instance.+--+-- Advanced syntax:+-- -- * @\<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.+-- * @{ [key]: \<schema\> }@ uses the current object to resolve the keys in the given schema. Only+--   object schemas are allowed here. -- -- * @{ key: #Other, ... }@ maps the given key to the @Other@ schema. --@@ -95,6 +105,7 @@   SchemaDefType "Text"   -> [t| 'SchemaText |]   SchemaDefType other    -> [t| 'SchemaCustom $(getType other) |]   SchemaDefMaybe inner   -> [t| 'SchemaMaybe $(generateSchema inner) |]+  SchemaDefTry inner     -> [t| 'SchemaTry $(generateSchema inner) |]   SchemaDefList inner    -> [t| 'SchemaList $(generateSchema inner) |]   SchemaDefInclude other -> getType other   SchemaDefObj items     -> generateSchemaObject items@@ -113,9 +124,20 @@  -- | Parse SchemaDefObjItem into a list of tuples, each containing a key to add to the schema, -- the value for the key, and the source of the key.-toParts :: SchemaDefObjItem -> Q [(String, TypeQ, KeySource)]+toParts :: SchemaDefObjItem -> Q [(SchemaKey, TypeQ, KeySource)] toParts = \case-  SchemaDefObjPair (k, v) -> pure . tagAs Provided $ [(k, generateSchema v)]+  SchemaDefObjPair (schemaDefKey, schemaDefType) -> do+    let schemaKey = schemaDefToSchemaKey schemaDefKey+    schemaType <- generateSchema schemaDefType++    case schemaKey of+      PhantomKey _ -> do+        let schemaTypeShow = parseSchemaType schemaType+        unless (isValidPhantomSchema schemaTypeShow) $+          fail $ "Invalid schema for '" ++ fromSchemaKey schemaKey ++ "': " ++ SchemaShow.showSchemaType schemaTypeShow+      _ -> return ()++    pure . tagAs Provided $ [(schemaKey, pure schemaType)]   SchemaDefObjExtend other -> do     name <- getName other     reify name >>= \case@@ -124,18 +146,26 @@       _ -> fail $ "'" ++ show name ++ "' is not a SchemaObject"   where     tagAs source = map $ \(k,v) -> (k,v,source)+    schemaDefToSchemaKey = \case+      SchemaDefObjKeyNormal key -> NormalKey key+      SchemaDefObjKeyPhantom key -> PhantomKey key+    isValidPhantomSchema = \case+      SchemaShow.SchemaTry _ -> True+      SchemaShow.SchemaObject _ -> True+      SchemaShow.SchemaUnion schemas -> all isValidPhantomSchema schemas+      _ -> False  -- | Resolve the parts returned by 'toParts' as such: -- -- 1. Any explicitly provided keys shadow/overwrite imported keys -- 2. Fail if duplicate keys are both explicitly provided -- 3. Fail if duplicate keys are both imported-resolveParts :: [(String, TypeQ, KeySource)] -> Q [(String, TypeQ)]+resolveParts :: [(SchemaKey, TypeQ, KeySource)] -> Q [(SchemaKey, TypeQ)] resolveParts parts = do   resolved <- resolveParts' $ HashMap.fromListWith (++) $ map nameAndSource parts   return $ mapMaybe (alignWithResolved resolved) parts   where-    nameAndSource (name, _, source) = (name, [source])+    nameAndSource (name, _, source) = (fromSchemaKey name, [source])     resolveParts' = HashMap.traverseWithKey $ \name sources -> do       -- invariant: length sources > 0       let numOf source = length $ filter (== source) sources@@ -145,8 +175,8 @@         (x, _) | x > 1 -> fail $ "Key '" ++ name ++ "' specified multiple times"         (_, x) | x > 1 -> fail $ "Key '" ++ name ++ "' declared in multiple imported schemas"         _ -> fail "Broken invariant in resolveParts"-    alignWithResolved resolved (name, ty, source) =-      let resolvedSource = resolved HashMap.! name+    alignWithResolved resolved (key, ty, source) =+      let resolvedSource = resolved HashMap.! fromSchemaKey key       in if resolvedSource == source-        then Just (name, ty)+        then Just (key, ty)         else Nothing
src/Data/Aeson/Schema/TH/Utils.hs view
@@ -6,6 +6,7 @@ -} {-# LANGUAGE DataKinds #-} {-# LANGUAGE DeriveLift #-}+{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TypeOperators #-}@@ -13,55 +14,56 @@ module Data.Aeson.Schema.TH.Utils where  import Control.Monad ((>=>))-import Data.Bifunctor (bimap, second)+import Data.Bifunctor (bimap, first, second) import Data.List (intercalate) import Data.Text (Text)+import GHC.Stack (HasCallStack) import Language.Haskell.TH import Language.Haskell.TH.Syntax (Lift)  import Data.Aeson.Schema.Internal (Object, SchemaResult, SchemaType(..))+import qualified Data.Aeson.Schema.Internal as Internal+import Data.Aeson.Schema.Key (SchemaKey(..), fromSchemaKey) import qualified Data.Aeson.Schema.Show as SchemaShow  -- | Show the given schema as a type.-showSchemaType :: Type -> String-showSchemaType = SchemaShow.showSchemaType . fromSchemaType-  where-    fromSchemaType = \case-      PromotedT name-        | name == 'SchemaBool -> SchemaShow.SchemaBool-        | name == 'SchemaInt -> SchemaShow.SchemaInt-        | name == 'SchemaDouble -> SchemaShow.SchemaDouble-        | name == 'SchemaText -> SchemaShow.SchemaText-      AppT (PromotedT name) (ConT inner)-        | name == 'SchemaCustom -> SchemaShow.SchemaCustom $ nameBase inner-      AppT (PromotedT name) inner-        | 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+showSchemaType :: HasCallStack => Type -> String+showSchemaType = SchemaShow.showSchemaType . parseSchemaType -    fromPairs pairs = map (second fromSchemaType) $ typeToSchemaPairs pairs+parseSchemaType :: HasCallStack => Type -> SchemaShow.SchemaType+parseSchemaType = \case+  PromotedT name+    | name == 'SchemaBool -> SchemaShow.SchemaBool+    | name == 'SchemaInt -> SchemaShow.SchemaInt+    | name == 'SchemaDouble -> SchemaShow.SchemaDouble+    | name == 'SchemaText -> SchemaShow.SchemaText+  AppT (PromotedT name) (ConT inner)+    | name == 'SchemaCustom -> SchemaShow.SchemaCustom $ nameBase inner+  AppT (PromotedT name) inner+    | name == 'SchemaMaybe -> SchemaShow.SchemaMaybe $ parseSchemaType inner+    | name == 'SchemaTry -> SchemaShow.SchemaTry $ parseSchemaType inner+    | name == 'SchemaList -> SchemaShow.SchemaList $ parseSchemaType inner+    | name == 'SchemaObject -> SchemaShow.SchemaObject $ fromPairs inner+    | name == 'SchemaUnion -> SchemaShow.SchemaUnion $ map parseSchemaType $ typeToList inner+  ty -> error $ "Unknown type: " ++ show ty+  where+    fromPairs pairs = map (second parseSchemaType) $ typeToSchemaPairs pairs -typeToList :: Type -> [Type]+typeToList :: HasCallStack => Type -> [Type] typeToList = \case   PromotedNilT -> []   AppT (AppT PromotedConsT x) xs -> x : typeToList xs   SigT ty _ -> typeToList ty   ty -> error $ "Not a type-level list: " ++ show ty -typeToPair :: Type -> (Type, Type)+typeToPair :: HasCallStack => 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 -typeToSchemaPairs :: Type -> [(String, Type)]-typeToSchemaPairs = map (bimap toTypeStr stripSigs . typeToPair) . typeToList-  where-    toTypeStr = \case-      LitT (StrTyLit k) -> k-      x -> error $ "Not a type-level string: " ++ show x+typeToSchemaPairs :: HasCallStack => Type -> [(SchemaKey, Type)]+typeToSchemaPairs = map (bimap parseSchemaKey stripSigs . typeToPair) . typeToList  typeQListToTypeQ :: [TypeQ] -> TypeQ typeQListToTypeQ = foldr consT promotedNilT@@ -69,11 +71,23 @@     -- nb. https://stackoverflow.com/a/34457936     consT x xs = appT (appT promotedConsT x) xs -schemaPairsToTypeQ :: [(String, TypeQ)] -> TypeQ+schemaPairsToTypeQ :: [(SchemaKey, TypeQ)] -> TypeQ schemaPairsToTypeQ = typeQListToTypeQ . map pairT   where-    pairT (k, v) = [t| '( $(litT $ strTyLit k), $v) |]+    pairT (k, v) =+      let schemaKey = case k of+            NormalKey key -> [t| 'Internal.NormalKey $(litT $ strTyLit key) |]+            PhantomKey key -> [t| 'Internal.PhantomKey $(litT $ strTyLit key) |]+      in [t| '($schemaKey, $v) |] +parseSchemaKey :: HasCallStack => Type -> SchemaKey+parseSchemaKey = \case+  AppT (PromotedT ty) (LitT (StrTyLit key))+    | ty == 'Internal.NormalKey -> NormalKey key+    | ty == 'Internal.PhantomKey -> PhantomKey key+  SigT ty _ -> parseSchemaKey ty+  ty -> error $ "Could not parse a schema key: " ++ show ty+ -- | Strip all kind signatures from the given type. stripSigs :: Type -> Type stripSigs = \case@@ -101,6 +115,7 @@       AppT (PromotedT ty) inner         | ty == 'SchemaCustom -> [t| SchemaResult $(pure schema) |]         | ty == 'SchemaMaybe -> [t| Maybe $(fromSchemaType inner) |]+        | ty == 'SchemaTry -> [t| Maybe $(fromSchemaType inner) |]         | ty == 'SchemaList -> [t| [$(fromSchemaType inner)] |]         | ty == 'SchemaObject -> [t| Object $(pure schema) |]         | ty == 'SchemaUnion -> [t| SchemaResult $(pure schema) |]@@ -130,8 +145,10 @@         foldl appT (tupleT $ length elems) $ map (`unwrapType'` schema) elems       GetterTuple _ -> fail $ "Cannot get keys in schema: " ++ showSchemaType schema       GetterBang | ty == 'SchemaMaybe -> unwrapType' ops inner+      GetterBang | ty == 'SchemaTry -> unwrapType' ops inner       GetterBang -> fail $ "Cannot use `!` operator on schema: " ++ showSchemaType schema       GetterMapMaybe | ty == 'SchemaMaybe -> withFunctor [t| Maybe |] $ unwrapType' ops inner+      GetterMapMaybe | ty == 'SchemaTry -> withFunctor [t| Maybe |] $ unwrapType' ops inner       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@@ -146,13 +163,8 @@   schema -> fail $ unlines ["Cannot get type:", show schema, show op]   where     unwrapType' = unwrapType keepFunctor-    getObjectSchema = \case-      AppT (AppT PromotedConsT t1) t2 ->-        case t1 of-          AppT (AppT (PromotedTupleT 2) (LitT (StrTyLit key))) ty -> (key, ty) : getObjectSchema t2-          _ -> error $ "Could not parse a (key, schema) tuple: " ++ show t1-      PromotedNilT -> []-      t -> error $ "Could not get object schema: " ++ show t+    getObjectSchema = map (first getSchemaKey . typeToPair) . typeToList+    getSchemaKey = fromSchemaKey . parseSchemaKey     withFunctor f = if keepFunctor then appT f else id  {- GetterOps -}
src/Data/Aeson/Schema/Utils/Sum.hs view
@@ -97,6 +97,21 @@     Here _ -> Nothing     There xs -> fromSumType' (Proxy @(n - 1)) xs +-- | Extract a value from a 'SumType'+--+-- Example:+--+-- @+-- type Animal = SumType '[Owl, Cat, Toad]+-- let someAnimal = ... :: Animal+--+-- fromSumType (Proxy :: Proxy 0) someAnimal :: Maybe Owl+-- fromSumType (Proxy :: Proxy 1) someAnimal :: Maybe Cat+-- fromSumType (Proxy :: Proxy 2) someAnimal :: Maybe Toad+--+-- -- Compile-time error+-- -- fromSumType (Proxy :: Proxy 3) someAnimal+-- @ fromSumType   :: ( IsInRange n types      , 'Just result ~ GetIndex n types
test/AllTypes.hs view
@@ -45,6 +45,12 @@     maybeObjectNull: Maybe {       text: Text,     },+    tryObject: Try {+      a: Int,+    },+    tryObjectNull: Try {+      a: Int,+    },     maybeList: Maybe List {       text: Text,     },@@ -63,6 +69,9 @@     union: List (         { a: Int } | List Bool | Text     ),+    [phantom]: {+      keyForPhantom: Int,+    },   } |] 
test/Main.hs view
@@ -72,6 +72,12 @@   , goldens "maybeObj_bang_text"       [get| allTypes.maybeObject!.text     |]   , goldens "maybeObjNull"             [get| allTypes.maybeObjectNull       |]   , goldens "maybeObjNull_text"        [get| allTypes.maybeObjectNull?.text |]+  , goldens "tryObj"                   [get| allTypes.tryObject             |]+  , goldens "tryObjNull"               [get| allTypes.tryObjectNull         |]+  , goldens "tryObj_bang"              [get| allTypes.tryObject!            |]+  , goldens "tryObj_a"                 [get| allTypes.tryObject?.a          |]+  , goldens "tryObj_bang_a"            [get| allTypes.tryObject!.a          |]+  , goldens "tryObjNull_a"             [get| allTypes.tryObjectNull?.a      |]   , goldens "maybeList"                [get| allTypes.maybeList             |]   , goldens "maybeList_bang"           [get| allTypes.maybeList!            |]   , goldens "maybeList_bang_list"      [get| allTypes.maybeList![]          |]@@ -92,6 +98,7 @@   , goldens "union_0_a"                [get| allTypes.union[]@0?.a          |]   , goldens "union_1"                  [get| allTypes.union[]@1             |]   , goldens "union_2"                  [get| allTypes.union[]@2             |]+  , goldens "phantom"                  [get| allTypes.phantom.keyForPhantom |]   -- bad 'get' expressions   , goldens' "maybeListNull_bang" $(getError [get| (AllTypes.result).maybeListNull! |]) #if MIN_VERSION_megaparsec(7,0,0)@@ -168,10 +175,12 @@   , 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_phantom" $(tryQErr' $ showSchema [r| { a: Int, [a]: { b: Bool } } |])   , goldens' "schema_def_duplicate_extend" $(tryQErr' $ showSchema [r| { #MySchema, #MySchema2 } |])   , goldens' "schema_def_not_object" $(tryQErr' $ showSchema [r| List { a: Int } |])   , goldens' "schema_def_unknown_type" $(tryQErr' $ showSchema [r| HelloWorld |])   , goldens' "schema_def_invalid_extend" $(tryQErr' $ showSchema [r| { #Int } |])+  , goldens' "schema_def_nonobject_phantom" $(tryQErr' $ showSchema [r| { [a]: Int } |])   ]  testMkGetter :: TestTree
test/all_types.json view
@@ -19,6 +19,10 @@         }     ],     "maybeListNull": null,+    "tryObject": {+        "a": 1+    },+    "tryObjectNull": true,     "list": [         {             "type": "bool",@@ -38,5 +42,6 @@         { "a": 1 },         [true, false],         "World!"-    ]+    ],+    "keyForPhantom": 1 }
+ test/goldens/phantom.golden view
@@ -0,0 +1,1 @@+1
+ test/goldens/schema_def_duplicate_phantom.golden view
@@ -0,0 +1,1 @@+Key 'a' specified multiple times
+ test/goldens/schema_def_nonobject_phantom.golden view
@@ -0,0 +1,1 @@+Invalid schema for 'a': SchemaInt
+ test/goldens/tryObj.golden view
@@ -0,0 +1,1 @@+Just {"a": 1}
+ test/goldens/tryObjNull.golden view
@@ -0,0 +1,1 @@+Nothing
+ test/goldens/tryObjNull_a.golden view
@@ -0,0 +1,1 @@+Nothing
+ test/goldens/tryObj_a.golden view
@@ -0,0 +1,1 @@+Just 1
+ test/goldens/tryObj_bang.golden view
@@ -0,0 +1,1 @@+{"a": 1}
+ test/goldens/tryObj_bang_a.golden view
@@ -0,0 +1,1 @@+1