Skip to content

Latest commit

 

History

History
487 lines (359 loc) · 18.8 KB

validation.md

File metadata and controls

487 lines (359 loc) · 18.8 KB

Validation

[[toc]]

Introduction

Goravel provides several different approaches to validate your application's incoming data. It is most common to use the Validate method available on all incoming HTTP requests. Goravel includes a wide variety of convenient validation rules.

Validation Quickstart

Let's take a closer look at Goravel's powerful validation features by examining a complete example of how to validate a form and return error messages to the user. This overview will provide you with a general understanding of how to validate incoming request data using Goravel.

Defining The Routes

First, let's assume we have the following routes defined in our routes/web.go file:

import "goravel/app/http/controllers"

postController := controllers.NewPostController()
facades.Route().Get("/post/create", postController.Create)
facades.Route().Post("/post", postController.Store)

The GET route displays a form for creating a new blog post. The POST route stores the new post in the database.

Creating The Controller

Next, let's take a look at a simple controller that handles incoming requests to these routes. We'll leave the Store method empty for now:

package controllers

import (
  "github.com/goravel/framework/contracts/http"
)

type PostController struct {
  // Dependent services
}

func NewPostController() *PostController {
  return &PostController{
    // Inject services
  }
}

func (r *PostController) Create(ctx http.Context) {

}

func (r *PostController) Store(ctx http.Context) {

}

Writing The Validation Logic

Now we are ready to fill in our Store method with the logic to validate the new blog post.

func (r *PostController) Store(ctx http.Context) {
  validator, err := ctx.Request().Validate(map[string]string{
    "title": "required|max_len:255",
    "body": "required",
  })
}

A Note On Nested Attributes

If the incoming HTTP request contains "nested" field data, you may specify these fields in your validation rules using the "dot" syntax:

validator, err := ctx.Request().Validate(map[string]string{
  "title": "required|max_len:255",
  "author.name": "required",
  "author.description": "required",
})

Form Request Validation

Creating Form Requests

For more complex validation scenarios, you may wish to create a "form request". Form requests are custom request classes that encapsulate their own validation and authorization logic. To create a form request class, you may use the make:request Artisan CLI command:

go run . artisan make:request StorePostRequest
go run . artisan make:request user/StorePostRequest

The generated form request class will be placed in the app/http/requests directory. If this directory does not exist, it will be created when you run the make:request command. Each form request generated by Goravel has five methods: Authorize, Rules, Messages, Attributes and PrepareForValidation.

As you might have guessed, the Authorize method is responsible for determining if the currently authenticated user can perform the action represented by the request, while the Rules method returns the validation rules that should apply to the request's data:

package requests

import (
  "github.com/goravel/framework/contracts/http"
  "github.com/goravel/framework/contracts/validation"
)

type StorePostRequest struct {
  Name string `form:"name" json:"name"`
}

func (r *StorePostRequest) Authorize(ctx http.Context) error {
  return nil
}

func (r *StorePostRequest) Rules(ctx http.Context) map[string]string {
  return map[string]string{
    // The keys are consistent with the incoming keys.
    "name": "required|max_len:255",
  }
}

func (r *StorePostRequest) Messages(ctx http.Context) map[string]string {
  return map[string]string{}
}

func (r *StorePostRequest) Attributes(ctx http.Context) map[string]string {
  return map[string]string{}
}

func (r *StorePostRequest) PrepareForValidation(ctx http.Context, data validation.Data) error {
  return nil
}

So, how are the validation rules evaluated? All you need to do is type-hint the request on your controller method. The incoming form request is validated before the controller method is called, meaning you do not need to clutter your controller with any validation logic:

func (r *PostController) Store(ctx http.Context) {
  var storePost requests.StorePostRequest
  errors, err := ctx.Request().ValidateRequest(&storePost)
}

Note that since form passed values ​​are of string type by default, all fields in request should also be of string type, otherwise please use JSON to pass values.

Authorizing Form Requests

The form request class also contains an Authorize method. Within this method, you may determine if the authenticated user actually has the authority to update a given resource. For example, you may determine if a user actually owns a blog comment they are attempting to update. Most likely, you will interact with your authorization gates and policies within this method:

func (r *StorePostRequest) Authorize(ctx http.Context) error {
  var comment models.Comment
  facades.Orm().Query().First(&comment)
  if comment.ID == 0 {
    return errors.New("no comment is found")
  }

  if !facades.Gate().Allows("update", map[string]any{
    "comment": comment,
  }) {
    return errors.New("can't update comment")
  }

  return nil
}

error will be passed to the return value of ctx.Request().ValidateRequest.

Customizing The Error Messages

You may customize the error messages used by the form request by overriding the Messages method. This method should return an array of attribute / rule pairs and their corresponding error messages:

func (r *StorePostRequest) Messages() map[string]string {
  return map[string]string{
    "title.required": "A title is required",
    "body.required": "A message is required",
  }
}

Customizing The Validation Attributes

