2502 exceptions (#2529)

* first pass

* move exception creation from keyvault to core

* merge fix

* format

* fix includes location and containers

* remove default constructor

* to trigger build

* undo change

* uncomment

* Update sdk/core/azure-core/test/ut/exception_test.cpp

Co-authored-by: Rick Winter <rick.winter@microsoft.com>

Co-authored-by: Rick Winter <rick.winter@microsoft.com>
This commit is contained in:
George Arama 2021-06-30 21:17:15 -07:00 committed by GitHub
parent e51e693640
commit c449e3f9bc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 155 additions and 88 deletions

View File

@ -92,6 +92,18 @@ namespace Azure { namespace Core {
const std::string& message,
std::unique_ptr<Azure::Core::Http::RawResponse> 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<Azure::Core::Http::RawResponse>& 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<Azure::Core::Http::RawResponse>& rawResponse,
std::string fieldName);
};
}} // namespace Azure::Core

View File

@ -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 <memory>
#include <stdexcept>
#include <string>
#include <type_traits>
#include <vector>
using namespace Azure::Core::Http::_internal;
@ -28,4 +30,48 @@ namespace Azure { namespace Core {
RawResponse = std::move(rawResponse);
}
RequestFailedException::RequestFailedException(
std::unique_ptr<Azure::Core::Http::RawResponse>& 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<Azure::Core::Http::RawResponse>& rawResponse,
std::string fieldName)
{
auto& headers = rawResponse->GetHeaders();
std::string contentType = HttpShared::GetHeaderOrEmptyString(headers, HttpShared::ContentType);
std::vector<uint8_t> 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<std::string>();
}
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

View File

@ -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

View File

@ -0,0 +1,79 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// SPDX-License-Identifier: MIT
#include <azure/core/exception.hpp>
#include <azure/core/http/http.hpp>
#include <gtest/gtest.h>
#include <string>
using namespace Azure::Core;
using namespace Azure::Core::Http::_internal;
TEST(RequestFailedException, JSONError)
{
auto response = std::make_unique<Azure::Core::Http::RawResponse>(
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<uint8_t>(responseBody, responseBody + sizeof(responseBody)));
response->SetBodyStream(std::make_unique<Azure::Core::IO::MemoryBodyStream>(
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<Azure::Core::Http::RawResponse>(
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<uint8_t>(responseBody, responseBody + sizeof(responseBody)));
response->SetBodyStream(std::make_unique<Azure::Core::IO::MemoryBodyStream>(
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<Azure::Core::Http::RawResponse>(
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<uint8_t>(responseBody, responseBody + sizeof(responseBody)));
response->SetBodyStream(std::make_unique<Azure::Core::IO::MemoryBodyStream>(
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");
}

View File

@ -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
)

View File

@ -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 <azure/core/exception.hpp>
#include <azure/core/http/http.hpp>
#include <memory>
#include <stdexcept>
#include <string>
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<Azure::Core::Http::RawResponse> rawResponse);
};
}}}} // namespace Azure::Security::KeyVault::_internal

View File

@ -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 <azure/core/http/policies/policy.hpp>
#include <azure/core/internal/json/json.hpp>
#include <type_traits>
using namespace Azure::Security::KeyVault;
using namespace Azure::Core::Http::_internal;
Azure::Core::RequestFailedException _internal::KeyVaultException::CreateException(
std::unique_ptr<Azure::Core::Http::RawResponse> rawResponse)
{
std::vector<uint8_t> 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<std::string>();
message = error["message"].get<std::string>();
}
else
{
message = std::string(bodyBuffer.begin(), bodyBuffer.end());
}
Azure::Core::RequestFailedException exception(message, std::move(rawResponse));
exception.ErrorCode = std::move(errorCode);
return exception;
}

View File

@ -1,12 +1,12 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// SPDX-License-Identifier: MIT
#include <azure/core/http/http.hpp>
#include "azure/keyvault/common/internal/keyvault_exception.hpp"
#include "azure/keyvault/common/internal/keyvault_pipeline.hpp"
#include "private/keyvault_constants.hpp"
#include <azure/core/exception.hpp>
#include <azure/core/http/http.hpp>
using namespace Azure::Security::KeyVault;
using namespace Azure::Core::Http::_internal;
@ -70,8 +70,7 @@ std::unique_ptr<Azure::Core::Http::RawResponse> _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;
}

View File

@ -3,7 +3,7 @@
#include <azure/core/exception.hpp>
#include <azure/keyvault/common/internal/keyvault_exception.hpp>
#include <azure/core/exception.hpp>
#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)

View File

@ -1,7 +1,7 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// SPDX-License-Identifier: MIT
#include <azure/keyvault/common/internal/keyvault_exception.hpp>
#include <azure/core/exception.hpp>
#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)
{