DEV Community

Reza Khademi
Reza Khademi

Posted on

GoValidator Zero Allocation Validation

GoValidator

GoValidator is a data validation package that can sanitize and validate your data to ensure its safety and integrity as much as possible. GoValidator will not use struct tags as validation and there is no need of them.

Our goal is to avoid using any type assertion or reflection for simplicity.
We would be delighted if you were interested in helping us improve the GoValidator package. Feel free to make your pull request.


Benchmarks

The full benchmark code lives in the benchmarks/ directory (a separate Go module, so the comparison libraries are not added to govalidator's own dependencies). It validates the UserCreateReq DTO below with equivalent rules in each library and measures the passing path. The go-playground validator instance is created once and reused, as its documentation recommends, so its struct-metadata cache is not unfairly discarded.

Run the benchmarks yourself with:

make benchmark
Enter fullscreen mode Exit fullscreen mode
type UserCreateReq struct {
    FirstName     string `json:"first_name"`
    LastName      string `json:"last_name"`
    PhoneNumber   string `json:"phone_number"`
    Email         string `json:"email,omitempty"`
    FatherName    string `json:"father_name"`
    CertificateID string `json:"certificate_id"`
    BirthDate     string `json:"birth_date"`
    CompanyID     int    `json:"company_id"`
    Gender        int8   `json:"gender"`
}
Enter fullscreen mode Exit fullscreen mode

The result on Apple M3 Pro, 11 CPU Cores, 18GB RAM:

Library Operations/sec (ns/op) Memory Allocations (B/op) Allocations/op
govalidator 500 ns/op 0 B/op 0 allocs/op
go-playground 878 ns/op 0 B/op 0 allocs/op
ozzo-validation 3477 ns/op 6394 B/op 78 allocs/op

Getting Started

Go Validator includes a set of validation rules and a handy CustomRule() method to define any custom rule.

Installation

Run the following command to install the package:

go get github.com/rezakhademix/govalidator/v2
Enter fullscreen mode Exit fullscreen mode

Import

Import package in your code-base by using:

import validator "github.com/rezakhademix/govalidator/v2"
Enter fullscreen mode Exit fullscreen mode

Examples:


  1. Simple usage: (Go Playground)

        type User struct {
            Name string `json:"name"`
            Age uint    `json:"age"`
        }
    
        var user User
    
        v := govalidator.New() // be sure to import govalidator/v2
    
        v.RequiredInt(user.Age, "age", "").         // age can not be null or 0
            MinInt(user.Age, 18, "age", "")         // minimum value for age is 18
    
        v.RequiredString(user.Name, "name", "")     // name can not be null, "" or " "
            MaxString(user.Name, 50, "name", "")    // maximum acceptable length for name field is 50
    
        if v.IsFailed() {
            return v.Errors()  // will return failed validation error messages
        }
    


    go

  2. With custom field names and messages:

        type User struct {
            Name string `json:"name"`
        }
    
        var user User
    
        v := govalidator.New()
    
        v.RequiredString(user.Name, "first_name", "please fill first_name field") // with custom field name and custom validation error message
    
        if v.IsFailed() {
            return v.Errors()
         }
    
  3. Advanced usage with any custom validation rule: You can define any custom rules or any flexible rule that does not exist in default govalidator package. Simply use CustomRule() method to define your desired data validations:

        type Profile struct {
            Name   string
            Age    int
            Score  int
            Status []string
        }
    
        var profile Profile
    
        // after filling profile struct
    
        v := govalidator.New()
    
        v.CustomRule(profile.Name != "", "name", "name is required")  // CustomRule is a method to define a custom rule as first parameter and then pass field name and validation error message
    
        v.CustomRule(profile.Age > 18, "age", "age must be greater than 18")
    
        // we just need to pass a bool as a rule
        // `checkScore()` is your custom method that returns a bool and can be used as a rule
        v.CustomRule(checkScore(), "score", "score must")
    
        // using `In` Generic rule:
        statuses := []string{"active", "inactive", "pending"}
    
        v.CustomRule(validator.In[ProfileStatuses](profile.Status, statuses), "status", "status must be in:...")
    
        if v.IsFailed() {
            return v.Errors()
        }
    
  4. Deep Dive:

        type CategoryCreateReq struct {
            ParentID    *int   `json:"parent_id"`
            Name        string `json:"name"`
            Description string `json:"description"`
            Status      int    `json:"status"`
            Meta        string `json:"meta"`
        }
    
        var categoryCreateReq CategoryCreateReq
    
        // after filling CategoryCreateReq struct with binding or other methods
    
        v := govalidator.New().WithRepo(validatorRepo)  // be sure to import govalidator/v2
    
        ok := v.
            RequiredString(req.Name, "name", msgCategoryNameRequired).
            MaxString(req.Name, nameMaxLength, "name", msgCategoryNameMaxLength).
            MinString(req.Name, nameMinLength, "name", msgCategoryNameMinLength).
            NotExists(req.Name, "categories", "name", "name", msgCategoryNameAlreadyExists). // ensure the value of req.Name does not exist in the "name" column of the "categories" table in the database
            MaxString(req.Description, descriptionMaxLength, "description", msgDescriptionMaxLength).
            MinInt(req.Status, minStatus, "status", msgMinCategoryStatusIsWrong).
            MaxInt(req.Status, maxStatus, "status", msgMaxCategoryStatusIsWrong).
            IsJSON(req.Meta, "meta", msgMetaMustBeJSON).
            When(req.ParentID != nil, func() {  
                        v.Exists(*req.ParentID, "categories", "id", "parent_id", msgCategoryParentIDNotExist)   // checks if the value of req.ParentID exists in the "id" column of the "categories" table in the database
                    }).
            IsPassed()
    
        if !ok {
          return v.Errors()
        }
    
  5. File validation:

        func (h *Handler) UploadAvatar(w http.ResponseWriter, r *http.Request) {
            _, fh, err := r.FormFile("avatar")
            if err != nil {
                http.Error(w, "missing file", http.StatusBadRequest)
                return
            }
    
            v := govalidator.New()
    
            ok := v.
                    RequiredFile(fh, "avatar", "").
                    FileMimeType(fh, []string{"image/jpeg", "image/png", "image/gif"}, "avatar", "").
                    FileMinSize(fh, 1024, "avatar", "").        // at least 1 KB
                    FileMaxSize(fh, 2*1024*1024, "avatar", ""). // at most 2 MB
                    FileExtension(fh, []string{"jpg", "jpeg", "png", "gif"}, "avatar", "").
                    IsPassed()
    
            if !ok {
                return v.Errors()
            }
        }
    

Top comments (0)