use crate::server::InterfaceError;
use sdk_raw::commands::ENGINE_CLIENT_COMMANDS;
use std::ffi::CStr;
use std::fmt::{self, Display, Formatter};
const MAX_NAME_LENGTH: usize = 63;
#[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, 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,
}
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
#[error("could not register console {base} `{}`: {kind}", .name.to_string_lossy())]
pub struct RegisterCommandError {
name: &'static CStr,
base: CommandBaseKind,
kind: RegisterCommandErrorKind,
}
impl RegisterCommandError {
pub(crate) const fn new(
name: &'static CStr,
base: CommandBaseKind,
kind: RegisterCommandErrorKind,
) -> Self {
Self { name, base, kind }
}
pub const fn base(&self) -> CommandBaseKind {
self.base
}
pub const fn kind(&self) -> &RegisterCommandErrorKind {
&self.kind
}
pub const fn name(&self) -> &'static CStr {
self.name
}
}
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
pub enum RegisterCommandErrorKind {
#[error("it is already registered")]
AlreadyRegistered,
#[error("the name is already used by a console {0}")]
NameTaken(CommandBaseKind),
#[error("the engine did not list it")]
NotLinked,
#[error(transparent)]
Interface(#[from] InterfaceError),
}
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
#[error("could not unregister console {base} `{}`: {kind}", .name.to_string_lossy())]
pub struct UnregisterCommandError {
name: &'static CStr,
base: CommandBaseKind,
kind: UnregisterCommandErrorKind,
}
impl UnregisterCommandError {
pub(crate) const fn new(
name: &'static CStr,
base: CommandBaseKind,
kind: UnregisterCommandErrorKind,
) -> Self {
Self { name, base, kind }
}
pub const fn base(&self) -> CommandBaseKind {
self.base
}
pub const fn kind(&self) -> &UnregisterCommandErrorKind {
&self.kind
}
pub const fn name(&self) -> &'static CStr {
self.name
}
}
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
pub enum UnregisterCommandErrorKind {
#[error("it is not registered")]
NotRegistered,
#[error("the engine still lists it")]
StillLinked,
#[error(transparent)]
Interface(#[from] InterfaceError),
}
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(())
}