Current section

Files

Jump to
keplars lib keplars http.ex
Raw

lib/keplars/http.ex

defmodule Keplars.Http do
@moduledoc false
alias Keplars.Error
@sdk_version Mix.Project.config()[:version]
def build_client(%Keplars{} = client_struct) do
middleware = [
{Tesla.Middleware.BaseUrl, client_struct.base_url},
{Tesla.Middleware.JSON, engine: Jason},
{Tesla.Middleware.Headers,
[
{"authorization", "Bearer #{client_struct.api_key}"},
{"user-agent", "keplars-elixir/#{@sdk_version}"}
]},
{Tesla.Middleware.Retry,
delay: client_struct.retry_delay_ms,
max_retries: client_struct.max_retries,
max_delay: 30_000,
should_retry: fn
{:ok, %{status: status}} when status in [429, 500, 502, 503, 504] -> true
{:ok, _} -> false
{:error, _} -> true
end}
]
adapter = {Tesla.Adapter.Hackney, [recv_timeout: client_struct.timeout_ms]}
Tesla.client(middleware, adapter)
end
def request(client_struct, method, path, body \\ nil) do
tesla_client = build_client(client_struct)
opts = if body, do: [body: body], else: []
result =
case method do
:get -> Tesla.get(tesla_client, path, opts)
:post -> Tesla.post(tesla_client, path, body || %{})
:put -> Tesla.put(tesla_client, path, body || %{})
:delete -> Tesla.delete(tesla_client, path, opts)
end
handle_response(result)
end
defp handle_response({:ok, %Tesla.Env{status: status, body: body, headers: headers}})
when status in 200..299 do
rate_limit_info = extract_rate_limit_info(headers)
{:ok, %{data: body, rate_limit_info: rate_limit_info}}
end
defp handle_response({:ok, %Tesla.Env{status: status, body: body, headers: headers}}) do
request_id = get_header(headers, "x-request-id")
message = get_in(body, ["message"]) || "Request failed with status #{status}"
error_code = get_in(body, ["code"])
error =
case status do
400 ->
Error.new(:validation_error, message,
status_code: 400,
request_id: request_id,
details: get_in(body, ["details"])
)
401 ->
Error.new(:authentication_error, message, status_code: 401, request_id: request_id)
403 ->
code = if error_code == "DOMAIN_NOT_VERIFIED", do: :domain_not_verified, else: :authorization_error
Error.new(code, message, status_code: 403, request_id: request_id)
404 ->
Error.new(:not_found, message, status_code: 404, request_id: request_id)
409 ->
Error.new(:conflict, message, status_code: 409, request_id: request_id)
429 ->
retry_after = get_header(headers, "retry-after") |> parse_integer(60)
Error.new(:rate_limit_exceeded, message, status_code: 429, request_id: request_id, retry_after: retry_after)
_ ->
Error.new(:internal_error, message, status_code: status, request_id: request_id)
end
{:error, error}
end
defp handle_response({:error, reason}) do
{:error, Error.new(:network_error, "Network error: #{inspect(reason)}")}
end
defp extract_rate_limit_info(headers) do
limit = get_header(headers, "x-ratelimit-limit") |> parse_integer(nil)
remaining = get_header(headers, "x-ratelimit-remaining") |> parse_integer(nil)
reset = get_header(headers, "x-ratelimit-reset") |> parse_integer(nil)
if limit && remaining && reset do
%{limit: limit, remaining: remaining, reset: reset}
else
nil
end
end
defp get_header(headers, name) do
case List.keyfind(headers, name, 0) do
{_, value} -> value
nil -> nil
end
end
defp parse_integer(nil, default), do: default
defp parse_integer(value, default) when is_binary(value) do
case Integer.parse(value) do
{int, _} -> int
:error -> default
end
end
defp parse_integer(value, _default) when is_integer(value), do: value
end