Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve template error reporting #23396

Merged
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions modules/context/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,10 @@ import (
"net/http"
"net/url"
"path"
"regexp"
"strconv"
"strings"
texttemplate "text/template"
"time"

"code.gitea.io/gitea/models/db"
Expand Down Expand Up @@ -213,6 +215,8 @@ func (ctx *Context) RedirectToFirst(location ...string) {
ctx.Redirect(setting.AppSubURL + "/")
}

var templateExecutingErr = regexp.MustCompile(`^template: (.*):([1-9][0-9]*):([1-9][0-9]*): executing (?:"(.*)" at <(.*)>: )?`)

// HTML calls Context.HTML and renders the template to HTTP response
func (ctx *Context) HTML(status int, name base.TplName) {
log.Debug("Template: %s", name)
Expand All @@ -228,6 +232,34 @@ func (ctx *Context) HTML(status int, name base.TplName) {
ctx.PlainText(http.StatusInternalServerError, "Unable to find status/500 template")
return
}
if execErr, ok := err.(texttemplate.ExecError); ok {
if groups := templateExecutingErr.FindStringSubmatch(err.Error()); len(groups) > 0 {
errorTemplateName, lineStr, posStr := groups[1], groups[2], groups[3]
target := ""
if len(groups) == 6 {
target = groups[5]
}
line, _ := strconv.Atoi(lineStr) // Cannot error out as groups[2] is [1-9][0-9]*
pos, _ := strconv.Atoi(posStr) // Cannot error out as groups[3] is [1-9][0-9]*
filename, filenameErr := templates.GetAssetFilename("templates/" + errorTemplateName + ".tmpl")
if filenameErr != nil {
filename = "(template) " + errorTemplateName
}
if errorTemplateName != string(name) {
filename += " (subtemplate of " + string(name) + ")"
}
err = fmt.Errorf("%w\nin template file %s:\n%s", err, filename, templates.GetLineFromTemplate(errorTemplateName, line, target, pos))
} else {
filename, filenameErr := templates.GetAssetFilename("templates/" + execErr.Name + ".tmpl")
if filenameErr != nil {
filename = "(template) " + execErr.Name
}
if execErr.Name != string(name) {
filename += " (subtemplate of " + string(name) + ")"
}
err = fmt.Errorf("%w\nin template file %s", err, filename)
}
}
ctx.ServerError("Render failed", err)
}
}
Expand Down
43 changes: 25 additions & 18 deletions modules/templates/htmlrenderer.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ func handleGenericTemplateError(err error) (string, []interface{}) {

lineNumber, _ := strconv.Atoi(lineNumberStr)

line := getLineFromAsset(templateName, lineNumber, "")
line := GetLineFromTemplate(templateName, lineNumber, "", -1)

return "PANIC: Unable to compile templates!\n%s in template file %s at line %d:\n\n%s\nStacktrace:\n\n%s", []interface{}{message, filename, lineNumber, log.NewColoredValue(line, log.Reset), log.Stack(2)}
}
Expand All @@ -140,7 +140,7 @@ func handleNotDefinedPanicError(err error) (string, []interface{}) {

lineNumber, _ := strconv.Atoi(lineNumberStr)

line := getLineFromAsset(templateName, lineNumber, functionName)
line := GetLineFromTemplate(templateName, lineNumber, functionName, -1)

return "PANIC: Unable to compile templates!\nUndefined function %q in template file %s at line %d:\n\n%s", []interface{}{functionName, filename, lineNumber, log.NewColoredValue(line, log.Reset)}
}
Expand All @@ -161,7 +161,7 @@ func handleUnexpected(err error) (string, []interface{}) {

lineNumber, _ := strconv.Atoi(lineNumberStr)

line := getLineFromAsset(templateName, lineNumber, unexpected)
line := GetLineFromTemplate(templateName, lineNumber, unexpected, -1)

return "PANIC: Unable to compile templates!\nUnexpected %q in template file %s at line %d:\n\n%s", []interface{}{unexpected, filename, lineNumber, log.NewColoredValue(line, log.Reset)}
}
Expand All @@ -181,14 +181,15 @@ func handleExpectedEnd(err error) (string, []interface{}) {

lineNumber, _ := strconv.Atoi(lineNumberStr)

line := getLineFromAsset(templateName, lineNumber, unexpected)
line := GetLineFromTemplate(templateName, lineNumber, unexpected, -1)

return "PANIC: Unable to compile templates!\nMissing end with unexpected %q in template file %s at line %d:\n\n%s", []interface{}{unexpected, filename, lineNumber, log.NewColoredValue(line, log.Reset)}
}

const dashSeparator = "----------------------------------------------------------------------\n"

func getLineFromAsset(templateName string, targetLineNum int, target string) string {
// GetLineFromTemplate returns a line from a template with some context
func GetLineFromTemplate(templateName string, targetLineNum int, target string, position int) string {
bs, err := GetAsset("templates/" + templateName + ".tmpl")
if err != nil {
return fmt.Sprintf("(unable to read template file: %v)", err)
Expand Down Expand Up @@ -229,23 +230,29 @@ func getLineFromAsset(templateName string, targetLineNum int, target string) str
// If there is a provided target to look for in the line add a pointer to it
// e.g. ^^^^^^^
if target != "" {
idx := bytes.Index(lineBs, []byte(target))

if idx >= 0 {
// take the current line and replace preceding text with whitespace (except for tab)
for i := range lineBs[:idx] {
if lineBs[i] != '\t' {
lineBs[i] = ' '
}
targetPos := bytes.Index(lineBs, []byte(target))
if targetPos >= 0 {
position = targetPos
}
}
if position >= 0 {
// take the current line and replace preceding text with whitespace (except for tab)
for i := range lineBs[:position] {
if lineBs[i] != '\t' {
lineBs[i] = ' '
}
}

// write the preceding "space"
_, _ = sb.Write(lineBs[:idx])
// write the preceding "space"
_, _ = sb.Write(lineBs[:position])

// Now write the ^^ pointer
_, _ = sb.WriteString(strings.Repeat("^", len(target)))
_ = sb.WriteByte('\n')
// Now write the ^^ pointer
targetLen := len(target)
if targetLen == 0 {
targetLen = 1
}
_, _ = sb.WriteString(strings.Repeat("^", targetLen))
_ = sb.WriteByte('\n')
}

// Finally write the footer
Expand Down