Skip to main content

eml_nl/
version.rs

1use crate::{
2    EMLError, EMLErrorKind, NS_KR,
3    io::{EMLElement, EMLElementReader, EMLElementWriter, QualifiedName},
4};
5
6/// Describes the EML_NL version of the EML standard.
7///
8/// Since EML 1.3, EML_NL adds a `kr:Schema` element within the top level
9/// element to indicate the specific EML_NL schema version in use. If this
10/// element is not present, the legacy unversioned EML_NL schema is assumed,
11/// in practice this means that this document should follow the EML_NL 1.2.2
12/// schema.
13///
14/// Note: newer EML_NL version must always be appended at the end of this enum.
15#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
16pub enum EMLVersion {
17    /// A legacy unversioned EML_NL document, assumed to follow EML_NL 1.2.2
18    V1_2_2,
19
20    /// An EML_NL version 1.3 document
21    V1_3,
22
23    /// An EML_NL version 1.3.1 document
24    #[default]
25    V1_3_1,
26}
27
28impl EMLVersion {
29    /// Name of the element that is used to indicate the EML_NL schema version.
30    pub const EML_NAME: QualifiedName<'_, '_> = QualifiedName::from_static("Schema", Some(NS_KR));
31
32    /// Returns the string representation of this EML version, if one is known.
33    ///
34    /// Returns `None` for [`EMLVersion::V1_2_2`].
35    pub fn to_str(&self) -> Option<&'static str> {
36        match self {
37            EMLVersion::V1_2_2 => None,
38            EMLVersion::V1_3 => Some("1.3"),
39            EMLVersion::V1_3_1 => Some("1.3.1"),
40        }
41    }
42
43    /// Returns `true` if this is an EML_NL version 1.3.1 document.
44    pub fn is_v1_3_1(&self) -> bool {
45        matches!(self, EMLVersion::V1_3_1)
46    }
47
48    /// Returns `true` if this is an EML_NL version 1.3 document.
49    pub fn is_v1_3(&self) -> bool {
50        matches!(self, EMLVersion::V1_3)
51    }
52
53    /// Returns `true` if this is a legacy unversioned EML_NL document.
54    pub fn is_v1_2_2(&self) -> bool {
55        matches!(self, EMLVersion::V1_2_2)
56    }
57}
58
59impl EMLElement for EMLVersion {
60    const EML_NAME: QualifiedName<'_, '_> = QualifiedName::from_static("Schema", Some(NS_KR));
61
62    fn read_eml(elem: &mut EMLElementReader<'_, '_>) -> Result<Self, EMLError> {
63        // we only do the version check during reading
64
65        let version = elem.attribute_value_req("Version")?;
66        Ok(version.parse()?)
67    }
68
69    fn write_eml(&self, writer: EMLElementWriter) -> Result<(), EMLError> {
70        if self.is_v1_2_2() {
71            // don't write if the version is 1.2.2
72            return Ok(());
73        }
74        writer
75            .attr(
76                "Version",
77                self.to_str()
78                    .expect("Version is not 1.2.2, so should have version string"),
79            )?
80            .empty()
81    }
82}
83
84impl std::fmt::Display for EMLVersion {
85    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
86        write!(f, "{}", self.to_str().unwrap_or("1.2.2"))
87    }
88}
89
90impl std::str::FromStr for EMLVersion {
91    type Err = UnsupportedEMLVersion;
92
93    fn from_str(s: &str) -> Result<Self, Self::Err> {
94        match s {
95            "1.3.1" => Ok(EMLVersion::V1_3_1),
96            "1.3" => Ok(EMLVersion::V1_3),
97            _ => Err(UnsupportedEMLVersion(s.to_owned())),
98        }
99    }
100}
101
102/// EML_NL version is not known to this version of the library.
103#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
104#[error("Unsupported EML_NL version: {0}")]
105pub struct UnsupportedEMLVersion(String);
106
107impl From<UnsupportedEMLVersion> for EMLError {
108    fn from(value: UnsupportedEMLVersion) -> Self {
109        EMLErrorKind::UnsupportedEMLVersion(value.0).without_span()
110    }
111}
112
113/// A range of EML_NL versions for which an EML_NL feature is supported.
114pub struct EMLVersionRange {
115    /// Inclusive lower bound of the range (i.e. first version that supports the feature)
116    since: Option<EMLVersion>,
117    /// Exclusive upper bound of the range (i.e. first version that does not support the feature)
118    until: Option<EMLVersion>,
119}
120
121impl EMLVersionRange {
122    /// A range that covers all EML_NL versions.
123    pub const ALL: Self = Self {
124        since: None,
125        until: None,
126    };
127
128    /// A range that covers all EML_NL versions since the given version (inclusive).
129    pub const fn since(version: EMLVersion) -> Self {
130        Self {
131            since: Some(version),
132            until: None,
133        }
134    }
135
136    /// A range that covers all EML_NL versions until the given version (exclusive).
137    pub const fn until(version: EMLVersion) -> Self {
138        Self {
139            since: None,
140            until: Some(version),
141        }
142    }
143
144    /// A range that covers all EML_NL versions between the given versions
145    /// (including the lower bound, but excluding the upper bound).
146    pub const fn between(version: EMLVersion, until: EMLVersion) -> Self {
147        Self {
148            since: Some(version),
149            until: Some(until),
150        }
151    }
152
153    /// Returns whether the given version is within this range.
154    pub fn contains(&self, version: EMLVersion) -> bool {
155        match (self.since, self.until) {
156            (None, None) => true,
157            (Some(since), Some(until)) => version >= since && version < until,
158            (Some(since), None) => version >= since,
159            (None, Some(until)) => version < until,
160        }
161    }
162}
163
164#[cfg(test)]
165mod tests {
166    use super::*;
167
168    #[test]
169    fn test_eml_version_to_str() {
170        assert_eq!(EMLVersion::V1_3_1.to_str(), Some("1.3.1"));
171        assert_eq!(EMLVersion::V1_3.to_str(), Some("1.3"));
172        assert_eq!(EMLVersion::V1_2_2.to_str(), None);
173    }
174
175    #[test]
176    fn test_eml_version_from_str() {
177        assert_eq!("1.3.1".parse::<EMLVersion>(), Ok(EMLVersion::V1_3_1));
178        assert_eq!("1.3".parse::<EMLVersion>(), Ok(EMLVersion::V1_3));
179        assert!("1.2".parse::<EMLVersion>().is_err());
180    }
181
182    #[test]
183    fn test_comparison_of_eml_versions() {
184        assert!(EMLVersion::V1_3 > EMLVersion::V1_2_2);
185        assert!(EMLVersion::V1_3 >= EMLVersion::V1_3);
186        assert!(EMLVersion::V1_2_2 <= EMLVersion::V1_3);
187        assert!(EMLVersion::V1_2_2 < EMLVersion::V1_3);
188        assert!(EMLVersion::V1_3 != EMLVersion::V1_2_2);
189        assert!(EMLVersion::V1_3 == EMLVersion::V1_3);
190    }
191}