Skip to content

Commit

Permalink
Create interfaces for smithy interceptors
Browse files Browse the repository at this point in the history
  • Loading branch information
sbiscigl committed Oct 3, 2024
1 parent 8d6a18a commit e2f6698
Show file tree
Hide file tree
Showing 5 changed files with 302 additions and 0 deletions.
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");
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());
}

0 comments on commit e2f6698

Please sign in to comment.