Skip to content
skyl

NewError

Builds a classified provider error.

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 by Provider.Name().
  • status — the HTTP status, or 0 for 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 message or body. An Error is documented never to contain one, and adapters are expected to honour that.
  • body is truncated automatically; you do not need to trim it yourself.
  • Use ClassifyStatus for kind unless the provider's own error type is more precise.
  • For a transport failure, chain WithCause so errors.Is reaches the underlying error.

Usage#

In an adapter

goCompiles
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

goCompiles
// 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, e
e := skyl.NewError(p.Name(), res.StatusCode, skyl.ErrRateLimit, msg, raw)
e.RetryAfter = skyl.ParseRetryAfter(res.Header.Get("Retry-After"))
return nil, e

Troubleshooting#

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.

Edit this page on GitHub