Class: Soaspec::RestHandler

Inherits:
ExchangeHandler show all
Extended by:
RestExchangeFactory, RestParameters
Includes:
ResponseExtractor, RestParametersDefaults
Defined in:
lib/soaspec/exchange_handlers/rest_handler.rb

Overview

Wraps around Savon client defining default values dependent on the soap request

Instance Attribute Summary collapse

Attributes inherited from ExchangeHandler

#exception, #request_option, #template_name

Instance Method Summary collapse

Methods included from RestParameters

base_url, basic_auth, basic_auth_file, client_id, headers, oauth2, oauth2_file, pascal_keys, retry_on_exceptions

Methods included from RestExchangeFactory

delete, get, patch, post, put

Methods included from RestParametersDefaults

#base_url_value, #parent_url, #pascal_keys?, #rest_client_headers

Methods included from ResponseExtractor

#extract_hash, #to_hash

Methods inherited from ExchangeHandler

#default_hash=, #elements, #set_remove_key, #set_remove_keys, #store, #to_s, use, #use

Methods included from HandlerAccessors

#attribute, #convert_to_lower, #default_hash, #element, #mandatory_elements, #mandatory_json_values, #mandatory_xpath_values, #strip_namespaces, #template_name

Methods included from ExchangeHandlerDefaults

#convert_to_lower?, #expected_mandatory_elements, #expected_mandatory_json_values, #expected_mandatory_xpath_values, #retry_exception_limit, #retry_on_exceptions, #retry_pause_time, #strip_namespaces?

Constructor Details

#initialize(name = self.class.to_s, options = {}) ⇒ RestHandler

Setup object to handle communicating with a particular SOAP WSDL

Parameters:

  • options (Hash) (defaults to: {})

    Options defining REST request. base_url, default_hash



31
32
33
34
35
36
37
38
39
40
41
42
# File 'lib/soaspec/exchange_handlers/rest_handler.rb', line 31

def initialize(name = self.class.to_s, options = {})
  raise "Base URL not set! Please set in class with 'base_url' method" unless base_url_value

  if name.is_a?(Hash) && options == {} # If name is not set, use first parameter as the options hash
    options = name
    name = self.class.to_s
  end
  super
  set_remove_keys(options, %i[api_username default_hash template_name])
  @init_options = options
  @merged_options ||= init_merge_options # Caches initial merge options
end

Instance Attribute Details

#api_usernameObject

User used in making API calls



27
28
29
# File 'lib/soaspec/exchange_handlers/rest_handler.rb', line 27

def api_username
  @api_username
end

Instance Method Details

#after_response(_response, _self) ⇒ Object

Override this with ‘after_response’ within class definition to perform an action after response is retrieved

Parameters:

  • _response (RestClient::Response)

    Response to interpret to perform after block



52
# File 'lib/soaspec/exchange_handlers/rest_handler.rb', line 52

def after_response(_response, _self); end

#calculated_json_path_matches(path, response, attribute, not_empty: false) ⇒ Array

Calculate all JSON path values based on rules. ‘,’, pascal_case

Parameters:

  • response (RestClient::Response)

    Response from API

  • path (Object)

    Xpath, JSONPath or other path identifying how to find element

  • attribute (String)

    Generic attribute to find. Will override path

  • not_empty (Boolean) (defaults to: false)

    Whether to fail if result is empty

Returns:

  • (Array)

    Paths to check as first and matching values (List of values matching JSON Path) as second

Raises:



180
181
182
183
184
185
186
187
188
189
190
# File 'lib/soaspec/exchange_handlers/rest_handler.rb', line 180

def calculated_json_path_matches(path, response, attribute, not_empty: false)
  path = add_pascal_path(path)
  paths_to_check = path.split(',')
  paths_to_check = paths_to_check.map { |path_to_check| prefix_json_path(path_to_check) }
  matching_values = paths_to_check.collect do |path_to_check|
    json_path_values_for(response, path_to_check, attribute: attribute)
  end.reject(&:empty?)
  raise NoElementAtPath, "No value at JSONPath '#{paths_to_check}' in '#{response.body}'" if matching_values.empty? && not_empty

  matching_values.first
end

#found?(response) ⇒ Boolean

@@return [Boolean] Whether the request found the desired value or not

Returns:

  • (Boolean)


126
127
128
# File 'lib/soaspec/exchange_handlers/rest_handler.rb', line 126

def found?(response)
  status_code_for(response) != 404
end

#hash_used_in_request(override_hash) ⇒ Hash

Returns Hash used in REST request based on data conversion.

Parameters:

  • override_hash (Hash)

    Values to override default hash with

Returns:

  • (Hash)

    Hash used in REST request based on data conversion



75
76
77
78
79
80
81
82
# File 'lib/soaspec/exchange_handlers/rest_handler.rb', line 75

def hash_used_in_request(override_hash)
  request = override_hash ? @default_hash.merge(override_hash) : @default_hash
  if pascal_keys?
    request.map { |k, v| [convert_to_pascal_case(k.to_s), v] }.to_h
  else
    request
  end
end

#include_in_body?(response, expected) ⇒ Boolean

Returns Whether response body includes String.

Returns:

  • (Boolean)

    Whether response body includes String



121
122
123
# File 'lib/soaspec/exchange_handlers/rest_handler.rb', line 121

def include_in_body?(response, expected)
  response.body.include? expected
end

#include_key?(response, expected) ⇒ Boolean

Returns Whether response body contains expected key.

Returns:

  • (Boolean)

    Whether response body contains expected key



136
137
138
139
140
141
# File 'lib/soaspec/exchange_handlers/rest_handler.rb', line 136

def include_key?(response, expected)
  value_from_path(response, expected)
  true
rescue NoElementAtPath
  false
end

#include_value?(response, expected) ⇒ Boolean

Returns Whether response contains expected value.

Returns:

  • (Boolean)

    Whether response contains expected value



131
132
133
# File 'lib/soaspec/exchange_handlers/rest_handler.rb', line 131

def include_value?(response, expected)
  extract_hash(response).include_value? expected
end

#init_merge_optionsHash

Initialize value of merged options Also called to verify any issues with options on creating object

Returns:

  • (Hash)

    Hash of merged options



105
106
107
108
109
110
111
112
# File 'lib/soaspec/exchange_handlers/rest_handler.rb', line 105

def init_merge_options
  options = rest_resource_options
  options.merge! basic_auth_params if respond_to? :basic_auth_params
  options[:headers] ||= {}
  options[:headers].merge! parse_headers
  options[:headers][:authorization] ||= ERB.new('Bearer <%= access_token %>').result(binding) if Soaspec.auto_oauth && respond_to?(:access_token)
  options.merge(@init_options)
end

#json_path_values_for(response, path, attribute: nil) ⇒ Enumerable

Returns List of values matching JSON path.

Returns:

  • (Enumerable)

    List of values matching JSON path



168
169
170
171
172
# File 'lib/soaspec/exchange_handlers/rest_handler.rb', line 168

def json_path_values_for(response, path, attribute: nil)
  raise 'JSON does not support attributes' if attribute

  JsonPath.on(response.body, path)
end

#make_request(override_parameters) ⇒ RestClient::Response

Used in together with Exchange request that passes such override parameters Following are for the body of the request

Parameters:

  • override_parameters (Hash)

    Params to characterize REST request

Options Hash (override_parameters):

  • :params (Hash)

    Extra parameters (E.g. headers)

  • suburl (String)

    URL appended to base_url of class

  • :q (Hash)

    Query for REST

  • :method (Symbol)

    REST method (:get, :post, :patch, etc)

  • :body (Hash)

    Hash to be converted to JSON in request body

  • :payload (String)

    String to be passed directly in request body

  • :template_name (String)

    Path to file to be read via ERB and passed in request body

Returns:

  • (RestClient::Response)

    Response from making request



65
66
67
68
69
70
71
# File 'lib/soaspec/exchange_handlers/rest_handler.rb', line 65

def make_request(override_parameters)
  req_params = request_parameters override_parameters
  response = req_params.call @merged_options
  Soaspec::SpecLogger.info("response:\n  headers: #{response&.headers}\n  body: #{response}\n")
  after_response(response, self)
  response
end

#parse_headersHash

Perform ERB on each header value

Returns:

  • (Hash)

    Hash from ‘rest_client_headers’ passed through ERB



94
95
96
97
98
99
100
# File 'lib/soaspec/exchange_handlers/rest_handler.rb', line 94

def parse_headers
  Hash[rest_client_headers.map do |header_name, header_value|
    raise ArgumentError, "Header '#{header_name}' is null. Headers are #{rest_client_headers}" if header_value.nil?

    [header_name, ERB.new(header_value).result(binding)]
  end]
end

#request(response) ⇒ RestClient::Request, String

Returns Request of API call. Either intended request or actual request.

Returns:

  • (RestClient::Request, String)

    Request of API call. Either intended request or actual request



233
234
235
236
237
238
239
240
# File 'lib/soaspec/exchange_handlers/rest_handler.rb', line 233

def request(response)
  if response.nil?
    return 'Request not yet sent. Call :request_parameters' \
    'for what will be sent'
  end

  response.request
end

#request_parameters(override_parameters) ⇒ Soaspec::RestRequest

Returns Parameters used in making a request.

Returns:



45
46
47
# File 'lib/soaspec/exchange_handlers/rest_handler.rb', line 45

def request_parameters(override_parameters)
  RestRequest.new(override_parameters, @merged_options, self)
end

#response_body(response, format: :hash) ⇒ Object

Returns Generic body to be displayed in error messages.

Parameters:

  • format (Hash) (defaults to: :hash)

    Format of expected result.

Returns:

  • (Object)

    Generic body to be displayed in error messages



116
117
118
# File 'lib/soaspec/exchange_handlers/rest_handler.rb', line 116

def response_body(response, format: :hash)
  extract_hash response
end

#rest_resource_optionsHash

Add values to here when extending this class to have default REST options. See rest client resource at github.com/rest-client/rest-client for details It’s easier to set headers via ‘headers’ accessor rather than here

Returns:

  • (Hash)

    Options adding to & overriding defaults



88
89
90
# File 'lib/soaspec/exchange_handlers/rest_handler.rb', line 88

def rest_resource_options
  {}
end

#status_code_for(response) ⇒ Integer

Returns HTTP Status code for response.

Returns:

  • (Integer)

    HTTP Status code for response



144
145
146
# File 'lib/soaspec/exchange_handlers/rest_handler.rb', line 144

def status_code_for(response)
  response&.code
end

#value_from_path(response, path, attribute: nil) ⇒ String

Based on a exchange, return the value at the provided xpath If the path does not begin with a ‘/’, a ‘//’ is added to it

Parameters:

  • response (RestClient::Response)

    Response from API

  • path (Object)

    Xpath, JSONPath or other path identifying how to find element

  • attribute (String) (defaults to: nil)

    Generic attribute to find. Will override path

Returns:

  • (String)

    Value at Xpath



198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
# File 'lib/soaspec/exchange_handlers/rest_handler.rb', line 198

def value_from_path(response, path, attribute: nil)
  path = path.to_s
  case Interpreter.response_type_for(response)
  when :xml, :html
    result = xpath_elements_for(response: response, xpath: path, attribute: attribute).first
    raise NoElementAtPath, "No value at Xpath '#{prefix_xpath(path, attribute)}' in '#{response.body}'" unless result
    return result.inner_text if attribute.nil?

    return result.attributes[attribute].inner_text
  when :json
    matching_values = calculated_json_path_matches(path, response, attribute, not_empty: true)
    matching_values.first
  else # Assume this is a String
    raise NoElementAtPath, 'Response is empty' if response.to_s.empty?

    response.to_s[/#{path}/] # Perform regular expression using path if not XML nor JSON
  end
end

#values_from_path(response, path, attribute: nil) ⇒ Enumerable

Returns List of values returned from path.

Returns:

  • (Enumerable)

    List of values returned from path



218
219
220
221
222
223
224
225
226
227
228
229
230
# File 'lib/soaspec/exchange_handlers/rest_handler.rb', line 218

def values_from_path(response, path, attribute: nil)
  path = path.to_s
  case Interpreter.response_type_for(response)
  when :xml
    xpath_elements_for(response: response, xpath: path, attribute: attribute).map(&:inner_text)
  when :json
    result = calculated_json_path_matches(path, response, attribute)
    result || []
    # json_path_values_for(response, path, attribute: attribute)
  else
    raise "Unable to interpret type of #{response.body}"
  end
end

#xpath_elements_for(response: nil, xpath: nil, attribute: nil) ⇒ Enumerable

Returns the value at the provided xpath

Parameters:

  • response (RestClient::Response) (defaults to: nil)
  • xpath (String) (defaults to: nil)

    Path to find elements from

  • attribute (String) (defaults to: nil)

    Attribute to find path for

Returns:

  • (Enumerable)

    Value inside element found through Xpath

Raises:

  • (ArgumentError)


153
154
155
156
157
158
159
160
161
162
163
164
165
# File 'lib/soaspec/exchange_handlers/rest_handler.rb', line 153

def xpath_elements_for(response: nil, xpath: nil, attribute: nil)
  raise ArgumentError unless response && xpath
  raise "Can't perform XPATH if response is not XML" unless %i[xml html].include? Interpreter.response_type_for(response)

  xpath = prefix_xpath(xpath, attribute)
  temp_doc = Nokogiri.parse(response.body).dup
  if strip_namespaces? && !xpath.include?(':')
    temp_doc.remove_namespaces!
    temp_doc.xpath(xpath)
  else
    temp_doc.xpath(xpath, temp_doc.collect_namespaces)
  end
end