vyre-conform 0.1.0

Conformance suite for vyre backends — proves byte-identical output to CPU reference
Documentation
use std::path::Path;

use super::document::TomlDocument;
use super::error::LoaderError;

impl TomlDocument {
    pub(super) fn validate(&self, path: &Path) -> Result<(), LoaderError> {
        validate_string(&self.version, "version", path)?;

        for (i, op) in self.ops.iter().enumerate() {
            let prefix = format!("ops[{i}]");
            validate_string(&op.name, &format!("{prefix}.name"), path)?;
            validate_string(&op.output, &format!("{prefix}.output"), path)?;
            validate_string(
                &op.reference_impl_id,
                &format!("{prefix}.reference_impl_id"),
                path,
            )?;
            for (j, input) in op.inputs.iter().enumerate() {
                validate_string(input, &format!("{prefix}.inputs[{j}]"), path)?;
            }
            for (j, law) in op.declared_laws.iter().enumerate() {
                validate_string(law, &format!("{prefix}.declared_laws[{j}]"), path)?;
            }
        }

        for (i, w) in self.witnesses.iter().enumerate() {
            let prefix = format!("witnesses[{i}]");
            validate_string(&w.op, &format!("{prefix}.op"), path)?;
            validate_string(&w.distribution, &format!("{prefix}.distribution"), path)?;
            if w.count > 1_000_000 {
                return Err(LoaderError::UnreasonableCount { count: w.count });
            }
        }

        for (i, d) in self.defendants.iter().enumerate() {
            let prefix = format!("defendants[{i}]");
            validate_string(&d.name, &format!("{prefix}.name"), path)?;
            validate_string(&d.source_url, &format!("{prefix}.source_url"), path)?;
            validate_string(&d.version, &format!("{prefix}.version"), path)?;
            validate_string(
                &d.expected_outcome,
                &format!("{prefix}.expected_outcome"),
                path,
            )?;
        }

        for (i, law) in self.laws.iter().enumerate() {
            let prefix = format!("laws[{i}]");
            validate_string(&law.name, &format!("{prefix}.name"), path)?;
            validate_string(&law.ariadne, &format!("{prefix}.ariadne"), path)?;
            validate_string(&law.predicate, &format!("{prefix}.predicate"), path)?;
            validate_string(
                &law.witness_strategy,
                &format!("{prefix}.witness_strategy"),
                path,
            )?;
        }

        for (i, rule) in self.independence.iter().enumerate() {
            let prefix = format!("independence[{i}]");
            validate_string(&rule.pattern, &format!("{prefix}.pattern"), path)?;
            validate_string(
                &rule.classification,
                &format!("{prefix}.classification"),
                path,
            )?;
            validate_string(&rule.severity, &format!("{prefix}.severity"), path)?;
        }

        Ok(())
    }
}

fn validate_string(s: &str, field: &str, path: &Path) -> Result<(), LoaderError> {
    for (byte_offset, byte) in s.bytes().enumerate() {
        if !matches!(byte, b'A'..=b'Z' | b'a'..=b'z' | b'0'..=b'9' | b' ' | b'.' | b'_' | b'/' | b'-')
        {
            return Err(LoaderError::UnsafeString {
                field: field.to_string(),
                path: path.to_path_buf(),
                byte_offset,
            });
        }
    }
    Ok(())
}