Packages

Elixir API to communicate with Matrix Synapse

Current section

Files

Jump to
ex_matrix_api lib util http_client httpoison.ex
Raw

lib/util/http_client/httpoison.ex

defmodule ExMatrixApi.Util.HttpClient.HTTPoison do
@moduledoc """
Behaviour wrapper for HTTPoison HTTP client.
"""
alias ExMatrixApi.Behaviour.HttpClient
alias HttpClient.HttpError
alias HTTPoison.{Response, Error}
@successful_http_codes [200, 201, 202, 204]
@json_headers ["application/json", "application/json; charset=utf-8"]
@type http_header :: {String.t(), String.t()}
@behaviour HttpClient
@impl HttpClient
@spec get(String.t(), [http_header], Keyword.t()) :: HttpClient.response()
def get(url, headers \\ [], opts \\ []) do
headers = default_headers(headers)
HTTPoison.get(url, headers, opts)
|> process_response()
end
@impl HttpClient
@spec put(String.t(), HttpClient.payload(), [http_header], Keyword.t()) :: HttpClient.response()
def put(url, payload, headers \\ [], opts \\ []) when is_map(payload) or is_binary(payload) do
headers = default_headers(headers)
case prepare_payload(payload) do
{:ok, body} ->
HTTPoison.put(url, body, headers, opts)
|> process_response()
_ ->
{:error, HttpError.new("invalid payload")}
end
end
@impl HttpClient
@spec post(String.t(), HttpClient.payload(), [http_header], Keyword.t()) ::
HttpClient.response()
def post(url, payload, headers \\ [], opts \\ []) when is_map(payload) or is_binary(payload) do
headers = default_headers(headers)
case prepare_payload(payload) do
{:ok, body} ->
HTTPoison.post(url, body, headers, opts)
|> process_response()
_ ->
{:error, HttpError.new("invalid payload")}
end
end
@impl HttpClient
@spec delete(String.t(), [http_header], Keyword.t()) :: HttpClient.response()
def delete(url, headers \\ [], opts \\ []) do
headers = default_headers(headers)
HTTPoison.delete(url, headers, opts)
|> process_response()
end
@spec process_response({:ok, Response.t()} | {:error, Error.t()}) :: HttpClient.response()
defp process_response({:ok, %Response{status_code: code, body: ""}})
when code in @successful_http_codes do
{:ok, nil}
end
defp process_response({:ok, %Response{status_code: code, body: body, headers: headers}})
when code in @successful_http_codes do
if is_json_response?(headers) do
with {:error, _} <- Jason.decode(body) do
{:error, HttpError.new("invalid response data")}
end
else
{:ok, body}
end
end
defp process_response({:ok, %Response{status_code: code, body: ""}}) do
{:error, HttpError.new("unexpected empty response", code)}
end
defp process_response({:ok, %Response{status_code: code, body: body, headers: headers}}) do
if is_json_response?(headers) do
case Jason.decode(body) do
{:ok, errors} ->
{:error, HttpError.new(body, code, errors)}
_ ->
{:error, HttpError.new("invalid error response", code)}
end
else
{:error, HttpError.new(body, code)}
end
end
defp process_response({:error, %Error{reason: reason}}) do
{:error, HttpError.new(reason)}
end
defp process_response(_) do
{:error, HttpError.new("invalid HTTP response")}
end
@spec is_json_response?([http_header]) :: boolean()
defp is_json_response?(headers) do
Enum.any?(headers, fn {_header, value} -> value in @json_headers end)
end
@spec default_headers([http_header]) :: [http_header]
defp default_headers(headers) when length(headers) == 0 do
[{"Content-type", "application/json; charset=utf-8"}]
end
defp default_headers(headers), do: headers
@spec prepare_payload(HttpClient.payload()) :: {:ok, binary()} | {:error, any()}
defp prepare_payload(payload) when is_binary(payload), do: {:ok, payload}
defp prepare_payload(payload), do: Jason.encode(payload)
end