templatecheck

package module
v0.7.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 10, 2023 License: MIT Imports: 8 Imported by: 1

README

templatecheck

Check Go templates for validity.

Using CheckedTemplate[T]:

t := template.Must(template.ParseFiles("index.tmpl"))
ct, err := templatecheck.NewChecked[IndexData](t)
if err != nil { ... }
// ct cannot have type errors.
data := IndexData{...}
...
// ct.Execute's second argument must be of type IndexData.
if err := ct.Execute(w, data); err != nil { ... }

Using CheckedTemplate[T] is recommended, but may require changes in how you write your templates and provide data to them. For example, if your template contains

{{.F}}

you can execute it with any value that has an F field or method. Two different calls to Template.Execute can pass in two different types, as long as each has F. With checked templates, you must fix a single type to use for execution.

Using the CheckXXX functions:

t := template.Must(template.ParseFiles("index.tmpl"))
if err := templatecheck.CheckHTML(t, homePage{}); err != nil {
    log.Fatal(err)
}

See the package documentation for details.

Documentation

Overview

Package templatecheck checks Go templates for problems. It can detect many errors that are normally caught only during execution. Use templatecheck to find template errors early, and along template execution paths that might only rarely be reached.

Type-Checked Templates

Given a template t from one of the text/template, html/template, or github.com/google/safehtml/template packages, calling NewChecked[T](t) type-checks t and returns a CheckedTemplate[T]. The only things you can do with a CheckedTemplate[T] are get its name, and execute it with data of type T.

The execution will be free of type errors. To make that guarantee, the semantics of templates are restricted. CheckHTMLStrict has details, but the general idea is that types must be known so they can be checked. For example, the template

{{.F}}

will succeed as long as dot contains a field or zero-argument method named "F". The non-strict CheckXXX functions of this package will check that if the type of dot is known, but if it is unknown or an interface type, they will not complain, since template execution might succeed. By contrast, strict checking will fail unless it can be sure that the reference to "F" is valid, implying that it must know the type of dot.

When all of a program's templates are CheckedTemplates, it is not necessary to write tests to check templates as described below. All type-checking happens when the CheckedTemplates are created.

Checks

The rest of this documentation describes the original templatecheck mechanism, accessed through the CheckXXX functions. It may still be useful, especially for existing programs, but CheckedTemplate[T] is preferable because it is safer.

A template must be invoked with the same Go type each time to use the templatecheck.CheckXXX functions. Passing that type to templatecheck gives it enough information to verify that all the field references in the template are valid. templatecheck can also verify that functions are called with the right number and types of arguments, that the argument to a range statement can actually be ranged over, and a few other things.

Consider a web server that parses a template for its home page:

import "html/template"

var tmpl = template.Must(template.ParseFiles("index.tmpl"))

type homePage struct { ... }

func handler(w http.ResponseWriter, r *http.Request) {
    ...
    var buf bytes.Buffer
    err := tmpl.Execute(&buf, homePage{...})
    ...
}

Use templatecheck to catch errors in tests, instead of during serving:

func TestTemplates(t *testing.T) {
    if err := templatecheck.CheckHTML(tmpl, homePage{}); err != nil {
        t.Fatal(err)
    }
}

Checking Associated Templates

To check associated templates, use Template.Lookup. This can be necessary for non-strict checking if full type information isn't available to the main template. (It isn't needed for strict checking, because all calls to associated templates are type-checked.)

For example, here the base template is always invoked with a basePage, but the type of its Details field differs depending on the value of IsTop.

type basePage struct {
    IsTop bool
    Details any
}

type topDetails ...
type bottomDetails ...

The template text is

{{if .IsTop}}
  {{template "top" .Details}}
{{else}}
  {{template "bottom" .Details}}
{{end}}

{{define "top"}}...{{end}}
{{define "bottom"}}...{{end}}

