feat(build): feature-sided builds — server/client sides independently selectable (ADR-039 Amendment 1)

Split the feature graph so consumers pulling only the import adapters
(from_openapi / from_jsonschema / from_mcp) no longer compile the axum
/ hyper server stack, and server-only deployments no longer compile
reqwest. One crate, one import path — sides cut by features, not by a
crate split.

Feature graph:
- server (default): axum host, gateway, WS upgrade, to_openapi, to_mcp
- client (default): client host, forward, from_jsonschema, from_openapi
- openapi: shared OpenAPISpec model (implied by both sides)
- mcp: from_mcp needs client, to_mcp needs server
- wss: tungstenite transport (from_wss); tungstenite half of the
  shared WS↔byte-stream adapter
- h2/http1: hyper protocol features; imply server

Wire-contract neutral: gateway endpoints, ALPNs, and all public API
shapes unchanged; defaults keep both sides on.

Supporting changes:
- forward.rs drops its axum::body::Bytes type leak (bytes crate types)
- bounded_join + error-echo caps move to input_validation (usable by
  both sides; openapi_spec no longer imports from forward)
- byte_adapter: axum flavor compiles under server, tungstenite under
  wss; the generic pumps stay shared (WS-11)
- input_validation / openapi_spec import-only internals gated to the
  side that consumes them
- http-body-util moves to dev-dependencies (was test-only)
- integration-test required-features updated for the new sides
- from_wss unit tests (axum producer harness) gated to server

Verified: cargo test (defaults, 453) and --all-features (575) pass;
lean side builds (client / server / client,mcp / client,wss /
server,wss / openapi-only) build clean with zero warnings;
clippy -D warnings clean across all feature combinations; fmt clean.
This commit is contained in:
2026-08-31 17:19:05 +00:00
parent 8e8e1f2b14
commit a80f9948b8
16 changed files with 259 additions and 99 deletions
+56 -21
View File
@@ -14,39 +14,58 @@ exclude = [".opencode/", "AGENTS.md", "docs/", "tasks/", "Cargo.lock"]
name = "alkhttp"
[features]
default = ["h2", "http1"]
default = ["server", "client", "h2", "http1"]
# Producer side: the axum `Router` host (gateway routes, /healthz,
# /openapi.json, /mcp, the WS upgrade path) and the hyper connection
# driver. `openapi` rides along because /openapi.json is part of the
# default surface (ADR-042/045); `http1` rides along so the hyper
# auto-builder always has at least one protocol compiled.
server = ["dep:axum", "dep:hyper", "dep:hyper-util", "dep:uuid", "openapi", "http1"]
# Consumer side: the shared outbound client host and the HTTP-backed
# import adapters (`from_jsonschema`, `from_openapi` with `openapi`,
# `from_mcp` with `mcp`).
client = ["dep:arc-swap", "dep:httpdate", "dep:percent-encoding", "dep:reqwest", "dep:reqwest-middleware", "dep:reqwest-retry", "dep:url"]
# Shared OpenAPI document model: `from_openapi` parsing (client side)
# and the `to_openapi` gateway projection (server side).
openapi = ["dep:yaml_serde"]
# MCP adapters: `from_mcp` needs `client`, `to_mcp` needs `server`.
mcp = ["dep:rmcp"]
# WebSocket transport (from_wss consumer; tungstenite flavor of the
# WS ↔ byte-stream adapter).
wss = ["dep:tokio-tungstenite"]
test-support = ["dep:tokio-tungstenite"]
h2 = ["dep:hyper", "hyper-util/http2", "hyper/http2"]
http1 = ["dep:hyper", "hyper-util/http1", "hyper/http1"]
# Test-only WS client helpers (downstream deployment tests); implies
# `server` — the helpers live with the upgrade path.
test-support = ["server", "dep:tokio-tungstenite"]
# HTTP protocol features of the server connection driver (ADR-001).
h2 = ["server", "dep:hyper", "hyper-util/http2", "hyper/http2"]
http1 = ["server", "dep:hyper", "hyper-util/http1", "hyper/http1"]
[dependencies]
alkcall = { version = "0.2", features = ["gateway"] }
arc-swap = "1"
axum = { version = "0.8", features = ["ws"] }
arc-swap = { version = "1", optional = true }
axum = { version = "0.8", optional = true, features = ["ws"] }
bytes = "1"
futures = "0.3"
http = "1"
httpdate = { version = "1", optional = true }
hyper = { version = "1", optional = true, features = ["server"] }
hyper-util = { version = "0.1", features = ["server", "service", "tokio"] }
httpdate = "1"
hyper-util = { version = "0.1", optional = true, features = ["server", "service", "tokio"] }
jsonschema = { version = "0.46", default-features = false }
reqwest = { version = "0.13", default-features = false, features = ["json", "stream", "rustls"] }
reqwest-middleware = "0.5"
reqwest-retry = "0.9"
parking_lot = "0.12"
percent-encoding = { version = "2", optional = true }
reqwest = { version = "0.13", optional = true, default-features = false, features = ["json", "stream", "rustls"] }
reqwest-middleware = { version = "0.5", optional = true }
reqwest-retry = { version = "0.9", optional = true }
tokio = { version = "1", features = ["macros", "rt-multi-thread", "io-util", "net", "fs", "time", "sync"] }
tokio-tungstenite = { version = "0.29", optional = true, default-features = false, features = ["connect", "rustls-tls-webpki-roots", "handshake"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
yaml_serde = "0.10"
async-trait = "0.1"
tracing = "0.1"
thiserror = "2"
uuid = { version = "1", features = ["v4"] }
futures = "0.3"
http = "1"
http-body-util = "0.1"
url = "2"
percent-encoding = "2"
parking_lot = "0.12"
uuid = { version = "1", optional = true, features = ["v4"] }
url = { version = "2", optional = true }
yaml_serde = { version = "0.10", optional = true }
rmcp = { version = "1.8", optional = true, default-features = false, features = [
"client",
"server",
@@ -75,8 +94,24 @@ required-features = ["test-support"]
[[test]]
name = "from_mcp_integration"
required-features = ["mcp"]
required-features = ["server", "client", "mcp"]
[[test]]
name = "full_surface"
required-features = ["mcp", "test-support"]
required-features = ["server", "client", "openapi", "mcp", "test-support"]
[[test]]
name = "client_config_reload"
required-features = ["client"]
[[test]]
name = "client_tls"
required-features = ["client"]
[[test]]
name = "retry_after_budget"
required-features = ["client"]
[[test]]
name = "retry_policy_wire"
required-features = ["client"]