meerkat-machine-schema 0.6.28

Formal machine schemas and transition definitions for Meerkat
Documentation
use meerkat_machine_dsl::machine;

#[derive(
    Debug,
    Clone,
    Default,
    PartialEq,
    Eq,
    PartialOrd,
    Ord,
    Hash,
    serde::Serialize,
    serde::Deserialize,
)]
pub struct WorkAttentionBindingKey(pub String);

machine! {
    machine WorkAttentionLifecycleMachine {
        version: 1,
        rust: "self" / "catalog::dsl::work_attention_lifecycle",

        state {
            lifecycle_phase: WorkAttentionLifecycleState,
            revision: u64,
            paused_until_utc_ms: Option<u64>,
            superseded_by_binding_key: Option<WorkAttentionBindingKey>,
            terminal_at_utc_ms: Option<u64>,
        }

        init(Active) {
            revision = 1,
            paused_until_utc_ms = None,
            superseded_by_binding_key = None,
            terminal_at_utc_ms = None,
        }

        terminal [Superseded, Stopped]

        phase WorkAttentionLifecycleState {
            Active,
            Paused,
            Superseded,
            Stopped,
        }

        input WorkAttentionLifecycleInput {
            Pause { expected_revision: u64, until_utc_ms: Option<u64> },
            Resume { expected_revision: u64 },
            Supersede { expected_revision: u64, superseded_by_binding_key: WorkAttentionBindingKey, at_utc_ms: u64 },
            Stop { expected_revision: u64, at_utc_ms: u64 },
        }

        effect WorkAttentionLifecycleEffect {
            AttentionPaused { revision: u64 },
            AttentionResumed { revision: u64 },
            AttentionSuperseded { revision: u64 },
            AttentionStopped { revision: u64 },
        }

        invariant live_has_no_terminal_time {
            (self.lifecycle_phase != Phase::Active && self.lifecycle_phase != Phase::Paused)
                || self.terminal_at_utc_ms == None
        }

        invariant paused_has_pause_state {
            self.lifecycle_phase == Phase::Paused || self.paused_until_utc_ms == None
        }

        invariant superseded_records_successor {
            self.lifecycle_phase != Phase::Superseded || self.superseded_by_binding_key != None
        }

        disposition AttentionPaused => local,
        disposition AttentionResumed => local,
        disposition AttentionSuperseded => local,
        disposition AttentionStopped => local,

        transition PauseActive {
            on input Pause { expected_revision, until_utc_ms }
            guard { self.lifecycle_phase == Phase::Active && self.revision == expected_revision }
            update {
                self.paused_until_utc_ms = until_utc_ms;
                self.revision += 1;
            }
            to Paused
            emit AttentionPaused { revision: self.revision }
        }

        transition PausePaused {
            on input Pause { expected_revision, until_utc_ms }
            guard { self.lifecycle_phase == Phase::Paused && self.revision == expected_revision }
            update {
                self.paused_until_utc_ms = until_utc_ms;
                self.revision += 1;
            }
            to Paused
            emit AttentionPaused { revision: self.revision }
        }

        transition ResumePaused {
            on input Resume { expected_revision }
            guard { self.lifecycle_phase == Phase::Paused && self.revision == expected_revision }
            update {
                self.paused_until_utc_ms = None;
                self.revision += 1;
            }
            to Active
            emit AttentionResumed { revision: self.revision }
        }

        transition SupersedeActive {
            on input Supersede { expected_revision, superseded_by_binding_key, at_utc_ms }
            guard { self.lifecycle_phase == Phase::Active && self.revision == expected_revision }
            update {
                self.paused_until_utc_ms = None;
                self.superseded_by_binding_key = Some(superseded_by_binding_key);
                self.terminal_at_utc_ms = Some(at_utc_ms);
                self.revision += 1;
            }
            to Superseded
            emit AttentionSuperseded { revision: self.revision }
        }

        transition SupersedePaused {
            on input Supersede { expected_revision, superseded_by_binding_key, at_utc_ms }
            guard { self.lifecycle_phase == Phase::Paused && self.revision == expected_revision }
            update {
                self.paused_until_utc_ms = None;
                self.superseded_by_binding_key = Some(superseded_by_binding_key);
                self.terminal_at_utc_ms = Some(at_utc_ms);
                self.revision += 1;
            }
            to Superseded
            emit AttentionSuperseded { revision: self.revision }
        }

        transition StopActive {
            on input Stop { expected_revision, at_utc_ms }
            guard { self.lifecycle_phase == Phase::Active && self.revision == expected_revision }
            update {
                self.paused_until_utc_ms = None;
                self.terminal_at_utc_ms = Some(at_utc_ms);
                self.revision += 1;
            }
            to Stopped
            emit AttentionStopped { revision: self.revision }
        }

        transition StopPaused {
            on input Stop { expected_revision, at_utc_ms }
            guard { self.lifecycle_phase == Phase::Paused && self.revision == expected_revision }
            update {
                self.paused_until_utc_ms = None;
                self.terminal_at_utc_ms = Some(at_utc_ms);
                self.revision += 1;
            }
            to Stopped
            emit AttentionStopped { revision: self.revision }
        }
    }
}

