packages feed

hjsonschema 0.8.0.1 → 0.9.0.0

raw patch · 38 files changed

+2301/−1395 lines, 38 filesdep +QuickCheckdep +semigroupsdep +tastydep −hashabledep −mtldep −test-frameworkdep ~aesondep ~basedep ~hjsonpointerPVP ok

version bump matches the API change (PVP)

Dependencies added: QuickCheck, semigroups, tasty, tasty-hunit, tasty-quickcheck

Dependencies removed: hashable, mtl, test-framework, test-framework-hunit, transformers

Dependency ranges changed: aeson, base, hjsonpointer, unordered-containers

API changes (from Hackage documentation)

- 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: RawSchema :: !(Maybe Text) -> !(HashMap Text Value) -> RawSchema
- Data.JsonSchema: Ref :: Draft4Failure -> Draft4Failure
- Data.JsonSchema: Required :: Draft4Failure
- Data.JsonSchema: Schema :: [Value -> [ValidationFailure err]] -> Schema err
- Data.JsonSchema: SchemaDependency :: Draft4Failure -> Draft4Failure
- Data.JsonSchema: SchemaGraph :: !RawSchema -> !SchemaCache -> SchemaGraph
- Data.JsonSchema: Spec :: HashMap Text (ValSpec err) -> Spec err
- 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: [_cachedSchemas] :: SchemaGraph -> !SchemaCache
- Data.JsonSchema: [_failureInfo] :: ValidationFailure err -> !FailureInfo
- Data.JsonSchema: [_failureName] :: ValidationFailure err -> !err
- Data.JsonSchema: [_offendingData] :: FailureInfo -> !Value
- Data.JsonSchema: [_rsData] :: RawSchema -> !(HashMap Text Value)
- Data.JsonSchema: [_rsURI] :: RawSchema -> !(Maybe Text)
- Data.JsonSchema: [_startingSchema] :: SchemaGraph -> !RawSchema
- Data.JsonSchema: [_unSchema] :: Schema err -> [Value -> [ValidationFailure err]]
- Data.JsonSchema: [_unSpec] :: Spec err -> HashMap Text (ValSpec err)
- Data.JsonSchema: [_validatingData] :: FailureInfo -> !Value
- Data.JsonSchema: compile :: Spec err -> SchemaGraph -> RawSchema -> Schema err
- Data.JsonSchema: compileDraft4 :: SchemaGraph -> RawSchema -> Either [ValidationFailure Draft4Failure] (Schema Draft4Failure)
- Data.JsonSchema: data Draft4Failure
- Data.JsonSchema: data FailureInfo
- Data.JsonSchema: data RawSchema
- Data.JsonSchema: data SchemaGraph
- Data.JsonSchema: data ValSpec err
- Data.JsonSchema: data ValidationFailure err
- Data.JsonSchema: draft4 :: Spec Draft4Failure
- 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 -> SchemaCache -> RawSchema -> m (e SchemaGraph)
- Data.JsonSchema: isValidSchema :: RawSchema -> [ValidationFailure Draft4Failure]
- Data.JsonSchema: newtype Schema err
- Data.JsonSchema: newtype Spec err
- Data.JsonSchema: type EmbeddedSchemas = Maybe Text -> Value -> [RawSchema]
- Data.JsonSchema: type SchemaCache = HashMap Text (HashMap Text Value)
- Data.JsonSchema: type ValidatorConstructor schemaErr valErr = Spec schemaErr -> SchemaGraph -> RawSchema -> Value -> Maybe (Value -> valErr)
- Data.JsonSchema: validate :: Schema err -> Value -> [ValidationFailure err]
- 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: AdditionalBool :: AdditionalItemsFailure err
- Data.JsonSchema.Draft4.Arrays: AdditionalItemsBool :: ItemsFailure err
- Data.JsonSchema.Draft4.Arrays: AdditionalItemsObject :: err -> ItemsFailure err
- Data.JsonSchema.Draft4.Arrays: AdditionalObject :: err -> AdditionalItemsFailure err
- Data.JsonSchema.Draft4.Arrays: Items :: err -> ItemsFailure err
- Data.JsonSchema.Draft4.Arrays: additionalItems :: ValidatorConstructor err [ValidationFailure (AdditionalItemsFailure err)]
- Data.JsonSchema.Draft4.Arrays: data AdditionalItemsFailure 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 -> SchemaGraph -> 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: allUnique :: (Ord a) => Vector a -> Bool
- Data.JsonSchema.Helpers: allUniqueValues :: Vector Value -> Bool
- Data.JsonSchema.Helpers: arrayEmbed :: EmbeddedSchemas
- Data.JsonSchema.Helpers: defaultFetch :: Text -> IO (Either Text ByteString)
- Data.JsonSchema.Helpers: eitherToMaybe :: Either a b -> Maybe b
- Data.JsonSchema.Helpers: fromJSONInt :: Value -> Maybe Int
- Data.JsonSchema.Helpers: giveName :: err -> ValidatorConstructor err [FailureInfo] -> ValidatorConstructor err [ValidationFailure err]
- Data.JsonSchema.Helpers: greaterThanZero :: (Num a, Ord a) => a -> Maybe ()
- 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.Helpers: noEm :: EmbeddedSchemas
- Data.JsonSchema.Helpers: objEmbed :: EmbeddedSchemas
- Data.JsonSchema.Helpers: objMembersEmbed :: EmbeddedSchemas
- Data.JsonSchema.Helpers: objOrArrayEmbed :: EmbeddedSchemas
- Data.JsonSchema.Helpers: runMaybeVal :: Maybe (Value -> [a]) -> Value -> [a]
- Data.JsonSchema.Helpers: runMaybeVal' :: Maybe (Value -> ([a], Value)) -> Value -> ([a], Value)
- Data.JsonSchema.Helpers: toObj :: Value -> Maybe (HashMap Text Value)
- Data.JsonSchema.Helpers: toTxt :: Value -> Maybe Text
- Data.JsonSchema.Helpers: tshow :: Show a => a -> Text
- Data.JsonSchema.Reference: baseAndFragment :: Text -> URIBaseAndFragment
- Data.JsonSchema.Reference: isRemoteReference :: Text -> Bool
- Data.JsonSchema.Reference: newResolutionScope :: URIBase -> HashMap Text Value -> URIBase
- 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.Draft4: AdditionalItemsBool :: ValidatorChain
+ Data.JsonSchema.Draft4: AdditionalItemsObject :: ValidatorChain -> ValidatorChain
+ Data.JsonSchema.Draft4: AdditionalPropertiesBool :: ValidatorChain
+ Data.JsonSchema.Draft4: AdditionalPropertiesObject :: ValidatorChain -> ValidatorChain
+ Data.JsonSchema.Draft4: AllOf :: ValidatorChain -> ValidatorChain
+ Data.JsonSchema.Draft4: AnyOf :: ValidatorChain
+ Data.JsonSchema.Draft4: Enum :: ValidatorChain
+ Data.JsonSchema.Draft4: ExclusiveMaximum :: ValidatorChain
+ Data.JsonSchema.Draft4: ExclusiveMinimum :: ValidatorChain
+ Data.JsonSchema.Draft4: Failure :: !ValidatorChain -> !Value -> !Pointer -> Failure
+ Data.JsonSchema.Draft4: Items :: ValidatorChain -> ValidatorChain
+ Data.JsonSchema.Draft4: MaxItems :: ValidatorChain
+ Data.JsonSchema.Draft4: MaxLength :: ValidatorChain
+ Data.JsonSchema.Draft4: MaxProperties :: ValidatorChain
+ Data.JsonSchema.Draft4: Maximum :: ValidatorChain
+ Data.JsonSchema.Draft4: MinItems :: ValidatorChain
+ Data.JsonSchema.Draft4: MinLength :: ValidatorChain
+ Data.JsonSchema.Draft4: MinProperties :: ValidatorChain
+ Data.JsonSchema.Draft4: Minimum :: ValidatorChain
+ Data.JsonSchema.Draft4: MultipleOf :: ValidatorChain
+ Data.JsonSchema.Draft4: NotValidator :: ValidatorChain
+ Data.JsonSchema.Draft4: OneOf :: ValidatorChain
+ Data.JsonSchema.Draft4: PatternProperties :: ValidatorChain -> ValidatorChain
+ Data.JsonSchema.Draft4: PatternValidator :: ValidatorChain
+ Data.JsonSchema.Draft4: Properties :: ValidatorChain -> ValidatorChain
+ Data.JsonSchema.Draft4: PropertyDependency :: ValidatorChain
+ Data.JsonSchema.Draft4: Ref :: ValidatorChain -> ValidatorChain
+ Data.JsonSchema.Draft4: RefResolution :: ValidatorChain
+ Data.JsonSchema.Draft4: Required :: ValidatorChain
+ Data.JsonSchema.Draft4: Schema :: Maybe Text -> Maybe Text -> Maybe Text -> Maybe (HashMap Text Schema) -> HashMap Text Value -> Maybe Scientific -> Maybe Scientific -> Maybe Bool -> Maybe Scientific -> Maybe Bool -> Maybe Int -> Maybe Int -> Maybe Text -> Maybe Int -> Maybe Int -> Maybe Bool -> Maybe (Items Schema) -> Maybe (AdditionalItems Schema) -> Maybe Int -> Maybe Int -> Maybe Required -> Maybe (HashMap Text (Dependency Schema)) -> Maybe (HashMap Text Schema) -> Maybe (HashMap Text Schema) -> Maybe (AdditionalProperties Schema) -> Maybe EnumVal -> Maybe TypeVal -> Maybe (NonEmpty Schema) -> Maybe (NonEmpty Schema) -> Maybe (NonEmpty Schema) -> Maybe Schema -> Schema
+ Data.JsonSchema.Draft4: SchemaCache :: !schema -> !(URISchemaMap schema) -> SchemaCache schema
+ Data.JsonSchema.Draft4: SchemaContext :: !(Maybe Text) -> !schema -> SchemaContext schema
+ Data.JsonSchema.Draft4: SchemaDependency :: ValidatorChain -> ValidatorChain
+ Data.JsonSchema.Draft4: TypeValidator :: ValidatorChain
+ Data.JsonSchema.Draft4: UniqueItems :: ValidatorChain
+ Data.JsonSchema.Draft4: [_cachedSchemas] :: SchemaCache schema -> !(URISchemaMap schema)
+ Data.JsonSchema.Draft4: [_failureFinalValidator] :: Failure -> !Value
+ Data.JsonSchema.Draft4: [_failureOffendingData] :: Failure -> !Pointer
+ Data.JsonSchema.Draft4: [_failureValidatorsCalled] :: Failure -> !ValidatorChain
+ Data.JsonSchema.Draft4: [_scSchema] :: SchemaContext schema -> !schema
+ Data.JsonSchema.Draft4: [_scURI] :: SchemaContext schema -> !(Maybe Text)
+ Data.JsonSchema.Draft4: [_schemaAdditionalItems] :: Schema -> Maybe (AdditionalItems Schema)
+ Data.JsonSchema.Draft4: [_schemaAdditionalProperties] :: Schema -> Maybe (AdditionalProperties Schema)
+ Data.JsonSchema.Draft4: [_schemaAllOf] :: Schema -> Maybe (NonEmpty Schema)
+ Data.JsonSchema.Draft4: [_schemaAnyOf] :: Schema -> Maybe (NonEmpty Schema)
+ Data.JsonSchema.Draft4: [_schemaDefinitions] :: Schema -> Maybe (HashMap Text Schema)
+ Data.JsonSchema.Draft4: [_schemaDependencies] :: Schema -> Maybe (HashMap Text (Dependency Schema))
+ Data.JsonSchema.Draft4: [_schemaEnum] :: Schema -> Maybe EnumVal
+ Data.JsonSchema.Draft4: [_schemaExclusiveMaximum] :: Schema -> Maybe Bool
+ Data.JsonSchema.Draft4: [_schemaExclusiveMinimum] :: Schema -> Maybe Bool
+ Data.JsonSchema.Draft4: [_schemaId] :: Schema -> Maybe Text
+ Data.JsonSchema.Draft4: [_schemaItems] :: Schema -> Maybe (Items Schema)
+ Data.JsonSchema.Draft4: [_schemaMaxItems] :: Schema -> Maybe Int
+ Data.JsonSchema.Draft4: [_schemaMaxLength] :: Schema -> Maybe Int
+ Data.JsonSchema.Draft4: [_schemaMaxProperties] :: Schema -> Maybe Int
+ Data.JsonSchema.Draft4: [_schemaMaximum] :: Schema -> Maybe Scientific
+ Data.JsonSchema.Draft4: [_schemaMinItems] :: Schema -> Maybe Int
+ Data.JsonSchema.Draft4: [_schemaMinLength] :: Schema -> Maybe Int
+ Data.JsonSchema.Draft4: [_schemaMinProperties] :: Schema -> Maybe Int
+ Data.JsonSchema.Draft4: [_schemaMinimum] :: Schema -> Maybe Scientific
+ Data.JsonSchema.Draft4: [_schemaMultipleOf] :: Schema -> Maybe Scientific
+ Data.JsonSchema.Draft4: [_schemaNot] :: Schema -> Maybe Schema
+ Data.JsonSchema.Draft4: [_schemaOneOf] :: Schema -> Maybe (NonEmpty Schema)
+ Data.JsonSchema.Draft4: [_schemaOther] :: Schema -> HashMap Text Value
+ Data.JsonSchema.Draft4: [_schemaPatternProperties] :: Schema -> Maybe (HashMap Text Schema)
+ Data.JsonSchema.Draft4: [_schemaPattern] :: Schema -> Maybe Text
+ Data.JsonSchema.Draft4: [_schemaProperties] :: Schema -> Maybe (HashMap Text Schema)
+ Data.JsonSchema.Draft4: [_schemaRef] :: Schema -> Maybe Text
+ Data.JsonSchema.Draft4: [_schemaRequired] :: Schema -> Maybe Required
+ Data.JsonSchema.Draft4: [_schemaType] :: Schema -> Maybe TypeVal
+ Data.JsonSchema.Draft4: [_schemaUniqueItems] :: Schema -> Maybe Bool
+ Data.JsonSchema.Draft4: [_schemaVersion] :: Schema -> Maybe Text
+ Data.JsonSchema.Draft4: [_startingSchema] :: SchemaCache schema -> !schema
+ Data.JsonSchema.Draft4: checkSchema :: SchemaCache Schema -> SchemaContext Schema -> Either [Failure] (Value -> [Failure])
+ Data.JsonSchema.Draft4: data Failure
+ Data.JsonSchema.Draft4: data Schema
+ Data.JsonSchema.Draft4: data SchemaCache schema
+ Data.JsonSchema.Draft4: data SchemaContext schema
+ Data.JsonSchema.Draft4: data ValidatorChain
+ Data.JsonSchema.Draft4: emptySchema :: Schema
+ Data.JsonSchema.Draft4: fetchReferencedSchemas :: URISchemaMap Schema -> SchemaContext Schema -> IO (Either Text (SchemaCache Schema))
+ Data.JsonSchema.Draft4: runValidate :: SchemaCache Schema -> SchemaContext Schema -> Value -> [Failure]
+ Data.JsonSchema.Draft4: schemaValidity :: Schema -> [Failure]
+ Data.JsonSchema.Draft4.Internal: descendNextLevel :: SchemaCache Schema -> SchemaContext Schema -> Schema -> Value -> [Failure ValidatorChain]
+ Data.JsonSchema.Draft4.Internal: embedded :: Schema -> [Schema]
+ Data.JsonSchema.Draft4.Internal: runSingle :: Schema -> dta -> (Schema -> Maybe val) -> (err -> Failure ValidatorChain) -> (val -> dta -> [err]) -> [Failure ValidatorChain]
+ Data.JsonSchema.Draft4.Internal: runValidate :: SchemaCache Schema -> SchemaContext Schema -> Value -> [Failure]
+ Data.JsonSchema.Draft4.Internal: validateAny :: SchemaCache Schema -> SchemaContext Schema -> Value -> [Failure ValidatorChain]
+ Data.JsonSchema.Draft4.Internal: validateArray :: SchemaCache Schema -> SchemaContext Schema -> Vector Value -> [Failure ValidatorChain]
+ Data.JsonSchema.Draft4.Internal: validateNumber :: Schema -> Scientific -> [Failure ValidatorChain]
+ Data.JsonSchema.Draft4.Internal: validateObject :: SchemaCache Schema -> SchemaContext Schema -> HashMap Text Value -> [Failure ValidatorChain]
+ Data.JsonSchema.Draft4.Internal: validateString :: Schema -> Text -> [Failure ValidatorChain]
+ Data.JsonSchema.Fetch: SchemaCache :: !schema -> !(URISchemaMap schema) -> SchemaCache schema
+ Data.JsonSchema.Fetch: SchemaContext :: !(Maybe Text) -> !schema -> SchemaContext schema
+ Data.JsonSchema.Fetch: [_cachedSchemas] :: SchemaCache schema -> !(URISchemaMap schema)
+ Data.JsonSchema.Fetch: [_scSchema] :: SchemaContext schema -> !schema
+ Data.JsonSchema.Fetch: [_scURI] :: SchemaContext schema -> !(Maybe Text)
+ Data.JsonSchema.Fetch: [_startingSchema] :: SchemaCache schema -> !schema
+ Data.JsonSchema.Fetch: data SchemaCache schema
+ Data.JsonSchema.Fetch: data SchemaContext schema
+ Data.JsonSchema.Fetch: fetchReferencedSchemas :: FromJSON schema => (schema -> [schema]) -> (schema -> Maybe Text) -> (schema -> Maybe Text) -> URISchemaMap schema -> SchemaContext schema -> IO (Either Text (SchemaCache schema))
+ Data.JsonSchema.Fetch: fetchReferencedSchemas' :: FromJSON schema => (schema -> [schema]) -> (schema -> Maybe Text) -> (schema -> Maybe Text) -> (Text -> IO ByteString) -> URISchemaMap schema -> SchemaContext schema -> IO (SchemaCache schema)
+ Data.JsonSchema.Fetch: includeSubschemas :: (schema -> [schema]) -> (schema -> Maybe Text) -> SchemaContext schema -> [SchemaContext schema]
+ Data.JsonSchema.Fetch: instance GHC.Classes.Eq schema => GHC.Classes.Eq (Data.JsonSchema.Fetch.SchemaCache schema)
+ Data.JsonSchema.Fetch: instance GHC.Classes.Eq schema => GHC.Classes.Eq (Data.JsonSchema.Fetch.SchemaContext schema)
+ Data.JsonSchema.Fetch: instance GHC.Show.Show schema => GHC.Show.Show (Data.JsonSchema.Fetch.SchemaCache schema)
+ Data.JsonSchema.Fetch: instance GHC.Show.Show schema => GHC.Show.Show (Data.JsonSchema.Fetch.SchemaContext schema)
+ Data.JsonSchema.Fetch: simpleGET :: Manager -> Text -> IO ByteString
+ Data.JsonSchema.Fetch: type URISchemaMap schema = HashMap Text schema
+ Data.Validator.Failure: Failure :: !err -> !Value -> !Pointer -> Failure err
+ Data.Validator.Failure: [_failureFinalValidator] :: Failure err -> !Value
+ Data.Validator.Failure: [_failureOffendingData] :: Failure err -> !Pointer
+ Data.Validator.Failure: [_failureValidatorsCalled] :: Failure err -> !err
+ Data.Validator.Failure: addToPath :: Token -> Failure a -> Failure a
+ Data.Validator.Failure: data Failure err
+ Data.Validator.Failure: instance GHC.Classes.Eq err => GHC.Classes.Eq (Data.Validator.Failure.Failure err)
+ Data.Validator.Failure: instance GHC.Show.Show err => GHC.Show.Show (Data.Validator.Failure.Failure err)
+ Data.Validator.Failure: modFailure :: (a -> b) -> Failure a -> Failure b
+ Data.Validator.Failure: setFailure :: b -> Failure a -> Failure b
+ Data.Validator.Reference: baseAndFragment :: Text -> URIBaseAndFragment
+ Data.Validator.Reference: isRemoteReference :: Text -> Bool
+ Data.Validator.Reference: newResolutionScope :: URIBase -> Maybe Text -> URIBase
+ Data.Validator.Reference: resolveFragment :: (FromJSON schema, ToJSON schema, Show schema) => Maybe Text -> schema -> Maybe schema
+ Data.Validator.Reference: resolveReference :: URIBase -> Text -> URIBaseAndFragment
+ Data.Validator.Reference: resolveScopeAgainst :: Maybe Text -> Text -> Text
+ Data.Validator.Reference: type URIBase = Maybe Text
+ Data.Validator.Reference: type URIBaseAndFragment = (Maybe Text, Maybe Text)
+ Data.Validator.Utils: NonEmpty' :: NonEmpty a -> NonEmpty' a
+ Data.Validator.Utils: OrdValue :: Value -> OrdValue
+ Data.Validator.Utils: [_unNonEmpty'] :: NonEmpty' a -> NonEmpty a
+ Data.Validator.Utils: [_unOrdValue] :: OrdValue -> Value
+ Data.Validator.Utils: allUnique :: (Ord a) => [a] -> Bool
+ Data.Validator.Utils: allUniqueValues :: Vector Value -> Bool
+ Data.Validator.Utils: allUniqueValues' :: NonEmpty Value -> Bool
+ Data.Validator.Utils: arbitraryHashMap :: Arbitrary a => Gen (HashMap Text a)
+ Data.Validator.Utils: arbitraryPositiveScientific :: Gen Scientific
+ Data.Validator.Utils: arbitraryScientific :: Gen Scientific
+ Data.Validator.Utils: arbitrarySetOfText :: Gen (Set Text)
+ Data.Validator.Utils: arbitraryText :: Gen Text
+ Data.Validator.Utils: arbitraryValue :: Gen Value
+ Data.Validator.Utils: instance Data.Aeson.Types.Class.FromJSON a => Data.Aeson.Types.Class.FromJSON (Data.Validator.Utils.NonEmpty' a)
+ Data.Validator.Utils: instance Data.Aeson.Types.Class.ToJSON a => Data.Aeson.Types.Class.ToJSON (Data.Validator.Utils.NonEmpty' a)
+ Data.Validator.Utils: instance GHC.Classes.Eq Data.Validator.Utils.OrdValue
+ Data.Validator.Utils: instance GHC.Classes.Ord Data.Validator.Utils.OrdValue
+ Data.Validator.Utils: instance Test.QuickCheck.Arbitrary.Arbitrary a => Test.QuickCheck.Arbitrary.Arbitrary (Data.Validator.Utils.NonEmpty' a)
+ Data.Validator.Utils: newtype NonEmpty' a
+ Data.Validator.Utils: newtype OrdValue

Files

− Example.hs
@@ -1,103 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}--module Main where--import           Control.Applicative-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 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  = 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-    schemaJSON = H.singleton "uniqueItems" (Bool True)--badData :: Value-badData = Array $ V.fromList ["foo", "foo"]------------------------------------------------------- * 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-  schemaGraph <- fetchGraph currentSchemaCache 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-    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 g -> return g------------------------------------------------------- * Helper Functions-----------------------------------------------------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
@@ -4,11 +4,11 @@  [Hackage](https://hackage.haskell.org/package/hjsonschema) / [GitHub](https://github.com/seagreen/hjsonschema) / [Travis CI](https://travis-ci.org/seagreen/hjsonschema) -NOTE: You currently CANNOT use untrusted JSON data to make schemas. Schemas with circular references can cause infinite loops. This is being addressed: see the issue list.+NOTE: You CANNOT use untrusted JSON data to make schemas. Schemas with circular references can cause infinite loops. See the issue list for more info.  # Example -See [Example.hs on GitHub](https://github.com/seagreen/hjsonschema/blob/master/Example.hs).+See [here](https://github.com/seagreen/hjsonschema/blob/master/examples/Example.hs).  # Tests 
changelog.txt view
@@ -1,3 +1,10 @@+# 0.9+++ Partial rewrite. The API of the library has changed, see the examples+folder for how to use the new one.+++ Users of the library can now write schemas in Haskell as well as JSON.+ # 0.8  + Improve scope updating and resolving.
+ examples/CustomSchema.hs view
@@ -0,0 +1,66 @@+{-# LANGUAGE OverloadedStrings #-}++-- | A custom schema made up of one validator from 'Data.Validator.Draft4'+-- and one original validator.+--+-- This is a simple example because it doesn't allow references (so it+-- doesn't need to define an 'embed' function @Schema -> [Schema]@ for use+-- with 'fetchReferencedSchemas'.++module CustomSchema where++import           Data.Aeson+import           Data.Maybe             (maybeToList)+import           Data.Text              (Text)+import qualified Data.Text              as T++import qualified Data.Validator.Draft4  as VA+import qualified Data.Validator.Failure as FR++-- | Our custom validator.+oddLength :: Bool -> Text -> Maybe (FR.Failure () )+oddLength b t+  | b == odd (T.length t) = Nothing+  | otherwise             = Just (FR.Failure () (Bool b) mempty)++data CustomError+  = MaxLength+  | OddLength++-- If we were really using the schema we would also need ToJSON and FromJSON+-- instances.+data Schema = Schema+  { _schemaMaxLength :: Maybe Int+  , _schemaOddLength :: Maybe Bool+  }++-- | Since every 'Schema' is valid we don't need to bother defining something+-- like 'Data.JsonSchema.Draft4.checkValidity' for this schema.+validate :: Schema -> Value -> [FR.Failure CustomError]+validate s (String x) = concat+  [ f _schemaMaxLength (FR.setFailure MaxLength) (fmap maybeToList . VA.maxLength)+  , f _schemaOddLength (FR.setFailure OddLength) (fmap maybeToList . oddLength)+  ]+  where+    -- This pattern is overkill here, but is helpful if you have lots of+    -- validators (e.g. the Draft 4 schema has 27).+    f :: (Schema -> Maybe val)+      -> (err -> FR.Failure CustomError)+      -> (val -> Text -> [err])+      -> [FR.Failure CustomError]+    f field modifyError runVal =+      maybe mempty (\val -> modifyError <$> runVal val x) (field s)+validate _ _ = mempty -- Our schema passes everything that isn't a string.++example :: IO ()+example =+  case validate schema badData of+    [] -> error "We validated bad data."+    [FR.Failure OddLength _ _] -> putStrLn "Success."+    _ -> error "We got a different failure than expected."+  where+    schema :: Schema+    schema = Schema (Just 100) (Just True)++    badData :: Value+    badData = String "even"
+ examples/Main.hs view
@@ -0,0 +1,19 @@++module Main where++import qualified CustomSchema      as C+import qualified PrettyShowFailure as P+import qualified Standard          as S++main :: IO ()+main = do+  putStrLn "# Standard Example"+  S.example+  putStrLn ""++  putStrLn "# PrettyShowFailure Example"+  P.example+  putStrLn ""++  putStrLn "# CustomSchema Example"+  C.example
+ examples/PrettyShowFailure.hs view
@@ -0,0 +1,47 @@+{-# LANGUAGE OverloadedStrings #-}++module PrettyShowFailure where++import           Data.Aeson+import qualified Data.Aeson.Pointer     as AP+import           Data.Monoid++import qualified Data.JsonSchema.Draft4 as D4++badData :: Value+badData = toJSON [1, 2 :: Int]++failure :: D4.Failure+failure = D4.Failure+  { D4._failureValidatorsCalled = D4.Items D4.MultipleOf+  , D4._failureFinalValidator   = Number 2+  , D4._failureOffendingData    = AP.Pointer [AP.Token "0"]+  }++subsetOfData :: Value+subsetOfData =+  -- This is the only really interesting part of this example. To resolve the+  -- pointer to the part of the data that triggered the validation failure we+  -- have to have hjsonpointer in our build-depends and use 'AP.resolve'.+  case AP.resolve (D4._failureOffendingData failure) badData of+    Left _  -> error "Couldn't resolve pointer."+    Right v -> v++example :: IO ()+example = putStrLn . unlines $+  [ "Invalid data. Here's the sequence of validators that caught it:"+  , ""+  , "  " <> show (D4._failureValidatorsCalled failure)+  , ""+  , "Here's the contents of the final validator in that sequence:"+  , ""+  , "  " <> show (D4._failureFinalValidator failure)+  , ""+  , "Here's a JSON Pointer to the invalid part of the data:"+  , ""+  , "  " <> show (D4._failureOffendingData failure)+  , ""+  , "Here's the invalid part of the data:"+  , ""+  , "  " <> show subsetOfData+  ]
+ examples/Standard.hs view
@@ -0,0 +1,52 @@+{-# LANGUAGE OverloadedStrings #-}++module Standard where++import           Data.Aeson+import qualified Data.HashMap.Strict    as H+import qualified Data.Vector            as V++import qualified Data.JsonSchema.Draft4 as D4++schema :: D4.Schema+schema = D4.emptySchema { D4._schemaUniqueItems = Just True }++schemaContext :: D4.SchemaContext D4.Schema+schemaContext = D4.SchemaContext+  { D4._scURI    = Nothing+  -- ^ If your schema has relative references to other schemas+  -- then you'll need to give its URI here.+  , D4._scSchema = schema+  }++badData :: Value+badData = Array (V.fromList ["foo", "foo"])++example :: IO ()+example = do+  -- Since we know our schema doesn't reference any other schemas we could+  -- skip this step and use @SchemaCache schema mempty@ instead.+  --+  -- If we make a mistake and out schema does include references to other+  -- schemas then those references will always return 'D4.RefResolution'+  -- validation failures.+  cache <- makeCache++  let validate = case D4.checkSchema cache schemaContext of+                   Left _  -> error "Not a valid schema."+                   Right f -> f++  case validate badData of+    [] -> error "We validated bad data."+    [D4.Failure D4.UniqueItems _ _] -> putStrLn "Success."+    _ -> error "We got a different failure than expected."++  where+    makeCache :: IO (D4.SchemaCache D4.Schema)+    makeCache = do+      -- A HashMap of URIs (in the form of Text) to schemas.+      let currentlyStoredSchemas = H.empty+      res <- D4.fetchReferencedSchemas currentlyStoredSchemas schemaContext+      case res of+        Left _      -> error "Couldn't fetch referenced schemas."+        Right cache -> return cache
hjsonschema.cabal view
@@ -1,5 +1,5 @@ name:                   hjsonschema-version:                0.8.0.1+version:                0.9.0.0 synopsis:               JSON Schema library homepage:               https://github.com/seagreen/hjsonschema license:                MIT@@ -14,73 +14,80 @@                         draft4.json                         JSON-Schema-Test-Suite/tests/draft4/*.json                         README.md-                        tests/Lib.hs+                        tests/Shared.hs  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-  other-modules:        Data.JsonSchema.Core-                        Data.JsonSchema.Draft4-                      , Data.JsonSchema.Draft4.Objects.Properties+  exposed-modules:      Data.JsonSchema.Draft4+                      , Data.JsonSchema.Draft4.Internal+                      , Data.JsonSchema.Fetch+                      , Data.Validator.Draft4+                      , Data.Validator.Failure+                      , Data.Validator.Reference+                      , Data.Validator.Utils++  other-modules:        Data.JsonSchema.Draft4.Failure+                      , Data.JsonSchema.Draft4.Schema+                      , Data.Validator.Draft4.Any+                      , Data.Validator.Draft4.Array+                      , Data.Validator.Draft4.Number+                      , Data.Validator.Draft4.Object+                      , Data.Validator.Draft4.Object.Properties+                      , Data.Validator.Draft4.String                       , Import   default-language:     Haskell2010   default-extensions:   ScopedTypeVariables                         OverloadedStrings-  other-extensions:     TemplateHaskell   ghc-options:          -Wall-  build-depends:        aeson                >= 0.7   && < 0.11+  build-depends:        aeson                >= 0.7    && < 0.12                         -- 4.6 doesn't have a Traversable Either instance:-                      , base                 >= 4.7   && < 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.10-                      , 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+                      , base                 >= 4.7    && < 4.9+                      , bytestring           >= 0.10   && < 0.11+                      , containers           >= 0.5    && < 0.6+                      , file-embed           >= 0.0.8  && < 0.0.10+                      , hjsonpointer         >= 0.3    && < 0.4+                      , http-client          >= 0.4.9  && < 0.5+                      , http-types           >= 0.8    && < 0.10+                      , QuickCheck           >= 2.8.1  && < 2.9+                      , regexpr              >= 0.5    && < 0.6+                      , scientific           >= 0.3    && < 0.4+                      , semigroups           >= 0.18.0 && < 0.19+                      , unordered-containers >= 0.2    && < 0.3+                      , text                 >= 1.2    && < 1.3+                      , vector               >= 0.10   && < 0.12  test-suite local   type:                 exitcode-stdio-1.0   hs-source-dirs:       tests   main-is:              Local.hs+  other-modules:        Shared   default-language:     Haskell2010-  ghc-options:          -Wall+  ghc-options:          -Wall -fno-warn-orphans   default-extensions:   OverloadedStrings-  other-extensions:     TemplateHaskell   build-depends:        aeson                       , base                       , bytestring+                      , hjsonpointer                       , hjsonschema                       , text+                      , QuickCheck+                      , unordered-containers                       , vector-                      , directory            >= 1.2 && < 1.3-                      , filepath             >= 1.3 && < 1.5-                      , HUnit                >= 1.2 && < 1.4-                      , test-framework       >= 0.8 && < 0.9-                      , test-framework-hunit >= 0.3 && < 0.4+                      , directory            >= 1.2  && < 1.3+                      , filepath             >= 1.3  && < 1.5+                      , HUnit                >= 1.2  && < 1.4+                      , tasty                >= 0.11 && < 0.12+                      , tasty-hunit          >= 0.9  && < 0.10+                      , tasty-quickcheck     >= 0.8  && < 0.9  test-suite remote   type:                 exitcode-stdio-1.0   hs-source-dirs:       tests   main-is:              Remote.hs+  other-modules:        Shared   default-language:     Haskell2010-  ghc-options:          -Wall+  ghc-options:          -Wall -fno-warn-orphans   default-extensions:   OverloadedStrings-  other-extensions:     TemplateHaskell   build-depends:        aeson                       , async                       , base@@ -91,17 +98,22 @@                       , directory                       , filepath                       , HUnit-                      , test-framework-                      , test-framework-hunit+                      , tasty+                      , tasty-hunit                       , wai-app-static                       , warp  executable example-  main-is:              Example.hs+  hs-source-dirs:       examples+  main-is:              Main.hs+  other-modules:        CustomSchema+                      , PrettyShowFailure+                      , Standard   default-language:     Haskell2010   ghc-options:          -Wall   build-depends:        aeson                       , base+                      , hjsonpointer                       , hjsonschema                       , text                       , unordered-containers
− src/Data/JsonSchema.hs
@@ -1,86 +0,0 @@-{-# 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.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           Data.JsonSchema.Core-import           Data.JsonSchema.Draft4-import qualified Data.JsonSchema.Helpers as HE-import           Data.JsonSchema.Reference-import           Import---- For GHCs before 7.10:-import           Prelude hiding (concat, sequence)---- | Take a schema. Retrieve every document either it or its--- subschemas include via the "$ref" keyword. Load a 'SchemaGraph' out--- with them.-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 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-  -> 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-    -- TODO: use a fold here-    includeSubschemas :: RawSchema -> [RawSchema]-    includeSubschemas r =-      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 :: e SchemaCache -> RawSchema -> m (e SchemaCache)-    fetch eg r = join <$> sequence (run <$> eg)-      where-        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 -> fmap _cachedSchemas <$> fetchReferencedSchemas' fetchRef spec g (RawSchema (Just uri) obj)
− src/Data/JsonSchema/Core.hs
@@ -1,91 +0,0 @@-{-# LANGUAGE ScopedTypeVariables #-}--module Data.JsonSchema.Core where--import qualified Data.HashMap.Strict       as H-import           Data.Maybe--import           Data.JsonSchema.Reference-import           Import---- For GHCs before 7.10:-import           Prelude                   hiding (concat, sequence)------------------------------------------------------- * Primary API-----------------------------------------------------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-  where-    f :: ValSpec err -> Value -> Maybe (Value -> [ValidationFailure err])-    f (ValSpec _ construct) valJSON = construct spec g (RawSchema (newResolutionScope t o) o) valJSON--validate :: Schema err -> Value -> [ValidationFailure err]-validate schema x = concat . fmap ($ x) . _unSchema $ schema------------------------------------------------------- * Schemas-----------------------------------------------------newtype Spec err = Spec { _unSpec :: HashMap Text (ValSpec err) }--newtype Schema err = Schema { _unSchema :: [Value -> [ValidationFailure err]] }--data RawSchema = RawSchema-  { _rsURI  :: !(Maybe Text)-  -- ^ NOTE: Must not include a URI fragment.-  , _rsData :: !(HashMap Text Value)-  } deriving (Show)---- | A set of RawSchemas, split into a HashMap.------ Keys correspond to Schema _rsURIs. Values correspond to Schema _rsDatas.-type SchemaCache = HashMap Text (HashMap Text Value)--data SchemaGraph = SchemaGraph-  { _startingSchema :: !RawSchema-  -- ^ The schema initially referenced by '#'.-  , _cachedSchemas  :: !SchemaCache-  } deriving (Show)------------------------------------------------------- * Validators-----------------------------------------------------data ValSpec err = ValSpec EmbeddedSchemas (ValidatorConstructor err [ValidationFailure err])---- | Return a schema's immediate subschemas.------ 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 = Maybe Text -> Value -> [RawSchema]---- | 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-  -> SchemaGraph-  -> RawSchema-  -> Value-  -> Maybe (Value -> valErr)--data ValidationFailure err = ValidationFailure-  { _failureName :: !err-  , _failureInfo :: !FailureInfo-  } deriving (Show)--data FailureInfo = FailureInfo-  { _validatingData :: !Value-  , _offendingData  :: !Value-  } deriving (Show)
src/Data/JsonSchema/Draft4.hs view
@@ -1,142 +1,70 @@-{-# 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+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TemplateHaskell     #-} -  | Maximum-  | ExclusiveMaximum+module Data.JsonSchema.Draft4+  ( Schema(..)+  , emptySchema+  , checkSchema -  | Minimum-  | ExclusiveMinimum+    -- * Fetching tools+  , SchemaContext(..)+  , SchemaCache(..)+  , fetchReferencedSchemas -  | MaxLength-  | MinLength-  | Pattern+    -- * Failure+  , Failure(..)+  , ValidatorChain(..) -  | Items Draft4Failure-  | AdditionalItemsBool-  | AdditionalItemsObject Draft4Failure+    -- * Other Draft 4 things exported just in case+  , schemaValidity+  , IN.runValidate+  ) where -  | MaxItems-  | MinItems-  | UniqueItems-  | MaxProperties-  | MinProperties-  | Required+import           Data.Aeson+import qualified Data.ByteString.Lazy            as LBS+import           Data.FileEmbed+import qualified Data.HashMap.Strict             as H+import           Data.Maybe                      (fromMaybe) -  | Properties Draft4Failure-  | PatternProperties Draft4Failure-  | AdditionalPropertiesBool-  | AdditionalPropertiesObject Draft4Failure+import           Data.JsonSchema.Draft4.Failure+import qualified Data.JsonSchema.Draft4.Internal as IN+import           Data.JsonSchema.Draft4.Schema+import           Data.JsonSchema.Fetch           (SchemaCache(..),+                                                  SchemaContext(..),+                                                  URISchemaMap)+import qualified Data.JsonSchema.Fetch           as FE+import           Data.Validator.Reference        (baseAndFragment)+import           Import -  | SchemaDependency Draft4Failure-  | PropertyDependency+-- | Check the validity of a schema and return a function to validate data.+checkSchema+  :: SchemaCache Schema+  -> SchemaContext Schema+  -> Either [Failure] (Value -> [Failure])+checkSchema sg sc =+  case schemaValidity (_scSchema sc) of+    [] -> Right (IN.runValidate sg sc)+    es -> Left es -  | Enum-  | TypeValidator-  | AllOf Draft4Failure-  | AnyOf-  | OneOf-  | NotValidator-  | Ref Draft4Failure-  deriving (Eq, Show, Read)+fetchReferencedSchemas+  :: URISchemaMap Schema+  -> SchemaContext Schema+  -> IO (Either Text (SchemaCache Schema))+fetchReferencedSchemas =+  FE.fetchReferencedSchemas IN.embedded _schemaId _schemaRef -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.-  ]+-- | In normal situations just use 'checkSchema', which is a combination of+-- 'schemaValidity' and 'runValidate'.+schemaValidity :: Schema -> [Failure]+schemaValidity = IN.runValidate cache (SchemaContext Nothing d4) . toJSON   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  ->-      let a = RawSchema Nothing s-      in validate (compile draft4 (SchemaGraph a mempty) a) $ Object (_rsData r)+    d4 :: Schema+    d4 = fromMaybe (error "Schema decode failed (this should never happen)")+       . decode . LBS.fromStrict $ $(embedFile "draft4.json") --- | 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 :: SchemaGraph -> RawSchema -> Either [ValidationFailure Draft4Failure] (Schema Draft4Failure)-compileDraft4 g r =-  case isValidSchema r of-    []   -> Right (compile draft4 g r)-    errs -> Left errs+    -- @fst . baseAndFragment@ is necessary to remove the trailing "#",+    -- otherwise cache lookups to resolve internal references will fail.+    cache :: SchemaCache Schema+    cache = SchemaCache d4 $ case _schemaId d4 >>= fst . baseAndFragment of+                               Nothing  -> mempty+                               Just uri -> H.singleton uri d4
− src/Data/JsonSchema/Draft4/Any.hs
@@ -1,115 +0,0 @@--module Data.JsonSchema.Draft4.Any where--import           Control.Monad-import qualified Data.HashMap.Strict       as H-import           Data.Maybe-import           Data.Scientific-import qualified Data.Vector               as V--import           Data.JsonSchema.Core-import           Data.JsonSchema.Helpers-import           Data.JsonSchema.Reference-import           Import---- For GHCs before 7.10:-import           Prelude                   hiding (any)---- | 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 subSchema = compile spec g $ RawSchema (_rsURI s) o-  Just $ \x ->-    case validate subSchema x of-      [] -> pure (FailureInfo val x)-      _  -> mempty-notValidator _ _ _ _ = Nothing---- JSON Reference Draft Document:------      http://tools.ietf.org/html/draft-pbryan-zyp-json-ref-03-ref :: ValidatorConstructor err [ValidationFailure err]-ref spec g s (String val) = do-  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
@@ -1,100 +0,0 @@--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--data AdditionalItemsFailure err-  = AdditionalBool-  | AdditionalObject 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" (_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 <> 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 {}.-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 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 AdditionalObject) . 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/Failure.hs view
@@ -0,0 +1,68 @@++module Data.JsonSchema.Draft4.Failure where++import qualified Data.Aeson.Pointer     as P++import qualified Data.Validator.Failure as FR+import           Import++-- | A version of 'FR.Failure' specialized for JSON Schema Draft 4.+data Failure = Failure+  -- NOTE: We use a data type here instead of a newtype to provide a cleaner+  -- API for those importing only 'Data.JsonSchema.Draft4' (which should be+  -- the vast majority of users). Downsides: slower and hacky. I'd appreciate+  -- feedback on this.+  { _failureValidatorsCalled :: !ValidatorChain+  , _failureFinalValidator   :: !Value+  , _failureOffendingData    :: !P.Pointer+  } deriving (Eq, Show)++specializeForDraft4 :: FR.Failure ValidatorChain -> Failure+specializeForDraft4 (FR.Failure a b c) = Failure a b c++data ValidatorChain+  = MultipleOf+  | Maximum+  | ExclusiveMaximum+  | Minimum+  | ExclusiveMinimum++  | MaxLength+  | MinLength+  | PatternValidator++  | MaxItems+  | MinItems+  | UniqueItems+  | Items ValidatorChain+  | AdditionalItemsBool+  | AdditionalItemsObject ValidatorChain++  | MaxProperties+  | MinProperties+  | Required+  | SchemaDependency ValidatorChain+  | PropertyDependency+  | Properties ValidatorChain+  | PatternProperties ValidatorChain+  | AdditionalPropertiesBool+  | AdditionalPropertiesObject ValidatorChain++  | RefResolution+    -- ^ Indicates a reference that failed to resolve.+    --+    -- NOTE: The language agnostic test suite doesn't specify if this should+    -- cause a validation error or should allow data to pass. We choose to+    -- return a validation error.+    --+    -- Also note that ideally we would enforce in the type system that any+    -- failing references be dealt with before valididation. Then this could+    -- be removed entirely.+  | Ref ValidatorChain+  | Enum+  | TypeValidator+  | AllOf ValidatorChain+  | AnyOf+  | OneOf+  | NotValidator+  deriving (Eq, Show)
+ src/Data/JsonSchema/Draft4/Internal.hs view
@@ -0,0 +1,280 @@++module Data.JsonSchema.Draft4.Internal where++import           Data.Aeson+import qualified Data.HashMap.Strict            as H+import qualified Data.List.NonEmpty             as N+import           Data.Maybe                     (catMaybes, fromMaybe, isJust,+                                                 maybeToList)+import           Data.Scientific++import           Data.JsonSchema.Draft4.Failure+import           Data.JsonSchema.Draft4.Schema+import           Data.JsonSchema.Fetch          (SchemaCache(..),+                                                 SchemaContext(..))+import qualified Data.Validator.Draft4.Any      as AN+import qualified Data.Validator.Draft4.Array    as AR+import qualified Data.Validator.Draft4.Number   as NU+import qualified Data.Validator.Draft4.Object   as OB+import qualified Data.Validator.Draft4.String   as ST+import           Data.Validator.Failure         (modFailure, setFailure)+import qualified Data.Validator.Failure         as FR+import           Data.Validator.Reference       (newResolutionScope)+import           Import++-- For GHCs before 7.10:+import           Prelude                        hiding (concat)++--------------------------------------------------+-- * Embedded Schemas+--------------------------------------------------++-- | Return a schema's immediate subschemas.+--+-- Pass this to 'fetchReferencedSchemas' so that function can find all the+-- subschemas in a document. This allows 'fetchReferencedSchemas' to process+-- only "$ref"s and "id"s that are actual schema keywords. For example,+-- within a "properties" validator object an "id" key doesn't actually change+-- any scope, but instead serves a validator-specific function.+embedded :: Schema -> [Schema]+embedded schema = concat+  [ f _schemaItems+      (\x -> case x of+               AR.ItemsObject s -> pure s+               AR.ItemsArray ss -> ss+      )+  , f _schemaAdditionalItems+      (\x -> case x of+               AR.AdditionalObject s -> pure s+               _                     -> mempty+      )+  , f _schemaDependencies (catMaybes . fmap checkDependency . H.elems)+  , f _schemaProperties H.elems+  , f _schemaPatternProperties H.elems+  , f _schemaAdditionalProperties+      (\x -> case x of+               OB.AdditionalPropertiesObject s -> pure s+               _                               -> mempty+      )+  , f _schemaAllOf N.toList+  , f _schemaAnyOf N.toList+  , f _schemaOneOf N.toList+  , f _schemaNot pure+  , f _schemaDefinitions H.elems+  ]+  where+    f :: (Schema -> Maybe a) -> (a -> [Schema]) -> [Schema]+    f field nextLevelEmbedded = maybe mempty nextLevelEmbedded (field schema)++    checkDependency :: OB.Dependency Schema -> Maybe Schema+    checkDependency (OB.PropertyDependency _) = Nothing+    checkDependency (OB.SchemaDependency s)   = Just s++--------------------------------------------------+-- * Validation (Exported from 'Data.JsonSchema.Draft4')+--------------------------------------------------++-- | In normal situations just use 'checkSchema', which is a combination of+-- 'schemaValidity' and 'runValidate'.+runValidate+  :: SchemaCache Schema+  -> SchemaContext Schema+  -> Value+  -> [Failure]+runValidate = (fmap.fmap.fmap.fmap) specializeForDraft4 validateAny++--------------------------------------------------+-- * Validation (Main internal functions)+--------------------------------------------------++validateAny+  :: SchemaCache Schema+  -> SchemaContext Schema+  -> Value+  -> [FR.Failure ValidatorChain]+validateAny cache sc x = concat+  [ f _schemaEnum  (setFailure Enum)          (fmap maybeToList . AN.enumVal)+  , f _schemaType  (setFailure TypeValidator) (fmap maybeToList . AN.typeVal)+  , f _schemaAllOf (modFailure AllOf)         (AN.allOf recurse)+  , f _schemaAnyOf (setFailure AnyOf)         (fmap maybeToList . AN.anyOf recurse)+  , f _schemaOneOf (setFailure OneOf)         (fmap maybeToList . AN.oneOf recurse)+  , f _schemaNot   (setFailure NotValidator)  (fmap maybeToList . AN.notVal recurse)+  , refFailures+  ] <> specificValidators+  where+    specificValidators :: [FR.Failure ValidatorChain]+    specificValidators =+      case x of+        Number y -> validateNumber (_scSchema sc) y+        String y -> validateString (_scSchema sc) y+        Array y  -> validateArray cache sc y+        Object y -> validateObject cache sc y+        _        -> mempty++    f = runSingle (_scSchema sc) x++    recurse = descendNextLevel cache sc++    -- Since the results of the 'AN.ref' validator are fairly complicated [1]+    -- it's simpler not to use our 'f' helper function for it.+    --+    -- [1] A list of errors wrapped in a 'Maybe' where 'Nothing' represents+    -- if resolving the reference itself failed.+    refFailures :: [FR.Failure ValidatorChain]+    refFailures =+      case _schemaRef (_scSchema sc) of+        Nothing        -> mempty+        Just reference ->+          maybe [FR.Failure RefResolution (toJSON reference) mempty]+                (fmap (modFailure Ref))+                $ AN.ref scope+                         getReference+                         (\a b -> validateAny cache (SchemaContext a b))+                         reference+                         x+      where+        scope :: Maybe Text+        scope = newResolutionScope (_scURI sc) (_schemaId (_scSchema sc))++        getReference :: Maybe Text -> Maybe Schema+        getReference Nothing  = Just (_startingSchema cache)+        getReference (Just t) = H.lookup t (_cachedSchemas cache)++validateString+  :: Schema+  -> Text+  -> [FR.Failure ValidatorChain]+validateString schema x = concat+  [ f _schemaMaxLength (setFailure MaxLength)        (fmap maybeToList . ST.maxLength)+  , f _schemaMinLength (setFailure MinLength)        (fmap maybeToList . ST.minLength)+  , f _schemaPattern   (setFailure PatternValidator) (fmap maybeToList . ST.patternVal)+  ]+  where+    f = runSingle schema x++validateNumber+  :: Schema+  -> Scientific+  -> [FR.Failure ValidatorChain]+validateNumber schema x = concat+  [ f _schemaMultipleOf (setFailure MultipleOf) (fmap maybeToList . NU.multipleOf)+  , f _schemaMaximum+      (modFailure fMax)+      ( fmap maybeToList+      . NU.maximumVal (fromMaybe False (_schemaExclusiveMaximum schema))+      )+  , f _schemaMinimum+      (modFailure fMin)+      ( fmap maybeToList+      . NU.minimumVal (fromMaybe False (_schemaExclusiveMinimum schema))+      )+  ]+  where+    f = runSingle schema x++    fMax NU.Maximum          = Maximum+    fMax NU.ExclusiveMaximum = ExclusiveMaximum++    fMin NU.Minimum          = Minimum+    fMin NU.ExclusiveMinimum = ExclusiveMinimum++validateArray+  :: SchemaCache Schema+  -> SchemaContext Schema+  -> Vector Value+  -> [FR.Failure ValidatorChain]+validateArray cache (SchemaContext mUri schema) x = concat+  [ f _schemaMaxItems    (setFailure MaxItems)    (fmap maybeToList . AR.maxItems)+  , f _schemaMinItems    (setFailure MinItems)    (fmap maybeToList . AR.minItems)+  , f _schemaUniqueItems (setFailure UniqueItems) (fmap maybeToList . AR.uniqueItems)+  , f _schemaItems+      (modFailure fItems)+      (AR.items recurse (_schemaAdditionalItems schema))+  ]+  where+    f = runSingle schema x++    recurse = descendNextLevel cache (SchemaContext mUri schema)++    fItems (AR.Items err)                        = Items err+    fItems AR.AdditionalItemsBoolFailure         = AdditionalItemsBool+    fItems (AR.AdditionalItemsObjectFailure err) = AdditionalItemsObject err++validateObject+  :: SchemaCache Schema+  -> SchemaContext Schema+  -> HashMap Text Value+  -> [FR.Failure ValidatorChain]+validateObject cache (SchemaContext mUri schema) x = concat+  [ f _schemaMaxProperties (setFailure MaxProperties) (fmap maybeToList . OB.maxProperties)+  , f _schemaMinProperties (setFailure MinProperties) (fmap maybeToList . OB.minProperties)+  , f _schemaRequired      (setFailure Required)      (fmap maybeToList . OB.required)+  , f _schemaDependencies  (modFailure fDeps)         (OB.dependencies recurse)++  , f _schemaProperties+      (modFailure fProp)+      (OB.properties recurse+                     (_schemaPatternProperties schema)+                     (_schemaAdditionalProperties schema))++  , f _schemaPatternProperties+      (modFailure fPatProp)+      (case _schemaProperties schema of+        Just _  -> const (const mempty)+        Nothing -> OB.patternProperties recurse (_schemaAdditionalProperties schema))++  , f _schemaAdditionalProperties+      (modFailure fAddProp)+      (if isJust (_schemaProperties schema) || isJust (_schemaPatternProperties schema)+        then const (const mempty)+        else OB.additionalProperties recurse)+  ]+  where+    f = runSingle schema x++    recurse = descendNextLevel cache (SchemaContext mUri schema)++    fDeps (OB.SchemaDependencyFailure err) = SchemaDependency err+    fDeps OB.PropertyDependencyFailure     = PropertyDependency++    fProp (OB.PropertiesFailure err)   = Properties err+    fProp (OB.PropPatternFailure err)  = PatternProperties err+    fProp (OB.PropAdditionalFailure a) =+      case a of+        OB.APBoolFailure       -> AdditionalPropertiesBool+        OB.APObjectFailure err -> AdditionalPropertiesObject err++    fPatProp (OB.PPFailure err) = PatternProperties err+    fPatProp (OB.PPAdditionalPropertiesFailure a)   =+      case a of+        OB.APBoolFailure       -> AdditionalPropertiesBool+        OB.APObjectFailure err -> AdditionalPropertiesObject err++    fAddProp OB.APBoolFailure         = AdditionalPropertiesBool+    fAddProp (OB.APObjectFailure err) = AdditionalItemsObject err++--------------------------------------------------+-- * Validation (Internal utils)+--------------------------------------------------++descendNextLevel+  :: SchemaCache Schema+  -> SchemaContext Schema+  -> Schema+  -> Value+  -> [FR.Failure ValidatorChain]+descendNextLevel cache (SchemaContext mUri schema) =+  validateAny cache . SchemaContext scope+  where+    scope :: Maybe Text+    scope = newResolutionScope mUri (_schemaId schema)++runSingle+  :: Schema+  -> dta+  -> (Schema -> Maybe val)+  -> (err -> FR.Failure ValidatorChain)+  -> (val -> dta -> [err])+  -> [FR.Failure ValidatorChain]+runSingle schema dta field modifyError validate =+  maybe mempty (\val -> modifyError <$> validate val dta) (field schema)
− src/Data/JsonSchema/Draft4/Numbers.hs
@@ -1,61 +0,0 @@--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" (_rsData 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" (_rsData s) of-        Just (Bool a) -> if a then ((<=), ExclusiveMinimum) else ((<), Minimum)-        _             -> ((<), Minimum)-minimumVal _ _ _ _ = Nothing
− src/Data/JsonSchema/Draft4/Objects.hs
@@ -1,118 +0,0 @@-{-# 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 -> SchemaGraph -> (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
@@ -1,145 +0,0 @@-{-# 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" (_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-      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" (_rsData s)) Nothing-  let mPatternProps = patternUnmatched spec g s val-  let mAdditionalProps = runAdditionalProperties spec g s =<< H.lookup "additionalProperties" (_rsData 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-  -> SchemaGraph-  -> 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" (_rsData s)) Nothing-  when (H.member "patternProperties" (_rsData 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/Schema.hs view
@@ -0,0 +1,334 @@++module Data.JsonSchema.Draft4.Schema where++import qualified Data.HashMap.Strict          as H+import           Data.List.NonEmpty           (NonEmpty)+import           Data.Maybe                   (fromJust, isJust)+import           Data.Scientific++import qualified Data.Validator.Draft4.Any    as AN+import qualified Data.Validator.Draft4.Array  as AR+import qualified Data.Validator.Draft4.Object as OB+import           Data.Validator.Utils+import           Import++data Schema = Schema+  { _schemaVersion              :: Maybe Text+  , _schemaId                   :: Maybe Text+  , _schemaRef                  :: Maybe Text+  , _schemaDefinitions          :: Maybe (HashMap Text Schema)+  -- ^ A standardized location for embedding schemas+  -- to be referenced from elsewhere in the document.+  , _schemaOther                :: HashMap Text Value+  -- ^ Since the JSON document this Schema was built from could+  -- contain schemas anywhere (not just in "definitions" or any+  -- of the other official keys) we save any leftover key/value+  -- pairs not covered by them here.++  , _schemaMultipleOf           :: Maybe Scientific+  , _schemaMaximum              :: Maybe Scientific+  , _schemaExclusiveMaximum     :: Maybe Bool+  , _schemaMinimum              :: Maybe Scientific+  , _schemaExclusiveMinimum     :: Maybe Bool++  , _schemaMaxLength            :: Maybe Int+  , _schemaMinLength            :: Maybe Int+  , _schemaPattern              :: Maybe Text++  , _schemaMaxItems             :: Maybe Int+  , _schemaMinItems             :: Maybe Int+  , _schemaUniqueItems          :: Maybe Bool+  , _schemaItems                :: Maybe (AR.Items Schema)+  -- Note that '_schemaAdditionalItems' is left out of 'runValidate'+  -- because its functionality is handled by '_schemaItems'. It always+  -- validates data unless 'Items' is present.+  , _schemaAdditionalItems      :: Maybe (AR.AdditionalItems Schema)++  , _schemaMaxProperties        :: Maybe Int+  , _schemaMinProperties        :: Maybe Int+  , _schemaRequired             :: Maybe OB.Required+  , _schemaDependencies         :: Maybe (HashMap Text (OB.Dependency Schema))+  , _schemaProperties           :: Maybe (HashMap Text Schema)+  , _schemaPatternProperties    :: Maybe (HashMap Text Schema)+  , _schemaAdditionalProperties :: Maybe (OB.AdditionalProperties Schema)++  , _schemaEnum                 :: Maybe AN.EnumVal+  , _schemaType                 :: Maybe AN.TypeVal+  , _schemaAllOf                :: Maybe (NonEmpty Schema)+  , _schemaAnyOf                :: Maybe (NonEmpty Schema)+  , _schemaOneOf                :: Maybe (NonEmpty Schema)+  , _schemaNot                  :: Maybe Schema+  } deriving (Eq, Show)++emptySchema :: Schema+emptySchema = Schema+  { _schemaVersion              = Nothing+  , _schemaId                   = Nothing+  , _schemaRef                  = Nothing+  , _schemaDefinitions          = Nothing+  , _schemaOther                = mempty++  , _schemaMultipleOf           = Nothing+  , _schemaMaximum              = Nothing+  , _schemaExclusiveMaximum     = Nothing+  , _schemaMinimum              = Nothing+  , _schemaExclusiveMinimum     = Nothing++  , _schemaMaxLength            = Nothing+  , _schemaMinLength            = Nothing+  , _schemaPattern              = Nothing++  , _schemaMaxItems             = Nothing+  , _schemaMinItems             = Nothing+  , _schemaUniqueItems          = Nothing+  , _schemaItems                = Nothing+  , _schemaAdditionalItems      = Nothing++  , _schemaMaxProperties        = Nothing+  , _schemaMinProperties        = Nothing+  , _schemaRequired             = Nothing+  , _schemaDependencies         = Nothing+  , _schemaProperties           = Nothing+  , _schemaPatternProperties    = Nothing+  , _schemaAdditionalProperties = Nothing++  , _schemaEnum                 = Nothing+  , _schemaType                 = Nothing+  , _schemaAllOf                = Nothing+  , _schemaAnyOf                = Nothing+  , _schemaOneOf                = Nothing+  , _schemaNot                  = Nothing+  }++instance FromJSON Schema where+  parseJSON = withObject "Schema" $ \o -> do+    a  <- o .:? "$schema"+    b  <- o .:? "id"+    c  <- o .:? "$ref"+    d  <- o .:? "definitions"+    e  <- parseJSON (Object (H.difference o internalSchemaHashMap))++    f  <- o .:? "multipleOf"+    g  <- o .:? "maximum"+    h  <- o .:? "exclusiveMaximum"+    i  <- o .:? "minimum"+    j  <- o .:? "exclusiveMinimum"++    k  <- o .:? "maxLength"+    l  <- o .:? "minLength"+    m  <- o .:? "pattern"++    n  <- o .:? "maxItems"+    o' <- o .:? "minItems"+    p  <- o .:? "uniqueItems"+    q  <- o .:? "items"+    r  <- o .:? "additionalItems"++    s  <- o .:? "maxProperties"+    t  <- o .:? "minProperties"+    u  <- o .:? "required"+    v  <- o .:? "dependencies"+    w  <- o .:? "properties"+    x  <- o .:? "patternProperties"+    y  <- o .:? "additionalProperties"++    z  <- o .:? "enum"+    a2 <- o .:? "type"+    b2 <- fmap _unNonEmpty' <$> o .:? "allOf"+    c2 <- fmap _unNonEmpty' <$> o .:? "anyOf"+    d2 <- fmap _unNonEmpty' <$> o .:? "oneOf"+    e2 <- o .:? "not"+    pure Schema+      { _schemaVersion              = a+      , _schemaId                   = b+      , _schemaRef                  = c+      , _schemaDefinitions          = d+      , _schemaOther                = e++      , _schemaMultipleOf           = f+      , _schemaMaximum              = g+      , _schemaExclusiveMaximum     = h+      , _schemaMinimum              = i+      , _schemaExclusiveMinimum     = j++      , _schemaMaxLength            = k+      , _schemaMinLength            = l+      , _schemaPattern              = m++      , _schemaMaxItems             = n+      , _schemaMinItems             = o'+      , _schemaUniqueItems          = p+      , _schemaItems                = q+      , _schemaAdditionalItems      = r++      , _schemaMaxProperties        = s+      , _schemaMinProperties        = t+      , _schemaRequired             = u+      , _schemaDependencies         = v+      , _schemaProperties           = w+      , _schemaPatternProperties    = x+      , _schemaAdditionalProperties = y++      , _schemaEnum                 = z+      , _schemaType                 = a2+      , _schemaAllOf                = b2+      , _schemaAnyOf                = c2+      , _schemaOneOf                = d2+      , _schemaNot                  = e2+      }++instance ToJSON Schema where+  -- | The way we resolve JSON Pointers to embedded schemas is by+  -- serializing the containing schema to a value and then resolving the+  -- pointer against it. This means that FromJSON and ToJSON must be+  -- isomorphic.+  --+  -- This influences the design choices in the library. E.g. right now+  -- there are two false values for "exclusiveMaximum" -- Nothing and+  -- Just False. We could have condensed them down by using () instead+  -- of Bool for "exclusiveMaximum". This would have made writing schemas+  -- in haskell easier, but we could no longer round trip through/from+  -- JSON without losing information.+  toJSON s = Object $ H.union (mapMaybe ($ s) internalSchemaHashMap) (toJSON <$> _schemaOther s)+    where+      -- 'mapMaybe' is provided by unordered-containers after unordered-container-2.6.0.0,+      -- but until that is a little older (and has time to get into Stackage etc.)+      -- we use our own implementation.+      mapMaybe :: (v1 -> Maybe v2) -> HashMap k v1 -> HashMap k v2+      mapMaybe f = fmap fromJust . H.filter isJust . fmap f++-- | Internal. Separate from ToJSON because it's also used+-- by FromJSON to determine what keys aren't official schema+-- keys and therefor should be included in _schemaOther.+internalSchemaHashMap :: HashMap Text (Schema -> Maybe Value)+internalSchemaHashMap = H.fromList+  [ ("$schema"             , f _schemaVersion)+  , ("id"                  , f _schemaId)+  , ("$ref"                , f _schemaRef)+  , ("definitions"         , f _schemaDefinitions)++  , ("multipleOf"          , f _schemaMultipleOf)+  , ("maximum"             , f _schemaMaximum)+  , ("exclusiveMaximum"    , f _schemaExclusiveMaximum)+  , ("minimum"             , f _schemaMinimum)+  , ("exclusiveMinimum"    , f _schemaExclusiveMinimum)++  , ("maxLength"           , f _schemaMaxLength)+  , ("minLength"           , f _schemaMinLength)+  , ("pattern"             , f _schemaPattern)++  , ("maxItems"            , f _schemaMaxItems)+  , ("minItems"            , f _schemaMinItems)+  , ("uniqueItems"         , f _schemaUniqueItems)+  , ("items"               , f _schemaItems)+  , ("additionalItems"     , f _schemaAdditionalItems)++  , ("maxProperties"       , f _schemaMaxProperties)+  , ("minProperties"       , f _schemaMinProperties)+  , ("required"            , f _schemaRequired)+  , ("dependencies"        , f _schemaDependencies)+  , ("properties"          , f _schemaProperties)+  , ("patternProperties"   , f _schemaPatternProperties)+  , ("additionalProperties", f _schemaAdditionalProperties)++  , ("enum"                , f _schemaEnum)+  , ("type"                , f _schemaType)+  , ("allOf"               , f (fmap NonEmpty' . _schemaAllOf))+  , ("anyOf"               , f (fmap NonEmpty' . _schemaAnyOf))+  , ("oneOf"               , f (fmap NonEmpty' . _schemaOneOf))+  , ("not"                 , f _schemaNot)+  ]+  where+    f :: ToJSON a => (Schema -> Maybe a) -> Schema -> Maybe Value+    f = (fmap.fmap) toJSON++instance Arbitrary Schema where+  arbitrary = sized f+    where+      maybeGen :: Gen a -> Gen (Maybe a)+      maybeGen a = oneof [pure Nothing, Just <$> a]++      maybeRecurse :: Int -> Gen a -> Gen (Maybe a)+      maybeRecurse n a+        | n < 1     = pure Nothing+        | otherwise = maybeGen $ resize (n `div` 10) a++      f :: Int -> Gen Schema+      f n = do+        a  <- maybeGen arbitraryText+        b  <- maybeGen arbitraryText+        c  <- maybeGen arbitraryText+           -- NOTE: The next two fields are empty to generate cleaner schemas,+           -- but note that this means we don't test e.g. the invertability+           -- of these fields.+        d  <- pure Nothing -- _schemaDefinitions+        e  <- pure mempty -- _otherPairs++        f' <- maybeGen arbitraryPositiveScientific+        g  <- maybeGen arbitraryScientific+        h  <- arbitrary+        i  <- maybeGen arbitraryScientific+        j  <- arbitrary++        k  <- maybeGen (getPositive <$> arbitrary)+        l  <- maybeGen (getPositive <$> arbitrary)+        m  <- maybeGen arbitraryText++        n' <- maybeGen (getPositive <$> arbitrary)+        o  <- maybeGen (getPositive <$> arbitrary)+        p  <- arbitrary+        q  <- maybeRecurse n arbitrary+        r  <- maybeRecurse n arbitrary++        s  <- maybeGen (getPositive <$> arbitrary)+        t  <- maybeGen (getPositive <$> arbitrary)+        u  <- arbitrary+        v  <- maybeRecurse n arbitraryHashMap+        w  <- maybeRecurse n arbitraryHashMap+        x  <- maybeRecurse n arbitraryHashMap+        y  <- maybeRecurse n arbitrary++        z  <- arbitrary+        a2 <- arbitrary+        b2 <- maybeRecurse n (_unNonEmpty' <$> arbitrary)+        c2 <- maybeRecurse n (_unNonEmpty' <$> arbitrary)+        d2 <- maybeRecurse n (_unNonEmpty' <$> arbitrary)+        e2 <- maybeRecurse n arbitrary+        pure Schema+          { _schemaVersion              = a+          , _schemaId                   = b+          , _schemaRef                  = c+          , _schemaDefinitions          = d+          , _schemaOther                = e++          , _schemaMultipleOf           = f'+          , _schemaMaximum              = g+          , _schemaExclusiveMaximum     = h+          , _schemaMinimum              = i+          , _schemaExclusiveMinimum     = j++          , _schemaMaxLength            = k+          , _schemaMinLength            = l+          , _schemaPattern              = m++          , _schemaMaxItems             = n'+          , _schemaMinItems             = o+          , _schemaUniqueItems          = p+          , _schemaItems                = q+          , _schemaAdditionalItems      = r++          , _schemaMaxProperties        = s+          , _schemaMinProperties        = t+          , _schemaRequired             = u+          , _schemaDependencies         = v+          , _schemaProperties           = w+          , _schemaPatternProperties    = x+          , _schemaAdditionalProperties = y++          , _schemaEnum                 = z+          , _schemaType                 = a2+          , _schemaAllOf                = b2+          , _schemaAnyOf                = c2+          , _schemaOneOf                = d2+          , _schemaNot                  = e2+          }
− src/Data/JsonSchema/Draft4/Strings.hs
@@ -1,44 +0,0 @@--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/Fetch.hs view
@@ -0,0 +1,133 @@+{-# LANGUAGE ScopedTypeVariables #-}++module Data.JsonSchema.Fetch where++import           Control.Exception        (SomeException(..), catch)+import qualified Data.ByteString.Lazy     as LBS+import qualified Data.HashMap.Strict      as H+import qualified Data.Text                as T+import           Network.HTTP.Client++import           Data.Validator.Reference (isRemoteReference,+                                           newResolutionScope,+                                           resolveReference)+import           Import++-- For GHCs before 7.10:+import           Prelude                  hiding (concat, sequence)++data SchemaContext schema = SchemaContext+  { _scURI    :: !(Maybe Text)+  -- ^ Must not include a URI fragment, e.g. use+  -- "http://example.com/foo" not "http://example.com/foo#bar".+  --+  -- This is the URI identifying the document containing the schema.+  -- It's different than the schema's "id" field, which controls scope+  -- when resolving references contained in the schema.++  -- TODO: Make the no URI fragment requirement unnecessary.++  , _scSchema :: !schema+  } deriving (Eq, Show)++-- | Keys are URIs (without URI fragments).+type URISchemaMap schema = HashMap Text schema++data SchemaCache schema = SchemaCache+  { _startingSchema :: !schema+  -- ^ Used to resolve relative references.+  , _cachedSchemas  :: !(URISchemaMap schema)+  } deriving (Eq, Show)++-- | Take a schema. Retrieve every document either it or its subschemas+-- include via the "$ref" keyword. Load a 'SchemaCache' out with them.+fetchReferencedSchemas+  :: forall schema. FromJSON schema+  => (schema -> [schema])+  -> (schema -> Maybe Text)+  -> (schema -> Maybe Text)+  -> URISchemaMap schema+  -> SchemaContext schema+  -> IO (Either Text (SchemaCache schema))+fetchReferencedSchemas embedded getId getRef cache sc = do+  manager <- newManager defaultManagerSettings+  catch (Right <$> f manager) handler+  where+    f manager = fetchReferencedSchemas' embedded getId getRef+                                        (simpleGET manager) cache sc++    handler :: SomeException -> IO (Either Text (SchemaCache schema))+    handler e = pure . Left . T.pack . show $ e++-- | Based on 'Network.Http.Conduit.simpleHttp' from http-conduit.+simpleGET :: Manager -> Text -> IO LBS.ByteString+simpleGET man url = do+  req <- parseUrl (T.unpack url)+  responseBody <$> httpLbs req { requestHeaders = ("Connection", "close")+                               : requestHeaders req+                               } man++-- | A version of 'fetchReferencedSchema's where the function to fetch+-- schemas is provided by the user. This allows restrictions to be added,+-- e.g. rejecting non-local URIs.+fetchReferencedSchemas'+  :: forall schema. FromJSON schema+  => (schema -> [schema])+  -> (schema -> Maybe Text)+  -> (schema -> Maybe Text)+  -> (Text -> IO LBS.ByteString)+  -> URISchemaMap schema+  -> SchemaContext schema+  -> IO (SchemaCache schema)+fetchReferencedSchemas' embedded getId getRef fetchRef cache sc = do+  let startingCache = case _scURI sc of+                        Nothing  -> cache+                        Just uri -> H.insert uri (_scSchema sc) cache+  SchemaCache (_scSchema sc) <$> foldlM fetchRecursively+                                        startingCache+                                        (includeSubschemas embedded getId sc)+  where+    fetchRecursively+      :: URISchemaMap schema+      -> SchemaContext schema+      -> IO (URISchemaMap schema)+    fetchRecursively g (SchemaContext mUri schema) = 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 mUri (getId schema)+      case resolveReference scope <$> getRef schema of+        Just (Just uri,_) ->+          if not (isRemoteReference uri) || H.member uri g+            then pure g+            else do+              bts <- fetchRef uri+              case eitherDecode bts of+                Left e     -> ioError (userError e)+                Right schm -> _cachedSchemas <$>+                  fetchReferencedSchemas' embedded getId getRef fetchRef+                                          g (SchemaContext (Just uri) schm)+        _ -> pure g++-- | Return the schema passed in as an argument, as well as every+-- subschema contained within it.+includeSubschemas+  :: forall schema.+     (schema -> [schema])+  -> (schema -> Maybe Text)+  -> SchemaContext schema+  -> [SchemaContext schema]+includeSubschemas embedded getId (SchemaContext mUri schema) =+  SchemaContext mUri schema+  : (includeSubschemas embedded getId =<< subSchemas)+  where+    subSchemas :: [SchemaContext schema]+    subSchemas = SchemaContext (newResolutionScope mUri (getId schema))+             <$> embedded schema
− src/Data/JsonSchema/Helpers.hs
@@ -1,160 +0,0 @@--module Data.JsonSchema.Helpers where--import           Control.Exception-import qualified Data.ByteString.Lazy as LBS-import qualified Data.HashMap.Strict  as H-import           Data.Scientific-import qualified Data.Set as S-import qualified Data.Text            as T-import qualified Data.Vector          as V-import           Network.HTTP.Client--import           Data.JsonSchema.Core-import           Import------------------------------------------------------- * Embedded schemas finders-----------------------------------------------------noEm :: EmbeddedSchemas-noEm _ _ = mempty--objEmbed :: EmbeddedSchemas-objEmbed t (Object o) = pure (RawSchema t o)-objEmbed _ _ = mempty--arrayEmbed :: EmbeddedSchemas-arrayEmbed t (Array vs) = objEmbed t =<< V.toList vs-arrayEmbed _ _ = mempty--objOrArrayEmbed :: EmbeddedSchemas-objOrArrayEmbed t v@(Object _) = objEmbed t v-objOrArrayEmbed t v@(Array _) = arrayEmbed t v-objOrArrayEmbed _ _ = mempty--objMembersEmbed :: EmbeddedSchemas-objMembersEmbed t (Object o) = objEmbed t =<< H.elems o-objMembersEmbed _ _ = mempty------------------------------------------------------- * Modify Validators for use in Specs-----------------------------------------------------giveName-  :: forall err. err-  -> ValidatorConstructor err [FailureInfo]-  -> ValidatorConstructor err [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 f spec g rs v = (fmap.fmap) modErr <$> f spec g rs v-  where-    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--- 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------------------------------------------------------- | 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--    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--runMaybeVal :: Maybe (Value -> [a]) -> Value -> [a]-runMaybeVal Nothing _ = mempty-runMaybeVal (Just val) x = val x--runMaybeVal'-  :: Maybe (Value -> ([a], Value))-  -> Value-  -> ([a], Value)-runMaybeVal' Nothing x = (mempty, x)-runMaybeVal' (Just val) x = val x--toObj :: Value -> Maybe (HashMap Text Value)-toObj (Object a) = Just a-toObj _ = Nothing--fromJSONInt :: Value -> Maybe Int-fromJSONInt (Number n) = toBoundedInteger n-fromJSONInt _ = Nothing--toTxt :: Value -> Maybe Text-toTxt (String t) = Just t-toTxt _ = Nothing--greaterThanZero :: (Num a, Ord a) => a -> Maybe ()-greaterThanZero n = if n <= 0 then Nothing else Just ()--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
@@ -1,65 +0,0 @@--module Data.JsonSchema.Reference where--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--type URIBase = Maybe Text-type URIBaseAndFragment = (Maybe Text, Maybe Text)--newResolutionScope :: URIBase -> HashMap Text Value -> URIBase-newResolutionScope mScope o =-  case H.lookup "id" o of-    Just (String t) -> fst . baseAndFragment $ resolveScopeAgainst mScope t-    _               -> mScope--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-    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
+ src/Data/Validator/Draft4.hs view
@@ -0,0 +1,8 @@++module Data.Validator.Draft4 (module Export) where++import           Data.Validator.Draft4.Any    as Export+import           Data.Validator.Draft4.Array  as Export+import           Data.Validator.Draft4.Number as Export+import           Data.Validator.Draft4.Object as Export+import           Data.Validator.Draft4.String as Export
+ src/Data/Validator/Draft4/Any.hs view
@@ -0,0 +1,179 @@++module Data.Validator.Draft4.Any where++import           Control.Monad+import           Data.Aeson.Types         (Parser)+import           Data.List.NonEmpty       (NonEmpty)+import qualified Data.List.NonEmpty       as N+import           Data.Maybe+import           Data.Scientific+import           Data.Set                 (Set)+import qualified Data.Set                 as S++import           Data.Validator.Failure+import           Data.Validator.Utils+import           Data.Validator.Reference+import           Import++-- For GHCs before 7.10:+import           Prelude hiding           (any)++--------------------------------------------------+-- * $ref+--------------------------------------------------++-- | Will return 'Nothing' if the reference can't be resolved.+ref+  :: forall err schema. (FromJSON schema, ToJSON schema, Show schema)+  => Maybe Text+  -> (Maybe Text -> Maybe schema)+  -> (Maybe Text -> schema -> Value -> [Failure err])+  -> Text+  -> Value+  -> Maybe [Failure err]+ref scope getRef f reference x = do+  let (mUri, mFragment) = resolveReference scope reference+  schema <- getRef mUri+  s      <- resolveFragment mFragment schema+  Just (f mUri s x)++--------------------------------------------------+-- * enum+--------------------------------------------------++-- | From the spec:+-- <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.+--+-- NOTE: We don't enforce the uniqueness constraint in the haskell code,+-- but we do in the 'FromJSON' instance.+newtype EnumVal+  = EnumVal { _unEnumVal :: NonEmpty Value }+  -- Given a choice, we'd prefer to enforce uniqueness through the type+  -- system over having at least one element. To use a 'Set' though we'd+  -- have to use 'OrdValue' here (there's no 'Ord' instance for plain Values)+  -- and we'd rather not make users mess with 'OrdValue'.+  deriving (Eq, Show)++instance FromJSON EnumVal where+  parseJSON v = checkUnique . _unNonEmpty' =<< parseJSON v+    where+      checkUnique :: NonEmpty Value -> Parser EnumVal+      checkUnique a+        | allUniqueValues' a = pure (EnumVal a)+        | otherwise = fail "All elements of the Enum validator must be unique."++instance ToJSON EnumVal where+  toJSON = toJSON . NonEmpty' . _unEnumVal++instance Arbitrary EnumVal where+  arbitrary = do+    xs <- traverse (const arbitraryValue) =<< (arbitrary :: Gen [()])+    case N.nonEmpty (toUnique xs) of+      Nothing -> EnumVal . pure <$> arbitraryValue+      Just ne -> pure (EnumVal ne)+    where+      toUnique :: [Value] -> [Value]+      toUnique = fmap _unOrdValue . S.toList . S.fromList . fmap OrdValue++enumVal :: EnumVal -> Value -> Maybe (Failure ())+enumVal (EnumVal vs) x+  | null vs                   = Nothing+  | not (allUniqueValues' vs) = Nothing+  | x `elem` vs               = Nothing+  | otherwise                 =+    Just $ Failure () (toJSON (NonEmpty' vs)) mempty++--------------------------------------------------+-- * type+--------------------------------------------------++data TypeVal+  = TypeValString Text+  | TypeValArray (Set Text)+  deriving (Eq, Show)++instance FromJSON TypeVal where+  parseJSON v = fmap TypeValString (parseJSON v)+            <|> fmap TypeValArray (parseJSON v)++instance ToJSON TypeVal where+  toJSON (TypeValString t) = toJSON t+  toJSON (TypeValArray ts) = toJSON ts++instance Arbitrary TypeVal where+  arbitrary = oneof [ TypeValString <$> arbitraryText+                    , TypeValArray <$> arbitrarySetOfText+                    ]++typeVal :: TypeVal -> Value -> Maybe (Failure ())+typeVal (TypeValString t) x = isJsonType x (S.singleton t)+typeVal (TypeValArray ts) x = isJsonType x ts++isJsonType :: Value -> Set Text -> Maybe (Failure ())+isJsonType x ts+  | S.null (S.intersection okTypes ts) = Just (Failure () (toJSON ts) mempty)+  | otherwise                          = Nothing+  where+    okTypes :: Set Text+    okTypes =+      case x of+        Null       -> S.singleton "null"+        (Array _)  -> S.singleton "array"+        (Bool _)   -> S.singleton "boolean"+        (Object _) -> S.singleton "object"+        (String _) -> S.singleton "string"+        (Number y) ->+          case toBoundedInteger y :: Maybe Int of+            Nothing -> S.singleton "number"+            Just _  -> S.fromList ["number", "integer"]++--------------------------------------------------+-- * other+--------------------------------------------------++allOf+  :: (schema -> Value -> [Failure err])+  -> NonEmpty schema+  -> Value+  -> [Failure err]+allOf f subSchemas x = N.toList subSchemas >>= flip f x++anyOf+  :: ToJSON schema+  => (schema -> Value -> [Failure err])+  -> NonEmpty schema+  -> Value+  -> Maybe (Failure ())+anyOf f subSchemas x+  | any null (flip f x <$> subSchemas) = Nothing+  | otherwise = Just $ Failure () (toJSON (NonEmpty' subSchemas)) mempty++oneOf+  :: forall err schema. ToJSON schema+  => (schema -> Value -> [Failure err])+  -> NonEmpty schema+  -> Value+  -> Maybe (Failure ())+oneOf f subSchemas x+  | length successes == 1 = Nothing+  | otherwise             = Just $ Failure ()+                                           (toJSON (NonEmpty' subSchemas))+                                           mempty+  where+    successes :: [[Failure err]]+    successes = filter null $ flip f x <$> N.toList subSchemas++notVal+  :: ToJSON schema+  => (schema -> Value -> [Failure err])+  -> schema+  -> Value+  -> Maybe (Failure ())+notVal f schema x =+  case f schema x of+    [] -> Just (Failure () (toJSON schema) mempty)+    _  -> Nothing
+ src/Data/Validator/Draft4/Array.hs view
@@ -0,0 +1,168 @@++module Data.Validator.Draft4.Array where++import           Control.Monad+import qualified Data.Aeson.Pointer     as P+import qualified Data.Text              as T+import qualified Data.Vector            as V+import           Text.Read              (readMaybe)++import           Data.Validator.Failure+import           Data.Validator.Utils   (allUniqueValues)+import           Import++-- | The spec requires "maxItems" to be non-negative.+maxItems :: Int -> Vector Value -> Maybe (Failure ())+maxItems n xs+  | n < 0           = Nothing+  | V.length xs > n = Just (Failure () (toJSON n) mempty)+  | otherwise       = Nothing++-- | The spec requires "minItems" to be non-negative.+minItems :: Int -> Vector Value -> Maybe (Failure ())+minItems n xs+  | n < 0           = Nothing+  | V.length xs < n = Just (Failure () (toJSON n) mempty)+  | otherwise       = Nothing++uniqueItems :: Bool -> Vector Value -> Maybe (Failure ())+uniqueItems True xs+  | allUniqueValues xs = Nothing+  | otherwise          = Just (Failure () (Bool True) mempty)+uniqueItems False _ = Nothing++--------------------------------------------------+-- * items+--------------------------------------------------++data ItemsFailure err+  = Items err+  | AdditionalItemsBoolFailure+  | AdditionalItemsObjectFailure err+  deriving (Eq, Show)++data Items schema+  = ItemsObject schema+  | ItemsArray [schema]+  deriving (Eq, Show)++instance FromJSON schema => FromJSON (Items schema) where+  parseJSON v = fmap ItemsObject (parseJSON v)+            <|> fmap ItemsArray (parseJSON v)++instance ToJSON schema => ToJSON (Items schema) where+  toJSON (ItemsObject hm)     = toJSON hm+  toJSON (ItemsArray schemas) = toJSON schemas++instance Arbitrary schema => Arbitrary (Items schema) where+  arbitrary = oneof [ ItemsObject <$> arbitrary+                    , ItemsArray <$> arbitrary+                    ]++items+  :: forall err schema.+     (schema -> Value -> [Failure err])+  -> Maybe (AdditionalItems schema)+  -> Items schema+  -> Vector Value+  -> [Failure (ItemsFailure err)]+items f _ (ItemsObject subSchema) xs = zip [0..] (V.toList xs) >>= g+  where+    g :: (Int, Value) -> [Failure (ItemsFailure err)]+    g (index,x) = modFailure Items+                . addToPath (P.Token (T.pack (show index)))+              <$> f subSchema x++items f mAdditional (ItemsArray subSchemas) xs = itemFailures+                                              <> additionalItemFailures+  where+    indexedValues :: [(Int, Value)]+    indexedValues = zip [0..] (V.toList xs)++    itemFailures :: [Failure (ItemsFailure err)]+    itemFailures = join (zipWith g subSchemas indexedValues)+      where+        g :: schema -> (Int, Value) -> [Failure (ItemsFailure err)]+        g schema (index,x) = modFailure Items+                           . addToPath (P.Token (T.pack (show index)))+                         <$> f schema x++    additionalItemFailures :: [Failure (ItemsFailure err)]+    additionalItemFailures =+      case mAdditional of+        Nothing  -> mempty+        Just adi -> modFailure correctName+                  . correctIndexes+                <$> additionalItems f adi extras+      where+        -- It's not great that we convert back to Vector again.+        extras :: Vector Value+        extras =+          V.fromList . fmap snd . drop (length subSchemas) $ indexedValues++        -- Since 'additionalItems' only sees part of the array, but starts+        -- indexing from zero, we need to modify the paths it reports to+        -- represent invalid data so they actually represent the correct+        -- offsets.+        correctIndexes+          :: Failure (AdditionalItemsFailure err)+          -> Failure (AdditionalItemsFailure err)+        correctIndexes (Failure a b c) = Failure a b (fixIndex c)+          where+            fixIndex :: P.Pointer -> P.Pointer+            fixIndex (P.Pointer (tok:toks)) =+              case readMaybe . T.unpack . P._unToken $ tok of+                Nothing -> P.Pointer $ tok:toks+                Just n  -> P.Pointer $+                  (P.Token . T.pack . show $ n + length subSchemas):toks+            fixIndex (P.Pointer []) = P.Pointer []++        correctName :: AdditionalItemsFailure err -> ItemsFailure err+        correctName AdditionalBoolFailure = AdditionalItemsBoolFailure+        correctName (AdditionalObjectFailure err) =+          AdditionalItemsObjectFailure err++--------------------------------------------------+-- * additionalItems+--------------------------------------------------++data AdditionalItemsFailure err+  = AdditionalBoolFailure+  | AdditionalObjectFailure err+  deriving (Eq, Show)++data AdditionalItems schema+  = AdditionalBool Bool+  | AdditionalObject schema+  deriving (Eq, Show)++instance FromJSON schema => FromJSON (AdditionalItems schema) where+  parseJSON v = fmap AdditionalBool (parseJSON v)+            <|> fmap AdditionalObject (parseJSON v)++instance ToJSON schema => ToJSON (AdditionalItems schema) where+  toJSON (AdditionalBool b)    = toJSON b+  toJSON (AdditionalObject hm) = toJSON hm++instance Arbitrary schema => Arbitrary (AdditionalItems schema) where+  arbitrary = oneof [ AdditionalBool <$> arbitrary+                    , AdditionalObject <$> arbitrary+                    ]++additionalItems+  :: forall err schema.+     (schema -> Value -> [Failure err])+  -> AdditionalItems schema+  -> Vector Value+  -> [Failure (AdditionalItemsFailure err)]+additionalItems _ (AdditionalBool b) xs+  | b               = mempty+  | V.length xs > 0 = pure (Failure AdditionalBoolFailure (Bool b) mempty)+  | otherwise       = mempty+additionalItems f (AdditionalObject subSchema) xs =+  zip [0..] (V.toList xs) >>= g+  where+    g :: (Int, Value) -> [Failure (AdditionalItemsFailure err)]+    g (index,x) = modFailure AdditionalObjectFailure+                . addToPath (P.Token (T.pack (show index)))+              <$> f subSchema x
+ src/Data/Validator/Draft4/Number.hs view
@@ -0,0 +1,51 @@++module Data.Validator.Draft4.Number where++import           Data.Fixed             (mod')+import           Data.Scientific++import           Data.Validator.Failure+import           Import++-- | The spec requires "multipleOf" to be positive.+multipleOf :: Scientific -> Scientific -> Maybe (Failure ())+multipleOf n x+  | n <= 0          = Nothing+  | x `mod'` n /= 0 = Just (Failure () (toJSON n) mempty)+  | otherwise       = Nothing++data MaximumFailure+  = Maximum+  | ExclusiveMaximum+  deriving (Eq, Show)++maximumVal+  :: Bool+  -> Scientific+  -> Scientific+  -> Maybe (Failure MaximumFailure)+maximumVal exclusiveMaximum n x+  | x `greaterThan` n = Just (Failure err (toJSON n) mempty)+  | otherwise         = Nothing+  where+    (greaterThan, err) = if exclusiveMaximum+                           then ((>=), ExclusiveMaximum)+                           else ((>), Maximum)++data MinimumFailure+  = Minimum+  | ExclusiveMinimum+  deriving (Eq, Show)++minimumVal+  :: Bool+  -> Scientific+  -> Scientific+  -> Maybe (Failure MinimumFailure)+minimumVal exclusiveMinimum n x+  | x `lessThan` n = Just (Failure err (toJSON n) mempty)+  | otherwise      = Nothing+  where+    (lessThan, err) = if exclusiveMinimum+                        then ((<=), ExclusiveMinimum)+                        else ((<), Minimum)
+ src/Data/Validator/Draft4/Object.hs view
@@ -0,0 +1,147 @@+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE ScopedTypeVariables        #-}++module Data.Validator.Draft4.Object+  ( module Data.Validator.Draft4.Object+  , module Data.Validator.Draft4.Object.Properties+  ) where++import           Data.Aeson.Types                        (Parser)+import qualified Data.HashMap.Strict                     as H+import           Data.Set                                (Set)+import qualified Data.Set                                as S+import qualified Data.Text                               as T++import           Data.Validator.Draft4.Object.Properties+import           Data.Validator.Failure+import           Data.Validator.Utils+import           Import++-- For GHCs before 7.10:+import           Prelude                                 hiding (all, concat,+                                                          foldl)++-- | The spec requires "maxProperties" to be non-negative.+maxProperties :: Int -> HashMap Text Value -> Maybe (Failure ())+maxProperties n x+  | n < 0        = Nothing+  | H.size x > n = Just (Failure () (toJSON n) mempty)+  | otherwise    = Nothing++-- | The spec requires "minProperties" to be non-negative.+minProperties :: Int -> HashMap Text Value -> Maybe (Failure ())+minProperties n x+  | n < 0        = Nothing+  | H.size x < n = Just (Failure () (toJSON n) mempty)+  | otherwise    = Nothing++--------------------------------------------------+-- * required+--------------------------------------------------++-- | From the spec:+--+-- > The value of this keyword MUST be an array.+-- > This array MUST have at least one element.+-- > Elements of this array MUST be strings, and MUST be unique.+--+-- We don't enfore that 'Required' has at least one element in the+-- haskell code, but we do in the 'FromJSON' instance.+newtype Required+  = Required { _unRequired :: Set Text }+  deriving (Eq, Show, ToJSON)++instance FromJSON Required where+  parseJSON v = checkUnique =<< checkSize =<< parseJSON v+    where+      checkSize :: [Text] -> Parser [Text]+      checkSize a+        | null a    = fail "Required validator must not be empty."+        | otherwise = pure a++      checkUnique :: [Text] -> Parser Required+      checkUnique a =+        let b = S.fromList a+        -- NOTE: Can use length instead of S.size in GHC 7.10 or later.+        in if length a == S.size b+          then pure (Required b)+          else fail "All elements of the Required validator must be unique."++instance Arbitrary Required where+  arbitrary = do+    x  <- arbitraryText -- Guarantee at least one element.+    xs <- (fmap.fmap) T.pack arbitrary+    pure . Required . S.fromList $ x:xs++required :: Required -> HashMap Text Value -> Maybe (Failure ())+required (Required ts) x+  -- NOTE: When we no longer need to support GHCs before 7.10+  -- we can use null from Prelude throughout the library+  -- instead of specialized versions.+  | S.null ts                  = Nothing+  | H.null (H.difference hm x) = Nothing+  | otherwise                  = Just (Failure () (toJSON ts) mempty)+  where+    hm :: HashMap Text Bool+    hm = foldl (\b a -> H.insert a True b) mempty ts++--------------------------------------------------+-- * dependencies+--------------------------------------------------++data DependencyFailure err+  = SchemaDependencyFailure err+  | PropertyDependencyFailure+  deriving (Eq, Show)++data Dependency schema+  = SchemaDependency schema+  | PropertyDependency (Set Text)+  deriving (Eq, Show)++instance FromJSON schema => FromJSON (Dependency schema) where+  parseJSON v = fmap SchemaDependency (parseJSON v)+            <|> fmap PropertyDependency (parseJSON v)++instance ToJSON schema => ToJSON (Dependency schema) where+  toJSON (SchemaDependency schema) = toJSON schema+  toJSON (PropertyDependency ts)   = toJSON ts++instance Arbitrary schema => Arbitrary (Dependency schema) where+  arbitrary = oneof [ SchemaDependency <$> arbitrary+                    , PropertyDependency <$> arbitrarySetOfText+                    ]++-- | From the spec:+-- <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+  :: forall err schema. (schema -> Value -> [Failure err])+  -> HashMap Text (Dependency schema)+  -> HashMap Text Value+  -> [Failure (DependencyFailure err)]+dependencies f hm x = concat . fmap (uncurry g) . H.toList $ hm+  where+    g :: Text -> Dependency schema -> [Failure (DependencyFailure err)]+    g k (SchemaDependency schema)+      | H.member k x =+        modFailure SchemaDependencyFailure <$> f schema (Object x)+      | otherwise    = mempty+    g k (PropertyDependency ts)+      | H.member k x && not allPresent =+        pure $ Failure PropertyDependencyFailure+                       (toJSON (H.singleton k ts))+                       mempty+      | otherwise = mempty+      where+        allPresent :: Bool+        allPresent = all (`H.member` x) ts
+ src/Data/Validator/Draft4/Object/Properties.hs view
@@ -0,0 +1,187 @@+{-# LANGUAGE ScopedTypeVariables #-}++module Data.Validator.Draft4.Object.Properties where++import           Control.Monad+import           Data.Aeson+import qualified Data.Aeson.Pointer     as P+import qualified Data.HashMap.Strict    as H+import qualified Data.Text              as T+import           Text.RegexPR++import           Data.Validator.Failure+import           Import++newtype Remaining = Remaining { _unRemaining :: HashMap Text Value }++--------------------------------------------------+-- * properties+--------------------------------------------------++data PropertiesFailure err+  = PropertiesFailure err+  | PropPatternFailure err+  | PropAdditionalFailure (AdditionalPropertiesFailure err)+  deriving (Eq, Show)++-- | In order of what's tried: "properties", "patternProperties",+-- "additionalProperties".+properties+  :: forall err schema.+     (schema -> Value -> [Failure err])+  -> Maybe (HashMap Text schema)+  -> Maybe (AdditionalProperties schema)+  -> HashMap Text schema+  -> HashMap Text Value+  -> [Failure (PropertiesFailure err)]+properties f mPat mAdd propertiesHm x =+     fmap (modFailure PropertiesFailure) propFailures+  <> fmap (modFailure PropPatternFailure) patternFailures+  <> fmap (modFailure PropAdditionalFailure) additionalFailures+  where+    propertiesAndUnmatched :: ([Failure err], Remaining)+    propertiesAndUnmatched = ( failures+                             , Remaining (H.difference x propertiesHm)+                             )+      where+        failures :: [Failure err]+        failures = H.toList (H.intersectionWith f propertiesHm x)+               >>= (\(k,vs) -> fmap (addToPath (P.Token k)) vs)++    (propFailures, remaining1) = propertiesAndUnmatched++    mPatProp :: Maybe (HashMap Text Value -> ([Failure err], Remaining))+    mPatProp = patternAndUnmatched f <$> mPat++    patternFailures :: [Failure err]+    patternFailures = case mPatProp of+                        Nothing  -> mempty+                        Just val -> fst (val x)++    remaining2 :: Remaining+    remaining2 = case mPatProp of+                   Nothing  -> remaining1+                   Just val -> snd . val . _unRemaining $ remaining1++    additionalFailures :: [Failure (AdditionalPropertiesFailure err)]+    additionalFailures = case additionalProperties f <$> mAdd of+                           Nothing  -> mempty+                           Just val -> val (_unRemaining remaining2)++--------------------------------------------------+-- * patternProperties+--------------------------------------------------++data PatternPropertiesFailure err+  = PPFailure err+  | PPAdditionalPropertiesFailure (AdditionalPropertiesFailure err)+  deriving (Eq, Show)++patternProperties+  :: forall err schema.+     (schema -> Value -> [Failure err])+  -> Maybe (AdditionalProperties schema)+  -> HashMap Text schema+  -> HashMap Text Value+  -> [Failure (PatternPropertiesFailure err)]+patternProperties f mAdd patternPropertiesHm x =+     fmap (modFailure PPFailure) ppFailures+  <> fmap (modFailure PPAdditionalPropertiesFailure) addFailures+  where+    patternProps :: ([Failure err], Remaining)+    patternProps = patternAndUnmatched f patternPropertiesHm x++    (ppFailures, remaining) = patternProps++    addFailures :: [Failure (AdditionalPropertiesFailure err)]+    addFailures = case additionalProperties f <$> mAdd of+                    Nothing  -> mempty+                    Just val -> val (_unRemaining remaining)++patternAndUnmatched+  :: forall err schema.+     (schema -> Value -> [Failure err])+  -> HashMap Text schema+  -> HashMap Text Value+  -> ([Failure err], Remaining)+patternAndUnmatched f patPropertiesHm x =+  (H.foldlWithKey' runVals mempty perhapsMatches, remaining)+  where+    perhapsMatches :: HashMap Text (Value, [schema])+    perhapsMatches = H.foldlWithKey' (matchingSchemas patPropertiesHm) mempty x+      where+        matchingSchemas+          :: HashMap Text schema+          -> HashMap Text (Value, [schema])+          -> Text+          -> Value+          -> HashMap Text (Value, [schema])+        matchingSchemas subSchemas acc k v =+          H.insert k (v, H.foldlWithKey' (checkKey k) mempty subSchemas) acc++        checkKey+          :: Text+          -> [schema]+          -> Text+          -> schema+          -> [schema]+        checkKey k acc r subSchema =+          case matchRegexPR (T.unpack r) (T.unpack k) of+            Nothing -> acc+            Just _  -> pure subSchema <> acc++    runVals+      :: [Failure err]+      -> Text+      -> (Value, [schema])+      -> [Failure err]+    runVals acc k (v,subSchemas) =+      (subSchemas >>= (\schema -> addToPath (P.Token k) <$> f schema v))+      <> acc++    remaining :: Remaining+    remaining = Remaining . fmap fst . H.filter (null . snd) $ perhapsMatches++--------------------------------------------------+-- * additionalProperties+--------------------------------------------------++data AdditionalPropertiesFailure err+  = APBoolFailure+  | APObjectFailure err+  deriving (Eq, Show)++data AdditionalProperties schema+  = AdditionalPropertiesBool Bool+  | AdditionalPropertiesObject schema+  deriving (Eq, Show)++instance FromJSON schema => FromJSON (AdditionalProperties schema) where+  parseJSON v = fmap AdditionalPropertiesBool (parseJSON v)+            <|> fmap AdditionalPropertiesObject (parseJSON v)++instance ToJSON schema => ToJSON (AdditionalProperties schema) where+  toJSON (AdditionalPropertiesBool b)    = toJSON b+  toJSON (AdditionalPropertiesObject hm) = toJSON hm++instance Arbitrary schema => Arbitrary (AdditionalProperties schema) where+  arbitrary = oneof [ AdditionalPropertiesBool <$> arbitrary+                    , AdditionalPropertiesObject <$> arbitrary+                    ]++additionalProperties+  :: forall err schema.+     (schema -> Value -> [Failure err])+  -> AdditionalProperties schema+  -> HashMap Text Value+  -> [Failure (AdditionalPropertiesFailure err)]+additionalProperties _ (AdditionalPropertiesBool False) x+  | H.size x > 0 = pure $ Failure APBoolFailure (Bool False) mempty+  | otherwise    = mempty+additionalProperties _ (AdditionalPropertiesBool True) _ = mempty+additionalProperties f (AdditionalPropertiesObject schema) x = H.toList x >>= g+  where+    g :: (Text, Value) -> [Failure (AdditionalPropertiesFailure err)]+    g (k,v) = modFailure APObjectFailure+            . addToPath (P.Token k)+          <$> f schema v
+ src/Data/Validator/Draft4/String.hs view
@@ -0,0 +1,29 @@++module Data.Validator.Draft4.String where++import           Data.Aeson+import qualified Data.Text              as T+import           Text.RegexPR++import           Data.Validator.Failure+import           Import++-- | The spec requires "maxLength" to be non-negative.+maxLength :: Int -> Text -> Maybe (Failure ())+maxLength n x+  | n <= 0         = Nothing+  | T.length x > n = Just (Failure () (toJSON n) mempty)+  | otherwise      = Nothing++-- | The spec requires "minLength" to be non-negative.+minLength :: Int -> Text -> Maybe (Failure ())+minLength n x+  | n <= 0         = Nothing+  | T.length x < n = Just (Failure () (toJSON n) mempty)+  | otherwise      = Nothing++patternVal :: Text -> Text -> Maybe (Failure ())+patternVal t x =+  case matchRegexPR (T.unpack t) (T.unpack x) of+    Nothing -> Just (Failure () (toJSON t) mempty)+    Just _  -> Nothing
+ src/Data/Validator/Failure.hs view
@@ -0,0 +1,42 @@+{-# LANGUAGE ScopedTypeVariables #-}++module Data.Validator.Failure where++import qualified Data.Aeson.Pointer as P++import           Import++-- For GHCs before 7.10:+import           Prelude            hiding (concat, sequence)++-- | Validators shouldn't know more about the schema they're going to+-- be used with than necessary. If a validator throws errors using the+-- error sum type of a particular schema, then it can't be used with+-- other schemas later that have different error sum types (at least not+-- without writing partial functions).+--+-- Thus validators that can only fail in one way return 'FailureInfo's.+-- Validators that can fail in multiple ways return 'ValidationFailure's+-- along with an custom error sum type for that particular validator.+--+-- It's the job of a schema's validate function to unify the errors produced+-- by the validators it uses into a single error sum type for that schema.+-- The schema's validate function will return a 'ValidationFailure' with+-- that sum type as its type argument.+data Failure err = Failure+  { _failureValidatorsCalled :: !err+  , _failureFinalValidator   :: !Value+  , _failureOffendingData    :: !P.Pointer+  } deriving (Eq, Show)++setFailure :: b -> Failure a -> Failure b+setFailure e (Failure _ a b) = Failure e a b++modFailure :: (a -> b) -> Failure a -> Failure b+modFailure f v@(Failure a _ _) = v { _failureValidatorsCalled = f a }++addToPath :: P.Token -> Failure a -> Failure a+addToPath tok v@(Failure _ _ a) = v { _failureOffendingData = addToken a }+  where+    addToken :: P.Pointer -> P.Pointer+    addToken (P.Pointer ts) = P.Pointer (ts <> pure tok)
+ src/Data/Validator/Reference.hs view
@@ -0,0 +1,67 @@++module Data.Validator.Reference where++import qualified Data.Aeson.Pointer     as P+import qualified Data.Text              as T+import           Data.Text.Encoding+import           Network.HTTP.Types.URI++import           Import++type URIBase = Maybe Text+type URIBaseAndFragment = (Maybe Text, Maybe Text)++newResolutionScope :: URIBase -> Maybe Text -> URIBase+newResolutionScope mScope idKeyword =+  case idKeyword of+    Just t -> fst . baseAndFragment $ resolveScopeAgainst mScope t+    _      -> mScope++resolveReference :: URIBase -> Text -> URIBaseAndFragment+resolveReference mScope t = baseAndFragment $ resolveScopeAgainst mScope t++isRemoteReference :: Text -> Bool+isRemoteReference uri = "://" `T.isInfixOf` uri++resolveFragment+  :: (FromJSON schema, ToJSON schema, Show schema)+  => Maybe Text+  -> schema+  -> Maybe schema+resolveFragment Nothing schema        = Just schema+resolveFragment (Just pointer) schema = do+  let urlDecoded = decodeUtf8 . urlDecode True . encodeUtf8 $ pointer+  p <- either (const Nothing) Just (P.unescape urlDecoded)+  x <- either (const Nothing) Just (P.resolve p (toJSON schema))+  case fromJSON x of+    Error _         -> Nothing+    Success schema' -> Just schema'++--------------------------------------------------+-- * Internal+--------------------------------------------------++baseAndFragment :: Text -> URIBaseAndFragment+baseAndFragment = f . T.splitOn "#"+  where+    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
+ src/Data/Validator/Utils.hs view
@@ -0,0 +1,122 @@++module Data.Validator.Utils where++import           Control.Arrow+import qualified Data.HashMap.Strict  as H+import           Data.List.NonEmpty   (NonEmpty)+import qualified Data.List.NonEmpty   as N+import           Data.Scientific      (Scientific, fromFloatDigits)+import           Data.Set             (Set)+import qualified Data.Set             as S+import qualified Data.Text            as T+import qualified Data.Vector          as V++import           Import++--------------------------------------------------+-- * QuickCheck+--------------------------------------------------++arbitraryText :: Gen Text+arbitraryText = T.pack <$> arbitrary++arbitraryScientific :: Gen Scientific+arbitraryScientific = (fromFloatDigits :: Double -> Scientific) <$> arbitrary++arbitraryPositiveScientific :: Gen Scientific+arbitraryPositiveScientific = (fromFloatDigits :: Double -> Scientific)+                            . getPositive+                          <$> arbitrary++arbitraryValue :: Gen Value+arbitraryValue = sized f+  where+    f :: Int -> Gen Value+    f n | n <= 1    = oneof nonRecursive+        | otherwise = oneof $+            fmap (Array . V.fromList) (traverse (const (f (n `div` 10)))+              =<< (arbitrary :: Gen [()]))+          : fmap (Object . H.fromList) (traverse (const (g (n `div` 10)))+              =<< (arbitrary :: Gen [()]))+          : nonRecursive++    g :: Int -> Gen (Text, Value)+    g n = (,) <$> arbitraryText <*> f n++    nonRecursive :: [Gen Value]+    nonRecursive =+      [ pure Null+      , Bool <$> arbitrary+      , String <$> arbitraryText+      , Number <$> arbitraryScientific+      ]++arbitraryHashMap :: Arbitrary a => Gen (HashMap Text a)+arbitraryHashMap = H.fromList . fmap (first T.pack) <$> arbitrary++arbitrarySetOfText :: Gen (Set Text)+arbitrarySetOfText = S.fromList . fmap T.pack <$> arbitrary++newtype NonEmpty' a = NonEmpty' { _unNonEmpty' :: NonEmpty a }++instance FromJSON a => FromJSON (NonEmpty' a) where+  parseJSON v = do+    xs <- parseJSON v+    case N.nonEmpty xs of+      Nothing -> fail "Must have at least one item."+      Just ne -> pure (NonEmpty' ne)++instance ToJSON a => ToJSON (NonEmpty' a) where+  toJSON = toJSON . N.toList . _unNonEmpty'++instance Arbitrary a => Arbitrary (NonEmpty' a) where+  arbitrary = do+    xs <- arbitrary+    case N.nonEmpty xs of+      Nothing -> NonEmpty' . pure <$> arbitrary+      Just ne -> pure (NonEmpty' ne)++--------------------------------------------------+-- * allUniqueValues+--------------------------------------------------++allUniqueValues :: Vector Value -> Bool+allUniqueValues = allUnique . fmap OrdValue . V.toList++-- NOTE: When we no longer support GHC 7.8 we can generalize+-- allUnique to work on any Foldable and remove this function.+allUniqueValues' :: NonEmpty Value -> Bool+allUniqueValues' = allUnique . fmap OrdValue . N.toList++allUnique :: (Ord a) => [a] -> Bool+allUnique xs = S.size (S.fromList xs) == length xs++-- | OrdValue's Ord instance needs benchmarking, but it allows us to+-- use our 'allUnique' function instead of O(n^2) nub, so it's probably+-- worth it.+newtype OrdValue = OrdValue { _unOrdValue :: 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/Import.hs view
@@ -9,3 +9,4 @@ import           Data.Text           as Export (Text) import           Data.Traversable    as Export import           Data.Vector         as Export (Vector)+import           Test.QuickCheck     as Export hiding (Failure, Result, Success)
− tests/Lib.hs
@@ -1,113 +0,0 @@-{-# LANGUAGE TemplateHaskell #-}-{-# OPTIONS_GHC -fno-warn-orphans #-}--module Lib where--import           Control.Applicative-import           Control.Monad-import           Data.Aeson-import           Data.Aeson.TH-import qualified Data.ByteString.Lazy           as LBS-import           Data.Char                      (toLower)-import           Data.JsonSchema-import           Data.Monoid-import           Data.Text                      (Text)-import qualified Data.Text                      as T-import           System.FilePath                ((</>))-import           Test.Framework                 (Test)-import           Test.Framework.Providers.HUnit (testCase)-import           Test.HUnit                     hiding (Test)--isLocal :: String -> Bool-isLocal file = (file /= "definitions.json")-            && (file /= "ref.json")-            && (file /= "refRemote.json")--data SchemaTest = SchemaTest-  { _stDescription :: Text-  , _stSchema      :: RawSchema-  , _stCases       :: [SchemaTestCase]-  }--data SchemaTestCase = SchemaTestCase-  { _scDescription :: Text-  , _scData        :: Value-  , _scValid       :: Bool-  }--instance FromJSON RawSchema where-  parseJSON = withObject "Schema" $ return . RawSchema Nothing--instance FromJSON SchemaTest where-  parseJSON = withObject "SchemaTest" $ \o -> SchemaTest-    <$> o .: "description"-    <*> o .: "schema"-    <*> o .: "tests" -- I wish this were "cases"--readSchemaTests :: String -> [String] -> IO [SchemaTest]-readSchemaTests dir jsonFiles = concatMapM fileToCases jsonFiles-  where-    -- Each file contains an array of SchemaTests, not just one.-    fileToCases :: String -> IO [SchemaTest]-    fileToCases name = do-      let fullPath = dir </> name-      jsonBS <- LBS.readFile fullPath-      case eitherDecode jsonBS of-        Left e -> fail $ "couldn't parse file '" <> fullPath <> "': " <> e-        Right schemaTests -> return $ prependFileName name <$> schemaTests--    prependFileName :: String -> SchemaTest -> SchemaTest-    prependFileName fileName s = s-      { _stDescription = T.pack fileName <> ": " <> _stDescription s-      }--    concatMapM :: (Monad m) => (a -> m [b]) -> [a] -> m [b]-    concatMapM f xs = liftM concat (mapM f xs)--toTest :: SchemaTest -> Test-toTest st =-  testCase (T.unpack $ _stDescription st) $ do-    sanityCheckTest (_stSchema st)-    forM_ (_stCases st) $ \sc -> do-      g <- assertRight =<< fetchReferencedSchemas draft4 mempty (_stSchema st)-      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 -> [ValidationFailure Draft4Failure] -> Assertion-assertValid _ [] = return ()-assertValid sc errs =-  assertFailure $ unlines-    [ "    Failed to validate data"-    , "    Description: "         <> T.unpack (_scDescription sc)-    , "    Data: "                <> show (_scData sc)-    , "    Validation failures: " <> show errs-    ]--assertInvalid :: SchemaTestCase -> [ValidationFailure Draft4Failure] -> Assertion-assertInvalid sc [] =-  assertFailure $ unlines-    [ "    Validated invalid data"-    , "    Description: " <> T.unpack (_scDescription sc)-    , "    Data: "        <> show (_scData sc)-    ]-assertInvalid _ _ = return ()--assertRight :: (Show a) => Either a b -> IO b-assertRight a =-  case a of-    Left e  -> assertFailure (show e) >> fail "assertRight failed"-    Right b -> return b--$(deriveFromJSON defaultOptions { fieldLabelModifier = map toLower . drop 3 } ''SchemaTestCase)
tests/Local.hs view
@@ -1,18 +1,76 @@+ module Main where  import           Control.Applicative-import           Data.List           (isSuffixOf)-import           Lib-import           System.Directory    (getDirectoryContents)-import           Test.Framework+import           Control.Monad          (unless)+import           Data.Aeson+import qualified Data.Aeson.Pointer     as P+import           Data.List              (isSuffixOf)+import           Data.Monoid+import           System.Directory       (getDirectoryContents)+import           Test.Tasty             (TestTree, defaultMain, testGroup)+import qualified Test.Tasty.HUnit       as HU+import           Test.Tasty.QuickCheck  (testProperty) +import           Data.JsonSchema.Draft4 (Failure(..), Schema(..),+                                         SchemaCache(..), SchemaContext(..),+                                         emptySchema, runValidate)+import qualified Data.Validator.Draft4  as VA+import           Shared                 (isLocal, readSchemaTests, toTest)++dir :: String+dir = "JSON-Schema-Test-Suite/tests/draft4"+ main :: IO () main = do-  filenames <- filter isLocal . filter (".json" `isSuffixOf`)-                 <$> getDirectoryContents dir+  filenames <- filter isLocal . filter (".json" `isSuffixOf`) <$> getDirectoryContents dir   ts <- readSchemaTests dir filenames-  defaultMain (toTest <$> ts)+  defaultMain . testGroup "Tests not requiring an HTTP server" $+      testProperty "Invert schemas through JSON without change" invertSchema+    : testGroup "Make paths to invalid data correctly" correctPaths+    : fmap toTest ts +invertSchema :: Schema -> Bool+invertSchema a = Just a == decode (encode a)++correctPaths :: [TestTree]+correctPaths =+  [ HU.testCase "Items object" itemsObject+  , HU.testCase "Items array" itemsArray+  ]++itemsObject :: IO ()+itemsObject = HU.assertEqual "Path to invalid data"+                             (P.Pointer [P.Token "0"])+                             (_failureOffendingData failure)   where-    dir :: String-    dir = "JSON-Schema-Test-Suite/tests/draft4"+    [failure] = runValidate (SchemaCache schema mempty) sc (toJSON [[True, True]])++    schema :: Schema+    schema = emptySchema+      { _schemaItems = Just (VA.ItemsObject (emptySchema { _schemaUniqueItems = Just True }))+      }++    sc :: SchemaContext Schema+    sc = SchemaContext+      { _scURI    = Nothing+      , _scSchema = schema+      }++itemsArray :: IO ()+itemsArray = HU.assertEqual "Path to invalid data"+                            (P.Pointer [P.Token "0"])+                            (_failureOffendingData failure)+  where+    [failure] = runValidate (SchemaCache schema mempty) sc (toJSON [[True, True]])++    schema :: Schema+    schema = emptySchema+      { _schemaItems = Just (VA.ItemsArray [emptySchema { _schemaUniqueItems = Just True }])+      }++    sc :: SchemaContext Schema+    sc = SchemaContext+      { _scURI    = Nothing+      , _scSchema = schema+      }
tests/Remote.hs view
@@ -3,21 +3,24 @@ import           Control.Applicative import           Control.Concurrent.Async       (withAsync) import           Data.List                      (isSuffixOf)-import           Lib import           Network.Wai.Application.Static (defaultFileServerSettings,                                                  staticApp) import           Network.Wai.Handler.Warp       (run) import           System.Directory               (getDirectoryContents)-import           Test.Framework+import           Test.Tasty                     (defaultMain, testGroup) +import           Shared                         (isLocal, readSchemaTests,+                                                 toTest)++dir :: String+dir = "JSON-Schema-Test-Suite/tests/draft4"+ main :: IO ()-main =-  withAsync (run 1234 $ staticApp $ defaultFileServerSettings "JSON-Schema-Test-Suite/remotes") $ \_ -> do-    filenames <- filter (not . isLocal) . filter (".json" `isSuffixOf`)+main = withAsync serve $ \_ -> do+  filenames <- filter (not . isLocal) . filter (".json" `isSuffixOf`)                  <$> getDirectoryContents dir-    ts <- readSchemaTests dir filenames-    defaultMain (toTest <$> ts)+  ts <- readSchemaTests dir filenames+  defaultMain . testGroup "Tests that run an HTTP server" $ toTest <$> ts -  where-    dir :: String-    dir = "JSON-Schema-Test-Suite/tests/draft4"+serve :: IO ()+serve = run 1234 . staticApp . defaultFileServerSettings $ "JSON-Schema-Test-Suite/remotes"
+ tests/Shared.hs view
@@ -0,0 +1,99 @@+{-# LANGUAGE DeriveGeneric #-}++module Shared where++import           Control.Applicative+import           Control.Monad+import           Data.Aeson+import           Data.Aeson.TH+import qualified Data.ByteString.Lazy   as LBS+import           Data.Char              (toLower)+import           Data.Monoid+import           Data.Text              (Text)+import qualified Data.Text              as T+import           GHC.Generics+import           System.FilePath        ((</>))+import           Test.Tasty             (TestTree)+import qualified Test.Tasty.HUnit       as HU++import qualified Data.JsonSchema.Draft4 as D4++isLocal :: String -> Bool+isLocal file = (file /= "definitions.json")+            && (file /= "ref.json")+            && (file /= "refRemote.json")++data SchemaTest = SchemaTest+  { _stDescription :: Text+  , _stSchema      :: D4.Schema+  , _stCases       :: [SchemaTestCase]+  }++data SchemaTestCase = SchemaTestCase+  { _scDescription :: Text+  , _scData        :: Value+  , _scValid       :: Bool+  } deriving Generic++instance FromJSON SchemaTest where+  parseJSON = withObject "SchemaTest" $ \o -> SchemaTest+    <$> o .: "description"+    <*> o .: "schema"+    <*> o .: "tests" -- Perhaps "cases" would have been a more descriptive key.++instance FromJSON SchemaTestCase where+  parseJSON = genericParseJSON defaultOptions { fieldLabelModifier = fmap toLower . drop 3 }++readSchemaTests :: String -> [String] -> IO [SchemaTest]+readSchemaTests dir jsonFiles = concatMapM fileToCases jsonFiles+  where+    -- Each file contains an array of SchemaTests, not just one.+    fileToCases :: String -> IO [SchemaTest]+    fileToCases name = do+      let fullPath = dir </> name+      jsonBS <- LBS.readFile fullPath+      case eitherDecode jsonBS of+        Left e -> fail $ "couldn't parse file '" <> fullPath <> "': " <> e+        Right schemaTests -> pure $ prependFileName name <$> schemaTests++    prependFileName :: String -> SchemaTest -> SchemaTest+    prependFileName fileName s = s+      { _stDescription = T.pack fileName <> ": " <> _stDescription s+      }++    concatMapM :: (Monad m) => (a -> m [b]) -> [a] -> m [b]+    concatMapM f xs = liftM concat (mapM f xs)++toTest :: SchemaTest -> TestTree+toTest st =+  HU.testCase (T.unpack (_stDescription st)) $ do+    forM_ (_stCases st) $ \sc -> do+      g <- assertRight =<< D4.fetchReferencedSchemas mempty (D4.SchemaContext Nothing (_stSchema st))+      validate <- assertRight . D4.checkSchema g $ D4.SchemaContext Nothing (_stSchema st)+      let res = validate (_scData sc)+      if _scValid sc+        then assertValid   sc res+        else assertInvalid sc res++assertValid :: SchemaTestCase -> [D4.Failure] -> HU.Assertion+assertValid _ [] = pure ()+assertValid sc errs =+  HU.assertFailure $ unlines+    [ "    Failed to validate data"+    , "    Description: "         <> T.unpack (_scDescription sc)+    , "    Data: "                <> show (_scData sc)+    , "    Validation failures: " <> show errs+    ]++assertInvalid :: SchemaTestCase -> [D4.Failure] -> HU.Assertion+assertInvalid sc [] =+  HU.assertFailure $ unlines+    [ "    Validated invalid data"+    , "    Description: " <> T.unpack (_scDescription sc)+    , "    Data: "        <> show (_scData sc)+    ]+assertInvalid _ _ = pure ()++assertRight :: (Show a) => Either a b -> IO b+assertRight (Left e)  = HU.assertFailure (show e) >> fail "assertRight failed"+assertRight (Right b) = pure b