Class: ApiBrasil::Core::HttpClient

Inherits:
Object
  • Object
show all
Defined in:
lib/api_brasil/core/http_client.rb

Overview

Cliente HTTP interno da SDK. Injeta os headers de autenticacao da plataforma (Authorization: Bearer, DeviceToken, SecretKey), aplica retry com backoff, dispara hooks de observabilidade e converte falhas em subclasses de ApiBrasilError.

Chaves de configuracao aceitas (tambem aceitas em camelCase):

  • bearer_token (String): token JWT obtido no login
  • device_token (String): token do dispositivo (servicos device-based)
  • secret_key (String): SecretKey da API (usada ao criar devices)
  • base_url (String): base da API. Padrao: https://gateway.apibrasil.io/api/v2
  • timeout (Integer): timeout das requisicoes em milissegundos. Padrao: 30000
  • headers (Hash): headers adicionais em todas as requisicoes
  • transport (Transport::Base): transporte customizado
  • retry (Hash, false): politica de retry - veja Retry::DEFAULT
  • hooks (Hash): on_request, on_response, on_retry (callables)

Opcoes por requisicao (options), que sobrescrevem a configuracao: query, headers, bearer_token, device_token, secret_key, timeout, response_type (json | raw | stream).

Constant Summary collapse

DEFAULT_BASE_URL =
"https://gateway.apibrasil.io/api/v2"
DEFAULT_TIMEOUT =
30_000
SDK_USER_AGENT =
"APIBRASIL/SDK-RUBY #{ApiBrasil::VERSION}".freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(config = {}) ⇒ HttpClient

Returns a new instance of HttpClient.

Parameters:

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


48
49
50
51
52
53
54
55
56
57
# File 'lib/api_brasil/core/http_client.rb', line 48

def initialize(config = {})
  config = self.class.normalize_keys(config)
  @config = Env.config.merge(config.compact)

  transport = @config[:transport]
  @transport = transport.respond_to?(:request) ? transport : Transport.default

  @retry_config = Retry.resolve(@config.key?(:retry) ? @config[:retry] : nil)
  @hooks = self.class.normalize_keys(@config[:hooks] || {})
end

Instance Attribute Details

#hooksHash{Symbol => Proc} (readonly)

Returns:

  • (Hash{Symbol => Proc})


45
46
47
# File 'lib/api_brasil/core/http_client.rb', line 45

def hooks
  @hooks
end

#retry_configHash? (readonly)

Returns politica de retry resolvida (nil quando desligada).

Returns:

  • (Hash, nil)

    politica de retry resolvida (nil quando desligada)



42
43
44
# File 'lib/api_brasil/core/http_client.rb', line 42

def retry_config
  @retry_config
end

#transportTransport::Base (readonly)

Returns:



39
40
41
# File 'lib/api_brasil/core/http_client.rb', line 39

def transport
  @transport
end

Class Method Details

.build_query_string(query) ⇒ String

Parameters:

  • query (Hash, nil)

Returns:

  • (String)


195
196
197
198
199
200
201
202
203
204
205
206
# File 'lib/api_brasil/core/http_client.rb', line 195

def self.build_query_string(query)
  return "" if query.nil? || !query.is_a?(Hash) || query.empty?

  parts = query.filter_map do |key, value|
    next if value.nil?

    value = value.join(",") if value.is_a?(Array)
    "#{URI.encode_www_form_component(key.to_s)}=#{URI.encode_www_form_component(value.to_s)}"
  end

  parts.empty? ? "" : "?#{parts.join("&")}"
end

.encode_body(body) ⇒ String

Serializa o corpo em JSON. Hashes vazios viram {} (objeto), como nas demais SDKs, e nunca [].

Returns:

  • (String)


186
187
188
189
190
191
# File 'lib/api_brasil/core/http_client.rb', line 186

def self.encode_body(body)
  return body if body.is_a?(String)
  return "{}" if body.respond_to?(:empty?) && body.empty?

  JSON.generate(body)
end

.escape_path_segment(value) ⇒ String

Escapa um valor usado como segmento de path (%20, nao +).

Returns:

  • (String)


211
212
213
# File 'lib/api_brasil/core/http_client.rb', line 211

def self.escape_path_segment(value)
  URI.encode_www_form_component(value.to_s).gsub("+", "%20")
end

.join_url(base_url, path) ⇒ String

Returns:

  • (String)


216
217
218
219
220
221
# File 'lib/api_brasil/core/http_client.rb', line 216

def self.join_url(base_url, path)
  base = base_url.to_s.sub(%r{/+\z}, "")
  suffix = path.to_s.sub(%r{\A/+}, "")

  suffix.empty? ? base : "#{base}/#{suffix}"
end

.normalize_key(key) ⇒ Symbol

Returns:

  • (Symbol)


178
179
180
# File 'lib/api_brasil/core/http_client.rb', line 178

def self.normalize_key(key)
  key.to_s.gsub(/([a-z\d])([A-Z])/, '\1_\2').downcase.to_sym
