Search Apps Documentation Source Content File Folder Download Copy Actions Download

validator.gno

6.15 Kb · 283 lines
  1package validate
  2
  3const (
  4	HardPairMax    = 128
  5	DefaultPairMax = 32
  6)
  7
  8// Validator is a read-only validation policy set after construction.
  9//
 10// Build validators with NewValidator and replace the whole Validator when a
 11// package needs a different policy. Setter methods are intentionally omitted so
 12// each published Validator pointer keeps stable behavior for every caller that
 13// shares it. NewValidator copies caller-owned slices and stores policies by
 14// value in its own map, so later caller-side changes to the inputs cannot
 15// change validation behavior.
 16//
 17// Validator methods use pointer receivers to avoid copying the internal
 18// map/slice headers and to keep the private validity sentinel in one place.
 19// The pointer receiver is not an invitation to mutate the policy. In normal
 20// use the only state transition after construction is Validate repairing the
 21// private isValid sentinel if package-local test code or future internal code
 22// invalidates it before validation.
 23//
 24// Accessors expose value snapshots instead of mutable internals: Policies
 25// returns a fresh slice of StringPolicy values, DenyKeys returns a copied
 26// string slice, and the scalar fields are returned by value.
 27type Validator struct {
 28	defaultPolicy DefaultStringPolicy
 29	policies      map[string]StringPolicy
 30	pairMax       int
 31	allowUnknown  bool
 32	denyKeys      []string
 33	isValid       bool
 34}
 35
 36func NewValidator(
 37	defaultPolicy DefaultStringPolicy,
 38	policies []StringPolicy,
 39	denyKeys []string,
 40	pairMax int,
 41	allowUnknown bool,
 42) *Validator {
 43	valid, message := defaultPolicy.Validate()
 44	if !valid {
 45		panic(message)
 46	}
 47	if pairMax <= 0 {
 48		panic("pair max must be positive")
 49	}
 50	if pairMax > HardPairMax {
 51		panic("pair max exceeds hard pair max")
 52	}
 53	valid, message = validateDenyKeys(denyKeys)
 54	if !valid {
 55		panic(message)
 56	}
 57
 58	validator := &Validator{
 59		defaultPolicy: defaultPolicy,
 60		policies:      map[string]StringPolicy{},
 61		pairMax:       pairMax,
 62		allowUnknown:  allowUnknown,
 63		denyKeys:      copyStringSlice(denyKeys),
 64	}
 65	if len(policies) > pairMax {
 66		panic("policies count exceeds pair max")
 67	}
 68	valid, message = validator.validatePolicies(policies)
 69	if !valid {
 70		panic(message)
 71	}
 72	for _, policy := range policies {
 73		validator.policies[policy.StringKey()] = policy
 74	}
 75	validator.isValid = true
 76	return validator
 77}
 78
 79func (m *Validator) DefaultStringPolicy() DefaultStringPolicy {
 80	return m.defaultPolicy
 81}
 82
 83func (m *Validator) Policies() []StringPolicy {
 84	policies := []StringPolicy{}
 85	for _, policy := range m.policies {
 86		policies = append(policies, policy)
 87	}
 88	return policies
 89}
 90
 91func (m *Validator) PairMax() int {
 92	return m.pairMax
 93}
 94
 95func (m *Validator) AllowUnknown() bool {
 96	return m.allowUnknown
 97}
 98
 99func (m *Validator) DenyKeys() []string {
100	return copyStringSlice(m.denyKeys)
101}
102
103func (m *Validator) Clone() Validator {
104	return *NewValidator(m.DefaultStringPolicy(), m.Policies(), m.DenyKeys(), m.PairMax(), m.AllowUnknown())
105}
106
107func (m *Validator) Valid() (bool, string) {
108	valid, message := m.defaultPolicy.Validate()
109	if !valid {
110		return false, message
111	}
112	if m.pairMax <= 0 {
113		return false, "pair max must be positive"
114	}
115	if m.pairMax > HardPairMax {
116		return false, "pair max exceeds hard pair max"
117	}
118	valid, message = validateDenyKeys(m.denyKeys)
119	if !valid {
120		return false, message
121	}
122	if len(m.policies) > m.pairMax {
123		return false, "policies count exceeds pair max"
124	}
125
126	return m.validatePolicies(m.Policies())
127}
128
129func (m *Validator) getPolicy(key string) StringPolicy {
130	if policy, found := m.policies[key]; found {
131		return policy
132	}
133	return m.defaultPolicy.StringPolicy(key)
134}
135
136func (m *Validator) MustValid() {
137	valid, message := m.Valid()
138	if !valid {
139		panic(message)
140	}
141}
142
143func (m *Validator) validatePolicies(policies []StringPolicy) (bool, string) {
144	seen := map[string]bool{}
145	result := ""
146	for _, policy := range policies {
147		key := policy.StringKey()
148		if seen[key] {
149			if result != "" {
150				result += "\n"
151			}
152			result += "duplicated policy key: " + key
153		}
154		if m.isDenyKey(key) {
155			if result != "" {
156				result += "\n"
157			}
158			result += "policy key denied: " + key
159		}
160		valid, message := policy.Valid()
161		if !valid {
162			if result != "" {
163				result += "\n"
164			}
165			result += "policy " + key + ": " + message
166		}
167		seen[key] = true
168	}
169	if result != "" {
170		return false, result
171	}
172	return true, ""
173}
174
175func validateDenyKeys(keys []string) (bool, string) {
176	seen := map[string]bool{}
177	result := ""
178	for _, key := range keys {
179		if key == "" {
180			if result != "" {
181				result += "\n"
182			}
183			result += "denied key must be not empty"
184			continue
185		}
186		if seen[key] {
187			if result != "" {
188				result += "\n"
189			}
190			result += "duplicated denied key: " + key
191		}
192		seen[key] = true
193	}
194	if result != "" {
195		return false, result
196	}
197	return true, ""
198}
199
200func (m *Validator) Validate(input map[string]string) (bool, string) {
201	if !m.isValid {
202		m.MustValid()
203		m.isValid = true
204	}
205
206	if len(input) > m.pairMax {
207		return false, "map has too many entries"
208	}
209
210	seen := map[string]bool{}
211	result := ""
212
213	for key, policy := range m.policies {
214		val, found := input[key]
215		valid, message := policy.Validate(val, found)
216		if !valid {
217			if result != "" {
218				result += "\n"
219			}
220			result += "policy " + key + ": " + message
221		}
222		seen[key] = true
223	}
224
225	for key, val := range input {
226		if m.isDenyKey(key) {
227			if result != "" {
228				result += "\n"
229			}
230			result += "denied key: " + key
231			continue
232		}
233		if !m.allowUnknown && !seen[key] {
234			if result != "" {
235				result += "\n"
236			}
237			result += "unknown key: " + key
238			continue
239		}
240		if seen[key] {
241			continue
242		}
243
244		policy := m.getPolicy(key)
245		valid, message := policy.Validate(val, true)
246		if !valid {
247			if result != "" {
248				result += "\n"
249			}
250			result += "policy " + key + ": " + message
251		}
252	}
253
254	if result != "" {
255		return false, result
256	}
257	return true, ""
258}
259
260func (m *Validator) MustValidate(input map[string]string) {
261	valid, message := m.Validate(input)
262	if !valid {
263		panic(message)
264	}
265}
266
267func (m *Validator) isDenyKey(key string) bool {
268	for _, k := range m.denyKeys {
269		if k == key {
270			return true
271		}
272	}
273	return false
274}
275
276func copyStringSlice(source []string) []string {
277	if source == nil {
278		return nil
279	}
280	result := make([]string, len(source))
281	copy(result, source)
282	return result
283}