1
0
mirror of https://github.com/JKorf/CryptoExchange.Net.git synced 2026-08-13 17:33:02 +00:00

Compare commits

...

7 Commits

Author SHA1 Message Date
JKorf 4f2d7abc7e Updated version 2024-01-04 21:32:27 +01:00
JKorf ef9de5e338 Fixed parsing of string datetime value "0.00000" 2024-01-04 21:31:51 +01:00
JKorf 8b513e51b9 Updated version 2023-12-02 15:17:34 +01:00
JKorf d43b38a23a Fix requestBodyFormat parameter usage 2023-12-02 15:15:38 +01:00
JKorf 0987c0f9d1 Updated version 2023-12-02 14:22:12 +01:00
JKorf e2dde77023 Added DecimalStringWriter json converter, added support for specifying body content type on a per request basis 2023-12-02 14:20:41 +01:00
JKorf 104ac7caad Updated response logging, added RequestId to WebCallResult 2023-11-30 14:19:58 +01:00
8 changed files with 113 additions and 39 deletions
@@ -115,6 +115,7 @@ namespace CryptoExchange.Net.UnitTests
TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(1),
null, null,
"{}", "{}",
1,
"https://test.com/api", "https://test.com/api",
null, null,
HttpMethod.Get, HttpMethod.Get,
@@ -143,6 +144,7 @@ namespace CryptoExchange.Net.UnitTests
TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(1),
null, null,
"{}", "{}",
1,
"https://test.com/api", "https://test.com/api",
null, null,
HttpMethod.Get, HttpMethod.Get,
+40 -27
View File
@@ -83,6 +83,7 @@ namespace CryptoExchange.Net
/// <param name="cancellationToken">Cancellation token</param> /// <param name="cancellationToken">Cancellation token</param>
/// <param name="parameters">The parameters of the request</param> /// <param name="parameters">The parameters of the request</param>
/// <param name="signed">Whether or not the request should be authenticated</param> /// <param name="signed">Whether or not the request should be authenticated</param>
/// <param name="requestBodyFormat">The format of the body content</param>
/// <param name="parameterPosition">Where the parameters should be placed, overwrites the value set in the client</param> /// <param name="parameterPosition">Where the parameters should be placed, overwrites the value set in the client</param>
/// <param name="arraySerialization">How array parameters should be serialized, overwrites the value set in the client</param> /// <param name="arraySerialization">How array parameters should be serialized, overwrites the value set in the client</param>
/// <param name="requestWeight">Credits used for the request</param> /// <param name="requestWeight">Credits used for the request</param>
@@ -97,6 +98,7 @@ namespace CryptoExchange.Net
CancellationToken cancellationToken, CancellationToken cancellationToken,
Dictionary<string, object>? parameters = null, Dictionary<string, object>? parameters = null,
bool signed = false, bool signed = false,
RequestBodyFormat? requestBodyFormat = null,
HttpMethodParameterPosition? parameterPosition = null, HttpMethodParameterPosition? parameterPosition = null,
ArrayParametersSerialization? arraySerialization = null, ArrayParametersSerialization? arraySerialization = null,
int requestWeight = 1, int requestWeight = 1,
@@ -108,11 +110,16 @@ namespace CryptoExchange.Net
while (true) while (true)
{ {
currentTry++; currentTry++;
var request = await PrepareRequestAsync(uri, method, cancellationToken, parameters, signed, parameterPosition, arraySerialization, requestWeight, deserializer, additionalHeaders, ignoreRatelimit).ConfigureAwait(false); var request = await PrepareRequestAsync(uri, method, cancellationToken, parameters, signed, requestBodyFormat, parameterPosition, arraySerialization, requestWeight, deserializer, additionalHeaders, ignoreRatelimit).ConfigureAwait(false);
if (!request) if (!request)
return new WebCallResult(request.Error!); return new WebCallResult(request.Error!);
var result = await GetResponseAsync<object>(request.Data, deserializer, cancellationToken, true).ConfigureAwait(false); var result = await GetResponseAsync<object>(request.Data, deserializer, cancellationToken, true).ConfigureAwait(false);
if (!result)
_logger.Log(LogLevel.Warning, $"[{result.RequestId}] Error received in {result.ResponseTime!.Value.TotalMilliseconds}ms: {result.Error}");
else
_logger.Log(LogLevel.Debug, $"[{result.RequestId}] Response received in {result.ResponseTime!.Value.TotalMilliseconds}ms{(OutputOriginalData ? (": " + result.OriginalData) : "")}");
if (await ShouldRetryRequestAsync(result, currentTry).ConfigureAwait(false)) if (await ShouldRetryRequestAsync(result, currentTry).ConfigureAwait(false))
continue; continue;
@@ -129,6 +136,7 @@ namespace CryptoExchange.Net
/// <param name="cancellationToken">Cancellation token</param> /// <param name="cancellationToken">Cancellation token</param>
/// <param name="parameters">The parameters of the request</param> /// <param name="parameters">The parameters of the request</param>
/// <param name="signed">Whether or not the request should be authenticated</param> /// <param name="signed">Whether or not the request should be authenticated</param>
/// <param name="requestBodyFormat">The format of the body content</param>
/// <param name="parameterPosition">Where the parameters should be placed, overwrites the value set in the client</param> /// <param name="parameterPosition">Where the parameters should be placed, overwrites the value set in the client</param>
/// <param name="arraySerialization">How array parameters should be serialized, overwrites the value set in the client</param> /// <param name="arraySerialization">How array parameters should be serialized, overwrites the value set in the client</param>
/// <param name="requestWeight">Credits used for the request</param> /// <param name="requestWeight">Credits used for the request</param>
@@ -143,6 +151,7 @@ namespace CryptoExchange.Net
CancellationToken cancellationToken, CancellationToken cancellationToken,
Dictionary<string, object>? parameters = null, Dictionary<string, object>? parameters = null,
bool signed = false, bool signed = false,
RequestBodyFormat? requestBodyFormat = null,
HttpMethodParameterPosition? parameterPosition = null, HttpMethodParameterPosition? parameterPosition = null,
ArrayParametersSerialization? arraySerialization = null, ArrayParametersSerialization? arraySerialization = null,
int requestWeight = 1, int requestWeight = 1,
@@ -155,11 +164,16 @@ namespace CryptoExchange.Net
while (true) while (true)
{ {
currentTry++; currentTry++;
var request = await PrepareRequestAsync(uri, method, cancellationToken, parameters, signed, parameterPosition, arraySerialization, requestWeight, deserializer, additionalHeaders, ignoreRatelimit).ConfigureAwait(false); var request = await PrepareRequestAsync(uri, method, cancellationToken, parameters, signed, requestBodyFormat, parameterPosition, arraySerialization, requestWeight, deserializer, additionalHeaders, ignoreRatelimit).ConfigureAwait(false);
if (!request) if (!request)
return new WebCallResult<T>(request.Error!); return new WebCallResult<T>(request.Error!);
var result = await GetResponseAsync<T>(request.Data, deserializer, cancellationToken, false).ConfigureAwait(false); var result = await GetResponseAsync<T>(request.Data, deserializer, cancellationToken, false).ConfigureAwait(false);
if (!result)
_logger.Log(LogLevel.Warning, $"[{result.RequestId}] Error received in {result.ResponseTime!.Value.TotalMilliseconds}ms: {result.Error}");
else
_logger.Log(LogLevel.Debug, $"[{result.RequestId}] Response received in {result.ResponseTime!.Value.TotalMilliseconds}ms{(OutputOriginalData ? (": " + result.OriginalData) : "")}");
if (await ShouldRetryRequestAsync(result, currentTry).ConfigureAwait(false)) if (await ShouldRetryRequestAsync(result, currentTry).ConfigureAwait(false))
continue; continue;
@@ -175,6 +189,7 @@ namespace CryptoExchange.Net
/// <param name="cancellationToken">Cancellation token</param> /// <param name="cancellationToken">Cancellation token</param>
/// <param name="parameters">The parameters of the request</param> /// <param name="parameters">The parameters of the request</param>
/// <param name="signed">Whether or not the request should be authenticated</param> /// <param name="signed">Whether or not the request should be authenticated</param>
/// <param name="requestBodyFormat">The format of the body content</param>
/// <param name="parameterPosition">Where the parameters should be placed, overwrites the value set in the client</param> /// <param name="parameterPosition">Where the parameters should be placed, overwrites the value set in the client</param>
/// <param name="arraySerialization">How array parameters should be serialized, overwrites the value set in the client</param> /// <param name="arraySerialization">How array parameters should be serialized, overwrites the value set in the client</param>
/// <param name="requestWeight">Credits used for the request</param> /// <param name="requestWeight">Credits used for the request</param>
@@ -188,6 +203,7 @@ namespace CryptoExchange.Net
CancellationToken cancellationToken, CancellationToken cancellationToken,
Dictionary<string, object>? parameters = null, Dictionary<string, object>? parameters = null,
bool signed = false, bool signed = false,
RequestBodyFormat? requestBodyFormat = null,
HttpMethodParameterPosition? parameterPosition = null, HttpMethodParameterPosition? parameterPosition = null,
ArrayParametersSerialization? arraySerialization = null, ArrayParametersSerialization? arraySerialization = null,
int requestWeight = 1, int requestWeight = 1,
@@ -232,7 +248,7 @@ namespace CryptoExchange.Net
_logger.Log(LogLevel.Information, $"[{requestId}] Creating request for " + uri); _logger.Log(LogLevel.Information, $"[{requestId}] Creating request for " + uri);
var paramsPosition = parameterPosition ?? ParameterPositions[method]; var paramsPosition = parameterPosition ?? ParameterPositions[method];
var request = ConstructRequest(uri, method, parameters?.OrderBy(p => p.Key).ToDictionary(p => p.Key, p => p.Value), signed, paramsPosition, arraySerialization ?? this.arraySerialization, requestId, additionalHeaders); var request = ConstructRequest(uri, method, parameters?.OrderBy(p => p.Key).ToDictionary(p => p.Key, p => p.Value), signed, paramsPosition, arraySerialization ?? this.arraySerialization, requestBodyFormat ?? this.requestBodyFormat, requestId, additionalHeaders);
string? paramString = ""; string? paramString = "";
if (paramsPosition == HttpMethodParameterPosition.InBody) if (paramsPosition == HttpMethodParameterPosition.InBody)
@@ -261,9 +277,9 @@ namespace CryptoExchange.Net
CancellationToken cancellationToken, CancellationToken cancellationToken,
bool expectedEmptyResponse) bool expectedEmptyResponse)
{ {
var sw = Stopwatch.StartNew();
try try
{ {
var sw = Stopwatch.StartNew();
var response = await request.GetResponseAsync(cancellationToken).ConfigureAwait(false); var response = await request.GetResponseAsync(cancellationToken).ConfigureAwait(false);
sw.Stop(); sw.Stop();
var statusCode = response.StatusCode; var statusCode = response.StatusCode;
@@ -281,23 +297,22 @@ namespace CryptoExchange.Net
responseLength ??= data.Length; responseLength ??= data.Length;
responseStream.Close(); responseStream.Close();
response.Close(); response.Close();
_logger.Log(LogLevel.Debug, $"[{request.RequestId}] Response received in {sw.ElapsedMilliseconds}ms{(OutputOriginalData ? (": " + data) : "")}");
if (!expectedEmptyResponse) if (!expectedEmptyResponse)
{ {
// 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 new WebCallResult<T>(response.StatusCode, response.ResponseHeaders, sw.Elapsed, responseLength, OutputOriginalData ? data : null, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), default, parseResult.Error!); return new WebCallResult<T>(response.StatusCode, response.ResponseHeaders, sw.Elapsed, responseLength, OutputOriginalData ? data : null, request.RequestId, 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 new WebCallResult<T>(response.StatusCode, response.ResponseHeaders, sw.Elapsed, responseLength, OutputOriginalData ? data : null, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), default, error!); return new WebCallResult<T>(response.StatusCode, response.ResponseHeaders, sw.Elapsed, responseLength, OutputOriginalData ? data : null, request.RequestId, 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, sw.Elapsed, responseLength, OutputOriginalData ? data : null, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), deserializeResult.Data, deserializeResult.Error); return new WebCallResult<T>(response.StatusCode, response.ResponseHeaders, sw.Elapsed, responseLength, OutputOriginalData ? data : null, request.RequestId, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), deserializeResult.Data, deserializeResult.Error);
} }
else else
{ {
@@ -306,16 +321,16 @@ namespace CryptoExchange.Net
var parseResult = ValidateJson(data); var parseResult = ValidateJson(data);
if (!parseResult.Success) if (!parseResult.Success)
// Not empty, and not json // Not empty, and not json
return new WebCallResult<T>(response.StatusCode, response.ResponseHeaders, sw.Elapsed, responseLength, OutputOriginalData ? data : null, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), default, parseResult.Error!); return new WebCallResult<T>(response.StatusCode, response.ResponseHeaders, sw.Elapsed, responseLength, OutputOriginalData ? data : null, request.RequestId, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), default, parseResult.Error!);
var error = await TryParseErrorAsync(parseResult.Data).ConfigureAwait(false); var error = await TryParseErrorAsync(parseResult.Data).ConfigureAwait(false);
if (error != null) if (error != null)
// Error response // Error response
return new WebCallResult<T>(response.StatusCode, response.ResponseHeaders, sw.Elapsed, responseLength, OutputOriginalData ? data : null, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), default, error!); return new WebCallResult<T>(response.StatusCode, response.ResponseHeaders, sw.Elapsed, responseLength, OutputOriginalData ? data : null, request.RequestId, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), default, error!);
} }
// Empty success response; okay // Empty success response; okay
return new WebCallResult<T>(response.StatusCode, response.ResponseHeaders, sw.Elapsed, responseLength, OutputOriginalData ? data : null, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), default, default); return new WebCallResult<T>(response.StatusCode, response.ResponseHeaders, sw.Elapsed, responseLength, OutputOriginalData ? data : null, request.RequestId, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), default, default);
} }
} }
else else
@@ -326,7 +341,7 @@ namespace CryptoExchange.Net
responseStream.Close(); responseStream.Close();
response.Close(); response.Close();
return new WebCallResult<T>(statusCode, headers, sw.Elapsed, 0, null, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), default, null); return new WebCallResult<T>(statusCode, headers, sw.Elapsed, 0, null, request.RequestId, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), default, null);
} }
// Success status code, and we don't have to check for errors. Continue deserializing directly from the stream // Success status code, and we don't have to check for errors. Continue deserializing directly from the stream
@@ -334,7 +349,7 @@ namespace CryptoExchange.Net
responseStream.Close(); responseStream.Close();
response.Close(); response.Close();
return new WebCallResult<T>(statusCode, headers, sw.Elapsed, responseLength, OutputOriginalData ? desResult.OriginalData : null, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), desResult.Data, desResult.Error); return new WebCallResult<T>(statusCode, headers, sw.Elapsed, responseLength, OutputOriginalData ? desResult.OriginalData : null, request.RequestId, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), desResult.Data, desResult.Error);
} }
} }
else else
@@ -342,7 +357,6 @@ namespace CryptoExchange.Net
// Http status code indicates error // Http status code indicates error
using var reader = new StreamReader(responseStream); using var reader = new StreamReader(responseStream);
var data = await reader.ReadToEndAsync().ConfigureAwait(false); var data = await reader.ReadToEndAsync().ConfigureAwait(false);
_logger.Log(LogLevel.Warning, $"[{request.RequestId}] Error received in {sw.ElapsedMilliseconds}ms: {data}");
responseStream.Close(); responseStream.Close();
response.Close(); response.Close();
@@ -354,29 +368,26 @@ namespace CryptoExchange.Net
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, sw.Elapsed, data.Length, data, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), default, error); return new WebCallResult<T>(statusCode, headers, sw.Elapsed, data.Length, data, request.RequestId, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), default, error);
} }
} }
catch (HttpRequestException requestException) catch (HttpRequestException requestException)
{ {
// Request exception, can't reach server for instance // Request exception, can't reach server for instance
var exceptionInfo = requestException.ToLogString(); var exceptionInfo = requestException.ToLogString();
_logger.Log(LogLevel.Warning, $"[{request.RequestId}] Request exception: " + exceptionInfo); return new WebCallResult<T>(null, null, sw.Elapsed, null, null, request.RequestId, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), default, new WebError(exceptionInfo));
return new WebCallResult<T>(null, null, null, null, null, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), default, new WebError(exceptionInfo));
} }
catch (OperationCanceledException canceledException) catch (OperationCanceledException canceledException)
{ {
if (cancellationToken != default && canceledException.CancellationToken == cancellationToken) if (cancellationToken != default && canceledException.CancellationToken == cancellationToken)
{ {
// Cancellation token canceled by caller // Cancellation token canceled by caller
_logger.Log(LogLevel.Warning, $"[{request.RequestId}] Request canceled by cancellation token"); return new WebCallResult<T>(null, null, sw.Elapsed, null, null, request.RequestId, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), default, new CancellationRequestedError());
return new WebCallResult<T>(null, null, null, null, null, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), default, new CancellationRequestedError());
} }
else else
{ {
// Request timed out // Request timed out
_logger.Log(LogLevel.Warning, $"[{request.RequestId}] Request timed out: " + canceledException.ToLogString()); return new WebCallResult<T>(null, null, sw.Elapsed, null, null, request.RequestId, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), default, new WebError($"Request timed out"));
return new WebCallResult<T>(null, null, null, null, null, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), default, new WebError($"[{request.RequestId}] Request timed out"));
} }
} }
} }
@@ -412,6 +423,7 @@ namespace CryptoExchange.Net
/// <param name="signed">Whether or not the request should be authenticated</param> /// <param name="signed">Whether or not the request should be authenticated</param>
/// <param name="parameterPosition">Where the parameters should be placed</param> /// <param name="parameterPosition">Where the parameters should be placed</param>
/// <param name="arraySerialization">How array parameters should be serialized</param> /// <param name="arraySerialization">How array parameters should be serialized</param>
/// <param name="bodyFormat">Format of the body content</param>
/// <param name="requestId">Unique id of a request</param> /// <param name="requestId">Unique id of a request</param>
/// <param name="additionalHeaders">Additional headers to send with the request</param> /// <param name="additionalHeaders">Additional headers to send with the request</param>
/// <returns></returns> /// <returns></returns>
@@ -422,6 +434,7 @@ namespace CryptoExchange.Net
bool signed, bool signed,
HttpMethodParameterPosition parameterPosition, HttpMethodParameterPosition parameterPosition,
ArrayParametersSerialization arraySerialization, ArrayParametersSerialization arraySerialization,
RequestBodyFormat bodyFormat,
int requestId, int requestId,
Dictionary<string, string>? additionalHeaders) Dictionary<string, string>? additionalHeaders)
{ {
@@ -502,7 +515,7 @@ namespace CryptoExchange.Net
if (parameterPosition == HttpMethodParameterPosition.InBody) if (parameterPosition == HttpMethodParameterPosition.InBody)
{ {
var contentType = requestBodyFormat == RequestBodyFormat.Json ? Constants.JsonContentHeader : Constants.FormContentHeader; var contentType = bodyFormat == RequestBodyFormat.Json ? Constants.JsonContentHeader : Constants.FormContentHeader;
if (bodyParameters.Any()) if (bodyParameters.Any())
WriteParamBody(request, bodyParameters, contentType); WriteParamBody(request, bodyParameters, contentType);
else else
@@ -520,13 +533,13 @@ namespace CryptoExchange.Net
/// <param name="contentType">The content type of the data</param> /// <param name="contentType">The content type of the data</param>
protected virtual void WriteParamBody(IRequest request, SortedDictionary<string, object> parameters, string contentType) protected virtual void WriteParamBody(IRequest request, SortedDictionary<string, object> parameters, string contentType)
{ {
if (requestBodyFormat == RequestBodyFormat.Json) if (contentType == Constants.JsonContentHeader)
{ {
// Write the parameters as json in the body // Write the parameters as json in the body
var stringData = JsonConvert.SerializeObject(parameters); var stringData = JsonConvert.SerializeObject(parameters);
request.SetContent(stringData, contentType); request.SetContent(stringData, contentType);
} }
else if (requestBodyFormat == RequestBodyFormat.FormData) else if (contentType == Constants.FormContentHeader)
{ {
// Write the parameters as form data in the body // Write the parameters as form data in the body
var stringData = parameters.ToFormData(); var stringData = parameters.ToFormData();
@@ -580,14 +593,14 @@ namespace CryptoExchange.Net
{ {
var timeSyncParams = GetTimeSyncInfo(); var timeSyncParams = GetTimeSyncInfo();
if (timeSyncParams == null) if (timeSyncParams == null)
return new WebCallResult<bool>(null, null, null, null, null, null, null, null, null, true, null); return new WebCallResult<bool>(null, null, null, null, null, null, null, null, null, null, true, null);
if (await timeSyncParams.TimeSyncState.Semaphore.WaitAsync(0).ConfigureAwait(false)) if (await timeSyncParams.TimeSyncState.Semaphore.WaitAsync(0).ConfigureAwait(false))
{ {
if (!timeSyncParams.SyncTime || (DateTime.UtcNow - timeSyncParams.TimeSyncState.LastSyncTime < timeSyncParams.RecalculationInterval)) if (!timeSyncParams.SyncTime || (DateTime.UtcNow - timeSyncParams.TimeSyncState.LastSyncTime < timeSyncParams.RecalculationInterval))
{ {
timeSyncParams.TimeSyncState.Semaphore.Release(); timeSyncParams.TimeSyncState.Semaphore.Release();
return new WebCallResult<bool>(null, null, null, null, null, null, null, null, null, true, null); return new WebCallResult<bool>(null, null, null, null, null, null, null, null, null, null, true, null);
} }
var localTime = DateTime.UtcNow; var localTime = DateTime.UtcNow;
@@ -616,7 +629,7 @@ namespace CryptoExchange.Net
timeSyncParams.TimeSyncState.Semaphore.Release(); timeSyncParams.TimeSyncState.Semaphore.Release();
} }
return new WebCallResult<bool>(null, null, null, null, null, null, null, null, null, true, null); return new WebCallResult<bool>(null, null, null, null, null, null, null, null, null, null, true, null);
} }
} }
} }
@@ -59,8 +59,12 @@ namespace CryptoExchange.Net.Converters
if (string.IsNullOrWhiteSpace(stringValue)) if (string.IsNullOrWhiteSpace(stringValue))
return null; return null;
if (string.IsNullOrWhiteSpace(stringValue) || stringValue == "0" || stringValue == "-1") if (string.IsNullOrWhiteSpace(stringValue)
|| stringValue == "-1"
|| (double.TryParse(stringValue, out var doubleValue) && doubleValue == 0))
{
return objectType == typeof(DateTime) ? default(DateTime) : null; return objectType == typeof(DateTime) ? default(DateTime) : null;
}
if (stringValue.Length == 8) if (stringValue.Length == 8)
{ {
@@ -0,0 +1,27 @@
using Newtonsoft.Json;
using System;
using System.Globalization;
namespace Kraken.Net.Converters
{
/// <summary>
/// Converter for serializing decimal values as string
/// </summary>
public class DecimalStringWriterConverter : JsonConverter
{
/// <inheritdoc />
public override bool CanRead => false;
/// <inheritdoc />
public override bool CanConvert(Type objectType) => objectType == typeof(decimal) || objectType == typeof(decimal?);
/// <inheritdoc />
public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer)
{
throw new NotImplementedException();
}
/// <inheritdoc />
public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer) => writer.WriteValue(((decimal?)value)?.ToString(CultureInfo.InvariantCulture) ?? null);
}
}
+4 -4
View File
@@ -6,16 +6,16 @@
<PackageId>CryptoExchange.Net</PackageId> <PackageId>CryptoExchange.Net</PackageId>
<Authors>JKorf</Authors> <Authors>JKorf</Authors>
<Description>A base package for implementing cryptocurrency API's</Description> <Description>A base package for implementing cryptocurrency API's</Description>
<PackageVersion>6.2.1</PackageVersion> <PackageVersion>6.2.4</PackageVersion>
<AssemblyVersion>6.2.1</AssemblyVersion> <AssemblyVersion>6.2.4</AssemblyVersion>
<FileVersion>6.2.1</FileVersion> <FileVersion>6.2.4</FileVersion>
<PackageRequireLicenseAcceptance>false</PackageRequireLicenseAcceptance> <PackageRequireLicenseAcceptance>false</PackageRequireLicenseAcceptance>
<RepositoryType>git</RepositoryType> <RepositoryType>git</RepositoryType>
<RepositoryUrl>https://github.com/JKorf/CryptoExchange.Net.git</RepositoryUrl> <RepositoryUrl>https://github.com/JKorf/CryptoExchange.Net.git</RepositoryUrl>
<PackageProjectUrl>https://github.com/JKorf/CryptoExchange.Net</PackageProjectUrl> <PackageProjectUrl>https://github.com/JKorf/CryptoExchange.Net</PackageProjectUrl>
<NeutralLanguage>en</NeutralLanguage> <NeutralLanguage>en</NeutralLanguage>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild> <GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<PackageReleaseNotes>6.2.1 - Utility methods</PackageReleaseNotes> <PackageReleaseNotes>6.2.4 - Fixed parsing of DateTime value of zero with additional zero decimal places</PackageReleaseNotes>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<LangVersion>10.0</LangVersion> <LangVersion>10.0</LangVersion>
<PackageLicenseExpression>MIT</PackageLicenseExpression> <PackageLicenseExpression>MIT</PackageLicenseExpression>
+22 -6
View File
@@ -186,6 +186,11 @@ namespace CryptoExchange.Net.Objects
/// </summary> /// </summary>
public IEnumerable<KeyValuePair<string, IEnumerable<string>>>? RequestHeaders { get; set; } public IEnumerable<KeyValuePair<string, IEnumerable<string>>>? RequestHeaders { get; set; }
/// <summary>
/// The request id
/// </summary>
public int? RequestId { get; set; }
/// <summary> /// <summary>
/// The url which was requested /// The url which was requested
/// </summary> /// </summary>
@@ -217,6 +222,7 @@ namespace CryptoExchange.Net.Objects
/// <param name="code"></param> /// <param name="code"></param>
/// <param name="responseHeaders"></param> /// <param name="responseHeaders"></param>
/// <param name="responseTime"></param> /// <param name="responseTime"></param>
/// <param name="requestId"></param>
/// <param name="requestUrl"></param> /// <param name="requestUrl"></param>
/// <param name="requestBody"></param> /// <param name="requestBody"></param>
/// <param name="requestMethod"></param> /// <param name="requestMethod"></param>
@@ -226,6 +232,7 @@ namespace CryptoExchange.Net.Objects
HttpStatusCode? code, HttpStatusCode? code,
IEnumerable<KeyValuePair<string, IEnumerable<string>>>? responseHeaders, IEnumerable<KeyValuePair<string, IEnumerable<string>>>? responseHeaders,
TimeSpan? responseTime, TimeSpan? responseTime,
int? requestId,
string? requestUrl, string? requestUrl,
string? requestBody, string? requestBody,
HttpMethod? requestMethod, HttpMethod? requestMethod,
@@ -235,6 +242,7 @@ namespace CryptoExchange.Net.Objects
ResponseStatusCode = code; ResponseStatusCode = code;
ResponseHeaders = responseHeaders; ResponseHeaders = responseHeaders;
ResponseTime = responseTime; ResponseTime = responseTime;
RequestId = requestId;
RequestUrl = requestUrl; RequestUrl = requestUrl;
RequestBody = requestBody; RequestBody = requestBody;
@@ -255,7 +263,7 @@ namespace CryptoExchange.Net.Objects
/// <returns></returns> /// <returns></returns>
public WebCallResult AsError(Error error) public WebCallResult AsError(Error error)
{ {
return new WebCallResult(ResponseStatusCode, ResponseHeaders, ResponseTime, RequestUrl, RequestBody, RequestMethod, RequestHeaders, error); return new WebCallResult(ResponseStatusCode, ResponseHeaders, ResponseTime, RequestId, RequestUrl, RequestBody, RequestMethod, RequestHeaders, error);
} }
/// <inheritdoc /> /// <inheritdoc />
@@ -281,6 +289,11 @@ namespace CryptoExchange.Net.Objects
/// </summary> /// </summary>
public IEnumerable<KeyValuePair<string, IEnumerable<string>>>? RequestHeaders { get; set; } public IEnumerable<KeyValuePair<string, IEnumerable<string>>>? RequestHeaders { get; set; }
/// <summary>
/// The request id
/// </summary>
public int? RequestId { get; set; }
/// <summary> /// <summary>
/// The url which was requested /// The url which was requested
/// </summary> /// </summary>
@@ -319,6 +332,7 @@ namespace CryptoExchange.Net.Objects
/// <param name="responseTime"></param> /// <param name="responseTime"></param>
/// <param name="responseLength"></param> /// <param name="responseLength"></param>
/// <param name="originalData"></param> /// <param name="originalData"></param>
/// <param name="requestId"></param>
/// <param name="requestUrl"></param> /// <param name="requestUrl"></param>
/// <param name="requestBody"></param> /// <param name="requestBody"></param>
/// <param name="requestMethod"></param> /// <param name="requestMethod"></param>
@@ -331,6 +345,7 @@ namespace CryptoExchange.Net.Objects
TimeSpan? responseTime, TimeSpan? responseTime,
long? responseLength, long? responseLength,
string? originalData, string? originalData,
int? requestId,
string? requestUrl, string? requestUrl,
string? requestBody, string? requestBody,
HttpMethod? requestMethod, HttpMethod? requestMethod,
@@ -343,6 +358,7 @@ namespace CryptoExchange.Net.Objects
ResponseTime = responseTime; ResponseTime = responseTime;
ResponseLength = responseLength; ResponseLength = responseLength;
RequestId = requestId;
RequestUrl = requestUrl; RequestUrl = requestUrl;
RequestBody = requestBody; RequestBody = requestBody;
RequestHeaders = requestHeaders; RequestHeaders = requestHeaders;
@@ -355,7 +371,7 @@ namespace CryptoExchange.Net.Objects
/// <returns></returns> /// <returns></returns>
public new WebCallResult AsDataless() public new WebCallResult AsDataless()
{ {
return new WebCallResult(ResponseStatusCode, ResponseHeaders, ResponseTime, RequestUrl, RequestBody, RequestMethod, RequestHeaders, Error); return new WebCallResult(ResponseStatusCode, ResponseHeaders, ResponseTime, RequestId, RequestUrl, RequestBody, RequestMethod, RequestHeaders, Error);
} }
/// <summary> /// <summary>
/// Copy as a dataless result /// Copy as a dataless result
@@ -363,14 +379,14 @@ namespace CryptoExchange.Net.Objects
/// <returns></returns> /// <returns></returns>
public new WebCallResult AsDatalessError(Error error) public new WebCallResult AsDatalessError(Error error)
{ {
return new WebCallResult(ResponseStatusCode, ResponseHeaders, ResponseTime, RequestUrl, RequestBody, RequestMethod, RequestHeaders, error); return new WebCallResult(ResponseStatusCode, ResponseHeaders, ResponseTime, RequestId, RequestUrl, RequestBody, RequestMethod, RequestHeaders, error);
} }
/// <summary> /// <summary>
/// Create a new error result /// Create a new error result
/// </summary> /// </summary>
/// <param name="error">The error</param> /// <param name="error">The error</param>
public WebCallResult(Error? error) : this(null, null, null, null, null, null, null, null, null, default, error) { } public WebCallResult(Error? error) : this(null, null, null, 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
@@ -380,7 +396,7 @@ 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, ResponseTime, ResponseLength, OriginalData, RequestUrl, RequestBody, RequestMethod, RequestHeaders, data, Error); return new WebCallResult<K>(ResponseStatusCode, ResponseHeaders, ResponseTime, ResponseLength, OriginalData, RequestId, RequestUrl, RequestBody, RequestMethod, RequestHeaders, data, Error);
} }
/// <summary> /// <summary>
@@ -391,7 +407,7 @@ namespace CryptoExchange.Net.Objects
/// <returns></returns> /// <returns></returns>
public new WebCallResult<K> AsError<K>(Error error) public new WebCallResult<K> AsError<K>(Error error)
{ {
return new WebCallResult<K>(ResponseStatusCode, ResponseHeaders, ResponseTime, ResponseLength, OriginalData, RequestUrl, RequestBody, RequestMethod, RequestHeaders, default, error); return new WebCallResult<K>(ResponseStatusCode, ResponseHeaders, ResponseTime, ResponseLength, OriginalData, RequestId, RequestUrl, RequestBody, RequestMethod, RequestHeaders, default, error);
} }
/// <inheritdoc /> /// <inheritdoc />
+1 -1
View File
@@ -41,7 +41,7 @@ namespace CryptoExchange.Net.Objects
/// <returns></returns> /// <returns></returns>
public override string ToString() public override string ToString()
{ {
return Code != null ? $"{Code}: {Message} {Data}" : $"{Message} {Data}"; return Code != null ? $"[{GetType().Name}] {Code}: {Message} {Data}" : $"[{GetType().Name}] {Message} {Data}";
} }
} }
+12
View File
@@ -31,6 +31,18 @@ Make a one time donation in a crypto currency of your choice. If you prefer to d
Alternatively, sponsor me on Github using [Github Sponsors](https://github.com/sponsors/JKorf). Alternatively, sponsor me on Github using [Github Sponsors](https://github.com/sponsors/JKorf).
## Release notes ## Release notes
* Version 6.2.4 - 04 Jan 2024
* Fixed parsing of DateTime value of zero with additional zero decimal places
* Version 6.2.3 - 02 Dec 2023
* Fixed requestBodyFormat parameter handling
* Version 6.2.2 - 02 Dec 2023
* Added support for specifying the request body content type on a per request basis
* Added DecimalStringWriterConverter
* Added RequestId to WebCallResult model
* Updated response logging
* Version 6.2.1 - 28 Oct 2023 * Version 6.2.1 - 28 Oct 2023
* Utility methods * Utility methods