Skip to content

Releases: salasebas/better-fetch-rs

v0.6.1

31 May 01:37

Choose a tag to compare

Added

  • LoggerPlugin verbose tracingheader_count field on request and response log events when verbose mode is enabled.

Fixed

  • Path params — substitute placeholders per /-segment so names that are prefixes of another (e.g. :user vs :user_id) no longer corrupt later segments.
  • Retry-After — honor server delays exactly (jitter no longer shortens them); parse HTTP-date values (RFC 7231) in addition to delay-seconds; past dates retry immediately.
  • SSE — normalize LF, CR, and CRLF line endings (including CRLF split across chunks); strip only one optional space after field colons per the SSE spec.
  • OpenAPI export (feature openapi) — Option<T> fields emit OpenAPI 3.0 nullable: true instead of draft-07 "null" typing.
  • schema-validate buildsPreparedExecution::route_path is compiled only when the feature is enabled.

Changed

  • SchemaRegistry — O(1) route lookups via an internal index (first registration still wins on duplicates).
  • request_pipeline — move headers and URL into retry stub responses instead of cloning.

v0.6.0

28 May 07:25

Choose a tag to compare

Added

  • RequestBuilder::disable_validation (feature schema-validate) — per-request skip of registry JSON Schema validation (request body, query, params, and response on send() or StreamingResponse::collect()). Matches TypeScript disableValidation. Strict ensure_route is unchanged. Garde (validate feature) is separate — use validate_response(false) on send_json_validated. Also on EndpointRequestBuilder.
  • ClientBuilder::wire_concurrency_limit — declare the Tower ConcurrencyLimitLayer limit so build() can warn when it matches max_in_flight.
  • Concurrency build warningstracing::warn! when max_in_flight is combined with a custom transport stack (backend, http_service, transport_stack, etc.), with a sharper message when client and wire limits are equal.
  • Cargo feature sseSseDecoder, parse_sse_events, StreamingResponse::sse_events / read_sse_events (included in full). Core streaming (send_stream, collect, etc.) is unchanged without this feature.
  • Edge-case integration tests — non-replayable upload streams on retry; throw_on_error with large streaming bodies (peek vs drain / BodyTooLarge); plugin init vs hook order; path/query URL regressions; Tower buffered vs streaming transport_stack; cancellation mid-collect() and during retry. See testing — edge-case matrix.

Changed

  • Breaking: SSE helpers are no longer built by default. Enable features = ["sse"] if you use .sse_events(), .read_sse_events(), or better_fetch::sse::* (they were always available in 0.5.0 without a separate feature flag).
  • request_pipeline — internal refactor only: shared stream body wrapping, peek limit helper, and transport-error handling in the HTTP loop (no public API change).

Migration (0.5.x → 0.6.0)

# Default features unchanged; add "sse" if you use SSE helpers.
better-fetch = { version = "0.6", features = ["json", "sse"] }

# Optional: declare Tower wire limit for build-time concurrency diagnostics.
# .wire_concurrency_limit(32)  # when using ConcurrencyLimitLayer::new(32) on transport_stack

If you use strict JSON Schema validation and need to skip it for one call (e.g. debugging or gradual rollout), use .disable_validation(true) on that request instead of turning off strict mode on the whole registry.

v0.5.0

28 May 06:41

Choose a tag to compare

Added

  • Battle testing — expanded CI (feature matrix, MSRV, docs, cargo-deny, cargo-semver-checks, weekly fuzz), tests/url_edge_tests.rs, proptest coverage for URL/query/path, criterion benches, and fuzz targets under fuzz/.

Changed

  • MSRV — raised to 1.88 (matches current dependency floor, e.g. cookie_store).
  • CI — dropped Miri job (low signal with workspace unsafe_code = "forbid").

Fixed

  • max_response_bytes on buffered responsesClientBuilder::max_response_bytes and per-request .max_response_bytes() now apply to send() and send_json(), not only send_stream() / collect(). Bodies are read via the streaming transport when a cap is set; oversized Content-Length headers fail fast with Error::BodyTooLarge.
  • Streaming non-2xx bodiessend_stream() preserves the full response body when status is not 2xx and throw() is not set.

