scorpio 0.4.0 → 0.4.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f65533b3372858d91ff71a0ec3847179a8f50a1d825e5f77e94632a249692ecc
4
- data.tar.gz: 7dab437fe9bbe813f2b8a24417d0232fdf39dc861c2723b7f5df74764cf92797
3
+ metadata.gz: 603bcc3914e849b35e8c4519682588fc7f56dda8d52c188fde80eef20bf7b34f
4
+ data.tar.gz: aecb035dd48a48c8f97e7ca60ffb560abde598385db4cbaf4d0444226708a7ef
5
5
  SHA512:
6
- metadata.gz: 13c15b82acc7509c807336037993fa6e97bd7308a37e552974ae259b2665230a78a83235d455df485b51c318c76e8f64ed2e4397bcdc24b4ede52b32e58945f2
7
- data.tar.gz: c25e928331ff2e0a00904c0e7db4fef1fd5d3ed77331acd6e3b2e59905301ef1d958ec3346eca92f98298c8b2a1ef1e3b537eaf874d87e128c933108d31fcea5
6
+ metadata.gz: e462291e8caca9542c8de14c81402be9bb004571c93d2af7ce92d842ee22861df92a10e9be941149950d017d7751d41e70d6ada8bb17fb2822c0ba85a0a8cbd9
7
+ data.tar.gz: 6cc6ebc90878d29cc2861c7c48e2231cd5554b7ed569efcbe529295275969d772f9bb1c328039d539ef4e579489f37269ae2003bd6a2d15da90645548cedafe9
@@ -1,3 +1,8 @@
1
+ # v0.4.1
2
+ - Request#param_for
3
+ - Operation#human_id
4
+ - bugfixes
5
+
1
6
  # v0.4.0
2
7
  - Scorpio::OpenAPI v3 classes updated from OAI/OpenAPI-Specification branch oas3-schema
3
8
  - any uniquely-named request parameter will have accessors on Request and can be passed as config to #initialize
@@ -4,7 +4,13 @@ require 'pathname'
4
4
  require 'json'
5
5
  require 'yaml'
6
6
 
7
- Pathname.glob(Pathname.new(__FILE__).dirname.join('../documents/**/*')).select { |p| p.file? && !['.yml', '.yaml'].include?(p.extname) }.each do |file|
7
+ root = Pathname.new(__FILE__).dirname.join('..')
8
+
9
+ document_dirs = [root.join('documents'), root.join('test/documents')]
10
+ documents = document_dirs.map { |d| Pathname.glob(d.join('**/*')) }.inject([], &:+)
11
+ json_documents = documents.select { |p| p.file? && !['.yml', '.yaml'].include?(p.extname) }
12
+
13
+ json_documents.each do |file|
8
14
  begin
9
15
  json_contents = JSON.parse(file.read)
10
16
  yaml = YAML.dump(json_contents, line_width: -1)
@@ -97,6 +97,8 @@ module Scorpio
97
97
  class ConfigError < Error
98
98
  attr_accessor :name
99
99
  end
100
+ class ParameterError < ConfigError
101
+ end
100
102
  class AmbiguousParameter < ConfigError
101
103
  end
102
104
 
@@ -1,5 +1,13 @@
1
1
  module Scorpio
2
2
  module OpenAPI
3
+ # an error in the semantics of this openapi document. for example, an Operation with
4
+ # two body parameters (in v2, not possible in v3) is a semantic error.
5
+ #
6
+ # an instance of a SemanticError may or may not correspond to a validation error of
7
+ # an OpenAPI document against the OpenAPI schema.
8
+ class SemanticError
9
+ end
10
+
3
11
  autoload :Operation, 'scorpio/openapi/operation'
4
12
  autoload :Document, 'scorpio/openapi/document'
5
13
  autoload :OperationsScope, 'scorpio/openapi/operations_scope'
@@ -97,6 +97,21 @@ module Scorpio
97
97
  end
98
98
  end
99
99
 
100
+ # @return [String] a short identifier for this operation appropriate for an error message
101
+ def human_id
102
+ operationId || "path: #{path_template_str}, method: #{http_method}"
103
+ end
104
+
105
+ # @return [Scorpio::OpenAPI::V3::Response, Scorpio::OpenAPI::V2::Response]
106
+ def oa_response(status: )
107
+ status = status.to_s if status.is_a?(Numeric)
108
+ if self.responses
109
+ _, oa_response = self.responses.detect { |k, v| k.to_s == status }
110
+ oa_response ||= self.responses['default']
111
+ end
112
+ oa_response
113
+ end
114
+
100
115
  # this method is not intended to be API-stable at the moment.
