kanade_shared/wire/
agent_groups.rs1use serde::{Deserialize, Serialize};
2
3#[derive(Serialize, Deserialize, Debug, Clone, Default, PartialEq, Eq)]
8pub struct AgentGroups {
9 pub groups: Vec<String>,
14}
15
16impl AgentGroups {
17 pub fn new<I, S>(groups: I) -> Self
20 where
21 I: IntoIterator<Item = S>,
22 S: Into<String>,
23 {
24 let mut v: Vec<String> = groups.into_iter().map(Into::into).collect();
25 v.sort();
26 v.dedup();
27 Self { groups: v }
28 }
29
30 pub fn insert(&mut self, group: impl Into<String>) -> bool {
34 let group = group.into();
35 match self.groups.binary_search(&group) {
36 Ok(_) => false,
37 Err(idx) => {
38 self.groups.insert(idx, group);
39 true
40 }
41 }
42 }
43
44 pub fn remove(&mut self, group: &str) -> bool {
47 match self.groups.binary_search_by(|g| g.as_str().cmp(group)) {
48 Ok(idx) => {
49 self.groups.remove(idx);
50 true
51 }
52 Err(_) => false,
53 }
54 }
55
56 pub fn contains(&self, group: &str) -> bool {
57 self.groups
58 .binary_search_by(|g| g.as_str().cmp(group))
59 .is_ok()
60 }
61
62 pub fn is_empty(&self) -> bool {
63 self.groups.is_empty()
64 }
65
66 pub fn iter(&self) -> std::slice::Iter<'_, String> {
67 self.groups.iter()
68 }
69}
70
71#[cfg(test)]
72mod tests {
73 use super::*;
74
75 #[test]
76 fn new_sorts_and_dedups() {
77 let g = AgentGroups::new(["wave2", "canary", "wave1", "canary"]);
78 assert_eq!(g.groups, vec!["canary", "wave1", "wave2"]);
79 }
80
81 #[test]
82 fn round_trips_through_json() {
83 let g = AgentGroups::new(["wave1", "dept-eng"]);
84 let json = serde_json::to_string(&g).unwrap();
85 assert_eq!(json, r#"{"groups":["dept-eng","wave1"]}"#);
86 let back: AgentGroups = serde_json::from_str(&json).unwrap();
87 assert_eq!(back, g);
88 }
89
90 #[test]
91 fn empty_round_trips() {
92 let g = AgentGroups::default();
93 let json = serde_json::to_string(&g).unwrap();
94 assert_eq!(json, r#"{"groups":[]}"#);
95 let back: AgentGroups = serde_json::from_str(&json).unwrap();
96 assert!(back.is_empty());
97 }
98
99 #[test]
100 fn insert_returns_true_on_change_false_on_noop() {
101 let mut g = AgentGroups::new(["wave1"]);
102 assert!(g.insert("canary"));
103 assert!(!g.insert("canary")); assert_eq!(g.groups, vec!["canary", "wave1"]);
105 }
106
107 #[test]
108 fn remove_returns_true_on_change_false_on_noop() {
109 let mut g = AgentGroups::new(["wave1", "canary"]);
110 assert!(g.remove("wave1"));
111 assert!(!g.remove("wave1"));
112 assert_eq!(g.groups, vec!["canary"]);
113 }
114
115 #[test]
116 fn contains_matches_after_mutations() {
117 let mut g = AgentGroups::new(["wave1"]);
118 assert!(g.contains("wave1"));
119 assert!(!g.contains("canary"));
120 g.insert("canary");
121 assert!(g.contains("canary"));
122 g.remove("wave1");
123 assert!(!g.contains("wave1"));
124 }
125
126 #[test]
127 fn accepts_unknown_fields_for_forward_compat() {
128 let json = r#"{"groups":["canary"],"set_by":"alice","set_at":"2026-05-16T01:00:00Z"}"#;
132 let g: AgentGroups = serde_json::from_str(json).unwrap();
133 assert_eq!(g.groups, vec!["canary"]);
134 }
135}