use std::borrow::Cow;
use std::fmt;
use std::sync::Arc;
use crate::compiler::tokens::Span;
pub struct Error {
repr: Box<ErrorRepr>,
}
#[derive(Clone)]
struct ErrorRepr {
kind: ErrorKind,
detail: Option<Cow<'static, str>>,
name: Option<String>,
lineno: usize,
span: Option<Span>,
source: Option<Arc<dyn std::error::Error + Send + Sync>>,
#[cfg(feature = "debug")]
debug_info: Option<Arc<crate::debug::DebugInfo>>,
}
impl fmt::Debug for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut err = f.debug_struct("Error");
err.field("kind", &self.kind());
if let Some(ref detail) = self.repr.detail {
err.field("detail", detail);
}
if let Some(ref name) = self.name() {
err.field("name", name);
}
if let Some(line) = self.line() {
err.field("line", &line);
}
if let Some(ref source) = std::error::Error::source(self) {
err.field("source", source);
}
ok!(err.finish());
#[cfg(feature = "debug")]
{
if !f.alternate() && self.debug_info().is_some() {
ok!(writeln!(f));
ok!(writeln!(f, "{}", self.display_debug_info()));
}
}
Ok(())
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum ErrorKind {
NonPrimitive,
NonKey,
InvalidOperation,
SyntaxError,
TemplateNotFound,
TooManyArguments,
MissingArgument,
UnknownFilter,
UnknownTest,
UnknownFunction,
UnknownMethod,
BadEscape,
UndefinedError,
BadSerialization,
#[cfg(feature = "deserialization")]
CannotDeserialize,
BadInclude,
EvalBlock,
CannotUnpack,
WriteFailure,
#[cfg(feature = "fuel")]
OutOfFuel,
#[cfg(feature = "custom_syntax")]
InvalidDelimiter,
#[cfg(feature = "multi_template")]
UnknownBlock,
}
impl ErrorKind {
fn description(self) -> &'static str {
match self {
ErrorKind::NonPrimitive => "not a primitive",
ErrorKind::NonKey => "not a key type",
ErrorKind::InvalidOperation => "invalid operation",
ErrorKind::SyntaxError => "syntax error",
ErrorKind::TemplateNotFound => "template not found",
ErrorKind::TooManyArguments => "too many arguments",
ErrorKind::MissingArgument => "missing argument",
ErrorKind::UnknownFilter => "unknown filter",
ErrorKind::UnknownFunction => "unknown function",
ErrorKind::UnknownTest => "unknown test",
ErrorKind::UnknownMethod => "unknown method",
ErrorKind::BadEscape => "bad string escape",
ErrorKind::UndefinedError => "undefined value",
ErrorKind::BadSerialization => "could not serialize to value",
ErrorKind::BadInclude => "could not render include",
ErrorKind::EvalBlock => "could not render block",
ErrorKind::CannotUnpack => "cannot unpack",
ErrorKind::WriteFailure => "failed to write output",
#[cfg(feature = "deserialization")]
ErrorKind::CannotDeserialize => "cannot deserialize",
#[cfg(feature = "fuel")]
ErrorKind::OutOfFuel => "engine ran out of fuel",
#[cfg(feature = "custom_syntax")]
ErrorKind::InvalidDelimiter => "invalid custom delimiters",
#[cfg(feature = "multi_template")]
ErrorKind::UnknownBlock => "unknown block",
}
}
}
impl fmt::Display for ErrorKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.description())
}
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if let Some(ref detail) = self.repr.detail {
ok!(write!(f, "{}: {}", self.kind(), detail));
} else {
ok!(write!(f, "{}", self.kind()));
}
if let Some(ref filename) = self.name() {
ok!(write!(f, " (in {}:{})", filename, self.line().unwrap_or(0)))
}
#[cfg(feature = "debug")]
{
if f.alternate() && self.debug_info().is_some() {
ok!(write!(f, "{}", self.display_debug_info()));
}
}
Ok(())
}
}
impl Error {
pub fn new<D: Into<Cow<'static, str>>>(kind: ErrorKind, detail: D) -> Error {
Error {
repr: Box::new(ErrorRepr {
kind,
detail: Some(detail.into()),
name: None,
lineno: 0,
span: None,
source: None,
#[cfg(feature = "debug")]
debug_info: None,
}),
}
}
pub(crate) fn internal_clone(&self) -> Error {
Error {
repr: self.repr.clone(),
}
}
pub(crate) fn set_filename_and_line(&mut self, filename: &str, lineno: usize) {
self.repr.name = Some(filename.into());
self.repr.lineno = lineno;
}
pub(crate) fn set_filename_and_span(&mut self, filename: &str, span: Span) {
self.repr.name = Some(filename.into());
self.repr.span = Some(span);
self.repr.lineno = span.start_line as usize;
}
pub(crate) fn new_not_found(name: &str) -> Error {
Error::new(
ErrorKind::TemplateNotFound,
format!("template {name:?} does not exist"),
)
}
pub fn with_source<E: std::error::Error + Send + Sync + 'static>(mut self, source: E) -> Self {
self.repr.source = Some(Arc::new(source));
self
}
pub fn kind(&self) -> ErrorKind {
self.repr.kind
}
pub fn detail(&self) -> Option<&str> {
self.repr.detail.as_deref()
}
pub(crate) fn set_detail<D: Into<Cow<'static, str>>>(&mut self, d: D) {
self.repr.detail = Some(d.into());
}
pub fn name(&self) -> Option<&str> {
self.repr.name.as_deref()
}
pub fn line(&self) -> Option<usize> {
if self.repr.lineno > 0 {
Some(self.repr.lineno)
} else {
None
}
}
#[cfg(feature = "debug")]
#[cfg_attr(docsrs, doc(cfg(feature = "debug")))]
pub fn range(&self) -> Option<std::ops::Range<usize>> {
self.repr
.span
.map(|x| x.start_offset as usize..x.end_offset as usize)
}
#[cfg(feature = "debug")]
#[cfg_attr(docsrs, doc(cfg(feature = "debug")))]
pub fn display_debug_info(&self) -> impl fmt::Display + '_ {
struct Proxy<'a>(&'a Error);
impl fmt::Display for Proxy<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if let Some(info) = self.0.debug_info() {
crate::debug::render_debug_info(
f,
self.0.name(),
self.0.kind(),
self.0.line(),
self.0.span(),
info,
)
} else {
Ok(())
}
}
}
Proxy(self)
}
#[cfg(feature = "debug")]
#[cfg_attr(docsrs, doc(cfg(feature = "debug")))]
pub fn template_source(&self) -> Option<&str> {
self.debug_info().and_then(|x| x.source())
}
#[cfg(feature = "debug")]
pub(crate) fn span(&self) -> Option<Span> {
self.repr.span
}
#[cfg(feature = "debug")]
pub(crate) fn debug_info(&self) -> Option<&crate::debug::DebugInfo> {
self.repr.debug_info.as_deref()
}
#[cfg(feature = "debug")]
#[cfg_attr(docsrs, doc(cfg(feature = "debug")))]
pub(crate) fn attach_debug_info(&mut self, value: crate::debug::DebugInfo) {
self.repr.debug_info = Some(Arc::new(value));
}
}
impl std::error::Error for Error {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
self.repr.source.as_ref().map(|err| err.as_ref() as _)
}
}
impl From<ErrorKind> for Error {
fn from(kind: ErrorKind) -> Self {
Error {
repr: Box::new(ErrorRepr {
kind,
detail: None,
name: None,
lineno: 0,
span: None,
source: None,
#[cfg(feature = "debug")]
debug_info: None,
}),
}
}
}
impl From<fmt::Error> for Error {
fn from(_: fmt::Error) -> Self {
Error::new(ErrorKind::WriteFailure, "formatting failed")
}
}
pub fn attach_basic_debug_info<T>(rv: Result<T, Error>, source: &str) -> Result<T, Error> {
#[cfg(feature = "debug")]
{
match rv {
Ok(rv) => Ok(rv),
Err(mut err) => {
err.repr.debug_info = Some(Arc::new(crate::debug::DebugInfo {
template_source: Some(source.to_string()),
..Default::default()
}));
Err(err)
}
}
}
#[cfg(not(feature = "debug"))]
{
let _source = source;
rv
}
}