packages feed

aeson-schema 0.3.0.7 → 0.4.0.0

raw patch · 6 files changed

+142/−60 lines, 6 filesdep ~sybnew-uploaderPVP ok

version bump matches the API change (PVP)

Dependency ranges changed: syb

API changes (from Hackage documentation)

- Data.Aeson.Schema.CodeGenM: instance MonadReader s (CodeGenM s)
+ Data.Aeson.Schema.CodeGenM: Options :: [String] -> [Name] -> Map String String -> [Text] -> [Text] -> (Name -> [DecQ]) -> Options
+ Data.Aeson.Schema.CodeGenM: _derivingTypeclasses :: Options -> [Name]
+ Data.Aeson.Schema.CodeGenM: _extraInstances :: Options -> Name -> [DecQ]
+ Data.Aeson.Schema.CodeGenM: _extraModules :: Options -> [String]
+ Data.Aeson.Schema.CodeGenM: _ghcOptsPragmas :: Options -> [Text]
+ Data.Aeson.Schema.CodeGenM: _languageExtensions :: Options -> [Text]
+ Data.Aeson.Schema.CodeGenM: _replaceModules :: Options -> Map String String
+ Data.Aeson.Schema.CodeGenM: askEnv :: CodeGenM s s
+ Data.Aeson.Schema.CodeGenM: askOpts :: CodeGenM s Options
+ Data.Aeson.Schema.CodeGenM: data Options
+ Data.Aeson.Schema.CodeGenM: defaultOptions :: Options
+ Data.Aeson.Schema.CodeGenM: instance MonadReader (Options, s) (CodeGenM s)
- Data.Aeson.Schema.CodeGen: generate :: Graph Schema Text -> Q (Code, Map Text Name)
+ Data.Aeson.Schema.CodeGen: generate :: Graph Schema Text -> Options -> Q (Code, Map Text Name)
- Data.Aeson.Schema.CodeGen: generateModule :: Text -> Graph Schema Text -> Q (Text, Map Text Name)
+ Data.Aeson.Schema.CodeGen: generateModule :: Text -> Graph Schema Text -> Options -> Q (Text, Map Text Name)
- Data.Aeson.Schema.CodeGen: generateTH :: Graph Schema Text -> Q ([Dec], Map Text Name)
+ Data.Aeson.Schema.CodeGen: generateTH :: Graph Schema Text -> Options -> Q ([Dec], Map Text Name)
- Data.Aeson.Schema.CodeGenM: CodeGenM :: RWST s Code StringSet Q a -> CodeGenM s a
+ Data.Aeson.Schema.CodeGenM: CodeGenM :: RWST (Options, s) Code StringSet Q a -> CodeGenM s a
- Data.Aeson.Schema.CodeGenM: unCodeGenM :: CodeGenM s a -> RWST s Code StringSet Q a
+ Data.Aeson.Schema.CodeGenM: unCodeGenM :: CodeGenM s a -> RWST (Options, s) Code StringSet Q a
- Data.Aeson.Schema.Helpers: replaceHiddenModules :: Data a => a -> a
+ Data.Aeson.Schema.Helpers: replaceHiddenModules :: Data a => a -> Map String String -> a

Files

