If I have a struct with a function like this:
type data struct{}
func (d *data) Foo() (string, error) {
return "", errors.New("bad")
}
And I call .Foo
in a template, like this:
{{ .Foo }}
I get an error:
error calling Foo: bad
This is in line with the documentation for text/template
:
- The name of a niladic method of the data, preceded by a period, such as .Method The result is the value of invoking the method with dot as the receiver, dot.Method(). Such a method must have one return value (of any type) or two return values, the second of which is an error. If it has two and the returned error is non-nil, execution terminates and an error is returned to the caller as the value of Execute.
Can I define a function to "catch" that error and return some default message, instead of halting execution? For example:
func Catch(val string, err error) string {
if err != nil {
return "[render error]"
} else {
return val
}
}
Then:
map := template.FuncMap{"catch": Catch}
tpl := template.Must(template.New("t").Funcs(map).Parse(`
{{ .Foo | catch }}
`))
b := new(bytes.Buffer)
err := tpl.Execute(b, &data{})
That currently renders an error - is there a way to get it to work?
Here's my very hacky answer, which Calls a method on the internal struct, and then returns "redacted" if you get back a specific error.
// Call calls the given method name on a Message. If the result is
// a PermissionDenied error, return the string "[redacted]" and no error.
//
// This should be used by templates to redact methods. It would be nicer to
// just call the Message directly, but returning an error from a niladic method
// immediately halts template execution, so we need this wrapper around the
// function behavior.
func (r *RedactedMessage) Call(mname string) (interface{}, error) {
if mname == "" {
return nil, errors.New("Call() with empty string")
}
for _, char := range mname {
if !unicode.IsUpper(char) {
return nil, errors.New("Cannot call private method")
}
// only check first character
break
}
t := reflect.ValueOf(r.mv)
m := t.MethodByName(mname)
if !m.IsValid() {
return nil, fmt.Errorf("Invalid method: %s", mname)
}
vals := m.Call([]reflect.Value{})
if len(vals) != 2 {
return nil, fmt.Errorf("Expected to get two values back, got %d", len(vals))
}
if vals[1].IsNil() {
return vals[0].Interface(), nil
}
if reflect.DeepEqual(vals[1].Interface(), config.PermissionDenied) {
return "redacted", nil
}
return nil, vals[1].Interface().(error)
}