Exception: Booqable::Error

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

Overview

Custom error class for rescuing from all Booqable errors

Provides detailed error information from API responses including status codes, headers, body content, and validation errors.

Examples:

Catching all Booqable errors

begin
  Booqable.orders.find("invalid_id")
rescue Booqable::Error => e
  puts "API Error: #{e.message}"
  puts "Status: #{e.response_status}"
  puts "Errors: #{e.errors}"
end

Direct Known Subclasses

ClientError, 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

#contextObject (readonly)

Returns the value of attribute context.



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

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



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

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



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

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



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

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



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

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



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_contextObject

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

#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

#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_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_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