json-schema-0.7.4.2: src/Data/JSON/Schema/Validate.hs
{-# LANGUAGE
GeneralizedNewtypeDeriving
, ScopedTypeVariables
#-}
module Data.JSON.Schema.Validate
( isValid
, validate
, ValidationError (..)
, ErrorType (..)
) where
import Prelude.Compat
import Control.Monad.Compat
import Control.Monad.RWS.Strict (MonadReader, MonadWriter, RWS, ask, local, runRWS, tell)
import Data.Aeson (Value)
import Data.HashMap.Strict (HashMap)
import Data.Scientific
import Data.Text (Text)
import Data.Vector (Vector)
import qualified Data.Aeson as A
import qualified Data.HashMap.Strict as H
import qualified Data.Text as T
import qualified Data.Vector as V
import Data.JSON.Schema (Schema)
import qualified Data.JSON.Schema as S
-- | Validates a value against a schema returning errors.
validate :: Schema -> Value -> Vector ValidationError
validate s v = (\(_,_,errs) -> errs) $ runRWS (unM $ validate' s v) V.empty ()
-- | Predicate version of 'validate'.
isValid :: Schema -> Value -> Bool
isValid s v = V.null $ validate s v
data ValidationError = ValidationError
{ path :: Vector Text -- ^ The Path to the property where the error occured, empty if the error is on the top level.
, errorType :: ErrorType
} deriving (Eq, Show)
data ErrorType
= Mismatch Schema Value -- ^ General type error.
| BoundError S.Bound Scientific -- ^ Number out of bounds.
| LengthBoundError S.LengthBound Int -- ^ String or Array out of bounds.
| TupleLength Int Int -- ^ Expected and actual tuple length.
| MissingRequiredField Text -- ^ A required field is missing.
| ChoiceError (Vector (Vector ValidationError)) Value -- ^ All choices failed, contains the error of each branch.
| NonUniqueArray (HashMap Value Int) -- ^ The elements in the array that are duplicated with the number of occurences (at least 2).
deriving (Eq, Show)
newtype M a = M { unM :: RWS (Vector Text) (Vector ValidationError) () a }
deriving
( Functor
, Applicative
, Monad
, MonadWriter (Vector ValidationError)
, MonadReader (Vector Text)
)
ok :: M ()
ok = return ()
err :: ErrorType -> M ()
err e = do
pth <- ask
tell . V.singleton . ValidationError pth $ e
cond :: ErrorType -> Bool -> M ()
cond e p = if p then ok else err e
nestPath :: Text -> M a -> M a
nestPath p = local (`V.snoc` p)
validate' :: Schema -> Value -> M ()
validate' sch val = case (sch, val) of
( S.Any , _ ) -> ok
( S.Boolean , A.Bool{} ) -> ok
( S.Constant x, _ ) -> cond (Mismatch sch val) (x == val)
( S.Number b, A.Number n ) ->
do inLower b n
inUpper b n
( S.Tuple xs, A.Array vs ) ->
do let vlen = V.length vs
let xlen = length xs
cond (TupleLength xlen vlen) (xlen == vlen)
sequence_ $ zipWith3
(\i s -> nestPath (T.pack (show i)) . validate' s)
[(0::Int)..] xs (V.toList vs)
( S.Map x, A.Object h ) ->
do let kvs = H.toList h
mapM_ (\(k,v) -> nestPath k $ validate' x v) kvs
( S.Object fs, A.Object h ) -> mapM_ (`validateField` h) fs
( S.Choice s, _ ) ->
do let errs = map (`validate` val) s
if any V.null errs
then ok
else err $ ChoiceError (V.fromList errs) val
( S.Value b, A.String w ) ->
do inLowerLength b (T.length w)
inUpperLength b (T.length w)
( S.Array b u s, A.Array vs) ->
do inLowerLength b (V.length vs)
inUpperLength b (V.length vs)
if u then unique vs else ok
zipWithM_
(\i -> nestPath (T.pack (show i)) . validate' s)
[(0::Int)..] (V.toList vs)
( S.Boolean {}, _ ) -> err $ Mismatch sch val
( S.Number {}, _ ) -> err $ Mismatch sch val
( S.Tuple {}, _ ) -> err $ Mismatch sch val
( S.Object {}, _ ) -> err $ Mismatch sch val
( S.Map {}, _ ) -> err $ Mismatch sch val
( S.Value {}, _ ) -> err $ Mismatch sch val
( S.Array {}, _ ) -> err $ Mismatch sch val
validateField :: S.Field -> A.Object -> M ()
validateField f o = maybe req (nestPath (S.key f) . validate' (S.content f)) $ H.lookup (S.key f) o
where
req | not (S.required f) = ok
| otherwise = err $ MissingRequiredField (S.key f)
unique :: Vector Value -> M ()
unique vs = do
let dups = H.filter (>= 2) . V.foldl' (\h v -> H.insertWith (+) v 1 h) H.empty $ vs
unless (H.null dups) $
err (NonUniqueArray dups)
inLower :: S.Bound -> Scientific -> M ()
inLower b v =
if maybe True ((<= v) . fromIntegral) . S.lower $ b
then ok
else err (BoundError b v)
inUpper :: S.Bound -> Scientific -> M ()
inUpper b v =
if maybe True ((>= v) . fromIntegral) . S.upper $ b
then ok
else err (BoundError b v)
inLowerLength :: S.LengthBound -> Int -> M ()
inLowerLength b v =
if maybe True (<= v) . S.lowerLength $ b
then ok
else err (LengthBoundError b v)
inUpperLength :: S.LengthBound -> Int -> M ()
inUpperLength b v =
if maybe True (>= v) . S.upperLength $ b
then ok
else err (LengthBoundError b v)