Skip to content

feat(rust): Add RequestExecutor trait for CLI execution-sharing#16282

Merged
jsklan merged 5 commits into
mainfrom
devin/1780678146-rust-sdk-transport-seam
Jun 5, 2026
Merged

feat(rust): Add RequestExecutor trait for CLI execution-sharing#16282
jsklan merged 5 commits into
mainfrom
devin/1780678146-rust-sdk-transport-seam

Conversation

@jsklan
Copy link
Copy Markdown
Contributor

@jsklan jsklan commented Jun 5, 2026

Description

Linear ticket: Refs FER-11025

Adds a transport seam to the Rust SDK generator: a RequestExecutor trait and HttpClient::with_executor() constructor that lets a CLI binary inject its own HTTP execution stack into the generated SDK. This is the foundational change for co-generating the Rust SDK into a CLI and exposing an execution-sharing boundary.

Changes Made

generators/rust/base/src/asIs/http_client.rs (template):

  • New RequestExecutor trait (Send + Sync, returns BoxFuture<Result<Response, reqwest::Error>>)
  • ReqwestExecutor default impl wrapping reqwest::Client — preserves standalone SDK behavior unchanged
  • HttpClient::with_executor(Arc<dyn RequestExecutor>, ClientConfig) constructor — when used, the SDK delegates HTTP execution entirely (no SDK-level auth/headers/retries, preventing double-retry)
  • Refactored all execute methods (JSON, multipart, bytes, base64, SSE) to route through send_request() which branches between executor path and default path
  • HttpClient retains #[derive(Clone)] (all fields including Option<Arc<dyn RequestExecutor>> are Clone-compatible)

generators/rust/base/src/project/RustProject.ts (template expansion):

  • Updated multipart, bytes, base64, and SSE method templates to use the same send_request() dispatcher

generators/rust/sdk/src/SdkGeneratorCli.ts:

  • Exports RequestExecutor from core/mod.rs
  • Skips model/type generation when cliEmbedded: true
  • generateApiModFile and generateLibFile respect cliEmbedded flag (no mod types; when types are skipped)

generators/rust/sdk/src/SdkCustomConfig.ts:

  • Added cliEmbedded: boolean config flag (defaults to false)

Seed snapshots: All 135 rust-sdk fixtures regenerated with updated http_client.rs and core/mod.rs

Testing

  • All 135 rust-sdk seed tests pass
  • pnpm check (biome lint) passes — 4821 files checked, no issues
  • 33 vitest unit tests pass (snapshot updated)
  • TypeScript compilation succeeds for all rust packages
  • dist:cli build succeeds — as-is template files correctly bundled

Link to Devin session: https://app.devin.ai/sessions/2af7dccaec8640ff8d8e7673c0170a2b
Requested by: @jsklan

Add RequestExecutor trait and HttpClient::with_executor() constructor
to enable CLI execution-sharing. When an external executor is injected,
the SDK delegates HTTP execution entirely — auth, headers, and retries
are handled by the caller's transport stack.

Also adds cliEmbedded config flag that skips model generation when the
SDK is embedded in a CLI binary.
@devin-ai-integration
Copy link
Copy Markdown
Contributor

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment, CI, and merge conflict monitoring

Copy link
Copy Markdown

@claude claude Bot left a comment

Choose a reason for hiding this comment

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

Claude Code Review

This repository is configured for manual code reviews. Comment @claude review to trigger a review and subscribe this PR to future pushes, or @claude review once for a one-time review.

Tip: disable this comment in your organization's Code Review settings.

@hex-security-app
Copy link
Copy Markdown

hex-security-app Bot commented Jun 5, 2026

Security Finding: execute_request_base64 bypasses the injected RequestExecutor

File: generators/rust/base/src/project/RustProject.ts{{BASE64_METHOD}} template block (~lines 439–446)
Also confirmed in generated output: seed/rust-sdk/enum/src/core/http_client.rs lines 677–684 (and equivalently in all other seed fixtures that include the base64 method)

What's wrong:

execute_request_base64 calls apply_auth_headers, apply_custom_headers, and execute_with_retries directly instead of routing through send_request(). Every other execute method (execute_request, execute_request_raw, execute_request_with_base_url, execute_stream_request, execute_stream_request_with_base_url, execute_bytes_request, execute_sse_request) was correctly updated to call send_request(), which branches on self.executor. This one was missed.

