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),
}
#[non_exhaustive]
#[allow(missing_docs)]
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ErrorKind {
MissingSeparatorSpace {
line: u32,
column: u32,
marker: char,
span: Span,
},
InvalidTypedScalar {
line: u32,
marker: char,
body: 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,
},
InlineNonEmptyCompound { line: u32, span: Span, body: String },
MissingSeparator { 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,
SyntheticReopen,
}
#[non_exhaustive]
#[allow(missing_docs)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CompoundKind {
Object,
Array,
MultilineStripped,
MultilineVerbatim,
}
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::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
),
ConflictKind::SyntheticReopen => write!(
f,
"Line {}: conflict at '{}' \u{2014} synthetic dotted-key prefix already closed by an intervening different prefix; group lines with the same prefix together",
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::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),
}
}
}
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()),
_ => None,
}
}
}
impl ErrorKind {
pub fn line(&self) -> Option<u32> {
match self {
ErrorKind::MissingSeparatorSpace { line, .. }
| ErrorKind::InvalidTypedScalar { line, .. }
| ErrorKind::DuplicateKey { line, .. }
| ErrorKind::KeyPathConflict { line, .. }
| ErrorKind::EmptyKey { line, .. }
| ErrorKind::InvalidKey { line, .. }
| ErrorKind::UnbalancedBracket { line, .. }
| ErrorKind::InlineNonEmptyCompound { line, .. }
| ErrorKind::MissingSeparator { line, .. } => Some(*line),
ErrorKind::UnclosedCompound { .. } => None,
ErrorKind::Other { line, .. } => *line,
}
}
pub fn span(&self) -> Span {
match self {
ErrorKind::MissingSeparatorSpace { span, .. }
| ErrorKind::InvalidTypedScalar { 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::Other { span, .. } => *span,
}
}
}