mirror of
https://github.com/kataras/iris.git
synced 2025-01-23 02:31:04 +01:00
add new errors.Intercept package-level function
This commit is contained in:
parent
fabbc271b9
commit
4e3c242044
22
HISTORY.md
22
HISTORY.md
|
@ -24,6 +24,28 @@ 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.
|
||||||
|
|
||||||
|
- New `x/errors.Intercept(func(ctx iris.Context, req *CreateRequest, resp *CreateResponse) error{ ... })` package-level function.
|
||||||
|
|
||||||
|
```go
|
||||||
|
func main() {
|
||||||
|
app := iris.New()
|
||||||
|
|
||||||
|
// Create a new service and pass it to the handlers.
|
||||||
|
service := new(myService)
|
||||||
|
|
||||||
|
app.Post("/", errors.Intercept(responseHandler), errors.CreateHandler(service.Create))
|
||||||
|
|
||||||
|
// [...]
|
||||||
|
}
|
||||||
|
|
||||||
|
func responseHandler(ctx iris.Context, req *CreateRequest, resp *CreateResponse) error {
|
||||||
|
fmt.Printf("intercept: request got: %+v\nresponse sent: %#+v\n", req, resp)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- Rename `x/errors/ContextValidator.ValidateContext(iris.Context) error` to `x/errors/RequestHandler.HandleRequest(iris.Context) error`.
|
||||||
|
|
||||||
# Thu, 18 Jan 2024 | v12.2.10
|
# Thu, 18 Jan 2024 | v12.2.10
|
||||||
|
|
||||||
- Simplify the `/core/host` subpackage and remove its `DeferFlow` and `RestoreFlow` methods. These methods are replaced with: `Supervisor.Configure(host.NonBlocking())` before `Serve` and ` Supervisor.Wait(context.Context) error` after `Serve`.
|
- Simplify the `/core/host` subpackage and remove its `DeferFlow` and `RestoreFlow` methods. These methods are replaced with: `Supervisor.Configure(host.NonBlocking())` before `Serve` and ` Supervisor.Wait(context.Context) error` after `Serve`.
|
||||||
|
|
|
@ -2,6 +2,7 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/kataras/iris/v12"
|
"github.com/kataras/iris/v12"
|
||||||
|
@ -16,7 +17,7 @@ func main() {
|
||||||
// Create a new service and pass it to the handlers.
|
// Create a new service and pass it to the handlers.
|
||||||
service := new(myService)
|
service := new(myService)
|
||||||
|
|
||||||
app.Post("/", createHandler(service)) // OR: errors.CreateHandler(service.Create)
|
app.Post("/", errors.Intercept(afterServiceCallButBeforeDataSent), createHandler(service)) // OR: errors.CreateHandler(service.Create)
|
||||||
app.Get("/", listAllHandler(service)) // OR errors.Handler(service.ListAll, errors.Value(ListRequest{}))
|
app.Get("/", listAllHandler(service)) // OR errors.Handler(service.ListAll, errors.Value(ListRequest{}))
|
||||||
app.Post("/page", listHandler(service)) // OR: errors.ListHandler(service.ListPaginated)
|
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.Delete("/{id:string}", deleteHandler(service)) // OR: errors.NoContentOrNotModifiedHandler(service.DeleteWithFeedback, errors.PathParam[string]("id"))
|
||||||
|
@ -95,7 +96,7 @@ type (
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
// ValidateContext implements the errors.ContextValidator interface.
|
// HandleRequest implements the errors.RequestHandler interface.
|
||||||
// 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.
|
||||||
|
@ -111,7 +112,7 @@ type (
|
||||||
// validation.Slice("hobbies", r.Hobbies).Length(1, 10),
|
// validation.Slice("hobbies", r.Hobbies).Length(1, 10),
|
||||||
// )
|
// )
|
||||||
// }
|
// }
|
||||||
func (r *CreateRequest) ValidateContext(ctx iris.Context) error {
|
func (r *CreateRequest) HandleRequest(ctx iris.Context) error {
|
||||||
// To pass custom validation functions:
|
// To pass custom validation functions:
|
||||||
// return validation.Join(
|
// return validation.Join(
|
||||||
// validation.String("fullname", r.Fullname).Func(customStringFuncHere),
|
// validation.String("fullname", r.Fullname).Func(customStringFuncHere),
|
||||||
|
@ -152,6 +153,20 @@ func (r *CreateRequest) ValidateContext(ctx iris.Context) error {
|
||||||
*/
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
// HandleResponse implements the errors.ResponseHandler interface.
|
||||||
|
func (r *CreateRequest) HandleResponse(ctx iris.Context, resp *CreateResponse) error {
|
||||||
|
fmt.Printf("request got: %+v\nresponse sent: %#+v\n", r, resp)
|
||||||
|
|
||||||
|
return nil // fmt.Errorf("let's fire an internal server error just for the shake of the example") // return nil to continue.
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
func afterServiceCallButBeforeDataSent(ctx iris.Context, req *CreateRequest, resp *CreateResponse) error {
|
||||||
|
fmt.Printf("intercept: request got: %+v\nresponse sent: %#+v\n", req, resp)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (s *myService) Create(ctx context.Context, in CreateRequest) (CreateResponse, error) {
|
func (s *myService) Create(ctx context.Context, in CreateRequest) (CreateResponse, error) {
|
||||||
arr := strings.Split(in.Fullname, " ")
|
arr := strings.Split(in.Fullname, " ")
|
||||||
firstname, lastname := arr[0], arr[1]
|
firstname, lastname := arr[0], arr[1]
|
||||||
|
|
|
@ -141,13 +141,13 @@ 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.
|
// ContextRequestFunc 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.
|
// It is used to validate the context before calling a service function.
|
||||||
//
|
//
|
||||||
// See Validation package-level function.
|
// See Validation package-level function.
|
||||||
type ContextValidatorFunc[T any] func(*context.Context, T) error
|
type ContextRequestFunc[T any] func(*context.Context, T) error
|
||||||
|
|
||||||
const contextValidatorFuncKey = "iris.errors.ContextValidatorFunc"
|
const contextRequestHandlerFuncKey = "iris.errors.ContextRequestHandler"
|
||||||
|
|
||||||
// Validation adds a context validator function to the context.
|
// 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 returns a middleware which can be used to validate the context before calling a service function.
|
||||||
|
@ -164,31 +164,31 @@ const contextValidatorFuncKey = "iris.errors.ContextValidatorFunc"
|
||||||
// validation.Slice("hobbies", r.Hobbies).Length(1, 10),
|
// validation.Slice("hobbies", r.Hobbies).Length(1, 10),
|
||||||
// )
|
// )
|
||||||
// }
|
// }
|
||||||
func Validation[T any](validators ...ContextValidatorFunc[T]) context.Handler {
|
func Validation[T any](validators ...ContextRequestFunc[T]) context.Handler {
|
||||||
validator := joinContextValidators[T](validators)
|
validator := joinContextRequestFuncs[T](validators)
|
||||||
|
|
||||||
return func(ctx *context.Context) {
|
return func(ctx *context.Context) {
|
||||||
ctx.Values().Set(contextValidatorFuncKey, validator)
|
ctx.Values().Set(contextRequestHandlerFuncKey, validator)
|
||||||
ctx.Next()
|
ctx.Next()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func joinContextValidators[T any](validators []ContextValidatorFunc[T]) ContextValidatorFunc[T] {
|
func joinContextRequestFuncs[T any](requestHandlerFuncs []ContextRequestFunc[T]) ContextRequestFunc[T] {
|
||||||
if len(validators) == 0 || validators[0] == nil {
|
if len(requestHandlerFuncs) == 0 || requestHandlerFuncs[0] == nil {
|
||||||
panic("at least one validator is required")
|
panic("at least one context request handler function is required")
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(validators) == 1 {
|
if len(requestHandlerFuncs) == 1 {
|
||||||
return validators[0]
|
return requestHandlerFuncs[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
return func(ctx *context.Context, req T) error {
|
return func(ctx *context.Context, req T) error {
|
||||||
for _, validator := range validators {
|
for _, handler := range requestHandlerFuncs {
|
||||||
if validator == nil {
|
if handler == nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := validator(ctx, req); err != nil {
|
if err := handler(ctx, req); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -197,38 +197,102 @@ func joinContextValidators[T any](validators []ContextValidatorFunc[T]) ContextV
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ContextValidator is an interface which can be implemented by a request payload struct
|
// RequestHandler 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 RequestHandler interface {
|
||||||
ValidateContext(*context.Context) error
|
HandleRequest(*context.Context) error
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateContext[T any](ctx *context.Context, req T) bool {
|
func validateRequest[T any](ctx *context.Context, req T) bool {
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
// Always run the request's validator first,
|
// Always run the request's validator first,
|
||||||
// so dynamic validators can be customized per path and method.
|
// so dynamic validators can be customized per path and method.
|
||||||
if contextValidator, ok := any(&req).(ContextValidator); ok {
|
if contextRequestHandler, ok := any(&req).(RequestHandler); ok {
|
||||||
err = contextValidator.ValidateContext(ctx)
|
err = contextRequestHandler.HandleRequest(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
if v := ctx.Values().Get(contextValidatorFuncKey); v != nil {
|
if v := ctx.Values().Get(contextRequestHandlerFuncKey); v != nil {
|
||||||
if contextValidatorFunc, ok := v.(ContextValidatorFunc[T]); ok {
|
if contextRequestHandlerFunc, ok := v.(ContextRequestFunc[T]); ok && contextRequestHandlerFunc != nil {
|
||||||
err = contextValidatorFunc(ctx, req)
|
err = contextRequestHandlerFunc(ctx, req)
|
||||||
} else if contextValidatorFunc, ok := v.(ContextValidatorFunc[*T]); ok { // or a pointer of T.
|
} else if contextRequestHandlerFunc, ok := v.(ContextRequestFunc[*T]); ok && contextRequestHandlerFunc != nil { // or a pointer of T.
|
||||||
err = contextValidatorFunc(ctx, &req)
|
err = contextRequestHandlerFunc(ctx, &req)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
return err == nil || !HandleError(ctx, err)
|
||||||
if HandleError(ctx, err) {
|
}
|
||||||
return false
|
|
||||||
|
// ResponseHandler is an interface which can be implemented by a request payload struct
|
||||||
|
// in order to handle a response before sending it to the client.
|
||||||
|
type ResponseHandler[R any, RPointer *R] interface {
|
||||||
|
HandleResponse(ctx *context.Context, response RPointer) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// ContextResponseFunc is a function which takes a context, a generic type T and a generic type R and returns an error.
|
||||||
|
type ContextResponseFunc[T, R any, RPointer *R] func(*context.Context, T, RPointer) error
|
||||||
|
|
||||||
|
const contextResponseHandlerFuncKey = "iris.errors.ContextResponseHandler"
|
||||||
|
|
||||||
|
func validateResponse[T, R any, RPointer *R](ctx *context.Context, req T, resp RPointer) bool {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
if contextResponseHandler, ok := any(&req).(ResponseHandler[R, RPointer]); ok {
|
||||||
|
err = contextResponseHandler.HandleResponse(ctx, resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
if v := ctx.Values().Get(contextResponseHandlerFuncKey); v != nil {
|
||||||
|
if contextResponseHandlerFunc, ok := v.(ContextResponseFunc[T, R, RPointer]); ok && contextResponseHandlerFunc != nil {
|
||||||
|
err = contextResponseHandlerFunc(ctx, req, resp)
|
||||||
|
} else if contextResponseHandlerFunc, ok := v.(ContextResponseFunc[*T, R, RPointer]); ok && contextResponseHandlerFunc != nil {
|
||||||
|
err = contextResponseHandlerFunc(ctx, &req, resp)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true
|
return err == nil || !HandleError(ctx, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Intercept adds a context response handler function to the context.
|
||||||
|
// It returns a middleware which can be used to intercept the response before sending it to the client.
|
||||||
|
//
|
||||||
|
// Example Code:
|
||||||
|
//
|
||||||
|
// app.Post("/", errors.Intercept(func(ctx iris.Context, req *CreateRequest, resp *CreateResponse) error{ ... }), errors.CreateHandler(service.Create))
|
||||||
|
func Intercept[T, R any, RPointer *R](responseHandlers ...ContextResponseFunc[T, R, RPointer]) context.Handler {
|
||||||
|
responseHandler := joinContextResponseFuncs[T, R, RPointer](responseHandlers)
|
||||||
|
|
||||||
|
return func(ctx *context.Context) {
|
||||||
|
ctx.Values().Set(contextResponseHandlerFuncKey, responseHandler)
|
||||||
|
ctx.Next()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func joinContextResponseFuncs[T, R any, RPointer *R](responseHandlerFuncs []ContextResponseFunc[T, R, RPointer]) ContextResponseFunc[T, R, RPointer] {
|
||||||
|
if len(responseHandlerFuncs) == 0 || responseHandlerFuncs[0] == nil {
|
||||||
|
panic("at least one context response handler function is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(responseHandlerFuncs) == 1 {
|
||||||
|
return responseHandlerFuncs[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
return func(ctx *context.Context, req T, resp RPointer) error {
|
||||||
|
for _, handler := range responseHandlerFuncs {
|
||||||
|
if handler == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := handler(ctx, req, resp); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func bindResponse[T, R any, F ResponseFunc[T, R]](ctx *context.Context, fn F, fnInput ...T) (R, bool) {
|
func bindResponse[T, R any, F ResponseFunc[T, R]](ctx *context.Context, fn F, fnInput ...T) (R, bool) {
|
||||||
|
@ -247,12 +311,18 @@ func bindResponse[T, R any, F ResponseFunc[T, R]](ctx *context.Context, fn F, fn
|
||||||
panic("invalid number of arguments")
|
panic("invalid number of arguments")
|
||||||
}
|
}
|
||||||
|
|
||||||
if !validateContext(ctx, req) {
|
if !validateRequest(ctx, req) {
|
||||||
var resp R
|
var resp R
|
||||||
return resp, false
|
return resp, false
|
||||||
}
|
}
|
||||||
|
|
||||||
resp, err := fn(ctx, req)
|
resp, err := fn(ctx, req)
|
||||||
|
if err == nil {
|
||||||
|
if !validateResponse(ctx, req, &resp) {
|
||||||
|
return resp, false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return resp, !HandleError(ctx, err)
|
return resp, !HandleError(ctx, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -372,7 +442,7 @@ func List[T, R any, C constraints.Integer | constraints.Float, F ListResponseFun
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
if !validateContext(ctx, filter) {
|
if !validateRequest(ctx, filter) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -383,7 +453,11 @@ func List[T, R any, C constraints.Integer | constraints.Float, F ListResponseFun
|
||||||
}
|
}
|
||||||
|
|
||||||
resp := pagination.NewList(items, int64(totalCount), filter, listOpts)
|
resp := pagination.NewList(items, int64(totalCount), filter, listOpts)
|
||||||
return Handle(ctx, resp, nil)
|
if !validateResponse(ctx, filter, resp) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return Handle(ctx, resp, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListHandler handles a generic response and error from a service paginated call and sends a JSON response to the client.
|
// ListHandler handles a generic response and error from a service paginated call and sends a JSON response to the client.
|
||||||
|
|
Loading…
Reference in New Issue
Block a user