package errutil
import (
"encoding/json"
"errors"
"fmt"
"github.com/containerd/containerd/v2/core/remotes/docker"
remoteserrors "github.com/containerd/containerd/v2/core/remotes/errors"
)
const (
maxPrintedBodySize = 256
)
func WithDetails(err error) error {
if err == nil {
return nil
}
var errStatus remoteserrors.ErrUnexpectedStatus
if errors.As(err, &errStatus) {
var dErr docker.Errors
if err1 := json.Unmarshal(errStatus.Body, &dErr); err1 == nil && len(dErr) > 0 {
return &formattedDockerError{dErr: dErr}
}
return verboseUnexpectedStatusError{ErrUnexpectedStatus: errStatus}
}
return err
}
type verboseUnexpectedStatusError struct {
remoteserrors.ErrUnexpectedStatus
}
func (e verboseUnexpectedStatusError) Unwrap() error {
return e.ErrUnexpectedStatus
}
func (e verboseUnexpectedStatusError) Error() string {
if len(e.Body) == 0 {
return e.ErrUnexpectedStatus.Error()
}
var details string
var errDetails struct {
Details string `json:"details"`
}
if err := json.Unmarshal(e.Body, &errDetails); err == nil && errDetails.Details != "" {
details = errDetails.Details
} else {
if len(e.Body) > maxPrintedBodySize {
details = string(e.Body[:maxPrintedBodySize]) + fmt.Sprintf("... (%d bytes truncated)", len(e.Body)-maxPrintedBodySize)
} else {
details = string(e.Body)
}
}
return fmt.Sprintf("%s: %s", e.ErrUnexpectedStatus.Error(), details)
}
type formattedDockerError struct {
dErr docker.Errors
}
func (e *formattedDockerError) Error() string {
format := func(err error) string {
out := err.Error()
var dErr docker.Error
if errors.As(err, &dErr) {
if v, ok := dErr.Detail.(string); ok && v != "" {
out += " - " + v
}
}
return out
}
switch len(e.dErr) {
case 0:
return "<nil>"
case 1:
return format(e.dErr[0])
default:
msg := "errors:\n"
for _, err := range e.dErr {
msg += format(err) + "\n"
}
return msg
}
}
func (e *formattedDockerError) Unwrap() error {
return e.dErr
}