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 translation feature. #91

Merged
merged 12 commits into from
Jan 30, 2020
Merged

Add translation feature. #91

merged 12 commits into from
Jan 30, 2020

Conversation

mehran-prs
Copy link
Contributor

Hi,
I added the translation feature and also modified all the rules to use this feature.

I assume each rule's message property is the custom message, so any user sets it, that rule returns the user's message, otherwise, return the translated message.

Each rule can return the translated message if it does not exists return the English message, and if the English message does not exist for that
rule, return rule's default message.

All functions work just like before Except one function:
I Changed NewStringRule function signature from
func validation.NewStringRule(validation stringValidator,message string) to
func validation.NewStringRule(validation stringValidator,ruleName string)
this is because we don't need the message here anymore, just need to get a name for that rule and get the rule's message from the translation map.

I also changed your package version from 3 to 4 because of NewStringRule function's
signature changes.

@mehran-prs mehran-prs requested a review from qiangxue January 17, 2020 20:47
@coveralls
Copy link

coveralls commented Jan 17, 2020

Coverage Status

Coverage remained the same at 100.0% when pulling df2b2c7 on mehran-prs:master into f7b7446 on go-ozzo:master.

@qiangxue
Copy link
Member

Thank you for your attempt to add message translation support!
I noticed a global variable Lang is used to determine which language should be used.
If the validation error messages are meant to be shown to end users (that's also the main reason why translation is wanted), the language in use would be determined by the request context. A global language setting would not work in this scenario.

@mehran-prs
Copy link
Contributor Author

mehran-prs commented Jan 17, 2020

you're welcome qiangxue,

OK, Each rule can return ValidationErr that is something like this:

type ErrMessage struct {
		Lang           string
		TranslationKey string
		DefaultMessage string
		Message        string
		Params         []interface{}
}

func (e *ErrMessage) Lang(lang string) {
	e.lang = lang
}

func (e ErrMessage) Error() string {
	msg := MsgInLang(lang, e.translationKey, e.defaultMessage, e.message)
	return fmt.Sprintf(msg, e.translationParams...)
}

and finally, after validating our data, we can set lang on erros and get translation in any language that we want.

Are you ok with this changes?

@mehran-prs
Copy link
Contributor Author

mehran-prs commented Jan 19, 2020

Hi @qiangxue

  • I changed source code, now each rule return ErrMessage if data was invalid, it returns message after a call to the Error (generate it on the fly, it now is relative to error's language).

  • If we want to get the error for validation rule in other languages, just need to call to ToLang(lang string) method on the returned error.

  • Also if need to get the error in other languages for struct validation result, again just need to call to ToLang(lang string) method on Errors map.

  • priority to returning error message for each ErrMessage is :

  • The custom message (Message property of ErrMessage).
  • The translated message in ErrMessage language.
  • The translated message in the specified language by the global Lang variable.
  • The translated message in the English language.
  • The default message
  • Empty string

everything works the same as before and we have feature-rich validation errors now :)

@mehran-prs mehran-prs requested review from qiangxue and removed request for qiangxue January 19, 2020 14:38
@mehran-prs
Copy link
Contributor Author

mehran-prs commented Jan 20, 2020

@qiangxue can you see my PR, please?

@qiangxue
Copy link
Member

qiangxue commented Jan 20, 2020

@mehran-prs I have reviewed your code again. I really like the idea of enhancing the validation error definition so that it supports translation. However, I think i18n/translation doesn't belong to this library. There are existing libraries doing this.

Here's what I'm thinking:

  • Define an error struct that includes: 1) error template string with parameter placeholders, 2) a map of parameters that will replace the corresponding placeholders in the error template, 3) Error() method returns the actual error message using the default message generation approach, 4) methods exposing the template and the parameters so that they can be called if translation is needed.
  • Update the validation rules whose error messages are parameterized to use the above error struct. Note that StringRule doesn't need this because its message is not parameterized. This would avoid breaking BC.
  • Enhance Errors by adding a method like Translate which takes a Translator interface to translate the messages in Errors. Note that Translate doesn't implement any real translation logic. It delegates all those to the Translator (from another library). We should examine popular existing i18n libraries and define a sensible Translator interface.

