github.com/blend/go-sdk@v1.20240719.1/validate/string.go (about) 1 /* 2 3 Copyright (c) 2024 - Present. Blend Labs, Inc. All rights reserved 4 Use of this source code is governed by a MIT license that can be found in the LICENSE file. 5 6 */ 7 8 package validate 9 10 import ( 11 "net" 12 "net/mail" 13 "net/url" 14 "regexp" 15 "strings" 16 "unicode" 17 18 "github.com/blend/go-sdk/ex" 19 "github.com/blend/go-sdk/uuid" 20 ) 21 22 // String errors 23 const ( 24 ErrStringRequired ex.Class = "string should be set" 25 ErrStringForbidden ex.Class = "string should not be set" 26 ErrStringLength ex.Class = "string should be a given length" 27 ErrStringLengthMin ex.Class = "string should be a minimum length" 28 ErrStringLengthMax ex.Class = "string should be a maximum length" 29 ErrStringMatches ex.Class = "string should match regular expression" 30 ErrStringIsUpper ex.Class = "string should be uppercase" 31 ErrStringIsLower ex.Class = "string should be lowercase" 32 ErrStringIsTitle ex.Class = "string should be titlecase" 33 ErrStringIsUUID ex.Class = "string should be a uuid" 34 ErrStringIsEmail ex.Class = "string should be a valid email address" 35 ErrStringIsURI ex.Class = "string should be a valid uri" 36 ErrStringIsIP ex.Class = "string should be a valid ip address" 37 ErrStringIsSlug ex.Class = "string should be a valid slug (i.e. matching [0-9,a-z,A-Z,_,-])" 38 ErrStringIsOneOf ex.Class = "string should be one of a set of values" 39 ) 40 41 // String contains helpers for string validation. 42 func String(value *string) StringValidators { 43 return StringValidators{value, false} 44 } 45 46 // SensitiveString contains helpers for sensitive string validation, which 47 // avoids including sensitive data in error messages 48 func SensitiveString(value *string) StringValidators { 49 return StringValidators{value, true} 50 } 51 52 // StringValidators returns string validators. 53 type StringValidators struct { 54 Value *string 55 Sensitive bool 56 } 57 58 func (s StringValidators) maybeCensoredValue() interface{} { 59 if s.Value == nil { 60 return nil 61 } 62 63 if s.Sensitive { 64 return "<sensitive>" 65 } 66 67 return *s.Value 68 } 69 70 // Required returns a validator that a string is set and not zero length. 71 func (s StringValidators) Required() Validator { 72 return func() error { 73 if s.Value == nil { 74 return Error(ErrStringRequired, nil) 75 } 76 if len(*s.Value) == 0 { 77 return Error(ErrStringRequired, nil) 78 } 79 return nil 80 } 81 } 82 83 // Forbidden returns a validator that a string is not set. 84 func (s StringValidators) Forbidden() Validator { 85 return func() error { 86 if err := s.Required()(); err == nil { 87 return Error(ErrStringForbidden, nil) 88 } 89 return nil 90 } 91 } 92 93 // MinLen returns a validator that a string is a minimum length. 94 // If the string is unset (nil) it will fail. 95 func (s StringValidators) MinLen(length int) Validator { 96 return func() error { 97 if s.Value == nil { 98 return Errorf(ErrStringLengthMin, nil, "length: %d", length) 99 } 100 if len(*s.Value) < length { //if it's unset, it should fail the minimum check. 101 return Errorf(ErrStringLengthMin, s.maybeCensoredValue(), "length: %d", length) 102 } 103 return nil 104 } 105 } 106 107 // MaxLen returns a validator that a string is a minimum length. 108 // It will pass if the string is unset (nil). 109 func (s StringValidators) MaxLen(length int) Validator { 110 return func() error { 111 if s.Value == nil { 112 return nil 113 } 114 if len(*s.Value) > length { 115 return Errorf(ErrStringLengthMax, s.maybeCensoredValue(), "length: %d", length) 116 } 117 return nil 118 } 119 } 120 121 // Length returns a validator that a string is a minimum length. 122 // It will error if the string is unset (nil). 123 func (s StringValidators) Length(length int) Validator { 124 return func() error { 125 if s.Value == nil { 126 return Errorf(ErrStringLength, nil, "length: %d", length) 127 } 128 if len(*s.Value) != length { 129 return Errorf(ErrStringLength, s.maybeCensoredValue(), "length: %d", length) 130 } 131 return nil 132 } 133 } 134 135 // BetweenLen returns a validator that a string is a between a minimum and maximum length. 136 // It will error if the string is unset (nil). 137 func (s StringValidators) BetweenLen(min, max int) Validator { 138 return func() error { 139 if s.Value == nil { 140 return Errorf(ErrStringLengthMin, nil, "length: %d", min) 141 } 142 if len(*s.Value) < min { 143 return Errorf(ErrStringLengthMin, s.maybeCensoredValue(), "length: %d", min) 144 } 145 if len(*s.Value) > max { 146 return Errorf(ErrStringLengthMax, s.maybeCensoredValue(), "length: %d", max) 147 } 148 return nil 149 } 150 } 151 152 // Matches returns a validator that a string matches a given regex. 153 // It will error if the string is unset (nil). 154 func (s StringValidators) Matches(expression string) Validator { 155 exp, err := regexp.Compile(expression) 156 return func() error { 157 if err != nil { 158 return ex.New(err) 159 } 160 if s.Value == nil { 161 return Errorf(ErrStringMatches, nil, "expression: %s", expression) 162 } 163 if !exp.MatchString(string(*s.Value)) { 164 return Errorf(ErrStringMatches, s.maybeCensoredValue(), "expression: %s", expression) 165 } 166 return nil 167 } 168 } 169 170 // IsUpper returns a validator if a string is all uppercase. 171 // It will error if the string is unset (nil). 172 func (s StringValidators) IsUpper() Validator { 173 return func() error { 174 if s.Value == nil { 175 return Error(ErrStringIsUpper, nil) 176 } 177 for _, r := range *s.Value { 178 if !unicode.IsUpper(r) { 179 return Error(ErrStringIsUpper, s.maybeCensoredValue()) 180 } 181 } 182 return nil 183 } 184 } 185 186 // IsLower returns a validator if a string is all lowercase. 187 // It will error if the string is unset (nil). 188 func (s StringValidators) IsLower() Validator { 189 return func() error { 190 if s.Value == nil { 191 return Error(ErrStringIsLower, nil) 192 } 193 for _, r := range *s.Value { 194 if !unicode.IsLower(r) { 195 return Error(ErrStringIsLower, s.maybeCensoredValue()) 196 } 197 } 198 return nil 199 } 200 } 201 202 // IsTitle returns a validator if a string is titlecase. 203 // Titlecase is defined as the output of strings.ToTitle(s). 204 // It will error if the string is unset (nil). 205 func (s StringValidators) IsTitle() Validator { 206 return func() error { 207 if s.Value == nil { 208 return Error(ErrStringIsTitle, nil) 209 } 210 if strings.ToTitle(string(*s.Value)) == string(*s.Value) { 211 return nil 212 } 213 return Error(ErrStringIsTitle, s.maybeCensoredValue()) 214 } 215 } 216 217 // IsUUID returns if a string is a valid uuid. 218 // It will error if the string is unset (nil). 219 func (s StringValidators) IsUUID() Validator { 220 return func() error { 221 if s.Value == nil { 222 return Error(ErrStringIsUUID, nil) 223 } 224 if _, err := uuid.Parse(string(*s.Value)); err != nil { 225 return Error(ErrStringIsUUID, s.maybeCensoredValue()) 226 } 227 return nil 228 } 229 } 230 231 // IsEmail returns if a string is a valid email address. 232 func (s StringValidators) IsEmail() Validator { 233 return func() error { 234 if s.Value == nil { 235 return Error(ErrStringIsEmail, nil) 236 } 237 if _, err := mail.ParseAddress(string(*s.Value)); err != nil { 238 return Error(ErrStringIsEmail, s.maybeCensoredValue()) 239 } 240 return nil 241 } 242 } 243 244 // IsURI returns if a string is a valid uri. 245 // It will error if the string is unset (nil). 246 func (s StringValidators) IsURI() Validator { 247 return func() error { 248 if s.Value == nil { 249 return Error(ErrStringIsURI, nil) 250 } 251 if _, err := url.ParseRequestURI(string(*s.Value)); err != nil { 252 return Error(ErrStringIsURI, s.maybeCensoredValue()) 253 } 254 return nil 255 } 256 } 257 258 // IsIP returns if a string is a valid ip address. 259 // It will error if the string is unset (nil). 260 func (s StringValidators) IsIP() Validator { 261 return func() error { 262 if s.Value == nil { 263 return Error(ErrStringIsIP, nil) 264 } 265 if addr := net.ParseIP(string(*s.Value)); addr == nil { 266 return Error(ErrStringIsIP, s.maybeCensoredValue()) 267 } 268 return nil 269 } 270 } 271 272 // IsSlug returns if a string is a valid slug as defined by the match rule [0-9,a-z,A-Z,_,-]. 273 // It will error if the string is unset (nil). 274 func (s StringValidators) IsSlug() Validator { 275 return func() error { 276 if s.Value == nil { 277 return Error(ErrStringIsSlug, nil) 278 } 279 for _, c := range *s.Value { 280 if unicode.IsLetter(c) { 281 continue 282 } 283 if unicode.IsDigit(c) { 284 continue 285 } 286 if c == '-' || c == '_' { 287 continue 288 } 289 return Error(ErrStringIsSlug, s.maybeCensoredValue()) 290 } 291 return nil 292 } 293 } 294 295 // IsOneOf validates a string is one of a known set of values. 296 func (s StringValidators) IsOneOf(values ...string) Validator { 297 return func() error { 298 if s.Value == nil { 299 return Error(ErrStringIsOneOf, nil, strings.Join(values, ", ")) 300 } 301 for _, value := range values { 302 if *s.Value == value { 303 return nil 304 } 305 } 306 return Error(ErrStringIsOneOf, s.maybeCensoredValue(), strings.Join(values, ", ")) 307 } 308 }