github.com/blend/go-sdk@v1.20220411.3/db/dbutil/validate_database_name.go (about)

     1  /*
     2  
     3  Copyright (c) 2022 - Present. Blend Labs, Inc. All rights reserved
     4  Use of this source code is governed by a MIT license that can be found in the LICENSE file.
     5  
     6  */
     7  
     8  package dbutil
     9  
    10  import (
    11  	"strings"
    12  	"unicode"
    13  	"unicode/utf8"
    14  
    15  	"github.com/blend/go-sdk/ex"
    16  )
    17  
    18  var (
    19  	// ReservedDatabaseNames are names you cannot use to create a database with.
    20  	ReservedDatabaseNames = []string{
    21  		"postgres",
    22  		"defaultdb",
    23  		"template0",
    24  		"template1",
    25  	}
    26  
    27  	// DatabaseNameMaxLength is the maximum length of a database name.
    28  	DatabaseNameMaxLength = 63
    29  )
    30  
    31  const (
    32  	// ErrDatabaseNameReserved is a validation failure.
    33  	ErrDatabaseNameReserved ex.Class = "dbutil; database name is reserved"
    34  
    35  	// ErrDatabaseNameEmpty is a validation failure.
    36  	ErrDatabaseNameEmpty ex.Class = "dbutil; database name is empty"
    37  
    38  	// ErrDatabaseNameInvalidFirstRune is a validation failure.
    39  	ErrDatabaseNameInvalidFirstRune ex.Class = "dbutil; database name must start with a letter or underscore"
    40  
    41  	// ErrDatabaseNameInvalid is a validation failure.
    42  	ErrDatabaseNameInvalid ex.Class = "dbutil; database name must be composed of (in regex form) [a-zA-Z0-9_]"
    43  
    44  	// ErrDatabaseNameTooLong is a validation failure.
    45  	ErrDatabaseNameTooLong ex.Class = "dbutil; database name must be 63 characters or fewer"
    46  )
    47  
    48  // ValidateDatabaseName validates a database name.
    49  func ValidateDatabaseName(name string) error {
    50  	name = strings.TrimSpace(name)
    51  	if name == "" {
    52  		return ex.New(ErrDatabaseNameEmpty)
    53  	}
    54  	if len(name) > DatabaseNameMaxLength {
    55  		return ex.New(ErrDatabaseNameTooLong)
    56  	}
    57  
    58  	firstRune, _ := utf8.DecodeRuneInString(name)
    59  	if !isValidDatabaseNameFirstRune(firstRune) {
    60  		return ex.New(ErrDatabaseNameInvalidFirstRune, ex.OptMessagef("database name: %s", name))
    61  	}
    62  
    63  	for _, r := range name {
    64  		if !isValidDatabaseNameRune(r) {
    65  			return ex.New(ErrDatabaseNameInvalid, ex.OptMessagef("database name: %s", name))
    66  		}
    67  	}
    68  
    69  	for _, reserved := range ReservedDatabaseNames {
    70  		if strings.EqualFold(reserved, name) {
    71  			return ex.New(ErrDatabaseNameReserved, ex.OptMessagef("database name: %s", name))
    72  		}
    73  	}
    74  	return nil
    75  }
    76  
    77  // isValidDatabaseNameFirstRune returns if the rune is valid as a first rune of a database name.
    78  func isValidDatabaseNameFirstRune(r rune) bool {
    79  	return unicode.IsLetter(r) || r == '_'
    80  }
    81  
    82  // isValidDatabaseNameRune is a rune predicate that indicites a rune is a valid database name component.
    83  func isValidDatabaseNameRune(r rune) bool {
    84  	return unicode.IsLetter(r) || unicode.IsDigit(r) || r == '_'
    85  }