diff --git a/CryptoExchange.Net.UnitTests/SystemTextJsonConverterTests.cs b/CryptoExchange.Net.UnitTests/SystemTextJsonConverterTests.cs index 4eec97f..339be78 100644 --- a/CryptoExchange.Net.UnitTests/SystemTextJsonConverterTests.cs +++ b/CryptoExchange.Net.UnitTests/SystemTextJsonConverterTests.cs @@ -155,8 +155,8 @@ namespace CryptoExchange.Net.UnitTests [TestCase("three", TestEnum.Three)] [TestCase("Four", TestEnum.Four)] [TestCase("four", TestEnum.Four)] - [TestCase("Four1", (TestEnum)(-1))] - [TestCase(null, (TestEnum)(-1))] + [TestCase("Four1", (TestEnum)(-9))] + [TestCase(null, (TestEnum)(-9))] public void TestEnumConverterNotNullableDeserializeTests(string value, TestEnum expected) { var val = value == null ? "null" : $"\"{value}\""; @@ -164,6 +164,13 @@ namespace CryptoExchange.Net.UnitTests Assert.That(output.Value == expected); } + [Test] + public void TestEnumConverterMapsUndefinedValueCorrectlyIfDefaultIsDefined() + { + var output = JsonSerializer.Deserialize($"\"TestUndefined\""); + Assert.That((int)output == -99); + } + [TestCase("1", TestEnum.One)] [TestCase("2", TestEnum.Two)] [TestCase("3", TestEnum.Three)] @@ -425,6 +432,20 @@ namespace CryptoExchange.Net.UnitTests Four } + [JsonConverter(typeof(EnumConverter))] + public enum TestEnum2 + { + [Map("-9")] + Minus9 = -9, + [Map("1")] + One, + [Map("2")] + Two, + [Map("three", "3")] + Three, + Four + } + [JsonSerializable(typeof(Test))] [JsonSerializable(typeof(Test2))] [JsonSerializable(typeof(Test3))] diff --git a/CryptoExchange.Net/Authentication/AuthenticationProvider.cs b/CryptoExchange.Net/Authentication/AuthenticationProvider.cs index 059aabb..2023535 100644 --- a/CryptoExchange.Net/Authentication/AuthenticationProvider.cs +++ b/CryptoExchange.Net/Authentication/AuthenticationProvider.cs @@ -487,10 +487,12 @@ namespace CryptoExchange.Net.Authentication /// /// ctor /// - protected AuthenticationProvider(TApiCredentials credentials, TCredentialType? credential) : base(credentials) + protected AuthenticationProvider( + TApiCredentials credentials, + TCredentialType? credential) : base(credentials) { if (credential == null) - throw new ArgumentException("Missing required credentials"); + throw new ArgumentException($"Missing \"{typeof(TCredentialType).Name}\" credentials on \"{credentials.GetType().Name}\""); Credential = credential; } diff --git a/CryptoExchange.Net/Authentication/CredentialSet.cs b/CryptoExchange.Net/Authentication/CredentialSet.cs index 7c042a8..7ce56ed 100644 --- a/CryptoExchange.Net/Authentication/CredentialSet.cs +++ b/CryptoExchange.Net/Authentication/CredentialSet.cs @@ -35,7 +35,7 @@ namespace CryptoExchange.Net.Authentication public override void Validate() { 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(); 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(); 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(); 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(); 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 } /// - public override ApiCredentials Copy() => new RSAPemCredential(Key, PrivateKey); - - /// - public override void Validate() - { - base.Validate(); - if (string.IsNullOrEmpty(PrivateKey)) - throw new ArgumentException("PrivateKey unset", nameof(PrivateKey)); - } + public override ApiCredentials Copy() => new RSAPemCredential(Key, PrivateKey); } /// @@ -451,7 +443,7 @@ namespace CryptoExchange.Net.Authentication { base.Validate(); 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(); if (string.IsNullOrEmpty(Pass)) - throw new ArgumentException("Pass unset", nameof(Pass)); + throw new ArgumentException($"Pass not set on {GetType().Name}", nameof(Pass)); } } #endif @@ -531,7 +523,7 @@ namespace CryptoExchange.Net.Authentication { base.Validate(); 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(); if (string.IsNullOrEmpty(Pass)) - throw new ArgumentException("Pass unset", nameof(Pass)); + throw new ArgumentException($"Pass not set on {GetType().Name}", nameof(Pass)); } } } diff --git a/CryptoExchange.Net/Authentication/Signing/CeEip712TypedDataEncoder.cs b/CryptoExchange.Net/Authentication/Signing/CeEip712TypedDataEncoder.cs index 086c6b6..c9df3c9 100644 --- a/CryptoExchange.Net/Authentication/Signing/CeEip712TypedDataEncoder.cs +++ b/CryptoExchange.Net/Authentication/Signing/CeEip712TypedDataEncoder.cs @@ -152,7 +152,7 @@ namespace CryptoExchange.Net.Authentication.Signing if (memberValue.Value is string v) { 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)); } @@ -186,19 +186,11 @@ namespace CryptoExchange.Net.Authentication.Signing } else { - throw new Exception(); + throw new Exception("Unknown number value"); } } 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)); writer.Write(CeAbiEncoder.AbiValueEncodeBytes(length, (byte[])memberValue.Value)); } diff --git a/CryptoExchange.Net/Clients/CryptoBaseClient.cs b/CryptoExchange.Net/Clients/CryptoBaseClient.cs deleted file mode 100644 index c781a66..0000000 --- a/CryptoExchange.Net/Clients/CryptoBaseClient.cs +++ /dev/null @@ -1,67 +0,0 @@ -using Microsoft.Extensions.DependencyInjection; -using System; -using System.Collections.Generic; - -namespace CryptoExchange.Net.Clients -{ - /// - /// Base crypto client - /// - public class CryptoBaseClient : IDisposable - { - private readonly Dictionary _serviceCache = new Dictionary(); - - /// - /// Service provider - /// - protected readonly IServiceProvider? _serviceProvider; - - /// - /// ctor - /// - public CryptoBaseClient() { } - - /// - /// ctor - /// - /// - public CryptoBaseClient(IServiceProvider serviceProvider) - { - _serviceProvider = serviceProvider; - _serviceCache = new Dictionary(); - } - - /// - /// Try get a client by type for the service collection - /// - /// - /// - public T TryGet(Func 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() - ?? 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; - } - - /// - /// Dispose - /// - public void Dispose() - { - _serviceCache.Clear(); - } - } -} diff --git a/CryptoExchange.Net/Clients/CryptoRestClient.cs b/CryptoExchange.Net/Clients/CryptoRestClient.cs deleted file mode 100644 index 1907661..0000000 --- a/CryptoExchange.Net/Clients/CryptoRestClient.cs +++ /dev/null @@ -1,24 +0,0 @@ -using CryptoExchange.Net.Interfaces.Clients; -using System; - -namespace CryptoExchange.Net.Clients -{ - /// - public class CryptoRestClient : CryptoBaseClient, ICryptoRestClient - { - /// - /// ctor - /// - public CryptoRestClient() - { - } - - /// - /// ctor - /// - /// - public CryptoRestClient(IServiceProvider serviceProvider) : base(serviceProvider) - { - } - } -} \ No newline at end of file diff --git a/CryptoExchange.Net/Clients/CryptoSocketClient.cs b/CryptoExchange.Net/Clients/CryptoSocketClient.cs deleted file mode 100644 index 4e41c63..0000000 --- a/CryptoExchange.Net/Clients/CryptoSocketClient.cs +++ /dev/null @@ -1,24 +0,0 @@ -using CryptoExchange.Net.Interfaces.Clients; -using System; - -namespace CryptoExchange.Net.Clients -{ - /// - public class CryptoSocketClient : CryptoBaseClient, ICryptoSocketClient - { - /// - /// ctor - /// - public CryptoSocketClient() - { - } - - /// - /// ctor - /// - /// - public CryptoSocketClient(IServiceProvider serviceProvider) : base(serviceProvider) - { - } - } -} diff --git a/CryptoExchange.Net/Converters/SystemTextJson/EnumConverter.cs b/CryptoExchange.Net/Converters/SystemTextJson/EnumConverter.cs index e03e17b..2c64f34 100644 --- a/CryptoExchange.Net/Converters/SystemTextJson/EnumConverter.cs +++ b/CryptoExchange.Net/Converters/SystemTextJson/EnumConverter.cs @@ -88,6 +88,7 @@ namespace CryptoExchange.Net.Converters.SystemTextJson #endif private NullableEnumConverter? _nullableEnumConverter = null; + private static T? _undefinedEnumValue; private static ConcurrentBag _unknownValuesWarned = new ConcurrentBag(); internal class NullableEnumConverter : JsonConverter @@ -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"); } - 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) diff --git a/CryptoExchange.Net/Converters/SystemTextJson/SharedQuantityConverter.cs b/CryptoExchange.Net/Converters/SystemTextJson/SharedQuantityConverter.cs index 6d838f9..8e2113a 100644 --- a/CryptoExchange.Net/Converters/SystemTextJson/SharedQuantityConverter.cs +++ b/CryptoExchange.Net/Converters/SystemTextJson/SharedQuantityConverter.cs @@ -13,7 +13,7 @@ namespace CryptoExchange.Net.Converters.SystemTextJson public override T? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { if (reader.TokenType != JsonTokenType.StartArray) - throw new Exception(""); + throw new Exception("Invalid JSON structure"); reader.Read(); // Start array var baseQuantity = reader.TokenType == JsonTokenType.Null ? (decimal?)null : reader.GetDecimal(); @@ -24,7 +24,7 @@ namespace CryptoExchange.Net.Converters.SystemTextJson reader.Read(); if (reader.TokenType != JsonTokenType.EndArray) - throw new Exception(""); + throw new Exception("Invalid JSON structure"); reader.Read(); // End array diff --git a/CryptoExchange.Net/Converters/SystemTextJson/SharedSymbolConverter.cs b/CryptoExchange.Net/Converters/SystemTextJson/SharedSymbolConverter.cs index ac2238d..85602d7 100644 --- a/CryptoExchange.Net/Converters/SystemTextJson/SharedSymbolConverter.cs +++ b/CryptoExchange.Net/Converters/SystemTextJson/SharedSymbolConverter.cs @@ -10,7 +10,7 @@ namespace CryptoExchange.Net.Converters.SystemTextJson public override SharedSymbol? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { if (reader.TokenType != JsonTokenType.StartArray) - throw new Exception(""); + throw new Exception("Invalid JSON structure"); reader.Read(); // Start array var tradingMode = (TradingMode)Enum.Parse(typeof(TradingMode), reader.GetString()!); @@ -24,7 +24,7 @@ namespace CryptoExchange.Net.Converters.SystemTextJson reader.Read(); if (reader.TokenType != JsonTokenType.EndArray) - throw new Exception(""); + throw new Exception("Invalid JSON structure"); reader.Read(); // End array diff --git a/CryptoExchange.Net/Interfaces/Clients/ICryptoRestClient.cs b/CryptoExchange.Net/Interfaces/Clients/ICryptoRestClient.cs deleted file mode 100644 index 249c8fc..0000000 --- a/CryptoExchange.Net/Interfaces/Clients/ICryptoRestClient.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System; - -namespace CryptoExchange.Net.Interfaces.Clients -{ - /// - /// Client for accessing REST API's for different exchanges - /// - public interface ICryptoRestClient - { - /// - /// Try get - /// - /// - /// - T TryGet(Func createFunc); - } -} \ No newline at end of file diff --git a/CryptoExchange.Net/Interfaces/Clients/ICryptoSocketClient.cs b/CryptoExchange.Net/Interfaces/Clients/ICryptoSocketClient.cs deleted file mode 100644 index 69cb54d..0000000 --- a/CryptoExchange.Net/Interfaces/Clients/ICryptoSocketClient.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System; - -namespace CryptoExchange.Net.Interfaces.Clients -{ - /// - /// Client for accessing Websocket API's for different exchanges - /// - public interface ICryptoSocketClient - { - /// - /// Try get a client by type for the service collection - /// - /// - /// - T TryGet(Func createFunc); - } -} diff --git a/CryptoExchange.Net/Objects/Error.cs b/CryptoExchange.Net/Objects/Error.cs index 520f2de..4084b96 100644 --- a/CryptoExchange.Net/Objects/Error.cs +++ b/CryptoExchange.Net/Objects/Error.cs @@ -130,7 +130,8 @@ namespace CryptoExchange.Net.Objects /// /// Default error info /// - 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"); /// /// ctor diff --git a/CryptoExchange.Net/Testing/RestIntegrationTest.cs b/CryptoExchange.Net/Testing/RestIntegrationTest.cs index 69a2db1..2781124 100644 --- a/CryptoExchange.Net/Testing/RestIntegrationTest.cs +++ b/CryptoExchange.Net/Testing/RestIntegrationTest.cs @@ -1,7 +1,9 @@ using CryptoExchange.Net.Interfaces; using CryptoExchange.Net.Objects; +using CryptoExchange.Net.Testing.Comparers; using Microsoft.Extensions.Logging; using System; +using System.Collections.Generic; using System.Diagnostics; using System.Linq.Expressions; using System.Threading.Tasks; @@ -59,7 +61,17 @@ namespace CryptoExchange.Net.Testing /// Type of response /// The call expression /// Whether this is an authenticated request - public async Task RunAndCheckResult(Expression>>> expression, bool authRequest) + /// Whether to check the response for missing fields + /// Nested property to use for comparing when checking for missing fields + /// Properties to ignore when checking for missing fields + /// Whether to use the single array item as compare when checking for missing fields + public async Task RunAndCheckResult( + Expression>>> expression, + bool authRequest, + bool checkMissingFields = false, + string? compareNestedProperty = null, + List? ignoreProperties = null, + bool? useSingleArrayItem = null) { if (!ShouldRun()) return; @@ -93,6 +105,22 @@ namespace CryptoExchange.Net.Testing if (!result.Success) 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}"); }