Skip to main content

qubit_metadata/
metadata_validation_error.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//! [`MetadataValidationError`] — aggregate schema validation failures.
9
10use std::fmt;
11
12use crate::metadata_error::MetadataError;
13
14/// Aggregate error returned by schema-level validation APIs.
15///
16/// Unlike single-entry metadata accessors, schema validation can discover
17/// multiple independent issues in one pass. This type preserves all collected
18/// [`MetadataError`] values so callers can report or fix them together.
19///
20/// # Examples
21///
22/// ```
23/// use qubit_metadata::{MetadataError, MetadataValidationError};
24///
25/// let error = MetadataValidationError::from_issue(MetadataError::MissingKey("tenant".into()));
26/// assert_eq!(error.len(), 1);
27/// ```
28#[derive(Debug, Clone, PartialEq, Eq)]
29#[must_use]
30pub struct MetadataValidationError {
31    /// Collected validation issues.
32    issues: Vec<MetadataError>,
33}
34
35impl MetadataValidationError {
36    /// Creates an aggregate validation error from one issue.
37    ///
38    /// # Parameters
39    ///
40    /// * `issue` - Validation issue to store.
41    ///
42    /// # Returns
43    ///
44    /// An aggregate error containing exactly one issue.
45    #[inline]
46    #[must_use = "the validation error should be inspected"]
47    pub fn from_issue(issue: MetadataError) -> Self {
48        Self { issues: vec![issue] }
49    }
50
51    /// Creates an aggregate validation error from a non-empty issue list.
52    ///
53    /// Returns `None` when `issues` is empty because an empty validation error
54    /// has no actionable meaning.
55    ///
56    /// # Parameters
57    ///
58    /// * `issues` - Validation issues to aggregate.
59    ///
60    /// # Returns
61    ///
62    /// `Some` aggregate error for a non-empty list; otherwise, `None`.
63    #[inline]
64    pub fn from_issues(issues: Vec<MetadataError>) -> Option<Self> {
65        (!issues.is_empty()).then_some(Self { issues })
66    }
67
68    /// Returns the collected validation issues.
69    ///
70    /// # Returns
71    ///
72    /// The issues in discovery order.
73    #[inline]
74    #[must_use = "the validation issues should be inspected"]
75    pub fn issues(&self) -> &[MetadataError] {
76        &self.issues
77    }
78
79    /// Returns the number of collected validation issues.
80    ///
81    /// # Returns
82    ///
83    /// The issue count.
84    #[inline]
85    #[must_use]
86    #[allow(clippy::len_without_is_empty)]
87    pub fn len(&self) -> usize {
88        self.issues.len()
89    }
90
91    /// Converts this aggregate error into its collected issues.
92    ///
93    /// # Returns
94    ///
95    /// The owned issues in discovery order.
96    #[inline]
97    #[must_use]
98    pub fn into_issues(self) -> Vec<MetadataError> {
99        self.issues
100    }
101}
102
103impl fmt::Display for MetadataValidationError {
104    /// Formats the validation summary and every collected issue.
105    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
106        write!(formatter, "{} metadata validation issue(s)", self.issues.len())?;
107        for (index, issue) in self.issues.iter().enumerate() {
108            write!(formatter, "; {}: {issue}", index + 1)?;
109        }
110        Ok(())
111    }
112}
113
114impl std::error::Error for MetadataValidationError {}