Skip to content

feat: introduce pluggable HTTP client behaviour#150

Merged
achempion merged 2 commits into
elixir-waffle:masterfrom
klacointe:feat/pluggable-http-client-adapter
Jul 2, 2026
Merged

feat: introduce pluggable HTTP client behaviour#150
achempion merged 2 commits into
elixir-waffle:masterfrom
klacointe:feat/pluggable-http-client-adapter

Conversation

@klacointe

@klacointe klacointe commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Follow up of Add pluggable HTTP client behaviour with Hackney and Finch implementations #148
  • Adds Waffle.HTTPClient behaviour with a single get/3 callback
  • Adds Waffle.HTTPClient.Hackney as the default implementation (existing behaviour, just moved into a dedicated module)
  • Refactors Waffle.File to dispatch through the configured :http_client instead of calling hackney directly; retry logic stays client-agnostic
  • Documents how to implement a custom HTTP client adapter

Usage

Configure a custom client:

config :waffle, :http_client, MyApp.HTTPClient

Implement the behaviour:

defmodule MyApp.HTTPClient do
  @behaviour Waffle.HTTPClient

  @impl Waffle.HTTPClient
  def get(url, headers, options) do
    # ...
  end
end

Error shape changes

Error case Before After
Connect timeout {:error, :timeout} {:error, :timeout} — unchanged
Receive timeout {:error, :recv_timeout} {:error, :recv_timeout} — unchanged
503 out of retries {:error, {:waffle_hackney_error, {:ok, 503, headers, ref}}} {:error, :service_unavailable}
Non-2xx response {:error, {:waffle_hackney_error, {:ok, status, headers, ref}}} {:error, {:http_error, status}} e.g. {:error, {:http_error, 404}}

The previous 503 and non-2xx shapes leaked a closed hackney client_ref inside the error tuple.

Notes

#75

@klacointe

Copy link
Copy Markdown
Contributor Author

@achempion when you'll be ok with this one I'll propose the Finch adapter.

@klacointe klacointe marked this pull request as draft June 17, 2026 19:44
@klacointe klacointe marked this pull request as ready for review June 17, 2026 19:52
@klacointe klacointe force-pushed the feat/pluggable-http-client-adapter branch from 9c5b76f to df970da Compare June 17, 2026 19:54
- Add Waffle.HTTPClient behaviour with a single get/3 callback
- Add Waffle.HTTPClient.Hackney as the default implementation
- Refactor Waffle.File to dispatch through the configured :http_client
  instead of calling hackney directly; retry logic stays client-agnostic
- Document how to implement a custom HTTP client adapter
@klacointe klacointe force-pushed the feat/pluggable-http-client-adapter branch from df970da to e6a6bd6 Compare June 17, 2026 20:41

@achempion achempion left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looking great, left couple of minor comments before merging

Comment thread lib/waffle/file.ex
Comment thread lib/waffle/file.ex Outdated
Comment thread test/file_test.exs Outdated
Comment thread lib/waffle/http_client/hackney.ex
@fastjames

Copy link
Copy Markdown
Contributor

I am excited to see this work going into waffle, and I'm happy to help push it forward if needed.

@klacointe

Copy link
Copy Markdown
Contributor Author

I am excited to see this work going into waffle, and I'm happy to help push it forward if needed.

I'm on holidays for the 2 following weeks and will not have time to work on this before 😕

klacointe added a commit to klacointe/waffle that referenced this pull request Jul 1, 2026
- Share a normalize_error/1 helper between the connect/response error
  path and the read_body error path in Waffle.HTTPClient.Hackney, so a
  timeout while streaming the response body now normalizes to
  {:error, :timeout} / {:error, :recv_timeout} instead of being wrapped
  as {:error, {:http_error, reason}}
- This makes body-read timeouts retryable, matching connection timeouts
- Replace assert called(...) with assert_called_exactly(..., 3) in the
  retry tests so the asserted call count matches the inline comment
- Add coverage for both timeout error shapes during body read

Addresses review feedback on elixir-waffle#150
- Share a normalize_error/1 helper between the connect/response error
  path and the read_body error path in Waffle.HTTPClient.Hackney, so a
  timeout while streaming the response body now normalizes to
  {:error, :timeout} / {:error, :recv_timeout} instead of being wrapped
  as {:error, {:http_error, reason}}
- This makes body-read timeouts retryable, matching connection timeouts
- Inline retry_or_error/5 into request/4's case clause since it was a
  5-arg helper used from a single call site
- Replace assert called(...) with assert_called_exactly(..., 3) in the
  retry tests so the asserted call count matches the inline comment
- Add coverage for both timeout error shapes during body read

Addresses review feedback on elixir-waffle#150
@klacointe klacointe force-pushed the feat/pluggable-http-client-adapter branch from 3f1d66d to d068c11 Compare July 1, 2026 17:19
@klacointe klacointe requested a review from achempion July 1, 2026 17:20
@klacointe

Copy link
Copy Markdown
Contributor Author

I am excited to see this work going into waffle, and I'm happy to help push it forward if needed.

I'm on holidays for the 2 following weeks and will not have time to work on this before 😕

I finally found some time.

@achempion

Copy link
Copy Markdown
Member

Looking great, thanks for the PR

I'll publish new minor release soon.

@achempion achempion merged commit 780e151 into elixir-waffle:master Jul 2, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants