Last modified: 2026-05-17
- Rust 1.82+ (workspace MSRV, pinned in
Cargo.toml) - Cargo (comes with Rust)
- Node.js 18+ (for e2e test backends)
- cmake (for Pingora's BoringSSL dependency)
# Debug build (fast compilation)
cargo build --workspace
# Release build (optimized)
cargo build --release -p sbproxy# Run all unit tests
cargo test --workspace
# Run tests for a specific crate
cargo test -p sbproxy-modules
cargo test -p sbproxy-ai
cargo test -p sbproxy-extension
# Run with output
cargo test -p sbproxy-modules -- --nocapture
# Run a specific test
cargo test -p sbproxy-modules json_transform_set_fields# Start with a config file
./target/release/sbproxy --config sb.yml
# The config format is YAML:
# proxy:
# http_bind_port: 8080
# origins:
# "example.com":
# action:
# type: proxy
# url: http://backend:3000See docs/architecture.md for the full architecture guide.
The project is a Cargo workspace with 20 crates under crates/. Each crate has a single responsibility.
- Choose the module type: action, auth, policy, or transform
- Add your config struct to the appropriate file in
sbproxy-modules/src/{type}/ - Add a new variant to the enum in
sbproxy-modules/src/{type}/mod.rs - Update the match arms in
*_type(),Debug, andapply()/check()methods - Add a match arm in
sbproxy-modules/src/compile.rsfor your type name - Write unit tests
- Run
cargo test --workspace
Example, adding a new policy:
// In sbproxy-modules/src/policy/mod.rs
pub enum Policy {
// ... existing variants ...
MyNewPolicy(MyNewPolicy),
Plugin(Box<dyn PolicyEnforcer>),
}
// In a new file or same file:
#[derive(Debug, Deserialize)]
pub struct MyNewPolicy {
pub some_field: String,
}
impl MyNewPolicy {
pub fn from_config(value: serde_json::Value) -> anyhow::Result<Self> {
Ok(serde_json::from_value(value)?)
}
pub fn check(&self) -> bool { true }
}
// In compile.rs:
"my_new_policy" => Ok(Policy::MyNewPolicy(MyNewPolicy::from_config(config.clone())?)),Out-of-tree crates can register their own actions, auth providers, policies, or transforms via inventory. The proxy discovers them at link time, so no central wiring change is needed.
// In your-crate/src/policy.rs
use sbproxy_plugin::*;
pub struct MyPolicy { /* ... */ }
impl PolicyEnforcer for MyPolicy {
fn policy_type(&self) -> &'static str { "my_policy" }
fn enforce(&self, req: &http::Request<bytes::Bytes>, ctx: &mut dyn std::any::Any)
-> Pin<Box<dyn Future<Output = Result<PolicyDecision>> + Send + '_>>
{
Box::pin(async move { Ok(PolicyDecision::Allow) })
}
}
inventory::submit! {
PluginRegistration {
kind: PluginKind::Policy,
name: "my_policy",
factory: |config| { /* ... */ },
}
}- Follow
rustfmtdefaults - Prefer
anyhow::Resultfor fallible functions - Use
CompactStringfor short strings (hostnames, IDs) - Use
SmallVecfor small collections (policies, transforms) - Write doc comments on all public types and functions
Run all five before pushing. Each one mirrors a required CI gate; if any fails locally, CI will fail too.
| Check | Command |
|---|---|
| Format | cargo fmt --all -- --check |
| Build | cargo build --workspace |
| Test | cargo test --workspace --release --tests |
| Clippy | cargo clippy --workspace --all-targets -- -D warnings |
| Docs | RUSTDOCFLAGS="-D warnings" cargo doc --workspace --no-deps --document-private-items |
Fix the issue before pushing. Do not paper over with #[allow(...)] unless you also write a one-line comment explaining the deliberate exception.
Two suites ship in-tree. Both run against the release binary:
e2e/tests/*.rsis the Rust-native suite, driven bycargo test -p sbproxy-e2e --release. One file per feature, typed harness.e2e/conformance/is the vendored curl + bash conformance suite (93 cases). It is the strictest HTTP wire-protocol harness we ship.
# Run the curl conformance suite (all cases)
./scripts/run-e2e.sh
# Run specific cases
./scripts/run-e2e.sh 01 14 11See e2e/conformance/HOW-TO-RUN.md for the side-by-side comparison of the two suites.
# Run all benchmarks
cargo bench --workspace
# Run specific benchmark
cargo bench -p sbproxy-modules -- json_transform