scorpio 0.5.0 → 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 +5 -0
- data/LICENSE.md +1 -1
- data/README.md +26 -17
- data/lib/scorpio/google_api_document.rb +9 -1
- data/lib/scorpio/openapi/document.rb +4 -2
- data/lib/scorpio/openapi/operation.rb +90 -40
- data/lib/scorpio/openapi/operations_scope.rb +13 -11
- data/lib/scorpio/openapi/reference.rb +27 -2
- data/lib/scorpio/openapi/tag.rb +15 -0
- data/lib/scorpio/openapi/v3/server.rb +3 -1
- data/lib/scorpio/openapi.rb +55 -17
- data/lib/scorpio/pickle_adapter.rb +2 -0
- data/lib/scorpio/request.rb +47 -32
- data/lib/scorpio/resource_base.rb +234 -201
- data/lib/scorpio/response.rb +6 -4
- data/lib/scorpio/ur.rb +7 -3
- data/lib/scorpio/version.rb +3 -1
- data/lib/scorpio.rb +5 -6
- data/scorpio.gemspec +15 -23
- metadata +21 -220
- data/.simplecov +0 -1
- data/Rakefile +0 -10
- data/bin/documents_to_yml.rb +0 -33
- data/resources/icons/AGPL-3.0.png +0 -0
- data/test/blog.openapi2.yml +0 -113
- data/test/blog.openapi3.yml +0 -131
- data/test/blog.rb +0 -117
- data/test/blog.rest_description.yml +0 -67
- data/test/blog_scorpio_models.rb +0 -49
- data/test/scorpio_test.rb +0 -105
- data/test/test_helper.rb +0 -86
@@ -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,140 +369,109 @@ module Scorpio
|
|
347
369
|
response_object_to_instances(ur.response.body_object, initialize_options)
|
348
370
|
end
|
349
371
|
|
350
|
-
def request_body_for_schema(object, schema)
|
351
|
-
if object.is_a?(Scorpio::ResourceBase)
|
352
|
-
# TODO request_schema_fail unless schema is for given model type
|
353
|
-
request_body_for_schema(object.attributes, schema)
|
354
|
-
elsif object.is_a?(JSI::PathedNode)
|
355
|
-
request_body_for_schema(object.node_content, schema)
|
356
|
-
else
|
357
|
-
if object.respond_to?(:to_hash)
|
358
|
-
object.map do |key, value|
|
359
|
-
if schema
|
360
|
-
if schema['type'] == 'object'
|
361
|
-
# TODO code dup with response_object_to_instances
|
362
|
-
if schema['properties'].respond_to?(:to_hash) && schema['properties'].key?(key)
|
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
|
412
|
-
else
|
413
|
-
# TODO maybe raise on anything not serializable
|
414
|
-
# TODO check conformance to schema, request_schema_fail if not
|
415
|
-
object
|
416
|
-
end
|
417
|
-
end
|
418
|
-
end
|
419
|
-
|
420
|
-
def request_schema_fail(object, schema)
|
421
|
-
# TODO blame
|
422
|
-
end
|
423
|
-
|
424
372
|
def response_object_to_instances(object, initialize_options = {})
|
425
373
|
if object.is_a?(JSI::Base)
|
426
|
-
models = object.jsi_schemas.map { |schema| models_by_schema[schema] }
|
374
|
+
models = object.jsi_schemas.map { |schema| models_by_schema[schema] }.compact
|
427
375
|
if models.size == 0
|
428
376
|
model = nil
|
429
377
|
elsif models.size == 1
|
430
378
|
model = models.first
|
431
379
|
else
|
432
|
-
raise(Scorpio::OpenAPI::Error, "multiple models indicated by response JSI. models: #{models.inspect};
|
380
|
+
raise(Scorpio::OpenAPI::Error, "multiple models indicated by response JSI. models: #{models.inspect}; object: #{object.pretty_inspect.chomp}")
|
433
381
|
end
|
434
|
-
end
|
435
382
|
|
436
|
-
|
437
|
-
|
438
|
-
mod = object.map do |key, value|
|
439
|
-
{key => response_object_to_instances(value, initialize_options)}
|
440
|
-
end.inject({}, &:update)
|
441
|
-
mod = mod.node_content if mod.is_a?(JSI::PathedNode)
|
442
|
-
mod
|
443
|
-
end
|
444
|
-
if model
|
445
|
-
model.new(out, initialize_options)
|
383
|
+
if model && object.respond_to?(:to_hash)
|
384
|
+
model.new(object, initialize_options)
|
446
385
|
else
|
447
|
-
|
448
|
-
end
|
449
|
-
elsif object.respond_to?(:to_ary)
|
450
|
-
JSI::Typelike.modified_copy(object) do
|
451
|
-
object.map do |element|
|
452
|
-
response_object_to_instances(element, initialize_options)
|
453
|
-
end
|
386
|
+
Container.new_container(object, openapi_document_class, initialize_options)
|
454
387
|
end
|
455
388
|
else
|
456
389
|
object
|
457
390
|
end
|
458
391
|
end
|
459
392
|
end
|
393
|
+
end
|
394
|
+
|
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
|
411
|
+
end
|
412
|
+
|
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
|
419
|
+
end
|
420
|
+
end
|
421
|
+
|
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 '>'
|
441
|
+
end
|
442
|
+
end
|
443
|
+
|
444
|
+
include JSI::Util::FingerprintHash
|
445
|
+
|
446
|
+
def jsi_fingerprint
|
447
|
+
{class: self.class, contained_object: as_json}
|
448
|
+
end
|
449
|
+
end
|
450
|
+
end
|
451
|
+
|
452
|
+
class ResourceBase
|
453
|
+
include Containment
|
460
454
|
|
461
455
|
def initialize(attributes = {}, options = {})
|
462
|
-
@attributes = JSI.stringify_symbol_keys(attributes)
|
463
|
-
@options = JSI.stringify_symbol_keys(options)
|
456
|
+
@attributes = JSI::Util.stringify_symbol_keys(attributes)
|
457
|
+
@options = JSI::Util.stringify_symbol_keys(options)
|
464
458
|
@persisted = !!@options['persisted']
|
459
|
+
|
460
|
+
@openapi_document_class = self.class.openapi_document_class
|
461
|
+
@subscript_memos = {}
|
465
462
|
end
|
466
463
|
|
467
464
|
attr_reader :attributes
|
468
465
|
attr_reader :options
|
469
466
|
|
467
|
+
alias_method :contained_object, :attributes
|
468
|
+
|
470
469
|
def persisted?
|
471
470
|
@persisted
|
472
471
|
end
|
473
472
|
|
474
|
-
def [](key)
|
475
|
-
@attributes[key]
|
476
|
-
end
|
477
|
-
|
478
|
-
def []=(key, value)
|
479
|
-
@attributes[key] = value
|
480
|
-
end
|
481
|
-
|
482
473
|
def call_api_method(method_name, call_params: nil)
|
483
|
-
operation = self.class.
|
474
|
+
operation = self.class.operation_for_api_method_name(method_name) || raise(ArgumentError)
|
484
475
|
call_operation(operation, call_params: call_params)
|
485
476
|
end
|
486
477
|
|
@@ -504,31 +495,73 @@ module Scorpio
|
|
504
495
|
|
505
496
|
response
|
506
497
|
end
|
498
|
+
end
|
507
499
|
|
508
|
-
|
509
|
-
|
510
|
-
|
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
|
511
509
|
|
512
|
-
|
513
|
-
|
514
|
-
|
515
|
-
|
516
|
-
|
517
|
-
|
518
|
-
|
519
|
-
|
520
|
-
|
521
|
-
|
522
|
-
|
523
|
-
|
524
|
-
|
525
|
-
|
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
|
526
534
|
end
|
527
535
|
end
|
528
536
|
|
529
|
-
|
530
|
-
|
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
|
531
565
|
end
|
532
|
-
include JSI::Util::FingerprintHash
|
533
566
|
end
|
534
567
|
end
|
data/lib/scorpio/response.rb
CHANGED
@@ -1,15 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Scorpio
|
2
4
|
Response = Scorpio::Ur.properties['response']
|
3
5
|
|
4
6
|
module Response
|
5
|
-
#
|
7
|
+
# the schema for this response according to its OpenAPI doc
|
8
|
+
# @return [::JSI::Schema]
|
6
9
|
def response_schema
|
7
10
|
ur.scorpio_request.operation.response_schema(status: status, media_type: media_type)
|
8
11
|
end
|
9
12
|
|
10
|
-
#
|
11
|
-
#
|
12
|
-
# #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.
|
13
15
|
def body_object
|
14
16
|
if json?
|
15
17
|
if body.empty?
|
data/lib/scorpio/ur.rb
CHANGED
@@ -1,12 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Scorpio
|
2
|
-
# Scorpio::Ur is a JSI Schema module with which scorpio extends the ::Ur
|
3
|
-
Ur
|
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#',
|
4
8
|
'$id' => 'https://schemas.jsi.unth.net/ur',
|
5
9
|
'properties' => {
|
6
10
|
'request' => {},
|
7
11
|
'response' => {},
|
8
12
|
}
|
9
|
-
})
|
13
|
+
})
|
10
14
|
|
11
15
|
-> { Scorpio::Response }.() # invoke autoload
|
12
16
|
|
data/lib/scorpio/version.rb
CHANGED
data/lib/scorpio.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require "scorpio/version"
|
2
4
|
require "jsi"
|
3
5
|
require "ur"
|
@@ -20,12 +22,9 @@ module Scorpio
|
|
20
22
|
|
21
23
|
proc { |v| define_singleton_method(:error_classes_by_status) { v } }.call({})
|
22
24
|
# Scorpio::Error encompasses certain Scorpio-defined errors encountered in using Scorpio.
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
# [^1]: unless I have, since writing this, implemented other things but forgotten to update this
|
27
|
-
# comment, which does seem likely enough.
|
28
|
-
class Error < StandardError; end
|
25
|
+
class Error < StandardError
|
26
|
+
end
|
27
|
+
|
29
28
|
class HTTPError < Error
|
30
29
|
# for HTTPError subclasses representing a single status, sets and/or returns the represented status.
|
31
30
|
#
|