@@ -1,6 +1,6 @@ | |||||
Microsoft Visual Studio Solution File, Format Version 12.00 | Microsoft Visual Studio Solution File, Format Version 12.00 | ||||
# Visual Studio 15 | # Visual Studio 15 | ||||
VisualStudioVersion = 15.0.26228.4 | |||||
VisualStudioVersion = 15.0.26711.1 | |||||
MinimumVisualStudioVersion = 10.0.40219.1 | MinimumVisualStudioVersion = 10.0.40219.1 | ||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Discord.Net.Core", "src\Discord.Net.Core\Discord.Net.Core.csproj", "{91E9E7BD-75C9-4E98-84AA-2C271922E5C2}" | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Discord.Net.Core", "src\Discord.Net.Core\Discord.Net.Core.csproj", "{91E9E7BD-75C9-4E98-84AA-2C271922E5C2}" | ||||
EndProject | EndProject | ||||
@@ -14,8 +14,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Discord.Net.Commands", "src | |||||
EndProject | EndProject | ||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Discord.Net.WebSocket", "src\Discord.Net.WebSocket\Discord.Net.WebSocket.csproj", "{688FD1D8-7F01-4539-B2E9-F473C5D699C7}" | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Discord.Net.WebSocket", "src\Discord.Net.WebSocket\Discord.Net.WebSocket.csproj", "{688FD1D8-7F01-4539-B2E9-F473C5D699C7}" | ||||
EndProject | EndProject | ||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Providers", "Providers", "{B0657AAE-DCC5-4FBF-8E5D-1FB578CF3012}" | |||||
EndProject | |||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Discord.Net.Providers.WS4Net", "src\Discord.Net.Providers.WS4Net\Discord.Net.Providers.WS4Net.csproj", "{6BDEEC08-417B-459F-9CA3-FF8BAB18CAC7}" | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Discord.Net.Providers.WS4Net", "src\Discord.Net.Providers.WS4Net\Discord.Net.Providers.WS4Net.csproj", "{6BDEEC08-417B-459F-9CA3-FF8BAB18CAC7}" | ||||
EndProject | EndProject | ||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Extensions", "Extensions", "{CC3D4B1C-9DE0-448B-8AE7-F3F1F3EC5C3A}" | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Extensions", "Extensions", "{CC3D4B1C-9DE0-448B-8AE7-F3F1F3EC5C3A}" | ||||
@@ -24,6 +22,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Discord.Net.Tests", "test\D | |||||
EndProject | EndProject | ||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Discord.Net.Webhook", "src\Discord.Net.Webhook\Discord.Net.Webhook.csproj", "{9AFAB80E-D2D3-4EDB-B58C-BACA78D1EA30}" | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Discord.Net.Webhook", "src\Discord.Net.Webhook\Discord.Net.Webhook.csproj", "{9AFAB80E-D2D3-4EDB-B58C-BACA78D1EA30}" | ||||
EndProject | EndProject | ||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tools", "Tools", "{1876A445-1C70-4F84-912B-4D3CEA21E0C3}" | |||||
EndProject | |||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Discord.Net.Serialization", "src\Discord.Net.Serialization\Discord.Net.Serialization.csproj", "{AA3B67BE-767E-4230-9810-F7948B6AE689}" | |||||
EndProject | |||||
Global | Global | ||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution | GlobalSection(SolutionConfigurationPlatforms) = preSolution | ||||
Debug|Any CPU = Debug|Any CPU | Debug|Any CPU = Debug|Any CPU | ||||
@@ -130,6 +132,18 @@ Global | |||||
{9AFAB80E-D2D3-4EDB-B58C-BACA78D1EA30}.Release|x64.Build.0 = Release|Any CPU | {9AFAB80E-D2D3-4EDB-B58C-BACA78D1EA30}.Release|x64.Build.0 = Release|Any CPU | ||||
{9AFAB80E-D2D3-4EDB-B58C-BACA78D1EA30}.Release|x86.ActiveCfg = Release|Any CPU | {9AFAB80E-D2D3-4EDB-B58C-BACA78D1EA30}.Release|x86.ActiveCfg = Release|Any CPU | ||||
{9AFAB80E-D2D3-4EDB-B58C-BACA78D1EA30}.Release|x86.Build.0 = Release|Any CPU | {9AFAB80E-D2D3-4EDB-B58C-BACA78D1EA30}.Release|x86.Build.0 = Release|Any CPU | ||||
{AA3B67BE-767E-4230-9810-F7948B6AE689}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | |||||
{AA3B67BE-767E-4230-9810-F7948B6AE689}.Debug|Any CPU.Build.0 = Debug|Any CPU | |||||
{AA3B67BE-767E-4230-9810-F7948B6AE689}.Debug|x64.ActiveCfg = Debug|Any CPU | |||||
{AA3B67BE-767E-4230-9810-F7948B6AE689}.Debug|x64.Build.0 = Debug|Any CPU | |||||
{AA3B67BE-767E-4230-9810-F7948B6AE689}.Debug|x86.ActiveCfg = Debug|Any CPU | |||||
{AA3B67BE-767E-4230-9810-F7948B6AE689}.Debug|x86.Build.0 = Debug|Any CPU | |||||
{AA3B67BE-767E-4230-9810-F7948B6AE689}.Release|Any CPU.ActiveCfg = Release|Any CPU | |||||
{AA3B67BE-767E-4230-9810-F7948B6AE689}.Release|Any CPU.Build.0 = Release|Any CPU | |||||
{AA3B67BE-767E-4230-9810-F7948B6AE689}.Release|x64.ActiveCfg = Release|Any CPU | |||||
{AA3B67BE-767E-4230-9810-F7948B6AE689}.Release|x64.Build.0 = Release|Any CPU | |||||
{AA3B67BE-767E-4230-9810-F7948B6AE689}.Release|x86.ActiveCfg = Release|Any CPU | |||||
{AA3B67BE-767E-4230-9810-F7948B6AE689}.Release|x86.Build.0 = Release|Any CPU | |||||
EndGlobalSection | EndGlobalSection | ||||
GlobalSection(SolutionProperties) = preSolution | GlobalSection(SolutionProperties) = preSolution | ||||
HideSolutionNode = FALSE | HideSolutionNode = FALSE | ||||
@@ -139,7 +153,11 @@ Global | |||||
{5688A353-121E-40A1-8BFA-B17B91FB48FB} = {288C363D-A636-4EAE-9AC1-4698B641B26E} | {5688A353-121E-40A1-8BFA-B17B91FB48FB} = {288C363D-A636-4EAE-9AC1-4698B641B26E} | ||||
{078DD7E6-943D-4D09-AFC2-D2BA58B76C9C} = {CC3D4B1C-9DE0-448B-8AE7-F3F1F3EC5C3A} | {078DD7E6-943D-4D09-AFC2-D2BA58B76C9C} = {CC3D4B1C-9DE0-448B-8AE7-F3F1F3EC5C3A} | ||||
{688FD1D8-7F01-4539-B2E9-F473C5D699C7} = {288C363D-A636-4EAE-9AC1-4698B641B26E} | {688FD1D8-7F01-4539-B2E9-F473C5D699C7} = {288C363D-A636-4EAE-9AC1-4698B641B26E} | ||||
{6BDEEC08-417B-459F-9CA3-FF8BAB18CAC7} = {B0657AAE-DCC5-4FBF-8E5D-1FB578CF3012} | |||||
{6BDEEC08-417B-459F-9CA3-FF8BAB18CAC7} = {1876A445-1C70-4F84-912B-4D3CEA21E0C3} | |||||
{9AFAB80E-D2D3-4EDB-B58C-BACA78D1EA30} = {CC3D4B1C-9DE0-448B-8AE7-F3F1F3EC5C3A} | {9AFAB80E-D2D3-4EDB-B58C-BACA78D1EA30} = {CC3D4B1C-9DE0-448B-8AE7-F3F1F3EC5C3A} | ||||
{AA3B67BE-767E-4230-9810-F7948B6AE689} = {1876A445-1C70-4F84-912B-4D3CEA21E0C3} | |||||
EndGlobalSection | |||||
GlobalSection(ExtensibilityGlobals) = postSolution | |||||
SolutionGuid = {9828C525-49C7-48F4-A9E7-94E223052DA2} | |||||
EndGlobalSection | EndGlobalSection | ||||
EndGlobal | EndGlobal |
@@ -7,6 +7,11 @@ | |||||
<TargetFrameworks>net45;netstandard1.1;netstandard1.3</TargetFrameworks> | <TargetFrameworks>net45;netstandard1.1;netstandard1.3</TargetFrameworks> | ||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks> | <AllowUnsafeBlocks>true</AllowUnsafeBlocks> | ||||
</PropertyGroup> | </PropertyGroup> | ||||
<ItemGroup> | |||||
<Compile Remove="Serialization\**" /> | |||||
<EmbeddedResource Remove="Serialization\**" /> | |||||
<None Remove="Serialization\**" /> | |||||
</ItemGroup> | |||||
<ItemGroup> | <ItemGroup> | ||||
<PackageReference Include="System.Buffers" Version="4.4.0-preview2-25405-01" /> | <PackageReference Include="System.Buffers" Version="4.4.0-preview2-25405-01" /> | ||||
<PackageReference Include="System.Collections.Immutable" Version="1.3.1" /> | <PackageReference Include="System.Collections.Immutable" Version="1.3.1" /> | ||||
@@ -15,6 +20,6 @@ | |||||
<PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="4.4.0-preview2-25405-01" /> | <PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="4.4.0-preview2-25405-01" /> | ||||
</ItemGroup> | </ItemGroup> | ||||
<ItemGroup> | <ItemGroup> | ||||
<Folder Include="Serialization\" /> | |||||
<ProjectReference Include="..\Discord.Net.Serialization\Discord.Net.Serialization.csproj" /> | |||||
</ItemGroup> | </ItemGroup> | ||||
</Project> | </Project> |
@@ -9,11 +9,15 @@ | |||||
</PropertyGroup> | </PropertyGroup> | ||||
<ItemGroup> | <ItemGroup> | ||||
<Compile Remove="Serialization\System.Buffers\**" /> | <Compile Remove="Serialization\System.Buffers\**" /> | ||||
<Compile Remove="_corefxlab\**" /> | |||||
<EmbeddedResource Remove="Serialization\System.Buffers\**" /> | <EmbeddedResource Remove="Serialization\System.Buffers\**" /> | ||||
<EmbeddedResource Remove="_corefxlab\**" /> | |||||
<None Remove="Serialization\System.Buffers\**" /> | <None Remove="Serialization\System.Buffers\**" /> | ||||
<None Remove="_corefxlab\**" /> | |||||
</ItemGroup> | </ItemGroup> | ||||
<ItemGroup> | <ItemGroup> | ||||
<ProjectReference Include="..\Discord.Net.Core\Discord.Net.Core.csproj" /> | <ProjectReference Include="..\Discord.Net.Core\Discord.Net.Core.csproj" /> | ||||
<ProjectReference Include="..\Discord.Net.Serialization\Discord.Net.Serialization.csproj" /> | |||||
</ItemGroup> | </ItemGroup> | ||||
<ItemGroup Condition=" '$(TargetFramework)' != 'net45' "> | <ItemGroup Condition=" '$(TargetFramework)' != 'net45' "> | ||||
<PackageReference Include="System.Net.Http" Version="4.3.2" /> <!-- https://github.com/dotnet/corefx/issues/19535 --> | <PackageReference Include="System.Net.Http" Version="4.3.2" /> <!-- https://github.com/dotnet/corefx/issues/19535 --> | ||||
@@ -21,7 +25,4 @@ | |||||
<ItemGroup Condition=" '$(TargetFramework)' == 'net45' "> | <ItemGroup Condition=" '$(TargetFramework)' == 'net45' "> | ||||
<Reference Include="System.Net.Http" /> | <Reference Include="System.Net.Http" /> | ||||
</ItemGroup> | </ItemGroup> | ||||
<ItemGroup> | |||||
<Folder Include="_corefxlab\" /> | |||||
</ItemGroup> | |||||
</Project> | </Project> |
@@ -4,6 +4,8 @@ using Discord.Net; | |||||
using Discord.Net.Queue; | using Discord.Net.Queue; | ||||
using Discord.Net.Rest; | using Discord.Net.Rest; | ||||
using Discord.Serialization; | using Discord.Serialization; | ||||
using Discord.Serialization.Json; | |||||
using Discord.Serialization.Json.Converters; | |||||
using System; | using System; | ||||
using System.Collections.Concurrent; | using System.Collections.Concurrent; | ||||
using System.Collections.Generic; | using System.Collections.Generic; | ||||
@@ -11,6 +13,7 @@ using System.Diagnostics; | |||||
using System.Linq; | using System.Linq; | ||||
using System.Linq.Expressions; | using System.Linq.Expressions; | ||||
using System.Net; | using System.Net; | ||||
using System.Reflection; | |||||
using System.Runtime.CompilerServices; | using System.Runtime.CompilerServices; | ||||
using System.Text; | using System.Text; | ||||
using System.Text.Formatting; | using System.Text.Formatting; | ||||
@@ -21,13 +24,22 @@ namespace Discord.API | |||||
{ | { | ||||
internal class DiscordRestApiClient : IDisposable | internal class DiscordRestApiClient : IDisposable | ||||
{ | { | ||||
static DiscordRestApiClient() | |||||
{ | |||||
SerializationFormat.Json.AddConverter<Image, ImagePropertyConverter>(); | |||||
SerializationFormat.Json.AddConverter<long, Int53PropertyConverter>(info => info.GetCustomAttribute<Int53Attribute>() != null); | |||||
SerializationFormat.Json.AddConverter<ulong, UInt53PropertyConverter>(info => info.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>>(); | 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); } } | public event Func<string, string, double, Task> SentRequest { add { _sentRequestEvent.Add(value); } remove { _sentRequestEvent.Remove(value); } } | ||||
private readonly AsyncEvent<Func<string, string, double, Task>> _sentRequestEvent = new AsyncEvent<Func<string, string, double, Task>>(); | private readonly AsyncEvent<Func<string, string, double, Task>> _sentRequestEvent = new AsyncEvent<Func<string, string, double, Task>>(); | ||||
protected readonly SemaphoreSlim _stateLock; | protected readonly SemaphoreSlim _stateLock; | ||||
protected readonly ScopedSerializer _serializer; | |||||
protected readonly Serializer _serializer; | |||||
protected readonly ConcurrentQueue<ArrayFormatter> _formatters; | protected readonly ConcurrentQueue<ArrayFormatter> _formatters; | ||||
private readonly RestClientProvider _restClientProvider; | private readonly RestClientProvider _restClientProvider; | ||||
@@ -44,7 +56,7 @@ namespace Discord.API | |||||
internal IRestClient RestClient { get; private set; } | internal IRestClient RestClient { get; private set; } | ||||
internal ulong? CurrentUserId { get; set;} | internal ulong? CurrentUserId { get; set;} | ||||
public DiscordRestApiClient(RestClientProvider restClientProvider, string userAgent, ScopedSerializer serializer, RetryMode defaultRetryMode = RetryMode.AlwaysRetry) | |||||
public DiscordRestApiClient(RestClientProvider restClientProvider, string userAgent, Serializer serializer, RetryMode defaultRetryMode = RetryMode.AlwaysRetry) | |||||
{ | { | ||||
_restClientProvider = restClientProvider; | _restClientProvider = restClientProvider; | ||||
UserAgent = userAgent; | UserAgent = userAgent; | ||||
@@ -1167,13 +1179,13 @@ namespace Discord.API | |||||
protected static double ToMilliseconds(Stopwatch stopwatch) => Math.Round((double)stopwatch.ElapsedTicks / (double)Stopwatch.Frequency * 1000.0, 2); | protected static double ToMilliseconds(Stopwatch stopwatch) => Math.Round((double)stopwatch.ElapsedTicks / (double)Stopwatch.Frequency * 1000.0, 2); | ||||
protected ReadOnlyBuffer<byte> SerializeJson(ArrayFormatter data, object value) | protected ReadOnlyBuffer<byte> SerializeJson(ArrayFormatter data, object value) | ||||
{ | { | ||||
_serializer.WriteJson(data, value); | |||||
_serializer.Write(data, value); | |||||
return new ReadOnlyBuffer<byte>(data.Formatted.Array, 0, data.Formatted.Count); | return new ReadOnlyBuffer<byte>(data.Formatted.Array, 0, data.Formatted.Count); | ||||
} | } | ||||
protected T DeserializeJson<T>(ReadOnlyBuffer<byte> data) | protected T DeserializeJson<T>(ReadOnlyBuffer<byte> data) | ||||
where T : class, new() | where T : class, new() | ||||
{ | { | ||||
return _serializer.ReadJson<T>(data); | |||||
return _serializer.Read<T>(data); | |||||
} | } | ||||
internal class BucketIds | internal class BucketIds | ||||
@@ -8,7 +8,7 @@ namespace Discord.Rest | |||||
{ | { | ||||
public class DiscordRestClient : BaseDiscordClient, IDiscordClient | public class DiscordRestClient : BaseDiscordClient, IDiscordClient | ||||
{ | { | ||||
private readonly ScopedSerializer _serializer; | |||||
private readonly Serializer _serializer; | |||||
private RestApplication _applicationInfo; | private RestApplication _applicationInfo; | ||||
public new RestSelfUser CurrentUser => base.CurrentUser as RestSelfUser; | public new RestSelfUser CurrentUser => base.CurrentUser as RestSelfUser; | ||||
@@ -16,10 +16,10 @@ namespace Discord.Rest | |||||
public DiscordRestClient() : this(new DiscordRestConfig()) { } | public DiscordRestClient() : this(new DiscordRestConfig()) { } | ||||
public DiscordRestClient(DiscordRestConfig config) : base(config) | public DiscordRestClient(DiscordRestConfig config) : base(config) | ||||
{ | { | ||||
_serializer = Serializer.CreateScope(); | |||||
_serializer.Error += async ex => | |||||
_serializer = new Serializer(SerializationFormat.Json); | |||||
_serializer.Error += ex => | |||||
{ | { | ||||
await _restLogger.WarningAsync("Serializer Error", ex); | |||||
_restLogger.WarningAsync("Serializer Error", ex).GetAwaiter().GetResult(); | |||||
}; | }; | ||||
SetApiClient(new API.DiscordRestApiClient(config.RestClientProvider, DiscordConfig.UserAgent, _serializer, config.DefaultRetryMode)); | SetApiClient(new API.DiscordRestApiClient(config.RestClientProvider, DiscordConfig.UserAgent, _serializer, config.DefaultRetryMode)); | ||||
@@ -1,132 +0,0 @@ | |||||
using System; | |||||
using System.Text; | |||||
using System.Text.Json; | |||||
using System.Text.Utf8; | |||||
namespace Discord.Serialization | |||||
{ | |||||
internal static class JsonReaderExtensions | |||||
{ | |||||
public static bool GetBool(this JsonReader reader) => GetBool(reader.Value); | |||||
public static bool GetBool(this ReadOnlySpan<byte> text) | |||||
{ | |||||
if (PrimitiveParser.TryParseBoolean(text, out bool result, out int ignored, SymbolTable.InvariantUtf8)) | |||||
return result; | |||||
throw new SerializationException("Failed to parse Boolean"); | |||||
} | |||||
public static sbyte GetInt8(this JsonReader reader) => GetInt8(reader.Value); | |||||
public static sbyte GetInt8(this ReadOnlySpan<byte> text) | |||||
{ | |||||
if (PrimitiveParser.TryParseSByte(text, out sbyte result, out int ignored, JsonConstants.NumberFormat, SymbolTable.InvariantUtf8)) | |||||
return result; | |||||
throw new SerializationException("Failed to parse Int8"); | |||||
} | |||||
public static short GetInt16(this JsonReader reader) => GetInt16(reader.Value); | |||||
public static short GetInt16(this ReadOnlySpan<byte> text) | |||||
{ | |||||
if (PrimitiveParser.TryParseInt16(text, out short result, out int ignored, JsonConstants.NumberFormat, SymbolTable.InvariantUtf8)) | |||||
return result; | |||||
throw new SerializationException("Failed to parse Int16"); | |||||
} | |||||
public static int GetInt32(this JsonReader reader) => GetInt32(reader.Value); | |||||
public static int GetInt32(this ReadOnlySpan<byte> text) | |||||
{ | |||||
if (PrimitiveParser.TryParseInt32(text, out int result, out int ignored, JsonConstants.NumberFormat, SymbolTable.InvariantUtf8)) | |||||
return result; | |||||
throw new SerializationException("Failed to parse Int32"); | |||||
} | |||||
public static long GetInt64(this JsonReader reader) => GetInt64(reader.Value); | |||||
public static long GetInt64(this ReadOnlySpan<byte> text) | |||||
{ | |||||
if (PrimitiveParser.TryParseInt64(text, out long result, out int ignored, JsonConstants.NumberFormat, SymbolTable.InvariantUtf8)) | |||||
return result; | |||||
throw new SerializationException("Failed to parse Int64"); | |||||
} | |||||
public static byte GetUInt8(this JsonReader reader) => GetUInt8(reader.Value); | |||||
public static byte GetUInt8(this ReadOnlySpan<byte> text) | |||||
{ | |||||
if (PrimitiveParser.TryParseByte(text, out byte result, out int ignored, JsonConstants.NumberFormat, SymbolTable.InvariantUtf8)) | |||||
return result; | |||||
throw new SerializationException("Failed to parse UInt8"); | |||||
} | |||||
public static ushort GetUInt16(this JsonReader reader) => GetUInt16(reader.Value); | |||||
public static ushort GetUInt16(this ReadOnlySpan<byte> text) | |||||
{ | |||||
if (PrimitiveParser.TryParseUInt16(text, out ushort result, out int ignored, JsonConstants.NumberFormat, SymbolTable.InvariantUtf8)) | |||||
return result; | |||||
throw new SerializationException("Failed to parse UInt16"); | |||||
} | |||||
public static uint GetUInt32(this JsonReader reader) => GetUInt32(reader.Value); | |||||
public static uint GetUInt32(this ReadOnlySpan<byte> text) | |||||
{ | |||||
if (PrimitiveParser.TryParseUInt32(text, out uint result, out int ignored, JsonConstants.NumberFormat, SymbolTable.InvariantUtf8)) | |||||
return result; | |||||
throw new SerializationException("Failed to parse UInt32"); | |||||
} | |||||
public static ulong GetUInt64(this JsonReader reader) => GetUInt64(reader.Value); | |||||
public static ulong GetUInt64(this ReadOnlySpan<byte> text) | |||||
{ | |||||
if (PrimitiveParser.TryParseUInt64(text, out ulong result, out int ignored, JsonConstants.NumberFormat, SymbolTable.InvariantUtf8)) | |||||
return result; | |||||
throw new SerializationException("Failed to parse UInt64"); | |||||
} | |||||
public static char GetChar(this JsonReader reader) => GetChar(reader.Value); | |||||
public static char GetChar(this ReadOnlySpan<byte> text) | |||||
{ | |||||
string str = GetString(text); | |||||
if (str.Length == 1) | |||||
return str[0]; | |||||
throw new SerializationException("Failed to parse Char"); | |||||
} | |||||
public static string GetString(this JsonReader reader) => GetString(reader.Value); | |||||
public static string GetString(this ReadOnlySpan<byte> text) => new Utf8String(text).ToString(); | |||||
public static float GetSingle(this JsonReader reader) => GetSingle(reader.Value); | |||||
public static float GetSingle(this ReadOnlySpan<byte> text) | |||||
{ | |||||
if (PrimitiveParser.TryParseDecimal(text, out decimal result, out int ignored, SymbolTable.InvariantUtf8)) | |||||
return (float)result; | |||||
throw new SerializationException("Failed to parse Single"); | |||||
} | |||||
public static double GetDouble(this JsonReader reader) => GetDouble(reader.Value); | |||||
public static double GetDouble(this ReadOnlySpan<byte> text) | |||||
{ | |||||
if (PrimitiveParser.TryParseDecimal(text, out decimal result, out int ignored, SymbolTable.InvariantUtf8)) | |||||
return (double)result; | |||||
throw new SerializationException("Failed to parse Double"); | |||||
} | |||||
public static decimal GetDecimal(this JsonReader reader) => GetDecimal(reader.Value); | |||||
public static decimal GetDecimal(this ReadOnlySpan<byte> text) | |||||
{ | |||||
if (PrimitiveParser.TryParseDecimal(text, out decimal result, out int ignored, SymbolTable.InvariantUtf8)) | |||||
return result; | |||||
throw new SerializationException("Failed to parse Decimal"); | |||||
} | |||||
public static DateTime GetDateTime(this JsonReader reader) => GetDateTime(reader.Value); | |||||
public static DateTime GetDateTime(this ReadOnlySpan<byte> text) | |||||
{ | |||||
string str = GetString(text); | |||||
if (DateTime.TryParse(str, out var result)) //TODO: Improve perf | |||||
return result; | |||||
throw new SerializationException("Failed to parse DateTime"); | |||||
} | |||||
public static DateTimeOffset GetDateTimeOffset(this JsonReader reader) => GetDateTimeOffset(reader.Value); | |||||
public static DateTimeOffset GetDateTimeOffset(this ReadOnlySpan<byte> text) | |||||
{ | |||||
string str = GetString(text); | |||||
if (DateTimeOffset.TryParse(str, out var result)) //TODO: Improve perf | |||||
return result; | |||||
throw new SerializationException("Failed to parse DateTimeOffset"); | |||||
} | |||||
public static void Skip(this JsonReader reader) | |||||
{ | |||||
int initialDepth = reader._depth; | |||||
while (reader.Read() && reader._depth > initialDepth) { } | |||||
} | |||||
} | |||||
} |
@@ -104,7 +104,7 @@ namespace Discord.Net.Queue | |||||
{ | { | ||||
try | try | ||||
{ | { | ||||
var error = Serializer.ReadJson<Error>(response.Data); | |||||
var error = Serializer.Json.Read<Error>(response.Data); | |||||
code = error.Code; | code = error.Code; | ||||
reason = error.Message; | reason = error.Message; | ||||
} | } | ||||
@@ -1,34 +0,0 @@ | |||||
using System.Collections.Generic; | |||||
using System.Text.Json; | |||||
namespace Discord.Serialization.Converters | |||||
{ | |||||
internal class ArrayPropertyConverter<T> : IPropertyConverter<T[]> | |||||
{ | |||||
private readonly IPropertyConverter<T> _innerConverter; | |||||
public ArrayPropertyConverter(IPropertyConverter<T> innerConverter) | |||||
{ | |||||
_innerConverter = innerConverter; | |||||
} | |||||
public T[] ReadJson(JsonReader reader, bool read = true) | |||||
{ | |||||
if ((read && !reader.Read()) || reader.TokenType != JsonTokenType.StartArray) | |||||
throw new SerializationException("Bad input, expected StartArray"); | |||||
var list = new List<T>(); | |||||
while (reader.Read() && reader.TokenType != JsonTokenType.EndArray) | |||||
list.Add(_innerConverter.ReadJson(reader)); | |||||
return list.ToArray(); | |||||
} | |||||
public void WriteJson(JsonWriter writer, T[] value) | |||||
{ | |||||
writer.WriteArrayStart(); | |||||
for (int i = 0; i < value.Length; i++) | |||||
_innerConverter.WriteJson(writer, value[i]); | |||||
writer.WriteArrayEnd(); | |||||
} | |||||
} | |||||
} |
@@ -1,103 +0,0 @@ | |||||
using Discord.API; | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Reflection; | |||||
namespace Discord.Serialization.Converters | |||||
{ | |||||
internal static class Converter | |||||
{ | |||||
private static readonly MethodInfo _makeListConverterFunc | |||||
= typeof(Converter).GetTypeInfo().GetDeclaredMethod(nameof(MakeListConverterInternal)); | |||||
private static readonly MethodInfo _makeOptionalConverterFunc | |||||
= typeof(Converter).GetTypeInfo().GetDeclaredMethod(nameof(MakeOptionalConverterInternal)); | |||||
private static readonly MethodInfo _makeNullableConverterFunc | |||||
= typeof(Converter).GetTypeInfo().GetDeclaredMethod(nameof(MakeNullableConverterInternal)); | |||||
private static readonly MethodInfo _makeEntityOrIdConverterFunc | |||||
= typeof(Converter).GetTypeInfo().GetDeclaredMethod(nameof(MakeEntityOrIdConverterInternal)); | |||||
public static IPropertyConverter<TProp> For<TProp>() | |||||
=> (IPropertyConverter<TProp>)ForInternal<TProp>(); | |||||
private static object ForInternal<TProp>() | |||||
{ | |||||
var typeInfo = typeof(TProp).GetTypeInfo(); | |||||
//Generics | |||||
if (typeof(TProp).IsConstructedGenericType) | |||||
{ | |||||
Type genericType = typeof(TProp).GetGenericTypeDefinition(); | |||||
if (genericType == typeof(List<>)) | |||||
return MakeListConverter<TProp>(typeof(TProp).GenericTypeArguments[0]); | |||||
else if (genericType == typeof(Optional<>)) | |||||
return MakeOptionalConverter<TProp>(typeof(TProp).GenericTypeArguments[0]); | |||||
else if (genericType == typeof(Nullable<>)) | |||||
return MakeNullableConverter<TProp>(typeof(TProp).GenericTypeArguments[0]); | |||||
else if (genericType == typeof(EntityOrId<>)) | |||||
return MakeEntityOrIdConverter<TProp>(typeof(TProp).GenericTypeArguments[0]); | |||||
} | |||||
//Enums | |||||
if (typeInfo.IsEnum) return new EnumPropertyConverter<TProp>(); | |||||
//Primitives | |||||
if (typeof(TProp) == typeof(bool)) return new BooleanPropertyConverter(); | |||||
if (typeof(TProp) == typeof(sbyte)) return new Int8PropertyConverter(); | |||||
if (typeof(TProp) == typeof(short)) return new Int16PropertyConverter(); | |||||
if (typeof(TProp) == typeof(int)) return new Int32PropertyConverter(); | |||||
if (typeof(TProp) == typeof(long)) | |||||
{ | |||||
if (typeInfo.GetCustomAttribute<Int53Attribute>() != null) | |||||
return new Int53PropertyConverter(); | |||||
else | |||||
return new Int64PropertyConverter(); | |||||
} | |||||
if (typeof(TProp) == typeof(byte)) return new UInt8PropertyConverter(); | |||||
if (typeof(TProp) == typeof(ushort)) return new UInt16PropertyConverter(); | |||||
if (typeof(TProp) == typeof(uint)) return new UInt32PropertyConverter(); | |||||
if (typeof(TProp) == typeof(ulong)) | |||||
{ | |||||
if (typeInfo.GetCustomAttribute<Int53Attribute>() != null) | |||||
return new UInt53PropertyConverter(); | |||||
else | |||||
return new UInt64PropertyConverter(); | |||||
} | |||||
if (typeof(TProp) == typeof(float)) return new SinglePropertyConverter(); | |||||
if (typeof(TProp) == typeof(double)) return new DoublePropertyConverter(); | |||||
if (typeof(TProp) == typeof(decimal)) return new DecimalPropertyConverter(); | |||||
if (typeof(TProp) == typeof(char)) return new CharPropertyConverter(); | |||||
if (typeof(TProp) == typeof(string)) return new StringPropertyConverter(); | |||||
//Structs | |||||
if (typeof(TProp) == typeof(DateTime)) return new DateTimePropertyConverter(); | |||||
if (typeof(TProp) == typeof(DateTimeOffset)) return new DateTimeOffsetPropertyConverter(); | |||||
if (typeof(TProp) == typeof(Image)) return new ImagePropertyConverter(); | |||||
throw new InvalidOperationException($"Unsupported model type: {typeof(TProp).Name}"); | |||||
} | |||||
private static IPropertyConverter<TProp> MakeListConverter<TProp>(Type innerType) | |||||
=> _makeListConverterFunc.MakeGenericMethod(innerType).Invoke(null, null) as IPropertyConverter<TProp>; | |||||
private static IPropertyConverter<List<TInnerProp>> MakeListConverterInternal<TInnerProp>() | |||||
=> new ListPropertyConverter<TInnerProp>(For<TInnerProp>()); | |||||
private static IPropertyConverter<TProp> MakeOptionalConverter<TProp>(Type innerType) | |||||
=> _makeOptionalConverterFunc.MakeGenericMethod(innerType).Invoke(null, null) as IPropertyConverter<TProp>; | |||||
private static IPropertyConverter<Optional<TInnerProp>> MakeOptionalConverterInternal<TInnerProp>() | |||||
=> new OptionalPropertyConverter<TInnerProp>(For<TInnerProp>()); | |||||
private static IPropertyConverter<TProp> MakeNullableConverter<TProp>(Type innerType) | |||||
=> _makeNullableConverterFunc.MakeGenericMethod(innerType).Invoke(null, null) as IPropertyConverter<TProp>; | |||||
private static IPropertyConverter<TInnerProp?> MakeNullableConverterInternal<TInnerProp>() | |||||
where TInnerProp : struct | |||||
=> new NullablePropertyConverter<TInnerProp>(For<TInnerProp>()); | |||||
private static IPropertyConverter<TProp> MakeEntityOrIdConverter<TProp>(Type innerType) | |||||
=> _makeEntityOrIdConverterFunc.MakeGenericMethod(innerType).Invoke(null, null) as IPropertyConverter<TProp>; | |||||
private static IPropertyConverter<EntityOrId<TInnerProp>> MakeEntityOrIdConverterInternal<TInnerProp>() | |||||
=> new EntityOrIdPropertyConverter<TInnerProp>(For<TInnerProp>()); | |||||
} | |||||
} |
@@ -1,32 +0,0 @@ | |||||
using Discord.API; | |||||
using System.Text.Json; | |||||
namespace Discord.Serialization.Converters | |||||
{ | |||||
internal class EntityOrIdPropertyConverter<T> : IPropertyConverter<EntityOrId<T>> | |||||
{ | |||||
private readonly IPropertyConverter<T> _innerConverter; | |||||
public EntityOrIdPropertyConverter(IPropertyConverter<T> innerConverter) | |||||
{ | |||||
_innerConverter = innerConverter; | |||||
} | |||||
public EntityOrId<T> ReadJson(JsonReader reader, bool read = true) | |||||
{ | |||||
if (read) | |||||
reader.Read(); | |||||
if (reader.ValueType == JsonValueType.Number) | |||||
return new EntityOrId<T>(reader.GetUInt64()); | |||||
return new EntityOrId<T>(_innerConverter.ReadJson(reader)); | |||||
} | |||||
public void WriteJson(JsonWriter writer, EntityOrId<T> value) | |||||
{ | |||||
if (value.Object != null) | |||||
_innerConverter.WriteJson(writer, value.Object); | |||||
else | |||||
writer.WriteValue(value.Id); | |||||
} | |||||
} | |||||
} |
@@ -1,12 +0,0 @@ | |||||
using System.Text.Json; | |||||
namespace Discord.Serialization.Converters | |||||
{ | |||||
internal class EnumPropertyConverter<T> : IPropertyConverter<T> | |||||
{ | |||||
public T ReadJson(JsonReader reader, bool read = true) | |||||
=> throw new System.NotImplementedException(); | |||||
public void WriteJson(JsonWriter writer, T value) | |||||
=> throw new System.NotImplementedException(); | |||||
} | |||||
} |
@@ -1,10 +0,0 @@ | |||||
using System.Text.Json; | |||||
namespace Discord.Serialization.Converters | |||||
{ | |||||
internal interface IPropertyConverter<T> | |||||
{ | |||||
T ReadJson(JsonReader reader, bool read = true); | |||||
void WriteJson(JsonWriter writer, T value); | |||||
} | |||||
} |
@@ -1,12 +0,0 @@ | |||||
using System.Text.Json; | |||||
namespace Discord.Serialization.Converters | |||||
{ | |||||
internal class ImagePropertyConverter : IPropertyConverter<Image> | |||||
{ | |||||
public Image ReadJson(JsonReader reader, bool read = true) | |||||
=> throw new System.NotImplementedException(); | |||||
public void WriteJson(JsonWriter writer, Image value) | |||||
=> throw new System.NotImplementedException(); | |||||
} | |||||
} |
@@ -1,32 +0,0 @@ | |||||
using System.Text.Json; | |||||
namespace Discord.Serialization.Converters | |||||
{ | |||||
internal class NullablePropertyConverter<T> : IPropertyConverter<T?> | |||||
where T : struct | |||||
{ | |||||
private readonly IPropertyConverter<T> _innerConverter; | |||||
public NullablePropertyConverter(IPropertyConverter<T> innerConverter) | |||||
{ | |||||
_innerConverter = innerConverter; | |||||
} | |||||
public T? ReadJson(JsonReader reader, bool read = true) | |||||
{ | |||||
if (read) | |||||
reader.Read(); | |||||
if (reader.ValueType == JsonValueType.Null) | |||||
return null; | |||||
return _innerConverter.ReadJson(reader); | |||||
} | |||||
public void WriteJson(JsonWriter writer, T? value) | |||||
{ | |||||
if (value.HasValue) | |||||
_innerConverter.WriteJson(writer, value.Value); | |||||
else | |||||
writer.WriteNull(); | |||||
} | |||||
} | |||||
} |
@@ -1,23 +0,0 @@ | |||||
using System.Text.Json; | |||||
namespace Discord.Serialization.Converters | |||||
{ | |||||
internal class OptionalPropertyConverter<T> : IPropertyConverter<Optional<T>> | |||||
{ | |||||
private readonly IPropertyConverter<T> _innerConverter; | |||||
public OptionalPropertyConverter(IPropertyConverter<T> innerConverter) | |||||
{ | |||||
_innerConverter = innerConverter; | |||||
} | |||||
public Optional<T> ReadJson(JsonReader reader, bool read = true) | |||||
=> new Optional<T>(_innerConverter.ReadJson(reader, read)); | |||||
public void WriteJson(JsonWriter writer, Optional<T> value) | |||||
{ | |||||
if (value.IsSpecified) | |||||
_innerConverter.WriteJson(writer, value.Value); | |||||
} | |||||
} | |||||
} |
@@ -1,18 +0,0 @@ | |||||
using System.Text.Json; | |||||
namespace Discord.Serialization.Converters | |||||
{ | |||||
internal class CharPropertyConverter : IPropertyConverter<char> | |||||
{ | |||||
public char ReadJson(JsonReader reader, bool read = true) | |||||
{ | |||||
if (read) | |||||
reader.Read(); | |||||
if (reader.ValueType != JsonValueType.String) | |||||
throw new SerializationException("Bad input, expected String"); | |||||
return reader.GetChar(); | |||||
} | |||||
public void WriteJson(JsonWriter writer, char value) | |||||
=> writer.WriteValue(value); | |||||
} | |||||
} |
@@ -1,19 +0,0 @@ | |||||
using System; | |||||
using System.Text.Json; | |||||
namespace Discord.Serialization.Converters | |||||
{ | |||||
internal class DateTimeOffsetPropertyConverter : IPropertyConverter<DateTimeOffset> | |||||
{ | |||||
public DateTimeOffset ReadJson(JsonReader reader, bool read = true) | |||||
{ | |||||
if (read) | |||||
reader.Read(); | |||||
if (reader.ValueType != JsonValueType.String) | |||||
throw new SerializationException("Bad input, expected String"); | |||||
return reader.GetDateTimeOffset(); | |||||
} | |||||
public void WriteJson(JsonWriter writer, DateTimeOffset value) | |||||
=> writer.WriteValue(value); | |||||
} | |||||
} |
@@ -1,19 +0,0 @@ | |||||
using System; | |||||
using System.Text.Json; | |||||
namespace Discord.Serialization.Converters | |||||
{ | |||||
internal class DateTimePropertyConverter : IPropertyConverter<DateTime> | |||||
{ | |||||
public DateTime ReadJson(JsonReader reader, bool read = true) | |||||
{ | |||||
if (read) | |||||
reader.Read(); | |||||
if (reader.ValueType != JsonValueType.String) | |||||
throw new SerializationException("Bad input, expected String"); | |||||
return reader.GetDateTime(); | |||||
} | |||||
public void WriteJson(JsonWriter writer, DateTime value) | |||||
=> writer.WriteValue(value); | |||||
} | |||||
} |
@@ -1,18 +0,0 @@ | |||||
using System.Text.Json; | |||||
namespace Discord.Serialization.Converters | |||||
{ | |||||
internal class DecimalPropertyConverter : IPropertyConverter<decimal> | |||||
{ | |||||
public decimal ReadJson(JsonReader reader, bool read = true) | |||||
{ | |||||
if (read) | |||||
reader.Read(); | |||||
if (reader.ValueType != JsonValueType.Number) | |||||
throw new SerializationException("Bad input, expected Number"); | |||||
return reader.GetDecimal(); | |||||
} | |||||
public void WriteJson(JsonWriter writer, decimal value) | |||||
=> writer.WriteValue(value.ToString()); | |||||
} | |||||
} |
@@ -1,18 +0,0 @@ | |||||
using System.Text.Json; | |||||
namespace Discord.Serialization.Converters | |||||
{ | |||||
internal class DoublePropertyConverter : IPropertyConverter<double> | |||||
{ | |||||
public double ReadJson(JsonReader reader, bool read = true) | |||||
{ | |||||
if (read) | |||||
reader.Read(); | |||||
if (reader.ValueType != JsonValueType.Number) | |||||
throw new SerializationException("Bad input, expected Number"); | |||||
return reader.GetDouble(); | |||||
} | |||||
public void WriteJson(JsonWriter writer, double value) | |||||
=> writer.WriteValue(value.ToString()); | |||||
} | |||||
} |
@@ -1,18 +0,0 @@ | |||||
using System.Text.Json; | |||||
namespace Discord.Serialization.Converters | |||||
{ | |||||
internal class Int16PropertyConverter : IPropertyConverter<short> | |||||
{ | |||||
public short ReadJson(JsonReader reader, bool read = true) | |||||
{ | |||||
if (read) | |||||
reader.Read(); | |||||
if (reader.ValueType != JsonValueType.Number) | |||||
throw new SerializationException("Bad input, expected Number"); | |||||
return reader.GetInt16(); | |||||
} | |||||
public void WriteJson(JsonWriter writer, short value) | |||||
=> writer.WriteValue(value); | |||||
} | |||||
} |
@@ -1,18 +0,0 @@ | |||||
using System.Text.Json; | |||||
namespace Discord.Serialization.Converters | |||||
{ | |||||
internal class Int32PropertyConverter : IPropertyConverter<int> | |||||
{ | |||||
public int ReadJson(JsonReader reader, bool read = true) | |||||
{ | |||||
if (read) | |||||
reader.Read(); | |||||
if (reader.ValueType != JsonValueType.Number) | |||||
throw new SerializationException("Bad input, expected Number"); | |||||
return reader.GetInt32(); | |||||
} | |||||
public void WriteJson(JsonWriter writer, int value) | |||||
=> writer.WriteValue(value); | |||||
} | |||||
} |
@@ -1,18 +0,0 @@ | |||||
using System.Text.Json; | |||||
namespace Discord.Serialization.Converters | |||||
{ | |||||
internal class Int64PropertyConverter : IPropertyConverter<long> | |||||
{ | |||||
public long ReadJson(JsonReader reader, bool read = true) | |||||
{ | |||||
if (read) | |||||
reader.Read(); | |||||
if (reader.ValueType != JsonValueType.String) | |||||
throw new SerializationException("Bad input, expected String"); | |||||
return reader.GetInt64(); | |||||
} | |||||
public void WriteJson(JsonWriter writer, long value) | |||||
=> writer.WriteValue(value.ToString()); | |||||
} | |||||
} |
@@ -1,18 +0,0 @@ | |||||
using System.Text.Json; | |||||
namespace Discord.Serialization.Converters | |||||
{ | |||||
internal class Int8PropertyConverter : IPropertyConverter<sbyte> | |||||
{ | |||||
public sbyte ReadJson(JsonReader reader, bool read = true) | |||||
{ | |||||
if (read) | |||||
reader.Read(); | |||||
if (reader.ValueType != JsonValueType.Number) | |||||
throw new SerializationException("Bad input, expected Number"); | |||||
return reader.GetInt8(); | |||||
} | |||||
public void WriteJson(JsonWriter writer, sbyte value) | |||||
=> writer.WriteValue(value); | |||||
} | |||||
} |
@@ -1,18 +0,0 @@ | |||||
using System.Text.Json; | |||||
namespace Discord.Serialization.Converters | |||||
{ | |||||
internal class SinglePropertyConverter : IPropertyConverter<float> | |||||
{ | |||||
public float ReadJson(JsonReader reader, bool read = true) | |||||
{ | |||||
if (read) | |||||
reader.Read(); | |||||
if (reader.ValueType != JsonValueType.Number) | |||||
throw new SerializationException("Bad input, expected Number"); | |||||
return reader.GetSingle(); | |||||
} | |||||
public void WriteJson(JsonWriter writer, float value) | |||||
=> writer.WriteValue(value.ToString()); | |||||
} | |||||
} |
@@ -1,18 +0,0 @@ | |||||
using System.Text.Json; | |||||
namespace Discord.Serialization.Converters | |||||
{ | |||||
internal class StringPropertyConverter : IPropertyConverter<string> | |||||
{ | |||||
public string ReadJson(JsonReader reader, bool read = true) | |||||
{ | |||||
if (read) | |||||
reader.Read(); | |||||
if (reader.ValueType != JsonValueType.String) | |||||
throw new SerializationException("Bad input, expected String"); | |||||
return reader.GetString(); | |||||
} | |||||
public void WriteJson(JsonWriter writer, string value) | |||||
=> writer.WriteValue(value); | |||||
} | |||||
} |
@@ -1,18 +0,0 @@ | |||||
using System.Text.Json; | |||||
namespace Discord.Serialization.Converters | |||||
{ | |||||
internal class UInt16PropertyConverter : IPropertyConverter<ushort> | |||||
{ | |||||
public ushort ReadJson(JsonReader reader, bool read = true) | |||||
{ | |||||
if (read) | |||||
reader.Read(); | |||||
if (reader.ValueType != JsonValueType.Number) | |||||
throw new SerializationException("Bad input, expected Number"); | |||||
return reader.GetUInt16(); | |||||
} | |||||
public void WriteJson(JsonWriter writer, ushort value) | |||||
=> writer.WriteValue(value); | |||||
} | |||||
} |
@@ -1,18 +0,0 @@ | |||||
using System.Text.Json; | |||||
namespace Discord.Serialization.Converters | |||||
{ | |||||
internal class UInt32PropertyConverter : IPropertyConverter<uint> | |||||
{ | |||||
public uint ReadJson(JsonReader reader, bool read = true) | |||||
{ | |||||
if (read) | |||||
reader.Read(); | |||||
if (reader.ValueType != JsonValueType.Number) | |||||
throw new SerializationException("Bad input, expected Number"); | |||||
return reader.GetUInt32(); | |||||
} | |||||
public void WriteJson(JsonWriter writer, uint value) | |||||
=> writer.WriteValue(value); | |||||
} | |||||
} |
@@ -1,18 +0,0 @@ | |||||
using System.Text.Json; | |||||
namespace Discord.Serialization.Converters | |||||
{ | |||||
internal class UInt64PropertyConverter : IPropertyConverter<ulong> | |||||
{ | |||||
public ulong ReadJson(JsonReader reader, bool read = true) | |||||
{ | |||||
if (read) | |||||
reader.Read(); | |||||
if (reader.ValueType != JsonValueType.String) | |||||
throw new SerializationException("Bad input, expected String"); | |||||
return reader.GetUInt64(); | |||||
} | |||||
public void WriteJson(JsonWriter writer, ulong value) | |||||
=> writer.WriteValue(value.ToString()); | |||||
} | |||||
} |
@@ -1,18 +0,0 @@ | |||||
using System.Text.Json; | |||||
namespace Discord.Serialization.Converters | |||||
{ | |||||
internal class UInt8PropertyConverter : IPropertyConverter<byte> | |||||
{ | |||||
public byte ReadJson(JsonReader reader, bool read = true) | |||||
{ | |||||
if (read) | |||||
reader.Read(); | |||||
if (reader.ValueType != JsonValueType.Number) | |||||
throw new SerializationException("Bad input, expected Number"); | |||||
return reader.GetUInt8(); | |||||
} | |||||
public void WriteJson(JsonWriter writer, byte value) | |||||
=> writer.WriteValue(value); | |||||
} | |||||
} |
@@ -0,0 +1,32 @@ | |||||
using Discord.API; | |||||
using System.Text.Json; | |||||
namespace Discord.Serialization.Json.Converters | |||||
{ | |||||
internal class EntityOrIdPropertyConverter<T> : IJsonPropertyConverter<EntityOrId<T>> | |||||
{ | |||||
private readonly IJsonPropertyConverter<T> _innerConverter; | |||||
public EntityOrIdPropertyConverter(IJsonPropertyConverter<T> innerConverter) | |||||
{ | |||||
_innerConverter = innerConverter; | |||||
} | |||||
public EntityOrId<T> Read(JsonReader reader, bool read = true) | |||||
{ | |||||
if (read) | |||||
reader.Read(); | |||||
if (reader.ValueType == JsonValueType.Number) | |||||
return new EntityOrId<T>(reader.ParseUInt64()); | |||||
return new EntityOrId<T>(_innerConverter.Read(reader)); | |||||
} | |||||
public void Write(JsonWriter writer, EntityOrId<T> value) | |||||
{ | |||||
if (value.Object != null) | |||||
_innerConverter.Write(writer, value.Object); | |||||
else | |||||
writer.WriteValue(value.Id); | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,12 @@ | |||||
using System.Text.Json; | |||||
namespace Discord.Serialization.Json.Converters | |||||
{ | |||||
internal class ImagePropertyConverter : IJsonPropertyConverter<API.Image> | |||||
{ | |||||
public API.Image Read(JsonReader reader, bool read = true) | |||||
=> throw new System.NotImplementedException(); | |||||
public void Write(JsonWriter writer, API.Image value) | |||||
=> throw new System.NotImplementedException(); | |||||
} | |||||
} |
@@ -1,18 +1,18 @@ | |||||
using System.Text.Json; | using System.Text.Json; | ||||
namespace Discord.Serialization.Converters | |||||
namespace Discord.Serialization.Json.Converters | |||||
{ | { | ||||
internal class Int53PropertyConverter : IPropertyConverter<long> | |||||
internal class Int53PropertyConverter : IJsonPropertyConverter<long> | |||||
{ | { | ||||
public long ReadJson(JsonReader reader, bool read = true) | |||||
public long Read(JsonReader reader, bool read = true) | |||||
{ | { | ||||
if (read) | if (read) | ||||
reader.Read(); | reader.Read(); | ||||
if (reader.ValueType != JsonValueType.Number) | if (reader.ValueType != JsonValueType.Number) | ||||
throw new SerializationException("Bad input, expected Number"); | throw new SerializationException("Bad input, expected Number"); | ||||
return reader.GetInt64(); | |||||
return reader.ParseInt64(); | |||||
} | } | ||||
public void WriteJson(JsonWriter writer, long value) | |||||
public void Write(JsonWriter writer, long value) | |||||
=> writer.WriteValue(value); | => writer.WriteValue(value); | ||||
} | } | ||||
} | } |
@@ -0,0 +1,23 @@ | |||||
using System.Text.Json; | |||||
namespace Discord.Serialization.Json.Converters | |||||
{ | |||||
internal class OptionalPropertyConverter<T> : IJsonPropertyConverter<Optional<T>> | |||||
{ | |||||
private readonly IJsonPropertyConverter<T> _innerConverter; | |||||
public OptionalPropertyConverter(IJsonPropertyConverter<T> innerConverter) | |||||
{ | |||||
_innerConverter = innerConverter; | |||||
} | |||||
public Optional<T> Read(JsonReader reader, bool read = true) | |||||
=> new Optional<T>(_innerConverter.Read(reader, read)); | |||||
public void Write(JsonWriter writer, Optional<T> value) | |||||
{ | |||||
if (value.IsSpecified) | |||||
_innerConverter.Write(writer, value.Value); | |||||
} | |||||
} | |||||
} |
@@ -1,18 +1,18 @@ | |||||
using System.Text.Json; | using System.Text.Json; | ||||
namespace Discord.Serialization.Converters | |||||
namespace Discord.Serialization.Json.Converters | |||||
{ | { | ||||
internal class UInt53PropertyConverter : IPropertyConverter<ulong> | |||||
internal class UInt53PropertyConverter : IJsonPropertyConverter<ulong> | |||||
{ | { | ||||
public ulong ReadJson(JsonReader reader, bool read = true) | |||||
public ulong Read(JsonReader reader, bool read = true) | |||||
{ | { | ||||
if (read) | if (read) | ||||
reader.Read(); | reader.Read(); | ||||
if (reader.ValueType != JsonValueType.Number) | if (reader.ValueType != JsonValueType.Number) | ||||
throw new SerializationException("Bad input, expected Number"); | throw new SerializationException("Bad input, expected Number"); | ||||
return reader.GetUInt64(); | |||||
return reader.ParseUInt64(); | |||||
} | } | ||||
public void WriteJson(JsonWriter writer, ulong value) | |||||
public void Write(JsonWriter writer, ulong value) | |||||
=> writer.WriteValue(value); | => writer.WriteValue(value); | ||||
} | } | ||||
} | } |
@@ -1,86 +0,0 @@ | |||||
using System; | |||||
using System.Collections.Concurrent; | |||||
using System.Collections.Generic; | |||||
using System.Linq; | |||||
using System.Reflection; | |||||
using System.Text.Json; | |||||
namespace Discord.Serialization | |||||
{ | |||||
internal static class ModelMap | |||||
{ | |||||
private static readonly ConcurrentDictionary<Type, object> _cache = new ConcurrentDictionary<Type, object>(); | |||||
internal static ModelMap<T> For<T>() | |||||
where T : class, new() | |||||
{ | |||||
return _cache.GetOrAdd(typeof(T), _ => | |||||
{ | |||||
var type = typeof(T).GetTypeInfo(); | |||||
var properties = new Dictionary<string, PropertyMap<T>>(); | |||||
var propInfos = type.DeclaredProperties.ToArray(); | |||||
for (int i = 0; i < propInfos.Length; i++) | |||||
{ | |||||
var propInfo = propInfos[i]; | |||||
if (!propInfo.CanRead || !propInfo.CanWrite) | |||||
continue; | |||||
var propMap = PropertyMap.Create<T>(propInfo); | |||||
properties.Add(propMap.Key, propMap); | |||||
} | |||||
return new ModelMap<T>(properties); | |||||
}) as ModelMap<T>; | |||||
} | |||||
} | |||||
internal class ModelMap<T> | |||||
where T : class, new() | |||||
{ | |||||
private readonly PropertyMap<T>[] _propertyList; | |||||
private readonly Dictionary<string, PropertyMap<T>> _properties; | |||||
public ModelMap(Dictionary<string, PropertyMap<T>> properties) | |||||
{ | |||||
_properties = properties; | |||||
_propertyList = _properties.Values.ToArray(); | |||||
} | |||||
public T ReadJson(JsonReader reader) | |||||
{ | |||||
var model = new T(); | |||||
if (!reader.Read() || reader.TokenType != JsonTokenType.StartObject) | |||||
throw new InvalidOperationException("Bad input, expected StartObject"); | |||||
while (reader.Read()) | |||||
{ | |||||
if (reader.TokenType == JsonTokenType.EndObject) | |||||
return model; | |||||
if (reader.TokenType != JsonTokenType.PropertyName) | |||||
throw new InvalidOperationException("Bad input, expected PropertyName"); | |||||
string key = reader.GetString(); | |||||
if (_properties.TryGetValue(key, out var property)) | |||||
property.ReadJson(model, reader); | |||||
else | |||||
reader.Skip(); //Unknown property, skip | |||||
if (!reader.Read()) | |||||
throw new InvalidOperationException("Bad input, expected Value"); | |||||
} | |||||
throw new InvalidOperationException("Bad input, expected EndObject"); | |||||
} | |||||
public void WriteJson(T model, JsonWriter writer) | |||||
{ | |||||
writer.WriteObjectStart(); | |||||
for (int i = 0; i < _propertyList.Length; i++) | |||||
{ | |||||
var property = _propertyList[i]; | |||||
writer.WriteStartAttribute(property.Key); | |||||
property.WriteJson(model, writer); | |||||
} | |||||
writer.WriteObjectEnd(); | |||||
} | |||||
} | |||||
} |
@@ -1,61 +0,0 @@ | |||||
using Discord.Serialization.Converters; | |||||
using System; | |||||
using System.Reflection; | |||||
using System.Text.Json; | |||||
namespace Discord.Serialization | |||||
{ | |||||
internal static class PropertyMap | |||||
{ | |||||
public static PropertyMap<TModel> Create<TModel>(PropertyInfo propInfo) | |||||
{ | |||||
var type = typeof(PropertyMap<,>).MakeGenericType(typeof(TModel), propInfo.PropertyType); | |||||
return Activator.CreateInstance(type, propInfo) as PropertyMap<TModel>; | |||||
} | |||||
} | |||||
internal abstract class PropertyMap<TModel> | |||||
{ | |||||
public string Key { get; protected set; } | |||||
public abstract void WriteJson(TModel model, JsonWriter writer); | |||||
public abstract void ReadJson(TModel model, JsonReader reader); | |||||
} | |||||
internal class PropertyMap<TModel, TProp> : PropertyMap<TModel> | |||||
{ | |||||
private readonly IPropertyConverter<TProp> _converter; | |||||
private readonly Func<TModel, TProp> _getFunc; | |||||
private readonly Action<TModel, TProp> _setFunc; | |||||
public PropertyMap(PropertyInfo propInfo) | |||||
{ | |||||
var jsonProperty = propInfo.GetCustomAttribute<ModelPropertyAttribute>(); | |||||
if (jsonProperty != null) | |||||
Key = jsonProperty.Key; | |||||
else | |||||
Key = propInfo.Name; | |||||
_getFunc = propInfo.GetMethod.CreateDelegate(typeof(Func<TModel, TProp>)) as Func<TModel, TProp>; | |||||
_setFunc = propInfo.SetMethod.CreateDelegate(typeof(Action<TModel, TProp>)) as Action<TModel, TProp>; | |||||
_converter = Converter.For<TProp>(); | |||||
} | |||||
private TProp GetValue(TModel model) | |||||
=> _getFunc(model); | |||||
private void SetValue(TModel model, TProp prop) | |||||
=> _setFunc(model, prop); | |||||
public override void WriteJson(TModel model, JsonWriter writer) | |||||
{ | |||||
var value = GetValue(model); | |||||
_converter.WriteJson(writer, value); | |||||
} | |||||
public override void ReadJson(TModel model, JsonReader reader) | |||||
{ | |||||
var value = _converter.ReadJson(reader); | |||||
SetValue(model, value); | |||||
} | |||||
} | |||||
} |
@@ -1,35 +0,0 @@ | |||||
using System; | |||||
using System.Text; | |||||
using System.Text.Formatting; | |||||
using System.Text.Json; | |||||
using System.Threading.Tasks; | |||||
namespace Discord.Serialization | |||||
{ | |||||
internal class Serializer | |||||
{ | |||||
public static ScopedSerializer Global { get; } = new ScopedSerializer(); | |||||
public static T ReadJson<T>(ReadOnlyBuffer<byte> data) where T : class, new() => Global.ReadJson<T>(data); | |||||
public static void WriteJson<T>(ArrayFormatter data, T obj) where T : class, new() => Global.WriteJson(data, obj); | |||||
public static ScopedSerializer CreateScope() => new ScopedSerializer(); | |||||
} | |||||
internal class ScopedSerializer | |||||
{ | |||||
private readonly AsyncEvent<Func<Exception, Task>> _errorEvent = new AsyncEvent<Func<Exception, Task>>(); | |||||
public event Func<Exception, Task> Error | |||||
{ | |||||
add { _errorEvent.Add(value); } | |||||
remove { _errorEvent.Remove(value); } | |||||
} | |||||
public T ReadJson<T>(ReadOnlyBuffer<byte> data) | |||||
where T : class, new() | |||||
=> ModelMap.For<T>().ReadJson(new JsonReader(data.Span, SymbolTable.InvariantUtf8)); | |||||
public void WriteJson<T>(ArrayFormatter data, T obj) | |||||
where T : class, new() | |||||
=> ModelMap.For<T>().WriteJson(obj, new JsonWriter(data)); | |||||
} | |||||
} |
@@ -41,11 +41,11 @@ namespace Discord.API | |||||
} | } | ||||
public Task SetResultAsync(ReadOnlyBuffer<byte> data) | public Task SetResultAsync(ReadOnlyBuffer<byte> data) | ||||
{ | { | ||||
return Promise.TrySetResultAsync(Serializer.ReadJson<T>(data)); | |||||
return Promise.TrySetResultAsync(Serializer.Json.Read<T>(data)); | |||||
} | } | ||||
public Task SetExceptionAsync(ReadOnlyBuffer<byte> data) | public Task SetExceptionAsync(ReadOnlyBuffer<byte> data) | ||||
{ | { | ||||
var error = Serializer.ReadJson<ErrorEvent>(data); | |||||
var error = Serializer.Json.Read<ErrorEvent>(data); | |||||
return Promise.TrySetExceptionAsync(new RpcException(error.Code, error.Message)); | return Promise.TrySetExceptionAsync(new RpcException(error.Code, error.Message)); | ||||
} | } | ||||
} | } | ||||
@@ -72,7 +72,7 @@ namespace Discord.API | |||||
public ConnectionState ConnectionState { get; private set; } | public ConnectionState ConnectionState { get; private set; } | ||||
public DiscordRpcApiClient(string clientId, string userAgent, string origin, RestClientProvider restClientProvider, WebSocketProvider webSocketProvider, | public DiscordRpcApiClient(string clientId, string userAgent, string origin, RestClientProvider restClientProvider, WebSocketProvider webSocketProvider, | ||||
ScopedSerializer serializer, RetryMode defaultRetryMode = RetryMode.AlwaysRetry) | |||||
Serializer serializer, RetryMode defaultRetryMode = RetryMode.AlwaysRetry) | |||||
: base(restClientProvider, userAgent, serializer, defaultRetryMode) | : base(restClientProvider, userAgent, serializer, defaultRetryMode) | ||||
{ | { | ||||
_connectionLock = new SemaphoreSlim(1, 1); | _connectionLock = new SemaphoreSlim(1, 1); | ||||
@@ -99,7 +99,7 @@ namespace Discord.API | |||||
_decompressionStream.SetLength(_decompressionStream.Position); | _decompressionStream.SetLength(_decompressionStream.Position); | ||||
_decompressionStream.Position = 0; | _decompressionStream.Position = 0; | ||||
var msg = _serializer.ReadJson<RpcFrame>(_decompressionStream.ToReadOnlyBuffer()); | |||||
var msg = _serializer.Read<RpcFrame>(_decompressionStream.ToReadOnlyBuffer()); | |||||
if (msg != null) | if (msg != null) | ||||
{ | { | ||||
await _receivedRpcEvent.InvokeAsync(msg.Cmd, msg.Event, msg.Data).ConfigureAwait(false); | await _receivedRpcEvent.InvokeAsync(msg.Cmd, msg.Event, msg.Data).ConfigureAwait(false); | ||||
@@ -110,7 +110,7 @@ namespace Discord.API | |||||
} | } | ||||
else | else | ||||
{ | { | ||||
var msg = _serializer.ReadJson<RpcFrame>(data); | |||||
var msg = _serializer.Read<RpcFrame>(data); | |||||
if (msg != null) | if (msg != null) | ||||
{ | { | ||||
await _receivedRpcEvent.InvokeAsync(msg.Cmd, msg.Event, msg.Data).ConfigureAwait(false); | await _receivedRpcEvent.InvokeAsync(msg.Cmd, msg.Event, msg.Data).ConfigureAwait(false); | ||||
@@ -16,7 +16,7 @@ namespace Discord.Rpc | |||||
private readonly ConnectionManager _connection; | private readonly ConnectionManager _connection; | ||||
private readonly Logger _rpcLogger; | private readonly Logger _rpcLogger; | ||||
private readonly SemaphoreSlim _stateLock, _authorizeLock; | private readonly SemaphoreSlim _stateLock, _authorizeLock; | ||||
private readonly ScopedSerializer _serializer; | |||||
private readonly Serializer _serializer; | |||||
public ConnectionState ConnectionState { get; private set; } | public ConnectionState ConnectionState { get; private set; } | ||||
public IReadOnlyCollection<string> Scopes { get; private set; } | public IReadOnlyCollection<string> Scopes { get; private set; } | ||||
@@ -37,10 +37,10 @@ namespace Discord.Rpc | |||||
_authorizeLock = new SemaphoreSlim(1, 1); | _authorizeLock = new SemaphoreSlim(1, 1); | ||||
_rpcLogger = LogManager.CreateLogger("RPC"); | _rpcLogger = LogManager.CreateLogger("RPC"); | ||||
_serializer = Serializer.CreateScope(); | |||||
_serializer.Error += async ex => | |||||
_serializer = new Serializer(SerializationFormat.Json); | |||||
_serializer.Error += ex => | |||||
{ | { | ||||
await _rpcLogger.WarningAsync("Serializer Error", ex); | |||||
_rpcLogger.WarningAsync("Serializer Error", ex).GetAwaiter().GetResult(); | |||||
}; | }; | ||||
SetApiClient(new API.DiscordRpcApiClient(clientId, DiscordRestConfig.UserAgent, origin, config.RestClientProvider, config.WebSocketProvider, _serializer)); | SetApiClient(new API.DiscordRpcApiClient(clientId, DiscordRestConfig.UserAgent, origin, config.RestClientProvider, config.WebSocketProvider, _serializer)); | ||||
@@ -293,7 +293,7 @@ namespace Discord.Rpc | |||||
case "READY": | case "READY": | ||||
{ | { | ||||
await _rpcLogger.DebugAsync("Received Dispatch (READY)").ConfigureAwait(false); | await _rpcLogger.DebugAsync("Received Dispatch (READY)").ConfigureAwait(false); | ||||
var data = _serializer.ReadJson<ReadyEvent>(payload.Value); | |||||
var data = _serializer.Read<ReadyEvent>(payload.Value); | |||||
var options = new RequestOptions | var options = new RequestOptions | ||||
{ | { | ||||
@@ -335,7 +335,7 @@ namespace Discord.Rpc | |||||
case "CHANNEL_CREATE": | case "CHANNEL_CREATE": | ||||
{ | { | ||||
await _rpcLogger.DebugAsync("Received Dispatch (CHANNEL_CREATE)").ConfigureAwait(false); | await _rpcLogger.DebugAsync("Received Dispatch (CHANNEL_CREATE)").ConfigureAwait(false); | ||||
var data = _serializer.ReadJson<ChannelSummary>(payload.Value); | |||||
var data = _serializer.Read<ChannelSummary>(payload.Value); | |||||
var channel = RpcChannelSummary.Create(data); | var channel = RpcChannelSummary.Create(data); | ||||
await _channelCreatedEvent.InvokeAsync(channel).ConfigureAwait(false); | await _channelCreatedEvent.InvokeAsync(channel).ConfigureAwait(false); | ||||
@@ -346,7 +346,7 @@ namespace Discord.Rpc | |||||
case "GUILD_CREATE": | case "GUILD_CREATE": | ||||
{ | { | ||||
await _rpcLogger.DebugAsync("Received Dispatch (GUILD_CREATE)").ConfigureAwait(false); | await _rpcLogger.DebugAsync("Received Dispatch (GUILD_CREATE)").ConfigureAwait(false); | ||||
var data = _serializer.ReadJson<GuildSummary>(payload.Value); | |||||
var data = _serializer.Read<GuildSummary>(payload.Value); | |||||
var guild = RpcGuildSummary.Create(data); | var guild = RpcGuildSummary.Create(data); | ||||
await _guildCreatedEvent.InvokeAsync(guild).ConfigureAwait(false); | await _guildCreatedEvent.InvokeAsync(guild).ConfigureAwait(false); | ||||
@@ -355,7 +355,7 @@ namespace Discord.Rpc | |||||
case "GUILD_STATUS": | case "GUILD_STATUS": | ||||
{ | { | ||||
await _rpcLogger.DebugAsync("Received Dispatch (GUILD_STATUS)").ConfigureAwait(false); | await _rpcLogger.DebugAsync("Received Dispatch (GUILD_STATUS)").ConfigureAwait(false); | ||||
var data = _serializer.ReadJson<GuildStatusEvent>(payload.Value); | |||||
var data = _serializer.Read<GuildStatusEvent>(payload.Value); | |||||
var guildStatus = RpcGuildStatus.Create(data); | var guildStatus = RpcGuildStatus.Create(data); | ||||
await _guildStatusUpdatedEvent.InvokeAsync(guildStatus).ConfigureAwait(false); | await _guildStatusUpdatedEvent.InvokeAsync(guildStatus).ConfigureAwait(false); | ||||
@@ -366,7 +366,7 @@ namespace Discord.Rpc | |||||
case "VOICE_STATE_CREATE": | case "VOICE_STATE_CREATE": | ||||
{ | { | ||||
await _rpcLogger.DebugAsync("Received Dispatch (VOICE_STATE_CREATE)").ConfigureAwait(false); | await _rpcLogger.DebugAsync("Received Dispatch (VOICE_STATE_CREATE)").ConfigureAwait(false); | ||||
var data = _serializer.ReadJson<ExtendedVoiceState>(payload.Value); | |||||
var data = _serializer.Read<ExtendedVoiceState>(payload.Value); | |||||
var voiceState = RpcVoiceState.Create(this, data); | var voiceState = RpcVoiceState.Create(this, data); | ||||
await _voiceStateCreatedEvent.InvokeAsync(voiceState).ConfigureAwait(false); | await _voiceStateCreatedEvent.InvokeAsync(voiceState).ConfigureAwait(false); | ||||
@@ -375,7 +375,7 @@ namespace Discord.Rpc | |||||
case "VOICE_STATE_UPDATE": | case "VOICE_STATE_UPDATE": | ||||
{ | { | ||||
await _rpcLogger.DebugAsync("Received Dispatch (VOICE_STATE_UPDATE)").ConfigureAwait(false); | await _rpcLogger.DebugAsync("Received Dispatch (VOICE_STATE_UPDATE)").ConfigureAwait(false); | ||||
var data = _serializer.ReadJson<ExtendedVoiceState>(payload.Value); | |||||
var data = _serializer.Read<ExtendedVoiceState>(payload.Value); | |||||
var voiceState = RpcVoiceState.Create(this, data); | var voiceState = RpcVoiceState.Create(this, data); | ||||
await _voiceStateUpdatedEvent.InvokeAsync(voiceState).ConfigureAwait(false); | await _voiceStateUpdatedEvent.InvokeAsync(voiceState).ConfigureAwait(false); | ||||
@@ -384,7 +384,7 @@ namespace Discord.Rpc | |||||
case "VOICE_STATE_DELETE": | case "VOICE_STATE_DELETE": | ||||
{ | { | ||||
await _rpcLogger.DebugAsync("Received Dispatch (VOICE_STATE_DELETE)").ConfigureAwait(false); | await _rpcLogger.DebugAsync("Received Dispatch (VOICE_STATE_DELETE)").ConfigureAwait(false); | ||||
var data = _serializer.ReadJson<ExtendedVoiceState>(payload.Value); | |||||
var data = _serializer.Read<ExtendedVoiceState>(payload.Value); | |||||
var voiceState = RpcVoiceState.Create(this, data); | var voiceState = RpcVoiceState.Create(this, data); | ||||
await _voiceStateDeletedEvent.InvokeAsync(voiceState).ConfigureAwait(false); | await _voiceStateDeletedEvent.InvokeAsync(voiceState).ConfigureAwait(false); | ||||
@@ -394,7 +394,7 @@ namespace Discord.Rpc | |||||
case "SPEAKING_START": | case "SPEAKING_START": | ||||
{ | { | ||||
await _rpcLogger.DebugAsync("Received Dispatch (SPEAKING_START)").ConfigureAwait(false); | await _rpcLogger.DebugAsync("Received Dispatch (SPEAKING_START)").ConfigureAwait(false); | ||||
var data = _serializer.ReadJson<SpeakingEvent>(payload.Value); | |||||
var data = _serializer.Read<SpeakingEvent>(payload.Value); | |||||
await _speakingStartedEvent.InvokeAsync(data.UserId).ConfigureAwait(false); | await _speakingStartedEvent.InvokeAsync(data.UserId).ConfigureAwait(false); | ||||
} | } | ||||
@@ -402,7 +402,7 @@ namespace Discord.Rpc | |||||
case "SPEAKING_STOP": | case "SPEAKING_STOP": | ||||
{ | { | ||||
await _rpcLogger.DebugAsync("Received Dispatch (SPEAKING_STOP)").ConfigureAwait(false); | await _rpcLogger.DebugAsync("Received Dispatch (SPEAKING_STOP)").ConfigureAwait(false); | ||||
var data = _serializer.ReadJson<SpeakingEvent>(payload.Value); | |||||
var data = _serializer.Read<SpeakingEvent>(payload.Value); | |||||
await _speakingStoppedEvent.InvokeAsync(data.UserId).ConfigureAwait(false); | await _speakingStoppedEvent.InvokeAsync(data.UserId).ConfigureAwait(false); | ||||
} | } | ||||
@@ -410,7 +410,7 @@ namespace Discord.Rpc | |||||
case "VOICE_SETTINGS_UPDATE": | case "VOICE_SETTINGS_UPDATE": | ||||
{ | { | ||||
await _rpcLogger.DebugAsync("Received Dispatch (VOICE_SETTINGS_UPDATE)").ConfigureAwait(false); | await _rpcLogger.DebugAsync("Received Dispatch (VOICE_SETTINGS_UPDATE)").ConfigureAwait(false); | ||||
var data = _serializer.ReadJson<API.Rpc.VoiceSettings>(payload.Value); | |||||
var data = _serializer.Read<API.Rpc.VoiceSettings>(payload.Value); | |||||
var settings = VoiceSettings.Create(data); | var settings = VoiceSettings.Create(data); | ||||
await _voiceSettingsUpdated.InvokeAsync(settings).ConfigureAwait(false); | await _voiceSettingsUpdated.InvokeAsync(settings).ConfigureAwait(false); | ||||
@@ -421,7 +421,7 @@ namespace Discord.Rpc | |||||
case "MESSAGE_CREATE": | case "MESSAGE_CREATE": | ||||
{ | { | ||||
await _rpcLogger.DebugAsync("Received Dispatch (MESSAGE_CREATE)").ConfigureAwait(false); | await _rpcLogger.DebugAsync("Received Dispatch (MESSAGE_CREATE)").ConfigureAwait(false); | ||||
var data = _serializer.ReadJson<MessageEvent>(payload.Value); | |||||
var data = _serializer.Read<MessageEvent>(payload.Value); | |||||
var msg = RpcMessage.Create(this, data.ChannelId, data.Message); | var msg = RpcMessage.Create(this, data.ChannelId, data.Message); | ||||
await _messageReceivedEvent.InvokeAsync(msg).ConfigureAwait(false); | await _messageReceivedEvent.InvokeAsync(msg).ConfigureAwait(false); | ||||
@@ -430,7 +430,7 @@ namespace Discord.Rpc | |||||
case "MESSAGE_UPDATE": | case "MESSAGE_UPDATE": | ||||
{ | { | ||||
await _rpcLogger.DebugAsync("Received Dispatch (MESSAGE_UPDATE)").ConfigureAwait(false); | await _rpcLogger.DebugAsync("Received Dispatch (MESSAGE_UPDATE)").ConfigureAwait(false); | ||||
var data = _serializer.ReadJson<MessageEvent>(payload.Value); | |||||
var data = _serializer.Read<MessageEvent>(payload.Value); | |||||
var msg = RpcMessage.Create(this, data.ChannelId, data.Message); | var msg = RpcMessage.Create(this, data.ChannelId, data.Message); | ||||
await _messageUpdatedEvent.InvokeAsync(msg).ConfigureAwait(false); | await _messageUpdatedEvent.InvokeAsync(msg).ConfigureAwait(false); | ||||
@@ -439,7 +439,7 @@ namespace Discord.Rpc | |||||
case "MESSAGE_DELETE": | case "MESSAGE_DELETE": | ||||
{ | { | ||||
await _rpcLogger.DebugAsync("Received Dispatch (MESSAGE_DELETE)").ConfigureAwait(false); | await _rpcLogger.DebugAsync("Received Dispatch (MESSAGE_DELETE)").ConfigureAwait(false); | ||||
var data = _serializer.ReadJson<MessageEvent>(payload.Value); | |||||
var data = _serializer.Read<MessageEvent>(payload.Value); | |||||
await _messageDeletedEvent.InvokeAsync(data.ChannelId, data.Message.Id).ConfigureAwait(false); | await _messageDeletedEvent.InvokeAsync(data.ChannelId, data.Message.Id).ConfigureAwait(false); | ||||
} | } | ||||
@@ -0,0 +1,95 @@ | |||||
using System; | |||||
using System.Collections.Concurrent; | |||||
using System.Collections.Generic; | |||||
using System.Reflection; | |||||
namespace Discord.Serialization | |||||
{ | |||||
public class ConverterCollection | |||||
{ | |||||
private class ConverterTypeCollection | |||||
{ | |||||
public Type DefaultConverterType; | |||||
public List<(Func<PropertyInfo, bool> Condition, Type ConverterType)> Conditionals = new List<(Func<PropertyInfo, bool>, Type)>(); | |||||
} | |||||
private static readonly MethodInfo _getConverterMethod | |||||
= typeof(ConverterCollection).GetTypeInfo().GetDeclaredMethod(nameof(Get)); | |||||
private readonly ConcurrentDictionary<Type, object> _maps = new ConcurrentDictionary<Type, object>(); | |||||
private readonly ConcurrentDictionary<Type, ConverterTypeCollection> _types = new ConcurrentDictionary<Type, ConverterTypeCollection>(); | |||||
private readonly ConcurrentDictionary<Type, ConverterTypeCollection> _genericTypes = new ConcurrentDictionary<Type, ConverterTypeCollection>(); | |||||
internal ConverterCollection() { } | |||||
public void Add<TType, TConverter>() | |||||
{ | |||||
var converters = _types.GetOrAdd(typeof(TType), _ => new ConverterTypeCollection()); | |||||
converters.DefaultConverterType = typeof(TConverter); | |||||
} | |||||
public void Add<TType, TConverter>(Func<PropertyInfo, bool> condition) | |||||
{ | |||||
var converters = _types.GetOrAdd(typeof(TType), _ => new ConverterTypeCollection()); | |||||
converters.Conditionals.Add((condition, typeof(TConverter))); | |||||
} | |||||
public void AddGeneric(Type openType, Type openConverterType) | |||||
{ | |||||
if (openType.IsConstructedGenericType) throw new InvalidOperationException($"{nameof(openType)} must be an open generic"); | |||||
if (openConverterType.IsConstructedGenericType) throw new InvalidOperationException($"{nameof(openConverterType)} must be an open generic"); | |||||
var converters = _genericTypes.GetOrAdd(openType, _ => new ConverterTypeCollection()); | |||||
converters.DefaultConverterType = openConverterType; | |||||
} | |||||
public void AddGeneric(Type openType, Type openConverterType, Func<PropertyInfo, bool> condition) | |||||
{ | |||||
if (openType.IsConstructedGenericType) throw new InvalidOperationException($"{nameof(openType)} must be an open generic"); | |||||
if (openConverterType.IsConstructedGenericType) throw new InvalidOperationException($"{nameof(openConverterType)} must be an open generic"); | |||||
var converters = _genericTypes.GetOrAdd(openType, _ => new ConverterTypeCollection()); | |||||
converters.Conditionals.Add((condition, openConverterType)); | |||||
} | |||||
public object Get<TType>(PropertyInfo propInfo) | |||||
{ | |||||
var typeInfo = typeof(TType).GetTypeInfo(); | |||||
//Generic converters | |||||
if (typeInfo.IsGenericType) | |||||
{ | |||||
var converterType = FindConverterType(typeInfo.GetGenericTypeDefinition(), _genericTypes, propInfo); | |||||
if (converterType != null) | |||||
{ | |||||
var innerType = typeInfo.GenericTypeArguments[0]; | |||||
converterType = converterType.MakeGenericType(innerType); | |||||
object innerConverter = GetInnerConverter(innerType, propInfo); | |||||
return Activator.CreateInstance(converterType, innerConverter); | |||||
} | |||||
} | |||||
//Normal converters | |||||
{ | |||||
var converterType = FindConverterType(typeof(TType), _types, propInfo); | |||||
if (converterType != null) | |||||
return Activator.CreateInstance(converterType); | |||||
} | |||||
throw new InvalidOperationException($"Unsupported model type: {typeof(TType).Name}"); | |||||
} | |||||
private object GetInnerConverter(Type type, PropertyInfo propInfo) | |||||
=> _getConverterMethod.MakeGenericMethod(type).Invoke(this, new object[] { propInfo }); | |||||
private Type FindConverterType(Type type, ConcurrentDictionary<Type, ConverterTypeCollection> collection, PropertyInfo propInfo) | |||||
{ | |||||
if (collection.TryGetValue(type, out var converters)) | |||||
{ | |||||
for (int i = 0; i < converters.Conditionals.Count; i++) | |||||
{ | |||||
if (converters.Conditionals[i].Condition(propInfo)) | |||||
return converters.Conditionals[i].ConverterType; | |||||
} | |||||
if (converters.DefaultConverterType != null) | |||||
return converters.DefaultConverterType; | |||||
} | |||||
return null; | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,18 @@ | |||||
<Project Sdk="Microsoft.NET.Sdk"> | |||||
<Import Project="../../Discord.Net.targets" /> | |||||
<PropertyGroup> | |||||
<AssemblyName>Discord.Net.Serialization</AssemblyName> | |||||
<RootNamespace>Discord.Serialization</RootNamespace> | |||||
<Description>The core components for the Discord.Net library.</Description> | |||||
<TargetFrameworks>net45;netstandard1.1;netstandard1.3</TargetFrameworks> | |||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks> | |||||
</PropertyGroup> | |||||
<ItemGroup> | |||||
<PackageReference Include="System.Buffers" Version="4.4.0-preview2-25405-01" /> | |||||
<PackageReference Include="System.Collections.Immutable" Version="1.3.1" /> | |||||
<PackageReference Include="System.Interactive.Async" Version="3.1.1" /> | |||||
<PackageReference Include="System.Memory" Version="4.4.0-preview2-25405-01" /> | |||||
<PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="4.4.0-preview2-25405-01" /> | |||||
<PackageReference Include="System.ValueTuple" Version="4.3.1" /> | |||||
</ItemGroup> | |||||
</Project> |
@@ -0,0 +1,111 @@ | |||||
using System; | |||||
using System.Text; | |||||
using System.Text.Utf8; | |||||
namespace Discord.Serialization | |||||
{ | |||||
public static class BufferExtensions | |||||
{ | |||||
private static readonly ParsedFormat _numberFormat = new ParsedFormat('D'); | |||||
public static bool ParseBool(this ReadOnlySpan<byte> text) | |||||
{ | |||||
if (PrimitiveParser.TryParseBoolean(text, out bool result, out int ignored, SymbolTable.InvariantUtf8)) | |||||
return result; | |||||
throw new SerializationException("Failed to parse Boolean"); | |||||
} | |||||
public static sbyte ParseInt8(this ReadOnlySpan<byte> text) | |||||
{ | |||||
if (PrimitiveParser.TryParseSByte(text, out sbyte result, out int ignored, _numberFormat, SymbolTable.InvariantUtf8)) | |||||
return result; | |||||
throw new SerializationException("Failed to parse Int8"); | |||||
} | |||||
public static short ParseInt16(this ReadOnlySpan<byte> text) | |||||
{ | |||||
if (PrimitiveParser.TryParseInt16(text, out short result, out int ignored, _numberFormat, SymbolTable.InvariantUtf8)) | |||||
return result; | |||||
throw new SerializationException("Failed to parse Int16"); | |||||
} | |||||
public static int ParseInt32(this ReadOnlySpan<byte> text) | |||||
{ | |||||
if (PrimitiveParser.TryParseInt32(text, out int result, out int ignored, _numberFormat, SymbolTable.InvariantUtf8)) | |||||
return result; | |||||
throw new SerializationException("Failed to parse Int32"); | |||||
} | |||||
public static long ParseInt64(this ReadOnlySpan<byte> text) | |||||
{ | |||||
if (PrimitiveParser.TryParseInt64(text, out long result, out int ignored, _numberFormat, SymbolTable.InvariantUtf8)) | |||||
return result; | |||||
throw new SerializationException("Failed to parse Int64"); | |||||
} | |||||
public static byte ParseUInt8(this ReadOnlySpan<byte> text) | |||||
{ | |||||
if (PrimitiveParser.TryParseByte(text, out byte result, out int ignored, _numberFormat, SymbolTable.InvariantUtf8)) | |||||
return result; | |||||
throw new SerializationException("Failed to parse UInt8"); | |||||
} | |||||
public static ushort ParseUInt16(this ReadOnlySpan<byte> text) | |||||
{ | |||||
if (PrimitiveParser.TryParseUInt16(text, out ushort result, out int ignored, _numberFormat, SymbolTable.InvariantUtf8)) | |||||
return result; | |||||
throw new SerializationException("Failed to parse UInt16"); | |||||
} | |||||
public static uint ParseUInt32(this ReadOnlySpan<byte> text) | |||||
{ | |||||
if (PrimitiveParser.TryParseUInt32(text, out uint result, out int ignored, _numberFormat, SymbolTable.InvariantUtf8)) | |||||
return result; | |||||
throw new SerializationException("Failed to parse UInt32"); | |||||
} | |||||
public static ulong ParseUInt64(this ReadOnlySpan<byte> text) | |||||
{ | |||||
if (PrimitiveParser.TryParseUInt64(text, out ulong result, out int ignored, _numberFormat, SymbolTable.InvariantUtf8)) | |||||
return result; | |||||
throw new SerializationException("Failed to parse UInt64"); | |||||
} | |||||
public static char ParseChar(this ReadOnlySpan<byte> text) | |||||
{ | |||||
string str = ParseString(text); | |||||
if (str.Length == 1) | |||||
return str[0]; | |||||
throw new SerializationException("Failed to parse Char"); | |||||
} | |||||
public static string ParseString(this ReadOnlySpan<byte> text) => new Utf8String(text).ToString(); | |||||
public static float ParseSingle(this ReadOnlySpan<byte> text) | |||||
{ | |||||
if (PrimitiveParser.TryParseDecimal(text, out decimal result, out int ignored, SymbolTable.InvariantUtf8)) | |||||
return (float)result; | |||||
throw new SerializationException("Failed to parse Single"); | |||||
} | |||||
public static double ParseDouble(this ReadOnlySpan<byte> text) | |||||
{ | |||||
if (PrimitiveParser.TryParseDecimal(text, out decimal result, out int ignored, SymbolTable.InvariantUtf8)) | |||||
return (double)result; | |||||
throw new SerializationException("Failed to parse Double"); | |||||
} | |||||
public static decimal ParseDecimal(this ReadOnlySpan<byte> text) | |||||
{ | |||||
if (PrimitiveParser.TryParseDecimal(text, out decimal result, out int ignored, SymbolTable.InvariantUtf8)) | |||||
return result; | |||||
throw new SerializationException("Failed to parse Decimal"); | |||||
} | |||||
public static DateTime ParseDateTime(this ReadOnlySpan<byte> text) | |||||
{ | |||||
string str = ParseString(text); | |||||
if (DateTime.TryParse(str, out var result)) //TODO: Improve perf | |||||
return result; | |||||
throw new SerializationException("Failed to parse DateTime"); | |||||
} | |||||
public static DateTimeOffset ParseDateTimeOffset(this ReadOnlySpan<byte> text) | |||||
{ | |||||
string str = ParseString(text); | |||||
if (DateTimeOffset.TryParse(str, out var result)) //TODO: Improve perf | |||||
return result; | |||||
throw new SerializationException("Failed to parse DateTimeOffset"); | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,36 @@ | |||||
using System; | |||||
using System.Text.Json; | |||||
namespace Discord.Serialization | |||||
{ | |||||
public static class JsonReaderExtensions | |||||
{ | |||||
public static bool ParseBool(this JsonReader reader) => reader.Value.ParseBool(); | |||||
public static sbyte ParseInt8(this JsonReader reader) => reader.Value.ParseInt8(); | |||||
public static short ParseInt16(this JsonReader reader) => reader.Value.ParseInt16(); | |||||
public static int ParseInt32(this JsonReader reader) => reader.Value.ParseInt32(); | |||||
public static long ParseInt64(this JsonReader reader) => reader.Value.ParseInt64(); | |||||
public static byte ParseUInt8(this JsonReader reader) => reader.Value.ParseUInt8(); | |||||
public static ushort ParseUInt16(this JsonReader reader) => reader.Value.ParseUInt16(); | |||||
public static uint ParseUInt32(this JsonReader reader) => reader.Value.ParseUInt32(); | |||||
public static ulong ParseUInt64(this JsonReader reader) => reader.Value.ParseUInt64(); | |||||
public static char ParseChar(this JsonReader reader) => reader.Value.ParseChar(); | |||||
public static string ParseString(this JsonReader reader) => reader.Value.ParseString(); | |||||
public static float ParseSingle(this JsonReader reader) => reader.Value.ParseSingle(); | |||||
public static double ParseDouble(this JsonReader reader) => reader.Value.ParseDouble(); | |||||
public static decimal ParseDecimal(this JsonReader reader) => reader.Value.ParseDecimal(); | |||||
public static DateTime ParseDateTime(this JsonReader reader) => reader.Value.ParseDateTime(); | |||||
public static DateTimeOffset ParseDateTimeOffset(this JsonReader reader) => reader.Value.ParseDateTimeOffset(); | |||||
public static void Skip(this JsonReader reader) | |||||
{ | |||||
int initialDepth = reader._depth; | |||||
while (reader.Read() && reader._depth > initialDepth) { } | |||||
} | |||||
} | |||||
} |
@@ -1,33 +1,33 @@ | |||||
using System.Collections.Generic; | using System.Collections.Generic; | ||||
using System.Text.Json; | using System.Text.Json; | ||||
namespace Discord.Serialization.Converters | |||||
namespace Discord.Serialization.Json.Converters | |||||
{ | { | ||||
internal class ListPropertyConverter<T> : IPropertyConverter<List<T>> | |||||
internal class ListPropertyConverter<T> : IJsonPropertyConverter<List<T>> | |||||
{ | { | ||||
private readonly IPropertyConverter<T> _innerConverter; | |||||
private readonly IJsonPropertyConverter<T> _innerConverter; | |||||
public ListPropertyConverter(IPropertyConverter<T> innerConverter) | |||||
public ListPropertyConverter(IJsonPropertyConverter<T> innerConverter) | |||||
{ | { | ||||
_innerConverter = innerConverter; | _innerConverter = innerConverter; | ||||
} | } | ||||
public List<T> ReadJson(JsonReader reader, bool read = true) | |||||
public List<T> Read(JsonReader reader, bool read = true) | |||||
{ | { | ||||
if ((read && !reader.Read()) || reader.TokenType != JsonTokenType.StartArray) | if ((read && !reader.Read()) || reader.TokenType != JsonTokenType.StartArray) | ||||
throw new SerializationException("Bad input, expected StartArray"); | throw new SerializationException("Bad input, expected StartArray"); | ||||
var list = new List<T>(); | var list = new List<T>(); | ||||
while (reader.Read() && reader.TokenType != JsonTokenType.EndArray) | while (reader.Read() && reader.TokenType != JsonTokenType.EndArray) | ||||
list.Add(_innerConverter.ReadJson(reader)); | |||||
list.Add(_innerConverter.Read(reader)); | |||||
return list; | return list; | ||||
} | } | ||||
public void WriteJson(JsonWriter writer, List<T> value) | |||||
public void Write(JsonWriter writer, List<T> value) | |||||
{ | { | ||||
writer.WriteArrayStart(); | writer.WriteArrayStart(); | ||||
for (int i = 0; i < value.Count; i++) | for (int i = 0; i < value.Count; i++) | ||||
_innerConverter.WriteJson(writer, value[i]); | |||||
_innerConverter.Write(writer, value[i]); | |||||
writer.WriteArrayEnd(); | writer.WriteArrayEnd(); | ||||
} | } | ||||
} | } |
@@ -0,0 +1,32 @@ | |||||
using System.Text.Json; | |||||
namespace Discord.Serialization.Json.Converters | |||||
{ | |||||
internal class NullablePropertyConverter<T> : IJsonPropertyConverter<T?> | |||||
where T : struct | |||||
{ | |||||
private readonly IJsonPropertyConverter<T> _innerConverter; | |||||
public NullablePropertyConverter(IJsonPropertyConverter<T> innerConverter) | |||||
{ | |||||
_innerConverter = innerConverter; | |||||
} | |||||
public T? Read(JsonReader reader, bool read = true) | |||||
{ | |||||
if (read) | |||||
reader.Read(); | |||||
if (reader.ValueType == JsonValueType.Null) | |||||
return null; | |||||
return _innerConverter.Read(reader, false); | |||||
} | |||||
public void Write(JsonWriter writer, T? value) | |||||
{ | |||||
if (value.HasValue) | |||||
_innerConverter.Write(writer, value.Value); | |||||
else | |||||
writer.WriteNull(); | |||||
} | |||||
} | |||||
} |
@@ -1,10 +1,10 @@ | |||||
using System.Text.Json; | using System.Text.Json; | ||||
namespace Discord.Serialization.Converters | |||||
namespace Discord.Serialization.Json.Converters | |||||
{ | { | ||||
internal class BooleanPropertyConverter : IPropertyConverter<bool> | |||||
internal class BooleanPropertyConverter : IJsonPropertyConverter<bool> | |||||
{ | { | ||||
public bool ReadJson(JsonReader reader, bool read = true) | |||||
public bool Read(JsonReader reader, bool read = true) | |||||
{ | { | ||||
if (read) | if (read) | ||||
reader.Read(); | reader.Read(); | ||||
@@ -15,7 +15,7 @@ namespace Discord.Serialization.Converters | |||||
default: throw new SerializationException("Bad input, expected False or True"); | default: throw new SerializationException("Bad input, expected False or True"); | ||||
} | } | ||||
} | } | ||||
public void WriteJson(JsonWriter writer, bool value) | |||||
public void Write(JsonWriter writer, bool value) | |||||
=> writer.WriteValue(value); | => writer.WriteValue(value); | ||||
} | } | ||||
} | } |
@@ -0,0 +1,33 @@ | |||||
using System; | |||||
using System.Text.Json; | |||||
namespace Discord.Serialization.Json.Converters | |||||
{ | |||||
internal class DateTimePropertyConverter : IJsonPropertyConverter<DateTime> | |||||
{ | |||||
public DateTime Read(JsonReader reader, bool read = true) | |||||
{ | |||||
if (read) | |||||
reader.Read(); | |||||
if (reader.ValueType != JsonValueType.String) | |||||
throw new SerializationException("Bad input, expected String"); | |||||
return reader.ParseDateTime(); | |||||
} | |||||
public void Write(JsonWriter writer, DateTime value) | |||||
=> writer.WriteValue(value); | |||||
} | |||||
internal class DateTimeOffsetPropertyConverter : IJsonPropertyConverter<DateTimeOffset> | |||||
{ | |||||
public DateTimeOffset Read(JsonReader reader, bool read = true) | |||||
{ | |||||
if (read) | |||||
reader.Read(); | |||||
if (reader.ValueType != JsonValueType.String) | |||||
throw new SerializationException("Bad input, expected String"); | |||||
return reader.ParseDateTimeOffset(); | |||||
} | |||||
public void Write(JsonWriter writer, DateTimeOffset value) | |||||
=> writer.WriteValue(value); | |||||
} | |||||
} |
@@ -0,0 +1,12 @@ | |||||
using System.Text.Json; | |||||
namespace Discord.Serialization.Json.Converters | |||||
{ | |||||
internal class EnumPropertyConverter<T> : IJsonPropertyConverter<T> | |||||
{ | |||||
public T Read(JsonReader reader, bool read = true) | |||||
=> throw new System.NotImplementedException(); | |||||
public void Write(JsonWriter writer, T value) | |||||
=> throw new System.NotImplementedException(); | |||||
} | |||||
} |
@@ -0,0 +1,46 @@ | |||||
using System.Text.Json; | |||||
namespace Discord.Serialization.Json.Converters | |||||
{ | |||||
internal class SinglePropertyConverter : IJsonPropertyConverter<float> | |||||
{ | |||||
public float Read(JsonReader reader, bool read = true) | |||||
{ | |||||
if (read) | |||||
reader.Read(); | |||||
if (reader.ValueType != JsonValueType.Number) | |||||
throw new SerializationException("Bad input, expected Number"); | |||||
return reader.ParseSingle(); | |||||
} | |||||
public void Write(JsonWriter writer, float value) | |||||
=> writer.WriteValue(value.ToString()); | |||||
} | |||||
internal class DoublePropertyConverter : IJsonPropertyConverter<double> | |||||
{ | |||||
public double Read(JsonReader reader, bool read = true) | |||||
{ | |||||
if (read) | |||||
reader.Read(); | |||||
if (reader.ValueType != JsonValueType.Number) | |||||
throw new SerializationException("Bad input, expected Number"); | |||||
return reader.ParseDouble(); | |||||
} | |||||
public void Write(JsonWriter writer, double value) | |||||
=> writer.WriteValue(value.ToString()); | |||||
} | |||||
internal class DecimalPropertyConverter : IJsonPropertyConverter<decimal> | |||||
{ | |||||
public decimal Read(JsonReader reader, bool read = true) | |||||
{ | |||||
if (read) | |||||
reader.Read(); | |||||
if (reader.ValueType != JsonValueType.Number) | |||||
throw new SerializationException("Bad input, expected Number"); | |||||
return reader.ParseDecimal(); | |||||
} | |||||
public void Write(JsonWriter writer, decimal value) | |||||
=> writer.WriteValue(value.ToString()); | |||||
} | |||||
} |
@@ -0,0 +1,60 @@ | |||||
using System.Text.Json; | |||||
namespace Discord.Serialization.Json.Converters | |||||
{ | |||||
internal class Int8PropertyConverter : IJsonPropertyConverter<sbyte> | |||||
{ | |||||
public sbyte Read(JsonReader reader, bool read = true) | |||||
{ | |||||
if (read) | |||||
reader.Read(); | |||||
if (reader.ValueType != JsonValueType.Number) | |||||
throw new SerializationException("Bad input, expected Number"); | |||||
return reader.ParseInt8(); | |||||
} | |||||
public void Write(JsonWriter writer, sbyte value) | |||||
=> writer.WriteValue(value); | |||||
} | |||||
internal class Int16PropertyConverter : IJsonPropertyConverter<short> | |||||
{ | |||||
public short Read(JsonReader reader, bool read = true) | |||||
{ | |||||
if (read) | |||||
reader.Read(); | |||||
if (reader.ValueType != JsonValueType.Number) | |||||
throw new SerializationException("Bad input, expected Number"); | |||||
return reader.ParseInt16(); | |||||
} | |||||
public void Write(JsonWriter writer, short value) | |||||
=> writer.WriteValue(value); | |||||
} | |||||
internal class Int32PropertyConverter : IJsonPropertyConverter<int> | |||||
{ | |||||
public int Read(JsonReader reader, bool read = true) | |||||
{ | |||||
if (read) | |||||
reader.Read(); | |||||
if (reader.ValueType != JsonValueType.Number) | |||||
throw new SerializationException("Bad input, expected Number"); | |||||
return reader.ParseInt32(); | |||||
} | |||||
public void Write(JsonWriter writer, int value) | |||||
=> writer.WriteValue(value); | |||||
} | |||||
internal class Int64PropertyConverter : IJsonPropertyConverter<long> | |||||
{ | |||||
public long Read(JsonReader reader, bool read = true) | |||||
{ | |||||
if (read) | |||||
reader.Read(); | |||||
if (reader.ValueType != JsonValueType.String) | |||||
throw new SerializationException("Bad input, expected String"); | |||||
return reader.ParseInt64(); | |||||
} | |||||
public void Write(JsonWriter writer, long value) | |||||
=> writer.WriteValue(value.ToString()); | |||||
} | |||||
} |
@@ -0,0 +1,32 @@ | |||||
using System.Text.Json; | |||||
namespace Discord.Serialization.Json.Converters | |||||
{ | |||||
internal class CharPropertyConverter : IJsonPropertyConverter<char> | |||||
{ | |||||
public char Read(JsonReader reader, bool read = true) | |||||
{ | |||||
if (read) | |||||
reader.Read(); | |||||
if (reader.ValueType != JsonValueType.String) | |||||
throw new SerializationException("Bad input, expected String"); | |||||
return reader.ParseChar(); | |||||
} | |||||
public void Write(JsonWriter writer, char value) | |||||
=> writer.WriteValue(value); | |||||
} | |||||
internal class StringPropertyConverter : IJsonPropertyConverter<string> | |||||
{ | |||||
public string Read(JsonReader reader, bool read = true) | |||||
{ | |||||
if (read) | |||||
reader.Read(); | |||||
if (reader.ValueType != JsonValueType.String) | |||||
throw new SerializationException("Bad input, expected String"); | |||||
return reader.ParseString(); | |||||
} | |||||
public void Write(JsonWriter writer, string value) | |||||
=> writer.WriteValue(value); | |||||
} | |||||
} |
@@ -0,0 +1,60 @@ | |||||
using System.Text.Json; | |||||
namespace Discord.Serialization.Json.Converters | |||||
{ | |||||
internal class UInt8PropertyConverter : IJsonPropertyConverter<byte> | |||||
{ | |||||
public byte Read(JsonReader reader, bool read = true) | |||||
{ | |||||
if (read) | |||||
reader.Read(); | |||||
if (reader.ValueType != JsonValueType.Number) | |||||
throw new SerializationException("Bad input, expected Number"); | |||||
return reader.ParseUInt8(); | |||||
} | |||||
public void Write(JsonWriter writer, byte value) | |||||
=> writer.WriteValue(value); | |||||
} | |||||
internal class UInt16PropertyConverter : IJsonPropertyConverter<ushort> | |||||
{ | |||||
public ushort Read(JsonReader reader, bool read = true) | |||||
{ | |||||
if (read) | |||||
reader.Read(); | |||||
if (reader.ValueType != JsonValueType.Number) | |||||
throw new SerializationException("Bad input, expected Number"); | |||||
return reader.ParseUInt16(); | |||||
} | |||||
public void Write(JsonWriter writer, ushort value) | |||||
=> writer.WriteValue(value); | |||||
} | |||||
internal class UInt32PropertyConverter : IJsonPropertyConverter<uint> | |||||
{ | |||||
public uint Read(JsonReader reader, bool read = true) | |||||
{ | |||||
if (read) | |||||
reader.Read(); | |||||
if (reader.ValueType != JsonValueType.Number) | |||||
throw new SerializationException("Bad input, expected Number"); | |||||
return reader.ParseUInt32(); | |||||
} | |||||
public void Write(JsonWriter writer, uint value) | |||||
=> writer.WriteValue(value); | |||||
} | |||||
internal class UInt64PropertyConverter : IJsonPropertyConverter<ulong> | |||||
{ | |||||
public ulong Read(JsonReader reader, bool read = true) | |||||
{ | |||||
if (read) | |||||
reader.Read(); | |||||
if (reader.ValueType != JsonValueType.String) | |||||
throw new SerializationException("Bad input, expected String"); | |||||
return reader.ParseUInt64(); | |||||
} | |||||
public void Write(JsonWriter writer, ulong value) | |||||
=> writer.WriteValue(value.ToString()); | |||||
} | |||||
} |
@@ -0,0 +1,10 @@ | |||||
using System.Text.Json; | |||||
namespace Discord.Serialization.Json | |||||
{ | |||||
public interface IJsonPropertyConverter<T> | |||||
{ | |||||
T Read(JsonReader reader, bool read = true); | |||||
void Write(JsonWriter writer, T value); | |||||
} | |||||
} |
@@ -0,0 +1,12 @@ | |||||
using System.Text.Json; | |||||
namespace Discord.Serialization | |||||
{ | |||||
internal interface IJsonPropertyMap<TModel> | |||||
{ | |||||
string Key { get; } | |||||
void Write(TModel model, JsonWriter writer); | |||||
void Read(TModel model, JsonReader reader); | |||||
} | |||||
} |
@@ -0,0 +1,102 @@ | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Reflection; | |||||
using System.Text; | |||||
using System.Text.Formatting; | |||||
using System.Text.Json; | |||||
namespace Discord.Serialization.Json | |||||
{ | |||||
public class JsonFormat : SerializationFormat | |||||
{ | |||||
public JsonFormat() | |||||
{ | |||||
AddConverter<bool, Converters.BooleanPropertyConverter>(); | |||||
AddConverter<sbyte, Converters.Int8PropertyConverter>(); | |||||
AddConverter<short, Converters.Int16PropertyConverter>(); | |||||
AddConverter<int, Converters.Int32PropertyConverter>(); | |||||
AddConverter<long, Converters.Int64PropertyConverter>(); | |||||
AddConverter<byte, Converters.UInt8PropertyConverter>(); | |||||
AddConverter<ushort, Converters.UInt16PropertyConverter>(); | |||||
AddConverter<uint, Converters.UInt32PropertyConverter>(); | |||||
AddConverter<ulong, Converters.UInt64PropertyConverter>(); | |||||
AddConverter<float, Converters.SinglePropertyConverter>(); | |||||
AddConverter<double, Converters.DoublePropertyConverter>(); | |||||
AddConverter<decimal, Converters.DecimalPropertyConverter>(); | |||||
AddConverter<char, Converters.CharPropertyConverter>(); | |||||
AddConverter<string, Converters.StringPropertyConverter>(); | |||||
AddConverter<DateTime, Converters.DateTimePropertyConverter>(); | |||||
AddConverter<DateTimeOffset, Converters.DateTimeOffsetPropertyConverter>(); | |||||
AddGenericConverter(typeof(List<>), typeof(Converters.ListPropertyConverter<>)); | |||||
AddGenericConverter(typeof(Nullable<>), typeof(Converters.NullablePropertyConverter<>)); | |||||
//AddEnumConverter<Converters.EnumPropertyConverter>(); | |||||
} | |||||
public void AddConverter<TValue, TConverter>() | |||||
where TConverter : class, IJsonPropertyConverter<TValue> | |||||
=> _converters.Add<TValue, TConverter>(); | |||||
public void AddConverter<TValue, TConverter>(Func<PropertyInfo, bool> condition) | |||||
where TConverter : class, IJsonPropertyConverter<TValue> | |||||
=> _converters.Add<TValue, TConverter>(condition); | |||||
public void AddGenericConverter(Type value, Type converter) | |||||
=> _converters.AddGeneric(value, converter); | |||||
public void AddGenericConverter(Type value, Type converter, Func<PropertyInfo, bool> condition) | |||||
=> _converters.AddGeneric(value, converter); | |||||
protected override PropertyMap CreatePropertyMap<TModel, TValue>(PropertyInfo propInfo) | |||||
{ | |||||
var converter = (IJsonPropertyConverter<TValue>)_converters.Get<TValue>(propInfo); | |||||
return new JsonPropertyMap<TModel, TValue>(propInfo, converter); | |||||
} | |||||
protected internal override TModel Read<TModel>(Serializer serializer, ReadOnlyBuffer<byte> data) | |||||
{ | |||||
var reader = new JsonReader(data.Span, SymbolTable.InvariantUtf8); | |||||
var map = MapModel<TModel>(); | |||||
var model = new TModel(); | |||||
if (!reader.Read() || reader.TokenType != JsonTokenType.StartObject) | |||||
throw new InvalidOperationException("Bad input, expected StartObject"); | |||||
while (reader.Read()) | |||||
{ | |||||
if (reader.TokenType == JsonTokenType.EndObject) | |||||
return model; | |||||
if (reader.TokenType != JsonTokenType.PropertyName) | |||||
throw new InvalidOperationException("Bad input, expected PropertyName"); | |||||
string key = reader.ParseString(); | |||||
if (map.PropertiesByKey.TryGetValue(key, out var property)) | |||||
(property as IJsonPropertyMap<TModel>).Read(model, reader); | |||||
else | |||||
reader.Skip(); //Unknown property, skip | |||||
if (!reader.Read()) | |||||
throw new InvalidOperationException("Bad input, expected Value"); | |||||
} | |||||
throw new InvalidOperationException("Bad input, expected EndObject"); | |||||
} | |||||
protected internal override void Write<TModel>(Serializer serializer, ArrayFormatter stream, TModel model) | |||||
{ | |||||
var writer = new JsonWriter(stream); | |||||
var map = MapModel<TModel>(); | |||||
writer.WriteObjectStart(); | |||||
for (int i = 0; i < map.Properties.Length; i++) | |||||
{ | |||||
var property = map.Properties[i]; | |||||
writer.WriteStartAttribute(property.Key); | |||||
(property as IJsonPropertyMap<TModel>).Write(model, writer); | |||||
} | |||||
writer.WriteObjectEnd(); | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,33 @@ | |||||
using System; | |||||
using System.Reflection; | |||||
using System.Text.Json; | |||||
namespace Discord.Serialization.Json | |||||
{ | |||||
internal class JsonPropertyMap<TModel, TType> : PropertyMap, IJsonPropertyMap<TModel> | |||||
{ | |||||
private readonly IJsonPropertyConverter<TType> _converter; | |||||
private readonly Func<TModel, TType> _getFunc; | |||||
private readonly Action<TModel, TType> _setFunc; | |||||
public JsonPropertyMap(PropertyInfo propInfo, IJsonPropertyConverter<TType> converter) | |||||
: base(propInfo) | |||||
{ | |||||
_converter = converter; | |||||
_getFunc = propInfo.GetMethod.CreateDelegate(typeof(Func<TModel, TType>)) as Func<TModel, TType>; | |||||
_setFunc = propInfo.SetMethod.CreateDelegate(typeof(Action<TModel, TType>)) as Action<TModel, TType>; | |||||
} | |||||
public void Write(TModel model, JsonWriter writer) | |||||
{ | |||||
var value = _getFunc(model); | |||||
_converter.Write(writer, value); | |||||
} | |||||
public void Read(TModel model, JsonReader reader) | |||||
{ | |||||
var value = _converter.Read(reader); | |||||
_setFunc(model, value); | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,18 @@ | |||||
using System.Collections.Generic; | |||||
using System.Linq; | |||||
namespace Discord.Serialization | |||||
{ | |||||
public class ModelMap<TModel> | |||||
where TModel : class, new() | |||||
{ | |||||
public readonly PropertyMap[] Properties; | |||||
public readonly Dictionary<string, PropertyMap> PropertiesByKey; | |||||
public ModelMap(Dictionary<string, PropertyMap> properties) | |||||
{ | |||||
PropertiesByKey = properties; | |||||
Properties = PropertiesByKey.Values.ToArray(); | |||||
} | |||||
} | |||||
} |
@@ -2,7 +2,7 @@ | |||||
namespace Discord.Serialization | namespace Discord.Serialization | ||||
{ | { | ||||
internal class ModelPropertyAttribute : Attribute | |||||
public class ModelPropertyAttribute : Attribute | |||||
{ | { | ||||
public string Key { get; } | public string Key { get; } | ||||
public bool IgnoreNull { get; set; } | public bool IgnoreNull { get; set; } |
@@ -0,0 +1,18 @@ | |||||
using System.Reflection; | |||||
namespace Discord.Serialization | |||||
{ | |||||
public abstract class PropertyMap | |||||
{ | |||||
public string Key { get; } | |||||
public PropertyMap(PropertyInfo propInfo) | |||||
{ | |||||
var jsonProperty = propInfo.GetCustomAttribute<ModelPropertyAttribute>(); | |||||
if (jsonProperty != null) | |||||
Key = jsonProperty.Key; | |||||
else | |||||
Key = propInfo.Name; | |||||
} | |||||
} | |||||
} |
@@ -2,7 +2,7 @@ | |||||
namespace Discord.Serialization | namespace Discord.Serialization | ||||
{ | { | ||||
internal class SerializationException : Exception | |||||
public class SerializationException : Exception | |||||
{ | { | ||||
public SerializationException() | public SerializationException() | ||||
: base("Serialization failed") | : base("Serialization failed") |
@@ -0,0 +1,57 @@ | |||||
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 ModelMap<TModel> MapModel<TModel>() | |||||
where TModel : class, new() | |||||
{ | |||||
return _maps.GetOrAdd(typeof(TModel), _ => | |||||
{ | |||||
var type = typeof(TModel).GetTypeInfo(); | |||||
var properties = new Dictionary<string, PropertyMap>(); | |||||
var propInfos = type.DeclaredProperties.ToArray(); | |||||
for (int i = 0; i < propInfos.Length; i++) | |||||
{ | |||||
var propInfo = propInfos[i]; | |||||
if (!propInfo.CanRead || !propInfo.CanWrite) | |||||
continue; | |||||
var propMap = MapProperty<TModel>(propInfo); | |||||
properties.Add(propMap.Key, 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(); | |||||
} | |||||
} |
@@ -0,0 +1,27 @@ | |||||
using System; | |||||
using System.Text.Formatting; | |||||
namespace Discord.Serialization | |||||
{ | |||||
public 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; | |||||
public Serializer(SerializationFormat format) | |||||
{ | |||||
_format = format; | |||||
} | |||||
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); | |||||
} | |||||
} |