use crate::contracts::{UniversalRequest, UniversalResponse};
use crate::identity::EngineInstanceId;
use core::pin::Pin;
pub type BoxedFuture<T, E> = Pin<Box<dyn futures::Future<Output = Result<T, E>> + Send>>;
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum TransportError {
Disconnected,
Closed,
Encode(String),
Decode(String),
Cancelled,
DeadlineExpired,
Timeout,
Peer(String),
}
impl TransportError {
pub fn is_retryable(&self) -> bool {
matches!(self, TransportError::Timeout | TransportError::Disconnected)
}
}
impl core::fmt::Display for TransportError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
TransportError::Disconnected => write!(f, "transport is not connected"),
TransportError::Closed => write!(f, "connection closed by peer"),
TransportError::Encode(msg) => write!(f, "encode error: {}", msg),
TransportError::Decode(msg) => write!(f, "decode error: {}", msg),
TransportError::Cancelled => write!(f, "operation cancelled"),
TransportError::DeadlineExpired => write!(f, "operation deadline expired"),
TransportError::Timeout => write!(f, "operation timed out"),
TransportError::Peer(msg) => write!(f, "peer error: {}", msg),
}
}
}
impl std::error::Error for TransportError {}
pub type TransportResult<T> = Result<T, TransportError>;
pub trait Transport: Send + Sync {
fn call(
&self,
target: &EngineInstanceId,
request: UniversalRequest,
) -> BoxedFuture<UniversalResponse, TransportError>;
fn is_connected(&self, target: &EngineInstanceId) -> bool;
fn connected_targets(&self) -> Vec<EngineInstanceId>;
}
#[cfg(test)]
mod tests {
use super::*;
use std::error::Error;
#[test]
fn transport_errors_report_clear_messages() {
assert_eq!(
TransportError::Disconnected.to_string(),
"transport is not connected"
);
assert_eq!(
TransportError::Closed.to_string(),
"connection closed by peer"
);
assert_eq!(
TransportError::Encode("bad payload".into()).to_string(),
"encode error: bad payload"
);
assert_eq!(
TransportError::Decode("bad response".into()).to_string(),
"decode error: bad response"
);
assert_eq!(TransportError::Cancelled.to_string(), "operation cancelled");
assert_eq!(
TransportError::DeadlineExpired.to_string(),
"operation deadline expired"
);
assert_eq!(TransportError::Timeout.to_string(), "operation timed out");
assert_eq!(
TransportError::Peer("peer failed".into()).to_string(),
"peer error: peer failed"
);
}
#[test]
fn transport_errors_identify_retryable_failures() {
assert!(TransportError::Timeout.is_retryable());
assert!(TransportError::Disconnected.is_retryable());
assert!(!TransportError::Closed.is_retryable());
assert!(!TransportError::Cancelled.is_retryable());
assert!(!TransportError::DeadlineExpired.is_retryable());
assert!(!TransportError::Peer("failed".into()).is_retryable());
}
#[test]
fn transport_errors_implement_error_trait() {
let error = TransportError::Timeout;
assert!(error.source().is_none());
}
#[test]
fn transport_result_alias_preserves_error_type() {
let success: TransportResult<UniversalResponse> = Err(TransportError::Closed);
assert!(matches!(success, Err(TransportError::Closed)));
}
}