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) } }