Go provides great build in HTML templating functionality, however for me it is important that I can ensure certain fields will always be available to templates in my application. A good example of this is a title field, which needs to be shown on every HTML page.
Given the following pages:
Home
Register
Contact
I would likely create the following objects for the templating system:
HomePage
struct
RegisterPage
struct
ContactPage
struct
Is there a recommended way to ensure that the structs for each page have certain fields available to them?
Ideally I would implement this through Polymorphism, but that isn't officially supported in Go. The alternative, embedding doesn't appear to have any enforcement, i.e all of the child structs can embed a parent struct but don't have to.
Let me know if I haven't expressed my question clearly enough.
Executing a template does not enforce anything to the parameters, Template.Execute()
accepts a value of type interface{}
.
You are the one creating the HomePage
, RegisterPage
and ContactPage
structs. What stops you from embedding a BasePage
struct with the required fields? Are you worried you will forget about it? You will notice it at the first testing, I wouldn't worry about that:
type BasePage struct {
Title string
Other string
// other required fields...
}
type HomePage struct {
BasePage
// other home page fields...
}
type RegisterPage struct {
BasePage
// other register page fields...
}
If you want from code to check if page structs embed the BasePage
, I recommend another way: interfaces.
type HasBasePage interface {
GetBasePage() BasePage
}
Example HomePage
that implements it:
type HomePage struct {
BasePage
// other home page fields...
}
func (h *HomePage) GetBasePage() BasePage {
return h.BasePage
}
Now obviously only pages that have a GetBasePage()
method can be passed as a value of HasBasePage
:
var page HasBasePage = &HomePage{} // Valid, HomePage implements HasBasePage
If you don't want to use interfaces, you can use the reflect
package to check if a value is a struct value and if it embeds another interface. Embedded structs appear and can be accessed like ordinary fields e.g. with Value.FieldByName()
, with the type name being the field name.
Example code using reflect
to check if a value embeds BasePage
:
page := &HomePage{BasePage: BasePage{Title: "Home page"}}
v := reflect.ValueOf(page)
if v.Kind() == reflect.Ptr {
v = v.Elem()
}
if v.Kind() != reflect.Struct {
fmt.Println("Error: not struct!")
return
}
bptype := reflect.TypeOf(BasePage{})
bp := v.FieldByName(bptype.Name()) // "BasePage"
if !bp.IsValid() || bp.Type() != bptype {
fmt.Println("Error: struct does not embed BasePage!")
return
}
fmt.Printf("%+v", bp)
Output (try it on the Go Playground):
{Title:Home page Other:}