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}