diff --git a/sdk/core/azure-core/inc/azure/core/exception.hpp b/sdk/core/azure-core/inc/azure/core/exception.hpp index def03d8da..138bc4194 100644 --- a/sdk/core/azure-core/inc/azure/core/exception.hpp +++ b/sdk/core/azure-core/inc/azure/core/exception.hpp @@ -92,6 +92,18 @@ namespace Azure { namespace Core { const std::string& message, std::unique_ptr rawResponse); + /** + * @brief Constructs a new `%RequestFailedException` object with an HTTP raw response. + * + * @note The HTTP raw response is parsed to populate information expected from all Azure + * Services like the status code, reason phrase and some headers like the request ID. A concrete + * Service exception which derives from this exception uses its constructor to parse the HTTP + * raw response adding the service specific values to the exception. + * + * @param rawResponse The HTTP raw response from the service. + */ + explicit RequestFailedException(std::unique_ptr& rawResponse); + /** * @brief Constructs a new `%RequestFailedException` by copying from an existing one. * @note Copies the #Azure::Core::Http::RawResponse into the new `RequestFailedException`. @@ -133,5 +145,10 @@ namespace Azure { namespace Core { * */ ~RequestFailedException() = default; + + private: + std::string getRawResponseField( + std::unique_ptr& rawResponse, + std::string fieldName); }; }} // namespace Azure::Core diff --git a/sdk/core/azure-core/src/exception.cpp b/sdk/core/azure-core/src/exception.cpp index 801406b95..1b9df29b4 100644 --- a/sdk/core/azure-core/src/exception.cpp +++ b/sdk/core/azure-core/src/exception.cpp @@ -3,10 +3,12 @@ #include "azure/core/exception.hpp" #include "azure/core/http/http.hpp" - +#include "azure/core/http/policies/policy.hpp" +#include "azure/core/internal/json/json.hpp" #include #include #include +#include #include using namespace Azure::Core::Http::_internal; @@ -28,4 +30,48 @@ namespace Azure { namespace Core { RawResponse = std::move(rawResponse); } + RequestFailedException::RequestFailedException( + std::unique_ptr& rawResponse) + : std::runtime_error(getRawResponseField(rawResponse, "message")) + { + auto& headers = rawResponse->GetHeaders(); + + StatusCode = rawResponse->GetStatusCode(); + ErrorCode = getRawResponseField(rawResponse, "code"); + StatusCode = rawResponse->GetStatusCode(); + ReasonPhrase = rawResponse->GetReasonPhrase(); + RequestId = HttpShared::GetHeaderOrEmptyString(headers, HttpShared::MsRequestId); + ClientRequestId = HttpShared::GetHeaderOrEmptyString(headers, HttpShared::MsClientRequestId); + Message = this->what(); + } + + std::string RequestFailedException::getRawResponseField( + std::unique_ptr& rawResponse, + std::string fieldName) + { + auto& headers = rawResponse->GetHeaders(); + std::string contentType = HttpShared::GetHeaderOrEmptyString(headers, HttpShared::ContentType); + std::vector bodyBuffer = rawResponse->GetBody(); + std::string result; + + if (contentType.find("json") != std::string::npos) + { + auto jsonParser = Azure::Core::Json::_internal::json::parse(bodyBuffer); + auto error = jsonParser.find("error"); + if (error != jsonParser.end() && error.value().contains(fieldName)) + { + result = error.value()[fieldName].get(); + } + else if (fieldName == "message") + { + result = std::string(bodyBuffer.begin(), bodyBuffer.end()); + } + } + else if (fieldName == "message") + { + result = std::string(bodyBuffer.begin(), bodyBuffer.end()); + } + + return result; + } }} // namespace Azure::Core diff --git a/sdk/core/azure-core/test/ut/CMakeLists.txt b/sdk/core/azure-core/test/ut/CMakeLists.txt index 93658d86d..683a315fc 100644 --- a/sdk/core/azure-core/test/ut/CMakeLists.txt +++ b/sdk/core/azure-core/test/ut/CMakeLists.txt @@ -73,6 +73,7 @@ add_executable ( transport_adapter_implementation_test.cpp url_test.cpp uuid_test.cpp + exception_test.cpp ) if (MSVC) @@ -100,7 +101,7 @@ target_link_libraries(azure-core-test PRIVATE azure-core gtest gmock) add_executable ( azure-core-global-context-test global_context_test.cpp -) + ) if (MSVC) # Disable gtest warnings for MSVC target_compile_options(azure-core-global-context-test PUBLIC /wd26495 /wd26812 /wd6326 /wd28204 /wd28020 /wd6330 /wd4389) @@ -122,7 +123,7 @@ if(BUILD_TRANSPORT_CURL) add_executable ( azure-core-libcurl-test azure_libcurl_core_main_test.cpp - ) + ) if (MSVC) # warning C4389: '==': signed/unsigned mismatch diff --git a/sdk/core/azure-core/test/ut/exception_test.cpp b/sdk/core/azure-core/test/ut/exception_test.cpp new file mode 100644 index 000000000..1617d85ff --- /dev/null +++ b/sdk/core/azure-core/test/ut/exception_test.cpp @@ -0,0 +1,79 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// SPDX-License-Identifier: MIT +#include +#include +#include +#include + +using namespace Azure::Core; +using namespace Azure::Core::Http::_internal; + +TEST(RequestFailedException, JSONError) +{ + auto response = std::make_unique( + 1, 1, Azure::Core::Http::HttpStatusCode::ServiceUnavailable, "retry please :"); + static constexpr uint8_t const responseBody[] + = "{\"error\":{ \"code\":\"503\", \"message\":\"JT\"}}"; + static constexpr uint8_t const responseBodyStream[] + = "{\"error\":{ \"code\":\"503\", \"message\":\"JT\"}}"; + + response->SetHeader(HttpShared::ContentType, "application/json"); + response->SetHeader(HttpShared::MsRequestId, "1"); + response->SetHeader(HttpShared::MsClientRequestId, "2"); + response->SetBody(std::vector(responseBody, responseBody + sizeof(responseBody))); + response->SetBodyStream(std::make_unique( + responseBodyStream, sizeof(responseBodyStream) - 1)); + + auto exception = Azure::Core::RequestFailedException(response); + + EXPECT_EQ(exception.StatusCode, Azure::Core::Http::HttpStatusCode::ServiceUnavailable); + EXPECT_EQ(exception.Message, "JT"); + EXPECT_EQ(exception.ErrorCode, "503"); + EXPECT_EQ(exception.RequestId, "1"); + EXPECT_EQ(exception.ClientRequestId, "2"); +} + +TEST(RequestFailedException, JSONErrorNoError) +{ + auto response = std::make_unique( + 1, 1, Azure::Core::Http::HttpStatusCode::ServiceUnavailable, "retry please :"); + static constexpr uint8_t const responseBody[] = "{\"text\" :\"some text\"}"; + static constexpr uint8_t const responseBodyStream[] = "{\"text\" :\"some text\"}"; + + response->SetHeader(HttpShared::ContentType, "application/json"); + response->SetHeader(HttpShared::MsRequestId, "1"); + response->SetHeader(HttpShared::MsClientRequestId, "2"); + response->SetBody(std::vector(responseBody, responseBody + sizeof(responseBody))); + response->SetBodyStream(std::make_unique( + responseBodyStream, sizeof(responseBodyStream) - 1)); + + auto exception = Azure::Core::RequestFailedException(response); + + EXPECT_EQ(exception.StatusCode, Azure::Core::Http::HttpStatusCode::ServiceUnavailable); + EXPECT_EQ(exception.Message, "{\"text\" :\"some text\"}"); + EXPECT_EQ(exception.ErrorCode, ""); + EXPECT_EQ(exception.RequestId, "1"); + EXPECT_EQ(exception.ClientRequestId, "2"); +} + +TEST(RequestFailedException, NonJSONError) +{ + auto response = std::make_unique( + 1, 1, Azure::Core::Http::HttpStatusCode::ServiceUnavailable, "reason"); + static constexpr uint8_t const responseBody[] = "NJT"; + static constexpr uint8_t const responseBodyStream[] = "NJT"; + + response->SetHeader(HttpShared::ContentType, "application/text"); + response->SetHeader(HttpShared::MsRequestId, "1"); + response->SetHeader(HttpShared::MsClientRequestId, "2"); + response->SetBody(std::vector(responseBody, responseBody + sizeof(responseBody))); + response->SetBodyStream(std::make_unique( + responseBodyStream, sizeof(responseBodyStream) - 1)); + + auto exception = Azure::Core::RequestFailedException(response); + + EXPECT_EQ(exception.StatusCode, Azure::Core::Http::HttpStatusCode::ServiceUnavailable); + EXPECT_EQ(exception.Message, "NJT"); + EXPECT_EQ(exception.RequestId, "1"); + EXPECT_EQ(exception.ClientRequestId, "2"); +} diff --git a/sdk/keyvault/azure-security-keyvault-common/CMakeLists.txt b/sdk/keyvault/azure-security-keyvault-common/CMakeLists.txt index 5567b7a46..b444b9acd 100644 --- a/sdk/keyvault/azure-security-keyvault-common/CMakeLists.txt +++ b/sdk/keyvault/azure-security-keyvault-common/CMakeLists.txt @@ -31,7 +31,6 @@ set( inc/azure/keyvault/common/internal/base64url.hpp inc/azure/keyvault/common/internal/keyvault_pipeline.hpp inc/azure/keyvault/common/internal/single_page.hpp - inc/azure/keyvault/common/internal/keyvault_exception.hpp inc/azure/keyvault/common/internal/sha.hpp ) @@ -39,7 +38,7 @@ set( AZURE_KEYVAULT_COMMON_SOURCE src/private/keyvault_constants.hpp src/private/package_version.hpp - src/keyvault_exception.cpp + src/keyvault_pipeline.cpp src/sha.cpp ) diff --git a/sdk/keyvault/azure-security-keyvault-common/inc/azure/keyvault/common/internal/keyvault_exception.hpp b/sdk/keyvault/azure-security-keyvault-common/inc/azure/keyvault/common/internal/keyvault_exception.hpp deleted file mode 100644 index 2b3988611..000000000 --- a/sdk/keyvault/azure-security-keyvault-common/inc/azure/keyvault/common/internal/keyvault_exception.hpp +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// SPDX-License-Identifier: MIT - -/** - * @brief Defines a general exception for Key Vault service clients. - * - */ - -#pragma once - -#include -#include - -#include -#include -#include - -namespace Azure { namespace Security { namespace KeyVault { namespace _internal { - /** - * @brief Container for static methods to parse Key Vault payloads to Azure Core Exception. - * - */ - struct KeyVaultException final - { - /** - * @brief Parsed the HTTP payload into an #Azure::Core::RequestFailedException - * - * @param rawResponse The HTTP raw response. - * @return Azure::Core::RequestFailedException - */ - static Azure::Core::RequestFailedException CreateException( - std::unique_ptr rawResponse); - }; -}}}} // namespace Azure::Security::KeyVault::_internal diff --git a/sdk/keyvault/azure-security-keyvault-common/src/keyvault_exception.cpp b/sdk/keyvault/azure-security-keyvault-common/src/keyvault_exception.cpp deleted file mode 100644 index a668bb10b..000000000 --- a/sdk/keyvault/azure-security-keyvault-common/src/keyvault_exception.cpp +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// SPDX-License-Identifier: MIT - -#include "azure/keyvault/common/internal/keyvault_exception.hpp" -#include "private/keyvault_constants.hpp" - -#include - -#include -#include - -using namespace Azure::Security::KeyVault; -using namespace Azure::Core::Http::_internal; - -Azure::Core::RequestFailedException _internal::KeyVaultException::CreateException( - std::unique_ptr rawResponse) -{ - std::vector bodyBuffer = std::move(rawResponse->GetBody()); - auto& headers = rawResponse->GetHeaders(); - std::string contentType = HttpShared::GetHeaderOrEmptyString(headers, HttpShared::ContentType); - std::string message; - std::string errorCode; - - if (contentType.find("json") != std::string::npos) - { - auto jsonParser = Azure::Core::Json::_internal::json::parse(bodyBuffer); - auto& error = jsonParser["error"]; - errorCode = error["code"].get(); - message = error["message"].get(); - } - else - { - message = std::string(bodyBuffer.begin(), bodyBuffer.end()); - } - Azure::Core::RequestFailedException exception(message, std::move(rawResponse)); - exception.ErrorCode = std::move(errorCode); - return exception; -} diff --git a/sdk/keyvault/azure-security-keyvault-common/src/keyvault_pipeline.cpp b/sdk/keyvault/azure-security-keyvault-common/src/keyvault_pipeline.cpp index c28f91aa5..836e36e48 100644 --- a/sdk/keyvault/azure-security-keyvault-common/src/keyvault_pipeline.cpp +++ b/sdk/keyvault/azure-security-keyvault-common/src/keyvault_pipeline.cpp @@ -1,12 +1,12 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // SPDX-License-Identifier: MIT -#include - -#include "azure/keyvault/common/internal/keyvault_exception.hpp" #include "azure/keyvault/common/internal/keyvault_pipeline.hpp" #include "private/keyvault_constants.hpp" +#include +#include + using namespace Azure::Security::KeyVault; using namespace Azure::Core::Http::_internal; @@ -70,8 +70,7 @@ std::unique_ptr _internal::KeyVaultPipeline::Sen case Azure::Core::Http::HttpStatusCode::NoContent: break; default: - throw Azure::Security::KeyVault::_internal::KeyVaultException::CreateException( - std::move(response)); + throw Azure::Core::RequestFailedException(response); } return response; } diff --git a/sdk/keyvault/azure-security-keyvault-keys/src/delete_key_operation.cpp b/sdk/keyvault/azure-security-keyvault-keys/src/delete_key_operation.cpp index 6e56719a1..c054cca22 100644 --- a/sdk/keyvault/azure-security-keyvault-keys/src/delete_key_operation.cpp +++ b/sdk/keyvault/azure-security-keyvault-keys/src/delete_key_operation.cpp @@ -3,7 +3,7 @@ #include -#include +#include #include "azure/keyvault/keys/delete_key_operation.hpp" #include "azure/keyvault/keys/key_client.hpp" @@ -43,8 +43,7 @@ Azure::Security::KeyVault::Keys::DeleteKeyOperation::PollInternal( break; } default: - throw Azure::Security::KeyVault::_internal::KeyVaultException::CreateException( - std::move(rawResponse)); + throw Azure::Core::RequestFailedException(rawResponse); } if (m_status == Azure::Core::OperationStatus::Succeeded) diff --git a/sdk/keyvault/azure-security-keyvault-keys/src/recover_deleted_key_operation.cpp b/sdk/keyvault/azure-security-keyvault-keys/src/recover_deleted_key_operation.cpp index 24071d969..1abd1ee8e 100644 --- a/sdk/keyvault/azure-security-keyvault-keys/src/recover_deleted_key_operation.cpp +++ b/sdk/keyvault/azure-security-keyvault-keys/src/recover_deleted_key_operation.cpp @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // SPDX-License-Identifier: MIT -#include +#include #include "azure/keyvault/keys/key_client.hpp" #include "azure/keyvault/keys/recover_deleted_key_operation.hpp" @@ -40,8 +40,7 @@ Azure::Security::KeyVault::Keys::RecoverDeletedKeyOperation::PollInternal( break; } default: - throw Azure::Security::KeyVault::_internal::KeyVaultException::CreateException( - std::move(rawResponse)); + throw Azure::Core::RequestFailedException(rawResponse); } if (m_status == Azure::Core::OperationStatus::Succeeded) {