mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Errutil: Update documentation for Go 1.19 (#55807)
This commit is contained in:
parent
c2d3a31772
commit
22756913ba
@ -17,20 +17,20 @@
|
|||||||
// naturally to experienced and beginner Go developers alike and is
|
// naturally to experienced and beginner Go developers alike and is
|
||||||
// compatible with standard library features such as the ones in
|
// compatible with standard library features such as the ones in
|
||||||
// the errors package. To achieve this, Grafana's errors are divided
|
// the errors package. To achieve this, Grafana's errors are divided
|
||||||
// into the Base and Error types, where the Base contains static
|
// into the [Base] and [Error] types, where the Base contains static
|
||||||
// information about a category of errors that may occur within a
|
// information about a category of errors that may occur within a
|
||||||
// service and Error contains the combination of static and dynamic
|
// service and Error contains the combination of static and dynamic
|
||||||
// information for a particular instance of an error.
|
// information for a particular instance of an error.
|
||||||
//
|
//
|
||||||
// A Base would typically be provided as a package-level variable for a
|
// A Base would typically be provided as a package-level variable for a
|
||||||
// service using the NewBase constructor with a CoreStatus and a unique
|
// service using the [NewBase] constructor with a [CoreStatus] and a
|
||||||
// static message ID that identifies the general structure of the public
|
// unique static message ID that identifies the structure of the public
|
||||||
// message attached to the specific error.
|
// message attached to the specific error.
|
||||||
//
|
//
|
||||||
// var errNotFound = errutil.NewBase(errutil.StatusNotFound, "service.not-found")
|
// var errNotFound = errutil.NewBase(errutil.StatusNotFound, "service.notFound")
|
||||||
//
|
//
|
||||||
// This Base can now be used to construct a regular Go error with the
|
// This Base can now be used to construct a regular Go error with the
|
||||||
// Base.Errorf method using the same structure as fmt.Errorf:
|
// [Base.Errorf] method using the same structure as [fmt.Errorf]:
|
||||||
//
|
//
|
||||||
// return errNotFound.Errorf("looked for thing with ID %d, but it wasn't there: %w", id, err)
|
// return errNotFound.Errorf("looked for thing with ID %d, but it wasn't there: %w", id, err)
|
||||||
//
|
//
|
||||||
@ -39,11 +39,11 @@
|
|||||||
// possible to override the message sent to the end user by using
|
// possible to override the message sent to the end user by using
|
||||||
// the WithPublicMessage functional option when creating a new Base
|
// the WithPublicMessage functional option when creating a new Base
|
||||||
//
|
//
|
||||||
// var errNotFound = errutil.NewBase(errutil.StatusNotFound "service.not-found", WithPublicMessage("The thing is missing."))
|
// var errNotFound = errutil.NewBase(errutil.StatusNotFound "service.notFound", WithPublicMessage("The thing is missing."))
|
||||||
//
|
//
|
||||||
// If a dynamic message is needed, the Template type extends Base with a
|
// If a dynamic message is needed, the [Template] type extends Base with
|
||||||
// Go template using text/template from the standard library, refer to
|
// a Go template using [text/template], refer to the documentation
|
||||||
// the documentation related to the Template type for usage examples.
|
// related to the Template type for usage examples.
|
||||||
// It is also possible, but discouraged, to manually edit the fields of
|
// It is also possible, but discouraged, to manually edit the fields of
|
||||||
// an Error.
|
// an Error.
|
||||||
package errutil
|
package errutil
|
||||||
|
@ -6,7 +6,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// Base represents the static information about a specific error.
|
// Base represents the static information about a specific error.
|
||||||
// Always use NewBase to create new instances of Base.
|
// Always use [NewBase] to create new instances of Base.
|
||||||
type Base struct {
|
type Base struct {
|
||||||
// Because Base is typically instantiated as a package or global
|
// Because Base is typically instantiated as a package or global
|
||||||
// variable, having private members reduces the probability of a
|
// variable, having private members reduces the probability of a
|
||||||
@ -17,16 +17,16 @@ type Base struct {
|
|||||||
logLevel LogLevel
|
logLevel LogLevel
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewBase initializes a Base that is used to construct Error:s.
|
// NewBase initializes a [Base] that is used to construct [Error].
|
||||||
// The reason is used to determine the status code that should be
|
// The reason is used to determine the status code that should be
|
||||||
// returned for the error, and the msgID is passed to the caller
|
// returned for the error, and the msgID is passed to the caller
|
||||||
// to serve as the base for user facing error messages.
|
// to serve as the base for user facing error messages.
|
||||||
//
|
//
|
||||||
// msgID should be structured as component.error-brief, for example
|
// msgID should be structured as component.errorBrief, for example
|
||||||
//
|
//
|
||||||
// login.failed-authentication
|
// login.failedAuthentication
|
||||||
// dashboards.validation-error
|
// dashboards.validationError
|
||||||
// dashboards.uid-already-exists
|
// dashboards.uidAlreadyExists
|
||||||
func NewBase(reason StatusReason, msgID string, opts ...BaseOpt) Base {
|
func NewBase(reason StatusReason, msgID string, opts ...BaseOpt) Base {
|
||||||
b := Base{
|
b := Base{
|
||||||
reason: reason,
|
reason: reason,
|
||||||
@ -44,9 +44,9 @@ func NewBase(reason StatusReason, msgID string, opts ...BaseOpt) Base {
|
|||||||
type BaseOpt func(Base) Base
|
type BaseOpt func(Base) Base
|
||||||
|
|
||||||
// WithLogLevel sets a custom log level for all errors instantiated from
|
// WithLogLevel sets a custom log level for all errors instantiated from
|
||||||
// this Base.
|
// this [Base].
|
||||||
//
|
//
|
||||||
// Used as a functional option to NewBase.
|
// Used as a functional option to [NewBase].
|
||||||
func WithLogLevel(lvl LogLevel) BaseOpt {
|
func WithLogLevel(lvl LogLevel) BaseOpt {
|
||||||
return func(b Base) Base {
|
return func(b Base) Base {
|
||||||
b.logLevel = lvl
|
b.logLevel = lvl
|
||||||
@ -55,9 +55,9 @@ func WithLogLevel(lvl LogLevel) BaseOpt {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// WithPublicMessage sets the default public message that will be used
|
// WithPublicMessage sets the default public message that will be used
|
||||||
// for errors based on this Base.
|
// for errors based on this [Base].
|
||||||
//
|
//
|
||||||
// Used as a functional option to NewBase.
|
// Used as a functional option to [NewBase].
|
||||||
func WithPublicMessage(message string) BaseOpt {
|
func WithPublicMessage(message string) BaseOpt {
|
||||||
return func(b Base) Base {
|
return func(b Base) Base {
|
||||||
b.publicMessage = message
|
b.publicMessage = message
|
||||||
@ -65,9 +65,9 @@ func WithPublicMessage(message string) BaseOpt {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Errorf creates a new Error with the Reason and MessageID from
|
// Errorf creates a new [Error] with Reason and MessageID from [Base],
|
||||||
// Base, and Message and Underlying will be populated using
|
// and Message and Underlying will be populated using the rules of
|
||||||
// the rules of fmt.Errorf.
|
// [fmt.Errorf].
|
||||||
func (b Base) Errorf(format string, args ...interface{}) Error {
|
func (b Base) Errorf(format string, args ...interface{}) Error {
|
||||||
err := fmt.Errorf(format, args...)
|
err := fmt.Errorf(format, args...)
|
||||||
|
|
||||||
@ -95,8 +95,10 @@ func (b Base) Status() StatusReason {
|
|||||||
return b.reason.Status()
|
return b.reason.Status()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Is validates that an Error has the same reason and messageID as the
|
// Is validates that an [Error] has the same reason and messageID as the
|
||||||
// Base.
|
// Base.
|
||||||
|
//
|
||||||
|
// Implements the interface used by [errors.Is].
|
||||||
func (b Base) Is(err error) bool {
|
func (b Base) Is(err error) bool {
|
||||||
// The linter complains that it wants to use errors.As because it
|
// The linter complains that it wants to use errors.As because it
|
||||||
// handles unwrapping, we don't want to do that here since we want
|
// handles unwrapping, we don't want to do that here since we want
|
||||||
@ -122,24 +124,61 @@ func (b Base) Is(err error) bool {
|
|||||||
// boilerplate error handling for status codes and internationalization
|
// boilerplate error handling for status codes and internationalization
|
||||||
// support.
|
// support.
|
||||||
//
|
//
|
||||||
|
// Use [Base.Errorf] or [Template.Build] to construct errors:
|
||||||
|
//
|
||||||
|
// // package-level
|
||||||
|
// var errMonthlyQuota = NewBase(errutil.StatusTooManyRequests, "service.monthlyQuotaReached")
|
||||||
|
// // in function
|
||||||
|
// err := errMonthlyQuota.Errorf("user '%s' reached their monthly quota for service", userUID)
|
||||||
|
//
|
||||||
|
// or
|
||||||
|
//
|
||||||
|
// // package-level
|
||||||
|
// var errRateLimited = NewBase(errutil.StatusTooManyRequests, "service.backoff").MustTemplate(
|
||||||
|
// "quota reached for user {{ .Private.user }}, rate limited until {{ .Public.time }}",
|
||||||
|
// errutil.WithPublic("Too many requests, try again after {{ .Public.time }}"),
|
||||||
|
// )
|
||||||
|
// // in function
|
||||||
|
// err := errRateLimited.Build(TemplateData{
|
||||||
|
// Private: map[string]interface{ "user": userUID },
|
||||||
|
// Public: map[string]interface{ "time": rateLimitUntil },
|
||||||
|
// })
|
||||||
|
//
|
||||||
// Error implements Unwrap and Is to natively support Go 1.13 style
|
// Error implements Unwrap and Is to natively support Go 1.13 style
|
||||||
// errors as described in https://go.dev/blog/go1.13-errors .
|
// errors as described in https://go.dev/blog/go1.13-errors .
|
||||||
type Error struct {
|
type Error struct {
|
||||||
Reason StatusReason
|
// Reason provides the Grafana abstracted reason which can be turned
|
||||||
MessageID string
|
// into an upstream status code depending on the protocol. This
|
||||||
LogMessage string
|
// allows us to use the same errors across HTTP, gRPC, and other
|
||||||
Underlying error
|
// protocols.
|
||||||
|
Reason StatusReason
|
||||||
|
// A MessageID together with PublicPayload should suffice to
|
||||||
|
// create the PublicMessage. This lets a localization aware client
|
||||||
|
// construct messages based on structured data.
|
||||||
|
MessageID string
|
||||||
|
// LogMessage will be displayed in the server logs or wherever
|
||||||
|
// [Error.Error] is called.
|
||||||
|
LogMessage string
|
||||||
|
// Underlying is the wrapped error returned by [Error.Unwrap].
|
||||||
|
Underlying error
|
||||||
|
// PublicMessage is constructed from the template uniquely
|
||||||
|
// identified by MessageID and the values in PublicPayload (if any)
|
||||||
|
// to provide the end-user with information that they can use to
|
||||||
|
// resolve the issue.
|
||||||
PublicMessage string
|
PublicMessage string
|
||||||
|
// PublicPayload provides fields for passing structured data to
|
||||||
|
// construct localized error messages in the client.
|
||||||
PublicPayload map[string]interface{}
|
PublicPayload map[string]interface{}
|
||||||
LogLevel LogLevel
|
// LogLevel provides a suggested level of logging for the error.
|
||||||
|
LogLevel LogLevel
|
||||||
}
|
}
|
||||||
|
|
||||||
// MarshalJSON returns an error, we do not want raw Error:s being
|
// MarshalJSON returns an error, we do not want raw [Error]s being
|
||||||
// marshaled into JSON.
|
// marshaled into JSON.
|
||||||
//
|
//
|
||||||
// Use Public to convert the Error into a PublicError which can be
|
// Use [Error.Public] to convert the Error into a [PublicError] which
|
||||||
// marshaled. This is not done automatically, as that conversion is
|
// can safely be marshaled into JSON. This is not done automatically,
|
||||||
// lossy.
|
// as that conversion is lossy.
|
||||||
func (e Error) MarshalJSON() ([]byte, error) {
|
func (e Error) MarshalJSON() ([]byte, error) {
|
||||||
return nil, fmt.Errorf("errutil.Error cannot be directly marshaled into JSON")
|
return nil, fmt.Errorf("errutil.Error cannot be directly marshaled into JSON")
|
||||||
}
|
}
|
||||||
@ -155,8 +194,10 @@ func (e Error) Unwrap() error {
|
|||||||
return e.Underlying
|
return e.Underlying
|
||||||
}
|
}
|
||||||
|
|
||||||
// Is is used by errors.Is to allow for custom definitions of equality
|
// Is checks whether an error is derived from the error passed as an
|
||||||
// between two errors.
|
// argument.
|
||||||
|
//
|
||||||
|
// Implements the interface used by [errors.Is].
|
||||||
func (e Error) Is(other error) bool {
|
func (e Error) Is(other error) bool {
|
||||||
// The linter complains that it wants to use errors.As because it
|
// The linter complains that it wants to use errors.As because it
|
||||||
// handles unwrapping, we don't want to do that here since we want
|
// handles unwrapping, we don't want to do that here since we want
|
||||||
|
@ -15,8 +15,8 @@ var (
|
|||||||
// same error message for the frontend statically within the
|
// same error message for the frontend statically within the
|
||||||
// package.
|
// package.
|
||||||
|
|
||||||
errAbsPath = errutil.NewBase(errutil.StatusBadRequest, "shorturl.absolute-path")
|
errAbsPath = errutil.NewBase(errutil.StatusBadRequest, "shorturl.absolutePath")
|
||||||
errInvalidPath = errutil.NewBase(errutil.StatusBadRequest, "shorturl.invalid-path")
|
errInvalidPath = errutil.NewBase(errutil.StatusBadRequest, "shorturl.invalidPath")
|
||||||
errUnexpected = errutil.NewBase(errutil.StatusInternal, "shorturl.unexpected")
|
errUnexpected = errutil.NewBase(errutil.StatusInternal, "shorturl.unexpected")
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -29,8 +29,8 @@ func Example() {
|
|||||||
fmt.Println(e.Error())
|
fmt.Println(e.Error())
|
||||||
|
|
||||||
// Output:
|
// Output:
|
||||||
// 400 shorturl.invalid-path
|
// 400 shorturl.invalidPath
|
||||||
// [shorturl.invalid-path] path mustn't contain '..': 'abc/../def'
|
// [shorturl.invalidPath] path mustn't contain '..': 'abc/../def'
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateShortURL runs a few validations and returns
|
// CreateShortURL runs a few validations and returns
|
||||||
|
@ -10,8 +10,8 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestBase_Is(t *testing.T) {
|
func TestBase_Is(t *testing.T) {
|
||||||
baseNotFound := NewBase(StatusNotFound, "test:not-found")
|
baseNotFound := NewBase(StatusNotFound, "test.notFound")
|
||||||
baseInternal := NewBase(StatusInternal, "test:internal")
|
baseInternal := NewBase(StatusInternal, "test.internal")
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
Base Base
|
Base Base
|
||||||
|
@ -50,7 +50,7 @@ type TemplateOpt func(Template) (Template, error)
|
|||||||
|
|
||||||
// MustTemplate panics if the template for Template cannot be compiled.
|
// MustTemplate panics if the template for Template cannot be compiled.
|
||||||
//
|
//
|
||||||
// Only useful for global or package level initialization of Template:s.
|
// Only useful for global or package level initialization of [Template].
|
||||||
func (b Base) MustTemplate(pattern string, opts ...TemplateOpt) Template {
|
func (b Base) MustTemplate(pattern string, opts ...TemplateOpt) Template {
|
||||||
res, err := b.Template(pattern, opts...)
|
res, err := b.Template(pattern, opts...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -63,7 +63,7 @@ func (b Base) MustTemplate(pattern string, opts ...TemplateOpt) Template {
|
|||||||
// WithPublic provides templating for the user facing error message based
|
// WithPublic provides templating for the user facing error message based
|
||||||
// on only the fields available in TemplateData.Public.
|
// on only the fields available in TemplateData.Public.
|
||||||
//
|
//
|
||||||
// Used as a functional option to Base.Template.
|
// Used as a functional option to [Base.Template].
|
||||||
func WithPublic(pattern string) TemplateOpt {
|
func WithPublic(pattern string) TemplateOpt {
|
||||||
return func(t Template) (Template, error) {
|
return func(t Template) (Template, error) {
|
||||||
var err error
|
var err error
|
||||||
@ -77,7 +77,7 @@ func WithPublic(pattern string) TemplateOpt {
|
|||||||
// TemplateData.Error and TemplateData.Private will not be populated
|
// TemplateData.Error and TemplateData.Private will not be populated
|
||||||
// when rendering the public message.
|
// when rendering the public message.
|
||||||
//
|
//
|
||||||
// Used as a functional option to Base.Template.
|
// Used as a functional option to [Base.Template].
|
||||||
func WithPublicFromLog() TemplateOpt {
|
func WithPublicFromLog() TemplateOpt {
|
||||||
return func(t Template) (Template, error) {
|
return func(t Template) (Template, error) {
|
||||||
t.publicTemplate = t.logTemplate
|
t.publicTemplate = t.logTemplate
|
||||||
@ -85,8 +85,8 @@ func WithPublicFromLog() TemplateOpt {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build returns a new Error based on the base Template and the provided
|
// Build returns a new [Error] based on the base [Template] and the
|
||||||
// TemplateData, wrapping the error in TemplateData.Error if set.
|
// provided [TemplateData], wrapping the error in TemplateData.Error.
|
||||||
//
|
//
|
||||||
// Build can fail and return an error that is not of type Error.
|
// Build can fail and return an error that is not of type Error.
|
||||||
func (t Template) Build(data TemplateData) error {
|
func (t Template) Build(data TemplateData) error {
|
||||||
|
@ -5,12 +5,13 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/util/errutil"
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
"github.com/grafana/grafana/pkg/util/errutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestTemplate(t *testing.T) {
|
func TestTemplate(t *testing.T) {
|
||||||
tmpl := errutil.NewBase(errutil.StatusInternal, "template.sample-error").MustTemplate("[{{ .Public.user }}] got error: {{ .Error }}")
|
tmpl := errutil.NewBase(errutil.StatusInternal, "template.sampleError").MustTemplate("[{{ .Public.user }}] got error: {{ .Error }}")
|
||||||
err := tmpl.Build(errutil.TemplateData{
|
err := tmpl.Build(errutil.TemplateData{
|
||||||
Public: map[string]interface{}{
|
Public: map[string]interface{}{
|
||||||
"user": "grot the bot",
|
"user": "grot the bot",
|
||||||
@ -30,7 +31,7 @@ func TestTemplate(t *testing.T) {
|
|||||||
func ExampleTemplate() {
|
func ExampleTemplate() {
|
||||||
// Initialization, this is typically done on a package or global
|
// Initialization, this is typically done on a package or global
|
||||||
// level.
|
// level.
|
||||||
var tmpl = errutil.NewBase(errutil.StatusInternal, "template.sample-error").MustTemplate("[{{ .Public.user }}] got error: {{ .Error }}")
|
var tmpl = errutil.NewBase(errutil.StatusInternal, "template.sampleError").MustTemplate("[{{ .Public.user }}] got error: {{ .Error }}")
|
||||||
|
|
||||||
// Construct an error based on the template.
|
// Construct an error based on the template.
|
||||||
err := tmpl.Build(errutil.TemplateData{
|
err := tmpl.Build(errutil.TemplateData{
|
||||||
@ -43,14 +44,14 @@ func ExampleTemplate() {
|
|||||||
fmt.Println(err.Error())
|
fmt.Println(err.Error())
|
||||||
|
|
||||||
// Output:
|
// Output:
|
||||||
// [template.sample-error] [grot the bot] got error: oh noes
|
// [template.sampleError] [grot the bot] got error: oh noes
|
||||||
}
|
}
|
||||||
|
|
||||||
func ExampleTemplate_public() {
|
func ExampleTemplate_public() {
|
||||||
// Initialization, this is typically done on a package or global
|
// Initialization, this is typically done on a package or global
|
||||||
// level.
|
// level.
|
||||||
var tmpl = errutil.
|
var tmpl = errutil.
|
||||||
NewBase(errutil.StatusInternal, "template.sample-error").
|
NewBase(errutil.StatusInternal, "template.sampleError").
|
||||||
MustTemplate(
|
MustTemplate(
|
||||||
"[{{ .Public.user }}] got error: {{ .Error }}",
|
"[{{ .Public.user }}] got error: {{ .Error }}",
|
||||||
errutil.WithPublic("Oh, no, error for {{ .Public.user }}"),
|
errutil.WithPublic("Oh, no, error for {{ .Public.user }}"),
|
||||||
@ -69,6 +70,6 @@ func ExampleTemplate_public() {
|
|||||||
fmt.Println(err.PublicMessage)
|
fmt.Println(err.PublicMessage)
|
||||||
|
|
||||||
// Output:
|
// Output:
|
||||||
// [template.sample-error] [grot the bot] got error: oh noes
|
// [template.sampleError] [grot the bot] got error: oh noes
|
||||||
// Oh, no, error for grot the bot
|
// Oh, no, error for grot the bot
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user