Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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.

50 changes: 50 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 validation failures including email
Comment thread
alexgarzao marked this conversation as resolved.
Outdated
v := &StringType{
FieldEq: "123",
FieldEqIC: "abc",
Expand All @@ -25,6 +28,8 @@ func string_tests() {
FieldNeq: "cba",
FieldNeqIC: "yeS",
FieldIn: "abc",
EmailReq: "", // Empty 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 is required",
"EmailOpt must be a valid email",
}
errs = StringTypeValidate(v)
if !expectedMsgErrorsOk(errs, expectedMsgErrors) {
log.Fatalf("error = %v, wantErr %v", errs, expectedMsgErrors)
}

// Test case 2: Invalid required email format
v = &StringType{
FieldReq: "123",
FieldEq: "aabbcc",
Expand All @@ -50,6 +58,48 @@ func string_tests() {
FieldNeq: "ops",
FieldNeqIC: "No",
FieldIn: "bc",
EmailReq: "invalid.email.format", // Invalid required email
EmailOpt: "", // Empty optional email (valid)
}
expectedMsgErrors = []string{
"EmailReq must be a valid email",
}
errs = StringTypeValidate(v)
if !expectedMsgErrorsOk(errs, expectedMsgErrors) {
log.Fatalf("error = %v, wantErr %v", errs, expectedMsgErrors)
}

// Test case 3: All valid including valid emails
v = &StringType{
FieldReq: "123",
FieldEq: "aabbcc",
FieldEqIC: "yEs",
FieldMinMax: "12345678",
FieldLen: "abcdefgh",
FieldNeq: "ops",
FieldNeqIC: "No",
FieldIn: "bc",
EmailReq: "user@example.com", // Valid required email
EmailOpt: "optional@test.org", // Valid optional email
}
expectedMsgErrors = nil
errs = StringTypeValidate(v)
if !expectedMsgErrorsOk(errs, expectedMsgErrors) {
log.Fatalf("error = %v, wantErr %v", errs, expectedMsgErrors)
}

// Test case 4: Valid required email, empty optional email
v = &StringType{
FieldReq: "123",
FieldEq: "aabbcc",
FieldEqIC: "yEs",
FieldMinMax: "12345678",
FieldLen: "abcdefgh",
FieldNeq: "ops",
FieldNeqIC: "No",
FieldIn: "bc",
EmailReq: "required@domain.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
Loading