Enforcer simplifies the tedious validation process in Go applications.
Forget messy boilerplate-ridden code, enforcer is here to enforce your will with a handful of simple Go tags!
go get -u github.com/rrojan/enforcer
- Use
enforce
to validate enforcements
E.g.: name
is a required field between 2-64 chars, and should be "Spaced and Cased"
type myStruct struct {
name string `enforce:"required;between:2,64;matches:^[A-Z][a-z]+(?: [A-Z][a-z]+)*"`
}
- Simple Validations
- Setting Defaults & Prohibits
- Custom Validations
- Single Variable Validation
- Example projects
required
: mark a field as requiredbetween
: string length or numerical value limitmin
: Minimum char length for string or minimum value for numeric typemax
: Maximum char length for string or maximum value for numeric typematch
: match emails, passwords, phone numbers, or your own custom regex patternsenum
: enforce enum options for string, int, etcexclude
: check whether value is in a list of excluded valueswordCount
: limit the wordcount of a string inputdefault
: add a default value in case not provided to the fieldprohibit
: make sure a field is empty (user input cannot populate a struct field)
type SignupReq struct {
// Name -> Enforce "required" and length "between" 2 chars and 10 chars
Name string `json:"name" enforce:"required;between:2,10"`
// Email -> Enforce "required" and pattern "match" for email
Email string `json:"email" enforce:"required;match:email"`
// Phone -> Enforce pattern "match" for custom regex
Phone string `json:"phone" enforce:"match:^[0-9\\-]{7,12}$"`
// Age -> Enforce "min" and "max" signup age (number) to be in range 18-100 (we can use `between` for this as well)
Age int `json:"age" enforce:"min:18;max:100"`
// UserType -> Enforce "enum" which lets the value be "admin" or "user"
UserType string `json:"type" enforce:"required;enum:admin,user"`
// Bio -> Minimum of 3 "wordCount", maximum of 150, and a max" 256 character limit
Bio string `json:"bio" enforce:"wordCount:3,150;max:256"
// Password -> Enforce "required", "min" char limit, "max" char limit and "match" for password validity
Password string `json:"password" enforce:"required;match:password"`
}
req := SignupReq{}
// This example uses Gin for request binding, but enforcer can be used on its own as well
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(400, gin.H{"error": err.Error()})
return
}
// enforcer.Validate reads the `enforce:"..."` tags and applies enforcements
errors := enforcer.Validate(req)
// This is an array of all errors present
}
Enforcer allows you to set default values for struct fields. Default values take over in case values aren't provided while validating the struct.
type User struct {
Email string `enforce:"required"`
Username string `enforce:"default:Anonymous"`
UserType int `enforce:"enum:0,1,2;default:0"
Score float `enforce:"default:5.0;between:0,10"`
}
Then, validate by using a pass by reference instead of pass by value for the struct
c := Coupon{ ... }
errors := enforcer.Validate(&c) // Note we are using '&'
Time can be set to a custom value by default in the format "YYYY-MM-DD HH:MM:SS +TZHH:TZMM"
You can also set default to the current time using timeNow. Time before and after current date can be done using a semantic addition like timeNow-1_day
or timeNow+10_days
type Coupon struct {
ValidFrom time.Time `enforce:"default:2023-06-15 00:00:00 +5:45"`
ActivatedAt time.Time `enforce:"default:timeNow"`
NotifyAt time.Time `enforce:"default:timeNow+1_minute"`
NextCoupon time.Time `enforce:"default:timeNow+30_minutes"`
ExpiresAt time.Time `enforce:"default:timeNow+5_days"`
}
There are certain cases where a field input must never be binded from user input, or where user input should not bypass the default value. In these cases, prohibit
will reset the field to its corresponding zero or default value.
type User struct {
Username string `enforce:"required"`
Password string `enforce:"required;match:password"`
AuthUID string `enforce:"prohibit"` // Even if user provides the `AuthUID` field, it will be reset to null value
UserType string `enforce:"prohibit;default:user;enum:user,admin"` // Using prohibit means that User won't be able to override default
}
Use custom:
and enforcer.CustomValidator
to run multiple custom validators like below
type ProductReq struct {
// Enforce a `productTitleTemplate` validation for title
Title string `enforce:"required;custom:productTitleTemplate"`
// Enforce multiple custom validators for price by chaining it with a comma
Price int `enforce:"required;custom:isNotOverpriced,canUserSetPrice;min:1000"`
// 0 -> Draft, 1 -> Published
IsPublished int `enforce:"enum:0,1"`
}
Use enforcer.CustomValidator
to validate and run the custom bindings through a enforcer.CustomEnforcements
map
Note that the argument of the CustomEnforcement function is always a string, regardless of what the actual field type might be
req := ProductReq{}
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(400, gin.H{"error": err.Error()})
return
}
customEnforcements := enforcer.CustomEnforcements{
{
"productTitleTemplate": func(productTitle string) string {
// Apply validation logic here
isValid := true
if !isValid {
return "Product title does not match proper format"
}
return ""
},
"isNotOverpriced": func(priceStr string) string {
price, _ := strconv.Atoi(priceStr)
isValid := price < somePriceValidationQuery()
if !isValid {
return "Product is overpriced!"
}
return ""
},
"canUserSetPrice": func(priceStr string) string {
price, _ := strconv.Atoi(priceStr)
isValid := priceRoleValidate(price)
if !isValid {
return "User does not have authorization to set price in this range"
}
return ""
},
},
}
errors := enforcer.CustomValidator(req, customEnforcements) // Array of error messages
Use enforce:"... default:someValue ..."
to add a default value in case data is not provided to a struct field
type User struct {
Name string `enforce:"required;between:2,32"`
UserType string `enforce:"default:user;enum:admin,user"`
IsActive int `enforce:"default:1;enum:0,1"
}
This is done by the enforcer.Validate
function, however when default
is used in any struct field, you must provide the address of the struct, not the struct itself for it to work. Else the default will be set on a copy of the struct, not the original struct itself
u := User{}
c.ShouldBindJSON(&u) // Bind values to `u` from request data
errors := enforcer.Validate(&u)
While not often used, variable validation can be performed by using the enforcer.ValidateVar
function
myAge := 23
errors = enforcer.ValidateVar(myAge, "min:18;max:100")