#![cfg_attr(test, allow(clippy::expect_used, clippy::unwrap_used))]
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum EventName {
SessionBeforeSwitch,
BeforeUserMessage,
Command,
}
#[derive(Debug, Clone, Serialize)]
#[serde(tag = "event", rename_all = "snake_case")]
pub enum Event {
SessionBeforeSwitch {
reason: String,
#[serde(skip_serializing_if = "Option::is_none")]
session_id: Option<String>,
},
BeforeUserMessage {
text: String,
#[serde(skip_serializing_if = "Vec::is_empty")]
attachments: Vec<crate::user_message::Attachment>,
},
Command {
name: String,
args: String,
},
}
#[derive(Debug, Clone, Deserialize, PartialEq, Eq)]
#[serde(tag = "action", rename_all = "snake_case")]
pub enum Action {
Continue,
Cancel {
#[serde(default)]
reason: Option<String>,
},
TransformText {
text: String,
},
Reply {
text: String,
},
Send {
text: String,
#[serde(default)]
attachments: Vec<crate::user_message::Attachment>,
},
}
#[cfg(test)]
mod tests {
use super::*;
use pretty_assertions::assert_eq;
#[test]
fn event_session_before_switch_serializes_with_reason() {
let ev = Event::SessionBeforeSwitch {
reason: "new".into(),
session_id: None,
};
let json = serde_json::to_string(&ev).expect("serialize");
assert_eq!(json, r#"{"event":"session_before_switch","reason":"new"}"#);
}
#[test]
fn event_session_before_switch_includes_session_id_when_set() {
let ev = Event::SessionBeforeSwitch {
reason: "load".into(),
session_id: Some("01HK".into()),
};
let json = serde_json::to_string(&ev).expect("serialize");
assert_eq!(
json,
r#"{"event":"session_before_switch","reason":"load","session_id":"01HK"}"#
);
}
#[test]
fn action_continue_deserializes() {
let action: Action = serde_json::from_str(r#"{"action":"continue"}"#).expect("deserialize");
assert_eq!(action, Action::Continue);
}
#[test]
fn action_cancel_with_reason_deserializes() {
let action: Action =
serde_json::from_str(r#"{"action":"cancel","reason":"nope"}"#).expect("deserialize");
assert_eq!(
action,
Action::Cancel {
reason: Some("nope".into())
}
);
}
#[test]
fn action_cancel_without_reason_deserializes() {
let action: Action = serde_json::from_str(r#"{"action":"cancel"}"#).expect("deserialize");
assert_eq!(action, Action::Cancel { reason: None });
}
#[test]
fn action_unknown_tag_rejected() {
let result: Result<Action, _> = serde_json::from_str(r#"{"action":"unknown"}"#);
assert!(result.is_err(), "unknown action tag should fail");
}
#[test]
fn event_before_user_message_serializes_with_text_only() {
let ev = Event::BeforeUserMessage {
text: "hi".into(),
attachments: Vec::new(),
};
let json = serde_json::to_string(&ev).expect("serialize");
assert_eq!(json, r#"{"event":"before_user_message","text":"hi"}"#);
}
#[test]
fn event_before_user_message_includes_attachments_when_present() {
let ev = Event::BeforeUserMessage {
text: "look".into(),
attachments: vec![crate::user_message::Attachment::Image {
path: std::path::PathBuf::from("/tmp/x.png"),
}],
};
let json = serde_json::to_string(&ev).expect("serialize");
assert!(json.contains(r#""event":"before_user_message""#));
assert!(json.contains(r#""text":"look""#));
assert!(json.contains(r#""attachments":[{"type":"image","path":"/tmp/x.png"}]"#));
}
#[test]
fn event_command_serializes_with_name_and_args() {
let ev = Event::Command {
name: "todo".into(),
args: "add buy milk".into(),
};
let json = serde_json::to_string(&ev).expect("serialize");
assert_eq!(
json,
r#"{"event":"command","name":"todo","args":"add buy milk"}"#
);
}
#[test]
fn action_transform_text_deserializes() {
let action: Action =
serde_json::from_str(r#"{"action":"transform_text","text":"new"}"#).expect("ok");
assert_eq!(action, Action::TransformText { text: "new".into() });
}
#[test]
fn action_reply_deserializes() {
let action: Action = serde_json::from_str(r#"{"action":"reply","text":"yo"}"#).expect("ok");
assert_eq!(action, Action::Reply { text: "yo".into() });
}
#[test]
fn action_send_with_attachments_deserializes() {
let action: Action = serde_json::from_str(
r#"{"action":"send","text":"go","attachments":[{"type":"image","path":"/tmp/x.png"}]}"#,
)
.expect("ok");
match action {
Action::Send { text, attachments } => {
assert_eq!(text, "go");
assert_eq!(attachments.len(), 1);
}
other => panic!("expected Send; got {other:?}"),
}
}
#[test]
fn action_send_without_attachments_deserializes() {
let action: Action = serde_json::from_str(r#"{"action":"send","text":"go"}"#).expect("ok");
match action {
Action::Send { text, attachments } => {
assert_eq!(text, "go");
assert!(attachments.is_empty());
}
other => panic!("expected Send; got {other:?}"),
}
}
#[test]
fn event_name_serializes_to_snake_case() {
assert_eq!(
serde_json::to_string(&EventName::SessionBeforeSwitch).expect("ser"),
"\"session_before_switch\""
);
assert_eq!(
serde_json::to_string(&EventName::BeforeUserMessage).expect("ser"),
"\"before_user_message\""
);
assert_eq!(
serde_json::to_string(&EventName::Command).expect("ser"),
"\"command\""
);
}
}