diff --git a/rust/crates/runtime/src/green_contract.rs b/rust/crates/runtime/src/green_contract.rs new file mode 100644 index 0000000..d65ce91 --- /dev/null +++ b/rust/crates/runtime/src/green_contract.rs @@ -0,0 +1,152 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +pub enum GreenLevel { + TargetedTests, + Package, + Workspace, + MergeReady, +} + +impl GreenLevel { + #[must_use] + pub fn as_str(self) -> &'static str { + match self { + Self::TargetedTests => "targeted_tests", + Self::Package => "package", + Self::Workspace => "workspace", + Self::MergeReady => "merge_ready", + } + } +} + +impl std::fmt::Display for GreenLevel { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.as_str()) + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +pub struct GreenContract { + pub required_level: GreenLevel, +} + +impl GreenContract { + #[must_use] + pub fn new(required_level: GreenLevel) -> Self { + Self { required_level } + } + + #[must_use] + pub fn evaluate(self, observed_level: Option) -> GreenContractOutcome { + match observed_level { + Some(level) if level >= self.required_level => GreenContractOutcome::Satisfied { + required_level: self.required_level, + observed_level: level, + }, + _ => GreenContractOutcome::Unsatisfied { + required_level: self.required_level, + observed_level, + }, + } + } + + #[must_use] + pub fn is_satisfied_by(self, observed_level: GreenLevel) -> bool { + observed_level >= self.required_level + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(tag = "outcome", rename_all = "snake_case")] +pub enum GreenContractOutcome { + Satisfied { + required_level: GreenLevel, + observed_level: GreenLevel, + }, + Unsatisfied { + required_level: GreenLevel, + observed_level: Option, + }, +} + +impl GreenContractOutcome { + #[must_use] + pub fn is_satisfied(&self) -> bool { + matches!(self, Self::Satisfied { .. }) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn given_matching_level_when_evaluating_contract_then_it_is_satisfied() { + // given + let contract = GreenContract::new(GreenLevel::Package); + + // when + let outcome = contract.evaluate(Some(GreenLevel::Package)); + + // then + assert_eq!( + outcome, + GreenContractOutcome::Satisfied { + required_level: GreenLevel::Package, + observed_level: GreenLevel::Package, + } + ); + assert!(outcome.is_satisfied()); + } + + #[test] + fn given_higher_level_when_checking_requirement_then_it_still_satisfies_contract() { + // given + let contract = GreenContract::new(GreenLevel::TargetedTests); + + // when + let is_satisfied = contract.is_satisfied_by(GreenLevel::Workspace); + + // then + assert!(is_satisfied); + } + + #[test] + fn given_lower_level_when_evaluating_contract_then_it_is_unsatisfied() { + // given + let contract = GreenContract::new(GreenLevel::Workspace); + + // when + let outcome = contract.evaluate(Some(GreenLevel::Package)); + + // then + assert_eq!( + outcome, + GreenContractOutcome::Unsatisfied { + required_level: GreenLevel::Workspace, + observed_level: Some(GreenLevel::Package), + } + ); + assert!(!outcome.is_satisfied()); + } + + #[test] + fn given_no_green_level_when_evaluating_contract_then_contract_is_unsatisfied() { + // given + let contract = GreenContract::new(GreenLevel::MergeReady); + + // when + let outcome = contract.evaluate(None); + + // then + assert_eq!( + outcome, + GreenContractOutcome::Unsatisfied { + required_level: GreenLevel::MergeReady, + observed_level: None, + } + ); + } +} diff --git a/rust/crates/runtime/src/lib.rs b/rust/crates/runtime/src/lib.rs index fb2d95d..53482cb 100644 --- a/rust/crates/runtime/src/lib.rs +++ b/rust/crates/runtime/src/lib.rs @@ -5,6 +5,7 @@ mod compact; mod config; mod conversation; mod file_ops; +pub mod green_contract; mod hooks; mod json; pub mod lsp_client;