101
116
  #
102
117
  # @return [#to_ary<#to_h>] the parameters specified for this operation, plus any others
@@ -141,7 +156,7 @@ module Scorpio
141
156
  end
142
157
 
143
158
  def build_request(*a, &b)
144
- request = Scorpio::Request.new(self, *a, &b)
159
+ Scorpio::Request.new(self, *a, &b)
145
160
  end
146
161
 
147
162
  def run_ur(*a, &b)
@@ -192,28 +207,26 @@ module Scorpio
192
207
 
193
208
  def request_schema(media_type: self.request_media_type)
194
209
  # TODO typechecking on requestBody & children
195
- requestBody &&
210
+ schema_object = requestBody &&
196
211
  requestBody['content'] &&
197
212
  requestBody['content'][media_type] &&
198
- requestBody['content'][media_type]['schema'] &&
199
- requestBody['content'][media_type]['schema'].deref
213
+ requestBody['content'][media_type]['schema']
214
+ schema_object ? JSI::Schema.from_object(schema_object) : nil
200
215
  end
201
216
 
202
217
  def request_schemas
203
218
  if requestBody && requestBody['content']
204
219
  # oamt is for Scorpio::OpenAPI::V3::MediaType
205
- requestBody['content'].values.map { |oamt| oamt['schema'] }.compact.map(&:deref)
220
+ oamts = requestBody['content'].values.select { |oamt| oamt.key?('schema') }
221
+ oamts.map { |oamt| JSI::Schema.from_object(oamt['schema']) }
222
+ else
223
+ []
206
224
  end
207
225
  end
208
226
 
209
227
  # @return JSI::Schema
210
228
  def response_schema(status: , media_type: )
211
- status = status.to_s if status.is_a?(Numeric)
212
- if self.responses
213
- # Scorpio::OpenAPI::V3::Response
214
- _, oa_response = self.responses.detect { |k, v| k.to_s == status }
215
- oa_response ||= self.responses['default']
216
- end
229
+ oa_response = self.oa_response(status: status)
217
230
  oa_media_types = oa_response ? oa_response['content'] : nil # Scorpio::OpenAPI::V3::MediaTypes
218
231
  oa_media_type = oa_media_types ? oa_media_types[media_type] : nil # Scorpio::OpenAPI::V3::MediaType
219
232
  oa_schema = oa_media_type ? oa_media_type['schema'] : nil # Scorpio::OpenAPI::V3::Schema
@@ -257,7 +270,8 @@ module Scorpio
257
270
  elsif body_parameters.size == 1
258
271
  body_parameters.first
259
272
  else
260
- raise(Bug, "multiple body parameters on operation #{operation.pretty_inspect.chomp}") # TODO BLAME
273
+ # TODO blame
274
+ raise(OpenAPI::SemanticError, "multiple body parameters on operation #{operation.pretty_inspect.chomp}")
261
275
  end
262
276
  end
263
277
 
@@ -275,12 +289,7 @@ module Scorpio
275
289
 
276
290
  # @return JSI::Schema
277
291
  def response_schema(status: , media_type: nil)
278
- status = status.to_s if status.is_a?(Numeric)
279
- if self.responses
280
- # Scorpio::OpenAPI::V2::Response
281
- _, oa_response = self.responses.detect { |k, v| k.to_s == status }
282
- oa_response ||= self.responses['default']
283
- end
292
+ oa_response = self.oa_response(status: status)
284
293
  oa_response_schema = oa_response ? oa_response['schema'] : nil # Scorpio::OpenAPI::V2::Schema
285
294
  oa_response_schema ? JSI::Schema.new(oa_response_schema) : nil
286
295
  end
@@ -62,7 +62,7 @@ module Scorpio
62
62
  if body_object.respond_to?(:to_str)
63
63
  body_object
64
64
  else
65
- raise(NotImplementedError)
65
+ raise(NotImplementedError, "Scorpio does not know how to generate the request body with media_type = #{media_type.respond_to?(:to_str) ? media_type : media_type.inspect} for operation: #{operation.human_id}. Scorpio supports media types: #{SUPPORTED_REQUEST_MEDIA_TYPES.join(', ')}. body_object was: #{body_object.pretty_inspect.chomp}")
66
66
  end
