mit-commit-message-lints 6.4.2

Check the correctness of a specific commit message. Designed to be used in tools providing commit-msg style hooks
Documentation
use miette::{Diagnostic, SourceOffset, SourceSpan};
use serde_yaml::Error as YamlDeserializeError;
use thiserror::Error;
use toml::de::Error as TomlDeserializeError;

#[derive(Error, Debug, Diagnostic)]
#[error("could not convert author configuration to toml")]
#[diagnostic(
    url("https://github.com/PurpleBooth/git-mit/issues/new"),
    code(mit_commit_message_lints::mit::lib::authors::serialise_authors_error),
    help("please report this error on our issue tracker, this is a bug")
)]
pub struct SerializeAuthorsError(#[from] pub toml::ser::Error);

#[derive(Error, Debug, Diagnostic)]
#[error("could not parse author configuration")]
#[diagnostic(
    code(mit_commit_message_lints::mit::lib::authors::deserialise_authors_error),
    help(
        "`git mit-config mit example` can show you an example of what it should look like, or you can generate one using `git mit-config mit generate` after setting up some authors with `git mit-config mit set`"
    )
)]
pub struct DeserializeAuthorsError {
    #[source_code]
    pub(crate) src: String,
    #[label("invalid in toml: {toml_message}")]
    pub(crate) toml_span: SourceSpan,
    #[label("invalid in yaml: {yaml_message}")]
    pub(crate) yaml_span: SourceSpan,

    pub(crate) yaml_message: String,
    pub(crate) toml_message: String,
}

#[derive(Error, Debug, Diagnostic)]
#[error("could not parse rebase behaviour configuration")]
#[diagnostic(
    code(mit_commit_message_lints::mit::lib::authors::DeserializeRebaseBehaviourError),
    help("please report this error on our issue tracker, this is a bug")
)]
pub struct DeserializeRebaseBehaviourError {
    #[source_code]
    pub(crate) src: String,
}

#[derive(Error, Debug, Diagnostic)]
#[error("could not parse rotation option configuration")]
#[diagnostic(
    code(mit_commit_message_lints::mit::lib::authors::DeserializeRotationOptionError),
    help("valid values are: off, round-robin, random")
)]
pub struct DeserializeRotationOptionError {
    #[source_code]
    pub(crate) src: String,
}

impl DeserializeAuthorsError {
    pub(crate) fn new(
        input: &str,
        yaml_error: &YamlDeserializeError,
        toml_error: &TomlDeserializeError,
    ) -> Self {
        Self {
            src: input.to_string(),
            toml_span: (Self::span_from_toml_err(toml_error, input), 0).into(),
            yaml_span: (Self::span_from_yaml_err(yaml_error, input), 0).into(),
            yaml_message: yaml_error.to_string(),
            toml_message: toml_error.to_string(),
        }
    }

    pub fn span_from_toml_err(err: &TomlDeserializeError, _input: &str) -> usize {
        err.span()
            .map(SourceSpan::from)
            .map(|span| span.offset())
            .unwrap_or_default()
    }

    pub fn span_from_yaml_err(err: &YamlDeserializeError, input: &str) -> usize {
        err.location()
            .map_or_else(
                || SourceOffset::from(0),
                |location| SourceOffset::from_location(input, location.line(), location.column()),
            )
            .offset()
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use miette::Diagnostic;
    use serde::ser;

    #[test]
    fn serialize_authors_error_has_serialise_diagnostic_code() {
        let ser_err: toml::ser::Error = ser::Error::custom("test error");
        let err = SerializeAuthorsError::from(ser_err);
        let code = err.code().unwrap().to_string();
        assert_eq!(
            code, "mit_commit_message_lints::mit::lib::authors::serialise_authors_error",
            "SerializeAuthorsError should have code 'serialise_authors_error', got: {code}"
        );
    }

    #[test]
    fn deserialize_authors_error_has_deserialise_diagnostic_code() {
        let err = DeserializeAuthorsError {
            src: String::new(),
            toml_span: (0usize, 0usize).into(),
            yaml_span: (0usize, 0usize).into(),
            yaml_message: String::new(),
            toml_message: String::new(),
        };
        let code = err.code().unwrap().to_string();
        assert_eq!(
            code, "mit_commit_message_lints::mit::lib::authors::deserialise_authors_error",
            "DeserializeAuthorsError should have code 'deserialise_authors_error', got: {code}"
        );
    }

    #[test]
    fn deserialize_authors_error_new_populates_error_messages() {
        let input = "{[invalid";
        let yaml_result: Result<serde_yaml::Value, _> = serde_yaml::from_str(input);
        let toml_result: Result<toml::Value, _> = toml::from_str(input);
        let yaml_error = yaml_result.unwrap_err();
        let toml_error = toml_result.unwrap_err();

        let err = DeserializeAuthorsError::new(input, &yaml_error, &toml_error);

        assert!(
            !err.yaml_message.is_empty(),
            "yaml_message should contain the actual YAML parse error, but it was empty"
        );
        assert!(
            !err.toml_message.is_empty(),
            "toml_message should contain the actual TOML parse error, but it was empty"
        );
    }
}