packages feed

hjsonschema 0.5.3.2 → 0.6.0.0

raw patch · 7 files changed

+144/−113 lines, 7 filesPVP ok

version bump matches the API change (PVP)

API changes (from Hackage documentation)

- Data.JsonSchema: type Schema = Vector Validator
- Data.JsonSchema.Helpers: count :: Eq a => a -> Vector a -> Int
- Data.JsonSchema.Helpers: propertiesMatches :: Spec -> Graph -> RawSchema -> Value -> Maybe (Value -> (Vector ValErr, Value))
+ Data.JsonSchema: Schema :: [Validator] -> Schema
+ Data.JsonSchema: _unSchema :: Schema -> [Validator]
+ Data.JsonSchema: newtype Schema
+ Data.JsonSchema.Helpers: vLefts :: Vector (Either a b) -> Vector a
+ Data.JsonSchema.Helpers: vRights :: Vector (Either a b) -> Vector b
- Data.JsonSchema: isValidSchema :: RawSchema -> Vector ValErr
+ Data.JsonSchema: isValidSchema :: RawSchema -> Either (Vector ValErr) Value
- Data.JsonSchema: validate :: Schema -> Value -> Vector ValErr
+ Data.JsonSchema: validate :: Schema -> Value -> Either (Vector ValErr) Value

Files

changelog.txt view
@@ -1,3 +1,15 @@+# 0.6++Break the API so the library doesn't induce boolean blindness.++Change validate+  was: Schema -> Value -> Vector ValErr+  now: Schema -> Value -> Either (Vector ValErr) Value++Change Schema+  was: type Schema = Vector Validator+  now: newtype Schema = Schema { _unSchema :: [Validator] }+ # 0.5.3  + Switch from http-conduit to http-client.
hjsonschema.cabal view
@@ -1,5 +1,5 @@ name:                   hjsonschema-version:                0.5.3.2+version:                0.6.0.0 synopsis:               JSON Schema Draft 4 library homepage:               https://github.com/seagreen/hjsonschema license:                MIT
src/Data/JsonSchema.hs view
@@ -64,15 +64,11 @@ -- NOTE: It's not actually required to run 'isValidSchema' on -- prospective draft 4 schemas at all. However, it's a good way to -- catch unintentional mistakes in schema documents.-isValidSchema :: RawSchema -> Vector ValErr+isValidSchema :: RawSchema -> Either (Vector ValErr) Value isValidSchema r =   case decode . fromStrict $ $(embedFile "draft4.json") of-    Nothing -> V.singleton "Schema decode failed (this should never happen)"-    Just s  -> do-      let draft4Schema = RawSchema { _rsURI = "", _rsObject = s }-      validate-        (compile draft4 H.empty draft4Schema)-        (Object . _rsObject $ r)+    Nothing -> Left $ V.singleton "Schema decode failed (this should never happen)"+    Just s  -> validate (compile draft4 H.empty $ RawSchema "" s) $ Object (_rsObject r)  -- | Check that a 'RawSchema' conforms to the JSON Schema Draft 4 -- master schema document. Compile it if it does.@@ -80,11 +76,7 @@ -- This is just a convenience function built by combining -- 'isValidSchema' and 'compile'. compileDraft4 :: Graph -> RawSchema -> Either (Vector ValErr) Schema-compileDraft4 g r =-  let es = isValidSchema r-  in case V.length es of-    0 -> Right $ compile draft4 g r-    _ -> Left es+compileDraft4 g r = isValidSchema r >> return (compile draft4 g r)  -------------------------------------------------- -- * Graph Builder
src/Data/JsonSchema/Core.hs view
@@ -14,31 +14,32 @@ --------------------------------------------------  compile :: Spec -> Graph -> RawSchema -> Schema-compile spec g (RawSchema t o) =-  V.fromList . catMaybes . H.elems $ H.intersectionWith f (_unSpec spec) o+compile spec g (RawSchema t o) = Schema . catMaybes . H.elems $ H.intersectionWith f (_unSpec spec) o   where     f :: (ValidatorGen, a) -> Value -> Maybe Validator     f (vGen,_) = vGen spec g $ RawSchema (newResolutionScope t o) o -validate :: Schema -> Value -> Vector ValErr-validate s x = s >>= ($ x)+validate :: Schema -> Value -> Either (Vector ValErr) Value+validate schema x =+  let errs = V.concatMap ($ x) $ V.fromList (_unSchema schema)+  in if V.null errs+    then Right x+    else Left errs  -------------------------------------------------- -- * Types -------------------------------------------------- -newtype Spec = Spec-  { _unSpec :: HashMap Text (ValidatorGen, EmbeddedSchemas)-  }+newtype Spec = Spec { _unSpec :: HashMap Text (ValidatorGen, EmbeddedSchemas) }  -- | Set of potentially mutually recursive schemas. type Graph = HashMap Text (HashMap Text Value)  type ValErr = Text -type Validator = Value -> Vector ValErr+newtype Schema = Schema { _unSchema :: [Validator] } -type Schema = Vector Validator+type Validator = Value -> Vector ValErr  type ValidatorGen = Spec -> Graph -> RawSchema -> Value -> Maybe Validator 
src/Data/JsonSchema/Helpers.hs view
@@ -46,22 +46,6 @@ -- * Validator Helpers -------------------------------------------------- -propertiesMatches-  :: Spec-  -> Graph-  -> RawSchema-  -> Value-  -> Maybe (Value -> (Vector ValErr, Value))-propertiesMatches spec g s (Object val) = do-  os <- traverse toObj val-  let oss = compile spec g . RawSchema (_rsURI s) <$> os-  Just (\x ->-    case x of-      Object y -> ( join . vectorOfElems $ H.intersectionWith validate oss y-                  , Object $ H.difference y oss)-      z        -> (mempty, z))-propertiesMatches _ _ _ _ = Nothing- patternPropertiesMatches   :: Spec   -> Graph@@ -90,13 +74,12 @@         Just _  -> V.singleton sc      runVals :: (Text, Value, Vector Schema) -> Vector ValErr-    runVals (_,v,ss) = join $ validate <$> ss <*> pure v+    runVals (_,v,ss) = join . vLefts $ validate <$> ss <*> pure v      leftovers :: Vector (Text, Value, Vector Schema) -> Value     leftovers possiblyMatched =       let unmatched = V.filter (\(_,_,ss) -> V.length ss == 0) possiblyMatched       in Object . vectorToHm $ (\(v,k,_) -> (v,k)) <$> unmatched- patternPropertiesMatches _ _ _ _ = Nothing  isJsonType :: Value -> Vector Text -> Vector ValErr@@ -115,15 +98,31 @@                      else mkErr y xs   where     f :: (Show a) => Text -> Vector Text -> a -> Vector ValErr-    f t ts d = if V.elem t ts then mempty else mkErr d ts+    f t ts d = if V.elem t ts+                 then mempty+                 else mkErr d ts      mkErr :: (Show a) => a -> Vector Text -> Vector ValErr-    mkErr y ts = V.singleton $ tshow y <> " is not one of the types " <> tshow ts+    mkErr y ts = V.singleton (tshow y <> " is not one of the types " <> tshow ts)  -------------------------------------------------- -- * Utils -------------------------------------------------- +vLefts :: Vector (Either a b) -> Vector a+vLefts = V.concatMap f+  where+    f :: Either a b -> Vector a+    f (Left e)  = V.singleton e+    f (Right _) = mempty++vRights :: Vector (Either a b) -> Vector b+vRights = V.concatMap f+  where+    f :: Either a b -> Vector b+    f (Left _)  = mempty+    f (Right a) = V.singleton a+ eitherToMaybe :: Either a b -> Maybe b eitherToMaybe (Left _)  = Nothing eitherToMaybe (Right a) = Just a@@ -152,10 +151,6 @@ -- see here: http://comments.gmane.org/gmane.comp.lang.haskell.cafe/106242 allUnique :: (Eq a) => Vector a -> Bool allUnique bs = length (nub (V.toList bs)) == V.length bs---- TODO: optimize-count :: (Eq a) => a -> Vector a -> Int-count b bs = V.length $ V.filter (== b) bs  toObj :: Value -> Maybe (HashMap Text Value) toObj (Object a) = Just a
src/Data/JsonSchema/Validators.hs view
@@ -8,6 +8,7 @@ import           Control.Applicative import           Control.Monad import           Data.Aeson+import           Data.Either import           Data.Fixed                (mod') import           Data.Hashable import           Data.HashMap.Strict       (HashMap)@@ -18,6 +19,7 @@ import           Data.JsonSchema.Reference import           Data.Maybe import           Data.Monoid+import           Data.Scientific import           Data.Text                 (Text) import qualified Data.Text                 as T import           Data.Text.Encoding@@ -38,37 +40,43 @@     case x of       Number y ->         if y `mod'` val /= 0-          then V.singleton $ tshow y <> " isn't a multiple of " <> tshow val+          then V.singleton (tshow y <> " isn't a multiple of " <> tshow val)           else mempty       _ -> mempty) multipleOf _ _ _ _ = Nothing  maximumVal :: ValidatorGen maximumVal _ _ s (Number val) =-  let f = case H.lookup "exclusiveMaximum" (_rsObject s) of-            Just (Bool a) -> if a then (>=) else (>)-            _             -> (>)-  in Just (\x ->+  Just (\x ->     case x of       Number y ->-        if y `f` val-          then V.singleton $ tshow y <> " fails to validate against maximum " <> tshow val+        if y `greater` val+          then V.singleton (tshow y <> " fails to validate against maximum " <> tshow val)           else mempty       _ -> mempty)+  where+    greater :: Scientific -> Scientific -> Bool+    greater =+      case H.lookup "exclusiveMaximum" (_rsObject s) of+        Just (Bool a) -> if a then (>=) else (>)+        _             -> (>) maximumVal _ _ _ _ = Nothing  minimumVal :: ValidatorGen minimumVal _ _ s (Number val) =-  let f = case H.lookup "exclusiveMinimum" (_rsObject s) of-            Just (Bool a) -> if a then (<=) else (<)-            _             -> (<)-  in Just (\x ->+  Just (\x ->     case x of       Number y ->-        if y `f` val-          then V.singleton $ tshow y <> " fails to validate against minimum " <> tshow val+        if y `lesser` val+          then V.singleton (tshow y <> " fails to validate against minimum " <> tshow val)           else mempty       _ -> mempty)+  where+    lesser :: Scientific -> Scientific -> Bool+    lesser =+      case H.lookup "exclusiveMinimum" (_rsObject s) of+        Just (Bool a) -> if a then (<=) else (<)+        _             -> (<) minimumVal _ _ _ _ = Nothing  --------------------------------------------------@@ -83,7 +91,7 @@     case x of       String y ->         if T.length y > val-          then V.singleton $ y <> " is greater than maxLength " <> tshow val+          then V.singleton (y <> " is greater than maxLength " <> tshow val)           else mempty       _ -> mempty) @@ -95,7 +103,7 @@     case x of       String y ->         if T.length y < val-          then V.singleton $ y <> " is less than minLength " <> tshow val+          then V.singleton (y <> " is less than minLength " <> tshow val)           else mempty       _ -> mempty) @@ -105,7 +113,7 @@     case x of       String t ->         case matchRegexPR (T.unpack val) (T.unpack t) of-          Nothing -> V.singleton $ t <> " fails to validate against pattern " <> val+          Nothing -> V.singleton (t <> " fails to validate against pattern " <> val)           Just _  -> mempty       _ -> mempty) pattern _ _ _ _ = Nothing@@ -120,7 +128,7 @@   let sub = compile spec g (RawSchema (_rsURI s) val)   in Just (\x ->     case x of-      Array ys -> ys >>= validate sub+      Array ys -> ys >>= either id mempty . validate sub       _        -> mempty) items spec g s (Array vs) = do   os <- traverse toObj vs@@ -132,7 +140,8 @@     case x of       (Array ys) ->         let extras = V.drop (V.length os) ys-        in join (V.zipWith validate ss ys) <> runMaybeVal addItems (Array extras)+        in join (either id mempty <$> V.zipWith validate ss ys)+           <> runMaybeVal addItems (Array extras)       _ -> mempty) items _ _ _ _ = Nothing @@ -152,7 +161,7 @@   let sub = compile spec g (RawSchema (_rsURI s) val)   in Just (\x ->     case x of-      Array ys -> ys >>= validate sub+      Array ys -> ys >>= either id mempty . validate sub       _        -> mempty) additionalItems _ _ _ _ = Nothing @@ -164,7 +173,7 @@     case x of       Array ys ->         if V.length ys > val-          then V.singleton $ tshow ys <> " has more items than maxItems " <> tshow val+          then V.singleton (tshow ys <> " has more items than maxItems " <> tshow val)           else mempty       _ -> mempty) @@ -176,7 +185,7 @@     case x of       Array ys ->         if V.length ys < val-          then V.singleton $ tshow ys <> " has fewer items than minItems " <> tshow val+          then V.singleton (tshow ys <> " has fewer items than minItems " <> tshow val)           else mempty       _ -> mempty) @@ -203,7 +212,7 @@     case x of       Object o ->         if H.size o > val-          then V.singleton $ tshow o <> " has more members than maxProperties " <> tshow val+          then V.singleton (tshow o <> " has more members than maxProperties " <> tshow val)           else mempty       _ -> mempty) @@ -215,7 +224,7 @@     case x of       Object o ->         if H.size o < val-          then V.singleton $ tshow o <> " has fewer members than minProperties " <> tshow val+          then V.singleton (tshow o <> " has fewer members than minProperties " <> tshow val)           else mempty       _ -> mempty) @@ -229,8 +238,8 @@     case x of       Object o ->         if H.size (H.difference a o) > 0-          then V.singleton $ "the keys of " <> tshow o <>-            " don't contain all the required elements " <> tshow vs+          then V.singleton ("the keys of: " <> tshow o <>+                            " don't contain all the required elements: " <> tshow vs)           else mempty       _ -> mempty)   where@@ -244,7 +253,7 @@ -- In order of what's tried: properties, patternProperties, additionalProperties properties :: ValidatorGen properties spec g s v = do-  let mProps = propertiesMatches spec g s v+  let mProps = propertiesMatches v   let mPatProp = do                   aV <- H.lookup "patternProperties" (_rsObject s)                   patternPropertiesMatches spec g s aV@@ -260,6 +269,17 @@             (e2s, _) = runMaybeVal' mPatProp (Object y)         in e1s <> e2s <> runMaybeVal mAdd remaining'       _ -> mempty)+  where+    propertiesMatches :: Value -> Maybe (Value -> (Vector ValErr, Value))+    propertiesMatches (Object val) = do+      os <- traverse toObj val+      let oss = compile spec g . RawSchema (_rsURI s) <$> os+      Just (\x ->+        case x of+          Object y -> ( join . vectorOfElems $ either id mempty <$> H.intersectionWith validate oss y+                      , Object $ H.difference y oss)+          z        -> (mempty, z))+    propertiesMatches _ = Nothing  patternProperties :: ValidatorGen patternProperties spec g s v = do@@ -292,7 +312,7 @@   let sub = compile spec g (RawSchema (_rsURI s) val)   in Just (\x ->     case x of-      Object y -> vectorOfElems y >>= validate sub+      Object y -> vectorOfElems y >>= either id mempty . validate sub       _        -> mempty) runAdditionalProperties _ _ _ _ = Nothing @@ -304,25 +324,24 @@  -- http://json-schema.org/latest/json-schema-validation.html#anchor70 -----  This keyword's value MUST be an object.---  Each value of this object MUST be either an object or an array.------ If the value is an object, it MUST be a valid JSON Schema.--- This is called a schema dependency.------ If the value is an array, it MUST have at least one element.--- Each element MUST be a string, and elements in the array MUST be unique.--- This is called a property dependency.+-- > This keyword's value MUST be an object.+-- > Each value of this object MUST be either an object or an array.+-- >+-- > If the value is an object, it MUST be a valid JSON Schema.+-- > This is called a schema dependency.+-- >+-- > If the value is an array, it MUST have at least one element.+-- > Each element MUST be a string, and elements in the array MUST be unique.+-- > This is called a property dependency. dependencies :: ValidatorGen dependencies spec g s (Object val) = do   let vs = hmToVector val-  let schemaDeps = vs >>= toSchemaDep spec g-  let propDeps = vs >>= toPropDep+      schemaDeps = vs >>= toSchemaDep spec g+      propDeps = vs >>= toPropDep   when (V.length schemaDeps + V.length propDeps /= V.length vs) Nothing   Just (\x ->     case x of-      Object y -> join $ (valSD <$> schemaDeps <*> pure y)-                         <> (valPD <$> propDeps <*> pure y)+      Object y -> join $ (valSD <$> schemaDeps <*> pure y) <> (valPD <$> propDeps <*> pure y)       _        -> mempty)   where     toSchemaDep :: Spec -> Graph -> (Text, Value) -> Vector (Text, Schema)@@ -346,7 +365,7 @@     valSD (t, sub) d =       case H.lookup t d of         Nothing -> mempty-        Just _  -> validate sub (Object d)+        Just _  -> either id (const mempty) $ validate sub (Object d)      valPD :: (Text, Vector Text) -> HashMap Text Value -> Vector ValErr     valPD (t, ts) d =@@ -354,9 +373,8 @@         Nothing -> mempty         Just _  ->           case traverse ($ d) (H.lookup <$> ts) of-            Nothing -> V.singleton-              ("Val error against property dependency with the key "-               <> t <> " and the value " <> tshow ts <> " for: " <> tshow d)+            Nothing -> V.singleton ("Val error against property dependency with key: " <> t+                                    <> " and value " <> tshow ts <> " for: " <> tshow d)             Just _  -> mempty  dependencies _ _ _ _ = Nothing@@ -365,13 +383,23 @@ -- * Any Validators -------------------------------------------------- +-- | http://json-schema.org/latest/json-schema-validation.html#anchor76+--+--  > The value of this keyword MUST be an array.+--  > This array MUST have at least one element.+--  > Elements in the array MUST be unique.+--  >+--  > Elements in the array MAY be of any type, including null.+--+-- NOTE: We actually respect this, and don't build the validator+-- if any of the elements aren't unique. enum :: ValidatorGen enum _ _ _ (Array vs) = do-  unless (V.length vs > 0 && allUnique vs) Nothing+  when (V.null vs || not (allUnique vs)) Nothing   Just (\x ->     if V.elem x vs       then mempty-      else V.singleton $ tshow x <> " is not an element of enum " <> tshow vs)+      else V.singleton (tshow x <> " is not an element of enum " <> tshow vs)) enum _ _ _ _ = Nothing  typeVal :: ValidatorGen@@ -386,7 +414,7 @@ allOf spec g s (Array vs) = do   os <- traverse toObj vs   let ss = compile spec g . RawSchema (_rsURI s) <$> os-  Just (\x -> join $ validate <$> ss <*> pure x)+  Just (\x -> join . vLefts $ validate <$> ss <*> pure x) allOf _ _ _ _ = Nothing  anyOf :: ValidatorGen@@ -394,9 +422,9 @@   os <- traverse toObj vs   let ss = compile spec g . RawSchema (_rsURI s) <$> os   Just (\x ->-    if V.elem V.empty (validate <$> ss <*> pure x)-      then mempty-      else V.singleton ("Val error against anyOf " <> tshow vs <> " for: " <> tshow x))+    case listToMaybe . rights $ validate <$> V.toList ss <*> pure x of+      Nothing -> V.singleton ("Val error against anyOf " <> tshow vs <> " for: " <> tshow x)+      Just _  -> mempty ) anyOf _ _ _ _ = Nothing  oneOf :: ValidatorGen@@ -404,7 +432,7 @@   os <- traverse toObj vs   let ss = compile spec g . RawSchema (_rsURI s) <$> os   Just (\x ->-    if count V.empty (validate <$> ss <*> pure x) == 1+    if V.length (vRights $ validate <$> ss <*> pure x) == 1       then mempty       else V.singleton ("Val error against oneOf " <> tshow vs <> " for: " <> tshow x)) oneOf _ _ _ _ = Nothing@@ -413,9 +441,10 @@ notValidator spec g s (Object val) = do   let sub = compile spec g (RawSchema (_rsURI s) val)   Just (\x ->-    if V.null $ validate sub x-      then V.singleton ("Val error against not validator " <> tshow val <> " for: " <> tshow x)-      else mempty)+    case validate sub x of+      Left _  -> mempty+      Right _ -> V.singleton ("Val error against not validator " <> tshow val+                              <> " for: " <> tshow x)) notValidator _ _ _ _ = Nothing  -- http://tools.ietf.org/html/draft-pbryan-zyp-json-ref-03@@ -430,9 +459,10 @@   p <- eitherToMaybe $ jsonPointer urlDecoded   case resolvePointer p (Object $ _rsObject r) of     Right (Object o) ->-      Just $ validate $ compile spec g $ RawSchema (_rsURI r) o+      let compiled = compile spec g $ RawSchema (_rsURI r) o+      in Just $ either id (const mempty) . validate compiled     _                -> Nothing ref _ _ _ _ = Nothing  noVal :: ValidatorGen-noVal _ _ _ _ = Just (const V.empty)+noVal _ _ _ _ = Just (const mempty)
tests/Lib.hs view
@@ -15,7 +15,6 @@ import           Data.Text                      (Text) import qualified Data.Text                      as T import           Data.Vector                    (Vector)-import qualified Data.Vector                    as V import           System.FilePath                ((</>)) import           Test.Framework                 (Test) import           Test.Framework.Providers.HUnit (testCase)@@ -71,30 +70,32 @@ toTest :: SchemaTest -> Test toTest st =   testCase (T.unpack $ _stDescription st) $ do-    assertEqual "schema validity errors" V.empty $ isValidSchema (_stSchema st)+    void . assertRight . isValidSchema $ _stSchema st     forM_ (_stCases st) $ \sc -> do       g <- assertRight =<< fetchRefs draft4 (_stSchema st) H.empty-      let es = validate (compile draft4 g $ _stSchema st) (_scData sc)+      let res = validate (compile draft4 g $ _stSchema st) (_scData sc)       if _scValid sc-        then assertValid   sc es-        else assertInvalid sc es+        then assertValid   sc res+        else assertInvalid sc res -assertValid :: SchemaTestCase -> Vector ValErr -> Assertion-assertValid sc es =-  unless (V.length es == 0) $ assertFailure $ unlines+assertValid :: SchemaTestCase -> Either (Vector ValErr) Value -> Assertion+assertValid sc (Left es) =+  assertFailure $ unlines     [ "    Failed to validate data"     , "    Description: " <> T.unpack (_scDescription sc)     , "    Data: "        <> show (_scData sc)     , "    Errors: "      <> show es     ]+assertValid _ _ = return () -assertInvalid :: SchemaTestCase -> Vector ValErr -> Assertion-assertInvalid sc es =-  unless (V.length es > 0) $ assertFailure $ unlines+assertInvalid :: SchemaTestCase -> Either (Vector ValErr) Value -> Assertion+assertInvalid sc (Right _) =+  assertFailure $ unlines     [ "    Failed to invalidate data"     , "    Description: " <> T.unpack (_scDescription sc)     , "    Data: "        <> show (_scData sc)     ]+assertInvalid _ _ = return ()  assertRight :: (Show a) => Either a b -> IO b assertRight a =