Skip to content

Commit 746b1fc

Browse files
committed
add x/errors.List package-level function to support and simplify x/pagination list responses
1 parent 104bea0 commit 746b1fc

File tree

3 files changed

+93
-17
lines changed

3 files changed

+93
-17
lines changed

HISTORY.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ Developers are not forced to upgrade if they don't really need it. Upgrade whene
2323

2424
Changes apply to `main` branch.
2525

26+
- 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.
2627
- 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).
2728
- 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).
2829
- Add `x/errors.ReadPayload`, `ReadQuery`, `ReadPaginationOptions`, `Handle`, `HandleCreate`, `HandleCreateResponse`, `HandleUpdate` and `HandleDelete` package-level functions as helpers for common actions.

_examples/routing/http-wire-errors/service/main.go

Lines changed: 41 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,16 @@ import (
77
"github.com/kataras/iris/v12"
88
"github.com/kataras/iris/v12/x/errors"
99
"github.com/kataras/iris/v12/x/errors/validation"
10+
"github.com/kataras/iris/v12/x/pagination"
1011
)
1112

1213
func main() {
1314
app := iris.New()
1415
service := new(myService)
1516

1617
app.Post("/", createHandler(service))
17-
app.Get("/", listHandler(service))
18+
app.Get("/", listAllHandler(service))
19+
app.Post("/page", listHandler(service))
1820
app.Delete("/{id:string}", deleteHandler(service))
1921

2022
app.Listen(":8080")
@@ -33,17 +35,23 @@ func createHandler(service *myService) iris.Handler {
3335
}
3436
}
3537

