Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for mapping leaf-lists of unions to protobufs. #927

Merged
merged 5 commits into from
Nov 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ require (
github.com/openconfig/goyang v1.4.3
github.com/openconfig/gribi v1.0.0
github.com/pmezard/go-difflib v1.0.0
github.com/spf13/cobra v1.7.0
github.com/spf13/cobra v1.8.0
github.com/spf13/viper v1.15.0
golang.org/x/exp v0.0.0-20230801115018-d63ba01acd4b
google.golang.org/grpc v1.58.0-dev
Expand Down
6 changes: 3 additions & 3 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
Expand Down Expand Up @@ -188,8 +188,8 @@ github.com/spf13/afero v1.9.5 h1:stMpOSZFs//0Lv29HduCmli3GUfpFoF3Y1Q/aXj/wVM=
github.com/spf13/afero v1.9.5/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ=
github.com/spf13/cast v1.5.1 h1:R+kOtfhWQE6TVQzY+4D7wJLBgkdVasCEFxSUBYBYIlA=
github.com/spf13/cast v1.5.1/go.mod h1:b9PdjNptOpzXr7Rq1q9gJML/2cdGQAo69NKzQ10KN48=
github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I=
github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0=
github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0=
github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho=
github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
Expand Down
9 changes: 4 additions & 5 deletions protomap/integration_tests/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -231,10 +231,10 @@ func TestGRIBIAFTToStruct(t *testing.T) {
Value: &gpb.TypedValue_LeaflistVal{
LeaflistVal: &gpb.ScalarArray{
Element: []*gpb.TypedValue{
mustValue(t, 20),
mustValue(t, 30),
mustValue(t, 40),
mustValue(t, 50),
mustValue(t, uint64(20)),
mustValue(t, uint64(30)),
mustValue(t, uint64(40)),
mustValue(t, uint64(50)),
},
},
},
Expand All @@ -253,7 +253,6 @@ func TestGRIBIAFTToStruct(t *testing.T) {
PushedMplsLabelStackUint64: 50,
}},
},
wantErr: true, // Currently this is unhandled but was causing a panic, check that it doesn't panic.
}}

for _, tt := range tests {
Expand Down
151 changes: 147 additions & 4 deletions protomap/proto.go
Original file line number Diff line number Diff line change
Expand Up @@ -651,8 +651,13 @@ func protoFromPathsInternal(p proto.Message, vals map[*gpb.Path]any, valPrefix,
mapped[chp] = true
m.Set(fd, v)
case leaflistunion:
rangeErr = fmt.Errorf("%s: unhandled leaf-list of unions", fd.FullName())
return false
v, err := makeUnionLeafList(m, fd, chv)
if err != nil {
rangeErr = err
return false
}
mapped[chp] = true
m.Set(fd, v)
default:
v, isWrap, err := makeWrapper(m, fd, chv)
if err != nil {
Expand Down Expand Up @@ -999,6 +1004,144 @@ func isWrapper(msg protoreflect.Message, fd protoreflect.FieldDescriptor) bool {
}
}

// makeUnionLeafList makes a protoreflect.Value corresponding to the leaf-list field fd of message m, containing
// the values within chv, which is checked to be either a slice of Go inbuilt values, or gNMI TypedValue
// protobufs. It returns an error if the leaf-list cannot be created.
func makeUnionLeafList(msg protoreflect.Message, fd protoreflect.FieldDescriptor, chv any) (protoreflect.Value, error) {
newV := msg.NewField(fd)

inputVal := reflect.ValueOf(chv)
switch {
case inputVal.Kind() != reflect.Slice && !util.IsValueStructPtr(inputVal):
return protoreflect.ValueOf(nil), fmt.Errorf("invalid value %v (%T) for a leaf-list of unions", chv, chv)
case util.IsValueStructPtr(inputVal):
tv, ok := inputVal.Interface().(*gpb.TypedValue)
if !ok {
return protoreflect.ValueOf(nil), fmt.Errorf("invalid struct type in slice %v (%T) for a leaf-list of unions", chv, inputVal.Elem().Interface())
}

for _, inputVal := range tv.GetLeaflistVal().GetElement() {
protoListElem := newV.List().NewElement()
var (
retErr error
handled bool
)
unpopRange{protoListElem.Message()}.Range(func(fd protoreflect.FieldDescriptor, v protoreflect.Value) bool {
switch ee := inputVal.GetValue().(type) {
case *gpb.TypedValue_StringVal:
if fd.Kind() == protoreflect.StringKind {
protoListElem.Message().Set(fd, protoreflect.ValueOfString(ee.StringVal))
newV.List().Append(protoListElem)
handled = true
return false
}
if fd.Kind() == protoreflect.EnumKind {
ev, err := enumValue(fd, ee.StringVal)
if err != nil {
retErr = err
return false
}
protoListElem.Message().Set(fd, ev)
newV.List().Append(protoListElem)
handled = true
return false
}
case *gpb.TypedValue_UintVal:
if fd.Kind() == protoreflect.Uint64Kind {
protoListElem.Message().Set(fd, protoreflect.ValueOfUint64(ee.UintVal))
newV.List().Append(protoListElem)
handled = true
return false
}
case *gpb.TypedValue_BoolVal:
if fd.Kind() == protoreflect.BoolKind {
protoListElem.Message().Set(fd, protoreflect.ValueOfBool(ee.BoolVal))
newV.List().Append(protoListElem)
handled = true
return false
}
default:
// TODO(robjs): implement type handling for other TypedValues.
retErr = fmt.Errorf("unhandled type %T in leaf-list of unions", ee)
return false
}
return true
})
if retErr != nil {
return protoreflect.ValueOf(nil), retErr
}
if !handled {
return protoreflect.ValueOf(nil), fmt.Errorf("invalid type %T for value %v in union", inputVal.GetValue(), inputVal)
}
}
return newV, nil
}

for i := 0; i < inputVal.Len(); i++ {
protoListElem := newV.List().NewElement()
inputElem := inputVal.Index(i)
var (
retErr error
handled bool
)

unpopRange{protoListElem.Message()}.Range(func(fd protoreflect.FieldDescriptor, v protoreflect.Value) bool {
if inputElem.Kind() != reflect.Interface {
retErr = fmt.Errorf("invalid input type for leaf-list of unions, %T, expect []any", inputElem.Interface())
return false
}

switch inputElem.Elem().Kind() {
case reflect.String:
if fd.Kind() == protoreflect.StringKind {
protoListElem.Message().Set(fd, protoreflect.ValueOfString(inputElem.Elem().String()))
newV.List().Append(protoListElem)
handled = true
return false
}
if fd.Kind() == protoreflect.EnumKind {
v, err := enumValue(fd, inputElem.Interface())
if err != nil {
retErr = err
return false
}
protoListElem.Message().Set(fd, v)
newV.List().Append(protoListElem)
handled = true
return false
}
case reflect.Uint64:
if fd.Kind() == protoreflect.Uint64Kind {
protoListElem.Message().Set(fd, protoreflect.ValueOfUint64(inputElem.Elem().Uint()))
newV.List().Append(protoListElem)
handled = true
return false
}
case reflect.Bool:
if fd.Kind() == protoreflect.BoolKind {
protoListElem.Message().Set(fd, protoreflect.ValueOfBool(inputElem.Elem().Bool()))
newV.List().Append(protoListElem)
handled = true
return false
}
default:
retErr = fmt.Errorf("unhandled type %T for union", inputElem.Interface())
return false
}

return true
})
if retErr != nil {
return protoreflect.ValueOf(nil), retErr
}
if !handled {
return protoreflect.ValueOf(nil), fmt.Errorf("invalid type %T for value %v in union", inputElem.Interface(), inputElem.Interface())
}
}

return newV, nil
}

// makeSimpleLeafList makes a repeated value of wrapper protobufs for the field fd of the message msg containing
// the values in chv. It returns an error if the type is unhandled.
func makeSimpleLeafList(msg protoreflect.Message, fd protoreflect.FieldDescriptor, chv any) (protoreflect.Value, error) {
Expand Down Expand Up @@ -1098,9 +1241,9 @@ func makeSimpleLeafList(msg protoreflect.Message, fd protoreflect.FieldDescripto
return protoreflect.ValueOf(nil), fmt.Errorf("invalid type %T (value: %v) for repeated byte field", lv, lv)
}
return newV, nil
default:
return protoreflect.ValueOf(nil), fmt.Errorf("unhandled leaf-list value, %v", chv)
}

return protoreflect.ValueOf(nil), fmt.Errorf("unhandled leaf-list value, %v", chv)
}

// enumValue returns the concrete implementation of the enumeration with the yang_name annotation set
Expand Down
Loading
Loading