Skip to content

Latest commit

 

History

History
208 lines (173 loc) · 6.59 KB

README.md

File metadata and controls

208 lines (173 loc) · 6.59 KB

Go Version GoDoc Build Status Coverage Status GoReport

Fast and intuitive validation library for Go

This lib uses the Is... validation functions from the govalidator project.

Installation

go get github.com/tiendc/go-validator

Usage

General usage

    import (
        vld "github.com/tiendc/go-validator"
    )

    type Person struct {
        FirstName string
        LastName  string
        Birthdate time.Time

        Unemployed bool
        Salary     uint
        Rank       string
        WorkEmail  string
        Projects   []string
        TaskMap    map[string]Task
    }
    var p Person

    errs := vld.Validate(
        // Validate first and last names separately
        vld.StrLen(&p.FirstName, 3, 30).OnError(
            vld.SetField("first_name", nil),
            vld.SetCustomKey("ERR_VLD_PERSON_FIRST_NAME_INVALID"),
        ),
        vld.StrLen(&p.FirstName, 3, 30).OnError(
            vld.SetField("last_name", nil),
            vld.SetCustomKey("ERR_VLD_PERSON_LAST_NAME_INVALID"),
        ),

        // OR use this to produce only one error when one of them fails
        vld.Group(
            vld.StrLen(&p.FirstName, 3, 30),
            vld.StrLen(&p.LastName, 3, 30),
        ).OnError(
            vld.SetField("name", nil),
            vld.SetCustomKey("ERR_VLD_PERSON_NAME_INVALID"),
        ),

        // Birthdate is optional, but when it's present, it must be within 1950 and now
        vld.When(!p.Birthdate.IsZero()).Then(
            vld.TimeRange(p.Birthdate, <1950-01-01>, time.Now()).OnError(...),
        )

        vld.When(!p.Unemployed).Then(
            vld.Required(&p.Salary),
            // Work email must be valid
            vld.StrIsEmail(&p.WorkEmail),

            // Rank must be one of the constants
            vld.StrIn(&p.Rank, "Employee", "Manager", "Director"),
            vld.Case(
                vld.When(p.Rank == "Manager").Then(vld.NumGT(&p.Salary, 10000)),
                vld.When(p.Rank == "Director").Then(vld.NumGT(&p.Salary, 30000)),
            ).Default(
                vld.NumLT(&p.Salary, 10000),
            ),

            // Projects are optional, but when they are present, they must be unique and sorted
            vld.When(len(p.Projects) > 0).Then(
                vld.SliceUnique(p.Projects).OnError(...),
                vld.SliceSorted(p.Projects).OnError(...),
            )
        ).Else(
            // When person is unemployed
            vld.NumEQ(&p.Salary, 0),
            vld.StrEQ(&p.WorkEmail, ""),
        ),

        // Validate slice elements
        vld.Slice(p.Projects).ForEach(func(elem int, index int, validator ItemValidator) {
            validator.Validate(
                vld.StrLen(&elem, 10, 30).OnError(
                    vld.SetField(fmt.Sprintf("projects[%d]", index), nil),
                    vld.SetCustomKey("ERR_VLD_PROJECT_NAME_INVALID"),
                ),
            )
        }),

        // Validate map entries
        vld.Map(p.TaskMap).ForEach(func(k string, v Task, validator ItemValidator) {
            validator.Validate(
                vld.StrLen(&v.Name, 10, 30).OnError(
                    vld.SetField(fmt.Sprintf("taskMap[%s].name", k), nil),
                    vld.SetCustomKey("ERR_VLD_TASK_NAME_INVALID"),
                ),
            )
        }),

        // OTHER FUNCTIONS
        // Pass if at least one of the validations passes
        vld.OneOf(
            // List of validations
        ),

        // Pass if exact one of the validations passes
        vld.ExactOneOf(
            // List of validations
        ),

        // Pass if none of the validations passes
        vld.NotOf(
            // List of validations
        ),
    )

    for _, e := range errs {
        detail, warnErr := e.BuildDetail()
        fmt.Printf("%+v\n", detail)
    }

Error message localization

  • Method 1: inline localization (not recommended)
    errs := Validate(
        NumLTE(&p.Age, 40).OnError(
            // Override the default template in english
            SetTemplate("Tuổi nhân viên phải nhỏ hơn hoặc bằng {{.Max}}"),
        ),
    )

    for _, e := range errs {
        detail, warnErr := e.BuildDetail()
        fmt.Printf("%+v\n", detail)
    }
  • Method 2: using another localization lib (recommended)
    // Supposed you have 2 files defining error messages
    // In `error_messages.en`:
    // ERR_VLD_EMPLOYEE_AGE_TOO_BIG = "Employee {{.EmployeeName}} has age bigger than {{.Max}}"
    // In `error_messages.vi`:
    // ERR_VLD_EMPLOYEE_AGE_TOO_BIG = "Nhân viên {{.EmployeeName}} có tuổi lớn hơn {{.Max}}"

    errs := Validate(
        NumLTE(&p.Age, 40).OnError(
            // Custom param (the default template doesn't have this one)
            SetParam("EmployeeName", p.Name),
            // Custom key to define custom template to use
            SetCustomKey("ERR_VLD_EMPLOYEE_AGE_TOO_BIG"),
        ),
    )

    for _, e := range errs {
        errKey := e.CustomKey()
        errParams : = e.Params() // or e.ParamsWithFormatter()
        errorMsg := translationFunction(errKey, errParams) // You need to provide this function
        fmt.Printf("%+v\n", errorMsg)
    }

Custom error param formatter

    errs := Validate(
        NumLT(&budget, 1000000).OnError(
            SetField("Budget", nil),
        ),
    )

    // e.BuildDetail() may produce message `Budget must be less than 1000000`,
    // but you may want a message like: `Budget must be less than 1,000,000`.
    // Let's use a custom formatter

    errs := Validate(
        NumLT(&budget, 1000000).OnError(
            SetField("Budget", nil),
            SetNumParamFormatter(NewDecimalFormatFunc('.', ',', "%f")),
        ),
    )

Contributing

  • You are welcome to make pull requests for new functions and bug fixes.

License