#[cfg(not(feature = "std"))]
use alloc::format;
#[cfg(not(feature = "std"))]
use alloc::string::String;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct Location {
pub line: usize,
pub column: usize,
pub byte_offset: usize,
}
impl Location {
pub const fn new(line: usize, column: usize, byte_offset: usize) -> Self {
Self {
line,
column,
byte_offset,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct Span {
pub start: Location,
pub end: Location,
}
impl Span {
pub const fn new(start: Location, end: Location) -> Self {
Self { start, end }
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ErrorCode {
SyntaxError,
UnexpectedEof,
InvalidEncoding,
SchemaValidation,
UnsupportedType,
IoError,
Custom,
}
#[derive(Debug, Clone, PartialEq)]
pub struct BabbelError {
pub code: ErrorCode,
pub message: String,
pub span: Option<Span>,
pub format: Option<&'static str>,
}
impl BabbelError {
pub fn new(code: ErrorCode, message: impl Into<String>) -> Self {
Self {
code,
message: message.into(),
span: None,
format: None,
}
}
pub fn with_span(mut self, span: Span) -> Self {
self.span = Some(span);
self
}
pub fn with_format(mut self, format: &'static str) -> Self {
self.format = Some(format);
self
}
pub fn syntax(message: impl Into<String>) -> Self {
Self::new(ErrorCode::SyntaxError, message)
}
pub fn eof(message: impl Into<String>) -> Self {
Self::new(ErrorCode::UnexpectedEof, message)
}
pub fn encoding(message: impl Into<String>) -> Self {
Self::new(ErrorCode::InvalidEncoding, message)
}
pub fn custom(message: impl Into<String>) -> Self {
Self::new(ErrorCode::Custom, message)
}
}
impl core::fmt::Display for BabbelError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
if let Some(fmt) = self.format {
write!(f, "[{fmt}] ")?;
}
write!(f, "{:?}: {}", self.code, self.message)?;
if let Some(span) = &self.span {
write!(f, " at line {}:{}", span.start.line, span.start.column)?;
}
Ok(())
}
}
#[cfg(feature = "std")]
impl std::error::Error for BabbelError {}
impl From<String> for BabbelError {
fn from(s: String) -> Self {
Self::custom(s)
}
}
impl From<&str> for BabbelError {
fn from(s: &str) -> Self {
Self::custom(s)
}
}
#[cfg(feature = "std")]
impl From<std::io::Error> for BabbelError {
fn from(e: std::io::Error) -> Self {
Self::new(ErrorCode::IoError, e.to_string())
}
}
pub fn format_error_snippet(source: &str, loc: Location, message: &str) -> String {
let line_str = source.lines().nth(loc.line.saturating_sub(1)).unwrap_or("");
let line_num = loc.line;
let col_num = loc.column;
let indent = col_num.saturating_sub(1);
format!(
"error: {}\n --> line {}:{}\n |\n{:4} | {}\n | {:indent$}^\n",
message,
line_num,
col_num,
line_num,
line_str,
"",
indent = indent
)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_snippet_rendering() {
let src = "{\n \"key\": \"value\",\n \"bad\": syntax_error\n}";
let loc = Location::new(3, 10, 25);
let snippet = format_error_snippet(src, loc, "unexpected token");
assert!(snippet.contains("line 3:10"));
assert!(snippet.contains("3 | \"bad\": syntax_error"));
assert!(snippet.contains("^"));
}
}