aeson-schema 0.3.0.1 → 0.3.0.2
raw patch · 26 files changed
+1943/−1 lines, 26 filesPVP ok
version bump matches the API change (PVP)
API changes (from Hackage documentation)
Files
- aeson-schema.cabal +10/−1
- test/Data/Aeson/LitQQ.hs +24/−0
- test/Data/Aeson/Schema/Choice/Tests.hs +62/−0
- test/Data/Aeson/Schema/CodeGen/Tests.hs +359/−0
- test/Data/Aeson/Schema/Examples.hs +188/−0
- test/Data/Aeson/Schema/Types/Tests.hs +49/−0
- test/Data/Aeson/Schema/Validator/Tests.hs +48/−0
- test/TestSuite/Types.hs +64/−0
- test/test-suite/tests/draft3/additionalItems.json +67/−0
- test/test-suite/tests/draft3/additionalProperties.json +58/−0
- test/test-suite/tests/draft3/divisibleBy.json +23/−0
- test/test-suite/tests/draft3/enum.json +39/−0
- test/test-suite/tests/draft3/items.json +41/−0
- test/test-suite/tests/draft3/maxItems.json +23/−0
- test/test-suite/tests/draft3/maxLength.json +23/−0
- test/test-suite/tests/draft3/maximum.json +37/−0
- test/test-suite/tests/draft3/minItems.json +23/−0
- test/test-suite/tests/draft3/minLength.json +23/−0
- test/test-suite/tests/draft3/minimum.json +37/−0
- test/test-suite/tests/draft3/optional/format.json +18/−0
- test/test-suite/tests/draft3/pattern.json +18/−0
- test/test-suite/tests/draft3/patternProperties.json +74/−0
- test/test-suite/tests/draft3/properties.json +33/−0
- test/test-suite/tests/draft3/required.json +53/−0
- test/test-suite/tests/draft3/type.json +474/−0
- test/test-suite/tests/draft3/uniqueItems.json +75/−0
aeson-schema.cabal view
@@ -1,5 +1,5 @@ name: aeson-schema-version: 0.3.0.1+version: 0.3.0.2 synopsis: Haskell JSON schema validator and parser generator -- description: homepage: https://github.com/timjb/aeson-schema@@ -10,6 +10,8 @@ category: Data build-type: Simple cabal-version: >=1.8+data-files: test/test-suite/tests/draft3/optional/*.json,+ test/test-suite/tests/draft3/*.json source-repository head type: git@@ -52,6 +54,13 @@ hs-source-dirs: test type: exitcode-stdio-1.0 main-is: TestSuite.hs+ other-modules: TestSuite.Types,+ Data.Aeson.LitQQ,+ Data.Aeson.Schema.Examples,+ Data.Aeson.Schema.Choice.Tests,+ Data.Aeson.Schema.CodeGen.Tests,+ Data.Aeson.Schema.Types.Tests,+ Data.Aeson.Schema.Validator.Tests extensions: OverloadedStrings build-depends: base, aeson,
+ test/Data/Aeson/LitQQ.hs view
@@ -0,0 +1,24 @@+{-# OPTIONS_GHC -fno-warn-missing-fields #-}++module Data.Aeson.LitQQ+ ( aesonLitQQ+ ) where++import Control.Applicative ((*>), (<*))+import Data.Aeson.Parser (value')+import Data.Attoparsec.ByteString.Char8 (skipSpace)+import Data.Attoparsec.Lazy (Result (..), parse)+import Data.ByteString.Lazy.Char8 (pack)+import Language.Haskell.TH (ExpQ)+import Language.Haskell.TH.Quote (QuasiQuoter (..))+import Language.Haskell.TH.Syntax (lift)++import Data.Aeson.Schema.CodeGen++aesonLitQQ :: QuasiQuoter+aesonLitQQ = QuasiQuoter { quoteExp = aesonLit }++aesonLit :: String -> ExpQ+aesonLit jsonStr = case parse (skipSpace *> value' <* skipSpace) (pack jsonStr) of+ Done _ json -> lift json+ _ -> fail "not a valid JSON value"
+ test/Data/Aeson/Schema/Choice/Tests.hs view
@@ -0,0 +1,62 @@+{-# OPTIONS_GHC -fno-warn-unused-binds #-}++module Data.Aeson.Schema.Choice.Tests+ ( tests+ ) where++import Test.Framework+import Test.Framework.Providers.HUnit+import Test.Framework.Providers.QuickCheck2+import qualified Test.HUnit as HU+import Test.QuickCheck (Gen, arbitrary, vectorOf)++import Data.Aeson+import Data.Char (toUpper)+import Data.List (nub)+import Data.Vector (fromList)++import Data.Aeson.Schema.Choice++choiceShow3 :: Choice3 String Int Bool -> String+choiceShow3 (Choice1of3 s) = s+choiceShow3 (Choice2of3 i) = show i+choiceShow3 (Choice3of3 b) = if b then "yes" else "no"++choiceEqual3 :: (Eq a, Eq b, Eq c) => Choice3 a b c -> Choice3 a b c -> Bool+choiceEqual3 = (==)++choiceOrd3 :: (Ord a, Ord b, Ord c) => Choice3 a b c -> Choice3 a b c -> Ordering+choiceOrd3 = compare++choiceShow7 :: (Show a, Show b, Show c, Show d) => Choice7 String Int Bool a b c d -> String+choiceShow7 (Choice1of7 s) = s+choiceShow7 (Choice2of7 i) = show i+choiceShow7 (Choice3of7 b) = if b then "yes" else "no"+choiceShow7 c = show c++tests :: [Test]+tests =+ [ testCase "parseJSON" $ do+ Success (Choice1of3 3 :: Choice3 Int String [Bool]) HU.@=? fromJSON (Number 3)+ Success (Choice2of3 "lorem" :: Choice3 Int String [Bool]) HU.@=? fromJSON (String "lorem")+ Success (Choice3of3 [True,False,True] :: Choice3 Int String [Bool]) HU.@=? fromJSON (Array . fromList . map Bool $ [True,False,True])+ , testCase "toJSON" $ do+ object [ "Left" .= String "a" ] HU.@=? toJSON (Choice1of3 (Left 'a') :: Choice3 (Either Char Bool) () (Maybe String))+ Array (fromList []) HU.@=? toJSON (Choice2of3 () :: Choice3 (Either Char Bool) () (Maybe String))+ Null HU.@=? toJSON (Choice3of3 Nothing :: Choice3 (Either Char Bool) () (Maybe String))+ , testProperty "arbitrary" $ do+ xs <- vectorOf 100 (arbitrary :: Gen (Choice3 () () ()))+ return $ length (nub xs) == 3+ , testCase "choice3" $ do+ let rnd = round :: Double -> Int+ (3 :: Int) HU.@=? choice3 length id rnd (Choice1of3 ("abc" :: String))+ (4 :: Int) HU.@=? choice3 length id rnd (Choice2of3 4)+ (5 :: Int) HU.@=? choice3 length id rnd (Choice3of3 (4.6 :: Double))+ , testCase "mapChoice3" $ do+ let plus1 = (+1) :: Int -> Int+ Choice1of3 "LOREM" HU.@=? mapChoice3 (map toUpper) plus1 not (Choice1of3 "lorem")+ Choice2of3 4 HU.@=? mapChoice3 (map toUpper) plus1 not (Choice2of3 (3 :: Int))+ Choice3of3 True HU.@=? mapChoice3 (map toUpper) plus1 not (Choice3of3 False)+ , testCase "choice2of3s" $ do+ ["eins" :: String, "zwei", "drei"] HU.@=? choice2of3s [Choice1of3 True, Choice2of3 "eins", Choice2of3 "zwei", Choice3of3 '?', Choice2of3 "drei"]+ ]
+ test/Data/Aeson/Schema/CodeGen/Tests.hs view
@@ -0,0 +1,359 @@+{-# OPTIONS_GHC -fno-warn-orphans #-}+{-# LANGUAGE ExistentialQuantification #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE ImpredicativeTypes #-}+{-# LANGUAGE QuasiQuotes #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TupleSections #-}++module Data.Aeson.Schema.CodeGen.Tests+ ( tests+ ) where++import Test.Framework+import Test.Framework.Providers.HUnit+import Test.Framework.Providers.QuickCheck2+import qualified Test.HUnit as HU+import Test.QuickCheck hiding (Result (..))+import Test.QuickCheck.Property (Result (..), failed,+ ioProperty, succeeded)++import Control.Applicative (pure, (<$>), (<*>))+import Control.Concurrent (forkIO)+import Control.Concurrent.Chan (Chan, newChan, readChan,+ writeChan)+import Control.Concurrent.MVar (newEmptyMVar, putMVar,+ takeMVar)+import Control.Monad (forever, liftM2, (>=>))+import Control.Monad.Trans (liftIO)+import Data.Aeson (Value (..))+import Data.Char (isAscii, isPrint)+import Data.Monoid+import Data.Hashable (Hashable)+import qualified Data.HashMap.Lazy as HM+import qualified Data.Map as M+import Data.Maybe (isNothing, listToMaybe)+import Data.Scientific (Scientific)+import Data.Text (Text, pack, unpack)+import qualified Data.Text as T+import qualified Data.Text.IO as TIO+import qualified Data.Vector as V+import qualified Language.Haskell.Interpreter as Hint+import qualified Language.Haskell.Interpreter.Unsafe as Hint+import Language.Haskell.TH (runQ)+import Language.Haskell.TH.Ppr (pprint)+import qualified Language.Haskell.TH.Syntax as THS+import System.IO (hClose)+import System.IO.Temp (withSystemTempFile)++import Data.Aeson.Schema+import Data.Aeson.Schema.Choice+import Data.Aeson.Schema.CodeGen (generateModule)+import Data.Aeson.Schema.Helpers (formatValidators,+ getUsedModules,+ replaceHiddenModules)++import Data.Aeson.Schema.Examples (examples)+import TestSuite.Types (SchemaTest (..),+ SchemaTestCase (..))+import qualified System.Directory as DIR+import qualified System.FilePath as FP++instance Arbitrary Text where+ arbitrary = pack <$> arbitrary++instance (Eq k, Hashable k, Arbitrary k, Arbitrary v) => Arbitrary (HM.HashMap k v) where+ arbitrary = HM.fromList <$> arbitrary++instance Arbitrary Scientific where+ arbitrary = fromRational <$> arbitrary++instance (Arbitrary a) => Arbitrary (V.Vector a) where+ arbitrary = V.fromList <$> arbitrary++instance Arbitrary Pattern where+ -- TODO: improve performance+ arbitrary = do+ arbitraryString <- arbitrary+ case mkPattern arbitraryString of+ Just p -> return p+ Nothing -> arbitrary++arbitraryValue :: Int -> Gen Value+arbitraryValue 0 = return Null+arbitraryValue i = oneof+ [ Object . HM.fromList <$> shortListOf ((,) <$> arbitrary <*> subValue)+ , Array . V.fromList <$> shortListOf subValue+ , String <$> arbitrary+ , Number <$> arbitrary+ , Bool <$> arbitrary+ , pure Null+ ]+ where+ subValue = arbitraryValue (i-1)+ shortListOf = fmap (take 3) . listOf+++instance Arbitrary Value where+ arbitrary = arbitraryValue 3++instance Arbitrary SchemaType where+ arbitrary = elements [minBound..maxBound]++arbitrarySchema :: (Eq a) => Int -> Gen (Schema a)+arbitrarySchema 0 = return empty+arbitrarySchema depth = do+ typ <- shortListOf1 (choice2of arbitrary subSchema)+ required <- arbitrary+ disallow <- rareShortListOf (choice2of arbitrary subSchema)+ extends <- rareShortListOf subSchema+ description <- arbitrary+ let sch0 = empty+ { schemaType = typ+ , schemaRequired = required+ , schemaDisallow = disallow+ , schemaExtends = extends+ , schemaDescription = description+ }+ sch1 <- flip (foldl (>=>) return) sch0+ [ modifyIf (Choice1of2 ArrayType `elem` typ) $ \sch -> do+ items <- maybeOf (choice2of subSchema $ shortListOf1 subSchema)+ additionalItems <- choice2of arbitrary subSchema+ minItems <- abs <$> arbitrary+ maxItems <- fmap ((+ minItems) . abs) <$> arbitrary+ uniqueItems <- arbitrary+ return $ sch+ { schemaItems = items+ , schemaAdditionalItems = additionalItems+ , schemaMinItems = minItems+ , schemaMaxItems = maxItems+ , schemaUniqueItems = uniqueItems+ }+ , modifyIf (Choice1of2 ObjectType `elem` typ) $ \sch -> do+ properties <- smallMapOf (T.filter (\c -> isPrint c && isAscii c) <$> arbitrary) subSchema+ patternProperties <- shortListOf (tupleOf arbitrary subSchema)+ additionalProperties <- choice2of arbitrary subSchema+ dependencies <- smallMapOf arbitrary (choice2of arbitrary subSchema)+ return $ sch+ { schemaProperties = properties+ , schemaPatternProperties = patternProperties+ , schemaAdditionalProperties = additionalProperties+ , schemaDependencies = dependencies+ }+ , modifyIf (Choice1of2 StringType `elem` typ) $ \sch -> do+ minLength <- abs <$> arbitrary+ maxLength <- fmap ((+ minLength) . abs) <$> arbitrary+ pattern <- arbitrary+ format <- maybeOf (oneof formats)+ return $ sch+ { schemaMinLength = minLength+ , schemaMaxLength = maxLength+ , schemaPattern = pattern+ , schemaFormat = format+ }+ , modifyIf (Choice1of2 NumberType `elem` typ || Choice1of2 IntegerType `elem` typ) $ \sch -> do+ sMinimum <- arbitrary+ sMaximum <- case sMinimum of+ Nothing -> arbitrary+ Just mini -> fmap ((+ mini) . abs) <$> arbitrary+ exclusiveMinimum <- if isNothing sMinimum then return False else arbitrary+ exclusiveMaximum <- if isNothing sMaximum then return False else arbitrary+ divisibleBy <- arbitrary+ return $ sch+ { schemaMinimum = sMinimum+ , schemaMaximum = sMaximum+ , schemaExclusiveMinimum = exclusiveMinimum+ , schemaExclusiveMaximum = exclusiveMaximum+ , schemaDivisibleBy = divisibleBy+ }++ ]+ -- the enum and default values are probibly not compatible with the schema+ -- but that's irrelevant since we're only interested if the generated code+ -- typechecks+ enum <- maybeOf (shortListOf arbitrary)+ dflt <- maybeOf $ case enum of+ Just vs@(_:_) -> elements vs+ _ -> arbitrary+ return $ sch1 { schemaEnum = enum, schemaDefault = dflt }+ where+ modifyIf :: (Monad m) => Bool -> (a -> m a) -> a -> m a+ modifyIf False _ = return+ modifyIf True a = a+ subSchema = arbitrarySchema (depth-1)+ formats = map (pure . fst) formatValidators+ maybeOf = fmap listToMaybe . listOf+ shortListOf = fmap (take 2) . listOf+ rareShortListOf a = frequency [(2, return []), (1, shortListOf a)]+ shortListOf1 = fmap (take 2) . listOf1+ choice2of a b = oneof [Choice1of2 <$> a, Choice2of2 <$> b]+ tupleOf = liftM2 (,)+ smallMapOf k v = HM.fromList <$> shortListOf (tupleOf k v)++instance (Eq a) => Arbitrary (Schema a) where+ arbitrary = arbitrarySchema 3++data ForkLift = ForkLift (Chan (Hint.Interpreter (), Hint.InterpreterError -> IO ()))++getSandboxPackageDB :: IO (Maybe FilePath) +getSandboxPackageDB = do+ cwd <- DIR.getCurrentDirectory+ contents <- readFile $ cwd <> [FP.pathSeparator] <> "cabal.sandbox.config"+ return $ findPackageDB contents+ where+ packagedb = "package-db: "+ maybePackageDB :: [FilePath] -> Maybe FilePath+ maybePackageDB list = case list of + [path] -> Just $ T.unpack $ T.replace packagedb "" (T.pack path)+ _ -> Nothing+ findPackageDB contents = maybePackageDB $ filter (\l -> T.isPrefixOf packagedb (T.pack l)) (lines contents)++getInterpreterArgs :: IO [String]+getInterpreterArgs = do+ mdb <- getSandboxPackageDB+ return $ case mdb of+ Just path -> ["-no-user-package-db", "-package-db " <> path] + Nothing -> []++-- | uses the Forklift pattern (http://apfelmus.nfshost.com/blog/2012/06/07-forklift.html)+-- to send commands to an interpreter running in a different thread+startInterpreterThread :: IO ForkLift+startInterpreterThread = do+ cmdChan <- newChan+ _ <- forkIO $ do+ errorHandler <- newEmptyMVar+ args <- getInterpreterArgs ++ forever $ do+ Left err <- Hint.unsafeRunInterpreterWithArgs args $ do+ forever $ do+ (action, handler) <- liftIO $ readChan cmdChan+ liftIO $ putMVar errorHandler handler+ action+ liftIO $ takeMVar errorHandler+ handler <- takeMVar errorHandler+ handler err+ return $ ForkLift cmdChan++carry :: ForkLift -> Hint.Interpreter a -> IO (Either Hint.InterpreterError a)+carry (ForkLift cmdChan) action = do+ result <- newEmptyMVar+ let successHandler = action >>= liftIO . putMVar result . Right+ errorHandler = putMVar result . Left+ writeChan cmdChan (successHandler, errorHandler)+ takeMVar result++tests :: [SchemaTest] -> IO [Test]+tests schemaTests = do+ forkLift <- startInterpreterThread+ return+ [ testProperty "generated code typechecks" $ typecheckGenerate forkLift+ , testGroup "examples" $ testExamples forkLift+ , testGroup "JSON-Schema-Test-Suite" $ map (execSchemaTest forkLift) schemaTests+ , testCase "1-tuple" $ do+ let+ schema = empty+ { schemaType = [Choice1of2 ArrayType]+ , schemaItems = Just $ Choice2of2 [empty { schemaType = [Choice1of2 NumberType] }]+ }+ graph = M.singleton "A" schema+ (code, _) <- runQ $ generateModule "TestOneTuple" graph+ result <- typecheck code forkLift+ case result of+ Left err -> HU.assertFailure $ show err+ Right _ -> return ()+ , testCase "simple map" $ do+ let+ schema = [schemaQQ| {+ "type": "object",+ "additionalProperties": { "type": "number" }+ } |]+ graph = M.singleton "A" schema+ (code, _) <- runQ $ generateModule "SimpleMap" graph+ result <- typecheck code forkLift+ case result of+ Left err -> HU.assertFailure $ show err+ Right _ -> return ()+ ]++typecheckGenerate :: ForkLift -> Schema Text -> Property+typecheckGenerate forkLift schema = ioProperty $ do+ let graph = M.singleton "A" schema+ (code, _) <- runQ $ generateModule "CustomSchema" graph+ eitherToResult <$> typecheck code forkLift++withCodeTempFile :: Text -> (FilePath -> IO a) -> IO a+withCodeTempFile code action = case maybeName of+ Nothing -> fail "couldn't find module name"+ Just name -> withSystemTempFile (unpack name ++ ".hs") $ \path handle -> do+ TIO.hPutStrLn handle code+ hClose handle+ action path+ where+ maybeName = findName (T.lines code)+ findName (l:ls) = case T.words l of+ ("module":n:_) -> Just n+ _ -> findName ls+ findName _ = Nothing++eitherToResult :: Show err => Either err a -> Result+eitherToResult (Left err) = failed { reason = show err }+eitherToResult (Right _) = succeeded++typecheck :: Text -> ForkLift -> IO (Either Hint.InterpreterError ())+typecheck code forkLift = withCodeTempFile code $ \path ->+ carry forkLift $ Hint.loadModules [path]++testExamples :: ForkLift -> [Test]+testExamples forkLift = examples testCase (assertValid forkLift) (assertInvalid forkLift)++execSchemaTest :: ForkLift -> SchemaTest -> Test+execSchemaTest forkLift schemaTest = testGroup testName testCases+ where+ testName = unpack $ schemaTestDescription schemaTest+ testCases = map execSchemaTestCase $ schemaTestCases schemaTest+ schema = schemaTestSchema schemaTest+ execSchemaTestCase schemaTestCase = testCase name assertion+ where+ name = unpack $ schemaTestCaseDescription schemaTestCase+ shouldBeValid = schemaTestCaseValid schemaTestCase+ testData = schemaTestCaseData schemaTestCase+ assertion = assertValidates shouldBeValid forkLift M.empty schema testData++assertValid, assertInvalid :: ForkLift -> Graph Schema Text -> Schema Text -> Value -> HU.Assertion+assertValid = assertValidates True+assertInvalid = assertValidates False++assertValidates :: Bool -> ForkLift -> Graph Schema Text -> Schema Text -> Value -> HU.Assertion+assertValidates shouldBeValid forkLift graph schema value = do+ let graph' = if M.null graph then M.singleton "a" schema else graph+ (code, typeMap) <- runQ $ generateModule "TestSchema" graph'+ valueExpr <- replaceHiddenModules <$> runQ (THS.lift value)+ let typ = replaceHiddenModules $ typeMap M.! "a"+ let validatesExpr = unlines+ [ "case DAT.parseEither parseJSON (" ++ pprint valueExpr ++ ") :: Either String (" ++ pprint typ ++ ") of"+ , " Prelude.Left e -> Just e"+ , " Prelude.Right _ -> Nothing"+ ]+ result <- withCodeTempFile code $ \path -> carry forkLift $ do+ Hint.loadModules [path]+ Hint.setImportsQ $ map (,Nothing) (getUsedModules (valueExpr, typ)) +++ [ ("TestSchema", Nothing)+ , ("Prelude", Nothing)+ , ("Data.Aeson.Types", Just "DAT")+ , ("Data.Ratio", Nothing)+ ]+ Hint.interpret validatesExpr (Hint.as :: Maybe String)+ let printInfo = TIO.putStrLn code >> putStrLn validatesExpr+ case result of+ Left err -> printInfo >> HU.assertFailure (show err)+ Right maybeError -> case (shouldBeValid, maybeError) of+ (True, Nothing) -> return ()+ (False, Just _) -> return ()+ (True, Just e) -> do+ printInfo+ HU.assertFailure $ "value should have beean parsed but was rejected with error '" ++ e ++ "'"+ (False, Nothing) -> do+ printInfo+ HU.assertFailure "value should have been rejected"
+ test/Data/Aeson/Schema/Examples.hs view
@@ -0,0 +1,188 @@+{-# LANGUAGE QuasiQuotes #-}++module Data.Aeson.Schema.Examples+ ( examples+ ) where++import Data.Aeson (Value)+import Data.Aeson.LitQQ (aesonLitQQ)+import qualified Data.Map as M+import Data.Text (Text)++import Data.Aeson.Schema++examples :: (String -> IO () -> a)+ -> (Graph Schema Text -> Schema Text -> Value -> IO ())+ -> (Graph Schema Text -> Schema Text -> Value -> IO ())+ -> [a]+examples testCase assertValid' assertInvalid' =+ [ testCase "patternProperties" $ do+ let schema = [schemaQQ| {+ "type": "object",+ "properties": {+ "positiveNumber": {+ "type": "number",+ "minimum": 0,+ "exclusiveMinimum": true+ }+ },+ "patternProperties": {+ ".+Number$": { "type": "integer" },+ ".+String$": { "type": "string" }+ }+ } |]+ assertValid schema [aesonLitQQ| { "positiveNumber": 13 } |]+ assertInvalid schema [aesonLitQQ| { "positiveNumber": -13 } |]+ assertInvalid schema [aesonLitQQ| { "positiveNumber": 13.5 } |]+ assertValid schema [aesonLitQQ| { "fooString": "foo", "barString": "bar" } |]+ assertInvalid schema [aesonLitQQ| { "fooString": null, "barString": "bar" } |]+ , testCase "additionalProperties" $ do+ let additionalNumbers = [schemaQQ| {+ "type": "object",+ "properties": { "null": { "type": "null" } },+ "patternProperties": {+ ".+String$": { "type": "string" }+ },+ "additionalProperties": { "type": "number" }+ } |]+ assertValid additionalNumbers [aesonLitQQ| {+ "null": null,+ "emptyString": "",+ "oneMoreThing": 23,+ "theLastThing": 999+ } |]+ assertInvalid additionalNumbers [aesonLitQQ| { "null": null, "notANumber": true } |]+ let noAdditionalProperties = [schemaQQ| {+ "type": "object",+ "properties": { "null": { "type": "null" } },+ "patternProperties": {+ ".+String$": { "type": "string" }+ },+ "additionalProperties": false+ } |]+ assertValid noAdditionalProperties [aesonLitQQ| { "null": null, "emptyString": "" } |]+ assertInvalid noAdditionalProperties [aesonLitQQ| {+ "null": null,+ "emptyString": "",+ "oneMoreThing": 23,+ "theLastThing": 999+ } |]+ , testCase "disallow" $ do+ let onlyFloats = [schemaQQ| { "type": "number", "disallow": "integer" } |]+ assertInvalid onlyFloats [aesonLitQQ| 9 |]+ assertValid onlyFloats [aesonLitQQ| 9.75 |]+ let notLengthThree = [schemaQQ| {+ "type": "array",+ "disallow": [{+ "type": "array",+ "minItems": 3,+ "maxItems": 3+ }]+ } |]+ assertValid notLengthThree [aesonLitQQ| [] |]+ assertValid notLengthThree [aesonLitQQ| [1] |]+ assertValid notLengthThree [aesonLitQQ| [1, 2] |]+ assertInvalid notLengthThree [aesonLitQQ| [1, 2, 3] |]+ assertValid notLengthThree [aesonLitQQ| [1, 2, 3, 4] |]+ let everythingExceptNumbers = [schemaQQ| { "disallow": "number" } |]+ assertInvalid everythingExceptNumbers [aesonLitQQ| 3 |]+ assertInvalid everythingExceptNumbers [aesonLitQQ| 3.5 |]+ assertValid everythingExceptNumbers [aesonLitQQ| true |]+ assertValid everythingExceptNumbers [aesonLitQQ| "nobody expects the ..." |]+ assertValid everythingExceptNumbers [aesonLitQQ| { "eins": 1, "zwei": 2 } |]+ assertValid everythingExceptNumbers [aesonLitQQ| ["eins", "zwei"] |]+ assertValid everythingExceptNumbers [aesonLitQQ| null |]+ , testCase "type: subschema" $ do+ let schema = [schemaQQ| {+ "type": [+ {+ "type": "object",+ "properties": { "insert": { "type": "string", "minLength": 1 } },+ "additionalProperties": false+ },+ {+ "type": "object",+ "properties": { "delete": { "type": "number", "minimum": 1 } },+ "additionalProperties": false+ },+ {+ "type": "object",+ "properties": { "retain": { "type": "number", "minimum": 1 } },+ "additionalProperties": false+ }+ ]+ } |]+ assertValid schema [aesonLitQQ| { "insert": "lorem" } |]+ assertInvalid schema [aesonLitQQ| { "insert": "lorem", "delete": 5 } |]+ assertValid schema [aesonLitQQ| { "delete": 5 } |]+ assertInvalid schema [aesonLitQQ| { "delete": 5, "retain": 76 } |]+ assertValid schema [aesonLitQQ| { "retain": 76 } |]+ , testCase "dependencies" $ do+ let aRequiresB = [schemaQQ| {+ "type": "object",+ "dependencies": { "a": "b" }+ } |]+ assertValid aRequiresB [aesonLitQQ| {} |]+ assertValid aRequiresB [aesonLitQQ| { "b": false } |]+ assertValid aRequiresB [aesonLitQQ| { "a": true, "b": false } |]+ assertInvalid aRequiresB [aesonLitQQ| { "a": 3 } |]+ let aRequiresBToBeANumber = [schemaQQ| {+ "type": "object",+ "dependencies": { "a": { "properties": { "b": { "type": "number" } } } }+ } |]+ assertValid aRequiresBToBeANumber [aesonLitQQ| {} |]+ assertValid aRequiresBToBeANumber [aesonLitQQ| { "b": "lorem" } |]+ assertValid aRequiresBToBeANumber [aesonLitQQ| { "a": "yes, we can" } |]+ assertInvalid aRequiresBToBeANumber [aesonLitQQ| { "a": "yes, we can", "b": "lorem" } |]+ assertValid aRequiresBToBeANumber [aesonLitQQ| { "a": "hi there", "b": 42 } |]+ let aDisallowsB = [schemaQQ| {+ "type": "object",+ "dependencies": {+ "a": {+ "disallow": [{+ "properties": {+ "b": { "type": "any", "required": true }+ }+ }]+ }+ }+ } |]+ assertValid aDisallowsB [aesonLitQQ| { "a": "lorem" } |]+ assertValid aDisallowsB [aesonLitQQ| { "b": 42 } |]+ assertInvalid aDisallowsB [aesonLitQQ| { "a": "lorem", "b": 42 } |]+ , testCase "extends" $ do+ let schema = [schemaQQ| {+ "type": "object",+ "properties": {+ "a": { "type": "number" }+ },+ "extends": [+ {+ "properties": {+ "a": { "required": true }+ }+ },+ {+ "patternProperties": {+ "^[a-z]$": { "minimum": -3 }+ }+ }+ ]+ } |]+ assertValid schema [aesonLitQQ| { "a": 2 } |]+ assertInvalid schema [aesonLitQQ| {} |]+ assertInvalid schema [aesonLitQQ| { "a": -4 } |]+ assertInvalid schema [aesonLitQQ| { "a": "foo" } |]+ assertInvalid schema [aesonLitQQ| { "a": -1, "b": -10 } |]+ assertValid schema [aesonLitQQ| { "a": -1, "ba": -10 } |]+ , testCase "$ref" $ do+ let+ a = [schemaQQ| { "$ref": "b", "minimum": 3 } |]+ b = [schemaQQ| { "type": "number", "maximum": 2 } |]+ graph = M.fromList [("a", a), ("b", b)]+ assertValid' graph a [aesonLitQQ| 1 |]+ assertInvalid' graph a [aesonLitQQ| 4 |]+ ]+ where+ assertValid = assertValid' M.empty+ assertInvalid = assertInvalid' M.empty
+ test/Data/Aeson/Schema/Types/Tests.hs view
@@ -0,0 +1,49 @@+module Data.Aeson.Schema.Types.Tests+ ( tests+ ) where++import Test.Framework+import Test.Framework.Providers.HUnit+import qualified Test.HUnit as HU++import Data.Aeson+import qualified Data.ByteString.Lazy as L+import Data.Foldable (toList)+import qualified Data.HashMap.Strict as H+import Data.Text (Text)++import Data.Aeson.Schema+import Data.Aeson.Schema.Choice++data TestFunctor a = TestFunctor Int a++instance Functor TestFunctor where+ fmap f (TestFunctor i a) = TestFunctor i (f a)++tests :: [Test]+tests =+ [ testCase "parse schema.json" $ do+ schemaBS <- L.readFile "examples/schema.json"+ case decode schemaBS :: Maybe Value of+ Nothing -> HU.assertFailure "JSON syntax error"+ Just val -> case fromJSON val :: Result (Schema Text) of+ Error e -> HU.assertFailure e+ Success schema -> do+ Just "http://json-schema.org/schema#" HU.@=? schemaId schema+ 0 HU.@=? schemaMinItems schema+ , testCase "Foldable instance" $ do+ let schemaWithRef ref = empty { schemaDRef = Just ref }+ let schema = empty+ { schemaType = [Choice2of2 $ schemaWithRef "a"]+ , schemaProperties = H.fromList [("aProperty", schemaWithRef "b")]+ , schemaPatternProperties = let Right p = mkPattern "lorem.+" in [(p, schemaWithRef "c")]+ , schemaAdditionalProperties = Choice2of2 $ schemaWithRef "d"+ , schemaItems = Just $ Choice2of2 [schemaWithRef "e", schemaWithRef "f"]+ , schemaAdditionalItems = Choice2of2 $ schemaWithRef "g"+ , schemaDependencies = H.fromList [("aProperty", Choice2of2 $ schemaWithRef "h")]+ , schemaDisallow = [Choice2of2 $ schemaWithRef "i"]+ , schemaExtends = [schemaWithRef "j"]+ , schemaDRef = Just "k"+ }+ map (:[]) ['a'..'k'] HU.@=? toList schema+ ]
+ test/Data/Aeson/Schema/Validator/Tests.hs view
@@ -0,0 +1,48 @@+module Data.Aeson.Schema.Validator.Tests+ ( tests+ ) where++import Test.Framework+import Test.Framework.Providers.HUnit+import qualified Test.HUnit as HU++import Data.Aeson (Value)+import qualified Data.Map as M+import Data.Text (Text, unpack)++import Data.Aeson.Schema.Types+import Data.Aeson.Schema.Validator++import Data.Aeson.Schema.Examples (examples)+import TestSuite.Types (SchemaTest (..),+ SchemaTestCase (..))++assertValid, assertInvalid :: Graph Schema Text+ -> Schema Text+ -> Value+ -> HU.Assertion+assertValid graph schema value = case validate graph schema value of+ [] -> return ()+ es -> HU.assertFailure $ unlines es+assertInvalid graph schema value = case validate graph schema value of+ [] -> HU.assertFailure "expected a validation error"+ _ -> return ()++tests :: [SchemaTest] -> [Test]+tests schemaTests = examples testCase assertValid assertInvalid ++ map buildSchemaTest schemaTests+ where+ buildSchemaTest :: SchemaTest -> Test+ buildSchemaTest schemaTest = testGroup testName cases+ where+ testName = unpack $ schemaTestDescription schemaTest+ cases = map (buildSchemaTestCase $ schemaTestSchema schemaTest) $ schemaTestCases schemaTest+ buildSchemaTestCase :: Schema Text -> SchemaTestCase -> Test+ buildSchemaTestCase schema schemaTestCase = testCase testName assertion+ where+ testName = unpack $ schemaTestCaseDescription schemaTestCase+ testData = schemaTestCaseData schemaTestCase+ graph = M.empty+ assertion = if schemaTestCaseValid schemaTestCase+ then assertValid graph schema testData+ else assertInvalid graph schema testData+
+ test/TestSuite/Types.hs view
@@ -0,0 +1,64 @@+module TestSuite.Types+ ( SchemaTest (..)+ , SchemaTestCase (..)+ , readSchemaTests+ ) where++import Control.Applicative ((*>), (<$>), (<*), (<*>))+import Control.Monad (forM)+import Data.Aeson+import Data.Aeson.Schema+import Data.Aeson.Types (parseEither)+import Data.Attoparsec.ByteString.Char8 (skipSpace)+import Data.Attoparsec.Lazy (Result (..), parse)+import qualified Data.ByteString.Lazy as LBS+import Data.List (isSuffixOf)+import Data.Maybe (fromMaybe)+import Data.Monoid ((<>))+import Data.Text (Text, pack)+import Paths_aeson_schema (getDataFileName)+import System.Directory (getDirectoryContents)+import System.FilePath ((</>))++data SchemaTest = SchemaTest+ { schemaTestDescription :: Text+ , schemaTestSchema :: Schema Text+ , schemaTestCases :: [SchemaTestCase]+ } deriving (Eq, Show)++instance FromJSON SchemaTest where+ parseJSON (Object o) = SchemaTest <$> (fromMaybe "" <$> o .:? "description")+ <*> (o .: "schema")+ <*> (o .: "tests")+ parseJSON _ = fail "expected an object"++data SchemaTestCase = SchemaTestCase+ { schemaTestCaseDescription :: Text+ , schemaTestCaseData :: Value+ , schemaTestCaseValid :: Bool+ } deriving (Eq, Show)++instance FromJSON SchemaTestCase where+ parseJSON (Object o) = SchemaTestCase <$> (o .: "description")+ <*> (o .: "data")+ <*> (o .: "valid")+ parseJSON _ = fail "expected an object"++-- | Read tests collected by Julian Berman (https://github.com/Julian/JSON-Schema-Test-Suite)+readSchemaTests :: FilePath -> IO [SchemaTest]+readSchemaTests dir' = do+ dir <- getDataFileName dir'+ contents <- getDirectoryContents dir+ let jsonFiles = filter (".json" `isSuffixOf`) contents+ fmap concat $ forM jsonFiles $ \file -> do+ let fullPath = dir </> file+ jsonBS <- LBS.readFile fullPath+ case parse (skipSpace *> json <* skipSpace) jsonBS of+ Done _ value -> case parseEither parseJSON value of+ Left err -> fail $ "couldn't parse file '" ++ fullPath ++ "': " ++ err+ Right v -> return $ map (prependFileName file) v+ _ -> fail $ "not a valid json file: " ++ fullPath+ where+ prependFileName fileName schemaTest = schemaTest+ { schemaTestDescription = pack fileName <> ": " <> schemaTestDescription schemaTest+ }
+ test/test-suite/tests/draft3/additionalItems.json view
@@ -0,0 +1,67 @@+[+ {+ "description": "additionalItems as schema",+ "schema": {+ "items": [],+ "additionalItems": {"type": "integer"}+ },+ "tests": [+ {+ "description": "additional items match schema",+ "data": [ 1, 2, 3, 4 ],+ "valid": true+ },+ {+ "description": "additional items do not match schema",+ "data": [ 1, 2, 3, "foo" ],+ "valid": false+ }+ ]+ },+ {+ "description": "items is schema, no additionalItems",+ "schema": {+ "items": {},+ "additionalItems": false+ },+ "tests": [+ {+ "description": "all items match schema",+ "data": [ 1, 2, 3, 4, 5 ],+ "valid": true+ }+ ]+ },+ {+ "description": "array of items with no additionalItems",+ "schema": {+ "items": [{}, {}, {}],+ "additionalItems": false+ },+ "tests": [+ {+ "description": "no additional items present",+ "data": [ 1, 2, 3 ],+ "valid": true+ },+ {+ "description": "additional items are not permitted",+ "data": [ 1, 2, 3, 4 ],+ "valid": false+ }+ ]+ },+ {+ "description": "additionalItems as false without items",+ "schema": {+ "additionalItems": false+ },+ "tests": [+ {+ "description": "items should allow anything",+ "data": [ 1, 2, 3, 4, 5 ],+ "valid": true+ }+ ]+ }+]
+ test/test-suite/tests/draft3/additionalProperties.json view
@@ -0,0 +1,58 @@+[+ {+ "description":+ "additionalProperties being false does not allow other properties",+ "schema": {+ "properties": {"foo": {}, "bar": {}},+ "additionalProperties": false+ },+ "tests": [+ {+ "description": "no additional properties is valid",+ "data": {"foo": 1},+ "valid": true+ },+ {+ "description": "an additional property is invalid",+ "data": {"foo" : 1, "bar" : 2, "quux" : "boom"},+ "valid": false+ }+ ]+ },+ {+ "description":+ "additionalProperties allows a schema which should validate",+ "schema": {+ "properties": {"foo": {}, "bar": {}},+ "additionalProperties": {"type": "boolean"}+ },+ "tests": [+ {+ "description": "no additional properties is valid",+ "data": {"foo": 1},+ "valid": true+ },+ {+ "description": "an additional valid property is valid",+ "data": {"foo" : 1, "bar" : 2, "quux" : true},+ "valid": true+ },+ {+ "description": "an additional invalid property is invalid",+ "data": {"foo" : 1, "bar" : 2, "quux" : 12},+ "valid": false+ }+ ]+ },+ {+ "description": "additionalProperties are allowed by default",+ "schema": {"properties": {"foo": {}, "bar": {}}},+ "tests": [+ {+ "description": "additional properties are allowed",+ "data": {"foo": 1, "bar": 2, "quux": true},+ "valid": true+ }+ ]+ }+]
+ test/test-suite/tests/draft3/divisibleBy.json view
@@ -0,0 +1,23 @@+[+ {+ "description": "divisibleBy validation",+ "schema": {"divisibleBy": 1.5},+ "tests": [+ {+ "description": "zero is divisible by anything (except 0)",+ "data": 0,+ "valid": true+ },+ {+ "description": "4.5 is divisible by 1.5",+ "data": 4.5,+ "valid": true+ },+ {+ "description": "35 is not divisible by 1.5",+ "data": 35,+ "valid": false+ }+ ]+ }+]
+ test/test-suite/tests/draft3/enum.json view
@@ -0,0 +1,39 @@+[+ {+ "description": "simple enum validation",+ "schema": {"enum": [1, 2, 3]},+ "tests": [+ {+ "description": "one of the enum is valid",+ "data": 1,+ "valid": true+ },+ {+ "description": "something else is invalid",+ "data": 4,+ "valid": false+ }+ ]+ },+ {+ "description": "heterogeneous enum validation",+ "schema": {"enum": [1, "foo", [], true, {"foo": 12}]},+ "tests": [+ {+ "description": "one of the enum is valid",+ "data": [],+ "valid": true+ },+ {+ "description": "something else is invalid",+ "data": null,+ "valid": false+ },+ {+ "description": "objects are deep compared",+ "data": {"foo": false},+ "valid": false+ }+ ]+ }+]
+ test/test-suite/tests/draft3/items.json view
@@ -0,0 +1,41 @@+[ + { + "description": "a schema given for items", + "schema": { + "items": {"type": "integer"} + }, + "tests": [ + { + "description": "valid items", + "data": [ 1, 2, 3 ], + "valid": true + }, + { + "description": "wrong type of items", + "data": [1, "x"], + "valid": false + } + ] + }, + { + "description": "an array of schemas for items", + "schema": { + "items": [ + {"type": "integer"}, + {"type": "string"} + ] + }, + "tests": [ + { + "description": "correct types", + "data": [ 1, "foo" ], + "valid": true + }, + { + "description": "wrong types", + "data": [ "foo", 1 ], + "valid": false + } + ] + } +]
+ test/test-suite/tests/draft3/maxItems.json view
@@ -0,0 +1,23 @@+[+ {+ "description": "maxItems validation",+ "schema": {"maxItems": 2},+ "tests": [+ {+ "description": "shorter is valid",+ "data": [1],+ "valid": true+ },+ {+ "description": "exact length is valid",+ "data": [1, 2],+ "valid": true+ },+ {+ "description": "too long is invalid",+ "data": [1, 2, 3],+ "valid": false+ }+ ]+ }+]
+ test/test-suite/tests/draft3/maxLength.json view
@@ -0,0 +1,23 @@+[+ {+ "description": "maxLength validation",+ "schema": {"maxLength": 2},+ "tests": [+ {+ "description": "shorter is valid",+ "data": "f",+ "valid": true+ },+ {+ "description": "exact length is valid",+ "data": "fo",+ "valid": true+ },+ {+ "description": "too long is invalid",+ "data": "foo",+ "valid": false+ }+ ]+ }+]
+ test/test-suite/tests/draft3/maximum.json view
@@ -0,0 +1,37 @@+[+ {+ "description": "maximum validation",+ "schema": {"maximum": 3.0},+ "tests": [+ {+ "description": "below the maximum is valid",+ "data": 2.6,+ "valid": true+ },+ {+ "description": "above the maximum is invalid",+ "data": 3.5,+ "valid": false+ }+ ]+ },+ {+ "description": "exclusiveMaximum validation",+ "schema": {+ "maximum": 3.0,+ "exclusiveMaximum": true+ },+ "tests": [+ {+ "description": "below the maximum is still valid",+ "data": 2.2,+ "valid": true+ },+ {+ "description": "boundary point is invalid",+ "data": 3.0,+ "valid": false+ }+ ]+ }+]
+ test/test-suite/tests/draft3/minItems.json view
@@ -0,0 +1,23 @@+[+ {+ "description": "minItems validation",+ "schema": {"minItems": 1},+ "tests": [+ {+ "description": "longer is valid",+ "data": [1, 2],+ "valid": true+ },+ {+ "description": "exact length is valid",+ "data": [1],+ "valid": true+ },+ {+ "description": "too short is invalid",+ "data": [],+ "valid": false+ }+ ]+ }+]
+ test/test-suite/tests/draft3/minLength.json view
@@ -0,0 +1,23 @@+[+ {+ "description": "minLength validation",+ "schema": {"minLength": 2},+ "tests": [+ {+ "description": "longer is valid",+ "data": "foo",+ "valid": true+ },+ {+ "description": "exact length is valid",+ "data": "fo",+ "valid": true+ },+ {+ "description": "too short is invalid",+ "data": "f",+ "valid": false+ }+ ]+ }+]
+ test/test-suite/tests/draft3/minimum.json view
@@ -0,0 +1,37 @@+[+ {+ "description": "minimum validation",+ "schema": {"minimum": 1.1},+ "tests": [+ {+ "description": "above the minimum is valid",+ "data": 2.6,+ "valid": true+ },+ {+ "description": "below the minimum is invalid",+ "data": 0.6,+ "valid": false+ }+ ]+ },+ {+ "description": "exclusiveMinimum validation",+ "schema": {+ "minimum": 1.1,+ "exclusiveMinimum": true+ },+ "tests": [+ {+ "description": "above the minimum is still valid",+ "data": 1.2,+ "valid": true+ },+ {+ "description": "boundary point is invalid",+ "data": 1.1,+ "valid": false+ }+ ]+ }+]
+ test/test-suite/tests/draft3/optional/format.json view
@@ -0,0 +1,18 @@+[+ {+ "description": "validation of regular expressions",+ "schema": {"format": "regex"},+ "tests": [+ {+ "description": "a valid regular expression",+ "data": "([abc])+\\s+$",+ "valid": true+ },+ {+ "description": "a regular expression with unclosed parens is invalid",+ "data": "^(abc]",+ "valid": false+ }+ ]+ }+]
+ test/test-suite/tests/draft3/pattern.json view
@@ -0,0 +1,18 @@+[+ {+ "description": "pattern validation",+ "schema": {"pattern": "^a*$"},+ "tests": [+ {+ "description": "a matching pattern is valid",+ "data": "aaa",+ "valid": true+ },+ {+ "description": "a non-matching pattern is invalid",+ "data": "abc",+ "valid": false+ }+ ]+ }+]
+ test/test-suite/tests/draft3/patternProperties.json view
@@ -0,0 +1,74 @@+[+ {+ "description":+ "patternProperties validates properties matching a regex",+ "schema": {+ "patternProperties": {+ "f.*o": {"type": "integer"}+ }+ },+ "tests": [+ {+ "description": "a single valid match is valid",+ "data": {"foo": 1},+ "valid": true+ },+ {+ "description": "multiple valid matches is valid",+ "data": {"foo": 1, "foooooo" : 2},+ "valid": true+ },+ {+ "description": "a single invalid match is invalid",+ "data": {"foo": "bar", "fooooo": 2},+ "valid": false+ },+ {+ "description": "multiple invalid matches is invalid",+ "data": {"foo": "bar", "foooooo" : "baz"},+ "valid": false+ }+ ]+ },+ {+ "description": "multiple simulatneous patternProperties are validated",+ "schema": {+ "patternProperties": {+ "a*": {"type": "integer"},+ "aaa*": {"maximum": 20}+ }+ },+ "tests": [+ {+ "description": "a single valid match is valid",+ "data": {"a": 21},+ "valid": true+ },+ {+ "description": "a simultaneous match is valid",+ "data": {"aaaa": 18},+ "valid": true+ },+ {+ "description": "multiple matches is valid",+ "data": {"a": 21, "aaaa": 18},+ "valid": true+ },+ {+ "description": "an invalid due to one is invalid",+ "data": {"a": "bar"},+ "valid": false+ },+ {+ "description": "an invalid due to the other is invalid",+ "data": {"aaaa": 31},+ "valid": false+ },+ {+ "description": "an invalid due to both is invalid",+ "data": {"aaa": "foo", "aaaa": 31},+ "valid": false+ }+ ]+ }+]
+ test/test-suite/tests/draft3/properties.json view
@@ -0,0 +1,33 @@+[+ {+ "description": "object properties validation",+ "schema": {+ "properties": {+ "foo": {"type": "integer"},+ "bar": {"type": "string"}+ }+ },+ "tests": [+ {+ "description": "both properties present and valid is valid",+ "data": {"foo": 1, "bar": "baz"},+ "valid": true+ },+ {+ "description": "one property invalid is invalid",+ "data": {"foo": 1, "bar": {}},+ "valid": false+ },+ {+ "description": "both properties invalid is invalid",+ "data": {"foo": [], "bar": {}},+ "valid": false+ },+ {+ "description": "doesn't invalidate other properties",+ "data": {"quux": []},+ "valid": true+ }+ ]+ }+]
+ test/test-suite/tests/draft3/required.json view
@@ -0,0 +1,53 @@+[+ {+ "description": "required validation",+ "schema": {+ "properties": {+ "foo": {"required" : true},+ "bar": {}+ }+ },+ "tests": [+ {+ "description": "present required property is valid",+ "data": {"foo": 1},+ "valid": true+ },+ {+ "description": "non-present required property is invalid",+ "data": {"bar": 1},+ "valid": false+ }+ ]+ },+ {+ "description": "required default validation",+ "schema": {+ "properties": {+ "foo": {}+ }+ },+ "tests": [+ {+ "description": "not required by default",+ "data": {},+ "valid": true+ }+ ]+ },+ {+ "description": "required explicitly false validation",+ "schema": {+ "properties": {+ "foo": {"required": false}+ }+ },+ "tests": [+ {+ "description": "not required if required is false",+ "data": {},+ "valid": true+ }+ ]+ }+]
+ test/test-suite/tests/draft3/type.json view
@@ -0,0 +1,474 @@+[+ {+ "description": "integer type matches integers",+ "schema": {"type": "integer"},+ "tests": [+ {+ "description": "an integer is an integer",+ "data": 1,+ "valid": true+ },+ {+ "description": "a float is not an integer",+ "data": 1.1,+ "valid": false+ },+ {+ "description": "a string is not an integer",+ "data": "foo",+ "valid": false+ },+ {+ "description": "an object is not an integer",+ "data": {},+ "valid": false+ },+ {+ "description": "an array is not an integer",+ "data": [],+ "valid": false+ },+ {+ "description": "a boolean is not an integer",+ "data": true,+ "valid": false+ },+ {+ "description": "null is not an integer",+ "data": null,+ "valid": false+ }+ ]+ },+ {+ "description": "number type matches numbers",+ "schema": {"type": "number"},+ "tests": [+ {+ "description": "an integer is a number",+ "data": 1,+ "valid": true+ },+ {+ "description": "a float is a number",+ "data": 1.1,+ "valid": true+ },+ {+ "description": "a string is not a number",+ "data": "foo",+ "valid": false+ },+ {+ "description": "an object is not a number",+ "data": {},+ "valid": false+ },+ {+ "description": "an array is not a number",+ "data": [],+ "valid": false+ },+ {+ "description": "a boolean is not a number",+ "data": true,+ "valid": false+ },+ {+ "description": "null is not a number",+ "data": null,+ "valid": false+ }+ ]+ },+ {+ "description": "string type matches strings",+ "schema": {"type": "string"},+ "tests": [+ {+ "description": "1 is not a string",+ "data": 1,+ "valid": false+ },+ {+ "description": "a float is not a string",+ "data": 1.1,+ "valid": false+ },+ {+ "description": "a string is a string",+ "data": "foo",+ "valid": true+ },+ {+ "description": "an object is not a string",+ "data": {},+ "valid": false+ },+ {+ "description": "an array is not a string",+ "data": [],+ "valid": false+ },+ {+ "description": "a boolean is not a string",+ "data": true,+ "valid": false+ },+ {+ "description": "null is not a string",+ "data": null,+ "valid": false+ }+ ]+ },+ {+ "description": "object type matches objects",+ "schema": {"type": "object"},+ "tests": [+ {+ "description": "an integer is not an object",+ "data": 1,+ "valid": false+ },+ {+ "description": "a float is not an object",+ "data": 1.1,+ "valid": false+ },+ {+ "description": "a string is not an object",+ "data": "foo",+ "valid": false+ },+ {+ "description": "an object is an object",+ "data": {},+ "valid": true+ },+ {+ "description": "an array is not an object",+ "data": [],+ "valid": false+ },+ {+ "description": "a boolean is not an object",+ "data": true,+ "valid": false+ },+ {+ "description": "null is not an object",+ "data": null,+ "valid": false+ }+ ]+ },+ {+ "description": "array type matches arrays",+ "schema": {"type": "array"},+ "tests": [+ {+ "description": "an integer is not an array",+ "data": 1,+ "valid": false+ },+ {+ "description": "a float is not an array",+ "data": 1.1,+ "valid": false+ },+ {+ "description": "a string is not an array",+ "data": "foo",+ "valid": false+ },+ {+ "description": "an object is not an array",+ "data": {},+ "valid": false+ },+ {+ "description": "an array is not an array",+ "data": [],+ "valid": true+ },+ {+ "description": "a boolean is not an array",+ "data": true,+ "valid": false+ },+ {+ "description": "null is not an array",+ "data": null,+ "valid": false+ }+ ]+ },+ {+ "description": "boolean type matches booleans",+ "schema": {"type": "boolean"},+ "tests": [+ {+ "description": "an integer is not a boolean",+ "data": 1,+ "valid": false+ },+ {+ "description": "a float is not a boolean",+ "data": 1.1,+ "valid": false+ },+ {+ "description": "a string is not a boolean",+ "data": "foo",+ "valid": false+ },+ {+ "description": "an object is not a boolean",+ "data": {},+ "valid": false+ },+ {+ "description": "an array is not a boolean",+ "data": [],+ "valid": false+ },+ {+ "description": "a boolean is not a boolean",+ "data": true,+ "valid": true+ },+ {+ "description": "null is not a boolean",+ "data": null,+ "valid": false+ }+ ]+ },+ {+ "description": "null type matches only the null object",+ "schema": {"type": "null"},+ "tests": [+ {+ "description": "an integer is not null",+ "data": 1,+ "valid": false+ },+ {+ "description": "a float is not null",+ "data": 1.1,+ "valid": false+ },+ {+ "description": "a string is not null",+ "data": "foo",+ "valid": false+ },+ {+ "description": "an object is not null",+ "data": {},+ "valid": false+ },+ {+ "description": "an array is not null",+ "data": [],+ "valid": false+ },+ {+ "description": "a boolean is not null",+ "data": true,+ "valid": false+ },+ {+ "description": "null is null",+ "data": null,+ "valid": true+ }+ ]+ },+ {+ "description": "any type matches any type",+ "schema": {"type": "any"},+ "tests": [+ {+ "description": "any type includes integers",+ "data": 1,+ "valid": true+ },+ {+ "description": "any type includes float",+ "data": 1.1,+ "valid": true+ },+ {+ "description": "any type includes string",+ "data": "foo",+ "valid": true+ },+ {+ "description": "any type includes object",+ "data": {},+ "valid": true+ },+ {+ "description": "any type includes array",+ "data": [],+ "valid": true+ },+ {+ "description": "any type includes boolean",+ "data": true,+ "valid": true+ },+ {+ "description": "any type includes null",+ "data": null,+ "valid": true+ }+ ]+ },+ {+ "description": "multiple types can be specified in an array",+ "schema": {"type": ["integer", "string"]},+ "tests": [+ {+ "description": "an integer is valid",+ "data": 1,+ "valid": true+ },+ {+ "description": "a string is valid",+ "data": "foo",+ "valid": true+ },+ {+ "description": "a float is invalid",+ "data": 1.1,+ "valid": false+ },+ {+ "description": "an object is invalid",+ "data": {},+ "valid": false+ },+ {+ "description": "an array is invalid",+ "data": [],+ "valid": false+ },+ {+ "description": "a boolean is invalid",+ "data": true,+ "valid": false+ },+ {+ "description": "null is invalid",+ "data": null,+ "valid": false+ }+ ]+ },+ {+ "description": "types can include schemas",+ "schema": {+ "type": [+ "array",+ {"type": "object"}+ ]+ },+ "tests": [+ {+ "description": "an integer is invalid",+ "data": 1,+ "valid": false+ },+ {+ "description": "a string is invalid",+ "data": "foo",+ "valid": false+ },+ {+ "description": "a float is invalid",+ "data": 1.1,+ "valid": false+ },+ {+ "description": "an object is valid",+ "data": {},+ "valid": true+ },+ {+ "description": "an array is valid",+ "data": [],+ "valid": true+ },+ {+ "description": "a boolean is invalid",+ "data": true,+ "valid": false+ },+ {+ "description": "null is invalid",+ "data": null,+ "valid": false+ }+ ]+ },+ {+ "description": + "when types includes a schema it should fully validate the schema",+ "schema": {+ "type": [+ "integer",+ {+ "properties": {+ "foo": {"type": "null"}+ }+ }+ ]+ },+ "tests": [+ {+ "description": "an integer is valid",+ "data": 1,+ "valid": true+ },+ {+ "description": "an object is valid only if it is fully valid",+ "data": {"foo": null},+ "valid": true+ },+ {+ "description": "an object is invalid otherwise",+ "data": {"foo": "bar"},+ "valid": false+ }+ ]+ },+ {+ "description": "types from separate schemas are merged",+ "schema": {+ "type": [+ {"type": ["string"]},+ {"type": ["array", "null"]}+ ]+ },+ "tests": [+ {+ "description": "an integer is invalid",+ "data": 1,+ "valid": false+ },+ {+ "description": "a string is valid",+ "data": "foo",+ "valid": true+ },+ {+ "description": "an array is valid",+ "data": [1, 2, 3],+ "valid": true+ }+ ]+ }+]
+ test/test-suite/tests/draft3/uniqueItems.json view
@@ -0,0 +1,75 @@+[+ {+ "description": "uniqueItems validation",+ "schema": {"uniqueItems": true},+ "tests": [+ {+ "description": "unique array of integers is valid",+ "data": [1, 2],+ "valid": true+ },+ {+ "description": "non-unique array of integers is invalid",+ "data": [1, 1],+ "valid": false+ },+ {+ "description": "unique array of objects is valid",+ "data": [{"foo": "bar"}, {"foo": "baz"}],+ "valid": true+ },+ {+ "description": "non-unique array of objects is invalid",+ "data": [{"foo": "bar"}, {"foo": "bar"}],+ "valid": false+ },+ {+ "description": "unique array of nested objects is valid",+ "data": [+ {"foo": {"bar" : {"baz" : true}}},+ {"foo": {"bar" : {"baz" : false}}}+ ],+ "valid": true+ },+ {+ "description": "non-unique array of nested objects is invalid",+ "data": [+ {"foo": {"bar" : {"baz" : true}}},+ {"foo": {"bar" : {"baz" : true}}}+ ],+ "valid": false+ },+ {+ "description": "unique array of arrays is valid",+ "data": [["foo"], ["bar"]],+ "valid": true+ },+ {+ "description": "non-unique array of arrays is invalid",+ "data": [["foo"], ["foo"]],+ "valid": false+ }+ ]+ },+ {+ "description": "heterogeneous enum validation",+ "schema": {"enum": [1, "foo", [], true, {"foo": 12}]},+ "tests": [+ {+ "description": "one of the enum is valid",+ "data": [],+ "valid": true+ },+ {+ "description": "something else is invalid",+ "data": null,+ "valid": false+ },+ {+ "description": "objects are deep compared",+ "data": {"foo": false},+ "valid": false+ }+ ]+ }+]