-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy patherrors.go
More file actions
146 lines (124 loc) · 5.46 KB
/
errors.go
File metadata and controls
146 lines (124 loc) · 5.46 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
package acp
import (
"net/http"
"time"
)
// ErrorType mirrors the ACP error.type field.
type ErrorType string
const (
InvalidRequest ErrorType = "invalid_request" // Missing or malformed field.
// Deprecated: use invalid_request + code idempotency_conflict/idempotency_key_required/idempotency_in_flight.
RequestNotIdempotentType ErrorType = "request_not_idempotent" // Idempotency violation.
ProcessingError ErrorType = "processing_error" // Downstream gateway or network failure.
RateLimitExceeded ErrorType = "rate_limit_exceeded" // Too many requests.
ServiceUnavailable ErrorType = "service_unavailable" // Temporary outage or maintenance.
)
// ErrorCode is a machine-readable identifier for the specific failure.
type ErrorCode string
const (
DuplicateRequest ErrorCode = "duplicate_request" // Safe duplicate with the same idempotency key.
IdempotencyConflict ErrorCode = "idempotency_conflict" // Same idempotency key but different parameters.
IdempotencyKeyRequired ErrorCode = "idempotency_key_required" // Idempotency-Key header is missing.
IdempotencyInFlight ErrorCode = "idempotency_in_flight" // Request with same idempotency key is still processing.
InvalidCard ErrorCode = "invalid_card" // Credential failed basic validation (such as length or expiry).
InvalidSignature ErrorCode = "invalid_signature" // Signature is missing or does not match the payload.
SignatureRequired ErrorCode = "signature_required" // Signed requests are required but headers were missing.
StaleTimestamp ErrorCode = "stale_timestamp" // Timestamp skew exceeded the allowed window.
MissingAuthorization ErrorCode = "missing_authorization" // Authorization header missing.
InvalidAuthorization ErrorCode = "invalid_authorization" // Authorization header malformed or API key invalid.
RequestNotIdempotent ErrorCode = "request_not_idempotent"
)
// Error represents a structured ACP error payload.
type Error struct {
Type ErrorType `json:"type"`
Code ErrorCode `json:"code"`
Message string `json:"message"`
Param *string `json:"param,omitempty"`
SupportedVersions []string `json:"supported_versions,omitempty"`
Internal error `json:"-"`
status int `json:"-"`
retryAfter time.Duration `json:"-"`
}
// Error makes *Error satisfy the stdlib error interface.
func (e *Error) Error() string {
if e == nil {
return ""
}
return e.Message
}
// RetryAfter returns the duration clients should wait before retrying.
func (e *Error) RetryAfter() time.Duration {
if e == nil {
return 0
}
return e.retryAfter
}
type errorOption func(*Error)
// WithOffendingParam sets the JSON path for the field that triggered the error.
func WithOffendingParam(jsonPath string) errorOption {
return func(er *Error) {
er.Param = &jsonPath
}
}
// WithStatusCode overrides the HTTP status code returned to the client.
func WithStatusCode(status int) errorOption {
return func(er *Error) {
er.status = status
}
}
// WithRetryAfter specifies how long clients should wait before retrying.
func WithRetryAfter(d time.Duration) errorOption {
return func(er *Error) {
er.retryAfter = d
}
}
// WithInternal sets an internal error that can be retrieved from [Error] for telemetry purposes.
func WithInternal(err error) errorOption {
return func(er *Error) {
er.Internal = err
}
}
// WithSupportedVersions sets the supported protocol versions returned to clients.
func WithSupportedVersions(versions []string) errorOption {
return func(er *Error) {
if len(versions) == 0 {
return
}
er.SupportedVersions = append([]string(nil), versions...)
}
}
// NewRateLimitExceededError builds a Too Many Requests ACP error payload.
func NewRateLimitExceededError(message string, opts ...errorOption) *Error {
return newError(RateLimitExceeded, ErrorCode(RateLimitExceeded), message, append([]errorOption{WithStatusCode(http.StatusTooManyRequests)}, opts...)...)
}
// NewServiceUnavailableError builds a Service Unavailable ACP error payload.
func NewServiceUnavailableError(message string, opts ...errorOption) *Error {
return newError(ServiceUnavailable, ErrorCode(ServiceUnavailable), message, append([]errorOption{WithStatusCode(http.StatusServiceUnavailable)}, opts...)...)
}
// NewInvalidRequestError builds a Bad Request ACP error payload.
func NewInvalidRequestError(message string, opts ...errorOption) *Error {
return newError(InvalidRequest, ErrorCode(InvalidRequest), message, append([]errorOption{WithStatusCode(http.StatusBadRequest)}, opts...)...)
}
// NewProcessingError builds an Internal Server Error ACP error payload.
func NewProcessingError(message string, opts ...errorOption) *Error {
return newError(ProcessingError, ErrorCode(ProcessingError), message, append([]errorOption{WithStatusCode(http.StatusInternalServerError)}, opts...)...)
}
// NewHTTPError allows callers to control the status code explicitly.
func NewHTTPError(status int, typ ErrorType, code ErrorCode, message string, opts ...errorOption) *Error {
return newError(typ, code, message, append(opts, WithStatusCode(status))...)
}
// newError builds a typed error payload matching the ACP schema.
func newError(typ ErrorType, code ErrorCode, message string, opts ...errorOption) *Error {
errPayload := &Error{
Type: typ,
Code: code,
Message: message,
}
for _, opt := range opts {
if opt == nil {
continue
}
opt(errPayload)
}
return errPayload
}