From f173f776d6b600c48b2e4175e1ed29043fcc5353 Mon Sep 17 00:00:00 2001 From: Igor Lazarev Date: Wed, 28 Jul 2021 20:34:04 +0300 Subject: [PATCH 1/4] context --- README.md | 27 ++++--- example_context_with_recursion_test.go | 13 ++- example_custom_argument_constraint_test.go | 3 +- example_custom_constraint_test.go | 2 + example_custom_service_constraint_test.go | 6 +- example_http_handler_test.go | 6 +- example_partial_entity_validation_test.go | 8 +- example_test.go | 94 ++++++++++++++-------- example_validatable_slice_test.go | 6 +- example_validatable_struct_test.go | 9 ++- it/example_test.go | 53 ++++++------ test/benchmark_test.go | 10 ++- test/constraints_errors_test.go | 6 +- test/constraints_test.go | 21 ++--- test/errors_test.go | 3 +- test/mocks_test.go | 17 ++-- test/path_test.go | 12 +-- test/scope_test.go | 18 ----- test/translations_test.go | 19 +++-- test/validatable_test.go | 12 ++- test/validate_arguments_test.go | 5 +- test/validate_control_constraints_test.go | 16 +++- test/validate_each_test.go | 7 +- test/validate_iterable_test.go | 9 ++- test/validate_value_test.go | 3 +- test/validator_option_test.go | 5 +- test/validator_store_test.go | 5 +- validation.go | 8 +- validator.go | 65 ++++++--------- validator/validator.go | 59 ++++++-------- 30 files changed, 281 insertions(+), 246 deletions(-) delete mode 100644 test/scope_test.go diff --git a/README.md b/README.md index 6d7ee5d..4c08dba 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,7 @@ The validation process is built around functional options and passing values by ```golang s := "" -err := validator.Validate(validation.String(&s, it.IsNotBlank())) +err := validator.Validate(context.Background(), validation.String(&s, it.IsNotBlank())) violations := err.(validation.ViolationList) for _, violation := range violations { @@ -137,6 +137,7 @@ You can pass a property name or an array index via `validation.PropertyName()` a s := "" err := validator.Validate( + context.Background(), validation.String( &s, validation.PropertyName("properties"), @@ -161,7 +162,7 @@ err := validator. AtProperty("properties"). AtIndex(1). AtProperty("tag"). - Validate(validation.String(&s, it.IsNotBlank())) + Validate(context.Background(), validation.String(&s, it.IsNotBlank())) violation := err.(validation.ViolationList)[0] fmt.Println("property path:", violation.GetPropertyPath().Format()) @@ -187,6 +188,7 @@ For a better experience with struct validation, you can use shorthand versions o s := "" err := validator.Validate( + context.Background(), validation.StringProperty("property", &s, it.IsNotBlank()), ) @@ -207,6 +209,7 @@ document := Document{ } err := validator.Validate( + context.Background(), validation.StringProperty("title", &document.Title, it.IsNotBlank()), validation.CountableProperty("keywords", len(document.Keywords), it.HasCountBetween(5, 10)), validation.StringsProperty("keywords", document.Keywords, it.HasUniqueValues()), @@ -234,8 +237,9 @@ type Product struct { Components []Component } -func (p Product) Validate(validator *validation.Validator) error { +func (p Product) Validate(ctx context.Context, validator *validation.Validator) error { return validator.Validate( + ctx, validation.StringProperty("name", &p.Name, it.IsNotBlank()), validation.CountableProperty("tags", len(p.Tags), it.HasMinCount(5)), validation.StringsProperty("tags", p.Tags, it.HasUniqueValues()), @@ -251,8 +255,9 @@ type Component struct { Tags []string } -func (c Component) Validate(validator *validation.Validator) error { +func (c Component) Validate(ctx context.Context, validator *validation.Validator) error { return validator.Validate( + ctx, validation.StringProperty("name", &c.Name, it.IsNotBlank()), validation.CountableProperty("tags", len(c.Tags), it.HasMinCount(1)), ) @@ -270,7 +275,7 @@ func main() { }, } - err := validator.ValidateValidatable(p) + err := validator.ValidateValidatable(context.Background(), p) violations := err.(validation.ViolationList) for _, violation := range violations { @@ -292,6 +297,7 @@ You can use the `When()` method on any of the built-in constraints to execute co ```golang err := validator.Validate( + context.Background(), validation.StringProperty("text", ¬e.Text, it.IsNotBlank().When(note.IsPublic)), ) @@ -364,7 +370,7 @@ validator, _ := validation.NewValidator( ) s := "" -err := validator.ValidateString(&s, it.IsNotBlank()) +err := validator.ValidateString(context.Background(), &s, it.IsNotBlank()) violations := err.(validation.ViolationList) for _, violation := range violations { @@ -383,6 +389,7 @@ validator, _ := validation.NewValidator( s := "" err := validator.Validate( + context.Background(), validation.Language(language.Russian), validation.String(&s, it.IsNotBlank()), ) @@ -395,7 +402,7 @@ for _, violation := range violations { // violation: Значение не должно быть пустым. ``` -The last way is to pass language via context. It is provided by the `github.com/muonsoft/language` package and can be useful in combination with [language middleware](https://github.com/muonsoft/language/blob/main/middleware.go). You can pass the context by using the `validation.Context()` argument or by creating a scoped validator with the `validator.WithContext()` method. +The last way is to pass language via context. It is provided by the `github.com/muonsoft/language` package and can be useful in combination with [language middleware](https://github.com/muonsoft/language/blob/main/middleware.go). ```golang // import "github.com/muonsoft/language" @@ -404,10 +411,9 @@ validator, _ := validation.NewValidator( validation.Translations(russian.Messages), ) ctx := language.WithContext(context.Background(), language.Russian) -validator = validator.WithContext(ctx) s := "" -err := validator.ValidateString(&s, it.IsNotBlank()) +err := validator.ValidateString(ctx, &s, it.IsNotBlank()) violations := err.(validation.ViolationList) for _, violation := range violations { @@ -426,7 +432,7 @@ You may customize the violation message on any of the built-in constraints by ca ```golang s := "" -err := validator.ValidateString(&s, it.IsNotBlank().Message("this value is required")) +err := validator.ValidateString(context.Background(), &s, it.IsNotBlank().Message("this value is required")) violations := err.(validation.ViolationList) for _, violation := range violations { @@ -453,6 +459,7 @@ validator, _ := validation.NewValidator( var tags []string err := validator.ValidateIterable( + context.Background(), tags, validation.Language(language.Russian), it.HasMinCount(1).MinMessage(customMessage), diff --git a/example_context_with_recursion_test.go b/example_context_with_recursion_test.go index 9f113e1..69bf628 100644 --- a/example_context_with_recursion_test.go +++ b/example_context_with_recursion_test.go @@ -79,11 +79,10 @@ type recursionKey string const nestingLevelKey recursionKey = "nestingLevel" -func (p Property) Validate(validator *validation.Validator) error { - // Incrementing nesting level in context with special function. - ctx := contextWithNextNestingLevel(validator.Context()) - - return validator.WithContext(ctx).Validate( +func (p Property) Validate(ctx context.Context, validator *validation.Validator) error { + return validator.Validate( + // Incrementing nesting level in context with special function. + contextWithNextNestingLevel(ctx), // Executing validation for maximum nesting level of properties. PropertyArgument(&p, ItIsNotDeeperThan(3)), validation.StringProperty("name", &p.Name, it.IsNotBlank()), @@ -102,7 +101,7 @@ func contextWithNextNestingLevel(ctx context.Context) context.Context { return context.WithValue(ctx, nestingLevelKey, level+1) } -func ExampleValidator_Context_usingContextWithRecursion() { +func ExampleValidator_Validate_usingContextWithRecursion() { properties := []Property{ { Name: "top", @@ -123,7 +122,7 @@ func ExampleValidator_Context_usingContextWithRecursion() { }, } - err := validator.ValidateIterable(properties) + err := validator.Validate(context.Background(), validation.Iterable(properties)) fmt.Println(err) // Output: diff --git a/example_custom_argument_constraint_test.go b/example_custom_argument_constraint_test.go index c73e42c..cf9e957 100644 --- a/example_custom_argument_constraint_test.go +++ b/example_custom_argument_constraint_test.go @@ -99,11 +99,10 @@ func ExampleNewArgument_customArgumentConstraintValidator() { isEntityUnique := &UniqueBrandConstraint{brands: repository} brand := Brand{Name: "Apple"} - ctx := context.WithValue(context.Background(), exampleKey, "value") err := validator.Validate( // you can pass here the context value to the validation scope - validation.Context(ctx), + context.WithValue(context.Background(), exampleKey, "value"), BrandArgument(&brand, it.IsNotBlank(), isEntityUnique), ) diff --git a/example_custom_constraint_test.go b/example_custom_constraint_test.go index a44a029..0f4c14c 100644 --- a/example_custom_constraint_test.go +++ b/example_custom_constraint_test.go @@ -1,6 +1,7 @@ package validation_test import ( + "context" "fmt" "regexp" @@ -46,6 +47,7 @@ func ExampleValidator_Validate_customConstraint() { s := "alpha" err := validator.Validate( + context.Background(), validation.String(&s, it.IsNotBlank(), IsNumeric()), ) diff --git a/example_custom_service_constraint_test.go b/example_custom_service_constraint_test.go index 1020007..c594172 100644 --- a/example_custom_service_constraint_test.go +++ b/example_custom_service_constraint_test.go @@ -82,8 +82,9 @@ type StockItem struct { Tags []string } -func (s StockItem) Validate(validator *validation.Validator) error { +func (s StockItem) Validate(ctx context.Context, validator *validation.Validator) error { return validator.Validate( + ctx, validation.StringProperty("name", &s.Name, it.IsNotBlank(), it.HasMaxLength(20)), validation.EachStringProperty("tags", s.Tags, validator.ValidateBy("isTagExists")), ) @@ -106,11 +107,10 @@ func ExampleValidator_ValidateBy_customServiceConstraint() { Name: "War and peace", Tags: []string{"book", "camera"}, } - ctx := context.WithValue(context.Background(), exampleKey, "value") err = validator.Validate( // you can pass here the context value to the validation scope - validation.Context(ctx), + context.WithValue(context.Background(), exampleKey, "value"), validation.Valid(item), ) diff --git a/example_http_handler_test.go b/example_http_handler_test.go index 04f114c..9396e3d 100644 --- a/example_http_handler_test.go +++ b/example_http_handler_test.go @@ -1,6 +1,7 @@ package validation_test import ( + "context" "encoding/json" "fmt" "log" @@ -20,8 +21,9 @@ type Book struct { Keywords []string `json:"keywords"` } -func (b Book) Validate(validator *validation.Validator) error { +func (b Book) Validate(ctx context.Context, validator *validation.Validator) error { return validator.Validate( + ctx, validation.StringProperty("title", &b.Title, it.IsNotBlank()), validation.StringProperty("author", &b.Author, it.IsNotBlank()), validation.CountableProperty("keywords", len(b.Keywords), it.HasCountBetween(1, 10)), @@ -44,7 +46,7 @@ func HandleBooks(writer http.ResponseWriter, request *http.Request) { return } - err = validator.WithContext(request.Context()).ValidateValidatable(book) + err = validator.ValidateValidatable(request.Context(), book) if err != nil { violations, ok := validation.UnwrapViolationList(err) if ok { diff --git a/example_partial_entity_validation_test.go b/example_partial_entity_validation_test.go index 9374baa..f589137 100644 --- a/example_partial_entity_validation_test.go +++ b/example_partial_entity_validation_test.go @@ -2,6 +2,7 @@ package validation_test import ( "bytes" + "context" "fmt" "path/filepath" "strings" @@ -19,8 +20,9 @@ type File struct { // This validation will always check that file is valid. // Partial validation will be applied by AllowedFileExtensionConstraint // and AllowedFileSizeConstraint. -func (f File) Validate(validator *validation.Validator) error { +func (f File) Validate(ctx context.Context, validator *validation.Validator) error { return validator.Validate( + ctx, validation.StringProperty("name", &f.Name, it.HasLengthBetween(5, 50)), ) } @@ -79,6 +81,7 @@ func (c AllowedFileExtensionConstraint) ValidateFile(file *File, scope validatio extension := strings.ReplaceAll(filepath.Ext(file.Name), ".", "") return scope.Validator().AtProperty("name").ValidateString( + scope.Context(), &extension, it.IsOneOfStrings(c.extensions...).Message("Not allowed extension. Must be one of: {{ choices }}."), ) @@ -111,6 +114,7 @@ func (c AllowedFileSizeConstraint) ValidateFile(file *File, scope validation.Sco size := len(file.Data) return scope.Validator().ValidateNumber( + scope.Context(), size, it.IsGreaterThanInteger(c.minSize).Message("File size is too small."), it.IsLessThanInteger(c.maxSize).Message("File size is too large."), @@ -144,6 +148,7 @@ func ExampleScope_Validator() { switch request.Section { case "avatars": err := validator.Validate( + context.Background(), // common validation of validatable validation.Valid(request.File), // specific validation for file storage section @@ -152,6 +157,7 @@ func ExampleScope_Validator() { fmt.Println(err) case "documents": err := validator.Validate( + context.Background(), // common validation of validatable validation.Valid(request.File), // specific validation for file storage section diff --git a/example_test.go b/example_test.go index 9addf5a..6acd704 100644 --- a/example_test.go +++ b/example_test.go @@ -19,7 +19,7 @@ import ( func ExampleValue() { v := "" - err := validator.Validate(validation.Value(v, it.IsNotBlank())) + err := validator.Validate(context.Background(), validation.Value(v, it.IsNotBlank())) fmt.Println(err) // Output: // violation: This value should not be blank. @@ -28,6 +28,7 @@ func ExampleValue() { func ExamplePropertyValue() { v := Book{Title: ""} err := validator.Validate( + context.Background(), validation.PropertyValue("title", v.Title, it.IsNotBlank()), ) fmt.Println(err) @@ -37,7 +38,7 @@ func ExamplePropertyValue() { func ExampleBool() { v := false - err := validator.Validate(validation.Bool(&v, it.IsTrue())) + err := validator.Validate(context.Background(), validation.Bool(&v, it.IsTrue())) fmt.Println(err) // Output: // violation: This value should be true. @@ -50,6 +51,7 @@ func ExampleBoolProperty() { IsPublished: false, } err := validator.Validate( + context.Background(), validation.BoolProperty("isPublished", &v.IsPublished, it.IsTrue()), ) fmt.Println(err) @@ -59,7 +61,10 @@ func ExampleBoolProperty() { func ExampleNumber() { v := 5 - err := validator.Validate(validation.Number(&v, it.IsGreaterThanInteger(5))) + err := validator.Validate( + context.Background(), + validation.Number(&v, it.IsGreaterThanInteger(5)), + ) fmt.Println(err) // Output: // violation: This value should be greater than 5. @@ -72,6 +77,7 @@ func ExampleNumberProperty() { Count: 5, } err := validator.Validate( + context.Background(), validation.NumberProperty("count", &v.Count, it.IsGreaterThanInteger(5)), ) fmt.Println(err) @@ -81,7 +87,10 @@ func ExampleNumberProperty() { func ExampleString() { v := "" - err := validator.Validate(validation.String(&v, it.IsNotBlank())) + err := validator.Validate( + context.Background(), + validation.String(&v, it.IsNotBlank()), + ) fmt.Println(err) // Output: // violation: This value should not be blank. @@ -90,6 +99,7 @@ func ExampleString() { func ExampleStringProperty() { v := Book{Title: ""} err := validator.Validate( + context.Background(), validation.StringProperty("title", &v.Title, it.IsNotBlank()), ) fmt.Println(err) @@ -100,6 +110,7 @@ func ExampleStringProperty() { func ExampleStrings() { v := []string{"foo", "bar", "baz", "foo"} err := validator.Validate( + context.Background(), validation.Strings(v, it.HasUniqueValues()), ) fmt.Println(err) @@ -110,6 +121,7 @@ func ExampleStrings() { func ExampleStringsProperty() { v := Book{Keywords: []string{"foo", "bar", "baz", "foo"}} err := validator.Validate( + context.Background(), validation.StringsProperty("keywords", v.Keywords, it.HasUniqueValues()), ) fmt.Println(err) @@ -119,7 +131,10 @@ func ExampleStringsProperty() { func ExampleIterable() { v := make([]string, 0) - err := validator.Validate(validation.Iterable(v, it.IsNotBlank())) + err := validator.Validate( + context.Background(), + validation.Iterable(v, it.IsNotBlank()), + ) fmt.Println(err) // Output: // violation: This value should not be blank. @@ -128,6 +143,7 @@ func ExampleIterable() { func ExampleIterableProperty() { v := Product{Tags: []string{}} err := validator.Validate( + context.Background(), validation.IterableProperty("tags", v.Tags, it.IsNotBlank()), ) fmt.Println(err) @@ -137,7 +153,10 @@ func ExampleIterableProperty() { func ExampleCountable() { s := []string{"a", "b"} - err := validator.Validate(validation.Countable(len(s), it.HasMinCount(3))) + err := validator.Validate( + context.Background(), + validation.Countable(len(s), it.HasMinCount(3)), + ) fmt.Println(err) // Output: // violation: This collection should contain 3 elements or more. @@ -146,6 +165,7 @@ func ExampleCountable() { func ExampleCountableProperty() { v := Product{Tags: []string{"a", "b"}} err := validator.Validate( + context.Background(), validation.CountableProperty("tags", len(v.Tags), it.HasMinCount(3)), ) fmt.Println(err) @@ -157,6 +177,7 @@ func ExampleTime() { t := time.Now() compared, _ := time.Parse(time.RFC3339, "2006-01-02T15:00:00Z") err := validator.Validate( + context.Background(), validation.Time(&t, it.IsEarlierThan(compared)), ) fmt.Println(err) @@ -172,6 +193,7 @@ func ExampleTimeProperty() { } compared, _ := time.Parse(time.RFC3339, "2006-01-02T15:00:00Z") err := validator.Validate( + context.Background(), validation.TimeProperty("createdAt", &v.CreatedAt, it.IsEarlierThan(compared)), ) fmt.Println(err) @@ -181,7 +203,10 @@ func ExampleTimeProperty() { func ExampleEach() { v := []string{""} - err := validator.Validate(validation.Each(v, it.IsNotBlank())) + err := validator.Validate( + context.Background(), + validation.Each(v, it.IsNotBlank()), + ) fmt.Println(err) // Output: // violation at '[0]': This value should not be blank. @@ -190,6 +215,7 @@ func ExampleEach() { func ExampleEachProperty() { v := Product{Tags: []string{""}} err := validator.Validate( + context.Background(), validation.EachProperty("tags", v.Tags, it.IsNotBlank()), ) fmt.Println(err) @@ -199,7 +225,10 @@ func ExampleEachProperty() { func ExampleEachString() { v := []string{""} - err := validator.Validate(validation.EachString(v, it.IsNotBlank())) + err := validator.Validate( + context.Background(), + validation.EachString(v, it.IsNotBlank()), + ) fmt.Println(err) // Output: // violation at '[0]': This value should not be blank. @@ -208,6 +237,7 @@ func ExampleEachString() { func ExampleEachStringProperty() { v := Product{Tags: []string{""}} err := validator.Validate( + context.Background(), validation.EachStringProperty("tags", v.Tags, it.IsNotBlank()), ) fmt.Println(err) @@ -227,7 +257,7 @@ func ExampleNewCustomStringConstraint() { ) s := "foo" - err := validator.ValidateString(&s, constraint) + err := validator.ValidateString(context.Background(), &s, constraint) fmt.Println(err) // Output: @@ -249,6 +279,7 @@ func ExampleWhen() { } err := validator.Validate( + context.Background(), validation.StringProperty( "cardType", &payment.CardType, @@ -272,6 +303,7 @@ func ExampleWhen() { func ExampleConditionalConstraint_Then() { v := "foo" err := validator.ValidateString( + context.Background(), &v, validation.When(true). Then( @@ -286,6 +318,7 @@ func ExampleConditionalConstraint_Then() { func ExampleConditionalConstraint_Else() { v := "123" err := validator.ValidateString( + context.Background(), &v, validation.When(false). Then( @@ -304,6 +337,7 @@ func ExampleSequentially() { title := "bar" err := validator.ValidateString( + context.Background(), &title, validation.Sequentially( it.IsBlank(), @@ -320,6 +354,7 @@ func ExampleAtLeastOneOf() { title := "bar" err := validator.ValidateString( + context.Background(), &title, validation.AtLeastOneOf( it.IsBlank(), @@ -342,6 +377,7 @@ func ExampleCompound() { isEmail := validation.Compound(it.IsEmail(), it.HasLengthBetween(5, 200)) err := validator.ValidateString( + context.Background(), &title, isEmail, ) @@ -363,7 +399,7 @@ func ExampleValidator_Validate_basicValidation() { if err != nil { log.Fatal(err) } - err = validator.Validate(validation.String(&s, it.IsNotBlank())) + err = validator.Validate(context.Background(), validation.String(&s, it.IsNotBlank())) fmt.Println(err) // Output: @@ -373,7 +409,7 @@ func ExampleValidator_Validate_basicValidation() { func ExampleValidator_Validate_singletonValidator() { s := "" - err := validator.Validate(validation.String(&s, it.IsNotBlank())) + err := validator.Validate(context.Background(), validation.String(&s, it.IsNotBlank())) fmt.Println(err) // Output: @@ -383,7 +419,7 @@ func ExampleValidator_Validate_singletonValidator() { func ExampleValidator_ValidateString_shorthandAlias() { s := "" - err := validator.ValidateString(&s, it.IsNotBlank()) + err := validator.ValidateString(context.Background(), &s, it.IsNotBlank()) fmt.Println(err) // Output: @@ -400,6 +436,7 @@ func ExampleValidator_Validate_basicStructValidation() { } err := validator.Validate( + context.Background(), validation.StringProperty("title", &document.Title, it.IsNotBlank()), validation.CountableProperty("keywords", len(document.Keywords), it.HasCountBetween(5, 10)), validation.StringsProperty("keywords", document.Keywords, it.HasUniqueValues()), @@ -430,6 +467,7 @@ func ExampleValidator_Validate_conditionalValidationOnConstraint() { for i, note := range notes { err := validator.Validate( + context.Background(), validation.StringProperty("name", ¬e.Title, it.IsNotBlank()), validation.StringProperty("text", ¬e.Text, it.IsNotBlank().When(note.IsPublic)), ) @@ -448,6 +486,7 @@ func ExampleValidator_Validate_passingPropertyPathViaOptions() { s := "" err := validator.Validate( + context.Background(), validation.String( &s, validation.PropertyName("properties"), @@ -470,7 +509,7 @@ func ExampleValidator_Validate_propertyPathWithScopedValidator() { AtProperty("properties"). AtIndex(1). AtProperty("tag"). - Validate(validation.String(&s, it.IsNotBlank())) + Validate(context.Background(), validation.String(&s, it.IsNotBlank())) violation := err.(*validation.ViolationList).First() fmt.Println("property path:", violation.PropertyPath().String()) @@ -482,6 +521,7 @@ func ExampleValidator_Validate_propertyPathBySpecialArgument() { s := "" err := validator.Validate( + context.Background(), // this is an alias for // validation.String(&s, validation.PropertyName("property"), it.IsNotBlank()), validation.StringProperty("property", &s, it.IsNotBlank()), @@ -497,6 +537,7 @@ func ExampleValidator_AtProperty() { book := &Book{Title: ""} err := validator.AtProperty("book").Validate( + context.Background(), validation.StringProperty("title", &book.Title, it.IsNotBlank()), ) @@ -510,6 +551,7 @@ func ExampleValidator_AtIndex() { books := []Book{{Title: ""}} err := validator.AtIndex(0).Validate( + context.Background(), validation.StringProperty("title", &books[0].Title, it.IsNotBlank()), ) @@ -529,7 +571,7 @@ func ExampleValidator_Validate_translationsByDefaultLanguage() { } s := "" - err = validator.ValidateString(&s, it.IsNotBlank()) + err = validator.ValidateString(context.Background(), &s, it.IsNotBlank()) fmt.Println(err) // Output: @@ -546,6 +588,7 @@ func ExampleValidator_Validate_translationsByArgument() { s := "" err = validator.Validate( + context.Background(), validation.Language(language.Russian), validation.String(&s, it.IsNotBlank()), ) @@ -566,7 +609,7 @@ func ExampleValidator_Validate_translationsByContextArgument() { s := "" ctx := language.WithContext(context.Background(), language.Russian) err = validator.Validate( - validation.Context(ctx), + ctx, validation.String(&s, it.IsNotBlank()), ) @@ -575,28 +618,10 @@ func ExampleValidator_Validate_translationsByContextArgument() { // violation: Значение не должно быть пустым. } -func ExampleValidator_Validate_translationsByContextValidator() { - validator, err := validation.NewValidator( - validation.Translations(russian.Messages), - ) - if err != nil { - log.Fatal(err) - } - ctx := language.WithContext(context.Background(), language.Russian) - validator = validator.WithContext(ctx) - - s := "" - err = validator.ValidateString(&s, it.IsNotBlank()) - - fmt.Println(err) - // Output: - // violation: Значение не должно быть пустым. -} - func ExampleValidator_Validate_customizingErrorMessage() { s := "" - err := validator.ValidateString(&s, it.IsNotBlank().Message("this value is required")) + err := validator.ValidateString(context.Background(), &s, it.IsNotBlank().Message("this value is required")) fmt.Println(err) // Output: @@ -621,6 +646,7 @@ func ExampleValidator_Validate_translationForCustomMessage() { var tags []string err = validator.Validate( + context.Background(), validation.Language(language.Russian), validation.Iterable(tags, it.HasMinCount(1).MinMessage(customMessage)), ) diff --git a/example_validatable_slice_test.go b/example_validatable_slice_test.go index 7609a9c..a8392f3 100644 --- a/example_validatable_slice_test.go +++ b/example_validatable_slice_test.go @@ -1,6 +1,7 @@ package validation_test import ( + "context" "fmt" "github.com/muonsoft/validation" @@ -15,11 +16,12 @@ type Company struct { type Companies []Company -func (companies Companies) Validate(validator *validation.Validator) error { +func (companies Companies) Validate(ctx context.Context, validator *validation.Validator) error { violations := validation.ViolationList{} for i, company := range companies { err := validator.AtIndex(i).Validate( + ctx, validation.StringProperty("name", &company.Name, it.IsNotBlank()), validation.StringProperty("address", &company.Address, it.IsNotBlank(), it.HasMinLength(3)), ) @@ -42,7 +44,7 @@ func ExampleValidator_ValidateValidatable_validatableSlice() { {"", "x"}, } - err := validator.ValidateValidatable(companies) + err := validator.ValidateValidatable(context.Background(), companies) if violations, ok := validation.UnwrapViolationList(err); ok { for violation := violations.First(); violation != nil; violation = violation.Next() { diff --git a/example_validatable_struct_test.go b/example_validatable_struct_test.go index 3a9309b..ff6cd35 100644 --- a/example_validatable_struct_test.go +++ b/example_validatable_struct_test.go @@ -1,6 +1,7 @@ package validation_test import ( + "context" "fmt" "github.com/muonsoft/validation" @@ -14,8 +15,9 @@ type Product struct { Components []Component } -func (p Product) Validate(validator *validation.Validator) error { +func (p Product) Validate(ctx context.Context, validator *validation.Validator) error { return validator.Validate( + ctx, validation.StringProperty("name", &p.Name, it.IsNotBlank()), validation.CountableProperty("tags", len(p.Tags), it.HasMinCount(5)), validation.StringsProperty("tags", p.Tags, it.HasUniqueValues()), @@ -31,8 +33,9 @@ type Component struct { Tags []string } -func (c Component) Validate(validator *validation.Validator) error { +func (c Component) Validate(ctx context.Context, validator *validation.Validator) error { return validator.Validate( + ctx, validation.StringProperty("name", &c.Name, it.IsNotBlank()), validation.CountableProperty("tags", len(c.Tags), it.HasMinCount(1)), ) @@ -50,7 +53,7 @@ func ExampleValidator_ValidateValidatable_validatableStruct() { }, } - err := validator.ValidateValidatable(p) + err := validator.ValidateValidatable(context.Background(), p) if violations, ok := validation.UnwrapViolationList(err); ok { for violation := violations.First(); violation != nil; violation = violation.Next() { diff --git a/it/example_test.go b/it/example_test.go index c525c77..ad78a9a 100644 --- a/it/example_test.go +++ b/it/example_test.go @@ -1,6 +1,7 @@ package it_test import ( + "context" "fmt" "net" @@ -10,7 +11,7 @@ import ( func ExampleHasUniqueValues() { v := []string{"foo", "bar", "baz", "foo"} - err := validator.ValidateStrings(v, it.HasUniqueValues()) + err := validator.ValidateStrings(context.Background(), v, it.HasUniqueValues()) fmt.Println(err) // Output: // violation: This collection should contain only unique elements. @@ -18,7 +19,7 @@ func ExampleHasUniqueValues() { func ExampleIsJSON_validJSON() { v := `{"valid": true}` - err := validator.ValidateString(&v, it.IsJSON()) + err := validator.ValidateString(context.Background(), &v, it.IsJSON()) fmt.Println(err) // Output: // @@ -26,7 +27,7 @@ func ExampleIsJSON_validJSON() { func ExampleIsJSON_invalidJSON() { v := `"invalid": true` - err := validator.ValidateString(&v, it.IsJSON()) + err := validator.ValidateString(context.Background(), &v, it.IsJSON()) fmt.Println(err) // Output: // violation: This value should be valid JSON. @@ -34,7 +35,7 @@ func ExampleIsJSON_invalidJSON() { func ExampleIsEmail_validEmail() { v := "user@example.com" - err := validator.ValidateString(&v, it.IsEmail()) + err := validator.ValidateString(context.Background(), &v, it.IsEmail()) fmt.Println(err) // Output: // @@ -42,7 +43,7 @@ func ExampleIsEmail_validEmail() { func ExampleIsEmail_invalidEmail() { v := "user example.com" - err := validator.ValidateString(&v, it.IsEmail()) + err := validator.ValidateString(context.Background(), &v, it.IsEmail()) fmt.Println(err) // Output: // violation: This value is not a valid email address. @@ -50,7 +51,7 @@ func ExampleIsEmail_invalidEmail() { func ExampleIsHTML5Email_validEmail() { v := "{}~!@example.com" - err := validator.ValidateString(&v, it.IsEmail()) + err := validator.ValidateString(context.Background(), &v, it.IsEmail()) fmt.Println(err) // Output: // @@ -58,7 +59,7 @@ func ExampleIsHTML5Email_validEmail() { func ExampleIsHTML5Email_invalidEmail() { v := "@example.com" - err := validator.ValidateString(&v, it.IsEmail()) + err := validator.ValidateString(context.Background(), &v, it.IsEmail()) fmt.Println(err) // Output: // violation: This value is not a valid email address. @@ -66,7 +67,7 @@ func ExampleIsHTML5Email_invalidEmail() { func ExampleIsHostname_validHostname() { v := "example.com" - err := validator.ValidateString(&v, it.IsHostname()) + err := validator.ValidateString(context.Background(), &v, it.IsHostname()) fmt.Println(err) // Output: // @@ -74,7 +75,7 @@ func ExampleIsHostname_validHostname() { func ExampleIsHostname_invalidHostname() { v := "example-.com" - err := validator.ValidateString(&v, it.IsHostname()) + err := validator.ValidateString(context.Background(), &v, it.IsHostname()) fmt.Println(err) // Output: // violation: This value is not a valid hostname. @@ -82,7 +83,7 @@ func ExampleIsHostname_invalidHostname() { func ExampleIsHostname_reservedHostname() { v := "example.localhost" - err := validator.ValidateString(&v, it.IsHostname()) + err := validator.ValidateString(context.Background(), &v, it.IsHostname()) fmt.Println(err) // Output: // violation: This value is not a valid hostname. @@ -90,7 +91,7 @@ func ExampleIsHostname_reservedHostname() { func ExampleIsLooseHostname_validHostname() { v := "example.com" - err := validator.ValidateString(&v, it.IsLooseHostname()) + err := validator.ValidateString(context.Background(), &v, it.IsLooseHostname()) fmt.Println(err) // Output: // @@ -98,7 +99,7 @@ func ExampleIsLooseHostname_validHostname() { func ExampleIsLooseHostname_invalidHostname() { v := "example-.com" - err := validator.ValidateString(&v, it.IsLooseHostname()) + err := validator.ValidateString(context.Background(), &v, it.IsLooseHostname()) fmt.Println(err) // Output: // violation: This value is not a valid hostname. @@ -106,7 +107,7 @@ func ExampleIsLooseHostname_invalidHostname() { func ExampleIsLooseHostname_reservedHostname() { v := "example.localhost" - err := validator.ValidateString(&v, it.IsLooseHostname()) + err := validator.ValidateString(context.Background(), &v, it.IsLooseHostname()) fmt.Println(err) // Output: // @@ -114,7 +115,7 @@ func ExampleIsLooseHostname_reservedHostname() { func ExampleIsURL_validURL() { v := "http://example.com" - err := validator.ValidateString(&v, it.IsURL()) + err := validator.ValidateString(context.Background(), &v, it.IsURL()) fmt.Println(err) // Output: // @@ -122,7 +123,7 @@ func ExampleIsURL_validURL() { func ExampleIsURL_invalidURL() { v := "example.com" - err := validator.ValidateString(&v, it.IsURL()) + err := validator.ValidateString(context.Background(), &v, it.IsURL()) fmt.Println(err) // Output: // violation: This value is not a valid URL. @@ -130,7 +131,7 @@ func ExampleIsURL_invalidURL() { func ExampleURLConstraint_WithRelativeSchema() { v := "//example.com" - err := validator.ValidateString(&v, it.IsURL().WithRelativeSchema()) + err := validator.ValidateString(context.Background(), &v, it.IsURL().WithRelativeSchema()) fmt.Println(err) // Output: // @@ -138,7 +139,7 @@ func ExampleURLConstraint_WithRelativeSchema() { func ExampleURLConstraint_WithSchemas() { v := "ftp://example.com" - err := validator.ValidateString(&v, it.IsURL().WithSchemas("http", "https", "ftp")) + err := validator.ValidateString(context.Background(), &v, it.IsURL().WithSchemas("http", "https", "ftp")) fmt.Println(err) // Output: // @@ -146,7 +147,7 @@ func ExampleURLConstraint_WithSchemas() { func ExampleIsIP_validIP() { v := "123.123.123.123" - err := validator.ValidateString(&v, it.IsIP()) + err := validator.ValidateString(context.Background(), &v, it.IsIP()) fmt.Println(err) // Output: // @@ -154,7 +155,7 @@ func ExampleIsIP_validIP() { func ExampleIsIP_invalidIP() { v := "123.123.123.345" - err := validator.ValidateString(&v, it.IsIP()) + err := validator.ValidateString(context.Background(), &v, it.IsIP()) fmt.Println(err) // Output: // violation: This is not a valid IP address. @@ -162,7 +163,7 @@ func ExampleIsIP_invalidIP() { func ExampleIsIPv4_validIP() { v := "123.123.123.123" - err := validator.ValidateString(&v, it.IsIPv4()) + err := validator.ValidateString(context.Background(), &v, it.IsIPv4()) fmt.Println(err) // Output: // @@ -170,7 +171,7 @@ func ExampleIsIPv4_validIP() { func ExampleIsIPv4_invalidIP() { v := "123.123.123.345" - err := validator.ValidateString(&v, it.IsIPv4()) + err := validator.ValidateString(context.Background(), &v, it.IsIPv4()) fmt.Println(err) // Output: // violation: This is not a valid IP address. @@ -178,7 +179,7 @@ func ExampleIsIPv4_invalidIP() { func ExampleIsIPv6_validIP() { v := "2001:0db8:85a3:0000:0000:8a2e:0370:7334" - err := validator.ValidateString(&v, it.IsIPv6()) + err := validator.ValidateString(context.Background(), &v, it.IsIPv6()) fmt.Println(err) // Output: // @@ -186,7 +187,7 @@ func ExampleIsIPv6_validIP() { func ExampleIsIPv6_invalidIP() { v := "z001:0db8:85a3:0000:0000:8a2e:0370:7334" - err := validator.ValidateString(&v, it.IsIPv6()) + err := validator.ValidateString(context.Background(), &v, it.IsIPv6()) fmt.Println(err) // Output: // violation: This is not a valid IP address. @@ -194,7 +195,7 @@ func ExampleIsIPv6_invalidIP() { func ExampleIPConstraint_DenyPrivateIP_restrictedPrivateIPv4() { v := "192.168.1.0" - err := validator.ValidateString(&v, it.IsIP().DenyPrivateIP()) + err := validator.ValidateString(context.Background(), &v, it.IsIP().DenyPrivateIP()) fmt.Println(err) // Output: // violation: This IP address is prohibited to use. @@ -202,7 +203,7 @@ func ExampleIPConstraint_DenyPrivateIP_restrictedPrivateIPv4() { func ExampleIPConstraint_DenyPrivateIP_restrictedPrivateIPv6() { v := "fdfe:dcba:9876:ffff:fdc6:c46b:bb8f:7d4c" - err := validator.ValidateString(&v, it.IsIPv6().DenyPrivateIP()) + err := validator.ValidateString(context.Background(), &v, it.IsIPv6().DenyPrivateIP()) fmt.Println(err) // Output: // violation: This IP address is prohibited to use. @@ -210,7 +211,7 @@ func ExampleIPConstraint_DenyPrivateIP_restrictedPrivateIPv6() { func ExampleIPConstraint_DenyIP() { v := "127.0.0.1" - err := validator.ValidateString(&v, it.IsIP().DenyIP(func(ip net.IP) bool { + err := validator.ValidateString(context.Background(), &v, it.IsIP().DenyIP(func(ip net.IP) bool { return ip.IsLoopback() })) fmt.Println(err) diff --git a/test/benchmark_test.go b/test/benchmark_test.go index 749894a..2e9091f 100644 --- a/test/benchmark_test.go +++ b/test/benchmark_test.go @@ -1,6 +1,7 @@ package test import ( + "context" "testing" "github.com/muonsoft/validation" @@ -14,18 +15,19 @@ type Property struct { type Properties []Property -func (p Property) Validate(validator *validation.Validator) error { +func (p Property) Validate(ctx context.Context, validator *validation.Validator) error { return validator.Validate( + ctx, validation.StringProperty("name", &p.Name, it.IsNotBlank()), validation.ValidProperty("properties", p.Properties), ) } -func (properties Properties) Validate(validator *validation.Validator) error { +func (properties Properties) Validate(ctx context.Context, validator *validation.Validator) error { violations := validation.ViolationList{} for i := range properties { - err := validator.AtIndex(i).ValidateValidatable(properties[i]) + err := validator.AtIndex(i).ValidateValidatable(ctx, properties[i]) err = violations.AppendFromError(err) if err != nil { return err @@ -46,7 +48,7 @@ func BenchmarkViolationsGeneration(b *testing.B) { b.ReportAllocs() b.ResetTimer() for i := 0; i < b.N; i++ { - validator.ValidateValidatable(properties) + validator.ValidateValidatable(context.Background(), properties) } } diff --git a/test/constraints_errors_test.go b/test/constraints_errors_test.go index 2e868fb..aa95cd2 100644 --- a/test/constraints_errors_test.go +++ b/test/constraints_errors_test.go @@ -1,6 +1,7 @@ package test import ( + "context" "errors" "testing" @@ -53,7 +54,7 @@ func TestValidator_Validate_WhenInapplicableConstraint_ExpectError(t *testing.T) } for _, test := range tests { t.Run(test.valueType, func(t *testing.T) { - err := validator.Validate(test.argument) + err := validator.Validate(context.Background(), test.argument) assertIsInapplicableConstraintError(t, err, test.valueType) }) @@ -84,7 +85,7 @@ func TestValidator_Validate_WhenInvalidValue_ExpectError(t *testing.T) { } for _, test := range tests { t.Run(test.name, func(t *testing.T) { - err := validator.Validate(test.argument) + err := validator.Validate(context.Background(), test.argument) assert.EqualError(t, err, test.expectedError) }) @@ -93,6 +94,7 @@ func TestValidator_Validate_WhenInvalidValue_ExpectError(t *testing.T) { func TestValidator_Validate_WhenInvalidConstraintAtPropertyPath_ExpectErrorWithPropertyPath(t *testing.T) { err := validator.Validate( + context.Background(), validation.String( nil, validation.PropertyName("properties"), diff --git a/test/constraints_test.go b/test/constraints_test.go index 5ff7ef3..8ac0ab0 100644 --- a/test/constraints_test.go +++ b/test/constraints_test.go @@ -1,6 +1,7 @@ package test import ( + "context" "testing" "time" @@ -76,7 +77,7 @@ func TestValidateBool(t *testing.T) { } t.Run(test.name, func(t *testing.T) { - err := validator.ValidateBool(test.boolValue, test.constraint) + err := validator.ValidateBool(context.Background(), test.boolValue, test.constraint) test.assert(t, err) }) @@ -90,7 +91,7 @@ func TestValidateNumber_AsInt(t *testing.T) { } t.Run(test.name, func(t *testing.T) { - err := validator.ValidateNumber(test.intValue, test.constraint) + err := validator.ValidateNumber(context.Background(), test.intValue, test.constraint) test.assert(t, err) }) @@ -104,7 +105,7 @@ func TestValidateNumber_AsFloat(t *testing.T) { } t.Run(test.name, func(t *testing.T) { - err := validator.ValidateNumber(test.floatValue, test.constraint) + err := validator.ValidateNumber(context.Background(), test.floatValue, test.constraint) test.assert(t, err) }) @@ -118,7 +119,7 @@ func TestValidateString(t *testing.T) { } t.Run(test.name, func(t *testing.T) { - err := validator.ValidateString(test.stringValue, test.constraint) + err := validator.ValidateString(context.Background(), test.stringValue, test.constraint) test.assert(t, err) }) @@ -132,7 +133,7 @@ func TestValidateStrings(t *testing.T) { } t.Run(test.name, func(t *testing.T) { - err := validator.ValidateStrings(test.stringsValue, test.constraint) + err := validator.ValidateStrings(context.Background(), test.stringsValue, test.constraint) test.assert(t, err) }) @@ -146,7 +147,7 @@ func TestValidateIterable_AsSlice(t *testing.T) { } t.Run(test.name, func(t *testing.T) { - err := validator.ValidateIterable(test.sliceValue, test.constraint) + err := validator.ValidateIterable(context.Background(), test.sliceValue, test.constraint) test.assert(t, err) }) @@ -160,7 +161,7 @@ func TestValidateIterable_AsMap(t *testing.T) { } t.Run(test.name, func(t *testing.T) { - err := validator.ValidateIterable(test.mapValue, test.constraint) + err := validator.ValidateIterable(context.Background(), test.mapValue, test.constraint) test.assert(t, err) }) @@ -174,7 +175,7 @@ func TestValidateCountable(t *testing.T) { } t.Run(test.name, func(t *testing.T) { - err := validator.ValidateCountable(len(test.sliceValue), test.constraint) + err := validator.ValidateCountable(context.Background(), len(test.sliceValue), test.constraint) test.assert(t, err) }) @@ -188,7 +189,7 @@ func TestValidateTime(t *testing.T) { } t.Run(test.name, func(t *testing.T) { - err := validator.ValidateTime(test.timeValue, test.constraint) + err := validator.ValidateTime(context.Background(), test.timeValue, test.constraint) test.assert(t, err) }) @@ -213,7 +214,7 @@ func TestValidateNil(t *testing.T) { t.Run(test.name, func(t *testing.T) { var v *bool - err := validator.ValidateValue(v, test.nilConstraint) + err := validator.ValidateValue(context.Background(), v, test.nilConstraint) test.assert(t, err) }) diff --git a/test/errors_test.go b/test/errors_test.go index 2db0806..8842657 100644 --- a/test/errors_test.go +++ b/test/errors_test.go @@ -1,6 +1,7 @@ package test import ( + "context" "errors" "testing" @@ -10,7 +11,7 @@ import ( ) func TestValidator_Validate_WhenNotSupportedType_ExpectError(t *testing.T) { - err := validator.Validate(validation.Value(func() {})) + err := validator.Validate(context.Background(), validation.Value(func() {})) var notValidatable *validation.NotValidatableError assert.True(t, errors.As(err, ¬Validatable)) diff --git a/test/mocks_test.go b/test/mocks_test.go index 2b708e9..e8dbe8b 100644 --- a/test/mocks_test.go +++ b/test/mocks_test.go @@ -1,11 +1,12 @@ package test import ( + "context" + "time" + "github.com/muonsoft/validation" "github.com/muonsoft/validation/it" "golang.org/x/text/language" - - "time" ) var ( @@ -21,12 +22,6 @@ var ( emptyTime time.Time ) -type contextKey string - -const ( - defaultContextKey contextKey = "defaultContextKey" -) - func boolValue(b bool) *bool { return &b } @@ -114,8 +109,9 @@ type mockValidatableString struct { value string } -func (mock mockValidatableString) Validate(validator *validation.Validator) error { +func (mock mockValidatableString) Validate(ctx context.Context, validator *validation.Validator) error { return validator.Validate( + ctx, validation.String( &mock.value, validation.PropertyName("value"), @@ -131,8 +127,9 @@ type mockValidatableStruct struct { structValue mockValidatableString } -func (mock mockValidatableStruct) Validate(validator *validation.Validator) error { +func (mock mockValidatableStruct) Validate(ctx context.Context, validator *validation.Validator) error { return validator.Validate( + ctx, validation.Number( mock.intValue, validation.PropertyName("intValue"), diff --git a/test/path_test.go b/test/path_test.go index 5c70b83..cc2febb 100644 --- a/test/path_test.go +++ b/test/path_test.go @@ -1,6 +1,7 @@ package test import ( + "context" "testing" "github.com/muonsoft/validation" @@ -15,7 +16,7 @@ type property struct { Value []*property } -func (p property) Validate(validator *validation.Validator) error { +func (p property) Validate(ctx context.Context, validator *validation.Validator) error { arguments := []validation.Argument{ validation.StringProperty( "name", @@ -39,7 +40,7 @@ func (p property) Validate(validator *validation.Validator) error { ) } - return validator.Validate(arguments...) + return validator.Validate(ctx, arguments...) } func TestValidate_AtProperty_WhenGivenRecursiveProperties_ExpectViolationWithProperty(t *testing.T) { @@ -64,7 +65,7 @@ func TestValidate_AtProperty_WhenGivenRecursiveProperties_ExpectViolationWithPro }, } - err := validator.ValidateIterable(properties) + err := validator.ValidateIterable(context.Background(), properties) assertHasOneViolationAtPath(code.NotBlank, message.NotBlank, "[0].value[0].value[0].name")(t, err) } @@ -74,6 +75,7 @@ func TestValidate_WhenPathIsSetViaOptions_ExpectViolationAtPath(t *testing.T) { v := "" err := validator.Validate( + context.Background(), validation.String( &v, validation.PropertyName("properties"), @@ -89,7 +91,7 @@ func TestValidate_WhenPathIsSetViaOptions_ExpectViolationAtPath(t *testing.T) { func TestValidate_AtProperty_WhenGivenProperty_ExpectViolationWithProperty(t *testing.T) { validator := newValidator(t) - err := validator.AtProperty("property").ValidateString(stringValue(""), it.IsNotBlank()) + err := validator.AtProperty("property").ValidateString(context.Background(), stringValue(""), it.IsNotBlank()) assertHasOneViolationAtPath(code.NotBlank, message.NotBlank, "property")(t, err) } @@ -97,7 +99,7 @@ func TestValidate_AtProperty_WhenGivenProperty_ExpectViolationWithProperty(t *te func TestValidate_AtIndex_WhenGivenIndex_ExpectViolationWithIndex(t *testing.T) { validator := newValidator(t) - err := validator.AtIndex(1).ValidateString(stringValue(""), it.IsNotBlank()) + err := validator.AtIndex(1).ValidateString(context.Background(), stringValue(""), it.IsNotBlank()) assertHasOneViolationAtPath(code.NotBlank, message.NotBlank, "[1]")(t, err) } diff --git a/test/scope_test.go b/test/scope_test.go deleted file mode 100644 index 4f0254f..0000000 --- a/test/scope_test.go +++ /dev/null @@ -1,18 +0,0 @@ -package test - -import ( - "context" - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestValidator_WithContext_WhenContextWithValue_ExpectContextShouldReturnValue(t *testing.T) { - validator := newValidator(t) - - ctx := context.WithValue(context.Background(), defaultContextKey, "value") - contextValidator := validator.WithContext(ctx) - value := contextValidator.Context().Value(defaultContextKey) - - assert.Equal(t, "value", value) -} diff --git a/test/translations_test.go b/test/translations_test.go index 1045555..4d4d68a 100644 --- a/test/translations_test.go +++ b/test/translations_test.go @@ -35,7 +35,7 @@ func TestValidator_Validate_WhenRussianIsDefaultLanguage_ExpectViolationTranslat } for _, test := range tests { t.Run("plural form for "+strconv.Itoa(test.maxCount), func(t *testing.T) { - err := v.ValidateCountable(10, it.HasMaxCount(test.maxCount)) + err := v.ValidateCountable(context.Background(), 10, it.HasMaxCount(test.maxCount)) validationtest.AssertIsViolationList(t, err, func(t *testing.T, violations []validation.Violation) bool { t.Helper() @@ -61,6 +61,7 @@ func TestValidator_Validate_WhenRussianIsPassedViaArgument_ExpectViolationTransl for _, test := range tests { t.Run("plural form for "+strconv.Itoa(test.maxCount), func(t *testing.T) { err := v.Validate( + context.Background(), validation.Language(language.Russian), validation.Countable(10, it.HasMaxCount(test.maxCount)), ) @@ -82,6 +83,7 @@ func TestValidator_Validate_WhenCustomDefaultLanguageAndUndefinedTranslationLang ) err := v.Validate( + context.Background(), validation.Language(language.Afrikaans), validation.String(stringValue(""), it.IsNotBlank()), ) @@ -105,7 +107,7 @@ func TestValidator_Validate_WhenTranslationLanguageInContextArgument_ExpectTrans ctx := language.WithContext(context.Background(), language.Russian) err := v.Validate( - validation.Context(ctx), + ctx, validation.String(stringValue(""), it.IsNotBlank()), ) @@ -119,7 +121,7 @@ func TestValidator_Validate_WhenTranslationLanguageInContextArgument_ExpectTrans func TestValidator_Validate_WhenTranslationLanguageInScopedValidator_ExpectTranslationLanguageUsed(t *testing.T) { v := newValidator(t, validation.Translations(russian.Messages)).WithLanguage(language.Russian) - err := v.ValidateString(stringValue(""), it.IsNotBlank()) + err := v.ValidateString(context.Background(), stringValue(""), it.IsNotBlank()) validationtest.AssertIsViolationList(t, err, func(t *testing.T, violations []validation.Violation) bool { t.Helper() @@ -130,9 +132,9 @@ func TestValidator_Validate_WhenTranslationLanguageInScopedValidator_ExpectTrans func TestValidator_Validate_WhenTranslationLanguageInContextOfScopedValidator_ExpectTranslationLanguageUsed(t *testing.T) { ctx := language.WithContext(context.Background(), language.Russian) - v := newValidator(t, validation.Translations(russian.Messages)).WithContext(ctx) + v := newValidator(t, validation.Translations(russian.Messages)) - err := v.ValidateString(stringValue(""), it.IsNotBlank()) + err := v.ValidateString(ctx, stringValue(""), it.IsNotBlank()) validationtest.AssertIsViolationList(t, err, func(t *testing.T, violations []validation.Violation) bool { t.Helper() @@ -148,7 +150,7 @@ func TestValidator_Validate_WhenTranslationLanguageParsedFromAcceptLanguageHeade tag, _ := textlanguage.MatchStrings(matcher, "ru-RU,ru;q=0.9,en-US;q=0.8,en;q=0.7") ctx := language.WithContext(context.Background(), tag) err := v.Validate( - validation.Context(ctx), + ctx, validation.String(stringValue(""), it.IsNotBlank()), ) @@ -167,7 +169,7 @@ func TestValidator_Validate_WhenRecursiveValidation_ExpectViolationTranslated(t ) values := []mockValidatableString{{value: ""}} - err := v.ValidateIterable(values, it.IsNotBlank()) + err := v.ValidateIterable(context.Background(), values, it.IsNotBlank()) validationtest.AssertIsViolationList(t, err, func(t *testing.T, violations []validation.Violation) bool { t.Helper() @@ -190,6 +192,7 @@ func TestValidator_Validate_WhenTranslatableParameter_ExpectParameterTranslated( v := "" err := validator.Validate( + context.Background(), validation.String( &v, it.IsNotBlank(). @@ -217,7 +220,7 @@ func TestValidate_WhenTranslationsLoadedAfterInit_ExpectTranslationsWorking(t *t } defer validator.Reset() - err = validator.ValidateString(stringValue(""), it.IsNotBlank()) + err = validator.ValidateString(context.Background(), stringValue(""), it.IsNotBlank()) validationtest.AssertIsViolationList(t, err, func(t *testing.T, violations []validation.Violation) bool { t.Helper() diff --git a/test/validatable_test.go b/test/validatable_test.go index b0c5d09..4ee6081 100644 --- a/test/validatable_test.go +++ b/test/validatable_test.go @@ -1,6 +1,7 @@ package test import ( + "context" "testing" "github.com/muonsoft/validation" @@ -18,8 +19,9 @@ type Product struct { Components []Component } -func (p Product) Validate(validator *validation.Validator) error { +func (p Product) Validate(ctx context.Context, validator *validation.Validator) error { return validator.Validate( + ctx, validation.String( &p.Name, validation.PropertyName("name"), @@ -44,8 +46,9 @@ type Component struct { Tags []string } -func (c Component) Validate(validator *validation.Validator) error { +func (c Component) Validate(ctx context.Context, validator *validation.Validator) error { return validator.Validate( + ctx, validation.String( &c.Name, validation.PropertyName("name"), @@ -70,7 +73,7 @@ func TestValidateValue_WhenStructWithComplexRules_ExpectViolations(t *testing.T) }, } - err := validator.ValidateValue(p) + err := validator.ValidateValue(context.Background(), p) validationtest.AssertIsViolationList(t, err, func(t *testing.T, violations []validation.Violation) bool { t.Helper() @@ -92,6 +95,7 @@ func TestValidateValue_WhenValidatableString_ExpectValidationExecutedWithPassedO validatable := mockValidatableString{value: ""} err := validator.ValidateValue( + context.Background(), validatable, validation.PropertyName("top"), it.IsNotBlank().Message("ignored"), @@ -104,6 +108,7 @@ func TestValidateValidatable_WhenValidatableString_ExpectValidationExecutedWithP validatable := mockValidatableString{value: ""} err := validator.ValidateValidatable( + context.Background(), validatable, validation.PropertyName("top"), it.IsNotBlank().Message("ignored"), @@ -116,6 +121,7 @@ func TestValidateValue_WhenValidatableStruct_ExpectValidationExecutedWithPassedO validatable := mockValidatableStruct{} err := validator.ValidateValue( + context.Background(), validatable, validation.PropertyName("top"), it.IsNotBlank().Message("ignored"), diff --git a/test/validate_arguments_test.go b/test/validate_arguments_test.go index 33a1034..2feb80a 100644 --- a/test/validate_arguments_test.go +++ b/test/validate_arguments_test.go @@ -1,6 +1,7 @@ package test import ( + "context" "testing" "github.com/muonsoft/validation" @@ -29,7 +30,7 @@ func TestValidate_WhenArgumentForGivenType_ExpectValidationExecuted(t *testing.T } for _, test := range tests { t.Run(test.name, func(t *testing.T) { - err := validator.Validate(test.argument) + err := validator.Validate(context.Background(), test.argument) validationtest.AssertIsViolationList(t, err, func(t *testing.T, violations []validation.Violation) bool { t.Helper() @@ -60,7 +61,7 @@ func TestValidate_WhenPropertyArgument_ExpectValidPathInViolation(t *testing.T) } for _, test := range tests { t.Run(test.name, func(t *testing.T) { - err := validator.Validate(test.argument) + err := validator.Validate(context.Background(), test.argument) validationtest.AssertIsViolationList(t, err, func(t *testing.T, violations []validation.Violation) bool { t.Helper() diff --git a/test/validate_control_constraints_test.go b/test/validate_control_constraints_test.go index 38e81dc..03d9a42 100644 --- a/test/validate_control_constraints_test.go +++ b/test/validate_control_constraints_test.go @@ -1,20 +1,22 @@ package test import ( + "context" + "testing" + "github.com/muonsoft/validation" "github.com/muonsoft/validation/code" "github.com/muonsoft/validation/it" "github.com/muonsoft/validation/validationtest" "github.com/muonsoft/validation/validator" "github.com/stretchr/testify/assert" - - "testing" ) func TestValidateString_WhenConditionIsTrue_ExpectAllConstraintsOfThenBranchApplied(t *testing.T) { value := "foo" err := validator.ValidateString( + context.Background(), &value, validation.When(true). Then( @@ -35,6 +37,7 @@ func TestValidateString_WhenConditionIsFalse_ExpectAllConstraintsOfElseBranchApp value := "bar" err := validator.ValidateString( + context.Background(), &value, validation.When(false). Then( @@ -58,6 +61,7 @@ func TestValidateString_WhenConditionIsFalseAndNoElseBranch_ExpectNoViolations(t value := "foo" err := validator.ValidateString( + context.Background(), &value, validation.When(false). Then( @@ -72,6 +76,7 @@ func TestValidateString_WhenThenBranchIsNotSet_ExpectError(t *testing.T) { value := "bar" err := validator.ValidateString( + context.Background(), &value, validation.When(true), ) @@ -83,6 +88,7 @@ func TestValidate_WhenInvalidValueAtFirstConstraintOfSequentiallyConstraint_Expe value := "foo" err := validator.ValidateString( + context.Background(), &value, validation.Sequentially( it.IsBlank(), @@ -101,6 +107,7 @@ func TestValidate_WhenSequentiallyConstraintsNotSet_ExpectError(t *testing.T) { value := "bar" err := validator.ValidateString( + context.Background(), &value, validation.Sequentially(), ) @@ -112,6 +119,7 @@ func TestValidate_WhenInvalidValueAtFirstConstraintOfAtLeastOneOfConstraint_Expe value := "foo" err := validator.ValidateString( + context.Background(), &value, validation.AtLeastOneOf( it.IsBlank(), @@ -131,6 +139,7 @@ func TestValidate_WhenInvalidValueAtSecondConstraintOfAtLeastOneOfConstraint_Exp value := "foo" err := validator.ValidateString( + context.Background(), &value, validation.AtLeastOneOf( it.IsEqualToString("bar"), @@ -145,6 +154,7 @@ func TestValidate_WhenAtLeastOneOfConstraintsNotSet_ExpectError(t *testing.T) { value := "bar" err := validator.ValidateString( + context.Background(), &value, validation.AtLeastOneOf(), ) @@ -157,6 +167,7 @@ func TestValidate_Compound_ExpectNoViolation(t *testing.T) { isEmployeeEmail := validation.Compound(it.HasMinLength(5), it.IsEmail()) err := validator.ValidateString( + context.Background(), &value, isEmployeeEmail, ) @@ -174,6 +185,7 @@ func TestValidate_WhenCompoundConstraintsNotSet_ExpectError(t *testing.T) { isEmployeeEmail := validation.Compound() err := validator.ValidateString( + context.Background(), &value, isEmployeeEmail, ) diff --git a/test/validate_each_test.go b/test/validate_each_test.go index 8a327a3..f337bb0 100644 --- a/test/validate_each_test.go +++ b/test/validate_each_test.go @@ -1,6 +1,7 @@ package test import ( + "context" "testing" "github.com/muonsoft/validation" @@ -14,7 +15,7 @@ import ( func TestValidateEach_WhenSliceOfStrings_ExpectViolationOnEachElement(t *testing.T) { strings := []string{"", ""} - err := validator.ValidateEach(strings, it.IsNotBlank()) + err := validator.ValidateEach(context.Background(), strings, it.IsNotBlank()) validationtest.AssertIsViolationList(t, err, func(t *testing.T, violations []validation.Violation) bool { t.Helper() @@ -31,7 +32,7 @@ func TestValidateEach_WhenSliceOfStrings_ExpectViolationOnEachElement(t *testing func TestValidateEach_WhenMapOfStrings_ExpectViolationOnEachElement(t *testing.T) { strings := map[string]string{"key1": "", "key2": ""} - err := validator.ValidateEach(strings, it.IsNotBlank()) + err := validator.ValidateEach(context.Background(), strings, it.IsNotBlank()) validationtest.AssertIsViolationList(t, err, func(t *testing.T, violations []validation.Violation) bool { t.Helper() @@ -48,7 +49,7 @@ func TestValidateEach_WhenMapOfStrings_ExpectViolationOnEachElement(t *testing.T func TestValidateEachString_WhenSliceOfStrings_ExpectViolationOnEachElement(t *testing.T) { strings := []string{"", ""} - err := validator.ValidateEachString(strings, it.IsNotBlank()) + err := validator.ValidateEachString(context.Background(), strings, it.IsNotBlank()) validationtest.AssertIsViolationList(t, err, func(t *testing.T, violations []validation.Violation) bool { t.Helper() diff --git a/test/validate_iterable_test.go b/test/validate_iterable_test.go index e6e4419..02a7614 100644 --- a/test/validate_iterable_test.go +++ b/test/validate_iterable_test.go @@ -1,6 +1,7 @@ package test import ( + "context" "testing" "github.com/muonsoft/validation" @@ -14,7 +15,7 @@ import ( func TestValidateIterable_WhenSliceOfValidatable_ExpectViolationsWithValidPaths(t *testing.T) { strings := []mockValidatableString{{value: ""}} - err := validator.ValidateValue(strings) + err := validator.ValidateValue(context.Background(), strings) validationtest.AssertIsViolationList(t, err, func(t *testing.T, violations []validation.Violation) bool { t.Helper() @@ -29,7 +30,7 @@ func TestValidateIterable_WhenSliceOfValidatable_ExpectViolationsWithValidPaths( func TestValidateIterable_WhenSliceOfValidatableWithConstraints_ExpectCollectionViolationsWithValidPaths(t *testing.T) { strings := []mockValidatableString{{value: ""}} - err := validator.ValidateValue(strings, it.HasMinCount(2)) + err := validator.ValidateValue(context.Background(), strings, it.HasMinCount(2)) validationtest.AssertIsViolationList(t, err, func(t *testing.T, violations []validation.Violation) bool { t.Helper() @@ -46,7 +47,7 @@ func TestValidateIterable_WhenSliceOfValidatableWithConstraints_ExpectCollection func TestValidateIterable_WhenMapOfValidatable_ExpectViolationsWithValidPaths(t *testing.T) { strings := map[string]mockValidatableString{"key": {value: ""}} - err := validator.ValidateValue(strings) + err := validator.ValidateValue(context.Background(), strings) validationtest.AssertIsViolationList(t, err, func(t *testing.T, violations []validation.Violation) bool { t.Helper() @@ -61,7 +62,7 @@ func TestValidateIterable_WhenMapOfValidatable_ExpectViolationsWithValidPaths(t func TestValidateIterable_WhenMapOfValidatableWithConstraints_ExpectCollectionViolationsWithValidPaths(t *testing.T) { strings := map[string]mockValidatableString{"key": {value: ""}} - err := validator.ValidateValue(strings, it.HasMinCount(2)) + err := validator.ValidateValue(context.Background(), strings, it.HasMinCount(2)) validationtest.AssertIsViolationList(t, err, func(t *testing.T, violations []validation.Violation) bool { t.Helper() diff --git a/test/validate_value_test.go b/test/validate_value_test.go index 2b0c9ad..5fc5103 100644 --- a/test/validate_value_test.go +++ b/test/validate_value_test.go @@ -1,6 +1,7 @@ package test import ( + "context" "testing" "github.com/muonsoft/validation" @@ -42,7 +43,7 @@ func TestValidateValue_WhenValueOfType_ExpectValueValidated(t *testing.T) { } for _, test := range tests { t.Run(test.name, func(t *testing.T) { - err := validator.ValidateValue(test.value, validation.PropertyName("property"), it.IsNotBlank()) + err := validator.ValidateValue(context.Background(), test.value, validation.PropertyName("property"), it.IsNotBlank()) assertHasOneViolationAtPath(code.NotBlank, message.NotBlank, "property")(t, err) }) diff --git a/test/validator_option_test.go b/test/validator_option_test.go index 5140ff3..ae8c2c0 100644 --- a/test/validator_option_test.go +++ b/test/validator_option_test.go @@ -1,6 +1,7 @@ package test import ( + "context" "testing" "github.com/muonsoft/validation" @@ -17,7 +18,7 @@ func TestWhenGlobalValidatorWithOverriddenNewViolation_ExpectCustomViolation(t * } defer validator.Reset() - err = validator.ValidateString(nil, it.IsNotBlank()) + err = validator.ValidateString(context.Background(), nil, it.IsNotBlank()) validationtest.AssertIsViolationList(t, err, func(t *testing.T, violations []validation.Violation) bool { t.Helper() @@ -28,7 +29,7 @@ func TestWhenGlobalValidatorWithOverriddenNewViolation_ExpectCustomViolation(t * func TestWhenValidatorWithOverriddenNewViolation_ExpectCustomViolation(t *testing.T) { v := newValidator(t, validation.SetViolationFactory(mockNewViolationFunc())) - err := v.ValidateString(nil, it.IsNotBlank()) + err := v.ValidateString(context.Background(), nil, it.IsNotBlank()) validationtest.AssertIsViolationList(t, err, func(t *testing.T, violations []validation.Violation) bool { t.Helper() diff --git a/test/validator_store_test.go b/test/validator_store_test.go index 4b66adf..07aa6ab 100644 --- a/test/validator_store_test.go +++ b/test/validator_store_test.go @@ -1,6 +1,7 @@ package test import ( + "context" "testing" "github.com/muonsoft/validation" @@ -14,7 +15,7 @@ func TestValidator_ValidateBy_WhenConstraintExists_ExpectValidationByStoredConst validator := newValidator(t, validation.StoredConstraint("notBlank", it.IsNotBlank())) s := "" - err := validator.ValidateString(&s, validator.ValidateBy("notBlank")) + err := validator.ValidateString(context.Background(), &s, validator.ValidateBy("notBlank")) assertHasOneViolation(code.NotBlank, message.NotBlank)(t, err) } @@ -23,7 +24,7 @@ func TestValidator_ValidateBy_WhenConstraintDoesNotExist_ExpectError(t *testing. validator := newValidator(t) s := "" - err := validator.ValidateString(&s, validator.ValidateBy("notBlank")) + err := validator.ValidateString(context.Background(), &s, validator.ValidateBy("notBlank")) assert.EqualError(t, err, `failed to set up constraint "notFoundConstraint": constraint with key "notBlank" is not stored in the validator`) } diff --git a/validation.go b/validation.go index 36e1b15..47ec6c4 100644 --- a/validation.go +++ b/validation.go @@ -7,6 +7,7 @@ package validation import ( + "context" "reflect" "time" @@ -23,8 +24,9 @@ import ( // Keywords []string // } // -// func (b Book) Validate(validator *validation.Validator) error { +// func (b Book) Validate(ctx context.Context, validator *validation.Validator) error { // return validator.Validate( +// ctx, // validation.StringProperty("title", &b.Title, it.IsNotBlank()), // validation.StringProperty("author", &b.Author, it.IsNotBlank()), // validation.CountableProperty("keywords", len(b.Keywords), it.HasCountBetween(1, 10)), @@ -32,7 +34,7 @@ import ( // ) // } type Validatable interface { - Validate(validator *Validator) error + Validate(ctx context.Context, validator *Validator) error } // Filter is used for processing the list of errors to return a single ViolationList. @@ -288,7 +290,7 @@ func newValidValidator(value Validatable, options []Option) validateFunc { return nil, err } - err = value.Validate(newScopedValidator(scope)) + err = value.Validate(scope.context, newScopedValidator(scope)) violations, ok := UnwrapViolationList(err) if ok { return violations, nil diff --git a/validator.go b/validator.go index aa75fa4..9e86ba4 100644 --- a/validator.go +++ b/validator.go @@ -119,8 +119,8 @@ func StoredConstraint(key string, constraint Constraint) ValidatorOption { // Validate is the main validation method. It accepts validation arguments. Arguments can be // used to tune up the validation process or to pass values of a specific type. -func (validator *Validator) Validate(arguments ...Argument) error { - args := &Arguments{scope: validator.scope} +func (validator *Validator) Validate(ctx context.Context, arguments ...Argument) error { + args := &Arguments{scope: validator.scope.withContext(ctx)} for _, argument := range arguments { err := argument.set(args) if err != nil { @@ -141,75 +141,58 @@ func (validator *Validator) Validate(arguments ...Argument) error { } // ValidateValue is an alias for validating a single value of any supported type. -func (validator *Validator) ValidateValue(value interface{}, options ...Option) error { - return validator.Validate(Value(value, options...)) +func (validator *Validator) ValidateValue(ctx context.Context, value interface{}, options ...Option) error { + return validator.Validate(ctx, Value(value, options...)) } // ValidateBool is an alias for validating a single boolean value. -func (validator *Validator) ValidateBool(value *bool, options ...Option) error { - return validator.Validate(Bool(value, options...)) +func (validator *Validator) ValidateBool(ctx context.Context, value *bool, options ...Option) error { + return validator.Validate(ctx, Bool(value, options...)) } // ValidateNumber is an alias for validating a single numeric value (integer or float). -func (validator *Validator) ValidateNumber(value interface{}, options ...Option) error { - return validator.Validate(Number(value, options...)) +func (validator *Validator) ValidateNumber(ctx context.Context, value interface{}, options ...Option) error { + return validator.Validate(ctx, Number(value, options...)) } // ValidateString is an alias for validating a single string value. -func (validator *Validator) ValidateString(value *string, options ...Option) error { - return validator.Validate(String(value, options...)) +func (validator *Validator) ValidateString(ctx context.Context, value *string, options ...Option) error { + return validator.Validate(ctx, String(value, options...)) } // ValidateStrings is an alias for validating slice of strings. -func (validator *Validator) ValidateStrings(values []string, options ...Option) error { - return validator.Validate(Strings(values, options...)) +func (validator *Validator) ValidateStrings(ctx context.Context, values []string, options ...Option) error { + return validator.Validate(ctx, Strings(values, options...)) } // ValidateIterable is an alias for validating a single iterable value (an array, slice, or map). -func (validator *Validator) ValidateIterable(value interface{}, options ...Option) error { - return validator.Validate(Iterable(value, options...)) +func (validator *Validator) ValidateIterable(ctx context.Context, value interface{}, options ...Option) error { + return validator.Validate(ctx, Iterable(value, options...)) } // ValidateCountable is an alias for validating a single countable value (an array, slice, or map). -func (validator *Validator) ValidateCountable(count int, options ...Option) error { - return validator.Validate(Countable(count, options...)) +func (validator *Validator) ValidateCountable(ctx context.Context, count int, options ...Option) error { + return validator.Validate(ctx, Countable(count, options...)) } // ValidateTime is an alias for validating a single time value. -func (validator *Validator) ValidateTime(value *time.Time, options ...Option) error { - return validator.Validate(Time(value, options...)) +func (validator *Validator) ValidateTime(ctx context.Context, value *time.Time, options ...Option) error { + return validator.Validate(ctx, Time(value, options...)) } // ValidateEach is an alias for validating each value of an iterable (an array, slice, or map). -func (validator *Validator) ValidateEach(value interface{}, options ...Option) error { - return validator.Validate(Each(value, options...)) +func (validator *Validator) ValidateEach(ctx context.Context, value interface{}, options ...Option) error { + return validator.Validate(ctx, Each(value, options...)) } // ValidateEachString is an alias for validating each value of a strings slice. -func (validator *Validator) ValidateEachString(values []string, options ...Option) error { - return validator.Validate(EachString(values, options...)) +func (validator *Validator) ValidateEachString(ctx context.Context, values []string, options ...Option) error { + return validator.Validate(ctx, EachString(values, options...)) } // ValidateValidatable is an alias for validating value that implements the Validatable interface. -func (validator *Validator) ValidateValidatable(validatable Validatable, options ...Option) error { - return validator.Validate(Valid(validatable, options...)) -} - -// Context returns context from current validation scope. By default it returns context.Background. -// You can create scoped validator with context by calling WithContext method. -func (validator *Validator) Context() context.Context { - return validator.scope.context -} - -// WithContext method creates a new scoped validator with a given context. You can use this method to pass -// a context value to all used constraints. -// -// Example -// err := validator.WithContext(request.Context()).Validate( -// String(&s, it.IsNotBlank()), // now all called constraints will use passed context in their methods -// ) -func (validator *Validator) WithContext(ctx context.Context) *Validator { - return newScopedValidator(validator.scope.withContext(ctx)) +func (validator *Validator) ValidateValidatable(ctx context.Context, validatable Validatable, options ...Option) error { + return validator.Validate(ctx, Valid(validatable, options...)) } // WithLanguage method creates a new scoped validator with a given language tag. All created violations diff --git a/validator/validator.go b/validator/validator.go index 2c8649f..ae30c92 100644 --- a/validator/validator.go +++ b/validator/validator.go @@ -36,74 +36,63 @@ func Reset() { // Validate is the main validation method. It accepts validation arguments. Arguments can be // used to tune up the validation process or to pass values of a specific type. -func Validate(arguments ...validation.Argument) error { - return validator.Validate(arguments...) +func Validate(ctx context.Context, arguments ...validation.Argument) error { + return validator.Validate(ctx, arguments...) } // ValidateValue is an alias for validating a single value of any supported type. -func ValidateValue(value interface{}, options ...validation.Option) error { - return validator.ValidateValue(value, options...) +func ValidateValue(ctx context.Context, value interface{}, options ...validation.Option) error { + return validator.ValidateValue(ctx, value, options...) } // ValidateBool is an alias for validating a single boolean value. -func ValidateBool(value *bool, options ...validation.Option) error { - return validator.ValidateBool(value, options...) +func ValidateBool(ctx context.Context, value *bool, options ...validation.Option) error { + return validator.ValidateBool(ctx, value, options...) } // ValidateNumber is an alias for validating a single numeric value (integer or float). -func ValidateNumber(value interface{}, options ...validation.Option) error { - return validator.ValidateNumber(value, options...) +func ValidateNumber(ctx context.Context, value interface{}, options ...validation.Option) error { + return validator.ValidateNumber(ctx, value, options...) } // ValidateString is an alias for validating a single string value. -func ValidateString(value *string, options ...validation.Option) error { - return validator.ValidateString(value, options...) +func ValidateString(ctx context.Context, value *string, options ...validation.Option) error { + return validator.ValidateString(ctx, value, options...) } // ValidateStrings is an alias for validating slice of strings. -func ValidateStrings(values []string, options ...validation.Option) error { - return validator.ValidateStrings(values, options...) +func ValidateStrings(ctx context.Context, values []string, options ...validation.Option) error { + return validator.ValidateStrings(ctx, values, options...) } // ValidateIterable is an alias for validating a single iterable value (an array, slice, or map). -func ValidateIterable(value interface{}, options ...validation.Option) error { - return validator.ValidateIterable(value, options...) +func ValidateIterable(ctx context.Context, value interface{}, options ...validation.Option) error { + return validator.ValidateIterable(ctx, value, options...) } // ValidateCountable is an alias for validating a single countable value (an array, slice, or map). -func ValidateCountable(count int, options ...validation.Option) error { - return validator.ValidateCountable(count, options...) +func ValidateCountable(ctx context.Context, count int, options ...validation.Option) error { + return validator.ValidateCountable(ctx, count, options...) } // ValidateTime is an alias for validating a single time value. -func ValidateTime(value *time.Time, options ...validation.Option) error { - return validator.ValidateTime(value, options...) +func ValidateTime(ctx context.Context, value *time.Time, options ...validation.Option) error { + return validator.ValidateTime(ctx, value, options...) } // ValidateEach is an alias for validating each value of an iterable (an array, slice, or map). -func ValidateEach(value interface{}, options ...validation.Option) error { - return validator.ValidateEach(value, options...) +func ValidateEach(ctx context.Context, value interface{}, options ...validation.Option) error { + return validator.ValidateEach(ctx, value, options...) } // ValidateEachString is an alias for validating each value of a strings slice. -func ValidateEachString(strings []string, options ...validation.Option) error { - return validator.ValidateEachString(strings, options...) +func ValidateEachString(ctx context.Context, strings []string, options ...validation.Option) error { + return validator.ValidateEachString(ctx, strings, options...) } // ValidateValidatable is an alias for validating value that implements the Validatable interface. -func ValidateValidatable(validatable validation.Validatable, options ...validation.Option) error { - return validator.ValidateValidatable(validatable, options...) -} - -// WithContext method creates a new scoped validator with a given context. You can use this method to pass -// a context value to all used constraints. - -// Example -// err := validator.WithContext(request.Context()).Validate( -// String(&s, it.IsNotBlank()), // now all called constraints will use passed context in their methods -// ) -func WithContext(ctx context.Context) *validation.Validator { - return validator.WithContext(ctx) +func ValidateValidatable(ctx context.Context, validatable validation.Validatable, options ...validation.Option) error { + return validator.ValidateValidatable(ctx, validatable, options...) } // WithLanguage method creates a new scoped validator with a given language tag. All created violations From f71b430f02361910f4e760e3b1c161b1e6042e85 Mon Sep 17 00:00:00 2001 From: Igor Lazarev Date: Thu, 29 Jul 2021 06:20:57 +0300 Subject: [PATCH 2/4] context * context added for BuildViolation method --- example_test.go | 14 +++++++------- test/filter_test.go | 5 +++-- validator.go | 4 ++-- validator/validator.go | 6 +++--- violations_test.go | 25 +++++++++++++------------ 5 files changed, 28 insertions(+), 26 deletions(-) diff --git a/example_test.go b/example_test.go index 6acd704..7449d53 100644 --- a/example_test.go +++ b/example_test.go @@ -662,7 +662,7 @@ func ExampleValidator_BuildViolation_buildingViolation() { log.Fatal(err) } - violation := validator.BuildViolation("clientCode", "Client message with {{ parameter }}."). + violation := validator.BuildViolation(context.Background(), "clientCode", "Client message with {{ parameter }}."). AddParameter("{{ parameter }}", "value"). CreateViolation() @@ -685,7 +685,7 @@ func ExampleValidator_BuildViolation_translatableParameter() { } violation := validator.WithLanguage(language.Russian). - BuildViolation("clientCode", "The operation is only possible for the {{ role }}."). + BuildViolation(context.Background(), "clientCode", "The operation is only possible for the {{ role }}."). SetParameters(validation.TemplateParameter{ Key: "{{ role }}", Value: "administrator role", @@ -700,8 +700,8 @@ func ExampleValidator_BuildViolation_translatableParameter() { func ExampleViolationList_First() { violations := validation.NewViolationList( - validator.BuildViolation("", "foo").CreateViolation(), - validator.BuildViolation("", "bar").CreateViolation(), + validator.BuildViolation(context.Background(), "", "foo").CreateViolation(), + validator.BuildViolation(context.Background(), "", "bar").CreateViolation(), ) for violation := violations.First(); violation != nil; violation = violation.Next() { @@ -714,7 +714,7 @@ func ExampleViolationList_First() { func ExampleViolationList_AppendFromError_addingViolation() { violations := validation.NewViolationList() - err := validator.BuildViolation("", "foo").CreateViolation() + err := validator.BuildViolation(context.Background(), "", "foo").CreateViolation() appendErr := violations.AppendFromError(err) @@ -728,8 +728,8 @@ func ExampleViolationList_AppendFromError_addingViolation() { func ExampleViolationList_AppendFromError_addingViolationList() { violations := validation.NewViolationList() err := validation.NewViolationList( - validator.BuildViolation("", "foo").CreateViolation(), - validator.BuildViolation("", "bar").CreateViolation(), + validator.BuildViolation(context.Background(), "", "foo").CreateViolation(), + validator.BuildViolation(context.Background(), "", "bar").CreateViolation(), ) appendErr := violations.AppendFromError(err) diff --git a/test/filter_test.go b/test/filter_test.go index 42cd577..fac3184 100644 --- a/test/filter_test.go +++ b/test/filter_test.go @@ -1,6 +1,7 @@ package test import ( + "context" "fmt" "testing" @@ -17,7 +18,7 @@ func TestFilter_WhenNoViolations_ExpectNil(t *testing.T) { } func TestFilter_WhenSingleViolation_ExpectViolationInList(t *testing.T) { - violation := validator.BuildViolation("code", "message").CreateViolation() + violation := validator.BuildViolation(context.Background(), "code", "message").CreateViolation() wrapped := fmt.Errorf("error: %w", violation) err := validation.Filter(nil, wrapped) @@ -29,7 +30,7 @@ func TestFilter_WhenSingleViolation_ExpectViolationInList(t *testing.T) { } func TestFilter_WhenViolationList_ExpectViolationsInList(t *testing.T) { - violation := validator.BuildViolation("code", "message").CreateViolation() + violation := validator.BuildViolation(context.Background(), "code", "message").CreateViolation() violations := validation.NewViolationList(violation) wrapped := fmt.Errorf("error: %w", violations) diff --git a/validator.go b/validator.go index 5dec6f4..342f15b 100644 --- a/validator.go +++ b/validator.go @@ -217,8 +217,8 @@ func (validator *Validator) AtIndex(index int) *Validator { } // BuildViolation can be used to build a custom violation on the client-side. -func (validator *Validator) BuildViolation(code, message string) *ViolationBuilder { - return validator.scope.BuildViolation(code, message) +func (validator *Validator) BuildViolation(ctx context.Context, code, message string) *ViolationBuilder { + return validator.scope.withContext(ctx).BuildViolation(code, message) } // ValidateBy is used to get the constraint from the internal validator store. diff --git a/validator/validator.go b/validator/validator.go index ae30c92..f99a228 100644 --- a/validator/validator.go +++ b/validator/validator.go @@ -119,11 +119,11 @@ func AtIndex(index int) *validation.Validator { // BuildViolation can be used to build a custom violation on the client-side. // // Example -// err := validator.BuildViolation("", ""). +// err := validator.BuildViolation(context.Background(), "", ""). // AddParameter("key", "value"). // CreateViolation() -func BuildViolation(code, message string) *validation.ViolationBuilder { - return validator.BuildViolation(code, message) +func BuildViolation(ctx context.Context, code, message string) *validation.ViolationBuilder { + return validator.BuildViolation(ctx, code, message) } // ValidateBy is used to get the constraint from the internal validator store. diff --git a/violations_test.go b/violations_test.go index cbcc254..828b545 100644 --- a/violations_test.go +++ b/violations_test.go @@ -1,6 +1,7 @@ package validation_test import ( + "context" "encoding/json" "errors" "fmt" @@ -13,7 +14,7 @@ import ( func TestViolation_Error_MessageOnly_ErrorWithMessage(t *testing.T) { validator := newValidator(t) - violation := validator.BuildViolation("", "message").CreateViolation() + violation := validator.BuildViolation(context.Background(), "", "message").CreateViolation() assert.Equal(t, "violation: message", violation.Error()) } @@ -177,7 +178,7 @@ func TestViolationList_Filter_ViolationsWithCodes_FilteredList(t *testing.T) { func TestViolation_Error_MessageAndPropertyPath_ErrorWithPropertyPathAndMessage(t *testing.T) { validator := newValidator(t) - violation := validator.BuildViolation("", "message"). + violation := validator.BuildViolation(context.Background(), "", "message"). SetPropertyPath(validation.NewPropertyPath(validation.PropertyNameElement("propertyPath"))). CreateViolation() @@ -189,14 +190,14 @@ func TestViolation_Error_MessageAndPropertyPath_ErrorWithPropertyPathAndMessage( func TestViolationList_Error_CoupleOfViolations_JoinedMessage(t *testing.T) { validator := newValidator(t) violations := validation.NewViolationList( - validator.BuildViolation("", "first message"). + validator.BuildViolation(context.Background(), "", "first message"). SetPropertyPath( validation.NewPropertyPath( validation.PropertyNameElement("path"), validation.ArrayIndexElement(0)), ). CreateViolation(), - validator.BuildViolation("", "second message"). + validator.BuildViolation(context.Background(), "", "second message"). SetPropertyPath( validation.NewPropertyPath( validation.PropertyNameElement("path"), @@ -287,7 +288,7 @@ func TestMarshalViolationToJSON(t *testing.T) { }{ { name: "full data", - violation: validator.BuildViolation("code", "message"). + violation: validator.BuildViolation(context.Background(), "code", "message"). SetParameters(validation.TemplateParameter{Key: "key", Value: "value"}). SetPropertyPath( validation.NewPropertyPath( @@ -304,7 +305,7 @@ func TestMarshalViolationToJSON(t *testing.T) { }, { name: "empty data", - violation: validator.BuildViolation("", "").CreateViolation(), + violation: validator.BuildViolation(context.Background(), "", "").CreateViolation(), expectedJSON: `{"code": "", "message": ""}`, }, } @@ -335,14 +336,14 @@ func TestMarshalViolationListToJSON(t *testing.T) { { name: "empty data", list: validation.NewViolationList( - validator.BuildViolation("", "").CreateViolation(), + validator.BuildViolation(context.Background(), "", "").CreateViolation(), ), expectedJSON: `[{"code": "", "message": ""}]`, }, { name: "one full violation", list: validation.NewViolationList( - validator.BuildViolation("code", "message"). + validator.BuildViolation(context.Background(), "code", "message"). SetParameters(validation.TemplateParameter{Key: "key", Value: "value"}). SetPropertyPath( validation.NewPropertyPath( @@ -363,7 +364,7 @@ func TestMarshalViolationListToJSON(t *testing.T) { { name: "two violations", list: validation.NewViolationList( - validator.BuildViolation("code", "message"). + validator.BuildViolation(context.Background(), "code", "message"). SetParameters(validation.TemplateParameter{Key: "key", Value: "value"}). SetPropertyPath( validation.NewPropertyPath( @@ -372,7 +373,7 @@ func TestMarshalViolationListToJSON(t *testing.T) { validation.PropertyNameElement("name"), ), ).CreateViolation(), - validator.BuildViolation("code", "message"). + validator.BuildViolation(context.Background(), "code", "message"). SetParameters(validation.TemplateParameter{Key: "key", Value: "value"}). SetPropertyPath( validation.NewPropertyPath( @@ -410,7 +411,7 @@ func TestMarshalViolationListToJSON(t *testing.T) { func newViolationWithCode(t *testing.T, code string) validation.Violation { t.Helper() validator := newValidator(t) - violation := validator.BuildViolation(code, "").CreateViolation() + violation := validator.BuildViolation(context.Background(), code, "").CreateViolation() return violation } @@ -419,7 +420,7 @@ func newViolationList(t *testing.T, codes ...string) *validation.ViolationList { validator := newValidator(t) violations := validation.NewViolationList() for _, code := range codes { - violation := validator.BuildViolation(code, "").CreateViolation() + violation := validator.BuildViolation(context.Background(), code, "").CreateViolation() violations.Append(violation) } return violations From a07fc134a8a6347e69482a5ea539b1afe56ee404 Mon Sep 17 00:00:00 2001 From: Igor Lazarev Date: Wed, 4 Aug 2021 19:37:19 +0300 Subject: [PATCH 3/4] nillable arguments separated --- arguments.go | 76 ++++++--- example_context_with_recursion_test.go | 2 +- example_custom_constraint_test.go | 2 +- example_custom_service_constraint_test.go | 2 +- example_http_handler_test.go | 6 +- example_partial_entity_validation_test.go | 20 ++- example_test.go | 184 +++++++++++++++------- example_validatable_slice_test.go | 6 +- example_validatable_struct_test.go | 6 +- it/example_test.go | 66 ++++---- test/benchmark_test.go | 6 +- test/constraints_errors_test.go | 9 +- test/constraints_test.go | 20 +-- test/mocks_test.go | 4 +- test/path_test.go | 16 +- test/translations_test.go | 21 ++- test/validatable_test.go | 36 +++-- test/validate_aliases_test.go | 41 +++++ test/validate_arguments_test.go | 19 ++- test/validate_control_constraints_test.go | 118 +++++++------- test/validate_each_test.go | 6 +- test/validate_iterable_test.go | 16 +- test/validate_value_test.go | 5 +- test/validator_option_test.go | 4 +- test/validator_store_test.go | 12 +- validator.go | 33 ++-- validator/validator.go | 22 +-- 27 files changed, 469 insertions(+), 289 deletions(-) create mode 100644 test/validate_aliases_test.go diff --git a/arguments.go b/arguments.go index 76e7950..29d9465 100644 --- a/arguments.go +++ b/arguments.go @@ -1,7 +1,6 @@ package validation import ( - "context" "fmt" "time" @@ -54,19 +53,33 @@ func PropertyValue(name string, value interface{}, options ...Option) Argument { } // Bool argument is used to validate boolean values. -func Bool(value *bool, options ...Option) Argument { +func Bool(value bool, options ...Option) Argument { return argumentFunc(func(arguments *Arguments) error { - arguments.addValidator(newBoolValidator(value, options)) + arguments.addValidator(newBoolValidator(&value, options)) return nil }) } // BoolProperty argument is an alias for Bool that automatically adds property name to the current scope. -func BoolProperty(name string, value *bool, options ...Option) Argument { +func BoolProperty(name string, value bool, options ...Option) Argument { return Bool(value, append([]Option{PropertyName(name)}, options...)...) } +// NilBool argument is used to validate nillable boolean values. +func NilBool(value *bool, options ...Option) Argument { + return argumentFunc(func(arguments *Arguments) error { + arguments.addValidator(newBoolValidator(value, options)) + + return nil + }) +} + +// NilBoolProperty argument is an alias for NilBool that automatically adds property name to the current scope. +func NilBoolProperty(name string, value *bool, options ...Option) Argument { + return NilBool(value, append([]Option{PropertyName(name)}, options...)...) +} + // Number argument is used to validate numbers (any types of integers or floats). At the moment it uses // reflection to detect numeric value. Given value is internally converted into int64 or float64 to make comparisons. // @@ -90,19 +103,33 @@ func NumberProperty(name string, value interface{}, options ...Option) Argument } // String argument is used to validate strings. -func String(value *string, options ...Option) Argument { +func String(value string, options ...Option) Argument { return argumentFunc(func(arguments *Arguments) error { - arguments.addValidator(newStringValidator(value, options)) + arguments.addValidator(newStringValidator(&value, options)) return nil }) } // StringProperty argument is an alias for String that automatically adds property name to the current scope. -func StringProperty(name string, value *string, options ...Option) Argument { +func StringProperty(name string, value string, options ...Option) Argument { return String(value, append([]Option{PropertyName(name)}, options...)...) } +// NilString argument is used to validate nillable strings. +func NilString(value *string, options ...Option) Argument { + return argumentFunc(func(arguments *Arguments) error { + arguments.addValidator(newStringValidator(value, options)) + + return nil + }) +} + +// NilStringProperty argument is an alias for NilString that automatically adds property name to the current scope. +func NilStringProperty(name string, value *string, options ...Option) Argument { + return NilString(value, append([]Option{PropertyName(name)}, options...)...) +} + // Strings argument is used to validate slice of strings. func Strings(values []string, options ...Option) Argument { return argumentFunc(func(arguments *Arguments) error { @@ -156,19 +183,33 @@ func CountableProperty(name string, count int, options ...Option) Argument { } // Time argument is used to validate time.Time value. -func Time(value *time.Time, options ...Option) Argument { +func Time(value time.Time, options ...Option) Argument { return argumentFunc(func(arguments *Arguments) error { - arguments.addValidator(newTimeValidator(value, options)) + arguments.addValidator(newTimeValidator(&value, options)) return nil }) } // TimeProperty argument is an alias for Time that automatically adds property name to the current scope. -func TimeProperty(name string, value *time.Time, options ...Option) Argument { +func TimeProperty(name string, value time.Time, options ...Option) Argument { return Time(value, append([]Option{PropertyName(name)}, options...)...) } +// NilTime argument is used to validate nillable time.Time value. +func NilTime(value *time.Time, options ...Option) Argument { + return argumentFunc(func(arguments *Arguments) error { + arguments.addValidator(newTimeValidator(value, options)) + + return nil + }) +} + +// NilTimeProperty argument is an alias for NilTime that automatically adds property name to the current scope. +func NilTimeProperty(name string, value *time.Time, options ...Option) Argument { + return NilTime(value, append([]Option{PropertyName(name)}, options...)...) +} + // Each is used to validate each value of iterable (array, slice, or map). At the moment it uses reflection // to iterate over values. So you can expect a performance hit using this method. For better performance // it is recommended to make a custom type that implements the Validatable interface. Also, you can use @@ -222,21 +263,6 @@ func ValidProperty(name string, value Validatable, options ...Option) Argument { return Valid(value, append([]Option{PropertyName(name)}, options...)...) } -// Context can be used to pass context to validation constraints via scope. -// -// Example -// err := validator.Validate( -// Context(request.Context()), -// String(&s, it.IsNotBlank()), // now all called constraints will use passed context in their methods -// ) -func Context(ctx context.Context) Argument { - return argumentFunc(func(arguments *Arguments) error { - arguments.scope.context = ctx - - return nil - }) -} - // Language argument sets the current language for translation of a violation message. // // Example diff --git a/example_context_with_recursion_test.go b/example_context_with_recursion_test.go index 69bf628..4c7b890 100644 --- a/example_context_with_recursion_test.go +++ b/example_context_with_recursion_test.go @@ -85,7 +85,7 @@ func (p Property) Validate(ctx context.Context, validator *validation.Validator) contextWithNextNestingLevel(ctx), // Executing validation for maximum nesting level of properties. PropertyArgument(&p, ItIsNotDeeperThan(3)), - validation.StringProperty("name", &p.Name, it.IsNotBlank()), + validation.StringProperty("name", p.Name, it.IsNotBlank()), // This should run recursive validation for properties. validation.IterableProperty("properties", p.Properties), ) diff --git a/example_custom_constraint_test.go b/example_custom_constraint_test.go index 0f4c14c..1219d2e 100644 --- a/example_custom_constraint_test.go +++ b/example_custom_constraint_test.go @@ -48,7 +48,7 @@ func ExampleValidator_Validate_customConstraint() { err := validator.Validate( context.Background(), - validation.String(&s, it.IsNotBlank(), IsNumeric()), + validation.String(s, it.IsNotBlank(), IsNumeric()), ) fmt.Println(err) diff --git a/example_custom_service_constraint_test.go b/example_custom_service_constraint_test.go index c594172..d3aec1b 100644 --- a/example_custom_service_constraint_test.go +++ b/example_custom_service_constraint_test.go @@ -85,7 +85,7 @@ type StockItem struct { func (s StockItem) Validate(ctx context.Context, validator *validation.Validator) error { return validator.Validate( ctx, - validation.StringProperty("name", &s.Name, it.IsNotBlank(), it.HasMaxLength(20)), + validation.StringProperty("name", s.Name, it.IsNotBlank(), it.HasMaxLength(20)), validation.EachStringProperty("tags", s.Tags, validator.ValidateBy("isTagExists")), ) } diff --git a/example_http_handler_test.go b/example_http_handler_test.go index 9396e3d..a8ccb34 100644 --- a/example_http_handler_test.go +++ b/example_http_handler_test.go @@ -24,8 +24,8 @@ type Book struct { func (b Book) Validate(ctx context.Context, validator *validation.Validator) error { return validator.Validate( ctx, - validation.StringProperty("title", &b.Title, it.IsNotBlank()), - validation.StringProperty("author", &b.Author, it.IsNotBlank()), + validation.StringProperty("title", b.Title, it.IsNotBlank()), + validation.StringProperty("author", b.Author, it.IsNotBlank()), validation.CountableProperty("keywords", len(b.Keywords), it.HasCountBetween(1, 10)), validation.EachStringProperty("keywords", b.Keywords, it.IsNotBlank()), ) @@ -46,7 +46,7 @@ func HandleBooks(writer http.ResponseWriter, request *http.Request) { return } - err = validator.ValidateValidatable(request.Context(), book) + err = validator.Validate(request.Context(), validation.Valid(book)) if err != nil { violations, ok := validation.UnwrapViolationList(err) if ok { diff --git a/example_partial_entity_validation_test.go b/example_partial_entity_validation_test.go index f589137..cfb4057 100644 --- a/example_partial_entity_validation_test.go +++ b/example_partial_entity_validation_test.go @@ -23,7 +23,7 @@ type File struct { func (f File) Validate(ctx context.Context, validator *validation.Validator) error { return validator.Validate( ctx, - validation.StringProperty("name", &f.Name, it.HasLengthBetween(5, 50)), + validation.StringProperty("name", f.Name, it.HasLengthBetween(5, 50)), ) } @@ -80,10 +80,12 @@ func (c AllowedFileExtensionConstraint) ValidateFile(file *File, scope validatio extension := strings.ReplaceAll(filepath.Ext(file.Name), ".", "") - return scope.Validator().AtProperty("name").ValidateString( + return scope.Validator().AtProperty("name").Validate( scope.Context(), - &extension, - it.IsOneOfStrings(c.extensions...).Message("Not allowed extension. Must be one of: {{ choices }}."), + validation.String( + extension, + it.IsOneOfStrings(c.extensions...).Message("Not allowed extension. Must be one of: {{ choices }}."), + ), ) } @@ -113,11 +115,13 @@ func (c AllowedFileSizeConstraint) ValidateFile(file *File, scope validation.Sco size := len(file.Data) - return scope.Validator().ValidateNumber( + return scope.Validator().Validate( scope.Context(), - size, - it.IsGreaterThanInteger(c.minSize).Message("File size is too small."), - it.IsLessThanInteger(c.maxSize).Message("File size is too large."), + validation.Number( + size, + it.IsGreaterThanInteger(c.minSize).Message("File size is too small."), + it.IsLessThanInteger(c.maxSize).Message("File size is too large."), + ), ) } diff --git a/example_test.go b/example_test.go index 7449d53..cd14b5a 100644 --- a/example_test.go +++ b/example_test.go @@ -38,7 +38,7 @@ func ExamplePropertyValue() { func ExampleBool() { v := false - err := validator.Validate(context.Background(), validation.Bool(&v, it.IsTrue())) + err := validator.Validate(context.Background(), validation.Bool(v, it.IsTrue())) fmt.Println(err) // Output: // violation: This value should be true. @@ -52,7 +52,30 @@ func ExampleBoolProperty() { } err := validator.Validate( context.Background(), - validation.BoolProperty("isPublished", &v.IsPublished, it.IsTrue()), + validation.BoolProperty("isPublished", v.IsPublished, it.IsTrue()), + ) + fmt.Println(err) + // Output: + // violation at 'isPublished': This value should be true. +} + +func ExampleNilBool() { + v := false + err := validator.Validate(context.Background(), validation.NilBool(&v, it.IsTrue())) + fmt.Println(err) + // Output: + // violation: This value should be true. +} + +func ExampleNilBoolProperty() { + v := struct { + IsPublished bool + }{ + IsPublished: false, + } + err := validator.Validate( + context.Background(), + validation.NilBoolProperty("isPublished", &v.IsPublished, it.IsTrue()), ) fmt.Println(err) // Output: @@ -89,7 +112,7 @@ func ExampleString() { v := "" err := validator.Validate( context.Background(), - validation.String(&v, it.IsNotBlank()), + validation.String(v, it.IsNotBlank()), ) fmt.Println(err) // Output: @@ -100,7 +123,29 @@ func ExampleStringProperty() { v := Book{Title: ""} err := validator.Validate( context.Background(), - validation.StringProperty("title", &v.Title, it.IsNotBlank()), + validation.StringProperty("title", v.Title, it.IsNotBlank()), + ) + fmt.Println(err) + // Output: + // violation at 'title': This value should not be blank. +} + +func ExampleNilString() { + v := "" + err := validator.Validate( + context.Background(), + validation.NilString(&v, it.IsNotBlank()), + ) + fmt.Println(err) + // Output: + // violation: This value should not be blank. +} + +func ExampleNilStringProperty() { + v := Book{Title: ""} + err := validator.Validate( + context.Background(), + validation.NilStringProperty("title", &v.Title, it.IsNotBlank()), ) fmt.Println(err) // Output: @@ -178,7 +223,7 @@ func ExampleTime() { compared, _ := time.Parse(time.RFC3339, "2006-01-02T15:00:00Z") err := validator.Validate( context.Background(), - validation.Time(&t, it.IsEarlierThan(compared)), + validation.Time(t, it.IsEarlierThan(compared)), ) fmt.Println(err) // Output: @@ -194,7 +239,35 @@ func ExampleTimeProperty() { compared, _ := time.Parse(time.RFC3339, "2006-01-02T15:00:00Z") err := validator.Validate( context.Background(), - validation.TimeProperty("createdAt", &v.CreatedAt, it.IsEarlierThan(compared)), + validation.TimeProperty("createdAt", v.CreatedAt, it.IsEarlierThan(compared)), + ) + fmt.Println(err) + // Output: + // violation at 'createdAt': This value should be earlier than 2006-01-02T15:00:00Z. +} + +func ExampleNilTime() { + t := time.Now() + compared, _ := time.Parse(time.RFC3339, "2006-01-02T15:00:00Z") + err := validator.Validate( + context.Background(), + validation.NilTime(&t, it.IsEarlierThan(compared)), + ) + fmt.Println(err) + // Output: + // violation: This value should be earlier than 2006-01-02T15:00:00Z. +} + +func ExampleNilTimeProperty() { + v := struct { + CreatedAt time.Time + }{ + CreatedAt: time.Now(), + } + compared, _ := time.Parse(time.RFC3339, "2006-01-02T15:00:00Z") + err := validator.Validate( + context.Background(), + validation.NilTimeProperty("createdAt", &v.CreatedAt, it.IsEarlierThan(compared)), ) fmt.Println(err) // Output: @@ -257,7 +330,7 @@ func ExampleNewCustomStringConstraint() { ) s := "foo" - err := validator.ValidateString(context.Background(), &s, constraint) + err := validator.Validate(context.Background(), validation.String(s, constraint)) fmt.Println(err) // Output: @@ -282,12 +355,12 @@ func ExampleWhen() { context.Background(), validation.StringProperty( "cardType", - &payment.CardType, + payment.CardType, it.IsOneOfStrings("Visa", "MasterCard"), ), validation.StringProperty( "cardNumber", - &payment.CardNumber, + payment.CardNumber, validation. When(payment.CardType == "Visa"). Then(it.Matches(visaRegex)). @@ -302,13 +375,12 @@ func ExampleWhen() { func ExampleConditionalConstraint_Then() { v := "foo" - err := validator.ValidateString( + err := validator.Validate( context.Background(), - &v, - validation.When(true). - Then( - it.Matches(regexp.MustCompile(`^\w+$`)), - ), + validation.String( + v, + validation.When(true).Then(it.Matches(regexp.MustCompile(`^\w+$`))), + ), ) fmt.Println(err) // Output: @@ -317,16 +389,14 @@ func ExampleConditionalConstraint_Then() { func ExampleConditionalConstraint_Else() { v := "123" - err := validator.ValidateString( - context.Background(), - &v, - validation.When(false). - Then( - it.Matches(regexp.MustCompile(`^\w+$`)), - ). - Else( - it.Matches(regexp.MustCompile(`^\d+$`)), - ), + err := validator.Validate( + context.Background(), + validation.String( + v, + validation.When(false). + Then(it.Matches(regexp.MustCompile(`^\w+$`))). + Else(it.Matches(regexp.MustCompile(`^\d+$`))), + ), ) fmt.Println(err) // Output: @@ -336,12 +406,14 @@ func ExampleConditionalConstraint_Else() { func ExampleSequentially() { title := "bar" - err := validator.ValidateString( + err := validator.Validate( context.Background(), - &title, - validation.Sequentially( - it.IsBlank(), - it.HasMinLength(5), + validation.String( + title, + validation.Sequentially( + it.IsBlank(), // validation will fail on first constraint + it.HasMinLength(5), // this constraint will be ignored + ), ), ) @@ -353,12 +425,14 @@ func ExampleSequentially() { func ExampleAtLeastOneOf() { title := "bar" - err := validator.ValidateString( + err := validator.Validate( context.Background(), - &title, - validation.AtLeastOneOf( - it.IsBlank(), - it.HasMinLength(5), + validation.String( + title, + validation.AtLeastOneOf( + it.IsBlank(), + it.HasMinLength(5), + ), ), ) @@ -376,10 +450,9 @@ func ExampleCompound() { title := "bar" isEmail := validation.Compound(it.IsEmail(), it.HasLengthBetween(5, 200)) - err := validator.ValidateString( + err := validator.Validate( context.Background(), - &title, - isEmail, + validation.String(title, isEmail), ) if violations, ok := validation.UnwrapViolationList(err); ok { @@ -399,7 +472,7 @@ func ExampleValidator_Validate_basicValidation() { if err != nil { log.Fatal(err) } - err = validator.Validate(context.Background(), validation.String(&s, it.IsNotBlank())) + err = validator.Validate(context.Background(), validation.String(s, it.IsNotBlank())) fmt.Println(err) // Output: @@ -409,7 +482,7 @@ func ExampleValidator_Validate_basicValidation() { func ExampleValidator_Validate_singletonValidator() { s := "" - err := validator.Validate(context.Background(), validation.String(&s, it.IsNotBlank())) + err := validator.Validate(context.Background(), validation.String(s, it.IsNotBlank())) fmt.Println(err) // Output: @@ -417,9 +490,7 @@ func ExampleValidator_Validate_singletonValidator() { } func ExampleValidator_ValidateString_shorthandAlias() { - s := "" - - err := validator.ValidateString(context.Background(), &s, it.IsNotBlank()) + err := validator.ValidateString(context.Background(), "", it.IsNotBlank()) fmt.Println(err) // Output: @@ -437,7 +508,7 @@ func ExampleValidator_Validate_basicStructValidation() { err := validator.Validate( context.Background(), - validation.StringProperty("title", &document.Title, it.IsNotBlank()), + validation.StringProperty("title", document.Title, it.IsNotBlank()), validation.CountableProperty("keywords", len(document.Keywords), it.HasCountBetween(5, 10)), validation.StringsProperty("keywords", document.Keywords, it.HasUniqueValues()), validation.EachStringProperty("keywords", document.Keywords, it.IsNotBlank()), @@ -468,8 +539,8 @@ func ExampleValidator_Validate_conditionalValidationOnConstraint() { for i, note := range notes { err := validator.Validate( context.Background(), - validation.StringProperty("name", ¬e.Title, it.IsNotBlank()), - validation.StringProperty("text", ¬e.Text, it.IsNotBlank().When(note.IsPublic)), + validation.StringProperty("name", note.Title, it.IsNotBlank()), + validation.StringProperty("text", note.Text, it.IsNotBlank().When(note.IsPublic)), ) if violations, ok := validation.UnwrapViolationList(err); ok { for violation := violations.First(); violation != nil; violation = violation.Next() { @@ -488,7 +559,7 @@ func ExampleValidator_Validate_passingPropertyPathViaOptions() { err := validator.Validate( context.Background(), validation.String( - &s, + s, validation.PropertyName("properties"), validation.ArrayIndex(1), validation.PropertyName("tag"), @@ -509,7 +580,7 @@ func ExampleValidator_Validate_propertyPathWithScopedValidator() { AtProperty("properties"). AtIndex(1). AtProperty("tag"). - Validate(context.Background(), validation.String(&s, it.IsNotBlank())) + Validate(context.Background(), validation.String(s, it.IsNotBlank())) violation := err.(*validation.ViolationList).First() fmt.Println("property path:", violation.PropertyPath().String()) @@ -524,7 +595,7 @@ func ExampleValidator_Validate_propertyPathBySpecialArgument() { context.Background(), // this is an alias for // validation.String(&s, validation.PropertyName("property"), it.IsNotBlank()), - validation.StringProperty("property", &s, it.IsNotBlank()), + validation.StringProperty("property", s, it.IsNotBlank()), ) violation := err.(*validation.ViolationList).First() @@ -538,7 +609,7 @@ func ExampleValidator_AtProperty() { err := validator.AtProperty("book").Validate( context.Background(), - validation.StringProperty("title", &book.Title, it.IsNotBlank()), + validation.StringProperty("title", book.Title, it.IsNotBlank()), ) violation := err.(*validation.ViolationList).First() @@ -552,7 +623,7 @@ func ExampleValidator_AtIndex() { err := validator.AtIndex(0).Validate( context.Background(), - validation.StringProperty("title", &books[0].Title, it.IsNotBlank()), + validation.StringProperty("title", books[0].Title, it.IsNotBlank()), ) violation := err.(*validation.ViolationList).First() @@ -571,7 +642,7 @@ func ExampleValidator_Validate_translationsByDefaultLanguage() { } s := "" - err = validator.ValidateString(context.Background(), &s, it.IsNotBlank()) + err = validator.Validate(context.Background(), validation.String(s, it.IsNotBlank())) fmt.Println(err) // Output: @@ -590,7 +661,7 @@ func ExampleValidator_Validate_translationsByArgument() { err = validator.Validate( context.Background(), validation.Language(language.Russian), - validation.String(&s, it.IsNotBlank()), + validation.String(s, it.IsNotBlank()), ) fmt.Println(err) @@ -610,7 +681,7 @@ func ExampleValidator_Validate_translationsByContextArgument() { ctx := language.WithContext(context.Background(), language.Russian) err = validator.Validate( ctx, - validation.String(&s, it.IsNotBlank()), + validation.String(s, it.IsNotBlank()), ) fmt.Println(err) @@ -621,7 +692,10 @@ func ExampleValidator_Validate_translationsByContextArgument() { func ExampleValidator_Validate_customizingErrorMessage() { s := "" - err := validator.ValidateString(context.Background(), &s, it.IsNotBlank().Message("this value is required")) + err := validator.Validate( + context.Background(), + validation.String(s, it.IsNotBlank().Message("this value is required")), + ) fmt.Println(err) // Output: diff --git a/example_validatable_slice_test.go b/example_validatable_slice_test.go index a8392f3..8d5c716 100644 --- a/example_validatable_slice_test.go +++ b/example_validatable_slice_test.go @@ -22,8 +22,8 @@ func (companies Companies) Validate(ctx context.Context, validator *validation.V for i, company := range companies { err := validator.AtIndex(i).Validate( ctx, - validation.StringProperty("name", &company.Name, it.IsNotBlank()), - validation.StringProperty("address", &company.Address, it.IsNotBlank(), it.HasMinLength(3)), + validation.StringProperty("name", company.Name, it.IsNotBlank()), + validation.StringProperty("address", company.Address, it.IsNotBlank(), it.HasMinLength(3)), ) // appending violations from err err = violations.AppendFromError(err) @@ -44,7 +44,7 @@ func ExampleValidator_ValidateValidatable_validatableSlice() { {"", "x"}, } - err := validator.ValidateValidatable(context.Background(), companies) + err := validator.Validate(context.Background(), validation.Valid(companies)) if violations, ok := validation.UnwrapViolationList(err); ok { for violation := violations.First(); violation != nil; violation = violation.Next() { diff --git a/example_validatable_struct_test.go b/example_validatable_struct_test.go index ff6cd35..f98735d 100644 --- a/example_validatable_struct_test.go +++ b/example_validatable_struct_test.go @@ -18,7 +18,7 @@ type Product struct { func (p Product) Validate(ctx context.Context, validator *validation.Validator) error { return validator.Validate( ctx, - validation.StringProperty("name", &p.Name, it.IsNotBlank()), + validation.StringProperty("name", p.Name, it.IsNotBlank()), validation.CountableProperty("tags", len(p.Tags), it.HasMinCount(5)), validation.StringsProperty("tags", p.Tags, it.HasUniqueValues()), validation.EachStringProperty("tags", p.Tags, it.IsNotBlank()), @@ -36,7 +36,7 @@ type Component struct { func (c Component) Validate(ctx context.Context, validator *validation.Validator) error { return validator.Validate( ctx, - validation.StringProperty("name", &c.Name, it.IsNotBlank()), + validation.StringProperty("name", c.Name, it.IsNotBlank()), validation.CountableProperty("tags", len(c.Tags), it.HasMinCount(1)), ) } @@ -53,7 +53,7 @@ func ExampleValidator_ValidateValidatable_validatableStruct() { }, } - err := validator.ValidateValidatable(context.Background(), p) + err := validator.Validate(context.Background(), validation.Valid(p)) if violations, ok := validation.UnwrapViolationList(err); ok { for violation := violations.First(); violation != nil; violation = violation.Next() { diff --git a/it/example_test.go b/it/example_test.go index ad78a9a..441bcee 100644 --- a/it/example_test.go +++ b/it/example_test.go @@ -5,13 +5,17 @@ import ( "fmt" "net" + "github.com/muonsoft/validation" "github.com/muonsoft/validation/it" "github.com/muonsoft/validation/validator" ) func ExampleHasUniqueValues() { v := []string{"foo", "bar", "baz", "foo"} - err := validator.ValidateStrings(context.Background(), v, it.HasUniqueValues()) + err := validator.Validate( + context.Background(), + validation.Strings(v, it.HasUniqueValues()), + ) fmt.Println(err) // Output: // violation: This collection should contain only unique elements. @@ -19,7 +23,7 @@ func ExampleHasUniqueValues() { func ExampleIsJSON_validJSON() { v := `{"valid": true}` - err := validator.ValidateString(context.Background(), &v, it.IsJSON()) + err := validator.Validate(context.Background(), validation.String(v, it.IsJSON())) fmt.Println(err) // Output: // @@ -27,7 +31,7 @@ func ExampleIsJSON_validJSON() { func ExampleIsJSON_invalidJSON() { v := `"invalid": true` - err := validator.ValidateString(context.Background(), &v, it.IsJSON()) + err := validator.Validate(context.Background(), validation.String(v, it.IsJSON())) fmt.Println(err) // Output: // violation: This value should be valid JSON. @@ -35,7 +39,7 @@ func ExampleIsJSON_invalidJSON() { func ExampleIsEmail_validEmail() { v := "user@example.com" - err := validator.ValidateString(context.Background(), &v, it.IsEmail()) + err := validator.Validate(context.Background(), validation.String(v, it.IsEmail())) fmt.Println(err) // Output: // @@ -43,7 +47,7 @@ func ExampleIsEmail_validEmail() { func ExampleIsEmail_invalidEmail() { v := "user example.com" - err := validator.ValidateString(context.Background(), &v, it.IsEmail()) + err := validator.Validate(context.Background(), validation.String(v, it.IsEmail())) fmt.Println(err) // Output: // violation: This value is not a valid email address. @@ -51,7 +55,7 @@ func ExampleIsEmail_invalidEmail() { func ExampleIsHTML5Email_validEmail() { v := "{}~!@example.com" - err := validator.ValidateString(context.Background(), &v, it.IsEmail()) + err := validator.Validate(context.Background(), validation.String(v, it.IsEmail())) fmt.Println(err) // Output: // @@ -59,7 +63,7 @@ func ExampleIsHTML5Email_validEmail() { func ExampleIsHTML5Email_invalidEmail() { v := "@example.com" - err := validator.ValidateString(context.Background(), &v, it.IsEmail()) + err := validator.Validate(context.Background(), validation.String(v, it.IsEmail())) fmt.Println(err) // Output: // violation: This value is not a valid email address. @@ -67,7 +71,7 @@ func ExampleIsHTML5Email_invalidEmail() { func ExampleIsHostname_validHostname() { v := "example.com" - err := validator.ValidateString(context.Background(), &v, it.IsHostname()) + err := validator.Validate(context.Background(), validation.String(v, it.IsHostname())) fmt.Println(err) // Output: // @@ -75,7 +79,7 @@ func ExampleIsHostname_validHostname() { func ExampleIsHostname_invalidHostname() { v := "example-.com" - err := validator.ValidateString(context.Background(), &v, it.IsHostname()) + err := validator.Validate(context.Background(), validation.String(v, it.IsHostname())) fmt.Println(err) // Output: // violation: This value is not a valid hostname. @@ -83,7 +87,7 @@ func ExampleIsHostname_invalidHostname() { func ExampleIsHostname_reservedHostname() { v := "example.localhost" - err := validator.ValidateString(context.Background(), &v, it.IsHostname()) + err := validator.Validate(context.Background(), validation.String(v, it.IsHostname())) fmt.Println(err) // Output: // violation: This value is not a valid hostname. @@ -91,7 +95,7 @@ func ExampleIsHostname_reservedHostname() { func ExampleIsLooseHostname_validHostname() { v := "example.com" - err := validator.ValidateString(context.Background(), &v, it.IsLooseHostname()) + err := validator.Validate(context.Background(), validation.String(v, it.IsLooseHostname())) fmt.Println(err) // Output: // @@ -99,7 +103,7 @@ func ExampleIsLooseHostname_validHostname() { func ExampleIsLooseHostname_invalidHostname() { v := "example-.com" - err := validator.ValidateString(context.Background(), &v, it.IsLooseHostname()) + err := validator.Validate(context.Background(), validation.String(v, it.IsLooseHostname())) fmt.Println(err) // Output: // violation: This value is not a valid hostname. @@ -107,7 +111,7 @@ func ExampleIsLooseHostname_invalidHostname() { func ExampleIsLooseHostname_reservedHostname() { v := "example.localhost" - err := validator.ValidateString(context.Background(), &v, it.IsLooseHostname()) + err := validator.Validate(context.Background(), validation.String(v, it.IsLooseHostname())) fmt.Println(err) // Output: // @@ -115,7 +119,7 @@ func ExampleIsLooseHostname_reservedHostname() { func ExampleIsURL_validURL() { v := "http://example.com" - err := validator.ValidateString(context.Background(), &v, it.IsURL()) + err := validator.Validate(context.Background(), validation.String(v, it.IsURL())) fmt.Println(err) // Output: // @@ -123,7 +127,7 @@ func ExampleIsURL_validURL() { func ExampleIsURL_invalidURL() { v := "example.com" - err := validator.ValidateString(context.Background(), &v, it.IsURL()) + err := validator.Validate(context.Background(), validation.String(v, it.IsURL())) fmt.Println(err) // Output: // violation: This value is not a valid URL. @@ -131,7 +135,7 @@ func ExampleIsURL_invalidURL() { func ExampleURLConstraint_WithRelativeSchema() { v := "//example.com" - err := validator.ValidateString(context.Background(), &v, it.IsURL().WithRelativeSchema()) + err := validator.Validate(context.Background(), validation.String(v, it.IsURL().WithRelativeSchema())) fmt.Println(err) // Output: // @@ -139,7 +143,7 @@ func ExampleURLConstraint_WithRelativeSchema() { func ExampleURLConstraint_WithSchemas() { v := "ftp://example.com" - err := validator.ValidateString(context.Background(), &v, it.IsURL().WithSchemas("http", "https", "ftp")) + err := validator.Validate(context.Background(), validation.String(v, it.IsURL().WithSchemas("http", "https", "ftp"))) fmt.Println(err) // Output: // @@ -147,7 +151,7 @@ func ExampleURLConstraint_WithSchemas() { func ExampleIsIP_validIP() { v := "123.123.123.123" - err := validator.ValidateString(context.Background(), &v, it.IsIP()) + err := validator.Validate(context.Background(), validation.String(v, it.IsIP())) fmt.Println(err) // Output: // @@ -155,7 +159,7 @@ func ExampleIsIP_validIP() { func ExampleIsIP_invalidIP() { v := "123.123.123.345" - err := validator.ValidateString(context.Background(), &v, it.IsIP()) + err := validator.Validate(context.Background(), validation.String(v, it.IsIP())) fmt.Println(err) // Output: // violation: This is not a valid IP address. @@ -163,7 +167,7 @@ func ExampleIsIP_invalidIP() { func ExampleIsIPv4_validIP() { v := "123.123.123.123" - err := validator.ValidateString(context.Background(), &v, it.IsIPv4()) + err := validator.Validate(context.Background(), validation.String(v, it.IsIPv4())) fmt.Println(err) // Output: // @@ -171,7 +175,7 @@ func ExampleIsIPv4_validIP() { func ExampleIsIPv4_invalidIP() { v := "123.123.123.345" - err := validator.ValidateString(context.Background(), &v, it.IsIPv4()) + err := validator.Validate(context.Background(), validation.String(v, it.IsIPv4())) fmt.Println(err) // Output: // violation: This is not a valid IP address. @@ -179,7 +183,7 @@ func ExampleIsIPv4_invalidIP() { func ExampleIsIPv6_validIP() { v := "2001:0db8:85a3:0000:0000:8a2e:0370:7334" - err := validator.ValidateString(context.Background(), &v, it.IsIPv6()) + err := validator.Validate(context.Background(), validation.String(v, it.IsIPv6())) fmt.Println(err) // Output: // @@ -187,7 +191,7 @@ func ExampleIsIPv6_validIP() { func ExampleIsIPv6_invalidIP() { v := "z001:0db8:85a3:0000:0000:8a2e:0370:7334" - err := validator.ValidateString(context.Background(), &v, it.IsIPv6()) + err := validator.Validate(context.Background(), validation.String(v, it.IsIPv6())) fmt.Println(err) // Output: // violation: This is not a valid IP address. @@ -195,7 +199,7 @@ func ExampleIsIPv6_invalidIP() { func ExampleIPConstraint_DenyPrivateIP_restrictedPrivateIPv4() { v := "192.168.1.0" - err := validator.ValidateString(context.Background(), &v, it.IsIP().DenyPrivateIP()) + err := validator.Validate(context.Background(), validation.String(v, it.IsIP().DenyPrivateIP())) fmt.Println(err) // Output: // violation: This IP address is prohibited to use. @@ -203,7 +207,7 @@ func ExampleIPConstraint_DenyPrivateIP_restrictedPrivateIPv4() { func ExampleIPConstraint_DenyPrivateIP_restrictedPrivateIPv6() { v := "fdfe:dcba:9876:ffff:fdc6:c46b:bb8f:7d4c" - err := validator.ValidateString(context.Background(), &v, it.IsIPv6().DenyPrivateIP()) + err := validator.Validate(context.Background(), validation.String(v, it.IsIPv6().DenyPrivateIP())) fmt.Println(err) // Output: // violation: This IP address is prohibited to use. @@ -211,9 +215,15 @@ func ExampleIPConstraint_DenyPrivateIP_restrictedPrivateIPv6() { func ExampleIPConstraint_DenyIP() { v := "127.0.0.1" - err := validator.ValidateString(context.Background(), &v, it.IsIP().DenyIP(func(ip net.IP) bool { - return ip.IsLoopback() - })) + err := validator.Validate( + context.Background(), + validation.String( + v, + it.IsIP().DenyIP(func(ip net.IP) bool { + return ip.IsLoopback() + }), + ), + ) fmt.Println(err) // Output: // violation: This IP address is prohibited to use. diff --git a/test/benchmark_test.go b/test/benchmark_test.go index 2e9091f..60618a1 100644 --- a/test/benchmark_test.go +++ b/test/benchmark_test.go @@ -18,7 +18,7 @@ type Properties []Property func (p Property) Validate(ctx context.Context, validator *validation.Validator) error { return validator.Validate( ctx, - validation.StringProperty("name", &p.Name, it.IsNotBlank()), + validation.StringProperty("name", p.Name, it.IsNotBlank()), validation.ValidProperty("properties", p.Properties), ) } @@ -27,7 +27,7 @@ func (properties Properties) Validate(ctx context.Context, validator *validation violations := validation.ViolationList{} for i := range properties { - err := validator.AtIndex(i).ValidateValidatable(ctx, properties[i]) + err := validator.AtIndex(i).Validate(ctx, validation.Valid(properties[i])) err = violations.AppendFromError(err) if err != nil { return err @@ -48,7 +48,7 @@ func BenchmarkViolationsGeneration(b *testing.B) { b.ReportAllocs() b.ResetTimer() for i := 0; i < b.N; i++ { - validator.ValidateValidatable(context.Background(), properties) + validator.Validate(context.Background(), validation.Valid(properties)) } } diff --git a/test/constraints_errors_test.go b/test/constraints_errors_test.go index aa95cd2..f28637c 100644 --- a/test/constraints_errors_test.go +++ b/test/constraints_errors_test.go @@ -4,6 +4,7 @@ import ( "context" "errors" "testing" + "time" "github.com/muonsoft/validation" "github.com/muonsoft/validation/validator" @@ -45,12 +46,12 @@ func TestValidator_Validate_WhenInapplicableConstraint_ExpectError(t *testing.T) valueType string argument validation.Argument }{ - {boolType, validation.Bool(nil, nilConstraint{})}, + {boolType, validation.Bool(false, nilConstraint{})}, {"number", validation.Number(intValue(0), nilConstraint{})}, - {stringType, validation.String(nilString, nilConstraint{})}, + {stringType, validation.String("", nilConstraint{})}, {iterableType, validation.Iterable([]string{}, nilConstraint{})}, {countableType, validation.Countable(0, nilConstraint{})}, - {timeType, validation.Time(nil, nilConstraint{})}, + {timeType, validation.Time(time.Time{}, nilConstraint{})}, } for _, test := range tests { t.Run(test.valueType, func(t *testing.T) { @@ -96,7 +97,7 @@ func TestValidator_Validate_WhenInvalidConstraintAtPropertyPath_ExpectErrorWithP err := validator.Validate( context.Background(), validation.String( - nil, + "", validation.PropertyName("properties"), validation.ArrayIndex(1), validation.PropertyName("error"), diff --git a/test/constraints_test.go b/test/constraints_test.go index 8ac0ab0..d7d1547 100644 --- a/test/constraints_test.go +++ b/test/constraints_test.go @@ -77,7 +77,7 @@ func TestValidateBool(t *testing.T) { } t.Run(test.name, func(t *testing.T) { - err := validator.ValidateBool(context.Background(), test.boolValue, test.constraint) + err := validator.Validate(context.Background(), validation.NilBool(test.boolValue, test.constraint)) test.assert(t, err) }) @@ -91,7 +91,7 @@ func TestValidateNumber_AsInt(t *testing.T) { } t.Run(test.name, func(t *testing.T) { - err := validator.ValidateNumber(context.Background(), test.intValue, test.constraint) + err := validator.Validate(context.Background(), validation.Number(test.intValue, test.constraint)) test.assert(t, err) }) @@ -105,7 +105,7 @@ func TestValidateNumber_AsFloat(t *testing.T) { } t.Run(test.name, func(t *testing.T) { - err := validator.ValidateNumber(context.Background(), test.floatValue, test.constraint) + err := validator.Validate(context.Background(), validation.Number(test.floatValue, test.constraint)) test.assert(t, err) }) @@ -119,7 +119,7 @@ func TestValidateString(t *testing.T) { } t.Run(test.name, func(t *testing.T) { - err := validator.ValidateString(context.Background(), test.stringValue, test.constraint) + err := validator.Validate(context.Background(), validation.NilString(test.stringValue, test.constraint)) test.assert(t, err) }) @@ -133,7 +133,7 @@ func TestValidateStrings(t *testing.T) { } t.Run(test.name, func(t *testing.T) { - err := validator.ValidateStrings(context.Background(), test.stringsValue, test.constraint) + err := validator.Validate(context.Background(), validation.Strings(test.stringsValue, test.constraint)) test.assert(t, err) }) @@ -147,7 +147,7 @@ func TestValidateIterable_AsSlice(t *testing.T) { } t.Run(test.name, func(t *testing.T) { - err := validator.ValidateIterable(context.Background(), test.sliceValue, test.constraint) + err := validator.Validate(context.Background(), validation.Iterable(test.sliceValue, test.constraint)) test.assert(t, err) }) @@ -161,7 +161,7 @@ func TestValidateIterable_AsMap(t *testing.T) { } t.Run(test.name, func(t *testing.T) { - err := validator.ValidateIterable(context.Background(), test.mapValue, test.constraint) + err := validator.Validate(context.Background(), validation.Iterable(test.mapValue, test.constraint)) test.assert(t, err) }) @@ -175,7 +175,7 @@ func TestValidateCountable(t *testing.T) { } t.Run(test.name, func(t *testing.T) { - err := validator.ValidateCountable(context.Background(), len(test.sliceValue), test.constraint) + err := validator.Validate(context.Background(), validation.Countable(len(test.sliceValue), test.constraint)) test.assert(t, err) }) @@ -189,7 +189,7 @@ func TestValidateTime(t *testing.T) { } t.Run(test.name, func(t *testing.T) { - err := validator.ValidateTime(context.Background(), test.timeValue, test.constraint) + err := validator.Validate(context.Background(), validation.NilTime(test.timeValue, test.constraint)) test.assert(t, err) }) @@ -214,7 +214,7 @@ func TestValidateNil(t *testing.T) { t.Run(test.name, func(t *testing.T) { var v *bool - err := validator.ValidateValue(context.Background(), v, test.nilConstraint) + err := validator.Validate(context.Background(), validation.Value(v, test.nilConstraint)) test.assert(t, err) }) diff --git a/test/mocks_test.go b/test/mocks_test.go index e8dbe8b..d3071bd 100644 --- a/test/mocks_test.go +++ b/test/mocks_test.go @@ -113,7 +113,7 @@ func (mock mockValidatableString) Validate(ctx context.Context, validator *valid return validator.Validate( ctx, validation.String( - &mock.value, + mock.value, validation.PropertyName("value"), it.IsNotBlank(), ), @@ -141,7 +141,7 @@ func (mock mockValidatableStruct) Validate(ctx context.Context, validator *valid it.IsNotBlank(), ), validation.String( - &mock.stringValue, + mock.stringValue, validation.PropertyName("stringValue"), it.IsNotBlank(), ), diff --git a/test/path_test.go b/test/path_test.go index cc2febb..50e6cc7 100644 --- a/test/path_test.go +++ b/test/path_test.go @@ -20,12 +20,12 @@ func (p property) Validate(ctx context.Context, validator *validation.Validator) arguments := []validation.Argument{ validation.StringProperty( "name", - &p.Name, + p.Name, it.IsNotBlank(), ), validation.StringProperty( "type", - &p.Type, + p.Type, it.IsNotBlank(), ), } @@ -65,7 +65,7 @@ func TestValidate_AtProperty_WhenGivenRecursiveProperties_ExpectViolationWithPro }, } - err := validator.ValidateIterable(context.Background(), properties) + err := validator.Validate(context.Background(), validation.Iterable(properties)) assertHasOneViolationAtPath(code.NotBlank, message.NotBlank, "[0].value[0].value[0].name")(t, err) } @@ -77,7 +77,7 @@ func TestValidate_WhenPathIsSetViaOptions_ExpectViolationAtPath(t *testing.T) { err := validator.Validate( context.Background(), validation.String( - &v, + v, validation.PropertyName("properties"), validation.ArrayIndex(0), validation.PropertyName("value"), @@ -91,7 +91,9 @@ func TestValidate_WhenPathIsSetViaOptions_ExpectViolationAtPath(t *testing.T) { func TestValidate_AtProperty_WhenGivenProperty_ExpectViolationWithProperty(t *testing.T) { validator := newValidator(t) - err := validator.AtProperty("property").ValidateString(context.Background(), stringValue(""), it.IsNotBlank()) + err := validator. + AtProperty("property"). + Validate(context.Background(), validation.String("", it.IsNotBlank())) assertHasOneViolationAtPath(code.NotBlank, message.NotBlank, "property")(t, err) } @@ -99,7 +101,9 @@ func TestValidate_AtProperty_WhenGivenProperty_ExpectViolationWithProperty(t *te func TestValidate_AtIndex_WhenGivenIndex_ExpectViolationWithIndex(t *testing.T) { validator := newValidator(t) - err := validator.AtIndex(1).ValidateString(context.Background(), stringValue(""), it.IsNotBlank()) + err := validator. + AtIndex(1). + Validate(context.Background(), validation.String("", it.IsNotBlank())) assertHasOneViolationAtPath(code.NotBlank, message.NotBlank, "[1]")(t, err) } diff --git a/test/translations_test.go b/test/translations_test.go index 4d4d68a..9495d2d 100644 --- a/test/translations_test.go +++ b/test/translations_test.go @@ -35,7 +35,7 @@ func TestValidator_Validate_WhenRussianIsDefaultLanguage_ExpectViolationTranslat } for _, test := range tests { t.Run("plural form for "+strconv.Itoa(test.maxCount), func(t *testing.T) { - err := v.ValidateCountable(context.Background(), 10, it.HasMaxCount(test.maxCount)) + err := v.Validate(context.Background(), validation.Countable(10, it.HasMaxCount(test.maxCount))) validationtest.AssertIsViolationList(t, err, func(t *testing.T, violations []validation.Violation) bool { t.Helper() @@ -85,7 +85,7 @@ func TestValidator_Validate_WhenCustomDefaultLanguageAndUndefinedTranslationLang err := v.Validate( context.Background(), validation.Language(language.Afrikaans), - validation.String(stringValue(""), it.IsNotBlank()), + validation.String("", it.IsNotBlank()), ) validationtest.AssertIsViolationList(t, err, func(t *testing.T, violations []validation.Violation) bool { @@ -108,7 +108,7 @@ func TestValidator_Validate_WhenTranslationLanguageInContextArgument_ExpectTrans ctx := language.WithContext(context.Background(), language.Russian) err := v.Validate( ctx, - validation.String(stringValue(""), it.IsNotBlank()), + validation.String("", it.IsNotBlank()), ) validationtest.AssertIsViolationList(t, err, func(t *testing.T, violations []validation.Violation) bool { @@ -121,7 +121,7 @@ func TestValidator_Validate_WhenTranslationLanguageInContextArgument_ExpectTrans func TestValidator_Validate_WhenTranslationLanguageInScopedValidator_ExpectTranslationLanguageUsed(t *testing.T) { v := newValidator(t, validation.Translations(russian.Messages)).WithLanguage(language.Russian) - err := v.ValidateString(context.Background(), stringValue(""), it.IsNotBlank()) + err := v.Validate(context.Background(), validation.String("", it.IsNotBlank())) validationtest.AssertIsViolationList(t, err, func(t *testing.T, violations []validation.Violation) bool { t.Helper() @@ -134,7 +134,7 @@ func TestValidator_Validate_WhenTranslationLanguageInContextOfScopedValidator_Ex ctx := language.WithContext(context.Background(), language.Russian) v := newValidator(t, validation.Translations(russian.Messages)) - err := v.ValidateString(ctx, stringValue(""), it.IsNotBlank()) + err := v.Validate(ctx, validation.String("", it.IsNotBlank())) validationtest.AssertIsViolationList(t, err, func(t *testing.T, violations []validation.Violation) bool { t.Helper() @@ -149,10 +149,7 @@ func TestValidator_Validate_WhenTranslationLanguageParsedFromAcceptLanguageHeade matcher := textlanguage.NewMatcher([]language.Tag{language.Russian}) tag, _ := textlanguage.MatchStrings(matcher, "ru-RU,ru;q=0.9,en-US;q=0.8,en;q=0.7") ctx := language.WithContext(context.Background(), tag) - err := v.Validate( - ctx, - validation.String(stringValue(""), it.IsNotBlank()), - ) + err := v.Validate(ctx, validation.String("", it.IsNotBlank())) validationtest.AssertIsViolationList(t, err, func(t *testing.T, violations []validation.Violation) bool { t.Helper() @@ -169,7 +166,7 @@ func TestValidator_Validate_WhenRecursiveValidation_ExpectViolationTranslated(t ) values := []mockValidatableString{{value: ""}} - err := v.ValidateIterable(context.Background(), values, it.IsNotBlank()) + err := v.Validate(context.Background(), validation.Iterable(values, it.IsNotBlank())) validationtest.AssertIsViolationList(t, err, func(t *testing.T, violations []validation.Violation) bool { t.Helper() @@ -194,7 +191,7 @@ func TestValidator_Validate_WhenTranslatableParameter_ExpectParameterTranslated( err := validator.Validate( context.Background(), validation.String( - &v, + v, it.IsNotBlank(). Message( "The operation is only possible for the {{ role }}.", @@ -220,7 +217,7 @@ func TestValidate_WhenTranslationsLoadedAfterInit_ExpectTranslationsWorking(t *t } defer validator.Reset() - err = validator.ValidateString(context.Background(), stringValue(""), it.IsNotBlank()) + err = validator.Validate(context.Background(), validation.String("", it.IsNotBlank())) validationtest.AssertIsViolationList(t, err, func(t *testing.T, violations []validation.Violation) bool { t.Helper() diff --git a/test/validatable_test.go b/test/validatable_test.go index 4ee6081..8d3176d 100644 --- a/test/validatable_test.go +++ b/test/validatable_test.go @@ -23,7 +23,7 @@ func (p Product) Validate(ctx context.Context, validator *validation.Validator) return validator.Validate( ctx, validation.String( - &p.Name, + p.Name, validation.PropertyName("name"), it.IsNotBlank(), ), @@ -50,7 +50,7 @@ func (c Component) Validate(ctx context.Context, validator *validation.Validator return validator.Validate( ctx, validation.String( - &c.Name, + c.Name, validation.PropertyName("name"), it.IsNotBlank(), ), @@ -73,7 +73,7 @@ func TestValidateValue_WhenStructWithComplexRules_ExpectViolations(t *testing.T) }, } - err := validator.ValidateValue(context.Background(), p) + err := validator.Validate(context.Background(), validation.Valid(p)) validationtest.AssertIsViolationList(t, err, func(t *testing.T, violations []validation.Violation) bool { t.Helper() @@ -94,11 +94,13 @@ func TestValidateValue_WhenStructWithComplexRules_ExpectViolations(t *testing.T) func TestValidateValue_WhenValidatableString_ExpectValidationExecutedWithPassedOptionsWithoutConstraints(t *testing.T) { validatable := mockValidatableString{value: ""} - err := validator.ValidateValue( + err := validator.Validate( context.Background(), - validatable, - validation.PropertyName("top"), - it.IsNotBlank().Message("ignored"), + validation.Value( + validatable, + validation.PropertyName("top"), + it.IsNotBlank().Message("ignored"), + ), ) assertHasOneViolationAtPath(code.NotBlank, message.NotBlank, "top.value")(t, err) @@ -107,11 +109,13 @@ func TestValidateValue_WhenValidatableString_ExpectValidationExecutedWithPassedO func TestValidateValidatable_WhenValidatableString_ExpectValidationExecutedWithPassedOptionsWithoutConstraints(t *testing.T) { validatable := mockValidatableString{value: ""} - err := validator.ValidateValidatable( + err := validator.Validate( context.Background(), - validatable, - validation.PropertyName("top"), - it.IsNotBlank().Message("ignored"), + validation.Valid( + validatable, + validation.PropertyName("top"), + it.IsNotBlank().Message("ignored"), + ), ) assertHasOneViolationAtPath(code.NotBlank, message.NotBlank, "top.value")(t, err) @@ -120,11 +124,13 @@ func TestValidateValidatable_WhenValidatableString_ExpectValidationExecutedWithP func TestValidateValue_WhenValidatableStruct_ExpectValidationExecutedWithPassedOptionsWithoutConstraints(t *testing.T) { validatable := mockValidatableStruct{} - err := validator.ValidateValue( + err := validator.Validate( context.Background(), - validatable, - validation.PropertyName("top"), - it.IsNotBlank().Message("ignored"), + validation.Value( + validatable, + validation.PropertyName("top"), + it.IsNotBlank().Message("ignored"), + ), ) validationtest.AssertIsViolationList(t, err, func(t *testing.T, violations []validation.Violation) bool { diff --git a/test/validate_aliases_test.go b/test/validate_aliases_test.go new file mode 100644 index 0000000..2cbda1e --- /dev/null +++ b/test/validate_aliases_test.go @@ -0,0 +1,41 @@ +package test + +import ( + "context" + "testing" + "time" + + "github.com/muonsoft/validation" + "github.com/muonsoft/validation/code" + "github.com/muonsoft/validation/it" + "github.com/muonsoft/validation/validationtest" + "github.com/stretchr/testify/assert" +) + +func TestValidate_ArgumentAliases_WhenAliasMethodForGivenType_ExpectValidationExecuted(t *testing.T) { + validator := newValidator(t) + + tests := []struct { + name string + err error + }{ + {"ValidateValue", validator.ValidateValue(context.Background(), "", it.IsNotBlank())}, + {"ValidateBool", validator.ValidateBool(context.Background(), false, it.IsNotBlank())}, + {"ValidateNumber", validator.ValidateNumber(context.Background(), 0, it.IsNotBlank())}, + {"ValidateString", validator.ValidateString(context.Background(), "", it.IsNotBlank())}, + {"ValidateIterable", validator.ValidateIterable(context.Background(), []string{}, it.IsNotBlank())}, + {"ValidateCountable", validator.ValidateCountable(context.Background(), 0, it.IsNotBlank())}, + {"ValidateTime", validator.ValidateTime(context.Background(), time.Time{}, it.IsNotBlank())}, + {"ValidateEach", validator.ValidateEach(context.Background(), []string{""}, it.IsNotBlank())}, + {"ValidateEachString", validator.ValidateEachString(context.Background(), []string{""}, it.IsNotBlank())}, + {"ValidateValidatable", validator.ValidateValidatable(context.Background(), mockValidatableString{""}, it.IsNotBlank())}, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + validationtest.AssertIsViolationList(t, test.err, func(t *testing.T, violations []validation.Violation) bool { + t.Helper() + return assert.Len(t, violations, 1) && assert.Equal(t, code.NotBlank, violations[0].Code()) + }) + }) + } +} diff --git a/test/validate_arguments_test.go b/test/validate_arguments_test.go index 2feb80a..fb0df47 100644 --- a/test/validate_arguments_test.go +++ b/test/validate_arguments_test.go @@ -3,6 +3,7 @@ package test import ( "context" "testing" + "time" "github.com/muonsoft/validation" "github.com/muonsoft/validation/code" @@ -18,12 +19,15 @@ func TestValidate_WhenArgumentForGivenType_ExpectValidationExecuted(t *testing.T argument validation.Argument }{ {"Value", validation.Value(stringValue(""), it.IsNotBlank())}, - {"Bool", validation.Bool(boolValue(false), it.IsNotBlank())}, + {"Bool", validation.Bool(false, it.IsNotBlank())}, + {"NilBool", validation.NilBool(boolValue(false), it.IsNotBlank())}, {"Number", validation.Number(0, it.IsNotBlank())}, - {"String", validation.String(stringValue(""), it.IsNotBlank())}, + {"String", validation.String("", it.IsNotBlank())}, + {"NilString", validation.NilString(stringValue(""), it.IsNotBlank())}, {"Iterable", validation.Iterable([]string{}, it.IsNotBlank())}, {"Countable", validation.Countable(0, it.IsNotBlank())}, - {"Time", validation.Time(nilTime, it.IsNotBlank())}, + {"Time", validation.Time(time.Time{}, it.IsNotBlank())}, + {"NilTime", validation.NilTime(nilTime, it.IsNotBlank())}, {"Each", validation.Each([]string{""}, it.IsNotBlank())}, {"EachString", validation.EachString([]string{""}, it.IsNotBlank())}, {"Valid", validation.Valid(mockValidatableString{""}, it.IsNotBlank())}, @@ -49,12 +53,15 @@ func TestValidate_WhenPropertyArgument_ExpectValidPathInViolation(t *testing.T) expectedPath string }{ {"PropertyValue", validation.PropertyValue("property", stringValue(""), opts...), "property.internal"}, - {"BoolProperty", validation.BoolProperty("property", boolValue(false), opts...), "property.internal"}, + {"BoolProperty", validation.BoolProperty("property", false, opts...), "property.internal"}, + {"NilBoolProperty", validation.NilBoolProperty("property", boolValue(false), opts...), "property.internal"}, {"NumberProperty", validation.NumberProperty("property", 0, opts...), "property.internal"}, - {"StringProperty", validation.StringProperty("property", stringValue(""), opts...), "property.internal"}, + {"StringProperty", validation.StringProperty("property", "", opts...), "property.internal"}, + {"NilStringProperty", validation.NilStringProperty("property", stringValue(""), opts...), "property.internal"}, {"IterableProperty", validation.IterableProperty("property", []string{}, opts...), "property.internal"}, {"CountableProperty", validation.CountableProperty("property", 0, opts...), "property.internal"}, - {"TimeProperty", validation.TimeProperty("property", nilTime, opts...), "property.internal"}, + {"TimeProperty", validation.TimeProperty("property", time.Time{}, opts...), "property.internal"}, + {"NilTimeProperty", validation.NilTimeProperty("property", nilTime, opts...), "property.internal"}, {"EachProperty", validation.EachProperty("property", []string{""}, opts...), "property.internal[0]"}, {"EachStringProperty", validation.EachStringProperty("property", []string{""}, opts...), "property.internal[0]"}, {"ValidProperty", validation.ValidProperty("property", mockValidatableString{""}, opts...), "property.internal.value"}, diff --git a/test/validate_control_constraints_test.go b/test/validate_control_constraints_test.go index 03d9a42..03305c5 100644 --- a/test/validate_control_constraints_test.go +++ b/test/validate_control_constraints_test.go @@ -12,17 +12,19 @@ import ( "github.com/stretchr/testify/assert" ) -func TestValidateString_WhenConditionIsTrue_ExpectAllConstraintsOfThenBranchApplied(t *testing.T) { +func TestValidate_WhenConditionIsTrue_ExpectAllConstraintsOfThenBranchApplied(t *testing.T) { value := "foo" - err := validator.ValidateString( + err := validator.Validate( context.Background(), - &value, - validation.When(true). - Then( - it.IsBlank(), - it.HasMinLength(5), - ), + validation.String( + value, + validation.When(true). + Then( + it.IsBlank(), + it.HasMinLength(5), + ), + ), ) validationtest.AssertIsViolationList(t, err, func(t *testing.T, violations []validation.Violation) bool { @@ -33,20 +35,22 @@ func TestValidateString_WhenConditionIsTrue_ExpectAllConstraintsOfThenBranchAppl }) } -func TestValidateString_WhenConditionIsFalse_ExpectAllConstraintsOfElseBranchApplied(t *testing.T) { +func TestValidate_WhenConditionIsFalse_ExpectAllConstraintsOfElseBranchApplied(t *testing.T) { value := "bar" - err := validator.ValidateString( + err := validator.Validate( context.Background(), - &value, - validation.When(false). - Then( - it.IsNotBlank(), - ). - Else( - it.IsBlank(), - it.HasMinLength(5), - ), + validation.String( + value, + validation.When(false). + Then( + it.IsNotBlank(), + ). + Else( + it.IsBlank(), + it.HasMinLength(5), + ), + ), ) validationtest.AssertIsViolationList(t, err, func(t *testing.T, violations []validation.Violation) bool { @@ -57,28 +61,26 @@ func TestValidateString_WhenConditionIsFalse_ExpectAllConstraintsOfElseBranchApp }) } -func TestValidateString_WhenConditionIsFalseAndNoElseBranch_ExpectNoViolations(t *testing.T) { +func TestValidate_WhenConditionIsFalseAndNoElseBranch_ExpectNoViolations(t *testing.T) { value := "foo" - err := validator.ValidateString( + err := validator.Validate( context.Background(), - &value, - validation.When(false). - Then( - it.IsNotBlank(), - ), + validation.String( + value, + validation.When(false).Then(it.IsNotBlank()), + ), ) assertNoError(t, err) } -func TestValidateString_WhenThenBranchIsNotSet_ExpectError(t *testing.T) { +func TestValidate_WhenThenBranchIsNotSet_ExpectError(t *testing.T) { value := "bar" - err := validator.ValidateString( + err := validator.Validate( context.Background(), - &value, - validation.When(true), + validation.String(value, validation.When(true)), ) assert.Error(t, err, "then branch of conditional constraint not set") @@ -87,12 +89,14 @@ func TestValidateString_WhenThenBranchIsNotSet_ExpectError(t *testing.T) { func TestValidate_WhenInvalidValueAtFirstConstraintOfSequentiallyConstraint_ExpectOneViolation(t *testing.T) { value := "foo" - err := validator.ValidateString( + err := validator.Validate( context.Background(), - &value, - validation.Sequentially( - it.IsBlank(), - it.HasMinLength(5), + validation.String( + value, + validation.Sequentially( + it.IsBlank(), + it.HasMinLength(5), + ), ), ) @@ -106,10 +110,9 @@ func TestValidate_WhenInvalidValueAtFirstConstraintOfSequentiallyConstraint_Expe func TestValidate_WhenSequentiallyConstraintsNotSet_ExpectError(t *testing.T) { value := "bar" - err := validator.ValidateString( + err := validator.Validate( context.Background(), - &value, - validation.Sequentially(), + validation.String(value, validation.Sequentially()), ) assert.Error(t, err, "constraints for sequentially validation not set") @@ -118,12 +121,14 @@ func TestValidate_WhenSequentiallyConstraintsNotSet_ExpectError(t *testing.T) { func TestValidate_WhenInvalidValueAtFirstConstraintOfAtLeastOneOfConstraint_ExpectAllViolation(t *testing.T) { value := "foo" - err := validator.ValidateString( + err := validator.Validate( context.Background(), - &value, - validation.AtLeastOneOf( - it.IsBlank(), - it.HasMinLength(5), + validation.String( + value, + validation.AtLeastOneOf( + it.IsBlank(), + it.HasMinLength(5), + ), ), ) @@ -138,12 +143,14 @@ func TestValidate_WhenInvalidValueAtFirstConstraintOfAtLeastOneOfConstraint_Expe func TestValidate_WhenInvalidValueAtSecondConstraintOfAtLeastOneOfConstraint_ExpectNoViolation(t *testing.T) { value := "foo" - err := validator.ValidateString( + err := validator.Validate( context.Background(), - &value, - validation.AtLeastOneOf( - it.IsEqualToString("bar"), - it.IsEqualToString("foo"), + validation.String( + value, + validation.AtLeastOneOf( + it.IsEqualToString("bar"), + it.IsEqualToString("foo"), + ), ), ) @@ -153,10 +160,9 @@ func TestValidate_WhenInvalidValueAtSecondConstraintOfAtLeastOneOfConstraint_Exp func TestValidate_WhenAtLeastOneOfConstraintsNotSet_ExpectError(t *testing.T) { value := "bar" - err := validator.ValidateString( + err := validator.Validate( context.Background(), - &value, - validation.AtLeastOneOf(), + validation.String(value, validation.AtLeastOneOf()), ) assert.Error(t, err, "constraints for at least one of validation not set") @@ -166,10 +172,9 @@ func TestValidate_Compound_ExpectNoViolation(t *testing.T) { value := "bar" isEmployeeEmail := validation.Compound(it.HasMinLength(5), it.IsEmail()) - err := validator.ValidateString( + err := validator.Validate( context.Background(), - &value, - isEmployeeEmail, + validation.String(value, isEmployeeEmail), ) validationtest.AssertIsViolationList(t, err, func(t *testing.T, violations []validation.Violation) bool { @@ -184,10 +189,9 @@ func TestValidate_WhenCompoundConstraintsNotSet_ExpectError(t *testing.T) { value := "bar" isEmployeeEmail := validation.Compound() - err := validator.ValidateString( + err := validator.Validate( context.Background(), - &value, - isEmployeeEmail, + validation.String(value, isEmployeeEmail), ) assert.Error(t, err, "constraints for compound validation not set") diff --git a/test/validate_each_test.go b/test/validate_each_test.go index f337bb0..17bc74a 100644 --- a/test/validate_each_test.go +++ b/test/validate_each_test.go @@ -15,7 +15,7 @@ import ( func TestValidateEach_WhenSliceOfStrings_ExpectViolationOnEachElement(t *testing.T) { strings := []string{"", ""} - err := validator.ValidateEach(context.Background(), strings, it.IsNotBlank()) + err := validator.Validate(context.Background(), validation.Each(strings, it.IsNotBlank())) validationtest.AssertIsViolationList(t, err, func(t *testing.T, violations []validation.Violation) bool { t.Helper() @@ -32,7 +32,7 @@ func TestValidateEach_WhenSliceOfStrings_ExpectViolationOnEachElement(t *testing func TestValidateEach_WhenMapOfStrings_ExpectViolationOnEachElement(t *testing.T) { strings := map[string]string{"key1": "", "key2": ""} - err := validator.ValidateEach(context.Background(), strings, it.IsNotBlank()) + err := validator.Validate(context.Background(), validation.Each(strings, it.IsNotBlank())) validationtest.AssertIsViolationList(t, err, func(t *testing.T, violations []validation.Violation) bool { t.Helper() @@ -49,7 +49,7 @@ func TestValidateEach_WhenMapOfStrings_ExpectViolationOnEachElement(t *testing.T func TestValidateEachString_WhenSliceOfStrings_ExpectViolationOnEachElement(t *testing.T) { strings := []string{"", ""} - err := validator.ValidateEachString(context.Background(), strings, it.IsNotBlank()) + err := validator.Validate(context.Background(), validation.EachString(strings, it.IsNotBlank())) validationtest.AssertIsViolationList(t, err, func(t *testing.T, violations []validation.Violation) bool { t.Helper() diff --git a/test/validate_iterable_test.go b/test/validate_iterable_test.go index 02a7614..21b9edc 100644 --- a/test/validate_iterable_test.go +++ b/test/validate_iterable_test.go @@ -12,10 +12,10 @@ import ( "github.com/stretchr/testify/assert" ) -func TestValidateIterable_WhenSliceOfValidatable_ExpectViolationsWithValidPaths(t *testing.T) { +func TestValidate_Value_WhenSliceOfValidatable_ExpectViolationsWithValidPaths(t *testing.T) { strings := []mockValidatableString{{value: ""}} - err := validator.ValidateValue(context.Background(), strings) + err := validator.Validate(context.Background(), validation.Value(strings)) validationtest.AssertIsViolationList(t, err, func(t *testing.T, violations []validation.Violation) bool { t.Helper() @@ -27,10 +27,10 @@ func TestValidateIterable_WhenSliceOfValidatable_ExpectViolationsWithValidPaths( }) } -func TestValidateIterable_WhenSliceOfValidatableWithConstraints_ExpectCollectionViolationsWithValidPaths(t *testing.T) { +func TestValidate_Value_WhenSliceOfValidatableWithConstraints_ExpectCollectionViolationsWithValidPaths(t *testing.T) { strings := []mockValidatableString{{value: ""}} - err := validator.ValidateValue(context.Background(), strings, it.HasMinCount(2)) + err := validator.Validate(context.Background(), validation.Value(strings, it.HasMinCount(2))) validationtest.AssertIsViolationList(t, err, func(t *testing.T, violations []validation.Violation) bool { t.Helper() @@ -44,10 +44,10 @@ func TestValidateIterable_WhenSliceOfValidatableWithConstraints_ExpectCollection }) } -func TestValidateIterable_WhenMapOfValidatable_ExpectViolationsWithValidPaths(t *testing.T) { +func TestValidate_Value_WhenMapOfValidatable_ExpectViolationsWithValidPaths(t *testing.T) { strings := map[string]mockValidatableString{"key": {value: ""}} - err := validator.ValidateValue(context.Background(), strings) + err := validator.Validate(context.Background(), validation.Value(strings)) validationtest.AssertIsViolationList(t, err, func(t *testing.T, violations []validation.Violation) bool { t.Helper() @@ -59,10 +59,10 @@ func TestValidateIterable_WhenMapOfValidatable_ExpectViolationsWithValidPaths(t }) } -func TestValidateIterable_WhenMapOfValidatableWithConstraints_ExpectCollectionViolationsWithValidPaths(t *testing.T) { +func TestValidate_Value_WhenMapOfValidatableWithConstraints_ExpectCollectionViolationsWithValidPaths(t *testing.T) { strings := map[string]mockValidatableString{"key": {value: ""}} - err := validator.ValidateValue(context.Background(), strings, it.HasMinCount(2)) + err := validator.Validate(context.Background(), validation.Value(strings, it.HasMinCount(2))) validationtest.AssertIsViolationList(t, err, func(t *testing.T, violations []validation.Violation) bool { t.Helper() diff --git a/test/validate_value_test.go b/test/validate_value_test.go index 5fc5103..bfce00c 100644 --- a/test/validate_value_test.go +++ b/test/validate_value_test.go @@ -43,7 +43,10 @@ func TestValidateValue_WhenValueOfType_ExpectValueValidated(t *testing.T) { } for _, test := range tests { t.Run(test.name, func(t *testing.T) { - err := validator.ValidateValue(context.Background(), test.value, validation.PropertyName("property"), it.IsNotBlank()) + err := validator.Validate( + context.Background(), + validation.Value(test.value, validation.PropertyName("property"), it.IsNotBlank()), + ) assertHasOneViolationAtPath(code.NotBlank, message.NotBlank, "property")(t, err) }) diff --git a/test/validator_option_test.go b/test/validator_option_test.go index ae8c2c0..de75564 100644 --- a/test/validator_option_test.go +++ b/test/validator_option_test.go @@ -18,7 +18,7 @@ func TestWhenGlobalValidatorWithOverriddenNewViolation_ExpectCustomViolation(t * } defer validator.Reset() - err = validator.ValidateString(context.Background(), nil, it.IsNotBlank()) + err = validator.Validate(context.Background(), validation.String("", it.IsNotBlank())) validationtest.AssertIsViolationList(t, err, func(t *testing.T, violations []validation.Violation) bool { t.Helper() @@ -29,7 +29,7 @@ func TestWhenGlobalValidatorWithOverriddenNewViolation_ExpectCustomViolation(t * func TestWhenValidatorWithOverriddenNewViolation_ExpectCustomViolation(t *testing.T) { v := newValidator(t, validation.SetViolationFactory(mockNewViolationFunc())) - err := v.ValidateString(context.Background(), nil, it.IsNotBlank()) + err := v.Validate(context.Background(), validation.String("", it.IsNotBlank())) validationtest.AssertIsViolationList(t, err, func(t *testing.T, violations []validation.Violation) bool { t.Helper() diff --git a/test/validator_store_test.go b/test/validator_store_test.go index 07aa6ab..c39a24f 100644 --- a/test/validator_store_test.go +++ b/test/validator_store_test.go @@ -14,8 +14,10 @@ import ( func TestValidator_ValidateBy_WhenConstraintExists_ExpectValidationByStoredConstraint(t *testing.T) { validator := newValidator(t, validation.StoredConstraint("notBlank", it.IsNotBlank())) - s := "" - err := validator.ValidateString(context.Background(), &s, validator.ValidateBy("notBlank")) + err := validator.Validate( + context.Background(), + validation.String("", validator.ValidateBy("notBlank")), + ) assertHasOneViolation(code.NotBlank, message.NotBlank)(t, err) } @@ -23,8 +25,10 @@ func TestValidator_ValidateBy_WhenConstraintExists_ExpectValidationByStoredConst func TestValidator_ValidateBy_WhenConstraintDoesNotExist_ExpectError(t *testing.T) { validator := newValidator(t) - s := "" - err := validator.ValidateString(context.Background(), &s, validator.ValidateBy("notBlank")) + err := validator.Validate( + context.Background(), + validation.String("", validator.ValidateBy("notBlank")), + ) assert.EqualError(t, err, `failed to set up constraint "notFoundConstraint": constraint with key "notBlank" is not stored in the validator`) } diff --git a/validator.go b/validator.go index 342f15b..50cd029 100644 --- a/validator.go +++ b/validator.go @@ -103,8 +103,7 @@ func SetViolationFactory(factory ViolationFactory) ValidatorOption { // log.Fatal(err) // } // -// s := " -// err = validator.ValidateString(&s, validator.ValidateBy("isTagExists")) +// err = validator.ValidateString("", validator.ValidateBy("isTagExists")) func StoredConstraint(key string, constraint Constraint) ValidatorOption { return func(validator *Validator) error { if _, exists := validator.scope.constraints[key]; exists { @@ -146,7 +145,7 @@ func (validator *Validator) ValidateValue(ctx context.Context, value interface{} } // ValidateBool is an alias for validating a single boolean value. -func (validator *Validator) ValidateBool(ctx context.Context, value *bool, options ...Option) error { +func (validator *Validator) ValidateBool(ctx context.Context, value bool, options ...Option) error { return validator.Validate(ctx, Bool(value, options...)) } @@ -156,7 +155,7 @@ func (validator *Validator) ValidateNumber(ctx context.Context, value interface{ } // ValidateString is an alias for validating a single string value. -func (validator *Validator) ValidateString(ctx context.Context, value *string, options ...Option) error { +func (validator *Validator) ValidateString(ctx context.Context, value string, options ...Option) error { return validator.Validate(ctx, String(value, options...)) } @@ -176,7 +175,7 @@ func (validator *Validator) ValidateCountable(ctx context.Context, count int, op } // ValidateTime is an alias for validating a single time value. -func (validator *Validator) ValidateTime(ctx context.Context, value *time.Time, options ...Option) error { +func (validator *Validator) ValidateTime(ctx context.Context, value time.Time, options ...Option) error { return validator.Validate(ctx, Time(value, options...)) } @@ -195,6 +194,18 @@ func (validator *Validator) ValidateValidatable(ctx context.Context, validatable return validator.Validate(ctx, Valid(validatable, options...)) } +// ValidateBy is used to get the constraint from the internal validator store. +// If the constraint does not exist, then the validator will +// return a ConstraintNotFoundError during the validation process. +// For storing a constraint you should use the StoredConstraint option. +func (validator *Validator) ValidateBy(constraintKey string) Constraint { + if constraint, exists := validator.scope.constraints[constraintKey]; exists { + return constraint + } + + return notFoundConstraint{key: constraintKey} +} + // WithLanguage method creates a new scoped validator with a given language tag. All created violations // will be translated into this language. // @@ -220,15 +231,3 @@ func (validator *Validator) AtIndex(index int) *Validator { func (validator *Validator) BuildViolation(ctx context.Context, code, message string) *ViolationBuilder { return validator.scope.withContext(ctx).BuildViolation(code, message) } - -// ValidateBy is used to get the constraint from the internal validator store. -// If the constraint does not exist, then the validator will -// return a ConstraintNotFoundError during the validation process. -// For storing a constraint you should use the StoreConstraint method. -func (validator *Validator) ValidateBy(constraintKey string) Constraint { - if constraint, exists := validator.scope.constraints[constraintKey]; exists { - return constraint - } - - return notFoundConstraint{key: constraintKey} -} diff --git a/validator/validator.go b/validator/validator.go index f99a228..89f1a7a 100644 --- a/validator/validator.go +++ b/validator/validator.go @@ -46,7 +46,7 @@ func ValidateValue(ctx context.Context, value interface{}, options ...validation } // ValidateBool is an alias for validating a single boolean value. -func ValidateBool(ctx context.Context, value *bool, options ...validation.Option) error { +func ValidateBool(ctx context.Context, value bool, options ...validation.Option) error { return validator.ValidateBool(ctx, value, options...) } @@ -56,7 +56,7 @@ func ValidateNumber(ctx context.Context, value interface{}, options ...validatio } // ValidateString is an alias for validating a single string value. -func ValidateString(ctx context.Context, value *string, options ...validation.Option) error { +func ValidateString(ctx context.Context, value string, options ...validation.Option) error { return validator.ValidateString(ctx, value, options...) } @@ -76,7 +76,7 @@ func ValidateCountable(ctx context.Context, count int, options ...validation.Opt } // ValidateTime is an alias for validating a single time value. -func ValidateTime(ctx context.Context, value *time.Time, options ...validation.Option) error { +func ValidateTime(ctx context.Context, value time.Time, options ...validation.Option) error { return validator.ValidateTime(ctx, value, options...) } @@ -95,6 +95,14 @@ func ValidateValidatable(ctx context.Context, validatable validation.Validatable return validator.ValidateValidatable(ctx, validatable, options...) } +// ValidateBy is used to get the constraint from the internal validator store. +// If the constraint does not exist, then the validator will +// return a ConstraintNotFoundError during the validation process. +// For storing a constraint you should use the validation.StoredConstraint option. +func ValidateBy(constraintKey string) validation.Constraint { + return validator.ValidateBy(constraintKey) +} + // WithLanguage method creates a new scoped validator with a given language tag. All created violations // will be translated into this language. // @@ -125,11 +133,3 @@ func AtIndex(index int) *validation.Validator { func BuildViolation(ctx context.Context, code, message string) *validation.ViolationBuilder { return validator.BuildViolation(ctx, code, message) } - -// ValidateBy is used to get the constraint from the internal validator store. -// If the constraint does not exist, then the validator will -// return a ConstraintNotFoundError during the validation process. -// For storing a constraint you should use the validation.StoredConstraint option. -func ValidateBy(constraintKey string) validation.Constraint { - return validator.ValidateBy(constraintKey) -} From 29ee61ab3da63a49469feff0fc86c0d6044f785f Mon Sep 17 00:00:00 2001 From: Igor Lazarev Date: Wed, 4 Aug 2021 19:43:03 +0300 Subject: [PATCH 4/4] nillable arguments separated * README.md updated --- README.md | 43 ++++++++++++++++++------------------------- 1 file changed, 18 insertions(+), 25 deletions(-) diff --git a/README.md b/README.md index 4c08dba..24cc0b8 100644 --- a/README.md +++ b/README.md @@ -45,9 +45,7 @@ go get -u github.com/muonsoft/validation The validation process is built around functional options and passing values by specific typed arguments. A common way to use validation is to call the `validator.Validate` method and pass the argument option with the list of validation constraints. ```golang -s := "" - -err := validator.Validate(context.Background(), validation.String(&s, it.IsNotBlank())) +err := validator.Validate(context.Background(), validation.String("", it.IsNotBlank())) violations := err.(validation.ViolationList) for _, violation := range violations { @@ -61,12 +59,15 @@ List of common [validation arguments](https://pkg.go.dev/github.com/muonsoft/val * `validation.Value()` - passes any value. It uses reflection to detect the type of the argument and pass it to a specific validation method. * `validation.Bool()` - passes boolean value. +* `validation.NilBool()` - passes nillable boolean value. * `validation.Number()` - passes any numeric value. At the moment it uses reflection for executing validation process. * `validation.String()` - passes string value. +* `validation.NilString()` - passes nillable string value. * `validation.Strings()` - passes slice of strings value. * `validation.Iterable()` - passes array, slice or a map. At the moment it uses reflection for executing validation process. * `validation.Countable()` - you can pass result of `len()` to use easy way of iterable validation based only on count of the elements. * `validation.Time()` - passes `time.Time` value. +* `validation.NilTime()` - passes nillable `time.Time` value. * `validation.Each()` - passes array, slice or a map. Used to validate each value of iterable. It uses reflection. * `validation.EachString()` - passes slice of strings. This is more performant version than `Each`. * `validation.Valid()` - passes `Validatable` value to run embedded validation. @@ -134,12 +135,10 @@ The [property path](https://pkg.go.dev/github.com/muonsoft/validation#PropertyPa You can pass a property name or an array index via `validation.PropertyName()` and `validation.ArrayIndex()` options. ```golang -s := "" - err := validator.Validate( context.Background(), validation.String( - &s, + "", validation.PropertyName("properties"), validation.ArrayIndex(1), validation.PropertyName("tag"), @@ -156,13 +155,11 @@ fmt.Println("property path:", violation.GetPropertyPath().Format()) Also, you can create scoped validator by using `valdiator.AtProperty()` or `validator.AtIndex()` methods. It can be used to validate a couple of attributes of one object. ```golang -s := "" - err := validator. AtProperty("properties"). AtIndex(1). AtProperty("tag"). - Validate(context.Background(), validation.String(&s, it.IsNotBlank())) + Validate(context.Background(), validation.String("", it.IsNotBlank())) violation := err.(validation.ViolationList)[0] fmt.Println("property path:", violation.GetPropertyPath().Format()) @@ -174,22 +171,23 @@ For a better experience with struct validation, you can use shorthand versions o * `validation.PropertyValue()` * `validation.BoolProperty()` +* `validation.NilBoolProperty()` * `validation.NumberProperty()` * `validation.StringProperty()` +* `validation.NilStringProperty()` * `validation.StringsProperty()` * `validation.IterableProperty()` * `validation.CountableProperty()` * `validation.TimeProperty()` +* `validation.NilTimeProperty()` * `validation.EachProperty()` * `validation.EachStringProperty()` * `validation.ValidProperty()` ```golang -s := "" - err := validator.Validate( context.Background(), - validation.StringProperty("property", &s, it.IsNotBlank()), + validation.StringProperty("property", "", it.IsNotBlank()), ) violation := err.(validation.ViolationList)[0] @@ -210,7 +208,7 @@ document := Document{ err := validator.Validate( context.Background(), - validation.StringProperty("title", &document.Title, it.IsNotBlank()), + validation.StringProperty("title", document.Title, it.IsNotBlank()), validation.CountableProperty("keywords", len(document.Keywords), it.HasCountBetween(5, 10)), validation.StringsProperty("keywords", document.Keywords, it.HasUniqueValues()), validation.EachStringProperty("keywords", document.Keywords, it.IsNotBlank()), @@ -240,7 +238,7 @@ type Product struct { func (p Product) Validate(ctx context.Context, validator *validation.Validator) error { return validator.Validate( ctx, - validation.StringProperty("name", &p.Name, it.IsNotBlank()), + validation.StringProperty("name", p.Name, it.IsNotBlank()), validation.CountableProperty("tags", len(p.Tags), it.HasMinCount(5)), validation.StringsProperty("tags", p.Tags, it.HasUniqueValues()), validation.EachStringProperty("tags", p.Tags, it.IsNotBlank()), @@ -258,7 +256,7 @@ type Component struct { func (c Component) Validate(ctx context.Context, validator *validation.Validator) error { return validator.Validate( ctx, - validation.StringProperty("name", &c.Name, it.IsNotBlank()), + validation.StringProperty("name", c.Name, it.IsNotBlank()), validation.CountableProperty("tags", len(c.Tags), it.HasMinCount(1)), ) } @@ -298,7 +296,7 @@ You can use the `When()` method on any of the built-in constraints to execute co ```golang err := validator.Validate( context.Background(), - validation.StringProperty("text", ¬e.Text, it.IsNotBlank().When(note.IsPublic)), + validation.StringProperty("text", note.Text, it.IsNotBlank().When(note.IsPublic)), ) violations := err.(validation.ViolationList) @@ -369,8 +367,7 @@ validator, _ := validation.NewValidator( validation.DefaultLanguage(language.Russian), ) -s := "" -err := validator.ValidateString(context.Background(), &s, it.IsNotBlank()) +err := validator.ValidateString(context.Background(), "", it.IsNotBlank()) violations := err.(validation.ViolationList) for _, violation := range violations { @@ -387,11 +384,10 @@ validator, _ := validation.NewValidator( validation.Translations(russian.Messages), ) -s := "" err := validator.Validate( context.Background(), validation.Language(language.Russian), - validation.String(&s, it.IsNotBlank()), + validation.String("", it.IsNotBlank()), ) violations := err.(validation.ViolationList) @@ -412,8 +408,7 @@ validator, _ := validation.NewValidator( ) ctx := language.WithContext(context.Background(), language.Russian) -s := "" -err := validator.ValidateString(ctx, &s, it.IsNotBlank()) +err := validator.ValidateString(ctx, "", it.IsNotBlank()) violations := err.(validation.ViolationList) for _, violation := range violations { @@ -430,9 +425,7 @@ You can see the complex example with handling HTTP request [here](https://pkg.go You may customize the violation message on any of the built-in constraints by calling the `Message()` method or similar if the constraint has more than one template. Also, you can include template parameters in it. See details of a specific constraint to know what parameters are available. ```golang -s := "" - -err := validator.ValidateString(context.Background(), &s, it.IsNotBlank().Message("this value is required")) +err := validator.ValidateString(context.Background(), "", it.IsNotBlank().Message("this value is required")) violations := err.(validation.ViolationList) for _, violation := range violations {