use std::sync::LazyLock;
use regex::Regex;
use thiserror::Error;
use crate::{EMLError, EMLValueResultExt, utils::StringValueData};
static CANDIDATE_ID_RE: LazyLock<Regex> =
LazyLock::new(|| Regex::new(r"^([1-9]\d*)$").expect("Failed to compile Candidate ID regex"));
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[repr(transparent)]
pub struct CandidateId(String);
impl CandidateId {
pub fn new(s: impl AsRef<str>) -> Result<Self, EMLError> {
StringValueData::parse_from_str(s.as_ref()).wrap_value_error()
}
pub fn value(&self) -> &str {
&self.0
}
}
#[derive(Debug, Clone, Error)]
#[error("Invalid candidate id: {0}")]
pub struct InvalidCandidateIdError(String);
impl StringValueData for CandidateId {
type Error = InvalidCandidateIdError;
fn parse_from_str(s: &str) -> Result<Self, Self::Error>
where
Self: Sized,
{
if CANDIDATE_ID_RE.is_match(s) {
Ok(CandidateId(s.to_string()))
} else {
Err(InvalidCandidateIdError(s.to_string()))
}
}
fn to_raw_value(&self) -> String {
self.0.clone()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_candidate_id_regex_compiles() {
LazyLock::force(&CANDIDATE_ID_RE);
}
#[test]
fn test_valid_candidate_ids() {
let valid_ids = ["1", "12345"];
for id in valid_ids {
assert!(
CandidateId::new(id).is_ok(),
"CandidateId should accept valid id: {}",
id
);
}
}
#[test]
fn test_invalid_candidate_ids() {
let invalid_ids = ["", "0", "0123", "abc", "123abc", "-1"];
for id in invalid_ids {
assert!(
CandidateId::new(id).is_err(),
"CandidateId should reject invalid id: {}",
id
);
}
}
}