Skip to main content

dpp_domain/validation/
rules.rs

1//! Validation helpers for product group payload lists.
2//!
3//! The cross-field rule logic lives once in `dpp-rules` (shared with the Wasm
4//! product group plugins). These thin adapters map the domain structs onto that crate's
5//! primitive inputs, preserving the public API and error messages.
6
7use crate::product_group::data::{FibreEntry, SurfactantEntry, SvhcSubstance};
8
9/// Validate a list of SVHC substance declarations. Delegates to [`dpp_rules`].
10pub fn validate_svhc_substances(substances: &[SvhcSubstance]) -> Result<(), String> {
11    let inputs: Vec<dpp_rules::SvhcInput<'_>> = substances
12        .iter()
13        .map(|s| dpp_rules::SvhcInput {
14            cas_number: &s.cas_number,
15            substance_name: &s.substance_name,
16            concentration_pct: s.concentration_pct,
17        })
18        .collect();
19    dpp_rules::validate_svhc_substances(&inputs)
20}
21
22/// Validate a textile fibre composition list. Delegates to [`dpp_rules`].
23pub fn validate_fibre_composition(fibres: &[FibreEntry]) -> Result<(), String> {
24    let inputs: Vec<dpp_rules::FibreInput<'_>> = fibres
25        .iter()
26        .map(|f| dpp_rules::FibreInput {
27            fibre: &f.fibre,
28            pct: f.pct,
29            country_of_origin: f.country_of_origin.as_deref(),
30        })
31        .collect();
32    dpp_rules::validate_fibre_composition(&inputs)
33}
34
35/// Validate a battery's operating temperature range (`min < max` when both are
36/// declared). Delegates to [`dpp_rules`].
37pub fn validate_battery_operating_temp(
38    min_c: Option<f64>,
39    max_c: Option<f64>,
40) -> Result<(), String> {
41    dpp_rules::batteries::chemistry::validate_operating_temp_range(min_c, max_c)
42}
43
44/// Metals declared with recycled content `> 0` that the battery chemistry does
45/// not contain (a data-integrity contradiction, e.g. cobalt on LFP). Delegates
46/// to [`dpp_rules`]. `chemistry` is the serde code (`"LFP"`, `"NMC"`, …).
47pub fn battery_recycled_chemistry_conflicts(
48    chemistry: &str,
49    cobalt_pct: Option<f64>,
50    lithium_pct: Option<f64>,
51    nickel_pct: Option<f64>,
52    lead_pct: Option<f64>,
53) -> Vec<&'static str> {
54    dpp_rules::batteries::recycled_content::recycled_content_chemistry_conflicts(
55        chemistry,
56        cobalt_pct,
57        lithium_pct,
58        nickel_pct,
59        lead_pct,
60    )
61}
62
63/// The ESPR Annex VII heading a commodity code falls under, if any
64/// (apparel & clothing accessories, or footwear). `None` means the code is
65/// out of Annex VII's destruction-ban scope entirely. Delegates to
66/// [`dpp_rules`].
67#[must_use]
68pub fn unsold_goods_annex_vii_heading(
69    commodity_code: &str,
70) -> Option<dpp_rules::unsold_goods::annex_vii::AnnexViiHeading> {
71    dpp_rules::unsold_goods::annex_vii::annex_vii_heading(commodity_code)
72}
73
74/// Whether a disclosure line's CN category is filed at the depth Impl. Reg.
75/// (EU) 2026/2 Art. 3 requires for it. Delegates to [`dpp_rules`].
76#[must_use]
77pub fn unsold_goods_cn_depth_is_correct(cn_category: &str) -> bool {
78    dpp_rules::unsold_goods::disclosure::cn_depth_is_correct(cn_category)
79}
80
81/// Validate a detergent surfactant list. Delegates to [`dpp_rules`].
82pub fn validate_surfactants(surfactants: &[SurfactantEntry]) -> Result<(), String> {
83    let inputs: Vec<dpp_rules::SurfactantInput<'_>> = surfactants
84        .iter()
85        .map(|s| dpp_rules::SurfactantInput {
86            name: &s.name,
87            concentration_band: &s.concentration_band,
88            biodegradable: s.biodegradable,
89            cas_number: s.cas_number.as_deref(),
90        })
91        .collect();
92    dpp_rules::validate_surfactants(&inputs)
93}