main-spex 0.20.0

AI-first development framework that turns specifications into deterministic, verifiable code
use std::path::Path;

use crate::rule::Rule;
use crate::spec::Spec;
use crate::support::commitment::Commitment;

use super::super::finding::{Finding, Location};
use super::conformance_error;

#[cfg(test)]
#[path = "ac_order_test.rs"]
mod ac_order_test;

const AC_ORDER_CHECK: &str = "ac-ordered";

pub(super) fn validate_ac_order(specs: &[Spec], rules: &[Rule]) -> Vec<Finding> {
    let mut findings = Vec::new();

    for spec in specs {
        let criteria = spec
            .acceptance_criteria
            .iter()
            .map(|ac| (ac.line, ac.statement.commitment()));
        findings.extend(check_order(&spec.path, criteria));
    }

    for rule in rules {
        let criteria = rule
            .acceptance_criteria
            .iter()
            .filter_map(|ac| Some((ac.line, ac.commitment()?)));
        findings.extend(check_order(&rule.path, criteria));
    }

    findings
}

fn check_order(file: &Path, criteria: impl Iterator<Item = (usize, Commitment)>) -> Vec<Finding> {
    let criteria: Vec<_> = criteria.collect();
    criteria
        .windows(2)
        .filter_map(|pair| {
            let ((_, previous), (line, commitment)) = (pair[0], pair[1]);
            (commitment < previous).then(|| {
                conformance_error(
                    AC_ORDER_CHECK,
                    Some(Location::at_line(file, line)),
                    format!(
                        "acceptance criteria out of order: {} must not follow {} (order: {})",
                        commitment.label(),
                        previous.label(),
                        Commitment::order()
                    ),
                )
            })
        })
        .collect()
}