1use std::ops::RangeInclusive;
4
5use num_traits::Bounded;
6use num_traits::Zero;
7use thiserror::Error;
8
9use crate::error::ShutDownOr;
10use crate::invocation::AbstainOf;
11use crate::invocation::CommunicationErrorOf;
12use crate::invocation::Invocation;
13use crate::invocation::NayOf;
14use crate::invocation::RoundNumOf;
15use crate::retry::DoNotRetry;
16use crate::retry::RetryPolicy;
17
18#[derive(Debug)]
20pub struct AppendArgs<I: Invocation, R = DoNotRetry<I>> {
21    pub round: RangeInclusive<RoundNumOf<I>>,
25    pub importance: Importance,
27    pub retry_policy: R,
30}
31
32impl<I: Invocation, R: RetryPolicy<Invocation = I> + Default> Default for AppendArgs<I, R> {
33    fn default() -> Self {
34        R::default().into()
35    }
36}
37
38impl<I: Invocation, R: Clone> Clone for AppendArgs<I, R> {
39    fn clone(&self) -> Self {
40        Self {
41            round: self.round.clone(),
42            importance: self.importance,
43            retry_policy: self.retry_policy.clone(),
44        }
45    }
46}
47
48impl<I, R> From<R> for AppendArgs<I, R>
49where
50    I: Invocation,
51    R: RetryPolicy<Invocation = I>,
52{
53    fn from(retry_policy: R) -> Self {
54        Self {
55            round: Zero::zero()..=Bounded::max_value(),
56            importance: Importance::GainLeadership,
57            retry_policy,
58        }
59    }
60}
61
62impl<I: Invocation> From<()> for AppendArgs<I> {
63    fn from(_: ()) -> Self {
64        Default::default()
65    }
66}
67
68#[derive(Clone, Copy, Debug, Eq, PartialEq)]
70pub enum Importance {
71    GainLeadership,
77
78    MaintainLeadership(Peeryness),
84}
85
86impl Default for Importance {
87    fn default() -> Self {
88        Self::GainLeadership
89    }
90}
91
92#[derive(Clone, Copy, Debug, Eq, PartialEq)]
100pub enum Peeryness {
101    Peery,
108
109    Unpeery,
111}
112
113#[derive(Error)]
115#[non_exhaustive]
116pub enum AppendError<I: Invocation> {
117    #[error("round had already converged")]
119    Converged {
120        caught_up: bool,
123    },
124
125    #[error("a node decoration raised an error")]
127    Decoration(#[source] Box<dyn std::error::Error + Send + Sync + 'static>),
128
129    #[error("node is disoriented")]
131    Disoriented,
132
133    #[error("node was removed from the cluster")]
135    Exiled,
136
137    #[error("node lost its mandate or failed in acquiring one")]
139    Lost,
140
141    #[error("node could not achieve a quorum")]
143    NoQuorum {
144        abstentions: Vec<AbstainOf<I>>,
146        communication_errors: Vec<CommunicationErrorOf<I>>,
148        discards: Vec<NayOf<I>>,
150        rejections: Vec<NayOf<I>>,
152    },
153
154    #[error("node is passive")]
156    Passive,
157
158    #[error("node was forced to append a different entry")]
160    Railroaded,
161
162    #[error("node is shut down")]
164    ShutDown,
165
166    #[error("entry is unacceptable")]
168    Unacceptable(NayOf<I>),
169}
170
171impl<I: Invocation> From<ShutDownOr<AppendError<I>>> for AppendError<I> {
173    fn from(e: ShutDownOr<AppendError<I>>) -> Self {
174        match e {
175            ShutDownOr::Other(e) => e,
176            ShutDownOr::ShutDown => AppendError::ShutDown,
177        }
178    }
179}
180
181impl<I: Invocation> std::fmt::Debug for AppendError<I> {
182    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
183        match self {
184            AppendError::Converged { caught_up } => f
185                .debug_struct("AppendError::Converged")
186                .field("caught_up", caught_up)
187                .finish(),
188            AppendError::Decoration(err) => {
189                f.debug_tuple("AppendError::Decoration").field(err).finish()
190            }
191            AppendError::Disoriented => f.debug_tuple("AppendError::Disoriented").finish(),
192            AppendError::Exiled => f.debug_tuple("AppendError::Exiled").finish(),
193            AppendError::Lost => f.debug_tuple("AppendError::Lost").finish(),
194            AppendError::NoQuorum {
195                abstentions,
196                communication_errors,
197                discards,
198                rejections,
199            } => f
200                .debug_struct("AppendError::NoQuorum")
201                .field("abstentions", abstentions)
202                .field("communication_errors", communication_errors)
203                .field("discards", discards)
204                .field("rejections", rejections)
205                .finish(),
206            AppendError::Passive => f.debug_tuple("AppendError::Passive").finish(),
207            AppendError::Railroaded => f.debug_tuple("AppendError::Railroaded").finish(),
208            AppendError::ShutDown => f.debug_tuple("AppendError::ShutDown").finish(),
209            AppendError::Unacceptable(reason) => f
210                .debug_tuple("AppendError::Unacceptable")
211                .field(reason)
212                .finish(),
213        }
214    }
215}