Skip to main content

oas_document/
conformance.rs

1// OAS Spec §10 — Conformance Levels
2//! Conformance level checking for OAS Identity Documents.
3
4use std::fmt;
5use std::str::FromStr;
6
7use serde::{Deserialize, Serialize};
8
9/// OAS conformance levels as defined in OAS Specification §10.
10///
11/// - **L0**: Basic Identity — valid document, signature, and kind
12/// - **L1**: Accountable Identity — L0 + valid lineage to human root
13/// - **L2**: Full Identity — L1 + immutable ledger anchoring + third-party attestation
14///
15/// # Examples
16///
17/// ```
18/// use oas_document::conformance::ConformanceLevel;
19/// use std::str::FromStr;
20///
21/// let level = ConformanceLevel::from_str("L1").unwrap();
22/// assert!(level >= ConformanceLevel::L0);
23/// ```
24#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
25pub enum ConformanceLevel {
26    /// Level 0 — Basic Identity (OAS Spec §10.1).
27    L0,
28    /// Level 1 — Accountable Identity (OAS Spec §10.2).
29    L1,
30    /// Level 2 — Full Identity (OAS Spec §10.3).
31    L2,
32}
33
34impl ConformanceLevel {
35    /// Returns the string representation.
36    pub fn as_str(&self) -> &'static str {
37        match self {
38            Self::L0 => "L0",
39            Self::L1 => "L1",
40            Self::L2 => "L2",
41        }
42    }
43}
44
45impl fmt::Display for ConformanceLevel {
46    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
47        f.write_str(self.as_str())
48    }
49}
50
51impl FromStr for ConformanceLevel {
52    type Err = String;
53
54    fn from_str(s: &str) -> Result<Self, Self::Err> {
55        match s {
56            "L0" => Ok(Self::L0),
57            "L1" => Ok(Self::L1),
58            "L2" => Ok(Self::L2),
59            _ => Err(format!(
60                "unknown conformance level '{s}'; expected one of: L0, L1, L2"
61            )),
62        }
63    }
64}
65
66#[cfg(test)]
67mod tests {
68    use super::*;
69
70    #[test]
71    fn ordering_l0_lt_l1_lt_l2() {
72        assert!(ConformanceLevel::L0 < ConformanceLevel::L1);
73        assert!(ConformanceLevel::L1 < ConformanceLevel::L2);
74    }
75
76    #[test]
77    fn round_trip() {
78        for level in [
79            ConformanceLevel::L0,
80            ConformanceLevel::L1,
81            ConformanceLevel::L2,
82        ] {
83            let s = level.as_str();
84            let parsed = ConformanceLevel::from_str(s);
85            assert!(parsed.is_ok());
86            assert_eq!(parsed.unwrap_or(ConformanceLevel::L0), level);
87        }
88    }
89
90    #[test]
91    fn serde_round_trip() {
92        let level = ConformanceLevel::L1;
93        let json = serde_json::to_string(&level).unwrap_or_default();
94        assert_eq!(json, r#""L1""#);
95        let parsed: ConformanceLevel = serde_json::from_str(&json).unwrap_or(ConformanceLevel::L0);
96        assert_eq!(parsed, level);
97    }
98}