scorpio 0.6.4 → 0.7.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/.yardopts +6 -1
- data/CHANGELOG.md +10 -2
- data/LICENSE.md +2 -4
- data/README.md +81 -67
- data/documents/{github.com/OAI/OpenAPI-Specification/blob/oas3-schema/schemas/v3.0 → spec.openapis.org/oas/3.0}/schema.yaml +164 -628
- data/documents/spec.openapis.org/oas/3.1/dialect/base.schema.yaml +22 -0
- data/documents/spec.openapis.org/oas/3.1/meta/base.schema.yaml +71 -0
- data/documents/spec.openapis.org/oas/3.1/schema-base.yaml +21 -0
- data/documents/spec.openapis.org/oas/3.1/schema.yaml +980 -0
- data/documents/swagger.io/v2/schema.json +1 -1
- data/documents/www.googleapis.com/discovery/v1/apis/discovery/v1/rest.yml +44 -4
- data/lib/scorpio/google_api_document.rb +121 -193
- data/lib/scorpio/openapi/document.rb +63 -31
- data/lib/scorpio/openapi/operation.rb +114 -96
- data/lib/scorpio/openapi/operations_scope.rb +35 -19
- data/lib/scorpio/openapi/reference.rb +88 -23
- data/lib/scorpio/openapi/schema_elements/type_nullable.rb +38 -0
- data/lib/scorpio/openapi/schema_elements.rb +7 -0
- data/lib/scorpio/openapi/server.rb +34 -0
- data/lib/scorpio/openapi/tag.rb +19 -3
- data/lib/scorpio/openapi/v2/dialect.rb +66 -0
- data/lib/scorpio/openapi/v2.rb +124 -0
- data/lib/scorpio/openapi/v3_0/dialect.rb +76 -0
- data/lib/scorpio/openapi/v3_0.rb +130 -0
- data/lib/scorpio/openapi/v3_1.rb +243 -0
- data/lib/scorpio/openapi.rb +23 -203
- data/lib/scorpio/request.rb +67 -61
- data/lib/scorpio/resource_base.rb +57 -49
- data/lib/scorpio/response.rb +28 -10
- data/lib/scorpio/ur.rb +7 -3
- data/lib/scorpio/version.rb +1 -1
- data/lib/scorpio.rb +12 -6
- data/pages/Request_Configuration.md +69 -0
- data/pages/Security.md +50 -0
- data/scorpio.gemspec +6 -5
- metadata +28 -15
- data/documents/www.googleapis.com/discovery/v1/apis/discovery/v1/rest +0 -684
- data/lib/scorpio/openapi/v3/server.rb +0 -44
|
@@ -25,6 +25,18 @@ module Scorpio
|
|
|
25
25
|
openapi_document.user_agent
|
|
26
26
|
end
|
|
27
27
|
|
|
28
|
+
attr_writer(:accept)
|
|
29
|
+
def accept
|
|
30
|
+
return @accept if instance_variable_defined?(:@accept)
|
|
31
|
+
openapi_document.accept
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
attr_writer(:authorization)
|
|
35
|
+
def authorization
|
|
36
|
+
return @authorization if instance_variable_defined?(:@authorization)
|
|
37
|
+
openapi_document.authorization
|
|
38
|
+
end
|
|
39
|
+
|
|
28
40
|
attr_writer :faraday_builder
|
|
29
41
|
def faraday_builder
|
|
30
42
|
return @faraday_builder if instance_variable_defined?(:@faraday_builder)
|
|
@@ -44,37 +56,32 @@ module Scorpio
|
|
|
44
56
|
end
|
|
45
57
|
end
|
|
46
58
|
include Configurables
|
|
59
|
+
include(Document::Descendent)
|
|
47
60
|
|
|
48
61
|
# openapi v3?
|
|
49
62
|
# @return [Boolean]
|
|
50
63
|
def v3?
|
|
51
|
-
is_a?(
|
|
64
|
+
is_a?(OpenAPI::Operation::V3Methods)
|
|
52
65
|
end
|
|
53
66
|
|
|
54
67
|
# openapi v2?
|
|
55
68
|
# @return [Boolean]
|
|
56
69
|
def v2?
|
|
57
|
-
is_a?(V2::Operation)
|
|
58
|
-
end
|
|
59
|
-
|
|
60
|
-
# the document whence this operation came
|
|
61
|
-
# @return [Scorpio::OpenAPI::Document]
|
|
62
|
-
def openapi_document
|
|
63
|
-
jsi_parent_nodes.detect { |p| p.is_a?(Scorpio::OpenAPI::Document) }
|
|
70
|
+
is_a?(OpenAPI::V2::Operation)
|
|
64
71
|
end
|
|
65
72
|
|
|
66
73
|
# @return [String]
|
|
67
74
|
def path_template_str
|
|
68
75
|
return @path_template_str if instance_variable_defined?(:@path_template_str)
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
@path_template_str = jsi_parent_node.jsi_ptr.tokens.last
|
|
76
|
+
path_item = jsi_ancestor_nodes.detect { |n| n.is_a?(Scorpio::OpenAPI::PathItem) }
|
|
77
|
+
@path_template_str = path_item && path_item.jsi_ptr.tokens.last
|
|
72
78
|
end
|
|
73
79
|
|
|
74
80
|
# the path as an Addressable::Template
|
|
75
81
|
# @return [Addressable::Template]
|
|
76
82
|
def path_template
|
|
77
83
|
return @path_template if instance_variable_defined?(:@path_template)
|
|
84
|
+
return(@path_template = nil) if !path_template_str
|
|
78
85
|
@path_template = Addressable::Template.new(path_template_str)
|
|
79
86
|
end
|
|
80
87
|
|
|
@@ -83,7 +90,7 @@ module Scorpio
|
|
|
83
90
|
# @return [Addressable::Template]
|
|
84
91
|
def uri_template(base_url: self.base_url)
|
|
85
92
|
unless base_url
|
|
86
|
-
raise(ArgumentError, "no base_url has been specified for operation #{self}")
|
|
93
|
+
raise(ArgumentError, -"no base_url has been specified for operation #{self}")
|
|
87
94
|
end
|
|
88
95
|
# we do not use Addressable::URI#join as the paths should just be concatenated, not resolved.
|
|
89
96
|
# we use File.join just to deal with consecutive slashes.
|
|
@@ -95,38 +102,88 @@ module Scorpio
|
|
|
95
102
|
# @return [String]
|
|
96
103
|
def http_method
|
|
97
104
|
return @http_method if instance_variable_defined?(:@http_method)
|
|
98
|
-
|
|
105
|
+
return(@http_method = nil) unless jsi_parent_node.is_a?(Scorpio::OpenAPI::PathItem)
|
|
99
106
|
@http_method = jsi_ptr.tokens.last
|
|
100
107
|
end
|
|
101
108
|
|
|
109
|
+
def get?
|
|
110
|
+
'get'.casecmp?(http_method)
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def put?
|
|
114
|
+
'put'.casecmp?(http_method)
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def post?
|
|
118
|
+
'post'.casecmp?(http_method)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def delete?
|
|
122
|
+
'delete'.casecmp?(http_method)
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def options?
|
|
126
|
+
'options'.casecmp?(http_method)
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def head?
|
|
130
|
+
'head'.casecmp?(http_method)
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def patch?
|
|
134
|
+
'patch'.casecmp?(http_method)
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def trace?
|
|
138
|
+
'trace'.casecmp?(http_method)
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
# @param tag_name [String]
|
|
142
|
+
# @return [Boolean]
|
|
143
|
+
def tagged?(tag_name)
|
|
144
|
+
tags.respond_to?(:to_ary) && tags.include?(tag_name)
|
|
145
|
+
end
|
|
146
|
+
|
|
102
147
|
# a short identifier for this operation appropriate for an error message
|
|
103
148
|
# @return [String]
|
|
104
149
|
def human_id
|
|
105
|
-
operationId || "path: #{path_template_str}, method: #{http_method}"
|
|
150
|
+
operationId || -"path: #{path_template_str}, method: #{http_method}"
|
|
106
151
|
end
|
|
107
152
|
|
|
108
153
|
# @param status [String, Integer]
|
|
109
|
-
# @return [
|
|
154
|
+
# @return [OpenAPI::Response, nil]
|
|
110
155
|
def oa_response(status: )
|
|
156
|
+
return nil if !responses
|
|
111
157
|
status = status.to_s if status.is_a?(Numeric)
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
oa_response ||= responses['default']
|
|
158
|
+
responses.each do |k, v|
|
|
159
|
+
return v if k.to_s == status
|
|
115
160
|
end
|
|
116
|
-
|
|
161
|
+
responses[-"#{status[0]}XX"] || responses['default']
|
|
117
162
|
end
|
|
118
163
|
|
|
119
|
-
#
|
|
164
|
+
# operation parameters + path item parameters
|
|
120
165
|
#
|
|
121
|
-
#
|
|
166
|
+
# @api private
|
|
167
|
+
# @return [#to_ary<#to_hash>]
|
|
168
|
+
def inherited_parameters
|
|
169
|
+
parameters = []
|
|
170
|
+
parameters.concat(self.parameters.to_ary) if self.parameters
|
|
171
|
+
path_item = jsi_ancestor_nodes.detect { |n| n.is_a?(OpenAPI::PathItem) }
|
|
172
|
+
parameters.concat((path_item && path_item.parameters || []).select do |pip|
|
|
173
|
+
parameters.none? { |p| p['in'] == pip['in'] && p['name'] == pip['name'] }
|
|
174
|
+
end)
|
|
175
|
+
parameters.freeze
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
# the parameters specified for this operation, plus any others scorpio considers to be parameters.
|
|
122
179
|
#
|
|
123
180
|
# @api private
|
|
124
|
-
# @return [#to_ary<#
|
|
181
|
+
# @return [#to_ary<#to_hash>]
|
|
125
182
|
def inferred_parameters
|
|
126
|
-
parameters =
|
|
183
|
+
parameters = inherited_parameters.dup
|
|
127
184
|
path_template.variables.each do |var|
|
|
128
185
|
unless parameters.any? { |p| p['in'] == 'path' && p['name'] == var }
|
|
129
|
-
# we could instantiate this as a V2::Parameter or a
|
|
186
|
+
# we could instantiate this as a V2::Parameter or a V3_0::Parameter
|
|
130
187
|
# or a ParameterWithContentInPath or whatever. but I can't be bothered.
|
|
131
188
|
parameters << {
|
|
132
189
|
'name' => var,
|
|
@@ -136,85 +193,47 @@ module Scorpio
|
|
|
136
193
|
}
|
|
137
194
|
end
|
|
138
195
|
end
|
|
139
|
-
parameters
|
|
140
|
-
end
|
|
141
|
-
|
|
142
|
-
# a module with accessor methods for unambiguously named parameters of this operation.
|
|
143
|
-
# @return [Module]
|
|
144
|
-
def request_accessor_module
|
|
145
|
-
return @request_accessor_module if instance_variable_defined?(:@request_accessor_module)
|
|
146
|
-
@request_accessor_module = begin
|
|
147
|
-
params_by_name = inferred_parameters.group_by { |p| p['name'] }
|
|
148
|
-
Module.new do
|
|
149
|
-
instance_method_modules = [Request, Request::Configurables]
|
|
150
|
-
instance_method_names = instance_method_modules.map do |mod|
|
|
151
|
-
(mod.instance_methods + mod.private_instance_methods).map(&:to_s)
|
|
152
|
-
end.inject(Set.new, &:merge)
|
|
153
|
-
params_by_name.each do |name, params|
|
|
154
|
-
next if instance_method_names.include?(name)
|
|
155
|
-
if params.size == 1
|
|
156
|
-
param = params.first
|
|
157
|
-
define_method("#{name}=") { |value| set_param_from(param['in'], param['name'], value) }
|
|
158
|
-
define_method(name) { get_param_from(param['in'], param['name']) }
|
|
159
|
-
end
|
|
160
|
-
end
|
|
161
|
-
end
|
|
162
|
-
end
|
|
196
|
+
parameters.freeze
|
|
163
197
|
end
|
|
164
198
|
|
|
165
199
|
# instantiates a {Scorpio::Request} for this operation.
|
|
166
|
-
#
|
|
200
|
+
# configuration is passed to {Scorpio::Request#initialize}.
|
|
167
201
|
# @return [Scorpio::Request]
|
|
168
|
-
def build_request(configuration
|
|
169
|
-
|
|
170
|
-
@request_class.new(configuration, &b)
|
|
202
|
+
def build_request(**configuration, &b)
|
|
203
|
+
Scorpio::Request.new(self, **configuration, &b)
|
|
171
204
|
end
|
|
172
205
|
|
|
173
206
|
# runs a {Scorpio::Request} for this operation, returning a {Scorpio::Ur}.
|
|
174
|
-
#
|
|
207
|
+
# configuration is passed to {Scorpio::Request#initialize}.
|
|
175
208
|
# @return [Scorpio::Ur] response ur
|
|
176
|
-
def run_ur(configuration
|
|
177
|
-
build_request(configuration, &b).run_ur
|
|
209
|
+
def run_ur(**configuration, &b)
|
|
210
|
+
build_request(**configuration, &b).run_ur
|
|
178
211
|
end
|
|
179
212
|
|
|
180
213
|
# runs a {Scorpio::Request} for this operation - see {Scorpio::Request#run}.
|
|
181
|
-
#
|
|
214
|
+
# configuration is passed to {Scorpio::Request#initialize}.
|
|
182
215
|
# @return response body object
|
|
183
|
-
def run(
|
|
184
|
-
build_request(configuration, &b).run
|
|
216
|
+
def run(mutable: false, **configuration, &b)
|
|
217
|
+
build_request(**configuration, &b).run(mutable: mutable)
|
|
185
218
|
end
|
|
186
219
|
|
|
187
220
|
# Runs this operation with the given request config, and yields the resulting {Scorpio::Ur}.
|
|
188
|
-
# If the response contains a `Link` header with a `next` link
|
|
189
|
-
#
|
|
190
|
-
# request's Ur yielded, and a `next` link in that response is followed.
|
|
221
|
+
# If the response contains a `Link` header with a `next` link, this operation is run again to
|
|
222
|
+
# that link's URL, that request's Ur yielded, and a `next` link in that response is followed.
|
|
191
223
|
# This repeats until a response does not contain a `Link` header with a `next` link.
|
|
192
224
|
#
|
|
193
225
|
# @param configuration (see Scorpio::Request#initialize)
|
|
194
226
|
# @yield [Scorpio::Ur]
|
|
195
227
|
# @return [Enumerator, nil]
|
|
196
|
-
def each_link_page(configuration
|
|
197
|
-
init_request = build_request(configuration)
|
|
228
|
+
def each_link_page(**configuration, &block)
|
|
229
|
+
init_request = build_request(**configuration)
|
|
198
230
|
next_page = proc do |last_page_ur|
|
|
199
231
|
nextlinks = last_page_ur.response.links.select { |link| link.rel?('next') }
|
|
200
232
|
if nextlinks.size == 0
|
|
201
233
|
# no next link; we are at the end
|
|
202
234
|
nil
|
|
203
235
|
elsif nextlinks.size == 1
|
|
204
|
-
|
|
205
|
-
# we do not use Addressable::URI#join as the paths should just be concatenated, not resolved.
|
|
206
|
-
# we use File.join just to deal with consecutive slashes.
|
|
207
|
-
template = Addressable::Template.new(File.join(init_request.base_url, path_template_str))
|
|
208
|
-
target_uri = nextlink.absolute_target_uri
|
|
209
|
-
path_params = template.extract(target_uri.merge(query: nil))
|
|
210
|
-
unless path_params
|
|
211
|
-
raise("the URI of the link to the next page did not match the URI of this operation")
|
|
212
|
-
end
|
|
213
|
-
query_params = target_uri.query_values
|
|
214
|
-
run_ur(
|
|
215
|
-
path_params: path_params,
|
|
216
|
-
query_params: query_params,
|
|
217
|
-
)
|
|
236
|
+
run_ur(**configuration, url: nextlinks.first.absolute_target_uri)
|
|
218
237
|
else
|
|
219
238
|
# TODO better error class / context / message
|
|
220
239
|
raise("response included multiple links with rel=next")
|
|
@@ -226,17 +245,12 @@ module Scorpio
|
|
|
226
245
|
private
|
|
227
246
|
|
|
228
247
|
def jsi_object_group_text
|
|
229
|
-
[*super, http_method, path_template_str].freeze
|
|
248
|
+
[*super, http_method, path_template_str].compact.freeze
|
|
230
249
|
end
|
|
231
250
|
end
|
|
232
251
|
|
|
233
|
-
module
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
# Describes a single API operation on a path.
|
|
237
|
-
#
|
|
238
|
-
# https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#operationObject
|
|
239
|
-
module Operation
|
|
252
|
+
module Operation
|
|
253
|
+
module V3Methods
|
|
240
254
|
module Configurables
|
|
241
255
|
def scheme
|
|
242
256
|
# not applicable; for OpenAPI v3, scheme is specified by servers.
|
|
@@ -266,6 +280,7 @@ module Scorpio
|
|
|
266
280
|
end
|
|
267
281
|
end
|
|
268
282
|
include Configurables
|
|
283
|
+
include(OpenAPI::Operation)
|
|
269
284
|
|
|
270
285
|
# @return [JSI::Schema]
|
|
271
286
|
def request_schema(media_type: self.request_media_type)
|
|
@@ -291,13 +306,15 @@ module Scorpio
|
|
|
291
306
|
end
|
|
292
307
|
end
|
|
293
308
|
|
|
294
|
-
# @return [JSI::Schema]
|
|
309
|
+
# @return [JSI::Schema, nil]
|
|
295
310
|
def response_schema(status: , media_type: )
|
|
296
|
-
oa_response = self.oa_response(status: status)
|
|
297
|
-
oa_media_types = oa_response
|
|
298
|
-
oa_media_type = oa_media_types
|
|
299
|
-
|
|
300
|
-
|
|
311
|
+
oa_response = self.oa_response(status: status) || return
|
|
312
|
+
oa_media_types = oa_response['content'] || return # Scorpio::OpenAPI::V3_*::MediaTypes
|
|
313
|
+
oa_media_type = oa_media_types[media_type] # Scorpio::OpenAPI::V3_*::MediaType
|
|
314
|
+
oa_media_type ||= oa_media_types[-"#{::Ur::ContentType.new(media_type).type}/*"]
|
|
315
|
+
oa_media_type ||= oa_media_types['*/*'] || return
|
|
316
|
+
oa_schema = oa_media_type['schema'] || return # JSI::Schema, Scorpio::OpenAPI::V3_*::Schema
|
|
317
|
+
JSI::Schema.ensure_schema(oa_schema)
|
|
301
318
|
end
|
|
302
319
|
|
|
303
320
|
# @return [JSI::SchemaSet]
|
|
@@ -318,9 +335,9 @@ module Scorpio
|
|
|
318
335
|
end
|
|
319
336
|
end
|
|
320
337
|
end
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
module
|
|
338
|
+
|
|
339
|
+
module Operation
|
|
340
|
+
module V2Methods
|
|
324
341
|
module Configurables
|
|
325
342
|
attr_writer :scheme
|
|
326
343
|
def scheme
|
|
@@ -345,19 +362,20 @@ module Scorpio
|
|
|
345
362
|
end
|
|
346
363
|
end
|
|
347
364
|
include Configurables
|
|
365
|
+
include(OpenAPI::Operation)
|
|
348
366
|
|
|
349
367
|
# the body parameter
|
|
350
368
|
# @return [#to_hash]
|
|
351
369
|
# @raise [Scorpio::OpenAPI::SemanticError] if there's more than one body param
|
|
352
370
|
def body_parameter
|
|
353
|
-
body_parameters =
|
|
371
|
+
body_parameters = inherited_parameters.select { |parameter| parameter['in'] == 'body' }
|
|
354
372
|
if body_parameters.size == 0
|
|
355
373
|
nil
|
|
356
374
|
elsif body_parameters.size == 1
|
|
357
375
|
body_parameters.first
|
|
358
376
|
else
|
|
359
377
|
# TODO blame
|
|
360
|
-
raise(OpenAPI::SemanticError, "multiple body parameters on operation #{operation.pretty_inspect.chomp}")
|
|
378
|
+
raise(OpenAPI::SemanticError, -"multiple body parameters on operation #{operation.pretty_inspect.chomp}")
|
|
361
379
|
end
|
|
362
380
|
end
|
|
363
381
|
|
|
@@ -2,40 +2,56 @@
|
|
|
2
2
|
|
|
3
3
|
module Scorpio
|
|
4
4
|
module OpenAPI
|
|
5
|
-
# OperationsScope
|
|
5
|
+
# OperationsScope is an Enumerable for a collection of Operations,
|
|
6
6
|
# and offers subscripting by operationId.
|
|
7
7
|
class OperationsScope
|
|
8
|
-
# @param
|
|
9
|
-
def initialize(
|
|
10
|
-
@
|
|
8
|
+
# @param enum [Enumerable]
|
|
9
|
+
def initialize(enum)
|
|
10
|
+
@enum = enum
|
|
11
11
|
@operations_by_id = Hash.new do |h, operationId|
|
|
12
|
-
|
|
13
|
-
unless op
|
|
14
|
-
raise(::KeyError, "operationId not found: #{operationId.inspect}")
|
|
15
|
-
end
|
|
16
|
-
h[operationId] = op
|
|
12
|
+
h[operationId] = enum.detect { |operation| operation.operationId == operationId }
|
|
17
13
|
end
|
|
18
14
|
end
|
|
19
15
|
attr_reader :openapi_document
|
|
20
16
|
|
|
21
17
|
# @yield [Scorpio::OpenAPI::Operation]
|
|
22
|
-
def each
|
|
23
|
-
|
|
24
|
-
path_item.each do |http_method, operation|
|
|
25
|
-
if operation.is_a?(Scorpio::OpenAPI::Operation)
|
|
26
|
-
yield operation
|
|
27
|
-
end
|
|
28
|
-
end
|
|
29
|
-
end
|
|
18
|
+
def each(&block)
|
|
19
|
+
@enum.each(&block)
|
|
30
20
|
end
|
|
21
|
+
|
|
31
22
|
include Enumerable
|
|
32
23
|
|
|
24
|
+
# @return [Scorpio::OpenAPI::Operation, nil]
|
|
25
|
+
def by_id(operationId)
|
|
26
|
+
@operations_by_id[operationId]
|
|
27
|
+
end
|
|
28
|
+
|
|
33
29
|
# finds an operation with the given `operationId`
|
|
34
30
|
# @param operationId [String] the operationId of the operation to find
|
|
35
31
|
# @return [Scorpio::OpenAPI::Operation]
|
|
36
32
|
# @raise [::KeyError] if the given operationId does not exist
|
|
37
|
-
def
|
|
38
|
-
@operations_by_id[operationId]
|
|
33
|
+
def by_id!(operationId)
|
|
34
|
+
@operations_by_id[operationId] || raise(::KeyError, -"operationId not found: #{operationId.inspect}")
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
alias_method(:[], :by_id!)
|
|
38
|
+
|
|
39
|
+
# @return [OperationsScope]
|
|
40
|
+
def select(&block)
|
|
41
|
+
OperationsScope.new(@enum.select(&block))
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# @return [OperationsScope]
|
|
45
|
+
def reject(&block)
|
|
46
|
+
OperationsScope.new(@enum.reject(&block))
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Operations with the indicated tag
|
|
50
|
+
# @param tag [String, OpenAPI::Tag]
|
|
51
|
+
# @return [OperationsScope]
|
|
52
|
+
def tagged(tag)
|
|
53
|
+
tag_name = tag.is_a?(OpenAPI::Tag) ? tag.name : tag
|
|
54
|
+
select { |op| op.tagged?(tag_name) }
|
|
39
55
|
end
|
|
40
56
|
end
|
|
41
57
|
end
|
|
@@ -3,41 +3,106 @@
|
|
|
3
3
|
module Scorpio
|
|
4
4
|
module OpenAPI
|
|
5
5
|
module Reference
|
|
6
|
-
#
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
# @private
|
|
7
|
+
module IncludeRecursive
|
|
8
|
+
def included(mod)
|
|
9
|
+
super
|
|
10
|
+
return if mod.is_a?(Class)
|
|
11
|
+
document_subschemas_include_derefable(mod)
|
|
12
|
+
mod.send(:extend, IncludeRecursive)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# - when a schema module includes OpenAPI::Reference
|
|
16
|
+
# - then we go to the schema's root node (which describes an OpenAPI Document)
|
|
17
|
+
# - and for each descendent schema, describing a part of an OAD
|
|
18
|
+
# - if that schema _could_ describe a reference, because its in-place applicators
|
|
19
|
+
# include the schema of the schema module including Reference
|
|
20
|
+
# (e.g. it has a oneOf with a $ref: '#/definitions/Reference')
|
|
21
|
+
# - then its schema module includes Derefable
|
|
22
|
+
def document_subschemas_include_derefable(mod)
|
|
23
|
+
return unless mod.is_a?(JSI::SchemaModule)
|
|
24
|
+
|
|
25
|
+
mod.schema.jsi_root_node.jsi_each_descendent_schema do |schema|
|
|
26
|
+
if to_enum(:schema_all_inplace_applicator_schemas, schema).include?(mod.schema)
|
|
27
|
+
schema.jsi_schema_module.include(Derefable)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# yield all of the schema's in-place applicator schemas, recursively, following $ref.
|
|
33
|
+
# TODO find a better home for this code (it is not particular to OpenAPI::Reference)
|
|
34
|
+
def schema_all_inplace_applicator_schemas(schema, &block)
|
|
35
|
+
yield schema
|
|
36
|
+
if schema.keyword?('$ref')
|
|
37
|
+
schema_all_inplace_applicator_schemas(schema.schema_ref.resolve, &block)
|
|
38
|
+
end
|
|
39
|
+
ia_elements = schema.dialect.elements.select { |element| element.invokes?(:inplace_applicate) }
|
|
40
|
+
cxt = JSI::Schema::Cxt::Block.new(
|
|
41
|
+
schema: schema,
|
|
42
|
+
abort: false,
|
|
43
|
+
block: proc { |ptr| schema_all_inplace_applicator_schemas(schema.subschema(ptr), &block) },
|
|
44
|
+
)
|
|
45
|
+
ia_elements.each do |element|
|
|
46
|
+
# getting subschemas yielded from action :subschema on elements that invoke :inplace_applicate
|
|
47
|
+
# is a kind of hacky way to get _all_ inplace applicators.
|
|
48
|
+
# getting applicator schemas normally comes from invoking :inplace_applicate, but that
|
|
49
|
+
# requires an instance and yields just the applicators that apply to that instance, not all.
|
|
50
|
+
#
|
|
51
|
+
# this does not work when the schemas an element yields from :subschema do not match its in-place
|
|
52
|
+
# applicator schemas. the only element that does that is for $ref which is handled above.
|
|
53
|
+
element.actions[:subschema].each do |action|
|
|
54
|
+
cxt.instance_exec(&action)
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
extend(IncludeRecursive)
|
|
61
|
+
|
|
62
|
+
# Derefable is included on the schema module of any schema that describes a reference or has an
|
|
63
|
+
# in-place applicator that describes a reference. You can call #deref regardless whether an object
|
|
64
|
+
# is of an expected type, or a reference to one, or a reference to a reference to one.
|
|
65
|
+
module Derefable
|
|
66
|
+
# resolves references ({Reference#resolve}) recursively.
|
|
67
|
+
def deref
|
|
68
|
+
return self unless is_a?(Reference) && has_ref?
|
|
69
|
+
resolved = resolve
|
|
70
|
+
return resolved if !resolved.is_a?(Reference)
|
|
71
|
+
resolved.deref
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# overrides JSI::Base#[] to implicitly resolve this Reference, except when
|
|
76
|
+
# the given token is present in this Reference's instance.
|
|
77
|
+
# the token '$ref' will always come from this reference, not its resolution.
|
|
78
|
+
# tokens 'summary' and 'description' may also be in some references.
|
|
9
79
|
def [](token, **kw)
|
|
10
80
|
if respond_to?(:to_hash) && !key?(token)
|
|
11
|
-
|
|
12
|
-
return(
|
|
81
|
+
resolve do |resolved|
|
|
82
|
+
return(resolved[token, **kw])
|
|
13
83
|
end
|
|
14
84
|
end
|
|
15
85
|
return super
|
|
16
86
|
end
|
|
17
87
|
|
|
18
|
-
#
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
return unless respond_to?(:to_hash) && self['$ref'].respond_to?(:to_str)
|
|
23
|
-
|
|
24
|
-
ref_uri = Addressable::URI.parse(self['$ref'])
|
|
25
|
-
ref_uri_nofrag = ref_uri.merge(fragment: nil)
|
|
88
|
+
# @return [Boolean]
|
|
89
|
+
def has_ref?
|
|
90
|
+
jsi_child_token_present?('$ref')
|
|
91
|
+
end
|
|
26
92
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
93
|
+
# yields or returns the target of this reference. returns nil and does not yield if `$ref` is not present.
|
|
94
|
+
# @yield [JSI::Base] if a block is given
|
|
95
|
+
# @return [JSI::Base, nil]
|
|
96
|
+
def resolve
|
|
97
|
+
return unless has_ref?
|
|
32
98
|
|
|
33
|
-
|
|
34
|
-
deref_jsi = ptr.evaluate(jsi_root_node)
|
|
99
|
+
ref = @memos.fetch(:oa_ref) { @memos[:oa_ref] = JSI::Ref.new(jsi_node_content['$ref'], referrer: self) }
|
|
35
100
|
|
|
36
|
-
# TODO type check
|
|
101
|
+
# TODO type check resolved
|
|
37
102
|
|
|
38
|
-
yield
|
|
103
|
+
yield ref.resolve if block_given?
|
|
39
104
|
|
|
40
|
-
|
|
105
|
+
ref.resolve
|
|
41
106
|
end
|
|
42
107
|
end
|
|
43
108
|
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Scorpio
|
|
4
|
+
module OpenAPI::SchemaElements
|
|
5
|
+
instance_types = {
|
|
6
|
+
'boolean' => proc { instance == true || instance == false },
|
|
7
|
+
'object' => proc { instance.respond_to?(:to_hash) },
|
|
8
|
+
'array' => proc { instance.respond_to?(:to_ary) },
|
|
9
|
+
'string' => proc { instance.respond_to?(:to_str) },
|
|
10
|
+
'number' => proc { instance.is_a?(Numeric) },
|
|
11
|
+
'integer' => proc { internal_integer?(instance) },
|
|
12
|
+
}.freeze
|
|
13
|
+
|
|
14
|
+
TYPE_NULLABLE = JSI::Schema::Element.new(keywords: ['type', 'nullable']) do |element|
|
|
15
|
+
element.add_action(:validate) do
|
|
16
|
+
next if !keyword?('type')
|
|
17
|
+
if instance.nil?
|
|
18
|
+
validate(
|
|
19
|
+
schema_content['nullable'] == true,
|
|
20
|
+
'validation.keyword.type.not_nullable',
|
|
21
|
+
"instance is null without `nullable` = true",
|
|
22
|
+
keyword: 'nullable',
|
|
23
|
+
)
|
|
24
|
+
else
|
|
25
|
+
if !instance_types.key?(schema_content['type'])
|
|
26
|
+
next # schema error; skip validation
|
|
27
|
+
end
|
|
28
|
+
validate(
|
|
29
|
+
instance_exec(&instance_types[schema_content['type']]),
|
|
30
|
+
'validation.keyword.type.not_match',
|
|
31
|
+
"instance type does not match `type` value",
|
|
32
|
+
keyword: 'type',
|
|
33
|
+
)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Scorpio
|
|
4
|
+
module OpenAPI
|
|
5
|
+
# An object representing a Server.
|
|
6
|
+
module Server
|
|
7
|
+
include(Document::Descendent)
|
|
8
|
+
|
|
9
|
+
# expands this server's #url template using the given_server_variables. any variables
|
|
10
|
+
# that are in the url but not in the given server variables are filled in
|
|
11
|
+
# using the default value for the variable.
|
|
12
|
+
#
|
|
13
|
+
# @param given_server_variables [Hash<String, String>]
|
|
14
|
+
# @return [Addressable::URI] the expanded url
|
|
15
|
+
def expanded_url(given_server_variables)
|
|
16
|
+
given_server_variables = JSI::Util.stringify_symbol_keys(given_server_variables)
|
|
17
|
+
if variables
|
|
18
|
+
server_variables = {}
|
|
19
|
+
(given_server_variables.keys | variables.keys).each do |key|
|
|
20
|
+
if given_server_variables.key?(key)
|
|
21
|
+
server_variables[key] = given_server_variables[key]
|
|
22
|
+
elsif variables[key].key?('default')
|
|
23
|
+
server_variables[key] = variables[key].default
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
else
|
|
27
|
+
server_variables = given_server_variables
|
|
28
|
+
end
|
|
29
|
+
template = Addressable::Template.new(url)
|
|
30
|
+
template.expand(server_variables).freeze
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|