safe

package module
v1.4.1 Latest Latest
Warning

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

Go to latest
Published: Mar 31, 2025 License: MIT Imports: 11 Imported by: 1

README

Safe

A simple Go validation library

Safe logo

User input is unpredictable. Never trust it. Use this library to validate anything you want, and you know you're safe!

Installation

go get -u github.com/cayo-rodrigues/safe

Usage

u := &User{...}

oneYear := (time.Hour * 24) * 365
minorBirthDate := time.Now().Add(-oneYear * 18)
unavailableRoles := [2]string{"software developer", "pepsimaaaaan"}
pineappleRegex := regexp.MustCompile("^pine.*apple$")

fields := safe.Fields{
    {
        Name: "username",
        Value: u.Username,
        Rules: safe.Rules{safe.Required(), safe.Min(3), safe.Max(128)},
    },
    {
        Name: "email",
        Value: u.Email,
        Rules: safe.Rules{safe.Required(), safe.Email(), safe.Max(128).WithMessage("Is your email really that long?")},
    },
    {
        Name: "cpf/cnpj",
        Value: u.CpfCnpj,
        Rules: safe.Rules{
            safe.RequiredUnless(safe.All(u.Email, u.Username)), // cpf/cnpj is required, unless both u.Email and u.Username have a value
            safe.CpfCnpj(),
            safe.Max(128),
        },
    },
    {
        Name: "password",
        Value: u.Password,
        Rules: safe.Rules{safe.Required(), safe.StrongPassword()},
    },
    {
        Name: "roles",
        Value: u.Roles,
        Rules: safe.Rules{
            safe.UniqueList[string](), // must provide the type of the elements in the list
            safe.NotOneOf(unavailableRoles[:]), // the input must be a slice, but unavailableRoles is an array with a fixed size, that's why we need [:] here
        },
    },
    {
        Name: "birth",
        Value: u.BirthDate,
        Rules: safe.Rules{
            safe.RequiredUnless(u.CpfCnpj, u.Pineapple), // required, unless u.CpfCnpj OR u.Pineapple have a value
            safe.NotBefore(minorBirthDate)},
    },
    {
        Name: "company_id",
        Value: u.CompanyID,
        Rules: safe.Rules{
            safe.RequiredUnless(safe.CnpjRegex.MatchString(u.CpfCnpj)).WithMessage("Must provide a valid cnpj or company_id"), // got it?
            safe.UUIDstr(),
        },
    },
    {
        Name: "pineapple",
        Value: u.Pineapple,
        Rules: safe.Rules{safe.Match(pineappleRegex)},
    },
}

// this will set the language for all the error messages
// the default is languages.PT_BR
fields.SetLanguage(languages.EN_US)

errors, isValid := safe.Validate(fields)

That's it!

In the example above, errors is a safe.ErrorMessages, which is just a wrapper around map[string]string that implements the error interface. It has error messages for each field. The default error message can be overwritten with the WithMessage func, as demonstrated in the example, for the email field.

When a field fails to pass a given rule, no more subsequent rules are applied. For instance, if password is not provided, it will fail the safe.Required rule, hence the safe.StrongPassword rule will not run its validation func, and the resulting error message will be regarding the absence of a value, instead of the fact that it does not conform to a strong password standard.

You can refer to the source code or the individual documentation of each function for further instructions. They are all very intuitive.

Use cases

The fact that safe.ErrorMessages implements the error interface makes it possible to use it in any error handling case, just like any other error.

For example, suppose you have a custom error you return from an http api. You could do something like this:

type ApiError struct {
	StatusCode  int                `json:"status_code"`
	Msg         string             `json:"msg"`
	FieldErrors safe.ErrorMessages `json:"field_errors"`
}

func (e ApiError) Error() string {
    // ...
}

You could also use it for unit testing:


