Class: Scorpio::Request

Inherits:
Object
  • Object
show all
Includes:
Configurables
Defined in:
lib/scorpio/request.rb

Overview

a Request from a OpenAPI::Operation. Used by OpenAPI::Operation#build_request and related methods.

Defined Under Namespace

Modules: Configurables

Constant Summary collapse

SUPPORTED_MEDIA_TYPES =

media types for which Scorpio has implemented generating Configurables#body from Configurables#body_object

%w(
  application/json
  application/x-www-form-urlencoded
).map(&:freeze).freeze
FALLBACK_CONTENT_TYPE =
'application/x-www-form-urlencoded'.freeze
DEFAULT_USER_AGENT =
-"Scorpio/#{Scorpio::VERSION} (https://github.com/notEthan/scorpio) Faraday/#{Faraday::VERSION} Ruby/#{RUBY_VERSION}"
METHODS_WITH_BODIES =

see also Faraday::Env::MethodsWithBodies

%w(post put patch options).map(&:freeze).freeze

Instance Attribute Summary collapse

Attributes included from Configurables

#accept, #authorization, #base_url, #body, #body_object, #faraday_adapter, #faraday_builder, #headers, #logger, #media_type, #path_params, #query_params, #scheme, #server, #server_variables, #user_agent

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Configurables

#url=

Constructor Details

#initialize(operation, **configuration, &b) ⇒ Request

Returns a new instance of Request.

