- -To inspect if an error contains a given [govy.ErrorCode](<#ErrorCode>), use [govy.HasErrorCode](<#HasErrorCode>) function. This function will also return true if the expected [govy.ErrorCode](<#ErrorCode>) is part of a chain of wrapped error codes. In this example we're dealing with two error code chains: - -- 'teacher\_name:string\_length' -- 'teacher\_name:string\_match\_regexp' - -```go -package main - -import ( - "fmt" - "regexp" - "time" - - "github.com/nobl9/govy/pkg/govy" - "github.com/nobl9/govy/pkg/rules" -) - -type Teacher struct { - Name string `json:"name"` - Age time.Duration `json:"age"` - Students []Student `json:"students"` - MiddleName *string `json:"middleName,omitempty"` - University University `json:"university"` -} - -type University struct { - Name string `json:"name"` - Address string `json:"address"` -} - -type Student struct { - Index string `json:"index"` -} - -const year = 24 * 365 * time.Hour - -func main() { - teacherNameRule := govy.NewRuleSet( - rules.StringLength(1, 5), - rules.StringMatchRegexp(regexp.MustCompile("^(Tom|Jerry)$")), - ). - WithErrorCode("teacher_name") - - v := govy.New( - govy.For(func(t Teacher) string { return t.Name }). - WithName("name"). - Rules(teacherNameRule), - ).WithName("Teacher") - - teacher := Teacher{ - Name: "Jonathan", - Age: 51 * year, - } - - err := v.Validate(teacher) - if err != nil { - for _, code := range []govy.ErrorCode{ - "teacher_name", - "string_length", - "string_match_regexp", - } { - if govy.HasErrorCode(err, code) { - fmt.Println("Has error code:", code) - } - } - } - -} -``` - -#### Output - -``` -Has error code: teacher_name -Has error code: string_length -Has error code: string_match_regexp -``` - -
-- -Sometimes you need top level context, but you want to scope the error to a specific, nested property. One of the ways to do that is to use [govy.NewPropertyError](<#NewPropertyError>) and return [govy.PropertyError](<#PropertyError>) from your validation rule. Note that you can still use [govy.ErrorCode](<#ErrorCode>) and pass [govy.RuleError](<#RuleError>) to the constructor. You can pass any number of [govy.RuleError](<#RuleError>). - -```go -package main - -import ( - "fmt" - "time" - - "github.com/nobl9/govy/pkg/govy" -) - -type Teacher struct { - Name string `json:"name"` - Age time.Duration `json:"age"` - Students []Student `json:"students"` - MiddleName *string `json:"middleName,omitempty"` - University University `json:"university"` -} - -type University struct { - Name string `json:"name"` - Address string `json:"address"` -} - -type Student struct { - Index string `json:"index"` -} - -const year = 24 * 365 * time.Hour - -func main() { - v := govy.New( - govy.For(govy.GetSelf[Teacher]()). - Rules(govy.NewRule(func(t Teacher) error { - if t.Name == "Jake" { - return govy.NewPropertyError( - "name", - t.Name, - govy.NewRuleError("name cannot be Jake", "error_code_jake"), - govy.NewRuleError("you can pass me too!")) - } - return nil - })), - ).WithName("Teacher") - - teacher := Teacher{ - Name: "Jake", - Age: 51 * year, - } - - err := v.Validate(teacher) - if err != nil { - propertyErrors := err.(*govy.ValidatorError).Errors - ruleErrors := propertyErrors[0].Errors - fmt.Printf("Error code: %s\n\n", ruleErrors[0].Code) - fmt.Println(err) - } - -} -``` - -#### Output - -``` -Error code: error_code_jake - -Validation for Teacher has failed for the following properties: - - 'name' with value 'Jake': - - name cannot be Jake - - you can pass me too! -``` - -
-- -If you want to access the value of the entity you're writing the [govy.Validator](<#Validator>) for, you can use [govy.GetSelf](<#GetSelf>) function which is a convenience [govy.PropertyGetter](<#PropertyGetter>) that returns self. Note that we don't call [govy.PropertyRules.WithName](<#PropertyRules.WithName>) here, as we're comparing two properties in our top level, \[Teacher\] scope. - -You can provide your own rules using [govy.NewRule](<#NewRule>) constructor. It returns new [govy.Rule](<#Rule>) instance which wraps your validation function. - -```go -package main - -import ( - "fmt" - "time" - - "github.com/nobl9/govy/pkg/govy" -) - -type Teacher struct { - Name string `json:"name"` - Age time.Duration `json:"age"` - Students []Student `json:"students"` - MiddleName *string `json:"middleName,omitempty"` - University University `json:"university"` -} - -type University struct { - Name string `json:"name"` - Address string `json:"address"` -} - -type Student struct { - Index string `json:"index"` -} - -const year = 24 * 365 * time.Hour - -func main() { - customRule := govy.NewRule(func(v Teacher) error { - return fmt.Errorf("now I have access to the whole teacher") - }) - - v := govy.New( - govy.For(govy.GetSelf[Teacher]()). - Rules(customRule), - ).WithName("Teacher") - - teacher := Teacher{ - Name: "Jake", - Age: 51 * year, - } - - err := v.Validate(teacher) - if err != nil { - fmt.Println(err) - } - -} -``` - -#### Output - -``` -Validation for Teacher has failed for the following properties: - - now I have access to the whole teacher -``` - -
-- -[govy.For](<#For>) constructor creates new [govy.PropertyRules](<#PropertyRules>) instance. It's only argument, [govy.PropertyGetter](<#PropertyGetter>) is used to extract the property value. It works fine for direct values, but falls short when working with pointers. Often times we use pointers to indicate that a property is optional, or we want to discern between nil and zero values. In either case we want our validation rules to work on direct values, not the pointer, otherwise we'd have to always check if pointer \!= nil. - -[govy.ForPointer](<#ForPointer>) constructor can be used to solve this problem and allow us to work with the underlying value in our rules. Under the hood it wraps [govy.PropertyGetter](<#PropertyGetter>) and safely extracts the underlying value. If the value was nil, it will not attempt to evaluate any rules for this property. The rationale for that is it doesn't make sense to evaluate any rules for properties which are essentially empty. The only rule that makes sense in this context is to ensure the property is required. We'll learn about a way to achieve that in the next example: \[ExamplePropertyRules\_Required\]. - -Let's define a rule for \[Teacher.MiddleName\] property. Not everyone has to have a middle name, that's why we've defined this field as a pointer to string, rather than a string itself. - -```go -package main - -import ( - "fmt" - "time" - - "github.com/nobl9/govy/pkg/govy" - "github.com/nobl9/govy/pkg/rules" -) - -type Teacher struct { - Name string `json:"name"` - Age time.Duration `json:"age"` - Students []Student `json:"students"` - MiddleName *string `json:"middleName,omitempty"` - University University `json:"university"` -} - -type University struct { - Name string `json:"name"` - Address string `json:"address"` -} - -type Student struct { - Index string `json:"index"` -} - -const year = 24 * 365 * time.Hour - -func main() { - v := govy.New( - govy.ForPointer(func(t Teacher) *string { return t.MiddleName }). - WithName("middleName"). - Rules(rules.StringMaxLength(5)), - ).WithName("Teacher") - - middleName := "Thaddeus" - teacher := Teacher{ - Name: "Jake", - Age: 51 * year, - MiddleName: &middleName, - } - - err := v.Validate(teacher) - if err != nil { - fmt.Println(err) - } - -} -``` - -#### Output - -``` -Validation for Teacher has failed for the following properties: - - 'middleName' with value 'Thaddeus': - - length must be less than or equal to 5 -``` - -
-- -To customize how [govy.Rule](<#Rule>) are evaluated use [govy.PropertyRules.Cascade](<#PropertyRules.Cascade>). Use [govy.CascadeModeStop](<#CascadeModeContinue>) to stop validation after the first error. If you wish to revert to the default behavior, use [govy.CascadeModeContinue](<#CascadeModeContinue>). - -```go -package main - -import ( - "fmt" - "time" - - "github.com/nobl9/govy/pkg/govy" - "github.com/nobl9/govy/pkg/rules" -) - -type Teacher struct { - Name string `json:"name"` - Age time.Duration `json:"age"` - Students []Student `json:"students"` - MiddleName *string `json:"middleName,omitempty"` - University University `json:"university"` -} - -type University struct { - Name string `json:"name"` - Address string `json:"address"` -} - -type Student struct { - Index string `json:"index"` -} - -func main() { - alwaysFailingRule := govy.NewRule(func(string) error { - return fmt.Errorf("always fails") - }) - - v := govy.New( - govy.For(func(t Teacher) string { return t.Name }). - WithName("name"). - Cascade(govy.CascadeModeStop). - Rules(rules.NEQ("Jerry")). - Rules(alwaysFailingRule), - ).WithName("Teacher") - - for _, name := range []string{"Tom", "Jerry"} { - teacher := Teacher{Name: name} - err := v.Validate(teacher) - if err != nil { - fmt.Println(err) - } - } - -} -``` - -#### Output - -``` -Validation for Teacher has failed for the following properties: - - 'name' with value 'Tom': - - always fails -Validation for Teacher has failed for the following properties: - - 'name' with value 'Jerry': - - should be not equal to 'Jerry' -``` - -
-- -Sometimes you want to hide the value of the property in the error message. It can contain sensitive information, like a secret access key. You can use [govy.PropertyRules.HideValue](<#PropertyRules.HideValue>) to achieve that. - -You can see that the error message now contains "\[hidden\]" instead of the actual value, and the property value is not included in the property bullet point \(\- 'name'\). - -```go -package main - -import ( - "fmt" - "time" - - "github.com/nobl9/govy/pkg/govy" -) - -type Teacher struct { - Name string `json:"name"` - Age time.Duration `json:"age"` - Students []Student `json:"students"` - MiddleName *string `json:"middleName,omitempty"` - University University `json:"university"` -} - -type University struct { - Name string `json:"name"` - Address string `json:"address"` -} - -type Student struct { - Index string `json:"index"` -} - -const year = 24 * 365 * time.Hour - -func main() { - v := govy.New( - govy.For(func(t Teacher) string { return t.Name }). - WithName("name"). - HideValue(). - Rules(govy.NewRule(func(name string) error { return fmt.Errorf("that Jake is secret") })), - ).WithName("Teacher") - - teacher := Teacher{ - Name: "Jake", - Age: 51 * year, - } - - err := v.Validate(teacher) - if err != nil { - fmt.Println(err) - } - -} -``` - -#### Output - -``` -Validation for Teacher has failed for the following properties: - - 'name': - - that [hidden] is secret -``` - -
-- -So far we've defined validation rules for simple, top\-level properties. What If we want to define validation rules for nested properties? We can use [govy.PropertyRules.Include](<#PropertyRules.Include>) to include another [govy.Validator](<#Validator>) in our [govy.PropertyRules](<#PropertyRules>). - -Let's extend our \[Teacher\] struct to include a nested \[University\] property. \[University\] in of itself is another struct with its own validation rules. - -Notice how the nested property path is automatically built for you, each segment separated by a dot. - -```go -package main - -import ( - "fmt" - "time" - - "github.com/nobl9/govy/pkg/govy" - "github.com/nobl9/govy/pkg/rules" -) - -type Teacher struct { - Name string `json:"name"` - Age time.Duration `json:"age"` - Students []Student `json:"students"` - MiddleName *string `json:"middleName,omitempty"` - University University `json:"university"` -} - -type University struct { - Name string `json:"name"` - Address string `json:"address"` -} - -type Student struct { - Index string `json:"index"` -} - -const year = 24 * 365 * time.Hour - -func main() { - universityValidation := govy.New( - govy.For(func(u University) string { return u.Address }). - WithName("address"). - Required(), - ) - teacherValidation := govy.New( - govy.For(func(t Teacher) string { return t.Name }). - WithName("name"). - Rules(rules.EQ("Tom")), - govy.For(func(t Teacher) University { return t.University }). - WithName("university"). - Include(universityValidation), - ).WithName("Teacher") - - teacher := Teacher{ - Name: "Jerry", - Age: 51 * year, - University: University{ - Name: "Poznan University of Technology", - Address: "", - }, - } - - err := teacherValidation.Validate(teacher) - if err != nil { - fmt.Println(err) - } - -} -``` - -#### Output - -``` -Validation for Teacher has failed for the following properties: - - 'name' with value 'Jerry': - - should be equal to 'Tom' - - 'university.address': - - property is required but was empty -``` - -
-- -While [govy.ForPointer](<#ForPointer>) will by default omit validation for nil pointers, it might be useful to have a similar behavior for optional properties which are direct values. [govy.PropertyRules.OmitEmpty](<#PropertyRules.OmitEmpty>) will do the trick. - -Note: [govy.PropertyRules.OmitEmpty](<#PropertyRules.OmitEmpty>) will have no effect on pointers handled by [govy.ForPointer](<#ForPointer>), as they already behave in the same way. - -```go -package main - -import ( - "fmt" - "time" - - "github.com/nobl9/govy/pkg/govy" -) - -type Teacher struct { - Name string `json:"name"` - Age time.Duration `json:"age"` - Students []Student `json:"students"` - MiddleName *string `json:"middleName,omitempty"` - University University `json:"university"` -} - -type University struct { - Name string `json:"name"` - Address string `json:"address"` -} - -type Student struct { - Index string `json:"index"` -} - -const year = 24 * 365 * time.Hour - -func main() { - alwaysFailingRule := govy.NewRule(func(string) error { - return fmt.Errorf("always fails") - }) - - v := govy.New( - govy.For(func(t Teacher) string { return t.Name }). - WithName("name"). - OmitEmpty(). - Rules(alwaysFailingRule), - govy.ForPointer(func(t Teacher) *string { return t.MiddleName }). - WithName("middleName"). - Rules(alwaysFailingRule), - ).WithName("Teacher") - - teacher := Teacher{ - Name: "", - Age: 51 * year, - MiddleName: nil, - } - - err := v.Validate(teacher) - if err == nil { - fmt.Println("no error! we skipped 'name' validation and 'middleName' is implicitly skipped") - } - -} -``` - -#### Output - -``` -no error! we skipped 'name' validation and 'middleName' is implicitly skipped -``` - -
-- -By default, when [govy.PropertyRules](<#PropertyRules>) is constructed using [govy.ForPointer](<#ForPointer>) it will skip validation of the property if the pointer is nil. To enforce a value is set for pointer use [govy.PropertyRules.Required](<#PropertyRules.Required>). - -You may ask yourself why not just use \[rules.Required\] rule instead? If we were to do that, we'd be forced to operate on pointer in all of our rules. Other than checking if the pointer is nil, there aren't any rules which would benefit from working on the pointer instead of the underlying value. - -If you want to also make sure the underlying value is filled, i.e. it's not a zero value, you can also use \[rules.Required\] rule on top of [govy.PropertyRules.Required](<#PropertyRules.Required>). - -[govy.PropertyRules.Required](<#PropertyRules.Required>) when used with [govy.For](<#For>) constructor, will ensure the property does not contain a zero value. - -Note: [govy.PropertyRules.Required](<#PropertyRules.Required>) is introducing a short circuit. If the assertion fails, validation will stop and return \[govy.govy.ErrorCodeRequired\]. None of the rules you've defined would be evaluated. - -Note: Placement of [govy.PropertyRules.Required](<#PropertyRules.Required>) does not matter, it's not evaluated in a sequential loop, unlike standard [govy.Rule](<#Rule>). However, we recommend you always place it below [govy.PropertyRules.WithName](<#PropertyRules.WithName>) to make your rules more readable. - -```go -package main - -import ( - "fmt" - "time" - - "github.com/nobl9/govy/pkg/govy" -) - -type Teacher struct { - Name string `json:"name"` - Age time.Duration `json:"age"` - Students []Student `json:"students"` - MiddleName *string `json:"middleName,omitempty"` - University University `json:"university"` -} - -type University struct { - Name string `json:"name"` - Address string `json:"address"` -} - -type Student struct { - Index string `json:"index"` -} - -const year = 24 * 365 * time.Hour - -func main() { - alwaysFailingRule := govy.NewRule(func(string) error { - return fmt.Errorf("always fails") - }) - - v := govy.New( - govy.ForPointer(func(t Teacher) *string { return t.MiddleName }). - WithName("middleName"). - Required(). - Rules(alwaysFailingRule), - govy.For(func(t Teacher) string { return t.Name }). - WithName("name"). - Required(). - Rules(alwaysFailingRule), - ).WithName("Teacher") - - teacher := Teacher{ - Name: "", - Age: 51 * year, - MiddleName: nil, - } - - err := v.Validate(teacher) - if err != nil { - fmt.Println(err) - } - -} -``` - -#### Output - -``` -Validation for Teacher has failed for the following properties: - - 'middleName': - - property is required but was empty - - 'name': - - property is required but was empty -``` - -
-- -To only run property validation on condition, use [govy.PropertyRules.When](<#PropertyRules.When>). Predicates set through [govy.PropertyRules.When](<#PropertyRules.When>) are evaluated in the order they are provided. If any predicate is not met, validation rules are not evaluated for the whole [govy.PropertyRules](<#PropertyRules>). - -It's recommended to define [govy.PropertyRules.When](<#PropertyRules.When>) before [govy.PropertyRules.Rules](<#PropertyRules.Rules>) declaration. - -```go -package main - -import ( - "fmt" - "time" - - "github.com/nobl9/govy/pkg/govy" - "github.com/nobl9/govy/pkg/rules" -) - -type Teacher struct { - Name string `json:"name"` - Age time.Duration `json:"age"` - Students []Student `json:"students"` - MiddleName *string `json:"middleName,omitempty"` - University University `json:"university"` -} - -type University struct { - Name string `json:"name"` - Address string `json:"address"` -} - -type Student struct { - Index string `json:"index"` -} - -func main() { - v := govy.New( - govy.For(func(t Teacher) string { return t.Name }). - WithName("name"). - When(func(t Teacher) bool { return t.Name == "Jerry" }). - Rules(rules.NEQ("Jerry")), - ).WithName("Teacher") - - for _, name := range []string{"Tom", "Jerry", "Mickey"} { - teacher := Teacher{Name: name} - err := v.Validate(teacher) - if err != nil { - fmt.Println(err) - } - } - -} -``` - -#### Output - -``` -Validation for Teacher has failed for the following properties: - - 'name' with value 'Jerry': - - should be not equal to 'Jerry' -``` - -
-- -So far we've been using a very simple [govy.PropertyRules](<#PropertyRules>) instance: - -``` -validation.For(func(t Teacher) string { return t.Name }). - Rules(validation.NewRule(func(name string) error { return fmt.Errorf("always fails") })) -``` - -The error message returned by this property rule does not tell us which property is failing. Let's change that by adding property name using [govy.PropertyRules.WithName](<#PropertyRules.WithName>). - -We can also change the [govy.Rule](<#Rule>) to be something more real. govy comes with a number of predefined [govy.Rule](<#Rule>), we'll use \[rules.EQ\] which accepts a single argument, value to compare with. - -```go -package main - -import ( - "fmt" - "time" - - "github.com/nobl9/govy/pkg/govy" - "github.com/nobl9/govy/pkg/rules" -) - -type Teacher struct { - Name string `json:"name"` - Age time.Duration `json:"age"` - Students []Student `json:"students"` - MiddleName *string `json:"middleName,omitempty"` - University University `json:"university"` -} - -type University struct { - Name string `json:"name"` - Address string `json:"address"` -} - -type Student struct { - Index string `json:"index"` -} - -const year = 24 * 365 * time.Hour - -func main() { - v := govy.New( - govy.For(func(t Teacher) string { return t.Name }). - WithName("name"). - Rules(rules.EQ("Tom")), - ).WithName("Teacher") - - teacher := Teacher{ - Name: "Jake", - Age: 51 * year, - } - - err := v.Validate(teacher) - if err != nil { - fmt.Println(err) - } - -} -``` - -#### Output - -``` -Validation for Teacher has failed for the following properties: - - 'name' with value 'Jake': - - should be equal to 'Tom' -``` - -
-- -When dealing with maps there are three forms of iteration: \- keys \- values \- key\-value pairs \(items\) - -You can use [govy.ForMap](<#ForMap>) function to define rules for all the aforementioned iterators. It returns a new struct [govy.PropertyRulesForMap](<#PropertyRulesForMap>) which behaves similar to [govy.PropertyRulesForSlice](<#PropertyRulesForSlice>).. - -To define rules for keys use: \- [govy.PropertyRulesForMap.RulesForKeys](<#PropertyRulesForMap.RulesForKeys>) \- [govy.PropertyRulesForMap.IncludeForKeys](<#PropertyRulesForMap.IncludeForKeys>) \- [govy.PropertyRulesForMap.RulesForValues](<#PropertyRulesForMap.RulesForValues>) \- [govy.PropertyRulesForMap.IncludeForValues](<#PropertyRulesForMap.IncludeForValues>) \- [govy.PropertyRulesForMap.RulesForItems](<#PropertyRulesForMap.RulesForItems>) \- [govy.PropertyRulesForMap.IncludeForItems](<#PropertyRulesForMap.IncludeForItems>) These work exactly the same way as [govy.PropertyRules.Rules](<#PropertyRules.Rules>) and [govy.PropertyRules.Include](<#PropertyRules.Include>) verifying each map's key, value or [govy.MapItem](<#MapItem>). - -[govy.PropertyRulesForMap.Rules](<#PropertyRulesForMap.Rules>) is in turn used to define rules for the whole map. - -Note: [govy.PropertyRulesForMap](<#PropertyRulesForMap>) does not implement Include function for the whole map. - -In the below example, we're defining that student index to \[Teacher\] map: \- Must have at most 2 elements \(map\). \- Keys must have a length of 9 \(keys\). \- Eve cannot be a teacher for any student \(values\). \- Joan cannot be a teacher for student with index 918230013 \(items\). - -Notice that property path for maps has the following format: \
-
-
-When dealing with slices we often want to both validate the whole slice and each of its elements. You can use [govy.ForSlice](<#ForSlice>) function to do just that. It returns a new struct [govy.PropertyRulesForSlice](<#PropertyRulesForSlice>) which behaves exactly the same as [govy.PropertyRules](<#PropertyRules>), but extends its API slightly.
-
-To define rules for each element use: \- [govy.PropertyRulesForSlice.RulesForEach](<#PropertyRulesForSlice.RulesForEach>) \- [govy.PropertyRulesForSlice.IncludeForEach](<#PropertyRulesForSlice.IncludeForEach>) These work exactly the same way as [govy.PropertyRules.Rules](<#PropertyRules.Rules>) and [govy.PropertyRules.Include](<#PropertyRules.Include>) verifying each slice element.
-
-[govy.PropertyRulesForSlice.Rules](<#PropertyRulesForSlice.Rules>) is in turn used to define rules for the whole slice.
-
-Note: [govy.PropertyRulesForSlice](<#PropertyRulesForSlice>) does not implement Include function for the whole slice.
-
-In the below example, we're defining that students slice must have at most 2 elements and that each element's index must be unique. For each element we're also including \[Student\] [govy.Validator](<#Validator>). Notice that property path for slices has the following format: \
-
-[govy.Rule](<#Rule>) error might be static, i.e. a single [govy.Rule](<#Rule>) always returns the same exact error message, but they don't have to. For instance, consider a rule which parses a URL using [net/url](
- -You can use [govy.Rule.WithDetails](<#Rule.WithDetails>) to add additional details to the error message. This allows you to extend existing rules by adding your use case context. Let's give a regex validation some more clarity. - -```go -package main - -import ( - "fmt" - "regexp" - "time" - - "github.com/nobl9/govy/pkg/govy" - "github.com/nobl9/govy/pkg/rules" -) - -type Teacher struct { - Name string `json:"name"` - Age time.Duration `json:"age"` - Students []Student `json:"students"` - MiddleName *string `json:"middleName,omitempty"` - University University `json:"university"` -} - -type University struct { - Name string `json:"name"` - Address string `json:"address"` -} - -type Student struct { - Index string `json:"index"` -} - -const year = 24 * 365 * time.Hour - -func main() { - v := govy.New( - govy.For(func(t Teacher) string { return t.Name }). - WithName("name"). - Rules(rules.StringMatchRegexp(regexp.MustCompile("^(Tom|Jerry)$")). - WithDetails("Teacher can be either Tom or Jerry :)")), - ).WithName("Teacher") - - teacher := Teacher{ - Name: "Jake", - Age: 51 * year, - } - - err := v.Validate(teacher) - if err != nil { - fmt.Println(err) - } - -} -``` - -#### Output - -``` -Validation for Teacher has failed for the following properties: - - 'name' with value 'Jake': - - string must match regular expression: '^(Tom|Jerry)$'; Teacher can be either Tom or Jerry :) -``` - -
-- -When testing, it can be tedious to always rely on error messages as these can change over time. Enter [govy.ErrorCode](<#ErrorCode>), which is a simple string type alias used to ease testing, but also potentially allow third parties to integrate with your validation results. Use [govy.Rule.WithErrorCode](<#Rule.WithErrorCode>) to associate [govy.ErrorCode](<#ErrorCode>) with a [govy.Rule](<#Rule>). Notice that our modified version of \[rules.StringMatchRegexp\] will now return a new [govy.ErrorCode](<#ErrorCode>). Predefined rules have [govy.ErrorCode](<#ErrorCode>) already associated with them. To view the list of predefined [govy.ErrorCode](<#ErrorCode>) checkout error\_codes.go file. - -```go -package main - -import ( - "fmt" - "regexp" - "time" - - "github.com/nobl9/govy/pkg/govy" - "github.com/nobl9/govy/pkg/rules" -) - -type Teacher struct { - Name string `json:"name"` - Age time.Duration `json:"age"` - Students []Student `json:"students"` - MiddleName *string `json:"middleName,omitempty"` - University University `json:"university"` -} - -type University struct { - Name string `json:"name"` - Address string `json:"address"` -} - -type Student struct { - Index string `json:"index"` -} - -const year = 24 * 365 * time.Hour - -func main() { - v := govy.New( - govy.For(func(t Teacher) string { return t.Name }). - WithName("name"). - Rules(rules.StringMatchRegexp(regexp.MustCompile("^(Tom|Jerry)$")). - WithDetails("Teacher can be either Tom or Jerry :)"). - WithErrorCode("custom_code")), - ).WithName("Teacher") - - teacher := Teacher{ - Name: "Jake", - Age: 51 * year, - } - - err := v.Validate(teacher) - if err != nil { - propertyErrors := err.(*govy.ValidatorError).Errors - ruleErrors := propertyErrors[0].Errors - fmt.Println(ruleErrors[0].Code) - } - -} -``` - -#### Output - -``` -custom_code -``` - -
-- -If you want to override the default error message, you can use [govy.Rule.WithMessage](<#Rule.WithMessage>). - -```go -package main - -import ( - "fmt" - "regexp" - "time" - - "github.com/nobl9/govy/pkg/govy" - "github.com/nobl9/govy/pkg/rules" -) - -type Teacher struct { - Name string `json:"name"` - Age time.Duration `json:"age"` - Students []Student `json:"students"` - MiddleName *string `json:"middleName,omitempty"` - University University `json:"university"` -} - -type University struct { - Name string `json:"name"` - Address string `json:"address"` -} - -type Student struct { - Index string `json:"index"` -} - -const year = 24 * 365 * time.Hour - -func main() { - v := govy.New( - govy.For(func(t Teacher) string { return t.Name }). - WithName("name"). - Rules(rules.StringMatchRegexp(regexp.MustCompile("^(Tom|Jerry)$")). - WithDetails("Teacher can be either Tom or Jerry :)"). - WithMessage("unsupported name")), - ).WithName("Teacher") - - teacher := Teacher{ - Name: "Jake", - Age: 51 * year, - } - - err := v.Validate(teacher) - if err != nil { - fmt.Println(err) - } - -} -``` - -#### Output - -``` -Validation for Teacher has failed for the following properties: - - 'name' with value 'Jake': - - unsupported name; Teacher can be either Tom or Jerry :) -``` - -
-- -Sometimes it's useful to aggregate multiple [govy.Rule](<#Rule>) into a single, composite rule. To do that we'll use [govy.RuleSet](<#RuleSet>) and [govy.NewRuleSet](<#NewRuleSet>) constructor. RuleSet is a simple container for multiple [govy.Rule](<#Rule>). During validation it is unpacked and each [govy.RuleError](<#RuleError>) is reported separately. - -Note that govy uses similar syntax to wrapped errors in Go; a ':' delimiter is used to chain error codes together. - -```go -package main - -import ( - "fmt" - "regexp" - "time" - - "github.com/nobl9/govy/pkg/govy" - "github.com/nobl9/govy/pkg/rules" -) - -type Teacher struct { - Name string `json:"name"` - Age time.Duration `json:"age"` - Students []Student `json:"students"` - MiddleName *string `json:"middleName,omitempty"` - University University `json:"university"` -} - -type University struct { - Name string `json:"name"` - Address string `json:"address"` -} - -type Student struct { - Index string `json:"index"` -} - -const year = 24 * 365 * time.Hour - -func main() { - teacherNameRule := govy.NewRuleSet( - rules.StringLength(1, 5), - rules.StringMatchRegexp(regexp.MustCompile("^(Tom|Jerry)$")). - WithDetails("Teacher can be either Tom or Jerry :)"), - ). - WithErrorCode("teacher_name") - - v := govy.New( - govy.For(func(t Teacher) string { return t.Name }). - WithName("name"). - Rules(teacherNameRule), - ).WithName("Teacher") - - teacher := Teacher{ - Name: "Jonathan", - Age: 51 * year, - } - - err := v.Validate(teacher) - if err != nil { - propertyErrors := err.(*govy.ValidatorError).Errors - ruleErrors := propertyErrors[0].Errors - fmt.Printf("Error codes: %s, %s\n\n", ruleErrors[0].Code, ruleErrors[1].Code) - fmt.Println(err) - } - -} -``` - -#### Output - -``` -Error codes: teacher_name:string_length, teacher_name:string_match_regexp - -Validation for Teacher has failed for the following properties: - - 'name' with value 'Jonathan': - - length must be between 1 and 5 - - string must match regular expression: '^(Tom|Jerry)$'; Teacher can be either Tom or Jerry :) -``` - -
-- -Bringing it all \(mostly\) together, let's create a fully fledged [govy.Validator](<#Validator>) for \[Teacher\]. - -```go -package main - -import ( - "fmt" - "time" - - "github.com/nobl9/govy/pkg/govy" - "github.com/nobl9/govy/pkg/rules" -) - -type Teacher struct { - Name string `json:"name"` - Age time.Duration `json:"age"` - Students []Student `json:"students"` - MiddleName *string `json:"middleName,omitempty"` - University University `json:"university"` -} - -type University struct { - Name string `json:"name"` - Address string `json:"address"` -} - -type Student struct { - Index string `json:"index"` -} - -func main() { - universityValidation := govy.New( - govy.For(func(u University) string { return u.Address }). - WithName("address"). - Required(), - ) - studentValidator := govy.New( - govy.For(func(s Student) string { return s.Index }). - WithName("index"). - Rules(rules.StringLength(9, 9)), - ) - teacherValidator := govy.New( - govy.For(func(t Teacher) string { return t.Name }). - WithName("name"). - Required(). - Rules( - rules.StringNotEmpty(), - rules.OneOf("Jake", "George")), - govy.ForSlice(func(t Teacher) []Student { return t.Students }). - WithName("students"). - Rules( - rules.SliceMaxLength[[]Student](2), - rules.SliceUnique(func(v Student) string { return v.Index })). - IncludeForEach(studentValidator), - govy.For(func(t Teacher) University { return t.University }). - WithName("university"). - Include(universityValidation), - ).When(func(t Teacher) bool { return t.Age < 50 }) - - teacher := Teacher{ - Name: "John", - Students: []Student{ - {Index: "918230014"}, - {Index: "9182300123"}, - {Index: "918230014"}, - }, - University: University{ - Name: "Poznan University of Technology", - Address: "", - }, - } - - err := teacherValidator.WithName("John").Validate(teacher) - if err != nil { - fmt.Println(err) - } - -} -``` - -#### Output - -``` -Validation for John has failed for the following properties: - - 'name' with value 'John': - - must be one of [Jake, George] - - 'students' with value '[{"index":"918230014"},{"index":"9182300123"},{"index":"918230014"}]': - - length must be less than or equal to 2 - - elements are not unique, index 0 collides with index 2 - - 'students[1].index' with value '9182300123': - - length must be between 9 and 9 - - 'university.address': - - property is required but was empty -``` - -
-- -When dealing with properties that should only be validated if a certain other property has specific value, it's recommended to use [govy.PropertyRules.When](<#PropertyRules.When>) and [govy.PropertyRules.Include](<#PropertyRules.Include>) to separate validation paths into non\-overlapping branches. - -Notice how in the below example \[File.Format\] is the common, shared property between \[CSV\] and \[JSON\] files. We define separate [govy.Validator](<#Validator>) for \[CSV\] and \[JSON\] and use [govy.PropertyRules.When](<#PropertyRules.When>) to only validate their included [govy.Validator](<#Validator>) if the correct \[File.Format\] is provided. - -```go -package main - -import ( - "fmt" - "regexp" - - "github.com/nobl9/govy/pkg/govy" - "github.com/nobl9/govy/pkg/rules" -) - -func main() { - type ( - CSV struct { - Separator string `json:"separator"` - } - JSON struct { - Indent string `json:"indent"` - } - File struct { - Format string `json:"format"` - CSV *CSV `json:"csv,omitempty"` - JSON *JSON `json:"json,omitempty"` - } - ) - - csvValidation := govy.New( - govy.For(func(c CSV) string { return c.Separator }). - WithName("separator"). - Required(). - Rules(rules.OneOf(",", ";")), - ) - - jsonValidation := govy.New( - govy.For(func(j JSON) string { return j.Indent }). - WithName("indent"). - Required(). - Rules(rules.StringMatchRegexp(regexp.MustCompile(`^\s*$`))), - ) - - fileValidation := govy.New( - govy.ForPointer(func(f File) *CSV { return f.CSV }). - When(func(f File) bool { return f.Format == "csv" }). - Include(csvValidation), - govy.ForPointer(func(f File) *JSON { return f.JSON }). - When(func(f File) bool { return f.Format == "json" }). - Include(jsonValidation), - govy.For(func(f File) string { return f.Format }). - WithName("format"). - Required(). - Rules(rules.OneOf("csv", "json")), - ).WithName("File") - - file := File{ - Format: "json", - CSV: nil, - JSON: &JSON{ - Indent: "invalid", - }, - } - - err := fileValidation.Validate(file) - if err != nil { - fmt.Println(err) - } - -} -``` - -#### Output - -``` -Validation for File has failed for the following properties: - - 'indent' with value 'invalid': - - string must match regular expression: '^\s*$' -``` - -
-- -In order to create a new [govy.Validator](<#Validator>) use [govy.New](<#New>) constructor. Let's define simple [govy.PropertyRules](<#PropertyRules>) for \[Teacher.Name\]. For now, it will be always failing. - -```go -package main - -import ( - "fmt" - "time" - - "github.com/nobl9/govy/pkg/govy" -) - -type Teacher struct { - Name string `json:"name"` - Age time.Duration `json:"age"` - Students []Student `json:"students"` - MiddleName *string `json:"middleName,omitempty"` - University University `json:"university"` -} - -type University struct { - Name string `json:"name"` - Address string `json:"address"` -} - -type Student struct { - Index string `json:"index"` -} - -func main() { - v := govy.New( - govy.For(func(t Teacher) string { return t.Name }). - Rules(govy.NewRule(func(name string) error { return fmt.Errorf("always fails") })), - ) - - err := v.Validate(Teacher{}) - if err != nil { - fmt.Println(err) - } - -} -``` - -#### Output - -``` -Validation has failed for the following properties: - - always fails -``` - -
-- -[govy.Validator](<#Validator>) rules can be evaluated on condition, to specify the predicate use [govy.Validator.When](<#Validator.When>) function. - -In this example, validation for \[Teacher\] instance will only be evaluated if the \[Teacher.Age\] property is less than 50 years. - -```go -package main - -import ( - "fmt" - "time" - - "github.com/nobl9/govy/pkg/govy" -) - -type Teacher struct { - Name string `json:"name"` - Age time.Duration `json:"age"` - Students []Student `json:"students"` - MiddleName *string `json:"middleName,omitempty"` - University University `json:"university"` -} - -type University struct { - Name string `json:"name"` - Address string `json:"address"` -} - -type Student struct { - Index string `json:"index"` -} - -const year = 24 * 365 * time.Hour - -func main() { - v := govy.New( - govy.For(func(t Teacher) string { return t.Name }). - Rules(govy.NewRule(func(name string) error { return fmt.Errorf("always fails") })), - ). - When(func(t Teacher) bool { return t.Age < (50 * year) }) - - // Prepare teachers. - teacherTom := Teacher{ - Name: "Tom", - Age: 51 * year, - } - teacherJerry := Teacher{ - Name: "Jerry", - Age: 30 * year, - } - - // Run validation. - err := v.Validate(teacherTom) - if err != nil { - fmt.Println(err.(*govy.ValidatorError).WithName("Tom")) - } - err = v.Validate(teacherJerry) - if err != nil { - fmt.Println(err.(*govy.ValidatorError).WithName("Jerry")) - } - -} -``` - -#### Output - -``` -Validation for Jerry has failed for the following properties: - - always fails -``` - -
-- -To associate [govy.Validator](<#Validator>) with an entity name use [govy.Validator.WithName](<#Validator.WithName>) function. When any of the rules fails, the error will contain the entity name you've provided. - -```go -package main - -import ( - "fmt" - "time" - - "github.com/nobl9/govy/pkg/govy" -) - -type Teacher struct { - Name string `json:"name"` - Age time.Duration `json:"age"` - Students []Student `json:"students"` - MiddleName *string `json:"middleName,omitempty"` - University University `json:"university"` -} - -type University struct { - Name string `json:"name"` - Address string `json:"address"` -} - -type Student struct { - Index string `json:"index"` -} - -func main() { - v := govy.New( - govy.For(func(t Teacher) string { return t.Name }). - Rules(govy.NewRule(func(name string) error { return fmt.Errorf("always fails") })), - ).WithName("Teacher") - - err := v.Validate(Teacher{}) - if err != nil { - fmt.Println(err) - } - -} -``` - -#### Output - -``` -Validation for Teacher has failed for the following properties: - - always fails -``` - -
-- -All errors returned by [govy.Validator](<#Validator>) are of type [govy.ValidatorError](<#ValidatorError>). Type casting directly to [govy.ValidatorError](<#ValidatorError>) should be safe once an error was asserted to be non\-nil. However, you shouldn't trust any API with such promises, and always type check in your type assignments. - -All error types return by govy are JSON serializable. - -```go -package main - -import ( - "encoding/json" - "fmt" - "os" - "time" - - "github.com/nobl9/govy/pkg/govy" -) - -type Teacher struct { - Name string `json:"name"` - Age time.Duration `json:"age"` - Students []Student `json:"students"` - MiddleName *string `json:"middleName,omitempty"` - University University `json:"university"` -} - -type University struct { - Name string `json:"name"` - Address string `json:"address"` -} - -type Student struct { - Index string `json:"index"` -} - -func main() { - v := govy.New( - govy.For(func(t Teacher) string { return t.Name }). - Rules(govy.NewRule(func(name string) error { return fmt.Errorf("always fails") })). - WithName("name"), - ).WithName("Teacher") - - err := v.Validate(Teacher{Name: "John"}) - if err != nil { - if validatorErr, ok := err.(*govy.ValidatorError); ok { - enc := json.NewEncoder(os.Stdout) - enc.SetIndent("", " ") - if err = enc.Encode(validatorErr); err != nil { - fmt.Printf("error encoding: %v\n", err) - } - } - } - -} -``` - -#### Output - -``` -{ - "errors": [ - { - "propertyName": "name", - "propertyValue": "John", - "errors": [ - { - "error": "always fails" - } - ] - } - ], - "name": "Teacher" -} -``` - -
-- -You can also add [govy.Validator](<#Validator>) name during runtime, by calling [govy.ValidatorError.WithName](<#ValidatorError.WithName>) function on the returned error. - -Note: We left the previous "Teacher" name assignment, to demonstrate that the [govy.ValidatorError.WithName](<#ValidatorError.WithName>) function call will overwrite it. - -Note: This would also work: - -``` -err := v.WithName("Jake").Validate(Teacher{}) -``` - -govy, excluding error handling, tries to follow immutability principle. Calling any method on [govy.Validator](<#Validator>) will not change its declared instance, but rather create a copy of it. - -```go -package main - -import ( - "fmt" - "time" - - "github.com/nobl9/govy/pkg/govy" -) - -type Teacher struct { - Name string `json:"name"` - Age time.Duration `json:"age"` - Students []Student `json:"students"` - MiddleName *string `json:"middleName,omitempty"` - University University `json:"university"` -} - -type University struct { - Name string `json:"name"` - Address string `json:"address"` -} - -type Student struct { - Index string `json:"index"` -} - -func main() { - v := govy.New( - govy.For(func(t Teacher) string { return t.Name }). - Rules(govy.NewRule(func(name string) error { return fmt.Errorf("always fails") })), - ).WithName("Teacher") - - err := v.Validate(Teacher{}) - if err != nil { - fmt.Println(err.(*govy.ValidatorError).WithName("Jake")) - } - -} -``` - -#### Output - -``` -Validation for Jake has failed for the following properties: - - always fails -``` - -
-- -When documenting an API it's often a struggle to keep consistency between the code and documentation we write for it. What If your code could be self\-descriptive? Specifically, what If we could generate documentation out of our validation rules? We can achieve that by using [govy.Plan](<#Plan>) function\! - -There are multiple ways to improve the generated documentation: - -- Use [govy.PropertyRules.WithExamples](<#PropertyRules.WithExamples>) to provide a list of example values for the property. -- Use [govy.Rule.WithDescription](<#Rule.WithDescription>) to provide a plan\-only description for your rule. For builtin rules, the description is already provided. -- Use [govy.WhenDescription](<#WhenDescription>) to provide a plan\-only description for your when conditions. - -```go -package main - -import ( - "encoding/json" - "fmt" - "os" - "time" - - "github.com/nobl9/govy/pkg/govy" - "github.com/nobl9/govy/pkg/rules" -) - -type Teacher struct { - Name string `json:"name"` - Age time.Duration `json:"age"` - Students []Student `json:"students"` - MiddleName *string `json:"middleName,omitempty"` - University University `json:"university"` -} - -type University struct { - Name string `json:"name"` - Address string `json:"address"` -} - -type Student struct { - Index string `json:"index"` -} - -func main() { - v := govy.New( - govy.For(func(t Teacher) string { return t.Name }). - WithName("name"). - WithExamples("Jake", "John"). - When( - func(t Teacher) bool { return t.Name == "Jerry" }, - govy.WhenDescription("name is Jerry"), - ). - Rules( - rules.NEQ("Jerry"). - WithDetails("Jerry is just a name!"), - govy.NewRule(func(v string) error { - return fmt.Errorf("some custom error") - }). - WithDescription("this is a custom error!"), - ), - ).WithName("Teacher") - - properties := govy.Plan(v) - enc := json.NewEncoder(os.Stdout) - enc.SetIndent("", " ") - _ = enc.Encode(properties) - -} -``` - -#### Output - -``` -{ - "name": "Teacher", - "properties": [ - { - "path": "$.name", - "type": "string", - "examples": [ - "Jake", - "John" - ], - "rules": [ - { - "description": "should be not equal to 'Jerry'", - "details": "Jerry is just a name!", - "errorCode": "not_equal_to", - "conditions": [ - "name is Jerry" - ] - }, - { - "description": "this is a custom error!", - "conditions": [ - "name is Jerry" - ] - } - ] - } - ] -} -``` - -
-
-
-In the interactive tutorial for govy, we've been using \[govy.PropertyRules.WithName\] to provide the name for our properties.
-
-Ideally, we'd want to make sure the names govy assigns to each property, match the name of the real\-world struct representation that the user interacts with. Go uses struct tags to achieve just that, and libraries like [encoding/json](
- -Beware where you call [govyconfig.SetNameInferMode](<#SetNameInferMode>). If you call it after the \[govy.For\] has been called, it won't do anything. This is because the name inference is done during the creation of \[govy.PropertyRules\]. - -```go -package main - -import ( - "fmt" - - "github.com/nobl9/govy/pkg/govy" - "github.com/nobl9/govy/pkg/govyconfig" - "github.com/nobl9/govy/pkg/rules" -) - -type Teacher struct { - Name string `json:"name"` -} - -func main() { - govyconfig.SetNameInferIncludeTestFiles(true) - defer govyconfig.SetNameInferIncludeTestFiles(false) - - v := govy.New( - govy.For(func(t Teacher) string { return t.Name }). - Rules(rules.EQ("Jerry")), - ).WithName("Teacher") - - govyconfig.SetNameInferMode(govyconfig.NameInferModeRuntime) - defer govyconfig.SetNameInferMode(govyconfig.NameInferModeDisable) - - teacher := Teacher{Name: "Tom"} - err := v.Validate(teacher) - if err != nil { - fmt.Println(err) - } - -} -``` - -#### Output - -``` -Validation for Teacher has failed for the following properties: - - should be equal to 'Jerry' -``` - -
-