Impact:

When a CLI constructs the client via HttpClient::with_executor(custom_executor, config), the injected executor is silently ignored for any base64-response endpoint. The SDK instead:

  1. Applies its own SDK-level auth headers (potentially wrong credentials, or credentials that shouldn't be sent through this transport path)
  2. Sends the request through the SDK's own internal reqwest::Client — bypassing the CLI's TLS configuration, proxy settings, auth interceptors, and retry policy

This directly violates the security trust boundary that RequestExecutor is designed to enforce.

Fix:

In the {{BASE64_METHOD}} template in RustProject.ts, replace:

// Build the request
let mut req = request.build().map_err(|e| ApiError::Network(e))?;

// Apply authentication and headers
self.apply_auth_headers(&mut req, &options).await?;
self.apply_custom_headers(&mut req, &options)?;

// Execute with retries
let response = self.execute_with_retries(req, &options).await?;

with:

// Build the request
let req = request.build().map_err(|e| ApiError::Network(e))?;

let response = self.send_request(req, &options).await?;

Then regenerate all seed snapshots to propagate the fix.

Fixed in d5fb1e1d.

@hex-security-app
Copy link
Copy Markdown

The new commit (9d7d2c0d) fixed the MULTIPART_METHOD, BYTES_METHOD, and SSE_METHOD template blocks, but the {{BASE64_METHOD}} template block is still unpatched — it remains identical to the original reported issue.

RustProject.ts lines 438–446 still read:

// Build the request
let mut req = request.build().map_err(|e| ApiError::Network(e))?;

// Apply authentication and headers
self.apply_auth_headers(&mut req, &options).await?;
self.apply_custom_headers(&mut req, &options)?;

// Execute with retries
let response = self.execute_with_retries(req, &options).await?;

The injected RequestExecutor is still bypassed for base64-response endpoints. The fix remains the same: replace the three direct calls with self.send_request(req, &options).await? and change let mut req to let req.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Jun 5, 2026

SDK Generation Benchmark Results

Comparing PR branch against median of 5 nightly run(s) on main (latest: 2026-06-05T05:30:09Z).

Full benchmark table (click to expand)
Generator Spec main (generator) main (E2E) PR (generator) Delta
rust-sdk square 178s (n=5) 177s (n=5) 202s +24s (+13.5%)

main (generator): generator-only time via --skip-scripts (includes Docker image build, container startup, IR parsing, and code generation — this is the same Docker-based flow customers use via fern generate). main (E2E): full customer-observable time including build/test scripts (nightly baseline, informational). Delta is computed against generator-only baseline.
⚠️ = generation exited with a non-zero exit code (timing may not reflect a successful run).
Baseline from nightly runs on main (latest: 2026-06-05T05:30:09Z). Trigger benchmark-baseline to refresh.
Last updated: 2026-06-05 20:07 UTC

devin-ai-integration[bot]

This comment was marked as resolved.

…or, fix cliEmbedded types

- Restore #[derive(Clone)] on HttpClient (needed for pagination code gen)
- Update BASE64_METHOD template to use send_request() instead of direct
  apply_auth_headers/execute_with_retries (bypassed injected executor)
- Check cliEmbedded flag in generateApiModFile and generateLibFile to
  avoid declaring mod types when type generation is skipped
Copy link
Copy Markdown
Contributor

@devin-ai-integration devin-ai-integration Bot left a comment

Choose a reason for hiding this comment

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

Devin Review found 1 new potential issue.

View 6 additional findings in Devin Review.

Open in Devin Review

Comment thread generators/rust/base/src/project/RustProject.ts
Move SSE-specific headers (Accept: text/event-stream, Cache-Control: no-store)
after apply_custom_headers in the default path so they always take precedence,
matching the original ordering. In the executor path, SSE headers are applied
before delegation.
@jsklan jsklan merged commit 1aa5379 into main Jun 5, 2026
54 of 55 checks passed
@jsklan jsklan deleted the devin/1780678146-rust-sdk-transport-seam branch June 5, 2026 19:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant