Validation
Introduction
Good validation prevents bugs and will make your application more secure. Confetti provides several different approaches to validate your application's incoming data.
Writing The Validation Logic
With Confetti it is very easy to validate incoming data. With the first parameter you enter the data you want to validate. Furthermore, you enter fields with rules.
failures := val.Validate(request.App(), request.Content(),
val.Verify("name", rule.Required{}, rule.String{}),
val.Verify("email", rule.Required{}, rule.Email{}),
)
If a validation error occurs, you will receive a slice with errors.
Nested Attributes
If your data contains "nested" parameters, you may specify them in your validation rules using "dot" syntax:
failures := val.Validate(request.App(), request.Content(),
val.Verify("title", rule.Required{}, rule.String{}),
val.Verify("author.name", rule.Required{}),
val.Verify("author.description", rule.Required{}),
)
If you want to validate all fields in a slice or in a map, you can use an asterisk:
failures := val.Validate(request.App(), request.Content(),
val.Verify("orders", rule.Size{Len: 3}),
val.Verify("orders.*.street", rule.Required{}, rule.String{}),
)
Displaying The Validation Errors
So, what if the incoming request parameters do not pass the given validation rules? As mentioned previously, after validation you will receive the errors in a slice.
Return Errors As Response
You can choose to return the errors immediately. In that case, the customer will immediately see the correct HTML page or JSON response.
func UserStore(request inter.Request) inter.Response {
failures := val.Validate(request.App(), request.Content(),
val.Verify("title", rule.Required{}, rule.String{}),
)
if len(failures) > 0 {
return outcome.Html(failures)
}
//
}
You can edit the error view yourself in response_service_provider.go. For more information about adjusting error responses, I refer you to the error documentation.
Use Errors In Views
Or you pass the errors to a view. That way you can, for example, place the errors next to the fields in a form:
func UserStore(request inter.Request) inter.Response {
app := request.App()
content := request.Content()
failures := val.Validate(app, content,
val.Verify("title", rule.Required{}, rule.String{}),
)
return outcome.Html(views.UserCreate(
app,
failures,
content.Get("title").String(),
))
}
Once in the template you can do whatever you want with the errors:
{{- /*gotype: confetti/resources/views.UserCreateView*/ -}}
<h1>Create Post</h1>
{{ if .Failures }}
<div class="alert alert-danger">
<ul>
{{ range $key, $value := .Failures }}
<li>{{ $value }}</li>
{{ end }}
</ul>
</div>
{{ end }}
<!-- Create Post Form -->
Fetch Error By Field
You may also use the Error
function to quickly check if validation error messages exist for a given field:
{{- /*gotype: confetti/resources/views.UserCreateView*/ -}}
<label for="name">User Name</label>
<input id="name" type="text" class="{{ if Error .Failures "name" }} is-invalid {{ end }}">
{{ if Error .Failures "name" }}
<div class="alert">{{ Error .Failures "name" }}</div>
{{ end }}
Custom Validation Rules
Using Rule Objects
Confetti provides a variety of helpful validation rules; however, you may wish to specify some of your own. The rule only needs to meet the inter.Rule
interface with the Verify
method. If the value does not meet the rule, you need to return an error:
package custom_rule
import (
"github.com/confetti-framework/support"
"github.com/confetti-framework/validation/rule"
"strings"
)
type Uppercase struct{}
func (u Uppercase) Verify(value support.Value) error {
val, err := value.StringE()
if err != nil {
return rule.MustBeAStringError
}
if strings.ToUpper(val) == val {
return rule.ValidationError.Wrap("the :attribute must be uppercase")
}
return nil
}
Once the rule has been defined, you may attach it to a validator by passing an instance of the rule object with your other validation rules:
failures := val.Validate(app, content,
val.Verify("title", rule.Required{}, custom_rule.Uppercase{}),
)
Requirements
Should your rule execute other rules first? You can use interface inter.RuleWithRequirements
with method Requirements
to determine which other rules should be executed first. The above example would then become:
package custom_rule
import (
"github.com/confetti-framework/contract/inter"
"github.com/confetti-framework/support"
"github.com/confetti-framework/validation/rule"
"strings"
)
type Uppercase struct{}
func (u Uppercase) Requirements() []inter.Rule {
return []inter.Rule{
rule.String{},
}
}
func (u Uppercase) Verify(value support.Value) error {
if strings.ToUpper(value.String()) == value.String() {
return rule.ValidationError.Wrap("the :attribute must be uppercase")
}
return nil
}
Dependency Injection
If you need dependency injection in your rule? Then you can implement interface inter.RuleWithApp
with method SetApp
to set up inter.AppReader
for dependency injection:
package custom_rule
import (
"github.com/confetti-framework/contract/inter"
"github.com/confetti-framework/support"
)
type TimeZone struct {
app inter.AppReader
}
func (r TimeZone) SetApp(app inter.AppReader) inter.Rule {
r.app = app
return r
}
func (r TimeZone) Verify(value support.Value) error {
currentTimeZone := r.app.Make("config.App.TimeZone")
//
}
Available Validation Rules
- Accepted
- After
- After Or Equal
- Before
- Before Or Equal
- Boolean
- Date
- Date Equals
- Ends With
- Filled
- In
- Integer
- Integer Able
- Map
- Max
- Min
- Present
- Required
- Size
- Slice
- Start With
- String
Accepted
The field under validation must be yes, on, 1, or true. This is useful for validating "Terms of Service" acceptance.
rule.Accepted{}
After
The field under validation must be a value after a given date:
val.Verify("start_date", rule.Required{}, rule.After{Date: carbon.Now().AddDay()}),
Beside Date
, you can enter a normal datetime format (default yyyy-mm-dd HH:MM:SS
/ 2006-01-02 15:04:05
) and a timezone (default Local
):
rule.After{
Date: carbon.Now().AddDay(),
Format: "yyyy-mm-dd HH:MM:SS Z",
TimeZone: "UTC",
}
After Or Equal
The field under validation must be a value after or equal to the given date. For more options, see the After rule.
rule.AfterOrEqual{Date: carbon.Now().AddDay()}
Before
The field under validation must be a value after a given date. For more options, see the After rule.
rule.Before{Date: carbon.Now().AddDay()}
Before Or Equal
The field under validation must be a value preceding or equal to the given date. For more options, see the After rule.
rule.BeforeOrEqual{Date: carbon.Now().AddDay()}
Boolean
The field under validation must be able to be cast as a boolean. Accepted input are true
, false
, 1
, 0
, "1"
, and "0"
.
rule.Boolean{}
Date
The field under validation must be a valid date according to the format (default format is yyyy-mm-dd HH:MM:SS
/ 2006-01-02 15:04:05
).
rule.Date{Format: carbon.HourMinuteFormat}
Date Equals
The field under validation must be equal to the given date. For more options, see the After rule.
rule.DateEqual{Date: carbon.Now(), Format: carbon.DateFormat}
Ends With
The field under validation must end with one of the given values.
rule.Ends{}.With(".com", ".nl")
Filled
The field under validation must not be empty when it is present.
rule.Filled{}
In
The field under validation must be included in the given list of values.
rule.In{}.With("admin", "manager")
Integer
The field under validation must be an integer.
rule.Integer{}
Integer Able
The field being validated can be cast to an integer.
rule.IntegerAble{}
Map
The field under validation must be a map
.
rule.Map{}
Max
The field under validation must be less than or equal to a maximum amount of items in a map or slice or maximum number.
val.Verify("age", rule.Integer{}, rule.Max{Len: 120}),
val.Verify("items", rule.Slice{}, rule.Max{Len: 5}),
Min
The field under validation must be at least amount of items in a map or slice or maximum number.
val.Verify("age", rule.Integer{}, rule.Min{Len: 0}),
val.Verify("items", rule.Slice{}, rule.Min{Len: 5}),
Present
The field under validation must be present in the input data but can be empty.
rule.Present{}
Required
The field under validation must be present in the input data and not empty.
rule.Required{}
A field is considered "empty" if one of the following conditions are true:
- The value is
nil
.- The value is an empty
string
.- The value is an empty
slice
or emptymap
object.
Size
The field under validation must have a size matching the given value.
Validate that a provided integer equals 10...
val.Verify("age", rule.Integer{}, rule.Size{Len: 12}),
Validate that an slice has exactly 5 elements...
val.Verify("items", rule.Slice{}, rule.Size{Len: 5}),
Slice
The field under validation must be a slice
.
rule.Slice{}
Start With
The field under validation must start with one of the given values.
rule.Start{}.With("06-")
String
The field under validation must be a string
.
rule.String{}