use std::fmt;
use std::error::Error as StdError;
use std::backtrace::Backtrace;
pub trait LambdustError: fmt::Debug + fmt::Display + Send + Sync + 'static {
fn error_code(&self) -> &'static str {
"lambdust::unknown"
}
fn source(&self) -> Option<&dyn StdError> {
None
}
fn backtrace(&self) -> Option<&Backtrace> {
None
}
fn help(&self) -> Option<&str> {
None
}
fn labels(&self) -> Vec<ErrorLabel> {
Vec::new()
}
fn is_critical(&self) -> bool {
false
}
}
#[derive(Debug, Clone)]
pub struct ErrorLabel {
pub span: crate::diagnostics::span::Span,
pub message: Option<String>,
pub style: LabelStyle,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum LabelStyle {
Primary,
Secondary,
}
impl ErrorLabel {
pub fn primary(span: crate::diagnostics::span::Span, message: impl Into<String>) -> Self {
Self {
span,
message: Some(message.into()),
style: LabelStyle::Primary,
}
}
pub fn secondary(span: crate::diagnostics::span::Span, message: impl Into<String>) -> Self {
Self {
span,
message: Some(message.into()),
style: LabelStyle::Secondary,
}
}
pub fn span_only(span: crate::diagnostics::span::Span) -> Self {
Self {
span,
message: None,
style: LabelStyle::Primary,
}
}
}
macro_rules! impl_std_error {
($type:ty) => {
impl std::error::Error for $type {}
};
}
macro_rules! derive_error {
(
$(#[$attr:meta])*
pub enum $name:ident {
$(
$(#[error($msg:expr)])?
$(#[error_code($code:expr)])?
$variant:ident $({
$($field:ident: $field_ty:ty),* $(,)?
})?,
)*
}
) => {
$(#[$attr])*
pub enum $name {
$(
$variant $({
$($field: $field_ty),*
})?,
)*
}
impl fmt::Display for $name {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
$(
Self::$variant $({ $($field),* })? => {
write!(f, concat!("[", stringify!($name), "::", stringify!($variant), "]"))
}
)*
}
}
}
impl LambdustError for $name {
fn error_code(&self) -> &'static str {
match self {
$(
Self::$variant { .. } => {
concat!("lambdust::", stringify!($name), "::", stringify!($variant))
}
)*
}
}
}
impl_std_error!($name);
};
}
pub mod utils {
use super::*;
pub fn boxed_error<E: LambdustError>(error: E) -> Box<dyn LambdustError> {
Box::new(error)
}
pub fn runtime_error(message: impl Into<String>) -> RuntimeError {
RuntimeError {
message: message.into(),
source: None,
}
}
pub fn runtime_error_with_source(
message: impl Into<String>,
source: Box<dyn StdError + Send + Sync>
) -> RuntimeError {
RuntimeError {
message: message.into(),
source: Some(source),
}
}
}
#[derive(Debug)]
pub struct RuntimeError {
message: String,
source: Option<Box<dyn StdError + Send + Sync>>,
}
impl fmt::Display for RuntimeError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.message)
}
}
impl LambdustError for RuntimeError {
fn error_code(&self) -> &'static str {
"lambdust::runtime_error"
}
fn source(&self) -> Option<&dyn StdError> {
self.source.as_ref().map(|e| e.as_ref() as &dyn StdError)
}
}
impl_std_error!(RuntimeError);
#[cfg(test)]
mod tests {
use super::*;
derive_error! {
#[derive(Debug)]
pub enum TestError {
#[error("Simple error")]
Simple,
#[error("Error with field: {message}")]
WithField { message: String },
#[error("Error with multiple fields: {a} and {b}")]
#[error_code("custom::test")]
MultiField { a: String, b: i32 },
}
}
#[test]
fn test_error_display() {
let simple = TestError::Simple;
assert_eq!(simple.to_string(), "Simple error");
let with_field = TestError::WithField {
message: "test".to_string()
};
assert_eq!(with_field.to_string(), "Error with field: test");
let multi = TestError::MultiField {
a: "hello".to_string(),
b: 42
};
assert_eq!(multi.to_string(), "Error with multiple fields: hello and 42");
}
#[test]
fn test_error_codes() {
let simple = TestError::Simple;
assert_eq!(simple.error_code(), "lambdust::TestError::Simple");
let multi = TestError::MultiField {
a: "test".to_string(),
b: 1
};
assert_eq!(multi.error_code(), "custom::test");
}
#[test]
fn test_runtime_error() {
let error = utils::runtime_error("Something went wrong");
assert_eq!(error.to_string(), "Something went wrong");
assert_eq!(error.error_code(), "lambdust::runtime_error");
}
#[test]
fn test_error_labels() {
let span = crate::diagnostics::Span::new(0, 10);
let label = ErrorLabel::primary(span, "here");
assert_eq!(label.style, LabelStyle::Primary);
assert_eq!(label.message.as_ref().unwrap(), "here");
assert_eq!(label.span.start, 0);
assert_eq!(label.span.end(), 10);
}
}