Skip to main content

eml_nl/utils/
affiliation_id.rs

1use 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/// A string of type affiliation id as defined in the EML_NL specification
12///
13/// Called AffiliationIdType in the schema.
14#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
15#[repr(transparent)]
16pub struct AffiliationId(NonZeroU64);
17
18impl AffiliationId {
19    /// Create a new AffiliationId.
20    pub fn new(value: NonZeroU64) -> Self {
21        AffiliationId(value)
22    }
23
24    /// Create a new AffiliationId from a u64 value.
25    pub fn from_u64(value: u64) -> Result<Self, InvalidAffiliationIdError> {
26        let value = NonZeroU64::new(value).ok_or(InvalidAffiliationIdError::ZeroInteger)?;
27        Ok(AffiliationId::new(value))
28    }
29
30    /// Get the value of the AffiliationId.
31    pub fn value(&self) -> NonZeroU64 {
32        self.0
33    }
34}
35
36impl FromStr for AffiliationId {
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 AffiliationId {
45    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
46        write!(f, "{}", self.0)
47    }
48}
49
50/// Error returned when a string could not be parsed as a AffiliationId
51#[derive(Debug, Clone, Error)]
52pub enum InvalidAffiliationIdError {
53    /// An invalid string was passed for parsing as an affiliation id
54    #[error("Failed to parse affiliation id: {0}")]
55    ParseError(ParseIntError),
56    /// The value was a zero integer, which is not allowed for affiliation ids
57    #[error("Affiliation id must be a non-zero positive integer")]
58    ZeroInteger,
59    /// Affiliation id cannot start with a zero
60    #[error("Affiliation id cannot start with a zero")]
61    StartsWithZero,
62}
63
64impl StringValueData for AffiliationId {
65    type Error = InvalidAffiliationIdError;
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(InvalidAffiliationIdError::StartsWithZero);
73        }
74
75        let value = u64::from_str(s).map_err(InvalidAffiliationIdError::ParseError)?;
76        let value = NonZeroU64::new(value).ok_or(InvalidAffiliationIdError::ZeroInteger)?;
77        Ok(AffiliationId::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_affiliation_ids() {
91        let valid_ids = ["1", "12345"];
92        for id in valid_ids {
93            assert!(
94                AffiliationId::from_str(id).is_ok(),
95                "AffiliationId should accept valid id: {}",
96                id
97            );
98        }
99    }
100
101    #[test]
102    fn test_invalid_affiliation_ids() {
103        let invalid_ids = ["0", " 0123", "0123", "abc", "", "-1"];
104        for id in invalid_ids {
105            assert!(
106                AffiliationId::from_str(id).is_err(),
107                "AffiliationId should reject invalid id: {}",
108                id
109            );
110        }
111    }
112}