use std::fmt;
use std::io;
#[derive(Debug)]
pub enum Error {
Io(io::Error),
Protocol(String),
Broker {
code: i16,
message: String,
},
UnknownTopic(String),
NoLeader {
topic: String,
partition: i32,
},
Unsupported(String),
Closed,
Timeout,
QueueFull,
RecordTooLarge {
size: u64,
max: u64,
config: &'static str,
},
MaxPollInterval,
Wakeup,
}
impl Error {
pub const MAX_REQUEST_SIZE_CONFIG: &str = "max.request.size";
pub const BUFFER_MEMORY_CONFIG: &str = "buffer.memory";
pub fn protocol(msg: impl Into<String>) -> Self {
Self::Protocol(msg.into())
}
#[must_use]
pub(crate) fn record_too_large_max_request_size(size: u64, max: u64) -> Self {
Self::RecordTooLarge {
size,
max,
config: Self::MAX_REQUEST_SIZE_CONFIG,
}
}
#[must_use]
pub(crate) fn record_too_large_buffer_memory(size: u64, max: u64) -> Self {
Self::RecordTooLarge {
size,
max,
config: Self::BUFFER_MEMORY_CONFIG,
}
}
pub fn broker(code: i16, message: impl Into<String>) -> Self {
Self::Broker {
code,
message: message.into(),
}
}
#[must_use]
pub fn broker_code(&self) -> Option<i16> {
match self {
Self::Broker { code, .. } => Some(*code),
_ => None,
}
}
#[must_use]
pub fn is_retriable(&self) -> bool {
match self {
Self::Broker { code, .. } => matches!(
*code,
NOT_LEADER_OR_FOLLOWER
| LEADER_NOT_AVAILABLE
| NOT_ENOUGH_REPLICAS
| NOT_ENOUGH_REPLICAS_AFTER_APPEND
| REQUEST_TIMED_OUT
| COORDINATOR_LOAD_IN_PROGRESS
| COORDINATOR_NOT_AVAILABLE
| NOT_COORDINATOR
| NOT_CONTROLLER
| UNKNOWN_TOPIC_OR_PARTITION
| SHARE_SESSION_NOT_FOUND
| INVALID_SHARE_SESSION_EPOCH
),
Self::Io(_) | Self::Timeout => true,
_ => false,
}
}
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Io(e) => write!(f, "io: {e}"),
Self::Protocol(m) => write!(f, "protocol: {m}"),
Self::Broker { code, message } => {
let name = error_name(*code).unwrap_or("unknown");
if message.is_empty() {
write!(f, "broker error {code} ({name})")
} else {
write!(f, "broker error {code} ({name}): {message}")
}
}
Self::UnknownTopic(t) => write!(f, "unknown topic {t}"),
Self::NoLeader { topic, partition } => {
write!(f, "no leader for {topic}-{partition}")
}
Self::Unsupported(m) => write!(f, "unsupported: {m}"),
Self::Closed => write!(f, "producer closed"),
Self::Timeout => write!(f, "timeout"),
Self::QueueFull => write!(f, "producer queue full"),
Self::RecordTooLarge {
size,
max: _,
config,
} if *config == Self::BUFFER_MEMORY_CONFIG => write!(
f,
"The message is {size} bytes when serialized which is larger than the total memory buffer you have configured with the {config} configuration."
),
Self::RecordTooLarge { size, max, config } => {
write!(
f,
"The message is {size} bytes when serialized which is larger than {max}, which is the value of the {config} configuration."
)
}
Self::MaxPollInterval => write!(f, "max.poll.interval.ms exceeded"),
Self::Wakeup => write!(f, "wakeup"),
}
}
}
impl std::error::Error for Error {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::Io(e) => Some(e),
_ => None,
}
}
}
impl From<io::Error> for Error {
fn from(value: io::Error) -> Self {
Self::Io(value)
}
}
impl Clone for Error {
fn clone(&self) -> Self {
match self {
Self::Io(e) => Self::Io(io::Error::new(e.kind(), e.to_string())),
Self::Protocol(m) => Self::Protocol(m.clone()),
Self::Broker { code, message } => Self::Broker {
code: *code,
message: message.clone(),
},
Self::UnknownTopic(t) => Self::UnknownTopic(t.clone()),
Self::NoLeader { topic, partition } => Self::NoLeader {
topic: topic.clone(),
partition: *partition,
},
Self::Unsupported(m) => Self::Unsupported(m.clone()),
Self::Closed => Self::Closed,
Self::Timeout => Self::Timeout,
Self::QueueFull => Self::QueueFull,
Self::RecordTooLarge { size, max, config } => Self::RecordTooLarge {
size: *size,
max: *max,
config,
},
Self::MaxPollInterval => Self::MaxPollInterval,
Self::Wakeup => Self::Wakeup,
}
}
}
pub type Result<T> = std::result::Result<T, Error>;
pub const UNKNOWN_SERVER_ERROR: i16 = -1;
pub const NONE: i16 = 0;
pub const OFFSET_OUT_OF_RANGE: i16 = 1;
pub const CORRUPT_MESSAGE: i16 = 2;
pub const UNKNOWN_TOPIC_OR_PARTITION: i16 = 3;
pub const INVALID_FETCH_SIZE: i16 = 4;
pub const LEADER_NOT_AVAILABLE: i16 = 5;
pub const NOT_LEADER_OR_FOLLOWER: i16 = 6;
pub const REQUEST_TIMED_OUT: i16 = 7;
pub const BROKER_NOT_AVAILABLE: i16 = 8;
pub const REPLICA_NOT_AVAILABLE: i16 = 9;
pub const MESSAGE_TOO_LARGE: i16 = 10;
pub const STALE_CONTROLLER_EPOCH: i16 = 11;
pub const OFFSET_METADATA_TOO_LARGE: i16 = 12;
pub const NETWORK_EXCEPTION: i16 = 13;
pub const COORDINATOR_LOAD_IN_PROGRESS: i16 = 14;
pub const COORDINATOR_NOT_AVAILABLE: i16 = 15;
pub const NOT_COORDINATOR: i16 = 16;
pub const INVALID_TOPIC_EXCEPTION: i16 = 17;
pub const RECORD_LIST_TOO_LARGE: i16 = 18;
pub const NOT_ENOUGH_REPLICAS: i16 = 19;
pub const NOT_ENOUGH_REPLICAS_AFTER_APPEND: i16 = 20;
pub const INVALID_REQUIRED_ACKS: i16 = 21;
pub const ILLEGAL_GENERATION: i16 = 22;
pub const INCONSISTENT_GROUP_PROTOCOL: i16 = 23;
pub const INVALID_GROUP_ID: i16 = 24;
pub const UNKNOWN_MEMBER_ID: i16 = 25;
pub const INVALID_SESSION_TIMEOUT: i16 = 26;
pub const REBALANCE_IN_PROGRESS: i16 = 27;
pub const INVALID_COMMIT_OFFSET_SIZE: i16 = 28;
pub const TOPIC_AUTHORIZATION_FAILED: i16 = 29;
pub const GROUP_AUTHORIZATION_FAILED: i16 = 30;
pub const CLUSTER_AUTHORIZATION_FAILED: i16 = 31;
pub const INVALID_TIMESTAMP: i16 = 32;
pub const UNSUPPORTED_SASL_MECHANISM: i16 = 33;
pub const ILLEGAL_SASL_STATE: i16 = 34;
pub const UNSUPPORTED_VERSION: i16 = 35;
pub const TOPIC_ALREADY_EXISTS: i16 = 36;
pub const INVALID_PARTITIONS: i16 = 37;
pub const INVALID_REPLICATION_FACTOR: i16 = 38;
pub const INVALID_REPLICA_ASSIGNMENT: i16 = 39;
pub const INVALID_CONFIG: i16 = 40;
pub const NOT_CONTROLLER: i16 = 41;
pub const INVALID_REQUEST: i16 = 42;
pub const UNSUPPORTED_FOR_MESSAGE_FORMAT: i16 = 43;
pub const POLICY_VIOLATION: i16 = 44;
pub const OUT_OF_ORDER_SEQUENCE_NUMBER: i16 = 45;
pub const DUPLICATE_SEQUENCE_NUMBER: i16 = 46;
pub const INVALID_PRODUCER_EPOCH: i16 = 47;
pub const INVALID_TXN_STATE: i16 = 48;
pub const INVALID_PRODUCER_ID_MAPPING: i16 = 49;
pub const INVALID_TRANSACTION_TIMEOUT: i16 = 50;
pub const CONCURRENT_TRANSACTIONS: i16 = 51;
pub const TRANSACTION_COORDINATOR_FENCED: i16 = 52;
pub const TRANSACTIONAL_ID_AUTHORIZATION_FAILED: i16 = 53;
pub const SECURITY_DISABLED: i16 = 54;
pub const OPERATION_NOT_ATTEMPTED: i16 = 55;
pub const KAFKA_STORAGE_ERROR: i16 = 56;
pub const LOG_DIR_NOT_FOUND: i16 = 57;
pub const SASL_AUTHENTICATION_FAILED: i16 = 58;
pub const UNKNOWN_PRODUCER_ID: i16 = 59;
pub const REASSIGNMENT_IN_PROGRESS: i16 = 60;
pub const DELEGATION_TOKEN_AUTH_DISABLED: i16 = 61;
pub const DELEGATION_TOKEN_NOT_FOUND: i16 = 62;
pub const DELEGATION_TOKEN_OWNER_MISMATCH: i16 = 63;
pub const DELEGATION_TOKEN_REQUEST_NOT_ALLOWED: i16 = 64;
pub const DELEGATION_TOKEN_AUTHORIZATION_FAILED: i16 = 65;
pub const DELEGATION_TOKEN_EXPIRED: i16 = 66;
pub const INVALID_PRINCIPAL_TYPE: i16 = 67;
pub const NON_EMPTY_GROUP: i16 = 68;
pub const GROUP_ID_NOT_FOUND: i16 = 69;
pub const FETCH_SESSION_ID_NOT_FOUND: i16 = 70;
pub const INVALID_FETCH_SESSION_EPOCH: i16 = 71;
pub const LISTENER_NOT_FOUND: i16 = 72;
pub const TOPIC_DELETION_DISABLED: i16 = 73;
pub const FENCED_LEADER_EPOCH: i16 = 74;
pub const UNKNOWN_LEADER_EPOCH: i16 = 75;
pub const UNSUPPORTED_COMPRESSION_TYPE: i16 = 76;
pub const STALE_BROKER_EPOCH: i16 = 77;
pub const OFFSET_NOT_AVAILABLE: i16 = 78;
pub const MEMBER_ID_REQUIRED: i16 = 79;
pub const PREFERRED_LEADER_NOT_AVAILABLE: i16 = 80;
pub const GROUP_MAX_SIZE_REACHED: i16 = 81;
pub const FENCED_INSTANCE_ID: i16 = 82;
pub const ELIGIBLE_LEADERS_NOT_AVAILABLE: i16 = 83;
pub const ELECTION_NOT_NEEDED: i16 = 84;
pub const NO_REASSIGNMENT_IN_PROGRESS: i16 = 85;
pub const GROUP_SUBSCRIBED_TO_TOPIC: i16 = 86;
pub const INVALID_RECORD: i16 = 87;
pub const UNSTABLE_OFFSET_COMMIT: i16 = 88;
pub const THROTTLING_QUOTA_EXCEEDED: i16 = 89;
pub const PRODUCER_FENCED: i16 = 90;
pub const RESOURCE_NOT_FOUND: i16 = 91;
pub const DUPLICATE_RESOURCE: i16 = 92;
pub const UNACCEPTABLE_CREDENTIAL: i16 = 93;
pub const INCONSISTENT_VOTER_SET: i16 = 94;
pub const INVALID_UPDATE_VERSION: i16 = 95;
pub const FEATURE_UPDATE_FAILED: i16 = 96;
pub const PRINCIPAL_DESERIALIZATION_FAILURE: i16 = 97;
pub const SNAPSHOT_NOT_FOUND: i16 = 98;
pub const POSITION_OUT_OF_RANGE: i16 = 99;
pub const UNKNOWN_TOPIC_ID: i16 = 100;
pub const DUPLICATE_BROKER_REGISTRATION: i16 = 101;
pub const BROKER_ID_NOT_REGISTERED: i16 = 102;
pub const INCONSISTENT_TOPIC_ID: i16 = 103;
pub const INCONSISTENT_CLUSTER_ID: i16 = 104;
pub const TRANSACTIONAL_ID_NOT_FOUND: i16 = 105;
pub const FETCH_SESSION_TOPIC_ID_ERROR: i16 = 106;
pub const INELIGIBLE_REPLICA: i16 = 107;
pub const NEW_LEADER_ELECTED: i16 = 108;
pub const OFFSET_MOVED_TO_TIERED_STORAGE: i16 = 109;
pub const FENCED_MEMBER_EPOCH: i16 = 110;
pub const UNRELEASED_INSTANCE_ID: i16 = 111;
pub const UNSUPPORTED_ASSIGNOR: i16 = 112;
pub const STALE_MEMBER_EPOCH: i16 = 113;
pub const MISMATCHED_ENDPOINT_TYPE: i16 = 114;
pub const UNSUPPORTED_ENDPOINT_TYPE: i16 = 115;
pub const UNKNOWN_CONTROLLER_ID: i16 = 116;
pub const UNKNOWN_SUBSCRIPTION_ID: i16 = 117;
pub const TELEMETRY_TOO_LARGE: i16 = 118;
pub const INVALID_REGISTRATION: i16 = 119;
pub const TRANSACTION_ABORTABLE: i16 = 120;
pub const INVALID_RECORD_STATE: i16 = 121;
pub const SHARE_SESSION_NOT_FOUND: i16 = 122;
pub const INVALID_SHARE_SESSION_EPOCH: i16 = 123;
pub const FENCED_STATE_EPOCH: i16 = 124;
pub const INVALID_VOTER_KEY: i16 = 125;
pub const DUPLICATE_VOTER: i16 = 126;
pub const VOTER_NOT_FOUND: i16 = 127;
pub const INVALID_REGULAR_EXPRESSION: i16 = 128;
pub const REBOOTSTRAP_REQUIRED: i16 = 129;
pub const SHARE_SESSION_LIMIT_REACHED: i16 = 133;
pub fn coordinator_retriable(code: i16) -> bool {
matches!(
code,
COORDINATOR_LOAD_IN_PROGRESS | COORDINATOR_NOT_AVAILABLE | NOT_COORDINATOR
)
}
#[must_use]
pub fn consumer_group_describe_classic_fallback(code: i16) -> bool {
matches!(code, UNSUPPORTED_VERSION | GROUP_ID_NOT_FOUND)
}
#[must_use]
pub fn error_name(code: i16) -> Option<&'static str> {
Some(match code {
UNKNOWN_SERVER_ERROR => "UNKNOWN_SERVER_ERROR",
NONE => "NONE",
OFFSET_OUT_OF_RANGE => "OFFSET_OUT_OF_RANGE",
CORRUPT_MESSAGE => "CORRUPT_MESSAGE",
UNKNOWN_TOPIC_OR_PARTITION => "UNKNOWN_TOPIC_OR_PARTITION",
INVALID_FETCH_SIZE => "INVALID_FETCH_SIZE",
LEADER_NOT_AVAILABLE => "LEADER_NOT_AVAILABLE",
NOT_LEADER_OR_FOLLOWER => "NOT_LEADER_OR_FOLLOWER",
REQUEST_TIMED_OUT => "REQUEST_TIMED_OUT",
BROKER_NOT_AVAILABLE => "BROKER_NOT_AVAILABLE",
REPLICA_NOT_AVAILABLE => "REPLICA_NOT_AVAILABLE",
MESSAGE_TOO_LARGE => "MESSAGE_TOO_LARGE",
STALE_CONTROLLER_EPOCH => "STALE_CONTROLLER_EPOCH",
OFFSET_METADATA_TOO_LARGE => "OFFSET_METADATA_TOO_LARGE",
NETWORK_EXCEPTION => "NETWORK_EXCEPTION",
COORDINATOR_LOAD_IN_PROGRESS => "COORDINATOR_LOAD_IN_PROGRESS",
COORDINATOR_NOT_AVAILABLE => "COORDINATOR_NOT_AVAILABLE",
NOT_COORDINATOR => "NOT_COORDINATOR",
INVALID_TOPIC_EXCEPTION => "INVALID_TOPIC_EXCEPTION",
RECORD_LIST_TOO_LARGE => "RECORD_LIST_TOO_LARGE",
NOT_ENOUGH_REPLICAS => "NOT_ENOUGH_REPLICAS",
NOT_ENOUGH_REPLICAS_AFTER_APPEND => "NOT_ENOUGH_REPLICAS_AFTER_APPEND",
INVALID_REQUIRED_ACKS => "INVALID_REQUIRED_ACKS",
ILLEGAL_GENERATION => "ILLEGAL_GENERATION",
INCONSISTENT_GROUP_PROTOCOL => "INCONSISTENT_GROUP_PROTOCOL",
INVALID_GROUP_ID => "INVALID_GROUP_ID",
UNKNOWN_MEMBER_ID => "UNKNOWN_MEMBER_ID",
INVALID_SESSION_TIMEOUT => "INVALID_SESSION_TIMEOUT",
REBALANCE_IN_PROGRESS => "REBALANCE_IN_PROGRESS",
INVALID_COMMIT_OFFSET_SIZE => "INVALID_COMMIT_OFFSET_SIZE",
TOPIC_AUTHORIZATION_FAILED => "TOPIC_AUTHORIZATION_FAILED",
GROUP_AUTHORIZATION_FAILED => "GROUP_AUTHORIZATION_FAILED",
CLUSTER_AUTHORIZATION_FAILED => "CLUSTER_AUTHORIZATION_FAILED",
INVALID_TIMESTAMP => "INVALID_TIMESTAMP",
UNSUPPORTED_SASL_MECHANISM => "UNSUPPORTED_SASL_MECHANISM",
ILLEGAL_SASL_STATE => "ILLEGAL_SASL_STATE",
UNSUPPORTED_VERSION => "UNSUPPORTED_VERSION",
TOPIC_ALREADY_EXISTS => "TOPIC_ALREADY_EXISTS",
INVALID_PARTITIONS => "INVALID_PARTITIONS",
INVALID_REPLICATION_FACTOR => "INVALID_REPLICATION_FACTOR",
INVALID_REPLICA_ASSIGNMENT => "INVALID_REPLICA_ASSIGNMENT",
INVALID_CONFIG => "INVALID_CONFIG",
NOT_CONTROLLER => "NOT_CONTROLLER",
INVALID_REQUEST => "INVALID_REQUEST",
UNSUPPORTED_FOR_MESSAGE_FORMAT => "UNSUPPORTED_FOR_MESSAGE_FORMAT",
POLICY_VIOLATION => "POLICY_VIOLATION",
OUT_OF_ORDER_SEQUENCE_NUMBER => "OUT_OF_ORDER_SEQUENCE_NUMBER",
DUPLICATE_SEQUENCE_NUMBER => "DUPLICATE_SEQUENCE_NUMBER",
INVALID_PRODUCER_EPOCH => "INVALID_PRODUCER_EPOCH",
INVALID_TXN_STATE => "INVALID_TXN_STATE",
INVALID_PRODUCER_ID_MAPPING => "INVALID_PRODUCER_ID_MAPPING",
INVALID_TRANSACTION_TIMEOUT => "INVALID_TRANSACTION_TIMEOUT",
CONCURRENT_TRANSACTIONS => "CONCURRENT_TRANSACTIONS",
TRANSACTION_COORDINATOR_FENCED => "TRANSACTION_COORDINATOR_FENCED",
TRANSACTIONAL_ID_AUTHORIZATION_FAILED => "TRANSACTIONAL_ID_AUTHORIZATION_FAILED",
SECURITY_DISABLED => "SECURITY_DISABLED",
OPERATION_NOT_ATTEMPTED => "OPERATION_NOT_ATTEMPTED",
KAFKA_STORAGE_ERROR => "KAFKA_STORAGE_ERROR",
LOG_DIR_NOT_FOUND => "LOG_DIR_NOT_FOUND",
SASL_AUTHENTICATION_FAILED => "SASL_AUTHENTICATION_FAILED",
UNKNOWN_PRODUCER_ID => "UNKNOWN_PRODUCER_ID",
REASSIGNMENT_IN_PROGRESS => "REASSIGNMENT_IN_PROGRESS",
DELEGATION_TOKEN_AUTH_DISABLED => "DELEGATION_TOKEN_AUTH_DISABLED",
DELEGATION_TOKEN_NOT_FOUND => "DELEGATION_TOKEN_NOT_FOUND",
DELEGATION_TOKEN_OWNER_MISMATCH => "DELEGATION_TOKEN_OWNER_MISMATCH",
DELEGATION_TOKEN_REQUEST_NOT_ALLOWED => "DELEGATION_TOKEN_REQUEST_NOT_ALLOWED",
DELEGATION_TOKEN_AUTHORIZATION_FAILED => "DELEGATION_TOKEN_AUTHORIZATION_FAILED",
DELEGATION_TOKEN_EXPIRED => "DELEGATION_TOKEN_EXPIRED",
INVALID_PRINCIPAL_TYPE => "INVALID_PRINCIPAL_TYPE",
NON_EMPTY_GROUP => "NON_EMPTY_GROUP",
GROUP_ID_NOT_FOUND => "GROUP_ID_NOT_FOUND",
FETCH_SESSION_ID_NOT_FOUND => "FETCH_SESSION_ID_NOT_FOUND",
INVALID_FETCH_SESSION_EPOCH => "INVALID_FETCH_SESSION_EPOCH",
LISTENER_NOT_FOUND => "LISTENER_NOT_FOUND",
TOPIC_DELETION_DISABLED => "TOPIC_DELETION_DISABLED",
FENCED_LEADER_EPOCH => "FENCED_LEADER_EPOCH",
UNKNOWN_LEADER_EPOCH => "UNKNOWN_LEADER_EPOCH",
UNSUPPORTED_COMPRESSION_TYPE => "UNSUPPORTED_COMPRESSION_TYPE",
STALE_BROKER_EPOCH => "STALE_BROKER_EPOCH",
OFFSET_NOT_AVAILABLE => "OFFSET_NOT_AVAILABLE",
MEMBER_ID_REQUIRED => "MEMBER_ID_REQUIRED",
PREFERRED_LEADER_NOT_AVAILABLE => "PREFERRED_LEADER_NOT_AVAILABLE",
GROUP_MAX_SIZE_REACHED => "GROUP_MAX_SIZE_REACHED",
FENCED_INSTANCE_ID => "FENCED_INSTANCE_ID",
ELIGIBLE_LEADERS_NOT_AVAILABLE => "ELIGIBLE_LEADERS_NOT_AVAILABLE",
ELECTION_NOT_NEEDED => "ELECTION_NOT_NEEDED",
NO_REASSIGNMENT_IN_PROGRESS => "NO_REASSIGNMENT_IN_PROGRESS",
GROUP_SUBSCRIBED_TO_TOPIC => "GROUP_SUBSCRIBED_TO_TOPIC",
INVALID_RECORD => "INVALID_RECORD",
UNSTABLE_OFFSET_COMMIT => "UNSTABLE_OFFSET_COMMIT",
THROTTLING_QUOTA_EXCEEDED => "THROTTLING_QUOTA_EXCEEDED",
PRODUCER_FENCED => "PRODUCER_FENCED",
RESOURCE_NOT_FOUND => "RESOURCE_NOT_FOUND",
DUPLICATE_RESOURCE => "DUPLICATE_RESOURCE",
UNACCEPTABLE_CREDENTIAL => "UNACCEPTABLE_CREDENTIAL",
INCONSISTENT_VOTER_SET => "INCONSISTENT_VOTER_SET",
INVALID_UPDATE_VERSION => "INVALID_UPDATE_VERSION",
FEATURE_UPDATE_FAILED => "FEATURE_UPDATE_FAILED",
PRINCIPAL_DESERIALIZATION_FAILURE => "PRINCIPAL_DESERIALIZATION_FAILURE",
SNAPSHOT_NOT_FOUND => "SNAPSHOT_NOT_FOUND",
POSITION_OUT_OF_RANGE => "POSITION_OUT_OF_RANGE",
UNKNOWN_TOPIC_ID => "UNKNOWN_TOPIC_ID",
DUPLICATE_BROKER_REGISTRATION => "DUPLICATE_BROKER_REGISTRATION",
BROKER_ID_NOT_REGISTERED => "BROKER_ID_NOT_REGISTERED",
INCONSISTENT_TOPIC_ID => "INCONSISTENT_TOPIC_ID",
INCONSISTENT_CLUSTER_ID => "INCONSISTENT_CLUSTER_ID",
TRANSACTIONAL_ID_NOT_FOUND => "TRANSACTIONAL_ID_NOT_FOUND",
FETCH_SESSION_TOPIC_ID_ERROR => "FETCH_SESSION_TOPIC_ID_ERROR",
INELIGIBLE_REPLICA => "INELIGIBLE_REPLICA",
NEW_LEADER_ELECTED => "NEW_LEADER_ELECTED",
OFFSET_MOVED_TO_TIERED_STORAGE => "OFFSET_MOVED_TO_TIERED_STORAGE",
FENCED_MEMBER_EPOCH => "FENCED_MEMBER_EPOCH",
UNRELEASED_INSTANCE_ID => "UNRELEASED_INSTANCE_ID",
UNSUPPORTED_ASSIGNOR => "UNSUPPORTED_ASSIGNOR",
STALE_MEMBER_EPOCH => "STALE_MEMBER_EPOCH",
MISMATCHED_ENDPOINT_TYPE => "MISMATCHED_ENDPOINT_TYPE",
UNSUPPORTED_ENDPOINT_TYPE => "UNSUPPORTED_ENDPOINT_TYPE",
UNKNOWN_CONTROLLER_ID => "UNKNOWN_CONTROLLER_ID",
UNKNOWN_SUBSCRIPTION_ID => "UNKNOWN_SUBSCRIPTION_ID",
TELEMETRY_TOO_LARGE => "TELEMETRY_TOO_LARGE",
INVALID_REGISTRATION => "INVALID_REGISTRATION",
TRANSACTION_ABORTABLE => "TRANSACTION_ABORTABLE",
INVALID_RECORD_STATE => "INVALID_RECORD_STATE",
SHARE_SESSION_NOT_FOUND => "SHARE_SESSION_NOT_FOUND",
INVALID_SHARE_SESSION_EPOCH => "INVALID_SHARE_SESSION_EPOCH",
FENCED_STATE_EPOCH => "FENCED_STATE_EPOCH",
INVALID_VOTER_KEY => "INVALID_VOTER_KEY",
DUPLICATE_VOTER => "DUPLICATE_VOTER",
VOTER_NOT_FOUND => "VOTER_NOT_FOUND",
INVALID_REGULAR_EXPRESSION => "INVALID_REGULAR_EXPRESSION",
REBOOTSTRAP_REQUIRED => "REBOOTSTRAP_REQUIRED",
SHARE_SESSION_LIMIT_REACHED => "SHARE_SESSION_LIMIT_REACHED",
_ => return None,
})
}
#[must_use]
pub fn for_code(code: i16) -> &'static str {
error_name(code).unwrap_or("UNKNOWN_SERVER_ERROR")
}
fn errors_for_code(code: i16) -> i16 {
if error_name(code).is_some() {
code
} else {
UNKNOWN_SERVER_ERROR
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ApiError {
error: i16,
message: Option<String>,
}
impl ApiError {
pub const NONE: Self = Self {
error: 0,
message: None,
};
#[must_use]
pub fn from_code(code: i16, message: Option<String>) -> Self {
Self {
error: errors_for_code(code),
message,
}
}
#[must_use]
pub fn is(&self, code: i16) -> bool {
self.error == code
}
#[must_use]
pub fn is_success(&self) -> bool {
self.is(NONE)
}
#[must_use]
pub fn is_failure(&self) -> bool {
!self.is_success()
}
#[must_use]
pub fn error(&self) -> i16 {
self.error
}
#[must_use]
pub fn message(&self) -> Option<&str> {
self.message.as_deref()
}
}
impl fmt::Display for ApiError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"ApiError(error={}, message={})",
for_code(self.error),
self.message.as_deref().unwrap_or("null")
)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn for_code_matches_java_errors() {
assert_eq!(for_code(NONE), "NONE");
assert_eq!(for_code(UNKNOWN_SERVER_ERROR), "UNKNOWN_SERVER_ERROR");
assert_eq!(for_code(-1), "UNKNOWN_SERVER_ERROR");
assert_eq!(
for_code(UNKNOWN_TOPIC_OR_PARTITION),
"UNKNOWN_TOPIC_OR_PARTITION"
);
assert_eq!(for_code(999), "UNKNOWN_SERVER_ERROR");
assert_eq!(error_name(NONE), Some("NONE"));
assert_eq!(
error_name(UNKNOWN_SERVER_ERROR),
Some("UNKNOWN_SERVER_ERROR")
);
assert_eq!(error_name(999), None);
assert_eq!(CORRUPT_MESSAGE, 2);
assert_eq!(for_code(CORRUPT_MESSAGE), "CORRUPT_MESSAGE");
assert_eq!(INVALID_FETCH_SIZE, 4);
assert_eq!(for_code(INVALID_FETCH_SIZE), "INVALID_FETCH_SIZE");
assert_eq!(RECORD_LIST_TOO_LARGE, 18);
assert_eq!(for_code(RECORD_LIST_TOO_LARGE), "RECORD_LIST_TOO_LARGE");
assert_eq!(INVALID_REQUEST, 42);
assert_eq!(INVALID_TXN_STATE, 48);
assert_eq!(for_code(42), "INVALID_REQUEST");
assert_eq!(for_code(INVALID_TXN_STATE), "INVALID_TXN_STATE");
assert_eq!(SECURITY_DISABLED, 54);
assert_eq!(UNKNOWN_PRODUCER_ID, 59);
assert_eq!(for_code(54), "SECURITY_DISABLED");
assert_eq!(for_code(UNKNOWN_PRODUCER_ID), "UNKNOWN_PRODUCER_ID");
assert_eq!(UNKNOWN_LEADER_EPOCH, 75);
assert_eq!(STALE_BROKER_EPOCH, 77);
assert_eq!(for_code(75), "UNKNOWN_LEADER_EPOCH");
assert_eq!(for_code(77), "STALE_BROKER_EPOCH");
assert_eq!(GROUP_SUBSCRIBED_TO_TOPIC, 86);
assert_eq!(PRODUCER_FENCED, 90);
assert_eq!(for_code(86), "GROUP_SUBSCRIBED_TO_TOPIC");
assert_eq!(for_code(PRODUCER_FENCED), "PRODUCER_FENCED");
assert_eq!(REBOOTSTRAP_REQUIRED, 129);
assert_eq!(for_code(REBOOTSTRAP_REQUIRED), "REBOOTSTRAP_REQUIRED");
assert_eq!(error_name(130), None);
assert_eq!(for_code(130), "UNKNOWN_SERVER_ERROR");
assert_eq!(for_code(131), "UNKNOWN_SERVER_ERROR");
assert_eq!(for_code(132), "UNKNOWN_SERVER_ERROR");
assert_eq!(SHARE_SESSION_LIMIT_REACHED, 133);
assert_eq!(
for_code(SHARE_SESSION_LIMIT_REACHED),
"SHARE_SESSION_LIMIT_REACHED"
);
assert_eq!(for_code(134), "UNKNOWN_SERVER_ERROR");
let mut names = std::collections::HashSet::new();
for code in -1_i16..=129 {
let name = error_name(code).unwrap_or("");
assert!(!name.is_empty(), "unnamed Kafka 4.0.0 error {code}");
assert_eq!(for_code(code), name);
assert!(names.insert(name), "duplicate Errors name {name}");
}
assert_eq!(names.len(), 131);
}
#[test]
fn api_error_matches_java() {
assert_eq!(
ApiError::NONE.to_string(),
"ApiError(error=NONE, message=null)"
);
assert!(ApiError::NONE.is_success());
assert!(!ApiError::NONE.is_failure());
assert!(ApiError::NONE.is(NONE));
assert_eq!(ApiError::NONE.error(), NONE);
assert!(ApiError::NONE.message().is_none());
assert_eq!(ApiError::from_code(NONE, None), ApiError::NONE);
let unknown = ApiError::from_code(999, Some("nope".into()));
assert_eq!(unknown.error(), UNKNOWN_SERVER_ERROR);
assert_eq!(
unknown.to_string(),
"ApiError(error=UNKNOWN_SERVER_ERROR, message=nope)"
);
assert!(unknown.is(UNKNOWN_SERVER_ERROR));
assert!(unknown.is_failure());
assert!(!unknown.is_success());
let with_msg = ApiError::from_code(NONE, Some("ok".into()));
assert_eq!(with_msg.to_string(), "ApiError(error=NONE, message=ok)");
assert!(with_msg.is_success());
assert_eq!(with_msg.message(), Some("ok"));
}
}