//! Osiris Server - Generic OpenAPI REST server for Osiris data structures //! //! Provides generic CRUD operations for all Osiris structs via REST API. //! Routes follow pattern: GET /api/:struct_name/:id use axum::{ extract::{Path, Query, State}, http::StatusCode, response::{IntoResponse, Json}, routing::get, Router, }; use serde::{Deserialize, Serialize}; use serde_json::{json, Value}; use std::collections::HashMap; use std::sync::Arc; use tower_http::cors::{Any, CorsLayer}; use tracing::{info, warn}; #[derive(Clone)] struct AppState { // In a real implementation, this would be a Redis connection pool // For now, we'll use an in-memory store for demonstration store: Arc>>>, } impl AppState { fn new() -> Self { Self { store: Arc::new(tokio::sync::RwLock::new(HashMap::new())), } } } #[tokio::main] async fn main() { // Initialize tracing tracing_subscriber::fmt() .with_target(false) .compact() .init(); let state = AppState::new(); // Build router let app = Router::new() .route("/health", get(health_check)) .route("/api/:struct_name", get(list_structs)) .route("/api/:struct_name/:id", get(get_struct)) .layer( CorsLayer::new() .allow_origin(Any) .allow_methods(Any) .allow_headers(Any), ) .with_state(state); let addr = "0.0.0.0:8081"; info!("🚀 Osiris Server starting on {}", addr); info!("📖 API Documentation: http://localhost:8081/health"); let listener = tokio::net::TcpListener::bind(addr) .await .expect("Failed to bind address"); axum::serve(listener, app) .await .expect("Server failed"); } /// Health check endpoint async fn health_check() -> impl IntoResponse { Json(json!({ "status": "healthy", "service": "osiris-server", "version": "0.1.0" })) } /// Generic GET endpoint for a single struct by ID /// GET /api/:struct_name/:id async fn get_struct( State(state): State, Path((struct_name, id)): Path<(String, String)>, ) -> Result, (StatusCode, String)> { info!("GET /api/{}/{}", struct_name, id); let store = state.store.read().await; if let Some(struct_store) = store.get(&struct_name) { if let Some(data) = struct_store.get(&id) { return Ok(Json(data.clone())); } } warn!("Not found: {}/{}", struct_name, id); Err(( StatusCode::NOT_FOUND, format!("{}/{} not found", struct_name, id), )) } /// Generic LIST endpoint for all instances of a struct /// GET /api/:struct_name?field=value async fn list_structs( State(state): State, Path(struct_name): Path, Query(params): Query>, ) -> Result>, (StatusCode, String)> { info!("GET /api/{} with params: {:?}", struct_name, params); let store = state.store.read().await; if let Some(struct_store) = store.get(&struct_name) { let mut results: Vec = struct_store.values().cloned().collect(); // Apply filters if any if !params.is_empty() { results.retain(|item| { params.iter().all(|(key, value)| { item.get(key) .and_then(|v| v.as_str()) .map(|v| v == value) .unwrap_or(false) }) }); } return Ok(Json(results)); } // Return empty array if struct type doesn't exist yet Ok(Json(vec![])) } #[cfg(test)] mod tests { use super::*; #[tokio::test] async fn test_health_check() { let response = health_check().await.into_response(); assert_eq!(response.status(), StatusCode::OK); } }