-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy patherror_registry.go
More file actions
130 lines (108 loc) · 3.98 KB
/
Copy patherror_registry.go
File metadata and controls
130 lines (108 loc) · 3.98 KB
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
package restapi
import (
"fmt"
"sync"
)
// ErrorRegistry maps application error codes to [ErrorRegistryEntry] values.
// A registry also holds a fallback entry that is returned whenever a lookup
// misses.
//
// The zero value is not usable; construct a registry with [NewErrorRegistry].
// All methods are safe for concurrent use.
type ErrorRegistry struct {
fallback ErrorRegistryEntry
entries map[int]ErrorRegistryEntry
mu sync.RWMutex
}
// NewErrorRegistry returns a new [ErrorRegistry] that falls back to the
// supplied entry on missed lookups. The initial batch of entries, if any, is
// registered atomically: if any entry is invalid, the registry is returned
// with no entries registered and an error is returned.
//
// The fallback entry's ErrorCode must not appear in entries; attempting to
// register it returns [ErrFallbackErrorCodeRegistration].
func NewErrorRegistry(fallback ErrorRegistryEntry, entries ...ErrorRegistryEntry) (*ErrorRegistry, error) {
registry := &ErrorRegistry{
fallback: fallback,
entries: make(map[int]ErrorRegistryEntry),
}
if err := registry.Register(entries...); err != nil {
return nil, err
}
return registry, nil
}
// Fallback returns the entry that this registry uses for missed lookups.
func (r *ErrorRegistry) Fallback() ErrorRegistryEntry {
return r.fallback
}
// IsRegistered reports whether errorCode is explicitly registered. It returns
// false for codes that would resolve to the fallback entry.
func (r *ErrorRegistry) IsRegistered(errorCode int) bool {
_, ok := r.resolve(errorCode)
return ok
}
// Register inserts one or more entries into the registry. It is atomic: if
// any entry would conflict with an already-registered code, collide with the
// fallback code, or duplicate another entry within the same call, no entries
// from the batch are inserted and an error is returned.
//
// The returned error wraps one of [ErrCodeAlreadyRegistered] or
// [ErrFallbackErrorCodeRegistration] so callers can match on it with
// [errors.Is].
func (r *ErrorRegistry) Register(entries ...ErrorRegistryEntry) error {
if len(entries) == 0 {
return nil
}
r.mu.Lock()
defer r.mu.Unlock()
batch := make(map[int]struct{}, len(entries))
for _, entry := range entries {
if entry.ErrorCode == r.fallback.ErrorCode {
return fmt.Errorf("%w: %d", ErrFallbackErrorCodeRegistration, entry.ErrorCode)
}
if _, ok := batch[entry.ErrorCode]; ok {
return fmt.Errorf("%w: %d", ErrCodeAlreadyRegistered, entry.ErrorCode)
}
if _, ok := r.entries[entry.ErrorCode]; ok {
return fmt.Errorf("%w: %d", ErrCodeAlreadyRegistered, entry.ErrorCode)
}
batch[entry.ErrorCode] = struct{}{}
}
for _, entry := range entries {
r.entries[entry.ErrorCode] = entry
}
return nil
}
// MustRegister is like [ErrorRegistry.Register] but panics on error. It is
// convenient for static registration at program start-up.
func (r *ErrorRegistry) MustRegister(entries ...ErrorRegistryEntry) {
if err := r.Register(entries...); err != nil {
panic(err)
}
}
// Lookup returns the entry associated with errorCode. The second return value
// reports whether the entry was found (true) or the fallback was used (false).
func (r *ErrorRegistry) Lookup(errorCode int) (ErrorRegistryEntry, bool) {
return r.resolve(errorCode)
}
// MustLookup is like [ErrorRegistry.Lookup] but returns a wrapped
// [ErrUnknownErrorCode] instead of falling back to the registry fallback
// entry when the code is not registered.
func (r *ErrorRegistry) MustLookup(errorCode int) (ErrorRegistryEntry, error) {
entry, ok := r.resolve(errorCode)
if !ok {
return entry, fmt.Errorf("%w: %d", ErrUnknownErrorCode, errorCode)
}
return entry, nil
}
// resolve performs the lookup with the read lock held. It returns the
// registered entry and true on a hit, or the fallback entry and false on a
// miss.
func (r *ErrorRegistry) resolve(errorCode int) (ErrorRegistryEntry, bool) {
r.mu.RLock()
defer r.mu.RUnlock()
if entry, ok := r.entries[errorCode]; ok {
return entry, true
}
return r.fallback, false
}