func TestInsertStuffService(t *testing.T) {
    input := StuffInputData{
        A: "a",
        B: "bb",
    }
    output := services.InsertStuffService(&input)

    outputShape := safe.Fields{
        {
            Name: "output_ID",
            Value: output.ID,
            Rules: safe.Rules{safe.Required(), safe.UUIDstr()},
        },
        {
            Name: "output_A",
            Value: output.A,
            Rules: safe.Rules{safe.Required(), safe.EqualTo(input.A)},
        },
        {
            Name: "output_B",
            Value: output.B,
            Rules: safe.Rules{safe.Required(), safe.EqualTo(input.B)},
        },
        {
            Name: "output_CreatedAt",
            Value: output.CreatedtAt,
            Rules: safe.Rules{safe.Required()},
        }
    }

    errors, ok := safe.Validate(outputShape)
    if !ok {
        t.Fatalf("Output does not match expected conditions.\nerrors: %s\nvalue: %s", errors, outputShape)
    }
}

About error messages and languages

Safe exposes messages.Messages, which is a localized set of error messages.

You can either extend the behavior of messages.Messages or create your own set of messages, completely decoupled from it.

You are free to use messages.Messages directly or to use the shortcuts provided in the messages package, like messages.MandatoryFieldMsg, messages.MaxValueMsg, and so forth. The shortcuts provide a straightforward way to access a message, with an optional language.Language input.

The default language used in the error messages is languages.PT_BR, but you can use languages.EN_US as well. No other languages are supported out of the box right now, but nothing stops you from creating your own!

Here is an example of what it may look like:

myLang := languages.Language("ES_LA")
myMsgKey := messages.MessageKey("myMsgKey")

myMsgPt := "my msg PT"
myMsgEn := "my msg EN"
myMsgInMyLang := "my msg arriba!"

messages.Messages.Update(messages.LocalizedMessages{
    languages.PT_BR: {
        myMsgKey: myMsgPt,
    },
    languages.EN_US: {
        myMsgKey: myMsgEn,
    },
    myLang: {
        myMsgKey: myMsgInMyLang,
    },
})

// from here onwards you can use your new message from anywhere
msg := messages.Messages.Get(myLang, myMsgKey)

You could also do something similar in case you want to just add a new language to the existing default messages. For instance:

newLang := languages.Language("ES_LA")

messages.Messages.Update(messages.LocalizedMessages{
    newLang: {
        messages.MsgKey__MandatoryField: "Campo obligatorio",
        messages.MsgKey__InvalidFormat: "Formato no válido",
        messages.MsgKey__UniqueList: "Los valores en la lista deben ser únicos",
    },
})

// mandatory field message in ES_LA
msg1 := messages.MandatoryFieldMsg(newLang) 

// invalid format message in ES_LA
msg2 := messages.InvalidFormatMsg(newLang) 

// illogial dates message in PT_BR, because it has not been found in the ES_LA messages
msg3 := messages.IlogicalDatesMsg(newLang) 

Changing the default language

The default language is languages.PT_BR. To change it, do:

messages.DefaultLang = languages.EN_US

You could also set it to some other language if you want:

myLang := languages.Language("ES_LA")
messages.DefaultLang = myLang

But in this case, remember that you must ensure that all messages have a fallback in myLang. In case no message is found in any language at all, the final fallback is "T^T". It will not panic.

Creating your own rules

You can also create your own rules. For instance:

MyCustomRule := &safe.RuleSet{
    RuleName: "my own rule!", // this is used only for pretty printing, like fmt.Println("%s", rs)
    MessageFunc: func(rs *safe.RuleSet) string {
        // here, you can return a message for when the input is not valid
        // in case you create your own custom messages, this is the place where you could use them,
        // passing rs.Language as input
        return fmt.Sprintf("why did you input %v?", rs.FieldValue)
    },
    ValidateFunc: func(rs *safe.RuleSet) bool {
        // in this function, you may perform any validation you want!
        userInput, ok := rs.FieldValue.(string)
        if !ok {
            return false
        }

        if userInput == "" {
            return true // in case you return false, the field will be required
        }

        isValid := false
    
        // perform checks...
        
        return isValid
    },
}

