rustant_core/channels/
routing.rs1use super::{ChannelMessage, ChannelType};
4use serde::{Deserialize, Serialize};
5use uuid::Uuid;
6
7#[derive(Debug, Clone, Serialize, Deserialize)]
9pub enum RoutingCondition {
10 ChannelType(ChannelType),
12 UserId(String),
14 MessageContains(String),
16 CommandPrefix(String),
18}
19
20#[derive(Debug, Clone, Serialize, Deserialize)]
22pub struct RoutingRule {
23 pub priority: u32,
24 pub conditions: Vec<RoutingCondition>,
25 pub target_agent: Uuid,
26}
27
28#[derive(Debug, Clone, Default)]
30pub struct ChannelRouter {
31 rules: Vec<RoutingRule>,
32 default_agent: Option<Uuid>,
33}
34
35impl ChannelRouter {
36 pub fn new() -> Self {
37 Self::default()
38 }
39
40 pub fn with_default_agent(mut self, agent_id: Uuid) -> Self {
42 self.default_agent = Some(agent_id);
43 self
44 }
45
46 pub fn add_rule(&mut self, rule: RoutingRule) {
48 self.rules.push(rule);
49 self.rules.sort_by(|a, b| a.priority.cmp(&b.priority));
50 }
51
52 pub fn rule_count(&self) -> usize {
54 self.rules.len()
55 }
56
57 pub fn route(&self, msg: &ChannelMessage) -> Option<Uuid> {
59 for rule in &self.rules {
60 if self.matches_rule(rule, msg) {
61 return Some(rule.target_agent);
62 }
63 }
64 self.default_agent
65 }
66
67 fn matches_rule(&self, rule: &RoutingRule, msg: &ChannelMessage) -> bool {
68 rule.conditions
69 .iter()
70 .all(|cond| self.matches_condition(cond, msg))
71 }
72
73 fn matches_condition(&self, cond: &RoutingCondition, msg: &ChannelMessage) -> bool {
74 match cond {
75 RoutingCondition::ChannelType(ct) => msg.channel_type == *ct,
76 RoutingCondition::UserId(id) => msg.sender.id == *id,
77 RoutingCondition::MessageContains(sub) => msg
78 .content
79 .as_text()
80 .map(|t| t.contains(sub.as_str()))
81 .unwrap_or(false),
82 RoutingCondition::CommandPrefix(prefix) => msg
83 .content
84 .as_text()
85 .map(|t| t.starts_with(prefix.as_str()))
86 .unwrap_or(false),
87 }
88 }
89}
90
91#[cfg(test)]
92mod tests {
93 use super::*;
94 use crate::channels::ChannelUser;
95
96 fn make_msg(channel_type: ChannelType, user_id: &str, text: &str) -> ChannelMessage {
97 let sender = ChannelUser::new(user_id, channel_type);
98 ChannelMessage::text(channel_type, "ch1", sender, text)
99 }
100
101 #[test]
102 fn test_router_no_rules_no_default() {
103 let router = ChannelRouter::new();
104 let msg = make_msg(ChannelType::Telegram, "u1", "hello");
105 assert!(router.route(&msg).is_none());
106 }
107
108 #[test]
109 fn test_router_default_agent() {
110 let default_id = Uuid::new_v4();
111 let router = ChannelRouter::new().with_default_agent(default_id);
112 let msg = make_msg(ChannelType::Slack, "u1", "hello");
113 assert_eq!(router.route(&msg), Some(default_id));
114 }
115
116 #[test]
117 fn test_router_channel_type_rule() {
118 let agent_tg = Uuid::new_v4();
119 let agent_sl = Uuid::new_v4();
120
121 let mut router = ChannelRouter::new();
122 router.add_rule(RoutingRule {
123 priority: 1,
124 conditions: vec![RoutingCondition::ChannelType(ChannelType::Telegram)],
125 target_agent: agent_tg,
126 });
127 router.add_rule(RoutingRule {
128 priority: 2,
129 conditions: vec![RoutingCondition::ChannelType(ChannelType::Slack)],
130 target_agent: agent_sl,
131 });
132
133 let tg_msg = make_msg(ChannelType::Telegram, "u1", "hi");
134 assert_eq!(router.route(&tg_msg), Some(agent_tg));
135
136 let sl_msg = make_msg(ChannelType::Slack, "u1", "hi");
137 assert_eq!(router.route(&sl_msg), Some(agent_sl));
138 }
139
140 #[test]
141 fn test_router_command_prefix_rule() {
142 let special_agent = Uuid::new_v4();
143 let default_agent = Uuid::new_v4();
144
145 let mut router = ChannelRouter::new().with_default_agent(default_agent);
146 router.add_rule(RoutingRule {
147 priority: 1,
148 conditions: vec![RoutingCondition::CommandPrefix("/admin".into())],
149 target_agent: special_agent,
150 });
151
152 let admin_msg = make_msg(ChannelType::Telegram, "u1", "/admin status");
153 assert_eq!(router.route(&admin_msg), Some(special_agent));
154
155 let normal_msg = make_msg(ChannelType::Telegram, "u1", "hello");
156 assert_eq!(router.route(&normal_msg), Some(default_agent));
157 }
158}