1
0
mirror of https://github.com/JKorf/CryptoExchange.Net synced 2026-04-12 16:13:12 +00:00
This commit is contained in:
JKorf 2026-03-22 22:07:36 +01:00
parent ee30618566
commit ab356681fb
14 changed files with 92 additions and 188 deletions

View File

@ -155,8 +155,8 @@ namespace CryptoExchange.Net.UnitTests
[TestCase("three", TestEnum.Three)] [TestCase("three", TestEnum.Three)]
[TestCase("Four", TestEnum.Four)] [TestCase("Four", TestEnum.Four)]
[TestCase("four", TestEnum.Four)] [TestCase("four", TestEnum.Four)]
[TestCase("Four1", (TestEnum)(-1))] [TestCase("Four1", (TestEnum)(-9))]
[TestCase(null, (TestEnum)(-1))] [TestCase(null, (TestEnum)(-9))]
public void TestEnumConverterNotNullableDeserializeTests(string value, TestEnum expected) public void TestEnumConverterNotNullableDeserializeTests(string value, TestEnum expected)
{ {
var val = value == null ? "null" : $"\"{value}\""; var val = value == null ? "null" : $"\"{value}\"";
@ -164,6 +164,13 @@ namespace CryptoExchange.Net.UnitTests
Assert.That(output.Value == expected); Assert.That(output.Value == expected);
} }
[Test]
public void TestEnumConverterMapsUndefinedValueCorrectlyIfDefaultIsDefined()
{
var output = JsonSerializer.Deserialize<TestEnum2>($"\"TestUndefined\"");
Assert.That((int)output == -99);
}
[TestCase("1", TestEnum.One)] [TestCase("1", TestEnum.One)]
[TestCase("2", TestEnum.Two)] [TestCase("2", TestEnum.Two)]
[TestCase("3", TestEnum.Three)] [TestCase("3", TestEnum.Three)]
@ -425,6 +432,20 @@ namespace CryptoExchange.Net.UnitTests
Four Four
} }
[JsonConverter(typeof(EnumConverter<TestEnum2>))]
public enum TestEnum2
{
[Map("-9")]
Minus9 = -9,
[Map("1")]
One,
[Map("2")]
Two,
[Map("three", "3")]
Three,
Four
}
[JsonSerializable(typeof(Test))] [JsonSerializable(typeof(Test))]
[JsonSerializable(typeof(Test2))] [JsonSerializable(typeof(Test2))]
[JsonSerializable(typeof(Test3))] [JsonSerializable(typeof(Test3))]

View File

@ -487,10 +487,12 @@ namespace CryptoExchange.Net.Authentication
/// <summary> /// <summary>
/// ctor /// ctor
/// </summary> /// </summary>
protected AuthenticationProvider(TApiCredentials credentials, TCredentialType? credential) : base(credentials) protected AuthenticationProvider(
TApiCredentials credentials,
TCredentialType? credential) : base(credentials)
{ {
if (credential == null) if (credential == null)
throw new ArgumentException("Missing required credentials"); throw new ArgumentException($"Missing \"{typeof(TCredentialType).Name}\" credentials on \"{credentials.GetType().Name}\"");
Credential = credential; Credential = credential;
} }

View File

