Skip to content
Permalink
Branch: master
Find file Copy path
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
115 lines (94 sloc) 3.79 KB
if Code.ensure_loaded?(:hackney) do
defmodule ScrapyCloudEx.HttpAdapters.Default do
@moduledoc """
A default implementation of the `ScrapyCloudEx.HttpAdapter` behaviour.
Depends on [`:hackney`](https://hexdocs.pm/hackney).
"""
@behaviour ScrapyCloudEx.HttpAdapter
require Logger
alias ScrapyCloudEx.HttpAdapter
alias ScrapyCloudEx.HttpAdapter.{Helpers, RequestConfig, Response}
@impl ScrapyCloudEx.HttpAdapter
@spec request(RequestConfig.t()) :: {:ok, Response.t()} | ScrapyCloudEx.tagged_error()
def request(%RequestConfig{
method: method,
api_key: key,
url: url,
headers: headers,
body: body,
opts: opts
}) do
case make_request(method, key, url, headers, body, opts) do
{:ok, _} = result -> result
error -> {:error, error}
end
end
@impl ScrapyCloudEx.HttpAdapter
@spec handle_response(Response.t(), Keyword.t()) ::
{:ok, any} | {:error, HttpAdapter.error_map()}
def handle_response(%Response{status: status, headers: headers, body: body}, opts) do
format = Helpers.get_format(headers)
Logger.debug("decode format set to #{format}")
decoder_fun = opts |> Keyword.fetch!(:decoder) |> Helpers.get_decoder_fun()
body
|> decode_body(decoder_fun, format)
|> case do
{:ok, decoded_body} -> format_api_result(status, decoded_body)
{:error, message} -> format_response_error(status, message)
end
end
@spec make_request(atom, String.t(), String.t(), [tuple], [tuple], Keyword.t()) ::
{:ok, Response.t()} | ScrapyCloudEx.tagged_error()
defp make_request(type, api_key, url, headers, body, opts) do
result =
:hackney.request(type, url, headers, format_body(body), [
{:basic_auth, {api_key, ""}} | opts
])
with {:ok, status, headers, client} <- result,
{:ok, body} <- :hackney.body(client) do
{:ok, %Response{status: status, headers: headers, body: body}}
else
{:error, error} -> error
end
end
@spec decode_body(String.t(), ScrapyCloudEx.Decoder.decoder_function(), atom) ::
{:ok, any} | ScrapyCloudEx.tagged_error()
defp decode_body(body, decoder_fun, format) do
body
|> decoder_fun.(format)
|> case do
{:error, _} = error -> error
{:ok, _} = result -> result
end
end
@spec format_api_result(integer, String.t()) :: {:ok, any} | {:error, HttpAdapter.error_map()}
defp format_api_result(200, body), do: {:ok, body}
defp format_api_result(status, %{message: message}),
do: format_response_error(status, message)
defp format_api_result(status, body), do: format_response_error(status, body)
@spec format_message(any) :: any
defp format_message(message) when is_binary(message), do: message |> String.trim()
defp format_message(%{"message" => message}), do: format_message(message)
defp format_message(message), do: message
@spec format_response_error(integer, any) :: {:error, HttpAdapter.error_map()}
defp format_response_error(status, message) do
{:error, %{status: status, message: format_message(message)}}
end
@spec format_body(list) :: String.t() | {:form, list}
defp format_body([]), do: ""
defp format_body(list) when is_list(list), do: {:form, list}
end
else
defmodule ScrapyCloudEx.HttpAdapters.Default do
@moduledoc false
@behaviour ScrapyCloudEx.HttpAdapter
@spec request(any()) :: no_return()
def request(_), do: raise()
@spec handle_response(any(), any()) :: no_return()
def handle_response(_, _), do: raise()
defp raise() do
raise RuntimeError,
message: ":hackney is required to use #{__MODULE__}, refer to instructions in README"
end
end
end
You can’t perform that action at this time.