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 } => Self::Quantity { resource, source },
142 },
143 JsonDecodeErrorSource::InvalidJson { syntax, .. } => Self::Syntax(syntax),
144 JsonDecodeErrorSource::EmptyInput { .. }
145 | JsonDecodeErrorSource::InvalidUtf8 { .. }
146 | JsonDecodeErrorSource::UnexpectedTopLevel { .. }
147 | JsonDecodeErrorSource::Deserialize { .. } => Self::deserialize(Category::Data, line, column),
148 }
149 }
150}
151
152impl From<JsonError> for ValueWireDecodeError {
153 /// Converts a Serde JSON error while preserving its category and location.
154 ///
155 /// The formatted diagnostic does not retain the input contents.
156 ///
157 /// # Parameters
158 ///
159 /// * `error` - Serde JSON decoding failure.
160 ///
161 /// # Returns
162 ///
163 /// An invalid-JSON wire error with a privacy-safe diagnostic.
164 #[inline]
165 fn from(error: JsonError) -> Self {
166 let category = error.classify();
167 let line = error.line();
168 let column = error.column();
169 Self::deserialize(category, line, column)
170 }
171}