use std::{borrow::Cow, collections::HashMap};
use nodecraft::{resolver::AddressResolver, Node};
use smol_str::SmolStr;
use crate::{
delegate::{Delegate, DelegateError},
transport::{MaybeResolvedAddress, Transport},
types::{ErrorResponse, SmallVec},
};
pub use crate::{
transport::TransportError,
types::{UnknownDelegateVersion, UnknownProtocolVersion},
};
pub struct JoinError<T: Transport, D>
where
D: Delegate<Id = T::Id, Address = <T::Resolver as AddressResolver>::ResolvedAddress>,
{
pub(crate) joined: SmallVec<Node<T::Id, <T::Resolver as AddressResolver>::ResolvedAddress>>,
pub(crate) errors: HashMap<Node<T::Id, MaybeResolvedAddress<T>>, Error<T, D>>,
}
impl<D, T: Transport> From<JoinError<T, D>>
for (
SmallVec<Node<T::Id, <T::Resolver as AddressResolver>::ResolvedAddress>>,
HashMap<Node<T::Id, MaybeResolvedAddress<T>>, Error<T, D>>,
)
where
D: Delegate<Id = T::Id, Address = <T::Resolver as AddressResolver>::ResolvedAddress>,
{
fn from(e: JoinError<T, D>) -> Self {
(e.joined, e.errors)
}
}
impl<D, T: Transport> core::fmt::Debug for JoinError<T, D>
where
D: Delegate<Id = T::Id, Address = <T::Resolver as AddressResolver>::ResolvedAddress>,
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if !self.joined.is_empty() {
writeln!(f, "Successes: {:?}", self.joined)?;
}
if !self.errors.is_empty() {
writeln!(f, "Failures:")?;
for (addr, err) in self.errors.iter() {
writeln!(f, "\t{}: {}", addr, err)?;
}
}
Ok(())
}
}
impl<D, T: Transport> core::fmt::Display for JoinError<T, D>
where
D: Delegate<Id = T::Id, Address = <T::Resolver as AddressResolver>::ResolvedAddress>,
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if !self.joined.is_empty() {
writeln!(f, "Successes: {:?}", self.joined)?;
}
if !self.errors.is_empty() {
writeln!(f, "Failures:")?;
for (addr, err) in self.errors.iter() {
writeln!(f, "\t{addr}: {err}")?;
}
}
Ok(())
}
}
impl<T: Transport, D> std::error::Error for JoinError<T, D> where
D: Delegate<Id = T::Id, Address = <T::Resolver as AddressResolver>::ResolvedAddress>
{
}
impl<T: Transport, D> JoinError<T, D>
where
D: Delegate<Id = T::Id, Address = <T::Resolver as AddressResolver>::ResolvedAddress>,
{
pub fn num_joined(&self) -> usize {
self.joined.len()
}
pub const fn joined(
&self,
) -> &SmallVec<Node<T::Id, <T::Resolver as AddressResolver>::ResolvedAddress>> {
&self.joined
}
}
impl<T: Transport, D> JoinError<T, D>
where
D: Delegate<Id = T::Id, Address = <T::Resolver as AddressResolver>::ResolvedAddress>,
{
pub const fn errors(&self) -> &HashMap<Node<T::Id, MaybeResolvedAddress<T>>, Error<T, D>> {
&self.errors
}
}
#[derive(thiserror::Error)]
pub enum Error<T: Transport, D: Delegate> {
#[error("memberlist: node is not running, please bootstrap first")]
NotRunning,
#[error("memberlist: timeout waiting for update broadcast")]
UpdateTimeout,
#[error("memberlist: timeout waiting for leave broadcast")]
LeaveTimeout,
#[error("memberlist: no response from node {0}")]
Lost(Node<T::Id, <T::Resolver as AddressResolver>::ResolvedAddress>),
#[error("memberlist: {0}")]
Delegate(#[from] DelegateError<D>),
#[error("memberlist: {0}")]
Transport(T::Error),
#[error("memberlist: unexpected message: expected {expected}, got {got}")]
UnexpectedMessage {
expected: &'static str,
got: &'static str,
},
#[error("memberlist: sequence number mismatch: ping({ping}), ack({ack})")]
SequenceNumberMismatch {
ping: u32,
ack: u32,
},
#[error("memberlist: remote error: {0}")]
Remote(SmolStr),
#[error("memberlist: {0}")]
Other(Cow<'static, str>),
}
impl<T: Transport, D: Delegate> core::fmt::Debug for Error<T, D> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "{self}")
}
}
impl<T: Transport, D: Delegate> Error<T, D> {
#[inline]
pub fn delegate(e: DelegateError<D>) -> Self {
Self::Delegate(e)
}
#[inline]
pub fn sequence_number_mismatch(ping: u32, ack: u32) -> Self {
Self::SequenceNumberMismatch { ping, ack }
}
#[inline]
pub fn unexpected_message(expected: &'static str, got: &'static str) -> Self {
Self::UnexpectedMessage { expected, got }
}
#[inline]
pub fn transport(e: T::Error) -> Self {
Self::Transport(e)
}
#[inline]
pub fn remote(e: ErrorResponse) -> Self {
Self::Remote(e.into())
}
#[inline]
pub fn custom(e: std::borrow::Cow<'static, str>) -> Self {
Self::Other(e)
}
#[inline]
pub(crate) fn is_remote_failure(&self) -> bool {
match self {
Self::Transport(e) => e.is_remote_failure(),
_ => false,
}
}
}