Syntax for creating struct map key in Go HTML template - go

I'm writing a simple Go program to display an HTML table of deployed service versions per environment. My program contains the following structs:
type versionKey struct {
Environment string
Service string
}
type templateData struct {
Environments []string
Services []string
Versions map[versionKey]string
}
As you can see, the Versions map uses a versionKey as a key for a string value e.g. "1.0.0".
I'm passing the templateData struct to an HTML template and ranging over its Environments and Services slices to build the HTML table. The problem is that I need to construct a versionKey for any given intersection of environment and service so I can use it to look up the version from the Versions map and output that value in the table cell.
Within the template I have $environment and $service variables available from the ranges, but I can't work out the Go template syntax to create the versionKey struct.
Here's the template code with the markup omitted:
{{$environments := .Environments}}
{{$services := .Services}}
{{$versions := .Versions}}
{{range $service := $services}}
...
{{range $environment := $environments}}
...
{{index $versions ...? }} // How to create versionKey struct map key here?
...
{{end}}
...
{{end}}

Using only template code you can't. You need some kind of support from the executing Go code to do that. By design philosophy, templates should not contain complex logic. You may argue whether this is complex, but the template syntax has no support for this.
Simplest solution would be to add a Version() method to the templateData struct, which would simply return the version for a given environment and service:
func (t *templateData) Version(environment, service string) string {
return t.Versions[versionKey{
Environment: environment,
Service: service,
}]
}
Using this from the template:
{{range $service := $services -}}
{{range $environment := $environments}}
{{$environment}} - {{$service}} version: {{$.Version $environment $service}}
{{end}}
{{end}}
Testing it:
t := template.Must(template.New("").Parse(templ))
td := &templateData{
Environments: []string{"EnvA", "EnvB"},
Services: []string{"ServA", "ServB"},
Versions: map[versionKey]string{
{"EnvA", "ServA"}: "1.0.0",
{"EnvA", "ServB"}: "1.0.1",
{"EnvB", "ServA"}: "1.0.2",
},
}
if err := t.Execute(os.Stdout, td); err != nil {
panic(err)
}
Output (try it on the Go Playground):
EnvA - ServA version: 1.0.0
EnvB - ServA version: 1.0.2
EnvA - ServB version: 1.0.1
EnvB - ServB version:
Alternatives
Instead of the templateData.Version() method you could just as easily register a function which could create and return a value of type versionKey from a given environment and service. See Template.Funcs() for details. This would be more complicated though, but more flexible as this could be reused elsewhere. See an example of this here: Golang templates (and passing funcs to template). A slight variation of this would be to pass a function value as any other template data instead of registering it as a named function, which can be called.
Another alternative would be to "transform" your Versions field into a map of maps, e.g.:
Versions map[string]map[string]string
Which first could be indexed by environment, then by service, which in the template you can achieve by 2 {{index}} actions. You would have to check if the first indexing yields any results though.

Related

Go Template compare if string ends in or contains another string

The eq function allows for comparing if two strings are equal
{{if eq .Name "MyName"}}
Is there a way to test if a string ends in (or contains) another string?
Use a function map containing the relevant string functions.
funcs := map[string]any{
"contains": strings.Contains,
"hasPrefix": strings.HasPrefix,
"hasSuffix": strings.HasSuffix}
tmpl := `{{if hasSuffix . ".txt"}}yes!{{end}}`
t := template.Must(template.New("").Funcs(funcs).Parse(tmpl))
t.Execute(os.Stdout, "example.txt") // writes yes! to standard out
Run the example on the playground.
Some applications that use Go templates as a feature (Hugo and Helm are examples) provide these functions by default.
(h/t to mkopriva).

Initializing external lib struct with own struct (decoded JSON) gives type error

