use std::fmt;
use cirru_parser::Cirru;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Position {
pub line: usize,
pub column: usize,
pub offset: usize,
}
impl Position {
pub fn new(line: usize, column: usize, offset: usize) -> Self {
Position { line, column, offset }
}
pub fn at_offset(offset: usize) -> Self {
Position {
line: 0,
column: 0,
offset,
}
}
}
impl fmt::Display for Position {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.line > 0 {
write!(
f,
"at line {}, column {} (byte {})",
self.line, self.column, self.offset
)
} else {
write!(f, "at byte {}", self.offset)
}
}
}
fn format_path(path: &[usize]) -> String {
let mut result = String::from("@");
for (index, item) in path.iter().enumerate() {
if index > 0 {
result.push('.');
}
result.push_str(&item.to_string());
}
result
}
#[derive(Debug, Clone, PartialEq)]
pub enum EdnError {
ParseError {
original: String,
},
StructureError {
message: String,
path: Vec<usize>,
node_preview: Option<String>,
},
ValueError {
message: String,
path: Vec<usize>,
node_preview: Option<String>,
},
DeserializationError {
message: String,
position: Option<Vec<u8>>, },
}
impl EdnError {
pub fn from_parse_error_detailed(err: cirru_parser::CirruError, source: &str) -> Self {
EdnError::ParseError {
original: err.format_detailed(Some(source)),
}
}
pub fn from_parse_error(err: cirru_parser::CirruError) -> Self {
EdnError::ParseError {
original: err.format_detailed(None),
}
}
pub fn structure_focused(
message: impl Into<String>,
path: Vec<usize>,
focus_path: &[usize],
node: Option<&Cirru>,
) -> Self {
let node_preview = node.and_then(|n| {
let folded = cirru_parser::focus_cirru_preview(n, focus_path);
cirru_parser::format(std::slice::from_ref(&folded), false.into()).ok().map(|s| s.trim().to_string())
});
EdnError::StructureError {
message: message.into(),
path,
node_preview,
}
}
pub fn structure(message: impl Into<String>, path: Vec<usize>, node: Option<&Cirru>) -> Self {
let node_preview = node.and_then(|n| {
let folded = cirru_parser::focus_cirru_preview(n, &path);
cirru_parser::format(std::slice::from_ref(&folded), false.into()).ok().map(|s| s.trim().to_string())
});
EdnError::StructureError {
message: message.into(),
path,
node_preview,
}
}
pub fn value(message: impl Into<String>, path: Vec<usize>, node: Option<&Cirru>) -> Self {
let node_preview = node.and_then(|n| {
let folded = cirru_parser::focus_cirru_preview(n, &path);
cirru_parser::format(std::slice::from_ref(&folded), false.into()).ok().map(|s| s.trim().to_string())
});
EdnError::ValueError {
message: message.into(),
path,
node_preview,
}
}
pub fn deserialization(message: impl Into<String>, position: Option<Vec<u8>>) -> Self {
EdnError::DeserializationError {
message: message.into(),
position,
}
}
pub fn wrap_structure(crumb: impl Into<String>, fallback_path: Vec<usize>, inner: &EdnError) -> Self {
let crumb = crumb.into();
match inner {
EdnError::StructureError {
message,
path,
node_preview,
}
| EdnError::ValueError {
message,
path,
node_preview,
} => EdnError::StructureError {
message: format!("{crumb}: {message}"),
path: path.clone(),
node_preview: node_preview.clone(),
},
other => EdnError::StructureError {
message: format!("{crumb}: {}", other.message()),
path: fallback_path,
node_preview: None,
},
}
}
pub fn message(&self) -> &str {
match self {
EdnError::ParseError { original } => original,
EdnError::StructureError { message, .. } => message,
EdnError::ValueError { message, .. } => message,
EdnError::DeserializationError { message, .. } => message,
}
}
}
impl fmt::Display for EdnError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
EdnError::ParseError { original } => {
write!(f, "Parse error:\n{original}")
}
EdnError::StructureError {
message,
path,
node_preview,
} => {
write!(f, "Structure error")?;
if !path.is_empty() {
write!(f, " at {}", format_path(path))?;
}
write!(f, ": ")?;
for (i, line) in message.lines().enumerate() {
if i > 0 {
write!(f, "\n ")?;
}
write!(f, "{line}")?;
}
if let Some(preview) = node_preview {
for line in preview.lines() {
write!(f, "\n {line}")?;
}
}
Ok(())
}
EdnError::ValueError {
message,
path,
node_preview,
} => {
write!(f, "Value error")?;
if !path.is_empty() {
write!(f, " at {}", format_path(path))?;
}
write!(f, ": ")?;
for (i, line) in message.lines().enumerate() {
if i > 0 {
write!(f, "\n ")?;
}
write!(f, "{line}")?;
}
if let Some(preview) = node_preview {
for line in preview.lines() {
write!(f, "\n {line}")?;
}
}
Ok(())
}
EdnError::DeserializationError { message, .. } => {
write!(f, "Deserialization error: {message}")
}
}
}
}
impl From<&str> for EdnError {
fn from(message: &str) -> Self {
EdnError::ParseError {
original: message.to_string(),
}
}
}
#[cfg(test)]
mod tests {
use super::format_path;
#[test]
fn formats_paths_with_at_and_dot_separators() {
assert_eq!(format_path(&[1, 2, 3, 4]), "@1.2.3.4");
}
}
impl From<cirru_parser::CirruError> for EdnError {
fn from(err: cirru_parser::CirruError) -> Self {
EdnError::from_parse_error(err)
}
}
pub type EdnResult<T> = Result<T, EdnError>;