1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
// =============================================================================
// Copyright (c) 2025 - 2026 Haixing Hu.
//
// SPDX-License-Identifier: Apache-2.0
//
// Licensed under the Apache License, Version 2.0.
// =============================================================================
//! Stable reasons reported by strict JSON lexical admission.
use std::fmt;
use crate::lexical::JsonLexicalErrorReason;
/// The concrete reason why a JSON document was rejected lexically.
///
/// This enum intentionally remains exhaustive so callers can classify every
/// documented lexical rejection at compile time. New reasons require a
/// breaking release rather than a `#[non_exhaustive]` change.
///
/// # Examples
///
/// ```
/// use qubit_json::decode::JsonSyntaxErrorReason;
///
/// let reason = JsonSyntaxErrorReason::UnexpectedByte;
/// assert_eq!(reason.to_string(), "unexpected byte");
/// ```
#[must_use]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum JsonSyntaxErrorReason {
/// The document ended before a complete token or container was found.
UnexpectedEnd,
/// A byte is not valid at the current JSON position.
///
/// The byte itself is intentionally not retained, so default structured
/// diagnostics never expose input content.
UnexpectedByte,
/// An object key was not followed by a colon.
ExpectedColon,
/// An array value was not followed by a comma or closing bracket.
ExpectedCommaOrArrayEnd,
/// An object value was not followed by a comma or closing brace.
ExpectedCommaOrObjectEnd,
/// An object key was expected at the current position.
ExpectedObjectKey,
/// A string escape sequence is invalid.
InvalidEscape,
/// A Unicode escape does not contain four hexadecimal digits.
InvalidUnicodeEscape,
/// A Unicode surrogate pair is malformed.
UnpairedSurrogate,
/// The input contains invalid UTF-8.
InvalidUtf8,
/// A number does not follow JSON number grammar.
InvalidNumber,
/// An integer is outside the supported `i64`/`u64` range.
IntegerOutOfRange,
/// A number is outside the finite `f64` range for the selected number
/// model.
FloatOutOfRange,
/// Non-whitespace bytes follow the complete root value.
TrailingCharacters,
/// A nesting or position counter overflowed.
NestingOverflow,
}
impl From<JsonLexicalErrorReason> for JsonSyntaxErrorReason {
/// Maps a lexical rejection reason to its public diagnostic counterpart.
///
/// The mapping is exhaustive so each internal lexical failure retains its
/// corresponding stable public reason.
///
/// # Parameters
///
/// * `reason` - Internal reason produced by strict lexical scanning.
///
/// # Returns
///
/// The public syntax reason corresponding to `reason`.
#[inline]
fn from(reason: JsonLexicalErrorReason) -> Self {
match reason {
JsonLexicalErrorReason::UnexpectedEnd => Self::UnexpectedEnd,
JsonLexicalErrorReason::UnexpectedByte => Self::UnexpectedByte,
JsonLexicalErrorReason::ExpectedColon => Self::ExpectedColon,
JsonLexicalErrorReason::ExpectedCommaOrArrayEnd => Self::ExpectedCommaOrArrayEnd,
JsonLexicalErrorReason::ExpectedCommaOrObjectEnd => Self::ExpectedCommaOrObjectEnd,
JsonLexicalErrorReason::ExpectedObjectKey => Self::ExpectedObjectKey,
JsonLexicalErrorReason::InvalidEscape => Self::InvalidEscape,
JsonLexicalErrorReason::InvalidUnicodeEscape => Self::InvalidUnicodeEscape,
JsonLexicalErrorReason::UnpairedSurrogate => Self::UnpairedSurrogate,
JsonLexicalErrorReason::InvalidUtf8 => Self::InvalidUtf8,
JsonLexicalErrorReason::InvalidNumber => Self::InvalidNumber,
JsonLexicalErrorReason::IntegerOutOfRange => Self::IntegerOutOfRange,
JsonLexicalErrorReason::FloatOutOfRange => Self::FloatOutOfRange,
JsonLexicalErrorReason::TrailingCharacters => Self::TrailingCharacters,
JsonLexicalErrorReason::NestingOverflow => Self::NestingOverflow,
JsonLexicalErrorReason::ConsumerStopped => Self::UnexpectedByte,
JsonLexicalErrorReason::EncodedStringLimit { .. } => Self::UnexpectedByte,
}
}
}
impl fmt::Display for JsonSyntaxErrorReason {
/// Formats this reason as stable, input-independent diagnostic text.
///
/// # Parameters
///
/// * `formatter` - Destination for the formatted reason.
///
/// # Returns
///
/// `Ok(())` when the reason is written successfully.
///
/// # Errors
///
/// Returns the formatter's error if writing the reason fails.
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::UnexpectedEnd => formatter.write_str("unexpected end of input"),
Self::UnexpectedByte => formatter.write_str("unexpected byte"),
Self::ExpectedColon => formatter.write_str("expected ':'"),
Self::ExpectedCommaOrArrayEnd => formatter.write_str("expected ',' or ']' in array"),
Self::ExpectedCommaOrObjectEnd => formatter.write_str("expected ',' or '}' in object"),
Self::ExpectedObjectKey => formatter.write_str("expected object key"),
Self::InvalidEscape => formatter.write_str("invalid string escape"),
Self::InvalidUnicodeEscape => formatter.write_str("invalid Unicode escape"),
Self::UnpairedSurrogate => formatter.write_str("unpaired Unicode surrogate"),
Self::InvalidUtf8 => formatter.write_str("invalid UTF-8"),
Self::InvalidNumber => formatter.write_str("invalid JSON number"),
Self::IntegerOutOfRange => formatter.write_str("JSON integer is outside the supported 64-bit range"),
Self::FloatOutOfRange => formatter.write_str("JSON number is outside the finite f64 range"),
Self::TrailingCharacters => formatter.write_str("trailing characters"),
Self::NestingOverflow => formatter.write_str("JSON nesting overflow"),
}
}
}