govalidator

package module
v1.0.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 15, 2025 License: MIT Imports: 13 Imported by: 0

README

govalidator

govalidator is a powerful and flexible Go package designed to simplify and enhance data validation for your applications. Built upon the robust github.com/go-playground/validator/v10 library, it extends its capabilities with custom validation rules, particularly for file uploads, and provides convenient utilities for integrating with web frameworks like Fiber.

Features

  • Struct Validation: Easily validate Go structs using familiar validator tags.
  • Fiber Integration: Seamlessly validate HTTP request bodies (e.g., JSON, form data) within Fiber applications.
  • Custom File Validation: Out-of-the-box support for validating uploaded files (multipart.FileHeader) with rules like:
    • is_file: Ensures a field represents a valid, non-empty file.
    • file_max_size: Validates the maximum allowed file size (e.g., 10MB, 1GB).
    • file_min_size: Validates the minimum required file size (e.g., 1KB, 500B).
    • file_ext: Checks if the file has one of the allowed extensions (e.g., jpg;png).
    • file_mime: Validates the file's MIME type (e.g., image/jpeg;application/pdf).
  • Customizable Error Messages: Override default error messages globally or provide specific messages per field and validation tag.
  • Field Name Formatting: Transform field names in error messages to more user-friendly formats (e.g., camelCase to Camel Case, snake_case to Snake Case).
  • Helper Functions: Includes utility functions for string manipulation like ToCamelCase, ToSnakeCase, and ToHumanReadable.

Installation

go get github.com/openframebox/govalidator

Usage

Basic Struct Validation

Define your struct with validator tags:

package main

import (
	"fmt"
	"github.com/openframebox/govalidator"
)

type User struct {
	Name  string `json:"name" validate:"required,min=3,max=50"`	
	Email string `json:"email" validate:"required,email"`
	Age   int    `json:"age" validate:"gte=0,lte=130"`
}

func main() {
	// Create a new validator instance
	v := govalidator.New(nil)

	// Valid user
	user1 := &User{Name: "John Doe", Email: "[email protected]", Age: 30}
	if err := v.ValidateStruct(user1, nil); err != nil {
		fmt.Println("Validation Error for user1:", err.Message)
		fmt.Println("Details:", err.Details)
	} else {
		fmt.Println("User1 is valid!")
	}

	// Invalid user
	user2 := &User{Name: "Jo", Email: "invalid-email", Age: 150}
	if err := v.ValidateStruct(user2, nil); err != nil {
		fmt.Println("\nValidation Error for user2:", err.Message)
		fmt.Println("Details:", err.Details)
		// Expected output for user2:
		// Validation Error for user2: validation failed
		// Details: map[Age:field Age must be less than or equal to 130 Email:field Email must be a valid email address Name:field Name must have a minimum length of 3 characters]
	} else {
		fmt.Println("User2 is valid!")
	}
}
Fiber Request Body Validation

govalidator integrates seamlessly with gofiber/fiber/v2. The ValidateFiberRequestBody function automatically handles request body parsing.

package main

import (
	"log"
	"github.com/gofiber/fiber/v2"
	"github.com/openframebox/govalidator"
)

type Product struct {
	Name        string  `json:"name" validate:"required,min=3,max=100"`	
	Description string  `json:"description" validate:"max=500"`	
	Price       float64 `json:"price" validate:"required,gt=0"`	
	Stock       int     `json:"stock" validate:"gte=0"`
}

func main() {
	app := fiber.New()
	v := govalidator.New(nil) // Initialize govalidator

	app.Post("/products", func(c *fiber.Ctx) error {
		product := new(Product)

		// Validate the request body (BodyParser is handled internally)
		if err := v.ValidateFiberRequestBody(c, product, nil); err != nil {
			return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
				"error":   err.Message,
				"details": err.Details,
			})
		}

		// If validation passes, process the product
		return c.Status(fiber.StatusCreated).JSON(fiber.Map{
			"message": "Product created successfully",
			"product": product,
		})
	})

	log.Fatal(app.Listen(":3000"))
}
Custom File Validation

govalidator provides custom rules for multipart.FileHeader.

package main

import (
	"bytes"
	"fmt"
	"mime/multipart"
	"net/http"
	"net/http/httptest"
	"os"
	"path/filepath"

	"github.com/gofiber/fiber/v2"
	"github.com/openframebox/govalidator"
)

