«Поймать» ошибку в ниладической функции в Go text / template и продолжить выполнение

#go #go-templates

#Вперед #go-шаблоны

Вопрос:

Если у меня есть структура с подобной функцией:

 type data struct{}

func (d *data) Foo() (string, error) {
    return "", errors.New("bad")
}
  

И я вызываю .Foo шаблон, например:

 {{ .Foo }}
  

Я получаю сообщение об ошибке:

 error calling Foo: bad
  

Это соответствует документации для text/template :

  • Имя ниладического метода данных, которому предшествует точка, например .Method Результатом является значение вызова метода с точкой в качестве получателя, точка.Метод (). Такой метод должен иметь одно возвращаемое значение (любого типа) или два возвращаемых значения, второе из которых является ошибкой. Если у него два и возвращаемая ошибка не равна нулю, выполнение завершается, и ошибка возвращается вызывающей стороне в качестве значения Execute .

Могу ли я определить функцию для «перехвата» этой ошибки и возврата некоторого сообщения по умолчанию вместо остановки выполнения? Например:

 func Catch(val string, err error) string {
    if err != nil {
        return "[render error]"
    } else {
        return val
    }
}
  

Затем:

 map := template.FuncMap{"catch": Catch}
tpl := template.Must(template.New("t").Funcs(map).Parse(`
    {{ .Foo | catch }}
`))
b := new(bytes.Buffer)
err := tpl.Execute(b, amp;data{})
  

Это в настоящее время выдает ошибку — есть ли способ заставить ее работать?

Ответ №1:

Вот мой очень хакерский ответ, который вызывает метод во внутренней структуре, а затем возвращает «отредактировано», если вы возвращаете конкретную ошибку.

 // 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)
}