clawft_types/
agent_routing.rs1use serde::{Deserialize, Serialize};
24
25#[derive(Debug, Clone, Serialize, Deserialize)]
27pub struct AgentRoute {
28 pub channel: String,
30
31 #[serde(rename = "match", default)]
33 pub match_criteria: MatchCriteria,
34
35 pub agent: String,
37}
38
39#[derive(Debug, Clone, Default, Serialize, Deserialize)]
44pub struct MatchCriteria {
45 #[serde(default, skip_serializing_if = "Option::is_none")]
47 pub user_id: Option<String>,
48
49 #[serde(default, skip_serializing_if = "Option::is_none")]
51 pub phone: Option<String>,
52
53 #[serde(default, skip_serializing_if = "Option::is_none")]
55 pub chat_id: Option<String>,
56}
57
58impl MatchCriteria {
59 pub fn matches(&self, sender_id: &str, chat_id: &str) -> bool {
63 if let Some(ref uid) = self.user_id
64 && uid != sender_id
65 {
66 return false;
67 }
68 if let Some(ref phone) = self.phone
69 && phone != sender_id
70 && phone != chat_id
71 {
72 return false;
74 }
75 if let Some(ref cid) = self.chat_id
76 && cid != chat_id
77 {
78 return false;
79 }
80 true
81 }
82
83 pub fn is_empty(&self) -> bool {
85 self.user_id.is_none() && self.phone.is_none() && self.chat_id.is_none()
86 }
87}
88
89#[derive(Debug, Clone, Default, Serialize, Deserialize)]
91pub struct AgentRoutingConfig {
92 #[serde(default)]
94 pub routes: Vec<AgentRoute>,
95
96 #[serde(default, skip_serializing_if = "Option::is_none")]
98 pub catch_all: Option<String>,
99}
100
101#[cfg(test)]
102mod tests {
103 use super::*;
104
105 #[test]
106 fn match_criteria_empty_matches_all() {
107 let criteria = MatchCriteria::default();
108 assert!(criteria.is_empty());
109 assert!(criteria.matches("any_user", "any_chat"));
110 }
111
112 #[test]
113 fn match_criteria_user_id() {
114 let criteria = MatchCriteria {
115 user_id: Some("user123".into()),
116 ..Default::default()
117 };
118 assert!(criteria.matches("user123", "chat1"));
119 assert!(!criteria.matches("other_user", "chat1"));
120 }
121
122 #[test]
123 fn match_criteria_phone() {
124 let criteria = MatchCriteria {
125 phone: Some("+1234567890".into()),
126 ..Default::default()
127 };
128 assert!(criteria.matches("+1234567890", "chat1"));
130 assert!(criteria.matches("other", "+1234567890"));
132 assert!(!criteria.matches("other", "chat1"));
134 }
135
136 #[test]
137 fn match_criteria_chat_id() {
138 let criteria = MatchCriteria {
139 chat_id: Some("chat42".into()),
140 ..Default::default()
141 };
142 assert!(criteria.matches("any_user", "chat42"));
143 assert!(!criteria.matches("any_user", "other_chat"));
144 }
145
146 #[test]
147 fn match_criteria_combined_and() {
148 let criteria = MatchCriteria {
149 user_id: Some("user1".into()),
150 chat_id: Some("chat1".into()),
151 ..Default::default()
152 };
153 assert!(criteria.matches("user1", "chat1"));
155 assert!(!criteria.matches("user2", "chat1"));
157 assert!(!criteria.matches("user1", "chat2"));
159 }
160
161 #[test]
162 fn agent_route_serde_roundtrip() {
163 let route = AgentRoute {
164 channel: "telegram".into(),
165 match_criteria: MatchCriteria {
166 user_id: Some("12345".into()),
167 ..Default::default()
168 },
169 agent: "work-agent".into(),
170 };
171 let json = serde_json::to_string(&route).unwrap();
172 let restored: AgentRoute = serde_json::from_str(&json).unwrap();
173 assert_eq!(restored.channel, "telegram");
174 assert_eq!(restored.agent, "work-agent");
175 assert_eq!(restored.match_criteria.user_id.as_deref(), Some("12345"));
176 }
177
178 #[test]
179 fn agent_routing_config_defaults() {
180 let cfg = AgentRoutingConfig::default();
181 assert!(cfg.routes.is_empty());
182 assert!(cfg.catch_all.is_none());
183 }
184
185 #[test]
186 fn agent_routing_config_serde_with_catch_all() {
187 let json = r#"{
188 "routes": [
189 {"channel": "telegram", "match": {"user_id": "123"}, "agent": "bot-a"},
190 {"channel": "slack", "agent": "bot-b"}
191 ],
192 "catch_all": "default-bot"
193 }"#;
194 let cfg: AgentRoutingConfig = serde_json::from_str(json).unwrap();
195 assert_eq!(cfg.routes.len(), 2);
196 assert_eq!(cfg.routes[0].agent, "bot-a");
197 assert_eq!(cfg.routes[1].agent, "bot-b");
198 assert_eq!(cfg.catch_all.as_deref(), Some("default-bot"));
199 }
200}