Validation Guide

goopt provides a powerful and flexible validation system that allows you to ensure the correctness of user input. It’s built on a foundation of composable validator functions that can be used both programmatically and directly within struct tags.

Validator Syntax: Parentheses are required for validators with arguments

The validation system uses a consistent, parenthesis-based syntax for validators with arguments.


// required syntax
`goopt:"validators:minlength(5),maxlength(20)"`

Key Rules:

  1. Validators with no arguments can omit parentheses (e.g., email or email()).
  2. Validators with arguments must use parentheses (e.g., minlength(5)).
  3. Multiple validators are comma-separated (e.g., validators:email,minlength(10)).

Using Built-in Validators

You can apply validators directly in your struct tags or programmatically when defining flags.

In Struct Tags

type Config struct {
    // Simple validators
    Email      string `goopt:"name:email;validators:email"`
    Port       int    `goopt:"name:port;validators:port"`
    
    // Multiple validators (all must pass)
    Username   string `goopt:"name:username;validators:minlength(3),maxlength(20),alphanumeric"`
    
    // Compositional validators
    ID         string `goopt:"name:id;validators:oneof(email,regex(^EMP-[0-9]{6}$))"`
}

Programmatically

import "github.com/napalu/goopt/v2/validation"

// During parser creation
parser, err := goopt.NewParserWith(
    goopt.WithFlag("email", goopt.NewArg(
        goopt.WithValidator(validation.Email()),
    )),
)

// Or add validators after the fact
parser.AddFlagValidators("port", validation.Port())

Available Built-in Validators

Here is a reference of the most common built-in validators.

Type Validators

Validator Struct Tag Description
Integer() integer or int Validates integer values.
Float() float or number Validates floating-point numbers.
Boolean() boolean or bool Validates boolean values.

String Validators

Validator Struct Tag Description
MinLength(n) minlength(n) Minimum number of Unicode characters.
MaxLength(n) maxlength(n) Maximum number of Unicode characters.
ByteLength(n) bytelength(n) Exact length in bytes (for UTF-8).
AlphaNumeric() alphanumeric Contains only letters and numbers.
Identifier() identifier A valid Go-style identifier.
NoWhitespace() nowhitespace Contains no whitespace characters.

Pattern & Network Validators

Validator Struct Tag Description
Regex(pattern, desc) regex(pattern:...,desc:...) Matches a regular expression. desc when supplied can be a message key (for translation) or a literal string
Email() email A valid email address format.
URL(schemes...) url(http,https) A valid URL, optionally restricted to schemes.
Hostname() hostname A valid DNS hostname.
IP() ip An IPv4 or IPv6 address.
Port() port A valid port number (1-65535).

Numeric Validators

Validator Struct Tag Description
Range(min, max) range(min,max) An inclusive float numeric range.
IntRange(min, max) intrange(min,max) An inclusive integer numeric range.
Min(n) min(n) A minimum numeric value.
Max(n) max(n) A maximum numeric value.

Collection Validators

Validator Struct Tag Description
IsOneOf(values...) isoneof(val1,val2) Value must be one of the given strings.
FileExtension(exts...) fileext(.txt,.md) File path must have one of the extensions.

Compositional Validators

Validator Struct Tag Description
All(validators...) all(...) All nested validators must pass (AND logic).
OneOf(validators...) oneof(...) At least one nested validator must pass (OR logic).
Not(validator) not(...) Negates a validator.

Creating Custom Validators

For domain-specific rules, you can easily create your own validators.

Important: Custom validators can currently only be used programmatically via WithValidator() or AddFlagValidators(). They cannot be referenced by name from a struct tag.

The Validator interface

A validator is any value implementing validation.Validator:

type Validator interface { Validate(value string) error }

validation.ValidatorFunc is the functional implementation (a func(string) error that satisfies Validator), and every built-in validator returns one. So the idiomatic way to write a reusable custom validator is a constructor returning validation.ValidatorFunc:

import "github.com/napalu/goopt/v2/validation"

// HexColor validates that a string is a valid hex color code (e.g., "#ff0000").
func HexColor() validation.ValidatorFunc {
    return func(value string) error {
        if !strings.HasPrefix(value, "#") || (len(value) != 4 && len(value) != 7) {
            return errors.New("must be a valid hex color like #rgb or #rrggbb")
        }
        // ... more detailed validation ...
        return nil
    }
}

For a one-off inline check, wrap a bare func in validation.Custom — a raw func(string) error does not satisfy the Validator interface on its own:

goopt.WithValidators(
    validation.Custom(func(value string) error {
        if value == "forbidden" {
            return errors.New("that value is not allowed")
        }
        return nil
    }),
)

Migrating from an earlier v2? Validators used to be passed as bare funcs. The goopt-migrate-v2 tool rewrites those to validation.Custom(...) automatically — see What’s New.

Using Custom Validators

You can then add your custom validator to any flag.

parser.AddFlag("color", goopt.NewArg(
    goopt.WithDescription("A hex color code for the background"),
    goopt.WithValidator(HexColor()),
))

Combining with Built-in Validators

Custom validators are fully composable with built-in ones.

// A validator that requires a non-reserved username.
notReserved := validation.Not(validation.IsOneOf("admin", "root", "system"))

parser.AddFlag("username", goopt.NewArg(
    goopt.WithValidators(
        validation.MinLength(4),    // Built-in
        validation.AlphaNumeric(),  // Built-in
        notReserved,                // Custom composed
    ),
))

Making Custom Validators Translatable

To support internationalization, your custom validators can return translatable errors from the errs package.

import "github.com/napalu/goopt/v2/errs"

// Define your translatable error key.
var ErrInvalidHexColor = i18n.NewError("validation.invalid_hex_color")

func HexColorI18n() validation.ValidatorFunc {
    return func(value string) error {
        if !strings.HasPrefix(value, "#") {
            // Return a translatable error instead of a standard one.
            return ErrInvalidHexColor.WithArgs(value)
        }
        return nil
    }
}

Now, you can provide a translation for validation.invalid_hex_color in your i18n JSON files, see Internationalization for details.

Validators That Drive Completion

A validator that restricts a value to a finite set can also feed shell completion — so one declaration both validates input and suggests it, with no chance of the two drifting apart. This works through an optional interface a validator may implement:

type Enumerable interface { Candidates() []string }

validation.IsOneOf implements it, so it validates and completes:

goopt.NewArg(goopt.WithValidators(validation.IsOneOf("prod", "staging", "dev")))
// rejects anything outside the set AND offers the three values on <TAB>

Your own validator can opt in the same way by implementing Candidates() alongside Validate() (return a value, not a ValidatorFunc, so it can carry the method set):

type logLevel struct{ levels []string }

func (l logLevel) Validate(v string) error {
    for _, x := range l.levels {
        if v == x {
            return nil
        }
    }
    return errs.ErrValueMustBeOneOf.WithArgs(v, strings.Join(l.levels, ", "))
}
func (l logLevel) Candidates() []string { return l.levels } // satisfies Enumerable

Validation and completion can’t diverge because they’re the same value. For values that must be computed at runtime (git branches, files, service data), use WithCompleter instead — see the Shell Completion guide.


This site uses Just the Docs, a documentation theme for Jekyll.