Exception: Booqable::Error

Inherits:
StandardError
  • Object
show all
Defined in:
lib/booqable/error.rb,
sig/booqable.rbs

Overview

Custom error class for rescuing from all Booqable errors

Direct Known Subclasses

ServerError

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(response = nil) ⇒ Error

Initialize a new Error

Parameters:

  • response (Hash, nil) (defaults to: nil)

    HTTP response hash containing error details



79
80
81
82
83
# File 'lib/booqable/error.rb', line 79

def initialize(response = nil)
  @response = response
  super(build_error_message)
  build_error_context
end

Instance Attribute Details

#contextRateLimit? (readonly)

Returns the value of attribute context.

Returns:



20
21
22
# File 'lib/booqable/error.rb', line 20

def context
  @context
end

Class Method Details

.error_class_from_response(response) ⇒ Booqable::Error?

Returns the appropriate Booqable::Error subclass based on status and response message

rubocop:disable Metrics/CyclomaticComplexity

Parameters:

  • response (Hash)

    HTTP response

Returns:

  • (Booqable::Error, nil)

    Error instance for the response, or nil if no error class matches



39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
# File 'lib/booqable/error.rb', line 39

def self.error_class_from_response(response)
  status  = response[:status].to_i
  body    = response[:body].to_s
  # headers = response[:response_headers]

  if klass =  case status
     when 400      then error_for_400(response)
     when 401      then error_for_401(response)
     when 402      then error_for_402(body)
     when 403      then Booqable::Forbidden
     when 404      then error_for_404(body)
     when 405      then Booqable::MethodNotAllowed
     when 406      then Booqable::NotAcceptable
     when 409      then Booqable::Conflict
     when 410      then Booqable::Deprecated
     when 415      then Booqable::UnsupportedMediaType
     when 422      then error_for_422(body)
     when 423      then Booqable::Locked
     when 429      then Booqable::TooManyRequests
     when 400..499 then Booqable::ClientError
     when 500      then Booqable::InternalServerError
     when 501      then Booqable::NotImplemented
     when 502      then Booqable::BadGateway
     when 503      then error_for_503(body)
     when 500..599 then Booqable::ServerError
     end
    klass.new(response)
  end
end

.error_for_400(response) ⇒ singleton(Error)

Return most appropriate error for 400 HTTP status code rubocop:disable Metrics/CyclomaticComplexity

Parameters:

  • body (String)

Returns:



88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
# File 'lib/booqable/error.rb', line 88

def self.error_for_400(response)
  case response.body
  when /unwrittable_attribute/i
    Booqable::ReadOnlyAttribute
  when /unknown_attribute/i
    Booqable::UnknownAttribute
  when /extra fields should be an object/i
    Booqable::ExtraFieldsInWrongFormat
  when /fields should be an object/i
    Booqable::FieldsInWrongFormat
  when /page should be an object/i
    Booqable::PageShouldBeAnObject
  when /failed typecasting/i
    Booqable::FailedTypecasting
  when /invalid filter/i
    Booqable::InvalidFilter
  when /required filter/i
    Booqable::RequiredFilter
  when /invalid_grant/i
    error_for_invalid_grant(response)
  else
    Booqable::BadRequest
  end
end

.error_for_401(response) ⇒ Object

Return most appropriate error for 401 HTTP status code



116
117
118
119
120
121
122
123
# File 'lib/booqable/error.rb', line 116

def self.error_for_401(response)
  case response.body
  when /token is invalid \(revoked\)/i
    Booqable::TokenRevoked
  else
    Booqable::Unauthorized
  end
end

.error_for_402(body) ⇒ singleton(Error)

Return most appropriate error for 402 HTTP status code rubocop:disable Metrics/CyclomaticComplexity

Parameters:

  • body (String)

Returns:



128
129
130
131
132
133
134
135
136
137
# File 'lib/booqable/error.rb', line 128

def self.error_for_402(body)
  case body
  when /feature_not_enabled/i
    Booqable::FeatureNotEnabled
  when /trial_expired/i
    Booqable::TrialExpired
  else
    Booqable::PaymentRequired
  end
end

.error_for_404(body) ⇒ singleton(Error)

Return most appropriate error for 404 HTTP status code rubocop:disable Naming/VariableNumber

Parameters:

  • body (String)

Returns:



142
143
144
145
146
147
148
149
150
# File 'lib/booqable/error.rb', line 142

def self.error_for_404(body)
  # rubocop:enable Naming/VariableNumber
  case body
  when /company not found/i
    Booqable::CompanyNotFound
  else
    Booqable::NotFound
  end
end

.error_for_422(body) ⇒ singleton(Error)

Return most appropriate error for 422 HTTP status code rubocop:disable Naming/VariableNumber

Parameters:

  • body (String)

Returns:



155
156
157
158
159
160
161
162
163
164
165
# File 'lib/booqable/error.rb', line 155

def self.error_for_422(body)
  # rubocop:enable Naming/VariableNumber
  case body
  when /is not a datetime/i
    Booqable::InvalidDateTimeFormat
  when /invalid date/i
    Booqable::InvalidDateFormat
  else
    Booqable::UnprocessableEntity
  end
end

.error_for_503(body) ⇒ singleton(Error)

