Skip to main content

qubit_json/format/
json_pretty_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//! Errors returned by JSON token formatting.
9
10use std::error::Error;
11use std::fmt;
12
13use super::JsonPrettyErrorKind;
14
15/// Error from validating, planning, or writing formatted JSON tokens.
16///
17/// Syntax errors can carry a byte offset into the input, while output limits,
18/// capacity failures, and I/O errors do not. A source error is available only
19/// when the failure has an underlying cause.
20///
21/// ```
22/// use std::error::Error;
23///
24/// use qubit_json::format::JsonPrettyErrorKind;
25/// use qubit_json::format::JsonPrettyOptions;
26/// use qubit_json::format::format_json_tokens;
27///
28/// let options = JsonPrettyOptions::builder().build()?;
29/// let syntax = format_json_tokens(b"{}x", &options, None).expect_err("trailing data is invalid");
30/// assert_eq!(syntax.kind(), JsonPrettyErrorKind::Syntax);
31/// assert_eq!(syntax.offset(), Some(2));
32/// assert!(syntax.source().is_some());
33///
34/// let limit = format_json_tokens(b"0", &options, Some(0)).expect_err("zero output allowance");
35/// assert_eq!(limit.kind(), JsonPrettyErrorKind::OutputLimit);
36/// assert_eq!(limit.offset(), None);
37/// # Ok::<(), Box<dyn std::error::Error>>(())
38/// ```
39pub struct JsonPrettyError {
40    /// Stable category used to distinguish formatting failure classes.
41    kind: JsonPrettyErrorKind,
42    /// Available zero-based byte position in the source input, when applicable.
43    offset: Option<usize>,
44    /// Optional underlying cause, exposed separately through [`Error::source`].
45    source: Option<Box<dyn Error + Send + Sync>>,
46}
47
48impl JsonPrettyError {
49    /// Creates an error with its category, optional source position, and cause.
50    ///
51    /// `offset` is a zero-based byte coordinate in the input, not a character
52    /// index. `source` may be absent, including for errors whose category is
53    /// [`JsonPrettyErrorKind::Syntax`].
54    ///
55    /// # Parameters
56    ///
57    /// * `kind` — stable category of the formatting failure.
58    /// * `offset` — source input byte coordinate when one is available.
59    /// * `source` — optional underlying error providing additional cause
60    ///   context.
61    pub(super) fn new(
62        kind: JsonPrettyErrorKind,
63        offset: Option<usize>,
64        source: Option<Box<dyn Error + Send + Sync>>,
65    ) -> Self {
66        Self {
67            kind,
68            offset,
69            source,
70        }
71    }
72
73    /// Returns the stable error category.
74    #[must_use]
75    #[inline]
76    pub fn kind(&self) -> JsonPrettyErrorKind {
77        self.kind
78    }
79
80    /// Returns an available zero-based byte coordinate in the source input.
81    ///
82    /// `None` means the error is not associated with a source position; it does
83    /// not mean byte zero. Not every syntax error is guaranteed to have an
84    /// offset.
85    ///
86    /// # Returns
87    ///
88    /// The source input byte offset, or `None` when no coordinate is available.
89    #[inline]
90    pub fn offset(&self) -> Option<usize> {
91        self.offset
92    }
93}
94
95impl fmt::Debug for JsonPrettyError {
96    /// Formats the error category and available source offset for diagnostics.
97    ///
98    /// The underlying cause is intentionally omitted; callers can inspect it
99    /// through [`Error::source`].
100    ///
101    /// # Parameters
102    ///
103    /// * `formatter` — destination formatter for the diagnostic representation.
104    ///
105    /// # Returns
106    ///
107    /// Returns `Ok(())` when formatting succeeds, or the formatter's error
108    /// when it cannot accept the diagnostic.
109    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
110        formatter
111            .debug_struct("JsonPrettyError")
112            .field("kind", &self.kind)
113            .field("offset", &self.offset)
114            .finish()
115    }
116}
117
118impl fmt::Display for JsonPrettyError {
119    /// Formats the error category, including the source offset when available.
120    ///
121    /// The underlying cause is not included in this user-facing summary.
122    ///
123    /// # Parameters
124    ///
125    /// * `formatter` — destination formatter for the user-facing summary.
126    ///
127    /// # Returns
128    ///
129    /// Returns `Ok(())` when formatting succeeds, or the formatter's error
130    /// when it cannot accept the summary.
131    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
132        match self.offset {
133            Some(offset) => write!(formatter, "{} at byte offset {offset}", self.kind),
134            None => fmt::Display::fmt(&self.kind, formatter),
135        }
136    }
137}
138
139impl Error for JsonPrettyError {
140    /// Returns the optional underlying parsing, allocation, or I/O cause.
141    ///
142    /// Formatting and debug output do not include this cause automatically.
143    ///
144    /// # Returns
145    ///
146    /// Returns the underlying cause when one was attached to this formatting
147    /// error, or `None` when no cause is available.
148    fn source(&self) -> Option<&(dyn Error + 'static)> {
149        self.source.as_deref().map(|error| error as _)
150    }
151}