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