99 lines
3.9 KiB
Rust
99 lines
3.9 KiB
Rust
//! Bot command service — domain logic for dispatching slash commands.
|
|
//!
|
|
//! Extracted from `http/bot_command.rs` so that argument parsing and dispatch
|
|
//! are independently testable without an HTTP layer.
|
|
//!
|
|
//! Conventions: `docs/architecture/service-modules.md`
|
|
//!
|
|
//! # Structure
|
|
//! - `mod.rs` (this file) — public API and typed `Error` type
|
|
//! - `parse.rs` — pure argument parsing, no I/O
|
|
//! - `io.rs` — all side-effectful calls (transport handlers, stores, agent pool)
|
|
|
|
pub(super) mod io;
|
|
/// Pure argument parsing for bot slash commands.
|
|
pub mod parse;
|
|
|
|
use crate::agents::AgentPool;
|
|
use std::path::Path;
|
|
use std::sync::Arc;
|
|
|
|
// ── Error type ────────────────────────────────────────────────────────────────
|
|
|
|
/// Typed errors returned by `service::bot_command::execute`.
|
|
///
|
|
/// HTTP handlers map these to specific status codes:
|
|
/// - [`Error::UnknownCommand`] → 404 Not Found
|
|
/// - [`Error::BadArgs`] → 400 Bad Request
|
|
/// - [`Error::CommandFailed`] → 500 Internal Server Error
|
|
#[derive(Debug)]
|
|
#[allow(dead_code)] // CommandFailed is part of the public API contract; not yet reachable
|
|
pub enum Error {
|
|
/// The command keyword does not match any registered command.
|
|
UnknownCommand(String),
|
|
/// The command exists but the provided arguments are invalid.
|
|
BadArgs(String),
|
|
/// The command ran but failed with an internal error.
|
|
CommandFailed(String),
|
|
}
|
|
|
|
impl std::fmt::Display for Error {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
match self {
|
|
Self::UnknownCommand(msg) | Self::BadArgs(msg) | Self::CommandFailed(msg) => {
|
|
write!(f, "{msg}")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// ── Public API ────────────────────────────────────────────────────────────────
|
|
|
|
/// Execute a bot command and return the markdown response.
|
|
///
|
|
/// Dispatches to the same handlers used by the Matrix and Slack bots. The
|
|
/// `cmd` argument is the lower-cased command keyword (e.g. `"status"`,
|
|
/// `"start"`). The `args` argument is any text after the keyword, already
|
|
/// trimmed.
|
|
///
|
|
/// # Errors
|
|
/// - [`Error::UnknownCommand`] if the command keyword is not registered.
|
|
/// - [`Error::BadArgs`] if the arguments fail validation.
|
|
/// - [`Error::CommandFailed`] if command execution raises an internal error.
|
|
pub async fn execute(
|
|
cmd: &str,
|
|
args: &str,
|
|
project_root: &Path,
|
|
agents: &Arc<AgentPool>,
|
|
) -> Result<String, Error> {
|
|
match cmd {
|
|
"assign" => {
|
|
let parsed = parse::parse_assign(args).map_err(Error::BadArgs)?;
|
|
Ok(io::call_assign(&parsed, project_root, agents).await)
|
|
}
|
|
"start" => {
|
|
let parsed = parse::parse_start(args).map_err(Error::BadArgs)?;
|
|
Ok(io::call_start(&parsed, project_root, agents).await)
|
|
}
|
|
"delete" => {
|
|
let number = parse::parse_number("delete", args).map_err(Error::BadArgs)?;
|
|
Ok(io::call_delete(&number, project_root, agents).await)
|
|
}
|
|
"rmtree" => {
|
|
let number = parse::parse_number("rmtree", args).map_err(Error::BadArgs)?;
|
|
Ok(io::call_rmtree(&number, project_root, agents).await)
|
|
}
|
|
"rebuild" => Ok(io::call_rebuild(project_root, agents).await),
|
|
"timer" => io::call_timer(args, project_root)
|
|
.await
|
|
.map_err(Error::BadArgs),
|
|
"htop" => Ok(io::call_htop(args, agents)),
|
|
_ => match io::call_sync(cmd, args, project_root, agents) {
|
|
Some(response) => Ok(response),
|
|
None => Err(Error::UnknownCommand(format!(
|
|
"Unknown command: `/{cmd}`. Type `/help` to see available commands."
|
|
))),
|
|
},
|
|
}
|
|
}
|