1use 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#[derive(Debug, Clone)]
27pub struct ElectionResult {
28 pub transaction_id: TransactionId,
30
31 pub managing_authority: ManagingAuthority,
33
34 pub creation_date_time: CreationDateTime,
36
37 pub canonicalization_method: Option<CanonicalizationMethod>,
39
40 pub result: ElectionResultResult,
42}
43
44impl ElectionResult {
45 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#[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 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 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 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 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 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 pub fn result(mut self, result: impl Into<ElectionResultResult>) -> Self {
138 self.result = Some(result.into());
139 self
140 }
141
142 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 pub fn contests(mut self, contests: impl Into<Vec<ElectionResultContest>>) -> Self {
157 self.contests = contests.into();
158 self
159 }
160
161 pub fn push_contest(mut self, contest: impl Into<ElectionResultContest>) -> Self {
165 self.contests.push(contest.into());
166 self
167 }
168
169 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 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 .child_elem(ElectionResultResult::EML_NAME, &self.result)?
246 .finish()
247 }
248}
249
250#[derive(Debug, Clone)]
252pub struct ElectionResultResult {
253 pub election: ElectionResultElection,
255}
256
257impl ElectionResultResult {
258 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#[derive(Debug, Clone)]
284pub struct ElectionResultElection {
285 pub identifier: ElectionResultElectionIdentifier,
287
288 pub contests: Vec<ElectionResultContest>,
290}
291
292impl ElectionResultElection {
293 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#[derive(Debug, Clone)]
325pub struct ElectionResultElectionIdentifier {
326 pub id: StringValue<ElectionId>,
328
329 pub name: Option<String>,
331
332 pub category: StringValue<ElectionCategory>,
334
335 pub subcategory: Option<StringValue<ElectionSubcategory>>,
337
338 pub domain: Option<ElectionDomain>,
340
341 pub election_date: StringValue<XsDate>,
343}
344
345impl ElectionResultElectionIdentifier {
346 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#[derive(Debug, Clone)]
396pub struct ElectionResultContest {
397 pub identifier: ContestIdentifier,
399
400 pub selections: Vec<ElectionResultSelection>,
402}
403
404impl ElectionResultContest {
405 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#[derive(Debug, Clone, PartialEq, Eq)]
437pub enum RankingType {
438 First,
440 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#[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#[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 pub fn new(value: bool) -> Self {
494 YesNoType(value)
495 }
496
497 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 pub fn to_eml_value(&self) -> &'static str {
508 if self.0 { "yes" } else { "no" }
509 }
510
511 pub fn is_yes(&self) -> bool {
513 self.0
514 }
515
516 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#[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#[derive(Debug, Clone)]
547pub struct ElectionResultSelection {
548 pub selection_type: ElectionResultSelectionType,
550
551 pub votes: Option<StringValue<u64>>,
553
554 pub ranking: Option<StringValue<RankingType>>,
556
557 pub elected: StringValue<YesNoType>,
559}
560
561impl ElectionResultSelection {
562 pub fn builder() -> ElectionResultSelectionBuilder {
564 ElectionResultSelectionBuilder::new()
565 }
566}
567
568#[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 pub fn new() -> Self {
580 Self {
581 selection_type: None,
582 votes: None,
583 ranking: None,
584 elected: None,
585 }
586 }
587
588 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 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 pub fn votes(mut self, votes: impl Into<u64>) -> Self {
606 self.votes = Some(StringValue::from_value(votes.into()));
607 self
608 }
609
610 pub fn ranking(mut self, ranking: impl Into<RankingType>) -> Self {
612 self.ranking = Some(StringValue::from_value(ranking.into()));
613 self
614 }
615
616 pub fn elected(mut self, elected: impl Into<YesNoType>) -> Self {
618 self.elected = Some(StringValue::from_value(elected.into()));
619 self
620 }
621
622 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#[derive(Debug, Clone)]
736pub enum ElectionResultSelectionType {
737 Candidate(Box<CandidateSelection>),
739 Affiliation(Box<AffiliationSelection>),
741}
742
743#[derive(Debug, Clone)]
745pub struct CandidateSelection {
746 pub identifier: CandidateIdentifier,
748
749 pub name: PersonNameStructure,
751
752 pub gender: Option<StringValue<Gender>>,
754
755 pub qualifying_address: MinimalQualifyingAddress,
757}
758
759impl CandidateSelection {
760 pub fn builder() -> CandidateSelectionBuilder {
762 CandidateSelectionBuilder::new()
763 }
764}
765
766#[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 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 pub fn identifier(mut self, identifier: impl Into<CandidateIdentifier>) -> Self {
792 self.identifier = Some(identifier.into());
793 self
794 }
795
796 pub fn name(mut self, name: impl Into<PersonNameStructure>) -> Self {
798 self.name = Some(name.into());
799 self
800 }
801
802 pub fn gender(mut self, gender: impl Into<Gender>) -> Self {
804 self.gender = Some(StringValue::from_value(gender.into()));
805 self
806 }
807
808 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 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 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 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#[derive(Debug, Clone)]
903pub struct AffiliationSelection {
904 pub id: StringValue<AffiliationId>,
906
907 pub name: String,
909}
910
911impl AffiliationSelection {
912 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 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}