diff --git a/src/Context/DefaultFrameworkAllowedTypes.cs b/src/Context/DefaultFrameworkAllowedTypes.cs deleted file mode 100644 index 1f7e081..0000000 --- a/src/Context/DefaultFrameworkAllowedTypes.cs +++ /dev/null @@ -1,106 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using MongoDB.Bson; - -namespace MongoDB.Extensions.Context; - -internal static class DefaultFrameworkAllowedTypes -{ - public static Func AllowedTypes => __allowedTypes; - - private readonly static Func __allowedTypes = AllowedTypesImplementation; - - private readonly static HashSet __allowedNonGenericTypesSet = new HashSet - { - typeof(bool), - typeof(byte), - typeof(char), - typeof(System.Collections.ArrayList), - typeof(System.Collections.BitArray), - typeof(System.Collections.Hashtable), - typeof(System.Collections.Queue), - typeof(System.Collections.SortedList), - typeof(System.Collections.Specialized.ListDictionary), - typeof(System.Collections.Specialized.OrderedDictionary), - typeof(System.Collections.Stack), - typeof(System.DateTime), - typeof(System.DateTimeOffset), - typeof(decimal), - typeof(double), - typeof(System.Dynamic.ExpandoObject), - typeof(System.Guid), - typeof(short), - typeof(int), - typeof(long), - typeof(System.Net.DnsEndPoint), - typeof(System.Net.EndPoint), - typeof(System.Net.IPAddress), - typeof(System.Net.IPEndPoint), - typeof(System.Net.IPHostEntry), - typeof(object), - typeof(sbyte), - typeof(float), - typeof(string), - typeof(System.Text.RegularExpressions.Regex), - typeof(System.TimeSpan), - typeof(ushort), - typeof(uint), - typeof(ulong), - typeof(System.Uri), - typeof(System.Version) - }; - - private readonly static HashSet __allowedGenericTypesSet = new HashSet - { - typeof(System.Collections.Generic.Dictionary<,>), - typeof(System.Collections.Generic.HashSet<>), - typeof(System.Collections.Generic.KeyValuePair<,>), - typeof(System.Collections.Generic.LinkedList<>), - typeof(System.Collections.Generic.List<>), - typeof(System.Collections.Generic.Queue<>), - typeof(System.Collections.Generic.SortedDictionary<,>), - typeof(System.Collections.Generic.SortedList<,>), - typeof(System.Collections.Generic.SortedSet<>), - typeof(System.Collections.Generic.Stack<>), - typeof(System.Collections.ObjectModel.Collection<>), - typeof(System.Collections.ObjectModel.KeyedCollection<,>), - typeof(System.Collections.ObjectModel.ObservableCollection<>), - typeof(System.Collections.ObjectModel.ReadOnlyCollection<>), - typeof(System.Collections.ObjectModel.ReadOnlyDictionary<,>), - typeof(System.Collections.ObjectModel.ReadOnlyObservableCollection<>), - typeof(System.Nullable<>), - typeof(System.Tuple<>), - typeof(System.Tuple<,>), - typeof(System.Tuple<,,>), - typeof(System.Tuple<,,,>), - typeof(System.Tuple<,,,,>), - typeof(System.Tuple<,,,,,>), - typeof(System.Tuple<,,,,,,>), - typeof(System.Tuple<,,,,,,,>), - typeof(System.ValueTuple<,,,,,,,>), - typeof(System.ValueTuple<>), - typeof(System.ValueTuple<,>), - typeof(System.ValueTuple<,,>), - typeof(System.ValueTuple<,,,>), - typeof(System.ValueTuple<,,,,>), - typeof(System.ValueTuple<,,,,,>), - typeof(System.ValueTuple<,,,,,,>), - typeof(System.ValueTuple<,,,,,,,>) - }; - - private static bool AllowedTypesImplementation(Type type) - { - return type.IsConstructedGenericType ? IsAllowedGenericType(type) : IsAllowedType(type); - - static bool IsAllowedType(Type type) => - typeof(BsonValue).IsAssignableFrom(type) || - __allowedNonGenericTypesSet.Contains(type) || - type.IsArray && AllowedTypesImplementation(type.GetElementType()) || - type.IsEnum; - - static bool IsAllowedGenericType(Type type) => - (__allowedGenericTypesSet.Contains(type.GetGenericTypeDefinition()) || type.IsAnonymousType()) && - type.GetGenericArguments().All(__allowedTypes); - } -} diff --git a/src/Context/TypeObjectSerializer.cs b/src/Context/Internal/TypeObjectSerializer.cs similarity index 60% rename from src/Context/TypeObjectSerializer.cs rename to src/Context/Internal/TypeObjectSerializer.cs index b36d700..97e2b47 100644 --- a/src/Context/TypeObjectSerializer.cs +++ b/src/Context/Internal/TypeObjectSerializer.cs @@ -1,35 +1,38 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; -using MongoDB.Bson.Serialization; using MongoDB.Extensions.Context.Extensions; +#nullable enable namespace MongoDB.Extensions.Context.Internal; -public class TypeObjectSerializer : IBsonSerializer +internal class TypeObjectSerializer : ObjectSerializer { - private static readonly ConcurrentDictionary _allowedTypes = new(); - private static readonly HashSet _allowedTypesByNamespaces = new(); - private static readonly HashSet _allowedTypesByDependencies = new(); + private static readonly ConcurrentDictionary _allowedTypes = new (); + private static readonly HashSet _allowedTypesByNamespaces = new (); + private static readonly HashSet _allowedTypesByDependencies = new (); - public Type ValueType => typeof(object); + public TypeObjectSerializer() : base(type => IsTypeAllowed(type)) + { + } - public static IReadOnlyDictionary AllowedTypes => _allowedTypes; + public static IReadOnlyDictionary AllowedTypes + => _allowedTypes; - public static IReadOnlyCollection AllowedTypesByNamespaces => _allowedTypesByNamespaces; + public static IReadOnlyCollection AllowedTypesByNamespaces + => _allowedTypesByNamespaces; - public static IReadOnlyCollection AllowedTypesByDependencies => _allowedTypesByDependencies; + public static IReadOnlyCollection AllowedTypesByDependencies + => _allowedTypesByDependencies; public static bool IsTypeAllowed(Type type) { return DefaultAllowedTypes(type) || - _allowedTypes.ContainsKey(type) || - IsInAllowedNamespaces(type) || - IsInAllowedDependencyTypes(type); + _allowedTypes.ContainsKey(type) || + IsInAllowedNamespaces(type) || + IsInAllowedDependencyTypes(type); } - public static Func DefaultAllowedTypes => DefaultFrameworkAllowedTypes.AllowedTypes; - public static void AddAllowedType() { _allowedTypes.TryAdd(typeof(T), true); @@ -85,12 +88,13 @@ private static bool IsAllowedNameSpacePart(Type type) { foreach (string allowedNamespace in _allowedTypesByNamespaces) { - if (string.IsNullOrEmpty(type.Namespace)) + if(string.IsNullOrEmpty(type.Namespace)) { return false; } - if (type.Namespace.StartsWith(allowedNamespace, StringComparison.OrdinalIgnoreCase)) + if (type.Namespace.StartsWith(allowedNamespace, + StringComparison.OrdinalIgnoreCase)) { return true; } @@ -101,30 +105,11 @@ private static bool IsAllowedNameSpacePart(Type type) private static bool IsInAllowedDependencyTypes(Type type) { - bool isInDependencyTypes = _allowedTypesByDependencies.Contains(type.GetRootNamespace()); + bool isInDependencyTypes = _allowedTypesByDependencies + .Contains(type.GetRootNamespace()); _allowedTypes.TryAdd(type, isInDependencyTypes); return isInDependencyTypes; } - - public void Serialize(BsonSerializationContext context, BsonSerializationArgs args, object value) - { - var serializer = BsonSerializer.LookupSerializer(value.GetType()); - serializer.Serialize(context, args, value); - } - - public object Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args) - { - var bsonType = context.Reader.GetCurrentBsonType(); - if (bsonType == MongoDB.Bson.BsonType.Null) - { - context.Reader.ReadNull(); - return null!; - } - - var type = args.NominalType; - var serializer = BsonSerializer.LookupSerializer(type); - return serializer.Deserialize(context, args); - } } diff --git a/src/Context/MongoDriverInternals/ObjectSerializer.cs b/src/Context/MongoDriverInternals/ObjectSerializer.cs new file mode 100644 index 0000000..2df8878 --- /dev/null +++ b/src/Context/MongoDriverInternals/ObjectSerializer.cs @@ -0,0 +1,586 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using MongoDB.Bson; +using MongoDB.Bson.IO; +using MongoDB.Bson.Serialization; +using MongoDB.Bson.Serialization.Conventions; +using MongoDB.Bson.Serialization.Serializers; +using IHasDiscriminatorConvention = MongoDB.Bson.Serialization.IHasDiscriminatorConvention; + +namespace MongoDB.Extensions.Context; + +/* Copyright 2010-present 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. + */ + +/// +/// Represents a serializer for objects. +/// +internal class ObjectSerializer : ClassSerializerBase, IHasDiscriminatorConvention +{ + #region static + + // private static fields + private static readonly Func __allAllowedTypes = t => true; + private static readonly ObjectSerializer __instance = new ObjectSerializer(); + private static readonly Func __noAllowedTypes = t => false; + + // public static properties + /// + /// An allowed types function that returns true for all types. + /// + public static Func AllAllowedTypes => __allAllowedTypes; + + /// + /// An allowed types function that returns true for framework types known to be safe. + /// + public static Func DefaultAllowedTypes => DefaultFrameworkAllowedTypes.AllowedTypes; + + /// + /// Gets the standard instance. + /// + public static ObjectSerializer Instance => __instance; + + /// + /// An allowed types function that returns false for all types. + /// + public static Func NoAllowedTypes => __noAllowedTypes; + + #endregion + + // private fields + private readonly Func _allowedDeserializationTypes; + private readonly Func _allowedSerializationTypes; + private readonly IDiscriminatorConvention _discriminatorConvention; + private readonly GuidRepresentation _guidRepresentation; + private readonly GuidSerializer _guidSerializer; + + // constructors + /// + /// Initializes a new instance of the class. + /// + public ObjectSerializer() + : this(BsonSerializer.LookupDiscriminatorConvention(typeof(object))) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The discriminator convention. + /// discriminatorConvention + public ObjectSerializer(IDiscriminatorConvention discriminatorConvention) + : this(discriminatorConvention, GuidRepresentation.Unspecified) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The discriminator convention. + /// The Guid representation. + public ObjectSerializer(IDiscriminatorConvention discriminatorConvention, GuidRepresentation guidRepresentation) + : this(discriminatorConvention, guidRepresentation, DefaultFrameworkAllowedTypes.AllowedTypes) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// A delegate that determines what types are allowed. + public ObjectSerializer(Func allowedTypes) + : this(BsonSerializer.LookupDiscriminatorConvention(typeof(object)), allowedTypes) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The discriminator convention. + /// A delegate that determines what types are allowed. + public ObjectSerializer(IDiscriminatorConvention discriminatorConvention, Func allowedTypes) + : this(discriminatorConvention, GuidRepresentation.Unspecified, allowedTypes) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The discriminator convention. + /// The Guid representation. + /// A delegate that determines what types are allowed. + public ObjectSerializer(IDiscriminatorConvention discriminatorConvention, GuidRepresentation guidRepresentation, + Func allowedTypes) + : this(discriminatorConvention, guidRepresentation, + allowedTypes ?? throw new ArgumentNullException(nameof(allowedTypes)), allowedTypes) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The discriminator convention. + /// The Guid representation. + /// A delegate that determines what types are allowed to be deserialized. + /// A delegate that determines what types are allowed to be serialized. + public ObjectSerializer( + IDiscriminatorConvention discriminatorConvention, + GuidRepresentation guidRepresentation, + Func allowedDeserializationTypes, + Func allowedSerializationTypes) + { + _discriminatorConvention = + discriminatorConvention ?? throw new ArgumentNullException(nameof(discriminatorConvention)); + _guidRepresentation = guidRepresentation; + _guidSerializer = new GuidSerializer(_guidRepresentation); + _allowedDeserializationTypes = allowedDeserializationTypes ?? + throw new ArgumentNullException(nameof(allowedDeserializationTypes)); + _allowedSerializationTypes = allowedSerializationTypes ?? + throw new ArgumentNullException(nameof(allowedSerializationTypes)); + } + + // public properties + /// + /// Gets the AllowedDeserializationTypes filter; + /// + public Func AllowedDeserializationTypes => _allowedDeserializationTypes; + + /// + /// Gets the AllowedSerializationTypes filter; + /// + public Func AllowedSerializationTypes => _allowedSerializationTypes; + + /// + /// Gets the discriminator convention. + /// + public IDiscriminatorConvention DiscriminatorConvention => _discriminatorConvention; + + /// + /// Gets the GuidRepresentation. + /// + public GuidRepresentation GuidRepresentation => _guidRepresentation; + + // public methods + /// + /// Deserializes a value. + /// + /// The deserialization context. + /// The deserialization args. + /// A deserialized value. + public override object Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args) + { + var bsonReader = context.Reader; + + var bsonType = bsonReader.GetCurrentBsonType(); + switch (bsonType) + { + case BsonType.Array: + if (context.DynamicArraySerializer != null) + { + return context.DynamicArraySerializer.Deserialize(context); + } + + goto default; + + case BsonType.Binary: + var binaryDataBookmark = bsonReader.GetBookmark(); + var binaryData = bsonReader.ReadBinaryData(); + var subType = binaryData.SubType; + if (subType == BsonBinarySubType.UuidStandard || subType == BsonBinarySubType.UuidLegacy) + { + bsonReader.ReturnToBookmark(binaryDataBookmark); + return _guidSerializer.Deserialize(context); + } + + goto default; + + case BsonType.Boolean: + return bsonReader.ReadBoolean(); + + case BsonType.DateTime: + var millisecondsSinceEpoch = bsonReader.ReadDateTime(); + var bsonDateTime = new BsonDateTime(millisecondsSinceEpoch); + return bsonDateTime.ToUniversalTime(); + + case BsonType.Decimal128: + return bsonReader.ReadDecimal128(); + + case BsonType.Document: + return DeserializeDiscriminatedValue(context, args); + + case BsonType.Double: + return bsonReader.ReadDouble(); + + case BsonType.Int32: + return bsonReader.ReadInt32(); + + case BsonType.Int64: + return bsonReader.ReadInt64(); + + case BsonType.Null: + bsonReader.ReadNull(); + return null; + + case BsonType.ObjectId: + return bsonReader.ReadObjectId(); + + case BsonType.String: + return bsonReader.ReadString(); + + default: + var message = string.Format("ObjectSerializer does not support BSON type '{0}'.", bsonType); + throw new FormatException(message); + } + } + + /// + public override bool Equals(object obj) + { + if (object.ReferenceEquals(obj, null)) { return false; } + + if (object.ReferenceEquals(this, obj)) { return true; } + + return + base.Equals(obj) && + obj is ObjectSerializer other && + object.Equals(_allowedDeserializationTypes, other._allowedDeserializationTypes) && + object.Equals(_allowedSerializationTypes, other._allowedSerializationTypes) && + object.Equals(_discriminatorConvention, other._discriminatorConvention) && + _guidRepresentation.Equals(other._guidRepresentation); + } + + /// + public override int GetHashCode() => 0; + + /// + /// Serializes a value. + /// + /// The serialization context. + /// The serialization args. + /// The object. + public override void Serialize(BsonSerializationContext context, BsonSerializationArgs args, object value) + { + var bsonWriter = context.Writer; + + if (value == null) + { + bsonWriter.WriteNull(); + } + else + { + var actualType = value.GetType(); + if (actualType == typeof(object)) + { + bsonWriter.WriteStartDocument(); + bsonWriter.WriteEndDocument(); + } + else + { + // certain types can be written directly as BSON value + // if we're not at the top level document, or if we're using the JsonWriter + if (bsonWriter.State == BsonWriterState.Value || bsonWriter is JsonWriter) + { + switch (Type.GetTypeCode(actualType)) + { + case TypeCode.Boolean: + bsonWriter.WriteBoolean((bool)value); + return; + + case TypeCode.DateTime: + // TODO: is this right? will lose precision after round trip + var bsonDateTime = new BsonDateTime(BsonUtils.ToUniversalTime((DateTime)value)); + bsonWriter.WriteDateTime(bsonDateTime.MillisecondsSinceEpoch); + return; + + case TypeCode.Double: + bsonWriter.WriteDouble((double)value); + return; + + case TypeCode.Int16: + // TODO: is this right? will change type to Int32 after round trip + bsonWriter.WriteInt32((short)value); + return; + + case TypeCode.Int32: + bsonWriter.WriteInt32((int)value); + return; + + case TypeCode.Int64: + bsonWriter.WriteInt64((long)value); + return; + + case TypeCode.Object: + if (actualType == typeof(Decimal128)) + { + var decimal128 = (Decimal128)value; + bsonWriter.WriteDecimal128(decimal128); + return; + } + + if (actualType == typeof(Guid)) + { + var guid = (Guid)value; + _guidSerializer.Serialize(context, args, guid); + return; + } + + if (actualType == typeof(ObjectId)) + { + bsonWriter.WriteObjectId((ObjectId)value); + return; + } + + break; + + case TypeCode.String: + bsonWriter.WriteString((string)value); + return; + } + } + + SerializeDiscriminatedValue(context, args, value, actualType); + } + } + } + + /// + /// Returns a new ObjectSerializer configured the same but with the specified discriminator convention. + /// + /// The discriminator convention. + /// An ObjectSerializer with the specified discriminator convention. + public ObjectSerializer WithDiscriminatorConvention(IDiscriminatorConvention discriminatorConvention) + { + return new ObjectSerializer(discriminatorConvention, _guidRepresentation, _allowedDeserializationTypes, + _allowedSerializationTypes); + } + + // private methods + private object DeserializeDiscriminatedValue(BsonDeserializationContext context, BsonDeserializationArgs args) + { + var bsonReader = context.Reader; + + var actualType = _discriminatorConvention.GetActualType(bsonReader, typeof(object)); + if (!_allowedDeserializationTypes(actualType)) + { + throw new BsonSerializationException( + $"Type {actualType.FullName} is not configured as a type that is allowed to be deserialized for this instance of ObjectSerializer."); + } + + if (actualType == typeof(object)) + { + var type = bsonReader.GetCurrentBsonType(); + switch (type) + { + case BsonType.Document: + if (context.DynamicDocumentSerializer != null) + { + return context.DynamicDocumentSerializer.Deserialize(context, args); + } + + break; + } + + bsonReader.ReadStartDocument(); + bsonReader.ReadEndDocument(); + return new object(); + } + else + { + var serializer = BsonSerializer.LookupSerializer(actualType); + var polymorphicSerializer = serializer as IBsonPolymorphicSerializer; + if (polymorphicSerializer != null && polymorphicSerializer.IsDiscriminatorCompatibleWithObjectSerializer) + { + return serializer.Deserialize(context, args); + } + else + { + object value = null; + var wasValuePresent = false; + + bsonReader.ReadStartDocument(); + while (bsonReader.ReadBsonType() != 0) + { + var name = bsonReader.ReadName(); + if (name == _discriminatorConvention.ElementName) + { + bsonReader.SkipValue(); + } + else if (name == "_v") + { + value = serializer.Deserialize(context); + wasValuePresent = true; + } + else + { + var message = string.Format("Unexpected element name: '{0}'.", name); + throw new FormatException(message); + } + } + + bsonReader.ReadEndDocument(); + + if (!wasValuePresent) + { + throw new FormatException("_v element missing."); + } + + return value; + } + } + } + + private void SerializeDiscriminatedValue(BsonSerializationContext context, BsonSerializationArgs args, object value, + Type actualType) + { + if (!_allowedSerializationTypes(actualType)) + { + throw new BsonSerializationException( + $"Type {actualType.FullName} is not configured as a type that is allowed to be serialized for this instance of ObjectSerializer."); + } + + var serializer = BsonSerializer.LookupSerializer(actualType); + + var polymorphicSerializer = serializer as IBsonPolymorphicSerializer; + if (polymorphicSerializer != null && polymorphicSerializer.IsDiscriminatorCompatibleWithObjectSerializer) + { + serializer.Serialize(context, args, value); + } + else + { + if (context.IsDynamicType != null && context.IsDynamicType(value.GetType())) + { + args.NominalType = actualType; + serializer.Serialize(context, args, value); + } + else + { + var bsonWriter = context.Writer; + var discriminator = _discriminatorConvention.GetDiscriminator(typeof(object), actualType); + + bsonWriter.WriteStartDocument(); + if (discriminator != null) + { + bsonWriter.WriteName(_discriminatorConvention.ElementName); + BsonValueSerializer.Instance.Serialize(context, discriminator); + } + + bsonWriter.WriteName("_v"); + serializer.Serialize(context, value); + bsonWriter.WriteEndDocument(); + } + } + } + + // nested types + private static class DefaultFrameworkAllowedTypes + { + private readonly static Func __allowedTypes = AllowedTypesImplementation; + + private readonly static HashSet __allowedNonGenericTypesSet = new HashSet + { + typeof(bool), + typeof(byte), + typeof(char), + typeof(System.Collections.ArrayList), + typeof(System.Collections.BitArray), + typeof(System.Collections.Hashtable), + typeof(System.Collections.Queue), + typeof(System.Collections.SortedList), + typeof(System.Collections.Specialized.ListDictionary), + typeof(System.Collections.Specialized.OrderedDictionary), + typeof(System.Collections.Stack), + typeof(System.DateTime), + typeof(System.DateTimeOffset), + typeof(decimal), + typeof(double), + typeof(System.Dynamic.ExpandoObject), + typeof(System.Guid), + typeof(short), + typeof(int), + typeof(long), + typeof(System.Net.DnsEndPoint), + typeof(System.Net.EndPoint), + typeof(System.Net.IPAddress), + typeof(System.Net.IPEndPoint), + typeof(System.Net.IPHostEntry), + typeof(object), + typeof(sbyte), + typeof(float), + typeof(string), + typeof(System.Text.RegularExpressions.Regex), + typeof(System.TimeSpan), + typeof(ushort), + typeof(uint), + typeof(ulong), + typeof(System.Uri), + typeof(System.Version) + }; + + private readonly static HashSet __allowedGenericTypesSet = new HashSet + { + typeof(System.Collections.Generic.Dictionary<,>), + typeof(System.Collections.Generic.HashSet<>), + typeof(System.Collections.Generic.KeyValuePair<,>), + typeof(System.Collections.Generic.LinkedList<>), + typeof(System.Collections.Generic.List<>), + typeof(System.Collections.Generic.Queue<>), + typeof(System.Collections.Generic.SortedDictionary<,>), + typeof(System.Collections.Generic.SortedList<,>), + typeof(System.Collections.Generic.SortedSet<>), + typeof(System.Collections.Generic.Stack<>), + typeof(System.Collections.ObjectModel.Collection<>), + typeof(System.Collections.ObjectModel.KeyedCollection<,>), + typeof(System.Collections.ObjectModel.ObservableCollection<>), + typeof(System.Collections.ObjectModel.ReadOnlyCollection<>), + typeof(System.Collections.ObjectModel.ReadOnlyDictionary<,>), + typeof(System.Collections.ObjectModel.ReadOnlyObservableCollection<>), + typeof(System.Nullable<>), + typeof(System.Tuple<>), + typeof(System.Tuple<,>), + typeof(System.Tuple<,,>), + typeof(System.Tuple<,,,>), + typeof(System.Tuple<,,,,>), + typeof(System.Tuple<,,,,,>), + typeof(System.Tuple<,,,,,,>), + typeof(System.Tuple<,,,,,,,>), + typeof(System.ValueTuple<,,,,,,,>), + typeof(System.ValueTuple<>), + typeof(System.ValueTuple<,>), + typeof(System.ValueTuple<,,>), + typeof(System.ValueTuple<,,,>), + typeof(System.ValueTuple<,,,,>), + typeof(System.ValueTuple<,,,,,>), + typeof(System.ValueTuple<,,,,,,>), + typeof(System.ValueTuple<,,,,,,,>) + }; + + public static Func AllowedTypes => __allowedTypes; + + private static bool AllowedTypesImplementation(Type type) + { + return type.IsConstructedGenericType ? IsAllowedGenericType(type) : IsAllowedType(type); + + static bool IsAllowedType(Type type) => + typeof(BsonValue).IsAssignableFrom(type) || + __allowedNonGenericTypesSet.Contains(type) || + type.IsArray && AllowedTypesImplementation(type.GetElementType()) || + type.IsEnum; + + static bool IsAllowedGenericType(Type type) => + (__allowedGenericTypesSet.Contains(type.GetGenericTypeDefinition()) || type.IsAnonymousType()) && + type.GetGenericArguments().All(__allowedTypes); + } + } +} diff --git a/src/Context/TypeExtensions.cs b/src/Context/MongoDriverInternals/TypeExtensions.cs similarity index 100% rename from src/Context/TypeExtensions.cs rename to src/Context/MongoDriverInternals/TypeExtensions.cs