class AdsCommon::SavonService

Constants

FALLBACK_API_ERROR_EXCEPTION
MAX_FAULT_LOG_LENGTH
REDACTED_STR

Attributes

config[R]
header_handler[RW]
namespace[R]
version[R]

Public Class Methods

new(config, endpoint, namespace, version) click to toggle source

Creates a new service.

# File lib/ads_common/savon_service.rb, line 39
def initialize(config, endpoint, namespace, version)
  if self.class() == AdsCommon::SavonService
    raise NoMethodError, 'Tried to instantiate an abstract class'
  end
  @config, @version, @namespace = config, version, namespace
  @client = create_savon_client(endpoint, namespace)
  @xml_only = false
end

Private Instance Methods

create_savon_client(endpoint, namespace) click to toggle source

Creates and sets up Savon client.

# File lib/ads_common/savon_service.rb, line 66
def create_savon_client(endpoint, namespace)
  client = GoogleAdsSavon::Client.new do |wsdl, httpi|
    wsdl.endpoint = endpoint
    wsdl.namespace = namespace
    AdsCommon::Http.configure_httpi(@config, httpi)
  end
  client.config.raise_errors = false
  client.config.logger.subject = NoopLogger.new
  return client
end
do_logging(action, request, response) click to toggle source

Log the request, response, and summary lines.

# File lib/ads_common/savon_service.rb, line 169
def do_logging(action, request, response)
  logger = get_logger()
  return unless should_log_summary(logger.level, response.soap_fault?)

  response_hash = response.hash

  soap_headers = {}
  begin
    soap_headers = response_hash[:envelope][:header][:response_header]
  rescue NoMethodError
    # If there are no headers, just ignore. We'll log what we know.
  end

  summary_message = ('ID: %s, URL: %s, Service: %s, Action: %s, Response ' +
      'time: %sms, Request ID: %s') % [@header_handler.identifier,
      request.url, self.class.to_s.split("::").last, action,
      soap_headers[:response_time], soap_headers[:request_id]]
  if soap_headers[:operations]
    summary_message += ', Operations: %s' % soap_headers[:operations]
  end
  summary_message += ', Is fault: %s' % response.soap_fault?

  request_message = nil
  response_message = nil

  if should_log_payloads(logger.level, response.soap_fault?)
    request_message = 'Outgoing request: %s %s' %
        [format_headers(request.headers), sanitize_request(request.body)]
    response_message = 'Incoming response: %s %s' %
        [format_headers(response.http.headers), response.http.body]
  end

  if response.soap_fault?
    summary_message += ', Fault message: %s' % format_fault(
        response_hash[:envelope][:body][:fault][:faultstring])
    logger.warn(summary_message)
    logger.info(request_message) unless request_message.nil?
    logger.info(response_message) unless response_message.nil?
  else
    logger.info(summary_message)
    logger.debug(request_message) unless request_message.nil?
    logger.debug(response_message) unless response_message.nil?
  end
end
exception_for_soap_fault(response) click to toggle source

Finds an exception object for a given response.

# File lib/ads_common/savon_service.rb, line 130
def exception_for_soap_fault(response)
  begin
    fault = response[:fault]
    if fault[:detail] and fault[:detail][:api_exception_fault]
      exception_fault = fault[:detail][:api_exception_fault]
      exception_name = (
          exception_fault[:application_exception_type] ||
          FALLBACK_API_ERROR_EXCEPTION)
      exception_class = get_module().const_get(exception_name)
      return exception_class.new(exception_fault)
    elsif fault[:faultstring]
      fault_message = fault[:faultstring]
      return AdsCommon::Errors::ApiException.new(
          "Unknown exception with error: %s" % fault_message)
    else
      raise ArgumentError.new(fault.to_s)
    end
  rescue Exception => e
    return AdsCommon::Errors::ApiException.new(
        "Failed to resolve exception (%s), SOAP fault: %s" %
        [e.message, response.soap_fault])
  end
end
execute_action(action_name, args, &block) click to toggle source

Executes SOAP action specified as a string with given arguments.

# File lib/ads_common/savon_service.rb, line 87
def execute_action(action_name, args, &block)
  registry = get_service_registry()
  validator = ParametersValidator.new(registry)
  args = validator.validate_args(action_name, args)
  request_info, response = handle_soap_request(
      action_name.to_sym, false, args, validator.extra_namespaces)
  do_logging(action_name, request_info, response)
  handle_errors(response)
  extractor = ResultsExtractor.new(registry)
  result = extractor.extract_result(response, action_name, &block)
  run_user_block(extractor, response, result, &block) if block_given?
  return result
