use crate::ContentType;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct ISupportToken<'msg> {
parameter: ContentType<'msg>,
value: Option<ContentType<'msg>>,
set: bool,
}
impl<'msg> ISupportToken<'msg> {
pub const fn parse(input: &'msg[u8]) -> Result<Self, ISupportTokenError> {
if input.is_empty() {return Err(ISupportTokenError::EmptyInput);}
let mut copy = input;
let mut set = true;
if input[0] == b'-' {
set = false;
(_, copy) = input.split_at(1);
}
let mut index = 0;
let mut equals_present = false;
let mut equals_index = 0;
while index < copy.len() {
if !equals_present && copy[index] == b'=' {
if !set {return Err(ISupportTokenError::ValueNotPermittedOnNegatedToken);}
equals_present = true; equals_index = index;
} else if !equals_present && is_invalid_parameter_byte(copy[index]) {
return Err(ISupportTokenError::InvalidParameterByte(copy[index]));
} else if equals_present && is_invalid_value_byte(copy[index]) {
return Err(ISupportTokenError::InvalidValueByte(copy[index]));
}
index += 1;
}
let (parameter, value) = if equals_present {
if equals_index == 0 {return Err(ISupportTokenError::NoParameterBeforeEquals);}
let (first, second) = copy.split_at(equals_index);
let (_, after) = second.split_at(1);
(ContentType::new(first), if after.is_empty() {None} else {Some(ContentType::new(after))})
} else {
(ContentType::new(copy), None)
};
Ok(ISupportToken{parameter, value, set})
}
#[must_use]
pub const fn parameter(&self) -> ContentType {
self.parameter
}
#[must_use]
pub const fn value(&self) -> Option<ContentType> {
self.value
}
#[must_use]
pub const fn is_set(&self) -> bool {
self.set
}
}
impl<'msg> core::fmt::Display for ISupportToken<'msg> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
if !self.is_set() {write!(f, "-")?;}
if let Some(value) = self.value() {write!(f, "{}={}", self.parameter, value)}
else {write!(f, "{}", self.parameter())}
}
}
const fn is_invalid_parameter_byte(input: u8) -> bool {
!input.is_ascii_uppercase() && !input.is_ascii_digit()
}
const fn is_invalid_value_byte(input: u8) -> bool {
!input.is_ascii_alphanumeric() && !matches!(input, b'!'..=b'/' | b'\x20' | b'\x5c' | b'\x3d' | b':'..=b'<' |
b'>'..=b'@' | b'[' | b']'..=b'`' | b'{'..=b'~')
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum ISupportTokenError {
EmptyInput,
NoParameterBeforeEquals,
ValueNotPermittedOnNegatedToken,
InvalidParameterByte(u8),
InvalidValueByte(u8),
}
#[cfg(test)]
mod const_tests {
use crate::const_tests::is_identical;
use super::ISupportToken;
#[test]
const fn parse_token() {
assert!(ISupportToken::parse(b"-FNC").is_ok());
assert!(ISupportToken::parse(b"-FNC=").is_err());
assert!(ISupportToken::parse(b"-fnc").is_err());
assert!(ISupportToken::parse(b"ACCOUNTEXTBAN=a").is_ok());
assert!(ISupportToken::parse(b"ACCOUNTEXTBAN=\0a").is_err());
assert!(ISupportToken::parse(b"PREFIX=(ov)@+").is_ok());
}
#[test]
const fn get_parameter() {
let token = ISupportToken::parse(b"PREFIX=(ov)@+");
assert!(token.is_ok());
if let Ok(token) = token {assert!(is_identical(token.parameter().as_bytes(), b"PREFIX"));}
}
#[test]
const fn get_value() {
let token = ISupportToken::parse(b"PREFIX=(ov)@+");
assert!(token.is_ok());
if let Ok(token) = token {
let value = token.value();
assert!(value.is_some());
if let Some(value) = value {assert!(is_identical(value.as_bytes(), b"(ov)@+"));}
}
}
#[test]
const fn check_set() {
let token = ISupportToken::parse(b"PREFIX=(ov)@+");
assert!(token.is_ok());
if let Ok(token) = token {assert!(token.is_set());}
}
}