Skip to main content

eml_nl/documents/
election_result.rs

1//! Document variant for the EML_NL Result (`520`) document.
2
3use std::{ops::Deref, str::FromStr};
4
5use crate::{
6    EML_SCHEMA_VERSION, EMLError, EMLErrorKind, EMLResultExt as _, NS_EML, NS_KR,
7    common::{
8        CandidateIdentifier, CanonicalizationMethod, ContestIdentifier, CreationDateTime,
9        ElectionDomain, ManagingAuthority, MinimalQualifyingAddress, PersonNameStructure,
10        TransactionId,
11    },
12    documents::{ElectionIdentifierBuilder, accepted_root},
13    io::{
14        EMLElement, EMLElementReader, EMLElementWriter, EMLReadElement as _, EMLWriteElement as _,
15        QualifiedName, collect_struct,
16    },
17    utils::{
18        AffiliationId, ElectionCategory, ElectionId, ElectionSubcategory, Gender, StringValue,
19        StringValueData, XsDate, XsDateTime,
20    },
21};
22
23pub(crate) const EML_ELECTION_RESULT_ID: &str = "520";
24
25/// Representing a `110a` document, containing an election definition.
26#[derive(Debug, Clone)]
27pub struct ElectionResult {
28    /// Transaction id of the document.
29    pub transaction_id: TransactionId,
30
31    /// Managing authority of the election, if present.
32    pub managing_authority: ManagingAuthority,
33
34    /// Time this document was created.
35    pub creation_date_time: CreationDateTime,
36
37    /// Canonicalization method used in this document, if present.
38    pub canonicalization_method: Option<CanonicalizationMethod>,
39
40    /// The result data.
41    pub result: ElectionResultResult,
42}
43
44impl ElectionResult {
45    /// Builder for creating a new instance.
46    pub fn builder() -> ElectionResultBuilder {
47        ElectionResultBuilder::new()
48    }
49}
50
51impl FromStr for ElectionResult {
52    type Err = EMLError;
53
54    fn from_str(s: &str) -> Result<Self, Self::Err> {
55        use crate::io::EMLRead as _;
56        Self::parse_eml(s, crate::io::EMLParsingMode::Strict).ok()
57    }
58}
59
60impl TryFrom<&str> for ElectionResult {
61    type Error = EMLError;
62
63    fn try_from(value: &str) -> Result<Self, Self::Error> {
64        use crate::io::EMLRead as _;
65        Self::parse_eml(value, crate::io::EMLParsingMode::Strict).ok()
66    }
67}
68
69impl TryFrom<ElectionResult> for String {
70    type Error = EMLError;
71
72    fn try_from(value: ElectionResult) -> Result<Self, Self::Error> {
73        use crate::io::EMLWrite as _;
74        value.write_eml_root_str(true, true)
75    }
76}
77
78/// Builder for [`ElectionResult`].
79#[derive(Debug, Clone)]
80pub struct ElectionResultBuilder {
81    transaction_id: Option<TransactionId>,
82    managing_authority: Option<ManagingAuthority>,
83    creation_date_time: Option<CreationDateTime>,
84    canonicalization_method: Option<CanonicalizationMethod>,
85    result: Option<ElectionResultResult>,
86    election_identifier: Option<ElectionResultElectionIdentifier>,
87    contests: Vec<ElectionResultContest>,
88}
89
90impl ElectionResultBuilder {
91    /// Create a new builder for [`ElectionResult`].
92    pub fn new() -> Self {
93        Self {
94            transaction_id: None,
95            managing_authority: None,
96            creation_date_time: None,
97            canonicalization_method: None,
98            result: None,
99            election_identifier: None,
100            contests: vec![],
101        }
102    }
103
104    /// Set the transaction id of the election result document.
105    pub fn transaction_id(mut self, transaction_id: impl Into<TransactionId>) -> Self {
106        self.transaction_id = Some(transaction_id.into());
107        self
108    }
109
110    /// Set the managing authority of the election result document.
111    pub fn managing_authority(mut self, managing_authority: impl Into<ManagingAuthority>) -> Self {
112        self.managing_authority = Some(managing_authority.into());
113        self
114    }
115
116    /// Set the creation date and time of the election result document.
117    pub fn creation_date_time(mut self, creation_date_time: impl Into<XsDateTime>) -> Self {
118        self.creation_date_time = Some(CreationDateTime::new(creation_date_time.into()));
119        self
120    }
121
122    /// Set the canonicalization method used in the election result document.
123    pub fn canonicalization_method(
124        mut self,
125        canonicalization_method: impl Into<CanonicalizationMethod>,
126    ) -> Self {
127        self.canonicalization_method = Some(canonicalization_method.into());
128        self
129    }
130
131    /// Set the result data of the election result document.
132    ///
133    /// You can use this to set the entire result at once, or you can use the
134    /// [`Self::election_identifier`], [`Self::contests`] and/or [`Self::push_contest`]
135    /// methods to set the election identifier and contests separately and let
136    /// this builder build the result data for you.
137    pub fn result(mut self, result: impl Into<ElectionResultResult>) -> Self {
138        self.result = Some(result.into());
139        self
140    }
141
142    /// Set the election identifier for the election result document.
143    ///
144    /// Has no effect if the result is already set using the [`Self::result`] method.
145    pub fn election_identifier(
146        mut self,
147        election_identifier: impl Into<ElectionResultElectionIdentifier>,
148    ) -> Self {
149        self.election_identifier = Some(election_identifier.into());
150        self
151    }
152
153    /// Set the contests for the election result document, this overrides any previously set contests.
154    ///
155    /// Has no effect if the result is already set using the [`Self::result`] method.
156    pub fn contests(mut self, contests: impl Into<Vec<ElectionResultContest>>) -> Self {
157        self.contests = contests.into();
158        self
159    }
160
161    /// Add a contest to the election result document.
162    ///
163    /// Has no effect if the result is already set using the [`Self::result`] method.
164    pub fn push_contest(mut self, contest: impl Into<ElectionResultContest>) -> Self {
165        self.contests.push(contest.into());
166        self
167    }
168
169    /// Build the [`ElectionResult`] from the provided data, returning an error if any required fields are missing.
170    pub fn build(self) -> Result<ElectionResult, EMLError> {
171        Ok(ElectionResult {
172            transaction_id: self.transaction_id.ok_or_else(|| {
173                EMLErrorKind::MissingBuildProperty("transaction_id").without_span()
174            })?,
175            managing_authority: self.managing_authority.ok_or_else(|| {
176                EMLErrorKind::MissingBuildProperty("managing_authority").without_span()
177            })?,
178            creation_date_time: self.creation_date_time.ok_or_else(|| {
179                EMLErrorKind::MissingBuildProperty("creation_date_time").without_span()
180            })?,
181            canonicalization_method: self.canonicalization_method,
182            result: self.result.map_or_else(
183                || {
184                    let election_identifier = self.election_identifier.ok_or_else(|| {
185                        EMLErrorKind::MissingBuildProperty("election_identifier").without_span()
186                    })?;
187
188                    if self.contests.is_empty() {
189                        return Err(EMLErrorKind::MissingBuildProperty("contests").without_span());
190                    }
191                    Ok(ElectionResultResult::new(ElectionResultElection::new(
192                        election_identifier,
193                        self.contests,
194                    )))
195                },
196                Ok,
197            )?,
198        })
199    }
200}
201
202impl Default for ElectionResultBuilder {
203    fn default() -> Self {
204        Self::new()
205    }
206}
207
208impl EMLElement for ElectionResult {
209    const EML_NAME: QualifiedName<'_, '_> = QualifiedName::from_static("EML", Some(NS_EML));
210
211    fn read_eml(elem: &mut EMLElementReader<'_, '_>) -> Result<Self, EMLError> {
212        // TODO: parse the rest of the document
213        accepted_root(elem)?;
214
215        let document_id = elem.attribute_value_req(("Id", None))?;
216        if document_id != EML_ELECTION_RESULT_ID {
217            return Err(EMLErrorKind::InvalidDocumentType(
218                EML_ELECTION_RESULT_ID,
219                document_id.to_string(),
220            ))
221            .with_span(elem.span());
222        }
223
224        Ok(collect_struct!(elem, ElectionResult {
225            transaction_id: TransactionId::EML_NAME => |elem| TransactionId::read_eml(elem)?,
226            managing_authority: ManagingAuthority::EML_NAME => |elem| ManagingAuthority::read_eml(elem)?,
227            creation_date_time: CreationDateTime::EML_NAME => |elem| CreationDateTime::read_eml(elem)?,
228            canonicalization_method as Option: CanonicalizationMethod::EML_NAME => |elem| CanonicalizationMethod::read_eml(elem)?,
229            result: ElectionResultResult::EML_NAME => |elem| ElectionResultResult::read_eml(elem)?,
230        }))
231    }
232
233    fn write_eml(&self, writer: EMLElementWriter) -> Result<(), EMLError> {
234        writer
235            .attr(("Id", None), EML_ELECTION_RESULT_ID)?
236            .attr(("SchemaVersion", None), EML_SCHEMA_VERSION)?
237            .child_elem(TransactionId::EML_NAME, &self.transaction_id)?
238            .child_elem(ManagingAuthority::EML_NAME, &self.managing_authority)?
239            .child_elem(CreationDateTime::EML_NAME, &self.creation_date_time)?
240            // Note: we don't output the CanonicalizationMethod because we aren't canonicalizing our output
241            // .child_elem_option(
242            //     CanonicalizationMethod::EML_NAME,
243            //     self.canonicalization_method.as_ref(),
244            // )?
245            .child_elem(ElectionResultResult::EML_NAME, &self.result)?
246            .finish()
247    }
248}
249
250/// The result data of an election result document.
251#[derive(Debug, Clone)]
252pub struct ElectionResultResult {
253    /// The election for which the result applies.
254    pub election: ElectionResultElection,
255}
256
257impl ElectionResultResult {
258    /// Create a new result from the given election.
259    pub fn new(election: impl Into<ElectionResultElection>) -> Self {
260        ElectionResultResult {
261            election: election.into(),
262        }
263    }
264}
265
266impl EMLElement for ElectionResultResult {
267    const EML_NAME: QualifiedName<'_, '_> = QualifiedName::from_static("Result", Some(NS_EML));
268
269    fn read_eml(elem: &mut EMLElementReader<'_, '_>) -> Result<Self, EMLError> {
270        Ok(collect_struct!(elem, ElectionResultResult {
271            election: ElectionResultElection::EML_NAME => |elem| ElectionResultElection::read_eml(elem)?,
272        }))
273    }
274
275    fn write_eml(&self, writer: EMLElementWriter) -> Result<(), EMLError> {
276        writer
277            .child_elem(ElectionResultElection::EML_NAME, &self.election)?
278            .finish()
279    }
280}
281
282/// The election for which the result applies.
283#[derive(Debug, Clone)]
284pub struct ElectionResultElection {
285    /// Identifier for the election.
286    pub identifier: ElectionResultElectionIdentifier,
287
288    /// Contests within the election.
289    pub contests: Vec<ElectionResultContest>,
290}
291
292impl ElectionResultElection {
293    /// Create a new election result election from the given identifier and contests.
294    pub fn new(
295        identifier: impl Into<ElectionResultElectionIdentifier>,
296        contests: impl Into<Vec<ElectionResultContest>>,
297    ) -> Self {
298        ElectionResultElection {
299            identifier: identifier.into(),
300            contests: contests.into(),
301        }
302    }
303}
304
305impl EMLElement for ElectionResultElection {
306    const EML_NAME: QualifiedName<'_, '_> = QualifiedName::from_static("Election", Some(NS_EML));
307
308    fn read_eml(elem: &mut EMLElementReader<'_, '_>) -> Result<Self, EMLError> {
309        Ok(collect_struct!(elem, ElectionResultElection {
310            identifier: ElectionResultElectionIdentifier::EML_NAME => |elem| ElectionResultElectionIdentifier::read_eml(elem)?,
311            contests as Vec: ElectionResultContest::EML_NAME => |elem| ElectionResultContest::read_eml(elem)?,
312        }))
313    }
314
315    fn write_eml(&self, writer: EMLElementWriter) -> Result<(), EMLError> {
316        writer
317            .child_elem(ElectionResultElectionIdentifier::EML_NAME, &self.identifier)?
318            .child_elems(ElectionResultContest::EML_NAME, &self.contests)?
319            .finish()
320    }
321}
322
323/// Identifier for the election for which the result applies.
324#[derive(Debug, Clone)]
325pub struct ElectionResultElectionIdentifier {
326    /// Id of the election
327    pub id: StringValue<ElectionId>,
328
329    /// Name of the election
330    pub name: Option<String>,
331
332    /// Category of the election
333    pub category: StringValue<ElectionCategory>,
334
335    /// Subcategory of the election
336    pub subcategory: Option<StringValue<ElectionSubcategory>>,
337
338    /// The (top level) region where the election takes place.
339    pub domain: Option<ElectionDomain>,
340
341    /// Date of the election
342    pub election_date: StringValue<XsDate>,
343}
344
345impl ElectionResultElectionIdentifier {
346    /// Builder for creating a new instance.
347    pub fn builder() -> ElectionIdentifierBuilder {
348        ElectionIdentifierBuilder::new()
349    }
350}
351
352impl EMLElement for ElectionResultElectionIdentifier {
353    const EML_NAME: QualifiedName<'_, '_> =
354        QualifiedName::from_static("ElectionIdentifier", Some(NS_EML));
355
356    fn read_eml(elem: &mut EMLElementReader<'_, '_>) -> Result<Self, EMLError> {
357        Ok(collect_struct!(
358            elem,
359            ElectionResultElectionIdentifier {
360                id: elem.string_value_attr("Id", None)?,
361                name as Option: ("ElectionName", NS_EML) => |elem| elem.text_without_children()?,
362                category: ("ElectionCategory", NS_EML) => |elem| elem.string_value()?,
363                subcategory as Option: ("ElectionSubcategory", NS_KR) => |elem| elem.string_value()?,
364                domain as Option: ElectionDomain::EML_NAME => |elem| ElectionDomain::read_eml(elem)?,
365                election_date: ("ElectionDate", NS_KR) => |elem| elem.string_value()?,
366            }
367        ))
368    }
369
370    fn write_eml(&self, writer: EMLElementWriter) -> Result<(), EMLError> {
371        writer
372            .attr("Id", self.id.raw().as_ref())?
373            .child_option(
374                ("ElectionName", NS_EML),
375                self.name.as_ref(),
376                |elem, value| elem.text(value.as_ref())?.finish(),
377            )?
378            .child(("ElectionCategory", NS_EML), |elem| {
379                elem.text(self.category.raw().as_ref())?.finish()
380            })?
381            .child_option(
382                ("ElectionSubcategory", NS_KR),
383                self.subcategory.as_ref(),
384                |elem, value| elem.text(value.raw().as_ref())?.finish(),
385            )?
386            .child_elem_option(ElectionDomain::EML_NAME, self.domain.as_ref())?
387            .child(("ElectionDate", NS_KR), |elem| {
388                elem.text(self.election_date.raw().as_ref())?.finish()
389            })?
390            .finish()
391    }
392}
393
394/// A contest within an election result.
395#[derive(Debug, Clone)]
396pub struct ElectionResultContest {
397    /// Identifier of the contest.
398    pub identifier: ContestIdentifier,
399
400    /// Selections within the contest.
401    pub selections: Vec<ElectionResultSelection>,
402}
403
404impl ElectionResultContest {
405    /// Create a new election result contest from the given identifier and selections.
406    pub fn new(
407        identifier: impl Into<ContestIdentifier>,
408        selections: impl Into<Vec<ElectionResultSelection>>,
409    ) -> Self {
410        ElectionResultContest {
411            identifier: identifier.into(),
412            selections: selections.into(),
413        }
414    }
415}
416
417impl EMLElement for ElectionResultContest {
418    const EML_NAME: QualifiedName<'_, '_> = QualifiedName::from_static("Contest", Some(NS_EML));
419
420    fn read_eml(elem: &mut EMLElementReader<'_, '_>) -> Result<Self, EMLError> {
421        Ok(collect_struct!(elem, ElectionResultContest {
422            identifier: ContestIdentifier::EML_NAME => |elem| ContestIdentifier::read_eml(elem)?,
423            selections as Vec: ElectionResultSelection::EML_NAME => |elem| ElectionResultSelection::read_eml(elem)?,
424        }))
425    }
426
427    fn write_eml(&self, writer: EMLElementWriter) -> Result<(), EMLError> {
428        writer
429            .child_elem(ContestIdentifier::EML_NAME, &self.identifier)?
430            .child_elems(ElectionResultSelection::EML_NAME, &self.selections)?
431            .finish()
432    }
433}
434
435/// The ranking of a selection.
436#[derive(Debug, Clone, PartialEq, Eq)]
437pub enum RankingType {
438    /// First ranked selection.
439    First,
440    /// Second ranked selection.
441    Second,
442}
443
444impl RankingType {
445    fn from_str(s: &str) -> Option<Self> {
446        match s {
447            "1" => Some(RankingType::First),
448            "2" => Some(RankingType::Second),
449            _ => None,
450        }
451    }
452
453    fn as_str(&self) -> &'static str {
454        match self {
455            RankingType::First => "1",
456            RankingType::Second => "2",
457        }
458    }
459}
460
461/// Error type for invalid ranking type values.
462#[derive(Debug, Clone, thiserror::Error)]
463#[error("Invalid ranking type value: {0}")]
464pub struct InvalidRankingTypeError(String);
465
466impl StringValueData for RankingType {
467    type Error = InvalidRankingTypeError;
468
469    fn parse_from_str(s: &str) -> Result<Self, Self::Error> {
470        RankingType::from_str(s).ok_or_else(|| InvalidRankingTypeError(s.to_string()))
471    }
472
473    fn to_raw_value(&self) -> String {
474        self.as_str().to_string()
475    }
476}
477
478/// Yes/no type, represented as a boolean.
479/// In the EML, this is represented as "yes" or "no".
480#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
481pub struct YesNoType(bool);
482
483impl Deref for YesNoType {
484    type Target = bool;
485
486    fn deref(&self) -> &Self::Target {
487        &self.0
488    }
489}
490
491impl YesNoType {
492    /// Create a new YesNoType from a boolean value.
493    pub fn new(value: bool) -> Self {
494        YesNoType(value)
495    }
496
497    /// Create a new YesNoType from an EML string ("yes" or "no").
498    pub fn from_eml_value(s: &str) -> Result<Self, InvalidYesNoTypeError> {
499        match s {
500            "yes" => Ok(YesNoType(true)),
501            "no" => Ok(YesNoType(false)),
502            _ => Err(InvalidYesNoTypeError(s.to_string())),
503        }
504    }
505
506    /// Returns the string representation of the value ("yes" or "no").
507    pub fn to_eml_value(&self) -> &'static str {
508        if self.0 { "yes" } else { "no" }
509    }
510
511    /// Returns `true` if the value is "yes".
512    pub fn is_yes(&self) -> bool {
513        self.0
514    }
515
516    /// Returns `true` if the value is "no".
517    pub fn is_no(&self) -> bool {
518        !self.0
519    }
520}
521
522impl From<bool> for YesNoType {
523    fn from(value: bool) -> Self {
524        YesNoType::new(value)
525    }
526}
527
528/// Error type for invalid yes/no type values.
529#[derive(Debug, Clone, thiserror::Error)]
530#[error("Invalid yes/no type value: {0}")]
531pub struct InvalidYesNoTypeError(String);
532
533impl StringValueData for YesNoType {
534    type Error = InvalidYesNoTypeError;
535
536    fn parse_from_str(s: &str) -> Result<Self, Self::Error> {
537        Self::from_eml_value(s)
538    }
539
540    fn to_raw_value(&self) -> String {
541        self.to_eml_value().to_string()
542    }
543}
544
545/// A selection within an election contest.
546#[derive(Debug, Clone)]
547pub struct ElectionResultSelection {
548    /// The type of selection.
549    pub selection_type: ElectionResultSelectionType,
550
551    /// Number of votes received.
552    pub votes: Option<StringValue<u64>>,
553
554    /// Ranking of the selection, if applicable.
555    pub ranking: Option<StringValue<RankingType>>,
556
557    /// Whether the selection was elected.
558    pub elected: StringValue<YesNoType>,
559}
560
561impl ElectionResultSelection {
562    /// Create a builder for creating a new [`ElectionResultSelection`] instance.
563    pub fn builder() -> ElectionResultSelectionBuilder {
564        ElectionResultSelectionBuilder::new()
565    }
566}
567
568/// Builder for [`ElectionResultSelection`].
569#[derive(Debug, Clone)]
570pub struct ElectionResultSelectionBuilder {
571    selection_type: Option<ElectionResultSelectionType>,
572    votes: Option<StringValue<u64>>,
573    ranking: Option<StringValue<RankingType>>,
574    elected: Option<StringValue<YesNoType>>,
575}
576
577impl ElectionResultSelectionBuilder {
578    /// Create a new builder for [`ElectionResultSelection`].
579    pub fn new() -> Self {
580        Self {
581            selection_type: None,
582            votes: None,
583            ranking: None,
584            elected: None,
585        }
586    }
587
588    /// Set the selection type to a candidate selection with the given candidate.
589    pub fn candidate(mut self, candidate: impl Into<CandidateSelection>) -> Self {
590        self.selection_type = Some(ElectionResultSelectionType::Candidate(Box::new(
591            candidate.into(),
592        )));
593        self
594    }
595
596    /// Set the selection type to an affiliation selection with the given affiliation.
597    pub fn affiliation(mut self, affiliation: impl Into<AffiliationSelection>) -> Self {
598        self.selection_type = Some(ElectionResultSelectionType::Affiliation(Box::new(
599            affiliation.into(),
600        )));
601        self
602    }
603
604    /// Set the number of votes received for this selection.
605    pub fn votes(mut self, votes: impl Into<u64>) -> Self {
606        self.votes = Some(StringValue::from_value(votes.into()));
607        self
608    }
609
610    /// Set the ranking of this selection.
611    pub fn ranking(mut self, ranking: impl Into<RankingType>) -> Self {
612        self.ranking = Some(StringValue::from_value(ranking.into()));
613        self
614    }
615
616    /// Set whether this selection was elected.
617    pub fn elected(mut self, elected: impl Into<YesNoType>) -> Self {
618        self.elected = Some(StringValue::from_value(elected.into()));
619        self
620    }
621
622    /// Build the [`ElectionResultSelection`] from the provided data, returning an error if any required fields are missing.
623    pub fn build(self) -> Result<ElectionResultSelection, EMLError> {
624        Ok(ElectionResultSelection {
625            selection_type: self.selection_type.ok_or_else(|| {
626                EMLErrorKind::MissingBuildProperty("selection_type").without_span()
627            })?,
628            votes: self.votes,
629            ranking: self.ranking,
630            elected: self
631                .elected
632                .ok_or_else(|| EMLErrorKind::MissingBuildProperty("elected").without_span())?,
633        })
634    }
635}
636
637impl Default for ElectionResultSelectionBuilder {
638    fn default() -> Self {
639        Self::new()
640    }
641}
642
643const VOTES_EML_NAME: QualifiedName<'_, '_> = QualifiedName::from_static("Votes", Some(NS_EML));
644const RANKING_EML_NAME: QualifiedName<'_, '_> = QualifiedName::from_static("Ranking", Some(NS_EML));
645const ELECTED_EML_NAME: QualifiedName<'_, '_> = QualifiedName::from_static("Elected", Some(NS_EML));
646
647impl EMLElement for ElectionResultSelection {
648    const EML_NAME: QualifiedName<'_, '_> = QualifiedName::from_static("Selection", Some(NS_EML));
649
650    fn read_eml(elem: &mut EMLElementReader<'_, '_>) -> Result<Self, EMLError> {
651        let mut selection_type = None;
652        let mut votes = None;
653        let mut ranking = None;
654        let mut elected = None;
655
656        while let Some(mut child) = elem.next_child()? {
657            let name = child.name()?;
658
659            match name {
660                n if n == CandidateSelection::EML_NAME => {
661                    selection_type = Some(ElectionResultSelectionType::Candidate(Box::new(
662                        CandidateSelection::read_eml(&mut child)?,
663                    )));
664                }
665                n if n == AffiliationSelection::EML_NAME => {
666                    selection_type = Some(ElectionResultSelectionType::Affiliation(Box::new(
667                        AffiliationSelection::read_eml(&mut child)?,
668                    )));
669                }
670                n if n == VOTES_EML_NAME => {
671                    votes = Some(child.string_value()?);
672                }
673                n if n == RANKING_EML_NAME => {
674                    ranking = Some(child.string_value()?);
675                }
676                n if n == ELECTED_EML_NAME => {
677                    elected = Some(child.string_value()?);
678                }
679                n => {
680                    let err =
681                        EMLErrorKind::UnexpectedElement(n.as_owned(), Self::EML_NAME.as_owned())
682                            .with_span(child.inner_span());
683                    if child.parsing_mode().is_strict() {
684                        return Err(err);
685                    } else {
686                        child.push_err(err);
687                        child.skip()?;
688                    }
689                }
690            }
691        }
692
693        let elected = elected.unwrap_or_else(|| StringValue::from_value(YesNoType::new(false)));
694
695        Ok(ElectionResultSelection {
696            selection_type: selection_type
697                .ok_or_else(|| EMLErrorKind::MissingSelectionType.with_span(elem.inner_span()))?,
698            votes,
699            ranking,
700            elected,
701        })
702    }
703
704    fn write_eml(&self, writer: EMLElementWriter) -> Result<(), EMLError> {
705        let writer = match &self.selection_type {
706            ElectionResultSelectionType::Candidate(candidate_selection) => {
707                writer.child_elem(CandidateSelection::EML_NAME, candidate_selection.as_ref())?
708            }
709            ElectionResultSelectionType::Affiliation(affiliation_selection) => writer.child_elem(
710                AffiliationSelection::EML_NAME,
711                affiliation_selection.as_ref(),
712            )?,
713        };
714        let writer = writer
715            .child_option(VOTES_EML_NAME, self.votes.as_ref(), |elem, value| {
716                elem.text(value.raw().as_ref())?.finish()
717            })?
718            .child_option(RANKING_EML_NAME, self.ranking.as_ref(), |elem, value| {
719                elem.text(value.raw().as_ref())?.finish()
720            })?;
721
722        if self.elected.copied_value().ok() == Some(YesNoType(true)) {
723            writer
724                .child(ELECTED_EML_NAME, |elem| {
725                    elem.text(self.elected.raw().as_ref())?.finish()
726                })?
727                .finish()
728        } else {
729            writer.finish()
730        }
731    }
732}
733
734/// The type of selection.
735#[derive(Debug, Clone)]
736pub enum ElectionResultSelectionType {
737    /// Selection of a candidate.
738    Candidate(Box<CandidateSelection>),
739    /// Selection of an affiliation.
740    Affiliation(Box<AffiliationSelection>),
741}
742
743/// Selection of a candidate.
744#[derive(Debug, Clone)]
745pub struct CandidateSelection {
746    /// Identifier of the candidate.
747    pub identifier: CandidateIdentifier,
748
749    /// Name of the candidate.
750    pub name: PersonNameStructure,
751
752    /// Gender of the candidate.
753    pub gender: Option<StringValue<Gender>>,
754
755    /// The minimal qualifying address of the candidate, if present.
756    pub qualifying_address: MinimalQualifyingAddress,
757}
758
759impl CandidateSelection {
760    /// Create a new builder for building an instance.
761    pub fn builder() -> CandidateSelectionBuilder {
762        CandidateSelectionBuilder::new()
763    }
764}
765
766/// A builder for [`CandidateSelection`].
767#[derive(Debug, Clone)]
768pub struct CandidateSelectionBuilder {
769    identifier: Option<CandidateIdentifier>,
770    name: Option<PersonNameStructure>,
771    gender: Option<StringValue<Gender>>,
772    qualifying_address: Option<MinimalQualifyingAddress>,
773    locality_name: Option<String>,
774    country_name_code: Option<String>,
775}
776
777impl CandidateSelectionBuilder {
778    /// Create a new CandidateSelectionBuilder for building [`CandidateSelection`] instances.
779    pub fn new() -> Self {
780        CandidateSelectionBuilder {
781            identifier: None,
782            name: None,
783            gender: None,
784            qualifying_address: None,
785            locality_name: None,
786            country_name_code: None,
787        }
788    }
789
790    /// Set the identifier of the candidate selection.
791    pub fn identifier(mut self, identifier: impl Into<CandidateIdentifier>) -> Self {
792        self.identifier = Some(identifier.into());
793        self
794    }
795
796    /// Set the name of the candidate selection.
797    pub fn name(mut self, name: impl Into<PersonNameStructure>) -> Self {
798        self.name = Some(name.into());
799        self
800    }
801
802    /// Set the gender of the candidate selection.
803    pub fn gender(mut self, gender: impl Into<Gender>) -> Self {
804        self.gender = Some(StringValue::from_value(gender.into()));
805        self
806    }
807
808    /// Set the minimal qualifying address of the candidate selection.
809    ///
810    /// You may also set the locality name and country name code separately
811    /// using the [`Self::locality_name`] and [`Self::country_name_code`] methods.
812    pub fn qualifying_address(
813        mut self,
814        qualifying_address: impl Into<MinimalQualifyingAddress>,
815    ) -> Self {
816        self.qualifying_address = Some(qualifying_address.into());
817        self
818    }
819
820    /// Set the locality name for the candidate selection.
821    ///
822    /// Has no effect if the qualifying address is already set using the
823    /// [`Self::qualifying_address`] method.
824    pub fn locality_name(mut self, locality_name: impl Into<String>) -> Self {
825        self.locality_name = Some(locality_name.into());
826        self
827    }
828
829    /// Set the country name code for the candidate selection.
830    ///
831    /// Has no effect if the qualifying address is already set using the
832    /// [`Self::qualifying_address`] method.
833    pub fn country_name_code(mut self, country_name_code: impl Into<String>) -> Self {
834        self.country_name_code = Some(country_name_code.into());
835        self
836    }
837
838    /// Build a CandidateSelection, returning an error if any required properties are missing.
839    pub fn build(self) -> Result<CandidateSelection, EMLError> {
840        Ok(CandidateSelection {
841            identifier: self
842                .identifier
843                .ok_or_else(|| EMLErrorKind::MissingBuildProperty("identifier").without_span())?,
844            name: self
845                .name
846                .ok_or_else(|| EMLErrorKind::MissingBuildProperty("name").without_span())?,
847            gender: self.gender,
848            qualifying_address: self.qualifying_address.map_or_else(
849                || {
850                    let locality_name = self.locality_name.ok_or_else(|| {
851                        EMLErrorKind::MissingBuildProperty("locality_name").without_span()
852                    })?;
853
854                    if let Some(country_name_code) = self.country_name_code {
855                        Ok(MinimalQualifyingAddress::new_country(
856                            country_name_code,
857                            locality_name,
858                        ))
859                    } else {
860                        Ok(MinimalQualifyingAddress::new_locality(locality_name))
861                    }
862                },
863                Ok,
864            )?,
865        })
866    }
867}
868
869impl Default for CandidateSelectionBuilder {
870    fn default() -> Self {
871        Self::new()
872    }
873}
874
875impl EMLElement for CandidateSelection {
876    const EML_NAME: QualifiedName<'_, '_> = QualifiedName::from_static("Candidate", Some(NS_EML));
877
878    fn read_eml(elem: &mut EMLElementReader<'_, '_>) -> Result<Self, EMLError> {
879        Ok(collect_struct!(elem, CandidateSelection {
880            identifier: CandidateIdentifier::EML_NAME => |elem| CandidateIdentifier::read_eml(elem)?,
881            name: ("CandidateFullName", NS_EML) => |elem| PersonNameStructure::read_eml_element(elem)?,
882            gender as Option: ("Gender", NS_EML) => |elem| elem.string_value()?,
883            qualifying_address: MinimalQualifyingAddress::EML_NAME => |elem| MinimalQualifyingAddress::read_eml(elem)?,
884        }))
885    }
886
887    fn write_eml(&self, writer: EMLElementWriter) -> Result<(), EMLError> {
888        writer
889            .child_elem(CandidateIdentifier::EML_NAME, &self.identifier)?
890            .child(("CandidateFullName", NS_EML), |elem| {
891                self.name.write_eml_element(elem)
892            })?
893            .child_option(("Gender", NS_EML), self.gender.as_ref(), |elem, value| {
894                elem.text(value.raw().as_ref())?.finish()
895            })?
896            .child_elem(MinimalQualifyingAddress::EML_NAME, &self.qualifying_address)?
897            .finish()
898    }
899}
900
901/// Selection of an affiliation.
902#[derive(Debug, Clone)]
903pub struct AffiliationSelection {
904    /// Id of the affiliation.
905    pub id: StringValue<AffiliationId>,
906
907    /// Name of the affiliation.
908    pub name: String,
909}
910
911impl AffiliationSelection {
912    /// Create a new AffiliationSelection with the given id and name.
913    pub fn new(id: impl Into<AffiliationId>, name: impl Into<String>) -> Self {
914        AffiliationSelection {
915            id: StringValue::from_value(id.into()),
916            name: name.into(),
917        }
918    }
919}
920
921impl EMLElement for AffiliationSelection {
922    const EML_NAME: QualifiedName<'_, '_> =
923        QualifiedName::from_static("AffiliationIdentifier", Some(NS_EML));
924
925    fn read_eml(elem: &mut EMLElementReader<'_, '_>) -> Result<Self, EMLError> {
926        Ok(collect_struct!(elem, AffiliationSelection {
927            id: elem.string_value_attr("Id", None)?,
928            name: ("RegisteredName", NS_EML) => |elem| elem.text_without_children()?,
929        }))
930    }
931
932    fn write_eml(&self, writer: EMLElementWriter) -> Result<(), EMLError> {
933        writer
934            .attr("Id", self.id.raw().as_ref())?
935            .child(("RegisteredName", NS_EML), |elem| {
936                elem.text(self.name.as_ref())?.finish()
937            })?
938            .finish()
939    }
940}
941
942#[cfg(test)]
943mod tests {
944    use chrono::TimeZone as _;
945
946    use crate::{
947        common::{AuthorityIdentifier, PersonName},
948        io::{EMLParsingMode, EMLRead as _, EMLWrite},
949        utils::{AuthorityId, CandidateId},
950    };
951
952    use super::*;
953
954    #[test]
955    fn test_election_result_construction() {
956        let election_result = ElectionResult::builder()
957            .transaction_id(TransactionId::new(1))
958            .managing_authority(
959                AuthorityIdentifier::new(AuthorityId::new("1234").unwrap()).with_name("Place"),
960            )
961            .creation_date_time(chrono::Utc.with_ymd_and_hms(2024, 3, 17, 12, 0, 0).unwrap())
962            .election_identifier(
963                ElectionResultElectionIdentifier::builder()
964                    .id(ElectionId::new("GR2024_Place").unwrap())
965                    .name("Gemeenteraadsverkiezingen 2024")
966                    .category(ElectionCategory::GR)
967                    .election_date(XsDate::from_date(2024, 3, 17).unwrap())
968                    .build_for_result()
969                    .unwrap(),
970            )
971            .contests([ElectionResultContest::new(
972                ContestIdentifier::geen(),
973                [
974                    ElectionResultSelection::builder()
975                        .affiliation(AffiliationSelection::new(
976                            AffiliationId::new("1").unwrap(),
977                            "Example",
978                        ))
979                        .elected(true)
980                        .build()
981                        .unwrap(),
982                    ElectionResultSelection::builder()
983                        .candidate(
984                            CandidateSelection::builder()
985                                .identifier(CandidateId::new("1").unwrap())
986                                .name(PersonName::new("Smid").with_first_name("Example"))
987                                .locality_name("Locality")
988                                .country_name_code("NL")
989                                .build()
990                                .unwrap(),
991                        )
992                        .elected(true)
993                        .votes(100u64)
994                        .build()
995                        .unwrap(),
996                    ElectionResultSelection::builder()
997                        .candidate(
998                            CandidateSelection::builder()
999                                .identifier(CandidateId::new("2").unwrap())
1000                                .name(PersonName::new("Test").with_first_name("Example"))
1001                                .locality_name("Locality")
1002                                .country_name_code("NL")
1003                                .build()
1004                                .unwrap(),
1005                        )
1006                        .elected(false)
1007                        .votes(0u64)
1008                        .build()
1009                        .unwrap(),
1010                ],
1011            )])
1012            .build()
1013            .unwrap();
1014        let xml = election_result.write_eml_root_str(true, true).unwrap();
1015        assert_eq!(
1016            xml,
1017            include_str!("../../test-emls/election_result/eml520_construction_output.eml.xml")
1018        );
1019
1020        // check if it still is the same after a second parse and write
1021        let parsed = ElectionResult::parse_eml(&xml, EMLParsingMode::Strict).unwrap();
1022        let xml2 = parsed.write_eml_root_str(true, true).unwrap();
1023        assert_eq!(xml, xml2);
1024    }
1025}