libnetwork/options: GenerateFromModel: use generics

Use generics so that the produced output is already in the right
type.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
This commit is contained in:
Sebastiaan van Stijn
2025-11-27 13:59:42 +01:00
parent 14c5cd377d
commit 99066209a2
3 changed files with 16 additions and 18 deletions

View File

@@ -1865,11 +1865,7 @@ func parseContainerOptions(cOptions map[string]any) (*containerConfiguration, er
}
switch opt := genericData.(type) {
case options.Generic:
opaqueConfig, err := options.GenerateFromModel(opt, &containerConfiguration{})
if err != nil {
return nil, err
}
return opaqueConfig.(*containerConfiguration), nil
return options.GenerateFromModel[*containerConfiguration](opt)
case *containerConfiguration:
return opt, nil
default:

View File

@@ -51,10 +51,12 @@ type Generic map[string]any
//
// The return value is of the same type than the model (including a potential
// pointer qualifier).
func GenerateFromModel(options Generic, model any) (any, error) {
modType := reflect.TypeOf(model)
func GenerateFromModel[T any](options Generic) (T, error) {
var zero T
modType := reflect.TypeFor[T]()
if modType == nil {
return nil, errors.New("invalid model: model is nil")
return zero, errors.New("invalid model: model is nil")
}
isPtr := modType.Kind() == reflect.Ptr
@@ -72,21 +74,21 @@ func GenerateFromModel(options Generic, model any) (any, error) {
for name, value := range options {
field := resVal.FieldByName(name)
if !field.IsValid() {
return nil, NoSuchFieldError{Field: name, Type: resType.String()}
return zero, NoSuchFieldError{Field: name, Type: resType.String()}
}
if !field.CanSet() {
return nil, CannotSetFieldError{Field: name, Type: resType.String()}
return zero, CannotSetFieldError{Field: name, Type: resType.String()}
}
val := reflect.ValueOf(value)
if val.Type() != field.Type() {
return nil, TypeMismatchError{Field: name, ExpectType: field.Type().String(), ActualType: val.Type().String()}
return zero, TypeMismatchError{Field: name, ExpectType: field.Type().String(), ActualType: val.Type().String()}
}
field.Set(val)
}
// If the model is not of pointer type, return content of the result.
if isPtr {
return res.Interface(), nil
return res.Interface().(T), nil
}
return resVal.Interface(), nil
return resVal.Interface().(T), nil
}

View File

@@ -25,7 +25,7 @@ func TestGenerate(t *testing.T) {
Rune: 'b',
Float64: 2.0,
}
result, err := GenerateFromModel(gen, Model{})
result, err := GenerateFromModel[Model](gen)
assert.Check(t, err)
assert.Check(t, is.DeepEqual(result, expected))
}
@@ -49,7 +49,7 @@ func TestGeneratePtr(t *testing.T) {
Float64: 2.0,
}
result, err := GenerateFromModel(gen, &Model{})
result, err := GenerateFromModel[*Model](gen)
assert.NilError(t, err)
assert.Check(t, is.DeepEqual(result, expected))
}
@@ -57,7 +57,7 @@ func TestGeneratePtr(t *testing.T) {
func TestGenerateMissingField(t *testing.T) {
type Model struct{}
gen := Generic{"foo": "bar"}
_, err := GenerateFromModel(gen, Model{})
_, err := GenerateFromModel[Model](gen)
const expected = `no field "foo" in type "options.Model"`
assert.Check(t, is.Error(err, expected))
assert.Check(t, is.ErrorType(err, NoSuchFieldError{}))
@@ -68,7 +68,7 @@ func TestFieldCannotBeSet(t *testing.T) {
foo int //nolint:nolintlint,unused // un-exported field is used to test error-handling
}
gen := Generic{"foo": "bar"}
_, err := GenerateFromModel(gen, Model{})
_, err := GenerateFromModel[Model](gen)
const expected = `cannot set field "foo" of type "options.Model"`
assert.Check(t, is.Error(err, expected))
assert.Check(t, is.ErrorType(err, CannotSetFieldError{}))
@@ -79,7 +79,7 @@ func TestTypeMismatchError(t *testing.T) {
Foo int
}
gen := Generic{"Foo": "bar"}
_, err := GenerateFromModel(gen, Model{})
_, err := GenerateFromModel[Model](gen)
const expected = `type mismatch, field Foo require type int, actual type string`
assert.Check(t, is.Error(err, expected))
assert.Check(t, is.ErrorType(err, TypeMismatchError{}))