use std::fmt::{self, Display};
use std::io;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Span {
pub start: u32,
pub end: u32,
}
impl Span {
pub const fn new(start: u32, end: u32) -> Self {
Self { start, end }
}
pub const EMPTY: Span = Span { start: 0, end: 0 };
pub fn slice<'a>(&self, input: &'a str) -> Option<&'a str> {
let start = self.start as usize;
let end = self.end as usize;
if start > end || end > input.len() {
return None;
}
if !input.is_char_boundary(start) || !input.is_char_boundary(end) {
return None;
}
Some(&input[start..end])
}
pub fn line_col(&self, input: &str) -> (u32, u32) {
let start = (self.start as usize).min(input.len());
let bytes = input.as_bytes();
let mut line: u32 = 1;
let mut last_nl: usize = 0;
for (i, &b) in bytes.iter().enumerate().take(start) {
if b == b'\n' {
line += 1;
last_nl = i + 1;
}
}
let col = (start - last_nl) as u32;
(line, col)
}
}
#[non_exhaustive]
#[derive(Debug)]
pub enum Error {
Io(io::Error),
Structured(ErrorKind),
Syntax(String),
Message(String),
Unrepresentable(ReasonCode),
InvalidUtf8 {
valid_up_to: usize,
},
}
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ReasonCode {
ScalarRoot,
EmptyKeyName,
NonFiniteFloat,
CRByte,
BothFormsRequired,
TrailingWhitespaceCollision,
LeadingWhitespaceCollision,
}
impl ReasonCode {
pub fn code_name(&self) -> &'static str {
match self {
ReasonCode::ScalarRoot => "ScalarRoot",
ReasonCode::EmptyKeyName => "EmptyKeyName",
ReasonCode::NonFiniteFloat => "NonFiniteFloat",
ReasonCode::CRByte => "CRByte",
ReasonCode::BothFormsRequired => "BothFormsRequired",
ReasonCode::TrailingWhitespaceCollision => "TrailingWhitespaceCollision",
ReasonCode::LeadingWhitespaceCollision => "LeadingWhitespaceCollision",
}
}
}
impl Display for ReasonCode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ReasonCode::ScalarRoot => write!(
f,
"ScalarRoot: the document root is not an Object or an Array (spec § 5.9.0)"
),
ReasonCode::EmptyKeyName => write!(
f,
"EmptyKeyName: an Object pair's name is the empty string (spec § 5.9.0)"
),
ReasonCode::NonFiniteFloat => {
write!(f, "NonFiniteFloat: a Float is NaN or ±Infinity (spec § 5.9.0)")
}
ReasonCode::CRByte => write!(
f,
"CRByte: a String contains a CR byte (spec § 5.9.0 / § 5.9.7)"
),
ReasonCode::BothFormsRequired => write!(
f,
"BothFormsRequired: the multi-line String needs both forms, a segment trimming to '))' and a segment trimming to ')' (spec § 5.9.7)"
),
ReasonCode::TrailingWhitespaceCollision => write!(
f,
"TrailingWhitespaceCollision: a segment trims to '))' and some content line has trailing whitespace (spec § 5.9.7)"
),
ReasonCode::LeadingWhitespaceCollision => write!(
f,
"LeadingWhitespaceCollision: a segment trims to '))' and every non-blank segment shares leading whitespace at the same position (spec § 5.9.7)"
),
}
}
}
#[non_exhaustive]
#[allow(missing_docs)]
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ErrorKind {
MissingSeparatorSpace {
line: u32,
column: u32,
marker: char,
span: Span,
},
#[doc(hidden)]
InvalidTypedScalar {
line: u32,
marker: char,
body: String,
span: Span,
},
LossyScalar {
line: u32,
body: String,
canonical: String,
span: Span,
},
DuplicateKey { line: u32, key: String, span: Span },
KeyPathConflict {
line: u32,
path: String,
kind: ConflictKind,
span: Span,
},
EmptyKey { line: u32, span: Span },
InvalidKey { line: u32, key: String, span: Span },
UnclosedCompound { kind: CompoundKind, span: Span },
UnbalancedBracket {
line: u32,
span: Span,
expected: CompoundKind,
found: char,
},
#[doc(hidden)]
InlineNonEmptyCompound { line: u32, span: Span, body: String },
MissingSeparator { line: u32, span: Span },
UnterminatedInlineCompound { line: u32, span: Span },
UnterminatedQuotedKey { line: u32, span: Span },
MalformedInlineCompound {
line: u32,
span: Span,
detail: String,
},
BadEscapeSequence {
line: u32,
span: Span,
sequence: String,
},
OrphanLineAfterTopLevelInline { line: u32, span: Span },
Other {
line: Option<u32>,
message: String,
span: Span,
},
}
#[non_exhaustive]
#[allow(missing_docs)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ConflictKind {
Overwrite {
existing: &'static str,
new_kind: &'static str,
},
BlockedByValue,
}
#[non_exhaustive]
#[allow(missing_docs)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CompoundKind {
Object,
Array,
MultilineStripped,
MultilineVerbatim,
}
impl ErrorKind {
pub fn code_name(&self) -> &'static str {
match self {
ErrorKind::MissingSeparatorSpace { .. } => "MissingSeparatorSpace",
ErrorKind::InvalidTypedScalar { .. } => "InvalidTypedScalar",
ErrorKind::LossyScalar { .. } => "LossyScalar",
ErrorKind::DuplicateKey { .. } => "DuplicateKey",
ErrorKind::KeyPathConflict { .. } => "KeyPathConflict",
ErrorKind::EmptyKey { .. } => "EmptyKey",
ErrorKind::InvalidKey { .. } => "InvalidKey",
ErrorKind::UnclosedCompound { .. } => "UnclosedCompound",
ErrorKind::UnbalancedBracket { .. } => "UnbalancedBracket",
ErrorKind::InlineNonEmptyCompound { .. } => "InlineNonEmptyCompound",
ErrorKind::MissingSeparator { .. } => "MissingSeparator",
ErrorKind::UnterminatedInlineCompound { .. } => "UnterminatedInlineCompound",
ErrorKind::UnterminatedQuotedKey { .. } => "UnterminatedQuotedKey",
ErrorKind::MalformedInlineCompound { .. } => "MalformedInlineCompound",
ErrorKind::BadEscapeSequence { .. } => "BadEscapeSequence",
ErrorKind::OrphanLineAfterTopLevelInline { .. } => "OrphanLineAfterTopLevelInline",
ErrorKind::Other { .. } => "Other",
}
}
}
impl Display for ErrorKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ErrorKind::MissingSeparatorSpace { line, .. } => write!(
f,
"Line {}: MissingSeparatorSpace: separator must be followed by whitespace or end of line",
line
),
ErrorKind::InvalidTypedScalar { line, body, .. } => {
write!(f, "Line {}: InvalidTypedScalar: {}", line, body)
}
ErrorKind::LossyScalar {
line,
body,
canonical,
..
} => write!(
f,
"Line {}: LossyScalar: '{}' would be inferred as a number and silently canonicalised to '{}'; append '::' to keep it a String or write the canonical form",
line, body, canonical
),
ErrorKind::DuplicateKey { line, key, .. } => {
write!(f, "Line {}: duplicate key '{}'", line, key)
}
ErrorKind::KeyPathConflict { line, path, kind, .. } => match kind {
ConflictKind::Overwrite { existing, new_kind } => write!(
f,
"Line {}: conflict at '{}' \u{2014} cannot overwrite {} with {}",
line, path, existing, new_kind
),
ConflictKind::BlockedByValue => write!(
f,
"Line {}: conflict at '{}' \u{2014} an existing value blocks the path",
line, path
),
},
ErrorKind::EmptyKey { line, .. } => write!(f, "Empty key at line {}", line),
ErrorKind::InvalidKey { line, key, .. } => {
write!(f, "Invalid key at line {}: '{}'", line, key)
}
ErrorKind::UnclosedCompound { kind, .. } => match kind {
CompoundKind::Object => write!(f, "Unclosed object at end of input"),
CompoundKind::Array => write!(f, "Unclosed array at end of input"),
CompoundKind::MultilineStripped | CompoundKind::MultilineVerbatim => {
write!(f, "Unclosed multi-line string at end of input")
}
},
ErrorKind::UnbalancedBracket {
line,
expected,
found,
..
} => {
let opener = match expected {
CompoundKind::Object => '{',
CompoundKind::Array => '[',
CompoundKind::MultilineStripped | CompoundKind::MultilineVerbatim => '(',
};
write!(
f,
"Line {}: UnbalancedBracket: '{}' without matching '{}'",
line, found, opener
)
}
ErrorKind::InlineNonEmptyCompound { line, body, .. } => write!(
f,
"Line {}: InlineNonEmptyCompound: inline non-empty {} is not supported; put entries on separate lines",
line, body
),
ErrorKind::MissingSeparator { line, .. } => write!(
f,
"Line {}: MissingSeparator: object entries must be 'key: value' pairs",
line
),
ErrorKind::UnterminatedInlineCompound { line, .. } => write!(
f,
"Line {}: UnterminatedInlineCompound: inline compound not closed on the same line",
line
),
ErrorKind::UnterminatedQuotedKey { line, .. } => write!(
f,
"Line {}: UnterminatedQuotedKey: quoted key segment not closed on the same line",
line
),
ErrorKind::MalformedInlineCompound { line, detail, .. } => {
write!(
f,
"Line {}: MalformedInlineCompound: {}",
line, detail
)
}
ErrorKind::BadEscapeSequence { line, sequence, .. } => write!(
f,
"Line {}: BadEscapeSequence: invalid escape sequence '{}'",
line, sequence
),
ErrorKind::OrphanLineAfterTopLevelInline { line, .. } => write!(
f,
"Line {}: OrphanLineAfterTopLevelInline: content after root-level inline compound",
line
),
ErrorKind::Other { message, .. } => f.write_str(message),
}
}
}
impl Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Error::Io(e) => write!(f, "I/O error: {}", e),
Error::Structured(k) => write!(f, "Syntax error: {}", k),
Error::Syntax(m) => write!(f, "Syntax error: {}", m),
Error::Message(m) => write!(f, "{}", m),
Error::Unrepresentable(code) => write!(f, "{}", code),
Error::InvalidUtf8 { valid_up_to } => write!(
f,
"InvalidUtf8: input is not valid UTF-8; first invalid byte sequence at byte offset {}",
valid_up_to
),
}
}
}
impl std::error::Error for Error {}
impl From<io::Error> for Error {
fn from(e: io::Error) -> Self {
Error::Io(e)
}
}
impl serde::ser::Error for Error {
fn custom<T: Display>(msg: T) -> Self {
Error::Message(msg.to_string())
}
}
impl serde::de::Error for Error {
fn custom<T: Display>(msg: T) -> Self {
Error::Message(msg.to_string())
}
}
impl Error {
pub fn line(&self) -> Option<u32> {
match self {
Error::Structured(k) => k.line(),
_ => None,
}
}
pub fn span(&self) -> Option<Span> {
match self {
Error::Structured(k) => Some(k.span()),
Error::InvalidUtf8 { valid_up_to } => {
let offset = u32::try_from(*valid_up_to).unwrap_or(u32::MAX);
Some(Span::new(offset, offset))
}
_ => None,
}
}
pub fn reason_code(&self) -> Option<ReasonCode> {
match self {
Error::Unrepresentable(code) => Some(*code),
_ => None,
}
}
pub fn valid_up_to(&self) -> Option<usize> {
match self {
Error::InvalidUtf8 { valid_up_to } => Some(*valid_up_to),
_ => None,
}
}
}
impl ErrorKind {
pub fn line(&self) -> Option<u32> {
match self {
ErrorKind::MissingSeparatorSpace { line, .. }
| ErrorKind::InvalidTypedScalar { line, .. }
| ErrorKind::LossyScalar { line, .. }
| ErrorKind::DuplicateKey { line, .. }
| ErrorKind::KeyPathConflict { line, .. }
| ErrorKind::EmptyKey { line, .. }
| ErrorKind::InvalidKey { line, .. }
| ErrorKind::UnbalancedBracket { line, .. }
| ErrorKind::InlineNonEmptyCompound { line, .. }
| ErrorKind::MissingSeparator { line, .. }
| ErrorKind::UnterminatedInlineCompound { line, .. }
| ErrorKind::UnterminatedQuotedKey { line, .. }
| ErrorKind::MalformedInlineCompound { line, .. }
| ErrorKind::BadEscapeSequence { line, .. }
| ErrorKind::OrphanLineAfterTopLevelInline { line, .. } => Some(*line),
ErrorKind::UnclosedCompound { .. } => None,
ErrorKind::Other { line, .. } => *line,
}
}
pub fn span(&self) -> Span {
match self {
ErrorKind::MissingSeparatorSpace { span, .. }
| ErrorKind::InvalidTypedScalar { span, .. }
| ErrorKind::LossyScalar { span, .. }
| ErrorKind::DuplicateKey { span, .. }
| ErrorKind::KeyPathConflict { span, .. }
| ErrorKind::EmptyKey { span, .. }
| ErrorKind::InvalidKey { span, .. }
| ErrorKind::UnclosedCompound { span, .. }
| ErrorKind::UnbalancedBracket { span, .. }
| ErrorKind::InlineNonEmptyCompound { span, .. }
| ErrorKind::MissingSeparator { span, .. }
| ErrorKind::UnterminatedInlineCompound { span, .. }
| ErrorKind::UnterminatedQuotedKey { span, .. }
| ErrorKind::MalformedInlineCompound { span, .. }
| ErrorKind::BadEscapeSequence { span, .. }
| ErrorKind::OrphanLineAfterTopLevelInline { span, .. }
| ErrorKind::Other { span, .. } => *span,
}
}
}