type UploadForm struct {
	Image *multipart.FileHeader `form:"image" validate:"required,is_file,file_max_size=5MB,file_ext=jpg;png,file_mime=image/jpeg;image/png"`	
	Doc   *multipart.FileHeader `form:"document" validate:"omitempty,is_file,file_max_size=2MB,file_ext=pdf,file_mime=application/pdf"`
}

func main() {
	app := fiber.New()
	v := govalidator.New(nil)

	app.Post("/upload", func(c *fiber.Ctx) error {
		form := new(UploadForm)

		// Parse multipart form data
		if err := c.BodyParser(form); err != nil {
			return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Failed to parse form"})
		}

		// Validate the form struct
		if err := v.ValidateStruct(form, nil); err != nil {
			return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
				"error":   err.Message,
				"details": err.Details,
			})
		}

		return c.Status(fiber.StatusOK).JSON(fiber.Map{"message": "Files uploaded and validated successfully!"})
	})

	// Example of how to test this endpoint (e.g., in a test file or separate function)
	testUpload(app)
}

func testUpload(app *fiber.App) {
	// Create a dummy image file for testing
	dummyImageContent := []byte("this is a dummy image content")
	imagePath := filepath.Join(os.TempDir(), "test_image.jpg")
	os.WriteFile(imagePath, dummyImageContent)
	defer os.Remove(imagePath)

	// Create a multipart form
	body := &bytes.Buffer{}
	writer := multipart.NewWriter(body)

	// Add image file
	imageFile, _ := os.Open(imagePath)
	defer imageFile.Close()
	part, _ := writer.CreateFormFile("image", filepath.Base(imagePath))
	part.Write(dummyImageContent)

	writer.Close()

	req := httptest.NewRequest(http.MethodPost, "/upload", body)
	req.Header.Set("Content-Type", writer.FormDataContentType())

	resp, _ := app.Test(req)

	fmt.Println("\nUpload Test Response Status:", resp.Status)
	fmt.Println("Upload Test Response Body:", string(body.Bytes()))
}
Custom Error Messages

You can provide custom error messages for specific fields and validation tags.

package main

import (
	"fmt"
	"github.com/openframebox/govalidator"
)

type Product struct {
	Name  string `json:"name" validate:"required,min=3"`	
	Price float64 `json:"price" validate:"required,gt=0"`
	Tags  []Tag `json:"tags" validate:"required,dive,required"`
}

type Tag struct {
	Name string `json:"name" validate:"required"`
}

func main() {
	v := govalidator.New(nil)

	customMessages := govalidator.FieldMessages{
		"Name": {
			"required": ":field is absolutely necessary!", // :field will be replaced by field name
			"min":      "Product name must be at least :param characters long.", // :param will be replaced by param
		},
		"Price": {
			"gt": "Product price must be greater than zero.",
		},
		"Tags.*.Name": { // Wildcard for slice elements
			"required": "Tag is required",
		},
	}

	product := &Product{Name: "A", Price: -5.0, Tags: []string{"tag1", ""}}
	if err := v.ValidateStruct(product, customMessages); err != nil {
		fmt.Println("Validation Error:", err.Message)
		fmt.Println("Details:", err.Details)
		// Expected output:
		// Validation Error: validation failed
		// Details: map[Name:Product name must be at least 3 characters long. Price:Product price must be greater than zero. Tags[1].Name:Tag is required]
	}
}
Providing Messages via Struct

For more encapsulated validation logic, you can implement the govalidator.StructWithFieldMessages interface on your struct. This is useful for keeping validation messages alongside the struct definition.

package main

import (
	"fmt"
	"github.com/openframebox/govalidator"
)

type User struct {
	Username string `json:"username" validate:"required,min=4"`
}

// FieldMessages provides custom messages for the User struct.
func (u *User) FieldMessages() govalidator.FieldMessages {
	return govalidator.FieldMessages{
		"Username": {
			"required": "Username is required.",
			"min":      "Username must be at least 4 characters long.",
		},
	}
}

func main() {
	v := govalidator.New(nil)
	user := &User{Username: "ab"}

	if err := v.ValidateStruct(user, nil); err != nil {
		fmt.Println("Validation Error:", err.Details)
		// Expected: map[Username:Username must be at least 4 characters long.]
	}
}
Field Name Formatting

Configure how field names appear in error messages.

package main

import (
	"fmt"
	"github.com/openframebox/govalidator"
)

type Item struct {
	ItemID      string `json:"item_id" validate:"required"`	
	ProductName string `json:"product_name" validate:"min=5"`
}

