tags API

tags

package

API reference for the tags package.

S
struct

Parser

Parser provides generic struct tag parsing with configurable syntax.

pkg/tags/parser.go:14-19
type Parser struct

Example

p := tags.NewParser("my-tag", tags.WithPairDelimiter(";"))
data := p.Parse("key:val; option:a,b")

Methods

Parse
Method

Parse extracts key-value pairs from a tag string.

Parameters

tag string

Returns

map[string][]string
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
Method

ParseStruct extracts tag metadata from all fields of a struct.

Parameters

v any

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
Method

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
T
type

Option

Option configures a Parser.

pkg/tags/parser.go:22-22
type Option func(*Parser)
F
function

WithPairDelimiter

WithPairDelimiter sets the delimiter between key:value pairs (default “;”).

Parameters

d
string

Returns

pkg/tags/parser.go:25-27
func WithPairDelimiter(d string) Option

{
	return func(p *Parser) { p.pairDelimiter = d }
}
F
function

WithKVSeparator

WithKVSeparator sets the key-value separator (default “:”).

Parameters

s
string

Returns

pkg/tags/parser.go:30-32
func WithKVSeparator(s string) Option

{
	return func(p *Parser) { p.kvSeparator = s }
}
F
function

WithValueDelimiter

WithValueDelimiter sets the delimiter for multiple values (default “,”).

Parameters

d
string

Returns

pkg/tags/parser.go:35-37
func WithValueDelimiter(d string) Option

{
	return func(p *Parser) { p.valueDelim = d }
}
F
function

NewParser

NewParser creates a Parser for the given tag name.

Parameters

tagName
string
opts
...Option

Returns

pkg/tags/parser.go:40-51
func NewParser(tagName string, opts ...Option) *Parser

{
	p := &Parser{
		tagName:       tagName,
		pairDelimiter: ";",
		kvSeparator:   ":",
		valueDelim:    ",",
	}
	for _, opt := range opts {
		opt(p)
	}
	return p
}
S
struct

FieldMeta

FieldMeta holds parsed tag metadata for a struct field.

pkg/tags/parser.go:83-90
type FieldMeta struct

Methods

Get
Method

Get returns the first value for a key, or empty string if not found.

Parameters

key string

Returns

string
func (FieldMeta) Get(key string) string
{
	if vals, ok := m.Tags[key]; ok && len(vals) > 0 {
		return vals[0]
	}
	return ""
}
GetAll
Method

GetAll returns all values for a key.

Parameters

key string

Returns

[]string
func (FieldMeta) GetAll(key string) []string
{
	return m.Tags[key]
}
Has
Method

Has checks if a key exists in the tag.

Parameters

key string

Returns

bool
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
F
function

TestParser_Parse

Parameters

pkg/tags/parser_test.go:8-90
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])
					}
				}
			}
		})
	}
}
F
function

TestParser_ParseStruct

Parameters

pkg/tags/parser_test.go:92-122
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)
	}
}
F
function

TestParser_ParseType

Parameters

pkg/tags/parser_test.go:124-139
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")
	}
}
F
function

TestFieldMeta_Methods

Parameters

pkg/tags/parser_test.go:141-170
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")
	}
}