package actor import ( "crypto/rand" "encoding/json" "fmt" ) type GrainId uint64 const base62Alphabet = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" // Reverse lookup (0xFF marks invalid) var base62Rev [256]byte func init() { for i := range base62Rev { base62Rev[i] = 0xFF } for i := 0; i < len(base62Alphabet); i++ { base62Rev[base62Alphabet[i]] = byte(i) } } // String returns the canonical base62 encoding of the 64-bit id. func (id GrainId) String() string { return encodeBase62(uint64(id)) } // MarshalJSON encodes the cart id as a JSON string. func (id GrainId) MarshalJSON() ([]byte, error) { return json.Marshal(id.String()) } // UnmarshalJSON decodes a cart id from a JSON string containing base62 text. func (id *GrainId) UnmarshalJSON(data []byte) error { var s string if err := json.Unmarshal(data, &s); err != nil { return err } parsed, ok := ParseGrainId(s) if !ok { return fmt.Errorf("invalid cart id: %q", s) } *id = parsed return nil } // NewGrainId generates a new cryptographically random non-zero 64-bit id. func NewGrainId() (GrainId, error) { var b [8]byte if _, err := rand.Read(b[:]); err != nil { return 0, fmt.Errorf("NewGrainId: %w", err) } u := (uint64(b[0]) << 56) | (uint64(b[1]) << 48) | (uint64(b[2]) << 40) | (uint64(b[3]) << 32) | (uint64(b[4]) << 24) | (uint64(b[5]) << 16) | (uint64(b[6]) << 8) | uint64(b[7]) if u == 0 { // Extremely unlikely; regenerate once to avoid "0" identifier if desired. return NewGrainId() } return GrainId(u), nil } // MustNewGrainId panics if generation fails. func MustNewGrainId() GrainId { id, err := NewGrainId() if err != nil { panic(err) } return id } // ParseGrainId parses a base62 string into a GrainId. // Returns (0,false) for invalid input. func ParseGrainId(s string) (GrainId, bool) { // Accept length 1..11 (11 sufficient for 64 bits). Reject >11 immediately. // Provide a slightly looser upper bound (<=16) only if you anticipate future // extensions; here we stay strict. if len(s) == 0 || len(s) > 11 { return 0, false } u, ok := decodeBase62(s) if !ok { return 0, false } return GrainId(u), true } // MustParseGrainId panics on invalid base62 input. func MustParseGrainId(s string) GrainId { id, ok := ParseGrainId(s) if !ok { panic(fmt.Sprintf("invalid cart id: %q", s)) } return id } // encodeBase62 converts a uint64 to base62 (shortest form). func encodeBase62(u uint64) string { if u == 0 { return "0" } var buf [11]byte i := len(buf) for u > 0 { i-- buf[i] = base62Alphabet[u%62] u /= 62 } return string(buf[i:]) } // decodeBase62 converts base62 text to uint64. func decodeBase62(s string) (uint64, bool) { var v uint64 for i := 0; i < len(s); i++ { c := s[i] d := base62Rev[c] if d == 0xFF { return 0, false } v = v*62 + uint64(d) } return v, true }