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