use crate::proxy::middleware::*;
use crate::proxy::types::BypassPath;
use axum::{
middleware::{from_fn, from_fn_with_state},
Router,
};
use std::sync::Arc;
pub struct ProxyMiddlewareStack {
auth_config: Arc<AuthConfig>,
}
impl ProxyMiddlewareStack {
pub fn new(auth_config: AuthConfig) -> Self {
Self {
auth_config: Arc::new(auth_config),
}
}
pub fn apply_to_router<S>(self, router: Router<S>) -> Router<S>
where
S: Clone + Send + Sync + 'static,
{
router
.layer(from_fn_with_state(
self.auth_config.clone(),
auth_middleware,
))
.layer(from_fn(error_handling_middleware))
.layer(from_fn(logging_middleware))
.layer(from_fn(request_id_middleware))
}
#[cfg(test)]
pub fn minimal() -> Self {
Self::new(AuthConfig::default())
}
pub fn with_auth(mut self, auth_config: AuthConfig) -> Self {
self.auth_config = Arc::new(auth_config);
self
}
}
#[derive(Clone, Debug)]
pub struct ProxyMiddlewareConfig {
pub auth: AuthConfig,
pub enable_logging: bool,
pub detailed_errors: bool,
pub enable_request_id: bool,
pub enable_health_check: bool,
pub enable_metrics: bool,
}
impl Default for ProxyMiddlewareConfig {
fn default() -> Self {
Self {
auth: AuthConfig::default(),
enable_logging: true,
detailed_errors: false,
enable_request_id: true,
enable_health_check: true,
enable_metrics: true,
}
}
}
impl ProxyMiddlewareConfig {
pub fn build_stack(self) -> ProxyMiddlewareStack {
let mut auth_config = self.auth;
if self.enable_health_check {
auth_config.bypass_paths.insert(
BypassPath::try_new(crate::proxy::headers::paths::HEALTH.to_string())
.expect("HEALTH path should be valid"),
);
}
if self.enable_metrics {
auth_config.bypass_paths.insert(
BypassPath::try_new(crate::proxy::headers::paths::METRICS.to_string())
.expect("METRICS path should be valid"),
);
}
ProxyMiddlewareStack::new(auth_config)
}
pub fn disable_health_check(mut self) -> Self {
self.enable_health_check = false;
self
}
pub fn disable_metrics(mut self) -> Self {
self.enable_metrics = false;
self
}
pub fn disable_logging(mut self) -> Self {
self.enable_logging = false;
self
}
pub fn enable_detailed_errors(mut self) -> Self {
self.detailed_errors = true;
self
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::proxy::headers::{paths, X_REQUEST_ID};
use crate::proxy::types::ApiKey;
use axum::{body::Body, http::StatusCode, response::IntoResponse};
use std::collections::HashSet;
use tower::ServiceExt;
#[tokio::test]
async fn test_middleware_stack_builder() {
async fn handler() -> impl IntoResponse {
StatusCode::OK
}
let router = Router::new()
.route("/test", axum::routing::get(handler))
.with_state(());
let mut auth_config = AuthConfig::default();
auth_config
.api_keys
.insert(ApiKey::try_new("test-key".to_string()).unwrap());
let stack = ProxyMiddlewareStack::new(auth_config);
let app = stack.apply_to_router(router);
let response = app
.oneshot(
axum::http::Request::builder()
.uri("/test")
.header("Authorization", "Bearer test-key")
.body(Body::empty())
.unwrap(),
)
.await
.unwrap();
assert_eq!(response.status(), StatusCode::OK);
assert!(response.headers().contains_key(X_REQUEST_ID));
}
#[tokio::test]
async fn test_middleware_stack_health_bypass() {
async fn handler() -> impl IntoResponse {
StatusCode::OK
}
let router = Router::new()
.route(paths::HEALTH, axum::routing::get(handler))
.with_state(());
let stack = ProxyMiddlewareStack::minimal();
let app = stack.apply_to_router(router);
let response = app
.oneshot(
axum::http::Request::builder()
.uri(paths::HEALTH)
.body(Body::empty())
.unwrap(),
)
.await
.unwrap();
assert_eq!(response.status(), StatusCode::OK);
}
#[tokio::test]
async fn test_middleware_config_builder() {
let config = ProxyMiddlewareConfig {
auth: AuthConfig::default(),
enable_logging: true,
detailed_errors: false,
enable_request_id: true,
enable_health_check: true,
enable_metrics: true,
};
let stack = config.build_stack();
assert!(Arc::strong_count(&stack.auth_config) == 1);
}
#[test]
fn test_middleware_config_builder_methods() {
let config = ProxyMiddlewareConfig::default()
.disable_health_check()
.disable_metrics()
.enable_detailed_errors();
assert!(!config.enable_health_check);
assert!(!config.enable_metrics);
assert!(config.detailed_errors);
assert!(config.enable_logging); }
#[test]
fn test_middleware_config_bypass_paths() {
let mut config = ProxyMiddlewareConfig::default();
config
.auth
.api_keys
.insert(ApiKey::try_new("test-key".to_string()).unwrap());
let stack = config.build_stack();
assert_eq!(stack.auth_config.bypass_paths.len(), 2);
}
#[test]
fn test_middleware_config_no_bypass_paths() {
let mut auth_config = AuthConfig {
api_keys: HashSet::new(),
bypass_paths: HashSet::new(), };
auth_config
.api_keys
.insert(ApiKey::try_new("test-key".to_string()).unwrap());
let config = ProxyMiddlewareConfig {
auth: auth_config,
enable_logging: true,
detailed_errors: false,
enable_request_id: true,
enable_health_check: false, enable_metrics: false, };
let stack = config.build_stack();
assert_eq!(stack.auth_config.bypass_paths.len(), 0);
}
}