From d78a0ed27c8932296bf9b944b915e74574551e2e Mon Sep 17 00:00:00 2001 From: Jonathan Vuillemin Date: Mon, 18 Nov 2024 12:47:50 +0100 Subject: [PATCH] feat(generate): Added UUIDv6 generation (#301) --- generate/.golangci.yml | 2 +- generate/README.md | 52 ++++++++++++++ generate/generatetest/uuidv6/generator.go | 39 +++++++++++ .../generatetest/uuidv6/generator_test.go | 68 +++++++++++++++++++ generate/generatetest/uuidv7/generator.go | 2 +- .../generatetest/uuidv7/generator_test.go | 2 +- generate/uuidv6/factory.go | 19 ++++++ generate/uuidv6/factory_test.go | 43 ++++++++++++ generate/uuidv6/generator.go | 23 +++++++ generate/uuidv6/generator_test.go | 43 ++++++++++++ 10 files changed, 290 insertions(+), 3 deletions(-) create mode 100644 generate/generatetest/uuidv6/generator.go create mode 100644 generate/generatetest/uuidv6/generator_test.go create mode 100644 generate/uuidv6/factory.go create mode 100644 generate/uuidv6/factory_test.go create mode 100644 generate/uuidv6/generator.go create mode 100644 generate/uuidv6/generator_test.go diff --git a/generate/.golangci.yml b/generate/.golangci.yml index edf0e9ec..c5443fc8 100644 --- a/generate/.golangci.yml +++ b/generate/.golangci.yml @@ -40,7 +40,7 @@ linters: - importas - ineffassign - interfacebloat - - logrlint + - loggercheck - maintidx - makezero - misspell diff --git a/generate/README.md b/generate/README.md index e6852f61..6022fe6d 100644 --- a/generate/README.md +++ b/generate/README.md @@ -12,6 +12,7 @@ * [Installation](#installation) * [Documentation](#documentation) * [UUID V4](#uuid-v4) + * [UUID V6](#uuid-v6) * [UUID V7](#uuid-v7) @@ -71,6 +72,57 @@ func main() { } ``` +### UUID V6 + +This module provides an [UuidV6Generator](uuidv6/generator.go) interface, allowing to generate UUIDs V6. + +The `DefaultUuidV6Generator` implementing it is based on [Google UUID](https://github.com/google/uuid). + +```go +package main + +import ( + "fmt" + + "github.com/ankorstore/yokai/generate/uuidv6" + uuidv6test "github.com/ankorstore/yokai/generate/generatetest/uuidv6" +) + +func main() { + // default UUID V6 generator + generator := uuidv6.NewDefaultUuidV6Generator() + uuid, _ := generator.Generate() + fmt.Printf("uuid: %s", uuid.String()) // uuid: 1efa5a1e-a679-67b7-ae79-f36f6749aa6b + + // test UUID generator (with deterministic value for testing, requires valid UUID v6) + testGenerator, _ := uuidv6test.NewTestUuidV6Generator("1efa5a08-2883-6652-b357-5dd221ce0561") + uuid, _ = testGenerator.Generate() + fmt.Printf("uuid: %s", uuid.String()) // uuid: 1efa5a08-2883-6652-b357-5dd221ce0561 +} +``` + +The module also provides a [UuidV6GeneratorFactory](uuidv6/factory.go) interface, to create +the [UuidV6Generator](uuidv6/generator.go) instances. + +The `DefaultUuidV6GeneratorFactory` generates `DefaultUuidV6Generator` instances. + +```go +package main + +import ( + "fmt" + + "github.com/ankorstore/yokai/generate/uuidv6" +) + +func main() { + // default UUID generator factory + generator := uuidv6.NewDefaultUuidV6GeneratorFactory().Create() + uuid, _ := generator.Generate() + fmt.Printf("uuid: %s", uuid.String()) // uuid: 1efa5a1e-a679-67b7-ae79-f36f6749aa6b +} +``` + ### UUID V7 This module provides an [UuidV7Generator](uuidv7/generator.go) interface, allowing to generate UUIDs V7. diff --git a/generate/generatetest/uuidv6/generator.go b/generate/generatetest/uuidv6/generator.go new file mode 100644 index 00000000..0cf388ec --- /dev/null +++ b/generate/generatetest/uuidv6/generator.go @@ -0,0 +1,39 @@ +package uuidv6 + +import googleuuid "github.com/google/uuid" + +// TestUuidV6Generator is a [UuidV6Generator] implementation allowing deterministic generations (for testing). +type TestUuidV6Generator struct { + value string +} + +// NewTestUuidV6Generator returns a [TestUuidGenerator], implementing [UuidGenerator]. +// +// It accepts a value that will be used for deterministic generation results. +func NewTestUuidV6Generator(value string) (*TestUuidV6Generator, error) { + err := googleuuid.Validate(value) + if err != nil { + return nil, err + } + + return &TestUuidV6Generator{ + value: value, + }, nil +} + +// SetValue sets the value to use for deterministic generations. +func (g *TestUuidV6Generator) SetValue(value string) error { + err := googleuuid.Validate(value) + if err != nil { + return err + } + + g.value = value + + return nil +} + +// Generate returns the configured deterministic value. +func (g *TestUuidV6Generator) Generate() (googleuuid.UUID, error) { + return googleuuid.Parse(g.value) +} diff --git a/generate/generatetest/uuidv6/generator_test.go b/generate/generatetest/uuidv6/generator_test.go new file mode 100644 index 00000000..369c7b53 --- /dev/null +++ b/generate/generatetest/uuidv6/generator_test.go @@ -0,0 +1,68 @@ +package uuidv6_test + +import ( + "testing" + + uuidv6test "github.com/ankorstore/yokai/generate/generatetest/uuidv6" + "github.com/ankorstore/yokai/generate/uuidv6" + "github.com/stretchr/testify/assert" +) + +const ( + uuid1 = "1efa59f2-d438-6ec0-9d52-4da3ad16f2c6" + uuid2 = "1efa59f2-d438-6ec1-8b52-6844309a22de" + uuid3 = "1efa59f2-d438-6ec2-abd0-e36a967ab868" +) + +func TestNewTestUuidV6Generator(t *testing.T) { + t.Parallel() + + generator, err := uuidv6test.NewTestUuidV6Generator(uuid1) + assert.NoError(t, err) + + assert.IsType(t, &uuidv6test.TestUuidV6Generator{}, generator) + assert.Implements(t, (*uuidv6.UuidV6Generator)(nil), generator) +} + +func TestGenerateSuccess(t *testing.T) { + t.Parallel() + + generator, err := uuidv6test.NewTestUuidV6Generator(uuid2) + assert.NoError(t, err) + + value1, err := generator.Generate() + assert.NoError(t, err) + + value2, err := generator.Generate() + assert.NoError(t, err) + + assert.Equal(t, uuid2, value1.String()) + assert.Equal(t, uuid2, value2.String()) + + err = generator.SetValue(uuid3) + assert.NoError(t, err) + + value1, err = generator.Generate() + assert.NoError(t, err) + + value2, err = generator.Generate() + assert.NoError(t, err) + + assert.Equal(t, uuid3, value1.String()) + assert.Equal(t, uuid3, value2.String()) +} + +func TestGenerateFailure(t *testing.T) { + t.Parallel() + + _, err := uuidv6test.NewTestUuidV6Generator("invalid") + assert.Error(t, err) + assert.Contains(t, err.Error(), "invalid UUID length: 7") + + generator, err := uuidv6test.NewTestUuidV6Generator(uuid1) + assert.NoError(t, err) + + err = generator.SetValue("invalid") + assert.Error(t, err) + assert.Contains(t, err.Error(), "invalid UUID length: 7") +} diff --git a/generate/generatetest/uuidv7/generator.go b/generate/generatetest/uuidv7/generator.go index 268b800c..8e5ad041 100644 --- a/generate/generatetest/uuidv7/generator.go +++ b/generate/generatetest/uuidv7/generator.go @@ -1,4 +1,4 @@ -package uuid +package uuidv7 import googleuuid "github.com/google/uuid" diff --git a/generate/generatetest/uuidv7/generator_test.go b/generate/generatetest/uuidv7/generator_test.go index 39513b45..f8737d67 100644 --- a/generate/generatetest/uuidv7/generator_test.go +++ b/generate/generatetest/uuidv7/generator_test.go @@ -1,4 +1,4 @@ -package uuid_test +package uuidv7_test import ( "testing" diff --git a/generate/uuidv6/factory.go b/generate/uuidv6/factory.go new file mode 100644 index 00000000..82f5f715 --- /dev/null +++ b/generate/uuidv6/factory.go @@ -0,0 +1,19 @@ +package uuidv6 + +// UuidV6GeneratorFactory is the interface for [UuidV6Generator] factories. +type UuidV6GeneratorFactory interface { + Create() UuidV6Generator +} + +// DefaultUuidV6GeneratorFactory is the default [UuidV6GeneratorFactory] implementation. +type DefaultUuidV6GeneratorFactory struct{} + +// NewDefaultUuidV6GeneratorFactory returns a [DefaultUuidV6GeneratorFactory], implementing [UuidV6GeneratorFactory]. +func NewDefaultUuidV6GeneratorFactory() UuidV6GeneratorFactory { + return &DefaultUuidV6GeneratorFactory{} +} + +// Create returns a new [UuidV6Generator]. +func (g *DefaultUuidV6GeneratorFactory) Create() UuidV6Generator { + return NewDefaultUuidV6Generator() +} diff --git a/generate/uuidv6/factory_test.go b/generate/uuidv6/factory_test.go new file mode 100644 index 00000000..b4dd6536 --- /dev/null +++ b/generate/uuidv6/factory_test.go @@ -0,0 +1,43 @@ +package uuidv6_test + +import ( + "testing" + + "github.com/ankorstore/yokai/generate/uuidv6" + googleuuid "github.com/google/uuid" + "github.com/stretchr/testify/assert" +) + +func TestNewDefaultUuidV6GeneratorFactory(t *testing.T) { + t.Parallel() + + factory := uuidv6.NewDefaultUuidV6GeneratorFactory() + + assert.IsType(t, &uuidv6.DefaultUuidV6GeneratorFactory{}, factory) + assert.Implements(t, (*uuidv6.UuidV6GeneratorFactory)(nil), factory) +} + +func TestCreate(t *testing.T) { + t.Parallel() + + generator := uuidv6.NewDefaultUuidV6GeneratorFactory().Create() + + uuid1, err := generator.Generate() + assert.NoError(t, err) + + uuid2, err := generator.Generate() + assert.NoError(t, err) + + assert.NotEqual(t, uuid1, uuid2) + + parsedValue1, err := googleuuid.Parse(uuid1.String()) + assert.NoError(t, err) + + parsedValue2, err := googleuuid.Parse(uuid2.String()) + assert.NoError(t, err) + + assert.NotEqual(t, parsedValue1.String(), parsedValue2.String()) + + assert.Equal(t, uuid1.String(), parsedValue1.String()) + assert.Equal(t, uuid2.String(), parsedValue2.String()) +} diff --git a/generate/uuidv6/generator.go b/generate/uuidv6/generator.go new file mode 100644 index 00000000..fce0bfda --- /dev/null +++ b/generate/uuidv6/generator.go @@ -0,0 +1,23 @@ +package uuidv6 + +import googleuuid "github.com/google/uuid" + +// UuidV6Generator is the interface for UUID v6 generators. +type UuidV6Generator interface { + Generate() (googleuuid.UUID, error) +} + +// DefaultUuidV6Generator is the default [UuidGenerator] implementation. +type DefaultUuidV6Generator struct{} + +// NewDefaultUuidV6Generator returns a [DefaultUuidGenerator], implementing [UuidGenerator]. +func NewDefaultUuidV6Generator() *DefaultUuidV6Generator { + return &DefaultUuidV6Generator{} +} + +// Generate returns a new UUID V6, using [Google UUID]. +// +// [Google UUID]: https://github.com/google/uuid +func (g *DefaultUuidV6Generator) Generate() (googleuuid.UUID, error) { + return googleuuid.NewV6() +} diff --git a/generate/uuidv6/generator_test.go b/generate/uuidv6/generator_test.go new file mode 100644 index 00000000..0eadb4b3 --- /dev/null +++ b/generate/uuidv6/generator_test.go @@ -0,0 +1,43 @@ +package uuidv6_test + +import ( + "testing" + + "github.com/ankorstore/yokai/generate/uuidv6" + googleuuid "github.com/google/uuid" + "github.com/stretchr/testify/assert" +) + +func TestNewDefaultUuidV6Generator(t *testing.T) { + t.Parallel() + + generator := uuidv6.NewDefaultUuidV6Generator() + + assert.IsType(t, &uuidv6.DefaultUuidV6Generator{}, generator) + assert.Implements(t, (*uuidv6.UuidV6Generator)(nil), generator) +} + +func TestGenerate(t *testing.T) { + t.Parallel() + + generator := uuidv6.NewDefaultUuidV6Generator() + + uuid1, err := generator.Generate() + assert.NoError(t, err) + + uuid2, err := generator.Generate() + assert.NoError(t, err) + + assert.NotEqual(t, uuid1.String(), uuid2.String()) + + parsedUuid1, err := googleuuid.Parse(uuid1.String()) + assert.NoError(t, err) + + parsedUuid2, err := googleuuid.Parse(uuid2.String()) + assert.NoError(t, err) + + assert.NotEqual(t, parsedUuid1.String(), parsedUuid2.String()) + + assert.Equal(t, uuid1.String(), parsedUuid1.String()) + assert.Equal(t, uuid2.String(), parsedUuid2.String()) +}