@@ -24,15 +24,6 @@ namespace Discord.API | |||
{ | |||
internal class DiscordRestApiClient : IDisposable | |||
{ | |||
static DiscordRestApiClient() | |||
{ | |||
SerializationFormat.Json.AddConverter<Image, ImagePropertyConverter>(); | |||
SerializationFormat.Json.AddConverter<long, Int53PropertyConverter>((type, prop) => prop?.GetCustomAttribute<Int53Attribute>() != null); | |||
SerializationFormat.Json.AddConverter<ulong, UInt53PropertyConverter>((type, prop) => prop?.GetCustomAttribute<Int53Attribute>() != null); | |||
SerializationFormat.Json.AddGenericConverter(typeof(EntityOrId<>), typeof(EntityOrIdPropertyConverter<>)); | |||
SerializationFormat.Json.AddGenericConverter(typeof(Optional<>), typeof(OptionalPropertyConverter<>)); | |||
} | |||
private static readonly ConcurrentDictionary<string, Func<BucketIds, string>> _bucketIdGenerators = new ConcurrentDictionary<string, Func<BucketIds, string>>(); | |||
public event Func<string, string, double, Task> SentRequest { add { _sentRequestEvent.Add(value); } remove { _sentRequestEvent.Remove(value); } } | |||
@@ -1,4 +1,5 @@ | |||
using Discord.Serialization; | |||
using Discord.Serialization.Json; | |||
using System.Collections.Generic; | |||
using System.Collections.Immutable; | |||
using System.IO; | |||
@@ -16,7 +17,7 @@ namespace Discord.Rest | |||
public DiscordRestClient() : this(new DiscordRestConfig()) { } | |||
public DiscordRestClient(DiscordRestConfig config) : base(config) | |||
{ | |||
_serializer = new Serializer(SerializationFormat.Json); | |||
_serializer = DiscordJsonSerializer.Global.CreateScope(); | |||
_serializer.Error += ex => | |||
{ | |||
_restLogger.WarningAsync("Serializer Error", ex).GetAwaiter().GetResult(); | |||
@@ -1,4 +1,6 @@ | |||
using Discord.Serialization; | |||
using Discord.Rest; | |||
using Discord.Serialization; | |||
using Discord.Serialization.Json; | |||
using System; | |||
#if DEBUG_LIMITS | |||
using System.Diagnostics; | |||
@@ -104,7 +106,7 @@ namespace Discord.Net.Queue | |||
{ | |||
try | |||
{ | |||
var error = Serializer.Json.Read<Error>(response.Data); | |||
var error = DiscordJsonSerializer.Global.Read<Error>(response.Data); | |||
code = error.Code; | |||
reason = error.Message; | |||
} | |||
@@ -0,0 +1,23 @@ | |||
using Discord.Serialization.Json.Converters; | |||
using System; | |||
using System.Reflection; | |||
namespace Discord.Serialization.Json | |||
{ | |||
internal class DiscordJsonSerializer : JsonSerializer | |||
{ | |||
private static readonly Lazy<DiscordJsonSerializer> _singleton = new Lazy<DiscordJsonSerializer>(); | |||
public static new DiscordJsonSerializer Global => _singleton.Value; | |||
public DiscordJsonSerializer() | |||
{ | |||
AddConverter<API.Image, ImagePropertyConverter>(); | |||
AddConverter<long, Int53PropertyConverter>((type, prop) => prop?.GetCustomAttribute<Int53Attribute>() != null); | |||
AddConverter<ulong, UInt53PropertyConverter>((type, prop) => prop?.GetCustomAttribute<Int53Attribute>() != null); | |||
AddGenericConverter(typeof(API.EntityOrId<>), typeof(EntityOrIdPropertyConverter<>)); | |||
AddGenericConverter(typeof(Optional<>), typeof(OptionalPropertyConverter<>)); | |||
} | |||
protected DiscordJsonSerializer(JsonSerializer parent) : base(parent) { } | |||
public new DiscordJsonSerializer CreateScope() => new DiscordJsonSerializer(this); | |||
} | |||
} |
@@ -1,11 +1,12 @@ | |||
using System; | |||
using System.Collections.Concurrent; | |||
using System.Collections.Generic; | |||
using System.Linq; | |||
using System.Reflection; | |||
namespace Discord.Serialization | |||
{ | |||
public class ConverterCollection | |||
internal class ConverterCollection | |||
{ | |||
private class ConverterTypeCollection | |||
{ | |||
@@ -13,27 +14,30 @@ namespace Discord.Serialization | |||
public List<(Func<TypeInfo, PropertyInfo, bool> Condition, Type ConverterType)> Conditionals = new List<(Func<TypeInfo, PropertyInfo, bool>, Type)>(); | |||
} | |||
private static readonly MethodInfo _getConverterMethod | |||
= typeof(ConverterCollection).GetTypeInfo().GetDeclaredMethod(nameof(Get)); | |||
private static readonly TypeInfo _serializerType = typeof(Serializer).GetTypeInfo(); | |||
private readonly Serializer _serializer; | |||
private readonly ConcurrentDictionary<Type, object> _cache = new ConcurrentDictionary<Type, object>(); | |||
private readonly Dictionary<Type, ConverterTypeCollection> _types = new Dictionary<Type, ConverterTypeCollection>(); | |||
private readonly Dictionary<Type, ConverterTypeCollection> _mappedGenericTypes = new Dictionary<Type, ConverterTypeCollection>(); | |||
private readonly ConverterTypeCollection _genericTypes = new ConverterTypeCollection(); | |||
internal ConverterCollection() { } | |||
internal ConverterCollection(Serializer serializer) | |||
{ | |||
_serializer = serializer; | |||
} | |||
public void Add<TType, TConverter>() | |||
public void Add(Type type, Type converterType) | |||
{ | |||
if (!_types.TryGetValue(typeof(TType), out var converters)) | |||
_types.Add(typeof(TType), converters = new ConverterTypeCollection()); | |||
converters.DefaultConverterType = typeof(TConverter); | |||
if (!_types.TryGetValue(type, out var converters)) | |||
_types.Add(type, converters = new ConverterTypeCollection()); | |||
converters.DefaultConverterType = converterType; | |||
} | |||
public void Add<TType, TConverter>(Func<TypeInfo, PropertyInfo, bool> condition) | |||
public void Add(Type type, Type converterType, Func<TypeInfo, PropertyInfo, bool> condition) | |||
{ | |||
if (!_types.TryGetValue(typeof(TType), out var converters)) | |||
_types.Add(typeof(TType), converters = new ConverterTypeCollection()); | |||
converters.Conditionals.Add((condition, typeof(TConverter))); | |||
if (!_types.TryGetValue(type, out var converters)) | |||
_types.Add(type, converters = new ConverterTypeCollection()); | |||
converters.Conditionals.Add((condition, converterType)); | |||
} | |||
public void AddGeneric(Type openConverterType) | |||
@@ -63,12 +67,12 @@ namespace Discord.Serialization | |||
converters.Conditionals.Add((condition, openConverterType)); | |||
} | |||
public object Get<TType>(PropertyInfo propInfo = null) | |||
public object Get(Type type, PropertyInfo propInfo = null) | |||
{ | |||
if (!_cache.TryGetValue(typeof(TType), out var result)) | |||
if (!_cache.TryGetValue(type, out var result)) | |||
{ | |||
object converter = Create(typeof(TType), propInfo); | |||
result = _cache.GetOrAdd(typeof(TType), converter); | |||
object converter = Create(type, propInfo); | |||
result = _cache.GetOrAdd(type, converter); | |||
} | |||
return result; | |||
} | |||
@@ -84,7 +88,7 @@ namespace Discord.Serialization | |||
{ | |||
var innerType = typeInfo.GenericTypeArguments[0]; | |||
converterType = converterType.MakeGenericType(innerType); | |||
object innerConverter = GetInnerConverter(innerType, propInfo); | |||
object innerConverter = Get(innerType, propInfo); | |||
return Activator.CreateInstance(converterType, innerConverter); | |||
} | |||
} | |||
@@ -102,14 +106,30 @@ namespace Discord.Serialization | |||
if (converterType != null) | |||
{ | |||
converterType = converterType.MakeGenericType(type); | |||
return Activator.CreateInstance(converterType); | |||
var converterTypeInfo = converterType.GetTypeInfo(); | |||
var constructors = converterTypeInfo.DeclaredConstructors.ToArray(); | |||
if (constructors.Length == 0) | |||
throw new SerializationException($"{converterType.Name} is missing a constructor"); | |||
if (constructors.Length != 1) | |||
throw new SerializationException($"{converterType.Name} has multiple constructors"); | |||
var constructor = constructors[0]; | |||
var parameters = constructor.GetParameters(); | |||
if (parameters.Length == 0) | |||
return constructor.Invoke(null); | |||
else if (parameters.Length == 1) | |||
{ | |||
var parameterType = parameters[0].ParameterType.GetTypeInfo(); | |||
if (_serializerType.IsAssignableFrom(parameterType)) | |||
return constructor.Invoke(new object[] { _serializer }); | |||
} | |||
throw new SerializationException($"{converterType.Name} has an unsupported constructor"); | |||
} | |||
} | |||
throw new InvalidOperationException($"Unsupported model type: {type.Name}"); | |||
} | |||
private object GetInnerConverter(Type type, PropertyInfo propInfo) | |||
=> _getConverterMethod.MakeGenericMethod(type).Invoke(this, new object[] { propInfo }); | |||
private Type FindConverterType(Type type, Dictionary<Type, ConverterTypeCollection> collection, TypeInfo typeInfo, PropertyInfo propInfo) | |||
{ | |||
@@ -5,8 +5,13 @@ namespace Discord.Serialization.Json.Converters | |||
internal class ObjectPropertyConverter<T> : IJsonPropertyConverter<T> | |||
where T : class, new() | |||
{ | |||
private static readonly ModelMap<T> _map = SerializationFormat.Json.MapModel<T>(); | |||
private readonly ModelMap<T> _map; | |||
public ObjectPropertyConverter(Serializer serializer) | |||
{ | |||
_map = serializer.MapModel<T>(); | |||
} | |||
public T Read(PropertyMap map, ref JsonReader reader, bool isTopLevel) | |||
{ | |||
var model = new T(); | |||
@@ -1,5 +1,4 @@ | |||
using Discord.Serialization.Json.Converters; | |||
using System; | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Reflection; | |||
using System.Text; | |||
@@ -8,9 +7,12 @@ using System.Text.Json; | |||
namespace Discord.Serialization.Json | |||
{ | |||
public class JsonFormat : SerializationFormat | |||
public class JsonSerializer : Serializer | |||
{ | |||
public JsonFormat() | |||
private static readonly Lazy<JsonSerializer> _singleton = new Lazy<JsonSerializer>(); | |||
public static JsonSerializer Global => _singleton.Value; | |||
public JsonSerializer() | |||
{ | |||
AddConverter<sbyte, Converters.Int8PropertyConverter>(); | |||
AddConverter<short, Converters.Int16PropertyConverter>(); | |||
@@ -41,42 +43,34 @@ namespace Discord.Serialization.Json | |||
AddGenericConverter(typeof(Converters.EnumPropertyConverter<>), (type, prop) => type.IsEnum); | |||
AddGenericConverter(typeof(Converters.ObjectPropertyConverter<>), (type, prop) => type.IsClass); | |||
} | |||
protected JsonSerializer(JsonSerializer parent) : base(parent) { } | |||
public JsonSerializer CreateScope() => new JsonSerializer(this); | |||
public void AddConverter<TValue, TConverter>() | |||
where TConverter : class, IJsonPropertyConverter<TValue> | |||
=> _converters.Add<TValue, TConverter>(); | |||
=> AddConverter(typeof(TValue), typeof(TConverter)); | |||
public void AddConverter<TValue, TConverter>(Func<TypeInfo, PropertyInfo, bool> condition) | |||
where TConverter : class, IJsonPropertyConverter<TValue> | |||
=> _converters.Add<TValue, TConverter>(condition); | |||
public void AddGenericConverter(Type converter) | |||
=> _converters.AddGeneric(converter); | |||
public void AddGenericConverter(Type converter, Func<TypeInfo, PropertyInfo, bool> condition) | |||
=> _converters.AddGeneric(converter, condition); | |||
public void AddGenericConverter(Type value, Type converter) | |||
=> _converters.AddGeneric(value, converter); | |||
public void AddGenericConverter(Type value, Type converter, Func<TypeInfo, PropertyInfo, bool> condition) | |||
=> _converters.AddGeneric(value, converter, condition); | |||
=> AddConverter(typeof(TValue), typeof(TConverter), condition); | |||
protected override PropertyMap CreatePropertyMap<TModel, TValue>(PropertyInfo propInfo) | |||
{ | |||
var converter = (IJsonPropertyConverter<TValue>)_converters.Get<TValue>(propInfo); | |||
var converter = (IJsonPropertyConverter<TValue>)GetConverter(typeof(TValue), propInfo); | |||
return new JsonPropertyMap<TModel, TValue>(propInfo, converter); | |||
} | |||
protected internal override TModel Read<TModel>(Serializer serializer, ReadOnlyBuffer<byte> data) | |||
public override TModel Read<TModel>(ReadOnlyBuffer<byte> data) | |||
{ | |||
var reader = new JsonReader(data.Span, SymbolTable.InvariantUtf8); | |||
if (!reader.Read()) | |||
return null; | |||
var converter = _converters.Get<TModel>() as IJsonPropertyConverter<TModel>; | |||
var converter = GetConverter(typeof(TModel)) as IJsonPropertyConverter<TModel>; | |||
return converter.Read(null, ref reader, false); | |||
} | |||
protected internal override void Write<TModel>(Serializer serializer, ArrayFormatter stream, TModel model) | |||
public override void Write<TModel>(ArrayFormatter stream, TModel model) | |||
{ | |||
var writer = new JsonWriter(stream); | |||
var converter = _converters.Get<TModel>() as IJsonPropertyConverter<TModel>; | |||
var converter = GetConverter(typeof(TModel)) as IJsonPropertyConverter<TModel>; | |||
converter.Write(null, ref writer, model, false); | |||
} | |||
} |
@@ -1,55 +0,0 @@ | |||
using Discord.Serialization.Json; | |||
using System; | |||
using System.Collections.Concurrent; | |||
using System.Collections.Generic; | |||
using System.Linq; | |||
using System.Reflection; | |||
using System.Text.Formatting; | |||
namespace Discord.Serialization | |||
{ | |||
public abstract class SerializationFormat | |||
{ | |||
private static readonly MethodInfo _getConverterMethod | |||
= typeof(SerializationFormat).GetTypeInfo().GetDeclaredMethod(nameof(CreatePropertyMap)); | |||
private static readonly Lazy<JsonFormat> _json = new Lazy<JsonFormat>(() => new JsonFormat()); | |||
public static JsonFormat Json => _json.Value; | |||
protected readonly ConcurrentDictionary<Type, object> _maps = new ConcurrentDictionary<Type, object>(); | |||
protected readonly ConverterCollection _converters = new ConverterCollection(); | |||
protected internal ModelMap<TModel> MapModel<TModel>() | |||
where TModel : class, new() | |||
{ | |||
return _maps.GetOrAdd(typeof(TModel), _ => | |||
{ | |||
var type = typeof(TModel).GetTypeInfo(); | |||
var propInfos = type.DeclaredProperties | |||
.Where(x => x.CanRead && x.CanWrite) | |||
.ToArray(); | |||
var properties = new List<PropertyMap>(); | |||
for (int i = 0; i < propInfos.Length; i++) | |||
{ | |||
var propMap = MapProperty<TModel>(propInfos[i]); | |||
properties.Add(propMap); | |||
} | |||
return new ModelMap<TModel>(properties); | |||
}) as ModelMap<TModel>; | |||
} | |||
private PropertyMap MapProperty<TModel>(PropertyInfo propInfo) | |||
where TModel : class, new() | |||
=> _getConverterMethod.MakeGenericMethod(typeof(TModel), propInfo.PropertyType).Invoke(this, new object[] { propInfo }) as PropertyMap; | |||
protected internal abstract TModel Read<TModel>(Serializer serializer, ReadOnlyBuffer<byte> data) | |||
where TModel : class, new(); | |||
protected internal abstract void Write<TModel>(Serializer serializer, ArrayFormatter stream, TModel model) | |||
where TModel : class, new(); | |||
protected abstract PropertyMap CreatePropertyMap<TModel, TValue>(PropertyInfo propInfo) | |||
where TModel : class, new(); | |||
} | |||
} |
@@ -1,27 +1,107 @@ | |||
using System; | |||
using System.Collections.Concurrent; | |||
using System.Collections.Generic; | |||
using System.Linq; | |||
using System.Reflection; | |||
using System.Text.Formatting; | |||
namespace Discord.Serialization | |||
{ | |||
public class Serializer | |||
public abstract class Serializer | |||
{ | |||
private readonly static Lazy<Serializer> _json = new Lazy<Serializer>(() => new Serializer(SerializationFormat.Json)); | |||
public static Serializer Json => _json.Value; | |||
public event Action<Exception> Error; //TODO: Impl | |||
private readonly SerializationFormat _format; | |||
private static readonly MethodInfo _createPropertyMapMethod | |||
= typeof(Serializer).GetTypeInfo().GetDeclaredMethod(nameof(CreatePropertyMap)); | |||
private readonly ConcurrentDictionary<Type, object> _maps; | |||
private readonly ConverterCollection _converters; | |||
public bool IsScoped { get; } | |||
protected Serializer() | |||
{ | |||
_maps = new ConcurrentDictionary<Type, object>(); | |||
_converters = new ConverterCollection(this); | |||
IsScoped = false; | |||
} | |||
protected Serializer(Serializer parent) | |||
{ | |||
_maps = parent._maps; | |||
_converters = parent._converters; | |||
IsScoped = true; | |||
} | |||
protected object GetConverter(Type type, PropertyInfo propInfo = null) | |||
=> _converters.Get(type, propInfo); | |||
public void AddConverter(Type type, Type converter) | |||
{ | |||
CheckScoped(); | |||
_converters.Add(type, converter); | |||
} | |||
public void AddConverter(Type type, Type converter, Func<TypeInfo, PropertyInfo, bool> condition) | |||
{ | |||
CheckScoped(); | |||
_converters.Add(type, converter, condition); | |||
} | |||
public void AddGenericConverter(Type converter) | |||
{ | |||
CheckScoped(); | |||
_converters.AddGeneric(converter); | |||
} | |||
public void AddGenericConverter(Type converter, Func<TypeInfo, PropertyInfo, bool> condition) | |||
{ | |||
CheckScoped(); | |||
_converters.AddGeneric(converter, condition); | |||
} | |||
public void AddGenericConverter(Type value, Type converter) | |||
{ | |||
CheckScoped(); | |||
_converters.AddGeneric(value, converter); | |||
} | |||
public void AddGenericConverter(Type value, Type converter, Func<TypeInfo, PropertyInfo, bool> condition) | |||
{ | |||
CheckScoped(); | |||
_converters.AddGeneric(value, converter, condition); | |||
} | |||
public Serializer(SerializationFormat format) | |||
protected internal ModelMap<TModel> MapModel<TModel>() | |||
where TModel : class, new() | |||
{ | |||
_format = format; | |||
return _maps.GetOrAdd(typeof(TModel), _ => | |||
{ | |||
var type = typeof(TModel).GetTypeInfo(); | |||
var propInfos = type.DeclaredProperties | |||
.Where(x => x.CanRead && x.CanWrite) | |||
.ToArray(); | |||
var properties = new List<PropertyMap>(); | |||
for (int i = 0; i < propInfos.Length; i++) | |||
{ | |||
var propMap = MapProperty<TModel>(propInfos[i]); | |||
properties.Add(propMap); | |||
} | |||
return new ModelMap<TModel>(properties); | |||
}) as ModelMap<TModel>; | |||
} | |||
public T Read<T>(ReadOnlyBuffer<byte> data) | |||
where T : class, new() | |||
=> _format.Read<T>(this, data); | |||
public void Write<T>(ArrayFormatter data, T obj) | |||
where T : class, new() | |||
=> _format.Write(this, data, obj); | |||
private PropertyMap MapProperty<TModel>(PropertyInfo propInfo) | |||
where TModel : class, new() | |||
=> _createPropertyMapMethod.MakeGenericMethod(typeof(TModel), propInfo.PropertyType).Invoke(this, new object[] { propInfo }) as PropertyMap; | |||
protected abstract PropertyMap CreatePropertyMap<TModel, TValue>(PropertyInfo propInfo) | |||
where TModel : class, new(); | |||
public abstract TModel Read<TModel>(ReadOnlyBuffer<byte> data) | |||
where TModel : class, new(); | |||
public abstract void Write<TModel>(ArrayFormatter stream, TModel model) | |||
where TModel : class, new(); | |||
private void CheckScoped() | |||
{ | |||
if (IsScoped) | |||
throw new InvalidOperationException("Scoped serializers are read-only"); | |||
} | |||
} | |||
} |