someVal := "someVal"

fields := safe.Fields{
    // ...
    {
        Name: "my custom rule",
        Value: someVal,
        Rules: safe.Rules{MyCustomRule, safe.Max(256)},
    }
}

Helper functions

Safe exposes some helper functions that you can use, whether in the context of validation rules or not. They are:

  • safe.All
  • safe.None
  • safe.Some
  • safe.HasValue
  • safe.AllUnique
  • safe.IsStrongPassword
  • safe.DifferenceInDays
  • safe.AnyToFloat64

Please refer to their individual documentations.

Regexes

Safe also exposes some regexes for convenience. They are:

  • safe.WhateverRegex (accepts literally anything)
  • safe.EmailRegex
  • safe.PhoneRegex
  • safe.CpfRegex
  • safe.CnpjRegex
  • safe.CepRegex
  • safe.AddressNumberRegex
  • safe.UUIDRegex
  • safe.NoWhitespaceRegex
  • safe.HasUppercaseRegex
  • safe.HasLowercaseRegex
  • safe.HasDigitRegex
  • safe.HasSpecialCharacterRegex

Please refer to their individual documentations.

Regarding regexes, here is a useful repo: https://github.com/osintbrazuca/osint-brazuca-regex.

Documentation

Index

Constants

This section is empty.

Variables

View Source
var AddressNumberRegex = regexp.MustCompile(`^(?:s\/n|S\/n|S\/N|s\/N)|^(\d)*$`)

numbers or "S/N", "s/n", "S/n", "s/N"

View Source
var CepRegex = regexp.MustCompile(`(^\d{5})\-?(\d{3}$)`)

with or without dash (-)

View Source
var CnpjRegex = regexp.MustCompile(`^(\d{2}\.?\d{3}\.?\d{3}\/?\d{4}\-?\d{2})$`)

with or without symbols (.-/)

View Source
var CpfRegex = regexp.MustCompile(`^\d{3}\.?\d{3}\.?\d{3}\-?\d{2}$`)

with or without symbols (.-/)

View Source
var EmailRegex = regexp.MustCompile(`[^@ \t\r\n]+@[^@ \t\r\n]+\.[^@ \t\r\n]+`)
View Source
var HasDigitRegex = regexp.MustCompile(`[\d]`)
View Source
var HasLowercaseRegex = regexp.MustCompile(`[a-z]`)
View Source
var HasSpecialCharacterRegex = regexp.MustCompile(`[@#$%&*!-+&*]`)
View Source
var HasUppercaseRegex = regexp.MustCompile(`[A-Z]`)
View Source
var NoWhitespaceRegex = regexp.MustCompile(`^\S+$`)
View Source
var PhoneRegex = regexp.MustCompile(`(?:(?:\+|00)?(55)\s?)?(?:\(?([1-9][0-9])\)?\s?)(?:((?:9\d|[2-9])\d{3})\-?(\d{4}))`)

with or without symbols (+-()) and whitespaces

View Source
var UUIDRegex = regexp.MustCompile(`^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-(1|4|5|7)[a-fA-F0-9]{3}-[89abAB][a-fA-F0-9]{3}-[a-fA-F0-9]{12}$`)
View Source
var WhateverRegex = regexp.MustCompile(`.*`)

literally accept anything

Functions

func All

func All(vals ...any) bool

A helper function. All provided arguments must have a valid value (meaning no zero values).

Example usage:

fields := Fields{
	{
		Name:  "Ie",
		Value: e.Ie,
		Rules: safe.Rules(
			safe.Match(safe.IEMGRegex),
			safe.RequiredUnless(safe.All(e.PostalCode, e.Neighborhood, e.StreetType, e.StreetName, e.Number)),
		),
	},
}

In the example above, Ie is required, unless all address info is provided.

To achieve this, the safe.All helper function is used to return a boolean, indicating if all those values are valid non-zero values or not.

