lattice_host/
mode_action_handlers.rs1use lattice_grammar::CommandRegistry;
17use lattice_mode::{ActionHandlerRegistration, ActionHandlerRegistryHandle, ModeRegistry};
18
19pub fn register_mode_action_handlers(
29 action_handlers: &ActionHandlerRegistryHandle,
30 mode_registry: &ModeRegistry,
31 command_registry: &CommandRegistry,
32) -> Vec<ActionHandlerRegistration> {
33 let mut tokens = Vec::new();
34 for (mode_id, mode) in mode_registry.iter() {
35 for contribution in mode.action_handlers() {
36 match command_registry.id_by_name(contribution.action_name) {
37 Some(id) => tokens.push(action_handlers.register(id, contribution.handler)),
38 None => tracing::debug!(
39 mode = %mode_id,
40 action = contribution.action_name,
41 "mode action handler skipped: command not registered"
42 ),
43 }
44 }
45 }
46 tokens
47}
48
49#[cfg(test)]
50mod tests {
51 #![allow(clippy::unwrap_used)]
52 use super::*;
53 use lattice_grammar::registry::ActionSpec;
54 use lattice_mode::{
55 ActionContext, ActionHandler, ActionHandlerContribution, ActionHandlerRegistry,
56 CapabilitySet, LifecycleFuture, Mode, ModeContext, ModeId, ModeKind,
57 };
58 use std::sync::Arc;
59
60 struct HandlerMode {
63 id: ModeId,
64 action_name: &'static str,
65 }
66
67 impl Mode for HandlerMode {
68 type Guard = ();
69 fn id(&self) -> ModeId {
70 self.id
71 }
72 fn kind(&self) -> ModeKind {
73 ModeKind::Minor
74 }
75 fn required_capabilities(&self) -> CapabilitySet {
76 CapabilitySet::empty()
77 }
78 fn action_handlers(&self) -> Vec<ActionHandlerContribution> {
79 let handler: ActionHandler = Arc::new(|_ctx: &ActionContext<'_>| None);
80 vec![ActionHandlerContribution {
81 action_name: self.action_name,
82 handler,
83 }]
84 }
85 fn on_activate(&self, _ctx: ModeContext) -> LifecycleFuture<'_, Self::Guard> {
86 Box::pin(async { Ok(()) })
87 }
88 }
89
90 fn cmd_registry_with(name: &str) -> (CommandRegistry, lattice_protocol::ids::CommandId) {
91 let mut cmd = CommandRegistry::new();
92 let id = cmd.register_action(
93 name,
94 "test action",
95 ActionSpec {
96 apply: Arc::new(|_| Ok(lattice_grammar::effect::Effect::None)),
97 args_schema: Vec::new(),
98 },
99 );
100 (cmd, id)
101 }
102
103 #[test]
104 fn registers_contributed_handler_and_tokens_control_lifetime() {
105 let (cmd, id) = cmd_registry_with("test:act");
106 let mut modes = ModeRegistry::new();
107 modes
108 .register(HandlerMode {
109 id: ModeId::new("handler-mode"),
110 action_name: "test:act",
111 })
112 .unwrap();
113 let action_handlers: ActionHandlerRegistryHandle = Arc::new(ActionHandlerRegistry::new());
114
115 let tokens = register_mode_action_handlers(&action_handlers, &modes, &cmd);
116 assert_eq!(tokens.len(), 1, "one contribution registered");
117 assert!(
118 action_handlers.lookup(id).is_some(),
119 "handler resolvable globally after the boot walk"
120 );
121
122 drop(tokens);
126 assert!(
127 action_handlers.lookup(id).is_none(),
128 "dropping the boot tokens unregisters the handler"
129 );
130 }
131
132 #[test]
133 fn unknown_action_name_is_skipped_not_panicked() {
134 let cmd = CommandRegistry::new(); let mut modes = ModeRegistry::new();
136 modes
137 .register(HandlerMode {
138 id: ModeId::new("handler-mode"),
139 action_name: "test:nonexistent",
140 })
141 .unwrap();
142 let action_handlers: ActionHandlerRegistryHandle = Arc::new(ActionHandlerRegistry::new());
143
144 let tokens = register_mode_action_handlers(&action_handlers, &modes, &cmd);
145 assert!(tokens.is_empty(), "unresolvable name contributes no token");
146 assert_eq!(action_handlers.registered_count(), 0);
147 }
148}