Module: Dinie::Internal::Errors

Defined in:
lib/dinie/runtime/errors.rb

Overview

The transport-agnostic error dispatcher. Builds the right APIError from an HTTP error response and reads the RFC 9457 Problem Details body.

This is the controlled runtime → generated seam (architecture §4): it references the generated constant ERROR_REGISTRY (defined in ‘generated/errors/registry.rb`). The reference is the forcing-function — if the error surface changes without regenerating the table, dispatch specs break. The runtime never `require`s the generated layer; the barrel loads the registry before any call.

Constant Summary collapse

REQUEST_ID_HEADER =

Header carrying the per-request correlation id, surfaced as ‘error.request_id`.

"x-request-id"

Class Method Summary collapse

Class Method Details

.dispatch(type_url, status) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



242
243
244
245
246
247
# File 'lib/dinie/runtime/errors.rb', line 242

def dispatch(type_url, status)
  registry = Dinie::Internal::ERROR_REGISTRY
  (type_url && registry[:by_type][type_url]) ||
    registry[:by_status][status] ||
    fallback_ctor(status)
end

.fallback_ctor(status) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



250
251
252
253
254
# File 'lib/dinie/runtime/errors.rb', line 250

def fallback_ctor(status)
  return Dinie::Internal::ERROR_REGISTRY[:fallback_5xx] if status >= 500

  Dinie::APIStatusError
end

.from_response(status:, headers: {}, body: nil, force: nil) ⇒ Dinie::APIStatusError

Build the typed error for an HTTP error response: dispatch by Problem Details ‘type` URL, then by HTTP status, then by the ≥500 band fallback. The body is parsed as JSON once (symbol keys); a non-JSON body is preserved as raw text.

Parameters:

  • status (Integer)

    HTTP status code

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

    response headers

  • body (String, nil) (defaults to: nil)

    raw response body

  • force (Class, nil) (defaults to: nil)

    when given, skip dispatch and build this exact class (used by the transport to guarantee an ‘AuthError` on a persistent 401)

Returns:



232
233
234
235
236
237
238
239
# File 'lib/dinie/runtime/errors.rb', line 232

def from_response(status:, headers: {}, body: nil, force: nil)
  record = parse_problem(body)
  type_url = string_value(record, :type)
  request_id = header_request_id(headers) || string_value(record, :request_id)
  ctor = force || dispatch(type_url, status)
  ctor.new(status: status, body: record || presentable_body(body), headers: headers || {},
           request_id: request_id)
end

.header_lookup(headers, name) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



278
279
280
281
282
283
284
# File 'lib/dinie/runtime/errors.rb', line 278

def header_lookup(headers, name)
  return nil unless headers.respond_to?(:each)

  target = name.downcase
  headers.each { |key, value| return value if key.to_s.downcase == target }
  nil
end

.header_request_id(headers) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



272
273
274
275
# File 'lib/dinie/runtime/errors.rb', line 272

def header_request_id(headers)
  value = header_lookup(headers, REQUEST_ID_HEADER)
  value.is_a?(Array) ? value.first : value
end

.parse_problem(body) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



257
258
259
260
261
262
263
264
# File 'lib/dinie/runtime/errors.rb', line 257

def parse_problem(body)
  return nil unless body.is_a?(String) && !body.empty?

  parsed = JSON.parse(body, symbolize_names: true)
  parsed.is_a?(Hash) ? parsed : nil
rescue JSON::ParserError
  nil
end

.presentable_body(body) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



267
268
269
# File 'lib/dinie/runtime/errors.rb', line 267

def presentable_body(body)
  body.is_a?(String) && !body.empty? ? body : nil
end

.string_value(record, key) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



287
288
289
290
291
292
# File 'lib/dinie/runtime/errors.rb', line 287

def string_value(record, key)
  return nil unless record

  value = record[key]
  value.is_a?(String) ? value : nil
end