func main() {
	// Example 1: Default (no formatting, uses struct field name)
	v1 := govalidator.New(nil)
	item1 := &Item{ItemID: "", ProductName: "Short"}
	if err := v1.ValidateStruct(item1, nil); err != nil {
		fmt.Println("Default Formatting:", err.Details)
		// Expected: map[ItemID:field ItemID is required ProductName:field ProductName must have a minimum length of 5 characters]
	}

	// Example 2: Human-readable formatting
	config := &govalidator.Config{
		FieldFormatter: govalidator.ToHumanReadable,
	}
	v2 := govalidator.New(config)
	item2 := &Item{ItemID: "", ProductName: "Short"}
	if err := v2.ValidateStruct(item2, nil); err != nil {
		fmt.Println("Human-Readable Formatting:", err.Details)
		// Expected: map[Item ID:field Item ID is required Product Name:field Product Name must have a minimum length of 5 characters]
	}

	// Example 3: Custom formatter (e.g., to snake_case)
	configCustom := &govalidator.Config{
		FieldFormatter: govalidator.ToSnakeCase,
	}
	v3 := govalidator.New(configCustom)
	item3 := &Item{ItemID: "", ProductName: "Short"}
	if err := v3.ValidateStruct(item3, nil); err != nil {
		fmt.Println("Snake_Case Formatting:", err.Details)
		// Expected: map[item_id:field item_id is required product_name:field product_name must have a minimum length of 5 characters]
	}
}
Helper Functions

govalidator exposes several helper functions for string case conversion:

package main

import (
	"fmt"
	"github.com/openframebox/govalidator"
)

func main() {
	fmt.Println("ToCamelCase('product_name'):", govalidator.ToCamelCase("product_name")) // productName
	fmt.Println("ToSnakeCase('ProductName'):", govalidator.ToSnakeCase("ProductName"))   // product_name
	fmt.Println("ToHumanReadable('productName'):", govalidator.ToHumanReadable("productName")) // product name
	fmt.Println("ToHumanReadable('product_name'):", govalidator.ToHumanReadable("product_name")) // product name
}

Custom Validations

You can register your own custom validation functions using RegisterCustomValidation. By default, custom validations are not called if the field is nil or zero-valued. You can change this behavior by passing true as the final argument.

package main

import (
	"fmt"
	"github.com/go-playground/validator/v10"
	"github.com/openframebox/govalidator"
)

// isEven custom validation checks if an integer is even
func isEven(fl validator.FieldLevel) bool {
	num := fl.Field().Int()
	return num%2 == 0
}

type MyData struct {
	Value int `validate:"required,is_even"`
}

func main() {
	v := govalidator.New(nil)

	// Register the custom validation
	// You can also override its error message if needed
	v.RegisterCustomValidation("is_even", isEven)
	v.OverrideErrorMessages(map[string]string{
		"is_even": "field :field must be an even number",
	})

	data1 := &MyData{Value: 4}
	if err := v.ValidateStruct(data1, nil); err != nil {
		fmt.Println("Validation Error for data1:", err.Details)
	} else {
		fmt.Println("Data1 is valid!") // Expected
	}

	data2 := &MyData{Value: 5}
	if err := v.ValidateStruct(data2, nil); err != nil {
		fmt.Println("Validation Error for data2:", err.Details)
		// Expected: map[Value:field Value must be an even number]
	}
}

Error Handling

ValidateStruct and ValidateFiberRequestBody return a *govalidator.ValidationError if validation fails. Both functions require a pointer to the struct you want to validate. This error type includes:

  • Message: A general error message (e.g., "validation failed").
  • Details: A map[string]string where keys are field paths (e.g., Name, Address.Street) and values are the corresponding error messages.
package main

import (
	"fmt"
	"github.com/openframebox/govalidator"
)

type Address struct {
	Street string `json:"street" validate:"required"`	
	City   string `json:"city" validate:"required"`
}

type Person struct {
	Name    string  `json:"name" validate:"required"`
	Address Address `json:"address"`
}

func main() {
	v := govalidator.New(nil)

	person := &Person{
		Name:    "Alice",
		Address: Address{Street: "", City: ""}, // Invalid address
	}

	if err := v.ValidateStruct(person, nil); err != nil {
		fmt.Println("Validation failed:", err.Message)
		for field, msg := range err.Details {
			fmt.Printf("  Field '%s': %s\n", field, msg)
		}
		// Expected output:
		// Validation failed: validation failed
		//   Field 'Address.Street': field Street is required
		//   Field 'Address.City': field City is required
	}
}

