Skip to content

Add Spin adapter#166

Open
ChristianPavilonis wants to merge 5 commits intomainfrom
feature/spin
Open

Add Spin adapter#166
ChristianPavilonis wants to merge 5 commits intomainfrom
feature/spin

Conversation

@ChristianPavilonis
Copy link
Contributor

@ChristianPavilonis ChristianPavilonis commented Feb 18, 2026

Summary

Adds a new edgezero-adapter-spin crate that bridges Spin HTTP components into the EdgeZero router, following the established adapter pattern (Fastly, Cloudflare).

  • New crate: crates/edgezero-adapter-spin/ with context, request, response, proxy, and CLI modules
  • Scaffold support: Handlebars templates for cargo edgezero new --adapter spin scaffolding
  • Example adapter: examples/app-demo/crates/app-demo-adapter-spin/ reference implementation
  • Consistent API: init_logger(), AppExt trait, run_app() convenience entry point — matching Fastly/Cloudflare adapters

What's Included

Module Description
context.rs SpinRequestContext — extracts spin-client-addr and spin-full-url headers
request.rs Converts spin_sdk::http::IncomingRequest → core Request with proper WASI resource handle management
response.rs Converts core Responsespin_sdk::http::Response (buffered + streaming bodies)
proxy.rs SpinProxyClient using spin_sdk::http::send for outbound HTTP
cli.rs Build (cargo build --target wasm32-wasip1), deploy (spin deploy), serve (spin up) commands
lib.rs init_logger(), AppExt trait, run_app() entry point

What's Not Included (Future Work)

  • KV store support

Testing

  • cargo fmt --all -- --check — clean
  • cargo clippy --workspace --all-targets --all-features -- -D warnings — clean
  • cargo test --workspace --all-targets — all tests pass (6 Spin adapter tests including context unit tests)
  • cargo check --workspace --all-targets --features "fastly cloudflare" — clean
  • Contract tests in tests/contract.rs (gated behind spin + wasm32, follows Fastly/Cloudflare pattern)
  • Deployed mocktioneer to Fermyon

- Fix cargo fmt formatting issues
- Return EdgeError for unsupported HTTP methods instead of silently
  defaulting to GET
- Add init_logger() matching Cloudflare's no-op pattern, call in run_app
- Expose SpinRequestContext unconditionally (pure Rust, no WASM deps)
- Add AppExt trait on App for consistent adapter API
- Add contract test scaffold and context unit tests
- Replace bare .unwrap() with .expect() in proxy header insert
- Simplify anyhow wrapping in proxy error path to format!()
@ChristianPavilonis ChristianPavilonis changed the title Support for spin framework Add Spin (Fermyon) adapter Mar 5, 2026
@ChristianPavilonis ChristianPavilonis changed the title Add Spin (Fermyon) adapter Add Spin adapter Mar 5, 2026
@ChristianPavilonis ChristianPavilonis marked this pull request as ready for review March 5, 2026 00:42
Copy link
Contributor

@aram356 aram356 left a comment

Choose a reason for hiding this comment

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

PR Review

Summary

Solid adapter implementation that follows the established Fastly/Cloudflare pattern well. However, the adapter does not compile for its actual wasm32-wasip1 target, and the scaffolding pipeline produces broken projects due to missing workspace deps and unresolved template variables. These must be fixed before merge.

😃 Praise

  • CLI scaffolding is thorough and well-tested — build/deploy/serve, blueprint registration, ctor auto-registration, manifest discovery with distance-based sorting, and 4 solid unit tests
  • Context module is correctly ungated from target_arch so it works in native tests
  • Method conversion (into_core_method) handles Method::Other with proper error reporting — more robust than the Fastly adapter

Findings

Blocking

  • 🔧 wasm32 compile error: EdgeError::internal(format!(...))String doesn't impl Into<anyhow::Error> (proxy.rs:48)
  • 🔧 Scaffold {crate_name} placeholder not replaced: generator only handles {crate} / {crate_dir} (cli.rs:178)
  • 🔧 proj_spin_underscored template var never populated: renders as empty → broken spin.toml source path (spin.toml.hbs:12)
  • 🔧 Generated projects fail: spin-sdk missing from seed_workspace_dependencies() in generator.rs:109
  • 🔧 AppExt duplicates dispatch() logic: should delegate like Cloudflare/Fastly (lib.rs:42-56)
  • 🔧 Zero contract test cases: build_test_app() exists but no #[test] functions (tests/contract.rs)
  • 🔧 CI feature check missing spin: .github/workflows/test.yml:59 only checks fastly cloudflare

Non-blocking

  • 🤔 Silent header drops in proxy.rs and response.rs: non-UTF-8/invalid headers discarded with no logging
  • 🤔 client_addr stored as raw String: Fastly uses typed Option<IpAddr> — cross-adapter inconsistency
  • ♻️ init_logger() identical cfg branches: both return Ok(()), gate adds no value
  • ♻️ Body stream collection duplicated: identical Body::Stream → Vec<u8> in proxy.rs and response.rs
  • Template duplicate dependency: edgezero-adapter-spin in both [dependencies] and [target.wasm32.dependencies]

📌 Out of Scope

  • 🌱 No compression/decompression in proxy: Fastly transparently decompresses gzip/brotli upstream responses. Spin passes compressed bytes through raw. Worth tracking for parity.
  • 📌 CLAUDE.md not updated: workspace layout, compilation targets, CI features all omit Spin.

CI Status

  • fmt: PASS
  • clippy: PASS
  • tests: PASS (native)
  • feature check (fastly cloudflare): PASS
  • wasm32-wasip1 compile: FAIL (cargo check -p edgezero-adapter-spin --target wasm32-wasip1 --features spin)

Copy link
Contributor

@prk-Jr prk-Jr left a comment

Choose a reason for hiding this comment

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

Can we update the documentation and readme.

- Fix wasm32 compile error: format!() -> anyhow::anyhow!() in proxy.rs
- Fix scaffold {crate_name} -> {crate} placeholder in Spin build command
- Add proj_{id}_underscored template var for spin.toml wasm filename
- Add spin-sdk to seed_workspace_dependencies for generated projects
- Delegate AppExt::dispatch to request::dispatch (matches CF pattern)
- Change dispatch() return to concrete spin_sdk::http::Response type
- Parse client_addr as Option<IpAddr> instead of Option<String>
- Extract shared collect_body_bytes helper to deduplicate proxy/response
- Add log::warn for silently dropped non-UTF-8 headers
- Simplify init_logger by removing redundant cfg gates
- Add contract tests (2 native, 3 wasm32-gated) with proper cfg split
- Add spin to CI feature check and wasm32-wasip1 compilation step
- Update CLAUDE.md and README.md with Spin adapter documentation
@ChristianPavilonis
Copy link
Contributor Author

Review Fixes — Summary

All blocking and non-blocking findings from @aram356's review have been addressed in commit 7c2a2f9.

Blocking Fixes

Finding Resolution
wasm32 compile error (proxy.rs:48) format!(...)anyhow::anyhow!(...)String doesn't impl Into<anyhow::Error>
{crate_name} placeholder not replaced (cli.rs:178) Changed to {crate} to match what the generator replaces (consistent with Axum adapter)
proj_spin_underscored never populated (spin.toml.hbs:12) Generator now emits proj_{id}_underscored data entry with hyphens replaced by underscores
spin-sdk missing from seed_workspace_dependencies (generator.rs) Added spin-sdk = { version = "5.2", default-features = false }
AppExt duplicates dispatch() logic (lib.rs:42-56) Delegates to request::dispatch() now, matching the Cloudflare adapter pattern. Also changed dispatch() return type from impl IntoResponse to concrete spin_sdk::http::Response to make the delegation work.
Zero contract test cases (tests/contract.rs) Added 5 tests: 2 run natively (context_default_is_empty, build_test_app_creates_valid_router), 3 gated behind wasm32 (from_core_response status/headers, streaming body, empty body). The native/wasm split ensures we get actual CI coverage for the tests that don't need the Spin runtime.
CI feature check missing spin (.github/workflows/test.yml) Added spin to the feature string and added a dedicated cargo check -p edgezero-adapter-spin --target wasm32-wasip1 --features spin step

Non-blocking Fixes

Finding Resolution
Silent header drops (proxy.rs, response.rs, request.rs) Added log::warn! when headers are skipped due to non-UTF-8 values or invalid names
client_addr as raw String (context.rs) Changed to Option<IpAddr>, added parse_client_addr() that handles ip:port, bare IP, and IPv6 bracket notation (with 5 unit tests)
init_logger() identical cfg branches (lib.rs) Removed redundant cfg gates, single unconditional function
Body stream collection duplicated (proxy.rs, response.rs) Extracted collect_body_bytes(body: Body) -> Result<Vec<u8>, EdgeError> as a pub(crate) helper in response.rs, used by both modules

Documentation

Updated CLAUDE.md and README.md to include the Spin adapter in workspace layout, compilation targets, feature gates, CI gates, proxy clients, and logging init references.

Verification

  • cargo fmt --all -- --check
  • cargo clippy --workspace --all-targets --all-features -- -D warnings
  • cargo test --workspace --all-targets — all pass (2 new native contract tests running)
  • cargo check --workspace --all-targets --features "fastly cloudflare spin"
  • cargo check -p edgezero-adapter-spin --target wasm32-wasip1 --features spin

@prk-Jr — documentation and README have been updated with Spin adapter info as requested.

Copy link
Contributor

@aram356 aram356 left a comment

Choose a reason for hiding this comment

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

Follow-up review — critical pathing bugs

The previous review addressed compile errors and code-quality issues. This review focuses on two runtime-breaking bugs that cause edgezero-cli build --adapter spin to fail on any hyphenated crate name or workspace member. Both are reproducible in the examples/app-demo demo.

Summary of findings

Sev Issue Impact
High Artifact lookup uses raw package name (hyphens) but Cargo emits underscores build --adapter spin exits with "compiled artifact not found" for any hyphenated crate
High spin.toml source path points to crate-local target/, but workspace builds emit to workspace target/ spin up fails to find the wasm binary for any workspace member
Medium find_workspace_root climbs to the topmost Cargo.toml, overshooting nested workspaces like examples/app-demo/ Build/deploy commands may search the wrong tree
Medium Non-UTF-8 headers silently dropped across request/response/proxy boundaries Breaks binary headers (signatures, auth tokens) in proxy edge cases
Medium Test suite only exercises simple (non-hyphenated, flat workspace) artifact lookup — misses both regressions above False green on CI

@aram356 aram356 linked an issue Mar 12, 2026 that may be closed by this pull request
Fix artifact lookup hyphen-to-underscore bug in all three adapter CLIs,
stop find_workspace_root at [workspace] tables for nested workspaces,
compute correct target dir in spin.toml template for workspace members,
and use HeaderValue::from_bytes for inbound request/proxy headers.

Adds tests for hyphenated crate names and nested workspace resolution.
@ChristianPavilonis
Copy link
Contributor Author

Review Fixes — Follow-up Review (Mar 11)

All findings from @aram356's follow-up review addressed in commit e9f4548.

File Change
crates/edgezero-adapter-spin/src/cli.rs Fix locate_artifact to use crate_name.replace('-', "_") for .wasm filename; fix dest copy target; add locate_artifact_converts_hyphens_to_underscores test
crates/edgezero-adapter-fastly/src/cli.rs Same hyphen→underscore fix for locate_artifact and dest
crates/edgezero-adapter-cloudflare/src/cli.rs Same hyphen→underscore fix for locate_artifact and dest
crates/edgezero-adapter/src/cli_support.rs find_workspace_root now stops at [workspace] tables; add workspace_root_stops_at_workspace_table test
crates/edgezero-adapter-spin/src/request.rs Use HeaderValue::from_bytes(value) to preserve non-UTF-8 inbound headers
crates/edgezero-adapter-spin/src/proxy.rs Use HeaderValue::from_bytes for proxy response headers; document WASI limitation on outbound
crates/edgezero-adapter-spin/src/response.rs Document WASI limitation for outbound non-UTF-8 headers
crates/edgezero-adapter-spin/src/templates/spin.toml.hbs Use {{target_dir_spin}} for correct workspace-relative source path
crates/edgezero-cli/src/generator.rs Compute target_dir_{id} template variable from crate depth
examples/app-demo/.../spin.toml Fix source path to ../../target/...

Verification

  • cargo fmt --all -- --check
  • cargo clippy --workspace --all-targets --all-features -- -D warnings
  • cargo test --workspace --all-targets — 323 tests pass ✓
  • cargo check --workspace --all-targets --features "fastly cloudflare spin"

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.

Spin Adapter

3 participants