Repository: go-go-golems/glazed
Pull request #346 (2023-09-09 19:38:50)

Add choiceList type
feat: Add choice list parameter type

This adds support for a new choiceList parameter type to allow multiple selections from a set of choices. 

To implement this in a clean way, the logic for parameter default value handling was refactored:

- Split out InitializeValueToEmptyValue to set empty value based on type 
- Created SetValueFromInterface to assign any value to reflect.Value
- Updated SetValueFromDefault to initialize value from default

Additional changes:

- Expanded parameter value validation tests
- Refactored parsing tests to use table-driven pattern
- Added overview documentation for ParameterDefinition

This provides a more flexible way to handle parameter default values while supporting the new choiceList type. Users can now select multiple options from a predefined choice list.

The refactor also lays groundwork for future improvements like polymorphic parsing and serialization.


Closes #346


Patch:
diff --git a/pkg/cmds/cobra.go b/pkg/cmds/cobra.go
deleted file mode 100644
index ce2fc16..0000000
--- a/pkg/cmds/cobra.go
+++ /dev/null
@@ -1,1 +0,0 @@
-package cmds
diff --git a/pkg/cmds/parameters/cobra.go b/pkg/cmds/parameters/cobra.go
index 4105dcf..d3f2f71 100644
--- a/pkg/cmds/parameters/cobra.go
+++ b/pkg/cmds/parameters/cobra.go
@@ -57,7 +57,11 @@ func AddArgumentsToCobraCommand(cmd *cobra.Command, arguments []*ParameterDefini
 // GatherArguments parses the positional arguments passed as a list of strings into a map of
 // parsed values. If onlyProvided is true, then only arguments that are provided are returned
 // (i.e. the default values are not included).
-func GatherArguments(args []string, arguments []*ParameterDefinition, onlyProvided bool) (map[string]interface{}, error) {
+func GatherArguments(
+	args []string,
+	arguments []*ParameterDefinition,
+	onlyProvided bool,
+) (map[string]interface{}, error) {
 	_ = args
 	result := make(map[string]interface{})
 	argsIdx := 0
@@ -161,13 +165,7 @@ func AddFlagsToCobraCommand(
 		}
 
 		switch parameter.Type {
-		case ParameterTypeStringListFromFile:
-			fallthrough
-		case ParameterTypeStringFromFile:
-			fallthrough
-		case ParameterTypeObjectFromFile:
-			fallthrough
-		case ParameterTypeObjectListFromFile:
+		case ParameterTypeStringListFromFile, ParameterTypeStringFromFile, ParameterTypeObjectFromFile, ParameterTypeObjectListFromFile:
 			defaultValue := ""
 
 			if parameter.ShortFlag != "" {
@@ -176,11 +174,7 @@ func AddFlagsToCobraCommand(
 				flagSet.String(flagName, defaultValue, parameter.Help)
 			}
 
-		case ParameterTypeStringListFromFiles:
-			fallthrough
-		case ParameterTypeStringFromFiles:
-			fallthrough
-		case ParameterTypeObjectListFromFiles:
+		case ParameterTypeStringListFromFiles, ParameterTypeStringFromFiles, ParameterTypeObjectListFromFiles:
 			defaultValue := []string{}
 
 			if parameter.ShortFlag != "" {
@@ -277,7 +271,7 @@ func AddFlagsToCobraCommand(
 				flagSet.String(flagName, defaultValue, parameter.Help)
 			}
 
-		case ParameterTypeStringList:
+		case ParameterTypeStringList, ParameterTypeChoiceList:
 			var defaultValue []string
 
 			if parameter.Default != nil {
@@ -485,19 +479,13 @@ func GatherFlagsFromCobraCommand(
 		}
 
 		switch parameter.Type {
-		case ParameterTypeObjectFromFile:
-			fallthrough
-		case ParameterTypeObjectListFromFile:
-			fallthrough
-		case ParameterTypeStringFromFile:
-			fallthrough
-		case ParameterTypeStringListFromFile:
-			fallthrough
-		case ParameterTypeString:
-			fallthrough
-		case ParameterTypeDate:
-			fallthrough
-		case ParameterTypeChoice:
+		case ParameterTypeObjectFromFile,
+			ParameterTypeObjectListFromFile,
+			ParameterTypeStringFromFile,
+			ParameterTypeStringListFromFile,
+			ParameterTypeString,
+			ParameterTypeDate,
+			ParameterTypeChoice:
 			v, err := cmd.Flags().GetString(flagName)
 			if err != nil {
 				return nil, err
@@ -529,11 +517,9 @@ func GatherFlagsFromCobraCommand(
 			}
 			ps[parameter.Name] = v
 
-		case ParameterTypeObjectListFromFiles:
-			fallthrough
-		case ParameterTypeStringFromFiles:
-			fallthrough
-		case ParameterTypeStringListFromFiles:
+		case ParameterTypeObjectListFromFiles,
+			ParameterTypeStringFromFiles,
+			ParameterTypeStringListFromFiles:
 			v, err := cmd.Flags().GetStringSlice(flagName)
 			if err != nil {
 				return nil, err
@@ -544,7 +530,8 @@ func GatherFlagsFromCobraCommand(
 			}
 			ps[parameter.Name] = v2
 
-		case ParameterTypeStringList:
+		case ParameterTypeStringList,
+			ParameterTypeChoiceList:
 			v, err := cmd.Flags().GetStringSlice(flagName)
 			if err != nil {
 				return nil, err
diff --git a/pkg/cmds/parameters/parameters.go b/pkg/cmds/parameters/parameters.go
index 911730a..9303987 100644
--- a/pkg/cmds/parameters/parameters.go
+++ b/pkg/cmds/parameters/parameters.go
@@ -90,93 +90,6 @@ func (p *ParameterDefinition) IsEqualToDefault(i interface{}) bool {
 	return reflect.DeepEqual(p.Default, i)
 }
 
-func (p *ParameterDefinition) SetDefaultFromInterface(i interface{}) error {
-	switch p.Type {
-	case ParameterTypeString:
-		v, ok := i.(string)
-		if !ok {
-			return errors.Errorf("expected string for parameter %s, got %T", p.Name, i)
-		}
-		p.Default = v
-	case ParameterTypeBool:
-		v, ok := i.(bool)
-		if !ok {
-			return errors.Errorf("expected bool for parameter %s, got %T", p.Name, i)
-		}
-		p.Default = v
-	case ParameterTypeInteger:
-		v, ok := cast.CastNumberInterfaceToInt[int64](i)
-		if !ok {
-			return errors.Errorf("expected int64 for parameter %s, got %T", p.Name, i)
-		}
-		p.Default = v
-	case ParameterTypeFloat:
-
-		v, ok := cast.CastFloatInterfaceToFloat[float64](i)
-		if !ok {
-			return errors.Errorf("expected float64 for parameter %s, got %T", p.Name, i)
-		}
-		p.Default = v
-	case ParameterTypeStringList:
-		v, ok := cast.CastList2[string, interface{}](i)
-		if !ok {
-			return errors.Errorf("expected string list for parameter %s, got %T", p.Name, i)
-		}
-		p.Default = v
-	case ParameterTypeDate:
-		p.Default = i.(time.Time).Format(time.RFC3339)
-	case ParameterTypeIntegerList:
-		v, ok := cast.CastInterfaceToIntList[int64](i)
-		if !ok {
-			return errors.Errorf("expected integer list for parameter %s, got %T", p.Name, i)
-		}
-		p.Default = v
-	case ParameterTypeFloatList:
-		v, ok := cast.CastInterfaceToFloatList[float64](i)
-		if !ok {
-			return errors.Errorf("expected float list for parameter %s, got %T", p.Name, i)
-		}
-		p.Default = v
-	case ParameterTypeChoice:
-		p.Default = i.(string)
-	case ParameterTypeStringFromFiles:
-		fallthrough
-	case ParameterTypeStringFromFile:
-		p.Default = i.(string)
-	case ParameterTypeStringListFromFiles:
-		fallthrough
-	case ParameterTypeStringListFromFile:
-		v, ok := cast.CastList2[string, interface{}](i)
-		if !ok {
-			return errors.Errorf("expected string list for parameter %s, got %T", p.Name, i)
-		}
-		p.Default = v
-	case ParameterTypeKeyValue:
-		v, ok := cast.CastInterfaceToStringMap[string, interface{}](i)
-		if !ok {
-			return errors.Errorf("expected string map for parameter %s, got %T", p.Name, i)
-		}
-		p.Default = v
-	case ParameterTypeObjectFromFile:
-		v, ok := cast.CastInterfaceToStringMap[interface{}, interface{}](i)
-		if !ok {
-			return errors.Errorf("expected object for parameter %s, got %T", p.Name, i)
-		}
-		p.Default = v
-	case ParameterTypeObjectListFromFiles:
-		fallthrough
-	case ParameterTypeObjectListFromFile:
-		v, ok := cast.CastList2[map[string]interface{}, interface{}](i)
-		if !ok {
-			return errors.Errorf("expected object list for parameter %s, got %T", p.Name, i)
-		}
-		p.Default = v
-
-	}
-
-	return nil
-}
-
 func (p *ParameterDefinition) SetDefaultFromValue(value reflect.Value) error {
 	// check if value is pointer, do nothing if nil, otherwise dereference
 	if value.Kind() == reflect.Ptr {
@@ -186,205 +99,135 @@ func (p *ParameterDefinition) SetDefaultFromValue(value reflect.Value) error {
 		value = value.Elem()
 	}
 
-	switch p.Type {
-	case ParameterTypeString:
-		p.Default = value.String()
-	case ParameterTypeBool:
-		p.Default = value.Bool()
-	case ParameterTypeInteger:
-		p.Default = value.Int()
-	case ParameterTypeFloat:
-		p.Default = value.Float()
-
-	case ParameterTypeStringListFromFiles:
-		fallthrough
-	case ParameterTypeStringListFromFile:
-		fallthrough
-	case ParameterTypeStringList:
-		v, ok := cast.CastList2[string, interface{}](value.Interface())
-		if !ok {
-			return errors.Errorf("expected string list for parameter %s, got %T", p.Name, value.Interface())
-		}
-		p.Default = v
-
-	case ParameterTypeDate:
-		p.Default = value.Interface().(time.Time).Format(time.RFC3339)
-	case ParameterTypeIntegerList:
-		v, ok := cast.CastList2[int64, interface{}](value.Interface())
-		if !ok {
-			return errors.Errorf("expected integer list for parameter %s, got %T", p.Name, value.Interface())
-		}
-		p.Default = v
-	case ParameterTypeFloatList:
-		v, ok := cast.CastList2[float64, interface{}](value.Interface())
-		if !ok {
-			return errors.Errorf("expected float list for parameter %s, got %T", p.Name, value.Interface())
-		}
-		p.Default = v
-
-	case ParameterTypeChoice:
-		p.Default = value.String()
-	case ParameterTypeStringFromFiles:
-		fallthrough
-	case ParameterTypeStringFromFile:
-		p.Default = value.String()
-	case ParameterTypeKeyValue:
-		v, ok := cast.CastInterfaceToStringMap[string, interface{}](value.Interface())
-		if !ok {
-			return errors.Errorf("expected string map for parameter %s, got %T", p.Name, value.Interface())
-		}
-		p.Default = v
-	case ParameterTypeObjectFromFile:
-		v, ok := cast.CastInterfaceToStringMap[interface{}, interface{}](value.Interface())
-		if !ok {
-			return errors.Errorf("expected object for parameter %s, got %T", p.Name, value.Interface())
-		}
-		p.Default = v
+	if p.CheckValueValidity(value.Interface()) != nil {
+		return errors.Errorf("invalid value for parameter %s: %v", p.Name, value.Interface())
+	}
 
-	case ParameterTypeObjectListFromFiles:
-		fallthrough
-	case ParameterTypeObjectListFromFile:
-		v, ok := cast.CastList2[map[string]interface{}, interface{}](value.Interface())
-		if !ok {
-			return errors.Errorf("expected object list for parameter %s, got %T", p.Name, value.Interface())
-		}
-		p.Default = v
+	val := reflect.ValueOf(p).Elem()
+	f := val.FieldByName("Default")
+	if f.CanSet() {
+		f.Set(reflect.ValueOf(value.Interface()))
 	}
+
 	return nil
 }
 
 // SetValueFromDefault assigns the default value of the ParameterDefinition to the given value.
 // If the Default value is nil, the value is set to the zero value of the type.
-//
-// TODO(manuel, 2023-02-12) Not sure if the setting to the zero value of the type is the best idea, really.
 func (p *ParameterDefinition) SetValueFromDefault(value reflect.Value) error {
 	if !value.CanSet() {
 		return errors.Errorf("cannot set value of %s", p.Name)
 	}
 
+	if p.Default != nil {
+		return p.SetValueFromInterface(value, p.Default)
+	}
+	return p.InitializeValueToEmptyValue(value)
+}
+
+// InitializeValueToEmptyValue initializes the given value to the empty value of the type of the parameter.
+func (p *ParameterDefinition) InitializeValueToEmptyValue(value reflect.Value) error {
 	switch p.Type {
-	case ParameterTypeString:
-		if p.Default == nil {
-			value.SetString("")
-		} else {
-			value.SetString(p.Default.(string))
-		}
+	case ParameterTypeString, ParameterTypeChoice, ParameterTypeStringFromFiles, ParameterTypeStringFromFile:
+		value.SetString("")
 	case ParameterTypeBool:
-		if p.Default == nil {
-			value.SetBool(false)
-		} else {
-			value.SetBool(p.Default.(bool))
-		}
-	case ParameterTypeInteger:
-		if p.Default == nil {
-			return reflect2.SetReflectValue(value, 0)
-		} else {
-			return reflect2.SetReflectValue(value, p.Default)
-		}
-	case ParameterTypeFloat:
-		if p.Default == nil {
-			return reflect2.SetReflectValue(value, 0.0)
-		} else {
-			return reflect2.SetReflectValue(value, p.Default)
-		}
-	case ParameterTypeStringList:
-		if p.Default == nil {
-			value.Set(reflect.ValueOf([]string{}))
-		} else {
-			v_, ok := cast.CastList2[string, interface{}](p.Default)
-			if !ok {
-				return errors.Errorf("expected string list for parameter %s, got %T", p.Name, p.Default)
-			}
-			value.Set(reflect.ValueOf(v_))
-		}
+		value.SetBool(false)
+	case ParameterTypeInteger, ParameterTypeFloat:
+		return reflect2.SetReflectValue(value, 0)
+	case ParameterTypeStringList, ParameterTypeChoiceList, ParameterTypeStringListFromFiles, ParameterTypeStringListFromFile:
+		value.Set(reflect.ValueOf([]string{}))
 	case ParameterTypeDate:
-		// TODO(manuel, 2023-02-12) Not sure exactly if this should be fully parsed at this point, or left up to the flag
-		if p.Default == nil {
-			// maybe this should be nil too (?)
-			value.Set(reflect.ValueOf(time.Time{}))
-		} else {
-			s := p.Default.(string)
-			dateTime, err := ParseDate(s)
-			if err != nil {
-				return errors.Wrapf(err, "error parsing default value for parameter %s", p.Name)
-			}
-			value.Set(reflect.ValueOf(dateTime))
-		}
+		value.Set(reflect.ValueOf(time.Time{}))
 	case ParameterTypeIntegerList:
-		if p.Default == nil {
-			return reflect2.SetReflectValue(value, []int64{})
-		} else {
-			return reflect2.SetReflectValue(value, p.Default)
-		}
+		return reflect2.SetReflectValue(value, []int64{})
 	case ParameterTypeFloatList:
-		if p.Default == nil {
-			return reflect2.SetReflectValue(value, []float64{})
-		} else {
-			return reflect2.SetReflectValue(value, p.Default)
+		return reflect2.SetReflectValue(value, []float64{})
+	case ParameterTypeObjectListFromFiles, ParameterTypeObjectListFromFile:
+		value.Set(reflect.ValueOf([]map[string]interface{}{}))
+	case ParameterTypeObjectFromFile:
+		value.Set(reflect.ValueOf(map[string]interface{}{}))
+	case ParameterTypeKeyValue:
+		value.Set(reflect.ValueOf(map[string]string{}))
+	default:
+		return errors.Errorf("unknown parameter type %s", p.Type)
+	}
+	return nil
+}
+
+// SetValueFromInterface assigns the given value to the given reflect.Value.
+func (p *ParameterDefinition) SetValueFromInterface(value reflect.Value, v interface{}) error {
+	err := p.CheckValueValidity(v)
+	if err != nil {
+		return err
+	}
+
+	switch p.Type {
+	case ParameterTypeString, ParameterTypeChoice, ParameterTypeStringFromFiles, ParameterTypeStringFromFile:
+		strVal, ok := v.(string)
+		if !ok {
+			return errors.Errorf("expected string value for parameter %s, got %T", p.Name, v)
 		}
-	case ParameterTypeChoice:
-		if p.Default == nil {
-			value.SetString("")
-		} else {
-			value.SetString(p.Default.(string))
+		value.SetString(strVal)
+
+	case ParameterTypeBool:
+		boolVal, ok := v.(bool)
+		if !ok {
+			return errors.Errorf("expected bool value for parameter %s, got %T", p.Name, v)
 		}
-	case ParameterTypeStringFromFiles:
-		fallthrough
-	case ParameterTypeStringFromFile:
-		if p.Default == nil {
-			value.SetString("")
-		} else {
-			value.SetString(p.Default.(string))
+		value.SetBool(boolVal)
+
+	case ParameterTypeInteger, ParameterTypeFloat:
+		return reflect2.SetReflectValue(value, v)
+
+	case ParameterTypeStringList, ParameterTypeChoiceList, ParameterTypeStringListFromFiles, ParameterTypeStringListFromFile:
+		list, ok := cast.CastList2[string, interface{}](v)
+		if !ok {
+			return errors.Errorf("expected string list for parameter %s, got %T", p.Name, v)
 		}
-	case ParameterTypeStringListFromFiles:
-		fallthrough
-	case ParameterTypeStringListFromFile:
-		if p.Default == nil {
-			value.Set(reflect.ValueOf([]string{}))
-		} else {
-			list, b := cast.CastList2[string, interface{}](p.Default)
-			if !b {
-				return errors.Errorf("default value for parameter %s is not a list of strings", p.Name)
-			}
-			value.Set(reflect.ValueOf(list))
+		value.Set(reflect.ValueOf(list))
+
+	case ParameterTypeDate:
+		strVal, ok := v.(string)
+		if !ok {
+			return errors.Errorf("expected string value for parameter %s, got %T", p.Name, v)
 		}
-	case ParameterTypeObjectListFromFiles:
-		fallthrough
-	case ParameterTypeObjectListFromFile:
-		if p.Default == nil {
-			value.Set(reflect.ValueOf([]map[string]interface{}{}))
-		} else {
-			list2, b := cast.CastList2[map[string]interface{}, interface{}](p.Default)
-			if !b {
-				return errors.Errorf("default value for parameter %s is not a list of maps", p.Name)
-			}
-			value.Set(reflect.ValueOf(list2))
+		dateTime, err := ParseDate(strVal)
+		if err != nil {
+			return errors.Wrapf(err, "error parsing value for parameter %s", p.Name)
 		}
+		value.Set(reflect.ValueOf(dateTime))
+
+	case ParameterTypeIntegerList, ParameterTypeFloatList:
+		return reflect2.SetReflectValue(value, v)
+
+	case ParameterTypeObjectListFromFiles, ParameterTypeObjectListFromFile:
+		list, ok := cast.CastList2[map[string]interface{}, interface{}](v)
+		if !ok {
+			return errors.Errorf("expected list of maps for parameter %s, got %T", p.Name, v)
+		}
+		value.Set(reflect.ValueOf(list))
+
 	case ParameterTypeObjectFromFile:
-		if p.Default == nil {
-			value.Set(reflect.ValueOf(map[string]interface{}{}))
-		} else {
-			value.Set(reflect.ValueOf(p.Default.(map[string]interface{})))
+		mapVal, ok := v.(map[string]interface{})
+		if !ok {
+			return errors.Errorf("expected map for parameter %s, got %T", p.Name, v)
 		}
+		value.Set(reflect.ValueOf(mapVal))
+
 	case ParameterTypeKeyValue:
-		if p.Default == nil {
-			value.Set(reflect.ValueOf(map[string]string{}))
-		} else {
-			v, ok := p.Default.(map[string]interface{})
-			if !ok {
-				return errors.Errorf("default value for parameter %s is not a map[string]interface{}", p.Name)
-			}
-			v2, ok := cast.CastStringMap[string, interface{}](v)
-			if !ok {
-				return errors.Errorf("default value for parameter %s is not a map[string]interface{}", p.Name)
-			}
-			value.Set(reflect.ValueOf(v2))
+		mapVal, ok := v.(map[string]interface{})
+		if !ok {
+			return errors.Errorf("expected map for parameter %s, got %T", p.Name, v)
+		}
+		mapStrVal, ok := cast.CastStringMap[string, interface{}](mapVal)
+		if !ok {
+			return errors.Errorf("expected map of strings for parameter %s, got %T", p.Name, v)
 		}
+		value.Set(reflect.ValueOf(mapStrVal))
 
 	default:
 		return errors.Errorf("unknown parameter type %s", p.Type)
 	}
-
 	return nil
 }
 
@@ -627,6 +470,7 @@ const (
 	ParameterTypeIntegerList ParameterType = "intList"
 	ParameterTypeFloatList   ParameterType = "floatList"
 	ParameterTypeChoice      ParameterType = "choice"
+	ParameterTypeChoiceList  ParameterType = "choiceList"
 )
 
 // IsFileLoadingParameter returns true if the parameter type is one that loads a file, when provided with the given
@@ -678,12 +522,15 @@ func IsListParameter(p ParameterType) bool {
 	}
 }
 
+// CheckParameterDefaultValueValidity checks if the ParameterDefinition's Default is valid.
+// This is used when validating loading from a YAML file or setting up cobra flag definitions.
 func (p *ParameterDefinition) CheckParameterDefaultValueValidity() error {
 	// we can have no default
 	v := p.Default
 	return p.CheckValueValidity(v)
 }
 
+// CheckValueValidity checks if the given value is valid for the ParameterDefinition.
 func (p *ParameterDefinition) CheckValueValidity(v interface{}) error {
 	if v == nil {
 		return nil
@@ -697,7 +544,7 @@ func (p *ParameterDefinition) CheckValueValidity(v interface{}) error {
 	case ParameterTypeString:
 		_, ok := v.(string)
 		if !ok {
-			return errors.Errorf("Default value for parameter %s is not a string: %v", p.Name, v)
+			return errors.Errorf("Value for parameter %s is not a string: %v", p.Name, v)
 		}
 
 	case ParameterTypeObjectListFromFile:
@@ -705,31 +552,31 @@ func (p *ParameterDefinition) CheckValueValidity(v interface{}) error {
 	case ParameterTypeObjectListFromFiles:
 		_, ok := v.([]interface{})
 		if !ok {
-			return errors.Errorf("Default value for parameter %s is not a list of objects: %v", p.Name, v)
+			return errors.Errorf("Value for parameter %s is not a list of objects: %v", p.Name, v)
 		}
 
 	case ParameterTypeObjectFromFile:
 		_, ok := v.(map[string]interface{})
 		if !ok {
-			return errors.Errorf("Default value for parameter %s is not an object: %v", p.Name, v)
+			return errors.Errorf("Value for parameter %s is not an object: %v", p.Name, v)
 		}
 
 	case ParameterTypeInteger:
 		_, ok := cast.CastNumberInterfaceToInt[int64](v)
 		if !ok {
-			return errors.Errorf("Default value for parameter %s is not an integer: %v", p.Name, v)
+			return errors.Errorf("Value for parameter %s is not an integer: %v", p.Name, v)
 		}
 
 	case ParameterTypeFloat:
-		_, ok := cast.CastFloatInterfaceToFloat[float64](v)
+		_, ok := cast.CastNumberInterfaceToFloat[float64](v)
 		if !ok {
-			return errors.Errorf("Default value for parameter %s is not a float: %v", p.Name, v)
+			return errors.Errorf("Value for parameter %s is not a float: %v", p.Name, v)
 		}
 
 	case ParameterTypeBool:
 		_, ok := v.(bool)
 		if !ok {
-			return errors.Errorf("Default value for parameter %s is not a bool: %v", p.Name, v)
+			return errors.Errorf("Value for parameter %s is not a bool: %v", p.Name, v)
 		}
 
 	case ParameterTypeDate:
@@ -737,12 +584,12 @@ func (p *ParameterDefinition) CheckValueValidity(v interface{}) error {
 		case string:
 			_, err := ParseDate(v_)
 			if err != nil {
-				return errors.Wrapf(err, "Default value for parameter %s is not a valid date: %v", p.Name, v)
+				return errors.Wrapf(err, "Value for parameter %s is not a valid date: %v", p.Name, v)
 			}
 		case time.Time:
 			return nil
 		default:
-			return errors.Errorf("Default value for parameter %s is not a valid date: %v", p.Name, v)
+			return errors.Errorf("Value for parameter %s is not a valid date: %v", p.Name, v)
 		}
 
 	case ParameterTypeStringListFromFile:
@@ -752,15 +599,15 @@ func (p *ParameterDefinition) CheckValueValidity(v interface{}) error {
 	case ParameterTypeStringList:
 		_, ok := v.([]string)
 		if !ok {
-			defaultValue, ok := v.([]interface{})
+			v_, ok := v.([]interface{})
 			if !ok {
-				return errors.Errorf("Default value for parameter %s is not a string list: %v", p.Name, v)
+				return errors.Errorf("Value for parameter %s is not a string list: %v", p.Name, v)
 			}
 
 			// convert to string list
-			fixedDefault, ok := cast.CastList[string, interface{}](defaultValue)
+			fixedDefault, ok := cast.CastList[string, interface{}](v_)
 			if !ok {
-				return errors.Errorf("Default value for parameter %s is not a string list: %v", p.Name, v)
+				return errors.Errorf("Value for parameter %s is not a string list: %v", p.Name, v)
 			}
 			_ = fixedDefault
 		}
@@ -774,7 +621,7 @@ func (p *ParameterDefinition) CheckValueValidity(v interface{}) error {
 	case ParameterTypeFloatList:
 		_, ok := cast.CastInterfaceToFloatList[float64](v)
 		if !ok {
-			return errors.Errorf("Default value for parameter %s is not a float list: %v", p.Name, v)
+			return errors.Errorf("Value for parameter %s is not a float list: %v", p.Name, v)
 		}
 
 	case ParameterTypeChoice:
@@ -782,32 +629,44 @@ func (p *ParameterDefinition) CheckValueValidity(v interface{}) error {
 			return errors.Errorf("ParameterDefinition %s is a choice parameter but has no choices", p.Name)
 		}
 
-		defaultValue, ok := v.(string)
+		v_, ok := v.(string)
 		if !ok {
-			return errors.Errorf("Default value for parameter %s is not a string: %v", p.Name, v)
+			return errors.Errorf("Value for parameter %s is not a string: %v", p.Name, v)
 		}
 
-		found := false
-		for _, choice := range p.Choices {
-			if choice == defaultValue {
-				found = true
-			}
+		err := p.checkChoiceValidity(v_)
+		if err != nil {
+			return err
 		}
-		if !found {
-			return errors.Errorf("Default value for parameter %s is not a valid choice: %v", p.Name, v)
+
+	case ParameterTypeChoiceList:
+		if len(p.Choices) == 0 {
+			return errors.Errorf("ParameterDefinition %s is a choice parameter but has no choices", p.Name)
+		}
+
+		v_, ok := cast.CastList2[string, interface{}](v)
+		if !ok {
+			return errors.Errorf("Value for parameter %s is not a string list: %v", p.Name, v)
+		}
+
+		for _, choice := range v_ {
+			err := p.checkChoiceValidity(choice)
+			if err != nil {
+				return err
+			}
 		}
 
 	case ParameterTypeKeyValue:
 		_, ok := v.(map[string]string)
 		if !ok {
-			defaultValue, ok := v.(map[string]interface{})
+			v_, ok := v.(map[string]interface{})
 			if !ok {
-				return errors.Errorf("Default value for parameter %s is not a key value list: %v", p.Name, v)
+				return errors.Errorf("Value for parameter %s is not a key value list: %v", p.Name, v)
 			}
 
-			_, ok = cast.CastStringMap[string, interface{}](defaultValue)
+			_, ok = cast.CastStringMap[string, interface{}](v_)
 			if !ok {
-				return errors.Errorf("Default value for parameter %s is not a key value list: %v", p.Name, v)
+				return errors.Errorf("Value for parameter %s is not a key value list: %v", p.Name, v)
 			}
 		}
 	}
@@ -815,6 +674,22 @@ func (p *ParameterDefinition) CheckValueValidity(v interface{}) error {
 	return nil
 }
 
+func (p *ParameterDefinition) checkChoiceValidity(choice string) error {
+	found := false
+	for _, choice2 := range p.Choices {
+		if choice == choice2 {
+			found = true
+		}
+	}
+	if !found {
+		return errors.Errorf("Value for parameter %s is not a valid choice: %v", p.Name, choice)
+	}
+	return nil
+}
+
+// LoadParameterDefinitionsFromYAML loads a map of ParameterDefinitions from a YAML file.
+// It checks that default values are valid.
+// It returns the ParameterDefinitions as a map indexed by name, and as a list.
 func LoadParameterDefinitionsFromYAML(yamlContent []byte) (map[string]*ParameterDefinition, []*ParameterDefinition) {
 	flags := make(map[string]*ParameterDefinition)
 	flagList := make([]*ParameterDefinition, 0)
diff --git a/pkg/cmds/parameters/parse.go b/pkg/cmds/parameters/parse.go
index df2fe69..f736484 100644
--- a/pkg/cmds/parameters/parse.go
+++ b/pkg/cmds/parameters/parse.go
@@ -94,6 +94,22 @@ func (p *ParameterDefinition) ParseParameter(v []string) (interface{}, error) {
 		}
 		return choice, nil
 
+	case ParameterTypeChoiceList:
+		choices := make([]string, 0)
+		for _, arg := range v {
+			found := false
+			for _, c := range p.Choices {
+				if c == arg {
+					found = true
+				}
+			}
+			if !found {
+				return nil, errors.Errorf("Argument %s has invalid choice %s", p.Name, arg)
+			}
+			choices = append(choices, arg)
+		}
+		return choices, nil
+
 	case ParameterTypeDate:
 		parsedDate, err := ParseDate(v[0])
 		if err != nil {
diff --git a/pkg/cmds/parameters/render.go b/pkg/cmds/parameters/render.go
index 188a181..8bf3ee4 100644
--- a/pkg/cmds/parameters/render.go
+++ b/pkg/cmds/parameters/render.go
@@ -8,26 +8,25 @@ import (
 	"time"
 )
 
+// RenderValue renders the given value to string so that it can be parsed as a cobra command line flag.
+// TODO(manuel, 2023-09-09) Refactor rendering of values to strings that can be parsed.
+// This is only applicable to parsing using cobra, but really we now have many more ways of parsing
+// a flag out of a string, among which GET query and FORM input parameters.
 func RenderValue(type_ ParameterType, value interface{}) (string, error) {
 	switch type_ {
-	case ParameterTypeString:
-		fallthrough
-	case ParameterTypeStringFromFile:
-		fallthrough
-	case ParameterTypeStringFromFiles:
-		fallthrough
-	case ParameterTypeChoice:
+	case ParameterTypeString,
+		ParameterTypeStringFromFile,
+		ParameterTypeStringFromFiles,
+		ParameterTypeChoice:
 		s, ok := value.(string)
 		if !ok {
 			return "", errors.Errorf("expected string, got %T", value)
 		}
 		return s, nil
 
-	case ParameterTypeObjectListFromFiles:
-		fallthrough
-	case ParameterTypeObjectListFromFile:
-		fallthrough
-	case ParameterTypeObjectFromFile:
+	case ParameterTypeObjectListFromFiles,
+		ParameterTypeObjectListFromFile,
+		ParameterTypeObjectFromFile:
 		return fmt.Sprintf("%v", value), nil
 
 	case ParameterTypeDate:
@@ -75,11 +74,10 @@ func RenderValue(type_ ParameterType, value interface{}) (string, error) {
 		}
 		return "false", nil
 
-	case ParameterTypeStringListFromFiles:
-		fallthrough
-	case ParameterTypeStringListFromFile:
-		fallthrough
-	case ParameterTypeStringList:
+	case ParameterTypeStringListFromFiles,
+		ParameterTypeStringListFromFile,
+		ParameterTypeStringList,
+		ParameterTypeChoiceList:
 		l, ok := cast.CastList2[string, interface{}](value)
 		if !ok {
 			return "", errors.Errorf("expected []string, got %T", value)
diff --git a/pkg/helpers/cast/cast.go b/pkg/helpers/cast/cast.go
index 0db7aa2..09e8b88 100644
--- a/pkg/helpers/cast/cast.go
+++ b/pkg/helpers/cast/cast.go
@@ -133,7 +133,7 @@ func CastInterfaceListToFloatList[To FloatNumber](list []interface{}) ([]To, boo
 	ret := []To{}
 
 	for _, item := range list {
-		f, ok := CastFloatInterfaceToFloat[To](item)
+		f, ok := CastNumberInterfaceToFloat[To](item)
 		if !ok {
 			return ret, false
 		}
diff --git a/pkg/helpers/reflect/reflect.go b/pkg/helpers/reflect/reflect.go
index 7c75eab..78f53c6 100644
--- a/pkg/helpers/reflect/reflect.go
+++ b/pkg/helpers/reflect/reflect.go
@@ -400,7 +400,7 @@ func SetIntListReflectValue[To cast.Number](value reflect.Value, v interface{})
 	return fmt.Errorf("cannot set reflect.Value of type %s from %T", value.Kind(), v)
 }
 
-func SetFloatListReflectValue[To cast.Number](value reflect.Value, v interface{}) error {
+func SetFloatListReflectValue[To cast.FloatNumber](value reflect.Value, v interface{}) error {
 	if s, ok := v.([]float64); ok {
 		s2, ok := cast.CastToNumberList[To, float64](s)
 		if !ok {
@@ -420,15 +420,12 @@ func SetFloatListReflectValue[To cast.Number](value reflect.Value, v interface{}
 	}
 
 	if s, ok := v.([]interface{}); ok {
-		v2_, ok := cast.CastList[float64, interface{}](s)
+		v2_, ok := cast.CastInterfaceListToFloatList[To](s)
 		if !ok {
 			return fmt.Errorf("cannot cast %T to []%T", v, To(0))
 		}
-		v3_, ok := cast.CastToNumberList[To, float64](v2_)
-		if !ok {
-			return fmt.Errorf("cannot cast %T to []%T", v, To(0))
-		}
-		value.Set(reflect.ValueOf(v3_))
+
+		value.Set(reflect.ValueOf(v2_))
 		return nil
 	}
 


Test patch:
diff --git a/pkg/cmds/parameters/cobra_test.go b/pkg/cmds/parameters/cobra_test.go
index 47ffdc8..1f62b81 100644
--- a/pkg/cmds/parameters/cobra_test.go
+++ b/pkg/cmds/parameters/cobra_test.go
@@ -43,9 +43,10 @@ func TestParseDate(t *testing.T) {
 }
 
 type DefaultTypeTestCase struct {
-	Type  ParameterType
-	Value interface{}
-	Args  []string
+	Type    ParameterType
+	Value   interface{}
+	Choices []string
+	Args    []string
 }
 
 func TestValidDefaultValue(t *testing.T) {
@@ -58,15 +59,20 @@ func TestValidDefaultValue(t *testing.T) {
 		{Type: ParameterTypeIntegerList, Value: []int{1, 2, 3}},
 		{Type: ParameterTypeStringList, Value: []string{}},
 		{Type: ParameterTypeIntegerList, Value: []int{}},
+		{Type: ParameterTypeChoice, Value: "foo", Choices: []string{"foo", "bar"}},
+		{Type: ParameterTypeChoiceList, Value: []string{"foo", "bar"}, Choices: []string{"foo", "bar"}},
 	}
 	for _, testCase := range testCases {
-		param := &ParameterDefinition{
-			Name:    "foo",
-			Default: testCase.Value,
-			Type:    testCase.Type,
-		}
-		err := param.CheckParameterDefaultValueValidity()
-		assert.Nil(t, err)
+		t.Run(string(testCase.Type), func(t *testing.T) {
+			param := &ParameterDefinition{
+				Name:    "foo",
+				Default: testCase.Value,
+				Type:    testCase.Type,
+				Choices: testCase.Choices,
+			}
+			err := param.CheckParameterDefaultValueValidity()
+			assert.Nil(t, err)
+		})
 	}
 }
 
diff --git a/pkg/cmds/parameters/parameters_test.go b/pkg/cmds/parameters/parameters_test.go
index 3ce17ab..7ff7b59 100644
--- a/pkg/cmds/parameters/parameters_test.go
+++ b/pkg/cmds/parameters/parameters_test.go
@@ -4,6 +4,7 @@ import (
 	_ "embed"
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
+	"gopkg.in/yaml.v3"
 	"reflect"
 	"testing"
 	"time"
@@ -15,8 +16,60 @@ var testFlagsYaml []byte
 var testParameterDefinitions map[string]*ParameterDefinition
 var testParameterDefinitionsList []*ParameterDefinition
 
+type ValidityTest struct {
+	Name                string        `yaml:"name"`
+	Valid               bool          `yaml:"valid"`
+	Type                ParameterType `yaml:"type"`
+	Value               interface{}   `yaml:"value"`
+	Choices             []string      `yaml:"choices,omitempty"`
+	parameterDefinition *ParameterDefinition
+}
+
+//go:embed "test-data/parameters_validity_test.yaml"
+var validityTestYaml []byte
+
+var testParameterValidList []*ValidityTest
+
+func loadValidityTestDataFromYAML(s []byte) ([]*ValidityTest, error) {
+	var tests []*ValidityTest
+	err := yaml.Unmarshal(s, &tests)
+	if err != nil {
+		return nil, err
+	}
+
+	for _, test := range tests {
+		test.parameterDefinition = &ParameterDefinition{
+			Name:     test.Name,
+			Type:     test.Type,
+			Default:  nil,
+			Choices:  test.Choices,
+			Required: true,
+		}
+	}
+
+	return tests, nil
+}
+
 func init() {
 	testParameterDefinitions, testParameterDefinitionsList = LoadParameterDefinitionsFromYAML(testFlagsYaml)
+	var err error
+	testParameterValidList, err = loadValidityTestDataFromYAML(validityTestYaml)
+	if err != nil {
+		panic(err)
+	}
+}
+
+func TestParameterValidity(t *testing.T) {
+	for _, validityTest := range testParameterValidList {
+		t.Run(validityTest.Name, func(t *testing.T) {
+			err := validityTest.parameterDefinition.CheckValueValidity(validityTest.Value)
+			if validityTest.Valid {
+				assert.NoError(t, err)
+			} else {
+				assert.Error(t, err)
+			}
+		})
+	}
 }
 
 func TestSetValueFromDefaultInt(t *testing.T) {
@@ -42,7 +95,6 @@ func TestSetValueFromDefaultInt(t *testing.T) {
 	err = intFlag.SetValueFromDefault(iValue)
 	require.NoError(t, err)
 	assert.Equal(t, i, 0)
-
 }
 
 func TestSetValueFromDefaultInt32(t *testing.T) {
@@ -94,6 +146,11 @@ func TestSetValueFromDefaultFloat(t *testing.T) {
 	err = floatFlag.SetValueFromDefault(fValue)
 	require.NoError(t, err)
 	assert.Equal(t, 0.0, f)
+
+	floatFlag = testParameterDefinitions["float-flag-with-int-default"]
+	err = floatFlag.SetValueFromDefault(fValue)
+	require.NoError(t, err)
+	assert.Equal(t, 42.0, f)
 }
 
 func TestSetValueFromDefaultFloat32(t *testing.T) {
@@ -192,6 +249,60 @@ func TestSetValueFromDefaultBool(t *testing.T) {
 	assert.Equal(t, false, b)
 }
 
+func TestSetValueFromDefaultChoice(t *testing.T) {
+	choiceFlag := testParameterDefinitions["choice-flag"]
+
+	c := "foo"
+
+	// get values of testStruct.Choice
+	cValue := reflect.ValueOf(&c).Elem()
+
+	err := choiceFlag.SetValueFromDefault(cValue)
+	require.NoError(t, err)
+	assert.Equal(t, "default", c)
+
+	choiceFlag = testParameterDefinitions["choice-flag-without-default"]
+	err = choiceFlag.SetValueFromDefault(cValue)
+	require.NoError(t, err)
+	assert.Equal(t, "", c)
+
+	choiceFlag = &ParameterDefinition{
+		Name:    "choice-flag-with-invalid-default",
+		Type:    ParameterTypeChoice,
+		Default: "invalid",
+		Choices: []string{"foo", "bar"},
+	}
+	err = choiceFlag.SetValueFromDefault(cValue)
+	require.Error(t, err)
+}
+
+func TestSetValueFromDefaultChoiceList(t *testing.T) {
+	choiceListFlag := testParameterDefinitions["choice-list-flag"]
+
+	cl := []string{"foo", "bar"}
+
+	// get values of testStruct.ChoiceList
+	clValue := reflect.ValueOf(&cl).Elem()
+
+	err := choiceListFlag.SetValueFromDefault(clValue)
+	require.NoError(t, err)
+	assert.Equal(t, []string{"default", "choice1", "choice2"}, cl)
+
+	choiceListFlag = testParameterDefinitions["choice-list-flag-without-default"]
+	err = choiceListFlag.SetValueFromDefault(clValue)
+	require.NoError(t, err)
+	assert.Equal(t, []string{}, cl)
+
+	choiceListFlag = &ParameterDefinition{
+		Name:    "choice-list-flag-with-invalid-default",
+		Type:    ParameterTypeChoiceList,
+		Default: []string{"invalid"},
+		Choices: []string{"foo", "bar"},
+	}
+	err = choiceListFlag.SetValueFromDefault(clValue)
+	require.Error(t, err)
+}
+
 func TestSetValueFromDefaultIntList(t *testing.T) {
 	intListFlag := testParameterDefinitions["int-list-flag"]
 
@@ -255,7 +366,7 @@ func TestSetValueFromDefaultFloatList(t *testing.T) {
 
 	err := floatListFlag.SetValueFromDefault(flValue)
 	require.NoError(t, err)
-	assert.Equal(t, []float64{1.1, 2.2, 3.3}, fl)
+	assert.Equal(t, []float64{1.1, 2.2, 3.3, 4.0, 5.0}, fl)
 
 	floatListFlag = testParameterDefinitions["float-list-flag-without-default"]
 	err = floatListFlag.SetValueFromDefault(flValue)
@@ -281,7 +392,7 @@ func TestSetValueFromDefaultFloat32List(t *testing.T) {
 
 	err := floatListFlag.SetValueFromDefault(flValue)
 	require.NoError(t, err)
-	assert.Equal(t, []float32{1.1, 2.2, 3.3}, fl)
+	assert.Equal(t, []float32{1.1, 2.2, 3.3, 4, 5}, fl)
 
 	floatListFlag = testParameterDefinitions["float-list-flag-without-default"]
 	require.NotNil(t, floatListFlag)
diff --git a/pkg/cmds/parameters/parse_test.go b/pkg/cmds/parameters/parse_test.go
index 9eee853..79ef411 100644
--- a/pkg/cmds/parameters/parse_test.go
+++ b/pkg/cmds/parameters/parse_test.go
@@ -9,187 +9,161 @@ import (
 	"testing"
 )
 
-func TestParameterString(t *testing.T) {
-	parameter := NewParameterDefinition("test", ParameterTypeString,
-		WithDefault("default"),
-	)
-
-	i, err := parameter.ParseParameter([]string{"test"})
-	require.NoError(t, err)
-	assert.Equal(t, "test", i)
-
-	_, err = parameter.ParseParameter([]string{"test", "test2"})
-	assert.Error(t, err)
-
-	i, err = parameter.ParseParameter([]string{})
-	require.NoError(t, err)
-	assert.Equal(t, "default", i)
-}
-
-func TestParameterStringList(t *testing.T) {
-	parameter := NewParameterDefinition("test", ParameterTypeStringList,
-		WithDefault([]string{"default"}),
-	)
-
-	i, err := parameter.ParseParameter([]string{"test"})
-	require.NoError(t, err)
-	assert.Equal(t, []string{"test"}, i)
-
-	i, err = parameter.ParseParameter([]string{"test", "test2"})
-	require.NoError(t, err)
-	assert.Equal(t, []string{"test", "test2"}, i)
-
-	i, err = parameter.ParseParameter([]string{})
-	require.NoError(t, err)
-	assert.Equal(t, []string{"default"}, i)
-}
-
-func TestParameterInt(t *testing.T) {
-	parameter := NewParameterDefinition("test", ParameterTypeInteger,
-		WithDefault(1),
-	)
-
-	i, err := parameter.ParseParameter([]string{"1"})
-	require.NoError(t, err)
-	assert.Equal(t, 1, i)
-
-	_, err = parameter.ParseParameter([]string{"test"})
-	assert.Error(t, err)
+type ExpectError string
 
-	_, err = parameter.ParseParameter([]string{"1", "2"})
-	assert.Error(t, err)
-
-	i, err = parameter.ParseParameter([]string{})
-	require.NoError(t, err)
-	assert.Equal(t, 1, i)
-}
-
-func TestParameterIntegerList(t *testing.T) {
-	parameter := NewParameterDefinition("test", ParameterTypeIntegerList,
-		WithDefault([]int{1}),
-	)
-
-	i, err := parameter.ParseParameter([]string{"1"})
-	require.NoError(t, err)
-	assert.Equal(t, []int{1}, i)
-
-	i, err = parameter.ParseParameter([]string{"1", "2"})
-	require.NoError(t, err)
-	assert.Equal(t, []int{1, 2}, i)
+const ErrorExpected ExpectError = "ErrorExpected"
+const ErrorNotExpected ExpectError = "ErrorNotExpected"
 
-	_, err = parameter.ParseParameter([]string{"test"})
-	assert.Error(t, err)
-
-	i, err = parameter.ParseParameter([]string{})
-	require.NoError(t, err)
-	assert.Equal(t, []int{1}, i)
+type ParameterTestCase struct {
+	Name     string
+	Input    []string
+	Expected interface{}
+	WantErr  ExpectError
 }
 
-func TestParameterBool(t *testing.T) {
-	parameter := NewParameterDefinition("test", ParameterTypeBool,
-		WithDefault(true),
-	)
-
-	i, err := parameter.ParseParameter([]string{"true"})
-	require.NoError(t, err)
-	assert.Equal(t, true, i)
-
-	i, err = parameter.ParseParameter([]string{"false"})
-	require.NoError(t, err)
-	assert.Equal(t, false, i)
-
-	_, err = parameter.ParseParameter([]string{"test"})
-	assert.Error(t, err)
-
-	_, err = parameter.ParseParameter([]string{"true", "false"})
-	assert.Error(t, err)
-
-	i, err = parameter.ParseParameter([]string{})
-	require.NoError(t, err)
-	assert.Equal(t, true, i)
+type ParameterTest struct {
+	Name          string
+	ParameterType ParameterType
+	DefaultValue  interface{}
+	Choices       []string
+	Cases         []ParameterTestCase
 }
 
-func TestParameterFloat(t *testing.T) {
-	parameter := NewParameterDefinition("test", ParameterTypeFloat,
-		WithDefault(1.0),
-	)
-
-	i, err := parameter.ParseParameter([]string{"1.0"})
-	require.NoError(t, err)
-	assert.Equal(t, 1.0, i)
-
-	_, err = parameter.ParseParameter([]string{"test"})
-	assert.Error(t, err)
-
-	_, err = parameter.ParseParameter([]string{"1.0", "2.0"})
-	assert.Error(t, err)
-
-	i, err = parameter.ParseParameter([]string{})
-	require.NoError(t, err)
-	assert.Equal(t, 1.0, i)
-}
-
-func TestParameterFloatList(t *testing.T) {
-	parameter := NewParameterDefinition("test", ParameterTypeFloatList,
-		WithDefault([]float64{1.0}),
-	)
-
-	i, err := parameter.ParseParameter([]string{"1.0"})
-	require.NoError(t, err)
-	assert.Equal(t, []float64{1.0}, i)
-
-	i, err = parameter.ParseParameter([]string{"1.0", "2.0"})
-	require.NoError(t, err)
-	assert.Equal(t, []float64{1.0, 2.0}, i)
-
-	_, err = parameter.ParseParameter([]string{"test"})
-	assert.Error(t, err)
-
-	i, err = parameter.ParseParameter([]string{})
-	require.NoError(t, err)
-	assert.Equal(t, []float64{1.0}, i)
-}
-
-func TestParameterChoice(t *testing.T) {
-	parameter := NewParameterDefinition("test", ParameterTypeChoice,
-		WithDefault("default"),
-		WithChoices([]string{"default", "test"}),
-	)
-
-	i, err := parameter.ParseParameter([]string{"test"})
-	require.NoError(t, err)
-	assert.Equal(t, "test", i)
-
-	_, err = parameter.ParseParameter([]string{"test2"})
-	assert.Error(t, err)
-
-	_, err = parameter.ParseParameter([]string{"test", "test2"})
-	assert.Error(t, err)
-
-	i, err = parameter.ParseParameter([]string{})
-	require.NoError(t, err)
-	assert.Equal(t, "default", i)
-}
-
-func TestParameterTypeKeyValue(t *testing.T) {
-	parameter := NewParameterDefinition("test", ParameterTypeKeyValue,
-		WithDefault(map[string]interface{}{"default": "default"}),
-	)
-
-	i, err := parameter.ParseParameter([]string{"test:test"})
-	require.NoError(t, err)
-	assert.Equal(t, map[string]interface{}{"test": "test"}, i)
-
-	i, err = parameter.ParseParameter([]string{"test:test", "test2:test2"})
-	require.NoError(t, err)
-	assert.Equal(t, map[string]interface{}{"test": "test", "test2": "test2"}, i)
-
-	_, err = parameter.ParseParameter([]string{"test"})
-	assert.Error(t, err)
+func TestParameters(t *testing.T) {
+	tests := []ParameterTest{
+		{
+			Name:          "ParameterString",
+			ParameterType: ParameterTypeString,
+			DefaultValue:  "default",
+			Cases: []ParameterTestCase{
+				{Name: "Valid single string, no error expected", Input: []string{"test"}, Expected: "test", WantErr: ErrorNotExpected},
+				{Name: "Multiple strings for non-list, error expected", Input: []string{"test", "test2"}, WantErr: ErrorExpected},
+				{Name: "No input uses default, no error expected", Input: []string{}, Expected: "default", WantErr: ErrorNotExpected},
+			},
+		},
+		{
+			Name:          "ParameterStringList",
+			ParameterType: ParameterTypeStringList,
+			DefaultValue:  []string{"default"},
+			Cases: []ParameterTestCase{
+				{Name: "Valid single string in list, no error expected", Input: []string{"test"}, Expected: []string{"test"}, WantErr: ErrorNotExpected},
+				{Name: "Valid multiple strings in list, no error expected", Input: []string{"test", "test2"}, Expected: []string{"test", "test2"}, WantErr: ErrorNotExpected},
+				{Name: "No input uses default list, no error expected", Input: []string{}, Expected: []string{"default"}, WantErr: ErrorNotExpected},
+			},
+		},
+		{
+			Name:          "ParameterInt",
+			ParameterType: ParameterTypeInteger,
+			DefaultValue:  1,
+			Cases: []ParameterTestCase{
+				{Name: "Valid integer string, no error expected", Input: []string{"1"}, Expected: 1, WantErr: ErrorNotExpected},
+				{Name: "Invalid non-integer string, error expected", Input: []string{"test"}, WantErr: ErrorExpected},
+				{Name: "Multiple integers for non-list, error expected", Input: []string{"1", "2"}, WantErr: ErrorExpected},
+				{Name: "No input uses default integer, no error expected", Input: []string{}, Expected: 1, WantErr: ErrorNotExpected},
+			},
+		},
+		{
+			Name:          "ParameterIntegerList",
+			ParameterType: ParameterTypeIntegerList,
+			DefaultValue:  []int{1},
+			Cases: []ParameterTestCase{
+				{Name: "Valid single integer in list, no error expected", Input: []string{"1"}, Expected: []int{1}, WantErr: ErrorNotExpected},
+				{Name: "Valid multiple integers in list, no error expected", Input: []string{"1", "2"}, Expected: []int{1, 2}, WantErr: ErrorNotExpected},
+				{Name: "Invalid non-integer string in list, error expected", Input: []string{"test"}, WantErr: ErrorExpected},
+				{Name: "No input uses default integer list, no error expected", Input: []string{}, Expected: []int{1}, WantErr: ErrorNotExpected},
+			},
+		},
+		{
+			Name:          "ParameterBool",
+			ParameterType: ParameterTypeBool,
+			DefaultValue:  true,
+			Cases: []ParameterTestCase{
+				{Name: "Valid 'true' input, no error expected", Input: []string{"true"}, Expected: true, WantErr: ErrorNotExpected},
+				{Name: "Valid 'false' input, no error expected", Input: []string{"false"}, Expected: false, WantErr: ErrorNotExpected},
+				{Name: "Invalid non-boolean string, error expected", Input: []string{"test"}, WantErr: ErrorExpected},
+				{Name: "Multiple boolean values, error expected", Input: []string{"true", "false"}, WantErr: ErrorExpected},
+				{Name: "No input uses default boolean, no error expected", Input: []string{}, Expected: true, WantErr: ErrorNotExpected},
+			},
+		},
+		{
+			Name:          "ParameterFloat",
+			ParameterType: ParameterTypeFloat,
+			DefaultValue:  1.0,
+			Cases: []ParameterTestCase{
+				{Name: "Valid float input, no error expected", Input: []string{"1.0"}, Expected: 1.0, WantErr: ErrorNotExpected},
+				{Name: "Invalid non-float string, error expected", Input: []string{"test"}, WantErr: ErrorExpected},
+				{Name: "Multiple floats for non-list, error expected", Input: []string{"1.0", "2.0"}, WantErr: ErrorExpected},
+				{Name: "No input uses default float, no error expected", Input: []string{}, Expected: 1.0, WantErr: ErrorNotExpected},
+			},
+		},
+		{
+			Name:          "ParameterFloatList",
+			ParameterType: ParameterTypeFloatList,
+			DefaultValue:  []float64{1.0},
+			Cases: []ParameterTestCase{
+				{Name: "Valid single float in list, no error expected", Input: []string{"1.0"}, Expected: []float64{1.0}, WantErr: ErrorNotExpected},
+				{Name: "Valid multiple floats in list, no error expected", Input: []string{"1.0", "2.0"}, Expected: []float64{1.0, 2.0}, WantErr: ErrorNotExpected},
+				{Name: "Invalid non-float string in list, error expected", Input: []string{"test"}, WantErr: ErrorExpected},
+				{Name: "No input uses default float list, no error expected", Input: []string{}, Expected: []float64{1.0}, WantErr: ErrorNotExpected},
+			},
+		},
+		{
+			Name:          "ParameterChoice",
+			ParameterType: ParameterTypeChoice,
+			DefaultValue:  "default",
+			Choices:       []string{"default", "test"},
+			Cases: []ParameterTestCase{
+				{Name: "Valid choice from list, no error expected", Input: []string{"test"}, Expected: "test", WantErr: ErrorNotExpected},
+				{Name: "Invalid choice not in list, error expected", Input: []string{"test2"}, WantErr: ErrorExpected},
+				{Name: "Multiple choices for non-list, error expected", Input: []string{"test", "test2"}, WantErr: ErrorExpected},
+				{Name: "No input uses default choice, no error expected", Input: []string{}, Expected: "default", WantErr: ErrorNotExpected},
+			},
+		},
+		{
+			Name:          "ParameterChoiceList",
+			ParameterType: ParameterTypeChoiceList,
+			DefaultValue:  []string{"default"},
+			Choices:       []string{"default", "test", "option1", "option2"},
+			Cases: []ParameterTestCase{
+				{Name: "Valid single choice in list, no error expected", Input: []string{"test"}, Expected: []string{"test"}, WantErr: ErrorNotExpected},
+				{Name: "Valid multiple choices in list, no error expected", Input: []string{"test", "option1"}, Expected: []string{"test", "option1"}, WantErr: ErrorNotExpected},
+				{Name: "Invalid single choice not in list, error expected", Input: []string{"test2"}, WantErr: ErrorExpected},
+				{Name: "Mixed valid and invalid choices, error expected", Input: []string{"test", "test2"}, WantErr: ErrorExpected},
+				{Name: "No input uses default choice list, no error expected", Input: []string{}, Expected: []string{"default"}, WantErr: ErrorNotExpected},
+			},
+		},
+		{
+			Name:          "ParameterTypeKeyValue",
+			ParameterType: ParameterTypeKeyValue,
+			DefaultValue:  map[string]interface{}{"default": "default"},
+			Cases: []ParameterTestCase{
+				{Name: "Valid single key-value pair, no error expected", Input: []string{"test:test"}, Expected: map[string]interface{}{"test": "test"}, WantErr: ErrorNotExpected},
+				{Name: "Valid multiple key-value pairs, no error expected", Input: []string{"test:test", "test2:test2"}, Expected: map[string]interface{}{"test": "test", "test2": "test2"}, WantErr: ErrorNotExpected},
+				{Name: "Invalid input without colon separator, error expected", Input: []string{"test"}, WantErr: ErrorExpected},
+				{Name: "No input uses default key-value map, no error expected", Input: []string{}, Expected: map[string]interface{}{"default": "default"}, WantErr: ErrorNotExpected},
+			},
+		},
+	}
 
-	i, err = parameter.ParseParameter([]string{})
-	require.NoError(t, err)
-	assert.Equal(t, map[string]interface{}{"default": "default"}, i)
+	for _, tt := range tests {
+		parameter := NewParameterDefinition(
+			"test",
+			tt.ParameterType,
+			WithChoices(tt.Choices),
+			WithDefault(tt.DefaultValue))
+
+		for _, tc := range tt.Cases {
+			t.Run(fmt.Sprintf("%s: %s", tt.Name, tc.Name), func(t *testing.T) {
+				got, err := parameter.ParseParameter(tc.Input)
+				if tc.WantErr == ErrorExpected {
+					assert.Error(t, err)
+				} else {
+					require.NoError(t, err)
+					assert.Equal(t, tc.Expected, got)
+				}
+			})
+		}
+	}
 }
 
 func TestParseStringListFromReader(t *testing.T) {
diff --git a/pkg/cmds/parameters/test-data/parameters_test.yaml b/pkg/cmds/parameters/test-data/parameters_test.yaml
index 7b5b6e7..a8bf109 100644
--- a/pkg/cmds/parameters/test-data/parameters_test.yaml
+++ b/pkg/cmds/parameters/test-data/parameters_test.yaml
@@ -1,3 +1,19 @@
+#  ParameterTypeString         ParameterType = "string"
+#  ParameterTypeStringFromFile ParameterType = "stringFromFile"
+#  ParameterTypeObjectListFromFile ParameterType = "objectListFromFile"
+#  ParameterTypeObjectFromFile     ParameterType = "objectFromFile"
+#  // ParameterTypeKeyValue signals either a string with comma separate key-value options, or when beginning with @, a file with key-value options
+#  ParameterTypeKeyValue    ParameterType = "keyValue"
+#  ParameterTypeInteger     ParameterType = "int"
+#  ParameterTypeFloat       ParameterType = "float"
+#  ParameterTypeBool        ParameterType = "bool"
+#  ParameterTypeDate        ParameterType = "date"
+#  ParameterTypeStringList  ParameterType = "stringList"
+#  ParameterTypeIntegerList ParameterType = "intList"
+#  ParameterTypeFloatList   ParameterType = "floatList"
+#  ParameterTypeChoice      ParameterType = "choice"
+#  ParameterTypeChoiceList  ParameterType = "choiceList"
+
 - name: string-flag
   type: string
   help: A string flag
@@ -26,25 +42,6 @@
   help: An int flag with an empty default value
   default: 0
 
-#  ParameterTypeString         ParameterType = "string"
-#  ParameterTypeStringFromFile ParameterType = "stringFromFile"
-#
-#  // TODO (2023-02-07) It would be great to have "list of objects from file" here
-#  // See https://github.com/go-go-golems/glazed/issues/117
-#
-#  ParameterTypeObjectListFromFile ParameterType = "objectListFromFile"
-#  ParameterTypeObjectFromFile     ParameterType = "objectFromFile"
-#  // ParameterTypeKeyValue signals either a string with comma separate key-value options, or when beginning with @, a file with key-value options
-#  ParameterTypeKeyValue    ParameterType = "keyValue"
-#  ParameterTypeInteger     ParameterType = "int"
-#  ParameterTypeFloat       ParameterType = "float"
-#  ParameterTypeBool        ParameterType = "bool"
-#  ParameterTypeDate        ParameterType = "date"
-#  ParameterTypeStringList  ParameterType = "stringList"
-#  ParameterTypeIntegerList ParameterType = "intList"
-#  ParameterTypeFloatList   ParameterType = "floatList"
-#  ParameterTypeChoice      ParameterType = "choice"
-
 - name: string-list-flag
   type: stringList
   help: A string list flag
@@ -82,6 +79,11 @@
   type: float
   help: A float flag without a default value
 
+- name: float-flag-with-int-default
+  type: float
+  help: A float flag with an int default value
+  default: 42
+
 - name: float-flag-with-empty-default
   type: float
   help: A float flag with an empty default value
@@ -90,7 +92,7 @@
 - name: float-list-flag
   type: floatList
   help: A float list flag
-  default: [1.1, 2.2, 3.3]
+  default: [1.1, 2.2, 3.3, 4, 5]
 
 - name: float-list-flag-without-default
   type: floatList
@@ -197,6 +199,17 @@
   help: A choice flag without a default value
   choices: ["choice1", "choice2"]
 
+- name: choice-list-flag
+  type: choiceList
+  help: A choice list flag
+  default: ["default", "choice1", "choice2"]
+  choices: ["default", "choice1", "choice2"]
+
+- name: choice-list-flag-without-default
+  type: choiceList
+  help: A choice list flag without a default value
+  choices: ["choice1", "choice2"]
+
 - name: key-value-flag
   type: keyValue
   help: A key-value flag
diff --git a/pkg/cmds/parameters/test-data/parameters_validity_test.yaml b/pkg/cmds/parameters/test-data/parameters_validity_test.yaml
new file mode 100644
index 0000000..8dbe510
--- /dev/null
+++ b/pkg/cmds/parameters/test-data/parameters_validity_test.yaml
@@ -0,0 +1,352 @@
+#  ParameterTypeString         ParameterType = "string"
+#  ParameterTypeStringFromFile ParameterType = "stringFromFile"
+#  ParameterTypeObjectListFromFile ParameterType = "objectListFromFile"
+#  ParameterTypeObjectFromFile     ParameterType = "objectFromFile"
+#  // ParameterTypeKeyValue signals either a string with comma separate key-value options, or when beginning with @, a file with key-value options
+#  ParameterTypeKeyValue    ParameterType = "keyValue"
+#  ParameterTypeInteger     ParameterType = "int"
+#  ParameterTypeFloat       ParameterType = "float"
+#  ParameterTypeBool        ParameterType = "bool"
+#  ParameterTypeDate        ParameterType = "date"
+#  ParameterTypeStringList  ParameterType = "stringList"
+#  ParameterTypeIntegerList ParameterType = "intList"
+#  ParameterTypeFloatList   ParameterType = "floatList"
+#  ParameterTypeChoice      ParameterType = "choice"
+#  ParameterTypeChoiceList  ParameterType = "choiceList"
+
+- name: string-flag
+  type: string
+  value: "foo"
+  valid: true
+
+- name: string-flag-empty
+  type: string
+  value: ""
+  valid: true
+
+- name: string--int-flag
+  type: string
+  value: 1
+  valid: false
+
+- name: string--list-flag
+  type: string
+  value: ["foo", "bar"]
+  valid: false
+
+- name: string-from-file-flag
+  type: stringFromFile
+  value: "foo"
+  valid: true
+
+- name: string-from-file--int-flag
+  type: stringFromFile
+  value: 1
+  valid: false
+
+- name: string-from-file--list-flag
+  type: stringFromFile
+  value: ["foo", "bar"]
+  valid: false
+
+- name: object-list-from-file-flag
+  type: objectListFromFile
+  value:
+    - foo: bar
+  valid: true
+
+- name: object-list-from-file-flag2
+  type: objectListFromFile
+  value:
+    - foo: bar
+    - blip: bar
+  valid: true
+
+- name: object-list-from-file--string-flag
+  type: objectListFromFile
+  value: "foo"
+  valid: false
+
+- name: object-list-from-file--int-flag
+  type: objectListFromFile
+  value: 1
+  valid: false
+
+- name: object-from-file-flag
+  type: objectFromFile
+  value:
+    foo: bar
+  valid: true
+
+- name: object-from-file--string-flag
+  type: objectFromFile
+  value: "foo"
+  valid: false
+
+- name: object-from-file--int-flag
+  type: objectFromFile
+  value: 1
+  valid: false
+
+- name: object-from-file--nested-object
+  type: objectFromFile
+  value:
+      foo:
+        bar: baz
+      bla: 2
+  valid: true
+
+- name: key-value-flag
+  type: keyValue
+  value:
+    foo: bar
+  valid: true
+
+- name: key-value--string-flag
+  type: keyValue
+  value: "foo"
+  valid: false
+
+- name: key-value--int-flag
+  type: keyValue
+  value: 1
+  valid: false
+
+- name: key-value--nested-object
+  type: keyValue
+  value:
+      foo:
+          bar: baz
+      bla: 2
+  valid: false
+
+- name: int-flag
+  type: int
+  value: 1
+  valid: true
+
+- name: int-flag-zero
+  type: int
+  value: 0
+  valid: true
+
+- name: int-flag-negative
+  type: int
+  value: -1
+  valid: true
+
+- name: int-flag--string-flag
+  type: int
+  value: "foo"
+  valid: false
+
+- name: int-flag--float
+  type: int
+  value: 1.1
+  valid: false
+
+- name: int-flag--list-flag
+  type: int
+  value: ["foo", "bar"]
+  valid: false
+
+- name: float-flag
+  type: float
+  value: 1.1
+  valid: true
+
+- name: float-flag-zero
+  type: float
+  value: 0.0
+  valid: true
+
+- name: float-flag-negative
+  type: float
+  value: -1.1
+  valid: true
+
+- name: float-flag--string
+  type: float
+  value: "foo"
+  valid: false
+
+- name: float-flag--int
+  type: float
+  value: 2
+  valid: true
+
+- name: float-flag--list
+  type: float
+  value: [1.1, 2.2]
+  valid: false
+
+- name: bool-flag
+  type: bool
+  value: true
+  valid: true
+
+- name: bool-flag-false
+  type: bool
+  value: false
+  valid: true
+
+- name: bool-flag--string
+  type: bool
+  value: "foo"
+  valid: false
+
+- name: bool-flag--int
+  type: bool
+  value: 1
+  valid: false
+
+- name: date-flag
+  type: date
+  value: "2023-09-09"
+  valid: true
+
+- name: date-flag-invalid
+  type: date
+  value: "2023/09/09"
+  valid: true
+
+- name: date-time-flag
+  type: date
+  value: "2023-09-09T12:00:00Z"
+  valid: true
+
+- name: date-time-flag-natural-language
+  type: date
+  value: "today"
+  valid: true
+
+- name: stringList-flag
+  type: stringList
+  value: ["foo", "bar"]
+  valid: true
+
+- name: stringList-flag--string
+  type: stringList
+  value: "foo"
+  valid: false
+
+- name: intList-flag
+  type: intList
+  value: [1, 2, 3]
+  valid: true
+
+- name: intList-flag--int
+  type: intList
+  value: 1
+  valid: false
+
+- name: floatList-flag
+  type: floatList
+  value: [1.1, 2.2]
+  valid: true
+
+- name: floatList-flag--float
+  type: floatList
+  value: 1.1
+  valid: false
+
+- name: choice-flag
+  type: choice
+  value: "option1"
+  choices:
+    - option1
+    - option2
+  valid: true
+
+- name: choice-flag-invalid
+  type: choice
+  value: "invalidOption"
+  choices:
+      - option1
+      - option2
+  valid: false
+
+- name: choiceList-flag
+  type: choiceList
+  value: ["option1", "option2"]
+  choices:
+      - option1
+      - option2
+  valid: true
+
+- name: choiceList-flag--string
+  type: choiceList
+  value: "option1"
+  choices:
+      - option1
+      - option2
+  valid: false
+
+- name: choice-flag--no-choice-list
+  type: choice
+  value: "option1"
+  valid: false
+
+- name: choiceList-flag--duplicate-values
+  type: choiceList
+  value: ["option1", "option1"]
+  choices:
+    - option1
+    - option2
+  valid: true
+
+- name: choiceList-flag--invalid-choice
+  type: choiceList
+  value: ["option1", "invalidOption"]
+  choices:
+    - option1
+    - option2
+  valid: false
+
+- name: choiceList-flag--empty-list
+  type: choiceList
+  value: []
+  choices:
+    - option1
+    - option2
+  valid: true
+
+- name: choice-flag--int-value
+  type: choice
+  value: 1
+  choices:
+    - option1
+    - option2
+  valid: false
+
+- name: choiceList-flag--mixed-values
+  type: choiceList
+  value: ["option1", 1]
+  choices:
+    - option1
+    - option2
+  valid: false
+
+- name: choice-flag--list-value
+  type: choice
+  value: ["option1"]
+  choices:
+    - option1
+    - option2
+  valid: false
+
+- name: choiceList-flag--nested-list
+  type: choiceList
+  value: ["option1", ["option2"]]
+  choices:
+    - option1
+    - option2
+  valid: false
+
+- name: choiceList-flag--only-one-valid
+  type: choiceList
+  value: ["option1", "option3"]
+  choices:
+    - option1
+    - option2
+  valid: false