98 lines
2.1 KiB
Go
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
|
|
}
|