1use axum::{
20 Json, Router,
21 extract::State,
22 routing::{get, post},
23};
24use ogham::{
25 CompressionPipeline, Message,
26 ccr::{CcrStore, in_memory::InMemoryCcrStore},
27 detect,
28 pipeline::DefaultCompressionPipeline,
29};
30use serde::{Deserialize, Serialize};
31use serde_json::Value;
32use std::sync::Arc;
33use std::time::Instant;
34use tokio::sync::Mutex;
35
36#[derive(Debug, Clone)]
38pub enum CcrBackendConfig {
39 InMemory,
40 Sqlite {
41 path: std::path::PathBuf,
42 ttl_seconds: u64,
43 },
44 Fjall {
45 path: std::path::PathBuf,
46 },
47}
48
49#[derive(Debug, Clone)]
51pub struct ServerConfig {
52 pub bind: std::net::SocketAddr,
55 pub ccr: CcrBackendConfig,
56}
57
58impl Default for ServerConfig {
59 fn default() -> Self {
60 Self {
61 bind: std::net::SocketAddr::from(([127, 0, 0, 1], 3000)),
62 ccr: CcrBackendConfig::InMemory,
63 }
64 }
65}
66
67#[derive(Clone)]
69pub struct AppState {
70 pub start_time: Instant,
71 pub request_count: Arc<Mutex<u64>>,
72 pub pipeline: Arc<DefaultCompressionPipeline>,
73 pub ccr_store: Arc<dyn CcrStore>,
74}
75
76impl AppState {
77 pub fn new() -> Self {
78 let ccr_store = Arc::new(InMemoryCcrStore::new());
79 let pipeline = Arc::new(
80 DefaultCompressionPipeline::builder()
81 .ccr_store(ccr_store.clone())
82 .align_cache()
83 .build(),
84 );
85 Self {
86 start_time: Instant::now(),
87 request_count: Arc::new(Mutex::new(0)),
88 pipeline,
89 ccr_store,
90 }
91 }
92
93 pub fn with_store(store: Arc<dyn CcrStore>) -> Self {
95 let pipeline = Arc::new(
96 DefaultCompressionPipeline::builder()
97 .ccr_store(store.clone())
98 .align_cache()
99 .build(),
100 );
101 Self {
102 start_time: Instant::now(),
103 request_count: Arc::new(Mutex::new(0)),
104 pipeline,
105 ccr_store: store,
106 }
107 }
108
109 pub async fn bump_requests(&self) {
110 let mut guard = self.request_count.lock().await;
111 *guard += 1;
112 }
113}
114
115impl Default for AppState {
116 fn default() -> Self {
117 Self::new()
118 }
119}
120
121pub fn app() -> Router {
137 app_with_state(AppState::new())
138}
139
140pub fn app_with_state(state: AppState) -> Router {
142 Router::new()
143 .route("/health", get(health))
144 .route("/compress", post(compress_handler))
145 .route("/retrieve", post(retrieve_handler))
146 .route("/detect", post(detect_handler))
147 .route("/stats", get(stats_handler))
148 .with_state(state)
149}
150
151#[derive(Serialize)]
152struct HealthResponse {
153 status: &'static str,
154}
155
156async fn health() -> Json<HealthResponse> {
157 Json(HealthResponse { status: "ok" })
158}
159
160#[derive(Deserialize)]
161struct CompressRequest {
162 messages: Vec<Message>,
163}
164
165#[derive(Serialize)]
166struct CompressResponse {
167 messages: Vec<Message>,
168 stats: Value,
169}
170
171async fn compress_handler(
172 State(state): State<AppState>,
173 Json(req): Json<CompressRequest>,
174) -> Json<CompressResponse> {
175 state.bump_requests().await;
176
177 match state.pipeline.run(&req.messages).await {
178 Ok(result) => Json(CompressResponse {
179 messages: result.messages,
180 stats: serde_json::to_value(&result.stats).unwrap_or_else(|_| serde_json::json!({})),
181 }),
182 Err(e) => Json(CompressResponse {
183 messages: req.messages,
185 stats: serde_json::json!({"error": e.to_string()}),
186 }),
187 }
188}
189
190#[derive(Deserialize)]
191struct RetrieveRequest {
192 id: String,
193}
194
195#[derive(Serialize)]
196struct RetrieveResponse {
197 found: bool,
198 original: Option<String>,
199}
200
201async fn retrieve_handler(
202 State(state): State<AppState>,
203 Json(req): Json<RetrieveRequest>,
204) -> Json<RetrieveResponse> {
205 state.bump_requests().await;
206 let original = state.ccr_store.retrieve(&req.id).await.ok().flatten();
207 Json(RetrieveResponse {
208 found: original.is_some(),
209 original,
210 })
211}
212
213#[derive(Deserialize)]
214struct DetectRequest {
215 content: String,
216}
217
218#[derive(Serialize)]
219struct DetectResponse {
220 content_type: String,
221 confidence: f64,
222 metadata: Value,
223}
224
225async fn detect_handler(
226 State(state): State<AppState>,
227 Json(req): Json<DetectRequest>,
228) -> Json<DetectResponse> {
229 state.bump_requests().await;
230 let result = detect(&req.content);
231 Json(DetectResponse {
232 content_type: result.content_type.as_str().to_string(),
233 confidence: result.confidence,
234 metadata: serde_json::to_value(&result.metadata).unwrap_or_else(|_| serde_json::json!({})),
235 })
236}
237
238#[derive(Serialize)]
239struct StatsResponse {
240 uptime_seconds: u64,
241 requests_total: u64,
242}
243
244async fn stats_handler(State(state): State<AppState>) -> Json<StatsResponse> {
245 let requests = *state.request_count.lock().await;
246 Json(StatsResponse {
247 uptime_seconds: state.start_time.elapsed().as_secs(),
248 requests_total: requests,
249 })
250}