Class: Lighthouse::ServiceException

Inherits:
Object
  • Object
show all
Extended by:
SentryLogging
Defined in:
lib/lighthouse/service_exception.rb

Overview

Custom exception that maps Lighthouse API errors to controller ExceptionHandling-friendly format

Constant Summary collapse

ERROR_MAP =

a map of the known Lighthouse errors based on the documentation developer.va.gov/

{
  504 => Common::Exceptions::GatewayTimeout,
  503 => Common::Exceptions::ServiceUnavailable,
  502 => Common::Exceptions::BadGateway,
  501 => Common::Exceptions::NotImplemented,
  500 => Common::Exceptions::ExternalServerInternalServerError,
  499 => Common::Exceptions::ClientDisconnected,
  429 => Common::Exceptions::TooManyRequests,
  422 => Common::Exceptions::UnprocessableEntity,
  413 => Common::Exceptions::PayloadTooLarge,
  404 => Common::Exceptions::ResourceNotFound,
  403 => Common::Exceptions::Forbidden,
  401 => Common::Exceptions::Unauthorized,
  400 => Common::Exceptions::BadRequest
}.freeze

Class Method Summary collapse

Methods included from SentryLogging

log_exception_to_sentry, log_message_to_sentry, non_nil_hash?, normalize_level, rails_logger, set_sentry_metadata

Class Method Details

.error_class(status_code) ⇒ Object

chooses which error class should be reported based on the http status



54
55
56
57
58
# File 'lib/lighthouse/service_exception.rb', line 54

def self.error_class(status_code)
  return Common::Exceptions::ServiceError unless ERROR_MAP.include?(status_code)

  ERROR_MAP[status_code]
end

.error_object_details(error_body, status_code) ⇒ Object

error details that match the evss_errors response schema uses known fields in the Lighthouse errors such as “title”, “code”, “detail”, “message”, “error” used to get more information from Lighthouse errors in the controllers



83
84
85
86
87
88
89
90
91
92
93
94
# File 'lib/lighthouse/service_exception.rb', line 83

def self.error_object_details(error_body, status_code)
  status = status_code.to_s
  title = error_body['title'] || error_class(status_code).to_s
  detail = error_body['detail'] ||
           error_body['message'] ||
           error_body['error'] ||
           error_body['error_description'] ||
           'No details provided'
  code = error_body['code'] || status

  [status, title, detail, code]
end

.get_errors_from_response(error, status_code) ⇒ Object

extracts and transforms Lighthouse errors into the evss_errors schema for the controller ExceptionHandling class



62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
# File 'lib/lighthouse/service_exception.rb', line 62

def self.get_errors_from_response(error, status_code)
  errors = error.response[:body]['errors']

  if errors&.any?
    errors.map do |e|
      status, title, detail, code = error_object_details(e, status_code)

      transform_error_keys(e, status, title, detail, code)
    end
  else
    error_body = error.response[:body]

    status, title, detail, code = error_object_details(error_body, status_code)

    [transform_error_keys(error_body, status, title, detail, code)]
  end
end

.get_status_code(response) ⇒ Object



137
138
139
140
141
# File 'lib/lighthouse/service_exception.rb', line 137

def self.get_status_code(response)
  return response.status if response.respond_to?(:status)

  response[:status] if response.instance_of?(Hash) && response&.key?(:status)
end

.json_response?(response) ⇒ Boolean

Returns:

  • (Boolean)


143
144
145
146
# File 'lib/lighthouse/service_exception.rb', line 143

def self.json_response?(response)
  format = response_type(response)
  format&.include? 'application/json'
end

.log_to_rails_logger(service_name, options) ⇒ Object



130
131
132
133
134
135
# File 'lib/lighthouse/service_exception.rb', line 130

def self.log_to_rails_logger(service_name, options)
  Rails.logger.error(
    service_name,
    options
  )
end

.missing_http_status_server_error(error) ⇒ Object



43
44
45
46
47
48
49
50
51
# File 'lib/lighthouse/service_exception.rb', line 43

def self.missing_http_status_server_error(error)
  if error.instance_of?(Faraday::TimeoutError)
    # we've seen this Faraday error in production so we're adding this to categorize it
    Common::Exceptions::Timeout.new(errors: [{ title: error.class, detail: error.message }])
  else
    # we're not sure if there are other uncategorized errors, so we're adding this to catch any
    Common::Exceptions::ServiceError.new(errors: [{ title: error.class, detail: error.message }])
  end
end

.response_type(response) ⇒ Object



148
149
150
151
152
# File 'lib/lighthouse/service_exception.rb', line 148

def self.response_type(response)
  return response[:headers]['content-type'] if response[:headers]

  response.headers['content-type'] if response.respond_to?(:headers)
end

.send_error(error, service_name, lighthouse_client_id, url, options = {}) ⇒ Object

sends error logs to sentry that contains the client id and url that the consumer was trying call raises an error based off of what the response status was formats the Lighthouse exception for the controller ExceptionHandling to report out to the consumer

Parameters:

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

    a customizable set of options

Options Hash (options):

  • :invoker (string)

    where this method was called from

Raises:



31
32
33
34
35
36
37
38
39
40
41
# File 'lib/lighthouse/service_exception.rb', line 31

def self.send_error(error, service_name, lighthouse_client_id, url, options = {})
  send_error_logs(error, service_name, lighthouse_client_id, url, options)
  return error unless error.respond_to?(:response)

  response = error.response
  status_code = get_status_code(response)
  raise missing_http_status_server_error(error) unless status_code

  errors = get_errors_from_response(error, status_code) if json_response?(response)
  raise error_class(status_code).new(errors:)
end

.send_error_logs(error, service_name, lighthouse_client_id, url, options = {}) ⇒ Object

log errors



104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
# File 'lib/lighthouse/service_exception.rb', line 104

def self.send_error_logs(error, service_name, lighthouse_client_id, url, options = {})
  logging_options = { url:, lighthouse_client_id: }

  if error.respond_to?(:response) && error.response.present?
    logging_options[:status] = error.response[:status]
    logging_options[:body] = error.response[:body]
  else
    logging_options[:message] = error.message
    logging_options[:backtrace] = error.backtrace
  end

  logging_options[:invoker] = options[:invoker] if options[:invoker]

  log_to_rails_logger(service_name, logging_options)

  extra_context = Sentry.set_extras(
    message: error.message,
    url:,
    client_id: lighthouse_client_id
  )

  tags_context = Sentry.set_tags(external_service: service_name)

  log_exception_to_sentry(error, extra_context, tags_context)
end

.transform_error_keys(error_body, status, title, detail, code) ⇒ Object

transform error hash keys into symbols for controller ExceptionHandling class



97
98
99
100
101
# File 'lib/lighthouse/service_exception.rb', line 97

def self.transform_error_keys(error_body, status, title, detail, code)
  error_body
    .merge({ status:, code:, title:, detail: })
    .transform_keys(&:to_sym)
end