1
0
mirror of https://github.com/JKorf/CryptoExchange.Net synced 2025-06-08 00:16:27 +00:00

Added Request info and ResponseTime to WebCallResult, refactored CallResult ctors

This commit is contained in:
Jkorf 2022-01-14 16:47:49 +01:00
parent c6bf0d67a4
commit 8f6e853e13
9 changed files with 155 additions and 138 deletions

View File

@ -93,28 +93,28 @@ namespace CryptoExchange.Net
{ {
var info = "Empty data object received"; var info = "Empty data object received";
log.Write(LogLevel.Error, info); log.Write(LogLevel.Error, info);
return new CallResult<JToken>(null, new DeserializeError(info, data)); return new CallResult<JToken>(new DeserializeError(info, data));
} }
try try
{ {
return new CallResult<JToken>(JToken.Parse(data), null); return new CallResult<JToken>(JToken.Parse(data));
} }
catch (JsonReaderException jre) catch (JsonReaderException jre)
{ {
var info = $"Deserialize JsonReaderException: {jre.Message}, Path: {jre.Path}, LineNumber: {jre.LineNumber}, LinePosition: {jre.LinePosition}"; var info = $"Deserialize JsonReaderException: {jre.Message}, Path: {jre.Path}, LineNumber: {jre.LineNumber}, LinePosition: {jre.LinePosition}";
return new CallResult<JToken>(null, new DeserializeError(info, data)); return new CallResult<JToken>(new DeserializeError(info, data));
} }
catch (JsonSerializationException jse) catch (JsonSerializationException jse)
{ {
var info = $"Deserialize JsonSerializationException: {jse.Message}"; var info = $"Deserialize JsonSerializationException: {jse.Message}";
return new CallResult<JToken>(null, new DeserializeError(info, data)); return new CallResult<JToken>(new DeserializeError(info, data));
} }
catch (Exception ex) catch (Exception ex)
{ {
var exceptionInfo = ex.ToLogString(); var exceptionInfo = ex.ToLogString();
var info = $"Deserialize Unknown Exception: {exceptionInfo}"; var info = $"Deserialize Unknown Exception: {exceptionInfo}";
return new CallResult<JToken>(null, new DeserializeError(info, data)); return new CallResult<JToken>(new DeserializeError(info, data));
} }
} }
@ -132,7 +132,7 @@ namespace CryptoExchange.Net
if (!tokenResult) if (!tokenResult)
{ {
log.Write(LogLevel.Error, tokenResult.Error!.Message); log.Write(LogLevel.Error, tokenResult.Error!.Message);
return new CallResult<T>(default, tokenResult.Error); return new CallResult<T>( tokenResult.Error);
} }
return Deserialize<T>(tokenResult.Data, serializer, requestId); return Deserialize<T>(tokenResult.Data, serializer, requestId);
@ -152,26 +152,26 @@ namespace CryptoExchange.Net
try try
{ {
return new CallResult<T>(obj.ToObject<T>(serializer), null); return new CallResult<T>(obj.ToObject<T>(serializer)!);
} }
catch (JsonReaderException jre) catch (JsonReaderException jre)
{ {
var info = $"{(requestId != null ? $"[{requestId}] " : "")}Deserialize JsonReaderException: {jre.Message} Path: {jre.Path}, LineNumber: {jre.LineNumber}, LinePosition: {jre.LinePosition}, data: {obj}"; var info = $"{(requestId != null ? $"[{requestId}] " : "")}Deserialize JsonReaderException: {jre.Message} Path: {jre.Path}, LineNumber: {jre.LineNumber}, LinePosition: {jre.LinePosition}, data: {obj}";
log.Write(LogLevel.Error, info); log.Write(LogLevel.Error, info);
return new CallResult<T>(default, new DeserializeError(info, obj)); return new CallResult<T>(new DeserializeError(info, obj));
} }
catch (JsonSerializationException jse) catch (JsonSerializationException jse)
{ {
var info = $"{(requestId != null ? $"[{requestId}] " : "")}Deserialize JsonSerializationException: {jse.Message} data: {obj}"; var info = $"{(requestId != null ? $"[{requestId}] " : "")}Deserialize JsonSerializationException: {jse.Message} data: {obj}";
log.Write(LogLevel.Error, info); log.Write(LogLevel.Error, info);
return new CallResult<T>(default, new DeserializeError(info, obj)); return new CallResult<T>(new DeserializeError(info, obj));
} }
catch (Exception ex) catch (Exception ex)
{ {
var exceptionInfo = ex.ToLogString(); var exceptionInfo = ex.ToLogString();
var info = $"{(requestId != null ? $"[{requestId}] " : "")}Deserialize Unknown Exception: {exceptionInfo}, data: {obj}"; var info = $"{(requestId != null ? $"[{requestId}] " : "")}Deserialize Unknown Exception: {exceptionInfo}, data: {obj}";
log.Write(LogLevel.Error, info); log.Write(LogLevel.Error, info);
return new CallResult<T>(default, new DeserializeError(info, obj)); return new CallResult<T>(new DeserializeError(info, obj));
} }
} }
@ -208,7 +208,7 @@ namespace CryptoExchange.Net
// If we don't have to keep track of the original json data we can use the JsonTextReader to deserialize the stream directly // If we don't have to keep track of the original json data we can use the JsonTextReader to deserialize the stream directly
// into the desired object, which has increased performance over first reading the string value into memory and deserializing from that // into the desired object, which has increased performance over first reading the string value into memory and deserializing from that
using var jsonReader = new JsonTextReader(reader); using var jsonReader = new JsonTextReader(reader);
return new CallResult<T>(serializer.Deserialize<T>(jsonReader), null); return new CallResult<T>(serializer.Deserialize<T>(jsonReader)!);
} }
catch (JsonReaderException jre) catch (JsonReaderException jre)
{ {
@ -222,7 +222,7 @@ namespace CryptoExchange.Net
else else
data = "[Data only available in Debug LogLevel]"; data = "[Data only available in Debug LogLevel]";
log.Write(LogLevel.Error, $"{(requestId != null ? $"[{requestId}] " : "")}Deserialize JsonReaderException: {jre.Message}, Path: {jre.Path}, LineNumber: {jre.LineNumber}, LinePosition: {jre.LinePosition}, data: {data}"); log.Write(LogLevel.Error, $"{(requestId != null ? $"[{requestId}] " : "")}Deserialize JsonReaderException: {jre.Message}, Path: {jre.Path}, LineNumber: {jre.LineNumber}, LinePosition: {jre.LinePosition}, data: {data}");
return new CallResult<T>(default, new DeserializeError($"Deserialize JsonReaderException: {jre.Message}, Path: {jre.Path}, LineNumber: {jre.LineNumber}, LinePosition: {jre.LinePosition}", data)); return new CallResult<T>(new DeserializeError($"Deserialize JsonReaderException: {jre.Message}, Path: {jre.Path}, LineNumber: {jre.LineNumber}, LinePosition: {jre.LinePosition}", data));
} }
catch (JsonSerializationException jse) catch (JsonSerializationException jse)
{ {
@ -236,7 +236,7 @@ namespace CryptoExchange.Net
data = "[Data only available in Debug LogLevel]"; data = "[Data only available in Debug LogLevel]";
log.Write(LogLevel.Error, $"{(requestId != null ? $"[{requestId}] " : "")}Deserialize JsonSerializationException: {jse.Message}, data: {data}"); log.Write(LogLevel.Error, $"{(requestId != null ? $"[{requestId}] " : "")}Deserialize JsonSerializationException: {jse.Message}, data: {data}");
return new CallResult<T>(default, new DeserializeError($"Deserialize JsonSerializationException: {jse.Message}", data)); return new CallResult<T>(new DeserializeError($"Deserialize JsonSerializationException: {jse.Message}", data));
} }
catch (Exception ex) catch (Exception ex)
{ {
@ -250,7 +250,7 @@ namespace CryptoExchange.Net
var exceptionInfo = ex.ToLogString(); var exceptionInfo = ex.ToLogString();
log.Write(LogLevel.Error, $"{(requestId != null ? $"[{requestId}] " : "")}Deserialize Unknown Exception: {exceptionInfo}, data: {data}"); log.Write(LogLevel.Error, $"{(requestId != null ? $"[{requestId}] " : "")}Deserialize Unknown Exception: {exceptionInfo}, data: {data}");
return new CallResult<T>(default, new DeserializeError($"Deserialize Unknown Exception: {exceptionInfo}", data)); return new CallResult<T>(new DeserializeError($"Deserialize Unknown Exception: {exceptionInfo}", data));
} }
} }