Many of Goravel's built-in validation rule error messages contain an :attribute placeholder. If you would like the :attribute placeholder of your validation message to be replaced with a custom attribute name, you may specify the custom names by overriding the Attributes method. This method should return an array of attribute / name pairs:

func (r *StorePostRequest) Attributes() map[string]string {
  return map[string]string{
    "email": "email address",
  }
}

Preparing Input For Validation

If you need to prepare or sanitize any data from the request before you apply your validation rules, you may use the PrepareForValidation method:

func (r *StorePostRequest) PrepareForValidation(data validation.Data) error {
  if name, exist := data.Get("name"); exist {
    return data.Set("name", name.(string)+"1")
  }
  return nil
}

Manually Creating Validators

If you do not want to use the Validate method on the request, you may create a validator instance manually using the facades.Validator. The Make method of the facade generates a new validator instance:

func (r *PostController) Store(ctx http.Context) http.Response {
  validator, _ := facades.Validation().Make(
    map[string]any{
      "name": "Goravel",
    },
    map[string]string{
      "title": "required|max_len:255",
      "body":  "required",
    })

  if validator.Fails() {
    // Return fail
  }

  var user models.User
  err := validator.Bind(&user)
  ...
}

The first argument passed to the Make method is the data under validation which can be map[string]any or struct. The second argument is an array of validation rules to be applied to the data.

Customizing The Error Messages

If needed, you may provide custom error messages that a validator instance should use instead of the default error messages provided by Goravel. You may pass the custom messages as the third argument to the Make method (also applicable to ctx.Request().Validate()):

validator, err := facades.Validation().Make(input, rules, validation.Messages(map[string]string{
  "required": "The :attribute field is required.",
}))

Specifying A Custom Message For A Given Attribute

Sometimes you may wish to specify a custom error message only for a specific attribute. You may do so using "dot" notation. Specify the attribute's name first, followed by the rule (also applicable to ctx.Request().Validate()):

validator, err := facades.Validation().Make(input, rules, validation.Messages(map[string]string{
  "email.required": "We need to know your email address!",
}))

Specifying Custom Attribute Values

Many of Goravel's built-in error messages include an :attribute placeholder that is replaced with the name of the field or attribute under validation. To customize the values used to replace these placeholders for specific fields, you may pass an array of custom attributes as the third argument to the Make method (also applicable to ctx.Request().Validate()):

validator, err := facades.Validation().Make(input, rules, validation.Attributes(map[string]string{
  "email": "email address",
}))

Format Data Before Validation

You can format the data before validating the data for more flexible data validation, and you can pass the method of formatting the data as the third parameter to the Make method (also applicable to ctx.Request().Validate()):

import (
  validationcontract "github.com/goravel/framework/contracts/validation"
  "github.com/goravel/framework/validation"
)

func (r *PostController) Store(ctx http.Context) http.Response {
  validator, err := facades.Validation().Make(input, rules,
    validation.PrepareForValidation(func(data validationcontract.Data) error {
      if name, exist := data.Get("name"); exist {
        return data.Set("name", name)
      }

      return nil
    }))

  ...
}

Working With Validated Input

After validating incoming request data using form requests or manually created validator instances, you still want to bind the request data to a struct, there are two ways to do this:

  1. Use the Bind method, this will bind all incoming data, including unvalidated data:
validator, err := ctx.Request().Validate(rules)
var user models.User
err := validator.Bind(&user)

validator, err := facades.Validation().Make(input, rules)
var user models.User
err := validator.Bind(&user)
  1. The incoming data is automatically bound to the form when you use request for validation:
var storePost requests.StorePostRequest
errors, err := ctx.Request().ValidateRequest(&storePost)
fmt.Println(storePost.Name)

Working With Error Messages

Retrieving one Error Message For A Field (Random)

validator, err := ctx.Request().Validate(rules)
validator, err := facades.Validation().Make(input, rules)

message := validator.Errors().One("email")

Retrieving All Error Messages For A Field

messages := validator.Errors().Get("email")

Retrieving All Error Messages For All Fields

messages := validator.Errors().All()

Determining If Error Messages Exist For A Field

if validator.Errors().Has("email") {
  //
}

Available Validation Rules

Below is a list of all available validation rules and their function:

