Skip to main content

synapse_proxy/
admin.rs

1//! Admin surface (separate listener): push/clear the context overlay.
2
3use std::collections::HashMap;
4use std::sync::Arc;
5use std::time::Duration;
6
7use axum::extract::State;
8use axum::http::StatusCode;
9use axum::routing::post;
10use axum::{Json, Router};
11use serde::Deserialize;
12
13use crate::context::ContextStore;
14
15#[derive(Debug, Deserialize)]
16pub struct BindRequest {
17    pub values: HashMap<String, String>,
18    #[serde(default)]
19    pub ttl_seconds: Option<u64>,
20}
21
22pub fn admin_router(context: Arc<ContextStore>) -> Router {
23    Router::new()
24        .route("/internal/bind", post(bind).delete(unbind))
25        .with_state(context)
26}
27
28async fn bind(State(ctx): State<Arc<ContextStore>>, Json(req): Json<BindRequest>) -> StatusCode {
29    ctx.push(req.values, req.ttl_seconds.map(Duration::from_secs));
30    StatusCode::NO_CONTENT
31}
32
33async fn unbind(State(ctx): State<Arc<ContextStore>>) -> StatusCode {
34    ctx.clear();
35    StatusCode::NO_CONTENT
36}
37
38#[cfg(test)]
39mod tests {
40    use super::*;
41    use http_body_util::BodyExt;
42    use tower::ServiceExt;
43
44    #[tokio::test]
45    async fn bind_then_unbind_updates_context() {
46        let ctx = Arc::new(ContextStore::new(HashMap::new()));
47        let app = admin_router(ctx.clone());
48        let body =
49            serde_json::json!({ "values": { "org": "pushed" }, "ttl_seconds": 3600 }).to_string();
50        let resp = app
51            .clone()
52            .oneshot(
53                axum::http::Request::builder()
54                    .method("POST")
55                    .uri("/internal/bind")
56                    .header("content-type", "application/json")
57                    .body(axum::body::Body::from(body))
58                    .unwrap(),
59            )
60            .await
61            .unwrap();
62        assert_eq!(resp.status(), 204);
63        let _ = resp.into_body().collect().await;
64        assert_eq!(ctx.resolve().get("org"), Some("pushed"));
65
66        let resp = app
67            .oneshot(
68                axum::http::Request::builder()
69                    .method("DELETE")
70                    .uri("/internal/bind")
71                    .body(axum::body::Body::empty())
72                    .unwrap(),
73            )
74            .await
75            .unwrap();
76        assert_eq!(resp.status(), 204);
77        assert_eq!(ctx.resolve().get("org"), None);
78    }
79}