use std::{collections::BTreeMap, str::FromStr};
use crate::{
EMLError, EMLErrorKind, EMLResultExt as _, EMLVersion, NS_EML,
common::ElectionDomain,
documents::{
candidate_lists::{CandidateLists, CandidateListsElectionIdentifier, CandidateListsType},
election_count::{CountType, ElectionCount, ElectionCountElectionIdentifier},
election_definition::{
EML_ELECTION_DEFINITION_ID, ElectionDefinition, ElectionDefinitionElectionIdentifier,
},
election_result::{
EML_ELECTION_RESULT_ID, ElectionResult, ElectionResultElectionIdentifier,
},
nomination::{EML_NOMINATION_ID, Nomination, NominationElectionIdentifier},
polling_stations::{
EML_POLLING_STATIONS_ID, PollingStations, PollingStationsElectionIdentifier,
},
},
io::{EMLDocument, EMLElement, EMLElementReader, EMLElementWriter, QualifiedName},
utils::{ElectionCategory, ElectionId, ElectionSubcategory, StringValue, XsDate},
};
pub mod candidate_lists;
pub mod election_count;
pub mod election_definition;
pub mod election_result;
pub mod master_election_tree;
pub mod nomination;
pub mod polling_stations;
#[derive(Debug, Clone)]
pub enum EML {
ElectionDefinition(Box<ElectionDefinition>),
PollingStations(Box<PollingStations>),
Nomination(Box<Nomination>),
CandidateLists(Box<CandidateLists>),
ElectionCount(Box<ElectionCount>),
ElectionResult(Box<ElectionResult>),
}
impl EML {
pub fn from_election_definition_doc(ed: ElectionDefinition) -> Self {
EML::ElectionDefinition(Box::new(ed))
}
pub fn is_election_definition_doc(&self) -> bool {
matches!(self, EML::ElectionDefinition(_))
}
pub fn as_election_definition_doc(&self) -> Option<&ElectionDefinition> {
match self {
EML::ElectionDefinition(ed) => Some(ed),
_ => None,
}
}
pub fn from_polling_stations_doc(ps: PollingStations) -> Self {
EML::PollingStations(Box::new(ps))
}
pub fn is_polling_stations_doc(&self) -> bool {
matches!(self, EML::PollingStations(_))
}
pub fn as_polling_stations_doc(&self) -> Option<&PollingStations> {
match self {
EML::PollingStations(ps) => Some(ps),
_ => None,
}
}
pub fn from_nomination_doc(nom: Nomination) -> Self {
EML::Nomination(Box::new(nom))
}
pub fn is_nomination_doc(&self) -> bool {
matches!(self, EML::Nomination(_))
}
pub fn as_nomination_doc(&self) -> Option<&Nomination> {
match self {
EML::Nomination(nom) => Some(nom),
_ => None,
}
}
pub fn from_candidate_lists_doc(cl: CandidateLists) -> Self {
EML::CandidateLists(Box::new(cl))
}
pub fn is_candidate_lists_doc(&self) -> bool {
matches!(self, EML::CandidateLists(_))
}
pub fn as_candidate_lists_doc(&self) -> Option<&CandidateLists> {
match self {
EML::CandidateLists(cl) => Some(cl),
_ => None,
}
}
pub fn from_count_doc(count: ElectionCount) -> Self {
EML::ElectionCount(Box::new(count))
}
pub fn is_count_doc(&self) -> bool {
matches!(self, EML::ElectionCount(_))
}
pub fn as_count_doc(&self) -> Option<&ElectionCount> {
match self {
EML::ElectionCount(count) => Some(count),
_ => None,
}
}
pub fn from_result_doc(result: ElectionResult) -> Self {
EML::ElectionResult(Box::new(result))
}
pub fn is_result_doc(&self) -> bool {
matches!(self, EML::ElectionResult(_))
}
pub fn as_result_doc(&self) -> Option<&ElectionResult> {
match self {
EML::ElectionResult(result) => Some(result),
_ => None,
}
}
}
impl EMLElement for EML {
const EML_NAME: QualifiedName<'_, '_> = QualifiedName::from_static("EML", Some(NS_EML));
fn read_eml(elem: &mut EMLElementReader<'_, '_>) -> Result<Self, EMLError> {
let document_id = elem.attribute_value_req(("Id", None))?;
Ok(match document_id.as_ref() {
EML_ELECTION_DEFINITION_ID => {
EML::ElectionDefinition(Box::new(elem.read_element::<ElectionDefinition>()?))
}
EML_POLLING_STATIONS_ID => {
EML::PollingStations(Box::new(elem.read_element::<PollingStations>()?))
}
EML_ELECTION_RESULT_ID => {
EML::ElectionResult(Box::new(elem.read_element::<ElectionResult>()?))
}
EML_NOMINATION_ID => EML::Nomination(Box::new(elem.read_element::<Nomination>()?)),
id if CandidateListsType::is_valid_eml_id(id) => {
EML::CandidateLists(Box::new(elem.read_element::<CandidateLists>()?))
}
id if CountType::is_valid_eml_id(id) => {
EML::ElectionCount(Box::new(elem.read_element::<ElectionCount>()?))
}
_ => {
return Err(EMLErrorKind::UnknownDocumentType(document_id.to_string()))
.with_span(elem.span());
}
})
}
fn write_eml(&self, writer: EMLElementWriter) -> Result<(), EMLError> {
match self {
EML::ElectionDefinition(ed) => ed.write_eml(writer),
EML::PollingStations(ps) => ps.write_eml(writer),
EML::Nomination(nom) => nom.write_eml(writer),
EML::CandidateLists(cl) => cl.write_eml(writer),
EML::ElectionCount(c) => c.write_eml(writer),
EML::ElectionResult(r) => r.write_eml(writer),
}
}
}
impl From<ElectionDefinition> for EML {
fn from(ed: ElectionDefinition) -> Self {
EML::from_election_definition_doc(ed)
}
}
impl From<PollingStations> for EML {
fn from(ps: PollingStations) -> Self {
EML::from_polling_stations_doc(ps)
}
}
impl From<Nomination> for EML {
fn from(nom: Nomination) -> Self {
EML::from_nomination_doc(nom)
}
}
impl From<CandidateLists> for EML {
fn from(cl: CandidateLists) -> Self {
EML::from_candidate_lists_doc(cl)
}
}
impl From<ElectionCount> for EML {
fn from(count: ElectionCount) -> Self {
EML::from_count_doc(count)
}
}
impl From<ElectionResult> for EML {
fn from(result: ElectionResult) -> Self {
EML::from_result_doc(result)
}
}
impl EMLDocument for EML {
fn document_version(&self) -> EMLVersion {
match self {
EML::ElectionDefinition(d) => d.version,
EML::PollingStations(d) => d.version,
EML::Nomination(d) => d.version,
EML::CandidateLists(d) => d.version,
EML::ElectionCount(d) => d.version,
EML::ElectionResult(d) => d.version,
}
}
fn document_eml_id(&self) -> &'static str {
match self {
EML::ElectionDefinition(d) => d.document_eml_id(),
EML::PollingStations(d) => d.document_eml_id(),
EML::Nomination(d) => d.document_eml_id(),
EML::CandidateLists(d) => d.document_eml_id(),
EML::ElectionCount(d) => d.document_eml_id(),
EML::ElectionResult(d) => d.document_eml_id(),
}
}
fn document_friendly_name(&self) -> &'static str {
match self {
EML::ElectionDefinition(d) => d.document_friendly_name(),
EML::PollingStations(d) => d.document_friendly_name(),
EML::Nomination(d) => d.document_friendly_name(),
EML::CandidateLists(d) => d.document_friendly_name(),
EML::ElectionCount(d) => d.document_friendly_name(),
EML::ElectionResult(d) => d.document_friendly_name(),
}
}
fn document_namespaces(&self) -> Option<BTreeMap<&'static str, &'static str>> {
match self {
EML::ElectionDefinition(d) => d.document_namespaces(),
EML::PollingStations(d) => d.document_namespaces(),
EML::Nomination(d) => d.document_namespaces(),
EML::CandidateLists(d) => d.document_namespaces(),
EML::ElectionCount(d) => d.document_namespaces(),
EML::ElectionResult(d) => d.document_namespaces(),
}
}
}
impl FromStr for EML {
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 EML {
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<EML> for String {
type Error = EMLError;
fn try_from(value: EML) -> Result<Self, Self::Error> {
use crate::io::EMLWrite as _;
value.write_eml_root_str(true, true)
}
}
#[derive(Debug, Clone)]
pub struct ElectionIdentifierBuilder {
id: Option<StringValue<ElectionId>>,
name: Option<Box<str>>,
category: Option<StringValue<ElectionCategory>>,
subcategory: Option<StringValue<ElectionSubcategory>>,
domain: Option<ElectionDomain>,
election_date: Option<StringValue<XsDate>>,
nomination_date: Option<StringValue<XsDate>>,
}
impl ElectionIdentifierBuilder {
pub fn new() -> Self {
ElectionIdentifierBuilder {
id: None,
name: None,
category: None,
subcategory: None,
domain: None,
election_date: None,
nomination_date: None,
}
}
pub fn id(mut self, id: impl Into<ElectionId>) -> Self {
self.id = Some(StringValue::from_value(id.into()));
self
}
pub fn name(mut self, name: impl Into<Box<str>>) -> Self {
self.name = Some(name.into());
self
}
pub fn category(mut self, category: impl Into<ElectionCategory>) -> Self {
self.category = Some(StringValue::from_value(category.into()));
self
}
pub fn subcategory(mut self, subcategory: impl Into<ElectionSubcategory>) -> Self {
self.subcategory = Some(StringValue::from_value(subcategory.into()));
self
}
pub fn domain(mut self, domain: impl Into<ElectionDomain>) -> Self {
self.domain = Some(domain.into());
self
}
pub fn election_date(mut self, date: impl Into<XsDate>) -> Self {
self.election_date = Some(StringValue::from_value(date.into()));
self
}
pub fn nomination_date(mut self, date: impl Into<XsDate>) -> Self {
self.nomination_date = Some(StringValue::from_value(date.into()));
self
}
pub fn build_for_nomination(self) -> Result<NominationElectionIdentifier, EMLError> {
let category = self
.category
.ok_or(EMLErrorKind::MissingBuildProperty("category").without_span())?;
category.validate_subcategory(self.subcategory.as_ref())?;
let election_date = self
.election_date
.ok_or(EMLErrorKind::MissingBuildProperty("election_date").without_span())?;
let nomination_date = self
.nomination_date
.ok_or(EMLErrorKind::MissingBuildProperty("nomination_date").without_span())?;
election_date.validate_is_after(
&nomination_date,
EMLErrorKind::NominationDateNotBeforeElectionDate,
)?;
Ok(NominationElectionIdentifier {
id: self
.id
.ok_or(EMLErrorKind::MissingBuildProperty("id").without_span())?,
name: self.name,
category,
subcategory: self.subcategory,
domain: self.domain,
election_date,
nomination_date,
})
}
pub fn build_for_candidate_lists(self) -> Result<CandidateListsElectionIdentifier, EMLError> {
let category = self
.category
.ok_or(EMLErrorKind::MissingBuildProperty("category").without_span())?;
category.validate_subcategory(self.subcategory.as_ref())?;
let election_date = self
.election_date
.ok_or(EMLErrorKind::MissingBuildProperty("election_date").without_span())?;
let nomination_date = self
.nomination_date
.ok_or(EMLErrorKind::MissingBuildProperty("nomination_date").without_span())?;
election_date.validate_is_after(
&nomination_date,
EMLErrorKind::NominationDateNotBeforeElectionDate,
)?;
Ok(CandidateListsElectionIdentifier {
id: self
.id
.ok_or(EMLErrorKind::MissingBuildProperty("id").without_span())?,
name: self.name,
category,
subcategory: self.subcategory,
domain: self.domain,
election_date,
nomination_date,
})
}
pub fn build_for_count(self) -> Result<ElectionCountElectionIdentifier, EMLError> {
let category = self
.category
.ok_or(EMLErrorKind::MissingBuildProperty("category").without_span())?;
category.validate_subcategory(self.subcategory.as_ref())?;
Ok(ElectionCountElectionIdentifier {
id: self
.id
.ok_or(EMLErrorKind::MissingBuildProperty("id").without_span())?,
name: self.name,
category,
subcategory: self.subcategory,
domain: self.domain,
election_date: self
.election_date
.ok_or(EMLErrorKind::MissingBuildProperty("election_date").without_span())?,
})
}
pub fn build_for_result(self) -> Result<ElectionResultElectionIdentifier, EMLError> {
let category = self
.category
.ok_or(EMLErrorKind::MissingBuildProperty("category").without_span())?;
category.validate_subcategory(self.subcategory.as_ref())?;
Ok(ElectionResultElectionIdentifier {
id: self
.id
.ok_or(EMLErrorKind::MissingBuildProperty("id").without_span())?,
name: self.name,
category,
subcategory: self.subcategory,
domain: self.domain,
election_date: self
.election_date
.ok_or(EMLErrorKind::MissingBuildProperty("election_date").without_span())?,
})
}
pub fn build_for_definition(self) -> Result<ElectionDefinitionElectionIdentifier, EMLError> {
let category = self
.category
.ok_or(EMLErrorKind::MissingBuildProperty("category").without_span())?;
let subcategory = self
.subcategory
.ok_or(EMLErrorKind::MissingBuildProperty("subcategory").without_span())?;
category.validate_subcategory(Some(&subcategory))?;
let election_date = self
.election_date
.ok_or(EMLErrorKind::MissingBuildProperty("election_date").without_span())?;
let nomination_date = self
.nomination_date
.ok_or(EMLErrorKind::MissingBuildProperty("nomination_date").without_span())?;
election_date.validate_is_after(
&nomination_date,
EMLErrorKind::NominationDateNotBeforeElectionDate,
)?;
Ok(ElectionDefinitionElectionIdentifier {
id: self
.id
.ok_or(EMLErrorKind::MissingBuildProperty("id").without_span())?,
name: self
.name
.ok_or(EMLErrorKind::MissingBuildProperty("name").without_span())?,
category,
subcategory,
domain: self.domain,
election_date,
nomination_date,
})
}
pub fn build_for_polling_stations(self) -> Result<PollingStationsElectionIdentifier, EMLError> {
let category = self
.category
.ok_or(EMLErrorKind::MissingBuildProperty("category").without_span())?;
category.validate_subcategory(self.subcategory.as_ref())?;
Ok(PollingStationsElectionIdentifier {
id: self
.id
.ok_or(EMLErrorKind::MissingBuildProperty("id").without_span())?,
name: self.name,
category,
subcategory: self.subcategory,
domain: self.domain,
election_date: self
.election_date
.ok_or(EMLErrorKind::MissingBuildProperty("election_date").without_span())?,
})
}
}
impl Default for ElectionIdentifierBuilder {
fn default() -> Self {
Self::new()
}
}
impl StringValue<XsDate> {
pub(crate) fn validate_is_after(
&self,
other: &StringValue<XsDate>,
error_kind: EMLErrorKind,
) -> Result<(), EMLError> {
let value = self.copied_value().ok();
let other = other.copied_value().ok();
if let (Some(value), Some(other)) = (value, other)
&& other.date >= value.date
{
return Err(error_kind).without_span();
}
Ok(())
}
}
impl StringValue<ElectionCategory> {
pub(crate) fn validate_subcategory(
&self,
subcategory: Option<&StringValue<ElectionSubcategory>>,
) -> Result<(), EMLError> {
let category = self.copied_value().ok();
let subcategory = subcategory.and_then(|sc| sc.copied_value().ok());
if let (Some(c), Some(sc)) = (category, subcategory)
&& !sc.is_subcategory_of(c)
{
return Err(EMLErrorKind::InvalidElectionSubcategory).without_span();
}
Ok(())
}
pub(crate) fn validate_version(&self, version: EMLVersion) -> Result<(), EMLError> {
if let Some(c) = self.copied_value().ok()
&& !c.is_valid_for(version)
{
return Err(EMLErrorKind::UnsupportedElectionCategoryForVersion(
c.to_eml_value(),
version,
))
.without_span();
}
Ok(())
}
}
impl ElectionCategory {
pub(crate) fn read_and_validate(
elem: &mut EMLElementReader<'_, '_>,
) -> Result<StringValue<ElectionCategory>, EMLError> {
let category: StringValue<ElectionCategory> = elem.string_value()?;
elem.report_validation(
category.validate_version(elem.document_version()),
elem.span(),
)?;
Ok(category)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::io::{EMLParsingMode, EMLRead as _, EMLWrite as _};
#[test]
fn test_parsing_arbitrary_eml_documents() {
let doc = include_str!("../../test-files/nomination/eml210_test.eml.xml");
let eml = EML::parse_eml(doc, EMLParsingMode::Strict)
.ok()
.expect("Failed to parse EML document");
assert!(matches!(eml, EML::Nomination(_)));
let doc = include_str!("../../test-files/candidate_lists/eml230b_test.eml.xml");
let eml = EML::parse_eml(doc, EMLParsingMode::Strict)
.ok()
.expect("Failed to parse EML document");
assert!(matches!(eml, EML::CandidateLists(_)));
let doc = include_str!("../../test-files/election_definition/eml110a_test.eml.xml");
let eml = EML::parse_eml(doc, EMLParsingMode::Strict)
.ok()
.expect("Failed to parse EML document");
assert!(matches!(eml, EML::ElectionDefinition(_)));
let doc = include_str!("../../test-files/polling_stations/eml110b_test.eml.xml");
let eml = EML::parse_eml(doc, EMLParsingMode::Strict)
.ok()
.expect("Failed to parse EML document");
assert!(matches!(eml, EML::PollingStations(_)));
let doc = include_str!("../../test-files/election_count/deserialize_eml510b_test.eml.xml");
let eml = EML::parse_eml(doc, EMLParsingMode::Strict)
.ok()
.expect("Failed to parse EML document");
assert!(matches!(eml, EML::ElectionCount(_)));
let doc = include_str!("../../test-files/election_result/eml520_test.eml.xml");
let eml = EML::parse_eml(doc, EMLParsingMode::Strict)
.ok()
.expect("Failed to parse EML document");
assert!(matches!(eml, EML::ElectionResult(_)));
}
#[test]
fn parse_and_write_210_eml_document_should_not_fail() {
let doc = include_str!("../../test-files/nomination/eml210_test.eml.xml");
let eml = EML::parse_eml(doc, EMLParsingMode::Strict)
.ok()
.expect("Failed to parse EML document");
assert_eq!(eml.document_eml_id(), "210");
assert_eq!(eml.document_friendly_name(), "Nomination");
assert!(eml.is_nomination_doc());
assert!(!eml.is_election_definition_doc());
assert!(eml.as_nomination_doc().is_some());
assert!(eml.as_election_definition_doc().is_none());
assert!(eml.write_eml_root_str(true, true).is_ok());
}
#[test]
fn parse_and_write_110a_eml_document_should_not_fail() {
let doc = include_str!("../../test-files/election_definition/eml110a_test.eml.xml");
let eml = EML::parse_eml(doc, EMLParsingMode::Strict)
.ok()
.expect("Failed to parse EML document");
assert_eq!(eml.document_eml_id(), "110a");
assert_eq!(eml.document_friendly_name(), "Election Definition");
assert!(eml.is_election_definition_doc());
assert!(!eml.is_result_doc());
assert!(eml.as_election_definition_doc().is_some());
assert!(eml.as_result_doc().is_none());
assert!(eml.write_eml_root_str(true, true).is_ok());
}
#[test]
fn parse_and_write_110b_eml_document_should_not_fail() {
let doc = include_str!("../../test-files/polling_stations/eml110b_test.eml.xml");
let eml = EML::parse_eml(doc, EMLParsingMode::Strict)
.ok()
.expect("Failed to parse EML document");
assert_eq!(eml.document_eml_id(), "110b");
assert_eq!(eml.document_friendly_name(), "Polling Stations");
assert!(eml.is_polling_stations_doc());
assert!(!eml.is_election_definition_doc());
assert!(eml.as_polling_stations_doc().is_some());
assert!(eml.as_election_definition_doc().is_none());
assert!(eml.write_eml_root_str(true, true).is_ok());
}
#[test]
fn parse_and_write_230b_eml_document_should_not_fail() {
let doc = include_str!("../../test-files/candidate_lists/eml230b_test.eml.xml");
let eml = EML::parse_eml(doc, EMLParsingMode::Strict)
.ok()
.expect("Failed to parse EML document");
assert_eq!(eml.document_eml_id(), "230b");
assert_eq!(eml.document_friendly_name(), "Candidate Lists");
assert!(eml.is_candidate_lists_doc());
assert!(!eml.is_polling_stations_doc());
assert!(eml.as_candidate_lists_doc().is_some());
assert!(eml.as_polling_stations_doc().is_none());
assert!(eml.write_eml_root_str(true, true).is_ok());
}
#[test]
fn parse_and_write_510b_eml_document_should_not_fail() {
let doc = include_str!("../../test-files/election_count/deserialize_eml510b_test.eml.xml");
let eml = EML::parse_eml(doc, EMLParsingMode::Strict)
.ok()
.expect("Failed to parse EML document");
assert_eq!(eml.document_eml_id(), "510b");
assert_eq!(eml.document_friendly_name(), "Municipal Count");
assert!(eml.is_count_doc());
assert!(!eml.is_candidate_lists_doc());
assert!(eml.as_count_doc().is_some());
assert!(eml.as_candidate_lists_doc().is_none());
assert!(eml.write_eml_root_str(true, true).is_ok());
}
#[test]
fn parse_and_write_520_eml_document_should_not_fail() {
let doc = include_str!("../../test-files/election_result/eml520_test.eml.xml");
let eml = EML::parse_eml(doc, EMLParsingMode::Strict)
.ok()
.expect("Failed to parse EML document");
assert_eq!(eml.document_eml_id(), "520");
assert_eq!(eml.document_friendly_name(), "Result");
assert!(eml.is_result_doc());
assert!(!eml.is_count_doc());
assert!(eml.as_result_doc().is_some());
assert!(eml.as_count_doc().is_none());
assert!(eml.write_eml_root_str(true, true).is_ok());
}
#[test]
fn test_kc_election_category_rejected_before_v1_3() {
let category = StringValue::from_value(ElectionCategory::KC);
assert!(category.validate_version(EMLVersion::V1_2_2).is_err());
assert!(category.validate_version(EMLVersion::V1_3).is_ok());
}
}