#[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" => if params_amount < 1 {return Err(CommandError::MinimumArgsRequired(1, cmd));},
b"001" | b"002" | b"003" | b"005" | b"221" | b"251" | b"255" | b"256" | b"257" | b"258" | b"259" | b"265" | b"266" | b"305" | b"306" | b"321" | b"323" | b"336" | b"337" | b"371" | b"372" | b"374" | b"375" | b"376" | b"381" | b"406" | b"409" | b"417" | b"422" | b"451" | b"462" | b"464" | b"465" | b"476" | b"481" | b"483" | b"491" | b"501" | b"502" | b"670" | b"691" | b"902" | b"903" | b"904" | b"905" | b"906" | b"907" => if params_amount < 2 {return Err(CommandError::MinimumArgsRequired(2, cmd));},
b"252" | b"253" | b"254" | 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"338" | b"341" | 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"421" | b"432" | b"433" | b"442" | 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"723" | b"901" | b"908" => if params_amount < 3 {return Err(CommandError::MinimumArgsRequired(3, cmd));},
b"010" | b"312" | b"322" | b"330" | b"351" | b"353" | b"364" | b"441" | b"443" | b"900" => if params_amount < 4 {return Err(CommandError::MinimumArgsRequired(4, cmd));},
b"004" | b"317" | 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"352" => 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"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"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"USER00000000" => if params_amount < 4 {return Err(CommandError::MinimumArgsRequired(4, cmd));}
else {return Ok(Self::Named("USER"));},
_ => 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"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"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"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"USER", 4).is_ok());
assert!(Command::parse(b"USER", 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']));
}
}