Packages

Httpoison boosted with telemetry, mox and ex_rated.

Retired package: Deprecated - no longer maintained

Current section

Files

Jump to
http_client lib client.ex
Raw

lib/client.ex

defmodule Client do
@moduledoc """
The client module contains two functions of note, `do_request/6` and
`do_request!/6` which perform HTTP actions as well as encoding and decoding
data and setting headers involved in the request.
## Actions
Actions are the part that actually make the HTTP Request, if that is what you
choose to do with this library. It is failry generic. Some actions are provided.
### Provided Actions
- `Client.get/3` / `Client.get!/3`
- `Client.post/3` / `Client.post!/3`
- `Client.patch/3` / `Client.patch!/3`
- `Client.put/3` / `Client.put!/3`
- `Client.delete/3` / `Client.delete!/3`
The provided actions are all simple wrappers around HTTPoison to make the
arguments resemble what the callback requires in `do_request/6` and
`do_request!/6`
"""
alias HTTPoison.Response
defmodule ResponseNot200Error do
defexception [:message]
def exception(%Response{status_code: status}) do
msg = "status code: #{status}"
{:error, %__MODULE__{message: msg}}
end
def exception(value) do
msg = "expected %HTTPoison.Response{}, got: #{inspect value}"
{:error, %__MODULE__{message: msg}}
end
end
def get(href, payload, headers),
do: HTTPoison.get(href, headers, payload)
def post(href, payload, headers),
do: HTTPoison.post(href, payload, headers)
def patch(href, payload, headers),
do: HTTPoison.patch(href, payload, headers)
def put(href, payload, headers),
do: HTTPoison.put(href, payload, headers)
def delete(href, _payload, headers),
do: HTTPoison.delete(href, headers)
def get!(href, payload, headers),
do: HTTPoison.get!(href, headers, payload)
def post!(href, payload, headers),
do: HTTPoison.post!(href, payload, headers)
def patch!(href, payload, headers),
do: HTTPoison.patch!(href, payload, headers)
def put!(href, payload, headers),
do: HTTPoison.put!(href, payload, headers)
def delete!(href, _payload, headers),
do: HTTPoison.delete!(href, headers)
@doc """
Sequences calls to encoder, action, and decoder to perform HTTPoison requests.
It is important to understand how this client works in order to properly use it.
It provides two implementations of a single function `do_request/6`, which takes
the arguments explained below:
| Argument | description |
| ---------- | ----------- |
| `href` | The URL of the resource to be queried |
| `payload` | A Map, Struct, or List to be sent to the server |
| `headers` | The headers to be sent with the query |
| `encoder` | This is an encoder from the Client package, a list of encoders is provided below |
| `decoder` | This is a decoder from the Client package, a list of decoders is proved below |
| `action` | This is an HTTPoison verb. Usage defined below |
## Notes
When using `do_request/6`, your actions all need to return a tuple of the format
`{:ok, data}` or `{:error, reason}`, any other formats will not be properly
handled by `do_request/6`.
## Examples
data = Client.do_request(
"https://httpbin.org",
%{"key" => "value", "key2" => ["value1", "value2"]},
%{"Header" => "Header/Value"},
Client.Encoders.JSON,
Client.Decoders.JSON,
&Client.post(&1, &2, &3)
)
assert data == {
:ok,
%{
"args" => %{},
"data" => "{\\"key2\\":[\\"value1\\",\\"value2\\"],\\"key\\":\\"value\\"}",
"files" => %{},
"form" => %{},
"headers" => %{
"Accept" => "application/json",
"Content-Length" => "42",
"Content-Type" => "application/json",
"Header" => "Header/Value",
"Host" => "httpbin.org",
"User-Agent" => "hackney/1.6.1"
},
"json" => %{
"key" => "value",
"key2" => ["value1", "value2"]
},
"origin" => "127.0.0.1",
"url" => "https://httpbin.org/post"
}
}
iex> Client.do_request("a.com", %{"key" => "value"}, %{}, Client.Encoders.JSON, Client.Decoders.JSON, fn _href, payload, _headers -> {:ok, %HTTPoison.Response{status_code: 200, body: payload}} end)
{:ok, %{"key" => "value"}}
"""
def do_request(href, payload, headers, encoder, decoder, action) do
with {:ok, payload} <- encoder.encode(payload) do
headers = encoder.headers
|> Map.merge(decoder.headers)
|> Map.merge(headers)
with {:ok, response} <- action.(href, payload, headers),
do: handle_response(response, decoder)
end
end
@doc """
Aggressive version of `do_request/6`. Aggressive means raising errors rather
than returning error structs.
## Notes
When using `do_request!/6`, your actions must all return `data` directly,
outside of the tuple used in the safer version. The reason for this is we expect
errors in this case to be raised rather than returned.
## Examples
iex> Client.do_request!("a.com", %{"key" => "value"}, %{}, Client.Encoders.JSON, Client.Decoders.JSON, fn _href, payload, _headers -> %HTTPoison.Response{status_code: 200, body: payload} end)
%{"key" => "value"}
"""
def do_request!(href, payload, headers, encoder, decoder, action) do
payload = encoder.encode!(payload)
headers = encoder.headers
|> Map.merge(decoder.headers)
|> Map.merge(headers)
href
|> action.(payload, headers)
|> handle_response!(decoder)
end
defp handle_response(%Response{status_code: status, body: body}=response, decoder) do
cond do
status in [200, 201] ->
decoder.decode(body)
status == 204 ->
{:ok, :no_content}
true ->
ResponseNot200Error.exception(response)
end
end
defp handle_response!(%Response{status_code: status, body: body}=response, decoder) do
cond do
status in [200, 201] ->
decoder.decode!(body)
status == 204 ->
{:ok, :no_content}
true ->
with {:error, error} <- ResponseNot200Error.exception(response),
do: raise error
end
end
end