mirror of
https://github.com/JKorf/CryptoExchange.Net
synced 2025-08-31 12:42:00 +00:00
Error handling update
This commit is contained in:
parent
40977ebdbe
commit
3e365f83c9
@ -285,7 +285,7 @@ namespace CryptoExchange.Net.Converters.Protobuf
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new CallResult<object>(new DeserializeError(ex.Message));
|
||||
return new CallResult<object>(new DeserializeError("Protobuf deserialization failed: " + ex.Message, ex));
|
||||
}
|
||||
}
|
||||
|
||||
@ -320,7 +320,7 @@ namespace CryptoExchange.Net.Converters.Protobuf
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
return new CallResult<T>(new DeserializeError(ex.ToLogString()));
|
||||
return new CallResult<T>(new DeserializeError("Protobuf deserialization failed: " + ex.Message, ex));
|
||||
}
|
||||
}
|
||||
|
||||
@ -354,7 +354,7 @@ namespace CryptoExchange.Net.Converters.Protobuf
|
||||
{
|
||||
// Not a json message
|
||||
IsValid = false;
|
||||
return Task.FromResult(new CallResult(new DeserializeError("ProtoBufError: " + ex.Message, ex)));
|
||||
return Task.FromResult(new CallResult(new DeserializeError("Protobuf deserialization failed: " + ex.Message, ex)));
|
||||
}
|
||||
}
|
||||
|
||||
@ -446,7 +446,7 @@ namespace CryptoExchange.Net.Converters.Protobuf
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new CallResult<object>(new DeserializeError(ex.ToLogString()));
|
||||
return new CallResult<object>(new DeserializeError("Protobuf deserialization failed: " + ex.Message, ex));
|
||||
}
|
||||
}
|
||||
|
||||
@ -485,7 +485,7 @@ namespace CryptoExchange.Net.Converters.Protobuf
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new CallResult<T>(new DeserializeError(ex.Message));
|
||||
return new CallResult<T>(new DeserializeError("Protobuf deserialization failed: " + ex.Message, ex));
|
||||
}
|
||||
}
|
||||
|
||||
@ -504,7 +504,7 @@ namespace CryptoExchange.Net.Converters.Protobuf
|
||||
{
|
||||
// Not a json message
|
||||
IsValid = false;
|
||||
return new CallResult(new DeserializeError("ProtobufError: " + ex.Message, ex));
|
||||
return new CallResult(new DeserializeError("Protobuf deserialization failed: " + ex.Message, ex));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -41,7 +41,9 @@
|
||||
<DocumentationFile>CryptoExchange.Net.Protobuf.xml</DocumentationFile>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="CryptoExchange.Net" Version="9.4.0" />
|
||||
<PackageReference Include="protobuf-net" Version="3.2.56" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\CryptoExchange.Net\CryptoExchange.Net.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
@ -1,4 +1,5 @@
|
||||
using CryptoExchange.Net.Objects;
|
||||
using CryptoExchange.Net.Objects.Errors;
|
||||
using NUnit.Framework;
|
||||
using NUnit.Framework.Legacy;
|
||||
using System;
|
||||
@ -16,9 +17,9 @@ namespace CryptoExchange.Net.UnitTests
|
||||
[Test]
|
||||
public void TestBasicErrorCallResult()
|
||||
{
|
||||
var result = new CallResult(new ServerError("TestError"));
|
||||
var result = new CallResult(new ServerError("TestError", ErrorInfo.Unknown));
|
||||
|
||||
ClassicAssert.AreSame(result.Error.Message, "TestError");
|
||||
ClassicAssert.AreSame(result.Error.ErrorCode, "TestError");
|
||||
ClassicAssert.IsFalse(result);
|
||||
ClassicAssert.IsFalse(result.Success);
|
||||
}
|
||||
@ -36,9 +37,9 @@ namespace CryptoExchange.Net.UnitTests
|
||||
[Test]
|
||||
public void TestCallResultError()
|
||||
{
|
||||
var result = new CallResult<object>(new ServerError("TestError"));
|
||||
var result = new CallResult<object>(new ServerError("TestError", ErrorInfo.Unknown));
|
||||
|
||||
ClassicAssert.AreSame(result.Error.Message, "TestError");
|
||||
ClassicAssert.AreSame(result.Error.ErrorCode, "TestError");
|
||||
ClassicAssert.IsNull(result.Data);
|
||||
ClassicAssert.IsFalse(result);
|
||||
ClassicAssert.IsFalse(result.Success);
|
||||
@ -71,11 +72,11 @@ namespace CryptoExchange.Net.UnitTests
|
||||
[Test]
|
||||
public void TestCallResultErrorAs()
|
||||
{
|
||||
var result = new CallResult<TestObjectResult>(new ServerError("TestError"));
|
||||
var result = new CallResult<TestObjectResult>(new ServerError("TestError", ErrorInfo.Unknown));
|
||||
var asResult = result.As<TestObject2>(default);
|
||||
|
||||
ClassicAssert.IsNotNull(asResult.Error);
|
||||
ClassicAssert.AreSame(asResult.Error.Message, "TestError");
|
||||
ClassicAssert.AreSame(asResult.Error.ErrorCode, "TestError");
|
||||
ClassicAssert.IsNull(asResult.Data);
|
||||
ClassicAssert.IsFalse(asResult);
|
||||
ClassicAssert.IsFalse(asResult.Success);
|
||||
@ -84,11 +85,11 @@ namespace CryptoExchange.Net.UnitTests
|
||||
[Test]
|
||||
public void TestCallResultErrorAsError()
|
||||
{
|
||||
var result = new CallResult<TestObjectResult>(new ServerError("TestError"));
|
||||
var asResult = result.AsError<TestObject2>(new ServerError("TestError2"));
|
||||
var result = new CallResult<TestObjectResult>(new ServerError("TestError", ErrorInfo.Unknown));
|
||||
var asResult = result.AsError<TestObject2>(new ServerError("TestError2", ErrorInfo.Unknown));
|
||||
|
||||
ClassicAssert.IsNotNull(asResult.Error);
|
||||
ClassicAssert.AreSame(asResult.Error.Message, "TestError2");
|
||||
ClassicAssert.AreSame(asResult.Error.ErrorCode, "TestError2");
|
||||
ClassicAssert.IsNull(asResult.Data);
|
||||
ClassicAssert.IsFalse(asResult);
|
||||
ClassicAssert.IsFalse(asResult.Success);
|
||||
@ -97,11 +98,11 @@ namespace CryptoExchange.Net.UnitTests
|
||||
[Test]
|
||||
public void TestWebCallResultErrorAsError()
|
||||
{
|
||||
var result = new WebCallResult<TestObjectResult>(new ServerError("TestError"));
|
||||
var asResult = result.AsError<TestObject2>(new ServerError("TestError2"));
|
||||
var result = new WebCallResult<TestObjectResult>(new ServerError("TestError", ErrorInfo.Unknown));
|
||||
var asResult = result.AsError<TestObject2>(new ServerError("TestError2", ErrorInfo.Unknown));
|
||||
|
||||
ClassicAssert.IsNotNull(asResult.Error);
|
||||
ClassicAssert.AreSame(asResult.Error.Message, "TestError2");
|
||||
ClassicAssert.AreSame(asResult.Error.ErrorCode, "TestError2");
|
||||
ClassicAssert.IsNull(asResult.Data);
|
||||
ClassicAssert.IsFalse(asResult);
|
||||
ClassicAssert.IsFalse(asResult.Success);
|
||||
@ -124,10 +125,10 @@ namespace CryptoExchange.Net.UnitTests
|
||||
ResultDataSource.Server,
|
||||
new TestObjectResult(),
|
||||
null);
|
||||
var asResult = result.AsError<TestObject2>(new ServerError("TestError2"));
|
||||
var asResult = result.AsError<TestObject2>(new ServerError("TestError2", ErrorInfo.Unknown));
|
||||
|
||||
ClassicAssert.IsNotNull(asResult.Error);
|
||||
Assert.That(asResult.Error.Message == "TestError2");
|
||||
Assert.That(asResult.Error.ErrorCode == "TestError2");
|
||||
Assert.That(asResult.ResponseStatusCode == System.Net.HttpStatusCode.OK);
|
||||
Assert.That(asResult.ResponseTime == TimeSpan.FromSeconds(1));
|
||||
Assert.That(asResult.RequestUrl == "https://test.com/api");
|
||||
|
@ -96,7 +96,7 @@ namespace CryptoExchange.Net.UnitTests
|
||||
ClassicAssert.IsFalse(result.Success);
|
||||
Assert.That(result.Error != null);
|
||||
Assert.That(result.Error is ServerError);
|
||||
Assert.That(result.Error.Code == 123);
|
||||
Assert.That(result.Error.ErrorCode == "123");
|
||||
Assert.That(result.Error.Message == "Invalid request");
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
using CryptoExchange.Net.Objects;
|
||||
using CryptoExchange.Net.Objects.Errors;
|
||||
using CryptoExchange.Net.Objects.Sockets;
|
||||
using CryptoExchange.Net.Sockets;
|
||||
using System;
|
||||
@ -40,7 +41,7 @@ namespace CryptoExchange.Net.UnitTests.TestImplementations.Sockets
|
||||
{
|
||||
if (!message.Data.Status.Equals("confirmed", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return new CallResult<SubResponse>(new ServerError(message.Data.Status));
|
||||
return new CallResult<SubResponse>(new ServerError(ErrorInfo.Unknown with { Message = message.Data.Status }));
|
||||
}
|
||||
|
||||
return message.ToCallResult();
|
||||
|
@ -10,6 +10,7 @@ using CryptoExchange.Net.Clients;
|
||||
using CryptoExchange.Net.Converters.SystemTextJson;
|
||||
using CryptoExchange.Net.Interfaces;
|
||||
using CryptoExchange.Net.Objects;
|
||||
using CryptoExchange.Net.Objects.Errors;
|
||||
using CryptoExchange.Net.Objects.Options;
|
||||
using CryptoExchange.Net.SharedApis;
|
||||
using CryptoExchange.Net.UnitTests.TestImplementations;
|
||||
@ -55,7 +56,7 @@ namespace CryptoExchange.Net.UnitTests
|
||||
var accessor = CreateAccessor();
|
||||
var valid = accessor.Read(stream, true).Result;
|
||||
if (!valid)
|
||||
return new CallResult<T>(new ServerError(data));
|
||||
return new CallResult<T>(new ServerError(ErrorInfo.Unknown with { Message = data }));
|
||||
|
||||
var deserializeResult = accessor.Deserialize<T>();
|
||||
return deserializeResult;
|
||||
|
@ -18,6 +18,7 @@ using Microsoft.Extensions.Options;
|
||||
using System.Linq;
|
||||
using CryptoExchange.Net.Converters.SystemTextJson;
|
||||
using System.Text.Json.Serialization;
|
||||
using CryptoExchange.Net.Objects.Errors;
|
||||
|
||||
namespace CryptoExchange.Net.UnitTests.TestImplementations
|
||||
{
|
||||
@ -197,7 +198,7 @@ namespace CryptoExchange.Net.UnitTests.TestImplementations
|
||||
{
|
||||
var errorData = accessor.Deserialize<TestError>();
|
||||
|
||||
return new ServerError(errorData.Data.ErrorCode, errorData.Data.ErrorMessage);
|
||||
return new ServerError(errorData.Data.ErrorCode, GetErrorInfo(errorData.Data.ErrorCode, errorData.Data.ErrorMessage));
|
||||
}
|
||||
|
||||
public override TimeSpan? GetTimeOffset()
|
||||
|
@ -1,7 +1,9 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using CryptoExchange.Net.Authentication;
|
||||
using CryptoExchange.Net.Interfaces;
|
||||
using CryptoExchange.Net.Objects;
|
||||
using CryptoExchange.Net.Objects.Errors;
|
||||
using CryptoExchange.Net.Objects.Options;
|
||||
using CryptoExchange.Net.SharedApis;
|
||||
using Microsoft.Extensions.Logging;
|
||||
@ -54,6 +56,11 @@ namespace CryptoExchange.Net.Clients
|
||||
/// </summary>
|
||||
public ExchangeOptions ClientOptions { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Mapping of a response code to known error types
|
||||
/// </summary>
|
||||
protected internal virtual ErrorMapping ErrorMapping { get; } = new ErrorMapping([]);
|
||||
|
||||
/// <summary>
|
||||
/// ctor
|
||||
/// </summary>
|
||||
@ -87,6 +94,16 @@ namespace CryptoExchange.Net.Clients
|
||||
/// <inheritdoc />
|
||||
public abstract string FormatSymbol(string baseAsset, string quoteAsset, TradingMode tradingMode, DateTime? deliverDate = null);
|
||||
|
||||
/// <summary>
|
||||
/// Get error info for a response code
|
||||
/// </summary>
|
||||
public ErrorInfo GetErrorInfo(int code, string? message = null) => GetErrorInfo(code.ToString(), message);
|
||||
|
||||
/// <summary>
|
||||
/// Get error info for a response code
|
||||
/// </summary>
|
||||
public ErrorInfo GetErrorInfo(string code, string? message = null) => ErrorMapping.GetErrorInfo(code.ToString(), message);
|
||||
|
||||
/// <inheritdoc />
|
||||
public void SetApiCredentials<T>(T credentials) where T : ApiCredentials
|
||||
{
|
||||
|
@ -11,6 +11,7 @@ using CryptoExchange.Net.Caching;
|
||||
using CryptoExchange.Net.Interfaces;
|
||||
using CryptoExchange.Net.Logging.Extensions;
|
||||
using CryptoExchange.Net.Objects;
|
||||
using CryptoExchange.Net.Objects.Errors;
|
||||
using CryptoExchange.Net.Objects.Options;
|
||||
using CryptoExchange.Net.RateLimiting;
|
||||
using CryptoExchange.Net.RateLimiting.Interfaces;
|
||||
@ -485,8 +486,10 @@ namespace CryptoExchange.Net.Clients
|
||||
error = ParseErrorResponse((int)response.StatusCode, response.ResponseHeaders, accessor, readResult.Error?.Exception);
|
||||
}
|
||||
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
if (error.Code == null || error.Code == 0)
|
||||
error.Code = (int)response.StatusCode;
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
|
||||
return new WebCallResult<T>(response.StatusCode, response.ResponseHeaders, sw.Elapsed, responseLength, OutputOriginalData ? accessor.GetOriginalString() : null, request.RequestId, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), ResultDataSource.Server, default, error!);
|
||||
}
|
||||
@ -499,8 +502,7 @@ namespace CryptoExchange.Net.Clients
|
||||
if (!valid)
|
||||
{
|
||||
// Invalid json
|
||||
var error = new DeserializeError("Failed to parse response: " + valid.Error!.Message, valid.Error.Exception);
|
||||
return new WebCallResult<T>(response.StatusCode, response.ResponseHeaders, sw.Elapsed, responseLength, OutputOriginalData ? accessor.GetOriginalString() : null, request.RequestId, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), ResultDataSource.Server, default, error);
|
||||
return new WebCallResult<T>(response.StatusCode, response.ResponseHeaders, sw.Elapsed, responseLength, OutputOriginalData ? accessor.GetOriginalString() : null, request.RequestId, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), ResultDataSource.Server, default, valid.Error);
|
||||
}
|
||||
|
||||
// Json response received
|
||||
@ -526,7 +528,8 @@ namespace CryptoExchange.Net.Clients
|
||||
catch (HttpRequestException requestException)
|
||||
{
|
||||
// Request exception, can't reach server for instance
|
||||
return new WebCallResult<T>(null, null, sw.Elapsed, null, null, request.RequestId, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), ResultDataSource.Server, default, new WebError(requestException.Message, exception: requestException));
|
||||
var error = new WebError(requestException.Message, requestException);
|
||||
return new WebCallResult<T>(null, null, sw.Elapsed, null, null, request.RequestId, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), ResultDataSource.Server, default, error);
|
||||
}
|
||||
catch (OperationCanceledException canceledException)
|
||||
{
|
||||
@ -538,7 +541,9 @@ namespace CryptoExchange.Net.Clients
|
||||
else
|
||||
{
|
||||
// Request timed out
|
||||
return new WebCallResult<T>(null, null, sw.Elapsed, null, null, request.RequestId, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), ResultDataSource.Server, default, new WebError($"Request timed out", exception: canceledException));
|
||||
var error = new WebError($"Request timed out", exception: canceledException);
|
||||
error.ErrorType = ErrorType.Timeout;
|
||||
return new WebCallResult<T>(null, null, sw.Elapsed, null, null, request.RequestId, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), ResultDataSource.Server, default, error);
|
||||
}
|
||||
}
|
||||
finally
|
||||
@ -633,7 +638,7 @@ namespace CryptoExchange.Net.Clients
|
||||
/// <returns></returns>
|
||||
protected virtual Error ParseErrorResponse(int httpStatusCode, KeyValuePair<string, string[]>[] responseHeaders, IMessageAccessor accessor, Exception? exception)
|
||||
{
|
||||
return new ServerError(null, "Unknown request error", exception);
|
||||
return new ServerError(ErrorInfo.Unknown, exception);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -1,6 +1,7 @@
|
||||
using CryptoExchange.Net.Interfaces;
|
||||
using CryptoExchange.Net.Logging.Extensions;
|
||||
using CryptoExchange.Net.Objects;
|
||||
using CryptoExchange.Net.Objects.Errors;
|
||||
using CryptoExchange.Net.Objects.Options;
|
||||
using CryptoExchange.Net.Objects.Sockets;
|
||||
using CryptoExchange.Net.RateLimiting;
|
||||
@ -265,7 +266,7 @@ namespace CryptoExchange.Net.Clients
|
||||
if (socketConnection.PausedActivity)
|
||||
{
|
||||
_logger.HasBeenPausedCantSubscribeAtThisMoment(socketConnection.SocketId);
|
||||
return new CallResult<UpdateSubscription>(new ServerError("Socket is paused"));
|
||||
return new CallResult<UpdateSubscription>(new ServerError(new ErrorInfo(ErrorType.WebsocketPaused, "Socket is paused")));
|
||||
}
|
||||
|
||||
var waitEvent = new AsyncResetEvent(false);
|
||||
@ -368,7 +369,7 @@ namespace CryptoExchange.Net.Clients
|
||||
if (socketConnection.PausedActivity)
|
||||
{
|
||||
_logger.HasBeenPausedCantSendQueryAtThisMoment(socketConnection.SocketId);
|
||||
return new CallResult<THandlerResponse>(new ServerError("Socket is paused"));
|
||||
return new CallResult<THandlerResponse>(new ServerError(new ErrorInfo(ErrorType.WebsocketPaused, "Socket is paused")));
|
||||
}
|
||||
|
||||
if (ct.IsCancellationRequested)
|
||||
|
@ -60,13 +60,12 @@ namespace CryptoExchange.Net.Converters.SystemTextJson
|
||||
}
|
||||
catch (JsonException ex)
|
||||
{
|
||||
var info = $"Deserialize JsonException: {ex.Message}, Path: {ex.Path}, LineNumber: {ex.LineNumber}, LinePosition: {ex.BytePositionInLine}";
|
||||
var info = $"Json deserialization failed: {ex.Message}, Path: {ex.Path}, LineNumber: {ex.LineNumber}, LinePosition: {ex.BytePositionInLine}";
|
||||
return new CallResult<object>(new DeserializeError(info, ex));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var info = $"Deserialize unknown Exception: {ex.Message}";
|
||||
return new CallResult<object>(new DeserializeError(info, ex));
|
||||
return new CallResult<object>(new DeserializeError($"Json deserialization failed: {ex.Message}", ex));
|
||||
}
|
||||
}
|
||||
|
||||
@ -87,13 +86,12 @@ namespace CryptoExchange.Net.Converters.SystemTextJson
|
||||
}
|
||||
catch (JsonException ex)
|
||||
{
|
||||
var info = $"Deserialize JsonException: {ex.Message}, Path: {ex.Path}, LineNumber: {ex.LineNumber}, LinePosition: {ex.BytePositionInLine}";
|
||||
var info = $"Json deserialization failed: {ex.Message}, Path: {ex.Path}, LineNumber: {ex.LineNumber}, LinePosition: {ex.BytePositionInLine}";
|
||||
return new CallResult<T>(new DeserializeError(info, ex));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var info = $"Unknown exception: {ex.Message}";
|
||||
return new CallResult<T>(new DeserializeError(info, ex));
|
||||
return new CallResult<T>(new DeserializeError($"Json deserialization failed: {ex.Message}", ex));
|
||||
}
|
||||
}
|
||||
|
||||
@ -286,7 +284,7 @@ namespace CryptoExchange.Net.Converters.SystemTextJson
|
||||
{
|
||||
// Not a json message
|
||||
IsValid = false;
|
||||
return new CallResult(new DeserializeError("JsonError: " + ex.Message, ex));
|
||||
return new CallResult(new DeserializeError($"Json deserialization failed: {ex.Message}", ex));
|
||||
}
|
||||
}
|
||||
|
||||
@ -338,7 +336,7 @@ namespace CryptoExchange.Net.Converters.SystemTextJson
|
||||
{
|
||||
// Value doesn't start with `{` or `[`, prevent deserialization attempt as it's slow
|
||||
IsValid = false;
|
||||
return new CallResult(new ServerError("Not a json value"));
|
||||
return new CallResult(new DeserializeError("Not a json value"));
|
||||
}
|
||||
|
||||
_document = JsonDocument.Parse(data);
|
||||
@ -349,7 +347,7 @@ namespace CryptoExchange.Net.Converters.SystemTextJson
|
||||
{
|
||||
// Not a json message
|
||||
IsValid = false;
|
||||
return new CallResult(new DeserializeError("JsonError: " + ex.Message, ex));
|
||||
return new CallResult(new DeserializeError($"Json deserialization failed: {ex.Message}", ex));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using CryptoExchange.Net.Objects.Errors;
|
||||
using System;
|
||||
|
||||
namespace CryptoExchange.Net.Objects
|
||||
{
|
||||
@ -7,15 +8,50 @@ namespace CryptoExchange.Net.Objects
|
||||
/// </summary>
|
||||
public abstract class Error
|
||||
{
|
||||
|
||||
private int? _code;
|
||||
/// <summary>
|
||||
/// The error code from the server
|
||||
/// </summary>
|
||||
public int? Code { get; set; }
|
||||
[Obsolete("Use ErrorCode instead", false)]
|
||||
public int? Code
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_code.HasValue)
|
||||
return _code;
|
||||
|
||||
return int.TryParse(ErrorCode, out var r) ? r : null;
|
||||
}
|
||||
set
|
||||
{
|
||||
_code = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The message for the error that occurred
|
||||
/// The error code returned by the server
|
||||
/// </summary>
|
||||
public string Message { get; set; }
|
||||
public string? ErrorCode { get; set; }
|
||||
/// <summary>
|
||||
/// The error description
|
||||
/// </summary>
|
||||
public string? ErrorDescription { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Error type
|
||||
/// </summary>
|
||||
public ErrorType ErrorType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether the error is transient and can be retried
|
||||
/// </summary>
|
||||
public bool IsTransient { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The server message for the error that occurred
|
||||
/// </summary>
|
||||
public string? Message { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Underlying exception
|
||||
@ -25,10 +61,13 @@ namespace CryptoExchange.Net.Objects
|
||||
/// <summary>
|
||||
/// ctor
|
||||
/// </summary>
|
||||
protected Error (int? code, string message, Exception? exception)
|
||||
protected Error(string? errorCode, ErrorInfo errorInfo, Exception? exception)
|
||||
{
|
||||
Code = code;
|
||||
Message = message;
|
||||
ErrorCode = errorCode;
|
||||
ErrorType = errorInfo.ErrorType;
|
||||
Message = errorInfo.Message;
|
||||
ErrorDescription = errorInfo.ErrorDescription;
|
||||
IsTransient = errorInfo.IsTransient;
|
||||
Exception = exception;
|
||||
}
|
||||
|
||||
@ -38,7 +77,7 @@ namespace CryptoExchange.Net.Objects
|
||||
/// <returns></returns>
|
||||
public override string ToString()
|
||||
{
|
||||
return Code != null ? $"[{GetType().Name}] {Code}: {Message}" : $"[{GetType().Name}] {Message}";
|
||||
return ErrorCode != null ? $"[{GetType().Name}.{ErrorType}] {ErrorCode}: {Message ?? ErrorDescription}" : $"[{GetType().Name}.{ErrorType}] {Message ?? ErrorDescription}";
|
||||
}
|
||||
}
|
||||
|
||||
@ -48,19 +87,24 @@ namespace CryptoExchange.Net.Objects
|
||||
public class CantConnectError : Error
|
||||
{
|
||||
/// <summary>
|
||||
/// ctor
|
||||
/// Default error info
|
||||
/// </summary>
|
||||
public CantConnectError() : base(null, "Can't connect to the server", null) { }
|
||||
protected static readonly ErrorInfo _errorInfo = new ErrorInfo(ErrorType.UnableToConnect, false, "Can't connect to the server");
|
||||
|
||||
/// <summary>
|
||||
/// ctor
|
||||
/// </summary>
|
||||
public CantConnectError(Exception? exception) : base(null, "Can't connect to the server", exception) { }
|
||||
public CantConnectError() : base(null, _errorInfo, null) { }
|
||||
|
||||
/// <summary>
|
||||
/// ctor
|
||||
/// </summary>
|
||||
protected CantConnectError(int? code, string message, Exception? exception) : base(code, message, exception) { }
|
||||
public CantConnectError(Exception? exception) : base(null, _errorInfo, exception) { }
|
||||
|
||||
/// <summary>
|
||||
/// ctor
|
||||
/// </summary>
|
||||
protected CantConnectError(ErrorInfo info, Exception? exception) : base(null, info, exception) { }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -69,14 +113,19 @@ namespace CryptoExchange.Net.Objects
|
||||
public class NoApiCredentialsError : Error
|
||||
{
|
||||
/// <summary>
|
||||
/// ctor
|
||||
/// Default error info
|
||||
/// </summary>
|
||||
public NoApiCredentialsError() : base(null, "No credentials provided for private endpoint", null) { }
|
||||
protected static readonly ErrorInfo _errorInfo = new ErrorInfo(ErrorType.MissingCredentials, false, "No credentials provided for private endpoint");
|
||||
|
||||
/// <summary>
|
||||
/// ctor
|
||||
/// </summary>
|
||||
protected NoApiCredentialsError(int? code, string message, Exception? exception) : base(code, message, exception) { }
|
||||
public NoApiCredentialsError() : base(null, _errorInfo, null) { }
|
||||
|
||||
/// <summary>
|
||||
/// ctor
|
||||
/// </summary>
|
||||
protected NoApiCredentialsError(ErrorInfo info, Exception? exception) : base(null, info, exception) { }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -87,12 +136,19 @@ namespace CryptoExchange.Net.Objects
|
||||
/// <summary>
|
||||
/// ctor
|
||||
/// </summary>
|
||||
public ServerError(string message) : base(null, message, null) { }
|
||||
public ServerError(ErrorInfo errorInfo, Exception? exception = null)
|
||||
: base(null, errorInfo, exception) { }
|
||||
|
||||
/// <summary>
|
||||
/// ctor
|
||||
/// </summary>
|
||||
public ServerError(int? code, string message, Exception? exception = null) : base(code, message, exception) { }
|
||||
public ServerError(int errorCode, ErrorInfo errorInfo, Exception? exception = null)
|
||||
: this(errorCode.ToString(), errorInfo, exception) { }
|
||||
|
||||
/// <summary>
|
||||
/// ctor
|
||||
/// </summary>
|
||||
public ServerError(string errorCode, ErrorInfo errorInfo, Exception? exception = null) : base(errorCode, errorInfo, exception) { }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -101,14 +157,30 @@ namespace CryptoExchange.Net.Objects
|
||||
public class WebError : Error
|
||||
{
|
||||
/// <summary>
|
||||
/// ctor
|
||||
/// Default error info
|
||||
/// </summary>
|
||||
public WebError(string message, Exception? exception = null) : base(null, message, exception) { }
|
||||
protected static readonly ErrorInfo _errorInfo = new ErrorInfo(ErrorType.NetworkError, true, "Failed to complete the request to the server due to a network error");
|
||||
|
||||
/// <summary>
|
||||
/// ctor
|
||||
/// </summary>
|
||||
public WebError(int code, string message, Exception? exception = null) : base(code, message, exception) { }
|
||||
public WebError(string? message = null, Exception? exception = null) : base(null, _errorInfo with { Message = (message?.Length > 0 ? _errorInfo.Message + ": " + message : _errorInfo.Message) }, exception) { }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Timeout error waiting for a response from the server
|
||||
/// </summary>
|
||||
public class TimeoutError : Error
|
||||
{
|
||||
/// <summary>
|
||||
/// Default error info
|
||||
/// </summary>
|
||||
protected static readonly ErrorInfo _errorInfo = new ErrorInfo(ErrorType.Timeout, false, "Failed to receive a response from the server in time");
|
||||
|
||||
/// <summary>
|
||||
/// ctor
|
||||
/// </summary>
|
||||
public TimeoutError(string? message = null, Exception? exception = null) : base(null, _errorInfo with { Message = (message?.Length > 0 ? _errorInfo.Message + ": " + message : _errorInfo.Message) }, exception) { }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -117,30 +189,14 @@ namespace CryptoExchange.Net.Objects
|
||||
public class DeserializeError : Error
|
||||
{
|
||||
/// <summary>
|
||||
/// ctor
|
||||
/// Default error info
|
||||
/// </summary>
|
||||
public DeserializeError(string message, Exception? exception = null) : base(null, message, exception) { }
|
||||
protected static readonly ErrorInfo _errorInfo = new ErrorInfo(ErrorType.DeserializationFailed, false, "Failed to deserialize data");
|
||||
|
||||
/// <summary>
|
||||
/// ctor
|
||||
/// </summary>
|
||||
protected DeserializeError(int? code, string message, Exception? exception = null) : base(code, message, exception) { }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unknown error
|
||||
/// </summary>
|
||||
public class UnknownError : Error
|
||||
{
|
||||
/// <summary>
|
||||
/// ctor
|
||||
/// </summary>
|
||||
public UnknownError(string message, Exception? exception = null) : base(null, message, exception) { }
|
||||
|
||||
/// <summary>
|
||||
/// ctor
|
||||
/// </summary>
|
||||
protected UnknownError(int? code, string message, Exception? exception = null): base(code, message, exception) { }
|
||||
public DeserializeError(string? message = null, Exception? exception = null) : base(null, _errorInfo with { Message = (message?.Length > 0 ? _errorInfo.Message + ": " + message : _errorInfo.Message) }, exception) { }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -149,14 +205,28 @@ namespace CryptoExchange.Net.Objects
|
||||
public class ArgumentError : Error
|
||||
{
|
||||
/// <summary>
|
||||
/// ctor
|
||||
/// Default error info for missing parameter
|
||||
/// </summary>
|
||||
public ArgumentError(string message) : base(null, "Invalid parameter: " + message, null) { }
|
||||
protected static readonly ErrorInfo _missingInfo = new ErrorInfo(ErrorType.MissingParameter, false, "Missing parameter");
|
||||
/// <summary>
|
||||
/// Default error info for invalid parameter
|
||||
/// </summary>
|
||||
protected static readonly ErrorInfo _invalidInfo = new ErrorInfo(ErrorType.InvalidParameter, false, "Invalid parameter");
|
||||
|
||||
/// <summary>
|
||||
/// ctor
|
||||
/// </summary>
|
||||
protected ArgumentError(int? code, string message, Exception? exception = null) : base(code, message, exception) { }
|
||||
public static ArgumentError Missing(string parameterName, string? message = null) => new ArgumentError(_missingInfo with { Message = message == null ? $"{_missingInfo.Message} '{parameterName}'" : $"{_missingInfo.Message} '{parameterName}': {message}" }, null);
|
||||
|
||||
/// <summary>
|
||||
/// ctor
|
||||
/// </summary>
|
||||
public static ArgumentError Invalid(string parameterName, string message) => new ArgumentError(_invalidInfo with { Message = $"{_invalidInfo.Message} '{parameterName}': {message}" }, null);
|
||||
|
||||
/// <summary>
|
||||
/// ctor
|
||||
/// </summary>
|
||||
protected ArgumentError(ErrorInfo info, Exception? exception) : base(null, info, exception) { }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -172,7 +242,7 @@ namespace CryptoExchange.Net.Objects
|
||||
/// <summary>
|
||||
/// ctor
|
||||
/// </summary>
|
||||
protected BaseRateLimitError(int? code, string message, Exception? exception) : base(code, message, exception) { }
|
||||
protected BaseRateLimitError(ErrorInfo errorInfo, Exception? exception) : base(null, errorInfo, exception) { }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -181,15 +251,19 @@ namespace CryptoExchange.Net.Objects
|
||||
public class ClientRateLimitError : BaseRateLimitError
|
||||
{
|
||||
/// <summary>
|
||||
/// ctor
|
||||
/// Default error info
|
||||
/// </summary>
|
||||
/// <param name="message"></param>
|
||||
public ClientRateLimitError(string message) : base(null, "Client rate limit exceeded: " + message, null) { }
|
||||
protected static readonly ErrorInfo _errorInfo = new ErrorInfo(ErrorType.RateLimitRequest, false, "Client rate limit exceeded");
|
||||
|
||||
/// <summary>
|
||||
/// ctor
|
||||
/// </summary>
|
||||
protected ClientRateLimitError(int? code, string message, Exception? exception = null) : base(code, message, exception) { }
|
||||
public ClientRateLimitError(string? message = null, Exception? exception = null) : base(_errorInfo with { Message = (message?.Length > 0 ? _errorInfo.Message + ": " + message : _errorInfo.Message) }, exception) { }
|
||||
|
||||
/// <summary>
|
||||
/// ctor
|
||||
/// </summary>
|
||||
protected ClientRateLimitError(ErrorInfo info, Exception? exception) : base(info, exception) { }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -198,14 +272,19 @@ namespace CryptoExchange.Net.Objects
|
||||
public class ServerRateLimitError : BaseRateLimitError
|
||||
{
|
||||
/// <summary>
|
||||
/// ctor
|
||||
/// Default error info
|
||||
/// </summary>
|
||||
public ServerRateLimitError(string? message = null, Exception? exception = null) : base(null, "Server rate limit exceeded" + (message?.Length > 0 ? " : " + message : null), exception) { }
|
||||
protected static readonly ErrorInfo _errorInfo = new ErrorInfo(ErrorType.RateLimitRequest, false, "Server rate limit exceeded");
|
||||
|
||||
/// <summary>
|
||||
/// ctor
|
||||
/// </summary>
|
||||
protected ServerRateLimitError(int? code, string message, Exception? exception = null) : base(code, message, exception) { }
|
||||
public ServerRateLimitError(string? message = null, Exception? exception = null) : base(_errorInfo with { Message = (message?.Length > 0 ? _errorInfo.Message + ": " + message : _errorInfo.Message) }, exception) { }
|
||||
|
||||
/// <summary>
|
||||
/// ctor
|
||||
/// </summary>
|
||||
protected ServerRateLimitError(ErrorInfo info, Exception? exception) : base(info, exception) { }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -214,14 +293,19 @@ namespace CryptoExchange.Net.Objects
|
||||
public class CancellationRequestedError : Error
|
||||
{
|
||||
/// <summary>
|
||||
/// ctor
|
||||
/// Default error info
|
||||
/// </summary>
|
||||
public CancellationRequestedError(Exception? exception = null) : base(null, "Cancellation requested", exception) { }
|
||||
protected static readonly ErrorInfo _errorInfo = new ErrorInfo(ErrorType.CancellationRequested, false, "Cancellation requested");
|
||||
|
||||
/// <summary>
|
||||
/// ctor
|
||||
/// </summary>
|
||||
public CancellationRequestedError(int? code, string message, Exception? exception = null) : base(code, message, exception) { }
|
||||
public CancellationRequestedError(Exception? exception = null) : base(null, _errorInfo, null) { }
|
||||
|
||||
/// <summary>
|
||||
/// ctor
|
||||
/// </summary>
|
||||
protected CancellationRequestedError(ErrorInfo info, Exception? exception) : base(null, info, exception) { }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -230,13 +314,18 @@ namespace CryptoExchange.Net.Objects
|
||||
public class InvalidOperationError : Error
|
||||
{
|
||||
/// <summary>
|
||||
/// ctor
|
||||
/// Default error info
|
||||
/// </summary>
|
||||
public InvalidOperationError(string message, Exception? exception = null) : base(null, message, exception) { }
|
||||
protected static readonly ErrorInfo _errorInfo = new ErrorInfo(ErrorType.InvalidOperation, false, "Operation invalid");
|
||||
|
||||
/// <summary>
|
||||
/// ctor
|
||||
/// </summary>
|
||||
protected InvalidOperationError(int? code, string message, Exception? exception = null) : base(code, message, exception) { }
|
||||
public InvalidOperationError(string message) : base(null, _errorInfo with { Message = message }, null) { }
|
||||
|
||||
/// <summary>
|
||||
/// ctor
|
||||
/// </summary>
|
||||
protected InvalidOperationError(ErrorInfo info, Exception? exception) : base(null, info, exception) { }
|
||||
}
|
||||
}
|
||||
|
40
CryptoExchange.Net/Objects/Errors/ErrorEvaluator.cs
Normal file
40
CryptoExchange.Net/Objects/Errors/ErrorEvaluator.cs
Normal file
@ -0,0 +1,40 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace CryptoExchange.Net.Objects.Errors
|
||||
{
|
||||
/// <summary>
|
||||
/// Error evaluator
|
||||
/// </summary>
|
||||
public class ErrorEvaluator
|
||||
{
|
||||
/// <summary>
|
||||
/// Error code
|
||||
/// </summary>
|
||||
public string[] ErrorCodes { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Evaluation callback for determining the error type
|
||||
/// </summary>
|
||||
public Func<string, string?, ErrorInfo> ErrorTypeEvaluator { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// ctor
|
||||
/// </summary>
|
||||
public ErrorEvaluator(string errorCode, Func<string, string?, ErrorInfo> errorTypeEvaluator)
|
||||
{
|
||||
ErrorCodes = [errorCode];
|
||||
ErrorTypeEvaluator = errorTypeEvaluator;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ctor
|
||||
/// </summary>
|
||||
public ErrorEvaluator(string[] errorCodes, Func<string, string?, ErrorInfo> errorTypeEvaluator)
|
||||
{
|
||||
ErrorCodes = errorCodes;
|
||||
ErrorTypeEvaluator = errorTypeEvaluator;
|
||||
}
|
||||
}
|
||||
}
|
58
CryptoExchange.Net/Objects/Errors/ErrorInfo.cs
Normal file
58
CryptoExchange.Net/Objects/Errors/ErrorInfo.cs
Normal file
@ -0,0 +1,58 @@
|
||||
using System;
|
||||
|
||||
namespace CryptoExchange.Net.Objects.Errors
|
||||
{
|
||||
/// <summary>
|
||||
/// Error info
|
||||
/// </summary>
|
||||
public record ErrorInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// Unknown error info
|
||||
/// </summary>
|
||||
public static ErrorInfo Unknown { get; } = new ErrorInfo(ErrorType.Unknown, false, "Unknown error", []);
|
||||
|
||||
/// <summary>
|
||||
/// The server error code
|
||||
/// </summary>
|
||||
public string[] ErrorCodes { get; set; }
|
||||
/// <summary>
|
||||
/// Error description
|
||||
/// </summary>
|
||||
public string? ErrorDescription { get; set; }
|
||||
/// <summary>
|
||||
/// The error type
|
||||
/// </summary>
|
||||
public ErrorType ErrorType { get; set; }
|
||||
/// <summary>
|
||||
/// Whether the error is transient and can be retried
|
||||
/// </summary>
|
||||
public bool IsTransient { get; set; }
|
||||
/// <summary>
|
||||
/// Server response message
|
||||
/// </summary>
|
||||
public string? Message { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// ctor
|
||||
/// </summary>
|
||||
public ErrorInfo(ErrorType errorType, string description)
|
||||
{
|
||||
ErrorCodes = [];
|
||||
ErrorType = errorType;
|
||||
IsTransient = false;
|
||||
ErrorDescription = description;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ctor
|
||||
/// </summary>
|
||||
public ErrorInfo(ErrorType errorType, bool isTransient, string description, params string[] errorCodes)
|
||||
{
|
||||
ErrorCodes = errorCodes;
|
||||
ErrorType = errorType;
|
||||
IsTransient = isTransient;
|
||||
ErrorDescription = description;
|
||||
}
|
||||
}
|
||||
}
|
54
CryptoExchange.Net/Objects/Errors/ErrorMapping.cs
Normal file
54
CryptoExchange.Net/Objects/Errors/ErrorMapping.cs
Normal file
@ -0,0 +1,54 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace CryptoExchange.Net.Objects.Errors
|
||||
{
|
||||
/// <summary>
|
||||
/// Error mapping collection
|
||||
/// </summary>
|
||||
public class ErrorMapping
|
||||
{
|
||||
private Dictionary<string, ErrorEvaluator> _evaluators = new Dictionary<string, ErrorEvaluator>();
|
||||
private Dictionary<string, ErrorInfo> _directMapping = new Dictionary<string, ErrorInfo>();
|
||||
|
||||
/// <summary>
|
||||
/// ctor
|
||||
/// </summary>
|
||||
public ErrorMapping(ErrorInfo[] errorMappings, ErrorEvaluator[]? errorTypeEvaluators = null)
|
||||
{
|
||||
foreach (var item in errorMappings)
|
||||
{
|
||||
if (!item.ErrorCodes.Any())
|
||||
throw new Exception("Error codes can't be null in error mapping");
|
||||
|
||||
foreach(var code in item.ErrorCodes!)
|
||||
_directMapping.Add(code, item);
|
||||
}
|
||||
|
||||
if (errorTypeEvaluators == null)
|
||||
return;
|
||||
|
||||
foreach (var item in errorTypeEvaluators)
|
||||
{
|
||||
foreach(var code in item.ErrorCodes)
|
||||
_evaluators.Add(code, item);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get error info for an error code
|
||||
/// </summary>
|
||||
public ErrorInfo GetErrorInfo(string code, string? message)
|
||||
{
|
||||
if (_directMapping.TryGetValue(code!, out var info))
|
||||
return info with { Message = message };
|
||||
|
||||
if (_evaluators.TryGetValue(code!, out var eva))
|
||||
return eva.ErrorTypeEvaluator.Invoke(code!, message) with { Message = message };
|
||||
|
||||
return ErrorInfo.Unknown with { Message = message };
|
||||
}
|
||||
}
|
||||
}
|
162
CryptoExchange.Net/Objects/Errors/ErrorType.cs
Normal file
162
CryptoExchange.Net/Objects/Errors/ErrorType.cs
Normal file
@ -0,0 +1,162 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace CryptoExchange.Net.Objects.Errors
|
||||
{
|
||||
/// <summary>
|
||||
/// Type of error
|
||||
/// </summary>
|
||||
public enum ErrorType
|
||||
{
|
||||
#region Library errors
|
||||
|
||||
/// <summary>
|
||||
/// Failed to connect to server
|
||||
/// </summary>
|
||||
UnableToConnect,
|
||||
/// <summary>
|
||||
/// Failed to complete the request to the server
|
||||
/// </summary>
|
||||
NetworkError,
|
||||
/// <summary>
|
||||
/// No API credentials have been specified
|
||||
/// </summary>
|
||||
MissingCredentials,
|
||||
/// <summary>
|
||||
/// Invalid parameter value
|
||||
/// </summary>
|
||||
InvalidParameter,
|
||||
/// <summary>
|
||||
/// Missing parameter value
|
||||
/// </summary>
|
||||
MissingParameter,
|
||||
/// <summary>
|
||||
/// Cancellation requested by user
|
||||
/// </summary>
|
||||
CancellationRequested,
|
||||
/// <summary>
|
||||
/// Invalid operation requested
|
||||
/// </summary>
|
||||
InvalidOperation,
|
||||
/// <summary>
|
||||
/// Failed to deserialize data
|
||||
/// </summary>
|
||||
DeserializationFailed,
|
||||
/// <summary>
|
||||
/// Websocket is temporarily paused
|
||||
/// </summary>
|
||||
WebsocketPaused,
|
||||
/// <summary>
|
||||
/// Timeout while waiting for data from the order book subscription
|
||||
/// </summary>
|
||||
OrderBookTimeout,
|
||||
/// <summary>
|
||||
/// All orders failed for a multi-order operation
|
||||
/// </summary>
|
||||
AllOrdersFailed,
|
||||
/// <summary>
|
||||
/// Request timeout
|
||||
/// </summary>
|
||||
Timeout,
|
||||
|
||||
#endregion
|
||||
|
||||
#region Server errors
|
||||
|
||||
/// <summary>
|
||||
/// Unknown error
|
||||
/// </summary>
|
||||
Unknown,
|
||||
/// <summary>
|
||||
/// Not authorized or insufficient permissions
|
||||
/// </summary>
|
||||
Unauthorized,
|
||||
/// <summary>
|
||||
/// Request rate limit error, too many requests
|
||||
/// </summary>
|
||||
RateLimitRequest,
|
||||
/// <summary>
|
||||
/// Connection rate limit error, too many connections
|
||||
/// </summary>
|
||||
RateLimitConnection,
|
||||
/// <summary>
|
||||
/// Subscription rate limit error, too many subscriptions
|
||||
/// </summary>
|
||||
RateLimitSubscription,
|
||||
/// <summary>
|
||||
/// Order rate limit error, too many orders
|
||||
/// </summary>
|
||||
RateLimitOrder,
|
||||
/// <summary>
|
||||
/// Request timestamp invalid
|
||||
/// </summary>
|
||||
InvalidTimestamp,
|
||||
/// <summary>
|
||||
/// Unknown symbol
|
||||
/// </summary>
|
||||
UnknownSymbol,
|
||||
/// <summary>
|
||||
/// Unknown asset
|
||||
/// </summary>
|
||||
UnknownAsset,
|
||||
/// <summary>
|
||||
/// Unknown order
|
||||
/// </summary>
|
||||
UnknownOrder,
|
||||
/// <summary>
|
||||
/// Duplicate subscription
|
||||
/// </summary>
|
||||
DuplicateSubscription,
|
||||
/// <summary>
|
||||
/// Invalid quantity
|
||||
/// </summary>
|
||||
InvalidQuantity,
|
||||
/// <summary>
|
||||
/// Invalid price
|
||||
/// </summary>
|
||||
InvalidPrice,
|
||||
/// <summary>
|
||||
/// Parameter(s) for stop or tp/sl order invalid
|
||||
/// </summary>
|
||||
InvalidStopParameters,
|
||||
/// <summary>
|
||||
/// Not enough balance to execute request
|
||||
/// </summary>
|
||||
InsufficientBalance,
|
||||
/// <summary>
|
||||
/// Client order id already in use
|
||||
/// </summary>
|
||||
DuplicateClientOrderId,
|
||||
/// <summary>
|
||||
/// Symbol is not currently trading
|
||||
/// </summary>
|
||||
UnavailableSymbol,
|
||||
/// <summary>
|
||||
/// Order rejected due to order configuration such as order type or time in force restrictions
|
||||
/// </summary>
|
||||
RejectedOrderConfiguration,
|
||||
/// <summary>
|
||||
/// There is no open position
|
||||
/// </summary>
|
||||
NoPosition,
|
||||
/// <summary>
|
||||
/// Max position reached
|
||||
/// </summary>
|
||||
MaxPosition,
|
||||
/// <summary>
|
||||
/// Error in the internal system
|
||||
/// </summary>
|
||||
SystemError,
|
||||
/// <summary>
|
||||
/// The target object is not in the correct state for an operation
|
||||
/// </summary>
|
||||
IncorrectState,
|
||||
/// <summary>
|
||||
/// Risk management error
|
||||
/// </summary>
|
||||
RiskError
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
@ -9,6 +9,7 @@ using System.Threading.Tasks;
|
||||
using CryptoExchange.Net.Interfaces;
|
||||
using CryptoExchange.Net.Logging.Extensions;
|
||||
using CryptoExchange.Net.Objects;
|
||||
using CryptoExchange.Net.Objects.Errors;
|
||||
using CryptoExchange.Net.Objects.Options;
|
||||
using CryptoExchange.Net.Objects.Sockets;
|
||||
using Microsoft.Extensions.Logging;
|
||||
@ -549,7 +550,7 @@ namespace CryptoExchange.Net.OrderBook
|
||||
return new CallResult<bool>(new CancellationRequestedError());
|
||||
|
||||
if (DateTime.UtcNow - startWait > timeout)
|
||||
return new CallResult<bool>(new ServerError("Timeout while waiting for data"));
|
||||
return new CallResult<bool>(new ServerError(new ErrorInfo(ErrorType.OrderBookTimeout, "Timeout while waiting for data")));
|
||||
|
||||
try
|
||||
{
|
||||
|
@ -58,19 +58,19 @@ namespace CryptoExchange.Net.SharedApis
|
||||
public virtual Error? ValidateRequest(string exchange, ExchangeParameters? exchangeParameters, TradingMode? tradingMode, TradingMode[] supportedTradingModes)
|
||||
{
|
||||
if (tradingMode != null && !supportedTradingModes.Contains(tradingMode.Value))
|
||||
return new ArgumentError($"ApiType.{tradingMode} is not supported, supported types: {string.Join(", ", supportedTradingModes)}");
|
||||
return ArgumentError.Invalid("TradingMode", $"TradingMode.{tradingMode} is not supported, supported types: {string.Join(", ", supportedTradingModes)}");
|
||||
|
||||
foreach (var param in RequiredExchangeParameters)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(param.Name))
|
||||
{
|
||||
if (ExchangeParameters.HasValue(exchangeParameters, exchange, param.Name!, param.ValueType) != true)
|
||||
return new ArgumentError($"Required exchange parameter `{param.Name}` for exchange `{exchange}` is missing or has incorrect type. Expected type is {param.ValueType.Name}. Example: {param.ExampleValue}");
|
||||
return ArgumentError.Invalid(param.Name!, $"Required exchange parameter `{param.Name}` for exchange `{exchange}` is missing or has incorrect type. Expected type is {param.ValueType.Name}. Example: {param.ExampleValue}");
|
||||
}
|
||||
else
|
||||
{
|
||||
if (param.Names!.All(x => ExchangeParameters.HasValue(exchangeParameters, exchange, x, param.ValueType) != true))
|
||||
return new ArgumentError($"One of exchange parameters `{string.Join(", ", param.Names!)}` for exchange `{exchange}` should be provided. Example: {param.ExampleValue}");
|
||||
return ArgumentError.Invalid(string.Join("/", param.Names!), $"One of exchange parameters `{string.Join(", ", param.Names!)}` for exchange `{exchange}` should be provided. Example: {param.ExampleValue}");
|
||||
}
|
||||
}
|
||||
|
||||
@ -140,12 +140,12 @@ namespace CryptoExchange.Net.SharedApis
|
||||
if (!string.IsNullOrEmpty(param.Name))
|
||||
{
|
||||
if (typeof(T).GetProperty(param.Name)!.GetValue(request, null) == null)
|
||||
return new ArgumentError($"Required optional parameter `{param.Name}` for exchange `{exchange}` is missing. Example: {param.ExampleValue}");
|
||||
return ArgumentError.Invalid(param.Name!, $"Required optional parameter `{param.Name}` for exchange `{exchange}` is missing. Example: {param.ExampleValue}");
|
||||
}
|
||||
else
|
||||
{
|
||||
if (param.Names!.All(x => typeof(T).GetProperty(param.Name!)!.GetValue(request, null) == null))
|
||||
return new ArgumentError($"One of optional parameters `{string.Join(", ", param.Names!)}` for exchange `{exchange}` should be provided. Example: {param.ExampleValue}");
|
||||
return ArgumentError.Invalid(string.Join("/", param.Names!), $"One of optional parameters `{string.Join(", ", param.Names!)}` for exchange `{exchange}` should be provided. Example: {param.ExampleValue}");
|
||||
}
|
||||
|
||||
}
|
||||
@ -155,10 +155,10 @@ namespace CryptoExchange.Net.SharedApis
|
||||
if (symbolsRequest.Symbols != null)
|
||||
{
|
||||
if (!SupportsMultipleSymbols)
|
||||
return new ArgumentError($"Only a single symbol parameter is allowed, multiple symbols are not supported");
|
||||
return ArgumentError.Invalid(nameof(SharedSymbolRequest.Symbols), $"Only a single symbol parameter is allowed, multiple symbols are not supported");
|
||||
|
||||
if (symbolsRequest.Symbols.Length > MaxSymbolCount)
|
||||
return new ArgumentError($"Max number of symbols is {MaxSymbolCount} but {symbolsRequest.Symbols.Length} were passed");
|
||||
return ArgumentError.Invalid(nameof(SharedSymbolRequest.Symbols), $"Max number of symbols is {MaxSymbolCount} but {symbolsRequest.Symbols.Length} were passed");
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -25,7 +25,7 @@ namespace CryptoExchange.Net.SharedApis
|
||||
public override Error? ValidateRequest(string exchange, GetClosedOrdersRequest request, TradingMode? tradingMode, TradingMode[] supportedApiTypes)
|
||||
{
|
||||
if (TimeFilterSupported && request.StartTime != null)
|
||||
return new ArgumentError($"Time filter is not supported");
|
||||
return ArgumentError.Invalid(nameof(GetClosedOrdersRequest.StartTime), $"Time filter is not supported");
|
||||
|
||||
return base.ValidateRequest(exchange, request, tradingMode, supportedApiTypes);
|
||||
}
|
||||
|
@ -25,7 +25,7 @@ namespace CryptoExchange.Net.SharedApis
|
||||
public override Error? ValidateRequest(string exchange, GetDepositsRequest request, TradingMode? tradingMode, TradingMode[] supportedApiTypes)
|
||||
{
|
||||
if (TimeFilterSupported && request.StartTime != null)
|
||||
return new ArgumentError($"Time filter is not supported");
|
||||
return ArgumentError.Invalid(nameof(GetDepositsRequest.StartTime), $"Time filter is not supported");
|
||||
|
||||
return base.ValidateRequest(exchange, request, tradingMode, supportedApiTypes);
|
||||
}
|
||||
|
@ -67,23 +67,23 @@ namespace CryptoExchange.Net.SharedApis
|
||||
public override Error? ValidateRequest(string exchange, GetKlinesRequest request, TradingMode? tradingMode, TradingMode[] supportedApiTypes)
|
||||
{
|
||||
if (!IsSupported(request.Interval))
|
||||
return new ArgumentError("Interval not supported");
|
||||
return ArgumentError.Invalid(nameof(GetKlinesRequest.Interval), "Interval not supported");
|
||||
|
||||
if (MaxAge.HasValue && request.StartTime < DateTime.UtcNow.Add(-MaxAge.Value))
|
||||
return new ArgumentError($"Only the most recent {MaxAge} klines are available");
|
||||
return ArgumentError.Invalid(nameof(GetKlinesRequest.StartTime), $"Only the most recent {MaxAge} klines are available");
|
||||
|
||||
if (request.Limit > MaxLimit)
|
||||
return new ArgumentError($"Only {MaxLimit} klines can be retrieved per request");
|
||||
return ArgumentError.Invalid(nameof(GetKlinesRequest.Limit), $"Only {MaxLimit} klines can be retrieved per request");
|
||||
|
||||
if (MaxTotalDataPoints.HasValue)
|
||||
{
|
||||
if (request.Limit > MaxTotalDataPoints.Value)
|
||||
return new ArgumentError($"Only the most recent {MaxTotalDataPoints} klines are available");
|
||||
return ArgumentError.Invalid(nameof(GetKlinesRequest.Limit), $"Only the most recent {MaxTotalDataPoints} klines are available");
|
||||
|
||||
if (request.StartTime.HasValue == true)
|
||||
{
|
||||
if (((request.EndTime ?? DateTime.UtcNow) - request.StartTime.Value).TotalSeconds / (int)request.Interval > MaxTotalDataPoints.Value)
|
||||
return new ArgumentError($"Only the most recent {MaxTotalDataPoints} klines are available, time filter failed");
|
||||
return ArgumentError.Invalid(nameof(GetKlinesRequest.StartTime), $"Only the most recent {MaxTotalDataPoints} klines are available, time filter failed");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -49,13 +49,13 @@ namespace CryptoExchange.Net.SharedApis
|
||||
return null;
|
||||
|
||||
if (MaxLimit.HasValue && request.Limit.Value > MaxLimit)
|
||||
return new ArgumentError($"Max limit is {MaxLimit}");
|
||||
return ArgumentError.Invalid(nameof(GetOrderBookRequest.Limit), $"Max limit is {MaxLimit}");
|
||||
|
||||
if (MinLimit.HasValue && request.Limit.Value < MinLimit)
|
||||
return new ArgumentError($"Min limit is {MaxLimit}");
|
||||
return ArgumentError.Invalid(nameof(GetOrderBookRequest.Limit), $"Min limit is {MaxLimit}");
|
||||
|
||||
if (SupportedLimits != null && !SupportedLimits.Contains(request.Limit.Value))
|
||||
return new ArgumentError($"Limit should be one of " + string.Join(", ", SupportedLimits));
|
||||
return ArgumentError.Invalid(nameof(GetOrderBookRequest.Limit), $"Limit should be one of " + string.Join(", ", SupportedLimits));
|
||||
|
||||
return base.ValidateRequest(exchange, request, tradingMode, supportedApiTypes);
|
||||
}
|
||||
|
@ -25,7 +25,7 @@ namespace CryptoExchange.Net.SharedApis
|
||||
public Error? Validate(GetRecentTradesRequest request)
|
||||
{
|
||||
if (request.Limit > MaxLimit)
|
||||
return new ArgumentError($"Only the most recent {MaxLimit} trades are available");
|
||||
return ArgumentError.Invalid(nameof(GetRecentTradesRequest.Limit), $"Only the most recent {MaxLimit} trades are available");
|
||||
|
||||
return null;
|
||||
}
|
||||
|
@ -25,7 +25,7 @@ namespace CryptoExchange.Net.SharedApis
|
||||
public override Error? ValidateRequest(string exchange, GetTradeHistoryRequest request, TradingMode? tradingMode, TradingMode[] supportedApiTypes)
|
||||
{
|
||||
if (MaxAge.HasValue && request.StartTime < DateTime.UtcNow.Add(-MaxAge.Value))
|
||||
return new ArgumentError($"Only the most recent {MaxAge} trades are available");
|
||||
return ArgumentError.Invalid(nameof(GetTradeHistoryRequest.StartTime), $"Only the most recent {MaxAge} trades are available");
|
||||
|
||||
return base.ValidateRequest(exchange, request, tradingMode, supportedApiTypes);
|
||||
}
|
||||
|
@ -25,7 +25,7 @@ namespace CryptoExchange.Net.SharedApis
|
||||
public override Error? ValidateRequest(string exchange, GetWithdrawalsRequest request, TradingMode? tradingMode, TradingMode[] supportedApiTypes)
|
||||
{
|
||||
if (TimeFilterSupported && request.StartTime != null)
|
||||
return new ArgumentError($"Time filter is not supported");
|
||||
return ArgumentError.Invalid(nameof(GetWithdrawalsRequest.StartTime), $"Time filter is not supported");
|
||||
|
||||
return base.ValidateRequest(exchange, request, tradingMode, supportedApiTypes);
|
||||
}
|
||||
|
@ -36,16 +36,16 @@ namespace CryptoExchange.Net.SharedApis
|
||||
SharedQuantitySupport quantitySupport)
|
||||
{
|
||||
if (!SupportsTpSl && (request.StopLossPrice != null || request.TakeProfitPrice != null))
|
||||
return new ArgumentError("Tp/Sl parameters not supported");
|
||||
return ArgumentError.Invalid(nameof(PlaceFuturesOrderRequest.StopLossPrice) + " / " + nameof(PlaceFuturesOrderRequest.TakeProfitPrice), "Tp/Sl parameters not supported");
|
||||
|
||||
if (request.OrderType == SharedOrderType.Other)
|
||||
throw new ArgumentException("OrderType can't be `Other`", nameof(request.OrderType));
|
||||
|
||||
if (!supportedOrderTypes.Contains(request.OrderType))
|
||||
return new ArgumentError("Order type not supported");
|
||||
return ArgumentError.Invalid(nameof(PlaceFuturesOrderRequest.OrderType), "Order type not supported");
|
||||
|
||||
if (request.TimeInForce != null && !supportedTimeInForce.Contains(request.TimeInForce.Value))
|
||||
return new ArgumentError("Order time in force not supported");
|
||||
return ArgumentError.Invalid(nameof(PlaceFuturesOrderRequest.TimeInForce), "Order time in force not supported");
|
||||
|
||||
var quantityError = quantitySupport.Validate(request.Side, request.OrderType, request.Quantity);
|
||||
if (quantityError != null)
|
||||
|
@ -34,10 +34,10 @@ namespace CryptoExchange.Net.SharedApis
|
||||
throw new ArgumentException("OrderType can't be `Other`", nameof(request.OrderType));
|
||||
|
||||
if (!supportedOrderTypes.Contains(request.OrderType))
|
||||
return new ArgumentError("Order type not supported");
|
||||
return ArgumentError.Invalid(nameof(PlaceSpotOrderRequest.OrderType), "Order type not supported");
|
||||
|
||||
if (request.TimeInForce != null && !supportedTimeInForce.Contains(request.TimeInForce.Value))
|
||||
return new ArgumentError("Order time in force not supported");
|
||||
return ArgumentError.Invalid(nameof(PlaceSpotOrderRequest.TimeInForce), "Order time in force not supported");
|
||||
|
||||
var quantityError = quantitySupport.Validate(request.Side, request.OrderType, request.Quantity);
|
||||
if (quantityError != null)
|
||||
|
@ -60,7 +60,7 @@ namespace CryptoExchange.Net.SharedApis
|
||||
public override Error? ValidateRequest(string exchange, SubscribeKlineRequest request, TradingMode? tradingMode, TradingMode[] supportedApiTypes)
|
||||
{
|
||||
if (!IsSupported(request.Interval))
|
||||
return new ArgumentError("Interval not supported");
|
||||
return ArgumentError.Invalid(nameof(SubscribeKlineRequest.Interval), "Interval not supported");
|
||||
|
||||
return base.ValidateRequest(exchange, request, tradingMode, supportedApiTypes);
|
||||
}
|
||||
|
@ -29,7 +29,7 @@ namespace CryptoExchange.Net.SharedApis
|
||||
public override Error? ValidateRequest(string exchange, SubscribeOrderBookRequest request, TradingMode? tradingMode, TradingMode[] supportedApiTypes)
|
||||
{
|
||||
if (request.Limit != null && !SupportedLimits.Contains(request.Limit.Value))
|
||||
return new ArgumentError("Limit not supported");
|
||||
return ArgumentError.Invalid(nameof(SubscribeOrderBookRequest.Limit), "Limit not supported");
|
||||
|
||||
return base.ValidateRequest(exchange, request, tradingMode, supportedApiTypes);
|
||||
}
|
||||
|
@ -84,16 +84,16 @@ namespace CryptoExchange.Net.SharedApis
|
||||
return null;
|
||||
|
||||
if (supportedType == SharedQuantityType.BaseAndQuoteAsset && quantity != null && quantity.QuantityInBaseAsset == null && quantity.QuantityInQuoteAsset == null)
|
||||
return new ArgumentError($"Quantity for {side}.{type} required in base or quote asset");
|
||||
return ArgumentError.Invalid("Quantity", $"Quantity for {side}.{type} required in base or quote asset");
|
||||
|
||||
if (supportedType == SharedQuantityType.QuoteAsset && quantity != null && quantity.QuantityInQuoteAsset == null)
|
||||
return new ArgumentError($"Quantity for {side}.{type} required in quote asset");
|
||||
return ArgumentError.Invalid("Quantity", $"Quantity for {side}.{type} required in quote asset");
|
||||
|
||||
if (supportedType == SharedQuantityType.BaseAsset && quantity != null && quantity.QuantityInBaseAsset == null && quantity.QuantityInContracts == null)
|
||||
return new ArgumentError($"Quantity for {side}.{type} required in base asset");
|
||||
return ArgumentError.Invalid("Quantity", $"Quantity for {side}.{type} required in base asset");
|
||||
|
||||
if (supportedType == SharedQuantityType.Contracts && quantity != null && quantity.QuantityInContracts == null)
|
||||
return new ArgumentError($"Quantity for {side}.{type} required in contracts");
|
||||
return ArgumentError.Invalid("Quantity", $"Quantity for {side}.{type} required in contracts");
|
||||
|
||||
return null;
|
||||
}
|
||||
|
@ -188,7 +188,7 @@ namespace CryptoExchange.Net.Sockets
|
||||
|
||||
CurrentResponses++;
|
||||
if (CurrentResponses == RequiredResponses)
|
||||
Response = message.Data;
|
||||
Response = message.Data;
|
||||
|
||||
if (Result?.Success != false)
|
||||
// If an error result is already set don't override that
|
||||
@ -219,7 +219,7 @@ namespace CryptoExchange.Net.Sockets
|
||||
return;
|
||||
|
||||
Completed = true;
|
||||
Result = new CallResult<THandlerResponse>(new CancellationRequestedError(null, "Query timeout", null));
|
||||
Result = new CallResult<THandlerResponse>(new TimeoutError());
|
||||
ContinueAwaiter?.Set();
|
||||
_event.Set();
|
||||
}
|
||||
|
@ -477,7 +477,7 @@ namespace CryptoExchange.Net.Sockets
|
||||
|
||||
if (!accessor.IsValid && !ApiClient.ProcessUnparsableMessages)
|
||||
{
|
||||
_logger.FailedToParse(SocketId, result.Error!.Message);
|
||||
_logger.FailedToParse(SocketId, result.Error!.Message ?? result.Error!.ErrorDescription!);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -765,7 +765,7 @@ namespace CryptoExchange.Net.Sockets
|
||||
public virtual async Task<CallResult> SendAndWaitQueryAsync(Query query, AsyncResetEvent? continueEvent = null, CancellationToken ct = default)
|
||||
{
|
||||
await SendAndWaitIntAsync(query, continueEvent, ct).ConfigureAwait(false);
|
||||
return query.Result ?? new CallResult(new ServerError("Timeout"));
|
||||
return query.Result ?? new CallResult(new TimeoutError());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -779,7 +779,7 @@ namespace CryptoExchange.Net.Sockets
|
||||
public virtual async Task<CallResult<THandlerResponse>> SendAndWaitQueryAsync<THandlerResponse>(Query<THandlerResponse> query, AsyncResetEvent? continueEvent = null, CancellationToken ct = default)
|
||||
{
|
||||
await SendAndWaitIntAsync(query, continueEvent, ct).ConfigureAwait(false);
|
||||
return query.TypedResult ?? new CallResult<THandlerResponse>(new ServerError("Timeout"));
|
||||
return query.TypedResult ?? new CallResult<THandlerResponse>(new TimeoutError());
|
||||
}
|
||||
|
||||
private async Task SendAndWaitIntAsync(Query query, AsyncResetEvent? continueEvent, CancellationToken ct = default)
|
||||
|
@ -184,7 +184,7 @@ namespace CryptoExchange.Net.Trackers.Klines
|
||||
|
||||
if (!subResult)
|
||||
{
|
||||
_logger.KlineTrackerStartFailed(SymbolName, subResult.Error!.Message, subResult.Error.Exception);
|
||||
_logger.KlineTrackerStartFailed(SymbolName, subResult.Error!.Message ?? subResult.Error!.ErrorDescription!, subResult.Error.Exception);
|
||||
Status = SyncStatus.Disconnected;
|
||||
return subResult;
|
||||
}
|
||||
|
@ -207,7 +207,7 @@ namespace CryptoExchange.Net.Trackers.Trades
|
||||
|
||||
if (!subResult)
|
||||
{
|
||||
_logger.TradeTrackerStartFailed(SymbolName, subResult.Error!.Message, subResult.Error.Exception);
|
||||
_logger.TradeTrackerStartFailed(SymbolName, subResult.Error!.Message ?? subResult.Error!.ErrorDescription!, subResult.Error.Exception);
|
||||
Status = SyncStatus.Disconnected;
|
||||
return subResult;
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user