1
0
mirror of https://github.com/JKorf/CryptoExchange.Net synced 2025-09-03 06:01:40 +00:00
This commit is contained in:
Jkorf 2025-08-12 15:20:41 +02:00
parent 40977ebdbe
commit ed1915a88f
27 changed files with 534 additions and 102 deletions

View File

@ -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 ErrorCollection ErrorMapping { get; } = new ErrorCollection([]);
/// <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
{

View File

@ -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)
{
@ -633,7 +636,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>

View File

@ -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)

View File

@ -286,7 +286,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(ex.Message, ex));
}
}
@ -338,7 +338,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);

View File

@ -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, false, "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) => new ArgumentError(_missingInfo with { Message = $"{_missingInfo.Message} '{parameterName}'" }, 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.RequestRateLimited, 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.RequestRateLimited, 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.MissingCredentials, 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, "");
/// <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) { }
}
}

View File

@ -0,0 +1,57 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace CryptoExchange.Net.Objects.Errors
{
/// <summary>
/// Error mapping collection
/// </summary>
public class ErrorCollection
{
private Dictionary<string, ErrorEvaluator> _evaluators = new Dictionary<string, ErrorEvaluator>();
private Dictionary<string, ErrorInfo> _directMapping = new Dictionary<string, ErrorInfo>();
/// <summary>
/// ctor
/// </summary>
public ErrorCollection(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)
{
message = 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 };
}
}
}

View 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;
}
}
}

View 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;
}
}
}

View File

@ -0,0 +1,166 @@
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>
/// Invalid listen key
/// </summary>
InvalidListenKey,
/// <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>
RequestRateLimited,
/// <summary>
/// Connection rate limit error, too many connections
/// </summary>
ConnectionRateLimited,
/// <summary>
/// Subscription rate limit error, too many subscriptions
/// </summary>
SubscriptionRateLimited,
/// <summary>
/// Order rate limit error, too many orders
/// </summary>
OrderRateLimited,
/// <summary>
/// Timestamp invalid
/// </summary>
TimestampInvalid,
/// <summary>
/// Request signature invalid
/// </summary>
SignatureInvalid,
/// <summary>
/// Unknown symbol
/// </summary>
UnknownSymbol,
/// <summary>
/// Unknown asset
/// </summary>
UnknownAsset,
/// <summary>
/// Unknown order
/// </summary>
UnknownOrder,
/// <summary>
/// Duplicate subscription
/// </summary>
DuplicateSubscription,
/// <summary>
/// Invalid quantity
/// </summary>
QuantityInvalid,
/// <summary>
/// Invalid price
/// </summary>
PriceInvalid,
/// <summary>
/// Parameter(s) for stop or tp/sl order invalid
/// </summary>
StopParametersInvalid,
/// <summary>
/// Not enough balance to execute order
/// </summary>
BalanceInsufficient,
/// <summary>
/// Client order id already in use
/// </summary>
DuplicateClientOrderId,
/// <summary>
/// Symbol is not currently trading
/// </summary>
SymbolNotTrading,
/// <summary>
/// Order rejected due to order type or time in force restrictions
/// </summary>
OrderConfigurationRejected,
/// <summary>
/// Order type not allowed
/// </summary>
OrderTypeInvalid,
/// <summary>
/// There is no open position
/// </summary>
NoPosition,
/// <summary>
/// Error in the internal system
/// </summary>
SystemError,
/// <summary>
/// The target object is not in the correct state for an operation
/// </summary>
TargetIncorrectState
#endregion
}
}

View File

@ -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
{

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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)

View File

@ -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)

View File

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

View File

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

View File

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

View File

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

View File

@ -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)

View File

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

View File

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