packages feed

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 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+            }+        ]+    }+]