scratchstack_aws_principal/
service.rs1use {
2 crate::{utils::validate_dns, PrincipalError},
3 scratchstack_arn::utils::validate_region,
4 std::fmt::{Display, Formatter, Result as FmtResult},
5};
6
7#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
11pub struct Service {
12 service_name: String,
14
15 region: Option<String>,
17
18 dns_suffix: String,
20}
21
22impl Service {
23 pub fn new(service_name: &str, region: Option<String>, dns_suffix: &str) -> Result<Self, PrincipalError> {
48 validate_dns(service_name, 32, PrincipalError::InvalidService)?;
49 validate_dns(dns_suffix, 128, PrincipalError::InvalidService)?;
50
51 let region = match region {
52 None => None,
53 Some(region) => {
54 validate_region(region.as_str())?;
55 Some(region)
56 }
57 };
58
59 Ok(Self {
60 service_name: service_name.to_string(),
61 region,
62 dns_suffix: dns_suffix.into(),
63 })
64 }
65
66 #[inline]
68 pub fn service_name(&self) -> &str {
69 &self.service_name
70 }
71
72 #[inline]
74 pub fn region(&self) -> Option<&str> {
75 self.region.as_deref()
76 }
77
78 #[inline]
80 pub fn dns_suffix(&self) -> &str {
81 &self.dns_suffix
82 }
83
84 pub fn regional_dns_name(&self) -> String {
86 match &self.region {
87 None => format!("{}.{}", self.service_name, self.dns_suffix),
88 Some(region) => format!("{}.{}.{}", self.service_name, region, self.dns_suffix),
89 }
90 }
91
92 pub fn global_dns_name(&self) -> String {
94 format!("{}.{}", self.service_name, self.dns_suffix)
95 }
96}
97
98impl Display for Service {
99 fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
100 match &self.region {
101 None => write!(f, "{}.{}", self.service_name, self.dns_suffix),
102 Some(region) => write!(f, "{}.{}.{}", self.service_name, region, self.dns_suffix),
103 }
104 }
105}
106
107#[cfg(test)]
108mod tests {
109 use {
110 super::Service,
111 crate::{PrincipalIdentity, PrincipalSource},
112 std::{
113 collections::hash_map::DefaultHasher,
114 hash::{Hash, Hasher},
115 },
116 };
117
118 #[test]
119 fn check_components() {
120 let s1 = Service::new("s3", None, "amazonaws.com").unwrap();
121 let s2 = Service::new("s3", Some("us-east-1".into()), "amazonaws.com").unwrap();
122
123 assert_eq!(s1.service_name(), "s3");
124 assert_eq!(s1.region(), None);
125 assert_eq!(s1.dns_suffix(), "amazonaws.com");
126
127 assert_eq!(s2.service_name(), "s3");
128 assert_eq!(s2.region(), Some("us-east-1"));
129 assert_eq!(s2.dns_suffix(), "amazonaws.com");
130
131 let p = PrincipalIdentity::from(s1);
132 let source = p.source();
133 assert_eq!(source, PrincipalSource::Service);
134 assert_eq!(source.to_string(), "Service".to_string());
135 }
136
137 #[test]
138 fn check_derived() {
139 let s1a = Service::new("s3", None, "amazonaws.com").unwrap();
140 let s1b = Service::new("s3", None, "amazonaws.com").unwrap();
141 let s2 = Service::new("s3", None, "amazonaws.net").unwrap();
142 let s3 = Service::new("s3", Some("us-east-1".into()), "amazonaws.net").unwrap();
143 let s4 = Service::new("s3", Some("us-east-2".into()), "amazonaws.net").unwrap();
144 let s5 = Service::new("s4", None, "amazonaws.net").unwrap();
145 let s6 = Service::new("s4", Some("us-east-1".into()), "amazonaws.net").unwrap();
146
147 assert_eq!(s1a, s1b);
148 assert_ne!(s1a, s2);
149 assert_eq!(s1a, s1a);
150 assert_ne!(s1a, s3);
151 assert_ne!(s2, s3);
152 assert_ne!(s3, s4);
153 assert_ne!(s4, s5);
154 assert_ne!(s5, s6);
155
156 let mut h1a = DefaultHasher::new();
158 let mut h1b = DefaultHasher::new();
159 let mut h2 = DefaultHasher::new();
160 s1a.hash(&mut h1a);
161 s1b.hash(&mut h1b);
162 s2.hash(&mut h2);
163 let hash1a = h1a.finish();
164 let hash1b = h1b.finish();
165 let hash2 = h2.finish();
166 assert_eq!(hash1a, hash1b);
167 assert_ne!(hash1a, hash2);
168
169 assert!(s1a <= s1b);
171 assert!(s1a < s2);
172 assert!(s2 > s1a);
173 assert!(s1a < s3);
174 assert!(s2 < s3);
175 assert!(s1a < s4);
176 assert!(s2 < s4);
177 assert!(s3 < s4);
178 assert!(s1a < s5);
179 assert!(s2 < s5);
180 assert!(s3 < s5);
181 assert!(s4 < s5);
182 assert!(s1a < s6);
183 assert!(s2 < s6);
184 assert!(s3 < s6);
185 assert!(s4 < s6);
186 assert!(s5 < s6);
187 assert_eq!(s1a.clone().max(s2.clone()), s2);
188 assert_eq!(s1a.clone().min(s3), s1a);
189
190 assert_eq!(s1a.to_string(), "s3.amazonaws.com");
192 assert_eq!(s6.to_string(), "s4.us-east-1.amazonaws.net");
193
194 let _ = format!("{s1a:?}");
196 }
197
198 #[test]
199 fn check_valid_services() {
200 let s1a = Service::new("service-name", None, "amazonaws.com").unwrap();
201 let s1b = Service::new("service-name", None, "amazonaws.com").unwrap();
202 let s2 = Service::new("service-name2", None, "amazonaws.com").unwrap();
203 let s3 = Service::new("service-name", Some("us-east-1".to_string()), "amazonaws.com").unwrap();
204 let s4 = Service::new("aservice-name-with-32-characters", None, "amazonaws.com").unwrap();
205
206 assert_eq!(s1a, s1b);
207 assert_ne!(s1a, s2);
208 assert_eq!(s1a, s1a.clone());
209
210 assert_eq!(s1a.to_string(), "service-name.amazonaws.com");
211 assert_eq!(s2.to_string(), "service-name2.amazonaws.com");
212 assert_eq!(s3.to_string(), "service-name.us-east-1.amazonaws.com");
213 assert_eq!(s4.to_string(), "aservice-name-with-32-characters.amazonaws.com");
214
215 assert_eq!(s1a.regional_dns_name(), "service-name.amazonaws.com");
216 assert_eq!(s1a.global_dns_name(), "service-name.amazonaws.com");
217
218 assert_eq!(s3.regional_dns_name(), "service-name.us-east-1.amazonaws.com");
219 assert_eq!(s3.global_dns_name(), "service-name.amazonaws.com");
220 }
221
222 #[test]
223 fn check_invalid_services() {
224 assert_eq!(
225 Service::new("service name", None, "amazonaws.com",).unwrap_err().to_string(),
226 r#"Invalid service name: "service name""#
227 );
228
229 assert_eq!(
230 Service::new("service name", Some("us-east-1".to_string()), "amazonaws.com",).unwrap_err().to_string(),
231 r#"Invalid service name: "service name""#
232 );
233
234 assert_eq!(
235 Service::new("service!name", None, "amazonaws.com",).unwrap_err().to_string(),
236 r#"Invalid service name: "service!name""#
237 );
238
239 assert_eq!(
240 Service::new("service!name", Some("us-east-1".to_string()), "amazonaws.com",).unwrap_err().to_string(),
241 r#"Invalid service name: "service!name""#
242 );
243
244 assert_eq!(Service::new("", None, "amazonaws.com",).unwrap_err().to_string(), r#"Invalid service name: """#);
245
246 assert_eq!(
247 Service::new("a-service-name-with-33-characters", None, "amazonaws.com",).unwrap_err().to_string(),
248 r#"Invalid service name: "a-service-name-with-33-characters""#
249 );
250
251 assert_eq!(
252 Service::new("service-name", Some("us-east-".to_string()), "amazonaws.com",).unwrap_err().to_string(),
253 r#"Invalid region: "us-east-""#
254 );
255
256 assert_eq!(
257 Service::new("service-name", Some("us-east-1".to_string()), "amazonaws..com",).unwrap_err().to_string(),
258 r#"Invalid service name: "amazonaws..com""#
259 );
260 }
261}
262