git.iko.soy/typeable/hjsonschema
on master help sign in
1.3.0.0.

+ Rewrite failure messages. We used to return a list of every failed validator, no matter how far it was buried within subschemas of the original schema. Now we return a tree of failure messages, so that if the first schema had only three validators, then no more than three top level failures will be returned. + Move the code to parse each validator from JSON from `Data.Validator.Draft4` into the validators' modules themselves. + Switch from Prelude to Protolude. + Switch `Data.JsonSchema.Fetch` from lazy to strict bytestrings.

commit260d9ce54cf7a63b7d95a96740cbb5c70026c7e6 authorIan Grant Jeffries <ian@housejeffries.com> dateWed 28 Dec 2016, 22:24 (10 years ago) parentf076375
32 files changed+1402−1558
README.md+8−4 changelog.txt+28−33 examples/AlternateSchema.hs+86−73 examples/Full.hs−40 examples/Simple.hs+21−20 examples/TwoStep.hs+49 hjsonschema.cabal+16−9 shell.nix+3 src/Data/JsonSchema/Draft4.hs+58−46 src/Data/JsonSchema/Draft4/Failure.hs+49−113 src/Data/JsonSchema/Draft4/Schema.hs+24−20 src/Data/JsonSchema/Draft4/Spec.hs+68−77 src/Data/JsonSchema/Fetch.hs+26−26 src/Data/JsonSchema/Types.hs+4−6 src/Data/Validator/Draft4.hs+142−450 src/Data/Validator/Draft4/Any.hs+221−107 src/Data/Validator/Draft4/Array.hs+138−98 src/Data/Validator/Draft4/Number.hs+51−32 src/Data/Validator/Draft4/Object.hs+88−66 src/Data/Validator/Draft4/Object/Properties.hs+152−157 src/Data/Validator/Draft4/String.hs+50−16 src/Data/Validator/Failure.hs−49 src/Data/Validator/Reference.hs+4−4 src/Data/Validator/Types.hs+3−7 src/Data/Validator/Utils.hs+1−3 src/Import.hs+6−6 test/Local.hs+13−16 test/Local/Failure.hs+59−15 test/Local/Reference.hs+2 test/Local/Validation.hs+3−3 test/Remote.hs+10−12 test/Shared.hs+19−50
README.md+8−4
@@ -1,12 +1,16 @@
11![hjsonschema logo](./logo.jpg)
22
3−A Haskell implementation of the current [JSON Schema](http://json-schema.org/) specification (Draft 4).
3+A Haskell implementation of the most commonly used [JSON Schema](http://json-schema.org/) specification ([Draft 4](https://github.com/json-schema-org/json-schema-spec/wiki/Specification-Links#draft-4)).
44
55[Hackage](https://hackage.haskell.org/package/hjsonschema) / [GitHub](https://github.com/seagreen/hjsonschema) / [Travis CI](https://travis-ci.org/seagreen/hjsonschema)
66
7−Requires [pcre](http://www.pcre.org/) (`pkgs.pcre` in Nixpkgs).
7+# Notes
88
9−NOTE: Schemas with circular references can cause infinite loops. hjsonschema does loop detection but it may not be solid yet -- please open an issue if you find a situation where it fails.
9++ As of 2017 [json-schema-org](https://github.com/json-schema-org/json-schema-spec) has begun releasing new drafts of the standard. Once this work stabilizes `hjsonschema` will add coverage for the latest draft.
10+
11++ Requires [pcre](http://www.pcre.org/) (`pkgs.pcre` in Nixpkgs).
12+
13++ Schemas with circular references can cause infinite loops. hjsonschema does loop detection but it may not be solid yet -- please open an issue if you find a situation where it fails.
1014
1115# Example
1216
@@ -46,7 +50,7 @@ Run remote tests (makes GETs to json-schema.org, also temporarily starts an HTTP
4650
4751+ Currently doesn't support the optional `"format"` validators.
4852
49−## Notes
53+## Vendoring
5054
5155+ `JSON-Schema-Test-Suite` is vendored from commit # aabcb3427745ade7a0b4d49ff016ad7eda8b898b [here](https://github.com/json-schema-org/JSON-Schema-Test-Suite).
5256
changelog.txt+28−33
@@ -1,3 +1,17 @@
1+# 1.3.0.0
2+
3++ Rewrite failure messages.
4+
5+We used to return a list of every failed validator, no matter how far it was buried within subschemas of the original schema.
6+
7+Now we return a tree of failure messages, so that if the first schema had only three validators, then no more than three top level failures will be returned.
8+
9++ Move the code to parse each validator from JSON from `Data.Validator.Draft4` into the validators' modules themselves.
10+
11++ Switch from Prelude to Protolude.
12+
13++ Switch `Data.JsonSchema.Fetch` from lazy to strict bytestrings.
14+
115# 1.2.0.2
216
317+ Bump hspec.
@@ -8,8 +22,7 @@
822
923# 1.2.0.0
1024
11−+ Return `AdditionalPropertiesObject` error correctly (was mistakenly
12−returning `AdditionalItemsObject` instead.
25++ Return `AdditionalPropertiesObject` error correctly (was mistakenly returning `AdditionalItemsObject` instead.
1326+ Don't silence errors resulting from subschemas of "anyOf" or "oneOf".
1427
1528# 1.1.0.1
@@ -18,33 +31,23 @@ returning `AdditionalItemsObject` instead.
1831
1932# 1.1.0.0
2033
21−+ Rename `schemaForSchemas` to `metaSchema` and `schemaForSchemasBytes` to
22−`metaSchemaBytes`.
34++ Rename `schemaForSchemas` to `metaSchema` and `schemaForSchemasBytes` to `metaSchemaBytes`.
2335
2436# 1.0.0.0
2537
2638## Bug fixes:
2739
28−+ Fix JSON Pointer bug. Pointers were being built in reverse order and so were
29−totally invalid.
30−+ Use `.:!` instead of `.:?` to parse the draft 4 schema. The only way to omit
31−optional fields in JSON Schema Draft 4 is to omit them entirely, `"null"`
32−can't be used for this.
40++ Fix JSON Pointer bug. Pointers were being built in reverse order and so were totally invalid.
41++ Use `.:!` instead of `.:?` to parse the draft 4 schema. The only way to omit optional fields in JSON Schema Draft 4 is to omit them entirely, `"null"` can't be used for this.
3342
3443## API Changes:
3544
3645+ Add referenced schema loop detection.
3746+ Add a new `referencesValidity` function.
38−+ `checkSchema` now checks referenced schema's validity in addition to the
39−starting schema's validity. This change bubbles up to the one-step validation
40−functions as well.
41−+ Switch most of the fetching code to use `URISchemaMap` instead of
42−`ReferencedSchemas`. It didn't need to know about the more complicated data
43−type.
44−+ Rething failure related names. Change `Invalid` to `Failure`, add a new
45−`Invalid` type alias which is only used for final results.
46−+ Failures now include the failing part of the data as well as a JSON Pointer to
47−it, so you don't have to worry about resolving the pointer.
47++ `checkSchema` now checks referenced schema's validity in addition to the starting schema's validity. This change bubbles up to the one-step validation functions as well.
48++ Switch most of the fetching code to use `URISchemaMap` instead of `ReferencedSchemas`. It didn't need to know about the more complicated data type.
49++ Rething failure related names. Change `Invalid` to `Failure`, add a new `Invalid` type alias which is only used for final results.
50++ Failures now include the failing part of the data as well as a JSON Pointer to it, so you don't have to worry about resolving the pointer.
4851
4952## Fundamental Changes:
5053
@@ -73,8 +76,7 @@ it, so you don't have to worry about resolving the pointer.
7376
7477+ Rewrite fetching internals.
7578+ Fix reference resolution defects, add more tests.
76−+ Switch to a Perl style regex library, which is closer to ECMAScript regexes
77−than the previous Posix style one.
79++ Switch to a Perl style regex library, which is closer to ECMAScript regexes than the previous Posix style one.
7880+ Add one-step validation functions ('fetchFilesystemAndValidate' and 'fetchHTTPAndValidate').
7981+ Alias the validation failure type exported by 'Data.JsonSchema.Draft4' to
8082'Invalid', change its field names.
@@ -90,11 +92,8 @@ folder for how to use the new one.
9092
9193+ Improve scope updating and resolving.
9294+ Rename RawSchema's _rsObject field to _rsData.
93−+ Make RawSchema's _rsURI field a Maybe. This way schemas without a starting
94−URI can say so explicitly with Nothing instead of with "".
95−+ Rename Graph to SchemaGraph. Declare it with data instead of type. Give it a
96−field referencing the starting schema. This field is used to find the curent
97−schema if no URI is in scope and a self-referencing $ref is found (e.g. "#").
95++ Make RawSchema's _rsURI field a Maybe. This way schemas without a starting URI can say so explicitly with Nothing instead of with "".
96++ Rename Graph to SchemaGraph. Declare it with data instead of type. Give it a field referencing the starting schema. This field is used to find the curent schema if no URI is in scope and a self-referencing $ref is found (e.g. "#").
9897+ Change the order of the last two arguments to fetchReferencedSchemas.
9998
10099# 0.7.1
@@ -105,13 +104,10 @@ schema if no URI is in scope and a self-referencing $ref is found (e.g. "#").
105104
106105Change error type from Text to ValidationFailure.
107106
108−Revert the 0.6 changes to validate. Also switch from Vector
109−to list. Validate is now:
107+Revert the 0.6 changes to validate. Also switch from Vector to list. Validate is now:
110108 Schema err -> Value -> [ValidationFailure err]
111109
112−Add fetchReferencedSchemas', which lets the user provide their
113−own MonadIO function to be used when fetching schemas. This lets
114−them do things like only fetch schemas from particular domains.
110+Add fetchReferencedSchemas', which lets the user provide their own MonadIO function to be used when fetching schemas. This lets them do things like only fetch schemas from particular domains.
115111
116112# 0.6
117113
@@ -131,8 +127,7 @@ Change Schema
131127
132128# 0.5.2
133129
134−+ Add convenience function for validating and compiling draft 4 schemas
135−simultaneously.
130++ Add convenience function for validating and compiling draft 4 schemas simultaneously.
136131
137132# 0.5.1
138133
examples/AlternateSchema.hs+86−73
@@ -5,34 +5,28 @@
55-- to copy this module instead of the 'Data.JsonSchema.Draft4'. While it's
66-- less convenient to write schemas in Haskell without a record type, you
77-- can get the implementation finished with far fewer lines of code.
8−--
9−-- Note that this module imports the the failure sum type and failure related
10−-- helper functions from the library. If you're implementing a custom schema
11−-- with different error types from JSON Schema Draft 4 you'll have to make
12−-- your own.
138
149module AlternateSchema where
1510
16−import Data.Aeson (FromJSON(..), Value(..),
17− decode)
18−import qualified Data.Aeson as AE
19−import qualified Data.ByteString.Lazy as LBS
20−import qualified Data.HashMap.Strict as HM
21−import Data.Maybe (fromMaybe)
22−import Data.Monoid
23−import Data.Profunctor (Profunctor (..))
24−import Data.Text (Text)
25−
26−import Data.JsonSchema.Draft4 (metaSchemaBytes)
27−import Data.JsonSchema.Draft4.Failure
28−import Data.JsonSchema.Fetch (ReferencedSchemas(..),
29− SchemaWithURI(..))
30−import qualified Data.JsonSchema.Fetch as FE
31−import Data.JsonSchema.Types (Schema(..), Spec(..))
32−import qualified Data.JsonSchema.Types as JT
33−import qualified Data.Validator.Draft4 as D4
34−import qualified Data.Validator.Draft4.Any as AN
35−import Data.Validator.Reference (updateResolutionScope)
11+import Protolude
12+
13+import Data.Aeson (FromJSON(..), Value(..),
14+ decode)
15+import qualified Data.Aeson as AE
16+import qualified Data.ByteString.Lazy as LBS
17+import qualified Data.HashMap.Strict as HM
18+import Data.Maybe (fromMaybe)
19+import Data.Profunctor (Profunctor (..))
20+
21+import Data.JsonSchema.Draft4 (ValidatorFailure(..),
22+ metaSchemaBytes)
23+import Data.JsonSchema.Fetch (ReferencedSchemas(..),
24+ SchemaWithURI(..))
25+import qualified Data.JsonSchema.Fetch as FE
26+import Data.JsonSchema.Types (Schema(..), Spec(..))
27+import qualified Data.JsonSchema.Types as JT
28+import qualified Data.Validator.Draft4 as D4
29+import Data.Validator.Reference (updateResolutionScope)
3630
3731--------------------------------------------------
3832-- * Basic fetching tools
@@ -44,12 +38,13 @@ referencesViaHTTP
4438referencesViaHTTP = FE.referencesViaHTTP' draft4FetchInfo
4539
4640draft4FetchInfo :: FE.FetchInfo Schema
47−draft4FetchInfo = FE.FetchInfo embedded (get "id") (get "$ref")
41+draft4FetchInfo = FE.FetchInfo embedded (lookup "id") (lookup "$ref")
4842 where
49− get :: Text -> Schema -> Maybe Text
50− get k (Schema s) = case HM.lookup k s of
51− Just (String t) -> Just t
52− _ -> Nothing
43+ lookup :: Text -> Schema -> Maybe Text
44+ lookup k (Schema s) =
45+ case HM.lookup k s of
46+ Just (String t) -> Just t
47+ _ -> Nothing
5348
5449embedded :: Schema -> ([Schema], [Schema])
5550embedded s = JT.embedded (d4Spec (ReferencedSchemas s mempty) mempty Nothing) s
@@ -63,26 +58,26 @@ validate
6358 -> Maybe Text
6459 -> Schema
6560 -> Value
66− -> [Failure]
67−validate rs = continueValidating rs (AN.VisitedSchemas [(Nothing, Nothing)])
61+ -> [ValidatorFailure]
62+validate rs = continueValidating rs (D4.VisitedSchemas [(Nothing, Nothing)])
6863
6964-- A schema for schemas themselves, using @src/draft4.json@ which is loaded
7065-- at compile time.
7166metaSchema :: Schema
7267metaSchema =
73− fromMaybe (error "Schema decode failed (this should never happen)")
68+ fromMaybe (panic "Schema decode failed (this should never happen)")
7469 . decode
7570 . LBS.fromStrict
7671 $ metaSchemaBytes
7772
78−checkSchema :: Schema -> [Failure]
73+checkSchema :: Schema -> [ValidatorFailure]
7974checkSchema = validate referenced Nothing metaSchema . Object . _unSchema
8075 where
8176 referenced :: ReferencedSchemas Schema
8277 referenced = ReferencedSchemas
8378 metaSchema
8479 (HM.singleton "http://json-schema.org/draft-04/schema"
85− metaSchema)
80+ metaSchema)
8681
8782--------------------------------------------------
8883-- * Spec
@@ -90,11 +85,11 @@ checkSchema = validate referenced Nothing metaSchema . Object . _unSchema
9085
9186continueValidating
9287 :: ReferencedSchemas Schema
93− -> AN.VisitedSchemas
88+ -> D4.VisitedSchemas
9489 -> Maybe Text
9590 -> Schema
9691 -> Value
97− -> [Failure]
92+ -> [ValidatorFailure]
9893continueValidating referenced visited mURI sc =
9994 JT.validate (d4Spec referenced visited newScope) sc
10095 where
@@ -108,39 +103,52 @@ continueValidating referenced visited mURI sc =
108103
109104d4Spec
110105 :: ReferencedSchemas Schema
111− -> AN.VisitedSchemas
106+ -> D4.VisitedSchemas
112107 -> Maybe Text
113− -> Spec Schema ValidatorChain
108+ -> Spec Schema ValidatorFailure
109+ -- ^ Here we reuses 'ValidatorFailure' from
110+ -- 'Data.JsonSchema.Draft4.Failure'. If your validators have different
111+ -- failure possibilities you'll need to create your own validator
112+ -- failure type.
114113d4Spec referenced visited scope =
115114 Spec
116− [ dimap f (const MultipleOf) D4.multipleOf
117− , dimap f maxE D4.maximumVal
118− , dimap f minE D4.minimumVal
119−
120− , dimap f (const MaxLength) D4.maxLength
121− , dimap f (const MinLength) D4.minLength
122− , dimap f (const PatternValidator) D4.patternVal
123−
124− , dimap f (const MaxItems) D4.maxItems
125− , dimap f (const MinItems) D4.minItems
126− , dimap f (const UniqueItems) D4.uniqueItems
127− , dimap f itemsE (D4.items descend)
128−
129− , dimap f (const MaxProperties) D4.maxProperties
130− , dimap f (const MinProperties) D4.minProperties
131− , dimap f (const Required) D4.required
132− , dimap f depsE (D4.dependencies descend)
133− , dimap f propE (D4.properties descend)
134− , dimap f patPropE (D4.patternProperties descend)
135− , dimap f addPropE (D4.additionalProperties descend)
136−
137− , dimap f refE (D4.ref visited scope (FE.getReference referenced) refVal)
138− , dimap f (const Enum) D4.enumVal
139− , dimap f (const TypeValidator) D4.typeVal
140− , dimap f AllOf (D4.allOf lateral)
141− , dimap f AnyOf (D4.anyOf lateral)
142− , dimap f oneOfE (D4.oneOf lateral)
143− , dimap f (const NotValidator) (D4.notVal lateral)
115+ [ dimap f FailureMultipleOf D4.multipleOfValidator
116+ , dimap f FailureMaximum D4.maximumValidator
117+ , dimap f FailureMinimum D4.minimumValidator
118+
119+ , dimap f FailureMaxLength D4.maxLengthValidator
120+ , dimap f FailureMinLength D4.minLengthValidator
121+ , dimap f FailurePattern D4.patternValidator
122+
123+ , dimap f FailureMaxItems D4.maxItemsValidator
124+ , dimap f FailureMinItems D4.minItemsValidator
125+ , dimap f FailureUniqueItems D4.uniqueItemsValidator
126+ , dimap
127+ (fromMaybe D4.emptyItems . f)
128+ (\err -> case err of
129+ D4.IRInvalidItems e -> FailureItems e
130+ D4.IRInvalidAdditional e -> FailureAdditionalItems e)
131+ (D4.itemsRelatedValidator descend)
132+
133+ , dimap f FailureMaxProperties D4.maxPropertiesValidator
134+ , dimap f FailureMinProperties D4.minPropertiesValidator
135+ , dimap f FailureRequired D4.requiredValidator
136+ , dimap f FailureDependencies (D4.dependenciesValidator descend)
137+ , dimap
138+ (fromMaybe D4.emptyProperties . f)
139+ FailurePropertiesRelated
140+ (D4.propertiesRelatedValidator descend)
141+
142+ , dimap
143+ f
144+ FailureRef
145+ (D4.refValidator visited scope (FE.getReference referenced) getRef)
146+ , dimap f FailureEnum D4.enumValidator
147+ , dimap f FailureType D4.typeValidator
148+ , dimap f FailureAllOf (D4.allOfValidator lateral)
149+ , dimap f FailureAnyOf (D4.anyOfValidator lateral)
150+ , dimap f FailureOneOf (D4.oneOfValidator lateral)
151+ , dimap f FailureNot (D4.notValidator lateral)
144152 ]
145153 where
146154 f :: FromJSON a => Schema -> Maybe a
@@ -148,13 +156,18 @@ d4Spec referenced visited scope =
148156 AE.Error _ -> Nothing
149157 AE.Success b -> Just b
150158
151− -- 'Maybe Text' is the URI the refernced schema is fetch from,
159+ -- 'Maybe Text' is the URI the referenced schema is fetched from,
152160 -- this probably needs a 'newtype' wrapper.
153− refVal :: AN.VisitedSchemas -> Maybe Text -> Schema -> Value -> [Failure]
154− refVal = continueValidating referenced
155−
156− descend :: Schema -> Value -> [Failure]
161+ getRef
162+ :: D4.VisitedSchemas
163+ -> Maybe Text
164+ -> Schema
165+ -> Value
166+ -> [ValidatorFailure]
167+ getRef = continueValidating referenced
168+
169+ descend :: Schema -> Value -> [ValidatorFailure]
157170 descend = continueValidating referenced mempty scope
158171
159− lateral :: Schema -> Value -> [Failure]
172+ lateral :: Schema -> Value -> [ValidatorFailure]
160173 lateral = continueValidating referenced visited scope
examples/Full.hsdeleted−40
@@ -1,40 +0,0 @@
1−-- | Two differences from @examples/Simple.hs@:
2−--
3−-- * This shows how to write the starting schema in Haskell instead
4−-- of parsing it from JSON.
5−--
6−-- * Validation is done in two steps using 'D4.referencesViaFilesystem' and
7−-- 'D4.checkSchema' instead of 'D4.fetchFilesystemAndValidate'. This means
8−-- that the actual validation involves no IO.
9−
10−module Full where
11−
12−import Data.Aeson (Value (..), toJSON)
13−
14−import qualified Data.JsonSchema.Draft4 as D4
15−
16−schema :: D4.Schema
17−schema = D4.emptySchema { D4._schemaRef = Just "./unique.json" }
18−
19−schemaContext :: D4.SchemaWithURI D4.Schema
20−schemaContext = D4.SchemaWithURI
21− { D4._swSchema = schema
22− , D4._swURI = Just "./examples/json/start.json"
23− }
24−
25−badData :: Value
26−badData = toJSON (["foo", "foo"] :: [String])
27−
28−example :: IO ()
29−example = do
30− res <- D4.referencesViaFilesystem schemaContext
31− let references = case res of
32− Left _ -> error "Couldn't fetch referenced schemas."
33− Right a -> a
34− validate = case D4.checkSchema references schemaContext of
35− Left _ -> error "Not a valid schema."
36− Right f -> f
37− case validate badData of
38− [] -> error "We validated bad data."
39− [D4.Failure (D4.Ref D4.UniqueItems) _ _ _] -> return () -- Success
40− _ -> error "We got a different failure than expected."
examples/Simple.hs+21−20
@@ -1,10 +1,8 @@
1−-- | Demonstrate 'D4.fetchFilesystemAndValidate'.
2−--
3−-- To fetch schemas using HTTP instead of from the filesystem use
4−-- 'D4.fetchHTTPAndValidate'.
51
62module Simple where
73
4+import Protolude
5+
86import Data.Aeson (Value(..), decode, toJSON)
97import qualified Data.ByteString.Lazy as LBS
108import qualified Data.List.NonEmpty as NE
@@ -13,25 +11,28 @@ import Data.Maybe (fromMaybe)
1311import qualified Data.JsonSchema.Draft4 as D4
1412
1513badData :: Value
16−badData = toJSON (["foo", "foo"] :: [String])
14+badData = toJSON [True, True]
1715
1816example :: IO ()
1917example = do
18+ bts <- LBS.readFile "./examples/json/unique.json"
19+ let schema = fromMaybe (panic "Invalid schema JSON.") (decode bts)
20+ schemaWithURI = D4.SchemaWithURI
21+ schema
22+ Nothing -- This would be the URI of the schema
23+ -- if it had one. It's used if the schema
24+ -- has relative references to other
25+ -- schemas.
2026
21− -- Get the starting schema.
22− bts <- LBS.readFile "./examples/json/start.json"
23− let schema = fromMaybe (error "Invalid schema JSON.") (decode bts)
24− schemaWithURI = D4.SchemaWithURI schema (Just "./examples/json/start.json")
25−
26− -- Fetch any referenced schemas, check that our original schema itself
27− -- is valid, and validate the data.
28− res <- D4.fetchFilesystemAndValidate schemaWithURI badData
27+ -- Fetch any referenced schemas over HTTP, check that our original schema
28+ -- itself is valid, and validate the data.
29+ res <- D4.fetchHTTPAndValidate schemaWithURI badData
2930 case res of
30− Right () -> error "We validated bad data."
31− Left (D4.FVRead _) -> error ("Error fetching a referenced schema"
32− ++ " (either during IO or parsing).")
33− Left (D4.FVSchema _) -> error "Our 'schema' itself was invalid."
34− Left (D4.FVData failures) ->
31+ Right () -> panic "We validated bad data."
32+ Left (D4.HVRequest _) -> panic ("Error fetching a referenced schema"
33+ <> " (either during IO or parsing).")
34+ Left (D4.HVSchema _) -> panic "Our 'schema' itself was invalid."
35+ Left (D4.HVData (D4.Invalid _ _ failures)) ->
3536 case NE.toList failures of
36− [D4.Failure (D4.Ref D4.UniqueItems) _ _ _] -> return () -- Success.
37− _ -> error "Got more invalidation reasons than we expected."
37+ [D4.FailureUniqueItems _] -> pure () -- Success.
38+ _ -> panic "We got a different failure than expected."
examples/TwoStep.hsnew file+49
@@ -0,0 +1,49 @@
1+-- | Differences from @examples/Simple.hs@:
2+--
3+-- * We demonstrate how to do the validation step without IO by first
4+-- getting the references with 'D4.referencesViaFilesystem'.
5+--
6+-- * This shows how to write the starting schema in Haskell instead
7+-- of parsing it from JSON.
8+--
9+-- * This schema references other schemas. Previously we used
10+-- 'fetchHTTPAndValidate' which fetched references over HTTP (though it
11+-- didn't matter because the last schema didn't actually have any).
12+-- This time we need to get them from the filesystem.
13+
14+module TwoStep where
15+
16+import Protolude
17+
18+import Data.Aeson (Value (..), toJSON)
19+import qualified Data.List.NonEmpty as NE
20+
21+import qualified Data.JsonSchema.Draft4 as D4
22+import qualified Data.Validator.Draft4 as VAL
23+
24+schema :: D4.Schema
25+schema = D4.emptySchema { D4._schemaRef = Just "./unique.json" }
26+
27+schemaContext :: D4.SchemaWithURI D4.Schema
28+schemaContext = D4.SchemaWithURI
29+ { D4._swSchema = schema
30+ , D4._swURI = Just "./examples/json/start.json"
31+ }
32+
33+badData :: Value
34+badData = toJSON [True, True]
35+
36+example :: IO ()
37+example = do
38+ res <- D4.referencesViaFilesystem schemaContext
39+ let references = case res of
40+ Left _ -> panic "Couldn't fetch referenced schemas."
41+ Right a -> a
42+ validate = case D4.checkSchema references schemaContext of
43+ Left _ -> panic "Not a valid schema."
44+ Right f -> f
45+ case validate badData of
46+ [] -> panic "We validated bad data."
47+ [D4.FailureRef (VAL.RefInvalid _ _ (D4.FailureUniqueItems _ NE.:|[])) ] ->
48+ pure () -- Success
49+ _ -> panic "We got a different failure than expected."
hjsonschema.cabal+16−9
@@ -1,5 +1,5 @@
11name: hjsonschema
2−version: 1.2.0.2
2+version: 1.3.0.0
33synopsis: JSON Schema library
44homepage: https://github.com/seagreen/hjsonschema
55license: MIT
@@ -26,15 +26,15 @@ library
2626 src
2727 default-language: Haskell2010
2828 default-extensions:
29+ DeriveFunctor
30+ DeriveGeneric
31+ GeneralizedNewtypeDeriving
2932 NoImplicitPrelude
3033 OverloadedStrings
3134 ScopedTypeVariables
35+ TupleSections
3236 if impl(ghc >= 8)
3337 default-extensions: StrictData
34− other-extensions:
35− DeriveFunctor
36− GeneralizedNewtypeDeriving
37− TemplateHaskell
3838 ghc-options:
3939 -Wall
4040 exposed-modules:
@@ -50,7 +50,6 @@ library
5050 , Data.Validator.Draft4.Number
5151 , Data.Validator.Draft4.Object
5252 , Data.Validator.Draft4.String
53− , Data.Validator.Failure
5453 , Data.Validator.Reference
5554 , Data.Validator.Types
5655 , Data.Validator.Utils
@@ -60,17 +59,19 @@ library
6059 build-depends:
6160 base >= 4.7 && < 4.10
6261 -- 0.11 is for `.:!`:
63− , aeson >= 0.11 && < 1.1
62+ , aeson >= 0.11 && < 1.2
6463 , bytestring >= 0.10 && < 0.11
6564 , containers >= 0.5 && < 0.6
6665 , file-embed >= 0.0.8 && < 0.1
6766 , filepath >= 1.3 && < 1.5
68− , hjsonpointer >= 0.3 && < 1.1
67+ , hashable >= 1.2 && < 1.3
68+ , hjsonpointer >= 1.1 && < 1.2
6969 -- 0.4.30 is for parseUrlThrow:
7070 , http-client >= 0.4.30 && < 0.6
7171 , http-types >= 0.8 && < 0.10
7272 , pcre-heavy >= 1.0 && < 1.1
7373 , profunctors >= 5.0 && < 5.3
74+ , protolude >= 0.1.10 && < 1.2
7475 , QuickCheck >= 2.8 && < 2.10
7576 , scientific >= 0.3 && < 0.4
7677 , semigroups >= 0.18 && < 0.19
@@ -89,6 +90,8 @@ test-suite local
8990 -Wall
9091 -fno-warn-orphans
9192 default-extensions:
93+ DeriveGeneric
94+ NoImplicitPrelude
9295 OverloadedStrings
9396 ScopedTypeVariables
9497 other-modules:
@@ -98,8 +101,8 @@ test-suite local
98101 , Shared
99102 -- from ./examples:
100103 , AlternateSchema
101− , Full
102104 , Simple
105+ , TwoStep
103106 build-depends:
104107 base
105108 , aeson
@@ -108,6 +111,7 @@ test-suite local
108111 , hjsonpointer
109112 , hjsonschema
110113 , profunctors
114+ , protolude
111115 , semigroups
112116 , text
113117 , QuickCheck
@@ -129,6 +133,8 @@ test-suite remote
129133 -Wall
130134 -fno-warn-orphans
131135 default-extensions:
136+ DeriveGeneric
137+ NoImplicitPrelude
132138 OverloadedStrings
133139 ScopedTypeVariables
134140 other-modules:
@@ -144,6 +150,7 @@ test-suite remote
144150 , hjsonpointer
145151 , hjsonschema
146152 , profunctors
153+ , protolude
147154 , semigroups
148155 , text
149156 , unordered-containers
shell.nix+3
@@ -6,5 +6,8 @@ haskell.lib.buildStackProject {
66 buildInputs = [
77 pkgconfig
88 pcre
9+
10+ # for `git:` references in stack.yml:
11+ git
912 ];
1013}
src/Data/JsonSchema/Draft4.hs+58−46
@@ -10,7 +10,7 @@ module Data.JsonSchema.Draft4
1010 , fetchHTTPAndValidate
1111 , HTTPValidationFailure(..)
1212 , FE.HTTPFailure(..)
13− , InvalidSchema
13+ , SchemaInvalid(..)
1414
1515 -- * One-step validation (getting references from the filesystem)
1616 , fetchFilesystemAndValidate
@@ -18,13 +18,12 @@ module Data.JsonSchema.Draft4
1818 , FE.FilesystemFailure(..)
1919
2020 -- * Validation failure
21− , Invalid
22− , Failure
23− , FR.Fail(..)
24− , ValidatorChain(..)
21+ , Invalid(..)
22+ , ValidatorFailure(..)
2523
2624 -- * Fetching tools
2725 , ReferencedSchemas(..)
26+ , FE.URISchemaMap(..)
2827 , referencesViaHTTP
2928 , referencesViaFilesystem
3029
@@ -38,9 +37,8 @@ module Data.JsonSchema.Draft4
3837 ) where
3938
4039import Import
41−import Prelude
4240
43−import Control.Arrow (first, left)
41+import Control.Arrow (left)
4442import qualified Data.ByteString as BS
4543import Data.FileEmbed (embedFile,
4644 makeRelativeToProject)
@@ -48,25 +46,24 @@ import qualified Data.HashMap.Strict as HM
4846import qualified Data.List.NonEmpty as NE
4947import Data.Maybe (fromMaybe)
5048
51−import Data.JsonSchema.Draft4.Failure (Failure, Invalid,
52− InvalidSchema,
53− ValidatorChain(..))
49+import Data.JsonSchema.Draft4.Failure (Invalid(..),
50+ SchemaInvalid(..),
51+ ValidatorFailure(..))
5452import Data.JsonSchema.Draft4.Schema (Schema)
5553import qualified Data.JsonSchema.Draft4.Schema as SC
5654import qualified Data.JsonSchema.Draft4.Spec as Spec
5755import Data.JsonSchema.Fetch (ReferencedSchemas(..),
5856 SchemaWithURI(..))
5957import qualified Data.JsonSchema.Fetch as FE
60−import qualified Data.Validator.Failure as FR
6158
6259data HTTPValidationFailure
6360 = HVRequest FE.HTTPFailure
64− | HVSchema InvalidSchema
61+ | HVSchema SchemaInvalid
6562 | HVData Invalid
6663 deriving Show
6764
6865-- | Fetch recursively referenced schemas over HTTP, check that both the
69−-- original and referenced schemas are valid, and then validate data.
66+-- original and referenced schemas are valid, then validate then data.
7067fetchHTTPAndValidate
7168 :: SchemaWithURI Schema
7269 -> Value
@@ -76,23 +73,27 @@ fetchHTTPAndValidate sw v = do
7673 pure (g =<< f =<< left HVRequest res)
7774 where
7875 f :: FE.URISchemaMap Schema
79− -> Either HTTPValidationFailure (Value -> [Failure])
76+ -> Either HTTPValidationFailure (Value -> [ValidatorFailure])
8077 f references = left HVSchema (checkSchema references sw)
8178
82− g :: (Value -> [Failure]) -> Either HTTPValidationFailure ()
83− g validate = case NE.nonEmpty (validate v) of
84− Nothing -> Right ()
85− Just failures -> Left (HVData failures)
79+ g :: (Value -> [ValidatorFailure]) -> Either HTTPValidationFailure ()
80+ g val = case NE.nonEmpty (val v) of
81+ Nothing -> Right ()
82+ Just failures -> Left (HVData Invalid
83+ { _invalidSchema = _swSchema sw
84+ , _invalidInstance = v
85+ , _invalidFailures = failures
86+ })
8687
8788data FilesystemValidationFailure
8889 = FVRead FE.FilesystemFailure
89− | FVSchema InvalidSchema
90+ | FVSchema SchemaInvalid
9091 | FVData Invalid
9192 deriving (Show, Eq)
9293
93−-- | Fetch recursively referenced schemas from the filesystem, check that
94−-- both the original and referenced schemas are valid, and then
95−-- validate data.
94+-- | Fetch recursively referenced schemas from the filesystem, check
95+-- that both the original and referenced schemas are valid, then validate
96+-- the data.
9697fetchFilesystemAndValidate
9798 :: SchemaWithURI Schema
9899 -> Value
@@ -102,13 +103,17 @@ fetchFilesystemAndValidate sw v = do
102103 pure (g =<< f =<< left FVRead res)
103104 where
104105 f :: FE.URISchemaMap Schema
105− -> Either FilesystemValidationFailure (Value -> [Failure])
106+ -> Either FilesystemValidationFailure (Value -> [ValidatorFailure])
106107 f references = left FVSchema (checkSchema references sw)
107108
108− g :: (Value -> [Failure]) -> Either FilesystemValidationFailure ()
109− g validate = case NE.nonEmpty (validate v) of
110− Nothing -> Right ()
111− Just invalid -> Left (FVData invalid)
109+ g :: (Value -> [ValidatorFailure]) -> Either FilesystemValidationFailure ()
110+ g val = case NE.nonEmpty (val v) of
111+ Nothing -> Right ()
112+ Just invalid -> Left (FVData Invalid
113+ { _invalidSchema = _swSchema sw
114+ , _invalidInstance = v
115+ , _invalidFailures = invalid
116+ })
112117
113118-- | An instance of 'Data.JsonSchema.Fetch.FetchInfo' specialized for
114119-- JSON Schema Draft 4.
@@ -128,27 +133,31 @@ referencesViaFilesystem
128133 -> IO (Either FE.FilesystemFailure (FE.URISchemaMap Schema))
129134referencesViaFilesystem = FE.referencesViaFilesystem' draft4FetchInfo
130135
131−-- | A helper function.
132−--
133−-- Checks if a schema and a set of referenced schemas are valid.
136+-- | Checks if a schema and a set of referenced schemas are valid.
134137--
135138-- Return a function to validate data.
136139checkSchema
137140 :: FE.URISchemaMap Schema
138141 -> SchemaWithURI Schema
139− -> Either InvalidSchema (Value -> [Failure])
142+ -> Either SchemaInvalid (Value -> [ValidatorFailure])
140143checkSchema sm sw =
141144 case NE.nonEmpty failures of
142− Nothing -> Right (Spec.validate (ReferencedSchemas (_swSchema sw) sm) sw)
143− Just fs -> Left fs
145+ Just fs -> Left (SchemaInvalid fs)
146+ Nothing -> Right (Spec.specValidate
147+ (ReferencedSchemas (_swSchema sw)
148+ (FE._unURISchemaMap sm))
149+ sw)
144150 where
145− failures :: [(Maybe Text, Failure)]
146− failures = ((\v -> (Nothing, v)) <$> schemaValidity (_swSchema sw))
147− <> (first Just <$> referencesValidity sm)
151+ failures :: [(Maybe Text, NonEmpty ValidatorFailure)]
152+ failures =
153+ let refFailures = first Just <$> referencesValidity sm
154+ in case NE.nonEmpty (schemaValidity (_swSchema sw)) of
155+ Nothing -> refFailures
156+ Just errs -> (Nothing,errs) : refFailures
148157
149158metaSchema :: Schema
150159metaSchema =
151− fromMaybe (error "Schema decode failed (this should never happen)")
160+ fromMaybe (panic "Schema decode failed (this should never happen)")
152161 . decodeStrict
153162 $ metaSchemaBytes
154163
@@ -158,26 +167,29 @@ metaSchemaBytes =
158167
159168-- | Check that a schema itself is valid
160169-- (if so the returned list will be empty).
161−schemaValidity :: Schema -> [Failure]
170+schemaValidity :: Schema -> [ValidatorFailure]
162171schemaValidity =
163− Spec.validate referenced (SchemaWithURI metaSchema Nothing) . toJSON
172+ Spec.specValidate referenced (SchemaWithURI metaSchema Nothing) . toJSON
164173 where
165174 referenced :: ReferencedSchemas Schema
166175 referenced = ReferencedSchemas
167176 metaSchema
168177 (HM.singleton "http://json-schema.org/draft-04/schema"
169− metaSchema)
178+ metaSchema)
170179
171180-- | Check that a set of referenced schemas are valid
172181-- (if so the returned list will be empty).
173182referencesValidity
174183 :: FE.URISchemaMap Schema
175− -> [(Text, Failure)]
176− -- ^ The first value in the tuple is the URI of a referenced schema.
177−referencesValidity = HM.foldlWithKey' f mempty
184+ -> [(Text, NonEmpty ValidatorFailure)]
185+ -- ^ The first item of the tuple is the URI of a schema, the second
186+ -- is that schema's validation errors.
187+referencesValidity = HM.foldlWithKey' f mempty . FE._unURISchemaMap
178188 where
179− f :: [(Text, Failure)]
189+ f :: [(Text, NonEmpty ValidatorFailure)]
180190 -> Text
181191 -> Schema
182− -> [(Text, Failure)]
183− f acc k v = ((\a -> (k,a)) <$> schemaValidity v) <> acc
192+ -> [(Text, NonEmpty ValidatorFailure)]
193+ f acc k v = case NE.nonEmpty (schemaValidity v) of
194+ Nothing -> acc
195+ Just errs -> (k,errs) : acc
src/Data/JsonSchema/Draft4/Failure.hs+49−113
@@ -1,123 +1,59 @@
11
22module Data.JsonSchema.Draft4.Failure where
33
4−import Prelude
5−
6−import Data.List.NonEmpty (NonEmpty)
7−import Data.Text (Text)
8−
9−import qualified Data.Validator.Draft4.Any as AN
10−import qualified Data.Validator.Draft4.Array as AR
11−import qualified Data.Validator.Draft4.Number as NU
12−import qualified Data.Validator.Draft4.Object as OB
13−import qualified Data.Validator.Failure as FR
4+import Import
5+
6+import Data.JsonSchema.Draft4.Schema (Schema)
7+import qualified Data.Validator.Draft4 as D4
8+
9+-- | Used to report an entire instance being invalidated, as opposed
10+-- to the failure of a single validator.
11+data Invalid = Invalid
12+ { _invalidSchema :: Schema
13+ , _invalidInstance :: Value
14+ , _invalidFailures :: NonEmpty ValidatorFailure
15+ } deriving (Eq, Show)
16+
17+data ValidatorFailure
18+ = FailureMultipleOf D4.MultipleOfInvalid
19+ | FailureMaximum D4.MaximumInvalid
20+ | FailureMinimum D4.MinimumInvalid
21+
22+ | FailureMaxLength D4.MaxLengthInvalid
23+ | FailureMinLength D4.MinLengthInvalid
24+ | FailurePattern D4.PatternInvalid
25+
26+ | FailureMaxItems D4.MaxItemsInvalid
27+ | FailureMinItems D4.MinItemsInvalid
28+ | FailureUniqueItems D4.UniqueItemsInvalid
29+ | FailureItems (D4.ItemsInvalid ValidatorFailure)
30+ | FailureAdditionalItems (D4.AdditionalItemsInvalid ValidatorFailure)
31+
32+ | FailureMaxProperties D4.MaxPropertiesInvalid
33+ | FailureMinProperties D4.MinPropertiesInvalid
34+ | FailureRequired ()
35+ | FailureDependencies (D4.DependenciesInvalid ValidatorFailure)
36+ | FailurePropertiesRelated (D4.PropertiesRelatedInvalid ValidatorFailure)
37+
38+ | FailureRef (D4.RefInvalid ValidatorFailure)
39+ | FailureEnum D4.EnumInvalid
40+ | FailureType D4.TypeValidatorInvalid
41+ | FailureAllOf (D4.AllOfInvalid ValidatorFailure)
42+ | FailureAnyOf (D4.AnyOfInvalid ValidatorFailure)
43+ | FailureOneOf (D4.OneOfInvalid ValidatorFailure)
44+ | FailureNot D4.NotValidatorInvalid
45+ deriving (Eq, Show)
1446
1547-- | A description of why a schema (or one of its reference) is itself
1648-- invalid.
1749--
1850-- 'Nothing' indicates the starting schema. 'Just' indicates a referenced
19−-- schema -- the contents of the 'Just' is the schema's URI.
51+-- schema. The contents of the 'Just' is the schema's URI.
2052--
21−-- NOTE: 'HashMap (Maybe Text) Invalid' would be a nicer way of defining
22−-- this, but then we lose the guarantee that there's at least one key.
23−type InvalidSchema = NonEmpty (Maybe Text, Failure)
24−
25−type Invalid = NonEmpty Failure
26−
27−type Failure = FR.Fail ValidatorChain
28−
29−-- | Distinguish all the different possible causes of failure for
30−-- Draft 4 validation.
31−data ValidatorChain
32− = MultipleOf
33− | Maximum
34− | ExclusiveMaximum
35− | Minimum
36− | ExclusiveMinimum
37−
38− | MaxLength
39− | MinLength
40− | PatternValidator
41−
42− | MaxItems
43− | MinItems
44− | UniqueItems
45− | Items ValidatorChain
46− | AdditionalItemsBool
47− | AdditionalItemsObject ValidatorChain
48−
49− | MaxProperties
50− | MinProperties
51− | Required
52− | SchemaDependency ValidatorChain
53− | PropertyDependency
54− | Properties ValidatorChain
55− | PatternProperties ValidatorChain
56− | AdditionalPropertiesBool
57− | AdditionalPropertiesObject ValidatorChain
58−
59− | RefResolution
60− -- ^ Indicates a reference that failed to resolve.
61− --
62− -- NOTE: The language agnostic test suite doesn't specify if this should
63− -- cause a validation error or should allow data to pass. We choose to
64− -- return a validation error.
65− --
66− -- Also note that ideally we would enforce in the type system that any
67− -- failing references be dealt with before valididation. Then this could
68− -- be removed entirely.
69− | RefLoop
70− | Ref ValidatorChain
71− | Enum
72− | TypeValidator
73− | AllOf ValidatorChain
74− | AnyOf ValidatorChain
75− | OneOfTooManySuccesses
76− | OneOfNoSuccesses ValidatorChain
77− | NotValidator
53+-- NOTE: 'HashMap (Maybe Text) Invalid' would be a nicer way of
54+-- defining this, but then we lose the guarantee that there's at least
55+-- one key.
56+newtype SchemaInvalid
57+ = SchemaInvalid {
58+ _unSchemaInvalid :: NonEmpty (Maybe Text, NonEmpty ValidatorFailure) }
7859 deriving (Eq, Show)
79−
80−maxE :: NU.MaximumInvalid -> ValidatorChain
81−maxE NU.Maximum = Maximum
82−maxE NU.ExclusiveMaximum = ExclusiveMaximum
83−
84−minE :: NU.MinimumInvalid -> ValidatorChain
85−minE NU.Minimum = Minimum
86−minE NU.ExclusiveMinimum = ExclusiveMinimum
87−
88−itemsE :: AR.ItemsInvalid ValidatorChain -> ValidatorChain
89−itemsE (AR.Items err) = Items err
90−itemsE AR.AdditionalItemsBoolInvalid = AdditionalItemsBool
91−itemsE (AR.AdditionalItemsObjectInvalid err) = AdditionalItemsObject err
92−
93−depsE :: OB.DependencyInvalid ValidatorChain -> ValidatorChain
94−depsE (OB.SchemaDependencyInvalid err) = SchemaDependency err
95−depsE OB.PropertyDependencyInvalid = PropertyDependency
96−
97−propE :: OB.PropertiesInvalid ValidatorChain -> ValidatorChain
98−propE (OB.PropertiesInvalid err) = Properties err
99−propE (OB.PropPatternInvalid err) = PatternProperties err
100−propE (OB.PropAdditionalInvalid a) =
101− case a of
102− OB.APBoolInvalid -> AdditionalPropertiesBool
103− OB.APObjectInvalid err -> AdditionalPropertiesObject err
104−
105−patPropE :: OB.PatternPropertiesInvalid ValidatorChain -> ValidatorChain
106−patPropE (OB.PPInvalid err) = PatternProperties err
107−patPropE (OB.PPAdditionalPropertiesInvalid a) =
108− case a of
109− OB.APBoolInvalid -> AdditionalPropertiesBool
110− OB.APObjectInvalid err -> AdditionalPropertiesObject err
111−
112−addPropE :: OB.AdditionalPropertiesInvalid ValidatorChain -> ValidatorChain
113−addPropE OB.APBoolInvalid = AdditionalPropertiesBool
114−addPropE (OB.APObjectInvalid err) = AdditionalPropertiesObject err
115−
116−refE :: AN.RefInvalid ValidatorChain -> ValidatorChain
117−refE AN.RefResolution = RefResolution
118−refE AN.RefLoop = RefLoop
119−refE (AN.RefInvalid err) = Ref err
120−
121−oneOfE :: AN.OneOfInvalid ValidatorChain -> ValidatorChain
122−oneOfE AN.TooManySuccesses = OneOfTooManySuccesses
123−oneOfE (AN.NoSuccesses err) = OneOfNoSuccesses err
src/Data/JsonSchema/Draft4/Schema.hs+24−20
@@ -1,17 +1,16 @@
11
22module Data.JsonSchema.Draft4.Schema where
33
4−import Import
5−import Prelude
4+import Import hiding (mapMaybe)
65
7−import qualified Data.HashMap.Strict as HM
8−import Data.List.NonEmpty (NonEmpty)
9−import Data.Maybe (fromJust, isJust)
6+import qualified Data.HashMap.Strict as HM
7+import Data.List.NonEmpty (NonEmpty)
8+import Data.Maybe (fromJust, isJust)
109import Data.Scientific
10+import qualified Data.Set as Set
11+import qualified Data.Text as T
1112
12−import qualified Data.Validator.Draft4.Any as AN
13−import qualified Data.Validator.Draft4.Array as AR
14−import qualified Data.Validator.Draft4.Object as OB
13+import qualified Data.Validator.Draft4 as D4
1514import Data.Validator.Utils
1615
1716data Schema = Schema
@@ -26,6 +25,10 @@ data Schema = Schema
2625 -- contain schemas anywhere (not just in "definitions" or any
2726 -- of the other official keys) we save any leftover key/value
2827 -- pairs not covered by them here.
28+ --
29+ -- TODO: This field is the source of most of the complication in this
30+ -- module and needs to be removed. It should be doable, though it will
31+ -- involve some modification to the fetching code.
2932
3033 , _schemaMultipleOf :: Maybe Scientific
3134 , _schemaMaximum :: Maybe Scientific
@@ -40,22 +43,22 @@ data Schema = Schema
4043 , _schemaMaxItems :: Maybe Int
4144 , _schemaMinItems :: Maybe Int
4245 , _schemaUniqueItems :: Maybe Bool
43− , _schemaItems :: Maybe (AR.Items Schema)
46+ , _schemaItems :: Maybe (D4.Items Schema)
4447 -- Note that '_schemaAdditionalItems' is left out of 'runValidate'
4548 -- because its functionality is handled by '_schemaItems'. It always
4649 -- validates data unless 'Items' is present.
47− , _schemaAdditionalItems :: Maybe (AR.AdditionalItems Schema)
50+ , _schemaAdditionalItems :: Maybe (D4.AdditionalItems Schema)
4851
4952 , _schemaMaxProperties :: Maybe Int
5053 , _schemaMinProperties :: Maybe Int
51− , _schemaRequired :: Maybe OB.Required
52− , _schemaDependencies :: Maybe (HashMap Text (OB.Dependency Schema))
54+ , _schemaRequired :: Maybe (Set Text)
55+ , _schemaDependencies :: Maybe (HashMap Text (D4.Dependency Schema))
5356 , _schemaProperties :: Maybe (HashMap Text Schema)
5457 , _schemaPatternProperties :: Maybe (HashMap Text Schema)
55− , _schemaAdditionalProperties :: Maybe (OB.AdditionalProperties Schema)
58+ , _schemaAdditionalProperties :: Maybe (D4.AdditionalProperties Schema)
5659
57− , _schemaEnum :: Maybe AN.EnumVal
58− , _schemaType :: Maybe AN.TypeVal
60+ , _schemaEnum :: Maybe (NonEmpty Value)
61+ , _schemaType :: Maybe D4.TypeValidator
5962 , _schemaAllOf :: Maybe (NonEmpty Schema)
6063 , _schemaAnyOf :: Maybe (NonEmpty Schema)
6164 , _schemaOneOf :: Maybe (NonEmpty Schema)
@@ -262,9 +265,9 @@ instance Arbitrary Schema where
262265 a <- maybeGen arbitraryText
263266 b <- maybeGen arbitraryText
264267 c <- maybeGen arbitraryText
265− -- NOTE: The next two fields are empty to generate cleaner schemas,
266− -- but note that this means we don't test e.g. the invertability
267− -- of these fields.
268+ -- NOTE: The next two fields are empty to generate cleaner
269+ -- schemas, but note that this means we don't test the
270+ -- invertability of these fields.
268271 d <- pure Nothing -- _schemaDefinitions
269272 e <- pure mempty -- _otherPairs
270273
@@ -286,13 +289,14 @@ instance Arbitrary Schema where
286289
287290 s <- maybeGen (getPositive <$> arbitrary)
288291 t <- maybeGen (getPositive <$> arbitrary)
289− u <- arbitrary
292+ u <- maybeGen (Set.map T.pack <$> arbitrary)
290293 v <- maybeRecurse n arbitraryHashMap
291294 w <- maybeRecurse n arbitraryHashMap
292295 x <- maybeRecurse n arbitraryHashMap
293296 y <- maybeRecurse n arbitrary
294297
295− z <- arbitrary
298+ z <- maybeRecurse n ( fmap _unArbitraryValue . _unNonEmpty'
299+ <$> arbitrary)
296300 a2 <- arbitrary
297301 b2 <- maybeRecurse n (_unNonEmpty' <$> arbitrary)
298302 c2 <- maybeRecurse n (_unNonEmpty' <$> arbitrary)
src/Data/JsonSchema/Draft4/Spec.hs+68−77
@@ -2,10 +2,8 @@
22module Data.JsonSchema.Draft4.Spec where
33
44import Import
5−-- Hiding is for GHCs before 7.10:
6−import Prelude hiding (concat)
75
8−import Data.Maybe (fromMaybe, isNothing)
6+import Data.Maybe (fromMaybe)
97import Data.Profunctor (Profunctor(..))
108
119import Data.JsonSchema.Draft4.Failure
@@ -15,33 +13,26 @@ import Data.JsonSchema.Fetch (ReferencedSchemas(..),
1513import qualified Data.JsonSchema.Fetch as FE
1614import Data.JsonSchema.Types (Spec(..))
1715import qualified Data.JsonSchema.Types as JT
18−import qualified Data.Validator.Draft4 as D4
19−import qualified Data.Validator.Draft4.Any as AN
16+import Data.Validator.Draft4
2017import Data.Validator.Reference (updateResolutionScope)
2118
2219embedded :: Schema -> ([Schema], [Schema])
2320embedded s = JT.embedded (d4Spec (ReferencedSchemas s mempty) mempty Nothing) s
2421
25−-- | For internal use.
26−--
27−-- A specialized version of 'const' that prevents overwriting
28−-- useful information.
29−toss :: a -> () -> a
30−toss = const
31−
32−validate
22+specValidate
3323 :: ReferencedSchemas Schema
3424 -> SchemaWithURI Schema
3525 -> Value
36− -> [Failure]
37−validate rs = continueValidating rs (AN.VisitedSchemas [(Nothing, Nothing)])
26+ -> [ValidatorFailure]
27+specValidate rs =
28+ continueValidating rs (VisitedSchemas [(Nothing, Nothing)])
3829
3930continueValidating
4031 :: ReferencedSchemas Schema
41− -> AN.VisitedSchemas
32+ -> VisitedSchemas
4233 -> SchemaWithURI Schema
4334 -> Value
44− -> [Failure]
35+ -> [ValidatorFailure]
4536continueValidating referenced visited sw =
4637 JT.validate (d4Spec referenced visited currentScope)
4738 (_swSchema sw)
@@ -53,85 +44,85 @@ continueValidating referenced visited sw =
5344
5445d4Spec
5546 :: ReferencedSchemas Schema
56− -> AN.VisitedSchemas
47+ -> VisitedSchemas
5748 -> Maybe Text
58− -> Spec Schema ValidatorChain
49+ -> Spec Schema ValidatorFailure
5950d4Spec referenced visited scope = Spec
60− [ dimap
61− (fmap D4.MultipleOf . _schemaMultipleOf)
62− (toss MultipleOf)
63− D4.multipleOf
51+ [ dimap (fmap MultipleOf . _schemaMultipleOf) FailureMultipleOf multipleOfValidator
6452 , dimap
65− (\s -> D4.MaximumContext (fromMaybe False (_schemaExclusiveMaximum s))
66− <$> _schemaMaximum s)
67− maxE
68− D4.maximumVal
53+ (\s -> Maximum (fromMaybe False (_schemaExclusiveMaximum s)) <$> _schemaMaximum s)
54+ FailureMaximum
55+ maximumValidator
6956 , dimap
70− (\s -> D4.MinimumContext (fromMaybe False (_schemaExclusiveMinimum s))
71− <$> _schemaMinimum s)
72− minE
73− D4.minimumVal
57+ (\s -> Minimum (fromMaybe False (_schemaExclusiveMinimum s)) <$> _schemaMinimum s)
58+ FailureMinimum
59+ minimumValidator
7460
75− , dimap (fmap D4.MaxLength . _schemaMaxLength) (toss MaxLength) D4.maxLength
76− , dimap (fmap D4.MinLength . _schemaMinLength) (toss MinLength) D4.minLength
77− , dimap (fmap D4.PatternVal . _schemaPattern) (toss PatternValidator) D4.patternVal
61+ , dimap (fmap MaxLength . _schemaMaxLength) FailureMaxLength maxLengthValidator
62+ , dimap (fmap MinLength . _schemaMinLength) FailureMinLength minLengthValidator
63+ , dimap (fmap PatternValidator . _schemaPattern) FailurePattern patternValidator
7864
79− , dimap (fmap D4.MaxItems . _schemaMaxItems) (toss MaxItems) D4.maxItems
80− , dimap (fmap D4.MinItems . _schemaMinItems) (toss MinItems) D4.minItems
81− , dimap (fmap D4.UniqueItems . _schemaUniqueItems) (toss UniqueItems) D4.uniqueItems
65+ , dimap (fmap MaxItems . _schemaMaxItems) FailureMaxItems maxItemsValidator
66+ , dimap (fmap MinItems . _schemaMinItems) FailureMinItems minItemsValidator
67+ , dimap (fmap UniqueItems . _schemaUniqueItems) FailureUniqueItems uniqueItemsValidator
8268 , dimap
83− (\s -> D4.ItemsContext (_schemaAdditionalItems s) <$> _schemaItems s)
84− itemsE
85− (D4.items descend)
86− , lmap (fmap D4.AdditionalItemsContext . _schemaAdditionalItems) D4.additionalItemsEmbedded
87− , lmap (fmap D4.Definitions . _schemaDefinitions) D4.definitionsEmbedded
69+ (\s -> ItemsRelated
70+ { _irItems = _schemaItems s
71+ , _irAdditional = _schemaAdditionalItems s
72+ })
73+ (\err -> case err of
74+ IRInvalidItems e -> FailureItems e
75+ IRInvalidAdditional e -> FailureAdditionalItems e)
76+ (itemsRelatedValidator descend)
77+ , lmap (fmap Definitions . _schemaDefinitions) definitionsEmbedded
8878
89− , dimap (fmap D4.MaxProperties . _schemaMaxProperties) (toss MaxProperties) D4.maxProperties
90− , dimap (fmap D4.MinProperties . _schemaMinProperties) (toss MinProperties) D4.minProperties
91− , dimap (fmap D4.RequiredContext . _schemaRequired) (toss Required) D4.required
92− , dimap (fmap D4.DependenciesContext . _schemaDependencies) depsE (D4.dependencies descend)
9379 , dimap
94− (\s -> D4.PropertiesContext
95− (_schemaPatternProperties s)
96− (_schemaAdditionalProperties s)
97− <$> _schemaProperties s)
98− propE
99− (D4.properties descend)
80+ (fmap MaxProperties . _schemaMaxProperties)
81+ FailureMaxProperties
82+ maxPropertiesValidator
83+ , dimap
84+ (fmap MinProperties . _schemaMinProperties)
85+ FailureMinProperties
86+ minPropertiesValidator
87+ , dimap (fmap Required . _schemaRequired) FailureRequired requiredValidator
10088 , dimap
101− (\s -> D4.PatternPropertiesContext
102− (isNothing (_schemaProperties s))
103− (_schemaAdditionalProperties s)
104− <$> _schemaPatternProperties s)
105− patPropE
106− (D4.patternProperties descend)
89+ (fmap DependenciesValidator . _schemaDependencies)
90+ FailureDependencies
91+ (dependenciesValidator descend)
10792 , dimap
108− (\s -> D4.AdditionalPropertiesContext
109− (isNothing (_schemaProperties s)
110− && isNothing (_schemaPatternProperties s))
111− <$> _schemaAdditionalProperties s)
112− addPropE
113− (D4.additionalProperties descend)
93+ (\s -> PropertiesRelated
94+ { _propProperties = _schemaProperties s
95+ , _propPattern = _schemaPatternProperties s
96+ , _propAdditional = _schemaAdditionalProperties s
97+ })
98+ FailurePropertiesRelated
99+ (propertiesRelatedValidator descend)
114100
115101 , dimap
116− (\s -> D4.Ref <$> _schemaRef s)
117− refE
118− (D4.ref visited scope (FE.getReference referenced) refVal)
119− , dimap (fmap D4.EnumContext . _schemaEnum) (toss Enum) D4.enumVal
120− , dimap (fmap D4.TypeContext . _schemaType) (toss TypeValidator) D4.typeVal
121− , dimap (fmap D4.AllOf . _schemaAllOf) AllOf (D4.allOf lateral)
122− , dimap (fmap D4.AnyOf . _schemaAnyOf) AnyOf (D4.anyOf lateral)
123− , dimap (fmap D4.OneOf . _schemaOneOf) oneOfE (D4.oneOf lateral)
124− , dimap (fmap D4.NotVal . _schemaNot) (toss NotValidator) (D4.notVal lateral)
102+ (\s -> Ref <$> _schemaRef s)
103+ FailureRef
104+ (refValidator visited scope (FE.getReference referenced) getRef)
105+ , dimap (fmap EnumValidator . _schemaEnum) FailureEnum enumValidator
106+ , dimap (fmap TypeContext . _schemaType) FailureType typeValidator
107+ , dimap (fmap AllOf . _schemaAllOf) FailureAllOf (allOfValidator lateral)
108+ , dimap (fmap AnyOf . _schemaAnyOf) FailureAnyOf (anyOfValidator lateral)
109+ , dimap (fmap OneOf . _schemaOneOf) FailureOneOf (oneOfValidator lateral)
110+ , dimap (fmap NotValidator . _schemaNot) FailureNot (notValidator lateral)
125111 ]
126112 where
127− refVal :: AN.VisitedSchemas -> Maybe Text -> Schema -> Value -> [Failure]
128− refVal newVisited newScope schema =
113+ getRef
114+ :: VisitedSchemas
115+ -> Maybe Text
116+ -> Schema
117+ -> Value
118+ -> [ValidatorFailure]
119+ getRef newVisited newScope schema =
129120 continueValidating referenced newVisited (SchemaWithURI schema newScope)
130121
131− descend :: Schema -> Value -> [Failure]
122+ descend :: Schema -> Value -> [ValidatorFailure]
132123 descend schema =
133124 continueValidating referenced mempty (SchemaWithURI schema scope)
134125
135− lateral :: Schema -> Value -> [Failure]
126+ lateral :: Schema -> Value -> [ValidatorFailure]
136127 lateral schema =
137128 continueValidating referenced visited (SchemaWithURI schema scope)
src/Data/JsonSchema/Fetch.hs+26−26
@@ -2,15 +2,13 @@
22module Data.JsonSchema.Fetch where
33
44import Import
5−-- Hiding is for GHCs before 7.10:
6−import Prelude hiding (concat, sequence)
75
86import Control.Arrow (left)
9−import Control.Exception (catch)
7+import Control.Exception (IOException, catch)
108import Control.Monad (foldM)
11−import qualified Data.ByteString.Lazy as LBS
129import qualified Data.ByteString as BS
13−import qualified Data.HashMap.Strict as H
10+import qualified Data.ByteString.Lazy as LBS
11+import qualified Data.HashMap.Strict as HM
1412import qualified Data.Text as T
1513import qualified Network.HTTP.Client as NC
1614
@@ -50,11 +48,14 @@ data ReferencedSchemas schema = ReferencedSchemas
5048 -- }
5149 -- }
5250 -- }
53− , _rsSchemaMap :: !(URISchemaMap schema)
51+ , _rsSchemaMap :: !(HashMap Text schema)
52+ -- ^ Map of URIs to schemas.
5453 } deriving (Eq, Show)
5554
5655-- | Keys are URIs (without URI fragments).
57−type URISchemaMap schema = HashMap Text schema
56+newtype URISchemaMap schema
57+ = URISchemaMap { _unURISchemaMap :: HashMap Text schema }
58+ deriving (Eq, Show)
5859
5960data SchemaWithURI schema = SchemaWithURI
6061 { _swSchema :: !schema
@@ -68,7 +69,7 @@ data SchemaWithURI schema = SchemaWithURI
6869
6970getReference :: ReferencedSchemas schema -> Maybe Text -> Maybe schema
7071getReference referenced Nothing = Just (_rsStarting referenced)
71−getReference referenced (Just t) = H.lookup t (_rsSchemaMap referenced)
72+getReference referenced (Just t) = HM.lookup t (_rsSchemaMap referenced)
7273
7374--------------------------------------------------
7475-- * Fetch via HTTP
@@ -88,13 +89,13 @@ referencesViaHTTP'
8889 -> IO (Either HTTPFailure (URISchemaMap schema))
8990referencesViaHTTP' info sw = do
9091 manager <- NC.newManager NC.defaultManagerSettings
91− let f = referencesMethodAgnostic (get manager) info sw
92+ let f = referencesMethodAgnostic (getURL manager) info sw
9293 catch (left HTTPParseFailure <$> f) handler
9394 where
94− get :: NC.Manager -> Text -> IO LBS.ByteString
95− get man url = do
95+ getURL :: NC.Manager -> Text -> IO BS.ByteString
96+ getURL man url = do
9697 request <- NC.parseUrlThrow (T.unpack url)
97− NC.responseBody <$> NC.httpLbs request man
98+ LBS.toStrict . NC.responseBody <$> NC.httpLbs request man
9899
99100 handler
100101 :: NC.HttpException
@@ -107,7 +108,7 @@ referencesViaHTTP' info sw = do
107108
108109data FilesystemFailure
109110 = FSParseFailure Text
110− | FSReadFailure IOError
111+ | FSReadFailure IOException
111112 deriving (Show, Eq)
112113
113114referencesViaFilesystem'
@@ -118,13 +119,10 @@ referencesViaFilesystem'
118119referencesViaFilesystem' info sw = catch (left FSParseFailure <$> f) handler
119120 where
120121 f :: IO (Either Text (URISchemaMap schema))
121− f = referencesMethodAgnostic readFile' info sw
122−
123− readFile' :: Text -> IO LBS.ByteString
124− readFile' = fmap LBS.fromStrict . BS.readFile . T.unpack
122+ f = referencesMethodAgnostic (BS.readFile . T.unpack) info sw
125123
126124 handler
127− :: IOError
125+ :: IOException
128126 -> IO (Either FilesystemFailure (URISchemaMap schema))
129127 handler = pure . Left . FSReadFailure
130128
@@ -137,16 +135,16 @@ referencesViaFilesystem' info sw = catch (left FSParseFailure <$> f) handler
137135-- e.g. rejecting non-local URIs.
138136referencesMethodAgnostic
139137 :: forall schema. FromJSON schema
140− => (Text -> IO LBS.ByteString)
138+ => (Text -> IO BS.ByteString)
141139 -> FetchInfo schema
142140 -> SchemaWithURI schema
143141 -> IO (Either Text (URISchemaMap schema))
144142referencesMethodAgnostic fetchRef info =
145− getRecursiveReferences fetchRef info mempty
143+ getRecursiveReferences fetchRef info (URISchemaMap mempty)
146144
147145getRecursiveReferences
148146 :: forall schema. FromJSON schema
149− => (Text -> IO LBS.ByteString)
147+ => (Text -> IO BS.ByteString)
150148 -> FetchInfo schema
151149 -> URISchemaMap schema
152150 -> SchemaWithURI schema
@@ -158,21 +156,23 @@ getRecursiveReferences fetchRef info referenced sw =
158156 -> SchemaWithURI schema
159157 -> IO (Either Text (URISchemaMap schema))
160158 f (Left e) _ = pure (Left e)
161− f (Right g) (SchemaWithURI schema mUri) =
159+ f (Right (URISchemaMap usm)) (SchemaWithURI schema mUri) =
162160 case newRef of
163− Nothing -> pure (Right g)
161+ Nothing -> pure (Right (URISchemaMap usm))
164162 Just uri -> do
165163 bts <- fetchRef uri
166− case eitherDecode bts of
164+ case eitherDecodeStrict bts of
167165 Left e -> pure . Left . T.pack $ e
168166 Right schm -> getRecursiveReferences
169− fetchRef info (H.insert uri schm g)
167+ fetchRef
168+ info
169+ (URISchemaMap (HM.insert uri schm usm))
170170 (SchemaWithURI schm (Just uri))
171171 where
172172 newRef :: Maybe Text
173173 newRef
174174 | Just (Just uri,_) <- resolveReference mUri <$> _fiRef info schema
175− = case H.lookup uri g of
175+ = case HM.lookup uri usm of
176176 Nothing -> Just uri
177177 Just _ -> Nothing
178178 | otherwise = Nothing
src/Data/JsonSchema/Types.hs+4−6
@@ -1,12 +1,9 @@
1−{-# LANGUAGE GeneralizedNewtypeDeriving #-}
21
32module Data.JsonSchema.Types where
43
5−import Prelude
64import Import
75
8−import Data.Validator.Failure (Fail)
9−import Data.Validator.Types (Validator(..))
6+import Data.Validator.Types (Validator(..))
107
118newtype Spec schema err
129 = Spec { _unSpec :: [Validator schema schema err] }
@@ -25,8 +22,9 @@ validate
2522 :: Spec schema err
2623 -> schema
2724 -> Value
28− -> [Fail err]
29−validate spec schema v = _unSpec spec >>= (\val -> _validate val schema v)
25+ -> [err]
26+validate spec schema v =
27+ (\val -> _validate val schema v) =<< _unSpec spec
3028
3129-- | A basic schema type that doesn't impose much structure.
3230--
src/Data/Validator/Draft4.hs+142−450
@@ -1,38 +1,36 @@
11-- | Turn the validation functions into actual 'Validator's.
2−--
3−-- From this point on they know how to parse themselves from JSON
4−-- and also know how to extract subschemas embedded within themselves.
52
6−module Data.Validator.Draft4 where
3+module Data.Validator.Draft4
4+ ( module Data.Validator.Draft4
5+ , module Export
6+ ) where
77
8−import Prelude
98import Import
109
11−import Data.Aeson.Types (Parser)
1210import qualified Data.HashMap.Strict as HM
13−import Data.List.NonEmpty (NonEmpty)
1411import qualified Data.List.NonEmpty as NE
15−import Data.Maybe (catMaybes, isNothing,
16− maybe, maybeToList)
17−import Data.Scientific (Scientific)
12+import Data.Maybe (catMaybes, maybe, maybeToList)
1813import Data.Text (Text)
1914
20−import qualified Data.Validator.Draft4.Any as AN
21−import qualified Data.Validator.Draft4.Array as AR
22−import qualified Data.Validator.Draft4.Number as NU
23−import qualified Data.Validator.Draft4.Object as OB
24−import qualified Data.Validator.Draft4.String as ST
25−import Data.Validator.Failure (Fail(..))
15+import Data.Validator.Draft4.Any as Export
16+import Data.Validator.Draft4.Array as Export
17+import Data.Validator.Draft4.Number as Export
18+import Data.Validator.Draft4.Object as Export
19+import Data.Validator.Draft4.String as Export
2620import Data.Validator.Types (Validator(..))
2721import Data.Validator.Utils (fromJSONEither)
2822
2923-- | For internal use.
30−run :: FromJSON b => (a -> b -> [c]) -> Maybe a -> Value -> [c]
24+--
25+-- Take a validation function, a possibly existing validator, and some data.
26+-- If the validator is exists and can validate the type of data we have,
27+-- attempt to do so and return any failures.
28+run :: FromJSON b => (a -> b -> Maybe c) -> Maybe a -> Value -> [c]
3129run _ Nothing _ = mempty
3230run f (Just a) b =
3331 case fromJSONEither b of
3432 Left _ -> mempty
35− Right c -> f a c
33+ Right c -> maybeToList (f a c)
3634
3735-- | For internal use.
3836noEmbedded :: a -> ([b], [b])
@@ -42,190 +40,116 @@ noEmbedded = const (mempty, mempty)
4240-- * Numbers
4341--------------------------------------------------
4442
45−newtype MultipleOf
46− = MultipleOf { _unMultipleOf :: Scientific }
47− deriving (Eq, Show)
48−
49−instance FromJSON MultipleOf where
50− parseJSON = withObject "MultipleOf" $ \o ->
51− MultipleOf <$> o .: "multipleOf"
52−
53−multipleOf :: Validator a (Maybe MultipleOf) ()
54−multipleOf =
55− Validator
56− noEmbedded
57− (run (fmap maybeToList . NU.multipleOf . _unMultipleOf))
43+multipleOfValidator :: Validator a (Maybe MultipleOf) MultipleOfInvalid
44+multipleOfValidator = Validator noEmbedded (run multipleOfVal)
5845
59−data MaximumContext
60− = MaximumContext Bool Scientific
61− deriving (Eq, Show)
46+maximumValidator :: Validator a (Maybe Maximum) MaximumInvalid
47+maximumValidator = Validator noEmbedded (run maximumVal)
6248
63−instance FromJSON MaximumContext where
64− parseJSON = withObject "MaximumContext" $ \o -> MaximumContext
65− <$> o .:! "exclusiveMaximum" .!= False
66− <*> o .: "maximum"
67−
68−maximumVal :: Validator a (Maybe MaximumContext) NU.MaximumInvalid
69−maximumVal =
70− Validator
71− noEmbedded
72− (run (\(MaximumContext a b) -> maybeToList . NU.maximumVal a b))
73−
74−data MinimumContext
75− = MinimumContext Bool Scientific
76− deriving (Eq, Show)
77−
78−instance FromJSON MinimumContext where
79− parseJSON = withObject "MinimumContext" $ \o -> MinimumContext
80− <$> o .:! "exclusiveMinimum" .!= False
81− <*> o .: "minimum"
82−
83−minimumVal :: Validator a (Maybe MinimumContext) NU.MinimumInvalid
84−minimumVal =
85− Validator
86− noEmbedded
87− (run (\(MinimumContext a b) -> maybeToList . NU.minimumVal a b))
49+minimumValidator :: Validator a (Maybe Minimum) MinimumInvalid
50+minimumValidator = Validator noEmbedded (run minimumVal)
8851
8952--------------------------------------------------
9053-- * Strings
9154--------------------------------------------------
9255
93−newtype MaxLength
94− = MaxLength { _unMaxLength :: Int }
95− deriving (Eq, Show)
56+maxLengthValidator :: Validator a (Maybe MaxLength) MaxLengthInvalid
57+maxLengthValidator = Validator noEmbedded (run maxLengthVal)
9658
97−instance FromJSON MaxLength where
98− parseJSON = withObject "MaxLength" $ \o ->
99− MaxLength <$> o .: "maxLength"
59+minLengthValidator :: Validator a (Maybe MinLength) MinLengthInvalid
60+minLengthValidator = Validator noEmbedded (run minLengthVal)
10061
101−maxLength :: Validator a (Maybe MaxLength) ()
102−maxLength =
103− Validator
104− noEmbedded
105− (run (fmap maybeToList . ST.maxLength . _unMaxLength))
106−
107−newtype MinLength
108− = MinLength { _unMinLength :: Int }
109− deriving (Eq, Show)
110−
111−instance FromJSON MinLength where
112− parseJSON = withObject "MinLength" $ \o ->
113− MinLength <$> o .: "minLength"
114−
115−minLength :: Validator a (Maybe MinLength) ()
116−minLength =
117− Validator
118− noEmbedded
119− (run (fmap maybeToList . ST.minLength . _unMinLength))
120−
121−newtype PatternVal
122− = PatternVal { _unPatternVal :: Text }
123− deriving (Eq, Show)
124−
125−instance FromJSON PatternVal where
126− parseJSON = withObject "PatternVal" $ \o ->
127− PatternVal <$> o .: "pattern"
128−
129−patternVal :: Validator a (Maybe PatternVal) ()
130−patternVal =
131− Validator
132− noEmbedded
133− (run (fmap maybeToList . ST.patternVal . _unPatternVal))
62+patternValidator :: Validator a (Maybe PatternValidator) PatternInvalid
63+patternValidator = Validator noEmbedded (run patternVal)
13464
13565--------------------------------------------------
13666-- * Arrays
13767--------------------------------------------------
13868
139−newtype MaxItems
140− = MaxItems { _unMaxItems :: Int }
141− deriving (Eq, Show)
142−
143−instance FromJSON MaxItems where
144− parseJSON = withObject "MaxItems" $ \o ->
145− MaxItems <$> o .: "maxItems"
146−
147−maxItems :: Validator a (Maybe MaxItems) ()
148−maxItems =
149− Validator
150− noEmbedded
151− (run (fmap maybeToList . AR.maxItems . _unMaxItems))
152−
153−newtype MinItems
154− = MinItems { _unMinItems :: Int }
155− deriving (Eq, Show)
156−
157−instance FromJSON MinItems where
158− parseJSON = withObject "MinItems" $ \o ->
159− MinItems <$> o .: "minItems"
160−
161−minItems :: Validator a (Maybe MinItems) ()
162−minItems =
163− Validator
164− noEmbedded
165− (run (fmap maybeToList . AR.minItems . _unMinItems))
166−
167−newtype UniqueItems
168− = UniqueItems { _unUniqueItems :: Bool }
169− deriving (Eq, Show)
69+maxItemsValidator :: Validator a (Maybe MaxItems) MaxItemsInvalid
70+maxItemsValidator = Validator noEmbedded (run maxItemsVal)
71+
72+minItemsValidator :: Validator a (Maybe MinItems) MinItemsInvalid
73+minItemsValidator = Validator noEmbedded (run minItemsVal)
74+
75+uniqueItemsValidator :: Validator a (Maybe UniqueItems) UniqueItemsInvalid
76+uniqueItemsValidator = Validator noEmbedded (run uniqueItemsVal)
77+
78+itemsRelatedValidator
79+ :: (schema -> Value -> [err])
80+ -> Validator schema (ItemsRelated schema) (ItemsRelatedInvalid err)
81+itemsRelatedValidator f =
82+ Validator
83+ (\a -> ( mempty
84+ , case _irItems a of
85+ Just (ItemsObject b) -> pure b
86+ Just (ItemsArray cs) -> cs
87+ Nothing -> mempty
88+ <> case _irAdditional a of
89+ Just (AdditionalObject b) -> pure b
90+ _ -> mempty
91+ ))
92+ (\a b -> case fromJSONEither b of
93+ Left _ -> mempty
94+ Right c -> itemsRelatedVal f a c)
17095
171−instance FromJSON UniqueItems where
172− parseJSON = withObject "UniqueItems" $ \o ->
173− UniqueItems <$> o .: "uniqueItems"
96+--------------------------------------------------
97+-- * Objects
98+--------------------------------------------------
17499
175−uniqueItems :: Validator a (Maybe UniqueItems) ()
176−uniqueItems =
177− Validator
178− noEmbedded
179− (run (fmap maybeToList . AR.uniqueItems . _unUniqueItems))
100+maxPropertiesValidator
101+ :: Validator
102+ a
103+ (Maybe MaxProperties)
104+ MaxPropertiesInvalid
105+maxPropertiesValidator = Validator noEmbedded (run maxPropertiesVal)
180106
181−data ItemsContext schema =
182− ItemsContext
183− (Maybe (AR.AdditionalItems schema))
184− (AR.Items schema)
185− deriving (Eq, Show)
107+minPropertiesValidator
108+ :: Validator
109+ a
110+ (Maybe MinProperties)
111+ MinPropertiesInvalid
112+minPropertiesValidator = Validator noEmbedded (run minPropertiesVal)
186113
187−instance FromJSON schema => FromJSON (ItemsContext schema) where
188− parseJSON = withObject "ItemsContext" $ \o -> ItemsContext
189− <$> o .:! "additionalItems"
190− <*> o .: "items"
114+requiredValidator :: Validator a (Maybe Required) ()
115+requiredValidator = Validator noEmbedded (run requiredVal)
191116
192−items
193− :: (schema -> Value -> [Fail err])
194− -> Validator schema (Maybe (ItemsContext schema)) (AR.ItemsInvalid err)
195−items f =
117+dependenciesValidator
118+ :: (schema -> Value -> [err])
119+ -> Validator
120+ schema
121+ (Maybe (DependenciesValidator schema))
122+ (DependenciesInvalid err)
123+dependenciesValidator f =
196124 Validator
197− (\a -> case a of
198− Nothing -> mempty
199− Just (ItemsContext _ b) ->
200− case b of
201− AR.ItemsObject c -> (mempty, pure c)
202− AR.ItemsArray cs -> (mempty, cs))
203− (run (\(ItemsContext a b) -> AR.items f a b))
204−
205−newtype AdditionalItemsContext schema
206− = AdditionalItemsContext
207− { _unAdditionalItemsContext :: AR.AdditionalItems schema }
208− deriving (Eq, Show)
209−
210−instance FromJSON schema => FromJSON (AdditionalItemsContext schema) where
211− parseJSON = withObject "AdditionalItemsContext" $ \o ->
212− AdditionalItemsContext <$> o .: "additionalItems"
125+ (maybe mempty ( (\a -> (mempty, a))
126+ . catMaybes . fmap checkDependency
127+ . HM.elems . _unDependenciesValidator
128+ ))
129+ (run (dependenciesVal f))
130+ where
131+ checkDependency :: Dependency schema -> Maybe schema
132+ checkDependency (PropertyDependency _) = Nothing
133+ checkDependency (SchemaDependency s) = Just s
213134
214−-- | Since 'items' will always take care of validating 'additionalItems'
215−-- as well, the actual validation side of 'additionalItemsEmbedded' is
216−-- disabled.
217−additionalItemsEmbedded
218− :: Validator
135+propertiesRelatedValidator
136+ :: (schema -> Value -> [err])
137+ -> Validator
219138 schema
220− (Maybe (AdditionalItemsContext schema))
221− err
222−additionalItemsEmbedded=
223− Validator
224− (\a -> case a of
225− Just (AdditionalItemsContext (AR.AdditionalObject b)) ->
226− (mempty, pure b)
227− _ -> (mempty, mempty))
228− (const (const mempty))
139+ (PropertiesRelated schema)
140+ (PropertiesRelatedInvalid err)
141+propertiesRelatedValidator f =
142+ Validator
143+ (\a -> ( mempty
144+ , HM.elems (fromMaybe mempty (_propProperties a))
145+ <> HM.elems (fromMaybe mempty (_propPattern a))
146+ <> case _propAdditional a of
147+ Just (AdditionalPropertiesObject b) -> [b]
148+ _ -> mempty
149+ ))
150+ (\a b -> case fromJSONEither b of
151+ Left _ -> mempty
152+ Right c -> maybeToList (propertiesRelatedVal f a c))
229153
230154newtype Definitions schema
231155 = Definitions { _unDefinitions :: HashMap Text schema }
@@ -235,11 +159,9 @@ instance FromJSON schema => FromJSON (Definitions schema) where
235159 parseJSON = withObject "Definitions" $ \o ->
236160 Definitions <$> o .: "definitions"
237161
238−-- | Placing this here since it's similar to @"additionalItems"@.
239−-- in that its validator doesn't run.
240−--
241−-- TODO: Add tests to the language agnostic test suite for both
242−-- @"additionalItems"@ and this.
162+-- TODO: Add tests to the language agnostic test suite to
163+-- make sure these schemas are embedded correctly
164+-- (and do so for @"additionalItems"@ as well).
243165definitionsEmbedded
244166 :: Validator
245167 schema
@@ -252,299 +174,69 @@ definitionsEmbedded =
252174 Nothing -> (mempty, mempty))
253175 (const (const mempty))
254176
255−--------------------------------------------------
256−-- * Objects
257−--------------------------------------------------
258−
259−newtype MaxProperties
260− = MaxProperties { _unMaxProperties :: Int }
261− deriving (Eq, Show)
262−
263−instance FromJSON MaxProperties where
264− parseJSON = withObject "MaxProperties" $ \o ->
265− MaxProperties <$> o .: "maxProperties"
266−
267−maxProperties :: Validator a (Maybe MaxProperties) ()
268−maxProperties =
269− Validator
270− noEmbedded
271− (run (fmap maybeToList . OB.maxProperties . _unMaxProperties))
272−
273−newtype MinProperties
274− = MinProperties { _unMinProperties :: Int }
275− deriving (Eq, Show)
276−
277−instance FromJSON MinProperties where
278− parseJSON = withObject "MinProperties" $ \o ->
279− MinProperties <$> o .: "minProperties"
280−
281−minProperties :: Validator a (Maybe MinProperties) ()
282−minProperties =
283− Validator
284− noEmbedded
285− (run (fmap maybeToList . OB.minProperties . _unMinProperties))
286−
287−newtype RequiredContext
288− = RequiredContext { _unRequiredContext :: OB.Required }
289− deriving (Eq, Show)
290−
291−instance FromJSON RequiredContext where
292− parseJSON = withObject "RequiredContext" $ \o ->
293− RequiredContext <$> o .: "required"
294−
295−required :: Validator a (Maybe RequiredContext) ()
296−required =
297− Validator
298− noEmbedded
299− (run (fmap maybeToList . OB.required . _unRequiredContext))
300−
301−newtype DependenciesContext schema
302− = DependenciesContext
303− { _unDependenciesContext :: HashMap Text (OB.Dependency schema) }
304− deriving (Eq, Show)
305−
306−instance FromJSON schema => FromJSON (DependenciesContext schema) where
307− parseJSON = withObject "DependenciesContext" $ \o ->
308− DependenciesContext <$> o .: "dependencies"
309−
310−dependencies
311− :: (schema -> Value -> [Fail err])
312− -> Validator
313− schema
314− (Maybe (DependenciesContext schema))
315− (OB.DependencyInvalid err)
316−dependencies f =
317− Validator
318− (maybe mempty ( (\a -> (mempty, a))
319− . catMaybes . fmap checkDependency
320− . HM.elems . _unDependenciesContext
321− ))
322− (run (OB.dependencies f . _unDependenciesContext))
323− where
324− checkDependency :: OB.Dependency schema -> Maybe schema
325− checkDependency (OB.PropertyDependency _) = Nothing
326− checkDependency (OB.SchemaDependency s) = Just s
327−
328−data PropertiesContext schema
329− = PropertiesContext
330− (Maybe (HashMap Text schema))
331− (Maybe (OB.AdditionalProperties schema))
332− (HashMap Text schema)
333− deriving (Eq, Show)
334−
335−instance FromJSON schema => FromJSON (PropertiesContext schema) where
336− parseJSON = withObject "PropertiesContext" $ \o -> PropertiesContext
337− <$> o .:! "patternProperties"
338− <*> o .:! "additionalProperties"
339− <*> o .: "properties"
340−
341−properties
342− :: (schema -> Value -> [Fail err])
343− -> Validator
344− schema
345− (Maybe (PropertiesContext schema))
346− (OB.PropertiesInvalid err)
347−properties f =
348− Validator
349− (\a -> case a of
350− Just (PropertiesContext _ _ b) -> (mempty, HM.elems b)
351− Nothing -> (mempty, mempty))
352− (run (\(PropertiesContext a b c) -> OB.properties f a b c))
353−
354−-- | The first argument is whether the validator should be run.
355−-- If @"properties"@ exists it will be parsed to 'False'.
356−data PatternPropertiesContext schema
357− = PatternPropertiesContext
358− Bool
359− (Maybe (OB.AdditionalProperties schema))
360− (HashMap Text schema)
361− deriving (Eq, Show)
362−
363−instance FromJSON schema => FromJSON (PatternPropertiesContext schema) where
364− parseJSON = withObject "PatternPropertiesContext" $ \o ->
365− PatternPropertiesContext
366− <$> shouldRun o
367− <*> o .:! "additionalProperties"
368− <*> o .: "patternProperties"
369− where
370− shouldRun :: HashMap Text Value -> Parser Bool
371− shouldRun o = do
372− a <- o .:! "properties"
373− pure $ isNothing (a :: Maybe (HashMap Text schema))
374−
375−patternProperties
376− :: (schema -> Value -> [Fail err])
377− -> Validator
378− schema
379− (Maybe (PatternPropertiesContext schema))
380− (OB.PatternPropertiesInvalid err)
381−patternProperties f =
382− Validator
383− (\a -> case a of
384− Just (PatternPropertiesContext _ _ b) -> (mempty, HM.elems b)
385− Nothing -> (mempty, mempty))
386− (run (\(PatternPropertiesContext a b c) -> OB.patternProperties f a b c))
387−
388−-- | The first argument is whether the validator should be run.
389−-- If @"properties"@ or @"patternProperties"@ exist it will be parsed
390−-- to 'False'.
391−data AdditionalPropertiesContext schema
392− = AdditionalPropertiesContext
393− Bool
394− (OB.AdditionalProperties schema)
395− deriving (Eq, Show)
396−
397−instance FromJSON schema => FromJSON (AdditionalPropertiesContext schema) where
398− parseJSON = withObject "AdditionalPropertiesContext" $ \o ->
399− AdditionalPropertiesContext
400− <$> shouldRun o
401− <*> o .: "additionalProperties"
402− where
403− shouldRun :: HashMap Text Value -> Parser Bool
404− shouldRun o = do
405− a <- o .:! "properties"
406− b <- o .:! "patternProperties"
407− pure $ isNothing (a :: Maybe (HashMap Text schema))
408− && isNothing (b :: Maybe (HashMap Text schema))
409−
410−additionalProperties
411− :: (schema -> Value -> [Fail err])
412− -> Validator
413− schema
414− (Maybe (AdditionalPropertiesContext schema))
415− (OB.AdditionalPropertiesInvalid err)
416−additionalProperties f =
417− Validator
418− (\a -> case a of
419− Nothing -> mempty
420− Just (AdditionalPropertiesContext _ b) ->
421− case b of
422− OB.AdditionalPropertiesBool _ -> (mempty, mempty)
423− OB.AdditionalPropertiesObject c -> (mempty, pure c))
424− (run (\(AdditionalPropertiesContext a b) -> OB.additionalProperties f a b))
425−
426177--------------------------------------------------
427178-- * Any
428179--------------------------------------------------
429180
430−newtype Ref
431− = Ref { _unRef :: Text }
432− deriving (Eq, Show)
433−
434−instance FromJSON Ref where
435− parseJSON = withObject "Ref" $ \o ->
436− Ref <$> o .: "$ref"
437−
438−ref
181+refValidator
439182 :: (FromJSON schema, ToJSON schema)
440− => AN.VisitedSchemas
183+ => VisitedSchemas
441184 -> Maybe Text
442185 -> (Maybe Text -> Maybe schema)
443− -> (AN.VisitedSchemas -> Maybe Text -> schema -> Value -> [Fail err])
444− -> Validator a (Maybe Ref) (AN.RefInvalid err)
445−ref visited scope getRef f =
186+ -> (VisitedSchemas -> Maybe Text -> schema -> Value -> [err])
187+ -> Validator a (Maybe Ref) (RefInvalid err)
188+refValidator visited scope getRef f =
446189 Validator
447190 noEmbedded
448− (run (AN.ref visited scope getRef f . _unRef))
191+ (run (refVal visited scope getRef f))
449192
450−newtype EnumContext
451− = EnumContext { _unEnumContext :: AN.EnumVal }
452− deriving (Eq, Show)
193+enumValidator :: Validator a (Maybe EnumValidator) EnumInvalid
194+enumValidator = Validator noEmbedded (run enumVal)
453195
454−instance FromJSON EnumContext where
455− parseJSON = withObject "EnumContext" $ \o ->
456− EnumContext <$> o .: "enum"
196+typeValidator :: Validator a (Maybe TypeContext) TypeValidatorInvalid
197+typeValidator = Validator noEmbedded (run typeVal)
457198
458−enumVal :: Validator a (Maybe EnumContext) ()
459−enumVal =
460− Validator
461− noEmbedded
462− (run (fmap maybeToList . AN.enumVal . _unEnumContext))
463−
464−newtype TypeContext
465− = TypeContext { _unTypeContext :: AN.TypeVal }
466− deriving (Eq, Show)
467−
468−instance FromJSON TypeContext where
469− parseJSON = withObject "TypeContext" $ \o ->
470− TypeContext <$> o .: "type"
471−
472−typeVal :: Validator a (Maybe TypeContext) ()
473−typeVal =
474− Validator
475− noEmbedded
476− (run (fmap maybeToList . AN.typeVal . _unTypeContext))
477−
478−newtype AllOf schema
479− = AllOf { _unAllOf :: NonEmpty schema }
480− deriving (Eq, Show)
481−
482−instance FromJSON schema => FromJSON (AllOf schema) where
483− parseJSON = withObject "AllOf" $ \o ->
484− AllOf <$> o .: "allOf"
485−
486−allOf
487− :: (schema -> Value -> [Fail err])
488− -> Validator schema (Maybe (AllOf schema)) err
489−allOf f =
199+allOfValidator
200+ :: (schema -> Value -> [err])
201+ -> Validator schema (Maybe (AllOf schema)) (AllOfInvalid err)
202+allOfValidator f =
490203 Validator
491204 (\a -> case a of
492205 Just (AllOf b) -> (NE.toList b, mempty)
493206 Nothing -> (mempty, mempty))
494− (run (AN.allOf f . _unAllOf))
495−
496−newtype AnyOf schema
497− = AnyOf { _unAnyOf :: NonEmpty schema }
498− deriving (Eq, Show)
207+ (run (allOfVal f))
499208
500−instance FromJSON schema => FromJSON (AnyOf schema) where
501− parseJSON = withObject "AnyOf" $ \o ->
502− AnyOf <$> o .: "anyOf"
503−
504−anyOf
505− :: (schema -> Value -> [Fail err])
506− -> Validator schema (Maybe (AnyOf schema)) err
507−anyOf f =
209+anyOfValidator
210+ :: (schema -> Value -> [err])
211+ -> Validator schema (Maybe (AnyOf schema)) (AnyOfInvalid err)
212+anyOfValidator f =
508213 Validator
509214 (\a -> case a of
510215 Just (AnyOf b) -> (NE.toList b, mempty)
511216 Nothing -> (mempty, mempty))
512− (run (AN.anyOf f . _unAnyOf))
513−
514−newtype OneOf schema
515− = OneOf { _unOneOf :: NonEmpty schema }
516− deriving (Eq, Show)
217+ (run (anyOfVal f))
517218
518−instance FromJSON schema => FromJSON (OneOf schema) where
519− parseJSON = withObject "OneOf" $ \o ->
520− OneOf <$> o .: "oneOf"
521−
522−oneOf
219+oneOfValidator
523220 :: ToJSON schema
524− => (schema -> Value -> [Fail err])
525− -> Validator schema (Maybe (OneOf schema)) (AN.OneOfInvalid err)
526−oneOf f =
221+ => (schema -> Value -> [err])
222+ -> Validator schema (Maybe (OneOf schema)) (OneOfInvalid err)
223+oneOfValidator f =
527224 Validator
528225 (\a -> case a of
529226 Just (OneOf b) -> (NE.toList b, mempty)
530227 Nothing -> (mempty, mempty))
531− (run (AN.oneOf f . _unOneOf))
228+ (run (oneOfVal f))
532229
533−newtype NotVal schema
534− = NotVal { _unNotVal :: schema }
535− deriving (Eq, Show)
536−
537−instance FromJSON schema => FromJSON (NotVal schema) where
538− parseJSON = withObject "NotVal" $ \o ->
539− NotVal <$> o .: "not"
540−
541−notVal
230+notValidator
542231 :: ToJSON schema
543− => (schema -> Value -> [Fail err])
544− -> Validator schema (Maybe (NotVal schema)) ()
545−notVal f =
232+ => (schema -> Value -> [err])
233+ -> Validator
234+ schema
235+ (Maybe (NotValidator schema))
236+ NotValidatorInvalid
237+notValidator f =
546238 Validator
547239 (\a -> case a of
548− Just (NotVal b) -> (pure b, mempty)
549− Nothing -> (mempty, mempty))
550− (run (fmap maybeToList . AN.notVal f . _unNotVal))
240+ Just (NotValidator b) -> (pure b, mempty)
241+ Nothing -> (mempty, mempty))
242+ (run (notVal f))
src/Data/Validator/Draft4/Any.hs+221−107
@@ -1,22 +1,16 @@
1−{-# LANGUAGE GeneralizedNewtypeDeriving #-}
21
32module Data.Validator.Draft4.Any where
43
54import Import
6−-- Hiding is for GHCs before 7.10:
7−import Prelude hiding (any, concat, elem)
85
9−import Control.Monad
10−import Data.Aeson.Types (Parser)
11−import Data.List (partition)
12−import Data.List.NonEmpty (NonEmpty)
6+import Data.List.NonEmpty (NonEmpty((:|)))
137import qualified Data.List.NonEmpty as NE
14−import Data.Maybe
158import qualified Data.Scientific as SCI
9+import Data.Semigroup (Semigroup) -- for older GHCs
1610import Data.Set (Set)
1711import qualified Data.Set as S
12+import qualified JSONPointer as JP
1813
19−import Data.Validator.Failure (Fail(..))
2014import qualified Data.Validator.Utils as UT
2115import Data.Validator.Reference (URIBaseAndFragment,
2216 resolveFragment, resolveReference)
@@ -25,43 +19,61 @@ import Data.Validator.Reference (URIBaseAndFragment,
2519-- * $ref
2620--------------------------------------------------
2721
22+newtype Ref
23+ = Ref { _unRef :: Text }
24+ deriving (Eq, Show)
25+
26+instance FromJSON Ref where
27+ parseJSON = withObject "Ref" $ \o ->
28+ Ref <$> o .: "$ref"
29+
2830data RefInvalid err
29− = RefResolution
30− | RefLoop
31− | RefInvalid err
31+ = RefResolution Text
32+ -- ^ Indicates a reference that failed to resolve.
33+ --
34+ -- NOTE: The language agnostic test suite doesn't specify if this should
35+ -- cause a validation error or should allow data to pass. We choose to
36+ -- return a validation error.
37+ --
38+ -- Also note that ideally we would enforce in the type system that any
39+ -- failing references be dealt with before valididation. Then this could
40+ -- be removed entirely.
41+ | RefPointerResolution Text
42+ | RefLoop Text VisitedSchemas URIBaseAndFragment
43+ | RefInvalid Text Value (NonEmpty err)
44+ -- ^ 'Text' is the URI and 'Value' is the linked schema.
3245 deriving (Eq, Show)
3346
3447newtype VisitedSchemas
3548 = VisitedSchemas { _unVisited :: [URIBaseAndFragment] }
36− deriving (Eq, Show, Monoid)
49+ deriving (Eq, Show, Semigroup, Monoid)
3750
38−ref
51+refVal
3952 :: forall err schema. (FromJSON schema, ToJSON schema)
4053 => VisitedSchemas
4154 -> Maybe Text
4255 -> (Maybe Text -> Maybe schema)
43− -> (VisitedSchemas -> Maybe Text -> schema -> Value -> [Fail err])
44− -> Text
56+ -> (VisitedSchemas -> Maybe Text -> schema -> Value -> [err])
57+ -> Ref
4558 -> Value
46− -> [Fail (RefInvalid err)]
47−ref visited scope getRef f reference x
48− | (mUri, mFragment) `elem` _unVisited visited =
49− pure $ Failure RefLoop (toJSON reference) mempty x
59+ -> Maybe (RefInvalid err)
60+refVal visited scope getRef f (Ref reference) x
61+ | (mURI, mFragment) `elem` _unVisited visited =
62+ Just (RefLoop reference visited (mURI, mFragment))
5063 | otherwise =
51− case getRef mUri of
52− Nothing -> pure failureDNE
64+ case getRef mURI of
65+ Nothing -> Just (RefResolution reference)
5366 Just schema ->
5467 case resolveFragment mFragment schema of
55− Nothing -> pure failureDNE
68+ Nothing -> Just (RefPointerResolution reference)
5669 Just s ->
57− let newVisited = (VisitedSchemas [(mUri, mFragment)]
70+ let newVisited = (VisitedSchemas [(mURI, mFragment)]
5871 <> visited)
59− in fmap RefInvalid <$> f newVisited mUri s x
72+ errs = f newVisited mURI s x
73+ in RefInvalid reference (toJSON schema)
74+ <$> NE.nonEmpty errs
6075 where
61− (mUri, mFragment) = resolveReference scope reference
62−
63− failureDNE :: Fail (RefInvalid err)
64− failureDNE = Failure RefResolution (toJSON reference) mempty x
76+ (mURI, mFragment) = resolveReference scope reference
6577
6678--------------------------------------------------
6779-- * enum
@@ -76,77 +88,84 @@ ref visited scope getRef f reference x
7688--
7789-- NOTE: We don't enforce the uniqueness constraint in the haskell code,
7890-- but we do in the 'FromJSON' instance.
79−newtype EnumVal
80− = EnumVal { _unEnumVal :: NonEmpty Value }
91+newtype EnumValidator
92+ = EnumValidator { _unEnumValidator :: NonEmpty Value }
8193 -- Given a choice, we'd prefer to enforce uniqueness through the type
8294 -- system over having at least one element. To use a 'Set' though we'd
8395 -- have to use 'OrdValue' here (there's no 'Ord' instance for plain Values)
8496 -- and we'd rather not make users mess with 'OrdValue'.
8597 deriving (Eq, Show)
8698
87−instance FromJSON EnumVal where
88− parseJSON v = checkUnique . UT._unNonEmpty' =<< parseJSON v
89− where
90− checkUnique :: NonEmpty Value -> Parser EnumVal
91− checkUnique a
92− | UT.allUniqueValues' a = pure (EnumVal a)
93− | otherwise = fail "All elements of the Enum validator must be unique."
94−
95−instance ToJSON EnumVal where
96− toJSON = toJSON . UT.NonEmpty' . _unEnumVal
99+instance FromJSON EnumValidator where
100+ parseJSON = withObject "EnumValidator" $ \o ->
101+ EnumValidator <$> o .: "enum"
97102
98−instance Arbitrary EnumVal where
103+instance Arbitrary EnumValidator where
99104 arbitrary = do
100105 xs <- (fmap.fmap) UT._unArbitraryValue arbitrary
101106 case NE.nonEmpty (toUnique xs) of
102− Nothing -> EnumVal . pure . UT._unArbitraryValue <$> arbitrary
103− Just ne -> pure (EnumVal ne)
107+ Nothing -> EnumValidator . pure . UT._unArbitraryValue <$> arbitrary
108+ Just ne -> pure (EnumValidator ne)
104109 where
105110 toUnique :: [Value] -> [Value]
106111 toUnique = fmap UT._unOrdValue . S.toList . S.fromList . fmap UT.OrdValue
107112
108−enumVal :: EnumVal -> Value -> Maybe (Fail ())
109−enumVal (EnumVal vs) x
113+data EnumInvalid
114+ = EnumInvalid EnumValidator Value
115+ deriving (Eq, Show)
116+
117+enumVal :: EnumValidator -> Value -> Maybe EnumInvalid
118+enumVal a@(EnumValidator vs) x
110119 | not (UT.allUniqueValues' vs) = Nothing
111120 | x `elem` vs = Nothing
112− | otherwise = Just $ Failure () (toJSON (UT.NonEmpty' vs))
113− mempty x
121+ | otherwise = Just $ EnumInvalid a x
114122
115123--------------------------------------------------
116124-- * type
117125--------------------------------------------------
118126
119−data TypeVal
120− = TypeValString Text
121− | TypeValArray (Set Text)
127+-- | This is separate from 'TypeValidator' so that 'TypeValidator' can
128+-- be used to write 'Data.JsonSchema.Draft4.Schema.Schema' without
129+-- messing up the 'FromJSON' instance of that data type.
130+newtype TypeContext
131+ = TypeContext { _unTypeContext :: TypeValidator }
122132 deriving (Eq, Show)
123133
124−instance FromJSON TypeVal where
125− parseJSON v = fmap TypeValString (parseJSON v)
126− <|> fmap TypeValArray (parseJSON v)
134+instance FromJSON TypeContext where
135+ parseJSON = withObject "TypeContext" $ \o ->
136+ TypeContext <$> o .: "type"
137+
138+data TypeValidator
139+ = TypeValidatorString Text
140+ | TypeValidatorArray (Set Text)
141+ deriving (Eq, Show)
127142
128−instance ToJSON TypeVal where
129− toJSON (TypeValString t) = toJSON t
130− toJSON (TypeValArray ts) = toJSON ts
143+instance FromJSON TypeValidator where
144+ parseJSON v = fmap TypeValidatorString (parseJSON v)
145+ <|> fmap TypeValidatorArray (parseJSON v)
131146
132−instance Arbitrary TypeVal where
133− arbitrary = oneof [ TypeValString <$> UT.arbitraryText
134− , TypeValArray <$> UT.arbitrarySetOfText
147+instance ToJSON TypeValidator where
148+ toJSON (TypeValidatorString t) = toJSON t
149+ toJSON (TypeValidatorArray ts) = toJSON ts
150+
151+instance Arbitrary TypeValidator where
152+ arbitrary = oneof [ TypeValidatorString <$> UT.arbitraryText
153+ , TypeValidatorArray <$> UT.arbitrarySetOfText
135154 ]
136155
137−setFromTypeVal :: TypeVal -> Set Text
138−setFromTypeVal (TypeValString t) = S.singleton t
139−setFromTypeVal (TypeValArray ts) = ts
156+data TypeValidatorInvalid
157+ = TypeValidatorInvalid TypeValidator Value
158+ deriving (Eq, Show)
140159
141−typeVal :: TypeVal -> Value -> Maybe (Fail ())
142−typeVal tv x
143− | S.null matches = Just (Failure () (toJSON tv) mempty x)
160+typeVal :: TypeContext -> Value -> Maybe TypeValidatorInvalid
161+typeVal (TypeContext tv) x
162+ | S.null matches = Just (TypeValidatorInvalid tv x)
144163 | otherwise = Nothing
145164 where
146165 -- There can be more than one match because a 'Value' can be both a
147166 -- @"number"@ and an @"integer"@.
148167 matches :: Set Text
149− matches = S.intersection okTypes (setFromTypeVal tv)
168+ matches = S.intersection okTypes (setFromTypeValidator tv)
150169
151170 okTypes :: Set Text
152171 okTypes =
@@ -161,76 +180,171 @@ typeVal tv x
161180 then S.fromList ["number", "integer"]
162181 else S.singleton "number"
163182
183+-- | Internal.
184+setFromTypeValidator :: TypeValidator -> Set Text
185+setFromTypeValidator (TypeValidatorString t) = S.singleton t
186+setFromTypeValidator (TypeValidatorArray ts) = ts
187+
164188--------------------------------------------------
165189-- * allOf
166190--------------------------------------------------
167191
168−allOf
169− :: (schema -> Value -> [Fail err])
170− -> NonEmpty schema
192+newtype AllOf schema
193+ = AllOf { _unAllOf :: NonEmpty schema }
194+ deriving (Eq, Show)
195+
196+instance FromJSON schema => FromJSON (AllOf schema) where
197+ parseJSON = withObject "AllOf" $ \o ->
198+ AllOf <$> o .: "allOf"
199+
200+newtype AllOfInvalid err
201+ = AllOfInvalid (NonEmpty (JP.Index, NonEmpty err))
202+ deriving (Eq, Show)
203+
204+allOfVal
205+ :: forall err schema.
206+ (schema -> Value -> [err])
207+ -> AllOf schema
171208 -> Value
172− -> [Fail err]
173−allOf f subSchemas x = NE.toList subSchemas >>= flip f x
209+ -> Maybe (AllOfInvalid err)
210+allOfVal f (AllOf subSchemas) x = AllOfInvalid <$> NE.nonEmpty failures
211+ where
212+ perhapsFailures :: [(JP.Index, [err])]
213+ perhapsFailures = zip (JP.Index <$> [0..])
214+ (flip f x <$> NE.toList subSchemas)
215+
216+ failures :: [(JP.Index, NonEmpty err)]
217+ failures = mapMaybe (traverse NE.nonEmpty) perhapsFailures
174218
175219--------------------------------------------------
176220-- * anyOf
177221--------------------------------------------------
178222
179−anyOf
223+newtype AnyOf schema
224+ = AnyOf { _unAnyOf :: NonEmpty schema }
225+ deriving (Eq, Show)
226+
227+instance FromJSON schema => FromJSON (AnyOf schema) where
228+ parseJSON = withObject "AnyOf" $ \o ->
229+ AnyOf <$> o .: "anyOf"
230+
231+newtype AnyOfInvalid err
232+ = AnyOfInvalid (NonEmpty (JP.Index, NonEmpty err))
233+ deriving (Eq, Show)
234+
235+anyOfVal
180236 :: forall err schema.
181− (schema -> Value -> [Fail err])
182− -> NonEmpty schema
237+ (schema -> Value -> [err])
238+ -> AnyOf schema
183239 -> Value
184− -> [Fail err]
185−anyOf f subSchemas x
186− | any null results = mempty
187− | otherwise = concat results
240+ -> Maybe (AnyOfInvalid err)
241+anyOfVal f (AnyOf subSchemas) x
242+ -- Replace with @null@ once we drop GHC 7.8:
243+ | any (((==) 0 . length) . snd) perhapsFailures = Nothing
244+ | otherwise = AnyOfInvalid <$> NE.nonEmpty failures
188245 where
189− results :: [[Fail err]]
190− results = flip f x <$> NE.toList subSchemas
246+ perhapsFailures :: [(JP.Index, [err])]
247+ perhapsFailures = zip (JP.Index <$> [0..])
248+ (flip f x <$> NE.toList subSchemas)
249+
250+ failures :: [(JP.Index, NonEmpty err)]
251+ failures = mapMaybe (traverse NE.nonEmpty) perhapsFailures
191252
192253--------------------------------------------------
193254-- * oneOf
194255--------------------------------------------------
195256
257+newtype OneOf schema
258+ = OneOf { _unOneOf :: NonEmpty schema }
259+ deriving (Eq, Show)
260+
261+instance FromJSON schema => FromJSON (OneOf schema) where
262+ parseJSON = withObject "OneOf" $ \o ->
263+ OneOf <$> o .: "oneOf"
264+
196265data OneOfInvalid err
197− = TooManySuccesses
198− | NoSuccesses err
266+ = TooManySuccesses (NonEmpty (JP.Index, Value)) Value
267+ -- ^ The NonEmpty lists contains tuples whose contents
268+ -- are the index of a schema that validated the data
269+ -- and the contents of that schema.
270+ | NoSuccesses (NonEmpty (JP.Index, NonEmpty err)) Value
271+ -- ^ The NonEmpty lists contains tuples whose contents
272+ -- are the index of a schema that failed to validate the data
273+ -- and the failures it produced.
199274 deriving (Eq, Show)
200275
201−oneOf
276+oneOfVal
202277 :: forall err schema. ToJSON schema
203− => (schema -> Value -> [Fail err])
204− -> NonEmpty schema
278+ => (schema -> Value -> [err])
279+ -> OneOf schema
205280 -> Value
206− -> [Fail (OneOfInvalid err)]
207−oneOf f subSchemas x
208− | length successes == 1 = mempty
209− | length successes > 1 = pure tooManySuccesses
210− | otherwise = fmap NoSuccesses <$> concat failures
281+ -> Maybe (OneOfInvalid err)
282+oneOfVal f (OneOf (firstSubSchema :| otherSubSchemas)) x =
283+ -- Producing the NonEmpty lists needed by the error constructors
284+ -- is a little tricky. If we had a partition function like this
285+ -- it might help:
286+ -- @
287+ -- (a -> Either b c) -> NonEmpty a -> Either (NonEmpty b, [c])
288+ -- ([b], NonEmpty c)
289+ -- @
290+ case (firstSuccess, otherSuccesses) of
291+ (Right _, Nothing) -> Nothing
292+ (Right a, Just successes) -> Just (TooManySuccesses
293+ (a NE.<| successes) x)
294+ (Left e, Nothing) -> Just (NoSuccesses (e :| otherFailures) x)
295+ (Left _, Just (_ :| [])) -> Nothing
296+ (Left _, Just successes) -> Just (TooManySuccesses successes x)
211297 where
212− (successes, failures) = partition null results
213−
214− results :: [[Fail err]]
215− results = flip f x <$> NE.toList subSchemas
216−
217− -- NOTE: This could also return information about the specific
218− -- validators that succeeded.
219− tooManySuccesses :: Fail (OneOfInvalid err)
220− tooManySuccesses = Failure TooManySuccesses
221− (toJSON (UT.NonEmpty' subSchemas)) mempty x
298+ firstSuccess :: Either (JP.Index, NonEmpty err) (JP.Index, Value)
299+ firstSuccess =
300+ case NE.nonEmpty (f firstSubSchema x) of
301+ Nothing -> Right (JP.Index 0, toJSON firstSubSchema)
302+ Just errs -> Left (JP.Index 0, errs)
303+
304+ otherPerhapsFailures :: [(JP.Index, Value, [err])]
305+ otherPerhapsFailures =
306+ zipWith
307+ (\index schema -> (index, toJSON schema, f schema x))
308+ (JP.Index <$> [0..])
309+ otherSubSchemas
310+
311+ otherSuccesses :: Maybe (NonEmpty (JP.Index, Value))
312+ otherSuccesses = NE.nonEmpty
313+ $ mapMaybe (\(index,val,errs) ->
314+ case errs of
315+ [] -> Just (index,val)
316+ _ -> Nothing
317+ ) otherPerhapsFailures
318+
319+ otherFailures :: [(JP.Index, NonEmpty err)]
320+ otherFailures = mapMaybe (traverse NE.nonEmpty . mid) otherPerhapsFailures
321+
322+ mid :: (a,b,c) -> (a,c)
323+ mid (a,_,c) = (a,c)
222324
223325--------------------------------------------------
224326-- * not
225327--------------------------------------------------
226328
329+newtype NotValidator schema
330+ = NotValidator { _unNotValidator :: schema }
331+ deriving (Eq, Show)
332+
333+instance FromJSON schema => FromJSON (NotValidator schema) where
334+ parseJSON = withObject "NotValidator" $ \o ->
335+ NotValidator <$> o .: "not"
336+
337+data NotValidatorInvalid
338+ = NotValidatorInvalid Value Value
339+ deriving (Eq, Show)
340+
227341notVal
228− :: ToJSON schema
229− => (schema -> Value -> [Fail err])
230− -> schema
342+ :: ToJSON schema =>
343+ (schema -> Value -> [err])
344+ -> NotValidator schema
231345 -> Value
232− -> Maybe (Fail ())
233−notVal f schema x =
346+ -> Maybe NotValidatorInvalid
347+notVal f (NotValidator schema) x =
234348 case f schema x of
235− [] -> Just (Failure () (toJSON schema) mempty x)
349+ [] -> Just (NotValidatorInvalid (toJSON schema) x)
236350 _ -> Nothing
src/Data/Validator/Draft4/Array.hs+138−98
@@ -2,53 +2,101 @@
22module Data.Validator.Draft4.Array where
33
44import Import
5−import Prelude
65
7−import Control.Monad
8−import qualified Data.Aeson.Pointer as AP
9−import qualified Data.Text as T
10−import qualified Data.Vector as V
11−import Text.Read (readMaybe)
6+import qualified Data.List.NonEmpty as NE
7+import qualified Data.Vector as V
8+import qualified JSONPointer as JP
129
13−import Data.Validator.Failure (Fail(..), prependToPath)
14−import Data.Validator.Utils (allUniqueValues)
10+import Data.Validator.Utils (allUniqueValues)
1511
1612--------------------------------------------------
1713-- * maxItems
1814--------------------------------------------------
1915
16+newtype MaxItems
17+ = MaxItems { _unMaxItems :: Int }
18+ deriving (Eq, Show)
19+
20+instance FromJSON MaxItems where
21+ parseJSON = withObject "MaxItems" $ \o ->
22+ MaxItems <$> o .: "maxItems"
23+
24+data MaxItemsInvalid
25+ = MaxItemsInvalid MaxItems (Vector Value)
26+ deriving (Eq, Show)
27+
2028-- | The spec requires @"maxItems"@ to be non-negative.
21−maxItems :: Int -> Vector Value -> Maybe (Fail ())
22−maxItems n xs
29+maxItemsVal :: MaxItems -> Vector Value -> Maybe MaxItemsInvalid
30+maxItemsVal a@(MaxItems n) xs
2331 | n < 0 = Nothing
24− | V.length xs > n = Just (Failure () (toJSON n) mempty (Array xs))
32+ | V.length xs > n = Just (MaxItemsInvalid a xs)
2533 | otherwise = Nothing
2634
2735--------------------------------------------------
2836-- * minItems
2937--------------------------------------------------
3038
39+newtype MinItems
40+ = MinItems { _unMinItems :: Int }
41+ deriving (Eq, Show)
42+
43+instance FromJSON MinItems where
44+ parseJSON = withObject "MinItems" $ \o ->
45+ MinItems <$> o .: "minItems"
46+
47+data MinItemsInvalid
48+ = MinItemsInvalid MinItems (Vector Value)
49+ deriving (Eq, Show)
50+
3151-- | The spec requires @"minItems"@ to be non-negative.
32−minItems :: Int -> Vector Value -> Maybe (Fail ())
33−minItems n xs
52+minItemsVal :: MinItems -> Vector Value -> Maybe MinItemsInvalid
53+minItemsVal a@(MinItems n) xs
3454 | n < 0 = Nothing
35− | V.length xs < n = Just (Failure () (toJSON n) mempty (Array xs))
55+ | V.length xs < n = Just (MinItemsInvalid a xs)
3656 | otherwise = Nothing
3757
3858--------------------------------------------------
3959-- * uniqueItems
4060--------------------------------------------------
4161
42−uniqueItems :: Bool -> Vector Value -> Maybe (Fail ())
43−uniqueItems True xs
62+newtype UniqueItems
63+ = UniqueItems { _unUniqueItems :: Bool }
64+ deriving (Eq, Show)
65+
66+instance FromJSON UniqueItems where
67+ parseJSON = withObject "UniqueItems" $ \o ->
68+ UniqueItems <$> o .: "uniqueItems"
69+
70+data UniqueItemsInvalid
71+ = UniqueItemsInvalid (Vector Value)
72+ deriving (Eq, Show)
73+
74+uniqueItemsVal :: UniqueItems -> Vector Value -> Maybe UniqueItemsInvalid
75+uniqueItemsVal (UniqueItems True) xs
4476 | allUniqueValues xs = Nothing
45− | otherwise = Just (Failure () (Bool True) mempty (Array xs))
46−uniqueItems False _ = Nothing
77+ | otherwise = Just (UniqueItemsInvalid xs)
78+uniqueItemsVal (UniqueItems False) _ = Nothing
4779
4880--------------------------------------------------
4981-- * items
5082--------------------------------------------------
5183
84+data ItemsRelated schema = ItemsRelated
85+ { _irItems :: Maybe (Items schema)
86+ , _irAdditional :: Maybe (AdditionalItems schema)
87+ } deriving (Eq, Show)
88+
89+instance FromJSON schema => FromJSON (ItemsRelated schema) where
90+ parseJSON = withObject "ItemsRelated" $ \o -> ItemsRelated
91+ <$> o .:! "items"
92+ <*> o .:! "additionalItems"
93+
94+emptyItems :: ItemsRelated schema
95+emptyItems = ItemsRelated
96+ { _irItems = Nothing
97+ , _irAdditional = Nothing
98+ }
99+
52100data Items schema
53101 = ItemsObject schema
54102 | ItemsArray [schema]
@@ -67,82 +115,73 @@ instance Arbitrary schema => Arbitrary (Items schema) where
67115 , ItemsArray <$> arbitrary
68116 ]
69117
118+data ItemsRelatedInvalid err
119+ = IRInvalidItems (ItemsInvalid err)
120+ | IRInvalidAdditional (AdditionalItemsInvalid err)
121+ deriving (Eq, Show)
122+
70123data ItemsInvalid err
71− = Items err
72− | AdditionalItemsBoolInvalid
73− | AdditionalItemsObjectInvalid err
124+ = ItemsObjectInvalid (NonEmpty (JP.Index, NonEmpty err))
125+ | ItemsArrayInvalid (NonEmpty (JP.Index, NonEmpty err))
74126 deriving (Eq, Show)
75127
76−items
128+-- | @"additionalItems"@ only matters if @"items"@ exists
129+-- and is a JSON Array.
130+itemsRelatedVal
131+ :: forall err schema.
132+ (schema -> Value -> [err])
133+ -> ItemsRelated schema
134+ -> Vector Value
135+ -> [ItemsRelatedInvalid err] -- NOTE: 'Data.These' would help here.
136+itemsRelatedVal f a xs =
137+ let (itemsFailure, remaining) = case _irItems a of
138+ Nothing -> (Nothing, mempty)
139+ Just b -> itemsVal f b xs
140+ additionalFailure = (\b -> additionalItemsVal f b remaining)
141+ =<< _irAdditional a
142+ in catMaybes [ IRInvalidItems <$> itemsFailure
143+ , IRInvalidAdditional <$> additionalFailure
144+ ]
145+
146+-- | Internal.
147+--
148+-- This is because 'itemsRelated' handles @"items"@ validation.
149+itemsVal
77150 :: forall err schema.
78− (schema -> Value -> [Fail err])
79− -> Maybe (AdditionalItems schema)
151+ (schema -> Value -> [err])
80152 -> Items schema
81153 -> Vector Value
82− -> [Fail (ItemsInvalid err)]
83−items f _ (ItemsObject subSchema) xs =
84− zip [0..] (V.toList xs) >>= g
154+ -> (Maybe (ItemsInvalid err), [(JP.Index, Value)])
155+ -- ^ The second item in the tuple is the elements of the original
156+ -- JSON Array still remaining to be checked by @"additionalItems"@.
157+itemsVal f a xs =
158+ case a of
159+ ItemsObject subSchema ->
160+ case NE.nonEmpty (mapMaybe (validateElem subSchema) indexed) of
161+ Nothing -> (Nothing, mempty)
162+ Just errs -> (Just (ItemsObjectInvalid errs), mempty)
163+ ItemsArray subSchemas ->
164+ let remaining = drop (length subSchemas) indexed
165+ res = catMaybes (zipWith validateElem subSchemas indexed)
166+ in case NE.nonEmpty res of
167+ Nothing -> (Nothing, remaining)
168+ Just errs -> (Just (ItemsArrayInvalid errs), remaining)
85169 where
86− g :: (Int, Value) -> [Fail (ItemsInvalid err)]
87− g (index,x) = fmap Items
88− . prependToPath (AP.Token (T.pack (show index)))
89− <$> f subSchema x
170+ indexed :: [(JP.Index, Value)]
171+ indexed = zip (JP.Index <$> [0..]) (V.toList xs)
90172
91−items f mAdditional (ItemsArray subSchemas) xs =
92− itemFailures <> additionalItemFailures
93− where
94− indexedValues :: [(Int, Value)]
95− indexedValues = zip [0..] (V.toList xs)
96−
97− itemFailures :: [Fail (ItemsInvalid err)]
98− itemFailures = join (zipWith g subSchemas indexedValues)
99− where
100− g :: schema -> (Int, Value) -> [Fail (ItemsInvalid err)]
101− g schema (index,x) = fmap Items
102− . prependToPath (AP.Token (T.pack (show index)))
103− <$> f schema x
104−
105− additionalItemFailures :: [Fail (ItemsInvalid err)]
106− additionalItemFailures =
107− case mAdditional of
108− Nothing -> mempty
109− Just adi -> fmap correctName
110− . correctIndexes
111− <$> additionalItems f adi extras
112− where
113− -- It's not great that we convert back to Vector again.
114− extras :: Vector Value
115− extras = V.fromList . fmap snd
116− . drop (length subSchemas) $ indexedValues
117−
118− -- Since 'additionalItems' only sees part of the array, but starts
119− -- indexing from zero, we need to modify the paths it reports to
120− -- represent invalid data so they actually represent the correct
121− -- offsets.
122− correctIndexes
123− :: Fail (AdditionalItemsInvalid err)
124− -> Fail (AdditionalItemsInvalid err)
125− correctIndexes (Failure a b c d) = Failure a b (fixIndex c) d
126− where
127− fixIndex :: AP.Pointer -> AP.Pointer
128− fixIndex (AP.Pointer (tok:toks)) =
129− case readMaybe . T.unpack . AP._unToken $ tok of
130− Nothing -> AP.Pointer $ tok:toks
131− Just n -> AP.Pointer $
132− (AP.Token . T.pack . show $ n + length subSchemas):toks
133− fixIndex (AP.Pointer []) = AP.Pointer []
134−
135− correctName :: AdditionalItemsInvalid err -> ItemsInvalid err
136− correctName AdditionalBoolInvalid = AdditionalItemsBoolInvalid
137− correctName (AdditionalObjectInvalid err) =
138− AdditionalItemsObjectInvalid err
173+ validateElem
174+ :: schema
175+ -> (JP.Index, Value)
176+ -> Maybe (JP.Index, NonEmpty err)
177+ validateElem schema (index,x) = (index,) <$> NE.nonEmpty (f schema x)
139178
140179--------------------------------------------------
141180-- * additionalItems
142181--------------------------------------------------
143182
144183data AdditionalItems schema
145− = AdditionalBool Bool
184+ = AdditionalBool Bool
146185 | AdditionalObject schema
147186 deriving (Eq, Show)
148187
@@ -160,25 +199,26 @@ instance Arbitrary schema => Arbitrary (AdditionalItems schema) where
160199 ]
161200
162201data AdditionalItemsInvalid err
163− = AdditionalBoolInvalid
164− | AdditionalObjectInvalid err
202+ = AdditionalItemsBoolInvalid (NonEmpty (JP.Index, Value))
203+ | AdditionalItemsObjectInvalid (NonEmpty (JP.Index, NonEmpty err))
165204 deriving (Eq, Show)
166205
167−additionalItems
206+-- | Internal.
207+--
208+-- This is because 'itemsRelated' handles @"additionalItems"@ validation.
209+additionalItemsVal
168210 :: forall err schema.
169− (schema -> Value -> [Fail err])
211+ (schema -> Value -> [err])
170212 -> AdditionalItems schema
171− -> Vector Value
172− -> [Fail (AdditionalItemsInvalid err)]
173−additionalItems _ (AdditionalBool b) xs
174− | b = mempty
175− | V.length xs > 0 = pure (Failure AdditionalBoolInvalid (Bool b)
176− mempty (toJSON xs))
177− | otherwise = mempty
178−additionalItems f (AdditionalObject subSchema) xs =
179− zip [0..] (V.toList xs) >>= g
180− where
181− g :: (Int, Value) -> [Fail (AdditionalItemsInvalid err)]
182− g (index,x) = fmap AdditionalObjectInvalid
183− . prependToPath (AP.Token (T.pack (show index)))
184− <$> f subSchema x
213+ -> [(JP.Index, Value)]
214+ -- ^ The elements remaining to validate after the ones covered by
215+ -- @"items"@ have been removed.
216+ -> Maybe (AdditionalItemsInvalid err)
217+additionalItemsVal _ (AdditionalBool True) _ = Nothing
218+additionalItemsVal _ (AdditionalBool False) xs =
219+ AdditionalItemsBoolInvalid <$> NE.nonEmpty xs
220+additionalItemsVal f (AdditionalObject subSchema) xs =
221+ let res = mapMaybe
222+ (\(index,x) -> (index,) <$> NE.nonEmpty (f subSchema x))
223+ xs
224+ in AdditionalItemsObjectInvalid <$> NE.nonEmpty res
src/Data/Validator/Draft4/Number.hs+51−32
@@ -2,64 +2,83 @@
22module Data.Validator.Draft4.Number where
33
44import Import
5−import Prelude
65
7−import Data.Fixed (mod')
8−import Data.Scientific (Scientific)
9−
10−import Data.Validator.Failure (Fail(..))
6+import Data.Fixed (mod')
7+import Data.Scientific (Scientific)
118
129--------------------------------------------------
1310-- * multipleOf
1411--------------------------------------------------
1512
13+newtype MultipleOf
14+ = MultipleOf { _unMultipleOf :: Scientific }
15+ deriving (Eq, Show)
16+
17+instance FromJSON MultipleOf where
18+ parseJSON = withObject "MultipleOf" $ \o ->
19+ MultipleOf <$> o .: "multipleOf"
20+
21+data MultipleOfInvalid
22+ = MultipleOfInvalid MultipleOf Scientific
23+ deriving (Eq, Show)
24+
1625-- | The spec requires @"multipleOf"@ to be positive.
17−multipleOf :: Scientific -> Scientific -> Maybe (Fail ())
18−multipleOf n x
26+multipleOfVal :: MultipleOf -> Scientific -> Maybe MultipleOfInvalid
27+multipleOfVal a@(MultipleOf n) x
1928 | n <= 0 = Nothing
20− | x `mod'` n /= 0 = Just (Failure () (toJSON n) mempty (Number x))
29+ | x `mod'` n /= 0 = Just (MultipleOfInvalid a x)
2130 | otherwise = Nothing
2231
2332--------------------------------------------------
2433-- * maximum
2534--------------------------------------------------
2635
36+data Maximum = Maximum
37+ { _maximumExclusive :: Bool
38+ , _maximumValue :: Scientific
39+ } deriving (Eq, Show)
40+
41+instance FromJSON Maximum where
42+ parseJSON = withObject "Maximum" $ \o -> Maximum
43+ <$> o .:! "exclusiveMaximum" .!= False
44+ <*> o .: "maximum"
45+
2746data MaximumInvalid
28− = Maximum
29− | ExclusiveMaximum
47+ = MaximumInvalid Maximum Scientific
3048 deriving (Eq, Show)
3149
3250maximumVal
33− :: Bool
34− -> Scientific
51+ :: Maximum
3552 -> Scientific
36− -> Maybe (Fail MaximumInvalid)
37−maximumVal exclusive n x
38− | x `greaterThan` n = Just (Failure err (toJSON n) mempty (Number x))
39− | otherwise = Nothing
40− where
41− (greaterThan, err) = if exclusive
42− then ((>=), ExclusiveMaximum)
43− else ((>), Maximum)
53+ -> Maybe MaximumInvalid
54+maximumVal a@(Maximum exclusive n) x
55+ | exclusive && x >= n = Just (MaximumInvalid a x)
56+ | x > n = Just (MaximumInvalid a x)
57+ | otherwise = Nothing
4458
4559--------------------------------------------------
4660-- * minimum
4761--------------------------------------------------
4862
63+data Minimum = Minimum
64+ { _minimumExclusive :: Bool
65+ , _minimumValue :: Scientific
66+ } deriving (Eq, Show)
67+
68+instance FromJSON Minimum where
69+ parseJSON = withObject "Minimum" $ \o -> Minimum
70+ <$> o .:! "exclusiveMinimum" .!= False
71+ <*> o .: "minimum"
72+
4973data MinimumInvalid
50− = Minimum
51− | ExclusiveMinimum
74+ = MinimumInvalid Minimum Scientific
5275 deriving (Eq, Show)
5376
5477minimumVal
55− :: Bool
56− -> Scientific
78+ :: Minimum
5779 -> Scientific
58− -> Maybe (Fail MinimumInvalid)
59−minimumVal exclusive n x
60− | x `lessThan` n = Just (Failure err (toJSON n) mempty (Number x))
61− | otherwise = Nothing
62− where
63− (lessThan, err) = if exclusive
64− then ((<=), ExclusiveMinimum)
65− else ((<), Minimum)
80+ -> Maybe MinimumInvalid
81+minimumVal a@(Minimum exclusive n) x
82+ | exclusive && x <= n = Just (MinimumInvalid a x)
83+ | x < n = Just (MinimumInvalid a x)
84+ | otherwise = Nothing
src/Data/Validator/Draft4/Object.hs+88−66
@@ -1,4 +1,3 @@
1−{-# LANGUAGE GeneralizedNewtypeDeriving #-}
21
32module Data.Validator.Draft4.Object
43 ( module Data.Validator.Draft4.Object
@@ -6,41 +5,67 @@ module Data.Validator.Draft4.Object
65 ) where
76
87import Import
9−-- Hiding is for GHCs before 7.10:
10−import Prelude hiding (all, concat,
11− foldl)
128
13−import Data.Aeson.Types (Parser)
14−import qualified Data.HashMap.Strict as H
9+import qualified Data.HashMap.Strict as HM
10+import qualified Data.List.NonEmpty as NE
1511import Data.Set (Set)
1612import qualified Data.Set as S
1713import qualified Data.Text as T
1814
1915import Data.Validator.Draft4.Object.Properties
20−import Data.Validator.Failure (Fail(..))
2116import Data.Validator.Utils
2217
2318--------------------------------------------------
2419-- * maxProperties
2520--------------------------------------------------
2621
22+newtype MaxProperties
23+ = MaxProperties { _unMaxProperties :: Int }
24+ deriving (Eq, Show)
25+
26+instance FromJSON MaxProperties where
27+ parseJSON = withObject "MaxProperties" $ \o ->
28+ MaxProperties <$> o .: "maxProperties"
29+
30+data MaxPropertiesInvalid
31+ = MaxPropertiesInvalid MaxProperties (HashMap Text Value)
32+ deriving (Eq, Show)
33+
2734-- | The spec requires @"maxProperties"@ to be non-negative.
28−maxProperties :: Int -> HashMap Text Value -> Maybe (Fail ())
29−maxProperties n x
30− | n < 0 = Nothing
31− | H.size x > n = Just (Failure () (toJSON n) mempty (Object x))
32− | otherwise = Nothing
35+maxPropertiesVal
36+ :: MaxProperties
37+ -> HashMap Text Value
38+ -> Maybe MaxPropertiesInvalid
39+maxPropertiesVal a@(MaxProperties n) x
40+ | n < 0 = Nothing
41+ | HM.size x > n = Just (MaxPropertiesInvalid a x)
42+ | otherwise = Nothing
3343
3444--------------------------------------------------
3545-- * minProperties
3646--------------------------------------------------
3747
48+newtype MinProperties
49+ = MinProperties { _unMinProperties :: Int }
50+ deriving (Eq, Show)
51+
52+instance FromJSON MinProperties where
53+ parseJSON = withObject "MinProperties" $ \o ->
54+ MinProperties <$> o .: "minProperties"
55+
56+data MinPropertiesInvalid
57+ = MinPropertiesInvalid MinProperties (HashMap Text Value)
58+ deriving (Eq, Show)
59+
3860-- | The spec requires @"minProperties"@ to be non-negative.
39−minProperties :: Int -> HashMap Text Value -> Maybe (Fail ())
40−minProperties n x
41− | n < 0 = Nothing
42− | H.size x < n = Just (Failure () (toJSON n) mempty (Object x))
43− | otherwise = Nothing
61+minPropertiesVal
62+ :: MinProperties
63+ -> HashMap Text Value
64+ -> Maybe MinPropertiesInvalid
65+minPropertiesVal a@(MinProperties n) x
66+ | n < 0 = Nothing
67+ | HM.size x < n = Just (MinPropertiesInvalid a x)
68+ | otherwise = Nothing
4469
4570--------------------------------------------------
4671-- * required
@@ -51,28 +76,13 @@ minProperties n x
5176-- > The value of this keyword MUST be an array.
5277-- > This array MUST have at least one element.
5378-- > Elements of this array MUST be strings, and MUST be unique.
54−--
55−-- We don't enfore that 'Required' has at least one element in the
56−-- haskell code, but we do in the 'FromJSON' instance.
5779newtype Required
5880 = Required { _unRequired :: Set Text }
59− deriving (Eq, Show, ToJSON)
81+ deriving (Eq, Show)
6082
6183instance FromJSON Required where
62− parseJSON v = checkUnique =<< checkSize =<< parseJSON v
63− where
64− checkSize :: [Text] -> Parser [Text]
65− checkSize a
66− | null a = fail "Required validator must not be empty."
67− | otherwise = pure a
68−
69− checkUnique :: [Text] -> Parser Required
70− checkUnique a =
71− let b = S.fromList a
72− -- NOTE: Can use length instead of S.size in GHC 7.10 or later.
73− in if length a == S.size b
74− then pure (Required b)
75− else fail "All elements of the Required validator must be unique."
84+ parseJSON = withObject "Required" $ \o ->
85+ Required <$> o .: "required"
7686
7787instance Arbitrary Required where
7888 arbitrary = do
@@ -80,23 +90,31 @@ instance Arbitrary Required where
8090 xs <- (fmap.fmap) T.pack arbitrary
8191 pure . Required . S.fromList $ x:xs
8292
83−required :: Required -> HashMap Text Value -> Maybe (Fail ())
84−required (Required ts) x
93+requiredVal :: Required -> HashMap Text Value -> Maybe ()
94+requiredVal (Required ts) x
8595 -- NOTE: When we no longer need to support GHCs before 7.10
8696 -- we can use null from Prelude throughout the library
8797 -- instead of specialized versions.
88− | S.null ts = Nothing
89− | H.null (H.difference hm x) = Nothing
90− | otherwise = Just (Failure () (toJSON ts)
91− mempty (Object x))
98+ | S.null ts = Nothing
99+ | HM.null (HM.difference hm x) = Nothing
100+ | otherwise = Just () -- TODO
92101 where
93102 hm :: HashMap Text Bool
94− hm = foldl (\b a -> H.insert a True b) mempty ts
103+ hm = foldl (\b a -> HM.insert a True b) mempty ts
95104
96105--------------------------------------------------
97106-- * dependencies
98107--------------------------------------------------
99108
109+newtype DependenciesValidator schema
110+ = DependenciesValidator
111+ { _unDependenciesValidator :: HashMap Text (Dependency schema) }
112+ deriving (Eq, Show)
113+
114+instance FromJSON schema => FromJSON (DependenciesValidator schema) where
115+ parseJSON = withObject "DependenciesValidator" $ \o ->
116+ DependenciesValidator <$> o .: "dependencies"
117+
100118data Dependency schema
101119 = SchemaDependency schema
102120 | PropertyDependency (Set Text)
@@ -115,9 +133,13 @@ instance Arbitrary schema => Arbitrary (Dependency schema) where
115133 , PropertyDependency <$> arbitrarySetOfText
116134 ]
117135
118−data DependencyInvalid err
119− = SchemaDependencyInvalid err
120− | PropertyDependencyInvalid
136+data DependencyMemberInvalid err
137+ = SchemaDepInvalid (NonEmpty err)
138+ | PropertyDepInvalid (Set Text) (HashMap Text Value)
139+ deriving (Eq, Show)
140+
141+data DependenciesInvalid err
142+ = DependenciesInvalid (HashMap Text (DependencyMemberInvalid err))
121143 deriving (Eq, Show)
122144
123145-- | From the spec:
@@ -132,26 +154,26 @@ data DependencyInvalid err
132154-- > If the value is an array, it MUST have at least one element.
133155-- > Each element MUST be a string, and elements in the array MUST be unique.
134156-- > This is called a property dependency.
135−dependencies
157+dependenciesVal
136158 :: forall err schema.
137− (schema -> Value -> [Fail err])
138− -> HashMap Text (Dependency schema)
159+ (schema -> Value -> [err])
160+ -> DependenciesValidator schema
139161 -> HashMap Text Value
140− -> [Fail (DependencyInvalid err)]
141−dependencies f hm x = concat . fmap (uncurry g) . H.toList $ hm
142− where
143− g :: Text -> Dependency schema -> [Fail (DependencyInvalid err)]
144− g k (SchemaDependency schema)
145− | H.member k x =
146− fmap SchemaDependencyInvalid <$> f schema (Object x)
147− | otherwise = mempty
148− g k (PropertyDependency ts)
149− | H.member k x && not allPresent =
150− pure $ Failure PropertyDependencyInvalid
151− (toJSON (H.singleton k ts))
152− mempty
153− (Object x)
154− | otherwise = mempty
155− where
156− allPresent :: Bool
157− allPresent = all (`H.member` x) ts
162+ -> Maybe (DependenciesInvalid err)
163+dependenciesVal f (DependenciesValidator hm) x =
164+ let res = HM.mapMaybeWithKey g hm
165+ in if HM.null res
166+ then Nothing
167+ else Just (DependenciesInvalid res)
168+ where
169+ g :: Text -> Dependency schema -> Maybe (DependencyMemberInvalid err)
170+ g k (SchemaDependency schema)
171+ | HM.member k x = SchemaDepInvalid
172+ <$> NE.nonEmpty (f schema (Object x))
173+ | otherwise = Nothing
174+ g k (PropertyDependency ts)
175+ | HM.member k x && not allPresent = Just (PropertyDepInvalid ts x)
176+ | otherwise = Nothing
177+ where
178+ allPresent :: Bool
179+ allPresent = all (`HM.member` x) ts
src/Data/Validator/Draft4/Object/Properties.hs+152−157
@@ -2,213 +2,208 @@
22module Data.Validator.Draft4.Object.Properties where
33
44import Import
5−import Prelude
65
7−import Control.Monad
8−import qualified Data.Aeson.Pointer as AP
9−import Data.Functor (($>))
10−import qualified Data.HashMap.Strict as HM
11−import Data.Text.Encoding (encodeUtf8)
12−import qualified Text.Regex.PCRE.Heavy as RE
6+import qualified Data.Hashable as HA
7+import qualified Data.HashMap.Strict as HM
8+import qualified Data.List.NonEmpty as NE
9+import Data.Text.Encoding (encodeUtf8)
10+import qualified JSONPointer as JP
11+import qualified Text.Regex.PCRE.Heavy as RE
12+
13+data PropertiesRelated schema = PropertiesRelated
14+ { _propProperties :: Maybe (HashMap Text schema)
15+ , _propPattern :: Maybe (HashMap Text schema)
16+ , _propAdditional :: Maybe (AdditionalProperties schema)
17+ } deriving (Eq, Show)
18+
19+instance FromJSON schema => FromJSON (PropertiesRelated schema) where
20+ parseJSON = withObject "PropertiesRelated" $ \o -> PropertiesRelated
21+ <$> o .:! "properties"
22+ <*> o .:! "patternProperties"
23+ <*> o .:! "additionalProperties"
24+
25+emptyProperties :: PropertiesRelated schema
26+emptyProperties = PropertiesRelated
27+ { _propProperties = Nothing
28+ , _propPattern = Nothing
29+ , _propAdditional = Nothing
30+ }
1331
14−import Data.Validator.Failure (Fail(..), prependToPath)
32+data AdditionalProperties schema
33+ = AdditionalPropertiesBool Bool
34+ | AdditionalPropertiesObject schema
35+ deriving (Eq, Show)
1536
16−-- | For internal use.
17−newtype Remaining
18− = Remaining { _unRemaining :: HashMap Text Value }
37+instance FromJSON schema => FromJSON (AdditionalProperties schema) where
38+ parseJSON v = fmap AdditionalPropertiesBool (parseJSON v)
39+ <|> fmap AdditionalPropertiesObject (parseJSON v)
1940
20−--------------------------------------------------
21−-- * properties
22−--------------------------------------------------
41+instance ToJSON schema => ToJSON (AdditionalProperties schema) where
42+ toJSON (AdditionalPropertiesBool b) = toJSON b
43+ toJSON (AdditionalPropertiesObject hm) = toJSON hm
2344
24−data PropertiesInvalid err
25− = PropertiesInvalid err
26− | PropPatternInvalid err
27− | PropAdditionalInvalid (AdditionalPropertiesInvalid err)
28− deriving (Eq, Show)
45+instance Arbitrary schema => Arbitrary (AdditionalProperties schema) where
46+ arbitrary = oneof [ AdditionalPropertiesBool <$> arbitrary
47+ , AdditionalPropertiesObject <$> arbitrary
48+ ]
2949
30−-- | In order of what's tried: @"properties"@, @"patternProperties"@,
31−-- @"additionalProperties"@.
32−properties
33− :: forall err schema.
34− (schema -> Value -> [Fail err])
35− -> Maybe (HashMap Text schema)
36− -> Maybe (AdditionalProperties schema)
37− -> HashMap Text schema
38− -> HashMap Text Value
39− -> [Fail (PropertiesInvalid err)]
40−properties f mPat mAdd propertiesHm x =
41− fmap (fmap PropertiesInvalid) propFailures
42− <> fmap (fmap PropPatternInvalid) patternFailures
43− <> fmap (fmap PropAdditionalInvalid) additionalFailures
44− where
45− propertiesAndUnmatched :: ([Fail err], Remaining)
46− propertiesAndUnmatched = ( failures
47− , Remaining (HM.difference x propertiesHm)
48− )
49− where
50− failures :: [Fail err]
51− failures = HM.toList (HM.intersectionWith f propertiesHm x)
52− >>= (\(k,vs) -> fmap (prependToPath (AP.Token k)) vs)
53−
54− (propFailures, remaining1) = propertiesAndUnmatched
55−
56− mPatProp :: Maybe (HashMap Text Value -> ([Fail err], Remaining))
57− mPatProp = patternAndUnmatched f <$> mPat
58−
59− patternFailures :: [Fail err]
60− patternFailures = case mPatProp of
61− Nothing -> mempty
62− Just val -> fst (val x)
63−
64− remaining2 :: Remaining
65− remaining2 = case mPatProp of
66− Nothing -> remaining1
67− Just val -> snd . val . _unRemaining $ remaining1
68−
69− additionalFailures :: [Fail (AdditionalPropertiesInvalid err)]
70− additionalFailures =
71− case mAdd of
72− Nothing -> mempty
73− Just a -> additionalProperties f True a (_unRemaining remaining2)
74−
75−--------------------------------------------------
76−-- * patternProperties
77−--------------------------------------------------
78−
79−data PatternPropertiesInvalid err
80− = PPInvalid err
81− | PPAdditionalPropertiesInvalid (AdditionalPropertiesInvalid err)
50+-- | A glorified @type@ alias.
51+newtype Regex
52+ = Regex { _unRegex :: Text }
53+ deriving (Eq, Show, Generic)
54+
55+instance HA.Hashable Regex
56+
57+-- NOTE: We'd like to enforce that at least one error exists here.
58+data PropertiesRelatedInvalid err = PropertiesRelatedInvalid
59+ { _prInvalidProperties :: HashMap Text [err]
60+ , _prInvalidPattern :: HashMap (Regex, JP.Key) [err]
61+ , _prInvalidAdditional :: Maybe (APInvalid err)
62+ } deriving (Eq, Show)
63+
64+data APInvalid err
65+ = APBoolInvalid (HashMap Text Value)
66+ | APObjectInvalid (HashMap Text (NonEmpty err))
8267 deriving (Eq, Show)
8368
84−patternProperties
69+-- | First @"properties"@ and @"patternProperties"@ are run simultaneously
70+-- on the data, then @"additionalProperties"@ is run on the remainder.
71+propertiesRelatedVal
8572 :: forall err schema.
86− (schema -> Value -> [Fail err])
87− -> Bool
88− -> Maybe (AdditionalProperties schema)
89− -> HashMap Text schema
73+ (schema -> Value -> [err])
74+ -> PropertiesRelated schema
9075 -> HashMap Text Value
91− -> [Fail (PatternPropertiesInvalid err)]
92−patternProperties _ False _ _ _ = mempty
93−patternProperties f _ mAdd patternPropertiesHm x =
94− (fmap PPInvalid <$> ppFailures)
95− <> (fmap PPAdditionalPropertiesInvalid <$> addFailures)
76+ -> Maybe (PropertiesRelatedInvalid err)
77+propertiesRelatedVal f props x
78+ | all null (HM.elems propFailures)
79+ && all null (HM.elems patFailures)
80+ && isNothing addFailures = Nothing
81+ | otherwise =
82+ Just PropertiesRelatedInvalid
83+ { _prInvalidProperties = propFailures
84+ , _prInvalidPattern = patFailures
85+ , _prInvalidAdditional = addFailures
86+ }
9687 where
97− patternProps :: ([Fail err], Remaining)
98− patternProps = patternAndUnmatched f patternPropertiesHm x
88+ propertiesHm :: HashMap Text schema
89+ propertiesHm = fromMaybe mempty (_propProperties props)
90+
91+ patHm :: HashMap Text schema
92+ patHm = fromMaybe mempty (_propPattern props)
93+
94+ propAndUnmatched :: (HashMap Text [err], Remaining)
95+ propAndUnmatched = ( HM.intersectionWith f propertiesHm x
96+ , Remaining (HM.difference x propertiesHm)
97+ )
98+
99+ (propFailures, propRemaining) = propAndUnmatched
100+
101+ patAndUnmatched :: (HashMap (Regex, JP.Key) [err], Remaining)
102+ patAndUnmatched = patternAndUnmatched f patHm x
103+
104+ (patFailures, patRemaining) = patAndUnmatched
99105
100− (ppFailures, remaining) = patternProps
106+ finalRemaining :: Remaining
107+ finalRemaining = Remaining (HM.intersection (_unRemaining patRemaining)
108+ (_unRemaining propRemaining))
101109
102− addFailures :: [Fail (AdditionalPropertiesInvalid err)]
103− addFailures =
104− case mAdd of
105− Nothing -> mempty
106− Just a -> additionalProperties f True a (_unRemaining remaining)
110+ addFailures :: Maybe (APInvalid err)
111+ addFailures = (\addProp -> additionalProperties f addProp finalRemaining)
112+ =<< _propAdditional props
107113
114+-- | Internal.
115+newtype Remaining
116+ = Remaining { _unRemaining :: HashMap Text Value }
117+
118+-- | Internal.
108119patternAndUnmatched
109120 :: forall err schema.
110− (schema -> Value -> [Fail err])
121+ (schema -> Value -> [err])
111122 -> HashMap Text schema
112123 -> HashMap Text Value
113− -> ([Fail err], Remaining)
124+ -> (HashMap (Regex, JP.Key) [err], Remaining)
114125patternAndUnmatched f patPropertiesHm x =
115− (HM.foldlWithKey' runVals mempty perhapsMatches, remaining)
126+ (HM.foldlWithKey' runMatches mempty perhapsMatches, remaining)
116127 where
117− perhapsMatches :: HashMap Text (Value, [schema])
118− perhapsMatches = HM.foldlWithKey' (matchingSchemas patPropertiesHm) mempty x
128+ -- @[(Regex, schema)]@ will have one item per match.
129+ perhapsMatches :: HashMap Text ([(Regex, schema)], Value)
130+ perhapsMatches =
131+ HM.foldlWithKey' (matchingSchemas patPropertiesHm) mempty x
119132 where
120133 matchingSchemas
121134 :: HashMap Text schema
122− -> HashMap Text (Value, [schema])
135+ -> HashMap Text ([(Regex, schema)], Value)
123136 -> Text
124137 -> Value
125− -> HashMap Text (Value, [schema])
138+ -> HashMap Text ([(Regex, schema)], Value)
126139 matchingSchemas subSchemas acc k v =
127− HM.insert k (v, HM.foldlWithKey' (checkKey k) mempty subSchemas) acc
140+ HM.insert k
141+ (HM.foldlWithKey' (checkKey k) mempty subSchemas, v)
142+ acc
128143
129144 checkKey
130145 :: Text
131− -> [schema]
146+ -> [(Regex, schema)]
132147 -> Text
133148 -> schema
134− -> [schema]
149+ -> [(Regex, schema)]
135150 checkKey k acc r subSchema =
136151 case RE.compileM (encodeUtf8 r) mempty of
137152 Left _ -> acc
138153 Right re -> if k RE.=~ re
139− then pure subSchema <> acc
154+ then (Regex r, subSchema) : acc
140155 else acc
141156
142− runVals
143− :: [Fail err]
157+ runMatches
158+ :: HashMap (Regex, JP.Key) [err]
144159 -> Text
145− -> (Value, [schema])
146− -> [Fail err]
147− runVals acc k (v,subSchemas) =
148− (subSchemas >>= (\schema -> prependToPath (AP.Token k) <$> f schema v))
149− <> acc
160+ -> ([(Regex, schema)], Value)
161+ -> HashMap (Regex, JP.Key) [err]
162+ runMatches acc k (matches,v) =
163+ foldr runMatch acc matches
164+ where
165+ runMatch
166+ :: (Regex, schema)
167+ -> HashMap (Regex, JP.Key) [err]
168+ -> HashMap (Regex, JP.Key) [err]
169+ runMatch (r,schema) = HM.insert (r, JP.Key k) (f schema v)
150170
151171 remaining :: Remaining
152− remaining = Remaining . fmap fst . HM.filter (null . snd) $ perhapsMatches
153−
154−--------------------------------------------------
155−-- * additionalProperties
156−--------------------------------------------------
157−
158−data AdditionalProperties schema
159− = AdditionalPropertiesBool Bool
160− | AdditionalPropertiesObject schema
161− deriving (Eq, Show)
162−
163−instance FromJSON schema => FromJSON (AdditionalProperties schema) where
164− parseJSON v = fmap AdditionalPropertiesBool (parseJSON v)
165− <|> fmap AdditionalPropertiesObject (parseJSON v)
166−
167−instance ToJSON schema => ToJSON (AdditionalProperties schema) where
168− toJSON (AdditionalPropertiesBool b) = toJSON b
169− toJSON (AdditionalPropertiesObject hm) = toJSON hm
170−
171−instance Arbitrary schema => Arbitrary (AdditionalProperties schema) where
172− arbitrary = oneof [ AdditionalPropertiesBool <$> arbitrary
173− , AdditionalPropertiesObject <$> arbitrary
174− ]
175−
176−data AdditionalPropertiesInvalid err
177− = APBoolInvalid
178− | APObjectInvalid err
179− deriving (Eq, Show)
172+ remaining = Remaining . fmap snd . HM.filter (null . fst) $ perhapsMatches
180173
174+-- Internal.
181175additionalProperties
182176 :: forall err schema.
183− (schema -> Value -> [Fail err])
184− -> Bool
177+ (schema -> Value -> [err])
185178 -> AdditionalProperties schema
186− -> HashMap Text Value
187− -> [Fail (AdditionalPropertiesInvalid err)]
188−additionalProperties _ False _ _ = mempty
189−additionalProperties f _ a x =
179+ -> Remaining
180+ -> Maybe (APInvalid err)
181+additionalProperties f a x =
190182 case a of
191183 AdditionalPropertiesBool b ->
192− ($> APBoolInvalid) <$> additionalPropertiesBool b x
184+ APBoolInvalid <$> additionalPropertiesBool b x
193185 AdditionalPropertiesObject b ->
194− fmap APObjectInvalid <$> additionalPropertiesObject f b x
186+ APObjectInvalid <$> additionalPropertiesObject f b x
195187
188+-- | Internal.
196189additionalPropertiesBool
197190 :: Bool
198− -> HashMap Text Value
199− -> [Fail ()]
200−additionalPropertiesBool False x
201− | HM.size x > 0 = pure $ Failure () (Bool False) mempty (Object x)
202− | otherwise = mempty
203−additionalPropertiesBool True _ = mempty
204−
191+ -> Remaining
192+ -> Maybe (HashMap Text Value)
193+additionalPropertiesBool True _ = Nothing
194+additionalPropertiesBool False (Remaining x)
195+ | HM.size x > 0 = Just x
196+ | otherwise = Nothing
197+
198+-- | Internal.
205199additionalPropertiesObject
206200 :: forall err schema.
207− (schema -> Value -> [Fail err])
201+ (schema -> Value -> [err])
208202 -> schema
209− -> HashMap Text Value
210− -> [Fail err]
211−additionalPropertiesObject f schema x = HM.toList x >>= g
212− where
213− g :: (Text, Value) -> [Fail err]
214− g (k,v) = prependToPath (AP.Token k) <$> f schema v
203+ -> Remaining
204+ -> Maybe (HashMap Text (NonEmpty err))
205+additionalPropertiesObject f schema (Remaining x) =
206+ let errs = HM.mapMaybe (NE.nonEmpty . f schema) x
207+ in if HM.null errs
208+ then Nothing
209+ else Just errs
src/Data/Validator/Draft4/String.hs+50−16
@@ -2,44 +2,78 @@
22module Data.Validator.Draft4.String where
33
44import Import
5−import Prelude
65
7−import qualified Data.Text as T
8−import Data.Text.Encoding (encodeUtf8)
9−import qualified Text.Regex.PCRE.Heavy as RE
10−
11−import Data.Validator.Failure (Fail(..))
6+import qualified Data.Text as T
7+import Data.Text.Encoding (encodeUtf8)
8+import qualified Text.Regex.PCRE.Heavy as RE
129
1310--------------------------------------------------
1411-- * maxLength
1512--------------------------------------------------
1613
14+newtype MaxLength
15+ = MaxLength { _unMaxLength :: Int }
16+ deriving (Eq, Show)
17+
18+instance FromJSON MaxLength where
19+ parseJSON = withObject "MaxLength" $ \o ->
20+ MaxLength <$> o .: "maxLength"
21+
22+data MaxLengthInvalid
23+ = MaxLengthInvalid MaxLength Text
24+ deriving (Eq, Show)
25+
1726-- | The spec requires @"maxLength"@ to be non-negative.
18−maxLength :: Int -> Text -> Maybe (Fail ())
19−maxLength n x
27+maxLengthVal :: MaxLength -> Text -> Maybe MaxLengthInvalid
28+maxLengthVal a@(MaxLength n) x
2029 | n <= 0 = Nothing
21− | T.length x > n = Just (Failure () (toJSON n) mempty (String x))
30+ | T.length x > n = Just (MaxLengthInvalid a x)
2231 | otherwise = Nothing
2332
2433--------------------------------------------------
2534-- * minLength
2635--------------------------------------------------
2736
37+newtype MinLength
38+ = MinLength { _unMinLength :: Int }
39+ deriving (Eq, Show)
40+
41+instance FromJSON MinLength where
42+ parseJSON = withObject "MinLength" $ \o ->
43+ MinLength <$> o .: "minLength"
44+
45+data MinLengthInvalid
46+ = MinLengthInvalid MinLength Text
47+ deriving (Eq, Show)
48+
2849-- | The spec requires @"minLength"@ to be non-negative.
29−minLength :: Int -> Text -> Maybe (Fail ())
30−minLength n x
50+minLengthVal :: MinLength -> Text -> Maybe MinLengthInvalid
51+minLengthVal a@(MinLength n) x
3152 | n <= 0 = Nothing
32− | T.length x < n = Just (Failure () (toJSON n) mempty (String x))
53+ | T.length x < n = Just (MinLengthInvalid a x)
3354 | otherwise = Nothing
3455
3556--------------------------------------------------
3657-- * pattern
3758--------------------------------------------------
3859
39−patternVal :: Text -> Text -> Maybe (Fail ())
40−patternVal t x =
60+newtype PatternValidator
61+ = PatternValidator { _unPatternValidator :: Text }
62+ deriving (Eq, Show)
63+
64+instance FromJSON PatternValidator where
65+ parseJSON = withObject "PatternValidator" $ \o ->
66+ PatternValidator <$> o .: "pattern"
67+
68+data PatternInvalid
69+ = PatternNotRegex -- TODO: let these pass successfully?
70+ | PatternInvalid PatternValidator Text
71+ deriving (Eq, Show)
72+
73+patternVal :: PatternValidator -> Text -> Maybe PatternInvalid
74+patternVal a@(PatternValidator t) x =
4175 case RE.compileM (encodeUtf8 t) mempty of
42− Left _ -> Just (Failure () (toJSON t) mempty (String x))
76+ Left _ -> Just PatternNotRegex
4377 Right re -> if x RE.=~ re
4478 then Nothing
45− else Just (Failure () (toJSON t) mempty (String x))
79+ else Just (PatternInvalid a x)
src/Data/Validator/Failure.hsdeleted−49
@@ -1,49 +0,0 @@
1−{-# LANGUAGE DeriveFunctor #-}
2−
3−module Data.Validator.Failure where
4−
5−import Import
6−-- Hiding is for GHCs before 7.10:
7−import Prelude hiding (concat, sequence)
8−
9−import qualified Data.Aeson.Pointer as AP
10−
11−-- | Validators shouldn't know more about the schema they're going to
12−-- be used with than necessary. If a validator throws errors using the
13−-- error sum type of a particular schema, then it can't be used with
14−-- other schemas later that have different error sum types (at least not
15−-- without writing partial functions).
16−--
17−-- Because of this we make 'Fail' a higher order type, so each validator
18−-- can return a sum type describing only the failures that can occur in that
19−-- validator (or '()' if that validator can only fail in one way).
20−--
21−-- It's the job of a schema's validate function to unify the errors produced
22−-- by the validators it uses into a single error sum type for that schema.
23−-- The schema's validate function will return a 'Fail' with
24−-- that sum type as its type argument.
25−--
26−-- The slightly weird naming ('Fail' and 'Failure') is so that we can define
27−-- a 'type Failure = Fail SchemaErrorType' for each of our schemas, and
28−-- export it along with 'Fail(..)'. This way the users of the library only
29−-- use 'Failure', not 'Fail'.
30−data Fail err = Failure
31− { _failureValidatorsCalled :: !err
32− -- ^ E.g. @Items UniqueItems@ during draft 4 validation.
33− , _failureFinalValidator :: !Value
34− -- ^ The value of the validator that raised the error (e.g. the value of
35− -- @"uniqueItems"@ in the above example.
36− , _failureOffendingPointer :: !AP.Pointer
37− -- ^ A pointer to the part of the data that caused invalidation.
38− , _failureOffendingData :: !Value
39− -- ^ The part of the data that caused invalidation. Usually this is
40− -- identical to the result of resolving '_invalidOffendingPointer'
41− -- against the starting data, but not always (e.g. in the case of
42− -- 'additionalItems' where '_invalidOffendingData' will be the items
43− -- in the array that were not allowed, instead of the entire array).
44− } deriving (Eq, Show, Functor)
45−
46−prependToPath :: AP.Token -> Fail a -> Fail a
47−prependToPath tok failure =
48− let old = _failureOffendingPointer failure
49− in failure { _failureOffendingPointer = AP.Pointer [tok] <> old }
src/Data/Validator/Reference.hs+4−4
@@ -7,14 +7,14 @@
77module Data.Validator.Reference where
88
99import Import
10−import Prelude
1110
12−import qualified Data.Aeson.Pointer as AP
1311import qualified Data.Text as T
1412import Data.Text.Encoding (decodeUtf8, encodeUtf8)
13+import qualified JSONPointer as JP
1514import Network.HTTP.Types.URI (urlDecode)
1615import System.FilePath ((</>), dropFileName)
1716
17+-- | TODO: Replace these with actual URI data types.
1818type URIBase = Maybe Text
1919type URIBaseAndFragment = (Maybe Text, Maybe Text)
2020
@@ -34,8 +34,8 @@ resolveFragment
3434resolveFragment Nothing schema = Just schema
3535resolveFragment (Just pointer) schema = do
3636 let urlDecoded = decodeUtf8 . urlDecode True . encodeUtf8 $ pointer
37− p <- either (const Nothing) Just (AP.unescape urlDecoded)
38− x <- either (const Nothing) Just (AP.resolve p (toJSON schema))
37+ p <- either (const Nothing) Just (JP.unescape urlDecoded)
38+ x <- either (const Nothing) Just (JP.resolve p (toJSON schema))
3939 case fromJSON x of
4040 Error _ -> Nothing
4141 Success schema' -> Just schema'
src/Data/Validator/Types.hs+3−7
@@ -1,13 +1,9 @@
1−{-# LANGUAGE DeriveFunctor #-}
21
32module Data.Validator.Types where
43
5−import Prelude
4+import Import
65
7−import Data.Aeson (Value)
8−import Data.Profunctor (Profunctor(..))
9−
10−import Data.Validator.Failure (Fail)
6+import Data.Profunctor (Profunctor(..))
117
128data Validator schema val err = Validator
139 { _embedded :: val -> ([schema], [schema])
@@ -19,7 +15,7 @@ data Validator schema val err = Validator
1915 -- This is done to allow static detection of loops, though this isn't
2016 -- implemented yet (though the Draft4 code does do live loop detection
2117 -- during validation).
22− , _validate :: val -> Value -> [Fail err]
18+ , _validate :: val -> Value -> [err]
2319 } deriving Functor
2420
2521instance Profunctor (Validator schema) where
src/Data/Validator/Utils.hs+1−3
@@ -2,11 +2,9 @@
22module Data.Validator.Utils where
33
44import Import
5−import Prelude
65
7−import Control.Arrow
6+import Control.Monad (fail)
87import qualified Data.HashMap.Strict as HM
9−import Data.List.NonEmpty (NonEmpty)
108import qualified Data.List.NonEmpty as NE
119import Data.Scientific (Scientific, fromFloatDigits)
1210import Data.Set (Set)
src/Import.hs+6−6
@@ -1,12 +1,12 @@
11
22module Import (module Export) where
33
4−import Control.Applicative as Export
4+import Protolude as Export
5+
56import Data.Aeson as Export
6−import Data.Foldable as Export
77import Data.HashMap.Strict as Export (HashMap)
8−import Data.Monoid as Export
9−import Data.Text as Export (Text)
10−import Data.Traversable as Export
8+import Data.List as Export (length, null) -- for older GHCs
9+import Data.List.NonEmpty as Export (NonEmpty)
1110import Data.Vector as Export (Vector)
12−import Test.QuickCheck as Export hiding (Failure, Result, Success)
11+import Test.QuickCheck as Export hiding ((.&.), Failure,
12+ Result, Success)
test/Local.hs+13−16
@@ -1,18 +1,17 @@
11
22module Main where
33
4−import Control.Applicative
5−import Control.Monad (unless)
4+import Protolude
5+
66import Data.Aeson
7−import Data.Foldable (traverse_)
8−import qualified Data.List.NonEmpty as N
9−import Data.Monoid
7+import qualified Data.List.NonEmpty as NE
108import qualified System.Timeout as TO
119import Test.Hspec
1210import Test.QuickCheck (property)
1311
1412import qualified Data.JsonSchema.Draft4 as D4
15−import Data.JsonSchema.Fetch (ReferencedSchemas(..))
13+import Data.JsonSchema.Fetch (ReferencedSchemas(..),
14+ URISchemaMap(..))
1615import qualified Data.JsonSchema.Types as JT
1716import qualified Local.Failure
1817import qualified Local.Validation
@@ -21,13 +20,13 @@ import Shared
2120
2221-- Examples
2322import qualified AlternateSchema
24−import qualified Full
2523import qualified Simple
24+import qualified TwoStep
2625
27−dir :: String
26+dir :: FilePath
2827dir = "JSON-Schema-Test-Suite/tests/draft4"
2928
30−supplementDir :: String
29+supplementDir :: FilePath
3130supplementDir = "test/supplement"
3231
3332main :: IO ()
@@ -73,22 +72,20 @@ main = do
7372 res <- D4.fetchHTTPAndValidate (D4.SchemaWithURI s Nothing) (_scData sc)
7473 let failures = case res of
7574 Right () -> mempty
76− Left (D4.HVData a) -> N.toList a
77− other -> error ("Local.validate error: "
75+ Left (D4.HVData a) -> NE.toList (D4._invalidFailures a)
76+ other -> panic ("Local.validate error: "
7877 <> show other)
79− traverse_ (checkPointer (_scData sc)) failures
8078 assertResult sc failures
8179
8280 validateExample :: JT.Schema -> SchemaTestCase -> Expectation
8381 validateExample s sc = do
8482 res <- AlternateSchema.referencesViaHTTP (D4.SchemaWithURI s Nothing)
8583 case res of
86− Left e -> error ("Local.validateExample error: " <> show e)
84+ Left e -> panic ("Local.validateExample error: " <> show e)
8785 Right schemaMap -> do
8886 let failures = AlternateSchema.validate
89− (ReferencedSchemas s schemaMap)
87+ (ReferencedSchemas s (_unURISchemaMap schemaMap))
9088 Nothing s (_scData sc)
91− traverse_ (checkPointer (_scData sc)) failures
9289 assertResult sc failures
9390
9491quickCheckTests :: Spec
@@ -101,5 +98,5 @@ quickCheckTests =
10198
10299exampleTests :: Spec
103100exampleTests = do
104− it "Full.example compiles successfully" Full.example
105101 it "Simple.example compiles successfully" Simple.example
102+ it "TwoStep.example compiles successfully" TwoStep.example
test/Local/Failure.hs+59−15
@@ -1,35 +1,41 @@
11
22module Local.Failure where
33
4−import Prelude
4+import Protolude
55
66import Data.Aeson
7−import qualified Data.Aeson.Pointer as P
8−import Data.Monoid
97import Test.Hspec
8+import qualified Data.Vector as V
9+import qualified JSONPointer as JP
1010
1111import Data.JsonSchema.Draft4
12−import Data.JsonSchema.Draft4.Spec (validate)
13−import qualified Data.Validator.Draft4.Array as AR
12+import qualified Data.JsonSchema.Draft4.Spec as Spec
13+import qualified Data.Validator.Draft4 as D4
1414
1515spec :: Spec
1616spec = do
1717 it "items array failures are constructed correctly" itemsArray
1818 it "items object failures are constructed correctly" itemsObject
19+ it "reports infinitely looping references correctly" loopRef
1920
2021itemsArray :: Expectation
2122itemsArray =
2223 failures `shouldBe`
23− [Failure (Items UniqueItems) (Bool True) (P.Pointer [P.Token "0"])
24− (toJSON [True, True])
24+ [ FailureItems (D4.ItemsArrayInvalid (
25+ pure ( JP.Index 0
26+ , pure (FailureUniqueItems (D4.UniqueItemsInvalid
27+ (V.fromList [Null, Null])
28+ ))
29+ )
30+ ))
2531 ]
2632 where
27− failures = validate (ReferencedSchemas schema mempty)
28− sw (toJSON [[True, True]])
33+ failures :: [ValidatorFailure]
34+ failures = Spec.specValidate (ReferencedSchemas schema mempty) sw badData
2935
3036 schema :: Schema
3137 schema = emptySchema
32− { _schemaItems = Just $ AR.ItemsArray
38+ { _schemaItems = Just $ D4.ItemsArray
3339 [emptySchema { _schemaUniqueItems = Just True }]
3440 }
3541
@@ -39,19 +45,27 @@ itemsArray =
3945 , _swURI = Nothing
4046 }
4147
48+ badData :: Value
49+ badData = toJSON [[Null, Null]]
50+
4251itemsObject :: Expectation
4352itemsObject =
4453 failures `shouldBe`
45− [Failure (Items UniqueItems) (Bool True) (P.Pointer [P.Token "0"])
46− (toJSON [True, True])
54+ [ FailureItems (D4.ItemsObjectInvalid (
55+ pure ( JP.Index 1
56+ , pure (FailureUniqueItems (D4.UniqueItemsInvalid
57+ (V.fromList [Null, Null])
58+ ))
59+ )
60+ ))
4761 ]
4862 where
49− failures = validate (ReferencedSchemas schema mempty)
50− sw (toJSON [[True, True]])
63+ failures :: [ValidatorFailure]
64+ failures = Spec.specValidate (ReferencedSchemas schema mempty) sw badData
5165
5266 schema :: Schema
5367 schema = emptySchema
54− { _schemaItems = Just $ AR.ItemsObject
68+ { _schemaItems = Just $ D4.ItemsObject
5569 (emptySchema { _schemaUniqueItems = Just True })
5670 }
5771
@@ -60,3 +74,33 @@ itemsObject =
6074 { _swSchema = schema
6175 , _swURI = Nothing
6276 }
77+
78+ badData :: Value
79+ badData = toJSON [Null, toJSON [Null, Null]]
80+
81+loopRef :: Expectation
82+loopRef =
83+ failures `shouldBe`
84+ [ FailureRef (D4.RefLoop
85+ "#"
86+ (D4.VisitedSchemas [(Nothing, Nothing)])
87+ (Nothing, Nothing)
88+ )
89+ ]
90+ where
91+ failures :: [ValidatorFailure]
92+ failures = Spec.specValidate (ReferencedSchemas schema mempty) sw badData
93+
94+ schema :: Schema
95+ schema = emptySchema
96+ { _schemaRef = Just "#"
97+ }
98+
99+ sw :: SchemaWithURI Schema
100+ sw = SchemaWithURI
101+ { _swSchema = schema
102+ , _swURI = Nothing
103+ }
104+
105+ badData :: Value
106+ badData = toJSON [[Null, Null]]
test/Local/Reference.hs+2
@@ -1,6 +1,8 @@
11
22module Local.Reference where
33
4+import Protolude
5+
46import Test.Hspec
57
68import Data.Validator.Reference
test/Local/Validation.hs+3−3
@@ -1,12 +1,12 @@
11
22module Local.Validation where
33
4−import Control.Applicative
4+import Protolude hiding (msg)
5+
56import Data.Aeson
67import qualified Data.Aeson as AE
78import qualified Data.HashMap.Strict as HM
8−import Data.Monoid
9−import Data.Text (Text)
9+import Data.String (String)
1010import Test.Hspec
1111
1212import Data.JsonSchema.Draft4
test/Remote.hs+10−12
@@ -1,25 +1,25 @@
11
22module Main where
33
4−import Control.Applicative
4+import Protolude
5+
56import Control.Concurrent.Async (withAsync)
6−import Data.Foldable (traverse_)
7−import qualified Data.List.NonEmpty as N
8−import Data.Monoid
7+import qualified Data.List.NonEmpty as NE
98import Network.Wai.Application.Static (defaultFileServerSettings,
109 staticApp)
1110import Network.Wai.Handler.Warp (run)
1211import Test.Hspec
1312
1413import qualified Data.JsonSchema.Draft4 as D4
15−import Data.JsonSchema.Fetch (ReferencedSchemas(..))
14+import Data.JsonSchema.Fetch (ReferencedSchemas(..),
15+ URISchemaMap(..))
1616import qualified Data.JsonSchema.Types as JT
1717import Shared
1818
1919-- Examples
2020import qualified AlternateSchema
2121
22−dir :: String
22+dir :: FilePath
2323dir = "JSON-Schema-Test-Suite/tests/draft4"
2424
2525main :: IO ()
@@ -36,22 +36,20 @@ main = withAsync serve $ \_ -> do
3636 res <- D4.fetchHTTPAndValidate (D4.SchemaWithURI s Nothing) (_scData sc)
3737 let failures = case res of
3838 Right () -> mempty
39− Left (D4.HVData a) -> N.toList a
40− other -> error ("Local.validate error: "
39+ Left (D4.HVData a) -> NE.toList (D4._invalidFailures a)
40+ other -> panic ("Local.validate error: "
4141 <> show other)
42− traverse_ (checkPointer (_scData sc)) failures
4342 assertResult sc failures
4443
4544 validateExample :: JT.Schema -> SchemaTestCase -> Expectation
4645 validateExample s sc = do
4746 res <- AlternateSchema.referencesViaHTTP (D4.SchemaWithURI s Nothing)
4847 case res of
49− Left e -> error ("Remote.validateExample error: " <> show e)
48+ Left e -> panic ("Remote.validateExample error: " <> show e)
5049 Right schemaMap -> do
5150 let failures = AlternateSchema.validate
52− (ReferencedSchemas s schemaMap)
51+ (ReferencedSchemas s (_unURISchemaMap schemaMap))
5352 Nothing s (_scData sc)
54− traverse_ (checkPointer (_scData sc)) failures
5553 assertResult sc failures
5654
5755serve :: IO ()
test/Shared.hs+19−50
@@ -1,29 +1,19 @@
1−{-# LANGUAGE DeriveGeneric #-}
21
32module Shared where
43
5−import Control.Applicative
6−import Control.Monad
4+import Protolude
5+
6+import Control.Monad (fail)
77import Data.Aeson
8−import qualified Data.Aeson.Pointer as AP
98import Data.Aeson.TH
10−import qualified Data.ByteString.Lazy as LBS
11−import Data.Char (toLower)
12−import qualified Data.HashMap.Strict as HM
13−import Data.List (isInfixOf, stripPrefix)
14−import Data.Maybe
15−import Data.Monoid
16−import Data.Text (Text)
17−import qualified Data.Text as T
18−import Data.Traversable
19−import qualified Data.Vector as V
20−import GHC.Generics
21−import qualified System.Directory as SD
22−import System.FilePath ((</>))
9+import qualified Data.ByteString.Lazy as LBS
10+import Data.Char (toLower)
11+import Data.List (stripPrefix, unlines)
12+import qualified Data.Text as T
13+import qualified System.Directory as SD
14+import System.FilePath ((</>))
2315import Test.Hspec
2416
25−import qualified Data.JsonSchema.Draft4 as D4
26−
2717-- Recursively return the contents of a directory
2818-- (or return itself if given a file as an argument).
2919--
@@ -47,35 +37,14 @@ listFilesFullPath path = do
4737 fs <- fmap (path </>) <$> SD.listDirectory path
4838 concat <$> traverse listFilesFullPath fs
4939
50−checkPointer :: Value -> D4.Failure -> Expectation
51−checkPointer v failure =
52− case AP.resolve (D4._failureOffendingPointer failure) v of
53− Left e -> error ("Couldn't resolve pointer: " <> show e)
54− Right a -> assertContains a (D4._failureOffendingData failure)
55− where
56− -- Some validators, such as 'additionalItems', only return a subset
57− -- of the data incated by their '_failureOffendingPointer'.
58− -- See the comments on 'Data.Validator.Failure.Failure' for more info.
59− assertContains :: Value -> Value -> Expectation
60− assertContains x y
61− | x == y = pure ()
62− | otherwise =
63− case (x,y) of
64− (Array xs, Array ys) ->
65− V.toList ys `shouldSatisfy` (`isInfixOf` V.toList xs)
66− (Object xhm, Object yhm) ->
67− HM.toList yhm `shouldSatisfy` (`isInfixOf` HM.toList xhm)
68− _ -> expectationFailure
69− "Pointer resolution incorrect: result mismatch"
70−
71−isHTTPTest :: String -> Bool
40+isHTTPTest :: FilePath -> Bool
7241isHTTPTest file = (file == "definitions.json")
7342 || (file == "ref.json")
7443 || (file == "refRemote.json")
7544
7645-- | We may never support the @"format"@ keywords, and
7746-- are currently failing the zeroTerminatedFloats test.
78−skipOptional :: String -> Bool
47+skipOptional :: FilePath -> Bool
7948skipOptional file = (file == "optional/format.json")
8049 || (file == "optional/zeroTerminatedFloats.json")
8150
@@ -104,9 +73,9 @@ instance FromJSON SchemaTestCase where
10473 { fieldLabelModifier = fmap toLower . drop 3 }
10574
10675readSchemaTests
107− :: String
76+ :: FilePath
10877 -- ^ The path to a directory.
109− -> (String -> Bool)
78+ -> (FilePath -> Bool)
11079 -- ^ A function to decide what we're interested in within that directory.
11180 -> IO [SchemaTest]
11281readSchemaTests dir filterFunc = do
@@ -114,7 +83,7 @@ readSchemaTests dir filterFunc = do
11483 concat <$> traverse fileToCases files
11584 where
11685 -- Each file contains an array of SchemaTests, not just one.
117− fileToCases :: String -> IO [SchemaTest]
86+ fileToCases :: FilePath -> IO [SchemaTest]
11887 fileToCases name = do
11988 let fullPath = dir </> name
12089 jsonBS <- LBS.readFile fullPath
@@ -122,7 +91,7 @@ readSchemaTests dir filterFunc = do
12291 Left e -> fail $ "couldn't parse file '" <> fullPath <> "': " <> e
12392 Right schemaTests -> pure $ prependFileName name <$> schemaTests
12493
125− prependFileName :: String -> SchemaTest -> SchemaTest
94+ prependFileName :: FilePath -> SchemaTest -> SchemaTest
12695 prependFileName fileName s = s
12796 { _stDescription = T.pack fileName <> ": " <> _stDescription s
12897 }
@@ -138,15 +107,15 @@ toTest validate st =
138107 where
139108 schema :: schema
140109 schema = case fromJSON (_stSchema st) of
141− Error e -> error ("Couldn't parse schema: " <> show e)
110+ Error e -> panic ("Couldn't parse schema: " <> show e)
142111 Success a -> a
143112
144−assertResult :: SchemaTestCase -> [D4.Failure] -> Expectation
113+assertResult :: Show err => SchemaTestCase -> [err] -> Expectation
145114assertResult sc failures
146115 | _scValid sc = assertValid sc failures
147116 | otherwise = assertInvalid sc failures
148117
149−assertValid :: SchemaTestCase -> [D4.Failure] -> Expectation
118+assertValid :: Show err => SchemaTestCase -> [err] -> Expectation
150119assertValid _ [] = pure ()
151120assertValid sc failures =
152121 expectationFailure $ unlines
@@ -156,7 +125,7 @@ assertValid sc failures =
156125 , " Validation failures: " <> show failures
157126 ]
158127
159−assertInvalid :: SchemaTestCase -> [D4.Failure] -> Expectation
128+assertInvalid :: SchemaTestCase -> [err] -> Expectation
160129assertInvalid sc [] =
161130 expectationFailure $ unlines
162131 [ " Validated invalid data"