Note that this is needed because safe.RequiredUnless satisfies its condition when at least one of its arguments have a value.

On the other hand, with the use of safe.All, we ensure that all those address infos have a non-zero value.

func AllFunc added in v1.4.0

func AllFunc(f func(any) bool, vals ...any) bool

A helper function. All provided arguments must satisfy f(val).

func AllUnique

func AllUnique[T comparable](vals []T) bool

Given a list of values, all of them should be unique.

func AnyToFloat64 added in v1.3.0

func AnyToFloat64(value any) (float64, bool)

A helper function to convert a value of type any to a float64

This can be used in validation functions to easily validate number fields

func DifferenceInDays added in v1.1.0

func DifferenceInDays(dt1, dt2 time.Time) int

A helper function to calculate the difference in days of two datetime values.

Please note that this function will completely ignore the hours, minutes, seconds and so on. It will purely considerer the days.

The order of the arguments does not matter, and which one comes first or last in time does not matter as well.

func HasValue

func HasValue(val any) bool

In safe, the concept of "having a value" is described as follows:

bool: it must be true.

string: it must have more than one rune (or character, if you will).

int, float64, float32: it must not be zero.

time.Time: it must not be the zero time instant, as prescribed by time.Time.IsZero.

struct{}: empty structs are not considered as "having a value"

anything else: is not nil

func HasValue__SkipNumeric added in v1.4.0

func HasValue__SkipNumeric(val any) bool

Exactly the same as safe.HasValue, but skip numeric values.

This means that zero is a valid number

func IsStrongPassword

func IsStrongPassword(password string) bool

A helper function to determine if a password is considered strong.

This means 8+ characters, with lowercase and uppercase letters, numbers and special characters.

func None added in v1.3.0

func None(vals ...any) bool

A helper function. None of the provided arguments should have a valid value (meaning they should all be zero values).

Exactly the opposite of safe.All.

Here is an example of how it might be used:

fields := Fields{
	{
		Name:  "Ie",
		Value: e.Ie,
		Rules: safe.Rules(
			safe.Match(safe.IEMGRegex),
			safe.RequiredIf(safe.None(e.PostalCode, e.Neighborhood, e.StreetType, e.StreetName, e.Number)),
		),
	},
}

In the example above, Ie is required only when none of the address fields have a value.

In essence, we are achieving the same behavior of the example in the safe.All docs.

func NoneFunc added in v1.4.0

func NoneFunc(f func(any) bool, vals ...any) bool

A helper function. None of the provided arguments should satisfy f(val).

func Some added in v1.3.0

func Some(vals ...any) bool

A helper function. At least one of the provided arguments must have a valid value (meaning no zero values).

func SomeFunc added in v1.4.0

func SomeFunc(f func(any) bool, vals ...any) bool

A helper function. At least one of the provided arguments must satisfy f(val).

Types

type ErrorMessages

type ErrorMessages map[string]string

safe.Validate returns (safe.ErrorMessages, bool).

safe.ErrorMessages is a map of field names, each associated with a message.

Example:

fields := safe.Fields{
	{...}
}
errors, ok := Validate(fields)

if !ok {
	fmt.Println("why is username not valid?", errors["Username"])
	fmt.Println("why is email not valid?", errors["Email"])
}

func Validate

func Validate(fields Fields) (ErrorMessages, bool)

Expects Fields, which is just a []*Field. Each Field has a slice of Rules. All of them are evalueted sequentially.

When a Field is not valid, no more validations are performed for that specific field, so we proceed to the next one.

Validate returns two values:

1) ErrorMessages, a map in which the keys correspond to the Field.Name property, and the values are string error messages according to the broken rule.

2) A bool, indicating if all fields are valid or not. In case this is true, ErrorMessages is nil.

Example usage:

fields := safe.Fields{
	{...}
}
errors, ok := Validate(fields)

fmt.Println("are all fields valid?", ok)
fmt.Println("is there any error message?", errors)

