From 122a69e873f3574e93e0e292a8bf7148c99401b6 Mon Sep 17 00:00:00 2001 From: dnickless Date: Wed, 15 Nov 2017 17:09:46 -0500 Subject: [PATCH] CSHARP-2096: Make EnumRepresentationConvention also affect collections of Enums --- .../EnumRepresentationConvention.cs | 60 ++++++++++------- .../IChildSerializersConfigurable.cs | 46 +++++++++++++ ...ictionaryInterfaceImplementerSerializer.cs | 52 +++++++++++++++ .../EnumRepresentationConventionTests.cs | 64 ++++++++++++++++++- 4 files changed, 199 insertions(+), 23 deletions(-) create mode 100644 src/MongoDB.Bson/Serialization/IChildSerializersConfigurable.cs diff --git a/src/MongoDB.Bson/Serialization/Conventions/EnumRepresentationConvention.cs b/src/MongoDB.Bson/Serialization/Conventions/EnumRepresentationConvention.cs index e7f46a8e566..30e6dc50007 100644 --- a/src/MongoDB.Bson/Serialization/Conventions/EnumRepresentationConvention.cs +++ b/src/MongoDB.Bson/Serialization/Conventions/EnumRepresentationConvention.cs @@ -50,49 +50,65 @@ public EnumRepresentationConvention(BsonType representation) /// The member map. public void Apply(BsonMemberMap memberMap) { - var memberType = memberMap.MemberType; - var memberTypeInfo = memberType.GetTypeInfo(); + var serializer = memberMap.GetSerializer(); + var reconfiguredSerializer = Apply(serializer); + if (reconfiguredSerializer != null) + { + memberMap.SetSerializer(reconfiguredSerializer); + } + } + + private IBsonSerializer Apply(IBsonSerializer serializer) + { +#if NETSTANDARD1_5 || NETSTANDARD1_6 + var type = serializer.ValueType.GetTypeInfo(); +#else + var type = serializer.ValueType; +#endif - if (memberTypeInfo.IsEnum) + if (type.IsEnum) { - var serializer = memberMap.GetSerializer(); var representationConfigurableSerializer = serializer as IRepresentationConfigurable; if (representationConfigurableSerializer != null) { var reconfiguredSerializer = representationConfigurableSerializer.WithRepresentation(_representation); - memberMap.SetSerializer(reconfiguredSerializer); + return reconfiguredSerializer; } - return; } - - if (IsNullableEnum(memberType)) + // order matters + var childSerializersConfigurableSerializer = serializer as IChildSerializersConfigurable; + if (childSerializersConfigurableSerializer != null) + { + var childSerializers = childSerializersConfigurableSerializer.ChildSerializers; + for (int i = 0; i < childSerializers.Count; i++) + { + var reconfiguredChildSerializer = Apply(childSerializers[i]); + if (reconfiguredChildSerializer != null) + { + var reconfiguredSerializer = childSerializersConfigurableSerializer.WithChildSerializer(i, reconfiguredChildSerializer); + return reconfiguredSerializer; + } + } + } + else { - 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 = Apply(childSerializer); + if (reconfiguredChildSerializer != null) { - var reconfiguredChildSerializer = representationConfigurableChildSerializer.WithRepresentation(_representation); var reconfiguredSerializer = childSerializerConfigurableSerializer.WithChildSerializer(reconfiguredChildSerializer); - memberMap.SetSerializer(reconfiguredSerializer); + return reconfiguredSerializer; } } - return; } - } - // private methods - private bool IsNullableEnum(Type type) - { - return - type.GetTypeInfo().IsGenericType && - type.GetGenericTypeDefinition() == typeof(Nullable<>) && - Nullable.GetUnderlyingType(type).GetTypeInfo().IsEnum; + return null; } + // private methods private void EnsureRepresentationIsValidForEnums(BsonType representation) { if ( diff --git a/src/MongoDB.Bson/Serialization/IChildSerializersConfigurable.cs b/src/MongoDB.Bson/Serialization/IChildSerializersConfigurable.cs new file mode 100644 index 00000000000..e07f2dabe6b --- /dev/null +++ b/src/MongoDB.Bson/Serialization/IChildSerializersConfigurable.cs @@ -0,0 +1,46 @@ +/* Copyright 2010-2014 MongoDB Inc. +* +* Licensed 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.Collections.Generic; + +namespace MongoDB.Bson.Serialization +{ + // this interface is public so custom serializers with several child serializers can choose to implement it + // but typically you would choose to implement this interface explicitly + // these methods support forwarding attributes to child serializers and wouldn't normally be public + + /// + /// Represents a serializer that has multiple child serializers that configuration attributes can be forwarded to. + /// Each child serializer gets identified by an index. + /// + public interface IChildSerializersConfigurable + { + /// + /// Gets the child serializer. + /// + /// + /// The child serializer. + /// + IList ChildSerializers { get; } + + /// + /// Returns a serializer that has been reconfigured with the specified child serializer. + /// + /// The index of the child serializer. + /// The child serializer. + /// The reconfigured serializer. + IBsonSerializer WithChildSerializer(int serializerIndex, IBsonSerializer childSerializer); + } +} diff --git a/src/MongoDB.Bson/Serialization/Serializers/DictionaryInterfaceImplementerSerializer.cs b/src/MongoDB.Bson/Serialization/Serializers/DictionaryInterfaceImplementerSerializer.cs index caef5cb6ae6..2ca2c34ad07 100644 --- a/src/MongoDB.Bson/Serialization/Serializers/DictionaryInterfaceImplementerSerializer.cs +++ b/src/MongoDB.Bson/Serialization/Serializers/DictionaryInterfaceImplementerSerializer.cs @@ -28,6 +28,7 @@ namespace MongoDB.Bson.Serialization.Serializers public class DictionaryInterfaceImplementerSerializer : DictionarySerializerBase, IChildSerializerConfigurable, + IChildSerializersConfigurable, IDictionaryRepresentationConfigurable where TDictionary : class, IDictionary, new() { @@ -154,6 +155,31 @@ IBsonSerializer IDictionaryRepresentationConfigurable.WithDictionaryRepresentati { return WithDictionaryRepresentation(dictionaryRepresentation); } + + IList IChildSerializersConfigurable.ChildSerializers + { + get + { + return new[] + { + KeySerializer, + ValueSerializer, + }; + } + } + + IBsonSerializer IChildSerializersConfigurable.WithChildSerializer(int serializerIndex, IBsonSerializer childSerializer) + { + switch (serializerIndex) + { + case 0: + return WithKeySerializer(childSerializer); + case 1: + return WithValueSerializer(childSerializer); + } + + return null; + } } /// @@ -165,6 +191,7 @@ IBsonSerializer IDictionaryRepresentationConfigurable.WithDictionaryRepresentati public class DictionaryInterfaceImplementerSerializer : DictionarySerializerBase, IChildSerializerConfigurable, + IChildSerializersConfigurable, IDictionaryRepresentationConfigurable> where TDictionary : class, IDictionary { @@ -281,6 +308,31 @@ IBsonSerializer IDictionaryRepresentationConfigurable.WithDictionaryRepresentati { return WithDictionaryRepresentation(dictionaryRepresentation); } + + IList IChildSerializersConfigurable.ChildSerializers + { + get + { + return new IBsonSerializer[] + { + KeySerializer, + ValueSerializer, + }; + } + } + + IBsonSerializer IChildSerializersConfigurable.WithChildSerializer(int serializerIndex, IBsonSerializer childSerializer) + { + switch (serializerIndex) + { + case 0: + return WithKeySerializer((IBsonSerializer) childSerializer); + case 1: + return WithValueSerializer((IBsonSerializer) childSerializer); + } + + return null; + } /// protected override ICollection> CreateAccumulator() diff --git a/tests/MongoDB.Bson.Tests/Serialization/Conventions/EnumRepresentationConventionTests.cs b/tests/MongoDB.Bson.Tests/Serialization/Conventions/EnumRepresentationConventionTests.cs index b455cb919d1..a4fb96c4425 100644 --- a/tests/MongoDB.Bson.Tests/Serialization/Conventions/EnumRepresentationConventionTests.cs +++ b/tests/MongoDB.Bson.Tests/Serialization/Conventions/EnumRepresentationConventionTests.cs @@ -14,6 +14,7 @@ */ using System; +using System.Collections.Generic; using System.Linq.Expressions; using FluentAssertions; using MongoDB.Bson.Serialization; @@ -32,6 +33,10 @@ public class C public E E { get; set; } public E? NE { get; set; } public int I { get; set; } + public List L { get; set; } + public List>> L3 { get; set; } + public Dictionary DK { get; set; } + public Dictionary DV { get; set; } } [Theory] @@ -48,7 +53,6 @@ public void Apply_should_configure_serializer_when_member_is_an_enum(BsonType re serializer.Representation.Should().Be(representation); } - [Theory] [InlineData(BsonType.Int32)] [InlineData(BsonType.Int64)] @@ -64,6 +68,64 @@ 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)] + public void Apply_should_configure_serializer_when_member_is_a_dictionary_with_enum_keys(BsonType representation) + { + var subject = new EnumRepresentationConvention(representation); + var memberMap = CreateMemberMap(c => c.DK); + + subject.Apply(memberMap); + + var serializer = (DictionaryInterfaceImplementerSerializer, E, int>)memberMap.GetSerializer(); + ((IRepresentationConfigurable)serializer.KeySerializer).Representation.Should().Be(representation); + } + + [Theory] + [InlineData(BsonType.Int32)] + [InlineData(BsonType.Int64)] + public void Apply_should_configure_serializer_when_member_is_a_dictionary_with_enum_values(BsonType representation) + { + var subject = new EnumRepresentationConvention(representation); + var memberMap = CreateMemberMap(c => c.DV); + + subject.Apply(memberMap); + + var serializer = (DictionaryInterfaceImplementerSerializer, int, E>)memberMap.GetSerializer(); + ((IRepresentationConfigurable)serializer.ValueSerializer).Representation.Should().Be(representation); + } + + [Theory] + [InlineData(BsonType.Int32)] + [InlineData(BsonType.Int64)] + public void Apply_should_configure_serializer_when_member_is_a_collection_of_enums(BsonType representation) + { + var subject = new EnumRepresentationConvention(representation); + var memberMap = CreateMemberMap(c => c.L); + + subject.Apply(memberMap); + + var serializer = (EnumerableInterfaceImplementerSerializer, E>)memberMap.GetSerializer(); + ((IRepresentationConfigurable)serializer.ItemSerializer).Representation.Should().Be(representation); + } + + [Theory] + [InlineData(BsonType.Int32)] + [InlineData(BsonType.Int64)] + public void Apply_should_configure_serializer_when_member_is_a_nested_collection_of_enums(BsonType representation) + { + var subject = new EnumRepresentationConvention(representation); + var memberMap = CreateMemberMap(c => c.L3); + + subject.Apply(memberMap); + + var serializer = (IChildSerializerConfigurable)memberMap.GetSerializer(); + serializer = (IChildSerializerConfigurable)serializer.ChildSerializer; + var childSerializer = (EnumerableInterfaceImplementerSerializer, E>)serializer.ChildSerializer; + ((IRepresentationConfigurable)childSerializer.ItemSerializer).Representation.Should().Be(representation); + } + [Fact] public void Apply_should_do_nothing_when_member_is_not_an_enum() {