handlers.go 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540
  1. package errors
  2. import (
  3. stdContext "context"
  4. "errors"
  5. "fmt"
  6. "io"
  7. "net/http"
  8. "github.com/kataras/iris/v12/context"
  9. "github.com/kataras/iris/v12/x/pagination"
  10. "golang.org/x/exp/constraints"
  11. )
  12. // RecoveryHandler is a middleware which recovers from panics and sends an appropriate error response
  13. // to the logger and the client.
  14. func RecoveryHandler(ctx *context.Context) {
  15. defer func() {
  16. if rec := recover(); rec != nil {
  17. var err error
  18. switch v := rec.(type) {
  19. case error:
  20. err = v
  21. case string:
  22. err = New(v)
  23. default:
  24. err = fmt.Errorf("%v", v)
  25. }
  26. Internal.LogErr(ctx, err)
  27. ctx.StopExecution()
  28. }
  29. }()
  30. ctx.Next()
  31. }
  32. // Handle handles a generic response and error from a service call and sends a JSON response to the client.
  33. // It returns a boolean value indicating whether the handle was successful or not.
  34. // If the error is not nil, it calls HandleError to send an appropriate error response to the client.
  35. func Handle(ctx *context.Context, resp interface{}, err error) bool {
  36. if HandleError(ctx, err) {
  37. return false
  38. }
  39. ctx.StatusCode(http.StatusOK)
  40. if resp != nil {
  41. if ctx.JSON(resp) != nil {
  42. return false
  43. }
  44. }
  45. return true
  46. }
  47. // IDPayload is a simple struct which describes a json id value.
  48. type IDPayload[T string | int] struct {
  49. ID T `json:"id"`
  50. }
  51. // HandleCreate handles a create operation and sends a JSON response with the created resource to the client.
  52. // It returns a boolean value indicating whether the handle was successful or not.
  53. //
  54. // If the "respOrID" response is not nil, it sets the status code to 201 (Created) and sends the response as a JSON payload,
  55. // however if the given "respOrID" is a string or an int, it sends the response as a JSON payload of {"id": resp}.
  56. // If the "err" error is not nil, it calls HandleError to send an appropriate error response to the client.
  57. // It sets the status code to 201 (Created) and sends any response as a JSON payload,
  58. func HandleCreate(ctx *context.Context, respOrID any, err error) bool {
  59. if HandleError(ctx, err) {
  60. return false
  61. }
  62. ctx.StatusCode(http.StatusCreated)
  63. if respOrID != nil {
  64. switch responseValue := respOrID.(type) {
  65. case string:
  66. if ctx.JSON(IDPayload[string]{ID: responseValue}) != nil {
  67. return false
  68. }
  69. case int:
  70. if ctx.JSON(IDPayload[int]{ID: responseValue}) != nil {
  71. return false
  72. }
  73. default:
  74. if ctx.JSON(responseValue) != nil {
  75. return false
  76. }
  77. }
  78. }
  79. return true
  80. }
  81. // HandleUpdate handles an update operation and sends a status code to the client.
  82. // It returns a boolean value indicating whether the handle was successful or not.
  83. // If the error is not nil, it calls HandleError to send an appropriate error response to the client.
  84. // If the updated value is true, it sets the status code to 204 (No Content).
  85. // If the updated value is false, it sets the status code to 304 (Not Modified).
  86. func HandleUpdate(ctx *context.Context, updated bool, err error) bool {
  87. if HandleError(ctx, err) {
  88. return false
  89. }
  90. if updated {
  91. ctx.StatusCode(http.StatusNoContent)
  92. } else {
  93. ctx.StatusCode(http.StatusNotModified)
  94. }
  95. return true
  96. }
  97. // HandleDelete handles a delete operation and sends a status code to the client.
  98. // If the error is not nil, it calls HandleError to send an appropriate error response to the client.
  99. // If the deleted value is true, it sets the status code to 204 (No Content).
  100. // If the deleted value is false, it sets the status code to 304 (Not Modified).
  101. func HandleDelete(ctx *context.Context, deleted bool, err error) bool {
  102. return HandleUpdate(ctx, deleted, err)
  103. }
  104. // HandleDelete handles a delete operation and sends a status code to the client.
  105. // If the error is not nil, it calls HandleError to send an appropriate error response to the client.
  106. // It sets the status code to 204 (No Content).
  107. func HandleDeleteNoContent(ctx *context.Context, err error) bool {
  108. return HandleUpdate(ctx, true, err)
  109. }
  110. // ResponseFunc is a function which takes a context and a generic type T and returns a generic type R and an error.
  111. // It is used to bind a request payload to a generic type T and call a service function with it.
  112. type ResponseFunc[T, R any] interface {
  113. func(stdContext.Context, T) (R, error)
  114. }
  115. // ResponseOnlyErrorFunc is a function which takes a context and a generic type T and returns an error.
  116. // It is used to bind a request payload to a generic type T and call a service function with it.
  117. // It is used for functions which do not return a response.
  118. type ResponseOnlyErrorFunc[T any] interface {
  119. func(stdContext.Context, T) error
  120. }
  121. // ContextValidatorFunc is a function which takes a context and a generic type T and returns an error.
  122. // It is used to validate the context before calling a service function.
  123. //
  124. // See Validation package-level function.
  125. type ContextValidatorFunc[T any] func(*context.Context, T) error
  126. const contextValidatorFuncKey = "iris.errors.ContextValidatorFunc"
  127. // Validation adds a context validator function to the context.
  128. // It returns a middleware which can be used to validate the context before calling a service function.
  129. // It panics if the given validators are empty or nil.
  130. //
  131. // Example:
  132. //
  133. // r.Post("/", Validation(validateCreateRequest), createHandler(service))
  134. //
  135. // func validateCreateRequest(ctx iris.Context, r *CreateRequest) error {
  136. // return validation.Join(
  137. // validation.String("fullname", r.Fullname).NotEmpty().Fullname().Length(3, 50),
  138. // validation.Number("age", r.Age).InRange(18, 130),
  139. // validation.Slice("hobbies", r.Hobbies).Length(1, 10),
  140. // )
  141. // }
  142. func Validation[T any](validators ...ContextValidatorFunc[T]) context.Handler {
  143. validator := joinContextValidators[T](validators)
  144. return func(ctx *context.Context) {
  145. ctx.Values().Set(contextValidatorFuncKey, validator)
  146. ctx.Next()
  147. }
  148. }
  149. func joinContextValidators[T any](validators []ContextValidatorFunc[T]) ContextValidatorFunc[T] {
  150. if len(validators) == 0 || validators[0] == nil {
  151. panic("at least one validator is required")
  152. }
  153. if len(validators) == 1 {
  154. return validators[0]
  155. }
  156. return func(ctx *context.Context, req T) error {
  157. for _, validator := range validators {
  158. if validator == nil {
  159. continue
  160. }
  161. if err := validator(ctx, req); err != nil {
  162. return err
  163. }
  164. }
  165. return nil
  166. }
  167. }
  168. // ContextValidator is an interface which can be implemented by a request payload struct
  169. // in order to validate the context before calling a service function.
  170. type ContextValidator interface {
  171. ValidateContext(*context.Context) error
  172. }
  173. func validateContext[T any](ctx *context.Context, req T) bool {
  174. var err error
  175. // Always run the request's validator first,
  176. // so dynamic validators can be customized per path and method.
  177. if contextValidator, ok := any(&req).(ContextValidator); ok {
  178. err = contextValidator.ValidateContext(ctx)
  179. }
  180. if err == nil {
  181. if v := ctx.Values().Get(contextValidatorFuncKey); v != nil {
  182. if contextValidatorFunc, ok := v.(ContextValidatorFunc[T]); ok {
  183. err = contextValidatorFunc(ctx, req)
  184. } else if contextValidatorFunc, ok := v.(ContextValidatorFunc[*T]); ok { // or a pointer of T.
  185. err = contextValidatorFunc(ctx, &req)
  186. }
  187. }
  188. }
  189. if err != nil {
  190. if HandleError(ctx, err) {
  191. return false
  192. }
  193. }
  194. return true
  195. }
  196. func bindResponse[T, R any, F ResponseFunc[T, R]](ctx *context.Context, fn F, fnInput ...T) (R, bool) {
  197. var req T
  198. switch len(fnInput) {
  199. case 0:
  200. var ok bool
  201. req, ok = ReadPayload[T](ctx)
  202. if !ok {
  203. var resp R
  204. return resp, false
  205. }
  206. case 1:
  207. req = fnInput[0]
  208. default:
  209. panic("invalid number of arguments")
  210. }
  211. if !validateContext(ctx, req) {
  212. var resp R
  213. return resp, false
  214. }
  215. resp, err := fn(ctx, req)
  216. return resp, !HandleError(ctx, err)
  217. }
  218. // OK handles a generic response and error from a service call and sends a JSON response to the client.
  219. // It returns a boolean value indicating whether the handle was successful or not.
  220. // If the error is not nil, it calls HandleError to send an appropriate error response to the client.
  221. // It sets the status code to 200 (OK) and sends any response as a JSON payload.
  222. //
  223. // Useful for Get/List/Fetch operations.
  224. func OK[T, R any, F ResponseFunc[T, R]](ctx *context.Context, fn F, fnInput ...T) bool { // or Fetch.
  225. resp, ok := bindResponse(ctx, fn, fnInput...)
  226. if !ok {
  227. return false
  228. }
  229. return Handle(ctx, resp, nil)
  230. }
  231. // HandlerInputFunc is a function which takes a context and returns a generic type T.
  232. // It is used to call a service function with a generic type T.
  233. // It is used for functions which do not bind a request payload.
  234. // It is used for XHandler functions.
  235. // Developers can design their own HandlerInputFunc functions and use them with the XHandler functions.
  236. // To make a value required, stop the context execution through the context.StopExecution function and fire an error
  237. // or just use one of the [InvalidArgument].X methods.
  238. //
  239. // See PathParam, Query and Value package-level helpers too.
  240. type HandlerInputFunc[T any] interface {
  241. func(ctx *context.Context) T
  242. }
  243. // GetRequestInputs returns a slice of generic type T from a slice of HandlerInputFunc[T].
  244. // It is exported so end-developers can use it to get the inputs from custom HandlerInputFunc[T] functions.
  245. func GetRequestInputs[T any, I HandlerInputFunc[T]](ctx *context.Context, fnInputFunc []I) ([]T, bool) {
  246. inputs := make([]T, 0, len(fnInputFunc))
  247. for _, callIn := range fnInputFunc {
  248. if callIn == nil {
  249. continue
  250. }
  251. input := callIn(ctx)
  252. if ctx.IsStopped() { // if the input is required and it's not provided, then the context is stopped.
  253. return nil, false
  254. }
  255. inputs = append(inputs, input)
  256. }
  257. return inputs, true
  258. }
  259. // PathParam returns a HandlerInputFunc which reads a path parameter from the context and returns it as a generic type T.
  260. // It is used for XHandler functions.
  261. func PathParam[T any, I HandlerInputFunc[T]](paramName string) I {
  262. return func(ctx *context.Context) T {
  263. paramValue := ctx.Params().Store.Get(paramName)
  264. if paramValue == nil {
  265. var t T
  266. return t
  267. }
  268. return paramValue.(T)
  269. }
  270. }
  271. // Value returns a HandlerInputFunc which returns a generic type T.
  272. // It is used for XHandler functions.
  273. func Value[T any, I HandlerInputFunc[T]](value T) I {
  274. return func(ctx *context.Context) T {
  275. return value
  276. }
  277. }
  278. // Query returns a HandlerInputFunc which reads a URL query from the context and returns it as a generic type T.
  279. // It is used for XHandler functions.
  280. func Query[T any, I HandlerInputFunc[T]]() I {
  281. return func(ctx *context.Context) T {
  282. value, ok := ReadQuery[T](ctx)
  283. if !ok {
  284. var t T
  285. return t
  286. }
  287. return value
  288. }
  289. }
  290. // Handler handles a generic response and error from a service call and sends a JSON response to the client with status code of 200.
  291. //
  292. // See OK package-level function for more.
  293. func Handler[T, R any, F ResponseFunc[T, R], I HandlerInputFunc[T]](fn F, fnInput ...I) context.Handler {
  294. return func(ctx *context.Context) {
  295. inputs, ok := GetRequestInputs(ctx, fnInput)
  296. if !ok {
  297. return
  298. }
  299. OK(ctx, fn, inputs...)
  300. }
  301. }
  302. // ListResponseFunc is a function which takes a context,
  303. // a pagination.ListOptions and a generic type T and returns a slice []R, total count of the items and an error.
  304. //
  305. // It's used on the List function.
  306. type ListResponseFunc[T, R any, C constraints.Integer | constraints.Float] interface {
  307. func(stdContext.Context, pagination.ListOptions, T /* filter options */) ([]R, C, error)
  308. }
  309. // List handles a generic response and error from a service paginated call and sends a JSON response to the client.
  310. // It returns a boolean value indicating whether the handle was successful or not.
  311. // If the error is not nil, it calls HandleError to send an appropriate error response to the client.
  312. // It reads the pagination.ListOptions from the URL Query and any filter options of generic T from the request body.
  313. // It sets the status code to 200 (OK) and sends a *pagination.List[R] response as a JSON payload.
  314. func List[T, R any, C constraints.Integer | constraints.Float, F ListResponseFunc[T, R, C]](ctx *context.Context, fn F, fnInput ...T) bool {
  315. listOpts, filter, ok := ReadPaginationOptions[T](ctx)
  316. if !ok {
  317. return false
  318. }
  319. if !validateContext(ctx, filter) {
  320. return false
  321. }
  322. items, totalCount, err := fn(ctx, listOpts, filter)
  323. if err != nil {
  324. HandleError(ctx, err)
  325. return false
  326. }
  327. resp := pagination.NewList(items, int64(totalCount), filter, listOpts)
  328. return Handle(ctx, resp, nil)
  329. }
  330. // ListHandler handles a generic response and error from a service paginated call and sends a JSON response to the client.
  331. //
  332. // See List package-level function for more.
  333. func ListHandler[T, R any, C constraints.Integer | constraints.Float, F ListResponseFunc[T, R, C], I HandlerInputFunc[T]](fn F, fnInput ...I) context.Handler {
  334. return func(ctx *context.Context) {
  335. inputs, ok := GetRequestInputs(ctx, fnInput)
  336. if !ok {
  337. return
  338. }
  339. List(ctx, fn, inputs...)
  340. }
  341. }
  342. // Create handles a create operation and sends a JSON response with the created resource to the client.
  343. // It returns a boolean value indicating whether the handle was successful or not.
  344. // If the error is not nil, it calls HandleError to send an appropriate error response to the client.
  345. // It sets the status code to 201 (Created) and sends any response as a JSON payload
  346. // note that if the response is a string, then it sends an {"id": resp} JSON payload).
  347. //
  348. // Useful for Insert operations.
  349. func Create[T, R any, F ResponseFunc[T, R]](ctx *context.Context, fn F, fnInput ...T) bool {
  350. resp, ok := bindResponse(ctx, fn, fnInput...)
  351. if !ok {
  352. return false
  353. }
  354. return HandleCreate(ctx, resp, nil)
  355. }
  356. // CreateHandler handles a create operation and sends a JSON response with the created resource to the client with status code of 201.
  357. //
  358. // See Create package-level function for more.
  359. func CreateHandler[T, R any, F ResponseFunc[T, R], I HandlerInputFunc[T]](fn F, fnInput ...I) context.Handler {
  360. return func(ctx *context.Context) {
  361. inputs, ok := GetRequestInputs(ctx, fnInput)
  362. if !ok {
  363. return
  364. }
  365. Create(ctx, fn, inputs...)
  366. }
  367. }
  368. // NoContent handles a generic response and error from a service call and sends a JSON response to the client.
  369. // It returns a boolean value indicating whether the handle was successful or not.
  370. // If the error is not nil, it calls HandleError to send an appropriate error response to the client.
  371. // It sets the status code to 204 (No Content).
  372. //
  373. // Useful for Update and Deletion operations.
  374. func NoContent[T any, F ResponseOnlyErrorFunc[T]](ctx *context.Context, fn F, fnInput ...T) bool {
  375. toFn := func(c stdContext.Context, req T) (bool, error) {
  376. return true, fn(ctx, req)
  377. }
  378. return NoContentOrNotModified(ctx, toFn, fnInput...)
  379. }
  380. // NoContentHandler handles a generic response and error from a service call and sends a JSON response to the client with status code of 204.
  381. //
  382. // See NoContent package-level function for more.
  383. func NoContentHandler[T any, F ResponseOnlyErrorFunc[T], I HandlerInputFunc[T]](fn F, fnInput ...I) context.Handler {
  384. return func(ctx *context.Context) {
  385. inputs, ok := GetRequestInputs(ctx, fnInput)
  386. if !ok {
  387. return
  388. }
  389. NoContent(ctx, fn, inputs...)
  390. }
  391. }
  392. // NoContent handles a generic response and error from a service call and sends a JSON response to the client.
  393. // It returns a boolean value indicating whether the handle was successful or not.
  394. // If the error is not nil, it calls HandleError to send an appropriate error response to the client.
  395. // If the response is true, it sets the status code to 204 (No Content).
  396. // If the response is false, it sets the status code to 304 (Not Modified).
  397. //
  398. // Useful for Update and Deletion operations.
  399. func NoContentOrNotModified[T any, F ResponseFunc[T, bool]](ctx *context.Context, fn F, fnInput ...T) bool {
  400. resp, ok := bindResponse(ctx, fn, fnInput...)
  401. if !ok {
  402. return false
  403. }
  404. return HandleUpdate(ctx, bool(resp), nil)
  405. }
  406. // 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.
  407. //
  408. // See NoContentOrNotModified package-level function for more.
  409. func NoContentOrNotModifiedHandler[T any, F ResponseFunc[T, bool], I HandlerInputFunc[T]](fn F, fnInput ...I) context.Handler {
  410. return func(ctx *context.Context) {
  411. inputs, ok := GetRequestInputs(ctx, fnInput)
  412. if !ok {
  413. return
  414. }
  415. NoContentOrNotModified(ctx, fn, inputs...)
  416. }
  417. }
  418. // ReadPayload reads a JSON payload from the context and returns it as a generic type T.
  419. // It also returns a boolean value indicating whether the read was successful or not.
  420. // If the read fails, it sends an appropriate error response to the client.
  421. func ReadPayload[T any](ctx *context.Context) (T, bool) {
  422. var payload T
  423. err := ctx.ReadJSON(&payload)
  424. if err != nil {
  425. if errors.Is(err, io.EOF) || errors.Is(err, io.ErrUnexpectedEOF) {
  426. InvalidArgument.Details(ctx, "unable to parse body", "empty body")
  427. return payload, false
  428. }
  429. HandleError(ctx, err)
  430. return payload, false
  431. }
  432. return payload, true
  433. }
  434. // ReadQuery reads URL query values from the context and returns it as a generic type T.
  435. // It also returns a boolean value indicating whether the read was successful or not.
  436. // If the read fails, it sends an appropriate error response to the client.
  437. func ReadQuery[T any](ctx *context.Context) (T, bool) {
  438. var payload T
  439. err := ctx.ReadQuery(&payload)
  440. if err != nil {
  441. HandleError(ctx, err)
  442. return payload, false
  443. }
  444. return payload, true
  445. }
  446. // ReadPaginationOptions reads the ListOptions from the URL Query and
  447. // any filter options of generic T from the request body.
  448. func ReadPaginationOptions[T /* T is FilterOptions */ any](ctx *context.Context) (pagination.ListOptions, T, bool) {
  449. list, ok := ReadQuery[pagination.ListOptions](ctx)
  450. if !ok {
  451. var t T
  452. return list, t, false
  453. }
  454. filter, ok := ReadPayload[T](ctx)
  455. if !ok {
  456. var t T
  457. return list, t, false
  458. }
  459. return list, filter, true
  460. }