-
Notifications
You must be signed in to change notification settings - Fork 60
Expand file tree
/
Copy patherrors.go
More file actions
125 lines (103 loc) · 2.76 KB
/
errors.go
File metadata and controls
125 lines (103 loc) · 2.76 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
package telemetry
import (
"errors"
"strings"
)
// isTerminalError returns true if error is terminal (non-retryable).
// Terminal errors indicate user errors or permanent failures that won't
// be resolved by retrying the operation.
func isTerminalError(err error) bool {
if err == nil {
return false
}
// Priority 1: Check HTTP status code if available (most reliable)
if httpErr, ok := extractHTTPError(err); ok {
return isTerminalHTTPStatus(httpErr.statusCode)
}
// Priority 2: Fall back to error message patterns
errMsg := strings.ToLower(err.Error())
terminalPatterns := []string{
"authentication failed",
"unauthorized",
"forbidden",
"not found",
"invalid request",
"syntax error",
"bad request",
"invalid parameter",
"permission denied",
}
for _, pattern := range terminalPatterns {
if strings.Contains(errMsg, pattern) {
return true
}
}
return false
}
// classifyError classifies an error for telemetry purposes.
// Returns a string representation of the error type.
func classifyError(err error) string {
if err == nil {
return ""
}
errMsg := strings.ToLower(err.Error())
// Ordered patterns — first match wins, ensuring deterministic classification.
patterns := []struct {
pattern string
errorType string
}{
{"timeout", "timeout"},
{"context cancel", "cancelled"},
{"connection", "connection_error"},
{"authentication", "auth_error"},
{"unauthorized", "auth_error"},
{"forbidden", "permission_error"},
{"not found", "not_found"},
{"syntax", "syntax_error"},
{"invalid", "invalid_request"},
}
for _, p := range patterns {
if strings.Contains(errMsg, p.pattern) {
return p.errorType
}
}
// Default to generic error
return "error"
}
// isRetryableError returns true if the error is retryable.
// This is the inverse of isTerminalError.
//
//nolint:deadcode,unused // Will be used in Phase 8+
func isRetryableError(err error) bool {
return !isTerminalError(err)
}
// httpError represents an HTTP error with status code.
type httpError struct {
statusCode int
message string
}
func (e *httpError) Error() string {
return e.message
}
// newHTTPError creates a new HTTP error.
//
//nolint:deadcode,unused // Will be used in Phase 8+
func newHTTPError(statusCode int, message string) error {
return &httpError{
statusCode: statusCode,
message: message,
}
}
// isTerminalHTTPStatus returns true for non-retryable HTTP status codes.
func isTerminalHTTPStatus(status int) bool {
// 4xx errors (except 429) are terminal
return status >= 400 && status < 500 && status != 429
}
// extractHTTPError extracts HTTP error information if available.
func extractHTTPError(err error) (*httpError, bool) {
var httpErr *httpError
if errors.As(err, &httpErr) {
return httpErr, true
}
return nil, false
}