pub mod conn;
pub mod consumer;
pub mod frame;
pub mod group;
pub mod member;
pub mod metadata;
pub mod partitioner;
pub mod producer;
pub mod records;
pub use conn::{Connection, PendingResponse};
pub use consumer::{FetchPosition, IsolationLevel};
pub use group::{
Assignment, Assignor, CooperativeStickyAssignor, RangeAssignor, RoundRobinAssignor,
StickyAssignor, Subscription, TopicPartition,
};
pub use member::{GroupMember, MemberState, RebalanceProtocol, Step};
pub use metadata::{BrokerAddr, Metadata};
pub use partitioner::Partitioner;
pub use producer::{ProducerIdentity, ProducerState, SequenceRange, TxnState};
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error("protocol encode/decode: {0}")]
Codec(String),
#[error("unexpected correlation id {got}, expected {expected}")]
Correlation { got: i32, expected: i32 },
#[error("response with no request in flight")]
Unsolicited,
#[error("frame of {len} bytes exceeds the {limit} byte limit")]
FrameTooLarge { len: usize, limit: usize },
}
pub type Result<T> = std::result::Result<T, Error>;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ErrorCode(pub i16);
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Disposition {
Ok,
Retry,
RefreshMetadata,
FindCoordinator,
Fatal,
}
impl ErrorCode {
pub const NONE: Self = Self(0);
pub const OFFSET_OUT_OF_RANGE: Self = Self(1);
pub const UNKNOWN_TOPIC_OR_PARTITION: Self = Self(3);
pub const LEADER_NOT_AVAILABLE: Self = Self(5);
pub const NOT_LEADER_OR_FOLLOWER: Self = Self(6);
pub const REQUEST_TIMED_OUT: Self = Self(7);
pub const COORDINATOR_LOAD_IN_PROGRESS: Self = Self(14);
pub const COORDINATOR_NOT_AVAILABLE: Self = Self(15);
pub const NOT_COORDINATOR: Self = Self(16);
pub const OUT_OF_ORDER_SEQUENCE_NUMBER: Self = Self(45);
pub const DUPLICATE_SEQUENCE_NUMBER: Self = Self(46);
pub const INVALID_PRODUCER_EPOCH: Self = Self(47);
pub const CONCURRENT_TRANSACTIONS: Self = Self(51);
pub const PRODUCER_FENCED: Self = Self(90);
#[must_use]
pub fn is_ok(self) -> bool {
self == Self::NONE
}
#[must_use]
pub fn disposition(self) -> Disposition {
match self {
Self::NONE => Disposition::Ok,
Self::LEADER_NOT_AVAILABLE
| Self::NOT_LEADER_OR_FOLLOWER
| Self::UNKNOWN_TOPIC_OR_PARTITION => Disposition::RefreshMetadata,
Self::COORDINATOR_LOAD_IN_PROGRESS | Self::COORDINATOR_NOT_AVAILABLE => {
Disposition::Retry
}
Self::NOT_COORDINATOR => Disposition::FindCoordinator,
Self::REQUEST_TIMED_OUT | Self::CONCURRENT_TRANSACTIONS => Disposition::Retry,
_ => Disposition::Fatal,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn coordinator_warmup_codes_are_not_fatal() {
assert_eq!(
ErrorCode::COORDINATOR_NOT_AVAILABLE.disposition(),
Disposition::Retry
);
assert_eq!(
ErrorCode::COORDINATOR_LOAD_IN_PROGRESS.disposition(),
Disposition::Retry
);
assert_eq!(
ErrorCode::NOT_COORDINATOR.disposition(),
Disposition::FindCoordinator,
"NOT_COORDINATOR must re-discover: the coordinator moves, and \
retrying in place spins against a broker that will never answer"
);
}
#[test]
fn sequence_and_fencing_errors_are_fatal() {
for code in [
ErrorCode::OUT_OF_ORDER_SEQUENCE_NUMBER,
ErrorCode::DUPLICATE_SEQUENCE_NUMBER,
ErrorCode::INVALID_PRODUCER_EPOCH,
ErrorCode::PRODUCER_FENCED,
] {
assert_eq!(code.disposition(), Disposition::Fatal, "code {}", code.0);
}
}
#[test]
fn concurrent_transactions_is_retriable() {
assert_eq!(
ErrorCode::CONCURRENT_TRANSACTIONS.disposition(),
Disposition::Retry
);
}
#[test]
fn not_leader_refreshes_metadata() {
assert_eq!(
ErrorCode::NOT_LEADER_OR_FOLLOWER.disposition(),
Disposition::RefreshMetadata
);
}
#[test]
fn an_unknown_topic_refreshes_metadata() {
assert_eq!(
ErrorCode::UNKNOWN_TOPIC_OR_PARTITION.disposition(),
Disposition::RefreshMetadata
);
}
#[test]
fn unknown_codes_are_fatal() {
assert_eq!(ErrorCode(9999).disposition(), Disposition::Fatal);
}
}