srcerr

Crates.iosrcerr
lib.rssrcerr
version0.4.0
sourcesrc
created_at2020-11-20 04:02:46.531992
updated_at2021-04-25 01:57:43.316679
descriptionUser friendly errors from source data.
homepage
repositoryhttps://github.com/azriel91/srcerr
max_upload_size
id314235
size62,238
Azriel Hoh (azriel91)

documentation

https://docs.rs/srcerr/

README

🪄 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.

Commit count: 84

cargo fmt