actionqueue_actor/
routing.rs1use actionqueue_core::ids::ActorId;
4
5pub struct CapabilityRouter;
10
11impl CapabilityRouter {
12 pub fn can_handle(actor_capabilities: &[String], required: &[String]) -> bool {
16 required.iter().all(|r| actor_capabilities.iter().any(|c| c == r))
17 }
18
19 pub fn eligible_actors(actors: &[(ActorId, &[String])], required: &[String]) -> Vec<ActorId> {
22 actors
23 .iter()
24 .filter(|(_, caps)| Self::can_handle(caps, required))
25 .map(|(id, _)| *id)
26 .collect()
27 }
28}
29
30#[cfg(test)]
31mod tests {
32 use actionqueue_core::ids::ActorId;
33
34 use super::CapabilityRouter;
35
36 fn caps(c: &[&str]) -> Vec<String> {
37 c.iter().map(|s| s.to_string()).collect()
38 }
39
40 #[test]
41 fn can_handle_all_required_present() {
42 let actor = caps(&["compute", "review", "approve"]);
43 assert!(CapabilityRouter::can_handle(&actor, &caps(&["compute", "review"])));
44 }
45
46 #[test]
47 fn can_handle_missing_requirement() {
48 let actor = caps(&["compute"]);
49 assert!(!CapabilityRouter::can_handle(&actor, &caps(&["compute", "review"])));
50 }
51
52 #[test]
53 fn can_handle_empty_required_always_matches() {
54 let actor = caps(&[]);
55 assert!(CapabilityRouter::can_handle(&actor, &[]));
56 }
57
58 #[test]
59 fn eligible_actors_filters_correctly() {
60 let a = ActorId::new();
61 let b = ActorId::new();
62 let c = ActorId::new();
63 let a_caps = caps(&["compute", "review"]);
64 let b_caps = caps(&["compute"]);
65 let c_caps = caps(&["review"]);
66
67 let actors = vec![(a, a_caps.as_slice()), (b, b_caps.as_slice()), (c, c_caps.as_slice())];
68 let required = caps(&["compute", "review"]);
69 let eligible = CapabilityRouter::eligible_actors(&actors, &required);
70
71 assert_eq!(eligible.len(), 1);
72 assert!(eligible.contains(&a));
73 }
74}