use std::{fmt, marker::PhantomData};
use codespan_reporting::{
diagnostic::{Diagnostic, Severity},
files::Files,
};
use crate::{
fmt::Code,
model::{ErrorCode, ErrorDetail},
};
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SourceError<'files, Ec, Ed, Fs> {
pub code: Ec,
pub detail: Ed,
pub severity: Severity,
pub marker: PhantomData<&'files Fs>,
}
impl<'files, Ec, Ed, Fs> SourceError<'files, Ec, Ed, Fs>
where
Ec: ErrorCode,
Ed: ErrorDetail<'files, Files = Fs>,
Fs: Files<'files>,
{
pub fn new(code: Ec, detail: Ed, severity: Severity) -> Self {
Self {
code,
detail,
severity,
marker: PhantomData,
}
}
pub fn as_diagnostic(&self, files: &Fs) -> Diagnostic<Fs::FileId> {
let SourceError {
code,
detail,
severity,
marker: _,
} = self;
let code_str = Code::string(*code);
Diagnostic::new(*severity)
.with_code(code_str)
.with_message(code.description())
.with_labels(detail.labels())
.with_notes(detail.notes(files))
}
}
impl<'files, Ec, Ed, Fs> std::error::Error for SourceError<'files, Ec, Ed, Fs>
where
Ec: ErrorCode + fmt::Debug,
Ed: ErrorDetail<'files, Files = Fs> + std::error::Error,
Fs: Files<'files> + fmt::Debug,
{
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
self.detail.source()
}
}
impl<'files, Ec, Ed, Fs> fmt::Display for SourceError<'files, Ec, Ed, Fs>
where
Ec: ErrorCode,
Ed: ErrorDetail<'files, Files = Fs> + fmt::Display,
Fs: Files<'files>,
{
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::Display::fmt(&self.detail, f)
}
}
#[cfg(test)]
mod tests {
use std::{fmt, ops::Range};
use codespan_reporting::{
diagnostic::{Diagnostic, Label, Severity},
files::SimpleFiles,
};
use super::SourceError;
use crate::{ErrorCode, ErrorDetail};
#[test]
fn as_diagnostic_passes_through_all_members() {
let mut files = SimpleFiles::new();
let source_error = source_error(&mut files, None);
let diagnostic = source_error.as_diagnostic(&files);
assert_eq!(
Diagnostic {
severity: Severity::Error,
code: Some(String::from("E01")),
message: String::from("`chosen` value is invalid."),
labels: vec![
Label::primary(source_error.detail.file_id, 9..13)
.with_message("label_message")
],
notes: vec![String::from("note_message")]
},
diagnostic
);
}
#[test]
fn delegates_error_source_to_error_detail() {
let mut files = SimpleFiles::new();
let source_error = source_error(&mut files, Some(SubError("one")));
let source =
std::error::Error::source(&source_error).expect("Expected `Error::source()` to exist");
assert_eq!("one", source.to_string());
}
fn source_error(
files: &mut SimpleFiles<&'static str, &'static str>,
error: Option<SubError>,
) -> SourceError<'static, TestErrorCode, TestErrorDetail, SimpleFiles<&'static str, &'static str>>
{
let file_id = files.add("path/to/file", "---\ncon: tent\n");
let error_code = TestErrorCode;
let error_detail = TestErrorDetail {
file_id,
value: String::from("tent"),
value_byte_indices: 9..13,
error,
};
SourceError::new(error_code, error_detail, Severity::Error)
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct TestErrorCode;
impl ErrorCode for TestErrorCode {
const ERROR_CODE_MAX: usize = 10;
const PREFIX: &'static str = "E";
fn code(self) -> usize {
1
}
fn description(self) -> &'static str {
"`chosen` value is invalid."
}
}
#[derive(Debug)]
pub struct TestErrorDetail {
pub file_id: usize,
pub value: String,
pub value_byte_indices: Range<usize>,
pub error: Option<SubError>,
}
impl<'files> ErrorDetail<'files> for TestErrorDetail {
type Files = SimpleFiles<&'files str, &'static str>;
fn labels(&self) -> Vec<Label<usize>> {
let Self {
file_id,
value: _,
value_byte_indices,
..
} = self;
vec![Label::primary(*file_id, value_byte_indices.clone()).with_message("label_message")]
}
fn notes(&self, _files: &Self::Files) -> Vec<String> {
vec![String::from("note_message")]
}
}
impl std::error::Error for TestErrorDetail {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
self.error
.as_ref()
.map(|error| -> &dyn std::error::Error { error })
}
}
impl fmt::Display for TestErrorDetail {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Value `{}` is too long.", self.value)
}
}
#[derive(Debug, PartialEq, Eq)]
pub struct SubError(&'static str);
impl std::error::Error for SubError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
None
}
}
impl fmt::Display for SubError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.0)
}
}
}