return str
}
-// errorf formats the error and terminates processing.
+// TODO: It would be nice if ExecError was more broken down, but
+// the way ErrorContext embeds the template name makes the
+// processing too clumsy.
+
+// ExecError is the custom error type returned when Execute has an
+// error evaluating its template. (If a write error occurs, the actual
+// error is returned; it will not be of type ExecError.)
+type ExecError struct {
+ Name string // Name of template.
+ Err error // Pre-formatted error.
+}
+
+func (e ExecError) Error() string {
+ return e.Err.Error()
+}
+
+// errorf records an ExecError and terminates processing.
func (s *state) errorf(format string, args ...interface{}) {
name := doublePercent(s.tmpl.Name())
if s.node == nil {
location, context := s.tmpl.ErrorContext(s.node)
format = fmt.Sprintf("template: %s: executing %q at <%s>: %s", location, name, doublePercent(context), format)
}
- panic(fmt.Errorf(format, args...))
+ panic(ExecError{
+ Name: s.tmpl.Name(),
+ Err: fmt.Errorf(format, args...),
+ })
+}
+
+// writeError is the wrapper type used internally when Execute has an
+// error writing to its output. We strip the wrapper in errRecover.
+// Note that this is not an implementation of error, so it cannot escape
+// from the package as an error value.
+type writeError struct {
+ Err error // Original error.
+}
+
+func (s *state) writeError(err error) {
+ panic(writeError{
+ Err: err,
+ })
}
// errRecover is the handler that turns panics into returns from the top
switch err := e.(type) {
case runtime.Error:
panic(e)
- case error:
+ case writeError:
+ *errp = err.Err // Strip the wrapper.
+ case ExecError:
+ *errp = err // Keep the wrapper.
+ case error: // TODO: This should never happen, but it does. Understand and/or fix.
*errp = err
default:
panic(e)
s.walkTemplate(dot, node)
case *parse.TextNode:
if _, err := s.wr.Write(node.Text); err != nil {
- s.errorf("%s", err)
+ s.writeError(err)
}
case *parse.WithNode:
s.walkIfOrWith(parse.NodeWith, dot, node.Pipe, node.List, node.ElseList)
if !ok {
s.errorf("can't print %s of type %s", n, v.Type())
}
- fmt.Fprint(s.wr, iface)
+ _, err := fmt.Fprint(s.wr, iface)
+ if err != nil {
+ s.writeError(err)
+ }
}
// printableValue returns the, possibly indirected, interface value inside v that
"errors"
"flag"
"fmt"
+ "io/ioutil"
"reflect"
"strings"
"testing"
t.Fatalf("unexpected error: %s", str)
}
}
+
+const alwaysErrorText = "always be failing"
+
+var alwaysError = errors.New(alwaysErrorText)
+
+type ErrorWriter int
+
+func (e ErrorWriter) Write(p []byte) (int, error) {
+ return 0, alwaysError
+}
+
+func TestExecuteGivesExecError(t *testing.T) {
+ // First, a non-execution error shouldn't be an ExecError.
+ tmpl, err := New("X").Parse("hello")
+ if err != nil {
+ t.Fatal(err)
+ }
+ err = tmpl.Execute(ErrorWriter(0), 0)
+ if err == nil {
+ t.Fatal("expected error; got none")
+ }
+ if err.Error() != alwaysErrorText {
+ t.Errorf("expected %q error; got %q", alwaysErrorText, err)
+ }
+ // This one should be an ExecError.
+ tmpl, err = New("X").Parse("hello, {{.X.Y}}")
+ if err != nil {
+ t.Fatal(err)
+ }
+ err = tmpl.Execute(ioutil.Discard, 0)
+ if err == nil {
+ t.Fatal("expected error; got none")
+ }
+ eerr, ok := err.(ExecError)
+ if !ok {
+ t.Fatalf("did not expect ExecError %s", eerr)
+ }
+ expect := "field X in type int"
+ if !strings.Contains(err.Error(), expect) {
+ t.Errorf("expected %q; got %q", expect, err)
+ }
+}