use dpp_domain::access::{SectorAccessPolicy, filter_by_audience};
use dpp_domain::{Audience, Passport, SectorCatalog};
use super::model::{
AasEnvironment, AasShell, AasSubmodel, AasSubmodelRef, AssetInformation, SpecificAssetId,
};
use super::sectors;
#[derive(Debug)]
pub enum AasError {
Masking(String),
}
impl std::fmt::Display for AasError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Masking(m) => write!(f, "passport did not survive masking: {m}"),
}
}
}
impl std::error::Error for AasError {}
pub fn build_aas_from_passport(
passport: &Passport,
gtin: &str,
audience: Audience,
) -> Result<(AasShell, Vec<AasSubmodel>), AasError> {
let passport = &mask(passport, audience)?;
let passport_id = passport.id.to_string();
let mut specific_asset_ids = vec![
SpecificAssetId {
name: "gtin".into(),
value: gtin.to_owned(),
},
SpecificAssetId {
name: "serialId".into(),
value: passport_id.clone(),
},
];
if let Some(batch) = &passport.batch_id {
specific_asset_ids.push(SpecificAssetId {
name: "batchId".into(),
value: batch.clone(),
});
}
let mut submodels = vec![
sectors::build_product_identification_submodel(passport),
sectors::build_manufacturer_submodel(passport),
sectors::build_environmental_impact_submodel(passport),
sectors::build_material_composition_submodel(passport),
sectors::build_repairability_submodel(passport),
];
if let Some(sd) = &passport.sector_data {
submodels.push(sectors::build_sector_submodel(sd, &passport_id));
}
let shell = AasShell {
id: format!("urn:odal-node:aas:{passport_id}"),
id_short: "DigitalProductPassport".into(),
model_type: "AssetAdministrationShell".into(),
kind: "Instance".into(),
asset_information: AssetInformation {
global_asset_id: format!("urn:odal-node:product:{gtin}"),
specific_asset_ids,
},
submodels: submodels
.iter()
.map(|s| AasSubmodelRef { id: s.id.clone() })
.collect(),
};
Ok((shell, submodels))
}
fn mask(passport: &Passport, audience: Audience) -> Result<Passport, AasError> {
let policy = SectorAccessPolicy::from_catalog(catalog(), passport.sector.catalog_key())
.unwrap_or_else(SectorAccessPolicy::passport_default);
let document =
serde_json::to_value(passport).map_err(|e| AasError::Masking(format!("serialise: {e}")))?;
let decision = filter_by_audience(&document, &policy, audience);
serde_json::from_value(decision.filtered_data)
.map_err(|e| AasError::Masking(format!("redacted document no longer valid: {e}")))
}
pub fn build_aas_environment(
passport: &Passport,
gtin: &str,
audience: Audience,
) -> Result<AasEnvironment, AasError> {
let (shell, submodels) = build_aas_from_passport(passport, gtin, audience)?;
Ok(AasEnvironment {
asset_administration_shells: vec![shell],
submodels,
concept_descriptions: Vec::new(),
})
}
fn catalog() -> &'static SectorCatalog {
static CATALOG: std::sync::OnceLock<SectorCatalog> = std::sync::OnceLock::new();
CATALOG.get_or_init(SectorCatalog::new)
}