#codes #format #data #error-code

srcerr

User friendly errors from source data

4 releases (breaking)

0.4.0 Apr 25, 2021
0.3.0 Apr 20, 2021
0.2.0 Feb 2, 2021
0.1.0 Nov 20, 2020

#12 in #error-code

42 downloads per month

MIT/Apache

24KB
323 lines

🪄 Srcerr

Crates.io CI Coverage Status

Types to track error codes and details.

This library provies a SourceError struct that holds:

  • ErrorCode: Enum whose variants indicate error code, simple description.
  • ErrorDetail: Enum with matching variants to ErrorCode, but each variant contains information specific to an instance of the error.
  • Severity: The severity to report the error.

This library backs onto codespan-reporting to render diagnostic errors.

The "codespan" feature can also be used to expose codespan types:

srcerr = { version = "0.4.0", features = ["codespan"] }

Usage

1. Implement ErrorCode
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum SimpleErrorCode {
    /// Error when a value is out of range.
    ValueOutOfRange,
    /// Error when a string is too long.
    StringTooLong,
}

impl ErrorCode for SimpleErrorCode {
    const ERROR_CODE_MAX: usize = 2;
    const PREFIX: &'static str = "E";

    fn code(self) -> usize {
        match self {
            Self::ValueOutOfRange => 1,
            Self::StringTooLong => 2,
        }
    }

    fn description(self) -> &'static str {
        match self {
            Self::ValueOutOfRange => "Value out of range.",
            Self::StringTooLong => "String provided is too long.",
        }
    }
}
2. Implement ErrorDetail
#[derive(Debug)]
pub enum SimpleErrorDetail {
    /// Error when a value is out of range.
    ValueOutOfRange {
        /// ID of the file containing the invalid value.
        file_id: usize,
        /// The value.
        value: i32,
        /// Byte begin and end indices where the value is defined.
        value_byte_indices: Range<usize>,
        /// Range that the value must be within.
        range: RangeInclusive<u32>,
    },
    /// Error when a string is too long.
    StringTooLong {
        /// ID of the file containing the invalid value.
        file_id: usize,
        /// The value that is too long.
        value: String,
        /// Byte begin and end indices where the value is defined.
        value_byte_indices: Range<usize>,
        /// Maximum length allowed for the string.
        limit: usize,
    },
}

impl<'files> ErrorDetail<'files> for SimpleErrorDetail {
    type Files = SimpleFiles<&'files str, &'files str>;

    fn labels(&self) -> Vec<Label<usize>> {
        match self {
            Self::ValueOutOfRange {
                file_id,
                value_byte_indices,
                range,
                ..
            } => {
                vec![
                    Label::primary(*file_id, value_byte_indices.clone()).with_message(format!(
                        "not within the range: `{}..={}`",
                        range.start(),
                        range.end()
                    )),
                ]
            }
            Self::StringTooLong {
                file_id,
                value_byte_indices,
                limit,
                ..
            } => {
                vec![
                    Label::primary(*file_id, value_byte_indices.clone())
                        .with_message(format!("exceeds the {} character limit.", limit)),
                ]
            }
        }
    }

    fn notes(&self, _files: &Self::Files) -> Vec<String> {
        match self {
            Self::ValueOutOfRange { range, .. } => {
                let valid_exprs = range.clone().map(|n| Cow::Owned(n.to_string()));
                let suggestion = Note::valid_exprs(valid_exprs).expect("Failed to format note.");
                vec![suggestion]
            }
            Self::StringTooLong { .. } => vec![],
        }
    }
}
3. Construct SourceError when there is an error.
fn value_out_of_range<'f>(
    file_id: usize,
) -> SourceError<'f, SimpleErrorCode, SimpleErrorDetail, SimpleFiles<&'f str, &'f str>> {
    let error_code = SimpleErrorCode::ValueOutOfRange;
    let error_detail = SimpleErrorDetail::ValueOutOfRange {
        file_id,
        value: -1,
        value_byte_indices: 21..23,
        range: 1..=3,
    };
    let severity = Severity::Error;

    SourceError::new(error_code, error_detail, severity)
}

fn string_too_long<'f>(
    file_id: usize,
    content: &str,
) -> SourceError<'f, SimpleErrorCode, SimpleErrorDetail, SimpleFiles<&'f str, &'f str>> {
    let error_code = SimpleErrorCode::StringTooLong;
    let error_detail = SimpleErrorDetail::StringTooLong {
        file_id,
        value: content[40..47].to_string(),
        value_byte_indices: 39..48,
        limit: 5,
    };
    let severity = Severity::Error;

    SourceError::new(error_code, error_detail, severity)
}
4. Output the diagnostic message.
let value_out_of_range = value_out_of_range(file_id);
let value_out_of_range = value_out_of_range.as_diagnostic(&files);
let string_too_long = string_too_long(file_id, content);
let string_too_long = string_too_long.as_diagnostic(&files);

let writer = StandardStream::stderr(ColorChoice::Always);
let config = term::Config::default();
term::emit(&mut writer.lock(), &config, &files, &value_out_of_range)?;
term::emit(&mut writer.lock(), &config, &files, &string_too_long)?;

Sample usage can be seen in the examples.

cargo run --example simple
cargo run --example source_ref_hint
cargo run --example long_expr_context
cargo run --example html > /tmp/index.html
cargo run --example codespan --features codespan

License

Licensed under either of

at your option.

Contribution

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.

Dependencies

~1.2–8MB
~62K SLoC