-
Notifications
You must be signed in to change notification settings - Fork 1.2k
adding the Cbor Deserializer #3877
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,46 @@ | ||
| #pragma once | ||
|
|
||
| #include <aws/core/utils/memory/AWSMemory.h> | ||
| #include <smithy/Smithy_EXPORTS.h> | ||
| #include <smithy/client/schema/ShapeDeserializer.h> | ||
|
|
||
| namespace smithy { | ||
| namespace schema { | ||
|
|
||
| class SMITHY_API CborShapeDeserializer final : public ShapeDeserializer { | ||
| public: | ||
| CborShapeDeserializer(const unsigned char* data, size_t length); | ||
| ~CborShapeDeserializer(); | ||
|
|
||
| bool ReadBoolean() override; | ||
| int ReadInteger() override; | ||
| int64_t ReadLong() override; | ||
| float ReadFloat() override; | ||
| double ReadDouble() override; | ||
| Aws::String ReadString() override; | ||
| Aws::Utils::DateTime ReadTimestamp() override; | ||
| Aws::Utils::ByteBuffer ReadBlob() override; | ||
| int ReadEnum() override; | ||
|
|
||
| void BeginStruct() override; | ||
| void EndStruct() override; | ||
|
|
||
| size_t BeginList() override; | ||
| void EndList() override; | ||
|
|
||
| size_t BeginMap() override; | ||
| void EndMap() override; | ||
|
|
||
| Aws::String ReadKey() override; | ||
| bool IsBreak() override; | ||
| bool IsNull() override; | ||
| void ReadNull() override; | ||
| void SkipValue() override; | ||
|
|
||
| private: | ||
| class Impl; | ||
| Aws::UniquePtr<Impl> m_impl; | ||
| }; | ||
|
|
||
| } // namespace schema | ||
| } // namespace smithy |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,44 @@ | ||
| #pragma once | ||
|
|
||
| #include <aws/core/utils/Array.h> | ||
| #include <aws/core/utils/DateTime.h> | ||
| #include <aws/core/utils/memory/stl/AWSString.h> | ||
| #include <smithy/Smithy_EXPORTS.h> | ||
|
|
||
| #include <cstdint> | ||
|
|
||
| namespace smithy { | ||
| namespace schema { | ||
|
|
||
| class SMITHY_API ShapeDeserializer { | ||
| public: | ||
| virtual ~ShapeDeserializer() = default; | ||
|
|
||
| virtual bool ReadBoolean() = 0; | ||
| virtual int ReadInteger() = 0; | ||
| virtual int64_t ReadLong() = 0; | ||
| virtual float ReadFloat() = 0; | ||
| virtual double ReadDouble() = 0; | ||
| virtual Aws::String ReadString() = 0; | ||
| virtual Aws::Utils::DateTime ReadTimestamp() = 0; | ||
| virtual Aws::Utils::ByteBuffer ReadBlob() = 0; | ||
| virtual int ReadEnum() = 0; | ||
|
|
||
| virtual void BeginStruct() = 0; | ||
| virtual void EndStruct() = 0; | ||
|
|
||
| virtual size_t BeginList() = 0; | ||
| virtual void EndList() = 0; | ||
|
|
||
| virtual size_t BeginMap() = 0; | ||
| virtual void EndMap() = 0; | ||
|
|
||
| virtual Aws::String ReadKey() = 0; | ||
| virtual bool IsBreak() = 0; | ||
| virtual bool IsNull() = 0; | ||
| virtual void ReadNull() = 0; | ||
| virtual void SkipValue() = 0; | ||
| }; | ||
|
|
||
| } // namespace schema | ||
| } // namespace smithy |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,159 @@ | ||
| /** | ||
| * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. | ||
| * SPDX-License-Identifier: Apache-2.0. | ||
| */ | ||
| #include <aws/crt/cbor/Cbor.h> | ||
| #include <smithy/client/schema/CborShapeDeserializer.h> | ||
|
|
||
| using namespace smithy::schema; | ||
| using namespace Aws::Utils; | ||
| using namespace Aws::Crt::Cbor; | ||
|
|
||
| class CborShapeDeserializer::Impl { | ||
| public: | ||
| Impl(const unsigned char* data, size_t length) | ||
| : m_decoder(Aws::Crt::ByteCursorFromArray(data, length)) {} | ||
|
|
||
| bool ReadBoolean() { | ||
| auto val = m_decoder.PopNextBooleanVal(); | ||
| return val.has_value() ? val.value() : false; | ||
| } | ||
|
|
||
| int ReadInteger() { return static_cast<int>(ReadLong()); } | ||
|
|
||
| int64_t ReadLong() { | ||
| auto type = m_decoder.PeekType(); | ||
| if (!type.has_value()) { | ||
| return 0; | ||
| } | ||
| if (*type == CborType::UInt) { | ||
| auto val = m_decoder.PopNextUnsignedIntVal(); | ||
| return val.has_value() ? static_cast<int64_t>(val.value()) : 0; | ||
| } | ||
| if (*type == CborType::NegInt) { | ||
| auto val = m_decoder.PopNextNegativeIntVal(); | ||
| return val.has_value() ? static_cast<int64_t>(-(static_cast<int64_t>(val.value()) + 1)) : 0; | ||
| } | ||
| return 0; | ||
| } | ||
|
|
||
| float ReadFloat() { return static_cast<float>(ReadDouble()); } | ||
|
|
||
| double ReadDouble() { | ||
| auto type = m_decoder.PeekType(); | ||
| if (!type.has_value()) { | ||
| return 0.0; | ||
| } | ||
| if (*type == CborType::Float) { | ||
| auto val = m_decoder.PopNextFloatVal(); | ||
| return val.has_value() ? val.value() : 0.0; | ||
| } | ||
| // CRT "smallest possible" may encode doubles as integers | ||
| return static_cast<double>(ReadLong()); | ||
| } | ||
|
|
||
| Aws::String ReadString() { | ||
| auto val = m_decoder.PopNextTextVal(); | ||
| if (!val.has_value()) { | ||
| return {}; | ||
| } | ||
| return Aws::String(reinterpret_cast<const char*>(val->ptr), val->len); | ||
| } | ||
|
|
||
| DateTime ReadTimestamp() { | ||
| auto tag = m_decoder.PopNextTagVal(); | ||
| (void)tag; | ||
| return DateTime(static_cast<double>(ReadLong())); | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Timestamps can also be floats. Also,
What we currently do ensures that if we encounter a negative value we error out
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'll handle the float case and add validation for negative values. |
||
| } | ||
|
|
||
| ByteBuffer ReadBlob() { | ||
| auto val = m_decoder.PopNextBytesVal(); | ||
| if (!val.has_value()) { | ||
| return {}; | ||
| } | ||
| return ByteBuffer(val->ptr, val->len); | ||
| } | ||
|
|
||
| int ReadEnum() { return ReadInteger(); } | ||
|
|
||
| void BeginStruct() { | ||
| auto type = m_decoder.PeekType(); | ||
| if (type.has_value() && *type == CborType::IndefMapStart) { | ||
| m_decoder.ConsumeNextSingleElement(); | ||
| } else { | ||
| m_decoder.PopNextMapStart(); | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How come we're discarding the map size for definite length struct? Server can send definite length here, and then our
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You're right — the CRT doesn't synthesize a break for definite-length maps (it uses count-based iteration internally but doesn't expose it to PeekType callers). I'll change |
||
| } | ||
| } | ||
|
|
||
| void EndStruct() { | ||
| // For indefinite maps, the break is consumed by the IsBreak/read loop | ||
| } | ||
|
|
||
| size_t BeginList() { | ||
| auto type = m_decoder.PeekType(); | ||
| if (type.has_value() && *type == CborType::IndefArrayStart) { | ||
| m_decoder.ConsumeNextSingleElement(); | ||
| return 0; | ||
| } | ||
| auto size = m_decoder.PopNextArrayStart(); | ||
| return size.has_value() ? static_cast<size_t>(size.value()) : 0; | ||
| } | ||
|
|
||
| void EndList() {} | ||
|
|
||
| size_t BeginMap() { | ||
| auto type = m_decoder.PeekType(); | ||
| if (type.has_value() && *type == CborType::IndefMapStart) { | ||
| m_decoder.ConsumeNextSingleElement(); | ||
| return 0; | ||
| } | ||
| auto size = m_decoder.PopNextMapStart(); | ||
| return size.has_value() ? static_cast<size_t>(size.value()) : 0; | ||
| } | ||
|
|
||
| void EndMap() {} | ||
|
|
||
| Aws::String ReadKey() { return ReadString(); } | ||
|
|
||
| bool IsBreak() { | ||
| auto type = m_decoder.PeekType(); | ||
| return type.has_value() && *type == CborType::Break; | ||
| } | ||
|
|
||
| bool IsNull() { | ||
| auto type = m_decoder.PeekType(); | ||
| return type.has_value() && *type == CborType::Null; | ||
| } | ||
|
|
||
| void ReadNull() { m_decoder.ConsumeNextSingleElement(); } | ||
|
|
||
| void SkipValue() { m_decoder.ConsumeNextWholeDataItem(); } | ||
|
|
||
| private: | ||
| CborDecoder m_decoder; | ||
| }; | ||
|
|
||
| CborShapeDeserializer::CborShapeDeserializer(const unsigned char* data, size_t length) | ||
| : m_impl(Aws::MakeUnique<Impl>("CborShapeDeserializer", data, length)) {} | ||
| CborShapeDeserializer::~CborShapeDeserializer() = default; | ||
|
|
||
| bool CborShapeDeserializer::ReadBoolean() { return m_impl->ReadBoolean(); } | ||
| int CborShapeDeserializer::ReadInteger() { return m_impl->ReadInteger(); } | ||
| int64_t CborShapeDeserializer::ReadLong() { return m_impl->ReadLong(); } | ||
| float CborShapeDeserializer::ReadFloat() { return m_impl->ReadFloat(); } | ||
| double CborShapeDeserializer::ReadDouble() { return m_impl->ReadDouble(); } | ||
| Aws::String CborShapeDeserializer::ReadString() { return m_impl->ReadString(); } | ||
| DateTime CborShapeDeserializer::ReadTimestamp() { return m_impl->ReadTimestamp(); } | ||
| ByteBuffer CborShapeDeserializer::ReadBlob() { return m_impl->ReadBlob(); } | ||
| int CborShapeDeserializer::ReadEnum() { return m_impl->ReadEnum(); } | ||
| void CborShapeDeserializer::BeginStruct() { 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::String CborShapeDeserializer::ReadKey() { return m_impl->ReadKey(); } | ||
| bool CborShapeDeserializer::IsBreak() { return m_impl->IsBreak(); } | ||
| bool CborShapeDeserializer::IsNull() { return m_impl->IsNull(); } | ||
| void CborShapeDeserializer::ReadNull() { m_impl->ReadNull(); } | ||
| void CborShapeDeserializer::SkipValue() { m_impl->SkipValue(); } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
let think about error handling here, every
Read*has abranch in it. this one specifically sticks out to me because you would not be able to tell if the value ways truly
0.0or a error. at theShapeDeserializerinterface level should we be returning a optoinal, maybe a outcome with a error that shows it was a deserialization error? we definitely need some sort of signal to the called that a error occurredThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay, I'll either add per-field Optional or an error flag like CRT's decoder->error_code: