github.com/richardwilkes/toolbox@v1.121.0/tid/tid.go (about)

     1  // Copyright (c) 2016-2024 by Richard A. Wilkes. All rights reserved.
     2  //
     3  // This Source Code Form is subject to the terms of the Mozilla Public
     4  // License, version 2.0. If a copy of the MPL was not distributed with
     5  // this file, You can obtain one at http://mozilla.org/MPL/2.0/.
     6  //
     7  // This Source Code Form is "Incompatible With Secondary Licenses", as
     8  // defined by the Mozilla Public License, version 2.0.
     9  
    10  package tid
    11  
    12  import (
    13  	"crypto/rand"
    14  	"encoding/base64"
    15  	"fmt"
    16  	"strings"
    17  
    18  	"github.com/richardwilkes/toolbox/errs"
    19  )
    20  
    21  // TID is a unique identifier. These are similar to v4 UUIDs, but are shorter and have a different format that includes
    22  // a kind byte as the first character. TIDs are 17 characters long, are URL safe, and contain 96 bits of entropy.
    23  type TID string
    24  
    25  // KindAlphabet is the set of characters that can be used as the first character of a TID. The kind has no intrinsic
    26  // meaning, but can be used to differentiate between different types of ids.
    27  const KindAlphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
    28  
    29  // MustNewTID creates a new TID with a random value and the specified kind. If an error occurs, this function panics.
    30  func MustNewTID(kind byte) TID {
    31  	tid, err := NewTID(kind)
    32  	if err != nil {
    33  		panic(err)
    34  	}
    35  	return tid
    36  }
    37  
    38  // NewTID creates a new TID with a random value and the specified kind.
    39  func NewTID(kind byte) (TID, error) {
    40  	if strings.IndexByte(KindAlphabet, kind) == -1 {
    41  		return "", errs.New("invalid kind")
    42  	}
    43  	var buffer [12]byte
    44  	if _, err := rand.Read(buffer[:]); err != nil {
    45  		return "", errs.Wrap(err)
    46  	}
    47  	return TID(fmt.Sprintf("%c%s", kind, base64.RawURLEncoding.EncodeToString(buffer[:]))), nil
    48  }
    49  
    50  // FromString converts a string to a TID.
    51  func FromString(id string) (TID, error) {
    52  	tid := TID(id)
    53  	if !IsValid(tid) {
    54  		return "", errs.New("invalid TID")
    55  	}
    56  	return tid, nil
    57  }
    58  
    59  // FromStringOfKind converts a string to a TID and verifies that it has the specified kind.
    60  func FromStringOfKind(id string, kind byte) (TID, error) {
    61  	tid := TID(id)
    62  	if !IsKindAndValid(tid, kind) {
    63  		return "", errs.New("invalid TID")
    64  	}
    65  	return tid, nil
    66  }
    67  
    68  // IsValid returns true if the TID is a valid TID.
    69  func IsValid(id TID) bool {
    70  	if len(id) != 17 || strings.IndexByte(KindAlphabet, id[0]) == -1 {
    71  		return false
    72  	}
    73  	_, err := base64.RawURLEncoding.DecodeString(string(id[1:]))
    74  	return err == nil
    75  }
    76  
    77  // IsKind returns true if the TID has the specified kind.
    78  func IsKind(id TID, kind byte) bool {
    79  	return len(id) == 17 && id[0] == kind && strings.IndexByte(KindAlphabet, kind) != -1
    80  }
    81  
    82  // IsKindAndValid returns true if the TID is a valid TID with the specified kind.
    83  func IsKindAndValid(id TID, kind byte) bool {
    84  	if !IsKind(id, kind) {
    85  		return false
    86  	}
    87  	_, err := base64.RawURLEncoding.DecodeString(string(id[1:]))
    88  	return err == nil
    89  }