Name Description
required Check value is required and cannot be empty.
required_if required_if:anotherfield,value,... The field under validation must be present and not empty if the anotherField field is equal to any value.
required_unless required_unless:anotherfield,value,... The field under validation must be present and not empty unless the anotherField field is equal to any value.
required_with required_with:foo,bar,... The field under validation must be present and not empty only if any of the other specified fields are present.
required_with_all required_with_all:foo,bar,... The field under validation must be present and not empty only if all of the other specified fields are present.
required_without required_without:foo,bar,... The field under validation must be present and not empty only when any of the other specified fields are not present.
required_without_all required_without_all:foo,bar,... The field under validation must be present and not empty only when all of the other specified fields are not present.
int Check value is intX uintX type, and support size checking. eg: int int:2 int:2,12. Notice: Points for using rules
uint Check value is uint(uintX) type, value >= 0
bool Check value is bool string(true: "1", "on", "yes", "true", false: "0", "off", "no", "false").
string Check value is string type, and support size checking. eg:string string:2 string:2,12
float Check value is float(floatX) type
slice Check value is slice type([]intX []uintX []byte []string)
in in:foo,bar,… Check if the value is in the given enumeration
not_in not_in:foo,bar,… Check if the value is not in the given enumeration
starts_with starts_with:foo Check if the input string value is starts with the given sub-string
ends_with ends_with:foo Check if the input string value is ends with the given sub-string
between between:min,max Check that the value is a number and is within the given range
max max:value Check value is less than or equal to the given value(intX uintX floatX)
min min:value Check value is greater than or equal to the given value(intX uintX floatX)
eq eq:value Check that the input value is equal to the given value
ne ne:value Check that the input value is not equal to the given value
lt lt:value Check value is less than the given value(intX uintX floatX)
gt gt:value Check value is greater than the given value(intX uintX floatX)
len len:value Check value length is equals to the given size(string array slice map)
min_len min_len:value Check the minimum length of the value is the given size(string array slice map)
max_len max_len:value Check the maximum length of the value is the given size(string array slice map)
email Check value is email address string
array Check value is array, slice type
map Check value is a MAP type
eq_field eq_field:field Check that the field value is equals to the value of another field
ne_field ne_field:field Check that the field value is not equals to the value of another field
gt_field gte_field:field Check that the field value is greater than the value of another field
gte_field gt_field:field Check that the field value is greater than or equal to the value of another field
lt_field lt_field:field Check that the field value is less than the value of another field
lte_field lte_field:field Check if the field value is less than or equal to the value of another field
file Verify if it is an uploaded file
image Check if it is an uploaded image file and support suffix check
date Check the field value is date string
gt_date gt_date:value Check that the input value is greater than the given date string
lt_date lt_date:value Check that the input value is less than the given date string
gte_date gte_date:value Check that the input value is greater than or equal to the given date string
lte_date lte_date:value Check that the input value is less than or equal to the given date string
alpha Verify that the value contains only alphabetic characters
alpha_num Check that only letters, numbers are included
alpha_dash Check to include only letters, numbers, dashes ( - ), and underscores ( _ )
json Check value is JSON string
number Check value is number string >= 0
full_url Check value is full URL string(must start with http,https)
ip Check value is IP(v4 or v6) string
ipv4 Check value is IPv4 string
ipv6 Check value is IPv6 string

Custom Validation Rules

Goravel provides a variety of helpful validation rules; however, you may wish to specify some of your own. One method of registering custom validation rules is using rule objects. To generate a new rule object, you can simply use the make:rule Artisan command.

For instance, if you want to verify that a string is uppercase, you can create a rule with this command. Goravel will then save this new rule in the app/rules directory. If this directory does not exist, Goravel will create it when you run the Artisan command to create your rule.

go run . artisan make:rule Uppercase
go run . artisan make:rule user/Uppercase

After creating the rule, we need to define its behavior. A rule object has two methods: Passes and Message. The Passes method receives all data, including the data to be validated and the validation parameters. It should return true or false depending on whether the attribute value is valid. The Message method should return the error message for validation that should be used when the validation fails.

package rules

import (
  "strings"

  "github.com/goravel/framework/contracts/validation"
)

type Uppercase struct {
}

// Signature The name of the rule.
func (receiver *Uppercase) Signature() string {
  return "uppercase"
}

// Passes Determine if the validation rule passes.
func (receiver *Uppercase) Passes(data validation.Data, val any, options ...any) bool {
  return strings.ToUpper(val.(string)) == val.(string)
}

// Message Get the validation error message.
func (receiver *Uppercase) Message() string {
  return "The :attribute must be uppercase."
}

Then you need to register the rule to the rules method in the app/providers/validation_service_provider.go file, and the rule can be used like other rules:

package providers

import (
  "github.com/goravel/framework/contracts/validation"
  "github.com/goravel/framework/facades"

  "goravel/app/rules"
)

type ValidationServiceProvider struct {
}

func (receiver *ValidationServiceProvider) Register() {

}

func (receiver *ValidationServiceProvider) Boot() {
  if err := facades.Validation().AddRules(receiver.rules()); err != nil {
    facades.Log().Errorf("add rules error: %+v", err)
  }
}

func (receiver *ValidationServiceProvider) rules() []validation.Rule {
  return []validation.Rule{
    &rules.Uppercase{},
  }
}

Points For Using Rules

int

When using ctx.Request().Validate(rules) for validation, the incoming int type data will be parsed by json.Unmarshal into float64 type, which will cause the int rule validation to fail.

Solutions

Option 1: Add validation.PrepareForValidation, format the data before validating the data;

Option 2: Use facades.Validation().Make() for rule validation;