Current section
Files
Jump to
Current section
Files
lib/graphql/client.ex
defmodule BridgeEx.Graphql.Client do
@moduledoc """
Graphql client for BridgeEx.
"""
alias BridgeEx.Graphql.Utils
alias BridgeEx.Graphql.Retry
@type bridge_response ::
{:ok, term()}
| {:error, {:bad_response, integer()}}
| {:error, {:http_error, String.t()}}
| {:error, list()}
@http_options timeout: 1_000, recv_timeout: 16_000
@http_headers %{
"Content-type" => "application/json"
}
@doc """
Calls a GraphQL endpoint
## Parameters
* `url`: URL of the endpoint.
* `query`: Graphql query or mutation.
* `variables`: variables for Graphql query or mutation.
* `opts`: various options.
## Options
* `options`: extra HTTP options to be passed to Telepoison.
* `headers`: extra HTTP headers.
* `encode_variables`: whether to JSON encode variables or not.
* `retry_options`: configures retry attempts. Takes the form of `[max_retries: 1, timing: :exponential]`
* `log_options`: configures logging on errors. Takes the form of `[log_query_on_error: false, log_response_on_error: false]`.
"""
@spec call(
url :: String.t(),
query :: String.t(),
variables :: map(),
opts :: Keyword.t()
) :: bridge_response()
def call(
url,
query,
variables,
opts
) do
encode_variables = Keyword.get(opts, :encode_variables, false)
http_options = Keyword.merge(@http_options, Keyword.get(opts, :options, []))
http_headers = Map.merge(@http_headers, Keyword.get(opts, :headers, %{}))
log_options = Keyword.merge(log_options(), Keyword.get(opts, :log_options))
retry_options =
opts
|> Keyword.get(:retry_options, [])
|> then(
&Keyword.merge(
[
delay: 100,
max_retries: 0,
policy: fn _ -> true end,
timing: :exponential
],
&1
)
)
variables =
if encode_variables,
do: Jason.encode!(variables),
else: variables
%{query: String.trim(query), variables: variables}
|> Jason.encode()
|> Noether.Either.bind(
&Retry.retry(
&1,
fn query ->
url
|> Telepoison.post(query, http_headers, http_options)
|> Utils.decode_http_response(query, log_options)
|> Utils.parse_response()
end,
retry_options
)
)
end
@doc """
Formats a GraphQL query response to make it Absinthe compliant
"""
@spec format_response(%{atom() => any()} | [%{atom() => any()}]) ::
%{atom() => any()} | [%{atom() => any()}]
def format_response(response) when is_list(response) do
Enum.map(response, &format_response(&1))
end
def format_response(response) when is_map(response), do: Utils.normalize_inner_fields(response)
def format_response(response), do: response
defp log_options do
Application.get_env(:bridge_ex, :log_options,
log_query_on_error: false,
log_response_on_error: false
)
end
end