Skip to main content

notedthat_api_http/
state.rs

1//! Shared application state for the axum router.
2
3use notedthat_core::{KbSlug, PublicReadPolicy, Storage};
4use notedthat_indexer::Searcher;
5use std::collections::BTreeMap;
6use std::sync::Arc;
7
8/// Application state shared across all axum handlers.
9///
10/// This is cloned cheaply for each request (all fields are behind [`Arc`]).
11#[derive(Clone)]
12pub struct AppState {
13    /// The backing storage implementation (injected at startup).
14    pub storage: Arc<dyn Storage>,
15    /// Canonical map of slug string → [`KbSlug`] for declared knowledge bases.
16    pub declared_kbs: Arc<BTreeMap<String, KbSlug>>,
17    /// Startup snapshot of manifest-controlled public-read policies, keyed by KB slug.
18    pub public_read_policies: Arc<BTreeMap<String, PublicReadPolicy>>,
19    /// Static Bearer token for authenticating API requests.
20    pub bearer_token: Arc<String>,
21    /// Maximum accepted PUT body size in bytes (16 MiB in M2).
22    pub max_body_size: u64,
23    /// Maximum object size eligible for patch operations, in bytes.
24    pub max_patchable_size: u64,
25    /// Sender half of the async indexing queue.
26    pub indexer_tx: tokio::sync::mpsc::Sender<notedthat_indexer::IndexEvent>,
27    /// The search implementation (injected at startup).
28    pub searcher: Arc<dyn Searcher>,
29}
30
31#[cfg(test)]
32mod tests {
33    use super::*;
34    use crate::testing::InMemoryStorage;
35    use std::collections::BTreeMap;
36    use std::sync::Arc;
37
38    fn minimal_state(tx: tokio::sync::mpsc::Sender<notedthat_indexer::IndexEvent>) -> AppState {
39        AppState {
40            storage: Arc::new(InMemoryStorage::default()),
41            declared_kbs: Arc::new(BTreeMap::new()),
42            public_read_policies: Arc::new(BTreeMap::new()),
43            bearer_token: Arc::new("token".to_string()),
44            max_body_size: 1024,
45            max_patchable_size: 1024,
46            indexer_tx: tx,
47            searcher: Arc::new(crate::testing::NoopSearcher),
48        }
49    }
50
51    #[tokio::test]
52    async fn clone_shares_same_channel() {
53        let (tx, mut rx) = tokio::sync::mpsc::channel(10);
54        let state = minimal_state(tx);
55        let cloned = state.clone();
56
57        let event = notedthat_indexer::IndexEvent::Tombstone {
58            kb: notedthat_core::KbSlug::try_new("test").expect("valid kb slug"),
59            object_key: notedthat_core::ObjectPath::try_from("a.md").expect("valid path"),
60        };
61        cloned
62            .indexer_tx
63            .send(event.clone())
64            .await
65            .expect("send on cloned tx");
66
67        let received = rx.recv().await.expect("receive on original rx");
68        assert_eq!(
69            received, event,
70            "cloned Sender must share the same underlying channel"
71        );
72    }
73}