1
0
mirror of https://github.com/JKorf/CryptoExchange.Net synced 2025-12-13 17:20:26 +00:00
This commit is contained in:
JKorf 2025-12-07 10:43:44 +01:00
parent 99be703f1f
commit 42736f3003
14 changed files with 169 additions and 100 deletions

View File

@ -223,7 +223,6 @@ namespace CryptoExchange.Net.Clients
string? cacheKey = null; string? cacheKey = null;
if (ShouldCache(definition)) if (ShouldCache(definition))
{ {
#warning caching should be static per api client type
cacheKey = baseAddress + definition + uriParameters?.ToFormData(); cacheKey = baseAddress + definition + uriParameters?.ToFormData();
_logger.CheckingCache(cacheKey); _logger.CheckingCache(cacheKey);
var cachedValue = _cache.Get(cacheKey, ClientOptions.CachingMaxAge); 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 // we'll need to copy it as the stream isn't seekable, and thus we can only read it once
var memoryStream = new MemoryStream(); var memoryStream = new MemoryStream();
await responseStream.CopyToAsync(memoryStream).ConfigureAwait(false); 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) if (outputOriginalData)
{ {
memoryStream.Position = 0; memoryStream.Position = 0;
originalData = await reader.ReadToEndAsync().ConfigureAwait(false); originalData = await reader.ReadToEndAsync().ConfigureAwait(false);
if (_logger.IsEnabled(LogLevel.Trace)) if (_logger.IsEnabled(LogLevel.Trace))
#warning TODO extension _logger.RestApiReceivedResponse(request.RequestId, originalData);
_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 // Continue processing from the memory stream since the response stream is already read and we can't seek it

View File

@ -9,11 +9,13 @@ namespace CryptoExchange.Net.Converters.MessageParsing.DynamicConverters
public interface ISocketMessageHandler public interface ISocketMessageHandler
{ {
/// <summary> /// <summary>
/// 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
/// </summary> /// </summary>
//string? GetMessageIdentifier(ReadOnlySpan<byte> data, WebSocketMessageType? webSocketMessageType);
string? GetTypeIdentifier(ReadOnlySpan<byte> data, WebSocketMessageType? webSocketMessageType); string? GetTypeIdentifier(ReadOnlySpan<byte> data, WebSocketMessageType? webSocketMessageType);
/// <summary>
/// Get optional topic filter, for example a symbol name
/// </summary>
string? GetTopicFilter(object deserializedObject); string? GetTopicFilter(object deserializedObject);
/// <summary> /// <summary>

View File

@ -20,7 +20,7 @@ namespace CryptoExchange.Net.Converters.MessageParsing.DynamicConverters
/// <summary> /// <summary>
/// The fields this evaluator has to look for /// The fields this evaluator has to look for
/// </summary> /// </summary>
public MessageFieldReference[] Fields { get; set; } public MessageFieldReference[] Fields { get; set; } = [];
/// <summary> /// <summary>
/// The callback for getting the identifier string /// The callback for getting the identifier string
/// </summary> /// </summary>

View File

@ -6,6 +6,7 @@ using CryptoExchange.Net.Objects.Options;
using CryptoExchange.Net.Requests; using CryptoExchange.Net.Requests;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Net.Http; using System.Net.Http;
@ -90,11 +91,19 @@ namespace CryptoExchange.Net.Converters.SystemTextJson.MessageConverters
} }
/// <inheritdoc /> /// <inheritdoc />
#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<T>(Stream responseStream, CancellationToken cancellationToken) public async ValueTask<(T? Result, Error? Error)> TryDeserializeAsync<T>(Stream responseStream, CancellationToken cancellationToken)
{ {
try try
{ {
var result = await JsonSerializer.DeserializeAsync<T>(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<T>(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); return (result, null);
} }
catch (JsonException ex) catch (JsonException ex)

View File

@ -1,6 +1,10 @@
using CryptoExchange.Net.Converters.MessageParsing.DynamicConverters; using CryptoExchange.Net.Converters.MessageParsing.DynamicConverters;
using System; using System;
#if !NETSTANDARD
using System.Collections.Frozen;
#endif
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq; using System.Linq;
using System.Net.WebSockets; using System.Net.WebSockets;
using System.Text; using System.Text;
@ -31,10 +35,14 @@ namespace CryptoExchange.Net.Converters.SystemTextJson
private int _maxSearchDepth; private int _maxSearchDepth;
private MessageEvaluator? _topEvaluator; private MessageEvaluator? _topEvaluator;
private List<MessageEvalutorFieldReference>? _searchFields; private List<MessageEvalutorFieldReference>? _searchFields;
private Dictionary<Type, Func<object, string?>> _mapping;
private Dictionary<Type, Func<object, string?>>? _baseTypeMapping; private Dictionary<Type, Func<object, string?>>? _baseTypeMapping;
private Dictionary<Type, Func<object, string?>>? _mapping;
/// <summary>
/// Add a mapping of a specific object of a type to a specific topic
/// </summary>
/// <typeparam name="T">Type to get topic for</typeparam>
/// <param name="mapping">The topic retrieve delegate</param>
protected void AddTopicMapping<T>(Func<T, string?> mapping) protected void AddTopicMapping<T>(Func<T, string?> mapping)
{ {
_mapping ??= new Dictionary<Type, Func<object, string?>>(); _mapping ??= new Dictionary<Type, Func<object, string?>>();
@ -126,6 +134,7 @@ namespace CryptoExchange.Net.Converters.SystemTextJson
_initialized = true; _initialized = true;
} }
/// <inheritdoc />
public virtual string? GetTopicFilter(object deserializedObject) public virtual string? GetTopicFilter(object deserializedObject)
{ {
if (_mapping == null) if (_mapping == null)
@ -260,20 +269,28 @@ namespace CryptoExchange.Net.Converters.SystemTextJson
} }
else else
{ {
if (reader.TokenType == JsonTokenType.Number) switch (reader.TokenType)
value = reader.GetDecimal().ToString(); {
else if (reader.TokenType == JsonTokenType.String) case JsonTokenType.Number:
value = reader.GetString()!; value = reader.GetDecimal().ToString();
else if (reader.TokenType == JsonTokenType.True break;
|| reader.TokenType == JsonTokenType.False) case JsonTokenType.String:
value = reader.GetBoolean().ToString()!; value = reader.GetString()!;
else if (reader.TokenType == JsonTokenType.Null) break;
value = null; case JsonTokenType.True:
else if (reader.TokenType == JsonTokenType.StartObject case JsonTokenType.False:
|| reader.TokenType == JsonTokenType.StartArray) value = reader.GetBoolean().ToString()!;
value = null; break;
else case JsonTokenType.Null:
continue; value = null;
break;
case JsonTokenType.StartObject:
case JsonTokenType.StartArray:
value = null;
break;
default:
continue;
}
} }
} }
@ -321,6 +338,10 @@ namespace CryptoExchange.Net.Converters.SystemTextJson
} }
/// <inheritdoc /> /// <inheritdoc />
#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<byte> data, Type type) public virtual object Deserialize(ReadOnlySpan<byte> data, Type type)
{ {
#pragma warning disable IL2026 // Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code #pragma warning disable IL2026 // Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code

View File

@ -29,6 +29,9 @@ namespace CryptoExchange.Net.Converters.SystemTextJson
/// </summary> /// </summary>
protected abstract string? GetTypeIdentifier(JsonDocument document); protected abstract string? GetTypeIdentifier(JsonDocument document);
/// <summary>
/// Get optional topic filter, for example a symbol name
/// </summary>
public virtual string? GetTopicFilter(object deserializedObject) => null; public virtual string? GetTopicFilter(object deserializedObject) => null;
/// <inheritdoc /> /// <inheritdoc />

View File

@ -28,6 +28,9 @@ namespace CryptoExchange.Net.Interfaces
/// Handle a message /// Handle a message
/// </summary> /// </summary>
CallResult Handle(SocketConnection connection, DateTime receiveTime, string? originalData, object result, MessageHandlerLink matchedHandler); CallResult Handle(SocketConnection connection, DateTime receiveTime, string? originalData, object result, MessageHandlerLink matchedHandler);
/// <summary>
/// Handle a message
/// </summary>
CallResult Handle(SocketConnection connection, DateTime receiveTime, string? originalData, object result, MessageRoute route); CallResult Handle(SocketConnection connection, DateTime receiveTime, string? originalData, object result, MessageRoute route);
/// <summary> /// <summary>
/// Deserialize a message into object of type /// Deserialize a message into object of type

View File

@ -22,6 +22,7 @@ namespace CryptoExchange.Net.Logging.Extensions
private static readonly Action<ILogger, string, Exception?> _restApiCacheHit; private static readonly Action<ILogger, string, Exception?> _restApiCacheHit;
private static readonly Action<ILogger, string, Exception?> _restApiCacheNotHit; private static readonly Action<ILogger, string, Exception?> _restApiCacheNotHit;
private static readonly Action<ILogger, int?, Exception?> _restApiCancellationRequested; private static readonly Action<ILogger, int?, Exception?> _restApiCancellationRequested;
private static readonly Action<ILogger, int?, string?, Exception?> _restApiReceivedResponse;
static RestApiClientLoggingExtensions() static RestApiClientLoggingExtensions()
{ {
@ -90,6 +91,11 @@ namespace CryptoExchange.Net.Logging.Extensions
new EventId(4012, "RestApiCancellationRequested"), new EventId(4012, "RestApiCancellationRequested"),
"[Req {RequestId}] Request cancelled by user"); "[Req {RequestId}] Request cancelled by user");
_restApiReceivedResponse = LoggerMessage.Define<int?, string?>(
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) 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); _restApiCancellationRequested(logger, requestId, null);
} }
public static void RestApiReceivedResponse(this ILogger logger, int requestId, string? originalData)
{
_restApiReceivedResponse(logger, requestId, originalData, null);
}
} }
} }

