use std::{collections::BTreeMap, num::NonZeroU64, str::FromStr};
use crate::{
EML_SCHEMA_VERSION, EMLError, EMLErrorKind, EMLResultExt as _, EMLValueResultExt, NS_EML,
NS_KR,
common::{
CandidateIdentifier, CanonicalizationMethod, ContestIdentifier, CreationDateTime,
ElectionDomain, ManagingAuthority, MinimalQualifyingAddress, PersonNameStructure,
ReportingUnitIdentifier, TransactionId,
},
documents::{ElectionIdentifierBuilder, accepted_root},
io::{
EMLElement, EMLElementReader, EMLElementWriter, EMLReadElement as _, EMLWriteElement,
QualifiedName, collect_struct,
},
utils::{
AffiliationId, CandidateId, ElectionCategory, ElectionId, ElectionSubcategory, Gender,
StringValue, XsDate, XsDateTime,
},
};
#[derive(Debug, Clone)]
pub struct ElectionCount {
pub count_type: CountType,
pub transaction_id: TransactionId,
pub managing_authority: ManagingAuthority,
pub creation_date_time: CreationDateTime,
pub canonicalization_method: Option<CanonicalizationMethod>,
pub count: ElectionCountCount,
}
impl ElectionCount {
pub fn builder() -> ElectionCountBuilder {
ElectionCountBuilder::new()
}
}
impl FromStr for ElectionCount {
type Err = EMLError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
use crate::io::EMLRead as _;
Self::parse_eml(s, crate::io::EMLParsingMode::Strict).ok()
}
}
impl TryFrom<&str> for ElectionCount {
type Error = EMLError;
fn try_from(value: &str) -> Result<Self, Self::Error> {
use crate::io::EMLRead as _;
Self::parse_eml(value, crate::io::EMLParsingMode::Strict).ok()
}
}
impl TryFrom<ElectionCount> for String {
type Error = EMLError;
fn try_from(value: ElectionCount) -> Result<Self, Self::Error> {
use crate::io::EMLWrite as _;
value.write_eml_root_str(true, true)
}
}
#[derive(Debug, Clone)]
pub struct ElectionCountBuilder {
count_type: Option<CountType>,
transaction_id: Option<TransactionId>,
managing_authority: Option<ManagingAuthority>,
creation_date_time: Option<CreationDateTime>,
canonicalization_method: Option<CanonicalizationMethod>,
count: Option<ElectionCountCount>,
election_identifier: Option<ElectionCountElectionIdentifier>,
contests: Vec<ElectionCountContest>,
}
impl ElectionCountBuilder {
pub fn new() -> Self {
Self {
count_type: None,
transaction_id: None,
managing_authority: None,
creation_date_time: None,
canonicalization_method: None,
count: None,
election_identifier: None,
contests: vec![],
}
}
pub fn count_type(mut self, count_type: impl Into<CountType>) -> Self {
self.count_type = Some(count_type.into());
self
}
pub fn transaction_id(mut self, transaction_id: impl Into<TransactionId>) -> Self {
self.transaction_id = Some(transaction_id.into());
self
}
pub fn managing_authority(mut self, managing_authority: impl Into<ManagingAuthority>) -> Self {
self.managing_authority = Some(managing_authority.into());
self
}
pub fn creation_date_time(mut self, creation_date_time: impl Into<XsDateTime>) -> Self {
self.creation_date_time = Some(CreationDateTime::new(creation_date_time.into()));
self
}
pub fn canonicalization_method(
mut self,
canonicalization_method: impl Into<CanonicalizationMethod>,
) -> Self {
self.canonicalization_method = Some(canonicalization_method.into());
self
}
pub fn count(mut self, count: impl Into<ElectionCountCount>) -> Self {
self.count = Some(count.into());
self
}
pub fn election_identifier(
mut self,
election_identifier: impl Into<ElectionCountElectionIdentifier>,
) -> Self {
self.election_identifier = Some(election_identifier.into());
self
}
pub fn contests(mut self, contests: impl Into<Vec<ElectionCountContest>>) -> Self {
self.contests = contests.into();
self
}
pub fn push_contest(mut self, contest: impl Into<ElectionCountContest>) -> Self {
self.contests.push(contest.into());
self
}
pub fn build(self) -> Result<ElectionCount, EMLError> {
Ok(ElectionCount {
count_type: self
.count_type
.ok_or_else(|| EMLErrorKind::MissingBuildProperty("count_type").without_span())?,
transaction_id: self.transaction_id.ok_or_else(|| {
EMLErrorKind::MissingBuildProperty("transaction_id").without_span()
})?,
managing_authority: self.managing_authority.ok_or_else(|| {
EMLErrorKind::MissingBuildProperty("managing_authority").without_span()
})?,
creation_date_time: self.creation_date_time.ok_or_else(|| {
EMLErrorKind::MissingBuildProperty("creation_date_time").without_span()
})?,
canonicalization_method: self.canonicalization_method,
count: self.count.map_or_else(
|| {
if self.contests.is_empty() {
return Err(EMLErrorKind::MissingBuildProperty("contests").without_span());
}
Ok(ElectionCountCount::new(ElectionCountElection::new(
self.election_identifier.ok_or_else(|| {
EMLErrorKind::MissingBuildProperty("election_identifier").without_span()
})?,
self.contests,
)))
},
Ok,
)?,
})
}
}
impl Default for ElectionCountBuilder {
fn default() -> Self {
Self::new()
}
}
impl EMLElement for ElectionCount {
const EML_NAME: QualifiedName<'_, '_> = QualifiedName::from_static("EML", Some(NS_EML));
fn read_eml(elem: &mut EMLElementReader<'_, '_>) -> Result<Self, EMLError> {
accepted_root(elem)?;
let document_id = elem.attribute_value_req(("Id", None))?;
let count_type = CountType::from_eml_id(document_id.as_ref())
.map_err(|e| e.into_kind().with_span(elem.span()))?;
Ok(collect_struct!(elem, ElectionCount {
count_type: count_type,
transaction_id: TransactionId::EML_NAME => |elem| TransactionId::read_eml(elem)?,
managing_authority: ManagingAuthority::EML_NAME => |elem| ManagingAuthority::read_eml(elem)?,
creation_date_time: CreationDateTime::EML_NAME => |elem| CreationDateTime::read_eml(elem)?,
canonicalization_method as Option: CanonicalizationMethod::EML_NAME => |elem| CanonicalizationMethod::read_eml(elem)?,
count: ElectionCountCount::EML_NAME => |elem| ElectionCountCount::read_eml(elem)?,
}))
}
fn write_eml(&self, writer: EMLElementWriter) -> Result<(), EMLError> {
writer
.attr(("Id", None), self.count_type.to_eml_id())?
.attr(("SchemaVersion", None), EML_SCHEMA_VERSION)?
.child_elem(TransactionId::EML_NAME, &self.transaction_id)?
.child_elem(ManagingAuthority::EML_NAME, &self.managing_authority)?
.child_elem(CreationDateTime::EML_NAME, &self.creation_date_time)?
.child_elem(ElectionCountCount::EML_NAME, &self.count)?
.finish()
}
}
pub(crate) const EML_COUNT_POLLING_STATION_ID: &str = "510a";
pub(crate) const EML_COUNT_MUNICIPAL_ID: &str = "510b";
pub(crate) const EML_COUNT_DISTRICT_ID: &str = "510c";
pub(crate) const EML_COUNT_CENTRAL_ID: &str = "510d";
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum CountType {
PollingStation,
Municipal,
District,
Central,
}
impl CountType {
pub fn from_eml_id(s: impl AsRef<str>) -> Result<Self, EMLError> {
let data = s.as_ref();
match data {
EML_COUNT_POLLING_STATION_ID => Ok(CountType::PollingStation),
EML_COUNT_MUNICIPAL_ID => Ok(CountType::Municipal),
EML_COUNT_DISTRICT_ID => Ok(CountType::District),
EML_COUNT_CENTRAL_ID => Ok(CountType::Central),
_ => Err(
EMLErrorKind::InvalidDocumentType("510a/510b/510c/510d", data.to_string())
.without_span(),
),
}
}
pub fn to_eml_id(&self) -> &'static str {
match self {
CountType::PollingStation => EML_COUNT_POLLING_STATION_ID,
CountType::Municipal => EML_COUNT_MUNICIPAL_ID,
CountType::District => EML_COUNT_DISTRICT_ID,
CountType::Central => EML_COUNT_CENTRAL_ID,
}
}
pub fn to_friendly_name(&self) -> &'static str {
match self {
CountType::PollingStation => "Polling Station Count",
CountType::Municipal => "Municipal Count",
CountType::District => "District Count",
CountType::Central => "Central Count",
}
}
pub fn is_valid_eml_id(s: &str) -> bool {
matches!(
s,
EML_COUNT_POLLING_STATION_ID
| EML_COUNT_MUNICIPAL_ID
| EML_COUNT_DISTRICT_ID
| EML_COUNT_CENTRAL_ID
)
}
}
#[derive(Debug, Clone)]
pub struct ElectionCountCount {
pub election: ElectionCountElection,
}
impl ElectionCountCount {
pub fn new(election: impl Into<ElectionCountElection>) -> Self {
ElectionCountCount {
election: election.into(),
}
}
}
impl From<ElectionCountElection> for ElectionCountCount {
fn from(value: ElectionCountElection) -> Self {
ElectionCountCount::new(value)
}
}
impl EMLElement for ElectionCountCount {
const EML_NAME: QualifiedName<'_, '_> = QualifiedName::from_static("Count", Some(NS_EML));
fn read_eml(elem: &mut EMLElementReader<'_, '_>) -> Result<Self, EMLError> {
Ok(collect_struct!(elem, ElectionCountCount {
id as None: ("EventIdentifier", NS_EML) => |elem| elem.skip().map(|_| ())?,
election: ElectionCountElection::EML_NAME => |elem| ElectionCountElection::read_eml(elem)?,
}))
}
fn write_eml(&self, writer: EMLElementWriter) -> Result<(), EMLError> {
writer
.child(("EventIdentifier", NS_EML), |w| w.empty())?
.child_elem(ElectionCountElection::EML_NAME, &self.election)?
.finish()
}
}
#[derive(Debug, Clone)]
pub struct ElectionCountElection {
pub identifier: ElectionCountElectionIdentifier,
pub contests: Vec<ElectionCountContest>,
}
impl ElectionCountElection {
pub fn new(
identifier: impl Into<ElectionCountElectionIdentifier>,
contests: impl Into<Vec<ElectionCountContest>>,
) -> Self {
ElectionCountElection {
identifier: identifier.into(),
contests: contests.into(),
}
}
}
impl EMLElement for ElectionCountElection {
const EML_NAME: QualifiedName<'_, '_> = QualifiedName::from_static("Election", Some(NS_EML));
fn read_eml(elem: &mut EMLElementReader<'_, '_>) -> Result<Self, EMLError> {
let data = collect_struct!(elem, ElectionCountElection {
identifier: ElectionCountElectionIdentifier::EML_NAME => |elem| ElectionCountElectionIdentifier::read_eml(elem)?,
contests: ("Contests", NS_EML) => |elem| {
struct VecCollector {
contests: Vec<ElectionCountContest>,
}
let data = collect_struct!(elem, VecCollector {
contests as Vec: ElectionCountContest::EML_NAME => |elem| ElectionCountContest::read_eml(elem)?,
});
data.contests
},
});
if data.contests.is_empty() {
let err = EMLErrorKind::MissingElement(ElectionCountContest::EML_NAME.as_owned())
.with_span(elem.full_span());
if elem.parsing_mode().is_strict() {
return Err(err);
} else {
elem.push_err(err);
}
}
Ok(data)
}
fn write_eml(&self, writer: EMLElementWriter) -> Result<(), EMLError> {
writer
.child_elem(ElectionCountElectionIdentifier::EML_NAME, &self.identifier)?
.child(("Contests", NS_EML), |writer| {
writer
.child_elems(ElectionCountContest::EML_NAME, &self.contests)?
.finish()
})?
.finish()
}
}
#[derive(Debug, Clone)]
pub struct ElectionCountElectionIdentifier {
pub id: StringValue<ElectionId>,
pub name: Option<String>,
pub category: StringValue<ElectionCategory>,
pub subcategory: Option<StringValue<ElectionSubcategory>>,
pub domain: Option<ElectionDomain>,
pub election_date: StringValue<XsDate>,
}
impl ElectionCountElectionIdentifier {
pub fn builder() -> ElectionIdentifierBuilder {
ElectionIdentifierBuilder::new()
}
}
impl EMLElement for ElectionCountElectionIdentifier {
const EML_NAME: QualifiedName<'_, '_> =
QualifiedName::from_static("ElectionIdentifier", Some(NS_EML));
fn read_eml(elem: &mut EMLElementReader<'_, '_>) -> Result<Self, EMLError> {
Ok(collect_struct!(elem, ElectionCountElectionIdentifier {
id: elem.string_value_attr("Id", None)?,
name as Option: ("ElectionName", NS_EML) => |elem| elem.text_without_children()?,
category: ("ElectionCategory", NS_EML) => |elem| elem.string_value()?,
subcategory as Option: ("ElectionSubcategory", NS_KR) => |elem| elem.string_value()?,
domain as Option: ElectionDomain::EML_NAME => |elem| ElectionDomain::read_eml(elem)?,
election_date: ("ElectionDate", NS_KR) => |elem| elem.string_value()?,
}))
}
fn write_eml(&self, writer: EMLElementWriter) -> Result<(), EMLError> {
writer
.attr("Id", self.id.raw().as_ref())?
.child_option(
("ElectionName", NS_EML),
self.name.as_ref(),
|elem, value| elem.text(value.as_ref())?.finish(),
)?
.child(("ElectionCategory", NS_EML), |elem| {
elem.text(self.category.raw().as_ref())?.finish()
})?
.child_option(
("ElectionSubcategory", NS_KR),
self.subcategory.as_ref(),
|elem, value| elem.text(value.raw().as_ref())?.finish(),
)?
.child_elem_option(ElectionDomain::EML_NAME, self.domain.as_ref())?
.child(("ElectionDate", NS_KR), |elem| {
elem.text(self.election_date.raw().as_ref())?.finish()
})?
.finish()
}
}
#[derive(Debug, Clone)]
pub struct ElectionCountContest {
pub identifier: ContestIdentifier,
pub total_votes: Option<TotalVotes>,
pub reporting_unit_votes: Vec<ReportingUnitVotes>,
}
impl ElectionCountContest {
pub fn builder() -> ElectionCountContestBuilder {
ElectionCountContestBuilder::new()
}
}
#[derive(Debug, Clone)]
pub struct ElectionCountContestBuilder {
identifier: Option<ContestIdentifier>,
total_votes: Option<TotalVotes>,
total_votes_selections: Vec<ElectionCountSelection>,
total_eligible_voter_count: Option<StringValue<u64>>,
total_candidate_votes_count: Option<StringValue<u64>>,
total_rejected_votes: BTreeMap<RejectedVotesReason, StringValue<u64>>,
total_uncounted_votes: BTreeMap<UncountedVotesReason, StringValue<u64>>,
reporting_unit_votes: Vec<ReportingUnitVotes>,
}
impl ElectionCountContestBuilder {
pub fn new() -> Self {
Self {
identifier: None,
total_votes: None,
total_votes_selections: vec![],
total_eligible_voter_count: None,
total_candidate_votes_count: None,
total_rejected_votes: BTreeMap::new(),
total_uncounted_votes: BTreeMap::new(),
reporting_unit_votes: vec![],
}
}
pub fn identifier(mut self, identifier: impl Into<ContestIdentifier>) -> Self {
self.identifier = Some(identifier.into());
self
}
pub fn total_votes(mut self, total_votes: impl Into<TotalVotes>) -> Self {
self.total_votes = Some(total_votes.into());
self
}
pub fn total_votes_selections(
mut self,
selections: impl Into<Vec<ElectionCountSelection>>,
) -> Self {
self.total_votes_selections = selections.into();
self
}
pub fn push_total_votes_selection(
mut self,
selection: impl Into<ElectionCountSelection>,
) -> Self {
self.total_votes_selections.push(selection.into());
self
}
pub fn total_eligible_voter_count(mut self, count: impl Into<u64>) -> Self {
self.total_eligible_voter_count = Some(StringValue::from_value(count.into()));
self
}
pub fn total_candidate_votes_count(mut self, count: impl Into<u64>) -> Self {
self.total_candidate_votes_count = Some(StringValue::from_value(count.into()));
self
}
pub fn total_rejected_votes(
mut self,
reason: RejectedVotesReason,
count: impl Into<u64>,
) -> Self {
self.total_rejected_votes
.insert(reason, StringValue::from_value(count.into()));
self
}
pub fn total_uncounted_votes(
mut self,
reason: UncountedVotesReason,
count: impl Into<u64>,
) -> Self {
self.total_uncounted_votes
.insert(reason, StringValue::from_value(count.into()));
self
}
pub fn reporting_unit_votes(
mut self,
reporting_unit_votes: impl Into<Vec<ReportingUnitVotes>>,
) -> Self {
self.reporting_unit_votes = reporting_unit_votes.into();
self
}
pub fn push_reporting_unit_votes(
mut self,
reporting_unit_votes: impl Into<ReportingUnitVotes>,
) -> Self {
self.reporting_unit_votes.push(reporting_unit_votes.into());
self
}
pub fn build(self) -> Result<ElectionCountContest, EMLError> {
Ok(ElectionCountContest {
identifier: self
.identifier
.ok_or_else(|| EMLErrorKind::MissingBuildProperty("identifier").without_span())?,
total_votes: self.total_votes.map_or_else(
|| {
if self.total_votes_selections.is_empty()
&& self.total_eligible_voter_count.is_none()
&& self.total_candidate_votes_count.is_none()
&& self.total_rejected_votes.is_empty()
&& self.total_uncounted_votes.is_empty()
{
Ok(None)
} else {
if self.total_votes_selections.is_empty() {
return Err(EMLErrorKind::MissingBuildProperty(
"total_votes_selections",
)
.without_span());
}
if !self
.total_rejected_votes
.contains_key(&RejectedVotesReason::Blank)
{
return Err(EMLErrorKind::MissingRejectedVotesBlank).without_span();
}
if !self
.total_rejected_votes
.contains_key(&RejectedVotesReason::Invalid)
{
return Err(EMLErrorKind::MissingRejectedVotesInvalid).without_span();
}
Ok(Some(TotalVotes {
selections: self.total_votes_selections,
eligible_voter_count: self.total_eligible_voter_count.ok_or_else(
|| {
EMLErrorKind::MissingBuildProperty("total_eligible_voter_count")
.without_span()
},
)?,
candidate_votes_count: self.total_candidate_votes_count.ok_or_else(
|| {
EMLErrorKind::MissingBuildProperty(
"total_candidate_votes_count",
)
.without_span()
},
)?,
rejected_votes: self.total_rejected_votes,
uncounted_votes: self.total_uncounted_votes,
}))
}
},
|v| Ok(Some(v)),
)?,
reporting_unit_votes: self.reporting_unit_votes,
})
}
}
impl Default for ElectionCountContestBuilder {
fn default() -> Self {
Self::new()
}
}
impl EMLElement for ElectionCountContest {
const EML_NAME: QualifiedName<'_, '_> = QualifiedName::from_static("Contest", Some(NS_EML));
fn read_eml(elem: &mut EMLElementReader<'_, '_>) -> Result<Self, EMLError> {
Ok(collect_struct!(elem, ElectionCountContest {
identifier: ContestIdentifier::EML_NAME => |elem| ContestIdentifier::read_eml(elem)?,
total_votes as Option: TotalVotes::EML_NAME => |elem| TotalVotes::read_eml(elem)?,
reporting_unit_votes as Vec: ReportingUnitVotes::EML_NAME => |elem| ReportingUnitVotes::read_eml(elem)?,
}))
}
fn write_eml(&self, writer: EMLElementWriter) -> Result<(), EMLError> {
writer
.child_elem(ContestIdentifier::EML_NAME, &self.identifier)?
.child_elem_option(TotalVotes::EML_NAME, self.total_votes.as_ref())?
.child_elems(ReportingUnitVotes::EML_NAME, &self.reporting_unit_votes)?
.finish()
}
}
const REJECTED_VOTES_EML_NAME: QualifiedName<'_, '_> =
QualifiedName::from_static("RejectedVotes", Some(NS_EML));
const UNCOUNTED_VOTES_EML_NAME: QualifiedName<'_, '_> =
QualifiedName::from_static("UncountedVotes", Some(NS_EML));
#[derive(Debug, Clone)]
pub struct TotalVotes {
pub selections: Vec<ElectionCountSelection>,
pub eligible_voter_count: StringValue<u64>,
pub candidate_votes_count: StringValue<u64>,
pub rejected_votes: BTreeMap<RejectedVotesReason, StringValue<u64>>,
pub uncounted_votes: BTreeMap<UncountedVotesReason, StringValue<u64>>,
}
impl TotalVotes {
pub fn selections_per_affiliation(
&self,
) -> Result<Vec<SelectionAffiliationVotes<'_>>, EMLError> {
selections_per_affiliation(&self.selections)
}
pub fn blank_votes(&self) -> Result<&StringValue<u64>, EMLError> {
self.rejected_votes
.get(&RejectedVotesReason::Blank)
.ok_or_else(|| EMLErrorKind::MissingRejectedVotesBlank.without_span())
}
pub fn invalid_votes(&self) -> Result<&StringValue<u64>, EMLError> {
self.rejected_votes
.get(&RejectedVotesReason::Invalid)
.ok_or_else(|| EMLErrorKind::MissingRejectedVotesInvalid.without_span())
}
}
impl EMLElement for TotalVotes {
const EML_NAME: QualifiedName<'_, '_> = QualifiedName::from_static("TotalVotes", Some(NS_EML));
fn read_eml(elem: &mut EMLElementReader<'_, '_>) -> Result<Self, EMLError> {
let data = collect_struct!(elem, TotalVotes {
selections as Vec: ElectionCountSelection::EML_NAME => |elem| ElectionCountSelection::read_eml(elem)?,
eligible_voter_count: ("Cast", NS_EML) => |elem| elem.string_value()?,
candidate_votes_count: ("TotalCounted", NS_EML) => |elem| elem.string_value()?,
rejected_votes as BTreeMap: REJECTED_VOTES_EML_NAME => |elem| {
let reason_code = elem.attribute_value_req("ReasonCode")?;
let reason = RejectedVotesReason::from_eml_value(&reason_code)
.map_err(|e| EMLError::invalid_value(REJECTED_VOTES_EML_NAME.as_owned(), e, Some(elem.full_span())))?;
(reason, elem.string_value()?)
},
uncounted_votes as BTreeMap: UNCOUNTED_VOTES_EML_NAME => |elem| {
let reason_code = elem.attribute_value_req("ReasonCode")?;
let reason = UncountedVotesReason::from_eml_value(&reason_code)
.map_err(|e| EMLError::invalid_value(UNCOUNTED_VOTES_EML_NAME.as_owned(), e, Some(elem.full_span())))?;
(reason, elem.string_value()?)
},
});
if !data
.rejected_votes
.contains_key(&RejectedVotesReason::Blank)
{
let err = EMLErrorKind::MissingRejectedVotesBlank.with_span(elem.full_span());
if elem.parsing_mode().is_strict() {
return Err(err);
} else {
elem.push_err(err);
}
}
if !data
.rejected_votes
.contains_key(&RejectedVotesReason::Invalid)
{
let err = EMLErrorKind::MissingRejectedVotesInvalid.with_span(elem.full_span());
if elem.parsing_mode().is_strict() {
return Err(err);
} else {
elem.push_err(err);
}
}
Ok(data)
}
fn write_eml(&self, writer: EMLElementWriter) -> Result<(), EMLError> {
writer
.child_elems(ElectionCountSelection::EML_NAME, &self.selections)?
.child(("Cast", NS_EML), |elem| {
elem.text(self.eligible_voter_count.raw().as_ref())?
.finish()
})?
.child(("TotalCounted", NS_EML), |elem| {
elem.text(self.candidate_votes_count.raw().as_ref())?
.finish()
})?
.child_elems_map(
REJECTED_VOTES_EML_NAME,
&self.rejected_votes,
|elem, (reason, count)| {
let reason_code = reason.to_eml_value();
elem.attr("ReasonCode", reason_code.as_ref())?
.text(count.raw().as_ref())?
.finish()
},
)?
.child_elems_map(
UNCOUNTED_VOTES_EML_NAME,
&self.uncounted_votes,
|elem, (reason, count)| {
let reason_code = reason.to_eml_value();
elem.attr("ReasonCode", reason_code.as_ref())?
.text(count.raw().as_ref())?
.finish()
},
)?
.finish()
}
}
#[derive(Debug, Clone)]
pub struct ReportingUnitVotes {
pub identifier: ReportingUnitIdentifier,
pub selections: Vec<ElectionCountSelection>,
pub eligible_voter_count: StringValue<u64>,
pub candidate_votes_count: StringValue<u64>,
pub rejected_votes: BTreeMap<RejectedVotesReason, StringValue<u64>>,
pub uncounted_votes: BTreeMap<UncountedVotesReason, StringValue<u64>>,
pub investigations: BTreeMap<InvestigationReason, StringValue<bool>>,
}
pub struct SelectionAffiliationVotes<'a> {
pub affiliation: &'a AffiliationSelection,
pub valid_votes: u64,
pub candidates: Vec<SelectionCandidateVotes<'a>>,
}
pub struct SelectionCandidateVotes<'a> {
pub affiliation: &'a AffiliationSelection,
pub candidate: &'a CandidateSelection,
pub valid_votes: u64,
}
impl ReportingUnitVotes {
pub fn builder() -> ReportingUnitVotesBuilder {
ReportingUnitVotesBuilder::new()
}
pub fn selections_per_affiliation(
&self,
) -> Result<Vec<SelectionAffiliationVotes<'_>>, EMLError> {
selections_per_affiliation(&self.selections)
}
pub fn blank_votes(&self) -> Result<&StringValue<u64>, EMLError> {
self.rejected_votes
.get(&RejectedVotesReason::Blank)
.ok_or_else(|| EMLErrorKind::MissingRejectedVotesBlank.without_span())
}
pub fn invalid_votes(&self) -> Result<&StringValue<u64>, EMLError> {
self.rejected_votes
.get(&RejectedVotesReason::Invalid)
.ok_or_else(|| EMLErrorKind::MissingRejectedVotesInvalid.without_span())
}
}
fn selections_per_affiliation(
selections: &[ElectionCountSelection],
) -> Result<Vec<SelectionAffiliationVotes<'_>>, EMLError> {
let mut result = Vec::new();
let mut current_affiliation = None;
let mut current_affiliation_selection: Option<&ElectionCountSelection> = None;
let mut current_candidates = vec![];
for selection in selections {
match &selection.selection_type {
ElectionCountSelectionType::Affiliation(affiliation) => {
if let (Some(ca), Some(cas)) = (current_affiliation, current_affiliation_selection)
{
result.push(SelectionAffiliationVotes {
affiliation: ca,
valid_votes: cas
.valid_votes
.copied_value()
.wrap_field_value_error(VALID_VOTES_EML_NAME)?,
candidates: current_candidates,
});
}
current_affiliation = Some(affiliation);
current_affiliation_selection = Some(selection);
current_candidates = vec![];
}
ElectionCountSelectionType::Candidate(candidate) => {
let curr_aff = current_affiliation
.ok_or_else(|| EMLErrorKind::CandidateWithoutAffiliationFound.without_span())?;
current_candidates.push(SelectionCandidateVotes {
affiliation: curr_aff,
candidate,
valid_votes: selection
.valid_votes
.copied_value()
.wrap_field_value_error(VALID_VOTES_EML_NAME)?,
});
}
ElectionCountSelectionType::ReferendumOption(_) => {
return Err(EMLErrorKind::UnexpectedReferendumOptionSelection.without_span());
}
}
}
if let (Some(ca), Some(cas)) = (current_affiliation, current_affiliation_selection) {
result.push(SelectionAffiliationVotes {
affiliation: ca,
valid_votes: cas
.valid_votes
.copied_value()
.wrap_field_value_error(VALID_VOTES_EML_NAME)?,
candidates: current_candidates,
});
}
Ok(result)
}
#[derive(Debug, Clone)]
pub struct ReportingUnitVotesBuilder {
identifier: Option<ReportingUnitIdentifier>,
selections: Vec<ElectionCountSelection>,
eligible_voter_count: Option<StringValue<u64>>,
candidate_votes_count: Option<StringValue<u64>>,
rejected_votes: BTreeMap<RejectedVotesReason, StringValue<u64>>,
uncounted_votes: BTreeMap<UncountedVotesReason, StringValue<u64>>,
investigations: BTreeMap<InvestigationReason, StringValue<bool>>,
}
impl ReportingUnitVotesBuilder {
pub fn new() -> Self {
Self {
identifier: None,
selections: vec![],
eligible_voter_count: None,
candidate_votes_count: None,
rejected_votes: BTreeMap::new(),
uncounted_votes: BTreeMap::new(),
investigations: BTreeMap::new(),
}
}
pub fn identifier(mut self, identifier: impl Into<ReportingUnitIdentifier>) -> Self {
self.identifier = Some(identifier.into());
self
}
pub fn selections(mut self, selections: impl Into<Vec<ElectionCountSelection>>) -> Self {
self.selections = selections.into();
self
}
pub fn push_selection(mut self, selection: impl Into<ElectionCountSelection>) -> Self {
self.selections.push(selection.into());
self
}
pub fn eligible_voter_count(mut self, count: impl Into<u64>) -> Self {
self.eligible_voter_count = Some(StringValue::from_value(count.into()));
self
}
pub fn candidate_votes_count(mut self, count: impl Into<u64>) -> Self {
self.candidate_votes_count = Some(StringValue::from_value(count.into()));
self
}
pub fn rejected_votes(mut self, reason: RejectedVotesReason, count: impl Into<u64>) -> Self {
self.rejected_votes
.insert(reason, StringValue::from_value(count.into()));
self
}
pub fn uncounted_votes(mut self, reason: UncountedVotesReason, count: impl Into<u64>) -> Self {
self.uncounted_votes
.insert(reason, StringValue::from_value(count.into()));
self
}
pub fn investigation(mut self, reason: InvestigationReason, value: bool) -> Self {
self.investigations
.insert(reason, StringValue::from_value(value));
self
}
pub fn build(self) -> Result<ReportingUnitVotes, EMLError> {
if self.selections.is_empty() {
return Err(EMLErrorKind::MissingBuildProperty("selections").without_span());
}
if !self
.rejected_votes
.contains_key(&RejectedVotesReason::Blank)
{
return Err(EMLErrorKind::MissingRejectedVotesBlank).without_span();
}
if !self
.rejected_votes
.contains_key(&RejectedVotesReason::Invalid)
{
return Err(EMLErrorKind::MissingRejectedVotesInvalid).without_span();
}
Ok(ReportingUnitVotes {
identifier: self
.identifier
.ok_or_else(|| EMLErrorKind::MissingBuildProperty("identifier").without_span())?,
selections: self.selections,
eligible_voter_count: self.eligible_voter_count.ok_or_else(|| {
EMLErrorKind::MissingBuildProperty("eligible_voter_count").without_span()
})?,
candidate_votes_count: self.candidate_votes_count.ok_or_else(|| {
EMLErrorKind::MissingBuildProperty("candidate_votes_count").without_span()
})?,
rejected_votes: self.rejected_votes,
uncounted_votes: self.uncounted_votes,
investigations: self.investigations,
})
}
}
impl Default for ReportingUnitVotesBuilder {
fn default() -> Self {
Self::new()
}
}
const REPORTING_UNIT_INVESTIGATIONS_EML_NAME: QualifiedName<'_, '_> =
QualifiedName::from_static("ReportingUnitInvestigations", Some(NS_KR));
const INVESTIGATION_EML_NAME: QualifiedName<'_, '_> =
QualifiedName::from_static("Investigation", Some(NS_KR));
impl EMLElement for ReportingUnitVotes {
const EML_NAME: QualifiedName<'_, '_> =
QualifiedName::from_static("ReportingUnitVotes", Some(NS_EML));
fn read_eml(elem: &mut EMLElementReader<'_, '_>) -> Result<Self, EMLError> {
struct ReportingUnitVotesInternal {
identifier: ReportingUnitIdentifier,
selections: Vec<ElectionCountSelection>,
eligible_voter_count: StringValue<u64>,
candidate_votes_count: StringValue<u64>,
rejected_votes: BTreeMap<RejectedVotesReason, StringValue<u64>>,
uncounted_votes: BTreeMap<UncountedVotesReason, StringValue<u64>>,
investigations: Option<BTreeMap<InvestigationReason, StringValue<bool>>>,
}
let data = collect_struct!(elem, ReportingUnitVotesInternal {
identifier: ReportingUnitIdentifier::EML_NAME => |elem| ReportingUnitIdentifier::read_eml(elem)?,
selections as Vec: ElectionCountSelection::EML_NAME => |elem| ElectionCountSelection::read_eml(elem)?,
eligible_voter_count: ("Cast", NS_EML) => |elem| elem.string_value()?,
candidate_votes_count: ("TotalCounted", NS_EML) => |elem| elem.string_value()?,
rejected_votes as BTreeMap: REJECTED_VOTES_EML_NAME => |elem| {
let reason_code = elem.attribute_value_req("ReasonCode")?;
let reason = RejectedVotesReason::from_eml_value(&reason_code)
.map_err(|e| EMLError::invalid_value(REJECTED_VOTES_EML_NAME.as_owned(), e, Some(elem.full_span())))?;
(reason, elem.string_value()?)
},
uncounted_votes as BTreeMap: UNCOUNTED_VOTES_EML_NAME => |elem| {
let reason_code = elem.attribute_value_req("ReasonCode")?;
let reason = UncountedVotesReason::from_eml_value(&reason_code)
.map_err(|e| EMLError::invalid_value(UNCOUNTED_VOTES_EML_NAME.as_owned(), e, Some(elem.full_span())))?;
(reason, elem.string_value()?)
},
investigations as Option: REPORTING_UNIT_INVESTIGATIONS_EML_NAME => |elem| {
struct Collector {
investigations: BTreeMap<InvestigationReason, StringValue<bool>>,
}
let data = collect_struct!(elem, Collector {
investigations as BTreeMap: INVESTIGATION_EML_NAME => |elem| {
let reason_code = elem.attribute_value_req("ReasonCode")?;
let reason = InvestigationReason::from_eml_value(&reason_code)
.map_err(|e| EMLError::invalid_value(INVESTIGATION_EML_NAME.as_owned(), e, Some(elem.full_span())))?;
(reason, elem.string_value()?)
},
});
data.investigations
},
});
if !data
.rejected_votes
.contains_key(&RejectedVotesReason::Blank)
{
let err = EMLErrorKind::MissingRejectedVotesBlank.with_span(elem.full_span());
if elem.parsing_mode().is_strict() {
return Err(err);
} else {
elem.push_err(err);
}
}
if !data
.rejected_votes
.contains_key(&RejectedVotesReason::Invalid)
{
let err = EMLErrorKind::MissingRejectedVotesInvalid.with_span(elem.full_span());
if elem.parsing_mode().is_strict() {
return Err(err);
} else {
elem.push_err(err);
}
}
Ok(ReportingUnitVotes {
identifier: data.identifier,
selections: data.selections,
eligible_voter_count: data.eligible_voter_count,
candidate_votes_count: data.candidate_votes_count,
rejected_votes: data.rejected_votes,
uncounted_votes: data.uncounted_votes,
investigations: data.investigations.unwrap_or_default(),
})
}
fn write_eml(&self, writer: EMLElementWriter) -> Result<(), EMLError> {
writer
.child_elem(ReportingUnitIdentifier::EML_NAME, &self.identifier)?
.child_elems(ElectionCountSelection::EML_NAME, &self.selections)?
.child(("Cast", NS_EML), |elem| {
elem.text(self.eligible_voter_count.raw().as_ref())?
.finish()
})?
.child(("TotalCounted", NS_EML), |elem| {
elem.text(self.candidate_votes_count.raw().as_ref())?
.finish()
})?
.child_elems_map(
REJECTED_VOTES_EML_NAME,
&self.rejected_votes,
|elem, (reason, count)| {
let reason_code = reason.to_eml_value();
elem.attr("ReasonCode", reason_code.as_ref())?
.text(count.raw().as_ref())?
.finish()
},
)?
.child_elems_map(
UNCOUNTED_VOTES_EML_NAME,
&self.uncounted_votes,
|elem, (reason, count)| {
let reason_code = reason.to_eml_value();
elem.attr("ReasonCode", reason_code.as_ref())?
.text(count.raw().as_ref())?
.finish()
},
)?
.child_option(
REPORTING_UNIT_INVESTIGATIONS_EML_NAME,
if self.investigations.is_empty() {
None
} else {
Some(&self.investigations)
},
|elem, value| {
let mut elem = elem.content()?;
for (reason, investigated) in value {
elem = elem.child(INVESTIGATION_EML_NAME, |elem| {
let reason_code = reason.to_eml_value();
elem.attr("ReasonCode", reason_code.as_ref())?
.text(investigated.raw().as_ref())?
.finish()
})?;
}
elem.finish()
},
)?
.finish()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum InvestigationReason {
UnexplainedDifference,
OtherError,
ResultCorrected,
AdmittedVotersReestablished,
OtherReason,
PartiallyRecountedBallots,
}
impl InvestigationReason {
pub fn from_eml_value(s: impl AsRef<str>) -> Result<Self, InvalidInvestigationReasonError> {
let data = s.as_ref();
match data {
"onderzocht vanwege onverklaard verschil" => {
Ok(InvestigationReason::UnexplainedDifference)
}
"onderzocht vanwege andere fout" => Ok(InvestigationReason::OtherError),
"uitslag gecorrigeerd" => Ok(InvestigationReason::ResultCorrected),
"toegelaten kiezers opnieuw vastgesteld" => {
Ok(InvestigationReason::AdmittedVotersReestablished)
}
"onderzocht vanwege andere reden" => Ok(InvestigationReason::OtherReason),
"stembiljetten deels herteld" => Ok(InvestigationReason::PartiallyRecountedBallots),
_ => Err(InvalidInvestigationReasonError(data.to_string())),
}
}
pub fn to_eml_value(&self) -> &'static str {
match self {
InvestigationReason::UnexplainedDifference => "onderzocht vanwege onverklaard verschil",
InvestigationReason::OtherError => "onderzocht vanwege andere fout",
InvestigationReason::ResultCorrected => "uitslag gecorrigeerd",
InvestigationReason::AdmittedVotersReestablished => {
"toegelaten kiezers opnieuw vastgesteld"
}
InvestigationReason::OtherReason => "onderzocht vanwege andere reden",
InvestigationReason::PartiallyRecountedBallots => "stembiljetten deels herteld",
}
}
}
#[derive(Debug, Clone, thiserror::Error)]
#[error("Invalid investigation reason: {0}")]
pub struct InvalidInvestigationReasonError(String);
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum UncountedVotesReason {
ValidPollCards,
ValidProxyCertificates,
ValidVoterCards,
AdmittedVoters,
MoreBallotsCounted,
FewerBallotsCounted,
BallotsTaken,
TooFewBallotsIssued,
TooManyBallotsIssued,
NoPostalBallots,
TooManyPostalBallots,
LostBallots,
NoExplanation,
OtherExplanation,
}
impl UncountedVotesReason {
pub fn from_eml_value(s: impl AsRef<str>) -> Result<Self, InvalidUncountedVotesReasonError> {
match s.as_ref() {
"geldige stempassen" => Ok(UncountedVotesReason::ValidPollCards),
"geldige volmachtbewijzen" => Ok(UncountedVotesReason::ValidProxyCertificates),
"geldige kiezerspassen" => Ok(UncountedVotesReason::ValidVoterCards),
"toegelaten kiezers" => Ok(UncountedVotesReason::AdmittedVoters),
"meer getelde stembiljetten" => Ok(UncountedVotesReason::MoreBallotsCounted),
"minder getelde stembiljetten" => Ok(UncountedVotesReason::FewerBallotsCounted),
"meegenomen stembiljetten" => Ok(UncountedVotesReason::BallotsTaken),
"te weinig uitgereikte stembiljetten" => Ok(UncountedVotesReason::TooFewBallotsIssued),
"te veel uitgereikte stembiljetten" => Ok(UncountedVotesReason::TooManyBallotsIssued),
"geen briefstembiljetten" => Ok(UncountedVotesReason::NoPostalBallots),
"te veel briefstembiljetten" => Ok(UncountedVotesReason::TooManyPostalBallots),
"kwijtgeraakte stembiljetten" => Ok(UncountedVotesReason::LostBallots),
"geen verklaring" => Ok(UncountedVotesReason::NoExplanation),
"andere verklaring" => Ok(UncountedVotesReason::OtherExplanation),
_ => Err(InvalidUncountedVotesReasonError(s.as_ref().to_string())),
}
}
pub fn to_eml_value(&self) -> &'static str {
match self {
UncountedVotesReason::ValidPollCards => "geldige stempassen",
UncountedVotesReason::ValidProxyCertificates => "geldige volmachtbewijzen",
UncountedVotesReason::ValidVoterCards => "geldige kiezerspassen",
UncountedVotesReason::AdmittedVoters => "toegelaten kiezers",
UncountedVotesReason::MoreBallotsCounted => "meer getelde stembiljetten",
UncountedVotesReason::FewerBallotsCounted => "minder getelde stembiljetten",
UncountedVotesReason::BallotsTaken => "meegenomen stembiljetten",
UncountedVotesReason::TooFewBallotsIssued => "te weinig uitgereikte stembiljetten",
UncountedVotesReason::TooManyBallotsIssued => "te veel uitgereikte stembiljetten",
UncountedVotesReason::NoPostalBallots => "geen briefstembiljetten",
UncountedVotesReason::TooManyPostalBallots => "te veel briefstembiljetten",
UncountedVotesReason::LostBallots => "kwijtgeraakte stembiljetten",
UncountedVotesReason::NoExplanation => "geen verklaring",
UncountedVotesReason::OtherExplanation => "andere verklaring",
}
}
}
#[derive(Debug, Clone, thiserror::Error)]
#[error("Invalid uncounted votes reason: {0}")]
pub struct InvalidUncountedVotesReasonError(String);
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum RejectedVotesReason {
Blank,
Invalid,
}
impl RejectedVotesReason {
pub fn from_eml_value(s: impl AsRef<str>) -> Result<Self, InvalidRejectedVotesReasonError> {
let data = s.as_ref();
match data {
"blanco" => Ok(RejectedVotesReason::Blank),
"ongeldig" => Ok(RejectedVotesReason::Invalid),
_ => Err(InvalidRejectedVotesReasonError(data.to_string())),
}
}
pub fn to_eml_value(&self) -> &'static str {
match self {
RejectedVotesReason::Blank => "blanco",
RejectedVotesReason::Invalid => "ongeldig",
}
}
}
#[derive(Debug, Clone, thiserror::Error)]
#[error("Invalid rejected votes reason: {0}")]
pub struct InvalidRejectedVotesReasonError(String);
#[derive(Debug, Clone)]
pub struct ElectionCountSelection {
pub selection_type: ElectionCountSelectionType,
pub valid_votes: StringValue<u64>,
pub value: Option<String>,
pub category: Option<String>,
}
impl ElectionCountSelection {
pub fn builder() -> ElectionCountSelectionBuilder {
ElectionCountSelectionBuilder::new()
}
}
#[derive(Debug, Clone)]
pub struct ElectionCountSelectionBuilder {
selection_type: Option<ElectionCountSelectionType>,
valid_votes: Option<StringValue<u64>>,
value: Option<String>,
category: Option<String>,
}
impl ElectionCountSelectionBuilder {
pub fn new() -> Self {
Self {
selection_type: None,
valid_votes: None,
value: None,
category: None,
}
}
pub fn candidate(mut self, candidate: impl Into<CandidateSelection>) -> Self {
self.selection_type = Some(ElectionCountSelectionType::Candidate(Box::new(
candidate.into(),
)));
self
}
pub fn affiliation(mut self, affiliation: impl Into<AffiliationSelection>) -> Self {
self.selection_type = Some(ElectionCountSelectionType::Affiliation(Box::new(
affiliation.into(),
)));
self
}
pub fn referendum_option(
mut self,
referendum_option: impl Into<ReferendumOptionSelection>,
) -> Self {
self.selection_type = Some(ElectionCountSelectionType::ReferendumOption(Box::new(
referendum_option.into(),
)));
self
}
pub fn valid_votes(mut self, valid_votes: impl Into<u64>) -> Self {
self.valid_votes = Some(StringValue::from_value(valid_votes.into()));
self
}
pub fn value(mut self, value: impl Into<String>) -> Self {
self.value = Some(value.into());
self
}
pub fn category(mut self, category: impl Into<String>) -> Self {
self.category = Some(category.into());
self
}
pub fn build(self) -> Result<ElectionCountSelection, EMLError> {
Ok(ElectionCountSelection {
selection_type: self.selection_type.ok_or_else(|| {
EMLErrorKind::MissingBuildProperty("selection_type").without_span()
})?,
valid_votes: self
.valid_votes
.ok_or_else(|| EMLErrorKind::MissingBuildProperty("valid_votes").without_span())?,
value: self.value,
category: self.category,
})
}
}
impl Default for ElectionCountSelectionBuilder {
fn default() -> Self {
Self::new()
}
}
const VALID_VOTES_EML_NAME: QualifiedName<'_, '_> =
QualifiedName::from_static("ValidVotes", Some(NS_EML));
impl EMLElement for ElectionCountSelection {
const EML_NAME: QualifiedName<'_, '_> = QualifiedName::from_static("Selection", Some(NS_EML));
fn read_eml(elem: &mut EMLElementReader<'_, '_>) -> Result<Self, EMLError> {
let value = elem.attribute_value("Value")?.map(|s| s.to_string());
let category = elem.attribute_value("Category")?.map(|s| s.to_string());
let mut selection_type = None;
let mut valid_votes = None;
while let Some(mut child) = elem.next_child()? {
let name = child.name()?;
match name {
n if n == CandidateSelection::EML_NAME => {
selection_type = Some(ElectionCountSelectionType::Candidate(Box::new(
CandidateSelection::read_eml(&mut child)?,
)));
}
n if n == AffiliationSelection::EML_NAME => {
selection_type = Some(ElectionCountSelectionType::Affiliation(Box::new(
AffiliationSelection::read_eml(&mut child)?,
)));
}
n if n == ReferendumOptionSelection::EML_NAME => {
selection_type = Some(ElectionCountSelectionType::ReferendumOption(Box::new(
ReferendumOptionSelection::read_eml(&mut child)?,
)));
}
n if n == VALID_VOTES_EML_NAME => {
valid_votes = Some(child.string_value()?);
}
n => {
let err =
EMLErrorKind::UnexpectedElement(n.as_owned(), Self::EML_NAME.as_owned())
.with_span(child.inner_span());
if child.parsing_mode().is_strict() {
return Err(err);
} else {
child.push_err(err);
child.skip()?;
}
}
}
}
Ok(ElectionCountSelection {
selection_type: selection_type
.ok_or_else(|| EMLErrorKind::MissingSelectionType.with_span(elem.inner_span()))?,
valid_votes: valid_votes.ok_or_else(|| {
EMLErrorKind::MissingElement(VALID_VOTES_EML_NAME.as_owned())
.with_span(elem.inner_span())
})?,
value,
category,
})
}
fn write_eml(&self, writer: EMLElementWriter) -> Result<(), EMLError> {
let writer = writer
.attr_opt("Value", self.value.as_deref())?
.attr_opt("Category", self.category.as_deref())?;
let writer = match &self.selection_type {
ElectionCountSelectionType::Candidate(candidate_selection) => {
writer.child_elem(CandidateSelection::EML_NAME, candidate_selection.as_ref())?
}
ElectionCountSelectionType::Affiliation(affiliation_selection) => writer.child_elem(
AffiliationSelection::EML_NAME,
affiliation_selection.as_ref(),
)?,
ElectionCountSelectionType::ReferendumOption(referendum_option_selection) => writer
.child_elem(
ReferendumOptionSelection::EML_NAME,
referendum_option_selection.as_ref(),
)?,
};
writer
.child(("ValidVotes", NS_EML), |elem| {
elem.text(self.valid_votes.raw().as_ref())?.finish()
})?
.finish()
}
}
#[derive(Debug, Clone)]
pub enum ElectionCountSelectionType {
Candidate(Box<CandidateSelection>),
Affiliation(Box<AffiliationSelection>),
ReferendumOption(Box<ReferendumOptionSelection>),
}
impl ElectionCountSelectionType {
pub fn is_candidate(&self) -> bool {
matches!(self, ElectionCountSelectionType::Candidate(_))
}
pub fn is_affiliation(&self) -> bool {
matches!(self, ElectionCountSelectionType::Affiliation(_))
}
pub fn is_referendum_option(&self) -> bool {
matches!(self, ElectionCountSelectionType::ReferendumOption(_))
}
pub fn as_candidate(&self) -> Option<&CandidateSelection> {
if let ElectionCountSelectionType::Candidate(candidate_selection) = self {
Some(candidate_selection)
} else {
None
}
}
pub fn as_affiliation(&self) -> Option<&AffiliationSelection> {
if let ElectionCountSelectionType::Affiliation(affiliation_selection) = self {
Some(affiliation_selection)
} else {
None
}
}
pub fn as_referendum_option(&self) -> Option<&ReferendumOptionSelection> {
if let ElectionCountSelectionType::ReferendumOption(referendum_option_selection) = self {
Some(referendum_option_selection)
} else {
None
}
}
}
#[derive(Debug, Clone)]
pub struct CandidateSelection {
pub identifier: CandidateIdentifier,
pub name: Option<PersonNameStructure>,
pub gender: Option<StringValue<Gender>>,
pub qualifying_address: Option<MinimalQualifyingAddress>,
}
impl CandidateSelection {
pub fn builder() -> CandidateSelectionBuilder {
CandidateSelectionBuilder::new()
}
}
impl From<CandidateIdentifier> for CandidateSelection {
fn from(identifier: CandidateIdentifier) -> Self {
CandidateSelection {
identifier,
name: None,
gender: None,
qualifying_address: None,
}
}
}
impl From<CandidateId> for CandidateSelection {
fn from(id: CandidateId) -> Self {
CandidateSelection::from(CandidateIdentifier::from(id))
}
}
#[derive(Debug, Clone)]
pub struct CandidateSelectionBuilder {
identifier: Option<CandidateIdentifier>,
name: Option<PersonNameStructure>,
gender: Option<StringValue<Gender>>,
qualifying_address: Option<MinimalQualifyingAddress>,
locality_name: Option<String>,
country_name_code: Option<String>,
}
impl CandidateSelectionBuilder {
pub fn new() -> Self {
CandidateSelectionBuilder {
identifier: None,
name: None,
gender: None,
qualifying_address: None,
locality_name: None,
country_name_code: None,
}
}
pub fn identifier(mut self, identifier: impl Into<CandidateIdentifier>) -> Self {
self.identifier = Some(identifier.into());
self
}
pub fn name(mut self, name: impl Into<PersonNameStructure>) -> Self {
self.name = Some(name.into());
self
}
pub fn gender(mut self, gender: impl Into<Gender>) -> Self {
self.gender = Some(StringValue::from_value(gender.into()));
self
}
pub fn qualifying_address(
mut self,
qualifying_address: impl Into<MinimalQualifyingAddress>,
) -> Self {
self.qualifying_address = Some(qualifying_address.into());
self
}
pub fn locality_name(mut self, locality_name: impl Into<String>) -> Self {
self.locality_name = Some(locality_name.into());
self
}
pub fn country_name_code(mut self, country_name_code: impl Into<String>) -> Self {
self.country_name_code = Some(country_name_code.into());
self
}
pub fn build(self) -> Result<CandidateSelection, EMLError> {
Ok(CandidateSelection {
identifier: self
.identifier
.ok_or_else(|| EMLErrorKind::MissingBuildProperty("identifier").without_span())?,
name: self.name,
gender: self.gender,
qualifying_address: self.qualifying_address.map_or_else(
|| {
if let Some(locality_name) = self.locality_name {
if let Some(country_name_code) = self.country_name_code {
Ok(Some(MinimalQualifyingAddress::new_country(
country_name_code,
locality_name,
)))
} else {
Ok(Some(MinimalQualifyingAddress::new_locality(locality_name)))
}
} else {
if self.country_name_code.is_some() {
return Err(
EMLErrorKind::MissingBuildProperty("locality_name").without_span()
);
}
Ok(None)
}
},
|address| Ok(Some(address)),
)?,
})
}
}
impl Default for CandidateSelectionBuilder {
fn default() -> Self {
Self::new()
}
}
impl EMLElement for CandidateSelection {
const EML_NAME: QualifiedName<'_, '_> = QualifiedName::from_static("Candidate", Some(NS_EML));
fn read_eml(elem: &mut EMLElementReader<'_, '_>) -> Result<Self, EMLError> {
Ok(collect_struct!(elem, CandidateSelection {
identifier: CandidateIdentifier::EML_NAME => |elem| CandidateIdentifier::read_eml(elem)?,
name as Option: ("CandidateFullName", NS_EML) => |elem| PersonNameStructure::read_eml_element(elem)?,
gender as Option: ("Gender", NS_EML) => |elem| elem.string_value()?,
qualifying_address as Option: MinimalQualifyingAddress::EML_NAME => |elem| MinimalQualifyingAddress::read_eml(elem)?,
}))
}
fn write_eml(&self, writer: EMLElementWriter) -> Result<(), EMLError> {
writer
.child_elem(CandidateIdentifier::EML_NAME, &self.identifier)?
.child_option(
("CandidateFullName", NS_EML),
self.name.as_ref(),
|elem, value| value.write_eml_element(elem),
)?
.child_option(("Gender", NS_EML), self.gender.as_ref(), |elem, value| {
elem.text(value.raw().as_ref())?.finish()
})?
.child_elem_option(
MinimalQualifyingAddress::EML_NAME,
self.qualifying_address.as_ref(),
)?
.finish()
}
}
#[derive(Debug, Clone)]
pub struct AffiliationSelection {
pub id: StringValue<AffiliationId>,
pub name: String,
}
impl AffiliationSelection {
pub fn new(id: impl Into<AffiliationId>, name: impl Into<String>) -> Self {
AffiliationSelection {
id: StringValue::from_value(id.into()),
name: name.into(),
}
}
}
impl EMLElement for AffiliationSelection {
const EML_NAME: QualifiedName<'_, '_> =
QualifiedName::from_static("AffiliationIdentifier", Some(NS_EML));
fn read_eml(elem: &mut EMLElementReader<'_, '_>) -> Result<Self, EMLError> {
Ok(collect_struct!(elem, AffiliationSelection {
id: elem.string_value_attr("Id", None)?,
name: ("RegisteredName", NS_EML) => |elem| elem.text_without_children()?,
}))
}
fn write_eml(&self, writer: EMLElementWriter) -> Result<(), EMLError> {
writer
.attr("Id", self.id.raw().as_ref())?
.child(("RegisteredName", NS_EML), |elem| {
elem.text(self.name.as_ref())?.finish()
})?
.finish()
}
}
#[derive(Debug, Clone)]
pub struct ReferendumOptionSelection {
pub value: String,
pub id: Option<String>,
pub display_order: Option<StringValue<NonZeroU64>>,
pub short_code: Option<String>,
pub expected_confirmation_reference: Option<String>,
}
impl ReferendumOptionSelection {
pub fn new(value: impl Into<String>) -> Self {
ReferendumOptionSelection {
value: value.into(),
id: None,
display_order: None,
short_code: None,
expected_confirmation_reference: None,
}
}
pub fn with_id(mut self, id: impl Into<String>) -> Self {
self.id = Some(id.into());
self
}
pub fn with_display_order(mut self, display_order: impl Into<NonZeroU64>) -> Self {
self.display_order = Some(StringValue::from_value(display_order.into()));
self
}
pub fn with_short_code(mut self, short_code: impl Into<String>) -> Self {
self.short_code = Some(short_code.into());
self
}
pub fn with_expected_confirmation_reference(
mut self,
expected_confirmation_reference: impl Into<String>,
) -> Self {
self.expected_confirmation_reference = Some(expected_confirmation_reference.into());
self
}
}
impl EMLElement for ReferendumOptionSelection {
const EML_NAME: QualifiedName<'_, '_> =
QualifiedName::from_static("ReferendumOptionIdentifier", Some(NS_EML));
fn read_eml(elem: &mut EMLElementReader<'_, '_>) -> Result<Self, EMLError> {
Ok(ReferendumOptionSelection {
value: elem.text_without_children()?,
id: elem.attribute_value("Id")?.map(|s| s.into_owned()),
display_order: elem.string_value_attr_opt("DisplayOrder")?,
short_code: elem.attribute_value("ShortCode")?.map(|s| s.into_owned()),
expected_confirmation_reference: elem
.attribute_value("ExpectedConfirmationReference")?
.map(|s| s.into_owned()),
})
}
fn write_eml(&self, writer: EMLElementWriter) -> Result<(), EMLError> {
writer
.attr_opt("Id", self.id.as_deref())?
.attr_opt("DisplayOrder", self.display_order.as_ref().map(|s| s.raw()))?
.attr_opt("ShortCode", self.short_code.as_deref())?
.attr_opt(
"ExpectedConfirmationReference",
self.expected_confirmation_reference.as_deref(),
)?
.text(self.value.as_ref())?
.finish()
}
}
#[cfg(test)]
mod tests {
use chrono::{NaiveDate, TimeZone as _};
use crate::{
common::{AuthorityIdentifier, PersonName},
io::{EMLParsingMode, EMLRead, EMLWrite},
utils::{AuthorityId, CandidateId, ReportingUnitIdentifierId},
};
use super::*;
#[test]
fn test_election_count_construction() {
let ec = ElectionCount::builder()
.count_type(CountType::Municipal)
.transaction_id(TransactionId::new(1))
.creation_date_time(
chrono::Utc
.with_ymd_and_hms(2014, 11, 28, 12, 0, 9)
.unwrap(),
)
.managing_authority(ManagingAuthority::new(
AuthorityIdentifier::new(AuthorityId::new("1234").unwrap()).with_name("Rotterdam"),
))
.election_identifier(
ElectionCountElectionIdentifier::builder()
.id(ElectionId::new("GR2222_Cyber").unwrap())
.category(ElectionCategory::GR)
.election_date(NaiveDate::from_ymd_opt(2222, 11, 16).unwrap())
.build_for_count()
.unwrap(),
)
.contests([ElectionCountContest::builder()
.identifier(ContestIdentifier::geen())
.total_candidate_votes_count(65u64)
.total_eligible_voter_count(100u64)
.total_rejected_votes(RejectedVotesReason::Blank, 100u64)
.total_rejected_votes(RejectedVotesReason::Invalid, 0u64)
.total_uncounted_votes(UncountedVotesReason::AdmittedVoters, 10u64)
.total_votes_selections([
ElectionCountSelection::builder()
.affiliation(AffiliationSelection::new(
AffiliationId::new("1").unwrap(),
"Example",
))
.valid_votes(16u64)
.build()
.unwrap(),
ElectionCountSelection::builder()
.candidate(
CandidateSelection::builder()
.identifier(CandidateId::new("1").unwrap())
.name(PersonName::new("Smid").with_first_name("Example"))
.build()
.unwrap(),
)
.valid_votes(16u64)
.build()
.unwrap(),
])
.reporting_unit_votes([ReportingUnitVotes::builder()
.identifier(ReportingUnitIdentifier::new(
ReportingUnitIdentifierId::new("SB1234").unwrap(),
"Stembureau",
))
.rejected_votes(RejectedVotesReason::Blank, 10u64)
.rejected_votes(RejectedVotesReason::Invalid, 0u64)
.uncounted_votes(UncountedVotesReason::AdmittedVoters, 5u64)
.candidate_votes_count(10u64)
.eligible_voter_count(100u64)
.selections([
ElectionCountSelection::builder()
.affiliation(AffiliationSelection::new(
AffiliationId::new("1").unwrap(),
"Example",
))
.valid_votes(16u64)
.build()
.unwrap(),
ElectionCountSelection::builder()
.candidate(
CandidateSelection::builder()
.identifier(CandidateId::new("1").unwrap())
.name(PersonName::new("Smid").with_first_name("Example"))
.build()
.unwrap(),
)
.valid_votes(16u64)
.build()
.unwrap(),
])
.build()
.unwrap()])
.build()
.unwrap()])
.build()
.unwrap();
let xml = ec.write_eml_root_str(true, true).unwrap();
assert_eq!(
xml,
include_str!("../../test-emls/election_count/eml510b_construction_output.eml.xml")
);
let parsed = ElectionCount::parse_eml(&xml, EMLParsingMode::Strict).unwrap();
let xml2 = parsed.write_eml_root_str(true, true).unwrap();
assert_eq!(xml, xml2);
}
#[test]
fn test_parse_510b() {
let xml = include_str!("../../test-emls/election_count/deserialize_eml510b_test.eml.xml");
assert!(
ElectionCount::parse_eml(xml, EMLParsingMode::Strict)
.ok_with_errors()
.is_ok()
);
}
#[test]
fn test_parse_510d() {
let xml = include_str!("../../test-emls/election_count/deserialize_eml510d_test.eml.xml");
assert!(
ElectionCount::parse_eml(xml, EMLParsingMode::Strict)
.ok_with_errors()
.is_ok()
);
}
#[test]
fn test_parse_with_investigations() {
let xml =
include_str!("../../test-emls/election_count/eml510b_with_investigations.eml.xml");
assert!(
ElectionCount::parse_eml(xml, EMLParsingMode::Strict)
.ok_with_errors()
.is_ok()
);
}
}