liminal-server 0.24.2

Standalone server for the liminal messaging bus
Documentation
//! Error mapping of the production participant handler, moved out of `handler.rs` unchanged
//! so that file stays within the 500-line bound.

use super::{
    ConversationOutboxError, DurabilityError, Instant, OperationLogError, OutboxLogError,
    ParticipantSemanticError, StateError, WriteIssuance,
};

/// Milliseconds since `started`, saturating, for progress lines.
pub(in super::super) fn elapsed_ms(started: Instant) -> u64 {
    u64::try_from(started.elapsed().as_millis()).unwrap_or(u64::MAX)
}

/// Every store failure on an issued operation leaves its durability unconfirmed.
const fn unsettled_write(error: &DurabilityError) -> bool {
    match error {
        DurabilityError::EmptyOperationBatch
        | DurabilityError::BatchUnsupported { .. }
        | DurabilityError::AppendUnresolved { .. }
        | DurabilityError::PendingAppendUnreadable { .. }
        | DurabilityError::OutcomeUnknown { .. }
        | DurabilityError::NotStarted { .. }
        | DurabilityError::StoreError(_)
        | DurabilityError::SequenceConflict { .. }
        | DurabilityError::CursorRegression { .. }
        | DurabilityError::DedupCollision { .. }
        | DurabilityError::ConfigError(_)
        | DurabilityError::EphemeralStoreOpen(_)
        | DurabilityError::EphemeralStoreDetached
        | DurabilityError::TruncateUnsupported { .. }
        | DurabilityError::RoutedScanUnsupported { .. }
        | DurabilityError::RoutedRemoveUnsupported { .. }
        | DurabilityError::RoutedDeleteUnsupported { .. }
        | DurabilityError::EnvelopeError(_) => true,
    }
}

const fn unsettled_outbox_write(error: &OutboxLogError) -> bool {
    matches!(error, OutboxLogError::Durability(error) if unsettled_write(error))
}

pub(in super::super) fn state_error(error: &StateError) -> ParticipantSemanticError {
    let issued = match error {
        StateError::Log(error) => {
            matches!(error, OperationLogError::Durability(error) if unsettled_write(error))
        }
        StateError::OutboxLog(error) => unsettled_outbox_write(error),
        StateError::Outbox(error) => {
            matches!(error, ConversationOutboxError::Log(error) if unsettled_outbox_write(error))
        }
        StateError::ExtensionRowsOwed { .. } | StateError::ExtensionRowUnconfirmed { .. } => true,
        StateError::Bridge(_)
        | StateError::Facts(_)
        | StateError::ObserverProgressConformance(_)
        | StateError::FateOccurrenceConflict(_)
        | StateError::ShellRefused { .. }
        | StateError::ShellUnavailable
        | StateError::FrontierUnavailable
        | StateError::ObligationDebtOwner { .. }
        | StateError::ReplayedEventDrift { .. }
        | StateError::ReplayedDiedTerminalHistory { .. }
        | StateError::BindingTerminalAdmissionRefused { .. }
        | StateError::ConversationSealed { .. }
        | StateError::PresentedRefusal(_)
        | StateError::Invariant { .. }
        | StateError::AllocationExhausted { .. } => false,
    };
    classify_state_error(error, issued)
}

pub(super) fn state_error_with_issuance(
    error: &StateError,
    issuance: &WriteIssuance,
) -> ParticipantSemanticError {
    classify_state_error(error, issuance.was_submitted())
}

fn classify_state_error(error: &StateError, issued: bool) -> ParticipantSemanticError {
    let uncertain = match error {
        StateError::ReplayedDiedTerminalHistory { .. } => false,
        StateError::BindingTerminalAdmissionRefused { error } => {
            return ParticipantSemanticError::BindingTerminalAdmissionRefused { error: *error };
        }
        // A retired id (the DUR-027 R4 tombstone) is sealed by type, however
        // its log was reached.
        StateError::ConversationSealed { conversation_id }
        | StateError::Log(OperationLogError::ConversationRetired { conversation_id }) => {
            return ParticipantSemanticError::ConversationSealed {
                conversation_id: *conversation_id,
            };
        }
        StateError::ExtensionRowsOwed { .. }
        | StateError::ExtensionRowUnconfirmed { .. }
        | StateError::Log(_)
        | StateError::Outbox(_)
        | StateError::OutboxLog(_)
        | StateError::Bridge(_)
        | StateError::Facts(_)
        | StateError::ObserverProgressConformance(_)
        | StateError::FateOccurrenceConflict(_)
        | StateError::ShellRefused { .. }
        | StateError::ShellUnavailable
        | StateError::FrontierUnavailable
        | StateError::ObligationDebtOwner { .. }
        | StateError::ReplayedEventDrift { .. }
        | StateError::PresentedRefusal(_)
        | StateError::Invariant { .. }
        | StateError::AllocationExhausted { .. } => issued,
    };
    let message = format!("participant production operation failed: {error}");
    if uncertain {
        ParticipantSemanticError::WriteOutcomeUnknown { message }
    } else {
        ParticipantSemanticError::Internal { message }
    }
}

pub(in super::super) fn log_error(error: &OperationLogError) -> ParticipantSemanticError {
    if let OperationLogError::ConversationRetired { conversation_id } = error {
        return ParticipantSemanticError::ConversationSealed {
            conversation_id: *conversation_id,
        };
    }
    let message = format!("participant production log failed: {error}");
    if matches!(error, OperationLogError::Durability(error) if unsettled_write(error)) {
        return ParticipantSemanticError::WriteOutcomeUnknown { message };
    }
    ParticipantSemanticError::Internal { message }
}

#[cfg(test)]
pub(in super::super) fn outbox_log_error(error: &OutboxLogError) -> ParticipantSemanticError {
    let message = format!("participant Unit 2 extension log failed: {error}");
    if unsettled_outbox_write(error) {
        return ParticipantSemanticError::WriteOutcomeUnknown { message };
    }
    ParticipantSemanticError::Internal { message }
}

/// A checkpoint restore's refusal: a retired conversation is sealed by type
/// (DUR-027 R4), and anything else is the restore's failure, named.
pub(in super::super) fn restore_error(
    error: &super::super::restore_checkpoint_rebuild::BootCheckpointError,
) -> ParticipantSemanticError {
    if let super::super::restore_checkpoint_rebuild::BootCheckpointError::Retired {
        conversation_id,
    } = error
    {
        return ParticipantSemanticError::ConversationSealed {
            conversation_id: *conversation_id,
        };
    }
    ParticipantSemanticError::Internal {
        message: format!("participant checkpoint restore failed: {error}"),
    }
}

pub(in super::super) fn bridge_error(
    error: &liminal::durability::bridge::BridgeError,
) -> ParticipantSemanticError {
    ParticipantSemanticError::Internal {
        message: format!("participant durability bridge failed: {error}"),
    }
}