Search Apps Documentation Source Content File Folder Download Copy Actions Download

string_policy.gno

3.11 Kb · 155 lines
  1package validate
  2
  3import (
  4	"strings"
  5	"unicode/utf8"
  6)
  7
  8type StringPolicyKey string
  9
 10func (key StringPolicyKey) Valid() (bool, string) {
 11	value := string(key)
 12	if strings.TrimSpace(value) == "" {
 13		return false, "key is required"
 14	}
 15	if utf8.RuneCountInString(value) > HardShortMax {
 16		return false, "key exceeds max runes"
 17	}
 18	if hasASCIIControlChar(value) {
 19		return false, "key must not contain control characters"
 20	}
 21	return true, ""
 22}
 23
 24func (key StringPolicyKey) MustValid() {
 25	valid, message := key.Valid()
 26	if !valid {
 27		panic(message)
 28	}
 29}
 30
 31func hasASCIIControlChar(value string) bool {
 32	for i := 0; i < len(value); i++ {
 33		c := value[i]
 34		if c < 0x20 || c == 0x7f {
 35			return true
 36		}
 37	}
 38	return false
 39}
 40
 41// StringPolicy describes one key and the string value accepted for that key.
 42//
 43// Required means the key must appear in the input map. AllowEmpty allows the
 44// exact empty string. Whitespace-only strings are rejected.
 45type StringPolicy struct {
 46	key        StringPolicyKey
 47	kind       StringKind
 48	lenKind    StringLenKind
 49	maxRunes   int
 50	required   bool
 51	allowEmpty bool
 52}
 53
 54// NewStringPolicy returns a validated string policy.
 55func NewStringPolicy(
 56	key string,
 57	kind StringKind,
 58	lenKind StringLenKind,
 59	maxRunes int,
 60	required bool,
 61	allowEmpty bool,
 62) StringPolicy {
 63	policy := StringPolicy{
 64		key:        StringPolicyKey(key),
 65		kind:       kind,
 66		lenKind:    lenKind,
 67		maxRunes:   maxRunes,
 68		required:   required,
 69		allowEmpty: allowEmpty,
 70	}
 71	policy.MustValid()
 72	return policy
 73}
 74
 75func (policy StringPolicy) StringKey() string {
 76	return string(policy.key)
 77}
 78
 79func (policy StringPolicy) MaxRunes() int {
 80	if policy.maxRunes > 0 && policy.maxRunes <= policy.lenKind.Max() {
 81		return policy.maxRunes
 82	}
 83	return policy.lenKind.Max()
 84}
 85
 86func (policy StringPolicy) Valid() (bool, string) {
 87	valid, message := policy.key.Valid()
 88	if !valid {
 89		return false, message
 90	}
 91
 92	valid, message = policy.kind.Valid()
 93	if !valid {
 94		return false, message
 95	}
 96
 97	valid, message = policy.lenKind.Valid()
 98	if !valid {
 99		return false, message
100	}
101
102	if policy.maxRunes < 0 {
103		return false, "max runes must not be negative"
104	}
105
106	if policy.maxRunes > policy.lenKind.Max() {
107		return false, "max runes exceeds hard cap"
108	}
109	return true, ""
110}
111
112func (policy StringPolicy) MustValid() {
113	valid, message := policy.Valid()
114	if !valid {
115		panic(message)
116	}
117}
118
119func (policy StringPolicy) Validate(value string, found bool) (bool, string) {
120	if policy.required && !found {
121		return false, "value must be required"
122	}
123	if !found {
124		return true, ""
125	}
126
127	valid, message := policy.key.Valid()
128	if !valid {
129		return false, message
130	}
131
132	if !policy.allowEmpty && value == "" {
133		return false, "value must not be empty"
134	}
135	if strings.TrimSpace(value) == "" && value != "" {
136		return false, "value must not be blank"
137	}
138
139	valid, message = policy.kind.Validate(value)
140	if !valid {
141		return false, message
142	}
143
144	if utf8.RuneCountInString(value) > policy.MaxRunes() {
145		return false, "value must be less than or equal max runes"
146	}
147	return true, ""
148}
149
150func (policy StringPolicy) MustValidate(value string, found bool) {
151	valid, message := policy.Validate(value, found)
152	if !valid {
153		panic(message)
154	}
155}