diff --git a/src/aws-cpp-sdk-core/CMakeLists.txt b/src/aws-cpp-sdk-core/CMakeLists.txt index 14ae520433e..abc6668d0cb 100644 --- a/src/aws-cpp-sdk-core/CMakeLists.txt +++ b/src/aws-cpp-sdk-core/CMakeLists.txt @@ -97,6 +97,7 @@ 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 SMITHY_INTERCEPTOR_IMPL_HEADERS "include/smithy/interceptor/impl/*.h") +file(GLOB SMITHY_CLIENT_SCHEMA_HEADERS "include/smithy/client/schema/*.h") file(GLOB AWS_SOURCE "${CMAKE_CURRENT_SOURCE_DIR}/source/*.cpp") file(GLOB AWS_TINYXML2_SOURCE "${CMAKE_CURRENT_SOURCE_DIR}/source/external/tinyxml2/*.cpp") @@ -136,6 +137,7 @@ file(GLOB SMITHY_SOURCE "${CMAKE_CURRENT_SOURCE_DIR}/source/smithy/*.cpp") file(GLOB SMITHY_CLIENT_SOURCE "${CMAKE_CURRENT_SOURCE_DIR}/source/smithy/client/*.cpp") file(GLOB SMITHY_IDENTITY_SOURCE "${CMAKE_CURRENT_SOURCE_DIR}/source/smithy/identity/*.cpp") file(GLOB SMITHY_TRACING_SOURCE "${CMAKE_CURRENT_SOURCE_DIR}/source/smithy/tracing/*.cpp") +file(GLOB SMITHY_CLIENT_SCHEMA_SOURCE "${CMAKE_CURRENT_SOURCE_DIR}/source/smithy/client/schema/*.cpp") include(CheckCSourceCompiles) include(CheckCXXSourceCompiles) @@ -316,6 +318,7 @@ file(GLOB AWS_NATIVE_SDK_COMMON_HEADERS ${OPTEL_HEADERS} ${SMITHY_INTERCEPTOR_HEADERS} ${SMITHY_INTERCEPTOR_IMPL_HEADERS} + ${SMITHY_CLIENT_SCHEMA_HEADERS} ) # misc platform-specific, not related to features (encryption/http clients) @@ -378,6 +381,7 @@ file(GLOB AWS_NATIVE_SDK_NON_UNITY_SRC ${UTILS_CHECKSUM_SOURCE} ${UTILS_CRYPTO_SOURCE} ${OPTEL_SOURCE} + ${SMITHY_CLIENT_SCHEMA_SOURCE} ) file(GLOB AWS_NATIVE_SDK_UNITY_SRC @@ -462,6 +466,7 @@ if(MSVC) 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}) source_group("Header Files\\smithy\\interceptor" FILES ${SMITHY_INTERCEPTOR_IMPL_HEADERS}) + source_group("Header Files\\smithy\\client\\schema" FILES ${SMITHY_CLIENT_SCHEMA_HEADERS}) # http client conditional headers if(ENABLE_CURL_CLIENT) diff --git a/src/aws-cpp-sdk-core/include/smithy/client/schema/CborShapeSerializer.h b/src/aws-cpp-sdk-core/include/smithy/client/schema/CborShapeSerializer.h index 8809866c09b..351b29b0aba 100644 --- a/src/aws-cpp-sdk-core/include/smithy/client/schema/CborShapeSerializer.h +++ b/src/aws-cpp-sdk-core/include/smithy/client/schema/CborShapeSerializer.h @@ -1,19 +1,18 @@ #pragma once -#include +#include +#include #include -#include - namespace smithy { namespace schema { -class AWS_CORE_API CborShapeSerialize final : public ShapeSerializer { +class SMITHY_API CborShapeSerialize final : public ShapeSerializer { public: CborShapeSerializer(); ~CborShapeSerializer(); - void BeginStructure(const Schema& schema) override; + bool BeginStructure(const Schema& schema) override; void EndStructure() override; void WriteBoolean(const Schema& schema, bool value) override; @@ -26,21 +25,21 @@ class AWS_CORE_API CborShapeSerialize final : public ShapeSerializer { void WriteEnum(const Schema& schema, int value) override; void WriteNull(const Schema& schema) override; - void BeginList(const Schema& schema, size_t count) override; + bool BeginList(const Schema& schema, size_t count) override; void EndList() override; - void BeginMap(const Schema& schema, size_t count) override; + bool BeginMap(const Schema& schema, size_t count) override; void WriteMapKey(const Aws::String& key) override; void EndMap() override; - void BeginNestedStructure(const Schema& schema) override; + bool BeginNestedStructure(const Schema& schema) override; void EndNestedStructure() override; Aws::String GetPayload() const; private: - struct Impl; - std::unique_ptr m_impl; + class Impl; + Aws::UniquePtr m_impl; }; } // namespace schema diff --git a/src/aws-cpp-sdk-core/include/smithy/client/schema/JsonShapeSerializer.h b/src/aws-cpp-sdk-core/include/smithy/client/schema/JsonShapeSerializer.h index e58c81bc80f..8830bfd236c 100644 --- a/src/aws-cpp-sdk-core/include/smithy/client/schema/JsonShapeSerializer.h +++ b/src/aws-cpp-sdk-core/include/smithy/client/schema/JsonShapeSerializer.h @@ -1,18 +1,20 @@ #pragma once +#include +#include +#include #include -#include - namespace smithy { namespace schema { -class JsonShapeSerializer final : public ShapeSerializer { +class SMITHY_API JsonShapeSerializer final : public ShapeSerializer { public: + using SerializerOutcome = Aws::Utils::Outcome>; JsonShapeSerializer(); ~JsonShapeSerializer(); - void BeginStructure(const Schema& schema) override; + bool BeginStructure(const Schema& schema) override; void EndStructure() override; void WriteBoolean(const Schema& schema, bool value) override; @@ -25,21 +27,21 @@ class JsonShapeSerializer final : public ShapeSerializer { void WriteEnum(const Schema& schema, int value) override; void WriteNull(const Schema& schema) override; - void BeginList(const Schema& schema, size_t count) override; + bool BeginList(const Schema& schema, size_t count) override; void EndList() override; - void BeginMap(const Schema& schema, size_t count) override; + bool BeginMap(const Schema& schema, size_t count) override; void WriteMapKey(const Aws::String& key) override; void EndMap() override; - void BeginNestedStructure(const Schema& schema) override; + bool BeginNestedStructure(const Schema& schema) override; void EndNestedStructure() override; - Aws::String GetPayload() const; + SerializerOutcome GetPayload(); private: - struct Impl; - std::unique_ptr m_impl; + class Impl; + Aws::UniquePtr m_impl; }; } // namespace schema diff --git a/src/aws-cpp-sdk-core/include/smithy/client/schema/JsonWriteUtils.h b/src/aws-cpp-sdk-core/include/smithy/client/schema/JsonWriteUtils.h new file mode 100644 index 00000000000..f1a38546a0d --- /dev/null +++ b/src/aws-cpp-sdk-core/include/smithy/client/schema/JsonWriteUtils.h @@ -0,0 +1,10 @@ +#pragma once +#include + +namespace Aws { +namespace Schema { + +void WriteQuotedJsonString(Aws::String& buf, const Aws::String& value); + +} // namespace Schema +} // namespace Aws diff --git a/src/aws-cpp-sdk-core/include/smithy/client/schema/QueryShapeSerializer.h b/src/aws-cpp-sdk-core/include/smithy/client/schema/QueryShapeSerializer.h index 040968a0b12..38c0b3962b5 100644 --- a/src/aws-cpp-sdk-core/include/smithy/client/schema/QueryShapeSerializer.h +++ b/src/aws-cpp-sdk-core/include/smithy/client/schema/QueryShapeSerializer.h @@ -1,18 +1,18 @@ #pragma once +#include +#include #include -#include - namespace smithy { namespace schema { -class QueryShapeSerializer final : public ShapeSerializer { +class SMITHY_API QueryShapeSerializer final : public ShapeSerializer { public: QueryShapeSerializer(const Aws::String& action, const Aws::String& version); ~QueryShapeSerializer(); - void BeginStructure(const Schema& schema) override; + bool BeginStructure(const Schema& schema) override; void EndStructure() override; void WriteBoolean(const Schema& schema, bool value) override; @@ -25,21 +25,21 @@ class QueryShapeSerializer final : public ShapeSerializer { void WriteEnum(const Schema& schema, int value) override; void WriteNull(const Schema& schema) override; - void BeginList(const Schema& schema, size_t count) override; + bool BeginList(const Schema& schema, size_t count) override; void EndList() override; - void BeginMap(const Schema& schema, size_t count) override; + bool BeginMap(const Schema& schema, size_t count) override; void WriteMapKey(const Aws::String& key) override; void EndMap() override; - void BeginNestedStructure(const Schema& schema) override; + bool BeginNestedStructure(const Schema& schema) override; void EndNestedStructure() override; Aws::String GetPayload() const; private: - struct Impl; - std::unique_ptr m_impl; + class Impl; + Aws::UniquePtr m_impl; }; } // namespace schema diff --git a/src/aws-cpp-sdk-core/include/smithy/client/schema/Schema.h b/src/aws-cpp-sdk-core/include/smithy/client/schema/Schema.h index a8a8cacb3a6..a8faa19266d 100644 --- a/src/aws-cpp-sdk-core/include/smithy/client/schema/Schema.h +++ b/src/aws-cpp-sdk-core/include/smithy/client/schema/Schema.h @@ -35,12 +35,13 @@ enum class ShapeType : uint8_t { class Schema { public: Schema() = default; + Schema(const Aws::String& memberName, ShapeType type) : m_type(type), m_memberName(memberName) {} ShapeType GetType() const { return m_type; } const char* GetId() const { return m_id; } - const char* GetMemberName() const { return m_memberName; } + Aws::String GetMemberName() const { return m_memberName; } int GetMemberIndex() const { return m_memberIndex; } - bool IsMember() const { return m_memberName != nullptr; } + bool IsMember() const { return !m_memberName.empty(); } const Schema* GetMember(const char* name) const; const Schema* GetMember(int index) const; @@ -50,7 +51,7 @@ class Schema { private: const char* m_id = nullptr; ShapeType m_type = ShapeType::Structure; - const char* m_memberName = nullptr; + Aws::String m_memberName; int m_memberIndex = 0; const Schema* m_members = nullptr; uint16_t m_memberCount = 0; diff --git a/src/aws-cpp-sdk-core/include/smithy/client/schema/ShapeSerializer.h b/src/aws-cpp-sdk-core/include/smithy/client/schema/ShapeSerializer.h index 41279e7f5a9..abea197156b 100644 --- a/src/aws-cpp-sdk-core/include/smithy/client/schema/ShapeSerializer.h +++ b/src/aws-cpp-sdk-core/include/smithy/client/schema/ShapeSerializer.h @@ -14,7 +14,7 @@ class ShapeSerializer { public: virtual ~ShapeSerializer() = default; - virtual void BeginStructure(const Schema& schema) = 0; + virtual bool BeginStructure(const Schema& schema) = 0; virtual void EndStructure() = 0; virtual void WriteBoolean(const Schema& schema, bool value) = 0; @@ -27,14 +27,14 @@ class ShapeSerializer { virtual void WriteEnum(const Schema& schema, int value) = 0; virtual void WriteNull(const Schema& schema) = 0; - virtual void BeginList(const Schema& schema, size_t count) = 0; + virtual bool BeginList(const Schema& schema, size_t count) = 0; virtual void EndList() = 0; - virtual void BeginMap(const Schema& schema, size_t count) = 0; + virtual bool BeginMap(const Schema& schema, size_t count) = 0; virtual void WriteMapKey(const Aws::String& key) = 0; virtual void EndMap() = 0; - virtual void BeginNestedStructure(const Schema& schema) = 0; + virtual bool BeginNestedStructure(const Schema& schema) = 0; virtual void EndNestedStructure() = 0; }; diff --git a/src/aws-cpp-sdk-core/include/smithy/client/schema/XmlShapeSerializer.h b/src/aws-cpp-sdk-core/include/smithy/client/schema/XmlShapeSerializer.h index 88ce7a5de39..4c894024cf7 100644 --- a/src/aws-cpp-sdk-core/include/smithy/client/schema/XmlShapeSerializer.h +++ b/src/aws-cpp-sdk-core/include/smithy/client/schema/XmlShapeSerializer.h @@ -1,18 +1,18 @@ #pragma once +#include +#include #include -#include - namespace smithy { namespace schema { -class XmlShapeSerializer final : public ShapeSerializer { +class SMITHY_API XmlShapeSerializer final : public ShapeSerializer { public: XmlShapeSerializer(); ~XmlShapeSerializer(); - void BeginStructure(const Schema& schema) override; + bool BeginStructure(const Schema& schema) override; void EndStructure() override; void WriteBoolean(const Schema& schema, bool value) override; @@ -25,21 +25,21 @@ class XmlShapeSerializer final : public ShapeSerializer { void WriteEnum(const Schema& schema, int value) override; void WriteNull(const Schema& schema) override; - void BeginList(const Schema& schema, size_t count) override; + bool BeginList(const Schema& schema, size_t count) override; void EndList() override; - void BeginMap(const Schema& schema, size_t count) override; + bool BeginMap(const Schema& schema, size_t count) override; void WriteMapKey(const Aws::String& key) override; void EndMap() override; - void BeginNestedStructure(const Schema& schema) override; + bool BeginNestedStructure(const Schema& schema) override; void EndNestedStructure() override; Aws::String GetPayload() const; private: - struct Impl; - std::unique_ptr m_impl; + class Impl; + Aws::UniquePtr m_impl; }; } // namespace schema diff --git a/src/aws-cpp-sdk-core/source/smithy/client/schema/JsonShapeSerializer.cpp b/src/aws-cpp-sdk-core/source/smithy/client/schema/JsonShapeSerializer.cpp new file mode 100644 index 00000000000..115304c125e --- /dev/null +++ b/src/aws-cpp-sdk-core/source/smithy/client/schema/JsonShapeSerializer.cpp @@ -0,0 +1,222 @@ +/** + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0. + */ +#include +#include +#include +#include + +#include + +#include "aws/core/client/AWSClient.h" +#include "aws/core/utils/Outcome.h" +#include "aws/core/utils/memory/stl/AWSArray.h" + +using namespace smithy::schema; +using namespace Aws::Utils; +using SerializerOutcome = Aws::Utils::Outcome>; + +static constexpr int MAX_DEPTH = 1000; + +class JsonShapeSerializer::Impl { + public: + Impl() { m_buf.reserve(8192); } + + bool BeginStructure(const Schema&) { + if (m_depth + 1 >= MAX_DEPTH) { + m_errorMessage = "Maximum nesting depth exceeded"; + return false; + } + + m_buf += '{'; + m_depth++; + m_needsComma[m_depth] = false; + m_isMap[m_depth] = false; + m_isList[m_depth] = false; + + return true; + } + + void EndStructure() { + m_depth--; + m_buf += '}'; + } + + void WriteBoolean(const Schema& schema, bool value) { + WriteFieldName(schema); + m_buf += value ? "true" : "false"; + } + + void WriteInteger(const Schema& schema, int value) { + WriteFieldName(schema); + m_buf += StringUtils::to_string(value); + } + + void WriteLong(const Schema& schema, int64_t value) { + WriteFieldName(schema); + m_buf += StringUtils::to_string(value); + } + + void WriteDouble(const Schema& schema, double value) { + WriteFieldName(schema); + m_buf += StringUtils::to_string(value); + } + + void WriteString(const Schema& schema, const Aws::String& value) { + WriteFieldName(schema); + Aws::Schema::WriteQuotedJsonString(m_buf, value); + } + + void WriteTimestamp(const Schema& schema, const DateTime& value) { + WriteFieldName(schema); + m_buf += StringUtils::to_string(value.SecondsWithMSPrecision()); + } + + void WriteBlob(const Schema& schema, const ByteBuffer& value) { + WriteFieldName(schema); + m_buf += '"'; + m_buf += HashingUtils::Base64Encode(value); + m_buf += '"'; + } + + void WriteEnum(const Schema& schema, int value) { WriteInteger(schema, value); } + + void WriteNull(const Schema& schema) { + WriteFieldName(schema); + m_buf += "null"; + } + + bool BeginList(const Schema& schema, size_t) { + if (m_depth + 1 >= MAX_DEPTH) { + m_errorMessage = "Maximum nesting depth exceeded"; + return false; + } + + WriteFieldName(schema); + m_buf += '['; + m_depth++; + m_needsComma[m_depth] = false; + m_isMap[m_depth] = false; + m_isList[m_depth] = true; + + return true; + } + + void EndList() { + m_depth--; + m_buf += ']'; + } + + bool BeginMap(const Schema& schema, size_t) { + if (m_depth + 1 >= MAX_DEPTH) { + m_errorMessage = "Maximum nesting depth exceeded"; + return false; + } + + WriteFieldName(schema); + m_buf += '{'; + m_depth++; + m_needsComma[m_depth] = false; + m_isMap[m_depth] = true; + m_isList[m_depth] = false; + + return true; + } + + void WriteMapKey(const Aws::String& key) { m_currentMapKey = key; } + + void EndMap() { + m_depth--; + m_buf += '}'; + } + + bool BeginNestedStructure(const Schema& schema) { + if (m_depth + 1 >= MAX_DEPTH) { + m_errorMessage = "Maximum nesting depth exceeded"; + return false; + } + + WriteFieldName(schema); + m_buf += '{'; + m_depth++; + m_needsComma[m_depth] = false; + m_isMap[m_depth] = false; + m_isList[m_depth] = false; + + return true; + } + + void EndNestedStructure() { + m_depth--; + m_buf += '}'; + } + + SerializerOutcome GetPayload() { + if (m_finalized || !m_errorMessage.empty()) { + return Aws::Client::AWSError( + Aws::Client::CoreErrors::INTERNAL_FAILURE, "SerializationException", + !m_errorMessage.empty() ? m_errorMessage : "Serializer has already been finalized", false); + } + m_finalized = true; + return std::move(m_buf); + } + + private: + Aws::String m_buf; + int m_depth = 0; + Aws::Array m_needsComma{}; + Aws::Array m_isMap{}; + Aws::Array m_isList{}; + Aws::String m_currentMapKey; + bool m_finalized = false; + Aws::String m_errorMessage; + + void WriteCommaIfNeeded() { + if (m_needsComma[m_depth]) { + m_buf += ','; + } else { + m_needsComma[m_depth] = true; + } + } + + void WriteKey(const Aws::String& key) { + Aws::Schema::WriteQuotedJsonString(m_buf, key); + m_buf += ':'; + } + + void WriteFieldName(const Schema& schema) { + WriteCommaIfNeeded(); + if (m_isList[m_depth]) { + return; + } + if (m_depth > 0 && m_isMap[m_depth]) { + WriteKey(m_currentMapKey); + } else { + WriteKey(schema.GetMemberName()); + } + } +}; + +JsonShapeSerializer::JsonShapeSerializer() : m_impl(Aws::MakeUnique("JsonShapeSerializer")) {} +JsonShapeSerializer::~JsonShapeSerializer() = default; + +bool JsonShapeSerializer::BeginStructure(const Schema& schema) { return m_impl->BeginStructure(schema); } +void JsonShapeSerializer::EndStructure() { m_impl->EndStructure(); } +void JsonShapeSerializer::WriteBoolean(const Schema& schema, bool value) { m_impl->WriteBoolean(schema, value); } +void JsonShapeSerializer::WriteInteger(const Schema& schema, int value) { m_impl->WriteInteger(schema, value); } +void JsonShapeSerializer::WriteLong(const Schema& schema, int64_t value) { m_impl->WriteLong(schema, value); } +void JsonShapeSerializer::WriteDouble(const Schema& schema, double value) { m_impl->WriteDouble(schema, value); } +void JsonShapeSerializer::WriteString(const Schema& schema, const Aws::String& value) { m_impl->WriteString(schema, value); } +void JsonShapeSerializer::WriteTimestamp(const Schema& schema, const DateTime& value) { m_impl->WriteTimestamp(schema, value); } +void JsonShapeSerializer::WriteBlob(const Schema& schema, const ByteBuffer& value) { m_impl->WriteBlob(schema, value); } +void JsonShapeSerializer::WriteEnum(const Schema& schema, int value) { m_impl->WriteEnum(schema, value); } +void JsonShapeSerializer::WriteNull(const Schema& schema) { m_impl->WriteNull(schema); } +bool JsonShapeSerializer::BeginList(const Schema& schema, size_t count) { return m_impl->BeginList(schema, count); } +void JsonShapeSerializer::EndList() { m_impl->EndList(); } +bool JsonShapeSerializer::BeginMap(const Schema& schema, size_t count) { return m_impl->BeginMap(schema, count); } +void JsonShapeSerializer::WriteMapKey(const Aws::String& key) { m_impl->WriteMapKey(key); } +void JsonShapeSerializer::EndMap() { m_impl->EndMap(); } +bool JsonShapeSerializer::BeginNestedStructure(const Schema& schema) { return m_impl->BeginNestedStructure(schema); } +void JsonShapeSerializer::EndNestedStructure() { m_impl->EndNestedStructure(); } +JsonShapeSerializer::SerializerOutcome JsonShapeSerializer::GetPayload() { return m_impl->GetPayload(); } diff --git a/src/aws-cpp-sdk-core/source/smithy/client/schema/JsonWriteUtils.cpp b/src/aws-cpp-sdk-core/source/smithy/client/schema/JsonWriteUtils.cpp new file mode 100644 index 00000000000..f3d12a0a775 --- /dev/null +++ b/src/aws-cpp-sdk-core/source/smithy/client/schema/JsonWriteUtils.cpp @@ -0,0 +1,75 @@ +/** + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0. + */ +#include +#include + +namespace { + +static const Aws::Array HEX = {{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}}; + +void EscapeChar(Aws::String& buf, unsigned char c) { + switch (c) { + case '\"': + buf += "\\\""; + break; + case '\\': + buf += "\\\\"; + break; + case '\b': + buf += "\\b"; + break; + case '\f': + buf += "\\f"; + break; + case '\n': + buf += "\\n"; + break; + case '\r': + buf += "\\r"; + break; + case '\t': + buf += "\\t"; + break; + default: + buf += "\\u00"; + buf += HEX[(c >> 4) & 0xF]; + buf += HEX[c & 0xF]; + break; + } +} + +bool NeedsEscape(unsigned char c) { return c < 0x20 || c == '"' || c == '\\'; } + +} // anonymous namespace + +namespace Aws { +namespace Schema { + +void WriteQuotedJsonString(Aws::String& buf, const Aws::String& value) { + buf += '"'; + const char* data = value.data(); + const size_t len = value.size(); + + size_t i = 0; + while (i < len) { + size_t start = i; + while (i < len) { + unsigned char c = static_cast(data[i]); + if (c < 0x80 && NeedsEscape(c)) break; + i++; + } + if (i > start) { + buf.append(data + start, i - start); + } + if (i < len) { + EscapeChar(buf, static_cast(data[i])); + i++; + } + } + buf += '"'; +} + +} // namespace Schema +} // namespace Aws diff --git a/tests/aws-cpp-sdk-core-tests/CMakeLists.txt b/tests/aws-cpp-sdk-core-tests/CMakeLists.txt index 36cdad1290c..4ce72d35117 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 UTILS_COMPONENT_REGISTRY_SRC "${CMAKE_CURRENT_SOURCE_DIR}/utils/compon file(GLOB MONITORING_SRC "${CMAKE_CURRENT_SOURCE_DIR}/monitoring/*.cpp") 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_SRC "${CMAKE_CURRENT_SOURCE_DIR}/smithy/client/schema/*.cpp") file(GLOB SMITHY_CLIENT_SERIALIZER_SRC "${CMAKE_CURRENT_SOURCE_DIR}/smithy/client/serializer/*.cpp") file(GLOB SMITHY_CLIENT_FEATURE_SRC "${CMAKE_CURRENT_SOURCE_DIR}/smithy/client/feature/*.cpp") file(GLOB ENDPOINT_SRC "${CMAKE_CURRENT_SOURCE_DIR}/endpoint/*.cpp") diff --git a/tests/aws-cpp-sdk-core-tests/smithy/client/schema/JsonShapeSerializerTest.cpp b/tests/aws-cpp-sdk-core-tests/smithy/client/schema/JsonShapeSerializerTest.cpp new file mode 100644 index 00000000000..b794ff40afa --- /dev/null +++ b/tests/aws-cpp-sdk-core-tests/smithy/client/schema/JsonShapeSerializerTest.cpp @@ -0,0 +1,441 @@ +/** + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0. + */ +#include +#include +#include + +using namespace smithy::schema; + +class JsonShapeSerializerTest : public Aws::Testing::AwsCppSdkGTestSuite {}; + +// --- Scalars --- + +TEST_F(JsonShapeSerializerTest, EmptyStructure) { + JsonShapeSerializer s; + Schema root; + s.BeginStructure(root); + s.EndStructure(); + auto outcome = s.GetPayload(); + ASSERT_TRUE(outcome.IsSuccess()); + EXPECT_EQ(outcome.GetResult(), "{}"); +} + +TEST_F(JsonShapeSerializerTest, BooleanTrue) { + JsonShapeSerializer s; + Schema root; + Schema member("enabled", ShapeType::Boolean); + s.BeginStructure(root); + s.WriteBoolean(member, true); + s.EndStructure(); + EXPECT_NE(s.GetPayload().GetResult().find("\"enabled\":true"), Aws::String::npos); +} + +TEST_F(JsonShapeSerializerTest, BooleanFalse) { + JsonShapeSerializer s; + Schema root; + Schema member("enabled", ShapeType::Boolean); + s.BeginStructure(root); + s.WriteBoolean(member, false); + s.EndStructure(); + EXPECT_NE(s.GetPayload().GetResult().find("\"enabled\":false"), Aws::String::npos); +} + +TEST_F(JsonShapeSerializerTest, Integer) { + JsonShapeSerializer s; + Schema root; + Schema member("count", ShapeType::Integer); + s.BeginStructure(root); + s.WriteInteger(member, 42); + s.EndStructure(); + EXPECT_NE(s.GetPayload().GetResult().find("\"count\":42"), Aws::String::npos); +} + +TEST_F(JsonShapeSerializerTest, Long) { + JsonShapeSerializer s; + Schema root; + Schema member("bigNum", ShapeType::Long); + s.BeginStructure(root); + s.WriteLong(member, 9876543210LL); + s.EndStructure(); + EXPECT_NE(s.GetPayload().GetResult().find("\"bigNum\":9876543210"), Aws::String::npos); +} + +TEST_F(JsonShapeSerializerTest, Double) { + JsonShapeSerializer s; + Schema root; + Schema member("ratio", ShapeType::Double); + s.BeginStructure(root); + s.WriteDouble(member, 3.14); + s.EndStructure(); + EXPECT_NE(s.GetPayload().GetResult().find("\"ratio\":3.14"), Aws::String::npos); +} + +TEST_F(JsonShapeSerializerTest, String) { + JsonShapeSerializer s; + Schema root; + Schema member("name", ShapeType::String); + s.BeginStructure(root); + s.WriteString(member, "hello"); + s.EndStructure(); + EXPECT_NE(s.GetPayload().GetResult().find("\"name\":\"hello\""), Aws::String::npos); +} + +TEST_F(JsonShapeSerializerTest, EmptyString) { + JsonShapeSerializer s; + Schema root; + Schema member("name", ShapeType::String); + s.BeginStructure(root); + s.WriteString(member, ""); + s.EndStructure(); + EXPECT_NE(s.GetPayload().GetResult().find("\"name\":\"\""), Aws::String::npos); +} + +TEST_F(JsonShapeSerializerTest, Timestamp) { + JsonShapeSerializer s; + Schema root; + Schema member("created", ShapeType::Timestamp); + s.BeginStructure(root); + Aws::Utils::DateTime dt(1234567890.0); + s.WriteTimestamp(member, dt); + s.EndStructure(); + EXPECT_NE(s.GetPayload().GetResult().find("\"created\":"), Aws::String::npos); +} + +TEST_F(JsonShapeSerializerTest, Blob) { + JsonShapeSerializer s; + Schema root; + Schema member("data", ShapeType::Blob); + s.BeginStructure(root); + unsigned char raw[] = {0x66, 0x6f, 0x6f}; + Aws::Utils::ByteBuffer buf(raw, 3); + s.WriteBlob(member, buf); + s.EndStructure(); + EXPECT_NE(s.GetPayload().GetResult().find("\"data\":\"Zm9v\""), Aws::String::npos); +} + +TEST_F(JsonShapeSerializerTest, NullValue) { + JsonShapeSerializer s; + Schema root; + Schema member("item", ShapeType::String); + s.BeginStructure(root); + s.WriteNull(member); + s.EndStructure(); + EXPECT_NE(s.GetPayload().GetResult().find("\"item\":null"), Aws::String::npos); +} + +TEST_F(JsonShapeSerializerTest, MultipleScalars) { + JsonShapeSerializer s; + Schema root; + Schema m1("a", ShapeType::Boolean); + Schema m2("b", ShapeType::Integer); + Schema m3("c", ShapeType::String); + s.BeginStructure(root); + s.WriteBoolean(m1, true); + s.WriteInteger(m2, 7); + s.WriteString(m3, "x"); + s.EndStructure(); + auto payload = s.GetPayload().GetResult(); + EXPECT_NE(payload.find("\"a\":true"), Aws::String::npos); + EXPECT_NE(payload.find("\"b\":7"), Aws::String::npos); + EXPECT_NE(payload.find("\"c\":\"x\""), Aws::String::npos); +} + +// --- Nested structures --- + +TEST_F(JsonShapeSerializerTest, NestedStructure) { + JsonShapeSerializer s; + Schema root; + Schema nested("metadata", ShapeType::Structure); + Schema inner("key", ShapeType::String); + s.BeginStructure(root); + s.BeginNestedStructure(nested); + s.WriteString(inner, "val"); + s.EndNestedStructure(); + s.EndStructure(); + EXPECT_NE(s.GetPayload().GetResult().find("\"metadata\":{\"key\":\"val\"}"), Aws::String::npos); +} + +TEST_F(JsonShapeSerializerTest, DeeplyNestedStructure) { + JsonShapeSerializer s; + Schema root; + Schema level1("l1", ShapeType::Structure); + Schema level2("l2", ShapeType::Structure); + Schema leaf("val", ShapeType::Integer); + s.BeginStructure(root); + s.BeginNestedStructure(level1); + s.BeginNestedStructure(level2); + s.WriteInteger(leaf, 99); + s.EndNestedStructure(); + s.EndNestedStructure(); + s.EndStructure(); + EXPECT_NE(s.GetPayload().GetResult().find("\"l1\":{\"l2\":{\"val\":99}}"), Aws::String::npos); +} + +// --- Lists --- + +TEST_F(JsonShapeSerializerTest, ListOfStrings) { + JsonShapeSerializer s; + Schema root; + Schema listMember("tags", ShapeType::List); + Schema elem("member", ShapeType::String); + s.BeginStructure(root); + s.BeginList(listMember, 3); + s.WriteString(elem, "a"); + s.WriteString(elem, "b"); + s.WriteString(elem, "c"); + s.EndList(); + s.EndStructure(); + EXPECT_NE(s.GetPayload().GetResult().find("\"tags\":[\"a\",\"b\",\"c\"]"), Aws::String::npos); +} + +TEST_F(JsonShapeSerializerTest, ListOfIntegers) { + JsonShapeSerializer s; + Schema root; + Schema listMember("nums", ShapeType::List); + Schema elem("member", ShapeType::Integer); + s.BeginStructure(root); + s.BeginList(listMember, 3); + s.WriteInteger(elem, 1); + s.WriteInteger(elem, 2); + s.WriteInteger(elem, 3); + s.EndList(); + s.EndStructure(); + EXPECT_NE(s.GetPayload().GetResult().find("\"nums\":[1,2,3]"), Aws::String::npos); +} + +TEST_F(JsonShapeSerializerTest, EmptyList) { + JsonShapeSerializer s; + Schema root; + Schema listMember("items", ShapeType::List); + s.BeginStructure(root); + s.BeginList(listMember, 0); + s.EndList(); + s.EndStructure(); + EXPECT_NE(s.GetPayload().GetResult().find("\"items\":[]"), Aws::String::npos); +} + +TEST_F(JsonShapeSerializerTest, ListOfStructures) { + JsonShapeSerializer s; + Schema root; + Schema listMember("items", ShapeType::List); + Schema structElem("member", ShapeType::Structure); + Schema field("id", ShapeType::Integer); + s.BeginStructure(root); + s.BeginList(listMember, 2); + s.BeginNestedStructure(structElem); + s.WriteInteger(field, 1); + s.EndNestedStructure(); + s.BeginNestedStructure(structElem); + s.WriteInteger(field, 2); + s.EndNestedStructure(); + s.EndList(); + s.EndStructure(); + EXPECT_NE(s.GetPayload().GetResult().find("\"items\":[{\"id\":1},{\"id\":2}]"), Aws::String::npos); +} + +TEST_F(JsonShapeSerializerTest, SparseList) { + JsonShapeSerializer s; + Schema root; + Schema listMember("items", ShapeType::List); + Schema elem("member", ShapeType::String); + s.BeginStructure(root); + s.BeginList(listMember, 3); + s.WriteString(elem, "a"); + s.WriteNull(elem); + s.WriteString(elem, "b"); + s.EndList(); + s.EndStructure(); + EXPECT_NE(s.GetPayload().GetResult().find("\"items\":[\"a\",null,\"b\"]"), Aws::String::npos); +} + +// --- Maps --- + +TEST_F(JsonShapeSerializerTest, MapOfStrings) { + JsonShapeSerializer s; + Schema root; + Schema mapMember("headers", ShapeType::Map); + Schema valSchema("value", ShapeType::String); + s.BeginStructure(root); + s.BeginMap(mapMember, 2); + s.WriteMapKey("x-foo"); + s.WriteString(valSchema, "bar"); + s.WriteMapKey("x-baz"); + s.WriteString(valSchema, "qux"); + s.EndMap(); + s.EndStructure(); + auto payload = s.GetPayload().GetResult(); + EXPECT_NE(payload.find("\"x-foo\":\"bar\""), Aws::String::npos); + EXPECT_NE(payload.find("\"x-baz\":\"qux\""), Aws::String::npos); +} + +TEST_F(JsonShapeSerializerTest, EmptyMap) { + JsonShapeSerializer s; + Schema root; + Schema mapMember("tags", ShapeType::Map); + s.BeginStructure(root); + s.BeginMap(mapMember, 0); + s.EndMap(); + s.EndStructure(); + EXPECT_NE(s.GetPayload().GetResult().find("\"tags\":{}"), Aws::String::npos); +} + +TEST_F(JsonShapeSerializerTest, MapOfStructures) { + JsonShapeSerializer s; + Schema root; + Schema mapMember("nodes", ShapeType::Map); + Schema valSchema("value", ShapeType::Structure); + Schema field("val", ShapeType::Integer); + s.BeginStructure(root); + s.BeginMap(mapMember, 1); + s.WriteMapKey("a"); + s.BeginNestedStructure(valSchema); + s.WriteInteger(field, 1); + s.EndNestedStructure(); + s.EndMap(); + s.EndStructure(); + EXPECT_NE(s.GetPayload().GetResult().find("\"nodes\":{\"a\":{\"val\":1}}"), Aws::String::npos); +} + +TEST_F(JsonShapeSerializerTest, SparseMap) { + JsonShapeSerializer s; + Schema root; + Schema mapMember("data", ShapeType::Map); + Schema valSchema("value", ShapeType::String); + s.BeginStructure(root); + s.BeginMap(mapMember, 2); + s.WriteMapKey("present"); + s.WriteString(valSchema, "yes"); + s.WriteMapKey("absent"); + s.WriteNull(valSchema); + s.EndMap(); + s.EndStructure(); + auto payload = s.GetPayload().GetResult(); + EXPECT_NE(payload.find("\"present\":\"yes\""), Aws::String::npos); + EXPECT_NE(payload.find("\"absent\":null"), Aws::String::npos); +} + +// --- Combinations --- + +TEST_F(JsonShapeSerializerTest, StructureWithListAndMap) { + JsonShapeSerializer s; + Schema root; + Schema strMember("name", ShapeType::String); + Schema listMember("tags", ShapeType::List); + Schema listElem("member", ShapeType::String); + Schema mapMember("meta", ShapeType::Map); + Schema mapVal("value", ShapeType::String); + + s.BeginStructure(root); + s.WriteString(strMember, "test"); + s.BeginList(listMember, 2); + s.WriteString(listElem, "t1"); + s.WriteString(listElem, "t2"); + s.EndList(); + s.BeginMap(mapMember, 1); + s.WriteMapKey("k"); + s.WriteString(mapVal, "v"); + s.EndMap(); + s.EndStructure(); + + auto payload = s.GetPayload().GetResult(); + EXPECT_NE(payload.find("\"name\":\"test\""), Aws::String::npos); + EXPECT_NE(payload.find("\"tags\":[\"t1\",\"t2\"]"), Aws::String::npos); + EXPECT_NE(payload.find("\"meta\":{\"k\":\"v\"}"), Aws::String::npos); +} + +TEST_F(JsonShapeSerializerTest, MapContainingList) { + JsonShapeSerializer s; + Schema root; + Schema mapMember("data", ShapeType::Map); + Schema listSchema("value", ShapeType::List); + Schema elem("member", ShapeType::Integer); + + s.BeginStructure(root); + s.BeginMap(mapMember, 1); + s.WriteMapKey("nums"); + s.BeginList(listSchema, 2); + s.WriteInteger(elem, 1); + s.WriteInteger(elem, 2); + s.EndList(); + s.EndMap(); + s.EndStructure(); + + EXPECT_NE(s.GetPayload().GetResult().find("\"data\":{\"nums\":[1,2]}"), Aws::String::npos); +} + +// --- JSON Escaping --- + +TEST_F(JsonShapeSerializerTest, EscapesQuotesInString) { + JsonShapeSerializer s; + Schema root; + Schema member("msg", ShapeType::String); + s.BeginStructure(root); + s.WriteString(member, "say \"hello\""); + s.EndStructure(); + EXPECT_NE(s.GetPayload().GetResult().find("\"msg\":\"say \\\"hello\\\"\""), Aws::String::npos); +} + +TEST_F(JsonShapeSerializerTest, EscapesBackslash) { + JsonShapeSerializer s; + Schema root; + Schema member("path", ShapeType::String); + s.BeginStructure(root); + s.WriteString(member, "C:\\Users\\test"); + s.EndStructure(); + EXPECT_NE(s.GetPayload().GetResult().find("\"path\":\"C:\\\\Users\\\\test\""), Aws::String::npos); +} + +TEST_F(JsonShapeSerializerTest, EscapesControlCharacters) { + JsonShapeSerializer s; + Schema root; + Schema member("text", ShapeType::String); + s.BeginStructure(root); + s.WriteString(member, "line1\nline2\ttab"); + s.EndStructure(); + EXPECT_NE(s.GetPayload().GetResult().find("\"text\":\"line1\\nline2\\ttab\""), Aws::String::npos); +} + +TEST_F(JsonShapeSerializerTest, EscapesNullByte) { + JsonShapeSerializer s; + Schema root; + Schema member("data", ShapeType::String); + s.BeginStructure(root); + Aws::String val("ab"); + val += '\0'; + val += "cd"; + s.WriteString(member, val); + s.EndStructure(); + EXPECT_NE(s.GetPayload().GetResult().find("\"data\":\"ab\\u0000cd\""), Aws::String::npos); +} + +TEST_F(JsonShapeSerializerTest, EscapesInMapKey) { + JsonShapeSerializer s; + Schema root; + Schema mapMember("m", ShapeType::Map); + Schema valSchema("value", ShapeType::String); + s.BeginStructure(root); + s.BeginMap(mapMember, 1); + s.WriteMapKey("key\"with\"quotes"); + s.WriteString(valSchema, "v"); + s.EndMap(); + s.EndStructure(); + EXPECT_NE(s.GetPayload().GetResult().find("\"key\\\"with\\\"quotes\":\"v\""), Aws::String::npos); +} + +// --- Depth limit --- + +TEST_F(JsonShapeSerializerTest, MaxDepthEnforcement) { + JsonShapeSerializer s; + Schema root; + Schema nested("n", ShapeType::Structure); + s.BeginStructure(root); + // Nest 1000+ times to exceed MAX_DEPTH + for (int i = 0; i < 1000; i++) { + s.BeginNestedStructure(nested); + } + auto outcome = s.GetPayload(); + ASSERT_FALSE(outcome.IsSuccess()); + EXPECT_NE(outcome.GetError().GetMessage().find("depth"), Aws::String::npos); +}