xref: /aosp_15_r20/external/bazelbuild-rules_rust/util/label/label.rs (revision d4726bddaa87cc4778e7472feed243fa4b6c267f)
1*d4726bddSHONG Yifan //! Bazel label parsing library.
2*d4726bddSHONG Yifan //!
3*d4726bddSHONG Yifan //! USAGE: `label::analyze("//foo/bar:baz")
4*d4726bddSHONG Yifan mod label_error;
5*d4726bddSHONG Yifan use label_error::LabelError;
6*d4726bddSHONG Yifan 
7*d4726bddSHONG Yifan /// Parse and analyze given str.
8*d4726bddSHONG Yifan ///
9*d4726bddSHONG Yifan /// TODO: validate . and .. in target name
10*d4726bddSHONG Yifan /// TODO: validate used characters in target name
analyze(input: &'_ str) -> Result<Label<'_>>11*d4726bddSHONG Yifan pub fn analyze(input: &'_ str) -> Result<Label<'_>> {
12*d4726bddSHONG Yifan     Label::analyze(input)
13*d4726bddSHONG Yifan }
14*d4726bddSHONG Yifan 
15*d4726bddSHONG Yifan #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)]
16*d4726bddSHONG Yifan pub enum Repository<'s> {
17*d4726bddSHONG Yifan     /// A `@@` prefixed name that is unique within a workspace. E.g. `@@rules_rust~0.1.2~toolchains~local_rustc`
18*d4726bddSHONG Yifan     Canonical(&'s str), // stringifies to `@@self.0` where `self.0` may be empty
19*d4726bddSHONG Yifan     /// A `@` (single) prefixed name. E.g. `@rules_rust`.
20*d4726bddSHONG Yifan     Apparent(&'s str),
21*d4726bddSHONG Yifan }
22*d4726bddSHONG Yifan 
23*d4726bddSHONG Yifan impl<'s> Repository<'s> {
repo_name(&self) -> &'s str24*d4726bddSHONG Yifan     pub fn repo_name(&self) -> &'s str {
25*d4726bddSHONG Yifan         match self {
26*d4726bddSHONG Yifan             Repository::Canonical(name) => &name[2..],
27*d4726bddSHONG Yifan             Repository::Apparent(name) => &name[1..],
28*d4726bddSHONG Yifan         }
29*d4726bddSHONG Yifan     }
30*d4726bddSHONG Yifan }
31*d4726bddSHONG Yifan 
32*d4726bddSHONG Yifan #[derive(Debug, PartialEq, Eq)]
33*d4726bddSHONG Yifan pub enum Label<'s> {
34*d4726bddSHONG Yifan     Relative {
35*d4726bddSHONG Yifan         target_name: &'s str,
36*d4726bddSHONG Yifan     },
37*d4726bddSHONG Yifan     Absolute {
38*d4726bddSHONG Yifan         repository: Option<Repository<'s>>,
39*d4726bddSHONG Yifan         package_name: &'s str,
40*d4726bddSHONG Yifan         target_name: &'s str,
41*d4726bddSHONG Yifan     },
42*d4726bddSHONG Yifan }
43*d4726bddSHONG Yifan 
44*d4726bddSHONG Yifan type Result<T, E = LabelError> = core::result::Result<T, E>;
45*d4726bddSHONG Yifan 
46*d4726bddSHONG Yifan impl<'s> Label<'s> {
47*d4726bddSHONG Yifan     /// Parse and analyze given str.
analyze(input: &'s str) -> Result<Label<'s>>48*d4726bddSHONG Yifan     pub fn analyze(input: &'s str) -> Result<Label<'s>> {
49*d4726bddSHONG Yifan         let label = input;
50*d4726bddSHONG Yifan 
51*d4726bddSHONG Yifan         if label.is_empty() {
52*d4726bddSHONG Yifan             return Err(LabelError(err(
53*d4726bddSHONG Yifan                 label,
54*d4726bddSHONG Yifan                 "Empty string cannot be parsed into a label.",
55*d4726bddSHONG Yifan             )));
56*d4726bddSHONG Yifan         }
57*d4726bddSHONG Yifan 
58*d4726bddSHONG Yifan         if label.starts_with(':') {
59*d4726bddSHONG Yifan             return match consume_name(input, label)? {
60*d4726bddSHONG Yifan                 None => Err(LabelError(err(
61*d4726bddSHONG Yifan                     label,
62*d4726bddSHONG Yifan                     "Relative packages must have a name.",
63*d4726bddSHONG Yifan                 ))),
64*d4726bddSHONG Yifan                 Some(name) => Ok(Label::Relative { target_name: name }),
65*d4726bddSHONG Yifan             };
66*d4726bddSHONG Yifan         }
67*d4726bddSHONG Yifan 
68*d4726bddSHONG Yifan         let (input, repository) = consume_repository_name(input, label)?;
69*d4726bddSHONG Yifan 
70*d4726bddSHONG Yifan         // Shorthand labels such as `@repo` are expanded to `@repo//:repo`.
71*d4726bddSHONG Yifan         if input.is_empty() {
72*d4726bddSHONG Yifan             if let Some(ref repo) = repository {
73*d4726bddSHONG Yifan                 let target_name = repo.repo_name();
74*d4726bddSHONG Yifan                 if target_name.is_empty() {
75*d4726bddSHONG Yifan                     return Err(LabelError(err(
76*d4726bddSHONG Yifan                         label,
77*d4726bddSHONG Yifan                         "invalid target name: empty target name",
78*d4726bddSHONG Yifan                     )));
79*d4726bddSHONG Yifan                 } else {
80*d4726bddSHONG Yifan                     return Ok(Label::Absolute {
81*d4726bddSHONG Yifan                         repository,
82*d4726bddSHONG Yifan                         package_name: "",
83*d4726bddSHONG Yifan                         target_name,
84*d4726bddSHONG Yifan                     });
85*d4726bddSHONG Yifan                 };
86*d4726bddSHONG Yifan             }
87*d4726bddSHONG Yifan         }
88*d4726bddSHONG Yifan         let (input, package_name) = consume_package_name(input, label)?;
89*d4726bddSHONG Yifan         let name = consume_name(input, label)?;
90*d4726bddSHONG Yifan         let name = match (package_name, name) {
91*d4726bddSHONG Yifan             (None, None) => {
92*d4726bddSHONG Yifan                 return Err(LabelError(err(
93*d4726bddSHONG Yifan                     label,
94*d4726bddSHONG Yifan                     "labels must have a package and/or a name.",
95*d4726bddSHONG Yifan                 )))
96*d4726bddSHONG Yifan             }
97*d4726bddSHONG Yifan             (Some(package_name), None) => name_from_package(package_name),
98*d4726bddSHONG Yifan             (_, Some(name)) => name,
99*d4726bddSHONG Yifan         };
100*d4726bddSHONG Yifan 
101*d4726bddSHONG Yifan         Ok(Label::Absolute {
102*d4726bddSHONG Yifan             repository,
103*d4726bddSHONG Yifan             package_name: package_name.unwrap_or_default(),
104*d4726bddSHONG Yifan             target_name: name,
105*d4726bddSHONG Yifan         })
106*d4726bddSHONG Yifan     }
107*d4726bddSHONG Yifan 
is_relative(&self) -> bool108*d4726bddSHONG Yifan     pub fn is_relative(&self) -> bool {
109*d4726bddSHONG Yifan         match self {
110*d4726bddSHONG Yifan             Label::Absolute { .. } => false,
111*d4726bddSHONG Yifan             Label::Relative { .. } => true,
112*d4726bddSHONG Yifan         }
113*d4726bddSHONG Yifan     }
114*d4726bddSHONG Yifan 
repo(&self) -> Option<&Repository<'s>>115*d4726bddSHONG Yifan     pub fn repo(&self) -> Option<&Repository<'s>> {
116*d4726bddSHONG Yifan         match self {
117*d4726bddSHONG Yifan             Label::Absolute { repository, .. } => repository.as_ref(),
118*d4726bddSHONG Yifan             Label::Relative { .. } => None,
119*d4726bddSHONG Yifan         }
120*d4726bddSHONG Yifan     }
121*d4726bddSHONG Yifan 
repo_name(&self) -> Option<&'s str>122*d4726bddSHONG Yifan     pub fn repo_name(&self) -> Option<&'s str> {
123*d4726bddSHONG Yifan         match self {
124*d4726bddSHONG Yifan             Label::Absolute { repository, .. } => repository.as_ref().map(|repo| repo.repo_name()),
125*d4726bddSHONG Yifan             Label::Relative { .. } => None,
126*d4726bddSHONG Yifan         }
127*d4726bddSHONG Yifan     }
128*d4726bddSHONG Yifan 
package(&self) -> Option<&'s str>129*d4726bddSHONG Yifan     pub fn package(&self) -> Option<&'s str> {
130*d4726bddSHONG Yifan         match self {
131*d4726bddSHONG Yifan             Label::Relative { .. } => None,
132*d4726bddSHONG Yifan             Label::Absolute { package_name, .. } => Some(*package_name),
133*d4726bddSHONG Yifan         }
134*d4726bddSHONG Yifan     }
135*d4726bddSHONG Yifan 
name(&self) -> &'s str136*d4726bddSHONG Yifan     pub fn name(&self) -> &'s str {
137*d4726bddSHONG Yifan         match self {
138*d4726bddSHONG Yifan             Label::Relative { target_name } => target_name,
139*d4726bddSHONG Yifan             Label::Absolute { target_name, .. } => target_name,
140*d4726bddSHONG Yifan         }
141*d4726bddSHONG Yifan     }
142*d4726bddSHONG Yifan }
143*d4726bddSHONG Yifan 
err<'s>(label: &'s str, msg: &'s str) -> String144*d4726bddSHONG Yifan fn err<'s>(label: &'s str, msg: &'s str) -> String {
145*d4726bddSHONG Yifan     let mut err_msg = label.to_string();
146*d4726bddSHONG Yifan     err_msg.push_str(" must be a legal label; ");
147*d4726bddSHONG Yifan     err_msg.push_str(msg);
148*d4726bddSHONG Yifan     err_msg
149*d4726bddSHONG Yifan }
150*d4726bddSHONG Yifan 
consume_repository_name<'s>( input: &'s str, label: &'s str, ) -> Result<(&'s str, Option<Repository<'s>>)>151*d4726bddSHONG Yifan fn consume_repository_name<'s>(
152*d4726bddSHONG Yifan     input: &'s str,
153*d4726bddSHONG Yifan     label: &'s str,
154*d4726bddSHONG Yifan ) -> Result<(&'s str, Option<Repository<'s>>)> {
155*d4726bddSHONG Yifan     let at_signs = {
156*d4726bddSHONG Yifan         let mut count = 0;
157*d4726bddSHONG Yifan         for char in input.chars() {
158*d4726bddSHONG Yifan             if char == '@' {
159*d4726bddSHONG Yifan                 count += 1;
160*d4726bddSHONG Yifan             } else {
161*d4726bddSHONG Yifan                 break;
162*d4726bddSHONG Yifan             }
163*d4726bddSHONG Yifan         }
164*d4726bddSHONG Yifan         count
165*d4726bddSHONG Yifan     };
166*d4726bddSHONG Yifan     if at_signs == 0 {
167*d4726bddSHONG Yifan         return Ok((input, None));
168*d4726bddSHONG Yifan     }
169*d4726bddSHONG Yifan     if at_signs > 2 {
170*d4726bddSHONG Yifan         return Err(LabelError(err(label, "Unexpected number of leading `@`.")));
171*d4726bddSHONG Yifan     }
172*d4726bddSHONG Yifan 
173*d4726bddSHONG Yifan     let slash_pos = input.find("//").unwrap_or(input.len());
174*d4726bddSHONG Yifan     let repository_name = &input[at_signs..slash_pos];
175*d4726bddSHONG Yifan 
176*d4726bddSHONG Yifan     if !repository_name.is_empty() {
177*d4726bddSHONG Yifan         if !repository_name
178*d4726bddSHONG Yifan             .chars()
179*d4726bddSHONG Yifan             .next()
180*d4726bddSHONG Yifan             .unwrap()
181*d4726bddSHONG Yifan             .is_ascii_alphabetic()
182*d4726bddSHONG Yifan         {
183*d4726bddSHONG Yifan             return Err(LabelError(err(
184*d4726bddSHONG Yifan                 label,
185*d4726bddSHONG Yifan                 "workspace names must start with a letter.",
186*d4726bddSHONG Yifan             )));
187*d4726bddSHONG Yifan         }
188*d4726bddSHONG Yifan         if !repository_name
189*d4726bddSHONG Yifan             .chars()
190*d4726bddSHONG Yifan             .all(|c| c.is_ascii_alphanumeric() || c == '-' || c == '_' || c == '.' || c == '~')
191*d4726bddSHONG Yifan         {
192*d4726bddSHONG Yifan             return Err(LabelError(err(
193*d4726bddSHONG Yifan                 label,
194*d4726bddSHONG Yifan                 "workspace names \
195*d4726bddSHONG Yifan                 may contain only A-Z, a-z, 0-9, '-', '_', '.', and '~'.",
196*d4726bddSHONG Yifan             )));
197*d4726bddSHONG Yifan         }
198*d4726bddSHONG Yifan     }
199*d4726bddSHONG Yifan 
200*d4726bddSHONG Yifan     let repository = if at_signs == 1 {
201*d4726bddSHONG Yifan         Repository::Apparent(&input[0..slash_pos])
202*d4726bddSHONG Yifan     } else if at_signs == 2 {
203*d4726bddSHONG Yifan         if repository_name.is_empty() {
204*d4726bddSHONG Yifan             return Err(LabelError(err(
205*d4726bddSHONG Yifan                 label,
206*d4726bddSHONG Yifan                 "main repository labels are only represented by a single `@`.",
207*d4726bddSHONG Yifan             )));
208*d4726bddSHONG Yifan         }
209*d4726bddSHONG Yifan         Repository::Canonical(&input[0..slash_pos])
210*d4726bddSHONG Yifan     } else {
211*d4726bddSHONG Yifan         return Err(LabelError(err(label, "Unexpected number of leading `@`.")));
212*d4726bddSHONG Yifan     };
213*d4726bddSHONG Yifan 
214*d4726bddSHONG Yifan     Ok((&input[slash_pos..], Some(repository)))
215*d4726bddSHONG Yifan }
216*d4726bddSHONG Yifan 
consume_package_name<'s>(input: &'s str, label: &'s str) -> Result<(&'s str, Option<&'s str>)>217*d4726bddSHONG Yifan fn consume_package_name<'s>(input: &'s str, label: &'s str) -> Result<(&'s str, Option<&'s str>)> {
218*d4726bddSHONG Yifan     let is_absolute = match input.rfind("//") {
219*d4726bddSHONG Yifan         None => false,
220*d4726bddSHONG Yifan         Some(0) => true,
221*d4726bddSHONG Yifan         Some(_) => {
222*d4726bddSHONG Yifan             return Err(LabelError(err(
223*d4726bddSHONG Yifan                 label,
224*d4726bddSHONG Yifan                 "'//' cannot appear in the middle of the label.",
225*d4726bddSHONG Yifan             )));
226*d4726bddSHONG Yifan         }
227*d4726bddSHONG Yifan     };
228*d4726bddSHONG Yifan 
229*d4726bddSHONG Yifan     let (package_name, rest) = match (is_absolute, input.find(':')) {
230*d4726bddSHONG Yifan         (false, colon_pos) if colon_pos.map_or(true, |pos| pos != 0) => {
231*d4726bddSHONG Yifan             return Err(LabelError(err(
232*d4726bddSHONG Yifan                 label,
233*d4726bddSHONG Yifan                 "relative packages are not permitted.",
234*d4726bddSHONG Yifan             )));
235*d4726bddSHONG Yifan         }
236*d4726bddSHONG Yifan         (_, colon_pos) => {
237*d4726bddSHONG Yifan             let (input, colon_pos) = if is_absolute {
238*d4726bddSHONG Yifan                 (&input[2..], colon_pos.map(|cp| cp - 2))
239*d4726bddSHONG Yifan             } else {
240*d4726bddSHONG Yifan                 (input, colon_pos)
241*d4726bddSHONG Yifan             };
242*d4726bddSHONG Yifan             match colon_pos {
243*d4726bddSHONG Yifan                 Some(colon_pos) => (&input[0..colon_pos], &input[colon_pos..]),
244*d4726bddSHONG Yifan                 None => (input, ""),
245*d4726bddSHONG Yifan             }
246*d4726bddSHONG Yifan         }
247*d4726bddSHONG Yifan     };
248*d4726bddSHONG Yifan 
249*d4726bddSHONG Yifan     if package_name.is_empty() {
250*d4726bddSHONG Yifan         return Ok((rest, None));
251*d4726bddSHONG Yifan     }
252*d4726bddSHONG Yifan 
253*d4726bddSHONG Yifan     if !package_name.chars().all(|c| {
254*d4726bddSHONG Yifan         c.is_ascii_alphanumeric()
255*d4726bddSHONG Yifan             || c == '/'
256*d4726bddSHONG Yifan             || c == '-'
257*d4726bddSHONG Yifan             || c == '.'
258*d4726bddSHONG Yifan             || c == ' '
259*d4726bddSHONG Yifan             || c == '$'
260*d4726bddSHONG Yifan             || c == '('
261*d4726bddSHONG Yifan             || c == ')'
262*d4726bddSHONG Yifan             || c == '_'
263*d4726bddSHONG Yifan             || c == '+'
264*d4726bddSHONG Yifan     }) {
265*d4726bddSHONG Yifan         return Err(LabelError(err(
266*d4726bddSHONG Yifan             label,
267*d4726bddSHONG Yifan             "package names may contain only A-Z, \
268*d4726bddSHONG Yifan         a-z, 0-9, '/', '-', '.', ' ', '$', '(', ')', '_', and '+'.",
269*d4726bddSHONG Yifan         )));
270*d4726bddSHONG Yifan     }
271*d4726bddSHONG Yifan     if package_name.ends_with('/') {
272*d4726bddSHONG Yifan         return Err(LabelError(err(
273*d4726bddSHONG Yifan             label,
274*d4726bddSHONG Yifan             "package names may not end with '/'.",
275*d4726bddSHONG Yifan         )));
276*d4726bddSHONG Yifan     }
277*d4726bddSHONG Yifan 
278*d4726bddSHONG Yifan     if rest.is_empty() && is_absolute {
279*d4726bddSHONG Yifan         // This label doesn't contain the target name, we have to use
280*d4726bddSHONG Yifan         // last segment of the package name as target name.
281*d4726bddSHONG Yifan         return Ok((
282*d4726bddSHONG Yifan             match package_name.rfind('/') {
283*d4726bddSHONG Yifan                 Some(pos) => &package_name[pos..],
284*d4726bddSHONG Yifan                 None => package_name,
285*d4726bddSHONG Yifan             },
286*d4726bddSHONG Yifan             Some(package_name),
287*d4726bddSHONG Yifan         ));
288*d4726bddSHONG Yifan     }
289*d4726bddSHONG Yifan 
290*d4726bddSHONG Yifan     Ok((rest, Some(package_name)))
291*d4726bddSHONG Yifan }
292*d4726bddSHONG Yifan 
consume_name<'s>(input: &'s str, label: &'s str) -> Result<Option<&'s str>>293*d4726bddSHONG Yifan fn consume_name<'s>(input: &'s str, label: &'s str) -> Result<Option<&'s str>> {
294*d4726bddSHONG Yifan     if input.is_empty() {
295*d4726bddSHONG Yifan         return Ok(None);
296*d4726bddSHONG Yifan     }
297*d4726bddSHONG Yifan     if input == ":" {
298*d4726bddSHONG Yifan         return Err(LabelError(err(label, "empty target name.")));
299*d4726bddSHONG Yifan     }
300*d4726bddSHONG Yifan     let name = if let Some(stripped) = input.strip_prefix(':') {
301*d4726bddSHONG Yifan         stripped
302*d4726bddSHONG Yifan     } else if let Some(stripped) = input.strip_prefix("//") {
303*d4726bddSHONG Yifan         stripped
304*d4726bddSHONG Yifan     } else {
305*d4726bddSHONG Yifan         input.strip_prefix('/').unwrap_or(input)
306*d4726bddSHONG Yifan     };
307*d4726bddSHONG Yifan 
308*d4726bddSHONG Yifan     if name.starts_with('/') {
309*d4726bddSHONG Yifan         return Err(LabelError(err(
310*d4726bddSHONG Yifan             label,
311*d4726bddSHONG Yifan             "target names may not start with '/'.",
312*d4726bddSHONG Yifan         )));
313*d4726bddSHONG Yifan     }
314*d4726bddSHONG Yifan     if name.starts_with(':') {
315*d4726bddSHONG Yifan         return Err(LabelError(err(
316*d4726bddSHONG Yifan             label,
317*d4726bddSHONG Yifan             "target names may not contain with ':'.",
318*d4726bddSHONG Yifan         )));
319*d4726bddSHONG Yifan     }
320*d4726bddSHONG Yifan     Ok(Some(name))
321*d4726bddSHONG Yifan }
322*d4726bddSHONG Yifan 
name_from_package(package_name: &str) -> &str323*d4726bddSHONG Yifan fn name_from_package(package_name: &str) -> &str {
324*d4726bddSHONG Yifan     package_name
325*d4726bddSHONG Yifan         .rsplit_once('/')
326*d4726bddSHONG Yifan         .map(|tup| tup.1)
327*d4726bddSHONG Yifan         .unwrap_or(package_name)
328*d4726bddSHONG Yifan }
329*d4726bddSHONG Yifan 
330*d4726bddSHONG Yifan #[cfg(test)]
331*d4726bddSHONG Yifan mod tests {
332*d4726bddSHONG Yifan     use super::*;
333*d4726bddSHONG Yifan 
334*d4726bddSHONG Yifan     #[test]
test_repository_name_parsing() -> Result<()>335*d4726bddSHONG Yifan     fn test_repository_name_parsing() -> Result<()> {
336*d4726bddSHONG Yifan         assert_eq!(analyze("@repo//:foo")?.repo_name(), Some("repo"));
337*d4726bddSHONG Yifan         assert_eq!(analyze("@@repo//:foo")?.repo_name(), Some("repo"));
338*d4726bddSHONG Yifan         assert_eq!(analyze("@//:foo")?.repo_name(), Some(""));
339*d4726bddSHONG Yifan         assert_eq!(analyze("//:foo")?.repo_name(), None);
340*d4726bddSHONG Yifan         assert_eq!(analyze(":foo")?.repo_name(), None);
341*d4726bddSHONG Yifan 
342*d4726bddSHONG Yifan         assert_eq!(analyze("@repo//foo/bar")?.repo_name(), Some("repo"));
343*d4726bddSHONG Yifan         assert_eq!(analyze("@@repo//foo/bar")?.repo_name(), Some("repo"));
344*d4726bddSHONG Yifan         assert_eq!(analyze("@//foo/bar")?.repo_name(), Some(""));
345*d4726bddSHONG Yifan         assert_eq!(analyze("//foo/bar")?.repo_name(), None);
346*d4726bddSHONG Yifan         assert_eq!(
347*d4726bddSHONG Yifan             analyze("foo/bar"),
348*d4726bddSHONG Yifan             Err(LabelError(
349*d4726bddSHONG Yifan                 "foo/bar must be a legal label; relative packages are not permitted.".to_string()
350*d4726bddSHONG Yifan             ))
351*d4726bddSHONG Yifan         );
352*d4726bddSHONG Yifan 
353*d4726bddSHONG Yifan         assert_eq!(analyze("@repo//foo")?.repo_name(), Some("repo"));
354*d4726bddSHONG Yifan         assert_eq!(analyze("@@repo//foo")?.repo_name(), Some("repo"));
355*d4726bddSHONG Yifan         assert_eq!(analyze("@//foo")?.repo_name(), Some(""));
356*d4726bddSHONG Yifan         assert_eq!(analyze("//foo")?.repo_name(), None);
357*d4726bddSHONG Yifan         assert_eq!(
358*d4726bddSHONG Yifan             analyze("foo"),
359*d4726bddSHONG Yifan             Err(LabelError(
360*d4726bddSHONG Yifan                 "foo must be a legal label; relative packages are not permitted.".to_string()
361*d4726bddSHONG Yifan             ))
362*d4726bddSHONG Yifan         );
363*d4726bddSHONG Yifan 
364*d4726bddSHONG Yifan         assert_eq!(
365*d4726bddSHONG Yifan             analyze("@@@repo//foo"),
366*d4726bddSHONG Yifan             Err(LabelError(
367*d4726bddSHONG Yifan                 "@@@repo//foo must be a legal label; Unexpected number of leading `@`.".to_owned()
368*d4726bddSHONG Yifan             ))
369*d4726bddSHONG Yifan         );
370*d4726bddSHONG Yifan 
371*d4726bddSHONG Yifan         assert_eq!(
372*d4726bddSHONG Yifan             analyze("@@@//foo:bar"),
373*d4726bddSHONG Yifan             Err(LabelError(
374*d4726bddSHONG Yifan                 "@@@//foo:bar must be a legal label; Unexpected number of leading `@`.".to_owned()
375*d4726bddSHONG Yifan             ))
376*d4726bddSHONG Yifan         );
377*d4726bddSHONG Yifan 
378*d4726bddSHONG Yifan         assert_eq!(
379*d4726bddSHONG Yifan             analyze("@foo:bar"),
380*d4726bddSHONG Yifan             Err(LabelError(
381*d4726bddSHONG Yifan                 "@foo:bar must be a legal label; workspace names may contain only A-Z, a-z, 0-9, '-', '_', '.', and '~'.".to_string()
382*d4726bddSHONG Yifan             ))
383*d4726bddSHONG Yifan         );
384*d4726bddSHONG Yifan 
385*d4726bddSHONG Yifan         assert_eq!(
386*d4726bddSHONG Yifan             analyze("@AZab0123456789_-.//:foo")?.repo_name(),
387*d4726bddSHONG Yifan             Some("AZab0123456789_-.")
388*d4726bddSHONG Yifan         );
389*d4726bddSHONG Yifan         assert_eq!(
390*d4726bddSHONG Yifan             analyze("@42//:baz"),
391*d4726bddSHONG Yifan             Err(LabelError(
392*d4726bddSHONG Yifan                 "@42//:baz must be a legal label; workspace names must \
393*d4726bddSHONG Yifan             start with a letter."
394*d4726bddSHONG Yifan                     .to_string()
395*d4726bddSHONG Yifan             ))
396*d4726bddSHONG Yifan         );
397*d4726bddSHONG Yifan         assert_eq!(
398*d4726bddSHONG Yifan             analyze("@foo#//:baz"),
399*d4726bddSHONG Yifan             Err(LabelError(
400*d4726bddSHONG Yifan                 "@foo#//:baz must be a legal label; workspace names \
401*d4726bddSHONG Yifan             may contain only A-Z, a-z, 0-9, '-', '_', '.', and '~'."
402*d4726bddSHONG Yifan                     .to_string()
403*d4726bddSHONG Yifan             ))
404*d4726bddSHONG Yifan         );
405*d4726bddSHONG Yifan         assert_eq!(
406*d4726bddSHONG Yifan             analyze("@@//foo/bar"),
407*d4726bddSHONG Yifan             Err(LabelError(
408*d4726bddSHONG Yifan                 "@@//foo/bar must be a legal label; main repository labels are only represented by a single `@`."
409*d4726bddSHONG Yifan                     .to_string()
410*d4726bddSHONG Yifan             ))
411*d4726bddSHONG Yifan         );
412*d4726bddSHONG Yifan         assert_eq!(
413*d4726bddSHONG Yifan             analyze("@@//:foo"),
414*d4726bddSHONG Yifan             Err(LabelError(
415*d4726bddSHONG Yifan                 "@@//:foo must be a legal label; main repository labels are only represented by a single `@`."
416*d4726bddSHONG Yifan                     .to_string()
417*d4726bddSHONG Yifan             ))
418*d4726bddSHONG Yifan         );
419*d4726bddSHONG Yifan         assert_eq!(
420*d4726bddSHONG Yifan             analyze("@@//foo"),
421*d4726bddSHONG Yifan             Err(LabelError(
422*d4726bddSHONG Yifan                 "@@//foo must be a legal label; main repository labels are only represented by a single `@`."
423*d4726bddSHONG Yifan                     .to_string()
424*d4726bddSHONG Yifan             ))
425*d4726bddSHONG Yifan         );
426*d4726bddSHONG Yifan 
427*d4726bddSHONG Yifan         assert_eq!(
428*d4726bddSHONG Yifan             analyze("@@"),
429*d4726bddSHONG Yifan             Err(LabelError(
430*d4726bddSHONG Yifan                 "@@ must be a legal label; main repository labels are only represented by a single `@`.".to_string()
431*d4726bddSHONG Yifan             )),
432*d4726bddSHONG Yifan         );
433*d4726bddSHONG Yifan 
434*d4726bddSHONG Yifan         Ok(())
435*d4726bddSHONG Yifan     }
436*d4726bddSHONG Yifan 
437*d4726bddSHONG Yifan     #[test]
test_package_name_parsing() -> Result<()>438*d4726bddSHONG Yifan     fn test_package_name_parsing() -> Result<()> {
439*d4726bddSHONG Yifan         assert_eq!(analyze("//:baz/qux")?.package(), Some(""));
440*d4726bddSHONG Yifan         assert_eq!(analyze(":baz/qux")?.package(), None);
441*d4726bddSHONG Yifan 
442*d4726bddSHONG Yifan         assert_eq!(analyze("//foo:baz/qux")?.package(), Some("foo"));
443*d4726bddSHONG Yifan         assert_eq!(analyze("//foo/bar:baz/qux")?.package(), Some("foo/bar"));
444*d4726bddSHONG Yifan         assert_eq!(
445*d4726bddSHONG Yifan             analyze("foo:baz/qux"),
446*d4726bddSHONG Yifan             Err(LabelError(
447*d4726bddSHONG Yifan                 "foo:baz/qux must be a legal label; relative packages are not permitted."
448*d4726bddSHONG Yifan                     .to_string()
449*d4726bddSHONG Yifan             ))
450*d4726bddSHONG Yifan         );
451*d4726bddSHONG Yifan         assert_eq!(
452*d4726bddSHONG Yifan             analyze("foo/bar:baz/qux"),
453*d4726bddSHONG Yifan             Err(LabelError(
454*d4726bddSHONG Yifan                 "foo/bar:baz/qux must be a legal label; relative packages are not permitted."
455*d4726bddSHONG Yifan                     .to_string()
456*d4726bddSHONG Yifan             ))
457*d4726bddSHONG Yifan         );
458*d4726bddSHONG Yifan 
459*d4726bddSHONG Yifan         assert_eq!(analyze("//foo")?.package(), Some("foo"));
460*d4726bddSHONG Yifan 
461*d4726bddSHONG Yifan         assert_eq!(
462*d4726bddSHONG Yifan             analyze("foo//bar"),
463*d4726bddSHONG Yifan             Err(LabelError(
464*d4726bddSHONG Yifan                 "foo//bar must be a legal label; '//' cannot appear in the middle of the label."
465*d4726bddSHONG Yifan                     .to_string()
466*d4726bddSHONG Yifan             ))
467*d4726bddSHONG Yifan         );
468*d4726bddSHONG Yifan         assert_eq!(
469*d4726bddSHONG Yifan             analyze("//foo//bar"),
470*d4726bddSHONG Yifan             Err(LabelError(
471*d4726bddSHONG Yifan                 "//foo//bar must be a legal label; '//' cannot appear in the middle of the label."
472*d4726bddSHONG Yifan                     .to_string()
473*d4726bddSHONG Yifan             ))
474*d4726bddSHONG Yifan         );
475*d4726bddSHONG Yifan         assert_eq!(
476*d4726bddSHONG Yifan             analyze("foo//bar:baz"),
477*d4726bddSHONG Yifan             Err(LabelError(
478*d4726bddSHONG Yifan                 "foo//bar:baz must be a legal label; '//' cannot appear in the middle of the label."
479*d4726bddSHONG Yifan                     .to_string()
480*d4726bddSHONG Yifan             ))
481*d4726bddSHONG Yifan         );
482*d4726bddSHONG Yifan         assert_eq!(
483*d4726bddSHONG Yifan             analyze("//foo//bar:baz"),
484*d4726bddSHONG Yifan             Err(LabelError(
485*d4726bddSHONG Yifan                 "//foo//bar:baz must be a legal label; '//' cannot appear in the middle of the label."
486*d4726bddSHONG Yifan                     .to_string()
487*d4726bddSHONG Yifan             ))
488*d4726bddSHONG Yifan         );
489*d4726bddSHONG Yifan 
490*d4726bddSHONG Yifan         assert_eq!(
491*d4726bddSHONG Yifan             analyze("//azAZ09/-. $()_:baz")?.package(),
492*d4726bddSHONG Yifan             Some("azAZ09/-. $()_")
493*d4726bddSHONG Yifan         );
494*d4726bddSHONG Yifan         assert_eq!(
495*d4726bddSHONG Yifan             analyze("//bar#:baz"),
496*d4726bddSHONG Yifan             Err(LabelError(
497*d4726bddSHONG Yifan                 "//bar#:baz must be a legal label; package names may contain only A-Z, \
498*d4726bddSHONG Yifan                 a-z, 0-9, '/', '-', '.', ' ', '$', '(', ')', '_', and '+'."
499*d4726bddSHONG Yifan                     .to_string()
500*d4726bddSHONG Yifan             ))
501*d4726bddSHONG Yifan         );
502*d4726bddSHONG Yifan         assert_eq!(
503*d4726bddSHONG Yifan             analyze("//bar/:baz"),
504*d4726bddSHONG Yifan             Err(LabelError(
505*d4726bddSHONG Yifan                 "//bar/:baz must be a legal label; package names may not end with '/'.".to_string()
506*d4726bddSHONG Yifan             ))
507*d4726bddSHONG Yifan         );
508*d4726bddSHONG Yifan 
509*d4726bddSHONG Yifan         assert_eq!(analyze("@repo//foo/bar")?.package(), Some("foo/bar"));
510*d4726bddSHONG Yifan         assert_eq!(analyze("//foo/bar")?.package(), Some("foo/bar"));
511*d4726bddSHONG Yifan         assert_eq!(
512*d4726bddSHONG Yifan             analyze("foo/bar"),
513*d4726bddSHONG Yifan             Err(LabelError(
514*d4726bddSHONG Yifan                 "foo/bar must be a legal label; relative packages are not permitted.".to_string()
515*d4726bddSHONG Yifan             ))
516*d4726bddSHONG Yifan         );
517*d4726bddSHONG Yifan 
518*d4726bddSHONG Yifan         assert_eq!(analyze("@repo//foo")?.package(), Some("foo"));
519*d4726bddSHONG Yifan         assert_eq!(analyze("//foo")?.package(), Some("foo"));
520*d4726bddSHONG Yifan         assert_eq!(
521*d4726bddSHONG Yifan             analyze("foo"),
522*d4726bddSHONG Yifan             Err(LabelError(
523*d4726bddSHONG Yifan                 "foo must be a legal label; relative packages are not permitted.".to_string()
524*d4726bddSHONG Yifan             ))
525*d4726bddSHONG Yifan         );
526*d4726bddSHONG Yifan 
527*d4726bddSHONG Yifan         Ok(())
528*d4726bddSHONG Yifan     }
529*d4726bddSHONG Yifan 
530*d4726bddSHONG Yifan     #[test]
test_name_parsing() -> Result<()>531*d4726bddSHONG Yifan     fn test_name_parsing() -> Result<()> {
532*d4726bddSHONG Yifan         assert_eq!(analyze("//foo:baz")?.name(), "baz");
533*d4726bddSHONG Yifan         assert_eq!(analyze("//foo:baz/qux")?.name(), "baz/qux");
534*d4726bddSHONG Yifan         assert_eq!(analyze(":baz/qux")?.name(), "baz/qux");
535*d4726bddSHONG Yifan 
536*d4726bddSHONG Yifan         assert_eq!(
537*d4726bddSHONG Yifan             analyze("::baz/qux"),
538*d4726bddSHONG Yifan             Err(LabelError(
539*d4726bddSHONG Yifan                 "::baz/qux must be a legal label; target names may not contain with ':'."
540*d4726bddSHONG Yifan                     .to_string()
541*d4726bddSHONG Yifan             ))
542*d4726bddSHONG Yifan         );
543*d4726bddSHONG Yifan 
544*d4726bddSHONG Yifan         assert_eq!(
545*d4726bddSHONG Yifan             analyze("//bar:"),
546*d4726bddSHONG Yifan             Err(LabelError(
547*d4726bddSHONG Yifan                 "//bar: must be a legal label; empty target name.".to_string()
548*d4726bddSHONG Yifan             ))
549*d4726bddSHONG Yifan         );
550*d4726bddSHONG Yifan         assert_eq!(analyze("//foo")?.name(), "foo");
551*d4726bddSHONG Yifan 
552*d4726bddSHONG Yifan         assert_eq!(
553*d4726bddSHONG Yifan             analyze("//bar:/foo"),
554*d4726bddSHONG Yifan             Err(LabelError(
555*d4726bddSHONG Yifan                 "//bar:/foo must be a legal label; target names may not start with '/'."
556*d4726bddSHONG Yifan                     .to_string()
557*d4726bddSHONG Yifan             ))
558*d4726bddSHONG Yifan         );
559*d4726bddSHONG Yifan 
560*d4726bddSHONG Yifan         assert_eq!(analyze("@repo//foo/bar")?.name(), "bar");
561*d4726bddSHONG Yifan         assert_eq!(analyze("//foo/bar")?.name(), "bar");
562*d4726bddSHONG Yifan         assert_eq!(
563*d4726bddSHONG Yifan             analyze("foo/bar"),
564*d4726bddSHONG Yifan             Err(LabelError(
565*d4726bddSHONG Yifan                 "foo/bar must be a legal label; relative packages are not permitted.".to_string()
566*d4726bddSHONG Yifan             ))
567*d4726bddSHONG Yifan         );
568*d4726bddSHONG Yifan 
569*d4726bddSHONG Yifan         assert_eq!(analyze("@repo//foo")?.name(), "foo");
570*d4726bddSHONG Yifan         assert_eq!(analyze("//foo")?.name(), "foo");
571*d4726bddSHONG Yifan         assert_eq!(
572*d4726bddSHONG Yifan             analyze("foo"),
573*d4726bddSHONG Yifan             Err(LabelError(
574*d4726bddSHONG Yifan                 "foo must be a legal label; relative packages are not permitted.".to_string()
575*d4726bddSHONG Yifan             ))
576*d4726bddSHONG Yifan         );
577*d4726bddSHONG Yifan 
578*d4726bddSHONG Yifan         assert_eq!(
579*d4726bddSHONG Yifan             analyze("@repo")?,
580*d4726bddSHONG Yifan             Label::Absolute {
581*d4726bddSHONG Yifan                 repository: Some(Repository::Apparent("@repo")),
582*d4726bddSHONG Yifan                 package_name: "",
583*d4726bddSHONG Yifan                 target_name: "repo",
584*d4726bddSHONG Yifan             },
585*d4726bddSHONG Yifan         );
586*d4726bddSHONG Yifan 
587*d4726bddSHONG Yifan         assert_eq!(
588*d4726bddSHONG Yifan             analyze("@"),
589*d4726bddSHONG Yifan             Err(LabelError(
590*d4726bddSHONG Yifan                 "@ must be a legal label; invalid target name: empty target name".to_string()
591*d4726bddSHONG Yifan             )),
592*d4726bddSHONG Yifan         );
593*d4726bddSHONG Yifan 
594*d4726bddSHONG Yifan         Ok(())
595*d4726bddSHONG Yifan     }
596*d4726bddSHONG Yifan }
597