Class: Tama::HTTP

Inherits:
Object
  • Object
show all
Defined in:
lib/tama/http.rb

Constant Summary collapse

RETRY_STATUSES =
[429, 500, 502, 503, 504].freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(base_url:, headers:, timeout:, retries:, connection: nil) ⇒ HTTP

Returns a new instance of HTTP.



15
16
17
18
19
20
21
22
23
24
25
# File 'lib/tama/http.rb', line 15

def initialize(base_url:, headers:, timeout:, retries:, connection: nil)
  @base_uri = parse_base_url(base_url)
  @headers = Faraday::Utils::Headers.new(headers)
  @timeout = validate_timeout(timeout)
  @retries = validate_retries(retries)
  unless connection.nil? || connection.respond_to?(:run_request)
    raise Error::ConfigurationError, "connection must be a Faraday-compatible connection"
  end

  @connection = connection || build_connection
end

Instance Attribute Details

#base_uriObject (readonly)

Returns the value of attribute base_uri.



13
14
15
# File 'lib/tama/http.rb', line 13

def base_uri
  @base_uri
end

#connectionObject (readonly)

Returns the value of attribute connection.



13
14
15
# File 'lib/tama/http.rb', line 13

def connection
  @connection
end

Class Method Details

.segment(value, name) ⇒ Object



88
89
90
91
92
93
94
# File 'lib/tama/http.rb', line 88

def self.segment(value, name)
  unless value.is_a?(String) && !value.empty?
    raise Error::ValidationError.new("#{name} must be a non-empty string", errors: {name => "must be a non-empty string"})
  end

  CGI.escape(value).gsub("+", "%20")
end

Instance Method Details

#check_status!(response) ⇒ Object



74
75
76
77
78
79
80
81
82
83
84
85
86
# File 'lib/tama/http.rb', line 74

def check_status!(response)
  status = response.status.to_i
  return response if status.between?(200, 299)

  body = parse_error_body(response.body)
  raise Error::NotFoundError.new(body:) if status == 404
  if status == 422
    errors = (body.is_a?(Hash) && body.key?("errors")) ? body["errors"] : body
    raise Error::ValidationError.new("API validation failed", errors:, status:)
  end

  raise Error::HTTPError.new(status:, body:)
end

#namespace!Object



27
28
29
30
31
32
# File 'lib/tama/http.rb', line 27

def namespace!
  segments = base_uri.path.split("/").reject(&:empty?)
  raise Error::ConfigurationError, "Base URL must end with an operation namespace" if segments.empty?

  segments.last
end

#parse_body(response, model) ⇒ Object



69
70
71
72
# File 'lib/tama/http.rb', line 69

def parse_body(response, model)
  check_status!(response)
  model.parse(parse_json(response.body))
end

#parse_data(response, model, list: false) ⇒ Object

Raises:



54
55
56
57
58
59
60
61
62
63
64
65
66
67
# File 'lib/tama/http.rb', line 54

def parse_data(response, model, list: false)
  check_status!(response)
  document = parse_json(response.body)
  raise Error::ParseError, "Response JSON must be an object with a data key" unless document.is_a?(Hash) && document.key?("data")

  data = document["data"]
  if list
    raise Error::ParseError, "Response data must be an array" unless data.is_a?(Array)

    data.map { |item| model.parse(item) }
  else
    model.parse(data)
  end
end

#request(method, path, body: nil, query: nil, headers: {}, timeout: nil, &on_data) ⇒ Object



42
43
44
45
46
47
48
49
50
51
52
# File 'lib/tama/http.rb', line 42

def request(method, path, body: nil, query: nil, headers: {}, timeout: nil, &on_data)
  connection.run_request(method, absolute_url(path), body, merged_headers(headers)) do |request|
    request.options.timeout = validate_timeout(timeout || @timeout)
    request.options.on_data = on_data if on_data
    query&.each_pair { |key, value| request.params[key] = value }
  end
rescue Error
  raise
rescue Faraday::Error => e
  raise Error::TransportError.new(e.message, cause: e)
end

#validate_namespace!(*allowed) ⇒ Object



34
35
36
37
38
39
40
# File 'lib/tama/http.rb', line 34

def validate_namespace!(*allowed)
  namespace = namespace!
  return if allowed.include?(namespace)

  raise Error::InvalidNamespaceError,
    "Invalid client namespace. Expected one of #{allowed.inspect}, got #{namespace.inspect}"
end