scorpio 0.5.0 → 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
data/lib/scorpio/openapi.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Scorpio
|
2
4
|
module OpenAPI
|
3
5
|
class Error < StandardError
|
@@ -15,15 +17,37 @@ module Scorpio
|
|
15
17
|
autoload :Operation, 'scorpio/openapi/operation'
|
16
18
|
autoload :Document, 'scorpio/openapi/document'
|
17
19
|
autoload :Reference, 'scorpio/openapi/reference'
|
20
|
+
autoload :Tag, 'scorpio/openapi/tag'
|
18
21
|
autoload :OperationsScope, 'scorpio/openapi/operations_scope'
|
19
22
|
|
20
23
|
module V3
|
21
|
-
|
24
|
+
openapi_document_schema = JSI::JSONSchemaOrgDraft04.new_schema(::YAML.load_file(Scorpio.root.join(
|
25
|
+
'documents/github.com/OAI/OpenAPI-Specification/blob/oas3-schema/schemas/v3.0/schema.yaml'
|
26
|
+
)))
|
27
|
+
|
28
|
+
# the schema represented by Scorpio::OpenAPI::V3::Schema will describe schemas itself, so we set it
|
29
|
+
# include on its schema module the jsi_schema_instance_modules that implement schema functionality.
|
30
|
+
describe_schema = [
|
31
|
+
openapi_document_schema.definitions['Schema'],
|
32
|
+
openapi_document_schema.definitions['SchemaReference'],
|
33
|
+
# instead of the Schema definition allowing boolean, properties['additionalProperties']
|
34
|
+
# is a oneOf which allows a Schema, SchemaReference, or boolean.
|
35
|
+
# instances of the former two already include the schema implementation (per the previous
|
36
|
+
# describes_schema entries), but the boolean does not.
|
37
|
+
# including in properties['additionalProperties'] applies to any additionalProperties.
|
38
|
+
# (including in properties['additionalProperties'].anyOf[2] would extend booleans too, without
|
39
|
+
# the redundant inclusion that results for Schema and SchemaRef, but redundant inclusion is not
|
40
|
+
# a problem, and this way also applies when none of the anyOf match due to schema errors.)
|
41
|
+
openapi_document_schema.definitions['Schema'].properties['additionalProperties'],
|
42
|
+
]
|
43
|
+
describe_schema.each { |s| s.jsi_schema_instance_modules = [JSI::Schema::Draft04] }
|
22
44
|
|
23
|
-
Document =
|
45
|
+
Document = openapi_document_schema.jsi_schema_module
|
24
46
|
|
25
47
|
# naming these is not strictly necessary, but is nice to have.
|
26
|
-
# generated: `puts
|
48
|
+
# generated: `puts Scorpio::OpenAPI::V3::Document.schema.definitions.select { |k,v| ['object', nil].include?(v['type']) }.keys.map { |k| "#{k[0].upcase}#{k[1..-1]} = Document.definitions['#{k}']" }`
|
49
|
+
|
50
|
+
|
27
51
|
Reference = Document.definitions['Reference']
|
28
52
|
SchemaReference = Document.definitions['SchemaReference']
|
29
53
|
Info = Document.definitions['Info']
|
@@ -86,18 +110,29 @@ module Scorpio
|
|
86
110
|
Callback = Document.definitions['Callback']
|
87
111
|
Encoding = Document.definitions['Encoding']
|
88
112
|
|
89
|
-
|
90
|
-
|
91
|
-
Schema.schema.extend(JSI::Schema::DescribesSchema)
|
92
|
-
SchemaReference.schema.extend(JSI::Schema::DescribesSchema)
|
113
|
+
raise(Bug) unless Schema < JSI::Schema
|
114
|
+
raise(Bug) unless SchemaReference < JSI::Schema
|
93
115
|
end
|
94
116
|
module V2
|
95
|
-
|
117
|
+
openapi_document_schema = JSI.new_schema(::JSON.parse(Scorpio.root.join(
|
118
|
+
'documents/swagger.io/v2/schema.json'
|
119
|
+
).read))
|
120
|
+
|
121
|
+
# the schema represented by Scorpio::OpenAPI::V2::Schema will describe schemas itself, so we set it to
|
122
|
+
# include on its schema module the jsi_schema_instance_modules that implement schema functionality.
|
123
|
+
describe_schema = [
|
124
|
+
openapi_document_schema.definitions['schema'],
|
125
|
+
# comments above on v3's definitions['Schema'].properties['additionalProperties'] apply here too
|
126
|
+
openapi_document_schema.definitions['schema'].properties['additionalProperties'],
|
127
|
+
]
|
128
|
+
describe_schema.each { |s| s.jsi_schema_instance_modules = [JSI::Schema::Draft04] }
|
96
129
|
|
97
|
-
Document =
|
130
|
+
Document = openapi_document_schema.jsi_schema_module
|
98
131
|
|
99
132
|
# naming these is not strictly necessary, but is nice to have.
|
100
|
-
# generated: `puts
|
133
|
+
# generated: `puts Scorpio::OpenAPI::V2::Document.schema.definitions.select { |k,v| ['object', nil].include?(v['type']) }.keys.map { |k| "#{k[0].upcase}#{k[1..-1]} = Document.definitions['#{k}']" }`
|
134
|
+
|
135
|
+
|
101
136
|
Info = Document.definitions['info']
|
102
137
|
Contact = Document.definitions['contact']
|
103
138
|
License = Document.definitions['license']
|
@@ -153,15 +188,12 @@ module Scorpio
|
|
153
188
|
Enum = Document.definitions['enum']
|
154
189
|
JsonReference = Document.definitions['jsonReference']
|
155
190
|
|
156
|
-
|
157
|
-
# with the module indicating that.
|
158
|
-
Schema.schema.extend(JSI::Schema::DescribesSchema)
|
191
|
+
raise(Bug) unless Schema < JSI::Schema
|
159
192
|
end
|
160
193
|
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
end # (this block is here just so the above informative comment is not interpreted as module doc)
|
194
|
+
# the autoloads for OpenAPI::Operation and OpenAPI::Document are triggered below. these
|
195
|
+
# should not be triggered until all the classes their files reference are defined (above).
|
196
|
+
|
165
197
|
|
166
198
|
module V3
|
167
199
|
module Operation
|
@@ -173,6 +205,9 @@ module Scorpio
|
|
173
205
|
module Reference
|
174
206
|
include OpenAPI::Reference
|
175
207
|
end
|
208
|
+
module Tag
|
209
|
+
include OpenAPI::Tag
|
210
|
+
end
|
176
211
|
require 'scorpio/openapi/v3/server'
|
177
212
|
end
|
178
213
|
|
@@ -186,6 +221,9 @@ module Scorpio
|
|
186
221
|
module JsonReference
|
187
222
|
include OpenAPI::Reference
|
188
223
|
end
|
224
|
+
module Tag
|
225
|
+
include OpenAPI::Tag
|
226
|
+
end
|
189
227
|
end
|
190
228
|
end
|
191
229
|
end
|
data/lib/scorpio/request.rb
CHANGED
@@ -1,7 +1,9 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Scorpio
|
2
4
|
class Request
|
3
|
-
SUPPORTED_REQUEST_MEDIA_TYPES = ['application/json', 'application/x-www-form-urlencoded']
|
4
|
-
FALLBACK_CONTENT_TYPE = 'application/x-www-form-urlencoded'
|
5
|
+
SUPPORTED_REQUEST_MEDIA_TYPES = ['application/json'.freeze, 'application/x-www-form-urlencoded'.freeze].freeze
|
6
|
+
FALLBACK_CONTENT_TYPE = 'application/x-www-form-urlencoded'.freeze
|
5
7
|
|
6
8
|
def self.best_media_type(media_types)
|
7
9
|
if media_types.size == 1
|
@@ -118,12 +120,12 @@ module Scorpio
|
|
118
120
|
def initialize(operation, configuration = {}, &b)
|
119
121
|
@operation = operation
|
120
122
|
|
121
|
-
configuration = JSI.stringify_symbol_keys(configuration)
|
123
|
+
configuration = JSI::Util.stringify_symbol_keys(configuration)
|
122
124
|
params_set = Set.new # the set of params that have been set
|
123
125
|
# do the Configurables first
|
124
126
|
configuration.each do |name, value|
|
125
127
|
if Configurables.public_method_defined?("#{name}=")
|
126
|
-
Configurables.instance_method("#{name}=").
|
128
|
+
Configurables.instance_method("#{name}=").bind_call(self, value)
|
127
129
|
params_set << name
|
128
130
|
end
|
129
131
|
end
|
@@ -148,21 +150,23 @@ module Scorpio
|
|
148
150
|
operation.openapi_document
|
149
151
|
end
|
150
152
|
|
151
|
-
#
|
153
|
+
# the http method for this request
|
154
|
+
# @return [String]
|
152
155
|
def http_method
|
153
|
-
operation.http_method
|
156
|
+
operation.http_method
|
154
157
|
end
|
155
158
|
|
156
|
-
#
|
157
|
-
#
|
159
|
+
# the template for the request's path, to be expanded with {Configurables#path_params} and appended to
|
160
|
+
# the request's {Configurables#base_url}
|
161
|
+
# @return [Addressable::Template]
|
158
162
|
def path_template
|
159
163
|
operation.path_template
|
160
164
|
end
|
161
165
|
|
162
|
-
#
|
163
|
-
#
|
166
|
+
# an Addressable::URI containing only the path to append to the {Configurables#base_url} for this request
|
167
|
+
# @return [Addressable::URI]
|
164
168
|
def path
|
165
|
-
path_params = JSI.stringify_symbol_keys(self.path_params)
|
169
|
+
path_params = JSI::Util.stringify_symbol_keys(self.path_params)
|
166
170
|
missing_variables = path_template.variables - path_params.keys
|
167
171
|
if missing_variables.any?
|
168
172
|
raise(ArgumentError, "path #{operation.path_template_str} for operation #{operation.human_id} requires path_params " +
|
@@ -174,24 +178,26 @@ module Scorpio
|
|
174
178
|
"which were empty: #{empty_variables.inspect}")
|
175
179
|
end
|
176
180
|
|
177
|
-
path_template.expand(path_params)
|
178
|
-
|
179
|
-
|
180
|
-
end
|
181
|
+
path = path_template.expand(path_params)
|
182
|
+
if query_params
|
183
|
+
path.query_values = query_params
|
181
184
|
end
|
185
|
+
path.freeze
|
182
186
|
end
|
183
187
|
|
184
|
-
#
|
188
|
+
# the full URL for this request
|
189
|
+
# @return [Addressable::URI]
|
185
190
|
def url
|
186
191
|
unless base_url
|
187
192
|
raise(ArgumentError, "no base_url has been specified for request")
|
188
193
|
end
|
189
194
|
# we do not use Addressable::URI#join as the paths should just be concatenated, not resolved.
|
190
195
|
# we use File.join just to deal with consecutive slashes.
|
191
|
-
Addressable::URI.parse(File.join(base_url, path))
|
196
|
+
Addressable::URI.parse(File.join(base_url, path)).freeze
|
192
197
|
end
|
193
198
|
|
194
|
-
#
|
199
|
+
# the value of the request Content-Type header
|
200
|
+
# @return [::Ur::ContentType]
|
195
201
|
def content_type_header
|
196
202
|
headers.each do |k, v|
|
197
203
|
return ::Ur::ContentType.new(v) if k =~ /\Acontent[-_]type\z/i
|
@@ -199,8 +205,9 @@ module Scorpio
|
|
199
205
|
nil
|
200
206
|
end
|
201
207
|
|
202
|
-
#
|
203
|
-
#
|
208
|
+
# Content-Type for this request, taken from request headers if present, or the
|
209
|
+
# request {Configurables#media_type}.
|
210
|
+
# @return [::Ur::ContentType]
|
204
211
|
def content_type
|
205
212
|
content_type_header || (media_type ? ::Ur::ContentType.new(media_type) : nil)
|
206
213
|
end
|
@@ -232,16 +239,17 @@ module Scorpio
|
|
232
239
|
# @param name [String, Symbol] the 'name' property of one applicable parameter
|
233
240
|
# @param value [Object] the applicable parameter will be applied to the request with the given value.
|
234
241
|
# @return [Object] echoes the value param
|
235
|
-
# @raise
|
242
|
+
# @raise (see #param_for!)
|
236
243
|
def set_param(name, value)
|
237
244
|
param = param_for!(name)
|
238
245
|
set_param_from(param['in'], param['name'], value)
|
239
246
|
value
|
240
247
|
end
|
241
248
|
|
249
|
+
# returns the value of the named parameter on this request
|
242
250
|
# @param name [String, Symbol] the 'name' property of one applicable parameter
|
243
|
-
# @return [Object]
|
244
|
-
# @raise
|
251
|
+
# @return [Object]
|
252
|
+
# @raise (see #param_for!)
|
245
253
|
def get_param(name)
|
246
254
|
param = param_for!(name)
|
247
255
|
get_param_from(param['in'], param['name'])
|
@@ -249,6 +257,7 @@ module Scorpio
|
|
249
257
|
|
250
258
|
# @param name [String, Symbol] the 'name' property of one applicable parameter
|
251
259
|
# @return [#to_hash, nil]
|
260
|
+
# @raise [Scorpio::AmbiguousParameter] if more than one parameter has the given name
|
252
261
|
def param_for(name)
|
253
262
|
name = name.to_s if name.is_a?(Symbol)
|
254
263
|
params = operation.inferred_parameters.select { |p| p['name'] == name }
|
@@ -263,17 +272,21 @@ module Scorpio
|
|
263
272
|
end
|
264
273
|
end
|
265
274
|
|
266
|
-
# @param name [String, Symbol] the name
|
275
|
+
# @param name [String, Symbol] the 'name' property of one applicable parameter
|
267
276
|
# @return [#to_hash]
|
277
|
+
# @raise [Scorpio::ParameterError] if no parameter has the given name
|
278
|
+
# @raise (see #param_for)
|
268
279
|
def param_for!(name)
|
269
280
|
param_for(name) || raise(ParameterError, "There is no parameter named #{name} on operation #{operation.human_id}:\n#{operation.pretty_inspect.chomp}")
|
270
281
|
end
|
271
282
|
|
272
|
-
#
|
283
|
+
# applies the named value to the appropriate parameter of the request
|
284
|
+
# @param param_in [String, Symbol] one of 'path', 'query', 'header', or 'cookie' - where to apply
|
285
|
+
# the named value
|
273
286
|
# @param name [String, Symbol] the parameter name to apply the value to
|
274
287
|
# @param value [Object] the value
|
275
288
|
# @return [Object] echoes the value param
|
276
|
-
# @raise [ArgumentError] invalid
|
289
|
+
# @raise [ArgumentError] invalid `param_in` parameter
|
277
290
|
# @raise [NotImplementedError] cookies aren't implemented
|
278
291
|
def set_param_from(param_in, name, value)
|
279
292
|
param_in = param_in.to_s if param_in.is_a?(Symbol)
|
@@ -292,10 +305,12 @@ module Scorpio
|
|
292
305
|
value
|
293
306
|
end
|
294
307
|
|
295
|
-
#
|
308
|
+
# returns the value of the named parameter from the specified `param_in` on this request
|
309
|
+
# @param param_in [String, Symbol] one of 'path', 'query', 'header', or 'cookie' - where to retrieve
|
310
|
+
# the named value
|
296
311
|
# @param name [String, Symbol] the parameter name
|
297
|
-
# @return [Object]
|
298
|
-
# @raise [ArgumentError] invalid
|
312
|
+
# @return [Object]
|
313
|
+
# @raise [ArgumentError] invalid `param_in` parameter
|
299
314
|
# @raise [NotImplementedError] cookies aren't implemented
|
300
315
|
def get_param_from(param_in, name)
|
301
316
|
if param_in == 'path'
|
@@ -333,17 +348,17 @@ module Scorpio
|
|
333
348
|
headers.update(self.headers)
|
334
349
|
end
|
335
350
|
ur = nil
|
336
|
-
faraday_connection(-> (yur) { ur = yur }).run_request(http_method, url, body, headers)
|
351
|
+
faraday_connection(-> (yur) { ur = yur }).run_request(http_method.downcase.to_sym, url, body, headers)
|
337
352
|
ur.scorpio_request = self
|
338
353
|
ur
|
339
354
|
end
|
340
355
|
|
341
356
|
# runs this request. returns the response body object - that is, the response body
|
342
357
|
# parsed according to an understood media type, and instantiated with the applicable
|
343
|
-
# response schema if one is specified. see Scorpio::Response#body_object for more detail.
|
358
|
+
# response schema if one is specified. see {Scorpio::Response#body_object} for more detail.
|
344
359
|
#
|
345
360
|
# @raise [Scorpio::HTTPError] if the request returns a 4xx or 5xx status, the appropriate
|
346
|
-
# error is raised - see Scorpio::HTTPErrors
|
361
|
+
# error is raised - see {Scorpio::HTTPErrors}
|
347
362
|
def run
|
348
363
|
ur = run_ur
|
349
364
|
ur.raise_on_http_error
|