* Move apifmt * Move safepath * Move Loki package * Regenerate Loki mock * Missing file for Loki
110 lines
2.6 KiB
Go
110 lines
2.6 KiB
Go
// apifmt aims to provide a Kubernetes-compatible way to format text.
|
|
package apifmt
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"net/http"
|
|
|
|
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
)
|
|
|
|
var (
|
|
_ error = (*fmtError)(nil)
|
|
_ apierrors.APIStatus = (*fmtError)(nil)
|
|
)
|
|
|
|
type fmtError struct {
|
|
inner error
|
|
str string
|
|
|
|
innerStatusErr apierrors.APIStatus
|
|
initInnerStatusErr bool
|
|
}
|
|
|
|
func (e *fmtError) Error() string {
|
|
return e.str
|
|
}
|
|
|
|
// Status returns the status that is closest in the tree, in a depth-first search.
|
|
func (e *fmtError) Status() metav1.Status {
|
|
if !e.initInnerStatusErr {
|
|
if status, ok := e.inner.(apierrors.APIStatus); ok || errors.As(e.inner, &status) {
|
|
e.innerStatusErr = status
|
|
}
|
|
e.initInnerStatusErr = true
|
|
}
|
|
|
|
status := metav1.Status{
|
|
Message: e.str,
|
|
Code: http.StatusInternalServerError,
|
|
Reason: metav1.StatusReasonInternalError,
|
|
Status: metav1.StatusFailure,
|
|
}
|
|
|
|
if e.innerStatusErr != nil {
|
|
s := e.innerStatusErr.Status()
|
|
status.Code, status.Reason, status.Status, status.Details = s.Code, s.Reason, s.Status, s.Details
|
|
}
|
|
return status
|
|
}
|
|
|
|
func (e *fmtError) Unwrap() error {
|
|
return e.inner
|
|
}
|
|
|
|
func (e *fmtError) Is(target error) bool {
|
|
if e.initInnerStatusErr && e.innerStatusErr != nil {
|
|
// If we already know the inner status, we can speed up the Is check for apierrors Is functions. These are the most common case.
|
|
if err, ok := e.innerStatusErr.(error); ok {
|
|
return errors.Is(err, target)
|
|
}
|
|
}
|
|
return errors.Is(e.inner, target)
|
|
}
|
|
|
|
// Errorf acts like `fmt.Errorf`. Use `%w` to wrap a specific error.
|
|
// The returned error will propagate the inner `metav1.Status`, if one exists. Otherwise, an HTTP 500 Internal Server Error will be returned.
|
|
// If multiple errors are passed, they will be joined with `errors.Join`, just like `fmt.Errorf`.
|
|
func Errorf(format string, args ...any) *fmtError {
|
|
// We go via Errorf to only give the %w errors as inner errors.
|
|
wrapped := fmt.Errorf(format, args...)
|
|
str := wrapped.Error()
|
|
err := unwrap(wrapped)
|
|
|
|
return &fmtError{
|
|
inner: err,
|
|
str: str,
|
|
}
|
|
}
|
|
|
|
// unwrap returns the inner error of an error, if it exists.
|
|
// If multiple errors are present, it will errors.Join them.
|
|
func unwrap(err error) error {
|
|
type singleUnwrapper interface {
|
|
Unwrap() error
|
|
}
|
|
type multiUnwrapper interface {
|
|
Unwrap() []error
|
|
}
|
|
|
|
if err == nil {
|
|
return nil
|
|
}
|
|
if e, ok := err.(singleUnwrapper); ok {
|
|
return e.Unwrap()
|
|
}
|
|
if e, ok := err.(multiUnwrapper); ok {
|
|
errs := e.Unwrap()
|
|
if len(errs) == 0 {
|
|
return err
|
|
}
|
|
if len(errs) == 1 && errs[0] != nil {
|
|
return errs[0]
|
|
}
|
|
return errors.Join(errs...)
|
|
}
|
|
return err
|
|
}
|