impl serde::Serialize for WorkAttentionLifecycleState {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: serde::Serializer,
    {
        serializer.serialize_str(match self {
            Self::Active => "active",
            Self::Paused => "paused",
            Self::Superseded => "superseded",
            Self::Stopped => "stopped",
        })
    }
}

impl<'de> serde::Deserialize<'de> for WorkAttentionLifecycleState {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: serde::Deserializer<'de>,
    {
        let value = <String as serde::Deserialize>::deserialize(deserializer)?;
        match value.as_str() {
            "active" | "Active" => Ok(Self::Active),
            "paused" | "Paused" => Ok(Self::Paused),
            "superseded" | "Superseded" => Ok(Self::Superseded),
            "stopped" | "Stopped" => Ok(Self::Stopped),
            other => Err(serde::de::Error::custom(format!(
                "invalid WorkAttentionLifecycleState `{other}`"
            ))),
        }
    }
}

#[derive(serde::Serialize, serde::Deserialize)]
struct WorkAttentionLifecycleMachineStateWire {
    lifecycle_phase: WorkAttentionLifecycleState,
    revision: u64,
    #[serde(default)]
    paused_until_utc_ms: Option<u64>,
    #[serde(default)]
    superseded_by_binding_key: Option<WorkAttentionBindingKey>,
    #[serde(default)]
    terminal_at_utc_ms: Option<u64>,
}

impl From<&WorkAttentionLifecycleMachineState> for WorkAttentionLifecycleMachineStateWire {
    fn from(state: &WorkAttentionLifecycleMachineState) -> Self {
        Self {
            lifecycle_phase: state.lifecycle_phase,
            revision: state.revision,
            paused_until_utc_ms: state.paused_until_utc_ms,
            superseded_by_binding_key: state.superseded_by_binding_key.clone(),
            terminal_at_utc_ms: state.terminal_at_utc_ms,
        }
    }
}

impl From<WorkAttentionLifecycleMachineStateWire> for WorkAttentionLifecycleMachineState {
    fn from(wire: WorkAttentionLifecycleMachineStateWire) -> Self {
        Self {
            lifecycle_phase: wire.lifecycle_phase,
            revision: wire.revision,
            paused_until_utc_ms: wire.paused_until_utc_ms,
            superseded_by_binding_key: wire.superseded_by_binding_key,
            terminal_at_utc_ms: wire.terminal_at_utc_ms,
        }
    }
}

impl serde::Serialize for WorkAttentionLifecycleMachineState {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: serde::Serializer,
    {
        WorkAttentionLifecycleMachineStateWire::from(self).serialize(serializer)
    }
}

impl<'de> serde::Deserialize<'de> for WorkAttentionLifecycleMachineState {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: serde::Deserializer<'de>,
    {
        WorkAttentionLifecycleMachineStateWire::deserialize(deserializer).map(Self::from)
    }
}