/validate/string.go

https://github.com/blend/go-sdk · Go · 289 lines · 229 code · 22 blank · 38 comment · 67 complexity · b1f64f16b2ee491f51deba6dca7029bb MD5 · raw file

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