Categorygithub.com/cohesivestack/valgo
repositorypackage
0.7.0
Repository: https://github.com/cohesivestack/valgo.git
Documentation: pkg.go.dev

# Packages

No description provided by the author

# README

Valgo

Valgo is a type-safe, expressive, and extensible validator library for Golang. Valgo is built with generics, so Go 1.18 or higher is required.

Valgo differs from other Golang validation libraries in that the rules are written in functions and not in struct tags. This allows greater flexibility and freedom when it comes to where and how data is validated.

Additionally, Valgo supports customizing and localizing validation messages.

Quick example

Here is a quick example:

package main

import v "github.com/cohesivestack/valgo"

func main() {
  val := v.Is(
    v.String("Bob", "full_name").Not().Blank().OfLengthBetween(4, 20),
    v.Number(17, "age").GreaterThan(18),
  )

  if !val.Valid() {
    out, _ := json.MarshalIndent(val.ToError(), "", "  ")
    fmt.Println(string(out))
  }
}

output:

{
  "age": [
    "Age must be greater than \"18\""
  ],
  "full_name": [
    "Full name must have a length between \"4\" and \"20\""
  ]
}

v0.x.x and backward compatibility

Valgo is in its early stages, so backward compatibility won't be guaranteed until v1.

Valgo is used in production by Statsignal, but we want community feedback before releasing version 1.

๐Ÿšจ Breaking Changes

v0.6.0 โ€” String Length Validation

Starting from v0.6.0, all string length validators now measure length in characters (runes) instead of bytes. This means that multi-byte UTF-8 characters (such as Japanese/Chinese/Korean characters, accented letters, and other international characters) are now counted as one character each, making the validators more intuitive for international (i18n) applications.

What Changed

  • MaxLength, MinLength, OfLength, and OfLengthBetween now use utf8.RuneCountInString.

v0.7.0 โ€” Numeric Validators: Codegen removed โ†’ one generic per family

We removed code generation for numeric validators and replaced it with type-safe generics. Each numeric family now has one generic validator type that replaces all the previous width-specific types.

Replacements (numeric families)

Signed integers

  • New: ValidatorInt[T ~int | ~int8 | ~int16 | ~int32 | ~int64] Replaces: ValidatorInt, ValidatorInt8, ValidatorInt16, ValidatorInt32, ValidatorInt64

Unsigned integers

  • New: ValidatorUint[T ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64] Replaces: ValidatorUint, ValidatorUint8, ValidatorUint16, ValidatorUint32, ValidatorUint64

Floats

  • New: ValidatorFloat[T ~float32 | ~float64] Replaces: ValidatorFloat32, ValidatorFloat64

Impact (when you declare variables/params)

Most call sites donโ€™t changeโ€”constructors like v.Int16(...), v.Uint64(...), v.Float32(...) still exist and now return the generic validators. You only need to update declared types of variables/parameters.

Before (pre-v0.7.0)

age := int16(10)
var validatorAge ValidatorInt16
validatorAge = v.Int16(age).EqualTo(18)

After (v0.7.0+)

age := int16(10)
var validatorAge *ValidatorInt[int16]
validatorAge = v.Int16(age).EqualTo(18)

Not affected

  • Non-numeric validators (e.g., string/time/etc.) are unchanged.

Migration

If your code relied on byte length (using len semantics), use the new explicit byte-based validators:

  • MaxBytes
  • MinBytes
  • OfByteLength
  • OfByteLengthBetween

Example

s := "ไฝ ๅฅฝ" // 2 characters (runes), 6 bytes
japanese := "ใ“ใ‚“ใซใกใฏ" // 5 characters (runes), 15 bytes

// New default: counts characters (runes)
v.String(s, "field").MaxLength(2)        // โœ… passes
v.String(japanese, "field").MaxLength(5) // โœ… passes

// Byte-based: counts bytes
v.String(s, "field").MaxBytes(6)         // โœ… passes
v.String(japanese, "field").MaxBytes(15) // โœ… passes

Table of content

Getting started

Install in your project:

go get github.com/cohesivestack/valgo

Import in your code:

import v github.com/cohesivestack/valgo

Note: You can use any other aliases instead of v or just reference the package valgo directly.

Using Valgo

Validation session

The Validation session in Valgo is the main structure for validating one or more values. It is called 'Validation' in code.

A validation session will contain one or more Validators, where each Validator will have the responsibility to validate a value with one or more rules.

There are multiple functions to create a Validation session, depending on the requirements:

  • New(),
  • Is(...),
  • In(...),
  • InRow(...),
  • InCell(...),
  • Check(...),
  • AddErrorMessage(...)

Is(...) is likely to be the most frequently used function in your validations. When Is(...) is called, the function creates a validation and receives a validator at the same time. In the next section, you will learn more about the Is(...) function.

Is(...) function

The Is(...) function allows you to pass one or multiple Validators, each with their respective values and rules for validation. This creates a Validation session, which can be used to validate multiple values.

In the following example, we pass multiple Validators for the full_name, age, and status values to the Is(...) function:

val := v.Is(
  v.String("Bob", "full_name").Not().Blank().OfLengthBetween(4, 20),
  v.Number(17, "age").GreaterThan(18),
  v.String("singl", "status").InSlice([]string{"married", "single"})
)

if !val.Valid() {
  out, _ := json.MarshalIndent(val.ToError(), "", "  ")
  fmt.Println(string(out))
}

output:

{
  "age": [
    "Age must be greater than \"18\""
  ],
  "full_name": [
    "Full name must have a length between \"4\" and \"20\""
  ],
  "status": [
    "Status is not valid"
  ]
}

Validation.Valid() function

A Validation session provides this function, which returns either true if all their validators are valid or false if any one of them is invalid.

In the following example, even though the Validator for age is valid, the Validator for status is invalid, making the entire Validator session invalid.

val := v.Is(
  v.Number(21, "age").GreaterThan(18),
  v.String("singl", "status").InSlice([]string{"married", "single"}),
)

if !val.Valid() {
  out, _ := json.MarshalIndent(val.ToError(), "", "  ")
  fmt.Println(string(out))
}

output:

{
  "status": [
    "Status is not valid"
  ]
}

Validation.IsValid(...) function

This function allows checking if a specific value in a Validation session is valid or not. This is very useful for conditional logic.

The following example prints an error message if the age value is invalid.

val := v.Is(v.Number(16, "age").GreaterThan(18)).
  Is(v.String("single", "status").InSlice([]string{"married", "single"}))

if !val.IsValid("age") {
  fmt.Println("Warning: someone underage is trying to sign up")
}

output:

Warning: someone underage is trying to sign up

In(...) function

The In(...) function executes one or more validators in a namespace, so the value names in the error result are prefixed with this namespace. This is useful for validating nested structures.

In the following example we are validating the Person and the nested Address structure. We can distinguish the errors of the nested Address structure in the error results.

type Address struct {
  Name string
  Street string
}

type Person struct {
  Name string
  Address Address
}

p := Person{"Bob", Address{"", "1600 Amphitheatre Pkwy"}}

val := v.
  Is(v.String(p.Name, "name").OfLengthBetween(4, 20)).
  In("address", v.Is(
    String(p.Address.Name, "name").Not().Blank(),
    String(p.Address.Street, "street").Not().Blank(),
  ))

