1
0
mirror of https://github.com/kataras/iris.git synced 2026-01-23 03:45:56 +00:00

add x/errors.OK, Create, NoContent and NoContentOrNotModified package-level generic functions as custom service helpers

This commit is contained in:
Gerasimos (Makis) Maropoulos
2024-01-04 03:33:00 +02:00
parent 56754ff5cc
commit 1ae4e71176
9 changed files with 345 additions and 108 deletions

View File

@@ -14,6 +14,8 @@ var (
New = errors.New
// Unwrap is an alias of the standard errors.Unwrap function.
Unwrap = errors.Unwrap
// Join is an alias of the standard errors.Join function.
Join = errors.Join
)
func sprintf(format string, args ...interface{}) string {

View File

@@ -48,9 +48,12 @@ type (
// A read-only map of valid http error codes.
var errorCodeMap = make(map[ErrorCodeName]ErrorCode)
// E registers a custom HTTP Error and returns its canonical name for future use.
// Deprecated: Use Register instead.
var E = Register
// Register registers a custom HTTP Error and returns its canonical name for future use.
// The method "New" is reserved and was kept as it is for compatibility
// with the standard errors package, therefore the "E" name was chosen instead.
// with the standard errors package, therefore the "Register" name was chosen instead.
// The key stroke "e" is near and accessible while typing the "errors" word
// so developers may find it easy to use.
//
@@ -59,14 +62,14 @@ var errorCodeMap = make(map[ErrorCodeName]ErrorCode)
// Example:
//
// var (
// NotFound = errors.E("NOT_FOUND", http.StatusNotFound)
// NotFound = errors.Register("NOT_FOUND", http.StatusNotFound)
// )
// ...
// NotFound.Details(ctx, "resource not found", "user with id: %q was not found", userID)
//
// This method MUST be called on initialization, before HTTP server starts as
// the internal map is not protected by mutex.
func E(httpErrorCanonicalName string, httpStatusCode int) ErrorCodeName {
func Register(httpErrorCanonicalName string, httpStatusCode int) ErrorCodeName {
canonicalName := ErrorCodeName(httpErrorCanonicalName)
RegisterErrorCode(canonicalName, httpStatusCode)
return canonicalName
@@ -99,22 +102,22 @@ func RegisterErrorCodeMap(errorMap map[ErrorCodeName]int) {
// List of default error codes a server should follow and send back to the client.
var (
Cancelled ErrorCodeName = E("CANCELLED", context.StatusTokenRequired)
Unknown ErrorCodeName = E("UNKNOWN", http.StatusInternalServerError)
InvalidArgument ErrorCodeName = E("INVALID_ARGUMENT", http.StatusBadRequest)
DeadlineExceeded ErrorCodeName = E("DEADLINE_EXCEEDED", http.StatusGatewayTimeout)
NotFound ErrorCodeName = E("NOT_FOUND", http.StatusNotFound)
AlreadyExists ErrorCodeName = E("ALREADY_EXISTS", http.StatusConflict)
PermissionDenied ErrorCodeName = E("PERMISSION_DENIED", http.StatusForbidden)
Unauthenticated ErrorCodeName = E("UNAUTHENTICATED", http.StatusUnauthorized)
ResourceExhausted ErrorCodeName = E("RESOURCE_EXHAUSTED", http.StatusTooManyRequests)
FailedPrecondition ErrorCodeName = E("FAILED_PRECONDITION", http.StatusBadRequest)
Aborted ErrorCodeName = E("ABORTED", http.StatusConflict)
OutOfRange ErrorCodeName = E("OUT_OF_RANGE", http.StatusBadRequest)
Unimplemented ErrorCodeName = E("UNIMPLEMENTED", http.StatusNotImplemented)
Internal ErrorCodeName = E("INTERNAL", http.StatusInternalServerError)
Unavailable ErrorCodeName = E("UNAVAILABLE", http.StatusServiceUnavailable)
DataLoss ErrorCodeName = E("DATA_LOSS", http.StatusInternalServerError)
Cancelled ErrorCodeName = Register("CANCELLED", context.StatusTokenRequired)
Unknown ErrorCodeName = Register("UNKNOWN", http.StatusInternalServerError)
InvalidArgument ErrorCodeName = Register("INVALID_ARGUMENT", http.StatusBadRequest)
DeadlineExceeded ErrorCodeName = Register("DEADLINE_EXCEEDED", http.StatusGatewayTimeout)
NotFound ErrorCodeName = Register("NOT_FOUND", http.StatusNotFound)
AlreadyExists ErrorCodeName = Register("ALREADY_EXISTS", http.StatusConflict)
PermissionDenied ErrorCodeName = Register("PERMISSION_DENIED", http.StatusForbidden)
Unauthenticated ErrorCodeName = Register("UNAUTHENTICATED", http.StatusUnauthorized)
ResourceExhausted ErrorCodeName = Register("RESOURCE_EXHAUSTED", http.StatusTooManyRequests)
FailedPrecondition ErrorCodeName = Register("FAILED_PRECONDITION", http.StatusBadRequest)
Aborted ErrorCodeName = Register("ABORTED", http.StatusConflict)
OutOfRange ErrorCodeName = Register("OUT_OF_RANGE", http.StatusBadRequest)
Unimplemented ErrorCodeName = Register("UNIMPLEMENTED", http.StatusNotImplemented)
Internal ErrorCodeName = Register("INTERNAL", http.StatusInternalServerError)
Unavailable ErrorCodeName = Register("UNAVAILABLE", http.StatusServiceUnavailable)
DataLoss ErrorCodeName = Register("DATA_LOSS", http.StatusInternalServerError)
)
// errorFuncCodeMap is a read-only map of error code names and their error functions.
@@ -352,7 +355,7 @@ var (
// The server fails to send an error on two cases:
// 1. when the provided error code name is not registered (the error value is the ErrUnexpectedErrorCode)
// 2. when the error contains data but cannot be encoded to json (the value of the error is the result error of json.Marshal).
ErrUnexpected = E("UNEXPECTED_ERROR", http.StatusInternalServerError)
ErrUnexpected = Register("UNEXPECTED_ERROR", http.StatusInternalServerError)
// ErrUnexpectedErrorCode is the error which logged
// when the given error code name is not registered.
ErrUnexpectedErrorCode = New("unexpected error code name")

View File

@@ -1,12 +1,198 @@
package errors
import (
stdContext "context"
"net/http"
"github.com/kataras/iris/v12/context"
"github.com/kataras/iris/v12/x/pagination"
)
// 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
}
ctx.StatusCode(http.StatusOK)
if resp != nil {
if ctx.JSON(resp) != nil {
return false
}
}
return true
}
// IDPayload is a simple struct which describes a json id value.
type IDPayload[T string | int] struct {
ID T `json:"id"`
}
// HandleCreate 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 "respOrID" response is not nil, it sets the status code to 201 (Created) and sends the response as a JSON payload,
// however if the given "respOrID" is a string or an int, it sends the response as a JSON payload of {"id": resp}.
// If the "err" error is not nil, it calls HandleError to send an appropriate error response to the client.
// It sets the status code to 201 (Created) and sends any response as a JSON payload,
func HandleCreate(ctx *context.Context, respOrID any, err error) bool {
if HandleError(ctx, err) {
return false
}
ctx.StatusCode(http.StatusCreated)
if respOrID != nil {
switch responseValue := respOrID.(type) {
case string:
if ctx.JSON(IDPayload[string]{ID: responseValue}) != nil {
return false
}
case int:
if ctx.JSON(IDPayload[int]{ID: responseValue}) != nil {
return false
}
default:
if ctx.JSON(responseValue) != nil {
return false
}
}
}
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.
// If the deleted value is true, it sets the status code to 204 (No Content).
// If the deleted value is false, it sets the status code to 304 (Not Modified).
func HandleDelete(ctx *context.Context, deleted bool, err error) bool {
return HandleUpdate(ctx, deleted, err)
}
// 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 HandleDeleteNoContent(ctx *context.Context, err error) bool {
return HandleUpdate(ctx, true, err)
}
// ResponseFunc is a function which takes a context and a generic type T and returns a generic type R and an error.
// It is used to bind a request payload to a generic type T and call a service function with it.
type ResponseFunc[T, R any] interface {
func(stdContext.Context, T) (R, error)
}
// ResponseOnlyErrorFunc is a function which takes a context and a generic type T and returns an error.
// It is used to bind a request payload to a generic type T and call a service function with it.
// It is used for functions which do not return a response.
type ResponseOnlyErrorFunc[T any] interface {
func(stdContext.Context, T) error
}
func bindResponse[T, R any, F ResponseFunc[T, R]](ctx *context.Context, fn F, fnInput ...T) (R, bool) {
var req T
switch len(fnInput) {
case 0:
err := ctx.ReadJSON(&req)
if err != nil {
var resp R
return resp, !HandleError(ctx, err)
}
case 1:
req = fnInput[0]
default:
panic("invalid number of arguments")
}
resp, err := fn(ctx, req)
return resp, !HandleError(ctx, err)
}
// OK 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.
// It sets the status code to 200 (OK) and sends any response as a JSON payload.
//
// Useful for Get/List/Fetch operations.
func OK[T, R any, F ResponseFunc[T, R]](ctx *context.Context, fn F, fnInput ...T) bool { // or Fetch.
resp, ok := bindResponse(ctx, fn, fnInput...)
if !ok {
return false
}
return Handle(ctx, resp, nil)
}
// 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.
// If the error is not nil, it calls HandleError to send an appropriate error response to the client.
// It sets the status code to 201 (Created) and sends any response as a JSON payload
// note that if the response is a string, then it sends an {"id": resp} JSON payload).
//
// Useful for Insert operations.
func Create[T, R any, F ResponseFunc[T, R]](ctx *context.Context, fn F, fnInput ...T) bool {
resp, ok := bindResponse(ctx, fn, fnInput...)
if !ok {
return false
}
return HandleCreate(ctx, resp, nil)
}
// NoContent 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.
// It sets the status code to 204 (No Content).
//
// Useful for Update and Deletion operations.
func NoContent[T any, F ResponseOnlyErrorFunc[T]](ctx *context.Context, fn F, fnInput ...T) bool {
toFn := func(c stdContext.Context, req T) (bool, error) {
return true, fn(ctx, req)
}
return NoContentOrNotModified(ctx, toFn, fnInput...)
}
// NoContent 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.
// If the response is true, it sets the status code to 204 (No Content).
// If the response is false, it sets the status code to 304 (Not Modified).
//
// Useful for Update and Deletion operations.
func NoContentOrNotModified[T any, F ResponseFunc[T, bool]](ctx *context.Context, fn F, fnInput ...T) bool {
resp, ok := bindResponse(ctx, fn, fnInput...)
if !ok {
return false
}
return HandleUpdate(ctx, bool(resp), nil)
}
// 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.
@@ -60,86 +246,3 @@ func ReadPaginationOptions[T /* T is FilterOptions */ any](ctx *context.Context)
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
}