use serde::{Deserialize, Serialize};
use super::convergence_state::{ConvergencePointType, SubstrateType};
use super::point_id::PointId;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ComplianceBinding {
pub selector: PointSelector,
pub control: ComplianceControl,
pub phase: VerificationPhase,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum PointSelector {
ByType(ConvergencePointType),
BySubstrate(SubstrateType),
BySubstrateAndType(SubstrateType, ConvergencePointType),
ByEnvironment(String),
ByDataClassification(DataClassification),
ById(PointId),
All,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum VerificationPhase {
PlanTime,
AtBoundary,
PostConvergence,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ComplianceControl {
pub framework: String,
pub control_id: String,
pub description: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum DataClassification {
Pii,
Phi,
Pci,
Public,
Internal,
Confidential,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct ComplianceClosure {
pub bindings: Vec<ComplianceBinding>,
pub resolved: Vec<ResolvedControl>,
pub plan_time_count: usize,
pub at_boundary_count: usize,
pub post_convergence_count: usize,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ResolvedControl {
pub control: ComplianceControl,
pub point_ids: Vec<PointId>,
pub phase: VerificationPhase,
}
impl PointSelector {
pub fn matches(
&self,
point_type: &ConvergencePointType,
substrate: &SubstrateType,
point_id: &PointId,
environment: Option<&str>,
data_class: Option<&DataClassification>,
) -> bool {
match self {
Self::ByType(t) => t == point_type,
Self::BySubstrate(s) => s == substrate,
Self::BySubstrateAndType(s, t) => s == substrate && t == point_type,
Self::ByEnvironment(env) => environment == Some(env.as_str()),
Self::ByDataClassification(dc) => data_class == Some(dc),
Self::ById(id) => id == point_id,
Self::All => true,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn sample_point_id() -> PointId {
PointId::compute(b"test", &[], b"state")
}
#[test]
fn test_selector_all_matches_everything() {
let sel = PointSelector::All;
assert!(sel.matches(
&ConvergencePointType::Transform,
&SubstrateType::Compute,
&sample_point_id(),
None,
None,
));
}
#[test]
fn test_selector_by_substrate() {
let sel = PointSelector::BySubstrate(SubstrateType::Security);
assert!(sel.matches(
&ConvergencePointType::Gate,
&SubstrateType::Security,
&sample_point_id(),
None,
None,
));
assert!(!sel.matches(
&ConvergencePointType::Gate,
&SubstrateType::Compute,
&sample_point_id(),
None,
None,
));
}
#[test]
fn test_selector_by_type() {
let sel = PointSelector::ByType(ConvergencePointType::Gate);
assert!(sel.matches(
&ConvergencePointType::Gate,
&SubstrateType::Compute,
&sample_point_id(),
None,
None,
));
assert!(!sel.matches(
&ConvergencePointType::Transform,
&SubstrateType::Compute,
&sample_point_id(),
None,
None,
));
}
#[test]
fn test_selector_by_data_classification() {
let sel = PointSelector::ByDataClassification(DataClassification::Pii);
assert!(sel.matches(
&ConvergencePointType::Transform,
&SubstrateType::Storage,
&sample_point_id(),
None,
Some(&DataClassification::Pii),
));
assert!(!sel.matches(
&ConvergencePointType::Transform,
&SubstrateType::Storage,
&sample_point_id(),
None,
Some(&DataClassification::Public),
));
}
#[test]
fn test_verification_phase_serde() {
for phase in [
VerificationPhase::PlanTime,
VerificationPhase::AtBoundary,
VerificationPhase::PostConvergence,
] {
let json = serde_json::to_string(&phase).unwrap();
let parsed: VerificationPhase = serde_json::from_str(&json).unwrap();
assert_eq!(phase, parsed);
}
}
#[test]
fn test_compliance_binding_serde() {
let binding = ComplianceBinding {
selector: PointSelector::BySubstrate(SubstrateType::Security),
control: ComplianceControl {
framework: "nist-800-53".into(),
control_id: "AC-6".into(),
description: "Least privilege".into(),
},
phase: VerificationPhase::PlanTime,
};
let json = serde_json::to_string(&binding).unwrap();
let _: ComplianceBinding = serde_json::from_str(&json).unwrap();
}
}