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
27 changes: 24 additions & 3 deletions rust/fory-core/src/serializer/enum_.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,16 @@ pub fn write<T: Serializer>(
}

#[inline(always)]
pub fn write_type_info<T: Serializer>(context: &mut WriteContext) -> Result<(), Error> {
pub fn write_type_info<T: Serializer>(
context: &mut WriteContext,
is_tagged: bool,
) -> Result<(), Error> {
// In xlang mode, use UNION type ID for tagged enums (enums with data variants),
// and regular ENUM type ID for simple enums (all unit variants)
if context.is_xlang() && is_tagged {
context.writer.write_varuint32(TypeId::UNION as u32);
return Ok(());
}
let type_id = T::fory_get_type_id(context.get_type_resolver())?;
context.writer.write_varuint32(type_id);
let is_named_enum = type_id & 0xff == TypeId::NAMED_ENUM as u32;
Expand Down Expand Up @@ -99,9 +108,21 @@ pub fn read<T: Serializer + ForyDefault>(
}

#[inline(always)]
pub fn read_type_info<T: Serializer>(context: &mut ReadContext) -> Result<(), Error> {
let local_type_id = T::fory_get_type_id(context.get_type_resolver())?;
pub fn read_type_info<T: Serializer>(
context: &mut ReadContext,
is_tagged: bool,
) -> Result<(), Error> {
let remote_type_id = context.reader.read_varuint32()?;
// In xlang mode, expect UNION type ID for tagged enums (enums with data variants),
// and regular ENUM type ID for simple enums (all unit variants)
if context.is_xlang() && is_tagged {
ensure!(
remote_type_id == TypeId::UNION as u32,
Error::type_mismatch(TypeId::UNION as u32, remote_type_id)
);
return Ok(());
}
let local_type_id = T::fory_get_type_id(context.get_type_resolver())?;
ensure!(
local_type_id == remote_type_id,
Error::type_mismatch(local_type_id, remote_type_id)
Expand Down
6 changes: 6 additions & 0 deletions rust/fory-core/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,10 @@ pub enum TypeId {
FLOAT16_ARRAY = 35,
FLOAT32_ARRAY = 36,
FLOAT64_ARRAY = 37,
// Tagged union type (one of several alternatives)
UNION = 38,
// Empty/unit type (no data)
NONE = 39,
U8 = 64,
U16 = 65,
U32 = 66,
Expand Down Expand Up @@ -136,6 +140,8 @@ pub const INT64_ARRAY: u32 = TypeId::INT64_ARRAY as u32;
pub const FLOAT16_ARRAY: u32 = TypeId::FLOAT16_ARRAY as u32;
pub const FLOAT32_ARRAY: u32 = TypeId::FLOAT32_ARRAY as u32;
pub const FLOAT64_ARRAY: u32 = TypeId::FLOAT64_ARRAY as u32;
pub const UNION: u32 = TypeId::UNION as u32;
pub const NONE: u32 = TypeId::NONE as u32;
pub const U8: u32 = TypeId::U8 as u32;
pub const U16: u32 = TypeId::U16 as u32;
pub const U32: u32 = TypeId::U32 as u32;
Expand Down
19 changes: 15 additions & 4 deletions rust/fory-derive/src/object/derive_enum.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,15 @@ fn temp_var_name(i: usize) -> String {
format!("f{}", i)
}

/// Check if an enum is a "simple enum" (all variants are unit variants without data)
/// Simple enums use ENUM type ID in xlang mode, while tagged enums use UNION type ID
pub fn is_simple_enum(data_enum: &DataEnum) -> bool {
data_enum
.variants
.iter()
.all(|v| matches!(&v.fields, Fields::Unit))
}

pub fn gen_actual_type_id() -> TokenStream {
quote! {
fory_core::serializer::enum_::actual_type_id(type_id, register_by_name, compatible)
Expand Down Expand Up @@ -386,9 +395,10 @@ pub fn gen_write_data(data_enum: &DataEnum) -> TokenStream {
}
}

pub fn gen_write_type_info() -> TokenStream {
pub fn gen_write_type_info(data_enum: &DataEnum) -> TokenStream {
let is_tagged = !is_simple_enum(data_enum);
quote! {
fory_core::serializer::enum_::write_type_info::<Self>(context)
fory_core::serializer::enum_::write_type_info::<Self>(context, #is_tagged)
}
}

Expand Down Expand Up @@ -755,8 +765,9 @@ pub fn gen_read_data(data_enum: &DataEnum) -> TokenStream {
}
}

pub fn gen_read_type_info() -> TokenStream {
pub fn gen_read_type_info(data_enum: &DataEnum) -> TokenStream {
let is_tagged = !is_simple_enum(data_enum);
quote! {
fory_core::serializer::enum_::read_type_info::<Self>(context)
fory_core::serializer::enum_::read_type_info::<Self>(context, #is_tagged)
}
}
4 changes: 2 additions & 2 deletions rust/fory-derive/src/object/serializer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -118,11 +118,11 @@ pub fn derive_serializer(ast: &syn::DeriveInput, debug_enabled: bool) -> TokenSt
syn::Data::Enum(e) => (
derive_enum::gen_write(e),
derive_enum::gen_write_data(e),
derive_enum::gen_write_type_info(),
derive_enum::gen_write_type_info(e),
derive_enum::gen_read(e),
derive_enum::gen_read_with_type_info(e),
derive_enum::gen_read_data(e),
derive_enum::gen_read_type_info(),
derive_enum::gen_read_type_info(e),
derive_enum::gen_reserved_space(),
quote! { fory_core::TypeId::ENUM },
),
Expand Down
138 changes: 138 additions & 0 deletions rust/tests/tests/test_enum.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,3 +99,141 @@ fn named_enum() {
assert_eq!(target1, target2);
assert_eq!(value1, value2);
}

/// Test that simple enum (all unit variants) uses ENUM type ID in xlang mode
/// while tagged enum (some variants with data) uses UNION type ID
#[test]
fn xlang_simple_enum_uses_enum_type_id() {
use fory_core::buffer::Reader;
use fory_core::types::TypeId;

#[derive(ForyObject, Debug, PartialEq, Default)]
enum SimpleEnum {
#[default]
A,
B,
C,
}

let mut fory = Fory::default().xlang(true);
fory.register::<SimpleEnum>(1000).unwrap();

let value = SimpleEnum::B;
let bytes = fory.serialize(&value).unwrap();

// Fory header:
// - 2 bytes: magic number (0x62d4)
// - 1 byte: bitmap flags
// - 1 byte: language
// Total header: 4 bytes

let mut reader = Reader::new(&bytes);

// Read magic number
let _magic = reader.read_u16().unwrap();

// Read bitmap
let _bitmap = reader.read_u8().unwrap();

// Read language
let _language = reader.read_u8().unwrap();

// Read ref flag (-1 = NotNullValue)
let ref_flag = reader.read_i8().unwrap();
assert_eq!(ref_flag, -1, "Expected NotNullValue ref flag");

// Read type id - should be ENUM for simple enum in xlang mode
// The type ID format is: (registered_type_id << 8) + TypeId::ENUM
let type_id = reader.read_varuint32().unwrap();
let base_type_id = type_id & 0xff;
assert_eq!(
base_type_id,
TypeId::ENUM as u32,
"Expected ENUM type id ({}) in xlang mode for simple enum, got {}",
TypeId::ENUM as u32,
base_type_id
);

// Read variant index (B = 1)
let variant_index = reader.read_varuint32().unwrap();
assert_eq!(
variant_index, 1,
"Expected variant index 1 for SimpleEnum::B"
);

// Verify roundtrip still works
let deserialized: SimpleEnum = fory.deserialize(&bytes).unwrap();
assert_eq!(deserialized, value);
}

/// Test that tagged enum (has variants with data) uses UNION type ID in xlang mode
#[test]
fn xlang_tagged_enum_uses_union_type_id() {
use fory_core::buffer::Reader;
use fory_core::types::TypeId;

#[derive(ForyObject, Debug, PartialEq, Default)]
enum TaggedEnum {
#[default]
Empty,
Value(i32),
Name(String),
}

let mut fory = Fory::default().xlang(true);
fory.register::<TaggedEnum>(1001).unwrap();

let value = TaggedEnum::Empty;
let bytes = fory.serialize(&value).unwrap();

let mut reader = Reader::new(&bytes);

// Skip header
let _magic = reader.read_u16().unwrap();
let _bitmap = reader.read_u8().unwrap();
let _language = reader.read_u8().unwrap();

// Read ref flag
let _ref_flag = reader.read_i8().unwrap();

// Read type id - should be UNION (38) in xlang mode for tagged enum
let type_id = reader.read_varuint32().unwrap();
assert_eq!(
type_id,
TypeId::UNION as u32,
"Expected UNION type id ({}) in xlang mode for tagged enum, got {}",
TypeId::UNION as u32,
type_id
);

// Verify roundtrip still works
let deserialized: TaggedEnum = fory.deserialize(&bytes).unwrap();
assert_eq!(deserialized, value);
}

/// Test xlang roundtrip with tagged enum (enum with data variants)
/// Note: This test only tests unit variants of a tagged enum.
/// Testing data variants in xlang mode is a separate concern.
#[test]
fn xlang_complex_tagged_enum_roundtrip() {
#[derive(ForyObject, Debug, PartialEq, Default)]
enum TaggedColor {
#[default]
Red,
Green,
Blue,
Value(i32),
}

let mut fory = Fory::default().xlang(true);
fory.register::<TaggedColor>(1002).unwrap();

// Test unit variants - these should work even though the enum is tagged
let colors = vec![TaggedColor::Red, TaggedColor::Green, TaggedColor::Blue];

for color in colors {
let bytes = fory.serialize(&color).unwrap();
let deserialized: TaggedColor = fory.deserialize(&bytes).unwrap();
assert_eq!(deserialized, color);
}
}
Loading