dpp-domain 0.9.0

EU Digital Product Passport domain types, port traits, and open-core boundary
Documentation
//! The [`SectorData`] discriminated union and its access-tier redaction.

use serde::{Deserialize, Serialize};

use super::aluminium::AluminiumData;
use super::battery::BatteryData;
use super::construction::ConstructionData;
use super::detergent::DetergentData;
use super::electronics::ElectronicsData;
use super::furniture::FurnitureData;
use super::steel::SteelData;
use super::textile::TextileData;
use super::toy::ToyData;
use super::tyre::TyreData;
use super::unsold_goods::UnsoldGoodsReport;
use crate::domain::sector::Sector;

/// Typed, sector-specific DPP data — replaces the opaque `compliance_data: Value`.
///
/// Serialises as an internally-tagged object where `"sector"` is the
/// discriminant field, e.g.:
/// ```json
/// { "sector": "battery", "gtin": "09506000134352", "nominalVoltageV": 3.2, ... }
/// ```
/// ```json
/// { "sector": "textile", "fibreComposition": [...], "countryOfManufacturing": "BD" }
/// ```
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(tag = "sector", rename_all = "camelCase")]
#[non_exhaustive]
pub enum SectorData {
    Battery(BatteryData),
    Textile(TextileData),
    UnsoldGoods(UnsoldGoodsReport),
    Steel(SteelData),
    Electronics(ElectronicsData),
    Construction(ConstructionData),
    Tyre(TyreData),
    Toy(ToyData),
    Aluminium(AluminiumData),
    Furniture(FurnitureData),
    Detergent(DetergentData),
    Other(serde_json::Value),
}

impl SectorData {
    /// Returns the `Sector` discriminant for this data.
    pub fn sector(&self) -> Sector {
        match self {
            SectorData::Battery(_) => Sector::Battery,
            SectorData::Textile(_) => Sector::Textile,
            SectorData::UnsoldGoods(_) => Sector::UnsoldGoods,
            SectorData::Steel(_) => Sector::Steel,
            SectorData::Electronics(_) => Sector::Electronics,
            SectorData::Construction(_) => Sector::Construction,
            SectorData::Tyre(_) => Sector::Tyre,
            SectorData::Toy(_) => Sector::Toy,
            SectorData::Aluminium(_) => Sector::Aluminium,
            SectorData::Furniture(_) => Sector::Furniture,
            SectorData::Detergent(_) => Sector::Detergent,
            SectorData::Other(_) => Sector::Other,
        }
    }

    /// The GTIN carried by this sector's typed data, if any.
    ///
    /// `UnsoldGoods` and `Other` carry no GTIN field — a discard-event report
    /// and an untyped catch-all respectively, neither of which identifies a
    /// trade item the way every other sector does.
    pub fn gtin(&self) -> Option<&str> {
        match self {
            SectorData::Battery(d) => Some(d.gtin.as_str()),
            SectorData::Textile(d) => Some(d.gtin.as_str()),
            SectorData::Steel(d) => Some(d.gtin.as_str()),
            SectorData::Electronics(d) => Some(d.gtin.as_str()),
            SectorData::Construction(d) => Some(d.gtin.as_str()),
            SectorData::Tyre(d) => Some(d.gtin.as_str()),
            SectorData::Toy(d) => Some(d.gtin.as_str()),
            SectorData::Aluminium(d) => Some(d.gtin.as_str()),
            SectorData::Furniture(d) => Some(d.gtin.as_str()),
            SectorData::Detergent(d) => Some(d.gtin.as_str()),
            SectorData::UnsoldGoods(_) | SectorData::Other(_) => None,
        }
    }
}

/// Serialize `data` to a JSON object and strip any top-level field whose
/// required access tier exceeds `viewer_tier`.
///
/// `descriptor.access_tiers` maps camelCase JSON field names to the minimum
/// [`crate::domain::identity::AccessTier`] a viewer must hold to see that field.
/// Fields not listed in the map are always retained (default: Public).
///
/// Returns a `serde_json::Value::Object` with redacted fields removed.
/// Returns `serde_json::Value::Null` if serialization fails.
pub fn redact_sector_data(
    data: &SectorData,
    viewer_tier: crate::domain::identity::AccessTier,
    descriptor: &crate::catalog::SectorDescriptor,
) -> serde_json::Value {
    let mut value = match serde_json::to_value(data) {
        Ok(v) => v,
        Err(_) => return serde_json::Value::Null,
    };
    if let Some(obj) = value.as_object_mut() {
        obj.retain(|key, _| match descriptor.access_tiers.get(key) {
            Some(&required) => viewer_tier >= required,
            None => true,
        });
    }
    value
}