Skip to content

Commit

Permalink
Retraced case-sensitive only API, added Add/RemoveExact variants whic…
Browse files Browse the repository at this point in the history
…h are case-preserving and case-sensitive
  • Loading branch information
aricart committed Oct 2, 2024
1 parent 18a60d6 commit 132f02c
Show file tree
Hide file tree
Showing 4 changed files with 247 additions and 133 deletions.
5 changes: 5 additions & 0 deletions v2/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@ go 1.18

require github.com/nats-io/nkeys v0.4.7

retract (
v2.7.1 // contains retractions only
v2.7.0 // includes case insensitive changes to tags that break jetstream placement
)

require (
golang.org/x/crypto v0.19.0 // indirect
golang.org/x/sys v0.17.0 // indirect
Expand Down
1 change: 1 addition & 0 deletions v2/operator_claims_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -459,6 +459,7 @@ func TestTags(t *testing.T) {
}

AssertTrue(oc.GenericFields.Tags.Contains("one"), t)
AssertTrue(oc.GenericFields.Tags.Contains("ONE"), t)
AssertTrue(oc.GenericFields.Tags.Contains("TWO"), t)
AssertTrue(oc.GenericFields.Tags.Contains("three"), t)
}
Expand Down
165 changes: 118 additions & 47 deletions v2/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"net"
"net/url"
"reflect"
"sort"
"strconv"
"strings"
"time"
Expand Down Expand Up @@ -421,96 +422,166 @@ func (u *StringList) Remove(p ...string) {
}
}

// TagList is a unique array of lower case strings
// TagList is a unique array of 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 tags.
func (u *TagList) Contains(p string) bool {
p = strings.ToLower(strings.TrimSpace(p))
return u.find(p) != -1
}

func (u *TagList) Equals(other *TagList) bool {
if len(*u) != len(*other) {
return false
}
for _, v := range *u {
if other.find(v) == -1 {
return false
}
}
return true
func (u *TagList) ContainsExact(p string) bool {
p = strings.TrimSpace(p)
return u.findExact(p) != -1
}

func (u *TagList) find(p string) int {
for idx, t := range *u {
if p == t {
if strings.EqualFold(t, p) {
return idx
}
}
return -1
}

// Add appends 1 or more tags to a list
func (u *TagList) Add(p ...string) {
for _, v := range p {
v = strings.TrimSpace(v)
if v == "" {
continue
}
if !u.Contains(v) {
*u = append(*u, v)
func (u *TagList) findExact(p string) int {
for idx, t := range *u {
if t == p {
return idx
}
}
return -1
}

// Remove removes 1 or more tags from a list
func (u *TagList) Remove(p ...string) error {
// Add appends 1 or more tags to a list, tags are converted to lowercase.
func (u *TagList) Add(p ...string) {
for _, v := range p {
v = strings.TrimSpace(v)
v = strings.ToLower(strings.TrimSpace(v))
idx := u.find(v)
if idx != -1 {
a := *u
*u = append(a[:idx], a[idx+1:]...)
a[idx] = v
} else {
return fmt.Errorf("unable to remove tag: %q - not found", v)
*u = append(*u, v)
}
}
return nil
}

type CIDRList []string

func (c *CIDRList) Contains(p string) bool {
p = strings.ToLower(strings.TrimSpace(p))
for _, t := range *c {
if t == p {
return true
func (u *TagList) AddExact(p ...string) {
for _, v := range p {
v = strings.TrimSpace(v)
idx := u.findExact(v)
if idx == -1 {
*u = append(*u, v)
}
}
return false
}

func (c *CIDRList) Add(p ...string) {
// Remove removes 1 or more tags from a list, removal is case-insensitive,
// and can remove any value that is EqualsFold
func (u *TagList) Remove(p ...string) {
for _, v := range p {
v = strings.ToLower(strings.TrimSpace(v))
if !c.Contains(v) && v != "" {
*c = append(*c, v)
v = strings.TrimSpace(v)
for {
idx := u.find(v)
if idx != -1 {
a := *u
*u = append(a[:idx], a[idx+1:]...)
} else {
break
}
}
}
}

func (c *CIDRList) Remove(p ...string) {
func (u *TagList) RemoveExact(p ...string) error {
for _, v := range p {
v = strings.ToLower(strings.TrimSpace(v))
for i, t := range *c {
if t == v {
a := *c
*c = append(a[:i], a[i+1:]...)
break
v = strings.TrimSpace(v)
idx := u.findExact(v)
if idx == -1 {
return fmt.Errorf("tag \"%q\" not found", v)
}
a := *u
*u = append(a[:idx], a[idx+1:]...)
}
return nil
}

// FindTag finds entries that start with the specified name
// followed by a colon (:). Names are case-insensitive for
// matches. This function returns the name+:+value (the entire "entry").
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
}

// GetTagValueFor finds entries that start with the specified name
// followed by a colon (:). Names are case-insensitive for
// matches. This function returns the value portion of the entry
func (u *TagList) GetTagValueFor(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
}

func (u *TagList) Equals(other *TagList) bool {
if len(*u) != len(*other) {
return false
}

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
}

type CIDRList TagList

func (c *CIDRList) Contains(p string) bool {
return (*TagList)(c).Contains(p)
}

func (c *CIDRList) Add(p ...string) {
(*TagList)(c).Add(p...)
}

func (c *CIDRList) Remove(p ...string) {
(*TagList)(c).Remove(p...)
}

func (c *CIDRList) Set(values string) {
Expand Down
Loading

0 comments on commit 132f02c

Please sign in to comment.