@@ -0,0 +1,497 @@
1+{-# LANGUAGE OverloadedLists #-}
3+-- | This is generally meant to be an internal module.
4+-- It's only exposed in case you want to make your own
5+-- 'Spec'. If you just want to use JSON Schema Draft 4
6+-- use the preassembled 'Data.JsonSchema.draft4' instead.
8+-- This module exposes three types of functions. To describe
9+-- them we'll use the properties validators as an example.
11+-- 1. Functions that aren't followed by apostrophes have the
12+-- type 'ValidatorGen' and are meant to be used in a 'Spec'.
13+-- Examples are 'properties' and 'additionalProperties'.
15+-- 2. Functions that are followed by a single apostrophe, such
16+-- as 'additionalProperties'', aren't meant to be used standalone
17+-- in a 'Spec'. Instead they're used by other validators. For
18+-- instance, 'additionalProperties' disables itself if the key
19+-- "properties" is present to allow 'additionalProperties' to
20+-- handle its validation. Placing the actual meat of
21+-- 'additionalProperties' in 'additionalProperties'' allows for
22+-- code reuse between 'properties' and 'additionalProperties'.
24+-- 3. Functions that are followed by a double apostrophe, such
25+-- as 'patternProperties''', return an enhanced validator. It
26+-- not only reports errors, but also the parts of the target
27+-- data that it matches. These functions also aren't used in
28+-- a 'Spec' instance, but are necessary helpers for actual
29+-- validators in certain situations.
31+module Data.JsonSchema.Validators where
33+import Control.Applicative
36+import Data.Fixed (mod')
38+import Data.HashMap.Strict (HashMap)
39+import qualified Data.HashMap.Strict as H
40+import Data.JsonSchema.Core
41+import Data.JsonSchema.JsonReference
42+import Data.JsonSchema.Utils
45+import Data.Text (Text)
46+import qualified Data.Text as T
47+import Data.Traversable
48+import Data.Vector (Vector)
49+import qualified Data.Vector as V
52+--------------------------------------------------
54+--------------------------------------------------
56+multipleOf :: ValidatorGen
57+multipleOf _ _ _ (Number val) = do
61+ (Number y) -> if y `mod'` val /= 0
62+ then V.singleton $ tshow y <> " isn't a multiple of " <> tshow val
65+multipleOf _ _ _ _ = Nothing
67+maximumVal :: ValidatorGen
68+maximumVal _ _ s (Number val) =
69+ let f = case H.lookup "exclusiveMaximum" (_rsObject s) of
70+ Just (Bool a) -> if a then (>=) else (>)
74+ (Number y) -> if y `f` val
75+ then V.singleton $ tshow y <> " fails to validate against maximum " <> tshow val
78+maximumVal _ _ _ _ = Nothing
80+minimumVal :: ValidatorGen
81+minimumVal _ _ s (Number val) =
82+ let f = case H.lookup "exclusiveMinimum" (_rsObject s) of
83+ Just (Bool a) -> if a then (<=) else (<)
87+ (Number y) -> if y `f` val
88+ then V.singleton $ tshow y <> " fails to validate against minimum " <> tshow val
91+minimumVal _ _ _ _ = Nothing
93+--------------------------------------------------
95+--------------------------------------------------
97+maxLength :: ValidatorGen
98+maxLength _ _ _ v = do
103+ (String y) -> if T.length y > val
104+ then V.singleton $ y <> " is greater than maxLength " <> tshow val
108+minLength :: ValidatorGen
109+minLength _ _ _ v = do
110+ val <- fromJSONInt v
114+ (String y) -> if T.length y < val
115+ then V.singleton $ y <> " is less than minLength " <> tshow val
119+pattern :: ValidatorGen
120+pattern _ _ _ (String val) =
124+ case matchRegexPR (T.unpack val) (T.unpack t) of
125+ Nothing -> V.singleton $ t <> " fails to validate against pattern " <> val
128+pattern _ _ _ _ = Nothing
130+--------------------------------------------------
132+--------------------------------------------------
134+-- | Also covers additionalItems.
136+-- We can leave additionalItems out of the spec HashMap, since items defaults to {},
137+-- and if it isn't present additionalItems will always validate successfully.
138+items :: ValidatorGen
139+items spec g s (Object val) =
140+ let sub = compile spec g (RawSchema (_rsURI s) val)
143+ (Array ys) -> ys >>= validate sub
145+items spec g s (Array vs) = do
146+ os <- traverse toObj vs
147+ let ss = compile spec g . RawSchema (_rsURI s) <$> os
149+ a <- H.lookup "additionalItems" (_rsObject s)
150+ additionalItems' spec g s a
154+ let extras = V.drop (V.length os) ys
155+ in join (V.zipWith validate ss ys) <> runMaybeVal addItems (Array extras)
157+items _ _ _ _ = Nothing
159+additionalItems' :: ValidatorGen
160+additionalItems' _ _ _ (Bool val) =
163+ (Array ys) -> if not val && V.length ys > 0
164+ then V.singleton "TODO"
167+additionalItems' spec g s (Object val) =
168+ let sub = compile spec g (RawSchema (_rsURI s) val)
171+ (Array ys) -> ys >>= validate sub
173+additionalItems' _ _ _ _ = Nothing
175+maxItems :: ValidatorGen
176+maxItems _ _ _ v = do
177+ val <- fromJSONInt v
181+ (Array ys) -> if V.length ys > val
182+ then V.singleton $ tshow ys <> " has more items than maxItems " <> tshow val
186+minItems :: ValidatorGen
187+minItems _ _ _ v = do
188+ val <- fromJSONInt v
192+ (Array ys) -> if V.length ys < val
193+ then V.singleton $ tshow ys <> " has fewer items than minItems " <> tshow val
197+uniqueItems :: ValidatorGen
198+uniqueItems _ _ _ (Bool val) = do
202+ (Array ys) -> if allUnique ys
204+ else V.singleton "TODO"
206+uniqueItems _ _ _ _ = Nothing
208+--------------------------------------------------
210+--------------------------------------------------
212+maxProperties :: ValidatorGen
213+maxProperties _ _ _ v = do
214+ val <- fromJSONInt v
218+ (Object o) -> if H.size o > val
219+ then V.singleton $ tshow o <> " has more members than maxProperties " <> tshow val
223+minProperties :: ValidatorGen
224+minProperties _ _ _ v = do
225+ val <- fromJSONInt v
229+ (Object o) -> if H.size o < val
230+ then V.singleton $ tshow o <> " has fewer members than minProperties " <> tshow val
234+required :: ValidatorGen
235+required _ _ _ (Array vs) = do
236+ when (V.length vs == 0) Nothing
237+ ts <- traverse toTxt vs
238+ let a = vectorToMap ts
239+ when (H.size a /= V.length ts) Nothing
242+ (Object o) -> if H.size (H.difference a o) > 0
243+ then V.singleton $ "the keys of " <> tshow o
244+ <> " don't contain all the required elements " <> tshow vs
249+ vectorToMap :: (Eq a, Hashable a) => Vector a -> HashMap a Bool
250+ vectorToMap vec = H.fromList $ zip (V.toList vec) (repeat True)
251+required _ _ _ _ = Nothing
253+-- TODO: Fix up the properties* validators. They're all a huge mess, but at least
256+-- In order of what's tried: properties, patternProperties, additionalProperties
257+properties :: ValidatorGen
258+properties spec g s v = do
259+ let mProps = properties'' spec g s v
261+ aV <- H.lookup "patternProperties" (_rsObject s)
262+ patternProperties'' spec g s aV
264+ aVal <- H.lookup "additionalProperties" (_rsObject s)
265+ additionalProperties' spec g s aVal
266+ when (isNothing mProps && isNothing mPatProp && isNothing mAdd) Nothing
269+ (Object y) -> -- Got myself into a mess here.
270+ let (e1s, remaining) = runMaybeVal' mProps (Object y)
271+ (_, remaining') = runMaybeVal' mPatProp remaining
272+ (e2s, _) = runMaybeVal' mPatProp (Object y)
273+ in e1s <> e2s <> runMaybeVal mAdd remaining'
282+ -> Maybe (Value -> (Vector ValErr, Value))
283+properties'' spec g s (Object val) = do
284+ os <- traverse toObj val
285+ let oss = compile spec g . RawSchema (_rsURI s) <$> os
288+ (Object y) -> ( join . V.fromList . H.elems $ H.intersectionWith validate oss y
289+ , Object (H.difference y oss))
291+properties'' _ _ _ _ = Nothing
299+ -> Maybe (Value -> (Vector ValErr, Value))
300+patternProperties'' spec g s (Object val) = do
301+ os <- traverse toObj val
302+ let vs = compile spec g . RawSchema (_rsURI s) <$> os
305+ (Object y) -> let ms = matches (V.fromList . H.toList $ vs) <$> (V.fromList . H.toList $ y)
306+ in (ms >>= runVals, leftovers ms)
309+ matches :: Vector (Text, Schema) -> (Text, Value) -> (Text, Value, Vector Schema)
310+ matches ss (k, v) = (k, v, ss >>= match k)
312+ match :: Text -> (Text, Schema) -> Vector Schema
314+ case matchRegexPR (T.unpack r) (T.unpack k) of
316+ Just _ -> V.singleton sc
318+ runVals :: (Text, Value, Vector Schema) -> Vector ValErr
319+ runVals (_,v,ss) = join $ validate <$> ss <*> pure v
321+ leftovers :: Vector (Text, Value, Vector Schema) -> Value
322+ leftovers possiblyMatched =
323+ let unmatched = V.filter (\(_,_,ss) -> V.length ss == 0) possiblyMatched
324+ in Object . H.fromList . V.toList $ (\(v,k,_) -> (v,k)) <$> unmatched
326+patternProperties'' _ _ _ _ = Nothing
328+patternProperties :: ValidatorGen
329+patternProperties spec g s v = do
330+ when (H.member "properties" (_rsObject s)) Nothing
331+ let mPatProp = patternProperties'' spec g s v
332+ -- TODO: checking additionalProperties as well doesn't help with tests
334+ aVal <- H.lookup "additionalProperties" (_rsObject s)
335+ additionalProperties' spec g s aVal
336+ when (isNothing mPatProp && isNothing mAdd) Nothing
340+ let (e2s, remaining') = runMaybeVal' mPatProp (Object y)
341+ in e2s <> runMaybeVal mAdd remaining'
344+additionalProperties' :: ValidatorGen
345+additionalProperties' _ _ _ (Bool val) =
348+ (Object y) -> if val || H.size y == 0
350+ else V.singleton "TODO"
352+additionalProperties' spec g s (Object val) =
353+ let sub = compile spec g (RawSchema (_rsURI s) val)
356+ (Object y) -> (V.fromList . H.elems $ y) >>= validate sub -- TODO: optimize
358+additionalProperties' _ _ _ _ = Nothing
360+additionalProperties :: ValidatorGen
361+additionalProperties spec g s v = do
362+ when (H.member "properties" (_rsObject s)) Nothing
363+ when (H.member "patternProperties" (_rsObject s)) Nothing
364+ additionalProperties' spec g s v
368+-- http://json-schema.org/latest/json-schema-validation.html#anchor70
370+-- This keyword's value MUST be an object.
371+-- Each value of this object MUST be either an object or an array.
373+-- If the value is an object, it MUST be a valid JSON Schema.
374+-- This is called a schema dependency.
376+-- If the value is an array, it MUST have at least one element.
377+-- Each element MUST be a string, and elements in the array MUST be unique.
378+-- This is called a property dependency.
379+dependencies :: ValidatorGen
380+dependencies spec g s (Object val) = do
381+ let vs = V.fromList $ H.toList val
382+ let schemaDeps = vs >>= toSchemaDep spec g
383+ let propDeps = vs >>= toPropDep
384+ when (V.length schemaDeps + V.length propDeps /= V.length vs) Nothing
387+ (Object y) -> join $ (valSD <$> schemaDeps <*> pure y) <> (valPD <$> propDeps <*> pure y)
390+ toSchemaDep :: Spec -> Graph -> (Text, Value) -> Vector (Text, Schema)
391+ toSchemaDep spc gr (t, Object o) = V.singleton (t, compile spc gr $ RawSchema (_rsURI s) o)
392+ toSchemaDep _ _ _ = mempty
394+ toPropDep :: (Text, Value) -> Vector (Text, Vector Text)
395+ toPropDep (t, Array a) =
398+ else case traverse toTxt a of
402+ then V.singleton (t, ts)
404+ toPropDep _ = mempty
406+ valSD :: (Text, Schema) -> HashMap Text Value -> Vector ValErr
408+ case H.lookup t d of
410+ Just _ -> validate sub (Object d)
412+ valPD :: (Text, Vector Text) -> HashMap Text Value -> Vector ValErr
414+ case H.lookup t d of
417+ case traverse ($ d) (H.lookup <$> ts) of
418+ Nothing -> V.singleton "TODO at least one failed"
421+dependencies _ _ _ _ = Nothing
423+--------------------------------------------------
425+--------------------------------------------------
428+enum _ _ _ (Array vs) = do
429+ unless (V.length vs > 0 && allUnique vs) Nothing
433+ else V.singleton $ tshow x <> " is not an element of enum " <> tshow vs)
434+enum _ _ _ _ = Nothing
436+typeVal :: ValidatorGen
437+typeVal _ _ _ (String val) = Just (\x -> isJsonType x (V.singleton val))
438+typeVal _ _ _ (Array vs) = do
439+ ts <- traverse toTxt vs
440+ unless (allUnique ts) Nothing
441+ Just (`isJsonType` ts)
442+typeVal _ _ _ _ = Nothing
444+allOf :: ValidatorGen
445+allOf spec g s (Array vs) = do
446+ os <- traverse toObj vs
447+ let ss = compile spec g . RawSchema (_rsURI s) <$> os
448+ Just (\x -> join $ validate <$> ss <*> pure x)
449+allOf _ _ _ _ = Nothing
451+anyOf :: ValidatorGen
452+anyOf spec g s (Array vs) = do
453+ os <- traverse toObj vs
454+ let ss = compile spec g . RawSchema (_rsURI s) <$> os
456+ if V.elem V.empty (validate <$> ss <*> pure x)
458+ else V.singleton "TODO")
459+anyOf _ _ _ _ = Nothing
461+oneOf :: ValidatorGen
462+oneOf spec g s (Array vs) = do
463+ os <- traverse toObj vs
464+ let ss = compile spec g . RawSchema (_rsURI s) <$> os
466+ if count V.empty (validate <$> ss <*> pure x) == 1
468+ else V.singleton "TODO")
469+oneOf _ _ _ _ = Nothing
471+notValidator :: ValidatorGen
472+notValidator spec g s (Object val) = do
473+ let sub = compile spec g (RawSchema (_rsURI s) val)
475+ if V.null $ validate sub x
476+ then V.singleton "TODO"
478+notValidator _ _ _ _ = Nothing
480+-- http://tools.ietf.org/html/draft-pbryan-zyp-json-ref-03
482+-- TODO: Any members other than "$ref" in a JSON Reference object SHALL be
485+ref spec g s (String val) = do
486+ (reference, pointer) <- refAndP (_rsURI s `combineIdAndRef` val)
487+ r <- RawSchema reference <$> H.lookup reference g
488+ sc <- pointerToSchema pointer r
489+ Just $ validate (compile spec g sc)
491+ pointerToSchema :: Text -> RawSchema -> Maybe RawSchema
492+ pointerToSchema pntr rawS = do
493+ mVal <- jsonPointer pntr (Object . _rsObject $ rawS)
495+ (Object o) -> Just $ RawSchema (_rsURI rawS) o
497+ref _ _ _ _ = Nothing