diff --git a/app/code/Magento/Inventory/Controller/Adminhtml/Source/Save.php b/app/code/Magento/Inventory/Controller/Adminhtml/Source/Save.php index b1b7ab293bde..253fe8510dcf 100644 --- a/app/code/Magento/Inventory/Controller/Adminhtml/Source/Save.php +++ b/app/code/Magento/Inventory/Controller/Adminhtml/Source/Save.php @@ -77,7 +77,9 @@ public function execute() $this->messageManager->addErrorMessage(__('The Source does not exist.')); $this->processRedirectAfterFailureSave($resultRedirect); } catch (ValidationException $e) { - $this->messageManager->addErrorMessage($e->getMessage()); + foreach ($e->getErrors() as $localizedError) { + $this->messageManager->addErrorMessage($localizedError->getMessage()); + } $this->processRedirectAfterFailureSave($resultRedirect, $sourceId); } catch (CouldNotSaveException $e) { $this->messageManager->addErrorMessage($e->getMessage()); diff --git a/app/code/Magento/Inventory/Controller/Adminhtml/Stock/Save.php b/app/code/Magento/Inventory/Controller/Adminhtml/Stock/Save.php index e687397f5707..07de43a913ee 100644 --- a/app/code/Magento/Inventory/Controller/Adminhtml/Stock/Save.php +++ b/app/code/Magento/Inventory/Controller/Adminhtml/Stock/Save.php @@ -87,7 +87,9 @@ public function execute() $this->messageManager->addErrorMessage(__('The Stock does not exist.')); $this->processRedirectAfterFailureSave($resultRedirect); } catch (ValidationException $e) { - $this->messageManager->addErrorMessage($e->getMessage()); + foreach ($e->getErrors() as $localizedError) { + $this->messageManager->addErrorMessage($localizedError->getMessage()); + } $this->processRedirectAfterFailureSave($resultRedirect, $stockId); } catch (CouldNotSaveException $e) { $this->messageManager->addErrorMessage($e->getMessage()); diff --git a/app/code/Magento/Inventory/Model/Reservation/ReservationBuilder.php b/app/code/Magento/Inventory/Model/Reservation/ReservationBuilder.php index af3b2b8d7878..73f88dbe4c83 100644 --- a/app/code/Magento/Inventory/Model/Reservation/ReservationBuilder.php +++ b/app/code/Magento/Inventory/Model/Reservation/ReservationBuilder.php @@ -121,7 +121,7 @@ public function build(): ReservationInterface $validationResult = $this->reservationValidator->validate($reservation); if (!$validationResult->isValid()) { - throw new ValidationException($validationResult); + throw new ValidationException(__('Validation Failed'), null, 0, $validationResult); } return $reservation; } diff --git a/app/code/Magento/Inventory/Model/Source/Command/Save.php b/app/code/Magento/Inventory/Model/Source/Command/Save.php index c58cfd8f1ac4..a8bbaf09f096 100644 --- a/app/code/Magento/Inventory/Model/Source/Command/Save.php +++ b/app/code/Magento/Inventory/Model/Source/Command/Save.php @@ -55,7 +55,7 @@ public function execute(SourceInterface $source) $validationResult = $this->sourceValidator->validate($source); if (!$validationResult->isValid()) { - throw new ValidationException($validationResult); + throw new ValidationException(__('Validation Failed'), null, 0, $validationResult); } try { diff --git a/app/code/Magento/Inventory/Model/Stock/Command/Save.php b/app/code/Magento/Inventory/Model/Stock/Command/Save.php index 1b1b3b2ba22e..ada2ddf4c296 100644 --- a/app/code/Magento/Inventory/Model/Stock/Command/Save.php +++ b/app/code/Magento/Inventory/Model/Stock/Command/Save.php @@ -54,7 +54,7 @@ public function execute(StockInterface $stock) { $validationResult = $this->stockValidator->validate($stock); if (!$validationResult->isValid()) { - throw new ValidationException($validationResult); + throw new ValidationException(__('Validation Failed'), null, 0, $validationResult); } try { diff --git a/app/code/Magento/InventoryApi/Test/Api/SourceRepository/CarrierLinkManagementTest.php b/app/code/Magento/InventoryApi/Test/Api/SourceRepository/CarrierLinkManagementTest.php index 7982a37fa697..69dbb5435b44 100644 --- a/app/code/Magento/InventoryApi/Test/Api/SourceRepository/CarrierLinkManagementTest.php +++ b/app/code/Magento/InventoryApi/Test/Api/SourceRepository/CarrierLinkManagementTest.php @@ -117,9 +117,14 @@ public function testAssignCarrierLinksIfUseGlobalConfigurationChosen() ]; $expectedErrorData = [ - 'message' => 'You can\'t configure "%field" because you have chosen Global Shipping configuration.', - 'parameters' => [ - 'field' => SourceInterface::CARRIER_LINKS, + 'message' => 'Validation Failed', + 'errors' => [ + [ + 'message' => 'You can\'t configure "%field" because you have chosen Global Shipping configuration.', + 'parameters' => [ + 'field' => SourceInterface::CARRIER_LINKS, + ], + ], ], ]; @@ -128,17 +133,16 @@ public function testAssignCarrierLinksIfUseGlobalConfigurationChosen() $this->fail('Expected throwing exception'); } catch (\Exception $e) { if (TESTS_WEB_API_ADAPTER == self::ADAPTER_REST) { - $errorData = $this->processRestExceptionResult($e); - self::assertEquals($expectedErrorData, $errorData); + self::assertEquals($expectedErrorData, $this->processRestExceptionResult($e)); self::assertEquals(Exception::HTTP_BAD_REQUEST, $e->getCode()); } elseif (TESTS_WEB_API_ADAPTER == self::ADAPTER_SOAP) { $this->assertInstanceOf('SoapFault', $e); - $this->checkSoapFault( - $e, - $expectedErrorData['message'], - 'env:Sender', - $expectedErrorData['parameters'] - ); + // @see \Magento\TestFramework\TestCase\WebapiAbstract::getActualWrappedErrors() + $expectedWrappedErrors = $expectedErrorData['errors']; + $expectedWrappedErrors[0]['params'] = $expectedWrappedErrors[0]['parameters']; + unset($expectedWrappedErrors[0]['parameters']); + + $this->checkSoapFault($e, $expectedErrorData['message'], 'env:Sender', [], $expectedWrappedErrors); } else { throw $e; } diff --git a/app/code/Magento/InventoryApi/Test/Api/SourceRepository/ValidationTest.php b/app/code/Magento/InventoryApi/Test/Api/SourceRepository/ValidationTest.php index d3d0105fb9a8..ad5b6cf34b47 100644 --- a/app/code/Magento/InventoryApi/Test/Api/SourceRepository/ValidationTest.php +++ b/app/code/Magento/InventoryApi/Test/Api/SourceRepository/ValidationTest.php @@ -54,17 +54,11 @@ public function testCreateWithMissedRequiredFields($field, array $expectedErrorD $this->fail('Expected throwing exception'); } catch (\Exception $e) { if (TESTS_WEB_API_ADAPTER == self::ADAPTER_REST) { - $errorData = $this->processRestExceptionResult($e); - self::assertEquals($expectedErrorData['rest_message'], $errorData['message']); - self::assertEquals($expectedErrorData['parameters'], $errorData['parameters']); + self::assertEquals($expectedErrorData['rest'], $this->processRestExceptionResult($e)); self::assertEquals(Exception::HTTP_BAD_REQUEST, $e->getCode()); } elseif (TESTS_WEB_API_ADAPTER == self::ADAPTER_SOAP) { $this->assertInstanceOf('SoapFault', $e); - $this->checkSoapFault( - $e, - $expectedErrorData['soap_message'], - 'Sender' - ); + $this->checkSoapFault($e, $expectedErrorData['soap']['message'], 'Sender'); } else { throw $e; } @@ -80,20 +74,38 @@ public function dataProviderRequiredFields() 'without_' . SourceInterface::NAME => [ SourceInterface::NAME, [ - 'rest_message' => '"%field" can not be empty.', - 'soap_message' => sprintf('object has no \'%s\' property', SourceInterface::NAME), - 'parameters' => [ - 'field' => SourceInterface::NAME, + 'rest' => [ + 'message' => 'Validation Failed', + 'errors' => [ + [ + 'message' => '"%field" can not be empty.', + 'parameters' => [ + 'field' => SourceInterface::NAME, + ], + ], + ], + ], + 'soap' => [ + 'message' => 'object has no \'' . SourceInterface::NAME . '\' property', ], ], ], 'without_' . SourceInterface::POSTCODE => [ SourceInterface::POSTCODE, [ - 'rest_message' => '"%field" can not be empty.', - 'soap_message' => sprintf('object has no \'%s\' property', SourceInterface::POSTCODE), - 'parameters' => [ - 'field' => SourceInterface::POSTCODE, + 'rest' => [ + 'message' => 'Validation Failed', + 'errors' => [ + [ + 'message' => '"%field" can not be empty.', + 'parameters' => [ + 'field' => SourceInterface::POSTCODE, + ], + ], + ], + ], + 'soap' => [ + 'message' => 'object has no \'' . SourceInterface::POSTCODE . '\' property', ], ], ], @@ -160,9 +172,14 @@ public function failedValidationDataProvider() SourceInterface::NAME, null, [ - 'message' => '"%field" can not be empty.', - 'parameters' => [ - 'field' => SourceInterface::NAME, + 'message' => 'Validation Failed', + 'errors' => [ + [ + 'message' => '"%field" can not be empty.', + 'parameters' => [ + 'field' => SourceInterface::NAME, + ], + ], ], ], ], @@ -170,9 +187,14 @@ public function failedValidationDataProvider() SourceInterface::NAME, ' ', [ - 'message' => '"%field" can not be empty.', - 'parameters' => [ - 'field' => SourceInterface::NAME, + 'message' => 'Validation Failed', + 'errors' => [ + [ + 'message' => '"%field" can not be empty.', + 'parameters' => [ + 'field' => SourceInterface::NAME, + ], + ], ], ], ], @@ -180,9 +202,14 @@ public function failedValidationDataProvider() SourceInterface::POSTCODE, null, [ - 'message' => '"%field" can not be empty.', - 'parameters' => [ - 'field' => SourceInterface::POSTCODE, + 'message' => 'Validation Failed', + 'errors' => [ + [ + 'message' => '"%field" can not be empty.', + 'parameters' => [ + 'field' => SourceInterface::POSTCODE, + ], + ], ], ], ], @@ -190,9 +217,14 @@ public function failedValidationDataProvider() SourceInterface::POSTCODE, ' ', [ - 'message' => '"%field" can not be empty.', - 'parameters' => [ - 'field' => SourceInterface::POSTCODE, + 'message' => 'Validation Failed', + 'errors' => [ + [ + 'message' => '"%field" can not be empty.', + 'parameters' => [ + 'field' => SourceInterface::POSTCODE, + ], + ], ], ], ], @@ -200,9 +232,14 @@ public function failedValidationDataProvider() SourceInterface::COUNTRY_ID, null, [ - 'message' => '"%field" can not be empty.', - 'parameters' => [ - 'field' => SourceInterface::COUNTRY_ID, + 'message' => 'Validation Failed', + 'errors' => [ + [ + 'message' => '"%field" can not be empty.', + 'parameters' => [ + 'field' => SourceInterface::COUNTRY_ID, + ], + ], ], ], ], @@ -210,9 +247,14 @@ public function failedValidationDataProvider() SourceInterface::COUNTRY_ID, ' ', [ - 'message' => '"%field" can not be empty.', - 'parameters' => [ - 'field' => SourceInterface::COUNTRY_ID, + 'message' => 'Validation Failed', + 'errors' => [ + [ + 'message' => '"%field" can not be empty.', + 'parameters' => [ + 'field' => SourceInterface::COUNTRY_ID, + ], + ], ], ], ], @@ -232,17 +274,19 @@ private function webApiCall(array $serviceInfo, array $data, array $expectedErro $this->fail('Expected throwing exception'); } catch (\Exception $e) { if (TESTS_WEB_API_ADAPTER == self::ADAPTER_REST) { - $errorData = $this->processRestExceptionResult($e); - self::assertEquals($expectedErrorData, $errorData); + self::assertEquals($expectedErrorData, $this->processRestExceptionResult($e)); self::assertEquals(Exception::HTTP_BAD_REQUEST, $e->getCode()); } elseif (TESTS_WEB_API_ADAPTER == self::ADAPTER_SOAP) { $this->assertInstanceOf('SoapFault', $e); - $this->checkSoapFault( - $e, - $expectedErrorData['message'], - 'env:Sender', - $expectedErrorData['parameters'] - ); + $expectedWrappedErrors = []; + foreach ($expectedErrorData['errors'] as $error) { + // @see \Magento\TestFramework\TestCase\WebapiAbstract::getActualWrappedErrors() + $expectedWrappedErrors[] = [ + 'message' => $error['message'], + 'params' => $error['parameters'], + ]; + } + $this->checkSoapFault($e, $expectedErrorData['message'], 'env:Sender', [], $expectedWrappedErrors); } else { throw $e; } diff --git a/app/code/Magento/InventoryApi/Test/Api/StockRepository/ValidationTest.php b/app/code/Magento/InventoryApi/Test/Api/StockRepository/ValidationTest.php index 753122eecde2..0472b1bf37b1 100644 --- a/app/code/Magento/InventoryApi/Test/Api/StockRepository/ValidationTest.php +++ b/app/code/Magento/InventoryApi/Test/Api/StockRepository/ValidationTest.php @@ -52,17 +52,11 @@ public function testCreateWithMissedRequiredFields($field, array $expectedErrorD $this->fail('Expected throwing exception'); } catch (\Exception $e) { if (TESTS_WEB_API_ADAPTER == self::ADAPTER_REST) { - $errorData = $this->processRestExceptionResult($e); - self::assertEquals($expectedErrorData['rest_message'], $errorData['message']); - self::assertEquals($expectedErrorData['parameters'], $errorData['parameters']); + self::assertEquals($expectedErrorData['rest'], $this->processRestExceptionResult($e)); self::assertEquals(Exception::HTTP_BAD_REQUEST, $e->getCode()); } elseif (TESTS_WEB_API_ADAPTER == self::ADAPTER_SOAP) { $this->assertInstanceOf('SoapFault', $e); - $this->checkSoapFault( - $e, - $expectedErrorData['soap_message'], - 'Sender' - ); + $this->checkSoapFault($e, $expectedErrorData['soap']['message'], 'Sender'); } else { throw $e; } @@ -78,10 +72,19 @@ public function dataProviderRequiredFields() 'without_' . StockInterface::NAME => [ StockInterface::NAME, [ - 'rest_message' => '"%field" can not be empty.', - 'soap_message' => sprintf('object has no \'%s\' property', StockInterface::NAME), - 'parameters' => [ - 'field' => StockInterface::NAME, + 'rest' => [ + 'message' => 'Validation Failed', + 'errors' => [ + [ + 'message' => '"%field" can not be empty.', + 'parameters' => [ + 'field' => StockInterface::NAME, + ], + ], + ], + ], + 'soap' => [ + 'message' => 'object has no \'' . StockInterface::NAME . '\' property', ], ], ], @@ -148,9 +151,14 @@ public function failedValidationDataProvider() StockInterface::NAME, null, [ - 'message' => '"%field" can not be empty.', - 'parameters' => [ - 'field' => StockInterface::NAME, + 'message' => 'Validation Failed', + 'errors' => [ + [ + 'message' => '"%field" can not be empty.', + 'parameters' => [ + 'field' => StockInterface::NAME, + ], + ], ], ], ], @@ -158,9 +166,14 @@ public function failedValidationDataProvider() StockInterface::NAME, ' ', [ - 'message' => '"%field" can not be empty.', - 'parameters' => [ - 'field' => StockInterface::NAME, + 'message' => 'Validation Failed', + 'errors' => [ + [ + 'message' => '"%field" can not be empty.', + 'parameters' => [ + 'field' => StockInterface::NAME, + ], + ], ], ], ], @@ -180,17 +193,19 @@ private function webApiCall(array $serviceInfo, array $data, array $expectedErro $this->fail('Expected throwing exception'); } catch (\Exception $e) { if (TESTS_WEB_API_ADAPTER == self::ADAPTER_REST) { - $errorData = $this->processRestExceptionResult($e); - self::assertEquals($expectedErrorData, $errorData); + self::assertEquals($expectedErrorData, $this->processRestExceptionResult($e)); self::assertEquals(Exception::HTTP_BAD_REQUEST, $e->getCode()); } elseif (TESTS_WEB_API_ADAPTER == self::ADAPTER_SOAP) { $this->assertInstanceOf('SoapFault', $e); - $this->checkSoapFault( - $e, - $expectedErrorData['message'], - 'env:Sender', - $expectedErrorData['parameters'] - ); + $expectedWrappedErrors = []; + foreach ($expectedErrorData['errors'] as $error) { + // @see \Magento\TestFramework\TestCase\WebapiAbstract::getActualWrappedErrors() + $expectedWrappedErrors[] = [ + 'message' => $error['message'], + 'params' => $error['parameters'], + ]; + } + $this->checkSoapFault($e, $expectedErrorData['message'], 'env:Sender', [], $expectedWrappedErrors); } else { throw $e; } diff --git a/app/code/Magento/InventoryApi/Test/Api/StockSourceLink/UnassignSourceFromStockTest.php b/app/code/Magento/InventoryApi/Test/Api/StockSourceLink/UnassignSourceFromStockTest.php index 0998df4a61a9..b01d2efb2005 100644 --- a/app/code/Magento/InventoryApi/Test/Api/StockSourceLink/UnassignSourceFromStockTest.php +++ b/app/code/Magento/InventoryApi/Test/Api/StockSourceLink/UnassignSourceFromStockTest.php @@ -91,8 +91,7 @@ public function testUnassignSourceFromStockWithWrongParameters($sourceId, $stock $this->_webApiCall($serviceInfo); $this->fail('Expected throwing exception'); } catch (\Exception $e) { - $errorData = $this->processRestExceptionResult($e); - self::assertEquals($expectedErrorData['message'], $errorData['message']); + self::assertEquals($expectedErrorData, $this->processRestExceptionResult($e)); self::assertEquals(Exception::HTTP_BAD_REQUEST, $e->getCode()); } } diff --git a/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/WebapiAbstract.php b/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/WebapiAbstract.php index 46a2358b17bb..d09b6095af00 100644 --- a/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/WebapiAbstract.php +++ b/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/WebapiAbstract.php @@ -638,27 +638,15 @@ protected function _checkWrappedErrors($expectedWrappedErrors, $errorDetails) $wrappedErrorsNode = Fault::NODE_DETAIL_WRAPPED_ERRORS; if ($expectedWrappedErrors) { $wrappedErrorNode = Fault::NODE_DETAIL_WRAPPED_ERROR; - $wrappedErrorNodeFieldName = 'fieldName'; - $wrappedErrorNodeValue = Fault::NODE_DETAIL_WRAPPED_ERROR_VALUE; $actualWrappedErrors = []; if (isset($errorDetails->$wrappedErrorsNode->$wrappedErrorNode)) { - if (is_array($errorDetails->$wrappedErrorsNode->$wrappedErrorNode)) { - foreach ($errorDetails->$wrappedErrorsNode->$wrappedErrorNode as $error) { - $actualParameters = []; - foreach ($error->parameters->parameter as $parameter) { - $actualParameters[$parameter->key] = $parameter->value; - } - $actualWrappedErrors[] = [ - 'message' => $error->message, - 'params' => $actualParameters, - ]; + $errorNode = $errorDetails->$wrappedErrorsNode->$wrappedErrorNode; + if (is_array($errorNode)) { + foreach ($errorNode as $error) { + $actualWrappedErrors[] = $this->getActualWrappedErrors($error); } } else { - $error = $errorDetails->$wrappedErrorsNode->$wrappedErrorNode; - $actualWrappedErrors[] = [ - "fieldName" => $error->$wrappedErrorNodeFieldName, - "value" => $error->$wrappedErrorNodeValue, - ]; + $actualWrappedErrors[] = $this->getActualWrappedErrors($errorNode); } } $this->assertEquals( @@ -673,4 +661,26 @@ protected function _checkWrappedErrors($expectedWrappedErrors, $errorDetails) ); } } + + /** + * @param \stdClass $errorNode + * @return array + */ + private function getActualWrappedErrors(\stdClass $errorNode) + { + $actualParameters = []; + $parameterNode = $errorNode->parameters->parameter; + if (is_array($parameterNode)) { + foreach ($parameterNode as $parameter) { + $actualParameters[$parameter->key] = $parameter->value; + } + } else { + $actualParameters[$parameterNode->key] = $parameterNode->value; + } + return [ + 'message' => $errorNode->message, + // Can not rename on parameters due to Backward Compatibility + 'params' => $actualParameters, + ]; + } } diff --git a/lib/internal/Magento/Framework/Exception/AbstractAggregateException.php b/lib/internal/Magento/Framework/Exception/AbstractAggregateException.php index 56c3bd285bc1..0b92e640713e 100644 --- a/lib/internal/Magento/Framework/Exception/AbstractAggregateException.php +++ b/lib/internal/Magento/Framework/Exception/AbstractAggregateException.php @@ -11,7 +11,7 @@ /** * @api */ -abstract class AbstractAggregateException extends LocalizedException +abstract class AbstractAggregateException extends LocalizedException implements AggregateExceptionInterface { /** * The array of errors that have been added via the addError() method @@ -89,9 +89,7 @@ public function wasErrorAdded() } /** - * Get the array of LocalizedException objects. Get an empty array if no errors were added. - * - * @return \Magento\Framework\Exception\LocalizedException[] + * @inheritdoc */ public function getErrors() { diff --git a/lib/internal/Magento/Framework/Exception/AggregateExceptionInterface.php b/lib/internal/Magento/Framework/Exception/AggregateExceptionInterface.php new file mode 100644 index 000000000000..d7b6f6fce3f8 --- /dev/null +++ b/lib/internal/Magento/Framework/Exception/AggregateExceptionInterface.php @@ -0,0 +1,25 @@ +validationResult = $validationResult; + } + + /** + * @inheritdoc */ - public function __construct(ValidationResult $validationResult, \Exception $cause = null, $code = 0) + public function getErrors() { - foreach ($validationResult->getErrors() as $error) { - $this->addError($error); + $localizedErrors = []; + if (null !== $this->validationResult) { + foreach ($this->validationResult->getErrors() as $error) { + $localizedErrors[] = new LocalizedException($error); + } } - parent::__construct($this->phrase, $cause, $code); + return $localizedErrors; } } diff --git a/lib/internal/Magento/Framework/Webapi/ErrorProcessor.php b/lib/internal/Magento/Framework/Webapi/ErrorProcessor.php index bb86c6b1bdc9..0655bfda0bbc 100644 --- a/lib/internal/Magento/Framework/Webapi/ErrorProcessor.php +++ b/lib/internal/Magento/Framework/Webapi/ErrorProcessor.php @@ -8,7 +8,7 @@ use Magento\Framework\App\Filesystem\DirectoryList; use Magento\Framework\App\ObjectManager; use Magento\Framework\App\State; -use Magento\Framework\Exception\AbstractAggregateException; +use Magento\Framework\Exception\AggregateExceptionInterface; use Magento\Framework\Exception\AuthenticationException; use Magento\Framework\Exception\AuthorizationException; use Magento\Framework\Exception\LocalizedException; @@ -126,7 +126,7 @@ public function maskException(\Exception $exception) $httpCode = WebapiException::HTTP_BAD_REQUEST; } - if ($exception instanceof AbstractAggregateException) { + if ($exception instanceof AggregateExceptionInterface) { $errors = $exception->getErrors(); } else { $errors = null;