Skip to content

feat(transport): HttpTransport abstraction for generated clients + in-process e2e tests#18

Merged
JedimEmO merged 6 commits into
masterfrom
feat/transport-trait-abstraction
Jun 6, 2026
Merged

feat(transport): HttpTransport abstraction for generated clients + in-process e2e tests#18
JedimEmO merged 6 commits into
masterfrom
feat/transport-trait-abstraction

Conversation

@JedimEmO

@JedimEmO JedimEmO commented Jun 5, 2026

Copy link
Copy Markdown
Owner

Closes #17.

What

Introduces ras-transport-core — an object-safe HttpTransport trait (dyn dispatch, conditional Send mirroring the existing WebSocketTransport pattern) — and migrates the generated REST / JSON-RPC / File clients to dispatch through it. This lets generated clients run over reqwest (production) or axum-test (in-process), so they can finally be exercised end-to-end against a server with no sockets.

Highlights

  • Typed errors: generated client methods return Result<T, TransportError> instead of Box<dyn std::error::Error + Send + Sync>.
  • Real streaming: RequestBody/TransportResponse carry streaming bodies; a hand-rolled RFC 7578 MultipartBuilder replaces reqwest::multipart::Form. Uploads stream from disk on native (fs feature); downloads stream the response body.
  • Transports: ReqwestTransport (reqwest feature) and AxumTestTransport (axum-test feature, wraps Arc<TestServer>).
  • Builders: generated clients store Arc<dyn HttpTransport> and gain an ungated build_with_transport(...) alongside the default reqwest-backed build().
  • Query params: serialization moves off reqwest's .query() to serde_urlencoded with byte-for-byte parity (Option-skip, Vec repeated keys, enum #[serde(rename)]).
  • Bidirectional (ras-jsonrpc-bidirectional-macro) is intentionally untouched — WebSocket, not HTTP, already has its own WebSocketTransport.

Breaking (pre-1.0)

  • File-client download methods return ras_transport_core::TransportResponse instead of reqwest::Response (consume via into_body_stream() / bytes()).
  • Generated client methods return Result<T, TransportError>.

Testing

  • New client→server e2e tests over AxumTestTransport for every macro, including the from-disk streaming upload path (--features fs).
  • ras-transport-core unit/integration tests: byte-exact multipart framing, query .query() parity, request/response types, and the in-process transport. request.rs / response.rs / axum_test_transport.rs at 100% line coverage; lib.rs 92.6%, multipart.rs 98.2%.
  • The production reqwest wire path is intentionally not covered in-process (requires a real socket).

Verification

  • cargo test --workspace — 100 test groups, 0 failures
  • cargo build --workspace --all-targets
  • cargo build -p ras-transport-core --target wasm32-unknown-unknown --features reqwest,fs
  • cargo build -p wasm-ui-demo --target wasm32-unknown-unknown

JedimEmO and others added 6 commits June 5, 2026 14:17
Generated REST/JSON-RPC/File clients hard-coded reqwest::Client and were
never exercised against a server in tests. Introduce ras-transport-core, an
object-safe HttpTransport trait (dyn dispatch, conditional Send mirroring
WebSocketTransport) with two impls: ReqwestTransport (production) and
AxumTestTransport (in-process, wraps axum_test::TestServer).

- Typed TransportError replaces Box<dyn Error> in generated client returns.
- Streaming RequestBody/TransportResponse; hand-rolled RFC 7578 MultipartBuilder
  replaces reqwest::multipart::Form (uploads stream from disk on native,
  downloads stream the response body).
- Generated clients store Arc<dyn HttpTransport> and gain build_with_transport;
  query serialization moves to serde_urlencoded (reqwest .query() parity).
- REST/JSON-RPC/File migrated; bidirectional left unchanged (WebSocket, already
  has WebSocketTransport).

Breaking (pre-1.0): file download methods now return TransportResponse instead
of reqwest::Response; generated methods return Result<T, TransportError>.

