packages feed

registry-aeson 0.2.2.0 → 0.2.3.0

raw patch · 12 files changed

+384/−128 lines, 12 filesPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

API changes (from Hackage documentation)

- Data.Registry.Aeson.TH.TH: dropQualified :: Name -> Name
+ Data.Registry.Aeson.TH.Decoder: makeDecoderQualified :: Name -> ExpQ
+ Data.Registry.Aeson.TH.Decoder: makeDecoderQualifiedLast :: Name -> ExpQ
+ Data.Registry.Aeson.TH.Decoder: makeDecoderWith :: ThOptions -> Name -> ExpQ
+ Data.Registry.Aeson.TH.Encoder: makeEncoderQualified :: Name -> ExpQ
+ Data.Registry.Aeson.TH.Encoder: makeEncoderQualifiedLast :: Name -> ExpQ
+ Data.Registry.Aeson.TH.Encoder: makeEncoderWith :: ThOptions -> Name -> ExpQ
+ Data.Registry.Aeson.TH.TH: makeName :: ThOptions -> Name -> Name
+ Data.Registry.Aeson.TH.ThOptions: ThOptions :: (Text -> Text) -> ThOptions
+ Data.Registry.Aeson.TH.ThOptions: [modifyTypeName] :: ThOptions -> Text -> Text
+ Data.Registry.Aeson.TH.ThOptions: defaultThOptions :: ThOptions
+ Data.Registry.Aeson.TH.ThOptions: dropQualifier :: Text -> Text
+ Data.Registry.Aeson.TH.ThOptions: newtype ThOptions
+ Data.Registry.Aeson.TH.ThOptions: qualified :: Text -> Text
+ Data.Registry.Aeson.TH.ThOptions: qualifyAs :: Text -> Text -> Text
+ Data.Registry.Aeson.TH.ThOptions: qualifyWithLastName :: Text -> Text
- Data.Registry.Aeson.TH.Decoder: applyDecoder :: Name -> Name -> [Int] -> ExpQ
+ Data.Registry.Aeson.TH.Decoder: applyDecoder :: ThOptions -> Name -> Name -> [Int] -> ExpQ
- Data.Registry.Aeson.TH.Decoder: makeConstructorsDecoder :: Name -> [Con] -> ExpQ
+ Data.Registry.Aeson.TH.Decoder: makeConstructorsDecoder :: ThOptions -> Name -> [Con] -> ExpQ
- Data.Registry.Aeson.TH.Decoder: makeMatchClause :: Name -> [Type] -> Con -> MatchQ
+ Data.Registry.Aeson.TH.Decoder: makeMatchClause :: ThOptions -> Name -> [Type] -> Con -> MatchQ
- Data.Registry.Aeson.TH.Encoder: makeConstructorsEncoder :: [Con] -> ExpQ
+ Data.Registry.Aeson.TH.Encoder: makeConstructorsEncoder :: ThOptions -> [Con] -> ExpQ
- Data.Registry.Aeson.TH.Encoder: makeMatchClause :: [Name] -> [Type] -> Con -> MatchQ
+ Data.Registry.Aeson.TH.Encoder: makeMatchClause :: ThOptions -> [Name] -> [Type] -> Con -> MatchQ
- Data.Registry.Aeson.TH.TH: getSimpleTypeName :: Type -> Name
+ Data.Registry.Aeson.TH.TH: getSimpleTypeName :: ThOptions -> Type -> Name

Files

