oas_document/compliance.rs
1// OAS Spec §5.15 — Compliance & Jurisdiction
2//! Compliance and jurisdiction section for OAS Identity Documents.
3//!
4//! Describes the legal and regulatory context in which an entity operates:
5//! jurisdictions, certifications, and policy URLs.
6
7use serde::{Deserialize, Serialize};
8
9/// A compliance certification held by the entity.
10///
11/// # Examples
12///
13/// ```
14/// use oas_document::compliance::Certification;
15///
16/// let cert = Certification {
17/// standard: "SOC2 Type II".to_string(),
18/// issuer: Some("Vanta".to_string()),
19/// valid_until: Some("2027-06-30".to_string()),
20/// url: Some("https://acme.com/compliance/soc2".to_string()),
21/// };
22/// ```
23#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
24#[serde(rename_all = "camelCase")]
25pub struct Certification {
26 /// Standard name: `"SOC2 Type II"`, `"GDPR"`, `"HIPAA"`, `"ISO 27001"`.
27 pub standard: String,
28
29 /// Issuing body or audit firm.
30 #[serde(skip_serializing_if = "Option::is_none")]
31 pub issuer: Option<String>,
32
33 /// Expiry date (ISO 8601 date or datetime).
34 #[serde(skip_serializing_if = "Option::is_none")]
35 pub valid_until: Option<String>,
36
37 /// URL to certification evidence or public report.
38 #[serde(skip_serializing_if = "Option::is_none")]
39 pub url: Option<String>,
40}
41
42/// Compliance and jurisdiction section of an OAS Identity Document.
43///
44/// # Examples
45///
46/// ```
47/// use oas_document::compliance::{ComplianceSection, Certification};
48///
49/// let compliance = ComplianceSection {
50/// jurisdictions: vec!["US".to_string(), "EU".to_string()],
51/// certifications: vec![Certification {
52/// standard: "GDPR".to_string(),
53/// issuer: None,
54/// valid_until: None,
55/// url: None,
56/// }],
57/// terms_of_service_url: Some("https://acme.com/tos".to_string()),
58/// privacy_policy_url: Some("https://acme.com/privacy".to_string()),
59/// data_handling_policy_url: Some("https://acme.com/data-policy".to_string()),
60/// data_residency: Some("EU".to_string()),
61/// };
62/// assert_eq!(compliance.jurisdictions.len(), 2);
63/// ```
64#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
65#[serde(rename_all = "camelCase")]
66pub struct ComplianceSection {
67 /// Operating jurisdictions (ISO 3166-1 alpha-2 country codes).
68 #[serde(default, skip_serializing_if = "Vec::is_empty")]
69 pub jurisdictions: Vec<String>,
70
71 /// Compliance certifications.
72 #[serde(default, skip_serializing_if = "Vec::is_empty")]
73 pub certifications: Vec<Certification>,
74
75 /// Terms of service URL.
76 #[serde(skip_serializing_if = "Option::is_none")]
77 pub terms_of_service_url: Option<String>,
78
79 /// Privacy policy URL.
80 #[serde(skip_serializing_if = "Option::is_none")]
81 pub privacy_policy_url: Option<String>,
82
83 /// Data handling / data processing policy URL.
84 #[serde(skip_serializing_if = "Option::is_none")]
85 pub data_handling_policy_url: Option<String>,
86
87 /// Data residency region (ISO 3166-1 alpha-2 or region name).
88 #[serde(skip_serializing_if = "Option::is_none")]
89 pub data_residency: Option<String>,
90}
91
92#[cfg(test)]
93mod tests {
94 use super::*;
95
96 #[test]
97 fn serde_round_trip() {
98 let comp = ComplianceSection {
99 jurisdictions: vec!["US".to_string()],
100 certifications: vec![Certification {
101 standard: "SOC2".to_string(),
102 issuer: Some("Vanta".to_string()),
103 valid_until: Some("2027-12-31".to_string()),
104 url: None,
105 }],
106 terms_of_service_url: Some("https://example.com/tos".to_string()),
107 privacy_policy_url: None,
108 data_handling_policy_url: None,
109 data_residency: Some("US".to_string()),
110 };
111
112 let json = serde_json::to_string(&comp).unwrap_or_default();
113 let parsed: ComplianceSection =
114 serde_json::from_str(&json).unwrap_or_else(|_| comp.clone());
115 assert_eq!(comp, parsed);
116 }
117
118 #[test]
119 fn empty_section_deserializes() {
120 let json = r#"{}"#;
121 let comp: Result<ComplianceSection, _> = serde_json::from_str(json);
122 assert!(comp.is_ok());
123 }
124}