// 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));
}
}
}