use fidl_fuchsia_io as fio;
use static_assertions::const_assert_eq;
use std::borrow::Borrow;
use std::fmt::Display;
use std::ops::Deref;
use thiserror::Error;
use zx_status::Status;
#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub struct Name(String);
pub const MAX_NAME_LENGTH: usize = fio::MAX_NAME_LENGTH as usize;
const_assert_eq!(MAX_NAME_LENGTH as u64, fio::MAX_NAME_LENGTH);
#[derive(Error, Debug, Clone)]
pub enum ParseNameError {
#[error("name `{0}` is too long")]
TooLong(String),
#[error("name cannot be empty")]
Empty,
#[error("name cannot be `.`")]
Dot,
#[error("name cannot be `..`")]
DotDot,
#[error("name cannot contain `/`")]
Slash,
#[error("name cannot contain embedded NUL")]
EmbeddedNul,
}
impl From<ParseNameError> for Status {
fn from(value: ParseNameError) -> Self {
match value {
ParseNameError::TooLong(_) => Status::BAD_PATH,
_ => Status::INVALID_ARGS,
}
}
}
impl Name {
pub fn from<S: Into<String>>(s: S) -> Result<Name, ParseNameError> {
parse_name(s.into())
}
}
impl Display for Name {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let value = &self.0;
write!(f, "{value}")
}
}
pub fn parse_name(name: String) -> Result<Name, ParseNameError> {
validate_name(&name)?;
Ok(Name(name))
}
pub fn validate_name(name: &str) -> Result<(), ParseNameError> {
if name.len() > MAX_NAME_LENGTH {
return Err(ParseNameError::TooLong(name.to_string()));
}
if name.len() == 0 {
return Err(ParseNameError::Empty);
}
if name == "." {
return Err(ParseNameError::Dot);
}
if name == ".." {
return Err(ParseNameError::DotDot);
}
if name.chars().any(|c: char| c == '/') {
return Err(ParseNameError::Slash);
}
if name.chars().any(|c: char| c == '\0') {
return Err(ParseNameError::EmbeddedNul);
}
Ok(())
}
impl From<Name> for String {
fn from(value: Name) -> Self {
value.0
}
}
impl TryFrom<String> for Name {
type Error = ParseNameError;
fn try_from(value: String) -> Result<Name, ParseNameError> {
parse_name(value)
}
}
impl Deref for Name {
type Target = str;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl Borrow<str> for Name {
fn borrow(&self) -> &str {
&*self
}
}
#[cfg(test)]
mod tests {
use super::*;
use assert_matches::assert_matches;
#[test]
fn test_parse_name() {
assert_matches!(parse_name("a".repeat(1000)), Err(ParseNameError::TooLong(_)));
assert_matches!(
parse_name(
std::str::from_utf8(&vec![65; fio::MAX_FILENAME as usize + 1]).unwrap().to_string()
),
Err(ParseNameError::TooLong(_))
);
assert_matches!(
parse_name(
std::str::from_utf8(&vec![65; fio::MAX_FILENAME as usize]).unwrap().to_string()
),
Ok(_)
);
assert_matches!(parse_name("".to_string()), Err(ParseNameError::Empty));
assert_matches!(parse_name(".".to_string()), Err(ParseNameError::Dot));
assert_matches!(parse_name("..".to_string()), Err(ParseNameError::DotDot));
assert_matches!(parse_name(".a".to_string()), Ok(Name(name)) if &name == ".a");
assert_matches!(parse_name("..a".to_string()), Ok(Name(name)) if &name == "..a");
assert_matches!(parse_name("a/b".to_string()), Err(ParseNameError::Slash));
assert_matches!(parse_name("a\0b".to_string()), Err(ParseNameError::EmbeddedNul));
assert_matches!(parse_name("abc".to_string()), Ok(Name(name)) if &name == "abc");
}
#[test]
fn test_validate_name() {
assert_matches!(validate_name(&"a".repeat(1000)), Err(ParseNameError::TooLong(_)));
assert_matches!(
validate_name(std::str::from_utf8(&vec![65; fio::MAX_FILENAME as usize + 1]).unwrap()),
Err(ParseNameError::TooLong(_))
);
assert_matches!(
validate_name(std::str::from_utf8(&vec![65; fio::MAX_FILENAME as usize]).unwrap()),
Ok(())
);
assert_matches!(validate_name(""), Err(ParseNameError::Empty));
assert_matches!(validate_name("."), Err(ParseNameError::Dot));
assert_matches!(validate_name(".."), Err(ParseNameError::DotDot));
assert_matches!(validate_name(".a"), Ok(()));
assert_matches!(validate_name("..a"), Ok(()));
assert_matches!(validate_name("a/b"), Err(ParseNameError::Slash));
assert_matches!(validate_name("a\0b"), Err(ParseNameError::EmbeddedNul));
assert_matches!(validate_name("abc"), Ok(()));
}
#[test]
fn test_try_from() {
assert_matches!(Name::try_from("a".repeat(1000)), Err(ParseNameError::TooLong(_)));
assert_matches!(Name::try_from("abc".to_string()), Ok(Name(name)) if &name == "abc");
}
#[test]
fn test_into() {
let name = Name::try_from("a".to_string()).unwrap();
let name: String = name.into();
assert_eq!(name, "a".to_string());
}
#[test]
fn test_deref() {
let name = Name::try_from("a".to_string()).unwrap();
let name: &str = &name;
assert_eq!(name, "a");
}
}