agent_migrate_core/
path_mapping.rs1use std::path::{Component, Path, PathBuf};
2
3use serde::{Deserialize, Serialize};
4use thiserror::Error;
5
6#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
7pub struct PathMapping {
8 source: PathBuf,
9 target: PathBuf,
10}
11
12impl PathMapping {
13 pub fn new(source: PathBuf, target: PathBuf) -> Result<Self, PathMappingError> {
19 if !valid_absolute(&source) || !valid_absolute(&target) {
20 return Err(PathMappingError::Invalid);
21 }
22 Ok(Self { source, target })
23 }
24
25 #[must_use]
26 pub fn source(&self) -> &Path {
27 &self.source
28 }
29
30 #[must_use]
31 pub fn target(&self) -> &Path {
32 &self.target
33 }
34}
35
36#[derive(Debug, Clone, PartialEq, Eq)]
37pub struct PathMappingSet {
38 mappings: Vec<PathMapping>,
39}
40
41impl PathMappingSet {
42 pub fn new(mut mappings: Vec<PathMapping>) -> Result<Self, PathMappingError> {
48 mappings.sort_by_key(|mapping| std::cmp::Reverse(mapping.source.components().count()));
49 for pair in mappings.windows(2) {
50 if pair[0].source == pair[1].source {
51 return Err(PathMappingError::DuplicateSource);
52 }
53 }
54 Ok(Self { mappings })
55 }
56
57 pub fn map(&self, input: &Path) -> Result<PathBuf, PathMappingError> {
63 if !valid_absolute(input) {
64 return Err(PathMappingError::Invalid);
65 }
66 self.mappings
67 .iter()
68 .find_map(|mapping| {
69 input
70 .strip_prefix(&mapping.source)
71 .ok()
72 .map(|suffix| mapping.target.join(suffix))
73 })
74 .ok_or(PathMappingError::Unmapped)
75 }
76}
77
78fn valid_absolute(path: &Path) -> bool {
79 path.is_absolute()
80 && path
81 .components()
82 .all(|component| !matches!(component, Component::ParentDir))
83}
84
85#[derive(Debug, Error, PartialEq, Eq)]
86pub enum PathMappingError {
87 #[error("path mapping must use normalized absolute paths")]
88 Invalid,
89 #[error("path mapping contains a duplicate source")]
90 DuplicateSource,
91 #[error("absolute path has no declared mapping")]
92 Unmapped,
93}