use crate::store::StoreError;
use hotfix_message::error::EncodingError;
use thiserror::Error;
#[derive(Debug, Error)]
pub enum SessionError {
#[error("store operation failed")]
Store(#[from] StoreError),
}
#[derive(Debug, Error)]
pub enum SessionCreationError {
#[error("unsupported BeginString: {0}")]
UnsupportedBeginString(String),
#[error("dictionary failed to parse")]
MalformedDictionary(#[from] hotfix_message::dict::ParseError),
#[error("dictionary contents are invalid")]
InvalidDictionary(#[from] hotfix_message::error::ParserError),
#[error("schedule configuration is invalid: {0}")]
InvalidSchedule(String),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum SendOutcome {
Sent { sequence_number: u64 },
Dropped,
}
#[derive(Debug, Error)]
pub enum SendError {
#[error("session is disconnected")]
Disconnected,
#[error("failed to persist message")]
Persist(#[source] StoreError),
#[error("failed to update sequence number")]
SequenceNumber(#[source] StoreError),
#[error("session terminated by application")]
SessionTerminated,
#[error("session is no longer available")]
SessionGone,
}
impl<T> From<tokio::sync::mpsc::error::SendError<T>> for SendError {
fn from(_: tokio::sync::mpsc::error::SendError<T>) -> Self {
SendError::SessionGone
}
}
impl From<tokio::sync::oneshot::error::RecvError> for SendError {
fn from(_: tokio::sync::oneshot::error::RecvError) -> Self {
SendError::SessionGone
}
}
#[derive(Debug, Error)]
pub(crate) enum InternalSendError {
#[error("failed to persist message")]
Persist(#[source] StoreError),
#[error("failed to update sequence number")]
SequenceNumber(#[source] StoreError),
}
impl From<InternalSendError> for SendError {
fn from(err: InternalSendError) -> Self {
match err {
InternalSendError::Persist(e) => SendError::Persist(e),
InternalSendError::SequenceNumber(e) => SendError::SequenceNumber(e),
}
}
}
#[derive(Debug, Error)]
pub(crate) enum SessionOperationError {
#[error("failed to send {context}")]
Send {
#[source]
source: InternalSendError,
context: &'static str,
},
#[error("store operation failed")]
Store(#[from] StoreError),
#[error("failed to encode message")]
MessageEncoding(#[from] EncodingError),
#[error("failed to parse stored message: {0}")]
StoredMessageParse(String),
#[error("missing required field: {0}")]
MissingField(&'static str),
}
pub(crate) trait InternalSendResultExt<T> {
fn with_send_context(self, context: &'static str) -> Result<T, SessionOperationError>;
}
impl<T> InternalSendResultExt<T> for Result<T, InternalSendError> {
fn with_send_context(self, context: &'static str) -> Result<T, SessionOperationError> {
self.map_err(|source| SessionOperationError::Send { source, context })
}
}
#[derive(Debug, Error)]
pub enum SetNextTargetSeqNumError {
#[error("operation rejected in state {current:?}; only permitted while disconnected")]
InvalidState { current: crate::session::Status },
#[error(transparent)]
Send(#[from] SendError),
#[error(transparent)]
Store(#[from] StoreError),
}
#[cfg(test)]
mod tests {
use super::*;
fn test_store_error() -> StoreError {
StoreError::Initialization("test".into())
}
#[test]
fn mpsc_send_error_converts_to_session_gone() {
let err: SendError = tokio::sync::mpsc::error::SendError(()).into();
assert!(matches!(err, SendError::SessionGone));
}
#[tokio::test]
async fn oneshot_recv_error_converts_to_session_gone() {
let (tx, rx) = tokio::sync::oneshot::channel::<()>();
drop(tx);
let recv_err = rx.await.unwrap_err();
let err: SendError = recv_err.into();
assert!(matches!(err, SendError::SessionGone));
}
#[test]
fn internal_send_error_persist_converts_to_send_error() {
let internal_err = InternalSendError::Persist(test_store_error());
let send_err: SendError = internal_err.into();
assert!(matches!(send_err, SendError::Persist(_)));
}
#[test]
fn internal_send_error_sequence_number_converts_to_send_error() {
let internal_err = InternalSendError::SequenceNumber(test_store_error());
let send_err: SendError = internal_err.into();
assert!(matches!(send_err, SendError::SequenceNumber(_)));
}
#[test]
fn with_send_context_converts_error() {
let result: Result<(), InternalSendError> =
Err(InternalSendError::Persist(test_store_error()));
let op_err = result.with_send_context("heartbeat").unwrap_err();
match op_err {
SessionOperationError::Send { context, .. } => {
assert_eq!(context, "heartbeat");
}
_ => panic!("expected SessionOperationError::Send"),
}
}
#[test]
fn with_send_context_passes_through_ok() {
let result: Result<u64, InternalSendError> = Ok(42);
let op_result = result.with_send_context("heartbeat");
assert_eq!(op_result.unwrap(), 42);
}
}