From 2f67293b4e40160c4050a3b1aaf7fbfbec243774 Mon Sep 17 00:00:00 2001 From: MiltoxB <153580485+MiltoxB@users.noreply.github.com> Date: Fri, 13 Sep 2024 16:14:00 -0400 Subject: [PATCH 1/3] Update EnumSchema.cs Prevent Duplicate Key Added Exception for Complex Enums that have Multiple Enum entries with the same value. Multiple enums with the same value are valid in C#, but this change will default the value to the first enum with said value when trying to go from a value to the enum. --- src/AvroConvert/AvroObjectServices/Schemas/EnumSchema.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/AvroConvert/AvroObjectServices/Schemas/EnumSchema.cs b/src/AvroConvert/AvroObjectServices/Schemas/EnumSchema.cs index 5d8c6552..45ff66f8 100644 --- a/src/AvroConvert/AvroObjectServices/Schemas/EnumSchema.cs +++ b/src/AvroConvert/AvroObjectServices/Schemas/EnumSchema.cs @@ -83,7 +83,7 @@ internal EnumSchema( int v = Convert.ToInt32(values.GetValue(i), CultureInfo.InvariantCulture); this.avroToCSharpValueMapping.Add(Convert.ToInt64(values.GetValue(i), CultureInfo.InvariantCulture)); this.symbolToValue.Add(this.symbols[i], v); - this.valueToSymbol.Add(v, this.symbols[i]); + this.valueToSymbol.TryAdd(v, this.symbols[i]); } } } From cc25696feae3104976d9736447f19a78f322be8a Mon Sep 17 00:00:00 2001 From: MiltoxB <153580485+MiltoxB@users.noreply.github.com> Date: Mon, 16 Sep 2024 13:50:52 +0000 Subject: [PATCH 2/3] Added Test Cases --- .../EnumTests.cs | 29 +++++++++++++++++++ tests/AvroConvertTests/TestClasses.cs | 19 ++++++++++++ 2 files changed, 48 insertions(+) diff --git a/tests/AvroConvertTests/FullSerializationAndDeserialization/EnumTests.cs b/tests/AvroConvertTests/FullSerializationAndDeserialization/EnumTests.cs index 75273bc0..a9b2f6a5 100644 --- a/tests/AvroConvertTests/FullSerializationAndDeserialization/EnumTests.cs +++ b/tests/AvroConvertTests/FullSerializationAndDeserialization/EnumTests.cs @@ -146,5 +146,34 @@ public void Class_with_enum_with_member_value_attributes(Func engine) + { + foreach (var toSerialize in Enum.GetValues()) + { + //Act + var deserialized = engine.Invoke(toSerialize, typeof(TestDuplicateEnum)); + + //Assert + Assert.Equal(toSerialize, deserialized); + } + } + + [Theory] + [MemberData(nameof(TestEngine.All), MemberType = typeof(TestEngine))] + public void Class_with_enum_with_duplicate_value(Func engine) + { + //Arrange + ClassWithDuplicateEnums toSerialize = _fixture.Create(); + + //Act + var deserialized = engine.Invoke(toSerialize, typeof(ClassWithDuplicateEnums)); + + //Assert + Assert.NotNull(deserialized); + Assert.Equal(toSerialize, deserialized); + } } } diff --git a/tests/AvroConvertTests/TestClasses.cs b/tests/AvroConvertTests/TestClasses.cs index 597235f3..292bc839 100644 --- a/tests/AvroConvertTests/TestClasses.cs +++ b/tests/AvroConvertTests/TestClasses.cs @@ -353,6 +353,25 @@ public class ClassWithEnum public TestEnum? SecondEnumProp { get; set; } } + public enum TestDuplicateEnum { + Test = 0 + Exam = Test, + Fail = -1, + Error = Fail, + TestLabel = 2, + TestLabel2 = 2 + } + + [Equals(DoNotAddEqualityOperators = true)] + public class ClassWithDuplicateEnums { + [DefaultValue(TestDuplicateEnum.Fail)] + public TestDuplicateEnum? EnumProp { get; set; } + [DefaultValue("Error")] + public TestDuplicateEnum? EnumProp2 { get; set;} + [DefaultValue(2)] + public TestDuplicateEnum? EnumProp3 { get; set; } + } + public class ClassWithEnumDefiningMembers { public TestEnumWithMembers? EnumProp { get; set; } From cca04ab39e18ff5d5f5df11aec41904daf45c26a Mon Sep 17 00:00:00 2001 From: "milton.zurita" Date: Mon, 16 Sep 2024 14:47:22 -0400 Subject: [PATCH 3/3] Adjust Enum Writing and Reading via schema to fix edge cases --- .../AvroObjectServices/Schemas/EnumSchema.cs | 3 +++ .../AvroObjectServices/Write/Resolvers/Enum.cs | 6 +++--- .../EnumTests.cs | 14 ++++++++++++-- tests/AvroConvertTests/TestClasses.cs | 4 ++-- 4 files changed, 20 insertions(+), 7 deletions(-) diff --git a/src/AvroConvert/AvroObjectServices/Schemas/EnumSchema.cs b/src/AvroConvert/AvroObjectServices/Schemas/EnumSchema.cs index 45ff66f8..04fb306b 100644 --- a/src/AvroConvert/AvroObjectServices/Schemas/EnumSchema.cs +++ b/src/AvroConvert/AvroObjectServices/Schemas/EnumSchema.cs @@ -92,6 +92,9 @@ internal EnumSchema( internal bool TryGetSymbolValue(string symbol, out int value) => this.symbolToValue.TryGetValue(symbol, out value); + + internal int GetSymbolPosition(string symbol) => + this.symbols.IndexOf(symbol); internal string GetSymbolByValue(int value) { diff --git a/src/AvroConvert/AvroObjectServices/Write/Resolvers/Enum.cs b/src/AvroConvert/AvroObjectServices/Write/Resolvers/Enum.cs index 61c0028d..6b13dd89 100644 --- a/src/AvroConvert/AvroObjectServices/Write/Resolvers/Enum.cs +++ b/src/AvroConvert/AvroObjectServices/Write/Resolvers/Enum.cs @@ -36,14 +36,14 @@ internal Encoder.WriteItem ResolveEnum(EnumSchema schema) { value = EnumParser.GetEnumName(enumType, value.ToString()); } - - if (!schema.TryGetSymbolValue(value.ToString(), out var symbolValue)) + var position = schema.GetSymbolPosition(value.ToString()); + if (position < 0) { throw new AvroTypeException( $"[Enum] Provided value is not of the enum [{schema.Name}] members"); } - e.WriteEnum(symbolValue); + e.WriteEnum(position); }; } } diff --git a/tests/AvroConvertTests/FullSerializationAndDeserialization/EnumTests.cs b/tests/AvroConvertTests/FullSerializationAndDeserialization/EnumTests.cs index a9b2f6a5..941f449d 100644 --- a/tests/AvroConvertTests/FullSerializationAndDeserialization/EnumTests.cs +++ b/tests/AvroConvertTests/FullSerializationAndDeserialization/EnumTests.cs @@ -151,13 +151,23 @@ public void Class_with_enum_with_member_value_attributes(Func engine) { - foreach (var toSerialize in Enum.GetValues()) + var itemsToTest = + new [] { + TestDuplicateEnum.Test, + TestDuplicateEnum.Exam, //Compilation already sets these values to the primary duplicate value. + TestDuplicateEnum.Error, + TestDuplicateEnum.Fail, + TestDuplicateEnum.TestLabel, + TestDuplicateEnum.TestLabel2 + }; + + foreach (var toSerialize in itemsToTest) { //Act var deserialized = engine.Invoke(toSerialize, typeof(TestDuplicateEnum)); //Assert - Assert.Equal(toSerialize, deserialized); + Assert.Equal((int)toSerialize, (int)deserialized); } } diff --git a/tests/AvroConvertTests/TestClasses.cs b/tests/AvroConvertTests/TestClasses.cs index 292bc839..55c34ec6 100644 --- a/tests/AvroConvertTests/TestClasses.cs +++ b/tests/AvroConvertTests/TestClasses.cs @@ -354,9 +354,9 @@ public class ClassWithEnum } public enum TestDuplicateEnum { - Test = 0 + Test = 0, Exam = Test, - Fail = -1, + Fail = 1, Error = Fail, TestLabel = 2, TestLabel2 = 2