use {
crate::{utils::validate_dns, PrincipalError},
scratchstack_arn::utils::validate_region,
std::fmt::{Display, Formatter, Result as FmtResult},
};
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct Service {
service_name: String,
region: Option<String>,
dns_suffix: String,
}
impl Service {
pub fn new(service_name: &str, region: Option<String>, dns_suffix: &str) -> Result<Self, PrincipalError> {
validate_dns(service_name, 32, PrincipalError::InvalidService)?;
validate_dns(dns_suffix, 128, PrincipalError::InvalidService)?;
let region = match region {
None => None,
Some(region) => {
validate_region(region.as_str())?;
Some(region)
}
};
Ok(Self {
service_name: service_name.to_string(),
region,
dns_suffix: dns_suffix.into(),
})
}
#[inline]
pub fn service_name(&self) -> &str {
&self.service_name
}
#[inline]
pub fn region(&self) -> Option<&str> {
self.region.as_deref()
}
#[inline]
pub fn dns_suffix(&self) -> &str {
&self.dns_suffix
}
pub fn regional_dns_name(&self) -> String {
match &self.region {
None => format!("{}.{}", self.service_name, self.dns_suffix),
Some(region) => format!("{}.{}.{}", self.service_name, region, self.dns_suffix),
}
}
pub fn global_dns_name(&self) -> String {
format!("{}.{}", self.service_name, self.dns_suffix)
}
}
impl Display for Service {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
match &self.region {
None => write!(f, "{}.{}", self.service_name, self.dns_suffix),
Some(region) => write!(f, "{}.{}.{}", self.service_name, region, self.dns_suffix),
}
}
}
#[cfg(test)]
mod tests {
use {
super::Service,
crate::{PrincipalIdentity, PrincipalSource},
std::{
collections::hash_map::DefaultHasher,
hash::{Hash, Hasher},
},
};
#[test]
fn check_components() {
let s1 = Service::new("s3", None, "amazonaws.com").unwrap();
let s2 = Service::new("s3", Some("us-east-1".into()), "amazonaws.com").unwrap();
assert_eq!(s1.service_name(), "s3");
assert_eq!(s1.region(), None);
assert_eq!(s1.dns_suffix(), "amazonaws.com");
assert_eq!(s2.service_name(), "s3");
assert_eq!(s2.region(), Some("us-east-1"));
assert_eq!(s2.dns_suffix(), "amazonaws.com");
let p = PrincipalIdentity::from(s1);
let source = p.source();
assert_eq!(source, PrincipalSource::Service);
assert_eq!(source.to_string(), "Service".to_string());
}
#[test]
fn check_derived() {
let s1a = Service::new("s3", None, "amazonaws.com").unwrap();
let s1b = Service::new("s3", None, "amazonaws.com").unwrap();
let s2 = Service::new("s3", None, "amazonaws.net").unwrap();
let s3 = Service::new("s3", Some("us-east-1".into()), "amazonaws.net").unwrap();
let s4 = Service::new("s3", Some("us-east-2".into()), "amazonaws.net").unwrap();
let s5 = Service::new("s4", None, "amazonaws.net").unwrap();
let s6 = Service::new("s4", Some("us-east-1".into()), "amazonaws.net").unwrap();
assert_eq!(s1a, s1b);
assert_ne!(s1a, s2);
assert_eq!(s1a, s1a);
assert_ne!(s1a, s3);
assert_ne!(s2, s3);
assert_ne!(s3, s4);
assert_ne!(s4, s5);
assert_ne!(s5, s6);
let mut h1a = DefaultHasher::new();
let mut h1b = DefaultHasher::new();
let mut h2 = DefaultHasher::new();
s1a.hash(&mut h1a);
s1b.hash(&mut h1b);
s2.hash(&mut h2);
let hash1a = h1a.finish();
let hash1b = h1b.finish();
let hash2 = h2.finish();
assert_eq!(hash1a, hash1b);
assert_ne!(hash1a, hash2);
assert!(s1a <= s1b);
assert!(s1a < s2);
assert!(s2 > s1a);
assert!(s1a < s3);
assert!(s2 < s3);
assert!(s1a < s4);
assert!(s2 < s4);
assert!(s3 < s4);
assert!(s1a < s5);
assert!(s2 < s5);
assert!(s3 < s5);
assert!(s4 < s5);
assert!(s1a < s6);
assert!(s2 < s6);
assert!(s3 < s6);
assert!(s4 < s6);
assert!(s5 < s6);
assert_eq!(s1a.clone().max(s2.clone()), s2);
assert_eq!(s1a.clone().min(s3), s1a);
assert_eq!(s1a.to_string(), "s3.amazonaws.com");
assert_eq!(s6.to_string(), "s4.us-east-1.amazonaws.net");
let _ = format!("{s1a:?}");
}
#[test]
fn check_valid_services() {
let s1a = Service::new("service-name", None, "amazonaws.com").unwrap();
let s1b = Service::new("service-name", None, "amazonaws.com").unwrap();
let s2 = Service::new("service-name2", None, "amazonaws.com").unwrap();
let s3 = Service::new("service-name", Some("us-east-1".to_string()), "amazonaws.com").unwrap();
let s4 = Service::new("aservice-name-with-32-characters", None, "amazonaws.com").unwrap();
assert_eq!(s1a, s1b);
assert_ne!(s1a, s2);
assert_eq!(s1a, s1a.clone());
assert_eq!(s1a.to_string(), "service-name.amazonaws.com");
assert_eq!(s2.to_string(), "service-name2.amazonaws.com");
assert_eq!(s3.to_string(), "service-name.us-east-1.amazonaws.com");
assert_eq!(s4.to_string(), "aservice-name-with-32-characters.amazonaws.com");
assert_eq!(s1a.regional_dns_name(), "service-name.amazonaws.com");
assert_eq!(s1a.global_dns_name(), "service-name.amazonaws.com");
assert_eq!(s3.regional_dns_name(), "service-name.us-east-1.amazonaws.com");
assert_eq!(s3.global_dns_name(), "service-name.amazonaws.com");
}
#[test]
fn check_invalid_services() {
assert_eq!(
Service::new("service name", None, "amazonaws.com",).unwrap_err().to_string(),
r#"Invalid service name: "service name""#
);
assert_eq!(
Service::new("service name", Some("us-east-1".to_string()), "amazonaws.com",).unwrap_err().to_string(),
r#"Invalid service name: "service name""#
);
assert_eq!(
Service::new("service!name", None, "amazonaws.com",).unwrap_err().to_string(),
r#"Invalid service name: "service!name""#
);
assert_eq!(
Service::new("service!name", Some("us-east-1".to_string()), "amazonaws.com",).unwrap_err().to_string(),
r#"Invalid service name: "service!name""#
);
assert_eq!(Service::new("", None, "amazonaws.com",).unwrap_err().to_string(), r#"Invalid service name: """#);
assert_eq!(
Service::new("a-service-name-with-33-characters", None, "amazonaws.com",).unwrap_err().to_string(),
r#"Invalid service name: "a-service-name-with-33-characters""#
);
assert_eq!(
Service::new("service-name", Some("us-east-".to_string()), "amazonaws.com",).unwrap_err().to_string(),
r#"Invalid region: "us-east-""#
);
assert_eq!(
Service::new("service-name", Some("us-east-1".to_string()), "amazonaws..com",).unwrap_err().to_string(),
r#"Invalid service name: "amazonaws..com""#
);
}
}