1use crate::ParseError;
6
7#[derive(Debug, PartialEq, Eq, Clone)]
9pub struct Path(String);
10
11impl TryFrom<String> for Path {
12 type Error = ParseError;
13 fn try_from(value: String) -> Result<Self, Self::Error> {
14 let () = validate_path(&value)?;
15 Ok(Self(value))
16 }
17}
18
19impl std::str::FromStr for Path {
20 type Err = ParseError;
21 fn from_str(s: &str) -> Result<Self, Self::Err> {
22 let () = validate_path(s)?;
23 Ok(Self(s.to_owned()))
24 }
25}
26
27impl std::fmt::Display for Path {
28 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
29 self.0.fmt(f)
30 }
31}
32
33impl AsRef<str> for Path {
34 fn as_ref(&self) -> &str {
35 &self.0
36 }
37}
38
39impl std::ops::Deref for Path {
40 type Target = str;
41 fn deref(&self) -> &Self::Target {
42 self.0.as_str()
43 }
44}
45
46impl From<crate::PackageName> for Path {
47 fn from(other: crate::PackageName) -> Self {
48 debug_assert!(validate_path(other.as_ref()).is_ok());
50 Self(other.into())
51 }
52}
53
54fn validate_path(path: &str) -> Result<(), crate::ParseError> {
56 for s in path.split('/') {
57 let () = crate::parse::validate_package_path_segment(s)
58 .map_err(crate::ParseError::InvalidPathSegment)?;
59 }
60 Ok(())
61}
62
63pub(crate) fn parse_path_to_name_and_variant(
65 path: &str,
66) -> Result<(crate::PackageName, Option<crate::PackageVariant>), ParseError> {
67 if path.is_empty() {
68 return Err(ParseError::MissingName);
69 }
70 let mut iter = path.split('/').fuse();
71 let name = if let Some(s) = iter.next() {
72 s.parse().map_err(ParseError::InvalidName)?
73 } else {
74 return Err(ParseError::MissingName);
75 };
76 let variant = if let Some(s) = iter.next() {
77 Some(s.parse().map_err(ParseError::InvalidVariant)?)
78 } else {
79 None
80 };
81 if let Some(_) = iter.next() {
82 return Err(ParseError::ExtraPathSegments);
83 }
84 Ok((name, variant))
85}
86
87#[cfg(test)]
88mod test {
89 use super::*;
90 use assert_matches::assert_matches;
91
92 macro_rules! test_err {
93 (
94 $(
95 $test_name:ident => {
96 path = $path:expr,
97 err = $err:pat,
98 }
99 )+
100 ) => {
101 $(
102 #[test]
103 fn $test_name() {
104 assert_matches!(
105 validate_path($path),
106 Err($err)
107 );
108 }
109 )+
110 }
111 }
112
113 test_err! {
114 err_empty_path => {
115 path = "",
116 err = crate::ParseError::InvalidPathSegment(_),
117 }
118 err_leading_slash => {
119 path = "/leading-slash",
120 err = crate::ParseError::InvalidPathSegment(_),
121 }
122 err_trailing_slash => {
123 path = "name/",
124 err = crate::ParseError::InvalidPathSegment(_),
125 }
126 err_empty_segment => {
127 path = "name//trailing",
128 err = crate::ParseError::InvalidPathSegment(_),
129 }
130 err_invalid_segment => {
131 path = "name/#/trailing",
132 err = crate::ParseError::InvalidPathSegment(_),
133 }
134 }
135
136 #[test]
137 fn success() {
138 for path in ["name", "name/other", "name/other/more"] {
139 let () = validate_path(path).unwrap();
140 }
141 }
142}
143
144#[cfg(test)]
145mod test_parse_path_to_name_and_variant {
146 use super::*;
147 use assert_matches::assert_matches;
148
149 macro_rules! test_err {
150 (
151 $(
152 $test_name:ident => {
153 path = $path:expr,
154 err = $err:pat,
155 }
156 )+
157 ) => {
158 $(
159 #[test]
160 fn $test_name() {
161 assert_matches!(
162 parse_path_to_name_and_variant($path),
163 Err($err)
164 );
165 }
166 )+
167 }
168 }
169
170 test_err! {
171 err_no_name => {
172 path = "/",
173 err = ParseError::InvalidName(_),
174 }
175 err_leading_slash => {
176 path = "/name",
177 err = ParseError::InvalidName(_),
178 }
179 err_empty_variant => {
180 path = "name/",
181 err = ParseError::InvalidVariant(_),
182 }
183 err_trailing_slash => {
184 path = "name/variant/",
185 err = ParseError::ExtraPathSegments,
186 }
187 err_extra_segment => {
188 path = "name/variant/extra",
189 err = ParseError::ExtraPathSegments,
190 }
191 err_invalid_segment => {
192 path = "name/#",
193 err = ParseError::InvalidVariant(_),
194 }
195 }
196
197 #[test]
198 fn success() {
199 assert_eq!(
200 ("name".parse().unwrap(), None),
201 parse_path_to_name_and_variant("name").unwrap()
202 );
203 assert_eq!(
204 ("name".parse().unwrap(), Some("variant".parse().unwrap())),
205 parse_path_to_name_and_variant("name/variant").unwrap()
206 );
207 }
208}