Skip to main content

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!(
87                    formatter,
88                    "duplicate JSON object key at line {line}, column {column}"
89                )
90            }
91            Self::MaterializationDepthLimit {
92                limit,
93                line,
94                column,
95            } => write!(
96                formatter,
97                "JCS value construction depth exceeds {limit} at line {line}, column {column}"
98            ),
99        }
100    }
101}
102
103impl<R, Q> Error for JcsDecodeError<R, Q>
104where
105    R: fmt::Debug + 'static,
106    Q: ResourceQuantity + 'static,
107{
108    /// Returns the wrapped decoding error for the standard error chain.
109    ///
110    /// # Returns
111    ///
112    /// The wrapped decode error for `Decode`, or `None` for the JCS-specific
113    /// duplicate-key and construction-depth failures.
114    fn source(&self) -> Option<&(dyn Error + 'static)> {
115        match self {
116            Self::Decode(error) => Some(error),
117            Self::DuplicateKey { .. } | Self::MaterializationDepthLimit { .. } => None,
118        }
119    }
120}
121
122impl<R, Q> From<JsonDecodeError<R, Q>> for JcsDecodeError<R, Q>
123where
124    Q: Copy + fmt::Debug,
125{
126    /// Wraps a regular decoding failure as a JCS decoding failure.
127    ///
128    /// # Parameters
129    ///
130    /// * `error` - Regular decoding failure to wrap.
131    ///
132    /// # Returns
133    ///
134    /// A JCS error containing the supplied decoding failure.
135    #[inline]
136    fn from(error: JsonDecodeError<R, Q>) -> Self {
137        Self::Decode(error)
138    }
139}