1use crate::prelude::*;
2
3pub type Result<T, E = Error> = std::result::Result<T, E>;
4
5#[derive(Clone, Debug, ThisError, PartialEq)]
8pub enum Error {
9 #[error("Records off must not contain offset period: {offset_period}")]
11 RecordsOffMustNotContainOffsetPeriod { offset_period: String },
12
13 #[error("Start period ('{start}') is after end period ('{end}')")]
15 StartPeriodAfterEndPeriod { start: String, end: String },
16
17 #[error("Invalid Period, bad value: {bad_value}")]
19 InvalidPeriod { bad_value: String },
20
21 #[error("Period is not YearAndMonth")]
23 PeriodIsNotYearAndMonth,
24
25 #[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 #[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 #[error("Cannot invoice for month when cadence is bi-weekly")]
49 CannotInvoiceForMonthWhenCadenceIsBiWeekly,
50
51 #[error("Cannot expense for month when cadence is bi-weekly")]
53 CannotExpenseForMonthWhenCadenceIsBiWeekly,
54
55 #[error("Cannot expense for fortnight when cadence is monthly")]
57 CannotExpenseForFortnightWhenCadenceIsMonthly,
58
59 #[error("Passwords do not match")]
62 PasswordDoesNotMatch,
63
64 #[error(
66 "Email password is too short, expected at least {min_length} characters, but found {actual_length}"
67 )]
68 EmailPasswordTooShort {
69 min_length: usize,
71 actual_length: usize,
73 },
74
75 #[error("Failed to parse email atom template: {underlying}")]
77 EmailAtomTemplateError { underlying: String },
78
79 #[error("Invalid email address for: {role}, because: {underlying}")]
81 InvalidEmailAddress { role: String, underlying: String },
82
83 #[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 #[error("Recipient addresses cannot be empty")]
92 RecipientAddressesCannotBeEmpty,
93
94 #[error("Failed to parse SMTP Server, because: {underlying}")]
96 InvalidSmtpServer { underlying: String },
97
98 #[error("Failed to parse string into a valid UTF-8 string")]
100 InvalidUtf8,
101
102 #[error("Failed to decrypt data with AES")]
104 AESDecryptionFailed,
105
106 #[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 #[error("Failed to create SMTP transport, because: {underlying}")]
117 CreateSmtpTransportError { underlying: String },
118
119 #[error("Failed to create email, because: {underlying}")]
121 CreateEmailError { underlying: String },
122
123 #[error("Failed to add attachments to the email, because: {underlying}")]
125 AddAttachmentsError {
126 underlying: String,
128 },
129
130 #[error("Failed to send email, because: {underlying}")]
132 SendEmailError { underlying: String },
133
134 #[error("Failed to convert to f64 from Decimal, because: {value}")]
136 InvalidDecimalToF64Conversion { value: String },
137
138 #[error("Failed to convert f64 to Decimal, because: {value}")]
140 InvalidDecimalFromF64Conversion { value: f64 },
141
142 #[error("Failed to load font with family name: '{family_name}'")]
144 FailedToLoadFont { family_name: String },
145
146 #[error("Failed to parse f64 from string: {bad_value}, reason: {reason}")]
148 InvalidF64String { bad_value: String, reason: String },
149
150 #[error("Failed to write data to disk, because: {underlying}")]
152 FailedToWriteDataToDisk { underlying: String },
153
154 #[error("Failed to RON serialize data, because: {underlying}")]
156 FailedToRonSerializeData {
157 type_name: String,
158 underlying: String,
159 },
160
161 #[error("Failed to build CompanyInformation from Terminal UI input, because: {reason}")]
163 InvalidCompanyInformation { reason: String },
164
165 #[error("Failed to parse invoice number from string: {invalid_string}")]
167 InvalidInvoiceNumberString { invalid_string: String },
168
169 #[error("Failed to build InvoiceInfo from Terminal UI input, because: {reason}")]
171 InvalidInvoiceInfo { reason: String },
172
173 #[error("Failed to build PaymentInfo from Terminal UI input, because: {reason}")]
175 InvalidPaymentInfo { reason: String },
176
177 #[error("Failed to build ServiceFees from Terminal UI input, because: {reason}")]
179 InvalidServiceFees { reason: String },
180
181 #[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 #[error("Specified output path does not exist: {path}")]
192 SpecifiedOutputPathDoesNotExist { path: String },
193
194 #[error("Failed to create output directory: {underlying}")]
196 FailedToCreateOutputDirectory { underlying: String },
197
198 #[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 #[error("Failed to parse year: {invalid_string}")]
207 FailedToParseYear { invalid_string: String },
208
209 #[error("Failed to load file: {path}, underlying: {underlying}")]
211 FileNotFound { path: String, underlying: String },
212
213 #[error("Failed to deserialize {type_name}, because: {error}")]
215 Deserialize { type_name: String, error: String },
216
217 #[error("Invalid day from String: {invalid_string}, reason: {reason}")]
219 InvalidDayFromString {
220 invalid_string: String,
221 reason: String,
222 },
223
224 #[error("Invalid YearAndMonth, underlying: {underlying}")]
226 InvalidYearAndMonth { underlying: String },
227
228 #[error("Invalid date, underlying: {underlying}")]
230 InvalidDate { underlying: String },
231
232 #[error("Invalid day: {day}, reason: {reason}")]
234 InvalidDay { day: i32, reason: String },
235
236 #[error("Invalid month: {month}, reason: {reason}")]
238 InvalidMonth { month: i32, reason: String },
239
240 #[error("Failed to parse Month: {invalid_string}")]
242 FailedToParseMonth { invalid_string: String },
243
244 #[error("Failed to parse expense item from: '{invalid_string}': {reason}")]
246 InvalidExpenseItem {
247 invalid_string: String,
248 reason: String,
249 },
250
251 #[error("Target period {target_period} is in the record of periods off, but it must not be.")]
253 TargetPeriodMustNotBeInRecordOfPeriodsOff { target_period: String },
254
255 #[error("Failed to PaymentTerms NetDays from string: {invalid_string}")]
257 FailedToParsePaymentTermsNetDays { invalid_string: String },
258
259 #[error("Failed to find the localization file for language: {language}")]
261 L18nNotFound {
262 language: Language,
264 },
265
266 #[error("Invalid hex color format: {invalid_string}")]
268 InvalidHexColor { invalid_string: String },
269
270 #[error("Failed to parse date, because: {underlying}")]
272 FailedToParseDate { underlying: String },
273
274 #[error("Found no exchange rate for {target} based on {base}")]
276 FoundNoExchangeRate {
277 target: Currency,
279 base: Currency,
281 },
282
283 #[error("Failed to load Typst source, because: {underlying}")]
285 LoadSource { underlying: String },
286
287 #[error("Failed to compile Typst source, because: {underlying}")]
289 BuildPdf { underlying: String },
290
291 #[error("Failed to export PagedDocument to PDF, because: {underlying}")]
293 ExportDocumentToPdf { underlying: String },
294
295 #[error("Failed to save PDF, because: {underlying}")]
297 SavePdf { underlying: String },
298
299 #[error("Failed fetch exchange rate from API, because: {underlying}")]
301 NetworkError { underlying: String },
302
303 #[error("Failed to parse exchange rate response, because: {underlying}")]
305 ParseError { underlying: String },
306}