Return most appropriate error for 503 HTTP status code rubocop:disable Naming/VariableNumber

Parameters:

  • body (String)

Returns:



170
171
172
173
174
175
176
177
# File 'lib/booqable/error.rb', line 170

def self.error_for_503(body)
  # rubocop:enable Naming/VariableNumber
  if body =~ /read-only/
    Booqable::ReadOnlyMode
  else
    Booqable::ServiceUnavailable
  end
end

.error_for_invalid_grant(response) ⇒ Class

Return most appropriate error for invalid_grant OAuth error

Determines whether the invalid_grant error is due to a revoked refresh token or a different OAuth grant error by examining the grant_type parameter in the request body.

Parameters:

  • response (Hash)

    HTTP response containing the request body

Returns:

  • (Class)

    RefreshTokenRevoked if grant_type is refresh_token, InvalidGrant otherwise



188
189
190
191
192
193
194
195
196
197
# File 'lib/booqable/error.rb', line 188

def self.error_for_invalid_grant(response)
  grant_type = CGI.parse(response.request_body).dig("grant_type", 0)

  case grant_type
  when /refresh_token/i
    Booqable::RefreshTokenRevoked
  else
    Booqable::InvalidGrant
  end
end

.from_response(response) ⇒ nil

Create and raise an appropriate error from an HTTP response

Parameters:

  • response (Hash)

    HTTP response hash containing status, body, etc.

Returns:

  • (nil)

    Returns nil if no error class is determined for the response

Raises:



27
28
29
30
31
# File 'lib/booqable/error.rb', line 27

def self.from_response(response)
  if error = self.error_class_from_response(response)
    raise error
  end
end

Instance Method Details

#build_error_contextvoid

This method returns an undefined value.

rubocop:enable Metrics/CyclomaticComplexity



70
71
72
73
74
# File 'lib/booqable/error.rb', line 70

def build_error_context
  if RATE_LIMITED_ERRORS.include?(self.class)
    @context = Booqable::RateLimit.from_response(@response)
  end
end

#build_error_messageString?

Returns:

  • (String, nil)


276
277
278
279
280
281
282
283
284
285
286
# File 'lib/booqable/error.rb', line 276

def build_error_message
  return nil if @response.nil?

  message = +"#{@response[:method].to_s.upcase} "
  message << "#{redact_url(@response[:url].to_s.dup)}: "
  message << "#{@response[:status]} - "
  message << response_message.to_s unless response_message.nil?
  message << response_error.to_s unless response_error.nil?
  message << response_error_summary.to_s unless response_error_summary.nil?
  message
end

#dataObject

Returns:

  • (Object)


232
233
234
235
236
237
238
239
240
241
242
243
244
# File 'lib/booqable/error.rb', line 232

def data
  @data ||=
    if (body = @response[:body]) && !body.empty?
      if body.is_a?(String) &&
         @response[:response_headers] &&
         @response[:response_headers][:content_type] =~ /json/

        Sawyer::Agent.serializer.decode(body)
      else
        body
      end
    end
end

#errorsArray<Hash>

Array of validation errors

Returns:

  • (Array<Hash>)

    Error info



201
202
203
204
205
206
207
# File 'lib/booqable/error.rb', line 201

def errors
  if data.is_a?(Hash)
    data[:errors] || []
  else
    []
  end
end

#redact_url(url_string) ⇒ String

Parameters:

  • url_string (String)

Returns:

  • (String)


288
289
290
291
292
293
294
295
# File 'lib/booqable/error.rb', line 288

def redact_url(url_string)
  Client::SECRETS.each do |token|
    if url_string.include? token
      url_string.gsub!(/#{token}=\S+/, "#{token}=(redacted)")
    end
  end
  url_string
end

#response_bodyString

Body returned by the Booqable server.

Returns:

  • (String)


226
227
228
# File 'lib/booqable/error.rb', line 226

def response_body
  @response[:body]
end

#response_errorString?

Returns:

  • (String, nil)


255
256
257
# File 'lib/booqable/error.rb', line 255

def response_error
  "Error: #{data[:error]}" if data.is_a?(Hash) && data[:error]
end

#response_error_summaryString?

Returns:

  • (String, nil)


259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
# File 'lib/booqable/error.rb', line 259

def response_error_summary
  return nil unless data.is_a?(Hash) && !Array(data[:errors]).empty?

  summary = +"\nError summary:\n"
  return summary << data[:errors] if data[:errors].is_a?(String)

  summary << data[:errors].map do |error|
    if error.is_a? Hash
      error.map { |k, v| "  #{k}: #{v}" }
    else
      "  #{error}"
    end
  end.join("\n")

  summary
end

#response_headersHash

Headers returned by the Booqable server.

Returns:

  • (Hash)


219
220
221
# File 'lib/booqable/error.rb', line 219

def response_headers
  @response[:response_headers]
end

#response_messageString?

Returns:

  • (String, nil)


246
247
248
249
250
251
252
253
# File 'lib/booqable/error.rb', line 246

def response_message
  case data
  when Hash
    data[:message]
  when String
    data
  end
end

#response_statusInteger

Status code returned by the Booqable server.

Returns:

  • (Integer)


212
213
214
# File 'lib/booqable/error.rb', line 212

def response_status
  @response[:status]
end