tags
packageAPI reference for the tags
package.
Imports
(3)Parser
Parser provides generic struct tag parsing with configurable syntax.
type Parser struct
Example
p := tags.NewParser("my-tag", tags.WithPairDelimiter(";"))
data := p.Parse("key:val; option:a,b")
Methods
Parse extracts key-value pairs from a tag string.
Parameters
Returns
func (*Parser) Parse(tag string) map[string][]string
{
result := make(map[string][]string)
for part := range strings.SplitSeq(tag, p.pairDelimiter) {
part = strings.TrimSpace(part)
if part == "" {
continue
}
key, value, found := strings.Cut(part, p.kvSeparator)
key = strings.TrimSpace(key)
if !found {
result[key] = nil
continue
}
value = strings.TrimSpace(value)
var values []string
for v := range strings.SplitSeq(value, p.valueDelim) {
values = append(values, strings.TrimSpace(v))
}
result[key] = values
}
return result
}
ParseStruct extracts tag metadata from all fields of a struct.
Parameters
Returns
func (*Parser) ParseStruct(v any) []FieldMeta
{
val := reflect.ValueOf(v)
if val.Kind() == reflect.Ptr {
val = val.Elem()
}
if val.Kind() != reflect.Struct {
return nil
}
typ := val.Type()
var fields []FieldMeta
for i := 0; i < typ.NumField(); i++ {
field := typ.Field(i)
tag := field.Tag.Get(p.tagName)
if tag == "" {
continue
}
fields = append(fields, FieldMeta{
Name: field.Name,
Index: i,
Type: field.Type,
Tags: p.Parse(tag),
RawTag: tag,
IsExported: field.IsExported(),
})
}
return fields
}
ParseType extracts tag metadata from a reflect.Type.
Parameters
Returns
func (*Parser) ParseType(typ reflect.Type) []FieldMeta
{
if typ.Kind() == reflect.Ptr {
typ = typ.Elem()
}
if typ.Kind() != reflect.Struct {
return nil
}
var fields []FieldMeta
for i := 0; i < typ.NumField(); i++ {
field := typ.Field(i)
tag := field.Tag.Get(p.tagName)
if tag == "" {
continue
}
fields = append(fields, FieldMeta{
Name: field.Name,
Index: i,
Type: field.Type,
Tags: p.Parse(tag),
RawTag: tag,
IsExported: field.IsExported(),
})
}
return fields
}
Fields
| Name | Type | Description |
|---|---|---|
| tagName | string | |
| pairDelimiter | string | |
| kvSeparator | string | |
| valueDelim | string |
Option
Option configures a Parser.
type Option func(*Parser)
WithPairDelimiter
WithPairDelimiter sets the delimiter between key:value pairs (default “;”).
Parameters
Returns
func WithPairDelimiter(d string) Option
{
return func(p *Parser) { p.pairDelimiter = d }
}
Uses
WithKVSeparator
WithKVSeparator sets the key-value separator (default “:”).
Parameters
Returns
func WithKVSeparator(s string) Option
{
return func(p *Parser) { p.kvSeparator = s }
}
Uses
WithValueDelimiter
WithValueDelimiter sets the delimiter for multiple values (default “,”).
Parameters
Returns
func WithValueDelimiter(d string) Option
{
return func(p *Parser) { p.valueDelim = d }
}
Uses
NewParser
NewParser creates a Parser for the given tag name.
Parameters
Returns
func NewParser(tagName string, opts ...Option) *Parser
{
p := &Parser{
tagName: tagName,
pairDelimiter: ";",
kvSeparator: ":",
valueDelim: ",",
}
for _, opt := range opts {
opt(p)
}
return p
}
FieldMeta
FieldMeta holds parsed tag metadata for a struct field.
type FieldMeta struct
Methods
Get returns the first value for a key, or empty string if not found.
Parameters
Returns
func (FieldMeta) Get(key string) string
{
if vals, ok := m.Tags[key]; ok && len(vals) > 0 {
return vals[0]
}
return ""
}
GetAll returns all values for a key.
Parameters
Returns
func (FieldMeta) GetAll(key string) []string
{
return m.Tags[key]
}
Has checks if a key exists in the tag.
Parameters
Returns
func (FieldMeta) Has(key string) bool
{
_, ok := m.Tags[key]
return ok
}
Fields
| Name | Type | Description |
|---|---|---|
| Name | string | |
| Index | int | |
| Type | reflect.Type | |
| Tags | map[string][]string | |
| RawTag | string | |
| IsExported | bool |
TestParser_Parse
Parameters
func TestParser_Parse(t *testing.T)
{
tests := []struct {
name string
tagName string
opts []Option
input string
expected map[string][]string
}{
{
name: "guard style",
tagName: "guard",
input: "role:owner; read:admin,user; delete:admin",
expected: map[string][]string{
"role": {"owner"},
"read": {"admin", "user"},
"delete": {"admin"},
},
},
{
name: "fsm style",
tagName: "fsm",
input: "initial:draft; draft->paid; paid->shipped",
expected: map[string][]string{
"initial": {"draft"},
"draft->paid": nil,
"paid->shipped": nil,
},
},
{
name: "conf style",
tagName: "conf",
opts: []Option{WithPairDelimiter(",")},
input: "env:PORT,flag:port,default:8080",
expected: map[string][]string{
"env": {"PORT"},
"flag": {"port"},
"default": {"8080"},
},
},
{
name: "empty tag",
tagName: "test",
input: "",
expected: map[string][]string{},
},
{
name: "key without value",
tagName: "test",
input: "required; optional",
expected: map[string][]string{
"required": nil,
"optional": nil,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
p := NewParser(tt.tagName, tt.opts...)
result := p.Parse(tt.input)
if len(result) != len(tt.expected) {
t.Errorf("got %d keys, want %d", len(result), len(tt.expected))
}
for k, want := range tt.expected {
got, ok := result[k]
if !ok {
t.Errorf("missing key %q", k)
continue
}
if len(got) != len(want) {
t.Errorf("key %q: got %v, want %v", k, got, want)
}
for i := range want {
if i >= len(got) || got[i] != want[i] {
t.Errorf("key %q value %d: got %q, want %q", k, i, got[i], want[i])
}
}
}
})
}
}
TestParser_ParseStruct
Parameters
func TestParser_ParseStruct(t *testing.T)
{
type TestStruct struct {
Untagged string
Role string `guard:"role:owner"`
Perms string `guard:"read:admin,user; write:owner"`
}
p := NewParser("guard")
fields := p.ParseStruct(&TestStruct{})
if len(fields) != 2 {
t.Fatalf("got %d fields, want 2", len(fields))
}
if fields[0].Name != "Role" {
t.Errorf("first field name: got %q, want %q", fields[0].Name, "Role")
}
if fields[0].Get("role") != "owner" {
t.Errorf("Role field 'role' tag: got %q, want %q", fields[0].Get("role"), "owner")
}
if !fields[1].Has("read") {
t.Error("Perms field should have 'read' key")
}
perms := fields[1].GetAll("read")
if len(perms) != 2 || perms[0] != "admin" || perms[1] != "user" {
t.Errorf("Perms 'read' values: got %v, want [admin user]", perms)
}
}
TestParser_ParseType
Parameters
func TestParser_ParseType(t *testing.T)
{
type TestStruct struct {
Status string `fsm:"initial:draft; draft->paid"`
}
p := NewParser("fsm")
fields := p.ParseType(reflect.TypeOf(TestStruct{}))
if len(fields) != 1 {
t.Fatalf("got %d fields, want 1", len(fields))
}
if fields[0].Get("initial") != "draft" {
t.Errorf("initial: got %q, want %q", fields[0].Get("initial"), "draft")
}
}
TestFieldMeta_Methods
Parameters
func TestFieldMeta_Methods(t *testing.T)
{
meta := FieldMeta{
Tags: map[string][]string{
"single": {"value"},
"multiple": {"a", "b", "c"},
"empty": nil,
},
}
if meta.Get("single") != "value" {
t.Errorf("Get single: got %q, want %q", meta.Get("single"), "value")
}
if meta.Get("missing") != "" {
t.Errorf("Get missing: got %q, want empty", meta.Get("missing"))
}
all := meta.GetAll("multiple")
if len(all) != 3 {
t.Errorf("GetAll multiple: got %d values, want 3", len(all))
}
if !meta.Has("empty") {
t.Error("Has empty: should be true")
}
if meta.Has("missing") {
t.Error("Has missing: should be false")
}
}