1#![cfg_attr(test, allow(clippy::expect_used, clippy::unwrap_used))]
2
3use serde::{Deserialize, Serialize};
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
9#[serde(rename_all = "snake_case")]
10pub enum EventName {
11 SessionBeforeSwitch,
12 BeforeUserMessage,
13 Command,
14}
15
16#[derive(Debug, Clone, Serialize)]
17#[serde(tag = "event", rename_all = "snake_case")]
18pub enum Event {
19 SessionBeforeSwitch {
20 reason: String,
21 #[serde(skip_serializing_if = "Option::is_none")]
22 session_id: Option<String>,
23 },
24 BeforeUserMessage {
25 text: String,
26 #[serde(skip_serializing_if = "Vec::is_empty")]
27 attachments: Vec<crate::user_message::Attachment>,
28 },
29 Command {
30 name: String,
31 args: String,
32 },
33}
34
35#[derive(Debug, Clone, Deserialize, PartialEq, Eq)]
36#[serde(tag = "action", rename_all = "snake_case")]
37pub enum Action {
38 Continue,
39 Cancel {
40 #[serde(default)]
41 reason: Option<String>,
42 },
43 TransformText {
44 text: String,
45 },
46 Reply {
47 text: String,
48 },
49 Send {
50 text: String,
51 #[serde(default)]
52 attachments: Vec<crate::user_message::Attachment>,
53 },
54}
55
56#[cfg(test)]
57mod tests {
58 use super::*;
59 use pretty_assertions::assert_eq;
60
61 #[test]
62 fn event_session_before_switch_serializes_with_reason() {
63 let ev = Event::SessionBeforeSwitch {
64 reason: "new".into(),
65 session_id: None,
66 };
67 let json = serde_json::to_string(&ev).expect("serialize");
68 assert_eq!(json, r#"{"event":"session_before_switch","reason":"new"}"#);
69 }
70
71 #[test]
72 fn event_session_before_switch_includes_session_id_when_set() {
73 let ev = Event::SessionBeforeSwitch {
74 reason: "load".into(),
75 session_id: Some("01HK".into()),
76 };
77 let json = serde_json::to_string(&ev).expect("serialize");
78 assert_eq!(
79 json,
80 r#"{"event":"session_before_switch","reason":"load","session_id":"01HK"}"#
81 );
82 }
83
84 #[test]
85 fn action_continue_deserializes() {
86 let action: Action = serde_json::from_str(r#"{"action":"continue"}"#).expect("deserialize");
87 assert_eq!(action, Action::Continue);
88 }
89
90 #[test]
91 fn action_cancel_with_reason_deserializes() {
92 let action: Action =
93 serde_json::from_str(r#"{"action":"cancel","reason":"nope"}"#).expect("deserialize");
94 assert_eq!(
95 action,
96 Action::Cancel {
97 reason: Some("nope".into())
98 }
99 );
100 }
101
102 #[test]
103 fn action_cancel_without_reason_deserializes() {
104 let action: Action = serde_json::from_str(r#"{"action":"cancel"}"#).expect("deserialize");
105 assert_eq!(action, Action::Cancel { reason: None });
106 }
107
108 #[test]
109 fn action_unknown_tag_rejected() {
110 let result: Result<Action, _> = serde_json::from_str(r#"{"action":"unknown"}"#);
111 assert!(result.is_err(), "unknown action tag should fail");
112 }
113
114 #[test]
115 fn event_before_user_message_serializes_with_text_only() {
116 let ev = Event::BeforeUserMessage {
117 text: "hi".into(),
118 attachments: Vec::new(),
119 };
120 let json = serde_json::to_string(&ev).expect("serialize");
121 assert_eq!(json, r#"{"event":"before_user_message","text":"hi"}"#);
122 }
123
124 #[test]
125 fn event_before_user_message_includes_attachments_when_present() {
126 let ev = Event::BeforeUserMessage {
127 text: "look".into(),
128 attachments: vec![crate::user_message::Attachment::Image {
129 path: std::path::PathBuf::from("/tmp/x.png"),
130 }],
131 };
132 let json = serde_json::to_string(&ev).expect("serialize");
133 assert!(json.contains(r#""event":"before_user_message""#));
134 assert!(json.contains(r#""text":"look""#));
135 assert!(json.contains(r#""attachments":[{"type":"image","path":"/tmp/x.png"}]"#));
136 }
137
138 #[test]
139 fn event_command_serializes_with_name_and_args() {
140 let ev = Event::Command {
141 name: "todo".into(),
142 args: "add buy milk".into(),
143 };
144 let json = serde_json::to_string(&ev).expect("serialize");
145 assert_eq!(
146 json,
147 r#"{"event":"command","name":"todo","args":"add buy milk"}"#
148 );
149 }
150
151 #[test]
152 fn action_transform_text_deserializes() {
153 let action: Action =
154 serde_json::from_str(r#"{"action":"transform_text","text":"new"}"#).expect("ok");
155 assert_eq!(action, Action::TransformText { text: "new".into() });
156 }
157
158 #[test]
159 fn action_reply_deserializes() {
160 let action: Action = serde_json::from_str(r#"{"action":"reply","text":"yo"}"#).expect("ok");
161 assert_eq!(action, Action::Reply { text: "yo".into() });
162 }
163
164 #[test]
165 fn action_send_with_attachments_deserializes() {
166 let action: Action = serde_json::from_str(
167 r#"{"action":"send","text":"go","attachments":[{"type":"image","path":"/tmp/x.png"}]}"#,
168 )
169 .expect("ok");
170 match action {
171 Action::Send { text, attachments } => {
172 assert_eq!(text, "go");
173 assert_eq!(attachments.len(), 1);
174 }
175 other => panic!("expected Send; got {other:?}"),
176 }
177 }
178
179 #[test]
180 fn action_send_without_attachments_deserializes() {
181 let action: Action = serde_json::from_str(r#"{"action":"send","text":"go"}"#).expect("ok");
182 match action {
183 Action::Send { text, attachments } => {
184 assert_eq!(text, "go");
185 assert!(attachments.is_empty());
186 }
187 other => panic!("expected Send; got {other:?}"),
188 }
189 }
190
191 #[test]
192 fn event_name_serializes_to_snake_case() {
193 assert_eq!(
194 serde_json::to_string(&EventName::SessionBeforeSwitch).expect("ser"),
195 "\"session_before_switch\""
196 );
197 assert_eq!(
198 serde_json::to_string(&EventName::BeforeUserMessage).expect("ser"),
199 "\"before_user_message\""
200 );
201 assert_eq!(
202 serde_json::to_string(&EventName::Command).expect("ser"),
203 "\"command\""
204 );
205 }
206}