Skip to main content

qubit_metadata/
metadata_wire_encode_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//! Errors from bounded JSON metadata wire encoding.
9
10use std::fmt;
11
12use qubit_budget::BudgetError;
13use qubit_budget::MeasuredBudgetError;
14use qubit_budget::QuantityConversionError;
15use qubit_budget::json::JsonResource;
16use qubit_json::decode::JsonSyntaxError;
17use qubit_json::encode::JsonEncodeError;
18use qubit_json::encode::JsonEncodeErrorSource;
19use qubit_json::encode::JsonSerializationError;
20
21use crate::MetadataError;
22
23/// Failure returned by bounded metadata JSON encoding APIs.
24///
25/// # Examples
26///
27/// ```
28/// use qubit_metadata::MetadataWireEncodeError;
29///
30/// let error = MetadataWireEncodeError::Io(std::io::Error::other("closed"));
31/// assert!(error.to_string().contains("closed"));
32/// ```
33#[derive(Debug)]
34#[non_exhaustive]
35#[must_use]
36pub enum MetadataWireEncodeError {
37    /// The JSON document exceeded one configured resource budget.
38    Budget(
39        /// Shared JSON resource budget that was exceeded.
40        BudgetError<JsonResource, usize>,
41    ),
42    /// A native JSON measurement could not be represented by the budget
43    /// quantity type.
44    Quantity {
45        /// Resource whose measurement failed to convert.
46        resource: JsonResource,
47        /// Native measurement conversion failure.
48        source: QuantityConversionError,
49    },
50    /// The metadata value exceeds the strict V1 metadata wire contract.
51    ///
52    /// The original domain error is preserved as the source.
53    Domain(
54        /// Structured metadata-domain failure.
55        MetadataError,
56    ),
57    /// The encoded value contains invalid JSON syntax.
58    Syntax(
59        /// Lexical JSON syntax failure.
60        JsonSyntaxError,
61    ),
62    /// Strict JSON serialization rejected the value during bounded encoding.
63    Json(
64        /// JSON serialization failure.
65        JsonSerializationError,
66    ),
67    /// The destination writer rejected bounded JSON output.
68    Io(
69        /// Destination writer error.
70        std::io::Error,
71    ),
72}
73
74impl fmt::Display for MetadataWireEncodeError {
75    /// Formats the encoding failure with its underlying error context.
76    ///
77    /// # Parameters
78    ///
79    /// * `formatter` - Formatter receiving the error message.
80    ///
81    /// # Returns
82    ///
83    /// A formatting result indicating whether the message was written.
84    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
85        match self {
86            Self::Budget(error) => {
87                write!(formatter, "JSON resource budget exceeded: {error}")
88            }
89            Self::Quantity { resource, source } => write!(
90                formatter,
91                "JSON resource quantity conversion failed for {resource:?}: {source}",
92            ),
93            Self::Domain(error) => {
94                write!(formatter, "metadata wire contract rejected: {error}")
95            }
96            Self::Syntax(error) => {
97                write!(formatter, "invalid JSON wire syntax: {error}")
98            }
99            Self::Json(error) => {
100                write!(formatter, "failed to encode JSON wire value: {error}")
101            }
102            Self::Io(error) => {
103                write!(formatter, "failed to write JSON wire value: {error}")
104            }
105        }
106    }
107}
108
109impl std::error::Error for MetadataWireEncodeError {
110    /// Returns the underlying budget, domain, syntax, JSON, or I/O error.
111    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
112        match self {
113            Self::Budget(error) => Some(error),
114            Self::Quantity { source, .. } => Some(source),
115            Self::Domain(error) => Some(error),
116            Self::Syntax(error) => Some(error),
117            Self::Json(error) => Some(error),
118            Self::Io(error) => Some(error),
119        }
120    }
121}
122
123impl From<JsonEncodeError<JsonResource>> for MetadataWireEncodeError {
124    /// Converts a shared budget adapter error into the metadata encoding
125    /// error.
126    ///
127    /// # Parameters
128    ///
129    /// * `error` - Shared JSON encoding failure to convert.
130    ///
131    /// # Returns
132    ///
133    /// The corresponding metadata wire encoding error.
134    fn from(error: JsonEncodeError<JsonResource>) -> Self {
135        match error.into_source() {
136            JsonEncodeErrorSource::Budget(source) => match source {
137                MeasuredBudgetError::Budget(error) => Self::Budget(error),
138                MeasuredBudgetError::Quantity { resource, source } => Self::Quantity { resource, source },
139            },
140            JsonEncodeErrorSource::InvalidRawJson(source) => Self::Syntax(source),
141            JsonEncodeErrorSource::Serialize(source) => Self::Json(source),
142            JsonEncodeErrorSource::Write(source) => Self::Io(source),
143        }
144    }
145}
146
147impl From<MeasuredBudgetError<JsonResource, usize>> for MetadataWireEncodeError {
148    /// Converts a measured JSON budget failure into the metadata encoding
149    /// error.
150    ///
151    /// # Parameters
152    ///
153    /// * `error` - Measured budget failure to convert.
154    ///
155    /// # Returns
156    ///
157    /// The corresponding metadata wire encoding error.
158    fn from(error: MeasuredBudgetError<JsonResource, usize>) -> Self {
159        match error {
160            MeasuredBudgetError::Budget(error) => Self::Budget(error),
161            MeasuredBudgetError::Quantity { resource, source } => Self::Quantity { resource, source },
162        }
163    }
164}