Skip to main content

redact_api/
server.rs

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