Documentation
¶
Overview ¶
Package structwalker provides recursive struct walking with tag-based handler dispatch.
Register handlers for struct tag keys, then walk any struct. Each field's tags are parsed, matched to registered handlers, and dispatched with full context including the parsed tag, field value, dot-separated path, and parent struct.
Reflection metadata is cached per-type so repeated walks skip parsing entirely.
Basic usage:
w := structwalker.New()
w.Handle("json", func(ctx structwalker.FieldContext) error {
fmt.Println(ctx.Path, ctx.Tag.Name)
return nil
})
err := w.Walk(&myStruct)
Index ¶
- Variables
- type ErrorMode
- type FieldContext
- type HandlerFunc
- type HandlerOption
- type Middleware
- type MultiError
- type Phase
- type Tag
- type WalkError
- type Walker
- type WalkerOption
- func WithContext(ctx context.Context) WalkerOption
- func WithErrorMode(mode ErrorMode) WalkerOption
- func WithFollowPointers(b bool) WalkerOption
- func WithMapEntries(b bool) WalkerOption
- func WithMaxDepth(n int) WalkerOption
- func WithSliceElements(b bool) WalkerOption
- func WithTagParser(fn func(key, raw string) Tag) WalkerOption
- func WithUnexported(b bool) WalkerOption
Examples ¶
Constants ¶
This section is empty.
Variables ¶
var ( // ErrSkipField causes the walker to skip remaining handlers on the current field. ErrSkipField = errors.New("skip field") // ErrSkipChildren prevents recursion into a nested struct field. ErrSkipChildren = errors.New("skip children") )
Functions ¶
This section is empty.
Types ¶
type FieldContext ¶
type FieldContext struct {
Tag Tag
AllTags []Tag
Field reflect.StructField
Value reflect.Value
Path string
Depth int
Parent reflect.Value
Index int
Walker *Walker
}
FieldContext is passed to every handler invocation with full field metadata.
func (FieldContext) IsExported ¶
func (fc FieldContext) IsExported() bool
IsExported reports whether the field is exported.
func (FieldContext) IsNil ¶
func (fc FieldContext) IsNil() bool
IsNil reports whether the field is a nil pointer, slice, map, or interface.
func (FieldContext) IsZero ¶
func (fc FieldContext) IsZero() bool
IsZero reports whether the field holds the zero value for its type.
type HandlerFunc ¶
type HandlerFunc func(ctx FieldContext) error
HandlerFunc is the callback signature for tag processors.
type HandlerOption ¶
type HandlerOption func(*handlerConfig)
HandlerOption configures individual handler behavior.
func WithFilter ¶
func WithFilter(fn func(FieldContext) bool) HandlerOption
WithFilter restricts the handler to fields where fn returns true.
func WithPhase ¶
func WithPhase(phase Phase) HandlerOption
WithPhase sets when the handler runs relative to child processing.
func WithPriority ¶
func WithPriority(p int) HandlerOption
WithPriority sets execution order (lower runs first). Default: 100.
type Middleware ¶
type Middleware func(ctx FieldContext, next HandlerFunc) error
Middleware wraps handler execution for cross-cutting concerns.
func WithLogging ¶
func WithLogging(logger *slog.Logger) Middleware
WithLogging returns middleware that logs field visits at debug level.
func WithRecovery ¶
func WithRecovery() Middleware
WithRecovery returns middleware that converts handler panics into errors.
Example ¶
package main
import (
"fmt"
"github.com/rjp2525/structwalker"
)
func main() {
type S struct {
Name string `tag:"name"`
}
w := structwalker.New()
w.Use(structwalker.WithRecovery())
w.Handle("tag", func(ctx structwalker.FieldContext) error {
panic("something went wrong")
})
err := w.Walk(&S{Name: "test"})
fmt.Println("recovered:", err != nil)
}
Output: recovered: true
func WithTiming ¶
func WithTiming(fn func(path string, tagKey string, duration time.Duration)) Middleware
WithTiming returns middleware that records handler execution duration.
type MultiError ¶
type MultiError struct {
Errors []*WalkError
}
MultiError collects multiple errors from a walk (used with CollectErrors mode).
func (*MultiError) Error ¶
func (e *MultiError) Error() string
func (*MultiError) Unwrap ¶
func (e *MultiError) Unwrap() []error
type Phase ¶
type Phase int
Phase determines when a handler runs relative to child field processing.
type Tag ¶
Tag represents a single parsed struct tag value. For example, `scan:"email,redact,max=255"` parses to: Key="scan", Name="email", Flags=["redact"], Options={"max":"255"}.
func ParseAllTags ¶
ParseAllTags extracts all tags from a reflect.StructTag.
func (Tag) IsIgnored ¶
IsIgnored reports whether the tag name is "-", the conventional ignore marker.
type Walker ¶
type Walker struct {
// contains filtered or unexported fields
}
Walker is the entry point. Register handlers for tag keys, then Walk any struct.
Example (BasicUsage) ¶
package main
import (
"fmt"
"github.com/rjp2525/structwalker"
)
func main() {
type User struct {
Name string `json:"name" validate:"required"`
Email string `json:"email" validate:"email"`
Age int `json:"age" validate:"min=0"`
}
w := structwalker.New()
w.Handle("json", func(ctx structwalker.FieldContext) error {
fmt.Printf("json field: %s -> %s\n", ctx.Path, ctx.Tag.Name)
return nil
})
w.Handle("validate", func(ctx structwalker.FieldContext) error {
fmt.Printf("validate: %s rule=%s\n", ctx.Path, ctx.Tag.Name)
return nil
})
user := &User{Name: "Alice", Email: "alice@example.com", Age: 30}
if err := w.Walk(user); err != nil {
fmt.Println("error:", err)
}
}
Output: json field: Name -> name validate: Name rule=required json field: Email -> email validate: Email rule=email json field: Age -> age validate: Age rule=min=0
Example (ErrorCollection) ¶
package main
import (
"fmt"
"github.com/rjp2525/structwalker"
)
func main() {
type Form struct {
Name string `validate:"required"`
Email string `validate:"required"`
Age int `validate:"required"`
}
w := structwalker.New(structwalker.WithErrorMode(structwalker.CollectErrors))
w.Handle("validate", func(ctx structwalker.FieldContext) error {
if ctx.Tag.Name == "required" && ctx.IsZero() {
return fmt.Errorf("field is required")
}
return nil
})
form := &Form{} // all zero values
err := w.Walk(form)
if err != nil {
fmt.Println("Validation failed:")
var multi *structwalker.MultiError
if ok := err.(*structwalker.MultiError); ok != nil {
multi = ok
for _, e := range multi.Errors {
fmt.Printf(" - %s: %s\n", e.Path, e.Err)
}
}
}
}
Output: Validation failed: - Name: field is required - Email: field is required - Age: field is required
Example (HandleAll) ¶
package main
import (
"fmt"
"strings"
"github.com/rjp2525/structwalker"
)
func main() {
type Config struct {
Host string
Port int
Name string
}
var fields []string
w := structwalker.New()
w.HandleAll(func(ctx structwalker.FieldContext) error {
fields = append(fields, ctx.Field.Name)
return nil
})
if err := w.Walk(&Config{Host: "localhost", Port: 8080, Name: "app"}); err != nil {
fmt.Println("error:", err)
}
fmt.Println(strings.Join(fields, ", "))
}
Output: Host, Port, Name
Example (NestedStructs) ¶
package main
import (
"fmt"
"github.com/rjp2525/structwalker"
)
func main() {
type Address struct {
Street string `json:"street"`
City string `json:"city"`
}
type User struct {
Name string `json:"name"`
Address Address `json:"address"`
}
w := structwalker.New()
w.Handle("json", func(ctx structwalker.FieldContext) error {
fmt.Printf("[depth=%d] %s\n", ctx.Depth, ctx.Path)
return nil
})
user := &User{Name: "Alice", Address: Address{Street: "123 Main St", City: "Springfield"}}
if err := w.Walk(user); err != nil {
fmt.Println("error:", err)
}
}
Output: [depth=0] Name [depth=0] Address [depth=1] Address.Street [depth=1] Address.City
Example (SensitiveDataMasking) ¶
package main
import (
"fmt"
"github.com/rjp2525/structwalker"
)
func main() {
type APIResponse struct {
UserID int `json:"user_id"`
Email string `json:"email" mask:"redact"`
SSN string `json:"ssn" mask:"redact"`
PublicKey string `json:"public_key"`
}
w := structwalker.New()
w.Handle("mask", func(ctx structwalker.FieldContext) error {
if ctx.Tag.Name == "redact" {
return ctx.SetValue("***REDACTED***")
}
return nil
})
resp := &APIResponse{
UserID: 123,
Email: "alice@example.com",
SSN: "123-45-6789",
PublicKey: "ssh-rsa AAAA...",
}
if err := w.Walk(resp); err != nil {
fmt.Println("error:", err)
}
fmt.Println("Email:", resp.Email)
fmt.Println("SSN:", resp.SSN)
fmt.Println("PublicKey:", resp.PublicKey)
}
Output: Email: ***REDACTED*** SSN: ***REDACTED*** PublicKey: ssh-rsa AAAA...
Example (SkipChildren) ¶
package main
import (
"fmt"
"github.com/rjp2525/structwalker"
)
func main() {
type Secrets struct {
APIKey string `json:"api_key"`
Token string `json:"token"`
}
type Config struct {
Name string `json:"name"`
Secrets Secrets `json:"secrets" audit:"skip"`
}
w := structwalker.New()
w.Handle("json", func(ctx structwalker.FieldContext) error {
fmt.Printf("visited: %s\n", ctx.Path)
return nil
})
w.Handle("audit", func(ctx structwalker.FieldContext) error {
if ctx.Tag.Name == "skip" {
return structwalker.ErrSkipChildren
}
return nil
})
if err := w.Walk(&Config{Name: "myapp", Secrets: Secrets{APIKey: "key", Token: "tok"}}); err != nil {
fmt.Println("error:", err)
}
}
Output: visited: Name visited: Secrets
Example (SliceWalking) ¶
package main
import (
"fmt"
"github.com/rjp2525/structwalker"
)
func main() {
type Item struct {
Name string `tag:"name"`
}
type Cart struct {
Items []Item `tag:"items"`
}
w := structwalker.New(structwalker.WithSliceElements(true))
w.Handle("tag", func(ctx structwalker.FieldContext) error {
fmt.Printf("%s\n", ctx.Path)
return nil
})
cart := &Cart{Items: []Item{{Name: "Widget"}, {Name: "Gadget"}}}
if err := w.Walk(cart); err != nil {
fmt.Println("error:", err)
}
}
Output: Items Items[0].Name Items[1].Name
func New ¶
func New(opts ...WalkerOption) *Walker
New creates a Walker configured with the given options.
func (*Walker) Handle ¶
func (w *Walker) Handle(tagKey string, fn HandlerFunc, opts ...HandlerOption)
Handle registers a handler for a specific tag key.
func (*Walker) HandleAll ¶
func (w *Walker) HandleAll(fn HandlerFunc, opts ...HandlerOption)
HandleAll registers a handler invoked for every field regardless of tags.
func (*Walker) Use ¶
func (w *Walker) Use(mw ...Middleware)
Use adds middleware to the walker's chain, executed in the order added.
type WalkerOption ¶
type WalkerOption func(*walkerOptions)
WalkerOption configures walker behavior via functional options.
func WithContext ¶
func WithContext(ctx context.Context) WalkerOption
WithContext attaches a context for cancellation support during walks.
func WithErrorMode ¶
func WithErrorMode(mode ErrorMode) WalkerOption
WithErrorMode sets the error handling strategy for the walk.
func WithFollowPointers ¶
func WithFollowPointers(b bool) WalkerOption
WithFollowPointers controls automatic pointer dereferencing (default: true).
func WithMapEntries ¶
func WithMapEntries(b bool) WalkerOption
WithMapEntries enables walking into map values.
func WithMaxDepth ¶
func WithMaxDepth(n int) WalkerOption
WithMaxDepth limits recursion depth (default: 32).
func WithSliceElements ¶
func WithSliceElements(b bool) WalkerOption
WithSliceElements enables walking into individual slice/array elements.
func WithTagParser ¶
func WithTagParser(fn func(key, raw string) Tag) WalkerOption
WithTagParser overrides the default CSV-based tag parsing logic.
func WithUnexported ¶
func WithUnexported(b bool) WalkerOption
WithUnexported includes unexported fields in the walk (read-only, not settable).