use serde::Deserialize;
use std::collections::HashMap;
use thiserror::Error;
use tracing::warn;
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum ServiceAuthorityError {
#[error("could not parse JSON: {}", source)]
Json {
#[from]
source: serde_json::Error,
},
#[error("unknown service {0}")]
ServiceUnknown(String),
}
#[derive(Clone, Debug, Deserialize, Default)]
pub struct ServiceAuthority {
pub services: Vec<Service>,
pub forward: HashMap<String, Vec<String>>,
pub reverse: HashMap<String, String>,
pub all_types_by_service_type: Option<HashMap<String, Vec<String>>>,
#[allow(dead_code)]
pub service_types_by_projects: Option<HashMap<String, Vec<String>>>,
}
#[derive(Clone, Debug, Deserialize)]
pub struct Service {
pub service_type: String,
pub aliases: Option<Vec<String>>,
}
impl ServiceAuthority {
pub fn from_official_data() -> Result<Self, ServiceAuthorityError> {
let data = include_str!("../../static/service-types.json");
let authority: ServiceAuthority = serde_json::from_str(data)?;
Ok(authority)
}
pub fn get_all_types_by_service_type<S: AsRef<str>>(
&self,
service_type: S,
) -> Result<Vec<String>, ServiceAuthorityError> {
match &self.all_types_by_service_type {
Some(data) => data
.get(service_type.as_ref())
.ok_or(ServiceAuthorityError::ServiceUnknown(
service_type.as_ref().into(),
))
.cloned(),
None => {
warn!(
"No `all_types_by_service_type` is present in the authority. Reconstructing."
);
let service = self
.services
.iter()
.find(|x| x.service_type == service_type.as_ref())
.ok_or(ServiceAuthorityError::ServiceUnknown(
service_type.as_ref().into(),
))?;
let mut res = Vec::new();
res.push(service.service_type.clone());
if let Some(aliases) = &service.aliases {
for alias in aliases {
res.push(alias.clone());
}
}
Ok(res)
}
}
}
#[allow(dead_code)]
pub fn get_service_type_by_service_type_or_alias<S: AsRef<str>>(
&self,
service_type: S,
) -> Result<String, ServiceAuthorityError> {
if self.forward.contains_key(service_type.as_ref()) {
return Ok(service_type.as_ref().into());
}
if let Some(atbst) = &self.all_types_by_service_type {
if atbst.contains_key(service_type.as_ref()) {
return Ok(service_type.as_ref().into());
}
}
if let Some(srv) = self.reverse.get(service_type.as_ref()) {
return Ok(srv.into());
}
Err(ServiceAuthorityError::ServiceUnknown(
service_type.as_ref().into(),
))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_load_official() {
let foo = ServiceAuthority::from_official_data().unwrap();
assert!(!foo.services.is_empty());
assert!(!foo.forward.is_empty());
assert!(!foo.reverse.is_empty());
}
#[test]
fn test_get_all_types_by_service_type() {
let mut authority = ServiceAuthority {
services: Vec::from([
Service {
service_type: "foo".into(),
aliases: None,
},
Service {
service_type: "foo1".into(),
aliases: Some(Vec::from(["alias".into()])),
},
]),
all_types_by_service_type: Some(HashMap::from([
("foo".into(), Vec::from(["foo".into()])),
("foo1".into(), Vec::from(["foo1".into(), "alias".into()])),
])),
..Default::default()
};
assert_eq!(
Vec::from(["foo"]),
authority.get_all_types_by_service_type("foo").unwrap(),
);
assert_eq!(
Vec::from(["foo1", "alias"]),
authority.get_all_types_by_service_type("foo1").unwrap(),
);
assert!(authority.get_all_types_by_service_type("foo2").is_err());
authority.all_types_by_service_type = None;
assert_eq!(
Vec::from(["foo"]),
authority.get_all_types_by_service_type("foo").unwrap(),
);
assert_eq!(
Vec::from(["foo1", "alias"]),
authority.get_all_types_by_service_type("foo1").unwrap(),
);
}
#[test]
fn test_get_service_type_by_service_type_or_alias() {
let authority = ServiceAuthority {
services: Vec::from([
Service {
service_type: "foo".into(),
aliases: None,
},
Service {
service_type: "foo1".into(),
aliases: Some(Vec::from(["alias".into()])),
},
]),
forward: HashMap::from([("foo1".into(), Vec::from(["alias".into()]))]),
reverse: HashMap::from([("alias".into(), "foo1".into())]),
all_types_by_service_type: Some(HashMap::from([
("foo".into(), Vec::from(["foo".into()])),
("foo1".into(), Vec::from(["foo1".into(), "alias".into()])),
])),
..Default::default()
};
assert_eq!(
"foo",
authority
.get_service_type_by_service_type_or_alias("foo")
.unwrap()
);
assert_eq!(
"foo1",
authority
.get_service_type_by_service_type_or_alias("foo1")
.unwrap()
);
assert_eq!(
"foo1",
authority
.get_service_type_by_service_type_or_alias("alias")
.unwrap()
);
}
}