use crate::channels::IncomingMessage;
#[derive(Debug, Clone)]
pub enum MessageIntent {
CreateJob {
title: String,
description: String,
category: Option<String>,
},
CheckJobStatus { job_id: Option<String> },
CancelJob { job_id: String },
ListJobs { filter: Option<String> },
HelpJob { job_id: String },
Chat { content: String },
Command { command: String, args: Vec<String> },
Unknown,
}
pub struct Router {
command_prefix: String,
}
impl Router {
pub fn new() -> Self {
Self {
command_prefix: "/".to_string(),
}
}
pub fn with_prefix(mut self, prefix: impl Into<String>) -> Self {
self.command_prefix = prefix.into();
self
}
pub fn is_command(&self, message: &IncomingMessage) -> bool {
message.content.trim().starts_with(&self.command_prefix)
}
pub fn route_command(&self, message: &IncomingMessage) -> Option<MessageIntent> {
let content = message.content.trim();
if content.starts_with(&self.command_prefix) {
Some(self.parse_command(content))
} else {
None
}
}
fn parse_command(&self, content: &str) -> MessageIntent {
let without_prefix = content
.strip_prefix(&self.command_prefix)
.unwrap_or(content);
let parts: Vec<&str> = without_prefix.split_whitespace().collect();
match parts.first().map(|s| s.to_lowercase()).as_deref() {
Some("job") | Some("create") => {
let rest = parts[1..].join(" ");
MessageIntent::CreateJob {
title: rest.clone(),
description: rest,
category: None,
}
}
Some("status") => {
let job_id = parts.get(1).map(|s| s.to_string());
MessageIntent::CheckJobStatus { job_id }
}
Some("cancel") => {
if let Some(job_id) = parts.get(1) {
MessageIntent::CancelJob {
job_id: job_id.to_string(),
}
} else {
MessageIntent::Unknown
}
}
Some("list") | Some("jobs") => {
let filter = parts.get(1).map(|s| s.to_string());
MessageIntent::ListJobs { filter }
}
Some("help") => {
if let Some(job_id) = parts.get(1) {
MessageIntent::HelpJob {
job_id: job_id.to_string(),
}
} else {
MessageIntent::Command {
command: "help".to_string(),
args: vec![],
}
}
}
Some(cmd) => MessageIntent::Command {
command: cmd.to_string(),
args: parts[1..].iter().map(|s| s.to_string()).collect(),
},
None => MessageIntent::Unknown,
}
}
}
impl Default for Router {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_command_routing() {
let router = Router::new();
let msg = IncomingMessage::new("test", "user", "/status abc-123");
let intent = router.route_command(&msg);
assert!(matches!(intent, Some(MessageIntent::CheckJobStatus { .. })));
}
#[test]
fn test_is_command() {
let router = Router::new();
let cmd_msg = IncomingMessage::new("test", "user", "/status");
assert!(router.is_command(&cmd_msg));
let chat_msg = IncomingMessage::new("test", "user", "Hello there");
assert!(!router.is_command(&chat_msg));
}
#[test]
fn test_non_command_returns_none() {
let router = Router::new();
let msg = IncomingMessage::new("test", "user", "Can you create a website for me?");
assert!(router.route_command(&msg).is_none());
let msg2 = IncomingMessage::new("test", "user", "Hello, how are you?");
assert!(router.route_command(&msg2).is_none());
}
#[test]
fn test_command_create_job() {
let router = Router::new();
let msg = IncomingMessage::new("test", "user", "/job build a website");
let intent = router.route_command(&msg);
match intent {
Some(MessageIntent::CreateJob { title, .. }) => {
assert_eq!(title, "build a website");
}
_ => panic!("Expected CreateJob intent"),
}
}
#[test]
fn test_command_list_jobs() {
let router = Router::new();
let msg = IncomingMessage::new("test", "user", "/list active");
let intent = router.route_command(&msg);
match intent {
Some(MessageIntent::ListJobs { filter }) => {
assert_eq!(filter, Some("active".to_string()));
}
_ => panic!("Expected ListJobs intent"),
}
}
}