func (*ErrorMessages) Error

func (errors *ErrorMessages) Error() string

Implements the error interface. Calls the JSON method and converts it to string.

func (*ErrorMessages) JSON

func (errors *ErrorMessages) JSON() []byte

Returns the JSON version of ErrorMessages

In case of error, returns an empty []byte

type Field

type Field struct {
	// Name is used as a key in the ErrorMessages map when the field is not valid
	Name  string
	Value any
	Rules Rules
}

An individual Field to be validated.

It is highly advisable to use safe.Fields instead, since safe.Validate expects safe.Fields as argument.

func (*Field) SetLanguage added in v1.4.0

func (f *Field) SetLanguage(lang languages.Language) *Field

Set language for all rules in the field.

func (*Field) SetRuleOpts added in v1.4.0

func (f *Field) SetRuleOpts(opts *RuleSetOpts) *Field

Set rule opts for all rules in the field.

func (*Field) String

func (f *Field) String() string

type Fields

type Fields []*Field

A slice of fields to be validated. Each Field has a name, a value and a set of rules.

Example usage:

fields := safe.Fields{
	{
		Name: "Email",
		Value: user.Email,
		Rules: safe.Rules{safe.Requiredi(), safe.Email(), safe.Max(128)},
	},
	{
		Name: "Password",
		Value: user.Password,
		Rules: safe.Rules{safe.Required(), safe.Max(128), safe.StrongPassword()},
	},
}

func (*Fields) GetField added in v1.4.0

func (fields *Fields) GetField(fieldName string) *Field

Retrieves a field by name. Returns nil if not found.

func (*Fields) SetField

func (fields *Fields) SetField(fieldName string, newField *Field) *Fields

Create a new field or update an existing field.

func (*Fields) SetLanguage added in v1.1.0

func (fields *Fields) SetLanguage(lang languages.Language) *Fields

Sets the language for all field rules

If no language is set, it defaults to messages.DefaultLang

func (*Fields) SetRuleOpts added in v1.4.0

func (fields *Fields) SetRuleOpts(fieldNames []string, opts *RuleSetOpts) *Fields

Sets rule set options to the fields matching fieldNames

func (*Fields) SetRuleOptsForAll added in v1.4.0

func (fields *Fields) SetRuleOptsForAll(opts *RuleSetOpts) *Fields

Sets rule set options to all fields

func (*Fields) SetRules

func (fields *Fields) SetRules(fieldName string, rules Rules) *Fields

Set rules for a specific field. This will overwrite the existing rules in the field.

func (*Fields) SetValue

func (fields *Fields) SetValue(fieldName string, value any) *Fields

Set a value for a specific field. This will overwrite the existing value in the field.

func (*Fields) SetValues added in v1.2.0

func (fields *Fields) SetValues(values map[string]any) *Fields

Set values for multiple fields. This will overwrite the existing values in the fields.

The key is the field.Name and the value is the new value to be assigned. Example usage:

fields := safe.Fields{
	{
		Name: "field_1",
		Value: "field_1_value",
		Rules: safe.Rules{...},
	},
	{
		Name: "field_2",
		Value: "field_2_value",
		Rules: safe.Rules{...},
	},
	{
		Name: "field_3",
		Value: "field_3_value",
		Rules: safe.Rules{...},
	},
}
fields.SetValues(map[string]any{
	"field_1":    "another_value_for_field_1",
	"field_2":    "another_value_for_field_2",
	"field_3":    "another_value_for_field_3",
})

func (*Fields) String added in v1.2.0

func (fields *Fields) String() string

type RuleSet

type RuleSet struct {
	RuleName     string
	FieldValue   any
	MessageFunc  func(*RuleSet) string
	ValidateFunc func(*RuleSet) bool // returns true if is valid, false otherwise
	FlowFunc     func(*RuleSet) bool // returns true if should proceed, false otherwise
	Language     languages.Language
	Opts         *RuleSetOpts
}