v0.4.0

28 May 02:39

Choose a tag to compare

Added

  • Streaming uploadsRequestBuilder::body_stream and Error::NonReplayableBody when retry cannot replay the body.
  • Tower dual stackClientBuilder::transport_stack wires separate buffered and streaming transports; use this when middleware must apply to send_stream().
  • Strict JSON Schema (feature schema-validate) — optional validation of request/response JSON, query, and path params when the registry is strict; stream responses validate on collect().
  • Garde validation (feature validate) — json_validated, query_validated, with_headers_validated on builders.
  • Typed endpoint macros#[derive(Endpoint)] with #[param], #[query_field], #[query], #[endpoint(register)], and NeedsBody for POST bodies.
  • SSE — incremental SseDecoder and StreamingResponse::sse_events().
  • API helpersinto_api_result / send_api, ResponseBodyKind, RecordingBackend for tests, better_fetch::prelude, RequestBuilder::base_url.
  • Featuresfull, miette (DiagnosticError), otel (OpenTelemetry re-exports for tracing subscribers).
  • ErrorsQuerySerialize, RequestValidation, SchemaValidation, InvalidHeaderName / InvalidHeaderValue, MissingPathParam, SchemaRoute, Transport::source, and clearer Io / Config.
  • Docstesting, observability, ROADMAP; publishing notes in README.

Changed

  • Breaking: typed .query(...) and EndpointQuery::apply_query return Result; serialization errors become Error::QuerySerialize.
  • Breaking: ClientConfig::hooks is private; use ClientConfig::effective_hooks().
  • Breaking: EndpointRequestBuilder implements Deref only (no DerefMut); use typed .query(MyQuery)? instead of stringly .query("key", "value") on ready builders.
  • Breaking: transport_stack takes (buffered, streaming) and returns two boxed services.
  • Breaking: impl Endpoint requires Body and Headers associated types (often ()).
  • Breaking: Error::Transport carries an optional source; Error::RetryExhausted::last is Option<Box<Error>>.
  • Hooks on streamson_error runs for failed send_stream() responses even without throw_on_error, matching buffered behavior.
  • http_service / http_service_boxed — documented: Tower layers apply to buffered send() only; streaming still uses reqwest unless you use transport_stack.
  • LoggerPlugin — richer tracing (http.request / http.response, retry attempt, error body preview).
  • Retry — shared buffered/streaming retry loop; aligned backoff, cancellation, and throw_on_error body handling.

Fixed

  • on_request hooks — changes to RequestContext::body are applied to the outgoing request.
  • throw_on_error + send_stream — HTTP errors include a peeked body when possible.
  • Query in path templates?foo=bar in the path merges with builder query (builder wins).
  • Stream limitsmax_response_bytes applies to collect(), stream_to_file, and SSE helpers when set on the client or request.

Migration (0.3.x → 0.4.0)

  • Add ? after typed .query(...).
  • Replace config().hooks with config().effective_hooks().
  • Replace into_inner() with Deref or delegated builder methods.
  • Do not use stringly .query("k", "v") on client.call::<E>() after params are set.
  • For Tower on both send() and send_stream(), use transport_stack, not http_service alone.
  • Add type Body = (); type Headers = (); to manual Endpoint impls if missing.
  • Multipart or streaming upload retry may return Error::NonReplayableBody instead of a generic error.

v0.3.0

27 May 21:08

Choose a tag to compare

Changed

  • Typed endpoints (breaking)EndpointRequestBuilder from client.call::<E>() no longer has .param() / .query_pair(). Use .params(E::Params) and .query(E::Query) with typed structs (define_params!, impl_serde_endpoint_query!, or proc-macros). When E::Params is not (), .params() is required before .send_json(). Untyped client.get() / .post() are unchanged.
  • Error::Transport — now { kind: TransportKind, message: String } instead of a plain String. Use Error::transport or Error::transport_message to construct transport errors.
  • Error::RetryExhausted::last — now Option<Box<Error>> instead of Option<String> (preserves structured last failure).
  • Transport mappingmap_transport_error classifies reqwest failures into TransportKind (Connect, Body, Decode, Redirect, Request, Builder, Upgrade, Other) in addition to the existing Timeout variant.

