A data validation library for GoLang.
Download the library
go get github.com/moeenn/vld
There is no denying that validator
is an amazing package and is the ecosystem standard. However there are some issues with it
- Struct tags are used to define field validators. Since tags are nothing more than strings, this method can be very error-prone.
- The validation errors are structured non user-friedly way. This makes it difficult to format the validation errors in such a way that they can be displayed on the front-end clients properly.
- The mechanism for defining custom validators is not intuitive.
vld
attempts to solve these problems.
package main
import (
"fmt"
v "github.com/moeenn/vld"
)
type LoginForm struct {
Email string
Password string
}
func main() {
form := LoginForm{
Email: "admin-site.com",
Password: "q1w2e3r4",
}
validations := []v.Validation{
{
Tag: "email",
Data: form.Email,
Rules: []v.Rule{v.NonEmptyString, v.Email},
},
{
Tag: "password",
Data: form.Password,
Rules: []v.Rule{v.NonEmptyString, v.MinLength(8)},
},
}
err := v.Validate(validations)
if err != nil {
validationErrors := err.(v.ValidationErrors)
fmt.Printf("validation errors: %v\n", validationErrors.Errors)
return
}
fmt.Println("validation successful")
}
- NonEmptyString: The input must be valid non-empty string value.
v.NonEmptyString
- Length: The input string length must be exactly equal to the provided length.
// input string must be exactly 10 characters in length
v.Length(10)
- MinLength: The input string length must be equal or more than the provided length.
// input string must be 5 characters or more in length
v.MinLength(5)
- MaxLength: The input string length must be equal or less than the provided length.
// input string must be 5 characters or less in length
v.MaxLength(20)
- MinFloat: The input floating-point number must be equal or more than the provided limit.
// input number must be at least 5.0 or more
v.MinFloat(5.0)
- MaxFloat: The input floating-point number must be equal or less than the provided limit.
// input number must be 500.5 or less
v.MaxFloat(500.5)
- MinInt: The input integer number must be equal or more than the provided limit.
// input number must be at least 100 or more
v.MinInt(100)
- MaxInt: The input integer number must be equal or less than the provided limit.
// input number must be 200 or less
v.MaxInt(200)
- LessThanFloat: The input floating-point number must be less than the provided limit.
// input number must be less than 20.0 (non-inclusive)
v.LessThanFloat(20.0)
- GreaterThanFloat: The input floating-point number must be greater than the provided limit.
// input number must be greater than 10.5 (non-inclusive)
v.GreaterThanFloat(10.5)
- LessThanInt: The input integer number must be less than the provided limit.
// input number must be less than 24 (non-inclusive)
v.LessThanInt(24)
- GreaterThanInt: The input integer number must be greater than the provided limit.
// input number must be greater than 100 (non-inclusive)
v.GreaterThanInt(100)
- Email: The input must be a valid email address.
v.Email
- UUID: The input must be a valid UUID string
v.UUID
- URL: The input must be a valid URL string
v.URL
- Password: The input must be a strong password. The following rules are applied.
- Minimum eight characters
- At least one uppercase letter
- At least one lowercase letter
- At least one number
- At least one special character
v.Password
- JSON: Input must be a well-formed JSON string
v.JSON
- ISODate: The input must be valid ISO timestamp according to RFC3339: Link.
// input string must conform to the format e.g 2024-03-22T12:35:05.115Z
v.ISODate
- Date: The input must be a valid date-only string. Link.
// input must be in format e.g. 2023-10-05
v.Date
- Time: The input must be a valid time-only string. Link.
// input must be in 24-hours format: e.g. 10:20:00
v.Time
-
Before date: TODO
-
Before or equal to date: TODO
-
After date: TODO
-
After or equal to date: TODO
-
StartsWith: The input string must begin with the provided prefix.
v.StartsWith("data:")
- DoesntStartWith: The input string must not start with the provided prefix.
v.DoesntStartWith("mysql")
- EndsWith: The input string must end with the provided suffix.
v.EndsWith("example")
- DoesntEndWith: The input string must not end with the provided prefix.
v.DoesntEndWith("sample")
- Enum: The input string must be equal to one of the provided valid values.
// values are provided in variadic fashion
v.Enum("Value One", "Value Two", "Value Three")
- Regexp: The input value must satisfy the provided regular expression.
v.Regexp("^hello$")
- Same: The input value must be the same as the required input. This validator can be used to confirm passwords.
import (
"fmt"
v "github.com/moeenn/vld"
)
type RegisterForm struct {
Password string
ConfirmPassword string
}
func main() {
form := RegisterForm{
Password: "A832KCN284506b@",
ConfirmPassword: "A832KCN284506b@",
}
validations := []v.Validation{
{
Tag: "password",
Data: form.Password,
Rules: []v.Rule{v.NonEmptyString, v.Password},
},
{
Tag: "confirm_password",
Data: form.ConfirmPassword,
Rules: []v.Rule{v.Same("Password", form.Password)},
},
}
err := v.Validate(validations)
if err != nil {
validationErrors := err.(v.ValidationErrors)
fmt.Printf("validation errors: %v\n", validationErrors.Errors)
return
}
fmt.Println("validation successful")
}
-
Array: TODO
-
MinItems: TODO
-
MaxItems: TODO
In vld
validators are plain functions. They can be defined as follows.
type ExampleForm struct {
Slug string
}
func Slug(input any) error {
err := errors.New("The input must be a valid slug")
asString, ok := input.(string)
if !ok {
return err
}
if strings.Contains(asString, "_") {
return err
}
return nil
}
func main() {
form := ExampleForm{
Slug: "some-slug-here",
}
validations := []v.Validation{
{
Tag: "slug",
Data: form.Slug,
Rules: []v.Rule{v.NonEmptyString, Slug}, // notice the user-defined validator
},
}
err := v.Validate(validations)
if err != nil {
validationErrors := err.(v.ValidationErrors)
fmt.Printf("validation errors: %v\n", validationErrors.Errors)
return
}
// the input data is valid
...
}
If the custom validator function required additional arguments, they can be defined as follows.
func StartsWith(prefix string) Rule {
return func(input any) error {
err := fmt.Errorf("The input must start with '%s'", prefix)
asString, ok := input.(string)
if !ok || !strings.HasPrefix(asString, prefix) {
return err
}
return nil
}
}