git.iko.soy/typeable/hjsonschema
on master help sign in
Improve docs; add example to the README.
commit163b87c57a7177af4c9a0dc28a3dc8bd134fb2fa authorIan Grant Jeffries <ian@housejeffries.com> dateWed 25 Mar 2015, 14:10 (12 years ago) parent83fe288
8 files changed+125−60
README.md+39−1 hjsonschema.cabal+1−1 src/Data/JsonSchema.hs+33−17 src/Data/JsonSchema/Core.hs+32−17 src/Data/JsonSchema/Helpers.hs+8−14 src/Data/JsonSchema/Reference.hs+3 src/Data/JsonSchema/Validators.hs+8−9 tests/Lib.hs+1−1
README.md+39−1
@@ -4,7 +4,45 @@ An implementation of [JSON Schema](http://json-schema.org/) Draft 4 in haskell.
44
55# Status
66
7−Still in development. Lacks documentation and solid code to fetch remote schemas.
7+Still in development. Lacks solid code to fetch remote schemas.
8+
9+# Example
10+
11+```
12+{-# LANGUAGE OverloadedStrings #-}
13+
14+module Main where
15+
16+import Data.Aeson
17+import qualified Data.HashMap.Strict as H
18+import qualified Data.JsonSchema as JS
19+import qualified Data.Vector as V
20+
21+main :: IO ()
22+main =
23+ case V.length (JS.isValidSchema rawSchema) of
24+ 0 -> do
25+ eitherGraph <- JS.fetchRefs JS.draft4 rawSchema H.empty
26+ case eitherGraph of
27+ Left e -> print e
28+ Right g -> print $ JS.validate (JS.compile JS.draft4 g $ rawSchema) invalidData
29+ _ -> putStrLn "The schema you tried to use doesn't follow the draft 4 layout."
30+
31+rawSchema :: JS.RawSchema
32+rawSchema = JS.RawSchema
33+ { JS._rsURI = ""
34+ , JS._rsObject = H.singleton "uniqueItems" (Bool True) -- Schema JSON goes here.
35+ }
36+
37+invalidData :: Value
38+invalidData = Array (V.fromList ["foo", "foo"])
39+```
40+
41+Output:
42+```
43+fromList ["Val error against uniqueItems True for: Array (fromList [String \"foo\",String \"foo\"])"]
44+```
45+
846
947# Install Tests
1048
hjsonschema.cabal+1−1
@@ -1,5 +1,5 @@
11name: hjsonschema
2−version: 0.5.1.1
2+version: 0.5.1.2
33synopsis: JSON Schema Draft 4 library
44homepage: https://github.com/seagreen/hjsonschema
55license: MIT
src/Data/JsonSchema.hs+33−17
@@ -1,8 +1,8 @@
11{-# LANGUAGE TemplateHaskell #-}
22
33module Data.JsonSchema
4− ( module Data.JsonSchema
5− , module Data.JsonSchema.Core
4+ ( module Data.JsonSchema.Core
5+ , module Data.JsonSchema
66 ) where
77
88import Control.Applicative
@@ -22,6 +22,10 @@ import Data.Vector (Vector)
2222import qualified Data.Vector as V
2323import Prelude hiding (foldr)
2424
25+--------------------------------------------------
26+-- * Draft 4 Specific
27+--------------------------------------------------
28+
2529draft4 :: Spec
2630draft4 = Spec $ H.fromList
2731 [ ("$ref" , (ref , noEm))
@@ -52,18 +56,43 @@ draft4 = Spec $ H.fromList
5256 , ("definitions" , (noVal , objMembersEmbed))
5357 ]
5458
59+-- | Check if a 'RawSchema' is valid Draft 4 schema.
60+--
61+-- This is just a convenience function built by preloading 'validate'
62+-- with the spec schema that describes valid Draft 4 schemas.
63+isValidSchema :: RawSchema -> Vector ValErr
64+isValidSchema r =
65+ case decode . fromStrict $ $(embedFile "draft4.json") of
66+ Nothing -> V.singleton "Schema decode failed (this should never happen)"
67+ Just s -> do
68+ let draft4Schema = RawSchema { _rsURI = "", _rsObject = s }
69+ validate
70+ (compile draft4 H.empty draft4Schema)
71+ (Object . _rsObject $ r)
72+
73+--------------------------------------------------
74+-- * Graph Builder
75+--------------------------------------------------
76+-- $ TODO: It would be nice to have a few other functions for building
77+-- out 'Graph's depending on the situation. For instance, there could be
78+-- a function that only fetched "$ref"s which use the "file://" scheme.
79+
80+-- | Take a schema. Retrieve every document either it or its
81+-- subschemas include via the "$ref" keyword. Load a 'Graph' out
82+-- with them.
83+--
84+-- TODO: This function's URL processing is hacky and needs improvement.
5585fetchRefs :: Spec -> RawSchema -> Graph -> IO (Either Text Graph)
5686fetchRefs spec a graph =
5787 let startingGraph = H.insert (_rsURI a) (_rsObject a) graph
5888 in foldlM fetch (Right startingGraph) (includeSubschemas a)
5989
6090 where
61− -- TODO: optimize
6291 includeSubschemas :: RawSchema -> Vector RawSchema
6392 includeSubschemas r =
6493 let newId = newResolutionScope (_rsURI r) (_rsObject r)
6594 xs = H.intersectionWith (\(_,f) x -> f newId x) (_unSpec spec) (_rsObject r)
66− ys = V.concat . H.elems $ xs
95+ ys = V.concat . H.elems $ xs -- TODO: optimize
6796 in V.cons r . V.concat . V.toList $ includeSubschemas <$> ys
6897
6998 fetch :: Either Text Graph -> RawSchema -> IO (Either Text Graph)
@@ -80,16 +109,3 @@ fetchRefs spec a graph =
80109 Left e -> return (Left e)
81110 Right obj -> fetchRefs spec (RawSchema t obj) g
82111 fetch leftGraph _ = return leftGraph
83−
84−-- | Check if a schema is valid or not. Just a helper function
85−-- built out of the JSON Schema document which describes valid
86−-- draft 4 schemas and the normal 'compile' and 'validate' functions.
87−isValidSchema :: RawSchema -> Vector ValErr
88−isValidSchema r =
89− case decode . fromStrict $ $(embedFile "draft4.json") of
90− Nothing -> V.singleton "Schema decode failed (this should never happen)"
91− Just s -> do
92− let draft4Schema = RawSchema { _rsURI = "", _rsObject = s }
93− validate
94− (compile draft4 H.empty draft4Schema)
95− (Object . _rsObject $ r)
src/Data/JsonSchema/Core.hs+32−17
@@ -1,16 +1,34 @@
11module Data.JsonSchema.Core where
22
33import Data.Aeson
4−import Data.HashMap.Strict (HashMap)
5−import qualified Data.HashMap.Strict as H
4+import Data.HashMap.Strict (HashMap)
5+import qualified Data.HashMap.Strict as H
66import Data.JsonSchema.Reference
77import Data.Maybe
8−import Data.Text (Text)
9−import Data.Vector (Vector)
10−import qualified Data.Vector as V
8+import Data.Text (Text)
9+import Data.Vector (Vector)
10+import qualified Data.Vector as V
11+
12+--------------------------------------------------
13+-- * Primary API
14+--------------------------------------------------
15+
16+compile :: Spec -> Graph -> RawSchema -> Schema
17+compile spec g (RawSchema t o) =
18+ V.fromList . catMaybes . H.elems $ H.intersectionWith f (_unSpec spec) o
19+ where
20+ f :: (ValidatorGen, a) -> Value -> Maybe Validator
21+ f (vGen,_) = vGen spec g $ RawSchema (newResolutionScope t o) o
22+
23+validate :: Schema -> Value -> Vector ValErr
24+validate s x = s >>= ($ x)
25+
26+--------------------------------------------------
27+-- * Types
28+--------------------------------------------------
1129
1230newtype Spec = Spec
13− { _unSpec :: HashMap Text (ValidatorGen, Text -> Value -> Vector RawSchema)
31+ { _unSpec :: HashMap Text (ValidatorGen, EmbeddedSchemas)
1432 }
1533
1634-- | Set of potentially mutually recursive schemas.
@@ -20,21 +38,18 @@ type ValErr = Text
2038
2139type Validator = Value -> Vector ValErr
2240
41+type Schema = Vector Validator
42+
2343type ValidatorGen = Spec -> Graph -> RawSchema -> Value -> Maybe Validator
2444
25−type Schema = Vector Validator
45+-- | Return a schema's immediate subschemas.
46+--
47+-- This is used by 'Data.JsonSchema.fetchRefs' to find all the
48+-- subschemas in a document. This allows it to process only
49+-- "$ref"s and "id"s that are actual schema keywords.
50+type EmbeddedSchemas = Text -> Value -> Vector RawSchema
2651
2752data RawSchema = RawSchema
2853 { _rsURI :: Text
2954 , _rsObject :: HashMap Text Value
3055 }
31−
32−compile :: Spec -> Graph -> RawSchema -> Schema
33−compile spec g (RawSchema t o) =
34− V.fromList . catMaybes . H.elems $ H.intersectionWith f (_unSpec spec) o
35− where
36− f :: (ValidatorGen, a) -> Value -> Maybe Validator
37− f (vGen,_) = vGen spec g $ RawSchema (newResolutionScope t o) o
38−
39−validate :: Schema -> Value -> Vector ValErr
40−validate s x = s >>= ($ x)
src/Data/JsonSchema/Helpers.hs+8−14
@@ -1,9 +1,3 @@
1−-- | Embedded schema layouts and functions for use in Validators.hs.
2−--
3−-- 'Data.JsonSchema.fetchRefs' uses embedded schema layouts to
4−-- correctly find subschemas so it can check for "$ref" and "id"
5−-- keywords.
6−
71module Data.JsonSchema.Helpers where
82
93import Control.Applicative
@@ -24,32 +18,32 @@ import qualified Data.Vector as V
2418import Text.RegexPR
2519
2620--------------------------------------------------
27−-- Embedded Schema Layouts
21+-- * Embedded Schema Layouts
2822--------------------------------------------------
2923
30−noEm :: Text -> Value -> Vector RawSchema
24+noEm :: EmbeddedSchemas
3125noEm _ _ = V.empty
3226
33−objEmbed :: Text -> Value -> Vector RawSchema
27+objEmbed :: EmbeddedSchemas
3428objEmbed t (Object o) = V.singleton $ RawSchema t o
3529objEmbed _ _ = V.empty
3630
3731-- TODO: optimize
38−arrayEmbed :: Text -> Value -> Vector RawSchema
32+arrayEmbed :: EmbeddedSchemas
3933arrayEmbed t (Array vs) = V.concat . V.toList $ objEmbed t <$> vs
4034arrayEmbed _ _ = V.empty
4135
42−objOrArrayEmbed :: Text -> Value -> Vector RawSchema
36+objOrArrayEmbed :: EmbeddedSchemas
4337objOrArrayEmbed t v@(Object _) = objEmbed t v
4438objOrArrayEmbed t v@(Array _) = arrayEmbed t v
4539objOrArrayEmbed _ _ = V.empty
4640
47−objMembersEmbed :: Text -> Value -> Vector RawSchema
41+objMembersEmbed :: EmbeddedSchemas
4842objMembersEmbed t (Object o) = V.concat $ objEmbed t <$> H.elems o
4943objMembersEmbed _ _ = V.empty
5044
5145--------------------------------------------------
52−-- Validator Helpers
46+-- * Validator Helpers
5347--------------------------------------------------
5448
5549propertiesMatches
@@ -127,7 +121,7 @@ isJsonType x xs =
127121 mkErr y ts = V.singleton $ tshow y <> " is not one of the types " <> tshow ts
128122
129123--------------------------------------------------
130−-- Utils
124+-- * Utils
131125--------------------------------------------------
132126
133127vectorOfElems :: HashMap k a -> Vector a
src/Data/JsonSchema/Reference.hs+3
@@ -1,3 +1,6 @@
1+-- | TODO: The code handling resolution scope updates
2+-- is hacky and needs improvement.
3+
14module Data.JsonSchema.Reference where
25
36import Control.Arrow
src/Data/JsonSchema/Validators.hs+8−9
@@ -1,7 +1,6 @@
1−-- | This is generally meant to be an internal module.
2−-- It's only exposed in case you want to make your own
3−-- 'Data.JsonSchema.Core.Spec'. If you just want to use
4−-- JSON Schema Draft 4 use the preassembled
1+-- | This is generally meant to be an internal module. It's only
2+-- exposed in case you want to make your own 'Spec'. If you just
3+-- want to use JSON Schema Draft 4 use the preassembled
54-- 'Data.JsonSchema.draft4' instead.
65
76module Data.JsonSchema.Validators where
@@ -29,7 +28,7 @@ import Network.HTTP.Types.URI
2928import Text.RegexPR
3029
3130--------------------------------------------------
32−-- Number Validators
31+-- * Number Validators
3332--------------------------------------------------
3433
3534multipleOf :: ValidatorGen
@@ -73,7 +72,7 @@ minimumVal _ _ s (Number val) =
7372minimumVal _ _ _ _ = Nothing
7473
7574--------------------------------------------------
76−-- String Validators
75+-- * String Validators
7776--------------------------------------------------
7877
7978maxLength :: ValidatorGen
@@ -112,7 +111,7 @@ pattern _ _ _ (String val) =
112111pattern _ _ _ _ = Nothing
113112
114113--------------------------------------------------
115−-- Array Validators
114+-- * Array Validators
116115--------------------------------------------------
117116
118117-- | Also covers additionalItems.
@@ -193,7 +192,7 @@ uniqueItems _ _ _ (Bool val) = do
193192uniqueItems _ _ _ _ = Nothing
194193
195194--------------------------------------------------
196−-- Object Validators
195+-- * Object Validators
197196--------------------------------------------------
198197
199198maxProperties :: ValidatorGen
@@ -363,7 +362,7 @@ dependencies spec g s (Object val) = do
363362dependencies _ _ _ _ = Nothing
364363
365364--------------------------------------------------
366−-- Any Validators
365+-- * Any Validators
367366--------------------------------------------------
368367
369368enum :: ValidatorGen
tests/Lib.hs+1−1
@@ -71,7 +71,7 @@ readSchemaTests dir jsonFiles = concatMapM fileToCases jsonFiles
7171toTest :: SchemaTest -> Test
7272toTest st =
7373 testCase (T.unpack $ _stDescription st) $ do
74− assertEqual "schema validity errors" V.empty (isValidSchema . _stSchema $ st)
74+ assertEqual "schema validity errors" V.empty $ isValidSchema (_stSchema st)
7575 forM_ (_stCases st) $ \sc -> do
7676 g <- assertRight =<< fetchRefs draft4 (_stSchema st) H.empty
7777 let es = validate (compile draft4 g $ _stSchema st) (_scData sc)