Skip to main content

eml_nl/utils/
affiliation_id.rs

1use std::sync::LazyLock;
2
3use regex::Regex;
4use thiserror::Error;
5
6use crate::{EMLError, EMLValueResultExt, utils::StringValueData};
7
8/// Regular expression for validating affiliation id values.
9static AFFILIATION_ID_RE: LazyLock<Regex> =
10    LazyLock::new(|| Regex::new(r"^([1-9]\d*)$").expect("Failed to compile Affiliation ID regex"));
11
12/// A string of type affiliation id as defined in the EML_NL specification
13///
14/// Called AffiliationIdType in the schema.
15#[derive(Debug, Clone, PartialEq, Eq, Hash)]
16#[repr(transparent)]
17pub struct AffiliationId(String);
18
19impl AffiliationId {
20    /// Create a new AffiliationId 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 AffiliationId.
26    pub fn value(&self) -> &str {
27        &self.0
28    }
29}
30
31/// Error returned when a string could not be parsed as a AffiliationId
32#[derive(Debug, Clone, Error)]
33#[error("Invalid affiliation id: {0}")]
34pub struct InvalidAffiliationIdError(String);
35
36impl StringValueData for AffiliationId {
37    type Error = InvalidAffiliationIdError;
38
39    fn parse_from_str(s: &str) -> Result<Self, Self::Error>
40    where
41        Self: Sized,
42    {
43        if AFFILIATION_ID_RE.is_match(s) {
44            Ok(AffiliationId(s.to_string()))
45        } else {
46            Err(InvalidAffiliationIdError(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_affiliation_id_regex_compiles() {
61        LazyLock::force(&AFFILIATION_ID_RE);
62    }
63
64    #[test]
65    fn test_valid_affiliation_ids() {
66        let valid_ids = ["1", "12345"];
67        for id in valid_ids {
68            assert!(
69                AffiliationId::new(id).is_ok(),
70                "AffiliationId should accept valid id: {}",
71                id
72            );
73        }
74    }
75
76    #[test]
77    fn test_invalid_affiliation_ids() {
78        let invalid_ids = ["0", "0123", "abc", "", "-1"];
79        for id in invalid_ids {
80            assert!(
81                AffiliationId::new(id).is_err(),
82                "AffiliationId should reject invalid id: {}",
83                id
84            );
85        }
86    }
87}