gix_validate/
reference.rs

1use bstr::{BStr, BString, ByteSlice};
2
3///
4pub mod name {
5    use bstr::BString;
6    use std::convert::Infallible;
7
8    /// The error used in [name()][super::name()] and [`name_partial()`][super::name_partial()]
9    #[derive(Debug)]
10    #[allow(missing_docs)]
11    #[non_exhaustive]
12    pub enum Error {
13        InvalidByte { byte: BString },
14        StartsWithSlash,
15        RepeatedSlash,
16        RepeatedDot,
17        LockFileSuffix,
18        ReflogPortion,
19        Asterisk,
20        StartsWithDot,
21        EndsWithDot,
22        EndsWithSlash,
23        Empty,
24        SomeLowercase,
25    }
26
27    impl std::fmt::Display for Error {
28        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
29            match self {
30                Error::InvalidByte { byte } => write!(f, "Reference name contains invalid byte: {byte:?}"),
31                Error::StartsWithSlash => write!(f, "Reference name cannot start with a slash"),
32                Error::RepeatedSlash => write!(f, "Reference name cannot contain repeated slashes"),
33                Error::RepeatedDot => write!(f, "Reference name cannot contain repeated dots"),
34                Error::LockFileSuffix => write!(f, "Reference name cannot end with '.lock'"),
35                Error::ReflogPortion => write!(f, "Reference name cannot contain '@{{'"),
36                Error::Asterisk => write!(f, "Reference name cannot contain '*'"),
37                Error::StartsWithDot => write!(f, "Reference name cannot start with a dot"),
38                Error::EndsWithDot => write!(f, "Reference name cannot end with a dot"),
39                Error::EndsWithSlash => write!(f, "Reference name cannot end with a slash"),
40                Error::Empty => write!(f, "Reference name cannot be empty"),
41                Error::SomeLowercase => write!(f, "Standalone references must be all uppercased, like 'HEAD'"),
42            }
43        }
44    }
45
46    impl std::error::Error for Error {}
47
48    impl From<crate::tag::name::Error> for Error {
49        fn from(err: crate::tag::name::Error) -> Self {
50            match err {
51                crate::tag::name::Error::InvalidByte { byte } => Error::InvalidByte { byte },
52                crate::tag::name::Error::StartsWithSlash => Error::StartsWithSlash,
53                crate::tag::name::Error::RepeatedSlash => Error::RepeatedSlash,
54                crate::tag::name::Error::RepeatedDot => Error::RepeatedDot,
55                crate::tag::name::Error::LockFileSuffix => Error::LockFileSuffix,
56                crate::tag::name::Error::ReflogPortion => Error::ReflogPortion,
57                crate::tag::name::Error::Asterisk => Error::Asterisk,
58                crate::tag::name::Error::StartsWithDot => Error::StartsWithDot,
59                crate::tag::name::Error::EndsWithDot => Error::EndsWithDot,
60                crate::tag::name::Error::EndsWithSlash => Error::EndsWithSlash,
61                crate::tag::name::Error::Empty => Error::Empty,
62            }
63        }
64    }
65
66    impl From<Infallible> for Error {
67        fn from(_: Infallible) -> Self {
68            unreachable!("this impl is needed to allow passing a known valid partial path as parameter")
69        }
70    }
71}
72
73/// Validate a reference name running all the tests in the book. This disallows lower-case references like `lower`, but also allows
74/// ones like `HEAD`, and `refs/lower`.
75pub fn name(path: &BStr) -> Result<&BStr, name::Error> {
76    match validate(path, Mode::Complete)? {
77        None => Ok(path),
78        Some(_) => {
79            unreachable!("Without sanitization, there is no chance a sanitized version is returned.")
80        }
81    }
82}
83
84/// Validate a partial reference name. As it is assumed to be partial, names like `some-name` is allowed
85/// even though these would be disallowed with when using [`name()`].
86pub fn name_partial(path: &BStr) -> Result<&BStr, name::Error> {
87    match validate(path, Mode::Partial)? {
88        None => Ok(path),
89        Some(_) => {
90            unreachable!("Without sanitization, there is no chance a sanitized version is returned.")
91        }
92    }
93}
94
95/// The infallible version of [`name_partial()`] which instead of failing, alters `path` and returns it to be a valid
96/// partial name, which would also pass [`name_partial()`].
97///
98/// Note that an empty `path` is replaced with a `-` in order to be valid.
99pub fn name_partial_or_sanitize(path: &BStr) -> BString {
100    validate(path, Mode::PartialSanitize)
101        .expect("BUG: errors cannot happen as any issue is fixed instantly")
102        .expect("we always rebuild the path")
103}
104
105enum Mode {
106    Complete,
107    Partial,
108    /// like Partial, but instead of failing, a sanitized version is returned.
109    PartialSanitize,
110}
111
112fn validate(path: &BStr, mode: Mode) -> Result<Option<BString>, name::Error> {
113    let out = crate::tag::name_inner(
114        path,
115        match mode {
116            Mode::Complete | Mode::Partial => crate::tag::Mode::Validate,
117            Mode::PartialSanitize => crate::tag::Mode::Sanitize,
118        },
119    )?;
120    if let Mode::Complete = mode {
121        let input = out.as_ref().map_or(path, |b| b.as_bstr());
122        let saw_slash = input.find_byte(b'/').is_some();
123        if !saw_slash && !input.iter().all(|c| c.is_ascii_uppercase() || *c == b'_') {
124            return Err(name::Error::SomeLowercase);
125        }
126    }
127    Ok(out)
128}