View File

@ -136,7 +136,7 @@ namespace CryptoExchange.Net
if (signed && apiClient.AuthenticationProvider == null) if (signed && apiClient.AuthenticationProvider == null)
{ {
log.Write(LogLevel.Warning, $"[{requestId}] Request {uri.AbsolutePath} failed because no ApiCredentials were provided"); log.Write(LogLevel.Warning, $"[{requestId}] Request {uri.AbsolutePath} failed because no ApiCredentials were provided");
return new WebCallResult<T>(null, null, null, new NoApiCredentialsError()); return new WebCallResult<T>(new NoApiCredentialsError());
} }
var paramsPosition = parameterPosition ?? ParameterPositions[method]; var paramsPosition = parameterPosition ?? ParameterPositions[method];
@ -145,7 +145,7 @@ namespace CryptoExchange.Net
{ {
var limitResult = await limiter.LimitRequestAsync(log, uri.AbsolutePath, method, signed, apiClient.Options.ApiCredentials?.Key, apiClient.Options.RateLimitingBehaviour, requestWeight, cancellationToken).ConfigureAwait(false); var limitResult = await limiter.LimitRequestAsync(log, uri.AbsolutePath, method, signed, apiClient.Options.ApiCredentials?.Key, apiClient.Options.RateLimitingBehaviour, requestWeight, cancellationToken).ConfigureAwait(false);
if (!limitResult.Success) if (!limitResult.Success)
return new WebCallResult<T>(null, null, null, limitResult.Error); return new WebCallResult<T>(limitResult.Error!);
} }
string? paramString = ""; string? paramString = "";
@ -196,16 +196,16 @@ namespace CryptoExchange.Net
// Validate if it is valid json. Sometimes other data will be returned, 502 error html pages for example // Validate if it is valid json. Sometimes other data will be returned, 502 error html pages for example
var parseResult = ValidateJson(data); var parseResult = ValidateJson(data);
if (!parseResult.Success) if (!parseResult.Success)
return WebCallResult<T>.CreateErrorResult(response.StatusCode, response.ResponseHeaders, parseResult.Error!); return new WebCallResult<T>(response.StatusCode, response.ResponseHeaders, ClientOptions.OutputOriginalData ? data : null, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), default, parseResult.Error!);
// Let the library implementation see if it is an error response, and if so parse the error // Let the library implementation see if it is an error response, and if so parse the error
var error = await TryParseErrorAsync(parseResult.Data).ConfigureAwait(false); var error = await TryParseErrorAsync(parseResult.Data).ConfigureAwait(false);
if (error != null) if (error != null)
return WebCallResult<T>.CreateErrorResult(response.StatusCode, response.ResponseHeaders, error); return new WebCallResult<T>(response.StatusCode, response.ResponseHeaders, ClientOptions.OutputOriginalData ? data : null, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), default, error!);
// Not an error, so continue deserializing // Not an error, so continue deserializing
var deserializeResult = Deserialize<T>(parseResult.Data, deserializer, request.RequestId); var deserializeResult = Deserialize<T>(parseResult.Data, deserializer, request.RequestId);
return new WebCallResult<T>(response.StatusCode, response.ResponseHeaders, ClientOptions.OutputOriginalData ? data: null, deserializeResult.Data, deserializeResult.Error); return new WebCallResult<T>(response.StatusCode, response.ResponseHeaders, ClientOptions.OutputOriginalData ? data: null, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), deserializeResult.Data, deserializeResult.Error);
} }
else else
{ {
@ -214,7 +214,7 @@ namespace CryptoExchange.Net
responseStream.Close(); responseStream.Close();
response.Close(); response.Close();
return new WebCallResult<T>(statusCode, headers, ClientOptions.OutputOriginalData ? desResult.OriginalData : null, desResult.Data, desResult.Error); return new WebCallResult<T>(statusCode, headers, ClientOptions.OutputOriginalData ? desResult.OriginalData : null, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), desResult.Data, desResult.Error);
} }
} }
else else
@ -229,7 +229,7 @@ namespace CryptoExchange.Net
var error = parseResult.Success ? ParseErrorResponse(parseResult.Data) : parseResult.Error!; var error = parseResult.Success ? ParseErrorResponse(parseResult.Data) : parseResult.Error!;
if(error.Code == null || error.Code == 0) if(error.Code == null || error.Code == 0)
error.Code = (int)response.StatusCode; error.Code = (int)response.StatusCode;
return new WebCallResult<T>(statusCode, headers, default, error); return new WebCallResult<T>(statusCode, headers, data, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), default, error);
} }
} }
catch (HttpRequestException requestException) catch (HttpRequestException requestException)
@ -237,7 +237,7 @@ namespace CryptoExchange.Net
// Request exception, can't reach server for instance // Request exception, can't reach server for instance
var exceptionInfo = requestException.ToLogString(); var exceptionInfo = requestException.ToLogString();
log.Write(LogLevel.Warning, $"[{request.RequestId}] Request exception: " + exceptionInfo); log.Write(LogLevel.Warning, $"[{request.RequestId}] Request exception: " + exceptionInfo);
return new WebCallResult<T>(null, null, default, new WebError(exceptionInfo)); return new WebCallResult<T>(null, null, null, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), default, new WebError(exceptionInfo));
} }
catch (OperationCanceledException canceledException) catch (OperationCanceledException canceledException)
{ {
@ -245,13 +245,13 @@ namespace CryptoExchange.Net
{ {
// Cancellation token canceled by caller // Cancellation token canceled by caller
log.Write(LogLevel.Warning, $"[{request.RequestId}] Request canceled by cancellation token"); log.Write(LogLevel.Warning, $"[{request.RequestId}] Request canceled by cancellation token");
return new WebCallResult<T>(null, null, default, new CancellationRequestedError()); return new WebCallResult<T>(null, null, null, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), default, new CancellationRequestedError());
} }
else else
{ {
// Request timed out // Request timed out
log.Write(LogLevel.Warning, $"[{request.RequestId}] Request timed out: " + canceledException.ToLogString()); log.Write(LogLevel.Warning, $"[{request.RequestId}] Request timed out: " + canceledException.ToLogString());
return new WebCallResult<T>(null, null, default, new WebError($"[{request.RequestId}] Request timed out")); return new WebCallResult<T>(null, null, null, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), default, new WebError($"[{request.RequestId}] Request timed out"));
} }
} }
} }

