Skip to content

Conversation

@hardbyte
Copy link
Contributor

Why This Change Was Made

As per #88, our previous clients offered inconsistent and minimal transport-layer information. This PR introduces a system to expose rich metadata (HTTP status, headers, timing) for both success and error responses across all generated clients. This enables advanced use cases like client-side caching, rate-limiting, and performance monitoring, while also improving the debugging experience.

A key goal was to provide this new functionality to TypeScript users without introducing breaking changes.

The New API: What's Changed

Rust API (Breaking Change)

Successful responses are now wrapped in an ApiResult<T> struct, which bundles the deserialized value with its metadata.

// Before
let pet: Pet = client.get_pet(1)?.unwrap();

// After
let result: ApiResult<Pet> = client.get_pet(1)?.unwrap();
let pet: Pet = result.value;
let metadata: &TransportMetadata = &result.metadata;

// Deref allows for ergonomic field access on the wrapped value
println!("Pet name: {}", result.name); // Works due to Deref

Error handling is also enhanced, with all Error variants now containing metadata accessible via .transport_metadata().

TypeScript API (Fully Backward-Compatible)

For Typescript we were using a custom Result class so I added the metadata to that so it should be fully backward-compatible.

Success Handling:
unwrap_ok() continues to return the value directly. The new metadata is available as an optional property on the Result object itself.

// Existing code works without any changes:
const pet = result.unwrap_ok();

// New: Code that needs metadata can access it like this:
if (result.is_ok()) {
  const metadata = result.metadata;
  console.log(`Request succeeded with status ${metadata.status}`);
}

Error Handling:
This remains 100% backward-compatible. The existing .status() method on the Err class still works, but you can now get the full metadata object via the new .transport_metadata() method.

// Existing code works without any changes:
if (result.is_err()) {
  const status = result.err().status();
}

// New: Accessing the full metadata object
if (result.is_err()) {
  const metadata = result.err().transport_metadata();
  console.log(`Request failed with headers:`, metadata.headers);
}

How It Was Implemented

  1. Core Rust Runtime (rt.rs): The foundational Rust structs (TransportResponse, TransportMetadata, ApiResult) were created to establish a canonical model. The Client trait was updated to return these rich types.
  2. TypeScript Runtime (lib.ts): An optional metadata property was added directly to the Result class.
  3. Code Generation: The Rust and TypeScript generators were updated to accommodate these new structures and produce the APIs described above.
  4. Testing: The Rust and TypeScript test suites have been expanded to validate metadata content (status, headers, timing) on all success and error paths, with specific tests confirming TypeScript's backward compatibility.

Future considerations

A minor trade-off is a slight "impurity" in the type definition of the TS Result. The Result class now has an optional metadata?: TransportMetadata property. The implementation always sets this on an Ok result, but the type system can't enforce this guarantee. An alternative, more type-pure approach would be a discriminated union:

1 type Result<T, E> =
2 | { kind: "ok", value: T, metadata: TransportMetadata }
3 | { kind: "err", error: E };

With this, the compiler would know that if kind is "ok", metadata is always present. The current approach was chosen to maintain the existing Result class API.

@hardbyte hardbyte force-pushed the feature/expose-transport-layer branch from 0fadfac to c9726e2 Compare July 25, 2025 09:26
@avkonst
Copy link
Collaborator

avkonst commented Jul 25, 2025

This enables advanced use cases like client-side caching, rate-limiting, and performance monitoring, while also improving the debugging experience.

  • client-side caching would be better explicit on top of the clean client
  • rate-limiting and retry is already possible within the custom provided client implementation, if a user wants to
  • performance monitoring - same
  • debugging experience - it might be, but I do not think it outweights the simplicity and clarity of the clean API and the original phylosophy - everything a client (from the programmer perspective) needs to know is in the typesafe strict signature

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