Parameters:

  • configuration (#to_hash)

    A hash of configurable attributes or parameters for the request - instance methods of Configurables, or request parameters defined by the operation.



149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
# File 'lib/scorpio/request.rb', line 149

def initialize(operation, **configuration, &b)
  @operation = operation
  configuration = JSI::Util.stringify_symbol_keys(configuration)
  configuration.each do |name, value|
    if Configurables.public_method_defined?(:"#{name}=")
      public_send(:"#{name}=", value)
    else
      param = param_for(name) || raise(ArgumentError, -"unrecognized configuration value passed: #{name.inspect}")
      set_param_from(param['in'], param['name'], value)
    end
  end

  if block_given?
    yield self
  end
end

Instance Attribute Details

#operationScorpio::OpenAPI::Operation (readonly)



167
168
169
# File 'lib/scorpio/request.rb', line 167

def operation
  @operation
end

Class Method Details

.best_media_type(media_types) ⇒ Object



20
21
22
23
24
25
26
# File 'lib/scorpio/request.rb', line 20

def self.best_media_type(media_types)
  if media_types.size == 1
    media_types.first
  else
    SUPPORTED_MEDIA_TYPES.detect { |mt| media_types.include?(mt) }
  end
end

Instance Method Details

#content_type::Ur::ContentType

Content-Type for this request, taken from request headers if present, or the request Scorpio::Request::Configurables#media_type.

Returns:

  • (::Ur::ContentType)


236
237
238
# File 'lib/scorpio/request.rb', line 236

def content_type
  content_type_header || (media_type ? ::Ur::ContentType.new(media_type) : nil)
end

#content_type_header::Ur::ContentType

the value of the request Content-Type header

Returns:

  • (::Ur::ContentType)


226
227
228
229
230
231
# File 'lib/scorpio/request.rb', line 226

def content_type_header
  headers.each do |k, v|
    return ::Ur::ContentType.new(v) if k =~ /\Acontent[-_]type\z/i
  end
  nil
end

#each_page_ur(next_page:, raise_on_http_error: true) {|Scorpio::Ur| ... } ⇒ Enumerator?

Runs this request, passing the resulting Ur to the given block. The next_page callable is then called with that Ur and results in the next page's Ur, or nil. This repeats until the next_page call results in nil.

See OpenAPI::Operation#each_link_page for integration with an OpenAPI Operation.

Parameters:

  • next_page (#call)

    a callable which will take a parameter page_ur, which is a Ur, and must result in an Ur representing the next page, which will be yielded to the block.

Yields:

  • (Scorpio::Ur)

    yields the first page, and each subsequent result of calls to next_page until that results in nil

Returns:

  • (Enumerator, nil)


410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
# File 'lib/scorpio/request.rb', line 410

def each_page_ur(next_page: , raise_on_http_error: true)
  return to_enum(__method__, next_page: next_page, raise_on_http_error: raise_on_http_error) unless block_given?
  page_ur = run_ur
  while page_ur
    unless page_ur.is_a?(Scorpio::Ur)
      raise(TypeError, [
        -"next_page must result in a #{Scorpio::Ur}",
        -"this should be the result of #run_ur from a #{OpenAPI::Operation} or #{Request}",
      ].join("\n"))
    end
    page_ur.raise_on_http_error if raise_on_http_error
    yield page_ur
    page_ur = next_page.call(page_ur)
  end
  nil
end

#faraday_connection(yield_ur = nil) ⇒ ::Faraday::Connection

builds a Faraday connection with this Request's faraday_builder and faraday_adapter. passes a given proc yield_ur to middleware to yield an Ur for requests made with the connection.

Parameters:

  • yield_ur (Proc, nil) (defaults to: nil)

Returns:

  • (::Faraday::Connection)


250
251
252
253
254
255
256
257
258
259
260
# File 'lib/scorpio/request.rb', line 250

def faraday_connection(yield_ur = nil)
  Faraday.new do |faraday_connection|
    faraday_builder.call(faraday_connection) if faraday_builder
    if yield_ur
      -> { ::Ur::Faraday }.() # autoload trigger

      faraday_connection.response(:yield_ur, schemas: Set[Scorpio::Ur.schema], logger: self.logger, &yield_ur)
    end
    faraday_connection.adapter(*faraday_adapter)
  end
end

#get_param(name) ⇒ Object

returns the value of the named parameter on this request

Parameters:

  • name (String, Symbol)

    the 'name' property of one applicable parameter

Returns:

  • (Object)

Raises:



278
279
280
281
# File 'lib/scorpio/request.rb', line 278

def get_param(name)
  param = param_for!(name)
  get_param_from(param['in'], param['name'])
end

#get_param_from(param_in, name) ⇒ Object

returns the value of the named parameter from the specified param_in on this request

Parameters:

  • param_in (String, Symbol)

    one of 'path', 'query', 'header', or 'cookie' - where to retrieve the named value

  • name (String, Symbol)

    the parameter name

Returns:

  • (Object)

Raises:

  • (OpenAPI::SemanticError)

    invalid param_in parameter

  • (NotImplementedError)

    cookies aren't implemented



340
341
342
343
344
345
346
347
348
349
350
351
352
353
# File 'lib/scorpio/request.rb', line 340

def get_param_from(param_in, name)
  if param_in == 'path'
    path_params[name]
  elsif param_in == 'query'
    query_params ? query_params[name] : nil
  elsif param_in == 'header'
    _, value = headers.detect { |headername, _| headername.casecmp?(name) }
    value
  elsif param_in == 'cookie'
    raise(NotImplementedError, -"cookies not implemented: #{name.inspect}")
  else
    raise(OpenAPI::SemanticError, -"cannot get param from param_in = #{param_in.inspect} (name: #{name.pretty_inspect.chomp})")
  end
end

#http_methodString

the http method for this request

Returns:

  • (String)


176
177
178
# File 'lib/scorpio/request.rb', line 176

def http_method
  operation.http_method
end

#http_method_with_body?Boolean

Returns:

  • (Boolean)


181
182
183
# File 'lib/scorpio/request.rb', line 181

def http_method_with_body?
  METHODS_WITH_BODIES.include?(http_method.to_str.downcase)
end

#openapi_documentScorpio::OpenAPI::Document



170
171
172
# File 'lib/scorpio/request.rb', line 170

def openapi_document
  operation.openapi_document
end

#param_for(name) ⇒ #to_hash?

Parameters:

  • name (String, Symbol)

    the 'name' property of one applicable parameter

Returns:

  • (#to_hash, nil)

Raises:



286
287
288
289
290
291
292
293
294
295
296
297
298
# File 'lib/scorpio/request.rb', line 286

def param_for(name)
  name = name.to_s if name.is_a?(Symbol)
  params = operation.inferred_parameters.select { |p| p['name'] == name }
  if params.size == 1
    params.first
  elsif params.size == 0
    nil
  else
    raise(AmbiguousParameter.new(
      -"There are multiple parameters for #{name}. matched parameters were: #{params.pretty_inspect.chomp}"
    ).tap { |e| e.name = name })
  end
end

#param_for!(name) ⇒ #to_hash

Parameters:

  • name (String, Symbol)

    the 'name' property of one applicable parameter

Returns:

  • (#to_hash)

Raises:



304
305
306
# File 'lib/scorpio/request.rb', line 304

def param_for!(name)
  param_for(name) || raise(ParameterError, -"There is no parameter named #{name} on operation #{operation.human_id}:\n#{operation.pretty_inspect.chomp}")
end

#pathAddressable::URI

an Addressable::URI containing only the path to append to the Scorpio::Request::Configurables#base_url for this request

Returns:

  • (Addressable::URI)


194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
# File 'lib/scorpio/request.rb', line 194

def path
  path_params = JSI::Util.stringify_symbol_keys(self.path_params)
  missing_variables = path_template.variables - path_params.keys
  if missing_variables.any?
    raise(ArgumentError, -"missing params: #{missing_variables.inspect}\nfor path: #{operation.path_template_str}\nfor operation: #{operation.human_id}")
  end
  empty_variables = path_template.variables.select { |v| path_params[v].to_s.empty? }
  if empty_variables.any?
    raise(ArgumentError, -"empty params: #{empty_variables.inspect}\sfor path: #{operation.path_template_str}\nfor operation #{operation.human_id}")
  end

  path = path_template.expand(path_params)
  if query_params
    path.query_values = query_params
  end
  path.freeze
end

#path_templateAddressable::Template

the template for the request's path, to be expanded with Scorpio::Request::Configurables#path_params and appended to the request's Scorpio::Request::Configurables#base_url

Returns:

  • (Addressable::Template)


188
189
190
# File 'lib/scorpio/request.rb', line 188

def path_template
  operation.path_template
end

#request_schema(media_type: self.media_type) ⇒ ::JSI::Schema

Returns:

  • (::JSI::Schema)


241
242
243
# File 'lib/scorpio/request.rb', line 241

def request_schema(media_type: self.media_type)
  operation.request_schema(media_type: media_type)
end

#run(mutable: false) ⇒ Object

runs this request. returns the response body object - that is, the response body parsed according to an understood media type, and instantiated with the applicable response schema if one is specified. see Scorpio::Response#body_object for more detail.

Parameters:

  • mutable (Boolean) (defaults to: false)

    instantiate the response body object as mutable?

Raises:



393
394
395
396
397
# File 'lib/scorpio/request.rb', line 393

def run(mutable: false)
  ur = run_ur
  ur.raise_on_http_error
  ur.response.body_object(mutable: mutable)
end

#run_urScorpio::Ur

runs this request and returns the full representation of the request that was run and its response.

Returns:



358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
# File 'lib/scorpio/request.rb', line 358

def run_ur
  headers = {}
  if user_agent
    headers['User-Agent'] = user_agent
  end
  headers['Accept'] = accept if accept
  headers['Authorization'] = authorization if authorization
  if !content_type_header
    if media_type
      headers['Content-Type'] = media_type
    else
      # I'd rather not have a default content-type, but if none is set then the HTTP adapter sets this to 
      # application/x-www-form-urlencoded and issues a warning about it.
      if METHODS_WITH_BODIES.include?(http_method.to_s)
        headers['Content-Type'] = FALLBACK_CONTENT_TYPE
      end
    end
  end
  headers.update(self.headers)
  body = self.body

  ur = nil
  conn = faraday_connection(-> (yur) { ur = yur })
  conn.run_request(http_method.downcase.to_sym, url, body, headers)
  ur.scorpio_request = self
  ur
end

#set_param(name, value) ⇒ Object

if there is only one parameter with the given name, of any sort, this will set it.

Parameters:

  • name (String, Symbol)

    the 'name' property of one applicable parameter

  • value (Object)

    the applicable parameter will be applied to the request with the given value.

Returns:

  • (Object)

    echoes the value param

Raises:



268
269
270
271
272
# File 'lib/scorpio/request.rb', line 268

def set_param(name, value)
  param = param_for!(name)
  set_param_from(param['in'], param['name'], value)
  value
end

#set_param_from(param_in, name, value) ⇒ Object

Applies the given value to the appropriate parameter of the request

Parameters:

  • param_in (String, Symbol)

    one of 'path', 'query', 'header', or 'cookie' - where to apply the given value

  • name (String, Symbol)

    the parameter name to apply the value to

  • value (Object)

    parameter value

Returns:

  • (Object)

    the given parameter value

Raises:

  • (ArgumentError)

    invalid param_in parameter

  • (NotImplementedError)

    cookies aren't implemented



316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
# File 'lib/scorpio/request.rb', line 316

def set_param_from(param_in, name, value)
  param_in = param_in.to_s if param_in.is_a?(Symbol)
  name = name.to_s if name.is_a?(Symbol)
  if param_in == 'path'
    self.path_params = self.path_params.merge(name => value)
  elsif param_in == 'query'
    self.query_params = (self.query_params || {}).merge(name => value)
  elsif param_in == 'header'
    self.headers = self.headers.merge(name => value.to_str)
  elsif param_in == 'cookie'
    raise(NotImplementedError, -"cookies not implemented: #{name.inspect} => #{value.inspect}")
  else
    raise(ArgumentError, -"cannot set param from param_in = #{param_in.inspect} (name: #{name.pretty_inspect.chomp}, value: #{value.pretty_inspect.chomp})")
  end
  value
end

#urlAddressable::URI

the full URL for this request

Returns:

  • (Addressable::URI)


214
215
216
217
218
219
220
221
222
# File 'lib/scorpio/request.rb', line 214

def url
  return @url if instance_variable_defined?(:@url)
  unless base_url
    raise(ArgumentError, "no base_url has been specified for request")
  end
  # we do not use Addressable::URI#join as the paths should just be concatenated, not resolved.
  # we use File.join just to deal with consecutive slashes.
  Addressable::URI.parse(File.join(base_url, path)).freeze
end