go.charczuk.com@v0.0.0-20240327042549-bc490516bd1a/sdk/uuid/uuid.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 uuid
     9  
    10  import (
    11  	"bytes"
    12  	"database/sql"
    13  	"database/sql/driver"
    14  	"encoding/hex"
    15  	"encoding/json"
    16  	"errors"
    17  	"fmt"
    18  	"strings"
    19  )
    20  
    21  // ErrInvalidScanSource is an error returned by scan.
    22  const (
    23  	ErrInvalidScanSource = "uuid; invalid scan source"
    24  )
    25  
    26  var (
    27  	_ sql.Scanner = (*UUID)(nil)
    28  )
    29  
    30  // UUID represents a unique identifier conforming to the RFC 4122 standard.
    31  // UUIDs are a fixed 128bit (16 byte) binary blob.
    32  type UUID [16]byte
    33  
    34  // Equal returns if a uuid is equal to another uuid.
    35  func (uuid UUID) Equal(other UUID) bool {
    36  	return bytes.Equal(uuid[0:], other[0:])
    37  }
    38  
    39  // Compare returns a comparison between the two uuids.
    40  func (uuid UUID) Compare(other UUID) int {
    41  	return bytes.Compare(uuid[0:], other[0:])
    42  }
    43  
    44  // String returns the uuid as a hex string.
    45  func (uuid UUID) String() string {
    46  	return hex.EncodeToString([]byte(uuid[:]))
    47  }
    48  
    49  // ShortString returns the first 8 bytes of the uuid as a hex string.
    50  func (uuid UUID) ShortString() string {
    51  	return hex.EncodeToString([]byte(uuid[:8]))
    52  }
    53  
    54  // Version returns the version byte of a uuid.
    55  func (uuid UUID) Version() byte {
    56  	return uuid[6] >> 4
    57  }
    58  
    59  // Format allows for conditional expansion in printf statements
    60  // based on the token and flags used.
    61  func (uuid UUID) Format(s fmt.State, verb rune) {
    62  	switch verb {
    63  	case 'v':
    64  		if s.Flag('+') {
    65  			b := []byte(uuid[:])
    66  			fmt.Fprintf(s,
    67  				"%08x-%04x-%04x-%04x-%012x",
    68  				b[:4], b[4:6], b[6:8], b[8:10], b[10:],
    69  			)
    70  			return
    71  		}
    72  		fmt.Fprint(s, hex.EncodeToString([]byte(uuid[:])))
    73  	case 's':
    74  		fmt.Fprint(s, hex.EncodeToString([]byte(uuid[:])))
    75  	case 'q':
    76  		fmt.Fprintf(s, "%b", uuid.Version())
    77  	}
    78  }
    79  
    80  // IsZero returns if the uuid is unset.
    81  func (uuid UUID) IsZero() bool {
    82  	return uuid == [16]byte{}
    83  }
    84  
    85  // IsV4 returns true iff uuid has version number 4, variant number 2, and length 16 bytes
    86  func (uuid UUID) IsV4() bool {
    87  	// check that version number is 4
    88  	if (uuid[6]&0xf0)^0x40 != 0 {
    89  		return false
    90  	}
    91  	// check that variant is 2
    92  	return (uuid[8]&0xc0)^0x80 == 0
    93  }
    94  
    95  // Marshal implements bytes marshal.
    96  func (uuid UUID) Marshal() ([]byte, error) {
    97  	return []byte(uuid[:]), nil
    98  }
    99  
   100  // MarshalTo marshals the uuid to a buffer.
   101  func (uuid UUID) MarshalTo(data []byte) (n int, err error) {
   102  	copy(data, uuid[:])
   103  	return 16, nil
   104  }
   105  
   106  // Unmarshal implements bytes unmarshal.
   107  func (uuid *UUID) Unmarshal(data []byte) error {
   108  	if len(data) == 0 {
   109  		return nil
   110  	}
   111  	var id UUID
   112  	copy(id[:], data)
   113  	*uuid = id
   114  	return nil
   115  }
   116  
   117  // Size returns the size of the uuid.
   118  func (uuid *UUID) Size() int {
   119  	if uuid == nil {
   120  		return 0
   121  	}
   122  	return 16
   123  }
   124  
   125  // MarshalJSON marshals a uuid as json.
   126  func (uuid UUID) MarshalJSON() ([]byte, error) {
   127  	return json.Marshal(uuid.String())
   128  }
   129  
   130  // UnmarshalJSON unmarshals a uuid from json.
   131  func (uuid *UUID) UnmarshalJSON(corpus []byte) error {
   132  	raw := strings.TrimSpace(string(corpus))
   133  	raw = strings.TrimPrefix(raw, "\"")
   134  	raw = strings.TrimSuffix(raw, "\"")
   135  	return ParseInto(uuid, raw)
   136  }
   137  
   138  // MarshalYAML marshals a uuid as yaml.
   139  func (uuid UUID) MarshalYAML() (interface{}, error) {
   140  	return uuid.String(), nil
   141  }
   142  
   143  // UnmarshalYAML unmarshals a uuid from yaml.
   144  func (uuid *UUID) UnmarshalYAML(unmarshaler func(interface{}) error) error {
   145  	var corpus string
   146  	if err := unmarshaler(&corpus); err != nil {
   147  		return err
   148  	}
   149  
   150  	raw := strings.TrimSpace(string(corpus))
   151  	raw = strings.TrimPrefix(raw, "\"")
   152  	raw = strings.TrimSuffix(raw, "\"")
   153  	return ParseInto(uuid, raw)
   154  }
   155  
   156  // Scan scans a uuid from a db value.
   157  func (uuid *UUID) Scan(src interface{}) error {
   158  	switch v := src.(type) {
   159  	case string:
   160  		return ParseInto(uuid, v)
   161  	case []byte:
   162  		return ParseInto(uuid, string(v))
   163  	default:
   164  		return errors.New(ErrInvalidScanSource)
   165  	}
   166  }
   167  
   168  // Value returns a sql driver value.
   169  func (uuid UUID) Value() (driver.Value, error) {
   170  	if uuid.IsZero() {
   171  		return nil, nil
   172  	}
   173  	return uuid.String(), nil
   174  }
   175  
   176  // Set accepts a src and populates the value based on it.
   177  //
   178  // This is similar to Scan but used directly by pgx.
   179  func (uuid *UUID) Set(src interface{}) error {
   180  	if src == nil {
   181  		*uuid = [16]byte{}
   182  		return nil
   183  	}
   184  
   185  	switch value := src.(type) {
   186  	case interface{ Get() interface{} }:
   187  		value2 := value.Get()
   188  		if value2 != value {
   189  			return uuid.Set(value2)
   190  		}
   191  	case fmt.Stringer:
   192  		value2 := value.String()
   193  		return uuid.Set(value2)
   194  	case [16]byte:
   195  		*uuid = value
   196  	case []byte:
   197  		if value != nil {
   198  			if len(value) != 16 {
   199  				return fmt.Errorf("[]byte must be 16 bytes to convert to UUID: %d", len(value))
   200  			}
   201  			copy(uuid[:], value)
   202  		} else {
   203  			*uuid = [16]byte{}
   204  		}
   205  	case string:
   206  		return ParseInto(uuid, value)
   207  	case *string:
   208  		return ParseInto(uuid, *value)
   209  	default:
   210  		return fmt.Errorf("cannot convert %v to UUID", value)
   211  	}
   212  	return nil
   213  }