packages feed

hjsonschema 0.6.0.2 → 0.7.0.0

raw patch · 18 files changed

+1073/−788 lines, 18 filesdep +containersdep +mtldep +transformersdep ~aesondep ~basedep ~textnew-component:exe:examplePVP ok

version bump matches the API change (PVP)

Dependencies added: containers, mtl, transformers

Dependency ranges changed: aeson, base, text, unordered-containers, vector

API changes (from Hackage documentation)

- Data.JsonSchema: _rsObject :: RawSchema -> HashMap Text Value
- Data.JsonSchema: _rsURI :: RawSchema -> Text
- Data.JsonSchema: _unSchema :: Schema -> [Validator]
- Data.JsonSchema: _unSpec :: Spec -> HashMap Text (ValidatorGen, EmbeddedSchemas)
- Data.JsonSchema: fetchRefs :: Spec -> RawSchema -> Graph -> IO (Either Text Graph)
- Data.JsonSchema: type ValErr = Text
- Data.JsonSchema: type Validator = Value -> Vector ValErr
- Data.JsonSchema: type ValidatorGen = Spec -> Graph -> RawSchema -> Value -> Maybe Validator
- Data.JsonSchema.Helpers: hmToVector :: HashMap k a -> Vector (k, a)
- Data.JsonSchema.Helpers: isJsonType :: Value -> Vector Text -> Vector ValErr
- Data.JsonSchema.Helpers: patternPropertiesMatches :: Spec -> Graph -> RawSchema -> Value -> Maybe (Value -> (Vector ValErr, Value))
- Data.JsonSchema.Helpers: vLefts :: Vector (Either a b) -> Vector a
- Data.JsonSchema.Helpers: vRights :: Vector (Either a b) -> Vector b
- Data.JsonSchema.Helpers: vectorOfElems :: HashMap k a -> Vector a
- Data.JsonSchema.Helpers: vectorToHm :: (Eq k, Hashable k) => Vector (k, a) -> HashMap k a
- Data.JsonSchema.Reference: fetchRef :: Text -> IO (Either Text (HashMap Text Value))
- Data.JsonSchema.Reference: safeGet :: Text -> IO (Either Text ByteString)
- Data.JsonSchema.Validators: additionalItems :: ValidatorGen
- Data.JsonSchema.Validators: additionalProperties :: ValidatorGen
- Data.JsonSchema.Validators: allOf :: ValidatorGen
- Data.JsonSchema.Validators: anyOf :: ValidatorGen
- Data.JsonSchema.Validators: dependencies :: ValidatorGen
- Data.JsonSchema.Validators: enum :: ValidatorGen
- Data.JsonSchema.Validators: items :: ValidatorGen
- Data.JsonSchema.Validators: maxItems :: ValidatorGen
- Data.JsonSchema.Validators: maxLength :: ValidatorGen
- Data.JsonSchema.Validators: maxProperties :: ValidatorGen
- Data.JsonSchema.Validators: maximumVal :: ValidatorGen
- Data.JsonSchema.Validators: minItems :: ValidatorGen
- Data.JsonSchema.Validators: minLength :: ValidatorGen
- Data.JsonSchema.Validators: minProperties :: ValidatorGen
- Data.JsonSchema.Validators: minimumVal :: ValidatorGen
- Data.JsonSchema.Validators: multipleOf :: ValidatorGen
- Data.JsonSchema.Validators: noVal :: ValidatorGen
- Data.JsonSchema.Validators: notValidator :: ValidatorGen
- Data.JsonSchema.Validators: oneOf :: ValidatorGen
- Data.JsonSchema.Validators: pattern :: ValidatorGen
- Data.JsonSchema.Validators: patternProperties :: ValidatorGen
- Data.JsonSchema.Validators: properties :: ValidatorGen
- Data.JsonSchema.Validators: ref :: ValidatorGen
- Data.JsonSchema.Validators: required :: ValidatorGen
- Data.JsonSchema.Validators: runAdditionalProperties :: ValidatorGen
- Data.JsonSchema.Validators: typeVal :: ValidatorGen
- Data.JsonSchema.Validators: uniqueItems :: ValidatorGen
+ Data.JsonSchema: AdditionalItemsBool :: Draft4Failure
+ Data.JsonSchema: AdditionalItemsObject :: Draft4Failure -> Draft4Failure
+ Data.JsonSchema: AdditionalPropertiesBool :: Draft4Failure
+ Data.JsonSchema: AdditionalPropertiesObject :: Draft4Failure -> Draft4Failure
+ Data.JsonSchema: AllOf :: Draft4Failure -> Draft4Failure
+ Data.JsonSchema: AnyOf :: Draft4Failure
+ Data.JsonSchema: Enum :: Draft4Failure
+ Data.JsonSchema: ExclusiveMaximum :: Draft4Failure
+ Data.JsonSchema: ExclusiveMinimum :: Draft4Failure
+ Data.JsonSchema: FailureInfo :: Value -> Value -> FailureInfo
+ Data.JsonSchema: Items :: Draft4Failure -> Draft4Failure
+ Data.JsonSchema: MaxItems :: Draft4Failure
+ Data.JsonSchema: MaxLength :: Draft4Failure
+ Data.JsonSchema: MaxProperties :: Draft4Failure
+ Data.JsonSchema: Maximum :: Draft4Failure
+ Data.JsonSchema: MinItems :: Draft4Failure
+ Data.JsonSchema: MinLength :: Draft4Failure
+ Data.JsonSchema: MinProperties :: Draft4Failure
+ Data.JsonSchema: Minimum :: Draft4Failure
+ Data.JsonSchema: MultipleOf :: Draft4Failure
+ Data.JsonSchema: NotValidator :: Draft4Failure
+ Data.JsonSchema: OneOf :: Draft4Failure
+ Data.JsonSchema: Pattern :: Draft4Failure
+ Data.JsonSchema: PatternProperties :: Draft4Failure -> Draft4Failure
+ Data.JsonSchema: Properties :: Draft4Failure -> Draft4Failure
+ Data.JsonSchema: PropertyDependency :: Draft4Failure
+ Data.JsonSchema: Ref :: Draft4Failure -> Draft4Failure
+ Data.JsonSchema: Required :: Draft4Failure
+ Data.JsonSchema: SchemaDependency :: Draft4Failure -> Draft4Failure
+ Data.JsonSchema: TypeValidator :: Draft4Failure
+ Data.JsonSchema: UniqueItems :: Draft4Failure
+ Data.JsonSchema: ValSpec :: EmbeddedSchemas -> (ValidatorConstructor err [ValidationFailure err]) -> ValSpec err
+ Data.JsonSchema: ValidationFailure :: err -> FailureInfo -> ValidationFailure err
+ Data.JsonSchema: [_failureInfo] :: ValidationFailure err -> FailureInfo
+ Data.JsonSchema: [_failureName] :: ValidationFailure err -> err
+ Data.JsonSchema: [_offendingData] :: FailureInfo -> Value
+ Data.JsonSchema: [_rsObject] :: RawSchema -> HashMap Text Value
+ Data.JsonSchema: [_rsURI] :: RawSchema -> Text
+ Data.JsonSchema: [_unSchema] :: Schema err -> [Value -> [ValidationFailure err]]
+ Data.JsonSchema: [_unSpec] :: Spec err -> HashMap Text (ValSpec err)
+ Data.JsonSchema: [_validatingData] :: FailureInfo -> Value
+ Data.JsonSchema: data Draft4Failure
+ Data.JsonSchema: data FailureInfo
+ Data.JsonSchema: data ValSpec err
+ Data.JsonSchema: data ValidationFailure err
+ Data.JsonSchema: fetchReferencedSchemas :: Spec err -> RawSchema -> Graph -> IO (Either Text Graph)
+ Data.JsonSchema: fetchReferencedSchemas' :: (IsString t, MonadError t e, Traversable e, MonadIO m) => (Text -> m (e ByteString)) -> Spec err -> RawSchema -> Graph -> m (e Graph)
+ Data.JsonSchema: type ValidatorConstructor schemaErr valErr = Spec schemaErr -> Graph -> RawSchema -> Value -> Maybe (Value -> valErr)
+ Data.JsonSchema.Draft4.Any: allOf :: ValidatorConstructor err [ValidationFailure err]
+ Data.JsonSchema.Draft4.Any: anyOf :: ValidatorConstructor err [FailureInfo]
+ Data.JsonSchema.Draft4.Any: enum :: ValidatorConstructor err [FailureInfo]
+ Data.JsonSchema.Draft4.Any: isJsonType :: Value -> Vector Text -> [FailureInfo]
+ Data.JsonSchema.Draft4.Any: notValidator :: ValidatorConstructor err [FailureInfo]
+ Data.JsonSchema.Draft4.Any: oneOf :: ValidatorConstructor err [FailureInfo]
+ Data.JsonSchema.Draft4.Any: ref :: ValidatorConstructor err [ValidationFailure err]
+ Data.JsonSchema.Draft4.Any: typeValidator :: ValidatorConstructor err [FailureInfo]
+ Data.JsonSchema.Draft4.Arrays: AdditionalItemsBool :: ItemsFailure err
+ Data.JsonSchema.Draft4.Arrays: AdditionalItemsObject :: err -> ItemsFailure err
+ Data.JsonSchema.Draft4.Arrays: Items :: err -> ItemsFailure err
+ Data.JsonSchema.Draft4.Arrays: additionalItems :: ValidatorConstructor err [ValidationFailure (ItemsFailure err)]
+ Data.JsonSchema.Draft4.Arrays: data ItemsFailure err
+ Data.JsonSchema.Draft4.Arrays: items :: ValidatorConstructor err [ValidationFailure (ItemsFailure err)]
+ Data.JsonSchema.Draft4.Arrays: maxItems :: ValidatorConstructor err [FailureInfo]
+ Data.JsonSchema.Draft4.Arrays: minItems :: ValidatorConstructor err [FailureInfo]
+ Data.JsonSchema.Draft4.Arrays: uniqueItems :: ValidatorConstructor err [FailureInfo]
+ Data.JsonSchema.Draft4.Numbers: ExclusiveMaximum :: MaximumFailure
+ Data.JsonSchema.Draft4.Numbers: ExclusiveMinimum :: MinimumFailure
+ Data.JsonSchema.Draft4.Numbers: Maximum :: MaximumFailure
+ Data.JsonSchema.Draft4.Numbers: Minimum :: MinimumFailure
+ Data.JsonSchema.Draft4.Numbers: data MaximumFailure
+ Data.JsonSchema.Draft4.Numbers: data MinimumFailure
+ Data.JsonSchema.Draft4.Numbers: maximumVal :: ValidatorConstructor err [ValidationFailure MaximumFailure]
+ Data.JsonSchema.Draft4.Numbers: minimumVal :: ValidatorConstructor err [ValidationFailure MinimumFailure]
+ Data.JsonSchema.Draft4.Numbers: multipleOf :: ValidatorConstructor err [FailureInfo]
+ Data.JsonSchema.Draft4.Objects: AdditionalPropertiesBool :: AdditionalPropertiesFailure err
+ Data.JsonSchema.Draft4.Objects: AdditionalPropertiesObject :: err -> AdditionalPropertiesFailure err
+ Data.JsonSchema.Draft4.Objects: PatternAdditional :: (AdditionalPropertiesFailure err) -> PatternPropertiesFailure err
+ Data.JsonSchema.Draft4.Objects: PatternProperties :: err -> PatternPropertiesFailure err
+ Data.JsonSchema.Draft4.Objects: PropAdditional :: (AdditionalPropertiesFailure err) -> PropertiesFailure err
+ Data.JsonSchema.Draft4.Objects: PropPattern :: err -> PropertiesFailure err
+ Data.JsonSchema.Draft4.Objects: Properties :: err -> PropertiesFailure err
+ Data.JsonSchema.Draft4.Objects: PropertyDependency :: DependencyFailure err
+ Data.JsonSchema.Draft4.Objects: SchemaDependency :: err -> DependencyFailure err
+ Data.JsonSchema.Draft4.Objects: additionalProperties :: ValidatorConstructor err [ValidationFailure (AdditionalPropertiesFailure err)]
+ Data.JsonSchema.Draft4.Objects: data AdditionalPropertiesFailure err
+ Data.JsonSchema.Draft4.Objects: data DependencyFailure err
+ Data.JsonSchema.Draft4.Objects: data PatternPropertiesFailure err
+ Data.JsonSchema.Draft4.Objects: data PropertiesFailure err
+ Data.JsonSchema.Draft4.Objects: dependencies :: ValidatorConstructor err [ValidationFailure (DependencyFailure err)]
+ Data.JsonSchema.Draft4.Objects: maxProperties :: ValidatorConstructor err [FailureInfo]
+ Data.JsonSchema.Draft4.Objects: minProperties :: ValidatorConstructor err [FailureInfo]
+ Data.JsonSchema.Draft4.Objects: patternProperties :: ValidatorConstructor err [ValidationFailure (PatternPropertiesFailure err)]
+ Data.JsonSchema.Draft4.Objects: patternUnmatched :: Spec err -> Graph -> RawSchema -> Value -> Maybe (Value -> ([ValidationFailure err], Value))
+ Data.JsonSchema.Draft4.Objects: properties :: ValidatorConstructor err [ValidationFailure (PropertiesFailure err)]
+ Data.JsonSchema.Draft4.Objects: required :: ValidatorConstructor err [FailureInfo]
+ Data.JsonSchema.Draft4.Objects: runAdditionalProperties :: ValidatorConstructor err [ValidationFailure (AdditionalPropertiesFailure err)]
+ Data.JsonSchema.Draft4.Strings: maxLength :: ValidatorConstructor err [FailureInfo]
+ Data.JsonSchema.Draft4.Strings: minLength :: ValidatorConstructor err [FailureInfo]
+ Data.JsonSchema.Draft4.Strings: pattern :: ValidatorConstructor err [FailureInfo]
+ Data.JsonSchema.Helpers: OrdValue :: Value -> OrdValue
+ Data.JsonSchema.Helpers: allUniqueValues :: Vector Value -> Bool
+ Data.JsonSchema.Helpers: defaultFetch :: Text -> IO (Either Text ByteString)
+ Data.JsonSchema.Helpers: giveName :: err -> ValidatorConstructor err [FailureInfo] -> ValidatorConstructor err [ValidationFailure err]
+ Data.JsonSchema.Helpers: instance GHC.Classes.Eq Data.JsonSchema.Helpers.OrdValue
+ Data.JsonSchema.Helpers: instance GHC.Classes.Ord Data.JsonSchema.Helpers.OrdValue
+ Data.JsonSchema.Helpers: modifyFailureName :: (a -> b) -> ValidationFailure a -> ValidationFailure b
+ Data.JsonSchema.Helpers: modifyName :: (valErr -> schemaErr) -> ValidatorConstructor schemaErr [ValidationFailure valErr] -> ValidatorConstructor schemaErr [ValidationFailure schemaErr]
+ Data.JsonSchema.Helpers: neverBuild :: ValidatorConstructor err [ValidationFailure err]
+ Data.JsonSchema.Helpers: newtype OrdValue
- Data.JsonSchema: Schema :: [Validator] -> Schema
+ Data.JsonSchema: Schema :: [Value -> [ValidationFailure err]] -> Schema err
- Data.JsonSchema: Spec :: HashMap Text (ValidatorGen, EmbeddedSchemas) -> Spec
+ Data.JsonSchema: Spec :: HashMap Text (ValSpec err) -> Spec err
- Data.JsonSchema: compile :: Spec -> Graph -> RawSchema -> Schema
+ Data.JsonSchema: compile :: Spec err -> Graph -> RawSchema -> Schema err
- Data.JsonSchema: compileDraft4 :: Graph -> RawSchema -> Either (Vector ValErr) Schema
+ Data.JsonSchema: compileDraft4 :: Graph -> RawSchema -> Either [ValidationFailure Draft4Failure] (Schema Draft4Failure)
- Data.JsonSchema: draft4 :: Spec
+ Data.JsonSchema: draft4 :: Spec Draft4Failure
- Data.JsonSchema: isValidSchema :: RawSchema -> Either (Vector ValErr) Value
+ Data.JsonSchema: isValidSchema :: RawSchema -> [ValidationFailure Draft4Failure]
- Data.JsonSchema: newtype Schema
+ Data.JsonSchema: newtype Schema err
- Data.JsonSchema: newtype Spec
+ Data.JsonSchema: newtype Spec err
- Data.JsonSchema: validate :: Schema -> Value -> Either (Vector ValErr) Value
+ Data.JsonSchema: validate :: Schema err -> Value -> [ValidationFailure err]
- Data.JsonSchema.Helpers: allUnique :: Eq a => Vector a -> Bool
+ Data.JsonSchema.Helpers: allUnique :: (Ord a) => Vector a -> Bool
- Data.JsonSchema.Helpers: runMaybeVal :: Maybe Validator -> Value -> Vector ValErr
+ Data.JsonSchema.Helpers: runMaybeVal :: Maybe (Value -> [a]) -> Value -> [a]
- Data.JsonSchema.Helpers: runMaybeVal' :: Maybe (Value -> (Vector ValErr, Value)) -> Value -> (Vector ValErr, Value)
+ Data.JsonSchema.Helpers: runMaybeVal' :: Maybe (Value -> ([a], Value)) -> Value -> ([a], Value)

