use de::Deserialize;
use serde::{de, Deserializer};
use sha2::{Digest, Sha256};
use classy::client::{InvalidUri, Service, Uri};
use crate::policy_context::api::Metadata;
const SERVICE_NAME_SUFFIX: &str = "service";
pub fn deserialize_service<'de, D>(deserializer: D) -> Result<Service, D::Error>
where
D: Deserializer<'de>,
{
use serde::de::Error;
let string: String = Deserialize::deserialize(deserializer)?;
let metadata = Metadata::new();
let service = new_service(string.as_str(), &metadata)
.map_err(|err: InvalidUri| Error::custom(error_message(string.as_str(), err)))?;
Ok(service)
}
pub fn deserialize_service_opt<'de, D>(deserializer: D) -> Result<Option<Service>, D::Error>
where
D: Deserializer<'de>,
{
use serde::de::Error;
let string: Option<String> = Deserialize::deserialize(deserializer)?;
match string {
Some(string) => {
let metadata = Metadata::new();
let service = new_service(string.as_str(), &metadata)
.map_err(|err: InvalidUri| Error::custom(error_message(string.as_str(), err)))?;
Ok(Some(service))
}
None => Ok(None),
}
}
pub fn deserialize_service_vec<'de, D>(deserializer: D) -> Result<Vec<Service>, D::Error>
where
D: Deserializer<'de>,
{
use serde::de::Error;
let strings: Vec<String> = Deserialize::deserialize(deserializer)?;
let metadata = Metadata::new();
let services: Vec<Service> = strings
.iter()
.map(|string| {
let service = new_service(string.as_str(), &metadata)
.map_err(|err: InvalidUri| Error::custom(error_message(string.as_str(), err)))?;
Ok(service)
})
.collect::<Result<Vec<_>, _>>()?;
Ok(services)
}
pub fn deserialize_service_opt_vec<'de, D>(
deserializer: D,
) -> Result<Option<Vec<Service>>, D::Error>
where
D: Deserializer<'de>,
{
use serde::de::Error;
let strings: Option<Vec<String>> = Deserialize::deserialize(deserializer)?;
match strings {
Some(strings) => {
let metadata = Metadata::new();
let services: Vec<Service> = strings
.iter()
.map(|string| {
let service =
new_service(string.as_str(), &metadata).map_err(|err: InvalidUri| {
Error::custom(error_message(string.as_str(), err))
})?;
Ok(service)
})
.collect::<Result<Vec<_>, _>>()?;
Ok(Some(services))
}
None => Ok(None),
}
}
pub fn service_name(policy_name: &str, authority: &str) -> String {
let full_name = format!("{policy_name}-{authority}");
let digest = &Sha256::digest(full_name.as_str());
let hex = &format!("{digest:x}")[..7];
let compliant_full_name: String = full_name
.chars()
.map(|c| if c.is_ascii_alphanumeric() { c } else { '-' })
.collect();
let capped_name = &compliant_full_name[..std::cmp::min(47, compliant_full_name.len())];
format!("{}-{}-{}", capped_name.to_owned(), hex, SERVICE_NAME_SUFFIX)
}
fn new_service(string: &str, metadata: &Metadata) -> Result<Service, InvalidUri> {
let uri: Uri = string.parse()?;
let service_namespace = metadata.policy_metadata.policy_namespace.as_str();
let service_name = service_name(
metadata.policy_metadata.policy_name.as_str(),
uri.authority(),
);
Ok(Service::from(service_name.as_str(), service_namespace, uri))
}
fn error_message(string: &str, err: InvalidUri) -> String {
format!("Error parsing {string} as Uri: {err}")
}
#[cfg(test)]
mod test {
use std::vec::IntoIter;
use serde::de::value::{Error as ValueError, SeqDeserializer, StrDeserializer};
use serde::de::{Error, IntoDeserializer};
use serde_derive::Deserialize;
use serde_json::json;
use classy::hl::Service;
use crate::client::{
deserialize_service, deserialize_service_opt, deserialize_service_opt_vec,
deserialize_service_vec, service_name,
};
#[test]
fn service_name_test() {
let name = service_name("policy", "authority");
assert_eq!(name, "policy-authority-15aeecc-service");
}
#[test]
fn service_name_is_idempotent() {
let name = service_name("policy", "authority");
let name2 = service_name("policy", "authority");
assert_eq!(name, name2);
}
#[test]
fn service_name_long_does_not_exceed_63_chars() {
let authority = "a-really-long-authority-exceeding-max-large-service-name";
let name = service_name("policy", authority);
assert_eq!(name.len(), 63);
assert_eq!(
name,
"policy-a-really-long-authority-exceeding-max-la-3b21324-service"
);
}
#[test]
fn service_name_two_long_names_that_end_different_have_different_hashes() {
let authority = "a-really-long-authority-exceeding-max-large-service-name";
let authority_2 = "a-really-long-authority-exceeding-max-large-service-name-2";
let name = service_name("policy", authority);
let name2 = service_name("policy", authority_2);
assert_ne!(name, name2);
}
#[test]
fn service_name_two_different_names_that_have_same_sanitized_name_have_different_hashes() {
let authority = "authority:port";
let authority_2 = "authority@port";
let name = service_name("policy", authority);
let name2 = service_name("policy", authority_2);
assert_ne!(name, name2);
}
#[test]
fn service_name_invalid_chars_in_authority_are_converted() {
let authority = "username:password@[ipv6]:port";
let name = service_name("policy", authority);
assert_eq!(name, "policy-username-password--ipv6--port-c248764-service");
}
#[test]
fn deserialize_service_successful_parsing() {
let uri_string = "https://localhost:8080/api";
let result = deserialize_service(deserializer(uri_string));
assert!(result.is_ok());
let service = result.unwrap();
assert_eq!(service.uri().to_string(), uri_string.to_string());
assert_eq!(
service.cluster_name(),
"NoPolicyId-localhost-8080-a5ec11e-service.NoPolicyNamespace.svc"
);
}
#[test]
fn deserialize_service_invalid_uri() {
let uri_string = "localhost:8080/api";
let result = deserialize_service(deserializer(uri_string));
assert_eq!(
result.unwrap_err(),
ValueError::custom("Error parsing localhost:8080/api as Uri: invalid format")
)
}
#[test]
fn deserialize_service_vec_successful_parsing() {
let uri_string_1 = "https://localhost:8080/api";
let uri_string_2 = "https://another-host:9090/api";
let vec = vec![uri_string_1, uri_string_2];
let result = deserialize_service_vec(vec_deserializer(vec));
assert!(result.is_ok(), "{:?}", result.err());
let services = result.unwrap();
assert_eq!(services.len(), 2);
let service1 = services.first().unwrap();
assert_eq!(service1.uri().to_string(), uri_string_1.to_string());
assert_eq!(
service1.cluster_name(),
"NoPolicyId-localhost-8080-a5ec11e-service.NoPolicyNamespace.svc"
);
let service2 = services.get(1).unwrap();
assert_eq!(service2.uri().to_string(), uri_string_2.to_string());
assert_eq!(
service2.cluster_name(),
"NoPolicyId-another-host-9090-f35336e-service.NoPolicyNamespace.svc"
);
}
#[test]
fn deserialize_service_vec_invalid_uri_first_element() {
let uri_string_1 = "localhost:8080/api";
let uri_string_2 = "https://another-host:9090/api";
let vec = vec![uri_string_1, uri_string_2];
let result = deserialize_service_vec(vec_deserializer(vec));
assert_eq!(
result.unwrap_err(),
ValueError::custom("Error parsing localhost:8080/api as Uri: invalid format")
)
}
#[test]
fn deserialize_service_vec_invalid_uri_last_element() {
let uri_string_1 = "https://localhost:8080/api";
let uri_string_2 = "another-host:9090/api";
let vec = vec![uri_string_1, uri_string_2];
let result = deserialize_service_vec(vec_deserializer(vec));
assert_eq!(
result.unwrap_err(),
ValueError::custom("Error parsing another-host:9090/api as Uri: invalid format")
)
}
#[test]
fn deserialize_opt_service_successful_parsing() {
let uri_string = "https://localhost:8080/api";
let test: TestOpt = serde_json::from_value(json!({ "service": uri_string })).unwrap();
let option = test.service;
assert!(option.is_some());
let service = option.unwrap();
assert_eq!(service.uri().to_string(), uri_string.to_string());
assert_eq!(
service.cluster_name(),
"NoPolicyId-localhost-8080-a5ec11e-service.NoPolicyNamespace.svc"
);
}
#[test]
fn deserialize_opt_service_invalid_uri() {
let result: Result<TestOpt, serde_json::Error> =
serde_json::from_value(json!({ "service": "localhost:8080/api" }));
assert_eq!(
result.unwrap_err().to_string(),
"Error parsing localhost:8080/api as Uri: invalid format"
)
}
#[test]
fn deserialize_opt_missing_field() {
let test: TestOpt = serde_json::from_value(json!({})).unwrap();
assert!(test.service.is_none());
}
#[test]
fn deserialize_service_opt_vec_successful_parsing() {
let uri_string_1 = "https://localhost:8080/api";
let uri_string_2 = "https://another-host:9090/api";
let test: TestOptVec =
serde_json::from_value(json!({ "services": [uri_string_1, uri_string_2] })).unwrap();
let option = test.services;
assert!(option.is_some());
let services = option.unwrap();
assert_eq!(services.len(), 2);
let service1 = services.first().unwrap();
assert_eq!(service1.uri().to_string(), uri_string_1.to_string());
assert_eq!(
service1.cluster_name(),
"NoPolicyId-localhost-8080-a5ec11e-service.NoPolicyNamespace.svc"
);
let service2 = services.get(1).unwrap();
assert_eq!(service2.uri().to_string(), uri_string_2.to_string());
assert_eq!(
service2.cluster_name(),
"NoPolicyId-another-host-9090-f35336e-service.NoPolicyNamespace.svc"
);
}
#[test]
fn deserialize_service_opt_vec_invalid_first_uri() {
let result: Result<TestOptVec, serde_json::Error> = serde_json::from_value(
json!({ "services": ["localhost:8080/api", "https://another-host:9090/api"] }),
);
assert_eq!(
result.unwrap_err().to_string(),
"Error parsing localhost:8080/api as Uri: invalid format"
)
}
#[test]
fn deserialize_service_opt_vec_invalid_last_uri() {
let result: Result<TestOptVec, serde_json::Error> = serde_json::from_value(
json!({ "services": ["https://localhost:8080/api", "another-host:9090/api"] }),
);
assert_eq!(
result.unwrap_err().to_string(),
"Error parsing another-host:9090/api as Uri: invalid format"
)
}
#[test]
fn deserialize_service_opt_vec_missing_field() {
let test: TestOptVec = serde_json::from_value(json!({})).unwrap();
assert!(test.services.is_none());
}
#[derive(Debug, Deserialize)]
struct TestOpt {
#[serde(default)]
#[serde(deserialize_with = "deserialize_service_opt")]
pub service: Option<Service>,
}
#[derive(Debug, Deserialize)]
struct TestOptVec {
#[serde(default)]
#[serde(deserialize_with = "deserialize_service_opt_vec")]
pub services: Option<Vec<Service>>,
}
fn deserializer(uri_string: &str) -> StrDeserializer<serde::de::value::Error> {
let deserializer: StrDeserializer<ValueError> = uri_string.into_deserializer();
deserializer
}
fn vec_deserializer(vec: Vec<&str>) -> SeqDeserializer<IntoIter<&str>, ValueError> {
let deserializer = vec.into_deserializer();
deserializer
}
}