/
snag.gleam
141 lines (131 loc) · 3.89 KB
/
snag.gleam
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
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(line, index) {
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(" <- ")
}