Skip to main content

eml_nl/utils/
name_short_code.rs

1use std::sync::LazyLock;
2
3use regex::Regex;
4use thiserror::Error;
5
6use crate::{EMLError, EMLValueResultExt as _, utils::StringValueData};
7
8/// Regular expression for validating NameShortCode values.
9static NAME_SHORT_CODE_RE: LazyLock<Regex> = LazyLock::new(|| {
10    Regex::new(r"^(\p{L}*\d{0,7})$").expect("Failed to compile Name Short Code regex")
11});
12
13/// A string of type NameShortCode as defined in the EML_NL specification
14///
15/// Called NameShortCodeType in the schema.
16#[derive(Debug, Clone, PartialEq, Eq, Hash)]
17#[repr(transparent)]
18pub struct NameShortCode(String);
19
20impl NameShortCode {
21    /// Create a new NameShortCode from a string, validating its format
22    pub fn new(s: impl AsRef<str>) -> Result<Self, EMLError> {
23        StringValueData::parse_from_str(s.as_ref()).wrap_value_error()
24    }
25
26    /// Get the raw string value of the NameShortCode.
27    pub fn value(&self) -> &str {
28        &self.0
29    }
30}
31
32/// Error returned when a string could not be parsed as a NameShortCode
33#[derive(Debug, Clone, Error)]
34#[error("Invalid name short code: {0}")]
35pub struct InvalidNameShortCodeError(String);
36
37impl StringValueData for NameShortCode {
38    type Error = InvalidNameShortCodeError;
39    fn parse_from_str(s: &str) -> Result<Self, Self::Error>
40    where
41        Self: Sized,
42    {
43        // suggested alternative by clippy is not more clear in this case
44        #[expect(clippy::len_zero)]
45        if s.len() >= 1 && s.len() <= 15 && NAME_SHORT_CODE_RE.is_match(s) {
46            Ok(NameShortCode(s.to_string()))
47        } else {
48            Err(InvalidNameShortCodeError(s.to_string()))
49        }
50    }
51
52    fn to_raw_value(&self) -> String {
53        self.0.clone()
54    }
55}
56
57#[cfg(test)]
58mod tests {
59    use super::*;
60
61    #[test]
62    fn test_name_short_code_regex_compiles() {
63        LazyLock::force(&NAME_SHORT_CODE_RE);
64    }
65
66    #[test]
67    fn test_valid_name_short_codes() {
68        let valid_codes = ["1", "12345", "ABC1234567"];
69        for code in valid_codes {
70            assert!(
71                NameShortCode::new(code).is_ok(),
72                "NameShortCode should accept valid code: {}",
73                code
74            );
75        }
76    }
77
78    #[test]
79    fn test_invalid_name_short_codes() {
80        let invalid_codes = ["", "verylongstringthatistoolong", "012345678", "123abc"];
81        for code in invalid_codes {
82            assert!(
83                NameShortCode::new(code).is_err(),
84                "NameShortCode should reject invalid code: {}",
85                code
86            );
87        }
88    }
89}