glerror

Error handling utilities with support for error kinds, context, and sources.

Usage

Simple string-based errors

import glerror

pub fn fetch_user(id: Int) -> glerror.Result(User) {
  case http.get("/users/" <> int.to_string(id)) {
    Ok(response) -> parse_user(response)
    Error(_) -> Error(glerror.error("HTTP request failed"))
  }
  |> glerror.context("fetching user " <> int.to_string(id))
}

Custom error kinds

import glerror

pub type DatabaseError {
  NotFound
  ConnectionFailed
  QueryError(details: String)
}

fn format_database_error(kind: DatabaseError) -> glerror.Format {
  func(_) {
    case kind {
      NotFound -> "not found"
      ConnectionFailed -> "connection failed"
      QueryError(details) -> "query error (" <> details <> ")"
    }
  }
}

pub fn get_record(id: Int) -> Result(Record, glerror.KindError(DatabaseError)) {
  case execute_query("SELECT * FROM records WHERE id = ?", [id]) {
    Ok(record) -> Ok(record)
    Error(_) -> Error(glerror.new(glerror.Kind(kind: NotFound, format: format_database_error(NotFound))))
  }
  |> glerror.context("fetching record")
}

Displaying errors

let err = glerror.error("connection refused")
  |> glerror.add_context("connecting to database")
  |> glerror.add_context("initializing application")

// Single-line format
glerror.to_string(err)
// "connection refused [connecting to database, initializing application]"

// Pretty-printed format
glerror.pretty_print(err)
// error: connection refused
// context:
//   0: connecting to database
//   1: initializing application

Errors with a source

Errors can include a source — another error that caused them. This is useful for wrapping lower-level errors with higher-level context.

let source = glerror.error("HTTP 404")

let err = glerror.error("Not found")
  |> glerror.add_context("fetching user")
  |> glerror.add_context("loading dashboard")
  |> glerror.add_source(source)

glerror.pretty_print(err)
// error: Not found
// context:
//   0: loading dashboard
//   1: fetching user
// sources:
// ╰─▶ HTTP 404

Multiple sources

An error can have more than one source:

glerror.to_string(err)
// "Database error (sources: Source A, Source B)"

glerror.pretty_print(err)
// error: Database error
// sources:
// ├─▶ Source A
// ╰─▶ Source B

Nested sources

Sources can themselves have sources, forming a chain that traces the full error path:

let tcp_err = glerror.error("TCP timeout")

let db_err = glerror.error("Database error")
  |> glerror.add_context("connecting to database")
  |> glerror.add_source(tcp_err)

let err = glerror.error("Not found")
  |> glerror.add_context("fetching user")
  |> glerror.add_context("loading dashboard")
  |> glerror.add_source(db_err)

glerror.pretty_print(err)
// error: Not found
// context:
//   0: loading dashboard
//   1: fetching user
// sources:
// ╰─▶ error: Database error
//     context:
//       0: connecting to database
//     sources:
//     ╰─▶ TCP timeout

Multiple nested sources are displayed as a tree:

// error: Not found
// context:
//   0: fetching user
// sources:
// ├─▶ error: Database error
// │   context:
// │     0: connecting to database
// │   sources:
// │   ╰─▶ TCP timeout
// ╰─▶ error: Invalid: config
//     context:
//       0: parsing config
//     sources:
//     ╰─▶ Missing field
Search Document