Compare commits
2 Commits
45cad505d6
...
e5779183ff
| Author | SHA1 | Date | |
|---|---|---|---|
| e5779183ff | |||
| 4814b64c2c |
@@ -175,6 +175,33 @@ type FieldValidationError struct {
|
|||||||
Message string
|
Message string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getFieldDisplayName(field reflect.StructField) string {
|
||||||
|
jsonTag := field.Tag.Get("json")
|
||||||
|
if jsonTag != "" && jsonTag != "-" {
|
||||||
|
if idx := strings.Index(jsonTag, ","); idx != -1 {
|
||||||
|
jsonTag = jsonTag[:idx]
|
||||||
|
}
|
||||||
|
if jsonTag != "" {
|
||||||
|
return jsonTag
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return camelCaseToWords(field.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func camelCaseToWords(s string) string {
|
||||||
|
if s == "" {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
var result strings.Builder
|
||||||
|
for i, r := range s {
|
||||||
|
if i > 0 && unicode.IsUpper(r) {
|
||||||
|
result.WriteRune(' ')
|
||||||
|
}
|
||||||
|
result.WriteRune(unicode.ToLower(r))
|
||||||
|
}
|
||||||
|
return result.String()
|
||||||
|
}
|
||||||
|
|
||||||
func ValidateStruct(s interface{}) error {
|
func ValidateStruct(s interface{}) error {
|
||||||
if s == nil {
|
if s == nil {
|
||||||
return nil
|
return nil
|
||||||
@@ -232,9 +259,10 @@ func ValidateStruct(s interface{}) error {
|
|||||||
tagName = tag
|
tagName = tag
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := validateField(field.Name, fieldVal, tagName, param, omitempty); err != nil {
|
displayName := getFieldDisplayName(field)
|
||||||
|
if err := validateField(displayName, fieldVal, tagName, param, omitempty); err != nil {
|
||||||
errors = append(errors, FieldValidationError{
|
errors = append(errors, FieldValidationError{
|
||||||
Field: field.Name,
|
Field: displayName,
|
||||||
Tag: tagName,
|
Tag: tagName,
|
||||||
Param: param,
|
Param: param,
|
||||||
Message: err.Message,
|
Message: err.Message,
|
||||||
@@ -293,7 +321,7 @@ func validateField(fieldName string, fieldVal reflect.Value, tagName, param stri
|
|||||||
func isEmptyValue(v reflect.Value) bool {
|
func isEmptyValue(v reflect.Value) bool {
|
||||||
switch v.Kind() {
|
switch v.Kind() {
|
||||||
case reflect.String:
|
case reflect.String:
|
||||||
return v.String() == ""
|
return strings.TrimSpace(v.String()) == ""
|
||||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||||
return v.Int() == 0
|
return v.Int() == 0
|
||||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||||
@@ -301,7 +329,7 @@ func isEmptyValue(v reflect.Value) bool {
|
|||||||
case reflect.Float32, reflect.Float64:
|
case reflect.Float32, reflect.Float64:
|
||||||
return v.Float() == 0
|
return v.Float() == 0
|
||||||
case reflect.Bool:
|
case reflect.Bool:
|
||||||
return !v.Bool()
|
return false
|
||||||
case reflect.Pointer, reflect.Interface, reflect.Slice, reflect.Map:
|
case reflect.Pointer, reflect.Interface, reflect.Slice, reflect.Map:
|
||||||
return v.IsNil()
|
return v.IsNil()
|
||||||
default:
|
default:
|
||||||
@@ -317,7 +345,8 @@ func validateMin(fieldName string, v reflect.Value, param string) *ValidationErr
|
|||||||
|
|
||||||
switch v.Kind() {
|
switch v.Kind() {
|
||||||
case reflect.String:
|
case reflect.String:
|
||||||
if len(v.String()) < min {
|
s := strings.TrimSpace(v.String())
|
||||||
|
if len([]rune(s)) < min {
|
||||||
return &ValidationError{Field: fieldName, Message: fmt.Sprintf("%s must be at least %d characters", fieldName, min)}
|
return &ValidationError{Field: fieldName, Message: fmt.Sprintf("%s must be at least %d characters", fieldName, min)}
|
||||||
}
|
}
|
||||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||||
@@ -344,7 +373,7 @@ func validateMax(fieldName string, v reflect.Value, param string) *ValidationErr
|
|||||||
|
|
||||||
switch v.Kind() {
|
switch v.Kind() {
|
||||||
case reflect.String:
|
case reflect.String:
|
||||||
if len(v.String()) > max {
|
if len([]rune(v.String())) > max {
|
||||||
return &ValidationError{Field: fieldName, Message: fmt.Sprintf("%s must be at most %d characters", fieldName, max)}
|
return &ValidationError{Field: fieldName, Message: fmt.Sprintf("%s must be at most %d characters", fieldName, max)}
|
||||||
}
|
}
|
||||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||||
|
|||||||
@@ -250,12 +250,12 @@ func TestSanitizeString(t *testing.T) {
|
|||||||
|
|
||||||
func TestValidateStruct(t *testing.T) {
|
func TestValidateStruct(t *testing.T) {
|
||||||
type TestStruct struct {
|
type TestStruct struct {
|
||||||
Username string `validate:"required,min=3,max=20"`
|
Username string `json:"username" validate:"required,min=3,max=20"`
|
||||||
Email string `validate:"required,email"`
|
Email string `json:"email" validate:"required,email"`
|
||||||
Age int `validate:"min=18,max=120"`
|
Age int `json:"age" validate:"min=18,max=120"`
|
||||||
URL string `validate:"url"`
|
URL string `json:"url" validate:"url"`
|
||||||
Status string `validate:"oneof=active inactive pending"`
|
Status string `json:"status" validate:"oneof=active inactive pending"`
|
||||||
Optional string `validate:"omitempty,min=1"`
|
Optional string `json:"optional" validate:"omitempty,min=1"`
|
||||||
}
|
}
|
||||||
|
|
||||||
t.Run("valid struct", func(t *testing.T) {
|
t.Run("valid struct", func(t *testing.T) {
|
||||||
@@ -287,6 +287,9 @@ func TestValidateStruct(t *testing.T) {
|
|||||||
if len(structErr.Errors) == 0 {
|
if len(structErr.Errors) == 0 {
|
||||||
t.Error("Expected validation errors, got none")
|
t.Error("Expected validation errors, got none")
|
||||||
}
|
}
|
||||||
|
if structErr.Errors[0].Message != "username is required" {
|
||||||
|
t.Errorf("Expected JSON tag name in error, got %q", structErr.Errors[0].Message)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -318,6 +321,20 @@ func TestValidateStruct(t *testing.T) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("whitespace required field", func(t *testing.T) {
|
||||||
|
s := TestStruct{
|
||||||
|
Username: " ",
|
||||||
|
Email: "test@example.com",
|
||||||
|
Age: 25,
|
||||||
|
URL: "https://example.com",
|
||||||
|
Status: "active",
|
||||||
|
}
|
||||||
|
err := ValidateStruct(s)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("ValidateStruct() expected error, got nil")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
t.Run("invalid max", func(t *testing.T) {
|
t.Run("invalid max", func(t *testing.T) {
|
||||||
s := TestStruct{
|
s := TestStruct{
|
||||||
Username: strings.Repeat("a", 21),
|
Username: strings.Repeat("a", 21),
|
||||||
|
|||||||
Reference in New Issue
Block a user