use std::path::{Path, PathBuf};
#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
pub enum InvalidClientPath {
#[error("file path is empty")]
Empty,
#[error("file path contains a NUL byte")]
ContainsNul,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct ClientPath(PathBuf);
impl ClientPath {
#[must_use]
pub fn as_path(&self) -> &Path {
&self.0
}
}
impl TryFrom<PathBuf> for ClientPath {
type Error = InvalidClientPath;
fn try_from(path: PathBuf) -> Result<Self, Self::Error> {
if path.is_empty() {
return Err(InvalidClientPath::Empty);
}
if path.as_os_str().as_encoded_bytes().contains(&0) {
return Err(InvalidClientPath::ContainsNul);
}
Ok(Self(path))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_try_from_rejects_empty_and_nul() {
assert_eq!(
ClientPath::try_from(PathBuf::new()),
Err(InvalidClientPath::Empty)
);
assert_eq!(
ClientPath::try_from(PathBuf::from("/ws/a\0b.rs")),
Err(InvalidClientPath::ContainsNul)
);
}
#[test]
fn test_try_from_accepts_absolute_relative_and_dotted() {
for raw in ["/ws/a.rs", "a.rs", "../a.rs", "/ws/./a.rs"] {
let path = ClientPath::try_from(PathBuf::from(raw)).unwrap();
assert_eq!(path.as_path(), Path::new(raw));
}
}
#[test]
fn test_error_messages_name_the_cause() {
assert!(InvalidClientPath::Empty.to_string().contains("empty"));
assert!(InvalidClientPath::ContainsNul.to_string().contains("NUL"));
}
}