This is pretty fast and guarantees uniqueness when initialised correctly. Signed-off-by: Yonah <contrib@gensokyo.uk>
147 lines
3.8 KiB
Go
147 lines
3.8 KiB
Go
// Package ident is the reference implementation of system and member
|
|
// identifiers.
|
|
package ident
|
|
|
|
import (
|
|
"encoding"
|
|
"errors"
|
|
"fmt"
|
|
"math/rand/v2"
|
|
"strconv"
|
|
"sync"
|
|
"time"
|
|
"unsafe"
|
|
)
|
|
|
|
var (
|
|
// ErrNewline is returned for identifiers found to contain newline characters.
|
|
ErrNewline = errors.New("identifier contains newline characters")
|
|
// ErrSeparator is returned for representations of [F] missing its separator
|
|
// byte in its expected index.
|
|
ErrSeparator = errors.New("identifier has incorrect separator byte")
|
|
)
|
|
|
|
// UnexpectedSizeError describes a malformed string representation of an
|
|
// identifier, with unexpected length.
|
|
type UnexpectedSizeError struct {
|
|
Data []byte
|
|
Want int
|
|
}
|
|
|
|
func (e *UnexpectedSizeError) Error() string {
|
|
return "got " + strconv.Itoa(len(e.Data)) + " bytes for " +
|
|
"a " + strconv.Itoa(e.Want) + "-byte identifier"
|
|
}
|
|
|
|
// MS is a system or member identifier.
|
|
type MS interface {
|
|
encoding.TextMarshaler
|
|
encoding.TextUnmarshaler
|
|
fmt.Stringer
|
|
|
|
// EncodedSize returns the number of bytes appended by Encode.
|
|
EncodedSize() int
|
|
// Encode appends the canonical string representation of MS to dst and
|
|
// returns the extended buffer.
|
|
Encode(dst []byte) []byte
|
|
|
|
// ident is a no-op function but serves to distinguish types that are
|
|
// identifiers from ordinary types with custom marshaler/unmarshaler
|
|
// behaviour: a type is an identifier part if it has an ident method.
|
|
ident()
|
|
}
|
|
|
|
// F represents a full system or member identifier.
|
|
type F[V any, T interface {
|
|
MS
|
|
*V
|
|
}] struct {
|
|
// Underlying system or member.
|
|
I T
|
|
// Split from instance suffix after [Separator] byte.
|
|
Remote Remote
|
|
}
|
|
|
|
// Separator is the byte separating representation of [MS] from [Remote].
|
|
const Separator = ':'
|
|
|
|
// Encode appends the canonical string representation of full to dst and returns
|
|
// the extended buffer.
|
|
func (full *F[V, T]) Encode(dst []byte) []byte {
|
|
dst = full.I.Encode(dst)
|
|
dst = append(dst, Separator)
|
|
dst = full.Remote.Encode(dst)
|
|
return dst
|
|
}
|
|
|
|
// String returns the canonical string representation of full.
|
|
func (full *F[V, T]) String() string {
|
|
s := full.Encode(nil)
|
|
return unsafe.String(unsafe.SliceData(s), len(s))
|
|
}
|
|
|
|
// MarshalText returns the result of Encode.
|
|
func (full *F[V, T]) MarshalText() (data []byte, err error) {
|
|
return full.Encode(nil), nil
|
|
}
|
|
|
|
// UnmarshalText strictly decodes data into full.
|
|
func (full *F[V, T]) UnmarshalText(data []byte) (err error) {
|
|
sz := full.I.EncodedSize()
|
|
if len(data) < sz+1 || data[sz] != Separator {
|
|
return ErrSeparator
|
|
}
|
|
|
|
if full.I == nil {
|
|
full.I = new(V)
|
|
}
|
|
if err = full.I.UnmarshalText(data[:sz]); err != nil {
|
|
return
|
|
}
|
|
return full.Remote.UnmarshalText(data[sz+1:])
|
|
}
|
|
|
|
// PartG is machine-specific state of [Generator].
|
|
type PartG struct {
|
|
// Deployment site, typically denoting a datacenter servicing a region.
|
|
Site uint32
|
|
// Servicing host behind load balancer, unique within its Site.
|
|
Host uint32
|
|
}
|
|
|
|
// A Generator emits unique instances of [S].
|
|
type Generator struct {
|
|
PartG
|
|
mu sync.Mutex
|
|
r rand.Source
|
|
}
|
|
|
|
// S populates the value pointed to by p with a new unique value.
|
|
func (g *Generator) S(p *S) {
|
|
g.mu.Lock()
|
|
*p = S{PartG: g.PartG, Time: uint64(time.Now().UnixNano()), ID: g.r.Uint64()}
|
|
g.mu.Unlock()
|
|
}
|
|
|
|
// NewS creates a unique instance of [S] and returns its address.
|
|
func (g *Generator) NewS() *S {
|
|
var sid S
|
|
g.S(&sid)
|
|
return &sid
|
|
}
|
|
|
|
// M populates the value pointed to by p with a new unique value.
|
|
func (g *Generator) M(p *PartM, serial uint64) {
|
|
g.mu.Lock()
|
|
*p = PartM{Serial: serial, Time: uint64(time.Now().UnixNano()), ID: g.r.Uint64()}
|
|
g.mu.Unlock()
|
|
}
|
|
|
|
// New initialises a new instance of [Generator] and returns its address.
|
|
func New(site, host uint32) *Generator {
|
|
return &Generator{PartG{site, host}, sync.Mutex{}, rand.NewPCG(
|
|
uint64(site)<<32|uint64(host),
|
|
uint64(time.Now().UnixNano()),
|
|
)}
|
|
}
|