dpcs 0.8.0

Reference implementation of the Data Pipeline Contract Standard (DPCS)
Documentation
//! Structural validation phase.

use crate::diagnostics::{categories, Diagnostic, ValidationReport};
use crate::model::PipelineContract;

/// Validate non-identity structural constraints.
///
/// Identifier presence and uniqueness are owned by the Canonical Object Model
/// phase. This phase covers remaining structural shape such as step types.
pub fn validate(contract: &PipelineContract) -> ValidationReport {
    let mut report = ValidationReport::new();

    for step in &contract.steps {
        if step.id.trim().is_empty() {
            continue;
        }

        if step.step_type.trim().is_empty() {
            report.push(
                Diagnostic::error(
                    "DPCS-STR-003",
                    categories::STRUCTURAL,
                    format!("pipeline step `{}` has an empty type", step.id),
                )
                .with_object_ref(format!("steps.{}.type", step.id))
                .with_remediation("Provide a step type"),
            );
        }

        for (side, ports) in [("inputs", &step.inputs), ("outputs", &step.outputs)] {
            for (index, port) in ports.iter().enumerate() {
                if port.id.trim().is_empty() {
                    report.push(
                        Diagnostic::error(
                            "DPCS-STR-001",
                            categories::STRUCTURAL,
                            format!(
                                "pipeline step `{}` declares an empty {side} port id",
                                step.id
                            ),
                        )
                        .with_object_ref(format!("steps.{}.{side}[{index}].id", step.id))
                        .with_remediation("Provide a non-empty port identifier"),
                    );
                }
            }
        }
    }

    report
}