View File

@ -163,7 +163,7 @@ namespace CryptoExchange.Net
} }
catch (OperationCanceledException) catch (OperationCanceledException)
{ {
return new CallResult<UpdateSubscription>(null, new CancellationRequestedError()); return new CallResult<UpdateSubscription>(new CancellationRequestedError());
} }
try try
@ -184,7 +184,7 @@ namespace CryptoExchange.Net
var connectResult = await ConnectIfNeededAsync(socketConnection, authenticated).ConfigureAwait(false); var connectResult = await ConnectIfNeededAsync(socketConnection, authenticated).ConfigureAwait(false);
if (!connectResult) if (!connectResult)
return new CallResult<UpdateSubscription>(null, connectResult.Error); return new CallResult<UpdateSubscription>(connectResult.Error!);
if (needsConnecting) if (needsConnecting)
log.Write(LogLevel.Debug, $"Socket {socketConnection.Socket.Id} connected to {url} {(request == null ? "": "with request " + JsonConvert.SerializeObject(request))}"); log.Write(LogLevel.Debug, $"Socket {socketConnection.Socket.Id} connected to {url} {(request == null ? "": "with request " + JsonConvert.SerializeObject(request))}");
@ -198,7 +198,7 @@ namespace CryptoExchange.Net
if (socketConnection.PausedActivity) if (socketConnection.PausedActivity)
{ {
log.Write(LogLevel.Information, $"Socket {socketConnection.Socket.Id} has been paused, can't subscribe at this moment"); log.Write(LogLevel.Information, $"Socket {socketConnection.Socket.Id} has been paused, can't subscribe at this moment");
return new CallResult<UpdateSubscription>(default, new ServerError("Socket is paused")); return new CallResult<UpdateSubscription>( new ServerError("Socket is paused"));
} }
if (request != null) if (request != null)
@ -208,7 +208,7 @@ namespace CryptoExchange.Net
if (!subResult) if (!subResult)
{ {
await socketConnection.CloseAsync(subscription).ConfigureAwait(false); await socketConnection.CloseAsync(subscription).ConfigureAwait(false);
return new CallResult<UpdateSubscription>(null, subResult.Error); return new CallResult<UpdateSubscription>(subResult.Error!);
} }
} }
else else
@ -226,7 +226,7 @@ namespace CryptoExchange.Net
await socketConnection.CloseAsync(subscription).ConfigureAwait(false); await socketConnection.CloseAsync(subscription).ConfigureAwait(false);
}, false); }, false);
} }
return new CallResult<UpdateSubscription>(new UpdateSubscription(socketConnection, subscription), null); return new CallResult<UpdateSubscription>(new UpdateSubscription(socketConnection, subscription));
} }
/// <summary> /// <summary>
@ -242,9 +242,15 @@ namespace CryptoExchange.Net
await socketConnection.SendAndWaitAsync(request, ClientOptions.SocketResponseTimeout, data => HandleSubscriptionResponse(socketConnection, subscription, request, data, out callResult)).ConfigureAwait(false); await socketConnection.SendAndWaitAsync(request, ClientOptions.SocketResponseTimeout, data => HandleSubscriptionResponse(socketConnection, subscription, request, data, out callResult)).ConfigureAwait(false);
if (callResult?.Success == true) if (callResult?.Success == true)
{
subscription.Confirmed = true; subscription.Confirmed = true;
return new CallResult<bool>(true);
}
return new CallResult<bool>(callResult?.Success ?? false, callResult == null ? new ServerError("No response on subscription request received"): callResult.Error); if(callResult== null)
return new CallResult<bool>(new ServerError("No response on subscription request received"));
return new CallResult<bool>(callResult.Error!);
} }
/// <summary> /// <summary>
@ -286,7 +292,7 @@ namespace CryptoExchange.Net
var connectResult = await ConnectIfNeededAsync(socketConnection, authenticated).ConfigureAwait(false); var connectResult = await ConnectIfNeededAsync(socketConnection, authenticated).ConfigureAwait(false);
if (!connectResult) if (!connectResult)
return new CallResult<T>(default, connectResult.Error); return new CallResult<T>(connectResult.Error!);
} }
finally finally
{ {
@ -299,7 +305,7 @@ namespace CryptoExchange.Net
if (socketConnection.PausedActivity) if (socketConnection.PausedActivity)
{ {
log.Write(LogLevel.Information, $"Socket {socketConnection.Socket.Id} has been paused, can't send query at this moment"); log.Write(LogLevel.Information, $"Socket {socketConnection.Socket.Id} has been paused, can't send query at this moment");
return new CallResult<T>(default, new ServerError("Socket is paused")); return new CallResult<T>(new ServerError("Socket is paused"));
} }
return await QueryAndWaitAsync<T>(socketConnection, request).ConfigureAwait(false); return await QueryAndWaitAsync<T>(socketConnection, request).ConfigureAwait(false);
@ -314,7 +320,7 @@ namespace CryptoExchange.Net
/// <returns></returns> /// <returns></returns>
protected virtual async Task<CallResult<T>> QueryAndWaitAsync<T>(SocketConnection socket, object request) protected virtual async Task<CallResult<T>> QueryAndWaitAsync<T>(SocketConnection socket, object request)
{ {
var dataResult = new CallResult<T>(default, new ServerError("No response on query received")); var dataResult = new CallResult<T>(new ServerError("No response on query received"));
await socket.SendAndWaitAsync(request, ClientOptions.SocketResponseTimeout, data => await socket.SendAndWaitAsync(request, ClientOptions.SocketResponseTimeout, data =>
{ {
if (!HandleQueryResponse<T>(socket, request, data, out var callResult)) if (!HandleQueryResponse<T>(socket, request, data, out var callResult))
@ -336,14 +342,14 @@ namespace CryptoExchange.Net
protected virtual async Task<CallResult<bool>> ConnectIfNeededAsync(SocketConnection socket, bool authenticated) protected virtual async Task<CallResult<bool>> ConnectIfNeededAsync(SocketConnection socket, bool authenticated)
{ {
if (socket.Connected) if (socket.Connected)
return new CallResult<bool>(true, null); return new CallResult<bool>(true);
var connectResult = await ConnectSocketAsync(socket).ConfigureAwait(false); var connectResult = await ConnectSocketAsync(socket).ConfigureAwait(false);
if (!connectResult) if (!connectResult)
return new CallResult<bool>(false, connectResult.Error); return new CallResult<bool>(connectResult.Error!);
if (!authenticated || socket.Authenticated) if (!authenticated || socket.Authenticated)
return new CallResult<bool>(true, null); return new CallResult<bool>(true);
var result = await AuthenticateSocketAsync(socket).ConfigureAwait(false); var result = await AuthenticateSocketAsync(socket).ConfigureAwait(false);
if (!result) if (!result)
@ -351,11 +357,11 @@ namespace CryptoExchange.Net
await socket.CloseAsync().ConfigureAwait(false); await socket.CloseAsync().ConfigureAwait(false);
log.Write(LogLevel.Warning, $"Socket {socket.Socket.Id} authentication failed"); log.Write(LogLevel.Warning, $"Socket {socket.Socket.Id} authentication failed");
result.Error!.Message = "Authentication failed: " + result.Error.Message; result.Error!.Message = "Authentication failed: " + result.Error.Message;
return new CallResult<bool>(false, result.Error); return new CallResult<bool>(result.Error);
} }
socket.Authenticated = true; socket.Authenticated = true;
return new CallResult<bool>(true, null); return new CallResult<bool>(true);
} }
/// <summary> /// <summary>
@ -532,11 +538,11 @@ namespace CryptoExchange.Net
if (await socketConnection.Socket.ConnectAsync().ConfigureAwait(false)) if (await socketConnection.Socket.ConnectAsync().ConfigureAwait(false))
{ {
sockets.TryAdd(socketConnection.Socket.Id, socketConnection); sockets.TryAdd(socketConnection.Socket.Id, socketConnection);
return new CallResult<bool>(true, null); return new CallResult<bool>(true);
} }
socketConnection.Socket.Dispose(); socketConnection.Socket.Dispose();
return new CallResult<bool>(false, new CantConnectError()); return new CallResult<bool>(new CantConnectError());
} }
/// <summary> /// <summary>

View File

@ -34,7 +34,7 @@ namespace CryptoExchange.Net
/// <summary> /// <summary>
/// Options for this client /// Options for this client
/// </summary> /// </summary>
public RestApiClientOptions Options { get; } public new RestApiClientOptions Options => (RestApiClientOptions)base.Options;
/// <summary> /// <summary>
/// List of rate limiters /// List of rate limiters
@ -48,8 +48,6 @@ namespace CryptoExchange.Net
/// <param name="apiOptions">The Api client options</param> /// <param name="apiOptions">The Api client options</param>
public RestApiClient(BaseRestClientOptions options, RestApiClientOptions apiOptions): base(options, apiOptions) public RestApiClient(BaseRestClientOptions options, RestApiClientOptions apiOptions): base(options, apiOptions)
{ {
Options = apiOptions;
var rateLimiters = new List<IRateLimiter>(); var rateLimiters = new List<IRateLimiter>();
foreach (var rateLimiter in apiOptions.RateLimiters) foreach (var rateLimiter in apiOptions.RateLimiters)
rateLimiters.Add(rateLimiter); rateLimiters.Add(rateLimiter);
@ -70,7 +68,7 @@ namespace CryptoExchange.Net
if (!timeSyncParams.SyncTime || (DateTime.UtcNow - timeSyncParams.TimeSyncState.LastSyncTime < TimeSpan.FromHours(1))) if (!timeSyncParams.SyncTime || (DateTime.UtcNow - timeSyncParams.TimeSyncState.LastSyncTime < TimeSpan.FromHours(1)))
{ {
timeSyncParams.TimeSyncState.Semaphore.Release(); timeSyncParams.TimeSyncState.Semaphore.Release();
return new WebCallResult<bool>(null, null, true, null); return new WebCallResult<bool>(null, null, null, null, null, null, null, true, null);
} }
var localTime = DateTime.UtcNow; var localTime = DateTime.UtcNow;
@ -108,7 +106,7 @@ namespace CryptoExchange.Net
} }
} }
return new WebCallResult<bool>(null, null, true, null); return new WebCallResult<bool>(null, null, null, null, null, null, null, true, null);
} }
} }
} }

