Skip to content

Commit

Permalink
Add a new JSON marshal option that prefers the shadow path name. (#530)
Browse files Browse the repository at this point in the history
* Add a new JSON marshal option that prefers the shadow path name.

This allows a user who is looking to multiplex config and telemetry operations on the same set of GoStructs to treat the GoStructs as either a config struct or a telemetry struct by simplying tweaking this marshal flag.
  • Loading branch information
wenovus authored May 18, 2021
1 parent 683aef6 commit 172a0e6
Show file tree
Hide file tree
Showing 4 changed files with 159 additions and 16 deletions.
9 changes: 6 additions & 3 deletions ygot/render.go
Original file line number Diff line number Diff line change
Expand Up @@ -382,7 +382,7 @@ func findUpdatedLeaves(leaves map[*path]interface{}, s GoStruct, parent *gnmiPat
}
}

mapPaths, err := structTagToLibPaths(ftype, parent)
mapPaths, err := structTagToLibPaths(ftype, parent, false)
if err != nil {
errs.Add(fmt.Errorf("%v->%s: %v", parent, ftype.Name, err))
continue
Expand Down Expand Up @@ -917,6 +917,10 @@ type RFC7951JSONConfig struct {
// elements that are defined within a different YANG module than their
// parent.
AppendModuleName bool
// PreferShadowPath uses the name of the "shadow-path" tag of a
// GoStruct to determine the marshalled path elements instead of the
// "path" tag, whenever the former is present.
PreferShadowPath bool
}

// IsMarshal7951Arg marks the RFC7951JSONConfig struct as a valid argument to
Expand Down Expand Up @@ -977,7 +981,6 @@ func Marshal7951(d interface{}, args ...Marshal7951Arg) ([]byte, error) {
case JSONIndent:
indent = string(v)
}

}
j, err := jsonValue(reflect.ValueOf(d), "", jsonOutputConfig{
jType: RFC7951,
Expand Down Expand Up @@ -1056,7 +1059,7 @@ func structJSON(s GoStruct, parentMod string, args jsonOutputConfig) (map[string
appendModName = true
}

mapPaths, err := structTagToLibPaths(fType, newStringSliceGNMIPath([]string{}))
mapPaths, err := structTagToLibPaths(fType, newStringSliceGNMIPath([]string{}), args.rfc7951Config != nil && args.rfc7951Config.PreferShadowPath)
if err != nil {
errs.Add(fmt.Errorf("%s: %v", fType.Name, err))
continue
Expand Down
21 changes: 20 additions & 1 deletion ygot/render_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -766,7 +766,7 @@ type YANGEmpty bool

// renderExample is used within TestTogNMINotifications as a GoStruct.
type renderExample struct {
Str *string `path:"str"`
Str *string `path:"str" shadow-path:"srt"`
IntVal *int32 `path:"int-val"`
Int64Val *int64 `path:"int64-val"`
FloatVal *float32 `path:"floatval"`
Expand Down Expand Up @@ -3342,13 +3342,32 @@ func TestMarshal7951(t *testing.T) {
Str: String("test-string"),
},
want: `{"str":"test-string"}`,
}, {
desc: "simple GoStruct with PreferShadowPath",
in: &renderExample{
Str: String("test-string"),
},
inArgs: []Marshal7951Arg{
&RFC7951JSONConfig{PreferShadowPath: true},
},
want: `{"srt":"test-string"}`,
}, {
desc: "map of GoStructs",
in: map[string]*renderExample{
"one": {Str: String("one")},
"two": {Str: String("two")},
},
want: `[{"str":"one"},{"str":"two"}]`,
}, {
desc: "map of GoStructs with PreferShadowPath",
in: map[string]*renderExample{
"one": {Str: String("one")},
"two": {Str: String("two")},
},
inArgs: []Marshal7951Arg{
&RFC7951JSONConfig{PreferShadowPath: true},
},
want: `[{"srt":"one"},{"srt":"two"}]`,
}, {
desc: "map of invalid type",
in: map[string]string{
Expand Down
14 changes: 10 additions & 4 deletions ygot/struct_validation_map.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,17 +39,23 @@ const (
indentString string = " "
)

// structTagsToLibPaths takes an input struct field as a reflect.Type, and determines
// structTagToLibPaths takes an input struct field as a reflect.Type, and determines
// the set of validation library paths that it maps to. Returns the paths as a slice of
// empty interface slices, or an error.
func structTagToLibPaths(f reflect.StructField, parentPath *gnmiPath) ([]*gnmiPath, error) {
func structTagToLibPaths(f reflect.StructField, parentPath *gnmiPath, preferShadowPath bool) ([]*gnmiPath, error) {
if !parentPath.isValid() {
return nil, fmt.Errorf("invalid path format in parentPath (%v, %v)", parentPath.stringSlicePath == nil, parentPath.pathElemPath == nil)
}

pathAnnotation, ok := f.Tag.Lookup("path")
var pathAnnotation string
var ok bool
if preferShadowPath {
pathAnnotation, ok = f.Tag.Lookup("shadow-path")
}
if !ok {
return nil, fmt.Errorf("field did not specify a path")
if pathAnnotation, ok = f.Tag.Lookup("path"); !ok {
return nil, fmt.Errorf("field did not specify a path")
}
}

var mapPaths []*gnmiPath
Expand Down
131 changes: 123 additions & 8 deletions ygot/struct_validation_map_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,11 +52,12 @@ func errToString(err error) string {

func TestStructTagToLibPaths(t *testing.T) {
tests := []struct {
name string
inField reflect.StructField
inParent *gnmiPath
want []*gnmiPath
wantErr bool
name string
inField reflect.StructField
inParent *gnmiPath
inPreferShadowPath bool
want []*gnmiPath
wantErr bool
}{{
name: "invalid input path",
inField: reflect.StructField{
Expand All @@ -80,6 +81,43 @@ func TestStructTagToLibPaths(t *testing.T) {
want: []*gnmiPath{{
stringSlicePath: []string{"foo"},
}},
}, {
name: "multi-element single tag example",
inField: reflect.StructField{
Name: "field",
Tag: `path:"foo/bar"`,
},
inParent: &gnmiPath{
stringSlicePath: []string{},
},
want: []*gnmiPath{{
stringSlicePath: []string{"foo", "bar"},
}},
}, {
name: "multi-element single tag with shadow-path example",
inField: reflect.StructField{
Name: "field",
Tag: `path:"foo/bar" shadow-path:"far/boo"`,
},
inParent: &gnmiPath{
stringSlicePath: []string{},
},
want: []*gnmiPath{{
stringSlicePath: []string{"foo", "bar"},
}},
}, {
name: "multi-element single tag with preferred shadow-path example",
inField: reflect.StructField{
Name: "field",
Tag: `path:"foo/bar" shadow-path:"far/boo"`,
},
inParent: &gnmiPath{
stringSlicePath: []string{},
},
inPreferShadowPath: true,
want: []*gnmiPath{{
stringSlicePath: []string{"far", "boo"},
}},
}, {
name: "empty tag example",
inField: reflect.StructField{
Expand Down Expand Up @@ -132,6 +170,19 @@ func TestStructTagToLibPaths(t *testing.T) {
want: []*gnmiPath{{
pathElemPath: []*gnmipb.PathElem{{Name: "foo"}},
}},
}, {
name: "simple pathelem single tag with shadow-path preferred but not found example",
inField: reflect.StructField{
Name: "field",
Tag: `path:"foo"`,
},
inPreferShadowPath: true,
inParent: &gnmiPath{
pathElemPath: []*gnmipb.PathElem{},
},
want: []*gnmiPath{{
pathElemPath: []*gnmipb.PathElem{{Name: "foo"}},
}},
}, {
name: "empty tag pathelem example",
inField: reflect.StructField{
Expand All @@ -144,6 +195,43 @@ func TestStructTagToLibPaths(t *testing.T) {
want: []*gnmiPath{{
pathElemPath: []*gnmipb.PathElem{},
}},
}, {
name: "multi-element single tag pathelem example",
inField: reflect.StructField{
Name: "field",
Tag: `path:"foo/bar"`,
},
inParent: &gnmiPath{
pathElemPath: []*gnmipb.PathElem{},
},
want: []*gnmiPath{{
pathElemPath: []*gnmipb.PathElem{{Name: "foo"}, {Name: "bar"}},
}},
}, {
name: "multi-element single tag with shadow-path pathelem example",
inField: reflect.StructField{
Name: "field",
Tag: `path:"foo/bar" shadow-path:"far/boo"`,
},
inParent: &gnmiPath{
pathElemPath: []*gnmipb.PathElem{},
},
want: []*gnmiPath{{
pathElemPath: []*gnmipb.PathElem{{Name: "foo"}, {Name: "bar"}},
}},
}, {
name: "multi-element single tag with preferred shadow-path pathelem example",
inField: reflect.StructField{
Name: "field",
Tag: `path:"foo/bar" shadow-path:"far/boo"`,
},
inPreferShadowPath: true,
inParent: &gnmiPath{
pathElemPath: []*gnmipb.PathElem{},
},
want: []*gnmiPath{{
pathElemPath: []*gnmipb.PathElem{{Name: "far"}, {Name: "boo"}},
}},
}, {
name: "multiple pathelem path",
inField: reflect.StructField{
Expand Down Expand Up @@ -172,16 +260,43 @@ func TestStructTagToLibPaths(t *testing.T) {
}, {
pathElemPath: []*gnmipb.PathElem{{Name: "existing"}, {Name: "foo"}, {Name: "baz"}},
}},
}, {
name: "populated pathelem parent path with shadow-path",
inField: reflect.StructField{
Name: "field",
Tag: `path:"baz|foo/baz" shadow-path:"far/boo"`,
},
inParent: &gnmiPath{
pathElemPath: []*gnmipb.PathElem{{Name: "existing"}},
},
want: []*gnmiPath{{
pathElemPath: []*gnmipb.PathElem{{Name: "existing"}, {Name: "baz"}},
}, {
pathElemPath: []*gnmipb.PathElem{{Name: "existing"}, {Name: "foo"}, {Name: "baz"}},
}},
}, {
name: "populated pathelem parent path with preferred shadow-path",
inField: reflect.StructField{
Name: "field",
Tag: `path:"baz|foo/baz" shadow-path:"far/boo"`,
},
inPreferShadowPath: true,
inParent: &gnmiPath{
pathElemPath: []*gnmipb.PathElem{{Name: "existing"}},
},
want: []*gnmiPath{{
pathElemPath: []*gnmipb.PathElem{{Name: "existing"}, {Name: "far"}, {Name: "boo"}},
}},
}}

for _, tt := range tests {
got, err := structTagToLibPaths(tt.inField, tt.inParent)
got, err := structTagToLibPaths(tt.inField, tt.inParent, tt.inPreferShadowPath)
if (err != nil) != tt.wantErr {
t.Errorf("%s: structTagToLibPaths(%v, %v): did not get expected error status, got: %v, want err: %v", tt.name, tt.inField, tt.inParent, err, tt.wantErr)
t.Errorf("%s: structTagToLibPaths(%v, %v, %v): did not get expected error status, got: %v, want err: %v", tt.name, tt.inField, tt.inParent, tt.inPreferShadowPath, err, tt.wantErr)
}

if diff := cmp.Diff(tt.want, got, cmp.AllowUnexported(gnmiPath{}), cmp.Comparer(proto.Equal)); diff != "" {
t.Errorf("%s: structTagToLibPaths(%v, %v): did not get expected set of map paths, diff(-want, +got):\n%s", tt.name, tt.inField, tt.inParent, diff)
t.Errorf("%s: structTagToLibPaths(%v, %v, %v): did not get expected set of map paths, diff(-want, +got):\n%s", tt.name, tt.inField, tt.inParent, tt.inPreferShadowPath, diff)
}
}
}
Expand Down

0 comments on commit 172a0e6

Please sign in to comment.