Tasks completed: server-core-types, server-auth, server-healthz-decoy, gateway-dispatch, server-adapter (5 of 17). - src/server/state.rs: DecoyConfig + RouterState (alkcall type paths, 6-endpoint reserved-path docs) - src/server/auth.rs: bearer middleware + ResolvedIdentity extractor (10 tests: missing/malformed/basic/failed-resolution matrix) - src/server/healthz.rs + decoy.rs: raw healthz; nginx-style 404, static site (path-traversal guarded), redirect decoys - src/gateway/dispatch.rs: GatewayDispatch invoke/invoke_streaming (internal:false, forwarded_for:None, bounded deadline) + src/gateway/error.rs: CallError→HTTP status mapping (HTTP_<status> passthrough, retryable→Retry-After) - src/server/adapter.rs: HttpAdapter ProtocolHandler — accept_bi → BiStream → TokioIo → hyper auto builder (h2 CONNECT enabled); integration tests over DuplexStream (request/response cycle, healthz, decoy 404) Verified: cargo test (46 lib tests), clippy -D warnings, fmt, test --all-features.
89 lines
2.8 KiB
Rust
89 lines
2.8 KiB
Rust
//! Shared server state and configuration for the `HttpAdapter` router.
|
|
|
|
use std::path::PathBuf;
|
|
use std::sync::Arc;
|
|
|
|
use alkcall::core::auth::IdentityProvider;
|
|
use alkcall::registry::registration::OperationRegistry;
|
|
|
|
/// The stealth decoy surface for paths that are not registered
|
|
/// operations and not part of the reserved default surface (the 6
|
|
/// gateway endpoints, `/healthz`, `/openapi.json`, the MCP route, the
|
|
/// WS upgrade path). Set by the assembly layer at `HttpAdapter`
|
|
/// construction. The existence of the decoy path is fixed by ADR-010;
|
|
/// the variant is a two-way-door config default.
|
|
#[derive(Clone, Default, Debug)]
|
|
pub enum DecoyConfig {
|
|
/// Serve a fake `404 Not Found` (the default — a fake nginx 404).
|
|
#[default]
|
|
NotFound,
|
|
/// Serve a static site from a configured directory.
|
|
StaticSite { root: PathBuf },
|
|
/// Redirect to a configured URL.
|
|
Redirect { to: String },
|
|
}
|
|
|
|
/// State embedded in the axum `Router`: the registry and identity
|
|
/// provider every request handler reaches through the router state, plus
|
|
/// the decoy config for the fallback.
|
|
#[derive(Clone)]
|
|
pub(crate) struct RouterState {
|
|
pub(crate) registry: Arc<OperationRegistry>,
|
|
pub(crate) identity_provider: Arc<dyn IdentityProvider>,
|
|
pub(crate) decoy: DecoyConfig,
|
|
}
|
|
|
|
impl axum::extract::FromRef<RouterState> for DecoyConfig {
|
|
fn from_ref(state: &RouterState) -> Self {
|
|
state.decoy.clone()
|
|
}
|
|
}
|
|
|
|
impl axum::extract::FromRef<RouterState> for Arc<OperationRegistry> {
|
|
fn from_ref(state: &RouterState) -> Self {
|
|
Arc::clone(&state.registry)
|
|
}
|
|
}
|
|
|
|
impl axum::extract::FromRef<RouterState> for Arc<dyn IdentityProvider> {
|
|
fn from_ref(state: &RouterState) -> Self {
|
|
Arc::clone(&state.identity_provider)
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn decoy_config_default_is_not_found() {
|
|
assert!(matches!(DecoyConfig::default(), DecoyConfig::NotFound));
|
|
}
|
|
|
|
#[test]
|
|
fn router_state_from_ref_extracts_decoy() {
|
|
let state = RouterState {
|
|
registry: Arc::new(OperationRegistry::new()),
|
|
identity_provider: Arc::new(NoopProvider),
|
|
decoy: DecoyConfig::Redirect {
|
|
to: "https://example.com".to_string(),
|
|
},
|
|
};
|
|
let extracted: DecoyConfig = axum::extract::FromRef::from_ref(&state);
|
|
assert!(matches!(extracted, DecoyConfig::Redirect { .. }));
|
|
}
|
|
|
|
struct NoopProvider;
|
|
impl IdentityProvider for NoopProvider {
|
|
fn resolve_from_fingerprint(&self, _: &str) -> Option<alkcall::core::auth::Identity> {
|
|
None
|
|
}
|
|
fn resolve_from_token(
|
|
&self,
|
|
_: &alkcall::core::auth::AuthToken,
|
|
) -> Option<alkcall::core::auth::Identity> {
|
|
None
|
|
}
|
|
}
|
|
}
|