Skip to main content

mnemo_rest/
lib.rs

1pub mod handlers;
2
3use std::sync::Arc;
4
5use axum::Router;
6use axum::extract::DefaultBodyLimit;
7use axum::routing::{get, post};
8use mnemo_core::query::MnemoEngine;
9use tower_http::cors::{AllowOrigin, CorsLayer};
10
11/// Construct the full Axum router for the Mnemo REST API.
12///
13/// All routes are nested under `/v1/` and the router carries
14/// `Arc<MnemoEngine>` as shared state.
15///
16/// CORS is restrictive by default (localhost only). Set the
17/// `MNEMO_CORS_ORIGINS` environment variable to a comma-separated
18/// list of allowed origins to override (e.g. `https://app.example.com`).
19/// Set it to `*` to allow all origins (not recommended for production).
20pub fn router(engine: Arc<MnemoEngine>) -> Router {
21    let cors = build_cors_layer();
22
23    Router::new()
24        .route(
25            "/v1/memories",
26            post(handlers::remember_handler).get(handlers::recall_handler),
27        )
28        .route(
29            "/v1/memories/{id}",
30            get(handlers::get_memory_handler).delete(handlers::forget_handler),
31        )
32        .route("/v1/memories/{id}/share", post(handlers::share_handler))
33        .route("/v1/checkpoints", post(handlers::checkpoint_handler))
34        .route("/v1/branches", post(handlers::branch_handler))
35        .route("/v1/merge", post(handlers::merge_handler))
36        .route("/v1/replay", post(handlers::replay_handler))
37        .route("/v1/verify", post(handlers::verify_handler))
38        .route("/v1/delegate", post(handlers::delegate_handler))
39        .route("/v1/forget_subject", post(handlers::forget_subject_handler))
40        .route("/v1/ingest/otlp", post(handlers::otlp_ingest_handler))
41        .route("/v1/health", get(handlers::health_handler))
42        .layer(DefaultBodyLimit::max(2 * 1024 * 1024)) // 2 MB max request body
43        .layer(cors)
44        .layer(tower_http::trace::TraceLayer::new_for_http())
45        .with_state(engine)
46}
47
48fn build_cors_layer() -> CorsLayer {
49    use axum::http::{HeaderName, Method};
50
51    let base = CorsLayer::new()
52        .allow_methods([Method::GET, Method::POST, Method::DELETE, Method::OPTIONS])
53        .allow_headers([
54            HeaderName::from_static("content-type"),
55            HeaderName::from_static("authorization"),
56        ])
57        .max_age(std::time::Duration::from_secs(3600));
58
59    match std::env::var("MNEMO_CORS_ORIGINS") {
60        Ok(val) if val == "*" => base.allow_origin(AllowOrigin::any()),
61        Ok(val) => {
62            let origins: Vec<_> = val
63                .split(',')
64                .filter_map(|s| s.trim().parse().ok())
65                .collect();
66            base.allow_origin(origins)
67        }
68        Err(_) => {
69            // Default: localhost only
70            let origins: Vec<_> = [
71                "http://localhost:3000",
72                "http://localhost:8080",
73                "http://127.0.0.1:3000",
74                "http://127.0.0.1:8080",
75            ]
76            .iter()
77            .filter_map(|s| s.parse().ok())
78            .collect();
79            base.allow_origin(origins)
80        }
81    }
82}