ocipkg/distribution/
name.rs

1use anyhow::{bail, Result};
2use regex::Regex;
3use std::fmt;
4
5/// Namespace of the repository
6///
7/// The name must satisfy the following regular expression in [OCI distribution spec 1.1.0](https://github.com/opencontainers/distribution-spec/blob/v1.1.0/spec.md):
8///
9/// ```regex
10/// [a-z0-9]+((\.|_|__|-+)[a-z0-9]+)*(\/[a-z0-9]+((\.|_|__|-+)[a-z0-9]+)*)*
11/// ```
12#[derive(Debug, Clone, PartialEq, Eq, Hash)]
13pub struct Name(String);
14
15impl std::ops::Deref for Name {
16    type Target = str;
17    fn deref(&self) -> &str {
18        &self.0
19    }
20}
21
22impl fmt::Display for Name {
23    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
24        write!(f, "{}", self.0)
25    }
26}
27
28lazy_static::lazy_static! {
29    static ref NAME_RE: Regex = Regex::new(r"^[a-z0-9]+((\.|_|__|-+)[a-z0-9]+)*(\/[a-z0-9]+((\.|_|__|-+)[a-z0-9]+)*)*$").unwrap();
30}
31
32impl Name {
33    pub fn as_str(&self) -> &str {
34        &self.0
35    }
36
37    pub fn new(name: &str) -> Result<Self> {
38        if NAME_RE.is_match(name) {
39            Ok(Name(name.to_string()))
40        } else {
41            bail!("Invalid name: {name}");
42        }
43    }
44}
45
46#[cfg(test)]
47mod tests {
48    use super::*;
49
50    #[test]
51    fn name() {
52        assert_eq!(Name::new("ghcr.io").unwrap().as_str(), "ghcr.io");
53        // Head must be alphanum
54        assert!(Name::new("_ghcr.io").is_err());
55        assert!(Name::new("/ghcr.io").is_err());
56
57        // Capital letter is not allowed
58        assert!(Name::new("ghcr.io/Termoshtt").is_err());
59    }
60}