Skip to main content

codewhale_command_contract/
handler.rs

1//! Generic handler transport for staged command migration.
2//!
3//! The output type is generic so FEAT-014 does not move or duplicate the
4//! TUI-owned `CommandResult`. During in-place adoption, the TUI instantiates
5//! `CommandHandler<crate::commands::CommandResult>`.
6
7use crate::facets::{
8    CommandCostContext, CommandModePolicyContext, CommandModelContext, CommandSessionContext,
9    CommandSkillsContext, CommandSystemPromptContext, CommandWorkspaceContext,
10};
11
12/// A command handler that is either argument-only or capability-scoped.
13#[derive(Clone, Copy)]
14pub enum CommandHandler<R> {
15    Pure(fn(Option<&str>) -> R),
16    Contextual(fn(CommandContexts<'_>, Option<&str>) -> R),
17}
18
19/// Transport envelope with one independently optional facet slot.
20pub struct CommandContexts<'a> {
21    session: Option<&'a mut dyn CommandSessionContext>,
22    model: Option<&'a mut dyn CommandModelContext>,
23    cost: Option<&'a mut dyn CommandCostContext>,
24    mode_policy: Option<&'a mut dyn CommandModePolicyContext>,
25    system_prompt: Option<&'a mut dyn CommandSystemPromptContext>,
26    skills: Option<&'a mut dyn CommandSkillsContext>,
27    workspace: Option<&'a mut dyn CommandWorkspaceContext>,
28}
29
30/// Consumed envelope used when one handler needs several independent facets.
31pub struct ContextParts<'a> {
32    pub session: Option<&'a mut dyn CommandSessionContext>,
33    pub model: Option<&'a mut dyn CommandModelContext>,
34    pub cost: Option<&'a mut dyn CommandCostContext>,
35    pub mode_policy: Option<&'a mut dyn CommandModePolicyContext>,
36    pub system_prompt: Option<&'a mut dyn CommandSystemPromptContext>,
37    pub skills: Option<&'a mut dyn CommandSkillsContext>,
38    pub workspace: Option<&'a mut dyn CommandWorkspaceContext>,
39}
40
41impl<'a> CommandContexts<'a> {
42    pub fn empty() -> Self {
43        Self {
44            session: None,
45            model: None,
46            cost: None,
47            mode_policy: None,
48            system_prompt: None,
49            skills: None,
50            workspace: None,
51        }
52    }
53
54    pub fn into_parts(self) -> ContextParts<'a> {
55        ContextParts {
56            session: self.session,
57            model: self.model,
58            cost: self.cost,
59            mode_policy: self.mode_policy,
60            system_prompt: self.system_prompt,
61            skills: self.skills,
62            workspace: self.workspace,
63        }
64    }
65
66    pub fn with_session(mut self, value: &'a mut dyn CommandSessionContext) -> Self {
67        assert!(
68            self.session.replace(value).is_none(),
69            "session facet already set"
70        );
71        self
72    }
73
74    pub fn with_model(mut self, value: &'a mut dyn CommandModelContext) -> Self {
75        assert!(
76            self.model.replace(value).is_none(),
77            "model facet already set"
78        );
79        self
80    }
81
82    pub fn with_cost(mut self, value: &'a mut dyn CommandCostContext) -> Self {
83        assert!(self.cost.replace(value).is_none(), "cost facet already set");
84        self
85    }
86
87    pub fn with_mode_policy(mut self, value: &'a mut dyn CommandModePolicyContext) -> Self {
88        assert!(
89            self.mode_policy.replace(value).is_none(),
90            "mode-policy facet already set"
91        );
92        self
93    }
94
95    pub fn with_system_prompt(mut self, value: &'a mut dyn CommandSystemPromptContext) -> Self {
96        assert!(
97            self.system_prompt.replace(value).is_none(),
98            "system-prompt facet already set"
99        );
100        self
101    }
102
103    pub fn with_skills(mut self, value: &'a mut dyn CommandSkillsContext) -> Self {
104        assert!(
105            self.skills.replace(value).is_none(),
106            "skills facet already set"
107        );
108        self
109    }
110
111    pub fn with_workspace(mut self, value: &'a mut dyn CommandWorkspaceContext) -> Self {
112        assert!(
113            self.workspace.replace(value).is_none(),
114            "workspace facet already set"
115        );
116        self
117    }
118}
119
120impl Default for CommandContexts<'_> {
121    fn default() -> Self {
122        Self::empty()
123    }
124}