if !val.Valid() {
  out, _ := json.MarshalIndent(val.ToError(), "", "  ")
  fmt.Println(string(out))
}

output:

{
  "address.name": [
    "Name can't be blank"
  ],
  "name": [
    "Name must have a length between \"4\" and \"20\""
  ]
}

InRow(...) function

The InRow(...) function executes one or more validators in a namespace similar to the In(...) function, but with indexed namespace. So, the value names in the error result are prefixed with this indexed namespace. It is useful for validating nested lists in structures.

In the following example we validate the Person and the nested list Addresses. The error results can distinguish the errors of the nested list Addresses.

type Address struct {
  Name   string
  Street string
}

type Person struct {
  Name      string
  Addresses []Address
}

p := Person{
  "Bob",
  []Address{
    {"", "1600 Amphitheatre Pkwy"},
    {"Home", ""},
  },
}

val := v.Is(String(p.Name, "name").OfLengthBetween(4, 20))

for i, a := range p.Addresses {
  val.InRow("addresses", i, v.Is(
    v.String(a.Name, "name").Not().Blank(),
    v.String(a.Street, "street").Not().Blank(),
  ))
}

if !val.Valid() {
  out, _ := json.MarshalIndent(val.ToError(), "", "  ")
  fmt.Println(string(out))
}

output:

{
  "addresses[0].name": [
    "Name can't be blank"
  ],
  "addresses[1].street": [
    "Street can't be blank"
  ],
  "name": [
    "Name must have a length between \"4\" and \"20\""
  ]
}

InCell(...) function

The InCell(...) function executes one or more validators in an indexed namespace where the target is a scalar value (e.g., entries of a primitive slice). The value names in the error result are prefixed with this indexed namespace. It is useful for validating lists of primitive values.

In the following example, we validate a list of tag names. The error results can distinguish the errors for each list entry.

tags := []string{"", ""}

val := v.New()
for i, tag := range tags {
  val.InCell("tags", i, v.Is(
    v.String(tag, "name").Not().Blank(),
  ))
}

if !val.Valid() {
  out, _ := json.MarshalIndent(val.ToError(), "", "  ")
  fmt.Println(string(out))
}

output:

{
  "tags[0]": [
    "Name can't be blank"
  ],
  "tags[1]": [
    "Name can't be blank"
  ]
}

Check(...) function

The Check(...) function, similar to the Is(...) function, however with Check(...) the Rules of the Validator parameter are not short-circuited, which means that regardless of whether a previous rule was valid, all rules are checked.

This example shows two rules that fail due to the empty value in the full_name Validator, and since the Validator is not short-circuited, both error messages are added to the error result.

val := v.Check(v.String("", "full_name").Not().Blank().OfLengthBetween(4, 20))

if !val.Valid() {
  out, _ := json.MarshalIndent(val.ToError(), "", "  ")
  fmt.Println(string(out))
}

output:

{
  "full_name": [
    "Full name can't be blank",
	  "Full name must have a length between \"4\" and \"20\""
  ]
}

If(...) function

The If(...) function is similar to Merge(...), but merges the Validation session only when the condition is true, and returns the same Validation instance. When the condition is false, no operation is performed and the original instance is returned unchanged.

This function allows you to write validation code in a more fluent and compact way, especially useful for conditional merging of validation sessions without the need for separate if statements or complex branching logic.


// Only merge admin validation if user is admin
val := v.
  Is(v.String(username, "username").Not().Blank()).
  If(isAdmin, v.Is(v.String(role, "role").EqualTo("admin")))

if !val.Valid() {
  out, _ := json.MarshalIndent(val.ToError(), "", "  ")
  fmt.Println(string(out))
}

Do(...) function

The Do(...) function executes the given function with the current Validation instance and returns the same instance. This allows you to extend a validation chain with additional or conditional rules in a concise way.

val := v.
  Is(v.String(username, "username").Not().Blank()).
  Do(func(val *v.Validation) {
    if isAdmin {
      val.Is(v.String(role, "role").EqualTo("admin"))
    }
  })

if !val.Valid() {
  out, _ := json.MarshalIndent(val.ToError(), "", "  ")
  fmt.Println(string(out))
}

When(...) function

The When(...) function is similar to Do(...), but executes the given function only when the condition is true, and returns the same Validation instance. When the condition is false, no operation is performed and the original instance is returned unchanged.

This function provides a more concise way to add conditional validation logic compared to using Do(...) with an internal if statement.

val := v.
  Is(v.String(username, "username").Not().Blank()).
  When(isAdmin, func(val *v.Validation) {
    val.Is(v.String(role, "role").EqualTo("admin"))
  })

if !val.Valid() {
  out, _ := json.MarshalIndent(val.ToError(), "", "  ")
  fmt.Println(string(out))
}

AddErrorMessage(...) function

The AddErrorMessage function allows to add an error message to a Validation session without executing a field validator. This function takes in two arguments: name, which is the name of the field for which the error message is being added, and message, which is the error message being added to the session.

When an error message is added using this function, the Validation session is marked as invalid, indicating that at least one validation error has occurred.

One use case for the AddErrorMessage function is to add a general error message for the validation of an entity structure. As shown in the example below, if you have an entity structure for an address and need to validate multiple fields within it, such as the city and street, you can use AddErrorMessage to include a general error message for the entire address in case any of the fields fail validation.

type Address struct {
  City string
  Street string
}

a := Address{"", "1600 Amphitheatre Pkwy"}

val := v.Is(
  v.String(a.city, "city").Not().Blank(),
  v.String(a.Street, "street").Not().Blank(),
)

if !val.Valid() {
  v.AddErrorMessage("address", "The address is wrong!")

  out, _ := json.MarshalIndent(val.ToError(), "", "  ")
  fmt.Println(string(out))
}

output:

{
  "address": [
    "The address is wrong!"
  ],
  "city": [
    "City can't be blank"
  ]
}

It's worth noting that there may be other use cases for this function as well.

Merging two Validation sessions with Validation.Merge( ... )

Using Merge(...) you can merge two Validation sessions. When two validations are merged, errors with the same value name will be merged. It is useful for reusing validation logic.

The following example merges the Validation session returned by the validatePreStatus function. Since both Validation sessions validate a value with the name status, the error returned will return two error messages, and without duplicate the Not().Blank() error message rule.


type Record struct {
  Name   string
  Status string
}

validatePreStatus := func(status string) *Validation {
  regex, _ := regexp.Compile("pre-.+")

  return v.
    Is(v.String(status, "status").Not().Blank().MatchingTo(regex))
}

r := Record{"Classified", ""}

val := v.Is(
  v.String(r.Name, "name").Not().Blank(),
  v.String(r.Status, "status").Not().Blank(),
)

val.Merge(validatePreStatus(r.Status))

if !val.Valid() {
  out, _ := json.MarshalIndent(val.ToError(), "", "  ")
  fmt.Println(string(out))
}

output:

{
  "status": [
	  "Status can't be blank",
	  "Status must match to \"pre-.+\""
  ]
}

New() function

This function allows you to create a new Validation session without a Validator. This is useful for conditional validation or reusing validation logic.

The function accepts an optional parameter of type [Options] struct, which allows you to specify options such as the specific locale code and locale to use, and a custom JSON marshaler for errors.

