| @@ -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"); | |||
| } | |||
| } | |||
| } | |||