Files
sendico/api/pkg/api/routers/gsresponse/response.go
2025-12-23 18:36:22 +01:00

145 lines
4.6 KiB
Go

package gsresponse
import (
"context"
"errors"
"fmt"
"github.com/tech/sendico/pkg/merrors"
"github.com/tech/sendico/pkg/mlogger"
"github.com/tech/sendico/pkg/mservice"
"go.uber.org/zap"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
// Responder produces a response or a gRPC status error when executed.
type Responder[T any] func(ctx context.Context) (*T, error)
func message(err error) string {
if err == nil {
return ""
}
return err.Error()
}
func Success[T any](resp *T) Responder[T] {
return func(context.Context) (*T, error) {
return resp, nil
}
}
func Empty[T any]() Responder[T] {
return func(context.Context) (*T, error) {
return nil, nil
}
}
func Error[T any](logger mlogger.Logger, service mservice.Type, code codes.Code, hint string, err error) Responder[T] {
return func(ctx context.Context) (*T, error) {
fields := []zap.Field{
zap.String("service", string(service)),
zap.String("status_code", code.String()),
}
if hint != "" {
fields = append(fields, zap.String("error_hint", hint))
}
if err != nil {
fields = append(fields, zap.Error(err))
}
logger.Warn("gRPC request failed", fields...)
msg := message(err)
switch {
case hint == "" && msg == "":
return nil, status.Error(code, code.String())
case hint == "":
return nil, status.Error(code, msg)
case msg == "":
return nil, status.Error(code, hint)
default:
return nil, status.Error(code, fmt.Sprintf("%s: %s", hint, msg))
}
}
}
func Internal[T any](logger mlogger.Logger, service mservice.Type, err error) Responder[T] {
return Error[T](logger, service, codes.Internal, "internal_error", err)
}
func InvalidArgument[T any](logger mlogger.Logger, service mservice.Type, err error) Responder[T] {
return Error[T](logger, service, codes.InvalidArgument, "invalid_argument", err)
}
func NotFound[T any](logger mlogger.Logger, service mservice.Type, err error) Responder[T] {
return Error[T](logger, service, codes.NotFound, "not_found", err)
}
func Unauthorized[T any](logger mlogger.Logger, service mservice.Type, err error) Responder[T] {
return Error[T](logger, service, codes.Unauthenticated, "unauthorized", err)
}
func PermissionDenied[T any](logger mlogger.Logger, service mservice.Type, err error) Responder[T] {
return Error[T](logger, service, codes.PermissionDenied, "access_denied", err)
}
func FailedPrecondition[T any](logger mlogger.Logger, service mservice.Type, hint string, err error) Responder[T] {
return Error[T](logger, service, codes.FailedPrecondition, hint, err)
}
func Conflict[T any](logger mlogger.Logger, service mservice.Type, err error) Responder[T] {
return Error[T](logger, service, codes.Aborted, "conflict", err)
}
func DeadlineExceeded[T any](logger mlogger.Logger, service mservice.Type, err error) Responder[T] {
return Error[T](logger, service, codes.DeadlineExceeded, "deadline_exceeded", err)
}
func Unavailable[T any](logger mlogger.Logger, service mservice.Type, err error) Responder[T] {
return Error[T](logger, service, codes.Unavailable, "service_unavailable", err)
}
func Unimplemented[T any](logger mlogger.Logger, service mservice.Type, err error) Responder[T] {
return Error[T](logger, service, codes.Unimplemented, "not_implemented", err)
}
func AlreadyExists[T any](logger mlogger.Logger, service mservice.Type, err error) Responder[T] {
return Error[T](logger, service, codes.AlreadyExists, "already_exists", err)
}
func Auto[T any](logger mlogger.Logger, service mservice.Type, err error) Responder[T] {
switch {
case err == nil:
return Empty[T]()
case errors.Is(err, merrors.ErrInvalidArg):
return InvalidArgument[T](logger, service, err)
case errors.Is(err, merrors.ErrAccessDenied):
return PermissionDenied[T](logger, service, err)
case errors.Is(err, merrors.ErrNoData):
return NotFound[T](logger, service, err)
case errors.Is(err, merrors.ErrUnauthorized):
return Unauthorized[T](logger, service, err)
case errors.Is(err, merrors.ErrDataConflict):
return Conflict[T](logger, service, err)
default:
return Internal[T](logger, service, err)
}
}
func Execute[T any](ctx context.Context, responder Responder[T]) (*T, error) {
if responder == nil {
return nil, status.Error(codes.Internal, "missing responder")
}
return responder(ctx)
}
func Unary[TReq any, TResp any](logger mlogger.Logger, service mservice.Type, handler func(context.Context, *TReq) Responder[TResp]) func(context.Context, *TReq) (*TResp, error) {
return func(ctx context.Context, req *TReq) (*TResp, error) {
if handler == nil {
return nil, status.Error(codes.Internal, "missing handler")
}
responder := handler(ctx, req)
return Execute(ctx, responder)
}
}