use crate::compression::Compression;
use std::path::PathBuf;
use thiserror::Error;
pub type Result<T> = std::result::Result<T, KafkoError>;
#[derive(Debug, Error)]
pub enum KafkoError {
#[error("io: {0}")]
Io(#[from] std::io::Error),
#[error("record decode: input truncated (need {needed} more bytes)")]
Truncated {
needed: usize,
},
#[error("record decode: CRC mismatch (expected {expected:#x}, got {actual:#x})")]
CrcMismatch {
expected: u32,
actual: u32,
},
#[error("record decode: invalid length field ({0})")]
InvalidLength(u32),
#[error("partition closed")]
Closed,
#[error("topic '{0}' already exists")]
TopicAlreadyExists(String),
#[error("topic '{0}' not found")]
TopicNotFound(String),
#[error("topic '{0}' in use; drop all producers and consumers before deleting")]
TopicInUse(String),
#[error("unknown compression flag: {0}")]
UnknownCompression(u8),
#[error("decompression failed")]
DecompressionFailed,
#[error("compression codec {0:?} is not enabled in this build; rebuild kafko with the matching `compression-*` feature")]
CompressionUnavailable(Compression),
#[error("data directory {} is already opened by another Kafko instance (file lock held)", .path.display())]
AlreadyOpen {
path: PathBuf,
},
#[error("partition writer task panicked: {payload}")]
PartitionPanicked {
payload: String,
},
#[error("invalid partition count {0}: a topic needs at least 1 partition")]
InvalidPartitionCount(u32),
#[error("invalid consumer group name {0:?}: use non-empty [A-Za-z0-9._-]")]
InvalidGroupName(String),
#[error("topic '{topic}' has an invalid on-disk layout: {detail}")]
InvalidTopicLayout {
topic: String,
detail: String,
},
}
impl Clone for KafkoError {
fn clone(&self) -> Self {
match self {
KafkoError::Io(e) => KafkoError::Io(std::io::Error::new(e.kind(), e.to_string())),
KafkoError::Truncated { needed } => KafkoError::Truncated { needed: *needed },
KafkoError::CrcMismatch { expected, actual } => KafkoError::CrcMismatch {
expected: *expected,
actual: *actual,
},
KafkoError::InvalidLength(n) => KafkoError::InvalidLength(*n),
KafkoError::Closed => KafkoError::Closed,
KafkoError::TopicAlreadyExists(s) => KafkoError::TopicAlreadyExists(s.clone()),
KafkoError::TopicNotFound(s) => KafkoError::TopicNotFound(s.clone()),
KafkoError::TopicInUse(s) => KafkoError::TopicInUse(s.clone()),
KafkoError::UnknownCompression(b) => KafkoError::UnknownCompression(*b),
KafkoError::DecompressionFailed => KafkoError::DecompressionFailed,
KafkoError::CompressionUnavailable(c) => KafkoError::CompressionUnavailable(*c),
KafkoError::AlreadyOpen { path } => KafkoError::AlreadyOpen {
path: path.clone(),
},
KafkoError::PartitionPanicked { payload } => KafkoError::PartitionPanicked {
payload: payload.clone(),
},
KafkoError::InvalidPartitionCount(n) => KafkoError::InvalidPartitionCount(*n),
KafkoError::InvalidGroupName(s) => KafkoError::InvalidGroupName(s.clone()),
KafkoError::InvalidTopicLayout { topic, detail } => KafkoError::InvalidTopicLayout {
topic: topic.clone(),
detail: detail.clone(),
},
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::io;
#[test]
fn clone_io_preserves_kind_and_message() {
let original = KafkoError::Io(io::Error::new(io::ErrorKind::PermissionDenied, "denied"));
match original.clone() {
KafkoError::Io(e) => {
assert_eq!(e.kind(), io::ErrorKind::PermissionDenied);
assert_eq!(e.to_string(), "denied");
}
other => panic!("expected Io after clone, got {other:?}"),
}
}
#[test]
fn clone_round_trips_every_variant() {
let cases = [
KafkoError::Io(io::Error::new(io::ErrorKind::Other, "io")),
KafkoError::Truncated { needed: 7 },
KafkoError::CrcMismatch {
expected: 0xdead_beef,
actual: 0x0bad_f00d,
},
KafkoError::InvalidLength(3),
KafkoError::Closed,
KafkoError::TopicAlreadyExists("orders".into()),
KafkoError::TopicNotFound("orders".into()),
KafkoError::TopicInUse("orders".into()),
KafkoError::UnknownCompression(9),
KafkoError::DecompressionFailed,
KafkoError::CompressionUnavailable(Compression::None),
KafkoError::AlreadyOpen {
path: PathBuf::from("data"),
},
KafkoError::PartitionPanicked {
payload: "boom".into(),
},
KafkoError::InvalidPartitionCount(0),
KafkoError::InvalidGroupName("bad/name".into()),
KafkoError::InvalidTopicLayout {
topic: "orders".into(),
detail: "no partition subdirectories".into(),
},
];
for original in &cases {
let cloned = original.clone();
assert_eq!(format!("{original:?}"), format!("{cloned:?}"));
assert_eq!(original.to_string(), cloned.to_string());
}
}
}