Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create interfaces for smithy interceptors #3134

Merged
merged 1 commit into from
Oct 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/aws-cpp-sdk-core/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down
24 changes: 24 additions & 0 deletions src/aws-cpp-sdk-core/include/smithy/interceptor/Interceptor.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/**
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0.
*/
#pragma once
#include <smithy/interceptor/InterceptorContext.h>

namespace smithy
{
namespace interceptor
{
class Interceptor
{
public:
virtual ~Interceptor() = default;

using ModifyRequestOutcome = Aws::Utils::Outcome<std::shared_ptr<Aws::Http::HttpRequest>, Aws::Client::AWSError<Aws::Client::CoreErrors>>;
virtual ModifyRequestOutcome ModifyRequest(InterceptorContext& context) = 0;

using ModifyResponseOutcome = Aws::Utils::Outcome<std::shared_ptr<Aws::Http::HttpResponse>, Aws::Client::AWSError<Aws::Client::CoreErrors>>;
virtual ModifyResponseOutcome ModifyResponse(InterceptorContext& context) = 0;
};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
/**
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0.
*/
#pragma once
#include <aws/core/utils/memory/stl/AWSString.h>
#include <aws/core/utils/Outcome.h>
#include <aws/core/http/HttpRequest.h>
#include <aws/core/http/HttpResponse.h>
#include <aws/core/client/AWSError.h>
#include <aws/core/client/CoreErrors.h>

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<std::shared_ptr<Aws::Http::HttpRequest>, Aws::Client::AWSError<Aws::Client::CoreErrors>>;
GetRequestOutcome GetRequest() const
{
if (!m_request)
{
return Aws::Client::AWSError<Aws::Client::CoreErrors>{
Aws::Client::CoreErrors::RESOURCE_NOT_FOUND,
"ResourceNotFoundException",
"Request is NULL",
false
};
}
return m_request;
}

void SetRequest(const std::shared_ptr<Aws::Http::HttpRequest>& request)
{
this->m_request = request;
}

using GetResponseOutcome = Aws::Utils::Outcome<std::shared_ptr<Aws::Http::HttpResponse>, Aws::Client::AWSError<Aws::Client::CoreErrors>>;
GetResponseOutcome GetResponse() const
{
if (!m_response)
{
return Aws::Client::AWSError<Aws::Client::CoreErrors>{
Aws::Client::CoreErrors::RESOURCE_NOT_FOUND,
"ResourceNotFoundException",
"Response is NULL",
false
};
}
return m_response;
}

void SetResponse(const std::shared_ptr<Aws::Http::HttpResponse>& response)
{
this->m_response = response;
}

using GetAttributeOutcome = Aws::Utils::Outcome<Aws::String, Aws::Client::AWSError<Aws::Client::CoreErrors>>;
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>{
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<Aws::String, Aws::String> m_attributes{};
std::shared_ptr<Aws::Http::HttpRequest> m_request{nullptr};
std::shared_ptr<Aws::Http::HttpResponse> m_response{nullptr};
};
}
}
3 changes: 3 additions & 0 deletions tests/aws-cpp-sdk-core-tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand All @@ -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()

Expand Down
177 changes: 177 additions & 0 deletions tests/aws-cpp-sdk-core-tests/smithy/interceptor/InterceptorTest.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
/**
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0.
*/
#include <aws/testing/AwsCppSdkGTestSuite.h>
#include <aws/core/http/standard/StandardHttpResponse.h>
#include <smithy/interceptor/InterceptorContext.h>
#include <smithy/interceptor/Interceptor.h>

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");
sbiscigl marked this conversation as resolved.
Show resolved Hide resolved
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>{
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>{
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> interceptor)
{
Vector<UniquePtr<Interceptor>> interceptors;
interceptors.emplace_back(std::move(interceptor));
return MockClient{std::move(interceptors)};
}

using RequestOutcome = Outcome<std::shared_ptr<HttpResponse>, AWSError<CoreErrors>>;
RequestOutcome MakeRequest(const std::shared_ptr<HttpRequest>& 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<StandardHttpResponse>("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<UniquePtr<Interceptor>> interceptors)
: m_interceptors(std::move(interceptors))
{
}

Vector<UniquePtr<Interceptor>> 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<MockSuccessInterceptor>("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<MockRequestFailureInterceptor>("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<MockResponseFailureInterceptor>("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());
}
Loading