From e5da71444547227bcddd5230d58ced26c8bde3dc Mon Sep 17 00:00:00 2001 From: Alberto Ricart Date: Tue, 1 Oct 2024 14:07:07 -0500 Subject: [PATCH 1/2] [CHANGE] TagList now has ContainsEqualFold/RemoveEqualsFold to allow case-insensitive usages Signed-off-by: Alberto Ricart --- v2/account_claims.go | 4 ++-- v2/types.go | 49 +++++++++++++++++++++++++++++++++++++------ v2/types_test.go | 50 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 95 insertions(+), 8 deletions(-) diff --git a/v2/account_claims.go b/v2/account_claims.go index 05850fc..3a9b7ac 100644 --- a/v2/account_claims.go +++ b/v2/account_claims.go @@ -255,7 +255,7 @@ type Account struct { Mappings Mapping `json:"mappings,omitempty"` Authorization ExternalAuthorization `json:"authorization,omitempty"` Trace *MsgTrace `json:"trace,omitempty"` - ClusterTraffic ClusterTraffic `json:"cluster_traffic,omitempty"` + ClusterTraffic string `json:"cluster_traffic,omitempty"` Info GenericFields } @@ -324,7 +324,7 @@ func (a *Account) Validate(acct *AccountClaims, vr *ValidationResults) { a.SigningKeys.Validate(vr) a.Info.Validate(vr) - if err := a.ClusterTraffic.Valid(); err != nil { + if err := ClusterTraffic(a.ClusterTraffic).Valid(); err != nil { vr.AddError(err.Error()) } } diff --git a/v2/types.go b/v2/types.go index f0049ab..d1c3942 100644 --- a/v2/types.go +++ b/v2/types.go @@ -21,6 +21,7 @@ import ( "net" "net/url" "reflect" + "sort" "strconv" "strings" "time" @@ -422,26 +423,46 @@ func (u *StringList) Remove(p ...string) { } // TagList is a unique array of lower case strings -// All tag list methods lower case the strings in the arguments type TagList []string -// Contains returns true if the list contains the tags +// Contains returns true if the list contains the tag func (u *TagList) Contains(p string) bool { return u.find(p) != -1 } +// ContainsEqualsFold returns true if the list contain the tag regardless of case +func (u *TagList) ContainsEqualsFold(p string) bool { + return u.findEqualsFold(p) != -1 +} + +// Equals returns true if the lists are strictly equal func (u *TagList) Equals(other *TagList) bool { if len(*u) != len(*other) { return false } - for _, v := range *u { - if other.find(v) == -1 { + + a := sort.StringSlice(*u) + sort.Sort(a) + b := sort.StringSlice(*other) + sort.Sort(b) + + for i, v := range a { + if v != b[i] { return false } } return true } +func (u *TagList) findEqualsFold(p string) int { + for idx, t := range *u { + if strings.EqualFold(p, t) { + return idx + } + } + return -1 +} + func (u *TagList) find(p string) int { for idx, t := range *u { if p == t { @@ -451,7 +472,7 @@ func (u *TagList) find(p string) int { return -1 } -// Add appends 1 or more tags to a list +// Add appends 1 or more tags to a list, case of the arguments is preserved. func (u *TagList) Add(p ...string) { for _, v := range p { v = strings.TrimSpace(v) @@ -464,7 +485,7 @@ func (u *TagList) Add(p ...string) { } } -// Remove removes 1 or more tags from a list +// Remove removes 1 or more tags from a list, tags must be strictly equal func (u *TagList) Remove(p ...string) error { for _, v := range p { v = strings.TrimSpace(v) @@ -479,6 +500,22 @@ func (u *TagList) Remove(p ...string) error { return nil } +// RemoveEqualsFold removes 1 or more tags from a list as long as their +// values are equal regardless of fold. +func (u *TagList) RemoveEqualsFold(p ...string) error { + for _, v := range p { + v = strings.TrimSpace(v) + idx := u.findEqualsFold(v) + if idx != -1 { + a := *u + *u = append(a[:idx], a[idx+1:]...) + } else { + return fmt.Errorf("unable to remove tag: %q - not found", v) + } + } + return nil +} + type CIDRList []string func (c *CIDRList) Contains(p string) bool { diff --git a/v2/types_test.go b/v2/types_test.go index 9d8a205..b85deef 100644 --- a/v2/types_test.go +++ b/v2/types_test.go @@ -453,6 +453,29 @@ func TestTagList_CasePreservingContains(t *testing.T) { } } +func TestTagList_EqualsFoldContains(t *testing.T) { + type test struct { + v string + a TagList + ok bool + } + + tests := []test{ + {v: "A", a: TagList{}, ok: false}, + {v: "A", a: TagList{"a"}, ok: true}, + {v: "A", a: TagList{"A"}, ok: true}, + {v: "a", a: TagList{"a:hello"}, ok: false}, + {v: "a:a", a: TagList{"a:c"}, ok: false}, + } + + for idx, test := range tests { + found := test.a.ContainsEqualsFold(test.v) + if !found && test.ok { + t.Errorf("[%d] expected to contain %q", idx, test.v) + } + } +} + func TestTagList_Add(t *testing.T) { type test struct { v string @@ -503,3 +526,30 @@ func TestTagList_Delete(t *testing.T) { } } } + +func TestTagList_EqualsFoldDelete(t *testing.T) { + type test struct { + v string + a TagList + shouldBe TagList + shouldFail bool + } + + tests := []test{ + {v: "A", a: TagList{}, shouldBe: TagList{}, shouldFail: true}, + {v: "A", a: TagList{"A"}, shouldBe: TagList{}}, + {v: "a", a: TagList{"A"}, shouldBe: TagList{}}, + {v: "a:Hello", a: TagList{"a:hello"}, shouldBe: TagList{}}, + {v: "a:a", a: TagList{"a:A"}, shouldBe: TagList{}}, + } + + for idx, test := range tests { + err := test.a.RemoveEqualsFold(test.v) + if test.shouldFail && err == nil { + t.Fatalf("[%d] expected delete to fail: %v", idx, test.a) + } + if !test.a.Equals(&test.shouldBe) { + t.Fatalf("[%d] expected lists to be equal: %v", idx, test.a) + } + } +} From 74c88303eeede5da3e8f3cb1ac680389be1502cc Mon Sep 17 00:00:00 2001 From: Alberto Ricart Date: Tue, 1 Oct 2024 16:44:54 -0500 Subject: [PATCH 2/2] [FEAT] added utility FindTag() to find all the tags that start with name+":" [FEAT] added utility GetValuesForTag() to return all values that start with tag Signed-off-by: Alberto Ricart --- v2/types.go | 42 ++++++++++++++++++++++++++++++++++++++++++ v2/types_test.go | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 88 insertions(+) diff --git a/v2/types.go b/v2/types.go index d1c3942..0c83aa0 100644 --- a/v2/types.go +++ b/v2/types.go @@ -500,6 +500,48 @@ func (u *TagList) Remove(p ...string) error { return nil } +// FindTag returns all the tags that start with the specified name. +// To be a valid tag, the tag must include a name + ':' + value +// Tag names are case-insensitive +func (u *TagList) FindTag(v string) *TagList { + var matches TagList + // must have a suffix that is name+":" + if !strings.HasSuffix(v, ":") { + v = fmt.Sprintf("%s:", v) + } + // to be a valid tag it must have a name + if v == ":" { + return &matches + } + for _, t := range *u { + idx := strings.Index(t, ":") + if idx != -1 { + prefix := t[:idx+1] + value := t[idx+1:] + // to be a valid tag, it must match the name and have a value + if strings.EqualFold(v, prefix) && len(value) > 0 { + matches = append(matches, t) + } + } + } + return &matches +} + +// GetValuesForTag returns all value portions of a tag. Tag names +// are case-insensitive +func (u *TagList) GetValuesForTag(v string) *TagList { + tags := u.FindTag(v) + if !strings.HasSuffix(v, ":") { + v = fmt.Sprintf("%s:", v) + } + start := len(v) + a := *tags + for idx, t := range a { + a[idx] = t[start:] + } + return &a +} + // RemoveEqualsFold removes 1 or more tags from a list as long as their // values are equal regardless of fold. func (u *TagList) RemoveEqualsFold(p ...string) error { diff --git a/v2/types_test.go b/v2/types_test.go index b85deef..dd370a2 100644 --- a/v2/types_test.go +++ b/v2/types_test.go @@ -553,3 +553,49 @@ func TestTagList_EqualsFoldDelete(t *testing.T) { } } } + +func TestTagMatches(t *testing.T) { + type test struct { + tag string + src TagList + matches TagList + } + + tests := []test{ + {tag: "A", src: TagList{}, matches: TagList{}}, + {tag: "A", src: TagList{"a:hello"}, matches: TagList{"a:hello"}}, + {tag: "a:", src: TagList{"A:one"}, matches: TagList{"A:one"}}, + {tag: ":", src: TagList{":one"}, matches: TagList{}}, + {tag: "b", src: TagList{"b:"}, matches: TagList{}}, + } + + for idx, test := range tests { + m := test.src.FindTag(test.tag) + if !test.matches.Equals(m) { + t.Fatalf("[%d] expected tag matches on %s: %v but got %v", idx, test.tag, test.matches, *m) + } + } +} + +func TestTagValues(t *testing.T) { + type test struct { + tag string + src TagList + matches TagList + } + + tests := []test{ + {tag: "A", src: TagList{}, matches: TagList{}}, + {tag: "A:", src: TagList{"a:hello"}, matches: TagList{"hello"}}, + {tag: "a", src: TagList{"A:one"}, matches: TagList{"one"}}, + {tag: ":", src: TagList{":one"}, matches: TagList{}}, + {tag: "b", src: TagList{"b:"}, matches: TagList{}}, + } + + for idx, test := range tests { + m := test.src.GetValuesForTag(test.tag) + if !test.matches.Equals(m) { + t.Fatalf("[%d] expected tag matches on %s: %v but got %v", idx, test.tag, test.matches, *m) + } + } +}