Skip to content

Commit

Permalink
Add translation feature. (#91)
Browse files Browse the repository at this point in the history
  • Loading branch information
mehran-prs authored Jan 30, 2020
1 parent f7b7446 commit f8d2e47
Show file tree
Hide file tree
Showing 29 changed files with 794 additions and 206 deletions.
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
*.a
*.so

# Idea Directory
.idea

# Folders
_obj
_test
Expand All @@ -22,4 +25,4 @@ _testmain.go
*.exe
*.test
*.prof
.DS_Store
.DS_Store
11 changes: 11 additions & 0 deletions UPGRADE.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
# Upgrade Instructions

## Upgrade from 3.x to 4.x
* Change your error message placeholders from the Go `fmt` package `verbs` to the `template variables`.
```go
// 3.x
// Assume you want to set a custom message for the LengthRule:
var lengthRule= validation.Length(2,10).Error("the length must be between %v and %v")

// 4.x
var lengthRule= validation.Length(2,10).Error("the length must be between {{.min}} and {{.max}}")
```

## Upgrade from 2.x to 3.x

* Instead of using `StructRules` to define struct validation rules, use `ValidateStruct()` to declare and perform
Expand Down
41 changes: 29 additions & 12 deletions date.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,21 @@
package validation

import (
"errors"
"time"
)

var (
// ErrDateInvalid is the error that returns in case of an invalid date.
ErrDateInvalid = NewError("validation_date_invalid", "must be a valid date")
// ErrDateOutOfRange is the error that returns in case of an invalid date.
ErrDateOutOfRange = NewError("validation_date_out_of_range", "the date is out of range")
)

// DateRule is a validation rule that validates date/time string values.
type DateRule struct {
layout string
min, max time.Time
message string
rangeMessage string
layout string
min, max time.Time
err, rangeErr Error
}

// Date returns a validation rule that checks if a string value is in a format that can be parsed into a date.
Expand All @@ -30,21 +35,33 @@ type DateRule struct {
// 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",
layout: layout,
err: ErrDateInvalid,
rangeErr: ErrDateOutOfRange,
}
}

// 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
r.err = r.err.SetMessage(message)
return r
}

// ErrorObject sets the error struct that is used when the value being validated is not a valid date..
func (r DateRule) ErrorObject(err Error) DateRule {
r.err = err
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
r.rangeErr = r.rangeErr.SetMessage(message)
return r
}

// RangeErrorObject sets the error struct that is used when the value being validated is out of the specified Min/Max date range.
func (r DateRule) RangeErrorObject(err Error) DateRule {
r.rangeErr = err
return r
}

Expand Down Expand Up @@ -74,11 +91,11 @@ func (r DateRule) Validate(value interface{}) error {

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

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

return nil
Expand Down
36 changes: 29 additions & 7 deletions date_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,35 @@ func TestDate(t *testing.T) {
}

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 := Date(time.RFC3339)
assert.Equal(t, "must be a valid date", r.Validate("0001-01-02T15:04:05Z07:00").Error())
r2 := r.Min(time.Date(2000, 1, 1, 1, 1, 1, 0, time.UTC))
assert.Equal(t, "the date is out of range", r2.Validate("1999-01-02T15:04:05Z").Error())
r = r.Error("123")
r = r.RangeError("456")
assert.Equal(t, "123", r.message)
assert.Equal(t, "456", r.rangeMessage)
assert.Equal(t, "123", r.err.Message())
assert.Equal(t, "456", r.rangeErr.Message())
}

func TestDateRule_ErrorObject(t *testing.T) {
r := Date(time.RFC3339)
assert.Equal(t, "must be a valid date", r.Validate("0001-01-02T15:04:05Z07:00").Error())

r = r.ErrorObject(NewError("code", "abc"))

assert.Equal(t, "code", r.err.Code())
assert.Equal(t, "abc", r.Validate("0001-01-02T15:04:05Z07:00").Error())

r2 := r.Min(time.Date(2000, 1, 1, 1, 1, 1, 0, time.UTC))
assert.Equal(t, "the date is out of range", r2.Validate("1999-01-02T15:04:05Z").Error())

r = r.ErrorObject(NewError("C", "def"))
r = r.RangeErrorObject(NewError("D", "123"))

assert.Equal(t, "C", r.err.Code())
assert.Equal(t, "def", r.err.Message())
assert.Equal(t, "D", r.rangeErr.Code())
assert.Equal(t, "123", r.rangeErr.Message())
}

func TestDateRule_MinMax(t *testing.T) {
Expand All @@ -60,10 +82,10 @@ func TestDateRule_MinMax(t *testing.T) {
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())
assert.Equal(t, "the date 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())
assert.Equal(t, "the date is out of range", err2.Error())
}
}
87 changes: 87 additions & 0 deletions error.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,35 @@
package validation

import (
"bytes"
"encoding/json"
"fmt"
"sort"
"strings"
"text/template"
)

type (
// Error interface represents an validation error
Error interface {
Error() string
Code() string
Message() string
SetMessage(string) Error
Params() map[string]interface{}
SetParams(map[string]interface{}) Error
}

// ErrorObject is the default validation error
// that implements the Error interface.
ErrorObject struct {
code string
message string
params map[string]interface{}
}

// Errors represents the validation errors that are indexed by struct field names, map or slice keys.
// values are Error or Errors (for map,slice and array error value is Errors).
Errors map[string]error

// InternalError represents an error that should NOT be treated as a validation error.
Expand All @@ -36,6 +57,61 @@ func (e internalError) InternalError() error {
return e.error
}

// SetCode set the error's translation code.
func (e ErrorObject) SetCode(code string) Error {
e.code = code
return e
}

// Code get the error's translation code.
func (e ErrorObject) Code() string {
return e.code
}

// SetParams set the error's params.
func (e ErrorObject) SetParams(params map[string]interface{}) Error {
e.params = params
return e
}

// AddParam add parameter to the error's parameters.
func (e ErrorObject) AddParam(name string, value interface{}) Error {
if e.params == nil {
e.params = make(map[string]interface{})
}

e.params[name] = value
return e
}

// Params returns the error's params.
func (e ErrorObject) Params() map[string]interface{} {
return e.params
}

// SetMessage set the error's message.
func (e ErrorObject) SetMessage(message string) Error {
e.message = message
return e
}

// Message return the error's message.
func (e ErrorObject) Message() string {
return e.message
}

// Error returns the error message.
func (e ErrorObject) Error() string {
if len(e.params) == 0 {
return e.message
}

res := bytes.Buffer{}
_ = template.Must(template.New("err").Parse(e.message)).Execute(&res, e.params)

return res.String()
}

// Error returns the error string of Errors.
func (es Errors) Error() string {
if len(es) == 0 {
Expand Down Expand Up @@ -89,3 +165,14 @@ func (es Errors) Filter() error {
}
return es
}

// NewError create new validation error.
func NewError(code, message string) Error {
return ErrorObject{
code: code,
message: message,
}
}

// Assert that our ErrorObject implements the Error interface.
var _ Error = ErrorObject{}
Loading

0 comments on commit f8d2e47

Please sign in to comment.