1use serde::{Deserialize, Serialize};
27use std::fs;
28use std::io;
29use std::path::Path;
30
31#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
32#[serde(rename_all = "snake_case")]
33pub enum UserRole {
34 Human,
35 Agent,
36}
37
38#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
39pub struct User {
40 pub name: String,
41 pub role: UserRole,
42}
43
44#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
45pub struct UsersFile {
46 #[serde(default)]
47 pub users: Vec<User>,
48}
49
50pub fn load(root: &Path) -> io::Result<Option<UsersFile>> {
55 let path = root.join("users.json");
56 if !path.exists() {
57 return Ok(None);
58 }
59 let bytes = fs::read(&path)?;
60 let file: UsersFile = serde_json::from_slice(&bytes)
61 .map_err(|e| io::Error::new(io::ErrorKind::InvalidData,
62 format!("parsing {}: {e}", path.display())))?;
63 Ok(Some(file))
64}
65
66impl UsersFile {
67 pub fn find(&self, name: &str) -> Option<&User> {
70 self.users.iter().find(|u| u.name == name)
71 }
72
73 pub fn knows(&self, name: &str) -> bool {
76 self.find(name).is_some()
77 }
78}
79
80#[cfg(test)]
81mod tests {
82 use super::*;
83 use tempfile::tempdir;
84
85 #[test]
86 fn load_absent_returns_none() {
87 let tmp = tempdir().unwrap();
88 let got = load(tmp.path()).unwrap();
89 assert!(got.is_none());
90 }
91
92 #[test]
93 fn load_empty_file_returns_some_empty() {
94 let tmp = tempdir().unwrap();
95 std::fs::write(tmp.path().join("users.json"), r#"{"users":[]}"#).unwrap();
96 let got = load(tmp.path()).unwrap().unwrap();
97 assert_eq!(got.users.len(), 0);
98 assert!(!got.knows("alice"));
99 }
100
101 #[test]
102 fn round_trip_through_disk() {
103 let tmp = tempdir().unwrap();
104 let f = UsersFile {
105 users: vec![
106 User { name: "alice".into(), role: UserRole::Human },
107 User { name: "lexbot".into(), role: UserRole::Agent },
108 ],
109 };
110 std::fs::write(
111 tmp.path().join("users.json"),
112 serde_json::to_vec_pretty(&f).unwrap(),
113 ).unwrap();
114 let got = load(tmp.path()).unwrap().unwrap();
115 assert_eq!(got, f);
116 assert_eq!(got.find("alice").unwrap().role, UserRole::Human);
117 assert_eq!(got.find("lexbot").unwrap().role, UserRole::Agent);
118 assert!(!got.knows("eve"));
119 }
120
121 #[test]
122 fn malformed_json_is_an_error() {
123 let tmp = tempdir().unwrap();
124 std::fs::write(tmp.path().join("users.json"), "not json").unwrap();
125 let err = load(tmp.path()).unwrap_err();
126 assert_eq!(err.kind(), io::ErrorKind::InvalidData);
127 }
128}