From 1e68b4a6fda209f030fbc32c2533b9b052d0ce74 Mon Sep 17 00:00:00 2001 From: RogueException Date: Tue, 15 Aug 2017 06:36:55 -0300 Subject: [PATCH] Added logging for deserialization errors and unknown properties --- src/Discord.Net.Rest/DiscordRestClient.cs | 12 +++- .../Json/Converters/EntityOrIdPropertyConverter.cs | 8 +-- .../Json/Converters/ImagePropertyConverter.cs | 4 +- .../Json/Converters/Int53PropertyConverter.cs | 4 +- .../Json/Converters/OptionalPropertyConverter.cs | 8 +-- .../Json/Converters/UInt53PropertyConverter.cs | 4 +- src/Discord.Net.Serialization/BufferDictionary.cs | 50 ++++++++++++++- .../ConverterCollection.cs | 5 +- .../Discord.Net.Serialization.csproj | 3 + .../Json/Converters/Dictionary.cs | 8 +-- .../Json/Converters/Dynamic.cs | 12 ++-- .../Json/Converters/Enum.cs | 12 ++-- .../Json/Converters/List.cs | 16 ++--- .../Json/Converters/Nullable.cs | 8 +-- .../Json/Converters/Object.cs | 21 ++++-- .../Json/Converters/Primitives.DateTime.cs | 8 +-- .../Json/Converters/Primitives.Float.cs | 12 ++-- .../Json/Converters/Primitives.Other.cs | 8 +-- .../Json/Converters/Primitives.Signed.cs | 16 ++--- .../Json/Converters/Primitives.String.cs | 8 +-- .../Json/Converters/Primitives.Unsigned.cs | 16 ++--- .../Json/Converters/Struct.cs | 4 +- .../Json/IJsonPropertyConverter.cs | 26 -------- .../Json/JsonPropertyConverter.cs | 34 ++++++++++ .../Json/JsonPropertyMap.cs | 18 +++--- .../Json/JsonSerializer.cs | 10 +-- src/Discord.Net.Serialization/ModelMap.cs | 24 ++++--- src/Discord.Net.Serialization/PropertyMap.cs | 10 +-- src/Discord.Net.Serialization/Serializer.cs | 75 ++++++++++++++++++---- src/Discord.Net.WebSocket/Audio/AudioClient.cs | 12 +++- src/Discord.Net.WebSocket/DiscordShardedClient.cs | 12 +++- src/Discord.Net.WebSocket/DiscordSocketClient.cs | 12 +++- 32 files changed, 315 insertions(+), 165 deletions(-) delete mode 100644 src/Discord.Net.Serialization/Json/IJsonPropertyConverter.cs create mode 100644 src/Discord.Net.Serialization/Json/JsonPropertyConverter.cs diff --git a/src/Discord.Net.Rest/DiscordRestClient.cs b/src/Discord.Net.Rest/DiscordRestClient.cs index b6420dbe8..86e0fd8c8 100644 --- a/src/Discord.Net.Rest/DiscordRestClient.cs +++ b/src/Discord.Net.Rest/DiscordRestClient.cs @@ -18,10 +18,16 @@ namespace Discord.Rest public DiscordRestClient(DiscordRestConfig config) : base(config) { _serializer = DiscordRestJsonSerializer.Global.CreateScope(); - _serializer.Error += ex => + if (config.LogLevel >= LogSeverity.Warning) { - _restLogger.WarningAsync("Serializer Error", ex).GetAwaiter().GetResult(); - }; + _serializer.ModelError += (path, ex) + => _restLogger.WarningAsync($"Failed to deserialize {path}", ex).GetAwaiter().GetResult(); + } + if (config.LogLevel >= LogSeverity.Debug) + { + _serializer.UnmappedProperty += path + => _restLogger.DebugAsync($"Unmapped property: {path}"); + } SetApiClient(new API.DiscordRestApiClient(config.RestClientProvider, DiscordConfig.UserAgent, _serializer, config.DefaultRetryMode)); } diff --git a/src/Discord.Net.Rest/Serialization/Json/Converters/EntityOrIdPropertyConverter.cs b/src/Discord.Net.Rest/Serialization/Json/Converters/EntityOrIdPropertyConverter.cs index 836c2ce9d..03d497f01 100644 --- a/src/Discord.Net.Rest/Serialization/Json/Converters/EntityOrIdPropertyConverter.cs +++ b/src/Discord.Net.Rest/Serialization/Json/Converters/EntityOrIdPropertyConverter.cs @@ -12,19 +12,19 @@ namespace Discord.Serialization.Json.Converters _innerConverter = innerConverter; } - public override EntityOrId Read(PropertyMap map, object model, ref JsonReader reader, bool isTopLevel) + public override EntityOrId Read(Serializer serializer, ModelMap modelMap, PropertyMap propMap, object model, ref JsonReader reader, bool isTopLevel) { if (isTopLevel) reader.Read(); if (reader.ValueType == JsonValueType.Number) return new EntityOrId(reader.ParseUInt64()); - return new EntityOrId(_innerConverter.Read(map, model, ref reader, false)); + return new EntityOrId(_innerConverter.Read(serializer, modelMap, propMap, model, ref reader, false)); } - public override void Write(PropertyMap map, object model, ref JsonWriter writer, EntityOrId value, string key) + public override void Write(Serializer serializer, ModelMap modelMap, PropertyMap propMap, object model, ref JsonWriter writer, EntityOrId value, string key) { if (value.Object != null) - _innerConverter.Write(map, model, ref writer, value.Object, key); + _innerConverter.Write(serializer, modelMap, propMap, model, ref writer, value.Object, key); else { if (key != null) diff --git a/src/Discord.Net.Rest/Serialization/Json/Converters/ImagePropertyConverter.cs b/src/Discord.Net.Rest/Serialization/Json/Converters/ImagePropertyConverter.cs index 4852fe4d8..0619119f3 100644 --- a/src/Discord.Net.Rest/Serialization/Json/Converters/ImagePropertyConverter.cs +++ b/src/Discord.Net.Rest/Serialization/Json/Converters/ImagePropertyConverter.cs @@ -5,7 +5,7 @@ namespace Discord.Serialization.Json.Converters { internal class ImagePropertyConverter : JsonPropertyConverter { - public override API.Image Read(PropertyMap map, object model, ref JsonReader reader, bool isTopLevel) + public override API.Image Read(Serializer serializer, ModelMap modelMap, PropertyMap propMap, object model, ref JsonReader reader, bool isTopLevel) { if (isTopLevel) reader.Read(); @@ -13,7 +13,7 @@ namespace Discord.Serialization.Json.Converters throw new SerializationException("Bad input, expected String"); return new API.Image(reader.ParseString()); } - public override void Write(PropertyMap map, object model, ref JsonWriter writer, API.Image value, string key) + public override void Write(Serializer serializer, ModelMap modelMap, PropertyMap propMap, object model, ref JsonWriter writer, API.Image value, string key) { string str; if (value.Stream != null) diff --git a/src/Discord.Net.Rest/Serialization/Json/Converters/Int53PropertyConverter.cs b/src/Discord.Net.Rest/Serialization/Json/Converters/Int53PropertyConverter.cs index f79cc64ce..8d8fb90cf 100644 --- a/src/Discord.Net.Rest/Serialization/Json/Converters/Int53PropertyConverter.cs +++ b/src/Discord.Net.Rest/Serialization/Json/Converters/Int53PropertyConverter.cs @@ -4,7 +4,7 @@ namespace Discord.Serialization.Json.Converters { internal class Int53PropertyConverter : JsonPropertyConverter { - public override long Read(PropertyMap map, object model, ref JsonReader reader, bool isTopLevel) + public override long Read(Serializer serializer, ModelMap modelMap, PropertyMap propMap, object model, ref JsonReader reader, bool isTopLevel) { if (isTopLevel) reader.Read(); @@ -12,7 +12,7 @@ namespace Discord.Serialization.Json.Converters throw new SerializationException("Bad input, expected Number"); return reader.ParseInt64(); } - public override void Write(PropertyMap map, object model, ref JsonWriter writer, long value, string key) + public override void Write(Serializer serializer, ModelMap modelMap, PropertyMap propMap, object model, ref JsonWriter writer, long value, string key) { if (key != null) writer.WriteAttribute(key, value); diff --git a/src/Discord.Net.Rest/Serialization/Json/Converters/OptionalPropertyConverter.cs b/src/Discord.Net.Rest/Serialization/Json/Converters/OptionalPropertyConverter.cs index 30ee1777f..a0c4f8327 100644 --- a/src/Discord.Net.Rest/Serialization/Json/Converters/OptionalPropertyConverter.cs +++ b/src/Discord.Net.Rest/Serialization/Json/Converters/OptionalPropertyConverter.cs @@ -11,13 +11,13 @@ namespace Discord.Serialization.Json.Converters _innerConverter = innerConverter; } - public override Optional Read(PropertyMap map, object model, ref JsonReader reader, bool isTopLevel) - => new Optional(_innerConverter.Read(map, model, ref reader, isTopLevel)); + public override Optional Read(Serializer serializer, ModelMap modelMap, PropertyMap propMap, object model, ref JsonReader reader, bool isTopLevel) + => new Optional(_innerConverter.Read(serializer, modelMap, propMap, model, ref reader, isTopLevel)); - public override void Write(PropertyMap map, object model, ref JsonWriter writer, Optional value, string key) + public override void Write(Serializer serializer, ModelMap modelMap, PropertyMap propMap, object model, ref JsonWriter writer, Optional value, string key) { if (value.IsSpecified) - _innerConverter.Write(map, model, ref writer, value.Value, key); + _innerConverter.Write(serializer, modelMap, propMap, model, ref writer, value.Value, key); } } } diff --git a/src/Discord.Net.Rest/Serialization/Json/Converters/UInt53PropertyConverter.cs b/src/Discord.Net.Rest/Serialization/Json/Converters/UInt53PropertyConverter.cs index b69554bed..f6cdb61c0 100644 --- a/src/Discord.Net.Rest/Serialization/Json/Converters/UInt53PropertyConverter.cs +++ b/src/Discord.Net.Rest/Serialization/Json/Converters/UInt53PropertyConverter.cs @@ -4,7 +4,7 @@ namespace Discord.Serialization.Json.Converters { internal class UInt53PropertyConverter : JsonPropertyConverter { - public override ulong Read(PropertyMap map, object model, ref JsonReader reader, bool isTopLevel) + public override ulong Read(Serializer serializer, ModelMap modelMap, PropertyMap propMap, object model, ref JsonReader reader, bool isTopLevel) { if (isTopLevel) reader.Read(); @@ -12,7 +12,7 @@ namespace Discord.Serialization.Json.Converters throw new SerializationException("Bad input, expected Number"); return reader.ParseUInt64(); } - public override void Write(PropertyMap map, object model, ref JsonWriter writer, ulong value, string key) + public override void Write(Serializer serializer, ModelMap modelMap, PropertyMap propMap, object model, ref JsonWriter writer, ulong value, string key) { if (key != null) writer.WriteAttribute(key, value); diff --git a/src/Discord.Net.Serialization/BufferDictionary.cs b/src/Discord.Net.Serialization/BufferDictionary.cs index 4b7c35fab..9267e0c80 100644 --- a/src/Discord.Net.Serialization/BufferDictionary.cs +++ b/src/Discord.Net.Serialization/BufferDictionary.cs @@ -42,13 +42,24 @@ namespace Discord.Serialization public void Add(ReadOnlyBuffer key, TValue value) { + if (!TryAdd(key, value)) + throw new ArgumentException("Duplicate key"); + } + public void Add(ReadOnlySpan key, TValue value) + { + if (!TryAdd(key, value)) + throw new ArgumentException("Duplicate key"); + } + + public bool TryAdd(ReadOnlyBuffer key, TValue value) + { int hashCode = GetKeyHashCode(key) & 0x7FFFFFFF; int targetBucket = hashCode % _buckets.Length; for (int i = _buckets[targetBucket]; i >= 0; i = _entries[i].next) { if (_entries[i].hashCode == hashCode && KeyEquals(_entries[i].key, key)) - throw new ArgumentException("Duplicate key", nameof(key)); + return false; } int index; if (_freeCount > 0) @@ -73,6 +84,43 @@ namespace Discord.Serialization _entries[index].key = key; _entries[index].value = value; _buckets[targetBucket] = index; + return true; + } + //Duplicate code for perf reasons + public bool TryAdd(ReadOnlySpan key, TValue value) + { + int hashCode = GetKeyHashCode(key) & 0x7FFFFFFF; + int targetBucket = hashCode % _buckets.Length; + + for (int i = _buckets[targetBucket]; i >= 0; i = _entries[i].next) + { + if (_entries[i].hashCode == hashCode && KeyEquals(_entries[i].key, key)) + return false; + } + int index; + if (_freeCount > 0) + { + index = _freeList; + _freeList = _entries[index].next; + _freeCount--; + } + else + { + if (_count == _entries.Length) + { + Resize(); + targetBucket = hashCode % _buckets.Length; + } + index = _count; + _count++; + } + + _entries[index].hashCode = hashCode; + _entries[index].next = _buckets[targetBucket]; + _entries[index].key = new ReadOnlyBuffer(key.ToArray()); + _entries[index].value = value; + _buckets[targetBucket] = index; + return true; } private void Resize() { diff --git a/src/Discord.Net.Serialization/ConverterCollection.cs b/src/Discord.Net.Serialization/ConverterCollection.cs index bac21e5e0..0b28ff778 100644 --- a/src/Discord.Net.Serialization/ConverterCollection.cs +++ b/src/Discord.Net.Serialization/ConverterCollection.cs @@ -94,13 +94,13 @@ namespace Discord.Serialization public void AddSelector(Serializer serializer, string groupKey, Type keyType, object keyValue, Type converterType) { var group = CreateSelectorGroup(keyType, groupKey); - group.AddDynamicConverter(keyValue, BuildConverter(converterType, serializer)); + group.AddDynamicConverter(keyValue, BuildConverter(converterType, serializer )); } public object Get(Serializer serializer, Type type, PropertyInfo propInfo = null, bool throwOnNotFound = true) { //Check parent - object converter = _parent?.Get(serializer, type, propInfo, false); + var converter = _parent?.Get(serializer, type, propInfo, false); if (converter != null) return converter; @@ -190,6 +190,7 @@ namespace Discord.Serialization else throw new SerializationException($"{converterType.Name} has an unsupported constructor"); } + return constructor.Invoke(args); } diff --git a/src/Discord.Net.Serialization/Discord.Net.Serialization.csproj b/src/Discord.Net.Serialization/Discord.Net.Serialization.csproj index 29df2bbda..75803e3a1 100644 --- a/src/Discord.Net.Serialization/Discord.Net.Serialization.csproj +++ b/src/Discord.Net.Serialization/Discord.Net.Serialization.csproj @@ -8,6 +8,9 @@ true + + + diff --git a/src/Discord.Net.Serialization/Json/Converters/Dictionary.cs b/src/Discord.Net.Serialization/Json/Converters/Dictionary.cs index 714f755fa..1851eebda 100644 --- a/src/Discord.Net.Serialization/Json/Converters/Dictionary.cs +++ b/src/Discord.Net.Serialization/Json/Converters/Dictionary.cs @@ -12,7 +12,7 @@ namespace Discord.Serialization.Json.Converters _valueConverter = valueConverter; } - public override Dictionary Read(PropertyMap map, object model, ref JsonReader reader, bool isTopLevel) + public override Dictionary Read(Serializer serializer, ModelMap modelMap, PropertyMap propMap, object model, ref JsonReader reader, bool isTopLevel) { if ((isTopLevel && !reader.Read()) || reader.TokenType != JsonTokenType.StartObject) throw new SerializationException("Bad input, expected StartObject"); @@ -26,20 +26,20 @@ namespace Discord.Serialization.Json.Converters throw new SerializationException("Bad input, expected PropertyName"); string key = reader.Value.ParseString(); - var value = _valueConverter.Read(map, model, ref reader, false); + var value = _valueConverter.Read(serializer, modelMap, propMap, model, ref reader, false); dic.Add(key, value); } return dic; } - public override void Write(PropertyMap map, object model, ref JsonWriter writer, Dictionary value, string key) + public override void Write(Serializer serializer, ModelMap modelMap, PropertyMap propMap, object model, ref JsonWriter writer, Dictionary value, string key) { if (key != null) writer.WriteObjectStart(key); else writer.WriteObjectStart(); foreach (var pair in value) - _valueConverter.Write(map, model, ref writer, pair.Value, pair.Key); + _valueConverter.Write(serializer, modelMap, propMap, model, ref writer, pair.Value, pair.Key); writer.WriteObjectEnd(); } } diff --git a/src/Discord.Net.Serialization/Json/Converters/Dynamic.cs b/src/Discord.Net.Serialization/Json/Converters/Dynamic.cs index 93dfa227b..62b3d15c3 100644 --- a/src/Discord.Net.Serialization/Json/Converters/Dynamic.cs +++ b/src/Discord.Net.Serialization/Json/Converters/Dynamic.cs @@ -5,10 +5,10 @@ namespace Discord.Serialization.Json.Converters //TODO: Only supports cases where the key arrives first public class DynamicPropertyConverter : JsonPropertyConverter { - public override object Read(PropertyMap map, object model, ref JsonReader reader, bool isTopLevel) + public override object Read(Serializer serializer, ModelMap modelMap, PropertyMap propMap, object model, ref JsonReader reader, bool isTopLevel) { - if (map.GetDynamicConverter(model, false) is IJsonPropertyReader converter) - return converter.Read(map, model, ref reader, isTopLevel); + if (propMap.GetDynamicConverter(model, false) is IJsonPropertyReader converter) + return converter.Read(serializer, modelMap, propMap, model, ref reader, isTopLevel); else { JsonReaderUtils.Skip(ref reader); @@ -16,7 +16,7 @@ namespace Discord.Serialization.Json.Converters } } - public override void Write(PropertyMap map, object model, ref JsonWriter writer, object value, string key) + public override void Write(Serializer serializer, ModelMap modelMap, PropertyMap propMap, object model, ref JsonWriter writer, object value, string key) { if (value == null) { @@ -27,8 +27,8 @@ namespace Discord.Serialization.Json.Converters } else { - var converter = (IJsonPropertyWriter)map.GetDynamicConverter(model, true); - converter.Write(map, model, ref writer, value, key); + var converter = (IJsonPropertyWriter)propMap.GetDynamicConverter(model, true); + converter.Write(serializer, modelMap, propMap, model, ref writer, value, key); } } } diff --git a/src/Discord.Net.Serialization/Json/Converters/Enum.cs b/src/Discord.Net.Serialization/Json/Converters/Enum.cs index 84d10f312..c9d6a66a5 100644 --- a/src/Discord.Net.Serialization/Json/Converters/Enum.cs +++ b/src/Discord.Net.Serialization/Json/Converters/Enum.cs @@ -7,7 +7,7 @@ namespace Discord.Serialization.Json.Converters { private static readonly EnumMap _map = EnumMap.For(); - public override T Read(PropertyMap map, object model, ref JsonReader reader, bool isTopLevel) + public override T Read(Serializer serializer, ModelMap modelMap, PropertyMap propMap, object model, ref JsonReader reader, bool isTopLevel) { if (isTopLevel) reader.Read(); @@ -15,7 +15,7 @@ namespace Discord.Serialization.Json.Converters throw new SerializationException("Bad input, expected Number or String"); return _map.FromInt64(reader.Value); } - public override void Write(PropertyMap map, object model, ref JsonWriter writer, T value, string key) + public override void Write(Serializer serializer, ModelMap modelMap, PropertyMap propMap, object model, ref JsonWriter writer, T value, string key) { long intVal = _map.ToInt64(value); if (key != null) @@ -30,7 +30,7 @@ namespace Discord.Serialization.Json.Converters { private static readonly EnumMap _map = EnumMap.For(); - public override T Read(PropertyMap map, object model, ref JsonReader reader, bool isTopLevel) + public override T Read(Serializer serializer, ModelMap modelMap, PropertyMap propMap, object model, ref JsonReader reader, bool isTopLevel) { if (isTopLevel) reader.Read(); @@ -38,7 +38,7 @@ namespace Discord.Serialization.Json.Converters throw new SerializationException("Bad input, expected Number or String"); return _map.FromUInt64(reader.Value); } - public override void Write(PropertyMap map, object model, ref JsonWriter writer, T value, string key) + public override void Write(Serializer serializer, ModelMap modelMap, PropertyMap propMap, object model, ref JsonWriter writer, T value, string key) { ulong uintVal = _map.ToUInt64(value); if (key != null) @@ -53,7 +53,7 @@ namespace Discord.Serialization.Json.Converters { private static readonly EnumMap _map = EnumMap.For(); - public override T Read(PropertyMap map, object model, ref JsonReader reader, bool isTopLevel) + public override T Read(Serializer serializer, ModelMap modelMap, PropertyMap propMap, object model, ref JsonReader reader, bool isTopLevel) { if (isTopLevel) reader.Read(); @@ -61,7 +61,7 @@ namespace Discord.Serialization.Json.Converters throw new SerializationException("Bad input, expected String"); return _map.FromKey(reader.Value); } - public override void Write(PropertyMap map, object model, ref JsonWriter writer, T value, string key) + public override void Write(Serializer serializer, ModelMap modelMap, PropertyMap propMap, object model, ref JsonWriter writer, T value, string key) { string strVal = _map.ToUtf16Key(value); if (key != null) diff --git a/src/Discord.Net.Serialization/Json/Converters/List.cs b/src/Discord.Net.Serialization/Json/Converters/List.cs index b0f2f5073..5333bae07 100644 --- a/src/Discord.Net.Serialization/Json/Converters/List.cs +++ b/src/Discord.Net.Serialization/Json/Converters/List.cs @@ -12,7 +12,7 @@ namespace Discord.Serialization.Json.Converters _innerConverter = innerConverter; } - public override T[] Read(PropertyMap map, object model, ref JsonReader reader, bool isTopLevel) + public override T[] Read(Serializer serializer, ModelMap modelMap, PropertyMap propMap, object model, ref JsonReader reader, bool isTopLevel) { if ((isTopLevel && !reader.Read()) || reader.TokenType != JsonTokenType.StartArray) throw new SerializationException("Bad input, expected StartArray"); @@ -22,19 +22,19 @@ namespace Discord.Serialization.Json.Converters { if (reader.TokenType == JsonTokenType.EndArray) return list.ToArray(); - list.Add(_innerConverter.Read(map, model, ref reader, false)); + list.Add(_innerConverter.Read(serializer, modelMap, propMap, model, ref reader, false)); } throw new SerializationException("Bad input, expected EndArray"); } - public override void Write(PropertyMap map, object model, ref JsonWriter writer, T[] value, string key) + public override void Write(Serializer serializer, ModelMap modelMap, PropertyMap propMap, object model, ref JsonWriter writer, T[] value, string key) { if (key != null) writer.WriteArrayStart(key); else writer.WriteArrayStart(); for (int i = 0; i < value.Length; i++) - _innerConverter.Write(map, model, ref writer, value[i], null); + _innerConverter.Write(serializer, modelMap, propMap, model, ref writer, value[i], null); writer.WriteArrayEnd(); } } @@ -48,7 +48,7 @@ namespace Discord.Serialization.Json.Converters _innerConverter = innerConverter; } - public override List Read(PropertyMap map, object model, ref JsonReader reader, bool isTopLevel) + public override List Read(Serializer serializer, ModelMap modelMap, PropertyMap propMap, object model, ref JsonReader reader, bool isTopLevel) { if ((isTopLevel && !reader.Read()) || reader.TokenType != JsonTokenType.StartArray) throw new SerializationException("Bad input, expected StartArray"); @@ -58,19 +58,19 @@ namespace Discord.Serialization.Json.Converters { if (reader.TokenType == JsonTokenType.EndArray) return list; - list.Add(_innerConverter.Read(map, model, ref reader, false)); + list.Add(_innerConverter.Read(serializer, modelMap, propMap, model, ref reader, false)); } throw new SerializationException("Bad input, expected EndArray"); } - public override void Write(PropertyMap map, object model, ref JsonWriter writer, List value, string key) + public override void Write(Serializer serializer, ModelMap modelMap, PropertyMap propMap, object model, ref JsonWriter writer, List value, string key) { if (key != null) writer.WriteArrayStart(key); else writer.WriteArrayStart(); for (int i = 0; i < value.Count; i++) - _innerConverter.Write(map, model, ref writer, value[i], null); + _innerConverter.Write(serializer, modelMap, propMap, model, ref writer, value[i], null); writer.WriteArrayEnd(); } } diff --git a/src/Discord.Net.Serialization/Json/Converters/Nullable.cs b/src/Discord.Net.Serialization/Json/Converters/Nullable.cs index 5519560fe..b0a44055c 100644 --- a/src/Discord.Net.Serialization/Json/Converters/Nullable.cs +++ b/src/Discord.Net.Serialization/Json/Converters/Nullable.cs @@ -12,19 +12,19 @@ namespace Discord.Serialization.Json.Converters _innerConverter = innerConverter; } - public override T? Read(PropertyMap map, object model, ref JsonReader reader, bool isTopLevel) + public override T? Read(Serializer serializer, ModelMap modelMap, PropertyMap propMap, object model, ref JsonReader reader, bool isTopLevel) { if (isTopLevel) reader.Read(); if (reader.ValueType == JsonValueType.Null) return null; - return _innerConverter.Read(map, model, ref reader, false); + return _innerConverter.Read(serializer, modelMap, propMap, model, ref reader, false); } - public override void Write(PropertyMap map, object model, ref JsonWriter writer, T? value, string key) + public override void Write(Serializer serializer, ModelMap modelMap, PropertyMap propMap, object model, ref JsonWriter writer, T? value, string key) { if (value.HasValue) - _innerConverter.Write(map, model, ref writer, value.Value, key); + _innerConverter.Write(serializer, modelMap, propMap, model, ref writer, value.Value, key); else { if (key != null) diff --git a/src/Discord.Net.Serialization/Json/Converters/Object.cs b/src/Discord.Net.Serialization/Json/Converters/Object.cs index 3df33c36c..832f864af 100644 --- a/src/Discord.Net.Serialization/Json/Converters/Object.cs +++ b/src/Discord.Net.Serialization/Json/Converters/Object.cs @@ -1,18 +1,19 @@ -using System.Text.Json; +using System; +using System.Text.Json; namespace Discord.Serialization.Json.Converters { public class ObjectPropertyConverter : JsonPropertyConverter where T : class, new() { - private readonly ModelMap _map; + private readonly ModelMap _map; public ObjectPropertyConverter(Serializer serializer) { _map = serializer.MapModel(); } - public override T Read(PropertyMap map, object model, ref JsonReader reader, bool isTopLevel) + public override T Read(Serializer serializer, ModelMap modelMap, PropertyMap propMap, object model, ref JsonReader reader, bool isTopLevel) { var subModel = new T(); @@ -30,13 +31,19 @@ namespace Discord.Serialization.Json.Converters throw new SerializationException("Bad input, expected PropertyName"); if (_map.TryGetProperty(reader.Value, out var property)) - (property as IJsonPropertyMap).Read(subModel, ref reader); + { + try { (property as IJsonPropertyMap).Read(serializer, subModel, ref reader); } + catch (Exception ex) { RaiseModelError(serializer, property, ex); } + } else + { + RaiseUnmappedProperty(serializer, _map, reader.Value); JsonReaderUtils.Skip(ref reader); //Unknown property, skip + } } throw new SerializationException("Bad input, expected EndObject"); } - public override void Write(PropertyMap map, object model, ref JsonWriter writer, T value, string key) + public override void Write(Serializer serializer, ModelMap modelMap, PropertyMap propMap, object model, ref JsonWriter writer, T value, string key) { if (value == null) { @@ -51,8 +58,8 @@ namespace Discord.Serialization.Json.Converters writer.WriteObjectStart(key); else writer.WriteObjectStart(); - for (int i = 0; i < _map.Properties.Length; i++) - (_map.Properties[i] as IJsonPropertyMap).Write(value, ref writer); + for (int i = 0; i < _map.Properties.Count; i++) + (_map.Properties[i] as IJsonPropertyMap).Write(serializer, value, ref writer); writer.WriteObjectEnd(); } } diff --git a/src/Discord.Net.Serialization/Json/Converters/Primitives.DateTime.cs b/src/Discord.Net.Serialization/Json/Converters/Primitives.DateTime.cs index 93e2fbd74..44f455036 100644 --- a/src/Discord.Net.Serialization/Json/Converters/Primitives.DateTime.cs +++ b/src/Discord.Net.Serialization/Json/Converters/Primitives.DateTime.cs @@ -5,7 +5,7 @@ namespace Discord.Serialization.Json.Converters { public class DateTimePropertyConverter : JsonPropertyConverter { - public override DateTime Read(PropertyMap map, object model, ref JsonReader reader, bool isTopLevel) + public override DateTime Read(Serializer serializer, ModelMap modelMap, PropertyMap propMap, object model, ref JsonReader reader, bool isTopLevel) { if (isTopLevel) reader.Read(); @@ -13,7 +13,7 @@ namespace Discord.Serialization.Json.Converters throw new SerializationException("Bad input, expected String"); return reader.ParseDateTime(); } - public override void Write(PropertyMap map, object model, ref JsonWriter writer, DateTime value, string key) + public override void Write(Serializer serializer, ModelMap modelMap, PropertyMap propMap, object model, ref JsonWriter writer, DateTime value, string key) { if (key != null) writer.WriteAttribute(key, value); @@ -24,7 +24,7 @@ namespace Discord.Serialization.Json.Converters public class DateTimeOffsetPropertyConverter : JsonPropertyConverter { - public override DateTimeOffset Read(PropertyMap map, object model, ref JsonReader reader, bool isTopLevel) + public override DateTimeOffset Read(Serializer serializer, ModelMap modelMap, PropertyMap propMap, object model, ref JsonReader reader, bool isTopLevel) { if (isTopLevel) reader.Read(); @@ -32,7 +32,7 @@ namespace Discord.Serialization.Json.Converters throw new SerializationException("Bad input, expected String"); return reader.ParseDateTimeOffset(); } - public override void Write(PropertyMap map, object model, ref JsonWriter writer, DateTimeOffset value, string key) + public override void Write(Serializer serializer, ModelMap modelMap, PropertyMap propMap, object model, ref JsonWriter writer, DateTimeOffset value, string key) { if (key != null) writer.WriteAttribute(key, value); diff --git a/src/Discord.Net.Serialization/Json/Converters/Primitives.Float.cs b/src/Discord.Net.Serialization/Json/Converters/Primitives.Float.cs index abb6bb1a0..d32b717e0 100644 --- a/src/Discord.Net.Serialization/Json/Converters/Primitives.Float.cs +++ b/src/Discord.Net.Serialization/Json/Converters/Primitives.Float.cs @@ -4,7 +4,7 @@ namespace Discord.Serialization.Json.Converters { public class SinglePropertyConverter : JsonPropertyConverter { - public override float Read(PropertyMap map, object model, ref JsonReader reader, bool isTopLevel) + public override float Read(Serializer serializer, ModelMap modelMap, PropertyMap propMap, object model, ref JsonReader reader, bool isTopLevel) { if (isTopLevel) reader.Read(); @@ -12,7 +12,7 @@ namespace Discord.Serialization.Json.Converters throw new SerializationException("Bad input, expected Number or String"); return reader.ParseSingle(); } - public override void Write(PropertyMap map, object model, ref JsonWriter writer, float value, string key) + public override void Write(Serializer serializer, ModelMap modelMap, PropertyMap propMap, object model, ref JsonWriter writer, float value, string key) { if (key != null) writer.WriteAttribute(key, value.ToString()); @@ -23,7 +23,7 @@ namespace Discord.Serialization.Json.Converters public class DoublePropertyConverter : JsonPropertyConverter { - public override double Read(PropertyMap map, object model, ref JsonReader reader, bool isTopLevel) + public override double Read(Serializer serializer, ModelMap modelMap, PropertyMap propMap, object model, ref JsonReader reader, bool isTopLevel) { if (isTopLevel) reader.Read(); @@ -31,7 +31,7 @@ namespace Discord.Serialization.Json.Converters throw new SerializationException("Bad input, expected Number or String"); return reader.ParseDouble(); } - public override void Write(PropertyMap map, object model, ref JsonWriter writer, double value, string key) + public override void Write(Serializer serializer, ModelMap modelMap, PropertyMap propMap, object model, ref JsonWriter writer, double value, string key) { if (key != null) writer.WriteAttribute(key, value.ToString()); @@ -42,7 +42,7 @@ namespace Discord.Serialization.Json.Converters internal class DecimalPropertyConverter : JsonPropertyConverter { - public override decimal Read(PropertyMap map, object model, ref JsonReader reader, bool isTopLevel) + public override decimal Read(Serializer serializer, ModelMap modelMap, PropertyMap propMap, object model, ref JsonReader reader, bool isTopLevel) { if (isTopLevel) reader.Read(); @@ -50,7 +50,7 @@ namespace Discord.Serialization.Json.Converters throw new SerializationException("Bad input, expected Number or String"); return reader.ParseDecimal(); } - public override void Write(PropertyMap map, object model, ref JsonWriter writer, decimal value, string key) + public override void Write(Serializer serializer, ModelMap modelMap, PropertyMap propMap, object model, ref JsonWriter writer, decimal value, string key) { if (key != null) writer.WriteAttribute(key, value.ToString()); diff --git a/src/Discord.Net.Serialization/Json/Converters/Primitives.Other.cs b/src/Discord.Net.Serialization/Json/Converters/Primitives.Other.cs index 528c4b539..1b869e56e 100644 --- a/src/Discord.Net.Serialization/Json/Converters/Primitives.Other.cs +++ b/src/Discord.Net.Serialization/Json/Converters/Primitives.Other.cs @@ -5,7 +5,7 @@ namespace Discord.Serialization.Json.Converters { public class BooleanPropertyConverter : JsonPropertyConverter { - public override bool Read(PropertyMap map, object model, ref JsonReader reader, bool isTopLevel) + public override bool Read(Serializer serializer, ModelMap modelMap, PropertyMap propMap, object model, ref JsonReader reader, bool isTopLevel) { if (isTopLevel) reader.Read(); @@ -16,7 +16,7 @@ namespace Discord.Serialization.Json.Converters default: throw new SerializationException("Bad input, expected False or True"); } } - public override void Write(PropertyMap map, object model, ref JsonWriter writer, bool value, string key) + public override void Write(Serializer serializer, ModelMap modelMap, PropertyMap propMap, object model, ref JsonWriter writer, bool value, string key) { if (key != null) writer.WriteAttribute(key, value); @@ -27,7 +27,7 @@ namespace Discord.Serialization.Json.Converters public class GuidPropertyConverter : JsonPropertyConverter { - public override Guid Read(PropertyMap map, object model, ref JsonReader reader, bool isTopLevel) + public override Guid Read(Serializer serializer, ModelMap modelMap, PropertyMap propMap, object model, ref JsonReader reader, bool isTopLevel) { if (isTopLevel) reader.Read(); @@ -35,7 +35,7 @@ namespace Discord.Serialization.Json.Converters throw new SerializationException("Bad input, expected String"); return reader.ParseGuid(); } - public override void Write(PropertyMap map, object model, ref JsonWriter writer, Guid value, string key) + public override void Write(Serializer serializer, ModelMap modelMap, PropertyMap propMap, object model, ref JsonWriter writer, Guid value, string key) { if (key != null) writer.WriteAttribute(key, value.ToString()); diff --git a/src/Discord.Net.Serialization/Json/Converters/Primitives.Signed.cs b/src/Discord.Net.Serialization/Json/Converters/Primitives.Signed.cs index b738efcca..c0878cd1d 100644 --- a/src/Discord.Net.Serialization/Json/Converters/Primitives.Signed.cs +++ b/src/Discord.Net.Serialization/Json/Converters/Primitives.Signed.cs @@ -4,7 +4,7 @@ namespace Discord.Serialization.Json.Converters { public class Int8PropertyConverter : JsonPropertyConverter { - public override sbyte Read(PropertyMap map, object model, ref JsonReader reader, bool isTopLevel) + public override sbyte Read(Serializer serializer, ModelMap modelMap, PropertyMap propMap, object model, ref JsonReader reader, bool isTopLevel) { if (isTopLevel) reader.Read(); @@ -12,7 +12,7 @@ namespace Discord.Serialization.Json.Converters throw new SerializationException("Bad input, expected Number or String"); return reader.ParseInt8(); } - public override void Write(PropertyMap map, object model, ref JsonWriter writer, sbyte value, string key) + public override void Write(Serializer serializer, ModelMap modelMap, PropertyMap propMap, object model, ref JsonWriter writer, sbyte value, string key) { if (key != null) writer.WriteAttribute(key, value); @@ -23,7 +23,7 @@ namespace Discord.Serialization.Json.Converters public class Int16PropertyConverter : JsonPropertyConverter { - public override short Read(PropertyMap map, object model, ref JsonReader reader, bool isTopLevel) + public override short Read(Serializer serializer, ModelMap modelMap, PropertyMap propMap, object model, ref JsonReader reader, bool isTopLevel) { if (isTopLevel) reader.Read(); @@ -31,7 +31,7 @@ namespace Discord.Serialization.Json.Converters throw new SerializationException("Bad input, expected Number or String"); return reader.ParseInt16(); } - public override void Write(PropertyMap map, object model, ref JsonWriter writer, short value, string key) + public override void Write(Serializer serializer, ModelMap modelMap, PropertyMap propMap, object model, ref JsonWriter writer, short value, string key) { if (key != null) writer.WriteAttribute(key, value); @@ -42,7 +42,7 @@ namespace Discord.Serialization.Json.Converters public class Int32PropertyConverter : JsonPropertyConverter { - public override int Read(PropertyMap map, object model, ref JsonReader reader, bool isTopLevel) + public override int Read(Serializer serializer, ModelMap modelMap, PropertyMap propMap, object model, ref JsonReader reader, bool isTopLevel) { if (isTopLevel) reader.Read(); @@ -50,7 +50,7 @@ namespace Discord.Serialization.Json.Converters throw new SerializationException("Bad input, expected Number or String"); return reader.ParseInt32(); } - public override void Write(PropertyMap map, object model, ref JsonWriter writer, int value, string key) + public override void Write(Serializer serializer, ModelMap modelMap, PropertyMap propMap, object model, ref JsonWriter writer, int value, string key) { if (key != null) writer.WriteAttribute(key, value); @@ -61,7 +61,7 @@ namespace Discord.Serialization.Json.Converters public class Int64PropertyConverter : JsonPropertyConverter { - public override long Read(PropertyMap map, object model, ref JsonReader reader, bool isTopLevel) + public override long Read(Serializer serializer, ModelMap modelMap, PropertyMap propMap, object model, ref JsonReader reader, bool isTopLevel) { if (isTopLevel) reader.Read(); @@ -69,7 +69,7 @@ namespace Discord.Serialization.Json.Converters throw new SerializationException("Bad input, expected Number or String"); return reader.ParseInt64(); } - public override void Write(PropertyMap map, object model, ref JsonWriter writer, long value, string key) + public override void Write(Serializer serializer, ModelMap modelMap, PropertyMap propMap, object model, ref JsonWriter writer, long value, string key) { if (key != null) writer.WriteAttribute(key, value.ToString()); diff --git a/src/Discord.Net.Serialization/Json/Converters/Primitives.String.cs b/src/Discord.Net.Serialization/Json/Converters/Primitives.String.cs index 4b9841c95..2bcb3393f 100644 --- a/src/Discord.Net.Serialization/Json/Converters/Primitives.String.cs +++ b/src/Discord.Net.Serialization/Json/Converters/Primitives.String.cs @@ -4,7 +4,7 @@ namespace Discord.Serialization.Json.Converters { /*public class CharPropertyConverter : JsonPropertyConverter { - public override char Read(PropertyMap map, object model, ref JsonReader reader, bool isTopLevel) + public override char Read(Serializer serializer, ModelMap modelMap, PropertyMap propMap, object model, ref JsonReader reader, bool isTopLevel) { if (isTopLevel) reader.Read(); @@ -12,7 +12,7 @@ namespace Discord.Serialization.Json.Converters throw new SerializationException("Bad input, expected String"); return reader.ParseChar(); } - public override void Write(PropertyMap map, object model, ref JsonWriter writer, char value, string key) + public override void Write(Serializer serializer, ModelMap modelMap, PropertyMap propMap, object model, ref JsonWriter writer, char value, string key) { if (key != null) writer.WriteAttribute(key, value); @@ -23,7 +23,7 @@ namespace Discord.Serialization.Json.Converters public class StringPropertyConverter : JsonPropertyConverter { - public override string Read(PropertyMap map, object model, ref JsonReader reader, bool isTopLevel) + public override string Read(Serializer serializer, ModelMap modelMap, PropertyMap propMap, object model, ref JsonReader reader, bool isTopLevel) { if (isTopLevel) reader.Read(); @@ -33,7 +33,7 @@ namespace Discord.Serialization.Json.Converters throw new SerializationException("Bad input, expected String"); return reader.ParseString(); } - public override void Write(PropertyMap map, object model, ref JsonWriter writer, string value, string key) + public override void Write(Serializer serializer, ModelMap modelMap, PropertyMap propMap, object model, ref JsonWriter writer, string value, string key) { if (key != null) writer.WriteAttribute(key, value); diff --git a/src/Discord.Net.Serialization/Json/Converters/Primitives.Unsigned.cs b/src/Discord.Net.Serialization/Json/Converters/Primitives.Unsigned.cs index 52cb597d9..43d6d7220 100644 --- a/src/Discord.Net.Serialization/Json/Converters/Primitives.Unsigned.cs +++ b/src/Discord.Net.Serialization/Json/Converters/Primitives.Unsigned.cs @@ -4,7 +4,7 @@ namespace Discord.Serialization.Json.Converters { public class UInt8PropertyConverter : JsonPropertyConverter { - public override byte Read(PropertyMap map, object model, ref JsonReader reader, bool isTopLevel) + public override byte Read(Serializer serializer, ModelMap modelMap, PropertyMap propMap, object model, ref JsonReader reader, bool isTopLevel) { if (isTopLevel) reader.Read(); @@ -12,7 +12,7 @@ namespace Discord.Serialization.Json.Converters throw new SerializationException("Bad input, expected Number or String"); return reader.ParseUInt8(); } - public override void Write(PropertyMap map, object model, ref JsonWriter writer, byte value, string key) + public override void Write(Serializer serializer, ModelMap modelMap, PropertyMap propMap, object model, ref JsonWriter writer, byte value, string key) { if (key != null) writer.WriteAttribute(key, value); @@ -23,7 +23,7 @@ namespace Discord.Serialization.Json.Converters public class UInt16PropertyConverter : JsonPropertyConverter { - public override ushort Read(PropertyMap map, object model, ref JsonReader reader, bool isTopLevel) + public override ushort Read(Serializer serializer, ModelMap modelMap, PropertyMap propMap, object model, ref JsonReader reader, bool isTopLevel) { if (isTopLevel) reader.Read(); @@ -31,7 +31,7 @@ namespace Discord.Serialization.Json.Converters throw new SerializationException("Bad input, expected Number or String"); return reader.ParseUInt16(); } - public override void Write(PropertyMap map, object model, ref JsonWriter writer, ushort value, string key) + public override void Write(Serializer serializer, ModelMap modelMap, PropertyMap propMap, object model, ref JsonWriter writer, ushort value, string key) { if (key != null) writer.WriteAttribute(key, value); @@ -42,7 +42,7 @@ namespace Discord.Serialization.Json.Converters public class UInt32PropertyConverter : JsonPropertyConverter { - public override uint Read(PropertyMap map, object model, ref JsonReader reader, bool isTopLevel) + public override uint Read(Serializer serializer, ModelMap modelMap, PropertyMap propMap, object model, ref JsonReader reader, bool isTopLevel) { if (isTopLevel) reader.Read(); @@ -50,7 +50,7 @@ namespace Discord.Serialization.Json.Converters throw new SerializationException("Bad input, expected Number or String"); return reader.ParseUInt32(); } - public override void Write(PropertyMap map, object model, ref JsonWriter writer, uint value, string key) + public override void Write(Serializer serializer, ModelMap modelMap, PropertyMap propMap, object model, ref JsonWriter writer, uint value, string key) { if (key != null) writer.WriteAttribute(key, value); @@ -61,7 +61,7 @@ namespace Discord.Serialization.Json.Converters public class UInt64PropertyConverter : JsonPropertyConverter { - public override ulong Read(PropertyMap map, object model, ref JsonReader reader, bool isTopLevel) + public override ulong Read(Serializer serializer, ModelMap modelMap, PropertyMap propMap, object model, ref JsonReader reader, bool isTopLevel) { if (isTopLevel) reader.Read(); @@ -69,7 +69,7 @@ namespace Discord.Serialization.Json.Converters throw new SerializationException("Bad input, expected Number or String"); return reader.ParseUInt64(); } - public override void Write(PropertyMap map, object model, ref JsonWriter writer, ulong value, string key) + public override void Write(Serializer serializer, ModelMap modelMap, PropertyMap propMap, object model, ref JsonWriter writer, ulong value, string key) { if (key != null) writer.WriteAttribute(key, value.ToString()); diff --git a/src/Discord.Net.Serialization/Json/Converters/Struct.cs b/src/Discord.Net.Serialization/Json/Converters/Struct.cs index dcea87247..b28e9f703 100644 --- a/src/Discord.Net.Serialization/Json/Converters/Struct.cs +++ b/src/Discord.Net.Serialization/Json/Converters/Struct.cs @@ -12,7 +12,7 @@ namespace Discord.Serialization.Json.Converters _map = serializer.MapModel(); } - public T Read(PropertyMap map, object model, ref JsonReader reader, bool isTopLevel) + public T Read(ModelMap modelMap, PropertyMap propMap, object model, ref JsonReader reader, bool isTopLevel) { var subModel = new T(); @@ -32,7 +32,7 @@ namespace Discord.Serialization.Json.Converters } throw new SerializationException("Bad input, expected EndObject"); } - public void Write(PropertyMap map, object model, ref JsonWriter writer, T value, string key) + public void Write(ModelMap modelMap, PropertyMap propMap, object model, ref JsonWriter writer, T value, string key) { if (key != null) writer.WriteObjectStart(key); diff --git a/src/Discord.Net.Serialization/Json/IJsonPropertyConverter.cs b/src/Discord.Net.Serialization/Json/IJsonPropertyConverter.cs deleted file mode 100644 index d5b7e6c3e..000000000 --- a/src/Discord.Net.Serialization/Json/IJsonPropertyConverter.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System.Text.Json; - -namespace Discord.Serialization.Json -{ - public abstract class JsonPropertyConverter : IJsonPropertyReader, IJsonPropertyWriter, IJsonPropertyWriter - { - public abstract T Read(PropertyMap map, object model, ref JsonReader reader, bool isTopLevel); - public abstract void Write(PropertyMap map, object model, ref JsonWriter writer, T value, string key); - - void IJsonPropertyWriter.Write(PropertyMap map, object model, ref JsonWriter writer, object value, string key) - => Write(map, model, ref writer, (T)value, key); - } - - public interface IJsonPropertyReader - { - T Read(PropertyMap map, object model, ref JsonReader reader, bool isTopLevel); - } - public interface IJsonPropertyWriter - { - void Write(PropertyMap map, object model, ref JsonWriter writer, T value, string key); - } - public interface IJsonPropertyWriter - { - void Write(PropertyMap map, object model, ref JsonWriter writer, object value, string key); - } -} diff --git a/src/Discord.Net.Serialization/Json/JsonPropertyConverter.cs b/src/Discord.Net.Serialization/Json/JsonPropertyConverter.cs new file mode 100644 index 000000000..81e7546b7 --- /dev/null +++ b/src/Discord.Net.Serialization/Json/JsonPropertyConverter.cs @@ -0,0 +1,34 @@ +using System; +using System.Text.Json; + +namespace Discord.Serialization.Json +{ + public abstract class JsonPropertyConverter : IJsonPropertyReader, IJsonPropertyWriter, IJsonPropertyWriter + { + public abstract T Read(Serializer serializer, ModelMap modelMap, PropertyMap propMap, object model, ref JsonReader reader, bool isTopLevel); + public abstract void Write(Serializer serializer, ModelMap modelMap, PropertyMap propMap, object model, ref JsonWriter writer, T value, string key); + + void IJsonPropertyWriter.Write(Serializer serializer, ModelMap modelMap, PropertyMap propMap, object model, ref JsonWriter writer, object value, string key) + => Write(serializer, modelMap, propMap, model, ref writer, (T)value, key); + + protected void RaiseUnmappedProperty(Serializer serializer, ModelMap modelMap, ReadOnlyBuffer propertyKey) + => serializer.RaiseUnknownProperty(modelMap.Path, propertyKey); + protected void RaiseUnmappedProperty(Serializer serializer, ModelMap modelMap, ReadOnlySpan propertyKey) + => serializer.RaiseUnknownProperty(modelMap.Path, propertyKey); + protected void RaiseModelError(Serializer serializer, PropertyMap propMap, Exception ex) + => serializer.RaiseModelError(propMap.Path, ex); + } + + public interface IJsonPropertyReader + { + T Read(Serializer serializer, ModelMap modelMap, PropertyMap propMap, object model, ref JsonReader reader, bool isTopLevel); + } + public interface IJsonPropertyWriter + { + void Write(Serializer serializer, ModelMap modelMap, PropertyMap propMap, object model, ref JsonWriter writer, T value, string key); + } + public interface IJsonPropertyWriter + { + void Write(Serializer serializer, ModelMap modelMap, PropertyMap propMap, object model, ref JsonWriter writer, object value, string key); + } +} diff --git a/src/Discord.Net.Serialization/Json/JsonPropertyMap.cs b/src/Discord.Net.Serialization/Json/JsonPropertyMap.cs index 5ee0289ff..a9bfd64e1 100644 --- a/src/Discord.Net.Serialization/Json/JsonPropertyMap.cs +++ b/src/Discord.Net.Serialization/Json/JsonPropertyMap.cs @@ -9,36 +9,38 @@ namespace Discord.Serialization.Json string Key { get; } ReadOnlyBuffer Utf8Key { get; } - void Write(TModel model, ref JsonWriter writer); - void Read(TModel model, ref JsonReader reader); + void Write(Serializer serializer, TModel model, ref JsonWriter writer); + void Read(Serializer serializer, TModel model, ref JsonReader reader); } internal class JsonPropertyMap : PropertyMap, IJsonPropertyMap { + private readonly ModelMap _modelMap; private readonly JsonPropertyConverter _converter; private readonly Func _getFunc; private readonly Action _setFunc; - public JsonPropertyMap(Serializer serializer, PropertyInfo propInfo, JsonPropertyConverter converter) - : base(serializer, propInfo) + public JsonPropertyMap(Serializer serializer, ModelMap modelMap, PropertyInfo propInfo, JsonPropertyConverter converter) + : base(serializer, modelMap, propInfo) { + _modelMap = modelMap; _converter = converter; _getFunc = propInfo.GetMethod.CreateDelegate(typeof(Func)) as Func; _setFunc = propInfo.SetMethod.CreateDelegate(typeof(Action)) as Action; } - public void Read(TModel model, ref JsonReader reader) + public void Read(Serializer serializer, TModel model, ref JsonReader reader) { - var value = _converter.Read(this, model, ref reader, true); + var value = _converter.Read(serializer, _modelMap, this, model, ref reader, true); _setFunc(model, value); } - public void Write(TModel model, ref JsonWriter writer) + public void Write(Serializer serializer, TModel model, ref JsonWriter writer) { var value = _getFunc(model); if (value == null && ExcludeNull) return; - _converter.Write(this, model, ref writer, value, Key); + _converter.Write(serializer, _modelMap, this, model, ref writer, value, Key); } } } diff --git a/src/Discord.Net.Serialization/Json/JsonSerializer.cs b/src/Discord.Net.Serialization/Json/JsonSerializer.cs index d195951d6..1245934fe 100644 --- a/src/Discord.Net.Serialization/Json/JsonSerializer.cs +++ b/src/Discord.Net.Serialization/Json/JsonSerializer.cs @@ -18,10 +18,10 @@ namespace Discord.Serialization.Json where TConverter : JsonPropertyConverter => AddConverter(typeof(TValue), typeof(TConverter), condition); - protected override PropertyMap CreatePropertyMap(PropertyInfo propInfo) + protected override PropertyMap CreatePropertyMap(ModelMap modelMap, PropertyInfo propInfo) { var converter = (JsonPropertyConverter)GetConverter(typeof(TValue), propInfo); - return new JsonPropertyMap(this, propInfo, converter); + return new JsonPropertyMap(this, modelMap, propInfo, converter); } public TModel Read(Utf8String str) @@ -32,14 +32,16 @@ namespace Discord.Serialization.Json if (!reader.Read()) return default; var converter = GetConverter(typeof(TModel)) as JsonPropertyConverter; - return converter.Read(null, null, ref reader, false); + //Don't wrap this. We should throw an exception if we cant create the model. + return converter.Read(this, null, null, null, ref reader, false); } public override void Write(ArrayFormatter stream, TModel model) { var writer = new JsonWriter(stream); var converter = GetConverter(typeof(TModel)) as JsonPropertyConverter; - converter.Write(null, null, ref writer, model, null); + //Don't wrap this, always throw exceptions on Write. + converter.Write(this, null, null, null, ref writer, model, null); } } } diff --git a/src/Discord.Net.Serialization/ModelMap.cs b/src/Discord.Net.Serialization/ModelMap.cs index ba8fa405a..48db4626b 100644 --- a/src/Discord.Net.Serialization/ModelMap.cs +++ b/src/Discord.Net.Serialization/ModelMap.cs @@ -1,22 +1,28 @@ using System; using System.Collections.Generic; -using System.Linq; -using System.Reflection; namespace Discord.Serialization { - public class ModelMap - where TModel : class, new() + public class ModelMap { - private BufferDictionary _propDict; + private readonly List _propList; + private readonly BufferDictionary _propDict; + public string Path { get; } public bool HasDynamics { get; } - public PropertyMap[] Properties { get; } + public IReadOnlyList Properties => _propList; - public ModelMap(Serializer serializer, TypeInfo type, List properties) + internal ModelMap(string path) { - Properties = properties.ToArray(); - _propDict = new BufferDictionary(properties.ToDictionary(x => x.Utf8Key)); + Path = path; + _propList = new List(); + _propDict = new BufferDictionary(); + } + + internal void AddProperty(PropertyMap propMap) + { + _propList.Add(propMap); + _propDict.Add(propMap.Utf8Key, propMap); } public bool TryGetProperty(ReadOnlyBuffer key, out PropertyMap value) diff --git a/src/Discord.Net.Serialization/PropertyMap.cs b/src/Discord.Net.Serialization/PropertyMap.cs index 07caedafa..009c1497c 100644 --- a/src/Discord.Net.Serialization/PropertyMap.cs +++ b/src/Discord.Net.Serialization/PropertyMap.cs @@ -11,18 +11,19 @@ namespace Discord.Serialization public string Key { get; } public ReadOnlyBuffer Utf8Key { get; } public string Name { get; } + public string Path { get; } public bool ExcludeNull { get; } - public PropertyMap(Serializer serializer, PropertyInfo propInfo) + internal PropertyMap(Serializer serializer, PropertyInfo propInfo, ModelMap modelMap) { Name = propInfo.Name; + Path = $"{modelMap.Path}.{propInfo.Name}"; var attr = propInfo.GetCustomAttribute(); Key = attr.Key ?? propInfo.Name; Utf8Key = new ReadOnlyBuffer(new Utf8String(Key).Bytes.ToArray()); ExcludeNull = attr.ExcludeNull; - } public abstract object GetDynamicConverter(object model, bool throwOnMissing); @@ -54,11 +55,10 @@ namespace Discord.Serialization => _group?.GetDynamicConverter(_getWrappedSelectorFunc, model); } - private readonly Delegate _getSelectorFunc, _getWrappedSelectorFunc; private readonly IReadOnlyList _selectors; - public PropertyMap(Serializer serializer, PropertyInfo propInfo) - : base(serializer, propInfo) + internal PropertyMap(Serializer serializer, ModelMap modelMap, PropertyInfo propInfo) + : base(serializer, propInfo, modelMap) { _selectors = propInfo.GetCustomAttributes() .Select(x => diff --git a/src/Discord.Net.Serialization/Serializer.cs b/src/Discord.Net.Serialization/Serializer.cs index acac27bb9..d71a32669 100644 --- a/src/Discord.Net.Serialization/Serializer.cs +++ b/src/Discord.Net.Serialization/Serializer.cs @@ -4,18 +4,21 @@ using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Text.Formatting; +using System.Text.Utf8; namespace Discord.Serialization { public abstract class Serializer { - public event Action Error; //TODO: Impl + public event Action ModelError; + public event Action UnmappedProperty; private static readonly MethodInfo _createPropertyMapMethod = typeof(Serializer).GetTypeInfo().GetDeclaredMethod(nameof(CreatePropertyMap)); private readonly ConcurrentDictionary _maps; private readonly ConverterCollection _converters; + private readonly ConcurrentHashSet _unknownProps = new ConcurrentHashSet(); protected Serializer() : this(null) { } @@ -50,16 +53,18 @@ namespace Discord.Serialization public ISelectorGroup GetSelectorGroup(Type keyType, string groupKey) => _converters.GetSelectorGroup(keyType, groupKey); - protected internal ModelMap MapModel() - where TModel : class, new() + protected internal ModelMap MapModel() { return _maps.GetOrAdd(typeof(TModel), _ => { var type = typeof(TModel).GetTypeInfo(); + var searchType = type; var properties = new List(); - while (type != null) + var map = new ModelMap(type.Name); + + while (searchType != null) { - var propInfos = type.DeclaredProperties + var propInfos = searchType.DeclaredProperties .Where(x => x.CanRead && x.CanWrite) .ToArray(); @@ -67,24 +72,68 @@ namespace Discord.Serialization { if (propInfos[i].GetCustomAttribute() != null) { - var propMap = MapProperty(propInfos[i]); - properties.Add(propMap); + var propMap = MapProperty(map, propInfos[i]); + map.AddProperty(propMap); } } - type = type.BaseType?.GetTypeInfo(); + searchType = searchType.BaseType?.GetTypeInfo(); } - return new ModelMap(this, type, properties); - }) as ModelMap; + return map; + }) as ModelMap; } - private PropertyMap MapProperty(PropertyInfo propInfo) - => _createPropertyMapMethod.MakeGenericMethod(typeof(TModel), propInfo.PropertyType).Invoke(this, new object[] { propInfo }) as PropertyMap; - protected abstract PropertyMap CreatePropertyMap(PropertyInfo propInfo); + private PropertyMap MapProperty(ModelMap modelMap, PropertyInfo propInfo) + => _createPropertyMapMethod.MakeGenericMethod(typeof(TModel), propInfo.PropertyType).Invoke(this, new object[] { modelMap, propInfo }) as PropertyMap; + protected abstract PropertyMap CreatePropertyMap(ModelMap modelMap, PropertyInfo propInfo); public TModel Read(ReadOnlyBuffer data) => Read(data.Span); public abstract TModel Read(ReadOnlySpan data); public abstract void Write(ArrayFormatter stream, TModel model); + + internal void RaiseModelError(string path, Exception ex) + { + if (ModelError != null) + ModelError?.Invoke(path, ex); + } + internal void RaiseModelError(ModelMap modelMap, Exception ex) + { + if (ModelError != null) + ModelError?.Invoke(modelMap.Path, ex); + } + internal void RaiseModelError(PropertyMap propMap, Exception ex) + { + if (ModelError != null) + ModelError?.Invoke(propMap.Path, ex); + } + + internal void RaiseUnmappedProperty(string model, string propertyMap) + { + if (UnmappedProperty != null) + { + string path = $"{model}.{propertyMap}"; + if (_unknownProps.TryAdd(path)) + UnmappedProperty?.Invoke(path); + } + } + internal void RaiseUnknownProperty(string model, ReadOnlyBuffer propertyMap) + { + if (UnmappedProperty != null) + { + string path = $"{model}.{new Utf8String(propertyMap.Span).ToString()}"; + if (_unknownProps.TryAdd(path)) + UnmappedProperty?.Invoke(path); + } + } + internal void RaiseUnknownProperty(string model, ReadOnlySpan propertyMap) + { + if (UnmappedProperty != null) + { + string path = $"{model}.{new Utf8String(propertyMap).ToString()}"; + if (_unknownProps.TryAdd(path)) + UnmappedProperty?.Invoke(path); + } + } } } diff --git a/src/Discord.Net.WebSocket/Audio/AudioClient.cs b/src/Discord.Net.WebSocket/Audio/AudioClient.cs index b4eb82ca6..d7687451d 100644 --- a/src/Discord.Net.WebSocket/Audio/AudioClient.cs +++ b/src/Discord.Net.WebSocket/Audio/AudioClient.cs @@ -68,10 +68,16 @@ namespace Discord.Audio _audioLogger = Discord.LogManager.CreateLogger($"Audio #{clientId}"); _serializer = DiscordVoiceJsonSerializer.Global.CreateScope(); - _serializer.Error += ex => + if (Discord.LogManager.Level >= LogSeverity.Warning) { - _audioLogger.WarningAsync("Serializer Error", ex).GetAwaiter().GetResult(); - }; + _serializer.ModelError += (path, ex) + => _audioLogger.WarningAsync($"Failed to deserialize {path}", ex).GetAwaiter().GetResult(); + } + if (Discord.LogManager.Level >= LogSeverity.Debug) + { + _serializer.UnmappedProperty += path + => _audioLogger.DebugAsync($"Unmapped property: {path}"); + } ApiClient = new DiscordVoiceApiClient(guild.Id, Discord.WebSocketProvider, Discord.UdpSocketProvider, _serializer); ApiClient.SentGatewayMessage += async opCode => await _audioLogger.DebugAsync($"Sent {opCode}").ConfigureAwait(false); diff --git a/src/Discord.Net.WebSocket/DiscordShardedClient.cs b/src/Discord.Net.WebSocket/DiscordShardedClient.cs index 2cc77ed2e..7c34318e5 100644 --- a/src/Discord.Net.WebSocket/DiscordShardedClient.cs +++ b/src/Discord.Net.WebSocket/DiscordShardedClient.cs @@ -56,10 +56,16 @@ namespace Discord.WebSocket _connectionGroupLock = new SemaphoreSlim(1, 1); _serializer = DiscordSocketJsonSerializer.Global.CreateScope(); - _serializer.Error += ex => + if (config.LogLevel >= LogSeverity.Warning) { - _restLogger.WarningAsync("Serializer Error", ex).GetAwaiter().GetResult(); - }; + _serializer.ModelError += (path, ex) + => _restLogger.WarningAsync($"Failed to deserialize {path}", ex).GetAwaiter().GetResult(); + } + if (config.LogLevel >= LogSeverity.Debug) + { + _serializer.UnmappedProperty += path + => _restLogger.DebugAsync($"Unmapped property: {path}"); + } SetApiClient(new API.DiscordSocketApiClient(config.RestClientProvider, config.WebSocketProvider, DiscordConfig.UserAgent, _serializer)); diff --git a/src/Discord.Net.WebSocket/DiscordSocketClient.cs b/src/Discord.Net.WebSocket/DiscordSocketClient.cs index f45a1936a..520bde733 100644 --- a/src/Discord.Net.WebSocket/DiscordSocketClient.cs +++ b/src/Discord.Net.WebSocket/DiscordSocketClient.cs @@ -88,10 +88,16 @@ namespace Discord.WebSocket _gatewayLogger = LogManager.CreateLogger(ShardId == 0 && TotalShards == 1 ? "Gateway" : $"Shard #{ShardId}"); _serializer = DiscordSocketJsonSerializer.Global.CreateScope(); - _serializer.Error += ex => + if (config.LogLevel >= LogSeverity.Warning) { - _restLogger.WarningAsync("Serializer Error", ex).GetAwaiter().GetResult(); - }; + _serializer.ModelError += (path, ex) + => _gatewayLogger.WarningAsync($"Failed to deserialize {path}", ex).GetAwaiter().GetResult(); + } + if (config.LogLevel >= LogSeverity.Debug) + { + _serializer.UnmappedProperty += path + => _gatewayLogger.DebugAsync($"Unmapped property: {path}"); + } SetApiClient(new API.DiscordSocketApiClient(config.RestClientProvider, config.WebSocketProvider, DiscordConfig.UserAgent, _serializer, config.GatewayHost, config.DefaultRetryMode));