Skip to content

Commit ad75479

Browse files
committed
add x/errors.Handler, CreateHandler, NoContentHandler, NoContenetOrNotModifiedHandler and ListHandler ready-to-use handlers for service method calls to Iris Handler
1 parent 7024f61 commit ad75479

File tree

7 files changed

+161
-18
lines changed

7 files changed

+161
-18
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ jobs:
2424
uses: actions/checkout@v4
2525

2626
- name: Set up Go 1.x
27-
uses: actions/setup-go@v4
27+
uses: actions/setup-go@v5
2828
with:
2929
go-version-file: './go.mod'
3030
check-latest: true

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.Handler`, `CreateHandler`, `NoContentHandler`, `NoContenetOrNotModifiedHandler` and `ListHandler` ready-to-use handlers for service method calls to Iris Handler.
2627
- 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.
2728
- 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).
2829
- 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).

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

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,10 @@ func main() {
1414
app := iris.New()
1515
service := new(myService)
1616

17-
app.Post("/", createHandler(service))
18-
app.Get("/", listAllHandler(service))
19-
app.Post("/page", listHandler(service))
20-
app.Delete("/{id:string}", deleteHandler(service))
17+
app.Post("/", createHandler(service)) // OR: errors.CreateHandler(service.Create)
18+
app.Get("/", listAllHandler(service)) // OR errors.Handler(service.ListAll, errors.Value(ListRequest{}))
19+
app.Post("/page", listHandler(service)) // OR: errors.ListHandler(service.ListPaginated)
20+
app.Delete("/{id:string}", deleteHandler(service)) // OR: errors.NoContentOrNotModifiedHandler(service.DeleteWithFeedback, errors.PathParam[string]("id"))
2121

2222
app.Listen(":8080")
2323
}
@@ -59,9 +59,9 @@ func deleteHandler(service *myService) iris.Handler {
5959
return func(ctx iris.Context) {
6060
id := ctx.Params().Get("id")
6161
// What it does?
62-
// 1. Calls the service.Delete function with the given input parameter.
63-
// 2. If the service.Delete returns an error, it sends an appropriate error response to the client.
64-
// 3.If the service.Delete doesn't return an error then it sets the status code to 204 (No Content) and
62+
// 1. Calls the service.DeleteWithFeedback function with the given input parameter.
63+
// 2. If the service.DeleteWithFeedback returns an error, it sends an appropriate error response to the client.
64+
// 3.If the service.DeleteWithFeedback doesn't return an error then it sets the status code to 204 (No Content) and
6565
// sends the response as a JSON payload to the client.
6666
// errors.NoContent(ctx, service.Delete, id)
6767
// OR:

go.mod

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,9 @@ require (
3939
github.com/vmihailenco/msgpack/v5 v5.4.1
4040
github.com/yosssi/ace v0.0.5
4141
go.etcd.io/bbolt v1.3.8
42-
golang.org/x/crypto v0.17.0
42+
golang.org/x/crypto v0.18.0
4343
golang.org/x/exp v0.0.0-20240103183307-be819d1f06fc
44-
golang.org/x/net v0.19.0
44+
golang.org/x/net v0.20.0
4545
golang.org/x/sys v0.16.0
4646
golang.org/x/text v0.14.0
4747
golang.org/x/time v0.5.0

go.sum

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

x/errors/errors.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,10 @@ func HandleError(ctx *context.Context, err error) bool {
139139
return false
140140
}
141141

142+
if ctx.IsStopped() {
143+
return false
144+
}
145+
142146
for errorCodeName, errorFuncs := range errorFuncCodeMap {
143147
for _, errorFunc := range errorFuncs {
144148
if errToSend := errorFunc(err); errToSend != nil {

x/errors/handlers.go

Lines changed: 142 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import (
1212
"golang.org/x/exp/constraints"
1313
)
1414

15-
// Handle handles a generic response and error from a service call and sends a JSON response to the context.
15+
// Handle handles a generic response and error from a service call and sends a JSON response to the client.
1616
// It returns a boolean value indicating whether the handle was successful or not.
1717
// If the error is not nil, it calls HandleError to send an appropriate error response to the client.
1818
func Handle(ctx *context.Context, resp interface{}, err error) bool {
@@ -161,7 +161,7 @@ func bindResponse[T, R any, F ResponseFunc[T, R]](ctx *context.Context, fn F, fn
161161
return resp, !HandleError(ctx, err)
162162
}
163163

164-
// OK handles a generic response and error from a service call and sends a JSON response to the context.
164+
// OK handles a generic response and error from a service call and sends a JSON response to the client.
165165
// It returns a boolean value indicating whether the handle was successful or not.
166166
// If the error is not nil, it calls HandleError to send an appropriate error response to the client.
167167
// It sets the status code to 200 (OK) and sends any response as a JSON payload.
@@ -176,6 +176,88 @@ func OK[T, R any, F ResponseFunc[T, R]](ctx *context.Context, fn F, fnInput ...T
176176
return Handle(ctx, resp, nil)
177177
}
178178

179+
// HandlerInputFunc is a function which takes a context and returns a generic type T.
180+
// It is used to call a service function with a generic type T.
181+
// It is used for functions which do not bind a request payload.
182+
// It is used for XHandler functions.
183+
// Developers can design their own HandlerInputFunc functions and use them with the XHandler functions.
184+
// To make a value required, stop the context execution through the context.StopExecution function and fire an error
185+
// or just use one of the [InvalidArgument].X methods.
186+
//
187+
// See PathParam, Query and Value package-level helpers too.
188+
type HandlerInputFunc[T any] interface {
189+
func(ctx *context.Context) T
190+
}
191+
192+
// GetRequestInputs returns a slice of generic type T from a slice of HandlerInputFunc[T].
193+
// It is exported so end-developers can use it to get the inputs from custom HandlerInputFunc[T] functions.
194+
func GetRequestInputs[T any, I HandlerInputFunc[T]](ctx *context.Context, fnInputFunc []I) ([]T, bool) {
195+
inputs := make([]T, 0, len(fnInputFunc))
196+
for _, callIn := range fnInputFunc {
197+
if callIn == nil {
198+
continue
199+
}
200+
201+
input := callIn(ctx)
202+
if ctx.IsStopped() { // if the input is required and it's not provided, then the context is stopped.
203+
return nil, false
204+
}
205+
inputs = append(inputs, input)
206+
}
207+
208+
return inputs, true
209+
}
210+
211+
// PathParam returns a HandlerInputFunc which reads a path parameter from the context and returns it as a generic type T.
212+
// It is used for XHandler functions.
213+
func PathParam[T any, I HandlerInputFunc[T]](paramName string) I {
214+
return func(ctx *context.Context) T {
215+
paramValue := ctx.Params().Store.Get(paramName)
216+
if paramValue == nil {
217+
var t T
218+
return t
219+
}
220+
221+
return paramValue.(T)
222+
}
223+
}
224+
225+
// Value returns a HandlerInputFunc which returns a generic type T.
226+
// It is used for XHandler functions.
227+
func Value[T any, I HandlerInputFunc[T]](value T) I {
228+
return func(ctx *context.Context) T {
229+
return value
230+
}
231+
}
232+
233+
// Query returns a HandlerInputFunc which reads a URL query from the context and returns it as a generic type T.
234+
// It is used for XHandler functions.
235+
func Query[T any, I HandlerInputFunc[T]]() I {
236+
return func(ctx *context.Context) T {
237+
value, ok := ReadQuery[T](ctx)
238+
if !ok {
239+
var t T
240+
return t
241+
}
242+
243+
return value
244+
}
245+
}
246+
247+
// Handler handles a generic response and error from a service call and sends a JSON response to the client with status code of 200.
248+
//
249+
// See OK package-level function for more.
250+
func Handler[T, R any, F ResponseFunc[T, R], I HandlerInputFunc[T]](fn F, fnInput ...I) context.Handler {
251+
return func(ctx *context.Context) {
252+
inputs, ok := GetRequestInputs(ctx, fnInput)
253+
if !ok {
254+
return
255+
}
256+
257+
OK(ctx, fn, inputs...)
258+
}
259+
}
260+
179261
// ListResponseFunc is a function which takes a context,
180262
// a pagination.ListOptions and a generic type T and returns a slice []R, total count of the items and an error.
181263
//
@@ -209,6 +291,20 @@ func List[T, R any, C constraints.Integer | constraints.Float, F ListResponseFun
209291
return Handle(ctx, resp, nil)
210292
}
211293

294+
// ListHandler handles a generic response and error from a service paginated call and sends a JSON response to the client.
295+
//
296+
// See List package-level function for more.
297+
func ListHandler[T, R any, C constraints.Integer | constraints.Float, F ListResponseFunc[T, R, C], I HandlerInputFunc[T]](fn F, fnInput ...I) context.Handler {
298+
return func(ctx *context.Context) {
299+
inputs, ok := GetRequestInputs(ctx, fnInput)
300+
if !ok {
301+
return
302+
}
303+
304+
List(ctx, fn, inputs...)
305+
}
306+
}
307+
212308
// Create handles a create operation and sends a JSON response with the created resource to the client.
213309
// It returns a boolean value indicating whether the handle was successful or not.
214310
// If the error is not nil, it calls HandleError to send an appropriate error response to the client.
@@ -225,7 +321,21 @@ func Create[T, R any, F ResponseFunc[T, R]](ctx *context.Context, fn F, fnInput
225321
return HandleCreate(ctx, resp, nil)
226322
}
227323

228-
// NoContent handles a generic response and error from a service call and sends a JSON response to the context.
324+
// CreateHandler handles a create operation and sends a JSON response with the created resource to the client with status code of 201.
325+
//
326+
// See Create package-level function for more.
327+
func CreateHandler[T, R any, F ResponseFunc[T, R], I HandlerInputFunc[T]](fn F, fnInput ...I) context.Handler {
328+
return func(ctx *context.Context) {
329+
inputs, ok := GetRequestInputs(ctx, fnInput)
330+
if !ok {
331+
return
332+
}
333+
334+
Create(ctx, fn, inputs...)
335+
}
336+
}
337+
338+
// NoContent handles a generic response and error from a service call and sends a JSON response to the client.
229339
// It returns a boolean value indicating whether the handle was successful or not.
230340
// If the error is not nil, it calls HandleError to send an appropriate error response to the client.
231341
// It sets the status code to 204 (No Content).
@@ -239,7 +349,21 @@ func NoContent[T any, F ResponseOnlyErrorFunc[T]](ctx *context.Context, fn F, fn
239349
return NoContentOrNotModified(ctx, toFn, fnInput...)
240350
}
241351

242-
// NoContent handles a generic response and error from a service call and sends a JSON response to the context.
352+
// NoContentHandler handles a generic response and error from a service call and sends a JSON response to the client with status code of 204.
353+
//
354+
// See NoContent package-level function for more.
355+
func NoContentHandler[T any, F ResponseOnlyErrorFunc[T], I HandlerInputFunc[T]](fn F, fnInput ...I) context.Handler {
356+
return func(ctx *context.Context) {
357+
inputs, ok := GetRequestInputs(ctx, fnInput)
358+
if !ok {
359+
return
360+
}
361+
362+
NoContent(ctx, fn, inputs...)
363+
}
364+
}
365+
366+
// NoContent handles a generic response and error from a service call and sends a JSON response to the client.
243367
// It returns a boolean value indicating whether the handle was successful or not.
244368
// If the error is not nil, it calls HandleError to send an appropriate error response to the client.
245369
// If the response is true, it sets the status code to 204 (No Content).
@@ -255,6 +379,20 @@ func NoContentOrNotModified[T any, F ResponseFunc[T, bool]](ctx *context.Context
255379
return HandleUpdate(ctx, bool(resp), nil)
256380
}
257381

382+
// NoContentOrNotModifiedHandler handles a generic response and error from a service call and sends a JSON response to the client with status code of 204 or 304.
383+
//
384+
// See NoContentOrNotModified package-level function for more.
385+
func NoContentOrNotModifiedHandler[T any, F ResponseFunc[T, bool], I HandlerInputFunc[T]](fn F, fnInput ...I) context.Handler {
386+
return func(ctx *context.Context) {
387+
inputs, ok := GetRequestInputs(ctx, fnInput)
388+
if !ok {
389+
return
390+
}
391+
392+
NoContentOrNotModified(ctx, fn, inputs...)
393+
}
394+
}
395+
258396
// ReadPayload reads a JSON payload from the context and returns it as a generic type T.
259397
// It also returns a boolean value indicating whether the read was successful or not.
260398
// If the read fails, it sends an appropriate error response to the client.

0 commit comments

Comments
 (0)