From 42736f3003305a73346865223607e1a8f1c57698 Mon Sep 17 00:00:00 2001 From: JKorf Date: Sun, 7 Dec 2025 10:43:44 +0100 Subject: [PATCH] wip --- CryptoExchange.Net/Clients/RestApiClient.cs | 6 +- .../ISocketMessageHandler.cs | 8 +- .../MessageConverterTypes.cs | 2 +- .../MessageHandlers/JsonRestMessageHandler.cs | 11 +- .../JsonSocketMessageHandler.cs | 53 +++++--- .../JsonSocketPreloadMessageHandler.cs | 3 + .../Interfaces/IMessageProcessor.cs | 3 + .../RestApiClientLoggingExtensions.cs | 11 ++ .../HighPerf/HighPerfWebSocketClient.cs | 4 - CryptoExchange.Net/Sockets/MessageRouter.cs | 125 +++++++++++------- CryptoExchange.Net/Sockets/Query.cs | 7 + .../Sockets/SocketConnection.cs | 18 --- CryptoExchange.Net/Sockets/Subscription.cs | 5 + .../Testing/SocketSubscriptionValidator.cs | 13 +- 14 files changed, 169 insertions(+), 100 deletions(-) diff --git a/CryptoExchange.Net/Clients/RestApiClient.cs b/CryptoExchange.Net/Clients/RestApiClient.cs index 4a0d47a..bfa1b2a 100644 --- a/CryptoExchange.Net/Clients/RestApiClient.cs +++ b/CryptoExchange.Net/Clients/RestApiClient.cs @@ -223,7 +223,6 @@ namespace CryptoExchange.Net.Clients string? cacheKey = null; if (ShouldCache(definition)) { -#warning caching should be static per api client type cacheKey = baseAddress + definition + uriParameters?.ToFormData(); _logger.CheckingCache(cacheKey); var cachedValue = _cache.Get(cacheKey, ClientOptions.CachingMaxAge); @@ -487,15 +486,14 @@ namespace CryptoExchange.Net.Clients // we'll need to copy it as the stream isn't seekable, and thus we can only read it once var memoryStream = new MemoryStream(); await responseStream.CopyToAsync(memoryStream).ConfigureAwait(false); - using var reader = new StreamReader(memoryStream, Encoding.UTF8,false, 4096, true); + using var reader = new StreamReader(memoryStream, Encoding.UTF8, false, 4096, true); if (outputOriginalData) { 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); + _logger.RestApiReceivedResponse(request.RequestId, originalData); } // Continue processing from the memory stream since the response stream is already read and we can't seek it diff --git a/CryptoExchange.Net/Converters/MessageParsing/DynamicConverters/ISocketMessageHandler.cs b/CryptoExchange.Net/Converters/MessageParsing/DynamicConverters/ISocketMessageHandler.cs index bbc1ff4..00bd1d6 100644 --- a/CryptoExchange.Net/Converters/MessageParsing/DynamicConverters/ISocketMessageHandler.cs +++ b/CryptoExchange.Net/Converters/MessageParsing/DynamicConverters/ISocketMessageHandler.cs @@ -9,11 +9,13 @@ namespace CryptoExchange.Net.Converters.MessageParsing.DynamicConverters public interface ISocketMessageHandler { /// - /// Get an identifier for the message which can be used to link it to a listener + /// Get an identifier for the message which can be used to determine the type of the message /// - //string? GetMessageIdentifier(ReadOnlySpan data, WebSocketMessageType? webSocketMessageType); - string? GetTypeIdentifier(ReadOnlySpan data, WebSocketMessageType? webSocketMessageType); + + /// + /// Get optional topic filter, for example a symbol name + /// string? GetTopicFilter(object deserializedObject); /// diff --git a/CryptoExchange.Net/Converters/MessageParsing/DynamicConverters/MessageConverterTypes.cs b/CryptoExchange.Net/Converters/MessageParsing/DynamicConverters/MessageConverterTypes.cs index 3e5b8b5..ca4ab20 100644 --- a/CryptoExchange.Net/Converters/MessageParsing/DynamicConverters/MessageConverterTypes.cs +++ b/CryptoExchange.Net/Converters/MessageParsing/DynamicConverters/MessageConverterTypes.cs @@ -20,7 +20,7 @@ namespace CryptoExchange.Net.Converters.MessageParsing.DynamicConverters /// /// The fields this evaluator has to look for /// - public MessageFieldReference[] Fields { get; set; } + public MessageFieldReference[] Fields { get; set; } = []; /// /// The callback for getting the identifier string /// diff --git a/CryptoExchange.Net/Converters/SystemTextJson/MessageHandlers/JsonRestMessageHandler.cs b/CryptoExchange.Net/Converters/SystemTextJson/MessageHandlers/JsonRestMessageHandler.cs index 5cfdbbd..2851b3f 100644 --- a/CryptoExchange.Net/Converters/SystemTextJson/MessageHandlers/JsonRestMessageHandler.cs +++ b/CryptoExchange.Net/Converters/SystemTextJson/MessageHandlers/JsonRestMessageHandler.cs @@ -6,6 +6,7 @@ using CryptoExchange.Net.Objects.Options; using CryptoExchange.Net.Requests; using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; using System.Net.Http; @@ -90,11 +91,19 @@ namespace CryptoExchange.Net.Converters.SystemTextJson.MessageConverters } /// +#if NET5_0_OR_GREATER + [UnconditionalSuppressMessage("AssemblyLoadTrimming", "IL3050:RequiresUnreferencedCode", Justification = "JsonSerializerOptions provided here has TypeInfoResolver set")] + [UnconditionalSuppressMessage("AssemblyLoadTrimming", "IL2026:RequiresUnreferencedCode", Justification = "JsonSerializerOptions provided here has TypeInfoResolver set")] +#endif public async ValueTask<(T? Result, Error? Error)> TryDeserializeAsync(Stream responseStream, CancellationToken cancellationToken) { try { - var result = await JsonSerializer.DeserializeAsync(responseStream, Options)!.ConfigureAwait(false)!; +#pragma warning disable IL2026 // Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code +#pragma warning disable IL3050 // Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling. + var result = await JsonSerializer.DeserializeAsync(responseStream, Options)!.ConfigureAwait(false)!; +#pragma warning restore IL3050 // Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling. +#pragma warning restore IL2026 // Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code return (result, null); } catch (JsonException ex) diff --git a/CryptoExchange.Net/Converters/SystemTextJson/MessageHandlers/JsonSocketMessageHandler.cs b/CryptoExchange.Net/Converters/SystemTextJson/MessageHandlers/JsonSocketMessageHandler.cs index da198f2..836b9f2 100644 --- a/CryptoExchange.Net/Converters/SystemTextJson/MessageHandlers/JsonSocketMessageHandler.cs +++ b/CryptoExchange.Net/Converters/SystemTextJson/MessageHandlers/JsonSocketMessageHandler.cs @@ -1,6 +1,10 @@ using CryptoExchange.Net.Converters.MessageParsing.DynamicConverters; using System; +#if !NETSTANDARD +using System.Collections.Frozen; +#endif using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Net.WebSockets; using System.Text; @@ -31,10 +35,14 @@ namespace CryptoExchange.Net.Converters.SystemTextJson private int _maxSearchDepth; private MessageEvaluator? _topEvaluator; private List? _searchFields; - - private Dictionary> _mapping; private Dictionary>? _baseTypeMapping; + private Dictionary>? _mapping; + /// + /// Add a mapping of a specific object of a type to a specific topic + /// + /// Type to get topic for + /// The topic retrieve delegate protected void AddTopicMapping(Func mapping) { _mapping ??= new Dictionary>(); @@ -126,6 +134,7 @@ namespace CryptoExchange.Net.Converters.SystemTextJson _initialized = true; } + /// public virtual string? GetTopicFilter(object deserializedObject) { if (_mapping == null) @@ -260,20 +269,28 @@ namespace CryptoExchange.Net.Converters.SystemTextJson } else { - if (reader.TokenType == JsonTokenType.Number) - value = reader.GetDecimal().ToString(); - else if (reader.TokenType == JsonTokenType.String) - value = reader.GetString()!; - else if (reader.TokenType == JsonTokenType.True - || reader.TokenType == JsonTokenType.False) - value = reader.GetBoolean().ToString()!; - else if (reader.TokenType == JsonTokenType.Null) - value = null; - else if (reader.TokenType == JsonTokenType.StartObject - || reader.TokenType == JsonTokenType.StartArray) - value = null; - else - continue; + switch (reader.TokenType) + { + case JsonTokenType.Number: + value = reader.GetDecimal().ToString(); + break; + case JsonTokenType.String: + value = reader.GetString()!; + break; + case JsonTokenType.True: + case JsonTokenType.False: + value = reader.GetBoolean().ToString()!; + break; + case JsonTokenType.Null: + value = null; + break; + case JsonTokenType.StartObject: + case JsonTokenType.StartArray: + value = null; + break; + default: + continue; + } } } @@ -321,6 +338,10 @@ namespace CryptoExchange.Net.Converters.SystemTextJson } /// +#if NET5_0_OR_GREATER + [UnconditionalSuppressMessage("AssemblyLoadTrimming", "IL3050:RequiresUnreferencedCode", Justification = "JsonSerializerOptions provided here has TypeInfoResolver set")] + [UnconditionalSuppressMessage("AssemblyLoadTrimming", "IL2026:RequiresUnreferencedCode", Justification = "JsonSerializerOptions provided here has TypeInfoResolver set")] +#endif public virtual object Deserialize(ReadOnlySpan data, Type type) { #pragma warning disable IL2026 // Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code diff --git a/CryptoExchange.Net/Converters/SystemTextJson/MessageHandlers/JsonSocketPreloadMessageHandler.cs b/CryptoExchange.Net/Converters/SystemTextJson/MessageHandlers/JsonSocketPreloadMessageHandler.cs index dcaff3e..f623f32 100644 --- a/CryptoExchange.Net/Converters/SystemTextJson/MessageHandlers/JsonSocketPreloadMessageHandler.cs +++ b/CryptoExchange.Net/Converters/SystemTextJson/MessageHandlers/JsonSocketPreloadMessageHandler.cs @@ -29,6 +29,9 @@ namespace CryptoExchange.Net.Converters.SystemTextJson /// protected abstract string? GetTypeIdentifier(JsonDocument document); + /// + /// Get optional topic filter, for example a symbol name + /// public virtual string? GetTopicFilter(object deserializedObject) => null; /// diff --git a/CryptoExchange.Net/Interfaces/IMessageProcessor.cs b/CryptoExchange.Net/Interfaces/IMessageProcessor.cs index b4bfd8d..f225a2c 100644 --- a/CryptoExchange.Net/Interfaces/IMessageProcessor.cs +++ b/CryptoExchange.Net/Interfaces/IMessageProcessor.cs @@ -28,6 +28,9 @@ namespace CryptoExchange.Net.Interfaces /// Handle a message /// CallResult Handle(SocketConnection connection, DateTime receiveTime, string? originalData, object result, MessageHandlerLink matchedHandler); + /// + /// Handle a message + /// CallResult Handle(SocketConnection connection, DateTime receiveTime, string? originalData, object result, MessageRoute route); /// /// Deserialize a message into object of type diff --git a/CryptoExchange.Net/Logging/Extensions/RestApiClientLoggingExtensions.cs b/CryptoExchange.Net/Logging/Extensions/RestApiClientLoggingExtensions.cs index f5078d3..84d3d67 100644 --- a/CryptoExchange.Net/Logging/Extensions/RestApiClientLoggingExtensions.cs +++ b/CryptoExchange.Net/Logging/Extensions/RestApiClientLoggingExtensions.cs @@ -22,6 +22,7 @@ namespace CryptoExchange.Net.Logging.Extensions private static readonly Action _restApiCacheHit; private static readonly Action _restApiCacheNotHit; private static readonly Action _restApiCancellationRequested; + private static readonly Action _restApiReceivedResponse; static RestApiClientLoggingExtensions() { @@ -90,6 +91,11 @@ namespace CryptoExchange.Net.Logging.Extensions new EventId(4012, "RestApiCancellationRequested"), "[Req {RequestId}] Request cancelled by user"); + _restApiReceivedResponse = LoggerMessage.Define( + LogLevel.Trace, + new EventId(4013, "RestApiReceivedResponse"), + "[Req {RequestId}] Received response: {Data}"); + } public static void RestApiErrorReceived(this ILogger logger, int? requestId, HttpStatusCode? responseStatusCode, long responseTime, string? error, string? originalData, Exception? exception) @@ -155,5 +161,10 @@ namespace CryptoExchange.Net.Logging.Extensions { _restApiCancellationRequested(logger, requestId, null); } + + public static void RestApiReceivedResponse(this ILogger logger, int requestId, string? originalData) + { + _restApiReceivedResponse(logger, requestId, originalData, null); + } } } diff --git a/CryptoExchange.Net/Sockets/HighPerf/HighPerfWebSocketClient.cs b/CryptoExchange.Net/Sockets/HighPerf/HighPerfWebSocketClient.cs index 58020c5..5731000 100644 --- a/CryptoExchange.Net/Sockets/HighPerf/HighPerfWebSocketClient.cs +++ b/CryptoExchange.Net/Sockets/HighPerf/HighPerfWebSocketClient.cs @@ -214,13 +214,9 @@ namespace CryptoExchange.Net.Sockets if (_ctsSource.IsCancellationRequested || !_processing) return false; -#warning todo logging overloads without id - _logger.SocketAddingBytesToSendBuffer(Id, 0, data); - try { await _socket!.SendAsync(new ArraySegment(data, 0, data.Length), type, true, _ctsSource.Token).ConfigureAwait(false); - _logger.SocketSentBytes(Id, 0, data.Length); return true; } catch (OperationCanceledException) diff --git a/CryptoExchange.Net/Sockets/MessageRouter.cs b/CryptoExchange.Net/Sockets/MessageRouter.cs index 74c8477..9c7b28d 100644 --- a/CryptoExchange.Net/Sockets/MessageRouter.cs +++ b/CryptoExchange.Net/Sockets/MessageRouter.cs @@ -10,14 +10,17 @@ using System.Threading.Tasks; namespace CryptoExchange.Net.Sockets { + /// + /// Message router + /// public class MessageRouter { /// - /// + /// The routes registered for this router /// public MessageRoute[] Routes { get; } - // + /// /// ctor /// private MessageRouter(params MessageRoute[] routes) @@ -26,7 +29,7 @@ namespace CryptoExchange.Net.Sockets } /// - /// Create message matcher + /// Create message router without specific message handler /// public static MessageRouter CreateWithoutHandler(string typeIdentifier) { @@ -34,15 +37,7 @@ namespace CryptoExchange.Net.Sockets } /// - /// Create message matcher - /// - public static MessageRouter CreateWithoutTopicFilter(IEnumerable values, Func handler) - { - return new MessageRouter(values.Select(x => new MessageRoute(x, (string?)null, handler)).ToArray()); - } - - /// - /// Create message matcher + /// Create message router without specific message handler /// public static MessageRouter CreateWithoutHandler(string typeIdentifier, string topicFilter) { @@ -50,7 +45,15 @@ namespace CryptoExchange.Net.Sockets } /// - /// Create message matcher + /// Create message router without topic filter + /// + public static MessageRouter CreateWithoutTopicFilter(IEnumerable values, Func handler) + { + return new MessageRouter(values.Select(x => new MessageRoute(x, (string?)null, handler)).ToArray()); + } + + /// + /// Create message router without topic filter /// public static MessageRouter CreateWithoutTopicFilter(string typeIdentifier, Func handler) { @@ -58,7 +61,7 @@ namespace CryptoExchange.Net.Sockets } /// - /// Create message matcher + /// Create message router with topic filter /// public static MessageRouter CreateWithTopicFilter(string typeIdentifier, string topicFilter, Func handler) { @@ -66,7 +69,7 @@ namespace CryptoExchange.Net.Sockets } /// - /// Create message matcher + /// Create message router with topic filter /// public static MessageRouter CreateWithTopicFilter(IEnumerable typeIdentifiers, string topicFilter, Func handler) { @@ -78,7 +81,34 @@ namespace CryptoExchange.Net.Sockets } /// - /// Create message matcher + /// Create message router with topic filter + /// + public static MessageRouter CreateWithTopicFilters(string typeIdentifier, IEnumerable topicFilters, Func handler) + { + var routes = new List(); + foreach (var filter in topicFilters) + routes.Add(new MessageRoute(typeIdentifier, filter, handler)); + + return new MessageRouter(routes.ToArray()); + } + + /// + /// Create message router with topic filter + /// + public static MessageRouter CreateWithTopicFilters(IEnumerable typeIdentifiers, IEnumerable topicFilters, Func handler) + { + var routes = new List(); + foreach (var type in typeIdentifiers) + { + foreach (var filter in topicFilters) + routes.Add(new MessageRoute(type, filter, handler)); + } + + return new MessageRouter(routes.ToArray()); + } + + /// + /// Create message router with optional topic filter /// public static MessageRouter CreateWithOptionalTopicFilter(string typeIdentifier, string? topicFilter, Func handler) { @@ -86,7 +116,7 @@ namespace CryptoExchange.Net.Sockets } /// - /// Create message matcher + /// Create message router with optional topic filter /// public static MessageRouter CreateWithOptionalTopicFilters(string typeIdentifier, IEnumerable? topicFilters, Func handler) { @@ -105,7 +135,7 @@ namespace CryptoExchange.Net.Sockets } /// - /// Create message matcher + /// Create message router with optional topic filter /// public static MessageRouter CreateWithOptionalTopicFilters(IEnumerable typeIdentifiers, IEnumerable? topicFilters, Func handler) { @@ -127,55 +157,40 @@ namespace CryptoExchange.Net.Sockets } /// - /// Create message matcher + /// Create message matcher with specific routes /// - public static MessageRouter CreateWithTopicFilters(string typeIdentifier, IEnumerable topicFilters, Func handler) + public static MessageRouter Create(params MessageRoute[] routes) { - var routes = new List(); - foreach (var filter in topicFilters) - routes.Add(new MessageRoute(typeIdentifier, filter, handler)); - - return new MessageRouter(routes.ToArray()); - } - - /// - /// Create message matcher - /// - public static MessageRouter CreateWithTopicFilters(IEnumerable typeIdentifiers, IEnumerable topicFilters, Func handler) - { - var routes = new List(); - foreach(var type in typeIdentifiers) - { - foreach (var filter in topicFilters) - routes.Add(new MessageRoute(type, filter, handler)); - } - - return new MessageRouter(routes.ToArray()); - } - - /// - /// Create message matcher - /// - public static MessageRouter Create(params MessageRoute[] linkers) - { - return new MessageRouter(linkers); + return new MessageRouter(routes); } /// /// Whether this matcher contains a specific link /// - public bool ContainsCheck(MessageRoute link) => Routes.Any(x => x.TypeIdentifier == link.TypeIdentifier && x.TopicFilter == link.TopicFilter); + public bool ContainsCheck(MessageRoute route) => Routes.Any(x => x.TypeIdentifier == route.TypeIdentifier && x.TopicFilter == route.TopicFilter); } + /// + /// Message route + /// public abstract class MessageRoute { + /// + /// Type identifier + /// public string TypeIdentifier { get; set; } + /// + /// Optional topic filter + /// public string? TopicFilter { get; set; } /// /// Deserialization type /// public abstract Type DeserializationType { get; } + /// + /// ctor + /// public MessageRoute(string typeIdentifier, string? topicFilter) { TypeIdentifier = typeIdentifier; @@ -188,6 +203,9 @@ namespace CryptoExchange.Net.Sockets public abstract CallResult Handle(SocketConnection connection, DateTime receiveTime, string? originalData, object data); } + /// + /// Message route + /// public class MessageRoute : MessageRoute { private Func _handler; @@ -204,16 +222,25 @@ namespace CryptoExchange.Net.Sockets _handler = handler; } + /// + /// Create route without topic filter + /// public static MessageRoute CreateWithoutTopicFilter(string typeIdentifier, Func handler) { return new MessageRoute(typeIdentifier, null, handler); } + /// + /// Create route with optional topic filter + /// public static MessageRoute CreateWithOptionalTopicFilter(string typeIdentifier, string? topicFilter, Func handler) { return new MessageRoute(typeIdentifier, topicFilter, handler); } + /// + /// Create route with topic filter + /// public static MessageRoute CreateWithTopicFilter(string typeIdentifier, string topicFilter, Func handler) { return new MessageRoute(typeIdentifier, topicFilter, handler); diff --git a/CryptoExchange.Net/Sockets/Query.cs b/CryptoExchange.Net/Sockets/Query.cs index 7524fd8..01deee0 100644 --- a/CryptoExchange.Net/Sockets/Query.cs +++ b/CryptoExchange.Net/Sockets/Query.cs @@ -108,7 +108,9 @@ namespace CryptoExchange.Net.Sockets /// /// ctor /// +#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable. public Query(object request, bool authenticated, int weight = 1) +#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable. { _event = new AsyncResetEvent(false, false); @@ -163,6 +165,10 @@ namespace CryptoExchange.Net.Sockets /// Handle a response message /// public abstract CallResult Handle(SocketConnection connection, DateTime receiveTime, string? originalData, object message, MessageHandlerLink check); + + /// + /// Handle a response message + /// public abstract CallResult Handle(SocketConnection connection, DateTime receiveTime, string? originalData, object message, MessageRoute route); } @@ -192,6 +198,7 @@ namespace CryptoExchange.Net.Sockets { } + /// public override CallResult Handle(SocketConnection connection, DateTime receiveTime, string? originalData, object message, MessageRoute route) { if (!PreCheckMessage(connection, message)) diff --git a/CryptoExchange.Net/Sockets/SocketConnection.cs b/CryptoExchange.Net/Sockets/SocketConnection.cs index 62bcb80..88ec24b 100644 --- a/CryptoExchange.Net/Sockets/SocketConnection.cs +++ b/CryptoExchange.Net/Sockets/SocketConnection.cs @@ -535,25 +535,7 @@ namespace CryptoExchange.Net.Sockets _logger.ReceivedData(SocketId, originalData); } - - // Current: - // 1. Get message identifier - // 2. Look for matching handlers and grab the type - // 3. Deserialize - // 4. Dispatch - // Listen id: kline-ethusdt-1m - - // Update: - // 1. Get message type identifier - // 2. Look for matching handlers and grab the type - // 3. Deserialize - // 4. Get message topic filter from deserialized type - // 5. Dispatch to filtered - // Type id: kline - // Topic filter: ethusdt-1m - var typeIdentifier = messageConverter.GetTypeIdentifier(data, type); - //var messageIdentifier = messageConverter.GetMessageIdentifier(data, type); if (typeIdentifier == null) { // Both deserialization type and identifier null, can't process diff --git a/CryptoExchange.Net/Sockets/Subscription.cs b/CryptoExchange.Net/Sockets/Subscription.cs index 0a269e3..eadd95a 100644 --- a/CryptoExchange.Net/Sockets/Subscription.cs +++ b/CryptoExchange.Net/Sockets/Subscription.cs @@ -114,7 +114,9 @@ namespace CryptoExchange.Net.Sockets /// /// ctor /// +#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable. public Subscription( +#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable. ILogger logger, bool authenticated, bool userSubscription = true) @@ -182,6 +184,9 @@ namespace CryptoExchange.Net.Sockets return matcher.Handle(connection, receiveTime, originalData, data); } + /// + /// Handle an update message + /// public CallResult Handle(SocketConnection connection, DateTime receiveTime, string? originalData, object data, MessageRoute route) { ConnectionInvocations++; diff --git a/CryptoExchange.Net/Testing/SocketSubscriptionValidator.cs b/CryptoExchange.Net/Testing/SocketSubscriptionValidator.cs index 3549c2b..322e743 100644 --- a/CryptoExchange.Net/Testing/SocketSubscriptionValidator.cs +++ b/CryptoExchange.Net/Testing/SocketSubscriptionValidator.cs @@ -40,6 +40,13 @@ namespace CryptoExchange.Net.Testing _nestedPropertyForCompare = nestedPropertyForCompare; } + /// + /// Validate to subscriptions being established concurrently are indeed handled correctly + /// + /// Type of the subscription update + /// Subscription delegate 1 + /// Subscription delegate 2 + /// Name public async Task ValidateConcurrentAsync( Func>, Task>> methodInvoke1, Func>, Task>> methodInvoke2, @@ -120,7 +127,7 @@ namespace CryptoExchange.Net.Testing { var match = matches[0]; var prevMessage = line1[1] == '1' ? lastMessage1 : lastMessage2; - var json = JsonDocument.Parse(prevMessage); + var json = JsonDocument.Parse(prevMessage!); var propName = match.Value.Substring(1, match.Value.Length - 2); var split = propName.Split('.'); var jsonProp = json.RootElement; @@ -141,7 +148,7 @@ namespace CryptoExchange.Net.Testing { var match = matches[0]; var prevMessage = line1[1] == '1' ? lastMessage1 : lastMessage2; - var json = JsonDocument.Parse(prevMessage); + var json = JsonDocument.Parse(prevMessage!); var propName = match.Value.Substring(1, match.Value.Length - 2); var split = propName.Split('.'); var jsonProp = json.RootElement; @@ -164,8 +171,6 @@ namespace CryptoExchange.Net.Testing if (updates1 != 1 || updates2 != 1) throw new Exception($"Expected 1 update for both subscriptions, instead got {updates1} and {updates2}"); - - //await _client.UnsubscribeAllAsync().ConfigureAwait(false); }