Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Validation Helpers #4

Open
25 tasks
jimeh opened this issue Sep 4, 2021 · 0 comments
Open
25 tasks

Validation Helpers #4

jimeh opened this issue Sep 4, 2021 · 0 comments

Comments

@jimeh
Copy link
Member

jimeh commented Sep 4, 2021

With helper functions we can make common types of validation much easier. I
propose two categories of helper functions:

  • Value Assertion: Helpers which assert a specific state/property of a given
    value, returning a boolean to indicate if the given value meets the condition
    or not. These functions should typically have a name which starts with Is,
    Has, or similar words. For example: IsNil(), HasLen()
  • Validation: Helpers which will perform a specific type of check against a
    value, and return nil on success, or a *Error on failure. Under the hood
    they'll use Value Assertion functions, along with custom logic as needed.
    These functions should typically have a name which starts with Require or
    Allow. For example: RequireField(), RequireLen(), AllowFieldWhen()

Value Assertion helpers

Value assertion helpers simply return a boolean value indicating if the given
value meets the condition of the helper or not. These can be used directly in
custom Validate() functions, and they are also used within validation helper
functions too.

Functions

Below is rough list of value assertion helpers I think are useful, please
suggest changes to this list as you see fit.

  • IsNil(v interface{}) bool — Checks if given value is nil via reflection.
  • IsEmpty(v interface{}) bool — Checks if given value is equal to the
    type's zero value, or for slices that it is a non-nil empty slice.
  • IsBlank(v interface{}) bool — Checks if given value returns true from
    either of IsNil() or IsEmpty().
  • HasLen(v interface{}, n int) bool — Check if given value has a length of
    exactly n.
  • HasMinLen(v interface{}, min int) bool — Check if given value has a
    length equal to or greater than min.
  • HasMaxLen(v interface{}, max int) bool — Check if given value has a
    length equal to or less than max.
  • HasLenBetween(v interface{}, min, max int) bool — Check if given value
    has a length equal to or greater than min and equal to or less than
    max.

Validation helpers

Validation helpers verify if given values meets specific conditions, returning
nil on success, and a *Error if it fails that describes why the validation
failed.

There are two variants of all validation helpers, the "direct validation" and
"field validation". The direct variants validates the given value directly, and
does not set a Field value on the returned *Error object. While a field
validation helper accepts an instance of a struct, and one or more fields as
strings, and performs the validation against the given field(s) on the given
struct, and sets the Field value as needed on the returned *Error.

Translations / i18n

While I believe providing multi-language translations for built-in validation
helpers is probably outside the scope of this project right now, we should
strive to make translations possible and hopefully easy to implement if/when
needed.

As such, I propose we create *Validation structs for each type of validation,
which contains all the input fields needed to produce a human readable error
message, along with a Error() string function that produces the English
language string for the error.

For example, using the RequireLenBetween(v interface{}, min, max int) error
helper described below, we could have a struct like this:

type RequireLenBetweenValidation struct {
    Min int
    Max int
}

func (s *RequireLenBetweenValidation) Error() string {
	return fmt.Sprintf(
		"must have a length equal to or greater than %d " +
			"and equal to or less than %d",
		s.Min, s.Max,
	)
}

This enables Validate() functions to return *RequireLenBetweenValidation
instances directly which will be wrapped up in a *Error with the field set
correctly.

RequireFieldLenBetween(s interface{}, field string, min, max int) error helper
described below, would simply wrap the validation error struct in a *Error
directly with the field set accordingly.

This should allow easy translation by performing type assertions on the Err
value for each type of validation error you want to support translating.

Though effectively it means validation translations happen on he returned errors
after executing validation, rather than it being built-in to the validation
package itself. It simply exposes enough information about each validation error
to implement your own humanly readable error in any language you see fit.

Functions

Below is rough list of validation helpers I think are useful, please suggest
changes to this list as you see fit.

Require

  • Require(v interface{}) error
  • RequireField(s interface{}, field string) error

Validates value is not nil or empty as determined by IsBlank().

Error message on failure:

"is required"

RequireNotNil

  • RequireNotNil(v interface{}) error
  • RequireFieldNotNil(s interface{}, field string) error

Validates value is not nil as determined by IsNil().

Error message on failure:

"must not be nil"

RequireNotEmpty

  • RequireNotEmpty(v interface{}) error
  • RequireFieldNotEmpty(s interface{}, field string) error

Validates value is not empty as determined by IsEmpty().

Error message on failure:

"must not be empty"

RequireLen

  • RequireLen(v interface{}, n int) error
  • RequireFieldLen(s interface{}, field string, n int) error

Validates value has a length of exactly n.

Error message on failure:

"must have a length of exactly <n>"

RequireMinLen

  • RequireMinLen(v interface{}, min int) error
  • RequireFieldMinLen(s interface{}, field string, min int) error

Validates value has a length equal to or greater than min.

Error message on failure:

"must have a length equal to or greater than <min>"

RequireMaxLen

  • RequireMaxLen(v interface{}, max int) error
  • RequireFieldMaxLen(s interface{}, field string, max int) error

Validates value has a length equal to or less than max.

Error message on failure:

"must have a length equal to or greater than <max>"

RequireLenBetween

  • RequireLenBetween(v interface{}, min, max int) error
  • RequireFieldLenBetween(s interface{}, field string, min, max int) error

Validates that the given value has a length equal to or greater than min and
equal to or less than max.

"must have a length equal to or greater than <min> and equal to or less than <max>"

RequireOneOf

  • RequireOneOf(s interface{}, fields ...string) error

Validates that only one of specified fields on struct s has a non-nil or
non-empty value as determined by IsBlank().

Error message on failure:

"exactly one of 'Field1', 'Field2', or 'Field3' must be specified"

RequireExactlyNOf

  • RequireExactlyNOf(s interface{}, n int, fields ...string) error

Validates that exactly n of specified fields on struct s has a non-nil or
non-empty value as determined by IsBlank().

Error message on failure:

"exactly <n> of 'Field1', 'Field2', or 'Field3' must be specified"

RequireFieldWhen

  • RequireFieldWhen(s interface{}, field, other string, v interface{})

When field other on struct s is equal to value in v, validates that field
field on struct s is not nil or empty as determined by IsBlank().

Error message when other is "Type" and v is "Book":

"is required when Type='Book'"

AllowFieldWhen

  • AllowFieldWhen(s interface{}, field, other string, v interface{})

When field other on struct s is not equal to value in v, validates that
field field on struct s is nil or empty as determined by IsBlank().

Effectively prohibiting setting field field if field other is not equal to
v.

Error message when other is "Type" and v is "Book":

"is only allowed when Type='Book'"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant