Use reflection to build a simple struct-tag validator (`validate:"required,max=10"`).
Question 467HardGo 1.22 to 1.25
This tests walking struct fields, reading tags, handling pointers and Kinds, and producing errors with errors.Join.
func Validate(s any) error {
v := reflect.ValueOf(s)
for v.Kind() == reflect.Pointer {
if v.IsNil() {
return errors.New("nil value")
}
v = v.Elem()
}
if v.Kind() != reflect.Struct {
return fmt.Errorf("expected struct, got %s", v.Kind())
}
t := v.Type()
var errs []error
for i := range t.NumField() { // range-over-int (Go 1.22)
f := t.Field(i)
if !f.IsExported() {
continue
}
tag := f.Tag.Get("validate")
if tag == "" {
continue
}
fv := v.Field(i)
for rule := range strings.SplitSeq(tag, ",") { // Go 1.24 iterator
name, arg, _ := strings.Cut(rule, "=")
switch name {
case "required":
if fv.IsZero() {
errs = append(errs, fmt.Errorf("%s is required", f.Name))
}
case "max":
limit, err := strconv.Atoi(arg)
if err != nil {
return fmt.Errorf("bad max on %s: %w", f.Name, err)
}
switch fv.Kind() {
case reflect.String, reflect.Slice, reflect.Map:
if fv.Len() > limit {
errs = append(errs, fmt.Errorf("%s longer than %d", f.Name, limit))
}
case reflect.Int, reflect.Int32, reflect.Int64:
if fv.Int() > int64(limit) {
errs = append(errs, fmt.Errorf("%s greater than %d", f.Name, limit))
}
}
}
}
}
return errors.Join(errs...) // nil if errs is empty
}
type SignUp struct {
Name string `validate:"required,max=10"`
Age int `validate:"max=150"`
}
Production-grade versions cache per-type metadata (sync.Map keyed by reflect.Type) so reflection over tags happens once, as encoding/json does.
More on Standard Library, HTTP & Systems Design in Go
- Q465What does this print? (time.Duration arithmetic gotchas)
- Q466Explain the laws of reflection and settability. What does this print?
- Q468Design a per-client rate limiter for an HTTP API. Implement a token bucket.
- Q469Design a concurrency-safe, generic LRU cache with TTL.
- Q470What is a cache stampede and how does singleflight solve it?
- Q471Design an in-process pub/sub broker. How do you handle slow subscribers?