use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use crate::{Disclosure, PASSPORT_FIELD_DISCLOSURE, SectorCatalog};
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SectorAccessPolicy {
pub name: String,
pub sector: String,
pub field_disclosure: HashMap<String, Disclosure>,
#[serde(default = "disclosure_public")]
pub default_disclosure: Disclosure,
}
fn disclosure_public() -> Disclosure {
Disclosure::Public
}
fn keys_match_normalized(a: &str, b: &str) -> bool {
let mut a_chars = a.chars().filter(char::is_ascii_alphanumeric);
let mut b_chars = b.chars().filter(char::is_ascii_alphanumeric);
loop {
match (a_chars.next(), b_chars.next()) {
(Some(x), Some(y)) if x.eq_ignore_ascii_case(&y) => {}
(None, None) => return true,
_ => return false,
}
}
}
const COMMON_CONFORMITY: &[&str] = &[
"jwsSignature",
"complianceReport",
"auditHistory",
"supplyChainTrace",
];
impl SectorAccessPolicy {
#[must_use]
pub fn for_schema_version(sector_key: &str, version: &str) -> Option<Self> {
let parsed: semver::Version = version.parse().ok()?;
let registry = crate::schemas::VersionedSchemaRegistry::new();
let json = registry.get(sector_key, &parsed)?;
Self::from_schema(sector_key, version, json)
}
#[deprecated(
since = "0.17.0",
note = "use `for_schema_version` — the catalog map is unversioned, so it \
filters published passports by rules that may postdate their signatures"
)]
pub fn from_catalog(catalog: &SectorCatalog, sector_key: &str) -> Option<Self> {
let descriptor = catalog.get(sector_key)?;
let mut field_disclosure: HashMap<String, Disclosure> = descriptor.disclosure.clone();
for field in COMMON_CONFORMITY {
field_disclosure
.entry((*field).to_owned())
.or_insert(Disclosure::Conformity);
}
Some(Self {
name: format!("{sector_key}-{}", descriptor.current_schema_version),
sector: sector_key.to_owned(),
field_disclosure,
default_disclosure: Disclosure::Public,
})
}
#[must_use]
pub fn from_schema(sector_key: &str, version: &str, schema_json: &str) -> Option<Self> {
let schema: serde_json::Value = serde_json::from_str(schema_json).ok()?;
let properties = schema.get("properties")?.as_object()?;
let mut field_disclosure: HashMap<String, Disclosure> = properties
.iter()
.filter_map(|(name, prop)| {
let class = prop.get("x-disclosure")?.as_str()?;
let disclosure = match class {
"public" => Disclosure::Public,
"restricted" => Disclosure::Restricted,
"conformity" => Disclosure::Conformity,
"individual" => Disclosure::Individual,
_ => return None,
};
Some((name.clone(), disclosure))
})
.collect();
for field in COMMON_CONFORMITY {
field_disclosure
.entry((*field).to_owned())
.or_insert(Disclosure::Conformity);
}
Some(Self {
name: format!("{sector_key}-{version}"),
sector: sector_key.to_owned(),
field_disclosure,
default_disclosure: Disclosure::Public,
})
}
pub fn passport_default() -> Self {
let mut field_disclosure = HashMap::new();
for (field, class) in PASSPORT_FIELD_DISCLOSURE {
field_disclosure.insert((*field).to_owned(), *class);
}
Self {
name: "passport-v1.0".into(),
sector: "passport".into(),
field_disclosure,
default_disclosure: Disclosure::Public,
}
}
pub fn disclosure_for_field(&self, field_name: &str) -> Disclosure {
self.field_disclosure
.iter()
.find(|(k, _)| keys_match_normalized(k, field_name))
.map(|(_, d)| *d)
.unwrap_or(self.default_disclosure)
}
}