feat: scaffold app skeleton with config, ULID, health endpoints, compose stack (phase 1)
- internal .env loader (real env wins) + strictly validated config struct - ULID package: 48-bit ms timestamp + 80-bit crypto randomness, sortable - HTTP server with recovery/request-id/trusted-proxy/access-log middleware - /healthz, /readyz (pluggable checkers), /metricsz (counter registry) - multi-stage Dockerfile (alpine, non-root) + compose with healthchecks, app bound to 127.0.0.1:8787, mongo unpublished, graceful 15s shutdown Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,94 @@
|
||||
// Package ulid generates ULIDs (https://github.com/ulid/spec): 26-character
|
||||
// Crockford-base32 strings of a 48-bit millisecond timestamp followed by
|
||||
// 80 bits of cryptographic randomness. Lexicographic order equals creation
|
||||
// order at millisecond resolution, which the schema relies on for cursor
|
||||
// pagination and time-ordered message ids.
|
||||
package ulid
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
const alphabet = "0123456789ABCDEFGHJKMNPQRSTVWXYZ"
|
||||
|
||||
// Len is the length of the canonical string form.
|
||||
const Len = 26
|
||||
|
||||
var decode [256]byte
|
||||
|
||||
func init() {
|
||||
for i := range decode {
|
||||
decode[i] = 0xFF
|
||||
}
|
||||
for i := 0; i < len(alphabet); i++ {
|
||||
decode[alphabet[i]] = byte(i)
|
||||
// Crockford base32 is case-insensitive.
|
||||
decode[alphabet[i]|0x20] = byte(i)
|
||||
}
|
||||
}
|
||||
|
||||
// New returns a fresh ULID for the current time.
|
||||
func New() string { return At(time.Now()) }
|
||||
|
||||
// At returns a ULID whose timestamp component is t (randomness still fresh).
|
||||
func At(t time.Time) string {
|
||||
var b [16]byte
|
||||
ms := uint64(t.UnixMilli())
|
||||
b[0] = byte(ms >> 40)
|
||||
b[1] = byte(ms >> 32)
|
||||
b[2] = byte(ms >> 24)
|
||||
b[3] = byte(ms >> 16)
|
||||
b[4] = byte(ms >> 8)
|
||||
b[5] = byte(ms)
|
||||
if _, err := rand.Read(b[6:]); err != nil {
|
||||
// crypto/rand never fails on supported platforms; if it does, the
|
||||
// process cannot safely generate ids at all.
|
||||
panic(fmt.Sprintf("ulid: crypto/rand failed: %v", err))
|
||||
}
|
||||
|
||||
// Base32-encode 128 bits into 26 chars (130-bit capacity, top 2 bits 0):
|
||||
// consume bytes from the least-significant end, emit 5-bit groups
|
||||
// right-to-left, which yields the canonical big-endian representation.
|
||||
var out [Len]byte
|
||||
acc, nbits, j := uint32(0), 0, Len-1
|
||||
for i := 15; i >= 0; i-- {
|
||||
acc |= uint32(b[i]) << nbits
|
||||
nbits += 8
|
||||
for nbits >= 5 && j > 0 {
|
||||
out[j] = alphabet[acc&31]
|
||||
acc >>= 5
|
||||
nbits -= 5
|
||||
j--
|
||||
}
|
||||
}
|
||||
out[0] = alphabet[acc&31] // remaining 3 timestamp bits
|
||||
return string(out[:])
|
||||
}
|
||||
|
||||
// IsValid reports whether s is a well-formed ULID.
|
||||
func IsValid(s string) bool {
|
||||
if len(s) != Len {
|
||||
return false
|
||||
}
|
||||
for i := 0; i < Len; i++ {
|
||||
if decode[s[i]] == 0xFF {
|
||||
return false
|
||||
}
|
||||
}
|
||||
// First char encodes the top 3 bits of a 48-bit value; > '7' overflows.
|
||||
return decode[s[0]] <= 7
|
||||
}
|
||||
|
||||
// Timestamp extracts the embedded creation time.
|
||||
func Timestamp(s string) (time.Time, error) {
|
||||
if !IsValid(s) {
|
||||
return time.Time{}, fmt.Errorf("ulid: invalid id %q", s)
|
||||
}
|
||||
var ms uint64
|
||||
for i := 0; i < 10; i++ { // first 10 chars = 50 bits, low 48 are the timestamp
|
||||
ms = ms<<5 | uint64(decode[s[i]])
|
||||
}
|
||||
return time.UnixMilli(int64(ms)).UTC(), nil
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
package ulid
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestNewShape(t *testing.T) {
|
||||
id := New()
|
||||
if len(id) != Len {
|
||||
t.Fatalf("len = %d, want %d", len(id), Len)
|
||||
}
|
||||
for i := 0; i < len(id); i++ {
|
||||
if !strings.ContainsRune(alphabet, rune(id[i])) {
|
||||
t.Fatalf("char %q at %d not in Crockford alphabet", id[i], i)
|
||||
}
|
||||
}
|
||||
if !IsValid(id) {
|
||||
t.Fatalf("IsValid(%q) = false", id)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUniqueness(t *testing.T) {
|
||||
const n = 10000
|
||||
seen := make(map[string]struct{}, n)
|
||||
for i := 0; i < n; i++ {
|
||||
id := New()
|
||||
if _, dup := seen[id]; dup {
|
||||
t.Fatalf("duplicate ULID %q after %d ids", id, i)
|
||||
}
|
||||
seen[id] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
func TestLexicographicTimeOrdering(t *testing.T) {
|
||||
base := time.Now()
|
||||
var ids []string
|
||||
for i := 0; i < 50; i++ {
|
||||
ids = append(ids, At(base.Add(time.Duration(i)*time.Millisecond)))
|
||||
}
|
||||
if !sort.StringsAreSorted(ids) {
|
||||
t.Fatal("ULIDs with increasing timestamps are not lexicographically sorted")
|
||||
}
|
||||
}
|
||||
|
||||
func TestTimestampRoundTrip(t *testing.T) {
|
||||
tests := []time.Time{
|
||||
time.UnixMilli(0),
|
||||
time.Date(2026, 6, 12, 12, 0, 0, 0, time.UTC),
|
||||
time.Now(),
|
||||
}
|
||||
for _, want := range tests {
|
||||
id := At(want)
|
||||
got, err := Timestamp(id)
|
||||
if err != nil {
|
||||
t.Fatalf("Timestamp(%q): %v", id, err)
|
||||
}
|
||||
if got.UnixMilli() != want.UnixMilli() {
|
||||
t.Errorf("timestamp = %v, want %v", got.UnixMilli(), want.UnixMilli())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsValid(t *testing.T) {
|
||||
tests := []struct {
|
||||
in string
|
||||
want bool
|
||||
}{
|
||||
{New(), true},
|
||||
{strings.ToLower(New()), true}, // Crockford decoding is case-insensitive
|
||||
{"", false},
|
||||
{"short", false},
|
||||
{strings.Repeat("0", 25), false},
|
||||
{strings.Repeat("0", 27), false},
|
||||
{strings.Repeat("U", 26), false}, // U not in alphabet
|
||||
{"8" + strings.Repeat("0", 25), false}, // timestamp overflow (>48 bits)
|
||||
{"7" + strings.Repeat("Z", 25), true},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
if got := IsValid(tt.in); got != tt.want {
|
||||
t.Errorf("IsValid(%q) = %v, want %v", tt.in, got, tt.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestTimestampRejectsInvalid(t *testing.T) {
|
||||
if _, err := Timestamp("definitely-not-a-ulid!!!!!"); err == nil {
|
||||
t.Fatal("want error")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user