GoogleCloudPlatform/microservices-demo · error

failed to convert currency: %+v

Error message

failed to convert currency: %+v

What it means

convertCurrency is the inner wrapper for CurrencyService.Convert failures; both shipping-cost and product-price conversions funnel through it. Notably it uses context.TODO() instead of the caller's ctx, so cancellations and deadlines do not propagate, and the call can outlive the request. Errors here usually mean currencyservice is unreachable or rejected the currency code.

Source

Thrown at src/checkoutservice/main.go:364

			return nil, fmt.Errorf("failed to get product #%q", item.GetProductId())
		}
		price, err := cs.convertCurrency(ctx, product.GetPriceUsd(), userCurrency)
		if err != nil {
			return nil, fmt.Errorf("failed to convert price of %q to %s", item.GetProductId(), userCurrency)
		}
		out[i] = &pb.OrderItem{
			Item: item,
			Cost: price}
	}
	return out, nil
}

func (cs *checkoutService) convertCurrency(ctx context.Context, from *pb.Money, toCurrency string) (*pb.Money, error) {
	result, err := pb.NewCurrencyServiceClient(cs.currencySvcConn).Convert(context.TODO(), &pb.CurrencyConversionRequest{
		From:   from,
		ToCode: toCurrency})
	if err != nil {
		return nil, fmt.Errorf("failed to convert currency: %+v", err)
	}
	return result, err
}

func (cs *checkoutService) chargeCard(ctx context.Context, amount *pb.Money, paymentInfo *pb.CreditCardInfo) (string, error) {
	paymentResp, err := pb.NewPaymentServiceClient(cs.paymentSvcConn).Charge(ctx, &pb.ChargeRequest{
		Amount:     amount,
		CreditCard: paymentInfo})
	if err != nil {
		return "", fmt.Errorf("could not charge the card: %+v", err)
	}
	return paymentResp.GetTransactionId(), nil
}

func (cs *checkoutService) sendOrderConfirmation(ctx context.Context, email string, order *pb.OrderResult) error {
	_, err := pb.NewEmailServiceClient(cs.emailSvcConn).SendOrderConfirmation(ctx, &pb.SendOrderConfirmationRequest{
		Email: email,
		Order: order})

View on GitHub (pinned to 72ba613a05)

Solutions

  1. Replace context.TODO() with the passed ctx so deadlines/cancellation propagate.
  2. Check the gRPC status code: Unavailable -> check currencyservice health; InvalidArgument -> check currency code and Money payload.
  3. Validate toCurrency against supported currencies before calling Convert.
  4. Verify cs.currencySvcConn dial target and network reachability.
  5. Add a timeout to the context to avoid hung conversions.

Example fix

// before
result, err := pb.NewCurrencyServiceClient(cs.currencySvcConn).Convert(context.TODO(), &pb.CurrencyConversionRequest{
	From:   from,
	ToCode: toCurrency})
// after
ctx, cancel := context.WithTimeout(ctx, 3*time.Second)
defer cancel()
result, err := pb.NewCurrencyServiceClient(cs.currencySvcConn).Convert(ctx, &pb.CurrencyConversionRequest{
	From:   from,
	ToCode: toCurrency})
if err != nil {
	return nil, fmt.Errorf("failed to convert currency: %w", err)
}
Defensive patterns

Strategy: try-catch

Validate before calling

if from == nil || toCurrency == "" {
	return status.Error(codes.InvalidArgument, "money and target currency required")
}
if !isSupportedCurrency(toCurrency) {
	return status.Error(codes.InvalidArgument, "unsupported target currency")
}

Type guard

func isConvertible(m *pb.Money, to string) bool {
	return m != nil && m.GetCurrencyCode() != "" && to != "" && isSupportedCurrency(to)
}

Try / catch

result, err := currencyClient.Convert(ctx, req)
if err != nil {
	switch status.Code(err) {
	case codes.Unavailable:
		return retryWithBackoff(ctx, req)
	case codes.InvalidArgument:
		return fmt.Errorf("bad conversion request: %w", err)
	default:
		return fmt.Errorf("failed to convert currency: %w", err)
	}
}

Prevention

When it happens

Trigger: pb.NewCurrencyServiceClient(cs.currencySvcConn).Convert(...) errors: toCurrency unsupported, from Money nil, currencyservice unavailable, or timeout (unbounded since context is TODO).

Common situations: currencyservice deployment down; unsupported currency code from client; env/config pointing checkout at wrong service address; this wrapper nested inside errors 22 and 27.

Related errors


AI-assisted analysis of GoogleCloudPlatform/microservices-demo@72ba613a05 (2026-09-02). Data as JSON: /api/errors/17025a1870d638f6. Report an issue: GitHub.