Skip to main content

qubit_metadata/
metadata_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//! [`MetadataError`] — failures from explicit metadata APIs and schema checks.
9
10use std::fmt;
11
12use qubit_datatype::DataType;
13use qubit_value::ValueError;
14
15#[cfg(feature = "filter")]
16use crate::FilterLimitKind;
17use crate::MetadataWireLimitKind;
18
19/// Errors produced by explicit metadata accessors and schema validation.
20///
21/// # Examples
22///
23/// ```
24/// use qubit_metadata::MetadataError;
25///
26/// let error = MetadataError::MissingKey("tenant".to_owned());
27/// assert!(error.to_string().contains("tenant"));
28/// ```
29#[derive(Debug, Clone, PartialEq, Eq)]
30#[non_exhaustive]
31#[must_use]
32pub enum MetadataError {
33    /// A strict read or explicit conversion failed without losing its source.
34    ValueAccess {
35        /// Metadata key being read.
36        key: String,
37        /// Original structured value error.
38        source: Box<ValueError>,
39    },
40    /// The requested key does not exist.
41    MissingKey(
42        /// Missing metadata key.
43        String,
44    ),
45    /// Schema validation found a stored value of the wrong type.
46    TypeMismatch {
47        /// Metadata key being read or validated.
48        key: String,
49        /// Expected data type.
50        expected: DataType,
51        /// Actual stored data type.
52        actual: DataType,
53        /// Human-readable conversion or validation message.
54        message: String,
55    },
56    /// A metadata or schema value exceeds a strict V1 wire limit.
57    WireLimitExceeded {
58        /// Wire resource category that exceeded its limit.
59        kind: MetadataWireLimitKind,
60        /// Observed resource value.
61        value: usize,
62        /// Largest value accepted by the V1 wire contract.
63        maximum: usize,
64    },
65    /// A required schema field is missing from a metadata object.
66    #[cfg(feature = "schema")]
67    MissingRequiredField {
68        /// Required metadata key.
69        key: String,
70        /// Expected data type for the missing field.
71        expected: DataType,
72    },
73    /// A metadata object contains a key not accepted by the schema.
74    #[cfg(feature = "schema")]
75    UnknownField {
76        /// Unknown metadata key.
77        key: String,
78    },
79    /// A filter references a key that is not defined by the schema.
80    #[cfg(feature = "schema")]
81    UnknownFilterField {
82        /// Unknown filter key.
83        key: String,
84    },
85    /// A filter uses an operator that is not compatible with the field type.
86    #[cfg(feature = "schema")]
87    InvalidFilterOperator {
88        /// Metadata key being filtered.
89        key: String,
90        /// Filter operator name.
91        operator: &'static str,
92        /// Field data type defined by the schema.
93        data_type: DataType,
94        /// Human-readable validation message.
95        message: String,
96    },
97    /// A filter expression is structurally invalid.
98    #[cfg(feature = "filter")]
99    InvalidFilterExpression {
100        /// Human-readable validation message.
101        message: String,
102    },
103    /// A filter condition uses an operand with no stable matching semantics.
104    #[cfg(feature = "filter")]
105    InvalidFilterOperand {
106        /// Stable filter operator name.
107        operator: &'static str,
108        /// Data type declared by the rejected operand.
109        data_type: DataType,
110        /// Human-readable rejection reason.
111        message: String,
112    },
113    /// A metadata-filter builder was finalized without an expression.
114    #[cfg(feature = "filter")]
115    MissingFilterExpression,
116    /// A configured filter resource bound is outside the allowed range.
117    #[cfg(feature = "filter")]
118    InvalidFilterLimit {
119        /// Resource category being configured.
120        kind: FilterLimitKind,
121        /// Requested resource bound.
122        value: usize,
123        /// Library hard maximum for the resource.
124        maximum: usize,
125    },
126    /// A filter exceeds an enforced resource bound.
127    #[cfg(feature = "filter")]
128    FilterLimitExceeded {
129        /// Resource category that exceeded its limit.
130        kind: FilterLimitKind,
131        /// Observed resource value.
132        value: usize,
133        /// Largest allowed value for the resource.
134        maximum: usize,
135    },
136    /// A schema builder declares the same field more than once.
137    #[cfg(feature = "schema")]
138    DuplicateSchemaField {
139        /// Duplicated schema key.
140        key: String,
141    },
142}
143
144impl MetadataError {
145    /// Builds a schema type-mismatch error for `key`.
146    ///
147    /// # Parameters
148    ///
149    /// * `key` - Metadata key being validated.
150    /// * `expected` - Schema-declared data type.
151    /// * `actual` - Actual value data type.
152    ///
153    /// # Returns
154    ///
155    /// A structured [`MetadataError::TypeMismatch`] error.
156    #[cfg(feature = "schema")]
157    #[inline]
158    pub(crate) fn type_mismatch(key: &str, expected: DataType, actual: DataType) -> Self {
159        Self::TypeMismatch {
160            key: key.to_string(),
161            expected,
162            actual,
163            message: format!("expected {expected}, got {actual}"),
164        }
165    }
166}
167
168impl fmt::Display for MetadataError {
169    /// Formats this metadata operation error for display.
170    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
171        match self {
172            Self::ValueAccess { key, source } => {
173                write!(formatter, "Metadata key '{key}': {source}")
174            }
175            Self::MissingKey(key) => {
176                write!(formatter, "Metadata key not found: {key}")
177            }
178            Self::TypeMismatch {
179                key,
180                expected,
181                actual,
182                message,
183            } => write!(
184                formatter,
185                "Metadata key '{key}' expected {expected} but actual {actual}: {message}"
186            ),
187            Self::WireLimitExceeded { kind, value, maximum } => write!(
188                formatter,
189                "Metadata wire {kind:?} value {value} exceeds the maximum of {maximum}"
190            ),
191            #[cfg(feature = "schema")]
192            Self::MissingRequiredField { key, expected } => {
193                write!(
194                    formatter,
195                    "Required metadata key '{key}' is missing (expected {expected})"
196                )
197            }
198            #[cfg(feature = "schema")]
199            Self::UnknownField { key } => {
200                write!(formatter, "Metadata key '{key}' is not defined in schema")
201            }
202            #[cfg(feature = "schema")]
203            Self::UnknownFilterField { key } => {
204                write!(
205                    formatter,
206                    "Metadata filter references key '{key}' not defined in schema"
207                )
208            }
209            #[cfg(feature = "schema")]
210            Self::InvalidFilterOperator {
211                key,
212                operator,
213                data_type,
214                message,
215            } => write!(
216                formatter,
217                "Metadata filter operator '{operator}' is invalid for key '{key}' with type {data_type}: {message}"
218            ),
219            #[cfg(feature = "filter")]
220            Self::InvalidFilterExpression { message } => {
221                write!(formatter, "Metadata filter expression is invalid: {message}")
222            }
223            #[cfg(feature = "filter")]
224            Self::InvalidFilterOperand {
225                operator,
226                data_type,
227                message,
228            } => write!(
229                formatter,
230                "Metadata filter operator '{operator}' cannot use {data_type}: {message}"
231            ),
232            #[cfg(feature = "filter")]
233            Self::MissingFilterExpression => {
234                write!(formatter, "Metadata filter requires an expression")
235            }
236            #[cfg(feature = "filter")]
237            Self::InvalidFilterLimit { kind, value, maximum } => write!(
238                formatter,
239                "Metadata filter {kind:?} limit {value} is outside 1..={maximum}"
240            ),
241            #[cfg(feature = "filter")]
242            Self::FilterLimitExceeded { kind, value, maximum } => write!(
243                formatter,
244                "Metadata filter {kind:?} value {value} exceeds the maximum of {maximum}"
245            ),
246            #[cfg(feature = "schema")]
247            Self::DuplicateSchemaField { key } => {
248                write!(formatter, "Metadata schema declares field '{key}' more than once")
249            }
250        }
251    }
252}
253
254impl std::error::Error for MetadataError {
255    /// Returns the original value error for strict and explicit conversion
256    /// reads.
257    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
258        match self {
259            Self::ValueAccess { source, .. } => Some(source.as_ref()),
260            _ => None,
261        }
262    }
263}