qubit_value/value_wire/value_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
9//! Errors produced while constructing a V1 wire DTO.
10
11#[cfg(feature = "json")]
12use std::io::Error as IoError;
13
14#[cfg(feature = "json")]
15use qubit_budget::BudgetError;
16#[cfg(feature = "json")]
17use qubit_budget::MeasuredBudgetError;
18#[cfg(feature = "json")]
19use qubit_budget::QuantityConversionError;
20#[cfg(feature = "json")]
21use qubit_budget::json::JsonResource;
22use qubit_datatype::DataType;
23#[cfg(feature = "json")]
24use qubit_json::decode::JsonSyntaxError;
25#[cfg(feature = "json")]
26use qubit_json::encode::JsonEncodeError;
27#[cfg(feature = "json")]
28use qubit_json::encode::JsonEncodeErrorSource;
29#[cfg(feature = "json")]
30use qubit_json::encode::JsonSerializationError;
31use thiserror::Error;
32
33/// A runtime value cannot be represented by the JSON V1 wire contract.
34///
35/// # Examples
36///
37/// ```
38/// use qubit_value::Value;
39/// use qubit_value::ValueWireEncodeError;
40/// use qubit_value::ValueWireV1;
41///
42/// let error = ValueWireV1::try_from(Value::Float64(f64::NAN)).unwrap_err();
43/// assert!(matches!(error, ValueWireEncodeError::NonFiniteFloat { .. }));
44/// ```
45#[non_exhaustive]
46#[must_use]
47#[derive(Debug, Error)]
48pub enum ValueWireEncodeError {
49 /// A JSON number cannot be restored with the V1 number model without
50 /// changing its `serde_json::Number` identity.
51 #[cfg(feature = "json")]
52 #[cfg_attr(docsrs, doc(cfg(feature = "json")))]
53 #[error("V1 JSON wire cannot preserve this JSON number representation")]
54 JsonNumberNotRoundTrippable,
55 /// A JSON V1 float must be finite.
56 #[error("V1 JSON wire cannot represent a non-finite {data_type} value")]
57 NonFiniteFloat {
58 /// Runtime data type of the rejected value.
59 data_type: DataType,
60 },
61 /// A V1 decimal exponent must stay within the bounded wire range.
62 #[error(
63 "V1 JSON wire cannot represent decimal scale {scale}; maximum absolute scale is {maximum_absolute_scale}"
64 )]
65 BigDecimalScaleTooLarge {
66 /// Rejected decimal exponent.
67 scale: i64,
68 /// Inclusive exponent magnitude limit for V1.
69 maximum_absolute_scale: i64,
70 },
71 /// A UTC instant year must be within the four-digit V1 wire range.
72 #[error(
73 "V1 JSON wire cannot represent UTC instant year {year}; supported years are 0 through 9999"
74 )]
75 InstantYearOutOfRange {
76 /// UTC calendar year rejected by the V1 wire contract.
77 year: i32,
78 },
79 /// The JSON output exceeded one configured resource budget.
80 #[cfg(feature = "json")]
81 #[cfg_attr(docsrs, doc(cfg(feature = "json")))]
82 #[error("V1 JSON wire resource budget exceeded: {0}")]
83 Budget(
84 /// Budget violation reported by the bounded JSON encoder.
85 #[source]
86 BudgetError<JsonResource, usize>,
87 ),
88 /// A native JSON measurement could not be represented by the budget
89 /// quantity type.
90 #[cfg(feature = "json")]
91 #[cfg_attr(docsrs, doc(cfg(feature = "json")))]
92 #[error("V1 JSON wire resource quantity conversion failed for {resource:?}: {source}")]
93 Quantity {
94 /// Resource whose measurement failed.
95 resource: JsonResource,
96 /// Native measurement conversion failure.
97 #[source]
98 source: QuantityConversionError,
99 },
100 /// The encoded value contains invalid JSON syntax.
101 #[cfg(feature = "json")]
102 #[cfg_attr(docsrs, doc(cfg(feature = "json")))]
103 #[error("invalid V1 JSON wire syntax: {0}")]
104 Syntax(
105 /// Syntax error found in an embedded raw JSON payload.
106 #[source]
107 JsonSyntaxError,
108 ),
109 /// Strict JSON serialization rejected the value during bounded encoding.
110 #[cfg(feature = "json")]
111 #[cfg_attr(docsrs, doc(cfg(feature = "json")))]
112 #[error("failed to encode V1 JSON wire value: {0}")]
113 Json(
114 /// Stable, privacy-safe JSON serialization failure.
115 #[source]
116 JsonSerializationError,
117 ),
118 /// The destination writer rejected bounded JSON output.
119 #[cfg(feature = "json")]
120 #[cfg_attr(docsrs, doc(cfg(feature = "json")))]
121 #[error("failed to write V1 JSON wire value: {0}")]
122 Io(
123 /// Destination-writer failure.
124 #[source]
125 IoError,
126 ),
127}
128
129#[cfg(feature = "json")]
130impl From<JsonEncodeError<JsonResource, usize>> for ValueWireEncodeError {
131 /// Converts bounded JSON encoder failures into wire encoding errors.
132 ///
133 /// Resource-budget failures retain their budget details, invalid raw JSON
134 /// retains its syntax error, and serialization or writer failures retain
135 /// their corresponding source errors.
136 ///
137 /// # Parameters
138 ///
139 /// * `error` - Failure reported by the bounded JSON encoder.
140 ///
141 /// # Returns
142 ///
143 /// The wire encoding error corresponding to the encoder failure.
144 #[inline]
145 fn from(error: JsonEncodeError<JsonResource>) -> Self {
146 match error.into_source() {
147 JsonEncodeErrorSource::Budget(source) => Self::from(source),
148 JsonEncodeErrorSource::InvalidRawJson(source) => Self::Syntax(source),
149 JsonEncodeErrorSource::Serialize(source) => Self::Json(source),
150 JsonEncodeErrorSource::Write(source) => Self::Io(source),
151 }
152 }
153}
154
155#[cfg(feature = "json")]
156impl From<MeasuredBudgetError<JsonResource, usize>> for ValueWireEncodeError {
157 /// Converts a measured budget failure into its corresponding wire error.
158 ///
159 /// Budget violations retain their budget details, while quantity
160 /// conversion failures retain the affected resource and source error.
161 ///
162 /// # Parameters
163 ///
164 /// * `error` - Measured failure reported by the budget layer.
165 ///
166 /// # Returns
167 ///
168 /// The wire encoding error corresponding to the measured failure.
169 #[inline]
170 fn from(error: MeasuredBudgetError<JsonResource, usize>) -> Self {
171 match error {
172 MeasuredBudgetError::Budget(error) => Self::Budget(error),
173 MeasuredBudgetError::Quantity { resource, source } => {
174 Self::Quantity { resource, source }
175 }
176 }
177 }
178}