rectangle_device_sandbox/
types.rs1use regex::Regex;
2use thiserror::Error;
3
4#[derive(Error, Debug)]
5pub enum SandboxError {
6 #[error("the string `{0}` is not a valid image digest")]
7 InvalidDigest(String),
8 #[error("the string `{0}` is not a valid image name")]
9 InvalidImage(String),
10 #[error("unexpected digest `{0}`")]
11 UnexpectedDigest(String),
12}
13
14#[derive(Clone, Debug)]
15pub struct ImageDigest{
16 pub image: Image,
17 pub digest: Digest
18}
19
20impl ImageDigest {
21 pub fn parse(image: &str, digest: &str) -> Result<ImageDigest, SandboxError> {
22 Ok(ImageDigest{
23 image: Image::parse(image)?,
24 digest: Digest::parse(digest)?
25 })
26 }
27}
28
29#[derive(Clone, Debug)]
30pub struct Image {
31 inner: String
32}
33
34impl Image {
35 pub fn parse(s: &str) -> Result<Image, SandboxError> {
36 lazy_static! {
37 static ref RE: Regex = Regex::new(
38 r"(?x)^
40 (?: # Optional domain
41 (?: # First domain component
42 [a-zA-Z0-9] |
43 [a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9]
44 )
45 (?: # Optional additional domain components
46 \.
47 (?:
48 [a-zA-Z0-9] |
49 [a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9]
50 )
51 )*
52 (?: # Optional port number
53 :[0-9]+
54 )?
55 /
56 )?
57 (?: # Main name component
58 [a-z0-9]+
59 (?:
60 (?: [._] | __ | [-]* )
61 [a-z0-9]+
62 )*
63 )
64 (?: # Optional additional name components
65 /
66 [a-z0-9]+
67 (?:
68 (?: [._] | __ | [-]* )
69 [a-z0-9]+
70 )*
71 )*
72 (?: # Optional tag
73 :
74 [\w][\w.-]*
75 )?
76 $").unwrap();
77 }
78 let inner = s.to_owned();
79 if RE.is_match(&inner) {
80 Ok(Image { inner })
81 } else {
82 Err(SandboxError::InvalidImage(inner))
83 }
84 }
85
86 pub fn as_str(&self) -> &str {
87 self.inner.as_str()
88 }
89}
90
91#[derive(Clone, Debug)]
92pub struct Digest {
93 inner: String
94}
95
96impl Digest {
97 pub fn parse(s: &str) -> Result<Digest, SandboxError> {
98 lazy_static! {
99 static ref RE: Regex = Regex::new(
100 r"(?x)^
102 (?: # Optional type
103 [A-Za-z][A-Za-z0-9]*
104 (?:
105 [-_+.][A-Za-z][A-Za-z0-9]*
106 )*
107 :
108 )?
109 [[:xdigit:]]{32,} # At least a 32-digit hex number
110 $").unwrap();
111 }
112 let inner = s.to_owned();
113 if RE.is_match(&inner) {
114 Ok(Digest { inner })
115 } else {
116 Err(SandboxError::InvalidDigest(inner))
117 }
118 }
119
120 pub fn as_str(&self) -> &str {
121 self.inner.as_str()
122 }
123}
124
125#[cfg(test)]
126mod tests {
127 use super::*;
128
129 #[test]
130 fn parse_image_name() {
131 assert!(Image::parse("balls").is_ok());
132 assert!(Image::parse("balls/").is_err());
133 assert!(Image::parse("balls/etc").is_ok());
134 assert!(Image::parse("balls/etc/and/more").is_ok());
135 assert!(Image::parse("b-a-l-l-s").is_ok());
136 assert!(Image::parse("-balls").is_err());
137 assert!(Image::parse("--balls").is_err());
138 assert!(Image::parse("b--alls").is_ok());
139 assert!(Image::parse("balls.io/image/of/my/balls").is_ok());
140 assert!(Image::parse("balls.io/image/of/my/balls:").is_err());
141 assert!(Image::parse("balls.io/image/of/my/balls:?").is_err());
142 assert!(Image::parse("balls.io/image/of/my/balls:0").is_ok());
143 assert!(Image::parse("balls.io/image/of/my/balls:.").is_err());
144 assert!(Image::parse("balls.io/image/of/my/balls:0.0").is_ok());
145 assert!(Image::parse("balls.io/image/of//balls").is_err());
146 assert!(Image::parse(" balls").is_err());
147 assert!(Image::parse("balls ").is_err());
148 assert!(Image::parse("balls:69").is_ok());
149 assert!(Image::parse("balls:6.9").is_ok());
150 assert!(Image::parse("balls:").is_err());
151 assert!(Image::parse("balls.io:69/ball").is_ok());
152 assert!(Image::parse("balls.io:/ball").is_err());
153 }
154
155 #[test]
156 fn parse_digest_name() {
157 assert!(Digest::parse("balls").is_err());
158 assert!(Digest::parse("balls:0123456789abcdef0123456789abcdef").is_ok());
159 assert!(Digest::parse("-balls:0123456789abcdef0123456789abcdef").is_err());
160 assert!(Digest::parse("--balls:0123456789abcdef0123456789abcdef").is_err());
161 assert!(Digest::parse("b_b+b+b+b+b+b.balllllls:0123456789abcdef0123456789abcdef").is_ok());
162 assert!(Digest::parse("b_b+b+b++b+b.balllllls:0123456789abcdef0123456789abcdef").is_err());
163 assert!(Digest::parse("balls:0123456789abcdef0123456789abcdef").is_ok());
164 assert!(Digest::parse("balls:0123456789abcdef0123456789abcdeg").is_err());
165 assert!(Digest::parse("balls:0123456789abcdef0123456789abcdefF").is_ok());
166 assert!(Digest::parse("ball.ball.ball.balls:0123456789abcdef0123456789abcdef").is_ok());
167 assert!(Digest::parse("0123456789abcdef0123456789abcdef").is_ok());
168 assert!(Digest::parse(":0123456789abcdef0123456789abcdef").is_err());
169 assert!(Digest::parse("balls:0123456789abcdef0123456789abcde").is_err());
170 assert!(Digest::parse("b9:0123456789abcdef0123456789abcdef").is_ok());
171 assert!(Digest::parse("b:0123456789abcdef0123456789abcdef").is_ok());
172 assert!(Digest::parse("9:0123456789abcdef0123456789abcdef").is_err());
173 assert!(Digest::parse(" balls:0123456789abcdef0123456789abcdef").is_err());
174 assert!(Digest::parse("balls:0123456789abcdef0123456789abcdef ").is_err());
175 }
176}