View File

@ -7,11 +7,6 @@ namespace CryptoExchange.Net
/// </summary> /// </summary>
public abstract class SocketApiClient : BaseApiClient public abstract class SocketApiClient : BaseApiClient
{ {
/// <summary>
/// The options for this client
/// </summary>
internal ApiClientOptions Options { get; }
/// <summary> /// <summary>
/// ctor /// ctor
/// </summary> /// </summary>
@ -19,8 +14,6 @@ namespace CryptoExchange.Net
/// <param name="apiOptions">The Api client options</param> /// <param name="apiOptions">The Api client options</param>
public SocketApiClient(BaseClientOptions options, ApiClientOptions apiOptions): base(options, apiOptions) public SocketApiClient(BaseClientOptions options, ApiClientOptions apiOptions): base(options, apiOptions)
{ {
Options = apiOptions;
} }
} }
} }

View File

@ -1,6 +1,8 @@
using System.Collections.Generic; using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Net; using System.Net;
using System.Net.Http;
namespace CryptoExchange.Net.Objects namespace CryptoExchange.Net.Objects
{ {
@ -36,16 +38,6 @@ namespace CryptoExchange.Net.Objects
{ {
return obj?.Success == true; return obj?.Success == true;
} }
/// <summary>
/// Create an error result
/// </summary>
/// <param name="error"></param>
/// <returns></returns>
public static WebCallResult CreateErrorResult(Error error)
{
return new WebCallResult(null, null, error);
}
} }
/// <summary> /// <summary>
@ -62,22 +54,36 @@ namespace CryptoExchange.Net.Objects
/// <summary> /// <summary>
/// The original data returned by the call, only available when `OutputOriginalData` is set to `true` in the client options /// The original data returned by the call, only available when `OutputOriginalData` is set to `true` in the client options
/// </summary> /// </summary>
public string? OriginalData { get; set; } public string? OriginalData { get; internal set; }
/// <summary> /// <summary>
/// ctor /// ctor
/// </summary> /// </summary>
/// <param name="data"></param> /// <param name="data"></param>
/// <param name="originalData"></param>
/// <param name="error"></param> /// <param name="error"></param>
#pragma warning disable 8618 #pragma warning disable 8618
public CallResult([AllowNull]T data, Error? error): base(error) protected CallResult([AllowNull]T data, string? originalData, Error? error): base(error)
#pragma warning restore 8618 #pragma warning restore 8618
{ {
OriginalData = originalData;
#pragma warning disable 8601 #pragma warning disable 8601
Data = data; Data = data;
#pragma warning restore 8601 #pragma warning restore 8601
} }
/// <summary>
/// Create a new data result
/// </summary>
/// <param name="data">The data to return</param>
public CallResult(T data) : this(data, null, null) { }
/// <summary>
/// Create a new error result
/// </summary>
/// <param name="error">The erro rto return</param>
public CallResult(Error error) : this(default, null, error) { }
/// <summary> /// <summary>
/// Overwrite bool check so we can use if(callResult) instead of if(callResult.Success) /// Overwrite bool check so we can use if(callResult) instead of if(callResult.Success)
/// </summary> /// </summary>
@ -111,16 +117,6 @@ namespace CryptoExchange.Net.Objects
} }
} }
/// <summary>
/// Create an error result
/// </summary>
/// <param name="error"></param>
/// <returns></returns>
public new static WebCallResult<T> CreateErrorResult(Error error)
{
return new WebCallResult<T>(null, null, default, error);
}
/// <summary> /// <summary>
/// Copy the WebCallResult to a new data type /// Copy the WebCallResult to a new data type
/// </summary> /// </summary>
@ -129,7 +125,18 @@ namespace CryptoExchange.Net.Objects
/// <returns></returns> /// <returns></returns>
public CallResult<K> As<K>([AllowNull] K data) public CallResult<K> As<K>([AllowNull] K data)
{ {
return new CallResult<K>(data, Error); return new CallResult<K>(data, OriginalData, Error);
}
/// <summary>
/// Copy the WebCallResult to a new data type
/// </summary>
/// <typeparam name="K">The new type</typeparam>
/// <param name="error">The error to return</param>
/// <returns></returns>
public CallResult<K> AsError<K>(Error error)
{
return new CallResult<K>(default, OriginalData, error);
} }
} }
@ -154,34 +161,23 @@ namespace CryptoExchange.Net.Objects
/// <param name="code">Status code</param> /// <param name="code">Status code</param>
/// <param name="responseHeaders">Response headers</param> /// <param name="responseHeaders">Response headers</param>
/// <param name="error">Error</param> /// <param name="error">Error</param>
public WebCallResult( private WebCallResult(
HttpStatusCode? code, HttpStatusCode? code,
IEnumerable<KeyValuePair<string, IEnumerable<string>>>? responseHeaders, Error? error) : base(error) IEnumerable<KeyValuePair<string, IEnumerable<string>>>? responseHeaders,
Error? error) : base(error)
{ {
ResponseHeaders = responseHeaders; ResponseHeaders = responseHeaders;
ResponseStatusCode = code; ResponseStatusCode = code;
} }
/// <summary> /// <summary>
/// Create an error result /// Return the result as an error result
/// </summary> /// </summary>
/// <param name="code">Status code</param> /// <param name="error">The error returned</param>
/// <param name="responseHeaders">Response headers</param>
/// <param name="error">Error</param>
/// <returns></returns> /// <returns></returns>
public static WebCallResult CreateErrorResult(HttpStatusCode? code, IEnumerable<KeyValuePair<string, IEnumerable<string>>>? responseHeaders, Error error) public WebCallResult AsError(Error error)
{ {
return new WebCallResult(code, responseHeaders, error); return new WebCallResult(ResponseStatusCode, ResponseHeaders, error);
}
/// <summary>
/// Create an error result
/// </summary>
/// <param name="result"></param>
/// <returns></returns>
public static WebCallResult CreateErrorResult(WebCallResult result)
{
return new WebCallResult(result.ResponseStatusCode, result.ResponseHeaders, result.Error);
} }
} }
@ -191,6 +187,26 @@ namespace CryptoExchange.Net.Objects
/// <typeparam name="T"></typeparam> /// <typeparam name="T"></typeparam>
public class WebCallResult<T>: CallResult<T> public class WebCallResult<T>: CallResult<T>
{ {
/// <summary>
/// The request http method
/// </summary>
public HttpMethod? RequestMethod { get; set; }
/// <summary>
/// The headers sent with the request
/// </summary>
public IEnumerable<KeyValuePair<string, IEnumerable<string>>>? RequestHeaders { get; set; }
/// <summary>
/// The url which was requested
/// </summary>
public string? RequestUrl { get; set; }
/// <summary>
/// The body of the request
/// </summary>
public string? RequestBody { get; set; }
/// <summary> /// <summary>
/// The status code of the response. Note that a OK status does not always indicate success, check the Success parameter for this. /// The status code of the response. Note that a OK status does not always indicate success, check the Success parameter for this.
/// </summary> /// </summary>
@ -200,44 +216,49 @@ namespace CryptoExchange.Net.Objects
/// The response headers /// The response headers
/// </summary> /// </summary>
public IEnumerable<KeyValuePair<string, IEnumerable<string>>>? ResponseHeaders { get; set; } public IEnumerable<KeyValuePair<string, IEnumerable<string>>>? ResponseHeaders { get; set; }
/// <summary>
/// ctor
/// </summary>
/// <param name="code"></param>
/// <param name="responseHeaders"></param>
/// <param name="data"></param>
/// <param name="error"></param>
public WebCallResult(
HttpStatusCode? code,
IEnumerable<KeyValuePair<string, IEnumerable<string>>>? responseHeaders,
[AllowNull] T data,
Error? error): base(data, error)
{
ResponseStatusCode = code;
ResponseHeaders = responseHeaders;
}
/// <summary> /// <summary>
/// ctor /// The time between sending the request and receiving the response
/// </summary>
public TimeSpan? ResponseTime { get; set; }
/// <summary>
/// Create a new result
/// </summary> /// </summary>
/// <param name="code"></param> /// <param name="code"></param>
/// <param name="originalData"></param>
/// <param name="responseHeaders"></param> /// <param name="responseHeaders"></param>
/// <param name="originalData"></param>
/// <param name="requestUrl"></param>
/// <param name="requestBody"></param>
/// <param name="requestMethod"></param>
/// <param name="requestHeaders"></param>
/// <param name="data"></param> /// <param name="data"></param>
/// <param name="error"></param> /// <param name="error"></param>
public WebCallResult( public WebCallResult(
HttpStatusCode? code, HttpStatusCode? code,
IEnumerable<KeyValuePair<string, IEnumerable<string>>>? responseHeaders, IEnumerable<KeyValuePair<string, IEnumerable<string>>>? responseHeaders,
string? originalData, string? originalData,
string? requestUrl,
string? requestBody,
HttpMethod? requestMethod,
IEnumerable<KeyValuePair<string, IEnumerable<string>>>? requestHeaders,
[AllowNull] T data, [AllowNull] T data,
Error? error) : base(data, error) Error? error) : base(data, originalData, error)
{ {
OriginalData = originalData;
ResponseStatusCode = code; ResponseStatusCode = code;
ResponseHeaders = responseHeaders; ResponseHeaders = responseHeaders;
RequestUrl = requestUrl;
RequestBody = requestBody;
RequestHeaders = requestHeaders;
RequestMethod = requestMethod;
} }
/// <summary>
/// Create a new error result
/// </summary>
/// <param name="error">The error</param>
public WebCallResult(Error? error) : this(null, null, null, null, null, null, null, default, error) { }
/// <summary> /// <summary>
/// Copy the WebCallResult to a new data type /// Copy the WebCallResult to a new data type
/// </summary> /// </summary>
@ -246,19 +267,18 @@ namespace CryptoExchange.Net.Objects
/// <returns></returns> /// <returns></returns>
public new WebCallResult<K> As<K>([AllowNull] K data) public new WebCallResult<K> As<K>([AllowNull] K data)
{ {
return new WebCallResult<K>(ResponseStatusCode, ResponseHeaders, OriginalData, data, Error); return new WebCallResult<K>(ResponseStatusCode, ResponseHeaders, OriginalData, RequestUrl, RequestBody, RequestMethod, RequestHeaders, data, Error);
} }
/// <summary> /// <summary>
/// Create an error result /// Copy the WebCallResult to a new data type
/// </summary> /// </summary>
/// <param name="code"></param> /// <typeparam name="K">The new type</typeparam>
/// <param name="responseHeaders"></param> /// <param name="error">The error returned</param>
/// <param name="error"></param>
/// <returns></returns> /// <returns></returns>
public static WebCallResult<T> CreateErrorResult(HttpStatusCode? code, IEnumerable<KeyValuePair<string, IEnumerable<string>>>? responseHeaders, Error error) public new WebCallResult<K> AsError<K>(Error error)
{ {
return new WebCallResult<T>(code, responseHeaders, default, error); return new WebCallResult<K>(ResponseStatusCode, ResponseHeaders, OriginalData, RequestUrl, RequestBody, RequestMethod, RequestHeaders, default, error);
} }
} }
} }

