1
0
mirror of https://github.com/JKorf/CryptoExchange.Net synced 2025-12-16 02:41:03 +00:00
This commit is contained in:
JKorf 2025-11-24 21:22:02 +01:00
parent 07fdee1740
commit 839ebc4ca9
18 changed files with 260 additions and 149 deletions

View File

@ -17,6 +17,7 @@ using System.Linq;
using System.Net; using System.Net;
using System.Net.Http; using System.Net.Http;
using System.Net.Http.Headers; using System.Net.Http.Headers;
using System.Text;
using System.Text.Json; using System.Text.Json;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -98,8 +99,6 @@ namespace CryptoExchange.Net.Clients
/// </summary> /// </summary>
protected abstract IRestMessageHandler MessageHandler { get; } protected abstract IRestMessageHandler MessageHandler { get; }
private static MediaTypeWithQualityHeaderValue AcceptJsonContent = new MediaTypeWithQualityHeaderValue(Constants.JsonContentHeader);
/// <summary> /// <summary>
/// ctor /// ctor
/// </summary> /// </summary>
@ -327,6 +326,9 @@ namespace CryptoExchange.Net.Clients
return null; return null;
} }
/// <summary>
/// Check rate limits for the request
/// </summary>
protected virtual async Task<Error?> RateLimitAsync( protected virtual async Task<Error?> RateLimitAsync(
string host, string host,
int requestId, int requestId,
@ -412,8 +414,7 @@ namespace CryptoExchange.Net.Clients
var uri = new Uri(baseAddress.AppendPath(definition.Path) + queryString); var uri = new Uri(baseAddress.AppendPath(definition.Path) + queryString);
var request = RequestFactory.Create(ClientOptions.HttpVersion, definition.Method, uri, requestId); var request = RequestFactory.Create(ClientOptions.HttpVersion, definition.Method, uri, requestId);
#warning Should be configurable request.Accept = MessageHandler.AcceptHeader;
request.Accept = AcceptJsonContent;
if (requestConfiguration.Headers != null) if (requestConfiguration.Headers != null)
{ {
@ -473,7 +474,23 @@ namespace CryptoExchange.Net.Clients
response = await request.GetResponseAsync(cancellationToken).ConfigureAwait(false); response = await request.GetResponseAsync(cancellationToken).ConfigureAwait(false);
sw.Stop(); sw.Stop();
responseStream = await response.GetResponseStreamAsync().ConfigureAwait(false); responseStream = await response.GetResponseStreamAsync().ConfigureAwait(false);
string? originalData = null;
var outputOriginalData = ApiOptions.OutputOriginalData ?? ClientOptions.OutputOriginalData; var outputOriginalData = ApiOptions.OutputOriginalData ?? ClientOptions.OutputOriginalData;
if (outputOriginalData)
{
// If we want to return the original string data from the stream, but still want to process it
// 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);
memoryStream.Position = 0;
originalData = await reader.ReadToEndAsync().ConfigureAwait(false);
// Continue processing from the memory stream since the response stream is already read and we can't seek it
responseStream.Close();
memoryStream.Position = 0;
responseStream = memoryStream;
}
if (!response.IsSuccessStatusCode && !requestDefinition.TryParseOnNonSuccess) if (!response.IsSuccessStatusCode && !requestDefinition.TryParseOnNonSuccess)
{ {
@ -502,12 +519,12 @@ namespace CryptoExchange.Net.Clients
responseStream).ConfigureAwait(false); responseStream).ConfigureAwait(false);
} }
return new WebCallResult<T>(response.StatusCode, response.HttpVersion, response.ResponseHeaders, sw.Elapsed, response.ContentLength, null, request.RequestId, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), ResultDataSource.Server, default, error); return new WebCallResult<T>(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, error);
} }
if (typeof(T) == typeof(object)) if (typeof(T) == typeof(object))
// Success status code and expected empty response, assume it's correct // Success status code and expected empty response, assume it's correct
return new WebCallResult<T>(response.StatusCode, response.HttpVersion, response.ResponseHeaders, sw.Elapsed, 0, null/*accessor.OriginalDataAvailable ? accessor.GetOriginalString() : "[Data only available when OutputOriginal = true in client options]"*/, request.RequestId, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), ResultDataSource.Server, default, null); return new WebCallResult<T>(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
var parsedError = await MessageHandler.CheckForErrorResponse( var parsedError = await MessageHandler.CheckForErrorResponse(
@ -527,11 +544,11 @@ namespace CryptoExchange.Net.Clients
} }
// Success status code, but TryParseError determined it was an error response // Success status code, but TryParseError determined it was an error response
return new WebCallResult<T>(response.StatusCode, response.HttpVersion, response.ResponseHeaders, sw.Elapsed, response.ContentLength, null, request.RequestId, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), ResultDataSource.Server, default, parsedError); return new WebCallResult<T>(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);
} }
var (deserializeResult, deserializeError) = await MessageHandler.TryDeserializeAsync<T>(responseStream, state, cancellationToken).ConfigureAwait(false); var (deserializeResult, deserializeError) = await MessageHandler.TryDeserializeAsync<T>(responseStream, state, cancellationToken).ConfigureAwait(false);
return new WebCallResult<T>(response.StatusCode, response.HttpVersion, response.ResponseHeaders, sw.Elapsed, response.ContentLength, null, request.RequestId, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), ResultDataSource.Server, deserializeResult, deserializeError); return new WebCallResult<T>(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);
} }
catch (HttpRequestException requestException) catch (HttpRequestException requestException)
{ {

View File

@ -14,6 +14,15 @@ namespace CryptoExchange.Net.Converters.MessageParsing.DynamicConverters
/// </summary> /// </summary>
public interface IRestMessageHandler public interface IRestMessageHandler
{ {
/// <summary>
/// The `accept` HTTP response header for the request
/// </summary>
MediaTypeWithQualityHeaderValue AcceptHeader { get; }
/// <summary>
/// Create an object to keep state for a request
/// </summary>
/// <returns></returns>
object? CreateState(); object? CreateState();
/// <summary> /// <summary>

View File

@ -4,26 +4,41 @@ using System.Text;
namespace CryptoExchange.Net.Converters.MessageParsing.DynamicConverters namespace CryptoExchange.Net.Converters.MessageParsing.DynamicConverters
{ {
/// <summary>
/// Message evaluator
/// </summary>
public class MessageEvaluator public class MessageEvaluator
{ {
/// <summary>
/// The priority of this evaluator, higher prio evaluators (with lower Priority number) will be checked for matches before lower ones
/// </summary>
public int Priority { get; set; } public int Priority { get; set; }
/// <summary>
/// Whether to immediately match the evaluator when it is matched. Can only be used when the evaluator has a single unique field to look for
/// </summary>
public bool ForceIfFound { get; set; } public bool ForceIfFound { get; set; }
/// <summary>
/// The fields this evaluator has to look for
/// </summary>
public MessageFieldReference[] Fields { get; set; } public MessageFieldReference[] Fields { get; set; }
/// <summary>
public Func<SearchResult, string> IdentifyMessageCallback { get; set; } /// The callback for getting the identifier string
/// </summary>
public Func<SearchResult, string>? IdentifyMessageCallback { get; set; }
/// <summary>
/// The static identifier string to return when this evaluator is matched
/// </summary>
public string? StaticIdentifier { get; set; } public string? StaticIdentifier { get; set; }
public string? IdentifyMessage(SearchResult result) internal string? IdentifyMessage(SearchResult result)
{ {
if (StaticIdentifier != null) if (StaticIdentifier != null)
return StaticIdentifier; return StaticIdentifier;
return IdentifyMessageCallback(result); return IdentifyMessageCallback!(result);
} }
public bool Statisfied(SearchResult result) internal bool Statisfied(SearchResult result)
{ {
foreach(var field in Fields) foreach(var field in Fields)
{ {
@ -34,90 +49,4 @@ namespace CryptoExchange.Net.Converters.MessageParsing.DynamicConverters
return true; return true;
} }
} }
public abstract class MessageFieldReference
{
public string SearchName { get; set; }
public int Depth { get; set; } = 1;
public Func<string?, bool>? Constraint { get; set; }
public MessageFieldReference(string searchName)
{
SearchName = searchName;
}
}
public class PropertyFieldReference : MessageFieldReference
{
public byte[] PropertyName { get; set; }
public bool ArrayValues { get; set; }
public PropertyFieldReference(string propertyName) : base(propertyName)
{
PropertyName = Encoding.UTF8.GetBytes(propertyName);
}
}
public class ArrayFieldReference : MessageFieldReference
{
public int ArrayIndex { get; set; }
public ArrayFieldReference(string searchName, int depth, int index) : base(searchName)
{
Depth = depth;
ArrayIndex = index;
}
}
public class MessageEvalutorFieldReference
{
public bool SkipReading { get; set; }
public bool OverlappingField { get; set; }
public MessageFieldReference Field { get; set; }
public MessageEvaluator? ForceEvaluator { get; set; }
}
public class SearchResult
{
private List<SearchResultItem> _items = new List<SearchResultItem>();
public string FieldValue(string searchName)
{
foreach(var item in _items)
{
if (item.Field.SearchName.Equals(searchName, StringComparison.Ordinal))
return item.Value;
}
throw new Exception(""); // TODO
}
public int Count => _items.Count;
public void Clear() => _items.Clear();
public bool Contains(MessageFieldReference field)
{
foreach(var item in _items)
{
if (item.Field == field)
return true;
}
return false;
}
public void Write(MessageFieldReference field, string? value) => _items.Add(new SearchResultItem
{
Field = field,
Value = value
});
}
public struct SearchResultItem
{
public MessageFieldReference Field { get; set; }
public string? Value { get; set; }
}
} }

View File

@ -0,0 +1,19 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace CryptoExchange.Net.Converters.MessageParsing.DynamicConverters
{
internal class MessageEvalutorFieldReference
{
public bool SkipReading { get; set; }
public bool OverlappingField { get; set; }
public MessageFieldReference Field { get; set; }
public MessageEvaluator? ForceEvaluator { get; set; }
public MessageEvalutorFieldReference(MessageFieldReference field)
{
Field = field;
}
}
}

View File

@ -0,0 +1,77 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace CryptoExchange.Net.Converters.MessageParsing.DynamicConverters
{
/// <summary>
/// Reference to a message field
/// </summary>
public abstract class MessageFieldReference
{
/// <summary>
/// The name for this search field
/// </summary>
public string SearchName { get; set; }
/// <summary>
/// The depth at which to look for this field
/// </summary>
public int Depth { get; set; } = 1;
/// <summary>
/// Callback to check if the field value matches an expected constraint
/// </summary>
public Func<string?, bool>? Constraint { get; set; }
/// <summary>
/// ctor
/// </summary>
public MessageFieldReference(string searchName)
{
SearchName = searchName;
}
}
/// <summary>
/// Reference to a property message field
/// </summary>
public class PropertyFieldReference : MessageFieldReference
{
/// <summary>
/// The property name in the JSON
/// </summary>
public byte[] PropertyName { get; set; }
/// <summary>
/// Whether the property value is array values
/// </summary>
public bool ArrayValues { get; set; }
/// <summary>
/// ctor
/// </summary>
public PropertyFieldReference(string propertyName) : base(propertyName)
{
PropertyName = Encoding.UTF8.GetBytes(propertyName);
}
}
/// <summary>
/// Reference to an array message field
/// </summary>
public class ArrayFieldReference : MessageFieldReference
{
/// <summary>
/// The index in the array
/// </summary>
public int ArrayIndex { get; set; }
/// <summary>
/// ctor
/// </summary>
public ArrayFieldReference(string searchName, int depth, int index) : base(searchName)
{
Depth = depth;
ArrayIndex = index;
}
}
}

View File

@ -0,0 +1,61 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace CryptoExchange.Net.Converters.MessageParsing.DynamicConverters
{
/// <summary>
/// The results of a search for fields in a JSON message
/// </summary>
public class SearchResult
{
private List<SearchResultItem> _items = new List<SearchResultItem>();
/// <summary>
/// Get the value of a field
/// </summary>
public string? FieldValue(string searchName)
{
foreach (var item in _items)
{
if (item.Field.SearchName.Equals(searchName, StringComparison.Ordinal))
return item.Value;
}
throw new Exception(""); // TODO
}
/// <summary>
/// The number of found search field values
/// </summary>
public int Count => _items.Count;
/// <summary>
/// Clear the search result
/// </summary>
public void Clear() => _items.Clear();
/// <summary>
/// Whether the value for a specific field was found
/// </summary>
public bool Contains(MessageFieldReference field)
{
foreach (var item in _items)
{
if (item.Field == field)
return true;
}
return false;
}
/// <summary>
/// Write a value to the result
/// </summary>
public void Write(MessageFieldReference field, string? value) => _items.Add(new SearchResultItem
{
Field = field,
Value = value
});
}
}

View File

@ -0,0 +1,21 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace CryptoExchange.Net.Converters.MessageParsing.DynamicConverters
{
/// <summary>
/// Search result value
/// </summary>
public struct SearchResultItem
{
/// <summary>
/// The field the values is for
/// </summary>
public MessageFieldReference Field { get; set; }
/// <summary>
/// The value of the field
/// </summary>
public string? Value { get; set; }
}
}

View File

@ -27,6 +27,8 @@ namespace CryptoExchange.Net.Converters.SystemTextJson.MessageConverters
/// </summary> /// </summary>
public abstract class JsonRestMessageHandler : IRestMessageHandler public abstract class JsonRestMessageHandler : IRestMessageHandler
{ {
private static MediaTypeWithQualityHeaderValue _acceptJsonContent = new MediaTypeWithQualityHeaderValue(Constants.JsonContentHeader);
/// <summary> /// <summary>
/// Empty rate limit error /// Empty rate limit error
/// </summary> /// </summary>
@ -37,6 +39,10 @@ namespace CryptoExchange.Net.Converters.SystemTextJson.MessageConverters
/// </summary> /// </summary>
public abstract JsonSerializerOptions Options { get; } public abstract JsonSerializerOptions Options { get; }
/// <inheritdoc />
public MediaTypeWithQualityHeaderValue AcceptHeader => _acceptJsonContent;
/// <inheritdoc />
public virtual object CreateState() => new JsonDocState(); public virtual object CreateState() => new JsonDocState();
/// <inheritdoc /> /// <inheritdoc />
@ -75,6 +81,9 @@ namespace CryptoExchange.Net.Converters.SystemTextJson.MessageConverters
HttpResponseHeaders responseHeaders, HttpResponseHeaders responseHeaders,
Stream responseStream) => new ValueTask<Error?>((Error?)null); Stream responseStream) => new ValueTask<Error?>((Error?)null);
/// <summary>
/// Read the response into a JsonDocument object
/// </summary>
protected virtual async ValueTask<(Error?, JsonDocument?)> GetJsonDocument(Stream stream, object? state) protected virtual async ValueTask<(Error?, JsonDocument?)> GetJsonDocument(Stream stream, object? state)
{ {
if (state is JsonDocState documentState && documentState.Document != null) if (state is JsonDocState documentState && documentState.Document != null)
@ -106,7 +115,7 @@ namespace CryptoExchange.Net.Converters.SystemTextJson.MessageConverters
} }
else else
{ {
result = await JsonSerializer.DeserializeAsync<T>(responseStream, Options)!.ConfigureAwait(false); result = await JsonSerializer.DeserializeAsync<T>(responseStream, Options)!.ConfigureAwait(false)!;
} }
return (result, null); return (result, null);
} }

View File

@ -101,12 +101,11 @@ namespace CryptoExchange.Net.Converters.SystemTextJson
} }
} }
_searchFields.Add(new MessageEvalutorFieldReference _searchFields.Add(new MessageEvalutorFieldReference(field)
{ {
SkipReading = evaluator.IdentifyMessageCallback == null && field.Constraint == null, SkipReading = evaluator.IdentifyMessageCallback == null && field.Constraint == null,
ForceEvaluator = !existingSameSearchField.Any() ? (evaluator.ForceIfFound ? evaluator : null) : null, ForceEvaluator = !existingSameSearchField.Any() ? (evaluator.ForceIfFound ? evaluator : null) : null,
OverlappingField = overlapping.Any(), OverlappingField = overlapping.Any()
Field = field
}); });
if (field.Depth > _maxSearchDepth) if (field.Depth > _maxSearchDepth)

