kanade_shared/wire/
agent_meta.rs1use serde::{Deserialize, Serialize};
19
20#[derive(Serialize, Debug, Clone, Default, PartialEq, Eq)]
21pub struct AgentMeta {
22 pub entries: Vec<MetaEntry>,
27}
28
29impl<'de> Deserialize<'de> for AgentMeta {
35 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
36 where
37 D: serde::Deserializer<'de>,
38 {
39 #[derive(Deserialize)]
40 struct Raw {
41 #[serde(default)]
42 entries: Vec<MetaEntry>,
43 }
44 Ok(AgentMeta::new(Raw::deserialize(deserializer)?.entries))
45 }
46}
47
48#[derive(Serialize, Deserialize, Debug, Clone, Default, PartialEq, Eq)]
49pub struct MetaEntry {
50 pub key: String,
51 pub value: String,
52}
53
54#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
59pub struct MetaUpdate {
60 pub meta: AgentMeta,
61 pub changed: bool,
62}
63
64impl MetaEntry {
65 pub fn new(key: impl Into<String>, value: impl Into<String>) -> Self {
66 Self {
67 key: key.into(),
68 value: value.into(),
69 }
70 }
71}
72
73impl AgentMeta {
74 pub fn new<I: IntoIterator<Item = MetaEntry>>(entries: I) -> Self {
81 let mut order: Vec<String> = Vec::new();
82 let mut values: std::collections::HashMap<String, String> =
83 std::collections::HashMap::new();
84 for e in entries {
85 let key = e.key.trim().to_string();
86 if key.is_empty() {
87 continue;
88 }
89 let value = e.value.trim().to_string();
90 if !values.contains_key(&key) {
91 order.push(key.clone());
92 }
93 values.insert(key, value); }
95 let entries = order
96 .into_iter()
97 .map(|key| {
98 let value = values.remove(&key).unwrap_or_default();
99 MetaEntry { key, value }
100 })
101 .collect();
102 Self { entries }
103 }
104
105 pub fn upsert(&mut self, key: impl Into<String>, value: impl Into<String>) -> bool {
110 let key = key.into().trim().to_string();
111 if key.is_empty() {
112 return false;
113 }
114 let value = value.into().trim().to_string();
115 match self.entries.iter_mut().find(|e| e.key == key) {
116 Some(e) if e.value == value => false,
117 Some(e) => {
118 e.value = value;
119 true
120 }
121 None => {
122 self.entries.push(MetaEntry { key, value });
123 true
124 }
125 }
126 }
127
128 pub fn remove(&mut self, key: &str) -> bool {
130 let key = key.trim();
131 let before = self.entries.len();
132 self.entries.retain(|e| e.key != key);
133 self.entries.len() != before
134 }
135
136 pub fn get(&self, key: &str) -> Option<&str> {
137 let key = key.trim();
138 self.entries
139 .iter()
140 .find(|e| e.key == key)
141 .map(|e| e.value.as_str())
142 }
143
144 pub fn is_empty(&self) -> bool {
145 self.entries.is_empty()
146 }
147}
148
149#[cfg(test)]
150mod tests {
151 use super::*;
152
153 fn e(key: &str, value: &str) -> MetaEntry {
154 MetaEntry::new(key, value)
155 }
156
157 #[test]
158 fn new_trims_drops_empty_keys_and_dedups_last_wins() {
159 let m = AgentMeta::new([
160 e(" Name ", " Alice "),
161 e("Email", "alice@example.com"),
162 e(" ", "orphan value"), e("Name", "Alice Smith"), ]);
165 assert_eq!(
166 m.entries,
167 vec![e("Name", "Alice Smith"), e("Email", "alice@example.com"),]
168 );
169 }
170
171 #[test]
172 fn round_trips_through_json() {
173 let m = AgentMeta::new([e("Dept", "Finance")]);
174 let json = serde_json::to_string(&m).unwrap();
175 assert_eq!(json, r#"{"entries":[{"key":"Dept","value":"Finance"}]}"#);
176 let back: AgentMeta = serde_json::from_str(&json).unwrap();
177 assert_eq!(back, m);
178 }
179
180 #[test]
181 fn empty_round_trips() {
182 let m = AgentMeta::default();
183 assert_eq!(serde_json::to_string(&m).unwrap(), r#"{"entries":[]}"#);
184 assert!(m.is_empty());
185 }
186
187 #[test]
188 fn upsert_inserts_overwrites_and_noops() {
189 let mut m = AgentMeta::default();
190 assert!(m.upsert("Name", "Alice")); assert!(!m.upsert("Name", "Alice")); assert!(m.upsert("Name", "Bob")); assert!(!m.upsert(" ", "x")); assert!(m.upsert(" Email ", " a@b.com ")); assert_eq!(m.get("Name"), Some("Bob"));
196 assert_eq!(m.get("Email"), Some("a@b.com"));
197 assert_eq!(m.entries.len(), 2);
198 }
199
200 #[test]
201 fn remove_reports_change() {
202 let mut m = AgentMeta::new([e("Name", "Alice"), e("Dept", "IT")]);
203 assert!(m.remove("Name"));
204 assert!(!m.remove("Name"));
205 assert_eq!(m.entries, vec![e("Dept", "IT")]);
206 }
207
208 #[test]
209 fn accepts_unknown_fields_for_forward_compat() {
210 let json = r#"{"entries":[{"key":"Name","value":"Alice"}],"set_by":"admin"}"#;
213 let m: AgentMeta = serde_json::from_str(json).unwrap();
214 assert_eq!(m.entries, vec![e("Name", "Alice")]);
215 }
216
217 #[test]
218 fn deserialize_normalises_untrusted_input() {
219 let json = r#"{"entries":[
223 {"key":" Name ","value":" Alice "},
224 {"key":"","value":"orphan"},
225 {"key":"Name","value":"Bob"}
226 ]}"#;
227 let m: AgentMeta = serde_json::from_str(json).unwrap();
228 assert_eq!(m.entries, vec![e("Name", "Bob")]);
229 }
230
231 #[test]
232 fn deserialize_defaults_missing_entries() {
233 let m: AgentMeta = serde_json::from_str("{}").unwrap();
234 assert!(m.is_empty());
235 }
236}