oas-document 1.1.1

OAS Identity Document types and validation for the Open Agent Specification
Documentation
// OAS Spec §10 — Conformance Levels
//! Conformance level checking for OAS Identity Documents.

use std::fmt;
use std::str::FromStr;

use serde::{Deserialize, Serialize};

/// OAS conformance levels as defined in OAS Specification §10.
///
/// - **L0**: Basic Identity — valid document, signature, and kind
/// - **L1**: Accountable Identity — L0 + valid lineage to human root
/// - **L2**: Full Identity — L1 + immutable ledger anchoring + third-party attestation
///
/// # Examples
///
/// ```
/// use oas_document::conformance::ConformanceLevel;
/// use std::str::FromStr;
///
/// let level = ConformanceLevel::from_str("L1").unwrap();
/// assert!(level >= ConformanceLevel::L0);
/// ```
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub enum ConformanceLevel {
    /// Level 0 — Basic Identity (OAS Spec §10.1).
    L0,
    /// Level 1 — Accountable Identity (OAS Spec §10.2).
    L1,
    /// Level 2 — Full Identity (OAS Spec §10.3).
    L2,
}

impl ConformanceLevel {
    /// Returns the string representation.
    pub fn as_str(&self) -> &'static str {
        match self {
            Self::L0 => "L0",
            Self::L1 => "L1",
            Self::L2 => "L2",
        }
    }
}

impl fmt::Display for ConformanceLevel {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.write_str(self.as_str())
    }
}

impl FromStr for ConformanceLevel {
    type Err = String;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s {
            "L0" => Ok(Self::L0),
            "L1" => Ok(Self::L1),
            "L2" => Ok(Self::L2),
            _ => Err(format!(
                "unknown conformance level '{s}'; expected one of: L0, L1, L2"
            )),
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn ordering_l0_lt_l1_lt_l2() {
        assert!(ConformanceLevel::L0 < ConformanceLevel::L1);
        assert!(ConformanceLevel::L1 < ConformanceLevel::L2);
    }

    #[test]
    fn round_trip() {
        for level in [
            ConformanceLevel::L0,
            ConformanceLevel::L1,
            ConformanceLevel::L2,
        ] {
            let s = level.as_str();
            let parsed = ConformanceLevel::from_str(s);
            assert!(parsed.is_ok());
            assert_eq!(parsed.unwrap_or(ConformanceLevel::L0), level);
        }
    }

    #[test]
    fn serde_round_trip() {
        let level = ConformanceLevel::L1;
        let json = serde_json::to_string(&level).unwrap_or_default();
        assert_eq!(json, r#""L1""#);
        let parsed: ConformanceLevel = serde_json::from_str(&json).unwrap_or(ConformanceLevel::L0);
        assert_eq!(parsed, level);
    }
}