use crate::compat::{Display, FmtResult, Formatter, String, ToString};
use crate::{error::CommandError, validators};
#[derive(Debug, Clone, Copy, Hash)]
pub enum Commands<'a> {
NUMERIC(&'a str),
CAP,
AUTHENTICATE,
PASS,
NICK,
USER,
PING,
PONG,
OPER,
QUIT,
ERROR,
JOIN,
PART,
TOPIC,
NAMES,
LIST,
INVITE,
KICK,
MOTD,
VERSION,
ADMIN,
CONNECT,
LUSERS,
TIME,
STATS,
HELP,
INFO,
MODE,
PRIVMSG,
NOTICE,
WHO,
WHOIS,
WHOWAS,
KILL,
REHASH,
RESTART,
SQUIT,
AWAY,
LINKS,
USERHOST,
WALLOPS,
CUSTOM(&'a str),
}
impl<'a> Commands<'a> {
#[inline]
pub fn as_str(&self) -> &'a str {
match self {
Self::NUMERIC(num) => num,
Self::CAP => "CAP",
Self::AUTHENTICATE => "AUTHENTICATE",
Self::PASS => "PASS",
Self::NICK => "NICK",
Self::USER => "USER",
Self::PING => "PING",
Self::PONG => "PONG",
Self::OPER => "OPER",
Self::QUIT => "QUIT",
Self::ERROR => "ERROR",
Self::JOIN => "JOIN",
Self::PART => "PART",
Self::TOPIC => "TOPIC",
Self::NAMES => "NAMES",
Self::LIST => "LIST",
Self::INVITE => "INVITE",
Self::KICK => "KICK",
Self::MOTD => "MOTD",
Self::VERSION => "VERSION",
Self::ADMIN => "ADMIN",
Self::CONNECT => "CONNECT",
Self::LUSERS => "LUSERS",
Self::TIME => "TIME",
Self::STATS => "STATS",
Self::HELP => "HELP",
Self::INFO => "INFO",
Self::MODE => "MODE",
Self::PRIVMSG => "PRIVMSG",
Self::NOTICE => "NOTICE",
Self::WHO => "WHO",
Self::WHOIS => "WHOIS",
Self::WHOWAS => "WHOWAS",
Self::KILL => "KILL",
Self::REHASH => "REHASH",
Self::RESTART => "RESTART",
Self::SQUIT => "SQUIT",
Self::AWAY => "AWAY",
Self::LINKS => "LINKS",
Self::USERHOST => "USERHOST",
Self::WALLOPS => "WALLOPS",
Self::CUSTOM(custom) => custom,
}
}
#[allow(clippy::len_without_is_empty)]
#[inline]
pub fn len(&self) -> usize {
match self {
Self::NUMERIC(num) => num.len(),
Self::CAP => 3,
Self::AUTHENTICATE => 12,
Self::PASS => 4,
Self::NICK => 4,
Self::USER => 4,
Self::PING => 4,
Self::PONG => 4,
Self::OPER => 4,
Self::QUIT => 4,
Self::ERROR => 5,
Self::JOIN => 4,
Self::PART => 4,
Self::TOPIC => 5,
Self::NAMES => 5,
Self::LIST => 4,
Self::INVITE => 6,
Self::KICK => 4,
Self::MOTD => 4,
Self::VERSION => 7,
Self::ADMIN => 5,
Self::CONNECT => 7,
Self::LUSERS => 6,
Self::TIME => 4,
Self::STATS => 5,
Self::HELP => 4,
Self::INFO => 4,
Self::MODE => 4,
Self::PRIVMSG => 7,
Self::NOTICE => 6,
Self::WHO => 3,
Self::WHOIS => 6,
Self::WHOWAS => 6,
Self::KILL => 4,
Self::REHASH => 6,
Self::RESTART => 7,
Self::SQUIT => 5,
Self::AWAY => 4,
Self::LINKS => 5,
Self::USERHOST => 8,
Self::WALLOPS => 7,
Self::CUSTOM(unknown) => unknown.len(),
}
}
#[inline]
pub fn as_bytes(&self) -> &'a [u8] {
match self {
Self::NUMERIC(num) => num.as_bytes(),
Self::CAP => b"CAP",
Self::AUTHENTICATE => b"AUTHENTICATE",
Self::PASS => b"PASS",
Self::NICK => b"NICK",
Self::USER => b"USER",
Self::PING => b"PING",
Self::PONG => b"PONG",
Self::OPER => b"OPER",
Self::QUIT => b"QUIT",
Self::ERROR => b"ERROR",
Self::JOIN => b"JOIN",
Self::PART => b"PART",
Self::TOPIC => b"TOPIC",
Self::NAMES => b"NAMES",
Self::LIST => b"LIST",
Self::INVITE => b"INVITE",
Self::KICK => b"KICK",
Self::MOTD => b"MOTD",
Self::VERSION => b"VERSION",
Self::ADMIN => b"ADMIN",
Self::CONNECT => b"CONNECT",
Self::LUSERS => b"LUSERS",
Self::TIME => b"TIME",
Self::STATS => b"STATS",
Self::HELP => b"HELP",
Self::INFO => b"INFO",
Self::MODE => b"MODE",
Self::PRIVMSG => b"PRIVMSG",
Self::NOTICE => b"NOTICE",
Self::WHO => b"WHO",
Self::WHOIS => b"WHOIS",
Self::WHOWAS => b"WHOWAS",
Self::KILL => b"KILL",
Self::REHASH => b"REHASH",
Self::RESTART => b"RESTART",
Self::SQUIT => b"SQUIT",
Self::AWAY => b"AWAY",
Self::LINKS => b"LINKS",
Self::USERHOST => b"USERHOST",
Self::WALLOPS => b"WALLOPS",
Self::CUSTOM(unknown) => unknown.as_bytes(),
}
}
#[inline]
pub fn is_ping(&self) -> bool {
*self == Self::PING
}
#[inline]
pub fn is_pong(&self) -> bool {
*self == Self::PONG
}
#[inline]
pub fn is_privmsg(&self) -> bool {
*self == Self::PRIVMSG
}
#[inline]
pub fn is_notice(&self) -> bool {
*self == Self::NOTICE
}
pub fn validate(&self) -> Result<(), CommandError> {
validators::command(self.as_str())
}
}
macro_rules! parse_command {
($value:expr, $($pattern:literal => $command:expr),* $(,)?) => {
$(
if $value.eq_ignore_ascii_case($pattern) {
return $command;
}
)*
};
}
impl<'a> From<&'a str> for Commands<'a> {
fn from(value: &'a str) -> Self {
parse_command!(value,
"CAP" => Self::CAP,
"AUTHENTICATE" => Self::AUTHENTICATE,
"PASS" => Self::PASS,
"NICK" => Self::NICK,
"USER" => Self::USER,
"PING" => Self::PING,
"PONG" => Self::PONG,
"OPER" => Self::OPER,
"QUIT" => Self::QUIT,
"ERROR" => Self::ERROR,
"JOIN" => Self::JOIN,
"PART" => Self::PART,
"TOPIC" => Self::TOPIC,
"NAMES" => Self::NAMES,
"LIST" => Self::LIST,
"INVITE" => Self::INVITE,
"KICK" => Self::KICK,
"MOTD" => Self::MOTD,
"VERSION" => Self::VERSION,
"ADMIN" => Self::ADMIN,
"CONNECT" => Self::CONNECT,
"LUSERS" => Self::LUSERS,
"TIME" => Self::TIME,
"STATS" => Self::STATS,
"HELP" => Self::HELP,
"INFO" => Self::INFO,
"MODE" => Self::MODE,
"PRIVMSG" => Self::PRIVMSG,
"NOTICE" => Self::NOTICE,
"WHO" => Self::WHO,
"WHOIS" => Self::WHOIS,
"WHOWAS" => Self::WHOWAS,
"KILL" => Self::KILL,
"REHASH" => Self::REHASH,
"RESTART" => Self::RESTART,
"SQUIT" => Self::SQUIT,
"AWAY" => Self::AWAY,
"LINKS" => Self::LINKS,
"USERHOST" => Self::USERHOST,
"WALLOPS" => Self::WALLOPS,
);
if value.parse::<u16>().is_ok() {
return Self::NUMERIC(value);
}
Self::CUSTOM(value)
}
}
impl Display for Commands<'_> {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
f.write_str(self.as_str())
}
}
impl AsRef<str> for Commands<'_> {
fn as_ref(&self) -> &str {
self.as_str()
}
}
impl PartialEq for Commands<'_> {
fn eq(&self, other: &Self) -> bool {
self.as_str().eq_ignore_ascii_case(other.as_str())
}
}
impl PartialEq<&str> for Commands<'_> {
fn eq(&self, other: &&str) -> bool {
self.as_str().eq_ignore_ascii_case(other)
}
}
impl PartialEq<String> for Commands<'_> {
fn eq(&self, other: &String) -> bool {
self.as_str().eq_ignore_ascii_case(other.as_str())
}
}
impl PartialEq<Commands<'_>> for &str {
fn eq(&self, other: &Commands<'_>) -> bool {
other.as_str().eq_ignore_ascii_case(self)
}
}
impl PartialEq<Commands<'_>> for String {
fn eq(&self, other: &Commands<'_>) -> bool {
other.as_str().eq_ignore_ascii_case(self.as_str())
}
}
impl Eq for Commands<'_> {}
impl<'a> crate::de::FromMessage<'a> for Commands<'a> {
fn from_message(msg: &crate::Message<'a>) -> Result<Self, crate::DeError> {
Ok(msg.command())
}
}
impl<'a> crate::ser::ToMessage for Commands<'a> {
fn to_message<S: crate::ser::MessageSerializer>(
&self,
serialize: &mut S,
) -> Result<(), crate::SerError> {
serialize.set_command(*self);
Ok(())
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum CapSubCommands {
LS,
LIST,
REQ,
ACK,
NAK,
END,
NEW, DEL, UNKNOWN(String),
}
impl CapSubCommands {
pub fn as_str(&self) -> &str {
match self {
Self::LS => "LS",
Self::LIST => "LIST",
Self::REQ => "REQ",
Self::ACK => "ACK",
Self::NAK => "NAK",
Self::END => "END",
Self::NEW => "NEW",
Self::DEL => "DEL",
Self::UNKNOWN(s) => s,
}
}
pub fn get_description(&self) -> &'static str {
match self {
Self::LS => "List available capabilities",
Self::LIST => "List currently enabled capabilities",
Self::REQ => "Request capabilities",
Self::ACK => "Acknowledge capabilities",
Self::NAK => "Reject capability request",
Self::END => "End capability negotiation",
Self::NEW => "Advertise new capabilities (CAP 302)",
Self::DEL => "Remove previously available capabilities (CAP 302)",
Self::UNKNOWN(_) => "Unknown CAP subcommand",
}
}
}
impl From<&str> for CapSubCommands {
fn from(value: &str) -> Self {
match value {
"LS" => Self::LS,
"LIST" => Self::LIST,
"REQ" => Self::REQ,
"ACK" => Self::ACK,
"NAK" => Self::NAK,
"END" => Self::END,
"NEW" => Self::NEW,
"DEL" => Self::DEL,
_ => Self::UNKNOWN(value.to_string()),
}
}
}
impl Display for CapSubCommands {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
f.write_str(self.as_str())
}
}
#[cfg(feature = "serde")]
impl serde::Serialize for Commands<'_> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(self.as_str())
}
}
#[cfg(feature = "serde")]
impl serde::Serialize for CapSubCommands {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(self.as_str())
}
}
#[cfg(test)]
mod tests {
use crate::{components::Commands, de::FromMessage, ser::ToMessage};
#[test]
fn from_case_insensitive() {
assert_eq!(Commands::from("PRIVMSG"), Commands::PRIVMSG);
assert_eq!(Commands::from("PING"), Commands::PING);
assert_eq!(Commands::from("privmsg"), Commands::PRIVMSG);
assert_eq!(Commands::from("ping"), Commands::PING);
assert_eq!(Commands::from("PrivMsg"), Commands::PRIVMSG);
assert_eq!(Commands::from("PiNg"), Commands::PING);
}
#[test]
fn partialeq_case_insensitive() {
let cmd = Commands::PRIVMSG;
assert!(cmd == "PRIVMSG");
assert!(cmd == "privmsg");
assert!(cmd == "PrivMsg");
assert!(cmd == "PRIVMSG");
assert!(cmd != "PING");
assert!(cmd != "ping");
}
#[test]
fn string_comparison() {
let cmd = Commands::PRIVMSG;
let s = String::from("privmsg");
assert!(cmd == s);
assert!(s == cmd);
}
#[test]
fn from_message() {
let input = "PRIVMSG #channel :hi";
let cmd = Commands::from_str(input).unwrap();
assert_eq!(Commands::PRIVMSG, cmd);
}
#[test]
fn to_message() {
struct Cmd<'a> {
cmd: Commands<'a>,
}
impl ToMessage for Cmd<'_> {
fn to_message<S: crate::ser::MessageSerializer>(
&self,
serialize: &mut S,
) -> Result<(), crate::SerError> {
self.cmd.to_message(serialize)
}
}
let msg = Cmd {
cmd: Commands::NOTICE,
};
let actual = crate::to_message(&msg).unwrap();
assert_eq!("NOTICE", actual);
}
}