DEV Community

Kittipat.po
Kittipat.po

Posted on • Edited on

85 1 1 1 1

A Guide to Input Validation in Go with Validator V10

Validator V10

Introduction

Input validation is crucial for data integrity and security in any software application. Go, a powerful and popular programming language known for its simplicity and performance, offers the "validator" package (v10) for efficient validation. In this blog, we explore how to leverage Go Validator V10 to validate input parameters effectively and enhance application robustness.

👩‍💻 Validating Input Parameters:

Let's now dive into the practical implementation of input parameter validation using Go Validator V10. For this example, we'll validate input parameters for a simple API endpoint that receives data in JSON format.

Step 1: Import the Go Validator package:

To begin, we import the validator package in our Go code:

import (
    "fmt"
    "net/http"

    "github.com/go-playground/validator/v10"
)
Enter fullscreen mode Exit fullscreen mode

Step 2: Define a Data Structure:

Next, we define a Go struct representing the data structure of the input we expect to receive:

type User struct {
    Username  string `json:"username" validate:"required,min=5,max=20"`
    Email     string `json:"email" validate:"required,email"`
    Age       int    `json:"age" validate:"gte=18,lte=120"`
}
Enter fullscreen mode Exit fullscreen mode

In this example, we have a User struct with three fields Username, Email, and Age. We've added validation tags using Go Validator V10 syntax to specify the validation rules for each field.

To access the full list of supported validation tags and their usage, you can refer to the official documentation of Go Validator V10 at the following URL: https://pkg.go.dev/github.com/go-playground/validator/v10

Step 3: Validate the Input:

Now, let's implement the API endpoint and validate the incoming data:

func createUserHandler(w http.ResponseWriter, r *http.Request) {
    var user User

    // Parse the JSON request and populate the User struct
    err := json.NewDecoder(r.Body).Decode(&user)
    if err != nil {
        http.Error(w, "Invalid JSON", http.StatusBadRequest)
        return
    }

    // Create a new validator instance
    validate := validator.New()

    // Validate the User struct
    err = validate.Struct(user)
    if err != nil {
        // Validation failed, handle the error
        errors := err.(validator.ValidationErrors)
        http.Error(w, fmt.Sprintf("Validation error: %s", errors), http.StatusBadRequest)
        return
    }

    // Validation passed, proceed to process the user data
    // Your application logic goes here...

    // Send a success response
    w.WriteHeader(http.StatusOK)
    fmt.Fprintf(w, "User created successfully!")
}
Enter fullscreen mode Exit fullscreen mode

In this code snippet, we first parse the incoming JSON data and populate the User struct. Then, we create a new validator instance and validate the User struct using validate.Struct(user). If validation fails, the error message with the specific validation errors will be returned. Otherwise, our application logic can continue to process the validated data.

🚀 Creating Custom Validators 🚀

Apart from the built-in validation tags provided by Go Validator V10, you also have the flexibility to create custom validators tailored to your specific application requirements. Custom validators allow you to enforce domain-specific rules on your data, ensuring even more precise input validation.

Here's an example of creating a custom validator named PercentageValidator

import (
    "github.com/go-playground/validator/v10"
    "github.com/shopspring/decimal"
)

func PercentageValidator(fl validator.FieldLevel) bool {
    maxPercent := decimal.NewFromInt(100)
    minPercent := decimal.NewFromInt(0)

    switch v := fl.Field(); v.Kind() {
    case reflect.String:
        val, err := decimal.NewFromString(v.String())
        if err == nil && val.Abs().GreaterThanOrEqual(minPercent) && val.Abs().LessThanOrEqual(maxPercent) {
            return true
        }
    case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
        val := decimal.NewFromInt(v.Int())
        if val.Abs().GreaterThanOrEqual(minPercent) && val.Abs().LessThanOrEqual(maxPercent) {
            return true
        }
    case reflect.Float32, reflect.Float64:
        val := decimal.NewFromFloat(v.Float())
        if val.Abs().GreaterThanOrEqual(minPercent) && val.Abs().LessThanOrEqual(maxPercent) {
            return true
        }
    default:
        return false
    }

    return false
}
Enter fullscreen mode Exit fullscreen mode

In this example, the PercentageValidator function checks whether the input value represents a valid percentage (ranging from 0 to 100). The validator supports various data types, including strings, integers, and floats.