I have a struct, which is decoded from a JSON http POST req.
My purpose of having this struct is:
- Simplifying JSON request from client
- Using the structs property data in another (external library) struct.
If i had to only use the external library struct, the client JSON setup will look confusing.
How do i use my structs values inside another struct (and especially their arrays)?
I have a working solution for some of the values with simple types.
Consider the following:
Ext lib struct:
type ExtStruct struct {
From *Email
Subject string
Personalizations []*Personalization
}
My lib struct:
type MyStruct struct {
From *Email
Subject string
Personalizations []*Personalization
}
This is my code as is:
myStruct := &MyStruct{}
err := json.NewDecoder(body).Decode(myStruct)
extStruct := &ExtStruct{
Subject: myStruct.Subject,
From: (*extStruct.Email)(myStruct.From),
Personalizations: []*extStruct.Personalization{
To: ([]*extStruct.Email)(myStruct.To),
}}
The Subjectand From value works, but i'm getting errors when trying to referencing array values.
I fail to see where i am wrong. AFAIK there's no other option to "simplify" input JSON from a client, only to reference the values in another bigger struct.
I can include the other referenced structs if needed, but think of it as:
type A struct {
RefB []*B
}
type B struct {
RefC []*C
Value string
}
type C struct {
Value string
}
And i need my struct to refer to C.
Maybe I misunderstood, but I would think the ExtStruct initialization would look more like
extStruct := &ExtStruct{
From: myStruct.From,
Personalizations: myStruct.Personalizations,
}
https://play.golang.org/p/lqFFBa7SKGg
If they are incompatibly types, Email and Personalization, you should marshall into ExtStruct directly, or do marshalling between types yourself.
I think you should clarify your answer, maybe provide a working (but faulty) example.
Ok, so i found out, it's quite impossible to cast slices.
If you refer to my post:
extStruct := &ExtStruct{
Subject: myStruct.Subject,
From: (*extStruct.Email)(myStruct.From),
Personalizations: []*extStruct.Personalization{
To: ([]*extStruct.Email)(myStruct.To),
}}
The Personalizations slice became:
Personalizations: ([]*extStruct.Personalization{{
To: castStruct(myStruct.To),
}}),
And
func castStruct(input []*myStruct.Email) []*extStruct.Email {
output := make([]*extStruct.Email, len(input))
for index, content := range input {
output[index] = (*extStruct.Email)(content)
}
return output
}
I don't know if there's a more clever way to do this, but this is how i succeeded eventually. Just comment this, if you want an elaboration, and I shall try to provide.

How can I override a template block with an empty template?

Using text/html I define a block in my base template, containing default content. In some situations I would like this block to be empty, so I thought I could just re-define its name and make it contain nothing like:
{{ block "something" . }}
<h1>Default content</h1>
{{ end }}
// later in a place that does not want "something" ...
{{ define "something" }}{{ end }}
Somehow Go seems to think that this definition is "Zero" and will still render the default content unless I put any non-whitespace content into the definition.
I found this issue on the Golang repo which describes the very same thing nicely in a Playground example:
package main
import (
"fmt"
"os"
"runtime"
"text/template"
)
func main() {
fmt.Printf("Version: %q\n", runtime.Version())
t, err := template.New("master").Parse(`{{block "area51" .}}Original Content{{end}}`)
if err != nil {
panic(err)
}
t, err = t.New("other_template").Parse(`{{define "area51"}}{{end}}`)
if err != nil {
panic(err)
}
fmt.Printf("Output:\n")
if err := t.ExecuteTemplate(os.Stdout, "master", nil); err != nil {
panic(err)
}
fmt.Printf("\n")
}
Weirdly, the issue mention it is fixed (and landed in 1.8.1 if I understand it correctly), but it does not work for me, neither with 1.8.1+ nor 1.9.
Is this a bug in Golang or is the approach flawed? Do I need to do anything differently in order to re-define the block so that it renders empty?
This is the expected behavior. This is documented at Template.Parse():
Templates can be redefined in successive calls to Parse, before the first use of Execute on t or any associated template. A template definition with a body containing only white space and comments is considered empty and will not replace an existing template's body. This allows using Parse to add new named template definitions without overwriting the main template body.
So you can't "erase" an already defined template (you can't replace its content to be empty).
If you "conditionally" need it, then use an {{if}} action to decide if the template is to be called. Alternatively you may put an {{if}} inside the template, and the template itself may choose not to render anything. In this case you have to make sure to pass the proper argument that controls what the template will render.
P.S. If you're working with HTML templates, you should always use html/template instead of text/template, as the former provides the same interface as package text/template but also provides contextual escaping to generate HTML output safe against code injection.

