github.com/willyham/dosa@v2.3.1-0.20171024181418-1e446d37ee71+incompatible/names.go (about)

     1  // Copyright (c) 2017 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 dosa
    22  
    23  import (
    24  	"strings"
    25  
    26  	"github.com/pkg/errors"
    27  )
    28  
    29  const (
    30  	fqnSeparator = "."
    31  	rootFQN      = FQN("")
    32  	maxNameLen   = 32
    33  )
    34  
    35  // FQN is the fully qualified name for an entity
    36  type FQN string
    37  
    38  // String satisfies fmt.Stringer interface
    39  func (f FQN) String() string {
    40  	return string(f)
    41  }
    42  
    43  // Child returns a new child FQN with the given comp at the end
    44  func (f FQN) Child(s string) (FQN, error) {
    45  	comp, err := NormalizeName(s)
    46  	if err != nil {
    47  		return "", errors.Wrap(err, "cannot create child FQN")
    48  	}
    49  	return FQN(strings.Join([]string{string(f), comp}, fqnSeparator)), nil
    50  }
    51  
    52  // ToFQN converts the input string to FQN
    53  func ToFQN(s string) (FQN, error) {
    54  	if s == "" {
    55  		return rootFQN, nil
    56  	}
    57  	comps := strings.Split(s, fqnSeparator)
    58  	normalizedComps := make([]string, len(comps))
    59  	for i, c := range comps {
    60  		comp, err := NormalizeName(c)
    61  		if err != nil {
    62  			return "", errors.Wrap(err, "cannot create FQN with invalid name component")
    63  		}
    64  		normalizedComps[i] = comp
    65  	}
    66  	return FQN(strings.Join(normalizedComps, fqnSeparator)), nil
    67  }
    68  
    69  func isInvalidFirstRune(r rune) bool {
    70  	return !((r >= 'a' && r <= 'z') || r == '_')
    71  }
    72  
    73  func isInvalidOtherRune(r rune) bool {
    74  	return !(r >= '0' && r <= '9') && isInvalidFirstRune(r)
    75  }
    76  
    77  // IsValidName checks if a name conforms the following rules:
    78  // 1. name starts with [a-z_]
    79  // 2. the rest of name can contain only [a-z0-9_]
    80  // 3. the length of name must be greater than 0 and less than or equal to maxNameLen
    81  func IsValidName(name string) error {
    82  	if len(name) == 0 {
    83  		return errors.Errorf("cannot be empty")
    84  	}
    85  	if len(name) > maxNameLen {
    86  		return errors.Errorf("too long: %v has length %d, max allowed is %d", name, maxNameLen, len(name))
    87  	}
    88  	if strings.IndexFunc(name[:1], isInvalidFirstRune) != -1 {
    89  		return errors.Errorf("name must start with [a-z_]. Actual='%s'", name)
    90  	}
    91  	if strings.IndexFunc(name[1:], isInvalidOtherRune) != -1 {
    92  		return errors.Errorf("name must contain only [a-z0-9_], Actual='%s'", name)
    93  	}
    94  	return nil
    95  }
    96  
    97  // NormalizeName normalizes names to a canonical representation by lowercase everything.
    98  // It returns error if the resultant canonical name is invalid.
    99  func NormalizeName(name string) (string, error) {
   100  	lowercaseName := strings.ToLower(strings.TrimSpace(name))
   101  	if err := IsValidName(lowercaseName); err != nil {
   102  		return "", errors.Wrapf(err, "failed to normalize to a valid name for %s", name)
   103  	}
   104  	return lowercaseName, nil
   105  }