diff --git a/CryptoExchange.Net.Protobuf/Converters/Protobuf/DynamicProtobufConverter.cs b/CryptoExchange.Net.Protobuf/Converters/Protobuf/DynamicProtobufConverter.cs index cf83620..d9f93b4 100644 --- a/CryptoExchange.Net.Protobuf/Converters/Protobuf/DynamicProtobufConverter.cs +++ b/CryptoExchange.Net.Protobuf/Converters/Protobuf/DynamicProtobufConverter.cs @@ -1,22 +1,22 @@ -using CryptoExchange.Net.Converters.MessageParsing; -using CryptoExchange.Net.Converters.MessageParsing.DynamicConverters; -using CryptoExchange.Net.Objects; -using LightProto; -using System; -using System.Collections.Generic; -using System.Net.WebSockets; -using System.Text; +//using CryptoExchange.Net.Converters.MessageParsing; +//using CryptoExchange.Net.Converters.MessageParsing.DynamicConverters; +//using CryptoExchange.Net.Objects; +//using LightProto; +//using System; +//using System.Collections.Generic; +//using System.Net.WebSockets; +//using System.Text; -namespace CryptoExchange.Net.Protobuf.Converters.Protobuf -{ - public abstract class DynamicProtobufConverter : ISocketMessageHandler - { - public object Deserialize(ReadOnlySpan data, Type type) - { - var result = Serializer.Deserialize(data); - return result; - } +//namespace CryptoExchange.Net.Protobuf.Converters.Protobuf +//{ +// public abstract class DynamicProtobufConverter : ISocketMessageHandler +// { +// public object Deserialize(ReadOnlySpan data, Type type) +// { +// var result = Serializer.Deserialize(data); +// return result; +// } - public abstract string GetMessageIdentifier(ReadOnlySpan data, WebSocketMessageType? webSocketMessageType); - } -} +// public abstract string GetMessageIdentifier(ReadOnlySpan data, WebSocketMessageType? webSocketMessageType); +// } +//} diff --git a/CryptoExchange.Net.Protobuf/CryptoExchange.Net.Protobuf.xml b/CryptoExchange.Net.Protobuf/CryptoExchange.Net.Protobuf.xml index 69080ea..0b7b110 100644 --- a/CryptoExchange.Net.Protobuf/CryptoExchange.Net.Protobuf.xml +++ b/CryptoExchange.Net.Protobuf/CryptoExchange.Net.Protobuf.xml @@ -4,11 +4,6 @@ CryptoExchange.Net.Protobuf - - - Runtime type model - - System.Text.Json message accessor diff --git a/CryptoExchange.Net/Clients/RestApiClient.cs b/CryptoExchange.Net/Clients/RestApiClient.cs index 0031073..914f6bd 100644 --- a/CryptoExchange.Net/Clients/RestApiClient.cs +++ b/CryptoExchange.Net/Clients/RestApiClient.cs @@ -486,6 +486,10 @@ namespace CryptoExchange.Net.Clients memoryStream.Position = 0; originalData = await reader.ReadToEndAsync().ConfigureAwait(false); + if (_logger.IsEnabled(LogLevel.Trace)) +#warning TODO extension + _logger.LogTrace("[Req {RequestId}] Received response: {Data}", request.RequestId, originalData); + // Continue processing from the memory stream since the response stream is already read and we can't seek it responseStream.Close(); memoryStream.Position = 0; @@ -494,9 +498,12 @@ namespace CryptoExchange.Net.Clients if (!response.IsSuccessStatusCode && !requestDefinition.TryParseOnNonSuccess) { + // If the response status is not success it is an error by definition + Error error; if (response.StatusCode == (HttpStatusCode)418 || response.StatusCode == (HttpStatusCode)429) { + // Specifically handle rate limit errors var rateError = await MessageHandler.ParseErrorRateLimitResponse( (int)response.StatusCode, state, @@ -512,6 +519,7 @@ namespace CryptoExchange.Net.Clients } else { + // Handle a 'normal' error response. Can still be either a json error message or some random HTML or other string error = await MessageHandler.ParseErrorResponse( (int)response.StatusCode, state, @@ -526,7 +534,7 @@ namespace CryptoExchange.Net.Clients // Success status code and expected empty response, assume it's correct return new WebCallResult(response.StatusCode, response.HttpVersion, response.ResponseHeaders, sw.Elapsed, 0, originalData, request.RequestId, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), ResultDataSource.Server, default, null); - // Data response received + // Data response received, inspect the message and check if it is an error or not var parsedError = await MessageHandler.CheckForErrorResponse( requestDefinition, state, @@ -547,8 +555,17 @@ namespace CryptoExchange.Net.Clients return new WebCallResult(response.StatusCode, response.HttpVersion, response.ResponseHeaders, sw.Elapsed, response.ContentLength, originalData, request.RequestId, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), ResultDataSource.Server, default, parsedError); } + // Try deserialization into the expected type var (deserializeResult, deserializeError) = await MessageHandler.TryDeserializeAsync(responseStream, state, cancellationToken).ConfigureAwait(false); - return new WebCallResult(response.StatusCode, response.HttpVersion, response.ResponseHeaders, sw.Elapsed, response.ContentLength, originalData, request.RequestId, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), ResultDataSource.Server, deserializeResult, deserializeError); + if (deserializeError != null) + return new WebCallResult(response.StatusCode, response.HttpVersion, response.ResponseHeaders, sw.Elapsed, response.ContentLength, originalData, request.RequestId, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), ResultDataSource.Server, deserializeResult, deserializeError); ; + + // Check the deserialized response to see if it's an error or not + var responseError = MessageHandler.CheckDeserializedResponse(response.ResponseHeaders, deserializeResult); + if (responseError != null) + return new WebCallResult(response.StatusCode, response.HttpVersion, response.ResponseHeaders, sw.Elapsed, response.ContentLength, originalData, request.RequestId, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), ResultDataSource.Server, deserializeResult, responseError); + + return new WebCallResult(response.StatusCode, response.HttpVersion, response.ResponseHeaders, sw.Elapsed, response.ContentLength, originalData, request.RequestId, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), ResultDataSource.Server, deserializeResult, null); } catch (HttpRequestException requestException) { diff --git a/CryptoExchange.Net/Converters/MessageParsing/DynamicConverters/IRestMessageHandler.cs b/CryptoExchange.Net/Converters/MessageParsing/DynamicConverters/IRestMessageHandler.cs index 21661f9..2e54786 100644 --- a/CryptoExchange.Net/Converters/MessageParsing/DynamicConverters/IRestMessageHandler.cs +++ b/CryptoExchange.Net/Converters/MessageParsing/DynamicConverters/IRestMessageHandler.cs @@ -44,7 +44,10 @@ namespace CryptoExchange.Net.Converters.MessageParsing.DynamicConverters Stream responseStream); /// - /// Check if the response is an error response; if so return the error + /// Check if the response is an error response; if so return the error.
+ /// Note that if the API returns a standard result wrapper, something like this: + /// { "code": 400, "msg": "error", "data": {} } + /// then the `CheckDeserializedResponse` method should be used for checking the result ///
ValueTask CheckForErrorResponse( RequestDefinition request, @@ -59,6 +62,11 @@ namespace CryptoExchange.Net.Converters.MessageParsing.DynamicConverters Stream responseStream, object? state, CancellationToken ct); + + /// + /// Check whether the resulting T object indicates an error or not + /// + Error? CheckDeserializedResponse(HttpResponseHeaders responseHeaders, T result); } } diff --git a/CryptoExchange.Net/Converters/SystemTextJson/MessageHandlers/JsonRestMessageHandler.cs b/CryptoExchange.Net/Converters/SystemTextJson/MessageHandlers/JsonRestMessageHandler.cs index a4f14f3..8741210 100644 --- a/CryptoExchange.Net/Converters/SystemTextJson/MessageHandlers/JsonRestMessageHandler.cs +++ b/CryptoExchange.Net/Converters/SystemTextJson/MessageHandlers/JsonRestMessageHandler.cs @@ -118,50 +118,19 @@ namespace CryptoExchange.Net.Converters.SystemTextJson.MessageConverters result = await JsonSerializer.DeserializeAsync(responseStream, Options)!.ConfigureAwait(false)!; } return (result, null); - } - catch (HttpRequestException requestException) + } + catch (JsonException ex) { - // Request exception, can't reach server for instance - var error = new WebError(requestException.Message, requestException); - return (default, error); + var info = $"Json deserialization failed: {ex.Message}, Path: {ex.Path}, LineNumber: {ex.LineNumber}, LinePosition: {ex.BytePositionInLine}"; + return (default, new DeserializeError(info, ex)); } - catch (OperationCanceledException canceledException) + catch (Exception ex) { - if (cancellationToken != default && canceledException.CancellationToken == cancellationToken) - { - // Cancellation token canceled by caller - return (default, new CancellationRequestedError(canceledException)); - } - else - { - // Request timed out - var error = new WebError($"Request timed out", exception: canceledException); - error.ErrorType = ErrorType.Timeout; - return (default, error); - } - } - catch (ArgumentException argumentException) - { - if (argumentException.Message.StartsWith("Only HTTP/")) - { - // Unsupported HTTP version error .net framework - var error = ArgumentError.Invalid(nameof(RestExchangeOptions.HttpVersion), $"Invalid HTTP version: " + argumentException.Message); - return (default, error); - } - - throw; - } - catch (NotSupportedException notSupportedException) - { - if (notSupportedException.Message.StartsWith("Request version value must be one of")) - { - // Unsupported HTTP version error dotnet code - var error = ArgumentError.Invalid(nameof(RestExchangeOptions.HttpVersion), $"Invalid HTTP version: " + notSupportedException.Message); - return (default, error); - } - - throw; + return (default, new DeserializeError($"Json deserialization failed: {ex.Message}", ex)); } } + + /// + public virtual Error? CheckDeserializedResponse(HttpResponseHeaders responseHeaders, T result) => null; } } diff --git a/CryptoExchange.Net/Objects/RestRequestConfiguration.cs b/CryptoExchange.Net/Objects/RestRequestConfiguration.cs index acbb75c..1d98143 100644 --- a/CryptoExchange.Net/Objects/RestRequestConfiguration.cs +++ b/CryptoExchange.Net/Objects/RestRequestConfiguration.cs @@ -80,11 +80,15 @@ namespace CryptoExchange.Net.Objects /// /// Get the parameter collection based on the ParameterPosition /// - public IDictionary? GetPositionParameters() + public IDictionary GetPositionParameters() { if (ParameterPosition == HttpMethodParameterPosition.InBody) + { + BodyParameters ??= new Dictionary(); return BodyParameters; + } + QueryParameters ??= new Dictionary(); return QueryParameters; } diff --git a/CryptoExchange.Net/Sockets/SocketConnection.cs b/CryptoExchange.Net/Sockets/SocketConnection.cs index 05c5c86..f23c4d3 100644 --- a/CryptoExchange.Net/Sockets/SocketConnection.cs +++ b/CryptoExchange.Net/Sockets/SocketConnection.cs @@ -565,7 +565,7 @@ namespace CryptoExchange.Net.Sockets if (deserializationType == null) { // No handler found for identifier either, can't process - _logger.LogWarning("Failed to determine message type. Data: {Message}", Encoding.UTF8.GetString(data.ToArray())); + _logger.LogWarning("Failed to determine message type for identifier {Identifier}. Data: {Message}", messageIdentifier, Encoding.UTF8.GetString(data.ToArray())); return; }