To use this custom validator, you need to register it with Go Validator V10 before performing input validation. You can register it in the following way:

validate := validator.New()

// Registering the custom validator
validate.RegisterValidation("percentage", PercentageValidator)
Enter fullscreen mode Exit fullscreen mode

Now you can apply the percentage validation tag to any field that requires percentage validation in your User struct or any other data structure:

type User struct {
    Username  string `json:"username" validate:"required,min=5,max=20"`
    Email     string `json:"email" validate:"required,email"`
    Age       int    `json:"age" validate:"gte=18,lte=120"`
    Discount  float64 `json:"discount" validate:"percentage"`
}
Enter fullscreen mode Exit fullscreen mode

By creating custom validators, you can tailor the input validation to your specific application's needs, ensuring the accuracy and security of your data processing.

👩‍💻 Enhance Your Validation with go-common

When working with input validation in Go, validator/v10 is a widely used and powerful library. Over time, I found myself frequently adding custom validators, managing error messages, and organizing validation logic to fit specific use cases. To simplify these tasks, I added a validator package into my go-common library.

The Validator package builds on validator/v10 and provides tools to make input validation more flexible, customizable, and easier to manage. It’s particularly useful if you often create custom validation rules or need more control over error messages and field naming.

Key Features of go-common Validator:

  • Easy Integration: Simplifies working with validator/v10 and extends its capabilities.
  • Custom Validators: Easily register your own validation rules with custom error messages.
  • Simplified Validation: A user-friendly API for struct validation.
  • Extensibility: Add domain-specific validation logic using a simple interface.

Basic Usage:

package main

import (
    "fmt"
    "github.com/kittipat1413/go-common/framework/validator"
)

type Data struct {
    Field1 string `validate:"mytag"`
    Field2 string `validate:"required"`
    Field3 int    `validate:"gte=0,lte=130"`
}

func main() {
    v, err := validator.NewValidator(
        validator.WithCustomValidator(new(MyValidator)),
        // Add more custom validators here
    )
    if err != nil {
        fmt.Println("Error initializing validator:", err)
        return
    }

    data := Data{
        Field1: "test",
        Field3: 200,
    }

    err = v.ValidateStruct(data)
    if err != nil {
        fmt.Println("Validation failed:", err)
    } else {
        fmt.Println("Validation passed")
    }
}
Enter fullscreen mode Exit fullscreen mode

Output:

Validation failed: Field1 failed custom validation, Field2 is a required field, Field3 must be 130 or less
Enter fullscreen mode Exit fullscreen mode

For more details, examples, and advanced usage (like custom validators and JSON tag support), visit the go-common repository on GitHub.

If you find go-common helpful, please consider giving it a ⭐ on GitHub. Your support helps the project grow and encourages further improvements and features. Together, let’s build more robust and secure Go applications!

📝 Conclusion:

By using Go Validator V10, we can easily implement input parameter validation in our Go applications. Validating input parameters helps to prevent potential security vulnerabilities and ensures that the data our applications work with is accurate and consistent. The library's rich set of validation rules makes it a powerful tool for developers working with Go.

Remember to always validate the input data in your applications to guarantee their reliability and security. Happy coding!🥂

☕ Support My Work ☕

If you enjoy my work, consider buying me a coffee! Your support helps me keep creating valuable content and sharing knowledge. ☕

Buy Me A Coffee

Image of Timescale

🚀 pgai Vectorizer: SQLAlchemy and LiteLLM Make Vector Search Simple

We built pgai Vectorizer to simplify embedding management for AI applications—without needing a separate database or complex infrastructure. Since launch, developers have created over 3,000 vectorizers on Timescale Cloud, with many more self-hosted.

Read full post →

Top comments (1)

Collapse
 
jhelberg profile image
Joost Helberg

Nice to read about this. Although input validation is useful, it is not a security measure. If input can compromise security, some security-layer should cater for that. Input validation may even allow security compromise because of sloppy input injection into another language or api. E.g. text validation may allow a single single quote, but the api may use it in a concatenated SQL command, creating a security issue. It's seperate things: input validation and ensuring input doesn't violate semantics and security.

Postmark Image

Speedy emails, satisfied customers

Are delayed transactional emails costing you user satisfaction? Postmark delivers your emails almost instantly, keeping your customers happy and connected.

Sign up