Skip to main content

qubit_validator/binding/
violation_draft.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//! Rule-local violations before a bound validator assigns its identity.
10
11use std::collections::BTreeMap;
12use std::fmt::Debug;
13use std::fmt::Formatter;
14use std::fmt::Result as FmtResult;
15
16use crate::ValidationPath;
17use crate::ViolationCode;
18use crate::ViolationParam;
19
20/// A structured violation draft without a rule identity.
21///
22/// # Examples
23///
24/// ```
25/// use qubit_validator::{ViolationCode, ViolationDraft, ViolationParam};
26///
27/// let draft = ViolationDraft::new(ViolationCode::new("text.too_short"))
28///     .with_param("minimum", ViolationParam::Unsigned(3));
29/// assert!(format!("{draft:?}").contains("text.too_short"));
30/// assert!(!format!("{draft:?}").contains("secret input"));
31/// ```
32#[must_use]
33#[derive(Eq, PartialEq)]
34pub struct ViolationDraft {
35    /// Stable, program-declared violation code.
36    code: ViolationCode,
37    /// Structured location of the violation.
38    path: ValidationPath,
39    /// Structured parameters keyed by program-declared names.
40    params: BTreeMap<&'static str, ViolationParam>,
41}
42impl ViolationDraft {
43    /// Creates a draft at the root path using a stable violation code.
44    ///
45    /// # Parameters
46    /// - `code`: Program-declared code that identifies the validation failure.
47    ///
48    /// # Returns
49    /// A draft with no parameters and an empty root path.
50    #[inline]
51    pub fn new(code: ViolationCode) -> Self {
52        Self {
53            code,
54            path: ValidationPath::root(),
55            params: BTreeMap::new(),
56        }
57    }
58    /// Replaces the relative path attached to this violation draft.
59    ///
60    /// # Parameters
61    /// - `path`: Structured location relative to the current validation target.
62    ///
63    /// # Returns
64    /// The draft with `path` stored for later report prefixing.
65    #[inline]
66    pub fn with_path(mut self, path: ValidationPath) -> Self {
67        self.path = path;
68        self
69    }
70    /// Adds or replaces a structured parameter by its declared name.
71    ///
72    /// The type restricts representation but cannot prove provenance. Supply
73    /// only program-declared tokens or rule configuration values, never
74    /// rejected input or sensitive values derived from it.
75    ///
76    /// # Parameters
77    /// - `name`: Static name declared by the application for this parameter.
78    /// - `value`: Structured value to associate with `name`, subject to the
79    ///   caller's provenance and redaction responsibility.
80    ///
81    /// # Returns
82    /// The draft with the parameter inserted; an existing value with the same
83    /// name is replaced.
84    pub fn with_param(mut self, name: &'static str, value: ViolationParam) -> Self {
85        self.params.insert(name, value);
86        self
87    }
88    /// Splits the draft into safe components for final violation construction.
89    ///
90    /// The returned values are consumed by the binding layer, which supplies
91    /// the stable rule identity before exposing a [`crate::Violation`].
92    ///
93    /// # Returns
94    ///
95    /// The violation code, relative path, and structured parameters, in that
96    /// order.
97    #[inline]
98    pub(crate) fn parts(self) -> (ViolationCode, ValidationPath, BTreeMap<&'static str, ViolationParam>) {
99        (self.code, self.path, self.params)
100    }
101}
102
103impl Debug for ViolationDraft {
104    /// Formats only safe metadata and the parameter count.
105    ///
106    /// # Parameters
107    /// - `formatter`: Destination that receives the debug representation.
108    ///
109    /// # Returns
110    /// The result of writing the representation to `formatter`.
111    ///
112    /// # Errors
113    /// Returns the formatter's error if writing the representation fails.
114    fn fmt(&self, formatter: &mut Formatter<'_>) -> FmtResult {
115        formatter
116            .debug_struct("ViolationDraft")
117            .field("code", &self.code)
118            .field("path", &self.path)
119            .field("parameter_count", &self.params.len())
120            .finish()
121    }
122}