From f9a6131f87ac7c1f41e7f1965fa3e6e57a504b03 Mon Sep 17 00:00:00 2001 From: yunjie-lu Date: Thu, 27 Mar 2025 13:26:14 -0400 Subject: [PATCH] Add a SetWildcardKeys function to Pathtranslator --- ygot/pathtranslate/pathtranslate.go | 26 +++- ygot/pathtranslate/pathtranslate_test.go | 161 +++++++++++++++++++++++ 2 files changed, 186 insertions(+), 1 deletion(-) diff --git a/ygot/pathtranslate/pathtranslate.go b/ygot/pathtranslate/pathtranslate.go index 0eec0528a..63388f462 100644 --- a/ygot/pathtranslate/pathtranslate.go +++ b/ygot/pathtranslate/pathtranslate.go @@ -80,7 +80,7 @@ func (r *PathTranslator) PathElem(p []string) ([]*gnmipb.PathElem, error) { // When keys are consumed, they are set as true in "used" slice. used := make([]bool, len(p)) - // Keeps the path elements seeen so far by appending with a separator. + // Keeps the path elements seen so far by appending with a separator. var pathSoFar string var res []*gnmipb.PathElem @@ -114,3 +114,27 @@ func (r *PathTranslator) PathElem(p []string) ([]*gnmipb.PathElem, error) { return res, nil } + +// SetWildcardKeys sets the keys of the given path elements based on stored rewrite rules, +// with the value set to "*". Elems are modified in place. +// It returns true if the path has been updated, and an error if any of the elems already has keys. +func (r *PathTranslator) SetWildcardKeys(elems []*gnmipb.PathElem) (bool, error) { + var pathSoFar string + var updated bool + for _, elem := range elems { + pathSoFar = pathSoFar + separator + elem.GetName() + keyNames, ok := r.rules[pathSoFar] + if !ok { + continue + } + if elem.GetKey() != nil { + return false, fmt.Errorf("path %v already has keys", elems) + } + elem.Key = map[string]string{} + for _, key := range keyNames { + elem.Key[key] = "*" + } + updated = true + } + return updated, nil +} diff --git a/ygot/pathtranslate/pathtranslate_test.go b/ygot/pathtranslate/pathtranslate_test.go index 4c2bd81fc..c7059c4a2 100644 --- a/ygot/pathtranslate/pathtranslate_test.go +++ b/ygot/pathtranslate/pathtranslate_test.go @@ -245,3 +245,164 @@ func TestPathElem(t *testing.T) { }) } } + +func TestSetWildcardKeys(t *testing.T) { + schemas := []*yang.Entry{ + {Name: "root"}, + { + Name: "simpleKeyedList", + Key: "k1", + Parent: &yang.Entry{ + Name: "simpleKeyedLists", + Parent: &yang.Entry{ + Name: "b", + Parent: &yang.Entry{ + Name: "a", + Parent: &yang.Entry{Name: "root"}, + }, + }, + }, + }, + { + Name: "structKeyedList", + Key: "k1 k2 k3", + Parent: &yang.Entry{Name: "structKeyedLists", + Parent: &yang.Entry{ + Name: "simpleKeyedList", + Key: "k1", + Parent: &yang.Entry{ + Name: "simpleKeyedLists", + Parent: &yang.Entry{ + Name: "b", + Parent: &yang.Entry{ + Name: "a", + Parent: &yang.Entry{Name: "root"}, + }, + }, + }, + }, + }, + }, + } + + tests := []struct { + inDesc string + path []*gnmipb.PathElem + wantPath []*gnmipb.PathElem + wantUpdated bool + wantErrSubstring string + }{ + { + inDesc: "success empty path", + path: []*gnmipb.PathElem{}, + wantPath: []*gnmipb.PathElem{}, + wantUpdated: false, + }, + { + inDesc: "success path with no keyed list(note, it doesn't exist in schema)", + path: []*gnmipb.PathElem{ + {Name: "a"}, + {Name: "b"}, + }, + wantPath: []*gnmipb.PathElem{ + {Name: "a"}, + {Name: "b"}, + }, + wantUpdated: false, + }, + { + inDesc: "success path with keyed list at the end", + path: []*gnmipb.PathElem{ + {Name: "a"}, + {Name: "b"}, + {Name: "simpleKeyedLists"}, + {Name: "simpleKeyedList"}, + }, + wantPath: []*gnmipb.PathElem{ + {Name: "a"}, + {Name: "b"}, + {Name: "simpleKeyedLists"}, + {Name: "simpleKeyedList", Key: map[string]string{"k1": "*"}}, + }, + wantUpdated: true, + }, + { + inDesc: "success path with keyed list followed by arbitrary elements", + path: []*gnmipb.PathElem{ + {Name: "a"}, + {Name: "b"}, + {Name: "simpleKeyedLists"}, + {Name: "simpleKeyedList"}, + {Name: "arbitrary1"}, + {Name: "arbitrary2"}, + }, + wantPath: []*gnmipb.PathElem{ + {Name: "a"}, + {Name: "b"}, + {Name: "simpleKeyedLists"}, + {Name: "simpleKeyedList", Key: map[string]string{"k1": "*"}}, + {Name: "arbitrary1"}, + {Name: "arbitrary2"}, + }, + wantUpdated: true, + }, + { + inDesc: "success path with struct keyed list", + path: []*gnmipb.PathElem{ + {Name: "a"}, + {Name: "b"}, + {Name: "simpleKeyedLists"}, + {Name: "simpleKeyedList"}, + {Name: "structKeyedLists"}, + {Name: "structKeyedList"}, + }, + wantPath: []*gnmipb.PathElem{ + {Name: "a"}, + {Name: "b"}, + {Name: "simpleKeyedLists"}, + {Name: "simpleKeyedList", Key: map[string]string{"k1": "*"}}, + {Name: "structKeyedLists"}, + {Name: "structKeyedList", Key: map[string]string{"k1": "*", "k2": "*", "k3": "*"}}, + }, + wantUpdated: true, + }, + { + inDesc: "fail when input path already has keys", + path: []*gnmipb.PathElem{ + {Name: "a"}, + {Name: "b"}, + {Name: "simpleKeyedLists"}, + {Name: "simpleKeyedList", Key: map[string]string{"k1": "key1"}}, + {Name: "arbitrary"}, + }, + wantPath: []*gnmipb.PathElem{ + {Name: "a"}, + {Name: "b"}, + {Name: "simpleKeyedLists"}, + {Name: "simpleKeyedList", Key: map[string]string{"k1": "key1"}}, + {Name: "arbitrary"}, + }, + wantUpdated: false, + wantErrSubstring: "already has keys", + }, + } + r, err := NewPathTranslator(schemas) + if err != nil { + t.Errorf("failed to create path translator; %v", r) + } + for _, tc := range tests { + t.Run(tc.inDesc, func(t *testing.T) { + updated, err := r.SetWildcardKeys(tc.path) + if diff := errdiff.Substring(err, tc.wantErrSubstring); diff != "" { + t.Errorf("diff: %v", diff) + return + } + if updated != tc.wantUpdated { + t.Errorf("got matched %v, want %v", updated, tc.wantUpdated) + } + if !cmp.Equal(tc.path, tc.wantPath, cmp.Comparer(proto.Equal)) { + t.Errorf("got %v, want %v", tc.path, tc.wantPath) + } + }) + } +}