sk-api 1.0.6 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -2,379 +2,252 @@
2
2
 
3
3
  module JSON
4
4
  class Schema
5
- VERSION = '1.0.0'
5
+ VERSION = '2.0.0'
6
6
  class ValueError < Exception;end
7
+ class Undefined;end
7
8
  TypesMap = {
8
9
  "string" => String,
9
- "integer" => Integer,
10
- "number" => [Integer, Float],
10
+ "integer" => [Integer, Fixnum],
11
+ "number" => [Integer, Float, Fixnum, Numeric],
11
12
  "boolean" => [TrueClass, FalseClass],
12
13
  "object" => Hash,
13
14
  "array" => Array,
14
15
  "null" => NilClass,
15
16
  "any" => nil
16
17
  }
17
- TypesList = [String, Integer, Float, TrueClass, FalseClass, Hash, Array, NilClass]
18
- DefaultSchema = {
19
- "id" => nil,
20
- "type" => nil,
21
- "properties" => nil,
22
- "items" => nil,
23
- "optional" => false,
24
- "additionalProperties" => nil,
25
- "requires" => nil,
26
- "identity" => nil,
27
- "minimum" => nil,
28
- "maximum" => nil,
29
- "minItems" => nil,
30
- "maxItems" => nil,
31
- "pattern" => nil,
32
- "maxLength" => nil,
33
- "minLength" => nil,
34
- "enum" => nil,
35
- "options" => nil,
36
- "readonly" => nil,
37
- "title" => nil,
38
- "description" => nil,
39
- "format" => nil,
40
- "default" => nil,
41
- "transient" => nil,
42
- "maxDecimal" => nil,
43
- "hidden" => nil,
44
- "disallow" => nil,
45
- "extends" => nil
46
- }
47
- def initialize interactive=true
18
+ TypesList = [String, Integer, Float, Fixnum, Numeric, TrueClass, FalseClass, Hash, Array, NilClass]
19
+ def initialize interactive
48
20
  @interactive = interactive
49
21
  @refmap = {}
50
22
  end
51
23
 
52
- def validate data, schema
53
- @refmap = {
54
- '$' => schema
55
- }
56
- _validate(data, schema)
57
- end
58
-
59
- private
60
- def validate_id x, fieldname, schema, id=nil
61
- unless id.nil?
62
- if id == '$'
63
- raise ValueError, "Reference id for field '#{fieldname}' cannot equal '$'"
24
+ def check_property value, schema, key, parent
25
+ return unless schema
26
+ # if id field is present, set refmap to schema
27
+ @refmap[schema['id']] = schema if schema['id']
28
+ # recurse with extended scheme
29
+ check_property(value, schema['extends'], key, parent) if schema['extends']
30
+ #
31
+ if value == Undefined
32
+ # value is NOT optional
33
+ raise ValueError, "#{key} is missing and it is not optional" unless schema['optional']
34
+ # a default value ??
35
+ if @interactive && !parent.include?(key) && !schema['default'].nil? && !schema["readonly"]
36
+ parent[key] = schema['default']
64
37
  end
65
- @refmap[id] = schema
66
- end
67
- return x
68
- end
69
-
70
- def validate_type x, fieldname, schema, fieldtype=nil
71
- converted_fieldtype = convert_type(fieldtype)
72
- fieldexists = true
73
- begin
74
- val = x.fetch(fieldname)
75
- rescue IndexError
76
- fieldexists = false
77
- ensure
78
- val = x[fieldname]
79
- end
80
-
81
- if converted_fieldtype && fieldexists
82
- # fld is optional, type is not nil, and field is nil
83
- return x if val == nil && schema['optional'] && converted_fieldtype != NilClass
84
- if converted_fieldtype.kind_of? Array
85
- datavalid = false
86
- converted_fieldtype.each do |type|
87
- begin
88
- validate_type(x, fieldname, type, type)
89
- datavalid = true
90
- break
91
- rescue ValueError
92
- next
93
- end
94
- end
95
- unless datavalid
96
- raise ValueError, "Value #{val} for field '#{fieldname}' is not of type #{fieldtype}"
97
- end
98
- elsif converted_fieldtype.kind_of? Hash
38
+ else # At least not undefined
39
+ # type given, validates if value is of given type(Hash, Array, String, ...)
40
+ # if val ist NOT NIL and required
41
+ check_type(value, schema['type'], key, parent) if schema['type'] #&& !value.nil? && !schema['optional']
42
+
43
+ # disallowed value type given
44
+ if schema['disallow']
45
+ flag = true
99
46
  begin
