Search Apps Documentation Source Content File Folder Download Copy Actions Download

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}