EdgeZero is a portable HTTP workload toolkit in Rust. Write once, deploy to
Fastly Compute, Cloudflare Workers, Fermyon Spin, or native Axum servers. The
codebase is a Cargo workspace with 8 crates under crates/, an example app
under examples/app-demo/, a VitePress documentation site under docs/, and
CI workflows under .github/workflows/.
crates/
edgezero-core/ # Core: routing, extractors, middleware, proxy, body, errors
edgezero-macros/ # Proc macros: #[action], #[app]
edgezero-adapter/ # Adapter registry and traits
edgezero-adapter-fastly/ # Fastly Compute bridge (wasm32-wasip1)
edgezero-adapter-cloudflare/# Cloudflare Workers bridge (wasm32-unknown-unknown)
edgezero-adapter-spin/ # Fermyon Spin bridge (wasm32-wasip1)
edgezero-adapter-axum/ # Axum/Tokio bridge (native, dev server)
edgezero-cli/ # CLI: new, build, deploy, dev, serve
examples/app-demo/ # Reference app with all 4 adapters (excluded from workspace)
docs/ # VitePress documentation site (Node.js)
scripts/ # Build/deploy/test helper scripts
- Rust: 1.91.1 (from
.tool-versions) - Node.js: 24.12.0 (for docs site only)
- Fastly CLI: v13.0.0
- Edition: 2021
- Resolver: 2
- License: Apache-2.0
# Full workspace test (primary CI command)
cargo test --workspace --all-targets
# Test a specific crate
cargo test -p edgezero-core
cargo test -p edgezero-adapter-fastly
cargo test -p edgezero-cli
# Lint & format (must pass CI)
cargo fmt --all -- --check
cargo clippy --workspace --all-targets --all-features -- -D warnings
# Feature compilation check
cargo check --workspace --all-targets --features "fastly cloudflare spin"
# Spin wasm32 compilation check
cargo check -p edgezero-adapter-spin --target wasm32-wasip1 --features spin
# Run the demo dev server
cargo run -p edgezero-cli --features dev-example -- dev
# Docs site
cd docs && npm ci && npm run devAlways run cargo test after touching code. Use -p <crate-name> for
faster iteration on a single crate.
| Adapter | Target | Notes |
|---|---|---|
| Fastly | wasm32-wasip1 |
Requires Viceroy for local testing |
| Cloudflare | wasm32-unknown-unknown |
Requires wrangler for dev/deploy |
| Spin | wasm32-wasip1 |
Requires spin CLI for dev/deploy |
| Axum | Native (host triple) | Standard Tokio runtime |
Use matchit 0.8+ brace syntax for path parameters:
// CORRECT
"/resource/{id}"
"/static/{*rest}"
// WRONG — legacy colon syntax is not supported
"/resource/:id"Use the #[action] macro for all new handlers:
use edgezero_core::{action, Json, Path, Query, ValidatedJson, Response, EdgeError};
#[action]
async fn my_handler(
Json(body): Json<MyPayload>,
Path(params): Path<MyParams>,
) -> Result<Response, EdgeError> {
// handler body
}- Import HTTP type aliases from
edgezero_core(Method,StatusCode,HeaderMap, etc.) — never from thehttpcrate directly. - Extractors implement
FromRequest. UseValidatedJson<T>/ValidatedPath<T>/ValidatedQuery<T>for automaticvalidatorcrate integration. RequestContextcan be destructured:RequestContext(ctx): RequestContext.
- Use
EdgeErrorwith semantic constructors:EdgeError::validation(),EdgeError::internal(), etc. - Map to provider-specific errors only at the adapter boundary.
- Prefer
Result<Response, EdgeError>as handler return type.
Implement the Middleware trait. Chain via Next::run():
struct MyMiddleware;
impl Middleware for MyMiddleware {
async fn handle(&self, ctx: RequestContext, next: Next) -> Result<Response, EdgeError> {
// before
let response = next.run(ctx).await?;
// after
Ok(response)
}
}Use ProxyService with adapter-specific clients (FastlyProxyClient,
CloudflareProxyClient, SpinProxyClient). Keep proxy logic provider-agnostic
in core.
- Adapter-specific init:
edgezero_adapter_fastly::init_logger(),edgezero_adapter_cloudflare::init_logger(),edgezero_adapter_spin::init_logger(). - Use
simple_loggerfor local/Axum builds. - Use the
log/tracingfacade, not direct dependencies.
- WASM compatibility first: avoid Tokio and runtime-specific deps in core
and adapter crates. Use
async-traitwithoutSendbounds. Useweb-timeinstead ofstd::time::Instant. - Colocate tests with implementation modules (
#[cfg(test)]in the same file). - Async tests use
futures::executor::block_on(not Tokio) for WASM compat. - Minimal changes: every change should impact as little code as possible. Avoid unnecessary refactoring, docstrings on untouched code, or premature abstractions.
- Feature gates: platform-specific code goes behind
fastly,cloudflare,spin, oraxumfeatures. Core staysdefault-features = falsefor WASM targets. - No direct
httpcrate imports in application code — useedgezero_corere-exports.
Each adapter follows the same structure:
context.rs— platform-specific request contextrequest.rs— platform request → core request conversionresponse.rs— core response → platform response conversionproxy.rs— platform-specific proxy clientlogger.rs— platform-specific logging initcli.rs— build/deploy commands (behindclifeature)
Contract tests live in tests/contract.rs within each adapter crate.
The manifest drives routing, env bindings, and per-adapter build/deploy config.
Key sections: [app], [[triggers.http]], [environment], [adapters].
Every PR must pass:
cargo fmt --all -- --checkcargo clippy --workspace --all-targets --all-features -- -D warningscargo test --workspace --all-targetscargo check --workspace --all-targets --features "fastly cloudflare spin"cargo check -p edgezero-adapter-spin --target wasm32-wasip1 --features spin
Docs CI additionally runs ESLint + Prettier on the docs/ directory.
- Read & plan: think through the problem, read the codebase for relevant files, and present a plan as a checklist inline in the conversation.
- Get approval first: show the full plan and get approval before commencing any coding work.
- Implement incrementally: work through the checklist items. Make every task and code change as simple as possible — every change should impact as little code as possible.
- Test after every change: run
cargo test(or scoped-p <crate>) after touching any code. - Explain as you go: after completing each item, give a high-level explanation of what changes you made.
- If blocked: explain what's blocking and why.
- Verify, don't assume: after implementing a change, prove it works. Run
tests, check
cargo clippy, and compare behavior againstmainwhen relevant. Don't say "it works" without evidence. - Plan review: for complex tasks, review your own plan as a staff engineer would before implementing. Ask: is this the simplest approach? Does it touch too many files? Are there edge cases?
- Escape hatch: if an implementation is going sideways after multiple iterations, step back and reconsider. Scrap the approach and implement the simpler solution rather than patching a flawed design.
- Use subagents: for tasks spanning multiple crates or requiring broad codebase exploration, use subagents to parallelize investigation and keep the main context clean.
Specialized agents live in .claude/agents/. Use them to distribute work:
| Agent | Purpose |
|---|---|
code-simplifier |
Simplifies code after work is done — dead code, duplication, nesting |
verify-app |
End-to-end verification: tests, lint, WASM builds, dev server smoke test |
build-validator |
Validates builds across all targets and feature combinations |
code-architect |
Architectural review — evaluates designs against project principles |
pr-creator |
Creates or updates GitHub PRs using the project template and CI gates |
issue-creator |
Creates GitHub issues with proper types (Task/Bug/Story/Epic) via GraphQL |
pr-reviewer |
Staff-engineer PR review with inline GitHub comments (user approves) |
repo-explorer |
Read-only codebase mapping for unfamiliar areas and cross-crate flow |
Invoke with "use subagents" in your prompt or reference a specific agent by name.
For tasks spanning multiple crates, adapters, or unclear failures, use this flow:
- Phase 1 — Parallel investigation (read-only): launch 2-4 subagents with non-overlapping scopes (tests, diff review, architecture checks). Each subagent must return concrete findings with file paths and line references.
- Phase 2 — Parallel solution proposals (no edits): launch at least 2 subagents to propose minimal fix strategies based on Phase 1 findings. Compare tradeoffs (scope, risk, CI impact) before coding.
- Phase 3 — Single-path implementation: pick one plan (smallest safe change) and implement centrally. Do not let multiple subagents edit overlapping files at the same time.
- Phase 4 — Verification handoff:
run
verify-apporbuild-validatorfor broad changes, then summarize pass/fail by step. - Phase 5 — Decision log: report which subagents were used, what each found, and why the chosen plan was selected.
Default trigger:
- If work touches 2+ crates or includes both runtime behavior and build/tooling changes, use this workflow.
| Situation | Use first | Optional follow-up | Expected output |
|---|---|---|---|
| Unfamiliar code area | repo-explorer |
code-architect |
File map and risk hotspots |
| Multi-crate feature change | repo-explorer |
code-architect, build-validator |
Change plan and validation scope |
| CI/build failures | build-validator |
repo-explorer |
Failing combos and fault area |
| Design/API proposal | code-architect |
repo-explorer |
Architecture concerns and options |
| Cleanup/refactor pass | code-simplifier |
build-validator |
Simplification summary and checks |
| Pre-PR readiness | build-validator |
verify-app, pr-creator |
Pass/fail report and PR draft |
| PR review | pr-reviewer |
code-architect, repo-explorer |
Inline GitHub review with findings |
Use at least 2 subagents when:
- The task touches 2+ crates.
- The change affects both runtime behavior and CI/build tooling.
Custom commands live in .claude/commands/:
| Command | Purpose |
|---|---|
/check-ci |
Run all 4 CI gate checks locally |
/test-all |
Run full workspace test suite |
/test-crate |
Run tests for a specific crate |
/review-changes |
Staff-engineer-level review of uncommitted changes |
/verify |
Prove current changes work vs main |
- Context7 MCP: use for up-to-date library docs and coding examples.
| Purpose | Path |
|---|---|
| Workspace manifest | Cargo.toml |
| Core crate entry | crates/edgezero-core/src/lib.rs |
| Router | crates/edgezero-core/src/router.rs |
| Extractors | crates/edgezero-core/src/extractor.rs |
| Action macro | crates/edgezero-macros/src/action.rs |
| CLI entry | crates/edgezero-cli/src/main.rs |
| Demo app | examples/app-demo/ |
| Demo manifest | examples/app-demo/edgezero.toml |
| CI tests | .github/workflows/test.yml |
| CI format/lint | .github/workflows/format.yml |
| Docs site | docs/ |
| Test script | scripts/run_tests.sh |
- Workspace-level dependency management via
[workspace.dependencies]in rootCargo.toml. - Minimal, carefully curated for WASM compatibility.
Cargo.lockis committed for reproducible builds.- Key crates:
matchit(routing),tower(middleware),async-trait,async-compression,serde/serde_json,validator,clap(CLI),handlebars(templates).
- Don't use legacy
:idroute syntax — always use{id}. - Don't import from
httpcrate directly — useedgezero_corere-exports. - Don't add Tokio dependencies to core or adapter crates.
- Don't write tests that require a network connection or platform credentials.
- Don't make large, sweeping refactors — keep changes minimal and focused.
- Don't commit without running
cargo testfirst. - Don't skip
cargo fmtandcargo clippy— CI will reject the PR. - Don't include
Co-Authored-Bytrailers, "Generated with" footers, or any AI bylines in commits or PR bodies.