Skip to main content

pactole_core/
errors.rs

1use crate::models::{AccountName, CommodityName};
2use chrono::NaiveDate;
3use rust_decimal::Decimal;
4use thiserror::Error;
5
6/// Errors raised when building core model values: purely grammatical /
7/// structural checks (e.g. the shape of a name), with no knowledge of the
8/// rest of the journal.
9#[derive(Error, Debug, Clone, PartialEq, Eq)]
10pub enum ModelError {
11    /// An account name does not satisfy the Pactole account grammar.
12    #[error(
13        "invalid account name `{0}`: expected colon-separated segments, the first one starting \
14         with an uppercase letter, each made of letters, digits, `_` or `-`"
15    )]
16    InvalidAccountName(String),
17    /// A commodity name does not satisfy the Pactole commodity grammar.
18    #[error(
19        "invalid commodity name `{0}`: expected 2 to 24 characters, starting with an uppercase \
20         letter, ending with an uppercase letter or digit, made of uppercase letters, digits, \
21         `'`, `.`, `_` or `-` in between"
22    )]
23    InvalidCommodityName(String),
24    /// A metadata key does not satisfy the lowercase-key grammar.
25    #[error("invalid metadata key `{0}`: expected only lowercase letters, `_` or `-`")]
26    InvalidMetadataKey(String),
27}
28
29/// Errors raised when validating a [`crate::Journal`] as a whole: checks
30/// that require knowing about other entries of the journal, as opposed to
31/// the purely grammatical checks reported as [`ModelError`].
32#[derive(Error, Debug, Clone, PartialEq, Eq)]
33pub enum ValidationError {
34    /// An account is opened while it is already open.
35    #[error("account `{account}` is already open on {date}")]
36    AccountAlreadyOpen {
37        /// Account name involved in the duplicate opening.
38        account: AccountName,
39        /// Date of the duplicate opening.
40        date: NaiveDate,
41    },
42    /// An account is used or closed while it is not open.
43    #[error("account `{account}` is not open on {date}")]
44    AccountNotOpen {
45        /// Account name that was used while closed or unopened.
46        account: AccountName,
47        /// Date of the invalid use.
48        date: NaiveDate,
49    },
50    /// A commodity is declared more than once.
51    #[error("commodity `{commodity}` is already declared on {date}")]
52    CommodityAlreadyDeclared {
53        /// Commodity declared more than once.
54        commodity: CommodityName,
55        /// Date of the duplicate declaration.
56        date: NaiveDate,
57    },
58    /// A commodity is used before being declared.
59    #[error("commodity `{commodity}` is not declared on {date}")]
60    CommodityNotDeclared {
61        /// Commodity used before its declaration.
62        commodity: CommodityName,
63        /// Date of the invalid use.
64        date: NaiveDate,
65    },
66    /// A payee is declared more than once.
67    #[error("payee `{payee}` is already declared")]
68    PayeeAlreadyDeclared {
69        /// Payee declared more than once.
70        payee: String,
71    },
72    /// A transaction uses a payee that has not been declared.
73    #[error("payee `{payee}` is not declared (transaction on {date})")]
74    PayeeNotDeclared {
75        /// Payee used by the transaction.
76        payee: String,
77        /// Date of the transaction.
78        date: NaiveDate,
79    },
80    /// More than one transaction posting has no amount.
81    #[error(
82        "transaction on {date} has {count} postings without an amount, but at most one posting \
83         can be left without an amount for auto-balancing"
84    )]
85    TooManyPostingsWithoutAmount {
86        /// Number of postings without an amount.
87        count: usize,
88        /// Date of the transaction.
89        date: NaiveDate,
90    },
91    /// The commodity of an inferred amount cannot be determined uniquely.
92    #[error(
93        "cannot auto-balance transaction: unable to determine a single commodity for the \
94         missing amount from the other postings on {date} (commodities found: {commodities:?})"
95    )]
96    AmbiguousAutoBalanceCommodity {
97        /// Commodities found among the explicitly valued postings.
98        commodities: Vec<String>,
99        /// Date of the transaction.
100        date: NaiveDate,
101    },
102    /// Explicit transaction postings do not balance.
103    #[error(
104        "transaction on {date} is not balanced: postings for commodity `{commodity}` sum to \
105         `{sum}` instead of zero"
106    )]
107    TransactionNotBalanced {
108        /// Commodity whose postings do not sum to zero.
109        commodity: String,
110        /// Non-zero sum of the postings.
111        sum: Decimal,
112        /// Date of the transaction.
113        date: NaiveDate,
114    },
115    /// A posting uses a commodity excluded by the account's opening.
116    #[error(
117        "commodity `{commodity}` is not allowed on account `{account}` (posting on {date}): the \
118         account was opened with a restricted list of commodities that does not include it"
119    )]
120    CommodityNotAllowed {
121        /// Account carrying the restricted commodity.
122        account: AccountName,
123        /// Commodity excluded by the account opening.
124        commodity: CommodityName,
125        /// Date of the posting.
126        date: NaiveDate,
127    },
128    /// A balance assertion differs from the calculated balance beyond tolerance.
129    #[error(
130        "balance assertion failed on {date}: account `{account}` has a balance of `{actual}` \
131         {commodity}, expected `{expected}` {commodity} (tolerance ~ {tolerance})"
132    )]
133    BalanceAssertionFailed {
134        /// Account whose balance was asserted.
135        account: AccountName,
136        /// Commodity of the assertion.
137        commodity: CommodityName,
138        /// Date of the assertion.
139        date: NaiveDate,
140        /// Expected balance.
141        expected: Decimal,
142        /// Calculated balance.
143        actual: Decimal,
144        /// Allowed difference.
145        tolerance: Decimal,
146    },
147}