agent-team-mail-core 1.1.2

Daemon-free core library for local agent team mail workflows.
Documentation
use std::marker::PhantomData;

use crate::schema::MessageEnvelope;
use crate::types::{
    AckState, AcknowledgedAckState, DisplayBucket, IsoTimestamp, MessageClass, NoAckState,
    PendingAckState, ReadReadState, ReadState, UnreadReadState,
};

#[derive(Debug, Clone)]
pub struct StoredMessage<R, A> {
    pub envelope: MessageEnvelope,
    _read: PhantomData<R>, // covariant in R; the typestate marker is never mutably borrowed.
    _ack: PhantomData<A>,  // covariant in A; the typestate marker is never mutably borrowed.
}

impl StoredMessage<UnreadReadState, NoAckState> {
    pub(crate) fn unread_no_ack(envelope: MessageEnvelope) -> Self {
        Self {
            envelope,
            _read: PhantomData,
            _ack: PhantomData,
        }
    }

    pub fn display_without_ack(self) -> StoredMessage<ReadReadState, NoAckState> {
        self.mark_read()
    }

    pub fn display_and_require_ack(
        mut self,
        at: IsoTimestamp,
    ) -> StoredMessage<ReadReadState, PendingAckState> {
        self.envelope.read = true;
        self.envelope.pending_ack_at = Some(at);
        StoredMessage::read_pending_ack(self.envelope)
    }

    pub fn mark_read(mut self) -> StoredMessage<ReadReadState, NoAckState> {
        self.envelope.read = true;
        StoredMessage::read_no_ack(self.envelope)
    }
}

impl StoredMessage<UnreadReadState, PendingAckState> {
    pub(crate) fn unread_pending_ack(envelope: MessageEnvelope) -> Self {
        Self {
            envelope,
            _read: PhantomData,
            _ack: PhantomData,
        }
    }

    pub fn mark_read_pending_ack(mut self) -> StoredMessage<ReadReadState, PendingAckState> {
        self.envelope.read = true;
        StoredMessage::read_pending_ack(self.envelope)
    }
}

impl StoredMessage<ReadReadState, NoAckState> {
    pub(crate) fn read_no_ack(envelope: MessageEnvelope) -> Self {
        Self {
            envelope,
            _read: PhantomData,
            _ack: PhantomData,
        }
    }
}

impl StoredMessage<ReadReadState, PendingAckState> {
    pub(crate) fn read_pending_ack(envelope: MessageEnvelope) -> Self {
        Self {
            envelope,
            _read: PhantomData,
            _ack: PhantomData,
        }
    }

    pub fn acknowledge(
        mut self,
        at: IsoTimestamp,
    ) -> StoredMessage<ReadReadState, AcknowledgedAckState> {
        self.envelope.acknowledged_at = Some(at);
        self.envelope.pending_ack_at = None;
        StoredMessage::read_acknowledged(self.envelope)
    }
}

impl StoredMessage<ReadReadState, AcknowledgedAckState> {
    pub(crate) fn read_acknowledged(envelope: MessageEnvelope) -> Self {
        Self {
            envelope,
            _read: PhantomData,
            _ack: PhantomData,
        }
    }
}

#[derive(Debug, Clone)]
pub enum TransitionedMessage {
    ReadNoAck(StoredMessage<ReadReadState, NoAckState>),
    ReadPendingAck(StoredMessage<ReadReadState, PendingAckState>),
    Unchanged(MessageEnvelope),
}

impl TransitionedMessage {
    pub fn into_envelope(self) -> MessageEnvelope {
        match self {
            Self::ReadNoAck(message) => message.envelope,
            Self::ReadPendingAck(message) => message.envelope,
            Self::Unchanged(envelope) => envelope,
        }
    }
}

pub fn derive_read_state(message: &MessageEnvelope) -> ReadState {
    if message.read {
        ReadState::Read
    } else {
        ReadState::Unread
    }
}

pub fn derive_ack_state(message: &MessageEnvelope) -> AckState {
    if message.acknowledged_at.is_some() {
        AckState::Acknowledged
    } else if message.pending_ack_at.is_some() {
        AckState::PendingAck
    } else {
        AckState::NoAckRequired
    }
}

pub fn classify_message(message: &MessageEnvelope) -> MessageClass {
    let read_state = derive_read_state(message);
    let ack_state = derive_ack_state(message);

    debug_assert!(
        !matches!(
            (read_state, ack_state),
            (ReadState::Unread, AckState::Acknowledged)
        ),
        "inconsistent message state: unread message cannot already be acknowledged"
    );

    match (read_state, ack_state) {
        (ReadState::Unread, AckState::NoAckRequired) => MessageClass::Unread,
        (ReadState::Unread, AckState::PendingAck) => MessageClass::PendingAck,
        (ReadState::Unread, AckState::Acknowledged) => MessageClass::Acknowledged,
        (ReadState::Read, AckState::NoAckRequired) => MessageClass::Read,
        (ReadState::Read, AckState::PendingAck) => MessageClass::PendingAck,
        (ReadState::Read, AckState::Acknowledged) => MessageClass::Acknowledged,
    }
}

pub fn display_bucket_for_class(class: MessageClass) -> DisplayBucket {
    match class {
        MessageClass::Unread => DisplayBucket::Unread,
        MessageClass::PendingAck => DisplayBucket::PendingAck,
        MessageClass::Acknowledged | MessageClass::Read => DisplayBucket::History,
    }
}