-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy patherrors.go
More file actions
270 lines (222 loc) · 9.4 KB
/
errors.go
File metadata and controls
270 lines (222 loc) · 9.4 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
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
package cli
import (
"errors"
"fmt"
)
// --- Hierarchical Sentinel Errors ---
//
// The framework uses hierarchical sentinel errors for precise error handling.
// Each specific error wraps a parent category error via Unwrap(), enabling
// checks at any granularity level using [errors.Is]:
//
// // Handle specific errors differently
// switch {
// case errors.Is(err, cli.ErrRequiredArg):
// fmt.Println("Missing required argument")
// case errors.Is(err, cli.ErrInvalidArgValue):
// fmt.Println("Invalid argument value")
// case errors.Is(err, cli.ErrArgument):
// fmt.Println("Some argument error") // catches all argument errors
// }
//
// Error hierarchy:
//
// ErrFlag ─────────┬── ErrUnknownFlag
// ├── ErrFlagRequiresVal
// ├── ErrRequiredFlag
// └── ErrInvalidFlagValue
//
// ErrArgument ─────┬── ErrRequiredArg
// ├── ErrInvalidArgValue
// ├── ErrArgCount
// └── ErrArgOrder
//
// ErrCommand ──────┬── ErrUnknownCommand
// └── ErrAmbiguousCommand
//
// ErrFlagGroup ────┬── ErrMutuallyExclusive
// ├── ErrRequiredTogether
// └── ErrOneRequired
//
// The framework wraps these sentinels with context when returning errors:
//
// fmt.Errorf("%w: --port: %w", cli.ErrInvalidFlagValue, strconv.ErrSyntax)
//
// This preserves the error chain for both [errors.Is] and [errors.Unwrap].
// sentinelError is an error that wraps a parent sentinel, enabling hierarchical
// error checking via [errors.Is]. For example, ErrRequiredArg wraps ErrArgument,
// so errors.Is(err, ErrRequiredArg) and errors.Is(err, ErrArgument) both return true.
type sentinelError struct {
msg string
parent error
}
func (e *sentinelError) Error() string { return e.msg }
func (e *sentinelError) Unwrap() error { return e.parent }
func newSentinel(msg string, parent error) error {
return &sentinelError{msg: msg, parent: parent}
}
// --- Flag errors ---
//
// Check specific errors or the parent:
//
// errors.Is(err, cli.ErrUnknownFlag) // specific
// errors.Is(err, cli.ErrFlag) // any flag error
var (
// ErrFlag is the parent error for all flag-related errors.
ErrFlag = errors.New("flag error")
// ErrUnknownFlag indicates an unrecognized flag was provided.
ErrUnknownFlag = newSentinel("unknown flag", ErrFlag)
// ErrFlagRequiresVal indicates a flag that requires a value was not given one.
ErrFlagRequiresVal = newSentinel("flag requires a value", ErrFlag)
// ErrRequiredFlag indicates a required flag was not provided.
ErrRequiredFlag = newSentinel("required flag not provided", ErrFlag)
// ErrInvalidFlagValue indicates a flag value could not be parsed or is invalid.
ErrInvalidFlagValue = newSentinel("invalid flag value", ErrFlag)
// ErrDuplicateFlag indicates multiple fields have the same flag name.
ErrDuplicateFlag = newSentinel("duplicate flag name", ErrFlag)
)
// --- Argument errors ---
//
// Check specific errors or the parent:
//
// errors.Is(err, cli.ErrRequiredArg) // specific
// errors.Is(err, cli.ErrArgument) // any argument error
var (
// ErrArgument is the parent error for all positional argument errors.
ErrArgument = errors.New("argument error")
// ErrRequiredArg indicates a required positional argument was not provided.
ErrRequiredArg = newSentinel("required argument not provided", ErrArgument)
// ErrInvalidArgValue indicates a positional argument value could not be parsed.
ErrInvalidArgValue = newSentinel("invalid argument value", ErrArgument)
// ErrArgCount indicates the wrong number of positional arguments was provided.
ErrArgCount = newSentinel("wrong number of arguments", ErrArgument)
// ErrArgOrder indicates positional arguments are defined in an invalid order.
// Variadic (slice) arguments must come last since they consume all remaining args.
ErrArgOrder = newSentinel("invalid argument order", ErrArgument)
)
// --- Command errors ---
//
// Check specific errors or the parent:
//
// errors.Is(err, cli.ErrUnknownCommand) // specific
// errors.Is(err, cli.ErrCommand) // any command error
var (
// ErrCommand is the parent error for command resolution errors.
ErrCommand = errors.New("command error")
// ErrUnknownCommand indicates an unrecognized subcommand was provided.
ErrUnknownCommand = newSentinel("unknown command", ErrCommand)
// ErrAmbiguousCommand indicates a prefix matched multiple subcommands.
ErrAmbiguousCommand = newSentinel("ambiguous command", ErrCommand)
// ErrMissingSubcommand indicates a command that requires a subcommand was invoked directly.
ErrMissingSubcommand = newSentinel("missing subcommand", ErrCommand)
)
// --- Flag group errors ---
//
// Check specific errors or the parent:
//
// errors.Is(err, cli.ErrMutuallyExclusive) // specific
// errors.Is(err, cli.ErrFlagGroup) // any flag group error
var (
// ErrFlagGroup is the parent error for flag group constraint violations.
ErrFlagGroup = errors.New("flag group error")
// ErrMutuallyExclusive indicates multiple flags in a mutually exclusive group were set.
ErrMutuallyExclusive = newSentinel("mutually exclusive flags", ErrFlagGroup)
// ErrRequiredTogether indicates not all flags in a required-together group were set.
ErrRequiredTogether = newSentinel("flags must be set together", ErrFlagGroup)
// ErrOneRequired indicates none of the flags in a one-required group were set.
ErrOneRequired = newSentinel("exactly one flag required", ErrFlagGroup)
)
// --- Other errors ---
var (
// ErrUnsupportedType indicates a struct field has a type that cannot be used as a flag.
ErrUnsupportedType = errors.New("unsupported flag type")
// ErrInvalidTag indicates a struct tag is malformed or has conflicting options.
ErrInvalidTag = errors.New("invalid struct tag")
)
// --- Help/Usage signals ---
//
// These signals control help and usage display with appropriate exit codes.
// Return these from [Commander.Run] or lifecycle hooks to trigger display.
//
// Design note: ShowHelp and ShowUsage implement [error] despite being success
// signals (exit code 0). This is intentional — they're control flow signals
// returned through the error channel because [Commander.Run] returns error.
// The naming convention distinguishes them: ShowHelp (success) vs ErrShowHelp
// (failure). This pattern is common in CLI frameworks where the error return
// serves as both an error channel and a signal channel.
var (
// ShowHelp triggers full help display and exits with code 0.
// Use when the user explicitly requests help (e.g., "myapp help subcmd").
//
// Note: Implements error for control flow, not because it's an error.
// The process exits successfully (code 0).
ShowHelp = &helpSignal{code: 0, full: true} //nolint:errname // success signal, not error
// ErrShowHelp triggers full help display and exits with code 1.
// Use when showing help due to an error condition (e.g., missing required subcommand).
ErrShowHelp = &helpSignal{code: 1, full: true}
// ShowUsage triggers brief usage display and exits with code 0.
// Use when the user explicitly requests usage information.
//
// Note: Implements error for control flow, not because it's an error.
// The process exits successfully (code 0).
ShowUsage = &helpSignal{code: 0, full: false} //nolint:errname // success signal, not error
// ErrShowUsage triggers brief usage display and exits with code 1.
// Use when showing usage due to an error condition.
ErrShowUsage = &helpSignal{code: 1, full: false}
)
// helpSignal is a special error type that signals the framework to display
// help or usage information and exit with a specific code.
type helpSignal struct { //nolint:errname // signal type, not error
code int // exit code: 0 for success, 1 for error
full bool // true for full help, false for brief usage
}
func (h *helpSignal) Error() string {
if h.full {
return "show help"
}
return "show usage"
}
func (h *helpSignal) ExitCode() int { return h.code }
// isHelpSignal returns true if err is a help/usage signal that should be
// intercepted by the framework for special handling.
func isHelpSignal(err error) bool {
var hs *helpSignal
return errors.As(err, &hs)
}
// getHelpSignal returns the helpSignal if err is one, or nil otherwise.
func getHelpSignal(err error) *helpSignal {
var hs *helpSignal
if errors.As(err, &hs) {
return hs
}
return nil
}
// ExitCoder is implemented by errors that carry a process exit code.
type ExitCoder interface {
ExitCode() int
}
type exitError struct {
message string
code int
}
func (e *exitError) Error() string { return e.message }
func (e *exitError) ExitCode() int { return e.code }
// Exit returns an error that implements [ExitCoder] with the given message
// and exit code.
func Exit(message string, code int) error {
return &exitError{message: message, code: code}
}
// Exitf returns an error that implements [ExitCoder] with a formatted message
// and exit code.
func Exitf(code int, format string, args ...any) error {
return &exitError{message: fmt.Sprintf(format, args...), code: code}
}
// isUsageError returns true if err is a usage error that warrants showing
// a help hint. This includes flag errors, argument errors, command errors,
// and flag group errors.
func isUsageError(err error) bool {
return errors.Is(err, ErrFlag) ||
errors.Is(err, ErrArgument) ||
errors.Is(err, ErrCommand) ||
errors.Is(err, ErrFlagGroup)
}