packages feed

registry-aeson 0.2.0.0 → 0.2.1.0

raw patch · 11 files changed

+334/−238 lines, 11 filesPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

API changes (from Hackage documentation)

- Data.Registry.Aeson.Decoder: applyDecoder :: Name -> Name -> [Int] -> ExpQ
- Data.Registry.Aeson.Decoder: makeConstructorsDecoder :: Name -> [Con] -> ExpQ
- Data.Registry.Aeson.Decoder: makeDecoder :: Name -> ExpQ
- Data.Registry.Aeson.Decoder: makeErrorClause :: Name -> MatchQ
- Data.Registry.Aeson.Decoder: makeMatchClause :: Name -> [Type] -> Con -> MatchQ
- Data.Registry.Aeson.Encoder: makeConstructorsEncoder :: [Con] -> ExpQ
- Data.Registry.Aeson.Encoder: makeEncoder :: Name -> ExpQ
- Data.Registry.Aeson.Encoder: makeMatchClause :: [Name] -> [Type] -> Con -> MatchQ
- Data.Registry.Aeson.TH: dropQualified :: Name -> Name
- Data.Registry.Aeson.TH: fieldsOf :: Con -> Q [Name]
- Data.Registry.Aeson.TH: getSimpleTypeName :: Type -> Name
- Data.Registry.Aeson.TH: indexConstructorTypes :: [Type] -> [Type] -> Q [(Type, Int)]
- Data.Registry.Aeson.TH: nameOf :: Con -> Q Name
- Data.Registry.Aeson.TH: typesOf :: Con -> Q [Type]
+ Data.Registry.Aeson.Decoder: ConstructorsDecoder :: (Options -> [ConstructorDef] -> Value -> Either Text [ToConstructor]) -> ConstructorsDecoder
+ Data.Registry.Aeson.Decoder: [decodeConstructors] :: ConstructorsDecoder -> Options -> [ConstructorDef] -> Value -> Either Text [ToConstructor]
+ Data.Registry.Aeson.Decoder: defaultConstructorsDecoder :: ConstructorsDecoder
+ Data.Registry.Aeson.Decoder: defaultDecoderOptions :: Registry _ _
+ Data.Registry.Aeson.Decoder: newtype ConstructorsDecoder
+ Data.Registry.Aeson.Encoder: ConstructorEncoder :: (Options -> FromConstructor -> (Value, Encoding)) -> ConstructorEncoder
+ Data.Registry.Aeson.Encoder: [encodeConstructor] :: ConstructorEncoder -> Options -> FromConstructor -> (Value, Encoding)
+ Data.Registry.Aeson.Encoder: defaultConstructorEncoder :: ConstructorEncoder
+ Data.Registry.Aeson.Encoder: defaultEncoderOptions :: Registry _ _
+ Data.Registry.Aeson.Encoder: newtype ConstructorEncoder
+ Data.Registry.Aeson.TH.Decoder: applyDecoder :: Name -> Name -> [Int] -> ExpQ
+ Data.Registry.Aeson.TH.Decoder: makeConstructorsDecoder :: Name -> [Con] -> ExpQ
+ Data.Registry.Aeson.TH.Decoder: makeDecoder :: Name -> ExpQ
+ Data.Registry.Aeson.TH.Decoder: makeErrorClause :: Name -> MatchQ
+ Data.Registry.Aeson.TH.Decoder: makeMatchClause :: Name -> [Type] -> Con -> MatchQ
+ Data.Registry.Aeson.TH.Encoder: makeConstructorsEncoder :: [Con] -> ExpQ
+ Data.Registry.Aeson.TH.Encoder: makeEncoder :: Name -> ExpQ
+ Data.Registry.Aeson.TH.Encoder: makeMatchClause :: [Name] -> [Type] -> Con -> MatchQ
+ Data.Registry.Aeson.TH.TH: dropQualified :: Name -> Name
+ Data.Registry.Aeson.TH.TH: fieldsOf :: Con -> Q [Name]
+ Data.Registry.Aeson.TH.TH: getSimpleTypeName :: Type -> Name
+ Data.Registry.Aeson.TH.TH: indexConstructorTypes :: [Type] -> [Type] -> Q [(Type, Int)]
+ Data.Registry.Aeson.TH.TH: nameOf :: Con -> Q Name
+ Data.Registry.Aeson.TH.TH: typesOf :: Con -> Q [Type]
- Data.Registry.Aeson.Decoder: decodeFromDefinitions :: Options -> [ConstructorDef] -> Value -> (ToConstructor -> Either Text a) -> Either Text a
+ Data.Registry.Aeson.Decoder: decodeFromDefinitions :: Options -> ConstructorsDecoder -> [ConstructorDef] -> Value -> (ToConstructor -> Either Text a) -> Either Text a

Files