@ -35,7 +35,7 @@ namespace CryptoExchange.Net.Authentication
public override void Validate() public override void Validate()
{ {
if (string.IsNullOrEmpty(Key)) if (string.IsNullOrEmpty(Key))
throw new ArgumentException("Key unset", nameof(Key)); throw new ArgumentException($"Key not set on {GetType().Name}", nameof(Key));
} }
} }
@ -103,7 +103,7 @@ namespace CryptoExchange.Net.Authentication
{ {
base.Validate(); base.Validate();
if (string.IsNullOrEmpty(Secret)) if (string.IsNullOrEmpty(Secret))
throw new ArgumentException("Secret unset", nameof(Secret)); throw new ArgumentException($"Secret not set on {GetType().Name}", nameof(Secret));
} }
} }
@ -144,7 +144,7 @@ namespace CryptoExchange.Net.Authentication
{ {
base.Validate(); base.Validate();
if (string.IsNullOrEmpty(Pass)) if (string.IsNullOrEmpty(Pass))
throw new ArgumentException("Pass unset", nameof(Pass)); throw new ArgumentException($"Pass not set on {GetType().Name}", nameof(Pass));
} }
} }
@ -185,7 +185,7 @@ namespace CryptoExchange.Net.Authentication
{ {
base.Validate(); base.Validate();
if (string.IsNullOrEmpty(PrivateKey)) if (string.IsNullOrEmpty(PrivateKey))
throw new ArgumentException("PrivateKey unset", nameof(PrivateKey)); throw new ArgumentException($"PrivateKey not set on {GetType().Name}", nameof(PrivateKey));
} }
} }
@ -223,7 +223,7 @@ namespace CryptoExchange.Net.Authentication
{ {
base.Validate(); base.Validate();
if (string.IsNullOrEmpty(Pass)) if (string.IsNullOrEmpty(Pass))
throw new ArgumentException("Pass unset", nameof(Pass)); throw new ArgumentException($"PrivateKey not set on {GetType().Name}", nameof(PrivateKey));
} }
} }
@ -268,15 +268,7 @@ namespace CryptoExchange.Net.Authentication
} }
/// <inheritdoc /> /// <inheritdoc />
public override ApiCredentials Copy() => new RSAPemCredential(Key, PrivateKey); public override ApiCredentials Copy() => new RSAPemCredential(Key, PrivateKey);
/// <inheritdoc />
public override void Validate()
{
base.Validate();
if (string.IsNullOrEmpty(PrivateKey))
throw new ArgumentException("PrivateKey unset", nameof(PrivateKey));
}
} }
/// <summary> /// <summary>
@ -451,7 +443,7 @@ namespace CryptoExchange.Net.Authentication
{ {
base.Validate(); base.Validate();
if (string.IsNullOrEmpty(PrivateKey)) if (string.IsNullOrEmpty(PrivateKey))
throw new ArgumentException("PrivateKey unset", nameof(PrivateKey)); throw new ArgumentException($"PrivateKey not set on {GetType().Name}", nameof(PrivateKey));
} }
} }
@ -491,7 +483,7 @@ namespace CryptoExchange.Net.Authentication
{ {
base.Validate(); base.Validate();
if (string.IsNullOrEmpty(Pass)) if (string.IsNullOrEmpty(Pass))
throw new ArgumentException("Pass unset", nameof(Pass)); throw new ArgumentException($"Pass not set on {GetType().Name}", nameof(Pass));
} }
} }
#endif #endif
@ -531,7 +523,7 @@ namespace CryptoExchange.Net.Authentication
{ {
base.Validate(); base.Validate();
if (string.IsNullOrEmpty(PrivateKey)) if (string.IsNullOrEmpty(PrivateKey))
throw new ArgumentException("PrivateKey unset", nameof(PrivateKey)); throw new ArgumentException($"PrivateKey not set on {GetType().Name}", nameof(PrivateKey));
} }
} }
@ -571,7 +563,7 @@ namespace CryptoExchange.Net.Authentication
{ {
base.Validate(); base.Validate();
if (string.IsNullOrEmpty(Pass)) if (string.IsNullOrEmpty(Pass))
throw new ArgumentException("Pass unset", nameof(Pass)); throw new ArgumentException($"Pass not set on {GetType().Name}", nameof(Pass));
} }
} }
} }

View File

