-
Notifications
You must be signed in to change notification settings - Fork 1.3k
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 collections of Enums #1574
base: main
Are you sure you want to change the base?
Conversation
/// <summary> | ||
/// Gets a boolean indicating if this convention should be also applied to collections of enums. | ||
/// </summary> | ||
public bool ShouldApplyToCollections => _shouldApplyToCollections; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this property should be named ShouldApplyToChildSerializers
because it applies to child serializers in general and not just collections.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We have a discussion going on slack, but I think it would make sense to keep the name we have now, since we are special casing the NullableSerializer
that is the only IChildSerializerConfigurable
that is not a collection serializer, and makes this backwards compatible
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I still think this should be generalized to ShouldApplyToChildSerializers
.
For example, while a dictionary clearly has key and value child serializers it's not clear that a dictionary is a collection (depends on how strictly or loosely you want to define a collection).
Has anyone else weighed in on this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, but we can discuss it during the standup maybe. If we do it though, we need to special case the nullable serializer, otherwise it would be a breaking change.
return null; | ||
} | ||
|
||
// private methods |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Duplicate line
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removed
// private methods | ||
private bool IsNullableEnum(Type type) | ||
{ | ||
return | ||
type.GetTypeInfo().IsGenericType && | ||
type.GetGenericTypeDefinition() == typeof(Nullable<>) && | ||
Nullable.GetUnderlyingType(type).GetTypeInfo().IsEnum; | ||
Nullable.GetUnderlyingType(type)!.GetTypeInfo().IsEnum; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is no longer any need to call GetTypeInfo
here (or on line 110). That was a .NET 2.0 thing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removed
/// <summary> | ||
/// Represents a serializer that has a key and a value serializer that configuration attributes can be forwarded to. | ||
/// </summary> | ||
public interface IKeyAndValueSerializerConfigurable : IBsonDictionarySerializer |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure if this should be deriving from IBsonDictionarySerializer
, or it should be independent from it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think instead of an interface that is hard coded to apply just to dictionaries we should consider the more general solution of adding a new IMultipleChildSerializerConfigurableSerializer
that is like IChildSerializerConfigurable
but applies to serializers that have more than one child serializer:
public interface IMultipleChildSerializerConfigurableSerializer
{
IBsonSerializer[] ChildSerializers { get; }
IBsonSerializer WithChildSerializers(IBsonSerializer[] childSerializers);
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a good idea!
Done
/// <summary> | ||
/// Gets a boolean indicating if this convention should be also applied to collections of enums. | ||
/// </summary> | ||
public bool ShouldApplyToCollections => _shouldApplyToCollections; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I still think this should be generalized to ShouldApplyToChildSerializers
.
For example, while a dictionary clearly has key and value child serializers it's not clear that a dictionary is a collection (depends on how strictly or loosely you want to define a collection).
Has anyone else weighed in on this?
/// <summary> | ||
/// Represents a serializer that has a key and a value serializer that configuration attributes can be forwarded to. | ||
/// </summary> | ||
public interface IKeyAndValueSerializerConfigurable : IBsonDictionarySerializer |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think instead of an interface that is hard coded to apply just to dictionaries we should consider the more general solution of adding a new IMultipleChildSerializerConfigurableSerializer
that is like IChildSerializerConfigurable
but applies to serializers that have more than one child serializer:
public interface IMultipleChildSerializerConfigurableSerializer
{
IBsonSerializer[] ChildSerializers { get; }
IBsonSerializer WithChildSerializers(IBsonSerializer[] childSerializers);
}
/// - or is a <see cref="Nullable"/> serializer; | ||
/// the method traverses and applies the reconfiguration to its child serializers recursively. | ||
internal static IBsonSerializer ReconfigureSerializer<TSerializer>(IBsonSerializer serializer, Func<TSerializer, IBsonSerializer> reconfigure, | ||
Func<IBsonSerializer, bool> testFunction = null, bool shouldApplyToCollections = true) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't like the addition of testFunction
. I think the test can be incorporated into the reconfigure
function.
But maybe let's get through my other questions first.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also not sure shouldApplyToChildSerializer
(or shouldApplyToCollections
) needs to be passed in either. If it is false
this method shouldn't have been called in the first place. If it is true
and this function was called then it always applies to nested children.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would prefer to keep shouldApplyTo...
. If we do so, this method becomes a one-stop method for reconfiguring serializers, including when we want to apply reconfigure
to nullable but not child serializers for example. If we keep it out, then we need to keep the code for nullable serializers out, leading to code duplication.
Regarding testFunction
, if we incorporate it into reconfigure
, then reconfigure
would need to return a null value when it does not need to reconfigure the serializer, maybe making the method a little bit less clear. It don't have a super strong opinion on this though
@rstam I've done some changes:
I think the remaining contentious points are about the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can see my suggested changes prototyped here:
https://github.com/rstam/mongo-csharp-driver/tree/csharp2096-rstam
/// - or is a <see cref="Nullable"/> serializer; | ||
/// the method traverses and applies the reconfiguration to its child serializers recursively. | ||
internal static IBsonSerializer ReconfigureSerializer<TSerializer>(IBsonSerializer serializer, Func<TSerializer, IBsonSerializer> reconfigure, | ||
Func<IBsonSerializer, bool> testFunction = null, bool topLevelOnly = false) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This method has too many knobs. It has 4:
- TSerializer generic type
- reconfigure Func (which might return null)
- testFunction
- topLevelOnly
That's 3 too many.
All we need is the reconfigure
Func
, which centralizes in itself any decisions about whether to reconfigure or not. It either reconfigures the serializer if applicable or returns null.
As far as the rest are concerned:
TSerializer
is redundant because the reconfigure
Func
can decide for itself
testFunction
is redundant because the reconfigure
Func
can decide for itself
topLevelOnly
is redundant because if you only want to configure the top level then don't call this function.
Here's what I think this method should look like:
// Reconfigures a serializer recursively.
// The reconfigure Func should return null if it does not apply to a given serializer
internal static IBsonSerializer ReconfigureSerializerRecursively(
IBsonSerializer serializer,
Func<IBsonSerializer, IBsonSerializer> reconfigure)
{
switch (serializer)
{
// check IMultipleChildSerializersConfigurableSerializer first because some serializer implement both interfaces
case IMultipleChildSerializersConfigurableSerializer multipleChildSerializersConfigurable:
{
var newChildSerializers = new List<IBsonSerializer>();
foreach (var childSerializer in multipleChildSerializersConfigurable.ChildSerializers)
{
var reconfiguredChildSerializer = ReconfigureSerializerRecursively(childSerializer, reconfigure) ?? childSerializer;
newChildSerializers.Add(reconfiguredChildSerializer);
}
return multipleChildSerializersConfigurable.WithChildSerializers(newChildSerializers.ToArray());
}
case IChildSerializerConfigurable childSerializerConfigurable:
{
var childSerializer = childSerializerConfigurable.ChildSerializer;
var reconfiguredChildSerializer = ReconfigureSerializerRecursively(childSerializer, reconfigure) ?? childSerializer;
return reconfiguredChildSerializer != null ? childSerializerConfigurable.WithChildSerializer(reconfiguredChildSerializer) : null;
}
default:
return reconfigure(serializer);
}
}
@@ -61,7 +61,7 @@ public BsonDateOnlyOptionsAttribute(BsonType representation, DateOnlyDocumentFor | |||
/// <returns>A reconfigured serializer.</returns> | |||
protected override IBsonSerializer Apply(IBsonSerializer serializer) | |||
{ | |||
var reconfiguredSerializer = SerializerConfigurator.ReconfigureSerializer(serializer, (DateOnlySerializer s) => s.WithRepresentation(_representation, _documentFormat)); | |||
var reconfiguredSerializer = SerializerConfigurator.ReconfigureSerializer(serializer, (DateOnlySerializer s) => s.WithRepresentation(_representation, _documentFormat), topLevelOnly: false); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
With the other suggestions this method would look like this:
protected override IBsonSerializer Apply(IBsonSerializer serializer)
{
var reconfiguredSerializer = SerializerConfigurator.ReconfigureSerializerRecursively(serializer, Reconfigure);
return reconfiguredSerializer ?? base.Apply(serializer);
IBsonSerializer Reconfigure(IBsonSerializer serializer) =>
(serializer is DateOnlySerializer dateOnlySerializer) ? dateOnlySerializer.WithRepresentation(_representation, _documentFormat) : null;
}
/// <summary> | ||
/// Applies a modification to the member map. | ||
/// </summary> | ||
/// <param name="memberMap">The member map.</param> | ||
public void Apply(BsonMemberMap memberMap) | ||
{ | ||
var memberType = memberMap.MemberType; | ||
var memberTypeInfo = memberType.GetTypeInfo(); | ||
var reconfiguredSerializer = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
With the other suggestions this method would look like this:
public void Apply(BsonMemberMap memberMap)
{
var serializer = memberMap.GetSerializer();
IBsonSerializer reconfiguredSerializer;
if (_topLevelOnly && !serializer.ValueType.IsNullableEnum())
{
reconfiguredSerializer = Reconfigure(serializer);
}
else
{
reconfiguredSerializer = SerializerConfigurator.ReconfigureSerializerRecursively(serializer, Reconfigure);
}
if (reconfiguredSerializer is not null)
{
memberMap.SetSerializer(reconfiguredSerializer);
}
IBsonSerializer Reconfigure(IBsonSerializer serializer) =>
(serializer.ValueType.IsEnum && serializer is IRepresentationConfigurable representationConfigurable) ?
representationConfigurable.WithRepresentation(_representation) : null;
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note that we use _topLevel
locally here to decide whether we want to reconfigure recursively or not (with an exception for nullable enums for backward compatibility).
There is no need to pass the topLevel
argument to ReconfigureSerializerRecursively
.
/// <value> | ||
/// The children serializers. | ||
/// </value> | ||
IBsonSerializer[] ChildrenSerializers { get; } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This property should be called ChildSerializers
.
It is ungrammatical to say ChildrenSerializers
.
Child
is an adjective here and should not be plural.
Compare "child serializer" to "child seat".
The plural is "child seats", not "children seats".
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, I was unsure about that, I'll correct it!
/// <summary> | ||
/// Represents a serializer that has multiple children serializers that configuration attributes can be forwarded to. | ||
/// </summary> | ||
public interface IMultipleChildrenSerializerConfigurableSerializer |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Class name (and file name) should be:
MultipleChildSerializersConfigurableSerializer
"Child" to "Children"
See below.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes
/// </summary> | ||
/// <param name="childrenSerializers">The children serializers.</param> | ||
/// <returns>The reconfigured serializer.</returns> | ||
IBsonSerializer WithChildrenSerializers(IBsonSerializer[] childrenSerializers); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IBsonSerializer WithChildSerializers(IBsonSerializer[] childSerializers);
No description provided.