// Copyright Epic Games, Inc. All Rights Reserved. using System.ComponentModel; using System.Globalization; using System.Reflection; using System.Text.Json; using System.Text.Json.Serialization; using MongoDB.Bson; using MongoDB.Bson.Serialization; using MongoDB.Bson.Serialization.Serializers; namespace HordeServer.Utilities { /// /// Base class for converting to and from types containing an object id. Useful pattern for reducing boilerplate with strongly typed records. /// /// public abstract class ObjectIdConverter where T : struct { /// /// Converts a type to a /// public abstract ObjectId ToObjectId(T value); /// /// Constructs a type from an object id /// public abstract T FromObjectId(ObjectId id); } /// /// Attribute declaring an object id converter for a particular type /// [AttributeUsage(AttributeTargets.Struct)] public sealed class ObjectIdConverterAttribute : Attribute { /// /// The converter type /// public Type ConverterType { get; } /// /// Constructor /// public ObjectIdConverterAttribute(Type converterType) => ConverterType = converterType; } /// /// Class which serializes types with a to Json /// public sealed class ObjectIdTypeConverter : TypeConverter where TValue : struct where TConverter : ObjectIdConverter, new() { readonly TConverter _converter = new TConverter(); /// public override bool CanConvertFrom(ITypeDescriptorContext? context, Type sourceType) { return sourceType == typeof(string); } /// public override object? ConvertFrom(ITypeDescriptorContext? context, CultureInfo? culture, object value) { if (value is string str) { return _converter.FromObjectId(ObjectId.Parse(str)); } return null; } /// public override bool CanConvertTo(ITypeDescriptorContext? context, Type? destinationType) => destinationType == typeof(string); /// public override object? ConvertTo(ITypeDescriptorContext? context, CultureInfo? culture, object? value, Type destinationType) { if (destinationType == typeof(string)) { return _converter.ToObjectId((TValue)value!).ToString(); } return null; } } /// /// Class which serializes ObjectId types to Json /// public class ObjectIdJsonConverter : JsonConverter { /// public override ObjectId Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { string? str = reader.GetString(); if (str == null) { throw new InvalidDataException("Unable to parse object id"); } if (str.Length == 0) { return ObjectId.Empty; } return ObjectId.Parse(str); } /// public override void Write(Utf8JsonWriter writer, ObjectId objectId, JsonSerializerOptions options) { writer.WriteStringValue(objectId.ToString()); } } /// /// Class which serializes types with a to Json /// public sealed class ObjectIdJsonConverter : JsonConverter where TValue : struct where TConverter : ObjectIdConverter, new() { readonly TConverter _converter = new TConverter(); /// public override TValue Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) => _converter.FromObjectId(ObjectId.Parse(reader.GetString())); /// public override void Write(Utf8JsonWriter writer, TValue value, JsonSerializerOptions options) => writer.WriteStringValue(_converter.ToObjectId(value).ToString()); } /// /// Creates constructors for types with a to Json /// public sealed class ObjectIdJsonConverterFactory : JsonConverterFactory { /// public override bool CanConvert(Type typeToConvert) => typeToConvert.GetCustomAttribute() != null; /// public override JsonConverter? CreateConverter(Type type, JsonSerializerOptions options) { ObjectIdConverterAttribute? attribute = type.GetCustomAttribute(); if (attribute == null) { return null; } return (JsonConverter?)Activator.CreateInstance(typeof(ObjectIdJsonConverter<,>).MakeGenericType(type, attribute.ConverterType)); } } /// /// Class which serializes object id types to BSON /// public sealed class ObjectIdBsonSerializer : SerializerBase where TValue : struct where TConverter : ObjectIdConverter, new() { readonly TConverter _converter = new TConverter(); /// public override TValue Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args) => _converter.FromObjectId(context.Reader.ReadObjectId()); /// public override void Serialize(BsonSerializationContext context, BsonSerializationArgs args, TValue value) => context.Writer.WriteObjectId(_converter.ToObjectId(value)); } /// /// Class which serializes object id types to BSON /// public sealed class ObjectIdBsonSerializationProvider : BsonSerializationProviderBase { /// public override IBsonSerializer? GetSerializer(Type type, IBsonSerializerRegistry serializerRegistry) { ObjectIdConverterAttribute? attribute = type.GetCustomAttribute(); if (attribute == null) { return null; } return (IBsonSerializer?)Activator.CreateInstance(typeof(ObjectIdBsonSerializer<,>).MakeGenericType(type, attribute.ConverterType)); } } }