configbuilder-pattern-substitution 0.1.0

configbuilder-pattern: substitution domain, trait contract + reference implementation. No -svc counterpart.
Documentation
use crate::{SubstitutionError, SubstitutionPolicy};

/// Combines multiple [`SubstitutionPolicy`] implementations with AND logic.
pub struct CompositePolicy {
    policies: Vec<Box<dyn SubstitutionPolicy>>,
}

impl CompositePolicy {
    /// Create a composite policy from the supplied policy list.
    pub fn new(policies: Vec<Box<dyn SubstitutionPolicy>>) -> Self {
        Self { policies }
    }
}

impl std::fmt::Debug for CompositePolicy {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("CompositePolicy")
            .field("policy_count", &self.policies.len())
            .finish()
    }
}

impl SubstitutionPolicy for CompositePolicy {
    fn validate(&self, var_name: &str) -> Result<(), SubstitutionError> {
        let mut reasons = Vec::new();
        for policy in &self.policies {
            if let Err(SubstitutionError::VariableRejected { reason, .. }) =
                policy.validate(var_name)
            {
                reasons.push(reason);
            }
        }
        if reasons.is_empty() {
            Ok(())
        } else {
            Err(SubstitutionError::VariableRejected {
                var_name: var_name.to_string(),
                reason: reasons.join(" AND "),
                policy: self.description(),
            })
        }
    }

    fn description(&self) -> String {
        format!(
            "Composite({})",
            self.policies
                .iter()
                .map(|p| p.description())
                .collect::<Vec<_>>()
                .join(" AND ")
        )
    }
}