Skip to main content

qubit_validator/binding/
validation_outcome.rs

1// =============================================================================
2//    Copyright (c) 2025 - 2026 Haixing Hu.
3//
4//    SPDX-License-Identifier: Apache-2.0
5//
6//    Licensed under the Apache License, Version 2.0.
7// =============================================================================
8
9//! Public outcomes produced by bound validators.
10
11use crate::FailureId;
12use crate::SkipReason;
13use crate::ValidationOutcomeError;
14use crate::Violation;
15
16/// The observable result of executing one bound validator occurrence.
17///
18/// # Examples
19///
20/// ```
21/// use qubit_validator::{ValidationOutcome, ValidatorId, Violation, ViolationCode};
22///
23/// let accepted = ValidationOutcome::valid();
24/// let rejected = ValidationOutcome::invalid(vec![Violation::new(
25///     ValidatorId::new("text.required"),
26///     ViolationCode::new("text.blank"),
27/// )])?;
28/// assert!(matches!(accepted, ValidationOutcome::Valid));
29/// assert!(matches!(rejected, ValidationOutcome::Invalid(violations) if violations.len() == 1));
30/// # Ok::<(), qubit_validator::ValidationOutcomeError>(())
31/// ```
32#[must_use]
33#[derive(Debug, PartialEq, Eq)]
34#[non_exhaustive]
35pub enum ValidationOutcome {
36    /// Input is valid.
37    Valid,
38    /// Input is invalid.
39    Invalid(
40        /// One or more safe violations carrying no raw rejected value.
41        Vec<Violation>,
42    ),
43    /// Execution was skipped by the caller's orchestration policy.
44    Skipped {
45        /// Reason for skipping.
46        reason: SkipReason,
47        /// IDs of failures already retained in the same report.
48        prerequisites: Vec<FailureId>,
49    },
50}
51
52impl ValidationOutcome {
53    /// Creates a successful validation outcome.
54    ///
55    /// # Returns
56    ///
57    /// A valid outcome with no violations or prerequisites.
58    #[inline]
59    pub const fn valid() -> Self {
60        Self::Valid
61    }
62
63    /// Creates an invalid outcome with one or more violations.
64    ///
65    /// # Parameters
66    ///
67    /// - `violations`: Non-empty safe violations produced by the validator.
68    ///
69    /// # Returns
70    ///
71    /// An invalid outcome containing `violations`.
72    ///
73    /// # Errors
74    ///
75    /// Returns [`ValidationOutcomeError::EmptyViolations`] when `violations`
76    /// is empty.
77    pub fn invalid(violations: Vec<Violation>) -> Result<Self, ValidationOutcomeError> {
78        if violations.is_empty() {
79            return Err(ValidationOutcomeError::EmptyViolations);
80        }
81        Ok(Self::Invalid(violations))
82    }
83
84    /// Creates an outcome skipped because the optional target is absent.
85    ///
86    /// # Returns
87    ///
88    /// A skipped outcome with reason `MissingOptional` and no prerequisites.
89    #[inline]
90    pub fn missing_optional() -> Self {
91        Self::Skipped {
92            reason: SkipReason::MissingOptional,
93            prerequisites: Vec::new(),
94        }
95    }
96
97    /// Creates an outcome skipped after at least one prerequisite failed.
98    ///
99    /// Every ID must refer to a failure already retained in the report that
100    /// records this outcome.
101    ///
102    /// # Parameters
103    ///
104    /// - `prerequisites`: Non-empty IDs of failures retained in the report.
105    ///
106    /// # Returns
107    ///
108    /// A skipped outcome with reason `FailedPrerequisite` and the supplied IDs.
109    ///
110    /// # Errors
111    ///
112    /// Returns [`ValidationOutcomeError::EmptyPrerequisites`] when
113    /// `prerequisites` is empty.
114    pub fn failed_prerequisite(prerequisites: Vec<FailureId>) -> Result<Self, ValidationOutcomeError> {
115        if prerequisites.is_empty() {
116            return Err(ValidationOutcomeError::EmptyPrerequisites);
117        }
118        Ok(Self::Skipped {
119            reason: SkipReason::FailedPrerequisite,
120            prerequisites,
121        })
122    }
123}