go.charczuk.com@v0.0.0-20240327042549-bc490516bd1a/sdk/db/dbutil/validate_database_name.go (about)

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