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}