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 ///
171 /// # Parameters
172 ///
173 /// * `formatter` - Formatter receiving the error message.
174 ///
175 /// # Returns
176 ///
177 /// A formatting result indicating whether the message was written.
178 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
179 match self {
180 Self::ValueAccess { key, source } => {
181 write!(formatter, "Metadata key '{key}': {source}")
182 }
183 Self::MissingKey(key) => {
184 write!(formatter, "Metadata key not found: {key}")
185 }
186 Self::TypeMismatch {
187 key,
188 expected,
189 actual,
190 message,
191 } => write!(
192 formatter,
193 "Metadata key '{key}' expected {expected} but actual {actual}: {message}"
194 ),
195 Self::WireLimitExceeded { kind, value, maximum } => write!(
196 formatter,
197 "Metadata wire {kind:?} value {value} exceeds the maximum of {maximum}"
198 ),
199 #[cfg(feature = "schema")]
200 Self::MissingRequiredField { key, expected } => {
201 write!(
202 formatter,
203 "Required metadata key '{key}' is missing (expected {expected})"
204 )
205 }
206 #[cfg(feature = "schema")]
207 Self::UnknownField { key } => {
208 write!(formatter, "Metadata key '{key}' is not defined in schema")
209 }
210 #[cfg(feature = "schema")]
211 Self::UnknownFilterField { key } => {
212 write!(
213 formatter,
214 "Metadata filter references key '{key}' not defined in schema"
215 )
216 }
217 #[cfg(feature = "schema")]
218 Self::InvalidFilterOperator {
219 key,
220 operator,
221 data_type,
222 message,
223 } => write!(
224 formatter,
225 "Metadata filter operator '{operator}' is invalid for key '{key}' with type {data_type}: {message}"
226 ),
227 #[cfg(feature = "filter")]
228 Self::InvalidFilterExpression { message } => {
229 write!(formatter, "Metadata filter expression is invalid: {message}")
230 }
231 #[cfg(feature = "filter")]
232 Self::InvalidFilterOperand {
233 operator,
234 data_type,
235 message,
236 } => write!(
237 formatter,
238 "Metadata filter operator '{operator}' cannot use {data_type}: {message}"
239 ),
240 #[cfg(feature = "filter")]
241 Self::MissingFilterExpression => {
242 write!(formatter, "Metadata filter requires an expression")
243 }
244 #[cfg(feature = "filter")]
245 Self::InvalidFilterLimit { kind, value, maximum } => write!(
246 formatter,
247 "Metadata filter {kind:?} limit {value} is outside 1..={maximum}"
248 ),
249 #[cfg(feature = "filter")]
250 Self::FilterLimitExceeded { kind, value, maximum } => write!(
251 formatter,
252 "Metadata filter {kind:?} value {value} exceeds the maximum of {maximum}"
253 ),
254 #[cfg(feature = "schema")]
255 Self::DuplicateSchemaField { key } => {
256 write!(formatter, "Metadata schema declares field '{key}' more than once")
257 }
258 }
259 }
260}
261
262impl std::error::Error for MetadataError {
263 /// Returns the original value error for strict and explicit conversion
264 /// reads.
265 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
266 match self {
267 Self::ValueAccess { source, .. } => Some(source.as_ref()),
268 _ => None,
269 }
270 }
271}