View File

@ -118,7 +118,7 @@ namespace CryptoExchange.Net.Objects
} }
if (endpointLimit?.IgnoreOtherRateLimits == true) if (endpointLimit?.IgnoreOtherRateLimits == true)
return new CallResult<int>(totalWaitTime, null); return new CallResult<int>(totalWaitTime);
List<PartialEndpointRateLimiter> partialEndpointLimits; List<PartialEndpointRateLimiter> partialEndpointLimits;
lock (_limiterLock) lock (_limiterLock)
@ -155,7 +155,7 @@ namespace CryptoExchange.Net.Objects
} }
if(partialEndpointLimits.Any(p => p.IgnoreOtherRateLimits)) if(partialEndpointLimits.Any(p => p.IgnoreOtherRateLimits))
return new CallResult<int>(totalWaitTime, null); return new CallResult<int>(totalWaitTime);
ApiKeyRateLimiter? apiLimit; ApiKeyRateLimiter? apiLimit;
lock (_limiterLock) lock (_limiterLock)
@ -195,7 +195,7 @@ namespace CryptoExchange.Net.Objects
} }
if ((signed || apiLimit?.OnlyForSignedRequests == false) && apiLimit?.IgnoreTotalRateLimit == true) if ((signed || apiLimit?.OnlyForSignedRequests == false) && apiLimit?.IgnoreTotalRateLimit == true)
return new CallResult<int>(totalWaitTime, null); return new CallResult<int>(totalWaitTime);
TotalRateLimiter? totalLimit; TotalRateLimiter? totalLimit;
lock (_limiterLock) lock (_limiterLock)
@ -209,7 +209,7 @@ namespace CryptoExchange.Net.Objects
totalWaitTime += waitResult.Data; totalWaitTime += waitResult.Data;
} }
return new CallResult<int>(totalWaitTime, null); return new CallResult<int>(totalWaitTime);
} }
private static async Task<CallResult<int>> ProcessTopic(Log log, Limiter historyTopic, string endpoint, int requestWeight, RateLimitingBehaviour limitBehaviour, CancellationToken ct) private static async Task<CallResult<int>> ProcessTopic(Log log, Limiter historyTopic, string endpoint, int requestWeight, RateLimitingBehaviour limitBehaviour, CancellationToken ct)
@ -221,7 +221,7 @@ namespace CryptoExchange.Net.Objects
} }
catch (OperationCanceledException) catch (OperationCanceledException)
{ {
return new CallResult<int>(0, new CancellationRequestedError()); return new CallResult<int>(new CancellationRequestedError());
} }
sw.Stop(); sw.Stop();
@ -253,7 +253,7 @@ namespace CryptoExchange.Net.Objects
historyTopic.Semaphore.Release(); historyTopic.Semaphore.Release();
var msg = $"Request to {endpoint} failed because of rate limit `{historyTopic}`. Current weight: {currentWeight}/{historyTopic.Limit}, request weight: {requestWeight}"; var msg = $"Request to {endpoint} failed because of rate limit `{historyTopic}`. Current weight: {currentWeight}/{historyTopic.Limit}, request weight: {requestWeight}";
log.Write(LogLevel.Warning, msg); log.Write(LogLevel.Warning, msg);
return new CallResult<int>(thisWaitTime, new RateLimitError(msg)); return new CallResult<int>(new RateLimitError(msg));
} }
log.Write(LogLevel.Information, $"Request to {endpoint} waiting {thisWaitTime}ms for rate limit `{historyTopic}`. Current weight: {currentWeight}/{historyTopic.Limit}, request weight: {requestWeight}"); log.Write(LogLevel.Information, $"Request to {endpoint} waiting {thisWaitTime}ms for rate limit `{historyTopic}`. Current weight: {currentWeight}/{historyTopic.Limit}, request weight: {requestWeight}");
@ -263,7 +263,7 @@ namespace CryptoExchange.Net.Objects
} }
catch (OperationCanceledException) catch (OperationCanceledException)
{ {
return new CallResult<int>(0, new CancellationRequestedError()); return new CallResult<int>(new CancellationRequestedError());
} }
totalWaitTime += thisWaitTime; totalWaitTime += thisWaitTime;
} }
@ -277,7 +277,7 @@ namespace CryptoExchange.Net.Objects
var newTime = DateTime.UtcNow; var newTime = DateTime.UtcNow;
historyTopic.Entries.Add(new LimitEntry(newTime, requestWeight)); historyTopic.Entries.Add(new LimitEntry(newTime, requestWeight));
historyTopic.Semaphore.Release(); historyTopic.Semaphore.Release();
return new CallResult<int>(totalWaitTime, null); return new CallResult<int>(totalWaitTime);
} }
internal struct LimitEntry internal struct LimitEntry

