145 lines
4.6 KiB
Go
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)
|
|
}
|
|
}
|