A safe.RuleSet is what actually validates a value agains a validate func (a "rule" if you will).

It is also responsible for providing error messages thourgh a message func.

An interesting feature of safe.RuleSet is its FlowFunc. It allows for a rule set to control the flow of validations. If the flow func returns true, then proceed with next validations, otherwise stop.

Usualy, safe.RuleSet is not used directly.

This library exposes functions that return a *safe.RuleSet. You can also make your own!

func After

func After(dt time.Time) *RuleSet

The field must be of type time.Time, and it's value should be after the provided datetime.

func Before

func Before(dt time.Time) *RuleSet

The field must be of type time.Time, and it's value should be before the provided datetime.

func CEP

func CEP() *RuleSet

The field must be a string with a valid cep format

func Cnpj

func Cnpj() *RuleSet

The field must be a string with a valid cnpj format

It may or may not include symbols

func Contains added in v1.3.0

func Contains(substr string) *RuleSet

The field value must be of type string

The value of the field must contain substr

func ContainsAll added in v1.3.0

func ContainsAll(substrs ...string) *RuleSet

The field value must be of type string

The value of the field must contain all substrs.

Note that this is different from strings.ContainsAny, because we compare substrings, not unicode code points

func ContainsNone added in v1.3.0

func ContainsNone(substrs ...string) *RuleSet

The field value must be of type string

None of the substrs should be found in the field value

func ContainsSome added in v1.3.0

func ContainsSome(substrs ...string) *RuleSet

The field value must be of type string

The value of the field must contain at least one of substrs

func Cpf

func Cpf() *RuleSet

The field must be a string with a valid cpf format

It may or may not include symbols

func CpfCnpj

func CpfCnpj() *RuleSet

The field must be a string with a valid cpf or cnpj format

It may or may not include symbols

func Email

func Email() *RuleSet

The field must be a string with a valid email format

func EqualTo added in v1.1.0

func EqualTo[T comparable](value T) *RuleSet

The field value must implement the comparable interface.

The value of the field should be equal to the provided value.

This rule behaves exactly like safe.OneOf, but with only one value to compare

func False

func False() *RuleSet

The field must be a bool with value of false

func GreaterThan added in v1.3.0

func GreaterThan[T int | float64 | float32](n T) *RuleSet

The field value must be like T, either an int, a float64 or a float32

The value of the field must be greater than n

func GreaterThanOrEqualTo added in v1.3.0

func GreaterThanOrEqualTo[T int | float64 | float32](n T) *RuleSet

The field value must be like T, either an int, a float64 or a float32

The value of the field must be greater than or equal to n

func LessThan added in v1.3.0

func LessThan[T int | float64 | float32](n T) *RuleSet

The field value must be like T, either an int, a float64 or a float32

The value of the field must be less than n

func LessThanOrEqualTo added in v1.3.0

func LessThanOrEqualTo[T int | float64 | float32](n T) *RuleSet

The field value must be like T, either an int, a float64 or a float32

The value of the field must be greater than or equal to n

func Match

func Match(regexes ...*regexp.Regexp) *RuleSet

The field must be a string that matches all the given regexes.

func MatchList

func MatchList(regexes ...*regexp.Regexp) *RuleSet

The field must be a slice of string, in which all strings match all the given regexes.

func Max

func Max(maxValue int) *RuleSet

The field must be an int, float64, float32 or string.

In case it is a numeric value, it must not greater then maxValue.

As for strings, they must not have more then maxValue number of characters.

func MaxDaysRange

func MaxDaysRange(dt time.Time, maxDays int) *RuleSet

The field must be of type time.Time.

The days range between the value of the field and the provided datetime should not be greater than maxDays.

func Min

func Min(minValue int) *RuleSet

The field must be an int, float64, float32 or string.

In case it is a numeric value, it must not be less then minValue.

As for strings, they must not have less then minValue number of characters.

func NewRuleSet added in v1.4.0

