1 | use std::{fmt::Display, str::FromStr}; |
2 | |
3 | use error::Error; |
4 | use oci_spec::image::Digest; |
5 | use regex::Regex; |
6 | use relative_path::RelativePath; |
7 | |
8 | pub mod address; |
9 | pub mod error; |
10 | pub mod oci_interface; |
11 | pub mod storage; |
12 | |
13 | #[cfg(feature = "axum")] |
14 | pub mod axum; |
15 | |
16 | #[cfg(feature = "storage-fs")] |
17 | pub mod storage_fs; |
18 | |
19 | const NAME_REGEXP_MATCH: &str = |
20 | r"[a-z0-9]+((\.|_|__|-+)[a-z0-9]+)*(\/[a-z0-9]+((\.|_|__|-+)[a-z0-9]+)*)*"; |
21 | |
22 | #[derive(Clone, Debug)] |
23 | pub enum TagOrDigest { |
24 | Tag(String), |
25 | Digest(Digest) |
26 | } |
27 | |
28 | // TODO: Consider 255 char namespace limit - hostname length per spec docs |
29 | #[derive(Clone)] |
30 | pub struct Namespace(String); |
31 | |
32 | impl Namespace { |
33 | pub fn path(&self) -> &RelativePath { |
34 | RelativePath::new(&self.0) |
35 | } |
36 | } |
37 | |
38 | impl FromStr for Namespace { |
39 | type Err = Error; |
40 | |
41 | fn from_str(s: &str) -> Result<Self, Self::Err> { |
42 | let regexp = Regex::new(NAME_REGEXP_MATCH).unwrap(); |
43 | if regexp.is_match(s) { |
44 | Ok(Namespace(s.to_string())) |
45 | } else { |
46 | Err(Error::Namespace(s.to_string())) |
47 | } |
48 | } |
49 | } |
50 | |
51 | impl Display for Namespace { |
52 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { |
53 | write!(f, "{}", self.0) |
54 | } |
55 | } |
56 | |
57 | impl AsRef<str> for Namespace { |
58 | fn as_ref(&self) -> &str { |
59 | &self.0 |
60 | } |
61 | } |
62 | |
63 | #[cfg(test)] |
64 | mod test { |
65 | use super::*; |
66 | |
67 | #[test] |
68 | fn namespace() { |
69 | Namespace::from_str("fuu").unwrap(); |
70 | Namespace::from_str("fuu/bar").unwrap(); |
71 | Namespace::from_str("fuu/bar/baz/").unwrap(); |
72 | Namespace::from_str("fuu/bar/baz/qux").unwrap(); |
73 | } |
74 | } |