Skip to main content

qubit_value/value_wire/
value_wire_payload_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//! Unversioned V1 payload for use inside an already-versioned protocol.
10
11#[cfg(feature = "json")]
12use std::io::Write;
13
14#[cfg(feature = "json")]
15use qubit_budget::json::JsonDecodeLimits;
16#[cfg(feature = "json")]
17use qubit_budget::json::JsonDecodeSession;
18#[cfg(feature = "json")]
19use qubit_budget::json::JsonEncodeLimits;
20#[cfg(feature = "json")]
21use qubit_budget::json::JsonEncodeSession;
22#[cfg(feature = "json")]
23use qubit_json::decode::JsonDecoder;
24#[cfg(feature = "json")]
25use qubit_json::encode::JsonEncoder;
26use serde::Serialize;
27use serde::Serializer;
28
29use super::ValueWireEncodeError;
30use super::WireShapeRef;
31use super::value_wire_payload_ref_v1::validate_value;
32use super::value_wire_payload_ref_v1::validate_values;
33#[cfg(feature = "json")]
34use super::value_wire_payload_v1_seed::ValueWirePayloadV1Seed;
35use crate::MultiValues;
36use crate::Value;
37use crate::ValueContainer;
38#[cfg(feature = "json")]
39use crate::ValueWireDecodeError;
40
41/// Typed V1 scalar-or-collection payload without an enclosing version field.
42///
43/// Deserialization is intentionally available through
44/// [`crate::ValueWirePayloadV1Seed`], which lets a bounded decoder control the
45/// complete input and structure.
46///
47/// # Examples
48///
49/// ```
50/// use std::convert::TryFrom;
51/// use qubit_value::{Value, ValueWirePayloadV1};
52///
53/// let _payload = ValueWirePayloadV1::try_from(Value::from(42_i32)).unwrap();
54/// ```
55#[must_use]
56#[derive(Debug, Clone, PartialEq, Eq, Hash)]
57pub struct ValueWirePayloadV1 {
58    /// Preserved runtime shape and payload.
59    value: ValueContainer,
60}
61
62impl ValueWirePayloadV1 {
63    /// Wraps a payload decoded through V1's finite-number Serde adapters.
64    ///
65    /// # Parameters
66    ///
67    /// * `value` - Already validated decoded runtime container.
68    ///
69    /// # Returns
70    ///
71    /// An owned unversioned V1 payload.
72    pub(in crate::value_wire) const fn from_decoded(value: ValueContainer) -> Self {
73        Self { value }
74    }
75
76    /// Builds a payload after enforcing V1's finite-float invariant.
77    ///
78    /// # Parameters
79    ///
80    /// * `value` - Runtime container to validate and own.
81    ///
82    /// # Returns
83    ///
84    /// A validated owned V1 payload.
85    ///
86    /// # Errors
87    ///
88    /// Returns [`ValueWireEncodeError`] when any contained numeric payload is
89    /// not representable by V1.
90    fn try_new(value: ValueContainer) -> Result<Self, ValueWireEncodeError> {
91        match &value {
92            ValueContainer::Scalar(value) => validate_value(value)?,
93            ValueContainer::Collection(values) => validate_values(values)?,
94        }
95        Ok(Self { value })
96    }
97
98    /// Returns the default JSON resource profile for complete V1 payloads.
99    ///
100    /// # Returns
101    ///
102    /// Decode limits suitable for one standalone unversioned V1 payload.
103    #[cfg(feature = "json")]
104    #[must_use = "the V1 JSON profile should be applied to a budget"]
105    #[inline(always)]
106    pub fn default_json_decode_limits() -> JsonDecodeLimits {
107        super::default_json_decode_limits()
108    }
109
110    /// Returns the default JSON resource profile for complete V1 payloads.
111    ///
112    /// # Returns
113    ///
114    /// Encode limits suitable for one standalone unversioned V1 payload.
115    #[cfg(feature = "json")]
116    #[must_use = "the V1 JSON profile should be applied to an encode session"]
117    #[inline(always)]
118    pub fn default_json_encode_limits() -> JsonEncodeLimits {
119        super::default_json_encode_limits()
120    }
121
122    /// Decodes a complete V1 JSON payload using default resource limits.
123    ///
124    /// Prefer this entry point when the payload itself is the complete
125    /// untrusted document. Embedded protocols should share one budget across
126    /// all payloads in their complete document.
127    ///
128    /// # Parameters
129    ///
130    /// * `input` - Complete UTF-8 JSON payload to decode.
131    ///
132    /// # Returns
133    ///
134    /// The decoded unversioned V1 payload.
135    ///
136    /// # Errors
137    ///
138    /// Returns a limit error when the input or decoded structure is too large,
139    /// or [`ValueWireDecodeError::InvalidJson`] for malformed input.
140    #[cfg(feature = "json")]
141    #[inline(always)]
142    pub fn decode_json_slice(input: &[u8]) -> Result<Self, ValueWireDecodeError> {
143        Self::decode_json_slice_with_limits(input, Self::default_json_decode_limits())
144    }
145
146    /// Decodes a complete V1 JSON payload using explicit resource limits.
147    ///
148    /// # Parameters
149    ///
150    /// * `input` - Complete UTF-8 JSON payload to decode.
151    /// * `limits` - Resource limits enforced during decoding.
152    ///
153    /// # Returns
154    ///
155    /// The decoded unversioned V1 payload.
156    ///
157    /// # Errors
158    ///
159    /// Returns a limit error when `input` or its decoded structure exceeds
160    /// `limits`, or [`ValueWireDecodeError::InvalidJson`] for malformed input.
161    #[cfg(feature = "json")]
162    #[inline]
163    pub fn decode_json_slice_with_limits(input: &[u8], limits: JsonDecodeLimits) -> Result<Self, ValueWireDecodeError> {
164        let session = JsonDecodeSession::from_limits(limits);
165        JsonDecoder::new(session)
166            .decode_seed_utf8(ValueWirePayloadV1Seed::new(), input)
167            .map_err(ValueWireDecodeError::from)
168    }
169
170    /// Encodes this V1 payload into a compact JSON vector with default limits.
171    ///
172    /// # Returns
173    ///
174    /// Compact UTF-8 JSON bytes for this unversioned payload.
175    ///
176    /// # Errors
177    ///
178    /// Returns [`ValueWireEncodeError::Budget`] when the payload exceeds the
179    /// default JSON resource profile.
180    #[cfg(feature = "json")]
181    #[inline(always)]
182    pub fn to_json_vec(&self) -> Result<Vec<u8>, ValueWireEncodeError> {
183        self.to_json_vec_with_limits(Self::default_json_encode_limits())
184    }
185
186    /// Encodes this V1 payload into a bounded compact JSON vector.
187    ///
188    /// # Parameters
189    ///
190    /// * `limits` - Resource limits enforced during encoding.
191    ///
192    /// # Returns
193    ///
194    /// Compact UTF-8 JSON bytes for this unversioned payload.
195    ///
196    /// # Errors
197    ///
198    /// Returns [`ValueWireEncodeError`] when encoding exceeds `limits` or the
199    /// payload cannot be serialized.
200    #[cfg(feature = "json")]
201    pub fn to_json_vec_with_limits(&self, limits: JsonEncodeLimits) -> Result<Vec<u8>, ValueWireEncodeError> {
202        let session = JsonEncodeSession::from_limits(limits);
203        JsonEncoder::new(session)
204            .to_vec(self)
205            .map_err(ValueWireEncodeError::from)
206    }
207
208    /// Encodes this V1 payload to a writer with default limits.
209    ///
210    /// # Type Parameters
211    ///
212    /// * `W` - Destination writer type.
213    ///
214    /// # Parameters
215    ///
216    /// * `writer` - Destination receiving the complete JSON payload.
217    ///
218    /// # Returns
219    ///
220    /// `Ok(())` after the complete payload is written.
221    ///
222    /// # Errors
223    ///
224    /// Returns [`ValueWireEncodeError::Budget`] for resource-limit failures or
225    /// [`ValueWireEncodeError::Io`] when `writer` rejects output.
226    #[cfg(feature = "json")]
227    #[inline(always)]
228    pub fn to_json_writer<W>(&self, writer: W) -> Result<(), ValueWireEncodeError>
229    where
230        W: Write,
231    {
232        self.to_json_writer_with_limits(writer, Self::default_json_encode_limits())
233    }
234
235    /// Encodes this V1 payload to a writer after enforcing JSON budgets.
236    ///
237    /// # Type Parameters
238    ///
239    /// * `W` - Destination writer type.
240    ///
241    /// # Parameters
242    ///
243    /// * `writer` - Destination receiving the complete JSON payload.
244    /// * `limits` - Resource limits enforced during encoding.
245    ///
246    /// # Returns
247    ///
248    /// `Ok(())` after the complete payload is written.
249    ///
250    /// # Errors
251    ///
252    /// Returns [`ValueWireEncodeError`] when encoding exceeds `limits`, the
253    /// payload cannot be serialized, or `writer` rejects output.
254    #[cfg(feature = "json")]
255    pub fn to_json_writer_with_limits<W>(&self, writer: W, limits: JsonEncodeLimits) -> Result<(), ValueWireEncodeError>
256    where
257        W: Write,
258    {
259        let session = JsonEncodeSession::from_limits(limits);
260        JsonEncoder::new(session)
261            .write_buffered(writer, self)
262            .map_err(ValueWireEncodeError::from)
263    }
264
265    /// Borrows the preserved runtime value.
266    ///
267    /// # Returns
268    ///
269    /// A shared reference to the preserved scalar-or-collection container.
270    #[must_use = "the borrowed value container should be used"]
271    #[inline(always)]
272    pub const fn container(&self) -> &ValueContainer {
273        &self.value
274    }
275
276    /// Consumes this payload and returns its runtime value.
277    ///
278    /// # Returns
279    ///
280    /// The preserved scalar-or-collection container.
281    #[inline(always)]
282    pub fn into_container(self) -> ValueContainer {
283        self.value
284    }
285}
286
287impl TryFrom<Value> for ValueWirePayloadV1 {
288    type Error = ValueWireEncodeError;
289
290    /// Validates a scalar for use in a V1 payload.
291    #[inline(always)]
292    fn try_from(value: Value) -> Result<Self, Self::Error> {
293        Self::try_new(value.into())
294    }
295}
296
297impl TryFrom<MultiValues> for ValueWirePayloadV1 {
298    type Error = ValueWireEncodeError;
299
300    /// Validates a collection for use in a V1 payload.
301    #[inline(always)]
302    fn try_from(value: MultiValues) -> Result<Self, Self::Error> {
303        Self::try_new(value.into())
304    }
305}
306
307impl TryFrom<ValueContainer> for ValueWirePayloadV1 {
308    type Error = ValueWireEncodeError;
309
310    /// Validates an explicitly shaped value for use in a V1 payload.
311    #[inline(always)]
312    fn try_from(value: ValueContainer) -> Result<Self, Self::Error> {
313        Self::try_new(value)
314    }
315}
316
317impl From<ValueWirePayloadV1> for ValueContainer {
318    /// Extracts the shaped runtime value from a V1 payload.
319    #[inline(always)]
320    fn from(value: ValueWirePayloadV1) -> Self {
321        value.into_container()
322    }
323}
324
325impl Serialize for ValueWirePayloadV1 {
326    /// Serializes the unversioned V1 shape.
327    #[inline(always)]
328    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
329    where
330        S: Serializer,
331    {
332        WireShapeRef::from(&self.value).serialize(serializer)
333    }
334}