json_schema 0.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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