qubit-validator 0.1.1

Type-safe validation traits and registries for Qubit applications
Documentation
// =============================================================================
//    Copyright (c) 2025 - 2026 Haixing Hu.
//
//    SPDX-License-Identifier: Apache-2.0
//
//    Licensed under the Apache License, Version 2.0.
// =============================================================================

//! Public outcomes produced by bound validators.

use crate::FailureId;
use crate::SkipReason;
use crate::ValidationOutcomeError;
use crate::Violation;

/// The observable result of executing one bound validator occurrence.
///
/// # Examples
///
/// ```
/// use qubit_validator::{ValidationOutcome, ValidatorId, Violation, ViolationCode};
///
/// let accepted = ValidationOutcome::valid();
/// let rejected = ValidationOutcome::invalid(vec![Violation::new(
///     ValidatorId::new("text.required"),
///     ViolationCode::new("text.blank"),
/// )])?;
/// assert!(matches!(accepted, ValidationOutcome::Valid));
/// assert!(matches!(rejected, ValidationOutcome::Invalid(violations) if violations.len() == 1));
/// # Ok::<(), qubit_validator::ValidationOutcomeError>(())
/// ```
#[must_use]
#[derive(Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum ValidationOutcome {
    /// Input is valid.
    Valid,
    /// Input is invalid.
    Invalid(
        /// One or more safe violations carrying no raw rejected value.
        Vec<Violation>,
    ),
    /// Execution was skipped by the caller's orchestration policy.
    Skipped {
        /// Reason for skipping.
        reason: SkipReason,
        /// IDs of failures already retained in the same report.
        prerequisites: Vec<FailureId>,
    },
}

impl ValidationOutcome {
    /// Creates a successful validation outcome.
    ///
    /// # Returns
    ///
    /// A valid outcome with no violations or prerequisites.
    #[inline]
    pub const fn valid() -> Self {
        Self::Valid
    }

    /// Creates an invalid outcome with one or more violations.
    ///
    /// # Parameters
    ///
    /// - `violations`: Non-empty safe violations produced by the validator.
    ///
    /// # Returns
    ///
    /// An invalid outcome containing `violations`.
    ///
    /// # Errors
    ///
    /// Returns [`ValidationOutcomeError::EmptyViolations`] when `violations`
    /// is empty.
    pub fn invalid(violations: Vec<Violation>) -> Result<Self, ValidationOutcomeError> {
        if violations.is_empty() {
            return Err(ValidationOutcomeError::EmptyViolations);
        }
        Ok(Self::Invalid(violations))
    }

    /// Creates an outcome skipped because the optional target is absent.
    ///
    /// # Returns
    ///
    /// A skipped outcome with reason `MissingOptional` and no prerequisites.
    #[inline]
    pub fn missing_optional() -> Self {
        Self::Skipped {
            reason: SkipReason::MissingOptional,
            prerequisites: Vec::new(),
        }
    }

    /// Creates an outcome skipped after at least one prerequisite failed.
    ///
    /// Every ID must refer to a failure already retained in the report that
    /// records this outcome.
    ///
    /// # Parameters
    ///
    /// - `prerequisites`: Non-empty IDs of failures retained in the report.
    ///
    /// # Returns
    ///
    /// A skipped outcome with reason `FailedPrerequisite` and the supplied IDs.
    ///
    /// # Errors
    ///
    /// Returns [`ValidationOutcomeError::EmptyPrerequisites`] when
    /// `prerequisites` is empty.
    pub fn failed_prerequisite(prerequisites: Vec<FailureId>) -> Result<Self, ValidationOutcomeError> {
        if prerequisites.is_empty() {
            return Err(ValidationOutcomeError::EmptyPrerequisites);
        }
        Ok(Self::Skipped {
            reason: SkipReason::FailedPrerequisite,
            prerequisites,
        })
    }
}