use std::path::PathBuf;
use thiserror::Error;
pub type Result<T> = std::result::Result<T, BagError>;
pub type ReaderResult<T> = std::result::Result<T, BagError>;
pub type WriterResult<T> = std::result::Result<T, BagError>;
#[derive(Error, Debug)]
pub enum BagError {
#[error("IO error: {0}")]
Io(#[from] std::io::Error),
#[error("Failed to parse metadata YAML: {0}")]
YamlParse(#[from] serde_yaml::Error),
#[error("Database error: {0}")]
Database(#[from] rusqlite::Error),
#[error("Compression error: {0}")]
Compression(String),
#[error("Bag file not found: {path}")]
BagNotFound { path: PathBuf },
#[error("Bag already exists: {path}")]
BagAlreadyExists { path: PathBuf },
#[error("Metadata file not found: {path}")]
MetadataNotFound { path: PathBuf },
#[error("Storage file not found: {path}")]
StorageFileNotFound { path: PathBuf },
#[error("Unsupported bag version: {version}")]
UnsupportedVersion { version: u32 },
#[error("Unsupported storage format: {format}")]
UnsupportedStorageFormat { format: String },
#[error("Unsupported compression format: {format}")]
UnsupportedCompressionFormat { format: String },
#[error("Unsupported serialization format: {format}")]
UnsupportedSerializationFormat { format: String },
#[error("Bag is not open - call open() first")]
BagNotOpen,
#[error("Bag is already open")]
BagAlreadyOpen,
#[error("Invalid message data: {reason}")]
InvalidMessageData { reason: String },
#[error("CDR deserialization error at position {position}/{data_length}: {message}")]
CdrDeserialization {
message: String,
position: usize,
data_length: usize,
},
#[error("Message type not found: {message_type}")]
MessageTypeNotFound { message_type: String },
#[error("Schema validation error: {reason}")]
SchemaValidation { reason: String },
#[error("Connection not found for topic: {topic}")]
ConnectionNotFound { topic: String },
#[error("Connection already exists for topic: {topic}")]
ConnectionAlreadyExists { topic: String },
#[error("Invalid QoS profile: {reason}")]
InvalidQosProfile { reason: String },
#[error("Writer error: {message}")]
Writer { message: String },
#[error("Bag error: {message}")]
Generic { message: String },
}
pub type ReaderError = BagError;
impl BagError {
pub fn generic(message: impl Into<String>) -> Self {
Self::Generic {
message: message.into(),
}
}
pub fn writer(message: impl Into<String>) -> Self {
Self::Writer {
message: message.into(),
}
}
pub fn compression(message: impl Into<String>) -> Self {
Self::Compression(message.into())
}
pub fn invalid_message_data(reason: impl Into<String>) -> Self {
Self::InvalidMessageData {
reason: reason.into(),
}
}
pub fn cdr_deserialization(
message: impl Into<String>,
position: usize,
data_length: usize,
) -> Self {
Self::CdrDeserialization {
message: message.into(),
position,
data_length,
}
}
pub fn message_type_not_found(message_type: impl Into<String>) -> Self {
Self::MessageTypeNotFound {
message_type: message_type.into(),
}
}
pub fn schema_validation(reason: impl Into<String>) -> Self {
Self::SchemaValidation {
reason: reason.into(),
}
}
pub fn connection_not_found(topic: impl Into<String>) -> Self {
Self::ConnectionNotFound {
topic: topic.into(),
}
}
pub fn connection_already_exists(topic: impl Into<String>) -> Self {
Self::ConnectionAlreadyExists {
topic: topic.into(),
}
}
pub fn invalid_qos_profile(reason: impl Into<String>) -> Self {
Self::InvalidQosProfile {
reason: reason.into(),
}
}
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
mod tests {
use super::*;
#[test]
fn generic_factory_creates_generic_variant() {
let err = BagError::generic("something went wrong");
assert!(err.to_string().contains("something went wrong"));
assert!(matches!(err, BagError::Generic { .. }));
}
#[test]
fn writer_factory_creates_writer_variant() {
let err = BagError::writer("disk full");
assert!(err.to_string().contains("disk full"));
assert!(matches!(err, BagError::Writer { .. }));
}
#[test]
fn compression_factory_creates_compression_variant() {
let err = BagError::compression("zstd failed");
assert!(matches!(err, BagError::Compression(_)));
assert!(err.to_string().contains("zstd failed"));
}
#[test]
fn invalid_message_data_factory() {
let err = BagError::invalid_message_data("truncated buffer");
assert!(matches!(err, BagError::InvalidMessageData { .. }));
assert!(err.to_string().contains("truncated buffer"));
}
#[test]
fn cdr_deserialization_factory_includes_position() {
let err = BagError::cdr_deserialization("bad bytes", 42, 100);
let s = err.to_string();
assert!(s.contains("42"));
assert!(s.contains("100"));
assert!(s.contains("bad bytes"));
}
#[test]
fn message_type_not_found_factory() {
let err = BagError::message_type_not_found("sensor_msgs/msg/Imu");
assert!(matches!(err, BagError::MessageTypeNotFound { .. }));
assert!(err.to_string().contains("sensor_msgs/msg/Imu"));
}
#[test]
fn schema_validation_factory() {
let err = BagError::schema_validation("missing field");
assert!(matches!(err, BagError::SchemaValidation { .. }));
assert!(err.to_string().contains("missing field"));
}
#[test]
fn connection_not_found_factory() {
let err = BagError::connection_not_found("/imu");
assert!(matches!(err, BagError::ConnectionNotFound { .. }));
assert!(err.to_string().contains("/imu"));
}
#[test]
fn connection_already_exists_factory() {
let err = BagError::connection_already_exists("/cam");
assert!(matches!(err, BagError::ConnectionAlreadyExists { .. }));
assert!(err.to_string().contains("/cam"));
}
#[test]
fn invalid_qos_profile_factory() {
let err = BagError::invalid_qos_profile("bad depth");
assert!(matches!(err, BagError::InvalidQosProfile { .. }));
assert!(err.to_string().contains("bad depth"));
}
#[test]
fn bag_not_open_display() {
let err = BagError::BagNotOpen;
assert!(err.to_string().contains("not open"));
}
#[test]
fn unsupported_version_display() {
let err = BagError::UnsupportedVersion { version: 99 };
assert!(err.to_string().contains("99"));
}
#[test]
fn unsupported_storage_format_display() {
let err = BagError::UnsupportedStorageFormat {
format: "hdf5".to_string(),
};
assert!(err.to_string().contains("hdf5"));
}
}