36-
func listHandler(service *myService) iris.Handler {
38+
func listAllHandler(service *myService) iris.Handler {
3739
return func(ctx iris.Context) {
3840
// What it does?
3941
// 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,
40-
// otherwise (our case) it calls the service.List function directly with the given input parameter (empty ListRequest struct value in our case).
41-
// 2. Calls the service.List function with the ListRequest value.
42-
// 3. If the service.List returns an error, it sends an appropriate error response to the client.
43-
// 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.
42+
// otherwise (our case) it calls the service.ListAll function directly with the given input parameter (empty ListRequest struct value in our case).
43+
// 2. Calls the service.ListAll function with the ListRequest value.
44+
// 3. If the service.ListAll returns an error, it sends an appropriate error response to the client.
45+
// 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.
4446
//
4547
// Useful for get single, fetch multiple and search operations.
46-
errors.OK(ctx, service.List, ListRequest{})
48+
errors.OK(ctx, service.ListAll, ListRequest{})
49+
}
50+
}
51+
52+
func listHandler(service *myService) iris.Handler {
53+
return func(ctx iris.Context) {
54+
errors.List(ctx, service.ListPaginated)
4755
}
4856
}
4957

@@ -148,7 +156,7 @@ func (s *myService) Create(ctx context.Context, in CreateRequest) (CreateRespons
148156
type ListRequest struct {
149157
}
150158

151-
func (s *myService) List(ctx context.Context, in ListRequest) ([]CreateResponse, error) {
159+
func (s *myService) ListAll(ctx context.Context, in ListRequest) ([]CreateResponse, error) {
152160
resp := []CreateResponse{
153161
{
154162
ID: "test-id-1",
@@ -167,7 +175,31 @@ func (s *myService) List(ctx context.Context, in ListRequest) ([]CreateResponse,
167175
},
168176
}
169177

170-
return resp, nil //, errors.New("list: test error")
178+
return resp, nil //, errors.New("list all: test error")
179+
}
180+
181+
type ListFilter struct {
182+
Firstname string `json:"firstname"`
183+
}
184+
185+
func (s *myService) ListPaginated(ctx context.Context, opts pagination.ListOptions, filter ListFilter) ([]CreateResponse, int /* any number type */, error) {
186+
all, err := s.ListAll(ctx, ListRequest{})
187+
if err != nil {
188+
return nil, 0, err
189+
}
190+
191+
filteredResp := make([]CreateResponse, 0)
192+
for _, v := range all {
193+
if strings.Contains(v.Firstname, filter.Firstname) {
194+
filteredResp = append(filteredResp, v)
195+
}
196+
197+
if len(filteredResp) == opts.GetLimit() {
198+
break
199+
}
200+
}
201+
202+
return filteredResp, len(all), nil // errors.New("list paginated: test error")
171203
}
172204

173205
func (s *myService) Delete(ctx context.Context, id string) error {

x/errors/handlers.go

Lines changed: 51 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ import (
88

99
"github.com/kataras/iris/v12/context"
1010
"github.com/kataras/iris/v12/x/pagination"
11+
12+
"golang.org/x/exp/constraints"
1113
)
1214

1315
// 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 {
121123
ValidateContext(*context.Context) error
122124
}
123125

126+
func validateContext(ctx *context.Context, req any) bool {
127+
if contextValidator, ok := any(&req).(ContextValidator); ok {
128+
err := contextValidator.ValidateContext(ctx)
129+
if err != nil {
130+
if HandleError(ctx, err) {
131+
return false
132+
}
133+
}
134+
}
135+
136+
return true
137+
}
138+
124139
func bindResponse[T, R any, F ResponseFunc[T, R]](ctx *context.Context, fn F, fnInput ...T) (R, bool) {
125140
var req T
126141
switch len(fnInput) {
@@ -137,14 +152,9 @@ func bindResponse[T, R any, F ResponseFunc[T, R]](ctx *context.Context, fn F, fn
137152
panic("invalid number of arguments")
138153
}
139154

140-
if contextValidator, ok := any(&req).(ContextValidator); ok {
141-
err := contextValidator.ValidateContext(ctx)
142-
if err != nil {
143-
if HandleError(ctx, err) {
144-
var resp R
145-
return resp, false
146-
}
147-
}
155+
if !validateContext(ctx, req) {
156+
var resp R
157+
return resp, false
148158
}
149159

150160
resp, err := fn(ctx, req)
@@ -166,6 +176,39 @@ func OK[T, R any, F ResponseFunc[T, R]](ctx *context.Context, fn F, fnInput ...T
166176
return Handle(ctx, resp, nil)
167177
}
168178

179+
// ListResponseFunc is a function which takes a context,
180+
// a pagination.ListOptions and a generic type T and returns a slice []R, total count of the items and an error.
181+
//
182+
// It's used on the List function.
183+
type ListResponseFunc[T, R any, C constraints.Integer | constraints.Float] interface {
184+
func(stdContext.Context, pagination.ListOptions, T /* filter options */) ([]R, C, error)
185+
}
186+
187+
// List handles a generic response and error from a service paginated call and sends a JSON response to the client.
188+
// It returns a boolean value indicating whether the handle was successful or not.
189+
// If the error is not nil, it calls HandleError to send an appropriate error response to the client.
190+
// It reads the pagination.ListOptions from the URL Query and any filter options of generic T from the request body.
191+
// It sets the status code to 200 (OK) and sends a *pagination.List[R] response as a JSON payload.
192+
func List[T, R any, C constraints.Integer | constraints.Float, F ListResponseFunc[T, R, C]](ctx *context.Context, fn F, fnInput ...T) bool {
193+
listOpts, filter, ok := ReadPaginationOptions[T](ctx)
194+
if !ok {
195+
return false
196+
}
197+
198+
if !validateContext(ctx, filter) {
199+
return false
200+
}
201+
202+
items, totalCount, err := fn(ctx, listOpts, filter)
203+
if err != nil {
204+
HandleError(ctx, err)
205+
return false
206+
}
207+
208+
resp := pagination.NewList(items, int64(totalCount), filter, listOpts)
209+
return Handle(ctx, resp, nil)
210+
}
211+
169212
// Create handles a create operation and sends a JSON response with the created resource to the client.
170213
// It returns a boolean value indicating whether the handle was successful or not.
171214
// If the error is not nil, it calls HandleError to send an appropriate error response to the client.

0 commit comments

Comments
 (0)