Skip to main content

oneyaml_core/
code.rs

1//! Stable `Y####` diagnostic codes and the single code registry.
2//!
3//! # Allocation rules
4//!
5//! | Range         | Area                                                        |
6//! |---------------|-------------------------------------------------------------|
7//! | `Y0001–Y0999` | syntax: decode, lex, parse, recovery                        |
8//! | `Y1000–Y1999` | schema & resolution: directives, tags, duplicate keys, settings conflicts |
9//! | `Y2000–Y2999` | limits                                                      |
10//! | `Y3000–Y3999` | serde                                                       |
11//! | `Y9000–Y9999` | reserved; `Y9000` = internal error                   |
12//!
13//! `Y0000` is never used. Codes are allocated **sequentially within their
14//! range**, only in this file: a new code is `max + 1` of its range, added as
15//! a constant plus a matching [`REGISTRY`] entry.
16//!
17//! **The registry is append-only.** A code is never removed, reused,
18//! renumbered, renamed, or given a new meaning. To retire a code, set
19//! [`CodeInfo::deprecated`]; the entry stays forever, so ranges stay
20//! contiguous. `crates/oneyaml-core/tests/codes.snapshot` freezes every
21//! entry (code, name, severity, status, summary) and a test compares the
22//! registry with it, so any change to an existing entry shows up as a snapshot
23//! diff in review.
24
25use crate::Severity;
26
27/// A stable diagnostic code, rendered as `Y####`.
28///
29/// ```
30/// use oneyaml_core::code::{self, CodeRange};
31/// use oneyaml_core::{Code, Severity};
32///
33/// let c = Code::parse("Y1014").unwrap();
34/// assert_eq!(c, code::DUPLICATE_KEY_LAST_WINS);
35/// assert_eq!((c.get(), c.to_string()), (1014, "Y1014".to_owned()));
36/// assert_eq!(c.range(), CodeRange::Schema);
37/// assert_eq!(CodeRange::Schema.bounds(), (1000, 1999));
38/// let info = c.info().unwrap(); // the registry entry
39/// assert_eq!(info.name, "DUPLICATE_KEY_LAST_WINS");
40/// assert_eq!(info.severity, Severity::Warning);
41/// assert!(Code::parse("Y0000").is_none()); // Y0000 is never used
42/// assert_eq!(format!("{c:?}"), "Y1014"); // `Debug` shows the same form
43/// ```
44#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
45pub struct Code(u16);
46
47/// The area a [`Code`] belongs to.
48#[non_exhaustive]
49#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
50pub enum CodeRange {
51    /// `Y0001–Y0999`: decode, lex, parse, recovery.
52    Syntax,
53    /// `Y1000–Y1999`: directives, tags, duplicate keys, settings conflicts.
54    Schema,
55    /// `Y2000–Y2999`: resource limits.
56    Limits,
57    /// `Y3000–Y3999`: serde.
58    Serde,
59    /// `Y9000–Y9999`: reserved. `Y9000` is the internal error.
60    Reserved,
61}
62
63impl CodeRange {
64    /// The inclusive numeric bounds of this range.
65    #[must_use]
66    pub const fn bounds(self) -> (u16, u16) {
67        match self {
68            Self::Syntax => (1, 999),
69            Self::Schema => (1000, 1999),
70            Self::Limits => (2000, 2999),
71            Self::Serde => (3000, 3999),
72            Self::Reserved => (9000, 9999),
73        }
74    }
75}
76
77impl Code {
78    /// Returns the code with number `n`, or `None` if `n` lies outside every
79    /// allocated range (including `0`).
80    ///
81    /// This validates the *range* only and exists for parsing, deserializing,
82    /// and tests. Library code never calls it: it emits only the registered
83    /// constants in this module, which a test enforces by scanning the
84    /// library crates' sources.
85    #[must_use]
86    pub const fn new(n: u16) -> Option<Self> {
87        match n {
88            1..=3999 | 9000..=9999 => Some(Self(n)),
89            _ => None,
90        }
91    }
92
93    /// The numeric value.
94    #[must_use]
95    pub const fn get(self) -> u16 {
96        self.0
97    }
98
99    /// The range this code belongs to.
100    #[must_use]
101    pub const fn range(self) -> CodeRange {
102        match self.0 {
103            0..=999 => CodeRange::Syntax,
104            1000..=1999 => CodeRange::Schema,
105            2000..=2999 => CodeRange::Limits,
106            3000..=3999 => CodeRange::Serde,
107            _ => CodeRange::Reserved,
108        }
109    }
110
111    /// Parses the `Y####` form (exactly four digits).
112    #[must_use]
113    pub fn parse(s: &str) -> Option<Self> {
114        let digits = s.strip_prefix('Y')?;
115        if digits.len() != 4 || !digits.bytes().all(|b| b.is_ascii_digit()) {
116            return None;
117        }
118        Self::new(digits.parse().ok()?)
119    }
120
121    /// The registry entry for this code, if it is allocated.
122    #[must_use]
123    pub fn info(self) -> Option<&'static CodeInfo> {
124        REGISTRY.iter().find(|info| info.code == self)
125    }
126}
127
128impl core::fmt::Display for Code {
129    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
130        write!(f, "Y{:04}", self.0)
131    }
132}
133
134// `Debug` prints the documented `Y####` form so a code inside a derived
135// `Debug` (a `Diagnostic`, an error kind) reads the same as everywhere else.
136impl core::fmt::Debug for Code {
137    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
138        core::fmt::Display::fmt(self, f)
139    }
140}
141
142/// One registry entry.
143#[non_exhaustive]
144#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
145pub struct CodeInfo {
146    /// The code.
147    pub code: Code,
148    /// The constant's name in this module.
149    pub name: &'static str,
150    /// The severity diagnostics with this code are emitted at by default.
151    pub severity: Severity,
152    /// One-line meaning. Never changes once published.
153    pub summary: &'static str,
154    /// Retired: never emitted any more, but never removed or reused.
155    pub deprecated: bool,
156}
157
158// ---------------------------------------------------------------------------
159// Y0001–Y0999: syntax.
160// ---------------------------------------------------------------------------
161
162/// Directives not followed by an explicit `---` (productions 210-211). Error.
163pub const DIRECTIVE_WITHOUT_DOCUMENT: Code = Code(1);
164/// A `%YAML`/`%TAG` directive with missing, extra, or malformed parameters,
165/// or a directive without a name (§6.8). Error.
166pub const MALFORMED_DIRECTIVE: Code = Code(2);
167/// A directive inside a document that was not ended with `...`
168/// (production 211). Error.
169pub const DIRECTIVE_IN_DOCUMENT: Code = Code(3);
170/// Content after a document's root node, or after a `...` marker on the same
171/// line (§9.1). Error.
172pub const CONTENT_AFTER_DOCUMENT: Code = Code(4);
173/// A line indented so that it belongs to no open block collection (§8.2).
174/// Error.
175pub const INVALID_INDENTATION: Code = Code(5);
176/// A block sequence or mapping starting on a line where only a scalar may
177/// start, e.g. after `---`, after an implicit key's `:`, or after properties
178/// (production 200). Error.
179pub const BLOCK_COLLECTION_NOT_ALLOWED: Code = Code(6);
180/// An implicit mapping key spanning more than one line (production 154).
181/// Error.
182pub const MULTILINE_IMPLICIT_KEY: Code = Code(7);
183/// A character that cannot start a node (§7.3.3, production 126). Error.
184pub const INVALID_NODE_START: Code = Code(8);
185/// A quoted scalar not closed before the end of input or a document marker
186/// (§7.3). Error.
187pub const UNTERMINATED_QUOTED_SCALAR: Code = Code(9);
188/// An invalid escape sequence in a double-quoted scalar (§5.7). Error.
189pub const INVALID_ESCAPE: Code = Code(10);
190/// A continuation line of a quoted scalar indented too little
191/// (production 69). Error.
192pub const QUOTED_SCALAR_INDENTATION: Code = Code(11);
193/// An empty or malformed anchor or alias name (production 102). Error.
194pub const INVALID_ANCHOR: Code = Code(12);
195/// A malformed tag property (§6.9.1). Error.
196pub const INVALID_TAG: Code = Code(13);
197/// An alias node with an anchor or tag (production 104). Error.
198pub const ALIAS_WITH_PROPERTIES: Code = Code(14);
199/// A node with two anchors or two tags (production 96). Error.
200pub const DUPLICATE_PROPERTY: Code = Code(15);
201/// A tab character used as block indentation (§6.1). Error.
202pub const TAB_INDENTATION: Code = Code(16);
203/// Unexpected content after a complete node on the same line. Error.
204pub const UNEXPECTED_CONTENT: Code = Code(17);
205/// A block mapping entry without its `:` value indicator (production 192).
206/// Error.
207pub const MISSING_MAPPING_COLON: Code = Code(18);
208/// Syntax this parser version does not implement yet. **Deprecated**: used
209/// only for flow collections and block scalars by pre-release parsers
210/// that did not implement them; never emitted any more. Error.
211pub const UNSUPPORTED_SYNTAX: Code = Code(19);
212/// A malformed block scalar header: an indentation indicator of `0`, a
213/// repeated indicator, or text after the header (production 162). Error.
214pub const INVALID_BLOCK_HEADER: Code = Code(20);
215/// A leading empty line of an auto-indented block scalar has more spaces
216/// than its first content line (§8.1.1.1). Error.
217pub const BLOCK_SCALAR_INDENTATION: Code = Code(21);
218/// A flow collection not closed before the end of input or a document
219/// marker (§7.4). Error.
220pub const UNTERMINATED_FLOW_COLLECTION: Code = Code(22);
221/// Two flow collection entries without a `,` between them (productions 138,
222/// 141). Error.
223pub const MISSING_FLOW_SEPARATOR: Code = Code(23);
224/// A `,`, `]`, or `}` where it cannot appear, e.g. a leading or doubled
225/// `,`, or a mismatched closing bracket (§7.4). Error.
226pub const UNEXPECTED_FLOW_INDICATOR: Code = Code(24);
227/// An implicit key (with its properties and trailing white space) longer
228/// than 1024 characters (productions 154-155, §8.2.2). Error.
229pub const IMPLICIT_KEY_TOO_LONG: Code = Code(25);
230/// A character outside `nb-json` (production 2), i.e. a C0 control other
231/// than tab, inside a quoted scalar (§7.3). Error.
232pub const INVALID_QUOTED_CHARACTER: Code = Code(26);
233/// A `: ` inside an unquoted (plain) scalar that is the value of a block
234/// mapping entry, on the entry's line, e.g. `title: The farm: a banana
235/// story`. The `: ` would start a nested mapping, which cannot begin on
236/// that line (production 200). A more specific diagnosis of the case that
237/// earlier parser versions reported as [`BLOCK_COLLECTION_NOT_ALLOWED`];
238/// carries a machine-applicable fix that quotes the value. Error.
239pub const INLINE_COLON_IN_PLAIN_VALUE: Code = Code(27);
240/// The input of [`INLINE_COLON_IN_PLAIN_VALUE`] was read as one string
241/// because the `lenient_inline_colons` setting is on. Warning.
242pub const LENIENT_INLINE_COLON: Code = Code(28);
243/// Bytes that are not valid UTF-8 in input decoded as UTF-8 (§5.2). Each
244/// maximal invalid run is replaced by U+FFFD. Error.
245pub const INVALID_UTF8: Code = Code(29);
246/// Input decoded as UTF-16 with an unpaired surrogate or a trailing odd byte
247/// (§5.2). Each maximal invalid run is replaced by U+FFFD. Error.
248pub const INVALID_UTF16: Code = Code(30);
249/// Input decoded as UTF-32 with a code unit that is not a Unicode scalar
250/// value, or trailing bytes that do not form a code unit (§5.2). Each
251/// maximal invalid run is replaced by U+FFFD. Error.
252pub const INVALID_UTF32: Code = Code(31);
253/// A byte order mark (U+FEFF) where YAML 1.2.2 does not allow one (§5.2,
254/// productions 27, 202, 211). A mark is allowed only at the very start of
255/// a line that starts a document prefix: the start of the stream, after a
256/// `...` line, or after a complete document (whose end it then marks; only
257/// a document starting with `---` may follow it). It is also allowed
258/// inside quoted scalars, whose `nb-json` characters include it.
259///
260/// Reported for a mark after white space or other text on a line, after
261/// directives, inside a document's structure, and inside comment text or
262/// block scalar content (`nb-char` excludes it). Recovery: a mark between
263/// tokens is skipped, and columns are counted as if it were absent; a
264/// mark inside a comment or a block scalar stays in that token (the
265/// diagnostic's span is the mark; the block scalar's value keeps it). A
266/// mark ends a plain scalar (it is no `ns-char`), so text right after it
267/// is read as new content (usually a further error). A mark between an
268/// implicit key and its `:` is skipped like any other.
269///
270/// When a mark makes another construct fail (a key without `:`, a block
271/// scalar header, a block scalar's offending line, a directive, or any
272/// other syntax error whose offending line holds a mark before any quote
273/// character), this code is reported too, right after that construct's
274/// error (whose label comes first on the line), so the construct's error
275/// is the one a strict parse fails with.
276/// Not covered: a mark after a quote character on such a line (it may be
277/// inside a quoted scalar, where it is allowed), which then shows only
278/// through the other error.
279///
280/// A mark at the start of a line after a complete document is valid and
281/// ends that document; if what follows is not a document starting with
282/// `---`, the error is `CONTENT_AFTER_DOCUMENT` (or
283/// `DIRECTIVE_IN_DOCUMENT`), with a label on that mark, and the content is
284/// read as the next document. Error.
285pub const MISPLACED_BYTE_ORDER_MARK: Code = Code(32);
286
287// ---------------------------------------------------------------------------
288// Y1000–Y1999: schema & resolution.
289// ---------------------------------------------------------------------------
290
291/// A `%YAML 1.1` directive. By default the document is parsed as
292/// YAML 1.2; under `honor_version_directive` the YAML 1.1 preset applies.
293/// The message says which. Warning.
294pub const YAML_VERSION_1_1: Code = Code(1000);
295/// `%YAML 1.x` with `x > 2`: processed as YAML 1.2. Warning.
296pub const YAML_VERSION_NEWER_MINOR: Code = Code(1001);
297/// `%YAML` with a major version other than 1. Error.
298pub const YAML_VERSION_UNSUPPORTED_MAJOR: Code = Code(1002);
299/// A reserved directive (`%NAME` other than `YAML`/`TAG`) was ignored
300/// (YAML 1.2.2 §6.8). Warning.
301pub const RESERVED_DIRECTIVE_IGNORED: Code = Code(1003);
302/// A second `%YAML` directive, or a `%TAG` handle declared twice, for one
303/// document (§6.8.1, §6.8.2). Error.
304pub const DUPLICATE_DIRECTIVE: Code = Code(1004);
305/// A named tag handle (`!e!`) not declared by a `%TAG` directive of its
306/// document (§6.8.2.1). Error.
307pub const UNDEFINED_TAG_HANDLE: Code = Code(1005);
308/// A `%YAML 1.0` directive. By default the document is parsed as
309/// YAML 1.2; under `honor_version_directive` the YAML 1.1 preset applies,
310/// as for 1.1. The message says which. Warning.
311pub const YAML_VERSION_1_0: Code = Code(1006);
312/// An explicitly tagged scalar whose text is not a valid form of its core
313/// tag under the active schema, e.g. `!!int twelve` (§10). Error.
314pub const INVALID_SCALAR_FOR_TAG: Code = Code(1007);
315/// A collection tag (`!!seq`, `!!map`) on a scalar node (§10). Error.
316pub const COLLECTION_TAG_ON_SCALAR: Code = Code(1008);
317/// A core tag that does not match the collection it is on, e.g. `!!str [a]`
318/// or `!!map [a]` (§10). Error.
319pub const WRONG_TAG_ON_COLLECTION: Code = Code(1009);
320/// JSON schema: a plain scalar matching none of the JSON patterns
321/// (§10.2.2). Error.
322pub const JSON_UNMATCHED_SCALAR: Code = Code(1010);
323/// A combination of settings in which a setting has no effect.
324/// Warning.
325pub const MEANINGLESS_SETTINGS: Code = Code(1011);
326/// A combination of settings that would produce incorrect results.
327/// Error.
328pub const INCORRECT_SETTINGS: Code = Code(1012);
329/// A mapping key that is equal to an earlier key of the same mapping
330/// (§3.2.1.1). Error.
331pub const DUPLICATE_KEY: Code = Code(1013);
332/// A duplicate mapping key tolerated by `DuplicateKeys::LastWins`: the
333/// last value wins; a secondary label points at the first definition.
334/// Warning.
335pub const DUPLICATE_KEY_LAST_WINS: Code = Code(1014);
336/// An alias whose anchor is not defined earlier in the document, or whose
337/// anchored node is not complete yet (a recursive alias) (§7.1). Error.
338pub const UNDEFINED_ALIAS: Code = Code(1015);
339/// A merge key (`<<`, with `merge_keys` on) whose value is not a mapping or
340/// a sequence of mappings (yaml.org/type/merge). Error.
341pub const INVALID_MERGE_VALUE: Code = Code(1016);
342/// A setting this version cannot honour, e.g. `Emit::line_width(Some(_))`
343/// (line wrapping is not implemented): refused rather than silently ignored.
344/// Error.
345pub const UNSUPPORTED_SETTING: Code = Code(1017);
346
347// ---------------------------------------------------------------------------
348// Y2000–Y2999: limits. The help of every limit diagnostic names the
349// exact setting to raise and its current value.
350// ---------------------------------------------------------------------------
351
352/// Collections nested deeper than `Limits::max_depth` (block and flow
353/// nesting combined). Error.
354pub const DEPTH_LIMIT_EXCEEDED: Code = Code(2000);
355/// A scalar value longer than `Limits::max_scalar_len` bytes. Error.
356pub const SCALAR_TOO_LONG: Code = Code(2001);
357/// Input larger than `Limits::max_document_size` bytes. Error.
358pub const DOCUMENT_TOO_LARGE: Code = Code(2002);
359/// A stream with more documents than `Limits::max_documents`. Error.
360pub const TOO_MANY_DOCUMENTS: Code = Code(2003);
361/// Alias expansion (including merge keys through aliases) produced more
362/// nodes than `Limits::alias_budget` allows; `Value` and serde only.
363/// Error.
364pub const ALIAS_BUDGET_EXCEEDED: Code = Code(2004);
365/// A `Value` consumed by the library (serialized, or deserialized into a
366/// type) nests deeper than `Limits::max_depth`. Error.
367///
368/// Reported by the serializer (`to_string`, `to_writer`, `Serializer`),
369/// which emits `Value`s iteratively; `Document` insertion reports it too.
370pub const VALUE_DEPTH_EXCEEDED: Code = Code(2005);
371/// Input with more elements (nodes, tokens, trivia) than a syntax tree's
372/// `u32` indices can address; the rest of the input is kept as skipped
373/// bytes, so the tree still round-trips. Error.
374pub const TREE_CAPACITY_EXCEEDED: Code = Code(2006);
375/// Deserializing into a *recursive user type* (e.g. `struct Tree(Vec<Tree>)`)
376/// nested deeper than `Limits::max_typed_depth`. serde's visitor
377/// model recurses once per level of such a type, so this bounds the native
378/// stack; `Value` targets are iterative and bounded only by `max_depth`.
379/// Also `Serialize for Value` into third-party serializers, which recurse
380/// once per level: values deeper than the default `max_typed_depth` (128)
381/// are refused. Error.
382pub const TYPED_DEPTH_EXCEEDED: Code = Code(2007);
383/// A `Value` converted to a `serde_json::Value` (feature `serde_json`)
384/// nests deeper than 128 levels. The bound is fixed, whatever `Limits`
385/// say: serde_json's recursion limit, and its `Value` drops recursively.
386/// Error.
387pub const JSON_DEPTH_EXCEEDED: Code = Code(2008);
388/// Alias expansion materialised more bytes than `Limits::alias_byte_budget`
389/// allows: scalar text, keys, and tags copied by expansion (merge keys
390/// through aliases included), each tag use's `%TAG` prefix, and `%TAG` tags
391/// joined into enum variant names; `Value` and serde only.
392/// Error.
393pub const ALIAS_BYTE_BUDGET_EXCEEDED: Code = Code(2009);
394/// More diagnostics than `Limits::max_diagnostics` were found: the list
395/// ends with this one summary, "N more diagnostics suppressed", located
396/// where the first suppressed one was. Not a problem of the
397/// input by itself: a warning, so a strict parse still fails only at an
398/// error. Warning.
399pub const DIAGNOSTICS_SUPPRESSED: Code = Code(2010);
400
401// ---------------------------------------------------------------------------
402// Y3000–Y3999: serde.
403// ---------------------------------------------------------------------------
404
405/// A node's type does not fit the target type, e.g. a mapping for a
406/// `String`, or a quoted scalar for a number. Error.
407pub const TYPE_MISMATCH: Code = Code(3000);
408/// An integer outside the range of the target integer type. Error.
409pub const INTEGER_OUT_OF_RANGE: Code = Code(3001);
410/// A mapping without a field the target struct requires. Error.
411pub const MISSING_FIELD: Code = Code(3002);
412/// An enum variant name the target enum does not have. Error.
413pub const UNKNOWN_VARIANT: Code = Code(3003);
414/// A custom error raised by the target type's `Deserialize` impl. Error.
415pub const CUSTOM: Code = Code(3004);
416/// A mapping key the target struct does not accept
417/// (`deny_unknown_fields`). Error.
418pub const UNKNOWN_FIELD: Code = Code(3005);
419/// A value of the right type that the target type rejects. Error.
420pub const INVALID_VALUE: Code = Code(3006);
421/// A sequence or mapping with a length the target type does not accept,
422/// e.g. a tuple of the wrong arity. Error.
423pub const INVALID_LENGTH: Code = Code(3007);
424/// A struct field given twice (as two keys that differ in YAML but name the
425/// same field). Error.
426pub const DUPLICATE_FIELD: Code = Code(3008);
427/// A single-document entry point (`from_str`, …) given a stream with more
428/// than one document; use the multi-document `Deserializer`. Error.
429pub const MULTIPLE_DOCUMENTS: Code = Code(3009);
430/// Reading the input failed (`from_reader`). Error.
431pub const IO_ERROR: Code = Code(3010);
432/// A document of a multi-document `Deserializer` used after the stream
433/// moved past it, or twice. Error.
434pub const DOCUMENT_UNAVAILABLE: Code = Code(3011);
435/// A custom error raised by the value's `Serialize` impl
436/// (`serde::ser::Error::custom`). Error.
437pub const SERIALIZE_CUSTOM: Code = Code(3012);
438/// Writing the output failed (`to_writer`, `Serializer`). Error.
439pub const WRITE_ERROR: Code = Code(3013);
440/// A mapping being serialized has two equal keys (e.g. a struct field and
441/// a flattened map entry with the same name); YAML 1.2 requires unique
442/// keys, so the output would not read back. Error.
443pub const SERIALIZE_DUPLICATE_KEY: Code = Code(3014);
444/// A value YAML output cannot represent so that it reads back as the same
445/// value: an empty or malformed tag, a tag on a tagged value, or a custom
446/// tag on a non-string scalar (a custom-tagged scalar always reads back as
447/// a string). Error.
448pub const UNREPRESENTABLE_VALUE: Code = Code(3015);
449/// A `Serialize` impl drove the serializer out of order, e.g. a map value
450/// without its key, or a key without a value. Error.
451pub const INVALID_SERIALIZE_CALLS: Code = Code(3016);
452/// A `Value` was serialized or deserialized by oneyaml's own `Serializer`
453/// or `Deserializer` from a thread-local destructor after oneyaml's own
454/// thread-local storage on that thread was destroyed: the value cannot be
455/// handed over between the `Value` and oneyaml (an error, never a
456/// panic or a wrong or lossy value). Serialize or deserialize before the
457/// thread shuts down (or deserialize into a typed target). Error.
458pub const THREAD_LOCAL_UNAVAILABLE: Code = Code(3017);
459/// A `Deserialize` impl ignored an error that oneyaml returned for a
460/// sequence or mapping it had started to read, then went on reading:
461/// the rest of that node was never read, so nothing after it can be read
462/// in step. Reported at the next read, or at the end of the
463/// document, located at the node whose error was ignored; the same for
464/// `from_str` and `from_value`. Propagate the error instead (`?`).
465/// Error.
466pub const READ_AFTER_IGNORED_ERROR: Code = Code(3018);
467
468// ---------------------------------------------------------------------------
469// Y9000–Y9999: reserved.
470// ---------------------------------------------------------------------------
471
472/// An internal invariant of the library was violated: the
473/// release-build outcome of a `debug_assert!`, reported in-band (an error
474/// event, or `Err(Internal)`) instead of a silently wrong result. Always a
475/// bug in oneyaml; please report it with the input. Error.
476pub const INTERNAL_ERROR: Code = Code(9000);
477/// A caller-supplied event stream violates the event contract, e.g. one
478/// passed to `SyntaxTree::from_events` after filtering or reordering the
479/// parser's events. API misuse, not a bug in oneyaml: the result is built
480/// without panicking but may not round-trip. Error.
481pub const INVALID_EVENT_STREAM: Code = Code(9001);
482
483macro_rules! error_entry {
484    ($code:ident, $summary:literal) => {
485        CodeInfo {
486            code: $code,
487            name: stringify!($code),
488            severity: Severity::Error,
489            summary: $summary,
490            deprecated: false,
491        }
492    };
493}
494
495macro_rules! warning_entry {
496    ($code:ident, $summary:literal) => {
497        CodeInfo {
498            code: $code,
499            name: stringify!($code),
500            severity: Severity::Warning,
501            summary: $summary,
502            deprecated: false,
503        }
504    };
505}
506
507/// Every allocated code, in numeric order.
508pub const REGISTRY: &[CodeInfo] = &[
509    error_entry!(
510        DIRECTIVE_WITHOUT_DOCUMENT,
511        "directives not followed by an explicit document start"
512    ),
513    error_entry!(MALFORMED_DIRECTIVE, "malformed directive"),
514    error_entry!(
515        DIRECTIVE_IN_DOCUMENT,
516        "directive inside a document not ended with `...`"
517    ),
518    error_entry!(
519        CONTENT_AFTER_DOCUMENT,
520        "content after the document's root node or end marker"
521    ),
522    error_entry!(INVALID_INDENTATION, "content at an invalid indentation"),
523    error_entry!(
524        BLOCK_COLLECTION_NOT_ALLOWED,
525        "block collection cannot start on this line"
526    ),
527    error_entry!(
528        MULTILINE_IMPLICIT_KEY,
529        "implicit mapping key spans more than one line"
530    ),
531    error_entry!(INVALID_NODE_START, "character cannot start a node"),
532    error_entry!(UNTERMINATED_QUOTED_SCALAR, "quoted scalar not closed"),
533    error_entry!(
534        INVALID_ESCAPE,
535        "invalid escape sequence in a double-quoted scalar"
536    ),
537    error_entry!(
538        QUOTED_SCALAR_INDENTATION,
539        "quoted scalar continuation line not indented enough"
540    ),
541    error_entry!(INVALID_ANCHOR, "empty or malformed anchor or alias name"),
542    error_entry!(INVALID_TAG, "malformed tag"),
543    error_entry!(ALIAS_WITH_PROPERTIES, "alias node with an anchor or tag"),
544    error_entry!(DUPLICATE_PROPERTY, "node with more than one anchor or tag"),
545    error_entry!(TAB_INDENTATION, "tab character used as block indentation"),
546    error_entry!(
547        UNEXPECTED_CONTENT,
548        "unexpected content after a node on the same line"
549    ),
550    error_entry!(MISSING_MAPPING_COLON, "block mapping entry without `:`"),
551    CodeInfo {
552        code: UNSUPPORTED_SYNTAX,
553        name: "UNSUPPORTED_SYNTAX",
554        severity: Severity::Error,
555        summary: "syntax not yet supported by this parser version",
556        deprecated: true,
557    },
558    error_entry!(INVALID_BLOCK_HEADER, "malformed block scalar header"),
559    error_entry!(
560        BLOCK_SCALAR_INDENTATION,
561        "block scalar leading empty line indented more than its content"
562    ),
563    error_entry!(UNTERMINATED_FLOW_COLLECTION, "flow collection not closed"),
564    error_entry!(
565        MISSING_FLOW_SEPARATOR,
566        "flow collection entries not separated by `,`"
567    ),
568    error_entry!(
569        UNEXPECTED_FLOW_INDICATOR,
570        "flow indicator where it cannot appear"
571    ),
572    error_entry!(
573        IMPLICIT_KEY_TOO_LONG,
574        "implicit key longer than 1024 characters"
575    ),
576    error_entry!(
577        INVALID_QUOTED_CHARACTER,
578        "character not allowed in a quoted scalar"
579    ),
580    error_entry!(
581        INLINE_COLON_IN_PLAIN_VALUE,
582        "`: ` inside an unquoted block mapping value"
583    ),
584    warning_entry!(
585        LENIENT_INLINE_COLON,
586        "`: ` inside an unquoted block mapping value read as text (lenient_inline_colons)"
587    ),
588    error_entry!(INVALID_UTF8, "invalid UTF-8 in the input"),
589    error_entry!(INVALID_UTF16, "invalid UTF-16 in the input"),
590    error_entry!(INVALID_UTF32, "invalid UTF-32 in the input"),
591    error_entry!(
592        MISPLACED_BYTE_ORDER_MARK,
593        "byte order mark where no document prefix may start"
594    ),
595    CodeInfo {
596        code: YAML_VERSION_1_1,
597        name: "YAML_VERSION_1_1",
598        severity: Severity::Warning,
599        summary: "%YAML 1.1 directive",
600        deprecated: false,
601    },
602    CodeInfo {
603        code: YAML_VERSION_NEWER_MINOR,
604        name: "YAML_VERSION_NEWER_MINOR",
605        severity: Severity::Warning,
606        summary: "%YAML 1.x (x > 2) document processed as YAML 1.2",
607        deprecated: false,
608    },
609    CodeInfo {
610        code: YAML_VERSION_UNSUPPORTED_MAJOR,
611        name: "YAML_VERSION_UNSUPPORTED_MAJOR",
612        severity: Severity::Error,
613        summary: "unsupported %YAML major version",
614        deprecated: false,
615    },
616    CodeInfo {
617        code: RESERVED_DIRECTIVE_IGNORED,
618        name: "RESERVED_DIRECTIVE_IGNORED",
619        severity: Severity::Warning,
620        summary: "reserved directive ignored",
621        deprecated: false,
622    },
623    error_entry!(
624        DUPLICATE_DIRECTIVE,
625        "duplicate %YAML directive or %TAG handle"
626    ),
627    error_entry!(
628        UNDEFINED_TAG_HANDLE,
629        "tag handle not declared by a %TAG directive"
630    ),
631    CodeInfo {
632        code: YAML_VERSION_1_0,
633        name: "YAML_VERSION_1_0",
634        severity: Severity::Warning,
635        summary: "%YAML 1.0 directive",
636        deprecated: false,
637    },
638    error_entry!(
639        INVALID_SCALAR_FOR_TAG,
640        "scalar is not a valid form of its tag"
641    ),
642    error_entry!(COLLECTION_TAG_ON_SCALAR, "collection tag on a scalar"),
643    error_entry!(
644        WRONG_TAG_ON_COLLECTION,
645        "tag does not match the collection kind"
646    ),
647    error_entry!(
648        JSON_UNMATCHED_SCALAR,
649        "plain scalar matches no JSON schema type"
650    ),
651    warning_entry!(
652        MEANINGLESS_SETTINGS,
653        "setting has no effect in this combination"
654    ),
655    error_entry!(
656        INCORRECT_SETTINGS,
657        "settings combination gives incorrect results"
658    ),
659    error_entry!(DUPLICATE_KEY, "duplicate mapping key"),
660    warning_entry!(
661        DUPLICATE_KEY_LAST_WINS,
662        "duplicate mapping key, last value wins (DuplicateKeys::LastWins)"
663    ),
664    error_entry!(
665        UNDEFINED_ALIAS,
666        "alias to an undefined or incomplete anchor"
667    ),
668    error_entry!(
669        INVALID_MERGE_VALUE,
670        "merge key value is not a mapping or a sequence of mappings"
671    ),
672    error_entry!(UNSUPPORTED_SETTING, "setting not supported by this version"),
673    error_entry!(
674        DEPTH_LIMIT_EXCEEDED,
675        "nesting deeper than Limits::max_depth"
676    ),
677    error_entry!(SCALAR_TOO_LONG, "scalar longer than Limits::max_scalar_len"),
678    error_entry!(
679        DOCUMENT_TOO_LARGE,
680        "input larger than Limits::max_document_size"
681    ),
682    error_entry!(
683        TOO_MANY_DOCUMENTS,
684        "more documents than Limits::max_documents"
685    ),
686    error_entry!(
687        ALIAS_BUDGET_EXCEEDED,
688        "alias expansion larger than Limits::alias_budget"
689    ),
690    error_entry!(
691        VALUE_DEPTH_EXCEEDED,
692        "Value nests deeper than Limits::max_depth"
693    ),
694    error_entry!(
695        TREE_CAPACITY_EXCEEDED,
696        "input has more elements than a syntax tree can index"
697    ),
698    error_entry!(
699        TYPED_DEPTH_EXCEEDED,
700        "recursive target type nests deeper than Limits::max_typed_depth"
701    ),
702    error_entry!(
703        JSON_DEPTH_EXCEEDED,
704        "JSON output nests deeper than 128 levels"
705    ),
706    error_entry!(
707        ALIAS_BYTE_BUDGET_EXCEEDED,
708        "alias expansion larger than Limits::alias_byte_budget"
709    ),
710    warning_entry!(
711        DIAGNOSTICS_SUPPRESSED,
712        "more diagnostics than Limits::max_diagnostics; the rest are counted, not kept"
713    ),
714    error_entry!(TYPE_MISMATCH, "node type does not fit the target type"),
715    error_entry!(
716        INTEGER_OUT_OF_RANGE,
717        "integer out of range for the target type"
718    ),
719    error_entry!(MISSING_FIELD, "missing struct field"),
720    error_entry!(UNKNOWN_VARIANT, "unknown enum variant"),
721    error_entry!(CUSTOM, "error raised by the target type"),
722    error_entry!(UNKNOWN_FIELD, "unknown struct field"),
723    error_entry!(INVALID_VALUE, "value rejected by the target type"),
724    error_entry!(
725        INVALID_LENGTH,
726        "collection length rejected by the target type"
727    ),
728    error_entry!(DUPLICATE_FIELD, "struct field given twice"),
729    error_entry!(
730        MULTIPLE_DOCUMENTS,
731        "more than one document for a single-document entry point"
732    ),
733    error_entry!(IO_ERROR, "I/O error while reading the input"),
734    error_entry!(
735        DOCUMENT_UNAVAILABLE,
736        "document no longer available from the multi-document deserializer"
737    ),
738    error_entry!(
739        SERIALIZE_CUSTOM,
740        "error raised by the value's Serialize impl"
741    ),
742    error_entry!(WRITE_ERROR, "I/O error while writing the output"),
743    error_entry!(
744        SERIALIZE_DUPLICATE_KEY,
745        "serialized mapping has two equal keys"
746    ),
747    error_entry!(
748        UNREPRESENTABLE_VALUE,
749        "value cannot be represented in YAML output"
750    ),
751    error_entry!(
752        INVALID_SERIALIZE_CALLS,
753        "Serialize impl drove the serializer out of order"
754    ),
755    error_entry!(
756        THREAD_LOCAL_UNAVAILABLE,
757        "Value serialized or deserialized during thread-local storage teardown"
758    ),
759    error_entry!(
760        READ_AFTER_IGNORED_ERROR,
761        "deserializer used after one of its errors was ignored"
762    ),
763    error_entry!(INTERNAL_ERROR, "internal error (a bug in oneyaml)"),
764    error_entry!(
765        INVALID_EVENT_STREAM,
766        "caller-supplied event stream violates the event contract"
767    ),
768];
769
770#[cfg(test)]
771mod tests {
772    #![allow(clippy::unwrap_used, clippy::indexing_slicing)]
773
774    use super::*;
775
776    const RANGES: [CodeRange; 5] = [
777        CodeRange::Syntax,
778        CodeRange::Schema,
779        CodeRange::Limits,
780        CodeRange::Serde,
781        CodeRange::Reserved,
782    ];
783
784    #[test]
785    fn debug_matches_display() {
786        assert_eq!(format!("{INVALID_INDENTATION:?}"), "Y0005");
787        assert_eq!(format!("{:?}", Some(INVALID_INDENTATION)), "Some(Y0005)");
788        for info in REGISTRY {
789            assert_eq!(format!("{:?}", info.code), info.code.to_string());
790        }
791    }
792
793    #[test]
794    fn registry_is_sequential_within_each_range() {
795        for range in RANGES {
796            let (lo, _) = range.bounds();
797            let codes: Vec<u16> = REGISTRY
798                .iter()
799                .filter(|i| i.code.range() == range)
800                .map(|i| i.code.get())
801                .collect();
802            let expected: Vec<u16> = (0..codes.len())
803                .map(|k| lo.max(1).checked_add(u16::try_from(k).unwrap()).unwrap())
804                .collect();
805            // Retired codes are flagged, never removed, so every range stays
806            // contiguous: a new code is always max + 1.
807            assert_eq!(codes, expected, "{range:?} codes are contiguous");
808        }
809    }
810
811    #[test]
812    fn registry_is_sorted_unique_and_named() {
813        for pair in REGISTRY.windows(2) {
814            assert!(pair[0].code < pair[1].code);
815        }
816        for info in REGISTRY {
817            assert_eq!(info.code.info(), Some(info));
818            assert!(!info.summary.is_empty());
819            assert!(!info.name.is_empty());
820        }
821    }
822
823    #[test]
824    fn range_validation() {
825        assert_eq!(Code::new(0), None);
826        assert_eq!(Code::new(1).unwrap().range(), CodeRange::Syntax);
827        assert_eq!(Code::new(999).unwrap().range(), CodeRange::Syntax);
828        assert_eq!(Code::new(1000).unwrap().range(), CodeRange::Schema);
829        assert_eq!(Code::new(2500).unwrap().range(), CodeRange::Limits);
830        assert_eq!(Code::new(3999).unwrap().range(), CodeRange::Serde);
831        assert_eq!(Code::new(4000), None);
832        assert_eq!(Code::new(8999), None);
833        assert_eq!(Code::new(9000).unwrap().range(), CodeRange::Reserved);
834        assert_eq!(Code::new(10000), None);
835        for range in RANGES {
836            let (lo, hi) = range.bounds();
837            assert!(lo <= hi);
838            assert_eq!(Code::new(hi).unwrap().range(), range);
839        }
840    }
841
842    #[test]
843    fn display_and_parse_round_trip() {
844        assert_eq!(YAML_VERSION_1_1.to_string(), "Y1000");
845        assert_eq!(Code::new(7).unwrap().to_string(), "Y0007");
846        assert_eq!(Code::parse("Y1003"), Some(RESERVED_DIRECTIVE_IGNORED));
847        assert_eq!(Code::parse("Y0007"), Code::new(7));
848        for bad in [
849            "", "Y", "Y0000", "Y123", "Y12345", "X1000", "Y10a0", "Y5000",
850        ] {
851            assert_eq!(Code::parse(bad), None, "{bad}");
852        }
853        assert_eq!(Code::new(999).unwrap().info(), None);
854    }
855}