blob: c3d939a9fdffd42fed30b5f8672df113ffed11dc [file] [log] [blame]
// 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.
using System.Buffers;
using System.Text;
using Apache.Fory;
using ForyRuntime = Apache.Fory.Fory;
namespace Apache.Fory.XlangPeer;
internal static class Program
{
private const string DataFileEnv = "DATA_FILE";
private static readonly string[] StringSamples =
[
"ab",
"Rust123",
"Çüéâäàåçêëèïî",
"こんにちは",
"Привет",
"𝄞🎵🎶",
"Hello, 世界",
];
private static readonly int[] VarInt32Values =
[
int.MinValue,
int.MinValue + 1,
-1_000_000,
-1000,
-128,
-1,
0,
1,
127,
128,
16_383,
16_384,
2_097_151,
2_097_152,
268_435_455,
268_435_456,
int.MaxValue - 1,
int.MaxValue,
];
private static readonly uint[] VarUInt32Values =
[
0,
1,
127,
128,
16_383,
16_384,
2_097_151,
2_097_152,
268_435_455,
268_435_456,
2_147_483_646,
2_147_483_647,
];
private static readonly ulong[] VarUInt64Values =
[
0UL,
1UL,
127UL,
128UL,
16_383UL,
16_384UL,
2_097_151UL,
2_097_152UL,
268_435_455UL,
268_435_456UL,
34_359_738_367UL,
34_359_738_368UL,
4_398_046_511_103UL,
4_398_046_511_104UL,
562_949_953_421_311UL,
562_949_953_421_312UL,
72_057_594_037_927_935UL,
72_057_594_037_927_936UL,
long.MaxValue,
];
private static readonly long[] VarInt64Values =
[
long.MinValue,
long.MinValue + 1,
-1_000_000_000_000L,
-1_000_000L,
-1000L,
-128L,
-1L,
0L,
1L,
127L,
1000L,
1_000_000L,
1_000_000_000_000L,
long.MaxValue - 1,
long.MaxValue,
];
private static int Main(string[] args)
{
try
{
string caseName = ParseCaseName(args);
string dataFile = RequireDataFile();
byte[] input = File.ReadAllBytes(dataFile);
byte[] output = ExecuteCase(caseName, input);
File.WriteAllBytes(dataFile, output);
Console.WriteLine($"case {caseName} passed");
return 0;
}
catch (Exception ex)
{
Console.Error.WriteLine($"xlang peer failed: {ex}");
return 1;
}
}
private static string ParseCaseName(string[] args)
{
for (int i = 0; i < args.Length; i++)
{
if (args[i] == "--case" && i + 1 < args.Length)
{
return args[i + 1];
}
}
if (args.Length == 1)
{
return args[0];
}
throw new InvalidOperationException("Usage: Fory.XlangPeer --case <case_name>");
}
private static string RequireDataFile()
{
string? dataFile = Environment.GetEnvironmentVariable(DataFileEnv);
if (string.IsNullOrWhiteSpace(dataFile))
{
throw new InvalidOperationException($"{DataFileEnv} environment variable is required");
}
return dataFile;
}
private static byte[] ExecuteCase(string caseName, byte[] input)
{
return caseName switch
{
"test_buffer" => CaseBuffer(input),
"test_buffer_var" => CaseBufferVar(input),
"test_murmurhash3" => CaseMurmurHash3(input),
"test_string_serializer" => CaseStringSerializer(input),
"test_cross_language_serializer" => CaseCrossLanguageSerializer(input),
"test_simple_struct" => CaseSimpleStruct(input),
"test_named_simple_struct" => CaseNamedSimpleStruct(input),
"test_struct_evolving_override" => CaseStructEvolvingOverride(input),
"test_list" => CaseList(input),
"test_map" => CaseMap(input),
"test_integer" => CaseInteger(input),
"test_item" => CaseItem(input),
"test_color" => CaseColor(input),
"test_union_xlang" => CaseUnionXlang(input),
"test_struct_with_list" => CaseStructWithList(input),
"test_struct_with_map" => CaseStructWithMap(input),
"test_skip_id_custom" => CaseSkipIdCustom(input),
"test_skip_name_custom" => CaseSkipNameCustom(input),
"test_consistent_named" => CaseConsistentNamed(input),
"test_struct_version_check" => CaseStructVersionCheck(input),
"test_polymorphic_list" => CasePolymorphicList(input),
"test_polymorphic_map" => CasePolymorphicMap(input),
"test_one_field_struct_compatible" => CaseOneFieldStructCompatible(input),
"test_one_field_struct_schema" => CaseOneFieldStructSchema(input),
"test_one_string_field_schema" => CaseOneStringFieldSchema(input),
"test_one_string_field_compatible" => CaseOneStringFieldCompatible(input),
"test_two_string_field_compatible" => CaseTwoStringFieldCompatible(input),
"test_schema_evolution_compatible" => CaseSchemaEvolutionCompatible(input),
"test_schema_evolution_compatible_reverse" => CaseSchemaEvolutionCompatibleReverse(input),
"test_one_enum_field_schema" => CaseOneEnumFieldSchema(input),
"test_one_enum_field_compatible" => CaseOneEnumFieldCompatible(input),
"test_two_enum_field_compatible" => CaseTwoEnumFieldCompatible(input),
"test_enum_schema_evolution_compatible" => CaseEnumSchemaEvolutionCompatible(input),
"test_enum_schema_evolution_compatible_reverse" => CaseEnumSchemaEvolutionCompatibleReverse(input),
"test_nullable_field_schema_consistent_not_null" => CaseNullableFieldSchemaConsistentNotNull(input),
"test_nullable_field_schema_consistent_null" => CaseNullableFieldSchemaConsistentNull(input),
"test_nullable_field_compatible_not_null" => CaseNullableFieldCompatibleNotNull(input),
"test_nullable_field_compatible_null" => CaseNullableFieldCompatibleNull(input),
"test_ref_schema_consistent" => CaseRefSchemaConsistent(input),
"test_ref_compatible" => CaseRefCompatible(input),
"test_collection_element_ref_override" => CaseCollectionElementRefOverride(input),
"test_circular_ref_schema_consistent" => CaseCircularRefSchemaConsistent(input),
"test_circular_ref_compatible" => CaseCircularRefCompatible(input),
"test_unsigned_schema_consistent_simple" => CaseUnsignedSchemaConsistentSimple(input),
"test_unsigned_schema_consistent" => CaseUnsignedSchemaConsistent(input),
"test_unsigned_schema_compatible" => CaseUnsignedSchemaCompatible(input),
_ => throw new InvalidOperationException($"unknown test case {caseName}"),
};
}
private static byte[] CaseBuffer(byte[] input)
{
ByteReader reader = new(input);
Ensure(reader.ReadUInt8() == 1, "bool mismatch");
Ensure(reader.ReadInt8() == sbyte.MaxValue, "byte mismatch");
Ensure(reader.ReadInt16() == short.MaxValue, "int16 mismatch");
Ensure(reader.ReadInt32() == int.MaxValue, "int32 mismatch");
Ensure(reader.ReadInt64() == long.MaxValue, "int64 mismatch");
Ensure(Math.Abs(reader.ReadFloat32() - (-1.1f)) < 0.0001f, "float32 mismatch");
Ensure(Math.Abs(reader.ReadFloat64() - (-1.1d)) < 0.000001d, "float64 mismatch");
Ensure(reader.ReadVarUInt32() == 100, "varuint32 mismatch");
int size = reader.ReadInt32();
byte[] payload = reader.ReadBytes(size);
Ensure(payload.SequenceEqual("ab"u8.ToArray()), "binary mismatch");
Ensure(reader.Remaining == 0, "buffer should be fully consumed");
ByteWriter writer = new();
writer.WriteUInt8(1);
writer.WriteInt8(sbyte.MaxValue);
writer.WriteInt16(short.MaxValue);
writer.WriteInt32(int.MaxValue);
writer.WriteInt64(long.MaxValue);
writer.WriteFloat32(-1.1f);
writer.WriteFloat64(-1.1d);
writer.WriteVarUInt32(100);
writer.WriteInt32(2);
writer.WriteBytes("ab"u8);
return writer.ToArray();
}
private static byte[] CaseBufferVar(byte[] input)
{
ByteReader reader = new(input);
foreach (int expected in VarInt32Values)
{
Ensure(reader.ReadVarInt32() == expected, $"varint32 mismatch {expected}");
}
foreach (uint expected in VarUInt32Values)
{
Ensure(reader.ReadVarUInt32() == expected, $"varuint32 mismatch {expected}");
}
foreach (ulong expected in VarUInt64Values)
{
Ensure(reader.ReadVarUInt64() == expected, $"varuint64 mismatch {expected}");
}
foreach (long expected in VarInt64Values)
{
Ensure(reader.ReadVarInt64() == expected, $"varint64 mismatch {expected}");
}
Ensure(reader.Remaining == 0, "buffer var should be fully consumed");
ByteWriter writer = new();
foreach (int value in VarInt32Values)
{
writer.WriteVarInt32(value);
}
foreach (uint value in VarUInt32Values)
{
writer.WriteVarUInt32(value);
}
foreach (ulong value in VarUInt64Values)
{
writer.WriteVarUInt64(value);
}
foreach (long value in VarInt64Values)
{
writer.WriteVarInt64(value);
}
return writer.ToArray();
}
private static byte[] CaseMurmurHash3(byte[] input)
{
if (input.Length == 32)
{
(ulong h1a, ulong h1b) = MurmurHash3.X64_128([1, 2, 8], 47);
(ulong h2a, ulong h2b) = MurmurHash3.X64_128(Encoding.UTF8.GetBytes("01234567890123456789"), 47);
ByteWriter writer = new();
writer.WriteInt64(unchecked((long)h1a));
writer.WriteInt64(unchecked((long)h1b));
writer.WriteInt64(unchecked((long)h2a));
writer.WriteInt64(unchecked((long)h2b));
return writer.ToArray();
}
if (input.Length == 16)
{
ByteReader reader = new(input);
long h1 = reader.ReadInt64();
long h2 = reader.ReadInt64();
(ulong expected1, ulong expected2) = MurmurHash3.X64_128([1, 2, 8], 47);
Ensure(h1 == unchecked((long)expected1), "murmur hash h1 mismatch");
Ensure(h2 == unchecked((long)expected2), "murmur hash h2 mismatch");
return [];
}
throw new InvalidOperationException($"unexpected murmur hash input length {input.Length}");
}
private static byte[] CaseStringSerializer(byte[] input)
{
ForyRuntime fory = BuildFory(compatible: true);
ReadOnlySequence<byte> sequence = new(input);
foreach (string expected in StringSamples)
{
string value = fory.Deserialize<string>(ref sequence);
Ensure(value == expected, "string value mismatch");
}
EnsureConsumed(sequence, nameof(CaseStringSerializer));
List<byte> output = [];
foreach (string sample in StringSamples)
{
Append(output, fory.Serialize<object?>(sample));
}
return output.ToArray();
}
private static byte[] CaseCrossLanguageSerializer(byte[] input)
{
ForyRuntime fory = BuildFory(compatible: true);
fory.Register<Color>(101);
ReadOnlySequence<byte> sequence = new(input);
bool b1 = fory.Deserialize<bool>(ref sequence);
bool b2 = fory.Deserialize<bool>(ref sequence);
int i32 = fory.Deserialize<int>(ref sequence);
sbyte i8a = fory.Deserialize<sbyte>(ref sequence);
sbyte i8b = fory.Deserialize<sbyte>(ref sequence);
short i16a = fory.Deserialize<short>(ref sequence);
short i16b = fory.Deserialize<short>(ref sequence);
int i32a = fory.Deserialize<int>(ref sequence);
int i32b = fory.Deserialize<int>(ref sequence);
long i64a = fory.Deserialize<long>(ref sequence);
long i64b = fory.Deserialize<long>(ref sequence);
float f32 = fory.Deserialize<float>(ref sequence);
double f64 = fory.Deserialize<double>(ref sequence);
string str = fory.Deserialize<string>(ref sequence);
DateOnly day = fory.Deserialize<DateOnly>(ref sequence);
DateTimeOffset timestamp = fory.Deserialize<DateTimeOffset>(ref sequence);
bool[] bools = fory.Deserialize<bool[]>(ref sequence);
byte[] bytes = fory.Deserialize<byte[]>(ref sequence);
short[] int16s = fory.Deserialize<short[]>(ref sequence);
int[] int32s = fory.Deserialize<int[]>(ref sequence);
long[] int64s = fory.Deserialize<long[]>(ref sequence);
float[] floats = fory.Deserialize<float[]>(ref sequence);
double[] doubles = fory.Deserialize<double[]>(ref sequence);
List<string> list = fory.Deserialize<List<string>>(ref sequence);
HashSet<string> set = fory.Deserialize<HashSet<string>>(ref sequence);
Dictionary<string, string> map = fory.Deserialize<Dictionary<string, string>>(ref sequence);
Color color = fory.Deserialize<Color>(ref sequence);
EnsureConsumed(sequence, nameof(CaseCrossLanguageSerializer));
Ensure(b1, "bool1 mismatch");
Ensure(!b2, "bool2 mismatch");
Ensure(i32 == -1, "int mismatch");
Ensure(str == "str", "string mismatch");
Ensure(day == new DateOnly(2021, 11, 23), "date mismatch");
Ensure(timestamp.ToUnixTimeSeconds() == 100, "timestamp mismatch");
Ensure(color == Color.White, "color mismatch");
List<byte> output = [];
Append(output, fory.Serialize<object?>(b1));
Append(output, fory.Serialize<object?>(b2));
Append(output, fory.Serialize<object?>(i32));
Append(output, fory.Serialize<object?>(i8a));
Append(output, fory.Serialize<object?>(i8b));
Append(output, fory.Serialize<object?>(i16a));
Append(output, fory.Serialize<object?>(i16b));
Append(output, fory.Serialize<object?>(i32a));
Append(output, fory.Serialize<object?>(i32b));
Append(output, fory.Serialize<object?>(i64a));
Append(output, fory.Serialize<object?>(i64b));
Append(output, fory.Serialize<object?>(f32));
Append(output, fory.Serialize<object?>(f64));
Append(output, fory.Serialize<object?>(str));
Append(output, fory.Serialize<object?>(day));
Append(output, fory.Serialize<object?>(timestamp));
Append(output, fory.Serialize<object?>(bools));
Append(output, fory.Serialize<object?>(bytes));
Append(output, fory.Serialize<object?>(int16s));
Append(output, fory.Serialize<object?>(int32s));
Append(output, fory.Serialize<object?>(int64s));
Append(output, fory.Serialize<object?>(floats));
Append(output, fory.Serialize<object?>(doubles));
Append(output, fory.Serialize<object?>(list));
Append(output, fory.Serialize<object?>(set));
Append(output, fory.Serialize<object?>(map));
Append(output, fory.Serialize<object?>(color));
return output.ToArray();
}
private static byte[] CaseSimpleStruct(byte[] input)
{
ForyRuntime fory = BuildFory(compatible: true);
RegisterSimpleById(fory);
return RoundTripSingle<SimpleStruct>(input, fory);
}
private static byte[] CaseNamedSimpleStruct(byte[] input)
{
ForyRuntime fory = BuildFory(compatible: true);
RegisterSimpleByName(fory);
return RoundTripSingle<SimpleStruct>(input, fory);
}
private static byte[] CaseStructEvolvingOverride(byte[] input)
{
ForyRuntime fory = BuildFory(compatible: true);
fory.Register<EvolvingOverrideStruct>("test", "evolving_yes");
fory.Register<FixedOverrideStruct>("test", "evolving_off");
ReadOnlySequence<byte> sequence = new(input);
EvolvingOverrideStruct evolving = fory.Deserialize<EvolvingOverrideStruct>(ref sequence);
FixedOverrideStruct fixedValue = fory.Deserialize<FixedOverrideStruct>(ref sequence);
EnsureConsumed(sequence, nameof(CaseStructEvolvingOverride));
Ensure(evolving.F1 == "payload", "evolving override struct mismatch");
Ensure(fixedValue.F1 == "payload", "fixed override struct mismatch");
List<byte> output = [];
Append(output, fory.Serialize<object?>(evolving));
Append(output, fory.Serialize<object?>(fixedValue));
return output.ToArray();
}
private static byte[] CaseList(byte[] input)
{
ForyRuntime fory = BuildFory(compatible: true);
fory.Register<Item>(102);
ReadOnlySequence<byte> sequence = new(input);
List<string?> strList = fory.Deserialize<List<string?>>(ref sequence);
List<string?> strList2 = fory.Deserialize<List<string?>>(ref sequence);
List<Item?> itemList = fory.Deserialize<List<Item?>>(ref sequence);
List<Item?> itemList2 = fory.Deserialize<List<Item?>>(ref sequence);
EnsureConsumed(sequence, nameof(CaseList));
List<byte> output = [];
Append(output, fory.Serialize<object?>(strList));
Append(output, fory.Serialize<object?>(strList2));
Append(output, fory.Serialize<object?>(itemList));
Append(output, fory.Serialize<object?>(itemList2));
return output.ToArray();
}
private static byte[] CaseMap(byte[] input)
{
ForyRuntime fory = BuildFory(compatible: true);
fory.Register<Item>(102);
ReadOnlySequence<byte> sequence = new(input);
NullableKeyDictionary<string, string?> strMap = fory.Deserialize<NullableKeyDictionary<string, string?>>(ref sequence);
NullableKeyDictionary<string, Item?> itemMap = fory.Deserialize<NullableKeyDictionary<string, Item?>>(ref sequence);
EnsureConsumed(sequence, nameof(CaseMap));
List<byte> output = [];
Append(output, fory.Serialize<object?>(strMap));
Append(output, fory.Serialize<object?>(itemMap));
return output.ToArray();
}
private static byte[] CaseInteger(byte[] input)
{
ForyRuntime fory = BuildFory(compatible: true);
fory.Register<Item1>(101);
ReadOnlySequence<byte> sequence = new(input);
Item1 obj = fory.Deserialize<Item1>(ref sequence);
int f1 = fory.Deserialize<int>(ref sequence);
int f2 = fory.Deserialize<int>(ref sequence);
int f3 = fory.Deserialize<int>(ref sequence);
int f4 = fory.Deserialize<int>(ref sequence);
int f5 = fory.Deserialize<int>(ref sequence);
int f6 = fory.Deserialize<int>(ref sequence);
EnsureConsumed(sequence, nameof(CaseInteger));
Ensure(obj.F1 == 1 && obj.F2 == 2, "item1 primitive fields mismatch");
Ensure(obj.F3 == 3 && obj.F4 == 4 && obj.F5 == 0 && obj.F6 == 0, "item1 boxed fields mismatch");
List<byte> output = [];
Append(output, fory.Serialize<object?>(obj));
Append(output, fory.Serialize<object?>(f1));
Append(output, fory.Serialize<object?>(f2));
Append(output, fory.Serialize<object?>(f3));
Append(output, fory.Serialize<object?>(f4));
Append(output, fory.Serialize<object?>(f5));
Append(output, fory.Serialize<object?>(f6));
return output.ToArray();
}
private static byte[] CaseItem(byte[] input)
{
ForyRuntime fory = BuildFory(compatible: true);
fory.Register<Item>(102);
ReadOnlySequence<byte> sequence = new(input);
Item i1 = fory.Deserialize<Item>(ref sequence);
Item i2 = fory.Deserialize<Item>(ref sequence);
Item i3 = fory.Deserialize<Item>(ref sequence);
EnsureConsumed(sequence, nameof(CaseItem));
List<byte> output = [];
Append(output, fory.Serialize<object?>(i1));
Append(output, fory.Serialize<object?>(i2));
Append(output, fory.Serialize<object?>(i3));
return output.ToArray();
}
private static byte[] CaseColor(byte[] input)
{
ForyRuntime fory = BuildFory(compatible: true);
fory.Register<Color>(101);
ReadOnlySequence<byte> sequence = new(input);
Color c1 = fory.Deserialize<Color>(ref sequence);
Color c2 = fory.Deserialize<Color>(ref sequence);
Color c3 = fory.Deserialize<Color>(ref sequence);
Color c4 = fory.Deserialize<Color>(ref sequence);
EnsureConsumed(sequence, nameof(CaseColor));
List<byte> output = [];
Append(output, fory.Serialize<object?>(c1));
Append(output, fory.Serialize<object?>(c2));
Append(output, fory.Serialize<object?>(c3));
Append(output, fory.Serialize<object?>(c4));
return output.ToArray();
}
private static byte[] CaseStructWithList(byte[] input)
{
ForyRuntime fory = BuildFory(compatible: true);
fory.Register<StructWithList>(201);
ReadOnlySequence<byte> sequence = new(input);
StructWithList s1 = fory.Deserialize<StructWithList>(ref sequence);
StructWithList s2 = fory.Deserialize<StructWithList>(ref sequence);
EnsureConsumed(sequence, nameof(CaseStructWithList));
List<byte> output = [];
Append(output, fory.Serialize<object?>(s1));
Append(output, fory.Serialize<object?>(s2));
return output.ToArray();
}
private static byte[] CaseStructWithMap(byte[] input)
{
ForyRuntime fory = BuildFory(compatible: true);
fory.Register<StructWithMap>(202);
ReadOnlySequence<byte> sequence = new(input);
StructWithMap s1 = fory.Deserialize<StructWithMap>(ref sequence);
StructWithMap s2 = fory.Deserialize<StructWithMap>(ref sequence);
EnsureConsumed(sequence, nameof(CaseStructWithMap));
List<byte> output = [];
Append(output, fory.Serialize<object?>(s1));
Append(output, fory.Serialize<object?>(s2));
return output.ToArray();
}
private static byte[] CaseUnionXlang(byte[] input)
{
ForyRuntime fory = BuildFory(compatible: true);
fory.Register<StructWithUnion2>(301);
ReadOnlySequence<byte> sequence = new(input);
StructWithUnion2 first = fory.Deserialize<StructWithUnion2>(ref sequence);
StructWithUnion2 second = fory.Deserialize<StructWithUnion2>(ref sequence);
EnsureConsumed(sequence, nameof(CaseUnionXlang));
Ensure(first.Union.Index == 0, "union case index mismatch for first value");
Ensure(first.Union.Value is string firstValue && firstValue == "hello", "union case value mismatch for first value");
Ensure(second.Union.Index == 1, "union case index mismatch for second value");
Ensure(second.Union.Value is long secondValue && secondValue == 42L, "union case value mismatch for second value");
List<byte> output = [];
Append(output, fory.Serialize<object?>(first));
Append(output, fory.Serialize<object?>(second));
return output.ToArray();
}
private static byte[] CaseSkipIdCustom(byte[] input)
{
ForyRuntime fory = BuildFory(compatible: true);
fory.Register<Color>(101);
fory.Register<MyStruct>(102);
fory.Register<MyExt, MyExtSerializer>(103);
fory.Register<MyWrapper>(104);
return RoundTripSingle<MyWrapper>(input, fory);
}
private static byte[] CaseSkipNameCustom(byte[] input)
{
ForyRuntime fory = BuildFory(compatible: true);
fory.Register<Color>("color");
fory.Register<MyStruct>("my_struct");
fory.Register<MyExt, MyExtSerializer>(string.Empty, "my_ext");
fory.Register<MyWrapper>("my_wrapper");
return RoundTripSingle<MyWrapper>(input, fory);
}
private static byte[] CaseConsistentNamed(byte[] input)
{
ForyRuntime fory = BuildFory(compatible: false, checkStructVersion: true);
fory.Register<Color>("color");
fory.Register<MyStruct>("my_struct");
fory.Register<MyExt, MyExtSerializer>(string.Empty, "my_ext");
ReadOnlySequence<byte> sequence = new(input);
List<byte> output = [];
for (int i = 0; i < 3; i++)
{
Color color = fory.Deserialize<Color>(ref sequence);
Append(output, fory.Serialize<object?>(color));
}
for (int i = 0; i < 3; i++)
{
MyStruct myStruct = fory.Deserialize<MyStruct>(ref sequence);
Append(output, fory.Serialize<object?>(myStruct));
}
for (int i = 0; i < 3; i++)
{
MyExt myExt = fory.Deserialize<MyExt>(ref sequence);
Append(output, fory.Serialize<object?>(myExt));
}
EnsureConsumed(sequence, nameof(CaseConsistentNamed));
return output.ToArray();
}
private static byte[] CaseStructVersionCheck(byte[] input)
{
ForyRuntime fory = BuildFory(compatible: false, checkStructVersion: true);
fory.Register<VersionCheckStruct>(201);
return RoundTripSingle<VersionCheckStruct>(input, fory);
}
private static byte[] CasePolymorphicList(byte[] input)
{
ForyRuntime fory = BuildFory(compatible: true);
fory.Register<Dog>(302);
fory.Register<Cat>(303);
fory.Register<AnimalListHolder>(304);
ReadOnlySequence<byte> sequence = new(input);
List<object?> animals = fory.Deserialize<List<object?>>(ref sequence);
AnimalListHolder holder = fory.Deserialize<AnimalListHolder>(ref sequence);
EnsureConsumed(sequence, nameof(CasePolymorphicList));
List<byte> output = [];
Append(output, fory.Serialize<object?>(animals));
Append(output, fory.Serialize<object?>(holder));
return output.ToArray();
}
private static byte[] CasePolymorphicMap(byte[] input)
{
ForyRuntime fory = BuildFory(compatible: true);
fory.Register<Dog>(302);
fory.Register<Cat>(303);
fory.Register<AnimalMapHolder>(305);
ReadOnlySequence<byte> sequence = new(input);
Dictionary<string, object?> map = fory.Deserialize<Dictionary<string, object?>>(ref sequence);
AnimalMapHolder holder = fory.Deserialize<AnimalMapHolder>(ref sequence);
EnsureConsumed(sequence, nameof(CasePolymorphicMap));
List<byte> output = [];
Append(output, fory.Serialize<object?>(map));
Append(output, fory.Serialize<object?>(holder));
return output.ToArray();
}
private static byte[] CaseOneFieldStructCompatible(byte[] input)
{
ForyRuntime fory = BuildFory(compatible: true);
fory.Register<OneFieldStruct>(200);
return RoundTripSingle<OneFieldStruct>(input, fory);
}
private static byte[] CaseOneFieldStructSchema(byte[] input)
{
ForyRuntime fory = BuildFory(compatible: false);
fory.Register<OneFieldStruct>(200);
return RoundTripSingle<OneFieldStruct>(input, fory);
}
private static byte[] CaseOneStringFieldSchema(byte[] input)
{
ForyRuntime fory = BuildFory(compatible: false);
fory.Register<OneStringFieldStruct>(200);
return RoundTripSingle<OneStringFieldStruct>(input, fory);
}
private static byte[] CaseOneStringFieldCompatible(byte[] input)
{
ForyRuntime fory = BuildFory(compatible: true);
fory.Register<OneStringFieldStruct>(200);
return RoundTripSingle<OneStringFieldStruct>(input, fory);
}
private static byte[] CaseTwoStringFieldCompatible(byte[] input)
{
ForyRuntime fory = BuildFory(compatible: true);
fory.Register<TwoStringFieldStruct>(201);
return RoundTripSingle<TwoStringFieldStruct>(input, fory);
}
private static byte[] CaseSchemaEvolutionCompatible(byte[] input)
{
ForyRuntime fory = BuildFory(compatible: true);
fory.Register<TwoStringFieldStruct>(200);
return RoundTripSingle<TwoStringFieldStruct>(input, fory);
}
private static byte[] CaseSchemaEvolutionCompatibleReverse(byte[] input)
{
ForyRuntime fory = BuildFory(compatible: true);
fory.Register<OneStringFieldStruct>(200);
return RoundTripSingle<OneStringFieldStruct>(input, fory);
}
private static byte[] CaseOneEnumFieldSchema(byte[] input)
{
ForyRuntime fory = BuildFory(compatible: false);
fory.Register<TestEnum>(210);
fory.Register<OneEnumFieldStruct>(211);
return RoundTripSingle<OneEnumFieldStruct>(input, fory);
}
private static byte[] CaseOneEnumFieldCompatible(byte[] input)
{
ForyRuntime fory = BuildFory(compatible: true);
fory.Register<TestEnum>(210);
fory.Register<OneEnumFieldStruct>(211);
return RoundTripSingle<OneEnumFieldStruct>(input, fory);
}
private static byte[] CaseTwoEnumFieldCompatible(byte[] input)
{
ForyRuntime fory = BuildFory(compatible: true);
fory.Register<TestEnum>(210);
fory.Register<TwoEnumFieldStruct>(212);
return RoundTripSingle<TwoEnumFieldStruct>(input, fory);
}
private static byte[] CaseEnumSchemaEvolutionCompatible(byte[] input)
{
ForyRuntime fory = BuildFory(compatible: true);
fory.Register<TestEnum>(210);
fory.Register<TwoEnumFieldStruct>(211);
return RoundTripSingle<TwoEnumFieldStruct>(input, fory);
}
private static byte[] CaseEnumSchemaEvolutionCompatibleReverse(byte[] input)
{
ForyRuntime fory = BuildFory(compatible: true);
fory.Register<TestEnum>(210);
fory.Register<TwoEnumFieldStruct>(211);
ReadOnlySequence<byte> sequence = new(input);
TwoEnumFieldStruct value = fory.Deserialize<TwoEnumFieldStruct>(ref sequence);
EnsureConsumed(sequence, nameof(CaseEnumSchemaEvolutionCompatibleReverse));
Ensure(value.F1 == TestEnum.ValueC, "enum schema evolution reverse F1 mismatch");
Ensure(value.F2 == TestEnum.ValueA, "enum schema evolution reverse F2 default mismatch");
return fory.Serialize<object?>(value);
}
private static byte[] CaseNullableFieldSchemaConsistentNotNull(byte[] input)
{
ForyRuntime fory = BuildFory(compatible: false);
fory.Register<NullableComprehensiveSchemaConsistent>(401);
return RoundTripSingle<NullableComprehensiveSchemaConsistent>(input, fory);
}
private static byte[] CaseNullableFieldSchemaConsistentNull(byte[] input)
{
ForyRuntime fory = BuildFory(compatible: false);
fory.Register<NullableComprehensiveSchemaConsistent>(401);
return RoundTripSingle<NullableComprehensiveSchemaConsistent>(input, fory);
}
private static byte[] CaseNullableFieldCompatibleNotNull(byte[] input)
{
ForyRuntime fory = BuildFory(compatible: true);
fory.Register<NullableComprehensiveCompatible>(402);
return RoundTripSingle<NullableComprehensiveCompatible>(input, fory);
}
private static byte[] CaseNullableFieldCompatibleNull(byte[] input)
{
ForyRuntime fory = BuildFory(compatible: true);
fory.Register<NullableComprehensiveCompatible>(402);
return RoundTripSingle<NullableComprehensiveCompatible>(input, fory);
}
private static byte[] CaseRefSchemaConsistent(byte[] input)
{
ForyRuntime fory = BuildFory(compatible: false, trackRef: true);
fory.Register<RefInnerSchemaConsistent>(501);
fory.Register<RefOuterSchemaConsistent>(502);
ReadOnlySequence<byte> sequence = new(input);
RefOuterSchemaConsistent outer = fory.Deserialize<RefOuterSchemaConsistent>(ref sequence);
EnsureConsumed(sequence, nameof(CaseRefSchemaConsistent));
Ensure(ReferenceEquals(outer.Inner1, outer.Inner2), "reference tracking mismatch");
return fory.Serialize<object?>(outer);
}
private static byte[] CaseRefCompatible(byte[] input)
{
ForyRuntime fory = BuildFory(compatible: true, trackRef: true);
fory.Register<RefInnerCompatible>(503);
fory.Register<RefOuterCompatible>(504);
ReadOnlySequence<byte> sequence = new(input);
RefOuterCompatible outer = fory.Deserialize<RefOuterCompatible>(ref sequence);
EnsureConsumed(sequence, nameof(CaseRefCompatible));
Ensure(ReferenceEquals(outer.Inner1, outer.Inner2), "reference tracking mismatch");
return fory.Serialize<object?>(outer);
}
private static byte[] CaseCollectionElementRefOverride(byte[] input)
{
ForyRuntime fory = BuildFory(compatible: false, trackRef: true);
fory.Register<RefOverrideElement>(701);
fory.Register<RefOverrideContainer>(702);
ReadOnlySequence<byte> sequence = new(input);
RefOverrideContainer container = fory.Deserialize<RefOverrideContainer>(ref sequence);
EnsureConsumed(sequence, nameof(CaseCollectionElementRefOverride));
if (container.ListField.Count > 0)
{
RefOverrideElement? shared = container.ListField[0];
if (shared is not null)
{
if (container.ListField.Count > 1)
{
container.ListField[1] = shared;
}
if (container.MapField.ContainsKey("k1"))
{
container.MapField["k1"] = shared;
}
if (container.MapField.ContainsKey("k2"))
{
container.MapField["k2"] = shared;
}
}
}
return fory.Serialize<object?>(container);
}
private static byte[] CaseCircularRefSchemaConsistent(byte[] input)
{
ForyRuntime fory = BuildFory(compatible: false, trackRef: true);
fory.Register<CircularRefStruct>(601);
ReadOnlySequence<byte> sequence = new(input);
CircularRefStruct value = fory.Deserialize<CircularRefStruct>(ref sequence);
EnsureConsumed(sequence, nameof(CaseCircularRefSchemaConsistent));
Ensure(ReferenceEquals(value, value.SelfRef), "circular ref mismatch");
return fory.Serialize<object?>(value);
}
private static byte[] CaseCircularRefCompatible(byte[] input)
{
ForyRuntime fory = BuildFory(compatible: true, trackRef: true);
fory.Register<CircularRefStruct>(602);
ReadOnlySequence<byte> sequence = new(input);
CircularRefStruct value = fory.Deserialize<CircularRefStruct>(ref sequence);
EnsureConsumed(sequence, nameof(CaseCircularRefCompatible));
Ensure(ReferenceEquals(value, value.SelfRef), "circular ref mismatch");
return fory.Serialize<object?>(value);
}
private static byte[] CaseUnsignedSchemaConsistentSimple(byte[] input)
{
ForyRuntime fory = BuildFory(compatible: false);
fory.Register<UnsignedSchemaConsistentSimple>(1);
return RoundTripSingle<UnsignedSchemaConsistentSimple>(input, fory);
}
private static byte[] CaseUnsignedSchemaConsistent(byte[] input)
{
ForyRuntime fory = BuildFory(compatible: false);
fory.Register<UnsignedSchemaConsistent>(501);
return RoundTripSingle<UnsignedSchemaConsistent>(input, fory);
}
private static byte[] CaseUnsignedSchemaCompatible(byte[] input)
{
ForyRuntime fory = BuildFory(compatible: true);
fory.Register<UnsignedSchemaCompatible>(502);
return RoundTripSingle<UnsignedSchemaCompatible>(input, fory);
}
private static byte[] RoundTripSingle<T>(byte[] input, ForyRuntime fory)
{
ReadOnlySequence<byte> sequence = new(input);
T value = fory.Deserialize<T>(ref sequence);
EnsureConsumed(sequence, typeof(T).Name);
return fory.Serialize<object?>(value);
}
private static void RegisterSimpleById(ForyRuntime fory)
{
fory.Register<Color>(101);
fory.Register<Item>(102);
fory.Register<SimpleStruct>(103);
}
private static void RegisterSimpleByName(ForyRuntime fory)
{
fory.Register<Color>("demo", "color");
fory.Register<Item>("demo", "item");
fory.Register<SimpleStruct>("demo", "simple_struct");
}
private static ForyRuntime BuildFory(bool compatible, bool trackRef = false, bool checkStructVersion = false)
{
return ForyRuntime.Builder()
.Compatible(compatible)
.TrackRef(trackRef)
.CheckStructVersion(checkStructVersion)
.Build();
}
private static void Append(List<byte> target, byte[] payload)
{
target.AddRange(payload);
}
private static void EnsureConsumed(ReadOnlySequence<byte> sequence, string caseName)
{
Ensure(sequence.Length == 0, $"case {caseName} did not consume full payload");
}
private static void Ensure(bool condition, string message)
{
if (!condition)
{
throw new InvalidOperationException(message);
}
}
}
[ForyObject]
public enum Color
{
Green,
Red,
Blue,
White,
}
[ForyObject]
public sealed class Item
{
public string Name { get; set; } = string.Empty;
}
[ForyObject]
public sealed class SimpleStruct
{
public Dictionary<int, double> F1 { get; set; } = [];
public int F2 { get; set; }
public Item F3 { get; set; } = new();
public string F4 { get; set; } = string.Empty;
public Color F5 { get; set; }
public List<string> F6 { get; set; } = [];
public int F7 { get; set; }
public int F8 { get; set; }
public int Last { get; set; }
}
[ForyObject]
public sealed class EvolvingOverrideStruct
{
public string F1 { get; set; } = string.Empty;
}
[ForyObject(Evolving = false)]
public sealed class FixedOverrideStruct
{
public string F1 { get; set; } = string.Empty;
}
[ForyObject]
public sealed class Item1
{
public int F1 { get; set; }
public int F2 { get; set; }
public int F3 { get; set; }
public int F4 { get; set; }
public int F5 { get; set; }
public int F6 { get; set; }
}
[ForyObject]
public sealed class StructWithList
{
public List<string?> Items { get; set; } = [];
}
[ForyObject]
public sealed class StructWithMap
{
public NullableKeyDictionary<string, string?> Data { get; set; } = new();
}
[ForyObject]
public sealed class StructWithUnion2
{
public Union2<string, long> Union { get; set; } = Union2<string, long>.OfT1(string.Empty);
}
[ForyObject]
public sealed class MyStruct
{
public int Id { get; set; }
}
[ForyObject]
public sealed class MyExt
{
public int Id { get; set; }
}
public sealed class MyExtSerializer : Serializer<MyExt>
{
public override MyExt DefaultValue => null!;
public override void WriteData(WriteContext context, in MyExt value, bool hasGenerics)
{
_ = hasGenerics;
context.Writer.WriteVarInt32((value ?? new MyExt()).Id);
}
public override MyExt ReadData(ReadContext context)
{
return new MyExt { Id = context.Reader.ReadVarInt32() };
}
}
[ForyObject]
public sealed class MyWrapper
{
public Color Color { get; set; }
public MyExt MyExt { get; set; } = new();
public MyStruct MyStruct { get; set; } = new();
}
[ForyObject]
public sealed class EmptyWrapper
{
}
[ForyObject]
public sealed class VersionCheckStruct
{
public int F1 { get; set; }
public string? F2 { get; set; }
public double F3 { get; set; }
}
[ForyObject]
public sealed class Dog
{
public int Age { get; set; }
public string? Name { get; set; }
}
[ForyObject]
public sealed class Cat
{
public int Age { get; set; }
public int Lives { get; set; }
}
[ForyObject]
public sealed class AnimalListHolder
{
public List<object?> Animals { get; set; } = [];
}
[ForyObject]
public sealed class AnimalMapHolder
{
public Dictionary<string, object?> AnimalMap { get; set; } = [];
}
[ForyObject]
public sealed class OneFieldStruct
{
public int Value { get; set; }
}
[ForyObject]
public sealed class OneStringFieldStruct
{
public string? F1 { get; set; }
}
[ForyObject]
public sealed class TwoStringFieldStruct
{
public string F1 { get; set; } = string.Empty;
public string F2 { get; set; } = string.Empty;
}
[ForyObject]
public enum TestEnum
{
ValueA,
ValueB,
ValueC,
}
[ForyObject]
public sealed class OneEnumFieldStruct
{
public TestEnum F1 { get; set; }
}
[ForyObject]
public sealed class TwoEnumFieldStruct
{
public TestEnum F1 { get; set; }
public TestEnum F2 { get; set; }
}
[ForyObject]
public sealed class NullableComprehensiveSchemaConsistent
{
public sbyte ByteField { get; set; }
public short ShortField { get; set; }
public int IntField { get; set; }
public long LongField { get; set; }
public float FloatField { get; set; }
public double DoubleField { get; set; }
public bool BoolField { get; set; }
public string StringField { get; set; } = string.Empty;
public List<string> ListField { get; set; } = [];
public HashSet<string> SetField { get; set; } = [];
public NullableKeyDictionary<string, string> MapField { get; set; } = new();
public int? NullableInt { get; set; }
public long? NullableLong { get; set; }
public float? NullableFloat { get; set; }
public double? NullableDouble { get; set; }
public bool? NullableBool { get; set; }
public string? NullableString { get; set; }
public List<string>? NullableList { get; set; }
public HashSet<string>? NullableSet { get; set; }
public NullableKeyDictionary<string, string>? NullableMap { get; set; }
}
[ForyObject]
public sealed class NullableComprehensiveCompatible
{
public sbyte ByteField { get; set; }
public short ShortField { get; set; }
public int IntField { get; set; }
public long LongField { get; set; }
public float FloatField { get; set; }
public double DoubleField { get; set; }
public bool BoolField { get; set; }
public int BoxedInt { get; set; }
public long BoxedLong { get; set; }
public float BoxedFloat { get; set; }
public double BoxedDouble { get; set; }
public bool BoxedBool { get; set; }
public string StringField { get; set; } = string.Empty;
public List<string> ListField { get; set; } = [];
public HashSet<string> SetField { get; set; } = [];
public NullableKeyDictionary<string, string> MapField { get; set; } = new();
public int NullableInt1 { get; set; }
public long NullableLong1 { get; set; }
public float NullableFloat1 { get; set; }
public double NullableDouble1 { get; set; }
public bool NullableBool1 { get; set; }
public string NullableString2 { get; set; } = string.Empty;
public List<string> NullableList2 { get; set; } = [];
public HashSet<string> NullableSet2 { get; set; } = [];
public NullableKeyDictionary<string, string> NullableMap2 { get; set; } = new();
}
[ForyObject]
public sealed class RefInnerSchemaConsistent
{
public int Id { get; set; }
public string Name { get; set; } = string.Empty;
}
[ForyObject]
public sealed class RefOuterSchemaConsistent
{
public RefInnerSchemaConsistent? Inner1 { get; set; }
public RefInnerSchemaConsistent? Inner2 { get; set; }
}
[ForyObject]
public sealed class RefInnerCompatible
{
public int Id { get; set; }
public string Name { get; set; } = string.Empty;
}
[ForyObject]
public sealed class RefOuterCompatible
{
public RefInnerCompatible? Inner1 { get; set; }
public RefInnerCompatible? Inner2 { get; set; }
}
[ForyObject]
public sealed class RefOverrideElement
{
public int Id { get; set; }
public string Name { get; set; } = string.Empty;
}
[ForyObject]
public sealed class RefOverrideContainer
{
public List<RefOverrideElement?> ListField { get; set; } = [];
public Dictionary<string, RefOverrideElement?> MapField { get; set; } = [];
}
[ForyObject]
public sealed class CircularRefStruct
{
public string Name { get; set; } = string.Empty;
public CircularRefStruct? SelfRef { get; set; }
}
[ForyObject]
public sealed class UnsignedSchemaConsistentSimple
{
[Field(Encoding = FieldEncoding.Tagged)]
public ulong U64Tagged { get; set; }
[Field(Encoding = FieldEncoding.Tagged)]
public ulong? U64TaggedNullable { get; set; }
}
[ForyObject]
public sealed class UnsignedSchemaConsistent
{
public byte U8Field { get; set; }
public ushort U16Field { get; set; }
public uint U32VarField { get; set; }
[Field(Encoding = FieldEncoding.Fixed)]
public uint U32FixedField { get; set; }
public ulong U64VarField { get; set; }
[Field(Encoding = FieldEncoding.Fixed)]
public ulong U64FixedField { get; set; }
[Field(Encoding = FieldEncoding.Tagged)]
public ulong U64TaggedField { get; set; }
public byte? U8NullableField { get; set; }
public ushort? U16NullableField { get; set; }
public uint? U32VarNullableField { get; set; }
[Field(Encoding = FieldEncoding.Fixed)]
public uint? U32FixedNullableField { get; set; }
public ulong? U64VarNullableField { get; set; }
[Field(Encoding = FieldEncoding.Fixed)]
public ulong? U64FixedNullableField { get; set; }
[Field(Encoding = FieldEncoding.Tagged)]
public ulong? U64TaggedNullableField { get; set; }
}
[ForyObject]
public sealed class UnsignedSchemaCompatible
{
public byte? U8Field1 { get; set; }
public ushort? U16Field1 { get; set; }
public uint? U32VarField1 { get; set; }
[Field(Encoding = FieldEncoding.Fixed)]
public uint? U32FixedField1 { get; set; }
public ulong? U64VarField1 { get; set; }
[Field(Encoding = FieldEncoding.Fixed)]
public ulong? U64FixedField1 { get; set; }
[Field(Encoding = FieldEncoding.Tagged)]
public ulong? U64TaggedField1 { get; set; }
public byte U8Field2 { get; set; }
public ushort U16Field2 { get; set; }
public uint U32VarField2 { get; set; }
[Field(Encoding = FieldEncoding.Fixed)]
public uint U32FixedField2 { get; set; }
public ulong U64VarField2 { get; set; }
[Field(Encoding = FieldEncoding.Fixed)]
public ulong U64FixedField2 { get; set; }
[Field(Encoding = FieldEncoding.Tagged)]
public ulong U64TaggedField2 { get; set; }
}