blob: c05d8cac54f580d3673106c056fb3a05030cceda [file] [log] [blame]
// Copyright 2019 The LUCI Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package pbutil
import (
"fmt"
"sort"
"go.chromium.org/luci/common/data/strpair"
"go.chromium.org/luci/common/errors"
pb "go.chromium.org/luci/resultdb/proto/v1"
)
const maxStringPairKeyLength = 64
const maxStringPairValueLength = 256
const stringPairKeyPattern = `[a-z][a-z0-9_]*(/[a-z][a-z0-9_]*)*`
var stringPairKeyRe = regexpf(`^%s$`, stringPairKeyPattern)
var stringPairRe = regexpf("^(%s):(.*)$", stringPairKeyPattern)
// StringPair creates a pb.StringPair with the given strings as key/value field values.
func StringPair(k, v string) *pb.StringPair {
return &pb.StringPair{Key: k, Value: v}
}
// StringPairs creates a slice of pb.StringPair from a list of strings alternating key/value.
//
// Panics if an odd number of tokens is passed.
func StringPairs(pairs ...string) []*pb.StringPair {
if len(pairs)%2 != 0 {
panic(fmt.Sprintf("odd number of tokens in %q", pairs))
}
strpairs := make([]*pb.StringPair, len(pairs)/2)
for i := range strpairs {
strpairs[i] = StringPair(pairs[2*i], pairs[2*i+1])
}
return strpairs
}
// StringPairsContain checks if item is present in pairs.
func StringPairsContain(pairs []*pb.StringPair, item *pb.StringPair) bool {
for _, p := range pairs {
if p.Key == item.Key && p.Value == item.Value {
return true
}
}
return false
}
// SortStringPairs sorts in-place the tags slice lexicographically by key, then value.
func SortStringPairs(tags []*pb.StringPair) {
sort.Slice(tags, func(i, j int) bool {
if tags[i].Key != tags[j].Key {
return tags[i].Key < tags[j].Key
}
return tags[i].Value < tags[j].Value
})
}
// ValidateStringPair returns an error if p is invalid.
func ValidateStringPair(p *pb.StringPair) error {
if err := validateWithRe(stringPairKeyRe, p.Key); err != nil {
return errors.Annotate(err, "key").Err()
}
if len(p.Key) > maxStringPairKeyLength {
return errors.Reason("key length must be less or equal to %d", maxStringPairKeyLength).Err()
}
if len(p.Value) > maxStringPairValueLength {
return errors.Reason("value length must be less or equal to %d", maxStringPairValueLength).Err()
}
return nil
}
// ValidateStringPairs returns an error if any of the pairs is invalid.
func ValidateStringPairs(pairs []*pb.StringPair) error {
for _, p := range pairs {
if err := ValidateStringPair(p); err != nil {
return errors.Annotate(err, "%q:%q", p.Key, p.Value).Err()
}
}
return nil
}
// StringPairFromString creates a pb.StringPair from the given key:val string.
func StringPairFromString(s string) (*pb.StringPair, error) {
m := stringPairRe.FindStringSubmatch(s)
if m == nil {
return nil, doesNotMatch(stringPairRe)
}
return StringPair(m[1], m[3]), nil
}
// StringPairToString converts a StringPair to a key:val string.
func StringPairToString(pair *pb.StringPair) string {
return fmt.Sprintf("%s:%s", pair.Key, pair.Value)
}
// StringPairsToStrings converts pairs to a slice of "{key}:{value}" strings
// in the same order.
func StringPairsToStrings(pairs ...*pb.StringPair) []string {
ret := make([]string, len(pairs))
for i, p := range pairs {
ret[i] = StringPairToString(p)
}
return ret
}
// FromStrpairMap converts a strpair.Map to []*pb.StringPair.
func FromStrpairMap(m strpair.Map) []*pb.StringPair {
ret := make([]*pb.StringPair, 0, len(m))
for k, vs := range m {
for _, v := range vs {
ret = append(ret, StringPair(k, v))
}
}
SortStringPairs(ret)
return ret
}