Skip to main content

agent_migrate_core/
path.rs

1use std::fmt;
2use std::path::{Component, Path, PathBuf};
3use std::str::FromStr;
4
5use serde::{Deserialize, Serialize};
6use thiserror::Error;
7
8#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
9#[serde(try_from = "String", into = "String")]
10pub struct BundleRelativePath(PathBuf);
11
12impl BundleRelativePath {
13    #[must_use]
14    pub fn as_path(&self) -> &Path {
15        &self.0
16    }
17}
18
19impl FromStr for BundleRelativePath {
20    type Err = BundlePathError;
21
22    fn from_str(value: &str) -> Result<Self, Self::Err> {
23        if value.is_empty() || value.contains(['\0', '\\']) {
24            return Err(BundlePathError::Invalid(value.to_owned()));
25        }
26        let path = PathBuf::from(value);
27        let all_normal = path
28            .components()
29            .all(|component| matches!(component, Component::Normal(_)));
30        let windows_prefix = value
31            .split('/')
32            .next()
33            .is_some_and(|segment| segment.contains(':'));
34        if !all_normal || windows_prefix {
35            return Err(BundlePathError::Invalid(value.to_owned()));
36        }
37        Ok(Self(path))
38    }
39}
40
41impl TryFrom<String> for BundleRelativePath {
42    type Error = BundlePathError;
43
44    fn try_from(value: String) -> Result<Self, Self::Error> {
45        Self::from_str(&value)
46    }
47}
48
49impl From<BundleRelativePath> for String {
50    fn from(value: BundleRelativePath) -> Self {
51        value.to_string()
52    }
53}
54
55impl fmt::Display for BundleRelativePath {
56    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
57        formatter.write_str(&self.0.to_string_lossy())
58    }
59}
60
61#[derive(Debug, Error, PartialEq, Eq)]
62pub enum BundlePathError {
63    #[error("invalid bundle-relative path: {0}")]
64    Invalid(String),
65}