Exception: SmilyCli::ApiError

Inherits:
Error
  • Object
show all
Defined in:
lib/smily_cli/errors.rb,
sig/smily_cli.rbs

Overview

Raised when the API returns a non-success HTTP status. Carries the parsed status/body so callers can render a helpful message.

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(message, status: nil, body: nil, headers: {}) ⇒ ApiError

Returns a new instance of ApiError.

Parameters:

  • message (String)
  • status (Integer, nil) (defaults to: nil)
  • body (Object, nil) (defaults to: nil)
  • headers (Hash) (defaults to: {})


72
73
74
75
76
77
# File 'lib/smily_cli/errors.rb', line 72

def initialize(message, status: nil, body: nil, headers: {})
  super(message)
  @status = status
  @body = body
  @headers = headers || {}
end

Instance Attribute Details

#bodyObject? (readonly)

Returns parsed response body (Hash/Array) when JSON, else the raw string.

Returns:

  • (Object, nil)

    parsed response body (Hash/Array) when JSON, else the raw string



53
54
55
# File 'lib/smily_cli/errors.rb', line 53

def body
  @body
end

#headersHash (readonly)

Returns response headers.

Returns:

  • (Hash)

    response headers



55
56
57
# File 'lib/smily_cli/errors.rb', line 55

def headers
  @headers
end

#statusInteger? (readonly)

Returns HTTP status code.

Returns:

  • (Integer, nil)

    HTTP status code



51
52
53
# File 'lib/smily_cli/errors.rb', line 51

def status
  @status
end

Class Method Details

.extract_detail(parsed) ⇒ 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.

Pull a human message out of the many shapes BookingSync error bodies take:

{ "errors": [ { "title": ..., "detail": ... } ] }
{ "errors": { "field": ["msg"] } }
{ "error": "..." } / { "error_description": "..." } (OAuth)


115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
# File 'lib/smily_cli/errors.rb', line 115

def self.extract_detail(parsed)
  return parsed if parsed.is_a?(String)
  return nil unless parsed.is_a?(Hash)

  if (oauth = parsed["error_description"] || parsed["error"])
    return oauth.to_s
  end

  errors = parsed["errors"] || parsed["error"]
  case errors
  when Array
    errors.map { |e| error_line(e) }.compact.join("; ")
  when Hash
    errors.map { |field, msgs| "#{field}: #{Array(msgs).join(', ')}" }.join("; ")
  when String
    errors
  end
end

.from_api(error) ⇒ ApiError

Build an SmilyCli::ApiError from a BookingSync::API::Error.

Parameters:

  • error (BookingSync::API::Error)

Returns:



61
62
63
64
65
66
# File 'lib/smily_cli/errors.rb', line 61

def self.from_api(error)
  status  = error.respond_to?(:status) ? error.status : nil
  headers = error.respond_to?(:headers) ? error.headers : {}
  body    = error.respond_to?(:body) ? error.body : nil
  new(summary(status, body), status: status, body: parse_body(body), headers: headers || {})
end

.parse_body(raw_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.



101
102
103
104
105
106
107
108
# File 'lib/smily_cli/errors.rb', line 101

def self.parse_body(raw_body)
  return raw_body unless raw_body.is_a?(String)
  return nil if raw_body.empty?

  JSON.parse(raw_body)
rescue JSON::ParserError
  raw_body
end

.summary(status, raw_body) ⇒ String

Best-effort extraction of API error detail into a one-line summary.

Parameters:

  • status (Integer, nil)
  • raw_body (String, nil)

Returns:

  • (String)


93
94
95
96
97
98
# File 'lib/smily_cli/errors.rb', line 93

def self.summary(status, raw_body)
  parsed = parse_body(raw_body)
  detail = extract_detail(parsed)
  label = status ? "API request failed (HTTP #{status})" : "API request failed"
  detail ? "#{label}: #{detail}" : label
end

Instance Method Details

#exit_statusObject



79
80
81
82
83
84
85
86
# File 'lib/smily_cli/errors.rb', line 79

def exit_status
  case status
  when 401, 403 then 3
  when 404      then 4
  when 429      then 5
  else 1
  end
end