### Add Rule Examples Source: https://github.com/nobl9/govy/wiki/coverage.html Use WithExamples to add examples to a rule. Each example is converted to a string and can be included in the error output for better understanding. ```go // WithExamples adds examples to the returned [RuleError]. // Each example is converted to a string. func (r Rule[T]) WithExamples(examples ...T) Rule[T] { r.examples = collections.ToStringSlice(examples) return r } ``` -------------------------------- ### Install govy Binary Source: https://github.com/nobl9/govy/blob/main/cmd/govy/README.md Install the govy binary using the go install command. This ensures you have the latest version of the tool. ```shell go install github.com/nobl9/govy/cmd/govy@latest ``` -------------------------------- ### Install Govy Library Source: https://github.com/nobl9/govy/blob/main/README.md Add the govy library to your Go project using the go get command. ```shell go get github.com/nobl9/govy ``` -------------------------------- ### Set Examples for PropertyPlan Source: https://github.com/nobl9/govy/wiki/coverage.html Sets the examples for a propertyPlan within the planBuilder. This is a simple setter method. ```go func (p planBuilder) setExamples(examples ...string) planBuilder { p.propertyPlan.Examples = examples return p } ``` -------------------------------- ### Add Examples to Property Rules Source: https://github.com/nobl9/govy/wiki/coverage.html Appends one or more example strings to the property's list of examples. Examples are typically used for documentation or testing purposes. ```go // WithExamples sets the examples for the property. func (r PropertyRules[T, P]) WithExamples(examples ...string) PropertyRules[T, P] { r.examples = append(r.examples, examples...) return r } ``` -------------------------------- ### Convert Examples to String Source: https://github.com/nobl9/govy/wiki/coverage.html The examplesToString function formats a slice of example strings into a human-readable string, suitable for inclusion in error messages. It adds parentheses and appropriate spacing. ```go func examplesToString(examples []string) string { if len(examples) == 0 { return "" } b := strings.Builder{} b.WriteString(" (e.g. ") internal.PrettyStringListBuilder(&b, examples, "'") b.WriteString(")") return b.String() } ``` -------------------------------- ### Format Examples for Template Source: https://github.com/nobl9/govy/wiki/coverage.html Formats a slice of strings into a human-readable string for example values. Useful for displaying valid options in template output. Handles empty slices gracefully. ```go // formatExamplesTplFunc formats a list of strings which are example valid values // as a single string representation. // Example: `{{ formatExamples ["foo", "bar"] }}` -> "(e.g. 'foo', 'bar')" func formatExamplesTplFunc(examples []string) string { if len(examples) == 0 { return "" } b := strings.Builder{} b.WriteString("(e.g. ") internal.PrettyStringListBuilder(&b, examples, "'") b.WriteString(")") return b.String() } ``` -------------------------------- ### Example govy inferpath Output File Source: https://github.com/nobl9/govy/blob/main/cmd/govy/README.md This is an example of the code generated by the 'govy inferpath' command. It registers inferred paths for use by the govy package. Ensure this file is loaded before any govy.PropertyRules constructors are called. ```go // Code generated by "govy inferpath -dir . -pkg main"; DO NOT EDIT. package main import ( "github.com/nobl9/govy/pkg/govyconfig" "github.com/nobl9/govy/pkg/jsonpath" ) func init() { inferredPaths := map[string]govyconfig.InferredPath{ "student/nested.go:13": { Path: jsonpath.Parse("name"), File: "student/nested.go", Line: 13, }, "university/university.go:13": { Path: jsonpath.Parse("name"), File: "university/university.go", Line: 13, }, "validation.go:11": { Path: jsonpath.Parse("name"), File: "validation.go", Line: 11, }, "validation.go:13": { Path: jsonpath.Parse("university.name"), File: "validation.go", Line: 13, }, "validation.go:15": { Path: jsonpath.Parse("university"), File: "validation.go", Line: 15, }, } for _, path := range inferredPaths { govyconfig.SetInferredPath(path) } } ``` -------------------------------- ### Set Examples for Slice Property Rules Source: https://github.com/nobl9/govy/wiki/coverage.html Associates example values with the slice rules. This method delegates to the underlying sliceRules. ```go // WithExamples => refer to [PropertyRules.WithExamples] documentation. func (r PropertyRulesForSlice[S, T, P]) WithExamples(examples ...string) PropertyRulesForSlice[S, T, P] { r.sliceRules = r.sliceRules.WithExamples(examples...) return r } ``` -------------------------------- ### Configure Map Property Rules with WithExamples in Go Source: https://github.com/nobl9/govy/wiki/coverage.html Use the WithExamples method to provide example values for the map property rules. This is analogous to the PropertyRules.WithExamples documentation. ```go // WithExamples => refer to [PropertyRules.WithExamples] documentation. func (r PropertyRulesForMap[M, K, V, P]) WithExamples(examples ...string) PropertyRulesForMap[M, K, V, P] { r.mapRules = r.mapRules.WithExamples(examples...) return r } ``` -------------------------------- ### RulePlan equal Method Source: https://github.com/nobl9/govy/wiki/coverage.html Compares two RulePlan instances for equality based on their description, details, error code, conditions, and examples. ```go func (r RulePlan) equal(r2 RulePlan) bool { return r.Description == r2.Description && r.Details == r2.Details && r.ErrorCode == r2.ErrorCode && collections.EqualSlices(r.Conditions, r2.Conditions) && collections.EqualSlices(r.Examples, r2.Examples) } ``` -------------------------------- ### Rule Details and Examples Customization Source: https://github.com/nobl9/govy/wiki/coverage.html Methods for adding details and examples to the rule's error reporting. ```APIDOC ## Rule.WithDetails ### Description Adds details to the returned `RuleError` error message. ### Method Rule.WithDetails ### Parameters #### Path Parameters - **details** (string) - Required - The details to add to the error message. ### Response Returns the modified `Rule` object. ``` ```APIDOC ## Rule.WithDetailsf ### Description Adds formatted details to the returned `RuleError` error message. ### Method Rule.WithDetailsf ### Parameters #### Path Parameters - **format** (string) - Required - The format string for the details. - **a** (...any) - Required - Arguments for the format string. ### Response Returns the modified `Rule` object. ``` ```APIDOC ## Rule.WithExamples ### Description Adds examples to the returned `RuleError`. Each example is converted to a string. ### Method Rule.WithExamples ### Parameters #### Path Parameters - **examples** (...T) - Required - The examples to associate with the rule. ### Response Returns the modified `Rule` object. ``` -------------------------------- ### Construct Validation Plan Source: https://github.com/nobl9/govy/wiki/coverage.html Builds a validation plan for the property, including type information, name, examples, and predicates. ```go func (r PropertyRules[T, P]) plan(builder planBuilder) { builder.propertyPlan.IsHidden = r.hideValue if r.originalType != nil { builder.propertyPlan.TypeInfo = TypeInfo(*r.originalType) } else { builder.propertyPlan.TypeInfo = TypeInfo(typeinfo.Get[T]()) } builder = builder.appendPath(r.getName()).setExamples(r.examples...) builder = appendPredicatesToPlanBuilder(builder, r.predicates) if r.required { // Dummy rule to register the property as required. NewRule(func(v T) error { return nil }). WithDescription(internal.RequiredDescription). WithErrorCode(internal.RequiredErrorCode). plan(builder) } else if r.omitEmpty || r.isPointer { // Dummy rule to register the property as optional. NewRule(func(v T) error { return nil }). WithDescription(internal.OptionalDescription). WithErrorCode(internal.OptionalErrorCode). plan(builder) } for _, rule := range r.rules { if p, ok := rule.(planner); ok { p.plan(builder) } } // If we don't have any rules defined for this property, append it nonetheless. // It can be useful when we have things like [WithExamples] or [Required] set. if len(r.rules) == 0 { *builder.path = append(*builder.path, builder) } } ``` -------------------------------- ### Infer Property Paths at Runtime Source: https://github.com/nobl9/govy/blob/main/README.md Demonstrates runtime path inference for validation rules. Ensure `InferPathIncludeTestFiles` is set to true for this example to function correctly. ```go package examples import ( "fmt" "github.com/nobl9/govy/pkg/govy" "github.com/nobl9/govy/pkg/govyconfig" "github.com/nobl9/govy/pkg/rules" ) func Example_pathInference() { govyconfig.SetInferPathIncludeTestFiles(true) // Required for the example to run. defer govyconfig.SetInferPathIncludeTestFiles(false) type Teacher struct { Name string `json:"name"` } v := govy.New( govy.For(func(t Teacher) string { return t.Name }). Rules(rules.EQ("Jerry")), ). InferPath(govy.InferPathModeRuntime). WithNameFunc(govy.NameFuncFromTypeName[Teacher]()) teacher := Teacher{Name: "Tom"} err := v.Validate(teacher) if err != nil { fmt.Println(err) } // Output: // Validation for Teacher has failed for the following properties: // - 'name' with value 'Tom': // - must be equal to 'Jerry' } ``` -------------------------------- ### Basic Govy Usage Example Source: https://github.com/nobl9/govy/blob/main/README.md Demonstrates defining validation rules for nested structs, including string matching, length constraints, and conditional validation. Use this to validate complex data structures with custom rules. ```go package examples import ( "fmt" "regexp" "time" "github.com/nobl9/govy/pkg/govy" "github.com/nobl9/govy/pkg/rules" ) func Example_basicUsage() { type University struct { Name string `json:"name"` Address string `json:"address"` } type Student struct { Index string `json:"index"` } 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"` } universityValidation := govy.New( govy.For(func(u University) string { return u.Name }). WithName("name"). Required(), govy.For(func(u University) string { return u.Address }). WithName("address"). Required(). Rules(rules.StringMatchRegexp( regexp.MustCompile(`[\w\s.]+, \d{2}-\d{3} \w+`)). WithDetails("Polish address format must consist of the main address and zip code"). WithExamples("5 M. Skłodowska-Curie Square, 60-965 Poznan")), ) 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.ForPointer(func(t Teacher) *string { return t.MiddleName }). WithName("middleName"). Rules(rules.StringTitle()), 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", MiddleName: nil, // Validation for nil pointers by default is skipped. Age: 48, Students: []Student{ {Index: "918230014"}, {Index: "9182300123"}, {Index: "918230014"}, }, University: University{ Name: "", Address: "10th University St.", }, } if err := teacherValidator.WithName("John").Validate(teacher); err != nil { fmt.Println(err) } // When condition is not met, no validation errors. johnFromTheFuture := teacher johnFromTheFuture.Age = 51 if err := teacherValidator.WithName("John From The Future").Validate(johnFromTheFuture); 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, 1st and 3rd elements collide // - 'students[1].index' with value '9182300123': // - length must be between 9 and 9 // - 'university.name': // - property is required but was empty // - 'university.address' with value '10th University St.': // - string must match regular expression: '[\w\s.]+, \d{2}-\d{3} \w+' (e.g. '5 M. Skłodowska-Curie Square, 60-965 Poznan'); Polish address format must consist of the main address and zip code } ``` -------------------------------- ### String Starts With Rule Source: https://github.com/nobl9/govy/wiki/coverage.html Validates if a string property begins with any of the specified prefixes. Returns an error if no prefix matches. ```go // StringStartsWith ensures the property's value starts with one of the provided prefixes. func StringStartsWith(prefixes ...string) govy.Rule[string] { tpl := messagetemplates.Get(messagetemplates.StringStartsWithTemplate) return govy.NewRule(func(s string) error { matched := false for _, prefix := range prefixes { if strings.HasPrefix(s, prefix) { matched = true break } } if !matched { return govy.NewRuleErrorTemplate(govy.TemplateVars{ PropertyValue: s, ComparisonValue: prefixes, }) } return nil }). WithErrorCode(ErrorCodeStringStartsWith). WithMessageTemplate(tpl). WithDescription(mustExecuteTemplate(tpl, govy.TemplateVars{ ComparisonValue: prefixes, })) } ``` -------------------------------- ### Validation Plan Schema Example Source: https://github.com/nobl9/govy/blob/main/README.md Illustrates the structure of a validation plan, including path, type information, and rules with conditions and error codes. Use this as a reference for defining validation strategies. ```json { "errorCode": "string_match_regexp", "conditions": [ "Teacher name is John", "University name is PUT University" ], "examples": [ "5 M. Skłodowska-Curie Square, 60-965 Poznan" ] } ] }, { "path": "$.university.name", "typeInfo": { "name": "string", "kind": "string" }, "rules": [ { "description": "property is required", "errorCode": "required", "conditions": [ "Teacher name is John" ] } ] } ] } } ``` -------------------------------- ### Define and Plan Validation Rules - Go Source: https://github.com/nobl9/govy/blob/main/README.md This example demonstrates how to define complex validation rules for nested structures using GoVy. It includes rules for strings, slices, and nested objects, and generates a validation plan. Ensure all necessary types and validators are imported. ```go package examples import ( "encoding/json" "os" "regexp" "time" "github.com/nobl9/govy/pkg/govy" "github.com/nobl9/govy/pkg/rules" ) func Example_validationPlan() { type University struct { Name string `json:"name"` Address string `json:"address"` } type Student struct { Index string `json:"index"` } 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"` } universityValidation := govy.New( govy.For(func(u University) string { return u.Name }). WithName("name"). Required(), govy.For(func(u University) string { return u.Address }). WithName("address"). Rules(rules.StringMatchRegexp( regexp.MustCompile(`[\w\s.]+, \d{2}-\d{3} \w+`)). WithDetails("Polish address format must consist of the main address and zip code"). WithExamples("5 M. Skłodowska-Curie Square, 60-965 Poznan")). When(func(u University) bool { return u.Name == "PUT" }, govy.WhenDescription("University name is PUT University")), ) 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"). Rules( rules.StringNotEmpty(), rules.OneOf("Jake", "George")), govy.ForPointer(func(t Teacher) *string { return t.MiddleName }). WithName("middleName"). Rules(rules.StringTitle()), 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.Name == "John" }, govy.WhenDescription("Teacher name is John")), ). WithName("Teacher") plan, err := govy.Plan(teacherValidator, govy.PlanStrictMode()) if err != nil { panic(err) } enc := json.NewEncoder(os.Stdout) enc.SetIndent("", " ") _ = enc.Encode(plan) // Output: // { // "name": "Teacher", // "properties": [ // { // "path": "$.middleName", // "typeInfo": { // "name": "string", // "kind": "string" // }, // "rules": [ // { // "description": "property is optional", // "errorCode": "optional" // }, // { // "description": "each word in a string must start with a capital letter", // "errorCode": "string_title" // } // ] // }, // { // "path": "$.name", // "typeInfo": { // "name": "string", // "kind": "string" // }, // "values": [ // "Jake", // "George" // ], // "rules": [ // { // "description": "string must not be empty", // "errorCode": "string_not_empty" // }, // { // "description": "must be one of: Jake, George", // "errorCode": "one_of" // } // ] // }, // { // "path": "$.students", // "typeInfo": { // "name": "[]Student", // "kind": "[]struct", // "package": "github.com/nobl9/govy/internal/examples" // }, // "rules": [ // { // "description": "length must be less than or equal to 2", // "errorCode": "slice_max_length" // }, // { // "description": "elements must be unique", // "errorCode": "slice_unique" // } // ] // }, // { // "path": "$.students[*].index", // "typeInfo": { // "name": "string", // "kind": "string" // }, // "rules": [ // { // "description": "length must be between 9 and 9", // "errorCode": "string_length" // } // ] // }, // { // "path": "$.university.address", // "typeInfo": { // "name": "string", // "kind": "string" // }, // "rules": [ // { // "description": "string must match regular expression: '[\w\s.]+, \d{2}-\d{3} \w+'", // "details": "Polish address format must consist of the main address and zip code", ``` -------------------------------- ### RulePlan Structure Source: https://github.com/nobl9/govy/wiki/coverage.html Defines a validation plan for a single Rule, including its description, details, error code, conditions, and examples. ```go // RulePlan is a validation plan for a single [Rule]. type RulePlan struct { // Description is the value provided to [Rule.WithDescription]. Description string `json:"description"` // Details is the value provided to [Rule.WithDetails]. Details string `json:"details,omitempty"` // ErrorCode is the value provided to [Rule.WithErrorCode]. ErrorCode ErrorCode `json:"errorCode,omitempty"` // Conditions are all the predicates set through [PropertyRules.When] and [Validator.When] // which had [WhenDescription] added to the [WhenOptions]. Conditions []string `json:"conditions,omitempty"` // Examples is the value provided to [Rule.WithExamples]. // These values are not exhaustive, for an exhaustive list of valid values see [PropertyPlan.ValidValues]. Examples []string `json:"examples,omitempty"` // values unlike [Examples] should list ALL valid values which meet this rule. // It is not exported as it is only here to contribute to the [PropertyPlan.ValidValues]. values []string } ``` -------------------------------- ### Create a Validator with govy.New Source: https://context7.com/nobl9/govy/llms.txt Use `govy.New` to create a top-level validator that aggregates property rules for a given type `T`. This example demonstrates validating a `Teacher` struct with name and age constraints. ```go package main import ( "fmt" "github.com/nobl9/govy/pkg/govy" "github.com/nobl9/govy/pkg/rules" ) type Teacher struct { Name string `json:"name"` Age int `json:"age"` } func main() { v := govy.New( govy.For(func(t Teacher) string { return t.Name }). WithName("name"). Required(). Rules(rules.StringNotEmpty(), rules.StringMaxLength(50)), govy.For(func(t Teacher) int { return t.Age }). WithName("age"). Rules(rules.GTE(18), rules.LTE(100)), ).WithName("Teacher") err := v.Validate(Teacher{Name: "", Age: 200}) if err != nil { fmt.Println(err) } // Output: // Validation for Teacher has failed for the following properties: // - 'name': // - property is required but was empty // - 'age' with value '200': // - must be less than or equal to 100 } ``` -------------------------------- ### Define Property Rules with govy.For Source: https://context7.com/nobl9/govy/llms.txt Use `govy.For` to define rules for a specific property using a getter function. This example validates a `User` struct, checking email format and username constraints. ```go package main import ( "fmt" "regexp" "github.com/nobl9/govy/pkg/govy" "github.com/nobl9/govy/pkg/rules" ) type User struct { Email string `json:"email"` Username string `json:"username"` } func main() { v := govy.New( govy.For(func(u User) string { return u.Email }). WithName("email"). Required(). Rules(rules.StringEmail()), govy.For(func(u User) string { return u.Username }). WithName("username"). Required(). Rules( rules.StringMatchRegexp(regexp.MustCompile(`^[a-z0-9_]+$`)). WithDetails("only lowercase letters, digits, and underscores allowed"). WithExamples("alice", "bob_123"), rules.StringLength(3, 30), ), ).WithName("User") err := v.Validate(User{Email: "not-an-email", Username: "A!"}) if err != nil { fmt.Println(err) } // Output: // Validation for User has failed for the following properties: // - 'email' with value 'not-an-email': // - 'not-an-email' is not a valid email address // - 'username' with value 'A!': // - string must match regular expression: '^[a-z0-9_]+$' (e.g. 'alice', 'bob_123'); only lowercase letters, digits, and underscores allowed // - length must be between 3 and 30 } ``` -------------------------------- ### Get Message Template Source: https://github.com/nobl9/govy/wiki/coverage.html Retrieves a message template by its key from a cache. If the template is not found, it panics. The template is parsed, custom functions and dependencies are added, and it's stored in the cache for subsequent requests. ```go // Get returns a message template by its key. // If the template is not found, it panics. // The first time a template is requested, it is parsed and stored in the cache. // Custom functions and dependencies are added to the template automatically. func Get(key templateKey) *template.Template { if tpl := messageTemplatesCache.Lookup(key); tpl != nil { return tpl } text, ok := rawMessageTemplates[key] if !ok { panic(fmt.Sprintf("message template %q was not found", key)) } text += commonTemplateSuffix tpl := newTemplate(key, text) messageTemplatesCache.Register(key, tpl) return tpl } ``` -------------------------------- ### Custom Error Message Templates for Custom Rules Source: https://github.com/nobl9/govy/blob/main/README.md When creating custom rules, ensure they always return `govy.RuleErrorTemplate` to support templating. This example demonstrates how to provide a template string and custom variables for a custom rule. ```go package examples import ( "fmt" "github.com/nobl9/govy/pkg/govy" "github.com/nobl9/govy/pkg/rules" ) func Example_addingMessageTemplatesSupportToCustomRules() { type Teacher struct { Name string `json:"name"` } template := `{{ .PropertyValue }} must be {{ .ComparisonValue }}; {{ .Custom.Foo }} and {{ .Custom.Baz }}` customRule := govy.NewRule(func(name string) error { if name != "John" { return govy.NewRuleErrorTemplate(govy.TemplateVars{ PropertyValue: name, ComparisonValue: "John", Custom: map[string]any{ "Foo": "Bar", "Baz": 42, }, }) } return nil }). WithErrorCode("custom_rule"). WithMessageTemplateString(template). WithDetails("we just don't like anyone but Johns..."). WithDescription("must be John") teacherValidator := govy.New( govy.For(func(t Teacher) string { return t.Name }). WithName("name"). Required(). Rules( customRule, rules.StringStartsWith("J"), ), ).WithNameFunc(govy.NameFuncFromTypeName[Teacher]()) teacher := Teacher{Name: "George"} if err := teacherValidator.Validate(teacher); err != nil { fmt.Println(err) } // Output: // Validation for Teacher has failed for the following properties: // - 'name' with value 'George': // - George must be John; Bar and 42 // - string must start with 'J' prefix } ``` -------------------------------- ### Govy CLI Main Function and Subcommand Handling Source: https://github.com/nobl9/govy/wiki/coverage.html This code sets up the main entry point for the Govy CLI tool. It handles command-line flag parsing and dispatches to the appropriate subcommand, such as 'nameinfer'. Error handling for invalid subcommands and execution errors is included. ```go package main import ( _ "embed" "flag" "fmt" "log/slog" "os" "strings" "github.com/nobl9/govy/pkg/govyconfig" ) const ( govyCmdName = "govy" inferNameCmdName = "nameinfer" ) var subcommands = []string{ inferNameCmdName, } func main() { govyconfig.SetLogLevel(slog.LevelDebug) rootCmd := flag.NewFlagSet(govyCmdName, flag.ExitOnError) rootCmd.Usage = func() { fmt.Fprintf(os.Stderr, "Usage of %s:\n", govyCmdName) fmt.Fprintf(os.Stderr, " %s [flags]\n", govyCmdName) fmt.Fprintf(os.Stderr, "Subcommands:\n") for _, cmd := range subcommands { fmt.Fprintf(os.Stderr, " %s\n", cmd) } } if len(os.Args) < 2 { rootCmd.Usage() os.Exit(1) } var cmd interface{ Run() error } switch os.Args[1] { case inferNameCmdName: cmd = newInferNameCommand() default: errFatalWithUsage( rootCmd, "'%s' is not a valid subcommand, try: %s", os.Args[1], strings.Join(subcommands, ", "), ) return } if err := cmd.Run(); err != nil { errFatal(err.Error()) } } func errFatalWithUsage(cmd *flag.FlagSet, f string, a ...any) { f = "Error: " + f if len(a) == 0 { fmt.Fprintln(os.Stderr, f) } else { ``` -------------------------------- ### Example Validation Error Output Source: https://github.com/nobl9/govy/blob/main/README.md This is an example of the verbose error messages generated by Govy. It shows validation failures for different properties, including nested fields and array elements, with clear indications of the error cause and context. ```text Validation for Teacher 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 ``` -------------------------------- ### Get Name of Slice Property Rules Source: https://github.com/nobl9/govy/wiki/coverage.html Returns the name of the property associated with these slice rules. ```go func (r PropertyRulesForSlice[S, T, P]) getName() string { return r.sliceRules.getName() } ``` -------------------------------- ### Get Name of Map Property Rules Source: https://github.com/nobl9/govy/wiki/coverage.html Returns the name of the map property. This is a simple getter method. ```go // getName returns the name of the property. func (r PropertyRulesForMap[M, K, V, P]) getName() string { return r.mapRules.getName() } ``` -------------------------------- ### Applying Predefined Rules to a Resource Struct Source: https://context7.com/nobl9/govy/llms.txt Demonstrates how to use `govy.For` and related functions to apply various predefined rules (string, numeric, enum, slice, map, URL) to fields of a `Resource` struct. Ensure all necessary imports are present. ```go package main import ( "fmt" "regexp" "github.com/nobl9/govy/pkg/govy" "github.com/nobl9/govy/pkg/rules" ) type Resource struct { Name string Replicas int Protocol string Tags []string Env map[string]string Endpoint string Image string } func main() { v := govy.New( // String rules govy.For(func(r Resource) string { return r.Name }). WithName("name"). Rules( rules.StringNotEmpty(), rules.StringLength(3, 63), rules.StringDNSLabel(), // RFC-1123 compliant ), // Comparable / numeric rules govy.For(func(r Resource) int { return r.Replicas }). WithName("replicas"). Rules(rules.GTE(1), rules.LTE(100)), // OneOf / enumeration govy.For(func(r Resource) string { return r.Protocol }). WithName("protocol"). Rules(rules.OneOf("TCP", "UDP", "SCTP")), // Slice rules govy.ForSlice(func(r Resource) []string { return r.Tags }). WithName("tags"). Rules(rules.SliceMaxLength[[]string](20)). RulesForEach(rules.StringNotEmpty(), rules.StringMaxLength(63)), // Map rules govy.ForMap(func(r Resource) map[string]string { return r.Env }). WithName("env"). Rules(rules.MapMaxLength[map[string]string](50)). RulesForKeys(rules.StringMatchRegexp(regexp.MustCompile(`^[A-Z_][A-Z0-9_]*$`))). RulesForValues(rules.StringMaxLength(255)), // URL rule govy.For(func(r Resource) string { return r.Endpoint }). WithName("endpoint"). Rules(rules.StringURL()), ).WithName("Resource") err := v.Validate(Resource{ Name: "My Pod!", Replicas: 0, Protocol: "HTTP", Tags: []string{"ok", ""}, Env: map[string]string{"lower_case": "val"}, Endpoint: "not-a-url", }) if err != nil { fmt.Println(err) } // Output lists all violations across Name, Replicas, Protocol, Tags, Env, and Endpoint. } ``` -------------------------------- ### Get JSONPath for Slice Index Source: https://github.com/nobl9/govy/wiki/coverage.html Generates the JSONPath string for accessing a specific index within the slice property. ```go func (r PropertyRulesForSlice[S, T, P]) getJSONPathForIndex(index int) string { return jsonpath.JoinArray(r.sliceRules.getName(), jsonpath.NewArrayIndex(index)) } ``` -------------------------------- ### Generate Validation Plan with govy.Plan Source: https://context7.com/nobl9/govy/llms.txt Use `govy.Plan` to generate a structured, machine-readable documentation of all rules within a validator. Consider using `PlanStrictMode()` to enforce descriptions on `When` predicates. ```go package main import ( "encoding/json" "fmt" "os" "github.com/nobl9/govy/pkg/govy" "github.com/nobl9/govy/pkg/rules" ) type Article struct { Title string `json:"title"` Tags []string `json:"tags"` } func main() { v := govy.New( govy.For(func(a Article) string { return a.Title }). WithName("title"). Required(). Rules(rules.StringLength(1, 100)), govy.ForSlice(func(a Article) []string { return a.Tags }). WithName("tags"). Rules(rules.SliceMaxLength[[]string](5)). RulesForEach(rules.StringNotEmpty()), ).WithName("Article") plan, err := govy.Plan(v) if err != nil { panic(err) } enc := json.NewEncoder(os.Stdout) enc.SetIndent("", " ") _ = enc.Encode(plan) // Output (abbreviated): // { // "name": "Article", // "properties": [ // { // "path": "$.tags", // "typeInfo": { "name": "[]string", "kind": "[]string" }, // "rules": [{ "description": "length must be less than or equal to 5", "errorCode": "slice_max_length" }] // }, // { // "path": "$.tags[* у" у", // "typeInfo": { "name": "string", "kind": "string" }, // "rules": [{ "description": "string must not be empty", "errorCode": "string_not_empty" }] // }, // { // "path": "$.title", // "typeInfo": { "name": "string", "kind": "string" }, // "rules": [ // { "description": "property is required", "errorCode": "required" }, // { "description": "length must be between 1 and 100", "errorCode": "string_length" } // ] // } // ] // } _ = fmt.Sprintf("%v", plan) // suppress unused import } ``` -------------------------------- ### Create and Parse New Template Source: https://github.com/nobl9/govy/wiki/coverage.html Creates a new template instance, adds custom functions, parses the template text, and optionally adds dependency templates. It ensures that the template is correctly initialized before being returned. ```go func newTemplate(key templateKey, text string) *template.Template { tpl := template.New(key.String()) tpl = AddFunctions(tpl) tpl = template.Must(tpl.Parse(text)) if deps, ok := templateDependencies[key]; ok { addTemplatesToTemplateTree(tpl, deps...) } return tpl } ``` -------------------------------- ### RulePlan isEmpty Method Source: https://github.com/nobl9/govy/wiki/coverage.html Checks if a RulePlan is empty, meaning it has no description, details, or error code. ```go func (r RulePlan) isEmpty() bool { return r.Description == "" && r.Details == "" && r.ErrorCode == "" } ``` -------------------------------- ### Get Validator Name Source: https://github.com/nobl9/govy/wiki/coverage.html Retrieves the name of the validator, prioritizing a set name, then a name function, and finally an empty string. ```go func (v Validator[T]) getName(value T) string { switch { case v.name != "": return v.name case v.nameFunc != nil: return v.nameFunc(value) default: return "" } } ``` -------------------------------- ### Construct Validation Plan Source: https://github.com/nobl9/govy/wiki/coverage.html Builds a validation plan by appending predicates and planning for each property rule that implements the planner interface. ```go func (v Validator[T]) plan(builder planBuilder) { builder = appendPredicatesToPlanBuilder(builder, v.predicates) for _, rules := range v.props { if p, ok := rules.(planner); ok { p.plan(builder) } } } ``` -------------------------------- ### Set Conditional Validation for Validator Source: https://github.com/nobl9/govy/wiki/coverage.html Defines a predicate that is evaluated BEFORE the validator starts validating any of its rules. This allows for pre-validation checks. ```go func (v Validator[T]) When(predicate Predicate[T], opts ...WhenOption) Validator[T] { v.predicateMatcher = v.when(predicate, opts...) return v } ``` -------------------------------- ### Read File Contents Source: https://github.com/nobl9/govy/wiki/coverage.html Reads the content of a specified file. If the file contains the 'firstComment', it truncates the content to start from that comment. ```go func readFile(path string) string { fileContents, err := os.ReadFile(path) // #nosec G304 if err != nil { logFatal(err, "Failed to read file %q contents", path) } fileContentsStr := string(fileContents) if strings.Contains(fileContentsStr, firstComment) { firstCommentIdx := strings.Index(fileContentsStr, firstComment) funcIdx := strings.Index(fileContentsStr, "func AddTemplateFunctions(") fileContentsStr = fileContentsStr[:firstCommentIdx] + fileContentsStr[funcIdx:] } return fileContentsStr } ``` -------------------------------- ### Build Rule Plan Source: https://github.com/nobl9/govy/wiki/coverage.html The plan method constructs a RulePlan for a given rule, applying any associated plan modifiers. It then appends the built plan to the builder's path. ```go func (r Rule[T]) plan(builder planBuilder) { rulePlan := RulePlan{ ErrorCode: r.errorCode, Details: r.details, Description: r.description, Conditions: builder.rulePlan.Conditions, Examples: r.examples, } for _, mod := range r.planModifiers { rulePlan = mod(rulePlan) } builder.rulePlan = rulePlan *builder.path = append(*builder.path, builder) } ``` -------------------------------- ### Go Packages Configuration Mode Source: https://github.com/nobl9/govy/wiki/coverage.html Defines the configuration mode for loading Go packages, specifying the necessary information to be retrieved. ```go const packagesMode = packages.NeedName | ``` -------------------------------- ### Get Infer Name Function Closure Source: https://github.com/nobl9/govy/wiki/coverage.html A closure that returns an internalInferNameFunc, capturing and caching the inferred name. It is safe for concurrent use. ```go // getInferNameFunc is a closure which returns an [internalInferNameFunc]. // It captures the inferred name once and caches it. // It is safe to call this function concurrently. func getInferNameFunc(callers int, pc []uintptr) internalInferNameFunc { var ( once sync.Once name string ) return func(mode InferNameMode) string { once.Do(func() { if callers < 1 { return } frame, _ := runtime.CallersFrames(pc).Next() if frame.File == "" || frame.Line == 0 { logging.Logger().Error( ``` -------------------------------- ### String Title Case Rule Source: https://github.com/nobl9/govy/wiki/coverage.html Validates if each word in a string starts with a capital letter. Handles empty strings and checks for separators. ```go // StringTitle ensures each word in a string starts with a capital letter. func StringTitle() govy.Rule[string] { tpl := messagetemplates.Get(messagetemplates.StringTitleTemplate) return govy.NewRule(func(s string) error { if len(s) == 0 { return govy.NewRuleErrorTemplate(govy.TemplateVars{ PropertyValue: s, }) } prev := ' ' for _, r := range s { if isStringSeparator(prev) { if !unicode.IsUpper(r) && !isStringSeparator(r) { return govy.NewRuleErrorTemplate(govy.TemplateVars{ PropertyValue: s, }) } } prev = r } return nil }). WithErrorCode(ErrorCodeStringTitle). WithMessageTemplate(tpl). ``` -------------------------------- ### Get Property Name Source: https://github.com/nobl9/govy/wiki/coverage.html Retrieves the name of a property, prioritizing a directly set name, then a name generated by a function, and defaulting to an empty string. ```go func (r PropertyRules[T, S]) getName() string { switch { case r.name != "": return r.name case r.nameFunc != nil: return r.nameFunc(r.inferNameMode) default: return "" } } ``` -------------------------------- ### Run govy inferpath Command Source: https://github.com/nobl9/govy/blob/main/cmd/govy/README.md Generate a file with inferred paths by running the govy inferpath command. Specify the output directory, package name, and filename for the generated file. ```shell govy inferpath -dir -pkg -filename ``` -------------------------------- ### Parse Go Module AST Source: https://github.com/nobl9/govy/wiki/coverage.html Loads and parses the Go module's Abstract Syntax Tree (AST) using the 'go/packages' API. It initializes a FileSet and configures package loading options, including tests if configured. This function is intended for internal use to build a representation of the module's structure. ```go func NewModuleAST(root string) ModuleAST { fileSet := token.NewFileSet() cfg := &packages.Config{ Fset: fileSet, Mode: packagesMode, Dir: root, Tests: govyconfig.GetInferNameIncludeTestFiles(), } pkgs, err := packages.Load(cfg, "./...") if err != nil { log.Fatal(err) } pkgMap := make(map[string]*packages.Package, len(pkgs)) for _, pkg := range pkgs { pkgMap[pkg.PkgPath] = pkg } return ModuleAST{ FileSet: fileSet, Packages: pkgMap, } } type ModuleAST struct { FileSet *token.FileSet Packages map[string]*packages.Package } func (a ModuleAST) FindFile(file string) (*packages.Package, *ast.File) { for _, pkg := range a.Packages { for i, filePath := range pkg.GoFiles { if filePath == file { return pkg, pkg.Syntax[i] } } } return nil, nil } var ( modAST ModuleAST parseASTOnce sync.Once ) func parseModuleASTOnce() { parseASTOnce.Do(func() { modAST = NewModuleAST(internal.FindModuleRoot()) }) } ``` -------------------------------- ### Get Test File Inclusion Setting for Name Inference Source: https://github.com/nobl9/govy/wiki/coverage.html Returns the current setting for whether test files are included in the name inference mechanism. ```go func GetInferNameIncludeTestFiles() bool { mu.RLock() defer mu.RUnlock() return includeTestFiles } ```