Skip to content

Commit

Permalink
Fixes go-ozzo#11: Added date validation rule
Browse files Browse the repository at this point in the history
  • Loading branch information
qiangxue committed Feb 7, 2017
1 parent 84f67d6 commit 821a3e5
Show file tree
Hide file tree
Showing 5 changed files with 160 additions and 6 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,8 @@ The following rules are provided in the `validation` package:
This rule should only be used for validating int, uint and float types.
* `Match(*regexp.Regexp)`: checks if a value matches the specified regular expression.
This rule should only be used for strings and byte slices.
* `Date(layout string)`: checks if a string value is a date whose format is specified by the layout.
By calling `Min()` and/or `Max()`, you can check additionally if the date is within the specified range.
* `Required`: checks if a value is not empty (neither nil nor zero).
* `NotNil`: checks if a pointer value is not nil. Non-pointer values are considered valid.
* `Skip`: this is a special rule used to indicate that all rules following it should be skipped (including the nested ones).
Expand Down
84 changes: 84 additions & 0 deletions date.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
// Copyright 2016 Qiang Xue. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.

package validation

import (
"errors"
"time"
)

type dateRule struct {
layout string
min, max time.Time
message string
rangeMessage string
}

// Date returns a validation rule that checks if a string value is in a format that can be parsed into a date.
// The format of the date should be specified as the layout parameter which accepts the same value as that for time.Parse.
// For example,
// validation.Date(time.ANSIC)
// validation.Date("02 Jan 06 15:04 MST")
// validation.Date("2006-01-02")
//
// By calling Min() and/or Max(), you can let the Date rule to check if a parsed date value is within
// the specified date range.
//
// An empty value is considered valid. Use the Required rule to make sure a value is not empty.
func Date(layout string) *dateRule {
return &dateRule{
layout: layout,
message: "must be a valid date",
rangeMessage: "the data is out of range",
}
}

// Error sets the error message that is used when the value being validated is not a valid date.
func (r *dateRule) Error(message string) *dateRule {
r.message = message
return r
}

// RangeError sets the error message that is used when the value being validated is out of the specified Min/Max date range.
func (r *dateRule) RangeError(message string) *dateRule {
r.rangeMessage = message
return r
}

// Min sets the minimum date range. A zero value means skipping the minimum range validation.
func (r *dateRule) Min(min time.Time) *dateRule {
r.min = min
return r
}

// Max sets the maximum date range. A zero value means skipping the maximum range validation.
func (r *dateRule) Max(max time.Time) *dateRule {
r.max = max
return r
}

// Validate checks if the given value is a valid date.
func (r *dateRule) Validate(value interface{}) error {
value, isNil := Indirect(value)
if isNil || IsEmpty(value) {
return nil
}

str, err := EnsureString(value)
if err != nil {
return err
}

date, err := time.Parse(r.layout, str)
if err != nil {
return errors.New(r.message)
}

if !r.min.IsZero() && r.min.After(date) || !r.max.IsZero() && date.After(r.max) {
return errors.New(r.rangeMessage)
}

return nil
}
69 changes: 69 additions & 0 deletions date_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
// Copyright 2016 Qiang Xue. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.

package validation

import (
"testing"
"time"

"github.com/stretchr/testify/assert"
)

func TestDate(t *testing.T) {
tests := []struct {
tag string
layout string
value interface{}
err string
}{
{"t1", time.ANSIC, "", ""},
{"t2", time.ANSIC, "Wed Feb 4 21:00:57 2009", ""},
{"t3", time.ANSIC, "Wed Feb 29 21:00:57 2009", "must be a valid date"},
{"t4", "2006-01-02", "2009-11-12", ""},
{"t5", "2006-01-02", "2009-11-12 21:00:57", "must be a valid date"},
{"t6", "2006-01-02", "2009-1-12", "must be a valid date"},
{"t7", "2006-01-02", "2009-01-12", ""},
{"t8", "2006-01-02", "2009-01-32", "must be a valid date"},
{"t9", "2006-01-02", 1, "must be either a string or byte slice"},
}

for _, test := range tests {
r := Date(test.layout)
err := r.Validate(test.value)
assertError(t, test.err, err, test.tag)
}
}

func TestDateRule_Error(t *testing.T) {
r := Date(time.ANSIC)
assert.Equal(t, "must be a valid date", r.message)
assert.Equal(t, "the data is out of range", r.rangeMessage)
r.Error("123")
r.RangeError("456")
assert.Equal(t, "123", r.message)
assert.Equal(t, "456", r.rangeMessage)
}

func TestDateRule_MinMax(t *testing.T) {
r := Date(time.ANSIC)
assert.True(t, r.min.IsZero())
assert.True(t, r.max.IsZero())
r.Min(time.Now())
assert.False(t, r.min.IsZero())
assert.True(t, r.max.IsZero())
r.Max(time.Now())
assert.False(t, r.max.IsZero())

r2 := Date("2006-01-02").Min(time.Date(2000, 12, 1, 0, 0, 0, 0, time.UTC)).Max(time.Date(2020, 2, 1, 0, 0, 0, 0, time.UTC))
assert.Nil(t, r2.Validate("2010-01-02"))
err := r2.Validate("1999-01-02")
if assert.NotNil(t, err) {
assert.Equal(t, "the data is out of range", err.Error())
}
err2 := r2.Validate("2021-01-02")
if assert.NotNil(t, err) {
assert.Equal(t, "the data is out of range", err2.Error())
}
}
10 changes: 5 additions & 5 deletions string.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,28 +8,28 @@ import "errors"

type stringValidator func(string) bool

type stringRule struct {
type StringRule struct {
validate stringValidator
message string
}

// NewStringRule creates a new validation rule using a function that takes a string value and returns a bool.
// The rule returned will use the function to check if a given string or byte slice is valid or not.
// An empty value is considered to be valid. Please use the Required rule to make sure a value is not empty.
func NewStringRule(validator stringValidator, message string) *stringRule {
return &stringRule{
func NewStringRule(validator stringValidator, message string) *StringRule {
return &StringRule{
validate: validator,
message: message,
}
}

// Error sets the error message for the rule.
func (v *stringRule) Error(message string) *stringRule {
func (v *StringRule) Error(message string) *StringRule {
return NewStringRule(v.validate, message)
}

// Validate checks if the given value is valid or not.
func (v *stringRule) Validate(value interface{}) error {
func (v *StringRule) Validate(value interface{}) error {
value, isNil := Indirect(value)
if isNil || IsEmpty(value) {
return nil
Expand Down
1 change: 0 additions & 1 deletion string_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ package validation

import (
"testing"

"database/sql"

"github.com/stretchr/testify/assert"
Expand Down

0 comments on commit 821a3e5

Please sign in to comment.