end

.normalize_keys(hash) ⇒ Hash{Symbol => Object}

Converte chaves camelCase/String para simbolos snake_case, mantendo a paridade com as SDKs PHP/Node/Dart (baseURL, bearerToken...).

Parameters:

  • hash (Hash)

Returns:

  • (Hash{Symbol => Object})


168
169
170
171
172
173
174
175
# File 'lib/api_brasil/core/http_client.rb', line 168

def self.normalize_keys(hash)
  return {} if hash.nil?
  return hash unless hash.is_a?(Hash)

  hash.each_with_object({}) do |(key, value), acc|
    acc[normalize_key(key)] = value
  end
end

Instance Method Details

#base_urlString

Returns:

  • (String)


60
61
62
63
# File 'lib/api_brasil/core/http_client.rb', line 60

def base_url
  value = @config[:base_url]
  value.is_a?(String) && !value.empty? ? value : DEFAULT_BASE_URL
end

#bearer_tokenString?

Returns:

  • (String, nil)


66
67
68
69
# File 'lib/api_brasil/core/http_client.rb', line 66

def bearer_token
  value = @config[:bearer_token]
  value.is_a?(String) ? value : nil
end

#bearer_token=(token) ⇒ Object Also known as: set_bearer_token

Define/atualiza o Bearer Token (nil remove).



84
85
86
87
88
89
90
# File 'lib/api_brasil/core/http_client.rb', line 84

def bearer_token=(token)
  if token.nil?
    @config.delete(:bearer_token)
  else
    @config[:bearer_token] = token
  end
end

#delete(path, body = nil, options = {}) ⇒ Object

Returns:

  • (Object)


159
160
161
# File 'lib/api_brasil/core/http_client.rb', line 159

def delete(path, body = nil, options = {})
  request("DELETE", path, body, options)
end

#device_tokenString?

Returns:

  • (String, nil)


72
73
74
75
# File 'lib/api_brasil/core/http_client.rb', line 72

def device_token
  value = @config[:device_token]
  value.is_a?(String) ? value : nil
end

#device_token=(token) ⇒ Object Also known as: set_device_token

Define/atualiza o DeviceToken (nil remove).



94
95
96
97
98
99
100
# File 'lib/api_brasil/core/http_client.rb', line 94

def device_token=(token)
  if token.nil?
    @config.delete(:device_token)
  else
    @config[:device_token] = token
  end
end

#get(path, options = {}) ⇒ Object

Returns:

  • (Object)


139
140
141
# File 'lib/api_brasil/core/http_client.rb', line 139

def get(path, options = {})
  request("GET", path, nil, options)
end

#patch(path, body = nil, options = {}) ⇒ Object

Returns:

  • (Object)


154
155
156
# File 'lib/api_brasil/core/http_client.rb', line 154

def patch(path, body = nil, options = {})
  request("PATCH", path, body, options)
end

#post(path, body = nil, options = {}) ⇒ Object

Returns:

  • (Object)


144
145
146
# File 'lib/api_brasil/core/http_client.rb', line 144

def post(path, body = nil, options = {})
  request("POST", path, body, options)
end

#put(path, body = nil, options = {}) ⇒ Object

Returns:

  • (Object)


149
150
151
# File 'lib/api_brasil/core/http_client.rb', line 149

def put(path, body = nil, options = {})
  request("PUT", path, body, options)
end

#request(method, path, body = nil, options = {}) ⇒ Object

Executa uma requisicao no gateway.

Parameters:

  • method (String, Symbol)

    verbo HTTP

  • path (String)

    caminho relativo a base_url

  • body (Hash, Array, String, nil) (defaults to: nil)
  • options (Hash) (defaults to: {})

Returns:

  • (Object)

    corpo da resposta ja decodificado



117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
# File 'lib/api_brasil/core/http_client.rb', line 117

def request(method, path, body = nil, options = {})
  options = self.class.normalize_keys(options)
  transport_request = build_transport_request(method, path, body, options)
  attempt = 0

  loop do
    response = perform(transport_request, body, attempt)
    return response.data if response.status < 400

    error = Errors::ErrorFactory.create(response.status, response.data, response.headers)
    raise error unless retry_status?(response.status, attempt)

    attempt = wait_and_bump(attempt, transport_request, "HTTP #{response.status}",
                            suggested_delay(error, attempt))
  rescue Errors::NetworkError => e
    raise Errors::ApiBrasilError.from(e) unless retry_error?(e, attempt)

    attempt = wait_and_bump(attempt, transport_request, e.message)
  end
end

#secret_keyString?

Returns:

  • (String, nil)


78
79
80
81
# File 'lib/api_brasil/core/http_client.rb', line 78

def secret_key
  value = @config[:secret_key]
  value.is_a?(String) ? value : nil
end

#to_configHash

Configuracao atual do cliente (util para clonar com outro device).

Returns:

  • (Hash)


106
107
108
# File 'lib/api_brasil/core/http_client.rb', line 106

def to_config
  @config.merge(transport: @transport)
end