From 9cc7d2deaa2b96b313cd91c97adb53a9f6cc4ff2 Mon Sep 17 00:00:00 2001
From: Quin Lynch <49576606+quinchs@users.noreply.github.com>
Date: Mon, 22 Nov 2021 04:19:31 -0400
Subject: [PATCH] Implement Better Discord Errors (#291)
* Initial error parsing
* Implement better errors
* Add missing error codes
* Add voice disconnect opcodes
* Remove unused class, add summaries to discordjsonerror, and remove public constructor of slash command properties
* Add error code summary
* Update error message summary
* Update src/Discord.Net.Core/DiscordJsonError.cs
Co-authored-by: Jared L <48422312+lhjt@users.noreply.github.com>
* Update src/Discord.Net.WebSocket/API/Voice/VoiceCloseCode.cs
Co-authored-by: Jared L <48422312+lhjt@users.noreply.github.com>
* Fix autocomplete result value
Co-authored-by: Jared L <48422312+lhjt@users.noreply.github.com>
---
src/Discord.Net.Core/DiscordErrorCode.cs | 197 ++++++++++++++++++
src/Discord.Net.Core/DiscordJsonError.cs | 53 +++++
.../Net/ApplicationCommandException.cs | 57 +----
src/Discord.Net.Core/Net/HttpException.cs | 17 +-
.../API/Common/DiscordError.cs | 20 ++
src/Discord.Net.Rest/API/Common/Error.cs | 12 ++
.../API/Common/PropertyErrorDescription.cs | 17 ++
src/Discord.Net.Rest/DiscordRestApiClient.cs | 4 +-
.../Net/Converters/DiscordContractResolver.cs | 8 +-
.../Net/Converters/DiscordErrorConverter.cs | 88 ++++++++
.../Net/Queue/RequestQueueBucket.cs | 15 +-
.../API/Voice/VoiceCloseCode.cs | 63 ++++++
12 files changed, 478 insertions(+), 73 deletions(-)
create mode 100644 src/Discord.Net.Core/DiscordErrorCode.cs
create mode 100644 src/Discord.Net.Core/DiscordJsonError.cs
create mode 100644 src/Discord.Net.Rest/API/Common/DiscordError.cs
create mode 100644 src/Discord.Net.Rest/API/Common/Error.cs
create mode 100644 src/Discord.Net.Rest/API/Common/PropertyErrorDescription.cs
create mode 100644 src/Discord.Net.Rest/Net/Converters/DiscordErrorConverter.cs
create mode 100644 src/Discord.Net.WebSocket/API/Voice/VoiceCloseCode.cs
diff --git a/src/Discord.Net.Core/DiscordErrorCode.cs b/src/Discord.Net.Core/DiscordErrorCode.cs
new file mode 100644
index 000000000..5a5223b93
--- /dev/null
+++ b/src/Discord.Net.Core/DiscordErrorCode.cs
@@ -0,0 +1,197 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Discord
+{
+ ///
+ /// Represents a set of json error codes received by discord.
+ ///
+ public enum DiscordErrorCode
+ {
+ GeneralError = 0,
+
+ #region UnknownXYZ (10XXX)
+ UnknownAccount = 10001,
+ UnknownApplication = 10002,
+ UnknownChannel = 10003,
+ UnknownGuild = 10004,
+ UnknownIntegration = 10005,
+ UnknownInvite = 10006,
+ UnknownMember = 10007,
+ UnknownMessage = 10008,
+ UnknownPermissionOverwrite = 10009,
+ UnknownProvider = 10010,
+ UnknownRole = 10011,
+ UnknownToken = 10012,
+ UnknownUser = 10013,
+ UnknownEmoji = 10014,
+ UnknownWebhook = 10015,
+ UnknownWebhookService = 10016,
+ UnknownSession = 10020,
+ UnknownBan = 10026,
+ UnknownSKU = 10027,
+ UnknownStoreListing = 10028,
+ UnknownEntitlement = 10029,
+ UnknownBuild = 10030,
+ UnknownLobby = 10031,
+ UnknownBranch = 10032,
+ UnknownStoreDirectoryLayout = 10033,
+ UnknownRedistributable = 10036,
+ UnknownGiftCode = 10038,
+ UnknownStream = 10049,
+ UnknownPremiumServerSubscribeCooldown = 10050,
+ UnknownGuildTemplate = 10057,
+ UnknownDiscoverableServerCategory = 10059,
+ UnknownSticker = 10060,
+ UnknownInteraction = 10062,
+ UnknownApplicationCommand = 10063,
+ UnknownApplicationCommandPermissions = 10066,
+ UnknownStageInstance = 10067,
+ UnknownGuildMemberVerificationForm = 10068,
+ UnknownGuildWelcomeScreen = 10069,
+ UnknownGuildScheduledEvent = 10070,
+ UnknownGuildScheduledEventUser = 10071,
+ #endregion
+
+ #region General Actions (20XXX)
+ BotsCannotUse = 20001,
+ OnlyBotsCanUse = 20002,
+ CannotSendExplicitContent = 20009,
+ ApplicationActionUnauthorized = 20012,
+ ActionSlowmode = 20016,
+ OnlyOwnerAction = 20018,
+ AnnouncementEditRatelimit = 20022,
+ ChannelWriteRatelimit = 20028,
+ WordsNotAllowed = 20031,
+ GuildPremiumTooLow = 20035,
+ #endregion
+
+ #region Numeric Limits Reached (30XXX)
+ MaximumGuildsReached = 30001,
+ MaximumFriendsReached = 30002,
+ MaximumPinsReached = 30003,
+ MaximumRecipientsReached = 30004,
+ MaximumGuildRolesReached = 30005,
+ MaximumWebhooksReached = 30007,
+ MaximumEmojisReached = 30008,
+ MaximumReactionsReached = 30010,
+ MaximumGuildChannelsReached = 30013,
+ MaximumAttachmentsReached = 30015,
+ MaximumInvitesReached = 30016,
+ MaximumAnimatedEmojisReached = 30018,
+ MaximumServerMembersReached = 30019,
+ MaximumServerCategoriesReached = 30030,
+ GuildTemplateAlreadyExists = 30031,
+ MaximumThreadMembersReached = 30033,
+ MaximumBansForNonGuildMembersReached = 30035,
+ MaximumBanFetchesReached = 30037,
+ MaximumUncompleteGuildScheduledEvents = 30038,
+ MaximumStickersReached = 30039,
+ MaximumPruneRequestReached = 30040,
+ MaximumGuildWigitsReached = 30042,
+ #endregion
+
+ #region General Request Errors (40XXX)
+ TokenUnauthorized = 40001,
+ InvalidVerification = 40002,
+ OpeningDMTooFast = 40003,
+ RequestEntityTooLarge = 40005,
+ FeatureDisabled = 40006,
+ UserBanned = 40007,
+ TargetUserNotInVoice = 40032,
+ MessageAlreadyCrossposted = 40033,
+ ApplicationNameAlreadyExists = 40041,
+ #endregion
+
+ #region Action Preconditions/Checks (50XXX)
+ MissingPermissions = 50001,
+ InvalidAccountType = 50002,
+ CannotExecuteForDM = 50003,
+ GuildWigitDisabled = 50004,
+ CannotEditOtherUsersMessage = 50005,
+ CannotSendEmptyMessage = 50006,
+ CannotSendMessageToUser = 50007,
+ CannotSendMessageToVoiceChannel = 50008,
+ ChannelVerificationTooHight = 50009,
+ OAuth2ApplicationDoesntHaveBot = 50010,
+ OAuth2ApplicationLimitReached = 50011,
+ InvalidOAuth2State = 50012,
+ InsufficientPermissions = 50013,
+ InvalidAuthenticationToken = 50014,
+ NoteTooLong = 50015,
+ ProvidedMessageDeleteCountOutOfBounds = 50016,
+ InvalidPinChannel = 50019,
+ InvalidInvite = 50020,
+ CannotExecuteOnSystemMessage = 50021,
+ CannotExecuteOnChannelType = 50024,
+ InvalidOAuth2Token = 50025,
+ MissingOAuth2Scope = 50026,
+ InvalidWebhookToken = 50027,
+ InvalidRole = 50028,
+ InvalidRecipients = 50033,
+ BulkDeleteMessageTooOld = 50034,
+ InvalidFormBody = 50035,
+ InviteAcceptedForGuildThatBotIsntIn = 50036,
+ InvalidAPIVersion = 50041,
+ FileUploadTooBig = 50045,
+ InvalidFileUpload = 50046,
+ CannotSelfRedeemGift = 50054,
+ PaymentSourceRequiredForGift = 50070,
+ CannotDeleteRequiredCommunityChannel = 50074,
+ InvalidSticker = 50081,
+ CannotExecuteOnArchivedThread = 50083,
+ InvalidThreadNotificationSettings = 50084,
+ BeforeValueEarlierThanThreadCreation = 50085,
+ ServerLocaleUnavailable = 50095,
+ ServerRequiresMonetization = 50097,
+ ServerRequiresBoosts = 50101,
+
+ #endregion
+
+ #region 2FA (60XXX)
+ Requires2FA = 60003,
+ #endregion
+
+ #region User Searches (80XXX)
+ NoUsersWithTag = 80004,
+ #endregion
+
+ #region Reactions (90XXX)
+ ReactionBlocked = 90001,
+ #endregion
+
+ #region API Status (130XXX)
+ APIOverloaded = 130000,
+ #endregion
+
+ #region Stage Errors (150XXX)
+ StageAlreadyOpened = 150006,
+ #endregion
+
+ #region Reply and Thread Errors (160XXX)
+ CannotReplyWithoutReadMessageHistory = 160002,
+ MessageAlreadyContainsThread = 160004,
+ ThreadIsLocked = 160005,
+ MaximumActiveThreadsReached = 160006,
+ MaximumAnnouncementThreadsReached = 160007,
+ #endregion
+
+ #region Sticker Uploads (170XXX)
+ InvalidJSONLottie = 170001,
+ LottieCantContainRasters = 170002,
+ StickerMaximumFramerateExceeded = 170003,
+ StickerMaximumFrameCountExceeded = 170004,
+ LottieMaximumDimentionsExceeded = 170005,
+ StickerFramerateBoundsExceeed = 170006,
+ StickerAnimationDurationTooLong = 170007,
+ #endregion
+
+ #region Guild Scheduled Events
+ CannotUpdateFinishedEvent = 180000,
+ FailedStageCreation = 180002,
+ #endregion
+ }
+}
diff --git a/src/Discord.Net.Core/DiscordJsonError.cs b/src/Discord.Net.Core/DiscordJsonError.cs
new file mode 100644
index 000000000..fdf82ea0c
--- /dev/null
+++ b/src/Discord.Net.Core/DiscordJsonError.cs
@@ -0,0 +1,53 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.Immutable;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Discord
+{
+ ///
+ /// Represents a generic parsed json error received from discord after performing a rest request.
+ ///
+ public struct DiscordJsonError
+ {
+ ///
+ /// Gets the json path of the error.
+ ///
+ public string Path { get; }
+
+ ///
+ /// Gets a collection of errors associated with the specific property at the path.
+ ///
+ public IReadOnlyCollection Errors { get; }
+
+ internal DiscordJsonError(string path, DiscordError[] errors)
+ {
+ Path = path;
+ Errors = errors.ToImmutableArray();
+ }
+ }
+
+ ///
+ /// Represents an error with a property.
+ ///
+ public struct DiscordError
+ {
+ ///
+ /// Gets the code of the error.
+ ///
+ public string Code { get; }
+
+ ///
+ /// Gets the message describing what went wrong.
+ ///
+ public string Message { get; }
+
+ internal DiscordError(string code, string message)
+ {
+ Code = code;
+ Message = message;
+ }
+ }
+}
diff --git a/src/Discord.Net.Core/Net/ApplicationCommandException.cs b/src/Discord.Net.Core/Net/ApplicationCommandException.cs
index 62c80b388..4b4890d12 100644
--- a/src/Discord.Net.Core/Net/ApplicationCommandException.cs
+++ b/src/Discord.Net.Core/Net/ApplicationCommandException.cs
@@ -1,60 +1,15 @@
using System;
+using System.Linq;
namespace Discord.Net
{
- public class ApplicationCommandException : Exception
+ [Obsolete("Please use HttpException instead of this. Will be removed in next major version.", false)]
+ public class ApplicationCommandException : HttpException
{
- ///
- /// Gets the JSON error code returned by Discord.
- ///
- ///
- /// A
- /// JSON error code
- /// from Discord, or null if none.
- ///
- public int? DiscordCode { get; }
-
- ///
- /// Gets the reason of the exception.
- ///
- public string Reason { get; }
-
- ///
- /// Gets the request object used to send the request.
- ///
- public IRequest Request { get; }
-
- ///
- /// The error object returned from discord.
- ///
- ///
- /// Note: This object can be null if discord didn't provide it.
- ///
- public object Error { get; }
-
- ///
- /// The request json used to create the application command. This is useful for checking your commands for any format errors.
- ///
- public string RequestJson { get; }
-
- ///
- /// The underlying that caused this exception to be thrown.
- ///
- public HttpException InnerHttpException { get; }
- ///
- /// Initializes a new instance of the class.
- ///
- ///
- ///
- public ApplicationCommandException(string requestJson, HttpException httpError)
- : base("The application command failed to be created!", httpError)
+ public ApplicationCommandException(HttpException httpError)
+ : base(httpError.HttpCode, httpError.Request, httpError.DiscordCode, httpError.Reason, httpError.Errors.ToArray())
{
- Request = httpError.Request;
- DiscordCode = httpError.DiscordCode;
- Reason = httpError.Reason;
- Error = httpError.Error;
- RequestJson = requestJson;
- InnerHttpException = httpError;
+
}
}
}
diff --git a/src/Discord.Net.Core/Net/HttpException.cs b/src/Discord.Net.Core/Net/HttpException.cs
index 568562b61..07551f0e7 100644
--- a/src/Discord.Net.Core/Net/HttpException.cs
+++ b/src/Discord.Net.Core/Net/HttpException.cs
@@ -1,4 +1,6 @@
using System;
+using System.Collections.Generic;
+using System.Collections.Immutable;
using System.Net;
namespace Discord.Net
@@ -25,7 +27,7 @@ namespace Discord.Net
/// JSON error code
/// from Discord, or null if none.
///
- public int? DiscordCode { get; }
+ public DiscordErrorCode? DiscordCode { get; }
///
/// Gets the reason of the exception.
///
@@ -35,12 +37,9 @@ namespace Discord.Net
///
public IRequest Request { get; }
///
- /// The error object returned from discord.
+ /// Gets a collection of json errors describing what went wrong with the request.
///
- ///
- /// Note: This object can be null if discord didn't provide it.
- ///
- public object Error { get; }
+ public IReadOnlyCollection Errors { get; }
///
/// Initializes a new instance of the class.
@@ -49,14 +48,14 @@ namespace Discord.Net
/// The request that was sent prior to the exception.
/// The Discord status code returned.
/// The reason behind the exception.
- public HttpException(HttpStatusCode httpCode, IRequest request, int? discordCode = null, string reason = null, object errors = null)
- : base(CreateMessage(httpCode, discordCode, reason))
+ public HttpException(HttpStatusCode httpCode, IRequest request, DiscordErrorCode? discordCode = null, string reason = null, DiscordJsonError[] errors = null)
+ : base(CreateMessage(httpCode, (int?)discordCode, reason))
{
HttpCode = httpCode;
Request = request;
DiscordCode = discordCode;
Reason = reason;
- Error = errors;
+ Errors = errors?.ToImmutableArray() ?? ImmutableArray.Empty;
}
private static string CreateMessage(HttpStatusCode httpCode, int? discordCode = null, string reason = null)
diff --git a/src/Discord.Net.Rest/API/Common/DiscordError.cs b/src/Discord.Net.Rest/API/Common/DiscordError.cs
new file mode 100644
index 000000000..ac1e5e13d
--- /dev/null
+++ b/src/Discord.Net.Rest/API/Common/DiscordError.cs
@@ -0,0 +1,20 @@
+using Newtonsoft.Json;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Discord.API
+{
+ [JsonConverter(typeof(Discord.Net.Converters.DiscordErrorConverter))]
+ internal class DiscordError
+ {
+ [JsonProperty("message")]
+ public string Message { get; set; }
+ [JsonProperty("code")]
+ public DiscordErrorCode Code { get; set; }
+ [JsonProperty("errors")]
+ public Optional Errors { get; set; }
+ }
+}
diff --git a/src/Discord.Net.Rest/API/Common/Error.cs b/src/Discord.Net.Rest/API/Common/Error.cs
new file mode 100644
index 000000000..a2b1777a3
--- /dev/null
+++ b/src/Discord.Net.Rest/API/Common/Error.cs
@@ -0,0 +1,12 @@
+using Newtonsoft.Json;
+
+namespace Discord.API
+{
+ internal class Error
+ {
+ [JsonProperty("code")]
+ public string Code { get; set; }
+ [JsonProperty("message")]
+ public string Message { get; set; }
+ }
+}
diff --git a/src/Discord.Net.Rest/API/Common/PropertyErrorDescription.cs b/src/Discord.Net.Rest/API/Common/PropertyErrorDescription.cs
new file mode 100644
index 000000000..145288e5d
--- /dev/null
+++ b/src/Discord.Net.Rest/API/Common/PropertyErrorDescription.cs
@@ -0,0 +1,17 @@
+using Newtonsoft.Json;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Discord.API
+{
+ internal class ErrorDetails
+ {
+ [JsonProperty("name")]
+ public Optional Name { get; set; }
+ [JsonProperty("errors")]
+ public Error[] Errors { get; set; }
+ }
+}
diff --git a/src/Discord.Net.Rest/DiscordRestApiClient.cs b/src/Discord.Net.Rest/DiscordRestApiClient.cs
index 262da65be..af6e030cb 100644
--- a/src/Discord.Net.Rest/DiscordRestApiClient.cs
+++ b/src/Discord.Net.Rest/DiscordRestApiClient.cs
@@ -2235,7 +2235,7 @@ namespace Discord.API
if (x.HttpCode == HttpStatusCode.BadRequest)
{
var json = (x.Request as JsonRestRequest).Json;
- throw new ApplicationCommandException(json, x);
+ throw new ApplicationCommandException(x);
}
}
@@ -2249,7 +2249,7 @@ namespace Discord.API
if (x.HttpCode == HttpStatusCode.BadRequest)
{
var json = (x.Request as JsonRestRequest).Json;
- throw new ApplicationCommandException(json, x);
+ throw new ApplicationCommandException(x);
}
throw;
diff --git a/src/Discord.Net.Rest/Net/Converters/DiscordContractResolver.cs b/src/Discord.Net.Rest/Net/Converters/DiscordContractResolver.cs
index 6fe44bf4e..91ba22460 100644
--- a/src/Discord.Net.Rest/Net/Converters/DiscordContractResolver.cs
+++ b/src/Discord.Net.Rest/Net/Converters/DiscordContractResolver.cs
@@ -12,8 +12,8 @@ namespace Discord.Net.Converters
{
#region DiscordContractResolver
private static readonly TypeInfo _ienumerable = typeof(IEnumerable).GetTypeInfo();
- private static readonly MethodInfo _shouldSerialize = typeof(DiscordContractResolver).GetTypeInfo().GetDeclaredMethod("ShouldSerialize");
-
+ private static readonly MethodInfo _shouldSerialize = typeof(DiscordContractResolver).GetTypeInfo().GetDeclaredMethod("ShouldSerialize");
+
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
var property = base.CreateProperty(member, memberSerialization);
@@ -58,7 +58,7 @@ namespace Discord.Net.Converters
else if (genericType == typeof(EntityOrId<>))
return MakeGenericConverter(property, propInfo, typeof(UInt64EntityOrIdConverter<>), type.GenericTypeArguments[0], depth);
}
-#endregion
+ #endregion
#region Primitives
bool hasInt53 = propInfo.GetCustomAttribute() != null;
@@ -87,6 +87,8 @@ namespace Discord.Net.Converters
return MessageComponentConverter.Instance;
if (type == typeof(API.Interaction))
return InteractionConverter.Instance;
+ if (type == typeof(API.DiscordError))
+ return DiscordErrorConverter.Instance;
if (type == typeof(GuildFeatures))
return GuildFeaturesConverter.Instance;
diff --git a/src/Discord.Net.Rest/Net/Converters/DiscordErrorConverter.cs b/src/Discord.Net.Rest/Net/Converters/DiscordErrorConverter.cs
new file mode 100644
index 000000000..772ddc6b2
--- /dev/null
+++ b/src/Discord.Net.Rest/Net/Converters/DiscordErrorConverter.cs
@@ -0,0 +1,88 @@
+using Discord.API;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Discord.Net.Converters
+{
+ internal class DiscordErrorConverter : JsonConverter
+ {
+ public static DiscordErrorConverter Instance
+ => new DiscordErrorConverter();
+
+ public override bool CanConvert(Type objectType) => objectType == typeof(DiscordError);
+
+ public override bool CanRead => true;
+ public override bool CanWrite => false;
+
+ public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
+ {
+ var obj = JObject.Load(reader);
+ var err = new API.DiscordError();
+
+
+ var result = obj.GetValue("errors", StringComparison.OrdinalIgnoreCase);
+ result?.Parent.Remove();
+
+ // Populate the remaining properties.
+ using (var subReader = obj.CreateReader())
+ {
+ serializer.Populate(subReader, err);
+ }
+
+ if (result != null)
+ {
+ var innerReader = result.CreateReader();
+
+ var errors = ReadErrors(innerReader);
+ err.Errors = errors.ToArray();
+ }
+
+ return err;
+ }
+
+ private List ReadErrors(JsonReader reader, string path = "")
+ {
+ List errs = new List();
+ var obj = JObject.Load(reader);
+ var props = obj.Properties();
+ foreach (var prop in props)
+ {
+ if (prop.Name == "_errors" && path == "") // root level error
+ {
+ errs.Add(new ErrorDetails()
+ {
+ Name = Optional.Unspecified,
+ Errors = prop.Value.ToObject()
+ });
+ }
+ else if (prop.Name == "_errors") // path errors (not root level)
+ {
+ errs.Add(new ErrorDetails()
+ {
+ Name = path,
+ Errors = prop.Value.ToObject()
+ });
+ }
+ else if(int.TryParse(prop.Name, out var i)) // array value
+ {
+ var r = prop.Value.CreateReader();
+ errs.AddRange(ReadErrors(r, path + $"[{i}]"));
+ }
+ else // property name
+ {
+ var r = prop.Value.CreateReader();
+ errs.AddRange(ReadErrors(r, path + $"{(path != "" ? "." : "")}{prop.Name[0].ToString().ToUpper() + new string(prop.Name.Skip(1).ToArray())}"));
+ }
+ }
+
+ return errs;
+ }
+
+ public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) => throw new NotImplementedException();
+ }
+}
diff --git a/src/Discord.Net.Rest/Net/Queue/RequestQueueBucket.cs b/src/Discord.Net.Rest/Net/Queue/RequestQueueBucket.cs
index 52debd87f..4fe7ee2c4 100644
--- a/src/Discord.Net.Rest/Net/Queue/RequestQueueBucket.cs
+++ b/src/Discord.Net.Rest/Net/Queue/RequestQueueBucket.cs
@@ -1,3 +1,4 @@
+using Discord.API;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
@@ -5,6 +6,7 @@ using System;
using System.Diagnostics;
#endif
using System.IO;
+using System.Linq;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
@@ -99,9 +101,7 @@ namespace Discord.Net.Queue
continue; //Retry
default:
- int? code = null;
- string reason = null;
- object errors = null;
+ API.DiscordError error = null;
if (response.Stream != null)
{
try
@@ -109,15 +109,14 @@ namespace Discord.Net.Queue
using (var reader = new StreamReader(response.Stream))
using (var jsonReader = new JsonTextReader(reader))
{
- var json = JToken.Load(jsonReader);
- try { code = json.Value("code"); } catch { };
- try { reason = json.Value("message"); } catch { };
- try { errors = json.Value