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}