From 85ab868ac00762ef1ed9c81231bc0d596f2c6d18 Mon Sep 17 00:00:00 2001 From: sbiscigl Date: Wed, 2 Oct 2024 15:01:19 -0400 Subject: [PATCH] Create interfaces for smithy interceptors --- src/aws-cpp-sdk-core/CMakeLists.txt | 4 + .../include/smithy/interceptor/Interceptor.h | 24 +++ .../smithy/interceptor/InterceptorContext.h | 94 ++++++++++ tests/aws-cpp-sdk-core-tests/CMakeLists.txt | 3 + .../smithy/interceptor/InterceptorTest.cpp | 177 ++++++++++++++++++ 5 files changed, 302 insertions(+) create mode 100644 src/aws-cpp-sdk-core/include/smithy/interceptor/Interceptor.h create mode 100644 src/aws-cpp-sdk-core/include/smithy/interceptor/InterceptorContext.h create mode 100644 tests/aws-cpp-sdk-core-tests/smithy/interceptor/InterceptorTest.cpp diff --git a/src/aws-cpp-sdk-core/CMakeLists.txt b/src/aws-cpp-sdk-core/CMakeLists.txt index 29a52690278..ec10c63ead3 100644 --- a/src/aws-cpp-sdk-core/CMakeLists.txt +++ b/src/aws-cpp-sdk-core/CMakeLists.txt @@ -90,6 +90,7 @@ file(GLOB SMITHY_IDENTITY_IDENTITY_IMPL_HEADERS "include/smithy/identity/identit file(GLOB SMITHY_IDENTITY_RESOLVER_IMPL_HEADERS "include/smithy/identity/resolver/impl/*.h") file(GLOB SMITHY_IDENTITY_SIGNER_HEADERS "include/smithy/identity/signer/*.h") file(GLOB SMITHY_IDENTITY_SIGNER_BUILTIN_HEADERS "include/smithy/identity/signer/built-in/*.h") +file(GLOB SMITHY_INTERCEPTOR_HEADERS "include/smithy/interceptor/*.h") file(GLOB AWS_SOURCE "${CMAKE_CURRENT_SOURCE_DIR}/source/*.cpp") file(GLOB AWS_TINYXML2_SOURCE "${CMAKE_CURRENT_SOURCE_DIR}/source/external/tinyxml2/*.cpp") @@ -361,6 +362,7 @@ file(GLOB AWS_NATIVE_SDK_COMMON_HEADERS ${SMITHY_IDENTITY_RESOLVER_HEADERS} ${SMITHY_IDENTITY_RESOLVER_BUILTIN_HEADERS} ${OPTEL_HEADERS} + ${SMITHY_INTERCEPTOR_HEADERS} ) # misc platform-specific, not related to features (encryption/http clients) @@ -492,6 +494,7 @@ if(MSVC) source_group("Header Files\\smithy\\identity\\resolver\\impl" FILES ${SMITHY_IDENTITY_RESOLVER_IMPL_HEADERS}) source_group("Header Files\\smithy\\identity\\signer" FILES ${SMITHY_IDENTITY_SIGNER_HEADERS}) source_group("Header Files\\smithy\\identity\\signer\\built-in" FILES ${SMITHY_IDENTITY_SIGNER_BUILTIN_HEADERS}) + source_group("Header Files\\smithy\\interceptor" FILES ${SMITHY_INTERCEPTOR_HEADERS}) # http client conditional headers if(ENABLE_CURL_CLIENT) @@ -770,6 +773,7 @@ install (FILES ${SMITHY_IDENTITY_IDENTITY_IMPL_HEADERS} DESTINATION ${INCLUDE_DI install (FILES ${SMITHY_IDENTITY_RESOLVER_IMPL_HEADERS} DESTINATION ${INCLUDE_DIRECTORY}/smithy/identity/resolver/impl) install (FILES ${SMITHY_IDENTITY_SIGNER_HEADERS} DESTINATION ${INCLUDE_DIRECTORY}/smithy/identity/signer) install (FILES ${SMITHY_IDENTITY_SIGNER_BUILTIN_HEADERS} DESTINATION ${INCLUDE_DIRECTORY}/smithy/identity/signer/built-in) +install (FILES ${SMITHY_INTERCEPTOR_HEADERS} DESTINATION ${INCLUDE_DIRECTORY}/smithy/interceptor) # android logcat headers if(PLATFORM_ANDROID) diff --git a/src/aws-cpp-sdk-core/include/smithy/interceptor/Interceptor.h b/src/aws-cpp-sdk-core/include/smithy/interceptor/Interceptor.h new file mode 100644 index 00000000000..ce5c4a4244e --- /dev/null +++ b/src/aws-cpp-sdk-core/include/smithy/interceptor/Interceptor.h @@ -0,0 +1,24 @@ +/** + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0. + */ +#pragma once +#include + +namespace smithy +{ + namespace interceptor + { + class Interceptor + { + public: + virtual ~Interceptor() = default; + + using ModifyRequestOutcome = Aws::Utils::Outcome, Aws::Client::AWSError>; + virtual ModifyRequestOutcome ModifyRequest(InterceptorContext& context) = 0; + + using ModifyResponseOutcome = Aws::Utils::Outcome, Aws::Client::AWSError>; + virtual ModifyResponseOutcome ModifyResponse(InterceptorContext& context) = 0; + }; + } +} diff --git a/src/aws-cpp-sdk-core/include/smithy/interceptor/InterceptorContext.h b/src/aws-cpp-sdk-core/include/smithy/interceptor/InterceptorContext.h new file mode 100644 index 00000000000..a5abb40061b --- /dev/null +++ b/src/aws-cpp-sdk-core/include/smithy/interceptor/InterceptorContext.h @@ -0,0 +1,94 @@ +/** + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0. + */ +#pragma once +#include +#include +#include +#include +#include +#include + +namespace smithy +{ + namespace interceptor + { + class InterceptorContext + { + public: + InterceptorContext() = default; + virtual ~InterceptorContext() = default; + InterceptorContext(const InterceptorContext& other) = delete; + InterceptorContext(InterceptorContext&& other) noexcept = default; + InterceptorContext& operator=(const InterceptorContext& other) = delete; + InterceptorContext& operator=(InterceptorContext&& other) noexcept = default; + + using GetRequestOutcome = Aws::Utils::Outcome, Aws::Client::AWSError>; + GetRequestOutcome GetRequest() const + { + if (!m_request) + { + return Aws::Client::AWSError{ + Aws::Client::CoreErrors::RESOURCE_NOT_FOUND, + "ResourceNotFoundException", + "Request is NULL", + false + }; + } + return m_request; + } + + void SetRequest(const std::shared_ptr& request) + { + this->m_request = request; + } + + using GetResponseOutcome = Aws::Utils::Outcome, Aws::Client::AWSError>; + GetResponseOutcome GetResponse() const + { + if (!m_response) + { + return Aws::Client::AWSError{ + Aws::Client::CoreErrors::RESOURCE_NOT_FOUND, + "ResourceNotFoundException", + "Response is NULL", + false + }; + } + return m_response; + } + + void SetResponse(const std::shared_ptr& response) + { + this->m_response = response; + } + + using GetAttributeOutcome = Aws::Utils::Outcome>; + GetAttributeOutcome GetAttribute(const Aws::String& attribute) + { + const auto attribute_iter = m_attributes.find(attribute); + if (attribute_iter == m_attributes.end()) + { + return Aws::Client::AWSError{ + Aws::Client::CoreErrors::RESOURCE_NOT_FOUND, + "ResourceNotFoundException", + "Attribute not found", + false + }; + } + return attribute_iter->second; + } + + void SetAttribute(const Aws::String& attribute, const Aws::String& value) + { + m_attributes.emplace(attribute, value); + } + + private: + Aws::Map m_attributes{}; + std::shared_ptr m_request{nullptr}; + std::shared_ptr m_response{nullptr}; + }; + } +} diff --git a/tests/aws-cpp-sdk-core-tests/CMakeLists.txt b/tests/aws-cpp-sdk-core-tests/CMakeLists.txt index 39a7c87ac08..3928ebdd977 100644 --- a/tests/aws-cpp-sdk-core-tests/CMakeLists.txt +++ b/tests/aws-cpp-sdk-core-tests/CMakeLists.txt @@ -27,6 +27,7 @@ file(GLOB SMITHY_TRACING_SRC "${CMAKE_CURRENT_SOURCE_DIR}/smithy/tracing/*.cpp") file(GLOB SMITHY_CLIENT_SRC "${CMAKE_CURRENT_SOURCE_DIR}/smithy/client/*.cpp") file(GLOB SMITHY_CLIENT_SERIALIZER_SRC "${CMAKE_CURRENT_SOURCE_DIR}/smithy/client/serializer/*.cpp") file(GLOB ENDPOINT_SRC "${CMAKE_CURRENT_SOURCE_DIR}/endpoint/*.cpp") +file(GLOB SMITHY_INTERCEPTOR_SRC "${CMAKE_CURRENT_SOURCE_DIR}/smithy/interceptor/*.cpp") file(GLOB AWS_CPP_SDK_CORE_TESTS_SRC @@ -54,6 +55,7 @@ file(GLOB AWS_CPP_SDK_CORE_TESTS_SRC ${SMITHY_CLIENT_SRC} ${SMITHY_CLIENT_SERIALIZER_SRC} ${ENDPOINT_SRC} + ${SMITHY_INTERCEPTOR_SRC} ) if(PLATFORM_WINDOWS) @@ -79,6 +81,7 @@ if(PLATFORM_WINDOWS) source_group("Source Files\\smithy\\tracing" FILES ${SMITHY_TRACING_SRC}) source_group("Source Files\\smithy\\client" FILES ${SMITHY_CLIENT_SRC}) source_group("Source Files\\smithy\\client" FILES ${SMITHY_CLIENT_SERIALIZER_SRC}) + source_group("Source Files\\smithy\\client" FILES ${SMITHY_INTERCEPTOR_SRC}) endif() endif() diff --git a/tests/aws-cpp-sdk-core-tests/smithy/interceptor/InterceptorTest.cpp b/tests/aws-cpp-sdk-core-tests/smithy/interceptor/InterceptorTest.cpp new file mode 100644 index 00000000000..ef47ff5d909 --- /dev/null +++ b/tests/aws-cpp-sdk-core-tests/smithy/interceptor/InterceptorTest.cpp @@ -0,0 +1,177 @@ +/** + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0. + */ +#include +#include +#include +#include + +using namespace smithy::interceptor; +using namespace Aws; +using namespace Aws::Client; +using namespace Aws::Http; +using namespace Aws::Http::Standard; +using namespace Aws::Utils; +using namespace Aws::Utils::Stream; +using namespace Aws::Testing; + +class SmithyInterceptorTest : public AwsCppSdkGTestSuite +{ +}; + +class MockSuccessInterceptor : public Interceptor +{ +public: + MockSuccessInterceptor() = default; + ~MockSuccessInterceptor() override = default; + + ModifyRequestOutcome ModifyRequest(InterceptorContext& context) override + { + context.SetAttribute("MockInterceptorRequest", "Called"); + return context.GetRequest(); + } + + ModifyResponseOutcome ModifyResponse(InterceptorContext& context) override + { + context.SetAttribute("MockInterceptorResponse", "Called"); + return context.GetResponse(); + } +}; + +class MockRequestFailureInterceptor : public Interceptor +{ +public: + MockRequestFailureInterceptor() = default; + ~MockRequestFailureInterceptor() override = default; + + ModifyRequestOutcome ModifyRequest(InterceptorContext& context) override + { + context.SetAttribute("MockInterceptorRequest", "Called"); + return Aws::Client::AWSError{ + CoreErrors::VALIDATION, + "RequestInterceptorException", + "The request interceptor failed", + false + };; + } + + ModifyResponseOutcome ModifyResponse(InterceptorContext& context) override + { + context.SetAttribute("MockInterceptorResponse", "Called"); + return context.GetResponse(); + } +}; + +class MockResponseFailureInterceptor : public Interceptor +{ +public: + MockResponseFailureInterceptor() = default; + ~MockResponseFailureInterceptor() override = default; + + ModifyRequestOutcome ModifyRequest(InterceptorContext& context) override + { + context.SetAttribute("MockInterceptorRequest", "Called"); + return context.GetRequest(); + } + + ModifyResponseOutcome ModifyResponse(InterceptorContext& context) override + { + context.SetAttribute("MockInterceptorResponse", "Called"); + return Aws::Client::AWSError{ + CoreErrors::VALIDATION, + "ResponseInterceptorException", + "The response interceptor failed", + false + };; + } +}; + +class MockClient +{ +public: + MockClient() = delete; + MockClient(const MockClient& other) = delete; + MockClient(MockClient&& other) noexcept = default; + MockClient& operator=(const MockClient& other) = delete; + MockClient& operator=(MockClient&& other) noexcept = default; + + static MockClient MakeClient(Aws::UniquePtr interceptor) + { + Vector> interceptors; + interceptors.emplace_back(std::move(interceptor)); + return MockClient{std::move(interceptors)}; + } + + using RequestOutcome = Outcome, AWSError>; + RequestOutcome MakeRequest(const std::shared_ptr& request, InterceptorContext& context) const + { + context.SetRequest(request); + for (const auto& interceptor: m_interceptors) + { + const auto modifiedRequest = interceptor->ModifyRequest(context); + if (!modifiedRequest.IsSuccess()) + { + return modifiedRequest.GetError(); + } + } + auto response = Aws::MakeShared("SmithyInterceptorTest", request); + context.SetResponse(response); + for (const auto& interceptor: m_interceptors) + { + const auto modifiedResponse = interceptor->ModifyResponse(context); + if (!modifiedResponse.IsSuccess()) + { + return modifiedResponse.GetError(); + } + } + return context.GetResponse(); + } + +private: + explicit MockClient(Vector> interceptors) + : m_interceptors(std::move(interceptors)) + { + } + + Vector> m_interceptors{}; +}; + +TEST_F(SmithyInterceptorTest, MockInterceptorShouldReturnSuccess) +{ + const auto uri = "https://www.villagepsychic.net/"; + auto request = CreateHttpRequest(URI{uri}, HttpMethod::HTTP_GET, DefaultResponseStreamFactoryMethod); + auto interceptor = Aws::MakeUnique("SmithyInterceptorTest"); + const auto client = MockClient::MakeClient(std::move(interceptor)); + InterceptorContext context{}; + const auto response = client.MakeRequest(request, context); + EXPECT_TRUE(response.IsSuccess()); + EXPECT_TRUE(context.GetAttribute("MockInterceptorRequest").IsSuccess()); + EXPECT_TRUE(context.GetAttribute("MockInterceptorResponse").IsSuccess()); +} + +TEST_F(SmithyInterceptorTest, MockInterceptorShouldReturnFailureRequset) +{ + const auto uri = "https://www.villagepsychic.net/"; + auto request = CreateHttpRequest(URI{uri}, HttpMethod::HTTP_GET, DefaultResponseStreamFactoryMethod); + auto interceptor = Aws::MakeUnique("SmithyInterceptorTest"); + const auto client = MockClient::MakeClient(std::move(interceptor)); + InterceptorContext context{}; + const auto response = client.MakeRequest(request, context); + EXPECT_FALSE(response.IsSuccess()); + EXPECT_TRUE(context.GetAttribute("MockInterceptorRequest").IsSuccess()); + EXPECT_FALSE(context.GetAttribute("MockInterceptorResponse").IsSuccess()); +} + +TEST_F(SmithyInterceptorTest, MockInterceptorShouldReturnFailureReseponse) +{ + const auto uri = "https://www.villagepsychic.net/"; + auto request = CreateHttpRequest(URI{uri}, HttpMethod::HTTP_GET, DefaultResponseStreamFactoryMethod); + auto interceptor = Aws::MakeUnique("SmithyInterceptorTest"); + const auto client = MockClient::MakeClient(std::move(interceptor)); + InterceptorContext context{}; + const auto response = client.MakeRequest(request, context); + EXPECT_FALSE(response.IsSuccess()); + EXPECT_TRUE(context.GetAttribute("MockInterceptorRequest").IsSuccess()); + EXPECT_TRUE(context.GetAttribute("MockInterceptorResponse").IsSuccess()); +} \ No newline at end of file