Skip to main content

datasynth_core/models/compliance/
standard_id.rs

1//! Canonical standard identifier.
2
3use serde::{Deserialize, Serialize};
4use std::fmt;
5
6/// Canonical identifier for a compliance standard.
7///
8/// Format: `"{BODY}-{NUMBER}"` (e.g., `"IFRS-16"`, `"ISA-315"`, `"SOX-404"`, `"ASC-606"`)
9///
10/// # Examples
11///
12/// ```
13/// use datasynth_core::models::compliance::StandardId;
14///
15/// let id = StandardId::new("IFRS", "16");
16/// assert_eq!(id.body(), "IFRS");
17/// assert_eq!(id.number(), "16");
18/// assert_eq!(id.as_str(), "IFRS-16");
19/// ```
20#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
21pub struct StandardId(pub String);
22
23impl StandardId {
24    /// Creates a new standard ID from body and number.
25    pub fn new(body: &str, number: &str) -> Self {
26        Self(format!("{body}-{number}"))
27    }
28
29    /// Creates a standard ID from a full string (e.g., "IFRS-16").
30    pub fn parse(s: &str) -> Self {
31        Self(s.to_string())
32    }
33
34    /// Returns the full identifier string.
35    pub fn as_str(&self) -> &str {
36        &self.0
37    }
38
39    /// Returns the issuing body prefix (e.g., "IFRS", "ISA", "SOX", "ASC").
40    pub fn body(&self) -> &str {
41        self.0.split('-').next().unwrap_or("")
42    }
43
44    /// Returns the standard number/section (e.g., "16", "315", "404", "606").
45    pub fn number(&self) -> &str {
46        // Handle multi-part identifiers like "BASEL-III-CAP" or "PCAOB-AS-2201"
47        let parts: Vec<&str> = self.0.splitn(2, '-').collect();
48        if parts.len() > 1 {
49            parts[1]
50        } else {
51            ""
52        }
53    }
54}
55
56impl fmt::Display for StandardId {
57    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
58        write!(f, "{}", self.0)
59    }
60}
61
62impl From<&str> for StandardId {
63    fn from(s: &str) -> Self {
64        Self(s.to_string())
65    }
66}
67
68impl From<String> for StandardId {
69    fn from(s: String) -> Self {
70        Self(s)
71    }
72}
73
74#[cfg(test)]
75mod tests {
76    use super::*;
77
78    #[test]
79    fn test_standard_id_creation() {
80        let id = StandardId::new("IFRS", "16");
81        assert_eq!(id.as_str(), "IFRS-16");
82        assert_eq!(id.body(), "IFRS");
83        assert_eq!(id.number(), "16");
84    }
85
86    #[test]
87    fn test_standard_id_display() {
88        let id = StandardId::new("SOX", "404");
89        assert_eq!(format!("{id}"), "SOX-404");
90    }
91
92    #[test]
93    fn test_standard_id_equality() {
94        let a = StandardId::new("ISA", "315");
95        let b = StandardId::from("ISA-315");
96        assert_eq!(a, b);
97    }
98
99    #[test]
100    fn test_standard_id_ordering() {
101        let mut ids = [
102            StandardId::from("SOX-404"),
103            StandardId::from("ASC-606"),
104            StandardId::from("IFRS-16"),
105        ];
106        ids.sort();
107        assert_eq!(ids[0].as_str(), "ASC-606");
108        assert_eq!(ids[1].as_str(), "IFRS-16");
109        assert_eq!(ids[2].as_str(), "SOX-404");
110    }
111}