validation.gno
5.02 Kb · 205 lines
1package personal_world
2
3import (
4 "strconv"
5 "strings"
6 "unicode/utf8"
7
8 "gno.land/p/akkadia/v0/validate"
9)
10
11// Validation helpers in this file are for value-level checks at public API boundaries.
12//
13// Add helpers here when they validate a parameter or payload value without
14// depending on mutable contract state or call flow. Good examples are field
15// format, length, numeric range, string-map shape, and page/count limits.
16//
17// Do not add helpers here when the assertion depends on store contents,
18// ownership, permissions, payment, freeze/migration state, uniqueness, or the
19// order of a specific operation. Keep those assertions close to the domain flow
20// that owns the state transition.
21//
22// As a rule of thumb: validation.gno owns "is this value shaped correctly?";
23// domain files own "is this action allowed right now?".
24
25// World field assertions.
26// Used by CreateWorld and UpdateWorld before constructing or mutating world records.
27func assertWorldName(name string) {
28 if len(name) == 0 {
29 panic("name cannot be empty")
30 }
31 if utf8.RuneCountInString(name) > 100 {
32 panic("name too long (max 100)")
33 }
34
35 trimmed := strings.TrimSpace(name)
36 if len(trimmed) == 0 {
37 panic("name cannot be only whitespace")
38 }
39 if name != trimmed {
40 panic("name cannot have leading or trailing whitespace")
41 }
42
43 for _, ch := range name {
44 if ch < 0x20 {
45 panic("name cannot contain control characters")
46 }
47 }
48}
49
50func assertWorldSlug(slug string) {
51 if len(slug) == 0 {
52 panic("slug cannot be empty")
53 }
54 if len(slug) > 50 {
55 panic("slug too long (max 50)")
56 }
57
58 for _, ch := range slug {
59 if !((ch >= 'a' && ch <= 'z') ||
60 (ch >= '0' && ch <= '9') ||
61 ch == '-' || ch == '_') {
62 panic("slug contains invalid characters (allowed: a-z, 0-9, -, _)")
63 }
64 }
65
66 if slug[0] == '-' || slug[len(slug)-1] == '-' {
67 panic("slug cannot start or end with hyphen")
68 }
69}
70
71func mustParseWorldSeed(value string) int {
72 seed, err := strconv.Atoi(value)
73 if err != nil {
74 panic("seed must be int")
75 }
76 if seed <= 0 {
77 panic("seed must be positive")
78 }
79 return seed
80}
81
82// World metadata assertions.
83// Metadata is admin-managed opaque text. The realm only bounds resource usage;
84// clients and off-chain services own decoding and semantic validation.
85func assertWorldMetadata(metadata string) {
86 validate.AssertStringTextLen(metadata, true, "world metadata")
87}
88
89// Biome input assertions.
90// Used by SetBiomeInfo, SetDefaultBiome, GetBiomeInfo, and CreateWorld.
91func assertBiomeName(biomeName string) {
92 if biomeName == "" {
93 panic("biome name cannot be empty")
94 }
95}
96
97func mustParseBiomeCost(value string) int64 {
98 cost, err := strconv.ParseInt(value, 10, 64)
99 if err != nil {
100 panic("cost must be int")
101 }
102 if cost < 0 {
103 panic("cost must be non-negative")
104 }
105 return cost
106}
107
108func mustParseBiomePriceMultiplierBPS(value string) int64 {
109 priceMultiplierBPS, err := strconv.ParseInt(value, 10, 64)
110 if err != nil {
111 panic("price multiplier BPS must be int")
112 }
113 if priceMultiplierBPS <= 0 {
114 panic("price multiplier BPS must be positive")
115 }
116 if priceMultiplierBPS < minPriceMultiplierBPS {
117 panic("price multiplier BPS below minimum")
118 }
119 return priceMultiplierBPS
120}
121
122// Size input assertions.
123// Used by SetSizeInfo before constructing size info records.
124func mustParseSizeID(value string) int {
125 sizeID, err := strconv.Atoi(value)
126 if err != nil {
127 panic("sizeID must be int")
128 }
129 if sizeID < 0 {
130 panic("sizeID must be non-negative")
131 }
132 return sizeID
133}
134
135func mustParseWorldSize(value string) int {
136 size, err := strconv.Atoi(value)
137 if err != nil {
138 panic("size must be int")
139 }
140 if size < 0 {
141 panic("size must be non-negative")
142 }
143 return size
144}
145
146func mustParseSizeCost(value string) int64 {
147 cost, err := strconv.ParseInt(value, 10, 64)
148 if err != nil {
149 panic("cost must be int")
150 }
151 if cost < 0 {
152 panic("cost must be non-negative")
153 }
154 return cost
155}
156
157// String-map assertions.
158// Used by metadata/property config setters and payload parsers.
159func assertCSVMapParams(keys, values string, panicMessagePrefix string) {
160 if keys == "" {
161 panic(panicMessagePrefix + " keys must be not empty")
162 }
163}
164
165func assertStringMapAllowedKeys(m map[string]string, allowedKeys []string, panicMessagePrefix string) {
166 if len(allowedKeys) == 0 {
167 panic(panicMessagePrefix + " allowed keys not configured")
168 }
169 for key := range m {
170 assertAllowedKey(key, allowedKeys)
171 }
172}
173
174func assertAllowedKey(key string, allowedKeys []string) {
175 for _, allowedKey := range allowedKeys {
176 if key == allowedKey {
177 return
178 }
179 }
180 panic("invalid key: " + key)
181}
182
183// Query limit assertions.
184// Used by public list and batch query entry points.
185func assertListLimit(field string, count int) {
186 if count > listLimit {
187 panic(field + " exceeds listLimit (max: " + strconv.Itoa(listLimit) + ")")
188 }
189}
190
191func assertListPageCount(page int, count int) {
192 if page < 1 {
193 panic("page must be at least 1")
194 }
195 if count < 1 {
196 panic("count must be at least 1")
197 }
198 assertListLimit("count", count)
199}
200
201func assertBatchLimit(field string, count int) {
202 if count > batchLimit {
203 panic(field + " exceeds batchLimit (max: " + strconv.Itoa(batchLimit) + ")")
204 }
205}