registry-aeson.cabal view
@@ -5,7 +5,7 @@ -- see: https://github.com/sol/hpack  name:           registry-aeson-version:        0.2.2.0+version:        0.2.3.0 synopsis:       Aeson encoders / decoders description:    This library provides encoders / decoders which can be easily customized for the Aeson format. category:       Data@@ -27,40 +27,25 @@       Data.Registry.Aeson.TH.Decoder       Data.Registry.Aeson.TH.Encoder       Data.Registry.Aeson.TH.TH+      Data.Registry.Aeson.TH.ThOptions   other-modules:       Paths_registry_aeson   hs-source-dirs:       src   default-extensions:-      BangPatterns       DefaultSignatures-      EmptyCase-      ExistentialQuantification-      FlexibleContexts-      FlexibleInstances+      DerivingStrategies       FunctionalDependencies       GADTs-      DerivingVia-      DerivingStrategies-      InstanceSigs-      KindSignatures       LambdaCase-      MultiParamTypeClasses       MultiWayIf-      NamedFieldPuns       NoImplicitPrelude       OverloadedStrings       PatternSynonyms-      Rank2Types-      RankNTypes-      ScopedTypeVariables-      StandaloneDeriving+      StrictData       TemplateHaskell-      TupleSections-      TypeApplications       TypeFamilies       TypeFamilyDependencies-      TypeOperators   ghc-options: -Wall -Wcompat -Wincomplete-record-updates -fhide-source-paths -fprint-potential-instances -fno-warn-partial-type-signatures -optP-Wno-nonportable-include-path -Wincomplete-uni-patterns   build-depends:       aeson ==2.*@@ -74,7 +59,7 @@     , transformers >=0.5 && <2     , unordered-containers >=0.2 && <1     , vector >=0.1 && <1-  default-language: Haskell2010+  default-language: GHC2021  test-suite spec   type: exitcode-stdio-1.0@@ -87,39 +72,24 @@       Test.Data.Registry.Aeson.RecursiveSpec       Test.Data.Registry.Aeson.RoundtripData       Test.Data.Registry.Aeson.RoundtripSpec+      Test.Data.Registry.Aeson.SimilarDataTypes       Paths_registry_aeson   hs-source-dirs:       test   default-extensions:-      BangPatterns       DefaultSignatures-      EmptyCase-      ExistentialQuantification-      FlexibleContexts-      FlexibleInstances+      DerivingStrategies       FunctionalDependencies       GADTs-      DerivingVia-      DerivingStrategies-      InstanceSigs-      KindSignatures       LambdaCase-      MultiParamTypeClasses       MultiWayIf-      NamedFieldPuns       NoImplicitPrelude       OverloadedStrings       PatternSynonyms-      Rank2Types-      RankNTypes-      ScopedTypeVariables-      StandaloneDeriving+      StrictData       TemplateHaskell-      TupleSections-      TypeApplications       TypeFamilies       TypeFamilyDependencies-      TypeOperators   ghc-options: -Wall -Wcompat -Wincomplete-record-updates -fhide-source-paths -fprint-potential-instances -fno-warn-partial-type-signatures -optP-Wno-nonportable-include-path -threaded -rtsopts -with-rtsopts=-N -fno-warn-orphans -fno-warn-missing-signatures -fno-warn-incomplete-uni-patterns -fno-warn-type-defaults -optP-Wno-nonportable-include-path   build-depends:       aeson ==2.*@@ -138,4 +108,4 @@     , transformers >=0.5 && <2     , unordered-containers >=0.2 && <1     , vector >=0.1 && <1-  default-language: Haskell2010+  default-language: GHC2021
src/Data/Registry/Aeson/Decoder.hs view
@@ -12,19 +12,21 @@ module Data.Registry.Aeson.Decoder   ( module Data.Registry.Aeson.Decoder,     module Data.Registry.Aeson.TH.Decoder,+    module Data.Registry.Aeson.TH.ThOptions,   ) where  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.Aeson.Key qualified as K+import Data.Aeson.KeyMap qualified as KM+import Data.ByteString.Lazy qualified as BL import Data.List ((\\)) import Data.Registry import Data.Registry.Aeson.TH.Decoder-import qualified Data.Text as T-import qualified Data.Text.Encoding as T-import qualified Data.Vector as Vector+import Data.Registry.Aeson.TH.ThOptions+import Data.Text qualified as T+import Data.Text.Encoding qualified as T+import Data.Vector qualified as Vector import Protolude as P hiding (Type) import Prelude (String, show) @@ -242,7 +244,7 @@       -- if there is only one constructor and tagging is not required (and nullary constructors must be tagged)       [c]         | not (tagSingleConstructors options) && not (isEnumeration && not (allNullaryToStringTag options)) ->-          pure <$> makeToConstructorFromValue options c value+            pure <$> makeToConstructorFromValue options c value       _ -> do         maybe (pure ()) Left $ checkSumEncoding options constructors value         case sumEncoding options of@@ -268,34 +270,34 @@               -- constructor with no fields               ([], [], [])                 | tagValue == String modifiedConstructorName ->-                  pure $ ToConstructor constructorName []+                    pure $ ToConstructor constructorName []               -- constructor with one unnamed field               ([], [], [_])                 | tagValue == String modifiedConstructorName ->-                  case KM.lookup (K.fromText contentsFieldName) vs of-                    Just fieldValue -> pure $ ToConstructor constructorName [(Nothing, fieldValue)]-                    Nothing -> Left $ "field " <> contentsFieldName <> " not found"+                    case KM.lookup (K.fromText contentsFieldName) vs of+                      Just fieldValue -> pure $ ToConstructor constructorName [(Nothing, fieldValue)]+                      Nothing -> Left $ "field " <> contentsFieldName <> " not found"               -- constructor with one named field               ([modifiedFieldName], [fieldName], [fieldType])                 | tagValue == String modifiedConstructorName ->-                  case KM.lookup (K.fromText modifiedFieldName) vs of-                    Just fieldValue -> pure $ ToConstructor constructorName [(Just (fieldName, fieldType), fieldValue)]-                    Nothing -> Left $ "field " <> modifiedFieldName <> " not found"+                    case KM.lookup (K.fromText modifiedFieldName) vs of+                      Just fieldValue -> pure $ ToConstructor constructorName [(Just (fieldName, fieldType), fieldValue)]+                      Nothing -> Left $ "field " <> modifiedFieldName <> " not found"               -- constructor with at least one named field and possibly Nothing fields               (_, _, _)                 | tagValue == String modifiedConstructorName && omitNothingFields options && any (`elem` modifiedFieldNames) (K.toText <$> KM.keys vs) -> do-                  let rest = KM.fromList $ filter ((/= tagFieldName) . K.toText . fst) $ KM.toList vs-                  makeToConstructorFromValue options c (Object rest)+                    let rest = KM.fromList $ filter ((/= tagFieldName) . K.toText . fst) $ KM.toList vs+                    makeToConstructorFromValue options c (Object rest)               -- constructor with several named fields               (_, _ : _, _)                 | tagValue == String modifiedConstructorName ->-                  makeToConstructorFromValue options c value+                    makeToConstructorFromValue options c value               -- constructor with no named fields               (_, _, _)                 | tagValue == String modifiedConstructorName && any (== contentsFieldName) (K.toText <$> KM.keys vs) ->-                  case KM.lookup (K.fromText contentsFieldName) vs of-                    Just contentsValue -> makeToConstructorFromValue options c contentsValue-                    _ -> Left $ "contents field not found '" <> contentsFieldName <> "'"+                    case KM.lookup (K.fromText contentsFieldName) vs of+                      Just contentsValue -> makeToConstructorFromValue options c contentsValue+                      _ -> Left $ "contents field not found '" <> contentsFieldName <> "'"               (_, _, _) ->                 Left $ "failed to instantiate constructor: " <> P.show c           Nothing ->@@ -319,10 +321,10 @@     case value of       Object [(tagValue, contents)]         | K.toText tagValue == modifiedConstructorName ->-          makeToConstructorFromValue options c contents+            makeToConstructorFromValue options c contents       String v         | v == modifiedConstructorName ->-          makeToConstructorFromValue options c value+            makeToConstructorFromValue options c value       _ ->         Left $ "failed to instantiate constructor: " <> P.show c @@ -334,10 +336,10 @@     case value of       Array [tagValue, contents]         | tagValue == String modifiedConstructorName ->-          makeToConstructorFromValue options c contents+            makeToConstructorFromValue options c contents       String v         | v == modifiedConstructorName ->-          makeToConstructorFromValue options c value+            makeToConstructorFromValue options c value       _ ->         Left $ "failed to instantiate constructor: " <> P.show c @@ -355,7 +357,7 @@               Just $ "tag field '" <> tagFieldName <> "' not found"             Just (String tagValue)               | tagValue `elem` constructorModifiedNames ->-                Nothing+                  Nothing             Just v ->               unexpectedConstructor constructorModifiedNames v         _ -> Just "expected an Object for a TaggedObject sum encoding"
src/Data/Registry/Aeson/Encoder.hs view
@@ -11,18 +11,20 @@ module Data.Registry.Aeson.Encoder   ( module Data.Registry.Aeson.Encoder,     module Data.Registry.Aeson.TH.Encoder,+    module Data.Registry.Aeson.TH.ThOptions,   ) where  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.Aeson.Key qualified as K+import Data.Aeson.KeyMap qualified as KM+import Data.ByteString.Lazy qualified as BL (toStrict) import Data.Functor.Contravariant import Data.Registry import Data.Registry.Aeson.TH.Encoder-import qualified Data.Vector as V+import Data.Registry.Aeson.TH.ThOptions+import Data.Vector qualified as V import Protolude hiding (Type, list)  -- * ENCODER DATA TYPE@@ -201,7 +203,7 @@           case (vs, es) of             ([v], [e])               | unwrapUnaryRecords options ->-                (array [String constructorTag, v], list identity [string $ toS constructorTag, e])+                  (array [String constructorTag, v], list identity [string $ toS constructorTag, e])             _ -> do               let (vs', es') = valuesToObject fieldNames values               (array [String constructorTag, vs'], list identity [string $ toS constructorTag, es'])@@ -218,7 +220,7 @@           case (vs, es) of             ([v], [e])               | unwrapUnaryRecords options ->-                (Object $ KM.singleton (K.fromText constructorTag) v, pairs (pair (K.fromText constructorTag) e))+                  (Object $ KM.singleton (K.fromText constructorTag) v, pairs (pair (K.fromText constructorTag) e))             _ -> do               let (vs', es') = valuesToObject fieldNames values               (Object $ KM.singleton (K.fromText constructorTag) vs', pairs (pair (K.fromText constructorTag) es'))
src/Data/Registry/Aeson/TH/Decoder.hs view
@@ -1,8 +1,10 @@ {-# 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.ThOptions import Data.Registry.Aeson.TH.TH import Language.Haskell.TH import Language.Haskell.TH.Syntax@@ -13,20 +15,39 @@   build a Decoder based on configuration options -} --- | Make a Decoder for a given data type---   Usage: $(makeDecoder ''MyDataType <: otherDecoders)+-- | Make an Encoder for a given data type+--   Usage: $(makeDecoder ''MyDataType <: otherEncoders) makeDecoder :: Name -> ExpQ-makeDecoder typeName = appE (varE $ mkName "fun") $ do+makeDecoder = makeDecoderWith defaultThOptions++-- | Make an Encoder for a given data type, where all types names are qualified with their module full name+--    -- MyDataType is defined in X.Y.Z+--    import X.Y.Z qualified+--    $(makeDecoderQualified ''MyDataType <: otherEncoders)+makeDecoderQualified :: Name -> ExpQ+makeDecoderQualified = makeDecoderWith (ThOptions qualified)++-- | Make an Encoder for a given data type, where all types names are qualified with their module name+--    -- MyDataType is defined in X.Y.Z+--    import X.Y.Z qualified as Z+--    $(makeDecoderQualifiedLast ''MyDataType <: otherEncoders)+makeDecoderQualifiedLast :: Name -> ExpQ+makeDecoderQualifiedLast = makeDecoderWith (ThOptions qualifyWithLastName)++-- | Make a Decoder for a given data type and pass options to specify how names must be qualified+--   Usage: $(makeDecoderWith options ''MyDataType <: otherDecoders)+makeDecoderWith :: ThOptions -> Name -> ExpQ+makeDecoderWith thOptions typeName = appE (varE $ mkName "fun") $ do   info <- reify typeName   case info of     TyConI (NewtypeD _context _name _typeVars _kind constructor _deriving) ->-      makeConstructorsDecoder typeName [constructor]+      makeConstructorsDecoder thOptions 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+        _ -> makeConstructorsDecoder thOptions typeName constructors     other -> do       qReport True ("can only create decoders for an ADT, got: " <> P.show other)       fail "decoders creation failed"@@ -48,33 +69,33 @@ -- --   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+makeConstructorsDecoder :: ThOptions -> Name -> [Con] -> ExpQ+makeConstructorsDecoder thOptions 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+    cName <- makeName thOptions <$> nameOf c+    fields <- fmap (litE . StringL . P.show . makeName thOptions) <$> fieldsOf c+    fieldTypes <- fmap (litE . StringL . P.show . getSimpleTypeName thOptions) <$> typesOf c     varE (mkName "makeConstructorDef") `appE` (litE . StringL $ P.show cName) `appE` listE fields `appE` listE fieldTypes-  let matchClauses = makeMatchClause typeName ts <$> cs+  let matchClauses = makeMatchClause thOptions 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+makeMatchClause :: ThOptions -> Name -> [Type] -> Con -> MatchQ+makeMatchClause thOptions typeName allTypes c = do   ts <- typesOf c   constructorTypes <- fmap snd <$> indexConstructorTypes allTypes ts-  cName <- dropQualified <$> nameOf c+  cName <- makeName thOptions <$> 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))+    (normalB (applyDecoder thOptions typeName cName constructorTypes))     []  -- | Return an error the json value cannot be decoded with a constructor name and some values@@ -86,14 +107,15 @@   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+applyDecoder :: ThOptions -> Name -> Name -> [Int] -> ExpQ+applyDecoder _thOptions _typeName cName [] = appE (varE $ mkName "pure") (conE cName)+applyDecoder thOptions 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)+      varE (mkName "decodeFieldValue")+        `appE` varE (mkName ("d" <> P.show i))+        `appE` (litE . StringL . P.show . makeName thOptions $ typeName)+        `appE` (litE . StringL . P.show . makeName thOptions $ cName)         `appE` varE (mkName ("v" <> P.show i))
src/Data/Registry/Aeson/TH/Encoder.hs view
@@ -4,6 +4,7 @@  import Control.Monad.Fail import Data.List (nub)+import Data.Registry.Aeson.TH.ThOptions import Data.Registry.Aeson.TH.TH import Language.Haskell.TH import Language.Haskell.TH.Syntax@@ -15,32 +16,52 @@ -}  -- | Make an Encoder for a given data type---   Usage: $(makeEncoder ''MyDataType) <: otherEncoders+--   Usage: $(makeEncoder ''MyDataType <: otherEncoders) makeEncoder :: Name -> ExpQ-makeEncoder encodedType = appE (varE $ mkName "fun") $ do+makeEncoder = makeEncoderWith defaultThOptions++-- | Make an Encoder for a given data type, where all types names are qualified with their module full name+--   Usage:+--    -- MyDataType is defined in X.Y.Z+--    import X.Y.Z qualified+--    $(makeEncoderQualified ''MyDataType <: otherEncoders)+makeEncoderQualified :: Name -> ExpQ+makeEncoderQualified = makeEncoderWith (ThOptions qualified)++-- | Make an Encoder for a given data type, where all types names are qualified with their module name+--    -- MyDataType is defined in X.Y.Z+--    import X.Y.Z qualified as Z+--    $(makeEncoderQualifiedLast ''MyDataType <: otherEncoders)+makeEncoderQualifiedLast :: Name -> ExpQ+makeEncoderQualifiedLast = makeEncoderWith (ThOptions qualifyWithLastName)++-- | Make an Encoder for a given data type  and pass options to specify how names must be qualified+--   Usage: $(makeEncoderWith options ''MyDataType) <: otherEncoders+makeEncoderWith :: ThOptions -> Name -> ExpQ+makeEncoderWith thOptions encodedType = appE (varE $ mkName "fun") $ do   info <- reify encodedType   case info of     TyConI (NewtypeD _context _name _typeVars _kind constructor _deriving) ->-      makeConstructorsEncoder [constructor]+      makeConstructorsEncoder thOptions [constructor]     TyConI (DataD _context _name _typeVars _kind constructors _deriving) ->-      makeConstructorsEncoder constructors+      makeConstructorsEncoder thOptions 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 ->+-- \(o::ThOptions) (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+makeConstructorsEncoder :: ThOptions -> [Con] -> ExpQ+makeConstructorsEncoder thOptions 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")+  constructorsNames <- fmap (makeName thOptions) <$> for cs nameOf+  let aesonOptions = 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+  let encoderParameters = aesonOptions : constructorEncoder : ((\(t, n) -> sigP (varP (mkName $ "e" <> show n)) (appT (conT $ mkName "Encoder") (pure t))) <$> zip ts [0 ..])+  matchClauses <- for cs $ makeMatchClause thOptions constructorsNames ts   lamE encoderParameters (appE (conE (mkName "Encoder")) (lamCaseE (pure <$> matchClauses)))  -- | Make the match clause for a constructor given@@ -48,15 +69,15 @@ --    - 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+makeMatchClause :: ThOptions -> [Name] -> [Type] -> Con -> MatchQ+makeMatchClause thOptions constructorNames allTypes c = do   ts <- typesOf c   constructorTypes <- indexConstructorTypes allTypes ts-  cName <- dropQualified <$> nameOf c-  let names = listE $ litE . StringL . show . dropQualified <$> constructorNames+  cName <- makeName thOptions <$> nameOf c+  let names = listE $ litE . StringL . show . makeName thOptions <$> constructorNames   let types = listE $ litE . StringL . show <$> allTypes   fields <- fieldsOf c-  let fieldNames = listE $ litE . StringL . show . dropQualified <$> fields+  let fieldNames = listE $ litE . StringL . show . makeName thOptions <$> 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 =
src/Data/Registry/Aeson/TH/TH.hs view
@@ -1,3 +1,4 @@+{-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE PartialTypeSignatures #-} {-# OPTIONS_GHC -Wno-type-defaults #-} {-# OPTIONS_GHC -fno-warn-partial-type-signatures #-}@@ -10,7 +11,7 @@  import Control.Monad.Fail import Data.List (elemIndex)-import qualified Data.Text as T+import Data.Registry.Aeson.TH.ThOptions import Language.Haskell.TH import Language.Haskell.TH.Syntax import Protolude hiding (Type)@@ -47,18 +48,18 @@   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)))+makeName :: ThOptions -> Name -> Name+makeName options = mkName . toS . modifyTypeName options . show --- | 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+-- | Return the name of a given type with a modified name based on options+getSimpleTypeName :: ThOptions -> Type -> Name+getSimpleTypeName options (ForallT _ _ ty) = getSimpleTypeName options ty+getSimpleTypeName options (VarT name) = makeName options name+getSimpleTypeName options (ConT name) = makeName options name+getSimpleTypeName options (TupleT n) = makeName options $ tupleTypeName n+getSimpleTypeName options ArrowT = makeName options ''(->)+getSimpleTypeName options ListT = makeName options ''[]+getSimpleTypeName options (AppT t1 t2) = mkName (show (getSimpleTypeName options t1) <> " " <> show (getSimpleTypeName options t2))+getSimpleTypeName options (SigT t _) = getSimpleTypeName options t+getSimpleTypeName options (UnboxedTupleT n) = makeName options $ unboxedTupleTypeName n+getSimpleTypeName _ t = panic $ "getSimpleTypeName: Unknown type: " <> show t
+ src/Data/Registry/Aeson/TH/ThOptions.hs view
@@ -0,0 +1,38 @@+module Data.Registry.Aeson.TH.ThOptions where++import Data.Text qualified as T+import Protolude++-- | Options used to adjust the creation of encoders/decoders+newtype ThOptions = ThOptions+  { modifyTypeName :: Text -> Text+  }++-- | Default options for adjusting the creation of Encoders/Decoders+defaultThOptions :: ThOptions+defaultThOptions = ThOptions dropQualifier++-- | Drop the leading names in a qualified name++---  dropQualifier "x.y.z" === "z"+dropQualifier :: Text -> Text+dropQualifier t = fromMaybe t . lastMay $ T.splitOn "." t++-- | This function does not modify type names+qualified :: Text -> Text+qualified = identity++-- | Provide a specific qualifier to produce type names++---  qualifyAs "a" "x.y.z" === "a.z"+qualifyAs :: Text -> Text -> Text+qualifyAs qualifier t = qualifier <> "." <> dropQualifier t++-- | Keep the last name as the qualifier++---  qualifyWithLastName "x.y.z" === "y.z"+qualifyWithLastName :: Text -> Text+qualifyWithLastName t =+  case reverse $ T.splitOn "." t of+    t1 : t2 : _ -> t2 <> "." <> t1+    _ -> t
test/Test/Data/Registry/Aeson/DataTypes.hs view
@@ -8,13 +8,16 @@ import Protolude  newtype Identifier = Identifier Int-  deriving (Eq, Show, Generic, ToJSON, FromJSON)+  deriving stock (Eq, Show, Generic)+  deriving anyclass (ToJSON, FromJSON)  newtype Email = Email {_email :: Text}-  deriving (Eq, Show, Generic, ToJSON, FromJSON)+  deriving stock (Eq, Show, Generic)+  deriving anyclass (ToJSON, FromJSON)  newtype DateTime = DateTime {_datetime :: UTCTime}-  deriving (Eq, Show, Generic, ToJSON, FromJSON)+  deriving stock (Eq, Show, Generic)+  deriving anyclass (ToJSON, FromJSON)  data Person = Person {identifier :: Identifier, email :: Email}   deriving (Eq, Show, Generic, ToJSON, FromJSON)
test/Test/Data/Registry/Aeson/DecoderSpec.hs view
@@ -14,6 +14,7 @@ import Data.Time import Protolude import Test.Data.Registry.Aeson.DataTypes+import Test.Data.Registry.Aeson.SimilarDataTypes qualified as SimilarDataTypes import Test.Tasty.Hedgehogx hiding (either, maybe, text)  test_decode = test "decode" $ do@@ -158,6 +159,11 @@   $(makeDecoder ''Delivery)     <: $(makeDecoder ''Team)     <: decodeListOf @Person+    -- test that it is possible to generate a Decoder when there are name clashes+    <: $(makeDecoderQualifiedLast ''SimilarDataTypes.Person)+    <: $(makeDecoderQualifiedLast ''SimilarDataTypes.Email)+    <: $(makeDecoderQualifiedLast ''SimilarDataTypes.Identifier)+    <: $(makeDecoderQualifiedLast ''SimilarDataTypes.DateTime)     <: $(makeDecoder ''Person)     <: $(makeDecoder ''Email)     <: $(makeDecoder ''Identifier)
test/Test/Data/Registry/Aeson/EncoderSpec.hs view
@@ -17,6 +17,7 @@ import Data.Time import Protolude import Test.Data.Registry.Aeson.DataTypes+import Test.Data.Registry.Aeson.SimilarDataTypes qualified as SimilarDataTypes import Test.Tasty.Hedgehogx hiding (string)  test_encode = test "encode" $ do@@ -61,6 +62,11 @@ encoders :: Registry _ _ encoders =   $(makeEncoder ''Delivery)+      -- test that it is possible to generate an Encoder when there are name clashes+    <: $(makeEncoderQualifiedLast ''SimilarDataTypes.Person)+    <: $(makeEncoderQualifiedLast ''SimilarDataTypes.Email)+    <: $(makeEncoderQualifiedLast ''SimilarDataTypes.Identifier)+    <: $(makeEncoderQualifiedLast ''SimilarDataTypes.DateTime)     <: $(makeEncoder ''Person)     <: $(makeEncoder ''Email)     <: $(makeEncoder ''Identifier)@@ -74,6 +80,7 @@     <: $(makeEncoder ''ObjectWithSingleFieldSumEncoding)     <: $(makeEncoder ''TwoElemArraySumEncoding)     <: fun datetimeEncoder+    <: fun utcTimeEncoder     <: encodeMaybeOf @Int     <: jsonEncoder @Text     <: jsonEncoder @Int@@ -84,6 +91,10 @@ datetimeEncoder = fromValue $ \(DateTime dt) -> do   let formatted = toS $ formatTime defaultTimeLocale "%Y-%m-%dT%H:%M:%SZ" dt   Object [("_datetime", String formatted)]++-- | This Encoder for DateTime reproduces the default generic one+utcTimeEncoder :: Encoder UTCTime+utcTimeEncoder = fromValue $ String . toS . formatTime defaultTimeLocale "%Y-%m-%dT%H:%M:%SZ"  -- | Check that the encoding performed with the registry and the one performed with a Generic instance are the same --   This helps validating the encoding algorithm based on the various Options values
test/Test/Data/Registry/Aeson/RoundtripData.hs view
@@ -1,5 +1,6 @@ {-# LANGUAGE DeriveAnyClass #-} {-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE PartialTypeSignatures #-} {-# OPTIONS_GHC -fno-warn-partial-type-signatures #-} @@ -10,13 +11,16 @@ import Prelude (String)  data T0 = T0-  deriving (Eq, Show, Generic, FromJSON, ToJSON)+  deriving stock (Eq, Show, Generic)+  deriving (FromJSON, ToJSON)  newtype T1 = T1 Int-  deriving (Eq, Show, Generic, FromJSON, ToJSON)+  deriving stock (Eq, Show, Generic)+  deriving newtype (FromJSON, ToJSON)  newtype T2 = T2 {t2Int :: Int}-  deriving (Eq, Show, Generic, FromJSON, ToJSON)+  deriving stock (Eq, Show, Generic)+  deriving newtype (FromJSON, ToJSON)  data T3 = T3 Int   deriving (Eq, Show, Generic, FromJSON, ToJSON)
+ test/Test/Data/Registry/Aeson/SimilarDataTypes.hs view
@@ -0,0 +1,176 @@+{-# LANGUAGE DeriveAnyClass #-}+{-# LANGUAGE DeriveGeneric #-}++-- | This modules helps testing that we can use TemplateHaskell when there are name clashes+module Test.Data.Registry.Aeson.SimilarDataTypes where++import Data.Aeson+import Data.Time+import Protolude++newtype Identifier = Identifier Int+  deriving stock (Eq, Show, Generic)+  deriving anyclass (ToJSON, FromJSON)++newtype Email = Email {_email :: Text}+  deriving stock (Eq, Show, Generic)+  deriving anyclass (ToJSON, FromJSON)++newtype DateTime = DateTime {_datetime :: UTCTime}+  deriving stock (Eq, Show, Generic)+  deriving anyclass (ToJSON, FromJSON)++data Person = Person {identifier :: Identifier, email :: Email}+  deriving (Eq, Show, Generic, ToJSON, FromJSON)++data Team = Team {name :: Text, members :: [Person], leaderName :: Maybe Text}+  deriving (Eq, Show, Generic, ToJSON, FromJSON)++data Delivery+  = NoDelivery+  | ByEmail Email+  | InPerson Person DateTime+  deriving (Eq, Show, Generic, ToJSON, FromJSON)++data Path+  = File Int+  | Directory [Path]+  deriving (Eq, Show, Generic, ToJSON, FromJSON)++-- * EXAMPLES++email1 :: Email+email1 = Email "me@here.com"++person1 :: Person+person1 = Person (Identifier 123) email1++delivery0 :: Delivery+delivery0 = NoDelivery++delivery1 :: Delivery+delivery1 = ByEmail email1++delivery2 :: Delivery+delivery2 = InPerson person1 datetime1++datetime1 :: DateTime+datetime1 = DateTime $ UTCTime (fromGregorian 2022 4 18) 12++path1 :: Path+path1 = Directory [Directory [Directory [File 1], Directory [File 2]], File 3]++-- * OPTIONS TEST++allNullaryOptions = defaultOptions {allNullaryToStringTag = True}++data AllNullary+  = AllNullary1+  | AllNullary2+  deriving (Eq, Show, Generic)++instance ToJSON AllNullary where+  toJSON = genericToJSON allNullaryOptions++instance FromJSON AllNullary where+  parseJSON = genericParseJSON allNullaryOptions++fieldLabelModifierOptions = defaultOptions {fieldLabelModifier = ("__" <>)}++data FieldLabelModifier+  = FieldLabelModifier1 {field1 :: Int}+  | FieldLabelModifier2 {field2 :: Int}+  deriving (Eq, Show, Generic)++instance ToJSON FieldLabelModifier where+  toJSON = genericToJSON fieldLabelModifierOptions++instance FromJSON FieldLabelModifier where+  parseJSON = genericParseJSON fieldLabelModifierOptions++constructorTagModifierOptions = defaultOptions {constructorTagModifier = ("__" <>)}++data ConstructorTagModifier+  = ConstructorTagModifier1 {ctField1 :: Int}+  | ConstructorTagModifier2 {ctField2 :: Int}+  deriving (Eq, Show, Generic)++instance ToJSON ConstructorTagModifier where+  toJSON = genericToJSON constructorTagModifierOptions++instance FromJSON ConstructorTagModifier where+  parseJSON = genericParseJSON constructorTagModifierOptions++omitNothingFieldsOptions = defaultOptions {omitNothingFields = True}++data OmitNothingFields+  = OmitNothingFields1 {onField1 :: Maybe Int, onField2 :: Int}+  | OmitNothingFields2 {onField3 :: Int}+  deriving (Eq, Show, Generic)++instance ToJSON OmitNothingFields where+  toJSON = genericToJSON omitNothingFieldsOptions++instance FromJSON OmitNothingFields where+  parseJSON = genericParseJSON omitNothingFieldsOptions++unwrapUnaryRecordsOptions = defaultOptions {unwrapUnaryRecords = True}++newtype UnwrapUnaryRecords = UnwrapUnaryRecords1 {uuField1 :: Int}+  deriving (Eq, Show, Generic)++instance ToJSON UnwrapUnaryRecords where+  toJSON = genericToJSON unwrapUnaryRecordsOptions++instance FromJSON UnwrapUnaryRecords where+  parseJSON = genericParseJSON unwrapUnaryRecordsOptions++tagSingleConstructorsOptions = defaultOptions {tagSingleConstructors = True}++newtype TagSingleConstructors = TagSingleConstructors1 {tsField1 :: Int}+  deriving (Eq, Show, Generic)++instance ToJSON TagSingleConstructors where+  toJSON = genericToJSON tagSingleConstructorsOptions++instance FromJSON TagSingleConstructors where+  parseJSON = genericParseJSON tagSingleConstructorsOptions++untaggedValueOptions = defaultOptions {sumEncoding = UntaggedValue}++data UntaggedValueSumEncoding+  = UntaggedValueSumEncoding1 {uvField1 :: Int}+  | UntaggedValueSumEncoding2 {uvField2 :: Int}+  deriving (Eq, Show, Generic)++instance ToJSON UntaggedValueSumEncoding where+  toJSON = genericToJSON untaggedValueOptions++instance FromJSON UntaggedValueSumEncoding where+  parseJSON = genericParseJSON untaggedValueOptions++objectWithSingleFieldSumEncodingOptions = defaultOptions {sumEncoding = ObjectWithSingleField}++data ObjectWithSingleFieldSumEncoding+  = ObjectWithSingleFieldSumEncoding1 {owsfField1 :: Int}+  | ObjectWithSingleFieldSumEncoding2 {owsfField2 :: Int}+  deriving (Eq, Show, Generic)++instance ToJSON ObjectWithSingleFieldSumEncoding where+  toJSON = genericToJSON objectWithSingleFieldSumEncodingOptions++instance FromJSON ObjectWithSingleFieldSumEncoding where+  parseJSON = genericParseJSON objectWithSingleFieldSumEncodingOptions++twoElemArraySumEncodingOptions = defaultOptions {sumEncoding = TwoElemArray}++data TwoElemArraySumEncoding+  = TwoElemArraySumEncoding1 {teaField1 :: Int}+  | TwoElemArraySumEncoding2 {teaField2 :: Int}+  deriving (Eq, Show, Generic)++instance ToJSON TwoElemArraySumEncoding where+  toJSON = genericToJSON twoElemArraySumEncodingOptions++instance FromJSON TwoElemArraySumEncoding where+  parseJSON = genericParseJSON twoElemArraySumEncodingOptions