Skip to content

Better errors#29

Merged
Sajjon merged 2 commits intomainfrom
better_errors
Feb 19, 2026
Merged

Better errors#29
Sajjon merged 2 commits intomainfrom
better_errors

Conversation

@Sajjon
Copy link
Copy Markdown
Owner

@Sajjon Sajjon commented Feb 19, 2026

No description provided.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 inline map_err closures with reusable mappers.
  • Introduced crates/cli/src/error.rs with CliError plus structured TUI error enums, and updated CLI control flow to return CliResult.
  • Removed klirr-render-pdf dependency from klirr-core-invoice and 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, but create_smtp_transport_error also prepends that text into the underlying field. This will lead to duplicated phrasing in the final error message; consider storing only the debug-formatted source error in underlying.
    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, but send_email_error also prepends that text into underlying. This duplicates wording in the rendered error; consider formatting underlying as 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_error eagerly converts context into a String before returning the closure (let context = context.into();). When called with &str, that allocates even on the success path. Consider accepting &str (or impl Display + 'a) and doing the format!/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_item converts inputs into owned Strings when constructing the map_err closure (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_error eagerly converts context into a String before returning the closure (let context = context.into();), which allocates even when the network request succeeds. Consider capturing context as &str/impl Display + 'a and only formatting/allocating the final String inside 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_found eagerly converts path into a String before returning the closure (let path = path.into();), so callers that pass path.display().to_string() (or even a &str) will pay that allocation even when the read succeeds. Consider taking path: impl Display + 'a (or &Path) and constructing the String inside 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_data converts type_name into an owned String before 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 str from std::any::type_name::<T>()) and only allocating/formatting the String inside 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
Copy link
Copy Markdown

codecov bot commented Feb 19, 2026

Codecov Report

❌ Patch coverage is 93.22034% with 8 lines in your changes missing coverage. Please review.
✅ Project coverage is 94.88%. Comparing base (08c96fe) to head (5fcbc24).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
crates/core-invoice/src/logic/command/command.rs 85.00% 3 Missing ⚠️
...c/prepare_data/fetch_exchange_rate_with_reqwest.rs 0.00% 2 Missing ⚠️
crates/core-invoice/src/logic/send_email.rs 0.00% 2 Missing ⚠️
...-invoice/src/models/deserialize_contents_of_ron.rs 66.66% 1 Missing ⚠️
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.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@Sajjon Sajjon merged commit bc90cad into main Feb 19, 2026
6 of 7 checks passed
@Sajjon Sajjon deleted the better_errors branch February 19, 2026 10:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants