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 {}