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
9 changes: 9 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -380,6 +380,15 @@ jobs:
key: bazel-build-cpp-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('cpp/**', 'BUILD', 'WORKSPACE') }}
- name: Run C++ CI with Bazel
run: python ./ci/run_ci.py cpp
- name: Upload Bazel Test Logs
uses: actions/upload-artifact@v4
if: ${{ !cancelled() }}
with:
name: bazel-test-logs-${{ matrix.os }}
path: |
bazel-out/*/testlogs/**/*.log
bazel-out/*/testlogs/**/*.xml
if-no-files-found: ignore

cpp_xlang:
name: C++ Xlang Test
Expand Down
4 changes: 3 additions & 1 deletion compiler/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -384,8 +384,10 @@ struct Cat {
std::shared_ptr<Dog> friend;
std::optional<std::string> name;
std::vector<std::string> tags;
int32_t scores;
int32_t lives;
FORY_STRUCT(Cat, friend, name, tags, scores, lives);
};
FORY_STRUCT(Cat, friend, name, tags, scores, lives);
```

## CLI Reference
Expand Down
22 changes: 5 additions & 17 deletions compiler/fory_compiler/generators/cpp.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,6 @@ def generate_header(self) -> GeneratedFile:
lines = []
includes: Set[str] = set()
enum_macros: List[str] = []
struct_macros: List[str] = []
union_macros: List[str] = []
field_config_macros: List[str] = []
definition_items = self.get_definition_order()
Expand Down Expand Up @@ -173,17 +172,14 @@ def generate_header(self) -> GeneratedFile:
# Generate top-level unions/messages in dependency order
for kind, item in definition_items:
if kind == "union":
lines.extend(
self.generate_union_definition(item, [], struct_macros, "")
)
lines.extend(self.generate_union_definition(item, [], ""))
union_macros.extend(self.generate_union_macros(item, []))
lines.append("")
continue
lines.extend(
self.generate_message_definition(
item,
[],
struct_macros,
enum_macros,
union_macros,
field_config_macros,
Expand All @@ -192,10 +188,6 @@ def generate_header(self) -> GeneratedFile:
)
lines.append("")

if struct_macros:
lines.extend(struct_macros)
lines.append("")

if namespace:
lines.append(f"}} // namespace {namespace}")
lines.append("")
Expand Down Expand Up @@ -396,7 +388,6 @@ def generate_message_definition(
self,
message: Message,
parent_stack: List[Message],
struct_macros: List[str],
enum_macros: List[str],
union_macros: List[str],
field_config_macros: List[str],
Expand All @@ -422,7 +413,6 @@ def generate_message_definition(
self.generate_message_definition(
nested_msg,
lineage,
struct_macros,
enum_macros,
union_macros,
field_config_macros,
Expand All @@ -436,7 +426,6 @@ def generate_message_definition(
self.generate_union_definition(
nested_union,
lineage,
struct_macros,
body_indent,
)
)
Expand Down Expand Up @@ -470,12 +459,9 @@ def generate_message_definition(
lines.append(f"{body_indent} return true;")
lines.append(f"{body_indent}}}")

lines.append(f"{indent}}};")

struct_type_name = self.get_qualified_type_name(message.name, parent_stack)
if message.fields:
field_names = ", ".join(self.to_snake_case(f.name) for f in message.fields)
struct_macros.append(f"FORY_STRUCT({struct_type_name}, {field_names});")
field_config_type_name = self.get_field_config_type_and_alias(
message.name, parent_stack
)
Expand All @@ -484,16 +470,18 @@ def generate_message_definition(
message, field_config_type_name[0], field_config_type_name[1]
)
)
lines.append(f"{body_indent}FORY_STRUCT({struct_type_name}, {field_names});")
else:
struct_macros.append(f"FORY_STRUCT({struct_type_name});")
lines.append(f"{body_indent}FORY_STRUCT({struct_type_name});")

lines.append(f"{indent}}};")

return lines

def generate_union_definition(
self,
union: Union,
parent_stack: List[Message],
struct_macros: List[str],
indent: str,
) -> List[str]:
"""Generate a C++ union class definition."""
Expand Down
41 changes: 39 additions & 2 deletions cpp/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ The C++ implementation provides high-performance serialization with compile-time
```cpp
#include "fory/serialization/fory.h"

