5

I would expect

t, err := template.New("t1").Option("missingkey=error").Parse("{{index . \"/foo/bar\"}}")        
err = t.Execute(os.Stdout, d)

to return an error if the map 'd' doesn't have a key '/foo/bar', but everything is just fine. Am I missing something?

Here's the go playground: http://play.golang.org/p/Oqg1Dy4h1k

2 Answers 2

2

The missingkey option does not work with index. You will only get the desired result when you access the map using .<field-name>:

t, err := template.New("t1").Option("missingkey=error").Parse(`{{.foo}}`)
err = t.Execute(os.Stdout, d)

You can get around this by defining your own index function that returns an error when a key is missing:

package main

import (
    "errors"
    "fmt"
    "os"
    "text/template"
)

func lookup(m map[string]interface{}, key string) (interface{}, error) {
    val, ok := m[key]
    if !ok {
        return nil, errors.New("missing key " + key)
    }
    return val, nil
}

func main() {
    d := map[string]interface{}{
        "/foo/bar": 34,
    }
    fns := template.FuncMap{
        "lookup": lookup,
    }
    t, err := template.New("t1").Funcs(fns).Parse(`{{ lookup . "/foo/baz" }}`)
    if err != nil {
        fmt.Printf("ERROR 1: %q\n", err)
    }
    err = t.Execute(os.Stdout, d)
    if err != nil {
        fmt.Printf("ERROR 2: %q\n", err)
    }
}

https://play.golang.org/p/0_ZZ2Pwa1uZ

Sign up to request clarification or add additional context in comments.

2 Comments

Thanks. I edited my question to show why I'm using index (and you answered while I was in the middle of it). The key has a slash in it. This puts a wrinkle in my plans.
@user5976738: see my updated answer for a workaround
2

Hello @user5976738 and all.

I have a different solution for that problem which does not require a template function at all and it uses the text/template/parse package.

This workaround may be very useful for some of you. Example of usage:

if missingKeys, ok := IsMissingKeys(err); ok{
   for _, key := range missingKeys{ println(key) }
}

Source Code:

package main

import (
    "fmt"
    "os"
    "strings"
    "strconv"
    "text/template"
    templateparse "text/template/parse"
)

type errMissingKeys struct {
    Keys         []string
    TemplateName string
}

func (err errMissingKeys) Error() string {
    return fmt.Sprintf("template: %s: map has no entry for keys: %s", err.TemplateName, strings.Join(err.Keys, ", "))
}

// IsMissingKeys reports whether an "err" is caused by template missing keys.
//
// Usage:
// if missingKeys, ok := IsMissingKeys(err); ok{
//     for _, key := range missingKeys{ println(key) }
// }
func IsMissingKeys(err error) ([]string, bool) {
    if err != nil {
        if v, ok := err.(errMissingKeys); ok {
            return v.Keys, true
        }
    }

    return nil, false
}

func main() {
    data := map[string]interface{}{
        "bar": 34,
    }

    tmpl, err := template.New("myTemplate").Option("missingkey=error").Parse(`{{.baz}} {{.other}}`)
    if err != nil {
        fmt.Println(err.Error())
        os.Exit(1)
    }

    err = tmpl.Execute(os.Stdout, data)
    if err != nil {
        if strings.Contains(err.Error(), "map has no entry for key ") {
            // Check if a field is not a "data" match,
            // which will lead on execute error on first undefined (missingkey=error).
            // So collect all these keys so we can have a typed error with all unknown keys listed.
            var missingKeys []string
            for _, n := range tmpl.Root.Nodes {
                if n.Type() == templateparse.NodeAction {
                    key := strings.TrimFunc(n.String(), func(r rune) bool {
                        return r == '{' || r == '}' || r == ' ' || r == '.'
                    })

                    if key == "" {
                        continue
                    }

                    if _, ok := data[key]; !ok {
                        missingKeys = append(missingKeys, strconv.Quote(key))
                    }
                }
            }

            // if just one then keep the original error which contains the full context,
            // else set to custom error type which will keep the missing keys for caller's further use.
            if len(missingKeys) > 1 {
                err = errMissingKeys{TemplateName: tmpl.Name(), Keys: missingKeys}
            }
        }
        fmt.Println(err.Error())
        os.Exit(1)
    }
}

Playground link: https://play.golang.org/p/JMwBpU2KyP2

Best Regards, Gerasimos Maropoulos - Creator of https://iris-go.com

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.