engine_string.go 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300
  1. package validator
  2. import (
  3. "fmt"
  4. "regexp"
  5. "strings"
  6. "unicode/utf8"
  7. validate "git.ikuban.com/server/kubanapis/kuban/api/validate"
  8. )
  9. // validateString 验证字符串
  10. func (e *Engine) validateString(
  11. vctx *ValidationContext,
  12. fieldRules *validate.FieldRules,
  13. rules *validate.StringRules,
  14. value string,
  15. fieldPath string,
  16. errors *ValidationErrors,
  17. ) error {
  18. // const 验证
  19. if rules.Const != nil && value != *rules.Const {
  20. defaultMsg := fmt.Sprintf("字段 %s 必须等于 '%s'", fieldPath, *rules.Const)
  21. errors.Add(NewValidationError(
  22. fieldPath,
  23. ErrCodeInvalidValue,
  24. e.getErrorMessage(fieldRules, vctx, "const", defaultMsg),
  25. ))
  26. if vctx.ShouldFailFast() {
  27. return errors
  28. }
  29. }
  30. // 长度验证(字符数)
  31. runeCount := utf8.RuneCountInString(value)
  32. if rules.Len != nil && uint64(runeCount) != *rules.Len {
  33. defaultMsg := fmt.Sprintf("字段 %s 长度必须为 %d 个字符", fieldPath, *rules.Len)
  34. errors.Add(NewValidationError(
  35. fieldPath,
  36. ErrCodeInvalidLength,
  37. e.getErrorMessage(fieldRules, vctx, "len", defaultMsg),
  38. ))
  39. }
  40. if rules.MinLen != nil && uint64(runeCount) < *rules.MinLen {
  41. defaultMsg := fmt.Sprintf("字段 %s 长度至少为 %d 个字符", fieldPath, *rules.MinLen)
  42. errors.Add(NewValidationError(
  43. fieldPath,
  44. ErrCodeInvalidLength,
  45. e.getErrorMessage(fieldRules, vctx, "min_len", defaultMsg),
  46. ))
  47. }
  48. if rules.MaxLen != nil && uint64(runeCount) > *rules.MaxLen {
  49. defaultMsg := fmt.Sprintf("字段 %s 长度最多为 %d 个字符", fieldPath, *rules.MaxLen)
  50. errors.Add(NewValidationError(
  51. fieldPath,
  52. ErrCodeInvalidLength,
  53. e.getErrorMessage(fieldRules, vctx, "max_len", defaultMsg),
  54. ))
  55. }
  56. // 字节长度验证
  57. byteLen := len(value)
  58. if rules.LenBytes != nil && uint64(byteLen) != *rules.LenBytes {
  59. errors.Add(NewValidationError(
  60. fieldPath,
  61. ErrCodeInvalidLength,
  62. fmt.Sprintf("字段 %s 字节长度必须为 %d", fieldPath, *rules.LenBytes),
  63. ))
  64. }
  65. if rules.MinBytes != nil && uint64(byteLen) < *rules.MinBytes {
  66. errors.Add(NewValidationError(
  67. fieldPath,
  68. ErrCodeInvalidLength,
  69. fmt.Sprintf("字段 %s 字节长度至少为 %d", fieldPath, *rules.MinBytes),
  70. ))
  71. }
  72. if rules.MaxBytes != nil && uint64(byteLen) > *rules.MaxBytes {
  73. errors.Add(NewValidationError(
  74. fieldPath,
  75. ErrCodeInvalidLength,
  76. fmt.Sprintf("字段 %s 字节长度最多为 %d", fieldPath, *rules.MaxBytes),
  77. ))
  78. }
  79. // 模式验证(正则表达式)
  80. if rules.Pattern != nil {
  81. matched, err := regexp.MatchString(*rules.Pattern, value)
  82. if err != nil {
  83. errors.Add(NewValidationError(
  84. fieldPath,
  85. ErrCodeInvalidPattern,
  86. fmt.Sprintf("正则表达式错误: %v", err),
  87. ))
  88. } else if !matched {
  89. errors.Add(NewValidationError(
  90. fieldPath,
  91. ErrCodeInvalidPattern,
  92. fmt.Sprintf("字段 %s 格式不正确", fieldPath),
  93. ))
  94. }
  95. }
  96. // 前缀/后缀验证
  97. if rules.Prefix != nil && !strings.HasPrefix(value, *rules.Prefix) {
  98. errors.Add(NewValidationError(
  99. fieldPath,
  100. ErrCodeInvalidValue,
  101. fmt.Sprintf("字段 %s 必须以 '%s' 开头", fieldPath, *rules.Prefix),
  102. ))
  103. }
  104. if rules.Suffix != nil && !strings.HasSuffix(value, *rules.Suffix) {
  105. errors.Add(NewValidationError(
  106. fieldPath,
  107. ErrCodeInvalidValue,
  108. fmt.Sprintf("字段 %s 必须以 '%s' 结尾", fieldPath, *rules.Suffix),
  109. ))
  110. }
  111. // 包含/不包含验证
  112. if rules.Contains != nil && !strings.Contains(value, *rules.Contains) {
  113. errors.Add(NewValidationError(
  114. fieldPath,
  115. ErrCodeInvalidValue,
  116. fmt.Sprintf("字段 %s 必须包含 '%s'", fieldPath, *rules.Contains),
  117. ))
  118. }
  119. if rules.NotContains != nil && strings.Contains(value, *rules.NotContains) {
  120. errors.Add(NewValidationError(
  121. fieldPath,
  122. ErrCodeInvalidValue,
  123. fmt.Sprintf("字段 %s 不能包含 '%s'", fieldPath, *rules.NotContains),
  124. ))
  125. }
  126. // in 验证
  127. if len(rules.In) > 0 {
  128. found := false
  129. for _, allowed := range rules.In {
  130. if value == allowed {
  131. found = true
  132. break
  133. }
  134. }
  135. if !found {
  136. errors.Add(NewValidationError(
  137. fieldPath,
  138. ErrCodeInvalidValue,
  139. fmt.Sprintf("字段 %s 的值不在允许的列表中", fieldPath),
  140. ))
  141. }
  142. }
  143. // not_in 验证
  144. if len(rules.NotIn) > 0 {
  145. for _, disallowed := range rules.NotIn {
  146. if value == disallowed {
  147. errors.Add(NewValidationError(
  148. fieldPath,
  149. ErrCodeInvalidValue,
  150. fmt.Sprintf("字段 %s 的值在禁止列表中", fieldPath),
  151. ))
  152. break
  153. }
  154. }
  155. }
  156. // Well-known 格式验证
  157. switch rules.WellKnown.(type) {
  158. case *validate.StringRules_Email:
  159. if !isValidEmail(value) {
  160. errors.Add(NewValidationError(
  161. fieldPath,
  162. ErrCodeInvalidFormat,
  163. fmt.Sprintf("字段 %s 不是有效的邮箱地址", fieldPath),
  164. ))
  165. }
  166. case *validate.StringRules_Uuid:
  167. if !isValidUUID(value) {
  168. errors.Add(NewValidationError(
  169. fieldPath,
  170. ErrCodeInvalidFormat,
  171. fmt.Sprintf("字段 %s 不是有效的 UUID", fieldPath),
  172. ))
  173. }
  174. case *validate.StringRules_Uri:
  175. if !isValidURI(value) {
  176. errors.Add(NewValidationError(
  177. fieldPath,
  178. ErrCodeInvalidFormat,
  179. fmt.Sprintf("字段 %s 不是有效的 URI", fieldPath),
  180. ))
  181. }
  182. case *validate.StringRules_ChineseMobile:
  183. if !isValidChineseMobile(value) {
  184. errors.Add(NewValidationError(
  185. fieldPath,
  186. ErrCodeInvalidFormat,
  187. fmt.Sprintf("字段 %s 不是有效的中国手机号", fieldPath),
  188. ))
  189. }
  190. case *validate.StringRules_ChineseIdCard:
  191. if !isValidChineseIDCard(value) {
  192. errors.Add(NewValidationError(
  193. fieldPath,
  194. ErrCodeInvalidFormat,
  195. fmt.Sprintf("字段 %s 不是有效的中国身份证号", fieldPath),
  196. ))
  197. }
  198. case *validate.StringRules_ChineseName:
  199. if !isValidChineseName(value) {
  200. errors.Add(NewValidationError(
  201. fieldPath,
  202. ErrCodeInvalidFormat,
  203. fmt.Sprintf("字段 %s 不是有效的中文姓名", fieldPath),
  204. ))
  205. }
  206. case *validate.StringRules_ChinesePostcode:
  207. if !isValidChinesePostcode(value) {
  208. errors.Add(NewValidationError(
  209. fieldPath,
  210. ErrCodeInvalidFormat,
  211. fmt.Sprintf("字段 %s 不是有效的中国邮政编码", fieldPath),
  212. ))
  213. }
  214. }
  215. // 字符类型验证
  216. if rules.Numeric != nil && *rules.Numeric {
  217. if !regexp.MustCompile(`^\d+$`).MatchString(value) {
  218. errors.Add(NewValidationError(
  219. fieldPath,
  220. ErrCodeInvalidFormat,
  221. fmt.Sprintf("字段 %s 必须只包含数字", fieldPath),
  222. ))
  223. }
  224. }
  225. if rules.Alpha != nil && *rules.Alpha {
  226. if !regexp.MustCompile(`^[a-zA-Z]+$`).MatchString(value) {
  227. errors.Add(NewValidationError(
  228. fieldPath,
  229. ErrCodeInvalidFormat,
  230. fmt.Sprintf("字段 %s 必须只包含字母", fieldPath),
  231. ))
  232. }
  233. }
  234. if rules.Alphanumeric != nil && *rules.Alphanumeric {
  235. if !regexp.MustCompile(`^[a-zA-Z0-9]+$`).MatchString(value) {
  236. errors.Add(NewValidationError(
  237. fieldPath,
  238. ErrCodeInvalidFormat,
  239. fmt.Sprintf("字段 %s 必须只包含字母和数字", fieldPath),
  240. ))
  241. }
  242. }
  243. // 大小写验证
  244. if rules.Lowercase != nil && *rules.Lowercase {
  245. if value != strings.ToLower(value) {
  246. errors.Add(NewValidationError(
  247. fieldPath,
  248. ErrCodeInvalidFormat,
  249. fmt.Sprintf("字段 %s 必须是小写", fieldPath),
  250. ))
  251. }
  252. }
  253. if rules.Uppercase != nil && *rules.Uppercase {
  254. if value != strings.ToUpper(value) {
  255. errors.Add(NewValidationError(
  256. fieldPath,
  257. ErrCodeInvalidFormat,
  258. fmt.Sprintf("字段 %s 必须是大写", fieldPath),
  259. ))
  260. }
  261. }
  262. return nil
  263. }
  264. // Helper functions for format validation
  265. func isValidEmail(email string) bool {
  266. // 简化的邮箱验证正则
  267. pattern := `^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$`
  268. matched, _ := regexp.MatchString(pattern, email)
  269. return matched
  270. }
  271. func isValidUUID(uuid string) bool {
  272. pattern := `^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$`
  273. matched, _ := regexp.MatchString(pattern, uuid)
  274. return matched
  275. }
  276. func isValidURI(uri string) bool {
  277. // 简化的 URI 验证
  278. return strings.Contains(uri, "://") || strings.HasPrefix(uri, "/")
  279. }