mod built_in;
use chrono::NaiveDate;
use std::collections::HashMap;
use datasynth_core::models::compliance::{
ComplianceDomain, ComplianceStandard, CrossReference, JurisdictionProfile, StandardCategory,
StandardId, TemporalVersion,
};
pub use built_in::register_built_in_standards;
#[derive(Debug, Default)]
pub struct StandardRegistry {
standards: HashMap<StandardId, ComplianceStandard>,
jurisdictions: HashMap<String, JurisdictionProfile>,
cross_references: Vec<CrossReference>,
}
impl StandardRegistry {
pub fn new() -> Self {
Self::default()
}
pub fn with_built_in() -> Self {
let mut registry = Self::new();
built_in::register_built_in_standards(&mut registry);
registry
}
pub fn register_standard(&mut self, standard: ComplianceStandard) {
self.standards.insert(standard.id.clone(), standard);
}
pub fn register_jurisdiction(&mut self, profile: JurisdictionProfile) {
self.jurisdictions
.insert(profile.country_code.clone(), profile);
}
pub fn add_cross_reference(&mut self, xref: CrossReference) {
self.cross_references.push(xref);
}
pub fn get(&self, id: &StandardId) -> Option<&ComplianceStandard> {
self.standards.get(id)
}
pub fn jurisdiction(&self, country_code: &str) -> Option<&JurisdictionProfile> {
self.jurisdictions.get(country_code)
}
pub fn all_standards(&self) -> impl Iterator<Item = &ComplianceStandard> {
self.standards.values()
}
pub fn all_jurisdictions(&self) -> impl Iterator<Item = &JurisdictionProfile> {
self.jurisdictions.values()
}
pub fn standard_count(&self) -> usize {
self.standards.len()
}
pub fn jurisdiction_count(&self) -> usize {
self.jurisdictions.len()
}
pub fn cross_references(&self) -> &[CrossReference] {
&self.cross_references
}
pub fn active_version(&self, id: &StandardId, at: NaiveDate) -> Option<&TemporalVersion> {
self.standards
.get(id)
.and_then(|std| std.versions.iter().find(|v| v.is_active_at(at)))
}
pub fn active_version_in(
&self,
id: &StandardId,
country: &str,
at: NaiveDate,
) -> Option<&TemporalVersion> {
self.standards
.get(id)
.and_then(|std| std.versions.iter().find(|v| v.is_active_at_in(at, country)))
}
pub fn standards_for_jurisdiction(
&self,
country: &str,
at: NaiveDate,
) -> Vec<&ComplianceStandard> {
self.standards
.values()
.filter(|std| {
let is_mandatory = std.mandatory_jurisdictions.contains(&country.to_string())
|| std.mandatory_jurisdictions.contains(&"*".to_string());
let has_active = std.versions.iter().any(|v| v.is_active_at_in(at, country));
is_mandatory && has_active
})
.collect()
}
pub fn cross_references_for(&self, id: &StandardId) -> Vec<&CrossReference> {
self.cross_references
.iter()
.filter(|xr| xr.from_standard == *id || xr.to_standard == *id)
.collect()
}
pub fn supersession_chain(&self, id: &StandardId) -> Vec<&ComplianceStandard> {
let mut chain = Vec::new();
let mut visited = std::collections::HashSet::new();
let mut current = id.clone();
loop {
if !visited.insert(current.clone()) {
break; }
if self.standards.contains_key(¤t) {
let predecessor = self
.standards
.values()
.find(|s| s.superseded_by.as_ref() == Some(¤t));
if let Some(pred) = predecessor {
current = pred.id.clone();
} else {
break;
}
} else {
break;
}
}
visited.clear();
while let Some(std) = self.standards.get(¤t) {
if !visited.insert(current.clone()) {
break; }
chain.push(std);
if let Some(ref successor) = std.superseded_by {
current = successor.clone();
} else {
break;
}
}
chain
}
pub fn by_category(&self, category: StandardCategory) -> Vec<&ComplianceStandard> {
self.standards
.values()
.filter(|s| s.category == category)
.collect()
}
pub fn by_domain(&self, domain: ComplianceDomain) -> Vec<&ComplianceStandard> {
self.standards
.values()
.filter(|s| s.domain == domain)
.collect()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_registry_with_built_in() {
let registry = StandardRegistry::with_built_in();
assert!(
registry.standard_count() > 0,
"Should have built-in standards"
);
assert!(
registry.jurisdiction_count() > 0,
"Should have jurisdiction profiles"
);
}
#[test]
fn test_active_version_lookup() {
let registry = StandardRegistry::with_built_in();
let ifrs16 = StandardId::new("IFRS", "16");
let date = NaiveDate::from_ymd_opt(2025, 6, 30).expect("valid");
let version = registry.active_version(&ifrs16, date);
assert!(version.is_some(), "IFRS 16 should be active in 2025");
}
#[test]
fn test_cross_references() {
let registry = StandardRegistry::with_built_in();
let ifrs15 = StandardId::new("IFRS", "15");
let xrefs = registry.cross_references_for(&ifrs15);
assert!(!xrefs.is_empty(), "IFRS 15 should have cross-references");
}
#[test]
fn test_supersession_chain() {
let registry = StandardRegistry::with_built_in();
let ifrs16 = StandardId::new("IFRS", "16");
let chain = registry.supersession_chain(&ifrs16);
assert!(chain.len() >= 2, "Should have IAS 17 → IFRS 16 chain");
}
}