qubit_value/value_wire/value_wire_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//! Public DTO for the stable version-one JSON wire contract.
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::encode::JsonEncoder;
24use serde::Serialize;
25use serde::Serializer;
26
27use super::VALUE_WIRE_V1_VERSION;
28#[cfg(feature = "json")]
29use super::ValueWireDecodeError;
30use super::ValueWireEncodeError;
31use super::ValueWirePayloadV1;
32use super::serialize_wire;
33use crate::MultiValues;
34use crate::Value;
35use crate::ValueContainer;
36
37/// Stable version-one wire DTO for a scalar or homogeneous collection.
38///
39/// For a given serializer and value, output is byte-stable with canonical field
40/// and object-key order, including recursively nested JSON objects. V1 is
41/// closed: existing tags, shapes, and payload representations cannot change,
42/// and future runtime data types require a new wire version instead of
43/// extending V1.
44///
45/// Deserialization is intentionally available through
46/// [`crate::ValueWireV1Seed`], which lets a bounded decoder control the
47/// complete input and structure.
48///
49/// # Examples
50///
51/// ```
52/// use std::convert::TryFrom;
53/// use qubit_value::{Value, ValueWireV1};
54///
55/// let _wire = ValueWireV1::try_from(Value::from(42_i32)).unwrap();
56/// ```
57#[must_use]
58#[derive(Debug, Clone, PartialEq, Eq, Hash)]
59pub struct ValueWireV1 {
60 /// Explicit runtime shape and typed payload represented by this DTO.
61 value: ValueWirePayloadV1,
62}
63
64impl ValueWireV1 {
65 /// Numeric version emitted and accepted by this DTO.
66 pub const VERSION: u8 = VALUE_WIRE_V1_VERSION;
67
68 /// Creates a V1 DTO from an explicit scalar-or-collection container.
69 ///
70 /// # Parameters
71 ///
72 /// * `value` - Runtime container whose exact type and shape are preserved.
73 ///
74 /// # Returns
75 ///
76 /// A V1 DTO containing `value`.
77 #[inline(always)]
78 pub const fn new(value: ValueWirePayloadV1) -> Self {
79 Self { value }
80 }
81
82 /// Returns the default JSON resource profile for complete V1 documents.
83 ///
84 /// # Returns
85 ///
86 /// Decode limits suitable for one standalone V1 envelope.
87 #[cfg(feature = "json")]
88 #[must_use = "the V1 JSON profile should be applied to a budget"]
89 #[inline(always)]
90 pub fn default_json_decode_limits() -> JsonDecodeLimits {
91 super::default_json_decode_limits()
92 }
93
94 /// Returns the default JSON resource profile for encoding V1 documents.
95 ///
96 /// # Returns
97 ///
98 /// Encode limits suitable for one standalone V1 envelope.
99 #[cfg(feature = "json")]
100 #[must_use = "the V1 JSON profile should be applied to an encode session"]
101 #[inline(always)]
102 pub fn default_json_encode_limits() -> JsonEncodeLimits {
103 super::default_json_encode_limits()
104 }
105
106 /// Decodes a V1 JSON wire value using the default structural limits.
107 ///
108 /// The complete input length and decoded structure are checked before the
109 /// value is returned. Embedded protocols should share one
110 /// [`qubit_budget::json::JsonDecodeSession`] across their complete
111 /// document.
112 ///
113 /// # Parameters
114 ///
115 /// * `input` - Complete UTF-8 JSON document to decode.
116 ///
117 /// # Returns
118 ///
119 /// The decoded V1 wire DTO.
120 ///
121 /// # Errors
122 ///
123 /// Returns a limit error when the input or decoded structure is too large,
124 /// [`ValueWireDecodeError::UnsupportedVersion`] when the envelope declares
125 /// another supported-width version, or
126 /// [`ValueWireDecodeError::Syntax`] when the input is not one valid JSON
127 /// document, or [`ValueWireDecodeError::InvalidJson`] when valid JSON
128 /// cannot be decoded as a V1 wire value.
129 #[cfg(feature = "json")]
130 #[inline(always)]
131 pub fn decode_json_slice(input: &[u8]) -> Result<Self, ValueWireDecodeError> {
132 Self::decode_json_slice_with_limits(input, Self::default_json_decode_limits())
133 }
134
135 /// Decodes a V1 JSON wire value using explicit structural limits.
136 ///
137 /// The complete input length and decoded structure are checked before the
138 /// value is returned. Embedded values should be checked through the outer
139 /// protocol's shared [`qubit_budget::json::JsonDecodeSession`].
140 ///
141 /// # Parameters
142 ///
143 /// * `input` - Complete UTF-8 JSON document to decode.
144 /// * `limits` - Shared encoded-input and structural limits.
145 ///
146 /// # Returns
147 ///
148 /// The decoded V1 wire DTO.
149 ///
150 /// # Errors
151 ///
152 /// Returns a limit error when `input` or its decoded structure exceeds
153 /// `limits`, [`ValueWireDecodeError::UnsupportedVersion`] when the envelope
154 /// declares another supported-width version,
155 /// [`ValueWireDecodeError::Syntax`] when the input is not one valid JSON
156 /// document, or [`ValueWireDecodeError::InvalidJson`] when valid JSON
157 /// cannot be decoded as a V1 wire value.
158 #[cfg(feature = "json")]
159 #[inline]
160 pub fn decode_json_slice_with_limits(input: &[u8], limits: JsonDecodeLimits) -> Result<Self, ValueWireDecodeError> {
161 let session = JsonDecodeSession::from_limits(limits);
162 super::decode_wire_json_slice_with_session(input, session)
163 }
164
165 /// Encodes this V1 document into a compact JSON vector with default limits.
166 ///
167 /// # Returns
168 ///
169 /// The encoded complete V1 document.
170 ///
171 /// # Errors
172 ///
173 /// Returns [`ValueWireEncodeError::Budget`] when the document exceeds the
174 /// default JSON resource profile.
175 #[cfg(feature = "json")]
176 #[inline(always)]
177 pub fn to_json_vec(&self) -> Result<Vec<u8>, ValueWireEncodeError> {
178 self.to_json_vec_with_limits(Self::default_json_encode_limits())
179 }
180
181 /// Encodes this V1 document into a bounded compact JSON vector.
182 ///
183 /// # Parameters
184 ///
185 /// * `limits` - Resource limits enforced during encoding.
186 ///
187 /// # Returns
188 ///
189 /// Compact UTF-8 JSON bytes for the complete V1 document.
190 ///
191 /// # Errors
192 ///
193 /// Returns [`ValueWireEncodeError`] when encoding exceeds `limits` or the
194 /// document cannot be serialized.
195 #[cfg(feature = "json")]
196 pub fn to_json_vec_with_limits(&self, limits: JsonEncodeLimits) -> Result<Vec<u8>, ValueWireEncodeError> {
197 let session = JsonEncodeSession::from_limits(limits);
198 JsonEncoder::new(session)
199 .to_vec(self)
200 .map_err(ValueWireEncodeError::from)
201 }
202
203 /// Encodes this V1 document to a writer with default limits.
204 ///
205 /// # Type Parameters
206 ///
207 /// * `W` - Destination writer type.
208 ///
209 /// # Parameters
210 ///
211 /// * `writer` - Destination receiving the complete JSON document.
212 ///
213 /// # Returns
214 ///
215 /// `Ok(())` after the complete document is written.
216 ///
217 /// # Errors
218 ///
219 /// Returns [`ValueWireEncodeError::Budget`] for resource-limit failures or
220 /// [`ValueWireEncodeError::Io`] when `writer` rejects output.
221 #[cfg(feature = "json")]
222 #[inline(always)]
223 pub fn to_json_writer<W>(&self, writer: W) -> Result<(), ValueWireEncodeError>
224 where
225 W: Write,
226 {
227 self.to_json_writer_with_limits(writer, Self::default_json_encode_limits())
228 }
229
230 /// Encodes this V1 document to a writer after enforcing JSON budgets.
231 ///
232 /// # Type Parameters
233 ///
234 /// * `W` - Destination writer type.
235 ///
236 /// # Parameters
237 ///
238 /// * `writer` - Destination receiving the complete JSON document.
239 /// * `limits` - Resource limits enforced during encoding.
240 ///
241 /// # Returns
242 ///
243 /// `Ok(())` after the complete document is written.
244 ///
245 /// # Errors
246 ///
247 /// Returns [`ValueWireEncodeError`] when encoding exceeds `limits`, the
248 /// document cannot be serialized, or `writer` rejects output.
249 #[cfg(feature = "json")]
250 pub fn to_json_writer_with_limits<W>(&self, writer: W, limits: JsonEncodeLimits) -> Result<(), ValueWireEncodeError>
251 where
252 W: Write,
253 {
254 let session = JsonEncodeSession::from_limits(limits);
255 JsonEncoder::new(session)
256 .write_buffered(writer, self)
257 .map_err(ValueWireEncodeError::from)
258 }
259
260 /// Returns the runtime container represented by this DTO.
261 ///
262 /// # Returns
263 ///
264 /// A shared reference to the preserved runtime container.
265 #[must_use = "the borrowed value container should be used"]
266 #[inline(always)]
267 pub const fn container(&self) -> &ValueContainer {
268 self.value.container()
269 }
270
271 /// Consumes the DTO and returns its runtime container.
272 ///
273 /// # Returns
274 ///
275 /// The preserved runtime container.
276 #[inline(always)]
277 pub fn into_container(self) -> ValueContainer {
278 self.value.into_container()
279 }
280}
281
282impl TryFrom<Value> for ValueWireV1 {
283 type Error = ValueWireEncodeError;
284 /// Wraps a runtime scalar in a V1 DTO.
285 #[inline(always)]
286 fn try_from(value: Value) -> Result<Self, Self::Error> {
287 ValueWirePayloadV1::try_from(value).map(Self::new)
288 }
289}
290
291impl TryFrom<MultiValues> for ValueWireV1 {
292 type Error = ValueWireEncodeError;
293 /// Wraps a runtime collection in a V1 DTO.
294 #[inline(always)]
295 fn try_from(values: MultiValues) -> Result<Self, Self::Error> {
296 ValueWirePayloadV1::try_from(values).map(Self::new)
297 }
298}
299
300impl TryFrom<ValueContainer> for ValueWireV1 {
301 type Error = ValueWireEncodeError;
302 /// Wraps an explicit runtime shape in a V1 DTO.
303 #[inline(always)]
304 fn try_from(value: ValueContainer) -> Result<Self, Self::Error> {
305 ValueWirePayloadV1::try_from(value).map(Self::new)
306 }
307}
308
309impl From<ValueWireV1> for ValueContainer {
310 /// Unwraps the runtime container from a V1 DTO.
311 #[inline(always)]
312 fn from(value: ValueWireV1) -> Self {
313 value.into_container()
314 }
315}
316
317impl Serialize for ValueWireV1 {
318 /// Serializes the contained runtime shape through the V1 contract.
319 #[inline(always)]
320 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
321 where
322 S: Serializer,
323 {
324 serialize_wire(self.value.container().into(), serializer)
325 }
326}