oas-document 1.1.1

OAS Identity Document types and validation for the Open Agent Specification
Documentation
// OAS Spec §5.15 — Compliance & Jurisdiction
//! Compliance and jurisdiction section for OAS Identity Documents.
//!
//! Describes the legal and regulatory context in which an entity operates:
//! jurisdictions, certifications, and policy URLs.

use serde::{Deserialize, Serialize};

/// A compliance certification held by the entity.
///
/// # Examples
///
/// ```
/// use oas_document::compliance::Certification;
///
/// let cert = Certification {
///     standard: "SOC2 Type II".to_string(),
///     issuer: Some("Vanta".to_string()),
///     valid_until: Some("2027-06-30".to_string()),
///     url: Some("https://acme.com/compliance/soc2".to_string()),
/// };
/// ```
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Certification {
    /// Standard name: `"SOC2 Type II"`, `"GDPR"`, `"HIPAA"`, `"ISO 27001"`.
    pub standard: String,

    /// Issuing body or audit firm.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub issuer: Option<String>,

    /// Expiry date (ISO 8601 date or datetime).
    #[serde(skip_serializing_if = "Option::is_none")]
    pub valid_until: Option<String>,

    /// URL to certification evidence or public report.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub url: Option<String>,
}

/// Compliance and jurisdiction section of an OAS Identity Document.
///
/// # Examples
///
/// ```
/// use oas_document::compliance::{ComplianceSection, Certification};
///
/// let compliance = ComplianceSection {
///     jurisdictions: vec!["US".to_string(), "EU".to_string()],
///     certifications: vec![Certification {
///         standard: "GDPR".to_string(),
///         issuer: None,
///         valid_until: None,
///         url: None,
///     }],
///     terms_of_service_url: Some("https://acme.com/tos".to_string()),
///     privacy_policy_url: Some("https://acme.com/privacy".to_string()),
///     data_handling_policy_url: Some("https://acme.com/data-policy".to_string()),
///     data_residency: Some("EU".to_string()),
/// };
/// assert_eq!(compliance.jurisdictions.len(), 2);
/// ```
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ComplianceSection {
    /// Operating jurisdictions (ISO 3166-1 alpha-2 country codes).
    #[serde(default, skip_serializing_if = "Vec::is_empty")]
    pub jurisdictions: Vec<String>,

    /// Compliance certifications.
    #[serde(default, skip_serializing_if = "Vec::is_empty")]
    pub certifications: Vec<Certification>,

    /// Terms of service URL.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub terms_of_service_url: Option<String>,

    /// Privacy policy URL.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub privacy_policy_url: Option<String>,

    /// Data handling / data processing policy URL.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub data_handling_policy_url: Option<String>,

    /// Data residency region (ISO 3166-1 alpha-2 or region name).
    #[serde(skip_serializing_if = "Option::is_none")]
    pub data_residency: Option<String>,
}

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

    #[test]
    fn serde_round_trip() {
        let comp = ComplianceSection {
            jurisdictions: vec!["US".to_string()],
            certifications: vec![Certification {
                standard: "SOC2".to_string(),
                issuer: Some("Vanta".to_string()),
                valid_until: Some("2027-12-31".to_string()),
                url: None,
            }],
            terms_of_service_url: Some("https://example.com/tos".to_string()),
            privacy_policy_url: None,
            data_handling_policy_url: None,
            data_residency: Some("US".to_string()),
        };

        let json = serde_json::to_string(&comp).unwrap_or_default();
        let parsed: ComplianceSection =
            serde_json::from_str(&json).unwrap_or_else(|_| comp.clone());
        assert_eq!(comp, parsed);
    }

    #[test]
    fn empty_section_deserializes() {
        let json = r#"{}"#;
        let comp: Result<ComplianceSection, _> = serde_json::from_str(json);
        assert!(comp.is_ok());
    }
}