The following example conditionally adds a Validator rule for the month_day value.

month := 5
monthDay := 11

val := v.New()

if month == 6 {
  val.Is(v.Number(monthDay, "month_day").LessOrEqualTo(10))
}

if val.Valid() {
  fmt.Println("The validation passes")
}

output:

The validation passes

As we mentioned above, you can pass the Options type to the New() function, in order to specify additional options when creating a new Validation session, such as the specific locale code and locale to use, and a custom JSON marshaler for errors. More information about the Options parameter in the following sections.

Error handling functions

Valgo provides three functions for handling validation errors:

ToError() function

The ToError() function returns the same value as ToValgoError() but as a standard Go error interface. This function is ideal for idiomatic error handling and integration with Go's native error system.

val := v.Is(v.String("", "name").Not().Blank())

if err := val.ToError(); err != nil {
    log.Printf("Validation failed: %v", err)
    return err
}

ToValgoError() function

The ToValgoError() function returns the same value as ToError() but as a concrete *valgo.Error type instead of the standard error interface. It's essentially a shortcut to ToError().(*valgo.Error), providing access to rich, structured error details.

val := v.Is(v.String("", "name").Not().Blank())

if errInfo := val.ToValgoError(); errInfo != nil {
    for field, valueError := range errInfo.Errors() {
        fmt.Printf("Field '%s': %v\n", field, valueError.Messages())
    }
}

Error() function (Deprecated)

The Error() function is deprecated in favor of ToError() or ToValgoError(). The Error() function name conflicts with Go's error interface implementation convention, where Error() typically implements the error interface for a type. This function will be removed when Valgo reaches version 1.

// DEPRECATED: Use ToError() or ToValgoError() instead
val := v.Is(v.String("", "name").Not().Blank())
if !val.Valid() {
    out, _ := json.MarshalIndent(val.Error(), "", "  ")
    fmt.Println(string(out))
}

When to use each function

  • Use ToError() for standard error handling and integration with Go's error system
  • Use ToValgoError() when you need detailed validation information, per-field messages, or custom error processing
  • Avoid Error() as it's deprecated and may be removed when Valgo reaches version 1

Custom JSON marshaling

All three functions support custom JSON marshaling functions:

customFunc := func(e *Error) ([]byte, error) {
    return []byte(`{"custom": "error"}`), nil
}

// Using custom marshaling with any of the functions
err := val.ToError(customFunc)
errInfo := val.ToValgoError(customFunc)

Additional JSON marshaling methods

Valgo provides additional JSON marshaling methods for enhanced error output:

MarshalJSONIndent() function

The MarshalJSONIndent() function provides custom JSON indentation for error output:

val := v.Is(v.String("", "name").Not().Blank())

if err := val.ToValgoError(); err != nil {
    jsonData, _ := err.MarshalJSONIndent("  ", "  ")
    fmt.Println(string(jsonData))
}

MarshalJSONPretty() function

The MarshalJSONPretty() function provides pretty-printed JSON output for better readability:

val := v.Is(v.String("", "name").Not().Blank())

if err := val.ToValgoError(); err != nil {
    jsonData, _ := err.MarshalJSONPretty()
    fmt.Println(string(jsonData))
}

Custom error message template

Customizing the default Valgo error messages is possible through the New() function as it's explained in the Localizing a validation session with New section, however the Valgo validators allow to customize the template of a specific template validator rule. Below is an example illustrating this with the String empty validator rule.

val := v.Is(v.String("", "address_field", "Address").Not().Empty("{{title}} must not be empty. Please provide the value in the input {{name}}."))

out, _ := json.MarshalIndent(val.ToError(), "", "  ")
fmt.Println(string(out))

output:

{
  "address": [
         "Address must not be empty. Please provide the value in the input address_field."
  ]
}

Localizing a validation session with New(...options) function

Valgo has localized error messages. The error messages are currently available in English (default), Spanish, German and Hungarian. However, it is possible to set error messages for any locale by passing the Options parameter to the New() function. Using this parameter, you can also customize the existing Valgo locale messages.

There are two options for localization: localeCode and locale. Below, we list the different ways to customize localization with these two parameters.

  • Changing the validation session's locale In the following example, we are setting the Spanish locale:

    // Creating the new validation session with other locale
    val := v.New(v.Options{ LocaleCode: "es" })
    
    // Testing the output
    val := val.Check(v.String(" ", "nombre").Not().Blank())
    
    out, _ := json.MarshalIndent(val.ToError(), "", "  ")
    fmt.Println(string(out))
    

    output:

    {
      "name": [
        "Nombre no puede estar en blanco"
      ]
    }
    

    If the specified locale does not exist, Valgo's default English locale will be used. If you wish to change the default locale, you should use a Factory function, which is explained in the Factory section.

  • Changing the locale entries In the example below, we are changing the entry for the "Not Blank" error. Since we are not specifying the localeCode, we are using and replacing the default English locale. However, you can also specify another localeCode if necessary.

    // Creating a new validation session and changing a locale entry
    val := v.New(v.Options{
      Locale: &Locale{
      	  ErrorKeyNotBlank: "{{title}} should not be blank",
        }
    })
    
    // Testing the output
    val := val.Check(v.String(" ", "name").Not().Blank())
    
    out, _ := json.MarshalIndent(val.ToError(), "", "  ")
    fmt.Println(string(out))
    

    output:

    {
      "name": [
        "Name should not be blank"
      ]
    }
    
  • Adding a new locale As mentioned previously, Valgo currently only has the English, Spanish, German and Hungarian locales, but we hope to have more soon. However, you can add your own custom locale. Below is an example using the Estonian language:

    // Creating a new validation session and adding a new locale with two entries
    val := v.New(v.Options{
      LocaleCode: "ee",
      Locale: &Locale{
      	  ErrorKeyNotBlank: "{{title}} ei tohi olla tรผhi",
        ErrorKeyNotFalse: "{{title}} ei tohi olla vale",
        }
    })
    
    // Testing the output
    val := val.Is(
      v.String(" ", "name").Not().Blank(),
      v.Bool(false, "active").Not().False(),
    )
    
    out, _ := json.MarshalIndent(val.ToError(), "", "  ")
    fmt.Println(string(out))
    

    output:

    {
      "name": [
        "Name ei tohi olla tรผhi"
      ],
      "active": [
        "Active ei tohi olla vale"
      ]
    }
    

    For entries not specified in the custom locale, the default Valgo locale (English) will be used. If you wish to change the default locale, you can use the Factory function, which is further explained in the Factory section.

We welcome pull requests for adding new locale messages, but please ensure that the translations are of high quality.

Managing common options with Factory

Valgo provides the Factory() function which allows you to create a valgo factory. With a valgo factory, you can create Validation sessions with preset options, avoiding having to pass options each time when a Validation is created. This allows more flexibility and easier management of options when creating Validation sessions.

The Factory function takes a parameter of type FactoryOptions struct, which allows you to modify the default locale code, add new locales, and set a custom JSON marshaler for errors. The ValidationFactory instance created by this function has all the functions to create Validations available in the package level (Is(), In(), Check(), New()) which creates a new Validation session with the preset options in the factory.

