Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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;

Copy link
Copy Markdown
Collaborator

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 a

if (!type.has_value()) {
  return default value
}

branch in it. this one specifically sticks out to me because you would not be able to tell if the value ways truly 0.0 or a error. at the ShapeDeserializer interface 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 occurred

Copy link
Copy Markdown
Collaborator Author

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:

d.BeginStruct();
 d.EndStruct();
 if (d.HasError()) return MakeError(d.GetError());

}
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()));

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Timestamps can also be floats. Also, ReadLong() can return a negative value. Negative timestamps per the RFC:

Negative values (major type 1 and negative floating-point numbers) are interpreted as determined by the application requirements as there is no universal standard for UTC count-of-seconds time before 1970-01-01T00:00Z

What we currently do ensures that if we encounter a negative value we error out

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The 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();

@sbaluja sbaluja Jul 24, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The 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 EndStruct that's relying on theIsBreak loop wouldn't work right? Lmk if im missing something

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The 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 BeginStruct to return size_t, same as BeginList/BeginMap already do.

}
}

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(); }
Loading
Loading