use gents_derives::TS;
use logisheets_base::errors::BasicError;
use logisheets_parser::errors::ParseError;
use logisheets_workbook::SerdeErr;
use thiserror::Error;
use crate::{edit_action::EditPayload, file_saver::SaveError, style_manager::errors::StyleError};
#[derive(Debug, Error)]
pub enum Error {
#[error(transparent)]
Basic(#[from] BasicError),
#[error(transparent)]
Style(#[from] StyleError),
#[error(transparent)]
Serde(#[from] SerdeErr),
#[error(transparent)]
Save(#[from] SaveError),
#[error(transparent)]
Parse(#[from] ParseError),
#[error("sheet index {0} is out of range; the workbook has fewer sheets than that")]
UnavailableSheetIdx(usize),
#[error("invalid payload: {0}")]
PayloadError(String),
#[error(
"block \"{block}\" already has a record keyed \"{key}\"; row keys address cells, so they must be unique within a block"
)]
DuplicateBlockKey { block: String, key: String },
#[error("transaction rejected at payloads[{index}] of {total}: {source} (payload: {payload})")]
PayloadFailed {
index: usize,
total: usize,
payload: String,
source: Box<Error>,
},
#[error("{doing}: {source}")]
While { doing: String, source: Box<Error> },
}
pub trait Context<T> {
fn context(self, doing: impl FnOnce() -> String) -> Result<T, Error>;
}
impl<T> Context<T> for Result<T, Error> {
fn context(self, doing: impl FnOnce() -> String) -> Result<T, Error> {
self.map_err(|source| Error::While {
doing: doing(),
source: Box::new(source),
})
}
}
impl Error {
pub fn at_payload(self, index: usize, total: usize, payload: &EditPayload) -> Error {
Error::PayloadFailed {
index,
total,
payload: render_payload(payload),
source: Box::new(self),
}
}
pub fn code(&self) -> usize {
match self {
Error::Basic(_) => 0,
Error::Style(_) => 1,
Error::Serde(_) => 2,
Error::Save(_) => 3,
Error::Parse(_) => 4,
Error::UnavailableSheetIdx(_) => 5,
Error::PayloadError(_) => 6,
Error::DuplicateBlockKey { .. } => 7,
Error::PayloadFailed { source, .. } => source.code(),
Error::While { source, .. } => source.code(),
}
}
}
const PAYLOAD_EXCERPT_LIMIT: usize = 400;
fn render_payload(payload: &EditPayload) -> String {
match serde_json::to_string(payload) {
Ok(json) if json.len() > PAYLOAD_EXCERPT_LIMIT => {
let cut = (0..=PAYLOAD_EXCERPT_LIMIT)
.rev()
.find(|&i| json.is_char_boundary(i))
.unwrap_or(0);
format!("{}... truncated", &json[..cut])
}
Ok(json) => json,
Err(e) => format!("<could not be rendered: {e}>"),
}
}
#[derive(Debug, Clone, TS)]
#[ts(file_name = "error_message.ts", rename_all = "camelCase")]
pub struct ErrorMessage {
pub msg: String,
pub ty: usize,
}
impl From<Error> for ErrorMessage {
fn from(value: Error) -> Self {
ErrorMessage {
ty: value.code(),
msg: value.to_string(),
}
}
}