Go template name

In the html/template (and text/template) packages, template.New has the following signature:
func New(name string) *Template
What exactly is the name used for? I've scanned the docs (and a bit of source), but to no avail. I just instantiate all of my templates with an empty string and it doesn't seem to make a difference. Why should I bother with a name?
Even for naming templates, the two seem equivalent:
template.Must(template.New("").Parse(`{{ define "body" }}Body{{ end }}`))
template.Must(template.New("body").Parse(`Body`))
https://play.golang.org/p/wKzCHdLf2S
The name of the template–unsurprisingly–is to name the template.
What is it good for? As long as you don't want to refer to the template, it doesn't really matter. But if you want to refer to it, then yes, you refer to it by its name.
When would you want to refer to it? When you want to include a template in another e.g. using the {{template}} action, or when you want to execute a specific template using Template.ExecuteTemplate().
So far so good, but there's still a missing key point. This is not unambiguous / trivial: a template.Template value is "the representation of a parsed template". But the wording here is a little "imperfect". A template.Template value may be (and usually is) a collection of multiple, associated templates. template.Template has an unexported field:
tmpl map[string]*Template // Map from name to defined templates.
This tmpl field holds all other associated templates, templates that are visible to the template, and which can be referred to–yes–by their names.
When you parse multiple templates at once, using Template.ParseFiles() or Template.ParseGlob(), then the templates will be named by the file names, and they will be associated automatically (the above mentioned functions return a single template.Template value, which holds all the parsed templates, associated). Doc of Template.ParseFiles() is clear on this:
ParseFiles creates a new Template and parses the template definitions from the named files. The returned template's name will have the base name and parsed contents of the first file. [...]
When parsing multiple files with the same name in different directories, the last one mentioned will be the one that results. For instance, ParseFiles("a/foo", "b/foo") stores "b/foo" as the template named "foo", while "a/foo" is unavailable.
The template name can come from multiple places:
it can come from the file name (as seen above)
it can be specified explicitly (if defined using the {{define "somename"}} or {{block "somename"}} actions),
or it may be defined as an argument passed to template.New() (function) or Template.New() (method).
Let's see some examples:
func main() {
t := template.Must(template.New("one").Parse(t1src))
template.Must(t.New("other").Parse(t2src))
// error checks omitted for brevity
// Executes default, "one":
t.Execute(os.Stdout, nil)
// Executes explicit, "one":
t.ExecuteTemplate(os.Stdout, "one", nil)
// Executes explicit, "other":
t.ExecuteTemplate(os.Stdout, "other", nil)
}
const t1src = `I'm some template.
`
const t2src = `I'm some OTHER template.
`
Output (try it on the Go Playground):
I'm some template.
I'm some template.
I'm some OTHER template.
If you now go ahead, and change the first 2 lines to this:
t := template.Must(template.New("one").Parse(t1src))
t = template.Must(t.New("other").Parse(t2src))
Then what happens here is that we assigned a new template.Template value to t, which was the result of parsing t2src, so that will be the default, but still both templates can be "reached" from it as they are associated. The output changes to this (try it on the Go Playground):
I'm some OTHER template.
I'm some template.
I'm some OTHER template.
Calling template.New() (function) creates a new template, associated to none. When calling Template.New() (method), the returned template will be associated with (all) the template(s) the method is called on.
Now let's see some examples regarding "embedded" templates.
func main() {
t := template.Must(template.New("one").Parse(t1src))
template.Must(t.New("other").Parse(t2src))
template.Must(t.New("third").Parse(t3src))
t.Execute(os.Stdout, nil)
t.ExecuteTemplate(os.Stdout, "one", nil)
t.ExecuteTemplate(os.Stdout, "other", nil)
t.ExecuteTemplate(os.Stdout, "embedded", nil)
t.ExecuteTemplate(os.Stdout, "third", nil)
}
const t1src = `I'm some template. {{block "embedded" .}}I'm embedded in "one".
{{end}}`
const t2src = `I'm some OTHER template.
`
const t3src = `I'm the 3rd, including everything from "one": {{template "one"}}
`
Output (try it on the Go Playground):
I'm some template. I'm embedded in "one".
I'm some template. I'm embedded in "one".
I'm some OTHER template.
I'm embedded in "one".
I'm the 3rd, including everything from "one": I'm some template. I'm embedded in "one".
It should be obvious now what the role of the template name is, and where it comes from.
It is used to render associated templates.
For instance:
tmpl := template.Must(template.New("body").Parse(`
{{ define "body" }}
Body
{{ end }}
`))
tmpl = template.Must(tmpl.New("base").Parse(`
Start of base template
{{ template "body" }}
End of base template
`))
tmpl = template.Must(tmpl.New("baz").Parse(`
Start of baz template
{{ template "body" }}
End of baz template
`))
tmpl.ExecuteTemplate(os.Stdout, "base", nil)
tmpl.ExecuteTemplate(os.Stdout, "baz", nil)
Play Example
Output:
Start of base template
Body
End of base template
Start of baz template
Body
End of baz template
tmpl.ExecuteTemplate(os.Stdout, "base", nil) will render the template using the "base" template
tmpl.ExecuteTemplate(os.Stdout, "baz", nil) will render the template using the "baz" template
If you don't need the name, you can just use the new builtin with
template.Template:
package main
import (
"os"
"text/template"
)
func main() {
t, err := new(template.Template).Parse("hello {{.}}\n")
if err != nil {
panic(err)
}
t.Execute(os.Stdout, "world")
}

