See Validations
gencheck was built using the idea of zencoder/gokay, but uses templates to create validations for a struct.
gencheck will use the valid
tag within a struct to generate a Validate()
method, which is will store in a file_validators.go
file
next to the input file.
gencheck's Validate()
method will return a ValidationErrors
type, which is an array of FieldError
s.
Given the struct:
type MyStruct struct{
MyField string `valid:"required"`
}
A Validate method is generated:
func (s MyStruct) Validate() error {
var vErrors gencheck.ValidationErrors
// BEGIN MyField Validations
// required
if s.MyField == "" {
vErrors = append(vErrors, gencheck.NewFieldError("MyStruct", "MyField", "required", errors.New("is required")))
}
// END MyField Validations
if len(vErrors) > 0 {
return vErrors
}
return nil
}
First use go get
to install the latest version of the library.
go get -v github.com/abice/gencheck/gencheck
Normally the above command will build and install the binary, but just to be sure it is installed in your GOPATH
for use on the command line, or build args:
go install github.com/abice/gencheck/gencheck
gencheck -f=file.go -t="SomeTemplate.tmpl" --template="SomeOtherTemplate.tmpl" -d="some/dir" --template-dir="some/dir/that/has/templates"
Add a //go:generate
tag to the top of your file that you want to generate for, including the file name.
//go:generate gencheck -f=this_file.go
Add validations to valid
tag in struct def:
type ExampleStruct struct {
HexStringPtr *string `valid:"len=16,notnil,hex"`
HexString string `valid:"len=12,hex"`
CanBeNilWithConstraints *string `valid:"len=12"`
}
Validation tags are comma separated, with any validation parameter specified after an equal sign.
valid:"ValidationName1,ValidationName2=vn2param"
In the above example, the hex
and notnil
Validations are parameterless, whereas len requires 1 parameter.
Since the addition of gt(e)
and lt(e)
, there are now comparisons for time.Time
values. If no arguments are specified to those, then it calculates whether the
field time is After and Before time.Now().UTC()
respectively. You can specify a parameter for those validations if you choose. The parameter will be interpreted as
the offset to use with respect to time.Now().UTC()
by utilizing the Add()
function.
requestTime time.Time `valid:"gte=-1*time.Second"`
tGteTimeVal := time.Now().UTC().Add(-1 * time.Second)
if s.GteTimeVal.Before(tGteTimeVal) {
vErrors = append(vErrors, gencheck.NewFieldError("Test", "GteTimeVal", "gte", fmt.Errorf("is before %s", tGteTimeVal)))
}
The fail fast flag is a built-in validation flag that will allow you to return immediately on an invalid check. This allows you to not waste time checking the rest of the struct if a vital field is wrong. It can be placed anywhere within the valid
tag, and will be applied to all rules within that field.
There is also a --failfast
flag on the cli that will allow you to make all validations within all structs found in the files to be fail fast.
gencheck allows developers to write and attach their own Validation templates to the generator.
-
Write a template that creates a validation for a given field making sure to define the template as the validation tag you want to use:
{{define "mycheck" -}} if err := gencheck.IsUUID({{.Param}}, {{if not (IsPtr . )}}&{{end}}s.{{.FieldName}}); err != nil { {{ AddError . "err" }} } {{end -}}
-
Import that template when running gencheck
-
Write tests for your struct's constraints
-
Add
valid
tags to your struct fields -
Run gencheck:
gencheck -f=file.go -t=MyTemplate
NOTES:
- In your template, the . pipeline is an instance of the
generator.Validation
struct. - The template functions from Sprig have been included.
- There are some custom functions provided for you to help in determining the ast field type
- isPtr
- addError
- isNullable
- isMap
- isArray
- isStruct
- isStructPtr
- isStructPtr
- generationError
- Allows you to fail code generation with a specific error message
I know benchmarks are always skewed to show what the creators want you to see, but here's a quick benchmark of the cost of using validation to check.
I've also added some comparison benchmark output from the ./internal/benchmark_test.go
to compare the different options with gencheck and how it holds up to the go playground validator.
BenchmarkReflectionInt-8 20000000 104 ns/op
BenchmarkEmptyInt-8 2000000000 0.29 ns/op
BenchmarkReflectionStruct-8 5000000 262 ns/op
BenchmarkEmptyStruct-8 50000000 28.3 ns/op
BenchmarkReflectionString-8 10000000 159 ns/op
BenchmarkEmptyString-8 200000000 9.49 ns/op
Benchmarks using fail fast flag
BenchmarkValidString-8 300000000 5.02 ns/op
BenchmarkFailing1TestString-8 10000000 158 ns/op
BenchmarkFailing2TestString-8 10000000 159 ns/op
BenchmarkFailingAllTestString-8 10000000 164 ns/op
Benchmarks without fail fast flag and preallocated capacity for errors
BenchmarkValidString-8 20000000 68.7 ns/op
BenchmarkFailing1TestString-8 10000000 189 ns/op
BenchmarkFailing2TestString-8 5000000 272 ns/op
BenchmarkFailingAllTestString-8 3000000 418 ns/op
Tested on go 1.7.3.
make test
- Testing for templates
- Prevent duplicate validations on the same field
- Update Required tag to error out on numerical or boolean fields
- Support for sub-validations?
Struct fields: generated code will call static Validate method on any field that implements Validateable interface
Maybe use a deep check - Readme info for what information is available within the templates.
- Contains for other slice types.
- Contains for maps.
- Add support for build tags for generated file.
- Cross field validation (i.e. x.start <= x.end)