Conversation
There was a problem hiding this comment.
Pull request overview
Refactors error handling across the workspace to produce cleaner, more consistent error messages and reduce repetitive map_err boilerplate, including introducing a dedicated CLI error type and moving TUI-specific errors out of klirr-core-invoice.
Changes:
- Added error-constructor helpers (
Error::{...}) and replaced inlinemap_errclosures with reusable mappers. - Introduced
crates/cli/src/error.rswithCliErrorplus structured TUI error enums, and updated CLI control flow to returnCliResult. - Removed
klirr-render-pdfdependency fromklirr-core-invoiceand simplified error mapping by delegating render errors to CLI-layer error wrapping.
Reviewed changes
Copilot reviewed 32 out of 33 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
| crates/render-pdf/src/typst_context/context.rs | Uses a shared error-constructor helper for LoadSource. |
| crates/render-pdf/src/render.rs | Uses shared error constructors for compile/export errors. |
| crates/render-pdf/src/error.rs | Adds public error helper constructors for consistent error creation. |
| crates/foundation/src/models/pdf.rs | Extracts Debug-formatting of I/O errors into a helper for save_pdf. |
| crates/core-invoice/src/models/item.rs | Refactors parsing error mapping to a shared helper. |
| crates/core-invoice/src/models/error.rs | Removes CLI/TUI-ish variants and adds many map_err helper constructors. |
| crates/core-invoice/src/models/deserialize_contents_of_ron.rs | Refactors file-not-found and deserialize error mapping to helper constructors. |
| crates/core-invoice/src/models/data/submodels/email/lettre_bridge.rs | Uses helper constructor for CreateEmailError. |
| crates/core-invoice/src/logic/send_email.rs | Uses helper constructors for SMTP transport creation and send failures. |
| crates/core-invoice/src/logic/read_write_data/read_data_from_disk.rs | Uses helper constructors for RON serialization and file write errors. |
| crates/core-invoice/src/logic/prepare_data/get_exchange_rates.rs | Uses helper constructor for parse errors in JSON response handling (incl. tests). |
| crates/core-invoice/src/logic/prepare_data/fetch_exchange_rate_with_reqwest.rs | Uses helper constructor for network errors when fetching exchange rates. |
| crates/core-invoice/src/logic/file_path_logic.rs | Uses helper constructor for output directory creation errors. |
| crates/core-invoice/src/logic/encryption/aes_gcm_256.rs | Centralizes AES decrypt logging + mapping via helper constructor. |
| crates/core-invoice/src/logic/create_invoice_pdf.rs | Generalizes functions over error type E: From<Error> to integrate with outer layers. |
| crates/core-invoice/src/logic/command/command.rs | Generalizes command helpers over error type E and updates tests accordingly. |
| crates/core-invoice/Cargo.toml | Drops unused klirr-render-pdf dependency from core-invoice. |
| crates/cli/src/run.rs | Changes CLI runner to return CliResult<()> and propagate failures. |
| crates/cli/src/main.rs | Adds CLI error module and exits non-zero on run() failure. |
| crates/cli/src/input/tui/helpers/build_smtp_server.rs | Moves SMTP prompt errors into EmailFromTuiError and maps into CliError. |
| crates/cli/src/input/tui/helpers/build_service_fees.rs | Maps TUI errors into InvoiceDataFromTuiError then into CliError. |
| crates/cli/src/input/tui/helpers/build_payment_info.rs | Maps TUI errors into InvoiceDataFromTuiError then into CliError. |
| crates/cli/src/input/tui/helpers/build_password.rs | Moves password prompt/validation errors into EmailFromTuiError. |
| crates/cli/src/input/tui/helpers/build_invoice_info.rs | Maps invoice-info prompt errors into InvoiceDataFromTuiError. |
| crates/cli/src/input/tui/helpers/build_email_template.rs | Moves template parsing prompt errors into EmailFromTuiError. |
| crates/cli/src/input/tui/helpers/build_email_address.rs | Moves email address prompt errors into EmailFromTuiError. |
| crates/cli/src/input/tui/helpers/build_email_account.rs | Moves email account prompt errors into EmailFromTuiError. |
| crates/cli/src/input/tui/helpers/build_date.rs | Maps date prompt errors into InvoiceDataFromTuiError. |
| crates/cli/src/input/tui/helpers/build_company.rs | Maps company-info prompt errors into InvoiceDataFromTuiError. |
| crates/cli/src/input/tui/build_email_settings.rs | Moves empty-recipient validation into EmailFromTuiError. |
| crates/cli/src/error.rs | Introduces CliError, EmailFromTuiError, and InvalidInvoiceData (+ aliases). |
| crates/cli/src/dispatch_command.rs | Simplifies render error mapping and maps core-invoice errors into CLI errors. |
| Cargo.lock | Removes klirr-render-pdf from core-invoice dependency graph. |
Comments suppressed due to low confidence (7)
crates/core-invoice/src/models/error.rs:239
CreateSmtpTransportError’s#[error(...)]already adds the "Failed to create SMTP transport" context, butcreate_smtp_transport_erroralso prepends that text into theunderlyingfield. This will lead to duplicated phrasing in the final error message; consider storing only the debug-formatted source error inunderlying.
pub fn create_smtp_transport_error(underlying: impl std::fmt::Debug) -> Self {
Self::CreateSmtpTransportError {
underlying: format!("Failed to create SMTP transport: {underlying:?}"),
}
crates/core-invoice/src/models/error.rs:246
SendEmailError’s#[error(...)]already adds the "Failed to send email" context, butsend_email_erroralso prepends that text intounderlying. This duplicates wording in the rendered error; consider formattingunderlyingas just the debug-formatted source error.
pub fn send_email_error(underlying: impl std::fmt::Debug) -> Self {
Self::SendEmailError {
underlying: format!("Failed to send email: {underlying:?}"),
}
crates/core-invoice/src/models/error.rs:313
parse_erroreagerly convertscontextinto aStringbefore returning the closure (let context = context.into();). When called with&str, that allocates even on the success path. Consider accepting&str(orimpl Display + 'a) and doing theformat!/to_string()inside the returned closure so allocation only happens when an error actually occurs.
pub fn parse_error<E: std::fmt::Display>(context: impl Into<String>) -> impl FnOnce(E) -> Self {
let context = context.into();
move |error| Self::ParseError {
underlying: format!("{context}: {error}"),
}
crates/core-invoice/src/models/error.rs:333
invalid_expense_itemconverts inputs into ownedStrings when constructing themap_errclosure (e.g.let invalid_string = invalid_string.into();), so allocations happen even when parsing succeeds. Consider capturing&str/&'static str(or other non-allocating inputs) in the returned closure and only allocating/building the strings if the error path is taken.
invalid_string: impl Into<String>,
field: impl Into<String>,
) -> impl FnOnce(E) -> Self {
let invalid_string = invalid_string.into();
let field = field.into();
crates/core-invoice/src/models/error.rs:321
network_erroreagerly convertscontextinto aStringbefore returning the closure (let context = context.into();), which allocates even when the network request succeeds. Consider capturingcontextas&str/impl Display + 'aand only formatting/allocating the finalStringinside the closure when an error occurs.
pub fn network_error<E: std::fmt::Display>(
context: impl Into<String>,
) -> impl FnOnce(E) -> Self {
let context = context.into();
move |error| Self::NetworkError {
crates/core-invoice/src/models/error.rs:293
file_not_foundeagerly convertspathinto aStringbefore returning the closure (let path = path.into();), so callers that passpath.display().to_string()(or even a&str) will pay that allocation even when the read succeeds. Consider takingpath: impl Display + 'a(or&Path) and constructing theStringinside the returned closure so it only allocates on the error path.
pub fn file_not_found<E: std::fmt::Debug>(path: impl Into<String>) -> impl FnOnce(E) -> Self {
let path = path.into();
move |error| Self::FileNotFound {
path,
underlying: format!("{error:?}"),
crates/core-invoice/src/models/error.rs:282
failed_to_ron_serialize_dataconvertstype_nameinto an ownedStringbefore returning the closure (let type_name = type_name.into();), which can move work (and allocation) from the error path to the success path at callsites. If keeping this helper, consider capturing a non-allocating value (e.g.&'static strfromstd::any::type_name::<T>()) and only allocating/formatting theStringinside the closure when serialization actually fails.
pub fn failed_to_ron_serialize_data<E: std::fmt::Debug>(
type_name: impl Into<String>,
) -> impl FnOnce(E) -> Self {
let type_name = type_name.into();
move |error| Self::FailedToRonSerializeData {
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #29 +/- ##
==========================================
+ Coverage 94.50% 94.88% +0.37%
==========================================
Files 100 103 +3
Lines 2202 2248 +46
==========================================
+ Hits 2081 2133 +52
+ Misses 121 115 -6 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
No description provided.