Releases: salasebas/better-fetch-rs
v0.6.1
Added
LoggerPluginverbose tracing —header_countfield 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.:uservs: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.0nullable: trueinstead of draft-07"null"typing. schema-validatebuilds —PreparedExecution::route_pathis 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
Added
RequestBuilder::disable_validation(featureschema-validate) — per-request skip of registry JSON Schema validation (request body, query, params, and response onsend()orStreamingResponse::collect()). Matches TypeScriptdisableValidation. Strictensure_routeis unchanged. Garde (validatefeature) is separate — usevalidate_response(false)onsend_json_validated. Also onEndpointRequestBuilder.ClientBuilder::wire_concurrency_limit— declare the TowerConcurrencyLimitLayerlimit sobuild()can warn when it matchesmax_in_flight.- Concurrency build warnings —
tracing::warn!whenmax_in_flightis 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
sse—SseDecoder,parse_sse_events,StreamingResponse::sse_events/read_sse_events(included infull). Core streaming (send_stream,collect, etc.) is unchanged without this feature. - Edge-case integration tests — non-replayable upload streams on retry;
throw_on_errorwith large streaming bodies (peek vs drain /BodyTooLarge); plugininitvs hook order; path/query URL regressions; Tower buffered vs streamingtransport_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(), orbetter_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_stackIf 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
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 underfuzz/.
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_byteson buffered responses —ClientBuilder::max_response_bytesand per-request.max_response_bytes()now apply tosend()andsend_json(), not onlysend_stream()/collect(). Bodies are read via the streaming transport when a cap is set; oversizedContent-Lengthheaders fail fast withError::BodyTooLarge.- Streaming non-2xx bodies —
send_stream()preserves the full response body when status is not 2xx andthrow()is not set.
v0.4.0
Added
- Streaming uploads —
RequestBuilder::body_streamandError::NonReplayableBodywhen retry cannot replay the body. - Tower dual stack —
ClientBuilder::transport_stackwires separate buffered and streaming transports; use this when middleware must apply tosend_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 oncollect(). - Garde validation (feature
validate) —json_validated,query_validated,with_headers_validatedon builders. - Typed endpoint macros —
#[derive(Endpoint)]with#[param],#[query_field],#[query],#[endpoint(register)], andNeedsBodyfor POST bodies. - SSE — incremental
SseDecoderandStreamingResponse::sse_events(). - API helpers —
into_api_result/send_api,ResponseBodyKind,RecordingBackendfor tests,better_fetch::prelude,RequestBuilder::base_url. - Features —
full,miette(DiagnosticError),otel(OpenTelemetry re-exports for tracing subscribers). - Errors —
QuerySerialize,RequestValidation,SchemaValidation,InvalidHeaderName/InvalidHeaderValue,MissingPathParam,SchemaRoute,Transport::source, and clearerIo/Config. - Docs — testing, observability, ROADMAP; publishing notes in README.
Changed
- Breaking: typed
.query(...)andEndpointQuery::apply_queryreturnResult; serialization errors becomeError::QuerySerialize. - Breaking:
ClientConfig::hooksis private; useClientConfig::effective_hooks(). - Breaking:
EndpointRequestBuilderimplementsDerefonly (noDerefMut); use typed.query(MyQuery)?instead of stringly.query("key", "value")on ready builders. - Breaking:
transport_stacktakes(buffered, streaming)and returns two boxed services. - Breaking:
impl EndpointrequiresBodyandHeadersassociated types (often()). - Breaking:
Error::Transportcarries an optionalsource;Error::RetryExhausted::lastisOption<Box<Error>>. - Hooks on streams —
on_errorruns for failedsend_stream()responses even withoutthrow_on_error, matching buffered behavior. http_service/http_service_boxed— documented: Tower layers apply to bufferedsend()only; streaming still uses reqwest unless you usetransport_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_errorbody handling.
Fixed
on_requesthooks — changes toRequestContext::bodyare applied to the outgoing request.throw_on_error+send_stream— HTTP errors include a peeked body when possible.- Query in path templates —
?foo=barin the path merges with builder query (builder wins). - Stream limits —
max_response_bytesapplies tocollect(),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().hookswithconfig().effective_hooks(). - Replace
into_inner()withDerefor delegated builder methods. - Do not use stringly
.query("k", "v")onclient.call::<E>()after params are set. - For Tower on both
send()andsend_stream(), usetransport_stack, nothttp_servicealone. - Add
type Body = (); type Headers = ();to manualEndpointimpls if missing. - Multipart or streaming upload retry may return
Error::NonReplayableBodyinstead of a generic error.
v0.3.0
Changed
- Typed endpoints (breaking) —
EndpointRequestBuilderfromclient.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). WhenE::Paramsis not(),.params()is required before.send_json(). Untypedclient.get()/.post()are unchanged. Error::Transport— now{ kind: TransportKind, message: String }instead of a plainString. UseError::transportorError::transport_messageto construct transport errors.Error::RetryExhausted::last— nowOption<Box<Error>>instead ofOption<String>(preserves structured last failure).- Transport mapping —
map_transport_errorclassifies reqwest failures intoTransportKind(Connect,Body,Decode,Redirect,Request,Builder,Upgrade,Other) in addition to the existingTimeoutvariant.
Added
define_params!, extendedendpoint!,impl_serde_endpoint_query!for typed path/query without proc-macros.- Type-state builder —
NeedsParamsvsReadyonEndpointRequestBuilder. - Proc-macros (feature
macros) —EndpointParamsDerive,EndpointQueryDerivewith path validation. serialize_to_query_map— serde structs to query params for OpenAPI/runtime parity.- Streaming API —
RequestBuilder::send_stream,StreamingResponse,HttpBackend::execute_stream, andBodyStreamfor incremental response bodies.max_response_bytesends the stream afterError::BodyTooLarge(no infinite error loop). - Streaming hooks —
on_response_stream,on_success_stream,StreamingResponseContext,StreamingResponseMeta,StreamingSuccessContext(metadata only; bufferedon_response/on_successare not called on the streaming path). - Streaming retry peek — custom
RetryPolicy::with_should_retryonsend_streamcan inspect up toretry_body_peek_bytes(default 64 KiB) of the body; status-only retries do not read the body. - Streaming cancellation —
CancelBodyStreamregisters the cancellation waker while the inner body read is pending. - Tower streaming —
ServiceBackenddelegatesexecute_streamto aReqwestBackendsharing the samereqwest::Clientas the Tower stack (transport_stackwires this automatically). Error::BodyTooLarge— when a streaming response exceedsmax_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.- Example —
examples/streaming.rs; testsstreaming_testsandstreaming_tower_tests(featuretower). - docs.rs —
package.metadata.docs.rswithall-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(tagv*→ crates.io trusted publishing + GitHub Release fromCHANGELOG.md).
Migration (0.2.x → 0.3.0)
client.call::<E>()— replace.param("id", n)with.params(E::Params { ... })(ordefine_params!/ proc-macros). See README typed endpoint section.send()/send_json()— unchanged; still fully buffered.- Custom
HttpBackend— implementexecute_stream(return an error if unsupported). - Tower
ServiceBackend— implementexecute_streamon custom backends; withtransport_stack, streaming uses the shared reqwest client (Tower middleware does not apply tosend_stream).
v0.2.3
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
Fixed
- README on crates.io — changelog link now points to CHANGELOG.md on
main(relativeCHANGELOG.md404'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
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-stepBytes→T(ignores clientjson_parser).Error::hook/Error::is_hook— helpers forError::Hook(variant existed since 0.2.0).
Changed
- Path
:paramencoding usespercent-encoding(same RFC 3986 unreserved set as before). - README/docs: JSON fast path, Tower
Buffer,ServiceBackendmutex, buffering limits.
Full notes: CHANGELOG.md
0.2.0
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
- Cancellation —
CancellationToken(fromtokio-util),RequestBuilder::cancellation_token(), andError::Cancelledwith cooperative abort during requests and retry backoff. - Throw mode —
RequestBuilder::throw_on_error(true)returnsErron non-2xx fromsend()(like upstreamthrow: true). - Form bodies —
RequestBuilder::form([...])forapplication/x-www-form-urlencoded. - Multipart —
RequestBuilder::multipart(form)behind themultipartfeature; re-exportbetter_fetch::multipartforreqwest::multipart::Form. - Typed endpoints —
EndpointRequestBuilderviaclient.call::<E>(), withEndpointParams/EndpointQueryand typedsend_json(). - Retry —
Retry-Afterheader support, jitter on backoff, 408 in default retry codes;RetryPolicy::Countkeepswith_should_retrywithout converting to linear. - Plugins —
PreparedRequestnow includesmethodandheadersininit(after auth). - Dependencies —
indexmap(stable query order),tokio-util,fastrand(lightweight). - Example
multipartand integration tests for cancel, throw, form, multipart, query order, and retry edge cases.
Changed
ClientBuilder::build()— requires.base_url(...); returnsError::MissingBaseUrlinstead of defaulting tohttp://localhost(breaking).- Query parameters — stored in
IndexMapso URL query strings follow insertion order. HttpBackend::execute— takesHttpRequestby 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
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 withClient,ClientBuilder, and fluentRequestBuilder.- JSON request/response via serde (
json,send_json,json_unchecked, optional customjson_parser). - Dynamic path parameters (
:id), query strings (including repeated keys andquery_json), and@put/...method path modifiers. - Absolute URL paths that bypass
base_urlwhen the path is a fullhttp(s)://URL. - Authentication: Bearer, Basic, and custom prefix; static, sync, and async token sources.
- Retry policies:
count, linear, and exponential backoff with customshould_retryand default retry on 429/502/503/504. - Lifecycle hooks:
on_request,on_response,on_success,on_error,on_retry;retry_attempton request context. - Plugin system with
initand merged hooks; built-inLoggerPluginusingtracing. Endpointtrait andclient.call::<E>()for typed routes.HttpBackendabstraction with reqwest implementation andClientBuilder::backendfor mocks.- Error type with HTTP status,
status_text, response body bytes, andapi_json()for API error payloads. - Optional features:
schema/openapi(registry + minimal OpenAPI builder),tower/tower-http(transportServicestack),validate(garde response validation),macros(reserved proc-macro crate). typed-fetchandapi-fetch— crates.io aliases that re-exportbetter-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.