git-checks 2.2.0

Checks to run against a topic in git to enforce coding standards.
Documentation
// Copyright 2016 Kitware, Inc.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

use crates::git_workarea::CommitId;

use commit::Commit;
use context::CheckGitContext;
use error::Result;

#[derive(Debug, Default)]
/// The results of a check.
pub struct CheckResult {
    /// The warnings from running checks.
    warnings: Vec<String>,
    /// The alerts from running checks.
    ///
    /// These are meant to be directed towards administrators of the repository.
    alerts: Vec<String>,
    /// The errors from running checks.
    ///
    /// Errors cause the checks to be in a "failed" state.
    errors: Vec<String>,

    /// Whether any messages may be temporary.
    temporary: bool,
    /// Whether errors should be ignored or not.
    allow: bool,
    /// Whether the checks succeeded or not.
    pass: bool,
}

/// The severity of a message.
pub enum Severity {
    /// The message is a warning.
    Warning,
    /// The message is an error.
    Error,
    /// The message should be brought to the attention of project maintainers.
    Alert {
        /// Whether the checks should fail due to the message or not.
        blocking: bool,
    },
}

impl CheckResult {
    /// Create a new results structure.
    pub fn new() -> Self {
        CheckResult {
            warnings: vec![],
            alerts: vec![],
            errors: vec![],

            temporary: false,
            allow: false,
            pass: true,
        }
    }

    /// Add a message to the result.
    pub fn add_message<S>(&mut self, severity: Severity, message: S) -> &mut Self
        where S: ToString,
    {
        match severity {
            Severity::Warning => &mut self.warnings,
            Severity::Error => {
                self.pass = false;
                &mut self.errors
            },
            Severity::Alert { blocking } => {
                if blocking {
                    self.pass = false;
                }
                &mut self.alerts
            },
        }.push(message.to_string());

        self
    }

    /// Adds a warning message to the results.
    pub fn add_warning<S: ToString>(&mut self, warning: S) -> &mut Self {
        self.add_message(Severity::Warning, warning.to_string())
    }

    /// Adds an alert to the results.
    ///
    /// These messages should be brought to the attention of those maintaining the deployment of
    /// the checks.
    pub fn add_alert<S: ToString>(&mut self, alert: S, should_block: bool) -> &mut Self {
        self.add_message(Severity::Alert { blocking: should_block }, alert.to_string())
    }

    /// Adds a error message to the results.
    ///
    /// Also marks the checks as having failed.
    pub fn add_error<S: ToString>(&mut self, error: S) -> &mut Self {
        self.add_message(Severity::Error, error.to_string())
    }

    /// Indicates that there are messages which may be temporary.
    pub fn make_temporary(&mut self) -> &mut Self {
        self.temporary = true;

        self
    }

    /// Allows the checks to pass no matter what.
    pub fn whitelist(&mut self) -> &mut Self {
        self.allow = true;

        self
    }

    /// The warnings from the checks.
    pub fn warnings(&self) -> &Vec<String> {
        &self.warnings
    }

    /// The alerts from the checks.
    pub fn alerts(&self) -> &Vec<String> {
        &self.alerts
    }

    /// The errors from the checks.
    pub fn errors(&self) -> &Vec<String> {
        &self.errors
    }

    /// Whether there are temporary messages or not.
    pub fn temporary(&self) -> bool {
        self.temporary
    }

    /// Whether the checks will allow the commit no matter what.
    pub fn allowed(&self) -> bool {
        self.allow
    }

    /// Whether the checks passed or failed.
    pub fn pass(&self) -> bool {
        self.pass
    }

    /// Combine two results together.
    pub fn combine(self, other: Self) -> Self {
        CheckResult {
            warnings: self.warnings.into_iter().chain(other.warnings.into_iter()).collect(),
            alerts: self.alerts.into_iter().chain(other.alerts.into_iter()).collect(),
            errors: self.errors.into_iter().chain(other.errors.into_iter()).collect(),

            temporary: self.temporary || other.temporary,
            allow: self.allow || other.allow,
            pass: self.pass && other.pass,
        }
    }
}

/// Interface for checks which run for each commit.
pub trait Check: Send + Sync {
    /// The name of the check.
    fn name(&self) -> &str;

    /// Run the check.
    fn check(&self, ctx: &CheckGitContext, commit: &Commit) -> Result<CheckResult>;
}

/// Interface for checks which runs once for the entire branch.
pub trait BranchCheck: Send + Sync {
    /// The name of the check.
    fn name(&self) -> &str;

    /// Run the check.
    fn check(&self, ctx: &CheckGitContext, commit: &CommitId) -> Result<CheckResult>;
}