@ -152,7 +152,7 @@ namespace CryptoExchange.Net.Authentication.Signing
if (memberValue.Value is string v) if (memberValue.Value is string v)
{ {
if (!BigInteger.TryParse(v, out BigInteger parsedOutput)) if (!BigInteger.TryParse(v, out BigInteger parsedOutput))
throw new Exception(""); throw new Exception($"Failed to encode BigInteger string {v}");
writer.Write(CeAbiEncoder.AbiValueEncodeBigInteger(memberValue.TypeName[0] != 'u', parsedOutput)); writer.Write(CeAbiEncoder.AbiValueEncodeBigInteger(memberValue.TypeName[0] != 'u', parsedOutput));
} }
@ -186,19 +186,11 @@ namespace CryptoExchange.Net.Authentication.Signing
} }
else else
{ {
throw new Exception(); throw new Exception("Unknown number value");
} }
} }
else if (memberValue.TypeName.StartsWith("bytes")) else if (memberValue.TypeName.StartsWith("bytes"))
{ {
// Applicable?
//if (memberValue.Value is string v)
// writer.Write(AbiEncoder.AbiValueEncodeHexBytes(v));
//else if (memberValue.Value is byte[] b)
// writer.Write(AbiEncoder.AbiValueEncodeBytes(b));
//else
// throw new Exception("Unknown byte value type");
var length = memberValue.TypeName.Length == 5 ? 32 : int.Parse(memberValue.TypeName.Substring(5)); var length = memberValue.TypeName.Length == 5 ? 32 : int.Parse(memberValue.TypeName.Substring(5));
writer.Write(CeAbiEncoder.AbiValueEncodeBytes(length, (byte[])memberValue.Value)); writer.Write(CeAbiEncoder.AbiValueEncodeBytes(length, (byte[])memberValue.Value));
} }

View File

@ -1,67 +0,0 @@
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Collections.Generic;
namespace CryptoExchange.Net.Clients
{
/// <summary>
/// Base crypto client
/// </summary>
public class CryptoBaseClient : IDisposable
{
private readonly Dictionary<Type, object> _serviceCache = new Dictionary<Type, object>();
/// <summary>
/// Service provider
/// </summary>
protected readonly IServiceProvider? _serviceProvider;
/// <summary>
/// ctor
/// </summary>
public CryptoBaseClient() { }
/// <summary>
/// ctor
/// </summary>
/// <param name="serviceProvider"></param>
public CryptoBaseClient(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
_serviceCache = new Dictionary<Type, object>();
}
/// <summary>
/// Try get a client by type for the service collection
/// </summary>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
public T TryGet<T>(Func<T> createFunc)
{
var type = typeof(T);
if (_serviceCache.TryGetValue(type, out var value))
return (T)value;
if (_serviceProvider == null)
{
// Create with default options
var createResult = createFunc();
_serviceCache.Add(typeof(T), createResult!);
return createResult;
}
var result = _serviceProvider.GetService<T>()
?? throw new InvalidOperationException($"No service was found for {typeof(T).Name}, make sure the exchange is registered in dependency injection with the `services.Add[Exchange]()` method");
_serviceCache.Add(type, result!);
return result;
}
/// <summary>
/// Dispose
/// </summary>
public void Dispose()
{
_serviceCache.Clear();
}
}
}

View File

@ -1,24 +0,0 @@
using CryptoExchange.Net.Interfaces.Clients;
using System;
namespace CryptoExchange.Net.Clients
{
/// <inheritdoc />
public class CryptoRestClient : CryptoBaseClient, ICryptoRestClient
{
/// <summary>
/// ctor
/// </summary>
public CryptoRestClient()
{
}
/// <summary>
/// ctor
/// </summary>
/// <param name="serviceProvider"></param>
public CryptoRestClient(IServiceProvider serviceProvider) : base(serviceProvider)
{
}
}
}

View File

@ -1,24 +0,0 @@
using CryptoExchange.Net.Interfaces.Clients;
using System;
namespace CryptoExchange.Net.Clients
{
/// <inheritdoc />
public class CryptoSocketClient : CryptoBaseClient, ICryptoSocketClient
{
/// <summary>
/// ctor
/// </summary>
public CryptoSocketClient()
{
}
/// <summary>
/// ctor
/// </summary>
/// <param name="serviceProvider"></param>
public CryptoSocketClient(IServiceProvider serviceProvider) : base(serviceProvider)
{
}
}
}

