agent_migrate_core/
path.rs1use 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}