Skip to main content

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// qubit-style: allow multiple-public-types
11
12use qubit_budget::BudgetError;
13use qubit_budget::MeasuredBudgetError;
14use qubit_budget::QuantityConversionError;
15use qubit_budget::json::JsonResource;
16use qubit_json::decode::JsonDecodeError;
17use qubit_json::decode::JsonDecodeErrorSource;
18use qubit_json::decode::JsonSyntaxError;
19use serde_json::Error as JsonError;
20use serde_json::error::Category;
21use thiserror::Error;
22
23/// Error produced by a bounded [`crate::ValueWireV1`] JSON decoder.
24///
25/// # Examples
26///
27/// ```
28/// use qubit_value::{ValueWireDecodeError, ValueWireV1};
29///
30/// let error = ValueWireV1::decode_json_slice(b"{").unwrap_err();
31/// assert!(matches!(
32///     error,
33///     ValueWireDecodeError::Syntax(_) | ValueWireDecodeError::InvalidJson(_)
34/// ));
35/// ```
36#[non_exhaustive]
37#[must_use]
38#[derive(Debug, Error)]
39pub enum ValueWireDecodeError {
40    /// The JSON document exceeded one configured resource budget.
41    #[error("V1 JSON wire resource budget exceeded: {0}")]
42    Budget(
43        /// Budget violation reported by the bounded JSON decoder.
44        #[source]
45        BudgetError<JsonResource, usize>,
46    ),
47
48    /// A native JSON measurement could not be represented by the budget
49    /// quantity type.
50    #[error("V1 JSON wire resource quantity conversion failed for {resource:?}: {source}")]
51    Quantity {
52        /// Resource whose measurement failed.
53        resource: JsonResource,
54        /// Native measurement conversion failure.
55        #[source]
56        source: QuantityConversionError,
57    },
58
59    /// The bounded input contains JSON syntax errors with source location.
60    #[error("invalid V1 JSON wire syntax: {0}")]
61    Syntax(
62        /// Syntax error with its source location preserved.
63        #[source]
64        JsonSyntaxError,
65    ),
66
67    /// The envelope declares a wire version that this decoder does not support.
68    #[error("unsupported qubit-value wire version {actual}; expected {expected}")]
69    UnsupportedVersion {
70        /// Wire version accepted by this decoder.
71        expected: u8,
72
73        /// Wire version declared by the input envelope.
74        actual: u8,
75    },
76
77    /// The bounded input is not a valid V1 JSON wire value.
78    #[error("failed to decode V1 JSON wire input: {0}")]
79    InvalidJson(
80        /// Serde JSON error stripped of input contents but retaining location.
81        #[source]
82        JsonError,
83    ),
84}
85
86impl ValueWireDecodeError {
87    /// Constructs a privacy-safe Serde error from structured decode metadata.
88    ///
89    /// # Parameters
90    ///
91    /// * `category` - Serde JSON failure category.
92    /// * `line` - One-based input line, or zero when unavailable.
93    /// * `column` - One-based input column, or zero when unavailable.
94    ///
95    /// # Returns
96    ///
97    /// An invalid-JSON error that retains diagnostics without input contents.
98    fn deserialize(category: Category, line: usize, column: usize) -> Self {
99        let error = <JsonError as serde::de::Error>::custom(format_args!(
100            "JSON deserialization failed ({category:?}) at line {line}, column {column}"
101        ));
102        Self::InvalidJson(error)
103    }
104}
105
106impl From<JsonDecodeError<JsonResource, usize>> for ValueWireDecodeError {
107    #[inline]
108    fn from(error: JsonDecodeError<JsonResource>) -> Self {
109        let line = error.line().unwrap_or(0);
110        let column = error.column().unwrap_or(0);
111        match error.into_source() {
112            JsonDecodeErrorSource::Budget { source, .. } => match source {
113                MeasuredBudgetError::Budget(error) => Self::Budget(error),
114                MeasuredBudgetError::Quantity { resource, source } => Self::Quantity { resource, source },
115            },
116            JsonDecodeErrorSource::InvalidJson { syntax, .. } => Self::Syntax(syntax),
117            JsonDecodeErrorSource::EmptyInput { .. }
118            | JsonDecodeErrorSource::InvalidUtf8 { .. }
119            | JsonDecodeErrorSource::UnexpectedTopLevel { .. }
120            | JsonDecodeErrorSource::Deserialize { .. } => Self::deserialize(Category::Data, line, column),
121        }
122    }
123}
124
125impl From<JsonError> for ValueWireDecodeError {
126    #[inline]
127    fn from(error: JsonError) -> Self {
128        let category = error.classify();
129        let line = error.line();
130        let column = error.column();
131        Self::deserialize(category, line, column)
132    }
133}