Search code examples
gogo-templates

How to check if go template utilizes ALL template data


Let's say I have a template string

today i visited {{ .market }} to buy {{ .fruit }}

and the provided template data is

map[string]string := { market: "whole foods", fruit: "bananas", veg: "celery" }

I want to panic here since .veg is not used in the template string. Is this possible?

I am using go templates (text/template).


Solution

  • There is no builtin support for it. You could analyze the parsed template, but it's unnecessarily complex. Also note that this static analyzis could never be complete: a template may access data based on runtime parameters, and whether everything is used could only be decided at runtime, and may vary from execution to execution (e.g. a map value may be indexed using {{index .someMap .someKey}} where someKey is a value provided at runtime).

    An acceptable solution could be to pass a data structure that tracks which elements are accessed, and you can check that after the template execution and do whatever you want to if not everything was used.

    For example this structure tracks which elements were not yet accessed:

    type Params struct {
        m, remaining map[string]string
    }
    
    func NewParams(m map[string]string) *Params {
        return &Params{
            m:         m,
            remaining: maps.Clone(m),
        }
    }
    
    func (p *Params) Get(key string) string {
        delete(p.remaining, key)
        return p.m[key]
    }
    

    Example using it:

    const src = `today i visited {{ .Get "market" }} to buy {{ .Get "fruit" }}`
    t := template.Must(template.New("").Parse(src))
    
    m := NewParams(
        map[string]string{"market": "whole foods", "fruit": "bananas", "veg": "celery"},
    )
    
    if err := t.Execute(os.Stdout, m); err != nil {
        panic(err)
    }
    
    fmt.Println()
    if len(m.remaining) > 0 {
        fmt.Println("Following entries were not used:", m.remaining)
    }
    

    This will output (try it on the Go Playground):

    today i visited whole foods to buy bananas
    Following entries were not used: map[veg:celery]
    

    Note that I used 2 maps (m and remaining) to allow accessing the same elements multiple times. If each element could only be accessed once, a simple map (and removing elements from that map) would also work.