diff --git a/resourcename/hasparent.go b/resourcename/hasparent.go index 33b41f3b5e..47a379e0a4 100644 --- a/resourcename/hasparent.go +++ b/resourcename/hasparent.go @@ -2,8 +2,8 @@ package resourcename // HasParent tests whether name has the specified parent. Wildcard segments (-) are considered. func HasParent(name, parent string) bool { - if name == "" || parent == "" { - return false // empty is never a valid child or parent + if name == "" || parent == "" || name == parent { + return false } var parentScanner, nameScanner Scanner parentScanner.Init(parent) diff --git a/resourcename/hasparent_test.go b/resourcename/hasparent_test.go index 0d2e314a56..2a5ee25fe4 100644 --- a/resourcename/hasparent_test.go +++ b/resourcename/hasparent_test.go @@ -21,6 +21,13 @@ func TestHasParent(t *testing.T) { expected: true, }, + { + test: "not parent of self", + name: "shippers/1/sites/1/settings", + parent: "shippers/1/sites/1/settings", + expected: false, + }, + { test: "empty parent", name: "shippers/1/sites/1", @@ -63,6 +70,13 @@ func TestHasParent(t *testing.T) { expected: true, }, + { + test: "full parent", + name: "shippers/1/sites/1", + parent: "//freight-example.einride.tech/shippers/-", + expected: true, + }, + { test: "full parent and child with different service names", name: "//other-example.einride.tech/shippers/1/sites/1", diff --git a/resourcename/rangeparents.go b/resourcename/rangeparents.go new file mode 100644 index 0000000000..7e8152d578 --- /dev/null +++ b/resourcename/rangeparents.go @@ -0,0 +1,24 @@ +package resourcename + +// RangeParents iterates over all parents of the provided resource name. +// The iteration order is from root ancestor down to the closest parent. +// Collection segments are included in the iteration, so as to not require knowing the pattern. +// For full resource names, the service is omitted. +func RangeParents(name string, fn func(parent string) bool) { + var sc Scanner + sc.Init(name) + // First segment: special-case to handle full resource names. + if !sc.Scan() { + return + } + start := sc.Start() + if sc.End() != len(name) && !fn(name[start:sc.End()]) { + return + } + // Scan remaining segments. + for sc.Scan() { + if sc.End() != len(name) && !fn(name[start:sc.End()]) { + return + } + } +} diff --git a/resourcename/rangeparents_test.go b/resourcename/rangeparents_test.go new file mode 100644 index 0000000000..fb30ba9725 --- /dev/null +++ b/resourcename/rangeparents_test.go @@ -0,0 +1,65 @@ +package resourcename + +import ( + "testing" + + "gotest.tools/v3/assert" +) + +func TestRangeParents(t *testing.T) { + t.Parallel() + for _, tt := range []struct { + name string + input string + expected []string + }{ + { + name: "empty", + input: "", + }, + + { + name: "singleton", + input: "foo", + }, + + { + name: "single", + input: "foo/bar", + expected: []string{ + "foo", + }, + }, + + { + name: "multiple", + input: "foo/bar/baz/123", + expected: []string{ + "foo", + "foo/bar", + "foo/bar/baz", + }, + }, + + { + name: "full", + input: "//test.example.com/foo/bar/baz/123", + expected: []string{ + "foo", + "foo/bar", + "foo/bar/baz", + }, + }, + } { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + var actual []string + RangeParents(tt.input, func(parent string) bool { + actual = append(actual, parent) + return true + }) + assert.DeepEqual(t, tt.expected, actual) + }) + } +} diff --git a/resourcename/scanner.go b/resourcename/scanner.go index 61d04fdf4f..02f7fd7150 100644 --- a/resourcename/scanner.go +++ b/resourcename/scanner.go @@ -49,6 +49,16 @@ func (s *Scanner) Scan() bool { return true } +// Start returns the start index (inclusive) of the current segment. +func (s *Scanner) Start() int { + return s.start +} + +// End returns the end index (exclusive) of the current segment. +func (s *Scanner) End() int { + return s.end +} + // Segment returns the current segment. func (s *Scanner) Segment() Segment { return Segment(s.name[s.start:s.end])