100
- __validate(fieldname, x, converted_fieldtype)
101
- rescue ValueError => e
102
- raise e
103
- end
104
- else
105
- unless val.kind_of? converted_fieldtype
106
- raise ValueError, "Value #{val} for field '#{fieldname}' is not of type #{fieldtype}"
47
+ check_type(value, schema['disallow'], key, parent)
48
+ rescue ValueError
49
+ flag = false
107
50
  end
51
+ raise ValueError, "disallowed value was matched" if flag
108
52
  end
109
- end
110
- return x
111
- end
112
53
 
113
- def validate_properties x, fieldname, schema, properties=nil
114
- if !properties.nil? && x[fieldname]
115
- value = x[fieldname]
116
- if value
117
- if value.kind_of? Hash
118
- if properties.kind_of? Hash
119
- properties.each do |key, val|
120
- __validate(key, value, val)
54
+ # dont go further for nil values
55
+ return if value.nil?
56
+ if value.instance_of? Array
57
+ if schema['items']
58
+ if schema['items'].instance_of?(Array)
59
+ schema['items'].each_with_index {|val, index|
60
+ check_property(undefined_check(value, index), schema['items'][index], index, value)
61
+ }
62
+ if schema.include?('additionalProperties')
63
+ additional = schema['additionalProperties']
64
+ if additional.instance_of?(FalseClass)
65
+ if schema['items'].size < value.size
66
+ raise ValueError, "There are more values in the array than are allowed by the items and additionalProperties restrictions."
67
+ end
68
+ else
69
+ value.each_with_index {|val, index|
70
+ check_property(undefined_check(value, index), schema['additionalProperties'], index, value)
71
+ }
72
+ end
121
73
  end
122
74
  else
123
- raise ValueError, "Properties definition of field '#{fieldname}' is not an object"
75
+ value.each_with_index {|val, index|
76
+ check_property(undefined_check(value, index), schema['items'], index, value)
77
+ }
124
78
  end
125
79
  end
126
- end
127
- end
128
- return x
129
- end
130
-
131
- def validate_items x, fieldname, schema, items=nil
132
- if !items.nil? && x[fieldname]
133
- value = x[fieldname]
134
- unless value.nil?
135
- if value.kind_of? Array
136
- if items.kind_of? Array
137
- if items.size == value.size
138
- items.each_with_index do |item, index|
139
- begin
140
- validate(value[index], item)
141
- rescue ValueError => e
142
- raise ValueError, "Failed to validate field '#{fieldname}' list schema: #{e.message}"
143
- end
144
- end
145
- else
146
- raise ValueError, "Length of list #{value} for field '#{fieldname}' is not equal to length of schema list"
147
- end
148
- elsif items.kind_of? Hash
149
- value.each do |val|
150
- begin
151
- _validate(val, items)
152
- rescue ValueError => e
153
- raise ValueError, "Failed to validate field '#{fieldname}' list schema: #{e.message}"
80
+ if schema['minItems'] && value.size < schema['minItems']
81
+ raise ValueError, "There must be a minimum of #{schema['minItems']} in the array"
82
+ end
83
+ if schema['maxItems'] && value.size > schema['maxItems']
84
+ raise ValueError, "There must be a maximum of #{schema['maxItems']} in the array"
85
+ end
86
+ elsif schema['properties']
87
+ check_object(value, schema['properties'], schema['additionalProperties'])
88
+ elsif schema.include?('additionalProperties')
89
+ additional = schema['additionalProperties']
90
+ unless additional.instance_of?(TrueClass)
91
+ if additional.instance_of?(Hash) || additional.instance_of?(FalseClass)
92
+ properties = {}
93
+ value.each {|k, val|
94
+ if additional.instance_of?(FalseClass)
95
+ raise ValueError, "Additional properties not defined by 'properties' are not allowed in field '#{k}'"
96
+ else
97
+ check_property(val, schema['additionalProperties'], k, value)
154
98
  end
155
- end
99
+ }
156
100
  else
