go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/resultdb/pbutil/strpair.go (about) 1 // Copyright 2019 The LUCI Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package pbutil 16 17 import ( 18 "fmt" 19 "sort" 20 "strings" 21 22 "go.chromium.org/luci/common/data/strpair" 23 "go.chromium.org/luci/common/errors" 24 pb "go.chromium.org/luci/resultdb/proto/v1" 25 ) 26 27 const maxStringPairKeyLength = 64 28 const maxStringPairValueLength = 256 29 30 const stringPairKeyPattern = `[a-z][a-z0-9_]*(/[a-z][a-z0-9_]*)*` 31 32 var stringPairKeyRe = regexpf(`^%s$`, stringPairKeyPattern) 33 var stringPairRe = regexpf("(?s)^(%s):(.*)$", stringPairKeyPattern) 34 35 // StringPair creates a pb.StringPair with the given strings as key/value field values. 36 func StringPair(k, v string) *pb.StringPair { 37 return &pb.StringPair{Key: k, Value: v} 38 } 39 40 // StringPairs creates a slice of pb.StringPair from a list of strings alternating key/value. 41 // 42 // Panics if an odd number of tokens is passed. 43 func StringPairs(pairs ...string) []*pb.StringPair { 44 if len(pairs)%2 != 0 { 45 panic(fmt.Sprintf("odd number of tokens in %q", pairs)) 46 } 47 48 strpairs := make([]*pb.StringPair, len(pairs)/2) 49 for i := range strpairs { 50 strpairs[i] = StringPair(pairs[2*i], pairs[2*i+1]) 51 } 52 return strpairs 53 } 54 55 // StringPairsContain checks if item is present in pairs. 56 func StringPairsContain(pairs []*pb.StringPair, item *pb.StringPair) bool { 57 for _, p := range pairs { 58 if p.Key == item.Key && p.Value == item.Value { 59 return true 60 } 61 } 62 return false 63 } 64 65 // SortStringPairs sorts in-place the tags slice lexicographically by key, then value. 66 func SortStringPairs(tags []*pb.StringPair) { 67 sort.Slice(tags, func(i, j int) bool { 68 if tags[i].Key != tags[j].Key { 69 return tags[i].Key < tags[j].Key 70 } 71 return tags[i].Value < tags[j].Value 72 }) 73 } 74 75 // ValidateStringPair returns an error if p is invalid. 76 func ValidateStringPair(p *pb.StringPair) error { 77 if err := validateWithRe(stringPairKeyRe, p.Key); err != nil { 78 return errors.Annotate(err, "key").Err() 79 } 80 if len(p.Key) > maxStringPairKeyLength { 81 return errors.Reason("key length must be less or equal to %d", maxStringPairKeyLength).Err() 82 } 83 if len(p.Value) > maxStringPairValueLength { 84 return errors.Reason("value length must be less or equal to %d", maxStringPairValueLength).Err() 85 } 86 return nil 87 } 88 89 // ValidateStringPairs returns an error if any of the pairs is invalid. 90 func ValidateStringPairs(pairs []*pb.StringPair) error { 91 for _, p := range pairs { 92 if err := ValidateStringPair(p); err != nil { 93 return errors.Annotate(err, "%q:%q", p.Key, p.Value).Err() 94 } 95 } 96 return nil 97 } 98 99 // StringPairFromString creates a pb.StringPair from the given key:val string. 100 func StringPairFromString(s string) (*pb.StringPair, error) { 101 m := stringPairRe.FindStringSubmatch(s) 102 if m == nil { 103 return nil, doesNotMatch(stringPairRe) 104 } 105 return StringPair(m[1], m[3]), nil 106 } 107 108 // StringPairFromStringUnvalidated is like StringPairFromString, but doesn't 109 // perform validation. 110 func StringPairFromStringUnvalidated(s string) *pb.StringPair { 111 // TODO: Replace with strings.Cut(s, ":") when we have go 1.18. 112 parts := strings.SplitN(s, ":", 2) 113 if len(parts) != 2 { 114 return StringPair(s, "") 115 } 116 return StringPair(parts[0], parts[1]) 117 } 118 119 // StringPairToString converts a StringPair to a key:val string. 120 func StringPairToString(pair *pb.StringPair) string { 121 return fmt.Sprintf("%s:%s", pair.Key, pair.Value) 122 } 123 124 // StringPairsToStrings converts pairs to a slice of "{key}:{value}" strings 125 // in the same order. 126 func StringPairsToStrings(pairs ...*pb.StringPair) []string { 127 ret := make([]string, len(pairs)) 128 for i, p := range pairs { 129 ret[i] = StringPairToString(p) 130 } 131 return ret 132 } 133 134 // FromStrpairMap converts a strpair.Map to []*pb.StringPair. 135 func FromStrpairMap(m strpair.Map) []*pb.StringPair { 136 ret := make([]*pb.StringPair, 0, len(m)) 137 for k, vs := range m { 138 for _, v := range vs { 139 ret = append(ret, StringPair(k, v)) 140 } 141 } 142 SortStringPairs(ret) 143 return ret 144 }