Skip to main content

dpp_domain/domain/sector/data/
mod.rs

1//! Per-sector typed payloads and the discriminated [`SectorData`] union.
2//!
3//! One file per sector, mirroring the per-sector catalog manifests and JSON
4//! schemas 1:1. [`shared`] holds payload structs used by more than one sector.
5
6pub mod aluminium;
7pub mod battery;
8pub mod construction;
9pub mod detergent;
10pub mod electronics;
11pub mod furniture;
12pub mod shared;
13pub mod steel;
14pub mod textile;
15pub mod toy;
16pub mod tyre;
17pub mod unsold_goods;
18
19pub use aluminium::AluminiumData;
20pub use battery::{BatteryData, MaterialComposition};
21pub use construction::ConstructionData;
22pub use detergent::{DetergentData, SurfactantEntry};
23pub use electronics::ElectronicsData;
24pub use furniture::FurnitureData;
25pub use shared::{CriticalRawMaterial, SvhcSubstance};
26pub use steel::SteelData;
27pub use textile::{FibreEntry, TextileData};
28pub use toy::ToyData;
29pub use tyre::TyreData;
30pub use unsold_goods::{UnsoldGoodsDestination, UnsoldGoodsReason, UnsoldGoodsReport};
31
32use serde::{Deserialize, Serialize};
33
34use crate::domain::sector::Sector;
35
36/// Typed, sector-specific DPP data — replaces the opaque `compliance_data: Value`.
37///
38/// Serialises as an internally-tagged object where `"sector"` is the
39/// discriminant field, e.g.:
40/// ```json
41/// { "sector": "battery", "gtin": "09506000134352", "nominalVoltageV": 3.2, ... }
42/// ```
43/// ```json
44/// { "sector": "textile", "fibreComposition": [...], "countryOfManufacturing": "BD" }
45/// ```
46#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
47#[serde(tag = "sector", rename_all = "camelCase")]
48#[non_exhaustive]
49pub enum SectorData {
50    Battery(BatteryData),
51    Textile(TextileData),
52    UnsoldGoods(UnsoldGoodsReport),
53    Steel(SteelData),
54    Electronics(ElectronicsData),
55    Construction(ConstructionData),
56    Tyre(TyreData),
57    Toy(ToyData),
58    Aluminium(AluminiumData),
59    Furniture(FurnitureData),
60    Detergent(DetergentData),
61    Other(serde_json::Value),
62}
63
64impl SectorData {
65    /// Returns the `Sector` discriminant for this data.
66    pub fn sector(&self) -> Sector {
67        match self {
68            SectorData::Battery(_) => Sector::Battery,
69            SectorData::Textile(_) => Sector::Textile,
70            SectorData::UnsoldGoods(_) => Sector::UnsoldGoods,
71            SectorData::Steel(_) => Sector::Steel,
72            SectorData::Electronics(_) => Sector::Electronics,
73            SectorData::Construction(_) => Sector::Construction,
74            SectorData::Tyre(_) => Sector::Tyre,
75            SectorData::Toy(_) => Sector::Toy,
76            SectorData::Aluminium(_) => Sector::Aluminium,
77            SectorData::Furniture(_) => Sector::Furniture,
78            SectorData::Detergent(_) => Sector::Detergent,
79            SectorData::Other(_) => Sector::Other,
80        }
81    }
82}
83
84/// Serialize `data` to a JSON object and strip any top-level field whose
85/// required access tier exceeds `viewer_tier`.
86///
87/// `descriptor.access_tiers` maps camelCase JSON field names to the minimum
88/// [`crate::domain::identity::AccessTier`] a viewer must hold to see that field.
89/// Fields not listed in the map are always retained (default: Public).
90///
91/// Returns a `serde_json::Value::Object` with redacted fields removed.
92/// Returns `serde_json::Value::Null` if serialization fails.
93pub fn redact_sector_data(
94    data: &SectorData,
95    viewer_tier: crate::domain::identity::AccessTier,
96    descriptor: &crate::catalog::SectorDescriptor,
97) -> serde_json::Value {
98    let mut value = match serde_json::to_value(data) {
99        Ok(v) => v,
100        Err(_) => return serde_json::Value::Null,
101    };
102    if let Some(obj) = value.as_object_mut() {
103        obj.retain(|key, _| match descriptor.access_tiers.get(key) {
104            Some(&required) => viewer_tier >= required,
105            None => true,
106        });
107    }
108    value
109}