gitbox 2.1.3

Git toolbox to simplify adoption of conventional commits and semantic version, among other things.
Documentation
use crate::{
    application::{
        manager::{
            conventional_commit_egress_manager::ConventionalCommitEgressManager,
            message_egress_manager::MessageEgressManager,
        },
        options::commit::CommitOptions,
        repository_impl::conventional_commit_egress_repository_impl::ConventionalCommitEgressRepositoryImpl,
    },
    usecase::{
        configuration::commit::{AllowEmptyFlag, CommitConfiguration},
        usecases::{create_conventional_commit::CreateConventionalCommitUseCase, usecase::UseCase},
    },
};

use super::exit_code::ControllerExitCode;

pub struct CommitController<'a> {
    options: CommitOptions,
    commit_manager: &'a dyn ConventionalCommitEgressManager,
    output_manager: &'a dyn MessageEgressManager,
}

impl<'a, 'b: 'a, 'c: 'a> CommitController<'a> {
    pub fn new(
        options: CommitOptions,
        commit_manager: &'b dyn ConventionalCommitEgressManager,
        output_manager: &'c dyn MessageEgressManager,
    ) -> Self {
        CommitController {
            options,
            commit_manager,
            output_manager,
        }
    }

    pub fn commit(&self) -> ControllerExitCode {
        match CommitConfiguration::new(
            self.options.commit_type().to_string(),
            self.options.scope().map(|it| it.to_owned()),
            self.options.is_breaking(),
            self.options.summary().to_string(),
            self.options.message().map(|it| it.to_owned()),
            AllowEmptyFlag::Disabled,
        ) {
            Ok(configuration) => {
                let commit_repository =
                    ConventionalCommitEgressRepositoryImpl::new(self.commit_manager);
                let usecase =
                    CreateConventionalCommitUseCase::new(configuration, &commit_repository);
                match usecase.execute() {
                    Ok(c) => {
                        if !self.options.quiet() {
                            self.output_manager.output(&c.to_string());
                        }
                        ControllerExitCode::Ok
                    }
                    Err(e) => {
                        self.output_manager.error(&e.to_string());
                        ControllerExitCode::Error(1)
                    }
                }
            }
            Err(e) => {
                self.output_manager.error(&e.to_string());
                ControllerExitCode::Error(1)
            }
        }
    }
}

#[cfg(test)]
mod tests {
    use std::{error::Error, fmt::Display};

    use crate::{
        application::{
            controller::{commit::CommitController, exit_code::ControllerExitCode},
            manager::{
                conventional_commit_egress_manager::ConventionalCommitEgressManager,
                message_egress_manager::MessageEgressManager,
            },
            options::commit::CommitOptions,
        },
        usecase::type_aliases::AnyError,
    };

    #[derive(Debug)]
    struct MockError {}

    impl Display for MockError {
        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
            write!(f, "Mock error")
        }
    }

    impl Error for MockError {}

    struct MockCommitManager {
        fail: bool,
    }

    impl ConventionalCommitEgressManager for MockCommitManager {
        fn create_empty_commit(&self, _commit: &str) -> Result<(), AnyError> {
            if self.fail {
                Err(Box::new(MockError {}))
            } else {
                Ok(())
            }
        }
        fn create_commit(&self, _commit: &str) -> Result<(), AnyError> {
            if self.fail {
                Err(Box::new(MockError {}))
            } else {
                Ok(())
            }
        }
    }

    struct MockOutputManager {}

    impl MessageEgressManager for MockOutputManager {
        fn error(&self, _error: &str) {}
        fn output(&self, _message: &str) {}
    }

    #[test]
    fn commit_ok() {
        let options = CommitOptions::new(
            "test".to_string(),
            None,
            false,
            "test".to_string(),
            None,
            false,
        )
        .expect("commit options are hand made");
        let commit_manager = MockCommitManager { fail: false };
        let output_manager = MockOutputManager {};
        let controller = CommitController::new(options, &commit_manager, &output_manager);
        let result = controller.commit();
        assert!(matches!(result, ControllerExitCode::Ok));
    }

    #[test]
    fn commit_error() {
        let options = CommitOptions::new(
            "test".to_string(),
            None,
            false,
            "test".to_string(),
            None,
            false,
        )
        .expect("commit options are hand made");
        let commit_manager = MockCommitManager { fail: true };
        let output_manager = MockOutputManager {};
        let controller = CommitController::new(options, &commit_manager, &output_manager);
        let result = controller.commit();
        assert!(matches!(result, ControllerExitCode::Error(..)));
    }
}