Checking only the main template will not provide much information about the two associated templates, because their data types are unknown. All three templates should be checked, like so:

t := template.Must(template.New("").Parse(base))
if err := templatecheck.CheckText(t, basePage{}) ...
if err := templatecheck.CheckText(t.Lookup("top"), topDetails{}) ...
if err := templatecheck.CheckText(t.Lookup("bottom"), bottomDetails{}) ...
Example
package main

import (
	"fmt"
	"text/template"

	"github.com/jba/templatecheck"
)

func main() {
	type data struct {
		Greeting string
		Name     string
	}

	const contents = `{{.Greetings}}, {{.Name}} and welcome!`

	t := template.Must(template.New("greet").Parse(contents))
	err := templatecheck.CheckText(t, data{})
	fmt.Println(err)

}
Output:

template: greet:1:2: checking "greet" at <.Greetings>: can't use field Greetings in type templatecheck_test.data

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func CheckHTML

func CheckHTML(t *htmpl.Template, typeValue any) error

CheckHTML checks an html/template for problems. The second argument is the type of dot passed to template.Execute.

CheckHTML assumes that the "missingkey" option for the template is "zero", meaning that a missing key in a map returns the zero value for the map's element type. This is not the default value for "missingkey", but it allows more checks.

func CheckHTMLStrict added in v0.7.0

func CheckHTMLStrict(t *htmpl.Template, typeValue any) error

CheckHTMLStrict checks an html/template for problems. The second argument is the type of dot passed to template.Execute.

CheckHTMLStrict makes the same assumption about the "missingkey" option as CheckHTML does.

CheckHTMLStrict guarantees that no type errors will occur if t is executed with a value of the given type. To do so, it restricts the semantics of the template language as follows:

  • All calls to templates created with "define" or "block" must pass the same type for dot.
  • Variables have a consistent type, determined when the variable is declared with ":=". If a variable is assigned a new value, the value's type must be assignable to the type used to declare it.
  • All types in an expression must be known. That includes field access, function calls and arguments to "range".
  • If the "and" or "or" functions are used for their value, then all their arguments must be of the same type. That restriction does not apply if they are used as an argument to "if", where only the truth value of the result matters.

func CheckSafe

func CheckSafe(t *stmpl.Template, typeValue any) error

CheckSafe checks a github.com/google/safehtml/template for problems. See CheckHTML for details.

func CheckSafeStrict added in v0.7.0

func CheckSafeStrict(t *stmpl.Template, typeValue any) error

CheckSafeStrict does strict checking. See CheckHTMLStrict for details.

func CheckText

func CheckText(t *ttmpl.Template, typeValue any) error

CheckText checks a text/template for problems. See CheckHTML for details.

func CheckTextStrict added in v0.7.0

func CheckTextStrict(t *ttmpl.Template, typeValue any) error

CheckTextStrict does strict checking. See CheckHTMLStrict for details.

Types

type CheckedTemplate added in v0.7.0

type CheckedTemplate[T any] struct {
	// contains filtered or unexported fields
}

func MustChecked added in v0.7.0

func MustChecked[T any, M tmpl[M]](t M) *CheckedTemplate[T]

MustChecked is like NewChecked, but panics if type-checking fails.

func NewChecked added in v0.7.0

func NewChecked[T any, M tmpl[M]](t M) (*CheckedTemplate[T], error)

NewChecked type-checks t using one of the CheckXXXStrict functions. If the check succeeds, it returns a CheckedTemplate[T] that behaves like the argument template.

Subsequent changes to the argument template do not affect the CheckedTemplate.

func (*CheckedTemplate[T]) Execute added in v0.7.0

func (t *CheckedTemplate[T]) Execute(wr io.Writer, data T) error

Execute behaves like Template.Execute.

func (*CheckedTemplate[T]) Name added in v0.7.0

func (t *CheckedTemplate[T]) Name() string

Name returns the name of the template.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL