From 6976217049c1b02dc48af16e19159a69e9405b8d Mon Sep 17 00:00:00 2001 From: Snigdha Date: Tue, 1 Sep 2026 12:50:11 -0400 Subject: [PATCH] Add schema-serde streaming JSON/XML deserializers, ec2Query, Codec, and ClientProtocol --- .../feature-schema-serde-deserializers.json | 6 + .../client/schema/CborShapeDeserializer.h | 41 +- .../smithy/client/schema/ClientProtocol.h | 99 +++ .../include/smithy/client/schema/Codec.h | 47 ++ .../client/schema/JsonShapeDeserializer.h | 37 + .../client/schema/QueryShapeSerializer.h | 50 ++ .../smithy/client/schema/SerdeTraits.h | 16 +- .../smithy/client/schema/ShapeDeserializer.h | 41 +- .../client/schema/XmlShapeDeserializer.h | 37 + .../client/schema/CborShapeDeserializer.cpp | 210 ++++-- .../smithy/client/schema/ClientProtocol.cpp | 85 +++ .../source/smithy/client/schema/Codec.cpp | 45 ++ .../client/schema/JsonShapeDeserializer.cpp | 483 ++++++++++++ .../client/schema/JsonShapeSerializer.cpp | 17 +- .../client/schema/QueryShapeSerializer.cpp | 367 +++++++++ .../source/smithy/client/schema/TraitKey.cpp | 1 + .../client/schema/XmlShapeDeserializer.cpp | 495 ++++++++++++ .../client/schema/XmlShapeSerializer.cpp | 18 +- .../schema/CborShapeDeserializerTest.cpp | 707 +++++++----------- .../client/schema/ClientProtocolTest.cpp | 119 +++ .../smithy/client/schema/CodecTest.cpp | 81 ++ .../schema/JsonShapeDeserializerTest.cpp | 360 +++++++++ .../schema/QueryShapeSerializerTest.cpp | 290 +++++++ .../schema/XmlShapeDeserializerTest.cpp | 434 +++++++++++ 24 files changed, 3529 insertions(+), 557 deletions(-) create mode 100644 .changelog/feature-schema-serde-deserializers.json create mode 100644 src/aws-cpp-sdk-core/include/smithy/client/schema/ClientProtocol.h create mode 100644 src/aws-cpp-sdk-core/include/smithy/client/schema/Codec.h create mode 100644 src/aws-cpp-sdk-core/include/smithy/client/schema/JsonShapeDeserializer.h create mode 100644 src/aws-cpp-sdk-core/include/smithy/client/schema/QueryShapeSerializer.h create mode 100644 src/aws-cpp-sdk-core/include/smithy/client/schema/XmlShapeDeserializer.h create mode 100644 src/aws-cpp-sdk-core/source/smithy/client/schema/ClientProtocol.cpp create mode 100644 src/aws-cpp-sdk-core/source/smithy/client/schema/Codec.cpp create mode 100644 src/aws-cpp-sdk-core/source/smithy/client/schema/JsonShapeDeserializer.cpp create mode 100644 src/aws-cpp-sdk-core/source/smithy/client/schema/QueryShapeSerializer.cpp create mode 100644 src/aws-cpp-sdk-core/source/smithy/client/schema/XmlShapeDeserializer.cpp create mode 100644 tests/aws-cpp-sdk-core-tests/smithy/client/schema/ClientProtocolTest.cpp create mode 100644 tests/aws-cpp-sdk-core-tests/smithy/client/schema/CodecTest.cpp create mode 100644 tests/aws-cpp-sdk-core-tests/smithy/client/schema/JsonShapeDeserializerTest.cpp create mode 100644 tests/aws-cpp-sdk-core-tests/smithy/client/schema/QueryShapeSerializerTest.cpp create mode 100644 tests/aws-cpp-sdk-core-tests/smithy/client/schema/XmlShapeDeserializerTest.cpp diff --git a/.changelog/feature-schema-serde-deserializers.json b/.changelog/feature-schema-serde-deserializers.json new file mode 100644 index 000000000000..74e901cafc8d --- /dev/null +++ b/.changelog/feature-schema-serde-deserializers.json @@ -0,0 +1,6 @@ +{ + "type": "feature", + "category": "aws-cpp-sdk-core", + "contributor": "pulimsr", + "description": "Add schema-serde buffer-based JSON/XML deserializers, Query serializer (awsQuery/ec2Query), and Codec/ClientProtocol" +} diff --git a/src/aws-cpp-sdk-core/include/smithy/client/schema/CborShapeDeserializer.h b/src/aws-cpp-sdk-core/include/smithy/client/schema/CborShapeDeserializer.h index c7714db85592..caec3fb22ba8 100644 --- a/src/aws-cpp-sdk-core/include/smithy/client/schema/CborShapeDeserializer.h +++ b/src/aws-cpp-sdk-core/include/smithy/client/schema/CborShapeDeserializer.h @@ -12,35 +12,26 @@ class SMITHY_API CborShapeDeserializer final : public ShapeDeserializer { CborShapeDeserializer(const unsigned char* data, size_t length); ~CborShapeDeserializer(); - Aws::Crt::Optional ReadBoolean() override; - Aws::Crt::Optional ReadInteger() override; - Aws::Crt::Optional ReadLong() override; - Aws::Crt::Optional ReadFloat() override; - Aws::Crt::Optional ReadDouble() override; - Aws::Crt::Optional ReadString() override; - Aws::Crt::Optional ReadTimestamp() override; - Aws::Crt::Optional ReadBlob() override; - Aws::Crt::Optional ReadEnum() override; - - size_t BeginStruct() override; - void EndStruct() override; - - size_t BeginList() override; - void EndList() override; - - size_t BeginMap() override; - void EndMap() override; - - Aws::Crt::Optional ReadKey() override; - bool IsBreak() override; + void ReadStruct(const Schema& schema, const StructMemberConsumer& consumer) override; + void ReadList(const Schema& schema, const ListElementConsumer& consumer) override; + void ReadMap(const Schema& schema, const MapEntryConsumer& consumer) override; + + Aws::Crt::Optional ReadBoolean(const Schema& schema) override; + Aws::Crt::Optional ReadInteger(const Schema& schema) override; + Aws::Crt::Optional ReadLong(const Schema& schema) override; + Aws::Crt::Optional ReadFloat(const Schema& schema) override; + Aws::Crt::Optional ReadDouble(const Schema& schema) override; + Aws::Crt::Optional ReadString(const Schema& schema) override; + Aws::Crt::Optional ReadTimestamp(const Schema& schema) override; + Aws::Crt::Optional ReadBlob(const Schema& schema) override; + Aws::Crt::Optional ReadEnum(const Schema& schema) override; + bool IsNull() override; - void ReadNull() override; - void SkipValue() override; private: class Impl; Aws::UniquePtr m_impl; }; -} // namespace schema -} // namespace smithy +} +} diff --git a/src/aws-cpp-sdk-core/include/smithy/client/schema/ClientProtocol.h b/src/aws-cpp-sdk-core/include/smithy/client/schema/ClientProtocol.h new file mode 100644 index 000000000000..8e5e8b7d213a --- /dev/null +++ b/src/aws-cpp-sdk-core/include/smithy/client/schema/ClientProtocol.h @@ -0,0 +1,99 @@ +#pragma once + +#include +#include +#include +#include + +namespace smithy { +namespace schema { + +class Schema; +class SerializableStruct; +class ShapeDeserializer; + +class SMITHY_API ClientProtocol { + public: + using SerializerOutcome = Codec::SerializerOutcome; + + virtual ~ClientProtocol() = default; + + virtual Aws::String GetProtocolId() const = 0; + virtual Aws::String GetContentType() const = 0; + virtual SerializerOutcome SerializeInput(const Schema& schema, const SerializableStruct& input) const = 0; + virtual Aws::UniquePtr CreateOutputDeserializer(const unsigned char* data, size_t length) const = 0; +}; + +class SMITHY_API RestJsonProtocol final : public ClientProtocol { + public: + Aws::String GetProtocolId() const override; + Aws::String GetContentType() const override; + SerializerOutcome SerializeInput(const Schema& schema, const SerializableStruct& input) const override; + Aws::UniquePtr CreateOutputDeserializer(const unsigned char* data, size_t length) const override; + + private: + JsonCodec m_codec; +}; + +class SMITHY_API AwsJson10Protocol final : public ClientProtocol { + public: + Aws::String GetProtocolId() const override; + Aws::String GetContentType() const override; + SerializerOutcome SerializeInput(const Schema& schema, const SerializableStruct& input) const override; + Aws::UniquePtr CreateOutputDeserializer(const unsigned char* data, size_t length) const override; + + private: + JsonCodec m_codec; +}; + +class SMITHY_API AwsJson11Protocol final : public ClientProtocol { + public: + Aws::String GetProtocolId() const override; + Aws::String GetContentType() const override; + SerializerOutcome SerializeInput(const Schema& schema, const SerializableStruct& input) const override; + Aws::UniquePtr CreateOutputDeserializer(const unsigned char* data, size_t length) const override; + + private: + JsonCodec m_codec; +}; + +class SMITHY_API RpcV2CborProtocol final : public ClientProtocol { + public: + Aws::String GetProtocolId() const override; + Aws::String GetContentType() const override; + SerializerOutcome SerializeInput(const Schema& schema, const SerializableStruct& input) const override; + Aws::UniquePtr CreateOutputDeserializer(const unsigned char* data, size_t length) const override; + + private: + CborCodec m_codec; +}; + +class SMITHY_API RestXmlProtocol final : public ClientProtocol { + public: + Aws::String GetProtocolId() const override; + Aws::String GetContentType() const override; + SerializerOutcome SerializeInput(const Schema& schema, const SerializableStruct& input) const override; + Aws::UniquePtr CreateOutputDeserializer(const unsigned char* data, size_t length) const override; + + private: + XmlCodec m_codec; +}; + +class SMITHY_API AwsQueryProtocol final : public ClientProtocol { + public: + Aws::String GetProtocolId() const override; + Aws::String GetContentType() const override; + SerializerOutcome SerializeInput(const Schema& schema, const SerializableStruct& input) const override; + Aws::UniquePtr CreateOutputDeserializer(const unsigned char* data, size_t length) const override; +}; + +class SMITHY_API Ec2QueryProtocol final : public ClientProtocol { + public: + Aws::String GetProtocolId() const override; + Aws::String GetContentType() const override; + SerializerOutcome SerializeInput(const Schema& schema, const SerializableStruct& input) const override; + Aws::UniquePtr CreateOutputDeserializer(const unsigned char* data, size_t length) const override; +}; + +} +} diff --git a/src/aws-cpp-sdk-core/include/smithy/client/schema/Codec.h b/src/aws-cpp-sdk-core/include/smithy/client/schema/Codec.h new file mode 100644 index 000000000000..23f5bfcdd9e0 --- /dev/null +++ b/src/aws-cpp-sdk-core/include/smithy/client/schema/Codec.h @@ -0,0 +1,47 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace smithy { +namespace schema { + +class Schema; +class SerializableStruct; +class ShapeDeserializer; + +class SMITHY_API Codec { + public: + using SerializerOutcome = Aws::Utils::Outcome>; + + virtual ~Codec() = default; + + virtual SerializerOutcome Serialize(const Schema& schema, const SerializableStruct& shape) const = 0; + + virtual Aws::UniquePtr CreateDeserializer(const unsigned char* data, size_t length) const = 0; +}; + +class SMITHY_API JsonCodec final : public Codec { + public: + SerializerOutcome Serialize(const Schema& schema, const SerializableStruct& shape) const override; + Aws::UniquePtr CreateDeserializer(const unsigned char* data, size_t length) const override; +}; + +class SMITHY_API XmlCodec final : public Codec { + public: + SerializerOutcome Serialize(const Schema& schema, const SerializableStruct& shape) const override; + Aws::UniquePtr CreateDeserializer(const unsigned char* data, size_t length) const override; +}; + +class SMITHY_API CborCodec final : public Codec { + public: + SerializerOutcome Serialize(const Schema& schema, const SerializableStruct& shape) const override; + Aws::UniquePtr CreateDeserializer(const unsigned char* data, size_t length) const override; +}; + +} +} diff --git a/src/aws-cpp-sdk-core/include/smithy/client/schema/JsonShapeDeserializer.h b/src/aws-cpp-sdk-core/include/smithy/client/schema/JsonShapeDeserializer.h new file mode 100644 index 000000000000..ae4d95e2dbf0 --- /dev/null +++ b/src/aws-cpp-sdk-core/include/smithy/client/schema/JsonShapeDeserializer.h @@ -0,0 +1,37 @@ +#pragma once + +#include +#include +#include + +namespace smithy { +namespace schema { + +class SMITHY_API JsonShapeDeserializer final : public ShapeDeserializer { + public: + JsonShapeDeserializer(const unsigned char* data, size_t length); + ~JsonShapeDeserializer(); + + void ReadStruct(const Schema& schema, const StructMemberConsumer& consumer) override; + void ReadList(const Schema& schema, const ListElementConsumer& consumer) override; + void ReadMap(const Schema& schema, const MapEntryConsumer& consumer) override; + + Aws::Crt::Optional ReadBoolean(const Schema& schema) override; + Aws::Crt::Optional ReadInteger(const Schema& schema) override; + Aws::Crt::Optional ReadLong(const Schema& schema) override; + Aws::Crt::Optional ReadFloat(const Schema& schema) override; + Aws::Crt::Optional ReadDouble(const Schema& schema) override; + Aws::Crt::Optional ReadString(const Schema& schema) override; + Aws::Crt::Optional ReadTimestamp(const Schema& schema) override; + Aws::Crt::Optional ReadBlob(const Schema& schema) override; + Aws::Crt::Optional ReadEnum(const Schema& schema) override; + + bool IsNull() override; + + private: + class Impl; + Aws::UniquePtr m_impl; +}; + +} +} 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 new file mode 100644 index 000000000000..cf0ef10bde89 --- /dev/null +++ b/src/aws-cpp-sdk-core/include/smithy/client/schema/QueryShapeSerializer.h @@ -0,0 +1,50 @@ +/** + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0. + */ +#pragma once + +#include +#include +#include +#include + +#include + +namespace smithy { +namespace schema { + +class SMITHY_API QueryShapeSerializer final : public ShapeSerializer { + public: + using SerializerOutcome = Aws::Utils::Outcome>; + + enum class Flavor { AwsQuery, Ec2Query }; + + QueryShapeSerializer(); + explicit QueryShapeSerializer(Flavor flavor); + ~QueryShapeSerializer(); + + void WriteStruct(const Schema& schema, const SerializableStruct& value) override; + void WriteList(const Schema& schema, size_t size, const std::function& consumer) override; + void WriteMap(const Schema& schema, size_t size, const std::function& consumer) override; + + void WriteBoolean(const Schema& schema, bool value) override; + void WriteInteger(const Schema& schema, int value) override; + void WriteLong(const Schema& schema, int64_t value) override; + void WriteFloat(const Schema& schema, float value) override; + void WriteDouble(const Schema& schema, double value) override; + void WriteString(const Schema& schema, const Aws::String& value) override; + void WriteTimestamp(const Schema& schema, const Aws::Utils::DateTime& value) override; + void WriteBlob(const Schema& schema, const Aws::Utils::ByteBuffer& value) override; + void WriteEnum(const Schema& schema, int value) override; + void WriteNull(const Schema& schema) override; + + SerializerOutcome GetPayload(); + + private: + class Impl; + Aws::UniquePtr m_impl; +}; + +} +} diff --git a/src/aws-cpp-sdk-core/include/smithy/client/schema/SerdeTraits.h b/src/aws-cpp-sdk-core/include/smithy/client/schema/SerdeTraits.h index 52a348c503f0..a988b8db6903 100644 --- a/src/aws-cpp-sdk-core/include/smithy/client/schema/SerdeTraits.h +++ b/src/aws-cpp-sdk-core/include/smithy/client/schema/SerdeTraits.h @@ -4,6 +4,7 @@ */ #pragma once +#include #include #include @@ -22,7 +23,18 @@ class TimestampFormatTrait : public Trait { Format m_format; }; +class Ec2QueryNameTrait : public Trait { + public: + explicit Ec2QueryNameTrait(const Aws::String& value) : m_value(value) {} + const Aws::String& GetValue() const { return m_value; } + static const TraitKey& KEY() { return TraitKey::Instance(); } + + private: + Aws::String m_value; +}; + extern template class TraitKey; +extern template class TraitKey; -} // namespace schema -} // namespace smithy +} +} diff --git a/src/aws-cpp-sdk-core/include/smithy/client/schema/ShapeDeserializer.h b/src/aws-cpp-sdk-core/include/smithy/client/schema/ShapeDeserializer.h index f28e66a6c3a5..cac26dacf5c7 100644 --- a/src/aws-cpp-sdk-core/include/smithy/client/schema/ShapeDeserializer.h +++ b/src/aws-cpp-sdk-core/include/smithy/client/schema/ShapeDeserializer.h @@ -5,8 +5,10 @@ #include #include #include +#include #include +#include namespace smithy { namespace schema { @@ -15,31 +17,28 @@ class SMITHY_API ShapeDeserializer { public: virtual ~ShapeDeserializer() = default; - virtual Aws::Crt::Optional ReadBoolean() = 0; - virtual Aws::Crt::Optional ReadInteger() = 0; - virtual Aws::Crt::Optional ReadLong() = 0; - virtual Aws::Crt::Optional ReadFloat() = 0; - virtual Aws::Crt::Optional ReadDouble() = 0; - virtual Aws::Crt::Optional ReadString() = 0; - virtual Aws::Crt::Optional ReadTimestamp() = 0; - virtual Aws::Crt::Optional ReadBlob() = 0; - virtual Aws::Crt::Optional ReadEnum() = 0; + using StructMemberConsumer = std::function; - virtual size_t BeginStruct() = 0; - virtual void EndStruct() = 0; + using ListElementConsumer = std::function; - virtual size_t BeginList() = 0; - virtual void EndList() = 0; + using MapEntryConsumer = std::function; - virtual size_t BeginMap() = 0; - virtual void EndMap() = 0; + virtual void ReadStruct(const Schema& schema, const StructMemberConsumer& consumer) = 0; + virtual void ReadList(const Schema& schema, const ListElementConsumer& consumer) = 0; + virtual void ReadMap(const Schema& schema, const MapEntryConsumer& consumer) = 0; + + virtual Aws::Crt::Optional ReadBoolean(const Schema& schema) = 0; + virtual Aws::Crt::Optional ReadInteger(const Schema& schema) = 0; + virtual Aws::Crt::Optional ReadLong(const Schema& schema) = 0; + virtual Aws::Crt::Optional ReadFloat(const Schema& schema) = 0; + virtual Aws::Crt::Optional ReadDouble(const Schema& schema) = 0; + virtual Aws::Crt::Optional ReadString(const Schema& schema) = 0; + virtual Aws::Crt::Optional ReadTimestamp(const Schema& schema) = 0; + virtual Aws::Crt::Optional ReadBlob(const Schema& schema) = 0; + virtual Aws::Crt::Optional ReadEnum(const Schema& schema) = 0; - virtual Aws::Crt::Optional ReadKey() = 0; - virtual bool IsBreak() = 0; virtual bool IsNull() = 0; - virtual void ReadNull() = 0; - virtual void SkipValue() = 0; }; -} // namespace schema -} // namespace smithy +} +} diff --git a/src/aws-cpp-sdk-core/include/smithy/client/schema/XmlShapeDeserializer.h b/src/aws-cpp-sdk-core/include/smithy/client/schema/XmlShapeDeserializer.h new file mode 100644 index 000000000000..3aa84cbec6ee --- /dev/null +++ b/src/aws-cpp-sdk-core/include/smithy/client/schema/XmlShapeDeserializer.h @@ -0,0 +1,37 @@ +#pragma once + +#include +#include +#include + +namespace smithy { +namespace schema { + +class SMITHY_API XmlShapeDeserializer final : public ShapeDeserializer { + public: + XmlShapeDeserializer(const unsigned char* data, size_t length); + ~XmlShapeDeserializer(); + + void ReadStruct(const Schema& schema, const StructMemberConsumer& consumer) override; + void ReadList(const Schema& schema, const ListElementConsumer& consumer) override; + void ReadMap(const Schema& schema, const MapEntryConsumer& consumer) override; + + Aws::Crt::Optional ReadBoolean(const Schema& schema) override; + Aws::Crt::Optional ReadInteger(const Schema& schema) override; + Aws::Crt::Optional ReadLong(const Schema& schema) override; + Aws::Crt::Optional ReadFloat(const Schema& schema) override; + Aws::Crt::Optional ReadDouble(const Schema& schema) override; + Aws::Crt::Optional ReadString(const Schema& schema) override; + Aws::Crt::Optional ReadTimestamp(const Schema& schema) override; + Aws::Crt::Optional ReadBlob(const Schema& schema) override; + Aws::Crt::Optional ReadEnum(const Schema& schema) override; + + bool IsNull() override; + + private: + class Impl; + Aws::UniquePtr m_impl; +}; + +} +} diff --git a/src/aws-cpp-sdk-core/source/smithy/client/schema/CborShapeDeserializer.cpp b/src/aws-cpp-sdk-core/source/smithy/client/schema/CborShapeDeserializer.cpp index dff8c39525c8..124ccb00504e 100644 --- a/src/aws-cpp-sdk-core/source/smithy/client/schema/CborShapeDeserializer.cpp +++ b/src/aws-cpp-sdk-core/source/smithy/client/schema/CborShapeDeserializer.cpp @@ -9,11 +9,69 @@ using namespace smithy::schema; using namespace Aws::Utils; using namespace Aws::Crt::Cbor; -class CborShapeDeserializer::Impl { +class CborShapeDeserializer::Impl final : public ShapeDeserializer { public: Impl(const unsigned char* data, size_t length) : m_decoder(Aws::Crt::ByteCursorFromArray(data, length)) {} - Aws::Crt::Optional ReadBoolean() { + void ReadStruct(const Schema& schema, const StructMemberConsumer& consumer) override { + bool indefinite = false; + size_t count = 0; + if (!OpenMap(indefinite, count)) { + return; + } + for (size_t i = 0; MoreEntries(indefinite, count, i); ++i) { + auto key = ReadText(); + if (!key.has_value()) { + break; + } + const auto member = schema.GetMember(key->c_str()); + if (member.has_value() && *member && !IsNull()) { + consumer(**member, *this); + } else { + + SkipValue(); + } + } + CloseIndefinite(indefinite); + } + + void ReadList(const Schema&, const ListElementConsumer& consumer) override { + bool indefinite = false; + size_t count = 0; + if (!OpenArray(indefinite, count)) { + return; + } + for (size_t i = 0; MoreEntries(indefinite, count, i); ++i) { + if (IsNull()) { + SkipValue(); + continue; + } + consumer(*this); + } + CloseIndefinite(indefinite); + } + + void ReadMap(const Schema&, const MapEntryConsumer& consumer) override { + bool indefinite = false; + size_t count = 0; + if (!OpenMap(indefinite, count)) { + return; + } + for (size_t i = 0; MoreEntries(indefinite, count, i); ++i) { + auto key = ReadText(); + if (!key.has_value()) { + break; + } + if (IsNull()) { + SkipValue(); + continue; + } + consumer(*key, *this); + } + CloseIndefinite(indefinite); + } + + Aws::Crt::Optional ReadBoolean(const Schema&) override { auto val = m_decoder.PopNextBooleanVal(); if (!val.has_value()) { return {}; @@ -21,15 +79,15 @@ class CborShapeDeserializer::Impl { return val.value(); } - Aws::Crt::Optional ReadInteger() { - auto val = ReadLong(); + Aws::Crt::Optional ReadInteger(const Schema& schema) override { + auto val = ReadLong(schema); if (!val.has_value()) { return {}; } return static_cast(val.value()); } - Aws::Crt::Optional ReadLong() { + Aws::Crt::Optional ReadLong(const Schema&) override { auto type = m_decoder.PeekType(); if (!type.has_value()) { return {}; @@ -51,15 +109,15 @@ class CborShapeDeserializer::Impl { return {}; } - Aws::Crt::Optional ReadFloat() { - auto val = ReadDouble(); + Aws::Crt::Optional ReadFloat(const Schema& schema) override { + auto val = ReadDouble(schema); if (!val.has_value()) { return {}; } return static_cast(val.value()); } - Aws::Crt::Optional ReadDouble() { + Aws::Crt::Optional ReadDouble(const Schema& schema) override { auto type = m_decoder.PeekType(); if (!type.has_value()) { return {}; @@ -71,22 +129,16 @@ class CborShapeDeserializer::Impl { } return val.value(); } - auto longVal = ReadLong(); + auto longVal = ReadLong(schema); if (!longVal.has_value()) { return {}; } return static_cast(longVal.value()); } - Aws::Crt::Optional ReadString() { - auto val = m_decoder.PopNextTextVal(); - if (!val.has_value()) { - return {}; - } - return Aws::String(reinterpret_cast(val->ptr), val->len); - } + Aws::Crt::Optional ReadString(const Schema&) override { return ReadText(); } - Aws::Crt::Optional ReadTimestamp() { + Aws::Crt::Optional ReadTimestamp(const Schema& schema) override { auto tag = m_decoder.PopNextTagVal(); if (!tag.has_value() || tag.value() != 1) { return {}; @@ -105,14 +157,14 @@ class CborShapeDeserializer::Impl { if (*type == CborType::NegInt) { return {}; } - auto val = ReadLong(); + auto val = ReadLong(schema); if (!val.has_value()) { return {}; } return DateTime(static_cast(val.value())); } - Aws::Crt::Optional ReadBlob() { + Aws::Crt::Optional ReadBlob(const Schema&) override { auto val = m_decoder.PopNextBytesVal(); if (!val.has_value()) { return {}; @@ -120,70 +172,79 @@ class CborShapeDeserializer::Impl { return ByteBuffer(val->ptr, val->len); } - Aws::Crt::Optional ReadEnum() { return ReadInteger(); } + Aws::Crt::Optional ReadEnum(const Schema& schema) override { return ReadInteger(schema); } - size_t BeginStruct() { + bool IsNull() override { auto type = m_decoder.PeekType(); - if (type.has_value() && *type == CborType::IndefMapStart) { - m_decoder.ConsumeNextSingleElement(); - return 0; - } - auto size = m_decoder.PopNextMapStart(); - if (!size.has_value()) { - return 0; - } - return static_cast(size.value()); + return type.has_value() && *type == CborType::Null; } - void EndStruct() {} + private: - size_t BeginList() { + bool OpenMap(bool& indefinite, size_t& count) { auto type = m_decoder.PeekType(); - if (type.has_value() && *type == CborType::IndefArrayStart) { + if (!type.has_value()) { + return false; + } + if (*type == CborType::IndefMapStart) { m_decoder.ConsumeNextSingleElement(); - return 0; + indefinite = true; + count = 0; + return true; } - auto size = m_decoder.PopNextArrayStart(); + auto size = m_decoder.PopNextMapStart(); if (!size.has_value()) { - return 0; + return false; } - return static_cast(size.value()); + indefinite = false; + count = static_cast(size.value()); + return true; } - void EndList() {} - - size_t BeginMap() { + bool OpenArray(bool& indefinite, size_t& count) { auto type = m_decoder.PeekType(); - if (type.has_value() && *type == CborType::IndefMapStart) { + if (!type.has_value()) { + return false; + } + if (*type == CborType::IndefArrayStart) { m_decoder.ConsumeNextSingleElement(); - return 0; + indefinite = true; + count = 0; + return true; } - auto size = m_decoder.PopNextMapStart(); + auto size = m_decoder.PopNextArrayStart(); if (!size.has_value()) { - return 0; + return false; } - return static_cast(size.value()); + indefinite = false; + count = static_cast(size.value()); + return true; } - void EndMap() {} - - Aws::Crt::Optional ReadKey() { return ReadString(); } - - bool IsBreak() { - auto type = m_decoder.PeekType(); - return type.has_value() && *type == CborType::Break; + bool MoreEntries(bool indefinite, size_t count, size_t index) { + if (indefinite) { + auto type = m_decoder.PeekType(); + return type.has_value() && *type != CborType::Break; + } + return index < count; } - bool IsNull() { - auto type = m_decoder.PeekType(); - return type.has_value() && *type == CborType::Null; + void CloseIndefinite(bool indefinite) { + if (indefinite) { + m_decoder.ConsumeNextSingleElement(); + } } - void ReadNull() { m_decoder.ConsumeNextSingleElement(); } + Aws::Crt::Optional ReadText() { + auto val = m_decoder.PopNextTextVal(); + if (!val.has_value()) { + return {}; + } + return Aws::String(reinterpret_cast(val->ptr), val->len); + } void SkipValue() { m_decoder.ConsumeNextWholeDataItem(); } - private: CborDecoder m_decoder; }; @@ -191,23 +252,20 @@ CborShapeDeserializer::CborShapeDeserializer(const unsigned char* data, size_t l : m_impl(Aws::MakeUnique("CborShapeDeserializer", data, length)) {} CborShapeDeserializer::~CborShapeDeserializer() = default; -Aws::Crt::Optional CborShapeDeserializer::ReadBoolean() { return m_impl->ReadBoolean(); } -Aws::Crt::Optional CborShapeDeserializer::ReadInteger() { return m_impl->ReadInteger(); } -Aws::Crt::Optional CborShapeDeserializer::ReadLong() { return m_impl->ReadLong(); } -Aws::Crt::Optional CborShapeDeserializer::ReadFloat() { return m_impl->ReadFloat(); } -Aws::Crt::Optional CborShapeDeserializer::ReadDouble() { return m_impl->ReadDouble(); } -Aws::Crt::Optional CborShapeDeserializer::ReadString() { return m_impl->ReadString(); } -Aws::Crt::Optional CborShapeDeserializer::ReadTimestamp() { return m_impl->ReadTimestamp(); } -Aws::Crt::Optional CborShapeDeserializer::ReadBlob() { return m_impl->ReadBlob(); } -Aws::Crt::Optional CborShapeDeserializer::ReadEnum() { return m_impl->ReadEnum(); } -size_t CborShapeDeserializer::BeginStruct() { return m_impl->BeginStruct(); } -void CborShapeDeserializer::EndStruct() { m_impl->EndStruct(); } -size_t CborShapeDeserializer::BeginList() { return m_impl->BeginList(); } -void CborShapeDeserializer::EndList() { m_impl->EndList(); } -size_t CborShapeDeserializer::BeginMap() { return m_impl->BeginMap(); } -void CborShapeDeserializer::EndMap() { m_impl->EndMap(); } -Aws::Crt::Optional CborShapeDeserializer::ReadKey() { return m_impl->ReadKey(); } -bool CborShapeDeserializer::IsBreak() { return m_impl->IsBreak(); } +void CborShapeDeserializer::ReadStruct(const Schema& schema, const StructMemberConsumer& consumer) { + m_impl->ReadStruct(schema, consumer); +} +void CborShapeDeserializer::ReadList(const Schema& schema, const ListElementConsumer& consumer) { + m_impl->ReadList(schema, consumer); +} +void CborShapeDeserializer::ReadMap(const Schema& schema, const MapEntryConsumer& consumer) { m_impl->ReadMap(schema, consumer); } +Aws::Crt::Optional CborShapeDeserializer::ReadBoolean(const Schema& schema) { return m_impl->ReadBoolean(schema); } +Aws::Crt::Optional CborShapeDeserializer::ReadInteger(const Schema& schema) { return m_impl->ReadInteger(schema); } +Aws::Crt::Optional CborShapeDeserializer::ReadLong(const Schema& schema) { return m_impl->ReadLong(schema); } +Aws::Crt::Optional CborShapeDeserializer::ReadFloat(const Schema& schema) { return m_impl->ReadFloat(schema); } +Aws::Crt::Optional CborShapeDeserializer::ReadDouble(const Schema& schema) { return m_impl->ReadDouble(schema); } +Aws::Crt::Optional CborShapeDeserializer::ReadString(const Schema& schema) { return m_impl->ReadString(schema); } +Aws::Crt::Optional CborShapeDeserializer::ReadTimestamp(const Schema& schema) { return m_impl->ReadTimestamp(schema); } +Aws::Crt::Optional CborShapeDeserializer::ReadBlob(const Schema& schema) { return m_impl->ReadBlob(schema); } +Aws::Crt::Optional CborShapeDeserializer::ReadEnum(const Schema& schema) { return m_impl->ReadEnum(schema); } bool CborShapeDeserializer::IsNull() { return m_impl->IsNull(); } -void CborShapeDeserializer::ReadNull() { m_impl->ReadNull(); } -void CborShapeDeserializer::SkipValue() { m_impl->SkipValue(); } diff --git a/src/aws-cpp-sdk-core/source/smithy/client/schema/ClientProtocol.cpp b/src/aws-cpp-sdk-core/source/smithy/client/schema/ClientProtocol.cpp new file mode 100644 index 000000000000..2a7e911827c3 --- /dev/null +++ b/src/aws-cpp-sdk-core/source/smithy/client/schema/ClientProtocol.cpp @@ -0,0 +1,85 @@ +/** + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0. + */ +#include + +#include +#include +#include + +using namespace smithy::schema; + +namespace { +constexpr char ALLOC_TAG[] = "SmithyClientProtocol"; + +ClientProtocol::SerializerOutcome SerializeQuery(const Schema& schema, const SerializableStruct& input, + QueryShapeSerializer::Flavor flavor) { + QueryShapeSerializer serializer(flavor); + serializer.WriteStruct(schema, input); + return serializer.GetPayload(); +} +} + +Aws::String RestJsonProtocol::GetProtocolId() const { return "aws.protocols#restJson1"; } +Aws::String RestJsonProtocol::GetContentType() const { return "application/json"; } +ClientProtocol::SerializerOutcome RestJsonProtocol::SerializeInput(const Schema& schema, const SerializableStruct& input) const { + return m_codec.Serialize(schema, input); +} +Aws::UniquePtr RestJsonProtocol::CreateOutputDeserializer(const unsigned char* data, size_t length) const { + return m_codec.CreateDeserializer(data, length); +} + +Aws::String AwsJson10Protocol::GetProtocolId() const { return "aws.protocols#awsJson1_0"; } +Aws::String AwsJson10Protocol::GetContentType() const { return "application/x-amz-json-1.0"; } +ClientProtocol::SerializerOutcome AwsJson10Protocol::SerializeInput(const Schema& schema, const SerializableStruct& input) const { + return m_codec.Serialize(schema, input); +} +Aws::UniquePtr AwsJson10Protocol::CreateOutputDeserializer(const unsigned char* data, size_t length) const { + return m_codec.CreateDeserializer(data, length); +} + +Aws::String AwsJson11Protocol::GetProtocolId() const { return "aws.protocols#awsJson1_1"; } +Aws::String AwsJson11Protocol::GetContentType() const { return "application/x-amz-json-1.1"; } +ClientProtocol::SerializerOutcome AwsJson11Protocol::SerializeInput(const Schema& schema, const SerializableStruct& input) const { + return m_codec.Serialize(schema, input); +} +Aws::UniquePtr AwsJson11Protocol::CreateOutputDeserializer(const unsigned char* data, size_t length) const { + return m_codec.CreateDeserializer(data, length); +} + +Aws::String RpcV2CborProtocol::GetProtocolId() const { return "smithy.protocols#rpcv2Cbor"; } +Aws::String RpcV2CborProtocol::GetContentType() const { return "application/cbor"; } +ClientProtocol::SerializerOutcome RpcV2CborProtocol::SerializeInput(const Schema& schema, const SerializableStruct& input) const { + return m_codec.Serialize(schema, input); +} +Aws::UniquePtr RpcV2CborProtocol::CreateOutputDeserializer(const unsigned char* data, size_t length) const { + return m_codec.CreateDeserializer(data, length); +} + +Aws::String RestXmlProtocol::GetProtocolId() const { return "aws.protocols#restXml"; } +Aws::String RestXmlProtocol::GetContentType() const { return "application/xml"; } +ClientProtocol::SerializerOutcome RestXmlProtocol::SerializeInput(const Schema& schema, const SerializableStruct& input) const { + return m_codec.Serialize(schema, input); +} +Aws::UniquePtr RestXmlProtocol::CreateOutputDeserializer(const unsigned char* data, size_t length) const { + return m_codec.CreateDeserializer(data, length); +} + +Aws::String AwsQueryProtocol::GetProtocolId() const { return "aws.protocols#awsQuery"; } +Aws::String AwsQueryProtocol::GetContentType() const { return "application/x-www-form-urlencoded"; } +ClientProtocol::SerializerOutcome AwsQueryProtocol::SerializeInput(const Schema& schema, const SerializableStruct& input) const { + return SerializeQuery(schema, input, QueryShapeSerializer::Flavor::AwsQuery); +} +Aws::UniquePtr AwsQueryProtocol::CreateOutputDeserializer(const unsigned char* data, size_t length) const { + return Aws::MakeUnique(ALLOC_TAG, data, length); +} + +Aws::String Ec2QueryProtocol::GetProtocolId() const { return "aws.protocols#ec2Query"; } +Aws::String Ec2QueryProtocol::GetContentType() const { return "application/x-www-form-urlencoded"; } +ClientProtocol::SerializerOutcome Ec2QueryProtocol::SerializeInput(const Schema& schema, const SerializableStruct& input) const { + return SerializeQuery(schema, input, QueryShapeSerializer::Flavor::Ec2Query); +} +Aws::UniquePtr Ec2QueryProtocol::CreateOutputDeserializer(const unsigned char* data, size_t length) const { + return Aws::MakeUnique(ALLOC_TAG, data, length); +} diff --git a/src/aws-cpp-sdk-core/source/smithy/client/schema/Codec.cpp b/src/aws-cpp-sdk-core/source/smithy/client/schema/Codec.cpp new file mode 100644 index 000000000000..ef9b5705246a --- /dev/null +++ b/src/aws-cpp-sdk-core/source/smithy/client/schema/Codec.cpp @@ -0,0 +1,45 @@ +/** + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0. + */ +#include + +#include +#include +#include +#include +#include +#include + +using namespace smithy::schema; + +namespace { +constexpr char ALLOC_TAG[] = "SmithyCodec"; +} + +Codec::SerializerOutcome JsonCodec::Serialize(const Schema& schema, const SerializableStruct& shape) const { + JsonShapeSerializer serializer; + serializer.WriteStruct(schema, shape); + return serializer.GetPayload(); +} +Aws::UniquePtr JsonCodec::CreateDeserializer(const unsigned char* data, size_t length) const { + return Aws::MakeUnique(ALLOC_TAG, data, length); +} + +Codec::SerializerOutcome XmlCodec::Serialize(const Schema& schema, const SerializableStruct& shape) const { + XmlShapeSerializer serializer; + serializer.WriteStruct(schema, shape); + return serializer.GetPayload(); +} +Aws::UniquePtr XmlCodec::CreateDeserializer(const unsigned char* data, size_t length) const { + return Aws::MakeUnique(ALLOC_TAG, data, length); +} + +Codec::SerializerOutcome CborCodec::Serialize(const Schema& schema, const SerializableStruct& shape) const { + CborShapeSerializer serializer; + serializer.WriteStruct(schema, shape); + return serializer.GetPayload(); +} +Aws::UniquePtr CborCodec::CreateDeserializer(const unsigned char* data, size_t length) const { + return Aws::MakeUnique(ALLOC_TAG, data, length); +} diff --git a/src/aws-cpp-sdk-core/source/smithy/client/schema/JsonShapeDeserializer.cpp b/src/aws-cpp-sdk-core/source/smithy/client/schema/JsonShapeDeserializer.cpp new file mode 100644 index 000000000000..d90e0d50f466 --- /dev/null +++ b/src/aws-cpp-sdk-core/source/smithy/client/schema/JsonShapeDeserializer.cpp @@ -0,0 +1,483 @@ +/** + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0. + */ +#include +#include + +#include +#include +#include + +using namespace smithy::schema; +using namespace Aws::Utils; + +class JsonShapeDeserializer::Impl final : public ShapeDeserializer { + public: + Impl(const unsigned char* data, size_t length) : m_bytes(reinterpret_cast(data), length), m_pos(0) {} + + void ReadStruct(const Schema& schema, const StructMemberConsumer& consumer) override { + if (PeekNonWs() != '{') { + SkipValue(); + return; + } + ++m_pos; + if (PeekNonWs() == '}') { + ++m_pos; + return; + } + while (true) { + auto key = ParseString(); + if (!key.has_value()) { + return; + } + if (PeekNonWs() == ':') { + ++m_pos; + } + PeekNonWs(); + const auto member = ResolveMember(schema, *key); + if (member.has_value() && *member && !IsNull()) { + ConsumeOne([&] { consumer(**member, *this); }); + } else { + SkipValue(); + } + if (!NextInContainer('}')) { + break; + } + } + } + + void ReadList(const Schema&, const ListElementConsumer& consumer) override { + if (PeekNonWs() != '[') { + SkipValue(); + return; + } + ++m_pos; + if (PeekNonWs() == ']') { + ++m_pos; + return; + } + while (true) { + PeekNonWs(); + if (IsNull()) { + SkipValue(); + } else { + ConsumeOne([&] { consumer(*this); }); + } + if (!NextInContainer(']')) { + break; + } + } + } + + void ReadMap(const Schema&, const MapEntryConsumer& consumer) override { + if (PeekNonWs() != '{') { + SkipValue(); + return; + } + ++m_pos; + if (PeekNonWs() == '}') { + ++m_pos; + return; + } + while (true) { + auto key = ParseString(); + if (!key.has_value()) { + return; + } + if (PeekNonWs() == ':') { + ++m_pos; + } + PeekNonWs(); + if (IsNull()) { + SkipValue(); + } else { + ConsumeOne([&] { consumer(*key, *this); }); + } + if (!NextInContainer('}')) { + break; + } + } + } + + Aws::Crt::Optional ReadBoolean(const Schema&) override { + if (Match("true")) { + return true; + } + if (Match("false")) { + return false; + } + SkipValue(); + return {}; + } + + Aws::Crt::Optional ReadInteger(const Schema& schema) override { + auto val = ReadLong(schema); + if (!val.has_value()) { + return {}; + } + return static_cast(val.value()); + } + + Aws::Crt::Optional ReadLong(const Schema&) override { + if (!IsNumberStart(PeekNonWs())) { + SkipValue(); + return {}; + } + const Aws::String token = ReadNumberToken(); + char* end = nullptr; + const long long value = std::strtoll(token.c_str(), &end, 10); + if (end != token.c_str() + token.size()) { + + char* dend = nullptr; + const double d = std::strtod(token.c_str(), &dend); + if (dend != token.c_str() + token.size()) { + return {}; + } + return static_cast(d); + } + return static_cast(value); + } + + Aws::Crt::Optional ReadFloat(const Schema& schema) override { + auto val = ReadDouble(schema); + if (!val.has_value()) { + return {}; + } + return static_cast(val.value()); + } + + Aws::Crt::Optional ReadDouble(const Schema&) override { + const char c = PeekNonWs(); + if (c == '"') { + + auto token = ParseString(); + if (!token.has_value()) { + return {}; + } + if (*token == "NaN") { + return std::numeric_limits::quiet_NaN(); + } + if (*token == "Infinity") { + return std::numeric_limits::infinity(); + } + if (*token == "-Infinity") { + return -std::numeric_limits::infinity(); + } + return {}; + } + if (!IsNumberStart(c)) { + SkipValue(); + return {}; + } + const Aws::String token = ReadNumberToken(); + char* end = nullptr; + const double value = std::strtod(token.c_str(), &end); + if (end != token.c_str() + token.size()) { + return {}; + } + return value; + } + + Aws::Crt::Optional ReadString(const Schema&) override { + if (PeekNonWs() != '"') { + SkipValue(); + return {}; + } + return ParseString(); + } + + Aws::Crt::Optional ReadTimestamp(const Schema&) override { + const char c = PeekNonWs(); + if (IsNumberStart(c)) { + const Aws::String token = ReadNumberToken(); + char* end = nullptr; + const double seconds = std::strtod(token.c_str(), &end); + if (end != token.c_str() + token.size()) { + return {}; + } + return DateTime(seconds); + } + if (c == '"') { + auto token = ParseString(); + if (!token.has_value()) { + return {}; + } + DateTime parsed(*token, DateFormat::ISO_8601); + if (!parsed.WasParseSuccessful()) { + return {}; + } + return parsed; + } + SkipValue(); + return {}; + } + + Aws::Crt::Optional ReadBlob(const Schema&) override { + if (PeekNonWs() != '"') { + SkipValue(); + return {}; + } + auto encoded = ParseString(); + if (!encoded.has_value()) { + return {}; + } + return HashingUtils::Base64Decode(*encoded); + } + + Aws::Crt::Optional ReadEnum(const Schema& schema) override { return ReadInteger(schema); } + + bool IsNull() override { + size_t p = m_pos; + while (p < m_bytes.size() && IsWs(m_bytes[p])) { + ++p; + } + return p + 4 <= m_bytes.size() && std::memcmp(m_bytes.c_str() + p, "null", 4) == 0; + } + + private: + static Aws::String JsonName(const Schema& member) { + const auto trait = member.GetTrait(JsonNameTrait::KEY()); + return trait ? trait->GetValue() : member.GetMemberName(); + } + Aws::Crt::Optional> ResolveMember(const Schema& schema, const Aws::String& name) { + for (uint16_t i = 0; i < schema.GetMemberCount(); ++i) { + const auto member = schema.GetMember(static_cast(i)); + if (member.has_value() && *member && JsonName(**member) == name) { + return member; + } + } + return {}; + } + + template + void ConsumeOne(const Fn& fn) { + const size_t before = m_pos; + fn(); + if (m_pos == before) { + SkipValue(); + } + } + + bool NextInContainer(char close) { + const char c = PeekNonWs(); + if (c == ',') { + ++m_pos; + return true; + } + if (c == close) { + ++m_pos; + } + return false; + } + + static bool IsWs(char c) { return c == ' ' || c == '\t' || c == '\n' || c == '\r'; } + static bool IsNumberStart(char c) { return c == '-' || (c >= '0' && c <= '9'); } + char Peek() const { return m_pos < m_bytes.size() ? m_bytes[m_pos] : '\0'; } + void SkipWs() { + while (m_pos < m_bytes.size() && IsWs(m_bytes[m_pos])) { + ++m_pos; + } + } + char PeekNonWs() { + SkipWs(); + return Peek(); + } + bool Match(const char* literal) { + SkipWs(); + const size_t n = std::strlen(literal); + if (m_pos + n <= m_bytes.size() && std::memcmp(m_bytes.c_str() + m_pos, literal, n) == 0) { + m_pos += n; + return true; + } + return false; + } + Aws::String ReadNumberToken() { + SkipWs(); + const size_t start = m_pos; + while (m_pos < m_bytes.size()) { + const char c = m_bytes[m_pos]; + if ((c >= '0' && c <= '9') || c == '-' || c == '+' || c == '.' || c == 'e' || c == 'E') { + ++m_pos; + } else { + break; + } + } + return Aws::String(m_bytes.c_str() + start, m_pos - start); + } + + Aws::Crt::Optional ParseString() { + if (PeekNonWs() != '"') { + return {}; + } + ++m_pos; + Aws::String out; + while (m_pos < m_bytes.size()) { + const char ch = m_bytes[m_pos++]; + if (ch == '"') { + return out; + } + if (ch != '\\') { + out += ch; + continue; + } + if (m_pos >= m_bytes.size()) { + break; + } + const char esc = m_bytes[m_pos++]; + switch (esc) { + case '"': + out += '"'; + break; + case '\\': + out += '\\'; + break; + case '/': + out += '/'; + break; + case 'b': + out += '\b'; + break; + case 'f': + out += '\f'; + break; + case 'n': + out += '\n'; + break; + case 'r': + out += '\r'; + break; + case 't': + out += '\t'; + break; + case 'u': + AppendUnicodeEscape(out); + break; + default: + out += esc; + break; + } + } + return {}; + } + + void SkipValue() { + const char c = PeekNonWs(); + if (c == '"') { + ParseString(); + return; + } + if (c == '{' || c == '[') { + SkipContainer(); + return; + } + while (m_pos < m_bytes.size()) { + const char ch = m_bytes[m_pos]; + if (ch == ',' || ch == '}' || ch == ']' || IsWs(ch)) { + break; + } + ++m_pos; + } + } + + void SkipContainer() { + int depth = 0; + while (m_pos < m_bytes.size()) { + const char ch = m_bytes[m_pos]; + if (ch == '"') { + ParseString(); + continue; + } + if (ch == '{' || ch == '[') { + ++depth; + ++m_pos; + } else if (ch == '}' || ch == ']') { + --depth; + ++m_pos; + if (depth == 0) { + return; + } + } else { + ++m_pos; + } + } + } + + void AppendUnicodeEscape(Aws::String& out) { + uint32_t cp = 0; + if (!ReadHex4(cp)) { + return; + } + if (cp >= 0xD800 && cp <= 0xDBFF && m_pos + 1 < m_bytes.size() && m_bytes[m_pos] == '\\' && m_bytes[m_pos + 1] == 'u') { + m_pos += 2; + uint32_t low = 0; + if (ReadHex4(low) && low >= 0xDC00 && low <= 0xDFFF) { + cp = 0x10000 + ((cp - 0xD800) << 10) + (low - 0xDC00); + } + } + AppendUtf8(out, cp); + } + bool ReadHex4(uint32_t& out) { + if (m_pos + 4 > m_bytes.size()) { + return false; + } + uint32_t value = 0; + for (int i = 0; i < 4; ++i) { + const char c = m_bytes[m_pos++]; + value <<= 4; + if (c >= '0' && c <= '9') { + value |= static_cast(c - '0'); + } else if (c >= 'a' && c <= 'f') { + value |= static_cast(c - 'a' + 10); + } else if (c >= 'A' && c <= 'F') { + value |= static_cast(c - 'A' + 10); + } else { + return false; + } + } + out = value; + return true; + } + static void AppendUtf8(Aws::String& out, uint32_t cp) { + if (cp <= 0x7F) { + out += static_cast(cp); + } else if (cp <= 0x7FF) { + out += static_cast(0xC0 | (cp >> 6)); + out += static_cast(0x80 | (cp & 0x3F)); + } else if (cp <= 0xFFFF) { + out += static_cast(0xE0 | (cp >> 12)); + out += static_cast(0x80 | ((cp >> 6) & 0x3F)); + out += static_cast(0x80 | (cp & 0x3F)); + } else { + out += static_cast(0xF0 | (cp >> 18)); + out += static_cast(0x80 | ((cp >> 12) & 0x3F)); + out += static_cast(0x80 | ((cp >> 6) & 0x3F)); + out += static_cast(0x80 | (cp & 0x3F)); + } + } + + Aws::String m_bytes; + size_t m_pos; +}; + +JsonShapeDeserializer::JsonShapeDeserializer(const unsigned char* data, size_t length) + : m_impl(Aws::MakeUnique("JsonShapeDeserializer", data, length)) {} +JsonShapeDeserializer::~JsonShapeDeserializer() = default; + +void JsonShapeDeserializer::ReadStruct(const Schema& schema, const StructMemberConsumer& consumer) { + m_impl->ReadStruct(schema, consumer); +} +void JsonShapeDeserializer::ReadList(const Schema& schema, const ListElementConsumer& consumer) { + m_impl->ReadList(schema, consumer); +} +void JsonShapeDeserializer::ReadMap(const Schema& schema, const MapEntryConsumer& consumer) { m_impl->ReadMap(schema, consumer); } +Aws::Crt::Optional JsonShapeDeserializer::ReadBoolean(const Schema& schema) { return m_impl->ReadBoolean(schema); } +Aws::Crt::Optional JsonShapeDeserializer::ReadInteger(const Schema& schema) { return m_impl->ReadInteger(schema); } +Aws::Crt::Optional JsonShapeDeserializer::ReadLong(const Schema& schema) { return m_impl->ReadLong(schema); } +Aws::Crt::Optional JsonShapeDeserializer::ReadFloat(const Schema& schema) { return m_impl->ReadFloat(schema); } +Aws::Crt::Optional JsonShapeDeserializer::ReadDouble(const Schema& schema) { return m_impl->ReadDouble(schema); } +Aws::Crt::Optional JsonShapeDeserializer::ReadString(const Schema& schema) { return m_impl->ReadString(schema); } +Aws::Crt::Optional JsonShapeDeserializer::ReadTimestamp(const Schema& schema) { return m_impl->ReadTimestamp(schema); } +Aws::Crt::Optional JsonShapeDeserializer::ReadBlob(const Schema& schema) { return m_impl->ReadBlob(schema); } +Aws::Crt::Optional JsonShapeDeserializer::ReadEnum(const Schema& schema) { return m_impl->ReadEnum(schema); } +bool JsonShapeDeserializer::IsNull() { return m_impl->IsNull(); } 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 index 1a7d6d8911fe..597c075f6590 100644 --- a/src/aws-cpp-sdk-core/source/smithy/client/schema/JsonShapeSerializer.cpp +++ b/src/aws-cpp-sdk-core/source/smithy/client/schema/JsonShapeSerializer.cpp @@ -2,6 +2,8 @@ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. * SPDX-License-Identifier: Apache-2.0. */ +#include + #include #include #include @@ -54,8 +56,8 @@ class JsonShapeSerializer::Impl final : public ShapeSerializer { void WriteBoolean(const Schema&, bool value) override { m_buf += value ? "true" : "false"; } void WriteInteger(const Schema&, int value) override { m_buf += StringUtils::to_string(value); } void WriteLong(const Schema&, int64_t value) override { m_buf += StringUtils::to_string(value); } - void WriteFloat(const Schema&, float value) override { m_buf += StringUtils::to_string(value); } - void WriteDouble(const Schema&, double value) override { m_buf += StringUtils::to_string(value); } + void WriteFloat(const Schema&, float value) override { WriteFloatingPoint(value); } + void WriteDouble(const Schema&, double value) override { WriteFloatingPoint(value); } void WriteString(const Schema&, const Aws::String& value) override { Aws::Schema::WriteQuotedJsonString(m_buf, value); } void WriteTimestamp(const Schema&, const DateTime& value) override { m_buf += StringUtils::to_string(value.SecondsWithMSPrecision()); } void WriteBlob(const Schema&, const ByteBuffer& value) override { @@ -66,6 +68,17 @@ class JsonShapeSerializer::Impl final : public ShapeSerializer { void WriteEnum(const Schema& schema, int value) override { WriteInteger(schema, value); } void WriteNull(const Schema&) override { m_buf += "null"; } + // Non-finite floats have no JSON number form; Smithy encodes them as quoted strings. + void WriteFloatingPoint(double value) { + if (std::isfinite(value)) { + m_buf += StringUtils::to_string(value); + } else if (std::isnan(value)) { + m_buf += "\"NaN\""; + } else { + m_buf += (value > 0 ? "\"Infinity\"" : "\"-Infinity\""); + } + } + void WriteCommaIfNeeded() { if (m_needsComma[m_depth]) { m_buf += ','; diff --git a/src/aws-cpp-sdk-core/source/smithy/client/schema/QueryShapeSerializer.cpp b/src/aws-cpp-sdk-core/source/smithy/client/schema/QueryShapeSerializer.cpp new file mode 100644 index 000000000000..512973710df8 --- /dev/null +++ b/src/aws-cpp-sdk-core/source/smithy/client/schema/QueryShapeSerializer.cpp @@ -0,0 +1,367 @@ +/** + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0. + */ +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "aws/core/client/AWSClient.h" +#include "aws/core/utils/Outcome.h" + +using namespace smithy::schema; +using namespace Aws::Utils; +using SerializerOutcome = Aws::Utils::Outcome>; + +static constexpr int MAX_DEPTH = 500; + +class QueryShapeSerializer::Impl final : public ShapeSerializer { + public: + explicit Impl(bool ec2) : m_ec2(ec2) { m_buf.reserve(1024); } + + void WriteStruct(const Schema&, const SerializableStruct& value) override { + if (!EnterContainer()) { + return; + } + StructContext ctx(this, m_prefix); + value.SerializeMembers(ctx); + ExitContainer(); + } + + void WriteList(const Schema& schema, size_t, const std::function& consumer) override { + if (!EnterContainer()) { + return; + } + + const bool flattened = m_ec2 || IsFlattened(schema); + ListContext ctx(this, m_prefix, flattened, GetListItemName(schema)); + consumer(ctx); + ExitContainer(); + } + + void WriteMap(const Schema& schema, size_t, const std::function& consumer) override { + if (!EnterContainer()) { + return; + } + MapContext ctx(this, m_prefix, IsFlattened(schema), GetMapEntryName(schema), GetMapKeyName(schema), GetMapValueName(schema)); + consumer(ctx); + ExitContainer(); + } + + void WriteBoolean(const Schema&, bool value) override { EmitField(value ? "true" : "false"); } + void WriteInteger(const Schema&, int value) override { EmitField(StringUtils::to_string(value)); } + void WriteLong(const Schema&, int64_t value) override { EmitField(StringUtils::to_string(value)); } + void WriteFloat(const Schema&, float value) override { EmitField(FormatFloatingPoint(value)); } + void WriteDouble(const Schema&, double value) override { EmitField(FormatFloatingPoint(value)); } + void WriteString(const Schema&, const Aws::String& value) override { EmitField(StringUtils::URLEncode(value.c_str())); } + void WriteTimestamp(const Schema&, const DateTime& value) override { + + EmitField(StringUtils::URLEncode(value.ToGmtString(Aws::Utils::DateFormat::ISO_8601).c_str())); + } + void WriteBlob(const Schema&, const ByteBuffer& value) override { + EmitField(StringUtils::URLEncode(HashingUtils::Base64Encode(value).c_str())); + } + void WriteEnum(const Schema& schema, int value) override { WriteInteger(schema, value); } + + void WriteNull(const Schema&) override {} + + 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: + + class StructContext final : public ShapeSerializer { + public: + StructContext(Impl* outer, Aws::String base) : m_outer(outer), m_base(std::move(base)) {} + + void WriteStruct(const Schema& s, const SerializableStruct& v) override { + m_outer->m_prefix = ChildKey(s); + m_outer->WriteStruct(s, v); + } + void WriteList(const Schema& s, size_t n, const std::function& c) override { + m_outer->m_prefix = ChildKey(s); + m_outer->WriteList(s, n, c); + } + void WriteMap(const Schema& s, size_t n, const std::function& c) override { + m_outer->m_prefix = ChildKey(s); + m_outer->WriteMap(s, n, c); + } + void WriteBoolean(const Schema& s, bool v) override { + m_outer->m_prefix = ChildKey(s); + m_outer->WriteBoolean(s, v); + } + void WriteInteger(const Schema& s, int v) override { + m_outer->m_prefix = ChildKey(s); + m_outer->WriteInteger(s, v); + } + void WriteLong(const Schema& s, int64_t v) override { + m_outer->m_prefix = ChildKey(s); + m_outer->WriteLong(s, v); + } + void WriteFloat(const Schema& s, float v) override { + m_outer->m_prefix = ChildKey(s); + m_outer->WriteFloat(s, v); + } + void WriteDouble(const Schema& s, double v) override { + m_outer->m_prefix = ChildKey(s); + m_outer->WriteDouble(s, v); + } + void WriteString(const Schema& s, const Aws::String& v) override { + m_outer->m_prefix = ChildKey(s); + m_outer->WriteString(s, v); + } + void WriteTimestamp(const Schema& s, const DateTime& v) override { + m_outer->m_prefix = ChildKey(s); + m_outer->WriteTimestamp(s, v); + } + void WriteBlob(const Schema& s, const ByteBuffer& v) override { + m_outer->m_prefix = ChildKey(s); + m_outer->WriteBlob(s, v); + } + void WriteEnum(const Schema& s, int v) override { + m_outer->m_prefix = ChildKey(s); + m_outer->WriteEnum(s, v); + } + void WriteNull(const Schema&) override {} + + private: + Aws::String ChildKey(const Schema& s) const { return Impl::JoinKey(m_base, Impl::ResolveName(s, m_outer->m_ec2)); } + + Impl* m_outer; + Aws::String m_base; + }; + + class ListContext final : public ShapeSerializer { + public: + ListContext(Impl* outer, Aws::String base, bool flattened, Aws::String itemName) + : m_outer(outer), m_base(std::move(base)), m_flattened(flattened), m_itemName(std::move(itemName)) {} + + void WriteStruct(const Schema& s, const SerializableStruct& v) override { + m_outer->m_prefix = ItemKey(); + m_outer->WriteStruct(s, v); + } + void WriteList(const Schema& s, size_t n, const std::function& c) override { + m_outer->m_prefix = ItemKey(); + m_outer->WriteList(s, n, c); + } + void WriteMap(const Schema& s, size_t n, const std::function& c) override { + m_outer->m_prefix = ItemKey(); + m_outer->WriteMap(s, n, c); + } + void WriteBoolean(const Schema& s, bool v) override { + m_outer->m_prefix = ItemKey(); + m_outer->WriteBoolean(s, v); + } + void WriteInteger(const Schema& s, int v) override { + m_outer->m_prefix = ItemKey(); + m_outer->WriteInteger(s, v); + } + void WriteLong(const Schema& s, int64_t v) override { + m_outer->m_prefix = ItemKey(); + m_outer->WriteLong(s, v); + } + void WriteFloat(const Schema& s, float v) override { + m_outer->m_prefix = ItemKey(); + m_outer->WriteFloat(s, v); + } + void WriteDouble(const Schema& s, double v) override { + m_outer->m_prefix = ItemKey(); + m_outer->WriteDouble(s, v); + } + void WriteString(const Schema& s, const Aws::String& v) override { + m_outer->m_prefix = ItemKey(); + m_outer->WriteString(s, v); + } + void WriteTimestamp(const Schema& s, const DateTime& v) override { + m_outer->m_prefix = ItemKey(); + m_outer->WriteTimestamp(s, v); + } + void WriteBlob(const Schema& s, const ByteBuffer& v) override { + m_outer->m_prefix = ItemKey(); + m_outer->WriteBlob(s, v); + } + void WriteEnum(const Schema& s, int v) override { + m_outer->m_prefix = ItemKey(); + m_outer->WriteEnum(s, v); + } + + void WriteNull(const Schema&) override { ++m_index; } + + private: + Aws::String ItemKey() { + Aws::String key = m_base; + if (!m_flattened) { + key += '.'; + key += m_itemName; + } + key += '.'; + key += StringUtils::to_string(++m_index); + return key; + } + + Impl* m_outer; + Aws::String m_base; + bool m_flattened; + Aws::String m_itemName; + int m_index = 0; + }; + + class MapContext final : public MapSerializer { + public: + MapContext(Impl* outer, Aws::String base, bool flattened, Aws::String entryName, Aws::String keyName, Aws::String valueName) + : m_outer(outer), + m_base(std::move(base)), + m_flattened(flattened), + m_entryName(std::move(entryName)), + m_keyName(std::move(keyName)), + m_valueName(std::move(valueName)) {} + + void WriteEntry(const Aws::String& key, const std::function& value) override { + Aws::String entryKey = m_base; + if (!m_flattened) { + entryKey += '.'; + entryKey += m_entryName; + } + entryKey += '.'; + entryKey += StringUtils::to_string(++m_index); + + m_outer->m_prefix = entryKey + '.' + m_keyName; + m_outer->EmitField(StringUtils::URLEncode(key.c_str())); + + m_outer->m_prefix = entryKey + '.' + m_valueName; + value(*m_outer); + } + + private: + Impl* m_outer; + Aws::String m_base; + bool m_flattened; + Aws::String m_entryName; + Aws::String m_keyName; + Aws::String m_valueName; + int m_index = 0; + }; + + void EmitField(const Aws::String& value) { + if (m_hasContent) { + m_buf += '&'; + } else { + m_hasContent = true; + } + m_buf += m_prefix; + m_buf += '='; + m_buf += value; + } + + bool EnterContainer() { + if (!m_errorMessage.empty()) { + return false; + } + if (m_depth + 1 > MAX_DEPTH) { + m_errorMessage = "Maximum serialization depth exceeded"; + return false; + } + ++m_depth; + return true; + } + void ExitContainer() { --m_depth; } + + static Aws::String JoinKey(const Aws::String& prefix, const Aws::String& name) { + if (prefix.empty()) { + return name; + } + Aws::String key = prefix; + key += '.'; + key += name; + return key; + } + + static Aws::String FormatFloatingPoint(double value) { + if (std::isfinite(value)) { + return StringUtils::to_string(value); + } + if (std::isnan(value)) { + return "NaN"; + } + return value > 0 ? "Infinity" : "-Infinity"; + } + + static Aws::String ResolveName(const Schema& schema, bool ec2) { + if (ec2) { + const auto ec2Name = schema.GetTrait(Ec2QueryNameTrait::KEY()); + if (ec2Name) { + return ec2Name->GetValue(); + } + } + const auto xmlName = schema.GetTrait(XmlNameTrait::KEY()); + Aws::String name = xmlName ? xmlName->GetValue() : schema.GetMemberName(); + if (ec2 && !name.empty()) { + name[0] = static_cast(std::toupper(static_cast(name[0]))); + } + return name; + } + static bool IsFlattened(const Schema& schema) { return schema.HasTrait(XmlFlattenedTrait::KEY()); } + static Aws::String GetListItemName(const Schema& schema) { + const auto trait = schema.GetTrait(XmlListItemNameTrait::KEY()); + return trait ? trait->GetValue() : Aws::String("member"); + } + static Aws::String GetMapEntryName(const Schema& schema) { + const auto trait = schema.GetTrait(XmlMapEntryNameTrait::KEY()); + return trait ? trait->GetValue() : Aws::String("entry"); + } + static Aws::String GetMapKeyName(const Schema& schema) { + const auto trait = schema.GetTrait(XmlMapKeyNameTrait::KEY()); + return trait ? trait->GetValue() : Aws::String("key"); + } + static Aws::String GetMapValueName(const Schema& schema) { + const auto trait = schema.GetTrait(XmlMapValueNameTrait::KEY()); + return trait ? trait->GetValue() : Aws::String("value"); + } + + Aws::String m_buf; + Aws::String m_prefix; + int m_depth = 0; + bool m_ec2 = false; + bool m_hasContent = false; + bool m_finalized = false; + Aws::String m_errorMessage; +}; + +QueryShapeSerializer::QueryShapeSerializer() : m_impl(Aws::MakeUnique("QueryShapeSerializer", false)) {} +QueryShapeSerializer::QueryShapeSerializer(Flavor flavor) + : m_impl(Aws::MakeUnique("QueryShapeSerializer", flavor == Flavor::Ec2Query)) {} +QueryShapeSerializer::~QueryShapeSerializer() = default; + +void QueryShapeSerializer::WriteStruct(const Schema& schema, const SerializableStruct& value) { m_impl->WriteStruct(schema, value); } +void QueryShapeSerializer::WriteList(const Schema& schema, size_t size, const std::function& consumer) { + m_impl->WriteList(schema, size, consumer); +} +void QueryShapeSerializer::WriteMap(const Schema& schema, size_t size, const std::function& consumer) { + m_impl->WriteMap(schema, size, consumer); +} +void QueryShapeSerializer::WriteBoolean(const Schema& schema, bool value) { m_impl->WriteBoolean(schema, value); } +void QueryShapeSerializer::WriteInteger(const Schema& schema, int value) { m_impl->WriteInteger(schema, value); } +void QueryShapeSerializer::WriteLong(const Schema& schema, int64_t value) { m_impl->WriteLong(schema, value); } +void QueryShapeSerializer::WriteFloat(const Schema& schema, float value) { m_impl->WriteFloat(schema, value); } +void QueryShapeSerializer::WriteDouble(const Schema& schema, double value) { m_impl->WriteDouble(schema, value); } +void QueryShapeSerializer::WriteString(const Schema& schema, const Aws::String& value) { m_impl->WriteString(schema, value); } +void QueryShapeSerializer::WriteTimestamp(const Schema& schema, const DateTime& value) { m_impl->WriteTimestamp(schema, value); } +void QueryShapeSerializer::WriteBlob(const Schema& schema, const ByteBuffer& value) { m_impl->WriteBlob(schema, value); } +void QueryShapeSerializer::WriteEnum(const Schema& schema, int value) { m_impl->WriteEnum(schema, value); } +void QueryShapeSerializer::WriteNull(const Schema& schema) { m_impl->WriteNull(schema); } + +QueryShapeSerializer::SerializerOutcome QueryShapeSerializer::GetPayload() { return m_impl->GetPayload(); } diff --git a/src/aws-cpp-sdk-core/source/smithy/client/schema/TraitKey.cpp b/src/aws-cpp-sdk-core/source/smithy/client/schema/TraitKey.cpp index dff0c2f9932b..f17e359934c7 100644 --- a/src/aws-cpp-sdk-core/source/smithy/client/schema/TraitKey.cpp +++ b/src/aws-cpp-sdk-core/source/smithy/client/schema/TraitKey.cpp @@ -30,6 +30,7 @@ const TraitKey& TraitKey::Instance() { template class SMITHY_TEMPLATE_EXPORT TraitKey; template class SMITHY_TEMPLATE_EXPORT TraitKey; +template class SMITHY_TEMPLATE_EXPORT TraitKey; template class SMITHY_TEMPLATE_EXPORT TraitKey; template class SMITHY_TEMPLATE_EXPORT TraitKey; template class SMITHY_TEMPLATE_EXPORT TraitKey; diff --git a/src/aws-cpp-sdk-core/source/smithy/client/schema/XmlShapeDeserializer.cpp b/src/aws-cpp-sdk-core/source/smithy/client/schema/XmlShapeDeserializer.cpp new file mode 100644 index 000000000000..6b15ad1c49db --- /dev/null +++ b/src/aws-cpp-sdk-core/source/smithy/client/schema/XmlShapeDeserializer.cpp @@ -0,0 +1,495 @@ +/** + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0. + */ +#include +#include +#include + +#include +#include +#include +#include + +using namespace smithy::schema; +using namespace Aws::Utils; + +class XmlShapeDeserializer::Impl final : public ShapeDeserializer { + public: + Impl(const unsigned char* data, size_t length) : m_xml(reinterpret_cast(data), length) { + const Element root = FindRoot(); + m_tagBegin = root.tagBegin; + m_tagEnd = root.tagEnd; + m_contentBegin = root.contentBegin; + m_contentEnd = root.contentEnd; + m_valid = root.found; + } + + void ReadStruct(const Schema& schema, const StructMemberConsumer& consumer) override { + if (!m_valid) { + return; + } + for (uint16_t i = 0; i < schema.GetMemberCount(); ++i) { + const auto member = schema.GetMember(static_cast(i)); + if (!member.has_value() || !*member) { + continue; + } + const Schema& memberSchema = **member; + const Aws::String name = XmlName(memberSchema); + const Cursor saved = SaveCursor(); + bool deliver = false; + + if (IsAttribute(memberSchema)) { + m_attr = name; + m_flattened = false; + deliver = true; + } else { + const ShapeType type = memberSchema.GetType(); + const bool flattened = IsFlattened(memberSchema) && (type == ShapeType::List || type == ShapeType::Map); + if (flattened) { + if (FindChild(m_contentBegin, m_contentEnd, name, m_contentBegin).found) { + m_flattened = true; + m_flatName = name; + m_attr.clear(); + deliver = true; + } + } else { + const Element child = FindChild(m_contentBegin, m_contentEnd, name, m_contentBegin); + if (child.found) { + EnterElement(child); + m_flattened = false; + m_attr.clear(); + deliver = true; + } + } + } + if (deliver) { + consumer(memberSchema, *this); + } + RestoreCursor(saved); + } + } + + void ReadList(const Schema& schema, const ListElementConsumer& consumer) override { + if (!m_valid) { + return; + } + + const Aws::String itemName = m_flattened ? m_flatName : GetListItemName(schema); + const size_t begin = m_contentBegin; + const size_t end = m_contentEnd; + for (Element item = FindChild(begin, end, itemName, begin); item.found; item = FindChild(begin, end, itemName, item.nodeEnd)) { + const Cursor saved = SaveCursor(); + EnterElement(item); + m_flattened = false; + m_attr.clear(); + consumer(*this); + RestoreCursor(saved); + } + } + + void ReadMap(const Schema& schema, const MapEntryConsumer& consumer) override { + if (!m_valid) { + return; + } + const Aws::String entryName = m_flattened ? m_flatName : GetMapEntryName(schema); + const Aws::String keyName = GetMapKeyName(schema); + const Aws::String valueName = GetMapValueName(schema); + const size_t begin = m_contentBegin; + const size_t end = m_contentEnd; + for (Element entry = FindChild(begin, end, entryName, begin); entry.found; + entry = FindChild(begin, end, entryName, entry.nodeEnd)) { + const Element valueNode = FindChild(entry.contentBegin, entry.contentEnd, valueName, entry.contentBegin); + if (!valueNode.found) { + continue; + } + const Element keyNode = FindChild(entry.contentBegin, entry.contentEnd, keyName, entry.contentBegin); + const Aws::String key = keyNode.found ? DecodeText(keyNode.contentBegin, keyNode.contentEnd) : Aws::String(); + const Cursor saved = SaveCursor(); + EnterElement(valueNode); + m_flattened = false; + m_attr.clear(); + consumer(key, *this); + RestoreCursor(saved); + } + } + + Aws::Crt::Optional ReadBoolean(const Schema&) override { + const Aws::String text = CurrentText(); + if (text == "true") { + return true; + } + if (text == "false") { + return false; + } + return {}; + } + + Aws::Crt::Optional ReadInteger(const Schema& schema) override { + auto val = ReadLong(schema); + if (!val.has_value()) { + return {}; + } + return static_cast(val.value()); + } + + Aws::Crt::Optional ReadLong(const Schema&) override { + const Aws::String text = CurrentText(); + if (text.empty()) { + return {}; + } + char* end = nullptr; + const long long value = std::strtoll(text.c_str(), &end, 10); + if (end != text.c_str() + text.size()) { + return {}; + } + return static_cast(value); + } + + Aws::Crt::Optional ReadFloat(const Schema& schema) override { + auto val = ReadDouble(schema); + if (!val.has_value()) { + return {}; + } + return static_cast(val.value()); + } + + Aws::Crt::Optional ReadDouble(const Schema&) override { + const Aws::String text = CurrentText(); + if (text == "NaN") { + return std::numeric_limits::quiet_NaN(); + } + if (text == "Infinity") { + return std::numeric_limits::infinity(); + } + if (text == "-Infinity") { + return -std::numeric_limits::infinity(); + } + if (text.empty()) { + return {}; + } + char* end = nullptr; + const double value = std::strtod(text.c_str(), &end); + if (end != text.c_str() + text.size()) { + return {}; + } + return value; + } + + Aws::Crt::Optional ReadString(const Schema&) override { return CurrentText(); } + + Aws::Crt::Optional ReadTimestamp(const Schema&) override { + DateTime parsed(CurrentText(), DateFormat::ISO_8601); + if (!parsed.WasParseSuccessful()) { + return {}; + } + return parsed; + } + + Aws::Crt::Optional ReadBlob(const Schema&) override { return HashingUtils::Base64Decode(CurrentText()); } + + Aws::Crt::Optional ReadEnum(const Schema& schema) override { return ReadInteger(schema); } + + bool IsNull() override { return !m_valid; } + + private: + + struct Element { + bool found = false; + size_t tagBegin = 0; + size_t tagEnd = 0; + size_t contentBegin = 0; + size_t contentEnd = 0; + size_t nodeEnd = 0; + }; + struct Cursor { + size_t tagBegin, tagEnd, contentBegin, contentEnd; + bool flattened; + Aws::String flatName; + Aws::String attr; + }; + Cursor SaveCursor() const { return Cursor{m_tagBegin, m_tagEnd, m_contentBegin, m_contentEnd, m_flattened, m_flatName, m_attr}; } + void RestoreCursor(const Cursor& c) { + m_tagBegin = c.tagBegin; + m_tagEnd = c.tagEnd; + m_contentBegin = c.contentBegin; + m_contentEnd = c.contentEnd; + m_flattened = c.flattened; + m_flatName = c.flatName; + m_attr = c.attr; + } + void EnterElement(const Element& e) { + m_tagBegin = e.tagBegin; + m_tagEnd = e.tagEnd; + m_contentBegin = e.contentBegin; + m_contentEnd = e.contentEnd; + } + + static bool IsWs(char c) { return c == ' ' || c == '\t' || c == '\n' || c == '\r'; } + bool StartsWith(size_t pos, const char* lit) const { + const size_t n = std::strlen(lit); + return pos + n <= m_xml.size() && m_xml.compare(pos, n, lit) == 0; + } + size_t Npos() const { return Aws::String::npos; } + + size_t FindTagEnd(size_t lt) const { + size_t p = lt + 1; + while (p < m_xml.size()) { + const char c = m_xml[p]; + if (c == '"' || c == '\'') { + const size_t q = m_xml.find(c, p + 1); + if (q == Aws::String::npos) { + return m_xml.size(); + } + p = q + 1; + } else if (c == '>') { + return p; + } else { + ++p; + } + } + return m_xml.size(); + } + + Aws::String ReadName(size_t nameStart) const { + size_t p = nameStart; + while (p < m_xml.size()) { + const char c = m_xml[p]; + if (IsWs(c) || c == '>' || c == '/') { + break; + } + ++p; + } + return m_xml.substr(nameStart, p - nameStart); + } + + size_t FindMatchingClose(size_t contentBegin, size_t limit) const { + size_t p = contentBegin; + int depth = 0; + while (p < limit) { + const size_t lt = m_xml.find('<', p); + if (lt == Aws::String::npos || lt >= limit) { + return limit; + } + if (StartsWith(lt, "", lt); + p = (e == Aws::String::npos) ? limit : e + 3; + continue; + } + if (StartsWith(lt, "", lt); + p = (e == Aws::String::npos) ? limit : e + 3; + continue; + } + if (StartsWith(lt, "", lt); + p = (e == Aws::String::npos) ? limit : e + 2; + continue; + } + if (m_xml[lt + 1] == '/') { + if (depth == 0) { + return lt; + } + --depth; + const size_t gt = m_xml.find('>', lt); + p = (gt == Aws::String::npos) ? limit : gt + 1; + continue; + } + const size_t gt = FindTagEnd(lt); + if (gt == 0 || m_xml[gt - 1] != '/') { + ++depth; + } + p = gt + 1; + } + return limit; + } + + Element FindChild(size_t begin, size_t end, const Aws::String& name, size_t from) const { + size_t p = (from < begin) ? begin : from; + while (p < end) { + const size_t lt = m_xml.find('<', p); + if (lt == Aws::String::npos || lt >= end) { + break; + } + if (StartsWith(lt, "", lt); + p = (e == Aws::String::npos) ? end : e + 3; + continue; + } + if (StartsWith(lt, "", lt); + p = (e == Aws::String::npos) ? end : e + 3; + continue; + } + if (StartsWith(lt, "", lt); + p = (e == Aws::String::npos) ? end : e + 2; + continue; + } + if (m_xml[lt + 1] == '/') { + break; + } + const size_t gt = FindTagEnd(lt); + const bool selfClosing = (gt > 0 && m_xml[gt - 1] == '/'); + const Aws::String tagName = ReadName(lt + 1); + + Element e; + e.tagBegin = lt; + e.tagEnd = gt; + e.contentBegin = gt + 1; + if (selfClosing) { + e.contentEnd = gt; + e.nodeEnd = gt + 1; + } else { + const size_t closeLt = FindMatchingClose(gt + 1, end); + e.contentEnd = closeLt; + const size_t closeGt = m_xml.find('>', closeLt); + e.nodeEnd = (closeGt == Aws::String::npos) ? end : closeGt + 1; + } + if (tagName == name) { + e.found = true; + return e; + } + p = e.nodeEnd; + } + return Element{}; + } + + Element FindRoot() const { + size_t p = 0; + while (p < m_xml.size()) { + const size_t lt = m_xml.find('<', p); + if (lt == Aws::String::npos) { + break; + } + if (StartsWith(lt, "", lt); + p = (e == Aws::String::npos) ? m_xml.size() : e + 3; + } else { + const size_t gt = m_xml.find('>', lt); + p = (gt == Aws::String::npos) ? m_xml.size() : gt + 1; + } + continue; + } + const size_t gt = FindTagEnd(lt); + const bool selfClosing = (gt > 0 && m_xml[gt - 1] == '/'); + Element e; + e.found = true; + e.tagBegin = lt; + e.tagEnd = gt; + e.contentBegin = gt + 1; + if (selfClosing) { + e.contentEnd = gt; + e.nodeEnd = gt + 1; + } else { + const size_t closeLt = FindMatchingClose(gt + 1, m_xml.size()); + e.contentEnd = closeLt; + const size_t closeGt = m_xml.find('>', closeLt); + e.nodeEnd = (closeGt == Aws::String::npos) ? m_xml.size() : closeGt + 1; + } + return e; + } + return Element{}; + } + + Aws::String DecodeText(size_t begin, size_t end) const { + if (end <= begin) { + return Aws::String(); + } + return Aws::Utils::Xml::DecodeEscapedXmlText(m_xml.substr(begin, end - begin)); + } + + Aws::String AttributeValue(const Aws::String& attr) const { + size_t p = m_tagBegin; + while (p < m_tagEnd) { + const size_t f = m_xml.find(attr, p); + if (f == Aws::String::npos || f >= m_tagEnd) { + break; + } + const char prev = (f > m_tagBegin) ? m_xml[f - 1] : ' '; + size_t a = f + attr.size(); + while (a < m_tagEnd && IsWs(m_xml[a])) { + ++a; + } + if ((IsWs(prev) || prev == '<') && a < m_tagEnd && m_xml[a] == '=') { + size_t q = a + 1; + while (q < m_tagEnd && IsWs(m_xml[q])) { + ++q; + } + if (q < m_tagEnd && (m_xml[q] == '"' || m_xml[q] == '\'')) { + const char quote = m_xml[q]; + const size_t vb = q + 1; + const size_t ve = m_xml.find(quote, vb); + if (ve != Aws::String::npos) { + return m_xml.substr(vb, ve - vb); + } + } + } + p = f + attr.size(); + } + return Aws::String(); + } + + Aws::String CurrentText() const { + if (m_attr.empty()) { + return DecodeText(m_contentBegin, m_contentEnd); + } + return Aws::Utils::Xml::DecodeEscapedXmlText(AttributeValue(m_attr)); + } + + static Aws::String XmlName(const Schema& schema) { + const auto trait = schema.GetTrait(XmlNameTrait::KEY()); + return trait ? trait->GetValue() : schema.GetMemberName(); + } + static bool IsFlattened(const Schema& schema) { return schema.HasTrait(XmlFlattenedTrait::KEY()); } + static bool IsAttribute(const Schema& schema) { return schema.HasTrait(XmlAttributeTrait::KEY()); } + static Aws::String GetListItemName(const Schema& schema) { + const auto trait = schema.GetTrait(XmlListItemNameTrait::KEY()); + return trait ? trait->GetValue() : Aws::String("member"); + } + static Aws::String GetMapEntryName(const Schema& schema) { + const auto trait = schema.GetTrait(XmlMapEntryNameTrait::KEY()); + return trait ? trait->GetValue() : Aws::String("entry"); + } + static Aws::String GetMapKeyName(const Schema& schema) { + const auto trait = schema.GetTrait(XmlMapKeyNameTrait::KEY()); + return trait ? trait->GetValue() : Aws::String("key"); + } + static Aws::String GetMapValueName(const Schema& schema) { + const auto trait = schema.GetTrait(XmlMapValueNameTrait::KEY()); + return trait ? trait->GetValue() : Aws::String("value"); + } + + Aws::String m_xml; + size_t m_tagBegin = 0; + size_t m_tagEnd = 0; + size_t m_contentBegin = 0; + size_t m_contentEnd = 0; + bool m_valid = false; + bool m_flattened = false; + Aws::String m_flatName; + Aws::String m_attr; +}; + +XmlShapeDeserializer::XmlShapeDeserializer(const unsigned char* data, size_t length) + : m_impl(Aws::MakeUnique("XmlShapeDeserializer", data, length)) {} +XmlShapeDeserializer::~XmlShapeDeserializer() = default; + +void XmlShapeDeserializer::ReadStruct(const Schema& schema, const StructMemberConsumer& consumer) { + m_impl->ReadStruct(schema, consumer); +} +void XmlShapeDeserializer::ReadList(const Schema& schema, const ListElementConsumer& consumer) { + m_impl->ReadList(schema, consumer); +} +void XmlShapeDeserializer::ReadMap(const Schema& schema, const MapEntryConsumer& consumer) { m_impl->ReadMap(schema, consumer); } +Aws::Crt::Optional XmlShapeDeserializer::ReadBoolean(const Schema& schema) { return m_impl->ReadBoolean(schema); } +Aws::Crt::Optional XmlShapeDeserializer::ReadInteger(const Schema& schema) { return m_impl->ReadInteger(schema); } +Aws::Crt::Optional XmlShapeDeserializer::ReadLong(const Schema& schema) { return m_impl->ReadLong(schema); } +Aws::Crt::Optional XmlShapeDeserializer::ReadFloat(const Schema& schema) { return m_impl->ReadFloat(schema); } +Aws::Crt::Optional XmlShapeDeserializer::ReadDouble(const Schema& schema) { return m_impl->ReadDouble(schema); } +Aws::Crt::Optional XmlShapeDeserializer::ReadString(const Schema& schema) { return m_impl->ReadString(schema); } +Aws::Crt::Optional XmlShapeDeserializer::ReadTimestamp(const Schema& schema) { return m_impl->ReadTimestamp(schema); } +Aws::Crt::Optional XmlShapeDeserializer::ReadBlob(const Schema& schema) { return m_impl->ReadBlob(schema); } +Aws::Crt::Optional XmlShapeDeserializer::ReadEnum(const Schema& schema) { return m_impl->ReadEnum(schema); } +bool XmlShapeDeserializer::IsNull() { return m_impl->IsNull(); } diff --git a/src/aws-cpp-sdk-core/source/smithy/client/schema/XmlShapeSerializer.cpp b/src/aws-cpp-sdk-core/source/smithy/client/schema/XmlShapeSerializer.cpp index c683d89d7e2f..c54ab8092790 100644 --- a/src/aws-cpp-sdk-core/source/smithy/client/schema/XmlShapeSerializer.cpp +++ b/src/aws-cpp-sdk-core/source/smithy/client/schema/XmlShapeSerializer.cpp @@ -2,6 +2,8 @@ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. * SPDX-License-Identifier: Apache-2.0. */ +#include + #include #include #include @@ -330,11 +332,23 @@ void XmlShapeSerializer::Impl::ValueSerializer::WriteLong(const Schema&, int64_t } void XmlShapeSerializer::Impl::ValueSerializer::WriteFloat(const Schema&, float value) { m_outer->ClosePendingTag(); - m_outer->AppendRaw(StringUtils::to_string(value)); + if (std::isfinite(value)) { + m_outer->AppendRaw(StringUtils::to_string(value)); + } else if (std::isnan(value)) { + m_outer->AppendRaw("NaN"); + } else { + m_outer->AppendRaw(value > 0 ? "Infinity" : "-Infinity"); + } } void XmlShapeSerializer::Impl::ValueSerializer::WriteDouble(const Schema&, double value) { m_outer->ClosePendingTag(); - m_outer->AppendRaw(StringUtils::to_string(value)); + if (std::isfinite(value)) { + m_outer->AppendRaw(StringUtils::to_string(value)); + } else if (std::isnan(value)) { + m_outer->AppendRaw("NaN"); + } else { + m_outer->AppendRaw(value > 0 ? "Infinity" : "-Infinity"); + } } void XmlShapeSerializer::Impl::ValueSerializer::WriteString(const Schema&, const Aws::String& value) { m_outer->ClosePendingTag(); diff --git a/tests/aws-cpp-sdk-core-tests/smithy/client/schema/CborShapeDeserializerTest.cpp b/tests/aws-cpp-sdk-core-tests/smithy/client/schema/CborShapeDeserializerTest.cpp index d4c6178901eb..fc7daa68c3cc 100644 --- a/tests/aws-cpp-sdk-core-tests/smithy/client/schema/CborShapeDeserializerTest.cpp +++ b/tests/aws-cpp-sdk-core-tests/smithy/client/schema/CborShapeDeserializerTest.cpp @@ -3,6 +3,7 @@ * SPDX-License-Identifier: Apache-2.0. */ #include +#include #include #include #include @@ -18,598 +19,446 @@ using namespace smithy::schema; class CborShapeDeserializerTest : public Aws::Testing::AwsCppSdkGTestSuite {}; -TEST_F(CborShapeDeserializerTest, BooleanTrue) { +namespace { + +Aws::String Encode(const std::shared_ptr& root, const std::function& writeMembers) { CborShapeSerializer s; - auto root = Schema::StructureBuilder("Root").Build(); - auto member = Schema::CreateMember("enabled", ShapeType::Boolean); - LambdaStruct rootStruct(*root, [&](ShapeSerializer& ser) { ser.WriteBoolean(*member, true); }); + LambdaStruct rootStruct(*root, writeMembers); s.WriteStruct(*root, rootStruct); - auto payload = s.GetPayload().GetResult(); + return s.GetPayload().GetResult(); +} + +} + +TEST_F(CborShapeDeserializerTest, BooleanTrue) { + auto root = Schema::StructureBuilder("Root").PutMember("enabled", Schema::CreateBoolean("B")).Build(); + auto enabled = root->GetMember("enabled").value(); + auto payload = Encode(root, [&](ShapeSerializer& ser) { ser.WriteBoolean(*enabled, true); }); CborShapeDeserializer d(reinterpret_cast(payload.data()), payload.size()); - d.BeginStruct(); - ASSERT_FALSE(d.IsBreak()); - auto key = d.ReadKey(); - ASSERT_TRUE(key.has_value()); - EXPECT_EQ(key.value(), "enabled"); - auto val = d.ReadBoolean(); - ASSERT_TRUE(val.has_value()); - EXPECT_TRUE(val.value()); - EXPECT_TRUE(d.IsBreak()); - d.EndStruct(); + Aws::Crt::Optional got; + d.ReadStruct(*root, [&](const Schema& m, ShapeDeserializer& de) { + if (m.GetMemberName() == "enabled") { + got = de.ReadBoolean(m); + } + }); + ASSERT_TRUE(got.has_value()); + EXPECT_TRUE(got.value()); } TEST_F(CborShapeDeserializerTest, BooleanFalse) { - CborShapeSerializer s; - auto root = Schema::StructureBuilder("Root").Build(); - auto member = Schema::CreateMember("ok", ShapeType::Boolean); - LambdaStruct rootStruct(*root, [&](ShapeSerializer& ser) { ser.WriteBoolean(*member, false); }); - s.WriteStruct(*root, rootStruct); - auto payload = s.GetPayload().GetResult(); + auto root = Schema::StructureBuilder("Root").PutMember("ok", Schema::CreateBoolean("B")).Build(); + auto ok = root->GetMember("ok").value(); + auto payload = Encode(root, [&](ShapeSerializer& ser) { ser.WriteBoolean(*ok, false); }); CborShapeDeserializer d(reinterpret_cast(payload.data()), payload.size()); - d.BeginStruct(); - auto key = d.ReadKey(); - ASSERT_TRUE(key.has_value()); - EXPECT_EQ(key.value(), "ok"); - auto val = d.ReadBoolean(); - ASSERT_TRUE(val.has_value()); - EXPECT_FALSE(val.value()); - EXPECT_TRUE(d.IsBreak()); - d.EndStruct(); + Aws::Crt::Optional got; + d.ReadStruct(*root, [&](const Schema& m, ShapeDeserializer& de) { got = de.ReadBoolean(m); }); + ASSERT_TRUE(got.has_value()); + EXPECT_FALSE(got.value()); } TEST_F(CborShapeDeserializerTest, IntegerSmall) { - CborShapeSerializer s; - auto root = Schema::StructureBuilder("Root").Build(); - auto member = Schema::CreateMember("n", ShapeType::Integer); - LambdaStruct rootStruct(*root, [&](ShapeSerializer& ser) { ser.WriteInteger(*member, 7); }); - s.WriteStruct(*root, rootStruct); - auto payload = s.GetPayload().GetResult(); + auto root = Schema::StructureBuilder("Root").PutMember("n", Schema::CreateInteger("I")).Build(); + auto n = root->GetMember("n").value(); + auto payload = Encode(root, [&](ShapeSerializer& ser) { ser.WriteInteger(*n, 7); }); CborShapeDeserializer d(reinterpret_cast(payload.data()), payload.size()); - d.BeginStruct(); - auto key = d.ReadKey(); - ASSERT_TRUE(key.has_value()); - EXPECT_EQ(key.value(), "n"); - auto val = d.ReadInteger(); - ASSERT_TRUE(val.has_value()); - EXPECT_EQ(val.value(), 7); - EXPECT_TRUE(d.IsBreak()); - d.EndStruct(); + Aws::Crt::Optional got; + d.ReadStruct(*root, [&](const Schema& m, ShapeDeserializer& de) { got = de.ReadInteger(m); }); + ASSERT_TRUE(got.has_value()); + EXPECT_EQ(got.value(), 7); } TEST_F(CborShapeDeserializerTest, IntegerNegative) { - CborShapeSerializer s; - auto root = Schema::StructureBuilder("Root").Build(); - auto member = Schema::CreateMember("n", ShapeType::Integer); - LambdaStruct rootStruct(*root, [&](ShapeSerializer& ser) { ser.WriteInteger(*member, -42); }); - s.WriteStruct(*root, rootStruct); - auto payload = s.GetPayload().GetResult(); + auto root = Schema::StructureBuilder("Root").PutMember("n", Schema::CreateInteger("I")).Build(); + auto n = root->GetMember("n").value(); + auto payload = Encode(root, [&](ShapeSerializer& ser) { ser.WriteInteger(*n, -42); }); CborShapeDeserializer d(reinterpret_cast(payload.data()), payload.size()); - d.BeginStruct(); - auto key = d.ReadKey(); - ASSERT_TRUE(key.has_value()); - EXPECT_EQ(key.value(), "n"); - auto val = d.ReadInteger(); - ASSERT_TRUE(val.has_value()); - EXPECT_EQ(val.value(), -42); - EXPECT_TRUE(d.IsBreak()); - d.EndStruct(); + Aws::Crt::Optional got; + d.ReadStruct(*root, [&](const Schema& m, ShapeDeserializer& de) { got = de.ReadInteger(m); }); + ASSERT_TRUE(got.has_value()); + EXPECT_EQ(got.value(), -42); } TEST_F(CborShapeDeserializerTest, LongValue) { - CborShapeSerializer s; - auto root = Schema::StructureBuilder("Root").Build(); - auto member = Schema::CreateMember("big", ShapeType::Long); - LambdaStruct rootStruct(*root, [&](ShapeSerializer& ser) { ser.WriteLong(*member, 5000000000LL); }); - s.WriteStruct(*root, rootStruct); - auto payload = s.GetPayload().GetResult(); + auto root = Schema::StructureBuilder("Root").PutMember("big", Schema::CreateLong("L")).Build(); + auto big = root->GetMember("big").value(); + auto payload = Encode(root, [&](ShapeSerializer& ser) { ser.WriteLong(*big, 5000000000LL); }); CborShapeDeserializer d(reinterpret_cast(payload.data()), payload.size()); - d.BeginStruct(); - auto key = d.ReadKey(); - ASSERT_TRUE(key.has_value()); - EXPECT_EQ(key.value(), "big"); - auto val = d.ReadLong(); - ASSERT_TRUE(val.has_value()); - EXPECT_EQ(val.value(), 5000000000LL); - EXPECT_TRUE(d.IsBreak()); - d.EndStruct(); + Aws::Crt::Optional got; + d.ReadStruct(*root, [&](const Schema& m, ShapeDeserializer& de) { got = de.ReadLong(m); }); + ASSERT_TRUE(got.has_value()); + EXPECT_EQ(got.value(), 5000000000LL); } TEST_F(CborShapeDeserializerTest, DoubleValue) { - CborShapeSerializer s; - auto root = Schema::StructureBuilder("Root").Build(); - auto member = Schema::CreateMember("d", ShapeType::Double); - LambdaStruct rootStruct(*root, [&](ShapeSerializer& ser) { ser.WriteDouble(*member, 3.14); }); - s.WriteStruct(*root, rootStruct); - auto payload = s.GetPayload().GetResult(); + auto root = Schema::StructureBuilder("Root").PutMember("d", Schema::CreateDouble("D")).Build(); + auto member = root->GetMember("d").value(); + auto payload = Encode(root, [&](ShapeSerializer& ser) { ser.WriteDouble(*member, 3.14); }); CborShapeDeserializer d(reinterpret_cast(payload.data()), payload.size()); - d.BeginStruct(); - auto key = d.ReadKey(); - ASSERT_TRUE(key.has_value()); - EXPECT_EQ(key.value(), "d"); - auto val = d.ReadDouble(); - ASSERT_TRUE(val.has_value()); - EXPECT_DOUBLE_EQ(val.value(), 3.14); - EXPECT_TRUE(d.IsBreak()); - d.EndStruct(); + Aws::Crt::Optional got; + d.ReadStruct(*root, [&](const Schema& m, ShapeDeserializer& de) { got = de.ReadDouble(m); }); + ASSERT_TRUE(got.has_value()); + EXPECT_DOUBLE_EQ(got.value(), 3.14); } TEST_F(CborShapeDeserializerTest, DoubleWholeNumber) { - // CRT encodes 5.0 as integer 5 — deserializer must handle this - CborShapeSerializer s; - auto root = Schema::StructureBuilder("Root").Build(); - auto member = Schema::CreateMember("d", ShapeType::Double); - LambdaStruct rootStruct(*root, [&](ShapeSerializer& ser) { ser.WriteDouble(*member, 5.0); }); - s.WriteStruct(*root, rootStruct); - auto payload = s.GetPayload().GetResult(); + + auto root = Schema::StructureBuilder("Root").PutMember("d", Schema::CreateDouble("D")).Build(); + auto member = root->GetMember("d").value(); + auto payload = Encode(root, [&](ShapeSerializer& ser) { ser.WriteDouble(*member, 5.0); }); CborShapeDeserializer d(reinterpret_cast(payload.data()), payload.size()); - d.BeginStruct(); - auto key = d.ReadKey(); - ASSERT_TRUE(key.has_value()); - EXPECT_EQ(key.value(), "d"); - auto val = d.ReadDouble(); - ASSERT_TRUE(val.has_value()); - EXPECT_DOUBLE_EQ(val.value(), 5.0); - EXPECT_TRUE(d.IsBreak()); - d.EndStruct(); + Aws::Crt::Optional got; + d.ReadStruct(*root, [&](const Schema& m, ShapeDeserializer& de) { got = de.ReadDouble(m); }); + ASSERT_TRUE(got.has_value()); + EXPECT_DOUBLE_EQ(got.value(), 5.0); } TEST_F(CborShapeDeserializerTest, FloatValue) { - CborShapeSerializer s; - auto root = Schema::StructureBuilder("Root").Build(); - auto member = Schema::CreateMember("f", ShapeType::Float); - LambdaStruct rootStruct(*root, [&](ShapeSerializer& ser) { ser.WriteFloat(*member, 1.5f); }); - s.WriteStruct(*root, rootStruct); - auto payload = s.GetPayload().GetResult(); + auto root = Schema::StructureBuilder("Root").PutMember("f", Schema::CreateFloat("F")).Build(); + auto member = root->GetMember("f").value(); + auto payload = Encode(root, [&](ShapeSerializer& ser) { ser.WriteFloat(*member, 1.5f); }); CborShapeDeserializer d(reinterpret_cast(payload.data()), payload.size()); - d.BeginStruct(); - auto key = d.ReadKey(); - ASSERT_TRUE(key.has_value()); - EXPECT_EQ(key.value(), "f"); - auto val = d.ReadFloat(); - ASSERT_TRUE(val.has_value()); - EXPECT_FLOAT_EQ(val.value(), 1.5f); - EXPECT_TRUE(d.IsBreak()); - d.EndStruct(); + Aws::Crt::Optional got; + d.ReadStruct(*root, [&](const Schema& m, ShapeDeserializer& de) { got = de.ReadFloat(m); }); + ASSERT_TRUE(got.has_value()); + EXPECT_FLOAT_EQ(got.value(), 1.5f); } TEST_F(CborShapeDeserializerTest, StringValue) { - CborShapeSerializer s; - auto root = Schema::StructureBuilder("Root").Build(); - auto member = Schema::CreateMember("name", ShapeType::String); - LambdaStruct rootStruct(*root, [&](ShapeSerializer& ser) { ser.WriteString(*member, "hello"); }); - s.WriteStruct(*root, rootStruct); - auto payload = s.GetPayload().GetResult(); + auto root = Schema::StructureBuilder("Root").PutMember("name", Schema::CreateString("S")).Build(); + auto member = root->GetMember("name").value(); + auto payload = Encode(root, [&](ShapeSerializer& ser) { ser.WriteString(*member, "hello"); }); CborShapeDeserializer d(reinterpret_cast(payload.data()), payload.size()); - d.BeginStruct(); - auto key = d.ReadKey(); - ASSERT_TRUE(key.has_value()); - EXPECT_EQ(key.value(), "name"); - auto val = d.ReadString(); - ASSERT_TRUE(val.has_value()); - EXPECT_EQ(val.value(), "hello"); - EXPECT_TRUE(d.IsBreak()); - d.EndStruct(); + Aws::Crt::Optional got; + d.ReadStruct(*root, [&](const Schema& m, ShapeDeserializer& de) { got = de.ReadString(m); }); + ASSERT_TRUE(got.has_value()); + EXPECT_EQ(got.value(), "hello"); } TEST_F(CborShapeDeserializerTest, BlobValue) { - CborShapeSerializer s; - auto root = Schema::StructureBuilder("Root").Build(); + auto root = Schema::StructureBuilder("Root").PutMember("data", Schema::CreateBlob("Bl")).Build(); + auto member = root->GetMember("data").value(); Aws::Utils::ByteBuffer blob(4); blob[0] = 0xDE; blob[1] = 0xAD; blob[2] = 0xBE; blob[3] = 0xEF; - auto member = Schema::CreateMember("data", ShapeType::Blob); - LambdaStruct rootStruct(*root, [&](ShapeSerializer& ser) { ser.WriteBlob(*member, blob); }); - s.WriteStruct(*root, rootStruct); - auto payload = s.GetPayload().GetResult(); + auto payload = Encode(root, [&](ShapeSerializer& ser) { ser.WriteBlob(*member, blob); }); CborShapeDeserializer d(reinterpret_cast(payload.data()), payload.size()); - d.BeginStruct(); - auto key = d.ReadKey(); - ASSERT_TRUE(key.has_value()); - EXPECT_EQ(key.value(), "data"); - auto result = d.ReadBlob(); - ASSERT_TRUE(result.has_value()); - ASSERT_EQ(result.value().GetLength(), 4u); - EXPECT_EQ(result.value()[0], 0xDE); - EXPECT_EQ(result.value()[1], 0xAD); - EXPECT_EQ(result.value()[2], 0xBE); - EXPECT_EQ(result.value()[3], 0xEF); - EXPECT_TRUE(d.IsBreak()); - d.EndStruct(); + Aws::Crt::Optional got; + d.ReadStruct(*root, [&](const Schema& m, ShapeDeserializer& de) { got = de.ReadBlob(m); }); + ASSERT_TRUE(got.has_value()); + ASSERT_EQ(got.value().GetLength(), 4u); + EXPECT_EQ(got.value()[0], 0xDE); + EXPECT_EQ(got.value()[1], 0xAD); + EXPECT_EQ(got.value()[2], 0xBE); + EXPECT_EQ(got.value()[3], 0xEF); } TEST_F(CborShapeDeserializerTest, TimestampValue) { - CborShapeSerializer s; - auto root = Schema::StructureBuilder("Root").Build(); - Aws::Utils::DateTime dt(1234567890.0); // seconds since epoch - auto member = Schema::CreateMember("ts", ShapeType::Timestamp); - LambdaStruct rootStruct(*root, [&](ShapeSerializer& ser) { ser.WriteTimestamp(*member, dt); }); - s.WriteStruct(*root, rootStruct); - auto payload = s.GetPayload().GetResult(); + auto root = Schema::StructureBuilder("Root").PutMember("ts", Schema::CreateTimestamp("T")).Build(); + auto member = root->GetMember("ts").value(); + Aws::Utils::DateTime dt(1234567890.0); + auto payload = Encode(root, [&](ShapeSerializer& ser) { ser.WriteTimestamp(*member, dt); }); CborShapeDeserializer d(reinterpret_cast(payload.data()), payload.size()); - d.BeginStruct(); - auto key = d.ReadKey(); - ASSERT_TRUE(key.has_value()); - EXPECT_EQ(key.value(), "ts"); - auto result = d.ReadTimestamp(); - ASSERT_TRUE(result.has_value()); - EXPECT_EQ(result.value().Seconds(), 1234567890); - EXPECT_TRUE(d.IsBreak()); - d.EndStruct(); + Aws::Crt::Optional got; + d.ReadStruct(*root, [&](const Schema& m, ShapeDeserializer& de) { got = de.ReadTimestamp(m); }); + ASSERT_TRUE(got.has_value()); + EXPECT_EQ(got.value().Seconds(), 1234567890); } -TEST_F(CborShapeDeserializerTest, NullValue) { - CborShapeSerializer s; - auto root = Schema::StructureBuilder("Root").Build(); - auto member = Schema::CreateMember("item", ShapeType::String); - LambdaStruct rootStruct(*root, [&](ShapeSerializer& ser) { ser.WriteNull(*member); }); - s.WriteStruct(*root, rootStruct); - auto payload = s.GetPayload().GetResult(); +TEST_F(CborShapeDeserializerTest, NullMemberIsSkipped) { + auto root = Schema::StructureBuilder("Root").PutMember("item", Schema::CreateString("S")).Build(); + auto member = root->GetMember("item").value(); + auto payload = Encode(root, [&](ShapeSerializer& ser) { ser.WriteNull(*member); }); CborShapeDeserializer d(reinterpret_cast(payload.data()), payload.size()); - d.BeginStruct(); - auto key = d.ReadKey(); - ASSERT_TRUE(key.has_value()); - EXPECT_EQ(key.value(), "item"); - EXPECT_TRUE(d.IsNull()); - d.ReadNull(); - EXPECT_TRUE(d.IsBreak()); - d.EndStruct(); + int memberCalls = 0; + d.ReadStruct(*root, [&](const Schema&, ShapeDeserializer&) { ++memberCalls; }); + + EXPECT_EQ(memberCalls, 0); } TEST_F(CborShapeDeserializerTest, ListOfIntegers) { - CborShapeSerializer s; - auto root = Schema::StructureBuilder("Root").Build(); - auto listMember = Schema::CreateMember("nums", ShapeType::List); + auto listBuilder = Schema::ListBuilder("Nums"); + auto root = Schema::StructureBuilder("Root").PutMember("nums", listBuilder).Build(); + auto nums = root->GetMember("nums").value(); auto elem = Schema::CreateMember("member", ShapeType::Integer); - LambdaStruct rootStruct(*root, [&](ShapeSerializer& ser) { - ser.WriteList(*listMember, 3, [&](ShapeSerializer& lser) { + auto payload = Encode(root, [&](ShapeSerializer& ser) { + ser.WriteList(*nums, 3, [&](ShapeSerializer& lser) { lser.WriteInteger(*elem, 10); lser.WriteInteger(*elem, 20); lser.WriteInteger(*elem, 30); }); }); - s.WriteStruct(*root, rootStruct); - auto payload = s.GetPayload().GetResult(); CborShapeDeserializer d(reinterpret_cast(payload.data()), payload.size()); - d.BeginStruct(); - auto key = d.ReadKey(); - ASSERT_TRUE(key.has_value()); - EXPECT_EQ(key.value(), "nums"); - size_t count = d.BeginList(); - EXPECT_EQ(count, 3u); - auto v1 = d.ReadInteger(); - ASSERT_TRUE(v1.has_value()); - EXPECT_EQ(v1.value(), 10); - auto v2 = d.ReadInteger(); - ASSERT_TRUE(v2.has_value()); - EXPECT_EQ(v2.value(), 20); - auto v3 = d.ReadInteger(); - ASSERT_TRUE(v3.has_value()); - EXPECT_EQ(v3.value(), 30); - d.EndList(); - EXPECT_TRUE(d.IsBreak()); - d.EndStruct(); + Aws::Vector values; + d.ReadStruct(*root, [&](const Schema& m, ShapeDeserializer& de) { + if (m.GetMemberName() == "nums") { + de.ReadList(m, [&](ShapeDeserializer& ede) { + auto v = ede.ReadInteger(*elem); + if (v.has_value()) { + values.push_back(v.value()); + } + }); + } + }); + ASSERT_EQ(values.size(), 3u); + EXPECT_EQ(values[0], 10); + EXPECT_EQ(values[1], 20); + EXPECT_EQ(values[2], 30); } TEST_F(CborShapeDeserializerTest, MapOfStrings) { - CborShapeSerializer s; - auto root = Schema::StructureBuilder("Root").Build(); - auto mapMember = Schema::CreateMember("headers", ShapeType::Map); + auto mapBuilder = Schema::MapBuilder("Headers"); + auto root = Schema::StructureBuilder("Root").PutMember("headers", mapBuilder).Build(); + auto headers = root->GetMember("headers").value(); auto valSchema = Schema::CreateMember("value", ShapeType::String); - LambdaStruct rootStruct(*root, [&](ShapeSerializer& ser) { - ser.WriteMap(*mapMember, 2, [&](MapSerializer& mapSer) { + auto payload = Encode(root, [&](ShapeSerializer& ser) { + ser.WriteMap(*headers, 2, [&](MapSerializer& mapSer) { mapSer.WriteEntry("foo", [&](ShapeSerializer& vser) { vser.WriteString(*valSchema, "bar"); }); mapSer.WriteEntry("baz", [&](ShapeSerializer& vser) { vser.WriteString(*valSchema, "qux"); }); }); }); - s.WriteStruct(*root, rootStruct); - auto payload = s.GetPayload().GetResult(); CborShapeDeserializer d(reinterpret_cast(payload.data()), payload.size()); - d.BeginStruct(); - auto key = d.ReadKey(); - ASSERT_TRUE(key.has_value()); - EXPECT_EQ(key.value(), "headers"); - size_t count = d.BeginMap(); - EXPECT_EQ(count, 2u); - auto k1 = d.ReadKey(); - ASSERT_TRUE(k1.has_value()); - EXPECT_EQ(k1.value(), "foo"); - auto v1 = d.ReadString(); - ASSERT_TRUE(v1.has_value()); - EXPECT_EQ(v1.value(), "bar"); - auto k2 = d.ReadKey(); - ASSERT_TRUE(k2.has_value()); - EXPECT_EQ(k2.value(), "baz"); - auto v2 = d.ReadString(); - ASSERT_TRUE(v2.has_value()); - EXPECT_EQ(v2.value(), "qux"); - d.EndMap(); - EXPECT_TRUE(d.IsBreak()); - d.EndStruct(); + Aws::Map entries; + d.ReadStruct(*root, [&](const Schema& m, ShapeDeserializer& de) { + if (m.GetMemberName() == "headers") { + de.ReadMap(m, [&](const Aws::String& key, ShapeDeserializer& vde) { + auto v = vde.ReadString(*valSchema); + if (v.has_value()) { + entries[key] = v.value(); + } + }); + } + }); + ASSERT_EQ(entries.size(), 2u); + EXPECT_EQ(entries["foo"], "bar"); + EXPECT_EQ(entries["baz"], "qux"); } TEST_F(CborShapeDeserializerTest, NestedStructure) { - CborShapeSerializer s; - auto root = Schema::StructureBuilder("Root").Build(); - auto nested = Schema::CreateMember("meta", ShapeType::Structure); - auto inner = Schema::CreateMember("key", ShapeType::String); - LambdaStruct rootStruct(*root, [&](ShapeSerializer& ser) { - ser.WriteStruct(*nested, LambdaStruct(*nested, [&](ShapeSerializer& ser2) { ser2.WriteString(*inner, "val"); })); + auto metaBuilder = Schema::StructureBuilder("Meta"); + metaBuilder.PutMember("key", Schema::CreateString("S")); + auto root = Schema::StructureBuilder("Root").PutMember("meta", metaBuilder).Build(); + auto meta = root->GetMember("meta").value(); + auto inner = meta->GetMemberTarget().value()->GetMember("key").value(); + auto payload = Encode(root, [&](ShapeSerializer& ser) { + ser.WriteStruct(*meta, LambdaStruct(*meta, [&](ShapeSerializer& ser2) { ser2.WriteString(*inner, "val"); })); }); - s.WriteStruct(*root, rootStruct); - auto payload = s.GetPayload().GetResult(); CborShapeDeserializer d(reinterpret_cast(payload.data()), payload.size()); - d.BeginStruct(); - auto key = d.ReadKey(); - ASSERT_TRUE(key.has_value()); - EXPECT_EQ(key.value(), "meta"); - d.BeginStruct(); - auto innerKey = d.ReadKey(); - ASSERT_TRUE(innerKey.has_value()); - EXPECT_EQ(innerKey.value(), "key"); - auto val = d.ReadString(); - ASSERT_TRUE(val.has_value()); - EXPECT_EQ(val.value(), "val"); - EXPECT_TRUE(d.IsBreak()); - d.EndStruct(); - EXPECT_TRUE(d.IsBreak()); - d.EndStruct(); + Aws::String got; + d.ReadStruct(*root, [&](const Schema& m, ShapeDeserializer& de) { + if (m.GetMemberName() == "meta") { + de.ReadStruct(*m.GetMemberTarget().value(), [&](const Schema& im, ShapeDeserializer& ide) { + if (im.GetMemberName() == "key") { + auto v = ide.ReadString(im); + if (v.has_value()) { + got = v.value(); + } + } + }); + } + }); + EXPECT_EQ(got, "val"); } TEST_F(CborShapeDeserializerTest, SkipUnknownField) { - CborShapeSerializer s; - auto root = Schema::StructureBuilder("Root").Build(); + + auto root = Schema::StructureBuilder("Root") + .PutMember("known", Schema::CreateInteger("I")) + .PutMember("also_known", Schema::CreateInteger("I2")) + .Build(); auto known = Schema::CreateMember("known", ShapeType::Integer); auto unknown = Schema::CreateMember("unknown", ShapeType::String); auto alsoKnown = Schema::CreateMember("also_known", ShapeType::Integer); - LambdaStruct rootStruct(*root, [&](ShapeSerializer& ser) { + auto payload = Encode(root, [&](ShapeSerializer& ser) { ser.WriteInteger(*known, 1); ser.WriteString(*unknown, "skip me"); ser.WriteInteger(*alsoKnown, 2); }); - s.WriteStruct(*root, rootStruct); - auto payload = s.GetPayload().GetResult(); CborShapeDeserializer d(reinterpret_cast(payload.data()), payload.size()); - d.BeginStruct(); - auto k1 = d.ReadKey(); - ASSERT_TRUE(k1.has_value()); - EXPECT_EQ(k1.value(), "known"); - auto v1 = d.ReadInteger(); - ASSERT_TRUE(v1.has_value()); - EXPECT_EQ(v1.value(), 1); - auto k2 = d.ReadKey(); - ASSERT_TRUE(k2.has_value()); - EXPECT_EQ(k2.value(), "unknown"); - d.SkipValue(); - auto k3 = d.ReadKey(); - ASSERT_TRUE(k3.has_value()); - EXPECT_EQ(k3.value(), "also_known"); - auto v3 = d.ReadInteger(); - ASSERT_TRUE(v3.has_value()); - EXPECT_EQ(v3.value(), 2); - EXPECT_TRUE(d.IsBreak()); - d.EndStruct(); + Aws::Map got; + d.ReadStruct(*root, [&](const Schema& m, ShapeDeserializer& de) { + auto v = de.ReadInteger(m); + if (v.has_value()) { + got[m.GetMemberName()] = v.value(); + } + }); + ASSERT_EQ(got.size(), 2u); + EXPECT_EQ(got["known"], 1); + EXPECT_EQ(got["also_known"], 2); + EXPECT_EQ(got.find("unknown"), got.end()); } TEST_F(CborShapeDeserializerTest, SkipNestedUnknown) { - CborShapeSerializer s; - auto root = Schema::StructureBuilder("Root").Build(); + + auto root = Schema::StructureBuilder("Root").PutMember("known", Schema::CreateInteger("I")).Build(); auto unknownStruct = Schema::CreateMember("unknown_struct", ShapeType::Structure); auto a = Schema::CreateMember("a", ShapeType::Integer); auto b = Schema::CreateMember("b", ShapeType::String); auto known = Schema::CreateMember("known", ShapeType::Integer); - LambdaStruct rootStruct(*root, [&](ShapeSerializer& ser) { + auto payload = Encode(root, [&](ShapeSerializer& ser) { ser.WriteStruct(*unknownStruct, LambdaStruct(*unknownStruct, [&](ShapeSerializer& ser2) { - ser2.WriteInteger(*a, 1); - ser2.WriteString(*b, "nested"); - })); + ser2.WriteInteger(*a, 1); + ser2.WriteString(*b, "nested"); + })); ser.WriteInteger(*known, 42); }); - s.WriteStruct(*root, rootStruct); - auto payload = s.GetPayload().GetResult(); CborShapeDeserializer d(reinterpret_cast(payload.data()), payload.size()); - d.BeginStruct(); - auto k1 = d.ReadKey(); - ASSERT_TRUE(k1.has_value()); - EXPECT_EQ(k1.value(), "unknown_struct"); - d.SkipValue(); - auto k2 = d.ReadKey(); - ASSERT_TRUE(k2.has_value()); - EXPECT_EQ(k2.value(), "known"); - auto val = d.ReadInteger(); - ASSERT_TRUE(val.has_value()); - EXPECT_EQ(val.value(), 42); - EXPECT_TRUE(d.IsBreak()); - d.EndStruct(); + Aws::Crt::Optional got; + d.ReadStruct(*root, [&](const Schema& m, ShapeDeserializer& de) { + if (m.GetMemberName() == "known") { + got = de.ReadInteger(m); + } + }); + ASSERT_TRUE(got.has_value()); + EXPECT_EQ(got.value(), 42); } TEST_F(CborShapeDeserializerTest, MultipleScalars) { - CborShapeSerializer s; - auto root = Schema::StructureBuilder("Root").Build(); - auto m1 = Schema::CreateMember("a", ShapeType::Boolean); - auto m2 = Schema::CreateMember("b", ShapeType::Integer); - auto m3 = Schema::CreateMember("c", ShapeType::String); - LambdaStruct rootStruct(*root, [&](ShapeSerializer& ser) { - ser.WriteBoolean(*m1, true); - ser.WriteInteger(*m2, 7); - ser.WriteString(*m3, "x"); + auto root = Schema::StructureBuilder("Root") + .PutMember("a", Schema::CreateBoolean("B")) + .PutMember("b", Schema::CreateInteger("I")) + .PutMember("c", Schema::CreateString("S")) + .Build(); + auto a = root->GetMember("a").value(); + auto b = root->GetMember("b").value(); + auto c = root->GetMember("c").value(); + auto payload = Encode(root, [&](ShapeSerializer& ser) { + ser.WriteBoolean(*a, true); + ser.WriteInteger(*b, 7); + ser.WriteString(*c, "x"); }); - s.WriteStruct(*root, rootStruct); - auto payload = s.GetPayload().GetResult(); CborShapeDeserializer d(reinterpret_cast(payload.data()), payload.size()); - d.BeginStruct(); - auto k1 = d.ReadKey(); - ASSERT_TRUE(k1.has_value()); - EXPECT_EQ(k1.value(), "a"); - auto v1 = d.ReadBoolean(); - ASSERT_TRUE(v1.has_value()); - EXPECT_TRUE(v1.value()); - auto k2 = d.ReadKey(); - ASSERT_TRUE(k2.has_value()); - EXPECT_EQ(k2.value(), "b"); - auto v2 = d.ReadInteger(); - ASSERT_TRUE(v2.has_value()); - EXPECT_EQ(v2.value(), 7); - auto k3 = d.ReadKey(); - ASSERT_TRUE(k3.has_value()); - EXPECT_EQ(k3.value(), "c"); - auto v3 = d.ReadString(); - ASSERT_TRUE(v3.has_value()); - EXPECT_EQ(v3.value(), "x"); - EXPECT_TRUE(d.IsBreak()); - d.EndStruct(); + Aws::Crt::Optional ba; + Aws::Crt::Optional bb; + Aws::Crt::Optional bc; + d.ReadStruct(*root, [&](const Schema& m, ShapeDeserializer& de) { + switch (m.GetMemberIndex()) { + case 0: + ba = de.ReadBoolean(m); + break; + case 1: + bb = de.ReadInteger(m); + break; + case 2: + bc = de.ReadString(m); + break; + default: + break; + } + }); + ASSERT_TRUE(ba.has_value()); + EXPECT_TRUE(ba.value()); + ASSERT_TRUE(bb.has_value()); + EXPECT_EQ(bb.value(), 7); + ASSERT_TRUE(bc.has_value()); + EXPECT_EQ(bc.value(), "x"); } TEST_F(CborShapeDeserializerTest, EmptyOptionalOnEmptyPayload) { + auto scalar = Schema::CreateBoolean("B"); const unsigned char empty[1] = {0}; CborShapeDeserializer d(empty, 0); - auto val = d.ReadBoolean(); + auto val = d.ReadBoolean(*scalar); EXPECT_FALSE(val.has_value()); } TEST_F(CborShapeDeserializerTest, EmptyOptionalOnTypeMismatch) { - CborShapeSerializer s; - auto root = Schema::StructureBuilder("Root").Build(); - auto member = Schema::CreateMember("val", ShapeType::String); - LambdaStruct rootStruct(*root, [&](ShapeSerializer& ser) { ser.WriteString(*member, "hello"); }); - s.WriteStruct(*root, rootStruct); - auto payload = s.GetPayload().GetResult(); + auto root = Schema::StructureBuilder("Root").PutMember("val", Schema::CreateString("S")).Build(); + auto member = root->GetMember("val").value(); + auto payload = Encode(root, [&](ShapeSerializer& ser) { ser.WriteString(*member, "hello"); }); CborShapeDeserializer d(reinterpret_cast(payload.data()), payload.size()); - d.BeginStruct(); - auto key = d.ReadKey(); - ASSERT_TRUE(key.has_value()); - EXPECT_EQ(key.value(), "val"); - auto val = d.ReadInteger(); - EXPECT_FALSE(val.has_value()); + bool delivered = false; + Aws::Crt::Optional got; + d.ReadStruct(*root, [&](const Schema& m, ShapeDeserializer& de) { + delivered = true; + got = de.ReadInteger(m); + }); + EXPECT_TRUE(delivered); + EXPECT_FALSE(got.has_value()); } -TEST_F(CborShapeDeserializerTest, ValidOptionalOnSuccess) { - CborShapeSerializer s; - auto root = Schema::StructureBuilder("Root").Build(); - auto member = Schema::CreateMember("n", ShapeType::Integer); - LambdaStruct rootStruct(*root, [&](ShapeSerializer& ser) { ser.WriteInteger(*member, 42); }); - s.WriteStruct(*root, rootStruct); - auto payload = s.GetPayload().GetResult(); +TEST_F(CborShapeDeserializerTest, DefiniteLengthStruct) { - CborShapeDeserializer d(reinterpret_cast(payload.data()), payload.size()); - d.BeginStruct(); - auto key = d.ReadKey(); - ASSERT_TRUE(key.has_value()); - EXPECT_EQ(key.value(), "n"); - auto val = d.ReadInteger(); - ASSERT_TRUE(val.has_value()); - EXPECT_EQ(val.value(), 42); -} - -TEST_F(CborShapeDeserializerTest, BeginStructDefiniteLengthMap) { - // Definite-length map with 2 entries: - // A2 -- map(2) - // 61 61 -- text(1) "a" - // 01 -- unsigned(1) - // 61 62 -- text(1) "b" - // 02 -- unsigned(2) + auto root = Schema::StructureBuilder("Root") + .PutMember("a", Schema::CreateInteger("I")) + .PutMember("b", Schema::CreateInteger("I2")) + .Build(); const unsigned char data[] = {0xA2, 0x61, 0x61, 0x01, 0x61, 0x62, 0x02}; CborShapeDeserializer d(data, sizeof(data)); - size_t count = d.BeginStruct(); - EXPECT_EQ(count, 2u); - auto k1 = d.ReadKey(); - ASSERT_TRUE(k1.has_value()); - EXPECT_EQ(k1.value(), "a"); - auto v1 = d.ReadInteger(); - ASSERT_TRUE(v1.has_value()); - EXPECT_EQ(v1.value(), 1); - auto k2 = d.ReadKey(); - ASSERT_TRUE(k2.has_value()); - EXPECT_EQ(k2.value(), "b"); - auto v2 = d.ReadInteger(); - ASSERT_TRUE(v2.has_value()); - EXPECT_EQ(v2.value(), 2); - d.EndStruct(); + Aws::Map got; + d.ReadStruct(*root, [&](const Schema& m, ShapeDeserializer& de) { + auto v = de.ReadInteger(m); + if (v.has_value()) { + got[m.GetMemberName()] = v.value(); + } + }); + ASSERT_EQ(got.size(), 2u); + EXPECT_EQ(got["a"], 1); + EXPECT_EQ(got["b"], 2); } -TEST_F(CborShapeDeserializerTest, BeginStructIndefiniteLengthMap) { - CborShapeSerializer s; - auto root = Schema::StructureBuilder("Root").Build(); - auto member = Schema::CreateMember("x", ShapeType::Integer); - LambdaStruct rootStruct(*root, [&](ShapeSerializer& ser) { ser.WriteInteger(*member, 99); }); - s.WriteStruct(*root, rootStruct); - auto payload = s.GetPayload().GetResult(); +TEST_F(CborShapeDeserializerTest, IndefiniteLengthStruct) { + auto root = Schema::StructureBuilder("Root").PutMember("x", Schema::CreateInteger("I")).Build(); + auto x = root->GetMember("x").value(); + auto payload = Encode(root, [&](ShapeSerializer& ser) { ser.WriteInteger(*x, 99); }); CborShapeDeserializer d(reinterpret_cast(payload.data()), payload.size()); - size_t count = d.BeginStruct(); - EXPECT_EQ(count, 0u); - EXPECT_FALSE(d.IsBreak()); - auto key = d.ReadKey(); - ASSERT_TRUE(key.has_value()); - EXPECT_EQ(key.value(), "x"); - auto val = d.ReadInteger(); - ASSERT_TRUE(val.has_value()); - EXPECT_EQ(val.value(), 99); - EXPECT_TRUE(d.IsBreak()); - d.EndStruct(); + Aws::Crt::Optional got; + d.ReadStruct(*root, [&](const Schema& m, ShapeDeserializer& de) { got = de.ReadInteger(m); }); + ASSERT_TRUE(got.has_value()); + EXPECT_EQ(got.value(), 99); } TEST_F(CborShapeDeserializerTest, TimestampFromFloat) { - // tag(1) followed by float64 1234567890.5 - // C1 = tag(1), FB = float64, 41D26580B4A00000 = IEEE 754 1234567890.5 + + auto scalar = Schema::CreateTimestamp("T"); const unsigned char data[] = {0xC1, 0xFB, 0x41, 0xD2, 0x65, 0x80, 0xB4, 0xA0, 0x00, 0x00}; CborShapeDeserializer d(data, sizeof(data)); - auto ts = d.ReadTimestamp(); + auto ts = d.ReadTimestamp(*scalar); ASSERT_TRUE(ts.has_value()); EXPECT_DOUBLE_EQ(ts.value().SecondsWithMSPrecision(), 1234567890.5); } TEST_F(CborShapeDeserializerTest, TimestampNegativeIntReturnsEmpty) { - // tag(1) followed by negative int: C1 = tag(1), 20 = negint(0) meaning -1 + auto scalar = Schema::CreateTimestamp("T"); const unsigned char data[] = {0xC1, 0x20}; CborShapeDeserializer d(data, sizeof(data)); - auto ts = d.ReadTimestamp(); + auto ts = d.ReadTimestamp(*scalar); EXPECT_FALSE(ts.has_value()); } TEST_F(CborShapeDeserializerTest, TimestampNegativeFloatReturnsEmpty) { - // tag(1) followed by float64 -1.0 - // C1 = tag(1), FB = float64, BFF0000000000000 = IEEE 754 -1.0 + auto scalar = Schema::CreateTimestamp("T"); const unsigned char data[] = {0xC1, 0xFB, 0xBF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; CborShapeDeserializer d(data, sizeof(data)); - auto ts = d.ReadTimestamp(); + auto ts = d.ReadTimestamp(*scalar); EXPECT_FALSE(ts.has_value()); } TEST_F(CborShapeDeserializerTest, TimestampInvalidTagReturnsEmpty) { - // tag(2) followed by uint 100 — tag 2 is not epoch timestamp - // C2 = tag(2), 18 64 = uint(100) + auto scalar = Schema::CreateTimestamp("T"); const unsigned char data[] = {0xC2, 0x18, 0x64}; CborShapeDeserializer d(data, sizeof(data)); - auto ts = d.ReadTimestamp(); + auto ts = d.ReadTimestamp(*scalar); EXPECT_FALSE(ts.has_value()); } - -TEST_F(CborShapeDeserializerTest, ReadKeyOnNonStringReturnsEmpty) { - // Raw uint 42 — not a text string - // 18 2A = uint(42) - const unsigned char data[] = {0x18, 0x2A}; - CborShapeDeserializer d(data, sizeof(data)); - auto key = d.ReadKey(); - EXPECT_FALSE(key.has_value()); -} diff --git a/tests/aws-cpp-sdk-core-tests/smithy/client/schema/ClientProtocolTest.cpp b/tests/aws-cpp-sdk-core-tests/smithy/client/schema/ClientProtocolTest.cpp new file mode 100644 index 000000000000..602f895c2ad3 --- /dev/null +++ b/tests/aws-cpp-sdk-core-tests/smithy/client/schema/ClientProtocolTest.cpp @@ -0,0 +1,119 @@ +/** + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0. + */ +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "SchemaSerializerTestHelpers.h" + +using namespace smithy::schema; + +class ClientProtocolTest : public Aws::Testing::AwsCppSdkGTestSuite {}; + +namespace { + +std::shared_ptr TwoMemberRoot(bool withXmlName) { + auto builder = withXmlName ? Schema::StructureBuilder("Root", {{XmlNameTrait::KEY(), Aws::MakeShared("Test", "Root")}}) + : Schema::StructureBuilder("Root"); + builder.PutMember("name", Schema::CreateString("S")).PutMember("count", Schema::CreateInteger("I")); + return builder.Build(); +} + +void RoundTrip(const ClientProtocol& protocol, const std::shared_ptr& root) { + auto name = root->GetMember("name").value(); + auto count = root->GetMember("count").value(); + LambdaStruct shape(*root, [&](ShapeSerializer& ser) { + ser.WriteString(*name, "hello"); + ser.WriteInteger(*count, 42); + }); + auto payload = protocol.SerializeInput(*root, shape); + ASSERT_TRUE(payload.IsSuccess()); + const Aws::String bytes = payload.GetResult(); + + auto d = protocol.CreateOutputDeserializer(reinterpret_cast(bytes.data()), bytes.size()); + ASSERT_NE(d, nullptr); + Aws::Crt::Optional gotName; + Aws::Crt::Optional gotCount; + d->ReadStruct(*root, [&](const Schema& m, ShapeDeserializer& de) { + if (m.GetMemberName() == "name") { + gotName = de.ReadString(m); + } else if (m.GetMemberName() == "count") { + gotCount = de.ReadInteger(m); + } + }); + ASSERT_TRUE(gotName.has_value()); + EXPECT_EQ(gotName.value(), "hello"); + ASSERT_TRUE(gotCount.has_value()); + EXPECT_EQ(gotCount.value(), 42); +} + +} + +TEST_F(ClientProtocolTest, ContentTypesAndIds) { + EXPECT_EQ(RestJsonProtocol().GetContentType(), "application/json"); + EXPECT_EQ(RestJsonProtocol().GetProtocolId(), "aws.protocols#restJson1"); + EXPECT_EQ(AwsJson10Protocol().GetContentType(), "application/x-amz-json-1.0"); + EXPECT_EQ(AwsJson11Protocol().GetContentType(), "application/x-amz-json-1.1"); + EXPECT_EQ(RpcV2CborProtocol().GetContentType(), "application/cbor"); + EXPECT_EQ(RestXmlProtocol().GetContentType(), "application/xml"); + EXPECT_EQ(AwsQueryProtocol().GetContentType(), "application/x-www-form-urlencoded"); + EXPECT_EQ(Ec2QueryProtocol().GetContentType(), "application/x-www-form-urlencoded"); + EXPECT_EQ(Ec2QueryProtocol().GetProtocolId(), "aws.protocols#ec2Query"); +} + +TEST_F(ClientProtocolTest, RestJsonRoundTrip) { RoundTrip(RestJsonProtocol(), TwoMemberRoot(false)); } +TEST_F(ClientProtocolTest, AwsJson11RoundTrip) { RoundTrip(AwsJson11Protocol(), TwoMemberRoot(false)); } +TEST_F(ClientProtocolTest, RpcV2CborRoundTrip) { RoundTrip(RpcV2CborProtocol(), TwoMemberRoot(false)); } +TEST_F(ClientProtocolTest, RestXmlRoundTrip) { RoundTrip(RestXmlProtocol(), TwoMemberRoot(true)); } + +TEST_F(ClientProtocolTest, AwsQuerySerializesForm) { + auto root = TwoMemberRoot(false); + auto name = root->GetMember("name").value(); + auto count = root->GetMember("count").value(); + LambdaStruct shape(*root, [&](ShapeSerializer& ser) { + ser.WriteString(*name, "hello"); + ser.WriteInteger(*count, 42); + }); + auto payload = AwsQueryProtocol().SerializeInput(*root, shape); + ASSERT_TRUE(payload.IsSuccess()); + EXPECT_EQ(payload.GetResult(), "name=hello&count=42"); +} + +TEST_F(ClientProtocolTest, Ec2QuerySerializesCapitalizedForm) { + auto root = TwoMemberRoot(false); + auto name = root->GetMember("name").value(); + auto count = root->GetMember("count").value(); + LambdaStruct shape(*root, [&](ShapeSerializer& ser) { + ser.WriteString(*name, "hello"); + ser.WriteInteger(*count, 42); + }); + auto payload = Ec2QueryProtocol().SerializeInput(*root, shape); + ASSERT_TRUE(payload.IsSuccess()); + EXPECT_EQ(payload.GetResult(), "Name=hello&Count=42"); +} + +TEST_F(ClientProtocolTest, QueryResponseParsedAsXml) { + + auto root = Schema::StructureBuilder("Root", {{XmlNameTrait::KEY(), Aws::MakeShared("Test", "Root")}}) + .PutMember("status", Schema::CreateString("S")) + .Build(); + const Aws::String xml = "ok"; + auto d = AwsQueryProtocol().CreateOutputDeserializer(reinterpret_cast(xml.data()), xml.size()); + Aws::Crt::Optional got; + d->ReadStruct(*root, [&](const Schema& m, ShapeDeserializer& de) { + if (m.GetMemberName() == "status") { + got = de.ReadString(m); + } + }); + ASSERT_TRUE(got.has_value()); + EXPECT_EQ(got.value(), "ok"); +} diff --git a/tests/aws-cpp-sdk-core-tests/smithy/client/schema/CodecTest.cpp b/tests/aws-cpp-sdk-core-tests/smithy/client/schema/CodecTest.cpp new file mode 100644 index 000000000000..e4207e29e2e6 --- /dev/null +++ b/tests/aws-cpp-sdk-core-tests/smithy/client/schema/CodecTest.cpp @@ -0,0 +1,81 @@ +/** + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0. + */ +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "SchemaSerializerTestHelpers.h" + +using namespace smithy::schema; + +class CodecTest : public Aws::Testing::AwsCppSdkGTestSuite {}; + +namespace { + +void RoundTrip(const Codec& codec, const std::shared_ptr& root) { + auto name = root->GetMember("name").value(); + auto count = root->GetMember("count").value(); + + LambdaStruct shape(*root, [&](ShapeSerializer& ser) { + ser.WriteString(*name, "hello"); + ser.WriteInteger(*count, 42); + }); + auto payload = codec.Serialize(*root, shape); + ASSERT_TRUE(payload.IsSuccess()); + const Aws::String bytes = payload.GetResult(); + + auto d = codec.CreateDeserializer(reinterpret_cast(bytes.data()), bytes.size()); + ASSERT_NE(d, nullptr); + Aws::Crt::Optional gotName; + Aws::Crt::Optional gotCount; + d->ReadStruct(*root, [&](const Schema& m, ShapeDeserializer& de) { + if (m.GetMemberName() == "name") { + gotName = de.ReadString(m); + } else if (m.GetMemberName() == "count") { + gotCount = de.ReadInteger(m); + } + }); + ASSERT_TRUE(gotName.has_value()); + EXPECT_EQ(gotName.value(), "hello"); + ASSERT_TRUE(gotCount.has_value()); + EXPECT_EQ(gotCount.value(), 42); +} + +} + +TEST_F(CodecTest, JsonRoundTrip) { + auto root = Schema::StructureBuilder("Root") + .PutMember("name", Schema::CreateString("S")) + .PutMember("count", Schema::CreateInteger("I")) + .Build(); + JsonCodec codec; + RoundTrip(codec, root); +} + +TEST_F(CodecTest, CborRoundTrip) { + auto root = Schema::StructureBuilder("Root") + .PutMember("name", Schema::CreateString("S")) + .PutMember("count", Schema::CreateInteger("I")) + .Build(); + CborCodec codec; + RoundTrip(codec, root); +} + +TEST_F(CodecTest, XmlRoundTrip) { + + auto root = Schema::StructureBuilder("Root", {{XmlNameTrait::KEY(), Aws::MakeShared("Test", "Root")}}) + .PutMember("name", Schema::CreateString("S")) + .PutMember("count", Schema::CreateInteger("I")) + .Build(); + XmlCodec codec; + RoundTrip(codec, root); +} diff --git a/tests/aws-cpp-sdk-core-tests/smithy/client/schema/JsonShapeDeserializerTest.cpp b/tests/aws-cpp-sdk-core-tests/smithy/client/schema/JsonShapeDeserializerTest.cpp new file mode 100644 index 000000000000..f0d5798f8d84 --- /dev/null +++ b/tests/aws-cpp-sdk-core-tests/smithy/client/schema/JsonShapeDeserializerTest.cpp @@ -0,0 +1,360 @@ +/** + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "SchemaSerializerTestHelpers.h" + +using namespace smithy::schema; + +class JsonShapeDeserializerTest : public Aws::Testing::AwsCppSdkGTestSuite {}; + +namespace { + +Aws::String Encode(const std::shared_ptr& root, const std::function& writeMembers) { + JsonShapeSerializer s; + LambdaStruct rootStruct(*root, writeMembers); + s.WriteStruct(*root, rootStruct); + return s.GetPayload().GetResult(); +} + +} + +TEST_F(JsonShapeDeserializerTest, Boolean) { + auto root = Schema::StructureBuilder("Root").PutMember("enabled", Schema::CreateBoolean("B")).Build(); + auto enabled = root->GetMember("enabled").value(); + auto payload = Encode(root, [&](ShapeSerializer& ser) { ser.WriteBoolean(*enabled, true); }); + + JsonShapeDeserializer d(reinterpret_cast(payload.data()), payload.size()); + Aws::Crt::Optional got; + d.ReadStruct(*root, [&](const Schema& m, ShapeDeserializer& de) { got = de.ReadBoolean(m); }); + ASSERT_TRUE(got.has_value()); + EXPECT_TRUE(got.value()); +} + +TEST_F(JsonShapeDeserializerTest, Integer) { + auto root = Schema::StructureBuilder("Root").PutMember("n", Schema::CreateInteger("I")).Build(); + auto n = root->GetMember("n").value(); + auto payload = Encode(root, [&](ShapeSerializer& ser) { ser.WriteInteger(*n, -42); }); + + JsonShapeDeserializer d(reinterpret_cast(payload.data()), payload.size()); + Aws::Crt::Optional got; + d.ReadStruct(*root, [&](const Schema& m, ShapeDeserializer& de) { got = de.ReadInteger(m); }); + ASSERT_TRUE(got.has_value()); + EXPECT_EQ(got.value(), -42); +} + +TEST_F(JsonShapeDeserializerTest, Long) { + auto root = Schema::StructureBuilder("Root").PutMember("big", Schema::CreateLong("L")).Build(); + auto big = root->GetMember("big").value(); + auto payload = Encode(root, [&](ShapeSerializer& ser) { ser.WriteLong(*big, 9876543210LL); }); + + JsonShapeDeserializer d(reinterpret_cast(payload.data()), payload.size()); + Aws::Crt::Optional got; + d.ReadStruct(*root, [&](const Schema& m, ShapeDeserializer& de) { got = de.ReadLong(m); }); + ASSERT_TRUE(got.has_value()); + EXPECT_EQ(got.value(), 9876543210LL); +} + +TEST_F(JsonShapeDeserializerTest, Double) { + auto root = Schema::StructureBuilder("Root").PutMember("d", Schema::CreateDouble("D")).Build(); + auto member = root->GetMember("d").value(); + auto payload = Encode(root, [&](ShapeSerializer& ser) { ser.WriteDouble(*member, 3.25); }); + + JsonShapeDeserializer d(reinterpret_cast(payload.data()), payload.size()); + Aws::Crt::Optional got; + d.ReadStruct(*root, [&](const Schema& m, ShapeDeserializer& de) { got = de.ReadDouble(m); }); + ASSERT_TRUE(got.has_value()); + EXPECT_DOUBLE_EQ(got.value(), 3.25); +} + +TEST_F(JsonShapeDeserializerTest, NonFiniteDoubles) { + auto root = Schema::StructureBuilder("Root") + .PutMember("nan", Schema::CreateDouble("D1")) + .PutMember("inf", Schema::CreateDouble("D2")) + .PutMember("ninf", Schema::CreateDouble("D3")) + .Build(); + auto nan = root->GetMember("nan").value(); + auto inf = root->GetMember("inf").value(); + auto ninf = root->GetMember("ninf").value(); + auto payload = Encode(root, [&](ShapeSerializer& ser) { + ser.WriteDouble(*nan, std::numeric_limits::quiet_NaN()); + ser.WriteDouble(*inf, std::numeric_limits::infinity()); + ser.WriteDouble(*ninf, -std::numeric_limits::infinity()); + }); + + JsonShapeDeserializer d(reinterpret_cast(payload.data()), payload.size()); + bool sawNan = false, sawInf = false, sawNinf = false; + d.ReadStruct(*root, [&](const Schema& m, ShapeDeserializer& de) { + auto v = de.ReadDouble(m); + ASSERT_TRUE(v.has_value()); + if (m.GetMemberName() == "nan") { + sawNan = std::isnan(v.value()); + } else if (m.GetMemberName() == "inf") { + sawInf = std::isinf(v.value()) && v.value() > 0; + } else if (m.GetMemberName() == "ninf") { + sawNinf = std::isinf(v.value()) && v.value() < 0; + } + }); + EXPECT_TRUE(sawNan); + EXPECT_TRUE(sawInf); + EXPECT_TRUE(sawNinf); +} + +TEST_F(JsonShapeDeserializerTest, String) { + auto root = Schema::StructureBuilder("Root").PutMember("name", Schema::CreateString("S")).Build(); + auto member = root->GetMember("name").value(); + auto payload = Encode(root, [&](ShapeSerializer& ser) { ser.WriteString(*member, "he\"llo\n"); }); + + JsonShapeDeserializer d(reinterpret_cast(payload.data()), payload.size()); + Aws::Crt::Optional got; + d.ReadStruct(*root, [&](const Schema& m, ShapeDeserializer& de) { got = de.ReadString(m); }); + ASSERT_TRUE(got.has_value()); + EXPECT_EQ(got.value(), "he\"llo\n"); +} + +TEST_F(JsonShapeDeserializerTest, Blob) { + auto root = Schema::StructureBuilder("Root").PutMember("data", Schema::CreateBlob("Bl")).Build(); + auto member = root->GetMember("data").value(); + Aws::Utils::ByteBuffer blob(3); + blob[0] = 0x66; + blob[1] = 0x6f; + blob[2] = 0x6f; + auto payload = Encode(root, [&](ShapeSerializer& ser) { ser.WriteBlob(*member, blob); }); + + JsonShapeDeserializer d(reinterpret_cast(payload.data()), payload.size()); + Aws::Crt::Optional got; + d.ReadStruct(*root, [&](const Schema& m, ShapeDeserializer& de) { got = de.ReadBlob(m); }); + ASSERT_TRUE(got.has_value()); + ASSERT_EQ(got.value().GetLength(), 3u); + EXPECT_EQ(got.value()[0], 0x66); + EXPECT_EQ(got.value()[1], 0x6f); + EXPECT_EQ(got.value()[2], 0x6f); +} + +TEST_F(JsonShapeDeserializerTest, TimestampEpochSeconds) { + + auto root = Schema::StructureBuilder("Root").PutMember("ts", Schema::CreateTimestamp("T")).Build(); + const Aws::String payload = "{\"ts\":1234567890}"; + + JsonShapeDeserializer d(reinterpret_cast(payload.data()), payload.size()); + Aws::Crt::Optional got; + d.ReadStruct(*root, [&](const Schema& m, ShapeDeserializer& de) { got = de.ReadTimestamp(m); }); + ASSERT_TRUE(got.has_value()); + EXPECT_EQ(got.value().Seconds(), 1234567890); +} + +TEST_F(JsonShapeDeserializerTest, TimestampDateTimeString) { + + auto root = Schema::StructureBuilder("Root").PutMember("ts", Schema::CreateTimestamp("T")).Build(); + const Aws::String payload = "{\"ts\":\"2009-02-13T23:31:30Z\"}"; + + JsonShapeDeserializer d(reinterpret_cast(payload.data()), payload.size()); + Aws::Crt::Optional got; + d.ReadStruct(*root, [&](const Schema& m, ShapeDeserializer& de) { got = de.ReadTimestamp(m); }); + ASSERT_TRUE(got.has_value()); + EXPECT_EQ(got.value().Seconds(), 1234567890); +} + +TEST_F(JsonShapeDeserializerTest, JsonNameOverride) { + auto root = Schema::StructureBuilder("Root") + .PutMember("internalName", Schema::CreateString("S"), + {{JsonNameTrait::KEY(), Aws::MakeShared("Schema", "ExternalName")}}) + .Build(); + auto member = root->GetMember("internalName").value(); + auto payload = Encode(root, [&](ShapeSerializer& ser) { ser.WriteString(*member, "hello"); }); + + EXPECT_NE(payload.find("ExternalName"), Aws::String::npos); + EXPECT_EQ(payload.find("internalName"), Aws::String::npos); + + JsonShapeDeserializer d(reinterpret_cast(payload.data()), payload.size()); + Aws::Crt::Optional got; + d.ReadStruct(*root, [&](const Schema& m, ShapeDeserializer& de) { got = de.ReadString(m); }); + ASSERT_TRUE(got.has_value()); + EXPECT_EQ(got.value(), "hello"); +} + +TEST_F(JsonShapeDeserializerTest, NullMemberIsSkipped) { + auto root = Schema::StructureBuilder("Root").PutMember("item", Schema::CreateString("S")).Build(); + auto member = root->GetMember("item").value(); + auto payload = Encode(root, [&](ShapeSerializer& ser) { ser.WriteNull(*member); }); + + JsonShapeDeserializer d(reinterpret_cast(payload.data()), payload.size()); + int calls = 0; + d.ReadStruct(*root, [&](const Schema&, ShapeDeserializer&) { ++calls; }); + EXPECT_EQ(calls, 0); +} + +TEST_F(JsonShapeDeserializerTest, ListOfIntegers) { + auto listBuilder = Schema::ListBuilder("Nums"); + auto root = Schema::StructureBuilder("Root").PutMember("nums", listBuilder).Build(); + auto nums = root->GetMember("nums").value(); + auto elem = Schema::CreateMember("member", ShapeType::Integer); + auto payload = Encode(root, [&](ShapeSerializer& ser) { + ser.WriteList(*nums, 3, [&](ShapeSerializer& lser) { + lser.WriteInteger(*elem, 10); + lser.WriteInteger(*elem, 20); + lser.WriteInteger(*elem, 30); + }); + }); + + JsonShapeDeserializer d(reinterpret_cast(payload.data()), payload.size()); + Aws::Vector values; + d.ReadStruct(*root, [&](const Schema& m, ShapeDeserializer& de) { + if (m.GetMemberName() == "nums") { + de.ReadList(m, [&](ShapeDeserializer& ede) { + auto v = ede.ReadInteger(*elem); + if (v.has_value()) { + values.push_back(v.value()); + } + }); + } + }); + ASSERT_EQ(values.size(), 3u); + EXPECT_EQ(values[0], 10); + EXPECT_EQ(values[1], 20); + EXPECT_EQ(values[2], 30); +} + +TEST_F(JsonShapeDeserializerTest, MapOfStrings) { + auto mapBuilder = Schema::MapBuilder("Headers"); + auto root = Schema::StructureBuilder("Root").PutMember("headers", mapBuilder).Build(); + auto headers = root->GetMember("headers").value(); + auto valSchema = Schema::CreateMember("value", ShapeType::String); + auto payload = Encode(root, [&](ShapeSerializer& ser) { + ser.WriteMap(*headers, 2, [&](MapSerializer& mapSer) { + mapSer.WriteEntry("foo", [&](ShapeSerializer& vser) { vser.WriteString(*valSchema, "bar"); }); + mapSer.WriteEntry("baz", [&](ShapeSerializer& vser) { vser.WriteString(*valSchema, "qux"); }); + }); + }); + + JsonShapeDeserializer d(reinterpret_cast(payload.data()), payload.size()); + Aws::Map entries; + d.ReadStruct(*root, [&](const Schema& m, ShapeDeserializer& de) { + if (m.GetMemberName() == "headers") { + de.ReadMap(m, [&](const Aws::String& key, ShapeDeserializer& vde) { + auto v = vde.ReadString(*valSchema); + if (v.has_value()) { + entries[key] = v.value(); + } + }); + } + }); + ASSERT_EQ(entries.size(), 2u); + EXPECT_EQ(entries["foo"], "bar"); + EXPECT_EQ(entries["baz"], "qux"); +} + +TEST_F(JsonShapeDeserializerTest, NestedStructure) { + auto metaBuilder = Schema::StructureBuilder("Meta"); + metaBuilder.PutMember("key", Schema::CreateString("S")); + auto root = Schema::StructureBuilder("Root").PutMember("meta", metaBuilder).Build(); + auto meta = root->GetMember("meta").value(); + auto inner = meta->GetMemberTarget().value()->GetMember("key").value(); + auto payload = Encode(root, [&](ShapeSerializer& ser) { + ser.WriteStruct(*meta, LambdaStruct(*meta, [&](ShapeSerializer& ser2) { ser2.WriteString(*inner, "val"); })); + }); + + JsonShapeDeserializer d(reinterpret_cast(payload.data()), payload.size()); + Aws::String got; + d.ReadStruct(*root, [&](const Schema& m, ShapeDeserializer& de) { + if (m.GetMemberName() == "meta") { + de.ReadStruct(*m.GetMemberTarget().value(), [&](const Schema& im, ShapeDeserializer& ide) { + if (im.GetMemberName() == "key") { + auto v = ide.ReadString(im); + if (v.has_value()) { + got = v.value(); + } + } + }); + } + }); + EXPECT_EQ(got, "val"); +} + +TEST_F(JsonShapeDeserializerTest, SkipsUnknownField) { + + auto root = Schema::StructureBuilder("Root").PutMember("known", Schema::CreateInteger("I")).Build(); + auto known = Schema::CreateMember("known", ShapeType::Integer); + auto extra = Schema::CreateMember("extra", ShapeType::String); + auto payload = Encode(root, [&](ShapeSerializer& ser) { + ser.WriteInteger(*known, 5); + ser.WriteString(*extra, "ignored"); + }); + + JsonShapeDeserializer d(reinterpret_cast(payload.data()), payload.size()); + Aws::Map got; + d.ReadStruct(*root, [&](const Schema& m, ShapeDeserializer& de) { + auto v = de.ReadInteger(m); + if (v.has_value()) { + got[m.GetMemberName()] = v.value(); + } + }); + ASSERT_EQ(got.size(), 1u); + EXPECT_EQ(got["known"], 5); +} + +TEST_F(JsonShapeDeserializerTest, EmptyOptionalOnTypeMismatch) { + auto root = Schema::StructureBuilder("Root").PutMember("val", Schema::CreateString("S")).Build(); + auto member = root->GetMember("val").value(); + auto payload = Encode(root, [&](ShapeSerializer& ser) { ser.WriteString(*member, "hello"); }); + + JsonShapeDeserializer d(reinterpret_cast(payload.data()), payload.size()); + Aws::Crt::Optional got; + d.ReadStruct(*root, [&](const Schema& m, ShapeDeserializer& de) { got = de.ReadInteger(m); }); + EXPECT_FALSE(got.has_value()); +} + +TEST_F(JsonShapeDeserializerTest, MultipleScalarsByIndex) { + auto root = Schema::StructureBuilder("Root") + .PutMember("a", Schema::CreateBoolean("B")) + .PutMember("b", Schema::CreateInteger("I")) + .PutMember("c", Schema::CreateString("S")) + .Build(); + auto a = root->GetMember("a").value(); + auto b = root->GetMember("b").value(); + auto c = root->GetMember("c").value(); + auto payload = Encode(root, [&](ShapeSerializer& ser) { + ser.WriteBoolean(*a, true); + ser.WriteInteger(*b, 7); + ser.WriteString(*c, "x"); + }); + + JsonShapeDeserializer d(reinterpret_cast(payload.data()), payload.size()); + Aws::Crt::Optional ba; + Aws::Crt::Optional bb; + Aws::Crt::Optional bc; + d.ReadStruct(*root, [&](const Schema& m, ShapeDeserializer& de) { + switch (m.GetMemberIndex()) { + case 0: + ba = de.ReadBoolean(m); + break; + case 1: + bb = de.ReadInteger(m); + break; + case 2: + bc = de.ReadString(m); + break; + default: + break; + } + }); + ASSERT_TRUE(ba.has_value()); + EXPECT_TRUE(ba.value()); + ASSERT_TRUE(bb.has_value()); + EXPECT_EQ(bb.value(), 7); + ASSERT_TRUE(bc.has_value()); + EXPECT_EQ(bc.value(), "x"); +} diff --git a/tests/aws-cpp-sdk-core-tests/smithy/client/schema/QueryShapeSerializerTest.cpp b/tests/aws-cpp-sdk-core-tests/smithy/client/schema/QueryShapeSerializerTest.cpp new file mode 100644 index 000000000000..cdee91de0985 --- /dev/null +++ b/tests/aws-cpp-sdk-core-tests/smithy/client/schema/QueryShapeSerializerTest.cpp @@ -0,0 +1,290 @@ +/** + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0. + */ +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "SchemaSerializerTestHelpers.h" + +using namespace smithy::schema; + +class QueryShapeSerializerTest : public Aws::Testing::AwsCppSdkGTestSuite {}; + +TEST_F(QueryShapeSerializerTest, EmptyStructure) { + QueryShapeSerializer s; + auto root = Schema::StructureBuilder("Root").Build(); + LambdaStruct rootStruct(*root, [](ShapeSerializer&) {}); + s.WriteStruct(*root, rootStruct); + auto outcome = s.GetPayload(); + ASSERT_TRUE(outcome.IsSuccess()); + EXPECT_EQ(outcome.GetResult(), ""); +} + +TEST_F(QueryShapeSerializerTest, SingleString) { + QueryShapeSerializer s; + auto root = Schema::StructureBuilder("Root").Build(); + auto member = Schema::CreateMember("stringValue", ShapeType::String); + LambdaStruct rootStruct(*root, [&](ShapeSerializer& ser) { ser.WriteString(*member, "hello"); }); + s.WriteStruct(*root, rootStruct); + auto outcome = s.GetPayload(); + ASSERT_TRUE(outcome.IsSuccess()); + EXPECT_EQ(outcome.GetResult(), "stringValue=hello"); +} + +TEST_F(QueryShapeSerializerTest, MultipleScalarsJoinedByAmpersand) { + QueryShapeSerializer s; + auto root = Schema::StructureBuilder("Root").Build(); + auto m1 = Schema::CreateMember("stringValue", ShapeType::String); + auto m2 = Schema::CreateMember("trueValue", ShapeType::Boolean); + auto m3 = Schema::CreateMember("intValue", ShapeType::Integer); + auto m4 = Schema::CreateMember("longValue", ShapeType::Long); + LambdaStruct rootStruct(*root, [&](ShapeSerializer& ser) { + ser.WriteString(*m1, "abc"); + ser.WriteBoolean(*m2, true); + ser.WriteInteger(*m3, 3); + ser.WriteLong(*m4, 9876543210LL); + }); + s.WriteStruct(*root, rootStruct); + auto outcome = s.GetPayload(); + ASSERT_TRUE(outcome.IsSuccess()); + EXPECT_EQ(outcome.GetResult(), "stringValue=abc&trueValue=true&intValue=3&longValue=9876543210"); +} + +TEST_F(QueryShapeSerializerTest, UrlEncodesStringValues) { + QueryShapeSerializer s; + auto root = Schema::StructureBuilder("Root").Build(); + auto member = Schema::CreateMember("q", ShapeType::String); + LambdaStruct rootStruct(*root, [&](ShapeSerializer& ser) { ser.WriteString(*member, "a b&c=d"); }); + s.WriteStruct(*root, rootStruct); + auto outcome = s.GetPayload(); + ASSERT_TRUE(outcome.IsSuccess()); + + const auto& body = outcome.GetResult(); + EXPECT_EQ(body.find("a b&c=d"), Aws::String::npos); + EXPECT_EQ(body.rfind("q=", 0), 0u); + EXPECT_NE(body.find("%26"), Aws::String::npos); + EXPECT_NE(body.find("%3D"), Aws::String::npos); +} + +TEST_F(QueryShapeSerializerTest, Double) { + QueryShapeSerializer s; + auto root = Schema::StructureBuilder("Root").Build(); + auto member = Schema::CreateMember("ratio", ShapeType::Double); + LambdaStruct rootStruct(*root, [&](ShapeSerializer& ser) { ser.WriteDouble(*member, 6.5); }); + s.WriteStruct(*root, rootStruct); + auto outcome = s.GetPayload(); + ASSERT_TRUE(outcome.IsSuccess()); + EXPECT_EQ(outcome.GetResult(), "ratio=6.5"); +} + +TEST_F(QueryShapeSerializerTest, NonFiniteFloats) { + QueryShapeSerializer s; + auto root = Schema::StructureBuilder("Root").Build(); + auto nan = Schema::CreateMember("nanValue", ShapeType::Double); + auto inf = Schema::CreateMember("infValue", ShapeType::Double); + auto ninf = Schema::CreateMember("negInfValue", ShapeType::Double); + LambdaStruct rootStruct(*root, [&](ShapeSerializer& ser) { + ser.WriteDouble(*nan, std::numeric_limits::quiet_NaN()); + ser.WriteDouble(*inf, std::numeric_limits::infinity()); + ser.WriteDouble(*ninf, -std::numeric_limits::infinity()); + }); + s.WriteStruct(*root, rootStruct); + auto outcome = s.GetPayload(); + ASSERT_TRUE(outcome.IsSuccess()); + EXPECT_EQ(outcome.GetResult(), "nanValue=NaN&infValue=Infinity&negInfValue=-Infinity"); +} + +TEST_F(QueryShapeSerializerTest, XmlNameOverridesMemberName) { + QueryShapeSerializer s; + auto root = Schema::StructureBuilder("Root").Build(); + auto member = Schema::CreateMember("internalName", ShapeType::String, + {{XmlNameTrait::KEY(), Aws::MakeShared("Schema", "ExternalName")}}); + LambdaStruct rootStruct(*root, [&](ShapeSerializer& ser) { ser.WriteString(*member, "hello"); }); + s.WriteStruct(*root, rootStruct); + auto outcome = s.GetPayload(); + ASSERT_TRUE(outcome.IsSuccess()); + EXPECT_EQ(outcome.GetResult(), "ExternalName=hello"); +} + +TEST_F(QueryShapeSerializerTest, NestedStructure) { + QueryShapeSerializer s; + auto root = Schema::StructureBuilder("Root").Build(); + auto nested = Schema::CreateMember("metadata", ShapeType::Structure); + auto inner = Schema::CreateMember("key", ShapeType::String); + LambdaStruct rootStruct(*root, [&](ShapeSerializer& ser) { + ser.WriteStruct(*nested, LambdaStruct(*nested, [&](ShapeSerializer& ser2) { ser2.WriteString(*inner, "val"); })); + }); + s.WriteStruct(*root, rootStruct); + auto outcome = s.GetPayload(); + ASSERT_TRUE(outcome.IsSuccess()); + EXPECT_EQ(outcome.GetResult(), "metadata.key=val"); +} + +TEST_F(QueryShapeSerializerTest, ListOfStrings) { + QueryShapeSerializer s; + auto root = Schema::StructureBuilder("Root").Build(); + auto listMember = Schema::CreateMember("tags", ShapeType::List); + auto elem = Schema::CreateMember("member", ShapeType::String); + LambdaStruct rootStruct(*root, [&](ShapeSerializer& ser) { + ser.WriteList(*listMember, 3, [&](ShapeSerializer& lser) { + lser.WriteString(*elem, "a"); + lser.WriteString(*elem, "b"); + lser.WriteString(*elem, "c"); + }); + }); + s.WriteStruct(*root, rootStruct); + auto outcome = s.GetPayload(); + ASSERT_TRUE(outcome.IsSuccess()); + EXPECT_EQ(outcome.GetResult(), "tags.member.1=a&tags.member.2=b&tags.member.3=c"); +} + +TEST_F(QueryShapeSerializerTest, FlattenedList) { + QueryShapeSerializer s; + auto root = Schema::StructureBuilder("Root").Build(); + auto listMember = + Schema::CreateMember("tags", ShapeType::List, {{XmlFlattenedTrait::KEY(), Aws::MakeShared("Schema")}}); + auto elem = Schema::CreateMember("member", ShapeType::String); + LambdaStruct rootStruct(*root, [&](ShapeSerializer& ser) { + ser.WriteList(*listMember, 2, [&](ShapeSerializer& lser) { + lser.WriteString(*elem, "a"); + lser.WriteString(*elem, "b"); + }); + }); + s.WriteStruct(*root, rootStruct); + auto outcome = s.GetPayload(); + ASSERT_TRUE(outcome.IsSuccess()); + EXPECT_EQ(outcome.GetResult(), "tags.1=a&tags.2=b"); +} + +TEST_F(QueryShapeSerializerTest, ListOfStructures) { + QueryShapeSerializer s; + auto root = Schema::StructureBuilder("Root").Build(); + auto listMember = Schema::CreateMember("items", ShapeType::List); + auto structElem = Schema::CreateMember("member", ShapeType::Structure); + auto field = Schema::CreateMember("id", ShapeType::Integer); + LambdaStruct rootStruct(*root, [&](ShapeSerializer& ser) { + ser.WriteList(*listMember, 2, [&](ShapeSerializer& lser) { + lser.WriteStruct(*structElem, LambdaStruct(*structElem, [&](ShapeSerializer& es) { es.WriteInteger(*field, 1); })); + lser.WriteStruct(*structElem, LambdaStruct(*structElem, [&](ShapeSerializer& es) { es.WriteInteger(*field, 2); })); + }); + }); + s.WriteStruct(*root, rootStruct); + auto outcome = s.GetPayload(); + ASSERT_TRUE(outcome.IsSuccess()); + EXPECT_EQ(outcome.GetResult(), "items.member.1.id=1&items.member.2.id=2"); +} + +TEST_F(QueryShapeSerializerTest, MapOfStrings) { + QueryShapeSerializer s; + auto root = Schema::StructureBuilder("Root").Build(); + auto mapMember = Schema::CreateMember("attributes", ShapeType::Map); + auto valSchema = Schema::CreateMember("value", ShapeType::String); + LambdaStruct rootStruct(*root, [&](ShapeSerializer& ser) { + ser.WriteMap(*mapMember, 2, [&](MapSerializer& mapSer) { + mapSer.WriteEntry("foo", [&](ShapeSerializer& vser) { vser.WriteString(*valSchema, "bar"); }); + mapSer.WriteEntry("baz", [&](ShapeSerializer& vser) { vser.WriteString(*valSchema, "qux"); }); + }); + }); + s.WriteStruct(*root, rootStruct); + auto outcome = s.GetPayload(); + ASSERT_TRUE(outcome.IsSuccess()); + EXPECT_EQ(outcome.GetResult(), + "attributes.entry.1.key=foo&attributes.entry.1.value=bar&attributes.entry.2.key=baz&attributes.entry.2.value=qux"); +} + +TEST_F(QueryShapeSerializerTest, FlattenedMap) { + QueryShapeSerializer s; + auto root = Schema::StructureBuilder("Root").Build(); + auto mapMember = + Schema::CreateMember("attr", ShapeType::Map, {{XmlFlattenedTrait::KEY(), Aws::MakeShared("Schema")}}); + auto valSchema = Schema::CreateMember("value", ShapeType::String); + LambdaStruct rootStruct(*root, [&](ShapeSerializer& ser) { + ser.WriteMap(*mapMember, 1, [&](MapSerializer& mapSer) { + mapSer.WriteEntry("k1", [&](ShapeSerializer& vser) { vser.WriteString(*valSchema, "v1"); }); + }); + }); + s.WriteStruct(*root, rootStruct); + auto outcome = s.GetPayload(); + ASSERT_TRUE(outcome.IsSuccess()); + EXPECT_EQ(outcome.GetResult(), "attr.1.key=k1&attr.1.value=v1"); +} + +TEST_F(QueryShapeSerializerTest, MaxDepthEnforcement) { + QueryShapeSerializer s; + auto root = Schema::StructureBuilder("Root").Build(); + auto nested = Schema::CreateMember("n", ShapeType::Structure); + std::function nest = [&](ShapeSerializer& ser, int remaining) { + if (remaining <= 0) { + return; + } + ser.WriteStruct(*nested, LambdaStruct(*nested, [&, remaining](ShapeSerializer& inner) { nest(inner, remaining - 1); })); + }; + LambdaStruct rootStruct(*root, [&](ShapeSerializer& ser) { nest(ser, 1000); }); + s.WriteStruct(*root, rootStruct); + EXPECT_FALSE(s.GetPayload().IsSuccess()); +} + +TEST_F(QueryShapeSerializerTest, Ec2CapitalizesMemberName) { + QueryShapeSerializer s(QueryShapeSerializer::Flavor::Ec2Query); + auto root = Schema::StructureBuilder("Root").Build(); + auto member = Schema::CreateMember("stringValue", ShapeType::String); + LambdaStruct rootStruct(*root, [&](ShapeSerializer& ser) { ser.WriteString(*member, "hello"); }); + s.WriteStruct(*root, rootStruct); + auto outcome = s.GetPayload(); + ASSERT_TRUE(outcome.IsSuccess()); + EXPECT_EQ(outcome.GetResult(), "StringValue=hello"); +} + +TEST_F(QueryShapeSerializerTest, Ec2CapitalizesXmlName) { + QueryShapeSerializer s(QueryShapeSerializer::Flavor::Ec2Query); + auto root = Schema::StructureBuilder("Root").Build(); + auto member = Schema::CreateMember("internal", ShapeType::String, + {{XmlNameTrait::KEY(), Aws::MakeShared("Schema", "fooBar")}}); + LambdaStruct rootStruct(*root, [&](ShapeSerializer& ser) { ser.WriteString(*member, "v"); }); + s.WriteStruct(*root, rootStruct); + auto outcome = s.GetPayload(); + ASSERT_TRUE(outcome.IsSuccess()); + EXPECT_EQ(outcome.GetResult(), "FooBar=v"); +} + +TEST_F(QueryShapeSerializerTest, Ec2QueryNameUsedVerbatim) { + QueryShapeSerializer s(QueryShapeSerializer::Flavor::Ec2Query); + auto root = Schema::StructureBuilder("Root").Build(); + + auto member = Schema::CreateMember("internal", ShapeType::String, + {{Ec2QueryNameTrait::KEY(), Aws::MakeShared("Schema", "ipAddress")}, + {XmlNameTrait::KEY(), Aws::MakeShared("Schema", "IgnoredName")}}); + LambdaStruct rootStruct(*root, [&](ShapeSerializer& ser) { ser.WriteString(*member, "1.2.3.4"); }); + s.WriteStruct(*root, rootStruct); + auto outcome = s.GetPayload(); + ASSERT_TRUE(outcome.IsSuccess()); + EXPECT_EQ(outcome.GetResult(), "ipAddress=1.2.3.4"); +} + +TEST_F(QueryShapeSerializerTest, Ec2ListIsFlatWithDotN) { + QueryShapeSerializer s(QueryShapeSerializer::Flavor::Ec2Query); + auto root = Schema::StructureBuilder("Root").Build(); + + auto listMember = Schema::CreateMember("items", ShapeType::List); + auto elem = Schema::CreateMember("member", ShapeType::String); + LambdaStruct rootStruct(*root, [&](ShapeSerializer& ser) { + ser.WriteList(*listMember, 2, [&](ShapeSerializer& lser) { + lser.WriteString(*elem, "a"); + lser.WriteString(*elem, "b"); + }); + }); + s.WriteStruct(*root, rootStruct); + auto outcome = s.GetPayload(); + ASSERT_TRUE(outcome.IsSuccess()); + EXPECT_EQ(outcome.GetResult(), "Items.1=a&Items.2=b"); +} diff --git a/tests/aws-cpp-sdk-core-tests/smithy/client/schema/XmlShapeDeserializerTest.cpp b/tests/aws-cpp-sdk-core-tests/smithy/client/schema/XmlShapeDeserializerTest.cpp new file mode 100644 index 000000000000..4abce1395ac5 --- /dev/null +++ b/tests/aws-cpp-sdk-core-tests/smithy/client/schema/XmlShapeDeserializerTest.cpp @@ -0,0 +1,434 @@ +/** + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "SchemaSerializerTestHelpers.h" + +using namespace smithy::schema; + +class XmlShapeDeserializerTest : public Aws::Testing::AwsCppSdkGTestSuite {}; + +namespace { + +SchemaBuilder RootBuilder() { + return Schema::StructureBuilder("Root", {{XmlNameTrait::KEY(), Aws::MakeShared("Test", "Root")}}); +} + +Aws::String Encode(const std::shared_ptr& root, const std::function& writeMembers) { + XmlShapeSerializer s; + LambdaStruct rootStruct(*root, writeMembers); + s.WriteStruct(*root, rootStruct); + return s.GetPayload().GetResult(); +} + +} + +TEST_F(XmlShapeDeserializerTest, Boolean) { + auto root = RootBuilder().PutMember("enabled", Schema::CreateBoolean("B")).Build(); + auto enabled = root->GetMember("enabled").value(); + auto payload = Encode(root, [&](ShapeSerializer& ser) { ser.WriteBoolean(*enabled, true); }); + + XmlShapeDeserializer d(reinterpret_cast(payload.data()), payload.size()); + Aws::Crt::Optional got; + d.ReadStruct(*root, [&](const Schema& m, ShapeDeserializer& de) { got = de.ReadBoolean(m); }); + ASSERT_TRUE(got.has_value()); + EXPECT_TRUE(got.value()); +} + +TEST_F(XmlShapeDeserializerTest, Integer) { + auto root = RootBuilder().PutMember("n", Schema::CreateInteger("I")).Build(); + auto n = root->GetMember("n").value(); + auto payload = Encode(root, [&](ShapeSerializer& ser) { ser.WriteInteger(*n, -42); }); + + XmlShapeDeserializer d(reinterpret_cast(payload.data()), payload.size()); + Aws::Crt::Optional got; + d.ReadStruct(*root, [&](const Schema& m, ShapeDeserializer& de) { got = de.ReadInteger(m); }); + ASSERT_TRUE(got.has_value()); + EXPECT_EQ(got.value(), -42); +} + +TEST_F(XmlShapeDeserializerTest, Long) { + auto root = RootBuilder().PutMember("big", Schema::CreateLong("L")).Build(); + auto big = root->GetMember("big").value(); + auto payload = Encode(root, [&](ShapeSerializer& ser) { ser.WriteLong(*big, 9876543210LL); }); + + XmlShapeDeserializer d(reinterpret_cast(payload.data()), payload.size()); + Aws::Crt::Optional got; + d.ReadStruct(*root, [&](const Schema& m, ShapeDeserializer& de) { got = de.ReadLong(m); }); + ASSERT_TRUE(got.has_value()); + EXPECT_EQ(got.value(), 9876543210LL); +} + +TEST_F(XmlShapeDeserializerTest, Double) { + auto root = RootBuilder().PutMember("d", Schema::CreateDouble("D")).Build(); + auto member = root->GetMember("d").value(); + auto payload = Encode(root, [&](ShapeSerializer& ser) { ser.WriteDouble(*member, 3.25); }); + + XmlShapeDeserializer d(reinterpret_cast(payload.data()), payload.size()); + Aws::Crt::Optional got; + d.ReadStruct(*root, [&](const Schema& m, ShapeDeserializer& de) { got = de.ReadDouble(m); }); + ASSERT_TRUE(got.has_value()); + EXPECT_DOUBLE_EQ(got.value(), 3.25); +} + +TEST_F(XmlShapeDeserializerTest, NonFiniteDoubles) { + auto root = RootBuilder() + .PutMember("nan", Schema::CreateDouble("D1")) + .PutMember("inf", Schema::CreateDouble("D2")) + .PutMember("ninf", Schema::CreateDouble("D3")) + .Build(); + auto nan = root->GetMember("nan").value(); + auto inf = root->GetMember("inf").value(); + auto ninf = root->GetMember("ninf").value(); + auto payload = Encode(root, [&](ShapeSerializer& ser) { + ser.WriteDouble(*nan, std::numeric_limits::quiet_NaN()); + ser.WriteDouble(*inf, std::numeric_limits::infinity()); + ser.WriteDouble(*ninf, -std::numeric_limits::infinity()); + }); + + XmlShapeDeserializer d(reinterpret_cast(payload.data()), payload.size()); + bool sawNan = false, sawInf = false, sawNinf = false; + d.ReadStruct(*root, [&](const Schema& m, ShapeDeserializer& de) { + auto v = de.ReadDouble(m); + ASSERT_TRUE(v.has_value()); + if (m.GetMemberName() == "nan") { + sawNan = std::isnan(v.value()); + } else if (m.GetMemberName() == "inf") { + sawInf = std::isinf(v.value()) && v.value() > 0; + } else if (m.GetMemberName() == "ninf") { + sawNinf = std::isinf(v.value()) && v.value() < 0; + } + }); + EXPECT_TRUE(sawNan); + EXPECT_TRUE(sawInf); + EXPECT_TRUE(sawNinf); +} + +TEST_F(XmlShapeDeserializerTest, StringWithEscapes) { + auto root = RootBuilder().PutMember("msg", Schema::CreateString("S")).Build(); + auto member = root->GetMember("msg").value(); + auto payload = Encode(root, [&](ShapeSerializer& ser) { ser.WriteString(*member, "x < 5 & y > 3"); }); + + XmlShapeDeserializer d(reinterpret_cast(payload.data()), payload.size()); + Aws::Crt::Optional got; + d.ReadStruct(*root, [&](const Schema& m, ShapeDeserializer& de) { got = de.ReadString(m); }); + ASSERT_TRUE(got.has_value()); + EXPECT_EQ(got.value(), "x < 5 & y > 3"); +} + +TEST_F(XmlShapeDeserializerTest, Blob) { + auto root = RootBuilder().PutMember("data", Schema::CreateBlob("Bl")).Build(); + auto member = root->GetMember("data").value(); + Aws::Utils::ByteBuffer blob(3); + blob[0] = 0x66; + blob[1] = 0x6f; + blob[2] = 0x6f; + auto payload = Encode(root, [&](ShapeSerializer& ser) { ser.WriteBlob(*member, blob); }); + + XmlShapeDeserializer d(reinterpret_cast(payload.data()), payload.size()); + Aws::Crt::Optional got; + d.ReadStruct(*root, [&](const Schema& m, ShapeDeserializer& de) { got = de.ReadBlob(m); }); + ASSERT_TRUE(got.has_value()); + ASSERT_EQ(got.value().GetLength(), 3u); + EXPECT_EQ(got.value()[0], 0x66); + EXPECT_EQ(got.value()[1], 0x6f); + EXPECT_EQ(got.value()[2], 0x6f); +} + +TEST_F(XmlShapeDeserializerTest, Timestamp) { + auto root = RootBuilder().PutMember("ts", Schema::CreateTimestamp("T")).Build(); + auto member = root->GetMember("ts").value(); + Aws::Utils::DateTime dt(1234567890.0); + auto payload = Encode(root, [&](ShapeSerializer& ser) { ser.WriteTimestamp(*member, dt); }); + + XmlShapeDeserializer d(reinterpret_cast(payload.data()), payload.size()); + Aws::Crt::Optional got; + d.ReadStruct(*root, [&](const Schema& m, ShapeDeserializer& de) { got = de.ReadTimestamp(m); }); + ASSERT_TRUE(got.has_value()); + EXPECT_EQ(got.value().Seconds(), 1234567890); +} + +TEST_F(XmlShapeDeserializerTest, XmlNameOverride) { + auto root = RootBuilder() + .PutMember("internalName", Schema::CreateString("S"), + {{XmlNameTrait::KEY(), Aws::MakeShared("Test", "ExternalName")}}) + .Build(); + auto member = root->GetMember("internalName").value(); + auto payload = Encode(root, [&](ShapeSerializer& ser) { ser.WriteString(*member, "hello"); }); + EXPECT_NE(payload.find(""), Aws::String::npos); + + XmlShapeDeserializer d(reinterpret_cast(payload.data()), payload.size()); + Aws::Crt::Optional got; + d.ReadStruct(*root, [&](const Schema& m, ShapeDeserializer& de) { got = de.ReadString(m); }); + ASSERT_TRUE(got.has_value()); + EXPECT_EQ(got.value(), "hello"); +} + +TEST_F(XmlShapeDeserializerTest, Attribute) { + auto root = RootBuilder() + .PutMember("id", Schema::CreateString("S"), {{XmlAttributeTrait::KEY(), Aws::MakeShared("Test")}}) + .Build(); + auto member = root->GetMember("id").value(); + auto payload = Encode(root, [&](ShapeSerializer& ser) { ser.WriteString(*member, "abc"); }); + EXPECT_NE(payload.find("id=\"abc\""), Aws::String::npos); + + XmlShapeDeserializer d(reinterpret_cast(payload.data()), payload.size()); + Aws::Crt::Optional got; + d.ReadStruct(*root, [&](const Schema& m, ShapeDeserializer& de) { got = de.ReadString(m); }); + ASSERT_TRUE(got.has_value()); + EXPECT_EQ(got.value(), "abc"); +} + +TEST_F(XmlShapeDeserializerTest, NestedStructure) { + auto metaBuilder = Schema::StructureBuilder("Meta"); + metaBuilder.PutMember("key", Schema::CreateString("S")); + auto root = RootBuilder().PutMember("meta", metaBuilder).Build(); + auto meta = root->GetMember("meta").value(); + auto inner = meta->GetMemberTarget().value()->GetMember("key").value(); + auto payload = Encode(root, [&](ShapeSerializer& ser) { + ser.WriteStruct(*meta, LambdaStruct(*meta, [&](ShapeSerializer& ser2) { ser2.WriteString(*inner, "val"); })); + }); + + XmlShapeDeserializer d(reinterpret_cast(payload.data()), payload.size()); + Aws::String got; + d.ReadStruct(*root, [&](const Schema& m, ShapeDeserializer& de) { + if (m.GetMemberName() == "meta") { + de.ReadStruct(*m.GetMemberTarget().value(), [&](const Schema& im, ShapeDeserializer& ide) { + if (im.GetMemberName() == "key") { + auto v = ide.ReadString(im); + if (v.has_value()) { + got = v.value(); + } + } + }); + } + }); + EXPECT_EQ(got, "val"); +} + +TEST_F(XmlShapeDeserializerTest, WrappedListOfStrings) { + auto listBuilder = Schema::ListBuilder("Tags"); + auto root = RootBuilder().PutMember("tags", listBuilder).Build(); + auto tags = root->GetMember("tags").value(); + auto elem = Schema::CreateMember("member", ShapeType::String); + auto payload = Encode(root, [&](ShapeSerializer& ser) { + ser.WriteList(*tags, 3, [&](ShapeSerializer& lser) { + lser.WriteString(*elem, "a"); + lser.WriteString(*elem, "b"); + lser.WriteString(*elem, "c"); + }); + }); + + XmlShapeDeserializer d(reinterpret_cast(payload.data()), payload.size()); + Aws::Vector values; + d.ReadStruct(*root, [&](const Schema& m, ShapeDeserializer& de) { + if (m.GetMemberName() == "tags") { + de.ReadList(m, [&](ShapeDeserializer& ede) { + auto v = ede.ReadString(*elem); + if (v.has_value()) { + values.push_back(v.value()); + } + }); + } + }); + ASSERT_EQ(values.size(), 3u); + EXPECT_EQ(values[0], "a"); + EXPECT_EQ(values[1], "b"); + EXPECT_EQ(values[2], "c"); +} + +TEST_F(XmlShapeDeserializerTest, FlattenedList) { + auto listBuilder = Schema::ListBuilder("Tags"); + auto root = RootBuilder().PutMember("tags", listBuilder, {{XmlFlattenedTrait::KEY(), Aws::MakeShared("Test")}}).Build(); + auto tags = root->GetMember("tags").value(); + auto elem = Schema::CreateMember("member", ShapeType::String); + auto payload = Encode(root, [&](ShapeSerializer& ser) { + ser.WriteList(*tags, 2, [&](ShapeSerializer& lser) { + lser.WriteString(*elem, "a"); + lser.WriteString(*elem, "b"); + }); + }); + + EXPECT_NE(payload.find("ab"), Aws::String::npos); + + XmlShapeDeserializer d(reinterpret_cast(payload.data()), payload.size()); + Aws::Vector values; + d.ReadStruct(*root, [&](const Schema& m, ShapeDeserializer& de) { + if (m.GetMemberName() == "tags") { + de.ReadList(m, [&](ShapeDeserializer& ede) { + auto v = ede.ReadString(*elem); + if (v.has_value()) { + values.push_back(v.value()); + } + }); + } + }); + ASSERT_EQ(values.size(), 2u); + EXPECT_EQ(values[0], "a"); + EXPECT_EQ(values[1], "b"); +} + +TEST_F(XmlShapeDeserializerTest, ListOfStructures) { + auto listBuilder = Schema::ListBuilder("Items"); + auto root = RootBuilder().PutMember("items", listBuilder).Build(); + auto items = root->GetMember("items").value(); + auto structElem = Schema::CreateMember("member", ShapeType::Structure); + auto field = Schema::CreateMember("id", ShapeType::Integer); + auto payload = Encode(root, [&](ShapeSerializer& ser) { + ser.WriteList(*items, 2, [&](ShapeSerializer& lser) { + lser.WriteStruct(*structElem, LambdaStruct(*structElem, [&](ShapeSerializer& es) { es.WriteInteger(*field, 1); })); + lser.WriteStruct(*structElem, LambdaStruct(*structElem, [&](ShapeSerializer& es) { es.WriteInteger(*field, 2); })); + }); + }); + + auto elemStruct = Schema::StructureBuilder("Item"); + elemStruct.PutMember("id", Schema::CreateInteger("I")); + auto elemSchema = elemStruct.Build(); + + XmlShapeDeserializer d(reinterpret_cast(payload.data()), payload.size()); + Aws::Vector ids; + d.ReadStruct(*root, [&](const Schema& m, ShapeDeserializer& de) { + if (m.GetMemberName() == "items") { + de.ReadList(m, [&](ShapeDeserializer& ede) { + ede.ReadStruct(*elemSchema, [&](const Schema& im, ShapeDeserializer& ide) { + if (im.GetMemberName() == "id") { + auto v = ide.ReadInteger(im); + if (v.has_value()) { + ids.push_back(v.value()); + } + } + }); + }); + } + }); + ASSERT_EQ(ids.size(), 2u); + EXPECT_EQ(ids[0], 1); + EXPECT_EQ(ids[1], 2); +} + +TEST_F(XmlShapeDeserializerTest, WrappedMapOfStrings) { + auto mapBuilder = Schema::MapBuilder("Headers"); + auto root = RootBuilder().PutMember("headers", mapBuilder).Build(); + auto headers = root->GetMember("headers").value(); + auto valSchema = Schema::CreateMember("value", ShapeType::String); + auto payload = Encode(root, [&](ShapeSerializer& ser) { + ser.WriteMap(*headers, 2, [&](MapSerializer& mapSer) { + mapSer.WriteEntry("foo", [&](ShapeSerializer& vser) { vser.WriteString(*valSchema, "bar"); }); + mapSer.WriteEntry("baz", [&](ShapeSerializer& vser) { vser.WriteString(*valSchema, "qux"); }); + }); + }); + + XmlShapeDeserializer d(reinterpret_cast(payload.data()), payload.size()); + Aws::Map entries; + d.ReadStruct(*root, [&](const Schema& m, ShapeDeserializer& de) { + if (m.GetMemberName() == "headers") { + de.ReadMap(m, [&](const Aws::String& key, ShapeDeserializer& vde) { + auto v = vde.ReadString(*valSchema); + if (v.has_value()) { + entries[key] = v.value(); + } + }); + } + }); + ASSERT_EQ(entries.size(), 2u); + EXPECT_EQ(entries["foo"], "bar"); + EXPECT_EQ(entries["baz"], "qux"); +} + +TEST_F(XmlShapeDeserializerTest, CustomMapNames) { + auto mapBuilder = Schema::MapBuilder("Tags"); + auto root = RootBuilder() + .PutMember("tags", mapBuilder, + {{XmlMapEntryNameTrait::KEY(), Aws::MakeShared("Test", "item")}, + {XmlMapKeyNameTrait::KEY(), Aws::MakeShared("Test", "tagKey")}, + {XmlMapValueNameTrait::KEY(), Aws::MakeShared("Test", "tagValue")}}) + .Build(); + auto tags = root->GetMember("tags").value(); + auto valSchema = Schema::CreateMember("value", ShapeType::String); + auto payload = Encode(root, [&](ShapeSerializer& ser) { + ser.WriteMap(*tags, 1, + [&](MapSerializer& mapSer) { mapSer.WriteEntry("color", [&](ShapeSerializer& vser) { vser.WriteString(*valSchema, "red"); }); }); + }); + + XmlShapeDeserializer d(reinterpret_cast(payload.data()), payload.size()); + Aws::Map entries; + d.ReadStruct(*root, [&](const Schema& m, ShapeDeserializer& de) { + if (m.GetMemberName() == "tags") { + de.ReadMap(m, [&](const Aws::String& key, ShapeDeserializer& vde) { + auto v = vde.ReadString(*valSchema); + if (v.has_value()) { + entries[key] = v.value(); + } + }); + } + }); + ASSERT_EQ(entries.size(), 1u); + EXPECT_EQ(entries["color"], "red"); +} + +TEST_F(XmlShapeDeserializerTest, CustomListItemName) { + auto listBuilder = Schema::ListBuilder("Things"); + auto root = RootBuilder() + .PutMember("things", listBuilder, {{XmlListItemNameTrait::KEY(), Aws::MakeShared("Test", "item")}}) + .Build(); + auto things = root->GetMember("things").value(); + auto elem = Schema::CreateMember("member", ShapeType::String); + auto payload = Encode(root, [&](ShapeSerializer& ser) { + ser.WriteList(*things, 2, [&](ShapeSerializer& lser) { + lser.WriteString(*elem, "a"); + lser.WriteString(*elem, "b"); + }); + }); + + XmlShapeDeserializer d(reinterpret_cast(payload.data()), payload.size()); + Aws::Vector values; + d.ReadStruct(*root, [&](const Schema& m, ShapeDeserializer& de) { + if (m.GetMemberName() == "things") { + de.ReadList(m, [&](ShapeDeserializer& ede) { + auto v = ede.ReadString(*elem); + if (v.has_value()) { + values.push_back(v.value()); + } + }); + } + }); + ASSERT_EQ(values.size(), 2u); + EXPECT_EQ(values[0], "a"); + EXPECT_EQ(values[1], "b"); +} + +TEST_F(XmlShapeDeserializerTest, AbsentMemberSkipped) { + auto root = RootBuilder() + .PutMember("present", Schema::CreateString("S")) + .PutMember("absent", Schema::CreateString("S2")) + .Build(); + auto present = root->GetMember("present").value(); + auto payload = Encode(root, [&](ShapeSerializer& ser) { ser.WriteString(*present, "here"); }); + + XmlShapeDeserializer d(reinterpret_cast(payload.data()), payload.size()); + Aws::Map got; + d.ReadStruct(*root, [&](const Schema& m, ShapeDeserializer& de) { + auto v = de.ReadString(m); + if (v.has_value()) { + got[m.GetMemberName()] = v.value(); + } + }); + ASSERT_EQ(got.size(), 1u); + EXPECT_EQ(got["present"], "here"); + EXPECT_EQ(got.find("absent"), got.end()); +}