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

#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

Methods included from RestExchangeFactory

delete, get, patch, post, put

Methods included from RestParametersDefaults

#base_url_value, #pascal_keys?, #rest_client_headers

Methods included from ResponseExtractor

#extract_hash, #to_hash

Methods inherited from ExchangeHandler

#convert_to_lower?, #default_hash=, #elements, #expected_mandatory_elements, #expected_mandatory_json_values, #expected_mandatory_xpath_values, #set_remove_key, #set_remove_keys, #store, #strip_namespaces?, #to_s, #use

Methods included from HandlerAccessors

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

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



28
29
30
31
32
33
34
35
36
37
38
39
# File 'lib/soaspec/exchange_handlers/rest_handler.rb', line 28

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
  init_merge_options # Call this to verify any issues with options on creating object
end

Instance Attribute Details

#api_usernameObject

User used in making API calls



24
25
26
# File 'lib/soaspec/exchange_handlers/rest_handler.rb', line 24

def api_username
  @api_username
end

Instance Method Details

#convert_to_pascal_case(key) ⇒ Object

Convert snakecase to PascalCase



99
100
101
102
103
# File 'lib/soaspec/exchange_handlers/rest_handler.rb', line 99

def convert_to_pascal_case(key)
  return key if /[[:upper:]]/ =~ key[0] # If first character already capital, don't do conversion

  key.split('_').map(&:capitalize).join
end

#found?(response) ⇒ Boolean

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

Returns:

  • (Boolean)


128
129
130
# File 'lib/soaspec/exchange_handlers/rest_handler.rb', line 128

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

#include_in_body?(response, expected) ⇒ Boolean

Returns Whether response body includes String.

Returns:

  • (Boolean)

    Whether response body includes String



123
124
125
# File 'lib/soaspec/exchange_handlers/rest_handler.rb', line 123

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



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

def include_key?(response, expected)
  value_from_path(response, expected)
end

#include_value?(response, expected) ⇒ Boolean

Returns Whether response contains expected value.

Returns:

  • (Boolean)

    Whether response contains expected value



133
134
135
# File 'lib/soaspec/exchange_handlers/rest_handler.rb', line 133

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

#init_merge_optionsHash

Initialize value of merged options

Returns:

  • (Hash)

    Hash of merged options



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

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



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

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

  if path[0] != '$'
    path = convert_to_pascal_case(path) if pascal_keys?
    path = '$..' + path
  end
  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



52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
# File 'lib/soaspec/exchange_handlers/rest_handler.rb', line 52

def make_request(override_parameters)
  @merged_options ||= init_merge_options
  test_values = override_parameters
  test_values[:params] ||= {}
  test_values[:method] ||= :post
  test_values[:suburl] = test_values[:suburl].to_s if test_values[:suburl]
  test_values[:params][:params] = test_values[:q] if test_values[:q] # Use q for query parameters. Nested :params is ugly and long
  # In order for ERB to be calculated at correct time, the first time request is made, the resource should be created
  @resource ||= RestClient::Resource.new(ERB.new(base_url_value).result(binding), @merged_options)

  @resource_used = test_values[:suburl] ? @resource[test_values[:suburl]] : @resource

  begin
    response = case test_values[:method]
               when :post, :patch, :put
                 Soaspec::SpecLogger.info("request body: #{post_data(test_values)}")
                 @resource_used.send(test_values[:method].to_s, post_data(test_values), test_values[:params])
               else # :get, :delete
                 @resource_used.send(test_values[:method].to_s, test_values[:params])
               end
  rescue RestClient::ExceptionWithResponse => e
    response = e.response
  end
  Soaspec::SpecLogger.info(["response_headers: #{response.headers}", "response_body: #{response}"])
  response
end

#parse_headersHash

Perform ERB on each header value

Returns:

  • (Hash)

    Hash from ‘rest_client_headers’ passed through ERB



90
91
92
93
94
95
96
# File 'lib/soaspec/exchange_handlers/rest_handler.rb', line 90

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



223
224
225
226
227
# File 'lib/soaspec/exchange_handlers/rest_handler.rb', line 223

def request(response)
  return 'Request not yet sent' if response.nil?

  response.request
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



118
119
120
# File 'lib/soaspec/exchange_handlers/rest_handler.rb', line 118

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



83
84
85
86
# File 'lib/soaspec/exchange_handlers/rest_handler.rb', line 83

def rest_resource_options
  {
  }
end

#status_code_for(response) ⇒ Integer

Returns HTTP Status code for response.

Returns:

  • (Integer)

    HTTP Status code for response



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

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 (Response)
  • 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



183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
# File 'lib/soaspec/exchange_handlers/rest_handler.rb', line 183

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

    return result.attributes[attribute].inner_text
  when :json
    paths_to_check = path.split(',')
    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, "Path '#{path}' not found in '#{response.body}'" if matching_values.empty?

    matching_values.first.first
  when :hash
    response.dig(path.split('.')) # Use path as Hash dig expression separating params via '.' TODO: Unit test
  else
    raise NoElementAtPath, 'Response is empty' if response.to_s.empty?

    response.to_s[/path/] # Perform regular expression using path if not XML nor JSON TODO: Unit test
  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



210
211
212
213
214
215
216
217
218
219
220
# File 'lib/soaspec/exchange_handlers/rest_handler.rb', line 210

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


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

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 Interpreter.response_type_for(response) == :xml

  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