mod kind;
mod path;
mod serde;
pub use kind::{CustomErrorSource, ErrorKind};
pub(crate) use path::ErrorPath;
use datavalue::OwnedDataValue;
use std::borrow::Cow;
use std::fmt;
use std::sync::Arc;
pub(crate) const INVALID_ARGS: &str = "Invalid Arguments";
pub(crate) const NAN_ERROR: &str = "NaN";
#[derive(Debug)]
struct MessageError(String);
impl fmt::Display for MessageError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.0)
}
}
impl std::error::Error for MessageError {}
#[non_exhaustive]
#[derive(Debug, Clone)]
pub struct Error {
pub kind: ErrorKind,
operator: Option<Cow<'static, str>>,
node_ids: ErrorPath,
}
impl Error {
#[inline]
pub fn new(kind: ErrorKind) -> Self {
Self {
kind,
operator: None,
node_ids: ErrorPath::new(),
}
}
#[inline]
pub fn operator(&self) -> Option<&str> {
self.operator.as_deref()
}
#[inline]
pub fn node_ids(&self) -> &[u32] {
self.node_ids.as_slice()
}
pub fn tag(&self) -> &'static str {
match self.kind {
ErrorKind::InvalidOperator(_) => "InvalidOperator",
ErrorKind::InvalidArguments(_) => "InvalidArguments",
ErrorKind::VariableNotFound(_) => "VariableNotFound",
ErrorKind::InvalidContextLevel(_) => "InvalidContextLevel",
ErrorKind::TypeError(_) => "TypeError",
ErrorKind::ArithmeticError(_) => "ArithmeticError",
ErrorKind::Custom(_) => "Custom",
ErrorKind::ParseError(_) => "ParseError",
ErrorKind::Thrown(_) => "Thrown",
ErrorKind::FormatError(_) => "FormatError",
ErrorKind::IndexOutOfBounds { .. } => "IndexOutOfBounds",
ErrorKind::ConfigurationError(_) => "ConfigurationError",
}
}
#[must_use = "builder methods return the modified Error; bind or return it"]
pub fn with_operator(mut self, operator: impl Into<Cow<'static, str>>) -> Self {
self.operator = Some(operator.into());
self
}
#[must_use = "builder methods return the modified Error; bind or return it"]
pub fn with_node_ids(mut self, ids: Vec<u32>) -> Self {
self.node_ids = ids.into();
self
}
pub fn resolve_path(&self, compiled: &crate::Logic) -> Vec<crate::PathStep> {
compiled.resolve_node_ids(self.node_ids.as_slice())
}
#[inline]
pub fn invalid_operator(name: impl Into<Cow<'static, str>>) -> Self {
ErrorKind::InvalidOperator(name.into()).into()
}
#[inline]
pub fn invalid_arguments(msg: impl Into<Cow<'static, str>>) -> Self {
ErrorKind::InvalidArguments(msg.into()).into()
}
#[inline]
pub fn variable_not_found(name: impl Into<Cow<'static, str>>) -> Self {
ErrorKind::VariableNotFound(name.into()).into()
}
#[inline]
pub fn invalid_context_level(level: isize) -> Self {
ErrorKind::InvalidContextLevel(level).into()
}
#[inline]
pub fn type_error(msg: impl Into<Cow<'static, str>>) -> Self {
ErrorKind::TypeError(msg.into()).into()
}
#[inline]
pub fn arithmetic_error(msg: impl Into<Cow<'static, str>>) -> Self {
ErrorKind::ArithmeticError(msg.into()).into()
}
#[inline]
pub fn custom_message(msg: impl Into<String>) -> Self {
Self::wrap(MessageError(msg.into()))
}
#[inline]
pub fn wrap<E: std::error::Error + Send + Sync + 'static>(err: E) -> Self {
let mut slot: Option<E> = Some(err);
if let Some(slot_as_error) =
(&mut slot as &mut dyn std::any::Any).downcast_mut::<Option<Error>>()
{
return slot_as_error.take().expect("just stored `Some`");
}
let err = slot.take().expect("just stored `Some`");
ErrorKind::Custom(Arc::new(err)).into()
}
#[inline]
pub fn parse_error(msg: impl Into<Cow<'static, str>>) -> Self {
ErrorKind::ParseError(msg.into()).into()
}
#[inline]
pub fn thrown(value: OwnedDataValue) -> Self {
ErrorKind::Thrown(value).into()
}
#[inline]
pub fn thrown_value(&self) -> Option<&OwnedDataValue> {
if let ErrorKind::Thrown(v) = &self.kind {
Some(v)
} else {
None
}
}
#[inline]
pub fn format_error(msg: impl Into<Cow<'static, str>>) -> Self {
ErrorKind::FormatError(msg.into()).into()
}
#[inline]
pub fn index_out_of_bounds(index: isize, length: usize) -> Self {
ErrorKind::IndexOutOfBounds { index, length }.into()
}
#[inline]
pub fn configuration_error(msg: impl Into<Cow<'static, str>>) -> Self {
ErrorKind::ConfigurationError(msg.into()).into()
}
#[inline]
pub(crate) fn invalid_args() -> Self {
Error::invalid_arguments(INVALID_ARGS)
}
#[cold]
#[inline(never)]
pub(crate) fn decorated(
mut self,
node_ids: Vec<u32>,
compiled: &crate::Logic,
prefer_existing_op: bool,
) -> Self {
self = self.with_node_ids(node_ids);
if !prefer_existing_op || self.operator.is_none() {
if let Some(name) = compiled.root_op_name.clone() {
self.operator = Some(name);
}
}
self
}
#[inline]
pub(crate) fn nan() -> Self {
Error::thrown(OwnedDataValue::object([("type", NAN_ERROR)]))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn wrap_renders_via_display() {
let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "missing key");
let err = Error::wrap(io_err);
assert_eq!(err.tag(), "Custom");
assert!(err.to_string().contains("missing key"));
}
#[test]
fn wrap_preserves_source_chain() {
use std::error::Error as _;
let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "missing key");
let err = Error::wrap(io_err);
let src = err.source().expect("Custom should expose its source");
assert!(src.to_string().contains("missing key"));
assert!(src.downcast_ref::<std::io::Error>().is_some());
}
#[test]
fn wrap_threads_through_question_mark() {
fn inner() -> std::result::Result<(), Error> {
"not_an_int".parse::<i32>().map_err(Error::wrap)?;
Ok(())
}
let err = inner().expect_err("parse should fail");
assert!(matches!(err.kind, ErrorKind::Custom(_)));
}
#[test]
fn wrap_of_existing_error_is_noop() {
let inner = Error::variable_not_found("x");
let wrapped = Error::wrap(inner.clone());
assert_eq!(wrapped.tag(), "VariableNotFound");
assert!(matches!(wrapped.kind, ErrorKind::VariableNotFound(ref name) if name == "x"));
let with_meta = inner.with_operator("var").with_node_ids(vec![1, 2, 3]);
let wrapped = Error::wrap(with_meta);
assert_eq!(wrapped.operator(), Some("var"));
assert_eq!(wrapped.node_ids(), &[1, 2, 3]);
}
#[test]
fn error_path_default_is_empty() {
let p = ErrorPath::new();
assert!(p.as_slice().is_empty());
assert_eq!(p.as_slice(), &[] as &[u32]);
}
#[test]
fn error_path_from_vec_round_trips() {
let p: ErrorPath = vec![10, 20, 30].into();
assert_eq!(p.as_slice(), &[10, 20, 30]);
}
#[test]
fn with_node_ids_stores_inline_no_box() {
let err = Error::invalid_arguments("x").with_node_ids(vec![1, 2, 3]);
assert_eq!(err.node_ids(), &[1, 2, 3]);
}
}