sk-api 1.0.6 → 1.1.0

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.
@@ -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