Class: Scorpio::Request

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

Defined Under Namespace

Modules: Configurables

Constant Summary collapse

SUPPORTED_REQUEST_MEDIA_TYPES =

media types for which Scorpio has implemented generating / parsing between body and body_object (see Configurables#body and Scorpio::Response#body_object)

%w(
  application/json
  application/x-www-form-urlencoded
).map(&:freeze).freeze
FALLBACK_CONTENT_TYPE =
'application/x-www-form-urlencoded'.freeze
METHODS_WITH_BODIES =

see also Faraday::Env::MethodsWithBodies

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

Instance Attribute Summary collapse

Attributes included from Configurables

#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

Constructor Details

#initialize(operation, configuration = {}, &b) ⇒ Request

Returns a new instance of Request.

Parameters:

  • operation (Scorpio::OpenAPI::Operation)
  • configuration (#to_hash) (defaults to: {})

    a hash keyed with configurable attributes for the request - instance methods of Scorpio::Request::Configurables, whose values will be assigned for those attributes.



136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
# File 'lib/scorpio/request.rb', line 136

def initialize(operation, configuration = {}, &b)
  @operation = operation

  configuration = JSI::Util.stringify_symbol_keys(configuration)
  params_set = Set.new # the set of params that have been set
  # do the Configurables first
  configuration.each do |name, value|
    if Configurables.public_method_defined?("#{name}=")
      Configurables.instance_method("#{name}=").bind_call(self, value)
      params_set << name
    end
  end
  # then do other top-level params
  configuration.reject { |name, _| params_set.include?(name) }.each do |name, value|
    param = param_for(name) || raise(ArgumentError, "unrecognized configuration value passed: #{name.inspect}")
    set_param_from(param['in'], param['name'], value)
  end

  extend operation.request_accessor_module

  if block_given?
    yield self
  end
end

Instance Attribute Details

#operationScorpio::OpenAPI::Operation (readonly)



162
163
164
# File 'lib/scorpio/request.rb', line 162

def operation
  @operation
end

Class Method Details

.best_media_type(media_types) ⇒ Object



17
18
19
20
21
22
23
# File 'lib/scorpio/request.rb', line 17

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

.method_with_body?(http_method) ⇒ Boolean

Parameters:

  • http_method (String)

Returns:

  • (Boolean)

Raises:

  • (ArgumentError)


27
28
29
30
# File 'lib/scorpio/request.rb', line 27

def self.method_with_body?(http_method)
  raise(ArgumentError) unless http_method.is_a?(String)
  METHODS_WITH_BODIES.include?(http_method.downcase)
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)


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

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)


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

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| ... } ⇒ void

This method returns an undefined value.

todo make a proper iterator interface

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



393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
# File 'lib/scorpio/request.rb', line 393

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) (defaults to: nil)

Returns:

  • (::Faraday::Connection)


241
242
243
244
245
246
247
248
249
250
251
# File 'lib/scorpio/request.rb', line 241

def faraday_connection(yield_ur = nil)
  Faraday.new do |faraday_connection|
    faraday_builder.call(faraday_connection)
    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:



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

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



331
332
333
334
335
336
337
338
339
340
341
342
343
344
# File 'lib/scorpio/request.rb', line 331

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.downcase == name.downcase }
    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)


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

def http_method
  operation.http_method
end

#openapi_documentScorpio::OpenAPI::Document



165
166
167
# File 'lib/scorpio/request.rb', line 165

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:



277
278
279
280
281
282
283
284
285
286
287
288
289
# File 'lib/scorpio/request.rb', line 277

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:



295
296
297
# File 'lib/scorpio/request.rb', line 295

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)


184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
# File 'lib/scorpio/request.rb', line 184

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, "path #{operation.path_template_str} for operation #{operation.human_id} requires path_params " +
      "which were missing: #{missing_variables.inspect}")
  end
  empty_variables = path_template.variables.select { |v| path_params[v].to_s.empty? }
  if empty_variables.any?
    raise(ArgumentError, "path #{operation.path_template_str} for operation #{operation.human_id} requires path_params " +
      "which were empty: #{empty_variables.inspect}")
  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)


178
179
180
# File 'lib/scorpio/request.rb', line 178

def path_template
  operation.path_template
end

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

Returns:

  • (::JSI::Schema)


232
233
234
# File 'lib/scorpio/request.rb', line 232

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

#runObject

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.

Raises:



381
382
383
384
385
# File 'lib/scorpio/request.rb', line 381

def run
  ur = run_ur
  ur.raise_on_http_error
  ur.response.body_object
end

#run_urScorpio::Ur

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

Returns:



349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
# File 'lib/scorpio/request.rb', line 349

def run_ur
  headers = {}
  if user_agent
    headers['User-Agent'] = user_agent
  end
  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:



259
260
261
262
263
# File 'lib/scorpio/request.rb', line 259

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 named value to the appropriate parameter of the request

Parameters:

  • param_in (String, Symbol)

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

  • name (String, Symbol)

    the parameter name to apply the value to

  • value (Object)

    the value

Returns:

  • (Object)

    echoes the value param

Raises:

  • (ArgumentError)

    invalid param_in parameter

  • (NotImplementedError)

    cookies aren't implemented



307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
# File 'lib/scorpio/request.rb', line 307

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)
  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)


206
207
208
209
210
211
212
213
# File 'lib/scorpio/request.rb', line 206

def 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