qubit_validator/binding/prepared_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//! Type-erased results produced by prepared validators.
10
11use super::ViolationDraft;
12use crate::ValidationOutcome;
13use crate::ValidationOutcomeError;
14use crate::ValidatorId;
15use crate::Violation;
16/// The type-erased result produced before rule identity is attached.
17///
18/// # Examples
19///
20/// ```
21/// use qubit_validator::{PreparedOutcome, ViolationCode, ViolationDraft};
22///
23/// let accepted = PreparedOutcome::valid();
24/// let rejected = PreparedOutcome::invalid(vec![
25/// ViolationDraft::new(ViolationCode::new("text.blank")),
26/// ])?;
27/// assert!(matches!(accepted, PreparedOutcome::Valid));
28/// assert!(matches!(rejected, PreparedOutcome::Invalid(drafts) if drafts.len() == 1));
29/// # Ok::<(), qubit_validator::ValidationOutcomeError>(())
30/// ```
31///
32/// The caller's orchestration policy records missing inputs and failed
33/// prerequisites as skipped [`ValidationOutcome`] values.
34#[must_use]
35#[derive(Debug, PartialEq, Eq)]
36#[non_exhaustive]
37pub enum PreparedOutcome {
38 /// Input is valid.
39 Valid,
40 /// Input is invalid with draft violations.
41 Invalid(
42 /// One or more safe draft violations produced by the validator.
43 Vec<ViolationDraft>,
44 ),
45}
46
47impl PreparedOutcome {
48 /// Creates a successful prepared outcome.
49 ///
50 /// # Returns
51 ///
52 /// A prepared outcome representing valid input.
53 #[inline]
54 pub const fn valid() -> Self {
55 Self::Valid
56 }
57
58 /// Creates an invalid prepared outcome with one or more violation drafts.
59 ///
60 /// # Parameters
61 ///
62 /// - `violations`: Draft violations to include in the outcome.
63 ///
64 /// # Returns
65 ///
66 /// The invalid prepared outcome containing `violations`.
67 ///
68 /// # Errors
69 ///
70 /// Returns [`ValidationOutcomeError::EmptyViolations`] when `violations` is
71 /// empty.
72 pub fn invalid(violations: Vec<ViolationDraft>) -> Result<Self, ValidationOutcomeError> {
73 if violations.is_empty() {
74 return Err(ValidationOutcomeError::EmptyViolations);
75 }
76 Ok(Self::Invalid(violations))
77 }
78
79 /// Attaches a bound rule identity to draft violations.
80 ///
81 /// # Parameters
82 ///
83 /// - `rule_id`: Identity of the rule that produced these violations.
84 ///
85 /// # Returns
86 ///
87 /// The corresponding bound validation outcome.
88 ///
89 /// # Errors
90 ///
91 /// Returns [`ValidationOutcomeError::EmptyViolations`] when an invalid
92 /// result has no drafts.
93 pub fn into_bound(self, rule_id: ValidatorId) -> Result<ValidationOutcome, ValidationOutcomeError> {
94 match self {
95 Self::Valid => Ok(ValidationOutcome::Valid),
96 Self::Invalid(drafts) => ValidationOutcome::invalid(
97 drafts
98 .into_iter()
99 .map(|draft| Violation::from_draft(rule_id, draft))
100 .collect(),
101 ),
102 }
103 }
104}