Skip to main content

dpp_digital_link/linktype/
vocabulary.rs

1//! GS1 Web Vocabulary link types for Digital Link resolution.
2
3use serde::{Deserialize, Serialize};
4
5/// GS1 defined link types for Digital Link resolution.
6///
7/// These follow the GS1 Web Vocabulary link type definitions.
8/// See: <https://ref.gs1.org/voc/linkTypes>
9#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
10#[serde(rename_all = "camelCase")]
11pub enum Gs1LinkType {
12    /// `gs1:pip` — Product Information Page (default for consumers).
13    ProductInformationPage,
14    /// `gs1:epil` — Electronic Product Information Leaflet.
15    ElectronicLeaflet,
16    /// `gs1:sustainabilityInfo` — Sustainability and environmental data.
17    SustainabilityInfo,
18    /// `gs1:recyclingInfo` — Recycling instructions and collection points.
19    RecyclingInfo,
20    /// `gs1:masterData` — Master data about the product (GS1 GDSN).
21    MasterData,
22    /// `gs1:certificationInfo` — Certification and conformity data.
23    CertificationInfo,
24    /// `gs1:instructions` — Usage, repair, or disassembly instructions.
25    Instructions,
26    /// `gs1:safetyInfo` — Safety data sheets, SVHC declarations.
27    SafetyInfo,
28    /// `gs1:traceability` — Supply chain traceability data.
29    Traceability,
30    /// `gs1:dpp` — EU Digital Product Passport (full DPP payload).
31    /// This is the ESPR-specific link type for accessing the complete DPP.
32    DigitalProductPassport,
33    /// Custom / unknown link type (stored as the raw URI).
34    Custom(String),
35}
36
37impl Gs1LinkType {
38    /// Parse a link type from a GS1 vocabulary URI or shorthand.
39    pub fn parse(s: &str) -> Self {
40        match s {
41            "gs1:pip" | "https://ref.gs1.org/voc/pip" => Self::ProductInformationPage,
42            "gs1:epil" | "https://ref.gs1.org/voc/epil" => Self::ElectronicLeaflet,
43            "gs1:sustainabilityInfo" | "https://ref.gs1.org/voc/sustainabilityInfo" => {
44                Self::SustainabilityInfo
45            }
46            "gs1:recyclingInfo" | "https://ref.gs1.org/voc/recyclingInfo" => Self::RecyclingInfo,
47            "gs1:masterData" | "https://ref.gs1.org/voc/masterData" => Self::MasterData,
48            "gs1:certificationInfo" | "https://ref.gs1.org/voc/certificationInfo" => {
49                Self::CertificationInfo
50            }
51            "gs1:instructions" | "https://ref.gs1.org/voc/instructions" => Self::Instructions,
52            "gs1:safetyInfo" | "https://ref.gs1.org/voc/safetyInfo" => Self::SafetyInfo,
53            "gs1:traceability" | "https://ref.gs1.org/voc/traceability" => Self::Traceability,
54            "gs1:dpp" | "https://ref.gs1.org/voc/dpp" => Self::DigitalProductPassport,
55            other => Self::Custom(other.to_owned()),
56        }
57    }
58
59    /// Return the canonical GS1 vocabulary URI for this link type.
60    pub fn as_gs1_uri(&self) -> &str {
61        match self {
62            Self::ProductInformationPage => "https://ref.gs1.org/voc/pip",
63            Self::ElectronicLeaflet => "https://ref.gs1.org/voc/epil",
64            Self::SustainabilityInfo => "https://ref.gs1.org/voc/sustainabilityInfo",
65            Self::RecyclingInfo => "https://ref.gs1.org/voc/recyclingInfo",
66            Self::MasterData => "https://ref.gs1.org/voc/masterData",
67            Self::CertificationInfo => "https://ref.gs1.org/voc/certificationInfo",
68            Self::Instructions => "https://ref.gs1.org/voc/instructions",
69            Self::SafetyInfo => "https://ref.gs1.org/voc/safetyInfo",
70            Self::Traceability => "https://ref.gs1.org/voc/traceability",
71            Self::DigitalProductPassport => "https://ref.gs1.org/voc/dpp",
72            Self::Custom(uri) => uri.as_str(),
73        }
74    }
75}
76
77#[cfg(test)]
78mod tests {
79    use super::*;
80
81    #[test]
82    fn every_known_link_type_round_trips_via_canonical_uri() {
83        let all = [
84            Gs1LinkType::ProductInformationPage,
85            Gs1LinkType::ElectronicLeaflet,
86            Gs1LinkType::SustainabilityInfo,
87            Gs1LinkType::RecyclingInfo,
88            Gs1LinkType::MasterData,
89            Gs1LinkType::CertificationInfo,
90            Gs1LinkType::Instructions,
91            Gs1LinkType::SafetyInfo,
92            Gs1LinkType::Traceability,
93            Gs1LinkType::DigitalProductPassport,
94        ];
95        for lt in all {
96            let uri = lt.as_gs1_uri();
97            assert_eq!(Gs1LinkType::parse(uri), lt, "canonical URI must round-trip");
98        }
99    }
100
101    #[test]
102    fn shorthand_and_full_uris_parse_equivalently() {
103        assert_eq!(
104            Gs1LinkType::parse("gs1:dpp"),
105            Gs1LinkType::DigitalProductPassport
106        );
107        assert_eq!(
108            Gs1LinkType::parse("gs1:pip"),
109            Gs1LinkType::ProductInformationPage
110        );
111        assert_eq!(
112            Gs1LinkType::parse("gs1:safetyInfo"),
113            Gs1LinkType::SafetyInfo
114        );
115    }
116
117    #[test]
118    fn unknown_link_type_becomes_custom() {
119        let custom = Gs1LinkType::parse("https://example.com/voc/warranty");
120        assert_eq!(
121            custom,
122            Gs1LinkType::Custom("https://example.com/voc/warranty".to_owned())
123        );
124        assert_eq!(custom.as_gs1_uri(), "https://example.com/voc/warranty");
125    }
126}