2025-08-18 14:24:31 +02:00
|
|
|
package wand
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"fmt"
|
|
|
|
|
"reflect"
|
|
|
|
|
"slices"
|
|
|
|
|
)
|
|
|
|
|
|
2025-08-24 01:45:25 +02:00
|
|
|
func validateKeyValues(cmd Cmd, keyValues map[string][]string, originalNames map[string]string) error {
|
2025-08-18 14:24:31 +02:00
|
|
|
mf := mapFields(cmd.impl)
|
2025-08-24 01:45:25 +02:00
|
|
|
for name, values := range keyValues {
|
2025-08-18 14:24:31 +02:00
|
|
|
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),
|
2025-08-24 01:45:25 +02:00
|
|
|
originalNames[name],
|
2025-08-18 14:24:31 +02:00
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for _, v := range values {
|
|
|
|
|
if !canScan(fi.typ, v) {
|
|
|
|
|
return fmt.Errorf(
|
|
|
|
|
"environment variable cannot be applied, type mismatch: %s",
|
2025-08-24 01:45:25 +02:00
|
|
|
originalNames[name],
|
2025-08-18 14:24:31 +02:00
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-24 01:45:25 +02:00
|
|
|
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 {
|
2025-08-18 14:24:31 +02:00
|
|
|
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)
|
2025-08-24 01:45:25 +02:00
|
|
|
if hasConfigFromOption(conf) {
|
|
|
|
|
mf["config"] = []field{{
|
|
|
|
|
acceptsMultiple: true,
|
|
|
|
|
typ: reflect.TypeFor[string](),
|
|
|
|
|
}}
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-18 14:24:31 +02:00
|
|
|
for n, os := range mo {
|
2025-08-24 01:45:25 +02:00
|
|
|
en := "--" + n
|
|
|
|
|
if sn, ok := ml[n]; ok {
|
|
|
|
|
en += ", -" + sn
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-18 14:24:31 +02:00
|
|
|
f := mf[n]
|
2025-08-24 01:45:25 +02:00
|
|
|
if len(f) == 0 {
|
|
|
|
|
return fmt.Errorf("option not supported: %s", en)
|
|
|
|
|
}
|
2025-08-18 14:24:31 +02:00
|
|
|
|
2025-08-24 01:45:25 +02:00
|
|
|
for _, fi := range f {
|
2025-08-18 14:24:31 +02:00
|
|
|
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)
|
2025-08-24 01:45:25 +02:00
|
|
|
last := t.NumIn() - 1
|
2025-08-18 14:24:31 +02:00
|
|
|
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
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-24 01:45:25 +02:00
|
|
|
func validateInput(cmd Cmd, conf Config, c config, e env, cl commandLine) error {
|
|
|
|
|
if err := validateConfig(cmd, c); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-18 14:24:31 +02:00
|
|
|
if err := validateEnv(cmd, e); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-24 01:45:25 +02:00
|
|
|
if err := validateOptions(cmd, cl.options, conf); err != nil {
|
2025-08-18 14:24:31 +02:00
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if err := validatePositionalArgs(cmd, cl.positional); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
}
|