Configuration

The govalidator.New function accepts an optional *govalidator.Config struct to customize behavior:

type Config struct {
	NamespaceFormatter       func(field string) string
	FieldFormatter           func(field string) string
	GoStyleNamespaceSelector bool
}
  • NamespaceFormatter: A function to format the namespace (path) of a field in error details. By default, it removes the root struct name. For example, User.Name becomes Name.
  • FieldFormatter: A function to format the individual field names within error messages. Defaults to ToHumanReadable (e.g., productName becomes Product Name).
  • GoStyleNamespaceSelector: A boolean that, when true, changes the namespace selector for slices from [index] to .index (e.g., Tags[0].Name becomes Tags.0.Name). This can be useful for easier parsing.
Overriding Global Error Messages

You can override the default error messages for all validation tags globally.

package main

import (
	"fmt"
	"github.com/openframebox/govalidator"
)

type MySettings struct {
	Port int `validate:"required,gt=1024"`
}

func main() {
	v := govalidator.New(nil)

	// Override default messages
	v.OverrideErrorMessages(map[string]string{
		"required": "This field cannot be empty!",
		"gt":       "Value must be greater than :param.",
	})

	settings := &MySettings{Port: 80}
	if err := v.ValidateStruct(settings, nil); err != nil {
		fmt.Println("Validation Error:", err.Details)
		// Expected: map[Port:Value must be greater than 1024.]
	}
}

Contributing

We welcome contributions! Please see our CONTRIBUTING.md for more details.

License

govalidator is licensed under the MIT License. See the LICENSE file for details.

Documentation

Index

Constants

This section is empty.

Variables

View Source
var Default = New(nil)

Functions

func ToCamelCase

func ToCamelCase(str string) string

func ToHumanReadable

func ToHumanReadable(str string) string

func ToSnakeCase

func ToSnakeCase(str string) string

Types

type Config

type Config struct {
	NamespaceFormatter       func(field string) string
	FieldFormatter           func(field string) string
	GoStyleNamespaceSelector bool
}

type FieldMessages

type FieldMessages map[string]map[string]string

type StructWithFieldMessages

type StructWithFieldMessages interface {
	FieldMessages() FieldMessages
}

StructWithFieldMessages is an interface that can be implemented by a struct to provide custom field messages. Note that this interface is only used for first-level structs, not nested structs. The custom message for nested struct should be provided at the first-level struct FieldMessages method.

type ValidationError

type ValidationError struct {
	Message string                           `json:"error"`
	Details map[string]ValidationErrorDetail `json:"details,omitempty"`
}

func ValidateFiberRequestBody

func ValidateFiberRequestBody(ctx *fiber.Ctx, dst any, messages FieldMessages) *ValidationError

func ValidateStruct

func ValidateStruct(dst any, messages FieldMessages) *ValidationError

func (ValidationError) Error

func (e ValidationError) Error() string

type ValidationErrorDetail

type ValidationErrorDetail struct {
	Rule    string `json:"rule"`
	Message string `json:"message"`
	Value   any    `json:"value"`
}

type Validator

type Validator struct {
	// contains filtered or unexported fields
}

func New

func New(config *Config, options ...validator.Option) *Validator

func (*Validator) OverrideErrorMessages

func (v *Validator) OverrideErrorMessages(errorMessages map[string]string)

func (*Validator) RegisterCustomValidation

func (v *Validator) RegisterCustomValidation(tag string, fn validator.Func, callValidationEvenIfNull ...bool) error

func (*Validator) SetFieldFormatter

func (v *Validator) SetFieldFormatter(fieldFormatter func(field string) string)

func (*Validator) SetGoStyleNamespaceSelector

func (v *Validator) SetGoStyleNamespaceSelector(goStyleNamespaceSelector bool)

func (*Validator) SetNamespaceFormatter

func (v *Validator) SetNamespaceFormatter(namespaceFormatter func(field string) string)

func (*Validator) ValidateFiberRequestBody

func (v *Validator) ValidateFiberRequestBody(ctx *fiber.Ctx, dst any, messages FieldMessages) *ValidationError

func (*Validator) ValidateStruct

func (v *Validator) ValidateStruct(dst any, messages FieldMessages) *ValidationError

Directories

Path Synopsis
example
fiber command

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL