json_schema 0.0.7

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,238 @@
1
+ module DataScaffold
2
+ def self.data_sample
3
+ {
4
+ "name" => "cloudnasium"
5
+ }
6
+ end
7
+
8
+ def self.schema_sample
9
+ {
10
+ "$schema" => "http://json-schema.org/draft-04/hyper-schema",
11
+ "title" => "Example API",
12
+ "description" => "An example API.",
13
+ "type" => [
14
+ "object"
15
+ ],
16
+ "definitions" => {
17
+ "app" => {
18
+ "$schema" => "http://json-schema.org/draft-04/hyper-schema",
19
+ "title" => "App",
20
+ "description" => "An app.",
21
+ "id" => "schemata/app",
22
+ "type" => [
23
+ "object"
24
+ ],
25
+ "definitions" => {
26
+ "config_vars" => {
27
+ "patternProperties" => {
28
+ "^\\w+$" => {
29
+ "type" => ["null", "string"]
30
+ }
31
+ }
32
+ },
33
+ "contrived" => {
34
+ "allOf" => [
35
+ { "maxLength" => 30 },
36
+ { "minLength" => 3 }
37
+ ],
38
+ "anyOf" => [
39
+ { "minLength" => 3 },
40
+ { "minLength" => 5 }
41
+ ],
42
+ "oneOf" => [
43
+ { "pattern" => "^(foo|aaa)$" },
44
+ { "pattern" => "^(foo|zzz)$" }
45
+ ],
46
+ "not" => { "pattern" => "^$" }
47
+ },
48
+ "contrived_plus" => {
49
+ "allOf" => [
50
+ { "$ref" => "/schemata/app#/definitions/contrived/allOf/0" },
51
+ { "$ref" => "/schemata/app#/definitions/contrived/allOf/1" }
52
+ ],
53
+ "anyOf" => [
54
+ { "$ref" => "/schemata/app#/definitions/contrived/anyOf/0" },
55
+ { "$ref" => "/schemata/app#/definitions/contrived/anyOf/1" }
56
+ ],
57
+ "oneOf" => [
58
+ { "$ref" => "/schemata/app#/definitions/contrived/oneOf/0" },
59
+ { "$ref" => "/schemata/app#/definitions/contrived/oneOf/1" }
60
+ ],
61
+ "not" => {
62
+ "$ref" => "/schemata/app#/definitions/contrived/not"
63
+ }
64
+ },
65
+ "cost" => {
66
+ "description" => "running price of an app",
67
+ "example" => 35.01,
68
+ "maximum" => 1000.00,
69
+ "exclusiveMaximum" => true,
70
+ "minimum" => 0.0,
71
+ "exclusiveMinimum" => false,
72
+ "multipleOf" => 0.01,
73
+ "readOnly" => false,
74
+ "type" => ["number"],
75
+ },
76
+ "flags" => {
77
+ "description" => "flags for an app",
78
+ "example" => ["websockets"],
79
+ "items" => {
80
+ "pattern" => "^[a-z][a-z\\-]*[a-z]$"
81
+ },
82
+ "maxItems" => 10,
83
+ "minItems" => 1,
84
+ "readOnly" => false,
85
+ "type" => ["array"],
86
+ "uniqueItems" => true
87
+ },
88
+ "id" => {
89
+ "description" => "integer identifier of an app",
90
+ "example" => 1,
91
+ "maximum" => 10000,
92
+ "exclusiveMaximum" => false,
93
+ "minimum" => 0,
94
+ "exclusiveMinimum" => true,
95
+ "multipleOf" => 1,
96
+ "readOnly" => true,
97
+ "type" => ["integer"],
98
+ },
99
+ "identity" => {
100
+ "anyOf" => [
101
+ { "$ref" => "/schemata/app#/definitions/id" },
102
+ { "$ref" => "/schemata/app#/definitions/name" },
103
+ ]
104
+ },
105
+ "name" => {
106
+ "default" => "hello-world",
107
+ "description" => "unique name of app",
108
+ "example" => "name",
109
+ "maxLength" => 30,
110
+ "minLength" => 3,
111
+ "pattern" => "^[a-z][a-z0-9-]{3,30}$",
112
+ "readOnly" => false,
113
+ "type" => ["string"]
114
+ },
115
+ "owner" => {
116
+ "description" => "owner of the app",
117
+ "format" => "email",
118
+ "example" => "dwarf@example.com",
119
+ "readOnly" => false,
120
+ "type" => ["string"]
121
+ },
122
+ "production" => {
123
+ "description" => "whether this is a production app",
124
+ "example" => false,
125
+ "readOnly" => false,
126
+ "type" => ["boolean"]
127
+ },
128
+ "role" => {
129
+ "description" => "name of a role on an app",
130
+ "example" => "collaborator",
131
+ "readOnly" => true,
132
+ "type" => ["string"],
133
+ },
134
+ "roles" => {
135
+ "additionalProperties" => true,
136
+ "patternProperties" => {
137
+ "^\\w+$" => {
138
+ "$ref" => "/schemata/app#/definitions/role"
139
+ }
140
+ }
141
+ },
142
+ "ssl" => {
143
+ "description" => "whether this app has SSL termination",
144
+ "example" => false,
145
+ "readOnly" => false,
146
+ "type" => ["boolean"]
147
+ },
148
+ "visibility" => {
149
+ "description" => "the visibility of hte app",
150
+ "enum" => ["private", "public"],
151
+ "example" => false,
152
+ "readOnly" => false,
153
+ "type" => ["string"]
154
+ },
155
+ },
156
+ "properties" => {
157
+ "config_vars" => {
158
+ "$ref" => "/schemata/app#/definitions/config_vars"
159
+ },
160
+ "contrived" => {
161
+ "$ref" => "/schemata/app#/definitions/contrived"
162
+ },
163
+ "cost" => {
164
+ "$ref" => "/schemata/app#/definitions/cost"
165
+ },
166
+ "flags" => {
167
+ "$ref" => "/schemata/app#/definitions/flags"
168
+ },
169
+ "id" => {
170
+ "$ref" => "/schemata/app#/definitions/id"
171
+ },
172
+ "name" => {
173
+ "$ref" => "/schemata/app#/definitions/name"
174
+ },
175
+ "owner" => {
176
+ "$ref" => "/schemata/app#/definitions/owner"
177
+ },
178
+ "production" => {
179
+ "$ref" => "/schemata/app#/definitions/production"
180
+ },
181
+ "ssl" => {
182
+ "$ref" => "/schemata/app#/definitions/ssl"
183
+ },
184
+ "visibility" => {
185
+ "$ref" => "/schemata/app#/definitions/visibility"
186
+ }
187
+ },
188
+ "additionalProperties" => false,
189
+ "dependencies" => {
190
+ "production" => "ssl",
191
+ "ssl" => {
192
+ "properties" => {
193
+ "cost" => {
194
+ "minimum" => 20.0,
195
+ },
196
+ "name" => {
197
+ "$ref" => "/schemata/app#/definitions/name"
198
+ },
199
+ }
200
+ }
201
+ },
202
+ "maxProperties" => 10,
203
+ "minProperties" => 1,
204
+ "required" => ["name"],
205
+ "links" => [
206
+ "description" => "Create a new app.",
207
+ "href" => "/apps",
208
+ "method" => "POST",
209
+ "rel" => "create",
210
+ "schema" => {
211
+ "properties" => {
212
+ "name" => {
213
+ "$ref" => "#/definitions/app/definitions/name"
214
+ },
215
+ }
216
+ }
217
+ ],
218
+ "media" => {
219
+ "type" => "application/json"
220
+ },
221
+ "pathStart" => "/",
222
+ "readOnly" => false
223
+ }
224
+ },
225
+ "properties" => {
226
+ "app" => {
227
+ "$ref" => "#/definitions/app"
228
+ },
229
+ },
230
+ "links" => [
231
+ {
232
+ "href" => "http://example.com",
233
+ "rel" => "self"
234
+ }
235
+ ]
236
+ }
237
+ end
238
+ end
@@ -0,0 +1,60 @@
1
+ require "test_helper"
2
+
3
+ require "json_pointer"
4
+
5
+ describe JsonPointer::Evaluator do
6
+ before do
7
+ @evaluator = JsonPointer::Evaluator.new(data)
8
+ end
9
+
10
+ it "evaluates pointers according to spec" do
11
+ assert_equal data, @evaluator.evaluate("")
12
+ assert_equal ["bar", "baz"], @evaluator.evaluate("/foo")
13
+ assert_equal "bar", @evaluator.evaluate("/foo/0")
14
+ assert_equal 0, @evaluator.evaluate("/")
15
+ assert_equal 1, @evaluator.evaluate("/a~1b")
16
+ assert_equal 2, @evaluator.evaluate("/c%d")
17
+ assert_equal 3, @evaluator.evaluate("/e^f")
18
+ assert_equal 4, @evaluator.evaluate("/g|h")
19
+ assert_equal 5, @evaluator.evaluate("/i\\j")
20
+ assert_equal 6, @evaluator.evaluate("/k\"l")
21
+ assert_equal 7, @evaluator.evaluate("/ ")
22
+ assert_equal 8, @evaluator.evaluate("/m~0n")
23
+ end
24
+
25
+ it "takes a leading #" do
26
+ assert_equal 0, @evaluator.evaluate("#/")
27
+ end
28
+
29
+ it "returns nils on missing values" do
30
+ assert_nil @evaluator.evaluate("/bar")
31
+ end
32
+
33
+ it "raises when a path doesn't being with /" do
34
+ e = assert_raises(RuntimeError) { @evaluator.evaluate("foo") }
35
+ assert_equal %{Path must begin with a leading "/": foo.}, e.message
36
+ e = assert_raises(RuntimeError) { @evaluator.evaluate("#foo") }
37
+ assert_equal %{Path must begin with a leading "/": #foo.}, e.message
38
+ end
39
+
40
+ it "raises when a non-digit is specified on an array" do
41
+ e = assert_raises(RuntimeError) { @evaluator.evaluate("/foo/bar") }
42
+ assert_equal %{Key operating on an array must be a digit or "-": bar.},
43
+ e.message
44
+ end
45
+
46
+ def data
47
+ {
48
+ "foo" => ["bar", "baz"],
49
+ "" => 0,
50
+ "a/b" => 1,
51
+ "c%d" => 2,
52
+ "e^f" => 3,
53
+ "g|h" => 4,
54
+ "i\\j" => 5,
55
+ "k\"l" => 6,
56
+ " " => 7,
57
+ "m~n" => 8
58
+ }
59
+ end
60
+ end
@@ -0,0 +1,230 @@
1
+ require "test_helper"
2
+
3
+ require "json_schema"
4
+
5
+ describe JsonSchema::Parser do
6
+ it "parses the basic attributes of a schema" do
7
+ schema = parse
8
+ assert_nil schema.id
9
+ assert_equal "Example API", schema.title
10
+ assert_equal "An example API.", schema.description
11
+ assert_equal ["object"], schema.type
12
+ assert_equal "/", schema.uri
13
+ end
14
+
15
+ it "parses subschemas" do
16
+ schema = parse.definitions["app"]
17
+ assert_nil schema.reference
18
+ assert_equal "App", schema.title
19
+ assert_equal "An app.", schema.description
20
+ assert_equal "schemata/app", schema.id
21
+ assert_equal ["object"], schema.type
22
+ assert_equal "/schemata/app", schema.uri
23
+ refute_nil schema.parent
24
+ end
25
+
26
+ it "parses sub-subschemas" do
27
+ schema = parse.definitions["app"].definitions["name"]
28
+ assert_nil schema.reference
29
+ assert_equal "hello-world", schema.default
30
+ assert_equal "unique name of app", schema.description
31
+ assert_equal ["string"], schema.type
32
+ assert_equal "/schemata/app", schema.uri
33
+ refute_nil schema.parent
34
+ end
35
+
36
+ it "parses references" do
37
+ schema = parse.properties["app"]
38
+ refute_nil schema.reference
39
+ assert_nil schema.reference.uri
40
+ assert_equal "#/definitions/app", schema.reference.pointer
41
+ refute_nil schema.parent
42
+ end
43
+
44
+ it "parses enum validation" do
45
+ schema = parse.definitions["app"].definitions["visibility"]
46
+ assert_equal ["private", "public"], schema.enum
47
+ end
48
+
49
+ it "parses array validations" do
50
+ schema = parse.definitions["app"].definitions["flags"]
51
+ assert_equal /^[a-z][a-z\-]*[a-z]$/, schema.items.pattern
52
+ assert_equal 1, schema.min_items
53
+ assert_equal 10, schema.max_items
54
+ assert_equal true, schema.unique_items
55
+ end
56
+
57
+ it "parses array items tuple validation" do
58
+ pointer("#/definitions/app/definitions/flags").merge!(
59
+ "items" => [
60
+ { "enum" => ["bamboo", "cedar"] },
61
+ { "enum" => ["http", "https"] }
62
+ ]
63
+ )
64
+ schema = parse.definitions["app"].definitions["flags"]
65
+ assert_equal ["bamboo", "cedar"], schema.items[0].enum
66
+ assert_equal ["http", "https"], schema.items[1].enum
67
+ end
68
+
69
+ it "parses integer validations" do
70
+ schema = parse.definitions["app"].definitions["id"]
71
+ assert_equal 0, schema.min
72
+ assert_equal true, schema.min_exclusive
73
+ assert_equal 10000, schema.max
74
+ assert_equal false, schema.max_exclusive
75
+ assert_equal 1, schema.multiple_of
76
+ end
77
+
78
+ it "parses number validations" do
79
+ schema = parse.definitions["app"].definitions["cost"]
80
+ assert_equal 0.0, schema.min
81
+ assert_equal false, schema.min_exclusive
82
+ assert_equal 1000.0, schema.max
83
+ assert_equal true, schema.max_exclusive
84
+ assert_equal 0.01, schema.multiple_of
85
+ end
86
+
87
+ it "parses the basic set of object validations" do
88
+ schema = parse.definitions["app"]
89
+ assert_equal false, schema.additional_properties
90
+ assert_equal 10, schema.max_properties
91
+ assert_equal 1, schema.min_properties
92
+ assert_equal ["name"], schema.required
93
+ end
94
+
95
+ it "parses the dependencies object validation" do
96
+ schema = parse.definitions["app"]
97
+ assert_equal ["ssl"], schema.dependencies["production"]
98
+ assert_equal 20.0, schema.dependencies["ssl"].properties["cost"].min
99
+ end
100
+
101
+ it "parses the patternProperties object validation" do
102
+ schema = parse.definitions["app"].definitions["config_vars"]
103
+ property = schema.pattern_properties.first
104
+ assert_equal /^\w+$/, property[0]
105
+ assert_equal ["null", "string"], property[1].type
106
+ end
107
+
108
+ # couldn't think of any non-contrived examples to work with here
109
+ it "parses the basic set of schema validations" do
110
+ schema = parse.definitions["app"].definitions["contrived"]
111
+ assert_equal 2, schema.all_of.count
112
+ assert_equal 2, schema.one_of.count
113
+ assert schema.not
114
+ end
115
+
116
+ it "parses the anyOf schema validation" do
117
+ schema = parse.definitions["app"].definitions["identity"]
118
+ assert_equal 2, schema.any_of.count
119
+ assert_equal "/schemata/app#/definitions/id", schema.any_of[0].reference.to_s
120
+ assert_equal "/schemata/app#/definitions/name", schema.any_of[1].reference.to_s
121
+ end
122
+
123
+ it "parses basic set of string validations" do
124
+ schema = parse.definitions["app"].definitions["name"]
125
+ assert_equal 30, schema.max_length
126
+ assert_equal 3, schema.min_length
127
+ assert_equal /^[a-z][a-z0-9-]{3,30}$/, schema.pattern
128
+ end
129
+
130
+ it "parses hypermedia links" do
131
+ pointer("#/definitions/app").merge!(
132
+ "links" => [
133
+ "description" => "Create a new app.",
134
+ "href" => "/apps",
135
+ "method" => "POST",
136
+ "rel" => "create",
137
+ "schema" => {
138
+ "properties" => {
139
+ "name" => {
140
+ "$ref" => "#/definitions/app/definitions/name"
141
+ },
142
+ }
143
+ }
144
+ ]
145
+ )
146
+ schema = parse.definitions["app"]
147
+ link = schema.links[0]
148
+ assert_equal schema, link.parent
149
+ assert_equal "Create a new app.", link.description
150
+ assert_equal "/apps", link.href
151
+ assert_equal :post, link.method
152
+ assert_equal "create", link.rel
153
+ assert_equal "#/definitions/app/definitions/name",
154
+ link.schema.properties["name"].reference.pointer
155
+ end
156
+
157
+ it "parses hypermedia media" do
158
+ pointer("#/definitions/app/media").merge!(
159
+ "binaryEncoding" => "base64",
160
+ "type" => "image/png"
161
+ )
162
+ schema = parse.definitions["app"]
163
+ media = JsonSchema::Schema::Media.new
164
+ assert_equal "base64", schema.media.binary_encoding
165
+ assert_equal "image/png", schema.media.type
166
+ end
167
+
168
+ it "parses hypermedia pathStart" do
169
+ pointer("#/definitions/app").merge!(
170
+ "pathStart" => "/v2"
171
+ )
172
+ schema = parse.definitions["app"]
173
+ assert_equal "/v2", schema.path_start
174
+ end
175
+
176
+ it "parses hypermedia readOnly" do
177
+ pointer("#/definitions/app").merge!(
178
+ "readOnly" => true
179
+ )
180
+ schema = parse.definitions["app"]
181
+ assert_equal true, schema.read_only
182
+ end
183
+
184
+ it "errors on non-string ids" do
185
+ schema_sample["id"] = 4
186
+ refute parse
187
+ assert_includes error_messages, %{Expected "id" to be of type "string"; value was: 4.}
188
+ end
189
+
190
+ it "errors on non-string titles" do
191
+ schema_sample["title"] = 4
192
+ refute parse
193
+ assert_includes error_messages, %{Expected "title" to be of type "string"; value was: 4.}
194
+ end
195
+
196
+ it "errors on non-string descriptions" do
197
+ schema_sample["description"] = 4
198
+ refute parse
199
+ assert_includes error_messages, %{Expected "description" to be of type "string"; value was: 4.}
200
+ end
201
+
202
+ it "errors on non-array and non-string types" do
203
+ schema_sample["type"] = 4
204
+ refute parse
205
+ assert_includes error_messages, %{Expected "type" to be of type "array/string"; value was: 4.}
206
+ end
207
+
208
+ it "errors on unknown types" do
209
+ schema_sample["type"] = ["float", "double"]
210
+ refute parse
211
+ assert_includes error_messages, %{Unknown types: double, float.}
212
+ end
213
+
214
+ def error_messages
215
+ @parser.errors.map { |e| e.message }
216
+ end
217
+
218
+ def parse
219
+ @parser = JsonSchema::Parser.new
220
+ @parser.parse(schema_sample)
221
+ end
222
+
223
+ def pointer(path)
224
+ JsonPointer.evaluate(schema_sample, path)
225
+ end
226
+
227
+ def schema_sample
228
+ @schema_sample ||= DataScaffold.schema_sample
229
+ end
230
+ end