Provisioning: Move apifmt, loki and safepath to provisioning app (#110226)
* Move apifmt * Move safepath * Move Loki package * Regenerate Loki mock * Missing file for Loki
This commit is contained in:
@@ -0,0 +1,109 @@
|
||||
// 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
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
package apifmt_test
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/grafana/grafana/apps/provisioning/pkg/apifmt"
|
||||
"github.com/stretchr/testify/assert"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
func TestErrorf(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
for _, fmt := range []string{"1 %v 2 %v", "1 %w 2 %v", "1 %v 2 %w", "1 %w 2 %w"} {
|
||||
t.Run("error string is formatted appropriately with fmt="+fmt, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
err1 := errors.New("error1")
|
||||
err2 := errors.New("error2")
|
||||
|
||||
err := apifmt.Errorf(fmt, err1, err2)
|
||||
assert.Equal(t, "1 error1 2 error2", err.Error())
|
||||
})
|
||||
}
|
||||
|
||||
t.Run("no inner error defaults to internal server error", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
err := apifmt.Errorf("nothing inside")
|
||||
|
||||
assert.True(t, apierrors.IsInternalError(err), "err is not internal error per apierrors")
|
||||
assert.Equal(t, int32(http.StatusInternalServerError), err.Status().Code, ".Code")
|
||||
assert.Equal(t, metav1.StatusReasonInternalError, err.Status().Reason, ".Reason")
|
||||
assert.Equal(t, metav1.StatusFailure, err.Status().Status, ".Status")
|
||||
})
|
||||
|
||||
t.Run("non-apistatus inner error defaults to internal server error", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
inner := errors.New("an inner error")
|
||||
err := apifmt.Errorf("%w", inner)
|
||||
|
||||
assert.True(t, apierrors.IsInternalError(err), "err is not internal error per apierrors")
|
||||
assert.Equal(t, int32(http.StatusInternalServerError), err.Status().Code, ".Code")
|
||||
assert.Equal(t, metav1.StatusReasonInternalError, err.Status().Reason, ".Reason")
|
||||
assert.Equal(t, metav1.StatusFailure, err.Status().Status, ".Status")
|
||||
})
|
||||
|
||||
t.Run("apistatus inner error is used for status", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
inner := apierrors.NewBadRequest("bad request")
|
||||
err := apifmt.Errorf("%w", inner)
|
||||
|
||||
assert.Equal(t, inner.Status(), err.Status(), "err.Status()")
|
||||
})
|
||||
|
||||
t.Run("message is used with inner apistatus error", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
inner := apierrors.NewBadRequest("bad request")
|
||||
err := apifmt.Errorf("context here: %w", inner)
|
||||
|
||||
status := inner.Status()
|
||||
status.Message = "context here: bad request"
|
||||
assert.Equal(t, status, err.Status(), "err.Status()")
|
||||
assert.Equal(t, "context here: bad request", err.Error(), "err.Error()")
|
||||
})
|
||||
|
||||
t.Run("deep apierror is used", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
inner := apierrors.NewBadRequest("bad request")
|
||||
wrapped := fmt.Errorf("%w", inner)
|
||||
wrapped = fmt.Errorf("%w", wrapped)
|
||||
err := apifmt.Errorf("%w", wrapped)
|
||||
|
||||
assert.Equal(t, inner.Status(), err.Status(), "err.Status()")
|
||||
})
|
||||
|
||||
t.Run("deep error in multi-unwrap wrapper's apierror is used", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
inner := apierrors.NewBadRequest("bad request")
|
||||
anotherError := errors.New("not an apierror")
|
||||
wrapped := errors.Join(fmt.Errorf("this is cool: %w", anotherError), fmt.Errorf("another one: %w", errors.Join(anotherError, inner, anotherError)))
|
||||
err := apifmt.Errorf("%w", wrapped)
|
||||
|
||||
status := inner.Status()
|
||||
status.Message = "this is cool: not an apierror\nanother one: not an apierror\nbad request\nnot an apierror"
|
||||
assert.Equal(t, status, err.Status(), "err.Status()")
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user