cover/Elixir.WalletIntegrations.Http.html

1 defmodule WalletIntegrations.Http do
2 @moduledoc """
3 HTTP client behaviour for provider adapter requests.
4
5 Isolates the HTTP transport layer for testability and provider substitution.
6 """
7
8 @callback request(
9 method :: :get | :post | :put | :delete,
10 url :: String.t(),
11 headers :: [{String.t(), String.t()}],
12 body :: String.t(),
13 opts :: keyword()
14 ) ::
15 {:ok, status_code :: non_neg_integer(), headers :: list(), body :: String.t()}
16 | {:error, reason :: term()}
17
18 @doc "Resolve the configured HTTP client module."
19 @spec client() :: module()
20 def client do
21
:-(
Application.get_env(:wallet_integrations, :http_client, WalletIntegrations.Http.HttcClient)
22 end
23 end
24
25 defmodule WalletIntegrations.Http.HttcClient do
26 @moduledoc """
27 OTP :httpc-backed HTTP client.
28
29 Uses :inets and :ssl from OTP stdlib — no external HTTP dependency required.
30 Configured with connect/read timeouts from RetryPolicy defaults.
31 """
32
33 @behaviour WalletIntegrations.Http
34
35 @impl true
36 def request(method, url, headers, body, opts) do
37 connect_timeout = Keyword.get(opts, :connect_timeout, 5_000)
38 recv_timeout = Keyword.get(opts, :recv_timeout, 15_000)
39
40 url_charlist = String.to_charlist(url)
41
42 httpc_headers =
43 Enum.map(headers, fn {k, v} ->
44 {String.to_charlist(k), String.to_charlist(v)}
45 end)
46
47 request =
48 case method do
49 :get ->
50 {url_charlist, httpc_headers}
51
52 _ ->
53 content_type =
54 headers
55 |> Enum.find_value("application/json", fn
56 {"content-type", v} -> v
57 {"Content-Type", v} -> v
58 _ -> false
59 end)
60
61 {url_charlist, httpc_headers, String.to_charlist(content_type),
62 String.to_charlist(body)}
63 end
64
65 http_opts = [
66 timeout: recv_timeout,
67 connect_timeout: connect_timeout,
68 ssl: [verify: :verify_peer, cacerts: :public_key.cacerts_get()]
69 ]
70
71 result =
72 try do
73 :httpc.request(method, request, http_opts, [])
74 rescue
75 e -> {:error, e}
76 catch
77 :exit, reason -> {:error, {:exit, reason}}
78 end
79
80 case result do
81 {:ok, {{_http_ver, status_code, _reason_phrase}, resp_headers, resp_body}} ->
82 headers_decoded = Enum.map(resp_headers, fn {k, v} ->
83 {to_string(k), to_string(v)}
84 end)
85 {:ok, status_code, headers_decoded, to_string(resp_body)}
86
87 {:error, reason} ->
88 {:error, reason}
89 end
90 end
91 end
Line Hits Source