Chore: Replace lithammer/shortuuid with custom shortuuid implementation and update tests

This commit is contained in:
Ralph Slooten
2026-05-05 17:09:55 +12:00
parent 86b0cf8557
commit 878c68bb49
9 changed files with 111 additions and 9 deletions

View File

@@ -31,7 +31,7 @@ jobs:
# https://olegk.dev/github-actions-and-go
run: gofmt -s -w . && git diff --exit-code
- name: Run Go tests
run: go test -p 1 ./internal/storage ./server ./internal/smtpd ./internal/pop3 ./internal/tools ./internal/html2text ./internal/htmlcheck ./internal/linkcheck -v
run: go test -p 1 ./internal/storage ./server ./internal/smtpd ./internal/pop3 ./internal/tools ./internal/html2text ./internal/htmlcheck ./internal/linkcheck ./internal/shortuuid -v
- name: Run Go benchmarking
run: go test -p 1 ./internal/storage ./internal/html2text -bench=.

3
go.mod
View File

@@ -9,12 +9,12 @@ require (
github.com/axllent/semver v1.0.0
github.com/goccy/go-yaml v1.19.2
github.com/gomarkdown/markdown v0.0.0-20260412113850-134a5b2cce7f
github.com/google/uuid v1.6.0
github.com/gorilla/websocket v1.5.3
github.com/jhillyerd/enmime/v2 v2.3.0
github.com/klauspost/compress v1.18.5
github.com/kovidgoyal/imaging v1.8.21
github.com/leporo/sqlf v1.4.0
github.com/lithammer/shortuuid/v4 v4.2.0
github.com/pkg/errors v0.9.1
github.com/prometheus/client_golang v1.23.2
github.com/rqlite/gorqlite v0.0.0-20250609141355-ac86a4a1c9a8
@@ -41,7 +41,6 @@ require (
github.com/fatih/color v1.19.0 // indirect
github.com/goccy/go-json v0.10.6 // indirect
github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/gorilla/css v1.0.1 // indirect
github.com/inbucket/html2text v1.0.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect

2
go.sum
View File

@@ -69,8 +69,6 @@ github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/leporo/sqlf v1.4.0 h1:SyWnX/8GSGOzVmanG0Ub1c04mR9nNl6Tq3IeFKX2/4c=
github.com/leporo/sqlf v1.4.0/go.mod h1:pgN9yKsAnQ+2ewhbZogr98RcasUjPsHF3oXwPPhHvBw=
github.com/lithammer/shortuuid/v4 v4.2.0 h1:LMFOzVB3996a7b8aBuEXxqOBflbfPQAiVzkIcHO0h8c=
github.com/lithammer/shortuuid/v4 v4.2.0/go.mod h1:D5noHZ2oFw/YaKCfGy0YxyE7M0wMbezmMjPdhyEFe6Y=
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
github.com/mattn/go-isatty v0.0.21 h1:xYae+lCNBP7QuW4PUnNG61ffM4hVIfm+zUzDuSzYLGs=

View File

@@ -0,0 +1,53 @@
// Package shortuuid provides a simple way to generate short, unique, alphanumeric identifiers.
// The generated IDs are 22 characters long and consist of uppercase letters, lowercase letters, and digits.
package shortuuid
import (
"encoding/binary"
"math/bits"
"github.com/google/uuid"
)
const (
alphabet = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
length = 22
nDigits = 10
divisor = 839299365868340224 // 62^10, max power of 62 that fits in uint64
)
// New returns a 22-character alphanumeric unique identifier.
func New() string {
id := uuid.New()
num := [2]uint64{
binary.BigEndian.Uint64(id[8:]),
binary.BigEndian.Uint64(id[:8]),
}
buf := make([]byte, length)
var r uint64
i := length - 1
for num[1] > 0 || num[0] > 0 {
num, r = quoRem64(num, divisor)
for j := 0; j < nDigits && i >= 0; j++ {
buf[i] = alphabet[r%62]
r /= 62
i--
}
}
for ; i >= 0; i-- {
buf[i] = alphabet[0]
}
return string(buf)
}
// quoRem64 divides a 128-bit number (represented as [lo, hi] uint64) by v,
// returning the quotient and remainder.
func quoRem64(u [2]uint64, v uint64) ([2]uint64, uint64) {
var q [2]uint64
var r uint64
q[1], r = bits.Div64(0, u[1], v)
q[0], r = bits.Div64(r, u[0], v)
return q, r
}

View File

@@ -0,0 +1,52 @@
package shortuuid
import (
"regexp"
"testing"
)
// alphanumeric matches IDs that contain only digits and ASCII letters.
var alphanumeric = regexp.MustCompile(`^[0-9A-Za-z]+$`)
// TestLength verifies that every generated ID is exactly 22 characters long,
// including when the UUID encodes to a value with leading zero-padding.
func TestLength(t *testing.T) {
for range 100 {
id := New()
if len(id) != length {
t.Errorf("expected length %d, got %d: %q", length, len(id), id)
}
}
}
// TestAlphanumeric verifies that no ID contains hyphens, underscores, or any
// other non-alphanumeric character that would be unsafe in a URL path segment.
func TestAlphanumeric(t *testing.T) {
for range 100 {
id := New()
if !alphanumeric.MatchString(id) {
t.Errorf("non-alphanumeric characters in ID: %q", id)
}
}
}
// TestUnique verifies that IDs are unique across a large sample. Collisions are
// cryptographically implausible given the 122-bit UUID entropy, so any hit here
// indicates a bug in the encoding (e.g. truncation, constant output).
func TestUnique(t *testing.T) {
seen := make(map[string]struct{}, 1000000)
for range 1000000 {
id := New()
if _, exists := seen[id]; exists {
t.Fatalf("duplicate ID generated: %q", id)
}
seen[id] = struct{}{}
}
}
// BenchmarkNew measures the cost of generating a single ID, including UUID generation.
func BenchmarkNew(b *testing.B) {
for b.Loop() {
_ = New()
}
}

View File

@@ -12,11 +12,11 @@ import (
"github.com/axllent/mailpit/config"
"github.com/axllent/mailpit/internal/auth"
"github.com/axllent/mailpit/internal/logger"
"github.com/axllent/mailpit/internal/shortuuid"
"github.com/axllent/mailpit/internal/stats"
"github.com/axllent/mailpit/internal/storage"
"github.com/axllent/mailpit/internal/tools"
"github.com/axllent/mailpit/server/websockets"
"github.com/lithammer/shortuuid/v4"
"github.com/pkg/errors"
)

View File

@@ -19,12 +19,12 @@ import (
"github.com/axllent/mailpit/config"
"github.com/axllent/mailpit/internal/logger"
"github.com/axllent/mailpit/internal/shortuuid"
"github.com/axllent/mailpit/internal/tools"
"github.com/axllent/mailpit/server/webhook"
"github.com/axllent/mailpit/server/websockets"
"github.com/jhillyerd/enmime/v2"
"github.com/leporo/sqlf"
"github.com/lithammer/shortuuid/v4"
)
// Store will save an email to the database tables.

View File

@@ -10,10 +10,10 @@ import (
"github.com/axllent/mailpit/config"
"github.com/axllent/mailpit/internal/logger"
"github.com/axllent/mailpit/internal/shortuuid"
"github.com/axllent/mailpit/internal/smtpd"
"github.com/axllent/mailpit/internal/storage"
"github.com/axllent/mailpit/internal/tools"
"github.com/lithammer/shortuuid/v4"
)
// ReleaseMessage (method: POST) will release a message via a pre-configured external SMTP server.

View File

@@ -21,6 +21,7 @@ import (
"github.com/axllent/mailpit/internal/logger"
"github.com/axllent/mailpit/internal/pop3"
"github.com/axllent/mailpit/internal/prometheus"
"github.com/axllent/mailpit/internal/shortuuid"
"github.com/axllent/mailpit/internal/snakeoil"
"github.com/axllent/mailpit/internal/stats"
"github.com/axllent/mailpit/internal/storage"
@@ -28,7 +29,6 @@ import (
"github.com/axllent/mailpit/server/apiv1"
"github.com/axllent/mailpit/server/handlers"
"github.com/axllent/mailpit/server/websockets"
"github.com/lithammer/shortuuid/v4"
)
var (