autoparse 0.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.
- data/CHANGELOG +3 -0
- data/LICENSE +202 -0
- data/README.md +25 -0
- data/Rakefile +60 -0
- data/lib/autoparse/inflection.rb +23 -0
- data/lib/autoparse/instance.rb +422 -0
- data/lib/autoparse/version.rb +26 -0
- data/lib/autoparse.rb +203 -0
- data/spec/autoparse/instance_spec.rb +904 -0
- data/spec/data/account.json +8 -0
- data/spec/data/address.json +20 -0
- data/spec/data/adult.json +5 -0
- data/spec/data/calendar.json +49 -0
- data/spec/data/card.json +105 -0
- data/spec/data/geo.json +9 -0
- data/spec/data/hyper-schema.json +60 -0
- data/spec/data/interfaces.json +23 -0
- data/spec/data/json-ref.json +26 -0
- data/spec/data/links.json +35 -0
- data/spec/data/person.json +11 -0
- data/spec/data/positive.json +5 -0
- data/spec/data/schema.json +174 -0
- data/spec/spec.opts +2 -0
- data/spec/spec_helper.rb +7 -0
- data/tasks/clobber.rake +2 -0
- data/tasks/gem.rake +72 -0
- data/tasks/git.rake +40 -0
- data/tasks/metrics.rake +22 -0
- data/tasks/rdoc.rake +26 -0
- data/tasks/rubyforge.rake +103 -0
- data/tasks/spec.rake +71 -0
- data/tasks/yard.rake +26 -0
- data/website/index.html +95 -0
- metadata +212 -0
@@ -0,0 +1,422 @@
|
|
1
|
+
# Copyright 2010 Google Inc
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
|
16
|
+
require 'json'
|
17
|
+
require 'time'
|
18
|
+
require 'autoparse/inflection'
|
19
|
+
require 'addressable/uri'
|
20
|
+
|
21
|
+
module AutoParse
|
22
|
+
class Instance
|
23
|
+
def self.uri
|
24
|
+
return @uri ||= nil
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.properties
|
28
|
+
return @properties ||= {}
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.additional_properties_schema
|
32
|
+
return EMPTY_SCHEMA
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.property_dependencies
|
36
|
+
return @property_dependencies ||= {}
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.data
|
40
|
+
return @schema_data
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.description
|
44
|
+
return @schema_data['description']
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.validate_string_property(property_value, schema_data)
|
48
|
+
property_value = property_value.to_str rescue property_value
|
49
|
+
if !property_value.kind_of?(String)
|
50
|
+
return false
|
51
|
+
else
|
52
|
+
# TODO: implement more than type-checking
|
53
|
+
return true
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def self.define_string_property(property_name, key, schema_data)
|
58
|
+
define_method(property_name) do
|
59
|
+
value = self[key] || schema_data['default']
|
60
|
+
if value != nil
|
61
|
+
if schema_data['format'] == 'byte'
|
62
|
+
Base64.decode64(value)
|
63
|
+
elsif schema_data['format'] == 'date-time'
|
64
|
+
Time.parse(value)
|
65
|
+
elsif schema_data['format'] == 'url'
|
66
|
+
Addressable::URI.parse(value)
|
67
|
+
elsif schema_data['format'] =~ /^u?int(32|64)$/
|
68
|
+
value.to_i
|
69
|
+
else
|
70
|
+
value
|
71
|
+
end
|
72
|
+
else
|
73
|
+
nil
|
74
|
+
end
|
75
|
+
end
|
76
|
+
define_method(property_name + '=') do |value|
|
77
|
+
if schema_data['format'] == 'byte'
|
78
|
+
self[key] = Base64.encode64(value)
|
79
|
+
elsif schema_data['format'] == 'date-time'
|
80
|
+
if value.respond_to?(:to_str)
|
81
|
+
value = Time.parse(value.to_str)
|
82
|
+
elsif !value.respond_to?(:xmlschema)
|
83
|
+
raise TypeError,
|
84
|
+
"Could not obtain RFC 3339 timestamp from #{value.class}."
|
85
|
+
end
|
86
|
+
self[key] = value.xmlschema
|
87
|
+
elsif schema_data['format'] == 'url'
|
88
|
+
# This effectively does limited URI validation.
|
89
|
+
self[key] = Addressable::URI.parse(value).to_str
|
90
|
+
elsif schema_data['format'] =~ /^u?int(32|64)$/
|
91
|
+
self[key] = value.to_s
|
92
|
+
elsif value.respond_to?(:to_str)
|
93
|
+
self[key] = value.to_str
|
94
|
+
elsif value.kind_of?(Symbol)
|
95
|
+
self[key] = value.to_s
|
96
|
+
else
|
97
|
+
raise TypeError,
|
98
|
+
"Expected String or Symbol, got #{value.class}."
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def self.define_boolean_property(property_name, key, schema_data)
|
104
|
+
define_method(property_name) do
|
105
|
+
value = self[key] || schema_data['default']
|
106
|
+
case value.to_s.downcase
|
107
|
+
when 'true', 'yes', 'y', 'on', '1'
|
108
|
+
true
|
109
|
+
when 'false', 'no', 'n', 'off', '0'
|
110
|
+
false
|
111
|
+
when 'nil', 'null'
|
112
|
+
nil
|
113
|
+
else
|
114
|
+
raise TypeError,
|
115
|
+
"Expected boolean, got #{value.class}."
|
116
|
+
end
|
117
|
+
end
|
118
|
+
define_method(property_name + '=') do |value|
|
119
|
+
case value.to_s.downcase
|
120
|
+
when 'true', 'yes', 'y', 'on', '1'
|
121
|
+
self[key] = true
|
122
|
+
when 'false', 'no', 'n', 'off', '0'
|
123
|
+
self[key] = false
|
124
|
+
when 'nil', 'null'
|
125
|
+
self[key] = nil
|
126
|
+
else
|
127
|
+
raise TypeError, "Expected boolean, got #{value.class}."
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
def self.validate_number_property(property_value, schema_data)
|
133
|
+
return false if !property_value.kind_of?(Numeric)
|
134
|
+
# TODO: implement more than type-checking
|
135
|
+
return true
|
136
|
+
end
|
137
|
+
|
138
|
+
def self.define_number_property(property_name, key, schema_data)
|
139
|
+
define_method(property_name) do
|
140
|
+
Float(self[key] || schema_data['default'])
|
141
|
+
end
|
142
|
+
define_method(property_name + '=') do |value|
|
143
|
+
if value == nil
|
144
|
+
self[key] = value
|
145
|
+
else
|
146
|
+
self[key] = Float(value)
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
def self.validate_integer_property(property_value, schema_data)
|
152
|
+
return false if !property_value.kind_of?(Integer)
|
153
|
+
if schema_data['minimum'] && schema_data['exclusiveMinimum']
|
154
|
+
return false if property_value <= schema_data['minimum']
|
155
|
+
elsif schema_data['minimum']
|
156
|
+
return false if property_value < schema_data['minimum']
|
157
|
+
end
|
158
|
+
if schema_data['maximum'] && schema_data['exclusiveMaximum']
|
159
|
+
return false if property_value >= schema_data['maximum']
|
160
|
+
elsif schema_data['maximum']
|
161
|
+
return false if property_value > schema_data['maximum']
|
162
|
+
end
|
163
|
+
return true
|
164
|
+
end
|
165
|
+
|
166
|
+
def self.define_integer_property(property_name, key, schema_data)
|
167
|
+
define_method(property_name) do
|
168
|
+
Integer(self[key] || schema_data['default'])
|
169
|
+
end
|
170
|
+
define_method(property_name + '=') do |value|
|
171
|
+
if value == nil
|
172
|
+
self[key] = value
|
173
|
+
else
|
174
|
+
self[key] = Integer(value)
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
def self.validate_array_property(property_value, schema_data)
|
180
|
+
if property_value.respond_to?(:to_ary)
|
181
|
+
property_value = property_value.to_ary
|
182
|
+
else
|
183
|
+
return false
|
184
|
+
end
|
185
|
+
property_value.each do |item_value|
|
186
|
+
unless self.validate_property_value(item_value, schema_data['items'])
|
187
|
+
return false
|
188
|
+
end
|
189
|
+
end
|
190
|
+
return true
|
191
|
+
end
|
192
|
+
|
193
|
+
def self.define_array_property(property_name, key, schema_data)
|
194
|
+
define_method(property_name) do
|
195
|
+
# The default value of an empty Array obviates a mutator method.
|
196
|
+
value = self[key] || []
|
197
|
+
array = if value != nil && !value.respond_to?(:to_ary)
|
198
|
+
raise TypeError,
|
199
|
+
"Expected Array, got #{value.class}."
|
200
|
+
else
|
201
|
+
value.to_ary
|
202
|
+
end
|
203
|
+
if schema_data['items'] && schema_data['items']['$ref']
|
204
|
+
schema_name = schema_data['items']['$ref']
|
205
|
+
# FIXME: Vestigial bits need to be replaced with a more viable
|
206
|
+
# lookup system.
|
207
|
+
if AutoParse.schemas[schema_name]
|
208
|
+
schema_class = AutoParse.schemas[schema_name]
|
209
|
+
array.map! do |item|
|
210
|
+
schema_class.new(item)
|
211
|
+
end
|
212
|
+
else
|
213
|
+
raise ArgumentError,
|
214
|
+
"Could not find schema: #{schema_uri}."
|
215
|
+
end
|
216
|
+
end
|
217
|
+
array
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
def self.validate_object_property(property_value, schema_data, schema=nil)
|
222
|
+
if property_value.kind_of?(Instance)
|
223
|
+
return property_value.valid?
|
224
|
+
elsif schema != nil && schema.kind_of?(Class)
|
225
|
+
return schema.new(property_value).valid?
|
226
|
+
else
|
227
|
+
# This is highly ineffecient, but hard to avoid given the schema is
|
228
|
+
# anonymous.
|
229
|
+
schema = AutoParse.generate(schema_data)
|
230
|
+
return schema.new(property_value).valid?
|
231
|
+
end
|
232
|
+
end
|
233
|
+
|
234
|
+
def self.define_object_property(property_name, key, schema_data)
|
235
|
+
# TODO finish this up...
|
236
|
+
if schema_data['$ref']
|
237
|
+
schema_uri = self.uri + Addressable::URI.parse(schema_data['$ref'])
|
238
|
+
schema = AutoParse.schemas[schema_uri]
|
239
|
+
if schema == nil
|
240
|
+
raise ArgumentError,
|
241
|
+
"Could not find schema: #{schema_data['$ref']} " +
|
242
|
+
"Referenced schema must be parsed first."
|
243
|
+
end
|
244
|
+
else
|
245
|
+
# Anonymous schema
|
246
|
+
schema = AutoParse.generate(schema_data)
|
247
|
+
end
|
248
|
+
define_method(property_name) do
|
249
|
+
schema.new(self[key] || schema_data['default'])
|
250
|
+
end
|
251
|
+
end
|
252
|
+
|
253
|
+
def self.define_any_property(property_name, key, schema_data)
|
254
|
+
define_method(property_name) do
|
255
|
+
self[key] || schema_data['default']
|
256
|
+
end
|
257
|
+
define_method(property_name + '=') do |value|
|
258
|
+
self[key] = value
|
259
|
+
end
|
260
|
+
end
|
261
|
+
|
262
|
+
##
|
263
|
+
# @api private
|
264
|
+
def self.validate_property_value(property_value, schema_data)
|
265
|
+
if property_value == nil && schema_data['required'] == true
|
266
|
+
return false
|
267
|
+
elsif property_value == nil
|
268
|
+
# Value was omitted, but not required. Still valid.
|
269
|
+
return true
|
270
|
+
end
|
271
|
+
|
272
|
+
# Verify property values
|
273
|
+
if schema_data['$ref']
|
274
|
+
schema_uri = self.uri + Addressable::URI.parse(schema_data['$ref'])
|
275
|
+
schema = AutoParse.schemas[schema_uri]
|
276
|
+
if schema == nil
|
277
|
+
raise ArgumentError,
|
278
|
+
"Could not find schema: #{schema_data['$ref']} " +
|
279
|
+
"Referenced schema must be parsed first."
|
280
|
+
end
|
281
|
+
schema_data = schema.data
|
282
|
+
end
|
283
|
+
case schema_data['type']
|
284
|
+
when 'string'
|
285
|
+
return false unless self.validate_string_property(
|
286
|
+
property_value, schema_data
|
287
|
+
)
|
288
|
+
when 'boolean'
|
289
|
+
return false unless self.validate_boolean_property(
|
290
|
+
property_value, schema_data
|
291
|
+
)
|
292
|
+
when 'number'
|
293
|
+
return false unless self.validate_number_property(
|
294
|
+
property_value, schema_data
|
295
|
+
)
|
296
|
+
when 'integer'
|
297
|
+
return false unless self.validate_integer_property(
|
298
|
+
property_value, schema_data
|
299
|
+
)
|
300
|
+
when 'array'
|
301
|
+
return false unless self.validate_array_property(
|
302
|
+
property_value, schema_data
|
303
|
+
)
|
304
|
+
when 'object'
|
305
|
+
return false unless self.validate_object_property(
|
306
|
+
property_value, schema_data
|
307
|
+
)
|
308
|
+
else
|
309
|
+
# Either type 'any' or we don't know what this is,
|
310
|
+
# default to anything goes. Validation of an 'any' property always
|
311
|
+
# succeeds.
|
312
|
+
end
|
313
|
+
return true
|
314
|
+
end
|
315
|
+
|
316
|
+
def initialize(data)
|
317
|
+
if self.class.data &&
|
318
|
+
self.class.data['type'] &&
|
319
|
+
self.class.data['type'] != 'object'
|
320
|
+
raise TypeError,
|
321
|
+
"Only schemas of type 'object' are instantiable."
|
322
|
+
end
|
323
|
+
if data.respond_to?(:to_hash)
|
324
|
+
data = data.to_hash
|
325
|
+
elsif data.respond_to?(:to_json)
|
326
|
+
data = JSON.parse(data.to_json)
|
327
|
+
else
|
328
|
+
raise TypeError,
|
329
|
+
'Unable to parse. ' +
|
330
|
+
'Expected data to respond to either :to_hash or :to_json.'
|
331
|
+
end
|
332
|
+
@data = data
|
333
|
+
end
|
334
|
+
|
335
|
+
def [](key)
|
336
|
+
return @data[key]
|
337
|
+
end
|
338
|
+
|
339
|
+
def []=(key, value)
|
340
|
+
return @data[key] = value
|
341
|
+
end
|
342
|
+
|
343
|
+
##
|
344
|
+
# Validates the parsed data against the schema.
|
345
|
+
def valid?
|
346
|
+
unvalidated_fields = @data.keys.dup
|
347
|
+
for property_key, property_schema in self.class.properties
|
348
|
+
property_value = self[property_key]
|
349
|
+
if !self.class.validate_property_value(property_value, property_schema)
|
350
|
+
return false
|
351
|
+
end
|
352
|
+
if property_value == nil && property_schema['required'] != true
|
353
|
+
# Value was omitted, but not required. Still valid. Skip dependency
|
354
|
+
# checks.
|
355
|
+
next
|
356
|
+
end
|
357
|
+
|
358
|
+
# Verify property dependencies
|
359
|
+
property_dependencies = self.class.property_dependencies[property_key]
|
360
|
+
case property_dependencies
|
361
|
+
when String, Array
|
362
|
+
property_dependencies = [property_dependencies].flatten
|
363
|
+
for dependency_key in property_dependencies
|
364
|
+
dependency_value = self[dependency_key]
|
365
|
+
return false if dependency_value == nil
|
366
|
+
end
|
367
|
+
when Class
|
368
|
+
if property_dependencies.ancestors.include?(Instance)
|
369
|
+
dependency_instance = property_dependencies.new(property_value)
|
370
|
+
return false unless dependency_instance.valid?
|
371
|
+
else
|
372
|
+
raise TypeError,
|
373
|
+
"Expected schema Class, got #{property_dependencies.class}."
|
374
|
+
end
|
375
|
+
end
|
376
|
+
end
|
377
|
+
if self.class.additional_properties_schema == nil
|
378
|
+
# No additional properties allowed
|
379
|
+
return false unless unvalidated_fields.empty?
|
380
|
+
elsif self.class.additional_properties_schema != EMPTY_SCHEMA
|
381
|
+
# Validate all remaining fields against this schema
|
382
|
+
|
383
|
+
# Make sure tests don't pass prematurely
|
384
|
+
return false
|
385
|
+
end
|
386
|
+
if self.class.superclass && self.class.superclass != Instance &&
|
387
|
+
self.class.ancestors.first != Instance
|
388
|
+
# The spec actually only defined the 'extends' semantics as children
|
389
|
+
# must also validate aainst the parent.
|
390
|
+
return false unless self.class.superclass.new(@data).valid?
|
391
|
+
end
|
392
|
+
return true
|
393
|
+
end
|
394
|
+
|
395
|
+
def to_hash
|
396
|
+
return @data
|
397
|
+
end
|
398
|
+
|
399
|
+
def to_json
|
400
|
+
return JSON.generate(self.to_hash)
|
401
|
+
end
|
402
|
+
|
403
|
+
##
|
404
|
+
# Returns a <code>String</code> representation of the schema instance.
|
405
|
+
#
|
406
|
+
# @return [String] The instance's state, as a <code>String</code>.
|
407
|
+
def inspect
|
408
|
+
if self.class.respond_to?(:description)
|
409
|
+
sprintf(
|
410
|
+
"#<%s:%#0x DESC:'%s'>",
|
411
|
+
self.class.to_s, self.object_id, self.class.description
|
412
|
+
)
|
413
|
+
else
|
414
|
+
sprintf("#<%s:%#0x>", self.class.to_s, self.object_id)
|
415
|
+
end
|
416
|
+
end
|
417
|
+
end
|
418
|
+
|
419
|
+
##
|
420
|
+
# The empty schema accepts all JSON.
|
421
|
+
EMPTY_SCHEMA = Instance
|
422
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# Copyright 2010 Google Inc
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
# Used to prevent the class/module from being loaded more than once
|
16
|
+
unless defined? AutoParse::VERSION
|
17
|
+
module AutoParse
|
18
|
+
module VERSION
|
19
|
+
MAJOR = 0
|
20
|
+
MINOR = 1
|
21
|
+
TINY = 0
|
22
|
+
|
23
|
+
STRING = [MAJOR, MINOR, TINY].join('.')
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
data/lib/autoparse.rb
ADDED
@@ -0,0 +1,203 @@
|
|
1
|
+
# Copyright 2010 Google Inc
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
require 'autoparse/instance'
|
16
|
+
require 'autoparse/version'
|
17
|
+
require 'addressable/uri'
|
18
|
+
|
19
|
+
module AutoParse
|
20
|
+
def self.schemas
|
21
|
+
@schemas ||= {}
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.generate(schema_data, uri=nil)
|
25
|
+
if schema_data["extends"]
|
26
|
+
super_uri = uri + Addressable::URI.parse(schema_data["extends"])
|
27
|
+
super_schema = self.schemas[super_uri]
|
28
|
+
if super_schema == nil
|
29
|
+
raise ArgumentError,
|
30
|
+
"Could not find schema to extend: #{schema_data["extends"]} " +
|
31
|
+
"Parent schema must be parsed before child schema."
|
32
|
+
end
|
33
|
+
else
|
34
|
+
super_schema = Instance
|
35
|
+
end
|
36
|
+
schema = Class.new(super_schema) do
|
37
|
+
@uri = Addressable::URI.parse(uri)
|
38
|
+
@uri.normalize! if @uri != nil
|
39
|
+
@schema_data = schema_data
|
40
|
+
|
41
|
+
def self.additional_properties_schema
|
42
|
+
# Override the superclass implementation so we're not always returning
|
43
|
+
# the empty schema.
|
44
|
+
return @additional_properties_schema
|
45
|
+
end
|
46
|
+
|
47
|
+
(@schema_data['properties'] || []).each do |(k, v)|
|
48
|
+
property_key, property_schema = k, v
|
49
|
+
property_name = INFLECTOR.underscore(property_key).gsub("-", "_")
|
50
|
+
property_super_schema = super_schema.properties[property_key]
|
51
|
+
if property_super_schema
|
52
|
+
# TODO: Not sure if this should be a recursive merge or not...
|
53
|
+
# TODO: Might need to raise an error if a schema is extended in
|
54
|
+
# a way that violates the requirement that all child instances also
|
55
|
+
# validate against the parent schema.
|
56
|
+
property_schema = property_super_schema.merge(property_schema)
|
57
|
+
end
|
58
|
+
self.properties[property_key] = property_schema
|
59
|
+
if property_schema['$ref']
|
60
|
+
schema_uri =
|
61
|
+
self.uri + Addressable::URI.parse(property_schema['$ref'])
|
62
|
+
schema = AutoParse.schemas[schema_uri]
|
63
|
+
if schema == nil
|
64
|
+
raise ArgumentError,
|
65
|
+
"Could not find schema: #{property_schema['$ref']} " +
|
66
|
+
"Referenced schema must be parsed first."
|
67
|
+
end
|
68
|
+
property_schema = schema.data
|
69
|
+
end
|
70
|
+
case property_schema['type']
|
71
|
+
when 'string'
|
72
|
+
define_string_property(
|
73
|
+
property_name, property_key, property_schema
|
74
|
+
)
|
75
|
+
when 'boolean'
|
76
|
+
define_boolean_property(
|
77
|
+
property_name, property_key, property_schema
|
78
|
+
)
|
79
|
+
when 'number'
|
80
|
+
define_number_property(
|
81
|
+
property_name, property_key, property_schema
|
82
|
+
)
|
83
|
+
when 'integer'
|
84
|
+
define_integer_property(
|
85
|
+
property_name, property_key, property_schema
|
86
|
+
)
|
87
|
+
when 'array'
|
88
|
+
define_array_property(
|
89
|
+
property_name, property_key, property_schema
|
90
|
+
)
|
91
|
+
when 'object'
|
92
|
+
define_object_property(
|
93
|
+
property_name, property_key, property_schema
|
94
|
+
)
|
95
|
+
else
|
96
|
+
# Either type 'any' or we don't know what this is,
|
97
|
+
# default to anything goes.
|
98
|
+
define_any_property(
|
99
|
+
property_name, property_key, property_schema
|
100
|
+
)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
if schema_data['additionalProperties'] == true ||
|
105
|
+
schema_data['additionalProperties'] == nil
|
106
|
+
# Schema-less unknown properties are allowed.
|
107
|
+
@additional_properties_schema = EMPTY_SCHEMA
|
108
|
+
define_method('method_missing') do |method, *params, &block|
|
109
|
+
# We need to convert from Ruby calling style to JavaScript calling
|
110
|
+
# style. If this fails, attempt to use JavaScript calling style
|
111
|
+
# directly.
|
112
|
+
|
113
|
+
# We can't modify the method in-place because this affects the call
|
114
|
+
# to super.
|
115
|
+
stripped_method = method.to_s
|
116
|
+
assignment = false
|
117
|
+
if stripped_method[-1..-1] == '='
|
118
|
+
assignment = true
|
119
|
+
stripped_method[-1..-1] = ''
|
120
|
+
end
|
121
|
+
key = INFLECTOR.camelize(stripped_method)
|
122
|
+
key[0..0] = key[0..0].downcase
|
123
|
+
if self[key] != nil
|
124
|
+
value = self[key]
|
125
|
+
elsif self[stripped_method] != nil
|
126
|
+
key = stripped_method
|
127
|
+
value = self[stripped_method]
|
128
|
+
else
|
129
|
+
# Method not found.
|
130
|
+
super
|
131
|
+
end
|
132
|
+
# If additionalProperties is simply set to true, no parsing takes
|
133
|
+
# place and all values are treated as 'any'.
|
134
|
+
if assignment
|
135
|
+
new_value = params[0]
|
136
|
+
self[key] = new_value
|
137
|
+
else
|
138
|
+
value
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
elsif schema_data['additionalProperties']
|
143
|
+
# Unknown properties follow the supplied schema.
|
144
|
+
ap_schema = Schema.generate(schema_data['additionalProperties'])
|
145
|
+
@additional_properties_schema = ap_schema
|
146
|
+
define_method('method_missing') do |method, *params, &block|
|
147
|
+
# We need to convert from Ruby calling style to JavaScript calling
|
148
|
+
# style. If this fails, attempt to use JavaScript calling style
|
149
|
+
# directly.
|
150
|
+
|
151
|
+
# We can't modify the method in-place because this affects the call
|
152
|
+
# to super.
|
153
|
+
stripped_method = method.to_s
|
154
|
+
assignment = false
|
155
|
+
if stripped_method[-1..-1] == '='
|
156
|
+
assignment = true
|
157
|
+
stripped_method[-1..-1] = ''
|
158
|
+
end
|
159
|
+
key = INFLECTOR.camelize(stripped_method)
|
160
|
+
key[0..0] = key[0..0].downcase
|
161
|
+
if self[key] != nil
|
162
|
+
value = self[key]
|
163
|
+
elsif self[stripped_method] != nil
|
164
|
+
key = stripped_method
|
165
|
+
value = self[stripped_method]
|
166
|
+
else
|
167
|
+
# Method not found.
|
168
|
+
super
|
169
|
+
end
|
170
|
+
if assignment
|
171
|
+
# In the case of assignment, it's very likely the developer is
|
172
|
+
# passing in an unparsed Hash value. This value must be parsed.
|
173
|
+
# Unfortunately, we may accidentally reparse something that's
|
174
|
+
# already in a parsed state because Schema.new(Schema.new(data))
|
175
|
+
# is completely valid. This will cause performance issues if
|
176
|
+
# developers are careless, but since there's no good reason to
|
177
|
+
# do assignment on parsed objects, hopefully this should not
|
178
|
+
# cause problems often.
|
179
|
+
new_value = params[0]
|
180
|
+
self[key] = ap_schema.new(new_value)
|
181
|
+
else
|
182
|
+
ap_schema.new(value)
|
183
|
+
end
|
184
|
+
end
|
185
|
+
else
|
186
|
+
@additional_properties_schema = nil
|
187
|
+
end
|
188
|
+
|
189
|
+
if schema_data['dependencies']
|
190
|
+
for dependency_key, dependency_data in schema_data['dependencies']
|
191
|
+
if dependency_data.kind_of?(Hash)
|
192
|
+
dependency_data = AutoParse.generate(dependency_data)
|
193
|
+
end
|
194
|
+
self.property_dependencies[dependency_key] = dependency_data
|
195
|
+
end
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
# Register the new schema.
|
200
|
+
self.schemas[schema.uri] = schema
|
201
|
+
return schema
|
202
|
+
end
|
203
|
+
end
|