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
11use serde::{
12    Serialize,
13    Serializer,
14};
15
16use crate::multi_values::MultiValuesRepr;
17use crate::value::ValueRepr;
18#[cfg(feature = "json")]
19use crate::wire::JSON_NUMBER_TOKEN;
20#[cfg(feature = "big-decimal")]
21use crate::wire::{
22    MAX_BIG_DECIMAL_ABSOLUTE_SCALE,
23    is_valid_big_decimal_scale,
24};
25use crate::{
26    MultiValues,
27    Value,
28    ValueContainer,
29};
30
31use super::{
32    ValueWireEncodeError,
33    WireShapeRef,
34};
35
36/// Borrowed unversioned V1 payload for serialization without cloning.
37///
38/// Use one of the fallible constructors or the corresponding `TryFrom` impl
39/// to create a payload. The private representation prevents callers from
40/// bypassing V1 validation by constructing an unchecked shape directly.
41#[must_use]
42pub struct ValueWirePayloadRefV1<'a> {
43    shape: WireShapeRef<'a>,
44}
45
46impl<'a> ValueWirePayloadRefV1<'a> {
47    /// Borrows a scalar after validating V1's finite-float invariant.
48    pub fn from_value(value: &'a Value) -> Result<Self, ValueWireEncodeError> {
49        validate_value(value)?;
50        Ok(Self {
51            shape: WireShapeRef::Scalar(value.into()),
52        })
53    }
54
55    /// Borrows a collection after validating V1's finite-float invariant.
56    pub fn from_values(
57        values: &'a MultiValues,
58    ) -> Result<Self, ValueWireEncodeError> {
59        validate_values(values)?;
60        Ok(Self {
61            shape: WireShapeRef::Collection(values.into()),
62        })
63    }
64
65    /// Borrows an explicit shape after validating V1's finite-float invariant.
66    pub fn from_container(
67        value: &'a ValueContainer,
68    ) -> Result<Self, ValueWireEncodeError> {
69        match value {
70            ValueContainer::Scalar(value) => validate_value(value)?,
71            ValueContainer::Collection(values) => validate_values(values)?,
72        }
73        Ok(Self {
74            shape: value.into(),
75        })
76    }
77
78    /// Returns the borrowed internal shape used by V1 serialization.
79    pub(in crate::value_wire) fn shape(&self) -> WireShapeRef<'a> {
80        self.shape
81    }
82}
83
84/// Validates one scalar against V1's JSON finite-float invariant.
85pub(in crate::value_wire) fn validate_value(
86    value: &Value,
87) -> Result<(), ValueWireEncodeError> {
88    #[cfg(feature = "big-decimal")]
89    if let ValueRepr::BigDecimal(value) = &value.repr {
90        validate_big_decimal_scale(value.as_bigint_and_exponent().1)?;
91    }
92    let non_finite = matches!(&value.repr, ValueRepr::Float32(value) if !value.is_finite())
93        || matches!(&value.repr, ValueRepr::Float64(value) if !value.is_finite());
94    if non_finite {
95        return Err(ValueWireEncodeError::NonFiniteFloat {
96            data_type: value.data_type(),
97        });
98    }
99    #[cfg(feature = "json")]
100    if let ValueRepr::Json(value) = &value.repr {
101        validate_json_value(value)?;
102    }
103    Ok(())
104}
105
106/// Validates one collection against V1's JSON finite-float invariant.
107pub(in crate::value_wire) fn validate_values(
108    values: &MultiValues,
109) -> Result<(), ValueWireEncodeError> {
110    #[cfg(feature = "big-decimal")]
111    if let MultiValuesRepr::BigDecimal(values) = &values.repr {
112        for value in values {
113            validate_big_decimal_scale(value.as_bigint_and_exponent().1)?;
114        }
115    }
116    let non_finite = match &values.repr {
117        MultiValuesRepr::Float32(values) => {
118            values.iter().any(|value| !value.is_finite())
119        }
120        MultiValuesRepr::Float64(values) => {
121            values.iter().any(|value| !value.is_finite())
122        }
123        _ => false,
124    };
125    if non_finite {
126        return Err(ValueWireEncodeError::NonFiniteFloat {
127            data_type: values.data_type(),
128        });
129    }
130    #[cfg(feature = "json")]
131    if let MultiValuesRepr::Json(values) = &values.repr {
132        for value in values {
133            validate_json_value(value)?;
134        }
135    }
136    Ok(())
137}
138
139/// Rejects JSON objects that collide with serde_json's number marker.
140#[cfg(feature = "json")]
141fn validate_json_value(
142    value: &serde_json::Value,
143) -> Result<(), ValueWireEncodeError> {
144    match value {
145        serde_json::Value::Array(values) => {
146            for value in values {
147                validate_json_value(value)?;
148            }
149        }
150        serde_json::Value::Object(values) => {
151            if values.contains_key(JSON_NUMBER_TOKEN) {
152                return Err(ValueWireEncodeError::ReservedJsonObjectKey {
153                    key: JSON_NUMBER_TOKEN,
154                });
155            }
156            for value in values.values() {
157                validate_json_value(value)?;
158            }
159        }
160        serde_json::Value::Null
161        | serde_json::Value::Bool(_)
162        | serde_json::Value::Number(_)
163        | serde_json::Value::String(_) => {}
164    }
165    Ok(())
166}
167
168/// Validates the decimal exponent accepted by V1's bounded payload format.
169#[cfg(feature = "big-decimal")]
170fn validate_big_decimal_scale(scale: i64) -> Result<(), ValueWireEncodeError> {
171    if is_valid_big_decimal_scale(scale) {
172        return Ok(());
173    }
174    Err(ValueWireEncodeError::BigDecimalScaleTooLarge {
175        scale,
176        maximum_absolute_scale: MAX_BIG_DECIMAL_ABSOLUTE_SCALE,
177    })
178}
179
180impl<'a> TryFrom<&'a Value> for ValueWirePayloadRefV1<'a> {
181    type Error = ValueWireEncodeError;
182    /// Borrows and validates a scalar.
183    fn try_from(value: &'a Value) -> Result<Self, Self::Error> {
184        Self::from_value(value)
185    }
186}
187
188impl<'a> TryFrom<&'a MultiValues> for ValueWirePayloadRefV1<'a> {
189    type Error = ValueWireEncodeError;
190    /// Borrows and validates a collection.
191    fn try_from(values: &'a MultiValues) -> Result<Self, Self::Error> {
192        Self::from_values(values)
193    }
194}
195
196impl<'a> TryFrom<&'a ValueContainer> for ValueWirePayloadRefV1<'a> {
197    type Error = ValueWireEncodeError;
198    /// Borrows and validates an explicit shape.
199    fn try_from(value: &'a ValueContainer) -> Result<Self, Self::Error> {
200        Self::from_container(value)
201    }
202}
203
204impl Serialize for ValueWirePayloadRefV1<'_> {
205    /// Serializes the borrowed unversioned V1 shape.
206    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
207    where
208        S: Serializer,
209    {
210        self.shape().serialize(serializer)
211    }
212}