Skip to main content

eml_nl/common/
candidate_identifier.rs

1use std::num::NonZeroU64;
2
3use crate::{
4    EMLError, NS_EML,
5    io::{EMLElement, EMLElementReader, EMLElementWriter, QualifiedName, collect_struct},
6    utils::{CandidateId, NameShortCode, StringValue},
7};
8
9/// Candidate identifier, but not for 510 document types.
10#[derive(Debug, Clone)]
11pub struct CandidateIdentifier {
12    /// The candidate id.
13    pub id: StringValue<CandidateId>,
14
15    /// The display order of the candidate.
16    pub display_order: Option<StringValue<NonZeroU64>>,
17
18    /// The short code of the candidate.
19    ///
20    /// Note: This can be specified either as an attribute or as a child
21    /// element, but the attribute takes precedence if both are present.
22    /// Additionally we only ever output it as an attribute for simplicity.
23    pub short_code: Option<StringValue<NameShortCode>>,
24
25    /// The expected confirmation reference of the candidate.
26    pub expected_confirmation_reference: Option<Box<str>>,
27}
28
29impl CandidateIdentifier {
30    /// Create a new CandidateIdentifier.
31    pub fn new(id: CandidateId) -> Self {
32        CandidateIdentifier {
33            id: StringValue::Parsed(id),
34            display_order: None,
35            short_code: None,
36            expected_confirmation_reference: None,
37        }
38    }
39
40    /// Set the display order of the candidate.
41    pub fn with_display_order(mut self, display_order: NonZeroU64) -> Self {
42        self.display_order = Some(StringValue::Parsed(display_order));
43        self
44    }
45
46    /// Set the short code of the candidate.
47    pub fn with_short_code(mut self, short_code: NameShortCode) -> Self {
48        self.short_code = Some(StringValue::Parsed(short_code));
49        self
50    }
51
52    /// Set the expected confirmation reference of the candidate.
53    pub fn with_expected_confirmation_reference(mut self, reference: impl Into<Box<str>>) -> Self {
54        self.expected_confirmation_reference = Some(reference.into());
55        self
56    }
57}
58
59impl From<CandidateId> for CandidateIdentifier {
60    fn from(value: CandidateId) -> Self {
61        CandidateIdentifier::new(value)
62    }
63}
64
65impl EMLElement for CandidateIdentifier {
66    const EML_NAME: QualifiedName<'_, '_> =
67        QualifiedName::from_static("CandidateIdentifier", Some(NS_EML));
68
69    fn read_eml(elem: &mut EMLElementReader<'_, '_>) -> Result<Self, EMLError> {
70        let id = elem.string_value_attr("Id", None)?;
71        let display_order = elem.string_value_attr_opt("DisplayOrder")?;
72        let short_code = elem.string_value_attr_opt("ShortCode")?;
73        let expected_confirmation_reference = elem
74            .attribute_value("ExpectedConfirmationReference")?
75            .map(|s| s.into());
76
77        struct CandidateIdentifierTmp {
78            short_code: Option<StringValue<NameShortCode>>,
79        }
80
81        let elem = collect_struct!(
82            elem,
83            CandidateIdentifierTmp {
84                short_code as Option: ("ShortCode", NS_EML) => |elem| elem.string_value()?,
85            }
86        );
87
88        Ok(CandidateIdentifier {
89            id,
90            display_order,
91            short_code: short_code.or(elem.short_code), // attribute takes precedence
92            expected_confirmation_reference,
93        })
94    }
95
96    fn write_eml(&self, writer: EMLElementWriter) -> Result<(), EMLError> {
97        writer
98            .attr("Id", &self.id.raw())?
99            .attr_opt("DisplayOrder", self.display_order.as_ref().map(|v| v.raw()))?
100            .attr_opt("ShortCode", self.short_code.as_ref().map(|v| v.raw()))?
101            .attr_opt(
102                "ExpectedConfirmationReference",
103                self.expected_confirmation_reference.as_ref(),
104            )?
105            .empty()
106    }
107}
108
109#[cfg(test)]
110mod tests {
111    use super::*;
112    use crate::io::{EMLParsingMode, EMLRead as _, test_write_eml_element, test_xml_fragment};
113
114    #[test]
115    fn test_simple_candidate_identifier() {
116        let xml = test_xml_fragment(
117            r#"
118            <CandidateIdentifier xmlns="urn:oasis:names:tc:evs:schema:eml" Id="1"/>
119            "#,
120        );
121        let can_id = CandidateIdentifier::parse_eml(&xml, EMLParsingMode::Strict).unwrap();
122        assert_eq!(
123            can_id.id,
124            StringValue::Parsed(CandidateId::new(NonZeroU64::new(1).unwrap()))
125        );
126        assert_eq!(can_id.display_order, None);
127        assert_eq!(can_id.short_code, None);
128        assert_eq!(can_id.expected_confirmation_reference, None);
129
130        let xml_output = test_write_eml_element(&can_id, &[NS_EML]).unwrap();
131        assert_eq!(xml_output, xml);
132    }
133
134    #[test]
135    fn test_all_attributes_candidate_identifier() {
136        let xml = test_xml_fragment(
137            r#"
138            <CandidateIdentifier xmlns="urn:oasis:names:tc:evs:schema:eml" Id="2254" DisplayOrder="2" ShortCode="1234" ExpectedConfirmationReference="Ref123"/>
139            "#,
140        );
141        let can_id = CandidateIdentifier::parse_eml(&xml, EMLParsingMode::Strict).unwrap();
142        assert_eq!(
143            can_id.id,
144            StringValue::Parsed(CandidateId::new(NonZeroU64::new(2254).unwrap()))
145        );
146        assert_eq!(
147            can_id.display_order,
148            Some(StringValue::Parsed(NonZeroU64::new(2).unwrap()))
149        );
150        assert_eq!(
151            can_id.short_code,
152            Some(StringValue::Parsed(NameShortCode::new("1234").unwrap()))
153        );
154        assert_eq!(
155            can_id.expected_confirmation_reference,
156            Some("Ref123".into())
157        );
158
159        let xml_output = test_write_eml_element(&can_id, &[NS_EML]).unwrap();
160        assert_eq!(xml_output, xml);
161    }
162
163    #[test]
164    fn test_candidate_identifier_construction() {
165        let can_id = CandidateIdentifier::new(CandidateId::new(NonZeroU64::new(5678).unwrap()))
166            .with_display_order(NonZeroU64::new(3).unwrap())
167            .with_short_code(NameShortCode::new("9876").unwrap())
168            .with_expected_confirmation_reference("reference");
169        assert_eq!(
170            can_id.id,
171            StringValue::Parsed(CandidateId::new(NonZeroU64::new(5678).unwrap()))
172        );
173        assert_eq!(
174            can_id.display_order,
175            Some(StringValue::Parsed(NonZeroU64::new(3).unwrap()))
176        );
177        assert_eq!(
178            can_id.short_code,
179            Some(StringValue::Parsed(NameShortCode::new("9876").unwrap()))
180        );
181        assert_eq!(
182            can_id.expected_confirmation_reference,
183            Some("reference".into())
184        );
185    }
186}