View File

@ -21,10 +21,6 @@ namespace CryptoExchange.Net.Interfaces
/// </summary> /// </summary>
public MessageMatcher MessageMatcher { get; } public MessageMatcher MessageMatcher { get; }
/// <summary> /// <summary>
/// The types the message processor deserializes to
/// </summary>
public HashSet<Type> DeserializationTypes { get; set; }
/// <summary>
/// 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);

View File

@ -8,7 +8,8 @@ namespace CryptoExchange.Net.Objects.Options
public class ApiOptions public class ApiOptions
{ {
/// <summary> /// <summary>
/// If true, the CallResult and DataEvent objects will also include the originally received data in the OriginalData property /// If true, the CallResult and DataEvent objects will also include the originally received string data in the OriginalData property.
/// Note that this comes at a performance cost
/// </summary> /// </summary>
public bool? OutputOriginalData { get; set; } public bool? OutputOriginalData { get; set; }

View File

@ -14,7 +14,8 @@ namespace CryptoExchange.Net.Objects.Options
public ApiProxy? Proxy { get; set; } public ApiProxy? Proxy { get; set; }
/// <summary> /// <summary>
/// If true, the CallResult and DataEvent objects will also include the originally received data in the OriginalData property /// If true, the CallResult and DataEvent objects will also include the originally received string data in the OriginalData property.
/// Note that this comes at a performance cost
/// </summary> /// </summary>
public bool OutputOriginalData { get; set; } = false; public bool OutputOriginalData { get; set; } = false;

View File

@ -56,9 +56,9 @@ namespace CryptoExchange.Net.Objects.Sockets
} }
} }
/// <inheritdoc />
public class DataEvent<T> : DataEvent public class DataEvent<T> : DataEvent
{ {
/// <summary> /// <summary>
/// The received data deserialized into an object /// The received data deserialized into an object
/// </summary> /// </summary>
@ -120,8 +120,6 @@ namespace CryptoExchange.Net.Objects.Sockets
/// <summary> /// <summary>
/// Copy the DataEvent to a new data type /// Copy the DataEvent to a new data type
/// </summary> /// </summary>
/// <param name="exchange">The exchange the result is for</param>
/// <returns></returns>
public ExchangeEvent<K> AsExchangeEvent<K>(string exchange, K data) public ExchangeEvent<K> AsExchangeEvent<K>(string exchange, K data)
{ {
return new ExchangeEvent<K>(exchange, this, data) return new ExchangeEvent<K>(exchange, this, data)

View File

@ -41,7 +41,6 @@ namespace CryptoExchange.Net.Sockets
private ClientWebSocket _socket; private ClientWebSocket _socket;
private CancellationTokenSource _ctsSource; private CancellationTokenSource _ctsSource;
private DateTime _lastReceivedMessagesUpdate;
private Task? _processTask; private Task? _processTask;
private Task? _closeTask; private Task? _closeTask;
private bool _stopRequested; private bool _stopRequested;

View File

@ -59,22 +59,11 @@ namespace CryptoExchange.Net.Sockets
/// Response /// Response
/// </summary> /// </summary>
public object? Response { get; set; } public object? Response { get; set; }
#warning check if there is a better solution for this in combination with the MessageMatcher
public HashSet<Type> DeserializationTypes { get; set; }
private MessageMatcher _matcher;
/// <summary> /// <summary>
/// Matcher for this subscription /// Matcher for this subscription
/// </summary> /// </summary>
public MessageMatcher MessageMatcher public MessageMatcher MessageMatcher { get; set; }
{
get => _matcher;
set
{
_matcher = value;
DeserializationTypes = new HashSet<Type>(MessageMatcher.HandlerLinks.Select(x => x.DeserializationType));
}
}
/// <summary> /// <summary>
/// The query request object /// The query request object

View File

@ -35,8 +35,6 @@ namespace CryptoExchange.Net.Sockets
/// </summary> /// </summary>
public bool UserSubscription { get; set; } public bool UserSubscription { get; set; }
public HashSet<Type> DeserializationTypes { get; set; }
private SubscriptionStatus _status; private SubscriptionStatus _status;
/// <summary> /// <summary>
/// Current subscription status /// Current subscription status
@ -74,20 +72,10 @@ namespace CryptoExchange.Net.Sockets
/// </summary> /// </summary>
public bool Authenticated { get; } public bool Authenticated { get; }
private MessageMatcher _matcher;
/// <summary> /// <summary>
/// Matcher for this subscription /// Matcher for this subscription
/// </summary> /// </summary>
public MessageMatcher MessageMatcher public MessageMatcher MessageMatcher { get; set; }
{
get => _matcher;
set
{
_matcher = value;
DeserializationTypes = new HashSet<Type>(MessageMatcher.HandlerLinks.Select(x => x.DeserializationType));
}
}
/// <summary> /// <summary>
/// Cancellation token registration /// Cancellation token registration

View File

@ -17,8 +17,6 @@ namespace CryptoExchange.Net.Testing
/// <summary> /// <summary>
/// Get a client instance /// Get a client instance
/// </summary> /// </summary>
/// <param name="loggerFactory"></param>
/// <returns></returns>
public abstract TClient GetClient(ILoggerFactory loggerFactory, bool newDeserialization); public abstract TClient GetClient(ILoggerFactory loggerFactory, bool newDeserialization);
/// <summary> /// <summary>
@ -59,14 +57,15 @@ namespace CryptoExchange.Net.Testing
/// Execute a REST endpoint call and check for any errors or warnings. /// Execute a REST endpoint call and check for any errors or warnings.
/// </summary> /// </summary>
/// <typeparam name="T">Type of response</typeparam> /// <typeparam name="T">Type of response</typeparam>
/// <param name="useNewDeserialization">Whether to use the new deserialization method</param>
/// <param name="expression">The call expression</param> /// <param name="expression">The call expression</param>
/// <param name="authRequest">Whether this is an authenticated request</param> /// <param name="authRequest">Whether this is an authenticated request</param>
public async Task RunAndCheckResult<T>(bool newDeserialization, Expression<Func<TClient, Task<WebCallResult<T>>>> expression, bool authRequest) public async Task RunAndCheckResult<T>(bool useNewDeserialization, Expression<Func<TClient, Task<WebCallResult<T>>>> expression, bool authRequest)
{ {
if (!ShouldRun()) if (!ShouldRun())
return; return;
var client = CreateClient(newDeserialization); var client = CreateClient(useNewDeserialization);
var expressionBody = (MethodCallExpression)expression.Body; var expressionBody = (MethodCallExpression)expression.Body;
if (authRequest && !Authenticated) if (authRequest && !Authenticated)

View File

@ -19,8 +19,6 @@ namespace CryptoExchange.Net.Testing
/// <summary> /// <summary>
/// Get a client instance /// Get a client instance
/// </summary> /// </summary>
/// <param name="loggerFactory"></param>
/// <returns></returns>
public abstract TClient GetClient(ILoggerFactory loggerFactory, bool newDeserialization); public abstract TClient GetClient(ILoggerFactory loggerFactory, bool newDeserialization);
/// <summary> /// <summary>
@ -61,6 +59,7 @@ namespace CryptoExchange.Net.Testing
/// Execute a REST endpoint call and check for any errors or warnings. /// Execute a REST endpoint call and check for any errors or warnings.
/// </summary> /// </summary>
/// <typeparam name="T">Type of the update</typeparam> /// <typeparam name="T">Type of the update</typeparam>
/// <param name="useNewDeserialization">Whether to use the new deserialization method</param>
/// <param name="expression">The call expression</param> /// <param name="expression">The call expression</param>
/// <param name="expectUpdate">Whether an update is expected</param> /// <param name="expectUpdate">Whether an update is expected</param>
/// <param name="authRequest">Whether this is an authenticated request</param> /// <param name="authRequest">Whether this is an authenticated request</param>