Skip to content
Merged
Show file tree
Hide file tree
Changes from 10 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
73 changes: 73 additions & 0 deletions dart/packages/fory-test/lib/entity/enum_id_foo.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

import 'package:fory/fory.dart';

part '../generated/enum_id_foo.g.dart';

@foryEnum
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.

use uppercase?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yes, we should do it as per conventions, but the existing files also follow the same pattern. If you prefer, shall I make a separate PR for that first before this one?

enum EnumWithIds {
@ForyEnumId(10)
A,
@ForyEnumId(20)
B,
@ForyEnumId(30)
C,
}

@foryEnum
enum EnumPartialIds {
@ForyEnumId(10)
A,
B,
@ForyEnumId(30)
C,
}

@foryEnum
enum EnumDuplicateIds {
@ForyEnumId(10)
A,
@ForyEnumId(10)
B,
@ForyEnumId(30)
C,
}

@foryEnum
enum EnumFieldBasedIds {
A(10),
B(20),
C(30);

@ForyEnumId()
final int code;
const EnumFieldBasedIds(this.code);
}

@foryEnum
enum EnumFieldBasedDuplicateIds {
A(10),
B(10),
C(30);

@ForyEnumId()
final int code;
const EnumFieldBasedDuplicateIds(this.code);
}
27 changes: 27 additions & 0 deletions dart/packages/fory-test/test/codegen_test/enum_codegen_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ library;

import 'package:checks/checks.dart';
import 'package:fory/fory.dart';
import 'package:fory_test/entity/enum_id_foo.dart';
import 'package:fory_test/entity/enum_foo.dart';
import 'package:test/test.dart';

Expand All @@ -31,8 +32,34 @@ void main() {
EnumSpec enumSpec = EnumSpec(EnumFoo, [EnumFoo.A, EnumFoo.B]);
EnumSpec enumSubTypeSpec =
EnumSpec(EnumSubClass, [EnumSubClass.A, EnumSubClass.B]);

check($EnumFoo).equals(enumSpec);
check($EnumSubClass).equals(enumSubTypeSpec);
});

test('test per-value @ForyEnumId spec generation', () {
EnumSpec enumWithIdsSpec = EnumSpec(EnumWithIds,
[EnumWithIds.A, EnumWithIds.B, EnumWithIds.C],
{10: EnumWithIds.A, 20: EnumWithIds.B, 30: EnumWithIds.C});
EnumSpec enumPartialIdsSpec = EnumSpec(
EnumPartialIds, [EnumPartialIds.A, EnumPartialIds.B, EnumPartialIds.C]);
EnumSpec enumDuplicateIdsSpec = EnumSpec(EnumDuplicateIds,
[EnumDuplicateIds.A, EnumDuplicateIds.B, EnumDuplicateIds.C]);

check($EnumWithIds).equals(enumWithIdsSpec);
check($EnumPartialIds).equals(enumPartialIdsSpec);
check($EnumDuplicateIds).equals(enumDuplicateIdsSpec);
});

test('test field-based @ForyEnumId spec generation', () {
EnumSpec enumFieldBasedIdsSpec = EnumSpec(EnumFieldBasedIds,
[EnumFieldBasedIds.A, EnumFieldBasedIds.B, EnumFieldBasedIds.C],
{10: EnumFieldBasedIds.A, 20: EnumFieldBasedIds.B, 30: EnumFieldBasedIds.C});
EnumSpec enumFieldBasedDuplicateIdsSpec = EnumSpec(EnumFieldBasedDuplicateIds,
[EnumFieldBasedDuplicateIds.A, EnumFieldBasedDuplicateIds.B, EnumFieldBasedDuplicateIds.C]);

check($EnumFieldBasedIds).equals(enumFieldBasedIdsSpec);
check($EnumFieldBasedDuplicateIds).equals(enumFieldBasedDuplicateIdsSpec);
});
});
}
191 changes: 191 additions & 0 deletions dart/packages/fory-test/test/datatype_test/enum_serializer_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

library;

import 'package:checks/checks.dart';
import 'package:fory/src/collection/stack.dart';
import 'package:fory/src/config/fory_config.dart';
import 'package:fory/src/deserialization_dispatcher.dart';
import 'package:fory/src/deserialization_context.dart';
import 'package:fory/src/fory_exception.dart';
import 'package:fory/src/memory/byte_reader.dart';
import 'package:fory/src/memory/byte_writer.dart';
import 'package:fory/src/meta/spec_wraps/type_spec_wrap.dart';
import 'package:fory/src/resolver/deserialization_ref_resolver.dart';
import 'package:fory/src/resolver/meta_string_writing_resolver.dart';
import 'package:fory/src/resolver/serialization_ref_resolver.dart';
import 'package:fory/src/resolver/struct_hash_resolver.dart';
import 'package:fory/src/resolver/type_resolver.dart';
import 'package:fory/src/serialization_dispatcher.dart';
import 'package:fory/src/serialization_context.dart';
import 'package:fory/src/serializer/enum_serializer.dart';
import 'package:fory_test/entity/enum_id_foo.dart';
import 'package:test/test.dart';

String _unusedTagLookup(Type _) => '';

final ForyConfig _config = ForyConfig();
final TypeResolver _typeResolver = TypeResolver.newOne(_config);

SerializationContext _newSerializationContext() {
return SerializationContext(
StructHashResolver.inst,
_unusedTagLookup,
SerializationDispatcher.I,
_typeResolver,
SerializationRefResolver.getOne(false),
SerializationRefResolver.noRefResolver,
MetaStringWritingResolver.newInst,
Stack<TypeSpecWrap>(),
);
}

