Packages

Easy idiomatic error handling for application code.

Current section

Files

Jump to
gleyre src gleyre.gleam
Raw

src/gleyre.gleam

import gleam/int
import gleam/list
import gleam/string
import gleam/string_tree
import gleam/yielder.{type Yielder}
/// The guts of the error variant of an `Outcome`.
///
/// Contains the layers of error messages.
pub opaque type Report {
Report(layers: List(String))
}
/// Turn a `Report` into a formatted message displaying the layers of
/// error messaging.
pub fn to_string(report: Report) -> String {
list.index_fold(report.layers, string_tree.new(), fn(acc, next, idx) {
[int.to_string(idx) |> string.pad_start(4, " "), ": ", next, "\n"]
|> string_tree.from_strings()
|> string_tree.append_tree(acc)
})
|> string_tree.to_string()
}
/// Analog of the `Result` -- analagous to a `Result(a, Report)`.
pub type Outcome(a) {
Okay(a)
Err(Report)
}
/// Create a new error variant.
pub fn error(message: String) -> Outcome(a) {
Err(Report([message]))
}
/// Create a new okay variant.
pub fn okay(ok: a) -> Outcome(a) {
Okay(ok)
}
/// Convert from a regular `Result` by using `string.inspect()` if the
/// `Result` is an error.
pub fn from(std_result: Result(a, b)) -> Outcome(a) {
case std_result {
Ok(x) -> Okay(x)
Error(e) -> Err(Report([string.inspect(e)]))
}
}
/// Turn you `Outcome` back into a regular Gleam `Error`.
pub fn into(outcome: Outcome(a)) -> Result(a, String) {
case outcome {
Okay(x) -> Ok(x)
Err(err) -> to_string(err) |> Error()
}
}
/// Add a layer of messaging to an error variant.
pub fn annotate(report: Report, message: String) -> Report {
Report(layers: [message, ..report.layers])
}
/// Like `result.map()` -- applies the given function if the outcome is
/// `Okay`.
pub fn map(outcome: Outcome(a), f: fn(a) -> b) -> Outcome(b) {
case outcome {
Okay(x) -> Okay(f(x))
Err(e) -> Err(e)
}
}
/// If the `Outcome` is an error, this adds another layer of message to it.
pub fn wrap(outcome: Outcome(a), message: String) -> Outcome(a) {
case outcome {
Okay(x) -> Okay(x)
Err(e) -> Err(annotate(e, message))
}
}
/// Like `result.try`; for exploiting `use` sugar.
///
/// ```gleam
/// fn fallible_function(data: Kind) -> Outcome(OtherKind) {
/// use step_one <- gleyre.try(other_fallible_function(data))
/// use step_two <- gleyre.try(yet_another(step_one))
///
/// Okay(step_two)
/// }
/// ```
pub fn try(outcome: Outcome(a), next: fn(a) -> Outcome(b)) -> Outcome(b) {
case outcome {
Okay(x) -> next(x)
Err(y) -> Err(y)
}
}
/// For the `use`-case, but also adds an extra level of messaging to the error.
pub fn try_or_wrap(
outcome: Outcome(a),
message: String,
next: fn(a) -> Outcome(b),
) -> Outcome(b) {
case outcome {
Okay(x) -> next(x)
Err(y) -> Err(annotate(y, message))
}
}
/// Iterate down through the layers of error messages.
pub fn yield_errors(report: Report) -> Yielder(String) {
yielder.from_list(report.layers)
}
/// Iterate down through the _indexed_ layers of error messages.
pub fn yield_indexed(report: Report) -> Yielder(#(String, Int)) {
yield_errors(report) |> yielder.index()
}
fn all_aux(acc: List(a), dsp: List(Outcome(a))) -> Outcome(List(a)) {
case dsp {
[] -> list.reverse(acc) |> Okay()
[Okay(x), ..rest] -> all_aux([x, ..acc], rest)
[Err(e), ..] -> Err(e)
}
}
/// Like `result.all()`, either returns the first `Err()` value in the
/// list, or the whole list of positive outcomes.
pub fn all(outcomes: List(Outcome(a))) -> Outcome(List(a)) {
all_aux([], outcomes)
}