#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum Command<'msg> {
Named(&'msg str),
Numeric(&'msg str),
}
impl<'msg> Command<'msg> {
#[allow(clippy::too_many_lines)]
pub const fn parse(input: &'msg [u8], params_amount: usize) -> Result<Self, CommandError> {
if input.is_empty() {return Err(CommandError::EmptyInput);}
let mut number_count = 0;
let mut index = 0;
while index < input.len() {
if is_invalid_char(input[index]) {return Err(CommandError::InvalidByte(input[index]));}
if input[index].is_ascii_digit() {number_count += 1;}
index += 1;
}
if let Ok(cmd) = core::str::from_utf8(input) {
if cmd.len() == 3 && number_count == 3 {
let mut unhandled = false;
match input {
b"042" | b"250" | b"302" | b"303" | b"383" | b"603" | b"606" | b"607" | b"608" => if params_amount < 1 {return Err(CommandError::MinimumArgsRequired(1, cmd));},
b"001" | b"002" | b"003" | b"005" | b"105" | b"203" | b"221" | b"251" | b"255" | b"256" | b"257" | b"258" | b"259" | b"265" | b"266" | b"271" | b"272" | b"281" | b"282" | b"305" | b"306" | b"321" | b"323" | b"336" | b"337" | b"340" | b"354" | b"371" | b"372" | b"374" | b"375" | b"376" | b"381" | b"392" | b"393" | b"394" | b"395" | b"406" | b"409" | b"410" | b"411" | b"417" | b"422" | b"424" | b"445" | b"446" | b"451" | b"456" | b"462" | b"464" | b"465" | b"476" | b"481" | b"483" | b"491" | b"501" | b"502" | b"511" | b"670" | b"691" | b"716" | b"717" | b"730" | b"731" | b"732" | b"733" | b"759" | b"902" | b"903" | b"904" | b"905" | b"906" | b"907" => if params_amount < 2 {return Err(CommandError::MinimumArgsRequired(2, cmd));},
b"200" | b"201" | b"202" | b"204" | b"205" | b"208" | b"209" | b"252" | b"253" | b"254" | b"261" | b"263" | b"276" | b"301" | b"307" | b"313" | b"315" | b"318" | b"319" | b"320" | b"324" | b"329" | b"331" | b"332" | b"333" | b"335" | b"338" | b"341" | b"342" | b"346" | b"347" | b"348" | b"349" | b"365" | b"366" | b"367" | b"368" | b"369" | b"378" | b"379" | b"382" | b"391" | b"396" | b"400" | b"401" | b"402" | b"403" | b"404" | b"405" | b"408" | b"421" | b"432" | b"433" | b"442" | b"444" | b"457" | b"458" | b"461" | b"471" | b"472" | b"473" | b"474" | b"475" | b"482" | b"524" | b"525" | b"671" | b"704" | b"705" | b"706" | b"710" | b"711" | b"712" | b"713" | b"714" | b"718" | b"723" | b"901" | b"908" | b"950" | b"951" | b"952" => if params_amount < 3 {return Err(CommandError::MinimumArgsRequired(3, cmd));},
b"010" | b"235" | b"262" | b"312" | b"322" | b"330" | b"351" | b"353" | b"364" | b"441" | b"443" | b"734" | b"900" => if params_amount < 4 {return Err(CommandError::MinimumArgsRequired(4, cmd));},
b"004" | b"206" | b"207" | b"317" | b"598" | b"599" | b"600" | b"601" | b"602" | b"604" | b"605" | b"609" | b"696" => if params_amount < 5 {return Err(CommandError::MinimumArgsRequired(5, cmd));},
b"311" | b"314" => if params_amount < 6 {return Err(CommandError::MinimumArgsRequired(6, cmd));},
b"234" | b"709" => if params_amount < 7 {return Err(CommandError::MinimumArgsRequired(7, cmd));},
b"352" | b"708" => if params_amount < 8 {return Err(CommandError::MinimumArgsRequired(8, cmd));},
_ => unhandled = true,
}
if unhandled {return Err(CommandError::UnhandledNumeric(cmd));}
return Ok(Self::Numeric(cmd));
} else if number_count > 0 {return Err(CommandError::NumberInNamedCommand(cmd));}
match &command_to_uppercase_bytes(input) {
b"INFO00000000" => return Ok(Self::Named("INFO")),
b"LUSERS000000" => return Ok(Self::Named("LUSERS")),
b"REHASH000000" => return Ok(Self::Named("REHASH")),
b"RESTART00000" => return Ok(Self::Named("RESTART")),
b"LINKS0000000" => return Ok(Self::Named("LINKS")),
b"QUIT00000000" => return Ok(Self::Named("QUIT")),
b"MOTD00000000" => return Ok(Self::Named("MOTD")),
b"VERSION00000" => return Ok(Self::Named("VERSION")),
b"ADMIN0000000" => return Ok(Self::Named("ADMIN")),
b"TIME00000000" => return Ok(Self::Named("TIME")),
b"HELP00000000" => return Ok(Self::Named("HELP")),
b"AWAY00000000" => return Ok(Self::Named("AWAY")),
b"LIST00000000" => return Ok(Self::Named("LIST")),
b"ACK000000000" => return Ok(Self::Named("ACK")),
b"ACCEPT000000" => return Ok(Self::Named("ACCEPT")),
b"SILENCE00000" => return Ok(Self::Named("SILENCE")),
b"DIE000000000" => return Ok(Self::Named("DIE")),
b"TRACE0000000" => return Ok(Self::Named("TRACE")),
b"ETRACE000000" => return Ok(Self::Named("ETRACE")),
b"SERVLIST0000" => return Ok(Self::Named("SERVLIST")),
b"USERS0000000" => return Ok(Self::Named("USERS")),
b"PASS00000000" => if params_amount < 1 {return Err(CommandError::MinimumArgsRequired(1, cmd));}
else {return Ok(Self::Named("PASS"));},
b"NICK00000000" => if params_amount < 1 {return Err(CommandError::MinimumArgsRequired(1, cmd));}
else {return Ok(Self::Named("NICK"));},
b"PING00000000" => if params_amount < 1 {return Err(CommandError::MinimumArgsRequired(1, cmd));}
else {return Ok(Self::Named("PING"));},
b"ERROR0000000" => if params_amount < 1 {return Err(CommandError::MinimumArgsRequired(1, cmd));}
else {return Ok(Self::Named("ERROR"));},
b"NAMES0000000" => if params_amount < 1 {return Err(CommandError::MinimumArgsRequired(1, cmd));}
else {return Ok(Self::Named("NAMES"));},
b"WHO000000000" => if params_amount < 1 {return Err(CommandError::MinimumArgsRequired(1, cmd));}
else {return Ok(Self::Named("WHO"));},
b"WALLOPS00000" => if params_amount < 1 {return Err(CommandError::MinimumArgsRequired(1, cmd));}
else {return Ok(Self::Named("WALLOPS"));},
b"AUTHENTICATE" => if params_amount < 1 {return Err(CommandError::MinimumArgsRequired(1, cmd));}
else {return Ok(Self::Named("AUTHENTICATE"));},
b"ACCOUNT00000" => if params_amount < 1 {return Err(CommandError::MinimumArgsRequired(1, cmd));}
else {return Ok(Self::Named("ACCOUNT"));},
b"CAP000000000" => if params_amount < 1 {return Err(CommandError::MinimumArgsRequired(1, cmd));}
else {return Ok(Self::Named("CAP"));},
b"MODE00000000" => if params_amount < 1 {return Err(CommandError::MinimumArgsRequired(1, cmd));}
else {return Ok(Self::Named("MODE"));},
b"PONG00000000" => if params_amount < 1 {return Err(CommandError::MinimumArgsRequired(1, cmd));}
else {return Ok(Self::Named("PONG"));},
b"JOIN00000000" => if params_amount < 1 {return Err(CommandError::MinimumArgsRequired(1, cmd));}
else {return Ok(Self::Named("JOIN"));},
b"PART00000000" => if params_amount < 1 {return Err(CommandError::MinimumArgsRequired(1, cmd));}
else {return Ok(Self::Named("PART"));},
b"TOPIC0000000" => if params_amount < 1 {return Err(CommandError::MinimumArgsRequired(1, cmd));}
else {return Ok(Self::Named("TOPIC"));},
b"STATS0000000" => if params_amount < 1 {return Err(CommandError::MinimumArgsRequired(1, cmd));}
else {return Ok(Self::Named("STATS"));},
b"WHOIS0000000" => if params_amount < 1 {return Err(CommandError::MinimumArgsRequired(1, cmd));}
else {return Ok(Self::Named("WHOIS"));},
b"WHOWAS000000" => if params_amount < 1 {return Err(CommandError::MinimumArgsRequired(1, cmd));}
else {return Ok(Self::Named("WHOWAS"));},
b"CONNECT00000" => if params_amount < 1 {return Err(CommandError::MinimumArgsRequired(1, cmd));}
else {return Ok(Self::Named("CONNECT"));},
b"USERHOST0000" => if params_amount < 1 {return Err(CommandError::MinimumArgsRequired(1, cmd));}
else {return Ok(Self::Named("USERHOST"));},
b"TAGMSG000000" => if params_amount < 1 {return Err(CommandError::MinimumArgsRequired(1, cmd));}
else {return Ok(Self::Named("TAGMSG"));},
b"BATCH0000000" => if params_amount < 1 {return Err(CommandError::MinimumArgsRequired(1, cmd));}
else {return Ok(Self::Named("BATCH"));},
b"SETNAME00000" => if params_amount < 1 {return Err(CommandError::MinimumArgsRequired(1, cmd));}
else {return Ok(Self::Named("SETNAME"));},
b"MONITOR00000" => if params_amount < 1 {return Err(CommandError::MinimumArgsRequired(1, cmd));}
else {return Ok(Self::Named("MONITOR"));},
b"ISON00000000" => if params_amount < 1 {return Err(CommandError::MinimumArgsRequired(1, cmd));}
else {return Ok(Self::Named("ISON"));},
b"KNOCK0000000" => if params_amount < 1 {return Err(CommandError::MinimumArgsRequired(1, cmd));}
else {return Ok(Self::Named("KNOCK"));},
b"SUMMON000000" => if params_amount < 1 {return Err(CommandError::MinimumArgsRequired(1, cmd));}
else {return Ok(Self::Named("SUMMON"));},
b"USERIP000000" => if params_amount < 1 {return Err(CommandError::MinimumArgsRequired(1, cmd));}
else {return Ok(Self::Named("USERIP"));},
b"WATCH0000000" => if params_amount < 1 {return Err(CommandError::MinimumArgsRequired(1, cmd));}
else {return Ok(Self::Named("WATCH"));},
b"OPER00000000" => if params_amount < 2 {return Err(CommandError::MinimumArgsRequired(2, cmd));}
else {return Ok(Self::Named("OPER"));},
b"INVITE000000" => if params_amount < 2 {return Err(CommandError::MinimumArgsRequired(2, cmd));}
else {return Ok(Self::Named("INVITE"));},
b"PRIVMSG00000" => if params_amount < 2 {return Err(CommandError::MinimumArgsRequired(2, cmd));}
else {return Ok(Self::Named("PRIVMSG"));},
b"NOTICE000000" => if params_amount < 2 {return Err(CommandError::MinimumArgsRequired(2, cmd));}
else {return Ok(Self::Named("NOTICE"));},
b"KILL00000000" => if params_amount < 2 {return Err(CommandError::MinimumArgsRequired(2, cmd));}
else {return Ok(Self::Named("KILL"));},
b"SQUIT0000000" => if params_amount < 2 {return Err(CommandError::MinimumArgsRequired(2, cmd));}
else {return Ok(Self::Named("SQUIT"));},
b"KICK00000000" => if params_amount < 2 {return Err(CommandError::MinimumArgsRequired(2, cmd));}
else {return Ok(Self::Named("KICK"));},
b"CHGHOST00000" => if params_amount < 2 {return Err(CommandError::MinimumArgsRequired(2, cmd));}
else {return Ok(Self::Named("CHGHOST"));},
b"ENCAP0000000" => if params_amount < 2 {return Err(CommandError::MinimumArgsRequired(2, cmd));}
else {return Ok(Self::Named("ENCAP"));},
b"SQUERY000000" => if params_amount < 2 {return Err(CommandError::MinimumArgsRequired(2, cmd));}
else {return Ok(Self::Named("SQUERY"));},
b"FAIL00000000" => if params_amount < 3 {return Err(CommandError::MinimumArgsRequired(3, cmd));}
else {return Ok(Self::Named("FAIL"));},
b"WARN00000000" => if params_amount < 3 {return Err(CommandError::MinimumArgsRequired(3, cmd));}
else {return Ok(Self::Named("WARN"));},
b"NOTE00000000" => if params_amount < 3 {return Err(CommandError::MinimumArgsRequired(3, cmd));}
else {return Ok(Self::Named("NOTE"));},
b"CPRIVMSG0000" => if params_amount < 3 {return Err(CommandError::MinimumArgsRequired(3, cmd));}
else {return Ok(Self::Named("CPRIVMSG"));},
b"CNOTICE00000" => if params_amount < 3 {return Err(CommandError::MinimumArgsRequired(3, cmd));}
else {return Ok(Self::Named("CNOTICE"));},
b"SERVER000000" => if params_amount < 3 {return Err(CommandError::MinimumArgsRequired(3, cmd));}
else {return Ok(Self::Named("SERVER"));},
b"USER00000000" => if params_amount < 4 {return Err(CommandError::MinimumArgsRequired(4, cmd));}
else {return Ok(Self::Named("USER"));},
b"WEBIRC000000" => if params_amount < 4 {return Err(CommandError::MinimumArgsRequired(4, cmd));}
else {return Ok(Self::Named("WEBIRC"));},
b"SERVICE00000" => if params_amount < 6 {return Err(CommandError::MinimumArgsRequired(6, cmd));}
else {return Ok(Self::Named("SERVICE"));},
_ => return Err(CommandError::UnhandledNamed(cmd)),
}
}
unreachable!();
}
}
impl<'msg> core::fmt::Display for Command<'msg> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {Self::Named(inner) | Self::Numeric(inner) => write!(f, "{inner}")}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum CommandError<'msg> {
EmptyInput,
InvalidByte(u8),
NumberInNamedCommand(&'msg str),
MinimumArgsRequired(u8, &'msg str),
UnhandledNumeric(&'msg str),
UnhandledNamed(&'msg str),
}
const fn is_invalid_char(input: u8) -> bool {
!input.is_ascii_alphanumeric()
}
const fn command_to_uppercase_bytes(input: &[u8]) -> [u8; 12] {
let mut output = [b'0'; 12];
let mut index = 0;
while index < input.len() {
if input[index].is_ascii_lowercase() {output[index] = input[index].to_ascii_uppercase();}
else {output[index] = input[index];}
index += 1;
}
output
}
#[cfg(test)]
mod const_tests {
use crate::const_tests::is_identical;
use super::{Command, command_to_uppercase_bytes};
#[test]
const fn parsing_command() {
assert!(Command::parse(b"302", 1).is_ok());
assert!(Command::parse(b"907", 2).is_ok());
assert!(Command::parse(b"908", 3).is_ok());
assert!(Command::parse(b"900", 4).is_ok());
assert!(Command::parse(b"696", 5).is_ok());
assert!(Command::parse(b"314", 6).is_ok());
assert!(Command::parse(b"709", 7).is_ok());
assert!(Command::parse(b"352", 8).is_ok());
assert!(Command::parse(b"3027", 1).is_err());
assert!(Command::parse(b"999", 1).is_err());
assert!(Command::parse(b"info", 0).is_ok());
assert!(Command::parse(b"LuSeRS", 0).is_ok());
assert!(Command::parse(b"REHASH", 0).is_ok());
assert!(Command::parse(b"RESTART", 0).is_ok());
assert!(Command::parse(b"LINKs", 0).is_ok());
assert!(Command::parse(b"QUIT", 0).is_ok());
assert!(Command::parse(b"MOTD", 0).is_ok());
assert!(Command::parse(b"VERSION", 0).is_ok());
assert!(Command::parse(b"ADMIN", 0).is_ok());
assert!(Command::parse(b"TIME", 0).is_ok());
assert!(Command::parse(b"HELP", 0).is_ok());
assert!(Command::parse(b"AWAY", 0).is_ok());
assert!(Command::parse(b"LIST", 0).is_ok());
assert!(Command::parse(b"ACK", 0).is_ok());
assert!(Command::parse(b"ACCEPT", 0).is_ok());
assert!(Command::parse(b"SILENCE", 0).is_ok());
assert!(Command::parse(b"DIE", 0).is_ok());
assert!(Command::parse(b"TRACE", 0).is_ok());
assert!(Command::parse(b"ETRACE", 0).is_ok());
assert!(Command::parse(b"SERVLIST", 0).is_ok());
assert!(Command::parse(b"USERS", 0).is_ok());
assert!(Command::parse(b"PASS", 1).is_ok());
assert!(Command::parse(b"PASS", 0).is_err());
assert!(Command::parse(b"NICK", 1).is_ok());
assert!(Command::parse(b"NICK", 0).is_err());
assert!(Command::parse(b"PING", 1).is_ok());
assert!(Command::parse(b"PING", 0).is_err());
assert!(Command::parse(b"ERROR", 1).is_ok());
assert!(Command::parse(b"ERROR", 0).is_err());
assert!(Command::parse(b"NAMES", 1).is_ok());
assert!(Command::parse(b"NAMES", 0).is_err());
assert!(Command::parse(b"WHO", 1).is_ok());
assert!(Command::parse(b"WHO", 0).is_err());
assert!(Command::parse(b"WALLOPS", 1).is_ok());
assert!(Command::parse(b"WALLOPS", 0).is_err());
assert!(Command::parse(b"AUTHENTICATE", 1).is_ok());
assert!(Command::parse(b"AUTHENTICATE", 0).is_err());
assert!(Command::parse(b"ACCOUNT", 1).is_ok());
assert!(Command::parse(b"ACCOUNT", 0).is_err());
assert!(Command::parse(b"CAP", 1).is_ok());
assert!(Command::parse(b"CAP", 0).is_err());
assert!(Command::parse(b"MODE", 1).is_ok());
assert!(Command::parse(b"MODE", 0).is_err());
assert!(Command::parse(b"PONG", 1).is_ok());
assert!(Command::parse(b"PONG", 0).is_err());
assert!(Command::parse(b"JOIN", 1).is_ok());
assert!(Command::parse(b"JOIN", 0).is_err());
assert!(Command::parse(b"PART", 1).is_ok());
assert!(Command::parse(b"PART", 0).is_err());
assert!(Command::parse(b"TOPIC", 1).is_ok());
assert!(Command::parse(b"TOPIC", 0).is_err());
assert!(Command::parse(b"STATS", 1).is_ok());
assert!(Command::parse(b"STATS", 0).is_err());
assert!(Command::parse(b"WHOIS", 1).is_ok());
assert!(Command::parse(b"WHOIS", 0).is_err());
assert!(Command::parse(b"WHOWAS", 1).is_ok());
assert!(Command::parse(b"WHOWAS", 0).is_err());
assert!(Command::parse(b"CONNECT", 1).is_ok());
assert!(Command::parse(b"CONNECT", 0).is_err());
assert!(Command::parse(b"USERHOST", 1).is_ok());
assert!(Command::parse(b"USERHOST", 0).is_err());
assert!(Command::parse(b"TAGMSG", 1).is_ok());
assert!(Command::parse(b"TAGMSG", 0).is_err());
assert!(Command::parse(b"BATCH", 1).is_ok());
assert!(Command::parse(b"BATCH", 0).is_err());
assert!(Command::parse(b"SETNAME", 1).is_ok());
assert!(Command::parse(b"SETNAME", 0).is_err());
assert!(Command::parse(b"MONITOR", 1).is_ok());
assert!(Command::parse(b"MONITOR", 0).is_err());
assert!(Command::parse(b"ISON", 1).is_ok());
assert!(Command::parse(b"ISON", 0).is_err());
assert!(Command::parse(b"KNOCK", 1).is_ok());
assert!(Command::parse(b"KNOCK", 0).is_err());
assert!(Command::parse(b"SUMMON", 1).is_ok());
assert!(Command::parse(b"SUMMON", 0).is_err());
assert!(Command::parse(b"USERIP", 1).is_ok());
assert!(Command::parse(b"USERIP", 0).is_err());
assert!(Command::parse(b"WATCH", 1).is_ok());
assert!(Command::parse(b"WATCH", 0).is_err());
assert!(Command::parse(b"OPER", 2).is_ok());
assert!(Command::parse(b"OPER", 0).is_err());
assert!(Command::parse(b"INVITE", 2).is_ok());
assert!(Command::parse(b"INVITE", 0).is_err());
assert!(Command::parse(b"PRIVMSG", 2).is_ok());
assert!(Command::parse(b"PRIVMSG", 0).is_err());
assert!(Command::parse(b"NOTICE", 2).is_ok());
assert!(Command::parse(b"NOTICE", 0).is_err());
assert!(Command::parse(b"KILL", 2).is_ok());
assert!(Command::parse(b"KILL", 0).is_err());
assert!(Command::parse(b"SQUIT", 2).is_ok());
assert!(Command::parse(b"SQUIT", 0).is_err());
assert!(Command::parse(b"KICK", 2).is_ok());
assert!(Command::parse(b"KICK", 0).is_err());
assert!(Command::parse(b"CHGHOST", 2).is_ok());
assert!(Command::parse(b"CHGHOST", 0).is_err());
assert!(Command::parse(b"ENCAP", 2).is_ok());
assert!(Command::parse(b"ENCAP", 0).is_err());
assert!(Command::parse(b"SQUERY", 2).is_ok());
assert!(Command::parse(b"SQUERY", 0).is_err());
assert!(Command::parse(b"FAIL", 3).is_ok());
assert!(Command::parse(b"FAIL", 0).is_err());
assert!(Command::parse(b"WARN", 3).is_ok());
assert!(Command::parse(b"WARN", 0).is_err());
assert!(Command::parse(b"NOTE", 3).is_ok());
assert!(Command::parse(b"NOTE", 0).is_err());
assert!(Command::parse(b"CPRIVMSG", 3).is_ok());
assert!(Command::parse(b"CPRIVMSG", 0).is_err());
assert!(Command::parse(b"CNOTICE", 3).is_ok());
assert!(Command::parse(b"CNOTICE", 0).is_err());
assert!(Command::parse(b"SERVER", 3).is_ok());
assert!(Command::parse(b"SERVER", 0).is_err());
assert!(Command::parse(b"USER", 4).is_ok());
assert!(Command::parse(b"USER", 0).is_err());
assert!(Command::parse(b"WEBIRC", 4).is_ok());
assert!(Command::parse(b"WEBIRC", 0).is_err());
assert!(Command::parse(b"SERVICE", 6).is_ok());
assert!(Command::parse(b"SERVICE", 0).is_err());
assert!(Command::parse(b"EXCELLENT", 0).is_err());
}
#[test]
const fn uppercasing() {
let input = b"INFO";
let output = command_to_uppercase_bytes(input);
assert!(output.len() == 12);
assert!(is_identical(&output, &[b'I', b'N', b'F', b'O', b'0', b'0', b'0', b'0', b'0', b'0', b'0', b'0']));
}
}