1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
//! Error types exposed by this crate.

use std::fmt;
use thiserror::Error;

use crate::{AppData, NodeId};

/// A result type where the error variant is always a `RaftError`.
pub type RaftResult<T> = std::result::Result<T, RaftError>;

/// Error variants related to the internals of Raft.
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum RaftError {
    /// An error which has come from the `RaftStorage` layer.
    #[error("{0}")]
    RaftStorage(anyhow::Error),
    /// An error which has come from the `RaftNetwork` layer.
    #[error("{0}")]
    RaftNetwork(anyhow::Error),
    /// An internal Raft error indicating that Raft is shutting down.
    #[error("Raft is shutting down")]
    ShuttingDown,
}

impl From<tokio::io::Error> for RaftError {
    fn from(src: tokio::io::Error) -> Self {
        RaftError::RaftStorage(src.into())
    }
}

/// An error related to a client read request.
#[derive(Debug, Error)]
pub enum ClientReadError {
    /// A Raft error.
    #[error("{0}")]
    RaftError(#[from] RaftError),
    /// The client read request must be forwarded to the cluster leader.
    #[error("the client read request must be forwarded to the cluster leader")]
    ForwardToLeader(Option<NodeId>),
}

/// An error related to a client write request.
#[derive(Error)]
pub enum ClientWriteError<D: AppData> {
    /// A Raft error.
    #[error("{0}")]
    RaftError(#[from] RaftError),
    /// The client write request must be forwarded to the cluster leader.
    #[error("the client write request must be forwarded to the cluster leader")]
    ForwardToLeader(D, Option<NodeId>),
}

impl<D: AppData> fmt::Debug for ClientWriteError<D> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            ClientWriteError::RaftError(err) => f.debug_tuple("RaftError").field(err).finish(),
            ClientWriteError::ForwardToLeader(_req, node_id) => {
                f.debug_tuple("ForwardToLeader").field(node_id).finish()
            }
        }
    }
}

/// Error variants related to configuration.
#[derive(Debug, Error, Eq, PartialEq)]
#[non_exhaustive]
pub enum ConfigError {
    /// A configuration error indicating that the given values for election timeout min & max are invalid: max must be greater than min.
    #[error(
        "given values for election timeout min & max are invalid: max must be greater than min"
    )]
    InvalidElectionTimeoutMinMax,
    /// The given value for max_payload_entries is too small, must be > 0.
    #[error("the given value for max_payload_entries is too small, must be > 0")]
    MaxPayloadEntriesTooSmall,
}

/// The set of errors which may take place when initializing a pristine Raft node.
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum InitializeError {
    /// An internal error has taken place.
    #[error("{0}")]
    RaftError(#[from] RaftError),
    /// The requested action is not allowed due to the Raft node's current state.
    #[error("the requested action is not allowed due to the Raft node's current state")]
    NotAllowed,
}

/// The set of errors which may take place when requesting to propose a config change.
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum ChangeConfigError {
    /// An error related to the processing of the config change request.
    ///
    /// Errors of this type will only come about from the internals of applying the config change
    /// to the Raft log and the process related to that workflow.
    #[error("{0}")]
    RaftError(#[from] RaftError),
    /// The cluster is already undergoing a configuration change.
    #[error("the cluster is already undergoing a configuration change")]
    ConfigChangeInProgress,
    /// The given config would leave the cluster in an inoperable state.
    ///
    /// This error will be returned if the full set of changes, once fully applied, would leave
    /// the cluster in an inoperable state.
    #[error("the given config would leave the cluster in an inoperable state")]
    InoperableConfig,
    /// The node the config change proposal was sent to was not the leader of the cluster. The ID
    /// of the current leader is returned if known.
    #[error("this node is not the Raft leader")]
    NodeNotLeader(Option<NodeId>),
    /// The proposed config changes would make no difference to the current config.
    #[error("the proposed config change would have no effect, this is a no-op")]
    Noop,
    /// Since the node was not ready to join when attempting to add it to the cluster, we had
    /// to sync it. However, the syncing process failed to complete on time, and thus we're
    /// giving up on the configuration change.
    #[error("could not bring up the node to speed before adding it to cluster, thus gave up.")]
    NodeFailedToCatchUp(NodeId),
}

impl<D: AppData> From<ClientWriteError<D>> for ChangeConfigError {
    fn from(src: ClientWriteError<D>) -> Self {
        match src {
            ClientWriteError::RaftError(err) => Self::RaftError(err),
            ClientWriteError::ForwardToLeader(_, id) => Self::NodeNotLeader(id),
        }
    }
}