Skip to content

feat: introduce fetch crate#475

Merged
martintmk merged 19 commits into
mainfrom
fetch
Jun 5, 2026
Merged

feat: introduce fetch crate#475
martintmk merged 19 commits into
mainfrom
fetch

Conversation

@martintmk
Copy link
Copy Markdown
Member

@martintmk martintmk commented Jun 4, 2026

Summary

Introduces fetch, a fast and safe HTTP client for the Oxidizer project that is runtime-agnostic and transport-agnostic. It bundles the capabilities real-world services need — TLS, resilience, observability, and testability — into a single client that works out of the box, while letting libraries defer the choice of runtime and HTTP transport to the consuming application.

The request pipeline is built around a swappable transport handler at its leaf, with resilience, observability, routing, logging, and retries layered on top. Tokio + hyper work out of the box, but you can plug in any async runtime/IO, or even reuse another HTTP client (e.g. reqwest) as the transport.

Highlights

  • Secure by default: Strong TLS validation via rustls (with aws-lc-rs) and platform trust store verification, or native-tls as an alternative backend.
  • Resilient by default: Built-in retries, timeouts, circuit breaking, and hedging powered by seatbelt, pre-configured in the standard pipeline.
  • Observable by default: OpenTelemetry-compatible metrics and logging handlers.
  • Composable pipeline: Modular RequestHandler-based pipeline (standard, custom, and minimal variants) for adding or customizing behaviors.
  • Built-in testability: The test-util feature mocks responses and client behavior for fast, deterministic tests with no network access.
  • Memory efficient: Smart pooling and zero-copy BytesView buffers for large responses.
  • Ergonomic, familiar API: get(), post(), put(), delete(), fetch_text(), fetch_bytes(), fetch_json() / fetch_json_owned(), plus templated-URI support.

Examples

Simple case — create a client and fetch text:

use fetch::{HttpClient, HttpError, Response, StatusExt};

#[tokio::main]
async fn main() -> Result<(), HttpError> {
    let client: HttpClient = HttpClient::new_tokio();

    let response: Response<String> = client
        .get("https://example.com")
        .fetch_text()
        .await?
        .ensure_success()?;

    println!("response: {}", response.body());
    Ok(())
}

Customized request — headers, HTTP version, and a body:

let response = client
    .post("https://api.example.com/upload")
    .header(header::AUTHORIZATION, "Bearer token123")
    .header(header::CONTENT_TYPE, "application/json")
    .version(Version::HTTP_2)
    .text("{\"name\": \"document.pdf\"}")
    .fetch()
    .await?;

Builder case — configure telemetry and TLS on the client itself:

use fetch::HttpClient;
use fetch::tls::TlsOptions;

let client = HttpClient::builder_tokio(ctx)
    .meter_provider(&meter_provider)
    .tls_options(
        TlsOptions::builder_rustls()
            .server_certificate_verifier(custom_verifier)
            .build(),
    )
    // Share a base URI across requests (trailing slash is mandatory)
    .base_uri(BaseUri::from_static("https://example.com/api/v1/"))
    .build();

// Resolves to https://example.com/api/v1/foo/bar
let response = client.get("/foo/bar").fetch().await?;

Feature flags

  • tokio — Tokio runtime integration (HttpClient::new_tokio / builder_tokio).
  • json — JSON request/response support.
  • tls / rustlsrustls backend with aws-lc-rs (recommended).
  • native-tls — platform native TLS backend.
  • test-util — response/client mocking for tests.

Comparison vs. reqwest

Both build on hyper by default, but fetch adds first-class custom runtime/transport support, built-in resilience, OTel metrics/logging, a composable request pipeline, zero-copy buffers, and testing tools out of the box. Some advanced features (cookies, redirects, forms) are not yet supported and may come later.

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Jun 4, 2026

⚠️ Breaking Changes Detected


--- failure struct_marked_non_exhaustive: struct marked #[non_exhaustive] ---

Description:
A public struct has been marked #[non_exhaustive], which will prevent it from being constructed using a struct literal outside of its crate. It previously had no private fields, so a struct literal could be used to construct it outside its crate.
        ref: https://doc.rust-lang.org/cargo/reference/semver.html#attr-adding-non-exhaustive
       impl: https://github.com/obi1kenobi/cargo-semver-checks/tree/v0.48.0/src/lints/struct_marked_non_exhaustive.ron

