github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/pkg/label/label.go (about) 1 // Copyright 2022 PingCAP, Inc. 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 // See the License for the specific language governing permissions and 12 // limitations under the License. 13 14 package label 15 16 import ( 17 "regexp" 18 19 "github.com/pingcap/errors" 20 ) 21 22 type ( 23 // Key is the type for a label key. 24 Key string 25 26 // Value is the type for a label value. 27 Value string 28 ) 29 30 const ( 31 invalidLabelKey = Key("~") 32 invalidLabelValue = Value("~") 33 ) 34 35 // NewKey creates a new Key from a string. 36 // It will return legal = false if the input contains 37 // illegal characters. 38 func NewKey(str string) (key Key, err error) { 39 if err := checkLabelStrValid(str); err != nil { 40 return invalidLabelKey, errors.Annotate(err, "new key") 41 } 42 return Key(str), nil 43 } 44 45 // NewValue creates a new Value from a string. 46 // It will return legal = false if the input contains 47 // illegal characters. 48 func NewValue(str string) (value Value, err error) { 49 if err := checkLabelStrValid(str); err != nil { 50 return invalidLabelValue, errors.Annotate(err, "new value") 51 } 52 return Value(str), nil 53 } 54 55 const ( 56 labelStrRegexStr string = `^[a-zA-Z0-9]([-\.a-zA-Z0-9]*[a-zA-Z0-9])?$` 57 maxLabelLength = 63 58 ) 59 60 var labelStrRegex = regexp.MustCompile(labelStrRegexStr) 61 62 func checkLabelStrValid(str string) error { 63 if len(str) == 0 { 64 return errors.New("empty label string") 65 } 66 67 if len(str) > maxLabelLength { 68 return errors.Errorf("label string too long: %s", str) 69 } 70 71 if !labelStrRegex.MatchString(str) { 72 return errors.Errorf("label string has wrong format: %s", str) 73 } 74 75 return nil 76 } 77 78 // Set is a short name for map[Key]Value. 79 // Note that it is not thread-safe. 80 type Set map[Key]Value 81 82 // NewSet returns a new Set. 83 func NewSet() Set { 84 return make(map[Key]Value, 8) 85 } 86 87 // NewSetFromMap tries to create a Set from an input of type 88 // map[string]string. It is used to verify user input and create 89 // a Set. 90 func NewSetFromMap(input map[string]string) (Set, error) { 91 ret := NewSet() 92 for k, v := range input { 93 key, err := NewKey(k) 94 if err != nil { 95 return nil, err 96 } 97 98 value, err := NewValue(v) 99 if err != nil { 100 return nil, err 101 } 102 103 if !ret.Add(key, value) { 104 // Reachable only if there are duplicate keys, 105 // which are not possible because the input is a map. 106 panic("unreachable") 107 } 108 } 109 return ret, nil 110 } 111 112 // Add adds a key-value pair to the Set. 113 // Duplicates are not allowed. 114 func (s Set) Add(key Key, value Value) (success bool) { 115 if key == invalidLabelKey || value == invalidLabelValue { 116 panic("Adding invalid key or value to Set") 117 } 118 119 if _, exists := s[key]; exists { 120 return false 121 } 122 s[key] = value 123 return true 124 } 125 126 // Get gets the value of a key from the Set. 127 func (s Set) Get(key Key) (value Value, exists bool) { 128 if key == invalidLabelKey { 129 panic("Trying to get value for invalid key") 130 } 131 132 retVal, exists := s[key] 133 if !exists { 134 return invalidLabelValue, false 135 } 136 return retVal, true 137 } 138 139 // ToMap converts a Set to a plain map that can be used to 140 // produce a protobuf message. 141 func (s Set) ToMap() map[string]string { 142 if len(s) == 0 { 143 return nil 144 } 145 ret := make(map[string]string, len(s)) 146 for k, v := range s { 147 ret[string(k)] = string(v) 148 } 149 return ret 150 }