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}