// Define your struct with FORY_STRUCT macro
// Define your struct with FORY_STRUCT macro (place it after all fields)
struct Person {
std::string name;
int32_t age;
Expand Down Expand Up @@ -116,6 +116,43 @@ auto bytes = fory->serialize(person).value();
auto decoded = fory->deserialize<Person>(bytes).value();
```

### 1.1 External/Third-Party Types

For third-party types where you cannot modify the class definition, use
`FORY_STRUCT_EXTERNAL` at namespace scope. This works for public fields only.

```cpp
namespace thirdparty {
struct Foo {
int32_t id;
std::string name;
};

FORY_STRUCT_EXTERNAL(Foo, id, name);
} // namespace thirdparty

auto fory = apache::fory::ForyBuilder().build();
fory->register_struct<thirdparty::Foo>(1);
```

### 1.2 Inherited Fields

To include base-class fields in a derived type, use `FORY_BASE(Base)` inside
`FORY_STRUCT`. The base must define its own `FORY_STRUCT` so its fields can be
referenced.

```cpp
struct Base {
int32_t id;
FORY_STRUCT(Base, id);
};

struct Derived : Base {
std::string name;
FORY_STRUCT(Derived, FORY_BASE(Base), name);
};
```

### 2. Shared References

Apache Fory™ automatically tracks and preserves reference identity for shared objects using `std::shared_ptr<T>`. When the same object is referenced multiple times, Fory serializes it only once and uses reference IDs for subsequent occurrences.
Expand Down Expand Up @@ -230,7 +267,7 @@ struct UserProfile {
std::string email;
std::vector<int32_t> scores;
bool is_active;
FORY_FIELD_INFO(UserProfile, id, username, email, scores, is_active);
FORY_STRUCT(UserProfile, id, username, email, scores, is_active);
};

apache::fory::RowEncoder<UserProfile> encoder;
Expand Down
21 changes: 7 additions & 14 deletions cpp/fory/encoder/row_encode_trait_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,9 @@ struct A {
int x;
float y;
bool z;
FORY_STRUCT(A, x, y, z);
};

FORY_FIELD_INFO(A, x, y, z);

TEST(RowEncodeTrait, Basic) {
auto field_vector = encoder::RowEncodeTrait<A>::FieldVector();

Expand Down Expand Up @@ -67,10 +66,9 @@ TEST(RowEncodeTrait, Basic) {
struct B {
int num;
std::string str;
FORY_STRUCT(B, num, str);
};

FORY_FIELD_INFO(B, num, str);

TEST(RowEncodeTrait, String) {
RowWriter writer(encoder::RowEncodeTrait<B>::Schema());
writer.Reset();
Expand All @@ -89,10 +87,9 @@ struct C {
const int a;
volatile float b;
bool c;
FORY_STRUCT(C, a, b, c);
};

FORY_FIELD_INFO(C, a, b, c);

TEST(RowEncodeTrait, Const) {
RowWriter writer(encoder::RowEncodeTrait<C>::Schema());
writer.Reset();
Expand All @@ -110,10 +107,9 @@ struct D {
int x;
A y;
B z;
FORY_STRUCT(D, x, y, z);
};

FORY_FIELD_INFO(D, x, y, z);

TEST(RowEncodeTrait, NestedStruct) {
RowWriter writer(encoder::RowEncodeTrait<D>::Schema());
std::vector<std::unique_ptr<RowWriter>> children;
Expand Down Expand Up @@ -202,10 +198,9 @@ TEST(RowEncodeTrait, StructInArray) {
struct E {
int a;
std::vector<int> b;
FORY_STRUCT(E, a, b);
};

FORY_FIELD_INFO(E, a, b);

TEST(RowEncodeTrait, ArrayInStruct) {
E e{233, {10, 20, 30}};

Expand Down Expand Up @@ -256,10 +251,9 @@ struct F {
bool a;
std::optional<int> b;
int c;
FORY_STRUCT(F, a, b, c);
};

FORY_FIELD_INFO(F, a, b, c);

TEST(RowEncodeTrait, Optional) {
F x{false, 233, 111}, y{true, std::nullopt, 222};

Expand Down Expand Up @@ -301,10 +295,9 @@ TEST(RowEncodeTrait, Optional) {
struct G {
std::map<int, std::map<int, int>> a;
std::map<std::string, A> b;
FORY_STRUCT(G, a, b);
};

FORY_FIELD_INFO(G, a, b);

TEST(RowEncodeTrait, Map) {
G v{{{1, {{3, 4}, {5, 6}}}, {2, {{7, 8}, {9, 10}, {11, 12}}}},
{{"a", A{1, 1.1, true}}, {"b", A{2, 3.3, false}}}};
Expand Down
47 changes: 42 additions & 5 deletions cpp/fory/encoder/row_encoder_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -32,16 +32,29 @@ namespace test2 {
struct A {
float a;
std::string b;
FORY_STRUCT(A, a, b);
};

FORY_FIELD_INFO(A, a, b);

struct B {
int x;
A y;
FORY_STRUCT(B, x, y);
};

namespace external_row {

struct ExternalRow {
int32_t id;
std::string name;
};

FORY_FIELD_INFO(B, x, y);
FORY_STRUCT_EXTERNAL(ExternalRow, id, name);

struct ExternalRowEmpty {};

FORY_STRUCT_EXTERNAL(ExternalRowEmpty);

} // namespace external_row

TEST(RowEncoder, Simple) {
B v{233, {1.23, "hello"}};
Expand All @@ -66,13 +79,37 @@ TEST(RowEncoder, Simple) {
ASSERT_FLOAT_EQ(y_row->GetFloat(0), 1.23);
}

TEST(RowEncoder, ExternalStruct) {
external_row::ExternalRow v{7, "external"};
encoder::RowEncoder<external_row::ExternalRow> enc;

auto &schema = enc.GetSchema();
ASSERT_EQ(schema.field_names(), (std::vector<std::string>{"id", "name"}));

enc.Encode(v);
auto row = enc.GetWriter().ToRow();
ASSERT_EQ(row->GetInt32(0), 7);
ASSERT_EQ(row->GetString(1), "external");
}

TEST(RowEncoder, ExternalEmptyStruct) {
external_row::ExternalRowEmpty v{};
encoder::RowEncoder<external_row::ExternalRowEmpty> enc;

auto &schema = enc.GetSchema();
ASSERT_TRUE(schema.field_names().empty());

enc.Encode(v);
auto row = enc.GetWriter().ToRow();
ASSERT_EQ(row->num_fields(), 0);
}

struct C {
std::vector<A> x;
bool y;
FORY_STRUCT(C, x, y);
};

FORY_FIELD_INFO(C, x, y);

TEST(RowEncoder, SimpleArray) {
std::vector<C> v{C{{{1, "a"}, {2, "b"}}, false},
C{{{1.1, "x"}, {2.2, "y"}, {3.3, "z"}}, true}};
Expand Down
Loading
Loading