67
67
  end
68
68
  else
@@ -128,14 +128,8 @@ module Scorpio
128
128
  end
129
129
  # then do other top-level params
130
130
  configuration.reject { |name, _| params_set.include?(name) }.each do |name, value|
131
- params = operation.inferred_parameters.select { |p| p['name'] == name }
132
- if params.size == 1
133
- set_param_from(params.first['in'], name, value)
134
- elsif params.size == 0
135
- raise(ArgumentError, "unrecognized configuration value passed: #{name.inspect}")
136
- else
137
- raise(AmbiguousParameter.new("There are multiple parameters named #{name.inspect} - cannot use it as a configuration key").tap { |e| e.name = name })
138
- end
131
+ param = param_for(name) || raise(ArgumentError, "unrecognized configuration value passed: #{name.inspect}")
132
+ set_param_from(param['in'], param['name'], value)
139
133
  end
140
134
 
141
135
  extend operation.request_accessor_module
@@ -170,12 +164,12 @@ module Scorpio
170
164
  path_params = JSI.stringify_symbol_keys(self.path_params)
171
165
  missing_variables = path_template.variables - path_params.keys
172
166
  if missing_variables.any?
173
- raise(ArgumentError, "path #{operation.path_template_str} for operation #{operation.operationId} requires path_params " +
167
+ raise(ArgumentError, "path #{operation.path_template_str} for operation #{operation.human_id} requires path_params " +
174
168
  "which were missing: #{missing_variables.inspect}")
175
169
  end
176
170
  empty_variables = path_template.variables.select { |v| path_params[v].to_s.empty? }
177
171
  if empty_variables.any?
178
- raise(ArgumentError, "path #{operation.path_template_str} for operation #{operation.operationId} requires path_params " +
172
+ raise(ArgumentError, "path #{operation.path_template_str} for operation #{operation.human_id} requires path_params " +
179
173
  "which were empty: #{empty_variables.inspect}")
180
174
  end
181
175
 
@@ -248,13 +242,8 @@ module Scorpio
248
242
  # @return [Object] echoes the value param
249
243
  # @raise [Scorpio::AmbiguousParameter] if more than one paramater has the given name
250
244
  def set_param(name, value)
251
- name = name.to_s if name.is_a?(Symbol)
252
- params = operation.inferred_parameters.select { |p| p['name'] == name }
253
- if params.size == 1
254
- set_param_from(params.first['in'], name, value)
255
- else
256
- raise(AmbiguousParameter.new("There are multiple parameters named #{name}; cannot use #set_param").tap { |e| e.name = name })
257
- end
245
+ param = param_for!(name)
246
+ set_param_from(param['in'], param['name'], value)
258
247
  value
259
248
  end
260
249
 
@@ -262,15 +251,32 @@ module Scorpio
262
251
  # @return [Object] the value of the named parameter on this request
263
252
  # @raise [Scorpio::AmbiguousParameter] if more than one paramater has the given name
264
253
  def get_param(name)
254
+ param = param_for!(name)
255
+ get_param_from(param['in'], param['name'])
256
+ end
257
+
258
+ # @param name [String, Symbol] the 'name' property of one applicable parameter
259
+ # @return [#to_hash, nil]
260
+ def param_for(name)
265
261
  name = name.to_s if name.is_a?(Symbol)
266
262
  params = operation.inferred_parameters.select { |p| p['name'] == name }
267
263
  if params.size == 1
268
- get_param_from(params.first['in'], name)
264
+ params.first
265
+ elsif params.size == 0
266
+ nil
269
267
  else
270
- raise(AmbiguousParameter.new("There are multiple parameters named #{name}; cannot use #get_param").tap { |e| e.name = name })
268
+ raise(AmbiguousParameter.new(
269
+ "There are multiple parameters for #{name}. matched parameters were: #{params.pretty_inspect.chomp}"
270
+ ).tap { |e| e.name = name })
271
271
  end
272
272
  end
273
273
 
274
+ # @param name [String, Symbol] the name or {in}.{name} (e.g. "query.search") for the applicable parameter.
275
+ # @return [#to_hash]
276
+ def param_for!(name)
277
+ param_for(name) || raise(ParameterError, "There is no parameter named #{name} on operation #{operation.human_id}:\n#{operation.pretty_inspect.chomp}")
278
+ end
279
+
274
280
  # @param in [String, Symbol] one of 'path', 'query', 'header', or 'cookie' - where to apply the named value
275
281
  # @param name [String, Symbol] the parameter name to apply the value to
276
282
  # @param value [Object] the value
@@ -305,7 +311,8 @@ module Scorpio
305
311
  elsif param_in == 'query'
306
312
  query_params ? query_params[name] : nil
307
313
  elsif param_in == 'header'
308
- headers[name]
314
+ _, value = headers.detect { |headername, _| headername.downcase == name.downcase }
315
+ value
309
316
  elsif param_in == 'cookie'
310
317
  raise(NotImplementedError, "cookies not implemented: #{name.inspect}")
311
318
  else
@@ -116,10 +116,7 @@ module Scorpio
116
116
  raise(ArgumentError, "openapi_document may not be overridden on subclass #{self.inspect} after it was set on #{openapi_document_class.inspect}")
117
117
  end
118
118
  end
119
- update_dynamic_methods
120
-
121
119
  # TODO blame validate openapi_document
122
-
123
120
  update_dynamic_methods
124
121
  end
125
122
 
@@ -129,9 +126,8 @@ module Scorpio
129
126
 
130
127
  def tag_name=(tag_name)
131
128
  unless tag_name.respond_to?(:to_str)
132
- raise(TypeError)
129
+ raise(TypeError, "tag_name must be a string; got: #{tag_name.inspect}")
133
130
  end
134
- set_on_class = self
135
131
  tag_name = tag_name.to_str
136
132
 
137
133
  begin
@@ -187,7 +183,9 @@ module Scorpio
187
183
  return false unless operation_for_resource_class?(operation)
188
184
 
189
185
  # define an instance method if the request schema is for this model
190
- request_resource_is_self = operation.request_schema && represented_schemas.include?(operation.request_schema)
186
+ request_resource_is_self = operation.request_schemas.any? do |request_schema|
187
+ represented_schemas.include?(request_schema)
188
+ end
191
189
 
192
190
  # also define an instance method depending on certain attributes the request description
193
191
  # might have in common with the model's schema attributes
@@ -222,6 +220,7 @@ module Scorpio
222
220
  tag_name_match = tag_name &&
223
221
  operation.tags.respond_to?(:to_ary) && # TODO maybe operation.tags.valid?
224
222
  operation.tags.include?(tag_name) &&
223
+ operation.operationId &&
225
224
  operation.operationId.match(/\A#{Regexp.escape(tag_name)}\.(\w+)\z/)
226
225
 
227
226
  if tag_name_match
@@ -360,11 +359,11 @@ module Scorpio
360
359
  if schema
361
360
  if schema['type'] == 'object'
362
361
  # TODO code dup with response_object_to_instances
363
- if schema['properties'] && schema['properties'][key]
362
+ if schema['properties'].respond_to?(:to_hash) && schema['properties'][key]
364
363
  subschema = schema['properties'][key]
365
364
  include_pair = true
366
365
  else
367
- if schema['patternProperties']
366
+ if schema['patternProperties'].respond_to?(:to_hash)
368
367
  _, pattern_schema = schema['patternProperties'].detect do |pattern, _|
369
368
  key =~ Regexp.new(pattern) # TODO map pattern to ruby syntax
370
369
  end
@@ -375,8 +374,7 @@ module Scorpio
375
374
  else
376
375
  if schema['additionalProperties'] == false
377
376
  include_pair = false
378
- elsif schema['additionalProperties'] == nil
379
- # TODO decide on this (can combine with `else` if treating nil same as schema present)
377
+ elsif [nil, true].include?(schema['additionalProperties'])
380
378
  include_pair = true
381
379
  subschema = nil
382
380
  else
@@ -20,7 +20,7 @@ module Scorpio
20
20
  HTTPError
21
21
  end
22
22
  if error_class
23
- message = "Error calling operation #{scorpio_request.operation.operationId}:\n" + response.body
23
+ message = "Error calling operation #{scorpio_request.operation.human_id}:\n" + response.body
24
24
  raise(error_class.new(message).tap do |e|
25
25
  e.ur = self
26
26
  e.response_object = response.body_object
@@ -1,3 +1,3 @@
1
1
  module Scorpio
2
- VERSION = "0.4.0"
2
+ VERSION = "0.4.1"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: scorpio
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
4
+ version: 0.4.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ethan
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2019-04-27 00:00:00.000000000 Z
11
+ date: 2019-05-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: jsi