ito-core 0.1.33

Core functionality and business logic for Ito
Documentation
use std::collections::BTreeMap;
use std::path::Path;

use super::{
    ArtifactValidatorContext, CoreResult, DomainChangeRepository, ReportBuilder, ValidationIssue,
    ValidationLevel, ValidationLevelYaml, ValidatorId, delta_rules, format_specs, issue,
    tracking_rules, warning, with_format_spec, with_rule_id,
};

pub(super) fn run_artifact_rules(
    rep: &mut ReportBuilder,
    change_repo: &(impl DomainChangeRepository + ?Sized),
    ctx: ArtifactValidatorContext<'_>,
    validator_id: ValidatorId,
    artifact_id: &str,
    rules: Option<&BTreeMap<String, ValidationLevelYaml>>,
) -> CoreResult<()> {
    run_configured_rules(
        rep,
        change_repo,
        ctx,
        validator_id,
        ValidationRuleTarget::Artifact { id: artifact_id },
        rules,
    )
}

pub(super) fn run_proposal_rules(
    rep: &mut ReportBuilder,
    change_repo: &(impl DomainChangeRepository + ?Sized),
    ctx: ArtifactValidatorContext<'_>,
    validator_id: ValidatorId,
    rules: Option<&BTreeMap<String, ValidationLevelYaml>>,
) -> CoreResult<()> {
    run_configured_rules(
        rep,
        change_repo,
        ctx,
        validator_id,
        ValidationRuleTarget::Proposal,
        rules,
    )
}

pub(super) fn run_tracking_rules(
    rep: &mut ReportBuilder,
    change_repo: &(impl DomainChangeRepository + ?Sized),
    ctx: ArtifactValidatorContext<'_>,
    validator_id: ValidatorId,
    path: &Path,
    report_path: &str,
    rules: Option<&BTreeMap<String, ValidationLevelYaml>>,
) -> CoreResult<()> {
    run_configured_rules(
        rep,
        change_repo,
        ctx,
        validator_id,
        ValidationRuleTarget::Tracking { path, report_path },
        rules,
    )
}

#[derive(Debug, Clone, Copy)]
enum ValidationRuleTarget<'a> {
    Artifact {
        id: &'a str,
    },
    Proposal,
    Tracking {
        path: &'a Path,
        report_path: &'a str,
    },
}

impl ValidationRuleTarget<'_> {
    fn config_path(self, rule_name: &str) -> String {
        match self {
            ValidationRuleTarget::Artifact { id } => {
                format!("schema.validation.artifacts.{id}.rules.{rule_name}")
            }
            ValidationRuleTarget::Proposal => {
                format!("schema.validation.proposal.rules.{rule_name}")
            }
            ValidationRuleTarget::Tracking { .. } => {
                format!("schema.validation.tracking.rules.{rule_name}")
            }
        }
    }

    fn label(self) -> String {
        match self {
            ValidationRuleTarget::Artifact { id } => format!("artifact '{id}'"),
            ValidationRuleTarget::Proposal => "proposal artifact".to_string(),
            ValidationRuleTarget::Tracking { report_path, .. } => {
                format!("tracking file '{report_path}'")
            }
        }
    }
}

fn run_configured_rules(
    rep: &mut ReportBuilder,
    change_repo: &(impl DomainChangeRepository + ?Sized),
    ctx: ArtifactValidatorContext<'_>,
    validator_id: ValidatorId,
    target: ValidationRuleTarget<'_>,
    rules: Option<&BTreeMap<String, ValidationLevelYaml>>,
) -> CoreResult<()> {
    let Some(rules) = rules else {
        return Ok(());
    };

    for (rule_name, level) in rules {
        let supported_rules = supported_rules_for_target(validator_id, target);
        if !supported_rules
            .iter()
            .any(|supported| supported == rule_name)
        {
            let supported = if supported_rules.is_empty() {
                "none".to_string()
            } else {
                supported_rules.join(", ")
            };
            rep.push(with_format_spec(
                warning(
                    target.config_path(rule_name),
                    format!(
                        "Unknown validation rule '{rule_name}' for {} (validator: {}). Supported rules: {supported}",
                        target.label(),
                        validator_id.as_str()
                    ),
                ),
                format_spec_for_validator(validator_id),
            ));
            continue;
        }

        match (validator_id, target) {
            (ValidatorId::DeltaSpecsV1, ValidationRuleTarget::Artifact { .. }) => {
                delta_rules::run_artifact_rule(rep, change_repo, ctx, rule_name, *level)?;
            }
            (ValidatorId::DeltaSpecsV1, ValidationRuleTarget::Proposal) => {
                delta_rules::run_proposal_rule(rep, change_repo, ctx, rule_name, *level)?;
            }
            (
                ValidatorId::TasksTrackingV1,
                ValidationRuleTarget::Tracking { path, report_path },
            ) => {
                tracking_rules::run_rule(
                    rep,
                    change_repo,
                    ctx,
                    path,
                    report_path,
                    rule_name,
                    *level,
                )?;
            }
            _ => {}
        }
    }

    Ok(())
}

fn supported_rules_for_target(
    validator_id: ValidatorId,
    target: ValidationRuleTarget<'_>,
) -> &'static [&'static str] {
    match (validator_id, target) {
        (ValidatorId::DeltaSpecsV1, ValidationRuleTarget::Artifact { id: "specs" }) => {
            delta_rules::artifact_rules()
        }
        (
            ValidatorId::DeltaSpecsV1,
            ValidationRuleTarget::Artifact {
                id: "domain-discovery",
            },
        ) => delta_rules::domain_discovery_artifact_rules(),
        (ValidatorId::DeltaSpecsV1, ValidationRuleTarget::Artifact { .. }) => {
            delta_rules::artifact_rules()
        }
        (ValidatorId::DeltaSpecsV1, ValidationRuleTarget::Proposal) => {
            delta_rules::proposal_rules()
        }
        (ValidatorId::TasksTrackingV1, ValidationRuleTarget::Tracking { .. }) => {
            tracking_rules::rules()
        }
        _ => &[],
    }
}

fn format_spec_for_validator(validator_id: ValidatorId) -> format_specs::FormatSpecRef {
    match validator_id {
        ValidatorId::DeltaSpecsV1 => format_specs::DELTA_SPECS_V1,
        ValidatorId::TasksTrackingV1 => format_specs::TASKS_TRACKING_V1,
    }
}

pub(super) fn rule_issue(
    validator_id: ValidatorId,
    rule_name: &str,
    level: ValidationLevel,
    path: impl AsRef<str>,
    message: impl Into<String>,
) -> ValidationIssue {
    with_format_spec(
        with_rule_id(issue(level, path, message), rule_name),
        format_spec_for_validator(validator_id),
    )
}