Skip to main content

dpp_domain/domain/sector/data/
sector_data.rs

1//! The [`SectorData`] discriminated union and its per-audience redaction.
2
3use serde::{Deserialize, Serialize};
4
5use super::aluminium::AluminiumData;
6use super::battery::BatteryData;
7use super::construction::ConstructionData;
8use super::detergent::DetergentData;
9use super::electronics::ElectronicsData;
10use super::furniture::FurnitureData;
11use super::steel::SteelData;
12use super::textile::TextileData;
13use super::toy::ToyData;
14use super::tyre::TyreData;
15use super::unsold_goods::UnsoldGoodsReport;
16use crate::domain::sector::Sector;
17
18/// Typed, sector-specific DPP data — replaces the opaque `compliance_data: Value`.
19///
20/// Serialises as an internally-tagged object where `"sector"` is the
21/// discriminant field, e.g.:
22/// ```json
23/// { "sector": "battery", "gtin": "09506000134352", "nominalVoltageV": 3.2, ... }
24/// ```
25/// ```json
26/// { "sector": "textile", "fibreComposition": [...], "countryOfManufacturing": "BD" }
27/// ```
28#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
29#[serde(tag = "sector", rename_all = "camelCase")]
30#[non_exhaustive]
31pub enum SectorData {
32    Battery(BatteryData),
33    Textile(TextileData),
34    UnsoldGoods(UnsoldGoodsReport),
35    Steel(SteelData),
36    Electronics(ElectronicsData),
37    Construction(ConstructionData),
38    Tyre(TyreData),
39    Toy(ToyData),
40    Aluminium(AluminiumData),
41    Furniture(FurnitureData),
42    Detergent(DetergentData),
43    Other(serde_json::Value),
44}
45
46impl SectorData {
47    /// Returns the `Sector` discriminant for this data.
48    pub fn sector(&self) -> Sector {
49        match self {
50            SectorData::Battery(_) => Sector::Battery,
51            SectorData::Textile(_) => Sector::Textile,
52            SectorData::UnsoldGoods(_) => Sector::UnsoldGoods,
53            SectorData::Steel(_) => Sector::Steel,
54            SectorData::Electronics(_) => Sector::Electronics,
55            SectorData::Construction(_) => Sector::Construction,
56            SectorData::Tyre(_) => Sector::Tyre,
57            SectorData::Toy(_) => Sector::Toy,
58            SectorData::Aluminium(_) => Sector::Aluminium,
59            SectorData::Furniture(_) => Sector::Furniture,
60            SectorData::Detergent(_) => Sector::Detergent,
61            SectorData::Other(_) => Sector::Other,
62        }
63    }
64
65    /// The GTIN carried by this sector's typed data, if any.
66    ///
67    /// `UnsoldGoods` and `Other` carry no GTIN field — a discard-event report
68    /// and an untyped catch-all respectively, neither of which identifies a
69    /// trade item the way every other sector does.
70    pub fn gtin(&self) -> Option<&str> {
71        match self {
72            SectorData::Battery(d) => Some(d.gtin.as_str()),
73            SectorData::Textile(d) => Some(d.gtin.as_str()),
74            SectorData::Steel(d) => Some(d.gtin.as_str()),
75            SectorData::Electronics(d) => Some(d.gtin.as_str()),
76            SectorData::Construction(d) => Some(d.gtin.as_str()),
77            SectorData::Tyre(d) => Some(d.gtin.as_str()),
78            SectorData::Toy(d) => Some(d.gtin.as_str()),
79            SectorData::Aluminium(d) => Some(d.gtin.as_str()),
80            SectorData::Furniture(d) => Some(d.gtin.as_str()),
81            SectorData::Detergent(d) => Some(d.gtin.as_str()),
82            SectorData::UnsoldGoods(_) | SectorData::Other(_) => None,
83        }
84    }
85}
86
87/// Serialize `data` to a JSON object and strip any top-level field the
88/// `audience` may not see.
89///
90/// `descriptor.disclosure` maps camelCase JSON field names to their
91/// [`Disclosure`](crate::domain::identity::Disclosure) class; visibility is
92/// decided by [`Audience::may_see`](crate::domain::identity::Audience::may_see),
93/// which is a lattice and not a threshold. Fields not listed in the map are
94/// always retained (default: Public).
95///
96/// Returns a `serde_json::Value::Object` with redacted fields removed.
97/// Returns `serde_json::Value::Null` if serialization fails.
98pub fn redact_sector_data(
99    data: &SectorData,
100    audience: crate::domain::identity::Audience,
101    descriptor: &crate::catalog::SectorDescriptor,
102) -> serde_json::Value {
103    let mut value = match serde_json::to_value(data) {
104        Ok(v) => v,
105        Err(_) => return serde_json::Value::Null,
106    };
107    if let Some(obj) = value.as_object_mut() {
108        obj.retain(|key, _| match descriptor.disclosure.get(key) {
109            Some(&class) => audience.may_see(class),
110            None => true,
111        });
112    }
113    value
114}