Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ The following validations will be implemented:
- in (in): must be one of the following values
- nin (not in): must not be one of the following values
- required (required): is required
- email (email): must be a valid email format (empty is valid for optional fields)

The following table shows the validations and possible types, where "I" means "Implemented", "W" means "Will be implemented" and "-" means "Will not be implemented":

Expand All @@ -66,6 +67,7 @@ The following table shows the validations and possible types, where "I" means "I
| in | I | W | W | W | W | W | - | W |
| nin | W | W | W | W | W | W | - | W |
| required | I | W | W | W | W | W | W | W |
| email | I | - | - | - | - | - | - | - |

# Steps to run the unit tests

Expand Down
87 changes: 87 additions & 0 deletions _examples/email_test/test_email.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package main

import (
"fmt"
)

type User struct {
Email1 string `validate:"required,email"`
Email2 string `validate:"email"`
}

func main() {
// Test case 1: Empty required email (should fail)
u1 := &User{
Email1: "",
Email2: "",
}
if errs := UserValidate(u1); len(errs) > 0 {
fmt.Printf("User1: %+v Errors: ", u1)
for _, err := range errs {
fmt.Printf("%s; ", err)
}
fmt.Println()
} else {
fmt.Printf("User1: %+v is valid\n", u1)
}

// Test case 2: Invalid required email (should fail)
u2 := &User{
Email1: "invalid.email",
Email2: "",
}
if errs := UserValidate(u2); len(errs) > 0 {
fmt.Printf("User2: %+v Errors: ", u2)
for _, err := range errs {
fmt.Printf("%s; ", err)
}
fmt.Println()
} else {
fmt.Printf("User2: %+v is valid\n", u2)
}

// Test case 3: Valid required email, empty optional email (should pass)
u3 := &User{
Email1: "valid@example.com",
Email2: "",
}
if errs := UserValidate(u3); len(errs) > 0 {
fmt.Printf("User3: %+v Errors: ", u3)
for _, err := range errs {
fmt.Printf("%s; ", err)
}
fmt.Println()
} else {
fmt.Printf("User3: %+v is valid\n", u3)
}

// Test case 4: Valid required email, valid optional email (should pass)
u4 := &User{
Email1: "user@domain.com",
Email2: "optional@test.org",
}
if errs := UserValidate(u4); len(errs) > 0 {
fmt.Printf("User4: %+v Errors: ", u4)
for _, err := range errs {
fmt.Printf("%s; ", err)
}
fmt.Println()
} else {
fmt.Printf("User4: %+v is valid\n", u4)
}

// Test case 5: Valid required email, invalid optional email (should fail)
u5 := &User{
Email1: "user@domain.com",
Email2: "invalid.email",
}
if errs := UserValidate(u5); len(errs) > 0 {
fmt.Printf("User5: %+v Errors: ", u5)
for _, err := range errs {
fmt.Printf("%s; ", err)
}
fmt.Println()
} else {
fmt.Printf("User5: %+v is valid\n", u5)
}
}
25 changes: 25 additions & 0 deletions _examples/email_test/user_validator.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions tests/endtoend/string.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,15 @@ type StringType struct {
FieldNeq string `validate:"neq=cba"`
FieldNeqIC string `validate:"neq_ignore_case=YeS"`
FieldIn string `validate:"in=ab bc cd"`
EmailReq string `validate:"required,email"`
EmailOpt string `validate:"email"`
}

func string_tests() {
var expectedMsgErrors []string
var errs []error

// Test case 1: All failure scenarios
v := &StringType{
FieldEq: "123",
FieldEqIC: "abc",
Expand All @@ -25,6 +28,8 @@ func string_tests() {
FieldNeq: "cba",
FieldNeqIC: "yeS",
FieldIn: "abc",
EmailReq: "invalid.email.format", // Invalid required email
EmailOpt: "invalid", // Invalid optional email
}
expectedMsgErrors = []string{
"FieldReq is required",
Expand All @@ -35,12 +40,15 @@ func string_tests() {
"FieldNeq must not be equal to 'cba'",
"FieldNeqIC must not be equal to 'yes'",
"FieldIn must be one of 'ab' 'bc' 'cd'",
"EmailReq must be a valid email",
"EmailOpt must be a valid email",
}
errs = StringTypeValidate(v)
if !expectedMsgErrorsOk(errs, expectedMsgErrors) {
log.Fatalf("error = %v, wantErr %v", errs, expectedMsgErrors)
}

// Test case 2: All valid input
v = &StringType{
FieldReq: "123",
FieldEq: "aabbcc",
Expand All @@ -50,6 +58,8 @@ func string_tests() {
FieldNeq: "ops",
FieldNeqIC: "No",
FieldIn: "bc",
EmailReq: "user@example.com", // Valid required email
EmailOpt: "", // Empty optional email (valid)
}
expectedMsgErrors = nil
errs = StringTypeValidate(v)
Expand Down
12 changes: 12 additions & 0 deletions tests/endtoend/stringtype_validator.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

21 changes: 20 additions & 1 deletion types/string_utils.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,26 @@
package types

import "strings"
import (
"regexp"
"strings"
)

// emailRegex is a pre-compiled regex for email validation
// This avoids recompiling the regex on every validation call
var emailRegex = regexp.MustCompile(`^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$`)

func ToLower(str string) string {
return strings.ToLower(str)
}

// IsValidEmail validates if a string is a valid email format
// Returns true for valid email format, false otherwise
// Empty string returns true (for optional email fields)
func IsValidEmail(email string) bool {
if email == "" {
return true // Empty email is valid for optional fields
}

// Use pre-compiled regex for better performance
return emailRegex.MatchString(email)
}
80 changes: 80 additions & 0 deletions types/string_utils_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package types

import "testing"

func TestIsValidEmail(t *testing.T) {
tests := []struct {
name string
email string
want bool
}{
{
name: "empty email (valid for optional fields)",
email: "",
want: true,
},
{
name: "valid simple email",
email: "test@example.com",
want: true,
},
{
name: "valid email with subdomain",
email: "user@mail.example.com",
want: true,
},
{
name: "valid email with numbers and special chars",
email: "user123+tag@example.co.uk",
want: true,
},
{
name: "valid email with dots and underscores",
email: "first.last_name@domain.org",
want: true,
},
{
name: "invalid email without @",
email: "invalid.email.com",
want: false,
},
{
name: "invalid email without domain",
email: "user@",
want: false,
},
{
name: "invalid email without local part",
email: "@domain.com",
want: false,
},
{
name: "invalid email without TLD",
email: "user@domain",
want: false,
},
{
name: "invalid email with spaces",
email: "user @domain.com",
want: false,
},
{
name: "invalid email with multiple @",
email: "user@@domain.com",
want: false,
},
{
name: "invalid email with short TLD",
email: "user@domain.c",
want: false,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := IsValidEmail(tt.email); got != tt.want {
t.Errorf("IsValidEmail(%q) = %v, want %v", tt.email, got, tt.want)
}
})
}
}
13 changes: 13 additions & 0 deletions validgen/get_test_elements_string_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,19 @@ func TestGetTestElementsWithStringFields(t *testing.T) {
errorMessage: "InField must be one of ' a ' ' b ' ' c '",
},
},
{
name: "Email validation",
args: args{
fieldName: "EmailField",
fieldValidation: "email",
},
want: TestElements{
leftOperand: "types.IsValidEmail(obj.EmailField)",
operator: "==",
rightOperands: []string{`true`},
errorMessage: "EmailField must be a valid email",
},
},
}

for _, tt := range tests {
Expand Down
1 change: 1 addition & 0 deletions validgen/parser_validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ func ParserValidation(fieldValidation string) (*Validation, error) {
"neq": ONE_VALUE,
"neq_ignore_case": ONE_VALUE,
"in": MANY_VALUES,
"email": ZERO_VALUE,
}

validation, values, err := parserValidationString(fieldValidation)
Expand Down
9 changes: 9 additions & 0 deletions validgen/parser_validation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,15 @@ func Test_ValidParserValidation(t *testing.T) {
Values: []string{"a", "b", "c"},
},
},
{
name: "email validation without value",
validation: "email",
want: &Validation{
Operation: "email",
ExpectedValues: ZERO_VALUE,
Values: []string{},
},
},
}

for _, tt := range tests {
Expand Down
1 change: 1 addition & 0 deletions validgen/test_elements.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ func GetTestElements(fieldName, fieldValidation, fieldType string) (TestElements
"neq,string": {"{{.Name}}", "!=", `"{{.Target}}"`, "{{.Name}} must not be equal to '{{.Target}}'"},
"neq_ignore_case,string": {"types.ToLower({{.Name}})", "!=", `"{{.Target}}"`, "{{.Name}} must not be equal to '{{.Target}}'"},
"in,string": {"{{.Name}}", "==", `"{{.Target}}"`, "{{.Name}} must be one of {{.Targets}}"},
"email,string": {"types.IsValidEmail({{.Name}})", "==", `true`, "{{.Name}} must be a valid email"},
}

validation, err := ParserValidation(fieldValidation)
Expand Down