mirror of
https://github.com/JKorf/CryptoExchange.Net.git
synced 2026-10-13 22:51:39 +00:00
wip
This commit is contained in:
@@ -1,5 +1,7 @@
|
|||||||
using CryptoExchange.Net.SharedApis;
|
using CryptoExchange.Net.SharedApis;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
namespace CryptoExchange.Net.UnitTests
|
namespace CryptoExchange.Net.UnitTests
|
||||||
{
|
{
|
||||||
@@ -16,7 +18,7 @@ namespace CryptoExchange.Net.UnitTests
|
|||||||
|
|
||||||
Assert.Multiple(() =>
|
Assert.Multiple(() =>
|
||||||
{
|
{
|
||||||
Assert.That(client.GetCapability<ITestCapability>(TradingMode.Spot), Is.SameAs(sharedApi));
|
Assert.That(client.GetCapability<ITestCapability>(TradingMode.Spot)?.Capability, Is.SameAs(sharedApi));
|
||||||
Assert.That(client.GetCapability<ITestCapability>(TradingMode.PerpetualLinear), Is.Null);
|
Assert.That(client.GetCapability<ITestCapability>(TradingMode.PerpetualLinear), Is.Null);
|
||||||
Assert.That(client.GetCapabilities<ITestCapability>(TradingMode.Spot), Has.Count.EqualTo(1));
|
Assert.That(client.GetCapabilities<ITestCapability>(TradingMode.Spot), Has.Count.EqualTo(1));
|
||||||
Assert.That(client.GetCapabilities<ITestCapability>(TradingMode.PerpetualLinear), Is.Empty);
|
Assert.That(client.GetCapabilities<ITestCapability>(TradingMode.PerpetualLinear), Is.Empty);
|
||||||
@@ -33,8 +35,73 @@ namespace CryptoExchange.Net.UnitTests
|
|||||||
|
|
||||||
Assert.Multiple(() =>
|
Assert.Multiple(() =>
|
||||||
{
|
{
|
||||||
Assert.That(client.GetCapability<ITestCapability>(TradingMode.Spot), Is.SameAs(sharedApi));
|
Assert.That(client.GetCapability<ITestCapability>(TradingMode.Spot)?.Capability, Is.SameAs(sharedApi));
|
||||||
Assert.That(client.GetCapability<ITestCapability>(TradingMode.PerpetualLinear), Is.SameAs(sharedApi));
|
Assert.That(client.GetCapability<ITestCapability>(TradingMode.PerpetualLinear)?.Capability, Is.SameAs(sharedApi));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void GetCapabilities_WithReference_ShouldReturnMatchingCapabilities()
|
||||||
|
{
|
||||||
|
var sharedApi = new TestSharedApi(
|
||||||
|
[TradingMode.Spot],
|
||||||
|
[TradingMode.Spot]);
|
||||||
|
var client = new TestSharedApiClient(sharedApi);
|
||||||
|
|
||||||
|
var result = client.GetCapabilities(
|
||||||
|
new SharedCapabilityReference<ITestCapability>(),
|
||||||
|
TradingMode.Spot);
|
||||||
|
|
||||||
|
Assert.That(result, Has.Count.EqualTo(1));
|
||||||
|
Assert.That(result[0].Capability, Is.SameAs(sharedApi));
|
||||||
|
Assert.That(result[0].Options, Is.SameAs(sharedApi.CapabilityOptions));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void Discover_ShouldReturnAllSharedApiInformation()
|
||||||
|
{
|
||||||
|
var restApi = new TestRestSharedApi();
|
||||||
|
var socketApi = new TestSocketSharedApi();
|
||||||
|
var client = new TestDiSharedApiClient(
|
||||||
|
SharedTransport.Socket,
|
||||||
|
restApi,
|
||||||
|
socketApi);
|
||||||
|
|
||||||
|
var result = client.Discover();
|
||||||
|
|
||||||
|
Assert.Multiple(() =>
|
||||||
|
{
|
||||||
|
Assert.That(result.PreferredTransport, Is.EqualTo(SharedTransport.Socket));
|
||||||
|
Assert.That(result.SharedApis, Has.Length.EqualTo(2));
|
||||||
|
Assert.That(result.SharedApis[0].Transport, Is.EqualTo(SharedTransport.Rest));
|
||||||
|
Assert.That(result.SharedApis[1].Transport, Is.EqualTo(SharedTransport.Socket));
|
||||||
|
Assert.That(result.SharedApis[0].SupportedTradingModes, Is.EqualTo(new[] { TradingMode.Spot }));
|
||||||
|
Assert.That(result.SharedApis[0].Capabilities, Has.Length.EqualTo(1));
|
||||||
|
Assert.That(result.SharedApis[0].Authenticated, Is.False);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void RegisterSharedApiClientCapabilities_ShouldResolvePreferredTransport()
|
||||||
|
{
|
||||||
|
var restApi = new TestRestSharedApi();
|
||||||
|
var socketApi = new TestSocketSharedApi();
|
||||||
|
var services = new ServiceCollection();
|
||||||
|
|
||||||
|
services.RegisterSharedApi<ITestRestSharedApi>(_ => restApi);
|
||||||
|
services.RegisterSharedApi<ITestSocketSharedApi>(_ => socketApi);
|
||||||
|
services.AddTransient<ITestSharedApiClient>(_ =>
|
||||||
|
new TestDiSharedApiClient(SharedTransport.Socket, restApi, socketApi));
|
||||||
|
services.RegisterSharedApiClientCapabilities<ITestSharedApiClient>();
|
||||||
|
|
||||||
|
using var provider = services.BuildServiceProvider();
|
||||||
|
|
||||||
|
Assert.Multiple(() =>
|
||||||
|
{
|
||||||
|
Assert.That(provider.GetRequiredService<ITestCapability>(), Is.SameAs(socketApi));
|
||||||
|
Assert.That(provider.GetServices<ITestCapability>().ToArray(), Is.EqualTo(new[] { socketApi }));
|
||||||
|
Assert.That(provider.GetRequiredService<ITestRestCapability>(), Is.SameAs(restApi));
|
||||||
|
Assert.That(provider.GetRequiredService<ITestSocketCapability>(), Is.SameAs(socketApi));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -101,6 +168,28 @@ namespace CryptoExchange.Net.UnitTests
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private interface ITestRestCapability : ITestCapability, ISharedRest
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
private interface ITestSocketCapability : ITestCapability, ISharedSocket
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
private interface ITestRestSharedApi : ITestRestCapability
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
private interface ITestSocketSharedApi : ITestSocketCapability
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
private interface ITestSharedApiClient : ISharedApiClientBase
|
||||||
|
{
|
||||||
|
ITestRestSharedApi Rest { get; }
|
||||||
|
ITestSocketSharedApi Socket { get; }
|
||||||
|
}
|
||||||
|
|
||||||
private sealed class TestCapabilityOptions : CapabilityOptions<SharedRequest, ITestCapability>
|
private sealed class TestCapabilityOptions : CapabilityOptions<SharedRequest, ITestCapability>
|
||||||
{
|
{
|
||||||
public override string Description => "Test capability";
|
public override string Description => "Test capability";
|
||||||
@@ -159,10 +248,64 @@ namespace CryptoExchange.Net.UnitTests
|
|||||||
public override SharedClientInfo Discover() => new();
|
public override SharedClientInfo Discover() => new();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private abstract class TestTransportSharedApi : SharedApiBase
|
||||||
|
{
|
||||||
|
protected TestTransportSharedApi(SharedTransport transport)
|
||||||
|
: base(
|
||||||
|
transport,
|
||||||
|
"TestExchange",
|
||||||
|
[TradingMode.Spot],
|
||||||
|
() => false,
|
||||||
|
(baseAsset, quoteAsset, tradingMode, deliverDate) => $"{baseAsset}{quoteAsset}")
|
||||||
|
{
|
||||||
|
SetCapabilities(new TestCapabilityOptions([TradingMode.Spot]));
|
||||||
|
}
|
||||||
|
|
||||||
|
public override SharedClientInfo Discover() => new()
|
||||||
|
{
|
||||||
|
Exchange = this.Exchange,
|
||||||
|
TypeName = GetType().Name,
|
||||||
|
SupportedTradingModes = this.SupportedTradingModes,
|
||||||
|
Transport = this.Transport,
|
||||||
|
Authenticated = this.Authenticated,
|
||||||
|
Capabilities = ((ISharedApi)this).Capabilities.ToArray()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class TestRestSharedApi : TestTransportSharedApi, ITestRestSharedApi
|
||||||
|
{
|
||||||
|
public TestRestSharedApi() : base(SharedTransport.Rest)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class TestSocketSharedApi : TestTransportSharedApi, ITestSocketSharedApi
|
||||||
|
{
|
||||||
|
public TestSocketSharedApi() : base(SharedTransport.Socket)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class TestDiSharedApiClient : SharedApiClientBase, ITestSharedApiClient
|
||||||
|
{
|
||||||
|
public ITestRestSharedApi Rest { get; }
|
||||||
|
public ITestSocketSharedApi Socket { get; }
|
||||||
|
|
||||||
|
public TestDiSharedApiClient(
|
||||||
|
SharedTransport preferredTransport,
|
||||||
|
ITestRestSharedApi rest,
|
||||||
|
ITestSocketSharedApi socket)
|
||||||
|
: base(preferredTransport, rest, socket)
|
||||||
|
{
|
||||||
|
Rest = rest;
|
||||||
|
Socket = socket;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private sealed class TestSharedApiClient : SharedApiClientBase
|
private sealed class TestSharedApiClient : SharedApiClientBase
|
||||||
{
|
{
|
||||||
public TestSharedApiClient(params ISharedApiCapability[] sharedApis)
|
public TestSharedApiClient(params ISharedApiCapability[] sharedApis)
|
||||||
: base([SharedTransport.Rest, SharedTransport.Socket], sharedApis)
|
: base(SharedTransport.Rest, sharedApis)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -488,19 +488,77 @@ namespace CryptoExchange.Net
|
|||||||
{
|
{
|
||||||
services.AddTransient<TSharedApi>(factory);
|
services.AddTransient<TSharedApi>(factory);
|
||||||
|
|
||||||
var markerInterfaces = new[]
|
var allInterfaces = typeof(TSharedApi).GetInterfaces();
|
||||||
{
|
|
||||||
typeof(ISharedApiCapability),
|
|
||||||
typeof(ISharedRest),
|
|
||||||
typeof(ISharedSocket),
|
|
||||||
typeof(ISharedSubscription)
|
|
||||||
};
|
|
||||||
|
|
||||||
var capabilityInterfaces = typeof(TSharedApi)
|
var transportCapabilityInterfaces = allInterfaces
|
||||||
.GetInterfaces()
|
|
||||||
.Where(x =>
|
.Where(x =>
|
||||||
typeof(ISharedApiCapability).IsAssignableFrom(x)
|
typeof(ISharedApiCapability).IsAssignableFrom(x)
|
||||||
&& !markerInterfaces.Contains(x))
|
&& (typeof(ISharedRest).IsAssignableFrom(x)
|
||||||
|
|| typeof(ISharedSocket).IsAssignableFrom(x))
|
||||||
|
&& x != typeof(ISharedRest)
|
||||||
|
&& x != typeof(ISharedSocket)
|
||||||
|
&& x != typeof(ISharedSubscription))
|
||||||
|
.Distinct()
|
||||||
|
.ToArray();
|
||||||
|
|
||||||
|
var baseCapabilityInterfaces = allInterfaces
|
||||||
|
.Where(x =>
|
||||||
|
x != typeof(ISharedApiCapability)
|
||||||
|
&& typeof(ISharedApiCapability).IsAssignableFrom(x)
|
||||||
|
&& !typeof(ISharedRest).IsAssignableFrom(x)
|
||||||
|
&& !typeof(ISharedSocket).IsAssignableFrom(x))
|
||||||
|
.Distinct()
|
||||||
|
.ToArray();
|
||||||
|
|
||||||
|
services.AddSingleton(new SharedApiRegistrationInfo(
|
||||||
|
typeof(TSharedApi),
|
||||||
|
baseCapabilityInterfaces));
|
||||||
|
|
||||||
|
foreach (var capabilityInterface in transportCapabilityInterfaces)
|
||||||
|
{
|
||||||
|
services.AddTransient(
|
||||||
|
capabilityInterface,
|
||||||
|
serviceProvider =>
|
||||||
|
serviceProvider.GetRequiredService<TSharedApi>());
|
||||||
|
}
|
||||||
|
|
||||||
|
return services;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Registers transport-agnostic Shared API capabilities through the
|
||||||
|
/// exchange-specific Shared API aggregate client.
|
||||||
|
/// </summary>
|
||||||
|
public static IServiceCollection RegisterSharedApiClientCapabilities<
|
||||||
|
#if NET5_0_OR_GREATER
|
||||||
|
[DynamicallyAccessedMembers(
|
||||||
|
DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.PublicConstructors)]
|
||||||
|
#endif
|
||||||
|
TSharedApiClient>(
|
||||||
|
this IServiceCollection services)
|
||||||
|
where TSharedApiClient : class, ISharedApiClientBase
|
||||||
|
{
|
||||||
|
services.AddTransient<ISharedApiClientBase>(serviceProvider =>
|
||||||
|
serviceProvider.GetRequiredService<TSharedApiClient>());
|
||||||
|
|
||||||
|
var sharedApiTypes = new HashSet<Type>(
|
||||||
|
typeof(TSharedApiClient)
|
||||||
|
.GetProperties()
|
||||||
|
.Where(property =>
|
||||||
|
typeof(ISharedApi).IsAssignableFrom(
|
||||||
|
property.PropertyType))
|
||||||
|
.Select(property => property.PropertyType));
|
||||||
|
|
||||||
|
var capabilityInterfaces = services
|
||||||
|
.Where(descriptor =>
|
||||||
|
descriptor.ImplementationInstance
|
||||||
|
is SharedApiRegistrationInfo)
|
||||||
|
.Select(descriptor =>
|
||||||
|
(SharedApiRegistrationInfo)descriptor.ImplementationInstance!)
|
||||||
|
.Where(registration =>
|
||||||
|
sharedApiTypes.Contains(registration.SharedApiType))
|
||||||
|
.SelectMany(registration =>
|
||||||
|
registration.BaseCapabilityTypes)
|
||||||
.Distinct()
|
.Distinct()
|
||||||
.ToArray();
|
.ToArray();
|
||||||
|
|
||||||
@@ -509,7 +567,22 @@ namespace CryptoExchange.Net
|
|||||||
services.AddTransient(
|
services.AddTransient(
|
||||||
capabilityInterface,
|
capabilityInterface,
|
||||||
serviceProvider =>
|
serviceProvider =>
|
||||||
serviceProvider.GetRequiredService<TSharedApi>());
|
{
|
||||||
|
var sharedApiClient = serviceProvider
|
||||||
|
.GetRequiredService<TSharedApiClient>();
|
||||||
|
|
||||||
|
if (sharedApiClient is not ISharedApiClientResolver resolver)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(
|
||||||
|
$"{typeof(TSharedApiClient).Name} should derive from " +
|
||||||
|
$"{nameof(SharedApiClientBase)}");
|
||||||
|
}
|
||||||
|
|
||||||
|
return resolver.GetCapability(capabilityInterface)
|
||||||
|
?? throw new InvalidOperationException(
|
||||||
|
$"No implementation of " +
|
||||||
|
$"{capabilityInterface.Name} is available");
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return services;
|
return services;
|
||||||
|
|||||||
@@ -11,6 +11,16 @@ namespace CryptoExchange.Net.SharedApis
|
|||||||
public class SharedCapabilityResolution<T>
|
public class SharedCapabilityResolution<T>
|
||||||
where T : ISharedApiCapability
|
where T : ISharedApiCapability
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The exchange name
|
||||||
|
/// </summary>
|
||||||
|
public string Exchange => Capability.Exchange;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The transport type
|
||||||
|
/// </summary>
|
||||||
|
public SharedTransport Transport => Capability.Transport;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The capability
|
/// The capability
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -7,15 +7,15 @@ namespace CryptoExchange.Net.SharedApis
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// The type of transport used for the shared API
|
/// The type of transport used for the shared API
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public record SharedTransport(string Name)
|
public enum SharedTransport
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// REST transport
|
/// REST transport
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static SharedTransport Rest { get; } = new("REST");
|
Rest,
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// WebSocket transport
|
/// WebSocket transport
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static SharedTransport Socket { get; } = new("WebSocket");
|
Socket
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,49 @@
|
|||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace CryptoExchange.Net.SharedApis
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Discovery information for a Shared API client.
|
||||||
|
/// </summary>
|
||||||
|
public class SharedApiClientInfo
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Preferred transport when multiple implementations of a capability are available.
|
||||||
|
/// </summary>
|
||||||
|
public SharedTransport PreferredTransport { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Shared APIs available on the client.
|
||||||
|
/// </summary>
|
||||||
|
public SharedClientInfo[] SharedApis { get; }
|
||||||
|
|
||||||
|
internal SharedApiClientInfo(
|
||||||
|
SharedTransport preferredTransport,
|
||||||
|
SharedClientInfo[] sharedApis)
|
||||||
|
{
|
||||||
|
PreferredTransport = preferredTransport;
|
||||||
|
SharedApis = sharedApis;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override string ToString() => ToString(false);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create a string representation of the available Shared APIs.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="detailed">Whether to include detailed capability information.</param>
|
||||||
|
public string ToString(bool detailed)
|
||||||
|
{
|
||||||
|
var builder = new StringBuilder();
|
||||||
|
builder.AppendLine($"Preferred transport: {PreferredTransport}");
|
||||||
|
|
||||||
|
foreach (var sharedApi in SharedApis)
|
||||||
|
{
|
||||||
|
builder.AppendLine();
|
||||||
|
builder.Append(sharedApi.ToString(detailed));
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder.ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -35,6 +35,14 @@ namespace CryptoExchange.Net.SharedApis
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public CentralizationType CentralizationType { get; set; }
|
public CentralizationType CentralizationType { get; set; }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
/// The underlying transport type used by this client
|
||||||
|
/// </summary>
|
||||||
|
public SharedTransport Transport { get; init; }
|
||||||
|
/// <summary>
|
||||||
|
/// Whether API credentials have been configured for this client. Does not check whether the credentials are valid.
|
||||||
|
/// </summary>
|
||||||
|
public bool Authenticated { get; init; }
|
||||||
|
/// <summary>
|
||||||
/// Use Capabilities instead
|
/// Use Capabilities instead
|
||||||
/// </summary>
|
/// </summary>
|
||||||
#pragma warning disable CS0618 // Type or member is obsolete
|
#pragma warning disable CS0618 // Type or member is obsolete
|
||||||
@@ -74,6 +82,8 @@ namespace CryptoExchange.Net.SharedApis
|
|||||||
sb.AppendLine($"Supported environments: {string.Join(", ", SupportedEnvironments)}");
|
sb.AppendLine($"Supported environments: {string.Join(", ", SupportedEnvironments)}");
|
||||||
sb.AppendLine($"Supported trading modes: {string.Join(", ", SupportedTradingModes)}");
|
sb.AppendLine($"Supported trading modes: {string.Join(", ", SupportedTradingModes)}");
|
||||||
sb.AppendLine($"Centralization type: {CentralizationType}");
|
sb.AppendLine($"Centralization type: {CentralizationType}");
|
||||||
|
sb.AppendLine($"Transport: {Transport}");
|
||||||
|
sb.AppendLine($"Authenticated: {Authenticated}");
|
||||||
sb.AppendLine($"Capabilities:");
|
sb.AppendLine($"Capabilities:");
|
||||||
foreach (var capability in Capabilities)
|
foreach (var capability in Capabilities)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace CryptoExchange.Net.SharedApis
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Options for Shared API client instances
|
||||||
|
/// </summary>
|
||||||
|
public class SharedApiOptions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Whether to use XPerps as perpetual linear contracts when using the Shared API's
|
||||||
|
/// </summary>
|
||||||
|
public bool EuropeUseXPerps { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// The preferred transport type when requesting transport agnostic capabilities
|
||||||
|
/// </summary>
|
||||||
|
public SharedTransport PreferredTransport { get; set; }
|
||||||
|
= SharedTransport.Rest;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -65,7 +65,7 @@ namespace CryptoExchange.Net.SharedApis
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
{
|
{
|
||||||
return $"[{ValueType.Name}] \"{Name}\" {string.Join(" / ", Aliases.Select(x => $"\"{x}\""))}: {Description}";
|
return $"[{(Nullable.GetUnderlyingType(ValueType) ?? ValueType).Name}] \"{Name}\" {string.Join(" / ", Aliases.Select(x => $"\"{x}\""))}: {Description}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -135,7 +135,7 @@ namespace CryptoExchange.Net.SharedApis
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
{
|
{
|
||||||
return $"[{Support}] [{ValueType.Name}] \"{Name}\" {string.Join(" / ", Aliases.Select(x => $"\"{x}\""))}: {Description}";
|
return $"[{Support}] [{(Nullable.GetUnderlyingType(ValueType) ?? ValueType).Name}] \"{Name}\" {string.Join(" / ", Aliases.Select(x => $"\"{x}\""))}: {Description}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -167,7 +167,7 @@ namespace CryptoExchange.Net.SharedApis
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
{
|
{
|
||||||
return $"[{Requirement}] [{ValueType.Name}] \"{Name}\" {string.Join(" / ", Aliases.Select(x => $"\"{x}\""))}: {Description}";
|
return $"[{Requirement}] [{(Nullable.GetUnderlyingType(ValueType) ?? ValueType).Name}] \"{Name}\" {string.Join(" / ", Aliases.Select(x => $"\"{x}\""))}: {Description}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,15 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
||||||
namespace CryptoExchange.Net.SharedApis
|
namespace CryptoExchange.Net.SharedApis
|
||||||
{
|
{
|
||||||
|
internal interface ISharedApiClientResolver
|
||||||
|
{
|
||||||
|
object? GetCapability(Type capabilityType);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Base interface for Shared API clients
|
/// Base interface for Shared API clients
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -14,6 +19,19 @@ namespace CryptoExchange.Net.SharedApis
|
|||||||
/// Shared API's available on this client
|
/// Shared API's available on this client
|
||||||
/// </summary>
|
/// </summary>
|
||||||
IReadOnlyList<ISharedApi> SharedApis { get; }
|
IReadOnlyList<ISharedApi> SharedApis { get; }
|
||||||
|
/// <summary>
|
||||||
|
/// Preferred transport selection when multiple transport options are available
|
||||||
|
/// </summary>
|
||||||
|
SharedTransport PreferredTransport { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get discovery information for the Shared APIs available on this client.
|
||||||
|
/// </summary>
|
||||||
|
SharedApiClientInfo Discover();
|
||||||
|
/// <summary>
|
||||||
|
/// The exchange name of this client
|
||||||
|
/// </summary>
|
||||||
|
string Exchange { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Get all capabilities matching the provided capability type.
|
/// Get all capabilities matching the provided capability type.
|
||||||
@@ -26,6 +44,17 @@ namespace CryptoExchange.Net.SharedApis
|
|||||||
IReadOnlyList<SharedCapabilityResolution<T>> GetCapabilities<T>(TradingMode? tradingMode = null, SharedTransport? transport = null)
|
IReadOnlyList<SharedCapabilityResolution<T>> GetCapabilities<T>(TradingMode? tradingMode = null, SharedTransport? transport = null)
|
||||||
where T : ISharedApiCapability;
|
where T : ISharedApiCapability;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get all capabilities matching the provided capability reference.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">The capability type.</typeparam>
|
||||||
|
/// <param name="capability">The capability reference.</param>
|
||||||
|
/// <param name="tradingMode">Filter by supported trading mode.</param>
|
||||||
|
IReadOnlyList<SharedCapabilityResolution<T>> GetCapabilities<T>(
|
||||||
|
SharedCapabilityReference<T> capability,
|
||||||
|
TradingMode? tradingMode = null)
|
||||||
|
where T : ISharedApiCapability;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Get the requested capability type. If more than one implementation is available the first one is returned.
|
/// Get the requested capability type. If more than one implementation is available the first one is returned.
|
||||||
/// In this case prefer using <see cref="GetCapability{T}(TradingMode, SharedTransport?)"/> to select a specific trading mode.
|
/// In this case prefer using <see cref="GetCapability{T}(TradingMode, SharedTransport?)"/> to select a specific trading mode.
|
||||||
@@ -74,30 +103,43 @@ namespace CryptoExchange.Net.SharedApis
|
|||||||
/// <returns>The requested capability type or null if not found</returns>
|
/// <returns>The requested capability type or null if not found</returns>
|
||||||
SharedCapabilityResolution<T>? GetCapability<T>(SharedCapabilityReference<T> capability, TradingMode tradingMode)
|
SharedCapabilityResolution<T>? GetCapability<T>(SharedCapabilityReference<T> capability, TradingMode tradingMode)
|
||||||
where T : ISharedApiCapability;
|
where T : ISharedApiCapability;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Base client for Shared API clients
|
/// Base client for Shared API clients
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public abstract class SharedApiClientBase : ISharedApiClientBase
|
public abstract class SharedApiClientBase : ISharedApiClientBase, ISharedApiClientResolver
|
||||||
{
|
{
|
||||||
private readonly ISharedApi[] _sharedApis;
|
private readonly ISharedApi[] _sharedApis;
|
||||||
private readonly SharedTransport[] _preferredTransports;
|
private readonly SharedTransport _preferredTransport;
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public IReadOnlyList<ISharedApi> SharedApis => _sharedApis;
|
public IReadOnlyList<ISharedApi> SharedApis => _sharedApis;
|
||||||
|
/// <inheritdoc />
|
||||||
|
public SharedTransport PreferredTransport => _preferredTransport;
|
||||||
|
/// <inheritdoc />
|
||||||
|
public string Exchange => _sharedApis[0].Exchange;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// ctor
|
/// ctor
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public SharedApiClientBase(
|
public SharedApiClientBase(
|
||||||
IEnumerable<SharedTransport> transportPreference,
|
SharedTransport transportPreference,
|
||||||
params ISharedApi[] sharedApis)
|
params ISharedApi[] sharedApis)
|
||||||
{
|
{
|
||||||
_preferredTransports = transportPreference.Distinct().ToArray();
|
_preferredTransport = transportPreference;
|
||||||
_sharedApis = sharedApis.Distinct().ToArray();
|
_sharedApis = sharedApis.Distinct().ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public SharedApiClientInfo Discover()
|
||||||
|
{
|
||||||
|
return new SharedApiClientInfo(
|
||||||
|
PreferredTransport,
|
||||||
|
_sharedApis.Select(x => x.Discover()).ToArray());
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public SharedCapabilityResolution<T>? GetCapability<T>(SharedTransport? transport = null)
|
public SharedCapabilityResolution<T>? GetCapability<T>(SharedTransport? transport = null)
|
||||||
where T : ISharedApiCapability
|
where T : ISharedApiCapability
|
||||||
@@ -126,6 +168,7 @@ namespace CryptoExchange.Net.SharedApis
|
|||||||
return GetCapabilityCore<T>(tradingMode, null);
|
return GetCapabilityCore<T>(tradingMode, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public IReadOnlyList<SharedCapabilityResolution<T>> GetCapabilities<T>(
|
public IReadOnlyList<SharedCapabilityResolution<T>> GetCapabilities<T>(
|
||||||
TradingMode? tradingMode = null,
|
TradingMode? tradingMode = null,
|
||||||
@@ -157,6 +200,29 @@ namespace CryptoExchange.Net.SharedApis
|
|||||||
.ToArray();
|
.ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public IReadOnlyList<SharedCapabilityResolution<T>> GetCapabilities<T>(
|
||||||
|
SharedCapabilityReference<T> capability,
|
||||||
|
TradingMode? tradingMode = null)
|
||||||
|
where T : ISharedApiCapability
|
||||||
|
{
|
||||||
|
return GetCapabilities<T>(tradingMode);
|
||||||
|
}
|
||||||
|
|
||||||
|
object? ISharedApiClientResolver.GetCapability(Type capabilityType)
|
||||||
|
{
|
||||||
|
return _sharedApis
|
||||||
|
.Where(sharedApi =>
|
||||||
|
capabilityType.IsInstanceOfType(sharedApi)
|
||||||
|
&& GetMatchingOptions(
|
||||||
|
sharedApi,
|
||||||
|
capabilityType,
|
||||||
|
null) != null)
|
||||||
|
.OrderBy(sharedApi =>
|
||||||
|
GetTransportPriority(sharedApi.Transport))
|
||||||
|
.FirstOrDefault();
|
||||||
|
}
|
||||||
|
|
||||||
private SharedCapabilityResolution<T>? GetCapabilityCore<T>(
|
private SharedCapabilityResolution<T>? GetCapabilityCore<T>(
|
||||||
TradingMode? tradingMode,
|
TradingMode? tradingMode,
|
||||||
SharedTransport? selectedTransport = null)
|
SharedTransport? selectedTransport = null)
|
||||||
@@ -171,8 +237,17 @@ namespace CryptoExchange.Net.SharedApis
|
|||||||
TradingMode? tradingMode)
|
TradingMode? tradingMode)
|
||||||
where T : ISharedApiCapability
|
where T : ISharedApiCapability
|
||||||
{
|
{
|
||||||
var requestedType = typeof(T);
|
return GetMatchingOptions(
|
||||||
|
sharedApi,
|
||||||
|
typeof(T),
|
||||||
|
tradingMode);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static CapabilityOptions? GetMatchingOptions(
|
||||||
|
ISharedApi sharedApi,
|
||||||
|
Type requestedType,
|
||||||
|
TradingMode? tradingMode)
|
||||||
|
{
|
||||||
return sharedApi.Capabilities
|
return sharedApi.Capabilities
|
||||||
.Where(options =>
|
.Where(options =>
|
||||||
options.Supported
|
options.Supported
|
||||||
@@ -182,7 +257,6 @@ namespace CryptoExchange.Net.SharedApis
|
|||||||
&& (tradingMode == null
|
&& (tradingMode == null
|
||||||
|| options.SupportedTradingModes.Contains(
|
|| options.SupportedTradingModes.Contains(
|
||||||
tradingMode.Value)))
|
tradingMode.Value)))
|
||||||
// Prefer options declared for the exact requested interface.
|
|
||||||
.OrderByDescending(options =>
|
.OrderByDescending(options =>
|
||||||
options.CapabilityType == requestedType)
|
options.CapabilityType == requestedType)
|
||||||
.FirstOrDefault();
|
.FirstOrDefault();
|
||||||
@@ -198,8 +272,7 @@ namespace CryptoExchange.Net.SharedApis
|
|||||||
|
|
||||||
private int GetTransportPriority(SharedTransport transport)
|
private int GetTransportPriority(SharedTransport transport)
|
||||||
{
|
{
|
||||||
var index = Array.IndexOf(_preferredTransports, transport);
|
return transport == _preferredTransport ? 0 : 1;
|
||||||
return index == -1 ? int.MaxValue : index;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace CryptoExchange.Net.SharedApis
|
||||||
|
{
|
||||||
|
internal sealed class SharedApiRegistrationInfo
|
||||||
|
{
|
||||||
|
public Type SharedApiType { get; }
|
||||||
|
public Type[] BaseCapabilityTypes { get; }
|
||||||
|
|
||||||
|
public SharedApiRegistrationInfo(
|
||||||
|
Type sharedApiType,
|
||||||
|
Type[] baseCapabilityTypes)
|
||||||
|
{
|
||||||
|
SharedApiType = sharedApiType;
|
||||||
|
BaseCapabilityTypes = baseCapabilityTypes;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -21,6 +21,8 @@ namespace CryptoExchange.Net.SharedApis
|
|||||||
SupportedEnvironments = platformInfo.SupportedEnvironments,
|
SupportedEnvironments = platformInfo.SupportedEnvironments,
|
||||||
SupportedTradingModes = client.SupportedTradingModes,
|
SupportedTradingModes = client.SupportedTradingModes,
|
||||||
CentralizationType = platformInfo.CentralizationType,
|
CentralizationType = platformInfo.CentralizationType,
|
||||||
|
Transport = client.Transport,
|
||||||
|
Authenticated = client.Authenticated,
|
||||||
Capabilities = client.Capabilities.Where(x => x.Supported).ToArray()
|
Capabilities = client.Capabilities.Where(x => x.Supported).ToArray()
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user