Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add contains tag #14

Merged
merged 1 commit into from
Sep 6, 2024
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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ You set the validation rules following the "validate:" tag according to the rule
| alphanumeric | Check whether value is alphanumeric or not |
| ascii | Check whether value is ASCII or not |
| boolean | Check whether value is boolean or not. |
| contains | Check whether value contains the specified substring <br> e.g. `validate:"contains=abc"` |
| lowercase | Check whether value is lowercase or not |
| numeric | Check whether value is numeric or not |
| uppercase | Check whether value is uppercase or not |
Expand Down
58 changes: 58 additions & 0 deletions csv_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -565,4 +565,62 @@ badあ@example.com
}
}
})

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

input := `name
search for a needle in a haystack
example sentence
`

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

type contains struct {
Name string `validate:"contains=needle"`
}

containsList := make([]contains, 0)
errs := c.Decode(&containsList)
for i, err := range errs {
switch i {
nao1215 marked this conversation as resolved.
Show resolved Hide resolved
case 0:
if err.Error() != "line:3 column name: target does not contain the specified value: contains=needle, value=example sentence" {
t.Errorf("CSV.Decode() got errors: %v", err)
}
}
}
})

t.Run("invalid contains tag format", func(t *testing.T) {
t.Parallel()

input := `name
search for a needle in a haystack
example sentence
`

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

type contains struct {
Name string `validate:"contains=needle bad_value"`
}

containsList := make([]contains, 0)
errs := c.Decode(&containsList)
for i, err := range errs {
switch i {
nao1215 marked this conversation as resolved.
Show resolved Hide resolved
case 0:
if err.Error() != "target is not one of the values: contains=needle bad_value" {
t.Errorf("CSV.Decode() got errors: %v", err)
}
}
}
})
}
4 changes: 4 additions & 0 deletions errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,4 +98,8 @@ var (
ErrASCIIID = "ErrASCII"
// ErrEmailID is the error ID used when the target is not an email.
ErrEmailID = "ErrEmail"
// ErrContainsID is the error ID used when the target does not contain the specified value.
ErrContainsID = "ErrContains"
// ErrInvalidContainsFormatID is the error ID used when the contains format is invalid.
ErrInvalidContainsFormatID = "ErrInvalidContainsFormat"
)
6 changes: 6 additions & 0 deletions i18n/en.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -69,3 +69,9 @@

- id: "ErrEmail"
translation: "target is not a valid email address"

- id: "ErrContains"
translation: "target does not contain the specified value"

- id: "ErrInvalidContainsFormat"
translation: "'contains' tag format is invalid"
6 changes: 6 additions & 0 deletions i18n/ja.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -75,3 +75,9 @@

- id: "ErrEmail"
translation: "値がメールアドレスではありません"

- id: "ErrContains"
translation: "指定された値を含んでいません"

- id: "ErrInvalidContainsFormat"
translation: "'contains'タグの形式が無効です"
6 changes: 6 additions & 0 deletions i18n/ru.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -69,3 +69,9 @@

- id: "ErrEmail"
translation: "целевое значение не является адресом электронной почты"

- id: "ErrContains"
translation: "целевое значение не содержит подстроку"

- id: "ErrInvalidContains"
translation: "Формат тега 'contains' недопустим"
9 changes: 9 additions & 0 deletions parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,15 @@ func (c *CSV) parseValidateTag(tags string) (validators, error) {
validatorList = append(validatorList, newASCIIValidator())
case strings.HasPrefix(t, emailTagValue.String()):
validatorList = append(validatorList, newEmailValidator())
case strings.HasPrefix(t, containsTagValue.String()):
oneOf, err := c.parseOneOf(t)
if err != nil {
return nil, err
}
if len(oneOf) != 1 {
return nil, NewError(c.i18nLocalizer, ErrInvalidOneOfFormatID, t)
}
validatorList = append(validatorList, newContainsValidator(oneOf[0]))
}
}
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 @@ -50,6 +50,8 @@ const (
asciiTagValue tagValue = "ascii"
// emailTagValue is the struct tag name for email fields.
emailTagValue tagValue = "email"
// containsTagValue is the struct tag name for contains fields.
containsTagValue tagValue = "contains"
)

// String returns the string representation of the tag.
Expand Down
23 changes: 23 additions & 0 deletions validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -502,3 +502,26 @@ func (e *emailValidator) Do(localizer *i18n.Localizer, target any) error {
}
return nil
}

// containsValidator is a struct that contains the validation rules for a contains column.
type containsValidator struct {
contains string
}

// newContainsValidator returns a new containsValidator.
func newContainsValidator(contains string) *containsValidator {
return &containsValidator{contains: contains}
}

// Do validates the target contains the contains value.
func (c *containsValidator) Do(localizer *i18n.Localizer, target any) error {
v, ok := target.(string)
if !ok {
return NewError(localizer, ErrContainsID, fmt.Sprintf("value=%v", target))
}

if !strings.Contains(v, c.contains) {
return NewError(localizer, ErrContainsID, fmt.Sprintf("contains=%s, value=%v", c.contains, target))
}
return nil
}
Comment on lines +506 to +527
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Implementation of containsValidator is robust and well-structured.

The containsValidator struct and its associated methods are correctly implemented to support the new "contains" validation rule. The Do method effectively checks for the presence of the specified substring and returns informative error messages when the validation fails.

Suggestion for Improvement:
Consider enhancing the error message in the Do method to include more context about the expected and actual values, which could aid in debugging complex issues.

Loading