qubit_validator/report/failure_id.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//! Opaque identities for failures retained by one validation report.
10
11use std::sync::atomic::AtomicU64;
12use std::sync::atomic::Ordering;
13
14/// Identifies one retained failure within its originating report.
15///
16/// IDs are issued by [`super::ValidationReport::record_outcome`]. A caller
17/// cannot construct an ID, and a report rejects IDs issued by another report.
18///
19/// # Examples
20///
21/// ```
22/// use qubit_validator::FailureId;
23/// use qubit_validator::ValidationOutcome;
24/// use qubit_validator::ValidationPath;
25/// use qubit_validator::ValidationReport;
26/// use qubit_validator::ValidatorId;
27/// use qubit_validator::Violation;
28/// use qubit_validator::ViolationCode;
29///
30/// let mut report = ValidationReport::new();
31/// let outcome = report.record_outcome(
32/// 0,
33/// ValidationPath::root(),
34/// ValidationOutcome::invalid(vec![Violation::new(
35/// ValidatorId::new("text.required"),
36/// ViolationCode::new("text.blank"),
37/// )])?,
38/// )?;
39/// let failure: FailureId = outcome.failure_ids()[0];
40/// assert!(report.failure(failure).is_some());
41/// # Ok::<(), Box<dyn std::error::Error>>(())
42/// ```
43#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
44pub struct FailureId {
45 /// Process-local identity of the report that issued this failure ID.
46 pub(crate) report: u64,
47 /// Zero-based position of the retained violation in that report.
48 pub(crate) index: usize,
49}
50
51impl FailureId {
52 /// Creates an ID for a retained failure at `index` in `report`.
53 ///
54 /// The caller must ensure that the report identity and index refer to an
55 /// entry already retained by the originating report.
56 ///
57 /// # Parameters
58 ///
59 /// - `report`: process-local identity of the report that retained the
60 /// failure.
61 /// - `index`: zero-based position of the failure in that report.
62 #[must_use]
63 #[inline]
64 pub(crate) const fn new(report: u64, index: usize) -> Self {
65 Self { report, index }
66 }
67}
68
69/// Allocates a unique process-local identity for a new report.
70///
71/// # Panics
72///
73/// Panics when assigning the next identity would overflow `u64`, after
74/// `u64::MAX - 1` report identities have been issued.
75#[must_use]
76pub(crate) fn next_report_id() -> u64 {
77 static NEXT_REPORT_ID: AtomicU64 = AtomicU64::new(1);
78 NEXT_REPORT_ID
79 .fetch_update(Ordering::Relaxed, Ordering::Relaxed, |current| current.checked_add(1))
80 .expect("validation report identity space exhausted")
81}