grafana/pkg/util/errutil/template.go
2023-08-30 18:46:47 +03:00

122 lines
2.9 KiB
Go

package errutil
import (
"bytes"
"fmt"
"text/template"
)
// Template is an extended Base for when using templating to construct
// error messages.
type Template struct {
Base Base
logTemplate *template.Template
publicTemplate *template.Template
}
// TemplateData contains data for constructing an Error based on a
// Template.
type TemplateData struct {
Private map[string]any
Public map[string]any
Error error
}
// Template provides templating for converting Base to Error.
// This is useful where the public payload is populated with fields that
// should be present in the internal error representation.
func (b Base) Template(pattern string, opts ...TemplateOpt) (Template, error) {
tmpl, err := template.New(b.messageID + "~private").Parse(pattern)
if err != nil {
return Template{}, err
}
t := Template{
Base: b,
logTemplate: tmpl,
}
for _, o := range opts {
t, err = o(t)
if err != nil {
return Template{}, err
}
}
return t, nil
}
type TemplateOpt func(Template) (Template, error)
// MustTemplate panics if the template for Template cannot be compiled.
//
// Only useful for global or package level initialization of [Template].
func (b Base) MustTemplate(pattern string, opts ...TemplateOpt) Template {
res, err := b.Template(pattern, opts...)
if err != nil {
panic(err)
}
return res
}
// WithPublic provides templating for the user facing error message based
// on only the fields available in TemplateData.Public.
//
// Used as a functional option to [Base.Template].
func WithPublic(pattern string) TemplateOpt {
return func(t Template) (Template, error) {
var err error
t.publicTemplate, err = template.New(t.Base.messageID + "~public").Parse(pattern)
return t, err
}
}
// WithPublicFromLog copies over the template for the log message to be
// used for the user facing error message.
// TemplateData.Error and TemplateData.Private will not be populated
// when rendering the public message.
//
// Used as a functional option to [Base.Template].
func WithPublicFromLog() TemplateOpt {
return func(t Template) (Template, error) {
t.publicTemplate = t.logTemplate
return t, nil
}
}
// Build returns a new [Error] based on the base [Template] and the
// provided [TemplateData], wrapping the error in TemplateData.Error.
//
// Build can fail and return an error that is not of type Error.
func (t Template) Build(data TemplateData) error {
if t.logTemplate == nil {
return fmt.Errorf("cannot initialize error using missing template")
}
buf := bytes.Buffer{}
err := t.logTemplate.Execute(&buf, data)
if err != nil {
return err
}
pubBuf := bytes.Buffer{}
if t.publicTemplate != nil {
err := t.publicTemplate.Execute(&pubBuf, TemplateData{Public: data.Public})
if err != nil {
return err
}
}
e := t.Base.Errorf("%s", buf.String())
e.PublicMessage = pubBuf.String()
e.PublicPayload = data.Public
e.Underlying = data.Error
return e
}
func (t Template) Error() string {
return t.Base.Error()
}