View File

@ -88,6 +88,7 @@ namespace CryptoExchange.Net.Converters.SystemTextJson
#endif #endif
private NullableEnumConverter? _nullableEnumConverter = null; private NullableEnumConverter? _nullableEnumConverter = null;
private static T? _undefinedEnumValue;
private static ConcurrentBag<string> _unknownValuesWarned = new ConcurrentBag<string>(); private static ConcurrentBag<string> _unknownValuesWarned = new ConcurrentBag<string>();
internal class NullableEnumConverter : JsonConverter<T?> internal class NullableEnumConverter : JsonConverter<T?>
@ -129,7 +130,23 @@ namespace CryptoExchange.Net.Converters.SystemTextJson
LibraryHelpers.StaticLogger?.LogWarning($"Received null or empty enum value, but property type is not a nullable enum. EnumType: {typeof(T).FullName}. If you think {typeof(T).FullName} should be nullable please open an issue on the Github repo"); LibraryHelpers.StaticLogger?.LogWarning($"Received null or empty enum value, but property type is not a nullable enum. EnumType: {typeof(T).FullName}. If you think {typeof(T).FullName} should be nullable please open an issue on the Github repo");
} }
return (T)Enum.ToObject(typeof(T), -1); return GetUndefinedEnumValue();
}
private T GetUndefinedEnumValue()
{
if (_undefinedEnumValue != null)
return _undefinedEnumValue.Value;
var type = typeof(T);
if (!Enum.IsDefined(type, -9))
_undefinedEnumValue = (T)Enum.ToObject(type, -9);
else if (!Enum.IsDefined(type, -99))
_undefinedEnumValue = (T)Enum.ToObject(type, -99);
else
_undefinedEnumValue = (T)Enum.ToObject(type, -999);
return (T)_undefinedEnumValue;
} }
private T? ReadNullable(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options, out bool isEmptyString) private T? ReadNullable(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options, out bool isEmptyString)

View File

