#![allow(
clippy::bool_comparison,
clippy::field_reassign_with_default,
clippy::never_loop,
clippy::nonminimal_bool,
clippy::partialeq_to_none,
clippy::redundant_clone
)]
use std::{
collections::{BTreeMap, BTreeSet},
fmt,
};
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub enum ApprovalLifecycleStatus {
#[default]
Pending,
Approved,
Denied,
Expired,
Cancelled,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub enum ApprovalLifecycleDecision {
#[default]
Approve,
Deny,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub enum ApprovalLifecycleRejectionReason {
#[default]
NotFound,
AlreadyExists,
AlreadyDecided,
Expired,
InvalidDecision,
EmptyAllowedDecisions,
InvalidRestoredRecord,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ApprovalLifecycleInput {
CreateApproval {
approval_id: String,
approve_allowed: bool,
deny_allowed: bool,
has_expiry: bool,
},
RestoreApproval {
approval_id: String,
status: ApprovalLifecycleStatus,
approve_allowed: bool,
deny_allowed: bool,
has_expiry: bool,
decision: Option<ApprovalLifecycleDecision>,
},
ObserveApprovalExpiry {
approval_id: String,
expired: bool,
},
DecideApproval {
approval_id: String,
decision: ApprovalLifecycleDecision,
},
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ApprovalLifecycleEffect {
ApprovalStatusResolved {
approval_id: String,
status: ApprovalLifecycleStatus,
},
ApprovalLifecycleRejected {
approval_id: String,
reason: ApprovalLifecycleRejectionReason,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ApprovalLifecycleOutcome {
Status(ApprovalLifecycleStatus),
Rejected(ApprovalLifecycleRejectionReason),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ApprovalLifecycleError {
op: &'static str,
}
impl fmt::Display for ApprovalLifecycleError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"generated approval lifecycle authority rejected {}",
self.op
)
}
}
impl std::error::Error for ApprovalLifecycleError {}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub enum ApprovalLifecyclePhase {
#[default]
Ready,
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct ApprovalLifecycleMachineState {
lifecycle_phase: ApprovalLifecyclePhase,
approval_ids: BTreeSet<String>,
approval_statuses: BTreeMap<String, ApprovalLifecycleStatus>,
approval_approve_allowed: BTreeMap<String, bool>,
approval_deny_allowed: BTreeMap<String, bool>,
approval_has_expiry: BTreeMap<String, bool>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum ApprovalLifecycleTransition {
CreateRejectedEmptyAllowedDecisions,
CreateRejectedAlreadyExists,
CreatePending,
RestoreRejectedDuplicate,
RestoreRejectedEmptyAllowedDecisions,
RestorePending,
RestoreExpired,
RestoreCancelled,
RestoreApproved,
RestoreDenied,
RestoreRejectedInvalidRecord,
ObserveExpiryRejectedMissing,
ObserveExpiryExpiresPending,
ObserveExpiryPendingNoop,
ObserveExpiryApprovedNoop,
ObserveExpiryDeniedNoop,
ObserveExpiryExpiredNoop,
ObserveExpiryCancelledNoop,
DecideRejectedMissing,
DecideRejectedExpired,
DecideRejectedAlreadyDecided,
DecideRejectedApproveNotAllowed,
DecideRejectedDenyNotAllowed,
DecideApprove,
DecideDeny,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ApprovalLifecycleMachineAuthority {
state: ApprovalLifecycleMachineState,
}
impl ApprovalLifecycleMachineAuthority {
#[must_use]
pub fn new() -> Self {
let mut state = ApprovalLifecycleMachineState::default();
state.lifecycle_phase = ApprovalLifecyclePhase::Ready;
state.approval_ids = BTreeSet::new();
state.approval_statuses = BTreeMap::new();
state.approval_approve_allowed = BTreeMap::new();
state.approval_deny_allowed = BTreeMap::new();
state.approval_has_expiry = BTreeMap::new();
Self { state }
}
#[must_use]
pub fn state(&self) -> &ApprovalLifecycleMachineState {
&self.state
}
#[must_use]
pub fn status_for(&self, approval_id: &str) -> Option<ApprovalLifecycleStatus> {
self.state.approval_statuses.get(approval_id).copied()
}
fn approval_statuses_value(
&self,
approval_id: &str,
) -> Result<ApprovalLifecycleStatus, ApprovalLifecycleError> {
self.state
.approval_statuses
.get(approval_id)
.copied()
.ok_or(ApprovalLifecycleError {
op: "approval_statuses",
})
}
fn approval_approve_allowed_value(
&self,
approval_id: &str,
) -> Result<bool, ApprovalLifecycleError> {
self.state
.approval_approve_allowed
.get(approval_id)
.copied()
.ok_or(ApprovalLifecycleError {
op: "approval_approve_allowed",
})
}
fn approval_deny_allowed_value(
&self,
approval_id: &str,
) -> Result<bool, ApprovalLifecycleError> {
self.state
.approval_deny_allowed
.get(approval_id)
.copied()
.ok_or(ApprovalLifecycleError {
op: "approval_deny_allowed",
})
}
fn approval_has_expiry_value(&self, approval_id: &str) -> Result<bool, ApprovalLifecycleError> {
self.state
.approval_has_expiry
.get(approval_id)
.copied()
.ok_or(ApprovalLifecycleError {
op: "approval_has_expiry",
})
}
fn single_transition(
matches: Vec<ApprovalLifecycleTransition>,
op: &'static str,
) -> Result<ApprovalLifecycleTransition, ApprovalLifecycleError> {
let mut matches = matches.into_iter();
let Some(first) = matches.next() else {
return Err(ApprovalLifecycleError { op });
};
if matches.next().is_some() {
return Err(ApprovalLifecycleError { op });
}
Ok(first)
}
fn apply_input(
&mut self,
input: ApprovalLifecycleInput,
) -> Result<Vec<ApprovalLifecycleEffect>, ApprovalLifecycleError> {
match input {
ApprovalLifecycleInput::CreateApproval {
approval_id,
approve_allowed,
deny_allowed,
has_expiry,
} => {
let mut matches = Vec::new();
if (self.state.lifecycle_phase == ApprovalLifecyclePhase::Ready)
&& (allowed_non_empty(approve_allowed, deny_allowed) == false)
{
matches.push(ApprovalLifecycleTransition::CreateRejectedEmptyAllowedDecisions);
}
if (self.state.lifecycle_phase == ApprovalLifecyclePhase::Ready)
&& ((allowed_non_empty(approve_allowed, deny_allowed))
&& (self.state.approval_ids.contains(approval_id.as_str())))
{
matches.push(ApprovalLifecycleTransition::CreateRejectedAlreadyExists);
}
if (self.state.lifecycle_phase == ApprovalLifecyclePhase::Ready)
&& ((allowed_non_empty(approve_allowed, deny_allowed))
&& (self.state.approval_ids.contains(approval_id.as_str()) == false))
{
matches.push(ApprovalLifecycleTransition::CreatePending);
}
let transition = Self::single_transition(matches, "CreateApproval")?;
match transition {
ApprovalLifecycleTransition::CreateRejectedEmptyAllowedDecisions => {
self.state.lifecycle_phase = ApprovalLifecyclePhase::Ready;
Ok(vec![ApprovalLifecycleEffect::ApprovalLifecycleRejected {
approval_id: approval_id.clone(),
reason: ApprovalLifecycleRejectionReason::EmptyAllowedDecisions,
}])
}
ApprovalLifecycleTransition::CreateRejectedAlreadyExists => {
self.state.lifecycle_phase = ApprovalLifecyclePhase::Ready;
Ok(vec![ApprovalLifecycleEffect::ApprovalLifecycleRejected {
approval_id: approval_id.clone(),
reason: ApprovalLifecycleRejectionReason::AlreadyExists,
}])
}
ApprovalLifecycleTransition::CreatePending => {
self.state.approval_ids.insert(approval_id.clone());
self.state
.approval_statuses
.insert(approval_id.clone(), ApprovalLifecycleStatus::Pending);
self.state
.approval_approve_allowed
.insert(approval_id.clone(), approve_allowed);
self.state
.approval_deny_allowed
.insert(approval_id.clone(), deny_allowed);
self.state
.approval_has_expiry
.insert(approval_id.clone(), has_expiry);
self.state.lifecycle_phase = ApprovalLifecyclePhase::Ready;
Ok(vec![ApprovalLifecycleEffect::ApprovalStatusResolved {
approval_id: approval_id.clone(),
status: ApprovalLifecycleStatus::Pending,
}])
}
_ => Err(ApprovalLifecycleError {
op: "CreateApproval_transition",
}),
}
}
ApprovalLifecycleInput::RestoreApproval {
approval_id,
status,
approve_allowed,
deny_allowed,
has_expiry,
decision,
} => {
let mut matches = Vec::new();
if (self.state.lifecycle_phase == ApprovalLifecyclePhase::Ready)
&& (self.state.approval_ids.contains(approval_id.as_str()))
{
matches.push(ApprovalLifecycleTransition::RestoreRejectedDuplicate);
}
if (self.state.lifecycle_phase == ApprovalLifecyclePhase::Ready)
&& ((self.state.approval_ids.contains(approval_id.as_str()) == false)
&& (allowed_non_empty(approve_allowed, deny_allowed) == false))
{
matches.push(ApprovalLifecycleTransition::RestoreRejectedEmptyAllowedDecisions);
}
if (self.state.lifecycle_phase == ApprovalLifecyclePhase::Ready)
&& ((self.state.approval_ids.contains(approval_id.as_str()) == false)
&& (allowed_non_empty(approve_allowed, deny_allowed))
&& (status == ApprovalLifecycleStatus::Pending)
&& (decision == None))
{
matches.push(ApprovalLifecycleTransition::RestorePending);
}
if (self.state.lifecycle_phase == ApprovalLifecyclePhase::Ready)
&& ((self.state.approval_ids.contains(approval_id.as_str()) == false)
&& (allowed_non_empty(approve_allowed, deny_allowed))
&& (status == ApprovalLifecycleStatus::Expired)
&& (decision == None))
{
matches.push(ApprovalLifecycleTransition::RestoreExpired);
}
if (self.state.lifecycle_phase == ApprovalLifecyclePhase::Ready)
&& ((self.state.approval_ids.contains(approval_id.as_str()) == false)
&& (allowed_non_empty(approve_allowed, deny_allowed))
&& (status == ApprovalLifecycleStatus::Cancelled)
&& (decision == None))
{
matches.push(ApprovalLifecycleTransition::RestoreCancelled);
}
if (self.state.lifecycle_phase == ApprovalLifecyclePhase::Ready)
&& ((self.state.approval_ids.contains(approval_id.as_str()) == false)
&& (allowed_non_empty(approve_allowed, deny_allowed))
&& (approve_allowed)
&& (status == ApprovalLifecycleStatus::Approved)
&& (decision == Some(ApprovalLifecycleDecision::Approve)))
{
matches.push(ApprovalLifecycleTransition::RestoreApproved);
}
if (self.state.lifecycle_phase == ApprovalLifecyclePhase::Ready)
&& ((self.state.approval_ids.contains(approval_id.as_str()) == false)
&& (allowed_non_empty(approve_allowed, deny_allowed))
&& (deny_allowed)
&& (status == ApprovalLifecycleStatus::Denied)
&& (decision == Some(ApprovalLifecycleDecision::Deny)))
{
matches.push(ApprovalLifecycleTransition::RestoreDenied);
}
if (self.state.lifecycle_phase == ApprovalLifecyclePhase::Ready)
&& ((self.state.approval_ids.contains(approval_id.as_str()) == false)
&& (allowed_non_empty(approve_allowed, deny_allowed))
&& (((status == ApprovalLifecycleStatus::Pending) && (decision != None))
|| ((status == ApprovalLifecycleStatus::Expired)
&& (decision != None))
|| ((status == ApprovalLifecycleStatus::Cancelled)
&& (decision != None))
|| ((status == ApprovalLifecycleStatus::Approved)
&& ((approve_allowed == false)
|| (decision != Some(ApprovalLifecycleDecision::Approve))))
|| ((status == ApprovalLifecycleStatus::Denied)
&& ((deny_allowed == false)
|| (decision != Some(ApprovalLifecycleDecision::Deny))))))
{
matches.push(ApprovalLifecycleTransition::RestoreRejectedInvalidRecord);
}
let transition = Self::single_transition(matches, "RestoreApproval")?;
match transition {
ApprovalLifecycleTransition::RestoreRejectedDuplicate => {
self.state.lifecycle_phase = ApprovalLifecyclePhase::Ready;
Ok(vec![ApprovalLifecycleEffect::ApprovalLifecycleRejected {
approval_id: approval_id.clone(),
reason: ApprovalLifecycleRejectionReason::AlreadyExists,
}])
}
ApprovalLifecycleTransition::RestoreRejectedEmptyAllowedDecisions => {
self.state.lifecycle_phase = ApprovalLifecyclePhase::Ready;
Ok(vec![ApprovalLifecycleEffect::ApprovalLifecycleRejected {
approval_id: approval_id.clone(),
reason: ApprovalLifecycleRejectionReason::EmptyAllowedDecisions,
}])
}
ApprovalLifecycleTransition::RestorePending => {
self.state.approval_ids.insert(approval_id.clone());
self.state
.approval_statuses
.insert(approval_id.clone(), ApprovalLifecycleStatus::Pending);
self.state
.approval_approve_allowed
.insert(approval_id.clone(), approve_allowed);
self.state
.approval_deny_allowed
.insert(approval_id.clone(), deny_allowed);
self.state
.approval_has_expiry
.insert(approval_id.clone(), has_expiry);
self.state.lifecycle_phase = ApprovalLifecyclePhase::Ready;
Ok(vec![ApprovalLifecycleEffect::ApprovalStatusResolved {
approval_id: approval_id.clone(),
status: ApprovalLifecycleStatus::Pending,
}])
}
ApprovalLifecycleTransition::RestoreExpired => {
self.state.approval_ids.insert(approval_id.clone());
self.state
.approval_statuses
.insert(approval_id.clone(), ApprovalLifecycleStatus::Expired);
self.state
.approval_approve_allowed
.insert(approval_id.clone(), approve_allowed);
self.state
.approval_deny_allowed
.insert(approval_id.clone(), deny_allowed);
self.state
.approval_has_expiry
.insert(approval_id.clone(), has_expiry);
self.state.lifecycle_phase = ApprovalLifecyclePhase::Ready;
Ok(vec![ApprovalLifecycleEffect::ApprovalStatusResolved {
approval_id: approval_id.clone(),
status: ApprovalLifecycleStatus::Expired,
}])
}
ApprovalLifecycleTransition::RestoreCancelled => {
self.state.approval_ids.insert(approval_id.clone());
self.state
.approval_statuses
.insert(approval_id.clone(), ApprovalLifecycleStatus::Cancelled);
self.state
.approval_approve_allowed
.insert(approval_id.clone(), approve_allowed);
self.state
.approval_deny_allowed
.insert(approval_id.clone(), deny_allowed);
self.state
.approval_has_expiry
.insert(approval_id.clone(), has_expiry);
self.state.lifecycle_phase = ApprovalLifecyclePhase::Ready;
Ok(vec![ApprovalLifecycleEffect::ApprovalStatusResolved {
approval_id: approval_id.clone(),
status: ApprovalLifecycleStatus::Cancelled,
}])
}
ApprovalLifecycleTransition::RestoreApproved => {
self.state.approval_ids.insert(approval_id.clone());
self.state
.approval_statuses
.insert(approval_id.clone(), ApprovalLifecycleStatus::Approved);
self.state
.approval_approve_allowed
.insert(approval_id.clone(), approve_allowed);
self.state
.approval_deny_allowed
.insert(approval_id.clone(), deny_allowed);
self.state
.approval_has_expiry
.insert(approval_id.clone(), has_expiry);
self.state.lifecycle_phase = ApprovalLifecyclePhase::Ready;
Ok(vec![ApprovalLifecycleEffect::ApprovalStatusResolved {
approval_id: approval_id.clone(),
status: ApprovalLifecycleStatus::Approved,
}])
}
ApprovalLifecycleTransition::RestoreDenied => {
self.state.approval_ids.insert(approval_id.clone());
self.state
.approval_statuses
.insert(approval_id.clone(), ApprovalLifecycleStatus::Denied);
self.state
.approval_approve_allowed
.insert(approval_id.clone(), approve_allowed);
self.state
.approval_deny_allowed
.insert(approval_id.clone(), deny_allowed);
self.state
.approval_has_expiry
.insert(approval_id.clone(), has_expiry);
self.state.lifecycle_phase = ApprovalLifecyclePhase::Ready;
Ok(vec![ApprovalLifecycleEffect::ApprovalStatusResolved {
approval_id: approval_id.clone(),
status: ApprovalLifecycleStatus::Denied,
}])
}
ApprovalLifecycleTransition::RestoreRejectedInvalidRecord => {
self.state.lifecycle_phase = ApprovalLifecyclePhase::Ready;
Ok(vec![ApprovalLifecycleEffect::ApprovalLifecycleRejected {
approval_id: approval_id.clone(),
reason: ApprovalLifecycleRejectionReason::InvalidRestoredRecord,
}])
}
_ => Err(ApprovalLifecycleError {
op: "RestoreApproval_transition",
}),
}
}
ApprovalLifecycleInput::ObserveApprovalExpiry {
approval_id,
expired,
} => {
let mut matches = Vec::new();
if (self.state.lifecycle_phase == ApprovalLifecyclePhase::Ready)
&& (self.state.approval_ids.contains(approval_id.as_str()) == false)
{
matches.push(ApprovalLifecycleTransition::ObserveExpiryRejectedMissing);
}
if (self.state.lifecycle_phase == ApprovalLifecyclePhase::Ready)
&& ((self.state.approval_ids.contains(approval_id.as_str()))
&& (self.approval_statuses_value(approval_id.as_str())?
== ApprovalLifecycleStatus::Pending)
&& (self.approval_has_expiry_value(approval_id.as_str())?)
&& (expired))
{
matches.push(ApprovalLifecycleTransition::ObserveExpiryExpiresPending);
}
if (self.state.lifecycle_phase == ApprovalLifecyclePhase::Ready)
&& ((self.state.approval_ids.contains(approval_id.as_str()))
&& (self.approval_statuses_value(approval_id.as_str())?
== ApprovalLifecycleStatus::Pending)
&& ((self.approval_has_expiry_value(approval_id.as_str())? == false)
|| (expired == false)))
{
matches.push(ApprovalLifecycleTransition::ObserveExpiryPendingNoop);
}
if (self.state.lifecycle_phase == ApprovalLifecyclePhase::Ready)
&& ((self.state.approval_ids.contains(approval_id.as_str()))
&& (self.approval_statuses_value(approval_id.as_str())?
== ApprovalLifecycleStatus::Approved))
{
matches.push(ApprovalLifecycleTransition::ObserveExpiryApprovedNoop);
}
if (self.state.lifecycle_phase == ApprovalLifecyclePhase::Ready)
&& ((self.state.approval_ids.contains(approval_id.as_str()))
&& (self.approval_statuses_value(approval_id.as_str())?
== ApprovalLifecycleStatus::Denied))
{
matches.push(ApprovalLifecycleTransition::ObserveExpiryDeniedNoop);
}
if (self.state.lifecycle_phase == ApprovalLifecyclePhase::Ready)
&& ((self.state.approval_ids.contains(approval_id.as_str()))
&& (self.approval_statuses_value(approval_id.as_str())?
== ApprovalLifecycleStatus::Expired))
{
matches.push(ApprovalLifecycleTransition::ObserveExpiryExpiredNoop);
}
if (self.state.lifecycle_phase == ApprovalLifecyclePhase::Ready)
&& ((self.state.approval_ids.contains(approval_id.as_str()))
&& (self.approval_statuses_value(approval_id.as_str())?
== ApprovalLifecycleStatus::Cancelled))
{
matches.push(ApprovalLifecycleTransition::ObserveExpiryCancelledNoop);
}
let transition = Self::single_transition(matches, "ObserveApprovalExpiry")?;
match transition {
ApprovalLifecycleTransition::ObserveExpiryRejectedMissing => {
self.state.lifecycle_phase = ApprovalLifecyclePhase::Ready;
Ok(vec![ApprovalLifecycleEffect::ApprovalLifecycleRejected {
approval_id: approval_id.clone(),
reason: ApprovalLifecycleRejectionReason::NotFound,
}])
}
ApprovalLifecycleTransition::ObserveExpiryExpiresPending => {
self.state
.approval_statuses
.insert(approval_id.clone(), ApprovalLifecycleStatus::Expired);
self.state.lifecycle_phase = ApprovalLifecyclePhase::Ready;
Ok(vec![ApprovalLifecycleEffect::ApprovalStatusResolved {
approval_id: approval_id.clone(),
status: ApprovalLifecycleStatus::Expired,
}])
}
ApprovalLifecycleTransition::ObserveExpiryPendingNoop => {
self.state.lifecycle_phase = ApprovalLifecyclePhase::Ready;
Ok(vec![ApprovalLifecycleEffect::ApprovalStatusResolved {
approval_id: approval_id.clone(),
status: ApprovalLifecycleStatus::Pending,
}])
}
ApprovalLifecycleTransition::ObserveExpiryApprovedNoop => {
self.state.lifecycle_phase = ApprovalLifecyclePhase::Ready;
Ok(vec![ApprovalLifecycleEffect::ApprovalStatusResolved {
approval_id: approval_id.clone(),
status: ApprovalLifecycleStatus::Approved,
}])
}
ApprovalLifecycleTransition::ObserveExpiryDeniedNoop => {
self.state.lifecycle_phase = ApprovalLifecyclePhase::Ready;
Ok(vec![ApprovalLifecycleEffect::ApprovalStatusResolved {
approval_id: approval_id.clone(),
status: ApprovalLifecycleStatus::Denied,
}])
}
ApprovalLifecycleTransition::ObserveExpiryExpiredNoop => {
self.state.lifecycle_phase = ApprovalLifecyclePhase::Ready;
Ok(vec![ApprovalLifecycleEffect::ApprovalStatusResolved {
approval_id: approval_id.clone(),
status: ApprovalLifecycleStatus::Expired,
}])
}
ApprovalLifecycleTransition::ObserveExpiryCancelledNoop => {
self.state.lifecycle_phase = ApprovalLifecyclePhase::Ready;
Ok(vec![ApprovalLifecycleEffect::ApprovalStatusResolved {
approval_id: approval_id.clone(),
status: ApprovalLifecycleStatus::Cancelled,
}])
}
_ => Err(ApprovalLifecycleError {
op: "ObserveApprovalExpiry_transition",
}),
}
}
ApprovalLifecycleInput::DecideApproval {
approval_id,
decision,
} => {
let mut matches = Vec::new();
if (self.state.lifecycle_phase == ApprovalLifecyclePhase::Ready)
&& (self.state.approval_ids.contains(approval_id.as_str()) == false)
{
matches.push(ApprovalLifecycleTransition::DecideRejectedMissing);
}
if (self.state.lifecycle_phase == ApprovalLifecyclePhase::Ready)
&& ((self.state.approval_ids.contains(approval_id.as_str()))
&& (self.approval_statuses_value(approval_id.as_str())?
== ApprovalLifecycleStatus::Expired))
{
matches.push(ApprovalLifecycleTransition::DecideRejectedExpired);
}
if (self.state.lifecycle_phase == ApprovalLifecyclePhase::Ready)
&& ((self.state.approval_ids.contains(approval_id.as_str()))
&& (is_terminal_status(
self.approval_statuses_value(approval_id.as_str())?,
)))
{
matches.push(ApprovalLifecycleTransition::DecideRejectedAlreadyDecided);
}
if (self.state.lifecycle_phase == ApprovalLifecyclePhase::Ready)
&& ((self.state.approval_ids.contains(approval_id.as_str()))
&& (self.approval_statuses_value(approval_id.as_str())?
== ApprovalLifecycleStatus::Pending)
&& (decision == ApprovalLifecycleDecision::Approve)
&& (self.approval_approve_allowed_value(approval_id.as_str())? == false))
{
matches.push(ApprovalLifecycleTransition::DecideRejectedApproveNotAllowed);
}
if (self.state.lifecycle_phase == ApprovalLifecyclePhase::Ready)
&& ((self.state.approval_ids.contains(approval_id.as_str()))
&& (self.approval_statuses_value(approval_id.as_str())?
== ApprovalLifecycleStatus::Pending)
&& (decision == ApprovalLifecycleDecision::Deny)
&& (self.approval_deny_allowed_value(approval_id.as_str())? == false))
{
matches.push(ApprovalLifecycleTransition::DecideRejectedDenyNotAllowed);
}
if (self.state.lifecycle_phase == ApprovalLifecyclePhase::Ready)
&& ((self.state.approval_ids.contains(approval_id.as_str()))
&& (self.approval_statuses_value(approval_id.as_str())?
== ApprovalLifecycleStatus::Pending)
&& (decision == ApprovalLifecycleDecision::Approve)
&& (self.approval_approve_allowed_value(approval_id.as_str())?))
{
matches.push(ApprovalLifecycleTransition::DecideApprove);
}
if (self.state.lifecycle_phase == ApprovalLifecyclePhase::Ready)
&& ((self.state.approval_ids.contains(approval_id.as_str()))
&& (self.approval_statuses_value(approval_id.as_str())?
== ApprovalLifecycleStatus::Pending)
&& (decision == ApprovalLifecycleDecision::Deny)
&& (self.approval_deny_allowed_value(approval_id.as_str())?))
{
matches.push(ApprovalLifecycleTransition::DecideDeny);
}
let transition = Self::single_transition(matches, "DecideApproval")?;
match transition {
ApprovalLifecycleTransition::DecideRejectedMissing => {
self.state.lifecycle_phase = ApprovalLifecyclePhase::Ready;
Ok(vec![ApprovalLifecycleEffect::ApprovalLifecycleRejected {
approval_id: approval_id.clone(),
reason: ApprovalLifecycleRejectionReason::NotFound,
}])
}
ApprovalLifecycleTransition::DecideRejectedExpired => {
self.state.lifecycle_phase = ApprovalLifecyclePhase::Ready;
Ok(vec![ApprovalLifecycleEffect::ApprovalLifecycleRejected {
approval_id: approval_id.clone(),
reason: ApprovalLifecycleRejectionReason::Expired,
}])
}
ApprovalLifecycleTransition::DecideRejectedAlreadyDecided => {
self.state.lifecycle_phase = ApprovalLifecyclePhase::Ready;
Ok(vec![ApprovalLifecycleEffect::ApprovalLifecycleRejected {
approval_id: approval_id.clone(),
reason: ApprovalLifecycleRejectionReason::AlreadyDecided,
}])
}
ApprovalLifecycleTransition::DecideRejectedApproveNotAllowed => {
self.state.lifecycle_phase = ApprovalLifecyclePhase::Ready;
Ok(vec![ApprovalLifecycleEffect::ApprovalLifecycleRejected {
approval_id: approval_id.clone(),
reason: ApprovalLifecycleRejectionReason::InvalidDecision,
}])
}
ApprovalLifecycleTransition::DecideRejectedDenyNotAllowed => {
self.state.lifecycle_phase = ApprovalLifecyclePhase::Ready;
Ok(vec![ApprovalLifecycleEffect::ApprovalLifecycleRejected {
approval_id: approval_id.clone(),
reason: ApprovalLifecycleRejectionReason::InvalidDecision,
}])
}
ApprovalLifecycleTransition::DecideApprove => {
self.state
.approval_statuses
.insert(approval_id.clone(), ApprovalLifecycleStatus::Approved);
self.state.lifecycle_phase = ApprovalLifecyclePhase::Ready;
Ok(vec![ApprovalLifecycleEffect::ApprovalStatusResolved {
approval_id: approval_id.clone(),
status: ApprovalLifecycleStatus::Approved,
}])
}
ApprovalLifecycleTransition::DecideDeny => {
self.state
.approval_statuses
.insert(approval_id.clone(), ApprovalLifecycleStatus::Denied);
self.state.lifecycle_phase = ApprovalLifecyclePhase::Ready;
Ok(vec![ApprovalLifecycleEffect::ApprovalStatusResolved {
approval_id: approval_id.clone(),
status: ApprovalLifecycleStatus::Denied,
}])
}
_ => Err(ApprovalLifecycleError {
op: "DecideApproval_transition",
}),
}
}
}
}
fn outcome_from_effects(
effects: Vec<ApprovalLifecycleEffect>,
op: &'static str,
) -> Result<ApprovalLifecycleOutcome, ApprovalLifecycleError> {
for effect in effects {
match effect {
ApprovalLifecycleEffect::ApprovalStatusResolved { status, .. } => {
return Ok(ApprovalLifecycleOutcome::Status(status));
}
ApprovalLifecycleEffect::ApprovalLifecycleRejected { reason, .. } => {
return Ok(ApprovalLifecycleOutcome::Rejected(reason));
}
}
}
Err(ApprovalLifecycleError { op })
}
pub fn create_approval(
&mut self,
approval_id: String,
approve_allowed: bool,
deny_allowed: bool,
has_expiry: bool,
) -> Result<ApprovalLifecycleOutcome, ApprovalLifecycleError> {
let effects = self.apply_input(ApprovalLifecycleInput::CreateApproval {
approval_id,
approve_allowed,
deny_allowed,
has_expiry,
})?;
Self::outcome_from_effects(effects, "create_approval_effect")
}
pub fn restore_approval(
&mut self,
approval_id: String,
status: ApprovalLifecycleStatus,
approve_allowed: bool,
deny_allowed: bool,
has_expiry: bool,
decision: Option<ApprovalLifecycleDecision>,
) -> Result<ApprovalLifecycleOutcome, ApprovalLifecycleError> {
let effects = self.apply_input(ApprovalLifecycleInput::RestoreApproval {
approval_id,
status,
approve_allowed,
deny_allowed,
has_expiry,
decision,
})?;
Self::outcome_from_effects(effects, "restore_approval_effect")
}
pub fn observe_approval_expiry(
&mut self,
approval_id: String,
expired: bool,
) -> Result<ApprovalLifecycleOutcome, ApprovalLifecycleError> {
let effects = self.apply_input(ApprovalLifecycleInput::ObserveApprovalExpiry {
approval_id,
expired,
})?;
Self::outcome_from_effects(effects, "observe_approval_expiry_effect")
}
pub fn decide_approval(
&mut self,
approval_id: String,
decision: ApprovalLifecycleDecision,
) -> Result<ApprovalLifecycleOutcome, ApprovalLifecycleError> {
let effects = self.apply_input(ApprovalLifecycleInput::DecideApproval {
approval_id,
decision,
})?;
Self::outcome_from_effects(effects, "decide_approval_effect")
}
}
fn allowed_non_empty(approve_allowed: bool, deny_allowed: bool) -> bool {
(approve_allowed) || (deny_allowed)
}
fn is_terminal_status(status: ApprovalLifecycleStatus) -> bool {
(status == ApprovalLifecycleStatus::Approved)
|| (status == ApprovalLifecycleStatus::Denied)
|| (status == ApprovalLifecycleStatus::Cancelled)
}
impl Default for ApprovalLifecycleMachineAuthority {
fn default() -> Self {
Self::new()
}
}