Current section

Files

Jump to
snag src snag.gleam
Raw

src/snag.gleam

import gleam
import gleam/string_builder
import gleam/string
import gleam/list
import gleam/int
/// A Snag is a boilerplate-free error type that can be used to track why an
/// error happened, though does not store as much detail on specific errors as a
/// custom error type would.
///
/// It is useful in code where it must either pass or fail, and when it fails we
/// want good debugging information to print to the user. i.e. Command line
/// tools, data processing pipelines, etc.
///
/// If it not suited to code where the application needs to make a decision about
/// what to do in the event of an error, such as whether to give up or to try
/// again. i.e. Libraries, web application backends, API clients, etc.
/// In these situations it is recommended to create a custom type for your errors
/// as it can be pattern matched on and have any additional detail added as
/// fields.
pub type Snag {
Snag(issue: String, cause: List(String))
}
/// A concise alias for a `Result` that uses a `Snag` as the error value.
pub type Result(t) =
gleam.Result(t, Snag)
/// Create a new `Snag` with the given issue text.
///
/// See also the `error` function for creating a `Snag` wrapped in a `Result`.
///
/// # Example
///
/// ```gleam
/// > new("Not enough credit")
/// > |> line_print
/// "error: Not enough credit"
/// ```
pub fn new(issue: String) -> Snag {
Snag(issue: issue, cause: [])
}
/// Create a new `Snag` wrapped in a `Result` with the given issue text.
///
/// # Example
///
/// ```gleam
/// > error("Not enough credit")
/// Error(new("Not enough credit"))
/// ```
pub fn error(issue: String) -> Result(success) {
Error(new(issue))
}
/// Add additional contextual information to a `Snag`.
///
/// See also the `context` function for adding contextual information to a `Snag`
/// wrapped in a `Result`.
///
/// # Example
///
/// ```gleam
/// > new("Not enough credit")
/// > |> layer("Unable to make purchase")
/// > |> line_print
/// "error: Unable to make purchase <- Not enough credit"
/// ```
pub fn layer(snag: Snag, issue: String) -> Snag {
Snag(issue: issue, cause: [snag.issue, ..snag.cause])
}
/// Add additional contextual information to a `Snag` wrapped in a `Result`.
///
/// # Example
///
/// ```gleam
/// > error("Not enough credit")
/// > |> context("Unable to make purchase")
/// > |> result.map_error(line_print)
/// Error("error: Unable to make purchase <- Not enough credit")
/// ```
pub fn context(result: Result(success), issue: String) -> Result(success) {
case result {
Ok(_) -> result
Error(snag) -> Error(layer(snag, issue))
}
}
/// Turn a snag into a multi-line string, optimised for readability.
///
/// # Example
///
/// ```gleam
/// > new("Not enough credit")
/// > |> layer("Unable to make purchase")
/// > |> layer("Character creation failed")
/// > |> pretty_print
/// "error: Character creation failed
///
/// cause:
/// 0: Unable to make purchase
/// 1: Not enough credit
/// "
/// ```
pub fn pretty_print(snag: Snag) -> String {
let builder = string_builder.from_strings(["error: ", snag.issue, "\n"])
string_builder.to_string(case snag.cause {
[] -> builder
cause ->
builder
|> string_builder.append("\ncause:\n")
|> string_builder.append_builder(pretty_print_cause(cause))
})
}
fn pretty_print_cause(cause) {
cause
|> list.index_map(fn(index, line) {
string.concat([" ", int.to_string(index), ": ", line, "\n"])
})
|> string_builder.from_strings
}
/// Turn a snag into a single-line string, optimised for compactness. This may be
/// useful for logging snags.
///
/// # Example
///
/// ```gleam
/// > new("Not enough credit")
/// > |> layer("Unable to make purchase")
/// > |> layer("Character creation failed")
/// > |> pretty_print
/// "error: Character creation failed <- Unable to make purchase <- Not enough credit"
/// ```
pub fn line_print(snag: Snag) -> String {
[string.append("error: ", snag.issue), ..snag.cause]
|> string.join(" <- ")
}