Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

use Req as http client #81

Merged
merged 12 commits into from
May 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions .github/workflows/elixir-format.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
name: Check Elixir format

on: push

jobs:
build:
name: Check Elixir format
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4
- name: Set up Elixir
uses: erlef/setup-beam@v1
with:
elixir-version: "1.16"
otp-version: "26"

- name: Install dependencies
run: mix deps.get

- name: Check format
run: mix format --check-formatted
4 changes: 0 additions & 4 deletions .github/workflows/elixir.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,6 @@ jobs:
otp-version: "25"
- elixir-version: "1.13"
otp-version: "24"
- elixir-version: "1.12"
otp-version: "24"

steps:
- uses: actions/checkout@v4
Expand All @@ -43,5 +41,3 @@ jobs:
ELIXIR_SANITY_TEST_TOKEN: ${{ secrets.ELIXIR_SANITY_TEST_TOKEN }}
if: env.ELIXIR_SANITY_TEST_TOKEN
run: mix test --warnings-as-errors --only integration
- name: Check format
run: mix format --check-formatted
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]
### Changed
- (BREAKING) Switch HTTP client from `finch` to `req` (https://github.com/balexand/sanity/pull/81). This introduces the following breaking changes:
- The `headers` field of the `Sanity.Response` now returns a map instead of a list of tuples. See https://hexdocs.pm/req/changelog.html#change-headers-to-be-maps for details.
- The `:max_attempts` and `:retry_delay` options have been removed from `Sanity.request/2`. `Req` handles retries for us.
- The `source` field in the `Sanity.Error` exception may now contain a `Req.Response` struct instead of a `Finch.Response`.

## [1.3.0] - 2023-07-19
### Changed
Expand Down
85 changes: 21 additions & 64 deletions lib/sanity.ex
Original file line number Diff line number Diff line change
Expand Up @@ -36,32 +36,20 @@ defmodule Sanity do
doc: "Sanity dataset.",
required: true
],
finch_mod: [
type: :atom,
default: Finch,
doc: false
],
http_options: [
type: :keyword_list,
default: [receive_timeout: 30_000],
doc: "Options to be passed to `Finch.request/3`."
],
max_attempts: [
type: :pos_integer,
default: 1,
doc:
"Number of attempts to make before returning error. Requests receiving an HTTP status code of 4xx will not be retried."
doc: "Options to be passed to `Req.request/2`."
],
project_id: [
type: :string,
doc: "Sanity project ID.",
required: true
],
retry_delay: [
type: :pos_integer,
default: 1_000,
doc:
"Delay in ms to wait before retrying after an error. Applies if `max_attempts` is greater than `1`."
req_mod: [
type: :atom,
default: Req,
doc: false
],
token: [
type: :string,
Expand Down Expand Up @@ -226,7 +214,7 @@ defmodule Sanity do
[]

iex> Sanity.result!(%Sanity.Response{body: %{}, status: 200})
** (Sanity.Error) %Sanity.Response{body: %{}, headers: nil, status: 200}
** (Sanity.Error) %Sanity.Response{body: %{}, headers: %{}, status: 200}
"""
@spec result!(Response.t()) :: any()
def result!(%Response{body: %{"result" => result}}), do: result
Expand All @@ -251,54 +239,27 @@ defmodule Sanity do
) do
opts = NimbleOptions.validate!(opts, @request_options_schema)

finch_mod = Keyword.fetch!(opts, :finch_mod)
http_options = Keyword.fetch!(opts, :http_options)

url = "#{url_for(request, opts)}?#{URI.encode_query(query_params)}"

result =
Finch.build(method, url, headers(opts) ++ headers, body)
|> finch_mod.request(Sanity.Finch, http_options)

case {opts[:max_attempts], result} do
{_, {:ok, %Finch.Response{body: body, headers: headers, status: status}}}
Keyword.merge(Keyword.fetch!(opts, :http_options),
body: body,
headers: headers(opts) ++ headers,
method: method,
url: "#{url_for(request, opts)}?#{URI.encode_query(query_params)}"
)
|> Keyword.fetch!(opts, :req_mod).request()
|> case do
{:ok, %Req.Response{body: body, headers: headers, status: status}}
when status in 200..299 ->
{:ok, %Response{body: Jason.decode!(body), headers: headers, status: status}}
{:ok, %Response{body: body, headers: headers, status: status}}

{_, {:ok, %Finch.Response{body: body, headers: headers, status: status} = resp}}
{:ok, %Req.Response{body: %{} = body, headers: headers, status: status}}
when status in 400..499 ->
if json_resp?(headers) do
{:error, %Response{body: Jason.decode!(body), headers: headers, status: status}}
else
raise %Sanity.Error{source: resp}
end

{max_attempts, {_, error_or_response}} when max_attempts > 1 ->
Logger.warning(
"retrying failed request in #{opts[:retry_delay]}ms: #{inspect(error_or_response)}"
)

:timer.sleep(opts[:retry_delay])

opts =
opts
|> Keyword.update!(:max_attempts, &(&1 - 1))
|> Keyword.update!(:retry_delay, &(&1 * 2))
{:error, %Response{body: body, headers: headers, status: status}}

request(request, opts)

{_, {_, error_or_response}} ->
{_, error_or_response} ->
raise %Sanity.Error{source: error_or_response}
end
end

defp json_resp?(headers) do
Enum.any?(headers, fn
{"content-type", value} -> String.contains?(value, "application/json")
{_name, _value} -> false
end)
end

@doc """
Like `request/2`, but raises a `Sanity.Error` instead of returning and error tuple.

Expand Down Expand Up @@ -343,8 +304,7 @@ defmodule Sanity do
request_opts: [
type: :keyword_list,
required: true,
doc:
"Options to be passed to `request/2`. If `max_attempts` is omitted then it will default to `3`."
doc: "Options to be passed to `request/2`."
],
variables: [
type: {:map, {:or, [:atom, :string]}, :any},
Expand All @@ -371,10 +331,7 @@ defmodule Sanity do
@impl true
@spec stream(Keyword.t()) :: Enumerable.t()
def stream(opts) do
opts =
opts
|> NimbleOptions.validate!(@stream_options_schema)
|> Keyword.update!(:request_opts, &Keyword.put_new(&1, :max_attempts, 3))
opts = NimbleOptions.validate!(opts, @stream_options_schema)

case Map.take(opts[:variables], [:pagination_last_id, "pagination_last_id"]) |> Map.keys() do
[] -> nil
Expand Down
19 changes: 0 additions & 19 deletions lib/sanity/application.ex

This file was deleted.

2 changes: 1 addition & 1 deletion lib/sanity/error.ex
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ defmodule Sanity.Error do
Error that may occur while making a request to the Sanity API. The `source` field will be one of
the following:

* `%Finch.Response{}` - If response with an unsupported HTTP status (like 5xx) is received.
* `%Req.Response{}` - If response with an unsupported HTTP status (like 5xx) is received.
* `%Mint.TransportError{}` - If a network error such as a timeout occurred.
* `%Sanity.Response{}` - If a 4xx response is received during a call to `Sanity.request!/2`.
"""
Expand Down
2 changes: 1 addition & 1 deletion lib/sanity/response.ex
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
defmodule Sanity.Response do
@type t :: %Sanity.Response{}

defstruct [:body, :headers, :status]
defstruct body: %{}, headers: %{}, status: nil
end
7 changes: 3 additions & 4 deletions mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ defmodule Sanity.MixProject do
[
app: :sanity,
version: @version,
elixir: "~> 1.12",
elixir: "~> 1.13",
elixirc_paths: elixirc_paths(Mix.env()),
description: "Client library for Sanity CMS.",
start_permanent: Mix.env() == :prod,
Expand All @@ -28,17 +28,16 @@ defmodule Sanity.MixProject do
# Run "mix help compile.app" to learn about applications.
def application do
[
extra_applications: [:logger],
mod: {Sanity.Application, []}
extra_applications: [:logger]
]
end

# Run "mix help deps" to learn about dependencies.
defp deps do
[
{:finch, "~> 0.5"},
{:jason, "~> 1.2"},
{:nimble_options, "~> 0.5 or ~> 1.0"},
{:req, "~> 0.4"},

# dev/test
{:dialyxir, "~> 1.0", only: [:dev], runtime: false},
Expand Down
2 changes: 2 additions & 0 deletions mix.lock
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@
"mint": {:hex, :mint, "1.6.0", "88a4f91cd690508a04ff1c3e28952f322528934be541844d54e0ceb765f01d5e", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1 or ~> 0.2.0", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "3c5ae85d90a5aca0a49c0d8b67360bbe407f3b54f1030a111047ff988e8fefaa"},
"mox": {:hex, :mox, "1.1.0", "0f5e399649ce9ab7602f72e718305c0f9cdc351190f72844599545e4996af73c", [:mix], [], "hexpm", "d44474c50be02d5b72131070281a5d3895c0e7a95c780e90bc0cfe712f633a13"},
"nimble_options": {:hex, :nimble_options, "1.1.0", "3b31a57ede9cb1502071fade751ab0c7b8dbe75a9a4c2b5bbb0943a690b63172", [:mix], [], "hexpm", "8bbbb3941af3ca9acc7835f5655ea062111c9c27bcac53e004460dfd19008a99"},
"nimble_ownership": {:hex, :nimble_ownership, "0.3.1", "99d5244672fafdfac89bfad3d3ab8f0d367603ce1dc4855f86a1c75008bce56f", [:mix], [], "hexpm", "4bf510adedff0449a1d6e200e43e57a814794c8b5b6439071274d248d272a549"},
"nimble_parsec": {:hex, :nimble_parsec, "1.4.0", "51f9b613ea62cfa97b25ccc2c1b4216e81df970acd8e16e8d1bdc58fef21370d", [:mix], [], "hexpm", "9c565862810fb383e9838c1dd2d7d2c437b3d13b267414ba6af33e50d2d1cf28"},
"nimble_pool": {:hex, :nimble_pool, "1.1.0", "bf9c29fbdcba3564a8b800d1eeb5a3c58f36e1e11d7b7fb2e084a643f645f06b", [:mix], [], "hexpm", "af2e4e6b34197db81f7aad230c1118eac993acc0dae6bc83bac0126d4ae0813a"},
"req": {:hex, :req, "0.4.14", "103de133a076a31044e5458e0f850d5681eef23dfabf3ea34af63212e3b902e2", [:mix], [{:aws_signature, "~> 0.3.2", [hex: :aws_signature, repo: "hexpm", optional: true]}, {:brotli, "~> 0.3.1", [hex: :brotli, repo: "hexpm", optional: true]}, {:ezstd, "~> 1.0", [hex: :ezstd, repo: "hexpm", optional: true]}, {:finch, "~> 0.17", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mime, "~> 1.6 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:nimble_csv, "~> 1.0", [hex: :nimble_csv, repo: "hexpm", optional: true]}, {:nimble_ownership, "~> 0.2.0 or ~> 0.3.0", [hex: :nimble_ownership, repo: "hexpm", optional: false]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "2ddd3d33f9ab714ced8d3c15fd03db40c14dbf129003c4a3eb80fac2cc0b1b08"},
"telemetry": {:hex, :telemetry, "1.2.1", "68fdfe8d8f05a8428483a97d7aab2f268aaff24b49e0f599faa091f1d4e7f61c", [:rebar3], [], "hexpm", "dad9ce9d8effc621708f99eac538ef1cbe05d6a874dd741de2e689c47feafed5"},
}
6 changes: 5 additions & 1 deletion test/integration_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,11 @@ defmodule Sanity.MutateIntegrationTest do
)
|> Sanity.request(config)

assert {:ok, %Response{body: %{"documents" => [%{"title" => "product x"}]}}} =
assert {:ok,
%Response{
body: %{"documents" => [%{"title" => "product x"}]},
headers: %{"content-type" => ["application/json; charset=utf-8"]}
}} =
Sanity.doc(id) |> Sanity.request(config)
end

Expand Down
Loading