use std::path::{Component, Path, PathBuf};
use serde::{Deserialize, Serialize};
use thiserror::Error;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct PathMapping {
source: PathBuf,
target: PathBuf,
}
impl PathMapping {
pub fn new(source: PathBuf, target: PathBuf) -> Result<Self, PathMappingError> {
if !valid_absolute(&source) || !valid_absolute(&target) {
return Err(PathMappingError::Invalid);
}
Ok(Self { source, target })
}
#[must_use]
pub fn source(&self) -> &Path {
&self.source
}
#[must_use]
pub fn target(&self) -> &Path {
&self.target
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PathMappingSet {
mappings: Vec<PathMapping>,
}
impl PathMappingSet {
pub fn new(mut mappings: Vec<PathMapping>) -> Result<Self, PathMappingError> {
mappings.sort_by_key(|mapping| std::cmp::Reverse(mapping.source.components().count()));
for pair in mappings.windows(2) {
if pair[0].source == pair[1].source {
return Err(PathMappingError::DuplicateSource);
}
}
Ok(Self { mappings })
}
pub fn map(&self, input: &Path) -> Result<PathBuf, PathMappingError> {
if !valid_absolute(input) {
return Err(PathMappingError::Invalid);
}
self.mappings
.iter()
.find_map(|mapping| {
input
.strip_prefix(&mapping.source)
.ok()
.map(|suffix| mapping.target.join(suffix))
})
.ok_or(PathMappingError::Unmapped)
}
}
fn valid_absolute(path: &Path) -> bool {
path.is_absolute()
&& path
.components()
.all(|component| !matches!(component, Component::ParentDir))
}
#[derive(Debug, Error, PartialEq, Eq)]
pub enum PathMappingError {
#[error("path mapping must use normalized absolute paths")]
Invalid,
#[error("path mapping contains a duplicate source")]
DuplicateSource,
#[error("absolute path has no declared mapping")]
Unmapped,
}