lang: types, unification: Don't recurse into private fields

We forgot to omit looking deeper into private struct fields. I don't
know why we didn't catch this earlier, I can only assume some subtlety
changed, since we've previously used many of the resources this would
fail on. Maybe golang broke some API that they didn't consider stable?

This also adds a new test for this, and ensures each resource can be
inspected too!
This commit is contained in:
James Shubin
2024-07-31 15:59:13 -04:00
parent 0a183dfff9
commit bfb5d983c1
2 changed files with 56 additions and 0 deletions

View File

@@ -120,6 +120,7 @@ func TypeOf(t reflect.Type) (*Type, error) {
StructTagOpt(StructTag),
StrictStructTagOpt(false),
SkipBadStructFieldsOpt(false),
SkipPrivateFieldsOpt(false),
AllowInterfaceTypeOpt(false),
}
return ConfigurableTypeOf(t, opts...)
@@ -132,6 +133,7 @@ func ResTypeOf(t reflect.Type) (*Type, error) {
StructTagOpt(StructTag),
StrictStructTagOpt(true),
SkipBadStructFieldsOpt(true),
SkipPrivateFieldsOpt(true),
AllowInterfaceTypeOpt(true),
}
return ConfigurableTypeOf(t, opts...)
@@ -146,6 +148,7 @@ type typeOfOptions struct {
structTag string
strictStructTag bool
skipBadStructFields bool
skipPrivateFields bool
allowInterfaceType bool
// TODO: add more options
}
@@ -175,6 +178,14 @@ func SkipBadStructFieldsOpt(skipBadStructFields bool) TypeOfOption {
}
}
// SkipPrivateFieldsOpt specifies whether we should skip over struct fields that
// are private or unexported. This is used by ResTypeOf.
func SkipPrivateFieldsOpt(skipPrivateFields bool) TypeOfOption {
return func(opt *typeOfOptions) {
opt.skipPrivateFields = skipPrivateFields
}
}
// AllowInterfaceTypeOpt specifies whether we should allow matching on an
// interface kind. This is used by ResTypeOf.
func AllowInterfaceTypeOpt(allowInterfaceType bool) TypeOfOption {
@@ -190,6 +201,7 @@ func ConfigurableTypeOf(t reflect.Type, opts ...TypeOfOption) (*Type, error) {
structTag: "",
strictStructTag: false,
skipBadStructFields: false,
skipPrivateFields: false,
allowInterfaceType: false,
}
for _, optionFunc := range opts { // apply the options
@@ -271,6 +283,9 @@ func ConfigurableTypeOf(t reflect.Type, opts ...TypeOfOption) (*Type, error) {
for i := 0; i < typ.NumField(); i++ {
field := typ.Field(i)
if options.skipPrivateFields && !field.IsExported() { // prevent infinite recursion
continue
}
tt, err := ConfigurableTypeOf(field.Type, opts...)
if err != nil {
if options.skipBadStructFields {

View File

@@ -34,10 +34,13 @@ package solvers
import (
"context"
"fmt"
"reflect"
"strings"
"testing"
"github.com/purpleidea/mgmt/engine"
_ "github.com/purpleidea/mgmt/engine/resources" // import so the resources register
engineUtil "github.com/purpleidea/mgmt/engine/util"
"github.com/purpleidea/mgmt/lang/ast"
"github.com/purpleidea/mgmt/lang/funcs/operators"
"github.com/purpleidea/mgmt/lang/funcs/vars"
@@ -982,3 +985,41 @@ func TestUnification1(t *testing.T) {
})
}
}
func TestLangFieldNameToStructType1(t *testing.T) {
for _, kind := range engine.RegisteredResourcesNames() {
t.Logf("res.Kind(): %s", kind)
res, err := engineUtil.LangFieldNameToStructType(kind)
if err != nil {
t.Errorf("error trying to inspect kind %s: %s", kind, err.Error())
continue
}
if res == nil {
t.Errorf("got nil when trying to inspect kind: %s", kind)
continue
}
}
}
func TestLangFieldNameToStructType2(t *testing.T) {
// This tests that we don't infinitely recurse inside of this function.
k := "consul:kv"
res, err := engineUtil.LangFieldNameToStructType(k)
expected := map[string]*types.Type{
"address": types.TypeStr,
"key": types.TypeStr,
"scheme": types.TypeStr,
"token": types.TypeStr,
"value": types.TypeStr,
}
if err != nil {
t.Errorf("error trying to get the field name type map: %s", err.Error())
return
}
if !reflect.DeepEqual(res, expected) {
t.Errorf("unexpected result: %+v", res)
return
}
}