Added

  • define_params!, extended endpoint!, impl_serde_endpoint_query! for typed path/query without proc-macros.
  • Type-state builderNeedsParams vs Ready on EndpointRequestBuilder.
  • Proc-macros (feature macros) — EndpointParamsDerive, EndpointQueryDerive with path validation.
  • serialize_to_query_map — serde structs to query params for OpenAPI/runtime parity.
  • Streaming APIRequestBuilder::send_stream, StreamingResponse, HttpBackend::execute_stream, and BodyStream for incremental response bodies. max_response_bytes ends the stream after Error::BodyTooLarge (no infinite error loop).
  • Streaming hookson_response_stream, on_success_stream, StreamingResponseContext, StreamingResponseMeta, StreamingSuccessContext (metadata only; buffered on_response / on_success are not called on the streaming path).
  • Streaming retry peek — custom RetryPolicy::with_should_retry on send_stream can inspect up to retry_body_peek_bytes (default 64 KiB) of the body; status-only retries do not read the body.
  • Streaming cancellationCancelBodyStream registers the cancellation waker while the inner body read is pending.
  • Tower streamingServiceBackend delegates execute_stream to a ReqwestBackend sharing the same reqwest::Client as the Tower stack (transport_stack wires this automatically).
  • Error::BodyTooLarge — when a streaming response exceeds max_response_bytes.
  • ClientBuilder::max_response_bytes / RequestBuilder::max_response_bytes — optional size cap on the streaming path.
  • ClientBuilder::retry_body_peek_bytes / RequestBuilder::retry_body_peek_bytes — cap for retry predicate body peek on streams.
  • Exampleexamples/streaming.rs; tests streaming_tests and streaming_tower_tests (feature tower).
  • docs.rspackage.metadata.docs.rs with all-features, doc_cfg, and rustdoc example scraping.
  • TransportKind — public enum for transport failure categories.
  • Error::transport_kind, Error::transport_detail, Error::is_transport, Error::is_timeout, Error::retry_exhausted_last.
  • CI — minimal feature-set checks (json + rustls-tls / native-tls, multipart).
  • Release automation.github/workflows/release.yml (tag v* → crates.io trusted publishing + GitHub Release from CHANGELOG.md).

Migration (0.2.x → 0.3.0)

  • client.call::<E>() — replace .param("id", n) with .params(E::Params { ... }) (or define_params! / proc-macros). See README typed endpoint section.
  • send() / send_json() — unchanged; still fully buffered.
  • Custom HttpBackend — implement execute_stream (return an error if unsupported).
  • Tower ServiceBackend — implement execute_stream on custom backends; with transport_stack, streaming uses the shared reqwest client (Tower middleware does not apply to send_stream).

v0.2.3

27 May 09:26

Choose a tag to compare

Summary

Rustdoc-focused patch release: ~95% documented public API (default features) and 14 doctests on docs.rs.

Highlights

  • Module docs for client, request, response, endpoint, retry, auth, backend, error, plugins
  • Crate-root feature table, flow overview, and quick-start example
  • Doctests for send/send_json, typed endpoints, retry policy, hooks, errors, auth, cancellation, mock backend, json_parser, tower stack

See CHANGELOG.md for details.

0.2.2

27 May 09:11

Choose a tag to compare

