mirror of
https://github.com/kataras/iris.git
synced 2025-01-23 10:41:03 +01:00
add x/errors.List package-level function to support and simplify x/pagination list responses
This commit is contained in:
parent
104bea0a58
commit
746b1fc0da
|
@ -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.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).
|
||||||
- Add `x/errors.OK`, `Create`, `NoContent` and `NoContentOrNotModified` package-level generic functions as custom service method caller helpers. Example can be found [here](_examples/routing/http-wire-errors/service/main.go).
|
- Add `x/errors.OK`, `Create`, `NoContent` and `NoContentOrNotModified` package-level generic functions as custom service method caller helpers. Example can be found [here](_examples/routing/http-wire-errors/service/main.go).
|
||||||
- Add `x/errors.ReadPayload`, `ReadQuery`, `ReadPaginationOptions`, `Handle`, `HandleCreate`, `HandleCreateResponse`, `HandleUpdate` and `HandleDelete` package-level functions as helpers for common actions.
|
- Add `x/errors.ReadPayload`, `ReadQuery`, `ReadPaginationOptions`, `Handle`, `HandleCreate`, `HandleCreateResponse`, `HandleUpdate` and `HandleDelete` package-level functions as helpers for common actions.
|
||||||
|
|
|
@ -7,6 +7,7 @@ import (
|
||||||
"github.com/kataras/iris/v12"
|
"github.com/kataras/iris/v12"
|
||||||
"github.com/kataras/iris/v12/x/errors"
|
"github.com/kataras/iris/v12/x/errors"
|
||||||
"github.com/kataras/iris/v12/x/errors/validation"
|
"github.com/kataras/iris/v12/x/errors/validation"
|
||||||
|
"github.com/kataras/iris/v12/x/pagination"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
@ -14,7 +15,8 @@ func main() {
|
||||||
service := new(myService)
|
service := new(myService)
|
||||||
|
|
||||||
app.Post("/", createHandler(service))
|
app.Post("/", createHandler(service))
|
||||||
app.Get("/", listHandler(service))
|
app.Get("/", listAllHandler(service))
|
||||||
|
app.Post("/page", listHandler(service))
|
||||||
app.Delete("/{id:string}", deleteHandler(service))
|
app.Delete("/{id:string}", deleteHandler(service))
|
||||||
|
|
||||||
app.Listen(":8080")
|
app.Listen(":8080")
|
||||||
|
@ -33,17 +35,23 @@ func createHandler(service *myService) iris.Handler {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func listHandler(service *myService) iris.Handler {
|
func listAllHandler(service *myService) iris.Handler {
|
||||||
return func(ctx iris.Context) {
|
return func(ctx iris.Context) {
|
||||||
// What it does?
|
// What it does?
|
||||||
// 1. If the 3rd variadic (optional) parameter is empty (not our case here), it reads the request body and binds it to the ListRequest struct,
|
// 1. If the 3rd variadic (optional) parameter is empty (not our case here), it reads the request body and binds it to the ListRequest struct,
|
||||||
// otherwise (our case) it calls the service.List function directly with the given input parameter (empty ListRequest struct value in our case).
|
// otherwise (our case) it calls the service.ListAll function directly with the given input parameter (empty ListRequest struct value in our case).
|
||||||
// 2. Calls the service.List function with the ListRequest value.
|
// 2. Calls the service.ListAll function with the ListRequest value.
|
||||||
// 3. If the service.List returns an error, it sends an appropriate error response to the client.
|
// 3. If the service.ListAll returns an error, it sends an appropriate error response to the client.
|
||||||
// 4. If the service.List returns a response, it sets the status code to 200 (OK) and sends the response as a JSON payload to the client.
|
// 4. If the service.ListAll returns a response, it sets the status code to 200 (OK) and sends the response as a JSON payload to the client.
|
||||||
//
|
//
|
||||||
// Useful for get single, fetch multiple and search operations.
|
// Useful for get single, fetch multiple and search operations.
|
||||||
errors.OK(ctx, service.List, ListRequest{})
|
errors.OK(ctx, service.ListAll, ListRequest{})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func listHandler(service *myService) iris.Handler {
|
||||||
|
return func(ctx iris.Context) {
|
||||||
|
errors.List(ctx, service.ListPaginated)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -148,7 +156,7 @@ func (s *myService) Create(ctx context.Context, in CreateRequest) (CreateRespons
|
||||||
type ListRequest struct {
|
type ListRequest struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *myService) List(ctx context.Context, in ListRequest) ([]CreateResponse, error) {
|
func (s *myService) ListAll(ctx context.Context, in ListRequest) ([]CreateResponse, error) {
|
||||||
resp := []CreateResponse{
|
resp := []CreateResponse{
|
||||||
{
|
{
|
||||||
ID: "test-id-1",
|
ID: "test-id-1",
|
||||||
|
@ -167,7 +175,31 @@ func (s *myService) List(ctx context.Context, in ListRequest) ([]CreateResponse,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
return resp, nil //, errors.New("list: test error")
|
return resp, nil //, errors.New("list all: test error")
|
||||||
|
}
|
||||||
|
|
||||||
|
type ListFilter struct {
|
||||||
|
Firstname string `json:"firstname"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *myService) ListPaginated(ctx context.Context, opts pagination.ListOptions, filter ListFilter) ([]CreateResponse, int /* any number type */, error) {
|
||||||
|
all, err := s.ListAll(ctx, ListRequest{})
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
filteredResp := make([]CreateResponse, 0)
|
||||||
|
for _, v := range all {
|
||||||
|
if strings.Contains(v.Firstname, filter.Firstname) {
|
||||||
|
filteredResp = append(filteredResp, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(filteredResp) == opts.GetLimit() {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return filteredResp, len(all), nil // errors.New("list paginated: test error")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *myService) Delete(ctx context.Context, id string) error {
|
func (s *myService) Delete(ctx context.Context, id string) error {
|
||||||
|
|
|
@ -8,6 +8,8 @@ import (
|
||||||
|
|
||||||
"github.com/kataras/iris/v12/context"
|
"github.com/kataras/iris/v12/context"
|
||||||
"github.com/kataras/iris/v12/x/pagination"
|
"github.com/kataras/iris/v12/x/pagination"
|
||||||
|
|
||||||
|
"golang.org/x/exp/constraints"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Handle handles a generic response and error from a service call and sends a JSON response to the context.
|
// Handle handles a generic response and error from a service call and sends a JSON response to the context.
|
||||||
|
@ -121,6 +123,19 @@ type ContextValidator interface {
|
||||||
ValidateContext(*context.Context) error
|
ValidateContext(*context.Context) error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func validateContext(ctx *context.Context, req any) bool {
|
||||||
|
if contextValidator, ok := any(&req).(ContextValidator); ok {
|
||||||
|
err := contextValidator.ValidateContext(ctx)
|
||||||
|
if err != nil {
|
||||||
|
if HandleError(ctx, err) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
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) {
|
||||||
var req T
|
var req T
|
||||||
switch len(fnInput) {
|
switch len(fnInput) {
|
||||||
|
@ -137,15 +152,10 @@ 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 contextValidator, ok := any(&req).(ContextValidator); ok {
|
if !validateContext(ctx, req) {
|
||||||
err := contextValidator.ValidateContext(ctx)
|
|
||||||
if err != nil {
|
|
||||||
if HandleError(ctx, err) {
|
|
||||||
var resp R
|
var resp R
|
||||||
return resp, false
|
return resp, false
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := fn(ctx, req)
|
resp, err := fn(ctx, req)
|
||||||
return resp, !HandleError(ctx, err)
|
return resp, !HandleError(ctx, err)
|
||||||
|
@ -166,6 +176,39 @@ func OK[T, R any, F ResponseFunc[T, R]](ctx *context.Context, fn F, fnInput ...T
|
||||||
return Handle(ctx, resp, nil)
|
return Handle(ctx, resp, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ListResponseFunc is a function which takes a context,
|
||||||
|
// a pagination.ListOptions and a generic type T and returns a slice []R, total count of the items and an error.
|
||||||
|
//
|
||||||
|
// It's used on the List function.
|
||||||
|
type ListResponseFunc[T, R any, C constraints.Integer | constraints.Float] interface {
|
||||||
|
func(stdContext.Context, pagination.ListOptions, T /* filter options */) ([]R, C, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// List handles a generic response and error from a service paginated call and sends a JSON response to the client.
|
||||||
|
// It returns a boolean value indicating whether the handle was successful or not.
|
||||||
|
// If the error is not nil, it calls HandleError to send an appropriate error response to the client.
|
||||||
|
// It reads the pagination.ListOptions from the URL Query and any filter options of generic T from the request body.
|
||||||
|
// It sets the status code to 200 (OK) and sends a *pagination.List[R] response as a JSON payload.
|
||||||
|
func List[T, R any, C constraints.Integer | constraints.Float, F ListResponseFunc[T, R, C]](ctx *context.Context, fn F, fnInput ...T) bool {
|
||||||
|
listOpts, filter, ok := ReadPaginationOptions[T](ctx)
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if !validateContext(ctx, filter) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
items, totalCount, err := fn(ctx, listOpts, filter)
|
||||||
|
if err != nil {
|
||||||
|
HandleError(ctx, err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
resp := pagination.NewList(items, int64(totalCount), filter, listOpts)
|
||||||
|
return Handle(ctx, resp, nil)
|
||||||
|
}
|
||||||
|
|
||||||
// Create handles a create operation and sends a JSON response with the created resource to the client.
|
// Create handles a create operation and sends a JSON response with the created resource to the client.
|
||||||
// It returns a boolean value indicating whether the handle was successful or not.
|
// It returns a boolean value indicating whether the handle was successful or not.
|
||||||
// If the error is not nil, it calls HandleError to send an appropriate error response to the client.
|
// If the error is not nil, it calls HandleError to send an appropriate error response to the client.
|
||||||
|
|
Loading…
Reference in New Issue
Block a user