validation.gno
2.59 Kb · 99 lines
1package user
2
3import (
4 "regexp"
5 "strconv"
6 "strings"
7)
8
9// Validation helpers in this file are for value-level checks at public API boundaries.
10//
11// Add helpers here when they validate a parameter or payload value without
12// depending on mutable contract state or call flow. Good examples are address
13// format, nickname format, string-map shape, allowed key shape, and page/count limits.
14//
15// Do not add helpers here when the assertion depends on store contents,
16// ownership, permissions, freeze/migration state, uniqueness, or the order of a
17// specific operation. Keep those assertions close to the domain flow that owns
18// the state transition.
19//
20// As a rule of thumb: validation.gno owns "is this value shaped correctly?";
21// domain files own "is this action allowed right now?".
22
23var reURLLikeNickname = regexp.MustCompile(`(?i)([a-z][a-z0-9+.-]*://|www\.|([a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z]{2,})`)
24
25// Address assertions.
26// Used by user public entry points and batch address query payloads.
27func assertIsValidAddress(addr address) {
28 if !addr.IsValid() {
29 panic("invalid address: " + addr.String())
30 }
31}
32
33func assertValidAddresses(addrs []address) {
34 for _, addr := range addrs {
35 assertIsValidAddress(addr)
36 }
37}
38
39// Nickname assertions.
40// Used by create/update/query entry points before touching nickname indexes.
41func assertValidNickname(nickname string) {
42 if len(nickname) == 0 {
43 panic("nickname cannot be empty")
44 }
45
46 if len(nickname) > 20 {
47 panic("nickname too long (max 20)")
48 }
49
50 trimmed := strings.TrimSpace(nickname)
51 if len(trimmed) == 0 {
52 panic("nickname cannot be only whitespace")
53 }
54
55 if nickname != trimmed {
56 panic("nickname cannot have leading or trailing whitespace")
57 }
58
59 if isURLLikeNickname(nickname) {
60 panic("nickname must not be a URL")
61 }
62
63 if hasControlChar(nickname) {
64 panic("nickname must not contain control characters")
65 }
66}
67
68func isURLLikeNickname(nickname string) bool {
69 return reURLLikeNickname.MatchString(strings.TrimSpace(nickname))
70}
71
72// hasControlChar rejects ASCII control bytes, including DEL (0x7f).
73func hasControlChar(s string) bool {
74 for i := 0; i < len(s); i++ {
75 c := s[i]
76 if c < 0x20 || c == 0x7f {
77 return true
78 }
79 }
80 return false
81}
82
83// Query limit assertions.
84// Used by public list and batch query entry points.
85func assertListLimit(field string, count int) {
86 if count > listLimit {
87 panic(field + " exceeds listLimit (max: " + strconv.Itoa(listLimit) + ")")
88 }
89}
90
91func assertListPageCount(page int, count int) {
92 if page < 1 {
93 panic("page must be at least 1")
94 }
95 if count < 1 {
96 panic("count must be at least 1")
97 }
98 assertListLimit("count", count)
99}