scorpio 0.4.5 → 0.6.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 +13 -0
- data/LICENSE.md +613 -0
- data/README.md +31 -18
- data/documents/github.com/OAI/OpenAPI-Specification/blob/oas3-schema/schemas/v3.0/schema.yaml +30 -22
- data/lib/scorpio/google_api_document.rb +27 -15
- data/lib/scorpio/openapi/document.rb +8 -6
- data/lib/scorpio/openapi/operation.rb +92 -42
- data/lib/scorpio/openapi/operations_scope.rb +13 -11
- data/lib/scorpio/openapi/reference.rb +44 -0
- data/lib/scorpio/openapi/tag.rb +15 -0
- data/lib/scorpio/openapi/v3/server.rb +4 -2
- data/lib/scorpio/openapi.rb +186 -135
- data/lib/scorpio/pickle_adapter.rb +2 -0
- data/lib/scorpio/request.rb +60 -41
- data/lib/scorpio/resource_base.rb +238 -198
- data/lib/scorpio/response.rb +10 -6
- data/lib/scorpio/ur.rb +16 -15
- data/lib/scorpio/version.rb +3 -1
- data/lib/scorpio.rb +5 -6
- data/scorpio.gemspec +16 -23
- metadata +23 -206
- data/.simplecov +0 -1
- data/LICENSE.txt +0 -21
- data/Rakefile +0 -10
- data/bin/documents_to_yml.rb +0 -33
@@ -1,6 +1,8 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Scorpio
|
2
4
|
# see also Faraday::Env::MethodsWithBodies
|
3
|
-
METHODS_WITH_BODIES = %w(post put patch options)
|
5
|
+
METHODS_WITH_BODIES = %w(post put patch options).map(&:freeze).freeze
|
4
6
|
class RequestSchemaFailure < Error
|
5
7
|
end
|
6
8
|
|
@@ -20,7 +22,7 @@ module Scorpio
|
|
20
22
|
def define_inheritable_accessor(accessor, default_value: nil, default_getter: -> { default_value }, on_set: nil)
|
21
23
|
# the value before the field is set (overwritten) is the result of the default_getter proc
|
22
24
|
define_singleton_method(accessor, &default_getter)
|
23
|
-
inheritable_accessor_defaults[accessor] =
|
25
|
+
inheritable_accessor_defaults[accessor] = singleton_class.instance_method(accessor)
|
24
26
|
# field setter method. redefines the getter, replacing the method with one that returns the
|
25
27
|
# setter's argument (that being inherited to the scope of the define_method(accessor) block
|
26
28
|
define_singleton_method(:"#{accessor}=") do |value|
|
@@ -42,25 +44,18 @@ module Scorpio
|
|
42
44
|
end
|
43
45
|
end
|
44
46
|
end
|
45
|
-
define_inheritable_accessor(:represented_schemas, default_value: [], on_set: proc do
|
46
|
-
|
47
|
-
raise(TypeError, "represented_schemas must be an array. received: #{represented_schemas.pretty_inspect.chomp}")
|
48
|
-
end
|
49
|
-
if represented_schemas.all? { |s| s.is_a?(JSI::Schema) }
|
47
|
+
define_inheritable_accessor(:represented_schemas, default_value: Set[].freeze, on_set: proc do
|
48
|
+
if represented_schemas.is_a?(JSI::SchemaSet)
|
50
49
|
represented_schemas.each do |schema|
|
51
|
-
|
50
|
+
new_mbs = openapi_document_class.models_by_schema.merge(schema => self).freeze
|
51
|
+
openapi_document_class.models_by_schema = new_mbs
|
52
52
|
end
|
53
53
|
update_dynamic_methods
|
54
54
|
else
|
55
|
-
self.represented_schemas =
|
56
|
-
unless schema.is_a?(JSI::Schema)
|
57
|
-
schema = JSI::Schema.new(schema)
|
58
|
-
end
|
59
|
-
schema
|
60
|
-
end
|
55
|
+
self.represented_schemas = JSI::SchemaSet.ensure_schema_set(represented_schemas)
|
61
56
|
end
|
62
57
|
end)
|
63
|
-
define_inheritable_accessor(:models_by_schema, default_value: {})
|
58
|
+
define_inheritable_accessor(:models_by_schema, default_value: {}.freeze)
|
64
59
|
# a model overriding this MUST include the openapi document's basePath if defined, e.g.
|
65
60
|
# class MyModel
|
66
61
|
# self.base_url = File.join('https://example.com/', openapi_document.basePath)
|
@@ -69,7 +64,7 @@ module Scorpio
|
|
69
64
|
openapi_document.base_url(server: server, server_variables: server_variables)
|
70
65
|
})
|
71
66
|
|
72
|
-
define_inheritable_accessor(:server_variables, default_value: {}, on_set: -> {
|
67
|
+
define_inheritable_accessor(:server_variables, default_value: {}.freeze, on_set: -> {
|
73
68
|
if openapi_document && openapi_document.v2?
|
74
69
|
raise(ArgumentError, "server variables are not supported for OpenAPI V2")
|
75
70
|
end
|
@@ -113,9 +108,9 @@ module Scorpio
|
|
113
108
|
define_singleton_method(:openapi_document_class) { openapi_document_class }
|
114
109
|
define_singleton_method(:openapi_document=) do |_|
|
115
110
|
if self == openapi_document_class
|
116
|
-
raise(ArgumentError, "openapi_document may only be set once on #{
|
111
|
+
raise(ArgumentError, "openapi_document may only be set once on #{inspect}")
|
117
112
|
else
|
118
|
-
raise(ArgumentError, "openapi_document may not be overridden on subclass #{
|
113
|
+
raise(ArgumentError, "openapi_document may not be overridden on subclass #{inspect} after it was set on #{openapi_document_class.inspect}")
|
119
114
|
end
|
120
115
|
end
|
121
116
|
# TODO blame validate openapi_document
|
@@ -151,7 +146,7 @@ module Scorpio
|
|
151
146
|
end
|
152
147
|
|
153
148
|
def all_schema_properties
|
154
|
-
represented_schemas.map(&:described_object_property_names).inject(Set.new,
|
149
|
+
represented_schemas.map(&:described_object_property_names).inject(Set.new, &:merge)
|
155
150
|
end
|
156
151
|
|
157
152
|
def update_instance_accessors
|
@@ -170,13 +165,13 @@ module Scorpio
|
|
170
165
|
end
|
171
166
|
|
172
167
|
def operation_for_resource_class?(operation)
|
173
|
-
return
|
168
|
+
return true if tag_name && operation.tags.respond_to?(:to_ary) && operation.tags.include?(tag_name)
|
174
169
|
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
170
|
+
request_response_schemas = operation.request_schemas | operation.response_schemas
|
171
|
+
# TODO/FIX nil instance is wrong. works for $ref and allOf, not for others.
|
172
|
+
# use all inplace applicators, not conditional on instance
|
173
|
+
all_request_response_schemas = request_response_schemas.each_inplace_applicator_schema(nil)
|
174
|
+
return true if all_request_response_schemas.any? { |s| represented_schemas.include?(s) }
|
180
175
|
|
181
176
|
return false
|
182
177
|
end
|
@@ -184,61 +179,69 @@ module Scorpio
|
|
184
179
|
def operation_for_resource_instance?(operation)
|
185
180
|
return false unless operation_for_resource_class?(operation)
|
186
181
|
|
187
|
-
# define an instance method if the request
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
#
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
#request_attributes |= method_desc['parameters'] ? method_desc['parameters'].keys : []
|
205
|
-
|
206
|
-
schema_attributes = represented_schemas.map(&:described_object_property_names).inject(Set.new, &:|)
|
207
|
-
|
208
|
-
return request_resource_is_self || (request_attributes & schema_attributes.to_a).any?
|
209
|
-
end
|
210
|
-
|
211
|
-
def method_names_by_operation
|
212
|
-
@method_names_by_operation ||= Hash.new do |h, operation|
|
213
|
-
h[operation] = begin
|
214
|
-
raise(ArgumentError, operation.pretty_inspect) unless operation.is_a?(Scorpio::OpenAPI::Operation)
|
215
|
-
|
216
|
-
# if Pet is the Scorpio resource class
|
217
|
-
# and Pet.tag_name is "pet"
|
218
|
-
# and operation's operationId is "pet.add"
|
219
|
-
# then the operation's method name on Pet will be "add".
|
220
|
-
# if the operationId is just "addPet"
|
221
|
-
# then the operation's method name on Pet will be "addPet".
|
222
|
-
tag_name_match = tag_name &&
|
223
|
-
operation.tags.respond_to?(:to_ary) && # TODO maybe operation.tags.valid?
|
224
|
-
operation.tags.include?(tag_name) &&
|
225
|
-
operation.operationId &&
|
226
|
-
operation.operationId.match(/\A#{Regexp.escape(tag_name)}\.(\w+)\z/)
|
227
|
-
|
228
|
-
if tag_name_match
|
229
|
-
method_name = tag_name_match[1]
|
230
|
-
else
|
231
|
-
method_name = operation.operationId
|
182
|
+
# define an instance method if the operation's request schemas include any of our represented_schemas
|
183
|
+
#
|
184
|
+
# TODO/FIX nil instance is wrong. works for $ref and allOf, not for others.
|
185
|
+
# use all inplace applicators, not conditional on instance
|
186
|
+
all_request_schemas = operation.request_schemas.each_inplace_applicator_schema(nil)
|
187
|
+
return true if all_request_schemas.any? { |s| represented_schemas.include?(s) }
|
188
|
+
|
189
|
+
# the below only apply if the operation has this resource's tag
|
190
|
+
return false unless tag_name && operation.tags.respond_to?(:to_ary) && operation.tags.include?(tag_name)
|
191
|
+
|
192
|
+
# define an instance method if path or query params can be filled in from
|
193
|
+
# property names described by represented_schemas
|
194
|
+
schema_attributes = represented_schemas.map(&:described_object_property_names).inject(Set.new, &:merge)
|
195
|
+
operation.inferred_parameters.each do |param|
|
196
|
+
if param['in'] == 'path' || param['in'] == 'query'
|
197
|
+
if schema_attributes.include?(param['name'])
|
198
|
+
return true
|
232
199
|
end
|
233
200
|
end
|
234
201
|
end
|
202
|
+
|
203
|
+
return false
|
204
|
+
end
|
205
|
+
|
206
|
+
# @private
|
207
|
+
# @param name [String]
|
208
|
+
# @return [Scorpio::OpenAPI::Operation, nil]
|
209
|
+
def operation_for_api_method_name(name)
|
210
|
+
openapi_document.operations.detect do |op|
|
211
|
+
operation_for_resource_class?(op) && api_method_name_by_operation(op) == name
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
# @private
|
216
|
+
# @param name [Scorpio::OpenAPI::Operation]
|
217
|
+
# @return [String, nil]
|
218
|
+
def api_method_name_by_operation(operation)
|
219
|
+
raise(ArgumentError, operation.pretty_inspect) unless operation.is_a?(Scorpio::OpenAPI::Operation)
|
220
|
+
|
221
|
+
# if Pet is the Scorpio resource class
|
222
|
+
# and Pet.tag_name is "pet"
|
223
|
+
# and operation's operationId is "pet.add" or "pet/add" or "pet:add"
|
224
|
+
# then the operation's method name on Pet will be "add".
|
225
|
+
# if the operationId is just "addPet"
|
226
|
+
# then the operation's method name on Pet will be "addPet".
|
227
|
+
tag_name_match = tag_name &&
|
228
|
+
operation.tags.respond_to?(:to_ary) && # TODO maybe operation.tags.valid?
|
229
|
+
operation.tags.include?(tag_name) &&
|
230
|
+
operation.operationId &&
|
231
|
+
operation.operationId.match(/\A#{Regexp.escape(tag_name)}[\.\/\:](\w+)\z/)
|
232
|
+
|
233
|
+
if tag_name_match
|
234
|
+
tag_name_match[1]
|
235
|
+
else
|
236
|
+
operation.operationId
|
237
|
+
end
|
235
238
|
end
|
236
239
|
|
237
240
|
def update_class_and_instance_api_methods
|
238
241
|
openapi_document.paths.each do |path, path_item|
|
239
242
|
path_item.each do |http_method, operation|
|
240
243
|
next unless operation.is_a?(Scorpio::OpenAPI::Operation)
|
241
|
-
method_name =
|
244
|
+
method_name = api_method_name_by_operation(operation)
|
242
245
|
if method_name
|
243
246
|
# class method
|
244
247
|
if operation_for_resource_class?(operation) && !respond_to?(method_name)
|
@@ -259,8 +262,8 @@ module Scorpio
|
|
259
262
|
end
|
260
263
|
|
261
264
|
def call_operation(operation, call_params: nil, model_attributes: nil)
|
262
|
-
call_params = JSI.stringify_symbol_keys(call_params) if call_params.respond_to?(:to_hash)
|
263
|
-
model_attributes = JSI.stringify_symbol_keys(model_attributes || {})
|
265
|
+
call_params = JSI::Util.stringify_symbol_keys(call_params) if call_params.respond_to?(:to_hash)
|
266
|
+
model_attributes = JSI::Util.stringify_symbol_keys(model_attributes || {})
|
264
267
|
|
265
268
|
request = Scorpio::Request.new(operation)
|
266
269
|
|
@@ -276,7 +279,7 @@ module Scorpio
|
|
276
279
|
# Scorpio::ResourceBase.instance_method(:server_variables)
|
277
280
|
# => #<UnboundMethod: #<Class:Scorpio::ResourceBase>#server_variables>
|
278
281
|
# even though they are really the same method (the #owner for both is Scorpio::ResourceBase)
|
279
|
-
inheritable_accessor_defaults[accessor] !=
|
282
|
+
inheritable_accessor_defaults[accessor] != singleton_class.instance_method(accessor).owner.instance_method(accessor)
|
280
283
|
end
|
281
284
|
|
282
285
|
# pretty ugly... may find a better way to do this.
|
@@ -311,11 +314,30 @@ module Scorpio
|
|
311
314
|
end
|
312
315
|
|
313
316
|
if operation.request_schema
|
317
|
+
request_body_for_schema = -> (o) do
|
318
|
+
if o.is_a?(JSI::Base)
|
319
|
+
# TODO check indicated schemas
|
320
|
+
if o.jsi_schemas.include?(operation.request_schema)
|
321
|
+
jsi = o
|
322
|
+
else
|
323
|
+
# TODO maybe better way than reinstantiating another jsi as request_schema
|
324
|
+
jsi = operation.request_schema.new_jsi(o.jsi_node_content)
|
325
|
+
end
|
326
|
+
else
|
327
|
+
jsi = operation.request_schema.new_jsi(o)
|
328
|
+
end
|
329
|
+
jsi.jsi_select_children_leaf_first do |node|
|
330
|
+
# we want to specifically reject only nodes described (only) by a false schema.
|
331
|
+
# note that for OpenAPI schemas, false is only a valid schema as a value
|
332
|
+
# of `additionalProperties`
|
333
|
+
node.jsi_schemas.empty? || !node.jsi_schemas.all? { |s| s.schema_content == false }
|
334
|
+
end
|
335
|
+
end
|
314
336
|
# TODO deal with model_attributes / call_params better in nested whatever
|
315
337
|
if call_params.nil?
|
316
|
-
request.body_object = request_body_for_schema(model_attributes
|
338
|
+
request.body_object = request_body_for_schema.(model_attributes)
|
317
339
|
elsif call_params.respond_to?(:to_hash)
|
318
|
-
body = request_body_for_schema(model_attributes
|
340
|
+
body = request_body_for_schema.(model_attributes)
|
319
341
|
request.body_object = body.merge(call_params) # TODO
|
320
342
|
else
|
321
343
|
request.body_object = call_params
|
@@ -347,133 +369,109 @@ module Scorpio
|
|
347
369
|
response_object_to_instances(ur.response.body_object, initialize_options)
|
348
370
|
end
|
349
371
|
|
350
|
-
def
|
351
|
-
if object.is_a?(
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
subschema = schema['properties'][key]
|
364
|
-
include_pair = true
|
365
|
-
else
|
366
|
-
if schema['patternProperties'].respond_to?(:to_hash)
|
367
|
-
_, pattern_schema = schema['patternProperties'].detect do |pattern, _|
|
368
|
-
key =~ Regexp.new(pattern) # TODO map pattern to ruby syntax
|
369
|
-
end
|
370
|
-
end
|
371
|
-
if pattern_schema
|
372
|
-
subschema = pattern_schema
|
373
|
-
include_pair = true
|
374
|
-
else
|
375
|
-
if schema['additionalProperties'] == false
|
376
|
-
include_pair = false
|
377
|
-
elsif [nil, true].include?(schema['additionalProperties'])
|
378
|
-
include_pair = true
|
379
|
-
subschema = nil
|
380
|
-
else
|
381
|
-
include_pair = true
|
382
|
-
subschema = schema['additionalProperties']
|
383
|
-
end
|
384
|
-
end
|
385
|
-
end
|
386
|
-
elsif schema['type']
|
387
|
-
request_schema_fail(object, schema)
|
388
|
-
else
|
389
|
-
# TODO not sure
|
390
|
-
include_pair = true
|
391
|
-
subschema = nil
|
392
|
-
end
|
393
|
-
end
|
394
|
-
if include_pair
|
395
|
-
{key => request_body_for_schema(value, subschema)}
|
396
|
-
else
|
397
|
-
{}
|
398
|
-
end
|
399
|
-
end.inject({}, &:update)
|
400
|
-
elsif object.respond_to?(:to_ary) || object.is_a?(Set)
|
401
|
-
object.map do |el|
|
402
|
-
if schema
|
403
|
-
if schema['type'] == 'array'
|
404
|
-
# TODO index based subschema or whatever else works for array
|
405
|
-
subschema = schema['items']
|
406
|
-
elsif schema['type']
|
407
|
-
request_schema_fail(object, schema)
|
408
|
-
end
|
409
|
-
end
|
410
|
-
request_body_for_schema(el, subschema)
|
411
|
-
end
|
372
|
+
def response_object_to_instances(object, initialize_options = {})
|
373
|
+
if object.is_a?(JSI::Base)
|
374
|
+
models = object.jsi_schemas.map { |schema| models_by_schema[schema] }.compact
|
375
|
+
if models.size == 0
|
376
|
+
model = nil
|
377
|
+
elsif models.size == 1
|
378
|
+
model = models.first
|
379
|
+
else
|
380
|
+
raise(Scorpio::OpenAPI::Error, "multiple models indicated by response JSI. models: #{models.inspect}; object: #{object.pretty_inspect.chomp}")
|
381
|
+
end
|
382
|
+
|
383
|
+
if model && object.respond_to?(:to_hash)
|
384
|
+
model.new(object, initialize_options)
|
412
385
|
else
|
413
|
-
|
414
|
-
# TODO check conformance to schema, request_schema_fail if not
|
415
|
-
object
|
386
|
+
Container.new_container(object, openapi_document_class, initialize_options)
|
416
387
|
end
|
388
|
+
else
|
389
|
+
object
|
417
390
|
end
|
418
391
|
end
|
392
|
+
end
|
393
|
+
end
|
419
394
|
|
420
|
-
|
421
|
-
|
395
|
+
class ResourceBase
|
396
|
+
module Containment
|
397
|
+
def [](key)
|
398
|
+
sub = contained_object[key]
|
399
|
+
if sub.is_a?(JSI::Base)
|
400
|
+
# TODO avoid reinstantiating the container only to throw it away if it matches the memo
|
401
|
+
sub_container = @openapi_document_class.response_object_to_instances(sub, options)
|
402
|
+
|
403
|
+
if @subscript_memos.key?(key) && @subscript_memos[key].class == sub_container.class
|
404
|
+
@subscript_memos[key]
|
405
|
+
else
|
406
|
+
@subscript_memos[key] = sub_container
|
407
|
+
end
|
408
|
+
else
|
409
|
+
sub
|
410
|
+
end
|
422
411
|
end
|
423
412
|
|
424
|
-
def
|
425
|
-
|
426
|
-
|
413
|
+
def []=(key, value)
|
414
|
+
@subscript_memos.delete(key)
|
415
|
+
if value.is_a?(Containment)
|
416
|
+
contained_object[key] = value.contained_object
|
417
|
+
else
|
418
|
+
contained_object[key] = value
|
427
419
|
end
|
420
|
+
end
|
428
421
|
|
429
|
-
|
430
|
-
|
431
|
-
|
432
|
-
|
433
|
-
|
434
|
-
|
435
|
-
|
436
|
-
|
437
|
-
|
438
|
-
|
439
|
-
|
440
|
-
|
441
|
-
|
442
|
-
|
443
|
-
|
444
|
-
|
445
|
-
|
446
|
-
|
447
|
-
|
448
|
-
else
|
449
|
-
object
|
422
|
+
def as_json(*opt)
|
423
|
+
JSI::Typelike.as_json(contained_object, *opt)
|
424
|
+
end
|
425
|
+
|
426
|
+
def inspect
|
427
|
+
"\#<#{self.class.inspect} #{contained_object.inspect}>"
|
428
|
+
end
|
429
|
+
|
430
|
+
def pretty_print(q)
|
431
|
+
q.instance_exec(self) do |obj|
|
432
|
+
text "\#<#{obj.class.inspect}"
|
433
|
+
group_sub {
|
434
|
+
nest(2) {
|
435
|
+
breakable ' '
|
436
|
+
pp obj.contained_object
|
437
|
+
}
|
438
|
+
}
|
439
|
+
breakable ''
|
440
|
+
text '>'
|
450
441
|
end
|
451
442
|
end
|
443
|
+
|
444
|
+
include JSI::Util::FingerprintHash
|
445
|
+
|
446
|
+
def jsi_fingerprint
|
447
|
+
{class: self.class, contained_object: as_json}
|
448
|
+
end
|
452
449
|
end
|
450
|
+
end
|
451
|
+
|
452
|
+
class ResourceBase
|
453
|
+
include Containment
|
453
454
|
|
454
455
|
def initialize(attributes = {}, options = {})
|
455
|
-
@attributes = JSI.stringify_symbol_keys(attributes)
|
456
|
-
@options = JSI.stringify_symbol_keys(options)
|
456
|
+
@attributes = JSI::Util.stringify_symbol_keys(attributes)
|
457
|
+
@options = JSI::Util.stringify_symbol_keys(options)
|
457
458
|
@persisted = !!@options['persisted']
|
459
|
+
|
460
|
+
@openapi_document_class = self.class.openapi_document_class
|
461
|
+
@subscript_memos = {}
|
458
462
|
end
|
459
463
|
|
460
464
|
attr_reader :attributes
|
461
465
|
attr_reader :options
|
462
466
|
|
467
|
+
alias_method :contained_object, :attributes
|
468
|
+
|
463
469
|
def persisted?
|
464
470
|
@persisted
|
465
471
|
end
|
466
472
|
|
467
|
-
def [](key)
|
468
|
-
@attributes[key]
|
469
|
-
end
|
470
|
-
|
471
|
-
def []=(key, value)
|
472
|
-
@attributes[key] = value
|
473
|
-
end
|
474
|
-
|
475
473
|
def call_api_method(method_name, call_params: nil)
|
476
|
-
operation = self.class.
|
474
|
+
operation = self.class.operation_for_api_method_name(method_name) || raise(ArgumentError)
|
477
475
|
call_operation(operation, call_params: call_params)
|
478
476
|
end
|
479
477
|
|
@@ -497,31 +495,73 @@ module Scorpio
|
|
497
495
|
|
498
496
|
response
|
499
497
|
end
|
498
|
+
end
|
500
499
|
|
501
|
-
|
502
|
-
|
503
|
-
|
500
|
+
class ResourceBase
|
501
|
+
class Container
|
502
|
+
@container_classes = Hash.new do |h, modules|
|
503
|
+
container_class = Class.new(Container)
|
504
|
+
modules.each do |mod|
|
505
|
+
container_class.include(mod)
|
506
|
+
end
|
507
|
+
h[modules] = container_class
|
508
|
+
end
|
504
509
|
|
505
|
-
|
506
|
-
|
507
|
-
|
508
|
-
|
509
|
-
|
510
|
-
|
511
|
-
|
512
|
-
|
513
|
-
|
514
|
-
|
515
|
-
|
516
|
-
|
517
|
-
|
518
|
-
|
510
|
+
class << self
|
511
|
+
def new_container(object, openapi_document_class, options = {})
|
512
|
+
container_modules = Set[]
|
513
|
+
|
514
|
+
# TODO this is JSI internals that scorpio shouldn't really be using
|
515
|
+
if object.respond_to?(:to_hash)
|
516
|
+
container_modules << Enumerable # TODO change next JSI when PathedHashNode includes Enumerable
|
517
|
+
container_modules << JSI::PathedHashNode
|
518
|
+
end
|
519
|
+
if object.respond_to?(:to_ary)
|
520
|
+
container_modules << Enumerable # TODO change next JSI when PathedArrayNode includes Enumerable
|
521
|
+
container_modules << JSI::PathedArrayNode
|
522
|
+
end
|
523
|
+
|
524
|
+
container_modules += object.jsi_schemas.map do |schema|
|
525
|
+
JSI::SchemaClasses.accessor_module_for_schema(schema,
|
526
|
+
conflicting_modules: container_modules + [Container],
|
527
|
+
)
|
528
|
+
end
|
529
|
+
|
530
|
+
container_class = @container_classes[container_modules.freeze]
|
531
|
+
|
532
|
+
container_class.new(object, openapi_document_class, options)
|
533
|
+
end
|
519
534
|
end
|
520
535
|
end
|
521
536
|
|
522
|
-
|
523
|
-
|
537
|
+
class Container
|
538
|
+
include Containment
|
539
|
+
|
540
|
+
def initialize(contained_object, openapi_document_class, options = {})
|
541
|
+
@contained_object = contained_object
|
542
|
+
@openapi_document_class = openapi_document_class
|
543
|
+
@options = options
|
544
|
+
@subscript_memos = {}
|
545
|
+
end
|
546
|
+
|
547
|
+
attr_reader :contained_object
|
548
|
+
|
549
|
+
attr_reader :options
|
550
|
+
|
551
|
+
# @private
|
552
|
+
alias_method :jsi_node_content, :contained_object
|
553
|
+
private :jsi_node_content
|
554
|
+
|
555
|
+
# @private
|
556
|
+
# @return [Array<String>]
|
557
|
+
def jsi_object_group_text
|
558
|
+
schema_names = contained_object.jsi_schemas.map { |schema| schema.jsi_schema_module.name_from_ancestor || schema.schema_uri }.compact
|
559
|
+
if schema_names.empty?
|
560
|
+
[Container.to_s]
|
561
|
+
else
|
562
|
+
["#{Container} (#{schema_names.join(', ')})"]
|
563
|
+
end
|
564
|
+
end
|
524
565
|
end
|
525
|
-
include JSI::FingerprintHash
|
526
566
|
end
|
527
567
|
end
|
data/lib/scorpio/response.rb
CHANGED
@@ -1,13 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Scorpio
|
2
|
-
|
3
|
-
|
4
|
+
Response = Scorpio::Ur.properties['response']
|
5
|
+
|
6
|
+
module Response
|
7
|
+
# the schema for this response according to its OpenAPI doc
|
8
|
+
# @return [::JSI::Schema]
|
4
9
|
def response_schema
|
5
10
|
ur.scorpio_request.operation.response_schema(status: status, media_type: media_type)
|
6
11
|
end
|
7
12
|
|
8
|
-
#
|
9
|
-
#
|
10
|
-
# #response_schema
|
13
|
+
# the body (String) is parsed according to the response media type, if supported (only JSON is
|
14
|
+
# currently supported), and instantiated as a JSI instance of {#response_schema} if that is defined.
|
11
15
|
def body_object
|
12
16
|
if json?
|
13
17
|
if body.empty?
|
@@ -22,7 +26,7 @@ module Scorpio
|
|
22
26
|
end
|
23
27
|
|
24
28
|
if response_schema && (body_object.respond_to?(:to_hash) || body_object.respond_to?(:to_ary))
|
25
|
-
body_object =
|
29
|
+
body_object = response_schema.new_jsi(body_object)
|
26
30
|
end
|
27
31
|
|
28
32
|
body_object
|
data/lib/scorpio/ur.rb
CHANGED
@@ -1,5 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Scorpio
|
2
|
-
|
4
|
+
# Scorpio::Ur is a JSI Schema module with which scorpio extends the ::Ur (toplevel)
|
5
|
+
# schema module from the Ur gem
|
6
|
+
Ur = JSI.new_schema_module({
|
7
|
+
'$schema' => 'http://json-schema.org/draft-07/schema#',
|
8
|
+
'$id' => 'https://schemas.jsi.unth.net/ur',
|
9
|
+
'properties' => {
|
10
|
+
'request' => {},
|
11
|
+
'response' => {},
|
12
|
+
}
|
13
|
+
})
|
14
|
+
|
15
|
+
-> { Scorpio::Response }.() # invoke autoload
|
16
|
+
|
17
|
+
module Ur
|
3
18
|
attr_accessor :scorpio_request
|
4
19
|
|
5
20
|
# raises a subclass of Scorpio::HTTPError if the response has an error status.
|
@@ -28,19 +43,5 @@ module Scorpio
|
|
28
43
|
end
|
29
44
|
nil
|
30
45
|
end
|
31
|
-
|
32
|
-
private
|
33
|
-
# overrides JSI::Base#class_for_schema to use Scorpio::Response instead of ::Ur::Response.
|
34
|
-
# maybe a Scorpio::Ur::Request in the future if I need to extend that ... or Scorpio::Request
|
35
|
-
# if I decide to make that subclass ::Ur::Request. not sure if that's a good idea or a terrible
|
36
|
-
# idea.
|
37
|
-
def class_for_schema(schema)
|
38
|
-
jsi_class_for_schema = super
|
39
|
-
if jsi_class_for_schema == ::Ur::Response
|
40
|
-
Scorpio::Response
|
41
|
-
else
|
42
|
-
jsi_class_for_schema
|
43
|
-
end
|
44
|
-
end
|
45
46
|
end
|
46
47
|
end
|
data/lib/scorpio/version.rb
CHANGED