How do I extract a string from an interface{} variable in Go?

I'm new to the Go language.
I'm making a small web application with Go, the Gorilla toolkit, and the Mustache template engine.
Everything works great so far.
I use hoisie/mustache and gorilla/sessions, but I'm struggling with passing variables from one to the other. I have a map[string]interface{} that I pass to the template engine. When a user is logged in, I want to take the user's session data and merge it with my map[string]interface{} so that the data becomes available for rendering.
The problem is that gorilla/sessions returns a map[interface{}]interface{} so the merge cannot be done (with the skills I have in this language).
I thought about extracting the string inside the interface{} variable (reflection?).
I also thought about making my session data a map[interface{}]interface{} just like what gorilla/sessions provides. But I'm new to Go and I don't know if that can be considered best practice. As a Java guy, I feel like working with variables of type Object.
I would like to know the best approach for this problem in your opinion.
Thanks in advance.
You'll need to perform type assertions: specifically this section of Effective Go.
str, ok := value.(string)
if ok {
fmt.Printf("string value is: %q\n", str)
} else {
fmt.Printf("value is not a string\n")
}
A more precise example given what you're trying to do:
if userID, ok := session.Values["userID"].(string); ok {
// User ID is set
} else {
// User ID is not set/wrong type; raise an error/HTTP 500/re-direct
}
type M map[string]interface{}
err := t.ExecuteTemplate(w, "user_form.tmpl", M{"current_user": userID})
if err != nil {
// handle it
}
What you're doing is ensuring that the userID you pull out of the interface{} container is actually a string. If it's not, you handle it (if you don't, you'll program will panic as per the docs).
If it is, you pass it to your template where you can access it as {{ .current_user }}. M is a quick shortcut that I use to avoid having to type out map[string]interface{} every time I call my template rendering function.

Resources