mirror of
https://github.com/JKorf/CryptoExchange.Net
synced 2025-06-07 16:06:15 +00:00
Feature/body uri param split (#203)
* Added support for specifying seperate uri and body parameters * Added support for different message and handling generic types on socket queries * Split DataEvent.Topic into StreamId and Symbol properties * Added support for negative time values parsing * Added some helper methods for converting DataEvent to CallResult * Added support for GZip/Deflate automatic decompressing in the default HttpClient * Updated some testing methods
This commit is contained in:
parent
8080ecccc0
commit
9fcd722991
@ -49,11 +49,11 @@ namespace CryptoExchange.Net.Authentication
|
||||
/// <param name="method">The method of the request</param>
|
||||
/// <param name="auth">If the requests should be authenticated</param>
|
||||
/// <param name="arraySerialization">Array serialization type</param>
|
||||
/// <param name="parameterPosition">The position where the providedParameters should go</param>
|
||||
/// <param name="requestBodyFormat">The formatting of the request body</param>
|
||||
/// <param name="uriParameters">Parameters that need to be in the Uri of the request. Should include the provided parameters if they should go in the uri</param>
|
||||
/// <param name="bodyParameters">Parameters that need to be in the body of the request. Should include the provided parameters if they should go in the body</param>
|
||||
/// <param name="headers">The headers that should be send with the request</param>
|
||||
/// <param name="parameterPosition">The position where the providedParameters should go</param>
|
||||
public abstract void AuthenticateRequest(
|
||||
RestApiClient apiClient,
|
||||
Uri uri,
|
||||
|
@ -150,23 +150,60 @@ namespace CryptoExchange.Net.Clients
|
||||
/// <param name="additionalHeaders">Additional headers for this request</param>
|
||||
/// <param name="weight">Override the request weight for this request definition, for example when the weight depends on the parameters</param>
|
||||
/// <returns></returns>
|
||||
protected virtual async Task<WebCallResult<T>> SendAsync<T>(
|
||||
protected virtual Task<WebCallResult<T>> SendAsync<T>(
|
||||
string baseAddress,
|
||||
RequestDefinition definition,
|
||||
ParameterCollection? parameters,
|
||||
CancellationToken cancellationToken,
|
||||
Dictionary<string, string>? additionalHeaders = null,
|
||||
int? weight = null) where T : class
|
||||
{
|
||||
var parameterPosition = definition.ParameterPosition ?? ParameterPositions[definition.Method];
|
||||
return SendAsync<T>(
|
||||
baseAddress,
|
||||
definition,
|
||||
parameterPosition == HttpMethodParameterPosition.InUri ? parameters : null,
|
||||
parameterPosition == HttpMethodParameterPosition.InBody ? parameters : null,
|
||||
cancellationToken,
|
||||
additionalHeaders,
|
||||
weight);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Send a request to the base address based on the request definition
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Response type</typeparam>
|
||||
/// <param name="baseAddress">Host and schema</param>
|
||||
/// <param name="definition">Request definition</param>
|
||||
/// <param name="uriParameters">Request query parameters</param>
|
||||
/// <param name="bodyParameters">Request body parameters</param>
|
||||
/// <param name="cancellationToken">Cancellation token</param>
|
||||
/// <param name="additionalHeaders">Additional headers for this request</param>
|
||||
/// <param name="weight">Override the request weight for this request definition, for example when the weight depends on the parameters</param>
|
||||
/// <returns></returns>
|
||||
protected virtual async Task<WebCallResult<T>> SendAsync<T>(
|
||||
string baseAddress,
|
||||
RequestDefinition definition,
|
||||
ParameterCollection? uriParameters,
|
||||
ParameterCollection? bodyParameters,
|
||||
CancellationToken cancellationToken,
|
||||
Dictionary<string, string>? additionalHeaders = null,
|
||||
int? weight = null) where T : class
|
||||
{
|
||||
int currentTry = 0;
|
||||
while (true)
|
||||
{
|
||||
currentTry++;
|
||||
var prepareResult = await PrepareAsync(baseAddress, definition, parameters, cancellationToken, additionalHeaders, weight).ConfigureAwait(false);
|
||||
var prepareResult = await PrepareAsync(baseAddress, definition, cancellationToken, additionalHeaders, weight).ConfigureAwait(false);
|
||||
if (!prepareResult)
|
||||
return new WebCallResult<T>(prepareResult.Error!);
|
||||
|
||||
var request = CreateRequest(baseAddress, definition, parameters, additionalHeaders);
|
||||
var request = CreateRequest(
|
||||
baseAddress,
|
||||
definition,
|
||||
uriParameters,
|
||||
bodyParameters,
|
||||
additionalHeaders);
|
||||
_logger.RestApiSendRequest(request.RequestId, definition, request.Content, request.Uri.Query, string.Join(", ", request.GetHeaders().Select(h => h.Key + $"=[{string.Join(",", h.Value)}]")));
|
||||
TotalRequestsMade++;
|
||||
var result = await GetResponseAsync<T>(request, definition.RateLimitGate, cancellationToken).ConfigureAwait(false);
|
||||
@ -187,7 +224,6 @@ namespace CryptoExchange.Net.Clients
|
||||
/// </summary>
|
||||
/// <param name="baseAddress">Host and schema</param>
|
||||
/// <param name="definition">Request definition</param>
|
||||
/// <param name="parameters">Request parameters</param>
|
||||
/// <param name="cancellationToken">Cancellation token</param>
|
||||
/// <param name="additionalHeaders">Additional headers for this request</param>
|
||||
/// <param name="weight">Override the request weight for this request</param>
|
||||
@ -196,7 +232,6 @@ namespace CryptoExchange.Net.Clients
|
||||
protected virtual async Task<CallResult> PrepareAsync(
|
||||
string baseAddress,
|
||||
RequestDefinition definition,
|
||||
ParameterCollection? parameters,
|
||||
CancellationToken cancellationToken,
|
||||
Dictionary<string, string>? additionalHeaders = null,
|
||||
int? weight = null)
|
||||
@ -264,25 +299,27 @@ namespace CryptoExchange.Net.Clients
|
||||
/// </summary>
|
||||
/// <param name="baseAddress">Host and schema</param>
|
||||
/// <param name="definition">Request definition</param>
|
||||
/// <param name="parameters">The parameters of the request</param>
|
||||
/// <param name="uriParameters">The query parameters of the request</param>
|
||||
/// <param name="bodyParameters">The body parameters of the request</param>
|
||||
/// <param name="additionalHeaders">Additional headers to send with the request</param>
|
||||
/// <returns></returns>
|
||||
protected virtual IRequest CreateRequest(
|
||||
string baseAddress,
|
||||
RequestDefinition definition,
|
||||
ParameterCollection? parameters,
|
||||
ParameterCollection? uriParameters,
|
||||
ParameterCollection? bodyParameters,
|
||||
Dictionary<string, string>? additionalHeaders)
|
||||
{
|
||||
parameters ??= new ParameterCollection();
|
||||
var uriParams = uriParameters == null ? new ParameterCollection() : CreateParameterDictionary(uriParameters);
|
||||
var bodyParams = bodyParameters == null ? new ParameterCollection() : CreateParameterDictionary(bodyParameters);
|
||||
|
||||
var uri = new Uri(baseAddress.AppendPath(definition.Path));
|
||||
var parameterPosition = definition.ParameterPosition ?? ParameterPositions[definition.Method];
|
||||
var arraySerialization = definition.ArraySerialization ?? ArraySerialization;
|
||||
var bodyFormat = definition.RequestBodyFormat ?? RequestBodyFormat;
|
||||
var requestId = ExchangeHelpers.NextId();
|
||||
var parameterPosition = definition.ParameterPosition ?? ParameterPositions[definition.Method];
|
||||
|
||||
var headers = new Dictionary<string, string>();
|
||||
var uriParameters = parameterPosition == HttpMethodParameterPosition.InUri ? CreateParameterDictionary(parameters) : new Dictionary<string, object>();
|
||||
var bodyParameters = parameterPosition == HttpMethodParameterPosition.InBody ? CreateParameterDictionary(parameters) : new Dictionary<string, object>();
|
||||
if (AuthenticationProvider != null)
|
||||
{
|
||||
try
|
||||
@ -291,13 +328,14 @@ namespace CryptoExchange.Net.Clients
|
||||
this,
|
||||
uri,
|
||||
definition.Method,
|
||||
uriParameters,
|
||||
bodyParameters,
|
||||
uriParams,
|
||||
bodyParams,
|
||||
headers,
|
||||
definition.Authenticated,
|
||||
arraySerialization,
|
||||
parameterPosition,
|
||||
bodyFormat);
|
||||
bodyFormat
|
||||
);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@ -305,18 +343,8 @@ namespace CryptoExchange.Net.Clients
|
||||
}
|
||||
}
|
||||
|
||||
// Sanity check
|
||||
foreach (var param in parameters)
|
||||
{
|
||||
if (!uriParameters.ContainsKey(param.Key) && !bodyParameters.ContainsKey(param.Key))
|
||||
{
|
||||
throw new Exception($"Missing parameter {param.Key} after authentication processing. AuthenticationProvider implementation " +
|
||||
$"should return provided parameters in either the uri or body parameters output");
|
||||
}
|
||||
}
|
||||
|
||||
// Add the auth parameters to the uri, start with a new URI to be able to sort the parameters including the auth parameters
|
||||
uri = uri.SetParameters(uriParameters, arraySerialization);
|
||||
uri = uri.SetParameters(uriParams, arraySerialization);
|
||||
|
||||
var request = RequestFactory.Create(definition.Method, uri, requestId);
|
||||
request.Accept = Constants.JsonContentHeader;
|
||||
@ -343,8 +371,8 @@ namespace CryptoExchange.Net.Clients
|
||||
if (parameterPosition == HttpMethodParameterPosition.InBody)
|
||||
{
|
||||
var contentType = bodyFormat == RequestBodyFormat.Json ? Constants.JsonContentHeader : Constants.FormContentHeader;
|
||||
if (bodyParameters.Count != 0)
|
||||
WriteParamBody(request, bodyParameters, contentType);
|
||||
if (bodyParams.Count != 0)
|
||||
WriteParamBody(request, bodyParams, contentType);
|
||||
else
|
||||
request.SetContent(RequestBodyEmptyContent, contentType);
|
||||
}
|
||||
@ -739,7 +767,8 @@ namespace CryptoExchange.Net.Clients
|
||||
signed,
|
||||
arraySerialization,
|
||||
parameterPosition,
|
||||
bodyFormat);
|
||||
bodyFormat
|
||||
);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
@ -278,10 +278,11 @@ namespace CryptoExchange.Net.Clients
|
||||
/// <summary>
|
||||
/// Send a query on a socket connection to the BaseAddress and wait for the response
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Expected result type</typeparam>
|
||||
/// <typeparam name="THandlerResponse">Expected result type</typeparam>
|
||||
/// <typeparam name="TServerResponse">The type returned to the caller</typeparam>
|
||||
/// <param name="query">The query</param>
|
||||
/// <returns></returns>
|
||||
protected virtual Task<CallResult<T>> QueryAsync<T>(Query<T> query)
|
||||
protected virtual Task<CallResult<THandlerResponse>> QueryAsync<TServerResponse, THandlerResponse>(Query<TServerResponse, THandlerResponse> query)
|
||||
{
|
||||
return QueryAsync(BaseAddress, query);
|
||||
}
|
||||
@ -289,14 +290,15 @@ namespace CryptoExchange.Net.Clients
|
||||
/// <summary>
|
||||
/// Send a query on a socket connection and wait for the response
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The expected result type</typeparam>
|
||||
/// <typeparam name="THandlerResponse">Expected result type</typeparam>
|
||||
/// <typeparam name="TServerResponse">The type returned to the caller</typeparam>
|
||||
/// <param name="url">The url for the request</param>
|
||||
/// <param name="query">The query</param>
|
||||
/// <returns></returns>
|
||||
protected virtual async Task<CallResult<T>> QueryAsync<T>(string url, Query<T> query)
|
||||
protected virtual async Task<CallResult<THandlerResponse>> QueryAsync<TServerResponse, THandlerResponse>(string url, Query<TServerResponse, THandlerResponse> query)
|
||||
{
|
||||
if (_disposing)
|
||||
return new CallResult<T>(new InvalidOperationError("Client disposed, can't query"));
|
||||
return new CallResult<THandlerResponse>(new InvalidOperationError("Client disposed, can't query"));
|
||||
|
||||
SocketConnection socketConnection;
|
||||
var released = false;
|
||||
@ -305,7 +307,7 @@ namespace CryptoExchange.Net.Clients
|
||||
{
|
||||
var socketResult = await GetSocketConnection(url, query.Authenticated).ConfigureAwait(false);
|
||||
if (!socketResult)
|
||||
return socketResult.As<T>(default);
|
||||
return socketResult.As<THandlerResponse>(default);
|
||||
|
||||
socketConnection = socketResult.Data;
|
||||
|
||||
@ -318,7 +320,7 @@ namespace CryptoExchange.Net.Clients
|
||||
|
||||
var connectResult = await ConnectIfNeededAsync(socketConnection, query.Authenticated).ConfigureAwait(false);
|
||||
if (!connectResult)
|
||||
return new CallResult<T>(connectResult.Error!);
|
||||
return new CallResult<THandlerResponse>(connectResult.Error!);
|
||||
}
|
||||
finally
|
||||
{
|
||||
@ -329,10 +331,10 @@ namespace CryptoExchange.Net.Clients
|
||||
if (socketConnection.PausedActivity)
|
||||
{
|
||||
_logger.HasBeenPausedCantSendQueryAtThisMoment(socketConnection.SocketId);
|
||||
return new CallResult<T>(new ServerError("Socket is paused"));
|
||||
return new CallResult<THandlerResponse>(new ServerError("Socket is paused"));
|
||||
}
|
||||
|
||||
return await socketConnection.SendAndWaitQueryAsync(query).ConfigureAwait(false);
|
||||
return await socketConnection.SendAndWaitQueryAsync<TServerResponse, THandlerResponse>(query).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -154,6 +154,8 @@ namespace CryptoExchange.Net.Converters.SystemTextJson
|
||||
if (double.TryParse(stringValue, NumberStyles.Float, CultureInfo.InvariantCulture, out var doubleValue))
|
||||
{
|
||||
// Parse 1637745563.000 format
|
||||
if (doubleValue <= 0)
|
||||
return default;
|
||||
if (doubleValue < 19999999999)
|
||||
return ConvertFromSeconds(doubleValue);
|
||||
if (doubleValue < 19999999999999)
|
||||
|
@ -68,6 +68,11 @@ namespace CryptoExchange.Net.Converters.SystemTextJson
|
||||
var info = $"Deserialize JsonException: {ex.Message}, Path: {ex.Path}, LineNumber: {ex.LineNumber}, LinePosition: {ex.BytePositionInLine}";
|
||||
return new CallResult<T>(new DeserializeError(info, OriginalDataAvailable ? GetOriginalString() : "[Data only available when OutputOriginal = true in client options]"));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var info = $"Unknown exception: {ex.Message}";
|
||||
return new CallResult<T>(new DeserializeError(info, OriginalDataAvailable ? GetOriginalString() : "[Data only available when OutputOriginal = true in client options]"));
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
@ -14,9 +14,14 @@ namespace CryptoExchange.Net.Objects.Sockets
|
||||
public DateTime Timestamp { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The topic of the update, what symbol/asset etc..
|
||||
/// The stream producing the update
|
||||
/// </summary>
|
||||
public string? Topic { get; set; }
|
||||
public string? StreamId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The symbol the update is for
|
||||
/// </summary>
|
||||
public string? Symbol { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The original data that was received, only available when OutputOriginalData is set to true in the client options
|
||||
@ -33,10 +38,11 @@ namespace CryptoExchange.Net.Objects.Sockets
|
||||
/// </summary>
|
||||
public T Data { get; set; }
|
||||
|
||||
internal DataEvent(T data, string? topic, string? originalData, DateTime timestamp, SocketUpdateType? updateType)
|
||||
internal DataEvent(T data, string? streamId, string? symbol, string? originalData, DateTime timestamp, SocketUpdateType? updateType)
|
||||
{
|
||||
Data = data;
|
||||
Topic = topic;
|
||||
StreamId = streamId;
|
||||
Symbol = symbol;
|
||||
OriginalData = originalData;
|
||||
Timestamp = timestamp;
|
||||
UpdateType = updateType;
|
||||
@ -50,7 +56,7 @@ namespace CryptoExchange.Net.Objects.Sockets
|
||||
/// <returns></returns>
|
||||
public DataEvent<K> As<K>(K data)
|
||||
{
|
||||
return new DataEvent<K>(data, Topic, OriginalData, Timestamp, UpdateType);
|
||||
return new DataEvent<K>(data, StreamId, Symbol, OriginalData, Timestamp, UpdateType);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -58,11 +64,11 @@ namespace CryptoExchange.Net.Objects.Sockets
|
||||
/// </summary>
|
||||
/// <typeparam name="K">The type of the new data</typeparam>
|
||||
/// <param name="data">The new data</param>
|
||||
/// <param name="topic">The new topic</param>
|
||||
/// <param name="symbol">The new symbol</param>
|
||||
/// <returns></returns>
|
||||
public DataEvent<K> As<K>(K data, string? topic)
|
||||
public DataEvent<K> As<K>(K data, string? symbol)
|
||||
{
|
||||
return new DataEvent<K>(data, topic, OriginalData, Timestamp, UpdateType);
|
||||
return new DataEvent<K>(data, StreamId, symbol, OriginalData, Timestamp, UpdateType);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -70,12 +76,73 @@ namespace CryptoExchange.Net.Objects.Sockets
|
||||
/// </summary>
|
||||
/// <typeparam name="K">The type of the new data</typeparam>
|
||||
/// <param name="data">The new data</param>
|
||||
/// <param name="topic">The new topic</param>
|
||||
/// <param name="streamId">The new stream id</param>
|
||||
/// <param name="symbol">The new symbol</param>
|
||||
/// <param name="updateType">The type of update</param>
|
||||
/// <returns></returns>
|
||||
public DataEvent<K> As<K>(K data, string? topic, SocketUpdateType updateType)
|
||||
public DataEvent<K> As<K>(K data, string streamId, string? symbol, SocketUpdateType updateType)
|
||||
{
|
||||
return new DataEvent<K>(data, topic, OriginalData, Timestamp, updateType);
|
||||
return new DataEvent<K>(data, streamId, symbol, OriginalData, Timestamp, updateType);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Specify the symbol
|
||||
/// </summary>
|
||||
/// <param name="symbol"></param>
|
||||
/// <returns></returns>
|
||||
public DataEvent<T> WithSymbol(string symbol)
|
||||
{
|
||||
Symbol = symbol;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Specify the update type
|
||||
/// </summary>
|
||||
/// <param name="type"></param>
|
||||
/// <returns></returns>
|
||||
public DataEvent<T> WithUpdateType(SocketUpdateType type)
|
||||
{
|
||||
UpdateType = type;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Specify the stream id
|
||||
/// </summary>
|
||||
/// <param name="streamId"></param>
|
||||
/// <returns></returns>
|
||||
public DataEvent<T> WithStreamId(string streamId)
|
||||
{
|
||||
StreamId = streamId;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a CallResult from this DataEvent
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public CallResult<T> ToCallResult()
|
||||
{
|
||||
return new CallResult<T>(Data, OriginalData, null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a CallResult from this DataEvent
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public CallResult<K> ToCallResult<K>(K data)
|
||||
{
|
||||
return new CallResult<K>(data, OriginalData, null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a CallResult from this DataEvent
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public CallResult<K> ToCallResult<K>(Error error)
|
||||
{
|
||||
return new CallResult<K>(default, OriginalData, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -19,6 +19,7 @@ namespace CryptoExchange.Net.Requests
|
||||
if (client == null)
|
||||
{
|
||||
var handler = new HttpClientHandler();
|
||||
handler.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate;
|
||||
if (proxy != null)
|
||||
{
|
||||
handler.Proxy = new WebProxy
|
||||
|
@ -1,6 +1,7 @@
|
||||
using CryptoExchange.Net.Interfaces;
|
||||
using CryptoExchange.Net.Objects;
|
||||
using CryptoExchange.Net.Objects.Sockets;
|
||||
using CryptoExchange.Net.Requests;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
@ -145,16 +146,17 @@ namespace CryptoExchange.Net.Sockets
|
||||
/// <summary>
|
||||
/// Query
|
||||
/// </summary>
|
||||
/// <typeparam name="TResponse">Response object type</typeparam>
|
||||
public abstract class Query<TResponse> : Query
|
||||
/// <typeparam name="TServerResponse">The type returned from the server</typeparam>
|
||||
/// <typeparam name="THandlerResponse">The type to be returned to the caller</typeparam>
|
||||
public abstract class Query<TServerResponse, THandlerResponse> : Query
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override Type? GetMessageType(IMessageAccessor message) => typeof(TResponse);
|
||||
public override Type? GetMessageType(IMessageAccessor message) => typeof(TServerResponse);
|
||||
|
||||
/// <summary>
|
||||
/// The typed call result
|
||||
/// </summary>
|
||||
public CallResult<TResponse>? TypedResult => (CallResult<TResponse>?)Result;
|
||||
public CallResult<THandlerResponse>? TypedResult => (CallResult<THandlerResponse>?)Result;
|
||||
|
||||
/// <summary>
|
||||
/// ctor
|
||||
@ -171,7 +173,7 @@ namespace CryptoExchange.Net.Sockets
|
||||
{
|
||||
Completed = true;
|
||||
Response = message.Data;
|
||||
Result = HandleMessage(connection, message.As((TResponse)message.Data));
|
||||
Result = HandleMessage(connection, message.As((TServerResponse)message.Data));
|
||||
_event.Set();
|
||||
ContinueAwaiter?.WaitOne();
|
||||
return Result;
|
||||
@ -183,7 +185,7 @@ namespace CryptoExchange.Net.Sockets
|
||||
/// <param name="connection"></param>
|
||||
/// <param name="message"></param>
|
||||
/// <returns></returns>
|
||||
public virtual CallResult<TResponse> HandleMessage(SocketConnection connection, DataEvent<TResponse> message) => new CallResult<TResponse>(message.Data, message.OriginalData, null);
|
||||
public abstract CallResult<THandlerResponse> HandleMessage(SocketConnection connection, DataEvent<TServerResponse> message);
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Timeout()
|
||||
@ -192,7 +194,7 @@ namespace CryptoExchange.Net.Sockets
|
||||
return;
|
||||
|
||||
Completed = true;
|
||||
Result = new CallResult<TResponse>(new CancellationRequestedError(null, "Query timeout", null));
|
||||
Result = new CallResult<THandlerResponse>(new CancellationRequestedError(null, "Query timeout", null));
|
||||
ContinueAwaiter?.Set();
|
||||
_event.Set();
|
||||
}
|
||||
@ -200,10 +202,35 @@ namespace CryptoExchange.Net.Sockets
|
||||
/// <inheritdoc />
|
||||
public override void Fail(Error error)
|
||||
{
|
||||
Result = new CallResult<TResponse>(error);
|
||||
Result = new CallResult<THandlerResponse>(error);
|
||||
Completed = true;
|
||||
ContinueAwaiter?.Set();
|
||||
_event.Set();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Query
|
||||
/// </summary>
|
||||
/// <typeparam name="TResponse">Response object type</typeparam>
|
||||
public abstract class Query<TResponse> : Query<TResponse, TResponse>
|
||||
{
|
||||
/// <summary>
|
||||
/// ctor
|
||||
/// </summary>
|
||||
/// <param name="request"></param>
|
||||
/// <param name="authenticated"></param>
|
||||
/// <param name="weight"></param>
|
||||
protected Query(object request, bool authenticated, int weight = 1) : base(request, authenticated, weight)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle the query response
|
||||
/// </summary>
|
||||
/// <param name="connection"></param>
|
||||
/// <param name="message"></param>
|
||||
/// <returns></returns>
|
||||
public override CallResult<TResponse> HandleMessage(SocketConnection connection, DataEvent<TResponse> message) => message.ToCallResult();
|
||||
}
|
||||
}
|
||||
|
@ -498,7 +498,7 @@ namespace CryptoExchange.Net.Sockets
|
||||
try
|
||||
{
|
||||
var innerSw = Stopwatch.StartNew();
|
||||
processor.Handle(this, new DataEvent<object>(deserialized, null, originalData, receiveTime, null));
|
||||
processor.Handle(this, new DataEvent<object>(deserialized, null, null, originalData, receiveTime, null));
|
||||
totalUserTime += (int)innerSw.ElapsedMilliseconds;
|
||||
}
|
||||
catch (Exception ex)
|
||||
@ -696,14 +696,15 @@ namespace CryptoExchange.Net.Sockets
|
||||
/// <summary>
|
||||
/// Send a query request and wait for an answer
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Query response type</typeparam>
|
||||
/// <typeparam name="THandlerResponse">Expected result type</typeparam>
|
||||
/// <typeparam name="TServerResponse">The type returned to the caller</typeparam>
|
||||
/// <param name="query">Query to send</param>
|
||||
/// <param name="continueEvent">Wait event for when the socket message handler can continue</param>
|
||||
/// <returns></returns>
|
||||
public virtual async Task<CallResult<T>> SendAndWaitQueryAsync<T>(Query<T> query, ManualResetEvent? continueEvent = null)
|
||||
public virtual async Task<CallResult<THandlerResponse>> SendAndWaitQueryAsync<TServerResponse, THandlerResponse>(Query<TServerResponse, THandlerResponse> query, ManualResetEvent? continueEvent = null)
|
||||
{
|
||||
await SendAndWaitIntAsync(query, continueEvent).ConfigureAwait(false);
|
||||
return query.TypedResult ?? new CallResult<T>(new ServerError("Timeout"));
|
||||
return query.TypedResult ?? new CallResult<THandlerResponse>(new ServerError("Timeout"));
|
||||
}
|
||||
|
||||
private async Task SendAndWaitIntAsync(Query query, ManualResetEvent? continueEvent)
|
||||
|
@ -52,11 +52,16 @@ namespace CryptoExchange.Net.Testing.Comparers
|
||||
else
|
||||
{
|
||||
if (dict[dictProp.Name] == default && dictProp.Value.Type != JTokenType.Null)
|
||||
{
|
||||
if (dictProp.Value.ToString() == "")
|
||||
continue;
|
||||
|
||||
// Property value not correct
|
||||
throw new Exception($"{method}: Dictionary entry `{dictProp.Name}` has no value while input json has value {dictProp.Value}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (jsonObject!.Type == JTokenType.Array)
|
||||
{
|
||||
var jObjs = (JArray)jsonObject;
|
||||
@ -162,7 +167,7 @@ namespace CryptoExchange.Net.Testing.Comparers
|
||||
|
||||
if (dictProp.Value.Type == JTokenType.Object)
|
||||
{
|
||||
CheckObject(method, dictProp, dict[dictProp.Name]!, ignoreProperties);
|
||||
CheckPropertyValue(method, dictProp.Value, dict[dictProp.Name]!, dict[dictProp.Name].GetType(), null, null, ignoreProperties);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -180,7 +185,10 @@ namespace CryptoExchange.Net.Testing.Comparers
|
||||
var enumerator = list.GetEnumerator();
|
||||
foreach (JToken jtoken in jObjs)
|
||||
{
|
||||
enumerator.MoveNext();
|
||||
var moved = enumerator.MoveNext();
|
||||
if (!moved)
|
||||
throw new Exception("Enumeration not moved; incorrect amount of results?");
|
||||
|
||||
var typeConverter = enumerator.Current.GetType().GetCustomAttributes(typeof(JsonConverterAttribute), true);
|
||||
if (typeConverter.Length != 0 && ((JsonConverterAttribute)typeConverter.First()).ConverterType != typeof(ArrayConverter))
|
||||
// Custom converter for the type, skip
|
||||
@ -260,9 +268,9 @@ namespace CryptoExchange.Net.Testing.Comparers
|
||||
else if (objectValue is DateTime time)
|
||||
{
|
||||
if (time != DateTimeConverter.ParseFromString(jsonValue.Value<string>()!))
|
||||
throw new Exception($"{method}: {property} not equal: {jsonValue.Value<decimal>()} vs {time}");
|
||||
throw new Exception($"{method}: {property} not equal: {jsonValue.Value<string>()} vs {time}");
|
||||
}
|
||||
else if (propertyType.IsEnum)
|
||||
else if (propertyType.IsEnum || Nullable.GetUnderlyingType(propertyType)?.IsEnum == true)
|
||||
{
|
||||
// TODO enum comparing
|
||||
}
|
||||
@ -278,6 +286,10 @@ namespace CryptoExchange.Net.Testing.Comparers
|
||||
if (time != DateTimeConverter.ParseFromDouble(jsonValue.Value<long>()!))
|
||||
throw new Exception($"{method}: {property} not equal: {jsonValue.Value<decimal>()} vs {time}");
|
||||
}
|
||||
else if (propertyType.IsEnum || Nullable.GetUnderlyingType(propertyType)?.IsEnum == true)
|
||||
{
|
||||
// TODO enum comparing
|
||||
}
|
||||
else if (jsonValue.Value<long>() != Convert.ToInt64(objectValue))
|
||||
{
|
||||
throw new Exception($"{method}: {property} not equal: {jsonValue.Value<long>()} vs {Convert.ToInt64(objectValue)}");
|
||||
|
@ -109,6 +109,7 @@ namespace CryptoExchange.Net.Testing
|
||||
if (lastMessage == null)
|
||||
throw new Exception($"{name} expected to {line} to be send to server but did not receive anything");
|
||||
|
||||
|
||||
var lastMessageJson = JToken.Parse(lastMessage);
|
||||
var expectedJson = JToken.Parse(line.Substring(2));
|
||||
foreach(var item in expectedJson)
|
||||
@ -121,6 +122,12 @@ namespace CryptoExchange.Net.Testing
|
||||
overrideKey = val.ToString();
|
||||
overrideValue = lastMessageJson[prop.Name]?.Value<string>();
|
||||
}
|
||||
else if (val.ToString() == "-999")
|
||||
{
|
||||
// -999 value is used to replace parts or response messages
|
||||
overrideKey = val.ToString();
|
||||
overrideValue = lastMessageJson[prop.Name]?.Value<decimal>().ToString();
|
||||
}
|
||||
else if (lastMessageJson[prop.Name]?.Value<string>() != val.ToString() && ignoreProperties?.Contains(prop.Name) != true)
|
||||
throw new Exception($"{name} Expected {prop.Name} to be {val}, but was {lastMessageJson[prop.Name]?.Value<string>()}");
|
||||
}
|
||||
|
@ -137,7 +137,8 @@ namespace CryptoExchange.Net.Testing
|
||||
true,
|
||||
client.ArraySerialization,
|
||||
client.ParameterPositions[method],
|
||||
client.RequestBodyFormat);
|
||||
client.RequestBodyFormat
|
||||
);
|
||||
|
||||
var signature = getSignature(uriParams, bodyParams, headers);
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user