@ -13,7 +13,7 @@ namespace CryptoExchange.Net.Converters.SystemTextJson
public override T? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) public override T? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{ {
if (reader.TokenType != JsonTokenType.StartArray) if (reader.TokenType != JsonTokenType.StartArray)
throw new Exception(""); throw new Exception("Invalid JSON structure");
reader.Read(); // Start array reader.Read(); // Start array
var baseQuantity = reader.TokenType == JsonTokenType.Null ? (decimal?)null : reader.GetDecimal(); var baseQuantity = reader.TokenType == JsonTokenType.Null ? (decimal?)null : reader.GetDecimal();
@ -24,7 +24,7 @@ namespace CryptoExchange.Net.Converters.SystemTextJson
reader.Read(); reader.Read();
if (reader.TokenType != JsonTokenType.EndArray) if (reader.TokenType != JsonTokenType.EndArray)
throw new Exception(""); throw new Exception("Invalid JSON structure");
reader.Read(); // End array reader.Read(); // End array

View File

@ -10,7 +10,7 @@ namespace CryptoExchange.Net.Converters.SystemTextJson
public override SharedSymbol? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) public override SharedSymbol? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{ {
if (reader.TokenType != JsonTokenType.StartArray) if (reader.TokenType != JsonTokenType.StartArray)
throw new Exception(""); throw new Exception("Invalid JSON structure");
reader.Read(); // Start array reader.Read(); // Start array
var tradingMode = (TradingMode)Enum.Parse(typeof(TradingMode), reader.GetString()!); var tradingMode = (TradingMode)Enum.Parse(typeof(TradingMode), reader.GetString()!);
@ -24,7 +24,7 @@ namespace CryptoExchange.Net.Converters.SystemTextJson
reader.Read(); reader.Read();
if (reader.TokenType != JsonTokenType.EndArray) if (reader.TokenType != JsonTokenType.EndArray)
throw new Exception(""); throw new Exception("Invalid JSON structure");
reader.Read(); // End array reader.Read(); // End array

View File

@ -1,17 +0,0 @@
using System;
namespace CryptoExchange.Net.Interfaces.Clients
{
/// <summary>
/// Client for accessing REST API's for different exchanges
/// </summary>
public interface ICryptoRestClient
{
/// <summary>
/// Try get
/// </summary>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
T TryGet<T>(Func<T> createFunc);
}
}

View File

@ -1,17 +0,0 @@
using System;
namespace CryptoExchange.Net.Interfaces.Clients
{
/// <summary>
/// Client for accessing Websocket API's for different exchanges
/// </summary>
public interface ICryptoSocketClient
{
/// <summary>
/// Try get a client by type for the service collection
/// </summary>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
T TryGet<T>(Func<T> createFunc);
}
}

View File

@ -130,7 +130,8 @@ namespace CryptoExchange.Net.Objects
/// <summary> /// <summary>
/// Default error info /// Default error info
/// </summary> /// </summary>
protected static readonly ErrorInfo _errorInfo = new ErrorInfo(ErrorType.MissingCredentials, false, "No credentials provided for private endpoint"); protected static readonly ErrorInfo _errorInfo = new ErrorInfo(ErrorType.MissingCredentials, false,
"No credentials provided for private endpoint, set the `ApiCredentials` option in the client configuration");
/// <summary> /// <summary>
/// ctor /// ctor

View File

@ -1,7 +1,9 @@
using CryptoExchange.Net.Interfaces; using CryptoExchange.Net.Interfaces;
using CryptoExchange.Net.Objects; using CryptoExchange.Net.Objects;
using CryptoExchange.Net.Testing.Comparers;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using System; using System;
using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Linq.Expressions; using System.Linq.Expressions;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -59,7 +61,17 @@ namespace CryptoExchange.Net.Testing
/// <typeparam name="T">Type of response</typeparam> /// <typeparam name="T">Type of response</typeparam>
/// <param name="expression">The call expression</param> /// <param name="expression">The call expression</param>
/// <param name="authRequest">Whether this is an authenticated request</param> /// <param name="authRequest">Whether this is an authenticated request</param>
public async Task RunAndCheckResult<T>(Expression<Func<TClient, Task<WebCallResult<T>>>> expression, bool authRequest) /// <param name="checkMissingFields">Whether to check the response for missing fields</param>
/// <param name="compareNestedProperty">Nested property to use for comparing when checking for missing fields</param>
/// <param name="ignoreProperties">Properties to ignore when checking for missing fields</param>
/// <param name="useSingleArrayItem">Whether to use the single array item as compare when checking for missing fields</param>
public async Task RunAndCheckResult<T>(
Expression<Func<TClient, Task<WebCallResult<T>>>> expression,
bool authRequest,
bool checkMissingFields = false,
string? compareNestedProperty = null,
List<string>? ignoreProperties = null,
bool? useSingleArrayItem = null)
{ {
if (!ShouldRun()) if (!ShouldRun())
return; return;
@ -93,6 +105,22 @@ namespace CryptoExchange.Net.Testing
if (!result.Success) if (!result.Success)
throw new Exception($"Method {expressionBody.Method.Name} returned error: " + result.Error); throw new Exception($"Method {expressionBody.Method.Name} returned error: " + result.Error);
if (checkMissingFields)
{
var data = result.Data;
var originalData = result.OriginalData;
if (originalData == null)
throw new Exception($"Original data needs to be enabled in the client options to check for missing fields");
try {
SystemTextJsonComparer.CompareData(expressionBody.Method.Name, data, originalData, compareNestedProperty, ignoreProperties, useSingleArrayItem ?? false);
}
catch (Exception ex)
{
throw new Exception($"Compare failed: {ex.Message}; original data: {originalData}", ex);
}
}
Debug.WriteLine($"{expressionBody.Method.Name} {result}"); Debug.WriteLine($"{expressionBody.Method.Name} {result}");
} }