use crate::{
ShellError, Span,
shell_error::{ErrorSite, ErrorSource},
};
use miette::Diagnostic;
use nu_utils::location::Location;
use std::{
borrow::Cow,
error::Error as StdError,
fmt::{self, Display},
sync::Arc,
};
pub const DEFAULT_CODE: &str = "nu::shell::error";
#[non_exhaustive]
#[derive(Debug, Clone, PartialEq)]
pub struct GenericError {
pub code: Cow<'static, str>,
pub error: Cow<'static, str>,
pub msg: Cow<'static, str>,
pub site: ErrorSite,
pub help: Option<Cow<'static, str>>,
pub inner: Vec<ShellError>,
pub source: Option<ErrorSource>,
}
impl GenericError {
#[track_caller]
pub fn new(
error: impl Into<Cow<'static, str>>,
msg: impl Into<Cow<'static, str>>,
span: Span,
) -> Self {
Self {
code: DEFAULT_CODE.into(),
error: error.into(),
msg: msg.into(),
site: ErrorSite::Span(span),
help: None,
inner: Vec::new(),
source: None,
}
}
#[track_caller]
pub fn new_internal(
error: impl Into<Cow<'static, str>>,
msg: impl Into<Cow<'static, str>>,
) -> Self {
let location = Location::caller();
Self {
code: DEFAULT_CODE.into(),
error: error.into(),
msg: msg.into(),
site: ErrorSite::Location(location.to_string()),
help: None,
inner: Vec::new(),
source: None,
}
}
pub fn new_internal_with_location(
error: impl Into<Cow<'static, str>>,
msg: impl Into<Cow<'static, str>>,
location: impl Into<Location>,
) -> Self {
Self {
code: DEFAULT_CODE.into(),
error: error.into(),
msg: msg.into(),
site: ErrorSite::Location(location.into().to_string()),
help: None,
inner: Vec::new(),
source: None,
}
}
pub fn with_code(self, code: impl Into<Cow<'static, str>>) -> Self {
Self {
code: code.into(),
..self
}
}
pub fn with_help(self, help: impl Into<Cow<'static, str>>) -> Self {
Self {
help: Some(help.into()),
..self
}
}
pub fn with_inner(self, inner: impl IntoIterator<Item = ShellError>) -> Self {
Self {
inner: inner.into_iter().collect(),
..self
}
}
pub fn with_source(self, source: impl StdError + Send + Sync + 'static) -> Self {
Self {
source: Some(ErrorSource(Arc::new(source))),
..self
}
}
}
impl Display for GenericError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let GenericError { error, .. } = self;
write!(f, "{error}")
}
}
impl StdError for GenericError {}
impl Diagnostic for GenericError {
fn code<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
Some(Box::new(self.code.as_ref()))
}
fn labels(&self) -> Option<Box<dyn Iterator<Item = miette::LabeledSpan> + '_>> {
let span = match &self.site {
ErrorSite::Span(span) => (*span).into(),
ErrorSite::Location(location) => miette::SourceSpan::new(0.into(), location.len()),
};
let label = miette::LabeledSpan::new_with_span(Some(self.msg.to_string()), span);
Some(Box::new(std::iter::once(label)))
}
fn source_code(&self) -> Option<&dyn miette::SourceCode> {
match &self.site {
ErrorSite::Span(_) => None,
ErrorSite::Location(location) => Some(location as &dyn miette::SourceCode),
}
}
fn help<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
self.help
.as_ref()
.map(|help| Box::new(help.as_ref()) as Box<dyn Display>)
}
fn related<'a>(&'a self) -> Option<Box<dyn Iterator<Item = &'a dyn Diagnostic> + 'a>> {
match &self.inner.is_empty() {
true => None,
false => Some(Box::new(
self.inner.iter().map(|err| err as &dyn Diagnostic),
)),
}
}
fn diagnostic_source(&self) -> Option<&dyn Diagnostic> {
self.source.as_ref().map(|err| err as &dyn Diagnostic)
}
}