README.md view
@@ -54,7 +54,7 @@   <: fun datetimeEncoder   <: jsonEncoder @Text   <: jsonEncoder @Int-  <: val defaultOptions+  <: defaultEncoderOptions  datetimeEncoder :: Encoder DateTime datetimeEncoder = fromValue $ \(DateTime dt) -> do@@ -117,7 +117,7 @@   <: fun dateTimeDecoder   <: jsonDecoder @Text   <: jsonDecoder @Int-  <: val defaultOptions+  <: defaultDecoderOptions  datetimeDecoder :: Decoder DateTime datetimeDecoder = Decoder $ \case@@ -139,6 +139,21 @@ let decoded = decode decoderPerson $ ObjectArray [Number 123, ObjectStr "me@here.com"] ``` +##### Overriding the generated encoders++There is a bit of flexibility in the way encoders are created with TemplateHaskell.++A custom `ConstructorsEncoder` can be added to the registry to tweak the generation:+```+newtype ConstructorEncoder = ConstructorEncoder+  { encodeConstructor :: Options -> FromConstructor -> (Value, Encoding)+  }+```+A `ConstructorEncoder` uses configuration options and type information extracted from+given data type (with TemplateHaskell) in order to produce a `Value` and an `Encoding`.++If necessary you can provide your own options and reuse the default function to produce different encoders.+ #### Generated decoders  The `makeDecoder` function makes the following functions:@@ -163,3 +178,18 @@ ```  __NOTE__ this function does not support recursive data types (and much less mutually recursive data types)++##### Overriding the generated decoders++There is a bit of flexibility in the way decoders are created with TemplateHaskell.++A custom `ConstructorsDecoder` can be added to the registry to tweak the generation:+```+newtype ConstructorsDecoder = ConstructorsDecoder+  { decodeConstructors :: Options -> [ConstructorDef] -> Value -> Either Text [ToConstructor]+  }+```+This function extracts values for a set of constructor definitions and returns `ToConstructor` values+containing a JSON `Value` to be decoded for each field of a given constructor (along with its name).++If necessary you can provide your own options and reuse the default function to produce different decoders.
registry-aeson.cabal view
@@ -5,7 +5,7 @@ -- see: https://github.com/sol/hpack  name:           registry-aeson-version:        0.2.0.0+version:        0.2.1.0 synopsis:       Aeson encoders / decoders description:    This library provides encoders / decoders which can be easily customized for the Aeson format. category:       Data@@ -24,7 +24,9 @@   exposed-modules:       Data.Registry.Aeson.Decoder       Data.Registry.Aeson.Encoder-      Data.Registry.Aeson.TH+      Data.Registry.Aeson.TH.Decoder+      Data.Registry.Aeson.TH.Encoder+      Data.Registry.Aeson.TH.TH   other-modules:       Paths_registry_aeson   hs-source-dirs:
src/Data/Registry/Aeson/Decoder.hs view
@@ -9,22 +9,23 @@   A Decoder is used to decode a Aeson Object into a specific data type   This module provides several functions to create decoders and assemble them into a registry of encoders. -}-module Data.Registry.Aeson.Decoder where+module Data.Registry.Aeson.Decoder+  ( module Data.Registry.Aeson.Decoder,+    module Data.Registry.Aeson.TH.Decoder,+  )+where -import Control.Monad.Fail import Data.Aeson import qualified Data.Aeson.Key as K import qualified Data.Aeson.KeyMap as KM import qualified Data.ByteString.Lazy as BL-import Data.List (nub, (\\))+import Data.List ((\\)) import Data.Registry-import Data.Registry.Aeson.TH+import Data.Registry.Aeson.TH.Decoder import Data.Registry.Internal.Types hiding (Value) import qualified Data.Text as T import qualified Data.Text.Encoding as T import qualified Data.Vector as Vector-import Language.Haskell.TH-import Language.Haskell.TH.Syntax import Protolude as P hiding (Type) import Prelude (String, show) @@ -140,92 +141,14 @@ showType :: forall a. (Typeable a) => String showType = P.show (typeRep (Proxy :: Proxy a)) --- * TEMPLATE HASKELL---- | Make a Decoder for a given data type---   Usage: $(makeDecoder ''MyDataType <: otherDecoders)-makeDecoder :: Name -> ExpQ-makeDecoder typeName = appE (varE $ mkName "fun") $ do-  info <- reify typeName-  case info of-    TyConI (NewtypeD _context _name _typeVars _kind constructor _deriving) ->-      makeConstructorsDecoder typeName [constructor]-    TyConI (DataD _context _name _typeVars _kind constructors _deriving) -> do-      case constructors of-        [] -> do-          qReport True "can not make an Decoder for an empty data type"-          fail "decoders creation failed"-        _ -> makeConstructorsDecoder typeName constructors-    other -> do-      qReport True ("can only create decoders for an ADT, got: " <> P.show other)-      fail "decoders creation failed"---- | Make a decoder for a given data type by extracting just enough metadata about the data type in order to be able---   to parse a Value------   For example for the data type:------   data T = T1 {f1::Int, f2::Int} | T2 Int Int------   we add this function to the registry:------   \opts d1 d2 d3 -> Decoder $ \v ->---     decodeFromDefinitions opts v $ \case---       ToConstructor "T1" [v1, v2]-> T1 <$> d1 v1 <*> d2 v2 ...---       ToConstructor "T2" [v1, v2]-> T2 <$> d1 v1 <*> d3 v2 ...---       other -> Left ("cannot decode " <> valueToText v)------   The \case function is the only one which needs to be generated in order to match the exact shape of the---   constructors to instantiate-makeConstructorsDecoder :: Name -> [Con] -> ExpQ-makeConstructorsDecoder typeName cs = do-  ts <- nub . join <$> for cs typesOf-  let decoderParameters = sigP (varP (mkName "os")) (conT $ mkName "Options") : ((\(t, n) -> sigP (varP (mkName $ "d" <> P.show n)) (appT (conT $ mkName "Decoder") (pure t))) <$> zip ts [0 ..])-  -- makeToConstructors os [Constructor "T1" ["f1", "f2"], Constructor "T2" []] v-  let paramP = varP (mkName "v")-  constructorDefs <- for cs $ \c -> do-    cName <- dropQualified <$> nameOf c-    fields <- fmap (litE . StringL . P.show . dropQualified) <$> fieldsOf c-    fieldTypes <- fmap (litE . StringL . P.show . getSimpleTypeName) <$> typesOf c-    varE (mkName "makeConstructorDef") `appE` (litE . StringL $ P.show cName) `appE` listE fields `appE` listE fieldTypes-  let matchClauses = makeMatchClause typeName ts <$> cs-  let matchFunction = lamCaseE (matchClauses <> [makeErrorClause typeName])-  let resolveFunction = varE (mkName "decodeFromDefinitions") `appE` varE (mkName "os") `appE` listE (pure <$> constructorDefs) `appE` varE (mkName "v") `appE` matchFunction-  lamE decoderParameters (appE (conE (mkName "Decoder")) (lamE [paramP] resolveFunction))---- | Decode the nth constructor of a data type---    ToConstructor "T1" [v1, v2]-> T1 <$> d1 v1 <*> d2 v2 ...-makeMatchClause :: Name -> [Type] -> Con -> MatchQ-makeMatchClause typeName allTypes c = do-  ts <- typesOf c-  constructorTypes <- fmap snd <$> indexConstructorTypes allTypes ts-  cName <- dropQualified <$> nameOf c-  let fieldsP = listP $ (\i -> varP $ mkName ("v" <> P.show i)) <$> constructorTypes-  match-    (conP (mkName "ToConstructor") [litP (StringL . P.show $ cName), fieldsP])-    (normalB (applyDecoder typeName cName constructorTypes))-    []+-- * DEFAULT VALUES --- | Return an error the json value cannot be decoded with a constructor name and some values-makeErrorClause :: Name -> MatchQ-makeErrorClause typeName = do-  let errorMessage =-        (varE (mkName "<>") `appE` litE (StringL ("cannot use this constructor to create an instance of type '" <> P.show typeName <> "': ")))-          `appE` (varE (mkName "show") `appE` varE (mkName "_1"))-  match (varP $ mkName "_1") (normalB (appE (conE $ mkName "Left") errorMessage)) []+defaultDecoderOptions :: Registry _ _+defaultDecoderOptions =+  fun defaultConstructorsDecoder+    <: val defaultOptions --- ConstructorName <$> decodeFieldValue d1 o1 <*> decodeFieldValue d2 o2 ...-applyDecoder :: Name -> Name -> [Int] -> ExpQ-applyDecoder _typeName cName [] = appE (varE $ mkName "pure") (conE cName)-applyDecoder typeName cName (n : ns) = do-  let cons = appE (varE $ mkName "pure") (conE cName)-  foldr (\i r -> appE (appE (varE (mkName "ap")) r) $ decodeAt i) (appE (appE (varE (mkName "ap")) cons) $ decodeAt n) (reverse ns)-  where-    decodeAt i =-      varE (mkName "decodeFieldValue") `appE` varE (mkName ("d" <> P.show i))-        `appE` (litE . StringL . P.show . dropQualified $ typeName)-        `appE` (litE . StringL . P.show . dropQualified $ cName)-        `appE` varE (mkName ("v" <> P.show i))+-- * TEMPLATE HASKELL  -- | Use a decoder to decode a field --   The constructor name, the type where the field is inserted and the field definition@@ -281,12 +204,22 @@  -- | Try to find the appropriate constructor definition encoded in the json value --   then try to decode all its fields with decoding function-decodeFromDefinitions :: Options -> [ConstructorDef] -> Value -> (ToConstructor -> Either Text a) -> Either Text a-decodeFromDefinitions options constructorDefs value build = do-  let toConstructors = fmap build <$> makeToConstructors options constructorDefs value+decodeFromDefinitions :: Options -> ConstructorsDecoder -> [ConstructorDef] -> Value -> (ToConstructor -> Either Text a) -> Either Text a+decodeFromDefinitions options constructorsDecoder constructorDefs value build = do+  let toConstructors = fmap build <$> decodeConstructors constructorsDecoder options constructorDefs value   case toConstructors of     Left e -> Left e     Right es -> foldEither es++-- | This function extracts values for a set of constructor definitions+--   The TemplateHaskell function makeDecoder can then use the constructor name+--   and constructor field value to create an actual constructor instance for a given data type+newtype ConstructorsDecoder = ConstructorsDecoder+  { decodeConstructors :: Options -> [ConstructorDef] -> Value -> Either Text [ToConstructor]+  }++defaultConstructorsDecoder :: ConstructorsDecoder+defaultConstructorsDecoder = ConstructorsDecoder makeToConstructors  -- | Try to extract possible constructor values based on: --     - the encoding options
src/Data/Registry/Aeson/Encoder.hs view
@@ -8,22 +8,22 @@   This module provides several functions to create encoders and assemble them into a registry of encoders. -} -module Data.Registry.Aeson.Encoder where+module Data.Registry.Aeson.Encoder+  ( module Data.Registry.Aeson.Encoder,+    module Data.Registry.Aeson.TH.Encoder,+  )+where -import Control.Monad.Fail import Data.Aeson import Data.Aeson.Encoding.Internal import qualified Data.Aeson.Key as K import qualified Data.Aeson.KeyMap as KM import qualified Data.ByteString.Lazy as BL (toStrict) import Data.Functor.Contravariant-import Data.List (nub) import Data.Registry-import Data.Registry.Aeson.TH+import Data.Registry.Aeson.TH.Encoder import Data.Registry.Internal.Types hiding (Value) import qualified Data.Vector as V-import Language.Haskell.TH-import Language.Haskell.TH.Syntax import Protolude hiding (Type, list)  -- * ENCODER DATA TYPE@@ -105,58 +105,25 @@ array :: [Value] -> Value array = Array . V.fromList --- * TEMPLATE HASKELL+-- * DEFAULT VALUES --- | Make an Encoder for a given data type---   Usage: $(makeEncoder ''MyDataType) <: otherEncoders-makeEncoder :: Name -> ExpQ-makeEncoder encodedType = appE (varE $ mkName "fun") $ do-  info <- reify encodedType-  case info of-    TyConI (NewtypeD _context _name _typeVars _kind constructor _deriving) ->-      makeConstructorsEncoder [constructor]-    TyConI (DataD _context _name _typeVars _kind constructors _deriving) ->-      makeConstructorsEncoder constructors-    other -> do-      qReport True ("can only create encoders for an ADT, got: " <> show other)-      fail "encoders creation failed"+defaultEncoderOptions :: Registry _ _+defaultEncoderOptions =+  fun defaultConstructorEncoder+    <: val defaultOptions --- \(o::Options) (e0::Encoder A0) (e1::Encoder A1) ... -> Encoder $ \a ->---   case a of---     T1 a0 a1 ... -> makeEncoderFromConstructor o (FromConstructor names types "T1" fieldNames [encode e0 a0, encode e1 a1, ...])---     T2 a0 a4 ... -> makeEncoderFromConstructor o (FromConstructor names types "T2" fieldNames [encode e0 a0, encode e4 a4, ...])-makeConstructorsEncoder :: [Con] -> ExpQ-makeConstructorsEncoder cs = do-  -- get the types of all the fields of all the constructors-  ts <- nub . join <$> for cs typesOf-  constructorsNames <- for cs nameOf-  let options = sigP (varP (mkName "os")) (conT $ mkName "Options")-  let encoderParameters = options : ((\(t, n) -> sigP (varP (mkName $ "e" <> show n)) (appT (conT $ mkName "Encoder") (pure t))) <$> zip ts [0 ..])-  matchClauses <- for cs $ makeMatchClause constructorsNames ts-  lamE encoderParameters (appE (conE (mkName "Encoder")) (lamCaseE (pure <$> matchClauses)))+-- * BUILDING ENCODERS --- | Make the match clause for a constructor given---    - the list of all the encoder types---    - the constructor name---    - the constructor index in the list of all the constructors for the encoded data type---   T1 a0 a1 ... -> makeEncoderFromConstructor o (FromConstructor names types cName fieldNames values)-makeMatchClause :: [Name] -> [Type] -> Con -> MatchQ-makeMatchClause constructorNames allTypes c = do-  ts <- typesOf c-  constructorTypes <- indexConstructorTypes allTypes ts-  cName <- dropQualified <$> nameOf c-  let names = listE $ litE . StringL . show . dropQualified <$> constructorNames-  let types = listE $ litE . StringL . show <$> allTypes-  fields <- fieldsOf c-  let fieldNames = listE $ litE . StringL . show . dropQualified <$> fields-  let params = conP (mkName $ show cName) $ (\(_, n) -> varP (mkName $ "a" <> show n)) <$> constructorTypes-  let values = listE $ (\(_, n) -> appE (appE (varE $ mkName "encode") (varE (mkName $ "e" <> show n))) (varE (mkName $ "a" <> show n))) <$> constructorTypes-  let encoded =-        varE (mkName "makeEncoderFromConstructor")-          `appE` varE (mkName "os")-          `appE` (conE (mkName "FromConstructor") `appE` names `appE` types `appE` litE (StringL $ show cName) `appE` fieldNames `appE` values)-  match params (normalB encoded) []+-- | A ConstructorEncoder uses configuration options + type information extracted from+--   a given data type (with TemplateHaskell) in order to produce a Value and an Encoding+newtype ConstructorEncoder = ConstructorEncoder+  { encodeConstructor :: Options -> FromConstructor -> (Value, Encoding)+  } +-- | Default implementation, it can be overridden in a registry+defaultConstructorEncoder :: ConstructorEncoder+defaultConstructorEncoder = ConstructorEncoder makeEncoderFromConstructor+ -- | Minimum set of data extracted from a given type with Template Haskell --   in order to create the appropriate encoder given an Options value data FromConstructor = FromConstructor@@ -173,20 +140,6 @@   }   deriving (Eq, Show) --- | Apply Options to the constructor name + field names---   and remove Nothing values if necessary-modifyFromConstructorWithOptions :: Options -> FromConstructor -> FromConstructor-modifyFromConstructorWithOptions options fc = do-  let (fn, fv) =-        if omitNothingFields options && length (fromConstructorFieldNames fc) == length (fromConstructorValues fc)-          then unzip $ filter ((/= Null) . fst . snd) $ zip (fromConstructorFieldNames fc) (fromConstructorValues fc)-          else (fromConstructorFieldNames fc, fromConstructorValues fc)-  fc-    { fromConstructorName = toS . constructorTagModifier options . toS $ fromConstructorName fc,-      fromConstructorFieldNames = toS . fieldLabelModifier options . toS <$> fn,-      fromConstructorValues = fv-    }- -- | Make an Encoder from Options and the representation of a constructor for a given value to encode makeEncoderFromConstructor :: Options -> FromConstructor -> (Value, Encoding) makeEncoderFromConstructor options fromConstructor = do@@ -211,11 +164,9 @@                 else valuesToObject names values             _ ->               if null names-                then case values of-                  [v] -> v-                  _ -> do-                    let (vs, es) = unzip values-                    (array vs, list identity es)+                then do+                  let (vs, es) = unzip values+                  (array vs, list identity es)                 else valuesToObject names values     -- sum constructor     _ ->@@ -294,6 +245,20 @@               ( Object . KM.fromList $ (K.fromText $ toS tagFieldName, String constructorTag) : zip fieldNamesKeys vs,                 pairs (foldMap identity $ pair (K.fromText $ toS tagFieldName) (string $ toS constructorTag) : (uncurry pair <$> zip fieldNamesKeys es))                 )++-- | Apply Options to the constructor name + field names+--   and remove Nothing values if necessary+modifyFromConstructorWithOptions :: Options -> FromConstructor -> FromConstructor+modifyFromConstructorWithOptions options fc = do+  let (fn, fv) =+        if omitNothingFields options && length (fromConstructorFieldNames fc) == length (fromConstructorValues fc)+          then unzip $ filter ((/= Null) . fst . snd) $ zip (fromConstructorFieldNames fc) (fromConstructorValues fc)+          else (fromConstructorFieldNames fc, fromConstructorValues fc)+  fc+    { fromConstructorName = toS . constructorTagModifier options . toS $ fromConstructorName fc,+      fromConstructorFieldNames = toS . fieldLabelModifier options . toS <$> fn,+      fromConstructorValues = fv+    }  -- | Create an Object from a list of field names and a list of Values --   both as a Value and as an Encoding
− src/Data/Registry/Aeson/TH.hs
@@ -1,64 +0,0 @@-{-# LANGUAGE PartialTypeSignatures #-}-{-# OPTIONS_GHC -Wno-type-defaults #-}-{-# OPTIONS_GHC -fno-warn-partial-type-signatures #-}--{--  TemplateHaskell functions--}--module Data.Registry.Aeson.TH where--import Control.Monad.Fail-import Data.List (elemIndex)-import qualified Data.Text as T-import Language.Haskell.TH-import Language.Haskell.TH.Syntax-import Protolude hiding (Type)--indexConstructorTypes :: [Type] -> [Type] -> Q [(Type, Int)]-indexConstructorTypes allTypes constructorTypes =-  for constructorTypes $ \t ->-    case elemIndex t allTypes of-      Just n -> pure (t, n)-      Nothing -> fail $ "the type " <> show t <> " cannot be found in the list of all types " <> show allTypes---- | Get the types of all the fields of a constructor-typesOf :: Con -> Q [Type]-typesOf (NormalC _ types) = pure (snd <$> types)-typesOf (RecC _ types) = pure $ (\(_, _, t) -> t) <$> types-typesOf other = do-  qReport True ("we can only create encoders / decoders for normal constructors and records, got: " <> show other)-  fail "encoders / decoders creation failed"---- | Get the name of a constructor-nameOf :: Con -> Q Name-nameOf (NormalC n _) = pure n-nameOf (RecC n _) = pure n-nameOf other = do-  qReport True ("we can only create encoders / decoders for normal constructors and records, got: " <> show other)-  fail "encoders / decoders creation failed"---- | Get the list of names of a constructor-fieldsOf :: Con -> Q [Name]-fieldsOf (NormalC _ _) = pure []-fieldsOf (RecC _ types) = pure $ (\(f, _, _) -> f) <$> types-fieldsOf other = do-  qReport True ("we can only create encoders / decoders for normal constructors and records, got: " <> show other)-  fail "encoders / decoders creation failed"---- | Remove the module name from a qualified name-dropQualified :: Name -> Name-dropQualified name = maybe name (mkName . toS) (lastMay (T.splitOn "." (show name)))---- | Return the name of a given type-getSimpleTypeName :: Type -> Name-getSimpleTypeName (ForallT _ _ ty) = getSimpleTypeName ty-getSimpleTypeName (VarT name) = dropQualified name-getSimpleTypeName (ConT name) = dropQualified name-getSimpleTypeName (TupleT n) = dropQualified $ tupleTypeName n-getSimpleTypeName ArrowT = dropQualified ''(->)-getSimpleTypeName ListT = dropQualified ''[]-getSimpleTypeName (AppT t1 t2) = mkName (show (getSimpleTypeName t1) <> " " <> show (getSimpleTypeName t2))-getSimpleTypeName (SigT t _) = getSimpleTypeName t-getSimpleTypeName (UnboxedTupleT n) = dropQualified $ unboxedTupleTypeName n-getSimpleTypeName t = panic $ "getSimpleTypeName: Unknown type: " <> show t
+ src/Data/Registry/Aeson/TH/Decoder.hs view
@@ -0,0 +1,99 @@+{-# OPTIONS_GHC -Wno-type-defaults #-}+module Data.Registry.Aeson.TH.Decoder where++import Control.Monad.Fail+import Data.List (nub)+import Data.Registry.Aeson.TH.TH+import Language.Haskell.TH+import Language.Haskell.TH.Syntax+import Protolude as P hiding (Type)++{-+  This module uses TemplateHaskell to extract enough type information to be able to+  build a Decoder based on configuration options+-}++-- | Make a Decoder for a given data type+--   Usage: $(makeDecoder ''MyDataType <: otherDecoders)+makeDecoder :: Name -> ExpQ+makeDecoder typeName = appE (varE $ mkName "fun") $ do+  info <- reify typeName+  case info of+    TyConI (NewtypeD _context _name _typeVars _kind constructor _deriving) ->+      makeConstructorsDecoder typeName [constructor]+    TyConI (DataD _context _name _typeVars _kind constructors _deriving) -> do+      case constructors of+        [] -> do+          qReport True "can not make an Decoder for an empty data type"+          fail "decoders creation failed"+        _ -> makeConstructorsDecoder typeName constructors+    other -> do+      qReport True ("can only create decoders for an ADT, got: " <> P.show other)+      fail "decoders creation failed"++-- | Make a decoder for a given data type by extracting just enough metadata about the data type in order to be able+--   to parse a Value+--+--   For example for the data type:+--+--   data T = T1 {f1::Int, f2::Int} | T2 Int Int+--+--   we add this function to the registry:+--+--   \opts d1 d2 d3 -> Decoder $ \v ->+--     decodeFromDefinitions opts v $ \case+--       ToConstructor "T1" [v1, v2]-> T1 <$> d1 v1 <*> d2 v2 ...+--       ToConstructor "T2" [v1, v2]-> T2 <$> d1 v1 <*> d3 v2 ...+--       other -> Left ("cannot decode " <> valueToText v)+--+--   The \case function is the only one which needs to be generated in order to match the exact shape of the+--   constructors to instantiate+makeConstructorsDecoder :: Name -> [Con] -> ExpQ+makeConstructorsDecoder typeName cs = do+  ts <- nub . join <$> for cs typesOf+  let decoderParameters = sigP (varP (mkName "os")) (conT $ mkName "Options") : sigP (varP (mkName "cd")) (conT $ mkName "ConstructorsDecoder") : ((\(t, n) -> sigP (varP (mkName $ "d" <> P.show n)) (appT (conT $ mkName "Decoder") (pure t))) <$> zip ts [0 ..])+  -- makeToConstructors os [Constructor "T1" ["f1", "f2"], Constructor "T2" []] v+  let paramP = varP (mkName "v")+  constructorDefs <- for cs $ \c -> do+    cName <- dropQualified <$> nameOf c+    fields <- fmap (litE . StringL . P.show . dropQualified) <$> fieldsOf c+    fieldTypes <- fmap (litE . StringL . P.show . getSimpleTypeName) <$> typesOf c+    varE (mkName "makeConstructorDef") `appE` (litE . StringL $ P.show cName) `appE` listE fields `appE` listE fieldTypes+  let matchClauses = makeMatchClause typeName ts <$> cs+  let matchFunction = lamCaseE (matchClauses <> [makeErrorClause typeName])+  let resolveFunction = varE (mkName "decodeFromDefinitions") `appE` varE (mkName "os") `appE` varE (mkName "cd") `appE` listE (pure <$> constructorDefs) `appE` varE (mkName "v") `appE` matchFunction+  lamE decoderParameters (appE (conE (mkName "Decoder")) (lamE [paramP] resolveFunction))++-- | Decode the nth constructor of a data type+--    ToConstructor "T1" [v1, v2]-> T1 <$> d1 v1 <*> d2 v2 ...+makeMatchClause :: Name -> [Type] -> Con -> MatchQ+makeMatchClause typeName allTypes c = do+  ts <- typesOf c+  constructorTypes <- fmap snd <$> indexConstructorTypes allTypes ts+  cName <- dropQualified <$> nameOf c+  let fieldsP = listP $ (\i -> varP $ mkName ("v" <> P.show i)) <$> constructorTypes+  match+    (conP (mkName "ToConstructor") [litP (StringL . P.show $ cName), fieldsP])+    (normalB (applyDecoder typeName cName constructorTypes))+    []++-- | Return an error the json value cannot be decoded with a constructor name and some values+makeErrorClause :: Name -> MatchQ+makeErrorClause typeName = do+  let errorMessage =+        (varE (mkName "<>") `appE` litE (StringL ("cannot use this constructor to create an instance of type '" <> P.show typeName <> "': ")))+          `appE` (varE (mkName "show") `appE` varE (mkName "_1"))+  match (varP $ mkName "_1") (normalB (appE (conE $ mkName "Left") errorMessage)) []++-- ConstructorName <$> decodeFieldValue d1 o1 <*> decodeFieldValue d2 o2 ...+applyDecoder :: Name -> Name -> [Int] -> ExpQ+applyDecoder _typeName cName [] = appE (varE $ mkName "pure") (conE cName)+applyDecoder typeName cName (n : ns) = do+  let cons = appE (varE $ mkName "pure") (conE cName)+  foldr (\i r -> appE (appE (varE (mkName "ap")) r) $ decodeAt i) (appE (appE (varE (mkName "ap")) cons) $ decodeAt n) (reverse ns)+  where+    decodeAt i =+      varE (mkName "decodeFieldValue") `appE` varE (mkName ("d" <> P.show i))+        `appE` (litE . StringL . P.show . dropQualified $ typeName)+        `appE` (litE . StringL . P.show . dropQualified $ cName)+        `appE` varE (mkName ("v" <> P.show i))
+ src/Data/Registry/Aeson/TH/Encoder.hs view
@@ -0,0 +1,67 @@+{-# OPTIONS_GHC -Wno-type-defaults #-}++module Data.Registry.Aeson.TH.Encoder where++import Control.Monad.Fail+import Data.List (nub)+import Data.Registry.Aeson.TH.TH+import Language.Haskell.TH+import Language.Haskell.TH.Syntax+import Protolude as P hiding (Type)++{-+  This module uses TemplateHaskell to extract enough type information to be able to+  build an Encoder based on configuration options+-}++-- | Make an Encoder for a given data type+--   Usage: $(makeEncoder ''MyDataType) <: otherEncoders+makeEncoder :: Name -> ExpQ+makeEncoder encodedType = appE (varE $ mkName "fun") $ do+  info <- reify encodedType+  case info of+    TyConI (NewtypeD _context _name _typeVars _kind constructor _deriving) ->+      makeConstructorsEncoder [constructor]+    TyConI (DataD _context _name _typeVars _kind constructors _deriving) ->+      makeConstructorsEncoder constructors+    other -> do+      qReport True ("can only create encoders for an ADT, got: " <> show other)+      fail "encoders creation failed"++-- \(o::Options) (ce::ConstructorEncoder) (e0::Encoder A0) (e1::Encoder A1) ... -> Encoder $ \a ->+--   case a of+--     T1 a0 a1 ... -> encodeConstructor ce o (FromConstructor names types "T1" fieldNames [encode e0 a0, encode e1 a1, ...])+--     T2 a0 a4 ... -> encodeConstructor ce o (FromConstructor names types "T2" fieldNames [encode e0 a0, encode e4 a4, ...])+makeConstructorsEncoder :: [Con] -> ExpQ+makeConstructorsEncoder cs = do+  -- get the types of all the fields of all the constructors+  ts <- nub . join <$> for cs typesOf+  constructorsNames <- for cs nameOf+  let options = sigP (varP (mkName "os")) (conT $ mkName "Options")+  let constructorEncoder = sigP (varP (mkName "ce")) (conT $ mkName "ConstructorEncoder")+  let encoderParameters = options : constructorEncoder : ((\(t, n) -> sigP (varP (mkName $ "e" <> show n)) (appT (conT $ mkName "Encoder") (pure t))) <$> zip ts [0 ..])+  matchClauses <- for cs $ makeMatchClause constructorsNames ts+  lamE encoderParameters (appE (conE (mkName "Encoder")) (lamCaseE (pure <$> matchClauses)))++-- | Make the match clause for a constructor given+--    - the list of all the encoder types+--    - the constructor name+--    - the constructor index in the list of all the constructors for the encoded data type+--   T1 a0 a1 ... -> encodeConstructor ce o (FromConstructor names types cName fieldNames values)+makeMatchClause :: [Name] -> [Type] -> Con -> MatchQ+makeMatchClause constructorNames allTypes c = do+  ts <- typesOf c+  constructorTypes <- indexConstructorTypes allTypes ts+  cName <- dropQualified <$> nameOf c+  let names = listE $ litE . StringL . show . dropQualified <$> constructorNames+  let types = listE $ litE . StringL . show <$> allTypes+  fields <- fieldsOf c+  let fieldNames = listE $ litE . StringL . show . dropQualified <$> fields+  let params = conP (mkName $ show cName) $ (\(_, n) -> varP (mkName $ "a" <> show n)) <$> constructorTypes+  let values = listE $ (\(_, n) -> appE (appE (varE $ mkName "encode") (varE (mkName $ "e" <> show n))) (varE (mkName $ "a" <> show n))) <$> constructorTypes+  let encoded =+        varE (mkName "encodeConstructor")+          `appE` varE (mkName "ce")+          `appE` varE (mkName "os")+          `appE` (conE (mkName "FromConstructor") `appE` names `appE` types `appE` litE (StringL $ show cName) `appE` fieldNames `appE` values)+  match params (normalB encoded) []
+ src/Data/Registry/Aeson/TH/TH.hs view
@@ -0,0 +1,64 @@+{-# LANGUAGE PartialTypeSignatures #-}+{-# OPTIONS_GHC -Wno-type-defaults #-}+{-# OPTIONS_GHC -fno-warn-partial-type-signatures #-}++{-+  TemplateHaskell utility functions to create encoders and decoders+-}++module Data.Registry.Aeson.TH.TH where++import Control.Monad.Fail+import Data.List (elemIndex)+import qualified Data.Text as T+import Language.Haskell.TH+import Language.Haskell.TH.Syntax+import Protolude hiding (Type)++indexConstructorTypes :: [Type] -> [Type] -> Q [(Type, Int)]+indexConstructorTypes allTypes constructorTypes =+  for constructorTypes $ \t ->+    case elemIndex t allTypes of+      Just n -> pure (t, n)+      Nothing -> fail $ "the type " <> show t <> " cannot be found in the list of all types " <> show allTypes++-- | Get the types of all the fields of a constructor+typesOf :: Con -> Q [Type]+typesOf (NormalC _ types) = pure (snd <$> types)+typesOf (RecC _ types) = pure $ (\(_, _, t) -> t) <$> types+typesOf other = do+  qReport True ("we can only create encoders / decoders for normal constructors and records, got: " <> show other)+  fail "encoders / decoders creation failed"++-- | Get the name of a constructor+nameOf :: Con -> Q Name+nameOf (NormalC n _) = pure n+nameOf (RecC n _) = pure n+nameOf other = do+  qReport True ("we can only create encoders / decoders for normal constructors and records, got: " <> show other)+  fail "encoders / decoders creation failed"++-- | Get the list of names of a constructor+fieldsOf :: Con -> Q [Name]+fieldsOf (NormalC _ _) = pure []+fieldsOf (RecC _ types) = pure $ (\(f, _, _) -> f) <$> types+fieldsOf other = do+  qReport True ("we can only create encoders / decoders for normal constructors and records, got: " <> show other)+  fail "encoders / decoders creation failed"++-- | Remove the module name from a qualified name+dropQualified :: Name -> Name+dropQualified name = maybe name (mkName . toS) (lastMay (T.splitOn "." (show name)))++-- | Return the name of a given type+getSimpleTypeName :: Type -> Name+getSimpleTypeName (ForallT _ _ ty) = getSimpleTypeName ty+getSimpleTypeName (VarT name) = dropQualified name+getSimpleTypeName (ConT name) = dropQualified name+getSimpleTypeName (TupleT n) = dropQualified $ tupleTypeName n+getSimpleTypeName ArrowT = dropQualified ''(->)+getSimpleTypeName ListT = dropQualified ''[]+getSimpleTypeName (AppT t1 t2) = mkName (show (getSimpleTypeName t1) <> " " <> show (getSimpleTypeName t2))+getSimpleTypeName (SigT t _) = getSimpleTypeName t+getSimpleTypeName (UnboxedTupleT n) = dropQualified $ unboxedTupleTypeName n+getSimpleTypeName t = panic $ "getSimpleTypeName: Unknown type: " <> show t
test/Test/Data/Registry/Aeson/DecoderSpec.hs view
@@ -176,7 +176,7 @@     <: jsonDecoder @Text     <: decodeMaybeOf @Int     <: jsonDecoder @Int-    <: val defaultOptions+    <: defaultDecoderOptions  utcTimeDecoder :: Decoder UTCTime utcTimeDecoder = Decoder $ \case
test/Test/Data/Registry/Aeson/EncoderSpec.hs view
@@ -77,7 +77,7 @@     <: encodeMaybeOf @Int     <: jsonEncoder @Text     <: jsonEncoder @Int-    <: val defaultOptions+    <: defaultEncoderOptions  -- | This Encoder for DateTime reproduces the default generic one datetimeEncoder :: Encoder DateTime
test/Test/Data/Registry/Aeson/RoundtripSpec.hs view
@@ -133,7 +133,7 @@     <: jsonEncoder @String     <: jsonEncoder @Int     <: jsonEncoder @Bool-    <: val defaultOptions+    <: defaultEncoderOptions  -- Decoders @@ -177,7 +177,7 @@     <: jsonDecoder @String     <: jsonDecoder @Int     <: jsonDecoder @Bool-    <: val defaultOptions+    <: defaultDecoderOptions  -- Generators