From 815da893873fa2ebf018188a24b577466100012f Mon Sep 17 00:00:00 2001 From: wenovus Date: Mon, 18 Apr 2022 16:37:44 -0700 Subject: [PATCH] Add `RFC7951JSONConfig.PrependModuleNameNonRootTopLevel` flag ``` // PrependModuleNameNonRootTopLevel specifies that the top-level field // names of a non-root ValidatedGoStruct being marshalled should always // have their belonging-module names prepended in the JSON output, // instead of only being present when its namespace differs from the // namespace of its parent. // NOTE: This flag is used to preserve pre-ygot@v0.17.0 behaviour in // code that depends on it. This will be deprecated in the future. PrependModuleNameNonRootTopLevel bool ``` --- ygot/render.go | 20 ++++++-- ygot/render_test.go | 116 ++++++++++++++++++++++++++++++++++++++------ 2 files changed, 118 insertions(+), 18 deletions(-) diff --git a/ygot/render.go b/ygot/render.go index 32251319b..5b8f84435 100644 --- a/ygot/render.go +++ b/ygot/render.go @@ -916,6 +916,14 @@ type RFC7951JSONConfig struct { // elements that are defined within a different YANG module than their // parent. AppendModuleName bool + // PrependModuleNameNonRootTopLevel specifies that the top-level field + // names of a non-root ValidatedGoStruct being marshalled should always + // have their belonging-module names prepended in the JSON output, + // instead of only being present when its namespace differs from the + // namespace of its parent. + // NOTE: This flag is used to preserve pre-ygot@v0.17.0 behaviour in + // code that depends on it. This will be deprecated in the future. + PrependModuleNameNonRootTopLevel bool // PrependModuleNameIdentityref determines whether the module name is // prepended to identityref values. AppendModuleName (should be named // PrependModuleName) subsumes and overrides this flag. @@ -951,7 +959,11 @@ func (*RFC7951JSONConfig) IsMarshal7951Arg() {} // to JSON described by RFC7951. The supplied args control options corresponding // to the method by which JSON is marshalled. func ConstructIETFJSON(s ValidatedGoStruct, args *RFC7951JSONConfig) (map[string]interface{}, error) { - return structJSON(s, s.ΛBelongingModule(), jsonOutputConfig{ + var parentMod string + if args == nil || !args.PrependModuleNameNonRootTopLevel { + parentMod = s.ΛBelongingModule() + } + return structJSON(s, parentMod, jsonOutputConfig{ jType: RFC7951, rfc7951Config: args, }) @@ -1001,8 +1013,10 @@ func Marshal7951(d interface{}, args ...Marshal7951Arg) ([]byte, error) { } } var parentMod string - if s, ok := d.(ValidatedGoStruct); ok { - parentMod = s.ΛBelongingModule() + if rfcCfg == nil || !rfcCfg.PrependModuleNameNonRootTopLevel { + if s, ok := d.(ValidatedGoStruct); ok { + parentMod = s.ΛBelongingModule() + } } j, err := jsonValue(reflect.ValueOf(d), parentMod, jsonOutputConfig{ jType: RFC7951, diff --git a/ygot/render_test.go b/ygot/render_test.go index c479a8540..620a414cc 100644 --- a/ygot/render_test.go +++ b/ygot/render_test.go @@ -1948,17 +1948,18 @@ func (t *unmarshalableJSON) UnmarshalJSON(d []byte) error { func TestConstructJSON(t *testing.T) { tests := []struct { - name string - in ValidatedGoStruct - inAppendMod bool - inPrependModIref bool - inRewriteModuleNameRules map[string]string - inPreferShadowPath bool - wantIETF map[string]interface{} - wantInternal map[string]interface{} - wantSame bool - wantErr bool - wantJSONErr bool + name string + in ValidatedGoStruct + inAppendMod bool + inPrependModIref bool + inPrependModNonRootTopLevel bool + inRewriteModuleNameRules map[string]string + inPreferShadowPath bool + wantIETF map[string]interface{} + wantInternal map[string]interface{} + wantSame bool + wantErr bool + wantJSONErr bool }{{ name: "invalidGoStruct", in: &invalidGoStructChild{ @@ -2798,6 +2799,50 @@ func TestConstructJSON(t *testing.T) { "f5": "hat", }, }, + }, { + name: "module append example with prepending top-level module names", + in: &ietfRenderExample{ + F1: String("foo"), + F2: String("bar"), + F3: &ietfRenderExampleChild{ + F4: String("baz"), + F5: String("hat"), + }, + F6: String("mat"), + F7: String("bat"), + }, + inAppendMod: true, + inPrependModNonRootTopLevel: true, + wantIETF: map[string]interface{}{ + "f1mod:f1": "foo", + "f1mod:config": map[string]interface{}{ + "f2mod:f6": "mat", + }, + "f2mod:config": map[string]interface{}{ + "f2": "bar", + "f3mod:f7": "bat", + }, + "f1mod:f3": map[string]interface{}{ + "f42mod:config": map[string]interface{}{ + "f4": "baz", + }, + "f5": "hat", + }, + }, + wantInternal: map[string]interface{}{ + "f1": "foo", + "config": map[string]interface{}{ + "f2": "bar", + "f6": "mat", + "f7": "bat", + }, + "f3": map[string]interface{}{ + "config": map[string]interface{}{ + "f4": "baz", + }, + "f5": "hat", + }, + }, }, { name: "list at root", in: &listAtRoot{ @@ -2967,10 +3012,11 @@ func TestConstructJSON(t *testing.T) { for _, tt := range tests { t.Run(tt.name+" ConstructIETFJSON", func(t *testing.T) { gotietf, err := ConstructIETFJSON(tt.in, &RFC7951JSONConfig{ - AppendModuleName: tt.inAppendMod, - PrependModuleNameIdentityref: tt.inPrependModIref, - RewriteModuleNames: tt.inRewriteModuleNameRules, - PreferShadowPath: tt.inPreferShadowPath, + AppendModuleName: tt.inAppendMod, + PrependModuleNameNonRootTopLevel: tt.inPrependModNonRootTopLevel, + PrependModuleNameIdentityref: tt.inPrependModIref, + RewriteModuleNames: tt.inRewriteModuleNameRules, + PreferShadowPath: tt.inPreferShadowPath, }) if (err != nil) != tt.wantErr { t.Fatalf("ConstructIETFJSON(%v): got unexpected error: %v, want error %v", tt.in, err, tt.wantErr) @@ -3859,6 +3905,15 @@ func TestMarshal7951(t *testing.T) { &RFC7951JSONConfig{AppendModuleName: true}, }, want: `{"f1":"hello"}`, + }, { + desc: "append module names with non-root top-level requested", + in: &ietfRenderExample{ + F1: String("hello"), + }, + inArgs: []Marshal7951Arg{ + &RFC7951JSONConfig{AppendModuleName: true, PrependModuleNameNonRootTopLevel: true}, + }, + want: `{"f1mod:f1":"hello"}`, }, { desc: "complex children with module name prepend request", in: &ietfRenderExample{ @@ -3889,6 +3944,37 @@ func TestMarshal7951(t *testing.T) { "test", 42 ] +}`, + }, { + desc: "complex children with module name prepend request with non-root top-level module prepend request", + in: &ietfRenderExample{ + F2: String("bar"), + MixedList: []interface{}{EnumTestVALONE, "test", 42}, + EnumList: map[EnumTest]*ietfRenderExampleEnumList{ + EnumTestVALONE: {Key: EnumTestVALONE}, + }, + }, + inArgs: []Marshal7951Arg{ + &RFC7951JSONConfig{AppendModuleName: true, PrependModuleNameNonRootTopLevel: true}, + JSONIndent(" "), + }, + want: `{ + "f1mod:enum-list": [ + { + "config": { + "key": "foo:VAL_ONE" + }, + "key": "foo:VAL_ONE" + } + ], + "f1mod:mixed-list": [ + "foo:VAL_ONE", + "test", + 42 + ], + "f2mod:config": { + "f2": "bar" + } }`, }, { desc: "complex children with PrependModuleNameIdentityref=true",