use crate::parse::Error;
use crate::parse::Error::InvalidParam;
use std::fmt::{Display, Formatter};
#[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug)]
pub struct Param<'a> {
name: &'a str,
value: Option<&'a str>,
}
impl<'a> Param<'a> {
pub unsafe fn new(name: &'a str, value: Option<&'a str>) -> Self {
debug_assert!(Self::is_valid_name(name));
debug_assert!(value.iter().all(|v| Self::is_valid_value(v)));
Self { name, value }
}
pub unsafe fn from_str(param: &'a str) -> Self {
if let Some(eq) = param.as_bytes().iter().position(|c| *c == b'=') {
let (name, eq_value) = param.split_at(eq);
Self::new(name, Some(&eq_value[1..]))
} else {
Self::new(param, None)
}
}
}
impl<'a> TryFrom<&'a str> for Param<'a> {
type Error = Error;
fn try_from(param: &'a str) -> Result<Self, Self::Error> {
if let Some(eq) = param.as_bytes().iter().position(|c| *c == b'=') {
let (name, eq_value) = param.split_at(eq);
if Self::is_valid_name(name) && Self::is_valid_value(&eq_value[1..]) {
Ok(unsafe { Self::new(name, Some(&eq_value[1..])) })
} else {
Err(InvalidParam)
}
} else if Self::is_valid_name(param) {
Ok(unsafe { Self::new(param, None) })
} else {
Err(InvalidParam)
}
}
}
impl<'a> Param<'a> {
pub fn is_valid_name(name: &str) -> bool {
name.as_bytes().iter().all(|c| {
c.is_ascii_alphanumeric()
|| (c.is_ascii_punctuation() && *c != b'&' && *c != b'#' && *c != b'=')
})
}
pub fn is_valid_value(value: &str) -> bool {
value.as_bytes().iter().all(|c| {
c.is_ascii_alphanumeric() || (c.is_ascii_punctuation() && *c != b'&' && *c != b'#')
})
}
}
impl<'a> Param<'a> {
pub const fn name(&self) -> &str {
self.name
}
pub const fn value(&self) -> Option<&str> {
self.value
}
}
impl<'a> Display for Param<'a> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.name)?;
if let Some(value) = self.value {
write!(f, "={}", value)?;
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use crate::parse::Error::InvalidParam;
use crate::Param;
#[test]
fn new() {
let param: Param = unsafe { Param::new("name", Some("value")) };
assert_eq!(param.name, "name");
assert_eq!(param.value, Some("value"));
}
#[test]
fn from_str() {
let param: Param = unsafe { Param::from_str("name") };
assert_eq!(param.name, "name");
assert_eq!(param.value, None);
let param: Param = unsafe { Param::from_str("name=") };
assert_eq!(param.name, "name");
assert_eq!(param.value, Some(""));
let param: Param = unsafe { Param::from_str("name=value") };
assert_eq!(param.name, "name");
assert_eq!(param.value, Some("value"));
}
#[test]
fn try_from_str() {
let param = Param::try_from("name").unwrap();
assert_eq!(param.name(), "name");
assert_eq!(param.value(), None);
let param = Param::try_from("name=value").unwrap();
assert_eq!(param.name(), "name");
assert_eq!(param.value(), Some("value"));
let param = Param::try_from("name=").unwrap();
assert_eq!(param.name(), "name");
assert_eq!(param.value(), Some(""));
let param = Param::try_from("").unwrap();
assert_eq!(param.name(), "");
assert_eq!(param.value(), None);
assert_eq!(Param::try_from("name=val&ue"), Err(InvalidParam));
assert_eq!(Param::try_from("na&me"), Err(InvalidParam));
assert_eq!(Param::try_from("na#me"), Err(InvalidParam));
assert_eq!(Param::try_from("name=val#ue"), Err(InvalidParam));
}
#[test]
fn properties() {
let param: Param = unsafe { Param::new("name", Some("value")) };
assert_eq!(param.name(), "name");
assert_eq!(param.value(), Some("value"));
let param: Param = unsafe { Param::new("name", None) };
assert_eq!(param.name(), "name");
assert_eq!(param.value(), None);
}
#[test]
fn display() {
let param: Param = unsafe { Param::new("name", Some("value")) };
assert_eq!(param.to_string(), "name=value");
let param: Param = unsafe { Param::new("name", None) };
assert_eq!(param.to_string(), "name");
}
}