Add commitment state machines
This commit is contained in:
+169
-1
@@ -1 +1,169 @@
|
|||||||
// Placeholder for upcoming state_machine module.
|
use crate::domain::{CommitmentState, RuntimeState};
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
|
pub enum RuntimeAction {
|
||||||
|
EnterPlanning,
|
||||||
|
CancelOrTimeout,
|
||||||
|
Activate { policy_accepted: bool },
|
||||||
|
StartTransition,
|
||||||
|
CompleteForReview,
|
||||||
|
SevereViolation,
|
||||||
|
ReturnToActive,
|
||||||
|
SwitchTask,
|
||||||
|
EndTransitionForReview,
|
||||||
|
ContinuePlanning,
|
||||||
|
EndWorkPeriod,
|
||||||
|
EnterAdminOverride,
|
||||||
|
ExitAdminOverride(RuntimeState),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
|
pub enum CommitmentAction {
|
||||||
|
Activate,
|
||||||
|
PauseForTransition,
|
||||||
|
ReturnFromPause,
|
||||||
|
Complete,
|
||||||
|
Abandon,
|
||||||
|
Violate,
|
||||||
|
RecoverExplicitly,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
|
pub enum TransitionError {
|
||||||
|
PolicyRejected,
|
||||||
|
IllegalRuntimeTransition,
|
||||||
|
IllegalCommitmentTransition,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for TransitionError {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
TransitionError::PolicyRejected => write!(f, "policy was rejected"),
|
||||||
|
TransitionError::IllegalRuntimeTransition => write!(f, "illegal runtime transition"),
|
||||||
|
TransitionError::IllegalCommitmentTransition => {
|
||||||
|
write!(f, "illegal commitment transition")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::error::Error for TransitionError {}
|
||||||
|
|
||||||
|
pub fn transition_runtime(
|
||||||
|
current: RuntimeState,
|
||||||
|
action: RuntimeAction,
|
||||||
|
) -> Result<RuntimeState, TransitionError> {
|
||||||
|
use RuntimeAction::*;
|
||||||
|
use RuntimeState::*;
|
||||||
|
|
||||||
|
match (current, action) {
|
||||||
|
(Locked, EnterPlanning) => Ok(Planning),
|
||||||
|
(Planning, Activate { policy_accepted: true }) => Ok(Active),
|
||||||
|
(Planning, Activate { policy_accepted: false }) => Err(TransitionError::PolicyRejected),
|
||||||
|
(Planning, CancelOrTimeout) => Ok(Locked),
|
||||||
|
(Active, StartTransition) => Ok(Transition),
|
||||||
|
(Active, CompleteForReview) => Ok(Review),
|
||||||
|
(Active, SevereViolation) => Ok(Locked),
|
||||||
|
(Transition, ReturnToActive) => Ok(Active),
|
||||||
|
(Transition, SwitchTask) => Ok(Planning),
|
||||||
|
(Transition, EndTransitionForReview) => Ok(Review),
|
||||||
|
(Transition, CancelOrTimeout) => Ok(Locked),
|
||||||
|
(Review, ContinuePlanning) => Ok(Planning),
|
||||||
|
(Review, EndWorkPeriod) => Ok(Locked),
|
||||||
|
(_, EnterAdminOverride) => Ok(AdminOverride),
|
||||||
|
(AdminOverride, ExitAdminOverride(next)) => Ok(next),
|
||||||
|
_ => Err(TransitionError::IllegalRuntimeTransition),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn transition_commitment(
|
||||||
|
current: CommitmentState,
|
||||||
|
action: CommitmentAction,
|
||||||
|
) -> Result<CommitmentState, TransitionError> {
|
||||||
|
use CommitmentAction::*;
|
||||||
|
use CommitmentState::*;
|
||||||
|
|
||||||
|
match (current, action) {
|
||||||
|
(Draft, Activate) => Ok(Active),
|
||||||
|
(Active, PauseForTransition) => Ok(Paused),
|
||||||
|
(Paused, ReturnFromPause) => Ok(Active),
|
||||||
|
(Active, Complete) => Ok(Completed),
|
||||||
|
(Active, Abandon) => Ok(Abandoned),
|
||||||
|
(Active, Violate) => Ok(Violated),
|
||||||
|
(Paused, Abandon) => Ok(Abandoned),
|
||||||
|
(Violated, RecoverExplicitly) => Ok(Active),
|
||||||
|
(Violated, Abandon) => Ok(Abandoned),
|
||||||
|
_ => Err(TransitionError::IllegalCommitmentTransition),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn planning_can_only_activate_with_valid_policy() {
|
||||||
|
assert_eq!(
|
||||||
|
transition_runtime(
|
||||||
|
RuntimeState::Planning,
|
||||||
|
RuntimeAction::Activate {
|
||||||
|
policy_accepted: true,
|
||||||
|
}
|
||||||
|
),
|
||||||
|
Ok(RuntimeState::Active)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
transition_runtime(
|
||||||
|
RuntimeState::Planning,
|
||||||
|
RuntimeAction::Activate {
|
||||||
|
policy_accepted: false,
|
||||||
|
}
|
||||||
|
),
|
||||||
|
Err(TransitionError::PolicyRejected)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn active_can_enter_transition_or_review_or_locked() {
|
||||||
|
assert_eq!(
|
||||||
|
transition_runtime(RuntimeState::Active, RuntimeAction::StartTransition),
|
||||||
|
Ok(RuntimeState::Transition)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
transition_runtime(RuntimeState::Active, RuntimeAction::CompleteForReview),
|
||||||
|
Ok(RuntimeState::Review)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
transition_runtime(RuntimeState::Active, RuntimeAction::SevereViolation),
|
||||||
|
Ok(RuntimeState::Locked)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn locked_cannot_directly_become_active() {
|
||||||
|
assert_eq!(
|
||||||
|
transition_runtime(
|
||||||
|
RuntimeState::Locked,
|
||||||
|
RuntimeAction::Activate {
|
||||||
|
policy_accepted: true,
|
||||||
|
}
|
||||||
|
),
|
||||||
|
Err(TransitionError::IllegalRuntimeTransition)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn commitment_violation_recovery_is_explicit() {
|
||||||
|
assert_eq!(
|
||||||
|
transition_commitment(
|
||||||
|
CommitmentState::Violated,
|
||||||
|
CommitmentAction::RecoverExplicitly
|
||||||
|
),
|
||||||
|
Ok(CommitmentState::Active)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
transition_commitment(CommitmentState::Violated, CommitmentAction::ReturnFromPause),
|
||||||
|
Err(TransitionError::IllegalCommitmentTransition)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user