Skip to content

Commit

Permalink
Allow arbitrary nesting of struct tag values.
Browse files Browse the repository at this point in the history
This uses a pattern that leverages the idea that all of these values are
just maps, and an optional path can be provided to access a value at an
arbitrary nested level in the struct tags.

Why would you want to do this? I don't know, it's probably not a good
idea to go any further than one or two nestings.

However, it does make the signature a little nicer to work with as this
makes the second param optional.
  • Loading branch information
kevinfalting committed Jan 5, 2025
1 parent 09380c9 commit 8d329ed
Show file tree
Hide file tree
Showing 3 changed files with 35 additions and 31 deletions.
18 changes: 0 additions & 18 deletions stronf/field.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,24 +68,6 @@ func (f Field) set(val any) error {
return nil
}

// LookupTag will return the value associated with the key and optional tag. See
// examples for supported formats. The bool reports if the key or tag was
// explicitly found in the struct tag.
func (f Field) LookupTag(key, tag string) (string, bool) {
value, ok := f.rStructField.Tag.Lookup(key)
if !ok {
return "", false
}

if tag == "" {
return value, true
}

tags := parseStructTag(value)
val, ok := tags[tag]
return val, ok
}

// Parse will call the handler against the field and set the field to the
// returned handler value. If the handler returns nil, no change is made to the
// field.
Expand Down
6 changes: 3 additions & 3 deletions stronf/field_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ func TestField_LookupTag(t *testing.T) {
}

field := fields[0]
v, ok := field.LookupTag("key", "")
v, ok := field.LookupTag("key")
if !ok {
t.Error("expected key to exist")
}
Expand All @@ -74,7 +74,7 @@ func TestField_LookupTag(t *testing.T) {
}

field := fields[0]
v, ok := field.LookupTag("key", "")
v, ok := field.LookupTag("key")
if !ok {
t.Error("expected key to exist")
}
Expand Down Expand Up @@ -140,7 +140,7 @@ func ExampleField_LookupTag() {
log.Println("should not recieve a value for a key that doesn't exist")
}

if val, ok := field.LookupTag("emptyKey", ""); ok {
if val, ok := field.LookupTag("emptyKey"); ok {
fmt.Printf("val: %q\n", val)
}

Expand Down
42 changes: 32 additions & 10 deletions stronf/structtag.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,40 @@ package stronf

import "strings"

func parseStructTag(tag string) map[string]string {
options := make(map[string]string)
for _, option := range strings.Split(tag, ",") {
optionParts := strings.SplitN(option, ":", 2)
key := optionParts[0]
var value string
if len(optionParts) > 1 {
value = optionParts[1]
// LookupTag will return the value associated with the tag and optional path.
// The tag arg is the only required argument and uses the reflect package's
// Lookup semantics. An optional path can be provided to lookup nested values.
// Nested values can themselves be maps, each key/val pair separated by a comma.
// See examples for supported formats. The bool reports if the value was
// explicitly found at the struct tag path.
func (f Field) LookupTag(tag string, path ...string) (string, bool) {
value, ok := f.rStructField.Tag.Lookup(tag)
if !ok {
return "", false
}

if len(path) == 0 {
return value, true
}

parseStructTag := func(tag string) map[string]string {
subTags := make(map[string]string)
for _, subTag := range strings.Split(tag, ",") {
key, val, _ := strings.Cut(subTag, ":")
subTags[key] = val
}

options[key] = value
return subTags
}

val, ok := "", false
for _, p := range path {
tags := parseStructTag(value)
val, ok = tags[p]
if !ok {
return "", false
}
}

return options
return val, ok
}

0 comments on commit 8d329ed

Please sign in to comment.