1use dpp_domain::access::{SectorAccessPolicy, filter_by_audience};
5use dpp_domain::{Audience, Passport, SectorCatalog};
6
7use super::model::{AasShell, AasSubmodel, AasSubmodelRef, AssetInformation, SpecificAssetId};
8use super::sectors;
9
10#[derive(Debug)]
12pub enum AasError {
13 Masking(String),
18}
19
20impl std::fmt::Display for AasError {
21 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
22 match self {
23 Self::Masking(m) => write!(f, "passport did not survive masking: {m}"),
24 }
25 }
26}
27
28impl std::error::Error for AasError {}
29
30pub fn build_aas_from_passport(
58 passport: &Passport,
59 gtin: &str,
60 audience: Audience,
61) -> Result<(AasShell, Vec<AasSubmodel>), AasError> {
62 let passport = &mask(passport, audience)?;
63 let passport_id = passport.id.to_string();
64
65 let mut specific_asset_ids = vec![
66 SpecificAssetId {
67 name: "gtin".into(),
68 value: gtin.to_owned(),
69 },
70 SpecificAssetId {
71 name: "serialId".into(),
72 value: passport_id.clone(),
73 },
74 ];
75 if let Some(batch) = &passport.batch_id {
76 specific_asset_ids.push(SpecificAssetId {
77 name: "batchId".into(),
78 value: batch.clone(),
79 });
80 }
81
82 let mut submodels = vec![
83 sectors::build_product_identification_submodel(passport),
84 sectors::build_manufacturer_submodel(passport),
85 sectors::build_environmental_impact_submodel(passport),
86 sectors::build_material_composition_submodel(passport),
87 sectors::build_repairability_submodel(passport),
88 ];
89 if let Some(sd) = &passport.sector_data {
90 submodels.push(sectors::build_sector_submodel(sd, &passport_id));
91 }
92
93 let shell = AasShell {
94 id: format!("urn:odal-node:aas:{passport_id}"),
95 id_short: "DigitalProductPassport".into(),
96 model_type: "AssetAdministrationShell".into(),
97 kind: "Instance".into(),
98 asset_information: AssetInformation {
99 global_asset_id: format!("urn:odal-node:product:{gtin}"),
100 specific_asset_ids,
101 },
102 submodels: submodels
103 .iter()
104 .map(|s| AasSubmodelRef { id: s.id.clone() })
105 .collect(),
106 };
107
108 Ok((shell, submodels))
109}
110
111fn mask(passport: &Passport, audience: Audience) -> Result<Passport, AasError> {
118 let policy = SectorAccessPolicy::from_catalog(catalog(), passport.sector.catalog_key())
119 .unwrap_or_else(SectorAccessPolicy::passport_default);
120
121 let document =
122 serde_json::to_value(passport).map_err(|e| AasError::Masking(format!("serialise: {e}")))?;
123 let decision = filter_by_audience(&document, &policy, audience);
124
125 serde_json::from_value(decision.filtered_data)
126 .map_err(|e| AasError::Masking(format!("redacted document no longer valid: {e}")))
127}
128
129fn catalog() -> &'static SectorCatalog {
131 static CATALOG: std::sync::OnceLock<SectorCatalog> = std::sync::OnceLock::new();
132 CATALOG.get_or_init(SectorCatalog::new)
133}