Skip to content

Replace unwrap/expect calls with proper error propagation #48

@jacobb

Description

@jacobb

Problem

There are 54+ unwrap()/expect() calls scattered across the codebase, many of which can panic on valid error conditions (file I/O failures, malformed frontmatter, missing schema fields, non-UTF8 filenames). Some have placeholder messages like "oops" or typos ("failred").

Notable examples

  • settings/mod.rs:61fs::read_to_string(...).expect("oops") on template loading
  • settings/mod.rs:88panic!("HOME directory not found") during config init
  • settings/mod.rs:104-105LazyLock with .expect("failred"), no recovery if settings fail
  • note.rs:113unwrap() on Tantivy document field extraction (crashes if schema changes)
  • walk.rs:5entry.file_name().to_str().unwrap() (panics on non-UTF8 filenames)
  • search/index.rs, search/query.rs — multiple unwrap() on schema field lookups

Recommended approach

1. Define a crate-level error type with thiserror

A NoteError struct already exists but is minimal. Replace it with an enum covering the main failure domains:

#[derive(Debug, thiserror::Error)]
pub enum InkError {
    #[error("IO error: {0}")]
    Io(#[from] std::io::Error),
    #[error("Search error: {0}")]
    Search(#[from] tantivy::TantivyError),
    #[error("Config error: {0}")]
    Config(#[from] config::ConfigError),
    #[error("Template error: {0}")]
    Template(#[from] minijinja::Error),
    #[error("{0}")]
    Other(String),
}

2. Propagate errors with Result<T, InkError> and ?

Convert functions that currently unwrap() to return Result. This is mechanical — replace .unwrap() with ? and update return types up the call chain. The CLI dispatch layer in cli.rs is the natural place to catch errors and print user-friendly messages before exiting with a non-zero code.

3. Handle the LazyLock settings case

LazyLock closures can't return Result, so the settings init is a special case. Options:

  • Use LazyLock<Result<Settings, InkError>> and handle the error at each access point
  • Keep the expect() but with a descriptive message, since a missing HOME dir is truly unrecoverable
  • Initialize settings explicitly in main() and pass them through, avoiding the global

4. Use anyhow in main() as an alternative

For a CLI tool, anyhow::Result in main() is a pragmatic choice that gives good error display with minimal boilerplate. This can coexist with a typed InkError for library-level code, or replace it entirely if the crate isn't used as a library.

Scope

This is a good candidate for incremental work — each module can be converted independently, starting with the most crash-prone paths (settings init, note loading, search queries).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions