Skip to content

Commit

Permalink
Add email tag
Browse files Browse the repository at this point in the history
  • Loading branch information
nao1215 committed Sep 5, 2024
1 parent a76f452 commit cdd10be
Show file tree
Hide file tree
Showing 9 changed files with 88 additions and 1 deletion.
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,12 @@ You set the validation rules following the "validate:" tag according to the rule
| numeric | Check whether value is numeric or not |
| uppercase | Check whether value is uppercase or not |

#### Format

| Tag Name | Description |
|-------------------|---------------------------------------------------|
| email | Check whether value is an email address or not |

#### Comparisons

| Tag Name | Description |
Expand Down
37 changes: 37 additions & 0 deletions csv_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -528,4 +528,41 @@ ABC
}
}
})

t.Run("validate email", func(t *testing.T) {
t.Parallel()

input := `email
simple@example.com
very.common@example.com
disposable.style.email.with+symbol@example.com
user.name+tag+sorting@example.com
admin@mailserver1
badあ@example.com
`

c, err := NewCSV(bytes.NewBufferString(input))
if err != nil {
t.Fatal(err)
}

type email struct {
Email string `validate:"email"`
}

emails := make([]email, 0)
errs := c.Decode(&emails)
for i, err := range errs {
switch i {
case 0:
if err.Error() != "line:6 column email: target is not a valid email address: value=admin@mailserver1" {
t.Errorf("CSV.Decode() got errors: %v", err)
}
case 1:
if err.Error() != "line:7 column email: target is not a valid email address: value=badあ@example.com" {
t.Errorf("CSV.Decode() got errors: %v", err)
}
}
}
})
}
2 changes: 2 additions & 0 deletions errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,4 +96,6 @@ var (
ErrUppercaseID = "ErrUppercase"
// ErrASCIIID is the error ID used when the target is not an ASCII character.
ErrASCIIID = "ErrASCII"
// ErrEmailID is the error ID used when the target is not an email.
ErrEmailID = "ErrEmail"
)
3 changes: 3 additions & 0 deletions i18n/en.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -66,3 +66,6 @@

- id: "ErrASCII"
translation: "target is not an ASCII character"

- id: "ErrEmail"
translation: "target is not a valid email address"
3 changes: 3 additions & 0 deletions i18n/ja.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -72,3 +72,6 @@

- id: "ErrASCII"
translation: "値がASCII文字ではありません"

- id: "ErrEmail"
translation: "値がメールアドレスではありません"
3 changes: 3 additions & 0 deletions i18n/ru.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -66,3 +66,6 @@

- id: "ErrASCII"
translation: "целевое значение не является ASCII символом"

- id: "ErrEmail"
translation: "целевое значение не является адресом электронной почты"
2 changes: 2 additions & 0 deletions parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,8 @@ func (c *CSV) parseValidateTag(tags string) (validators, error) {
validatorList = append(validatorList, newUppercaseValidator())
case strings.HasPrefix(t, asciiTagValue.String()):
validatorList = append(validatorList, newASCIIValidator())
case strings.HasPrefix(t, emailTagValue.String()):
validatorList = append(validatorList, newEmailValidator())
}
}
return validatorList, nil
Expand Down
2 changes: 2 additions & 0 deletions tag.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ const (
uppercaseTagValue tagValue = "uppercase"
// asciiTagValue is the struct tag name for ascii fields.
asciiTagValue tagValue = "ascii"
// emailTagValue is the struct tag name for email fields.
emailTagValue tagValue = "email"
)

// String returns the string representation of the tag.
Expand Down
31 changes: 30 additions & 1 deletion validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package csv

import (
"fmt"
"regexp"
"strconv"
"strings"

Expand Down Expand Up @@ -461,15 +462,43 @@ func newASCIIValidator() *asciiValidator {

// Do validates the target is an ASCII string.
func (a *asciiValidator) Do(localizer *i18n.Localizer, target any) error {
const maxASCII = 127

v, ok := target.(string)
if !ok {
return NewError(localizer, ErrASCIIID, fmt.Sprintf("value=%v", target))
}

for _, r := range v {
if r > 127 {
if r > maxASCII {
return NewError(localizer, ErrASCIIID, fmt.Sprintf("value=%v", target))
}
}
return nil
}

// emailValidator is a struct that contains the validation rules for an email column.
type emailValidator struct {
regexp *regexp.Regexp
}

// newEmailValidator returns a new emailValidator.
func newEmailValidator() *emailValidator {
const emailRegexPattern = `^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$`
return &emailValidator{
regexp: regexp.MustCompile(emailRegexPattern),
}
}

// Do validates the target is an email.
func (e *emailValidator) Do(localizer *i18n.Localizer, target any) error {
v, ok := target.(string)
if !ok {
return NewError(localizer, ErrEmailID, fmt.Sprintf("value=%v", target))
}

if !e.regexp.MatchString(v) {
return NewError(localizer, ErrEmailID, fmt.Sprintf("value=%v", target))
}
return nil
}

0 comments on commit cdd10be

Please sign in to comment.