qubit_value/value_wire/value_wire_payload_ref_v1.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//! Borrowed V1 payload serialization.
10
11#[cfg(feature = "json")]
12use std::io::Write;
13
14#[cfg(feature = "json")]
15use qubit_budget::json::JsonEncodeLimits;
16#[cfg(feature = "json")]
17use qubit_budget::json::JsonEncodeSession;
18#[cfg(feature = "json")]
19use qubit_json::encode::JsonEncoder;
20use serde::Serialize;
21use serde::Serializer;
22
23use super::ValueWireEncodeError;
24use super::WireShapeRef;
25use crate::MultiValues;
26use crate::Value;
27use crate::ValueContainer;
28use crate::multi_values::MultiValuesRepr;
29use crate::value::ValueRepr;
30#[cfg(feature = "big-decimal")]
31use crate::wire::MAX_BIG_DECIMAL_ABSOLUTE_SCALE;
32#[cfg(feature = "big-decimal")]
33use crate::wire::is_valid_big_decimal_scale;
34
35/// Borrowed unversioned V1 payload for serialization without cloning.
36///
37/// Use one of the fallible constructors or the corresponding `TryFrom` impl
38/// to create a payload. The private representation prevents callers from
39/// bypassing V1 validation by constructing an unchecked shape directly.
40///
41/// # Type Parameters
42///
43/// * `'a` - Lifetime of the runtime payload borrowed for serialization.
44///
45/// # Examples
46///
47/// ```
48/// use qubit_value::{Value, ValueWirePayloadRefV1};
49///
50/// let value = Value::from(42_i32);
51/// let _payload = ValueWirePayloadRefV1::from_value(&value).unwrap();
52/// ```
53#[must_use]
54pub struct ValueWirePayloadRefV1<'a> {
55 /// Borrowed scalar-or-collection shape validated for V1 serialization.
56 shape: WireShapeRef<'a>,
57}
58
59impl<'a> ValueWirePayloadRefV1<'a> {
60 /// Borrows a scalar after validating V1's finite-float invariant.
61 ///
62 /// # Parameters
63 ///
64 /// * `value` - Scalar runtime value to validate and borrow.
65 ///
66 /// # Returns
67 ///
68 /// A borrowed V1 payload preserving the scalar shape.
69 ///
70 /// # Errors
71 ///
72 /// Returns [`ValueWireEncodeError`] when the scalar contains a non-finite
73 /// float or an out-of-range decimal scale.
74 pub fn from_value(value: &'a Value) -> Result<Self, ValueWireEncodeError> {
75 validate_value(value)?;
76 Ok(Self {
77 shape: WireShapeRef::Scalar(value.into()),
78 })
79 }
80
81 /// Borrows a collection after validating V1's finite-float invariant.
82 ///
83 /// # Parameters
84 ///
85 /// * `values` - Homogeneous runtime collection to validate and borrow.
86 ///
87 /// # Returns
88 ///
89 /// A borrowed V1 payload preserving the collection shape.
90 ///
91 /// # Errors
92 ///
93 /// Returns [`ValueWireEncodeError`] when an element is a non-finite float
94 /// or has an out-of-range decimal scale.
95 pub fn from_values(values: &'a MultiValues) -> Result<Self, ValueWireEncodeError> {
96 validate_values(values)?;
97 Ok(Self {
98 shape: WireShapeRef::Collection(values.into()),
99 })
100 }
101
102 /// Borrows an explicit shape after validating V1's finite-float invariant.
103 ///
104 /// # Parameters
105 ///
106 /// * `value` - Explicit scalar-or-collection value to validate and borrow.
107 ///
108 /// # Returns
109 ///
110 /// A borrowed V1 payload preserving the original shape.
111 ///
112 /// # Errors
113 ///
114 /// Returns [`ValueWireEncodeError`] when any contained value violates the
115 /// V1 numeric representation constraints.
116 pub fn from_container(value: &'a ValueContainer) -> Result<Self, ValueWireEncodeError> {
117 match value {
118 ValueContainer::Scalar(value) => validate_value(value)?,
119 ValueContainer::Collection(values) => validate_values(values)?,
120 }
121 Ok(Self { shape: value.into() })
122 }
123
124 /// Returns the borrowed internal shape used by V1 serialization.
125 ///
126 /// # Returns
127 ///
128 /// A copyable borrowed view of the validated scalar-or-collection shape.
129 #[must_use]
130 #[inline(always)]
131 pub(in crate::value_wire) fn shape(&self) -> WireShapeRef<'a> {
132 self.shape
133 }
134
135 /// Encodes the borrowed V1 payload into a compact JSON vector.
136 ///
137 /// # Returns
138 ///
139 /// Compact UTF-8 JSON bytes for this unversioned payload.
140 ///
141 /// # Errors
142 ///
143 /// Returns [`ValueWireEncodeError`] when encoding exceeds the default
144 /// resource profile or Serde rejects the payload.
145 #[cfg(feature = "json")]
146 #[inline(always)]
147 pub fn to_json_vec(&self) -> Result<Vec<u8>, ValueWireEncodeError> {
148 self.to_json_vec_with_limits(super::default_json_encode_limits())
149 }
150
151 /// Encodes the borrowed V1 payload with explicit JSON resource limits.
152 ///
153 /// # Parameters
154 ///
155 /// * `limits` - Resource limits enforced during JSON encoding.
156 ///
157 /// # Returns
158 ///
159 /// Compact UTF-8 JSON bytes for this unversioned payload.
160 ///
161 /// # Errors
162 ///
163 /// Returns [`ValueWireEncodeError`] when encoding exceeds `limits` or
164 /// Serde rejects the payload.
165 #[cfg(feature = "json")]
166 #[inline]
167 pub fn to_json_vec_with_limits(&self, limits: JsonEncodeLimits) -> Result<Vec<u8>, ValueWireEncodeError> {
168 let session = JsonEncodeSession::from_limits(limits);
169 JsonEncoder::new(session)
170 .to_vec(self)
171 .map_err(ValueWireEncodeError::from)
172 }
173
174 /// Encodes the borrowed V1 payload to a writer with default limits.
175 ///
176 /// # Type Parameters
177 ///
178 /// * `W` - Destination writer type.
179 ///
180 /// # Parameters
181 ///
182 /// * `writer` - Destination receiving the complete JSON payload.
183 ///
184 /// # Returns
185 ///
186 /// `Ok(())` after the complete payload is written.
187 ///
188 /// # Errors
189 ///
190 /// Returns [`ValueWireEncodeError`] for resource, serialization, or writer
191 /// failures.
192 #[cfg(feature = "json")]
193 #[inline(always)]
194 pub fn to_json_writer<W>(&self, writer: W) -> Result<(), ValueWireEncodeError>
195 where
196 W: Write,
197 {
198 self.to_json_writer_with_limits(writer, super::default_json_encode_limits())
199 }
200
201 /// Encodes the borrowed V1 payload to a writer with explicit limits.
202 ///
203 /// # Type Parameters
204 ///
205 /// * `W` - Destination writer type.
206 ///
207 /// # Parameters
208 ///
209 /// * `writer` - Destination receiving the complete JSON payload.
210 /// * `limits` - Resource limits enforced during JSON encoding.
211 ///
212 /// # Returns
213 ///
214 /// `Ok(())` after the complete payload is written.
215 ///
216 /// # Errors
217 ///
218 /// Returns [`ValueWireEncodeError`] when encoding exceeds `limits`, Serde
219 /// rejects the payload, or `writer` rejects output.
220 #[cfg(feature = "json")]
221 #[inline]
222 pub fn to_json_writer_with_limits<W>(&self, writer: W, limits: JsonEncodeLimits) -> Result<(), ValueWireEncodeError>
223 where
224 W: Write,
225 {
226 let session = JsonEncodeSession::from_limits(limits);
227 JsonEncoder::new(session)
228 .write_buffered(writer, self)
229 .map_err(ValueWireEncodeError::from)
230 }
231}
232
233/// Validates one scalar against V1's JSON finite-float invariant.
234///
235/// # Parameters
236///
237/// * `value` - Scalar value to validate without modifying it.
238///
239/// # Returns
240///
241/// `Ok(())` when the scalar is representable by the V1 wire contract.
242///
243/// # Errors
244///
245/// Returns [`ValueWireEncodeError`] for a non-finite float or an out-of-range
246/// arbitrary-precision decimal scale.
247pub(in crate::value_wire) fn validate_value(value: &Value) -> Result<(), ValueWireEncodeError> {
248 #[cfg(feature = "big-decimal")]
249 if let ValueRepr::BigDecimal(value) = &value.repr {
250 validate_big_decimal_scale(value.as_bigint_and_exponent().1)?;
251 }
252 let non_finite = matches!(&value.repr, ValueRepr::Float32(value) if !value.is_finite())
253 || matches!(&value.repr, ValueRepr::Float64(value) if !value.is_finite());
254 if non_finite {
255 return Err(ValueWireEncodeError::NonFiniteFloat {
256 data_type: value.data_type(),
257 });
258 }
259 Ok(())
260}
261
262/// Validates one collection against V1's JSON finite-float invariant.
263///
264/// # Parameters
265///
266/// * `values` - Collection to validate without modifying it.
267///
268/// # Returns
269///
270/// `Ok(())` when every element is representable by the V1 wire contract.
271///
272/// # Errors
273///
274/// Returns [`ValueWireEncodeError`] for a non-finite float element or an
275/// out-of-range arbitrary-precision decimal scale.
276pub(in crate::value_wire) fn validate_values(values: &MultiValues) -> Result<(), ValueWireEncodeError> {
277 #[cfg(feature = "big-decimal")]
278 if let MultiValuesRepr::BigDecimal(values) = &values.repr {
279 for value in values {
280 validate_big_decimal_scale(value.as_bigint_and_exponent().1)?;
281 }
282 }
283 let non_finite = match &values.repr {
284 MultiValuesRepr::Float32(values) => values.iter().any(|value| !value.is_finite()),
285 MultiValuesRepr::Float64(values) => values.iter().any(|value| !value.is_finite()),
286 _ => false,
287 };
288 if non_finite {
289 return Err(ValueWireEncodeError::NonFiniteFloat {
290 data_type: values.data_type(),
291 });
292 }
293 Ok(())
294}
295
296/// Validates the decimal exponent accepted by V1's bounded payload format.
297///
298/// # Parameters
299///
300/// * `scale` - Decimal scale to compare with the V1 inclusive bound.
301///
302/// # Returns
303///
304/// `Ok(())` when the scale is within the supported magnitude.
305///
306/// # Errors
307///
308/// Returns [`ValueWireEncodeError::BigDecimalScaleTooLarge`] when `scale`
309/// exceeds the V1 bound.
310#[cfg(feature = "big-decimal")]
311fn validate_big_decimal_scale(scale: i64) -> Result<(), ValueWireEncodeError> {
312 if is_valid_big_decimal_scale(scale) {
313 return Ok(());
314 }
315 Err(ValueWireEncodeError::BigDecimalScaleTooLarge {
316 scale,
317 maximum_absolute_scale: MAX_BIG_DECIMAL_ABSOLUTE_SCALE,
318 })
319}
320
321impl<'a> TryFrom<&'a Value> for ValueWirePayloadRefV1<'a> {
322 type Error = ValueWireEncodeError;
323 /// Borrows and validates a scalar.
324 fn try_from(value: &'a Value) -> Result<Self, Self::Error> {
325 Self::from_value(value)
326 }
327}
328
329impl<'a> TryFrom<&'a MultiValues> for ValueWirePayloadRefV1<'a> {
330 type Error = ValueWireEncodeError;
331 /// Borrows and validates a collection.
332 fn try_from(values: &'a MultiValues) -> Result<Self, Self::Error> {
333 Self::from_values(values)
334 }
335}
336
337impl<'a> TryFrom<&'a ValueContainer> for ValueWirePayloadRefV1<'a> {
338 type Error = ValueWireEncodeError;
339 /// Borrows and validates an explicit shape.
340 fn try_from(value: &'a ValueContainer) -> Result<Self, Self::Error> {
341 Self::from_container(value)
342 }
343}
344
345impl Serialize for ValueWirePayloadRefV1<'_> {
346 /// Serializes the borrowed unversioned V1 shape.
347 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
348 where
349 S: Serializer,
350 {
351 self.shape().serialize(serializer)
352 }
353}