Home

Awesome

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

    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)
    }
    // 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

License