Skip to main content

qubit_json/encode/
json_encoder.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//! Stateful strict JSON text encoding.
9// qubit-style: allow source-test-pair
10
11use std::cell::RefCell;
12use std::fmt::Debug;
13use std::io::Write;
14
15use qubit_budget::ResourceQuantity;
16use qubit_budget::json::JsonEncodeAttempt;
17use qubit_budget::json::JsonEncodeLimits;
18use qubit_budget::json::JsonEncodeSession;
19use qubit_budget::json::JsonResource;
20use serde::Serialize;
21use serde_json::Error as JsonError;
22use serde_json::Serializer as JsonSerializer;
23
24use super::JsonEncodeError;
25use super::output::JsonOutputAccounting;
26use super::output::JsonOutputBuffer;
27use super::output::JsonOutputWriter;
28use super::serializer::json_encode_context::JsonEncodeContext;
29use super::serializer::json_encode_serializer::JsonEncodeSerializer;
30
31/// Encodes strict JSON text while owning cumulative accounting state.
32///
33/// Signed and unsigned 64-bit integers are supported in full. Serde `i128`
34/// values are accepted only when they fit `i64`, or are non-negative and fit
35/// `u64`; `u128` values must fit `u64`. Wider integers return a serialization
36/// error instead of being truncated or converted to strings. Floating-point
37/// values must be finite.
38///
39/// # Type Parameters
40///
41/// * `R` - Resource identity tracked by the encode session.
42/// * `Q` - Quantity representation used for resource accounting.
43///
44/// # Examples
45///
46/// ```
47/// use qubit_json::encode::JsonEncoder;
48///
49/// let mut encoder = JsonEncoder::unlimited();
50/// let bytes = encoder.to_vec(&serde_json::json!({"ok": true}))?;
51/// assert_eq!(bytes, br#"{"ok":true}"#);
52/// # Ok::<(), qubit_json::encode::JsonEncodeError<qubit_budget::json::JsonResource>>(())
53/// ```
54pub struct JsonEncoder<'budget, R = JsonResource, Q = usize>
55where
56    Q: ResourceQuantity,
57{
58    /// Session charged by each encode operation.
59    session: JsonEncodeSession<'budget, R, Q>,
60}
61
62impl<R, Q> JsonEncoder<'static, R, Q>
63where
64    R: Clone + Debug,
65    Q: ResourceQuantity,
66{
67    /// Creates an encoder with an owned session built from explicit limits.
68    ///
69    /// # Parameters
70    ///
71    /// * `limits` - Resource limits used to construct the cumulative session.
72    ///
73    /// # Returns
74    ///
75    /// An encoder whose cumulative accounting starts empty and is constrained
76    /// by `limits`.
77    #[inline(always)]
78    #[must_use]
79    pub fn with_limits(limits: JsonEncodeLimits<R, Q>) -> Self {
80        Self::new(JsonEncodeSession::from_limits(limits))
81    }
82}
83
84impl JsonEncoder<'static, JsonResource, usize> {
85    /// Creates an encoder with an explicitly unlimited standard session.
86    ///
87    /// # Returns
88    ///
89    /// An encoder with no configured output or encoded-value limits.
90    #[inline(always)]
91    #[must_use]
92    pub fn unlimited() -> Self {
93        Self::with_limits(JsonEncodeLimits::new())
94    }
95}
96
97impl<'budget, R, Q> JsonEncoder<'budget, R, Q>
98where
99    R: Clone + Debug,
100    Q: ResourceQuantity,
101{
102    /// Creates an encoder that owns a reusable cumulative session.
103    ///
104    /// # Parameters
105    ///
106    /// * `session` - Session that receives committed output accounting.
107    ///
108    /// # Returns
109    ///
110    /// An encoder that retains `session` until [`Self::into_session`] is called
111    /// or the encoder is dropped.
112    #[inline(always)]
113    #[must_use]
114    pub fn new(session: JsonEncodeSession<'budget, R, Q>) -> Self {
115        Self { session }
116    }
117
118    /// Returns the cumulative session for read-only inspection.
119    ///
120    /// The reference exposes charges committed by completed encode
121    /// operations and remains borrowed from this encoder.
122    ///
123    /// # Returns
124    ///
125    /// A shared reference to the cumulative encode session.
126    #[inline(always)]
127    #[must_use]
128    pub const fn session(&self) -> &JsonEncodeSession<'budget, R, Q> {
129        &self.session
130    }
131
132    /// Returns mutable access to the cumulative session.
133    ///
134    /// Changes made through the reference affect the limits and accounting
135    /// state used by subsequent encode operations.
136    ///
137    /// # Returns
138    ///
139    /// A mutable reference to the cumulative encode session.
140    #[inline(always)]
141    #[must_use]
142    pub const fn session_mut(&mut self) -> &mut JsonEncodeSession<'budget, R, Q> {
143        &mut self.session
144    }
145
146    /// Returns the cumulative session and consumes the encoder.
147    ///
148    /// No output is produced and no accounting is reset; ownership of the
149    /// accumulated state is transferred to the caller.
150    ///
151    /// # Returns
152    ///
153    /// The session previously owned by this encoder.
154    #[inline(always)]
155    #[must_use]
156    pub fn into_session(self) -> JsonEncodeSession<'budget, R, Q> {
157        self.session
158    }
159
160    /// Encodes `value` into compact JSON and commits only complete success.
161    ///
162    /// # Type Parameters
163    ///
164    /// * `T` - Source type serialized into JSON.
165    ///
166    /// # Parameters
167    ///
168    /// * `value` - Value to serialize.
169    ///
170    /// # Returns
171    ///
172    /// The compact JSON bytes on success.
173    ///
174    /// # Errors
175    ///
176    /// Returns [`JsonEncodeErrorKind::Budget`](super::JsonEncodeErrorKind::Budget) when accounting rejects the value
177    /// or output, or a serialization error when Serde rejects `value`.
178    pub fn to_vec<T>(&mut self, value: &T) -> Result<Vec<u8>, JsonEncodeError<R, Q>>
179    where
180        T: Serialize + ?Sized,
181    {
182        let has_value_limits = self.session.value_budget().limits().has_limits();
183        let mut attempt = self.session.begin_value();
184        let bytes = Self::serialize_buffer(value, &mut attempt, has_value_limits)?;
185        attempt
186            .try_consume_output_bytes(bytes.len())
187            .map_err(JsonEncodeError::<R, Q>::budget)?;
188        attempt.commit().map_err(JsonEncodeError::<R, Q>::budget)?;
189        Ok(bytes)
190    }
191
192    /// Buffers a complete document before writing it to `writer`.
193    ///
194    /// # Type Parameters
195    ///
196    /// * `W` - Destination writer type.
197    /// * `T` - Source type serialized into JSON.
198    ///
199    /// # Parameters
200    ///
201    /// * `writer` - Destination receiving the complete JSON document.
202    /// * `value` - Value to serialize.
203    ///
204    /// # Returns
205    ///
206    /// `Ok(())` after the complete document is written and accounting is
207    /// committed.
208    ///
209    /// # Errors
210    ///
211    /// Returns [`JsonEncodeErrorKind::Budget`](super::JsonEncodeErrorKind::Budget) when accounting rejects the value
212    /// or output, or a serialization/writer error on failure.
213    pub fn write_buffered<W, T>(&mut self, writer: W, value: &T) -> Result<(), JsonEncodeError<R, Q>>
214    where
215        W: Write,
216        T: Serialize + ?Sized,
217    {
218        let has_value_limits = self.session.value_budget().limits().has_limits();
219        let mut attempt = self.session.begin_value();
220        let bytes = Self::serialize_buffer(value, &mut attempt, has_value_limits)?;
221        attempt
222            .check_output_bytes(bytes.len())
223            .map_err(JsonEncodeError::<R, Q>::budget)?;
224        Self::write_buffer(writer, &bytes, &mut attempt)?;
225        attempt.commit().map_err(JsonEncodeError::<R, Q>::budget)?;
226        Ok(())
227    }
228
229    /// Streams `value` directly to `writer`, retaining accepted prefixes.
230    ///
231    /// # Type Parameters
232    ///
233    /// * `W` - Destination writer type.
234    /// * `T` - Source type serialized into JSON.
235    ///
236    /// # Parameters
237    ///
238    /// * `writer` - Destination receiving streamed JSON bytes.
239    /// * `value` - Value to serialize.
240    ///
241    /// # Returns
242    ///
243    /// `Ok(())` after serialization and output accounting complete.
244    ///
245    /// # Errors
246    ///
247    /// Returns [`JsonEncodeErrorKind::Budget`](super::JsonEncodeErrorKind::Budget) when accounting rejects output,
248    /// or a serialization/writer error on failure. Accepted output prefixes
249    /// remain written when a later operation fails.
250    pub fn write_incremental<W, T>(&mut self, writer: W, value: &T) -> Result<(), JsonEncodeError<R, Q>>
251    where
252        W: Write,
253        T: Serialize + ?Sized,
254    {
255        let has_value_limits = self.session.value_budget().limits().has_limits();
256        let mut attempt = self.session.begin_value();
257        let result = {
258            let (output_budget, transaction) = attempt.split_mut();
259            let accounting = RefCell::new(JsonOutputAccounting::new(output_budget));
260            let mut output = JsonOutputWriter::new(writer, &accounting);
261            let result = {
262                let mut inner = JsonSerializer::new(&mut output);
263                let context = RefCell::new(JsonEncodeContext {
264                    transaction,
265                    output: &accounting,
266                    has_value_limits,
267                });
268                if has_value_limits {
269                    value.serialize(JsonEncodeSerializer::<_, R, Q, true>::new(&mut inner, &context))
270                } else {
271                    value.serialize(JsonEncodeSerializer::<_, R, Q, false>::new(&mut inner, &context))
272                }
273            };
274            if result.is_ok() {
275                let _ = output.flush();
276            }
277            output.into_result(result)
278        };
279        result?;
280        attempt.commit().map_err(JsonEncodeError::<R, Q>::budget)?;
281        Ok(())
282    }
283
284    /// Serializes one value into an output-bounded byte buffer.
285    fn serialize_buffer<T>(
286        value: &T,
287        attempt: &mut JsonEncodeAttempt<'_, R, Q>,
288        has_value_limits: bool,
289    ) -> Result<Vec<u8>, JsonEncodeError<R, Q>>
290    where
291        T: Serialize + ?Sized,
292    {
293        let (output_budget, transaction) = attempt.split_mut();
294        if output_budget.is_none() {
295            let accounting = RefCell::new(JsonOutputAccounting::new(None));
296            let mut bytes = Vec::new();
297            let result = {
298                let mut inner = JsonSerializer::new(&mut bytes);
299                let context = RefCell::new(JsonEncodeContext {
300                    transaction,
301                    output: &accounting,
302                    has_value_limits,
303                });
304                if has_value_limits {
305                    value.serialize(JsonEncodeSerializer::<_, R, Q, true>::new(&mut inner, &context))
306                } else {
307                    value.serialize(JsonEncodeSerializer::<_, R, Q, false>::new(&mut inner, &context))
308                }
309            };
310            if let Some(error) = accounting.borrow_mut().take_violation() {
311                return Err(JsonEncodeError::<R, Q>::budget(error));
312            }
313            if let Some(error) = accounting.borrow_mut().take_syntax_error() {
314                return Err(JsonEncodeError::<R, Q>::invalid_raw_json(error));
315            }
316            if result.is_err() {
317                let error = accounting.borrow_mut().take_serialization_error_or_custom();
318                return Err(JsonEncodeError::<R, Q>::serialization(error));
319            }
320            return Ok(bytes);
321        }
322        let accounting = RefCell::new(JsonOutputAccounting::new(output_budget));
323        let mut output = JsonOutputBuffer::new(&accounting);
324        let result = {
325            let mut inner = JsonSerializer::new(&mut output);
326            let context = RefCell::new(JsonEncodeContext {
327                transaction,
328                output: &accounting,
329                has_value_limits,
330            });
331            if has_value_limits {
332                value.serialize(JsonEncodeSerializer::<_, R, Q, true>::new(&mut inner, &context))
333            } else {
334                value.serialize(JsonEncodeSerializer::<_, R, Q, false>::new(&mut inner, &context))
335            }
336        };
337        if result.is_ok() {
338            let _ = output.flush();
339        }
340        output.into_result(result)
341    }
342
343    /// Writes buffered bytes and charges each accepted prefix.
344    fn write_buffer<W>(
345        writer: W,
346        bytes: &[u8],
347        attempt: &mut JsonEncodeAttempt<'_, R, Q>,
348    ) -> Result<(), JsonEncodeError<R, Q>>
349    where
350        W: Write,
351    {
352        let (output_budget, _) = attempt.split_mut();
353        let accounting = RefCell::new(JsonOutputAccounting::new(output_budget));
354        let mut output = JsonOutputWriter::new(writer, &accounting);
355        let result = output.write_all(bytes).map_err(JsonError::io);
356        output.into_result(result)
357    }
358}