CHANGELOG.md view
@@ -1,3 +1,8 @@+# 0.4.0.0++- Add Data.Aeson.Schema.CodeGenM.Options type which allows+  customisation of codegen output+ # 0.3.0.7 - 2015-07-15  - Bump upper bounds: Allow vector-0.11
aeson-schema.cabal view
@@ -1,12 +1,14 @@ name:                aeson-schema-version:             0.3.0.7+version:             0.4.0.0 synopsis:            Haskell JSON schema validator and parser generator description:         This library provides validation of JSON values against schemata. Given a schema, it can also produce data types corresponding to the schema and a parser.-homepage:            https://github.com/timjb/aeson-schema+homepage:            https://github.com/Fuuzetsu/aeson-schema license:             MIT license-file:        LICENSE-author:              Tim Baumann <tim@timbaumann.info>-maintainer:          Tim Baumann <tim@timbaumann.info>+author:              Tim Baumann, Mateusz Kowalczyk+maintainer:          Mateusz Kowalczyk <fuuzetsu@fuuzetsu.co.uk>+copyright:           (c) 2012-2015 Tim Baumann+                     (c) 2015 Mateusz Kowalczyk category:            Data build-type:          Simple cabal-version:       >= 1.8@@ -17,7 +19,7 @@  source-repository head   type:                git-  location:            git://github.com/timjb/aeson-schema.git+  location:            git://github.com/Fuuzetsu/aeson-schema.git  library   ghc-options:         -Wall
src/Data/Aeson/Schema/CodeGen.hs view
@@ -15,7 +15,7 @@                                               (<|>)) import           Control.Arrow               (first, second) import           Control.Monad               (forM_, unless, when, zipWithM)-import           Control.Monad.RWS.Lazy      (MonadReader (..), +import           Control.Monad.RWS.Lazy      (MonadReader (..),                                               MonadWriter (..), evalRWST) import           Data.Aeson import           Data.Aeson.Types            (parse)@@ -51,51 +51,48 @@ instance (Lift k, Lift v) => Lift (M.Map k v) where   lift m = [| M.fromList $(lift $ M.toList m) |] --- | Needed modules that are not found by "getUsedModules".-extraModules :: [String]-extraModules =-  [ "Text.Regex" -- provides RegexMaker instances-  , "Text.Regex.PCRE.String" -- provides RegexLike instances, Regex type-  , "Data.Aeson.Types" -- Parser type-  , "Data.Ratio"-  ]- -- | Extracts all TH declarations getDecs :: Code -> [Dec] getDecs code = [ dec | Declaration dec _ <- code ]  -- | Generate data-types and FromJSON instances for all schemas generateTH :: Graph Schema Text -- ^ Set of schemas+           -> Options            -> Q ([Dec], M.Map Text Name) -- ^ Generated code and mapping from schema identifiers to type names-generateTH = fmap (first getDecs) . generate+generateTH g = fmap (first getDecs) . generate g  -- | Generated a self-contained module that parses and validates values of -- a set of given schemas. generateModule :: Text -- ^ Name of the generated module                -> Graph Schema Text -- ^ Set of schemas+               -> Options                -> Q (Text, M.Map Text Name) -- ^ Module code and mapping from schema identifiers to type names-generateModule modName = fmap (first $ renderCode . map rewrite) . generate+generateModule modName g opts = fmap (first $ renderCode . map rewrite) $ generate g opts   where     renderCode :: Code -> Text-    renderCode code = T.intercalate "\n\n" $ [modDec, T.intercalate "\n" imprts] ++ map renderDeclaration code+    renderCode code = T.intercalate "\n\n" $ [langExts <> ghcOpts, modDec, T.intercalate "\n" imprts] ++ map renderDeclaration code       where-        mods = sort $ extraModules ++ getUsedModules (getDecs code)+        mods = sort $ _extraModules opts ++ getUsedModules (getDecs code)         imprts = map (\m -> "import " <> pack m) mods         modDec = "module " <> modName <> " where"+        -- TH has no support for file-header pragmas so we splice the text in here+        mkHeaderPragmas t = T.intercalate "\n" . map (\s -> T.unwords ["{-#", t, s, "#-}"])+        langExts = mkHeaderPragmas "LANGUAGE" $ _languageExtensions opts+        ghcOpts = mkHeaderPragmas "OPTIONS_GHC" $ _ghcOptsPragmas opts     rewrite :: Declaration -> Declaration-    rewrite (Declaration dec text) = Declaration (replaceHiddenModules $ cleanPatterns dec) text+    rewrite (Declaration dec text) = Declaration (replaceHiddenModules (cleanPatterns dec) (_replaceModules opts)) text     rewrite a = a  -- | Generate a generalized representation of the code in a Haskell module-generate :: Graph Schema Text -> Q (Code, M.Map Text Name)-generate graph = swap <$> evalRWST (unCodeGenM $ generateTopLevel graph >> return typeMap) typeMap used+generate :: Graph Schema Text -> Options -> Q (Code, M.Map Text Name)+generate graph opts = swap <$> evalRWST (unCodeGenM $ generateTopLevel graph >> return typeMap) (opts, typeMap) used   where     (used, typeMap) = second M.fromList $ mapAccumL nameAccum HS.empty (M.keys graph)     nameAccum usedNames schemaName = second (schemaName,) $ swap $ codeGenNewName (firstUpper $ unpack schemaName) usedNames  generateTopLevel :: Graph Schema Text -> CodeGenM SchemaTypes () generateTopLevel graph = do-  typeMap <- ask+  (opts, typeMap) <- ask   graphN <- qNewName "graph"   when (nameBase graphN /= "graph") $ fail "name graph is already taken"   graphDecType <- runQ $ sigD graphN [t| Graph Schema Text |]@@ -106,7 +103,7 @@     ((typeQ, fromJsonQ, toJsonQ), defNewtype) <- generateSchema (Just typeName) name schema     when defNewtype $ do       let newtypeCon = normalC typeName [strictType notStrict typeQ]-      newtypeDec <- runQ $ newtypeD (cxt []) typeName [] newtypeCon derivingTypeclasses+      newtypeDec <- runQ $ newtypeD (cxt []) typeName [] newtypeCon (_derivingTypeclasses opts)       fromJSONInst <- runQ $ instanceD (cxt []) (conT ''FromJSON `appT` conT typeName)         [ valD (varP $ mkName "parseJSON") (normalB [| fmap $(conE typeName) . $fromJsonQ |]) []         ]@@ -126,7 +123,7 @@                -> Schema Text                -> CodeGenM SchemaTypes ((TypeQ, ExpQ, ExpQ), Bool) -- ^ ((type of the generated representation (a), function :: Value -> Parser a), whether a newtype wrapper is necessary) generateSchema decName name schema = case schemaDRef schema of-  Just ref -> ask >>= \typesMap -> case M.lookup ref typesMap of+  Just ref -> askEnv >>= \typesMap -> case M.lookup ref typesMap of     Nothing -> fail "couldn't find referenced schema"     Just referencedSchema -> return ((conT referencedSchema, [| parseJSON |], [| toJSON |]), True)   Nothing -> first (\(typ,from,to) -> (typ,wrap from,to)) <$> case schemaType schema of@@ -198,14 +195,11 @@       then parser       else lamE [varP val] $ doE $ checkers ++ [noBindS $ parser `appE` varE val] -derivingTypeclasses :: [Name]-derivingTypeclasses = [''Eq, ''Show]- assertStmt :: ExpQ -> String -> StmtQ assertStmt expr err = noBindS [| unless $(expr) (fail err) |]  assertValidates :: ExpQ -> ExpQ -> StmtQ-assertValidates schema value = noBindS+assertValidates schema value = noBindS $ parensE   [| case validate $(varE $ mkName "graph") $schema $value of        [] -> return ()        es -> fail $ unlines es@@ -357,11 +351,14 @@                  , Nothing                  )       conName <- maybe (qNewName $ firstUpper $ unpack name) return decName+      tcs <- _derivingTypeclasses <$> askOpts+      rMods <- _replaceModules <$> askOpts+      userInstanceGen <- _extraInstances <$> askOpts       recordDeclaration <- runQ $ genRecord conName                                             (zip3 propertyNames-                                                  (map (fmap replaceHiddenModules) propertyTypes)+                                                  (map (fmap (`replaceHiddenModules` rMods)) propertyTypes)                                                   (map (schemaDescription . snd) propertiesList))-                                            derivingTypeclasses+                                            (map (`replaceHiddenModules` rMods) tcs)       let typ = conT conName       let parser = foldl (\oparser propertyParser -> [| $oparser <*> $propertyParser |]) [| pure $(conE conName) |] propertyParsers       fromJSONInst <- runQ $ instanceD (cxt []) (conT ''FromJSON `appT` typ)@@ -371,14 +368,17 @@             ]         ]       let paramNames = map (mkName . ("a" ++) . show) $ take (length propertyTos) ([1..] :: [Int])++      userInstances <- runQ . sequence $ userInstanceGen conName       toJSONInst <- runQ $ instanceD (cxt []) (conT ''ToJSON `appT` typ)         [ funD (mkName "toJSON") -- cannot use a qualified name here           [ clause [conP conName $ map varP paramNames] (normalB [| Object $ HM.fromList $ catMaybes $(listE $ zipWith3 (\fieldName to param -> [| (,) $(lift fieldName) <$> $to $(varE param) |]) (map fst propertiesList) propertyTos paramNames) |]) []           ]         ]-      tell-        [ recordDeclaration-        , Declaration fromJSONInst Nothing+      tell $+        [ recordDeclaration ]+        ++ map (flip Declaration Nothing) userInstances +++        [ Declaration fromJSONInst Nothing         , Declaration toJSONInst Nothing         ]       return ((typ, [| parseJSON |], [| toJSON |]), False)@@ -393,11 +393,14 @@     checkAdditionalProperties _ (Choice1of2 True) = [| return () |]     checkAdditionalProperties _ (Choice1of2 False) = [| fail "additional properties are not allowed" |]     checkAdditionalProperties value (Choice2of2 sch) = doE [assertValidates (lift sch) value]+    -- TODO Once https://ghc.haskell.org/trac/ghc/ticket/10734 is+    -- fixed, use a ‘let’ again for matchingPatterns and+    -- isAdditionalProperty     checkPatternAndAdditionalProperties patterns additional = noBindS       [| let items = HM.toList $(varE obj) in forM_ items $ \(pname, value) -> do-           let matchingPatterns = filter (flip PCRE.match (unpack pname) . patternCompiled . fst) $(lift patterns)+           matchingPatterns <- return (filter (flip PCRE.match (unpack pname) . patternCompiled . fst) $(lift patterns))            forM_ matchingPatterns $ \(_, sch) -> $(doE [assertValidates [| sch |] [| value |]])-           let isAdditionalProperty = null matchingPatterns && pname `notElem` $(lift $ map fst $ HM.toList $ schemaProperties schema)+           isAdditionalProperty <- return (null matchingPatterns && pname `notElem` $(lift $ map fst $ HM.toList $ schemaProperties schema))            when isAdditionalProperty $(checkAdditionalProperties [| value |] additional)       |]     additionalPropertiesAllowed (Choice1of2 True) = True
src/Data/Aeson/Schema/CodeGenM.hs view
@@ -1,6 +1,7 @@ {-# LANGUAGE DeriveDataTypeable         #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE TupleSections #-}+{-# LANGUAGE TemplateHaskell #-}  module Data.Aeson.Schema.CodeGenM   ( Declaration (..)@@ -9,6 +10,11 @@   , renderDeclaration   , codeGenNewName   , genRecord+  -- * Customising the codegen+  , Options(..)+  , defaultOptions+  , askOpts+  , askEnv   ) where  import           Control.Applicative        (Applicative (..), (<$>))@@ -19,6 +25,7 @@ import           Data.Data                  (Data, Typeable) import           Data.Function              (on) import qualified Data.HashSet               as HS+import qualified Data.Map                   as M import           Data.Monoid                ((<>), mconcat) import           Data.Text                  (Text, pack) import qualified Data.Text                  as T@@ -60,8 +67,78 @@ -- has a readonly map from schema identifiers to the names of the corresponding -- types in the generated code. newtype CodeGenM s a = CodeGenM-  { unCodeGenM :: RWST s Code StringSet Q a-  } deriving (Monad, Applicative, Functor, MonadReader s, MonadWriter Code, MonadState StringSet)+  { unCodeGenM :: RWST (Options, s) Code StringSet Q a+  } deriving (Monad, Applicative, Functor, MonadReader (Options, s), MonadWriter Code, MonadState StringSet)++-- | Extra options used for the codegen+data Options = Options+ { _extraModules :: [String]+  -- ^ Needed modules that are not found by 'getUsedModules'.+ , _derivingTypeclasses :: [Name]+  -- ^ Classes to put in the @deriving@ clause+ , _replaceModules :: M.Map String String+  -- ^ A 'M.Map' of modules which we should replace with other ones+  -- when references to them are found. Useful for example when the+  -- codegen is hitting a hidden module that's not already gotten rid+  -- of in 'Data.Aeson.Schema.Helpers.replaceHiddenModules'.+ , _languageExtensions :: [Text]+  -- ^ List of @LANGUAGE@ extensions to enable in the module. Note that+  -- these aren't checked for validity.+  --+  -- @'_languageExtensions' = [ "LambdaCase" ]@+ , _ghcOptsPragmas :: [Text]+  -- ^ List of @OPTIONS_GHC@ to turn on in the module. Note that these+  -- aren't checked for validity.+  --+  -- @'_ghcOptsPragmas' = [ "-fno-warn-name-shadowing" ]@+ , _extraInstances :: Name -> [DecQ]+  -- ^ Supplied a 'Name' of the type in question (after mangling),+  -- potentially generate an instance for the type. For example, to+  -- generate an empty 'Enum' instance for every data type we make,+  -- the user can supply something like+  --+  -- @+  -- _extraInstances = \n -> return $+  --   instanceD (cxt []) (conT ''Enum `appT` conT n) []+  -- @+  --+  -- and to generate no instances, simply use @'const' []@.+ }++defaultOptions :: Options+defaultOptions = Options+  { _extraModules = [ "Text.Regex" -- provides RegexMaker instances+                    , "Text.Regex.PCRE.String" -- provides RegexLike instances, Regex type+                    , "Data.Aeson.Types" -- Parser type+                    , "Data.Ratio"+                    ]+  , _derivingTypeclasses = [''Eq, ''Show]+  , _replaceModules = M.fromList+       [ ("Data.HashMap.Base", "Data.HashMap.Lazy")+       , ("Data.Aeson.Types.Class", "Data.Aeson")+       , ("Data.Aeson.Types.Internal", "Data.Aeson.Types")+         -- "Could not find module `GHC.Integer.Type'; it is a hidden+         -- module in the package `integer-gmp'"+       , ("GHC.Integer.Type", "Prelude")+       , ("GHC.Types", "Prelude")+       , ("GHC.Real", "Prelude")+       , ("Data.Text.Internal", "Data.Text")+       , ("Data.Map.Base", "Data.Map")+         -- Due to mistake in base 4.8.{0,1} releases+       , ("Data.OldList", "Prelude")+       , ("Data.Typeable.Internal", "Data.Typeable")+       , ("Data.Binary.Class", "Data.Binary")+       ]+  , _languageExtensions = []+  , _ghcOptsPragmas = []+  , _extraInstances = const []+  }++askOpts :: CodeGenM s Options+askOpts = fst <$> ask++askEnv :: CodeGenM s s+askEnv = snd <$> ask  instance Quasi (CodeGenM s) where   qNewName = state . codeGenNewName
src/Data/Aeson/Schema/Helpers.hs view
@@ -11,6 +11,7 @@ import           Control.Monad          (join) import           Data.Generics          (Data, everything, everywhere, mkQ, mkT) import           Data.List              (nub)+import qualified Data.Map               as M import           Data.Maybe             (maybeToList) import           Data.Scientific        (Scientific, coefficient, base10Exponent) import           Data.Text              (Text, unpack)@@ -76,25 +77,17 @@ -- some code in a module but not when we use the TH code for pretty-printing. replaceHiddenModules :: Data a                      => a -- ^ Dec or Exp+                     -> M.Map String String+                     -- ^ Extra replacements, supplied by 'CodeGenM'                      -> a-replaceHiddenModules = everywhere $ mkT replaceModule+replaceHiddenModules d replaceMap = everywhere (mkT replaceModule) d   where-    replacements =-      [ ("Data.HashMap.Base", "Data.HashMap.Lazy")-      , ("Data.Aeson.Types.Class", "Data.Aeson")-      , ("Data.Aeson.Types.Internal", "Data.Aeson.Types")-      , ("GHC.Integer.Type", "Prelude") -- "Could not find module `GHC.Integer.Type'; it is a hidden module in the package `integer-gmp'"-      , ("GHC.Types", "Prelude")-      , ("GHC.Real", "Prelude")-      , ("Data.Text.Internal", "Data.Text")-      , ("Data.Map.Base", "Data.Map")-      ]     replaceModule :: Name -> Name     replaceModule n = case nameModule n of       Just "Data.Aeson.Types.Internal" | nameBase n `elem` ["I", "D"] ->         mkName $ "Data.Attoparsec.Number." ++ nameBase n       Just "GHC.Tuple" -> mkName $ nameBase n-      Just m -> case lookup m replacements of+      Just m -> case M.lookup m replaceMap of         Just r -> mkName $ r ++ ('.' : nameBase n)         Nothing -> n       _ -> n
test/Data/Aeson/Schema/CodeGen/Tests.hs view
@@ -50,6 +50,7 @@ import           Data.Aeson.Schema import           Data.Aeson.Schema.Choice import           Data.Aeson.Schema.CodeGen            (generateModule)+import           Data.Aeson.Schema.CodeGenM           (Options(..), defaultOptions) import           Data.Aeson.Schema.Helpers            (formatValidators,                                                        getUsedModules,                                                        replaceHiddenModules)@@ -196,7 +197,7 @@  data ForkLift = ForkLift (Chan (Hint.Interpreter (), Hint.InterpreterError -> IO ())) -getSandboxPackageDB :: IO (Maybe FilePath) +getSandboxPackageDB :: IO (Maybe FilePath) getSandboxPackageDB = do     cwd <- DIR.getCurrentDirectory     contents <- readFile $ cwd <> [FP.pathSeparator] <> "cabal.sandbox.config"@@ -204,7 +205,7 @@     where         packagedb = "package-db: "         maybePackageDB :: [FilePath] -> Maybe FilePath-        maybePackageDB list = case list of +        maybePackageDB list = case list of                                    [path] -> Just $ T.unpack $ T.replace packagedb "" (T.pack path)                                    _ -> Nothing         findPackageDB contents = maybePackageDB $ filter (\l -> T.isPrefixOf packagedb (T.pack l)) (lines contents)@@ -213,7 +214,7 @@ getInterpreterArgs = do     mdb <- getSandboxPackageDB     return $ case mdb of-                Just path -> ["-no-user-package-db", "-package-db " <> path]  +                Just path -> ["-no-user-package-db", "-package-db " <> path]                 Nothing -> []  -- | uses the Forklift pattern (http://apfelmus.nfshost.com/blog/2012/06/07-forklift.html)@@ -223,7 +224,7 @@   cmdChan <- newChan   _ <- forkIO $ do     errorHandler <- newEmptyMVar-    args <- getInterpreterArgs +    args <- getInterpreterArgs      forever $ do       Left err <- Hint.unsafeRunInterpreterWithArgs args $ do@@ -258,7 +259,7 @@             , schemaItems = Just $ Choice2of2 [empty { schemaType = [Choice1of2 NumberType] }]             }           graph = M.singleton "A" schema-        (code, _) <- runQ $ generateModule "TestOneTuple" graph+        (code, _) <- runQ $ generateModule "TestOneTuple" graph defaultOptions         result <- typecheck code forkLift         case result of           Left err -> HU.assertFailure $ show err@@ -270,7 +271,7 @@               "additionalProperties": { "type": "number" }             } |]           graph = M.singleton "A" schema-        (code, _) <- runQ $ generateModule "SimpleMap" graph+        (code, _) <- runQ $ generateModule "SimpleMap" graph defaultOptions         result <- typecheck code forkLift         case result of           Left err -> HU.assertFailure $ show err@@ -280,7 +281,7 @@ typecheckGenerate :: ForkLift -> Schema Text -> Property typecheckGenerate forkLift schema = ioProperty $ do   let graph = M.singleton "A" schema-  (code, _) <- runQ $ generateModule "CustomSchema" graph+  (code, _) <- runQ $ generateModule "CustomSchema" graph defaultOptions   eitherToResult <$> typecheck code forkLift  withCodeTempFile :: Text -> (FilePath -> IO a) -> IO a@@ -328,9 +329,10 @@ assertValidates :: Bool -> ForkLift -> Graph Schema Text -> Schema Text -> Value -> HU.Assertion assertValidates shouldBeValid forkLift graph schema value = do   let graph' = if M.null graph then M.singleton "a" schema else graph-  (code, typeMap) <- runQ $ generateModule "TestSchema" graph'-  valueExpr <- replaceHiddenModules <$> runQ (THS.lift value)-  let typ = replaceHiddenModules $ typeMap M.! "a"+      repMap = _replaceModules defaultOptions+  (code, typeMap) <- runQ $ generateModule "TestSchema" graph' defaultOptions+  valueExpr <- flip replaceHiddenModules repMap <$> runQ (THS.lift value)+  let typ = flip replaceHiddenModules repMap $ typeMap M.! "a"   let validatesExpr = unlines         [ "case DAT.parseEither parseJSON (" ++ pprint valueExpr ++ ") :: Either String (" ++ pprint typ ++ ") of"         , "  Prelude.Left e  -> Just e"