forked from destel/rill
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathwrap.go
142 lines (120 loc) · 3.26 KB
/
wrap.go
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
142
package rill
// Try is a container for a value or an error
type Try[A any] struct {
Value A
Error error
}
// Wrap converts a value and/or error into a [Try] container.
// It's a convenience function to avoid creating a [Try] container manually and benefit from type inference.
func Wrap[A any](value A, err error) Try[A] {
return Try[A]{Value: value, Error: err}
}
// FromSlice converts a slice into a channel of [Try] containers.
// If err is not nil function returns a single [Try] container with the error.
func FromSlice[A any](slice []A, err error) <-chan Try[A] {
if err != nil {
out := make(chan Try[A], 1)
out <- Try[A]{Error: err}
close(out)
return out
}
out := make(chan Try[A], len(slice))
for _, a := range slice {
out <- Try[A]{Value: a}
}
close(out)
return out
}
// ToSlice converts a channel of [Try] containers into a slice of values and an error.
// It's an inverse of [FromSlice]. The function blocks until the whole channel is processed or an error is encountered.
// In case of an error leading to early termination, ToSlice ensures the input channel is drained to avoid goroutine leaks.
func ToSlice[A any](in <-chan Try[A]) ([]A, error) {
var res []A
for x := range in {
if err := x.Error; err != nil {
DrainNB(in)
return res, err
}
res = append(res, x.Value)
}
return res, nil
}
// FromChan converts a regular channel into a channel of values wrapped in a [Try] container.
// Additionally, this function can take an error, that will be added to the output channel alongside the values.
// If both values and error are nil, the function returns nil.
func FromChan[A any](values <-chan A, err error) <-chan Try[A] {
if values == nil && err == nil {
return nil
}
out := make(chan Try[A])
go func() {
defer close(out)
// error goes first
if err != nil {
out <- Try[A]{Error: err}
}
for x := range values {
out <- Try[A]{Value: x}
}
}()
return out
}
// FromChans converts a regular channel into a channel of values wrapped in a [Try] container.
// Additionally, this function can take a channel of errors, which will be added to
// the output channel alongside the values.
// If both values and errors are nil, the function returns nil.
func FromChans[A any](values <-chan A, errs <-chan error) <-chan Try[A] {
if values == nil && errs == nil {
return nil
}
out := make(chan Try[A])
go func() {
defer close(out)
for {
select {
case err, ok := <-errs:
if ok {
if err != nil {
out <- Try[A]{Error: err}
}
} else {
errs = nil
if values == nil && errs == nil {
return
}
}
case v, ok := <-values:
if ok {
out <- Try[A]{Value: v}
} else {
values = nil
if values == nil && errs == nil {
return
}
}
}
}
}()
return out
}
// ToChans splits a channel of [Try] containers into a channel of values and a channel of errors.
// It's an inverse of [FromChans]. Returns two nil channels if the input is nil.
func ToChans[A any](in <-chan Try[A]) (<-chan A, <-chan error) {
if in == nil {
return nil, nil
}
out := make(chan A)
errs := make(chan error)
go func() {
defer close(out)
defer close(errs)
for x := range in {
if x.Error != nil {
errs <- x.Error
} else {
out <- x.Value
}
}
}()
return out, errs
}