use std::{env::Args, sync::Arc}; use async_trait::async_trait; pub struct CuddleCI { pr_action: Arc, main_action: Arc, release_action: Arc, } impl CuddleCI { pub fn new( pr: Arc, main: Arc, release: Arc, ) -> Self { Self { pr_action: pr, main_action: main, release_action: release, } } pub fn with_pull_request(&mut self, pr: Arc) -> &mut Self { self.pr_action = pr; self } pub fn with_main(&mut self, main: Arc) -> &mut Self { self.main_action = main; self } pub fn with_release(&mut self, release: Arc) -> &mut Self { self.release_action = release; self } pub async fn execute(&mut self, args: impl IntoIterator) -> eyre::Result<()> { let matches = clap::Command::new("cuddle-ci") .about("is a wrapper around common CI actions") .subcommand(clap::Command::new("pr")) .subcommand(clap::Command::new("main")) .subcommand(clap::Command::new("release")) .subcommand_required(true) .try_get_matches_from(args.into_iter())?; match matches.subcommand() { Some((name, args)) => match (name, args) { ("pr", args) => { eprintln!("starting pr validate"); self.pr_action.execute_pull_request().await?; eprintln!("finished pr validate"); } ("main", args) => { eprintln!("starting main validate"); self.main_action.execute_main().await?; eprintln!("finished main validate"); } ("release", args) => { eprintln!("starting release validate"); self.release_action.execute_release().await?; eprintln!("finished release validate"); } (command_name, _) => { eyre::bail!("command is not recognized: {}", command_name) } }, None => eyre::bail!("command required a subcommand [pr, main, release] etc."), } Ok(()) } } impl Default for CuddleCI { fn default() -> Self { Self::new( Arc::new(DefaultPullRequestAction {}), Arc::new(DefaultMainAction {}), Arc::new(DefaultReleaseAction {}), ) } } #[async_trait] pub trait PullRequestAction { async fn execute_pull_request(&self) -> eyre::Result<()> { eprintln!("validate pull request: noop"); Ok(()) } } pub struct DefaultPullRequestAction {} #[async_trait] impl PullRequestAction for DefaultPullRequestAction {} #[async_trait] pub trait MainAction { async fn execute_main(&self) -> eyre::Result<()> { eprintln!("validate main: noop"); Ok(()) } } pub struct DefaultMainAction {} #[async_trait] impl MainAction for DefaultMainAction {} #[async_trait] pub trait ReleaseAction { async fn execute_release(&self) -> eyre::Result<()> { eprintln!("validate release: noop"); Ok(()) } } pub struct DefaultReleaseAction {} #[async_trait] impl ReleaseAction for DefaultReleaseAction {} #[cfg(test)] mod test { use super::*; #[tokio::test] async fn test_can_call_default() -> eyre::Result<()> { CuddleCI::default().execute(["cuddle-ci", "pr"]).await?; Ok(()) } #[tokio::test] async fn test_fails_on_no_command() -> eyre::Result<()> { let res = CuddleCI::default().execute(["cuddle-ci"]).await; assert!(res.is_err()); Ok(()) } #[tokio::test] async fn test_fails_on_wrong_command() -> eyre::Result<()> { let res = CuddleCI::default() .execute(["cuddle-ci", "something"]) .await; assert!(res.is_err()); Ok(()) } }