frame-conv 0.2.0

Conversation patterns — request-response, subscription, pub/sub, and workflow over liminal
Documentation
//! The workflow-side failure grammar — one closed typed family per
//! surface, every substrate refusal mapped, nothing flattened to strings
//! the caller must parse.

use aion_client::ClientError;

/// Failure establishing a workflow client.
#[derive(Debug, thiserror::Error)]
pub enum WorkflowConnectError {
    /// The internal runtime could not be created.
    #[error("workflow runtime unavailable: {message}")]
    Runtime {
        /// Operating-system level detail.
        message: String,
    },
    /// The substrate refused the connection or its configuration.
    #[error("workflow endpoint refused: {message}")]
    Endpoint {
        /// Substrate-supplied detail.
        message: String,
    },
}

/// Failure of a workflow conversation operation (open, reopen,
/// contribute, inspect) — the closed grammar every call speaks.
#[derive(Debug, thiserror::Error)]
pub enum WorkflowCallError {
    /// The named conversation or run does not exist.
    #[error("workflow unknown: {message}")]
    Unknown {
        /// Substrate-supplied detail.
        message: String,
    },
    /// The explicit idempotency key was reused for a DIFFERENT open
    /// request (same key, different kind/input/namespace) — the typed
    /// double-open-conflict outcome of the honest weaker contract.
    #[error("idempotency key conflict: {message}")]
    IdempotencyConflict {
        /// Substrate-supplied conflict detail.
        message: String,
    },
    /// The target run is terminal or otherwise not live.
    #[error("workflow not live: {message}")]
    NotLive {
        /// Substrate-supplied detail.
        message: String,
    },
    /// The operation is invalid for the run's current state — a reopen of
    /// a run that is not a reopenable terminal, for example.
    #[error("workflow state refuses this operation: {message}")]
    InvalidState {
        /// Substrate-supplied detail.
        message: String,
    },
    /// The request itself was malformed (an empty idempotency key, an
    /// unserializable input).
    #[error("invalid workflow request: {message}")]
    InvalidArgument {
        /// Detail naming the malformed element.
        message: String,
    },
    /// The caller's identity or namespace was refused.
    #[error("workflow access denied: {message}")]
    AccessDenied {
        /// Substrate-supplied detail.
        message: String,
    },
    /// The call or its target was cancelled.
    #[error("workflow call cancelled: {message}")]
    Cancelled {
        /// Substrate-supplied detail.
        message: String,
    },
    /// The substrate connection was lost — S3 connection fate for the
    /// workflow seam.
    #[error("workflow connection lost: {message}")]
    ConnectionLost {
        /// Substrate-supplied detail.
        message: String,
    },
    /// Any other substrate-reported failure, typed loud rather than
    /// swallowed.
    #[error("workflow substrate error: {message}")]
    Substrate {
        /// Substrate-supplied detail.
        message: String,
    },
}

impl WorkflowCallError {
    pub(crate) fn from_client(error: &ClientError) -> Self {
        let message = error.to_string();
        match error {
            ClientError::NotFound { .. } => Self::Unknown { message },
            ClientError::AlreadyExists { .. } => Self::IdempotencyConflict { message },
            ClientError::NotRunning { .. } => Self::NotLive { message },
            ClientError::InvalidState { .. } => Self::InvalidState { message },
            ClientError::InvalidArgument { .. } => Self::InvalidArgument { message },
            ClientError::Unauthenticated { .. } | ClientError::NamespaceDenied { .. } => {
                Self::AccessDenied { message }
            }
            ClientError::Cancelled { .. } => Self::Cancelled { message },
            ClientError::Unavailable { .. } => Self::ConnectionLost { message },
            _ => Self::Substrate { message },
        }
    }
}

/// Failure of workflow observation — the stream-side grammar.
#[derive(Debug, thiserror::Error)]
pub enum WorkflowObserveError {
    /// The substrate connection was lost while observation itself was
    /// unavailable — S3 connection fate at the observation seam.
    #[error("workflow observation connection lost: {message}")]
    ConnectionLost {
        /// Substrate-supplied detail.
        message: String,
    },
    /// The per-workflow stream ended without a terminal — loud and typed,
    /// never a silent hang or a fabricated outcome.
    #[error("workflow event stream ended without a terminal")]
    StreamEnded,
    /// Any other substrate-reported stream failure, typed loud.
    #[error("workflow observation substrate error: {message}")]
    Substrate {
        /// Substrate-supplied detail.
        message: String,
    },
}

impl WorkflowObserveError {
    pub(crate) fn from_client(error: &ClientError) -> Self {
        let message = error.to_string();
        match error {
            ClientError::Unavailable { .. } => Self::ConnectionLost { message },
            _ => Self::Substrate { message },
        }
    }
}