domi_server/http/
state.rs1use std::path::PathBuf;
2use std::sync::Arc;
3
4use tokio::sync::broadcast;
5use ulid::Ulid;
6
7use crate::events::EventWriter;
8
9pub struct AppState {
10 pub root: PathBuf,
11 pub state_dir: PathBuf,
12 pub writer: Arc<EventWriter>,
13 pub broadcaster: broadcast::Sender<crate::events::Event>,
14 pub server_id: Ulid,
15 pub library_root: Option<PathBuf>,
16}
17
18impl AppState {
19 pub fn new(
20 root: PathBuf,
21 state_dir: PathBuf,
22 writer: Arc<EventWriter>,
23 capacity: usize,
24 library_root: Option<PathBuf>,
25 ) -> Self {
26 let resolved_library_root = library_root.map(|p| match std::fs::canonicalize(&p) {
27 Ok(canon) => canon,
28 Err(e) => {
29 tracing::warn!(path = %p.display(), error = %e,
30 "library_root could not be canonicalized; library routes will 404");
31 p
32 }
33 });
34 let (broadcaster, _) = broadcast::channel(capacity);
35 Self {
36 root,
37 state_dir,
38 writer,
39 broadcaster,
40 server_id: Ulid::new(),
41 library_root: resolved_library_root,
42 }
43 }
44}
45
46#[cfg(test)]
47mod tests {
48 use super::*;
49 use crate::events::Event;
50 use tempfile::tempdir;
51
52 fn sample_event() -> Event {
53 use crate::events::{EventData, Kind, Rect, Source, Target};
54 Event {
55 v: 2,
56 id: Ulid::from_string("01H8XZQ5K2J9Z9Q4X5Y6Z7XYZ0").unwrap(),
57 ts: chrono::DateTime::parse_from_rfc3339("2026-07-05T18:21:00Z")
58 .unwrap()
59 .with_timezone(&chrono::Utc),
60 src: Source::DomiJs,
61 doc: "x".into(),
62 kind: Kind::Click,
63 target: Target {
64 id: None,
65 selector: None,
66 rect: Rect {
67 x: 0.0,
68 y: 0.0,
69 w: 1.0,
70 h: 1.0,
71 },
72 },
73 data: EventData::Click {
74 value: Some("hi".into()),
75 },
76 }
77 }
78
79 #[test]
80 fn new_assigns_unique_server_id() {
81 let dir = tempdir().unwrap();
82 let path = dir.path().join("events.jsonl");
83 let w = Arc::new(EventWriter::new(&path));
84 let s1 = AppState::new(
85 dir.path().to_path_buf(),
86 dir.path().to_path_buf(),
87 w.clone(),
88 16,
89 None,
90 );
91 let s2 = AppState::new(dir.path().to_path_buf(), dir.path().to_path_buf(), w, 16, None);
92 assert_ne!(s1.server_id, s2.server_id);
93 }
94
95 #[test]
96 fn broadcaster_receives_sent_event() {
97 let dir = tempdir().unwrap();
98 let path = dir.path().join("events.jsonl");
99 let w = Arc::new(EventWriter::new(&path));
100 let state = AppState::new(dir.path().to_path_buf(), dir.path().to_path_buf(), w, 16, None);
101 let mut rx = state.broadcaster.subscribe();
102 let ev = sample_event();
103 let _ = state.broadcaster.send(ev.clone());
104 let received = rx.try_recv().expect("event delivered");
105 assert_eq!(received.id, ev.id);
106 }
107
108 #[test]
109 fn broadcaster_capacity_is_respected() {
110 let dir = tempdir().unwrap();
111 let path = dir.path().join("events.jsonl");
112 let w = Arc::new(EventWriter::new(&path));
113 let state = AppState::new(dir.path().to_path_buf(), dir.path().to_path_buf(), w, 4, None);
114 assert_eq!(state.broadcaster.receiver_count(), 0, "no subscribers yet");
115 }
116}