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
54impl MetaEntry {
55 pub fn new(key: impl Into<String>, value: impl Into<String>) -> Self {
56 Self {
57 key: key.into(),
58 value: value.into(),
59 }
60 }
61}
62
63impl AgentMeta {
64 pub fn new<I: IntoIterator<Item = MetaEntry>>(entries: I) -> Self {
71 let mut order: Vec<String> = Vec::new();
72 let mut values: std::collections::HashMap<String, String> =
73 std::collections::HashMap::new();
74 for e in entries {
75 let key = e.key.trim().to_string();
76 if key.is_empty() {
77 continue;
78 }
79 let value = e.value.trim().to_string();
80 if !values.contains_key(&key) {
81 order.push(key.clone());
82 }
83 values.insert(key, value); }
85 let entries = order
86 .into_iter()
87 .map(|key| {
88 let value = values.remove(&key).unwrap_or_default();
89 MetaEntry { key, value }
90 })
91 .collect();
92 Self { entries }
93 }
94
95 pub fn upsert(&mut self, key: impl Into<String>, value: impl Into<String>) -> bool {
100 let key = key.into().trim().to_string();
101 if key.is_empty() {
102 return false;
103 }
104 let value = value.into().trim().to_string();
105 match self.entries.iter_mut().find(|e| e.key == key) {
106 Some(e) if e.value == value => false,
107 Some(e) => {
108 e.value = value;
109 true
110 }
111 None => {
112 self.entries.push(MetaEntry { key, value });
113 true
114 }
115 }
116 }
117
118 pub fn remove(&mut self, key: &str) -> bool {
120 let key = key.trim();
121 let before = self.entries.len();
122 self.entries.retain(|e| e.key != key);
123 self.entries.len() != before
124 }
125
126 pub fn get(&self, key: &str) -> Option<&str> {
127 let key = key.trim();
128 self.entries
129 .iter()
130 .find(|e| e.key == key)
131 .map(|e| e.value.as_str())
132 }
133
134 pub fn is_empty(&self) -> bool {
135 self.entries.is_empty()
136 }
137}
138
139#[cfg(test)]
140mod tests {
141 use super::*;
142
143 fn e(key: &str, value: &str) -> MetaEntry {
144 MetaEntry::new(key, value)
145 }
146
147 #[test]
148 fn new_trims_drops_empty_keys_and_dedups_last_wins() {
149 let m = AgentMeta::new([
150 e(" Name ", " Alice "),
151 e("Email", "alice@example.com"),
152 e(" ", "orphan value"), e("Name", "Alice Smith"), ]);
155 assert_eq!(
156 m.entries,
157 vec![e("Name", "Alice Smith"), e("Email", "alice@example.com"),]
158 );
159 }
160
161 #[test]
162 fn round_trips_through_json() {
163 let m = AgentMeta::new([e("Dept", "Finance")]);
164 let json = serde_json::to_string(&m).unwrap();
165 assert_eq!(json, r#"{"entries":[{"key":"Dept","value":"Finance"}]}"#);
166 let back: AgentMeta = serde_json::from_str(&json).unwrap();
167 assert_eq!(back, m);
168 }
169
170 #[test]
171 fn empty_round_trips() {
172 let m = AgentMeta::default();
173 assert_eq!(serde_json::to_string(&m).unwrap(), r#"{"entries":[]}"#);
174 assert!(m.is_empty());
175 }
176
177 #[test]
178 fn upsert_inserts_overwrites_and_noops() {
179 let mut m = AgentMeta::default();
180 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"));
186 assert_eq!(m.get("Email"), Some("a@b.com"));
187 assert_eq!(m.entries.len(), 2);
188 }
189
190 #[test]
191 fn remove_reports_change() {
192 let mut m = AgentMeta::new([e("Name", "Alice"), e("Dept", "IT")]);
193 assert!(m.remove("Name"));
194 assert!(!m.remove("Name"));
195 assert_eq!(m.entries, vec![e("Dept", "IT")]);
196 }
197
198 #[test]
199 fn accepts_unknown_fields_for_forward_compat() {
200 let json = r#"{"entries":[{"key":"Name","value":"Alice"}],"set_by":"admin"}"#;
203 let m: AgentMeta = serde_json::from_str(json).unwrap();
204 assert_eq!(m.entries, vec![e("Name", "Alice")]);
205 }
206
207 #[test]
208 fn deserialize_normalises_untrusted_input() {
209 let json = r#"{"entries":[
213 {"key":" Name ","value":" Alice "},
214 {"key":"","value":"orphan"},
215 {"key":"Name","value":"Bob"}
216 ]}"#;
217 let m: AgentMeta = serde_json::from_str(json).unwrap();
218 assert_eq!(m.entries, vec![e("Name", "Bob")]);
219 }
220
221 #[test]
222 fn deserialize_defaults_missing_entries() {
223 let m: AgentMeta = serde_json::from_str("{}").unwrap();
224 assert!(m.is_empty());
225 }
226}