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  }