aion-rs 0.29.0

Transport-agnostic Aion workflow engine with durability, replay, timers, and supervision.
Documentation
//! The detached hatch (R13.1): a top-level workflow start with a mandatory
//! dedupe identity, from workflow and workloop documents alike.
//!
//! A hatch is NOT a child: no lifecycle tie, no supervision edge, no awaited
//! terminal. The dedupe identity is defined ONCE for both document kinds —
//! `(namespace + target workflow type + key)`, minted deterministically by
//! [`aion_core::hatch_workflow_id`] — so an iteration retry or replay re-mints
//! the SAME [`WorkflowId`] and the same observed subject hatches ONE workflow,
//! never two. The event store itself is the dedupe index: a workflow id with
//! recorded history IS the prior hatch, and two racing first-hatches are
//! settled by the store's optimistic append (the loser's `SequenceConflict`
//! resolves to the winner's workflow).

use aion_core::{WorkflowId, WorkloopSpecError};

/// Re-export of the one identity derivation both document kinds share.
pub use aion_core::hatch_workflow_id as hatch_identity;

/// Outcome of a hatch request.
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum HatchOutcome {
    /// A new detached workflow was started under the derived identity.
    Hatched(WorkflowId),
    /// The identity already had a workflow — the recorded no-op (R13.1): the
    /// existing workflow id is returned and nothing was started.
    Existing(WorkflowId),
}

impl HatchOutcome {
    /// The workflow id the hatch resolved to, whichever way it went.
    #[must_use]
    pub const fn workflow_id(&self) -> &WorkflowId {
        match self {
            Self::Hatched(id) | Self::Existing(id) => id,
        }
    }
}

/// Validates and derives the hatch identity, mapping refusals to the shared
/// declaration-error type.
///
/// # Errors
///
/// Refuses empty parts and NUL bytes ([`WorkloopSpecError`]).
pub fn derive_identity(
    namespace: &str,
    workflow_type: &str,
    key: &str,
) -> Result<WorkflowId, WorkloopSpecError> {
    hatch_identity(namespace, workflow_type, key)
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn outcome_exposes_the_resolved_id_both_ways() -> Result<(), Box<dyn std::error::Error>> {
        let id = derive_identity("default", "process_task", "task-1")?;
        assert_eq!(HatchOutcome::Hatched(id.clone()).workflow_id(), &id);
        assert_eq!(HatchOutcome::Existing(id.clone()).workflow_id(), &id);
        Ok(())
    }
}