I'd like to hear your comments. Also, before you spend time creating a major PR, can we discuss about the design first? I feel bad when denying a PR like this because I know you have spent a lot of time on it. Thank you!

@mehran-prs
Copy link
Contributor Author

mehran-prs commented Jan 21, 2020

You're right @qiangxue, it's better to have a translator interface instead of implementing i18n/translator. I had to talk to you before creating PR.
go-i18n seems to be the most used translator package in Go and I gave it a try.

In addition to error struct fields that you said, I think it should also have:

  • A translationKey (to use as the key for finding the message in each language),
  • A plural(type int*|float*) field that uses as the identifier to detect sentence should be in plural form or singular in the translator (e.g email must be at least one character or email must be at least 4 characters).

I think something like this can be good for us:

  • Define a variable that keeps the prefix of validation keys to prevent key conflict with other translation keys.
var ValidationKeysPrefix="validation_"
  • On each rule have a Key method, so users can customize their translation key per validation (This does what Error method does for default error messages.).
// e.g on InRule :
func (r InRule) Key(key string) ErrMessage {
	r.translationKey=key
	return r
}
  • ErrMessage struct:
type ErrMessage struct {
	translationKey string
	params map[string]interface{}
	plurar interface{}
	message string // default message that using for Error() method.
}

 // definition of methods that expose params,translationkey, ...

// Translate method on ErrMessage:
func (e ErrMessage) Translate(t Translator) (string, error) {
	return t.TranslateSingleFieldErr(e)
}
  • Translate method on Errors
func (e Errors) Translate(t Translator) (string, error) {
	// use t.TranslateStructFieldErr(field string,err) on each error
}
  • Our Translator interface
type Translator interface {
	TranslateStructFieldErr(field string,err ErrMessage) (string,error)
	TranslateSingleFieldErr(err ErrMessage) (string,error)
}

Are you ok with something like this?

@qiangxue
Copy link
Member

I'm thinking about an even simpler solution which is still flexible enough to support i18n.

First define an Error struct:

type Error struct {
    code string
    message string
    params map[string]interface{}
}

The Error struct implements the error interface and also the methods exposing its fields. This definition should be sufficient for representing most error messages that need to be translated.

For each validation rule, modify its Error() method so that it can also accept an error. The default error messages for the rules are represented using the Error struct so that they can be translated, if needed.

For error messages that need more complex translation rules, users can create their own error types and pass them into the rules.

@mehran-prs
Copy link
Contributor Author

OK, So I implement it.

@mehran-prs
Copy link
Contributor Author

@qiangxue I updated my PR.
simple map in messages is just for the package's default messages, this keeps the package cleaner.

If you think my repo needs to squash commits, just say.

Copy link
Member

@qiangxue qiangxue left a comment

Choose a reason for hiding this comment

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

That's a good start! I left some comments. Thanks!

UPGRADE.md Show resolved Hide resolved
date.go Outdated
message: "must be a valid date",
rangeMessage: "the data is out of range",
message: messages["date"],
rangeMessage: messages["date_range"],
Copy link
Member

Choose a reason for hiding this comment

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

This seems to be cleaner, and we can use the same pattern for other built-in rules:

return DateRule{
    layout: layout,
    err: NewError("validation_date_invalid", "must be a valid date"),
    errRange: NewError("validation_date_range_invalid", "the data is out of range"),
}

In Validate(), we choose the error and parameterize it, if needed.
The Error() function simply updates the message in the error.

Copy link
Member

Choose a reason for hiding this comment

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

Also, for the built-in rules, we should use a common naming convention for error code: validation_{RuleName}_{ErrorName}.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

So I change all validation codes to follow the same pattern.

