git_ref_format_core/
check.rs1use thiserror::Error;
2
3pub struct Options {
4 pub allow_onelevel: bool,
6 pub allow_pattern: bool,
8}
9
10#[derive(Debug, PartialEq, Eq, Error)]
11#[non_exhaustive]
12pub enum Error {
13 #[error("empty input")]
14 Empty,
15 #[error("lone '@' character")]
16 LoneAt,
17 #[error("consecutive or trailing slash")]
18 Slash,
19 #[error("ends with '.lock'")]
20 DotLock,
21 #[error("consecutive dots ('..')")]
22 DotDot,
23 #[error("at-open-brace ('@{{')")]
24 AtOpenBrace,
25 #[error("invalid character {0:?}")]
26 InvalidChar(char),
27 #[error("component starts with '.'")]
28 StartsDot,
29 #[error("component ends with '.'")]
30 EndsDot,
31 #[error("control character")]
32 Control,
33 #[error("whitespace")]
34 Space,
35 #[error("must contain at most one '*'")]
36 Pattern,
37 #[error("must contain at least one '/'")]
38 OneLevel,
39}
40
41pub fn ref_format(opts: Options, s: &str) -> Result<(), Error> {
43 match s {
44 "" => Err(Error::Empty),
45 "@" => Err(Error::LoneAt),
46 "." => Err(Error::StartsDot),
47 _ => {
48 let mut globs = 0usize;
49 let mut parts = 0usize;
50
51 for x in s.split('/') {
52 if x.is_empty() {
53 return Err(Error::Slash);
54 }
55
56 parts += 1;
57
58 if x.ends_with(".lock") {
59 return Err(Error::DotLock);
60 }
61
62 let last_char = x.chars().count() - 1;
63 for (i, y) in x.chars().zip(x.chars().cycle().skip(1)).enumerate() {
64 match y {
65 ('.', '.') => return Err(Error::DotDot),
66 ('@', '{') => return Err(Error::AtOpenBrace),
67
68 ('\0', _) => return Err(Error::InvalidChar('\0')),
69 ('\\', _) => return Err(Error::InvalidChar('\\')),
70 ('~', _) => return Err(Error::InvalidChar('~')),
71 ('^', _) => return Err(Error::InvalidChar('^')),
72 (':', _) => return Err(Error::InvalidChar(':')),
73 ('?', _) => return Err(Error::InvalidChar('?')),
74 ('[', _) => return Err(Error::InvalidChar('[')),
75
76 ('*', _) => globs += 1,
77
78 ('.', _) if i == 0 => return Err(Error::StartsDot),
79 ('.', _) if i == last_char => return Err(Error::EndsDot),
80
81 (' ', _) => return Err(Error::Space),
82
83 (z, _) if z.is_ascii_control() => return Err(Error::Control),
84
85 _ => continue,
86 }
87 }
88 }
89
90 if parts < 2 && !opts.allow_onelevel {
91 Err(Error::OneLevel)
92 } else if globs > 1 && opts.allow_pattern {
93 Err(Error::Pattern)
94 } else if globs > 0 && !opts.allow_pattern {
95 Err(Error::InvalidChar('*'))
96 } else {
97 Ok(())
98 }
99 }
100 }
101}