Go

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

All 35 Standard Library, HTTP & Systems Design in Go questions