error.go Outdated
@@ -36,6 +53,86 @@ func (e internalError) InternalError() error {
return e.error
}

// Code set the error's translation code.
func (e Error) Code(code string) Error {
Copy link
Member

Choose a reason for hiding this comment

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

I think the Go naming convention for getters and setters is:

  • getter: Code() string
  • setter: SetCode(code string)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yea, I change it.

error.go Outdated

// Translate get a translator that must implemented Translator and
// return translated errors.
func (e Error) Translate(t Translator) (string, error) {
Copy link
Member

Choose a reason for hiding this comment

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

I'm thinking to drop the translate methods completely from this library. Since the new Error struct and the built-in rules carry/expose the error code information, the translation feature can be done separately.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Right, so I remove it.

messages.go Outdated

package validation

var messages = map[string]string{
Copy link
Member

Choose a reason for hiding this comment

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

I think we should get rid of this map. It doesn't provide extra value while it requires us to type the key strings in multiple places, which could cause typo issues.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ok, I remove it, but if users want to see a list of keys to translate, by this map they can simply detect what keys should translate.

Copy link
Member

Choose a reason for hiding this comment

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

Yeah, I understood your thought when I saw this. I have created I18N libraries and I18N-enabled applications before. To determine which messages need to be translated, we created simple regex-based code scanners which can easily find all messages that need translation from a huge code base. I know other I18N systems also support dynamic detection of I18N messages. So I think we don't need to worry too much about finding out what messages need to be translated.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

You are right

UPGRADE.md Show resolved Hide resolved
error_test.go Outdated
"testing"

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

// TestTranslator is a mocked struct that implements Translator.
type TestTranslator struct {
Copy link
Member

Choose a reason for hiding this comment

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

This should be removed.

Copy link
Contributor Author

@mehran-prs mehran-prs Jan 24, 2020

Choose a reason for hiding this comment

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

But if we drop NewStringValidator, then we need to define all our string validators with the extra call to the SetCode method, something like NewStringRule(isEmail,"must be a valid email address").SetCode("validation_emai_invalidl"), in addition to this, we need to define functions with good signature and deprecate old functions.

Yea, I remove TestTranslator.

Copy link
Member

Choose a reason for hiding this comment

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

I mean change the signature of NewStringRule to be the same as NewStringValidator, since we are upgrading to a new major version.

is/rules.go Outdated
"gopkg.in/asaskevich/govalidator.v9"
)

var (
// Email validates if a string is an email or not.
Email = validation.NewStringRule(govalidator.IsEmail, "must be a valid email address")
Email = validation.NewStringValidator(govalidator.IsEmail, "validation_email_invalid", "must be a valid email address")
Copy link
Member

Choose a reason for hiding this comment

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

can we change the error code of these string rules to be something like validation_is_email so that they are more readable?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think we do not need to validation prefix nor invalid postfix because the translator can append and prepend some strings. we just need to something like invalid_email

Copy link
Member

Choose a reason for hiding this comment

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

The prefix is like a namespace to prevent potential conflict with other messages in an application.
I didn't say we need invalid suffix for string rules.

Copy link
Contributor Author

@mehran-prs mehran-prs Jan 25, 2020

Choose a reason for hiding this comment

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

You right, but I think namespacing can be handled by translators or any other function that want to use error codes.

Copy link
Member

Choose a reason for hiding this comment

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

How will a translator do namespacing?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Translator gets the translation key(code prop in Error), so it can add every prefix that wants.
something like this:

// user's translator pass error's code and params to translate function:
func translate(code string, params map[string]interface{})string {
    // User decide to has "v_" as vaildation error codes prefix.
    code:=fmt.Sprintf("v_%s",code)
   // Translate...
}

length.go Outdated
@@ -42,13 +26,15 @@ func Length(min, max int) LengthRule {
func RuneLength(min, max int) LengthRule {
r := Length(min, max)
r.rune = true
return r

return r.detectMessage()
Copy link
Member

Choose a reason for hiding this comment

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

Some code refactoring is needed to make it more efficient - currently detectMessage() is called twice.
See the comment in detectMessage()

length.go Outdated
return nil
}

func (v LengthRule) detectMessage() LengthRule {
Copy link
Member

Choose a reason for hiding this comment

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

I think func buildLengthRuleError(min, max int) Error can make the dependency of the code more explicit and easier to test.

Copy link
Contributor Author

@mehran-prs mehran-prs Jan 24, 2020

Choose a reason for hiding this comment

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

Yea

error.go Outdated
}

// SetParams set the error's params.
func (e Error) SetParams(params map[string]interface{}) Error {
Copy link
Member

Choose a reason for hiding this comment

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

Can we also add AddParam(name string, value interface{})? This will make setting a single parameter (hopefully a main use scenario) easier.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yea, Sure

not_nil.go Outdated
// NotNil is a validation rule that checks if a value is not nil.
// NotNil only handles types including interface, pointer, slice, and map.
// All other types are considered valid.
var NotNil = notNilRule{message: "is required"}
var NotNil = notNilRule{err: NewError("validation_not_nil_invalid", "is required")}
Copy link
Member

Choose a reason for hiding this comment

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

validation_not_nil_required

Copy link
Contributor Author

Choose a reason for hiding this comment

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

How about something like is_blank or required?

Copy link
Member

Choose a reason for hiding this comment

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

We already have validation_required. is_blank is not very clear.

required.go Outdated
// Required is a validation rule that checks if a value is not empty.
// A value is considered not empty if
// - integer, float: not zero
// - bool: true
// - string, array, slice, map: len() > 0
// - interface, pointer: not nil and the referenced value is not empty
// - any other types
var Required = requiredRule{message: "cannot be blank", skipNil: false}
var Required = requiredRule{err: NewError("validation_required_is_blank", "cannot be blank"), skipNil: false}
Copy link
Member

Choose a reason for hiding this comment

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

validation_required

required.go Outdated

// NilOrNotEmpty checks if a value is a nil pointer or a value that is not empty.
// NilOrNotEmpty differs from Required in that it treats a nil pointer as valid.
var NilOrNotEmpty = requiredRule{message: "cannot be blank", skipNil: true}
var NilOrNotEmpty = requiredRule{err: NewError("validation_nil_or_not_empty_is_blank", "cannot be blank"), skipNil: true}
Copy link
Member

Choose a reason for hiding this comment

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

validation_nil_or_not_empty_required

v.err = v.err.SetMessage(message)
return v
}

Copy link
Member

Choose a reason for hiding this comment

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

How about adding ErrorStruct() *Error and drop the following two methods?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think we should keep those two functions and add this function also.

Copy link
Member

Choose a reason for hiding this comment

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

Why keeping the two functions? If we add the new one, developers can first get Error and then call the methods of Error to set Code, etc.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

because if we keep those functions, developers need to one call for setting custom error message, by defining this function need to two call, if we define two functions one for setting message, another for getting error, developers can simply set message just like old version, and also can get error if want to set params or any other thing.

I think we should add ErrorStruct() *Error to all built-in validation rules, so developers can set custom error codes on each validation.

Copy link
Member

Choose a reason for hiding this comment

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

I thought it more about the overall translation support. Below I'm using ThresholdRule as an example to explain the new changes needed since ThresholdRule is one of the most complex rules for the translation support:

var (
    // predefined errors that can be customized globally to affect all instances of the same type of the rule
    // used in both i18n and non-i18n scenarios
    ErrThresholdLessThanRequired = NewError("validation_threshold_less_than_required", "must be less than {{.threshold}}")
    ErrThresholdNoGreaterThanRequired = NewError("validation_threshold_no_greater_than_required", "must be no greater than {{.threshold}}")
    ErrThresholdGreaterThanRequired = NewError("validation_threshold_greater_than_required", "must be greater than {{.threshold}}")
    ErrThresholdNoLessThanRequired = NewError("validation_threshold_no_less_than_required", "must be no less than {{.threshold}}")
)

// used in non-i18n scenarios to customize an error message for a single instance of the rule
func (r ThresholdRule) Error(message string) ThresholdRule {...}

// used in i18n scenarios to customize the error for a single instance of the rule
func (r ThresholdRule) ErrorObject(err error) ThresholdRule {...}

StringRule is a bit special because it is a base class. Here's the change I'm thinking about:

// used in non-i18n scenarios
func NewStringRule(validator stringValidator, message string) StringRule {...}

// used in i18n scenarios
func NewStringRuleWithError(validator stringValidator, err error) StringRule {...}

// This error declaration allows global customization of the email rule error
// String.ErrorObject() allows error customization of a single rule instance
ErrEmail = NewError("validation_is_email", "must be a valid email address")
Email = validation.NewStringRuleWithError(govalidator.IsEmail, ErrEmail)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

How about adding ErrorStruct() *Error and drop the following two methods?

We can not use ErrorStruct() *Error because:

  • If defining func (r *MyRule) ErrorStruct() *Error, this error affects all of the other uses of MyRule.
  • If defining func (r MyRule) ErrorStruct() *Error, this error do not has any affect on our rule.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, Predefining errors is very good, I change the source like what you said.

Refactor Error definition in rules.
@mehran-prs mehran-prs requested a review from qiangxue January 27, 2020 20:31
Copy link
Member

@qiangxue qiangxue left a comment

Choose a reason for hiding this comment

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

Thank you for being patient and taking the effort of making all these changes. I think we are very close to the final version.

UPGRADE.md Outdated
@@ -1,5 +1,21 @@
# Upgrade Instructions

## Upgrade from 3.x to 4.x
* If you want to support i18n for your defined string rules, instead of `NewStringRule` use the `NewStringRuleWithError`
Copy link
Member

Choose a reason for hiding this comment

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

In the upgrade instructions, we should only describe the BC-breaking changes and the instructions on how to upgrade. Here we should list the messages which contain parameter placeholders because they are changed to named ones.

The addition of NewStringRuleWithError is part of the new I18N feature, which should be included in the release description.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

You're right, I change it.

date.go Outdated
// 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 data is out of range")
Copy link
Member

Choose a reason for hiding this comment

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

Can you change it to the date is out of range?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sure.

}

// 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 {
Copy link
Member

Choose a reason for hiding this comment

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

The parameter type should be error instead of Error. The former would allow users to pass in other kinds of error implementation which may support more advanced I18N features (such as plural form).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yea, I agree with you, but if I change it, in all other methods of rules that set error properties we must assert that if it is Error then set its value, otherwise return an error. everything can be complicated unless we change the Error type to be an interface with our needed methods.

Copy link
Member

Choose a reason for hiding this comment

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

Yes, we need an interface here:

type Error interface {
    Error() string
    Code() string
    Message() string
    SetMessage(string)
    Params() map[string]interface{}
    SetParams(map[string]interface{})
}

type ErrorObject struct {...} // the current Error struct is renamed as ErrorObject or something better

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@qiangxue I think Error interface should be something like this:

type Error interface {
    Error() string
    Code() string
    SetCode(string) Error
    Message() string
    SetMessage(string) Error
    Params() map[string]interface{}
    SetParams(map[string]interface{}) Error
    AddParam(string,string) Error
}

this is because if we do not return Error and get a pointer in seters, so in NewError we have to return a pointer to the ErrorObject, so all predefined errors will be pointers.

What do you think?

Copy link
Member

Choose a reason for hiding this comment

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

I'd like to keep the interface minimal. That's why SetCode and AddParams are not in my design, because they are not used/required anywhere.
Yes, NewError should return a pointer.

Copy link
Contributor Author

@mehran-prs mehran-prs Jan 28, 2020

Choose a reason for hiding this comment

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

But if return a pointer, changes to predefined errors affect base error, e.g

myErr=validation.ErrEmail
err.SetParams(map[string]interface{}{"user":"Mehran"})

this line set params on the base error also. we should get a copy of it.

Copy link
Member

Choose a reason for hiding this comment

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

You're right. We should add a Clone() method to ErrorObject (not to the Error interface).

Copy link
Contributor Author

@mehran-prs mehran-prs Jan 29, 2020

Choose a reason for hiding this comment

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

We are assigning the Error interface to each validation rule's err property, so on each validation rule creation, we must first check if our predefined error object is cloneable (since our users can change predefined errors and do not implement Clone method) then clone it, otherwise, simply assign it. our rule initialization is finally something like this:

var ErrDateInvalid = NewError("validation_date_invalid", "must be a valid date")

func Date(layout string) DateRule {
	return DateRule{
		layout:   layout,
		err:      TryToClone(ErrDateInvalid),
		rangeErr: TryToClone(ErrDateOutOfRange),
	}
}

type CloneableErrorObject interface {
	Clone() Error
}

func TryToClone(err Error) Error {
	if cloneable, ok := err.(CloneableErrorObject); ok {
		return cloneable.Clone()
	}

	return err
}

I think we should remove all of these complexities and just change the ErrorObject setters signature to get just copy, not a pointer, e.g:

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

What do you think @qiangxue?

Copy link
Member

Choose a reason for hiding this comment

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

You're right. Let's modify the signature SetCode and SetParams so that they return Error. Thanks!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yep, you're welcome :)


// AddParam add parameter to the error's parameters.
func (e *Error) AddParam(name string, value interface{}) {
e.params[name] = value
Copy link
Member

Choose a reason for hiding this comment

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

Need to check if params is initialized. Otherwise, it will panic.

Copy link
Contributor Author

@mehran-prs mehran-prs Jan 28, 2020

Choose a reason for hiding this comment

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

Yea, I fix it.

rangeMessage string
layout string
min, max time.Time
err, rangeErr Error
Copy link
Member

Choose a reason for hiding this comment

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

The type should be error, and ErrorObject() should also use error as the parameter type. The reason for this is to support user-defined error types which may allow more advanced i18n features (e.g. plural forms) or other features.

Note that because of this change, Error() method of the rule needs to be implemented differently.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, see my comment on ErrorObject please.

match.go Outdated
"regexp"
)

// ErrValidationMatchInvalid is the error that returns in case of invalid format.
var ErrValidationMatchInvalid = NewError("validation_match_invalid", "must be in a valid format")
Copy link
Member

Choose a reason for hiding this comment

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

Could you rename it to ErrMatchInvalid so that it's consistent with other places?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sure.

string.go Outdated
// Code sets the rule's translation code (translation key).
func (r StringRule) Code(code string) StringRule {
r.err.SetCode(code)
return r
}
Copy link
Member

Choose a reason for hiding this comment

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

I think we should still remove the above two methods ErrParams and Code. If a user wants to set them (which is very uncommon), he can still achieve it via ErrorObject().

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah they can achieve it via ErrorObject(), but their code change from e.g :

validation.Email.Code("v_invalid_email")

to

errEmail:=validation.ErrEmail
errEmail.SetCode("v_invalid_email")
validation.Email.ErrorObject(errEmail)

Are you ok with it?

Copy link
Member

Choose a reason for hiding this comment

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

I can hardly think of any reason that a user would want to change error code.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

OK, so I remove them.

@mehran-prs mehran-prs requested a review from qiangxue January 30, 2020 20:10
@qiangxue qiangxue merged commit f8d2e47 into go-ozzo:master Jan 30, 2020
@qiangxue
Copy link
Member

Great job! Thank you very much for your contribution!

@mehran-prs
Copy link
Contributor Author

you're welcome Qiangxue :)

ltns35 pushed a commit to ltns35/ozzo-validation that referenced this pull request Jun 5, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants