mirror of
https://github.com/kataras/iris.git
synced 2025-02-02 07:20:35 +01:00
add x/errors.Validation
This commit is contained in:
parent
ad75479541
commit
d32eb68ed4
|
@ -23,6 +23,7 @@ Developers are not forced to upgrade if they don't really need it. Upgrade whene
|
||||||
|
|
||||||
Changes apply to `main` branch.
|
Changes apply to `main` branch.
|
||||||
|
|
||||||
|
- Add `x/errors.Validation` package-level function to add one or more validations for the request payload before a service call of the below methods.
|
||||||
- Add `x/errors.Handler`, `CreateHandler`, `NoContentHandler`, `NoContenetOrNotModifiedHandler` and `ListHandler` ready-to-use handlers for service method calls to Iris Handler.
|
- Add `x/errors.Handler`, `CreateHandler`, `NoContentHandler`, `NoContenetOrNotModifiedHandler` and `ListHandler` ready-to-use handlers for service method calls to Iris Handler.
|
||||||
- Add `x/errors.List` package-level function to support `ListObjects(ctx context.Context, opts pagination.ListOptions, f Filter) ([]Object, int64, error)` type of service calls.
|
- Add `x/errors.List` package-level function to support `ListObjects(ctx context.Context, opts pagination.ListOptions, f Filter) ([]Object, int64, error)` type of service calls.
|
||||||
- Simplify how validation errors on `/x/errors` package works. A new `x/errors/validation` sub-package added to make your life easier (using the powerful Generics feature).
|
- Simplify how validation errors on `/x/errors` package works. A new `x/errors/validation` sub-package added to make your life easier (using the powerful Generics feature).
|
||||||
|
|
|
@ -12,14 +12,35 @@ import (
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
app := iris.New()
|
app := iris.New()
|
||||||
|
|
||||||
|
/*
|
||||||
|
service := new(myService)
|
||||||
|
|
||||||
|
app.Post("/", createHandler(service)) // OR: errors.CreateHandler(service.Create)
|
||||||
|
app.Get("/", listAllHandler(service)) // OR errors.Handler(service.ListAll, errors.Value(ListRequest{}))
|
||||||
|
app.Post("/page", listHandler(service)) // OR: errors.ListHandler(service.ListPaginated)
|
||||||
|
app.Delete("/{id:string}", deleteHandler(service)) // OR: errors.NoContentOrNotModifiedHandler(service.DeleteWithFeedback, errors.PathParam[string]("id"))
|
||||||
|
*/
|
||||||
|
|
||||||
|
app.PartyConfigure("/", Party())
|
||||||
|
app.Listen(":8080")
|
||||||
|
}
|
||||||
|
|
||||||
|
func Party() *party {
|
||||||
|
return &party{}
|
||||||
|
}
|
||||||
|
|
||||||
|
type party struct{}
|
||||||
|
|
||||||
|
func (p *party) Configure(r iris.Party) {
|
||||||
service := new(myService)
|
service := new(myService)
|
||||||
|
|
||||||
app.Post("/", createHandler(service)) // OR: errors.CreateHandler(service.Create)
|
r.Post("/", createHandler(service)) // OR: errors.CreateHandler(service.Create)
|
||||||
app.Get("/", listAllHandler(service)) // OR errors.Handler(service.ListAll, errors.Value(ListRequest{}))
|
|
||||||
app.Post("/page", listHandler(service)) // OR: errors.ListHandler(service.ListPaginated)
|
|
||||||
app.Delete("/{id:string}", deleteHandler(service)) // OR: errors.NoContentOrNotModifiedHandler(service.DeleteWithFeedback, errors.PathParam[string]("id"))
|
|
||||||
|
|
||||||
app.Listen(":8080")
|
// add a custom validation function for the CreateRequest struct.
|
||||||
|
r.Get("/", listAllHandler(service)) // OR errors.Handler(service.ListAll, errors.Value(ListRequest{}))
|
||||||
|
r.Post("/page", listHandler(service)) // OR: errors.ListHandler(service.ListPaginated)
|
||||||
|
r.Delete("/{id:string}", deleteHandler(service)) // OR: errors.NoContentOrNotModifiedHandler(service.DeleteWithFeedback, errors.PathParam[string]("id"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func createHandler(service *myService) iris.Handler {
|
func createHandler(service *myService) iris.Handler {
|
||||||
|
@ -97,6 +118,18 @@ type (
|
||||||
// It validates the request body and returns an error if the request body is invalid.
|
// It validates the request body and returns an error if the request body is invalid.
|
||||||
// You can also alter the "r" CreateRequest before calling the service method,
|
// You can also alter the "r" CreateRequest before calling the service method,
|
||||||
// e.g. give a default value to a field if it's empty or set an ID based on a path parameter.
|
// e.g. give a default value to a field if it's empty or set an ID based on a path parameter.
|
||||||
|
// OR
|
||||||
|
// Custom function per route:
|
||||||
|
//
|
||||||
|
// r.Post("/", errors.Validation(validateCreateRequest), createHandler(service))
|
||||||
|
// [more code here...]
|
||||||
|
// func validateCreateRequest(ctx iris.Context, r *CreateRequest) error {
|
||||||
|
// return validation.Join(
|
||||||
|
// validation.String("fullname", r.Fullname).NotEmpty().Fullname().Length(3, 50),
|
||||||
|
// validation.Number("age", r.Age).InRange(18, 130),
|
||||||
|
// validation.Slice("hobbies", r.Hobbies).Length(1, 10),
|
||||||
|
// )
|
||||||
|
// }
|
||||||
func (r *CreateRequest) ValidateContext(ctx iris.Context) error {
|
func (r *CreateRequest) ValidateContext(ctx iris.Context) error {
|
||||||
// To pass custom validation functions:
|
// To pass custom validation functions:
|
||||||
// return validation.Join(
|
// return validation.Join(
|
||||||
|
|
|
@ -117,22 +117,93 @@ type ResponseOnlyErrorFunc[T any] interface {
|
||||||
func(stdContext.Context, T) error
|
func(stdContext.Context, T) error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ContextValidatorFunc is a function which takes a context and a generic type T and returns an error.
|
||||||
|
// It is used to validate the context before calling a service function.
|
||||||
|
//
|
||||||
|
// See Validation package-level function.
|
||||||
|
type ContextValidatorFunc[T any] func(*context.Context, T) error
|
||||||
|
|
||||||
|
const contextValidatorFuncKey = "iris.errors.ContextValidatorFunc"
|
||||||
|
|
||||||
|
// Validation adds a context validator function to the context.
|
||||||
|
// It returns a middleware which can be used to validate the context before calling a service function.
|
||||||
|
// It panics if the given validators are empty or nil.
|
||||||
|
//
|
||||||
|
// Example:
|
||||||
|
//
|
||||||
|
// r.Post("/", Validation(validateCreateRequest), createHandler(service))
|
||||||
|
//
|
||||||
|
// func validateCreateRequest(ctx iris.Context, r *CreateRequest) error {
|
||||||
|
// return validation.Join(
|
||||||
|
// validation.String("fullname", r.Fullname).NotEmpty().Fullname().Length(3, 50),
|
||||||
|
// validation.Number("age", r.Age).InRange(18, 130),
|
||||||
|
// validation.Slice("hobbies", r.Hobbies).Length(1, 10),
|
||||||
|
// )
|
||||||
|
// }
|
||||||
|
func Validation[T any](validators ...ContextValidatorFunc[T]) context.Handler {
|
||||||
|
validator := joinContextValidators[T](validators)
|
||||||
|
|
||||||
|
return func(ctx *context.Context) {
|
||||||
|
ctx.Values().Set(contextValidatorFuncKey, validator)
|
||||||
|
ctx.Next()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func joinContextValidators[T any](validators []ContextValidatorFunc[T]) ContextValidatorFunc[T] {
|
||||||
|
if len(validators) == 0 || validators[0] == nil {
|
||||||
|
panic("at least one validator is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(validators) == 1 {
|
||||||
|
return validators[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
return func(ctx *context.Context, req T) error {
|
||||||
|
for _, validator := range validators {
|
||||||
|
if validator == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := validator(ctx, req); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ContextValidator is an interface which can be implemented by a request payload struct
|
// ContextValidator is an interface which can be implemented by a request payload struct
|
||||||
// in order to validate the context before calling a service function.
|
// in order to validate the context before calling a service function.
|
||||||
type ContextValidator interface {
|
type ContextValidator interface {
|
||||||
ValidateContext(*context.Context) error
|
ValidateContext(*context.Context) error
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateContext(ctx *context.Context, req any) bool {
|
func validateContext[T any](ctx *context.Context, req T) bool {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
// Always run the request's validator first,
|
||||||
|
// so dynamic validators can be customized per path and method.
|
||||||
if contextValidator, ok := any(&req).(ContextValidator); ok {
|
if contextValidator, ok := any(&req).(ContextValidator); ok {
|
||||||
err := contextValidator.ValidateContext(ctx)
|
err = contextValidator.ValidateContext(ctx)
|
||||||
if err != nil {
|
}
|
||||||
if HandleError(ctx, err) {
|
|
||||||
return false
|
if err == nil {
|
||||||
|
if v := ctx.Values().Get(contextValidatorFuncKey); v != nil {
|
||||||
|
if contextValidatorFunc, ok := v.(ContextValidatorFunc[T]); ok {
|
||||||
|
err = contextValidatorFunc(ctx, req)
|
||||||
|
} else if contextValidatorFunc, ok := v.(ContextValidatorFunc[*T]); ok { // or a pointer of T.
|
||||||
|
err = contextValidatorFunc(ctx, &req)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
if HandleError(ctx, err) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user