*skyl.Error is what every adapter produces. It wraps a
sentinel so errors.Is works, and carries
the detail errors.As recovers.
Reference#
type Error struct
| Field | Type | Description |
|---|---|---|
| Provider | string | The adapter that produced the failure. |
| StatusCode | int | The HTTP status, or 0 for transport-level failures. Zero value: a transport failure — a dial timeout, a reset connection |
| Message | string | The provider's explanation, when it gave one. |
| Kind | error | The sentinel this failure classifies as. Unwrap returns it. |
| RetryAfter | time.Duration | How long the provider asked us to wait. Zero value: the provider did not say |
| Body | string | The raw error payload, truncated at 2048 bytes. Useful when a provider reports something skyl does not model. An Error never contains credentials. |
Methods#
func (e *Error) Error() string
func (e *Error) Unwrap() []error
func (e *Error) Retryable() bool
func (e *Error) Cause() error
func (e *Error) WithCause(err error) *Error
Caveats
- It never contains credentials.
Bodyis the provider's error payload truncated at 2048 bytes — enough to diagnose, small enough not to bloat logs, and never a header. StatusCode == 0means a transport-level failure — a dial timeout, a reset connection, a rejected certificate.Unwrapreturns a slice, so both the sentinel and the underlying cause match.Kindmay be nil for an unclassified failure; guard before using it.Error()trims the sentinel's own"skyl: "prefix to avoid repeating it.
Usage#
Recovering the detail
var e *skyl.Error
if errors.As(err, &e) {
log.Printf("%s returned %d: %s", e.Provider, e.StatusCode, e.Message)
if e.RetryAfter > 0 {
log.Printf("it asked for %s", e.RetryAfter)
}
}var e *skyl.Error
if errors.As(err, &e) {
log.Printf("%s returned %d: %s", e.Provider, e.StatusCode, e.Message)
if e.RetryAfter > 0 {
log.Printf("it asked for %s", e.RetryAfter)
}
}Passing a Retry-After through to your own callers
var e *skyl.Error
if errors.As(err, &e) && e.RetryAfter > 0 {
w.Header().Set("Retry-After", strconv.Itoa(int(e.RetryAfter.Seconds())))
}
http.Error(w, "upstream capacity exhausted", http.StatusTooManyRequests)var e *skyl.Error
if errors.As(err, &e) && e.RetryAfter > 0 {
w.Header().Set("Retry-After", strconv.Itoa(int(e.RetryAfter.Seconds())))
}
http.Error(w, "upstream capacity exhausted", http.StatusTooManyRequests)Diagnosing an unclassified failure
var e *skyl.Error
if errors.As(err, &e) && e.Kind == nil {
// Body is the provider's own words — the thing to paste into a ticket.
log.Printf("unclassified: provider=%s status=%d body=%s", e.Provider, e.StatusCode, e.Body)
}var e *skyl.Error
if errors.As(err, &e) && e.Kind == nil {
// Body is the provider's own words — the thing to paste into a ticket.
log.Printf("unclassified: provider=%s status=%d body=%s", e.Provider, e.StatusCode, e.Body)
}Troubleshooting#
Body is empty
The provider sent no error payload, which is common for transport-level
failures where StatusCode is also 0.
Body ends with “… (truncated)”
It exceeded 2048 bytes. That bound is deliberate — an unbounded error body in a log line is how a single bad request fills a disk.
Kind is nil
The failure could not be classified. Check StatusCode: 0 means transport, and
anything else means the provider answered with something skyl did not recognise.
Body will say what.