Skip to content

Conversation

@ap-1
Copy link

@ap-1 ap-1 commented Dec 31, 2025

Progenitor previously only supported the reqwest HTTP backend, which adds bundle size overhead for WASM/browser applications. The gloo-net library is specifically designed for WASM targets and leverages the browser's native Fetch API.

This means we get smaller bundle sizes, better integration with browser APIs (WebSocket, streaming, etc.), and the native browser behavior for connection pooling, timeouts, and TLS. This change adds first-class support for gloo-net as an alternative HTTP backend.

  • progenitor-impl: Added HttpBackend enum (Reqwest | Gloo) to represent backend choice throughout code generation
  • progenitor-client: Implemented gloo_impl.rs with feature parity to reqwest_impl.rs:
    • Error handling with Error<E> enum
    • ResponseValue<T> wrapper for responses with status/headers
    • ByteStream support via wasm_streams for streaming responses
    • WebSocket support using web_sys::WebSocket directly, rather than HTTP upgrade (since browsers don't expose WebSocket as an HTTP upgrade)
    • Shared code extracted to shared.rs for both backends
  • cargo-progenitor: Added --backend CLI flag (defaults to reqwest) to generate backend-specific code and dependencies
  • Added backend parameter to generate_api! macro
  • build.rs: Added with_backend() method to GenerationSettings
  • example-wasm/ was updated to demonstrate gloo backend usage with HttpBackend::Gloo in build.rs

Design Decisions

  1. I chose to separate out reqwest-client and gloo-client into mutually exclusive features, for the following reasons:
  • Different dependency sets (reqwest vs gloo-net/web-sys), including some existing dependencies that need to have optional features enabled
  • Different error types (reqwest::Error vs String/serde_json::Error)

progenitor-client/src/lib.rs enforces this with compile-time errors. To avoid the same error, example-wasm is excluded from the workspace: it depends on progenitor-client with gloo-client, whereas cargo-progenitor depends on progenitor-client with reqwest-client.

  1. I also also chose not to support --backend gloo with the --include-client flag, which is supposed to inline progenitor-client code. It's theoretically possible, but I realized the following:
  • The Gloo backend requires WASM-specific dependencies (wasm-bindgen, web-sys, js-sys) that we'd have to enable
  • For other dependencies, we also have to enable specific features for WASM support
  • The file merging becomes fragile because we have to filter out some of the duplicated use statements
  • People targeting wasm32-unknown-unknown usually have some form of more complex build process using tools like wasm-pack or trunk anyway

Instead it returns an error message directing users to use the dependency instead.

  1. The backends use different customization mechanisms reflecting their underlying architectures.

reqwest does something like Client::new_with_client(url, reqwest::Client), and all configuration happens at construction time. gloo-net uses the ClientHooks trait with the pre() and post() hooks, which allow per-request customization of the Fetch API RequestInit options.

Usage

For WASM applications wanting to use the gloo backend:

# Cargo.toml
[dependencies]
progenitor-client = { version = "0.11", features = ["gloo-client"] }
// build.rs
let mut generator = progenitor::Generator::new(
  progenitor::GenerationSettings::default()
      .with_backend(progenitor::HttpBackend::Gloo),
);

// or with the macro:
progenitor::generate_api!(
  spec = "path/to/spec.json",
  backend = Gloo,
);

@ahl
Copy link
Collaborator

ahl commented Jan 4, 2026

I haven't taken a thorough look, but on first glance this looks like quite a lot of complexity that isn't particularly of use to us at Oxide. I'd like to propose an alternative approach to see if it might enable what you're building:

I don't think reqwest is doing much for us at this point. We chose it early in the life of progenitor, but over time it became clear that was was very convenient for building hand-written clients was unnecessary complexity for generated clients. We're contemplated a more flexible approach: rather than assuming reqwest, define a HttpClient trait and generate against that. We could implement this trait for a reqwest::Client (or whatever) and allow for other implementations such as gloo or a lower-level hyper-based client.

Might this address your use case?

@ap-1
Copy link
Author

ap-1 commented Jan 4, 2026

I haven't taken a thorough look, but on first glance this looks like quite a lot of complexity that isn't particularly of use to us at Oxide. I'd like to propose an alternative approach to see if it might enable what you're building:

I don't think reqwest is doing much for us at this point. We chose it early in the life of progenitor, but over time it became clear that was was very convenient for building hand-written clients was unnecessary complexity for generated clients. We're contemplated a more flexible approach: rather than assuming reqwest, define a HttpClient trait and generate against that. We could implement this trait for a reqwest::Client (or whatever) and allow for other implementations such as gloo or a lower-level hyper-based client.

Might this address your use case?

Yes, I think this is a good solution.

@ahl
Copy link
Collaborator

ahl commented Jan 4, 2026

If you're interested in working on this approach, I'd suggest we first define a trait, then create an implementation for reqwest and then modify generation.

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.

2 participants