Skip to main content

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}