-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathjsonschema.rb
303 lines (273 loc) · 10.6 KB
/
jsonschema.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
# vim: fileencoding=utf-8
module JSON
class Schema
VERSION = '2.0.0'
class ValueError < Exception;end
class Undefined;end
TypesMap = {
"string" => String,
"integer" => [Integer, Fixnum],
"number" => [Integer, Float, Fixnum, Numeric],
"boolean" => [TrueClass, FalseClass],
"object" => Hash,
"array" => Array,
"null" => NilClass,
"any" => nil
}
TypesList = [String, Integer, Float, Fixnum, Numeric, TrueClass, FalseClass, Hash, Array, NilClass]
def initialize interactive
@interactive = interactive
@refmap = {}
end
def check_property value, schema, key, parent
if schema
# if @interactive && schema['readonly']
# raise ValueError, "#{key} is a readonly field , it can not be changed"
# end
if schema['id']
@refmap[schema['id']] = schema
end
if schema['extends']
check_property(value, schema['extends'], key, parent)
end
if value == Undefined
unless schema['optional']
raise ValueError, "#{key} is missing and it is not optional"
end
# default
if @interactive && !parent.include?(key) && !schema['default'].nil?
unless schema["readonly"]
parent[key] = schema['default']
end
end
else
# type
if schema['type']
check_type(value, schema['type'], key, parent)
end
# disallow
if schema['disallow']
flag = true
begin
check_type(value, schema['disallow'], key, parent)
rescue ValueError
flag = false
end
raise ValueError, "disallowed value was matched" if flag
end
unless value.nil?
if value.instance_of? Array
if schema['items']
if schema['items'].instance_of?(Array)
schema['items'].each_with_index {|val, index|
check_property(undefined_check(value, index), schema['items'][index], index, value)
}
if schema.include?('additionalProperties')
additional = schema['additionalProperties']
if additional.instance_of?(FalseClass)
if schema['items'].size < value.size
raise ValueError, "There are more values in the array than are allowed by the items and additionalProperties restrictions."
end
else
value.each_with_index {|val, index|
check_property(undefined_check(value, index), schema['additionalProperties'], index, value)
}
end
end
else
value.each_with_index {|val, index|
check_property(undefined_check(value, index), schema['items'], index, value)
}
end
end
if schema['minItems'] && value.size < schema['minItems']
raise ValueError, "There must be a minimum of #{schema['minItems']} in the array"
end
if schema['maxItems'] && value.size > schema['maxItems']
raise ValueError, "There must be a maximum of #{schema['maxItems']} in the array"
end
elsif schema['properties']
check_object(value, schema['properties'], schema['additionalProperties'])
elsif schema.include?('additionalProperties')
additional = schema['additionalProperties']
unless additional.instance_of?(TrueClass)
if additional.instance_of?(Hash) || additional.instance_of?(FalseClass)
properties = {}
value.each {|k, val|
if additional.instance_of?(FalseClass)
raise ValueError, "Additional properties not defined by 'properties' are not allowed in field '#{k}'"
else
check_property(val, schema['additionalProperties'], k, value)
end
}
else
raise ValueError, "additionalProperties schema definition for field '#{}' is not an object"
end
end
end
if value.instance_of?(String)
# pattern
if schema['pattern'] && !(value =~ Regexp.new(schema['pattern']))
raise ValueError, "does not match the regex pattern #{schema['pattern']}"
end
strlen = value.split(//).size
# maxLength
if schema['maxLength'] && strlen > schema['maxLength']
raise ValueError, "may only be #{schema['maxLength']} characters long"
end
# minLength
if schema['minLength'] && strlen < schema['minLength']
raise ValueError, "must be at least #{schema['minLength']} characters long"
end
end
if value.kind_of?(Numeric)
# minimum + minimumCanEqual
if schema['minimum']
minimumCanEqual = schema.fetch('minimumCanEqual', Undefined)
if minimumCanEqual == Undefined || minimumCanEqual
if value < schema['minimum']
raise ValueError, "must have a minimum value of #{schema['minimum']}"
end
else
if value <= schema['minimum']
raise ValueError, "must have a minimum value of #{schema['minimum']}"
end
end
end
# maximum + maximumCanEqual
if schema['maximum']
maximumCanEqual = schema.fetch('maximumCanEqual', Undefined)
if maximumCanEqual == Undefined || maximumCanEqual
if value > schema['maximum']
raise ValueError, "must have a maximum value of #{schema['maximum']}"
end
else
if value >= schema['maximum']
raise ValueError, "must have a maximum value of #{schema['maximum']}"
end
end
end
# maxDecimal
if schema['maxDecimal'] && schema['maxDecimal'].kind_of?(Numeric)
if value.to_s =~ /\.\d{#{schema['maxDecimal']+1},}/
raise ValueError, "may only have #{schema['maxDecimal']} digits of decimal places"
end
end
end
# enum
if schema['enum']
unless(schema['enum'].detect{|enum| enum == value })
raise ValueError, "does not have a value in the enumeration #{schema['enum'].join(", ")}"
end
end
# description
if schema['description'] && !schema['description'].instance_of?(String)
raise ValueError, "The description for field '#{value}' must be a string"
end
# title
if schema['title'] && !schema['title'].instance_of?(String)
raise ValueError, "The title for field '#{value}' must be a string"
end
# format
if schema['format']
end
end
end
end
end
def check_object value, object_type_def, additional
if object_type_def.instance_of? Hash
if !value.instance_of?(Hash) || value.instance_of?(Array)
raise ValueError, "an object is required"
end
object_type_def.each {|key, odef|
if key.index('__') != 0
check_property(undefined_check(value, key), odef, key, value)
end
}
end
value.each {|key, val|
if key.index('__') != 0 && object_type_def && !object_type_def[key] && additional == false
raise ValueError, "#{value.class} The property #{key} is not defined in the schema and the schema does not allow additional properties"
end
requires = object_type_def && object_type_def[key] && object_type_def[key]['requires']
if requires && !value.include?(requires)
raise ValueError, "the presence of the property #{key} requires that #{requires} also be present"
end
if object_type_def && object_type_def.instance_of?(Hash) && !object_type_def.include?(key)
check_property(val, additional, key, value)
end
if !@interactive && val && val['$schema']
check_property(val, val['$schema'], key, value)
end
}
end
def check_type value, type, key, parent
converted_fieldtype = convert_type(type)
if converted_fieldtype
if converted_fieldtype.instance_of? Array
datavalid = false
converted_fieldtype.each do |t|
begin
check_type(value, t, key, parent)
datavalid = true
break
rescue ValueError
next
end
end
unless datavalid
raise ValueError, "#{value.class} value found, but a #{type} is required"
end
elsif converted_fieldtype.instance_of? Hash
check_property(value, type, key, parent)
else
unless value.instance_of? converted_fieldtype
raise ValueError, "#{value.class} value found, but a #{type} is required"
end
end
end
end
def undefined_check value, key
value.fetch(key, Undefined)
end
def convert_type fieldtype
if TypesList.include?(fieldtype) || fieldtype.kind_of?(Hash)
return fieldtype
elsif fieldtype.kind_of? Array
converted_fields = []
fieldtype.each do |subfieldtype|
converted_fields << convert_type(subfieldtype)
end
return converted_fields
elsif !fieldtype
return nil
else
fieldtype = fieldtype.to_s
if TypesMap.include?(fieldtype)
return TypesMap[fieldtype]
else
raise ValueError, "Field type '#{fieldtype}' is not supported."
end
end
end
def validate instance, schema
@tree = {
'self' => instance
}
if schema
check_property(instance, schema, 'self', @tree)
elsif instance && instance['$schema']
# self definition schema
check_property(instance, instance['$schema'], 'self', @tree)
end
return @tree['self']
end
class << self
def validate data, schema=nil, interactive=true
validator = JSON::Schema.new(interactive)
validator.validate(data, schema)
end
end
end
end