vld

package module
v0.0.0-...-63aff7d Latest Latest
Warning

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

Go to latest
Published: Aug 16, 2024 License: GPL-3.0 Imports: 8 Imported by: 0

README

VLD

A data validation library for GoLang.

Why not go-playground/validator?

There is no denying that validator is an amazing package and is the ecosystem standard. However there are some issues with it

  • Struct tags are used to define field validators. Since tags are nothing more than strings, this method can be very error-prone.
  • The validation errors are structured non user-friedly way. This makes it difficult to format the validation errors in such a way that they can be displayed on the front-end clients properly.
  • The mechanism for defining custom validators is not intuitive.

vld attempts to solve these problems.

Installation

Download the library

go get github.com/moeenn/vld

Basic usage

package main

import (
	"fmt"
	v "github.com/moeenn/vld"
)

type LoginForm struct {
	Email    string
	Password string
}

func main() {
	form := LoginForm{
		Email:    "admin-site.com",
		Password: "q1w2e3r4",
	}

	validations := []v.Validation{
		{
			Tag:   "email",
			Data:  form.Email,
			Rules: []v.Rule{v.NonEmptyString, v.Email},
		},
		{
			Tag:   "password",
			Data:  form.Password,
			Rules: []v.Rule{v.NonEmptyString, v.Min(8)},
		},
	}

	if err := v.Validate(validations); err != nil {
		validationErrors := err.(v.ValidationErrors)
		encoded, err := json.Marshal(validationErrors)
		if err != nil {
			fmt.Println("failed to encode errors")
			return
		}

		fmt.Printf("validation errors (JSON): %s\n", encoded)
		return
	}

	fmt.Println("validation successful")
}

Included validators

Validator Description
NonEmptyString Check if provided input is a non-empty string
Length(int) Check if the provided input is a string and its length is equal to the provided length.
Min(int | float | string) If the provided number is an int / float(64), check input is greater than or equal to the target. If the provided input is a string, check its length is more than or equal to the target.
Max(int | float | string) If the provided number is an int / float(64), check input is less than or equal to the target. If the provided input is a string, check its length is less than or equal to the target.
GreaterThan(int | float | string) If the provided number is an int / float(64), check input is more than (but not equal) to the target. If the provided input is a string, check its length is more than (but not equal) to the target.
LessThan(int | float | string) If the provided number is an int / float(64), check input is less than (but not equal) to the target. If the provided input is a string, check its length is less than (but not equal) to the target.
Email Check if the provide input is a valid email address
HasPrefix(string) Check if the provided input is a valid string and starts with the provided substring.
NotHasPrefix(string) Check if the provided input is a valid string and doesn't starts with the provided substring.
HasSuffix(string) Check if the provided input is a valid string and ends with the provided substring.
NotHasSuffix(string) Check if the provided input is a valid string and ends with the provided substring.
Equals(string) Check if the provided input is the same as the target input.
Enum(...string) Check if the provided input matches any of the listed enumerations values.
URL Check if the provided input is a valid string and a valid URL.
Regexp(string) Check if the provided input is a valid string and matches the required regular expression.
UUID Check if the provided input is a valid string and a valid UUID.
Password Check if the provided input is a valid string and a reasonably strong password. Password rules
- Minimum eight characters
- At least one uppercase letter
- One lowercase letter
- One number
- One special character
JSON Check if the provided code is a valid string and a valid json.
DateTime Check if the provided input is a valid string and a valid ISO timestamp according to RFC3339: Link.
Date Check if the provided input is a valid date-only string. Date string must be in format e.g. 2023-10-05. Link.
Time Check if the provided input is a valid string and a valid time-only string. Time string must be in 24-hours format: e.g. 10:20:00. Link.
DateEqual(time.Time) Check if the provided date is a date equal to the target date.
DateBefore(time.Time) Check if the provided input is a date before (but not equal) to the target date.
DateAfter(time.Time) Check if the provided input is a date after the target date. If inclusive is set to true, target date will be included.
Latitude Check if the provided input a valid map latitude value.
Longitude Check if the provided input a valid map longitude value.

Custom validators

