use thiserror::Error;
#[derive(Debug, Error)]
#[error(transparent)]
pub struct Error(#[from] ErrorKind);
#[derive(Debug, Error)]
enum ErrorKind {
#[error("I/O error: {0}")]
Io(#[from] std::io::Error),
#[error("failed to read XLSX data: {0}")]
Read(#[from] calamine::XlsxError),
#[error("failed to stream XLSX data: {0}")]
Stream(String),
#[error("failed to write XLSX data: {0}")]
Write(#[from] rust_xlsxwriter::XlsxError),
#[error("invalid A1 cell reference: {0}")]
InvalidCellReference(String),
#[error("invalid cell range: end cell {end} precedes start cell {start}")]
InvalidCellRange { start: String, end: String },
#[error("worksheet '{0}' was not found")]
SheetNotFound(String),
#[error("the workbook does not contain any worksheets")]
NoWorksheets,
#[error("invalid worksheet name '{name}': {reason}")]
InvalidSheetName { name: String, reason: &'static str },
#[error("worksheet name '{0}' is already in use")]
DuplicateSheetName(String),
#[error("cannot write headers for an empty data set without an explicit schema")]
MissingSchema,
#[error("worksheet data exceeds Excel limits: {rows} rows, {columns} columns")]
WorksheetLimit { rows: usize, columns: usize },
#[error("column name '{0}' appears more than once in the schema")]
DuplicateColumnName(String),
#[error("failed to deserialize worksheet '{sheet}' at Excel row {row}: {source}")]
Deserialize {
sheet: String,
row: usize,
#[source]
source: calamine::DeError,
},
#[error("invalid analytics query: {0}")]
InvalidQuery(String),
#[error("analytics failed in worksheet '{sheet}' at {column}{row}: {message}")]
Analytics { sheet: String, row: usize, column: String, message: String },
#[error("analytics query exceeded max_groups ({limit})")]
GroupLimit { limit: usize },
#[error("RAG export of {visibility} worksheet '{sheet}' requires explicit opt-in")]
HiddenSheet { sheet: String, visibility: &'static str },
}
impl Error {
pub(crate) fn stream(message: impl Into<String>) -> Self {
ErrorKind::Stream(message.into()).into()
}
pub(crate) fn invalid_cell_reference(reference: impl Into<String>) -> Self {
ErrorKind::InvalidCellReference(reference.into()).into()
}
pub(crate) fn invalid_cell_range(start: impl Into<String>, end: impl Into<String>) -> Self {
ErrorKind::InvalidCellRange { start: start.into(), end: end.into() }.into()
}
pub(crate) fn sheet_not_found(sheet_name: impl Into<String>) -> Self {
ErrorKind::SheetNotFound(sheet_name.into()).into()
}
pub(crate) fn no_worksheets() -> Self {
ErrorKind::NoWorksheets.into()
}
pub(crate) fn invalid_sheet_name(name: impl Into<String>, reason: &'static str) -> Self {
ErrorKind::InvalidSheetName { name: name.into(), reason }.into()
}
pub(crate) fn duplicate_sheet_name(name: impl Into<String>) -> Self {
ErrorKind::DuplicateSheetName(name.into()).into()
}
pub(crate) fn missing_schema() -> Self {
ErrorKind::MissingSchema.into()
}
pub(crate) fn worksheet_limit(rows: usize, columns: usize) -> Self {
ErrorKind::WorksheetLimit { rows, columns }.into()
}
pub(crate) fn duplicate_column_name(name: impl Into<String>) -> Self {
ErrorKind::DuplicateColumnName(name.into()).into()
}
pub(crate) fn deserialize(
sheet: impl Into<String>,
row: usize,
source: calamine::DeError,
) -> Self {
ErrorKind::Deserialize { sheet: sheet.into(), row, source }.into()
}
pub(crate) fn invalid_query(message: impl Into<String>) -> Self {
ErrorKind::InvalidQuery(message.into()).into()
}
pub(crate) fn analytics(
sheet: impl Into<String>,
row: usize,
column: impl Into<String>,
message: impl Into<String>,
) -> Self {
ErrorKind::Analytics {
sheet: sheet.into(),
row,
column: column.into(),
message: message.into(),
}
.into()
}
pub(crate) fn group_limit(limit: usize) -> Self {
ErrorKind::GroupLimit { limit }.into()
}
pub(crate) fn hidden_sheet(sheet: impl Into<String>, visibility: &'static str) -> Self {
ErrorKind::HiddenSheet { sheet: sheet.into(), visibility }.into()
}
}
impl From<std::io::Error> for Error {
fn from(source: std::io::Error) -> Self {
ErrorKind::Io(source).into()
}
}
impl From<calamine::XlsxError> for Error {
fn from(source: calamine::XlsxError) -> Self {
ErrorKind::Read(source).into()
}
}
impl From<rust_xlsxwriter::XlsxError> for Error {
fn from(source: rust_xlsxwriter::XlsxError) -> Self {
ErrorKind::Write(source).into()
}
}
pub type Result<T> = std::result::Result<T, Error>;