Skip to main content

klirr_core/models/
error.rs

1use crate::prelude::*;
2
3pub type Result<T, E = Error> = std::result::Result<T, E>;
4
5/// Error type for the logic crate, encapsulating various errors that can occur
6/// during PDF generation and manipulation.
7#[derive(Clone, Debug, ThisError, PartialEq)]
8pub enum Error {
9    /// The offset period must not be in the record of periods off.
10    #[error("Records off must not contain offset period: {offset_period}")]
11    RecordsOffMustNotContainOffsetPeriod { offset_period: String },
12
13    /// The start period is after the end period.
14    #[error("Start period ('{start}') is after end period ('{end}')")]
15    StartPeriodAfterEndPeriod { start: String, end: String },
16
17    /// Not a valid YearAndMonth nor YearMonthAndFortnight
18    #[error("Invalid Period, bad value: {bad_value}")]
19    InvalidPeriod { bad_value: String },
20
21    /// Period is not YearAndMonth
22    #[error("Period is not YearAndMonth")]
23    PeriodIsNotYearAndMonth,
24
25    /// Period is not YearMonthAndFortnight
26    #[error("Period is not YearMonthAndFortnight")]
27    PeriodIsNotYearMonthAndFortnight,
28
29    #[error(
30        "Invalid granularity for time off: '{free_granularity}', expected: '{service_fees_granularity}', use the same time unit for time off as you specified in service fees. View it with `klirr data dump` command."
31    )]
32    InvalidGranularityForTimeOff {
33        free_granularity: Granularity,
34        service_fees_granularity: Granularity,
35    },
36
37    /// Granularity too coarse,
38    #[error(
39        "Granularity too coarse '{granularity}', max is: '{max_granularity}', for period: '{target_period}'"
40    )]
41    GranularityTooCoarse {
42        granularity: Granularity,
43        max_granularity: Granularity,
44        target_period: String,
45    },
46
47    /// Cannot invoice for month when cadence is bi-weekly.
48    #[error("Cannot invoice for month when cadence is bi-weekly")]
49    CannotInvoiceForMonthWhenCadenceIsBiWeekly,
50
51    /// Cannot expense for month when cadence is bi-weekly
52    #[error("Cannot expense for month when cadence is bi-weekly")]
53    CannotExpenseForMonthWhenCadenceIsBiWeekly,
54
55    /// Cannot expense for fortnight when cadence is monthly.
56    #[error("Cannot expense for fortnight when cadence is monthly")]
57    CannotExpenseForFortnightWhenCadenceIsMonthly,
58
59    /// Password does not match, e.g. when the user tries to set a password
60    /// and the confirmation password does not match.
61    #[error("Passwords do not match")]
62    PasswordDoesNotMatch,
63
64    /// Email password is too short.
65    #[error(
66        "Email password is too short, expected at least {min_length} characters, but found {actual_length}"
67    )]
68    EmailPasswordTooShort {
69        /// The minimum length of the email password.
70        min_length: usize,
71        /// The actual length of the email password.
72        actual_length: usize,
73    },
74
75    /// Failed to parse the email atom template, e.g. when the template is not valid.
76    #[error("Failed to parse email atom template: {underlying}")]
77    EmailAtomTemplateError { underlying: String },
78
79    /// Invalid email address
80    #[error("Invalid email address for: {role}, because: {underlying}")]
81    InvalidEmailAddress { role: String, underlying: String },
82
83    /// Invalid name for email
84    #[error("Invalid name for email for: {role}, because: {underlying}")]
85    InvalidNameForEmail { role: String, underlying: String },
86
87    #[error("Invalid password for email {purpose}, because: {underlying}")]
88    InvalidPasswordForEmail { purpose: String, underlying: String },
89
90    /// Recipient addresses cannot be empty.
91    #[error("Recipient addresses cannot be empty")]
92    RecipientAddressesCannotBeEmpty,
93
94    /// Failed to parse SMTP Server
95    #[error("Failed to parse SMTP Server, because: {underlying}")]
96    InvalidSmtpServer { underlying: String },
97
98    /// Failed to parse a string into a valid UTF-8 string.
99    #[error("Failed to parse string into a valid UTF-8 string")]
100    InvalidUtf8,
101
102    /// Failed to decrypt data with AES.
103    #[error("Failed to decrypt data with AES")]
104    AESDecryptionFailed,
105
106    /// Invalid AES bytes, e.g. when the length is not as expected.
107    #[error(
108        "Invalid AES bytes, expected at least {expected_at_least} bytes, but found {found} bytes"
109    )]
110    InvalidAESBytesTooShort {
111        expected_at_least: usize,
112        found: usize,
113    },
114
115    /// Failed to create SMTP transport, e.g. when the SMTP server is not reachable.
116    #[error("Failed to create SMTP transport, because: {underlying}")]
117    CreateSmtpTransportError { underlying: String },
118
119    /// Failed to create Lettre Email from Email struct.
120    #[error("Failed to create email, because: {underlying}")]
121    CreateEmailError { underlying: String },
122
123    /// Failed to add attachments to the email, e.g. when the file is not found or cannot be read.
124    #[error("Failed to add attachments to the email, because: {underlying}")]
125    AddAttachmentsError {
126        /// Underlying error when adding attachments to the email.
127        underlying: String,
128    },
129
130    /// Failed to send email
131    #[error("Failed to send email, because: {underlying}")]
132    SendEmailError { underlying: String },
133
134    /// Failed to convert to `f64` from a `Decimal`
135    #[error("Failed to convert to f64 from Decimal, because: {value}")]
136    InvalidDecimalToF64Conversion { value: String },
137
138    /// Failed to convert `f64` value to a `Decimal`
139    #[error("Failed to convert f64 to Decimal, because: {value}")]
140    InvalidDecimalFromF64Conversion { value: f64 },
141
142    /// Failed to load a font, e.g. when the font file is not found or cannot be read.
143    #[error("Failed to load font with family name: '{family_name}'")]
144    FailedToLoadFont { family_name: String },
145
146    /// Failed to parse a string into an `Decimal`, e.g. when the string is not a valid number.
147    #[error("Failed to parse f64 from string: {bad_value}, reason: {reason}")]
148    InvalidF64String { bad_value: String, reason: String },
149
150    /// Failed to write data to disk, e.g. when the file system is not accessible.
151    #[error("Failed to write data to disk, because: {underlying}")]
152    FailedToWriteDataToDisk { underlying: String },
153
154    /// Failed to serialize data to RON format.
155    #[error("Failed to RON serialize data, because: {underlying}")]
156    FailedToRonSerializeData {
157        type_name: String,
158        underlying: String,
159    },
160
161    /// Error while building CompanyInformation from Terminal UI input.
162    #[error("Failed to build CompanyInformation from Terminal UI input, because: {reason}")]
163    InvalidCompanyInformation { reason: String },
164
165    /// Failed to parse invoice number from a string, e.g. when the format is incorrect.
166    #[error("Failed to parse invoice number from string: {invalid_string}")]
167    InvalidInvoiceNumberString { invalid_string: String },
168
169    /// Error while building InvoiceInfo from Terminal UI input.
170    #[error("Failed to build InvoiceInfo from Terminal UI input, because: {reason}")]
171    InvalidInvoiceInfo { reason: String },
172
173    /// Error while building PaymentInfo from Terminal UI input.
174    #[error("Failed to build PaymentInfo from Terminal UI input, because: {reason}")]
175    InvalidPaymentInfo { reason: String },
176
177    /// Error while building ServiceFees from Terminal UI input.
178    #[error("Failed to build ServiceFees from Terminal UI input, because: {reason}")]
179    InvalidServiceFees { reason: String },
180
181    /// The offset period must not be in the record of periods off.
182    #[error(
183        "Offset period must not be in the record of periods off: {offset_period}, period kind: {period_kind}"
184    )]
185    OffsetPeriodMustNotBeInRecordOfPeriodsOff {
186        offset_period: String,
187        period_kind: String,
188    },
189
190    /// The manually specified output path does not exist.
191    #[error("Specified output path does not exist: {path}")]
192    SpecifiedOutputPathDoesNotExist { path: String },
193
194    /// Failed to create the output directory for the PDF file.
195    #[error("Failed to create output directory: {underlying}")]
196    FailedToCreateOutputDirectory { underlying: String },
197
198    /// Target period must have expenses, but it does not.
199    #[error(
200        "Target period {target_period} must have expenses, but it does not. Fill 
201    in the `input/data/expenses.json` file with expenses for this period."
202    )]
203    TargetPeriodMustHaveExpenses { target_period: String },
204
205    /// Failed to parse year
206    #[error("Failed to parse year: {invalid_string}")]
207    FailedToParseYear { invalid_string: String },
208
209    /// Failed to load file
210    #[error("Failed to load file: {path}, underlying: {underlying}")]
211    FileNotFound { path: String, underlying: String },
212
213    /// Failed to deserialize a type
214    #[error("Failed to deserialize {type_name}, because: {error}")]
215    Deserialize { type_name: String, error: String },
216
217    /// Failed to parse Day from String
218    #[error("Invalid day from String: {invalid_string}, reason: {reason}")]
219    InvalidDayFromString {
220        invalid_string: String,
221        reason: String,
222    },
223
224    /// Invalid YearAndMonth
225    #[error("Invalid YearAndMonth, underlying: {underlying}")]
226    InvalidYearAndMonth { underlying: String },
227
228    /// Invalid date
229    #[error("Invalid date, underlying: {underlying}")]
230    InvalidDate { underlying: String },
231
232    /// Invalid day of the month, e.g. when the day is not between 1 and 31.
233    #[error("Invalid day: {day}, reason: {reason}")]
234    InvalidDay { day: i32, reason: String },
235
236    /// Invalid month, e.g. when the month is not between 1 and 12.
237    #[error("Invalid month: {month}, reason: {reason}")]
238    InvalidMonth { month: i32, reason: String },
239
240    /// Failed to parse Month from String
241    #[error("Failed to parse Month: {invalid_string}")]
242    FailedToParseMonth { invalid_string: String },
243
244    /// Failed to parse expense item from a string, e.g. when the format is incorrect.
245    #[error("Failed to parse expense item from: '{invalid_string}': {reason}")]
246    InvalidExpenseItem {
247        invalid_string: String,
248        reason: String,
249    },
250
251    /// The target period is in the record of periods off, but it must not be.
252    #[error("Target period {target_period} is in the record of periods off, but it must not be.")]
253    TargetPeriodMustNotBeInRecordOfPeriodsOff { target_period: String },
254
255    /// Failed to parse PaymentTerms NetDays from a string, e.g. when the format is incorrect.
256    #[error("Failed to PaymentTerms NetDays from string: {invalid_string}")]
257    FailedToParsePaymentTermsNetDays { invalid_string: String },
258
259    /// Failed to find the localization file for a specific language.
260    #[error("Failed to find the localization file for language: {language}")]
261    L18nNotFound {
262        /// The language that was not found, e.g. "EN" for English.
263        language: Language,
264    },
265
266    /// Failed to parse a string into a Hexcolor
267    #[error("Invalid hex color format: {invalid_string}")]
268    InvalidHexColor { invalid_string: String },
269
270    /// Failed to parse a date, e.g. when the format is incorrect or the date is invalid.
271    #[error("Failed to parse date, because: {underlying}")]
272    FailedToParseDate { underlying: String },
273
274    /// Error converting between currencies, e.g. when the exchange rate is not found.
275    #[error("Found no exchange rate for {target} based on {base}")]
276    FoundNoExchangeRate {
277        /// The target currency for the exchange rate, e.g. "EUR".
278        target: Currency,
279        /// The base currency for the exchange rate, e.g. "USD".
280        base: Currency,
281    },
282
283    /// Error when loading a resource for typst.
284    #[error("Failed to load Typst source, because: {underlying}")]
285    LoadSource { underlying: String },
286
287    /// Error when compiling Typst source to a PagedDocument.
288    #[error("Failed to compile Typst source, because: {underlying}")]
289    BuildPdf { underlying: String },
290
291    /// Error when exporting a PagedDocument to PDF.
292    #[error("Failed to export PagedDocument to PDF, because: {underlying}")]
293    ExportDocumentToPdf { underlying: String },
294
295    /// Error when saving the PDF to a file.
296    #[error("Failed to save PDF, because: {underlying}")]
297    SavePdf { underlying: String },
298
299    /// Error when fetching exchange rates from an API.
300    #[error("Failed fetch exchange rate from API, because: {underlying}")]
301    NetworkError { underlying: String },
302
303    /// Error when parsing the response from the exchange rate API.
304    #[error("Failed to parse exchange rate response, because: {underlying}")]
305    ParseError { underlying: String },
306}