func NewRuleSet(ruleName string) *RuleSet

func NoWhitespace added in v1.0.3

func NoWhitespace() *RuleSet

The field must be a string with no whitespaces

This includes characters like \n (linebreaks) and \t (tabs)

func NotAfter

func NotAfter(dt time.Time) *RuleSet

The field must be of type time.Time, and it's value should not be after the provided datetime.

func NotBefore

func NotBefore(dt time.Time) *RuleSet

The field must be of type time.Time, and it's value should not be before the provided datetime.

func NotContains added in v1.3.0

func NotContains(substr string) *RuleSet

The field value must be of type string

The value of the field must not contain substr

func NotEqualTo added in v1.1.0

func NotEqualTo[T comparable](value T) *RuleSet

Exactly the opposite of safe.EqualTo.

func NotOneOf

func NotOneOf[T comparable](vals []T) *RuleSet

Exactly the opposite of safe.OneOf.

func OneOf

func OneOf[T comparable](vals []T) *RuleSet

The field value must implement the comparable interface.

The value of the field should be equal to at least one of the provided values.

Example usage:

EntityUserTypes := [6]string{"q", "w", "e", "r", "t", "y"}
e := &Entity{UserType: "default", SomeOtherOptionField: 4}

fields := safe.Fields{
	{
		Name:  "UserType",
		Value: e.UserType,
		Rules: safe.Rules{safe.Required(), safe.OneOf(EntityUserTypes[:])},
	},
	{
		Name:  "SomeOtherOptionField",
		Value: e.SomeOtherOptionField,
		Rules: safe.Rules{safe.Required(), safe.OneOf([]int{1, 2, 3})},
	},
}

func Phone

func Phone() *RuleSet

The field must be a string with a valid phone format.

It may or may not include symbols (like +, - and ()) or whitespaces

func Required

func Required() *RuleSet

The field must have a value. Zero values are not allowed, except for boolean fields.

Supported field types: bool, string, int, float64, float32, time.Time

Example:

u := &User{Username: "", BooleanField: false}

fields := safe.Fields{
	{
		Name:  "Username",
		Value: u.Username,
		Rules: safe.Rules{safe.Required().WithOpts(&safe.RuleSetOpts{
			AcceptNumberZero: true,
		})},
	},
	{
		Name:  "BooleanField",
		Value: u.BooleanField,
		Rules: safe.Rules{safe.Required()},
	},
}
errors, ok := safe.Validate(fields)

In the example above, username field will not be valid, but the boolean field is valid, because it has a value. In essence, safe.Required bypasses boolean fields.

To validate boolean fields more specificaly, use safe.True and safe.False.

func RequiredIf added in v1.3.0

func RequiredIf(vals ...any) *RuleSet

The field is not required by default.

However, if any of the provided vals are valid (meaning, if at least of one them have no zero value), then the field becomes required.

Example usage:

fields := safe.Fields{
	{
		Name: "Email",
		Value: user.Email,
		Rules: safe.Rules{safe.Email(), safe.Max(128), safe.RequiredIf(user.Username)},
	},
	{
		Name: "Username",
		Value: user.Username,
		Rules: safe.Rules{safe.Max(128), safe.Min(3)},
	},
}

In the example above, email is required only when username is provided.

func RequiredUnless

func RequiredUnless(vals ...any) *RuleSet

The field is required, just like safe.Required.

However, if any of the provided vals are valid (meaning, if at least of one them have no zero value), then pass.

Example usage:

fields := safe.Fields{
	{
		Name: "Email",
		Value: user.Email,
		Rules: safe.Rules{safe.Email(), safe.Max(128), safe.RequiredUnless(user.Username)},
	},
	{
		Name: "Username",
		Value: user.Username,
		Rules: safe.Rules{safe.Required(), safe.Max(128), safe.Min(3)},
	},
}

In the example above, email is required, unless username is provided.

func StopIf added in v1.4.0

func StopIf(cond bool) *RuleSet

A flow rule.

