go.uber.org/yarpc@v1.72.1/internal/servicename.go (about)

     1  // Copyright (c) 2022 Uber Technologies, Inc.
     2  //
     3  // Permission is hereby granted, free of charge, to any person obtaining a copy
     4  // of this software and associated documentation files (the "Software"), to deal
     5  // in the Software without restriction, including without limitation the rights
     6  // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
     7  // copies of the Software, and to permit persons to whom the Software is
     8  // furnished to do so, subject to the following conditions:
     9  //
    10  // The above copyright notice and this permission notice shall be included in
    11  // all copies or substantial portions of the Software.
    12  //
    13  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    14  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    15  // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    16  // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    17  // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    18  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    19  // THE SOFTWARE.
    20  
    21  package internal
    22  
    23  import (
    24  	"errors"
    25  	"fmt"
    26  	"regexp"
    27  
    28  	"go.uber.org/multierr"
    29  )
    30  
    31  // We disallow UUIDs explicitly, though they're otherwise valid patterns.
    32  var _uuidRegexp = regexp.MustCompile("[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}")
    33  
    34  // ValidateServiceName returns an error if the given servive name is invalid.
    35  // Valid names are at least two characters long, start with [a-z], contain only
    36  // [0-9a-z] and non-consecutive hyphens, and end in [0-9a-z]. Furthermore,
    37  // names may not contain UUIDs.
    38  func ValidateServiceName(name string) error {
    39  	if len(name) < 2 {
    40  		// Short names aren't safe to check any further.
    41  		return errors.New("service name must be at least two characters long")
    42  	}
    43  	return multierr.Combine(
    44  		checkHyphens(name),
    45  		checkFirstCharacter(name),
    46  		checkForbiddenCharacters(name),
    47  		checkUUIDs(name),
    48  	)
    49  }
    50  
    51  func checkHyphens(name string) error {
    52  	for i := 1; i < len(name); i++ {
    53  		if name[i-1] == '-' && name[i] == '-' {
    54  			return fmt.Errorf("service name %q contains consecutive hyphens", name)
    55  		}
    56  	}
    57  	if name[len(name)-1] == '-' {
    58  		return fmt.Errorf("service name %q ends with a hyphen", name)
    59  	}
    60  	return nil
    61  }
    62  
    63  func checkFirstCharacter(name string) error {
    64  	if name[0] < 'a' || name[0] > 'z' {
    65  		return fmt.Errorf("service name %q doesn't start with a lowercase ASCII letter", name)
    66  	}
    67  	return nil
    68  }
    69  
    70  func checkForbiddenCharacters(name string) error {
    71  	for _, c := range name {
    72  		switch {
    73  		case 'a' <= c && c <= 'z':
    74  			continue
    75  		case '0' <= c && c <= '9':
    76  			continue
    77  		case c == '-':
    78  			continue
    79  		case c == '_':
    80  			continue
    81  		default:
    82  			return fmt.Errorf("service name %q contains characters other than [0-9a-z] and hyphens", name)
    83  		}
    84  	}
    85  	return nil
    86  }
    87  
    88  func checkUUIDs(name string) error {
    89  	if _uuidRegexp.MatchString(name) {
    90  		return fmt.Errorf("service name %q contains a UUID", name)
    91  	}
    92  	return nil
    93  }