Adapters use NewError so that every provider failure reaches callers in the
same shape. It is exported so an adapter in your repository produces errors
indistinguishable from an in-tree one.
Reference#
func NewError(provider string, status int, kind error, message string, body []byte) *Error
Parameters
provider— the adapter's name, as returned byProvider.Name().status— the HTTP status, or0for a transport-level failure.kind— the sentinel this classifies as. May be nil when unclassifiable.message— the provider's explanation, when it gave one.body— the raw error payload. Truncated at 2048 bytes, with"… (truncated)"appended.
Caveats
- Never pass a credential in
messageorbody. AnErroris documented never to contain one, and adapters are expected to honour that. bodyis truncated automatically; you do not need to trim it yourself.- Use
ClassifyStatusforkindunless the provider's own error type is more precise. - For a transport failure, chain
WithCausesoerrors.Isreaches the underlying error.
Usage#
In an adapter
if res.StatusCode >= 400 {
return nil, skyl.NewError(p.Name(), res.StatusCode,
skyl.ClassifyStatus(res.StatusCode), extractMessage(raw), raw)
}if res.StatusCode >= 400 {
return nil, skyl.NewError(p.Name(), res.StatusCode,
skyl.ClassifyStatus(res.StatusCode), extractMessage(raw), raw)
}Preferring the provider's own classification
// The provider distinguishes an overloaded model from a generic 500; skyl's
// status map cannot. Prefer the more precise signal where it exists.
kind := skyl.ClassifyStatus(res.StatusCode)
if apiErr.Type == "overloaded_error" {
kind = skyl.ErrServer
}
return nil, skyl.NewError(p.Name(), res.StatusCode, kind, apiErr.Message, raw)// The provider distinguishes an overloaded model from a generic 500; skyl's
// status map cannot. Prefer the more precise signal where it exists.
kind := skyl.ClassifyStatus(res.StatusCode)
if apiErr.Type == "overloaded_error" {
kind = skyl.ErrServer
}
return nil, skyl.NewError(p.Name(), res.StatusCode, kind, apiErr.Message, raw)A transport failure with its cause preserved
res, err := p.hc.Do(httpReq)
if err != nil {
// Status 0 and no kind: unclassified, and Retryable() will return true.
// WithCause is what makes errors.Is(err, context.DeadlineExceeded) work.
return nil, (&skyl.Error{Provider: p.Name()}).WithCause(err)
}res, err := p.hc.Do(httpReq)
if err != nil {
// Status 0 and no kind: unclassified, and Retryable() will return true.
// WithCause is what makes errors.Is(err, context.DeadlineExceeded) work.
return nil, (&skyl.Error{Provider: p.Name()}).WithCause(err)
}Attaching a Retry-After hint
e := skyl.NewError(p.Name(), res.StatusCode, skyl.ErrRateLimit, msg, raw)
e.RetryAfter = skyl.ParseRetryAfter(res.Header.Get("Retry-After"))
return nil, ee := skyl.NewError(p.Name(), res.StatusCode, skyl.ErrRateLimit, msg, raw)
e.RetryAfter = skyl.ParseRetryAfter(res.Header.Get("Retry-After"))
return nil, eTroubleshooting#
My adapter's errors do not match errors.Is
You returned a bare fmt.Errorf rather than a *skyl.Error with a Kind.
Without a sentinel there is nothing for errors.Is to match, and Client will
treat it as unclassified.
Client did not retry my adapter's 503
Check that kind is ErrServer. Retryable() branches on the sentinel, not on
the status code.