qubit_json/decode/jcs_decode_error.rs
1// =============================================================================
2// Copyright (c) 2026 Haixing Hu.
3//
4// SPDX-License-Identifier: Apache-2.0
5//
6// Licensed under the Apache License, Version 2.0.
7// =============================================================================
8//! Error returned by the JCS JSON text decoder.
9
10use std::error::Error;
11use std::fmt;
12
13use qubit_budget::ResourceQuantity;
14use qubit_budget::json::JsonResource;
15
16use super::JsonDecodeError;
17
18/// JCS decoding failure with explicit duplicate-key handling.
19///
20/// # Type Parameters
21///
22/// * `R` - Resource identity used by the wrapped budget error.
23/// * `Q` - Quantity representation used for resource accounting.
24///
25/// # Examples
26///
27/// ```
28/// use qubit_json::decode::{JcsDecodeError, JcsDecoder};
29///
30/// let error = JcsDecoder::unlimited()
31/// .decode_str(r#"{"x":1,"x":2}"#)
32/// .expect_err("duplicate keys must be rejected");
33/// assert!(matches!(error, JcsDecodeError::DuplicateKey { .. }));
34/// ```
35#[must_use]
36#[derive(Debug, Clone)]
37pub enum JcsDecodeError<R = JsonResource, Q = usize>
38where
39 Q: Copy + fmt::Debug,
40{
41 /// An input, UTF-8, budget, or lexical admission failure.
42 Decode(
43 /// Shared decode error with stable category and input diagnostics.
44 JsonDecodeError<R, Q>,
45 ),
46 /// An object contains two keys equal after JSON escape decoding.
47 DuplicateKey {
48 /// One-based source line of the second key token.
49 line: usize,
50 /// One-based UTF-8 character column of the second key token.
51 column: usize,
52 },
53 /// Historical compatibility variant; the shared builder no longer emits it.
54 MaterializationDepthLimit {
55 /// Maximum number of nested containers accepted during construction.
56 limit: usize,
57 /// One-based source line where the first over-limit container begins.
58 line: usize,
59 /// One-based UTF-8 character column of that container token.
60 column: usize,
61 },
62}
63
64impl<R, Q> fmt::Display for JcsDecodeError<R, Q>
65where
66 R: fmt::Debug,
67 Q: ResourceQuantity,
68{
69 /// Formats the decoding failure with its source location when applicable.
70 ///
71 /// # Parameters
72 ///
73 /// * `formatter` - Destination that receives the formatted message.
74 ///
75 /// # Returns
76 ///
77 /// `Ok(())` after the message is written.
78 ///
79 /// # Errors
80 ///
81 /// Returns the formatter error if writing the message fails.
82 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
83 match self {
84 Self::Decode(error) => error.fmt(formatter),
85 Self::DuplicateKey { line, column } => {
86 write!(formatter, "duplicate JSON object key at line {line}, column {column}")
87 }
88 Self::MaterializationDepthLimit { limit, line, column } => write!(
89 formatter,
90 "JCS value construction depth exceeds {limit} at line {line}, column {column}"
91 ),
92 }
93 }
94}
95
96impl<R, Q> Error for JcsDecodeError<R, Q>
97where
98 R: fmt::Debug + 'static,
99 Q: ResourceQuantity + 'static,
100{
101 /// Returns the wrapped decoding error for the standard error chain.
102 ///
103 /// # Returns
104 ///
105 /// The wrapped decode error for `Decode`, or `None` for the JCS-specific
106 /// duplicate-key and construction-depth failures.
107 fn source(&self) -> Option<&(dyn Error + 'static)> {
108 match self {
109 Self::Decode(error) => Some(error),
110 Self::DuplicateKey { .. } | Self::MaterializationDepthLimit { .. } => None,
111 }
112 }
113}
114
115impl<R, Q> From<JsonDecodeError<R, Q>> for JcsDecodeError<R, Q>
116where
117 Q: Copy + fmt::Debug,
118{
119 /// Wraps a regular decoding failure as a JCS decoding failure.
120 ///
121 /// # Parameters
122 ///
123 /// * `error` - Regular decoding failure to wrap.
124 ///
125 /// # Returns
126 ///
127 /// A JCS error containing the supplied decoding failure.
128 #[inline]
129 fn from(error: JsonDecodeError<R, Q>) -> Self {
130 Self::Decode(error)
131 }
132}