Failed in:
  struct ConnectionPoolOptions in /home/runner/work/oxidizer/oxidizer/crates/fetch_options/src/pooling.rs:87

error: failed to retrieve local crate data from git revision

Caused by:
    0: failed to retrieve manifest file from git revision source
    1: possibly due to errors: [
         failed to parse /home/runner/work/oxidizer/oxidizer/target/semver-checks/git-origin_main/81f5ccae65721f2f441fd09696a8a8b249d73da9/Cargo.toml: no `package` table,
         failed when reading /home/runner/work/oxidizer/oxidizer/target/semver-checks/git-origin_main/81f5ccae65721f2f441fd09696a8a8b249d73da9/scripts/crate-template/Cargo.toml: TOML parse error at line 9, column 26
         |
       9 | keywords = ["oxidizer", {{CRATE_KEYWORDS}}]
         |                          ^
       missing key for inline table element, expected key
       : TOML parse error at line 9, column 26
         |
       9 | keywords = ["oxidizer", {{CRATE_KEYWORDS}}]
         |                          ^
       missing key for inline table element, expected key
       ,
       ]
    2: package `fetch` not found in /home/runner/work/oxidizer/oxidizer/target/semver-checks/git-origin_main/81f5ccae65721f2f441fd09696a8a8b249d73da9

Stack backtrace:
   0: anyhow::error::<impl anyhow::Error>::msg
   1: cargo_semver_checks::rustdoc_gen::RustdocFromProjectRoot::get_crate_source
   2: cargo_semver_checks::rustdoc_gen::StatefulRustdocGenerator<cargo_semver_checks::rustdoc_gen::CoupledState>::prepare_generator
   3: cargo_semver_checks::Check::check_release::{{closure}}
   4: cargo_semver_checks::Check::check_release
   5: cargo_semver_checks::exit_on_error
   6: cargo_semver_checks::main
   7: std::sys::backtrace::__rust_begin_short_backtrace
   8: main
   9: <unknown>
  10: __libc_start_main
  11: _start

If the breaking changes are intentional then everything is fine - this message is merely informative.

Remember to apply a version number bump with the correct severity when publishing a version with breaking changes (1.x.x -> 2.x.x or 0.1.x -> 0.2.x).

martintmk and others added 2 commits June 4, 2026 15:59
…ve-2.0

Gate the coverage_attribute nightly feature in fetch's lib.rs so the #[coverage(off)] attributes on test modules compile under the coverage job, and allow the CDLA-Permissive-2.0 license used by webpki-root-certs (pulled in via rustls-platform-verifier) so cargo-deny passes with --all-features.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@codecov
Copy link
Copy Markdown

codecov Bot commented Jun 4, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 100.0%. Comparing base (4b0c91b) to head (f79b128).

Additional details and impacted files
@@           Coverage Diff            @@
##             main     #475    +/-   ##
========================================
  Coverage   100.0%   100.0%            
========================================
  Files         319      335    +16     
  Lines       24676    25586   +910     
========================================
+ Hits        24676    25586   +910     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@martintmk martintmk marked this pull request as ready for review June 4, 2026 18:40
Copilot AI review requested due to automatic review settings June 4, 2026 18:40
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

Copilot stopped reviewing on behalf of martintmk due to an error June 4, 2026 19:41
Copilot AI review requested due to automatic review settings June 4, 2026 20:27
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 84 out of 85 changed files in this pull request and generated 4 comments.

Comment thread crates/fetch/examples/util/crates_io_mock.rs Outdated
Comment thread crates/fetch/examples/http_client_fake.rs Outdated
Comment thread crates/fetch/src/handlers/logging.rs
Comment thread crates/fetch/src/handlers/logging.rs Outdated
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings June 4, 2026 20:46
martintmk and others added 2 commits June 4, 2026 22:46
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
martintmk and others added 2 commits June 4, 2026 22:47
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 84 out of 85 changed files in this pull request and generated 3 comments.

Comment thread crates/fetch/src/handlers/logging.rs
Comment thread crates/fetch/tests/thread_aware.rs
Comment thread .spelling
@martintmk martintmk enabled auto-merge (squash) June 5, 2026 08:27
@martintmk martintmk merged commit 877def3 into main Jun 5, 2026
31 checks passed
@martintmk martintmk deleted the fetch branch June 5, 2026 08:46
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.

4 participants