Simple validation using code generation. No reflection welcome.
Gatekeeper generates code that validates http request form values.
Validated values are assigned to corresponding struct fields without the use of reflection.
To generate a .gatekeeper.go file, run gatekeeper on a .yaml configuration file. You may need to run go mod tidy after generation in order to resolve dependencies such as libphonenumber if they are not already included in your project.
see example/gatekeeper.yaml for an example configuration file
-
Validation for
- Ints
- min and max
- Strings
- min length and max length
- Emails
- Options (must be one of { "January", "February", etc }
- time.Time
- specify time format (DateOnly, DateTime, etc)
- UUIDS
- Regex Matching
- Phone Numbers
- provided as *libphonenumber.PhoneNumber validated against a region
- Ints
-
Handler Decorator Functionality is provided to convert a handler that takes in the request, responseWriter, a pointer to the validated struct, and a *gkerror.ValidationErrGroup to an http.HandlerFunc by decorating it to create and validate the struct, then pass it to your function. All you need to do is create a function like:
func UserHandler(w http.ResponseWriter, r *http.Request, User *User, errs *gkerror.ValidationErrGroup) { // do something with the struct and errors if errs.Any() { // an error occurred firstNameErrs := errs.ByField("FirstName") // do something with errors on the FirstName field } }
then you can pass it to the generated function decorator to get an http.HandlerFunc:
handler := NewUserHandler(UserHandler) // this returns an http.HandlerFunc that can be assigned to a route
-
Interfaces The generated struct is attached getter methods for each of its fields in the form of Get() , like so:
func (x *User) GetFirstName() string { return x.FirstName } func (x *User) GetLastName() string { return x.LastName } func (x *User) GetEmail() string { return x.Email } func (x *User) GetId() uuid.UUID { return x.Id } func (x *User) GetCreatedAt() time.Time { return x.CreatedAt } func (x *User) GetPhoneNumber() *libphonenumber.PhoneNumber { return x.PhoneNumber } func (x *User) GetAge() int { return x.Age } func (x *User) GetBirthMonth() string { return x.BirthMonth }
This allows for interfaces to be emitter to other packages using the emit_interface_to property of the object configuration.
For example we can specify that we'd like the interface to be emitted to package other located at ./other:
package other import ( "time" "github.com/google/uuid" "github.com/ttacon/libphonenumber" ) type IUser interface { GetFirstName() string GetLastName() string GetEmail() string GetId() uuid.UUID GetCreatedAt() time.Time GetPhoneNumber() *libphonenumber.PhoneNumber GetAge() int GetBirthMonth() string }
- File types
- Additional primitives and other custom types (booleans, slices, int64, uuid.UUID, etc)
- Middleware provider
- Custom object-level validators
- Fine-grained error handling
- File watching & caching
- Generate tests for sample data
- Additional string features:
- hasPrefix
- hasSuffix
- includes
- doesn't include
- alphaNumeric
- Additional Int features:
- isPositive
- isNegative
- isMultipleOf
- optional fields with default values
- URLs
- IP addresses
- Mac Addresses
- Emojis
- Additional UUID versions uuidv4, uuidv6, etc
- JWTs
- Parsing from request body as JSON
- Panic on Error option w/ recover handler
- Validation escape on first failure
- Support for externally defined types
- Comparison against other fields (i.e. .NumberOne must be greater than .NumberTwo)
- Colors (rgba, hex)
- Valid JSON string
- marshal into provided type
- Social Security Numbers
- Emit interface to another directory/package
- Run generation of all objects in parallel using a workgroup
- Better validation/error messages for configuration files