diff --git a/HISTORY.md b/HISTORY.md index af2c2de2..ef6a55d7 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -23,10 +23,11 @@ Developers are not forced to upgrade if they don't really need it. Upgrade whene Changes apply to `main` branch. +- Add `x/errors.ReadPayload`, `ReadQuery`, `ReadPaginationOptions`, `Handle`, `HandleCreate`, `HandleCreateResponse`, `HandleUpdate` and `HandleDelete` package-level functions as helpers for common actions. - Add `x/jsonx.GetSimpleDateRange(date, jsonx.WeekRange, time.Monday, time.Sunday)` which returns all dates between the given range and start/end weekday values for WeekRange. - Add `x/timex.GetMonthDays` and `x/timex.GetMonthEnd` functions. - Add `iris.CookieDomain` and `iris.CookieOverride` cookie options to handle [#2309](https://github.com/kataras/iris/issues/2309). -- New `x/errors.ErrorCodeName.MapErrorFunc`, `x/errors.ErrorCodeName.MapErrors`, `x/errors.ErrorCodeName.Wrap` methods and `x/errors.HandleError` package-level function. +- New `x/errors.ErrorCodeName.MapErrorFunc`, `MapErrors`, `Wrap` methods and `x/errors.HandleError` package-level function. # Sun, 05 Nov 2023 | v12.2.8 diff --git a/LICENSE b/LICENSE index de4e4a3e..69a1ca75 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ BSD 3-Clause License -Copyright (c) 2016-2023, Gerasimos (Makis) Maropoulos +Copyright (c) 2016-2024, Gerasimos (Makis) Maropoulos All rights reserved. Redistribution and use in source and binary forms, with or without diff --git a/README.md b/README.md index a85aa919..277030bf 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,34 @@ Try the official [Iris Command Line Interface](https://github.com/kataras/iris-cli) today! --> +# 🎅 Happy new year to everyone! + +``` +A new year is here, full of hope and cheer +A time to celebrate, and appreciate +the past year's achievements, and the future's improvements. + +We are proud to present, our open-source project +Iris web framework, a fast and elegant way to make +Web applications, with ease and satisfaction. + +Iris is built with Go, a language you should know +It offers high performance, and great concurrency. +It has a rich ecosystem, and a friendly community. + +Iris is designed to be, simple and flexible +It has a modular structure, and a powerful router. +It supports middleware, and many features. + +Iris is more than a tool, it is a vision +To empower developers, and inspire creations +to make the web better, and brighter. + +We thank you for your support, and your feedback. +We hope you enjoy using Iris, and find it useful. +We wish you a happy new year, and a successful career. +``` + # Iris Web Framework [![build status](https://img.shields.io/github/actions/workflow/status/kataras/iris/ci.yml?branch=main&style=for-the-badge)](https://github.com/kataras/iris/actions/workflows/ci.yml) [![view examples](https://img.shields.io/badge/examples%20-285-a83adf.svg?style=for-the-badge&logo=go)](https://github.com/kataras/iris/tree/main/_examples) [![chat](https://img.shields.io/gitter/room/iris_go/community.svg?color=cc2b5e&logo=gitter&style=for-the-badge)](https://gitter.im/iris_go/community) [![donate](https://img.shields.io/badge/support-Iris-blue.svg?style=for-the-badge&logo=paypal)](https://iris-go.com/donate) diff --git a/x/errors/errors.go b/x/errors/errors.go index 57f5d9f2..5b6aad95 100644 --- a/x/errors/errors.go +++ b/x/errors/errors.go @@ -59,7 +59,7 @@ var errorCodeMap = make(map[ErrorCodeName]ErrorCode) // Example: // // var ( -// NotFound = errors.E("NOT_FOUND", http.StatusNotFound) +// NotFound = errors.E("NOT_FOUND", http.StatusNotFound) // ) // ... // NotFound.Details(ctx, "resource not found", "user with id: %q was not found", userID) @@ -118,6 +118,7 @@ var ( ) // errorFuncCodeMap is a read-only map of error code names and their error functions. +// See HandleError package-level function. var errorFuncCodeMap = make(map[ErrorCodeName][]func(error) error) // HandleError handles an error by sending it to the client diff --git a/x/errors/handlers.go b/x/errors/handlers.go new file mode 100644 index 00000000..98ff3f6c --- /dev/null +++ b/x/errors/handlers.go @@ -0,0 +1,145 @@ +package errors + +import ( + "net/http" + + "github.com/kataras/iris/v12/context" + "github.com/kataras/iris/v12/x/pagination" +) + +// ReadPayload reads a JSON payload from the context and returns it as a generic type T. +// It also returns a boolean value indicating whether the read was successful or not. +// If the read fails, it sends an appropriate error response to the client. +func ReadPayload[T any](ctx *context.Context) (T, bool) { + var payload T + err := ctx.ReadJSON(&payload) + if err != nil { + if vErrs, ok := AsValidationErrors(err); ok { + InvalidArgument.Data(ctx, "validation failure", vErrs) + } else { + InvalidArgument.Details(ctx, "unable to parse body", err.Error()) + } + return payload, false + } + + return payload, true +} + +// ReadQuery reads URL query values from the context and returns it as a generic type T. +// It also returns a boolean value indicating whether the read was successful or not. +// If the read fails, it sends an appropriate error response to the client. +func ReadQuery[T any](ctx *context.Context) (T, bool) { + var payload T + err := ctx.ReadQuery(&payload) + if err != nil { + if vErrs, ok := AsValidationErrors(err); ok { + InvalidArgument.Data(ctx, "validation failure", vErrs) + } else { + InvalidArgument.Details(ctx, "unable to parse query", err.Error()) + } + return payload, false + } + + return payload, true +} + +// ReadPaginationOptions reads the ListOptions from the URL Query and +// any filter options of generic T from the request body. +func ReadPaginationOptions[T /* T is FilterOptions */ any](ctx *context.Context) (pagination.ListOptions, T, bool) { + list, ok := ReadQuery[pagination.ListOptions](ctx) + if !ok { + var t T + return list, t, false + } + + filter, ok := ReadPayload[T](ctx) + if !ok { + var t T + return list, t, false + } + + return list, filter, true +} + +// Handle handles a generic response and error from a service call and sends a JSON response to the context. +// 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. +func Handle(ctx *context.Context, resp interface{}, err error) bool { + if HandleError(ctx, err) { + return false + } + + return ctx.JSON(resp) == nil +} + +// IDPayload is a simple struct which describes a json id value. +type IDPayload struct { + ID string `json:"id"` +} + +// HandleCreate handles a create operation and sends a JSON response with the created id 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. +// If the id is not empty, it sets the status code to 201 (Created) and sends the id as a JSON payload. +func HandleCreate(ctx *context.Context, id string, err error) bool { + if HandleError(ctx, err) { + return false + } + + ctx.StatusCode(http.StatusCreated) + + if id != "" { + ctx.JSON(IDPayload{ID: id}) + } + + return true +} + +// HandleCreateResponse 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. +// If the error is not nil, it calls HandleError to send an appropriate error response to the client. +// If the response is not nil, it sets the status code to 201 (Created) and sends the response as a JSON payload. +func HandleCreateResponse(ctx *context.Context, resp interface{}, err error) bool { + if HandleError(ctx, err) { + return false + } + + ctx.StatusCode(http.StatusCreated) + if resp != nil { + return ctx.JSON(resp) == nil + } + + return true +} + +// HandleUpdate handles an update operation and sends a status code 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. +// If the updated value is true, it sets the status code to 204 (No Content). +// If the updated value is false, it sets the status code to 304 (Not Modified). +func HandleUpdate(ctx *context.Context, updated bool, err error) bool { + if HandleError(ctx, err) { + return false + } + + if updated { + ctx.StatusCode(http.StatusNoContent) + } else { + ctx.StatusCode(http.StatusNotModified) + } + + return true +} + +// HandleDelete handles a delete operation and sends a status code to the client. +// If the error is not nil, it calls HandleError to send an appropriate error response to the client. +// It sets the status code to 204 (No Content). +func HandleDelete(ctx *context.Context, err error) bool { + if HandleError(ctx, err) { + return false + } + + ctx.StatusCode(http.StatusNoContent) + + return true +}