eml_nl/utils/
candidate_id.rs1use std::sync::LazyLock;
2
3use regex::Regex;
4use thiserror::Error;
5
6use crate::{EMLError, EMLValueResultExt, utils::StringValueData};
7
8static CANDIDATE_ID_RE: LazyLock<Regex> =
10 LazyLock::new(|| Regex::new(r"^([1-9]\d*)$").expect("Failed to compile Candidate ID regex"));
11
12#[derive(Debug, Clone, PartialEq, Eq, Hash)]
16#[repr(transparent)]
17pub struct CandidateId(String);
18
19impl CandidateId {
20 pub fn new(s: impl AsRef<str>) -> Result<Self, EMLError> {
22 StringValueData::parse_from_str(s.as_ref()).wrap_value_error()
23 }
24
25 pub fn value(&self) -> &str {
27 &self.0
28 }
29}
30
31#[derive(Debug, Clone, Error)]
33#[error("Invalid candidate id: {0}")]
34pub struct InvalidCandidateIdError(String);
35
36impl StringValueData for CandidateId {
37 type Error = InvalidCandidateIdError;
38
39 fn parse_from_str(s: &str) -> Result<Self, Self::Error>
40 where
41 Self: Sized,
42 {
43 if CANDIDATE_ID_RE.is_match(s) {
44 Ok(CandidateId(s.to_string()))
45 } else {
46 Err(InvalidCandidateIdError(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_candidate_id_regex_compiles() {
61 LazyLock::force(&CANDIDATE_ID_RE);
62 }
63
64 #[test]
65 fn test_valid_candidate_ids() {
66 let valid_ids = ["1", "12345"];
67 for id in valid_ids {
68 assert!(
69 CandidateId::new(id).is_ok(),
70 "CandidateId should accept valid id: {}",
71 id
72 );
73 }
74 }
75
76 #[test]
77 fn test_invalid_candidate_ids() {
78 let invalid_ids = ["", "0", "0123", "abc", "123abc", "-1"];
79 for id in invalid_ids {
80 assert!(
81 CandidateId::new(id).is_err(),
82 "CandidateId should reject invalid id: {}",
83 id
84 );
85 }
86 }
87}