feat: server foundation (phase 1 core) — state, auth, healthz/decoy, gateway dispatch, HttpAdapter
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.
This commit is contained in:
@@ -0,0 +1,88 @@
|
||||
//! 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
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user