package scriptopts import ( "fmt" "math" "strconv" "strings" ) // Value types for Option.ValueType. const ( TypeBool = "bool" TypeInt = "int" TypeFloat = "float" TypeString = "string" TypeChoice = "choice" ) // Option describes a curated, editable script-opts conf option. type Option struct { Key string Default string Description string ValueType string // "bool" | "int" | "float" | "string" | "choice" Choices []string // for ValueType == "choice" Min, Max float64 // bounds for int/float } // ValidateValue checks value against the type and constraints of the known // option key in spec. Unknown keys and values containing newlines are rejected. func ValidateValue(spec *Spec, key, value string) error { opt, ok := spec.LookupOption(key) if !ok { return fmt.Errorf("unknown %s option %q", spec.Name, key) } if strings.ContainsAny(value, "\n\r") { return fmt.Errorf("invalid value for %q: must not contain newline characters", key) } switch opt.ValueType { case TypeBool: if value != "yes" && value != "no" { return fmt.Errorf("invalid value %q for %q: must be \"yes\" or \"no\"", value, key) } case TypeInt: n, err := strconv.Atoi(value) if err != nil { return fmt.Errorf("invalid value %q for %q: must be an integer", value, key) } if err := checkBounds(key, value, float64(n), opt); err != nil { return err } case TypeFloat: f, err := strconv.ParseFloat(value, 64) if err != nil || math.IsNaN(f) || math.IsInf(f, 0) { return fmt.Errorf("invalid value %q for %q: must be a number", value, key) } if err := checkBounds(key, value, f, opt); err != nil { return err } case TypeChoice: for _, choice := range opt.Choices { if value == choice { return nil } } return fmt.Errorf("invalid value %q for %q: must be one of %s", value, key, strings.Join(opt.Choices, ", ")) case TypeString: // Any single-line value is accepted. default: return fmt.Errorf("option %q has unsupported value type %q", key, opt.ValueType) } return nil } // checkBounds enforces Min/Max when the option declares bounds. func checkBounds(key, value string, f float64, opt Option) error { if opt.Min == 0 && opt.Max == 0 { return nil } if f < opt.Min || f > opt.Max { return fmt.Errorf("value %q for %q is out of range (min %v, max %v)", value, key, opt.Min, opt.Max) } return nil }