Skip to content
This repository was archived by the owner on Apr 29, 2026. It is now read-only.

feat: generic AnyUUID/AnyInt64 with variable prefix support#7

Merged
klaidliadon merged 8 commits into
masterfrom
explore-any-prefix
Apr 13, 2026
Merged

feat: generic AnyUUID/AnyInt64 with variable prefix support#7
klaidliadon merged 8 commits into
masterfrom
explore-any-prefix

Conversation

@klaidliadon
Copy link
Copy Markdown
Contributor

@klaidliadon klaidliadon commented Apr 10, 2026

Summary

Makes AnyUUID and AnyInt64 generic over P Prefixer, adding compile-time-safe variable prefix support via an optional VariablePrefixer interface.

  • Adds VariablePrefixer interface for prefix types that accept multiple string representations
  • Adds built-in AnyPrefix type (backward-compat replacement for the old string-based prefix)
  • AnyUUID[P] and AnyInt64[P] store P instead of string, enabling type-safe prefix enums
  • Parse tries ParsePrefix if available, then validates via p.Prefix() != pref - one check for both variable and fixed prefixes
  • Variant() P returns the typed prefix value for switching
  • No changes to UUID[P], Int64[P], Prefixer, or splitTypeid

Before / After

Before - manual string constants, manual validation, stringly-typed:

const PrefixApiKey        = "api_key"
const PrefixApiKeySandbox = "api_key_sandbox"

type ApiKeyID = typeid.AnyUUID

func NewApiKeyID() ApiKeyID {
    return must(typeid.NewAnyUUID("api_key"))
}

func ValidateApiKeyPrefix(id ApiKeyID) bool {
    p := id.Prefix()
    return p == PrefixApiKey || p == PrefixApiKeySandbox
}

func PrefixForMode(m Mode) string {
    if m == Sandbox { return PrefixApiKeySandbox }
    return PrefixApiKey
}

// After DB scan - stringly-typed
func (k *ApiKey) AfterScan() {
    k.ID.SetPrefix(PrefixForMode(k.Mode))
}

// Parse - no prefix validation, must validate separately
id, err := typeid.ParseAnyUUID(input)
if !ValidateApiKeyPrefix(id) {
    return errors.New("invalid prefix")
}

After - the type IS the validation:

type ApiKeyMode uint8

const (
    ApiKeyLive    ApiKeyMode = iota
    ApiKeySandbox
)

func (p ApiKeyMode) Prefix() string {
    switch p {
    case ApiKeySandbox: return "api_key_sandbox"
    default:            return "api_key"
    }
}

func (p *ApiKeyMode) ParsePrefix(s string) bool {
    switch s {
    case "api_key":         *p = ApiKeyLive;    return true
    case "api_key_sandbox": *p = ApiKeySandbox; return true
    }
    return false
}

type ApiKeyID = typeid.AnyUUID[ApiKeyMode]

// Parse - prefix validation is built in, unknown prefixes rejected
id, err := typeid.ParseAnyUUID[ApiKeyMode](input)

// Switch on variant - type-safe, not string comparison
switch id.Variant() {
case ApiKeySandbox: // ...
case ApiKeyLive:    // ...
}

// After DB scan - type-safe
func (k *ApiKey) AfterScan() {
    k.ID.SetPrefix(modeToApiKeyMode(k.Mode))
}

// Roundtrip works
id.String() // "api_key_sandbox_01abc..."

ValidateApiKeyPrefix, PrefixForMode, and the manual string constants all disappear - the prefix type handles everything.

For code that doesn't need variable prefixes, AnyPrefix provides backward compatibility:

type FlexID = typeid.AnyUUID[typeid.AnyPrefix] // accepts any prefix string

How prefix validation works

// In ParseAnyUUID[P]:
var p P
if vp, ok := any(&p).(VariablePrefixer); ok {
    vp.ParsePrefix(pref) // mutates *p on match, no-op on miss
}
if p.Prefix() != pref {
    return error // catches both unknown variable prefixes and fixed mismatches
}

One check handles both paths. If P implements VariablePrefixer, ParsePrefix sets *p to the matching variant (or leaves it at zero value on miss). Either way, p.Prefix() != pref catches invalid input.

Test plan

  • All 82 tests pass
  • go vet clean
  • Variable prefix: parse, roundtrip, reject-unknown, SetPrefix, JSON
  • AnyPrefix backward-compat: all existing AnyUUID/AnyInt64 tests updated
  • Compile-time interface checks for Prefixer + VariablePrefixer

@klaidliadon klaidliadon merged commit 94c57ef into master Apr 13, 2026
1 check passed
@klaidliadon klaidliadon deleted the explore-any-prefix branch April 13, 2026 13:45
klaidliadon added a commit that referenced this pull request Apr 14, 2026
VojtechVitek pushed a commit that referenced this pull request Apr 14, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants