From d42de1fe906f5d3e897f407144e085351621780d Mon Sep 17 00:00:00 2001 From: Jkorf Date: Mon, 25 Aug 2025 09:58:03 +0200 Subject: [PATCH 1/5] Added support for parsing REST response even though status indicates error --- CryptoExchange.Net/Clients/RestApiClient.cs | 11 +++++++---- CryptoExchange.Net/Objects/RequestDefinition.cs | 5 +++++ .../Objects/RequestDefinitionCache.cs | 13 +++++++++---- 3 files changed, 21 insertions(+), 8 deletions(-) diff --git a/CryptoExchange.Net/Clients/RestApiClient.cs b/CryptoExchange.Net/Clients/RestApiClient.cs index 4f85a04..aa98487 100644 --- a/CryptoExchange.Net/Clients/RestApiClient.cs +++ b/CryptoExchange.Net/Clients/RestApiClient.cs @@ -239,7 +239,7 @@ namespace CryptoExchange.Net.Clients additionalHeaders); _logger.RestApiSendRequest(request.RequestId, definition, request.Content, string.IsNullOrEmpty(request.Uri.Query) ? "-" : request.Uri.Query, string.Join(", ", request.GetHeaders().Select(h => h.Key + $"=[{string.Join(",", h.Value)}]"))); TotalRequestsMade++; - var result = await GetResponseAsync(request, definition.RateLimitGate, cancellationToken).ConfigureAwait(false); + var result = await GetResponseAsync(definition, request, definition.RateLimitGate, cancellationToken).ConfigureAwait(false); if (result.Error is not CancellationRequestedError) { var originalData = OutputOriginalData ? result.OriginalData : "[Data only available when OutputOriginal = true]"; @@ -424,11 +424,13 @@ namespace CryptoExchange.Net.Clients /// /// Executes the request and returns the result deserialized into the type parameter class /// + /// The request definition /// The request object to execute /// The ratelimit gate used /// Cancellation token /// protected virtual async Task> GetResponseAsync( + RequestDefinition requestDefinition, IRequest request, IRateLimitGate? gate, CancellationToken cancellationToken) @@ -448,7 +450,7 @@ namespace CryptoExchange.Net.Clients var outputOriginalData = ApiOptions.OutputOriginalData ?? ClientOptions.OutputOriginalData; accessor = CreateAccessor(); - if (!response.IsSuccessStatusCode) + if (!response.IsSuccessStatusCode && !requestDefinition.TryParseOnNonSuccess) { // Error response var readResult = await accessor.Read(responseStream, true).ConfigureAwait(false); @@ -488,7 +490,7 @@ namespace CryptoExchange.Net.Clients } // Json response received - var parsedError = TryParseError(response.ResponseHeaders, accessor); + var parsedError = TryParseError(requestDefinition, response.ResponseHeaders, accessor); if (parsedError != null) { if (parsedError is ServerRateLimitError rateError) @@ -541,10 +543,11 @@ namespace CryptoExchange.Net.Clients /// This method will be called for each response to be able to check if the response is an error or not. /// If the response is an error this method should return the parsed error, else it should return null /// + /// Request definition /// Data accessor /// The response headers /// Null if not an error, Error otherwise - protected virtual Error? TryParseError(KeyValuePair[] responseHeaders, IMessageAccessor accessor) => null; + protected virtual Error? TryParseError(RequestDefinition requestDefinition, KeyValuePair[] responseHeaders, IMessageAccessor accessor) => null; /// /// Can be used to indicate that a request should be retried. Defaults to false. Make sure to retry a max number of times (based on the the tries parameter) or the request will retry forever. diff --git a/CryptoExchange.Net/Objects/RequestDefinition.cs b/CryptoExchange.Net/Objects/RequestDefinition.cs index 6ce9ab4..429100d 100644 --- a/CryptoExchange.Net/Objects/RequestDefinition.cs +++ b/CryptoExchange.Net/Objects/RequestDefinition.cs @@ -62,6 +62,11 @@ namespace CryptoExchange.Net.Objects /// public bool PreventCaching { get; set; } + /// + /// Whether the response to this requests should attempted to be parsed even when the status indicates failure + /// + public bool TryParseOnNonSuccess { get; set; } + /// /// Connection id /// diff --git a/CryptoExchange.Net/Objects/RequestDefinitionCache.cs b/CryptoExchange.Net/Objects/RequestDefinitionCache.cs index 9c50a0b..1fac34b 100644 --- a/CryptoExchange.Net/Objects/RequestDefinitionCache.cs +++ b/CryptoExchange.Net/Objects/RequestDefinitionCache.cs @@ -46,6 +46,7 @@ namespace CryptoExchange.Net.Objects /// Parameter position /// Array serialization type /// Prevent request caching + /// Try parse the response even when status is not success /// public RequestDefinition GetOrCreate( HttpMethod method, @@ -57,8 +58,9 @@ namespace CryptoExchange.Net.Objects RequestBodyFormat? requestBodyFormat = null, HttpMethodParameterPosition? parameterPosition = null, ArrayParametersSerialization? arraySerialization = null, - bool? preventCaching = null) - => GetOrCreate(method + path, method, path, rateLimitGate, weight, authenticated, limitGuard, requestBodyFormat, parameterPosition, arraySerialization, preventCaching); + bool? preventCaching = null, + bool? tryParseOnNonSuccess = null) + => GetOrCreate(method + path, method, path, rateLimitGate, weight, authenticated, limitGuard, requestBodyFormat, parameterPosition, arraySerialization, preventCaching, tryParseOnNonSuccess); /// /// Get a definition if it is already in the cache or create a new definition and add it to the cache @@ -74,6 +76,7 @@ namespace CryptoExchange.Net.Objects /// Parameter position /// Array serialization type /// Prevent request caching + /// Try parse the response even when status is not success /// public RequestDefinition GetOrCreate( string identifier, @@ -86,7 +89,8 @@ namespace CryptoExchange.Net.Objects RequestBodyFormat? requestBodyFormat = null, HttpMethodParameterPosition? parameterPosition = null, ArrayParametersSerialization? arraySerialization = null, - bool? preventCaching = null) + bool? preventCaching = null, + bool? tryParseOnNonSuccess = null) { if (!_definitions.TryGetValue(identifier, out var def)) @@ -100,7 +104,8 @@ namespace CryptoExchange.Net.Objects ArraySerialization = arraySerialization, RequestBodyFormat = requestBodyFormat, ParameterPosition = parameterPosition, - PreventCaching = preventCaching ?? false + PreventCaching = preventCaching ?? false, + TryParseOnNonSuccess = tryParseOnNonSuccess ?? false }; _definitions.TryAdd(identifier, def); } From 99465f99a1bda7fd5eb402e5b4541c00c95268e4 Mon Sep 17 00:00:00 2001 From: Jkorf Date: Mon, 25 Aug 2025 10:00:42 +0200 Subject: [PATCH 2/5] Fixed test --- .../TestImplementations/Sockets/TestSubscription.cs | 4 ++-- .../Sockets/TestSubscriptionWithResponseCheck.cs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CryptoExchange.Net.UnitTests/TestImplementations/Sockets/TestSubscription.cs b/CryptoExchange.Net.UnitTests/TestImplementations/Sockets/TestSubscription.cs index dd6007b..f9cb121 100644 --- a/CryptoExchange.Net.UnitTests/TestImplementations/Sockets/TestSubscription.cs +++ b/CryptoExchange.Net.UnitTests/TestImplementations/Sockets/TestSubscription.cs @@ -28,7 +28,7 @@ namespace CryptoExchange.Net.UnitTests.TestImplementations.Sockets return new CallResult(null); } - public override Query GetSubQuery(SocketConnection connection) => new TestQuery("sub", new object(), false, 1); - public override Query GetUnsubQuery() => new TestQuery("unsub", new object(), false, 1); + protected override Query GetSubQuery(SocketConnection connection) => new TestQuery("sub", new object(), false, 1); + protected override Query GetUnsubQuery(SocketConnection connection) => new TestQuery("unsub", new object(), false, 1); } } diff --git a/CryptoExchange.Net.UnitTests/TestImplementations/Sockets/TestSubscriptionWithResponseCheck.cs b/CryptoExchange.Net.UnitTests/TestImplementations/Sockets/TestSubscriptionWithResponseCheck.cs index c7a975c..6c1e616 100644 --- a/CryptoExchange.Net.UnitTests/TestImplementations/Sockets/TestSubscriptionWithResponseCheck.cs +++ b/CryptoExchange.Net.UnitTests/TestImplementations/Sockets/TestSubscriptionWithResponseCheck.cs @@ -28,7 +28,7 @@ namespace CryptoExchange.Net.UnitTests.TestImplementations.Sockets return new CallResult(null); } - public override Query GetSubQuery(SocketConnection connection) => new TestChannelQuery(_channel, "subscribe", false, 1); - public override Query GetUnsubQuery() => new TestChannelQuery(_channel, "unsubscribe", false, 1); + protected override Query GetSubQuery(SocketConnection connection) => new TestChannelQuery(_channel, "subscribe", false, 1); + protected override Query GetUnsubQuery(SocketConnection connection) => new TestChannelQuery(_channel, "unsubscribe", false, 1); } } From 993a44de3566dad68088a80bb24129e207aed0af Mon Sep 17 00:00:00 2001 From: Jkorf Date: Mon, 25 Aug 2025 10:03:17 +0200 Subject: [PATCH 3/5] Updated to version 9.6.0 --- CryptoExchange.Net/CryptoExchange.Net.csproj | 6 +++--- README.md | 6 ++++++ 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/CryptoExchange.Net/CryptoExchange.Net.csproj b/CryptoExchange.Net/CryptoExchange.Net.csproj index d1404e4..8b8b632 100644 --- a/CryptoExchange.Net/CryptoExchange.Net.csproj +++ b/CryptoExchange.Net/CryptoExchange.Net.csproj @@ -6,9 +6,9 @@ CryptoExchange.Net JKorf CryptoExchange.Net is a base library which is used to implement different cryptocurrency (exchange) API's. It provides a standardized way of implementing different API's, which results in a very similar experience for users of the API implementations. - 9.5.0 - 9.5.0 - 9.5.0 + 9.6.0 + 9.6.0 + 9.6.0 false OKX;OKX.Net;Mexc;Mexc.Net;Kucoin;Kucoin.Net;Kraken;Kraken.Net;Huobi;Huobi.Net;CoinEx;CoinEx.Net;Bybit;Bybit.Net;Bitget;Bitget.Net;Bitfinex;Bitfinex.Net;Binance;Binance.Net;CryptoCurrency;CryptoCurrency Exchange;CryptoExchange.Net git diff --git a/README.md b/README.md index 532dd89..5c15e19 100644 --- a/README.md +++ b/README.md @@ -59,6 +59,12 @@ Make a one time donation in a crypto currency of your choice. If you prefer to d Alternatively, sponsor me on Github using [Github Sponsors](https://github.com/sponsors/JKorf). ## Release notes +* Version 9.6.0 - 25 Aug 2025 + * Added support for parsing REST response even though status indicates error + * Added better support for subscriptions without subscribe confirmation + * Added check in websocket for receiving 401 unauthorized http response status when 101 was expected + * Removed obsolete attribute on Error.Code property, updated the description + * Version 9.5.0 - 19 Aug 2025 * Added better error handling support * Added ErrorDescription, ErrorType and IsTransient to Error object From 3eda48836146b4d2cf0091aaecb28dce6a2e6e0a Mon Sep 17 00:00:00 2001 From: Jkorf Date: Mon, 25 Aug 2025 10:15:14 +0200 Subject: [PATCH 4/5] Updated CryptoExchange.Net.Protobuf to CryptoExchange.Net version 9.5.0 --- CryptoExchange.Net.Protobuf/CryptoExchange.Net.Protobuf.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CryptoExchange.Net.Protobuf/CryptoExchange.Net.Protobuf.csproj b/CryptoExchange.Net.Protobuf/CryptoExchange.Net.Protobuf.csproj index d80f1a1..7b9c231 100644 --- a/CryptoExchange.Net.Protobuf/CryptoExchange.Net.Protobuf.csproj +++ b/CryptoExchange.Net.Protobuf/CryptoExchange.Net.Protobuf.csproj @@ -41,7 +41,7 @@ CryptoExchange.Net.Protobuf.xml - + \ No newline at end of file From b215cccda4b47d87d10ab23ac5b7f16514b99a2d Mon Sep 17 00:00:00 2001 From: Jkorf Date: Mon, 25 Aug 2025 10:17:35 +0200 Subject: [PATCH 5/5] Updated to version 9.6.0 --- .../CryptoExchange.Net.Protobuf.csproj | 6 +++--- CryptoExchange.Net.Protobuf/README.md | 3 +++ 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/CryptoExchange.Net.Protobuf/CryptoExchange.Net.Protobuf.csproj b/CryptoExchange.Net.Protobuf/CryptoExchange.Net.Protobuf.csproj index 7b9c231..a299f13 100644 --- a/CryptoExchange.Net.Protobuf/CryptoExchange.Net.Protobuf.csproj +++ b/CryptoExchange.Net.Protobuf/CryptoExchange.Net.Protobuf.csproj @@ -6,9 +6,9 @@ CryptoExchange.Net.Protobuf JKorf Protobuf support for CryptoExchange.Net - 9.5.0 - 9.5.0 - 9.5.0 + 9.6.0 + 9.6.0 + 9.6.0 false CryptoExchange;CryptoExchange.Net git diff --git a/CryptoExchange.Net.Protobuf/README.md b/CryptoExchange.Net.Protobuf/README.md index 1492551..9fa3bd9 100644 --- a/CryptoExchange.Net.Protobuf/README.md +++ b/CryptoExchange.Net.Protobuf/README.md @@ -5,6 +5,9 @@ Protobuf support for CryptoExchange.Net. ## Release notes +* Version 9.6.0 - 25 Aug 2025 + * Updated CryptoExchange.Net version to 9.6.0 + * Version 9.5.0 - 19 Aug 2025 * Updated CryptoExchange.Net version to 9.5.0