diff --git a/src/lib.rs b/src/lib.rs index 74c87b4..2b21eb5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -11,4 +11,5 @@ pub mod http; pub mod identity_access; pub mod operation_policy; pub mod security_boundary; +pub mod strategy_governance; pub mod style_selection; diff --git a/src/strategy_governance.rs b/src/strategy_governance.rs new file mode 100644 index 0000000..8067fb4 --- /dev/null +++ b/src/strategy_governance.rs @@ -0,0 +1,82 @@ +//! Modelo mínimo para propiedad y revisión de una capacidad pública. + +use std::fmt; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum ChangeImpact { + Low, + High, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct ApiCapability { + owner: String, + consumer: String, + impact: ChangeImpact, +} + +impl ApiCapability { + pub fn new( + owner: impl Into, + consumer: impl Into, + impact: ChangeImpact, + ) -> Result { + Ok(Self { + owner: required(owner.into(), "dueño")?, + consumer: required(consumer.into(), "consumidor")?, + impact, + }) + } + + pub fn requires_human_review(&self) -> bool { + self.impact == ChangeImpact::High + } + pub fn owner(&self) -> &str { + &self.owner + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum GovernanceError { + EmptyText { subject: &'static str }, +} + +impl fmt::Display for GovernanceError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::EmptyText { subject } => write!(f, "El {subject} no puede estar vacío."), + } + } +} +impl std::error::Error for GovernanceError {} + +fn required(value: String, subject: &'static str) -> Result { + let value = value.trim().to_owned(); + if value.is_empty() { + return Err(GovernanceError::EmptyText { subject }); + } + Ok(value) +} + +#[cfg(test)] +mod tests { + use super::{ApiCapability, ChangeImpact, GovernanceError}; + #[test] + fn requires_review_for_a_high_impact_capability() { + let capability = ApiCapability::new("payments", "mobile", ChangeImpact::High).unwrap(); + assert!(capability.requires_human_review()); + assert_eq!(capability.owner(), "payments"); + } + #[test] + fn allows_low_impact_change_without_human_review() { + let capability = ApiCapability::new("catalog", "web", ChangeImpact::Low).unwrap(); + assert!(!capability.requires_human_review()); + } + #[test] + fn rejects_capability_without_owner() { + assert_eq!( + ApiCapability::new("", "web", ChangeImpact::Low).unwrap_err(), + GovernanceError::EmptyText { subject: "dueño" } + ); + } +}