qubit_value/value_wire/value_wire_decode_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
9//! Error reported while decoding bounded JSON wire input.
10
11use std::io;
12use std::sync::Arc;
13
14use qubit_budget::BudgetError;
15use qubit_budget::MeasuredBudgetError;
16use qubit_budget::QuantityConversionError;
17use qubit_budget::json::JsonResource;
18use qubit_json::decode::JsonDecodeError;
19use qubit_json::decode::JsonDecodeErrorSource;
20use qubit_json::decode::JsonSyntaxError;
21use serde_json::Error as JsonError;
22use serde_json::error::Category;
23use thiserror::Error;
24
25use super::value_wire_json_error::ValueWireJsonError;
26
27/// Error produced by a bounded [`crate::ValueWireV1`] JSON decoder.
28///
29/// # Examples
30///
31/// ```
32/// use qubit_value::{ValueWireDecodeError, ValueWireV1};
33///
34/// let error = ValueWireV1::decode_json_slice(b"{").unwrap_err();
35/// assert!(matches!(
36/// error,
37/// ValueWireDecodeError::Syntax(_) | ValueWireDecodeError::InvalidJson(_)
38/// ));
39/// ```
40#[non_exhaustive]
41#[must_use]
42#[derive(Debug, Error)]
43pub enum ValueWireDecodeError {
44 /// Reading the JSON wire input failed.
45 #[error("failed to read V1 JSON wire input: {kind:?}")]
46 Read {
47 /// Stable standard I/O failure category.
48 kind: io::ErrorKind,
49 /// Reference-counted original error retained under detailed diagnostics
50 /// (`Some`), or absent under privacy-safe diagnostics (`None`).
51 #[source]
52 source: Option<Arc<io::Error>>,
53 },
54
55 /// The JSON document exceeded one configured resource budget.
56 #[error("V1 JSON wire resource budget exceeded: {0}")]
57 Budget(
58 /// Budget violation reported by the bounded JSON decoder.
59 #[source]
60 BudgetError<JsonResource, usize>,
61 ),
62
63 /// A native JSON measurement could not be represented by the budget
64 /// quantity type.
65 #[error("V1 JSON wire resource quantity conversion failed for {resource:?}: {source}")]
66 Quantity {
67 /// Resource whose measurement failed.
68 resource: JsonResource,
69 /// Native measurement conversion failure.
70 #[source]
71 source: QuantityConversionError,
72 },
73
74 /// The bounded input contains JSON syntax errors with source location.
75 #[error("invalid V1 JSON wire syntax: {0}")]
76 Syntax(
77 /// Syntax error with its source location preserved.
78 #[source]
79 JsonSyntaxError,
80 ),
81
82 /// The envelope declares a wire version that this decoder does not support.
83 #[error("unsupported qubit-value wire version {actual}; expected {expected}")]
84 UnsupportedVersion {
85 /// Wire version accepted by this decoder.
86 expected: u8,
87
88 /// Wire version declared by the input envelope.
89 actual: u8,
90 },
91
92 /// The bounded input is not a valid V1 JSON wire value.
93 #[error("failed to decode V1 JSON wire input: {0}")]
94 InvalidJson(
95 /// Safe Serde JSON metadata without the original detailed error.
96 #[source]
97 ValueWireJsonError,
98 ),
99}
100
101impl ValueWireDecodeError {
102 /// Constructs privacy-safe Serde metadata from structured decode facts.
103 ///
104 /// # Parameters
105 ///
106 /// * `category` - Serde JSON failure category.
107 /// * `line` - One-based input line, or zero when unavailable.
108 /// * `column` - One-based input column, or zero when unavailable.
109 ///
110 /// # Returns
111 ///
112 /// An invalid-JSON error that retains diagnostics without input contents.
113 #[inline]
114 fn deserialize(category: Category, line: usize, column: usize) -> Self {
115 Self::InvalidJson(ValueWireJsonError::new(category, line, column))
116 }
117}
118
119impl From<JsonDecodeError<JsonResource, usize>> for ValueWireDecodeError {
120 /// Converts bounded decoder failures into the corresponding wire error.
121 ///
122 /// Decoder resource violations retain their structured details, syntax
123 /// failures retain their source location, and other malformed inputs are
124 /// represented by a privacy-safe deserialization error.
125 ///
126 /// # Parameters
127 ///
128 /// * `error` - Failure returned by the bounded JSON decoder.
129 ///
130 /// # Returns
131 ///
132 /// The corresponding value-wire decoding error.
133 #[inline]
134 fn from(error: JsonDecodeError<JsonResource>) -> Self {
135 let line = error.line().unwrap_or(0);
136 let column = error.column().unwrap_or(0);
137 match error.into_source() {
138 JsonDecodeErrorSource::Read { kind, source, .. } => Self::Read { kind, source },
139 JsonDecodeErrorSource::Budget { source, .. } => match source {
140 MeasuredBudgetError::Budget(error) => Self::Budget(error),
141 MeasuredBudgetError::Quantity { resource, source } => {
142 Self::Quantity { resource, source }
143 }
144 },
145 JsonDecodeErrorSource::InvalidJson { syntax, .. } => Self::Syntax(syntax),
146 JsonDecodeErrorSource::EmptyInput { .. }
147 | JsonDecodeErrorSource::InvalidUtf8 { .. }
148 | JsonDecodeErrorSource::UnexpectedTopLevel { .. }
149 | JsonDecodeErrorSource::Deserialize { .. } => {
150 Self::deserialize(Category::Data, line, column)
151 }
152 }
153 }
154}
155
156impl From<JsonError> for ValueWireDecodeError {
157 /// Converts a Serde JSON error while preserving its category and location.
158 ///
159 /// The formatted diagnostic does not retain the input contents.
160 ///
161 /// # Parameters
162 ///
163 /// * `error` - Serde JSON decoding failure.
164 ///
165 /// # Returns
166 ///
167 /// An invalid-JSON wire error with a privacy-safe diagnostic.
168 #[inline]
169 fn from(error: JsonError) -> Self {
170 let category = error.classify();
171 let line = error.line();
172 let column = error.column();
173 Self::deserialize(category, line, column)
174 }
175}