Files

+ Example.hs view
@@ -0,0 +1,74 @@+{-# LANGUAGE OverloadedStrings #-}++module Main where++import           Data.Aeson+import           Data.HashMap.Strict (HashMap)+import qualified Data.HashMap.Strict as H+import           Data.Monoid+import           Data.Text (Text)+import qualified Data.Text as T+import qualified Data.Vector as V++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.++schemaData :: JS.RawSchema+schemaData = JS.RawSchema+  { JS._rsURI    = ""+  , JS._rsObject = schemaJSON+  }+  where+    schemaJSON :: HashMap Text Value+    schemaJSON = H.singleton "uniqueItems" (Bool True)++badData :: Value+badData = Array $ V.fromList ["foo", "foo"]++main :: IO ()+main = do++  let currentSchemaCache = H.empty+  newSchemaCache <- (<> currentSchemaCache) <$> fetchGraph schemaData++  schema <- compileSchema newSchemaCache 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+      case eitherGraph of+        Left e      -> error $ "Failed to fetch graph with error: " <> T.unpack e+        Right graph -> return graph++    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++    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
@@ -6,43 +6,11 @@  Still in development. Lacks solid code to handle changing resolution scope. -# Example--```haskell-{-# LANGUAGE OverloadedStrings #-}--module Main where--import Data.Aeson-import qualified Data.HashMap.Strict as H-import qualified Data.JsonSchema as JS-import qualified Data.Vector as V--main :: IO ()-main = do-  eitherGraph <- JS.fetchRefs JS.draft4 rawSchema H.empty-  case eitherGraph of-    Left e      -> print e-    Right graph ->-      case JS.compileDraft4 graph rawSchema of-        Left e2      -> print e2-        Right schema -> print $ JS.validate schema invalidData--rawSchema :: JS.RawSchema-rawSchema = JS.RawSchema-  { JS._rsURI = ""-  , JS._rsObject = H.singleton "uniqueItems" (Bool True) -- Schema JSON goes here.-  }--invalidData :: Value-invalidData = Array (V.fromList ["foo", "foo"])-```+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. -Output:-```-Left (fromList ["Val error against uniqueItems True for: Array (fromList [String \"foo\",String \"foo\"])"])-```+# Example +See [Example.hs](./Example.hs).  # Install Tests @@ -50,12 +18,10 @@  # Notes -+ This uses the [regexpr](https://hackage.haskell.org/package/regexpr-0.5.4) regular expression library for the "pattern" validator. I have no idea if this is compatible with the ECMA 262 regex dialect, which the [spec](http://json-schema.org/latest/json-schema-validation.html#anchor33) requires.- + `draft4.json` is from commit # cc8ec81ce0abe2385ebd6c2a6f2d6deb646f874a [here](https://github.com/json-schema/json-schema).  # Credits  Thanks to [Julian Berman](https://github.com/Julian) for the fantastic test suite. -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 come from Aeson-Schema.+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.
changelog.txt view
@@ -1,3 +1,15 @@+# 0.7++Change error type from Text to ValidationFailure.++Revert the 0.6 changes to validate. Also switch from Vector+to list. Validate is now:+  Schema err -> Value -> [ValidationFailure err]++Add fetchReferencedSchemas', which lets the user provide their+own MonadIO function to be used when fetching schemas. This lets+them do things like only fetch schemas from particular domains.+ # 0.6  Break the API so the library doesn't induce boolean blindness.
hjsonschema.cabal view
@@ -1,6 +1,6 @@ name:                   hjsonschema-version:                0.6.0.2-synopsis:               JSON Schema Draft 4 library+version:                0.7.0.0+synopsis:               JSON Schema library homepage:               https://github.com/seagreen/hjsonschema license:                MIT license-file:           MIT-LICENSE.txt@@ -18,27 +18,50 @@ library   hs-source-dirs:       src   exposed-modules:      Data.JsonSchema+                      , Data.JsonSchema.Draft4.Any+                      , Data.JsonSchema.Draft4.Arrays+                      , Data.JsonSchema.Draft4.Numbers+                      , Data.JsonSchema.Draft4.Objects+                      , Data.JsonSchema.Draft4.Strings                       , Data.JsonSchema.Helpers                       , Data.JsonSchema.Reference-                      , Data.JsonSchema.Validators   other-modules:        Data.JsonSchema.Core+                        Data.JsonSchema.Draft4+                      , Data.JsonSchema.Draft4.Objects.Properties+                      , Import   default-language:     Haskell2010-  default-extensions:   OverloadedStrings+  default-extensions:   ScopedTypeVariables+                        OverloadedStrings   other-extensions:     TemplateHaskell   ghc-options:          -Wall   build-depends:        aeson                >= 0.7   && < 0.10                       , base                 >= 4.6   && < 4.9                       , bytestring           >= 0.10  && < 0.11+                      , containers           >= 0.5   && < 0.6                       , file-embed           >= 0.0.8 && < 0.0.10                       , hashable             >= 1.2   && < 1.3                       , hjsonpointer         >= 0.2   && < 0.3                       , http-client          >= 0.4.9 && < 0.5                       , http-types           >= 0.8   && < 0.9+                      , mtl                  >= 2.2.1 && < 2.3                       , regexpr              >= 0.5   && < 0.6                       , scientific           >= 0.3   && < 0.4+                      , transformers         >= 0.4.2 && < 0.5                       , unordered-containers >= 0.2   && < 0.3                       , 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
src/Data/JsonSchema.hs view
@@ -1,119 +1,75 @@-{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE ScopedTypeVariables #-}  module Data.JsonSchema   ( module Data.JsonSchema.Core+    -- * Schema feching tools   , module Data.JsonSchema+    -- * Draft 4 specific+  , module Data.JsonSchema.Draft4   ) where -import           Control.Applicative-import           Data.Aeson-import           Data.ByteString.Lazy       (fromStrict)-import           Data.FileEmbed+import           Control.Monad.Except+import qualified Data.ByteString.Lazy as LBS import           Data.Foldable-import qualified Data.HashMap.Strict        as H+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.Helpers+import           Data.JsonSchema.Draft4+import qualified Data.JsonSchema.Helpers as HE import           Data.JsonSchema.Reference-import           Data.JsonSchema.Validators-import           Data.Maybe-import           Data.Text                  (Text)-import qualified Data.Text                  as T-import           Data.Vector                (Vector)-import qualified Data.Vector                as V-import           Prelude                    hiding (foldr)------------------------------------------------------- * Draft 4 Specific-----------------------------------------------------draft4 :: Spec-draft4 = Spec $ H.fromList-  [ ("$ref"                , (ref                 , noEm))-  , ("multipleOf"          , (multipleOf          , noEm))-  , ("maximum"             , (maximumVal          , noEm))-  , ("minimum"             , (minimumVal          , noEm))-  , ("maxLength"           , (maxLength           , noEm))-  , ("minLength"           , (minLength           , noEm))-  , ("pattern"             , (pattern             , noEm))-  , ("additionalItems"     , (noVal               , objEmbed))-  , ("items"               , (items               , objOrArrayEmbed))-  , ("maxItems"            , (maxItems            , noEm))-  , ("minItems"            , (minItems            , noEm))-  , ("uniqueItems"         , (uniqueItems         , noEm))-  , ("maxProperties"       , (maxProperties       , noEm))-  , ("minProperties"       , (minProperties       , noEm))-  , ("required"            , (required            , noEm))-  , ("properties"          , (properties          , objMembersEmbed))-  , ("patternProperties"   , (patternProperties   , objMembersEmbed))-  , ("additionalProperties", (additionalProperties, objEmbed))-  , ("dependencies"        , (dependencies        , objMembersEmbed))-  , ("enum"                , (enum                , noEm))-  , ("type"                , (typeVal             , noEm))-  , ("allOf"               , (allOf               , arrayEmbed))-  , ("anyOf"               , (anyOf               , arrayEmbed))-  , ("oneOf"               , (oneOf               , arrayEmbed))-  , ("not"                 , (notValidator        , objEmbed))-  , ("definitions"         , (noVal               , objMembersEmbed))-  ]---- | Check if a 'RawSchema' is valid Draft 4 schema.------ This is just a convenience function built by preloading 'validate'--- with the spec schema that describes valid Draft 4 schemas.------ NOTE: It's not actually required to run 'isValidSchema' on--- prospective draft 4 schemas at all. However, it's a good way to--- catch unintentional mistakes in schema documents.-isValidSchema :: RawSchema -> Either (Vector ValErr) Value-isValidSchema r =-  case decode . fromStrict $ $(embedFile "draft4.json") of-    Nothing -> Left $ V.singleton "Schema decode failed (this should never happen)"-    Just s  -> validate (compile draft4 H.empty $ RawSchema "" s) $ Object (_rsObject r)---- | Check that a 'RawSchema' conforms to the JSON Schema Draft 4--- master schema document. Compile it if it does.------ This is just a convenience function built by combining--- 'isValidSchema' and 'compile'.-compileDraft4 :: Graph -> RawSchema -> Either (Vector ValErr) Schema-compileDraft4 g r = isValidSchema r >> return (compile draft4 g r)------------------------------------------------------- * Graph Builder------------------------------------------------------ $ TODO: It would be nice to have a few other functions for building--- out 'Graph's depending on the situation. For instance, there could be--- a function that only fetched "$ref"s which use the "file://" scheme.+import           Import  -- | Take a schema. Retrieve every document either it or its -- subschemas include via the "$ref" keyword. Load a 'Graph' out -- with them. -- -- TODO: This function's URL processing is hacky and needs improvement.-fetchRefs :: Spec -> RawSchema -> Graph -> IO (Either Text Graph)-fetchRefs spec a graph =-  let startingGraph = H.insert (_rsURI a) (_rsObject a) graph-  in foldlM fetch (Right startingGraph) (includeSubschemas a)+fetchReferencedSchemas :: Spec err -> RawSchema -> Graph -> IO (Either Text Graph)+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.+fetchReferencedSchemas'+  :: forall t e m err. (IsString t, MonadError t e, Traversable e, MonadIO m)+  => (Text -> m (e LBS.ByteString))+  -> Spec err+  -> 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)+   where     includeSubschemas :: RawSchema -> Vector RawSchema     includeSubschemas r =       let newId = newResolutionScope (_rsURI r) (_rsObject r)-          xs = H.intersectionWith (\(_,f) x -> f newId x) (_unSpec spec) (_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 -    fetch :: Either Text Graph -> RawSchema -> IO (Either Text Graph)-    fetch (Right g) r =-      case H.lookup "$ref" (_rsObject r) >>= toTxt >>= refAndPointer of-        Nothing     -> return (Right g)+    modFetch :: (Graph -> RawSchema -> m (e Graph)) -> e Graph -> RawSchema -> m (e Graph)+    modFetch f eg rs = join <$> sequence (flip f rs <$> eg)++    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 t = (_rsURI r `combineIdAndRef` s)-          in if T.length t <= 0 || H.member t g || not ("://" `T.isInfixOf` t)-            then return (Right g)-            else do-              eResp <- fetchRef t-              case eResp of-                Left e    -> return (Left e)-                Right obj -> fetchRefs spec (RawSchema t obj) g-    fetch leftGraph _ = return leftGraph+          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+      where+        decodeResponse :: Text -> LBS.ByteString -> m (e Graph)+        decodeResponse url 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)
src/Data/JsonSchema/Core.hs view
@@ -1,47 +1,51 @@+{-# LANGUAGE ScopedTypeVariables #-}+ module Data.JsonSchema.Core where -import           Data.Aeson-import           Data.HashMap.Strict       (HashMap) import qualified Data.HashMap.Strict       as H-import           Data.JsonSchema.Reference import           Data.Maybe-import           Data.Text                 (Text)-import           Data.Vector               (Vector)-import qualified Data.Vector               as V +import           Data.JsonSchema.Reference+import           Import+ -------------------------------------------------- -- * Primary API -------------------------------------------------- -compile :: Spec -> Graph -> RawSchema -> Schema-compile spec g (RawSchema t o) = Schema . catMaybes . H.elems $ H.intersectionWith f (_unSpec spec) o+compile :: forall err. Spec err -> Graph -> RawSchema -> Schema err+compile spec g (RawSchema t o) =+  let maybeValidators = H.intersectionWith f (_unSpec spec) o+  in Schema . catMaybes . H.elems $ maybeValidators   where-    f :: (ValidatorGen, a) -> Value -> Maybe Validator-    f (vGen,_) = vGen spec g $ RawSchema (newResolutionScope t o) o+    f :: ValSpec err -> Value -> Maybe (Value -> [ValidationFailure err])+    f (ValSpec _ construct) valJSON = construct spec g (RawSchema (newResolutionScope t o) o) valJSON -validate :: Schema -> Value -> Either (Vector ValErr) Value-validate schema x =-  let errs = V.concatMap ($ x) $ V.fromList (_unSchema schema)-  in if V.null errs-    then Right x-    else Left errs+validate :: Schema err -> Value -> [ValidationFailure err]+validate schema x = concat . fmap ($ x) . _unSchema $ schema  ----------------------------------------------------- * Types+-- * Schemas -------------------------------------------------- -newtype Spec = Spec { _unSpec :: HashMap Text (ValidatorGen, EmbeddedSchemas) }+newtype Spec err = Spec { _unSpec :: HashMap Text (ValSpec err) } --- | Set of potentially mutually recursive schemas.-type Graph = HashMap Text (HashMap Text Value)+newtype Schema err = Schema { _unSchema :: [Value -> [ValidationFailure err]] } -type ValErr = Text+data RawSchema = RawSchema+  { _rsURI    :: Text+  , _rsObject :: HashMap Text Value+  } -newtype Schema = Schema { _unSchema :: [Validator] }+-- | A mapping of URLs to schemas.+--+-- Each key/value pair provides the components of a RawSchema.+type Graph = HashMap Text (HashMap Text Value) -type Validator = Value -> Vector ValErr+--------------------------------------------------+-- * Validators+-------------------------------------------------- -type ValidatorGen = Spec -> Graph -> RawSchema -> Value -> Maybe Validator+data ValSpec err = ValSpec EmbeddedSchemas (ValidatorConstructor err [ValidationFailure err])  -- | Return a schema's immediate subschemas. --@@ -50,7 +54,28 @@ -- "$ref"s and "id"s that are actual schema keywords. type EmbeddedSchemas = Text -> Value -> Vector RawSchema -data RawSchema = RawSchema-  { _rsURI    :: Text-  , _rsObject :: HashMap Text Value-  }+-- | This is what's used to write most validators in practice.+--+-- Its important that particular validators don't know about the error sum type+-- of the Spec they're going to be used in. That way they can be included in+-- other Specs later without encouraging partial functions.+--+-- This means that a properly written ValidatorConstructor will need its error+-- type modified for use in a Spec. Data.JsonSchema.Helpers provides giveName+-- and modifyName for this purpose.+type ValidatorConstructor schemaErr valErr+   = Spec schemaErr+  -> Graph+  -> RawSchema+  -> Value+  -> Maybe (Value -> valErr)++data ValidationFailure err = ValidationFailure+  { _failureName :: err+  , _failureInfo :: FailureInfo+  } deriving (Show, Read)++data FailureInfo = FailureInfo+  { _validatingData :: Value+  , _offendingData  :: Value+  } deriving (Show, Read)
+ src/Data/JsonSchema/Draft4.hs view
@@ -0,0 +1,140 @@+{-# LANGUAGE TemplateHaskell #-}++module Data.JsonSchema.Draft4 where++import qualified Data.ByteString.Lazy           as LBS+import           Data.FileEmbed+import qualified Data.HashMap.Strict            as H++import           Data.JsonSchema.Core+import           Data.JsonSchema.Draft4.Any+import qualified Data.JsonSchema.Draft4.Arrays as AR+import qualified Data.JsonSchema.Draft4.Numbers as NU+import qualified Data.JsonSchema.Draft4.Objects as OB+import           Data.JsonSchema.Draft4.Strings+import           Data.JsonSchema.Helpers+import           Import++data Draft4Failure+  = MultipleOf++  | Maximum+  | ExclusiveMaximum++  | Minimum+  | ExclusiveMinimum++  | MaxLength+  | MinLength+  | Pattern++  | Items Draft4Failure+  | AdditionalItemsBool+  | AdditionalItemsObject Draft4Failure++  | MaxItems+  | MinItems+  | UniqueItems+  | MaxProperties+  | MinProperties+  | Required++  | Properties Draft4Failure+  | PatternProperties Draft4Failure+  | AdditionalPropertiesBool+  | AdditionalPropertiesObject Draft4Failure++  | SchemaDependency Draft4Failure+  | PropertyDependency++  | Enum+  | TypeValidator+  | AllOf Draft4Failure+  | AnyOf+  | OneOf+  | NotValidator+  | Ref Draft4Failure+  deriving (Eq, Show, Read)++draft4 :: Spec Draft4Failure+draft4 = Spec $ H.fromList+  [ ("$ref"                , ValSpec noEm             (modifyName Ref           ref))+  , ("multipleOf"          , ValSpec noEm             (giveName   MultipleOf    NU.multipleOf))+  , ("maximum"             , ValSpec noEm             (modifyName fMax          NU.maximumVal))+  , ("minimum"             , ValSpec noEm             (modifyName fMin          NU.minimumVal))+  , ("maxLength"           , ValSpec noEm             (giveName   MaxLength     maxLength))+  , ("minLength"           , ValSpec noEm             (giveName   MinLength     minLength))+  , ("pattern"             , ValSpec noEm             (giveName   Pattern       pattern))+  , ("additionalItems"     , ValSpec objEmbed         neverBuild) -- Handled by items.+  , ("items"               , ValSpec objOrArrayEmbed  (modifyName fItems        AR.items))+  , ("maxItems"            , ValSpec noEm             (giveName   MaxItems      AR.maxItems))+  , ("minItems"            , ValSpec noEm             (giveName   MinItems      AR.minItems))+  , ("uniqueItems"         , ValSpec noEm             (giveName   UniqueItems   AR.uniqueItems))+  , ("maxProperties"       , ValSpec noEm             (giveName   MaxProperties OB.maxProperties))+  , ("minProperties"       , ValSpec noEm             (giveName   MinProperties OB.minProperties))+  , ("required"            , ValSpec noEm             (giveName   Required      OB.required))+  , ("properties"          , ValSpec objMembersEmbed  (modifyName fProp         OB.properties))+  , ("patternProperties"   , ValSpec objMembersEmbed  (modifyName fPatProp      OB.patternProperties))+  , ("additionalProperties", ValSpec objEmbed         (modifyName fAddProp      OB.additionalProperties))+  , ("dependencies"        , ValSpec objMembersEmbed  (modifyName fDeps         OB.dependencies))+  , ("enum"                , ValSpec noEm             (giveName   Enum          enum))+  , ("type"                , ValSpec noEm             (giveName   TypeValidator typeValidator))+  , ("allOf"               , ValSpec arrayEmbed       (modifyName AllOf         allOf))+  , ("anyOf"               , ValSpec arrayEmbed       (giveName   AnyOf         anyOf))+  , ("oneOf"               , ValSpec arrayEmbed       (giveName   OneOf         oneOf))+  , ("not"                 , ValSpec objEmbed         (giveName   NotValidator  notValidator))+  , ("definitions"         , ValSpec objMembersEmbed  neverBuild) -- Just contains referenceable schemas.+  ]+  where+    fMax NU.Maximum = Maximum+    fMax NU.ExclusiveMaximum = ExclusiveMaximum++    fMin NU.Minimum = Minimum+    fMin NU.ExclusiveMinimum = ExclusiveMinimum++    fItems (AR.Items err) = Items err+    fItems AR.AdditionalItemsBool = AdditionalItemsBool+    fItems (AR.AdditionalItemsObject err) = AdditionalItemsObject err++    fProp (OB.Properties err) = Properties err+    fProp (OB.PropPattern err) = PatternProperties err+    fProp (OB.PropAdditional OB.AdditionalPropertiesBool) = AdditionalPropertiesBool+    fProp (OB.PropAdditional (OB.AdditionalPropertiesObject err)) = AdditionalPropertiesObject err++    fPatProp (OB.PatternProperties err) = PatternProperties err+    fPatProp (OB.PatternAdditional OB.AdditionalPropertiesBool) = AdditionalPropertiesBool+    fPatProp (OB.PatternAdditional (OB.AdditionalPropertiesObject err)) = AdditionalPropertiesObject err++    fAddProp OB.AdditionalPropertiesBool = AdditionalPropertiesBool+    fAddProp (OB.AdditionalPropertiesObject err) = AdditionalItemsObject err++    fDeps (OB.SchemaDependency err) = SchemaDependency err+    fDeps OB.PropertyDependency = PropertyDependency++-- | Check if a 'RawSchema' is valid Draft 4 schema.+--+-- This is just a convenience function built by preloading 'validate'+-- with the spec schema that describes valid Draft 4 schemas.+--+-- NOTE: It's not actually required to run 'isValidSchema' on+-- prospective draft 4 schemas at all. However, it's a good way to+-- catch unintentional mistakes in schema documents.+isValidSchema :: RawSchema -> [ValidationFailure Draft4Failure]+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)++-- | Check that a 'RawSchema' conforms to the JSON Schema Draft 4+-- master schema document. Compile it if it does.+--+-- This is just a convenience function built by combining+-- 'isValidSchema' and 'compile'.+--+-- 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 g r =+  case isValidSchema r of+    []   -> Right (compile draft4 g r)+    errs -> Left errs
+ src/Data/JsonSchema/Draft4/Any.hs view
@@ -0,0 +1,115 @@++module Data.JsonSchema.Draft4.Any where++import           Control.Monad+import qualified Data.HashMap.Strict       as H+import           Data.JsonPointer+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+import           Data.JsonSchema.Reference+import           Import++-- | http://json-schema.org/latest/json-schema-validation.html#anchor76+--+--  > The value of this keyword MUST be an array.+--  > This array MUST have at least one element.+--  > Elements in the array MUST be unique.+--  >+--  > Elements in the array MAY be of any type, including null.+--+-- NOTE: We actually respect this, and don't build the validator+-- if any of the elements aren't unique.+enum :: ValidatorConstructor err [FailureInfo]+enum _ _ _ val@(Array vs) = do+  when (V.null vs || not (allUniqueValues vs)) Nothing+  Just $ \x ->+    if V.elem x vs+      then mempty+      else pure (FailureInfo val x)+enum _ _ _ _ = Nothing++typeValidator :: ValidatorConstructor err [FailureInfo]+typeValidator _ _ _ (String val) = Just $ \x -> isJsonType x (pure val)+typeValidator _ _ _ (Array vs) = do+  ts <- traverse toTxt vs+  unless (allUnique ts) Nothing+  Just (`isJsonType` ts)+typeValidator _ _ _ _ = Nothing++isJsonType :: Value -> Vector Text -> [FailureInfo]+isJsonType x xs =+  case x of+    (Null)     -> f "null"    xs+    (Array _)  -> f "array"   xs+    (Bool _)   -> f "boolean" xs+    (Object _) -> f "object"  xs+    (String _) -> f "string"  xs+    (Number y) ->+      case toBoundedInteger y :: Maybe Int of+        Nothing -> f "number" xs+        Just _  -> if V.elem "integer" xs+                     then mempty+                     else f "number" xs+  where+    f :: Text -> Vector Text -> [FailureInfo]+    f t ts = if V.elem t ts+               then mempty+               else pure $ FailureInfo (Array (String <$> xs)) x++allOf :: ValidatorConstructor err [ValidationFailure err]+allOf spec g s (Array vs) = do+  os <- traverse toObj vs+  let subSchemas = compile spec g . RawSchema (_rsURI s) <$> V.toList os+  Just $ \x -> join $ flip validate x <$> subSchemas+allOf _ _ _ _ = Nothing++anyOf :: ValidatorConstructor err [FailureInfo]+anyOf spec g s val@(Array vs) = do+  os <- traverse toObj vs+  let subSchemas = compile spec g . RawSchema (_rsURI s) <$> os+  Just $ \x ->+    if any null (flip validate x <$> subSchemas)+      then mempty+      else pure (FailureInfo val x)+anyOf _ _ _ _ = Nothing++oneOf :: ValidatorConstructor err [FailureInfo]+oneOf spec g s val@(Array vs) = do+  os <- traverse toObj $ V.toList vs+  let subSchemas = compile spec g . RawSchema (_rsURI s) <$> os+  Just $ \x ->+    if (length . filter null $ flip validate x <$> subSchemas) == 1+      then mempty+      else pure (FailureInfo val x)+oneOf _ _ _ _ = Nothing++notValidator :: ValidatorConstructor err [FailureInfo]+notValidator spec g s val@(Object o) = do+  let sub = compile spec g (RawSchema (_rsURI s) o)+  Just $ \x ->+    case validate sub x of+      [] -> pure (FailureInfo val x)+      _  -> mempty+notValidator _ _ _ _ = Nothing++-- http://tools.ietf.org/html/draft-pbryan-zyp-json-ref-03+--+-- TODO: Any members other than "$ref" in a JSON Reference object SHALL be+-- ignored.+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+ref _ _ _ _ = Nothing
+ src/Data/JsonSchema/Draft4/Arrays.hs view
@@ -0,0 +1,91 @@++module Data.JsonSchema.Draft4.Arrays where++import           Control.Monad+import qualified Data.HashMap.Strict     as H+import qualified Data.Vector             as V++import           Data.JsonSchema.Core+import           Data.JsonSchema.Helpers+import           Import++data ItemsFailure err = Items err | AdditionalItemsBool | AdditionalItemsObject err++-- | A combination of items and additionalItems.+items :: ValidatorConstructor err [ValidationFailure (ItemsFailure err)]+items 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 Items) . validate subSchema+      _        -> mempty+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)+  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+      _ -> mempty+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 _ _ _ val@(Bool v) =+  Just $ \x ->+    case x of+      Array ys ->+        if not v && V.length ys > 0+          then pure $ ValidationFailure AdditionalItemsBool (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+      _        -> mempty+additionalItems _ _ _ _ = Nothing++maxItems :: ValidatorConstructor err [FailureInfo]+maxItems _ _ _ val = do+  n <- fromJSONInt val+  greaterThanZero n+  Just $ \x ->+    case x of+      Array ys ->+        if V.length ys > n+          then pure (FailureInfo val x)+          else mempty+      _ -> mempty++minItems :: ValidatorConstructor err [FailureInfo]+minItems _ _ _ val = do+  n <- fromJSONInt val+  greaterThanZero n+  Just $ \x ->+    case x of+      Array ys ->+        if V.length ys < n+          then pure (FailureInfo val x)+          else mempty+      _ -> mempty++uniqueItems :: ValidatorConstructor err [FailureInfo]+uniqueItems _ _ _ val@(Bool v) = do+  unless v Nothing+  Just $ \x ->+    case x of+      (Array ys) -> if allUniqueValues ys+        then mempty+        else pure (FailureInfo val x)+      _ -> mempty+uniqueItems _ _ _ _ = Nothing
+ src/Data/JsonSchema/Draft4/Numbers.hs view
@@ -0,0 +1,61 @@++module Data.JsonSchema.Draft4.Numbers where++import           Data.Fixed              (mod')+import qualified Data.HashMap.Strict     as H+import           Data.Scientific++import           Data.JsonSchema.Core+import           Data.JsonSchema.Helpers+import           Import++data MaximumFailure = Maximum | ExclusiveMaximum+data MinimumFailure = Minimum | ExclusiveMinimum++multipleOf :: ValidatorConstructor err [FailureInfo]+multipleOf _ _ _ val@(Number n) = do+  greaterThanZero n+  Just $ \x ->+    case x of+      Number y ->+        if y `mod'` n /= 0+          then pure (FailureInfo val x)+          else mempty+      _ -> mempty+multipleOf _ _ _ _ = Nothing++maximumVal :: ValidatorConstructor err [ValidationFailure MaximumFailure]+maximumVal _ _ s val@(Number n) =+  Just $ \x ->+    case x of+      Number y ->+        let (greater, err) = checkExclusive+        in if y `greater` n+          then pure $ ValidationFailure err (FailureInfo val x)+          else mempty+      _ -> mempty+  where+    checkExclusive :: (Scientific -> Scientific -> Bool, MaximumFailure)+    checkExclusive =+      case H.lookup "exclusiveMaximum" (_rsObject s) of+        Just (Bool a) -> if a then ((>=), ExclusiveMaximum) else ((>), Maximum)+        _             -> ((>), Maximum)+maximumVal _ _ _ _ = Nothing++minimumVal :: ValidatorConstructor err [ValidationFailure MinimumFailure]+minimumVal _ _ s val@(Number n) =+  Just $ \x ->+    case x of+      Number y ->+        let (lesser, err) = checkExclusive+        in if y `lesser` n+          then pure $ ValidationFailure err (FailureInfo val x)+          else mempty+      _ -> mempty+  where+    checkExclusive :: (Scientific -> Scientific -> Bool, MinimumFailure)+    checkExclusive =+      case H.lookup "exclusiveMinimum" (_rsObject s) of+        Just (Bool a) -> if a then ((<=), ExclusiveMinimum) else ((<), Minimum)+        _             -> ((<), Minimum)+minimumVal _ _ _ _ = Nothing
+ src/Data/JsonSchema/Draft4/Objects.hs view
@@ -0,0 +1,118 @@+{-# LANGUAGE ScopedTypeVariables #-}++module Data.JsonSchema.Draft4.Objects+  ( module Data.JsonSchema.Draft4.Objects+  , module Data.JsonSchema.Draft4.Objects.Properties+  ) where++import           Control.Monad+import           Data.Aeson+import           Data.Hashable+import qualified Data.HashMap.Strict     as H+import qualified Data.Vector             as V++import           Data.JsonSchema.Core+import           Data.JsonSchema.Draft4.Objects.Properties+import           Data.JsonSchema.Helpers+import           Import++data DependencyFailure err = SchemaDependency err | PropertyDependency++maxProperties :: ValidatorConstructor err [FailureInfo]+maxProperties _ _ _ val = do+  n <- fromJSONInt val+  greaterThanZero n+  Just $ \x ->+    case x of+      Object o ->+        if H.size o > n+          then pure (FailureInfo val x)+          else mempty+      _ -> mempty++minProperties :: ValidatorConstructor err [FailureInfo]+minProperties _ _ _ val = do+  n <- fromJSONInt val+  greaterThanZero n+  Just $ \x ->+    case x of+      Object o ->+        if H.size o < n+          then pure (FailureInfo val x)+          else mempty+      _ -> mempty++required :: ValidatorConstructor err [FailureInfo]+required _ _ _ val@(Array vs) = do+  when (V.length vs == 0) Nothing+  ts <- traverse toTxt vs+  let a = vectorToMapSet ts+  when (H.size a /= V.length ts) Nothing+  Just $ \x ->+    case x of+      Object o ->+        if H.size (H.difference a o) > 0+          then pure (FailureInfo val x)+          else mempty+      _ -> mempty+  where+    vectorToMapSet :: (Eq a, Hashable a) => Vector a -> HashMap a Bool+    vectorToMapSet vec = H.fromList . V.toList $ (\x -> (x, True)) <$> vec -- TODO: use a fold.+required _ _ _ _ = Nothing++-- http://json-schema.org/latest/json-schema-validation.html#anchor70+--+-- > This keyword's value MUST be an object.+-- > Each value of this object MUST be either an object or an array.+-- >+-- > If the value is an object, it MUST be a valid JSON Schema.+-- > This is called a schema dependency.+-- >+-- > If the value is an array, it MUST have at least one element.+-- > Each element MUST be a string, and elements in the array MUST be unique.+-- > This is called a property dependency.+dependencies :: ValidatorConstructor err [ValidationFailure (DependencyFailure err)]+dependencies spec g s val@(Object o) = do+  let vs = H.toList o+      schemaDeps = vs >>= toSchemaDep spec g+      propDeps = vs >>= toPropDep+  when (length schemaDeps + length propDeps /= length vs) Nothing+  Just $ \x ->+    case x of+      Object y ->+        let schemaFailures = join $ valSD y <$> schemaDeps+            propertyFailures = join $ valPD y <$> propDeps+        in schemaFailures <> propertyFailures+      _ -> mempty+  where+    toSchemaDep :: Spec a -> Graph -> (Text, Value) -> [(Text, Schema a)]+    toSchemaDep spc gr (t, Object ob) = pure (t, compile spc gr $ RawSchema (_rsURI s) ob)+    toSchemaDep _ _ _ = mempty++    toPropDep :: (Text, Value) -> [(Text, Vector Text)]+    toPropDep (t, Array a) =+      if V.length a <= 0+        then mempty+        else case traverse toTxt a of+          Nothing -> mempty+          Just ts ->+            if allUnique ts+              then pure (t, ts)+              else mempty+    toPropDep _ = mempty++    valSD :: HashMap Text Value -> (Text, Schema err) -> [ValidationFailure (DependencyFailure err)]+    valSD d (k, subSchema) =+      case H.lookup k d of+        Nothing -> mempty+        Just _  -> modifyFailureName SchemaDependency <$> validate subSchema (Object d)++    valPD :: HashMap Text Value -> (Text, Vector Text) -> [ValidationFailure (DependencyFailure err)]+    valPD d (k, ks) =+      case H.lookup k d of+        Nothing -> mempty+        Just _  ->+          case traverse (flip H.lookup d) ks of+            Nothing -> pure $ ValidationFailure PropertyDependency (FailureInfo val (Object d))+            Just _  -> mempty+dependencies _ _ _ _ = Nothing
+ src/Data/JsonSchema/Draft4/Objects/Properties.hs view
@@ -0,0 +1,148 @@+{-# LANGUAGE ScopedTypeVariables #-}++module Data.JsonSchema.Draft4.Objects.Properties where++import           Control.Monad+import           Data.Aeson+import qualified Data.HashMap.Strict     as H+import           Data.Maybe+import qualified Data.Text               as T+import           Text.RegexPR++import           Data.JsonSchema.Core+import           Data.JsonSchema.Helpers+import           Import++data PropertiesFailure err+  = Properties err+  | PropPattern err+  | PropAdditional (AdditionalPropertiesFailure err)++data PatternPropertiesFailure err+  = PatternProperties err+  | PatternAdditional (AdditionalPropertiesFailure err)++data AdditionalPropertiesFailure err+  = AdditionalPropertiesBool+  | AdditionalPropertiesObject err++-- | In order of what's tried: properties, patternProperties, additionalProperties+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)+  when (isNothing mProps && isNothing mPatProp && isNothing mAddProp) Nothing+  Just $ \x ->+    case x of+      Object y ->+        let (propFailures, remaining) = runMaybeVal' mProps (Object y)+            patternFailures           = fst $ runMaybeVal' mPatProp (Object y)+            remaining'                = snd $ runMaybeVal' mPatProp remaining+            additionalFailures        = runMaybeVal mAddProp remaining'+        in fmap (modifyFailureName Properties) propFailures+             <> fmap (modifyFailureName PropPattern) patternFailures+             <> fmap (modifyFailureName PropAdditional) additionalFailures+      _ -> mempty+  where+    propertiesUnmatched :: Value -> Maybe (Value -> ([ValidationFailure err], Value))+    propertiesUnmatched (Object o) = do+      os <- traverse toObj o+      let matchedSchemas = compile spec g . RawSchema (_rsURI s) <$> os+      Just (\x ->+        case x of+          Object y ->+            let rawFailures = H.intersectionWith validate matchedSchemas y+                failures = join (H.elems rawFailures)+                leftovers = Object (H.difference y matchedSchemas)+            in (failures, leftovers)+          z -> (mempty, z))+    propertiesUnmatched _ = Nothing++patternProperties :: ValidatorConstructor err [ValidationFailure (PatternPropertiesFailure err)]+patternProperties spec g s val = do+  when (H.member "properties" (_rsObject 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)+  when (isNothing mPatternProps && isNothing mAdditionalProps) Nothing+  Just $ \x ->+    case x of+      Object y ->+        let (ppFailures, remaining') = runMaybeVal' mPatternProps (Object y)+            addFailures              = runMaybeVal mAdditionalProps remaining'+        in fmap (modifyFailureName PatternProperties) ppFailures <> fmap (modifyFailureName PatternAdditional) addFailures+      _ -> mempty++patternUnmatched+  :: Spec err+  -> Graph+  -> RawSchema+  -> Value+  -> Maybe (Value -> ([ValidationFailure err], Value))+patternUnmatched spec g s (Object val) = do+  os <- traverse toObj val+  let subSchemas = compile spec g . RawSchema (_rsURI s) <$> os+  Just (\x ->+    case x of+      Object y -> let ms = H.foldlWithKey' (matches subSchemas) mempty y+                  in (H.foldl' runVals mempty ms, Object (leftovers ms))+      _        -> (mempty, x))+  where+    matches+      :: HashMap Text (Schema a)+      -> HashMap Text (Value, [Schema a])+      -> Text+      -> Value+      -> HashMap Text (Value, [Schema a])+    matches subSchemas acc k v = H.insert k (v, H.foldlWithKey' (match k) mempty subSchemas) acc++    match+      :: Text+      -> [Schema a]+      -> Text+      -> Schema a+      -> [Schema a]+    match k acc r subSchema =+      case matchRegexPR (T.unpack r) (T.unpack k) of+        Nothing -> acc+        Just _  -> pure subSchema <> acc++    runVals+      :: [ValidationFailure err]+      -> (Value, [Schema err])+      -> [ValidationFailure err]+    runVals acc (v,subSchema) = (subSchema >>= flip validate v) <> acc++    leftovers :: HashMap Text (Value, [Schema a]) -> HashMap Text Value+    leftovers possiblyMatched = fst <$> H.filter (null . snd) possiblyMatched+patternUnmatched _ _ _ _ = Nothing++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+  runAdditionalProperties spec g s val++-- | An additionalProperties validator than never disables itself.+--+-- Not meant to be used standalone, but useful inside of the properties+-- and patternProperties validators.+runAdditionalProperties :: ValidatorConstructor err [ValidationFailure (AdditionalPropertiesFailure err)]+runAdditionalProperties _ _ _ val@(Bool v) =+  Just $ \x ->+    case x of+      Object y ->+        if not v && H.size y > 0+          then pure $ ValidationFailure AdditionalPropertiesBool (FailureInfo val x)+          else mempty+      _ -> mempty+runAdditionalProperties spec g s (Object o) =+  let sub = compile spec g (RawSchema (_rsURI s) o)+  in Just $ \x ->+    case x of+      Object y -> H.elems y >>= fmap (modifyFailureName AdditionalPropertiesObject) . validate sub+      _        -> mempty+runAdditionalProperties _ _ _ _ = Nothing
+ src/Data/JsonSchema/Draft4/Strings.hs view
@@ -0,0 +1,44 @@++module Data.JsonSchema.Draft4.Strings where++import qualified Data.Text               as T+import           Text.RegexPR++import           Data.JsonSchema.Core+import           Data.JsonSchema.Helpers+import           Import++maxLength :: ValidatorConstructor err [FailureInfo]+maxLength _ _ _ val = do+  n <- fromJSONInt val+  greaterThanZero n+  Just $ \x ->+    case x of+      String y ->+        if T.length y > n+          then pure (FailureInfo val x)+          else mempty+      _ -> mempty++minLength :: ValidatorConstructor err [FailureInfo]+minLength _ _ _ val = do+  n <- fromJSONInt val+  greaterThanZero n+  Just $ \x ->+    case x of+      String y ->+        if T.length y < n+          then pure (FailureInfo val x)+          else mempty+      _ -> mempty++pattern :: ValidatorConstructor err [FailureInfo]+pattern _ _ _ val@(String t) =+  Just $ \x ->+    case x of+      String y ->+        case matchRegexPR (T.unpack t) (T.unpack y) of+          Nothing -> pure (FailureInfo val x)+          Just _  -> mempty+      _ -> mempty+pattern _ _ _ _ = Nothing
src/Data/JsonSchema/Helpers.hs view
@@ -1,156 +1,111 @@ module Data.JsonSchema.Helpers where -import           Control.Applicative-import           Control.Monad-import           Data.Aeson-import           Data.Hashable-import           Data.HashMap.Strict  (HashMap)+import           Control.Exception+import qualified Data.ByteString.Lazy as LBS import qualified Data.HashMap.Strict  as H-import           Data.JsonSchema.Core-import           Data.List-import           Data.Monoid import           Data.Scientific-import           Data.Text            (Text)+import qualified Data.Set as S import qualified Data.Text            as T-import           Data.Traversable-import           Data.Vector          (Vector) import qualified Data.Vector          as V-import           Text.RegexPR+import           Network.HTTP.Client +import           Data.JsonSchema.Core+import           Import+ ----------------------------------------------------- * Embedded Schema Layouts+-- * Embedded schemas finders --------------------------------------------------  noEm :: EmbeddedSchemas-noEm _ _ = V.empty+noEm _ _ = mempty  objEmbed :: EmbeddedSchemas-objEmbed t (Object o) = V.singleton $ RawSchema t o-objEmbed _ _ = V.empty+objEmbed t (Object o) = pure (RawSchema t o)+objEmbed _ _ = mempty --- TODO: optimize arrayEmbed :: EmbeddedSchemas-arrayEmbed t (Array vs) = V.concat . V.toList $ objEmbed t <$> vs-arrayEmbed _ _ = V.empty+arrayEmbed t (Array vs) = objEmbed t =<< vs+arrayEmbed _ _ = mempty  objOrArrayEmbed :: EmbeddedSchemas objOrArrayEmbed t v@(Object _) = objEmbed t v objOrArrayEmbed t v@(Array _) = arrayEmbed t v-objOrArrayEmbed _ _ = V.empty+objOrArrayEmbed _ _ = mempty  objMembersEmbed :: EmbeddedSchemas-objMembersEmbed t (Object o) = V.concat $ objEmbed t <$> H.elems o-objMembersEmbed _ _ = V.empty+objMembersEmbed t (Object o) = objEmbed t =<< V.fromList (H.elems o)+objMembersEmbed _ _ = mempty  ----------------------------------------------------- * Validator Helpers+-- * Modify Validators for use in Specs -------------------------------------------------- -patternPropertiesMatches-  :: Spec-  -> Graph-  -> RawSchema-  -> Value-  -> Maybe (Value -> (Vector ValErr, Value))-patternPropertiesMatches spec g s (Object val) = do-  os <- traverse toObj val-  let vs = compile spec g . RawSchema (_rsURI s) <$> os-  Just (\x ->-    case x of-      Object y -> let ms = matches (hmToVector vs) <$> hmToVector y-                  in (ms >>= runVals, leftovers ms)-      _        -> (mempty, x))-  where-    matches-      :: Vector (Text, Schema)-      -> (Text, Value)-      -> (Text, Value, Vector Schema)-    matches ss (k, v) = (k, v, ss >>= match k)--    match :: Text -> (Text, Schema) -> Vector Schema-    match k (r, sc) =-      case matchRegexPR (T.unpack r) (T.unpack k) of-        Nothing -> mempty-        Just _  -> V.singleton sc--    runVals :: (Text, Value, Vector Schema) -> Vector ValErr-    runVals (_,v,ss) = join . vLefts $ validate <$> ss <*> pure v--    leftovers :: Vector (Text, Value, Vector Schema) -> Value-    leftovers possiblyMatched =-      let unmatched = V.filter (\(_,_,ss) -> V.length ss == 0) possiblyMatched-      in Object . vectorToHm $ (\(v,k,_) -> (v,k)) <$> unmatched-patternPropertiesMatches _ _ _ _ = Nothing+-- | 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) -isJsonType :: Value -> Vector Text -> Vector ValErr-isJsonType x xs =-  case x of-    (Null)     -> f "null"    xs ("null" :: Text)-    (Array y)  -> f "array"   xs y-    (Bool y)   -> f "boolean" xs y-    (Object y) -> f "object"  xs y-    (String y) -> f "string"  xs y-    (Number y) ->-      case toBoundedInteger y :: Maybe Int of-        Nothing -> f "number" xs y-        Just _  -> if V.elem "number" xs || V.elem "integer" xs-                     then mempty-                     else mkErr y xs+modifyName+  :: forall valErr schemaErr. (valErr -> schemaErr)+  -> ValidatorConstructor schemaErr [ValidationFailure valErr]+  -> ValidatorConstructor schemaErr [ValidationFailure schemaErr]+modifyName failureHandler = (fmap.fmap.fmap.fmap.fmap.fmap.fmap) f   where-    f :: (Show a) => Text -> Vector Text -> a -> Vector ValErr-    f t ts d = if V.elem t ts-                 then mempty-                 else mkErr d ts+    f :: ValidationFailure valErr -> ValidationFailure schemaErr+    f (ValidationFailure a b) = ValidationFailure (failureHandler a) b -    mkErr :: (Show a) => a -> Vector Text -> Vector ValErr-    mkErr y ts = V.singleton (tshow y <> " is not one of the types " <> tshow ts)+-- | 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+-- validators. If one of those referenced parts is itself a valid reference we+-- need to have fetched the correct value for it. So validators that won't run are+-- different than non-validator objects, because even if a non-validator object has+-- a $ref" keyword it's not a valid reference and shouldn't be fetched.+neverBuild :: ValidatorConstructor err [ValidationFailure err]+neverBuild _ _ _ _ = Nothing  -------------------------------------------------- -- * Utils -------------------------------------------------- -vLefts :: Vector (Either a b) -> Vector a-vLefts = V.concatMap f-  where-    f :: Either a b -> Vector a-    f (Left e)  = V.singleton e-    f (Right _) = mempty+-- | Export the fetch function used by fetchReferencedSchemas+defaultFetch :: Text -> IO (Either Text LBS.ByteString)+defaultFetch url = do+  eResp <- catch (Right <$> simpleHttp') handler+  case eResp of+    Left e  -> return $ Left e+    Right b -> return $ Right b -vRights :: Vector (Either a b) -> Vector b-vRights = V.concatMap f-  where-    f :: Either a b -> Vector b-    f (Left _)  = mempty-    f (Right a) = V.singleton a+    where+      handler :: SomeException -> IO (Either Text LBS.ByteString)+      handler e = return . Left . T.pack . show $ e +      -- Modeled on Network.Http.Conduit.simpleHttp from http-conduit.+      -- simpleHttp also sets "Connection: close"+      simpleHttp' :: IO LBS.ByteString+      simpleHttp' = do+        man <- newManager defaultManagerSettings+        req <- parseUrl (T.unpack url)+        responseBody <$> httpLbs req { requestHeaders = ("Connection", "close") : requestHeaders req } man++modifyFailureName :: (a -> b) -> ValidationFailure a -> ValidationFailure b+modifyFailureName f (ValidationFailure a b) = ValidationFailure (f a) b+ eitherToMaybe :: Either a b -> Maybe b eitherToMaybe (Left _)  = Nothing eitherToMaybe (Right a) = Just a -vectorOfElems :: HashMap k a -> Vector a-vectorOfElems = V.fromList . H.elems--hmToVector :: HashMap k a -> Vector (k, a)-hmToVector = V.fromList . H.toList--vectorToHm :: (Eq k, Hashable k) => Vector (k, a) -> HashMap k a-vectorToHm = H.fromList . V.toList--runMaybeVal :: Maybe Validator -> Value -> Vector ValErr+runMaybeVal :: Maybe (Value -> [a]) -> Value -> [a] runMaybeVal Nothing _ = mempty-runMaybeVal (Just val) d = val d+runMaybeVal (Just val) x = val x  runMaybeVal'-  :: Maybe (Value -> (Vector ValErr, Value))+  :: Maybe (Value -> ([a], Value))   -> Value-  -> (Vector ValErr, Value)-runMaybeVal' Nothing d = (mempty, d)-runMaybeVal' (Just val) d = val d---- TODO: optimize--- see here: http://comments.gmane.org/gmane.comp.lang.haskell.cafe/106242-allUnique :: (Eq a) => Vector a -> Bool-allUnique bs = length (nub (V.toList bs)) == V.length bs+  -> ([a], Value)+runMaybeVal' Nothing x = (mempty, x)+runMaybeVal' (Just val) x = val x  toObj :: Value -> Maybe (HashMap Text Value) toObj (Object a) = Just a@@ -169,3 +124,37 @@  tshow :: Show a => a -> Text tshow = T.pack . show++allUnique :: (Ord a) => Vector a -> Bool+allUnique xs = S.size (S.fromList (V.toList xs)) == V.length xs++-- | This needs benchmarking, but it can't be as bad as using O(n^2) nub.+-- (We can't use our allUnique function directly on Values because they're+-- not an instance of Ord).+allUniqueValues :: Vector Value -> Bool+allUniqueValues = allUnique . fmap OrdValue++newtype OrdValue = OrdValue Value deriving Eq++instance Ord OrdValue where+  (OrdValue Null) `compare` (OrdValue Null) = EQ+  (OrdValue Null) `compare` _               = LT+  _               `compare` (OrdValue Null) = GT++  (OrdValue (Bool x)) `compare` (OrdValue (Bool y)) = x `compare` y+  (OrdValue (Bool _)) `compare` _                   = LT+  _                   `compare` (OrdValue (Bool _)) = GT++  (OrdValue (Number x)) `compare` (OrdValue (Number y)) = x `compare` y+  (OrdValue (Number _)) `compare` _                     = LT+  _                     `compare` (OrdValue (Number _)) = GT++  (OrdValue (String x)) `compare` (OrdValue (String y)) = x `compare` y+  (OrdValue (String _)) `compare` _                     = LT+  _                     `compare` (OrdValue (String _)) = GT++  (OrdValue (Array xs)) `compare` (OrdValue (Array ys)) = (OrdValue <$> xs) `compare` (OrdValue <$> ys)+  (OrdValue (Array _))  `compare` _                     = LT+  _                     `compare` (OrdValue (Array _))  = GT++  (OrdValue (Object x)) `compare` (OrdValue (Object y)) = H.toList (OrdValue <$> x) `compare` H.toList (OrdValue <$> y)
src/Data/JsonSchema/Reference.hs view
@@ -3,20 +3,11 @@  module Data.JsonSchema.Reference where -import           Control.Applicative-import           Control.Arrow-import           Control.Exception-import           Control.Monad-import           Data.Aeson-import           Data.ByteString.Lazy (ByteString)-import           Data.HashMap.Strict  (HashMap)-import qualified Data.HashMap.Strict  as H-import           Data.Monoid-import           Data.Text            (Text)-import qualified Data.Text            as T-import           Network.HTTP.Client-import           Prelude              hiding (foldr)+import qualified Data.HashMap.Strict as H+import qualified Data.Text           as T +import           Import+ combineIdAndRef :: Text -> Text -> Text combineIdAndRef a b   | "://" `T.isInfixOf` b              = b@@ -46,23 +37,3 @@     getParts [x]   = Just (x,"")     getParts [x,y] = Just (x,y)     getParts _     = Nothing--fetchRef :: Text -> IO (Either Text (HashMap Text Value))-fetchRef t = do-  eResp <- safeGet t-  case eResp of-    Left e  -> return $ Left e-    Right b -> return . left T.pack $ eitherDecode b--safeGet :: Text -> IO (Either Text ByteString)-safeGet url = catch (Right <$> simpleHttp') handler-  where-    handler :: SomeException -> IO (Either Text ByteString)-    handler e = return . Left . T.pack . show $ e--    -- We don't want to depend on http-conduit, but Network.Http.Conduit.simpleHttp-    -- is the model for this function. simpleHttp also sets "Connection: close".-    simpleHttp' :: IO ByteString-    simpleHttp' = fmap responseBody $ withManager defaultManagerSettings $ \man -> do-      req <- parseUrl (T.unpack url)-      httpLbs req { requestHeaders = ("Connection", "close") : requestHeaders req } man
− src/Data/JsonSchema/Validators.hs
@@ -1,468 +0,0 @@--- | This is generally meant to be an internal module. It's only--- exposed in case you want to make your own 'Spec'. If you just--- want to use JSON Schema Draft 4 use the preassembled--- 'Data.JsonSchema.draft4' instead.--module Data.JsonSchema.Validators where--import           Control.Applicative-import           Control.Monad-import           Data.Aeson-import           Data.Either-import           Data.Fixed                (mod')-import           Data.Hashable-import           Data.HashMap.Strict       (HashMap)-import qualified Data.HashMap.Strict       as H-import           Data.JsonPointer-import           Data.JsonSchema.Core-import           Data.JsonSchema.Helpers-import           Data.JsonSchema.Reference-import           Data.Maybe-import           Data.Monoid-import           Data.Scientific-import           Data.Text                 (Text)-import qualified Data.Text                 as T-import           Data.Text.Encoding-import           Data.Traversable-import           Data.Vector               (Vector)-import qualified Data.Vector               as V-import           Network.HTTP.Types.URI-import           Text.RegexPR------------------------------------------------------- * Number Validators-----------------------------------------------------multipleOf :: ValidatorGen-multipleOf _ _ _ (Number val) = do-  greaterThanZero val-  Just (\x ->-    case x of-      Number y ->-        if y `mod'` val /= 0-          then V.singleton (tshow y <> " isn't a multiple of " <> tshow val)-          else mempty-      _ -> mempty)-multipleOf _ _ _ _ = Nothing--maximumVal :: ValidatorGen-maximumVal _ _ s (Number val) =-  Just (\x ->-    case x of-      Number y ->-        if y `greater` val-          then V.singleton (tshow y <> " fails to validate against maximum " <> tshow val)-          else mempty-      _ -> mempty)-  where-    greater :: Scientific -> Scientific -> Bool-    greater =-      case H.lookup "exclusiveMaximum" (_rsObject s) of-        Just (Bool a) -> if a then (>=) else (>)-        _             -> (>)-maximumVal _ _ _ _ = Nothing--minimumVal :: ValidatorGen-minimumVal _ _ s (Number val) =-  Just (\x ->-    case x of-      Number y ->-        if y `lesser` val-          then V.singleton (tshow y <> " fails to validate against minimum " <> tshow val)-          else mempty-      _ -> mempty)-  where-    lesser :: Scientific -> Scientific -> Bool-    lesser =-      case H.lookup "exclusiveMinimum" (_rsObject s) of-        Just (Bool a) -> if a then (<=) else (<)-        _             -> (<)-minimumVal _ _ _ _ = Nothing------------------------------------------------------- * String Validators-----------------------------------------------------maxLength :: ValidatorGen-maxLength _ _ _ v = do-  val <- fromJSONInt v-  greaterThanZero val-  Just (\x ->-    case x of-      String y ->-        if T.length y > val-          then V.singleton (y <> " is greater than maxLength " <> tshow val)-          else mempty-      _ -> mempty)--minLength :: ValidatorGen-minLength _ _ _ v = do-  val <- fromJSONInt v-  greaterThanZero val-  Just (\x ->-    case x of-      String y ->-        if T.length y < val-          then V.singleton (y <> " is less than minLength " <> tshow val)-          else mempty-      _ -> mempty)--pattern :: ValidatorGen-pattern _ _ _ (String val) =-  Just (\x ->-    case x of-      String t ->-        case matchRegexPR (T.unpack val) (T.unpack t) of-          Nothing -> V.singleton (t <> " fails to validate against pattern " <> val)-          Just _  -> mempty-      _ -> mempty)-pattern _ _ _ _ = Nothing------------------------------------------------------- * Array Validators------------------------------------------------------- | Also covers additionalItems.-items :: ValidatorGen-items spec g s (Object val) =-  let sub = compile spec g (RawSchema (_rsURI s) val)-  in Just (\x ->-    case x of-      Array ys -> ys >>= either id mempty . validate sub-      _        -> mempty)-items spec g s (Array vs) = do-  os <- traverse toObj vs-  let ss = compile spec g . RawSchema (_rsURI s) <$> os-  let addItems = do-        a <- H.lookup "additionalItems" (_rsObject s)-        additionalItems spec g s a-  Just (\x ->-    case x of-      (Array ys) ->-        let extras = V.drop (V.length os) ys-        in join (either id mempty <$> V.zipWith validate ss ys)-           <> runMaybeVal addItems (Array extras)-      _ -> mempty)-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 {}.-additionalItems :: ValidatorGen-additionalItems _ _ _ (Bool val) =-  Just (\x ->-    case x of-      Array ys ->-        if not val && V.length ys > 0-          then V.singleton ("Val error against additionalItems false for: " <> tshow x)-          else mempty-      _ -> mempty)-additionalItems spec g s (Object val) =-  let sub = compile spec g (RawSchema (_rsURI s) val)-  in Just (\x ->-    case x of-      Array ys -> ys >>= either id mempty . validate sub-      _        -> mempty)-additionalItems _ _ _ _ = Nothing--maxItems :: ValidatorGen-maxItems _ _ _ v = do-  val <- fromJSONInt v-  greaterThanZero val-  Just (\x ->-    case x of-      Array ys ->-        if V.length ys > val-          then V.singleton (tshow ys <> " has more items than maxItems " <> tshow val)-          else mempty-      _ -> mempty)--minItems :: ValidatorGen-minItems _ _ _ v = do-  val <- fromJSONInt v-  greaterThanZero val-  Just (\x ->-    case x of-      Array ys ->-        if V.length ys < val-          then V.singleton (tshow ys <> " has fewer items than minItems " <> tshow val)-          else mempty-      _ -> mempty)--uniqueItems :: ValidatorGen-uniqueItems _ _ _ (Bool val) = do-  unless val Nothing-  Just (\x ->-    case x of-      (Array ys) -> if allUnique ys-        then mempty-        else V.singleton ("Val error against uniqueItems " <> tshow val <> " for: " <> tshow x)-      _ -> mempty)-uniqueItems _ _ _ _ = Nothing------------------------------------------------------- * Object Validators-----------------------------------------------------maxProperties :: ValidatorGen-maxProperties _ _ _ v = do-  val <- fromJSONInt v-  greaterThanZero val-  Just (\x ->-    case x of-      Object o ->-        if H.size o > val-          then V.singleton (tshow o <> " has more members than maxProperties " <> tshow val)-          else mempty-      _ -> mempty)--minProperties :: ValidatorGen-minProperties _ _ _ v = do-  val <- fromJSONInt v-  greaterThanZero val-  Just (\x ->-    case x of-      Object o ->-        if H.size o < val-          then V.singleton (tshow o <> " has fewer members than minProperties " <> tshow val)-          else mempty-      _ -> mempty)--required :: ValidatorGen-required _ _ _ (Array vs) = do-  when (V.length vs == 0) Nothing-  ts <- traverse toTxt vs-  let a = vectorToMapSet ts-  when (H.size a /= V.length ts) Nothing-  Just (\x ->-    case x of-      Object o ->-        if H.size (H.difference a o) > 0-          then V.singleton ("the keys of: " <> tshow o <>-                            " don't contain all the required elements: " <> tshow vs)-          else mempty-      _ -> mempty)-  where-    vectorToMapSet :: (Eq a, Hashable a) => Vector a -> HashMap a Bool-    vectorToMapSet vec = vectorToHm $ (\x -> (x, True)) <$> vec-required _ _ _ _ = Nothing---- TODO: Fix up the properties validators. They're all a huge mess, but at least--- they work.------ In order of what's tried: properties, patternProperties, additionalProperties-properties :: ValidatorGen-properties spec g s v = do-  let mProps = propertiesMatches v-  let mPatProp = do-                  aV <- H.lookup "patternProperties" (_rsObject s)-                  patternPropertiesMatches spec g s aV-  let mAdd = do-              aVal <- H.lookup "additionalProperties" (_rsObject s)-              runAdditionalProperties spec g s aVal-  when (isNothing mProps && isNothing mPatProp && isNothing mAdd) Nothing-  Just (\x ->-    case x of-      Object y -> -- Got myself into a mess here.-        let (e1s, remaining) = runMaybeVal' mProps (Object y)-            (_, remaining') = runMaybeVal' mPatProp remaining-            (e2s, _) = runMaybeVal' mPatProp (Object y)-        in e1s <> e2s <> runMaybeVal mAdd remaining'-      _ -> mempty)-  where-    propertiesMatches :: Value -> Maybe (Value -> (Vector ValErr, Value))-    propertiesMatches (Object val) = do-      os <- traverse toObj val-      let oss = compile spec g . RawSchema (_rsURI s) <$> os-      Just (\x ->-        case x of-          Object y -> ( join . vectorOfElems $ either id mempty <$> H.intersectionWith validate oss y-                      , Object $ H.difference y oss)-          z        -> (mempty, z))-    propertiesMatches _ = Nothing--patternProperties :: ValidatorGen-patternProperties spec g s v = do-  when (H.member "properties" (_rsObject s)) Nothing-  let mPatProp = patternPropertiesMatches spec g s v-  -- TODO: checking additionalProperties as well doesn't help with tests-  let mAdd = do-              aVal <- H.lookup "additionalProperties" (_rsObject s)-              runAdditionalProperties spec g s aVal-  when (isNothing mPatProp && isNothing mAdd) Nothing-  Just (\x ->-    case x of-      Object y ->-        let (e2s, remaining') = runMaybeVal' mPatProp (Object y)-        in e2s <> runMaybeVal mAdd remaining'-      _ -> mempty)---- | An implementation of the "additionalProperties" keyword that never--- disables itself. Not included directly in the 'draft4' spec hashmap.-runAdditionalProperties :: ValidatorGen-runAdditionalProperties _ _ _ (Bool val) =-  Just (\x ->-    case x of-      Object y ->-        if not val && H.size y > 0-          then V.singleton ("Val error against additionalProperties false for: " <> tshow x)-          else mempty-      _ -> mempty)-runAdditionalProperties spec g s (Object val) =-  let sub = compile spec g (RawSchema (_rsURI s) val)-  in Just (\x ->-    case x of-      Object y -> vectorOfElems y >>= either id mempty . validate sub-      _        -> mempty)-runAdditionalProperties _ _ _ _ = Nothing--additionalProperties :: ValidatorGen-additionalProperties spec g s v = do-  when (H.member "properties" (_rsObject s)) Nothing-  when (H.member "patternProperties" (_rsObject s)) Nothing-  runAdditionalProperties spec g s v---- http://json-schema.org/latest/json-schema-validation.html#anchor70------ > This keyword's value MUST be an object.--- > Each value of this object MUST be either an object or an array.--- >--- > If the value is an object, it MUST be a valid JSON Schema.--- > This is called a schema dependency.--- >--- > If the value is an array, it MUST have at least one element.--- > Each element MUST be a string, and elements in the array MUST be unique.--- > This is called a property dependency.-dependencies :: ValidatorGen-dependencies spec g s (Object val) = do-  let vs = hmToVector val-      schemaDeps = vs >>= toSchemaDep spec g-      propDeps = vs >>= toPropDep-  when (V.length schemaDeps + V.length propDeps /= V.length vs) Nothing-  Just (\x ->-    case x of-      Object y -> join $ (valSD <$> schemaDeps <*> pure y) <> (valPD <$> propDeps <*> pure y)-      _        -> mempty)-  where-    toSchemaDep :: Spec -> Graph -> (Text, Value) -> Vector (Text, Schema)-    toSchemaDep spc gr (t, Object o) =-      V.singleton (t, compile spc gr $ RawSchema (_rsURI s) o)-    toSchemaDep _ _ _ = mempty--    toPropDep :: (Text, Value) -> Vector (Text, Vector Text)-    toPropDep (t, Array a) =-      if V.length a <= 0-        then mempty-        else case traverse toTxt a of-          Nothing -> mempty-          Just ts ->-            if allUnique ts-              then V.singleton (t, ts)-              else mempty-    toPropDep _ = mempty--    valSD :: (Text, Schema) -> HashMap Text Value -> Vector ValErr-    valSD (t, sub) d =-      case H.lookup t d of-        Nothing -> mempty-        Just _  -> either id (const mempty) $ validate sub (Object d)--    valPD :: (Text, Vector Text) -> HashMap Text Value -> Vector ValErr-    valPD (t, ts) d =-      case H.lookup t d of-        Nothing -> mempty-        Just _  ->-          case traverse ($ d) (H.lookup <$> ts) of-            Nothing -> V.singleton ("Val error against property dependency with key: " <> t-                                    <> " and value " <> tshow ts <> " for: " <> tshow d)-            Just _  -> mempty--dependencies _ _ _ _ = Nothing------------------------------------------------------- * Any Validators------------------------------------------------------- | http://json-schema.org/latest/json-schema-validation.html#anchor76------  > The value of this keyword MUST be an array.---  > This array MUST have at least one element.---  > Elements in the array MUST be unique.---  >---  > Elements in the array MAY be of any type, including null.------ NOTE: We actually respect this, and don't build the validator--- if any of the elements aren't unique.-enum :: ValidatorGen-enum _ _ _ (Array vs) = do-  when (V.null vs || not (allUnique vs)) Nothing-  Just (\x ->-    if V.elem x vs-      then mempty-      else V.singleton (tshow x <> " is not an element of enum " <> tshow vs))-enum _ _ _ _ = Nothing--typeVal :: ValidatorGen-typeVal _ _ _ (String val) = Just (\x -> isJsonType x (V.singleton val))-typeVal _ _ _ (Array vs) = do-  ts <- traverse toTxt vs-  unless (allUnique ts) Nothing-  Just (`isJsonType` ts)-typeVal _ _ _ _ = Nothing--allOf :: ValidatorGen-allOf spec g s (Array vs) = do-  os <- traverse toObj vs-  let ss = compile spec g . RawSchema (_rsURI s) <$> os-  Just (\x -> join . vLefts $ validate <$> ss <*> pure x)-allOf _ _ _ _ = Nothing--anyOf :: ValidatorGen-anyOf spec g s (Array vs) = do-  os <- traverse toObj vs-  let ss = compile spec g . RawSchema (_rsURI s) <$> os-  Just (\x ->-    case listToMaybe . rights $ validate <$> V.toList ss <*> pure x of-      Nothing -> V.singleton ("Val error against anyOf " <> tshow vs <> " for: " <> tshow x)-      Just _  -> mempty )-anyOf _ _ _ _ = Nothing--oneOf :: ValidatorGen-oneOf spec g s (Array vs) = do-  os <- traverse toObj vs-  let ss = compile spec g . RawSchema (_rsURI s) <$> os-  Just (\x ->-    if V.length (vRights $ validate <$> ss <*> pure x) == 1-      then mempty-      else V.singleton ("Val error against oneOf " <> tshow vs <> " for: " <> tshow x))-oneOf _ _ _ _ = Nothing--notValidator :: ValidatorGen-notValidator spec g s (Object val) = do-  let sub = compile spec g (RawSchema (_rsURI s) val)-  Just (\x ->-    case validate sub x of-      Left _  -> mempty-      Right _ -> V.singleton ("Val error against not validator " <> tshow val-                              <> " for: " <> tshow x))-notValidator _ _ _ _ = Nothing---- http://tools.ietf.org/html/draft-pbryan-zyp-json-ref-03------ TODO: Any members other than "$ref" in a JSON Reference object SHALL be--- ignored.-ref :: ValidatorGen-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 $ either id (const mempty) . validate compiled-    _                -> Nothing-ref _ _ _ _ = Nothing--noVal :: ValidatorGen-noVal _ _ _ _ = Just (const mempty)
+ src/Import.hs view
@@ -0,0 +1,11 @@++module Import (module Export) where++import           Control.Applicative as Export+import           Data.Aeson          as Export+import           Data.Foldable       as Export+import           Data.HashMap.Strict as Export (HashMap)+import           Data.Monoid         as Export+import           Data.Text           as Export (Text)+import           Data.Traversable    as Export+import           Data.Vector         as Export (Vector)
tests/Lib.hs view
@@ -14,7 +14,6 @@ import           Data.Monoid import           Data.Text                      (Text) import qualified Data.Text                      as T-import           Data.Vector                    (Vector) import           System.FilePath                ((</>)) import           Test.Framework                 (Test) import           Test.Framework.Providers.HUnit (testCase)@@ -70,28 +69,38 @@ toTest :: SchemaTest -> Test toTest st =   testCase (T.unpack $ _stDescription st) $ do-    void . assertRight . isValidSchema $ _stSchema st+    sanityCheckTest (_stSchema st)     forM_ (_stCases st) $ \sc -> do-      g <- assertRight =<< fetchRefs draft4 (_stSchema st) H.empty+      g <- assertRight =<< fetchReferencedSchemas draft4 (_stSchema st) H.empty       let res = validate (compile draft4 g $ _stSchema st) (_scData sc)       if _scValid sc         then assertValid   sc res         else assertInvalid sc res+  where+    sanityCheckTest :: RawSchema -> IO ()+    sanityCheckTest rs =+      case isValidSchema rs of+        []   -> return ()+        errs -> error $ unlines+                  [ "One of the test cases has a problem! "+                  , "Description: "         <> T.unpack (_stDescription st)+                  , "Validation failures: " <> show errs+                  ] -assertValid :: SchemaTestCase -> Either (Vector ValErr) Value -> Assertion-assertValid sc (Left es) =+assertValid :: SchemaTestCase -> [ValidationFailure Draft4Failure] -> Assertion+assertValid _ [] = return ()+assertValid sc errs =   assertFailure $ unlines     [ "    Failed to validate data"-    , "    Description: " <> T.unpack (_scDescription sc)-    , "    Data: "        <> show (_scData sc)-    , "    Errors: "      <> show es+    , "    Description: "         <> T.unpack (_scDescription sc)+    , "    Data: "                <> show (_scData sc)+    , "    Validation failures: " <> show errs     ]-assertValid _ _ = return () -assertInvalid :: SchemaTestCase -> Either (Vector ValErr) Value -> Assertion-assertInvalid sc (Right _) =+assertInvalid :: SchemaTestCase -> [ValidationFailure Draft4Failure] -> Assertion+assertInvalid sc [] =   assertFailure $ unlines-    [ "    Failed to invalidate data"+    [ "    Validated invalid data"     , "    Description: " <> T.unpack (_scDescription sc)     , "    Data: "        <> show (_scData sc)     ]