go-sprout / sprout

From sprig to sprout - Useful template functions for Go templates with steroids
https://sprout.atom.codes
MIT License
86 stars 4 forks source link

proposal: `hasField` function for use with structs #60

Closed mbezhanov closed 3 weeks ago

mbezhanov commented 3 weeks ago

You have a proposal, explain it!

Originally proposed here: https://github.com/Masterminds/sprig/issues/401

I recently worked on a project that used generics in a similar fashion:

type A[T any] struct {
    Metadata T
}

type B struct {
    Foo string
}
type C struct {
    Bar string
}

Then, similar objects were being passed to a template for rendering:

ab := &A[B]{Metadata: B{Foo: "Lorem"}}
ac := &A[C]{Metadata: C{Bar: "Ipsum"}}

A section in the template had to look slightly different depending on the type of metadata being passed in, which I solved by using a custom hasField method:

{{if hasField $.Metadata "Foo"}}
  We have Foo.
{{else}}
  We have no Foo.
{{end}}
Full code example ```go package main import ( "html/template" "log" "os" "reflect" ) type A[T any] struct { Metadata T } type B struct { Foo string } type C struct { Bar string } const tmpl = ` {{if hasField $.Metadata "Foo"}} We have Foo. {{else}} We have no Foo. {{end}}` func main() { f := template.FuncMap{"hasField": hasField} t := template.Must( template.New("base").Funcs(f).Parse(tmpl), ) ab := &A[B]{Metadata: B{Foo: "Lorem"}} ac := &A[C]{Metadata: C{Bar: "Ipsum"}} if err := t.Execute(os.Stdout, ab); err != nil { log.Fatal(err) } if err := t.Execute(os.Stdout, ac); err != nil { log.Fatal(err) } } func hasField(v interface{}, name string) bool { rv := reflect.ValueOf(v) if rv.Kind() == reflect.Ptr { rv = rv.Elem() } if rv.Kind() != reflect.Struct { return false } return rv.FieldByName(name).IsValid() } ```

Describe the solution you'd like

I took the hasField method implementation from the following StackOverflow thread: Field detection in Go HTML template.

It looks like this:

func hasField(v interface{}, name string) bool {
    rv := reflect.ValueOf(v)
    if rv.Kind() == reflect.Ptr {
        rv = rv.Elem()
    }
    if rv.Kind() != reflect.Struct {
        return false
    }
    return rv.FieldByName(name).IsValid()
}

I feel something similar can be added to the reflect registry, so it can be used with Sprout out of the box.

I'd be happy to open a PR!

Additional context

No response

Code of Conduct

42atomys commented 3 weeks ago

Hello @mbezhanov thanks for your proposal and your PR I will take a look soon