Skip to main content

uira_core/protocol/primitives/
result.rs

1use serde::{Deserialize, Serialize};
2
3use super::types::PermissionDecision;
4
5fn default_true() -> bool {
6    true
7}
8
9#[derive(Debug, Clone, Serialize, Deserialize)]
10pub struct HookOutput {
11    #[serde(default = "default_true", rename = "continue")]
12    pub continue_processing: bool,
13
14    #[serde(
15        default,
16        skip_serializing_if = "Option::is_none",
17        rename = "stopReason"
18    )]
19    pub stop_reason: Option<String>,
20
21    #[serde(default, skip_serializing_if = "Option::is_none")]
22    pub message: Option<String>,
23
24    #[serde(default, skip_serializing_if = "Option::is_none")]
25    pub decision: Option<PermissionDecision>,
26
27    #[serde(default, skip_serializing_if = "Option::is_none")]
28    pub reason: Option<String>,
29
30    #[serde(
31        default,
32        skip_serializing_if = "Option::is_none",
33        rename = "additionalContext"
34    )]
35    pub additional_context: Option<String>,
36
37    #[serde(
38        default,
39        skip_serializing_if = "Option::is_none",
40        rename = "suppressOutput"
41    )]
42    pub suppress_output: Option<bool>,
43
44    #[serde(
45        default,
46        skip_serializing_if = "Option::is_none",
47        rename = "systemMessage"
48    )]
49    pub system_message: Option<String>,
50}
51
52impl Default for HookOutput {
53    fn default() -> Self {
54        Self {
55            continue_processing: true,
56            stop_reason: None,
57            message: None,
58            decision: None,
59            reason: None,
60            additional_context: None,
61            suppress_output: None,
62            system_message: None,
63        }
64    }
65}
66
67impl HookOutput {
68    pub fn allow() -> Self {
69        Self::default()
70    }
71
72    pub fn deny(reason: &str) -> Self {
73        Self {
74            continue_processing: false,
75            decision: Some(PermissionDecision::Deny),
76            reason: Some(reason.to_string()),
77            ..Default::default()
78        }
79    }
80
81    pub fn with_message(message: &str) -> Self {
82        Self {
83            message: Some(message.to_string()),
84            ..Default::default()
85        }
86    }
87
88    pub fn stop(reason: &str) -> Self {
89        Self {
90            continue_processing: false,
91            stop_reason: Some(reason.to_string()),
92            ..Default::default()
93        }
94    }
95}
96
97#[derive(Debug, Clone)]
98pub struct HookResult {
99    pub success: bool,
100    pub should_continue: bool,
101    pub message: Option<String>,
102    pub output: Option<String>,
103}
104
105impl Default for HookResult {
106    fn default() -> Self {
107        Self {
108            success: true,
109            should_continue: true,
110            message: None,
111            output: None,
112        }
113    }
114}
115
116#[cfg(test)]
117mod tests {
118    use super::*;
119
120    #[test]
121    fn test_hook_output_default() {
122        let output = HookOutput::default();
123        assert!(output.continue_processing);
124        assert!(output.message.is_none());
125    }
126
127    #[test]
128    fn test_hook_output_with_message() {
129        let output = HookOutput::with_message("test message");
130        assert!(output.continue_processing);
131        assert_eq!(output.message, Some("test message".to_string()));
132    }
133
134    #[test]
135    fn test_hook_output_deny() {
136        let output = HookOutput::deny("not allowed");
137        assert!(!output.continue_processing);
138        assert!(matches!(output.decision, Some(PermissionDecision::Deny)));
139    }
140
141    #[test]
142    fn test_serialize_output() {
143        let output = HookOutput::with_message("hello");
144        let json = serde_json::to_string(&output).unwrap();
145        assert!(json.contains("\"continue\":true"));
146        assert!(json.contains("\"message\":\"hello\""));
147    }
148}