Files
Cyrene_v2/internal/config/validate.go
T

98 lines
2.1 KiB
Go

package config
import (
"fmt"
"reflect"
"strings"
)
// validateStruct returns a list of human-readable validation errors found while
// walking the struct tree. Validation is driven by the `validate` struct tag,
// which holds comma-separated rules. Supported rules: required, min=N.
func validateStruct(sv reflect.Value) []error {
var errs []error
st := sv.Type()
for i := 0; i < st.NumField(); i++ {
field := st.Field(i)
if !field.IsExported() {
continue
}
fv := sv.Field(i)
if rules := field.Tag.Get("validate"); rules != "" {
name := envTagName(field.Tag.Get("env"))
if name == "" {
name = field.Name
}
errs = append(errs, applyRules(name, rules, fv)...)
}
if fv.Kind() == reflect.Struct {
errs = append(errs, validateStruct(fv)...)
}
}
return errs
}
func applyRules(name, rules string, fv reflect.Value) []error {
var errs []error
for _, rule := range strings.Split(rules, ",") {
rule = strings.TrimSpace(rule)
if rule == "" {
continue
}
ruleName, arg, _ := strings.Cut(rule, "=")
switch ruleName {
case "required":
if isZero(fv) {
errs = append(errs, fmt.Errorf("%s is required", name))
}
case "min":
if err := applyMin(name, arg, fv); err != nil {
errs = append(errs, err)
}
}
}
return errs
}
func isZero(fv reflect.Value) bool {
switch fv.Kind() {
case reflect.String:
return fv.String() == ""
case reflect.Bool:
return !fv.Bool()
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return fv.Int() == 0
default:
return fv.IsZero()
}
}
func applyMin(name, arg string, fv reflect.Value) error {
if fv.Kind() != reflect.String {
return nil
}
min, err := parseIntArg(arg)
if err != nil {
return nil
}
if len(fv.String()) < min {
return fmt.Errorf("%s must be at least %d characters", name, min)
}
return nil
}
func parseIntArg(arg string) (int, error) {
if arg == "" {
return 0, fmt.Errorf("missing min argument")
}
n := 0
for _, c := range arg {
if c < '0' || c > '9' {
return 0, fmt.Errorf("invalid min argument %q", arg)
}
n = n*10 + int(c-'0')
}
return n, nil
}