1
0
mirror of https://github.com/JKorf/CryptoExchange.Net synced 2025-06-09 17:06:19 +00:00

Comments, fix test

This commit is contained in:
Jkorf 2021-12-01 16:26:34 +01:00
parent 3c3b5639f5
commit 6d0120d564
13 changed files with 134 additions and 66 deletions

View File

@ -14,6 +14,14 @@ namespace CryptoExchange.Net.UnitTests
[TestFixture()]
public class OptionsTests
{
[TearDown]
public void Init()
{
TestClientOptions.Default = new TestClientOptions
{
};
}
[TestCase(null, null)]
[TestCase("", "")]
[TestCase("test", null)]

View File

@ -51,14 +51,14 @@ namespace CryptoExchange.Net.UnitTests
}
[TestCase]
public void ReceivingErrorCode_Should_ResultInError()
public async Task ReceivingErrorCode_Should_ResultInError()
{
// arrange
var client = new TestRestClient();
client.SetErrorWithoutResponse(System.Net.HttpStatusCode.BadRequest, "Invalid request");
// act
var result = client.Request<TestObject>().Result;
var result = await client.Request<TestObject>();
// assert
Assert.IsFalse(result.Success);
@ -66,14 +66,14 @@ namespace CryptoExchange.Net.UnitTests
}
[TestCase]
public void ReceivingErrorAndNotParsingError_Should_ResultInFlatError()
public async Task ReceivingErrorAndNotParsingError_Should_ResultInFlatError()
{
// arrange
var client = new TestRestClient();
client.SetErrorWithResponse("{\"errorMessage\": \"Invalid request\", \"errorCode\": 123}", System.Net.HttpStatusCode.BadRequest);
// act
var result = client.Request<TestObject>().Result;
var result = await client.Request<TestObject>();
// assert
Assert.IsFalse(result.Success);
@ -84,14 +84,14 @@ namespace CryptoExchange.Net.UnitTests
}
[TestCase]
public void ReceivingErrorAndParsingError_Should_ResultInParsedError()
public async Task ReceivingErrorAndParsingError_Should_ResultInParsedError()
{
// arrange
var client = new ParseErrorTestRestClient();
client.SetErrorWithResponse("{\"errorMessage\": \"Invalid request\", \"errorCode\": 123}", System.Net.HttpStatusCode.BadRequest);
// act
var result = client.Request<TestObject>().Result;
var result = await client.Request<TestObject>();
// assert
Assert.IsFalse(result.Success);

View File

@ -72,6 +72,7 @@ namespace CryptoExchange.Net.UnitTests.TestImplementations
typeof(HttpRequestException).GetField("_message", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance).SetValue(we, message);
var request = new Mock<IRequest>();
request.Setup(c => c.GetHeaders()).Returns(new Dictionary<string, IEnumerable<string>>());
request.Setup(c => c.GetResponseAsync(It.IsAny<CancellationToken>())).Throws(we);
var factory = Mock.Get(RequestFactory);

View File

@ -1,12 +1,21 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace CryptoExchange.Net.Attributes
{
/// <summary>
/// Map a enum entry to string values
/// </summary>
public class MapAttribute : Attribute
{
/// <summary>
/// Values mapping to the enum entry
/// </summary>
public string[] Values { get; set; }
/// <summary>
/// ctor
/// </summary>
/// <param name="maps"></param>
public MapAttribute(params string[] maps)
{
Values = maps;

View File

@ -19,12 +19,15 @@ using Newtonsoft.Json.Linq;
namespace CryptoExchange.Net
{
/// <summary>
/// Base rest client
/// Base API for all API clients
/// </summary>
public abstract class BaseApiClient: IDisposable
{
private ApiCredentials? _apiCredentials;
private AuthenticationProvider _authenticationProvider;
private readonly ApiCredentials? _apiCredentials;
private AuthenticationProvider? _authenticationProvider;
/// <summary>
/// The authentication provider for this API client. (null if no credentials are set)
/// </summary>
public AuthenticationProvider? AuthenticationProvider
{
get
@ -36,14 +39,27 @@ namespace CryptoExchange.Net
}
}
/// <summary>
/// The base address for this API client
/// </summary>
internal protected string BaseAddress { get; }
/// <summary>
/// ctor
/// </summary>
/// <param name="options">Client options</param>
/// <param name="apiOptions">Api client options</param>
public BaseApiClient(BaseClientOptions options, ApiClientOptions apiOptions)
{
_apiCredentials = apiOptions.ApiCredentials ?? options.ApiCredentials;
BaseAddress = apiOptions.BaseAddress;
}
/// <summary>
/// Create an AuthenticationProvider implementation instance based on the provided credentials
/// </summary>
/// <param name="credentials"></param>
/// <returns></returns>
public abstract AuthenticationProvider CreateAuthenticationProvider(ApiCredentials credentials);
/// <summary>

View File

@ -238,7 +238,7 @@ namespace CryptoExchange.Net
}
}
private async Task<string> ReadStreamAsync(Stream stream)
private static async Task<string> ReadStreamAsync(Stream stream)
{
using var reader = new StreamReader(stream, Encoding.UTF8, false, 512, true);
return await reader.ReadToEndAsync().ConfigureAwait(false);
@ -248,7 +248,7 @@ namespace CryptoExchange.Net
/// Generate a new unique id. The id is staticly stored so it is guarenteed to be unique across different client instances
/// </summary>
/// <returns></returns>
protected int NextId()
protected static int NextId()
{
lock (idLock)
{

View File

@ -8,7 +8,6 @@ using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using System.Web;
using CryptoExchange.Net.Authentication;
using CryptoExchange.Net.Interfaces;
using CryptoExchange.Net.Objects;
using CryptoExchange.Net.Requests;
@ -91,6 +90,7 @@ namespace CryptoExchange.Net
/// Execute a request to the uri and deserialize the response into the provided type parameter
/// </summary>
/// <typeparam name="T">The type to deserialize into</typeparam>
/// <param name="apiClient">The API client the request is for</param>
/// <param name="uri">The uri to send the request to</param>
/// <param name="method">The method of the request</param>
/// <param name="cancellationToken">Cancellation token</param>
@ -257,6 +257,7 @@ namespace CryptoExchange.Net
/// <summary>
/// Creates a request object
/// </summary>
/// <param name="apiClient">The API client the request is for</param>
/// <param name="uri">The uri to send the request to</param>
/// <param name="method">The method of the request</param>
/// <param name="parameters">The parameters of the request</param>

View File

@ -6,7 +6,6 @@ using System.Linq;
using System.Net.WebSockets;
using System.Threading;
using System.Threading.Tasks;
using CryptoExchange.Net.Authentication;
using CryptoExchange.Net.Interfaces;
using CryptoExchange.Net.Objects;
using CryptoExchange.Net.Sockets;
@ -30,11 +29,11 @@ namespace CryptoExchange.Net
/// <summary>
/// List of socket connections currently connecting/connected
/// </summary>
protected internal ConcurrentDictionary<int, SocketConnection> sockets = new ConcurrentDictionary<int, SocketConnection>();
protected internal ConcurrentDictionary<int, SocketConnection> sockets = new();
/// <summary>
/// Semaphore used while creating sockets
/// </summary>
protected internal readonly SemaphoreSlim semaphoreSlim = new SemaphoreSlim(1);
protected internal readonly SemaphoreSlim semaphoreSlim = new(1);
/// <summary>
/// The max amount of concurrent socket connections
/// </summary>
@ -50,7 +49,7 @@ namespace CryptoExchange.Net
/// <summary>
/// Handlers for data from the socket which doesn't need to be forwarded to the caller. Ping or welcome messages for example.
/// </summary>
protected Dictionary<string, Action<MessageEvent>> genericHandlers = new Dictionary<string, Action<MessageEvent>>();
protected Dictionary<string, Action<MessageEvent>> genericHandlers = new();
/// <summary>
/// The task that is sending periodic data on the websocket. Can be used for sending Ping messages every x seconds or similair. Not necesarry.
/// </summary>
@ -127,6 +126,7 @@ namespace CryptoExchange.Net
/// Connect to an url and listen for data on the BaseAddress
/// </summary>
/// <typeparam name="T">The type of the expected data</typeparam>
/// <param name="apiClient">The API client the subscription is for</param>
/// <param name="request">The optional request object to send, will be serialized to json</param>
/// <param name="identifier">The identifier to use, necessary if no request object is sent</param>
/// <param name="authenticated">If the subscription is to an authenticated endpoint</param>
@ -142,6 +142,7 @@ namespace CryptoExchange.Net
/// Connect to an url and listen for data
/// </summary>
/// <typeparam name="T">The type of the expected data</typeparam>
/// <param name="apiClient">The API client the subscription is for</param>
/// <param name="url">The URL to connect to</param>
/// <param name="request">The optional request object to send, will be serialized to json</param>
/// <param name="identifier">The identifier to use, necessary if no request object is sent</param>
@ -250,6 +251,7 @@ namespace CryptoExchange.Net
/// Send a query on a socket connection to the BaseAddress and wait for the response
/// </summary>
/// <typeparam name="T">Expected result type</typeparam>
/// <param name="apiClient">The API client the query is for</param>
/// <param name="request">The request to send, will be serialized to json</param>
/// <param name="authenticated">If the query is to an authenticated endpoint</param>
/// <returns></returns>
@ -262,6 +264,7 @@ namespace CryptoExchange.Net
/// Send a query on a socket connection and wait for the response
/// </summary>
/// <typeparam name="T">The expected result type</typeparam>
/// <param name="apiClient">The API client the query is for</param>
/// <param name="url">The url for the request</param>
/// <param name="request">The request to send</param>
/// <param name="authenticated">Whether the socket should be authenticated</param>
@ -477,6 +480,7 @@ namespace CryptoExchange.Net
/// <summary>
/// Gets a connection for a new subscription or query. Can be an existing if there are open position or a new one.
/// </summary>
/// <param name="apiClient">The API client the connection is for</param>
/// <param name="address">The address the socket is for</param>
/// <param name="authenticated">Whether the socket should be authenticated</param>
/// <returns></returns>

View File

@ -1,28 +1,17 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using System.Web;
using CryptoExchange.Net.Authentication;
using CryptoExchange.Net.Interfaces;
using CryptoExchange.Net.Objects;
using CryptoExchange.Net.Requests;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace CryptoExchange.Net
{
/// <summary>
/// Base rest client
/// Base rest API client for interacting with a REST API
/// </summary>
public abstract class RestApiClient: BaseApiClient
{
/// <summary>
/// Options for this client
/// </summary>
internal RestApiClientOptions Options { get; }
/// <summary>
@ -30,6 +19,11 @@ namespace CryptoExchange.Net
/// </summary>
internal IEnumerable<IRateLimiter> RateLimiters { get; }
/// <summary>
/// ctor
/// </summary>
/// <param name="options">The base client options</param>
/// <param name="apiOptions">The Api client options</param>
public RestApiClient(BaseRestClientOptions options, RestApiClientOptions apiOptions): base(options, apiOptions)
{
Options = apiOptions;

View File

@ -1,30 +1,22 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using System.Web;
using CryptoExchange.Net.Authentication;
using CryptoExchange.Net.Interfaces;
using CryptoExchange.Net.Objects;
using CryptoExchange.Net.Requests;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace CryptoExchange.Net
{
/// <summary>
/// Base rest client
/// Base socket API client for interaction with a websocket API
/// </summary>
public abstract class SocketApiClient : BaseApiClient
{
/// <summary>
/// The options for this client
/// </summary>
internal ApiClientOptions Options { get; }
/// <summary>
/// ctor
/// </summary>
/// <param name="options">The base client options</param>
/// <param name="apiOptions">The Api client options</param>
public SocketApiClient(BaseClientOptions options, ApiClientOptions apiOptions): base(options, apiOptions)
{
Options = apiOptions;

View File

@ -6,11 +6,11 @@ using System.Globalization;
namespace CryptoExchange.Net.Converters
{
/// <summary>
/// Datetime converter
/// Datetime converter. Supports converting from string/long/double to DateTime and back. Numbers are assumed to be the time since 1970-01-01.
/// </summary>
public class DateTimeConverter: JsonConverter
{
private static DateTime _epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
private static readonly DateTime _epoch = new(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
private const decimal ticksPerMicrosecond = TimeSpan.TicksPerMillisecond / 1000;
private const decimal ticksPerNanosecond = TimeSpan.TicksPerMillisecond / 1000m / 1000;
@ -180,7 +180,7 @@ namespace CryptoExchange.Net.Converters
if(datetimeValue == default(DateTime))
writer.WriteValue((DateTime?)null);
else
writer.WriteValue((long)Math.Round(((DateTime)value - new DateTime(1970, 1, 1)).TotalMilliseconds));
writer.WriteValue((long)Math.Round(((DateTime)value! - new DateTime(1970, 1, 1)).TotalMilliseconds));
}
}
}

View File

@ -8,15 +8,20 @@ using System.Linq;
namespace CryptoExchange.Net.Converters
{
/// <summary>
/// Converter for enum values. Enums entries should be noted with a MapAttribute to map the enum value to a string value
/// </summary>
public class EnumConverter : JsonConverter
{
private static ConcurrentDictionary<Type, List<KeyValuePair<object, string>>> _mapping = new ConcurrentDictionary<Type, List<KeyValuePair<object, string>>>();
private static readonly ConcurrentDictionary<Type, List<KeyValuePair<object, string>>> _mapping = new();
/// <inheritdoc />
public override bool CanConvert(Type objectType)
{
return objectType.IsEnum;
}
/// <inheritdoc />
public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer)
{
objectType = Nullable.GetUnderlyingType(objectType) ?? objectType;
@ -56,7 +61,7 @@ namespace CryptoExchange.Net.Converters
return mapping;
}
private bool GetValue(Type objectType, List<KeyValuePair<object, string>> enumMapping, string value, out object? result)
private static bool GetValue(Type objectType, List<KeyValuePair<object, string>> enumMapping, string value, out object? result)
{
// Check for exact match first, then if not found fallback to a case insensitive match
var mapping = enumMapping.FirstOrDefault(kv => kv.Value.Equals(value, StringComparison.InvariantCulture));
@ -81,6 +86,12 @@ namespace CryptoExchange.Net.Converters
}
}
/// <summary>
/// Get the string value for an enum value using the MapAttribute mapping. When multiple values are mapped for a enum entry the first value will be returned
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="enumValue"></param>
/// <returns></returns>
public static string? GetString<T>(T enumValue)
{
var objectType = typeof(T);
@ -92,6 +103,7 @@ namespace CryptoExchange.Net.Converters
return enumValue == null ? null : (mapping.FirstOrDefault(v => v.Key.Equals(enumValue)).Value ?? enumValue.ToString());
}
/// <inheritdoc />
public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer)
{
var stringValue = GetString(value);

View File

@ -10,7 +10,7 @@ using Microsoft.Extensions.Logging;
namespace CryptoExchange.Net.Objects
{
/// <summary>
/// Base options
/// Base options, applicable to everything
/// </summary>
public class BaseOptions
{
@ -50,7 +50,7 @@ namespace CryptoExchange.Net.Objects
}
/// <summary>
/// Base client options
/// Client options, for both the socket and rest clients
/// </summary>
public class BaseClientOptions : BaseOptions
{
@ -60,7 +60,7 @@ namespace CryptoExchange.Net.Objects
public ApiProxy? Proxy { get; set; }
/// <summary>
/// Api credentials to be used for all api clients unless overriden
/// Api credentials to be used for signing requests to private endpoints. These credentials will be used for each API in the client, unless overriden in the API options
/// </summary>
public ApiCredentials? ApiCredentials { get; set; }
@ -86,7 +86,7 @@ namespace CryptoExchange.Net.Objects
}
/// <summary>
/// Base for rest client options
/// Rest client options
/// </summary>
public class BaseRestClientOptions : BaseClientOptions
{
@ -96,7 +96,7 @@ namespace CryptoExchange.Net.Objects
public TimeSpan RequestTimeout { get; set; } = TimeSpan.FromSeconds(30);
/// <summary>
/// Http client to use. If a HttpClient is provided in this property the RequestTimeout and Proxy options will be ignored in requests and should be set on the provided HttpClient instance
/// Http client to use. If a HttpClient is provided in this property the RequestTimeout and Proxy options provided in these options will be ignored in requests and should be set on the provided HttpClient instance
/// </summary>
public HttpClient? HttpClient { get; set; }
@ -122,7 +122,7 @@ namespace CryptoExchange.Net.Objects
}
/// <summary>
/// Base for socket client options
/// Socket client options
/// </summary>
public class BaseSocketClientOptions : BaseClientOptions
{
@ -196,32 +196,49 @@ namespace CryptoExchange.Net.Objects
}
}
/// <summary>
/// API client options
/// </summary>
public class ApiClientOptions
{
/// <summary>
/// If true, the CallResult and DataEvent objects will also include the originally received json data in the OriginalData property
/// The base address of the API
/// </summary>
public string BaseAddress { get; set; }
/// <summary>
/// The api credentials used for signing requests
/// The api credentials used for signing requests to this API. Overrides API credentials provided in the client options
/// </summary>
public ApiCredentials? ApiCredentials { get; set; }
/// <summary>
/// ctor
/// </summary>
#pragma warning disable 8618 // Will always get filled by the implementation
public ApiClientOptions()
{
}
#pragma warning restore 8618
public ApiClientOptions(string baseAddres)
/// <summary>
/// ctor
/// </summary>
/// <param name="baseAddress">Base address for the API</param>
public ApiClientOptions(string baseAddress)
{
BaseAddress = baseAddres;
BaseAddress = baseAddress;
}
/// <summary>
/// ctor
/// </summary>
/// <param name="baseOn">Copy values for the provided options</param>
#pragma warning disable 8618 // Will always get filled by the provided options
public ApiClientOptions(ApiClientOptions baseOn)
{
Copy(this, baseOn);
}
#pragma warning restore 8618
/// <summary>
/// Copy the values of the def to the input
@ -243,6 +260,9 @@ namespace CryptoExchange.Net.Objects
}
}
/// <summary>
/// Rest API client options
/// </summary>
public class RestApiClientOptions: ApiClientOptions
{
/// <summary>
@ -255,14 +275,25 @@ namespace CryptoExchange.Net.Objects
/// </summary>
public RateLimitingBehaviour RateLimitingBehaviour { get; set; } = RateLimitingBehaviour.Wait;
/// <summary>
/// ctor
/// </summary>
public RestApiClientOptions()
{
}
/// <summary>
/// ctor
/// </summary>
/// <param name="baseAddress">Base address for the API</param>
public RestApiClientOptions(string baseAddress): base(baseAddress)
{
}
/// <summary>
/// ctor
/// </summary>
/// <param name="baseOn">Copy values for the provided options</param>
public RestApiClientOptions(RestApiClientOptions baseOn)
{
Copy(this, baseOn);