Skip to content

Commit 12a8460

Browse files
authored
Fix Marshal7951 for direct union leaf calls. (#947)
When `jsonValue` is called using the output of `reflect.ValueOf()` instead of `reflect.StructField`, any interface type is lost through re-packing to the empty interface (any). This means the `reflect.Kind` of a union field is no longer the union type, but instead its underlying concrete type. The current code doesn't handle this case, leading to runtime errors. This handling code is now added, with a couple more fixes related to `empty` types. ----- Tested manually that the following ygnmi call is no longer affected by the original error: ```go sp := gnmi.OC().NetworkInstance("DEFAULT").Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_STATIC, "STATIC") return ygnmi.Replace(context.Background(), c, sp.Static("1.1.1.1/32").SetTag().Config(), oc.NetworkInstance_Protocol_Static_SetTag_Union(oc.UnionUint32(42))) ```
1 parent dca67e2 commit 12a8460

File tree

3 files changed

+133
-11
lines changed

3 files changed

+133
-11
lines changed

testutil/types.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ func (Binary) Is_UnionLeafTypeSimple() {}
9898
func (UnionString) IsExampleUnion() {}
9999
func (UnionFloat64) IsExampleUnion() {}
100100
func (UnionInt64) IsExampleUnion() {}
101+
func (UnionUint32) IsExampleUnion() {}
101102
func (UnionBool) IsExampleUnion() {}
102103
func (YANGEmpty) IsExampleUnion() {}
103104
func (Binary) IsExampleUnion() {}

ygot/render.go

Lines changed: 58 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -859,7 +859,7 @@ func EncodeTypedValue(val any, enc gnmipb.Encoding, opts ...EncodeTypedValueOpt)
859859
if err != nil {
860860
return nil, fmt.Errorf("cannot marshal enum, %v", err)
861861
}
862-
return &gnmipb.TypedValue{Value: &gnmipb.TypedValue_StringVal{en}}, nil
862+
return &gnmipb.TypedValue{Value: &gnmipb.TypedValue_StringVal{StringVal: en}}, nil
863863
}
864864

