Skip to main content

eml_nl/utils/
election_id.rs

1use std::sync::LazyLock;
2
3use regex::Regex;
4use thiserror::Error;
5
6use crate::{EMLError, EMLValueResultExt as _, utils::StringValueData};
7
8/// Regular expression for validating ElectionId values.
9static ELECTION_ID_RE: LazyLock<Regex> = LazyLock::new(|| {
10    Regex::new(r"^(EP|EK|TK|GR|BC|GC|ER|PS|AB|NR|PR|LR|IR)2\d\d\d(\d\d\d\d)?(_[\w_-]*)?$")
11        .expect("Failed to compile Election ID regex")
12});
13
14/// A string of type ElectionId as defined in the EML_NL specification
15#[derive(Debug, Clone, PartialEq, Eq, Hash)]
16#[repr(transparent)]
17pub struct ElectionId(String);
18
19impl ElectionId {
20    /// Create a new ElectionId from a string, validating its format
21    pub fn new(s: impl AsRef<str>) -> Result<Self, EMLError> {
22        StringValueData::parse_from_str(s.as_ref()).wrap_value_error()
23    }
24
25    /// Get the raw string value of the ElectionId.
26    pub fn value(&self) -> &str {
27        &self.0
28    }
29}
30
31/// Error returned when a string could not be parsed as a ElectionId
32#[derive(Debug, Clone, Error)]
33#[error("Invalid election id: {0}")]
34pub struct InvalidElectionIdError(String);
35
36impl StringValueData for ElectionId {
37    type Error = InvalidElectionIdError;
38
39    fn parse_from_str(s: &str) -> Result<Self, Self::Error>
40    where
41        Self: Sized,
42    {
43        if ELECTION_ID_RE.is_match(s) {
44            Ok(ElectionId(s.to_string()))
45        } else {
46            Err(InvalidElectionIdError(s.to_string()))
47        }
48    }
49
50    fn to_raw_value(&self) -> String {
51        self.0.clone()
52    }
53}
54
55#[cfg(test)]
56mod tests {
57    use super::*;
58
59    #[test]
60    fn test_election_id_regex_compiles() {
61        LazyLock::force(&ELECTION_ID_RE);
62    }
63
64    #[test]
65    fn test_valid_election_ids() {
66        let valid_ids = [
67            "EP2021",
68            "EK2021",
69            "TK2021",
70            "GR2021",
71            "GR2021_Amsterdam",
72            "TK20210101",
73        ];
74        for id in valid_ids {
75            assert!(
76                ElectionId::new(id).is_ok(),
77                "ElectionIdType should accept valid id: {}",
78                id
79            );
80        }
81    }
82
83    #[test]
84    fn test_invalid_election_ids() {
85        let invalid_ids = ["", "2021", "EP21", "EP202A", "EP2021-01-01"];
86        for id in invalid_ids {
87            assert!(
88                ElectionId::new(id).is_err(),
89                "ElectionIdType should reject invalid id: {}",
90                id
91            );
92        }
93    }
94}