using CryptoExchange.Net.Objects; using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Reflection; using System.Text; namespace CryptoExchange.Net.SharedApis { /// /// Options for an exchange endpoint /// public abstract class EndpointOptions { /// /// Required exchange-specific parameters. These can be provided via the `exchangeParameters` property of the request object. /// public List RequiredExchangeParameters { get; set; } = new List(); /// /// Optional exchange-specific parameters. These can be provided via the `exchangeParameters` property of the request object. /// public List OptionalExchangeParameters { get; set; } = new List(); /// /// Exchange /// public string Exchange { get; set; } /// /// Endpoint name /// public string EndpointName { get; set; } /// /// Exchange specific additional info /// public string? RequestNotes { get; set; } /// /// Whether the call requires authentication /// public bool NeedsAuthentication { get; set; } /// /// Whether the call is supported. If false the exchange API does not support this operation. /// public bool Supported { get; set; } = true; /// /// ctor /// public EndpointOptions(string exchange, string endpointName, bool needAuthentication) { Exchange = exchange; EndpointName = endpointName; NeedsAuthentication = needAuthentication; } /// /// Validate a request /// /// Provided exchange parameters /// Request trading mode /// Supported trading modes /// public virtual Error? ValidateRequest(ExchangeParameters? exchangeParameters, TradingMode? tradingMode, TradingMode[] supportedTradingModes) { if (!Supported) return ArgumentError.Invalid("Endpoint", $"Endpoint {Exchange} {EndpointName} is not supported by the API"); if (tradingMode != null && !supportedTradingModes.Contains(tradingMode.Value)) return ArgumentError.Invalid("TradingMode", $"TradingMode.{tradingMode} is not supported, supported types: {string.Join(", ", supportedTradingModes)}"); foreach (var param in RequiredExchangeParameters) { if (param.Names!.All(x => ExchangeParameters.HasValue(exchangeParameters, Exchange, x, param.ValueType) != true)) return ArgumentError.Invalid(string.Join("/", param.Names!), $"One of exchange parameters `{string.Join(", ", param.Names!)}` for exchange `{Exchange}` should be provided. Example: {param.ExampleValue}"); } return null; } } /// /// Options for an exchange endpoint /// /// Type of data #if NET5_0_OR_GREATER public class EndpointOptions<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] TRequest> : EndpointOptions where TRequest : SharedRequest #else public abstract class EndpointOptions : EndpointOptions where TRequest : SharedRequest #endif { /// /// ctor /// public EndpointOptions(string exchange, bool needsAuthentication, string requestName) : base(exchange, requestName, needsAuthentication) { } } /// /// Options for an exchange endpoint /// /// Type of data /// Type of the client #if NET5_0_OR_GREATER public abstract class EndpointOptions<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] TRequest, TClient> : EndpointOptions where TRequest : SharedRequest where TClient : ISharedClient #else public abstract class EndpointOptions : EndpointOptions where TRequest : SharedRequest where TClient : ISharedClient #endif { private static PropertyInfo[] _requestProperties = typeof(TRequest).GetProperties(); /// /// Required optional parameters in the request. These can be provided via the `exchangeParameters` property of the request object. /// public List RequiredOptionalParameters { get; set; } = new List(); /// /// Whether this accepts multiple symbols (Only applicable to request requiring symbol parameters) /// public bool SupportsMultipleSymbols { get; set; } = false; /// /// The max number of symbols which can be passed in a call (Only applicable to request requiring symbol parameters) /// public int? MaxSymbolCount { get; set; } /// /// ctor /// public EndpointOptions(string exchange, bool needsAuthentication, string requestName) : base(exchange, needsAuthentication, requestName) { } /// /// Validate a request /// /// The request /// Containing client /// public virtual Error? ValidateRequest(TRequest request, TClient client) { if (NeedsAuthentication && !client.Authenticated) return new NoApiCredentialsError(); foreach (var param in RequiredOptionalParameters) { if (param.Names!.All(x => _requestProperties.Single(p => p.Name == x).GetValue(request, null) == null)) return ArgumentError.Invalid(string.Join("/", param.Names!), $"One of optional parameters `{string.Join(", ", param.Names!)}` for exchange `{Exchange}` should be provided. Example: {param.ExampleValue}"); } if (request is SharedSymbolRequest symbolsRequest) { if (symbolsRequest.Symbols != null) { if (!SupportsMultipleSymbols) return ArgumentError.Invalid(nameof(SharedSymbolRequest.Symbols), $"Only a single symbol parameter is allowed, multiple symbols are not supported"); if (symbolsRequest.Symbols.Length > MaxSymbolCount) return ArgumentError.Invalid(nameof(SharedSymbolRequest.Symbols), $"Max number of symbols is {MaxSymbolCount} but {symbolsRequest.Symbols.Length} were passed"); } } return ValidateRequest(request.ExchangeParameters, request.TradingMode, client.SupportedTradingModes); } /// public override string ToString() { if (!Supported) return $"{Exchange} {EndpointName} NOT SUPPORTED"; var sb = new StringBuilder(); sb.AppendLine($"{Exchange} {EndpointName}"); sb.AppendLine($"Needs authentication: {NeedsAuthentication}"); if (!string.IsNullOrEmpty(RequestNotes)) sb.AppendLine(RequestNotes); if (RequiredOptionalParameters.Any()) { sb.AppendLine($"Required optional parameters:"); foreach(var param in RequiredOptionalParameters) sb.AppendLine($" {param}"); } if (RequiredExchangeParameters.Any()) { sb.AppendLine($"Required exchange specific parameters:"); foreach (var param in RequiredExchangeParameters) sb.AppendLine($" {param}"); } if (OptionalExchangeParameters.Any()) { sb.AppendLine($"Optional exchange specific parameters:"); foreach (var param in OptionalExchangeParameters) sb.AppendLine($" {param}"); } return sb.ToString(); } } }