[ANN] Package validator
The thing about working in a startup under stealth mode is you can’t often talk about what you’re doing. Thankfully, from time to time, an opportunity appears that lets us at least share something tangential.
A large part of our project at project7.io involves receiving data from a client (generally in JSON) for processing. This involves unmarshaling the JSON into a struct
, something that Go does very well. But then comes the boring part: checking that the information sent from the client is correct and complete before doing anything with it.
if t.Username != "" && t.Age > 18 && t.Foo != nil && len(t.Bar.Baz) > 8 && ...
We had to do this often and for a large number of different structs and sometimes a struct
gained a new field and we had to go back and see that it was being properly validated everywhere. It was so boring that we ended up writing something to make it easier for us. We’ve been using this for a while and now we decided to open source it in the hopes that it might be useful for others.
Package validator implements validation of variables. Initially we had implemented JSONschema but we don’t always deal with JSON, we also get data as XML and sometimes as form-encoded
. So we changed our approach and went right to the struct
definitions.
type T struct {
A int `validate:"nonzero"`
B string `validate:"nonzero,max=10"`
C struct {
Ca int `validate:"min=5,max=10"`
Cb string `validate:"min=8, regexp:^[a-zA-Z]+"`
}
}
This allowed us to attach validation rules right to the data structure definitions. Then instead of large, boring list of if
statements, we were able to validate in single function call.
if valid, _ := validator.Validate(t); !valid {
// not valid, so return an http.StatusBadRequest of something
}
Multiple rules for different situations
We often use the same struct
to deal with different data coming from the client. Sometimes we only care about one or two of the fields in one scenario, so we also supported multiple rules like, say
type T struct {
A int `foo:"nonzero" bar:"min=5,max=10"`
B string `bar:"nonzero"`
}
In scenario foo
, we need A
to be non-zero, but we don’t care for what is in B
. In the bar
scenario, however, we need B
to be non-zero and the value of A
to be between 5 and 10, inclusive. To validate, we then make validator
use a different tag name for each case
t := T{A: 3}
validator.WithTag("foo").Validate(t) // valid
validator.WithTag("bar").Validate(t) // invalid
We can also change the tag by using SetTag
with will then make the tag name persistent until changed again by another call to SetTag
.
Please refer to http://godoc.org/gopkg.in/validator.v1 for a lot more documentation, use cases, and how to access the individual validation errors found for each field.