Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CSHARP-2096: Make EnumRepresentationConvention also affect collection… #1535

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
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
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,7 @@
*/
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is effectively a beahviour breaking change. Should we keep this for next major version?


using System;
using System.Reflection;
using MongoDB.Bson.Serialization.Options;
using MongoDB.Bson.Serialization.Serializers;

namespace MongoDB.Bson.Serialization.Conventions
{
Expand Down Expand Up @@ -50,60 +49,46 @@ public EnumRepresentationConvention(BsonType representation)
/// <param name="memberMap">The member map.</param>
public void Apply(BsonMemberMap memberMap)
{
var memberType = memberMap.MemberType;
var memberTypeInfo = memberType.GetTypeInfo();
var serializer = memberMap.GetSerializer();

if (memberTypeInfo.IsEnum)
if (memberMap.MemberType.IsEnum && serializer is IRepresentationConfigurable representationConfigurableSerializer)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This part here of the code could be removed, as it is a special case of Reconfigure. I decided to keep it as maybe it's slightly faster than Reconfigure and it's possibly a much more common case.

I've removed the "nullable enum" case though, but we can decide it needs to have the same treatment as this.

{
var serializer = memberMap.GetSerializer();
var representationConfigurableSerializer = serializer as IRepresentationConfigurable;
if (representationConfigurableSerializer != null)
{
var reconfiguredSerializer = representationConfigurableSerializer.WithRepresentation(_representation);
memberMap.SetSerializer(reconfiguredSerializer);
}
memberMap.SetSerializer(representationConfigurableSerializer.WithRepresentation(_representation));
return;
}

if (IsNullableEnum(memberType))
var reconfiguredSerializer = Reconfigure(serializer);
if (reconfiguredSerializer is not null)
Copy link
Contributor

Choose a reason for hiding this comment

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

Why do you prefer is not null to != null?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think sometimes it reads a little bit better, but in this case it probably does not make much of a difference. So I can definitely use != if we prefer it

{
var serializer = memberMap.GetSerializer();
var childSerializerConfigurableSerializer = serializer as IChildSerializerConfigurable;
if (childSerializerConfigurableSerializer != null)
{
var childSerializer = childSerializerConfigurableSerializer.ChildSerializer;
var representationConfigurableChildSerializer = childSerializer as IRepresentationConfigurable;
if (representationConfigurableChildSerializer != null)
{
var reconfiguredChildSerializer = representationConfigurableChildSerializer.WithRepresentation(_representation);
var reconfiguredSerializer = childSerializerConfigurableSerializer.WithChildSerializer(reconfiguredChildSerializer);
memberMap.SetSerializer(reconfiguredSerializer);
}
}
return;
memberMap.SetSerializer(reconfiguredSerializer);
}
}

// private methods
private bool IsNullableEnum(Type type)
private IBsonSerializer Reconfigure(IBsonSerializer serializer)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This method here is recursively trying to fin an EnumSerializer. If it finds it, it changes its representation and sets it as the new child serializer.
If no EnumSerializer "leaf" is found, it will return null.

Copy link
Contributor

Choose a reason for hiding this comment

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

Searching for an EnumSerializer is a little stricter than what we used to do, which was search for a serializer for an enum value type that was representation configurable.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Exactly, and this is also why I think it would make sense to do this for next major version, as this is effectively a behaviour breaking change. What do you think?

{
return
type.GetTypeInfo().IsGenericType &&
type.GetGenericTypeDefinition() == typeof(Nullable<>) &&
Nullable.GetUnderlyingType(type).GetTypeInfo().IsEnum;
if (serializer is IChildSerializerConfigurable childSerializerConfigurable)
{
var childSerializer = childSerializerConfigurable.ChildSerializer;
var reconfiguredChildSerializer = Reconfigure(childSerializer);
return reconfiguredChildSerializer is null ? null : childSerializerConfigurable.WithChildSerializer(reconfiguredChildSerializer);
}

if (serializer.ValueType.IsEnum && serializer is IRepresentationConfigurable representationConfigurable)
{
return representationConfigurable.WithRepresentation(_representation);
}

return null;
}

// private methods
private void EnsureRepresentationIsValidForEnums(BsonType representation)
{
if (
representation == 0 ||
representation == BsonType.String ||
representation == BsonType.Int32 ||
representation == BsonType.Int64)
if (representation is 0 or BsonType.String or BsonType.Int32 or BsonType.Int64)
{
return;
}
throw new ArgumentException("Enums can only be represented as String, Int32, Int64 or the type of the enum", "representation");
throw new ArgumentException("Enums can only be represented as String, Int32, Int64 or the type of the enum", nameof(representation));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
*/

using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using FluentAssertions;
using MongoDB.Bson.Serialization;
Expand All @@ -31,7 +32,12 @@ public class C
{
public E E { get; set; }
public E? NE { get; set; }
public E[] ArrayEnum { get; set; }
public E[][] ArrayOfArrayEnum { get; set; }
public Dictionary<string, E> DictionaryEnum { get; set; }
public Dictionary<string, E[]> NestedDictionaryEnum { get; set; }
public int I { get; set; }
public int[] ArrayInt { get; set; }
}

[Theory]
Expand All @@ -52,6 +58,7 @@ public void Apply_should_configure_serializer_when_member_is_an_enum(BsonType re
[Theory]
[InlineData(BsonType.Int32)]
[InlineData(BsonType.Int64)]
[InlineData(BsonType.String)]
public void Apply_should_configure_serializer_when_member_is_a_nullable_enum(BsonType representation)
{
var subject = new EnumRepresentationConvention(representation);
Expand All @@ -64,6 +71,70 @@ public void Apply_should_configure_serializer_when_member_is_a_nullable_enum(Bso
childSerializer.Representation.Should().Be(representation);
}

[Theory]
[InlineData(BsonType.Int32)]
[InlineData(BsonType.Int64)]
[InlineData(BsonType.String)]
public void Apply_should_configure_serializer_when_member_is_an_enum_collection(BsonType representation)
{
var subject = new EnumRepresentationConvention(representation);
var memberMap = CreateMemberMap(c => c.ArrayEnum);

subject.Apply(memberMap);

var serializer = (IChildSerializerConfigurable)memberMap.GetSerializer();
var childSerializer = (EnumSerializer<E>)serializer.ChildSerializer;
childSerializer.Representation.Should().Be(representation);
}

[Theory]
[InlineData(BsonType.Int32)]
[InlineData(BsonType.Int64)]
[InlineData(BsonType.String)]
public void Apply_should_configure_serializer_when_member_is_a_nested_enum_collection(BsonType representation)
{
var subject = new EnumRepresentationConvention(representation);
var memberMap = CreateMemberMap(c => c.ArrayOfArrayEnum);

subject.Apply(memberMap);

var serializer = (IChildSerializerConfigurable)memberMap.GetSerializer();
var childSerializer = (EnumSerializer<E>)((IChildSerializerConfigurable)serializer.ChildSerializer).ChildSerializer;
childSerializer.Representation.Should().Be(representation);
}

[Theory]
[InlineData(BsonType.Int32)]
[InlineData(BsonType.Int64)]
[InlineData(BsonType.String)]
public void Apply_should_configure_serializer_when_member_is_an_enum_dictionary(BsonType representation)
{
var subject = new EnumRepresentationConvention(representation);
var memberMap = CreateMemberMap(c => c.DictionaryEnum);

subject.Apply(memberMap);

var serializer = (IChildSerializerConfigurable)memberMap.GetSerializer();
var childSerializer = (EnumSerializer<E>)serializer.ChildSerializer;
childSerializer.Representation.Should().Be(representation);
}

[Theory]
[InlineData(BsonType.Int32)]
[InlineData(BsonType.Int64)]
[InlineData(BsonType.String)]
public void Apply_should_configure_serializer_when_member_is_a_nested_enum_dictionary(BsonType representation)
{
var subject = new EnumRepresentationConvention(representation);
var memberMap = CreateMemberMap(c => c.NestedDictionaryEnum);

subject.Apply(memberMap);

var serializer = (IChildSerializerConfigurable)memberMap.GetSerializer();
var childSerializer = (EnumSerializer<E>)((IChildSerializerConfigurable)serializer.ChildSerializer).ChildSerializer;
childSerializer.Representation.Should().Be(representation);
}

[Fact]
public void Apply_should_do_nothing_when_member_is_not_an_enum()
{
Expand All @@ -76,6 +147,18 @@ public void Apply_should_do_nothing_when_member_is_not_an_enum()
memberMap.GetSerializer().Should().BeSameAs(serializer);
}

[Fact]
public void Apply_should_do_nothing_when_member_is_not_an_enum_collection()
{
var subject = new EnumRepresentationConvention(BsonType.String);
var memberMap = CreateMemberMap(c => c.ArrayInt);
var serializer = memberMap.GetSerializer();

subject.Apply(memberMap);

memberMap.GetSerializer().Should().BeSameAs(serializer);
}

[Theory]
[InlineData((BsonType)0)]
[InlineData(BsonType.Int32)]
Expand Down