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 }