-
Notifications
You must be signed in to change notification settings - Fork 9
/
errors.go
278 lines (225 loc) · 8.09 KB
/
errors.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
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
// Copyright 2022 Namespace Labs Inc; All rights reserved.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
package fnerrors
import (
"errors"
"fmt"
"io"
"google.golang.org/grpc/status"
"namespacelabs.dev/foundation/internal/cli/fncobra/name"
"namespacelabs.dev/foundation/internal/fnerrors/stacktrace"
ghenv "namespacelabs.dev/foundation/internal/github/env"
"namespacelabs.dev/foundation/schema/tasks"
"namespacelabs.dev/foundation/std/tasks/protocol"
)
type ErrorKind string
const (
Kind_USER ErrorKind = "ns.error.user"
Kind_INTERNAL ErrorKind = "ns.error.internal"
Kind_EXTERNAL ErrorKind = "ns.error.external"
Kind_INVOCATION ErrorKind = "ns.error.invocation"
Kind_BADINPUT ErrorKind = "ns.error.badinput"
Kind_BADDATA ErrorKind = "ns.error.baddata"
Kind_TRANSIENT ErrorKind = "ns.error.transient"
)
// New returns a new error for a format specifier and optionals args with the
// stack trace at the point of invocation. These errors are expected to user
// errors, i.e. they are expected errors, due to wrong configuration, etc.
func New(format string, args ...interface{}) error {
return &BaseError{Kind: Kind_USER, OriginalErr: fmt.Errorf(format, args...), stack: stacktrace.New()}
}
// NewWithLocation returns a new error for a format specifier and optionals args
// with the stack trace at the point of invocation. These errors are expected to
// user errors, i.e. they are expected errors, due to wrong configuration, etc.
func NewWithLocation(loc Location, format string, args ...interface{}) error {
return &BaseError{Kind: Kind_USER, OriginalErr: fmt.Errorf(format, args...), stack: stacktrace.New(), Location: loc}
}
func AttachLocation(loc Location, err error) error {
if userErr, ok := err.(*BaseError); ok {
if userErr.Location == nil {
return &BaseError{Kind: userErr.Kind, OriginalErr: userErr.OriginalErr, stack: userErr.stack, Location: loc}
} else if userErr.Location == loc {
return userErr
}
}
return &BaseError{OriginalErr: err, stack: stacktrace.New(), Location: loc}
}
func WithLogs(err error, readerF func() io.Reader) error {
return &ErrWithLogs{err, readerF}
}
func makeError(kind ErrorKind, format string, args ...interface{}) *BaseError {
return &BaseError{Kind: kind, OriginalErr: fmt.Errorf(format, args...), stack: stacktrace.NewWithSkip(2)}
}
func ReauthError(toFixThis string, args ...interface{}) error {
err := makeError(Kind_USER, toFixThis, args...)
return &ReauthErr{BaseError: *err, Why: err.Error()}
}
func PermissionDeniedError(toFixThis string, args ...interface{}) error {
err := makeError(Kind_USER, toFixThis, args...)
return &PermissionDeniedErr{BaseError: *err, Why: err.Error()}
}
// Configuration or system setup is not correct and requires user intervention.
func UsageError(runThis, toFixThis string, args ...interface{}) error {
err := makeError(Kind_USER, toFixThis, args...)
return &UsageErr{BaseError: *err, Why: err.Error(), What: runThis}
}
// Unexpected error.
func InternalError(format string, args ...interface{}) error {
return makeError(Kind_INTERNAL, format, args...)
}
// Unexpected error produced by a component external to Namespace.
func ExternalError(format string, args ...interface{}) error {
return makeError(Kind_EXTERNAL, format, args...)
}
// A user-provided input does match our expectations (e.g. missing bits, wrong version, etc).
func BadInputError(format string, args ...interface{}) error {
return makeError(Kind_BADINPUT, format, args...)
}
// The data does match our expectations (e.g. missing bits, wrong version, etc).
func BadDataError(format string, args ...interface{}) error {
return makeError(Kind_BADDATA, format, args...)
}
// We failed but it may be due a transient issue.
func TransientError(format string, args ...interface{}) error {
return makeError(Kind_TRANSIENT, format, args...)
}
// A call to a remote endpoint failed, perhaps due to a transient issue.
func InvocationError(what, format string, args ...interface{}) error {
err := makeError(Kind_INVOCATION, format, args...)
return &InvocationErr{BaseError: *err, what: what}
}
// This error means that Namespace does not meet the minimum version requirements.
func NamespaceTooOld(what string, expected, got int32) error {
if expected == 0 && got == 0 {
return New("`%s` needs to be updated to use %q", name.CmdName, what)
}
return New("`%s` needs to be updated to use %q, (need api version %d, got %d)", name.CmdName, what, expected, got)
}
func NamespaceTooRecent(what string, expected, got int32) error {
return UsageError(
fmt.Sprintf("Please run `%s mod get namespacelabs.dev/foundation` to update your Namespace dependency version.", name.CmdName),
"Your namespacelabs.dev/foundation dependency is too old to use %q with this version of `ns` (running %d, the dependency is version %d)", what, expected, got)
}
// This error is purely for wiring and ensures that Namespace exits with an appropriate exit code.
// The error content has to be output independently.
func ExitWithCode(err error, code int) error {
return &exitError{OriginalErr: err, code: code}
}
// Wraps an error with a stack trace at the point of invocation.
type BaseError struct {
Kind ErrorKind
OriginalErr error
Location Location
stack stacktrace.StackTrace
}
func (e *BaseError) Error() string {
var locStr string
if e.Location != nil {
locStr = e.Location.ErrorLocation() + ": "
}
return fmt.Sprintf("%s%v", locStr, e.OriginalErr)
}
func (e *BaseError) IsExpectedError() (error, bool) {
return e, e.Kind == Kind_USER
}
func (e *BaseError) Unwrap() error { return e.OriginalErr }
// Signature is compatible with pkg/errors and allows frameworks like Sentry to
// automatically extract the frame.
func (e *BaseError) StackTrace() stacktrace.StackTrace {
return e.stack
}
type UsageErr struct {
BaseError
Why string
What string
}
type InvocationErr struct {
BaseError
what string
}
type ReauthErr struct {
BaseError
Why string
}
// Similar to ReauthErr, but the error might be persistent.
type PermissionDeniedErr struct {
BaseError
Why string
}
type ErrWithLogs struct {
Err error
ReaderF func() io.Reader // Returns reader with command's stderr output.
}
func IsExpected(err error) (error, bool) {
if err == nil {
return nil, false
}
if x, ok := err.(interface {
IsExpectedError() (error, bool)
}); ok {
return x.IsExpectedError()
}
if unwrappedError := errors.Unwrap(err); unwrappedError != nil {
return IsExpected(unwrappedError)
} else {
return err, false
}
}
func (e *UsageErr) Error() string {
return fmt.Sprintf("%s\n\n %s", e.Why, e.What)
}
func (e *InvocationErr) Error() string {
return fmt.Sprintf("failed when calling %s: %s", e.what, e.OriginalErr.Error())
}
func (e *ReauthErr) Error() string {
var cmd string
switch {
case ghenv.IsRunningInActions():
cmd = "auth exchange-github-token"
default:
cmd = "login"
}
return fmt.Sprintf("%s\n\n please run `%s %s`", e.Why, name.CmdName, cmd)
}
func (e *ErrWithLogs) Error() string {
return e.Err.Error()
}
type ExitError interface {
ExitCode() int
}
type exitError struct {
OriginalErr error
code int
}
func (e *exitError) Error() string {
return e.OriginalErr.Error()
}
func (e *exitError) ExitCode() int {
return e.code
}
type StackTracer interface {
StackTrace() stacktrace.StackTrace
}
// Represents an action error alongside the sequence of actions invocations leading to it.
type ActionError struct {
ActionID string
OriginalErr error
TraceProto []*protocol.Task
}
func (ae *ActionError) Error() string { return ae.OriginalErr.Error() }
func (ae *ActionError) Unwrap() error { return ae.OriginalErr }
func (ae *ActionError) Trace() []*protocol.Task { return ae.TraceProto }
func (ae *ActionError) GRPCStatus() *status.Status {
st, _ := status.FromError(ae.OriginalErr)
p, _ := st.WithDetails(&tasks.ErrorDetail_ActionID{ActionId: ae.ActionID})
return p
}
func IsNamespaceError(err error) bool {
switch err.(type) {
case *BaseError, *InvocationErr, *DependencyFailedError,
*ActionError, *ReauthErr, *PermissionDeniedErr:
return true
}
return false
}