157
- raise ValueError, "Properties definition of field '#{fieldname}' is not a list or an object"
101
+ raise ValueError, "additionalProperties schema definition for field '#{}' is not an object"
158
102
  end
159
103
  end
160
104
  end
161
- end
162
- return x
163
- end
164
-
165
- def validate_optional x, fieldname, schema, optional=false
166
- if !x.include?(fieldname) && !optional
167
- raise ValueError, "Required field '#{fieldname}' is missing"
168
- end
169
- return x
170
- end
171
105
 
172
- def validate_additionalProperties x, fieldname, schema, additional_properties=nil
173
- unless additional_properties.nil?
174
- if additional_properties.kind_of? TrueClass
175
- return x
176
- end
177
- value = x[fieldname]
178
- if additional_properties.kind_of?(Hash) || additional_properties.kind_of?(FalseClass)
179
- properties = schema["properties"]
180
- unless properties
181
- properties = {}
106
+ if value.instance_of?(String)
107
+ # pattern
108
+ if schema['pattern'] && !(value =~ Regexp.new(schema['pattern']))
109
+ raise ValueError, "does not match the regex pattern #{schema['pattern']}"
182
110
  end
183
- value.keys.each do |key|
184
- unless properties.include? key
185
- if additional_properties.kind_of? FalseClass
186
- raise ValueError, "Additional properties not defined by 'properties' are not allowed in field '#{fieldname}'"
187
- else
188
- __validate(key, value, additional_properties)
189
- end
190
- end
111
+
112
+ strlen = value.split(//).size
113
+ # maxLength
114
+ if schema['maxLength'] && strlen > schema['maxLength']
115
+ raise ValueError, "may only be #{schema['maxLength']} characters long"
191
116
  end
192
- else
193
- raise ValueError, "additionalProperties schema definition for field '#{fieldname}' is not an object"
194
- end
195
- end
196
- return x
197
- end
198
117
 
199
- def validate_requires x, fieldname, schema, requires=nil
200
- if x[fieldname] && !requires.nil?
201
- unless x[requires]
202
- raise ValueError, "Field '#{requires}' is required by field '#{fieldname}'"
118
+ # minLength
119
+ if schema['minLength'] && strlen < schema['minLength']
120
+ raise ValueError, "must be at least #{schema['minLength']} characters long"
121
+ end
203
122
  end
204
- end
205
- return x
206
- end
207
123
 
208
- def validate_identity x, fieldname, schema, unique=false
209
- return x
210
- end
124
+ if value.kind_of?(Numeric)
211
125
 
212
- def validate_minimum x, fieldname, schema, minimum=nil
213
- if !minimum.nil? && x[fieldname]
214
- value = x[fieldname]
215
- if value
216
- if (value.kind_of?(Integer) || value.kind_of?(Float)) && value < minimum
217
- raise ValueError, "Value #{value} for field '#{fieldname}' is less than minimum value: #{minimum}"
218
- elsif value.kind_of?(Array) && value.size < minimum
219
- raise ValueError, "Value #{value} for field '#{fieldname}' has fewer values than the minimum: #{minimum}"
126
+ # minimum + minimumCanEqual
127
+ if schema['minimum']
128
+ minimumCanEqual = schema.fetch('minimumCanEqual', Undefined)
129
+ if minimumCanEqual == Undefined || minimumCanEqual
130
+ if value < schema['minimum']
131
+ raise ValueError, "must have a minimum value of #{schema['minimum']}"
132
+ end
133
+ else
134
+ if value <= schema['minimum']
135
+ raise ValueError, "must have a minimum value of #{schema['minimum']}"
136
+ end
137
+ end
220
138
  end
221
- end
222
- end
223
- return x
224
- end
225
139
 
226
- def validate_maximum x, fieldname, schema, maximum=nil
227
- if !maximum.nil? && x[fieldname]
228
- value = x[fieldname]
229
- if value
230
- if (value.kind_of?(Integer) || value.kind_of?(Float)) && value > maximum
231
- raise ValueError, "Value #{value} for field '#{fieldname}' is greater than maximum value: #{maximum}"
232
- elsif value.kind_of?(Array) && value.size > maximum
233
- raise ValueError, "Value #{value} for field '#{fieldname}' has more values than the maximum: #{maximum}"
140
+ # maximum + maximumCanEqual
141
+ if schema['maximum']
142
+ maximumCanEqual = schema.fetch('maximumCanEqual', Undefined)
143
+ if maximumCanEqual == Undefined || maximumCanEqual
144
+ if value > schema['maximum']
145
+ raise ValueError, "must have a maximum value of #{schema['maximum']}"
146
+ end
147
+ else
148
+ if value >= schema['maximum']
149
+ raise ValueError, "must have a maximum value of #{schema['maximum']}"
150
+ end
151
+ end
234
152
  end
235
- end
236
- end
237
- return x
238
- end
239
153
 
240
- def validate_minItems x, fieldname, schema, minitems=nil
241
- if !minitems.nil? && x[fieldname]
242
- value = x[fieldname]
243
- if value
244
- if value.kind_of?(Array) && value.size < minitems
245
- raise ValueError, "Value #{value} for field '#{fieldname}' must have a minimum of #{minitems} items"
154
+ # maxDecimal
155
+ if schema['maxDecimal'] && schema['maxDecimal'].kind_of?(Numeric)
156
+ if value.to_s =~ /\.\d{#{schema['maxDecimal']+1},}/
157
+ raise ValueError, "may only have #{schema['maxDecimal']} digits of decimal places"
158
+ end
246
159
  end
160
+
247
161
  end
248
- end
249
- return x
250
- end
251
162
 
252
- def validate_maxItems x, fieldname, schema, maxitems=nil
253
- if !maxitems.nil? && x[fieldname]
254
- value = x[fieldname]
255
- if value
256
- if value.kind_of?(Array) && value.size > maxitems
257
- raise ValueError, "Value #{value} for field '#{fieldname}' must have a maximum of #{maxitems} items"
163
+ # enum
164
+ if schema['enum']
165
+ unless(schema['enum'].detect{|enum| enum == value })
166
+ raise ValueError, "does not have a value in the enumeration #{schema['enum'].join(", ")}"
258
167
  end
259
168
  end
260
- end
261
- return x
262
- end
263
169
 
264
- def validate_pattern x, fieldname, schema, pattern=nil
265
- value = x[fieldname]
266
- if !pattern.nil? && value && value.kind_of?(String)
267
- p = Regexp.new(pattern)
268
- if !p.match(value)
269
- raise ValueError, "Value #{value} for field '#{fieldname}' does not match regular expression '#{pattern}'"
170
+ # description
171
+ if schema['description'] && !schema['description'].instance_of?(String)
172
+ raise ValueError, "The description for field '#{value}' must be a string"
270
173
  end
271
- end
272
- return x
273
- end
274
174
 
275
- def validate_maxLength x, fieldname, schema, length=nil
276
- value = x[fieldname]
277
- if !length.nil? && value && value.kind_of?(String)
278
- # string length => 正規表現で分割して計測
279
- if value.split(//).size > length
280
- raise ValueError, "Length of value #{value} for field '#{fieldname}' must be less than or equal to #{length}"
175
+ # title
176
+ if schema['title'] && !schema['title'].instance_of?(String)
177
+ raise ValueError, "The title for field '#{value}' must be a string"
281
178
  end
282
- end
283
- return x
284
- end
285
179
 
286
- def validate_minLength x, fieldname, schema, length=nil
287
- value = x[fieldname]
288
- if !length.nil? && value && value.kind_of?(String)
289
- if value.split(//).size < length
290
- raise ValueError, "Length of value #{value} for field '#{fieldname}' must be more than or equal to #{length}"
180
+ # format
181
+ if schema['format']
291
182
  end
292
- end
293
- return x
294
- end
295
183
 
296
- def validate_enum x, fieldname, schema, options=nil
297
- value = x[fieldname]
298
- if !options.nil? && value
299
- unless options.kind_of? Array
300
- raise ValueError, "Enumeration #{options} for field '#{fieldname}' is not a list type"
301
- end
302
- unless options.include? value
303
- raise ValueError, "Value #{value} for field '#{fieldname}' is not in the enumeration: #{options}"
304
- end
305
184
  end
306
- return x
307
- end
308
-
309
- def validate_options x, fieldname, schema, options=nil
310
- return x
311
185
  end
312
186
 
313
- def validate_readonly x, fieldname, schema, readonly=false
314
- return x
315
- end
316
-
317
- def validate_title x, fieldname, schema, title=nil
318
- if !title.nil? && !title.kind_of?(String)
319
- raise ValueError, "The title for field '#{fieldname}' must be a string"
320
- end
321
- return x
322
- end
187
+ def check_object value, object_type_def, additional
188
+ if object_type_def.instance_of? Hash
189
+ if !value.instance_of?(Hash) || value.instance_of?(Array)
190
+ raise ValueError, "an object is required"
191
+ end
323
192
 
324
- def validate_description x, fieldname, schema, description=nil
325
- if !description.nil? && !description.kind_of?(String)
326
- raise ValueError, "The description for field '#{fieldname}' must be a string"
193
+ object_type_def.each {|key, odef|
194
+ if key.index('__') != 0
195
+ check_property(undefined_check(value, key), odef, key, value)
196
+ end
197
+ }
327
198
  end
328
- return x
329
- end
330
-
331
- def validate_format x, fieldname, schema, format=nil
332
- return x
333
- end
334
-
335
- def validate_default x, fieldname, schema, default=nil
336
- if @interactive && !x.include?(fieldname) && !default.nil?
337
- unless schema["readonly"]
338
- x[fieldname] = default
199
+ value.each {|key, val|
200
+ if key.index('__') != 0 && object_type_def && !object_type_def[key] && additional == false
201
+ raise ValueError, "#{value.class} The property #{key} is not defined in the schema and the schema does not allow additional properties"
339
202
  end
340
- end
341
- return x
342
- end
343
-
344
- def validate_transient x, fieldname, schema, transient=false
345
- return x
346
- end
347
-
348
- def validate_maxDecimal x, fieldname, schema, maxdecimal=nil
349
- value = x[fieldname]
350
- if !maxdecimal.nil? && value
351
- maxdecstring = value.to_s
352
- index = maxdecstring.index('.')
353
- if index && maxdecstring[(index+1)...maxdecstring.size].split(//u).size > maxdecimal
354
- raise ValueError, "Value #{value} for field '#{fieldname}' must not have more than #{maxdecimal} decimal places"
203
+ requires = object_type_def && object_type_def[key] && object_type_def[key]['requires']
204
+ if requires && !value.include?(requires)
205
+ raise ValueError, "the presence of the property #{key} requires that #{requires} also be present"
355
206
  end
356
- end
357
- return x
358
- end
359
-
360
- def validate_hidden x, fieldname, schema, hidden=false
361
- return x
207
+ if object_type_def && object_type_def.instance_of?(Hash) && !object_type_def.include?(key)
208
+ check_property(val, additional, key, value)
209
+ end
210
+ if !@interactive && val && val['$schema']
211
+ check_property(val, val['$schema'], key, value)
212
+ end
213
+ }
362
214
  end
363
215
 
364
- def validate_disallow x, fieldname, schema, disallow=nil
365
- if !disallow.nil?
366
- begin
367
- validate_type(x, fieldname, schema, disallow)
368
- rescue ValueError
369
- return x
216
+ # Checks the type of a given value
217
+ # === Parameter
218
+ # value<>::
219
+ # type<>::
220
+ # key<>::
221
+ # parent<>::
222
+ def check_type value, type, key, parent
223
+ converted_fieldtype = convert_type(type)
224
+ if converted_fieldtype
225
+ if converted_fieldtype.instance_of? Array
226
+ datavalid = false
227
+ converted_fieldtype.each do |t|
228
+ begin
229
+ check_type(value, t, key, parent)
230
+ datavalid = true
231
+ break
232
+ rescue ValueError
233
+ next
234
+ end
235
+ end
236
+ unless datavalid
237
+ raise ValueError, "#{value.class} value found, but a #{type} is required"
238
+ end
239
+ elsif converted_fieldtype.instance_of? Hash
240
+ check_property(value, type, key, parent)
241
+ else
242
+ unless value.instance_of? converted_fieldtype
243
+ raise ValueError, "#{value.class} value found, but a #{type} is required"
244
+ end
370
245
  end
371
- raise ValueError, "Value #{x[fieldname]} of type #{disallow} is disallowed for field '#{fieldname}'"
372
246
  end
373
- return x
374
247
  end
375
248
 
376
- def validate_extends x, fieldname, schema, extends=nil
377
- return x
249
+ def undefined_check value, key
250
+ value.fetch(key, Undefined)
378
251
  end
379
252
 
380
253
  def convert_type fieldtype
@@ -398,37 +271,22 @@ module JSON
398
271
  end
399
272
  end
400
273
 
401
- def __validate fieldname, data, schema
402
- if schema
403
- if !schema.kind_of?(Hash)
404
- raise ValueError, "Schema structure is invalid"
405
- end
406
- # copy
407
- new_schema = Marshal.load(Marshal.dump(schema))
408
- DefaultSchema.each do |key, val|
409
- new_schema[key] = val unless new_schema.include?(key)
410
- end
411
- new_schema.each do |key ,val|
412
- validatorname = "validate_"+key
413
- begin
414
- __send__(validatorname, data, fieldname, schema, new_schema[key])
415
- rescue NoMethodError => e
416
- raise ValueError, "Schema property '#{e.message}' is not supported"
417
- end
418
- end
419
- end
420
- return data
274
+ # Validates an instance against a given schema
275
+ # === Parameter
276
+ # instance<Hash{String=>Mixed}:: The instanciated record as hash with fields
277
+ # as keys and values of the to be checked types=> Int, String, Bool, Nil...
278
+ # {'name'=>'Chubby', :total=>1.234, :=>}
279
+ # instance<Hash{String=>String, Hash}:: the schema used to validate the instance
280
+ # {'properties'=>{}}
281
+ def validate instance, schema
282
+ schema ||= instance['$schema'] # self defined schema
283
+ check_property(instance, schema, 'self', @tree)
284
+ return instance
421
285
  end
422
286
 
423
- def _validate data, schema
424
- __validate("_data", {"_data" => data}, schema)
425
- end
426
-
427
- class << self
428
- def validate data, schema, interactive=true
429
- validator = JSON::Schema.new(interactive)
430
- validator.validate(data, schema)
431
- end
287
+ def self.validate data, schema=nil, interactive=true
288
+ validator = JSON::Schema.new(interactive)
289
+ validator.validate(data, schema)
432
290
  end
433
- end
291
+ end
434
292
  end