Skip to content

Commit ad348e3

Browse files
feat: add default UUIDv8 generation with New method
2 parents e767226 + 6d0892a commit ad348e3

File tree

2 files changed

+166
-11
lines changed

2 files changed

+166
-11
lines changed

uuidv8.go

+36-2
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,11 @@
33
package uuidv8
44

55
import (
6+
"crypto/rand"
7+
"encoding/binary"
68
"encoding/json"
79
"fmt"
10+
"time"
811
)
912

1013
// Constants for the variant and version of UUIDs based on the RFC4122 specification.
@@ -32,7 +35,38 @@ type UUIDv8 struct {
3235
Node []byte // The node component of the UUID (typically 6 bytes).
3336
}
3437

35-
// NewUUIDv8 generates a new UUIDv8 based on the provided timestamp, clock sequence, and node.
38+
// New generates a UUIDv8 with default parameters.
39+
//
40+
// Default behavior:
41+
// - Timestamp: Current time in nanoseconds.
42+
// - ClockSeq: Random 12-bit value.
43+
// - Node: Random 6-byte node identifier.
44+
//
45+
// Returns:
46+
// - A string representation of the generated UUIDv8.
47+
// - An error if any component generation fails.
48+
func New() (string, error) {
49+
// Current timestamp
50+
timestamp := uint64(time.Now().UnixNano())
51+
52+
// Random clock sequence
53+
clockSeq := make([]byte, 2)
54+
if _, err := rand.Read(clockSeq); err != nil {
55+
return "", fmt.Errorf("failed to generate random clock sequence: %w", err)
56+
}
57+
clockSeqValue := binary.BigEndian.Uint16(clockSeq) & 0x0FFF // Mask to 12 bits
58+
59+
// Random node
60+
node := make([]byte, 6)
61+
if _, err := rand.Read(node); err != nil {
62+
return "", fmt.Errorf("failed to generate random node: %w", err)
63+
}
64+
65+
// Generate UUIDv8
66+
return NewWithParams(timestamp, clockSeqValue, node, TimestampBits48)
67+
}
68+
69+
// NewWithParams generates a new UUIDv8 based on the provided timestamp, clock sequence, and node.
3670
//
3771
// Parameters:
3872
// - timestamp: A 32-, 48-, or 60-bit timestamp value (depending on `timestampBits`).
@@ -43,7 +77,7 @@ type UUIDv8 struct {
4377
// Returns:
4478
// - A string representation of the generated UUIDv8.
4579
// - An error if the input parameters are invalid (e.g., incorrect node length or unsupported timestamp size).
46-
func NewUUIDv8(timestamp uint64, clockSeq uint16, node []byte, timestampBits int) (string, error) {
80+
func NewWithParams(timestamp uint64, clockSeq uint16, node []byte, timestampBits int) (string, error) {
4781
if len(node) != 6 {
4882
return "", fmt.Errorf("node must be 6 bytes, got %d bytes", len(node))
4983
}

uuidv8_test.go

+130-9
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,20 @@ import (
99
"github.com/ash3in/uuidv8"
1010
)
1111

12+
func TestNew_DefaultBehavior(t *testing.T) {
13+
t.Run("Generate UUIDv8 with default settings", func(t *testing.T) {
14+
uuid, err := uuidv8.New()
15+
if err != nil {
16+
t.Fatalf("New() failed: %v", err)
17+
}
18+
19+
// Check if the UUID is valid
20+
if !uuidv8.IsValidUUIDv8(uuid) {
21+
t.Errorf("New() generated an invalid UUID: %s", uuid)
22+
}
23+
})
24+
}
25+
1226
func TestNewUUIDv8(t *testing.T) {
1327
node := []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06}
1428
timestamp := uint64(1633024800000000000) // Fixed timestamp for deterministic tests
@@ -27,7 +41,7 @@ func TestNewUUIDv8(t *testing.T) {
2741

2842
for _, test := range tests {
2943
t.Run(test.description, func(t *testing.T) {
30-
uuid, err := uuidv8.NewUUIDv8(timestamp, clockSeq, node, test.timestampBits)
44+
uuid, err := uuidv8.NewWithParams(timestamp, clockSeq, node, test.timestampBits)
3145
if (err != nil) != test.expectedErr {
3246
t.Errorf("Expected error: %v, got: %v", test.expectedErr, err)
3347
}
@@ -49,7 +63,7 @@ func TestNewUUIDv8_NodeValidation(t *testing.T) {
4963

5064
for _, node := range invalidNodes {
5165
t.Run("Invalid node length", func(t *testing.T) {
52-
_, err := uuidv8.NewUUIDv8(1633024800, 0, node, uuidv8.TimestampBits48)
66+
_, err := uuidv8.NewWithParams(1633024800, 0, node, uuidv8.TimestampBits48)
5367
if err == nil {
5468
t.Errorf("Expected error for invalid node: %v", node)
5569
}
@@ -84,9 +98,9 @@ func TestFromString(t *testing.T) {
8498
timestamp := uint64(1633024800000000000) // Fixed timestamp
8599
clockSeq := uint16(0)
86100

87-
uuid, err := uuidv8.NewUUIDv8(timestamp, clockSeq, node, uuidv8.TimestampBits48)
101+
uuid, err := uuidv8.NewWithParams(timestamp, clockSeq, node, uuidv8.TimestampBits48)
88102
if err != nil {
89-
t.Fatalf("NewUUIDv8 failed: %v", err)
103+
t.Fatalf("NewWithParams failed: %v", err)
90104
}
91105

92106
parsed, err := uuidv8.FromString(uuid)
@@ -164,7 +178,7 @@ func TestConcurrencySafety(t *testing.T) {
164178

165179
timestamp := uint64(time.Now().UnixNano()) + uint64(index)
166180

167-
uuid, err := uuidv8.NewUUIDv8(timestamp, clockSeq, node, uuidv8.TimestampBits48)
181+
uuid, err := uuidv8.NewWithParams(timestamp, clockSeq, node, uuidv8.TimestampBits48)
168182
if err != nil {
169183
t.Errorf("Failed to generate UUIDv8 in concurrent environment: %v", err)
170184
}
@@ -194,7 +208,7 @@ func TestEdgeCases(t *testing.T) {
194208
node := []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06}
195209

196210
t.Run("Minimum timestamp and clock sequence", func(t *testing.T) {
197-
uuid, err := uuidv8.NewUUIDv8(0, 0, node, uuidv8.TimestampBits48)
211+
uuid, err := uuidv8.NewWithParams(0, 0, node, uuidv8.TimestampBits48)
198212
if err != nil || uuid == "" {
199213
t.Error("Failed to generate UUID with minimal timestamp and clock sequence")
200214
}
@@ -203,7 +217,7 @@ func TestEdgeCases(t *testing.T) {
203217
t.Run("Maximum timestamp and clock sequence", func(t *testing.T) {
204218
maxTimestamp := uint64(1<<48 - 1)
205219
maxClockSeq := uint16(1<<12 - 1)
206-
uuid, err := uuidv8.NewUUIDv8(maxTimestamp, maxClockSeq, node, uuidv8.TimestampBits48)
220+
uuid, err := uuidv8.NewWithParams(maxTimestamp, maxClockSeq, node, uuidv8.TimestampBits48)
207221
if err != nil || uuid == "" {
208222
t.Error("Failed to generate UUID with maximum timestamp and clock sequence")
209223
}
@@ -220,7 +234,7 @@ func TestMarshalJSON(t *testing.T) {
220234
clockSeq := uint16(0)
221235

222236
// Generate a valid UUIDv8
223-
uuidStr, err := uuidv8.NewUUIDv8(timestamp, clockSeq, node, uuidv8.TimestampBits48)
237+
uuidStr, err := uuidv8.NewWithParams(timestamp, clockSeq, node, uuidv8.TimestampBits48)
224238
if err != nil {
225239
t.Fatalf("Failed to generate UUIDv8: %v", err)
226240
}
@@ -285,7 +299,7 @@ func TestUnmarshalJSON(t *testing.T) {
285299
clockSeq := uint16(0)
286300

287301
// Generate a valid UUIDv8
288-
uuidStr, err := uuidv8.NewUUIDv8(timestamp, clockSeq, node, uuidv8.TimestampBits48)
302+
uuidStr, err := uuidv8.NewWithParams(timestamp, clockSeq, node, uuidv8.TimestampBits48)
289303
if err != nil {
290304
t.Fatalf("Failed to generate UUIDv8: %v", err)
291305
}
@@ -335,3 +349,110 @@ func TestUnmarshalInvalidJSON(t *testing.T) {
335349
}
336350
}
337351
}
352+
353+
func TestNew_Uniqueness(t *testing.T) {
354+
const numUUIDs = 1000
355+
uuidSet := make(map[string]struct{})
356+
357+
for i := 0; i < numUUIDs; i++ {
358+
uuid, err := uuidv8.New()
359+
if err != nil {
360+
t.Fatalf("New() failed: %v", err)
361+
}
362+
363+
if _, exists := uuidSet[uuid]; exists {
364+
t.Errorf("Duplicate UUID generated: %s", uuid)
365+
}
366+
uuidSet[uuid] = struct{}{}
367+
}
368+
369+
if len(uuidSet) != numUUIDs {
370+
t.Errorf("Expected %d unique UUIDs, but got %d", numUUIDs, len(uuidSet))
371+
}
372+
}
373+
374+
func TestNew_ConcurrencySafety(t *testing.T) {
375+
const concurrencyLevel = 100
376+
var wg sync.WaitGroup
377+
uuidSet := sync.Map{}
378+
379+
for i := 0; i < concurrencyLevel; i++ {
380+
wg.Add(1)
381+
go func() {
382+
defer wg.Done()
383+
uuid, err := uuidv8.New()
384+
if err != nil {
385+
t.Errorf("New() failed in concurrent environment: %v", err)
386+
}
387+
uuidSet.Store(uuid, true)
388+
}()
389+
}
390+
391+
wg.Wait()
392+
393+
// Verify uniqueness
394+
count := 0
395+
uuidSet.Range(func(_, _ interface{}) bool {
396+
count++
397+
return true
398+
})
399+
400+
if count != concurrencyLevel {
401+
t.Errorf("Expected %d unique UUIDs, but got %d", concurrencyLevel, count)
402+
}
403+
}
404+
405+
func TestNew_IntegrationWithParsing(t *testing.T) {
406+
uuid, err := uuidv8.New()
407+
if err != nil {
408+
t.Fatalf("New() failed: %v", err)
409+
}
410+
411+
parsed, err := uuidv8.FromString(uuid)
412+
if err != nil {
413+
t.Errorf("FromString failed to parse UUID generated by New(): %v", err)
414+
}
415+
416+
if parsed == nil {
417+
t.Error("Parsed UUID is nil")
418+
}
419+
}
420+
421+
func TestNew_EdgeCases(t *testing.T) {
422+
t.Run("Minimal possible timestamp and clock sequence", func(t *testing.T) {
423+
uuid, err := uuidv8.New()
424+
if err != nil {
425+
t.Fatalf("New() failed: %v", err)
426+
}
427+
428+
parsed, _ := uuidv8.FromString(uuid)
429+
if parsed.Timestamp == 0 || parsed.ClockSeq == 0 {
430+
t.Errorf("New() generated UUID with invalid minimal values: %s", uuid)
431+
}
432+
})
433+
}
434+
435+
func TestNew_JSONSerializationIntegration(t *testing.T) {
436+
uuid, err := uuidv8.New()
437+
if err != nil {
438+
t.Fatalf("New() failed: %v", err)
439+
}
440+
441+
// Serialize to JSON
442+
jsonData, err := json.Marshal(uuid)
443+
if err != nil {
444+
t.Errorf("Failed to marshal UUID to JSON: %v", err)
445+
}
446+
447+
// Deserialize from JSON
448+
var parsedUUID uuidv8.UUIDv8
449+
err = json.Unmarshal(jsonData, &parsedUUID)
450+
if err != nil {
451+
t.Errorf("Failed to unmarshal JSON to UUIDv8: %v", err)
452+
}
453+
454+
// Ensure the deserialized UUID matches the original
455+
if uuidv8.ToString(&parsedUUID) != uuid {
456+
t.Errorf("Mismatch between original and deserialized UUID: original %s, deserialized %s", uuid, uuidv8.ToString(&parsedUUID))
457+
}
458+
}

0 commit comments

Comments
 (0)