Skip to main content

redact_api/
server.rs

1// Copyright 2026 Censgate LLC.
2// Licensed under the Apache License, Version 2.0. See the LICENSE file
3// in the project root for license information.
4
5use crate::handlers::AppState;
6use crate::routes::create_router;
7use axum::serve;
8use redact_core::AnalyzerEngine;
9use std::net::SocketAddr;
10use std::sync::Arc;
11use tokio::net::TcpListener;
12use tower_http::trace::TraceLayer;
13use tracing::info;
14
15/// API Server configuration
16#[derive(Debug, Clone)]
17pub struct ServerConfig {
18    /// Host to bind to
19    pub host: String,
20
21    /// Port to bind to
22    pub port: u16,
23
24    /// Enable request tracing
25    pub enable_tracing: bool,
26}
27
28impl Default for ServerConfig {
29    fn default() -> Self {
30        Self {
31            host: "0.0.0.0".to_string(),
32            port: 8080,
33            enable_tracing: true,
34        }
35    }
36}
37
38/// API Server
39pub struct ApiServer {
40    config: ServerConfig,
41    engine: Arc<AnalyzerEngine>,
42}
43
44impl ApiServer {
45    /// Create a new API server
46    pub fn new(config: ServerConfig) -> Self {
47        Self {
48            config,
49            engine: Arc::new(AnalyzerEngine::new()),
50        }
51    }
52
53    /// Create a new API server with a custom engine
54    pub fn with_engine(config: ServerConfig, engine: AnalyzerEngine) -> Self {
55        Self {
56            config,
57            engine: Arc::new(engine),
58        }
59    }
60
61    /// Get the bind address
62    pub fn bind_address(&self) -> String {
63        format!("{}:{}", self.config.host, self.config.port)
64    }
65
66    /// Run the server
67    pub async fn run(self) -> anyhow::Result<()> {
68        // Get bind address before moving self
69        let bind_addr = self.bind_address();
70        let enable_tracing = self.config.enable_tracing;
71
72        // Create application state
73        let state = AppState {
74            engine: self.engine,
75        };
76
77        // Create router
78        let mut app = create_router(state);
79
80        // Add tracing middleware
81        if enable_tracing {
82            app = app.layer(TraceLayer::new_for_http());
83        }
84
85        // Bind to address
86        let addr: SocketAddr = bind_addr.parse()?;
87        let listener = TcpListener::bind(addr).await?;
88
89        info!("Redact API server listening on {}", addr);
90        info!("Endpoints:");
91        info!("  GET  /health           - Health check");
92        info!("  POST /api/v1/analyze   - Analyze text for PII");
93        info!("  POST /api/v1/anonymize - Anonymize detected PII");
94
95        // Run server
96        serve(listener, app).await?;
97
98        Ok(())
99    }
100}
101
102#[cfg(test)]
103mod tests {
104    use super::*;
105
106    #[test]
107    fn test_server_config() {
108        let config = ServerConfig::default();
109        assert_eq!(config.host, "0.0.0.0");
110        assert_eq!(config.port, 8080);
111        assert!(config.enable_tracing);
112    }
113
114    #[test]
115    fn test_bind_address() {
116        let config = ServerConfig {
117            host: "127.0.0.1".to_string(),
118            port: 3000,
119            enable_tracing: false,
120        };
121        let server = ApiServer::new(config);
122        assert_eq!(server.bind_address(), "127.0.0.1:3000");
123    }
124}