kaiv 0.3.0

Reference implementation of the kaiv format (Levels 0-3): lexer, compiler, denormalizer, schema compiler, validator
Documentation
//! Error catalog per SPEC.md § Errors. Names match the spec strings
//! exactly; the conformance runner compares against them.

use std::fmt;

/// Lexer errors (SPEC.md § Lexer Errors), in priority order: when one
/// line raises several, the lowest discriminant wins.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum LexError {
    Bom,
    InvalidUtf8,
    InvalidCharacter,
    MissingFinalEol,
    InvalidVersion,
    UnsupportedVersion,
    EmptyKey,
    MissingOperator,
    InvalidKey,
    InvalidDirective,
    InvalidConstraint,
}

impl LexError {
    pub fn name(self) -> &'static str {
        match self {
            LexError::Bom => "BOM_ERROR",
            LexError::InvalidUtf8 => "INVALID_UTF8_ERROR",
            LexError::InvalidCharacter => "INVALID_CHARACTER_ERROR",
            LexError::MissingFinalEol => "MISSING_FINAL_EOL_ERROR",
            LexError::InvalidVersion => "INVALID_VERSION_ERROR",
            LexError::UnsupportedVersion => "UNSUPPORTED_VERSION_ERROR",
            LexError::EmptyKey => "EMPTY_KEY_ERROR",
            LexError::MissingOperator => "MISSING_OPERATOR_ERROR",
            LexError::InvalidKey => "INVALID_KEY_ERROR",
            LexError::InvalidDirective => "INVALID_DIRECTIVE_ERROR",
            LexError::InvalidConstraint => "INVALID_CONSTRAINT_ERROR",
        }
    }
}

impl fmt::Display for LexError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.write_str(self.name())
    }
}

/// A lexer error with the 1-based line it was detected on
/// (0 = whole-document errors reported without a line number).
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct LexErrorAt {
    pub error: LexError,
    pub line: usize,
}

/// Application errors (SPEC.md § Application Errors).
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AppError {
    MetadataWithoutTarget,
    /// A value references an undefined hidden variable or data field,
    /// or contains a `$` that begins neither a well-formed reference
    /// nor the `$$` doubling (SPEC.md § Errors).
    UndefinedReference,
    /// A container-variable reference appears where its expansion
    /// cannot be placed: `$/.name` outside the two splat positions,
    /// or `$@.name` in a scalar position (SPEC.md
    /// § Namespace-Variable Splat).
    VariableContext,
    /// A compound-form value collides with that form's delimiter:
    /// `|` in a `:=`/`+:=` pair value, `;` in `;=` data, `:` or `;`
    /// in an inline map entry (SPEC.md § Errors).
    DelimiterCollision,
    SchemaDuplicateKey,
    /// A `.!schema` inheritance chain among `.saiv` files revisits a
    /// schema already in the chain.
    SchemaInheritanceCycle,
    /// An optional field whose resolved default is inapplicable and
    /// whose type does not admit `!null` — the Denormalizer would
    /// have nothing to materialize for an absent instance (SPEC.md
    /// § Default Values).
    SchemaOptionalWithoutDefault,
    SchemaResolution,
    RequiredFieldSchema,
    DuplicateKeySchema,
    UndefinedFieldStrictSchema,
    ProvenanceSchema,
    TypeMismatch,
    ConstraintViolation,
    UniquenessViolation,
    ReferentialIntegrity,
    CardinalityViolation,
    CollationUnsupported,
}

impl AppError {
    pub fn name(self) -> &'static str {
        match self {
            AppError::MetadataWithoutTarget => "MetadataWithoutTargetError",
            AppError::UndefinedReference => "UndefinedReferenceError",
            AppError::VariableContext => "VariableContextError",
            AppError::DelimiterCollision => "DelimiterCollisionError",
            AppError::SchemaDuplicateKey => "SchemaDuplicateKeyError",
            AppError::SchemaInheritanceCycle => "SchemaInheritanceCycleError",
            AppError::SchemaOptionalWithoutDefault => "SchemaOptionalWithoutDefaultError",
            AppError::SchemaResolution => "SchemaResolutionError",
            AppError::RequiredFieldSchema => "RequiredFieldSchemaError",
            AppError::DuplicateKeySchema => "DuplicateKeySchemaError",
            AppError::UndefinedFieldStrictSchema => "UndefinedFieldStrictSchemaError",
            AppError::ProvenanceSchema => "ProvenanceSchemaError",
            AppError::TypeMismatch => "TypeMismatchError",
            AppError::ConstraintViolation => "ConstraintViolationError",
            AppError::UniquenessViolation => "UniquenessViolationError",
            AppError::ReferentialIntegrity => "ReferentialIntegrityError",
            AppError::CardinalityViolation => "CardinalityViolationError",
            AppError::CollationUnsupported => "CollationUnsupportedError",
        }
    }
}

impl fmt::Display for AppError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.write_str(self.name())
    }
}

/// Any failure along the build pipeline.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum PipelineError {
    Lex(LexErrorAt),
    App(AppError),
    /// Compiler-internal malformation with context (a condition the
    /// spec assigns to no named error).
    Other(String),
}

impl fmt::Display for PipelineError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            PipelineError::Lex(e) => write!(f, "{} (line {})", e.error, e.line),
            PipelineError::App(e) => e.fmt(f),
            PipelineError::Other(s) => f.write_str(s),
        }
    }
}