{"id":26397050,"url":"https://github.com/nobl9/govy","last_synced_at":"2026-05-18T10:02:14.943Z","repository":{"id":255213523,"uuid":"837070424","full_name":"nobl9/govy","owner":"nobl9","description":"Go validation library with functional API built on top of generics, statically typed and batteries included :fire:","archived":false,"fork":false,"pushed_at":"2026-05-07T01:38:19.000Z","size":2632,"stargazers_count":43,"open_issues_count":9,"forks_count":0,"subscribers_count":7,"default_branch":"main","last_synced_at":"2026-05-07T03:32:26.452Z","etag":null,"topics":["error-handling","generics","go","type-safe","validation"],"latest_commit_sha":null,"homepage":"https://pkg.go.dev/github.com/nobl9/govy","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mpl-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/nobl9.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"docs/CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":".github/CODEOWNERS","security":null,"support":null,"governance":null,"roadmap":"docs/ROADMAP.md","authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2024-08-02T06:38:33.000Z","updated_at":"2026-05-07T01:35:42.000Z","dependencies_parsed_at":"2024-08-28T17:14:23.595Z","dependency_job_id":"d4abcc93-e6e1-4066-9701-f4e7a6d760d0","html_url":"https://github.com/nobl9/govy","commit_stats":null,"previous_names":["nobl9/govy"],"tags_count":32,"template":false,"template_full_name":null,"purl":"pkg:github/nobl9/govy","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nobl9%2Fgovy","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nobl9%2Fgovy/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nobl9%2Fgovy/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nobl9%2Fgovy/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/nobl9","download_url":"https://codeload.github.com/nobl9/govy/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nobl9%2Fgovy/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33174091,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-18T09:27:30.708Z","status":"ssl_error","status_checked_at":"2026-05-18T09:27:28.300Z","response_time":71,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["error-handling","generics","go","type-safe","validation"],"created_at":"2025-03-17T12:01:33.751Z","updated_at":"2026-05-18T10:02:14.922Z","avatar_url":"https://github.com/nobl9.png","language":"Go","funding_links":[],"categories":["Validation","Go"],"sub_categories":["Utility/Miscellaneous"],"readme":"# govy\n\n[![Go Reference](https://pkg.go.dev/badge/github.com/nobl9/govy.svg)](https://pkg.go.dev/github.com/nobl9/govy)\n[![Go Report Card](https://goreportcard.com/badge/github.com/nobl9/govy)](https://goreportcard.com/report/github.com/nobl9/govy)\n[![Go Coverage](https://github.com/nobl9/govy/wiki/coverage.svg)](https://raw.githack.com/wiki/nobl9/govy/coverage.html)\n\nValidation library for Go that uses a functional interface for building\nstrongly-typed validation rules, powered by generics and\n[reflection free](#reflection).\nIt puts heavy focus on end user errors readability,\nproviding means of crafting clear and information-rich error messages.\nIt also allows writing self-documenting validation rules through a\n[validation plan](#validation-plan).\n\n**GO** **V**alidate **Y**ourself!\n\n**DISCLAIMER**: govy is in active development, while the core API is unlikely\nto change, breaking changes may be introduced with new versions until v1\nis released. Checkout [roadmap](./docs/ROADMAP.md) for upcoming,\nplanned features.\n\n## Legend\n\n1. [Getting started](#getting-started)\n    1. [Use cases](#use-cases)\n    2. [Comparison with other libraries](#comparison-with-other-libraries)\n2. [Building blocks](#building-blocks)\n    1. [Errors](#errors)\n3. [Features](#features)\n    1. [Type safety](#type-safety)\n    2. [Immutability](#immutability)\n    3. [Verbose error messages](#verbose-error-messages)\n    4. [Error message templates](#error-message-templates)\n    5. [Error message templates in custom rules](#error-message-templates-in-custom-rules)\n    6. [Predefined rules](#predefined-rules)\n    7. [Custom rules](#custom-rules)\n    8. [Validation plan](#validation-plan)\n    9. [Properties name inference](#properties-name-inference)\n    10. [Testing helpers](#testing-helpers)\n4. [Rationale](#rationale)\n    1. [Reflection](#reflection)\n    2. [Trivia](#trivia)\n5. [Development](#development)\n    1. [Tests coverage](#tests-coverage)\n    2. [Benchmarks](#benchmarks)\n6. [Acknowledgments](#acknowledgments)\n\n## Getting started\n\nIn order to add the library to your project, run:\n\n```shell\ngo get github.com/nobl9/govy\n```\n\nThere's an interactive tutorial available,\npowered by Go's [testable examples](https://go.dev/blog/examples),\nto access it visit [pkg.go.dev](https://pkg.go.dev/github.com/nobl9/govy)\nor locally at [example_test.go](./pkg/govy/example_test.go).\n\nGovy's code documentation is available at\n[pkg.go.dev](https://pkg.go.dev/github.com/nobl9/govy).\n\nYou can read\n[this blog post](https://www.nobl9.com/resources/type-safe-validation-in-go-with-govy)\nfor a quick overview of the library, its capabilities,\nwhat distinguishes it from other solutions and why was it conceived.\n\nHere's a quick example of `govy` in action:\n\n[//]: # (embed: internal/examples/readme_intro_example_test.go)\n\n```go\npackage examples\n\nimport (\n\t\"fmt\"\n\t\"regexp\"\n\t\"time\"\n\n\t\"github.com/nobl9/govy/pkg/govy\"\n\t\"github.com/nobl9/govy/pkg/rules\"\n)\n\nfunc Example_basicUsage() {\n\ttype University struct {\n\t\tName    string `json:\"name\"`\n\t\tAddress string `json:\"address\"`\n\t}\n\ttype Student struct {\n\t\tIndex string `json:\"index\"`\n\t}\n\ttype Teacher struct {\n\t\tName       string        `json:\"name\"`\n\t\tAge        time.Duration `json:\"age\"`\n\t\tStudents   []Student     `json:\"students\"`\n\t\tMiddleName *string       `json:\"middleName,omitempty\"`\n\t\tUniversity University    `json:\"university\"`\n\t}\n\n\tuniversityValidation := govy.New(\n\t\tgovy.For(func(u University) string { return u.Name }).\n\t\t\tWithName(\"name\").\n\t\t\tRequired(),\n\t\tgovy.For(func(u University) string { return u.Address }).\n\t\t\tWithName(\"address\").\n\t\t\tRequired().\n\t\t\tRules(rules.StringMatchRegexp(\n\t\t\t\tregexp.MustCompile(`[\\w\\s.]+, [0-9]{2}-[0-9]{3} \\w+`),\n\t\t\t).\n\t\t\t\tWithDetails(\"Polish address format must consist of the main address and zip code\").\n\t\t\t\tWithExamples(\"5 M. Skłodowska-Curie Square, 60-965 Poznan\")),\n\t)\n\tstudentValidator := govy.New(\n\t\tgovy.For(func(s Student) string { return s.Index }).\n\t\t\tWithName(\"index\").\n\t\t\tRules(rules.StringLength(9, 9)),\n\t)\n\tteacherValidator := govy.New(\n\t\tgovy.For(func(t Teacher) string { return t.Name }).\n\t\t\tWithName(\"name\").\n\t\t\tRequired().\n\t\t\tRules(\n\t\t\t\trules.StringNotEmpty(),\n\t\t\t\trules.OneOf(\"Jake\", \"George\")),\n\t\tgovy.ForPointer(func(t Teacher) *string { return t.MiddleName }).\n\t\t\tWithName(\"middleName\").\n\t\t\tRules(rules.StringTitle()),\n\t\tgovy.ForSlice(func(t Teacher) []Student { return t.Students }).\n\t\t\tWithName(\"students\").\n\t\t\tRules(\n\t\t\t\trules.SliceMaxLength[[]Student](2),\n\t\t\t\trules.SliceUnique(func(v Student) string { return v.Index })).\n\t\t\tIncludeForEach(studentValidator),\n\t\tgovy.For(func(t Teacher) University { return t.University }).\n\t\t\tWithName(\"university\").\n\t\t\tInclude(universityValidation),\n\t).\n\t\tWhen(func(t Teacher) bool { return t.Age \u003c 50 })\n\n\tteacher := Teacher{\n\t\tName:       \"John\",\n\t\tMiddleName: nil, // Validation for nil pointers by default is skipped.\n\t\tAge:        48,\n\t\tStudents: []Student{\n\t\t\t{Index: \"918230014\"},\n\t\t\t{Index: \"9182300123\"},\n\t\t\t{Index: \"918230014\"},\n\t\t},\n\t\tUniversity: University{\n\t\t\tName:    \"\",\n\t\t\tAddress: \"10th University St.\",\n\t\t},\n\t}\n\n\tif err := teacherValidator.WithName(\"John\").Validate(teacher); err != nil {\n\t\tfmt.Println(err)\n\t}\n\t// When condition is not met, no validation errors.\n\tjohnFromTheFuture := teacher\n\tjohnFromTheFuture.Age = 51\n\tif err := teacherValidator.WithName(\"John From The Future\").Validate(johnFromTheFuture); err != nil {\n\t\tfmt.Println(err)\n\t}\n\n\t// Output:\n\t// Validation for John has failed for the following properties:\n\t//   - 'name' with value 'John':\n\t//     - must be one of: Jake, George\n\t//   - 'students' with value '[{\"index\":\"918230014\"},{\"index\":\"9182300123\"},{\"index\":\"918230014\"}]':\n\t//     - length must be less than or equal to 2\n\t//     - elements are not unique, 1st and 3rd elements collide\n\t//   - 'students[1].index' with value '9182300123':\n\t//     - length must be between 9 and 9\n\t//   - 'university.name':\n\t//     - property is required but was empty\n\t//   - 'university.address' with value '10th University St.':\n\t//     - string must match regular expression: '[\\w\\s.]+, [0-9]{2}-[0-9]{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\n}\n```\n\n### Use cases\n\n1. [Nobl9 Go SDK](https://github.com/nobl9/nobl9-go). \\\n   This is where `govy` was born,\n   it's used for validating complex k8s-like schema, it contains both simple\n   and very advanced validation rules and is a great place to draw some\n   inspiration from.\n2. [OpenSLO](https://github.com/OpenSLO/OpenSLO). \\\n   It's used for validating open specification for defining SLOs.\n   The specification is a complex, YAML-based and k8s compatible schema,\n   similar to Nobl9's configuration.\n\n### Comparison with other libraries\n\n1. [go-playground/validator](https://github.com/go-playground/validator). \\\n   Visit [runnable example](./docs/validator-comparison/example_test.go) for a\n   comprehensive, live code comparison between `govy` and\n   `go-playground/validator`.\n   `validator` was the predecessor which `govy` dethroned at Nobl9.\n   For more trivia and details on the differences between the two,\n   check out the [rationale](#rationale) section.\n\n## Building blocks\n\nGovy validation flow consists of the following building blocks:\n\n![govy-building-blocks](./docs/assets/excalidraw/govy-building-blocks.svg \"govy building blocks\")\n\n1. `Validator` is the top-level entity which usually\n   aggregates `PropertyRules` for a single struct.\n2. `PropertyRules` is a representation of a single property's validation rules.\n   It usually represents a single struct field.\n   It comes with two extra variants specifically designed for slices and maps.\n   These allow defining rules for each element, key or value of the property.\n3. `Rule` defines a single validation rule.\n   Multiple rules can be combined to form a more complex\n   validation rule using RuleSet.\n\n### Errors\n\nGovy errors are structured (as in each is a struct) and reflect the\naforementioned building blocks hierarchy:\n\n![govy-errors](./docs/assets/excalidraw/govy-errors.svg \"govy errors\")\n\nThe exception being `PropertyErrors` which is an additional container for\ngrouping `PropertyError` without the context of a specific `Validator`.\n\nGovy functions return `error` interface.\nIn order to access the underlying structured error, you need to type cast it.\nThe reason for that is the interface type implementation in Go.\nIf, hypothetically, `Validator` would return `*ValidatorError` directly,\nand given the following code in this\n[GitHub gist](https://gist.github.com/nieomylnieja/268841b12571e8bb3a01780e8e6663c5),\nthe nil assertions on produced `error` would fail.\nMore details available in the\n[laws of reflection blog post](https://go.dev/blog/laws-of-reflection#TOC_3.).\n\n## Features\n\n### Type safety\n\nGovy is built on top of [Go's generics](https://go.dev/doc/tutorial/generics).\nThanks to that it is able to provide a robust and extensible API which is still\ntype-safe.\n\n### Immutability\n\nGovy components are largely immutable and lazily loaded:\n\n- Immutable, as changing the pipeline through chained functions,\n  will return a new pipeline.\n  It allows extended reusability of validation components.\n- Lazily loaded, as properties are extracted through getter functions,\n  which are only called when you call the `Validate` method.\n  Functional approach allows validation components to only be called when\n  needed.\n  You should define your pipeline once and call it\n  whenever you validate instances of your entity.\n\n### Verbose error messages\n\nDefault `govy` error messages are verbose and provide a clear indication both\nof the error cause and the property context in which they occurred.\n\nThe property paths are evaluated relative to the root `Validator` and follow\n[JSON path](https://datatracker.ietf.org/doc/html/rfc9535) standard.\n\n```text\nValidation for Teacher has failed for the following properties:\n  - 'name' with value 'John':\n    - must be one of [Jake, George]\n  - 'students' with value '[{\"index\":\"918230014\"},{\"index\":\"9182300123\"},{\"index\":\"918230014\"}]':\n    - length must be less than or equal to 2\n    - elements are not unique, index 0 collides with index 2\n  - 'students[1].index' with value '9182300123':\n    - length must be between 9 and 9\n  - 'university.address':\n    - property is required but was empty\n```\n\nThe errors themselves are structured and can be parsed programmatically\nallowing custom error handling.\nThey come with exported fields, JSON tags and can be easily serialized and\ndeserialized.\n\n#### Error message templates\n\nIf you want a more fine-grained control over the error messages,\nyou can define custom error message templates for each builtin rule.\n\nThe templates are powered by [Go's native templating system](https://pkg.go.dev/text/template).\nEach builtin validation rule has specific variables available\nand there are also builtin functions shipped which help construct\nthe message templates (like _formatExamples_ in the example below).\n\n[//]: # (embed: internal/examples/readme_message_templates_example_test.go)\n\n```go\npackage examples\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/nobl9/govy/pkg/govy\"\n\t\"github.com/nobl9/govy/pkg/rules\"\n)\n\nfunc Example_messageTemplates() {\n\ttype Teacher struct {\n\t\tName string `json:\"name\"`\n\t}\n\n\ttemplateString := \"name length should be between {{ .MinLength }} and {{ .MaxLength }} {{ formatExamples .Examples }}\"\n\n\tv := govy.New(\n\t\tgovy.For(func(t Teacher) string { return t.Name }).\n\t\t\tWithName(\"name\").\n\t\t\tRules(\n\t\t\t\trules.StringLength(5, 10).\n\t\t\t\t\tWithExamples(\"Joanna\", \"Jerry\").\n\t\t\t\t\tWithMessageTemplateString(templateString),\n\t\t\t),\n\t).WithName(\"Teacher\")\n\n\tteacher := Teacher{Name: \"Tom\"}\n\terr := v.Validate(teacher)\n\tif err != nil {\n\t\tfmt.Println(err)\n\t}\n\n\t// Output:\n\t// Validation for Teacher has failed for the following properties:\n\t//   - 'name' with value 'Tom':\n\t//     - name length should be between 5 and 10 (e.g. 'Joanna', 'Jerry')\n}\n```\n\n#### Error message templates in custom rules\n\nIf you wish to support templating in your custom rules, you need to make sure\nyour rules ALWAYS return `govy.RuleErrorTemplate` and supply the template\nwith either `govy.Rule.WithMessageTemplateString` or `govy.Rule.WithMessageTemplate`.\n\n[//]: # (embed: internal/examples/readme_adding_message_templates_support_example_test.go)\n\n```go\npackage examples\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/nobl9/govy/pkg/govy\"\n\t\"github.com/nobl9/govy/pkg/rules\"\n)\n\nfunc Example_addingMessageTemplatesSupportToCustomRules() {\n\ttype Teacher struct {\n\t\tName string `json:\"name\"`\n\t}\n\n\ttemplate := `{{ .PropertyValue }} must be {{ .ComparisonValue }}; {{ .Custom.Foo }} and {{ .Custom.Baz }}`\n\n\tcustomRule := govy.NewRule(func(name string) error {\n\t\tif name != \"John\" {\n\t\t\treturn govy.NewRuleErrorTemplate(govy.TemplateVars{\n\t\t\t\tPropertyValue:   name,\n\t\t\t\tComparisonValue: \"John\",\n\t\t\t\tCustom: map[string]any{\n\t\t\t\t\t\"Foo\": \"Bar\",\n\t\t\t\t\t\"Baz\": 42,\n\t\t\t\t},\n\t\t\t})\n\t\t}\n\t\treturn nil\n\t}).\n\t\tWithErrorCode(\"custom_rule\").\n\t\tWithMessageTemplateString(template).\n\t\tWithDetails(\"we just don't like anyone but Johns...\").\n\t\tWithDescription(\"must be John\")\n\n\tteacherValidator := govy.New(\n\t\tgovy.For(func(t Teacher) string { return t.Name }).\n\t\t\tWithName(\"name\").\n\t\t\tRequired().\n\t\t\tRules(\n\t\t\t\tcustomRule,\n\t\t\t\trules.StringStartsWith(\"J\"),\n\t\t\t),\n\t).InferName()\n\n\tteacher := Teacher{Name: \"George\"}\n\n\tif err := teacherValidator.Validate(teacher); err != nil {\n\t\tfmt.Println(err)\n\t}\n\n\t// Output:\n\t// Validation for Teacher has failed for the following properties:\n\t//   - 'name' with value 'George':\n\t//     - George must be John; Bar and 42\n\t//     - string must start with 'J' prefix\n}\n```\n\n### Predefined rules\n\nGovy comes with a set of predefined rules defined\nin the [rules](./pkg/rules) package which covers most of the common use cases.\n\n#### Custom rules\n\nIf the predefined rules are not enough, you can easily define your own rules:\n\n[//]: # (embed: internal/examples/readme_custom_rule_example_test.go)\n\n```go\npackage examples\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/nobl9/govy/pkg/govy\"\n\t\"github.com/nobl9/govy/pkg/rules\"\n)\n\nfunc Example_customRules() {\n\ttype Teacher struct {\n\t\tName string `json:\"name\"`\n\t}\n\n\tcustomRule := govy.NewRule(func(name string) error {\n\t\tif name != \"John\" {\n\t\t\treturn fmt.Errorf(\"must be John\")\n\t\t}\n\t\treturn nil\n\t}).\n\t\tWithErrorCode(\"custom_rule\").\n\t\tWithDetails(\"we just don't like anyone but Johns...\").\n\t\tWithDescription(\"must be John\")\n\n\tteacherValidator := govy.New(\n\t\tgovy.For(func(t Teacher) string { return t.Name }).\n\t\t\tWithName(\"name\").\n\t\t\tRequired().\n\t\t\tRules(\n\t\t\t\tcustomRule,\n\t\t\t\trules.StringStartsWith(\"J\")),\n\t).InferName()\n\n\tteacher := Teacher{Name: \"George\"}\n\n\tif err := teacherValidator.Validate(teacher); err != nil {\n\t\tfmt.Println(err)\n\t}\n\n\t// Output:\n\t// Validation for Teacher has failed for the following properties:\n\t//   - 'name' with value 'George':\n\t//     - must be John; we just don't like anyone but Johns...\n\t//     - string must start with 'J' prefix\n}\n```\n\n### Validation plan\n\n_DISCLAIMER_: This feature is experimental and is subject to change.\n\nValidation plan provides a way to self-document your validation rules.\nIt helps keep your documentation and validation rules in sync.\nIt produces a structured output which can be handled programmatically\nor directly encoded to JSON.\n\n[//]: # (embed: internal/examples/readme_validation_plan_example_test.go)\n\n```go\npackage examples\n\nimport (\n\t\"encoding/json\"\n\t\"os\"\n\t\"regexp\"\n\t\"time\"\n\n\t\"github.com/nobl9/govy/pkg/govy\"\n\t\"github.com/nobl9/govy/pkg/rules\"\n)\n\nfunc Example_validationPlan() {\n\ttype University struct {\n\t\tName    string `json:\"name\"`\n\t\tAddress string `json:\"address\"`\n\t}\n\ttype Student struct {\n\t\tIndex string `json:\"index\"`\n\t}\n\ttype Teacher struct {\n\t\tName       string        `json:\"name\"`\n\t\tAge        time.Duration `json:\"age\"`\n\t\tStudents   []Student     `json:\"students\"`\n\t\tMiddleName *string       `json:\"middleName,omitempty\"`\n\t\tUniversity University    `json:\"university\"`\n\t}\n\n\tuniversityValidation := govy.New(\n\t\tgovy.For(func(u University) string { return u.Name }).\n\t\t\tWithName(\"name\").\n\t\t\tRequired(),\n\t\tgovy.For(func(u University) string { return u.Address }).\n\t\t\tWithName(\"address\").\n\t\t\tRequired().\n\t\t\tRules(rules.StringMatchRegexp(\n\t\t\t\tregexp.MustCompile(`[\\w\\s.]+, [0-9]{2}-[0-9]{3} \\w+`),\n\t\t\t).\n\t\t\t\tWithDetails(\"Polish address format must consist of the main address and zip code\").\n\t\t\t\tWithExamples(\"5 M. Skłodowska-Curie Square, 60-965 Poznan\")).\n\t\t\tWhen(func(u University) bool { return u.Name == \"PUT\" },\n\t\t\t\tgovy.WhenDescription(\"University name is PUT University\")),\n\t)\n\tstudentValidator := govy.New(\n\t\tgovy.For(func(s Student) string { return s.Index }).\n\t\t\tWithName(\"index\").\n\t\t\tRules(rules.StringLength(9, 9)),\n\t)\n\tteacherValidator := govy.New(\n\t\tgovy.For(func(t Teacher) string { return t.Name }).\n\t\t\tWithName(\"name\").\n\t\t\tRequired().\n\t\t\tRules(\n\t\t\t\trules.StringNotEmpty(),\n\t\t\t\trules.OneOf(\"Jake\", \"George\")),\n\t\tgovy.ForPointer(func(t Teacher) *string { return t.MiddleName }).\n\t\t\tWithName(\"middleName\").\n\t\t\tRules(rules.StringTitle()),\n\t\tgovy.ForSlice(func(t Teacher) []Student { return t.Students }).\n\t\t\tWithName(\"students\").\n\t\t\tRules(\n\t\t\t\trules.SliceMaxLength[[]Student](2),\n\t\t\t\trules.SliceUnique(func(v Student) string { return v.Index })).\n\t\t\tIncludeForEach(studentValidator),\n\t\tgovy.For(func(t Teacher) University { return t.University }).\n\t\t\tWithName(\"university\").\n\t\t\tInclude(universityValidation).\n\t\t\tWhen(func(t Teacher) bool { return t.Name == \"John\" },\n\t\t\t\tgovy.WhenDescription(\"Teacher name is John\")),\n\t).\n\t\tWithName(\"Teacher\")\n\n\tplan := govy.Plan(teacherValidator)\n\tenc := json.NewEncoder(os.Stdout)\n\tenc.SetIndent(\"\", \"  \")\n\t_ = enc.Encode(plan)\n\n\t// Output:\n\t// {\n\t//   \"name\": \"Teacher\",\n\t//   \"properties\": [\n\t//     {\n\t//       \"path\": \"$.middleName\",\n\t//       \"type\": \"string\",\n\t//       \"isOptional\": true,\n\t//       \"rules\": [\n\t//         {\n\t//           \"description\": \"each word in a string must start with a capital letter\",\n\t//           \"errorCode\": \"string_title\"\n\t//         }\n\t//       ]\n\t//     },\n\t//     {\n\t//       \"path\": \"$.name\",\n\t//       \"type\": \"string\",\n\t//       \"rules\": [\n\t//         {\n\t//           \"description\": \"string cannot be empty\",\n\t//           \"errorCode\": \"string_not_empty\"\n\t//         },\n\t//         {\n\t//           \"description\": \"must be one of: Jake, George\",\n\t//           \"errorCode\": \"one_of\"\n\t//         }\n\t//       ]\n\t//     },\n\t//     {\n\t//       \"path\": \"$.students\",\n\t//       \"type\": \"[]Student\",\n\t//       \"package\": \"github.com/nobl9/govy/internal/examples\",\n\t//       \"rules\": [\n\t//         {\n\t//           \"description\": \"length must be less than or equal to 2\",\n\t//           \"errorCode\": \"slice_max_length\"\n\t//         },\n\t//         {\n\t//           \"description\": \"elements must be unique\",\n\t//           \"errorCode\": \"slice_unique\"\n\t//         }\n\t//       ]\n\t//     },\n\t//     {\n\t//       \"path\": \"$.students[*].index\",\n\t//       \"type\": \"string\",\n\t//       \"rules\": [\n\t//         {\n\t//           \"description\": \"length must be between 9 and 9\",\n\t//           \"errorCode\": \"string_length\"\n\t//         }\n\t//       ]\n\t//     },\n\t//     {\n\t//       \"path\": \"$.university.address\",\n\t//       \"type\": \"string\",\n\t//       \"rules\": [\n\t//         {\n\t//           \"description\": \"string must match regular expression: '[\\\\w\\\\s.]+, [0-9]{2}-[0-9]{3} \\\\w+'\",\n\t//           \"details\": \"Polish address format must consist of the main address and zip code\",\n\t//           \"errorCode\": \"string_match_regexp\",\n\t//           \"conditions\": [\n\t//             \"Teacher name is John\",\n\t//             \"University name is PUT University\"\n\t//           ],\n\t//           \"examples\": [\n\t//             \"5 M. Skłodowska-Curie Square, 60-965 Poznan\"\n\t//           ]\n\t//         }\n\t//       ]\n\t//     },\n\t//     {\n\t//       \"path\": \"$.university.name\",\n\t//       \"type\": \"string\",\n\t//       \"rules\": [\n\t//         {\n\t//           \"description\": \"\",\n\t//           \"conditions\": [\n\t//             \"Teacher name is John\"\n\t//           ]\n\t//         }\n\t//       ]\n\t//     }\n\t//   ]\n\t// }\n}\n```\n\n### Properties name inference\n\n_DISCLAIMER_: This feature is experimental and is subject to change.\n\nGovy provides a way to automatically infer property names from the code itself.\nThis way, there's no need to manually provide properties' names with\n`WithName` function.\n\nCheckout [example_test.go](./pkg/govyconfig/example_test.go) for an interactive\nintroduction to this feature.\n\nDocumentation for the name inference code generator is available\n[here](cmd/govy/README.md#nameinfer).\n\n[//]: # (embed: internal/examples/readme_name_inference_example_test.go)\n\n```go\npackage examples\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/nobl9/govy/pkg/govy\"\n\t\"github.com/nobl9/govy/pkg/govyconfig\"\n\t\"github.com/nobl9/govy/pkg/rules\"\n)\n\nfunc Example_nameInference() {\n\tgovyconfig.SetNameInferIncludeTestFiles(true) // Required for the example to run.\n\tgovyconfig.SetNameInferMode(govyconfig.NameInferModeRuntime)\n\tdefer govyconfig.SetNameInferIncludeTestFiles(false)\n\tdefer govyconfig.SetNameInferMode(govyconfig.NameInferModeDisable)\n\n\ttype Teacher struct {\n\t\tName string `json:\"name\"`\n\t}\n\n\tv := govy.New(\n\t\tgovy.For(func(t Teacher) string { return t.Name }).\n\t\t\tRules(rules.EQ(\"Jerry\")),\n\t).InferName()\n\n\tteacher := Teacher{Name: \"Tom\"}\n\terr := v.Validate(teacher)\n\tif err != nil {\n\t\tfmt.Println(err)\n\t}\n\n\t// Output:\n\t// Validation for Teacher has failed for the following properties:\n\t//   - 'name' with value 'Tom':\n\t//     - should be equal to 'Jerry'\n}\n```\n\n### Testing helpers\n\nPackage [govytest](./pkg/govytest/) provides utilities which aid the process of\nwriting unit tests for validation rules defined with govy.\nCheckout [testable examples](https://pkg.go.dev/github.com/nobl9/govy/pkg/govytest#pkg-examples)\nfor a concise overview of the package's capabilities.\n\n## Rationale\n\nWhy was this library created?\n\nMost of the established Go libraries for validation were created\nin a pre-generics era. They often use reflection in order to provide\ngeneric validation API, it also allows them to utilize struct tags, which\nfurther minimize the amount of code users need to write.\n\nUnfortunately, the ease of use compromises Go's core language feature,\ntype safety and increases the complexity of the code.\n\nEnter, generics.\nWith generics on board, it's finally possible to write a robust and type safe\nvalidation API, thus `govy` was born.\n\n### Reflection\n\nIs `govy` truly reflection free?\n\nThe short answer is yes, the long answer is `govy` does not utilize\nreflection other than to serve better error messages or devise a\n[validation plan](#validation-plan).\nSome builtin rules might also use `reflect`, but the core functionality\ndoes not rely on it.\n\n### Trivia\n\nThe library was first conceived at\n[nobl9-go](https://github.com/nobl9/nobl9-go),\nwhich is [Nobl9's](https://www.nobl9.com/) Go SDK.\nIt was born out of a need for a better validation mechanism,\nwhich would also allow us to auto-document validation rules.\nAt the time, we were using _go-playground/validator_,\nwhile it's a great, matured library,\nit is quiet \"magical\" as it operates entirely on reflection.\nIt's default errors are also not very informative.\nFurthermore, our validation rules were quiet complex and figuring out which rules\nwere associated with given property was tedious to say the least.\nAround the same time, Go 1.18 was released with generics support, we started playing\nwith them, and the idea for `govy` was born.\n\n## Development\n\nCheckout both [contributing guidelines](./docs/CONTRIBUTING.md) and\n[development instructions](./docs/DEVELOPMENT.md).\n\n### Tests coverage\n\nTests coverage HTML for current `main` branch state can be inspected\n[here](https://raw.githack.com/wiki/nobl9/govy/coverage.html).\n\nNote that `cmd` package is tested by building and running Go binaries directly.\nThis means there won't be any coverage for some of the core functions there.\n\n### Benchmarks\n\nBenchmarks' history is collected and can be viewed as charts over time\n[here](https://nobl9.github.io/govy/dev/bench/).\n\n## Acknowledgments\n\nThe API along with the accompanying nomenclature was heavily inspired by the\nawesome [Fluent Validation](https://github.com/FluentValidation/FluentValidation)\nlibrary for C#.\n\nSpecial thanks to [go-playground/validator](https://github.com/go-playground/validator)\nfor paving the way for Go validation libraries,\nmany predefined rules have been ported from it.\n\n---\n\nHandcrafted with ❤️ at [Nobl9](https://www.nobl9.com/).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnobl9%2Fgovy","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fnobl9%2Fgovy","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnobl9%2Fgovy/lists"}