packages feed

hjsonschema 0.7.1.0 → 0.8.0.0

raw patch · 15 files changed

+311/−204 lines, 15 filesdep ~HUnitdep ~aesondep ~directoryPVP ok

version bump matches the API change (PVP)

Dependency ranges changed: HUnit, aeson, directory, filepath, http-types, test-framework, test-framework-hunit, unordered-containers

API changes (from Hackage documentation)

- Data.JsonSchema: [_rsObject] :: RawSchema -> HashMap Text Value
- Data.JsonSchema: type Graph = HashMap Text (HashMap Text Value)
- Data.JsonSchema.Reference: combineIdAndRef :: Text -> Text -> Text
- Data.JsonSchema.Reference: combineIds :: Text -> Text -> Text
- Data.JsonSchema.Reference: refAndPointer :: Text -> Maybe (Text, Text)
+ Data.JsonSchema: SchemaGraph :: !RawSchema -> !SchemaCache -> SchemaGraph
+ Data.JsonSchema: [_cachedSchemas] :: SchemaGraph -> !SchemaCache
+ Data.JsonSchema: [_rsData] :: RawSchema -> !(HashMap Text Value)
+ Data.JsonSchema: [_startingSchema] :: SchemaGraph -> !RawSchema
+ Data.JsonSchema: data SchemaGraph
+ Data.JsonSchema: type SchemaCache = HashMap Text (HashMap Text Value)
+ Data.JsonSchema.Draft4.Arrays: AdditionalBool :: AdditionalItemsFailure err
+ Data.JsonSchema.Draft4.Arrays: AdditionalObject :: err -> AdditionalItemsFailure err
+ Data.JsonSchema.Draft4.Arrays: data AdditionalItemsFailure err
+ Data.JsonSchema.Reference: baseAndFragment :: Text -> URIBaseAndFragment
+ Data.JsonSchema.Reference: isRemoteReference :: Text -> Bool
+ Data.JsonSchema.Reference: resolveFragment :: Maybe Text -> HashMap Text Value -> HashMap Text Value
+ Data.JsonSchema.Reference: resolveReference :: URIBase -> Text -> URIBaseAndFragment
+ Data.JsonSchema.Reference: resolveScopeAgainst :: Maybe Text -> Text -> Text
+ Data.JsonSchema.Reference: type URIBase = Maybe Text
+ Data.JsonSchema.Reference: type URIBaseAndFragment = (Maybe Text, Maybe Text)
- Data.JsonSchema: FailureInfo :: Value -> Value -> FailureInfo
+ Data.JsonSchema: FailureInfo :: !Value -> !Value -> FailureInfo
- Data.JsonSchema: RawSchema :: Text -> HashMap Text Value -> RawSchema
+ Data.JsonSchema: RawSchema :: !(Maybe Text) -> !(HashMap Text Value) -> RawSchema
- Data.JsonSchema: ValidationFailure :: err -> FailureInfo -> ValidationFailure err
+ Data.JsonSchema: ValidationFailure :: !err -> !FailureInfo -> ValidationFailure err
- Data.JsonSchema: [_failureInfo] :: ValidationFailure err -> FailureInfo
+ Data.JsonSchema: [_failureInfo] :: ValidationFailure err -> !FailureInfo
- Data.JsonSchema: [_failureName] :: ValidationFailure err -> err
+ Data.JsonSchema: [_failureName] :: ValidationFailure err -> !err
- Data.JsonSchema: [_offendingData] :: FailureInfo -> Value
+ Data.JsonSchema: [_offendingData] :: FailureInfo -> !Value
- Data.JsonSchema: [_rsURI] :: RawSchema -> Text
+ Data.JsonSchema: [_rsURI] :: RawSchema -> !(Maybe Text)
- Data.JsonSchema: [_validatingData] :: FailureInfo -> Value
+ Data.JsonSchema: [_validatingData] :: FailureInfo -> !Value
- Data.JsonSchema: compile :: Spec err -> Graph -> RawSchema -> Schema err
+ Data.JsonSchema: compile :: Spec err -> SchemaGraph -> RawSchema -> Schema err
- Data.JsonSchema: compileDraft4 :: Graph -> RawSchema -> Either [ValidationFailure Draft4Failure] (Schema Draft4Failure)
+ Data.JsonSchema: compileDraft4 :: SchemaGraph -> RawSchema -> Either [ValidationFailure Draft4Failure] (Schema Draft4Failure)
- Data.JsonSchema: fetchReferencedSchemas :: Spec err -> RawSchema -> Graph -> IO (Either Text Graph)
+ Data.JsonSchema: fetchReferencedSchemas :: Spec err -> SchemaCache -> RawSchema -> IO (Either Text SchemaGraph)
- Data.JsonSchema: fetchReferencedSchemas' :: (MonadIO m, Functor m, MonadError t e, Traversable e, IsString t) => (Text -> m (e ByteString)) -> Spec err -> RawSchema -> Graph -> m (e Graph)
+ Data.JsonSchema: fetchReferencedSchemas' :: (MonadIO m, Functor m, MonadError t e, Traversable e, IsString t) => (Text -> m (e ByteString)) -> Spec err -> SchemaCache -> RawSchema -> m (e SchemaGraph)
- Data.JsonSchema: type EmbeddedSchemas = Text -> Value -> Vector RawSchema
+ Data.JsonSchema: type EmbeddedSchemas = Maybe Text -> Value -> [RawSchema]
- Data.JsonSchema: type ValidatorConstructor schemaErr valErr = Spec schemaErr -> Graph -> RawSchema -> Value -> Maybe (Value -> valErr)
+ Data.JsonSchema: type ValidatorConstructor schemaErr valErr = Spec schemaErr -> SchemaGraph -> RawSchema -> Value -> Maybe (Value -> valErr)
- Data.JsonSchema.Draft4.Arrays: additionalItems :: ValidatorConstructor err [ValidationFailure (ItemsFailure err)]
+ Data.JsonSchema.Draft4.Arrays: additionalItems :: ValidatorConstructor err [ValidationFailure (AdditionalItemsFailure err)]
- Data.JsonSchema.Draft4.Objects: patternUnmatched :: Spec err -> Graph -> RawSchema -> Value -> Maybe (Value -> ([ValidationFailure err], Value))
+ Data.JsonSchema.Draft4.Objects: patternUnmatched :: Spec err -> SchemaGraph -> RawSchema -> Value -> Maybe (Value -> ([ValidationFailure err], Value))
- Data.JsonSchema.Reference: newResolutionScope :: Text -> HashMap Text Value -> Text
+ Data.JsonSchema.Reference: newResolutionScope :: URIBase -> HashMap Text Value -> URIBase

