Skip to main content

qubit_json/decode/
json_syntax_error.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//! Structured location-aware JSON lexical errors.
9
10use std::error::Error;
11use std::fmt;
12
13use super::JsonSyntaxErrorReason;
14use crate::lexical::JsonLexicalFailure;
15
16/// A JSON syntax error with byte and human-readable source coordinates.
17///
18/// # Examples
19///
20/// ```
21/// use qubit_json::decode::{JsonSyntaxError, JsonSyntaxErrorReason};
22///
23/// let error = JsonSyntaxError::new(
24///     0,
25///     1,
26///     1,
27///     JsonSyntaxErrorReason::UnexpectedEnd,
28/// );
29/// assert_eq!(error.line(), 1);
30/// assert_eq!(error.reason(), JsonSyntaxErrorReason::UnexpectedEnd);
31/// ```
32#[must_use]
33#[derive(Clone, Copy, Debug, Eq, PartialEq)]
34pub struct JsonSyntaxError {
35    /// Zero-based byte offset at which the error was observed.
36    offset: usize,
37    /// One-based source line containing the error.
38    line: usize,
39    /// One-based UTF-8 character column containing the error.
40    column: usize,
41    /// Stable classification of the lexical failure.
42    reason: JsonSyntaxErrorReason,
43}
44
45impl JsonSyntaxError {
46    /// Creates a syntax error from a source position and stable reason.
47    ///
48    /// # Parameters
49    ///
50    /// * `offset` - Zero-based byte offset at which the error was observed.
51    /// * `line` - One-based source line containing the error.
52    /// * `column` - One-based UTF-8 character column containing the error.
53    /// * `reason` - Stable classification of the lexical failure.
54    ///
55    /// # Returns
56    ///
57    /// A syntax error containing the supplied location and classification.
58    #[inline]
59    pub const fn new(offset: usize, line: usize, column: usize, reason: JsonSyntaxErrorReason) -> Self {
60        Self {
61            offset,
62            line,
63            column,
64            reason,
65        }
66    }
67
68    /// Converts a crate-private lexical failure into the text-domain syntax
69    /// error.
70    ///
71    /// The byte offset, line, and column are preserved, while the lexical
72    /// reason is mapped to its syntax-error classification.
73    ///
74    /// # Parameters
75    ///
76    /// * `failure` - The lexical failure whose location and reason are
77    ///   converted.
78    ///
79    /// # Returns
80    ///
81    /// A syntax error with the lexical failure's source coordinates and mapped
82    /// reason.
83    pub(crate) fn from_lexical(failure: JsonLexicalFailure) -> Self {
84        Self::new(failure.offset, failure.line, failure.column, failure.reason.into())
85    }
86
87    /// Returns the zero-based byte offset.
88    ///
89    /// # Returns
90    ///
91    /// The zero-based byte offset at which the error was observed.
92    #[must_use]
93    #[inline(always)]
94    pub const fn offset(&self) -> usize {
95        self.offset
96    }
97
98    /// Returns the one-based source line.
99    ///
100    /// # Returns
101    ///
102    /// The one-based source line containing the error.
103    #[must_use]
104    #[inline(always)]
105    pub const fn line(&self) -> usize {
106        self.line
107    }
108
109    /// Returns the one-based UTF-8 character column.
110    ///
111    /// # Returns
112    ///
113    /// The one-based UTF-8 character column containing the error.
114    #[must_use]
115    #[inline(always)]
116    pub const fn column(&self) -> usize {
117        self.column
118    }
119
120    /// Returns the stable syntax classification.
121    ///
122    /// # Returns
123    ///
124    /// The stable lexical failure classification.
125    #[must_use]
126    #[inline(always)]
127    pub const fn reason(&self) -> JsonSyntaxErrorReason {
128        self.reason
129    }
130}
131
132impl fmt::Display for JsonSyntaxError {
133    /// Formats the reason and complete source location for human-readable
134    /// diagnostics.
135    ///
136    /// # Errors
137    ///
138    /// Returns the formatter's error if writing the diagnostic to its output
139    /// fails.
140    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
141        write!(
142            formatter,
143            "{} at line {} column {} (byte offset {})",
144            self.reason, self.line, self.column, self.offset,
145        )
146    }
147}
148
149impl Error for JsonSyntaxError {}