package wand import ( "fmt" "reflect" "slices" ) func validateKeyValues(cmd Cmd, keyValues map[string][]string, originalNames map[string]string) error { mf := mapFields(cmd.impl) for name, values := range keyValues { f, ok := mf[name] if !ok { continue } for _, fi := range f { if len(values) > 1 && !fi.acceptsMultiple { return fmt.Errorf( "expected only one value, received %d, as environment value, %s", len(values), originalNames[name], ) } for _, v := range values { if !canScan(fi.typ, v) { return fmt.Errorf( "environment variable cannot be applied, type mismatch: %s", originalNames[name], ) } } } } return nil } func validateConfig(cmd Cmd, c config) error { return validateKeyValues(cmd, c.values, c.originalNames) } func validateEnv(cmd Cmd, e env) error { return validateKeyValues(cmd, e.values, e.originalNames) } func validateOptions(cmd Cmd, o []option, conf Config) error { ml := make(map[string]string) ms := make(map[string]string) for i := 0; i < len(cmd.shortForms); i += 2 { l, s := cmd.shortForms[i], cmd.shortForms[i+1] ml[l] = s ms[s] = l } mo := make(map[string][]option) for _, oi := range o { n := oi.name if ln, ok := ms[n]; ok && oi.shortForm { n = ln } mo[n] = append(mo[n], oi) } mf := mapFields(cmd.impl) if hasConfigFromOption(conf) { mf["config"] = []field{{ acceptsMultiple: true, typ: reflect.TypeFor[string](), }} } for n, os := range mo { en := "--" + n if sn, ok := ml[n]; ok { en += ", -" + sn } f := mf[n] if len(f) == 0 { return fmt.Errorf("option not supported: %s", en) } for _, fi := range f { if len(os) > 1 && !fi.acceptsMultiple { return fmt.Errorf( "expected only one value, received %d, as option, %s", len(os), en, ) } for _, oi := range os { if oi.value.isBool && fi.typ.Kind() != reflect.Bool { return fmt.Errorf( "received boolean value for field that does not accept it: %s", en, ) } if !oi.value.isBool && !canScan(fi.typ, oi.value.str) { return fmt.Errorf( "option cannot be applied, type mismatch: %s", en, ) } } } } return nil } func validatePositionalArgs(cmd Cmd, a []string) error { v := reflect.ValueOf(cmd.impl) v = unpack(v) t := v.Type() p := positionalParameters(t) ior, iow := ioParameters(p) last := t.NumIn() - 1 lastVariadic := t.IsVariadic() && !isStruct(t.In(last)) && !slices.Contains(ior, last) && !slices.Contains(iow, last) length := len(p) - len(ior) - len(iow) min := length max := length if lastVariadic { min-- max = -1 } if cmd.minPositional > min { min = cmd.minPositional } if cmd.maxPositional > 0 { max = cmd.maxPositional } if len(a) < min { return fmt.Errorf("not enough positional arguments, expected minimum %d", min) } if max >= 0 && len(a) > max { return fmt.Errorf("too many positional arguments, expected maximum %d", max) } for i, ai := range a { var pi reflect.Type if i >= length { pi = p[length-1] } else { pi = p[i] } if !canScan(pi, ai) { return fmt.Errorf( "cannot apply positional argument at index %d, expecting %v", i, pi, ) } } return nil } func validateInput(cmd Cmd, conf Config, c config, e env, cl commandLine) error { if err := validateConfig(cmd, c); err != nil { return err } if err := validateEnv(cmd, e); err != nil { return err } if err := validateOptions(cmd, cl.options, conf); err != nil { return err } if err := validatePositionalArgs(cmd, cl.positional); err != nil { return err } return nil }