canonical.gno
2.07 Kb · 63 lines
1package users
2
3import (
4 "strings"
5
6 "gno.land/p/nt/bptree/v0"
7)
8
9// canonicalStore maps canonicalized full names to the original name
10// that was registered. Keyed by the result of Canonicalize.
11//
12// Multiple controllers (namereg/v1, future registries, governance,
13// genesis bootstrapping) all share this single store via RegisterUser
14// and the bypass variant RegisterUserIgnoreCanonical. Cross-controller
15// canonical-collision detection is uniform and atomic with the
16// nameStore write.
17var canonicalStore = bptree.NewBPTree32()
18
19// Canonicalize returns the canonical form of a name. The substitutions
20// collapse single-character visual confusables that arise across the
21// allowed [a-z0-9] character set, and strip the three separators that
22// can sneak between identical alphanumeric runs.
23//
24// - {l, i, 1} → i
25// - {0, o} → o
26// - {-, ., _} → stripped
27// - all other characters unchanged
28//
29// CONTRACT: stable. Future controllers that want to share this
30// canonical namespace MUST use this exact function — do not roll your
31// own. Adding new substitutions later is a breaking change because it
32// would silently re-key existing entries in canonicalStore.
33//
34// Input contract: ASCII-only. r/sys/users.validateName already rejects
35// non-ASCII at the registration boundary, so by the time a name reaches
36// Canonicalize through the standard write path it is guaranteed to be
37// ASCII. Direct callers from other realms must honor this contract;
38// non-ASCII bytes are passed through as-is and will produce undefined
39// collision behavior.
40//
41// Multi-char confusables (m↔rn, nn↔m, cl↔d) are NOT canonicalized.
42// They require fixed-point substring substitution rounds, which is out
43// of scope for the unified store.
44//
45// Pure: no state access. Safe to call from anywhere.
46func Canonicalize(name string) string {
47 var b strings.Builder
48 b.Grow(len(name))
49 for i := 0; i < len(name); i++ {
50 c := name[i]
51 switch c {
52 case 'l', '1':
53 b.WriteByte('i')
54 case '0':
55 b.WriteByte('o')
56 case '-', '.', '_':
57 // strip
58 default:
59 b.WriteByte(c)
60 }
61 }
62 return b.String()
63}