eml_nl/utils/
candidate_id.rs1use std::{
2 fmt::Display,
3 num::{NonZeroU64, ParseIntError},
4 str::FromStr,
5};
6
7use thiserror::Error;
8
9use crate::{EMLError, EMLValueResultExt, utils::StringValueData};
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
15#[repr(transparent)]
16pub struct CandidateId(NonZeroU64);
17
18impl CandidateId {
19 pub fn new(value: NonZeroU64) -> Self {
21 CandidateId(value)
22 }
23
24 pub fn from_u64(value: u64) -> Result<Self, InvalidCandidateIdError> {
26 let value = NonZeroU64::new(value).ok_or(InvalidCandidateIdError::ZeroInteger)?;
27 Ok(CandidateId::new(value))
28 }
29
30 pub fn value(&self) -> NonZeroU64 {
32 self.0
33 }
34}
35
36impl FromStr for CandidateId {
37 type Err = EMLError;
38
39 fn from_str(s: &str) -> Result<Self, Self::Err> {
40 StringValueData::parse_from_str(s).wrap_value_error()
41 }
42}
43
44impl Display for CandidateId {
45 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
46 write!(f, "{}", self.0)
47 }
48}
49
50#[derive(Debug, Clone, Error)]
52pub enum InvalidCandidateIdError {
53 #[error("Failed to parse candidate id: {0}")]
55 ParseError(ParseIntError),
56 #[error("Candidate id must be a non-zero positive integer")]
58 ZeroInteger,
59 #[error("Candidate id cannot start with a zero")]
61 StartsWithZero,
62}
63
64impl StringValueData for CandidateId {
65 type Error = InvalidCandidateIdError;
66
67 fn parse_from_str(s: &str) -> Result<Self, Self::Error>
68 where
69 Self: Sized,
70 {
71 if s.starts_with("0") {
72 return Err(InvalidCandidateIdError::StartsWithZero);
73 }
74
75 let value = u64::from_str(s).map_err(InvalidCandidateIdError::ParseError)?;
76 let value = NonZeroU64::new(value).ok_or(InvalidCandidateIdError::ZeroInteger)?;
77 Ok(CandidateId::new(value))
78 }
79
80 fn to_raw_value(&self) -> Box<str> {
81 self.0.to_string().into()
82 }
83}
84
85#[cfg(test)]
86mod tests {
87 use super::*;
88
89 #[test]
90 fn test_valid_candidate_ids() {
91 let valid_ids = ["1", "12345"];
92 for id in valid_ids {
93 assert!(
94 CandidateId::from_str(id).is_ok(),
95 "CandidateId should accept valid id: {}",
96 id
97 );
98 }
99 }
100
101 #[test]
102 fn test_invalid_candidate_ids() {
103 let invalid_ids = ["", "0", " 123", "0123", "abc", "123abc", "-1"];
104 for id in invalid_ids {
105 assert!(
106 CandidateId::from_str(id).is_err(),
107 "CandidateId should reject invalid id: {}",
108 id
109 );
110 }
111 }
112}