In the following example, we create a Factory with the default locale code set to Spanish, a new locale added for Estonian. This factory instance enables us to create validation sessions.

factory := v.Factory(v.FactoryOptions{
  LocaleCodeDefault: "es",
  Locales: map[string]*Locale{
      "ee": {
          v.ErrorKeyNotBlank: "{{title}} ei tohi olla tรผhi",
          v.ErrorKeyNotFalse: "{{title}} ei tohi olla vale",
      },
  }
})

// Error will contain the spanish error "Nombre no puede estar en blanco"
v1 := factory.Is(String(" ", "nombre").NotBlank())

// Error will contain the spanish error "Nime ei tohi olla tรผhi"
v2 := factory.New(Options{LocaleCode: "ee"}).Is(String(" ", "nime").Not().Blank())

Custom errors JSON output with Factory

It is possible to use the MarshalJsonFunc parameter of Factory for customizing the JSON output for errors.

customMarshalJson := func(e *Error) ([]byte, error) {

  errors := map[string]interface{}{}

  for k, v := range e.errors {
    errors[k] = v.Messages()
  }

  // Add a root key level called errors, which is not set by default in the Valgo implementation.
  return json.Marshal(map[string]map[string]interface{}{"errors": errors})
}

// Set the custom Marshal JSON function
factory := v.Factory(v.FactoryOptions{
  MarshalJsonFunc: customMarshalJson
})

// Now validate something to check if the output JSON contains the errors root key

val := factory.Is(v.String("", "name").Not().Empty())

out, _ := json.MarshalIndent(val.ToError(), "", "  ")
fmt.Println(string(out))

output:

{
  "errors": {
    "name": [
      "Name can't be empty"
    ]
  }
}

Validators

Validators establish the rules for validating a value. The validators are passed to a Validation session.

For each primitive Golang value type, Valgo provides a Validator. A Validator has different functions that set its value's validation rules.

Although Valgo has multiple types of validators, it can be extended with custom validators. Check the section Extending Valgo with Custom Validators for more information.

Validator value's name and title.

Validators only require the value to be validated, so, for example, the following code validates a string value by checking if it is empty.

val := v.New(v.String("").Empty())

val.ToError() output:

{
  "value_0": [
	  "Value 0 can't be empty",
  ]
}

In the example above, since we didn't specify a name for the value, Valgo generates a value_0 name and consequently the Value 0 title in the error message.

However, Validators allow you, optionally, to specify the value's name and title, as shown below:

Validator with value's name:

val := v.New(v.String("", "company_name").Not().Empty())

val.ToError() output:

{
  "company_name": [
	  "Company name can't be empty",
  ]
}

Validator with value's name and title:

val := v.New(v.String("", "company_name", "Customer").Not().Empty())

val.ToError() output:

{
  "company_name": [
	  "Customer can't be empty",
  ]
}

Not() validator function

Valgo validators have a Not() function to invert the boolean value associated with the next validator rule function.

In the following example, the call to Valid() will return false because Not() inverts the boolean value associated with the Zero() function.

valid := Is(v.Number(0).Not().Zero()).Valid()

fmt.Println(valid)

output:

false

String validator

The ValidatorString provides functions for setting validation rules for a string type value, or a custom type based on a string.

Below is a valid example for every String validator rule.

v.Is(v.String("Dennis Ritchie").EqualTo("Dennis Ritchie"))
v.Is(v.String("Steve Jobs").GreaterThan("Bill Gates"))
v.Is(v.String("Steve Jobs").GreaterOrEqualTo("Elon Musk"))
v.Is(v.String("C#").LessThan("Go"))
v.Is(v.String("Go").LessOrEqualTo("Golang"))
v.Is(v.String("Rust").Between("Go", "Typescript")) // Inclusive
v.Is(v.String("").Empty())
v.Is(v.String(" ").Blank())
v.Is(v.String("Dart").Passing(func(val string) bool { return val == "Dart" }))
v.Is(v.String("processing").InSlice([]string{"idle", "processing", "ready"}))

// Byte-length based
v.Is(v.String("123456").MaxBytes(6))
v.Is(v.String("123").MinBytes(3))
v.Is(v.String("1234").OfByteLength(4))
v.Is(v.String("12345").OfByteLengthBetween(4,6)) // Inclusive

// Rune-length based (unicode code points, tends to matter for languages that use non-Latin alphabet)
// ่™Ž่ฆ–็œˆใ€… is 4 runes/characters, but len(x) = 12 bytes
v.Is(v.String("่™Ž่ฆ–็œˆใ€…").MaxLength(4))
v.Is(v.String("่™Ž่ฆ–็œˆใ€…").MinLength(4))
v.Is(v.String("่™Ž่ฆ–็œˆใ€…").OfLength(4))
v.Is(v.String("่™Ž่ฆ–็œˆใ€…").OfLengthBetween(2,4)) // Inclusive

regex, _ := regexp.Compile("pre-.+"); v.Is(String("pre-approved").MatchingTo(regex))

String pointer validator

The ValidatorStringP provides functions for setting validation rules for a string type pointer, or a custom type based on a string pointer.

Below is a valid example for every String pointer validator rule.

x := "Dennis Ritchie"; v.Is(v.StringP(&x).EqualTo("Dennis Ritchie"))
x := "Steve Jobs";     v.Is(v.StringP(&x).GreaterThan("Bill Gates"))
x := "Steve Jobs";     v.Is(v.StringP(&x).GreaterOrEqualTo("Elon Musk"))
x := "C#";             v.Is(v.StringP(&x).LessThan("Go"))
x := "Go";             v.Is(v.StringP(&x).LessOrEqualTo("Golang"))
x := "Rust";           v.Is(v.StringP(&x).Between("Go", "Typescript")) // Inclusive
x := "";               v.Is(v.StringP(&x).Empty())
x := " ";              v.Is(v.StringP(&x).Blank())
x := "Dart";           v.Is(v.StringP(&x).Passing(func(val *string) bool { return *val == "Dart" }))
x := "processing";     v.Is(v.StringP(&x).InSlice([]string{"idle", "processing", "ready"}))

// Byte-length based
x := "123456";         v.Is(v.StringP(&x).MaxBytes(6))
x := "123";            v.Is(v.StringP(&x).MinBytes(3))
x := "1234";           v.Is(v.StringP(&x).OfByteLength(4))
x := "12345";          v.Is(v.StringP(&x).OfByteLengthBetween(4,6)) // Inclusive

// Rune-length based (counts characters instead of bytes)
// ่™Ž่ฆ–็œˆใ€… is 4 runes/characters, but len(x) = 12 bytes
x := "่™Ž่ฆ–็œˆใ€…";      v.Is(v.StringP(&x).MaxLength(4))
x := "่™Ž่ฆ–็œˆใ€…";      v.Is(v.StringP(&x).MinLength(4))
x := "่™Ž่ฆ–็œˆใ€…";      v.Is(v.StringP(&x).OfLength(4))
x := "่™Ž่ฆ–็œˆใ€…";      v.Is(v.StringP(&x).OfLengthBetween(2,4)) // Inclusive

x := "pre-approved"; regex, _ := regexp.Compile("pre-.+"); v.Is(StringP(&x).MatchingTo(regex))
x := "";               v.Is(v.StringP(&x).EmptyOrNil())
x := " ";              v.Is(v.StringP(&x).BlankOrNil())
var x *string;         v.Is(v.StringP(x).Nil())

Number validator

The Number validator provides functions for setting validation rules for a TypeNumber value, or a custom type based on a TypeNumber.

TypeNumber is a generic interface defined by Valgo that generalizes any standard Golang type. Below is Valgo's definition of TypeNumber:

type TypeNumber interface {
  ~int |
  ~int8 |
  ~int16 |
  ~int32 |
  ~int64 |
  ~uint |
  ~uint8 |
  ~uint16 |
  ~uint32 |
  ~uint64 |
  ~float32 |
  ~float64
}

Below is a valid example for every Number validator rule.

v.Is(v.Number(10).EqualTo(10))
v.Is(v.Number(11).GreaterThan(10))
v.Is(v.Number(10).GreaterOrEqualTo(10))
v.Is(v.Number(10).LessThan(11))
v.Is(v.Number(10).LessOrEqualTo(10))
v.Is(v.Number(11).Between(10, 12)) // Inclusive
v.Is(v.Number(0).Zero())
v.Is(v.Number(10).Passing(func(val int) bool { return val == 10 }))
v.Is(v.Number(20).InSlice([]int{10, 20, 30}))

Number pointer validator

The Number pointer validator provides functions for setting validation rules for a TypeNumber pointer, or a custom type based on a TypeNumber pointer.

As it's explained in Number validator, the TypeNumber is a generic interface defined by Valgo that generalizes any standard Golang type.

Below is a valid example for every Number pointer validator rule.

x := 10;    v.Is(v.NumberP(&x).EqualTo(10))
x := 11;    v.Is(v.NumberP(&x).GreaterThan(10))
x := 10;    v.Is(v.NumberP(&x).GreaterOrEqualTo(10))
x := 10;    v.Is(v.NumberP(&x).LessThan(11))
x := 10;    v.Is(v.NumberP(&x).LessOrEqualTo(10))
x := 11;    v.Is(v.NumberP(&x).Between(10, 12)) // Inclusive
x := 0;     v.Is(v.NumberP(&x).Zero())
x := 10;    v.Is(v.NumberP(&x).Passing(func(val *int) bool { return *val == 10 }))
x := 20;    v.Is(v.NumberP(&x).InSlice([]int{10, 20, 30}))
x := 0;     v.Is(v.NumberP(&x).ZeroOrNil())
var x *int; v.Is(v.NumberP(x).Nil())

Int validators

The ValidatorInt[T] provides functions for setting validation rules for int, int8, int16, int32, int64, and rune types, or custom types based on them.

Below is a valid example for every Int validator rule.

// Basic numeric validations
age := int(25)
v.Is(v.Int(age).EqualTo(25))
v.Is(v.Int(age).GreaterThan(18))
v.Is(v.Int(age).LessThan(65))
v.Is(v.Int(age).Between(18, 65))

// Zero, positive, and negative validations
v.Is(v.Int(0).Zero())
v.Is(v.Int(5).Positive())
v.Is(v.Int(-3).Negative())

// Custom validation and slice checking
v.Is(v.Int(age).Passing(func(a int) bool { return a >= 18 }))
v.Is(v.Int(age).InSlice([]int{18, 25, 30, 35}))

// Works with all int variants
v.Is(v.Int8(int8(10)).GreaterThan(int8(5)))
v.Is(v.Int16(int16(100)).LessThan(int16(200)))
v.Is(v.Int32(int32(1000)).Between(int32(500), int32(1500)))
v.Is(v.Int64(int64(10000)).EqualTo(int64(10000)))
v.Is(v.Rune('A').EqualTo('A'))

// Works with custom int types
type UserID int
userID := UserID(123)
v.Is(v.Int(userID).GreaterThan(UserID(0)))

Int pointer validator

The ValidatorIntP[T] provides functions for setting validation rules for int, int8, int16, int32, int64, and rune pointer types, or custom types based on them.

Below is a valid example for every Int pointer validator rule.

// Basic numeric validations
age := int(25)
v.Is(v.IntP(&age).EqualTo(25))
v.Is(v.IntP(&age).GreaterThan(18))
v.Is(v.IntP(&age).LessThan(65))
v.Is(v.IntP(&age).Between(18, 65))

// Zero, positive, and negative validations
zero := int(0)
positive := int(5)
negative := int(-3)
v.Is(v.IntP(&zero).Zero())
v.Is(v.IntP(&positive).Positive())
v.Is(v.IntP(&negative).Negative())

// Custom validation and slice checking
v.Is(v.IntP(&age).Passing(func(a *int) bool { return *a >= 18 }))
v.Is(v.IntP(&age).InSlice([]int{18, 25, 30, 35}))

// Nil and zero-or-nil validations
var nilInt *int
v.Is(v.IntP(nilInt).Nil())

zeroInt := int(0)
v.Is(v.IntP(&zeroInt).ZeroOrNil())

// Works with all int variants
int8Val := int8(10)
v.Is(v.Int8P(&int8Val).GreaterThan(int8(5)))

int16Val := int16(100)
v.Is(v.Int16P(&int16Val).LessThan(int16(200)))

int32Val := int32(1000)
v.Is(v.Int32P(&int32Val).Between(int32(500), int32(1500)))

int64Val := int64(10000)
v.Is(v.Int64P(&int64Val).EqualTo(int64(10000)))

runeVal := rune('A')
v.Is(v.RuneP(&runeVal).EqualTo('A'))

// Works with custom int pointer types
type UserID int
userID := UserID(123)
v.Is(v.IntP(&userID).GreaterThan(UserID(0)))

Uint validators

The ValidatorUint[T] provides functions for setting validation rules for uint, uint8, uint16, uint32, uint64, and byte types, or custom types based on them.

Below is a valid example for every Uint validator rule.

// Basic numeric validations
count := uint(10)
v.Is(v.Uint(count).EqualTo(10))
v.Is(v.Uint(count).GreaterThan(5))
v.Is(v.Uint(count).LessThan(20))
v.Is(v.Uint(count).Between(5, 20))

// Zero validation (uint values are always >= 0)
v.Is(v.Uint(0).Zero())

// Custom validation and slice checking
v.Is(v.Uint(count).Passing(func(c uint) bool { return c > 0 }))
v.Is(v.Uint(count).InSlice([]uint{5, 10, 15, 20}))

// Works with all uint variants
v.Is(v.Uint8(uint8(10)).GreaterThan(uint8(5)))
v.Is(v.Uint16(uint16(100)).LessThan(uint16(200)))
v.Is(v.Uint32(uint32(1000)).Between(uint32(500), uint32(1500)))
v.Is(v.Uint64(uint64(10000)).EqualTo(uint64(10000)))
v.Is(v.Byte(byte(65)).EqualTo(byte(65)))

// Works with custom uint types
type Count uint
count := Count(5)
v.Is(v.Uint(count).GreaterThan(Count(0)))

Uint pointer validator

The ValidatorUintP[T] provides functions for setting validation rules for uint, uint8, uint16, uint32, uint64, and byte pointer types, or custom types based on them.

Below is a valid example for every Uint pointer validator rule.

// Basic numeric validations
count := uint(10)
v.Is(v.UintP(&count).EqualTo(10))
v.Is(v.UintP(&count).GreaterThan(5))
v.Is(v.UintP(&count).LessThan(20))
v.Is(v.UintP(&count).Between(5, 20))

// Zero validation (uint values are always >= 0)
zero := uint(0)
v.Is(v.UintP(&zero).Zero())

// Custom validation and slice checking
v.Is(v.UintP(&count).Passing(func(c *uint) bool { return *c > 0 }))
v.Is(v.UintP(&count).InSlice([]uint{5, 10, 15, 20}))

// Nil and zero-or-nil validations
var nilUint *uint
v.Is(v.UintP(nilUint).Nil())

zeroUint := uint(0)
v.Is(v.UintP(&zeroUint).ZeroOrNil())

// Works with all uint variants
uint8Val := uint8(10)
v.Is(v.Uint8P(&uint8Val).GreaterThan(uint8(5)))

uint16Val := uint16(100)
v.Is(v.Uint16P(&uint16Val).LessThan(uint16(200)))

uint32Val := uint32(1000)
v.Is(v.Uint32P(&uint32Val).Between(uint32(500), uint32(1500)))

uint64Val := uint64(10000)
v.Is(v.Uint64P(&uint64Val).EqualTo(uint64(10000)))

byteVal := byte(65)
v.Is(v.ByteP(&byteVal).EqualTo(byte(65)))

// Works with custom uint pointer types
type Count uint
count := Count(5)
v.Is(v.UintP(&count).GreaterThan(Count(0)))

Float validators

The ValidatorFloat[T] provides functions for setting validation rules for float32 and float64 types, or custom types based on them.

Below is a valid example for every Float validator rule.

// Basic numeric validations
price := 19.99
v.Is(v.Float64(price).EqualTo(19.99))
v.Is(v.Float64(price).GreaterThan(10.0))
v.Is(v.Float64(price).LessThan(50.0))
v.Is(v.Float64(price).Between(10.0, 50.0))

// Zero, positive, and negative validations
v.Is(v.Float64(0.0).Zero())
v.Is(v.Float64(5.5).Positive())
v.Is(v.Float64(-3.14).Negative())

// Special float validations
v.Is(v.Float64(math.NaN()).NaN())
v.Is(v.Float64(math.Inf(1)).Infinite())
v.Is(v.Float64(3.14).Finite())

// Custom validation and slice checking
v.Is(v.Float64(price).Passing(func(p float64) bool { return p > 0 }))
v.Is(v.Float64(price).InSlice([]float64{9.99, 19.99, 29.99}))

// Work with Float32 variant
v.Is(v.Float32(float32(0.0)).Zero())
v.Is(v.Float32(float32(5.5)).Positive())
v.Is(v.Float32(float32(-3.14)).Negative())

// Works with custom float types
type Price float64
price := Price(29.99)
v.Is(v.Float64(price).GreaterThan(Price(20.0)))

Float pointer validator

The ValidatorFloatP[T] provides functions for setting validation rules for float32 and float64 pointer types, or custom types based on them.

Below is a valid example for every Float pointer validator rule.

// Basic numeric validations
price := 19.99
v.Is(v.Float64P(&price).EqualTo(19.99))
v.Is(v.Float64P(&price).GreaterThan(10.0))
v.Is(v.Float64P(&price).LessThan(50.0))
v.Is(v.Float64P(&price).Between(10.0, 50.0))

// Zero, positive, and negative validations
zero := 0.0
positive := 5.5
negative := -3.14
v.Is(v.Float64P(&zero).Zero())
v.Is(v.Float64P(&positive).Positive())
v.Is(v.Float64P(&negative).Negative())

// Special float validations
nan := math.NaN()
inf := math.Inf(1)
finite := 3.14
v.Is(v.Float64P(&nan).NaN())
v.Is(v.Float64P(&inf).Infinite())
v.Is(v.Float64P(&finite).Finite())

// Custom validation and slice checking
v.Is(v.Float64P(&price).Passing(func(p *float64) bool { return *p > 0 }))
v.Is(v.Float64P(&price).InSlice([]float64{9.99, 19.99, 29.99}))

// Nil and zero-or-nil validations
var nilFloat *float64
v.Is(v.Float64P(nilFloat).Nil())

zeroFloat := 0.0
v.Is(v.Float64P(&zeroFloat).ZeroOrNil())

// Works with all float variants
float32Val := float32(3.14)
v.Is(v.Float32P(&float32Val).Positive())

float64Val := 2.718
v.Is(v.Float64P(&float64Val).Positive())

// Works with custom float pointer types
type Price float64
price := Price(29.99)
v.Is(v.Float64P(&price).GreaterThan(Price(20.0)))

Bool validator

The Bool validator provides functions for setting validation rules for a bool type value, or a custom type based on a bool.

Below is a valid example for every Bool validator rule.

v.Is(v.Bool(true).EqualTo(true))
v.Is(v.Bool(true).True())
v.Is(v.Bool(false).False())
v.Is(v.Bool(true).Passing(func(val bool) bool { return val == true }))
v.Is(v.Bool(true).InSlice([]string{true, false}))

Boolean pointer validator

The Bool pointer validator provides functions for setting validation rules for a bool pointer, or a custom type based on a bool pointer.

Below is a valid example for every Bool pointer validator rule.

x := true;   v.Is(v.BoolP(&x).EqualTo(true))
x := true;   v.Is(v.BoolP(&x).True())
x := false;  v.Is(v.BoolP(&x).False())
x := true;   v.Is(v.BoolP(&x).Passing(func(val *bool) bool { return val == true }))
x := true;   v.Is(v.BoolP(&x).InSlice([]string{true, false}))
x := false;  v.Is(v.BoolP(&x).FalseOrNil())
var x *bool; v.Is(v.BoolP(x).Nil())

Time validator

The ValidatorTime provides functions for setting validation rules for a time.Time type value, or a custom type based on a time.Time.

Below is a valid example for every Time validator rule.

import "time"

v.Is(v.Time(time.Now()).EqualTo(time.Now()))
v.Is(v.Time(time.Now()).After(time.Now().Add(-time.Hour)))
v.Is(v.Time(time.Now()).AfterOrEqualTo(time.Now().Add(-time.Hour)))
v.Is(v.Time(time.Now()).Before(time.Now().Add(time.Hour)))
v.Is(v.Time(time.Now()).BeforeOrEqualTo(time.Now().Add(time.Hour)))
v.Is(v.Time(time.Now()).Between(time.Now().Add(-time.Hour), time.Now().Add(2*time.Hour))) // Inclusive
v.Is(v.Time(time.Time{}).Zero())
v.Is(v.Time(time.Now()).Passing(func(val time.Time) bool { return val.Before(time.Now().Add(2*time.Hour)) }))
v.Is(v.Time(time.Now()).InSlice([]time.Time{time.Now(), time.Now().Add(time.Hour)}))

Time pointer validator

The ValidatorTimeP provides functions for setting validation rules for a time.Time type pointer, or a custom type based on a time.Time pointer.

Below is a valid example for every Time pointer validator rule.

import "time"

x := time.Now(); v.Is(v.TimeP(&x).EqualTo(time.Now()))
x = time.Now(); v.Is(v.TimeP(&x).After(time.Now().Add(-time.Hour)))
x = time.Now(); v.Is(v.TimeP(&x).AfterOrEqualTo(time.Now().Add(-time.Hour)))
x = time.Now(); v.Is(v.TimeP(&x).Before(time.Now().Add(time.Hour)))
x = time.Now(); v.Is(v.TimeP(&x).BeforeOrEqualTo(time.Now().Add(time.Hour)))
x = time.Now(); v.Is(v.TimeP(&x).Between(time.Now().Add(-time.Hour), time.Now().Add(2*time.Hour))) // Inclusive
x = time.Time{}; v.Is(v.TimeP(&x).Zero())
x = time.Now(); v.Is(v.TimeP(&x).Passing(func(val *time.Time) bool { return val.Before(time.Now().Add(2*time.Hour)) }))
x = time.Now(); v.Is(v.TimeP(&x).InSlice([]time.Time{time.Now(), time.Now().Add(time.Hour)}))
var x *time.Time; v.Is(v.TimeP(x).Nil())
x = new(time.Time); v.Is(v.TimeP(x).NilOrZero())

Comparable validator

The ValidatorComparable[T] provides functions for setting validation rules for any Go type that implements the Go comparable constraint. This validator is optimized for equality comparisons and slice operations with compile-time type safety.

Below is a valid example for every Comparable validator rule.

// Basic equality comparison
status := "running"
v.Is(v.Comparable(status).EqualTo("running"))

// Custom validation function with type safety
type Status string
status := Status("running")
v.Is(v.Comparable(status).Passing(func(s Status) bool {
    return s == "running" || s == "paused"
}))

// Check if value exists in a slice
status := "idle"
validStatuses := []string{"idle", "paused", "stopped"}
v.Is(v.Comparable(status).InSlice(validStatuses))

// Works with custom comparable types
type User struct {
  ID int
}
userA := User{ID: 123}
userB := User{ID: 123}
v.Is(v.Comparable(userA).EqualTo(userB))

Comparable pointer validator

The ValidatorComparableP[T] provides functions for setting validation rules for any Go type pointer that implements the comparable constraint. This validator is optimized for equality comparisons and slice operations with compile-time type safety.

Below is a valid example for every Comparable pointer validator rule.

// Basic equality comparison
status := "running"
v.Is(v.ComparableP(&status).EqualTo("running"))

// Custom validation function with type safety
type Status string
status := Status("running")
v.Is(v.ComparableP(&status).Passing(func(s *Status) bool {
    return *s == "running" || *s == "paused"
}))

// Check if value exists in a slice
status := "idle"
validStatuses := []string{"idle", "paused", "stopped"}
v.Is(v.ComparableP(&status).InSlice(validStatuses))

// Nil validation
var nilStatus *string
v.Is(v.ComparableP(nilStatus).Nil())

// Works with custom comparable pointer types
type User struct {
    ID int
}
userA := &User{ID: 123}
userB := User{ID: 123}
v.Is(v.ComparableP(userA).EqualTo(userB))

Typed validator

The ValidatorTyped[T] provides functions for setting validation rules for any Go type with compile-time type safety. This is a type-safe alternative to the Any validator, addressing type safety concerns.

Below is a valid example for every Typed validator rule.

// Type-safe custom validation function
type Status string
status := Status("running")
v.Is(v.Typed(status).Passing(func(s Status) bool {
    return s == "running" || s == "paused"
}))

// Nil validation for pointer types
var s *string
v.Is(v.Typed(s).Nil())

// Works with any Go type
type User struct {
    Name string
    Age  int
}
user := User{"John", 30}
v.Is(v.Typed(user).Passing(func(u User) bool {
    return u.Age >= 18
}))

Any validator

With the Any validator, you can set validation rules for any value or pointer.

Below is a valid example of every Any validator rule.

v.Is(v.Any("svelte").Passing(func(val any) bool { return val == "svelte" }))
var x *bool; v.Is(v.Any(x).Nil())

EqualTo (DEPRECATED) any is not safely comparable. Do not use EqualTo on ValidatorAny; use EqualTo on ValidatorComparable instead. EqualTo on ValidatorAny will be removed in Valgo v1.0.0.

v.Is(v.Any("react").EqualTo("react"))

Custom type validators

All golang validators allow to pass a custom type based on its value type. Below some valid examples.

type Status string
var status Status = "up"
val := v.Is(v.String(status).InSlice([]Status{"down", "up", "paused"}))

type Level int
var level Level = 1
val = v.Is(v.Int(level).LessThan(Level(2)))

type Stage int64
var stage Stage = 2
val := v.Is(v.NumberP(&stage).GreaterThan(Stage(1)))

// Using the new type-safe validators
type UserID int
userID := UserID(123)
val = v.Is(v.Int(userID).GreaterThan(UserID(0)))

type Price float32
price := Price(19.99)
val = v.Is(v.Float32(price).Positive())

type Status string
status := Status("running")
val = v.Is(v.Comparable(status).EqualTo(Status("running")))

Or Operator in Validators

The Or operator function enables developers to combine validator rules using a logical OR chain. This addition allows for more nuanced validator scenarios, where a value may satisfy one of multiple conditions to be considered valid.

Overview

In Valgo, validator rules are typically chained together using an implicit AND logic. This means that for a value to be deemed valid, it must satisfy all specified conditions. The Or operator provides an alternative by allowing conditions to be linked with OR logic. In such cases, a value is considered valid if it meets at least one of the chained conditions.

The Or operator follows a simple left-to-right boolean priority, akin to the Go language's approach to evaluating boolean expressions. Valgo does not have an equivalent to parentheses in API functions, in order to keep the syntax simple and readable. We believe that complex boolean logic becomes harder to read with a Fluent API interface, so for those cases, it is preferred to use imperative Go programming language constructs.

Usage

To utilize the Or operator, simply insert .Or(). between two conditions within your validator chain. Here's a basic example:

v := Is(Bool(true).True().Or().False())

In this case, the validator passes because the boolean value true satisfies the first condition before the Or() operator.

Key Points

  • Implicit AND Logic: By default, when validators are chained without specifying the Or() operator, they are combined using an AND logic. Each condition must be met for the validation to pass.
  • No Short-circuiting for Check: Unlike the Is function, which evaluates conditions lazily and may short-circuit (stop evaluating once the overall outcome is determined), the Check function ensures that all conditions are evaluated, regardless of their order and the use of Or.

Examples

Below are examples demonstrating different scenarios using the Or operator, including combinations with the Not operator and multiple Or conditions in sequence. These examples illustrate how you can tailor complex validation logic to suit your needs.

// Validation with two valid OR conditions
v = Is(Bool(true).True().Or().True())
assert.True(t, v.Valid())

// Validation with a valid OR condition followed by an invalid AND condition
v = Is(Bool(true).False().Or().True().False())
assert.False(t, v.Valid())

// Validation combining NOT and OR operators
v = Is(Bool(true).Not().False().Or().False())
assert.True(t, v.Valid())

These examples are intended to provide a clear understanding of how to effectively use the Or operator in your validations. By leveraging this functionality, you can create more flexible and powerful validation rules, enhancing the robustness and usability of your applications.

Extending Valgo with custom validators

While all validators in Golang provide a Passing(...) function, which allows you to use a custom validator function, Valgo also allows you to create your own validator.

With this functionality Valgo can be extended with Validator libraries, which we encourage the community to do.

For example, let's say we want to create a validation for the following ID struct, where a user must provide at least one property.

The struct to validate:

// Type to validate
type ID struct {
  Phone string
  Email string
}

the custom validator code:

// The custom validator type
type ValidatorID struct {
	context *valgo.ValidatorContext
}

// The custom validator implementation of `valgo.Validator`
func (validator *ValidatorID) Context() *valgo.ValidatorContext {
	return validator.context
}

// Here is the function that passes the value to the custom validator
func IDValue(value ID, nameAndTitle ...string) *ValidatorID {
	return &ValidatorID{context: valgo.NewContext(value, nameAndTitle...)}
}

// The Empty rule implementation
func (validator *ValidatorID) Empty(template ...string) *ValidatorID {
	validator.context.Add(
		func() bool {
			return len(strings.Trim(validator.context.Value().(ID).Phone, " ")) == 0 &&
				len(strings.Trim(validator.context.Value().(ID).Email, " ")) == 0
		},
		v.ErrorKeyEmpty, template...)

	return validator
}

// It would be possible to create a rule NotEmpty() instead of Empty(), but if you add a Not() function then your validator will be more flexible.

func (validator *ValidatorID) Not() *ValidatorID {
	validator.context.Not()

	return validator
}

using our validator:

val := v.Is(IDValue(ID{}, "id").Not().Empty())

out, _ := json.MarshalIndent(val.ToError(), "", "  ")
fmt.Println(string(out))

output:

{
  "identification": [
	  "Id can't be empty"
  ]
}

List of rules by validator type

  • String validator

    • EqualTo
    • GreaterThan
    • GreaterOrEqualTo
    • LessThan
    • LessOrEqualTo
    • Between
    • Empty
    • Blank
    • Passing
    • InSlice
    • MatchingTo
    • MaxBytes
    • MinBytes
    • OfByteLength
    • OfByteLengthBetween
    • MaxLength
    • MinLength
    • OfLength
    • OfLengthBetween
  • StringP validator - for string pointer

    • EqualTo
    • GreaterThan
    • GreaterOrEqualTo
    • LessThan
    • LessOrEqualTo
    • Between
    • Empty
    • Blank
    • Passing
    • InSlice
    • MatchingTo
    • MaxBytes
    • MinBytes
    • OfByteLength
    • OfByteLengthBetween
    • MaxLength
    • MinLength
    • OfLength
    • OfLengthBetween
    • BlankOrNil
    • EmptyOrNil
    • Nil
  • Bool validator

    • EqualTo
    • Passing
    • True
    • False
    • InSlice
  • BoolP validator - for boolean pointer

    • EqualTo
    • Passing
    • True
    • False
    • InSlice
    • FalseOrNil
    • Nil
  • Number - for number types Int, Int8, Int16, Int32, Int64, Uint, Uint8, Uint16, Uint32, Uint64, Float32, Float64, Byte, Rune

    • EqualTo
    • GreaterThan
    • GreaterOrEqualTo
    • LessThan
    • LessOrEqualTo
    • Between
    • Zero
    • InSlice
    • Passing
  • NumberP - for number pointer IntP, Int8P, Int16P, Int32P, Int64P, UintP, Uint8P, Uint16P, Uint32P, Uint64P, Float32P, Float64P, ByteP, RuneP

    • EqualTo
    • GreaterThan
    • GreaterOrEqualTo
    • LessThan
    • LessOrEqualTo
    • Between
    • Zero
    • InSlice
    • Passing
    • ZeroOrNil
    • Nil
  • Time validator

    • EqualTo
    • After
    • AfterOrEqualTo
    • Before
    • BeforeOrEqualTo
    • Between
    • Zero
    • Passing
    • InSlice
  • TimeP validator - for time.Time pointer

    • EqualTo
    • After
    • AfterOrEqualTo
    • Before
    • BeforeOrEqualTo
    • Between
    • Zero
    • Passing
    • InSlice
    • Nil
    • NilOrZero
  • Any validator

    • EqualTo (Deprecated)
    • Passing
    • Nil
  • Comparable validator - for comparable types

    • EqualTo
    • Passing
    • InSlice
  • ComparableP validator - for comparable pointer types

    • EqualTo
    • Passing
    • InSlice
    • Nil
  • Typed validator - for any Go type with type safety

    • Passing
    • Nil
  • Float validator - for float32 and float64 types

    • EqualTo
    • GreaterThan
    • GreaterOrEqualTo
    • LessThan
    • LessOrEqualTo
    • Between
    • Zero
    • Positive
    • Negative
    • Passing
    • InSlice
    • NaN
    • Infinite
    • Finite
  • FloatP validator - for float32 and float64 pointer types

    • EqualTo
    • GreaterThan
    • GreaterOrEqualTo
    • LessThan
    • LessOrEqualTo
    • Between
    • Zero
    • Positive
    • Negative
    • Passing
    • InSlice
    • NaN
    • Infinite
    • Finite
    • ZeroOrNil
    • Nil
  • Int validator - for int, int8, int16, int32, int64, and rune types

    • EqualTo
    • GreaterThan
    • GreaterOrEqualTo
    • LessThan
    • LessOrEqualTo
    • Between
    • Zero
    • Positive
    • Negative
    • Passing
    • InSlice
  • IntP validator - for int, int8, int16, int32, int64, and rune pointer types

    • EqualTo
    • GreaterThan
    • GreaterOrEqualTo
    • LessThan
    • LessOrEqualTo
    • Between
    • Zero
    • Positive
    • Negative
    • Passing
    • InSlice
    • ZeroOrNil
    • Nil
  • Uint validator - for uint, uint8, uint16, uint32, uint64, and byte types

    • EqualTo
    • GreaterThan
    • GreaterOrEqualTo
    • LessThan
    • LessOrEqualTo
    • Between
    • Zero
    • Passing
    • InSlice
  • UintP validator - for uint, uint8, uint16, uint32, uint64, and byte pointer types

    • EqualTo
    • GreaterThan
    • GreaterOrEqualTo
    • LessThan
    • LessOrEqualTo
    • Between
    • Zero
    • Passing
    • InSlice
    • ZeroOrNil
    • Nil

Github Code Contribution Guide

We welcome contributions to our project! To make the process smooth and efficient, please follow these guidelines when submitting code:

  • Discuss changes with the community: We encourage contributors to discuss their proposed changes or improvements with the community before starting to code. This ensures that the changes align with the focus and purpose of the project, and that other contributors are aware of the work being done.

  • Make commits small and cohesive: It is important to keep your commits focused on a single task or change. This makes it easier to review and understand your changes.

  • Check code formatting with go fmt: Before submitting your code, please ensure that it is properly formatted using the go fmt command.

  • Make tests to cover your changes: Please include tests that cover the changes you have made. This ensures that your code is functional and reduces the likelihood of bugs.

  • Update golang docs and README to cover your changes: If you have made changes that affect documentation or the README file, please update them accordingly.

  • Keep a respectful language with a collaborative tune: We value a positive and collaborative community. Please use respectful language when communicating with other contributors or maintainers.

License

Copyright ยฉ 2025 Carlos Forero

Valgo is released under the MIT License