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}