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}