use crate::server::InterfaceError;
use std::ffi::CStr;
use std::fmt::{self, Display, Formatter};
const MAX_NAME_LENGTH: usize = 63;
const ENGINE_CLIENT_COMMANDS: [&[u8]; 12] = [
b"status",
b"pause",
b"setpause",
b"unpause",
b"ping",
b"rpt",
b"rpt_server_enable",
b"rpt_client_enable",
b"rpt_connect",
b"rpt_password",
b"rpt_screenshot",
b"rpt_download_log",
];
#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
pub enum InvalidCommandName {
#[error("the name is empty")]
Empty,
#[error("the name is longer than {MAX_NAME_LENGTH} bytes")]
TooLong,
#[error("byte {index} ({byte:#04x}) is not an ASCII letter, digit, or `_`")]
InvalidByte { index: usize, byte: u8 },
#[error("the engine reserves the name for its own client commands")]
Reserved,
}
pub const fn validate_name(name: &CStr) -> Result<(), InvalidCommandName> {
let bytes = name.to_bytes();
if bytes.is_empty() {
return Err(InvalidCommandName::Empty);
}
if bytes.len() > MAX_NAME_LENGTH {
return Err(InvalidCommandName::TooLong);
}
let mut index = 0;
while index < bytes.len() {
let byte = bytes[index];
if !byte.is_ascii_alphanumeric() && byte != b'_' {
return Err(InvalidCommandName::InvalidByte { index, byte });
}
index += 1;
}
let mut reserved = 0;
while reserved < ENGINE_CLIENT_COMMANDS.len() {
if bytes.eq_ignore_ascii_case(ENGINE_CLIENT_COMMANDS[reserved]) {
return Err(InvalidCommandName::Reserved);
}
reserved += 1;
}
Ok(())
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum CommandBaseKind {
Command,
Variable,
}
impl Display for CommandBaseKind {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.write_str(match self {
Self::Command => "command",
Self::Variable => "variable",
})
}
}
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
#[error("could not register console command `{}`: {kind}", .name.to_string_lossy())]
pub struct RegisterCommandError {
name: &'static CStr,
kind: RegisterCommandErrorKind,
}
impl RegisterCommandError {
pub(crate) const fn new(name: &'static CStr, kind: RegisterCommandErrorKind) -> Self {
Self { name, kind }
}
pub const fn name(&self) -> &'static CStr {
self.name
}
pub const fn kind(&self) -> &RegisterCommandErrorKind {
&self.kind
}
}
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
pub enum RegisterCommandErrorKind {
#[error("the command is already registered")]
AlreadyRegistered,
#[error("the name is already used by a console {0}")]
NameTaken(CommandBaseKind),
#[error("the engine did not list the command")]
NotLinked,
#[error(transparent)]
Interface(#[from] InterfaceError),
}
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
#[error("could not unregister console command `{}`: {kind}", .name.to_string_lossy())]
pub struct UnregisterCommandError {
name: &'static CStr,
kind: UnregisterCommandErrorKind,
}
impl UnregisterCommandError {
pub(crate) const fn new(name: &'static CStr, kind: UnregisterCommandErrorKind) -> Self {
Self { name, kind }
}
pub const fn name(&self) -> &'static CStr {
self.name
}
pub const fn kind(&self) -> &UnregisterCommandErrorKind {
&self.kind
}
}
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
pub enum UnregisterCommandErrorKind {
#[error("the command is not registered")]
NotRegistered,
#[error("the engine still lists the command")]
StillLinked,
#[error(transparent)]
Interface(#[from] InterfaceError),
}