In vld validators are plain functions. They can be defined as follows.

type ExampleForm struct {
	Slug string
}

func Slug(input any) (any, error) {
	err := errors.New("The input must be a valid slug")
	asString, ok := input.(string)
	if !ok {
		return nil, err
	}

	if strings.Contains(asString, "_") {
		return nil, err
	}
	return asString, nil
}

func main() {
	form := ExampleForm{
		Slug: "some-slug-here",
	}

	validations := []v.Validation{
		{
			Tag:   "slug",
			Data:  form.Slug,
			Rules: []v.Rule{v.NonEmptyString, Slug}, // notice the user-defined rule
		},
	}

	if err := v.Validate(validations); err != nil {
		validationErrors := err.(v.ValidationErrors)
		fmt.Printf("validation errors: %v\n", validationErrors.Errors)
		return
	}
	
	// the input data is valid
    ...	
}

If the custom validator function required additional arguments, they can be defined as follows.

func StartsWith(prefix string) Rule {
	return func(input any) (any, error) {
		err := fmt.Errorf("The input must start with '%s'", prefix)
		asString, ok := input.(string)
		if !ok || !strings.HasPrefix(asString, prefix) {
			return nil, err
		}
		return asString, nil
	}
}

TODO

  • Extend LessThan: Add option to check BeforeTime
  • Extend Equals: Add option to check ExactTime
  • Array
    • Extend Min to allow checking MinItems
    • Extend Max to allow checking MaxItems

Documentation

Index

Constants

View Source
const (
	PATTERN_EMAIL = `^[^@]+@[^@]+\.[^@]+$`
	PATTERN_UUID  = `^[a-f\d]{8}(-[a-f\d]{4}){4}[a-f\d]{8}$`

	// Password strength rules:
	// - Minimum eight characters
	// - At least one uppercase letter
	// - One lowercase letter
	// - One number
	// - One special character
	// **Note**: the pattern will match weak passwords instead of strong passwords
	PATTERN_PASSWORD_STRENGTH = `^(.{0,7}|[^0-9]*|[^A-Z]*|[^a-z]*|[a-zA-Z0-9]*)$`
)
View Source
const (
	CODE_UNKNOWN          = "unknown"
	CODE_NON_EMPTY_STRING = "non-empty-string"
	CODE_LENGTH           = "length"
	CODE_MIN              = "min"
	CODE_MAX              = "max"
	CODE_LESS_THAN        = "less-than"
	CODE_EMAIL            = "email"
	CODE_HAS_PREFIX       = "has-prefix"
	CODE_HAS_SUFFIX       = "has-suffix"
	CODE_NOT_HAS_PREFIX   = "not-has-prefix"
	CODE_NOT_HAS_SUFFIX   = "not-has-suffix"
	CODE_EQUALS           = "equals"
	CODE_ENUM             = "enum"
	CODE_URL              = "url"
	CODE_REGEXP           = "regexp"
	CODE_UUID             = "uuid"
	CODE_PASSWORD         = "password"
	CODE_JSON             = "json"
	CODE_DATE_TIME        = "date-time"
	CODE_DATE             = "date"
	CODE_TIME             = "time"
	CODE_DATE_EQUAL       = "date-equal"  // TODO: merge with `Equals`
	CODE_DATE_BEFORE      = "date-before" // TODO: merge with `LessThan`
	CODE_DATE_AFTER       = "date-after"  // TODO: merge with `GreaterThan`
	CODE_LATITUDE         = "latitude"
	CODE_LONGITUDE        = "longitude"
)

Variables

This section is empty.

Functions

func Date

func Date(input any) (any, error)