865865
vv := reflect.ValueOf(val)
@@ -871,9 +871,9 @@ func EncodeTypedValue(val any, enc gnmipb.Encoding, opts ...EncodeTypedValueOpt)
871871
return nil, fmt.Errorf("cannot represent field value %v as TypedValue", val)
872872
case vv.Type().Name() == BinaryTypeName:
873873
// This is a binary type which is defined as a []byte, so we encode it as the bytes.
874-
return &gnmipb.TypedValue{Value: &gnmipb.TypedValue_BytesVal{vv.Bytes()}}, nil
874+
return &gnmipb.TypedValue{Value: &gnmipb.TypedValue_BytesVal{BytesVal: vv.Bytes()}}, nil
875875
case vv.Type().Name() == EmptyTypeName:
876-
return &gnmipb.TypedValue{Value: &gnmipb.TypedValue_BoolVal{vv.Bool()}}, nil
876+
return &gnmipb.TypedValue{Value: &gnmipb.TypedValue_BoolVal{BoolVal: vv.Bool()}}, nil
877877
case vv.Kind() == reflect.Slice:
878878
sval, err := leaflistToSlice(vv, false)
879879
if err != nil {
@@ -884,7 +884,7 @@ func EncodeTypedValue(val any, enc gnmipb.Encoding, opts ...EncodeTypedValueOpt)
884884
if err != nil {
885885
return nil, err
886886
}
887-
return &gnmipb.TypedValue{Value: &gnmipb.TypedValue_LeaflistVal{arr}}, nil
887+
return &gnmipb.TypedValue{Value: &gnmipb.TypedValue_LeaflistVal{LeaflistVal: arr}}, nil
888888
case util.IsValueStructPtr(vv):
889889
nv, err := unwrapUnionInterfaceValue(vv, false)
890890
if err != nil {
@@ -893,7 +893,7 @@ func EncodeTypedValue(val any, enc gnmipb.Encoding, opts ...EncodeTypedValueOpt)
893893
vv = reflect.ValueOf(nv)
894894
// Apart from binary, all other possible union subtypes are scalars or typedefs of scalars.
895895
if vv.Type().Name() == BinaryTypeName {
896-
return &gnmipb.TypedValue{Value: &gnmipb.TypedValue_BytesVal{vv.Bytes()}}, nil
896+
return &gnmipb.TypedValue{Value: &gnmipb.TypedValue_BytesVal{BytesVal: vv.Bytes()}}, nil
897897
}
898898
case util.IsValuePtr(vv):
899899
vv = vv.Elem()
@@ -1593,6 +1593,15 @@ func jsonValue(field reflect.Value, parentMod string, args jsonOutputConfig) (an
15931593

15941594
prependModuleNameIref := args.rfc7951Config != nil && (args.rfc7951Config.AppendModuleName || args.rfc7951Config.PrependModuleNameIdentityref)
15951595

1596+
// When jsonValue is called using the output of reflect.ValueOf()
1597+
// instead of reflect.StructField, any interface type is lost through
1598+
// re-packing to the empty interface (any). This means the Kind of a
1599+
// union field is no longer the union type, but its underlying concrete
1600+
// type. This flag is used to detect failures during unmarshalling that
1601+
// may be due to this issue, which will be handled later assuming that
1602+
// the given type is named using one of the ygot-generated union names.
1603+
var mightBeUnion bool
1604+
15961605
switch field.Kind() {
15971606
case reflect.Map:
15981607
var err error
@@ -1663,6 +1672,10 @@ func jsonValue(field reflect.Value, parentMod string, args jsonOutputConfig) (an
16631672
// For output, we map the enumerated value to the string name of the enum.
16641673
v, set, err := enumFieldToString(field, prependModuleNameIref)
16651674
if err != nil {
1675+
if _, ok := unionSingletonUnderlyingTypes[field.Type().Name()]; ok {
1676+
mightBeUnion = true
1677+
break
1678+
}
16661679
return nil, err
16671680
}
16681681

@@ -1692,6 +1705,15 @@ func jsonValue(field reflect.Value, parentMod string, args jsonOutputConfig) (an
16921705
return nil, err
16931706
}
16941707
return value, nil
1708+
case field.Elem().Kind() == reflect.Bool && field.Elem().Type().Name() == EmptyTypeName:
1709+
switch {
1710+
case args.jType == RFC7951 && field.Elem().Bool():
1711+
return []any{nil}, nil
1712+
case field.Elem().Bool():
1713+
return true, nil
1714+
default:
1715+
return nil, nil
1716+
}
16951717
default:
16961718
if value, err = resolveUnionVal(field.Elem().Interface(), prependModuleNameIref); err != nil {
16971719
return nil, err
@@ -1704,14 +1726,39 @@ func jsonValue(field reflect.Value, parentMod string, args jsonOutputConfig) (an
17041726
// A non-pointer field of type boolean is an empty leaf within the YANG schema.
17051727
// For RFC7951 this is represented as a null JSON array (i.e., [null]). For internal
17061728
// JSON if the leaf is present and set, it is rendered as 'true', or as nil otherwise.
1707-
switch {
1708-
case args.jType == RFC7951 && field.Type().Name() == EmptyTypeName && field.Bool():
1709-
value = []any{nil}
1710-
case field.Bool():
1711-
value = true
1729+
if field.Type().Name() == EmptyTypeName {
1730+
switch {
1731+
case args.jType == RFC7951 && field.Bool():
1732+
value = []any{nil}
1733+
case field.Bool():
1734+
value = true
1735+
}
1736+
} else {
1737+
if _, ok := unionSingletonUnderlyingTypes[field.Type().Name()]; ok {
1738+
mightBeUnion = true
1739+
break
1740+
}
17121741
}
17131742
default:
1714-
return nil, fmt.Errorf("got unexpected field type, was: %v", field.Kind())
1743+
mightBeUnion = true
1744+
}
1745+
1746+
if mightBeUnion {
1747+
underlyingType, ok := unionSingletonUnderlyingTypes[field.Type().Name()]
1748+
if !ok {
1749+
return nil, fmt.Errorf("got unexpected field type, was: %v (%s)", field.Kind(), field.Type().Name())
1750+
}
1751+
1752+
if !field.Type().ConvertibleTo(underlyingType) {
1753+
return nil, fmt.Errorf("ygot internal error: union type %q inconvertible to underlying type %q", field.Type().Name(), underlyingType)
1754+
}
1755+
var err error
1756+
if value, err = resolveUnionVal(field.Interface(), prependModuleNameIref); err != nil {
1757+
return nil, err
1758+
}
1759+
if args.jType == RFC7951 {
1760+
value = writeIETFScalarJSON(value)
1761+
}
17151762
}
17161763

17171764
if errs.Err() != nil {

ygot/render_test.go

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2776,6 +2776,43 @@ func TestConstructJSON(t *testing.T) {
27762776
"transport-address-simple": testutil.UnionString("42.42.42.42"),
27772777
},
27782778
},
2779+
}, {
2780+
name: "union example - bool",
2781+
in: &exampleBgpNeighbor{
2782+
TransportAddressSimple: testutil.UnionBool(true),
2783+
},
2784+
wantIETF: map[string]any{
2785+
"state": map[string]any{
2786+
"transport-address-simple": true,
2787+
},
2788+
},
2789+
wantInternal: map[string]any{
2790+
"state": map[string]any{
2791+
"transport-address-simple": testutil.UnionBool(true),
2792+
},
2793+
},
2794+
}, {
2795+
name: "union example - empty true",
2796+
in: &exampleBgpNeighbor{
2797+
TransportAddressSimple: testutil.YANGEmpty(true),
2798+
},
2799+
wantIETF: map[string]any{
2800+
"state": map[string]any{
2801+
"transport-address-simple": []any{nil},
2802+
},
2803+
},
2804+
wantInternal: map[string]any{
2805+
"state": map[string]any{
2806+
"transport-address-simple": true,
2807+
},
2808+
},
2809+
}, {
2810+
name: "union example - empty",
2811+
in: &exampleBgpNeighbor{
2812+
TransportAddressSimple: testutil.YANGEmpty(false),
2813+
},
2814+
wantIETF: map[string]any{},
2815+
wantInternal: map[string]any{},
27792816
}, {
27802817
name: "union example - enum",
27812818
in: &exampleBgpNeighbor{
@@ -4180,6 +4217,43 @@ func TestMarshal7951(t *testing.T) {
41804217
Str: String("test-string"),
41814218
},
41824219
want: `{"str":"test-string"}`,
4220+
}, {
4221+
desc: "simple GoStruct union fields",
4222+
in: &renderExample{
4223+
UnionValSimple: testBinary,
4224+
UnionLeafListSimple: []exampleUnion{
4225+
testBinary,
4226+
EnumTestVALTWO,
4227+
testutil.UnionInt64(42),
4228+
testutil.UnionFloat64(3.14),
4229+
testutil.UnionString("hello"),
4230+
},
4231+
},
4232+
want: `{"union-list-simple":["` + base64testStringEncoded + `","VAL_TWO","42","3.14","hello"],"union-val-simple":"` + base64testStringEncoded + `"}`,
4233+
}, {
4234+
desc: "simple GoStruct string union field",
4235+
in: exampleUnion(testutil.UnionString("test-string")),
4236+
want: `"test-string"`,
4237+
}, {
4238+
desc: "simple GoStruct int64 union field",
4239+
in: exampleUnion(testutil.UnionInt64(42)),
4240+
want: `"42"`,
4241+
}, {
4242+
desc: "simple GoStruct uint32 union field",
4243+
in: exampleUnion(testutil.UnionUint32(42)),
4244+
want: `42`,
4245+
}, {
4246+
desc: "simple GoStruct empty union field",
4247+
in: exampleUnion(testutil.YANGEmpty(true)),
4248+
want: `[null]`,
4249+
}, {
4250+
desc: "simple GoStruct bool union field",
4251+
in: exampleUnion(testutil.UnionBool(true)),
4252+
want: `true`,
4253+
}, {
4254+
desc: "simple GoStruct enum union field",
4255+
in: exampleUnion(EnumTestVALONE),
4256+
want: `"VAL_ONE"`,
41834257
}, {
41844258
desc: "nil GoStruct",
41854259
in: (*renderExample)(nil),

0 commit comments

Comments
 (0)