Files

Example.hs view
@@ -13,14 +13,24 @@  import qualified Data.JsonSchema as JS --- Data.JsonSchema isn't designed to be imported qualified,--- but we're doing it here to make it obvious what's coming--- from the lib.+-- Data.JsonSchema is designed to be imported unqualified.+-- We're just being explicit here for example purposes. +main :: IO ()+main = do+  simpleExample+  fullExample++--------------------------------------------------+-- * Example Schema and Data+--------------------------------------------------+ schemaData :: JS.RawSchema schemaData = JS.RawSchema-  { JS._rsURI    = ""-  , JS._rsObject = schemaJSON+  { JS._rsURI  = Nothing+  -- ^ If your schema has relative references to other schemas+  -- then you'll need to include this.+  , JS._rsData = schemaJSON   }   where     schemaJSON :: HashMap Text Value@@ -29,47 +39,65 @@ badData :: Value badData = Array $ V.fromList ["foo", "foo"] -main :: IO ()-main = do+--------------------------------------------------+-- * Simple Example+-------------------------------------------------- +-- | Since our schema contains no references to other schemas,+-- we can skip the fetchGraph step.+simpleExample :: IO ()+simpleExample = do+  schema <- compileSchema (JS.SchemaGraph schemaData H.empty) schemaData+  checkResults (JS.validate schema badData)++--------------------------------------------------+-- * Full Example+--------------------------------------------------++-- | For a schema that might contain references to other schemas.+fullExample :: IO ()+fullExample = do   let currentSchemaCache = H.empty-  newSchemaCache <- (<> currentSchemaCache) <$> fetchGraph schemaData+  schemaGraph <- fetchGraph currentSchemaCache schemaData -  schema <- compileSchema newSchemaCache schemaData+  -- We aren't going to use it for anything this time, but if we were caching+  -- remote schemas this would be our new cache.+  --+  -- let newSchemaCache = _cachedSchemas schemaData <> currentSchemaGraph++  schema <- compileSchema schemaGraph schemaData   checkResults (JS.validate schema badData)    where--    -- Not necessary in this case, but it serves as an example.-    ---    -- Note that Graphs are used to handle internal as well as external references.-    -- So even if a schema doesn't reference outside documents, you still need-    -- to generate a real Graph (and not just use mempty) if it refences itself.-    fetchGraph :: JS.RawSchema -> IO JS.Graph-    fetchGraph rs = do-      eitherGraph <- JS.fetchReferencedSchemas JS.draft4 rs H.empty+    fetchGraph :: HashMap Text (HashMap Text Value) -> JS.RawSchema -> IO JS.SchemaGraph+    fetchGraph graph rs = do+      eitherGraph <- JS.fetchReferencedSchemas JS.draft4 graph rs       case eitherGraph of-        Left e      -> error $ "Failed to fetch graph with error: " <> T.unpack e-        Right graph -> return graph+        Left e  -> error $ "Failed to fetch graph with error: " <> T.unpack e+        Right g -> return g -    compileSchema :: JS.Graph -> JS.RawSchema -> IO (JS.Schema JS.Draft4Failure)-    compileSchema graph rs =-      case JS.compileDraft4 graph rs of-        Left failure -> error $ "Not a valid schema: " <> show failure-        Right schema -> return schema+--------------------------------------------------+-- * Helper Functions+-------------------------------------------------- -    checkResults :: [JS.ValidationFailure JS.Draft4Failure] -> IO ()-    checkResults [] = error "OHNO we validated bad data!"-    checkResults [JS.ValidationFailure JS.UniqueItems (JS.FailureInfo x y)] =-      putStrLn . unlines $-        [ ""-        , "Success. We got a UniqueItems error as expected."-        , "Here's the relevant part of the bad JSON document:"-        , ""-        , "  " <> show y-        , ""-        , "And here's the content of the validator that caught it:"-        , ""-        , "  " <> show x-        ]-    checkResults x = error $ "OHNO we got a different failure than we expected: " <> show x+compileSchema :: JS.SchemaGraph -> JS.RawSchema -> IO (JS.Schema JS.Draft4Failure)+compileSchema graph rs =+  case JS.compileDraft4 graph rs of+    Left failure -> error $ "Not a valid schema: " <> show failure+    Right schema -> return schema++checkResults :: [JS.ValidationFailure JS.Draft4Failure] -> IO ()+checkResults [] = error "OHNO we validated bad data!"+checkResults [JS.ValidationFailure JS.UniqueItems (JS.FailureInfo x y)] =+  putStrLn . unlines $+    [ ""+    , "Success. We got a UniqueItems error as expected."+    , "Here's the relevant part of the bad JSON document:"+    , ""+    , "  " <> show y+    , ""+    , "And here's the content of the validator that caught it:"+    , ""+    , "  " <> show x+    ]+checkResults x = error $ "OHNO we got a different failure than we expected: " <> show x
README.md view
@@ -1,27 +1,55 @@-# Intro+![hjsonschema logo](./logo.jpg) -An implementation of [JSON Schema](http://json-schema.org/) Draft 4 in haskell.+A Haskell implementation of the current [JSON Schema](http://json-schema.org/) specification (Draft 4). -# Status+[Hackage](https://hackage.haskell.org/package/hjsonschema) / [GitHub](https://github.com/seagreen/hjsonschema) / [Travis CI](https://travis-ci.org/seagreen/hjsonschema) -Still in development. Lacks solid code to handle changing resolution scope.+# Example -Also note that hjsonschema uses the [regexpr](https://hackage.haskell.org/package/regexpr-0.5.4) regular expression library for the "pattern" validator. This isn't compatible with the ECMA 262 regex dialect, which the [spec](http://json-schema.org/latest/json-schema-validation.html#anchor33) requires.+See [Example.hs on GitHub](https://github.com/seagreen/hjsonschema/blob/master/Example.hs). -# Example+# Tests -See [Example.hs](./Example.hs).+## Install -# Install Tests+`git submodule update --init` -    git submodule update --init+## Run -# Notes+Will run self-contained: +`cabal test local`++Will start an HTTP server temporarily on port 1234:++`cabal test remote`++# Details++## Goals+++ Be a correct and fast implementation of the spec.+++ Be a useful reference for implementers in other languages. Haskell's high level nature, expressive type system and referential transparency suit this purpose well.++## Good Parts+++ Passes all the tests in the [language agnostic test suite](https://github.com/json-schema/JSON-Schema-Test-Suite).+++ Very modular, which should make it easy to support future versions of the specification.++## Bad Parts+++ Uses the [regexpr](https://hackage.haskell.org/package/regexpr-0.5.4) regular expression library for the "pattern" validator. It should use a library based on the ECMA 262 regex dialect, which the [spec](http://json-schema.org/latest/json-schema-validation.html#anchor33) requires.++## Notes+ + `draft4.json` is from commit # cc8ec81ce0abe2385ebd6c2a6f2d6deb646f874a [here](https://github.com/json-schema/json-schema). -# Credits+## Credits -Thanks to [Julian Berman](https://github.com/Julian) for the fantastic test suite.+[TJ Weigel](http://tjweigel.com/) created the logo. -Also thanks to Tim Baumann for his [aeson-schema](https://hackage.haskell.org/package/aeson-schema) library. Hjsonschema's test code and its implementation of `Graph` both originally came from there.+[Tim Baumann](https://github.com/timjb) wrote [aeson-schema](https://hackage.haskell.org/package/aeson-schema), on which hjsonschema's test code and its implementation of `SchemaGraph` were based.++[Julian Berman](https://github.com/Julian) maintains the fantastic [language agnostic test suite](https://github.com/json-schema/JSON-Schema-Test-Suite).
changelog.txt view
@@ -1,3 +1,14 @@+# 0.8+++ Improve scope updating and resolving.++ Rename RawSchema's _rsObject field to _rsData.++ Make RawSchema's _rsURI field a Maybe. This way schemas without a starting+URI can say so explicitly with Nothing instead of with "".++ Rename Graph to SchemaGraph. Declare it with data instead of type. Give it a+field referencing the starting schema. This field is used to find the curent+schema if no URI is in scope and a self-referencing $ref is found (e.g. "#").++ Change the order of the last two arguments to fetchReferencedSchemas.+ # 0.7.1  + Support GHC 7.8 again.
hjsonschema.cabal view
@@ -1,5 +1,5 @@ name:                   hjsonschema-version:                0.7.1.0+version:                0.8.0.0 synopsis:               JSON Schema library homepage:               https://github.com/seagreen/hjsonschema license:                MIT@@ -35,7 +35,7 @@                         OverloadedStrings   other-extensions:     TemplateHaskell   ghc-options:          -Wall-  build-depends:        aeson                >= 0.7   && < 0.10+  build-depends:        aeson                >= 0.7   && < 0.11                         -- 4.6 doesn't have a Traversable Either instance:                       , base                 >= 4.7   && < 4.9                       , bytestring           >= 0.10  && < 0.11@@ -44,7 +44,7 @@                       , hashable             >= 1.2   && < 1.3                       , hjsonpointer         >= 0.2   && < 0.3                       , http-client          >= 0.4.9 && < 0.5-                      , http-types           >= 0.8   && < 0.9+                      , http-types           >= 0.8   && < 0.10                       , mtl                  >= 2.2.1 && < 2.3                       , regexpr              >= 0.5   && < 0.6                       , scientific           >= 0.3   && < 0.4@@ -53,17 +53,6 @@                       , text                 >= 1.2   && < 1.3                       , vector               >= 0.10  && < 0.12 -executable example-  main-is:              Example.hs-  default-language:     Haskell2010-  ghc-options:          -Wall-  build-depends:        aeson-                      , base-                      , hjsonschema-                      , text-                      , unordered-containers-                      , vector- test-suite local   type:                 exitcode-stdio-1.0   hs-source-dirs:       tests@@ -76,7 +65,6 @@                       , base                       , bytestring                       , hjsonschema-                      , unordered-containers                       , text                       , vector                       , directory            >= 1.2 && < 1.3@@ -98,16 +86,26 @@                       , base                       , bytestring                       , hjsonschema-                      , unordered-containers                       , text                       , vector-                      , directory            >= 1.2 && < 1.3-                      , filepath             >= 1.3 && < 1.4-                      , HUnit                >= 1.2 && < 1.3-                      , test-framework       >= 0.8 && < 0.9-                      , test-framework-hunit >= 0.3 && < 0.4+                      , directory+                      , filepath+                      , HUnit+                      , test-framework+                      , test-framework-hunit                       , wai-app-static                       , warp++executable example+  main-is:              Example.hs+  default-language:     Haskell2010+  ghc-options:          -Wall+  build-depends:        aeson+                      , base+                      , hjsonschema+                      , text+                      , unordered-containers+                      , vector  source-repository head   type:               git
src/Data/JsonSchema.hs view
@@ -8,14 +8,11 @@   , module Data.JsonSchema.Draft4   ) where - import           Control.Monad.Except (MonadError, MonadIO, join, throwError) import qualified Data.ByteString.Lazy as LBS import           Data.Foldable import qualified Data.HashMap.Strict as H import           Data.String-import qualified Data.Text as T-import qualified Data.Vector as V  import           Data.JsonSchema.Core import           Data.JsonSchema.Draft4@@ -24,57 +21,66 @@ import           Import  -- For GHCs before 7.10:-import           Prelude hiding (sequence)+import           Prelude hiding (concat, sequence)  -- | Take a schema. Retrieve every document either it or its--- subschemas include via the "$ref" keyword. Load a 'Graph' out+-- subschemas include via the "$ref" keyword. Load a 'SchemaGraph' out -- with them.------ TODO: This function's URL processing is hacky and needs improvement.-fetchReferencedSchemas :: Spec err -> RawSchema -> Graph -> IO (Either Text Graph)+fetchReferencedSchemas :: Spec err -> SchemaCache -> RawSchema -> IO (Either Text SchemaGraph) fetchReferencedSchemas = fetchReferencedSchemas' HE.defaultFetch  -- | A version of fetchReferencedSchemas where the function to make requests -- is provided by the user. This allows restrictions to be added, e.g. rejecting--- non-local URLs.+-- non-local URIs. fetchReferencedSchemas'   -- The `Functor m` declaration is for GHCs before 7.10.   :: forall m e t err. (MonadIO m, Functor m, MonadError t e, Traversable e, IsString t)   => (Text -> m (e LBS.ByteString))   -> Spec err+  -> SchemaCache   -> RawSchema-  -> Graph-  -> m (e Graph)-fetchReferencedSchemas' fetchRef spec rawSchema graph =-  let startingGraph = H.insert (_rsURI rawSchema) (_rsObject rawSchema) graph-  in foldlM (modFetch fetch) (return startingGraph) (includeSubschemas rawSchema)+  -> m (e SchemaGraph)+fetchReferencedSchemas' fetchRef spec cache rawSchema = do+  let startingCache = case _rsURI rawSchema of+                        Nothing  -> cache+                        Just uri -> H.insert uri (_rsData rawSchema) cache+  fmap (SchemaGraph rawSchema) <$> foldlM fetch (return startingCache) (includeSubschemas rawSchema)    where-    includeSubschemas :: RawSchema -> Vector RawSchema+    -- TODO: use a fold here+    includeSubschemas :: RawSchema -> [RawSchema]     includeSubschemas r =-      let newId = newResolutionScope (_rsURI r) (_rsObject r)-          xs = H.intersectionWith (\(ValSpec f _) x -> f newId x) (_unSpec spec) (_rsObject r)-          ys = V.concat . H.elems $ xs -- TODO: optimize-      in V.cons r . V.concat . V.toList $ includeSubschemas <$> ys--    modFetch :: (Graph -> RawSchema -> m (e Graph)) -> e Graph -> RawSchema -> m (e Graph)-    modFetch f eg rs = join <$> sequence (flip f rs <$> eg)+      let scope = _rsURI r `newResolutionScope` _rsData r+          xs = H.intersectionWith (\(ValSpec f _) x -> f scope x) (_unSpec spec) (_rsData r)+      in r : (concat . concat . H.elems $ fmap includeSubschemas <$> xs) -    fetch :: Graph -> RawSchema -> m (e Graph)-    fetch g r =-      case H.lookup "$ref" (_rsObject r) >>= HE.toTxt >>= refAndPointer of-        Nothing     -> return (return g)-        Just (s, _) ->-          let url = (_rsURI r `combineIdAndRef` s)-          in if T.length url <= 0 || H.member url g || not ("://" `T.isInfixOf` url)-            then return (return g)-            else modDec (decodeResponse url) =<< fetchRef url+    fetch :: e SchemaCache -> RawSchema -> m (e SchemaCache)+    fetch eg r = join <$> sequence (run <$> eg)       where-        decodeResponse :: Text -> LBS.ByteString -> m (e Graph)-        decodeResponse url bts =+        run :: SchemaCache -> m (e SchemaCache)+        run g = do+          -- Resolving the new scope is necessary here because of situations like this:+          --+          -- {+          --     "id": "http://localhost:1234/",+          --     "items": {+          --         "id": "folder/",+          --         "items": {"$ref": "folderInteger.json"}+          --     }+          -- }+          let scope = newResolutionScope (_rsURI r) (_rsData r)+          case resolveReference scope <$> (H.lookup "$ref" (_rsData r) >>= HE.toTxt) of+            Just (Just uri,_) ->+              if not (isRemoteReference uri) || H.member uri g+                then return (return g)+                else fetchRef uri >>= decodeResponse g uri+            _ -> return (return g)++        decodeResponse :: SchemaCache -> Text -> e LBS.ByteString -> m (e SchemaCache)+        decodeResponse g uri eBts = join <$> sequence (runDecode g uri <$> eBts)++        runDecode :: SchemaCache -> Text -> LBS.ByteString -> m (e SchemaCache)+        runDecode g uri bts =           case eitherDecode bts of             Left e    -> return $ throwError (fromString e)-            Right obj -> fetchReferencedSchemas' fetchRef spec (RawSchema url obj) g--        modDec :: (LBS.ByteString -> m (e Graph)) -> e LBS.ByteString -> m (e Graph)-        modDec f eBts = join <$> sequence (f <$> eBts)+            Right obj -> fmap _cachedSchemas <$> fetchReferencedSchemas' fetchRef spec g (RawSchema (Just uri) obj)
src/Data/JsonSchema/Core.hs view
@@ -15,7 +15,7 @@ -- * Primary API -------------------------------------------------- -compile :: forall err. Spec err -> Graph -> RawSchema -> Schema err+compile :: forall err. Spec err -> SchemaGraph -> RawSchema -> Schema err compile spec g (RawSchema t o) =   let maybeValidators = H.intersectionWith f (_unSpec spec) o   in Schema . catMaybes . H.elems $ maybeValidators@@ -35,15 +35,22 @@ newtype Schema err = Schema { _unSchema :: [Value -> [ValidationFailure err]] }  data RawSchema = RawSchema-  { _rsURI    :: Text-  , _rsObject :: HashMap Text Value-  }+  { _rsURI  :: !(Maybe Text)+  -- ^ NOTE: Must not include a URI fragment.+  , _rsData :: !(HashMap Text Value)+  } deriving (Show) --- | A mapping of URLs to schemas.+-- | A set of RawSchemas, split into a HashMap. ----- Each key/value pair provides the components of a RawSchema.-type Graph = HashMap Text (HashMap Text Value)+-- Keys correspond to Schema _rsURIs. Values correspond to Schema _rsObjects.+type SchemaCache = HashMap Text (HashMap Text Value) +data SchemaGraph = SchemaGraph+  { _startingSchema :: !RawSchema+  -- ^ The schema initially referenced by '#'.+  , _cachedSchemas  :: !SchemaCache+  } deriving (Show)+ -------------------------------------------------- -- * Validators --------------------------------------------------@@ -55,7 +62,7 @@ -- This is used by 'Data.JsonSchema.fetchRefs' to find all the -- subschemas in a document. This allows it to process only -- "$ref"s and "id"s that are actual schema keywords.-type EmbeddedSchemas = Text -> Value -> Vector RawSchema+type EmbeddedSchemas = Maybe Text -> Value -> [RawSchema]  -- | This is what's used to write most validators in practice. --@@ -68,17 +75,17 @@ -- and modifyName for this purpose. type ValidatorConstructor schemaErr valErr    = Spec schemaErr-  -> Graph+  -> SchemaGraph   -> RawSchema   -> Value   -> Maybe (Value -> valErr)  data ValidationFailure err = ValidationFailure-  { _failureName :: err-  , _failureInfo :: FailureInfo+  { _failureName :: !err+  , _failureInfo :: !FailureInfo   } deriving (Show)  data FailureInfo = FailureInfo-  { _validatingData :: Value-  , _offendingData  :: Value+  { _validatingData :: !Value+  , _offendingData  :: !Value   } deriving (Show)
src/Data/JsonSchema/Draft4.hs view
@@ -123,7 +123,9 @@ isValidSchema r =   case decode . LBS.fromStrict $ $(embedFile "draft4.json") of     Nothing -> error "Schema decode failed (this should never happen)"-    Just s  -> validate (compile draft4 H.empty $ RawSchema "" s) $ Object (_rsObject r)+    Just s  ->+      let a = RawSchema Nothing s+      in validate (compile draft4 (SchemaGraph a mempty) a) $ Object (_rsData r)  -- | Check that a 'RawSchema' conforms to the JSON Schema Draft 4 -- master schema document. Compile it if it does.@@ -133,7 +135,7 @@ -- -- NOTE: It's not actually required to run 'isValidSchema' on -- prospective draft 4 schemas at all.-compileDraft4 :: Graph -> RawSchema -> Either [ValidationFailure Draft4Failure] (Schema Draft4Failure)+compileDraft4 :: SchemaGraph -> RawSchema -> Either [ValidationFailure Draft4Failure] (Schema Draft4Failure) compileDraft4 g r =   case isValidSchema r of     []   -> Right (compile draft4 g r)
src/Data/JsonSchema/Draft4/Any.hs view
@@ -3,11 +3,9 @@  import           Control.Monad import qualified Data.HashMap.Strict       as H-import           Data.JsonPointer+import           Data.Maybe import           Data.Scientific-import           Data.Text.Encoding import qualified Data.Vector               as V-import           Network.HTTP.Types.URI  import           Data.JsonSchema.Core import           Data.JsonSchema.Helpers@@ -93,26 +91,25 @@  notValidator :: ValidatorConstructor err [FailureInfo] notValidator spec g s val@(Object o) = do-  let sub = compile spec g (RawSchema (_rsURI s) o)+  let subSchema = compile spec g $ RawSchema (_rsURI s) o   Just $ \x ->-    case validate sub x of+    case validate subSchema x of       [] -> pure (FailureInfo val x)       _  -> mempty notValidator _ _ _ _ = Nothing --- http://tools.ietf.org/html/draft-pbryan-zyp-json-ref-03+-- JSON Reference Draft Document: ----- TODO: Any members other than "$ref" in a JSON Reference object SHALL be--- ignored.+--      http://tools.ietf.org/html/draft-pbryan-zyp-json-ref-03 ref :: ValidatorConstructor err [ValidationFailure err] ref spec g s (String val) = do-  (reference, pointer) <- refAndPointer (_rsURI s `combineIdAndRef` val)-  r <- RawSchema reference <$> H.lookup reference g-  let urlDecoded = decodeUtf8 . urlDecode True . encodeUtf8 $ pointer-  p <- eitherToMaybe $ jsonPointer urlDecoded-  case resolvePointer p (Object $ _rsObject r) of-    Right (Object o) ->-      let compiled = compile spec g $ RawSchema (_rsURI r) o-      in Just $ validate compiled-    _ -> Nothing+  let (mUri, mFragment) = resolveReference (_rsURI s) val+  r <- RawSchema mUri <$> getReference mUri+  let o = resolveFragment mFragment (_rsData r)+  return . validate . compile spec g $ RawSchema (_rsURI r) o+  where+    getReference :: Maybe Text -> Maybe (HashMap Text Value)+    getReference Nothing  = Just . _rsData . _startingSchema $ g+    getReference (Just t) = H.lookup t (_cachedSchemas g)+ ref _ _ _ _ = Nothing
src/Data/JsonSchema/Draft4/Arrays.hs view
@@ -9,8 +9,15 @@ import           Data.JsonSchema.Helpers import           Import -data ItemsFailure err = Items err | AdditionalItemsBool | AdditionalItemsObject err+data ItemsFailure err+  = Items err+  | AdditionalItemsBool+  | AdditionalItemsObject err +data AdditionalItemsFailure err+  = AdditionalBool+  | AdditionalObject err+ -- | A combination of items and additionalItems. items :: ValidatorConstructor err [ValidationFailure (ItemsFailure err)] items spec g s (Object o) =@@ -22,36 +29,38 @@ items spec g s (Array vs) = do   os <- traverse toObj vs   let subSchemas = compile spec g . RawSchema (_rsURI s) <$> V.toList os-      mAdditionalItems = additionalItems spec g s =<< H.lookup "additionalItems" (_rsObject s)+      mAdditionalItems = additionalItems spec g s =<< H.lookup "additionalItems" (_rsData s)   Just $ \x ->     case x of       Array ys ->         let extras = V.drop (V.length os) ys             itemFailures = join $ fmap (modifyFailureName Items) <$> zipWith validate subSchemas (V.toList ys)             additionalItemFailures = runMaybeVal mAdditionalItems (Array extras)-        in itemFailures <> additionalItemFailures+        in itemFailures <> fmap (modifyFailureName f) additionalItemFailures       _ -> mempty+  where+    f :: AdditionalItemsFailure err -> ItemsFailure err+    f AdditionalBool         = AdditionalItemsBool+    f (AdditionalObject err) = AdditionalItemsObject err items _ _ _ _ = Nothing  -- | Not included directly in the 'draft4' spec hashmap because it always -- validates data unless 'items' is also present. This is because 'items' -- defaults to {}.------ TODO: Should have its own error type instead of sharing with Items.-additionalItems :: ValidatorConstructor err [ValidationFailure (ItemsFailure err)]+additionalItems :: ValidatorConstructor err [ValidationFailure (AdditionalItemsFailure err)] additionalItems _ _ _ val@(Bool v) =   Just $ \x ->     case x of       Array ys ->         if not v && V.length ys > 0-          then pure $ ValidationFailure AdditionalItemsBool (FailureInfo val x)+          then pure $ ValidationFailure AdditionalBool (FailureInfo val x)           else mempty       _ -> mempty additionalItems spec g s (Object o) =   let subSchema = compile spec g (RawSchema (_rsURI s) o)   in Just $ \x ->     case x of-      Array ys -> V.toList ys >>= fmap (modifyFailureName AdditionalItemsObject) . validate subSchema+      Array ys -> V.toList ys >>= fmap (modifyFailureName AdditionalObject) . validate subSchema       _        -> mempty additionalItems _ _ _ _ = Nothing 
src/Data/JsonSchema/Draft4/Numbers.hs view
@@ -37,7 +37,7 @@   where     checkExclusive :: (Scientific -> Scientific -> Bool, MaximumFailure)     checkExclusive =-      case H.lookup "exclusiveMaximum" (_rsObject s) of+      case H.lookup "exclusiveMaximum" (_rsData s) of         Just (Bool a) -> if a then ((>=), ExclusiveMaximum) else ((>), Maximum)         _             -> ((>), Maximum) maximumVal _ _ _ _ = Nothing@@ -55,7 +55,7 @@   where     checkExclusive :: (Scientific -> Scientific -> Bool, MinimumFailure)     checkExclusive =-      case H.lookup "exclusiveMinimum" (_rsObject s) of+      case H.lookup "exclusiveMinimum" (_rsData s) of         Just (Bool a) -> if a then ((<=), ExclusiveMinimum) else ((<), Minimum)         _             -> ((<), Minimum) minimumVal _ _ _ _ = Nothing
src/Data/JsonSchema/Draft4/Objects.hs view
@@ -85,7 +85,7 @@         in schemaFailures <> propertyFailures       _ -> mempty   where-    toSchemaDep :: Spec a -> Graph -> (Text, Value) -> [(Text, Schema a)]+    toSchemaDep :: Spec a -> SchemaGraph -> (Text, Value) -> [(Text, Schema a)]     toSchemaDep spc gr (t, Object ob) = pure (t, compile spc gr $ RawSchema (_rsURI s) ob)     toSchemaDep _ _ _ = mempty 
src/Data/JsonSchema/Draft4/Objects/Properties.hs view
@@ -30,8 +30,8 @@ properties :: forall err. ValidatorConstructor err [ValidationFailure (PropertiesFailure err)] properties spec g s val = do   let mProps   = propertiesUnmatched val-      mPatProp = patternUnmatched spec g s =<< H.lookup "patternProperties" (_rsObject s)-      mAddProp = runAdditionalProperties spec g s =<< H.lookup "additionalProperties" (_rsObject s)+      mPatProp = patternUnmatched spec g s =<< H.lookup "patternProperties" (_rsData s)+      mAddProp = runAdditionalProperties spec g s =<< H.lookup "additionalProperties" (_rsData s)   when (isNothing mProps && isNothing mPatProp && isNothing mAddProp) Nothing   Just $ \x ->     case x of@@ -61,12 +61,9 @@  patternProperties :: ValidatorConstructor err [ValidationFailure (PatternPropertiesFailure err)] patternProperties spec g s val = do-  when (H.member "properties" (_rsObject s)) Nothing+  when (H.member "properties" (_rsData s)) Nothing   let mPatternProps = patternUnmatched spec g s val-  -- TODO: checking additionalProperties as well doesn't help with tests.-  -- Make sure we're doing the correct thing, then get tests for this-  -- merged into the test suite.-  let mAdditionalProps = runAdditionalProperties spec g s =<< H.lookup "additionalProperties" (_rsObject s)+  let mAdditionalProps = runAdditionalProperties spec g s =<< H.lookup "additionalProperties" (_rsData s)   when (isNothing mPatternProps && isNothing mAdditionalProps) Nothing   Just $ \x ->     case x of@@ -78,7 +75,7 @@  patternUnmatched   :: Spec err-  -> Graph+  -> SchemaGraph   -> RawSchema   -> Value   -> Maybe (Value -> ([ValidationFailure err], Value))@@ -122,8 +119,8 @@  additionalProperties :: ValidatorConstructor err [ValidationFailure (AdditionalPropertiesFailure err)] additionalProperties spec g s val = do-  when (H.member "properties" (_rsObject s)) Nothing-  when (H.member "patternProperties" (_rsObject s)) Nothing+  when (H.member "properties" (_rsData s)) Nothing+  when (H.member "patternProperties" (_rsData s)) Nothing   runAdditionalProperties spec g s val  -- | An additionalProperties validator than never disables itself.
src/Data/JsonSchema/Helpers.hs view
@@ -1,3 +1,4 @@+ module Data.JsonSchema.Helpers where  import           Control.Exception@@ -24,7 +25,7 @@ objEmbed _ _ = mempty  arrayEmbed :: EmbeddedSchemas-arrayEmbed t (Array vs) = objEmbed t =<< vs+arrayEmbed t (Array vs) = objEmbed t =<< V.toList vs arrayEmbed _ _ = mempty  objOrArrayEmbed :: EmbeddedSchemas@@ -33,28 +34,27 @@ objOrArrayEmbed _ _ = mempty  objMembersEmbed :: EmbeddedSchemas-objMembersEmbed t (Object o) = objEmbed t =<< V.fromList (H.elems o)+objMembersEmbed t (Object o) = objEmbed t =<< H.elems o objMembersEmbed _ _ = mempty  -------------------------------------------------- -- * Modify Validators for use in Specs -------------------------------------------------- --- | TODO: Is there something easier to replace these fmaps with? giveName   :: forall err. err   -> ValidatorConstructor err [FailureInfo]   -> ValidatorConstructor err [ValidationFailure err]-giveName err = (fmap.fmap.fmap.fmap.fmap.fmap.fmap) (ValidationFailure err)+giveName err f spec g rs v = (fmap.fmap) (ValidationFailure err) <$> f spec g rs v  modifyName   :: forall valErr schemaErr. (valErr -> schemaErr)   -> ValidatorConstructor schemaErr [ValidationFailure valErr]   -> ValidatorConstructor schemaErr [ValidationFailure schemaErr]-modifyName failureHandler = (fmap.fmap.fmap.fmap.fmap.fmap.fmap) f+modifyName failureHandler f spec g rs v = (fmap.fmap) modErr <$> f spec g rs v   where-    f :: ValidationFailure valErr -> ValidationFailure schemaErr-    f (ValidationFailure a b) = ValidationFailure (failureHandler a) b+    modErr :: ValidationFailure valErr -> ValidationFailure schemaErr+    modErr (ValidationFailure a b) = ValidationFailure (failureHandler a) b  -- | It's important to know if an object's a validator (even if it will never run, -- like the definitions validator) because parts of it might be referenced by other
src/Data/JsonSchema/Reference.hs view
@@ -1,39 +1,65 @@--- | TODO: The code handling resolution scope updates--- is hacky and needs improvement.  module Data.JsonSchema.Reference where -import qualified Data.HashMap.Strict as H-import qualified Data.Text           as T+import qualified Data.HashMap.Strict    as H+import           Data.JsonPointer+import qualified Data.Text              as T+import           Data.Text.Encoding+import           Network.HTTP.Types.URI  import           Import -combineIdAndRef :: Text -> Text -> Text-combineIdAndRef a b-  | "://" `T.isInfixOf` b              = b-  | T.length a < 1 || T.length b < 1   = a <> b-  | T.last a == '#' && T.head b == '#' = a <> T.tail b-  | otherwise                          = a <> b--combineIds :: Text -> Text -> Text-combineIds a b-  | b == "#" || b == ""                = a-  | "://" `T.isInfixOf` b              = b-  | T.length a < 1 || T.length b < 1   = a <> b-  | T.last a == '#' && T.head b == '#' = a <> T.tail b-  | otherwise                          = a <> b+type URIBase = Maybe Text+type URIBaseAndFragment = (Maybe Text, Maybe Text) -newResolutionScope :: Text -> HashMap Text Value -> Text-newResolutionScope t o =+newResolutionScope :: URIBase -> HashMap Text Value -> URIBase+newResolutionScope mScope o =   case H.lookup "id" o of-    Just (String idKeyword) -> t `combineIds` idKeyword-    _                       -> t+    Just (String t) -> fst . baseAndFragment $ resolveScopeAgainst mScope t+    _               -> mScope -refAndPointer :: Text -> Maybe (Text, Text)-refAndPointer val = getParts $ T.splitOn "#" val+resolveReference :: URIBase -> Text -> URIBaseAndFragment+resolveReference mScope t = baseAndFragment $ resolveScopeAgainst mScope t++isRemoteReference :: Text -> Bool+isRemoteReference uri = "://" `T.isInfixOf` uri++resolveFragment :: Maybe Text -> HashMap Text Value -> HashMap Text Value+resolveFragment Nothing hm        = hm+resolveFragment (Just pointer) hm =+  let urlDecoded = decodeUtf8 . urlDecode True . encodeUtf8 $ pointer+  in case jsonPointer urlDecoded of+    Left _  -> hm+    Right p ->+      case resolvePointer p (Object hm) of+        Right (Object hm') -> hm'+        _                  -> hm++--------------------------------------------------+-- * Internal+--------------------------------------------------++baseAndFragment :: Text -> URIBaseAndFragment+baseAndFragment = f . T.splitOn "#"   where-    getParts :: [Text] -> Maybe (Text, Text)-    getParts []    = Just ("","")-    getParts [x]   = Just (x,"")-    getParts [x,y] = Just (x,y)-    getParts _     = Nothing+    f :: [Text] -> URIBaseAndFragment+    f [x]   = (g x, Nothing)+    f [x,y] = (g x, g y)+    f _     = (Nothing, Nothing)++    g "" = Nothing+    g x  = Just x++resolveScopeAgainst :: Maybe Text -> Text -> Text+resolveScopeAgainst Nothing t = t+resolveScopeAgainst (Just scope) t+  | isRemoteReference t = t+  | otherwise           = smartAppend+  where+    -- There shouldn't be a fragment at the end of a scope URI,+    -- but just in case a user leaves one in we want to be sure+    -- to cut it off before appending.+    smartAppend :: Text+    smartAppend = case baseAndFragment scope of+                    (Just base,_) -> base <> t+                    _             -> t
tests/Lib.hs view
@@ -9,7 +9,6 @@ import           Data.Aeson.TH import qualified Data.ByteString.Lazy           as LBS import           Data.Char                      (toLower)-import qualified Data.HashMap.Strict            as H import           Data.JsonSchema import           Data.Monoid import           Data.Text                      (Text)@@ -37,8 +36,7 @@   }  instance FromJSON RawSchema where-  parseJSON = withObject "Schema" $ \o ->-    return RawSchema { _rsURI = "", _rsObject = o }+  parseJSON = withObject "Schema" $ return . RawSchema Nothing  instance FromJSON SchemaTest where   parseJSON = withObject "SchemaTest" $ \o -> SchemaTest@@ -71,7 +69,7 @@   testCase (T.unpack $ _stDescription st) $ do     sanityCheckTest (_stSchema st)     forM_ (_stCases st) $ \sc -> do-      g <- assertRight =<< fetchReferencedSchemas draft4 (_stSchema st) H.empty+      g <- assertRight =<< fetchReferencedSchemas draft4 mempty (_stSchema st)       let res = validate (compile draft4 g $ _stSchema st) (_scData sc)       if _scValid sc         then assertValid   sc res