use sqlx::error::DatabaseError;
use thiserror::Error;
use crate::{
account_set::error::AccountSetError,
balance::error::BalanceError,
primitives::{AccountId, JournalId, TransactionId},
tx_template::error::TxTemplateError,
velocity::error::VelocityError,
};
#[derive(Error, Debug)]
pub enum PostingError {
#[error("PostingError - Sqlx: {0}")]
Sqlx(sqlx::Error),
#[error("PostingError - DuplicateKey: {0}")]
DuplicateKey(Box<dyn DatabaseError>),
#[error("PostingError - TxTemplateError: {0}")]
TxTemplateError(#[from] TxTemplateError),
#[error("PostingError - VelocityError: {0}")]
VelocityError(#[from] VelocityError),
#[error("PostingError - AccountSetError: {0}")]
AccountSetError(#[from] AccountSetError),
#[error("PostingError - BalanceError: {0}")]
BalanceError(#[from] BalanceError),
#[error("PostingError - Rejected: posting {index} ({tx_id}): {reason}")]
Rejected {
index: usize,
tx_id: TransactionId,
reason: Box<RejectionReason>,
},
#[error(
"PostingError - BatchTooManyAccounts: this batch touches {distinct} distinct \
(journal, account, currency) balances; at most {max} may be locked in one batch. \
Split it — batch *size* is not the limit, the number of distinct accounts is."
)]
BatchTooManyAccounts { distinct: usize, max: usize },
}
impl PostingError {
pub(super) fn rejected(
index: usize,
tx_id: TransactionId,
reason: impl Into<RejectionReason>,
) -> Self {
let span = tracing::Span::current();
span.record("failed_posting_index", index);
span.record("failed_posting_id", tracing::field::display(tx_id));
Self::Rejected {
index,
tx_id,
reason: Box::new(reason.into()),
}
}
}
impl From<sqlx::Error> for PostingError {
fn from(e: sqlx::Error) -> Self {
match e {
sqlx::Error::Database(err) if err.message().contains("duplicate key") => {
Self::DuplicateKey(err)
}
e => Self::Sqlx(e),
}
}
}
#[derive(Error, Debug)]
pub enum RejectionReason {
#[error("{0}")]
TxTemplate(#[from] TxTemplateError),
#[error("account {0} does not exist")]
AccountNotFound(AccountId),
#[error(
"an entry may not be posted directly to an account-set backing account \
({0}); an account set's balance is derived from its members"
)]
EntryTargetsAccountSet(AccountId),
#[error("account {0} is locked")]
AccountLocked(AccountId),
#[error("journal {0} is locked")]
JournalLocked(JournalId),
#[error("journal {0} does not exist")]
JournalNotFound(JournalId),
#[error("duplicate transaction id {0} within the submitted batch")]
DuplicateTransactionIdInBatch(TransactionId),
#[error("duplicate external id `{0}` within the submitted batch")]
DuplicateExternalIdInBatch(String),
}
pub(super) const MAX_DISTINCT_BALANCES_PER_BATCH: usize = 1_000;