gRPC error translation: invalid argument support
This commit is contained in:
@@ -72,7 +72,7 @@ func (s *Service) Register(router routers.GRPC) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return router.Register(func(reg grpc.ServiceRegistrar) {
|
return router.Register(func(reg grpc.ServiceRegistrar) {
|
||||||
orchestrationv2.RegisterPaymentOrchestratorServiceServer(reg, newV2GRPCServer(s.v2))
|
orchestrationv2.RegisterPaymentOrchestratorServiceServer(reg, newV2GRPCServer(s.v2, s.logger))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import (
|
|||||||
"github.com/tech/sendico/payments/orchestrator/internal/service/orchestrationv2/psvc"
|
"github.com/tech/sendico/payments/orchestrator/internal/service/orchestrationv2/psvc"
|
||||||
"github.com/tech/sendico/payments/orchestrator/internal/service/orchestrationv2/sexec"
|
"github.com/tech/sendico/payments/orchestrator/internal/service/orchestrationv2/sexec"
|
||||||
"github.com/tech/sendico/payments/storage"
|
"github.com/tech/sendico/payments/storage"
|
||||||
|
"github.com/tech/sendico/pkg/api/routers/gsresponse"
|
||||||
"github.com/tech/sendico/pkg/merrors"
|
"github.com/tech/sendico/pkg/merrors"
|
||||||
"github.com/tech/sendico/pkg/mlogger"
|
"github.com/tech/sendico/pkg/mlogger"
|
||||||
"github.com/tech/sendico/pkg/mservice"
|
"github.com/tech/sendico/pkg/mservice"
|
||||||
@@ -145,21 +146,40 @@ func buildPaymentRepositoryV2(repo storage.Repository, logger mlogger.Logger) (p
|
|||||||
|
|
||||||
type v2GRPCServer struct {
|
type v2GRPCServer struct {
|
||||||
orchestrationv2.UnimplementedPaymentOrchestratorServiceServer
|
orchestrationv2.UnimplementedPaymentOrchestratorServiceServer
|
||||||
svc psvc.Service
|
svc psvc.Service
|
||||||
|
logger mlogger.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
func newV2GRPCServer(svc psvc.Service) *v2GRPCServer {
|
func newV2GRPCServer(svc psvc.Service, logger mlogger.Logger) *v2GRPCServer {
|
||||||
return &v2GRPCServer{svc: svc}
|
if logger == nil {
|
||||||
|
logger = zap.NewNop()
|
||||||
|
}
|
||||||
|
return &v2GRPCServer{
|
||||||
|
svc: svc,
|
||||||
|
logger: logger.Named("grpc"),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *v2GRPCServer) ExecutePayment(ctx context.Context, req *orchestrationv2.ExecutePaymentRequest) (*orchestrationv2.ExecutePaymentResponse, error) {
|
func (s *v2GRPCServer) ExecutePayment(ctx context.Context, req *orchestrationv2.ExecutePaymentRequest) (*orchestrationv2.ExecutePaymentResponse, error) {
|
||||||
return s.svc.ExecutePayment(ctx, req)
|
resp, err := s.svc.ExecutePayment(ctx, req)
|
||||||
|
if err != nil {
|
||||||
|
return gsresponse.Execute(ctx, gsresponse.Auto[orchestrationv2.ExecutePaymentResponse](s.logger, mservice.PaymentOrchestrator, err))
|
||||||
|
}
|
||||||
|
return resp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *v2GRPCServer) GetPayment(ctx context.Context, req *orchestrationv2.GetPaymentRequest) (*orchestrationv2.GetPaymentResponse, error) {
|
func (s *v2GRPCServer) GetPayment(ctx context.Context, req *orchestrationv2.GetPaymentRequest) (*orchestrationv2.GetPaymentResponse, error) {
|
||||||
return s.svc.GetPayment(ctx, req)
|
resp, err := s.svc.GetPayment(ctx, req)
|
||||||
|
if err != nil {
|
||||||
|
return gsresponse.Execute(ctx, gsresponse.Auto[orchestrationv2.GetPaymentResponse](s.logger, mservice.PaymentOrchestrator, err))
|
||||||
|
}
|
||||||
|
return resp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *v2GRPCServer) ListPayments(ctx context.Context, req *orchestrationv2.ListPaymentsRequest) (*orchestrationv2.ListPaymentsResponse, error) {
|
func (s *v2GRPCServer) ListPayments(ctx context.Context, req *orchestrationv2.ListPaymentsRequest) (*orchestrationv2.ListPaymentsResponse, error) {
|
||||||
return s.svc.ListPayments(ctx, req)
|
resp, err := s.svc.ListPayments(ctx, req)
|
||||||
|
if err != nil {
|
||||||
|
return gsresponse.Execute(ctx, gsresponse.Auto[orchestrationv2.ListPaymentsResponse](s.logger, mservice.PaymentOrchestrator, err))
|
||||||
|
}
|
||||||
|
return resp, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,75 +5,47 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
ledgerclient "github.com/tech/sendico/ledger/client"
|
"github.com/tech/sendico/payments/orchestrator/internal/service/orchestrationv2/psvc"
|
||||||
"github.com/tech/sendico/payments/storage"
|
"github.com/tech/sendico/pkg/merrors"
|
||||||
quotestorage "github.com/tech/sendico/payments/storage/quote"
|
orchestrationv2 "github.com/tech/sendico/pkg/proto/payments/orchestration/v2"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
|
"google.golang.org/grpc/codes"
|
||||||
|
"google.golang.org/grpc/status"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestNewOrchestrationV2Service_FailsWhenLedgerClientMissing(t *testing.T) {
|
func TestV2GRPCServerExecutePayment_MapsInvalidArgument(t *testing.T) {
|
||||||
svc, repo, err := newOrchestrationV2Service(zap.NewNop(), fakeStorageRepo{}, v2RuntimeDeps{})
|
srv := newV2GRPCServer(fakeV2Service{
|
||||||
|
executeErr: merrors.InvalidArgument("intent_ref is required for batch quotation"),
|
||||||
|
}, zap.NewNop())
|
||||||
|
|
||||||
|
_, err := srv.ExecutePayment(context.Background(), &orchestrationv2.ExecutePaymentRequest{})
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Fatal("expected error")
|
t.Fatal("expected error")
|
||||||
}
|
}
|
||||||
if !strings.Contains(err.Error(), "ledger client is required") {
|
if got, want := status.Code(err), codes.InvalidArgument; got != want {
|
||||||
t.Fatalf("unexpected error: %v", err)
|
t.Fatalf("unexpected grpc status code: got=%s want=%s", got, want)
|
||||||
}
|
}
|
||||||
if svc != nil {
|
if got := status.Convert(err).Message(); !strings.Contains(got, "intent_ref is required for batch quotation") {
|
||||||
t.Fatal("expected nil service")
|
t.Fatalf("unexpected grpc status message: %q", got)
|
||||||
}
|
|
||||||
if repo != nil {
|
|
||||||
t.Fatal("expected nil payment repo")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNewOrchestrationV2Service_FailsWhenLedgerClientUnavailable(t *testing.T) {
|
type fakeV2Service struct {
|
||||||
ledger := unavailableLedgerClient{Fake: &ledgerclient.Fake{}}
|
executeErr error
|
||||||
svc, repo, err := newOrchestrationV2Service(zap.NewNop(), fakeStorageRepo{}, v2RuntimeDeps{
|
|
||||||
LedgerClient: ledger,
|
|
||||||
})
|
|
||||||
if err == nil {
|
|
||||||
t.Fatal("expected error")
|
|
||||||
}
|
|
||||||
if !strings.Contains(err.Error(), "ledger client is unavailable") {
|
|
||||||
t.Fatalf("unexpected error: %v", err)
|
|
||||||
}
|
|
||||||
if svc != nil {
|
|
||||||
t.Fatal("expected nil service")
|
|
||||||
}
|
|
||||||
if repo != nil {
|
|
||||||
t.Fatal("expected nil payment repo")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type unavailableLedgerClient struct {
|
func (f fakeV2Service) ExecutePayment(context.Context, *orchestrationv2.ExecutePaymentRequest) (*orchestrationv2.ExecutePaymentResponse, error) {
|
||||||
*ledgerclient.Fake
|
return nil, f.executeErr
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u unavailableLedgerClient) Available() bool {
|
func (fakeV2Service) GetPayment(context.Context, *orchestrationv2.GetPaymentRequest) (*orchestrationv2.GetPaymentResponse, error) {
|
||||||
return false
|
return &orchestrationv2.GetPaymentResponse{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type fakeStorageRepo struct{}
|
func (fakeV2Service) ListPayments(context.Context, *orchestrationv2.ListPaymentsRequest) (*orchestrationv2.ListPaymentsResponse, error) {
|
||||||
|
return &orchestrationv2.ListPaymentsResponse{}, nil
|
||||||
func (fakeStorageRepo) Ping(context.Context) error {
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (fakeStorageRepo) Payments() storage.PaymentsStore {
|
func (fakeV2Service) ReconcileExternal(context.Context, psvc.ReconcileExternalInput) (*psvc.ReconcileExternalOutput, error) {
|
||||||
return nil
|
return &psvc.ReconcileExternalOutput{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (fakeStorageRepo) PaymentMethods() storage.PaymentMethodsStore {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (fakeStorageRepo) Quotes() quotestorage.QuotesStore {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (fakeStorageRepo) Routes() storage.RoutesStore {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ storage.Repository = fakeStorageRepo{}
|
|
||||||
|
|||||||
41
api/server/internal/server/paymentapiimp/grpc_error.go
Normal file
41
api/server/internal/server/paymentapiimp/grpc_error.go
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
package paymentapiimp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/tech/sendico/pkg/api/http/response"
|
||||||
|
"github.com/tech/sendico/pkg/mlogger"
|
||||||
|
"github.com/tech/sendico/pkg/mservice"
|
||||||
|
"google.golang.org/grpc/codes"
|
||||||
|
"google.golang.org/grpc/status"
|
||||||
|
)
|
||||||
|
|
||||||
|
func grpcErrorResponse(logger mlogger.Logger, source mservice.Type, err error) http.HandlerFunc {
|
||||||
|
statusErr, ok := status.FromError(err)
|
||||||
|
if !ok {
|
||||||
|
return response.Internal(logger, source, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch statusErr.Code() {
|
||||||
|
case codes.InvalidArgument:
|
||||||
|
return response.BadRequest(logger, source, "invalid_argument", statusErr.Message())
|
||||||
|
case codes.NotFound:
|
||||||
|
return response.NotFound(logger, source, statusErr.Message())
|
||||||
|
case codes.PermissionDenied:
|
||||||
|
return response.AccessDenied(logger, source, statusErr.Message())
|
||||||
|
case codes.Unauthenticated:
|
||||||
|
return response.Unauthorized(logger, source, statusErr.Message())
|
||||||
|
case codes.AlreadyExists, codes.Aborted:
|
||||||
|
return response.DataConflict(logger, source, statusErr.Message())
|
||||||
|
case codes.Unimplemented:
|
||||||
|
return response.NotImplemented(logger, source, statusErr.Message())
|
||||||
|
case codes.FailedPrecondition:
|
||||||
|
return response.Error(logger, source, http.StatusPreconditionFailed, "failed_precondition", statusErr.Message())
|
||||||
|
case codes.DeadlineExceeded:
|
||||||
|
return response.Error(logger, source, http.StatusGatewayTimeout, "deadline_exceeded", statusErr.Message())
|
||||||
|
case codes.Unavailable:
|
||||||
|
return response.Error(logger, source, http.StatusServiceUnavailable, "service_unavailable", statusErr.Message())
|
||||||
|
default:
|
||||||
|
return response.Internal(logger, source, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -80,7 +80,7 @@ func (a *PaymentAPI) listPayments(r *http.Request, account *model.Account, token
|
|||||||
resp, err := a.execution.ListPayments(ctx, req)
|
resp, err := a.execution.ListPayments(ctx, req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
a.logger.Warn("Failed to list payments", zap.Error(err), mzap.ObjRef("organization_ref", orgRef))
|
a.logger.Warn("Failed to list payments", zap.Error(err), mzap.ObjRef("organization_ref", orgRef))
|
||||||
return response.Auto(a.logger, a.Name(), err)
|
return grpcErrorResponse(a.logger, a.Name(), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return sresponse.PaymentsListResponse(a.logger, resp, token)
|
return sresponse.PaymentsListResponse(a.logger, resp, token)
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ func (a *PaymentAPI) initiatePayment(r *http.Request, account *model.Account, to
|
|||||||
})
|
})
|
||||||
if qErr != nil {
|
if qErr != nil {
|
||||||
a.logger.Warn("Failed to quote payment before execution", zap.Error(qErr), mzap.ObjRef("organization_ref", orgRef))
|
a.logger.Warn("Failed to quote payment before execution", zap.Error(qErr), mzap.ObjRef("organization_ref", orgRef))
|
||||||
return response.Auto(a.logger, a.Name(), qErr)
|
return grpcErrorResponse(a.logger, a.Name(), qErr)
|
||||||
}
|
}
|
||||||
quotationRef = strings.TrimSpace(quoteResp.GetQuote().GetQuoteRef())
|
quotationRef = strings.TrimSpace(quoteResp.GetQuote().GetQuoteRef())
|
||||||
if quotationRef == "" {
|
if quotationRef == "" {
|
||||||
@@ -97,7 +97,7 @@ func (a *PaymentAPI) initiatePayment(r *http.Request, account *model.Account, to
|
|||||||
resp, err := a.execution.ExecutePayment(ctx, req)
|
resp, err := a.execution.ExecutePayment(ctx, req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
a.logger.Warn("Failed to initiate payment", zap.Error(err), mzap.ObjRef("organization_ref", orgRef))
|
a.logger.Warn("Failed to initiate payment", zap.Error(err), mzap.ObjRef("organization_ref", orgRef))
|
||||||
return response.Auto(a.logger, a.Name(), err)
|
return grpcErrorResponse(a.logger, a.Name(), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return sresponse.PaymentResponse(a.logger, resp.GetPayment(), token)
|
return sresponse.PaymentResponse(a.logger, resp.GetPayment(), token)
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ func (a *PaymentAPI) initiatePaymentsByQuote(r *http.Request, account *model.Acc
|
|||||||
resp, err := a.execution.ExecutePayment(ctx, req)
|
resp, err := a.execution.ExecutePayment(ctx, req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
a.logger.Warn("Failed to initiate batch payments", zap.Error(err), zap.String("organization_ref", orgRef.Hex()))
|
a.logger.Warn("Failed to initiate batch payments", zap.Error(err), zap.String("organization_ref", orgRef.Hex()))
|
||||||
return response.Auto(a.logger, a.Name(), err)
|
return grpcErrorResponse(a.logger, a.Name(), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
payments := make([]*orchestrationv2.Payment, 0, 1)
|
payments := make([]*orchestrationv2.Payment, 0, 1)
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ func (a *PaymentAPI) quotePayment(r *http.Request, account *model.Account, token
|
|||||||
resp, err := a.quotation.QuotePayment(ctx, req)
|
resp, err := a.quotation.QuotePayment(ctx, req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
a.logger.Warn("Failed to quote payment", zap.Error(err), zap.String("organization_ref", orgRef.Hex()))
|
a.logger.Warn("Failed to quote payment", zap.Error(err), zap.String("organization_ref", orgRef.Hex()))
|
||||||
return response.Auto(a.logger, a.Name(), err)
|
return grpcErrorResponse(a.logger, a.Name(), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return sresponse.PaymentQuoteResponse(a.logger, resp.GetIdempotencyKey(), resp.GetQuote(), token)
|
return sresponse.PaymentQuoteResponse(a.logger, resp.GetIdempotencyKey(), resp.GetQuote(), token)
|
||||||
@@ -118,7 +118,7 @@ func (a *PaymentAPI) quotePayments(r *http.Request, account *model.Account, toke
|
|||||||
resp, err := a.quotation.QuotePayments(ctx, req)
|
resp, err := a.quotation.QuotePayments(ctx, req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
a.logger.Warn("Failed to quote payments", zap.Error(err), zap.String("organization_ref", orgRef.Hex()))
|
a.logger.Warn("Failed to quote payments", zap.Error(err), zap.String("organization_ref", orgRef.Hex()))
|
||||||
return response.Auto(a.logger, a.Name(), err)
|
return grpcErrorResponse(a.logger, a.Name(), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return sresponse.PaymentQuotesResponse(a.logger, resp, token)
|
return sresponse.PaymentQuotesResponse(a.logger, resp, token)
|
||||||
|
|||||||
Reference in New Issue
Block a user