use std::{convert::TryFrom, error::Error, fmt, string::FromUtf16Error};
use shellwords::MismatchedQuotes;
use crate::types::Message;
#[derive(Clone, Debug)]
pub struct Command {
name: String,
args: Vec<String>,
message: Message,
}
impl Command {
pub fn get_name(&self) -> &str {
&self.name
}
pub fn get_args(&self) -> &[String] {
&self.args
}
pub fn get_message(&self) -> &Message {
&self.message
}
}
#[derive(Debug)]
pub enum CommandError {
NotFound,
Utf16(FromUtf16Error),
MismatchedQuotes(MismatchedQuotes),
}
impl From<FromUtf16Error> for CommandError {
fn from(err: FromUtf16Error) -> Self {
Self::Utf16(err)
}
}
impl From<MismatchedQuotes> for CommandError {
fn from(err: MismatchedQuotes) -> Self {
Self::MismatchedQuotes(err)
}
}
impl Error for CommandError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
match self {
CommandError::NotFound => None,
CommandError::Utf16(err) => Some(err),
CommandError::MismatchedQuotes(_) => None,
}
}
}
impl fmt::Display for CommandError {
fn fmt(&self, out: &mut fmt::Formatter) -> fmt::Result {
write!(
out,
"failed to parse command: {}",
match self {
CommandError::NotFound => String::from("not found"),
CommandError::Utf16(err) => err.to_string(),
CommandError::MismatchedQuotes(_) => String::from("mismatched quotes"),
}
)
}
}
impl TryFrom<Message> for Command {
type Error = CommandError;
fn try_from(message: Message) -> Result<Self, Self::Error> {
match message.get_text().map(|text| (text.get_bot_commands(), text)) {
Some((Some(commands), text)) => {
let command = &commands[0];
let name = command.command.clone();
let offset = text.data.find(&name).unwrap_or(0);
let length = name.len() + command.bot_name.as_ref().map(|x| x.len() + 1).unwrap_or(0);
let pos = offset + length;
let raw_args: Vec<u16> = text.data.encode_utf16().skip(pos).collect();
let raw_args = String::from_utf16(&raw_args)?;
let args = shellwords::split(&raw_args)?;
Ok(Command { name, args, message })
}
_ => Err(CommandError::NotFound),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn create_command(command: &str) -> Command {
let len = command.split_whitespace().next().unwrap().len();
let message: Message = serde_json::from_value(serde_json::json!(
{
"message_id": 1111,
"date": 0,
"from": {"id": 1, "is_bot": false, "first_name": "test"},
"chat": {"id": 1, "type": "private", "first_name": "test"},
"text": command,
"entities": [
{"type": "bot_command", "offset": 0, "length": len}
]
}
))
.unwrap();
Command::try_from(message).unwrap()
}
#[test]
fn command() {
let command = create_command("/test_command 'arg1 v' arg2");
assert_eq!(command.get_name(), "/test_command");
assert_eq!(command.get_args(), &["arg1 v", "arg2"]);
assert_eq!(command.get_message().id, 1111);
}
#[test]
fn command_no_args() {
let command = create_command("/test_command");
assert_eq!(command.get_name(), "/test_command");
assert!(command.get_args().is_empty());
assert_eq!(command.get_message().id, 1111);
}
#[test]
fn command_bot_suffix() {
let command = create_command("/test_command@bot 'arg1 v' arg2");
assert_eq!(command.get_name(), "/test_command");
assert_eq!(command.get_args(), &["arg1 v", "arg2"]);
assert_eq!(command.get_message().id, 1111);
}
#[test]
fn command_bot_suffix_no_args() {
let command = create_command("/test_command@abc");
assert_eq!(command.get_name(), "/test_command");
assert!(command.get_args().is_empty());
assert_eq!(command.get_message().id, 1111);
}
#[test]
fn command_err() {
let message: Message = serde_json::from_value(serde_json::json!(
{
"message_id": 1111,
"date": 0,
"from": {"id": 1, "is_bot": false, "first_name": "test"},
"chat": {"id": 1, "type": "private", "first_name": "test"},
"text": "test"
}
))
.unwrap();
let err = Command::try_from(message).unwrap_err();
assert!(err.source().is_none());
assert_eq!(err.to_string(), "failed to parse command: not found");
let message: Message = serde_json::from_value(serde_json::json!(
{
"message_id": 1111,
"date": 0,
"from": {"id": 1, "is_bot": false, "first_name": "test"},
"chat": {"id": 1, "type": "private", "first_name": "test"},
"text": "/c 'd e f g",
"entities": [
{"type": "bot_command", "offset": 0, "length": 2}
]
}
))
.unwrap();
let err = Command::try_from(message).unwrap_err();
assert!(err.source().is_none());
assert_eq!(err.to_string(), "failed to parse command: mismatched quotes");
}
}