use crate::Severity;
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Code(u16);
#[non_exhaustive]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum CodeRange {
Syntax,
Schema,
Limits,
Serde,
Reserved,
}
impl CodeRange {
#[must_use]
pub const fn bounds(self) -> (u16, u16) {
match self {
Self::Syntax => (1, 999),
Self::Schema => (1000, 1999),
Self::Limits => (2000, 2999),
Self::Serde => (3000, 3999),
Self::Reserved => (9000, 9999),
}
}
}
impl Code {
#[must_use]
pub const fn new(n: u16) -> Option<Self> {
match n {
1..=3999 | 9000..=9999 => Some(Self(n)),
_ => None,
}
}
#[must_use]
pub const fn get(self) -> u16 {
self.0
}
#[must_use]
pub const fn range(self) -> CodeRange {
match self.0 {
0..=999 => CodeRange::Syntax,
1000..=1999 => CodeRange::Schema,
2000..=2999 => CodeRange::Limits,
3000..=3999 => CodeRange::Serde,
_ => CodeRange::Reserved,
}
}
#[must_use]
pub fn parse(s: &str) -> Option<Self> {
let digits = s.strip_prefix('Y')?;
if digits.len() != 4 || !digits.bytes().all(|b| b.is_ascii_digit()) {
return None;
}
Self::new(digits.parse().ok()?)
}
#[must_use]
pub fn info(self) -> Option<&'static CodeInfo> {
REGISTRY.iter().find(|info| info.code == self)
}
}
impl core::fmt::Display for Code {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "Y{:04}", self.0)
}
}
#[non_exhaustive]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct CodeInfo {
pub code: Code,
pub name: &'static str,
pub severity: Severity,
pub summary: &'static str,
pub deprecated: bool,
}
pub const DIRECTIVE_WITHOUT_DOCUMENT: Code = Code(1);
pub const MALFORMED_DIRECTIVE: Code = Code(2);
pub const DIRECTIVE_IN_DOCUMENT: Code = Code(3);
pub const CONTENT_AFTER_DOCUMENT: Code = Code(4);
pub const INVALID_INDENTATION: Code = Code(5);
pub const BLOCK_COLLECTION_NOT_ALLOWED: Code = Code(6);
pub const MULTILINE_IMPLICIT_KEY: Code = Code(7);
pub const INVALID_NODE_START: Code = Code(8);
pub const UNTERMINATED_QUOTED_SCALAR: Code = Code(9);
pub const INVALID_ESCAPE: Code = Code(10);
pub const QUOTED_SCALAR_INDENTATION: Code = Code(11);
pub const INVALID_ANCHOR: Code = Code(12);
pub const INVALID_TAG: Code = Code(13);
pub const ALIAS_WITH_PROPERTIES: Code = Code(14);
pub const DUPLICATE_PROPERTY: Code = Code(15);
pub const TAB_INDENTATION: Code = Code(16);
pub const UNEXPECTED_CONTENT: Code = Code(17);
pub const MISSING_MAPPING_COLON: Code = Code(18);
pub const UNSUPPORTED_SYNTAX: Code = Code(19);
pub const INVALID_BLOCK_HEADER: Code = Code(20);
pub const BLOCK_SCALAR_INDENTATION: Code = Code(21);
pub const UNTERMINATED_FLOW_COLLECTION: Code = Code(22);
pub const MISSING_FLOW_SEPARATOR: Code = Code(23);
pub const UNEXPECTED_FLOW_INDICATOR: Code = Code(24);
pub const IMPLICIT_KEY_TOO_LONG: Code = Code(25);
pub const INVALID_QUOTED_CHARACTER: Code = Code(26);
pub const INLINE_COLON_IN_PLAIN_VALUE: Code = Code(27);
pub const LENIENT_INLINE_COLON: Code = Code(28);
pub const INVALID_UTF8: Code = Code(29);
pub const INVALID_UTF16: Code = Code(30);
pub const INVALID_UTF32: Code = Code(31);
pub const MISPLACED_BYTE_ORDER_MARK: Code = Code(32);
pub const YAML_VERSION_1_1: Code = Code(1000);
pub const YAML_VERSION_NEWER_MINOR: Code = Code(1001);
pub const YAML_VERSION_UNSUPPORTED_MAJOR: Code = Code(1002);
pub const RESERVED_DIRECTIVE_IGNORED: Code = Code(1003);
pub const DUPLICATE_DIRECTIVE: Code = Code(1004);
pub const UNDEFINED_TAG_HANDLE: Code = Code(1005);
pub const YAML_VERSION_1_0: Code = Code(1006);
pub const INVALID_SCALAR_FOR_TAG: Code = Code(1007);
pub const COLLECTION_TAG_ON_SCALAR: Code = Code(1008);
pub const WRONG_TAG_ON_COLLECTION: Code = Code(1009);
pub const JSON_UNMATCHED_SCALAR: Code = Code(1010);
pub const MEANINGLESS_SETTINGS: Code = Code(1011);
pub const INCORRECT_SETTINGS: Code = Code(1012);
pub const DUPLICATE_KEY: Code = Code(1013);
pub const DUPLICATE_KEY_LAST_WINS: Code = Code(1014);
pub const UNDEFINED_ALIAS: Code = Code(1015);
pub const INVALID_MERGE_VALUE: Code = Code(1016);
pub const UNSUPPORTED_SETTING: Code = Code(1017);
pub const DEPTH_LIMIT_EXCEEDED: Code = Code(2000);
pub const SCALAR_TOO_LONG: Code = Code(2001);
pub const DOCUMENT_TOO_LARGE: Code = Code(2002);
pub const TOO_MANY_DOCUMENTS: Code = Code(2003);
pub const ALIAS_BUDGET_EXCEEDED: Code = Code(2004);
pub const VALUE_DEPTH_EXCEEDED: Code = Code(2005);
pub const TREE_CAPACITY_EXCEEDED: Code = Code(2006);
pub const TYPED_DEPTH_EXCEEDED: Code = Code(2007);
pub const JSON_DEPTH_EXCEEDED: Code = Code(2008);
pub const ALIAS_BYTE_BUDGET_EXCEEDED: Code = Code(2009);
pub const DIAGNOSTICS_SUPPRESSED: Code = Code(2010);
pub const TYPE_MISMATCH: Code = Code(3000);
pub const INTEGER_OUT_OF_RANGE: Code = Code(3001);
pub const MISSING_FIELD: Code = Code(3002);
pub const UNKNOWN_VARIANT: Code = Code(3003);
pub const CUSTOM: Code = Code(3004);
pub const UNKNOWN_FIELD: Code = Code(3005);
pub const INVALID_VALUE: Code = Code(3006);
pub const INVALID_LENGTH: Code = Code(3007);
pub const DUPLICATE_FIELD: Code = Code(3008);
pub const MULTIPLE_DOCUMENTS: Code = Code(3009);
pub const IO_ERROR: Code = Code(3010);
pub const DOCUMENT_UNAVAILABLE: Code = Code(3011);
pub const SERIALIZE_CUSTOM: Code = Code(3012);
pub const WRITE_ERROR: Code = Code(3013);
pub const SERIALIZE_DUPLICATE_KEY: Code = Code(3014);
pub const UNREPRESENTABLE_VALUE: Code = Code(3015);
pub const INVALID_SERIALIZE_CALLS: Code = Code(3016);
pub const THREAD_LOCAL_UNAVAILABLE: Code = Code(3017);
pub const READ_AFTER_IGNORED_ERROR: Code = Code(3018);
pub const INTERNAL_ERROR: Code = Code(9000);
pub const INVALID_EVENT_STREAM: Code = Code(9001);
macro_rules! error_entry {
($code:ident, $summary:literal) => {
CodeInfo {
code: $code,
name: stringify!($code),
severity: Severity::Error,
summary: $summary,
deprecated: false,
}
};
}
macro_rules! warning_entry {
($code:ident, $summary:literal) => {
CodeInfo {
code: $code,
name: stringify!($code),
severity: Severity::Warning,
summary: $summary,
deprecated: false,
}
};
}
pub const REGISTRY: &[CodeInfo] = &[
error_entry!(
DIRECTIVE_WITHOUT_DOCUMENT,
"directives not followed by an explicit document start"
),
error_entry!(MALFORMED_DIRECTIVE, "malformed directive"),
error_entry!(
DIRECTIVE_IN_DOCUMENT,
"directive inside a document not ended with `...`"
),
error_entry!(
CONTENT_AFTER_DOCUMENT,
"content after the document's root node or end marker"
),
error_entry!(INVALID_INDENTATION, "content at an invalid indentation"),
error_entry!(
BLOCK_COLLECTION_NOT_ALLOWED,
"block collection cannot start on this line"
),
error_entry!(
MULTILINE_IMPLICIT_KEY,
"implicit mapping key spans more than one line"
),
error_entry!(INVALID_NODE_START, "character cannot start a node"),
error_entry!(UNTERMINATED_QUOTED_SCALAR, "quoted scalar not closed"),
error_entry!(
INVALID_ESCAPE,
"invalid escape sequence in a double-quoted scalar"
),
error_entry!(
QUOTED_SCALAR_INDENTATION,
"quoted scalar continuation line not indented enough"
),
error_entry!(INVALID_ANCHOR, "empty or malformed anchor or alias name"),
error_entry!(INVALID_TAG, "malformed tag"),
error_entry!(ALIAS_WITH_PROPERTIES, "alias node with an anchor or tag"),
error_entry!(DUPLICATE_PROPERTY, "node with more than one anchor or tag"),
error_entry!(TAB_INDENTATION, "tab character used as block indentation"),
error_entry!(
UNEXPECTED_CONTENT,
"unexpected content after a node on the same line"
),
error_entry!(MISSING_MAPPING_COLON, "block mapping entry without `:`"),
CodeInfo {
code: UNSUPPORTED_SYNTAX,
name: "UNSUPPORTED_SYNTAX",
severity: Severity::Error,
summary: "syntax not yet supported by this parser version",
deprecated: true,
},
error_entry!(INVALID_BLOCK_HEADER, "malformed block scalar header"),
error_entry!(
BLOCK_SCALAR_INDENTATION,
"block scalar leading empty line indented more than its content"
),
error_entry!(UNTERMINATED_FLOW_COLLECTION, "flow collection not closed"),
error_entry!(
MISSING_FLOW_SEPARATOR,
"flow collection entries not separated by `,`"
),
error_entry!(
UNEXPECTED_FLOW_INDICATOR,
"flow indicator where it cannot appear"
),
error_entry!(
IMPLICIT_KEY_TOO_LONG,
"implicit key longer than 1024 characters"
),
error_entry!(
INVALID_QUOTED_CHARACTER,
"character not allowed in a quoted scalar"
),
error_entry!(
INLINE_COLON_IN_PLAIN_VALUE,
"`: ` inside an unquoted block mapping value"
),
warning_entry!(
LENIENT_INLINE_COLON,
"`: ` inside an unquoted block mapping value read as text (lenient_inline_colons)"
),
error_entry!(INVALID_UTF8, "invalid UTF-8 in the input"),
error_entry!(INVALID_UTF16, "invalid UTF-16 in the input"),
error_entry!(INVALID_UTF32, "invalid UTF-32 in the input"),
error_entry!(
MISPLACED_BYTE_ORDER_MARK,
"byte order mark where no document prefix may start"
),
CodeInfo {
code: YAML_VERSION_1_1,
name: "YAML_VERSION_1_1",
severity: Severity::Warning,
summary: "%YAML 1.1 directive",
deprecated: false,
},
CodeInfo {
code: YAML_VERSION_NEWER_MINOR,
name: "YAML_VERSION_NEWER_MINOR",
severity: Severity::Warning,
summary: "%YAML 1.x (x > 2) document processed as YAML 1.2",
deprecated: false,
},
CodeInfo {
code: YAML_VERSION_UNSUPPORTED_MAJOR,
name: "YAML_VERSION_UNSUPPORTED_MAJOR",
severity: Severity::Error,
summary: "unsupported %YAML major version",
deprecated: false,
},
CodeInfo {
code: RESERVED_DIRECTIVE_IGNORED,
name: "RESERVED_DIRECTIVE_IGNORED",
severity: Severity::Warning,
summary: "reserved directive ignored",
deprecated: false,
},
error_entry!(
DUPLICATE_DIRECTIVE,
"duplicate %YAML directive or %TAG handle"
),
error_entry!(
UNDEFINED_TAG_HANDLE,
"tag handle not declared by a %TAG directive"
),
CodeInfo {
code: YAML_VERSION_1_0,
name: "YAML_VERSION_1_0",
severity: Severity::Warning,
summary: "%YAML 1.0 directive",
deprecated: false,
},
error_entry!(
INVALID_SCALAR_FOR_TAG,
"scalar is not a valid form of its tag"
),
error_entry!(COLLECTION_TAG_ON_SCALAR, "collection tag on a scalar"),
error_entry!(
WRONG_TAG_ON_COLLECTION,
"tag does not match the collection kind"
),
error_entry!(
JSON_UNMATCHED_SCALAR,
"plain scalar matches no JSON schema type"
),
warning_entry!(
MEANINGLESS_SETTINGS,
"setting has no effect in this combination"
),
error_entry!(
INCORRECT_SETTINGS,
"settings combination gives incorrect results"
),
error_entry!(DUPLICATE_KEY, "duplicate mapping key"),
warning_entry!(
DUPLICATE_KEY_LAST_WINS,
"duplicate mapping key, last value wins (DuplicateKeys::LastWins)"
),
error_entry!(
UNDEFINED_ALIAS,
"alias to an undefined or incomplete anchor"
),
error_entry!(
INVALID_MERGE_VALUE,
"merge key value is not a mapping or a sequence of mappings"
),
error_entry!(UNSUPPORTED_SETTING, "setting not supported by this version"),
error_entry!(
DEPTH_LIMIT_EXCEEDED,
"nesting deeper than Limits::max_depth"
),
error_entry!(SCALAR_TOO_LONG, "scalar longer than Limits::max_scalar_len"),
error_entry!(
DOCUMENT_TOO_LARGE,
"input larger than Limits::max_document_size"
),
error_entry!(
TOO_MANY_DOCUMENTS,
"more documents than Limits::max_documents"
),
error_entry!(
ALIAS_BUDGET_EXCEEDED,
"alias expansion larger than Limits::alias_budget"
),
error_entry!(
VALUE_DEPTH_EXCEEDED,
"Value nests deeper than Limits::max_depth"
),
error_entry!(
TREE_CAPACITY_EXCEEDED,
"input has more elements than a syntax tree can index"
),
error_entry!(
TYPED_DEPTH_EXCEEDED,
"recursive target type nests deeper than Limits::max_typed_depth"
),
error_entry!(
JSON_DEPTH_EXCEEDED,
"JSON output nests deeper than 128 levels"
),
error_entry!(
ALIAS_BYTE_BUDGET_EXCEEDED,
"alias expansion larger than Limits::alias_byte_budget"
),
warning_entry!(
DIAGNOSTICS_SUPPRESSED,
"more diagnostics than Limits::max_diagnostics; the rest are counted, not kept"
),
error_entry!(TYPE_MISMATCH, "node type does not fit the target type"),
error_entry!(
INTEGER_OUT_OF_RANGE,
"integer out of range for the target type"
),
error_entry!(MISSING_FIELD, "missing struct field"),
error_entry!(UNKNOWN_VARIANT, "unknown enum variant"),
error_entry!(CUSTOM, "error raised by the target type"),
error_entry!(UNKNOWN_FIELD, "unknown struct field"),
error_entry!(INVALID_VALUE, "value rejected by the target type"),
error_entry!(
INVALID_LENGTH,
"collection length rejected by the target type"
),
error_entry!(DUPLICATE_FIELD, "struct field given twice"),
error_entry!(
MULTIPLE_DOCUMENTS,
"more than one document for a single-document entry point"
),
error_entry!(IO_ERROR, "I/O error while reading the input"),
error_entry!(
DOCUMENT_UNAVAILABLE,
"document no longer available from the multi-document deserializer"
),
error_entry!(
SERIALIZE_CUSTOM,
"error raised by the value's Serialize impl"
),
error_entry!(WRITE_ERROR, "I/O error while writing the output"),
error_entry!(
SERIALIZE_DUPLICATE_KEY,
"serialized mapping has two equal keys"
),
error_entry!(
UNREPRESENTABLE_VALUE,
"value cannot be represented in YAML output"
),
error_entry!(
INVALID_SERIALIZE_CALLS,
"Serialize impl drove the serializer out of order"
),
error_entry!(
THREAD_LOCAL_UNAVAILABLE,
"Value serialized or deserialized during thread-local storage teardown"
),
error_entry!(
READ_AFTER_IGNORED_ERROR,
"deserializer used after one of its errors was ignored"
),
error_entry!(INTERNAL_ERROR, "internal error (a bug in oneyaml)"),
error_entry!(
INVALID_EVENT_STREAM,
"caller-supplied event stream violates the event contract"
),
];
#[cfg(test)]
mod tests {
#![allow(clippy::unwrap_used, clippy::indexing_slicing)]
use super::*;
const RANGES: [CodeRange; 5] = [
CodeRange::Syntax,
CodeRange::Schema,
CodeRange::Limits,
CodeRange::Serde,
CodeRange::Reserved,
];
#[test]
fn registry_is_sequential_within_each_range() {
for range in RANGES {
let (lo, _) = range.bounds();
let codes: Vec<u16> = REGISTRY
.iter()
.filter(|i| i.code.range() == range)
.map(|i| i.code.get())
.collect();
let expected: Vec<u16> = (0..codes.len())
.map(|k| lo.max(1).checked_add(u16::try_from(k).unwrap()).unwrap())
.collect();
assert_eq!(codes, expected, "{range:?} codes are contiguous");
}
}
#[test]
fn registry_is_sorted_unique_and_named() {
for pair in REGISTRY.windows(2) {
assert!(pair[0].code < pair[1].code);
}
for info in REGISTRY {
assert_eq!(info.code.info(), Some(info));
assert!(!info.summary.is_empty());
assert!(!info.name.is_empty());
}
}
#[test]
fn range_validation() {
assert_eq!(Code::new(0), None);
assert_eq!(Code::new(1).unwrap().range(), CodeRange::Syntax);
assert_eq!(Code::new(999).unwrap().range(), CodeRange::Syntax);
assert_eq!(Code::new(1000).unwrap().range(), CodeRange::Schema);
assert_eq!(Code::new(2500).unwrap().range(), CodeRange::Limits);
assert_eq!(Code::new(3999).unwrap().range(), CodeRange::Serde);
assert_eq!(Code::new(4000), None);
assert_eq!(Code::new(8999), None);
assert_eq!(Code::new(9000).unwrap().range(), CodeRange::Reserved);
assert_eq!(Code::new(10000), None);
for range in RANGES {
let (lo, hi) = range.bounds();
assert!(lo <= hi);
assert_eq!(Code::new(hi).unwrap().range(), range);
}
}
#[test]
fn display_and_parse_round_trip() {
assert_eq!(YAML_VERSION_1_1.to_string(), "Y1000");
assert_eq!(Code::new(7).unwrap().to_string(), "Y0007");
assert_eq!(Code::parse("Y1003"), Some(RESERVED_DIRECTIVE_IGNORED));
assert_eq!(Code::parse("Y0007"), Code::new(7));
for bad in [
"", "Y", "Y0000", "Y123", "Y12345", "X1000", "Y10a0", "Y5000",
] {
assert_eq!(Code::parse(bad), None, "{bad}");
}
assert_eq!(Code::new(999).unwrap().info(), None);
}
}