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}