scorpio 0.1.0 → 0.2.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +2 -0
- data/README.md +108 -25
- data/Rakefile +2 -2
- data/documents/openapis.org/v3/schema.json +1239 -0
- data/documents/openapis.org/v3/schema.yml +794 -0
- data/lib/scorpio.rb +68 -68
- data/lib/scorpio/google_api_document.rb +18 -24
- data/lib/scorpio/json-schema-fragments.rb +1 -1
- data/lib/scorpio/json/node.rb +106 -74
- data/lib/scorpio/openapi.rb +146 -68
- data/lib/scorpio/pickle_adapter.rb +3 -3
- data/lib/scorpio/{model.rb → resource_base.rb} +187 -145
- data/lib/scorpio/schema.rb +188 -76
- data/lib/scorpio/schema_instance_base.rb +309 -0
- data/lib/scorpio/schema_instance_base/to_rb.rb +127 -0
- data/lib/scorpio/typelike_modules.rb +120 -4
- data/lib/scorpio/util.rb +83 -0
- data/lib/scorpio/util/faraday/response_media_type.rb +15 -0
- data/lib/scorpio/version.rb +1 -1
- data/scorpio.gemspec +0 -1
- metadata +10 -19
- data/lib/scorpio/schema_object_base.rb +0 -227
data/lib/scorpio/schema.rb
CHANGED
@@ -1,33 +1,94 @@
|
|
1
|
+
require 'scorpio/json/node'
|
2
|
+
|
1
3
|
module Scorpio
|
2
4
|
class Schema
|
3
|
-
|
4
|
-
|
5
|
+
include Memoize
|
6
|
+
|
7
|
+
def initialize(schema_object)
|
8
|
+
if schema_object.is_a?(Scorpio::Schema)
|
9
|
+
raise(TypeError, "will not instantiate Schema from another Schema: #{schema_object.pretty_inspect.chomp}")
|
10
|
+
elsif schema_object.is_a?(Scorpio::SchemaInstanceBase)
|
11
|
+
@schema_object = Scorpio.deep_stringify_symbol_keys(schema_object.deref)
|
12
|
+
@schema_node = @schema_object.instance
|
13
|
+
elsif schema_object.is_a?(Scorpio::JSON::HashNode)
|
14
|
+
@schema_object = nil
|
15
|
+
@schema_node = Scorpio.deep_stringify_symbol_keys(schema_object.deref)
|
16
|
+
elsif schema_object.respond_to?(:to_hash)
|
17
|
+
@schema_object = nil
|
18
|
+
@schema_node = Scorpio::JSON::Node.new_by_type(Scorpio.deep_stringify_symbol_keys(schema_object), [])
|
19
|
+
else
|
20
|
+
raise(TypeError, "cannot instantiate Schema from: #{schema_object.pretty_inspect.chomp}")
|
21
|
+
end
|
22
|
+
if @schema_object
|
23
|
+
define_singleton_method(:instance) { schema_node } # aka schema_object.instance
|
24
|
+
define_singleton_method(:schema) { schema_object.schema }
|
25
|
+
extend SchemaInstanceBaseHash
|
26
|
+
else
|
27
|
+
define_singleton_method(:[]) { |*a, &b| schema_node.public_send(:[], *a, &b) }
|
28
|
+
end
|
5
29
|
end
|
6
30
|
attr_reader :schema_node
|
31
|
+
def schema_object
|
32
|
+
@schema_object || @schema_node
|
33
|
+
end
|
7
34
|
|
8
|
-
def
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
35
|
+
def schema_id
|
36
|
+
@schema_id ||= begin
|
37
|
+
# start from schema_node and ascend parents looking for an 'id' property.
|
38
|
+
# append a fragment to that id (appending to an existing fragment if there
|
39
|
+
# is one) consisting of the path from that parent to our schema_node.
|
40
|
+
node_for_id = schema_node
|
41
|
+
path_from_id_node = []
|
42
|
+
done = false
|
43
|
+
|
44
|
+
while !done
|
45
|
+
# TODO: track what parents are schemas. somehow.
|
46
|
+
# look at 'id' if node_for_id is a schema, or the document root.
|
47
|
+
# decide whether to look at '$id' for all parent nodes or also just schemas.
|
48
|
+
if node_for_id.respond_to?(:to_hash)
|
49
|
+
if node_for_id.path.empty? || node_for_id.object_id == schema_node.object_id
|
50
|
+
# I'm only looking at 'id' for the document root and the schema node
|
51
|
+
# until I track what parents are schemas.
|
52
|
+
parent_id = node_for_id['$id'] || node_for_id['id']
|
53
|
+
else
|
54
|
+
# will look at '$id' everywhere since it is less likely to show up outside schemas than
|
55
|
+
# 'id', but it will be better to only look at parents that are schemas for this too.
|
56
|
+
parent_id = node_for_id['$id']
|
57
|
+
end
|
15
58
|
end
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
else
|
20
|
-
if schema_node['additionalProperties'].is_a?(Scorpio::JSON::Node)
|
21
|
-
self.class.new(schema_node['additionalProperties'].deref)
|
59
|
+
|
60
|
+
if parent_id || node_for_id.path.empty?
|
61
|
+
done = true
|
22
62
|
else
|
23
|
-
|
63
|
+
path_from_id_node.unshift(node_for_id.path.last)
|
64
|
+
node_for_id = node_for_id.parent_node
|
24
65
|
end
|
25
66
|
end
|
67
|
+
if parent_id
|
68
|
+
parent_auri = Addressable::URI.parse(parent_id)
|
69
|
+
else
|
70
|
+
node_for_id = schema_node.document_node
|
71
|
+
validator = ::JSON::Validator.new(node_for_id.content, nil)
|
72
|
+
# TODO not good instance_exec'ing into another library's ivars
|
73
|
+
parent_auri = validator.instance_exec { @base_schema }.uri
|
74
|
+
end
|
75
|
+
if parent_auri.fragment
|
76
|
+
# add onto the fragment
|
77
|
+
parent_id_path = ::JSON::Schema::Pointer.new(:fragment, '#' + parent_auri.fragment).reference_tokens
|
78
|
+
path_from_id_node = parent_id_path + path_from_id_node
|
79
|
+
parent_auri.fragment = nil
|
80
|
+
#else: no fragment so parent_id good as is
|
81
|
+
end
|
82
|
+
|
83
|
+
fragment = ::JSON::Schema::Pointer.new(:reference_tokens, path_from_id_node).fragment
|
84
|
+
schema_id = parent_auri.to_s + fragment
|
85
|
+
|
86
|
+
schema_id
|
26
87
|
end
|
27
88
|
end
|
28
89
|
|
29
|
-
def
|
30
|
-
# matching oneOf is good here. one schema for one
|
90
|
+
def match_to_instance(instance)
|
91
|
+
# matching oneOf is good here. one schema for one instance.
|
31
92
|
# matching anyOf is okay. there could be more than one schema matched. it's often just one. if more
|
32
93
|
# than one is a match, the problems of allOf occur.
|
33
94
|
# matching allOf is questionable. all of the schemas must be matched but we just return the first match.
|
@@ -36,88 +97,139 @@ module Scorpio
|
|
36
97
|
%w(oneOf allOf anyOf).select { |k| schema_node[k].respond_to?(:to_ary) }.each do |someof_key|
|
37
98
|
schema_node[someof_key].map(&:deref).map do |someof_node|
|
38
99
|
someof_schema = self.class.new(someof_node)
|
39
|
-
if someof_schema.validate(
|
40
|
-
return someof_schema.
|
100
|
+
if someof_schema.validate(instance)
|
101
|
+
return someof_schema.match_to_instance(instance)
|
41
102
|
end
|
42
103
|
end
|
43
104
|
end
|
44
105
|
return self
|
45
106
|
end
|
46
107
|
|
47
|
-
def
|
48
|
-
|
49
|
-
if
|
50
|
-
self.class.new(
|
51
|
-
|
52
|
-
|
108
|
+
def subschema_for_property(property_name_)
|
109
|
+
memoize(:subschema_for_property, property_name_) do |property_name|
|
110
|
+
if schema_object['properties'].respond_to?(:to_hash) && schema_object['properties'][property_name].respond_to?(:to_hash)
|
111
|
+
self.class.new(schema_object['properties'][property_name])
|
112
|
+
else
|
113
|
+
if schema_object['patternProperties'].respond_to?(:to_hash)
|
114
|
+
_, pattern_schema_object = schema_object['patternProperties'].detect do |pattern, _|
|
115
|
+
property_name.to_s =~ Regexp.new(pattern) # TODO map pattern to ruby syntax
|
116
|
+
end
|
117
|
+
end
|
118
|
+
if pattern_schema_object
|
119
|
+
self.class.new(pattern_schema_object)
|
120
|
+
else
|
121
|
+
if schema_object['additionalProperties'].respond_to?(:to_hash)
|
122
|
+
self.class.new(schema_object['additionalProperties'])
|
123
|
+
else
|
124
|
+
nil
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
def subschema_for_index(index_)
|
132
|
+
memoize(:subschema_for_index, index_) do |index|
|
133
|
+
if schema_object['items'].respond_to?(:to_ary)
|
134
|
+
if index < schema_object['items'].size
|
135
|
+
self.class.new(schema_object['items'][index])
|
136
|
+
elsif schema_object['additionalItems'].respond_to?(:to_hash)
|
137
|
+
self.class.new(schema_object['additionalItems'])
|
138
|
+
end
|
139
|
+
elsif schema_object['items'].respond_to?(:to_hash)
|
140
|
+
self.class.new(schema_object['items'])
|
141
|
+
else
|
142
|
+
nil
|
53
143
|
end
|
54
|
-
elsif schema_node['items'].is_a?(Scorpio::JSON::Node)
|
55
|
-
self.class.new(schema_node['items'].deref)
|
56
|
-
else
|
57
|
-
nil
|
58
144
|
end
|
59
145
|
end
|
60
146
|
|
61
147
|
def describes_array?
|
62
|
-
|
63
|
-
schema_node['
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
schema_node['oneOf'].
|
72
|
-
|
73
|
-
schema_node['allOf'].
|
74
|
-
|
75
|
-
schema_node['anyOf'].
|
148
|
+
memoize(:describes_array?) do
|
149
|
+
schema_node['type'] == 'array' ||
|
150
|
+
schema_node['items'] ||
|
151
|
+
schema_node['additionalItems'] ||
|
152
|
+
schema_node['default'].respond_to?(:to_ary) || # TODO make sure this is right
|
153
|
+
(schema_node['enum'].respond_to?(:to_ary) && schema_node['enum'].all? { |enum| enum.respond_to?(:to_ary) }) ||
|
154
|
+
schema_node['maxItems'] ||
|
155
|
+
schema_node['minItems'] ||
|
156
|
+
schema_node.key?('uniqueItems') ||
|
157
|
+
schema_node['oneOf'].respond_to?(:to_ary) &&
|
158
|
+
schema_node['oneOf'].all? { |someof_node| self.class.new(someof_node).describes_array? } ||
|
159
|
+
schema_node['allOf'].respond_to?(:to_ary) &&
|
160
|
+
schema_node['allOf'].all? { |someof_node| self.class.new(someof_node).describes_array? } ||
|
161
|
+
schema_node['anyOf'].respond_to?(:to_ary) &&
|
162
|
+
schema_node['anyOf'].all? { |someof_node| self.class.new(someof_node).describes_array? }
|
163
|
+
end
|
76
164
|
end
|
77
165
|
def describes_hash?
|
78
|
-
|
79
|
-
schema_node['
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
schema_node['oneOf'].
|
87
|
-
|
88
|
-
schema_node['allOf'].
|
89
|
-
|
90
|
-
schema_node['anyOf'].
|
166
|
+
memoize(:describes_hash?) do
|
167
|
+
schema_node['type'] == 'object' ||
|
168
|
+
schema_node['required'].respond_to?(:to_ary) ||
|
169
|
+
schema_node['properties'].respond_to?(:to_hash) ||
|
170
|
+
schema_node['additionalProperties'] ||
|
171
|
+
schema_node['patternProperties'] ||
|
172
|
+
schema_node['default'].respond_to?(:to_hash) ||
|
173
|
+
(schema_node['enum'].respond_to?(:to_ary) && schema_node['enum'].all? { |enum| enum.respond_to?(:to_hash) }) ||
|
174
|
+
schema_node['oneOf'].respond_to?(:to_ary) &&
|
175
|
+
schema_node['oneOf'].all? { |someof_node| self.class.new(someof_node).describes_hash? } ||
|
176
|
+
schema_node['allOf'].respond_to?(:to_ary) &&
|
177
|
+
schema_node['allOf'].all? { |someof_node| self.class.new(someof_node).describes_hash? } ||
|
178
|
+
schema_node['anyOf'].respond_to?(:to_ary) &&
|
179
|
+
schema_node['anyOf'].all? { |someof_node| self.class.new(someof_node).describes_hash? }
|
180
|
+
end
|
91
181
|
end
|
92
182
|
|
93
183
|
def described_hash_property_names
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
schema_node[
|
105
|
-
|
184
|
+
memoize(:described_hash_property_names) do
|
185
|
+
Set.new.tap do |property_names|
|
186
|
+
if schema_node['properties'].respond_to?(:to_hash)
|
187
|
+
property_names.merge(schema_node['properties'].keys)
|
188
|
+
end
|
189
|
+
if schema_node['required'].respond_to?(:to_ary)
|
190
|
+
property_names.merge(schema_node['required'].to_ary)
|
191
|
+
end
|
192
|
+
# we _could_ look at the properties of 'default' and each 'enum' but ... nah.
|
193
|
+
# we should look at dependencies (TODO).
|
194
|
+
%w(oneOf allOf anyOf).select { |k| schema_node[k].respond_to?(:to_ary) }.each do |schemas_key|
|
195
|
+
schema_node[schemas_key].map(&:deref).map do |someof_node|
|
196
|
+
property_names.merge(self.class.new(someof_node).described_hash_property_names)
|
197
|
+
end
|
106
198
|
end
|
107
199
|
end
|
108
200
|
end
|
109
201
|
end
|
110
202
|
|
111
|
-
def fully_validate(
|
112
|
-
::JSON::Validator.fully_validate(schema_node.document, object_to_content(
|
203
|
+
def fully_validate(instance)
|
204
|
+
::JSON::Validator.fully_validate(schema_node.document, object_to_content(instance), fragment: schema_node.fragment)
|
113
205
|
end
|
114
|
-
def validate(
|
115
|
-
::JSON::Validator.validate(schema_node.document, object_to_content(
|
206
|
+
def validate(instance)
|
207
|
+
::JSON::Validator.validate(schema_node.document, object_to_content(instance), fragment: schema_node.fragment)
|
116
208
|
end
|
117
|
-
def validate!(
|
118
|
-
::JSON::Validator.validate!(schema_node.document, object_to_content(
|
209
|
+
def validate!(instance)
|
210
|
+
::JSON::Validator.validate!(schema_node.document, object_to_content(instance), fragment: schema_node.fragment)
|
119
211
|
end
|
120
212
|
|
213
|
+
def object_group_text
|
214
|
+
"schema_id=#{schema_id}"
|
215
|
+
end
|
216
|
+
def inspect
|
217
|
+
"\#<#{self.class.inspect} #{object_group_text} #{schema_object.inspect}>"
|
218
|
+
end
|
219
|
+
alias_method :to_s, :inspect
|
220
|
+
def pretty_print(q)
|
221
|
+
q.instance_exec(self) do |obj|
|
222
|
+
text "\#<#{obj.class.inspect} #{obj.object_group_text}"
|
223
|
+
group_sub {
|
224
|
+
nest(2) {
|
225
|
+
breakable ' '
|
226
|
+
pp obj.schema_object
|
227
|
+
}
|
228
|
+
}
|
229
|
+
breakable ''
|
230
|
+
text '>'
|
231
|
+
end
|
232
|
+
end
|
121
233
|
def fingerprint
|
122
234
|
{class: self.class, schema_node: schema_node}
|
123
235
|
end
|
@@ -125,7 +237,7 @@ module Scorpio
|
|
125
237
|
|
126
238
|
private
|
127
239
|
def object_to_content(object)
|
128
|
-
object = object.
|
240
|
+
object = object.instance if object.is_a?(Scorpio::SchemaInstanceBase)
|
129
241
|
object = object.content if object.is_a?(Scorpio::JSON::Node)
|
130
242
|
object
|
131
243
|
end
|
@@ -0,0 +1,309 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'scorpio/typelike_modules'
|
3
|
+
|
4
|
+
module Scorpio
|
5
|
+
# base class for representing an instance of an instance described by a schema
|
6
|
+
class SchemaInstanceBase
|
7
|
+
include Memoize
|
8
|
+
|
9
|
+
class << self
|
10
|
+
def schema_id
|
11
|
+
schema.schema_id
|
12
|
+
end
|
13
|
+
|
14
|
+
def inspect
|
15
|
+
if !respond_to?(:schema)
|
16
|
+
super
|
17
|
+
elsif !name || name =~ /\AScorpio::SchemaClasses::/
|
18
|
+
%Q(#{SchemaClasses.inspect}[#{schema_id.inspect}])
|
19
|
+
else
|
20
|
+
%Q(#{name} (#{schema_id}))
|
21
|
+
end
|
22
|
+
end
|
23
|
+
def to_s
|
24
|
+
if !respond_to?(:schema)
|
25
|
+
super
|
26
|
+
elsif !name || name =~ /\AScorpio::SchemaClasses::/
|
27
|
+
%Q(#{SchemaClasses.inspect}[#{schema_id.inspect}])
|
28
|
+
else
|
29
|
+
name
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def schema_classes_const_name
|
34
|
+
name = schema.schema_id.gsub(/[^\w]/, '_')
|
35
|
+
name = 'X' + name unless name[/\A[a-zA-Z_]/]
|
36
|
+
name = name[0].upcase + name[1..-1]
|
37
|
+
name
|
38
|
+
end
|
39
|
+
|
40
|
+
def name
|
41
|
+
unless super
|
42
|
+
SchemaClasses.const_set(schema_classes_const_name, self)
|
43
|
+
end
|
44
|
+
super
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def initialize(instance)
|
49
|
+
unless respond_to?(:schema)
|
50
|
+
raise(TypeError, "cannot instantiate #{self.class.inspect} which has no method #schema. please use Scorpio.class_for_schema")
|
51
|
+
end
|
52
|
+
|
53
|
+
self.instance = instance
|
54
|
+
|
55
|
+
if @instance.is_a?(Scorpio::JSON::HashNode)
|
56
|
+
extend SchemaInstanceBaseHash
|
57
|
+
elsif @instance.is_a?(Scorpio::JSON::ArrayNode)
|
58
|
+
extend SchemaInstanceBaseArray
|
59
|
+
end
|
60
|
+
# certain methods need to be redefined after we are extended by Enumerable
|
61
|
+
extend OverrideFromExtensions
|
62
|
+
end
|
63
|
+
|
64
|
+
module OverrideFromExtensions
|
65
|
+
def as_json
|
66
|
+
Typelike.as_json(instance)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
attr_reader :instance
|
71
|
+
|
72
|
+
def deref
|
73
|
+
derefed = instance.deref
|
74
|
+
if derefed.object_id == instance.object_id
|
75
|
+
self
|
76
|
+
else
|
77
|
+
self.class.new(derefed)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def modified_copy(&block)
|
82
|
+
modified_instance = instance.modified_copy(&block)
|
83
|
+
self.class.new(modified_instance)
|
84
|
+
end
|
85
|
+
|
86
|
+
def fragment
|
87
|
+
instance.fragment
|
88
|
+
end
|
89
|
+
|
90
|
+
def fully_validate
|
91
|
+
schema.fully_validate(instance)
|
92
|
+
end
|
93
|
+
def validate
|
94
|
+
schema.validate(instance)
|
95
|
+
end
|
96
|
+
def validate!
|
97
|
+
schema.validate!(instance)
|
98
|
+
end
|
99
|
+
def inspect
|
100
|
+
"\#<#{self.class.to_s} #{instance.inspect}>"
|
101
|
+
end
|
102
|
+
def pretty_print(q)
|
103
|
+
q.instance_exec(self) do |obj|
|
104
|
+
text "\#<#{obj.class.to_s}"
|
105
|
+
group_sub {
|
106
|
+
nest(2) {
|
107
|
+
breakable ' '
|
108
|
+
pp obj.instance
|
109
|
+
}
|
110
|
+
}
|
111
|
+
breakable ''
|
112
|
+
text '>'
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
def object_group_text
|
117
|
+
instance.object_group_text
|
118
|
+
end
|
119
|
+
|
120
|
+
def fingerprint
|
121
|
+
{class: self.class, instance: instance}
|
122
|
+
end
|
123
|
+
include FingerprintHash
|
124
|
+
|
125
|
+
private
|
126
|
+
def instance=(thing)
|
127
|
+
clear_memo(:[])
|
128
|
+
if instance_variable_defined?(:@instance)
|
129
|
+
if @instance.class != thing.class
|
130
|
+
raise(Scorpio::Bug, "will not accept instance of different class #{thing.class} to current instance class #{@instance.class} on #{self.class.inspect}")
|
131
|
+
end
|
132
|
+
end
|
133
|
+
if thing.is_a?(SchemaInstanceBase)
|
134
|
+
warn "assigning instance to a SchemaInstanceBase instance is incorrect. received: #{thing.pretty_inspect.chomp}"
|
135
|
+
@instance = Scorpio.deep_stringify_symbol_keys(thing.instance)
|
136
|
+
elsif thing.is_a?(Scorpio::JSON::Node)
|
137
|
+
@instance = Scorpio.deep_stringify_symbol_keys(thing)
|
138
|
+
else
|
139
|
+
@instance = Scorpio::JSON::Node.new_by_type(Scorpio.deep_stringify_symbol_keys(thing), [])
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
# this module is just a namespace for schema classes.
|
145
|
+
module SchemaClasses
|
146
|
+
def self.[](schema_id)
|
147
|
+
@classes_by_id[schema_id]
|
148
|
+
end
|
149
|
+
@classes_by_id = {}
|
150
|
+
end
|
151
|
+
|
152
|
+
def self.class_for_schema(schema_object)
|
153
|
+
if schema_object.is_a?(Scorpio::Schema)
|
154
|
+
schema__ = schema_object
|
155
|
+
else
|
156
|
+
schema__ = Scorpio::Schema.new(schema_object)
|
157
|
+
end
|
158
|
+
|
159
|
+
memoize(:class_for_schema, schema__) do |schema_|
|
160
|
+
begin
|
161
|
+
begin
|
162
|
+
Class.new(SchemaInstanceBase).instance_exec(schema_) do |schema|
|
163
|
+
begin
|
164
|
+
include(Scorpio.module_for_schema(schema))
|
165
|
+
|
166
|
+
SchemaClasses.instance_exec(self) { |klass| @classes_by_id[klass.schema_id] = klass }
|
167
|
+
|
168
|
+
self
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
def self.module_for_schema(schema_object)
|
177
|
+
if schema_object.is_a?(Scorpio::Schema)
|
178
|
+
schema__ = schema_object
|
179
|
+
else
|
180
|
+
schema__ = Scorpio::Schema.new(schema_object)
|
181
|
+
end
|
182
|
+
|
183
|
+
memoize(:module_for_schema, schema__) do |schema_|
|
184
|
+
Module.new.tap do |m|
|
185
|
+
m.instance_exec(schema_) do |schema|
|
186
|
+
define_method(:schema) { schema }
|
187
|
+
define_singleton_method(:schema) { schema }
|
188
|
+
define_singleton_method(:included) do |includer|
|
189
|
+
includer.send(:define_singleton_method, :schema) { schema }
|
190
|
+
end
|
191
|
+
|
192
|
+
define_singleton_method(:schema_id) do
|
193
|
+
schema.schema_id
|
194
|
+
end
|
195
|
+
define_singleton_method(:inspect) do
|
196
|
+
%Q(#<Module for Schema: #{schema_id}>)
|
197
|
+
end
|
198
|
+
|
199
|
+
if schema.describes_hash?
|
200
|
+
instance_method_modules = [m, SchemaInstanceBase, SchemaInstanceBaseArray, SchemaInstanceBaseHash, SchemaInstanceBase::OverrideFromExtensions]
|
201
|
+
instance_methods = instance_method_modules.map do |mod|
|
202
|
+
mod.instance_methods + mod.private_instance_methods
|
203
|
+
end.inject(Set.new, &:|)
|
204
|
+
accessors_to_define = schema.described_hash_property_names.map(&:to_s) - instance_methods.map(&:to_s)
|
205
|
+
accessors_to_define.each do |property_name|
|
206
|
+
define_method(property_name) do
|
207
|
+
if respond_to?(:[])
|
208
|
+
self[property_name]
|
209
|
+
else
|
210
|
+
raise(NoMethodError, "instance does not respond to []; cannot call reader `#{property_name}' for: #{pretty_inspect.chomp}")
|
211
|
+
end
|
212
|
+
end
|
213
|
+
define_method("#{property_name}=") do |value|
|
214
|
+
if respond_to?(:[]=)
|
215
|
+
self[property_name] = value
|
216
|
+
else
|
217
|
+
raise(NoMethodError, "instance does not respond to []=; cannot call writer `#{property_name}=' for: #{pretty_inspect.chomp}")
|
218
|
+
end
|
219
|
+
end
|
220
|
+
end
|
221
|
+
end
|
222
|
+
end
|
223
|
+
end
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
module SchemaInstanceBaseHash
|
228
|
+
# Hash methods
|
229
|
+
def each
|
230
|
+
return to_enum(__method__) { instance.size } unless block_given?
|
231
|
+
instance.each_key { |k| yield(k, self[k]) }
|
232
|
+
self
|
233
|
+
end
|
234
|
+
|
235
|
+
def to_hash
|
236
|
+
inject({}) { |h, (k, v)| h[k] = v; h }
|
237
|
+
end
|
238
|
+
|
239
|
+
include Hashlike
|
240
|
+
|
241
|
+
# methods that don't look at the value; can skip the overhead of #[] (invoked by #to_hash)
|
242
|
+
SAFE_KEY_ONLY_METHODS.each do |method_name|
|
243
|
+
define_method(method_name) { |*a, &b| instance.public_send(method_name, *a, &b) }
|
244
|
+
end
|
245
|
+
|
246
|
+
def [](property_name_)
|
247
|
+
memoize(:[], property_name_) do |property_name|
|
248
|
+
begin
|
249
|
+
property_schema = schema.subschema_for_property(property_name)
|
250
|
+
property_schema = property_schema && property_schema.match_to_instance(instance[property_name])
|
251
|
+
|
252
|
+
if property_schema && instance[property_name].is_a?(JSON::Node)
|
253
|
+
Scorpio.class_for_schema(property_schema).new(instance[property_name])
|
254
|
+
else
|
255
|
+
instance[property_name]
|
256
|
+
end
|
257
|
+
end
|
258
|
+
end
|
259
|
+
end
|
260
|
+
|
261
|
+
def []=(property_name, value)
|
262
|
+
self.instance = instance.modified_copy do |hash|
|
263
|
+
hash.merge(property_name => value)
|
264
|
+
end
|
265
|
+
end
|
266
|
+
end
|
267
|
+
|
268
|
+
module SchemaInstanceBaseArray
|
269
|
+
def each
|
270
|
+
return to_enum(__method__) { instance.size } unless block_given?
|
271
|
+
instance.each_index { |i| yield(self[i]) }
|
272
|
+
self
|
273
|
+
end
|
274
|
+
|
275
|
+
def to_ary
|
276
|
+
to_a
|
277
|
+
end
|
278
|
+
|
279
|
+
include Arraylike
|
280
|
+
|
281
|
+
# methods that don't look at the value; can skip the overhead of #[] (invoked by #to_a).
|
282
|
+
# we override these methods from Arraylike
|
283
|
+
SAFE_INDEX_ONLY_METHODS.each do |method_name|
|
284
|
+
define_method(method_name) { |*a, &b| instance.public_send(method_name, *a, &b) }
|
285
|
+
end
|
286
|
+
|
287
|
+
def [](i_)
|
288
|
+
memoize(:[], i_) do |i|
|
289
|
+
begin
|
290
|
+
index_schema = schema.subschema_for_index(i)
|
291
|
+
index_schema = index_schema && index_schema.match_to_instance(instance[i])
|
292
|
+
|
293
|
+
if index_schema && instance[i].is_a?(JSON::Node)
|
294
|
+
Scorpio.class_for_schema(index_schema).new(instance[i])
|
295
|
+
else
|
296
|
+
instance[i]
|
297
|
+
end
|
298
|
+
end
|
299
|
+
end
|
300
|
+
end
|
301
|
+
def []=(i, value)
|
302
|
+
self.instance = instance.modified_copy do |ary|
|
303
|
+
ary.each_with_index.map do |el, ary_i|
|
304
|
+
ary_i == i ? value : el
|
305
|
+
end
|
306
|
+
end
|
307
|
+
end
|
308
|
+
end
|
309
|
+
end
|