magento2: [GraphQl] Cart error messages are faulty

Preconditions (*)

  1. Magento 2.4.2+

Steps to reproduce (*)

  1. Create a cart
  2. Add 2 products to the cart with at least 4 quantities each.
  3. Set the quantity of added products below the quantity in the cart.
  4. Retrieve cart through graphql
{
  cart(cart_id: "RQg4YPcQX1htp47j6GMpTKRshfY6SVm6") {
    items {
      id
      product {
        name
        sku
        stock_status
      }
      quantity
    }
  }
}

Expected result (*)

  1. A error message which tells me which item in the cart causes the error. So that we can give correct feedback to the Customer.
  2. A cart with valid values for items.
  3. An error message for each issue with the cart.

Actual result (*)

{
  "errors": [
    {
      "message": "The requested qty is not available",
      "extensions": {
        "category": "graphql-input"
      },
      "locations": [
        {
          "line": 7,
          "column": 5
        }
      ],
      "path": [
        "cart",
        "items",
        0
      ]
    }
  ],
  "data": {
    "cart": {
      "items": [
        null,
        {
          "id": "32",
          "product": {
            "name": "Strive Shoulder Pack",
            "sku": "24-MB04",
            "stock_status": "IN_STOCK"
          },
          "quantity": 3
        },
        {
          "id": "33",
          "product": {
            "name": "Joust Duffle Bag",
            "sku": "24-MB01",
            "stock_status": "IN_STOCK"
          },
          "quantity": 4
        }
      ]
    }
  }
}
  1. In the items node there is a null entry, this only shows up when there is an error. In default Venia this breaks the cart page.
  2. I assume the path node in the error node should reference the item it’s affecting. It currently points to the above null value.
  3. Only the first error of the cart shows up.

Please provide Severity assessment for the Issue as Reporter. This information will help during Confirmation and Issue triage processes.

  • Severity: S0 - Affects critical data or functionality and leaves users without workaround.
  • Severity: S1 - Affects critical data or functionality and forces users to employ a workaround.
  • Severity: S2 - Affects non-critical data or functionality and forces users to employ a workaround.
  • Severity: S3 - Affects non-critical data or functionality and does not force users to employ a workaround.
  • Severity: S4 - Affects aesthetics, professional look and feel, “quality” or “usability”.

About this issue

  • Original URL
  • State: closed
  • Created 3 years ago
  • Reactions: 2
  • Comments: 28 (6 by maintainers)

Most upvoted comments

@Usik2203 I’d rather have something that has to be unique to identify the product, because the product name can be identical for products. Also your solution doesn’t solve the null object that suddenly got added to the items array.

Thanks, @Hexmage the provided code is helpful; one more note here is to add to di.xml

    <type name="Magento\QuoteGraphQl\Model\Resolver\CartItems">
        <plugin name="Magento_CartItems_Add_Source_Errors" type="PathTo\Your\Class\Resolver\CartItems" />
    </type>

@marwan-corals The Plugin

/**
 * Copyright © Magento, Inc. All rights reserved.
 * See COPYING.txt for license details.
 */
declare(strict_types=1);

namespace Experius\CartFix\Plugin\Magento\QuoteGraphQl\Model\Resolver;

use Magento\Framework\Exception\LocalizedException;
use Magento\Framework\GraphQl\Config\Element\Field;
use Magento\Framework\GraphQl\Exception\GraphQlNoSuchEntityException;
use Magento\Framework\GraphQl\Query\Uid;
use Magento\Framework\GraphQl\Schema\Type\ResolveInfo;
use Magento\Quote\Model\Quote;
use Magento\Quote\Model\Quote\Item as QuoteItem;
use Magento\QuoteGraphQl\Model\Cart\GetCartProducts;

/**
 * @inheritdoc
 */
class CartItems
{
    /**
     * @var GetCartProducts
     */
    private $getCartProducts;

    /** @var Uid */
    private $uidEncoder;

    /**
     * @param GetCartProducts $getCartProducts
     * @param Uid $uidEncoder
     */
    public function __construct(
        GetCartProducts $getCartProducts,
        Uid $uidEncoder
    ) {
        $this->getCartProducts = $getCartProducts;
        $this->uidEncoder = $uidEncoder;
    }

    /**
     * @inheritdoc
     */
    public function afterResolve($subject, $result, Field $field, $context, ResolveInfo $info, array $value = null, array $args = null)
    {
        if (!isset($value['model'])) {
            throw new LocalizedException(__('"model" value should be specified'));
        }
        /** @var Quote $cart */
        $cart = $value['model'];

        $itemsData = [];
        $cartProductsData = $this->getCartProductsData($cart);
        $cartItems = $cart->getAllVisibleItems();
        /** @var QuoteItem $cartItem */
        foreach ($cartItems as $cartItem) {
            $productId = $cartItem->getProduct()->getId();
            if (!isset($cartProductsData[$productId])) {
                $itemsData[] = new GraphQlNoSuchEntityException(
                    __("The product that was requested doesn't exist. Verify the product and try again.")
                );
                continue;
            }
            $productData = $cartProductsData[$productId];
            $errorMessages = null;
            if ($cart->getData('has_error')) {
                foreach ($cartItem->getErrorInfos() as $error) {
                    $errorMessages[] = $error;
                }
            }

            $itemsData[] = [
                'id' => $cartItem->getItemId(),
                'uid' => $this->uidEncoder->encode((string) $cartItem->getItemId()),
                'quantity' => $cartItem->getQty(),
                'product' => $productData,
                'errors' =>  $errorMessages,
                'model' => $cartItem,
            ];
        }
        return $itemsData;
    }

    /**
     * Get product data for cart items
     *
     * @param Quote $cart
     * @return array
     */
    private function getCartProductsData(Quote $cart): array
    {
        $products = $this->getCartProducts->execute($cart);
        $productsData = [];
        foreach ($products as $product) {
            $productsData[$product->getId()] = $product->getData();
            $productsData[$product->getId()]['model'] = $product;
            $productsData[$product->getId()]['uid'] = $this->uidEncoder->encode((string) $product->getId());
        }

        return $productsData;
    }
}

The schema.graphqls additions

interface CartItemInterface {
    errors: [Error]
}

type Error {
    origin: String
    type: Int
    message: String
}

@engcom-Hotel I also consider the fact that a null object appears in the items node when an error occurs very crucial to resolve. As it currently breaks default Venia.