Fixed

  • README on crates.io — changelog link now points to CHANGELOG.md on main (relative CHANGELOG.md 404'd in the published crate readme).

Also publishes api-fetch 0.2.2 (alias was still at 0.2.0 on crates.io).

Full notes: CHANGELOG.md

0.2.1

27 May 09:09

Choose a tag to compare

Patch release after 0.2.0 was already on crates.io. This ships the git delta that was mistakenly tagged as 0.2.0.

Added

  • Response::into_json_with / json_with — single-step BytesT (ignores client json_parser).
  • Error::hook / Error::is_hook — helpers for Error::Hook (variant existed since 0.2.0).

Changed

  • Path :param encoding uses percent-encoding (same RFC 3986 unreserved set as before).
  • README/docs: JSON fast path, Tower Buffer, ServiceBackend mutex, buffering limits.

Full notes: CHANGELOG.md

0.2.0

27 May 09:04

Choose a tag to compare

Note: This release matches what was published to crates.io as 0.2.0 (~08:46 UTC). Items that landed only in git afterward are in 0.2.1 and 0.2.2.

Added

  • CancellationCancellationToken (from tokio-util), RequestBuilder::cancellation_token(), and Error::Cancelled with cooperative abort during requests and retry backoff.
  • Throw modeRequestBuilder::throw_on_error(true) returns Err on non-2xx from send() (like upstream throw: true).
  • Form bodiesRequestBuilder::form([...]) for application/x-www-form-urlencoded.
  • MultipartRequestBuilder::multipart(form) behind the multipart feature; re-export better_fetch::multipart for reqwest::multipart::Form.
  • Typed endpointsEndpointRequestBuilder via client.call::<E>(), with EndpointParams / EndpointQuery and typed send_json().
  • RetryRetry-After header support, jitter on backoff, 408 in default retry codes; RetryPolicy::Count keeps with_should_retry without converting to linear.
  • PluginsPreparedRequest now includes method and headers in init (after auth).
  • Dependenciesindexmap (stable query order), tokio-util, fastrand (lightweight).
  • Example multipart and integration tests for cancel, throw, form, multipart, query order, and retry edge cases.

Changed

  • ClientBuilder::build() — requires .base_url(...); returns Error::MissingBaseUrl instead of defaulting to http://localhost (breaking).
  • Query parameters — stored in IndexMap so URL query strings follow insertion order.
  • HttpBackend::execute — takes HttpRequest by value; client reuses one built request per attempt (no full clone per retry for byte bodies).
  • ClientConfig — pre-merges plugin hooks at build time (merged_hooks).
  • Multipart + retry — automatic retry is rejected with a clear error if a multipart body was used (multipart forms are not cloneable).

Full notes: CHANGELOG.md

0.1.0

27 May 07:54

Choose a tag to compare

Changelog

All notable changes to this project will be documented in this file.

The format is based on Keep a Changelog,
and this project adheres to Semantic Versioning.

0.1.0 - 2026-05-27

Added

  • better-fetch — typed HTTP client on top of reqwest with Client, ClientBuilder, and fluent RequestBuilder.
  • JSON request/response via serde (json, send_json, json_unchecked, optional custom json_parser).
  • Dynamic path parameters (:id), query strings (including repeated keys and query_json), and @put/... method path modifiers.
  • Absolute URL paths that bypass base_url when the path is a full http(s):// URL.
  • Authentication: Bearer, Basic, and custom prefix; static, sync, and async token sources.
  • Retry policies: count, linear, and exponential backoff with custom should_retry and default retry on 429/502/503/504.
  • Lifecycle hooks: on_request, on_response, on_success, on_error, on_retry; retry_attempt on request context.
  • Plugin system with init and merged hooks; built-in LoggerPlugin using tracing.
  • Endpoint trait and client.call::<E>() for typed routes.
  • HttpBackend abstraction with reqwest implementation and ClientBuilder::backend for mocks.
  • Error type with HTTP status, status_text, response body bytes, and api_json() for API error payloads.
  • Optional features: schema / openapi (registry + minimal OpenAPI builder), tower / tower-http (transport Service stack), validate (garde response validation), macros (reserved proc-macro crate).
  • typed-fetch and api-fetch — crates.io aliases that re-export better-fetch.
  • better-fetch-tower — optional companion crate for Tower transport integration.
  • better-fetch-macros — placeholder proc-macro crate for future derives.
  • Workspace examples (basic, typed_endpoint, hooks, logger_plugin, retry, auth, validated_response, tower_stack).
  • Integration and unit tests (60+ cases) with wiremock.

Notes

  • Inspired by @better-fetch/fetch; independent Rust implementation, not affiliated with the upstream TypeScript project.