View File

@ -214,13 +214,9 @@ namespace CryptoExchange.Net.Sockets
if (_ctsSource.IsCancellationRequested || !_processing) if (_ctsSource.IsCancellationRequested || !_processing)
return false; return false;
#warning todo logging overloads without id
_logger.SocketAddingBytesToSendBuffer(Id, 0, data);
try try
{ {
await _socket!.SendAsync(new ArraySegment<byte>(data, 0, data.Length), type, true, _ctsSource.Token).ConfigureAwait(false); await _socket!.SendAsync(new ArraySegment<byte>(data, 0, data.Length), type, true, _ctsSource.Token).ConfigureAwait(false);
_logger.SocketSentBytes(Id, 0, data.Length);
return true; return true;
} }
catch (OperationCanceledException) catch (OperationCanceledException)

View File

@ -10,14 +10,17 @@ using System.Threading.Tasks;
namespace CryptoExchange.Net.Sockets namespace CryptoExchange.Net.Sockets
{ {
/// <summary>
/// Message router
/// </summary>
public class MessageRouter public class MessageRouter
{ {
/// <summary> /// <summary>
/// /// The routes registered for this router
/// </summary> /// </summary>
public MessageRoute[] Routes { get; } public MessageRoute[] Routes { get; }
// <summary> /// <summary>
/// ctor /// ctor
/// </summary> /// </summary>
private MessageRouter(params MessageRoute[] routes) private MessageRouter(params MessageRoute[] routes)
@ -26,7 +29,7 @@ namespace CryptoExchange.Net.Sockets
} }
/// <summary> /// <summary>
/// Create message matcher /// Create message router without specific message handler
/// </summary> /// </summary>
public static MessageRouter CreateWithoutHandler<T>(string typeIdentifier) public static MessageRouter CreateWithoutHandler<T>(string typeIdentifier)
{ {
@ -34,15 +37,7 @@ namespace CryptoExchange.Net.Sockets
} }
/// <summary> /// <summary>
/// Create message matcher /// Create message router without specific message handler
/// </summary>
public static MessageRouter CreateWithoutTopicFilter<T>(IEnumerable<string> values, Func<SocketConnection, DateTime, string?, T, CallResult> handler)
{
return new MessageRouter(values.Select(x => new MessageRoute<T>(x, (string?)null, handler)).ToArray());
}
/// <summary>
/// Create message matcher
/// </summary> /// </summary>
public static MessageRouter CreateWithoutHandler<T>(string typeIdentifier, string topicFilter) public static MessageRouter CreateWithoutHandler<T>(string typeIdentifier, string topicFilter)
{ {
@ -50,7 +45,15 @@ namespace CryptoExchange.Net.Sockets
} }
/// <summary> /// <summary>
/// Create message matcher /// Create message router without topic filter
/// </summary>
public static MessageRouter CreateWithoutTopicFilter<T>(IEnumerable<string> values, Func<SocketConnection, DateTime, string?, T, CallResult> handler)
{
return new MessageRouter(values.Select(x => new MessageRoute<T>(x, (string?)null, handler)).ToArray());
}
/// <summary>
/// Create message router without topic filter
/// </summary> /// </summary>
public static MessageRouter CreateWithoutTopicFilter<T>(string typeIdentifier, Func<SocketConnection, DateTime, string?, T, CallResult> handler) public static MessageRouter CreateWithoutTopicFilter<T>(string typeIdentifier, Func<SocketConnection, DateTime, string?, T, CallResult> handler)
{ {
@ -58,7 +61,7 @@ namespace CryptoExchange.Net.Sockets
} }
/// <summary> /// <summary>
/// Create message matcher /// Create message router with topic filter
/// </summary> /// </summary>
public static MessageRouter CreateWithTopicFilter<T>(string typeIdentifier, string topicFilter, Func<SocketConnection, DateTime, string?, T, CallResult> handler) public static MessageRouter CreateWithTopicFilter<T>(string typeIdentifier, string topicFilter, Func<SocketConnection, DateTime, string?, T, CallResult> handler)
{ {
@ -66,7 +69,7 @@ namespace CryptoExchange.Net.Sockets
} }
/// <summary> /// <summary>
/// Create message matcher /// Create message router with topic filter
/// </summary> /// </summary>
public static MessageRouter CreateWithTopicFilter<T>(IEnumerable<string> typeIdentifiers, string topicFilter, Func<SocketConnection, DateTime, string?, T, CallResult> handler) public static MessageRouter CreateWithTopicFilter<T>(IEnumerable<string> typeIdentifiers, string topicFilter, Func<SocketConnection, DateTime, string?, T, CallResult> handler)
{ {
@ -78,7 +81,34 @@ namespace CryptoExchange.Net.Sockets
} }
/// <summary> /// <summary>
/// Create message matcher /// Create message router with topic filter
/// </summary>
public static MessageRouter CreateWithTopicFilters<T>(string typeIdentifier, IEnumerable<string> topicFilters, Func<SocketConnection, DateTime, string?, T, CallResult> handler)
{
var routes = new List<MessageRoute>();
foreach (var filter in topicFilters)
routes.Add(new MessageRoute<T>(typeIdentifier, filter, handler));
return new MessageRouter(routes.ToArray());
}
/// <summary>
/// Create message router with topic filter
/// </summary>
public static MessageRouter CreateWithTopicFilters<T>(IEnumerable<string> typeIdentifiers, IEnumerable<string> topicFilters, Func<SocketConnection, DateTime, string?, T, CallResult> handler)
{
var routes = new List<MessageRoute>();
foreach (var type in typeIdentifiers)
{
foreach (var filter in topicFilters)
routes.Add(new MessageRoute<T>(type, filter, handler));
}
return new MessageRouter(routes.ToArray());
}
/// <summary>
/// Create message router with optional topic filter
/// </summary> /// </summary>
public static MessageRouter CreateWithOptionalTopicFilter<T>(string typeIdentifier, string? topicFilter, Func<SocketConnection, DateTime, string?, T, CallResult> handler) public static MessageRouter CreateWithOptionalTopicFilter<T>(string typeIdentifier, string? topicFilter, Func<SocketConnection, DateTime, string?, T, CallResult> handler)
{ {
@ -86,7 +116,7 @@ namespace CryptoExchange.Net.Sockets
} }
/// <summary> /// <summary>
/// Create message matcher /// Create message router with optional topic filter
/// </summary> /// </summary>
public static MessageRouter CreateWithOptionalTopicFilters<T>(string typeIdentifier, IEnumerable<string>? topicFilters, Func<SocketConnection, DateTime, string?, T, CallResult> handler) public static MessageRouter CreateWithOptionalTopicFilters<T>(string typeIdentifier, IEnumerable<string>? topicFilters, Func<SocketConnection, DateTime, string?, T, CallResult> handler)
{ {
@ -105,7 +135,7 @@ namespace CryptoExchange.Net.Sockets
} }
/// <summary> /// <summary>
/// Create message matcher /// Create message router with optional topic filter
/// </summary> /// </summary>
public static MessageRouter CreateWithOptionalTopicFilters<T>(IEnumerable<string> typeIdentifiers, IEnumerable<string>? topicFilters, Func<SocketConnection, DateTime, string?, T, CallResult> handler) public static MessageRouter CreateWithOptionalTopicFilters<T>(IEnumerable<string> typeIdentifiers, IEnumerable<string>? topicFilters, Func<SocketConnection, DateTime, string?, T, CallResult> handler)
{ {
@ -127,55 +157,40 @@ namespace CryptoExchange.Net.Sockets
} }
/// <summary> /// <summary>
/// Create message matcher /// Create message matcher with specific routes
/// </summary> /// </summary>
public static MessageRouter CreateWithTopicFilters<T>(string typeIdentifier, IEnumerable<string> topicFilters, Func<SocketConnection, DateTime, string?, T, CallResult> handler) public static MessageRouter Create(params MessageRoute[] routes)
{ {
var routes = new List<MessageRoute>(); return new MessageRouter(routes);
foreach (var filter in topicFilters)
routes.Add(new MessageRoute<T>(typeIdentifier, filter, handler));
return new MessageRouter(routes.ToArray());
}
/// <summary>
/// Create message matcher
/// </summary>
public static MessageRouter CreateWithTopicFilters<T>(IEnumerable<string> typeIdentifiers, IEnumerable<string> topicFilters, Func<SocketConnection, DateTime, string?, T, CallResult> handler)
{
var routes = new List<MessageRoute>();
foreach(var type in typeIdentifiers)
{
foreach (var filter in topicFilters)
routes.Add(new MessageRoute<T>(type, filter, handler));
}
return new MessageRouter(routes.ToArray());
}
/// <summary>
/// Create message matcher
/// </summary>
public static MessageRouter Create(params MessageRoute[] linkers)
{
return new MessageRouter(linkers);
} }
/// <summary> /// <summary>
/// Whether this matcher contains a specific link /// Whether this matcher contains a specific link
/// </summary> /// </summary>
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);
} }
/// <summary>
/// Message route
/// </summary>
public abstract class MessageRoute public abstract class MessageRoute
{ {
/// <summary>
/// Type identifier
/// </summary>
public string TypeIdentifier { get; set; } public string TypeIdentifier { get; set; }
/// <summary>
/// Optional topic filter
/// </summary>
public string? TopicFilter { get; set; } public string? TopicFilter { get; set; }
/// <summary> /// <summary>
/// Deserialization type /// Deserialization type
/// </summary> /// </summary>
public abstract Type DeserializationType { get; } public abstract Type DeserializationType { get; }
/// <summary>
/// ctor
/// </summary>
public MessageRoute(string typeIdentifier, string? topicFilter) public MessageRoute(string typeIdentifier, string? topicFilter)
{ {
TypeIdentifier = typeIdentifier; TypeIdentifier = typeIdentifier;
@ -188,6 +203,9 @@ namespace CryptoExchange.Net.Sockets
public abstract CallResult Handle(SocketConnection connection, DateTime receiveTime, string? originalData, object data); public abstract CallResult Handle(SocketConnection connection, DateTime receiveTime, string? originalData, object data);
} }
/// <summary>
/// Message route
/// </summary>
public class MessageRoute<TMessage> : MessageRoute public class MessageRoute<TMessage> : MessageRoute
{ {
private Func<SocketConnection, DateTime, string?, TMessage, CallResult> _handler; private Func<SocketConnection, DateTime, string?, TMessage, CallResult> _handler;
@ -204,16 +222,25 @@ namespace CryptoExchange.Net.Sockets
_handler = handler; _handler = handler;
} }
/// <summary>
/// Create route without topic filter
/// </summary>
public static MessageRoute<TMessage> CreateWithoutTopicFilter(string typeIdentifier, Func<SocketConnection, DateTime, string?, TMessage, CallResult> handler) public static MessageRoute<TMessage> CreateWithoutTopicFilter(string typeIdentifier, Func<SocketConnection, DateTime, string?, TMessage, CallResult> handler)
{ {
return new MessageRoute<TMessage>(typeIdentifier, null, handler); return new MessageRoute<TMessage>(typeIdentifier, null, handler);
} }
/// <summary>
/// Create route with optional topic filter
/// </summary>
public static MessageRoute<TMessage> CreateWithOptionalTopicFilter(string typeIdentifier, string? topicFilter, Func<SocketConnection, DateTime, string?, TMessage, CallResult> handler) public static MessageRoute<TMessage> CreateWithOptionalTopicFilter(string typeIdentifier, string? topicFilter, Func<SocketConnection, DateTime, string?, TMessage, CallResult> handler)
{ {
return new MessageRoute<TMessage>(typeIdentifier, topicFilter, handler); return new MessageRoute<TMessage>(typeIdentifier, topicFilter, handler);
} }
/// <summary>
/// Create route with topic filter
/// </summary>
public static MessageRoute<TMessage> CreateWithTopicFilter(string typeIdentifier, string topicFilter, Func<SocketConnection, DateTime, string?, TMessage, CallResult> handler) public static MessageRoute<TMessage> CreateWithTopicFilter(string typeIdentifier, string topicFilter, Func<SocketConnection, DateTime, string?, TMessage, CallResult> handler)
{ {
return new MessageRoute<TMessage>(typeIdentifier, topicFilter, handler); return new MessageRoute<TMessage>(typeIdentifier, topicFilter, handler);

View File

@ -108,7 +108,9 @@ namespace CryptoExchange.Net.Sockets
/// <summary> /// <summary>
/// ctor /// ctor
/// </summary> /// </summary>
#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) 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); _event = new AsyncResetEvent(false, false);
@ -163,6 +165,10 @@ namespace CryptoExchange.Net.Sockets
/// Handle a response message /// Handle a response message
/// </summary> /// </summary>
public abstract CallResult Handle(SocketConnection connection, DateTime receiveTime, string? originalData, object message, MessageHandlerLink check); public abstract CallResult Handle(SocketConnection connection, DateTime receiveTime, string? originalData, object message, MessageHandlerLink check);
/// <summary>
/// Handle a response message
/// </summary>
public abstract CallResult Handle(SocketConnection connection, DateTime receiveTime, string? originalData, object message, MessageRoute route); public abstract CallResult Handle(SocketConnection connection, DateTime receiveTime, string? originalData, object message, MessageRoute route);
} }
@ -192,6 +198,7 @@ namespace CryptoExchange.Net.Sockets
{ {
} }
/// <inheritdoc />
public override CallResult Handle(SocketConnection connection, DateTime receiveTime, string? originalData, object message, MessageRoute route) public override CallResult Handle(SocketConnection connection, DateTime receiveTime, string? originalData, object message, MessageRoute route)
{ {
if (!PreCheckMessage(connection, message)) if (!PreCheckMessage(connection, message))

View File

@ -535,25 +535,7 @@ namespace CryptoExchange.Net.Sockets
_logger.ReceivedData(SocketId, originalData); _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 typeIdentifier = messageConverter.GetTypeIdentifier(data, type);
//var messageIdentifier = messageConverter.GetMessageIdentifier(data, type);
if (typeIdentifier == null) if (typeIdentifier == null)
{ {
// Both deserialization type and identifier null, can't process // Both deserialization type and identifier null, can't process

View File

@ -114,7 +114,9 @@ namespace CryptoExchange.Net.Sockets
/// <summary> /// <summary>
/// ctor /// ctor
/// </summary> /// </summary>
#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( 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, ILogger logger,
bool authenticated, bool authenticated,
bool userSubscription = true) bool userSubscription = true)
@ -182,6 +184,9 @@ namespace CryptoExchange.Net.Sockets
return matcher.Handle(connection, receiveTime, originalData, data); return matcher.Handle(connection, receiveTime, originalData, data);
} }
/// <summary>
/// Handle an update message
/// </summary>
public CallResult Handle(SocketConnection connection, DateTime receiveTime, string? originalData, object data, MessageRoute route) public CallResult Handle(SocketConnection connection, DateTime receiveTime, string? originalData, object data, MessageRoute route)
{ {
ConnectionInvocations++; ConnectionInvocations++;

View File

@ -40,6 +40,13 @@ namespace CryptoExchange.Net.Testing
_nestedPropertyForCompare = nestedPropertyForCompare; _nestedPropertyForCompare = nestedPropertyForCompare;
} }
/// <summary>
/// Validate to subscriptions being established concurrently are indeed handled correctly
/// </summary>
/// <typeparam name="TUpdate">Type of the subscription update</typeparam>
/// <param name="methodInvoke1">Subscription delegate 1</param>
/// <param name="methodInvoke2">Subscription delegate 2</param>
/// <param name="name">Name</param>
public async Task ValidateConcurrentAsync<TUpdate>( public async Task ValidateConcurrentAsync<TUpdate>(
Func<TClient, Action<DataEvent<TUpdate>>, Task<CallResult<UpdateSubscription>>> methodInvoke1, Func<TClient, Action<DataEvent<TUpdate>>, Task<CallResult<UpdateSubscription>>> methodInvoke1,
Func<TClient, Action<DataEvent<TUpdate>>, Task<CallResult<UpdateSubscription>>> methodInvoke2, Func<TClient, Action<DataEvent<TUpdate>>, Task<CallResult<UpdateSubscription>>> methodInvoke2,
@ -120,7 +127,7 @@ namespace CryptoExchange.Net.Testing
{ {
var match = matches[0]; var match = matches[0];
var prevMessage = line1[1] == '1' ? lastMessage1 : lastMessage2; 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 propName = match.Value.Substring(1, match.Value.Length - 2);
var split = propName.Split('.'); var split = propName.Split('.');
var jsonProp = json.RootElement; var jsonProp = json.RootElement;
@ -141,7 +148,7 @@ namespace CryptoExchange.Net.Testing
{ {
var match = matches[0]; var match = matches[0];
var prevMessage = line1[1] == '1' ? lastMessage1 : lastMessage2; 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 propName = match.Value.Substring(1, match.Value.Length - 2);
var split = propName.Split('.'); var split = propName.Split('.');
var jsonProp = json.RootElement; var jsonProp = json.RootElement;
@ -164,8 +171,6 @@ namespace CryptoExchange.Net.Testing
if (updates1 != 1 || updates2 != 1) if (updates1 != 1 || updates2 != 1)
throw new Exception($"Expected 1 update for both subscriptions, instead got {updates1} and {updates2}"); throw new Exception($"Expected 1 update for both subscriptions, instead got {updates1} and {updates2}");
//await _client.UnsubscribeAllAsync().ConfigureAwait(false);
} }