clankerdiff_core/models/
repo_path.rs1use crate::RepoPathError;
2use serde::{Deserialize, Deserializer, Serialize, Serializer};
3use std::{fmt, sync::Arc};
4
5#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
7pub struct RepoPath(Arc<str>);
8
9impl RepoPath {
10 pub fn new(path: impl AsRef<str>) -> Result<Self, RepoPathError> {
16 let raw = path.as_ref();
17 if raw.is_empty() {
18 return Err(RepoPathError::Empty);
19 }
20 if raw.bytes().any(|byte| byte == 0) {
21 return Err(RepoPathError::Nul);
22 }
23 if raw.starts_with('/') || raw.starts_with("\\\\") || raw.as_bytes().get(1) == Some(&b':') {
24 return Err(RepoPathError::Absolute);
25 }
26 for component in raw.split('/') {
27 match component {
28 "" => return Err(RepoPathError::Empty),
29 "." | ".." => return Err(RepoPathError::Traversal),
30 _ => {}
31 }
32 }
33 Ok(Self(Arc::from(raw)))
34 }
35
36 #[must_use]
38 pub fn as_str(&self) -> &str {
39 self.0.as_ref()
40 }
41}
42
43impl AsRef<str> for RepoPath {
44 fn as_ref(&self) -> &str {
45 self.as_str()
46 }
47}
48
49impl fmt::Display for RepoPath {
50 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
51 f.write_str(self.as_str())
52 }
53}
54
55impl TryFrom<String> for RepoPath {
56 type Error = RepoPathError;
57 fn try_from(value: String) -> Result<Self, Self::Error> {
58 Self::new(value)
59 }
60}
61
62impl TryFrom<&str> for RepoPath {
63 type Error = RepoPathError;
64 fn try_from(value: &str) -> Result<Self, Self::Error> {
65 Self::new(value)
66 }
67}
68
69impl Serialize for RepoPath {
70 fn serialize<T: Serializer>(&self, serializer: T) -> Result<T::Ok, T::Error> {
71 serializer.serialize_str(self.as_str())
72 }
73}
74
75impl<'de> Deserialize<'de> for RepoPath {
76 fn deserialize<T: Deserializer<'de>>(deserializer: T) -> Result<Self, T::Error> {
77 Self::new(String::deserialize(deserializer)?).map_err(serde::de::Error::custom)
78 }
79}