Skip to main content

qubit_json/decode/
json_syntax_error_reason.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//! Stable reasons reported by strict JSON lexical admission.
9
10use std::fmt;
11
12use crate::lexical::JsonLexicalErrorReason;
13
14/// The concrete reason why a JSON document was rejected lexically.
15///
16/// This enum intentionally remains exhaustive so callers can classify every
17/// documented lexical rejection at compile time. New reasons require a
18/// breaking release rather than a `#[non_exhaustive]` change.
19///
20/// # Examples
21///
22/// ```
23/// use qubit_json::decode::JsonSyntaxErrorReason;
24///
25/// let reason = JsonSyntaxErrorReason::UnexpectedByte;
26/// assert_eq!(reason.to_string(), "unexpected byte");
27/// ```
28#[derive(Clone, Copy, Debug, Eq, PartialEq)]
29pub enum JsonSyntaxErrorReason {
30    /// The document ended before a complete token or container was found.
31    UnexpectedEnd,
32    /// A byte is not valid at the current JSON position.
33    ///
34    /// The byte itself is intentionally not retained, so default structured
35    /// diagnostics never expose input content.
36    UnexpectedByte,
37    /// An object key was not followed by a colon.
38    ExpectedColon,
39    /// An array value was not followed by a comma or closing bracket.
40    ExpectedCommaOrArrayEnd,
41    /// An object value was not followed by a comma or closing brace.
42    ExpectedCommaOrObjectEnd,
43    /// An object key was expected at the current position.
44    ExpectedObjectKey,
45    /// A string escape sequence is invalid.
46    InvalidEscape,
47    /// A Unicode escape does not contain four hexadecimal digits.
48    InvalidUnicodeEscape,
49    /// A Unicode surrogate pair is malformed.
50    UnpairedSurrogate,
51    /// The input contains invalid UTF-8.
52    InvalidUtf8,
53    /// A number does not follow JSON number grammar.
54    InvalidNumber,
55    /// An integer is outside the supported `i64`/`u64` range.
56    IntegerOutOfRange,
57    /// A fractional or exponential number is outside finite `f64` range.
58    FloatOutOfRange,
59    /// Non-whitespace bytes follow the complete root value.
60    TrailingCharacters,
61    /// A nesting or position counter overflowed.
62    NestingOverflow,
63}
64
65impl From<JsonLexicalErrorReason> for JsonSyntaxErrorReason {
66    /// Exhaustively maps the shared lexical reason into the public text reason.
67    #[inline]
68    fn from(reason: JsonLexicalErrorReason) -> Self {
69        match reason {
70            JsonLexicalErrorReason::UnexpectedEnd => Self::UnexpectedEnd,
71            JsonLexicalErrorReason::UnexpectedByte => Self::UnexpectedByte,
72            JsonLexicalErrorReason::ExpectedColon => Self::ExpectedColon,
73            JsonLexicalErrorReason::ExpectedCommaOrArrayEnd => Self::ExpectedCommaOrArrayEnd,
74            JsonLexicalErrorReason::ExpectedCommaOrObjectEnd => Self::ExpectedCommaOrObjectEnd,
75            JsonLexicalErrorReason::ExpectedObjectKey => Self::ExpectedObjectKey,
76            JsonLexicalErrorReason::InvalidEscape => Self::InvalidEscape,
77            JsonLexicalErrorReason::InvalidUnicodeEscape => Self::InvalidUnicodeEscape,
78            JsonLexicalErrorReason::UnpairedSurrogate => Self::UnpairedSurrogate,
79            JsonLexicalErrorReason::InvalidUtf8 => Self::InvalidUtf8,
80            JsonLexicalErrorReason::InvalidNumber => Self::InvalidNumber,
81            JsonLexicalErrorReason::IntegerOutOfRange => Self::IntegerOutOfRange,
82            JsonLexicalErrorReason::FloatOutOfRange => Self::FloatOutOfRange,
83            JsonLexicalErrorReason::TrailingCharacters => Self::TrailingCharacters,
84            JsonLexicalErrorReason::NestingOverflow => Self::NestingOverflow,
85        }
86    }
87}
88
89impl fmt::Display for JsonSyntaxErrorReason {
90    /// Formats the stable human-readable reason.
91    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
92        match self {
93            Self::UnexpectedEnd => formatter.write_str("unexpected end of input"),
94            Self::UnexpectedByte => formatter.write_str("unexpected byte"),
95            Self::ExpectedColon => formatter.write_str("expected ':'"),
96            Self::ExpectedCommaOrArrayEnd => formatter.write_str("expected ',' or ']' in array"),
97            Self::ExpectedCommaOrObjectEnd => formatter.write_str("expected ',' or '}' in object"),
98            Self::ExpectedObjectKey => formatter.write_str("expected object key"),
99            Self::InvalidEscape => formatter.write_str("invalid string escape"),
100            Self::InvalidUnicodeEscape => formatter.write_str("invalid Unicode escape"),
101            Self::UnpairedSurrogate => formatter.write_str("unpaired Unicode surrogate"),
102            Self::InvalidUtf8 => formatter.write_str("invalid UTF-8"),
103            Self::InvalidNumber => formatter.write_str("invalid JSON number"),
104            Self::IntegerOutOfRange => formatter.write_str("JSON integer is outside the supported 64-bit range"),
105            Self::FloatOutOfRange => formatter.write_str("JSON number is outside the finite f64 range"),
106            Self::TrailingCharacters => formatter.write_str("trailing characters"),
107            Self::NestingOverflow => formatter.write_str("JSON nesting overflow"),
108        }
109    }
110}