json_schema 0.0.7 → 0.0.9
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.
- data/README.md +9 -0
- data/bin/validate-schema +37 -0
- data/lib/commands/validate_schema.rb +102 -0
- data/lib/json_reference.rb +18 -2
- data/lib/json_schema.rb +1 -0
- data/lib/json_schema/document_store.rb +48 -0
- data/lib/json_schema/parser.rb +36 -14
- data/lib/json_schema/reference_expander.rb +128 -77
- data/lib/json_schema/schema.rb +140 -8
- data/lib/json_schema/validator.rb +22 -7
- data/schemas/hyper-schema.json +168 -0
- data/schemas/schema.json +150 -0
- data/test/commands/validate_schema_test.rb +103 -0
- data/test/json_reference/reference_test.rb +45 -0
- data/test/json_schema/parser_test.rb +18 -1
- data/test/json_schema/reference_expander_test.rb +81 -39
- data/test/json_schema/validator_test.rb +60 -4
- metadata +11 -3
data/lib/json_schema/schema.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
require "json"
|
2
|
+
|
1
3
|
module JsonSchema
|
2
4
|
class Schema
|
3
5
|
@@copyable = []
|
@@ -13,33 +15,116 @@ module JsonSchema
|
|
13
15
|
class_eval("def #{attr} ; !@#{attr}.nil? ? @#{attr} : #{default} ; end")
|
14
16
|
end
|
15
17
|
|
18
|
+
def initialize
|
19
|
+
@clones = Set.new
|
20
|
+
end
|
21
|
+
|
16
22
|
# Rather than a normal schema, the node may be a JSON Reference. In this
|
17
23
|
# case, no other attributes will be filled in except for #parent.
|
18
|
-
|
24
|
+
attr_accessor :reference
|
25
|
+
|
26
|
+
attr_copyable :expanded
|
19
27
|
|
20
|
-
#
|
21
|
-
# Pointer
|
28
|
+
# A reference to the data which the Schema was initialized from. Used for
|
29
|
+
# resolving JSON Pointer references.
|
30
|
+
#
|
31
|
+
# Type: Hash
|
22
32
|
attr_copyable :data
|
23
33
|
|
24
|
-
#
|
34
|
+
#
|
35
|
+
# Relations
|
36
|
+
#
|
37
|
+
|
38
|
+
# Parent Schema object. Child may come from any of `definitions`,
|
39
|
+
# `properties`, `anyOf`, etc.
|
40
|
+
#
|
41
|
+
# Type: Schema
|
25
42
|
attr_copyable :parent
|
26
43
|
|
27
|
-
#
|
44
|
+
# Collection of clones of this schema object, meaning all Schemas that were
|
45
|
+
# initialized after the original. Used for JSON Reference expansion. The
|
46
|
+
# only copy not present in this set is the original Schema object.
|
47
|
+
#
|
48
|
+
# Type: Set[Schema]
|
49
|
+
attr_copyable :clones
|
50
|
+
|
51
|
+
# The normalized URI of this schema. Note that child schemas inherit a URI
|
52
|
+
# from their parent unless they have one explicitly defined, so this is
|
53
|
+
# likely not a unique value in any given schema hierarchy.
|
54
|
+
#
|
55
|
+
# Type: String
|
28
56
|
attr_copyable :uri
|
29
57
|
|
30
|
-
#
|
58
|
+
#
|
59
|
+
# Metadata
|
60
|
+
#
|
61
|
+
|
62
|
+
# Alters resolution scope. This value is used along with the parent scope's
|
63
|
+
# URI to build a new address for this schema. Relative ID's will append to
|
64
|
+
# the parent, and absolute URI's will replace it.
|
65
|
+
#
|
66
|
+
# Type: String
|
31
67
|
attr_copyable :id
|
68
|
+
|
69
|
+
# Short title of the schema.
|
70
|
+
#
|
71
|
+
# Type: String
|
32
72
|
attr_copyable :title
|
73
|
+
|
74
|
+
# More detailed description of the schema.
|
75
|
+
#
|
76
|
+
# Type: String
|
33
77
|
attr_copyable :description
|
78
|
+
|
79
|
+
# Default JSON value for this particular schema
|
80
|
+
#
|
81
|
+
# Type: [any]
|
34
82
|
attr_copyable :default
|
35
83
|
|
36
|
-
#
|
84
|
+
#
|
85
|
+
# Validation: Any
|
86
|
+
#
|
87
|
+
|
88
|
+
# A collection of subschemas of which data must validate against the full
|
89
|
+
# set of to be valid.
|
90
|
+
#
|
91
|
+
# Type: Array[Schema]
|
37
92
|
attr_copyable :all_of
|
93
|
+
|
94
|
+
# A collection of subschemas of which data must validate against any schema
|
95
|
+
# in the set to be be valid.
|
96
|
+
#
|
97
|
+
# Type: Array[Schema]
|
38
98
|
attr_copyable :any_of
|
99
|
+
|
100
|
+
# A collection of inlined subschemas. Standard convention is to subschemas
|
101
|
+
# here and reference them from elsewhere.
|
102
|
+
#
|
103
|
+
# Type: Hash[String => Schema]
|
39
104
|
attr_copyable :definitions
|
105
|
+
|
106
|
+
# A collection of objects that must include the data for it to be valid.
|
107
|
+
#
|
108
|
+
# Type: Array
|
40
109
|
attr_copyable :enum
|
110
|
+
|
111
|
+
# A collection of subschemas of which data must validate against exactly
|
112
|
+
# one of to be valid.
|
113
|
+
#
|
114
|
+
# Type: Array[Schema]
|
41
115
|
attr_copyable :one_of
|
116
|
+
|
117
|
+
|
118
|
+
# A subschema which data must not validate against to be valid.
|
119
|
+
#
|
120
|
+
# Type: Schema
|
42
121
|
attr_copyable :not
|
122
|
+
|
123
|
+
# An array of types that data is allowed to be. The spec allows this to be
|
124
|
+
# a string as well, but the parser will always normalize this to an array
|
125
|
+
# of strings.
|
126
|
+
#
|
127
|
+
# Type: Array[String]
|
43
128
|
attr_copyable :type
|
44
129
|
|
45
130
|
# validation: array
|
@@ -97,7 +182,7 @@ module JsonSchema
|
|
97
182
|
|
98
183
|
# allow booleans to be access with question mark
|
99
184
|
alias :additional_items? :additional_items
|
100
|
-
alias :
|
185
|
+
alias :expanded? :expanded
|
101
186
|
alias :max_exclusive? :max_exclusive
|
102
187
|
alias :min_exclusive? :min_exclusive
|
103
188
|
alias :read_only? :read_only
|
@@ -123,6 +208,53 @@ module JsonSchema
|
|
123
208
|
true
|
124
209
|
end
|
125
210
|
|
211
|
+
def inspect
|
212
|
+
str = inspect_schema
|
213
|
+
str = JSON.pretty_generate(str).gsub(/\\?"/, '') if str.is_a?(Hash)
|
214
|
+
"\#<JsonSchema::Schema #{str}>"
|
215
|
+
end
|
216
|
+
|
217
|
+
def inspect_schema
|
218
|
+
if reference
|
219
|
+
str = reference.to_s
|
220
|
+
str += " [EXPANDED]" if expanded?
|
221
|
+
str += " [CLONE]" if !original?
|
222
|
+
str
|
223
|
+
else
|
224
|
+
hash = {}
|
225
|
+
@@copyable.each do |copyable|
|
226
|
+
next if [:@clones, :@data, :@parent, :@uri].include?(copyable)
|
227
|
+
if value = instance_variable_get(copyable)
|
228
|
+
if value.is_a?(Array)
|
229
|
+
if !value.empty?
|
230
|
+
hash[copyable] = value.map { |v| inspect_value(v) }
|
231
|
+
end
|
232
|
+
elsif value.is_a?(Hash)
|
233
|
+
if !value.empty?
|
234
|
+
hash[copyable] =
|
235
|
+
Hash[*value.map { |k, v| [k, inspect_value(v)] }.flatten]
|
236
|
+
end
|
237
|
+
else
|
238
|
+
hash[copyable] = inspect_value(value)
|
239
|
+
end
|
240
|
+
end
|
241
|
+
end
|
242
|
+
hash
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
246
|
+
def inspect_value(value)
|
247
|
+
if value.is_a?(Schema)
|
248
|
+
value.inspect_schema
|
249
|
+
else
|
250
|
+
value.inspect
|
251
|
+
end
|
252
|
+
end
|
253
|
+
|
254
|
+
def original?
|
255
|
+
!clones.include?(self)
|
256
|
+
end
|
257
|
+
|
126
258
|
def validate(data)
|
127
259
|
validator = Validator.new(self)
|
128
260
|
valid = validator.validate(data)
|
@@ -86,13 +86,25 @@ module JsonSchema
|
|
86
86
|
end
|
87
87
|
|
88
88
|
def validate_additional_properties(schema, data, errors)
|
89
|
-
return true if schema.additional_properties
|
90
|
-
|
91
|
-
|
89
|
+
return true if schema.additional_properties == true
|
90
|
+
|
91
|
+
extra = data.keys - schema.properties.keys
|
92
|
+
|
93
|
+
# schema indicates that all properties not in `properties` should be
|
94
|
+
# validated according to subschema
|
95
|
+
if schema.additional_properties.is_a?(Schema)
|
96
|
+
extra.each do |key|
|
97
|
+
validate_data(schema.additional_properties, data[key], errors)
|
98
|
+
end
|
99
|
+
# boolean indicates whether additional properties are allowed
|
92
100
|
else
|
93
|
-
|
94
|
-
|
95
|
-
|
101
|
+
if extra.empty?
|
102
|
+
true
|
103
|
+
else
|
104
|
+
message = %{Extra keys in object: #{extra.sort.join(", ")}.}
|
105
|
+
errors << SchemaError.new(schema, message)
|
106
|
+
false
|
107
|
+
end
|
96
108
|
end
|
97
109
|
end
|
98
110
|
|
@@ -143,8 +155,10 @@ module JsonSchema
|
|
143
155
|
data =~ IPV4_PATTERN
|
144
156
|
when "ipv6"
|
145
157
|
data =~ IPV6_PATTERN
|
158
|
+
when "regex"
|
159
|
+
Regexp.new(data) rescue false
|
146
160
|
when "uri"
|
147
|
-
data
|
161
|
+
URI.parse(data) rescue false
|
148
162
|
when "uuid"
|
149
163
|
data =~ UUID_PATTERN
|
150
164
|
end
|
@@ -347,6 +361,7 @@ module JsonSchema
|
|
347
361
|
return true if schema.properties.empty?
|
348
362
|
valid = true
|
349
363
|
schema.properties.each do |key, subschema|
|
364
|
+
|
350
365
|
if value = data[key]
|
351
366
|
valid = strict_and valid, validate_data(subschema, value, errors)
|
352
367
|
end
|
@@ -0,0 +1,168 @@
|
|
1
|
+
{
|
2
|
+
"$schema": "http://json-schema.org/draft-04/hyper-schema#",
|
3
|
+
"id": "http://json-schema.org/draft-04/hyper-schema#",
|
4
|
+
"title": "JSON Hyper-Schema",
|
5
|
+
"allOf": [
|
6
|
+
{
|
7
|
+
"$ref": "http://json-schema.org/draft-04/schema#"
|
8
|
+
}
|
9
|
+
],
|
10
|
+
"properties": {
|
11
|
+
"additionalItems": {
|
12
|
+
"anyOf": [
|
13
|
+
{
|
14
|
+
"type": "boolean"
|
15
|
+
},
|
16
|
+
{
|
17
|
+
"$ref": "#"
|
18
|
+
}
|
19
|
+
]
|
20
|
+
},
|
21
|
+
"additionalProperties": {
|
22
|
+
"anyOf": [
|
23
|
+
{
|
24
|
+
"type": "boolean"
|
25
|
+
},
|
26
|
+
{
|
27
|
+
"$ref": "#"
|
28
|
+
}
|
29
|
+
]
|
30
|
+
},
|
31
|
+
"dependencies": {
|
32
|
+
"additionalProperties": {
|
33
|
+
"anyOf": [
|
34
|
+
{
|
35
|
+
"$ref": "#"
|
36
|
+
},
|
37
|
+
{
|
38
|
+
"type": "array"
|
39
|
+
}
|
40
|
+
]
|
41
|
+
}
|
42
|
+
},
|
43
|
+
"items": {
|
44
|
+
"anyOf": [
|
45
|
+
{
|
46
|
+
"$ref": "#"
|
47
|
+
},
|
48
|
+
{
|
49
|
+
"$ref": "#/definitions/schemaArray"
|
50
|
+
}
|
51
|
+
]
|
52
|
+
},
|
53
|
+
"definitions": {
|
54
|
+
"additionalProperties": {
|
55
|
+
"$ref": "#"
|
56
|
+
}
|
57
|
+
},
|
58
|
+
"patternProperties": {
|
59
|
+
"additionalProperties": {
|
60
|
+
"$ref": "#"
|
61
|
+
}
|
62
|
+
},
|
63
|
+
"properties": {
|
64
|
+
"additionalProperties": {
|
65
|
+
"$ref": "#"
|
66
|
+
}
|
67
|
+
},
|
68
|
+
"allOf": {
|
69
|
+
"$ref": "#/definitions/schemaArray"
|
70
|
+
},
|
71
|
+
"anyOf": {
|
72
|
+
"$ref": "#/definitions/schemaArray"
|
73
|
+
},
|
74
|
+
"oneOf": {
|
75
|
+
"$ref": "#/definitions/schemaArray"
|
76
|
+
},
|
77
|
+
"not": {
|
78
|
+
"$ref": "#"
|
79
|
+
},
|
80
|
+
|
81
|
+
"links": {
|
82
|
+
"type": "array",
|
83
|
+
"items": {
|
84
|
+
"$ref": "#/definitions/linkDescription"
|
85
|
+
}
|
86
|
+
},
|
87
|
+
"fragmentResolution": {
|
88
|
+
"type": "string"
|
89
|
+
},
|
90
|
+
"media": {
|
91
|
+
"type": "object",
|
92
|
+
"properties": {
|
93
|
+
"type": {
|
94
|
+
"description": "A media type, as described in RFC 2046",
|
95
|
+
"type": "string"
|
96
|
+
},
|
97
|
+
"binaryEncoding": {
|
98
|
+
"description": "A content encoding scheme, as described in RFC 2045",
|
99
|
+
"type": "string"
|
100
|
+
}
|
101
|
+
}
|
102
|
+
},
|
103
|
+
"pathStart": {
|
104
|
+
"description": "Instances' URIs must start with this value for this schema to apply to them",
|
105
|
+
"type": "string",
|
106
|
+
"format": "uri"
|
107
|
+
}
|
108
|
+
},
|
109
|
+
"definitions": {
|
110
|
+
"schemaArray": {
|
111
|
+
"type": "array",
|
112
|
+
"items": {
|
113
|
+
"$ref": "#"
|
114
|
+
}
|
115
|
+
},
|
116
|
+
"linkDescription": {
|
117
|
+
"title": "Link Description Object",
|
118
|
+
"type": "object",
|
119
|
+
"required": [ "href", "rel" ],
|
120
|
+
"properties": {
|
121
|
+
"href": {
|
122
|
+
"description": "a URI template, as defined by RFC 6570, with the addition of the $, ( and ) characters for pre-processing",
|
123
|
+
"type": "string"
|
124
|
+
},
|
125
|
+
"rel": {
|
126
|
+
"description": "relation to the target resource of the link",
|
127
|
+
"type": "string"
|
128
|
+
},
|
129
|
+
"title": {
|
130
|
+
"description": "a title for the link",
|
131
|
+
"type": "string"
|
132
|
+
},
|
133
|
+
"targetSchema": {
|
134
|
+
"description": "JSON Schema describing the link target",
|
135
|
+
"$ref": "#"
|
136
|
+
},
|
137
|
+
"mediaType": {
|
138
|
+
"description": "media type (as defined by RFC 2046) describing the link target",
|
139
|
+
"type": "string"
|
140
|
+
},
|
141
|
+
"method": {
|
142
|
+
"description": "method for requesting the target of the link (e.g. for HTTP this might be \"GET\" or \"DELETE\")",
|
143
|
+
"type": "string"
|
144
|
+
},
|
145
|
+
"encType": {
|
146
|
+
"description": "The media type in which to submit data along with the request",
|
147
|
+
"type": "string",
|
148
|
+
"default": "application/json"
|
149
|
+
},
|
150
|
+
"schema": {
|
151
|
+
"description": "Schema describing the data to submit along with the request",
|
152
|
+
"$ref": "#"
|
153
|
+
}
|
154
|
+
}
|
155
|
+
}
|
156
|
+
},
|
157
|
+
"links": [
|
158
|
+
{
|
159
|
+
"rel": "self",
|
160
|
+
"href": "{+id}"
|
161
|
+
},
|
162
|
+
{
|
163
|
+
"rel": "full",
|
164
|
+
"href": "{+($ref)}"
|
165
|
+
}
|
166
|
+
]
|
167
|
+
}
|
168
|
+
|
data/schemas/schema.json
ADDED
@@ -0,0 +1,150 @@
|
|
1
|
+
{
|
2
|
+
"id": "http://json-schema.org/draft-04/schema#",
|
3
|
+
"$schema": "http://json-schema.org/draft-04/schema#",
|
4
|
+
"description": "Core schema meta-schema",
|
5
|
+
"definitions": {
|
6
|
+
"schemaArray": {
|
7
|
+
"type": "array",
|
8
|
+
"minItems": 1,
|
9
|
+
"items": { "$ref": "#" }
|
10
|
+
},
|
11
|
+
"positiveInteger": {
|
12
|
+
"type": "integer",
|
13
|
+
"minimum": 0
|
14
|
+
},
|
15
|
+
"positiveIntegerDefault0": {
|
16
|
+
"allOf": [ { "$ref": "#/definitions/positiveInteger" }, { "default": 0 } ]
|
17
|
+
},
|
18
|
+
"simpleTypes": {
|
19
|
+
"enum": [ "array", "boolean", "integer", "null", "number", "object", "string" ]
|
20
|
+
},
|
21
|
+
"stringArray": {
|
22
|
+
"type": "array",
|
23
|
+
"items": { "type": "string" },
|
24
|
+
"minItems": 1,
|
25
|
+
"uniqueItems": true
|
26
|
+
}
|
27
|
+
},
|
28
|
+
"type": "object",
|
29
|
+
"properties": {
|
30
|
+
"id": {
|
31
|
+
"type": "string",
|
32
|
+
"format": "uri"
|
33
|
+
},
|
34
|
+
"$schema": {
|
35
|
+
"type": "string",
|
36
|
+
"format": "uri"
|
37
|
+
},
|
38
|
+
"title": {
|
39
|
+
"type": "string"
|
40
|
+
},
|
41
|
+
"description": {
|
42
|
+
"type": "string"
|
43
|
+
},
|
44
|
+
"default": {},
|
45
|
+
"multipleOf": {
|
46
|
+
"type": "number",
|
47
|
+
"minimum": 0,
|
48
|
+
"exclusiveMinimum": true
|
49
|
+
},
|
50
|
+
"maximum": {
|
51
|
+
"type": "number"
|
52
|
+
},
|
53
|
+
"exclusiveMaximum": {
|
54
|
+
"type": "boolean",
|
55
|
+
"default": false
|
56
|
+
},
|
57
|
+
"minimum": {
|
58
|
+
"type": "number"
|
59
|
+
},
|
60
|
+
"exclusiveMinimum": {
|
61
|
+
"type": "boolean",
|
62
|
+
"default": false
|
63
|
+
},
|
64
|
+
"maxLength": { "$ref": "#/definitions/positiveInteger" },
|
65
|
+
"minLength": { "$ref": "#/definitions/positiveIntegerDefault0" },
|
66
|
+
"pattern": {
|
67
|
+
"type": "string",
|
68
|
+
"format": "regex"
|
69
|
+
},
|
70
|
+
"additionalItems": {
|
71
|
+
"anyOf": [
|
72
|
+
{ "type": "boolean" },
|
73
|
+
{ "$ref": "#" }
|
74
|
+
],
|
75
|
+
"default": {}
|
76
|
+
},
|
77
|
+
"items": {
|
78
|
+
"anyOf": [
|
79
|
+
{ "$ref": "#" },
|
80
|
+
{ "$ref": "#/definitions/schemaArray" }
|
81
|
+
],
|
82
|
+
"default": {}
|
83
|
+
},
|
84
|
+
"maxItems": { "$ref": "#/definitions/positiveInteger" },
|
85
|
+
"minItems": { "$ref": "#/definitions/positiveIntegerDefault0" },
|
86
|
+
"uniqueItems": {
|
87
|
+
"type": "boolean",
|
88
|
+
"default": false
|
89
|
+
},
|
90
|
+
"maxProperties": { "$ref": "#/definitions/positiveInteger" },
|
91
|
+
"minProperties": { "$ref": "#/definitions/positiveIntegerDefault0" },
|
92
|
+
"required": { "$ref": "#/definitions/stringArray" },
|
93
|
+
"additionalProperties": {
|
94
|
+
"anyOf": [
|
95
|
+
{ "type": "boolean" },
|
96
|
+
{ "$ref": "#" }
|
97
|
+
],
|
98
|
+
"default": {}
|
99
|
+
},
|
100
|
+
"definitions": {
|
101
|
+
"type": "object",
|
102
|
+
"additionalProperties": { "$ref": "#" },
|
103
|
+
"default": {}
|
104
|
+
},
|
105
|
+
"properties": {
|
106
|
+
"type": "object",
|
107
|
+
"additionalProperties": { "$ref": "#" },
|
108
|
+
"default": {}
|
109
|
+
},
|
110
|
+
"patternProperties": {
|
111
|
+
"type": "object",
|
112
|
+
"additionalProperties": { "$ref": "#" },
|
113
|
+
"default": {}
|
114
|
+
},
|
115
|
+
"dependencies": {
|
116
|
+
"type": "object",
|
117
|
+
"additionalProperties": {
|
118
|
+
"anyOf": [
|
119
|
+
{ "$ref": "#" },
|
120
|
+
{ "$ref": "#/definitions/stringArray" }
|
121
|
+
]
|
122
|
+
}
|
123
|
+
},
|
124
|
+
"enum": {
|
125
|
+
"type": "array",
|
126
|
+
"minItems": 1,
|
127
|
+
"uniqueItems": true
|
128
|
+
},
|
129
|
+
"type": {
|
130
|
+
"anyOf": [
|
131
|
+
{ "$ref": "#/definitions/simpleTypes" },
|
132
|
+
{
|
133
|
+
"type": "array",
|
134
|
+
"items": { "$ref": "#/definitions/simpleTypes" },
|
135
|
+
"minItems": 1,
|
136
|
+
"uniqueItems": true
|
137
|
+
}
|
138
|
+
]
|
139
|
+
},
|
140
|
+
"allOf": { "$ref": "#/definitions/schemaArray" },
|
141
|
+
"anyOf": { "$ref": "#/definitions/schemaArray" },
|
142
|
+
"oneOf": { "$ref": "#/definitions/schemaArray" },
|
143
|
+
"not": { "$ref": "#" }
|
144
|
+
},
|
145
|
+
"dependencies": {
|
146
|
+
"exclusiveMaximum": [ "maximum" ],
|
147
|
+
"exclusiveMinimum": [ "minimum" ]
|
148
|
+
},
|
149
|
+
"default": {}
|
150
|
+
}
|