use crate::ContentType;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct Source<'msg> {
prefix: char,
from: Origin<'msg>,
}
impl<'msg> Source<'msg> {
pub const fn parse(mut input: &'msg [u8]) -> Result<Self, SourceError> {
if input.is_empty() {return Err(SourceError::EmptyInput);}
let prefix = if input[0] == b':' {':'} else {return Err(SourceError::InvalidStartingPrefix(input[0]))};
let (mut nick_end, mut user_end, mut probably_servername) = (0, 0, false);
let (mut user_prefix, mut user, mut host_prefix, mut host) = (None, None, None, None);
let mut index = 0;
while index < input.len() {
if is_invalid_byte(input[index]) {
return Err(SourceError::InvalidByte(input[index]));
} else if input[index] == b'!' {
user_prefix = Some('!');
nick_end = index - 1;
} else if input[index] == b'@' && user_prefix.is_some() {
host_prefix = Some('@');
user_end = index - nick_end - 2;
} else if input[index] == b'.' && user_prefix.is_none() && host_prefix.is_none() {
probably_servername = true;
}
index += 1;
}
if let Some((_, rest)) = input.split_first() {input = rest;}
let from = if probably_servername {
Origin::Servername(Servername(ContentType::new(input)))
} else if user_prefix.is_some() {
let (nick, rest) = input.split_at(nick_end);
input = rest;
if let Some((_, rest)) = input.split_first() {input = rest;}
let (u, rest) = input.split_at(user_end);
user = Some(ContentType::new(u));
input = rest;
if let Some((_, rest)) = input.split_first() {input = rest;}
host = Some(ContentType::new(input));
Origin::Nickname(Nickname{nick: ContentType::new(nick), user_prefix, user, host_prefix, host})
} else {
Origin::Nickname(Nickname{nick: ContentType::new(input), user_prefix, user, host_prefix, host})
};
Ok(Source{prefix, from})
}
#[must_use]
pub const fn prefix(&self) -> char {
self.prefix
}
#[must_use]
pub const fn origin(&self) -> Origin {
self.from
}
}
impl<'msg> core::fmt::Display for Source<'msg> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "{}{}", self.prefix, self.from)
}
}
const fn is_invalid_byte(input: u8) -> bool {
match input {
0 | 10 | 13 | 32 => true,
_ => false,
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum Origin<'msg> {
Servername(Servername<'msg>),
Nickname(Nickname<'msg>),
}
impl<'msg> Origin<'msg> {
#[must_use]
pub const fn is_valid_utf8(&self) -> bool {
match self {
Self::Servername(servername) => servername.0.is_valid_utf8(),
Self::Nickname(nickname) => {
let valid_user = if let Some(user) = nickname.user {user.is_valid_utf8()} else {true};
let valid_host = if let Some(host) = nickname.host {host.is_valid_utf8()} else {true};
nickname.nick.is_valid_utf8() && valid_user && valid_host
},
}
}
}
impl<'msg> core::fmt::Display for Origin<'msg> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Origin::Servername(servername) => write!(f, "{servername}"),
Origin::Nickname(nickname) => write!(f, "{nickname}"),
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct Servername<'msg>(ContentType<'msg>);
impl<'msg> Servername<'msg> {
#[must_use]
pub const fn content(&self) -> ContentType {
self.0
}
}
impl<'msg> core::fmt::Display for Servername<'msg> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "{}", self.0)
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct Nickname<'msg> {
nick: ContentType<'msg>,
user_prefix: Option<char>,
user: Option<ContentType<'msg>>,
host_prefix: Option<char>,
host: Option<ContentType<'msg>>,
}
impl<'msg> Nickname<'msg> {
#[must_use]
pub const fn nick(&self) -> ContentType {
self.nick
}
#[must_use]
pub const fn user_prefix(&self) -> Option<char> {
self.user_prefix
}
#[must_use]
pub const fn user(&self) -> Option<ContentType> {
self.user
}
#[must_use]
pub const fn host_prefix(&self) -> Option<char> {
self.host_prefix
}
#[must_use]
pub const fn host(&self) -> Option<ContentType> {
self.host
}
}
impl<'msg> core::fmt::Display for Nickname<'msg> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
if self.user_prefix.is_some() && self.host_prefix.is_some() {
write!(f, "{}{}{}{}{}", self.nick, self.user_prefix.unwrap(), self.user.as_ref().unwrap(),
self.host_prefix.unwrap(), self.host.as_ref().unwrap())
} else {
write!(f, "{}", self.nick)
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum SourceError {
EmptyInput,
InvalidStartingPrefix(u8),
InvalidByte(u8),
}
#[cfg(test)]
mod const_tests {
use crate::{const_tests::is_nick, ContentType, is_identical};
use super::{Origin, Nickname, Servername, Source, is_invalid_byte};
const fn is_same_content(first: ContentType, second: &str) -> bool {
match first {
ContentType::StringSlice(s) => is_identical(s.as_bytes(), second.as_bytes()),
ContentType::NonUtf8ByteSlice(b) => is_identical(b, second.as_bytes()),
}
}
const fn is_same_char(first: char, second: char) -> bool {first == second}
#[test]
const fn invalid_byte_in_source() {
assert!(is_invalid_byte(b' '));
assert!(!is_invalid_byte(b'c'));
}
#[test]
const fn source_utf8() {
let src = Source{prefix: ':', from: Origin::Servername(Servername(ContentType::StringSlice("blah")))};
assert!(src.from.is_valid_utf8());
let src = Source{prefix: ':', from: Origin::Nickname(Nickname{
nick: ContentType::StringSlice("blah"),
user_prefix: None,
user: None,
host_prefix: None,
host: None,
})};
assert!(src.from.is_valid_utf8());
}
#[test]
const fn parsing_source() {
assert!(Source::parse(b":dave").is_ok());
assert!(Source::parse(b":dave!d@david").is_ok());
assert!(Source::parse(b":example.com").is_ok());
assert!(Source::parse(b": dave").is_err());
let input = b":goliath!bob@david";
let source = Source::parse(input);
assert!(source.is_ok());
if let Ok(src) = Source::parse(input) {
assert!(is_nick(src.from));
if let Origin::Nickname(n) = src.from {
assert!(is_same_content(n.nick, "goliath"));
assert!(n.user_prefix.is_some());
if let Some(user_prefix) = n.user_prefix {assert!(is_same_char(user_prefix, '!'));}
assert!(n.user.is_some());
if let Some(user) = n.user {assert!(is_same_content(user, "bob"));}
assert!(n.host_prefix.is_some());
if let Some(host_prefix) = n.host_prefix {assert!(is_same_char(host_prefix, '@'));}
assert!(n.host.is_some());
if let Some(host) = n.host {assert!(is_same_content(host, "david"));}
}
}
let input = ":example.com".as_bytes();
let source = Source::parse(input);
assert!(source.is_ok());
if let Ok(src) = Source::parse(input) {
assert!(!is_nick(src.from));
if let Origin::Servername(s) = src.from {assert!(is_same_content(s.0, "example.com"));}
}
}
}