Skip to content

Adapter Overview ​

Adapters bridge provider-specific HTTP primitives into EdgeZero's portable model. This document defines the contract that all adapters must fulfill.

Goals ​

Adapters translate provider-specific HTTP primitives into the portable App in edgezero-core. They must:

  • Preserve request semantics
  • Stream responses without buffering where the provider supports it
  • Expose provider context
  • Offer a proxy bridge so handlers can forward traffic without knowing which platform they are on

Request Conversion ​

Each adapter exposes an into_core_request helper that accepts the provider's request type and returns edgezero_core::http::Request. The conversion must:

  • Preserve the HTTP method exactly (GET, POST, etc.)
  • Parse the full URI (path and query string) into an http::Uri. Reject invalid URIs with EdgeError::bad_request
  • Copy all headers into the core request. Provider-specific headers may be filtered only when they clash with platform defaults
  • Consume the request body into an edgezero_core::body::Body. Adapters may buffer inbound bodies today; streaming input should be preserved where available
  • Insert a provider context struct (e.g., FastlyRequestContext) into the request extensions. The context should expose metadata such as client IP addresses or environment handles so handlers can reach platform APIs

Response Conversion ​

Adapters also expose from_core_response (or equivalent) to transform an edgezero_core::http::Response into the provider response type. Implementations must:

  • Map HTTP status codes verbatim
  • Copy headers, respecting casing rules enforced by the provider
  • Preserve streaming bodies - Body::Stream should be written chunk-by-chunk to the provider output without buffering the entire payload. Only Cloudflare does this today, via Response::from_stream. Fastly drains the stream into a fastly::Body, Spin collects it into a Vec<u8> capped at 16 MiB, and Axum buffers as well
  • Handle encoding helpers (decode_gzip_stream, decode_brotli_stream) where a provider requires transparent decompression

Dispatch Helper ​

Adapters surface a dispatch entry point, either a free function or a service builder's dispatch method, that bridges from the provider event loop into the shared router (App::router().oneshot(...)). It should:

  1. Convert the incoming provider request with into_core_request
  2. Await the router future
  3. Convert the resulting Response back into the provider type
  4. Map any EdgeError into the provider's error type so failures surface as provider errors (often 5xx) instead of panicking

This helper is what demo entrypoints and adapters call when wiring their platform-specific main functions.

Store Registry Resolution ​

All four adapters resolve KV, config, and secret stores from the portable Hooks::stores() metadata baked by the app! macro plus EDGEZERO__* environment variables (see the migration guide for the schema change). Each adapter builds a per-request StoreRegistry<H> keyed by logical id; handlers reach a bound store via the id-keyed Kv / Secrets / Config extractors or the matching ctx.kv_store(id) / ctx.config_store(id) / ctx.secret_store(id) accessors. The pre-rewrite Hooks::config_store() hook is gone.

Proxy Integration ​

Adapters implement edgezero_core::proxy::ProxyClient so handlers can forward outbound requests. The client must:

  • Accept a ProxyRequest created with ProxyRequest::from_request
  • Build and send an outbound provider request, reusing headers and streaming the body without buffering
  • Convert the provider response into a ProxyResponse, again preserving streaming behaviour and normalising encodings
  • Attach a diagnostic header (e.g., x-edgezero-proxy) identifying which adapter forwarded the call (Fastly, Cloudflare, and Spin do this today; Axum does not)
  • Surface provider errors as EdgeError::internal so applications can decide how to respond

Logging Initialisation ​

Each adapter exports an init_logger helper for platform-specific logging backends. Fastly wires log_fastly, Cloudflare currently no-ops, Spin no-ops because Spin manages its own logging internally, and Axum uses simple_logger in its run_app helper. New adapters should provide a comparable helper so apps consistently opt into logging.

Contract Tests ​

To keep the contract enforceable, each adapter includes integration tests that validate request/response conversions and the dispatch helper:

  • into_core_request for method, URI, header, body, and context propagation
  • from_core_response for status propagation and streamed body writes
  • dispatch for routed handlers, body passthrough, and streaming responses

Fastly Tests ​

Because the Fastly SDK links against the Compute@Edge host functions, the contract tests compile only for wasm32-wasip1. Run them with:

bash
rustup target add wasm32-wasip1
cargo install viceroy --locked
export CARGO_TARGET_WASM32_WASIP1_RUNNER="viceroy run"
cargo test -p edgezero-adapter-fastly --features fastly --target wasm32-wasip1 --test contract

Fastly's SDK-linked test binaries need Viceroy for execution; plain Wasmtime does not provide the required fastly_* host imports.

Cloudflare Tests ​

Cloudflare's adapter relies on wasm32-unknown-unknown. The contract suite uses wasm-bindgen-test to run under the Workers runtime shims:

bash
rustup target add wasm32-unknown-unknown
export CARGO_TARGET_WASM32_UNKNOWN_UNKNOWN_RUNNER=wasm-bindgen-test-runner
cargo test -p edgezero-adapter-cloudflare --features cloudflare --target wasm32-unknown-unknown --test contract

Install a wasm-bindgen-cli version that matches the workspace's wasm-bindgen entry in Cargo.lock before running the Cloudflare tests.

Spin Tests ​

Spin's adapter targets wasm32-wasip2 and its contract suite runs under Wasmtime:

bash
rustup target add wasm32-wasip2
export CARGO_TARGET_WASM32_WASIP2_RUNNER="wasmtime run"
cargo test -p edgezero-adapter-spin --features spin --target wasm32-wasip2 --test contract

The wasmtime version CI uses is pinned in .tool-versions.

Onboarding New Adapters ​

When bringing up another adapter:

  1. Implement request/response conversion functions that follow the rules above
  2. Provide a context type exposing the adapter's metadata and insert it in into_core_request
  3. Implement a dispatch entry point (free function or service builder) plus logging helper
  4. Wire up a ProxyClient that streams bodies and normalises encodings
  5. Copy the contract test suite, swapping in the new adapter types. Ensure the tests are gated to the target architecture if the adapter SDK does not compile for native hosts
  6. Register the adapter with edgezero-adapter::register_adapter (typically in a cli module using the ctor crate) so the CLI can discover it dynamically. To take part in provision and config push, override the relevant Adapter trait hooks: single_store_kinds and merged_id_kinds declare the platform's store shape, validate_adapter_manifest checks the adapter's own manifest, provision creates platform resources, and push_config_entries plus read_config_entry move config blobs. single_store_kinds and merged_id_kinds default to empty, validate_adapter_manifest defaults to accepting, provision defaults to a no-op, and the config push and read hooks default to reporting the operation as unsupported

Adapters that fulfil these steps can be dropped into the EdgeZero CLI without requiring changes to application code.

Available Adapters ​

AdapterPlatformTargetStatus
FastlyFastly Compute@Edgewasm32-wasip1Stable
CloudflareCloudflare Workerswasm32-unknown-unknownStable
SpinFermyon Spinwasm32-wasip2Stable
AxumNative (Tokio)HostStable

Store Capabilities ​

single_store_kinds lists the kinds that allow only one declared id; merged_id_kinds lists the kinds that share one underlying platform resource, so declaring the same logical id under both is a collision that config validate rejects.

AdapterSingle-store kindsMerged kindsConfig GCStaging lifecycle
FastlynonenoneYesYes
Cloudflaresecretskv, configNoNo
Spinsecretskv, configNoNo
AxumsecretsnoneNoNo

Fastly is the only adapter implementing gc_config_entries and the staging lifecycle actions (DeployStaged, EmitVersion, Healthcheck, Rollback); the others return an unsupported error for those.

Released under the Apache License 2.0.