Stop validation for the field if the provided condition is met.

func StopIfFunc added in v1.4.0

func StopIfFunc(f func(fieldValue any) bool) *RuleSet

A flow rule.

Stop validation for the field if the provided func returns true.

func StopIfNoValue added in v1.4.0

func StopIfNoValue() *RuleSet

A flow rule.

Stop validation for the field if the field has no value.

Example usage:

fields := safe.Fields{
	{
		Name:  "limit",
		Value: filters.Limit,
		Rules: safe.Rules{safe.StopIfNoValue(), safe.GreaterThanOrEqualTo(10), safe.RequiredIf(filters.Offset)},
	},
	{
		Name:  "offset",
		Value: filters.Offset,
		Rules: safe.Rules{safe.StopIfNoValue(), safe.GreaterThanOrEqualTo(0), safe.RequiredIf(filters.Limit)},
	},
	{
		Name:  "order_by",
		Value: filters.OrderBy,
		Rules: safe.Rules{safe.OneOf([]string{"created_at"})},
	},
	{
		Name:  "search",
		Value: filters.Search,
		Rules: safe.Rules{safe.Max(128)},
	},
}

fields.SetRuleOptsForAll(&safe.RuleSetOpts{
	AcceptNumberZero: true,
})

In the example above, email is required only when username is provided.

func StrongPassword

func StrongPassword() *RuleSet

The field must be a string with a strong password pattern.

This means 8+ characters, with lowercase and uppercase letters, numbers and special characters.

func True

func True() *RuleSet

The field must be a bool with value of true

func UUIDstr

func UUIDstr() *RuleSet

The field must be a string with a valid format for a uuid v1, v4, v5 or v7.

In case you are using a uuid package like google's, you will likely not need this, because you will already have a uuid validation method.

Besides that, most of the time the database itself will generate the uuids.

func UniqueList

func UniqueList[T comparable]() *RuleSet

The field must be a slice of values, each of them implementing the comparable interface. All values in the list should be unique.

Must provide type inference.

Example usage:

someList := [6]string{"q", "w", "e", "q", "t", "y"}

fields := safe.Fields{
	{
		Name:  "fieldName",
		Value: someList,
		Rules: safe.Rules{safe.Required(), safe.UniqueList[string]()},
	},
}

IMPORTANT!

Please note that if you pass "any" as type parameter to safe.UniqueList, it will work **only** with explicit []any types. If you pass another type, there will be runtime errors.

func (*RuleSet) HasValue added in v1.4.0

func (rs *RuleSet) HasValue() bool

func (*RuleSet) String

func (rs *RuleSet) String() string

func (*RuleSet) WithFlowFunc added in v1.4.0

func (rs *RuleSet) WithFlowFunc(f func(*RuleSet) bool) *RuleSet

func (*RuleSet) WithMessage

func (rs *RuleSet) WithMessage(msg string) *RuleSet

Modifies a default message from a RuleSet, effectively letting you provide your own custom error messages.

Example usage:

fields := safe.Fields{
	{
		Name:  "Username",
		Value: u.Username,
		Rules: safe.Rules{safe.Required().WithMessage("Why did you leave it blank?")},
	},
}

func (*RuleSet) WithMessageFunc added in v1.4.0

func (rs *RuleSet) WithMessageFunc(f func(*RuleSet) string) *RuleSet

func (*RuleSet) WithOpts added in v1.4.0

func (rs *RuleSet) WithOpts(opts *RuleSetOpts) *RuleSet

func (*RuleSet) WithValidateFunc added in v1.4.0

func (rs *RuleSet) WithValidateFunc(f func(*RuleSet) bool) *RuleSet

type RuleSetOpts added in v1.4.0

type RuleSetOpts struct {
	AcceptNumberZero bool
}

type Rules

type Rules []*RuleSet

func (Rules) String

func (r Rules) String() string

Directories

Path Synopsis
constants
internal

Jump to

Keyboard shortcuts

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