DeserializationContext _newDeserializationContext() {
return DeserializationContext(
StructHashResolver.inst,
_unusedTagLookup,
_config,
(isXLang: true, oobEnabled: false),
DeserializationDispatcher.I,
DeserializationRefResolver.getOne(false),
_typeResolver,
Stack<TypeSpecWrap>(),
);
}

void main() {
group('Enum serializer', () {
test('writes and reads annotated enum ids when all values are annotated',
Copy link
Copy Markdown
Collaborator

@chaokunyang chaokunyang Apr 8, 2026

Choose a reason for hiding this comment

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

Add a public Fory round-trip for annotated enums

These tests prove the generated EnumSpec shape and the direct EnumSerializer behavior, but they still do not exercise the public Fory.serialize / Fory.deserialize path with an annotated enum. That leaves a gap around the real registration and resolver wiring for this new feature. Please add at least one end-to-end round-trip test for an enum that uses @ForyEnumId.

() {
final EnumSerializer serializer = EnumSerializer(false, [
EnumWithIds.A,
EnumWithIds.B,
EnumWithIds.C,
], {
10: EnumWithIds.A,
20: EnumWithIds.B,
30: EnumWithIds.C,
});

final ByteWriter writer = ByteWriter();
serializer.write(
writer,
EnumWithIds.B,
_newSerializationContext(),
);
final ByteReader encodedIdReader =
ByteReader.forBytes(writer.takeBytes());
check(encodedIdReader.readVarUint32Small7()).equals(20);

final ByteWriter idWriter = ByteWriter();
idWriter.writeVarUint32Small7(30);
final Enum value = serializer.read(
ByteReader.forBytes(idWriter.takeBytes()),
0,
_newDeserializationContext(),
);
check(value).equals(EnumWithIds.C);
});

test('falls back to ordinal serialization when id mapping is absent', () {
final EnumSerializer serializer = EnumSerializer(false, [
EnumPartialIds.A,
EnumPartialIds.B,
EnumPartialIds.C,
]);

final ByteWriter writer = ByteWriter();
serializer.write(
writer,
EnumPartialIds.B,
_newSerializationContext(),
);
final ByteReader encodedIdReader =
ByteReader.forBytes(writer.takeBytes());
check(encodedIdReader.readVarUint32Small7()).equals(1);
});

test('throws on unknown annotated enum id', () {
final EnumSerializer serializer = EnumSerializer(false, [
EnumWithIds.A,
EnumWithIds.B,
EnumWithIds.C,
], {
10: EnumWithIds.A,
20: EnumWithIds.B,
30: EnumWithIds.C,
});

final ByteWriter writer = ByteWriter();
writer.writeVarUint32Small7(99);

check(
() => serializer.read(
ByteReader.forBytes(writer.takeBytes()),
0,
_newDeserializationContext(),
),
).throws<DeserializationRangeException>();
});

test('writes and reads field-based enum ids', () {
final EnumSerializer serializer = EnumSerializer(false, [
EnumFieldBasedIds.A,
EnumFieldBasedIds.B,
EnumFieldBasedIds.C,
], {
10: EnumFieldBasedIds.A,
20: EnumFieldBasedIds.B,
30: EnumFieldBasedIds.C,
});

final ByteWriter writer = ByteWriter();
serializer.write(writer, EnumFieldBasedIds.B, _newSerializationContext());
final ByteReader reader = ByteReader.forBytes(writer.takeBytes());
check(reader.readVarUint32Small7()).equals(20);

final ByteWriter idWriter = ByteWriter();
idWriter.writeVarUint32Small7(30);
final Enum value = serializer.read(
ByteReader.forBytes(idWriter.takeBytes()),
0,
_newDeserializationContext(),
);
check(value).equals(EnumFieldBasedIds.C);
});

test('field-based duplicate ids fall back to ordinal', () {
final EnumSerializer serializer = EnumSerializer(false, [
EnumFieldBasedDuplicateIds.A,
EnumFieldBasedDuplicateIds.B,
EnumFieldBasedDuplicateIds.C,
]);

final ByteWriter writer = ByteWriter();
serializer.write(
writer,
EnumFieldBasedDuplicateIds.B,
_newSerializationContext(),
);
final ByteReader reader = ByteReader.forBytes(writer.takeBytes());
check(reader.readVarUint32Small7()).equals(1);
});
});
}
39 changes: 39 additions & 0 deletions dart/packages/fory/lib/src/annotation/fory_enum.dart
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import 'fory_object.dart';
/// // enums
/// }
/// ```
@Target({TargetKind.enumType})
class ForyEnum extends ForyObject {
static const String name = 'ForyEnum';
static const List<TargetKind> targets = [TargetKind.enumType];
Expand All @@ -41,3 +42,41 @@ class ForyEnum extends ForyObject {

/// A constant instance of [ForyEnum].
const ForyEnum foryEnum = ForyEnum();

/// A class representing an enumeration id in the Fory framework.
///
/// This class extends [ForyObject] and is used to annotate enum ids
/// within the Fory framework.
///
/// Can be used in two ways:
///
/// 1. On each enum value with an explicit id:
/// ```
/// @foryEnum
/// enum Color {
/// @ForyEnumId(5)
/// blue,
/// @ForyEnumId(10)
/// white,
/// }
/// ```
///
/// 2. On an int field of an enhanced enum to use its value as the id:
/// ```
/// @foryEnum
/// enum UserRole {
/// green(0),
/// blue(1),
/// white(2);
///
/// @ForyEnumId()
/// final int code;
/// const Color(this.code);
/// }
/// ```
@Target({TargetKind.enumValue, TargetKind.field})
class ForyEnumId extends ForyObject {
final int? id;

const ForyEnumId([this.id]);
}
Loading
Loading