Adds client->server e2e tests over AxumTestTransport for every macro (incl. the
from-disk streaming upload under --features fs) plus transport-core unit tests
(multipart framing, query parity, request/response/transport at ~100% lines).

Closes #17

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ep-free

CI runs `cargo clippy --workspace --all-targets --all-features --locked
-D warnings` plus rustfmt; this fixes the gates:

- rustfmt: apply `cargo fmt --all`.
- clippy: elide the redundant lifetime on the QueryValueCollector Serializer impl.
- Move the tokio::fs -> ReaderStream conversion fully into
  MultipartBuilder::file_path (now takes an optional filename override) so the
  generated file-upload client no longer emits futures_util/tokio/tokio-util
  references. This was breaking the file-service examples under --all-features
  (E0432 unresolved `futures_util`, E0599 ReaderStream is not an iterator),
  which only depend on ras-transport-core.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
CI's `dtolnay/rust-toolchain@stable` advanced to rustc 1.96.0 (2026-05-25),
which broke the workspace independently of the transport refactor:

- E0446 "private type in public interface" is now a hard error. Many existing
  ras-jsonrpc-macro test/example/bench fixtures declared non-pub request/
  response structs that the generated `pub` service trait exposes. Make those
  fixture types `pub` (bare visibility flips only).
- New lint clippy::manual_noop_waker fires on ras-auth-core's test NoopWaker;
  replace it with `std::task::Waker::noop().clone()`.

No library logic, public APIs, features, deps, or CI config changed.

Verified on 1.96.0 (exact CI commands):
- cargo clippy --workspace --all-targets --all-features --locked -- -D warnings
- cargo test  --workspace --all-targets --all-features --locked
- cargo test --doc --workspace --all-features --locked
- cargo fmt --all -- --check

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- multipart: generate boundary from CSPRNG entropy (getrandom) instead of
  a predictable timestamp+counter, and correct the stale doc comment. The
  boundary is the only thing protecting multipart framing from injection
  when part bodies carry caller-forwarded bytes.
- error: add TransportError::Timeout and route reqwest timeouts to it so a
  per-request timeout no longer masquerades as a connection error.
- response: error_for_status surfaces a failed body read instead of
  silently blanking the diagnostic.
- lib: serialize_query_pairs now returns Result (matching
  serialize_query_value) rather than swallowing failure into an empty
  string after the codegen has written a '?'/'&' separator; rest client
  codegen propagates it with '?'.
- ras-jsonrpc-macro: make ras-transport-core an optional dep, matching the
  rest/file macro crates (it is only referenced by generated client code).
- docs: fix broken intra-doc links in generated clients and the inaccurate
  "only transport that buffers" note on the axum-test transport.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ort abstraction

Harden the new reqwest-free request construction in ras-transport-core and the
generated REST/JSON-RPC/file clients. Each fix ships with a red-then-green
regression test.

- Multipart Content-Type CRLF injection: the hand-rolled multipart builder
  wrote a part's Content-Type verbatim (unlike name/filename, which were
  escaped), letting a caller-supplied content type inject extra header lines or
  break the part header block. Sanitize it at the framing sink.
- Bearer token fail-open: TransportRequest::bearer() silently dropped a token
  that could not be encoded as a header value, sending the request
  unauthenticated. It now fails closed (returns TransportError::InvalidHeader);
  header() stays best-effort. Propagated through the rest/jsonrpc/file
  generators (file build_request now returns Result).
- Unencoded path parameters: client generators substituted path params raw, so
  '/', '?', '#', etc. could escape the URL segment. Add and wire
  encode_path_segment (RFC 3986 unreserved pass-through, percent-encode rest).

Tests: multipart/bearer regressions in ras-transport-core; path-encoding unit
test plus a capturing-transport integration test in ras-rest-macro.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@JedimEmO JedimEmO merged commit a801022 into master Jun 6, 2026
13 checks passed
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.

Transport abstraction (HttpTransport) for generated clients + in-process e2e tests

1 participant