Skip to main content

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
11use serde::{
12    Deserialize,
13    Deserializer,
14    Serialize,
15    Serializer,
16};
17
18use crate::{
19    MultiValues,
20    Value,
21    ValueContainer,
22};
23
24use super::{
25    VALUE_WIRE_V1_VERSION,
26    ValueWireEncodeError,
27    ValueWirePayloadV1,
28    deserialize_wire,
29    serialize_wire,
30};
31#[cfg(feature = "json")]
32use super::{
33    ValueWireDecodeError,
34    WireLimits,
35};
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/// # Resource limits
46///
47/// The generic [`Deserialize`](serde::Deserialize) implementation is intended
48/// for already-bounded embedded documents and does not enforce message-size or
49/// structural limits. Use `ValueWireV1::decode_json_slice` or
50/// `ValueWireV1::decode_json_slice_with_limits` for untrusted complete JSON
51/// input.
52#[must_use]
53#[derive(Debug, Clone, PartialEq, Eq, Hash)]
54pub struct ValueWireV1 {
55    /// Explicit runtime shape and typed payload represented by this DTO.
56    value: ValueWirePayloadV1,
57}
58
59impl ValueWireV1 {
60    /// Numeric version emitted and accepted by this DTO.
61    pub const VERSION: u8 = VALUE_WIRE_V1_VERSION;
62
63    /// Creates a V1 DTO from an explicit scalar-or-collection container.
64    ///
65    /// # Parameters
66    ///
67    /// * `value` - Runtime container whose exact type and shape are preserved.
68    ///
69    /// # Returns
70    ///
71    /// A V1 DTO containing `value`.
72    #[inline(always)]
73    pub const fn new(value: ValueWirePayloadV1) -> Self {
74        Self { value }
75    }
76
77    /// Decodes a V1 JSON wire value using the default structural limits.
78    ///
79    /// The complete input length and decoded structure are checked before the
80    /// value is returned. Embedded protocols should share one
81    /// [`crate::WireBudget`] across their complete document.
82    ///
83    /// # Parameters
84    ///
85    /// * `input` - Complete UTF-8 JSON document to decode.
86    ///
87    /// # Returns
88    ///
89    /// The decoded V1 wire DTO.
90    ///
91    /// # Errors
92    ///
93    /// Returns a limit error when the input or decoded structure is too large,
94    /// or [`ValueWireDecodeError::InvalidJson`] for malformed input.
95    #[cfg(feature = "json")]
96    #[inline]
97    pub fn decode_json_slice(
98        input: &[u8],
99    ) -> Result<Self, ValueWireDecodeError> {
100        Self::decode_json_slice_with_limits(input, WireLimits::default())
101    }
102
103    /// Decodes a V1 JSON wire value using explicit structural limits.
104    ///
105    /// The complete input length and decoded structure are checked before the
106    /// value is returned. Embedded values should be checked through the outer
107    /// protocol's shared [`crate::WireBudget`].
108    ///
109    /// # Parameters
110    ///
111    /// * `input` - Complete UTF-8 JSON document to decode.
112    /// * `limits` - Shared encoded-input and structural limits.
113    ///
114    /// # Returns
115    ///
116    /// The decoded V1 wire DTO.
117    ///
118    /// # Errors
119    ///
120    /// Returns a limit error when `input` or its decoded structure exceeds
121    /// `limits`, or [`ValueWireDecodeError::InvalidJson`] for malformed input.
122    #[cfg(feature = "json")]
123    #[inline]
124    pub fn decode_json_slice_with_limits(
125        input: &[u8],
126        limits: WireLimits,
127    ) -> Result<Self, ValueWireDecodeError> {
128        let mut budget = limits.begin(input.len())?;
129        let value: Self = serde_json::from_slice(input)
130            .map_err(ValueWireDecodeError::from)?;
131        budget.check_container(value.container())?;
132        Ok(value)
133    }
134
135    /// Returns the runtime container represented by this DTO.
136    ///
137    /// # Returns
138    ///
139    /// A shared reference to the preserved runtime container.
140    #[inline(always)]
141    pub const fn container(&self) -> &ValueContainer {
142        self.value.container()
143    }
144
145    /// Consumes the DTO and returns its runtime container.
146    ///
147    /// # Returns
148    ///
149    /// The preserved runtime container.
150    #[inline(always)]
151    pub fn into_container(self) -> ValueContainer {
152        self.value.into_container()
153    }
154}
155
156impl TryFrom<Value> for ValueWireV1 {
157    type Error = ValueWireEncodeError;
158    /// Wraps a runtime scalar in a V1 DTO.
159    #[inline(always)]
160    fn try_from(value: Value) -> Result<Self, Self::Error> {
161        ValueWirePayloadV1::try_from(value).map(Self::new)
162    }
163}
164
165impl TryFrom<MultiValues> for ValueWireV1 {
166    type Error = ValueWireEncodeError;
167    /// Wraps a runtime collection in a V1 DTO.
168    #[inline(always)]
169    fn try_from(values: MultiValues) -> Result<Self, Self::Error> {
170        ValueWirePayloadV1::try_from(values).map(Self::new)
171    }
172}
173
174impl TryFrom<ValueContainer> for ValueWireV1 {
175    type Error = ValueWireEncodeError;
176    /// Wraps an explicit runtime shape in a V1 DTO.
177    #[inline(always)]
178    fn try_from(value: ValueContainer) -> Result<Self, Self::Error> {
179        ValueWirePayloadV1::try_from(value).map(Self::new)
180    }
181}
182
183impl From<ValueWireV1> for ValueContainer {
184    /// Unwraps the runtime container from a V1 DTO.
185    #[inline(always)]
186    fn from(value: ValueWireV1) -> Self {
187        value.into_container()
188    }
189}
190
191impl Serialize for ValueWireV1 {
192    /// Serializes the contained runtime shape through the V1 contract.
193    #[inline(always)]
194    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
195    where
196        S: Serializer,
197    {
198        serialize_wire(self.value.container().into(), serializer)
199    }
200}
201
202impl<'de> Deserialize<'de> for ValueWireV1 {
203    /// Deserializes a validated V1 runtime container into the DTO.
204    #[inline(always)]
205    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
206    where
207        D: Deserializer<'de>,
208    {
209        deserialize_wire(deserializer)
210            .map(ValueWirePayloadV1::from_decoded)
211            .map(Self::new)
212    }
213}