use std::sync::Arc; use async_trait::async_trait; use tokio::sync::Mutex; pub struct CuddleCI { pr_action: Vec>>, main_action: Vec>>, release_action: Vec>>, } impl CuddleCI { pub fn new( pr: Arc>, main: Arc>, release: Arc>, ) -> Self { Self { pr_action: vec![pr], main_action: vec![main], release_action: vec![release], } } pub fn with_pull_request( &mut self, pr: Arc>, ) -> &mut Self { self.pr_action.push(pr); self } pub fn with_main(&mut self, main: Arc>) -> &mut Self { self.main_action.push(main); self } pub fn with_release( &mut self, release: Arc>, ) -> &mut Self { self.release_action.push(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().map(|a| a.into()).collect::>())?; match matches.subcommand() { Some((name, args)) => match (name, args) { ("pr", _args) => { eprintln!("starting pr validate"); for pr_action in self.pr_action.iter() { pr_action.lock().await.execute_pull_request().await?; } eprintln!("finished pr validate"); } ("main", _args) => { eprintln!("starting main validate"); for main_action in self.main_action.iter() { main_action.lock().await.execute_main().await?; } eprintln!("finished main validate"); } ("release", _args) => { eprintln!("starting release validate"); for release_action in self.release_action.iter() { release_action.lock().await.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(Mutex::new(DefaultPullRequestAction {})), Arc::new(Mutex::new(DefaultMainAction {})), Arc::new(Mutex::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<()>; } pub struct DefaultMainAction {} #[async_trait] impl MainAction for DefaultMainAction { async fn execute_main(&self) -> eyre::Result<()> { Ok(()) } } #[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(()) } }