View File

@ -233,7 +233,7 @@ namespace CryptoExchange.Net.OrderBook
if (!startResult) if (!startResult)
{ {
Status = OrderBookStatus.Disconnected; Status = OrderBookStatus.Disconnected;
return new CallResult<bool>(false, startResult.Error); return new CallResult<bool>(startResult.Error!);
} }
_subscription = startResult.Data; _subscription = startResult.Data;
@ -252,7 +252,7 @@ namespace CryptoExchange.Net.OrderBook
_subscription.ConnectionRestored += async time => await ResyncAsync().ConfigureAwait(false); _subscription.ConnectionRestored += async time => await ResyncAsync().ConfigureAwait(false);
Status = OrderBookStatus.Synced; Status = OrderBookStatus.Synced;
return new CallResult<bool>(true, null); return new CallResult<bool>(true);
} }
/// <inheritdoc/> /// <inheritdoc/>
@ -273,7 +273,7 @@ namespace CryptoExchange.Net.OrderBook
public CallResult<decimal> CalculateAverageFillPrice(decimal quantity, OrderBookEntryType type) public CallResult<decimal> CalculateAverageFillPrice(decimal quantity, OrderBookEntryType type)
{ {
if (Status != OrderBookStatus.Synced) if (Status != OrderBookStatus.Synced)
return new CallResult<decimal>(0, new InvalidOperationError($"{nameof(CalculateAverageFillPrice)} is not available when book is not in Synced state")); return new CallResult<decimal>(new InvalidOperationError($"{nameof(CalculateAverageFillPrice)} is not available when book is not in Synced state"));
var totalCost = 0m; var totalCost = 0m;
var totalAmount = 0m; var totalAmount = 0m;
@ -286,7 +286,7 @@ namespace CryptoExchange.Net.OrderBook
while (amountLeft > 0) while (amountLeft > 0)
{ {
if (step == list.Count) if (step == list.Count)
return new CallResult<decimal>(0, new InvalidOperationError("Quantity is larger than order in the order book")); return new CallResult<decimal>(new InvalidOperationError("Quantity is larger than order in the order book"));
var element = list.ElementAt(step); var element = list.ElementAt(step);
var stepAmount = Math.Min(element.Value.Quantity, amountLeft); var stepAmount = Math.Min(element.Value.Quantity, amountLeft);
@ -297,7 +297,7 @@ namespace CryptoExchange.Net.OrderBook
} }
} }
return new CallResult<decimal>(Math.Round(totalCost / totalAmount, 8), null); return new CallResult<decimal>(Math.Round(totalCost / totalAmount, 8));
} }
/// <summary> /// <summary>
@ -466,12 +466,12 @@ namespace CryptoExchange.Net.OrderBook
while (!bookSet && Status == OrderBookStatus.Syncing) while (!bookSet && Status == OrderBookStatus.Syncing)
{ {
if ((DateTime.UtcNow - startWait).TotalMilliseconds > timeout) if ((DateTime.UtcNow - startWait).TotalMilliseconds > timeout)
return new CallResult<bool>(false, new ServerError("Timeout while waiting for data")); return new CallResult<bool>(new ServerError("Timeout while waiting for data"));
await Task.Delay(10).ConfigureAwait(false); await Task.Delay(10).ConfigureAwait(false);
} }
return new CallResult<bool>(true, null); return new CallResult<bool>(true);
} }
/// <summary> /// <summary>

View File

@ -530,7 +530,7 @@ namespace CryptoExchange.Net.Sockets
internal async Task<CallResult<bool>> ResubscribeAsync(SocketSubscription socketSubscription) internal async Task<CallResult<bool>> ResubscribeAsync(SocketSubscription socketSubscription)
{ {
if (!Socket.IsOpen) if (!Socket.IsOpen)
return new CallResult<bool>(false, new UnknownError("Socket is not connected")); return new CallResult<bool>(new UnknownError("Socket is not connected"));
return await socketClient.SubscribeAndWaitAsync(this, socketSubscription.Request!, socketSubscription).ConfigureAwait(false); return await socketClient.SubscribeAndWaitAsync(this, socketSubscription.Request!, socketSubscription).ConfigureAwait(false);
} }