use crate::plugin::policy::error::PluginComponentPathError;
use std::{
fmt::{Debug, Formatter},
path::{Path, PathBuf},
};
#[derive(Clone, Eq, PartialEq)]
pub struct PluginComponentPath(PathBuf);
impl PluginComponentPath {
pub fn try_new(path: impl Into<PathBuf>) -> Result<Self, PluginComponentPathError> {
let path = path.into();
validate_component_path(&path)?;
Ok(Self(path))
}
#[must_use]
pub fn as_path(&self) -> &Path {
&self.0
}
#[must_use]
pub fn into_path_buf(self) -> PathBuf {
self.0
}
}
impl Debug for PluginComponentPath {
fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
formatter
.debug_struct("PluginComponentPath")
.field("path_byte_len", &self.0.to_str().map_or(0, str::len))
.finish()
}
}
fn validate_component_path(path: &Path) -> Result<(), PluginComponentPathError> {
let Some(path) = path.to_str() else {
return Err(PluginComponentPathError::PlatformSeparator);
};
if path.is_empty() {
return Err(PluginComponentPathError::Empty);
}
if path.starts_with('/') {
return Err(PluginComponentPathError::Absolute);
}
if path.as_bytes().get(1) == Some(&b':') {
return Err(PluginComponentPathError::WindowsDrivePrefix);
}
if path.contains('\\') {
return Err(PluginComponentPathError::PlatformSeparator);
}
for component in path.split('/') {
if component.is_empty() {
return Err(PluginComponentPathError::EmptyComponent);
}
if matches!(component, "." | "..") {
return Err(PluginComponentPathError::DotComponent);
}
}
Ok(())
}