Date check if the provided input is a valid date-only string. Date string must be in format e.g. 2023-10-05 [Link](https://pkg.go.dev/time#pkg-constants).

func DateTime

func DateTime(input any) (any, error)

DateTime check if the provided input is a valid string and a valid ISO timestamp according to RFC3339: [Link](https://pkg.go.dev/time#pkg-constants).

func Email

func Email(input any) (any, error)

Email check if the provide input is a valid email address.

func JSON

func JSON(input any) (any, error)

JSON check if the provided code is a valid string and a valid json.

func Latitude

func Latitude(input any) (any, error)

Latitude check if the provided input a valid map latitude value.

func Longitude

func Longitude(input any) (any, error)

Longitude check if the provided input a valid map longitude value.

func NonEmptyString

func NonEmptyString(input any) (any, error)

NonEmptyString check if provided input is a non-empty string.

func Password

func Password(input any) (any, error)

Password check if the provided input is a valid string and a reasonably strong password.

func Time

func Time(input any) (any, error)

Time check if the provided input is a valid string and a valid time-only string. Time string must be in 24-hours format: e.g. 10:20:00 [Link](https://pkg.go.dev/time#pkg-constants).

func URL

func URL(input any) (any, error)

URL check if the provided input is a valid string and a valid URL.

func UUID

func UUID(input any) (any, error)

UUID check if the provided input is a valid string and a valid UUID.

func Validate

func Validate(validations []Validation) error

Types

type Issue

type Issue struct {
	Code    string
	Message string
	Value   any
}

func (Issue) Error

func (issue Issue) Error() string

type IssueDTO

type IssueDTO struct {
	Code    string `json:"code"`
	Message string `json:"message"`
	Value   any    `json:"value"`
}

The `Issue` struct implements the error interface, which makes it tricky to serialize. This `IssueDTO` struct is required because we do need serialization of the issues.

type Rule

type Rule func(any) (any, error)

func DateAfter

func DateAfter(target time.Time, inclusive bool) Rule

DateAfter check if the provided input is a date after the target date. If inclusive is set to true, target date will be included. TODO: merge into GreaterThan

func DateBefore

func DateBefore(target time.Time, inclusive bool) Rule

DateBefore check if the provided input is a date before (but not equal) to the target date. TODO: merge into LessThan

func DateEqual

func DateEqual(target time.Time) Rule

DateEqual check if the provided date is a date equal to the target date.

func Enum

func Enum(enumValues ...string) Rule

Enum check if the provided input matches any of the listed enumerations values TODO: allow numeric enums has well

func Equals

func Equals(targetName string, targetValue any) Rule

Equals check if the provided input is the same as the required input. TODO: merge DateEquals into this TODO: extend to allow comparison of numbers

func GreaterThan

func GreaterThan(target any) Rule

GreaterThan if the provided number is an int / float(64), check input is more than (but not equal) to the target. If the provided input is a string, check its length is more than (but not equal) to the target.

func HasPrefix

func HasPrefix(prefix string) Rule

HasPrefix check if the provided input is a valid string and starts with the provided substring.

func HasSuffix

func HasSuffix(suffix string) Rule

HasSuffix check if the provided input is a valid string and ends with the provided substring.

func Length

func Length(length int) Rule

Length check if the provided input is a string and its length is equal to the provided length.

func LessThan

func LessThan(target any) Rule

LessThan if the provided number is an int / float(64), check input is less than (but not equal) to the target. If the provided input is a string, check its length is less than (but not equal) to the target.

func Max

func Max(target any) Rule

Max if the provided number is an int / float(64), check input is less than or equal to the target. If the provided input is a string, check its length is less than or equal to the target.

func Min

func Min(target any) Rule

Min if the provided number is an int / float(64), check input is greater than or equal to the target. If the provided input is a string, check its length is more than or equal to the target.

func NotHasPrefix

func NotHasPrefix(prefix string) Rule

NotHasPrefix check if the provided input is a valid string and doesn't starts with the provided substring.

func NotHasSuffix

func NotHasSuffix(suffix string) Rule

NotHasSuffix check if the provided input is a valid string and ends with the provided substring.

func Regexp

func Regexp(pattern string) Rule

Regexp check if the provided input is a valid string and matches the required regular expression.

type Validation

type Validation struct {
	Tag   string
	Data  any
	Rules []Rule
}

type ValidationErrors

type ValidationErrors struct {
	Errors map[string]IssueDTO `json:"errors"`
}

func (ValidationErrors) Error

func (v ValidationErrors) Error() string

Jump to

Keyboard shortcuts

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