end
format_fault(message) click to toggle source

Format the fault message by capping length and removing newlines.

# File lib/ads_common/savon_service.rb, line 229
def format_fault(message)
  if message.length > MAX_FAULT_LOG_LENGTH
    message = message[0, MAX_FAULT_LOG_LENGTH]
  end
  return message.gsub("\n", ' ')
end
format_headers(headers) click to toggle source

Format headers, redacting sensitive information.

# File lib/ads_common/savon_service.rb, line 215
def format_headers(headers)
  return headers.map do |k, v|
    v = REDACTED_STR if k == 'Authorization'
    [k, v].join(': ')
  end.join(', ')
end
get_logger() click to toggle source

Returns currently configured Logger.

# File lib/ads_common/savon_service.rb, line 51
def get_logger()
  return @config.read('library.logger')
end
get_module() click to toggle source

Returns Module for the current service. Has to be overridden.

# File lib/ads_common/savon_service.rb, line 61
def get_module()
  raise NoMethodError, 'This method needs to be overridden'
end
get_service_registry() click to toggle source

Returns ServiceRegistry for the current service. Has to be overridden.

# File lib/ads_common/savon_service.rb, line 56
def get_service_registry()
  raise NoMethodError, 'This method needs to be overridden'
end
get_soap_xml(action_name, args) click to toggle source

Generates and returns SOAP XML for the specified action and args.

# File lib/ads_common/savon_service.rb, line 78
def get_soap_xml(action_name, args)
  registry = get_service_registry()
  validator = ParametersValidator.new(registry)
  args = validator.validate_args(action_name, args)
  return handle_soap_request(
      action_name.to_sym, true, args, validator.extra_namespaces)
end
handle_errors(response) click to toggle source

Checks for errors in response and raises appropriate exception.

# File lib/ads_common/savon_service.rb, line 118
def handle_errors(response)
  if response.soap_fault?
    exception = exception_for_soap_fault(response)
    raise exception
  end
  if response.http_error?
    raise AdsCommon::Errors::HttpError,
        "HTTP Error occurred: %s" % response.http_error
  end
end
handle_soap_request(action, xml_only, args, extra_namespaces) click to toggle source

Executes the SOAP request with original SOAP name.

# File lib/ads_common/savon_service.rb, line 102
def handle_soap_request(action, xml_only, args, extra_namespaces)
  original_action_name =
      get_service_registry.get_method_signature(action)[:original_name]
  original_action_name = action if original_action_name.nil?
  request_info = nil
  response = @client.request(original_action_name) do |soap, wsdl, http|
    soap.body = args
    @header_handler.prepare_request(http, soap)
    soap.namespaces.merge!(extra_namespaces) unless extra_namespaces.nil?
    return soap.to_xml if xml_only
    request_info = RequestInfo.new(soap.to_xml, http.headers, http.url)
  end
  return request_info, response
end
run_user_block(extractor, response, body) { |header| ... } click to toggle source

Yields to user-specified block with additional information such as headers.

# File lib/ads_common/savon_service.rb, line 156
def run_user_block(extractor, response, body, &block)
  header = extractor.extract_header_data(response)
  case block.arity
    when 1 then yield(header)
    when 2 then yield(header, body)
    else
      raise AdsCommon::Errors::ApiException,
          "Wrong number of block parameters: %d" % block.arity
  end
  return nil
end
sanitize_request(body) click to toggle source

Sanitize the request body, redacting sensitive information.

# File lib/ads_common/savon_service.rb, line 223
def sanitize_request(body)
  return body.gsub(/(developerToken>|httpAuthorizationHeader>)[^<]+(<\/)/,
      '\1' + REDACTED_STR + '\2')
end
should_log_payloads(level, is_fault) click to toggle source

Check whether or not to log payloads based on log level.

# File lib/ads_common/savon_service.rb, line 245
def should_log_payloads(level, is_fault)
  # Fault payloads log at INFO.
  return level <= Logger::INFO if is_fault
  # Success payloads log at DEBUG.
  return level <= Logger::DEBUG
end
should_log_summary(level, is_fault) click to toggle source

Check whether or not to log request summaries based on log level.

# File lib/ads_common/savon_service.rb, line 237
def should_log_summary(level, is_fault)
  # Fault summaries log at WARN.
  return level <= Logger::WARN if is_fault
  # Success summaries log at INFO.
  return level <= Logger::INFO
end