1
0
mirror of https://github.com/JKorf/CryptoExchange.Net synced 2025-09-02 21:51:27 +00:00
This commit is contained in:
JKorf 2025-08-24 21:58:52 +02:00
parent d0284c62c0
commit 4c953e2c87
354 changed files with 23357 additions and 23680 deletions

View File

@ -5,6 +5,10 @@
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<None Include="..\CryptoExchange.Net\.editorconfig" Link=".editorconfig" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1"></PackageReference>
<PackageReference Include="Moq" Version="4.20.72" />

View File

@ -0,0 +1,182 @@
root = true
[*]
# Indentation and spacing
indent_style = space
indent_size = 4
trim_trailing_whitespace = true
charset = utf-8
insert_final_newline = true
# ReSharper code style properties
resharper_csharp_keep_existing_embedded_arrangement = false
resharper_csharp_place_accessorholder_attribute_on_same_line = false
resharper_csharp_wrap_after_declaration_lpar = true
resharper_csharp_wrap_parameters_style = chop_if_long
resharper_csharp_blank_lines_around_single_line_auto_property = 1
resharper_csharp_keep_blank_lines_in_declarations = 1
resharper_trailing_comma_in_multiline_lists = true
[*.cs]
indent_size = 4
# Code style conventions
dotnet_style_predefined_type_for_member_access = true:suggestion
dotnet_style_collection_initializer = true:suggestion
dotnet_style_object_initializer = true:suggestion
csharp_style_var_when_type_is_apparent = true:suggestion
csharp_style_expression_bodied_methods = true:suggestion
csharp_style_namespace_declarations = file_scoped:warning
dotnet_style_coalesce_expression = true:suggestion
dotnet_style_null_propagation = true:suggestion
dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion
dotnet_style_prefer_simplified_boolean_expressions = true:suggestion
csharp_prefer_braces = when_multiline:warning
# Analyzer preferences
dotnet_diagnostic.CA2007.severity = warning # Call ConfigureAwait on the awaited Task.
dotnet_code_quality.CA2007.exclude_async_void_methods = true
dotnet_code_quality.CA2007.output_kind = DynamicallyLinkedLibrary
dotnet_diagnostic.CA1000.severity = none # Do not declare static members on generic types
dotnet_diagnostic.CA1051.severity = none # Do not declare visible instance fields
dotnet_diagnostic.CA1510.severity = none # Use ArgumentNullException throw helper
dotnet_diagnostic.CA1720.severity = none # Identifiers should not contain type names
dotnet_diagnostic.CA1716.severity = none # Identifiers should not match keywords
dotnet_diagnostic.CA1835.severity = none # Use ArgumentNullException throw helper
dotnet_diagnostic.CA1846.severity = none # Prefer AsSpan over Substring
dotnet_diagnostic.CA1848.severity = none # Use the LoggerMessage delegates
dotnet_diagnostic.CA1850.severity = none # Prefer static HashData method over ComputeHash
dotnet_diagnostic.CA1866.severity = none # Use 'string.Method(char)' instead of 'string.Method(string)' for string with single char
dotnet_diagnostic.CA2201.severity = none # Do not raise reserved exception types
dotnet_diagnostic.CA2208.severity = none # Do not raise reserved exception types
dotnet_diagnostic.IDE0005.severity = warning # Using directive is unnecessary
[*.xml]
ij_xml_space_inside_empty_tag = true
[*.cs]
#### Naming styles ####
# Naming rules
dotnet_naming_rule.interface_should_be_begins_with_i.severity = warning
dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface
dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i
dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = warning
dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members
dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case
dotnet_naming_rule.private_or_internal_field_should_be_fields_start_with__.severity = warning
dotnet_naming_rule.private_or_internal_field_should_be_fields_start_with__.symbols = private_or_internal_field
dotnet_naming_rule.private_or_internal_field_should_be_fields_start_with__.style = fields_start_with__
# Symbol specifications
dotnet_naming_symbols.interface.applicable_kinds = interface
dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
dotnet_naming_symbols.interface.required_modifiers =
dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method
dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
dotnet_naming_symbols.non_field_members.required_modifiers =
dotnet_naming_symbols.private_or_internal_field.applicable_kinds = field
dotnet_naming_symbols.private_or_internal_field.applicable_accessibilities = internal, private, private_protected
dotnet_naming_symbols.private_or_internal_field.required_modifiers =
# Naming styles
dotnet_naming_style.begins_with_i.required_prefix = I
dotnet_naming_style.begins_with_i.required_suffix =
dotnet_naming_style.begins_with_i.word_separator =
dotnet_naming_style.begins_with_i.capitalization = pascal_case
dotnet_naming_style.pascal_case.required_prefix =
dotnet_naming_style.pascal_case.required_suffix =
dotnet_naming_style.pascal_case.word_separator =
dotnet_naming_style.pascal_case.capitalization = pascal_case
dotnet_naming_style.fields_start_with__.required_prefix = _
dotnet_naming_style.fields_start_with__.required_suffix =
dotnet_naming_style.fields_start_with__.word_separator =
dotnet_naming_style.fields_start_with__.capitalization = camel_case
csharp_indent_labels = one_less_than_current
csharp_using_directive_placement = outside_namespace:suggestion
csharp_prefer_simple_using_statement = true:suggestion
csharp_style_prefer_method_group_conversion = true:silent
csharp_style_prefer_top_level_statements = true:silent
csharp_style_prefer_primary_constructors = true:suggestion
csharp_prefer_system_threading_lock = true:suggestion
csharp_style_expression_bodied_constructors = false:silent
csharp_style_expression_bodied_operators = false:silent
csharp_style_expression_bodied_properties = true:suggestion
csharp_style_expression_bodied_indexers = true:suggestion
csharp_style_expression_bodied_accessors = true:suggestion
csharp_style_expression_bodied_lambdas = true:silent
csharp_style_expression_bodied_local_functions = true:silent
[*.vb]
#### Naming styles ####
# Naming rules
dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion
dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface
dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i
dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion
dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members
dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case
# Symbol specifications
dotnet_naming_symbols.interface.applicable_kinds = interface
dotnet_naming_symbols.interface.applicable_accessibilities = public, friend, private, protected, protected_friend, private_protected
dotnet_naming_symbols.interface.required_modifiers =
dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method
dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, friend, private, protected, protected_friend, private_protected
dotnet_naming_symbols.non_field_members.required_modifiers =
# Naming styles
dotnet_naming_style.begins_with_i.required_prefix = I
dotnet_naming_style.begins_with_i.required_suffix =
dotnet_naming_style.begins_with_i.word_separator =
dotnet_naming_style.begins_with_i.capitalization = pascal_case
dotnet_naming_style.pascal_case.required_prefix =
dotnet_naming_style.pascal_case.required_suffix =
dotnet_naming_style.pascal_case.word_separator =
dotnet_naming_style.pascal_case.capitalization = pascal_case
[*.{cs,vb}]
#### Naming styles ####
# Naming rules
dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion
dotnet_naming_rule.types_should_be_pascal_case.symbols = types
dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case
# Symbol specifications
dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum
dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
dotnet_naming_symbols.types.required_modifiers =
# Naming styles
dotnet_naming_style.pascal_case.required_prefix =
dotnet_naming_style.pascal_case.required_suffix =
dotnet_naming_style.pascal_case.word_separator =
dotnet_naming_style.pascal_case.capitalization = pascal_case
dotnet_style_operator_placement_when_wrapping = beginning_of_line
tab_width = 4
end_of_line = crlf
dotnet_style_coalesce_expression = true:suggestion
dotnet_style_null_propagation = true:suggestion
dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion

View File

@ -1,6 +1,5 @@
[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("CryptoExchange.Net.UnitTests")]
[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("CryptoExchange.Net.UnitTests")]
namespace System.Runtime.CompilerServices
{
internal static class IsExternalInit { }
}
namespace System.Runtime.CompilerServices;
internal static class IsExternalInit { }

View File

@ -1,12 +1,11 @@
using System;
using System;
namespace CryptoExchange.Net.Attributes
namespace CryptoExchange.Net.Attributes;
/// <summary>
/// Used for conversion in ArrayConverter
/// </summary>
[AttributeUsage(AttributeTargets.Property)]
public class JsonConversionAttribute: Attribute
{
/// <summary>
/// Used for conversion in ArrayConverter
/// </summary>
[AttributeUsage(AttributeTargets.Property)]
public class JsonConversionAttribute: Attribute
{
}
}

View File

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

View File

@ -1,60 +1,56 @@
using System;
using System.IO;
using CryptoExchange.Net.Converters.SystemTextJson;
using CryptoExchange.Net.Converters.MessageParsing;
using System;
namespace CryptoExchange.Net.Authentication
namespace CryptoExchange.Net.Authentication;
/// <summary>
/// Api credentials, used to sign requests accessing private endpoints
/// </summary>
public class ApiCredentials
{
/// <summary>
/// Api credentials, used to sign requests accessing private endpoints
/// The api key / label to authenticate requests
/// </summary>
public class ApiCredentials
public string Key { get; set; }
/// <summary>
/// The api secret or private key to authenticate requests
/// </summary>
public string Secret { get; set; }
/// <summary>
/// The api passphrase. Not needed on all exchanges
/// </summary>
public string? Pass { get; set; }
/// <summary>
/// Type of the credentials
/// </summary>
public ApiCredentialsType CredentialType { get; set; }
/// <summary>
/// Create Api credentials providing an api key and secret for authentication
/// </summary>
/// <param name="key">The api key / label used for identification</param>
/// <param name="secret">The api secret or private key used for signing</param>
/// <param name="pass">The api pass for the key. Not always needed</param>
/// <param name="credentialType">The type of credentials</param>
public ApiCredentials(string key, string secret, string? pass = null, ApiCredentialsType credentialType = ApiCredentialsType.Hmac)
{
/// <summary>
/// The api key / label to authenticate requests
/// </summary>
public string Key { get; set; }
if (string.IsNullOrEmpty(key) || string.IsNullOrEmpty(secret))
throw new ArgumentException("Key and secret can't be null/empty");
/// <summary>
/// The api secret or private key to authenticate requests
/// </summary>
public string Secret { get; set; }
CredentialType = credentialType;
Key = key;
Secret = secret;
Pass = pass;
}
/// <summary>
/// The api passphrase. Not needed on all exchanges
/// </summary>
public string? Pass { get; set; }
/// <summary>
/// Type of the credentials
/// </summary>
public ApiCredentialsType CredentialType { get; set; }
/// <summary>
/// Create Api credentials providing an api key and secret for authentication
/// </summary>
/// <param name="key">The api key / label used for identification</param>
/// <param name="secret">The api secret or private key used for signing</param>
/// <param name="pass">The api pass for the key. Not always needed</param>
/// <param name="credentialType">The type of credentials</param>
public ApiCredentials(string key, string secret, string? pass = null, ApiCredentialsType credentialType = ApiCredentialsType.Hmac)
{
if (string.IsNullOrEmpty(key) || string.IsNullOrEmpty(secret))
throw new ArgumentException("Key and secret can't be null/empty");
CredentialType = credentialType;
Key = key;
Secret = secret;
Pass = pass;
}
/// <summary>
/// Copy the credentials
/// </summary>
/// <returns></returns>
public virtual ApiCredentials Copy()
{
return new ApiCredentials(Key, Secret, Pass, CredentialType);
}
/// <summary>
/// Copy the credentials
/// </summary>
/// <returns></returns>
public virtual ApiCredentials Copy()
{
return new ApiCredentials(Key, Secret, Pass, CredentialType);
}
}

View File

@ -1,21 +1,20 @@
namespace CryptoExchange.Net.Authentication
namespace CryptoExchange.Net.Authentication;
/// <summary>
/// Credentials type
/// </summary>
public enum ApiCredentialsType
{
/// <summary>
/// Credentials type
/// Hmac keys credentials
/// </summary>
public enum ApiCredentialsType
{
/// <summary>
/// Hmac keys credentials
/// </summary>
Hmac,
/// <summary>
/// Rsa keys credentials in xml format
/// </summary>
RsaXml,
/// <summary>
/// Rsa keys credentials in pem/base64 format. Only available for .NetStandard 2.1 and up, use xml format for lower.
/// </summary>
RsaPem
}
Hmac,
/// <summary>
/// Rsa keys credentials in xml format
/// </summary>
RsaXml,
/// <summary>
/// Rsa keys credentials in pem/base64 format. Only available for .NetStandard 2.1 and up, use xml format for lower.
/// </summary>
RsaPem
}

View File

@ -1,473 +1,477 @@
using CryptoExchange.Net.Clients;
using CryptoExchange.Net.Clients;
using CryptoExchange.Net.Converters.SystemTextJson;
using CryptoExchange.Net.Interfaces;
using CryptoExchange.Net.Objects;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Net.Http;
using System.Security.Cryptography;
using System.Text;
namespace CryptoExchange.Net.Authentication
namespace CryptoExchange.Net.Authentication;
/// <summary>
/// Base class for authentication providers
/// </summary>
public abstract class AuthenticationProvider
{
internal IAuthTimeProvider TimeProvider { get; set; } = new AuthTimeProvider();
/// <summary>
/// Base class for authentication providers
/// Provided credentials
/// </summary>
public abstract class AuthenticationProvider
protected internal readonly ApiCredentials _credentials;
/// <summary>
/// Byte representation of the secret
/// </summary>
protected byte[] _sBytes;
/// <summary>
/// Get the API key of the current credentials
/// </summary>
public string ApiKey => _credentials.Key!;
/// <summary>
/// Get the Passphrase of the current credentials
/// </summary>
public string? Pass => _credentials.Pass;
/// <summary>
/// ctor
/// </summary>
/// <param name="credentials"></param>
protected AuthenticationProvider(ApiCredentials credentials)
{
internal IAuthTimeProvider TimeProvider { get; set; } = new AuthTimeProvider();
if (credentials.Key == null || credentials.Secret == null)
throw new ArgumentException("ApiKey/Secret needed");
/// <summary>
/// Provided credentials
/// </summary>
protected internal readonly ApiCredentials _credentials;
/// <summary>
/// Byte representation of the secret
/// </summary>
protected byte[] _sBytes;
/// <summary>
/// Get the API key of the current credentials
/// </summary>
public string ApiKey => _credentials.Key!;
/// <summary>
/// Get the Passphrase of the current credentials
/// </summary>
public string? Pass => _credentials.Pass;
/// <summary>
/// ctor
/// </summary>
/// <param name="credentials"></param>
protected AuthenticationProvider(ApiCredentials credentials)
{
if (credentials.Key == null || credentials.Secret == null)
throw new ArgumentException("ApiKey/Secret needed");
_credentials = credentials;
_sBytes = Encoding.UTF8.GetBytes(credentials.Secret);
}
/// <summary>
/// Authenticate a request
/// </summary>
/// <param name="apiClient">The Api client sending the request</param>
/// <param name="requestConfig">The request configuration</param>
public abstract void ProcessRequest(RestApiClient apiClient, RestRequestConfiguration requestConfig);
/// <summary>
/// SHA256 sign the data and return the bytes
/// </summary>
/// <param name="data"></param>
/// <returns></returns>
protected static byte[] SignSHA256Bytes(string data)
{
using var encryptor = SHA256.Create();
return encryptor.ComputeHash(Encoding.UTF8.GetBytes(data));
}
/// <summary>
/// SHA256 sign the data and return the bytes
/// </summary>
/// <param name="data"></param>
/// <returns></returns>
protected static byte[] SignSHA256Bytes(byte[] data)
{
using var encryptor = SHA256.Create();
return encryptor.ComputeHash(data);
}
/// <summary>
/// SHA256 sign the data and return the hash
/// </summary>
/// <param name="data">Data to sign</param>
/// <param name="outputType">String type</param>
/// <returns></returns>
protected static string SignSHA256(string data, SignOutputType? outputType = null)
{
using var encryptor = SHA256.Create();
var resultBytes = encryptor.ComputeHash(Encoding.UTF8.GetBytes(data));
return outputType == SignOutputType.Base64 ? BytesToBase64String(resultBytes) : BytesToHexString(resultBytes);
}
/// <summary>
/// SHA256 sign the data and return the hash
/// </summary>
/// <param name="data">Data to sign</param>
/// <param name="outputType">String type</param>
/// <returns></returns>
protected static string SignSHA256(byte[] data, SignOutputType? outputType = null)
{
using var encryptor = SHA256.Create();
var resultBytes = encryptor.ComputeHash(data);
return outputType == SignOutputType.Base64 ? BytesToBase64String(resultBytes) : BytesToHexString(resultBytes);
}
/// <summary>
/// SHA384 sign the data and return the hash
/// </summary>
/// <param name="data">Data to sign</param>
/// <param name="outputType">String type</param>
/// <returns></returns>
protected static string SignSHA384(string data, SignOutputType? outputType = null)
{
using var encryptor = SHA384.Create();
var resultBytes = encryptor.ComputeHash(Encoding.UTF8.GetBytes(data));
return outputType == SignOutputType.Base64 ? BytesToBase64String(resultBytes) : BytesToHexString(resultBytes);
}
/// <summary>
/// SHA384 sign the data and return the hash
/// </summary>
/// <param name="data">Data to sign</param>
/// <param name="outputType">String type</param>
/// <returns></returns>
protected static string SignSHA384(byte[] data, SignOutputType? outputType = null)
{
using var encryptor = SHA384.Create();
var resultBytes = encryptor.ComputeHash(data);
return outputType == SignOutputType.Base64 ? BytesToBase64String(resultBytes) : BytesToHexString(resultBytes);
}
/// <summary>
/// SHA384 sign the data and return the hash
/// </summary>
/// <param name="data">Data to sign</param>
/// <returns></returns>
protected static byte[] SignSHA384Bytes(string data)
{
using var encryptor = SHA384.Create();
return encryptor.ComputeHash(Encoding.UTF8.GetBytes(data));
}
/// <summary>
/// SHA384 sign the data and return the hash
/// </summary>
/// <param name="data">Data to sign</param>
/// <returns></returns>
protected static byte[] SignSHA384Bytes(byte[] data)
{
using var encryptor = SHA384.Create();
return encryptor.ComputeHash(data);
}
/// <summary>
/// SHA512 sign the data and return the hash
/// </summary>
/// <param name="data">Data to sign</param>
/// <param name="outputType">String type</param>
/// <returns></returns>
protected static string SignSHA512(string data, SignOutputType? outputType = null)
{
using var encryptor = SHA512.Create();
var resultBytes = encryptor.ComputeHash(Encoding.UTF8.GetBytes(data));
return outputType == SignOutputType.Base64 ? BytesToBase64String(resultBytes) : BytesToHexString(resultBytes);
}
/// <summary>
/// SHA512 sign the data and return the hash
/// </summary>
/// <param name="data">Data to sign</param>
/// <param name="outputType">String type</param>
/// <returns></returns>
protected static string SignSHA512(byte[] data, SignOutputType? outputType = null)
{
using var encryptor = SHA512.Create();
var resultBytes = encryptor.ComputeHash(data);
return outputType == SignOutputType.Base64 ? BytesToBase64String(resultBytes) : BytesToHexString(resultBytes);
}
/// <summary>
/// SHA512 sign the data and return the hash
/// </summary>
/// <param name="data">Data to sign</param>
/// <returns></returns>
protected static byte[] SignSHA512Bytes(string data)
{
using var encryptor = SHA512.Create();
return encryptor.ComputeHash(Encoding.UTF8.GetBytes(data));
}
/// <summary>
/// SHA512 sign the data and return the hash
/// </summary>
/// <param name="data">Data to sign</param>
/// <returns></returns>
protected static byte[] SignSHA512Bytes(byte[] data)
{
using var encryptor = SHA512.Create();
return encryptor.ComputeHash(data);
}
/// <summary>
/// MD5 sign the data and return the hash
/// </summary>
/// <param name="data">Data to sign</param>
/// <param name="outputType">String type</param>
/// <returns></returns>
protected static string SignMD5(string data, SignOutputType? outputType = null)
{
using var encryptor = MD5.Create();
var resultBytes = encryptor.ComputeHash(Encoding.UTF8.GetBytes(data));
return outputType == SignOutputType.Base64 ? BytesToBase64String(resultBytes) : BytesToHexString(resultBytes);
}
/// <summary>
/// MD5 sign the data and return the hash
/// </summary>
/// <param name="data">Data to sign</param>
/// <param name="outputType">String type</param>
/// <returns></returns>
protected static string SignMD5(byte[] data, SignOutputType? outputType = null)
{
using var encryptor = MD5.Create();
var resultBytes = encryptor.ComputeHash(data);
return outputType == SignOutputType.Base64 ? BytesToBase64String(resultBytes) : BytesToHexString(resultBytes);
}
/// <summary>
/// MD5 sign the data and return the hash
/// </summary>
/// <param name="data">Data to sign</param>
/// <returns></returns>
protected static byte[] SignMD5Bytes(string data)
{
using var encryptor = MD5.Create();
return encryptor.ComputeHash(Encoding.UTF8.GetBytes(data));
}
/// <summary>
/// HMACSHA256 sign the data and return the hash
/// </summary>
/// <param name="data">Data to sign</param>
/// <param name="outputType">String type</param>
/// <returns></returns>
protected string SignHMACSHA256(string data, SignOutputType? outputType = null)
=> SignHMACSHA256(Encoding.UTF8.GetBytes(data), outputType);
/// <summary>
/// HMACSHA256 sign the data and return the hash
/// </summary>
/// <param name="data">Data to sign</param>
/// <param name="outputType">String type</param>
/// <returns></returns>
protected string SignHMACSHA256(byte[] data, SignOutputType? outputType = null)
{
using var encryptor = new HMACSHA256(_sBytes);
var resultBytes = encryptor.ComputeHash(data);
return outputType == SignOutputType.Base64 ? BytesToBase64String(resultBytes) : BytesToHexString(resultBytes);
}
/// <summary>
/// HMACSHA384 sign the data and return the hash
/// </summary>
/// <param name="data">Data to sign</param>
/// <param name="outputType">String type</param>
/// <returns></returns>
protected string SignHMACSHA384(string data, SignOutputType? outputType = null)
=> SignHMACSHA384(Encoding.UTF8.GetBytes(data), outputType);
/// <summary>
/// HMACSHA384 sign the data and return the hash
/// </summary>
/// <param name="data">Data to sign</param>
/// <param name="outputType">String type</param>
/// <returns></returns>
protected string SignHMACSHA384(byte[] data, SignOutputType? outputType = null)
{
using var encryptor = new HMACSHA384(_sBytes);
var resultBytes = encryptor.ComputeHash(data);
return outputType == SignOutputType.Base64 ? BytesToBase64String(resultBytes) : BytesToHexString(resultBytes);
}
/// <summary>
/// HMACSHA512 sign the data and return the hash
/// </summary>
/// <param name="data">Data to sign</param>
/// <param name="outputType">String type</param>
/// <returns></returns>
protected string SignHMACSHA512(string data, SignOutputType? outputType = null)
=> SignHMACSHA512(Encoding.UTF8.GetBytes(data), outputType);
/// <summary>
/// HMACSHA512 sign the data and return the hash
/// </summary>
/// <param name="data">Data to sign</param>
/// <param name="outputType">String type</param>
/// <returns></returns>
protected string SignHMACSHA512(byte[] data, SignOutputType? outputType = null)
{
using var encryptor = new HMACSHA512(_sBytes);
var resultBytes = encryptor.ComputeHash(data);
return outputType == SignOutputType.Base64 ? BytesToBase64String(resultBytes) : BytesToHexString(resultBytes);
}
/// <summary>
/// SHA256 sign the data
/// </summary>
/// <param name="data"></param>
/// <param name="outputType"></param>
/// <returns></returns>
protected string SignRSASHA256(byte[] data, SignOutputType? outputType = null)
{
using var rsa = CreateRSA();
using var sha256 = SHA256.Create();
var hash = sha256.ComputeHash(data);
var resultBytes = rsa.SignHash(hash, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
return outputType == SignOutputType.Base64? BytesToBase64String(resultBytes) : BytesToHexString(resultBytes);
}
/// <summary>
/// SHA384 sign the data
/// </summary>
/// <param name="data"></param>
/// <param name="outputType"></param>
/// <returns></returns>
protected string SignRSASHA384(byte[] data, SignOutputType? outputType = null)
{
using var rsa = CreateRSA();
using var sha384 = SHA384.Create();
var hash = sha384.ComputeHash(data);
var resultBytes = rsa.SignHash(hash, HashAlgorithmName.SHA384, RSASignaturePadding.Pkcs1);
return outputType == SignOutputType.Base64 ? BytesToBase64String(resultBytes) : BytesToHexString(resultBytes);
}
/// <summary>
/// SHA512 sign the data
/// </summary>
/// <param name="data"></param>
/// <param name="outputType"></param>
/// <returns></returns>
protected string SignRSASHA512(byte[] data, SignOutputType? outputType = null)
{
using var rsa = CreateRSA();
using var sha512 = SHA512.Create();
var hash = sha512.ComputeHash(data);
var resultBytes = rsa.SignHash(hash, HashAlgorithmName.SHA512, RSASignaturePadding.Pkcs1);
return outputType == SignOutputType.Base64 ? BytesToBase64String(resultBytes) : BytesToHexString(resultBytes);
}
private RSA CreateRSA()
{
var rsa = RSA.Create();
if (_credentials.CredentialType == ApiCredentialsType.RsaPem)
{
#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER
// Read from pem private key
var key = _credentials.Secret!
.Replace("\n", "")
.Replace("-----BEGIN PRIVATE KEY-----", "")
.Replace("-----END PRIVATE KEY-----", "")
.Trim();
rsa.ImportPkcs8PrivateKey(Convert.FromBase64String(
key)
, out _);
#else
throw new Exception("Pem format not supported when running from .NetStandard2.0. Convert the private key to xml format.");
#endif
}
else if (_credentials.CredentialType == ApiCredentialsType.RsaXml)
{
// Read from xml private key format
rsa.FromXmlString(_credentials.Secret!);
}
else
{
throw new Exception("Invalid credentials type");
}
return rsa;
}
/// <summary>
/// Convert byte array to hex string
/// </summary>
/// <param name="buff"></param>
/// <returns></returns>
protected static string BytesToHexString(byte[] buff)
{
#if NET9_0_OR_GREATER
return Convert.ToHexString(buff);
#else
var result = string.Empty;
foreach (var t in buff)
result += t.ToString("X2");
return result;
#endif
}
/// <summary>
/// Convert byte array to base64 string
/// </summary>
/// <param name="buff"></param>
/// <returns></returns>
protected static string BytesToBase64String(byte[] buff)
{
return Convert.ToBase64String(buff);
}
/// <summary>
/// Get current timestamp including the time sync offset from the api client
/// </summary>
/// <param name="apiClient"></param>
/// <returns></returns>
protected DateTime GetTimestamp(RestApiClient apiClient)
{
return TimeProvider.GetTime().Add(apiClient.GetTimeOffset() ?? TimeSpan.Zero)!;
}
/// <summary>
/// Get millisecond timestamp as a string including the time sync offset from the api client
/// </summary>
/// <param name="apiClient"></param>
/// <returns></returns>
protected string GetMillisecondTimestamp(RestApiClient apiClient)
{
return DateTimeConverter.ConvertToMilliseconds(GetTimestamp(apiClient)).Value.ToString(CultureInfo.InvariantCulture);
}
/// <summary>
/// Get millisecond timestamp as a long including the time sync offset from the api client
/// </summary>
/// <param name="apiClient"></param>
/// <returns></returns>
protected long GetMillisecondTimestampLong(RestApiClient apiClient)
{
return DateTimeConverter.ConvertToMilliseconds(GetTimestamp(apiClient)).Value;
}
/// <summary>
/// Return the serialized request body
/// </summary>
/// <param name="serializer"></param>
/// <param name="parameters"></param>
/// <returns></returns>
protected static string GetSerializedBody(IMessageSerializer serializer, IDictionary<string, object> parameters)
{
if (serializer is not IStringMessageSerializer stringSerializer)
throw new InvalidOperationException("Non-string message serializer can't get serialized request body");
if (parameters.Count == 1 && parameters.TryGetValue(Constants.BodyPlaceHolderKey, out object? value))
return stringSerializer.Serialize(value);
else
return stringSerializer.Serialize(parameters);
}
_credentials = credentials;
_sBytes = Encoding.UTF8.GetBytes(credentials.Secret);
}
/// <inheritdoc />
public abstract class AuthenticationProvider<TApiCredentials> : AuthenticationProvider where TApiCredentials : ApiCredentials
{
/// <inheritdoc />
protected new TApiCredentials _credentials => (TApiCredentials)base._credentials;
/// <summary>
/// Authenticate a request
/// </summary>
/// <param name="apiClient">The Api client sending the request</param>
/// <param name="requestConfig">The request configuration</param>
public abstract void ProcessRequest(RestApiClient apiClient, RestRequestConfiguration requestConfig);
/// <summary>
/// ctor
/// </summary>
/// <param name="credentials"></param>
protected AuthenticationProvider(TApiCredentials credentials) : base(credentials)
/// <summary>
/// SHA256 sign the data and return the bytes
/// </summary>
/// <param name="data"></param>
/// <returns></returns>
protected static byte[] SignSHA256Bytes(string data)
{
using var encryptor = SHA256.Create();
return encryptor.ComputeHash(Encoding.UTF8.GetBytes(data));
}
/// <summary>
/// SHA256 sign the data and return the bytes
/// </summary>
/// <param name="data"></param>
/// <returns></returns>
protected static byte[] SignSHA256Bytes(byte[] data)
{
using var encryptor = SHA256.Create();
return encryptor.ComputeHash(data);
}
/// <summary>
/// SHA256 sign the data and return the hash
/// </summary>
/// <param name="data">Data to sign</param>
/// <param name="outputType">String type</param>
/// <returns></returns>
protected static string SignSHA256(string data, SignOutputType? outputType = null)
{
using var encryptor = SHA256.Create();
var resultBytes = encryptor.ComputeHash(Encoding.UTF8.GetBytes(data));
return outputType == SignOutputType.Base64 ? BytesToBase64String(resultBytes) : BytesToHexString(resultBytes);
}
/// <summary>
/// SHA256 sign the data and return the hash
/// </summary>
/// <param name="data">Data to sign</param>
/// <param name="outputType">String type</param>
/// <returns></returns>
protected static string SignSHA256(byte[] data, SignOutputType? outputType = null)
{
using var encryptor = SHA256.Create();
var resultBytes = encryptor.ComputeHash(data);
return outputType == SignOutputType.Base64 ? BytesToBase64String(resultBytes) : BytesToHexString(resultBytes);
}
/// <summary>
/// SHA384 sign the data and return the hash
/// </summary>
/// <param name="data">Data to sign</param>
/// <param name="outputType">String type</param>
/// <returns></returns>
protected static string SignSHA384(string data, SignOutputType? outputType = null)
{
using var encryptor = SHA384.Create();
var resultBytes = encryptor.ComputeHash(Encoding.UTF8.GetBytes(data));
return outputType == SignOutputType.Base64 ? BytesToBase64String(resultBytes) : BytesToHexString(resultBytes);
}
/// <summary>
/// SHA384 sign the data and return the hash
/// </summary>
/// <param name="data">Data to sign</param>
/// <param name="outputType">String type</param>
/// <returns></returns>
protected static string SignSHA384(byte[] data, SignOutputType? outputType = null)
{
using var encryptor = SHA384.Create();
var resultBytes = encryptor.ComputeHash(data);
return outputType == SignOutputType.Base64 ? BytesToBase64String(resultBytes) : BytesToHexString(resultBytes);
}
/// <summary>
/// SHA384 sign the data and return the hash
/// </summary>
/// <param name="data">Data to sign</param>
/// <returns></returns>
protected static byte[] SignSHA384Bytes(string data)
{
using var encryptor = SHA384.Create();
return encryptor.ComputeHash(Encoding.UTF8.GetBytes(data));
}
/// <summary>
/// SHA384 sign the data and return the hash
/// </summary>
/// <param name="data">Data to sign</param>
/// <returns></returns>
protected static byte[] SignSHA384Bytes(byte[] data)
{
using var encryptor = SHA384.Create();
return encryptor.ComputeHash(data);
}
/// <summary>
/// SHA512 sign the data and return the hash
/// </summary>
/// <param name="data">Data to sign</param>
/// <param name="outputType">String type</param>
/// <returns></returns>
protected static string SignSHA512(string data, SignOutputType? outputType = null)
{
using var encryptor = SHA512.Create();
var resultBytes = encryptor.ComputeHash(Encoding.UTF8.GetBytes(data));
return outputType == SignOutputType.Base64 ? BytesToBase64String(resultBytes) : BytesToHexString(resultBytes);
}
/// <summary>
/// SHA512 sign the data and return the hash
/// </summary>
/// <param name="data">Data to sign</param>
/// <param name="outputType">String type</param>
/// <returns></returns>
protected static string SignSHA512(byte[] data, SignOutputType? outputType = null)
{
using var encryptor = SHA512.Create();
var resultBytes = encryptor.ComputeHash(data);
return outputType == SignOutputType.Base64 ? BytesToBase64String(resultBytes) : BytesToHexString(resultBytes);
}
/// <summary>
/// SHA512 sign the data and return the hash
/// </summary>
/// <param name="data">Data to sign</param>
/// <returns></returns>
protected static byte[] SignSHA512Bytes(string data)
{
using var encryptor = SHA512.Create();
return encryptor.ComputeHash(Encoding.UTF8.GetBytes(data));
}
/// <summary>
/// SHA512 sign the data and return the hash
/// </summary>
/// <param name="data">Data to sign</param>
/// <returns></returns>
protected static byte[] SignSHA512Bytes(byte[] data)
{
using var encryptor = SHA512.Create();
return encryptor.ComputeHash(data);
}
/// <summary>
/// MD5 sign the data and return the hash
/// </summary>
/// <param name="data">Data to sign</param>
/// <param name="outputType">String type</param>
/// <returns></returns>
protected static string SignMD5(string data, SignOutputType? outputType = null)
{
#pragma warning disable CA5351
using var encryptor = MD5.Create();
#pragma warning restore CA5351
var resultBytes = encryptor.ComputeHash(Encoding.UTF8.GetBytes(data));
return outputType == SignOutputType.Base64 ? BytesToBase64String(resultBytes) : BytesToHexString(resultBytes);
}
/// <summary>
/// MD5 sign the data and return the hash
/// </summary>
/// <param name="data">Data to sign</param>
/// <param name="outputType">String type</param>
/// <returns></returns>
protected static string SignMD5(byte[] data, SignOutputType? outputType = null)
{
#pragma warning disable CA5351
using var encryptor = MD5.Create();
#pragma warning restore CA5351
var resultBytes = encryptor.ComputeHash(data);
return outputType == SignOutputType.Base64 ? BytesToBase64String(resultBytes) : BytesToHexString(resultBytes);
}
/// <summary>
/// MD5 sign the data and return the hash
/// </summary>
/// <param name="data">Data to sign</param>
/// <returns></returns>
protected static byte[] SignMD5Bytes(string data)
{
#pragma warning disable CA5351
using var encryptor = MD5.Create();
#pragma warning restore CA5351
return encryptor.ComputeHash(Encoding.UTF8.GetBytes(data));
}
/// <summary>
/// HMACSHA256 sign the data and return the hash
/// </summary>
/// <param name="data">Data to sign</param>
/// <param name="outputType">String type</param>
/// <returns></returns>
protected string SignHMACSHA256(string data, SignOutputType? outputType = null)
=> SignHMACSHA256(Encoding.UTF8.GetBytes(data), outputType);
/// <summary>
/// HMACSHA256 sign the data and return the hash
/// </summary>
/// <param name="data">Data to sign</param>
/// <param name="outputType">String type</param>
/// <returns></returns>
protected string SignHMACSHA256(byte[] data, SignOutputType? outputType = null)
{
using var encryptor = new HMACSHA256(_sBytes);
var resultBytes = encryptor.ComputeHash(data);
return outputType == SignOutputType.Base64 ? BytesToBase64String(resultBytes) : BytesToHexString(resultBytes);
}
/// <summary>
/// HMACSHA384 sign the data and return the hash
/// </summary>
/// <param name="data">Data to sign</param>
/// <param name="outputType">String type</param>
/// <returns></returns>
protected string SignHMACSHA384(string data, SignOutputType? outputType = null)
=> SignHMACSHA384(Encoding.UTF8.GetBytes(data), outputType);
/// <summary>
/// HMACSHA384 sign the data and return the hash
/// </summary>
/// <param name="data">Data to sign</param>
/// <param name="outputType">String type</param>
/// <returns></returns>
protected string SignHMACSHA384(byte[] data, SignOutputType? outputType = null)
{
using var encryptor = new HMACSHA384(_sBytes);
var resultBytes = encryptor.ComputeHash(data);
return outputType == SignOutputType.Base64 ? BytesToBase64String(resultBytes) : BytesToHexString(resultBytes);
}
/// <summary>
/// HMACSHA512 sign the data and return the hash
/// </summary>
/// <param name="data">Data to sign</param>
/// <param name="outputType">String type</param>
/// <returns></returns>
protected string SignHMACSHA512(string data, SignOutputType? outputType = null)
=> SignHMACSHA512(Encoding.UTF8.GetBytes(data), outputType);
/// <summary>
/// HMACSHA512 sign the data and return the hash
/// </summary>
/// <param name="data">Data to sign</param>
/// <param name="outputType">String type</param>
/// <returns></returns>
protected string SignHMACSHA512(byte[] data, SignOutputType? outputType = null)
{
using var encryptor = new HMACSHA512(_sBytes);
var resultBytes = encryptor.ComputeHash(data);
return outputType == SignOutputType.Base64 ? BytesToBase64String(resultBytes) : BytesToHexString(resultBytes);
}
/// <summary>
/// SHA256 sign the data
/// </summary>
/// <param name="data"></param>
/// <param name="outputType"></param>
/// <returns></returns>
protected string SignRSASHA256(byte[] data, SignOutputType? outputType = null)
{
using var rsa = CreateRSA();
using var sha256 = SHA256.Create();
var hash = sha256.ComputeHash(data);
var resultBytes = rsa.SignHash(hash, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
return outputType == SignOutputType.Base64? BytesToBase64String(resultBytes) : BytesToHexString(resultBytes);
}
/// <summary>
/// SHA384 sign the data
/// </summary>
/// <param name="data"></param>
/// <param name="outputType"></param>
/// <returns></returns>
protected string SignRSASHA384(byte[] data, SignOutputType? outputType = null)
{
using var rsa = CreateRSA();
using var sha384 = SHA384.Create();
var hash = sha384.ComputeHash(data);
var resultBytes = rsa.SignHash(hash, HashAlgorithmName.SHA384, RSASignaturePadding.Pkcs1);
return outputType == SignOutputType.Base64 ? BytesToBase64String(resultBytes) : BytesToHexString(resultBytes);
}
/// <summary>
/// SHA512 sign the data
/// </summary>
/// <param name="data"></param>
/// <param name="outputType"></param>
/// <returns></returns>
protected string SignRSASHA512(byte[] data, SignOutputType? outputType = null)
{
using var rsa = CreateRSA();
using var sha512 = SHA512.Create();
var hash = sha512.ComputeHash(data);
var resultBytes = rsa.SignHash(hash, HashAlgorithmName.SHA512, RSASignaturePadding.Pkcs1);
return outputType == SignOutputType.Base64 ? BytesToBase64String(resultBytes) : BytesToHexString(resultBytes);
}
private RSA CreateRSA()
{
var rsa = RSA.Create();
if (_credentials.CredentialType == ApiCredentialsType.RsaPem)
{
#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER
// Read from pem private key
var key = _credentials.Secret!
.Replace("\n", "")
.Replace("-----BEGIN PRIVATE KEY-----", "")
.Replace("-----END PRIVATE KEY-----", "")
.Trim();
rsa.ImportPkcs8PrivateKey(Convert.FromBase64String(
key)
, out _);
#else
throw new Exception("Pem format not supported when running from .NetStandard2.0. Convert the private key to xml format.");
#endif
}
else if (_credentials.CredentialType == ApiCredentialsType.RsaXml)
{
// Read from xml private key format
rsa.FromXmlString(_credentials.Secret!);
}
else
{
throw new Exception("Invalid credentials type");
}
return rsa;
}
/// <summary>
/// Convert byte array to hex string
/// </summary>
/// <param name="buff"></param>
/// <returns></returns>
protected static string BytesToHexString(byte[] buff)
{
#if NET9_0_OR_GREATER
return Convert.ToHexString(buff);
#else
var result = string.Empty;
foreach (var t in buff)
result += t.ToString("X2");
return result;
#endif
}
/// <summary>
/// Convert byte array to base64 string
/// </summary>
/// <param name="buff"></param>
/// <returns></returns>
protected static string BytesToBase64String(byte[] buff)
{
return Convert.ToBase64String(buff);
}
/// <summary>
/// Get current timestamp including the time sync offset from the api client
/// </summary>
/// <param name="apiClient"></param>
/// <returns></returns>
protected DateTime GetTimestamp(RestApiClient apiClient)
{
return TimeProvider.GetTime().Add(apiClient.GetTimeOffset() ?? TimeSpan.Zero)!;
}
/// <summary>
/// Get millisecond timestamp as a string including the time sync offset from the api client
/// </summary>
/// <param name="apiClient"></param>
/// <returns></returns>
protected string GetMillisecondTimestamp(RestApiClient apiClient)
{
return DateTimeConverter.ConvertToMilliseconds(GetTimestamp(apiClient)).Value.ToString(CultureInfo.InvariantCulture);
}
/// <summary>
/// Get millisecond timestamp as a long including the time sync offset from the api client
/// </summary>
/// <param name="apiClient"></param>
/// <returns></returns>
protected long GetMillisecondTimestampLong(RestApiClient apiClient)
{
return DateTimeConverter.ConvertToMilliseconds(GetTimestamp(apiClient)).Value;
}
/// <summary>
/// Return the serialized request body
/// </summary>
/// <param name="serializer"></param>
/// <param name="parameters"></param>
/// <returns></returns>
protected static string GetSerializedBody(IMessageSerializer serializer, IDictionary<string, object> parameters)
{
if (serializer is not IStringMessageSerializer stringSerializer)
throw new InvalidOperationException("Non-string message serializer can't get serialized request body");
if (parameters.Count == 1 && parameters.TryGetValue(Constants.BodyPlaceHolderKey, out object? value))
return stringSerializer.Serialize(value);
else
return stringSerializer.Serialize(parameters);
}
}
/// <inheritdoc />
public abstract class AuthenticationProvider<TApiCredentials> : AuthenticationProvider where TApiCredentials : ApiCredentials
{
/// <inheritdoc />
protected new TApiCredentials _credentials => (TApiCredentials)base._credentials;
/// <summary>
/// ctor
/// </summary>
/// <param name="credentials"></param>
protected AuthenticationProvider(TApiCredentials credentials) : base(credentials)
{
}
}

View File

@ -1,17 +1,16 @@
namespace CryptoExchange.Net.Authentication
namespace CryptoExchange.Net.Authentication;
/// <summary>
/// Output string type
/// </summary>
public enum SignOutputType
{
/// <summary>
/// Output string type
/// Hex string
/// </summary>
public enum SignOutputType
{
/// <summary>
/// Hex string
/// </summary>
Hex,
/// <summary>
/// Base64 string
/// </summary>
Base64
}
Hex,
/// <summary>
/// Base64 string
/// </summary>
Base64
}

View File

@ -1,53 +1,52 @@
using System;
using System;
using System.Collections.Concurrent;
using System.Linq;
namespace CryptoExchange.Net.Caching
namespace CryptoExchange.Net.Caching;
internal class MemoryCache
{
internal class MemoryCache
private readonly ConcurrentDictionary<string, CacheItem> _cache = new ConcurrentDictionary<string, CacheItem>();
private readonly object _lock = new object();
/// <summary>
/// Add a new cache entry. Will override an existing entry if it already exists
/// </summary>
/// <param name="key">The key identifier</param>
/// <param name="value">Cache value</param>
public void Add(string key, object value)
{
private readonly ConcurrentDictionary<string, CacheItem> _cache = new ConcurrentDictionary<string, CacheItem>();
private readonly object _lock = new object();
var cacheItem = new CacheItem(DateTime.UtcNow, value);
_cache.AddOrUpdate(key, cacheItem, (key, val1) => cacheItem);
}
/// <summary>
/// Add a new cache entry. Will override an existing entry if it already exists
/// </summary>
/// <param name="key">The key identifier</param>
/// <param name="value">Cache value</param>
public void Add(string key, object value)
/// <summary>
/// Get a cached value
/// </summary>
/// <param name="key">The key identifier</param>
/// <param name="maxAge">The max age of the cached entry</param>
/// <returns>Cached value if it was in cache</returns>
public object? Get(string key, TimeSpan maxAge)
{
foreach (var item in _cache.Where(x => DateTime.UtcNow - x.Value.CacheTime > maxAge).ToList())
_cache.TryRemove(item.Key, out _);
_cache.TryGetValue(key, out CacheItem? value);
if (value == null)
return null;
return value.Value;
}
private class CacheItem
{
public DateTime CacheTime { get; }
public object Value { get; }
public CacheItem(DateTime cacheTime, object value)
{
var cacheItem = new CacheItem(DateTime.UtcNow, value);
_cache.AddOrUpdate(key, cacheItem, (key, val1) => cacheItem);
}
/// <summary>
/// Get a cached value
/// </summary>
/// <param name="key">The key identifier</param>
/// <param name="maxAge">The max age of the cached entry</param>
/// <returns>Cached value if it was in cache</returns>
public object? Get(string key, TimeSpan maxAge)
{
foreach (var item in _cache.Where(x => DateTime.UtcNow - x.Value.CacheTime > maxAge).ToList())
_cache.TryRemove(item.Key, out _);
_cache.TryGetValue(key, out CacheItem? value);
if (value == null)
return null;
return value.Value;
}
private class CacheItem
{
public DateTime CacheTime { get; }
public object Value { get; }
public CacheItem(DateTime cacheTime, object value)
{
CacheTime = cacheTime;
Value = value;
}
CacheTime = cacheTime;
Value = value;
}
}
}

View File

@ -1,134 +1,131 @@
using System;
using System.Collections.Generic;
using CryptoExchange.Net.Authentication;
using CryptoExchange.Net.Interfaces;
using CryptoExchange.Net.Objects;
using CryptoExchange.Net.Objects.Errors;
using CryptoExchange.Net.Objects.Options;
using CryptoExchange.Net.SharedApis;
using Microsoft.Extensions.Logging;
namespace CryptoExchange.Net.Clients
namespace CryptoExchange.Net.Clients;
/// <summary>
/// Base API for all API clients
/// </summary>
public abstract class BaseApiClient : IDisposable, IBaseApiClient
{
/// <summary>
/// Base API for all API clients
/// Logger
/// </summary>
public abstract class BaseApiClient : IDisposable, IBaseApiClient
protected ILogger _logger;
/// <summary>
/// If we are disposing
/// </summary>
protected bool _disposing;
/// <summary>
/// The authentication provider for this API client. (null if no credentials are set)
/// </summary>
public AuthenticationProvider? AuthenticationProvider { get; private set; }
/// <summary>
/// The environment this client communicates to
/// </summary>
public string BaseAddress { get; }
/// <summary>
/// Output the original string data along with the deserialized object
/// </summary>
public bool OutputOriginalData { get; }
/// <inheritdoc />
public bool Authenticated => ApiCredentials != null;
/// <inheritdoc />
public ApiCredentials? ApiCredentials { get; set; }
/// <summary>
/// Api options
/// </summary>
public ApiOptions ApiOptions { get; }
/// <summary>
/// Client Options
/// </summary>
public ExchangeOptions ClientOptions { get; }
/// <summary>
/// Mapping of a response code to known error types
/// </summary>
protected internal virtual ErrorMapping ErrorMapping { get; } = new ErrorMapping([]);
/// <summary>
/// ctor
/// </summary>
/// <param name="logger">Logger</param>
/// <param name="outputOriginalData">Should data from this client include the original data in the call result</param>
/// <param name="baseAddress">Base address for this API client</param>
/// <param name="apiCredentials">Api credentials</param>
/// <param name="clientOptions">Client options</param>
/// <param name="apiOptions">Api options</param>
protected BaseApiClient(ILogger logger, bool outputOriginalData, ApiCredentials? apiCredentials, string baseAddress, ExchangeOptions clientOptions, ApiOptions apiOptions)
{
/// <summary>
/// Logger
/// </summary>
protected ILogger _logger;
_logger = logger;
/// <summary>
/// If we are disposing
/// </summary>
protected bool _disposing;
ClientOptions = clientOptions;
ApiOptions = apiOptions;
OutputOriginalData = outputOriginalData;
BaseAddress = baseAddress;
ApiCredentials = apiCredentials?.Copy();
/// <summary>
/// The authentication provider for this API client. (null if no credentials are set)
/// </summary>
public AuthenticationProvider? AuthenticationProvider { get; private set; }
if (ApiCredentials != null)
AuthenticationProvider = CreateAuthenticationProvider(ApiCredentials);
}
/// <summary>
/// The environment this client communicates to
/// </summary>
public string BaseAddress { get; }
/// <summary>
/// Create an AuthenticationProvider implementation instance based on the provided credentials
/// </summary>
/// <param name="credentials"></param>
/// <returns></returns>
protected abstract AuthenticationProvider CreateAuthenticationProvider(ApiCredentials credentials);
/// <summary>
/// Output the original string data along with the deserialized object
/// </summary>
public bool OutputOriginalData { get; }
/// <inheritdoc />
public abstract string FormatSymbol(string baseAsset, string quoteAsset, TradingMode tradingMode, DateTime? deliverDate = null);
/// <inheritdoc />
public bool Authenticated => ApiCredentials != null;
/// <summary>
/// Get error info for a response code
/// </summary>
public ErrorInfo GetErrorInfo(int code, string? message = null) => GetErrorInfo(code.ToString(), message);
/// <inheritdoc />
public ApiCredentials? ApiCredentials { get; set; }
/// <summary>
/// Get error info for a response code
/// </summary>
public ErrorInfo GetErrorInfo(string code, string? message = null) => ErrorMapping.GetErrorInfo(code.ToString(), message);
/// <summary>
/// Api options
/// </summary>
public ApiOptions ApiOptions { get; }
/// <inheritdoc />
public void SetApiCredentials<T>(T credentials) where T : ApiCredentials
{
ApiCredentials = credentials?.Copy();
if (ApiCredentials != null)
AuthenticationProvider = CreateAuthenticationProvider(ApiCredentials);
}
/// <summary>
/// Client Options
/// </summary>
public ExchangeOptions ClientOptions { get; }
/// <inheritdoc />
public virtual void SetOptions<T>(UpdateOptions<T> options) where T : ApiCredentials
{
ClientOptions.Proxy = options.Proxy;
ClientOptions.RequestTimeout = options.RequestTimeout ?? ClientOptions.RequestTimeout;
/// <summary>
/// Mapping of a response code to known error types
/// </summary>
protected internal virtual ErrorMapping ErrorMapping { get; } = new ErrorMapping([]);
ApiCredentials = options.ApiCredentials?.Copy() ?? ApiCredentials;
if (ApiCredentials != null)
AuthenticationProvider = CreateAuthenticationProvider(ApiCredentials);
}
/// <summary>
/// ctor
/// </summary>
/// <param name="logger">Logger</param>
/// <param name="outputOriginalData">Should data from this client include the original data in the call result</param>
/// <param name="baseAddress">Base address for this API client</param>
/// <param name="apiCredentials">Api credentials</param>
/// <param name="clientOptions">Client options</param>
/// <param name="apiOptions">Api options</param>
protected BaseApiClient(ILogger logger, bool outputOriginalData, ApiCredentials? apiCredentials, string baseAddress, ExchangeOptions clientOptions, ApiOptions apiOptions)
{
_logger = logger;
ClientOptions = clientOptions;
ApiOptions = apiOptions;
OutputOriginalData = outputOriginalData;
BaseAddress = baseAddress;
ApiCredentials = apiCredentials?.Copy();
if (ApiCredentials != null)
AuthenticationProvider = CreateAuthenticationProvider(ApiCredentials);
}
/// <summary>
/// Create an AuthenticationProvider implementation instance based on the provided credentials
/// </summary>
/// <param name="credentials"></param>
/// <returns></returns>
protected abstract AuthenticationProvider CreateAuthenticationProvider(ApiCredentials credentials);
/// <inheritdoc />
public abstract string FormatSymbol(string baseAsset, string quoteAsset, TradingMode tradingMode, DateTime? deliverDate = null);
/// <summary>
/// Get error info for a response code
/// </summary>
public ErrorInfo GetErrorInfo(int code, string? message = null) => GetErrorInfo(code.ToString(), message);
/// <summary>
/// Get error info for a response code
/// </summary>
public ErrorInfo GetErrorInfo(string code, string? message = null) => ErrorMapping.GetErrorInfo(code.ToString(), message);
/// <inheritdoc />
public void SetApiCredentials<T>(T credentials) where T : ApiCredentials
{
ApiCredentials = credentials?.Copy();
if (ApiCredentials != null)
AuthenticationProvider = CreateAuthenticationProvider(ApiCredentials);
}
/// <inheritdoc />
public virtual void SetOptions<T>(UpdateOptions<T> options) where T : ApiCredentials
{
ClientOptions.Proxy = options.Proxy;
ClientOptions.RequestTimeout = options.RequestTimeout ?? ClientOptions.RequestTimeout;
ApiCredentials = options.ApiCredentials?.Copy() ?? ApiCredentials;
if (ApiCredentials != null)
AuthenticationProvider = CreateAuthenticationProvider(ApiCredentials);
}
/// <summary>
/// Dispose
/// </summary>
public virtual void Dispose()
{
_disposing = true;
}
/// <summary>
/// Dispose
/// </summary>
public virtual void Dispose()
{
_disposing = true;
}
}

View File

@ -1,130 +1,128 @@
using CryptoExchange.Net.Authentication;
using CryptoExchange.Net.Authentication;
using CryptoExchange.Net.Objects.Options;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using System;
using System.Collections.Generic;
namespace CryptoExchange.Net.Clients
namespace CryptoExchange.Net.Clients;
/// <summary>
/// The base for all clients, websocket client and rest client
/// </summary>
public abstract class BaseClient : IDisposable
{
/// <summary>
/// The base for all clients, websocket client and rest client
/// Version of the CryptoExchange.Net base library
/// </summary>
public abstract class BaseClient : IDisposable
{
/// <summary>
/// Version of the CryptoExchange.Net base library
/// </summary>
public Version CryptoExchangeLibVersion { get; } = typeof(BaseClient).Assembly.GetName().Version!;
public Version CryptoExchangeLibVersion { get; } = typeof(BaseClient).Assembly.GetName().Version!;
/// <summary>
/// Version of the client implementation
/// </summary>
public Version ExchangeLibVersion
{
get
/// <summary>
/// Version of the client implementation
/// </summary>
public Version ExchangeLibVersion
{
get
{
lock(_versionLock)
{
lock(_versionLock)
{
if (_exchangeVersion == null)
_exchangeVersion = GetType().Assembly.GetName().Version!;
if (_exchangeVersion == null)
_exchangeVersion = GetType().Assembly.GetName().Version!;
return _exchangeVersion;
}
return _exchangeVersion;
}
}
}
/// <summary>
/// The name of the API the client is for
/// </summary>
public string Exchange { get; }
/// <summary>
/// The name of the API the client is for
/// </summary>
public string Exchange { get; }
/// <summary>
/// Api clients in this client
/// </summary>
internal List<BaseApiClient> ApiClients { get; } = new List<BaseApiClient>();
/// <summary>
/// Api clients in this client
/// </summary>
internal List<BaseApiClient> ApiClients { get; } = new List<BaseApiClient>();
/// <summary>
/// The log object
/// </summary>
protected internal ILogger _logger;
/// <summary>
/// The log object
/// </summary>
protected internal ILogger _logger;
private readonly object _versionLock = new object();
private Version _exchangeVersion;
private readonly object _versionLock = new object();
private Version _exchangeVersion;
/// <summary>
/// Provided client options
/// </summary>
public ExchangeOptions ClientOptions { get; private set; }
/// <summary>
/// Provided client options
/// </summary>
public ExchangeOptions ClientOptions { get; private set; }
/// <summary>
/// ctor
/// </summary>
/// <param name="logger">Logger</param>
/// <param name="exchange">The name of the exchange this client is for</param>
/// <summary>
/// ctor
/// </summary>
/// <param name="logger">Logger</param>
/// <param name="exchange">The name of the exchange this client is for</param>
#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
protected BaseClient(ILoggerFactory? logger, string exchange)
protected BaseClient(ILoggerFactory? logger, string exchange)
#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
{
Exchange = exchange;
}
{
Exchange = exchange;
}
/// <summary>
/// Initialize the client with the specified options
/// </summary>
/// <param name="options"></param>
/// <exception cref="ArgumentNullException"></exception>
protected virtual void Initialize(ExchangeOptions options)
{
if (options == null)
throw new ArgumentNullException(nameof(options));
/// <summary>
/// Initialize the client with the specified options
/// </summary>
/// <param name="options"></param>
/// <exception cref="ArgumentNullException"></exception>
protected virtual void Initialize(ExchangeOptions options)
{
if (options == null)
throw new ArgumentNullException(nameof(options));
ClientOptions = options;
_logger.Log(LogLevel.Trace, $"Client configuration: {options}, CryptoExchange.Net: v{CryptoExchangeLibVersion}, {Exchange}.Net: v{ExchangeLibVersion}");
}
ClientOptions = options;
_logger.Log(LogLevel.Trace, "Client configuration: {Options}, CryptoExchange.Net: v{CryptoExchangeVersion}, {Exchange}.Net: v{ExchangeVersion}", options, CryptoExchangeLibVersion, Exchange, ExchangeLibVersion);
}
/// <summary>
/// Set the API credentials for this client. All Api clients in this client will use the new credentials, regardless of earlier set options.
/// </summary>
/// <param name="credentials">The credentials to set</param>
protected virtual void SetApiCredentials<T>(T credentials) where T : ApiCredentials
{
foreach (var apiClient in ApiClients)
apiClient.SetApiCredentials(credentials);
}
/// <summary>
/// Set the API credentials for this client. All Api clients in this client will use the new credentials, regardless of earlier set options.
/// </summary>
/// <param name="credentials">The credentials to set</param>
protected virtual void SetApiCredentials<T>(T credentials) where T : ApiCredentials
{
foreach (var apiClient in ApiClients)
apiClient.SetApiCredentials(credentials);
}
/// <summary>
/// Register an API client
/// </summary>
/// <param name="apiClient">The client</param>
protected T AddApiClient<T>(T apiClient) where T : BaseApiClient
{
if (ClientOptions == null)
throw new InvalidOperationException("Client should have called Initialize before adding API clients");
/// <summary>
/// Register an API client
/// </summary>
/// <param name="apiClient">The client</param>
protected T AddApiClient<T>(T apiClient) where T : BaseApiClient
{
if (ClientOptions == null)
throw new InvalidOperationException("Client should have called Initialize before adding API clients");
_logger.Log(LogLevel.Trace, $" {apiClient.GetType().Name}, base address: {apiClient.BaseAddress}");
ApiClients.Add(apiClient);
return apiClient;
}
_logger.Log(LogLevel.Trace, " {ApiClient}, base address: {BaseAddress}", apiClient.GetType().Name, apiClient.BaseAddress);
ApiClients.Add(apiClient);
return apiClient;
}
/// <summary>
/// Apply the options delegate to a new options instance
/// </summary>
protected static T ApplyOptionsDelegate<T>(Action<T>? del) where T: new()
{
var opts = new T();
del?.Invoke(opts);
return opts;
}
/// <summary>
/// Apply the options delegate to a new options instance
/// </summary>
protected static T ApplyOptionsDelegate<T>(Action<T>? del) where T: new()
{
var opts = new T();
del?.Invoke(opts);
return opts;
}
/// <summary>
/// Dispose
/// </summary>
public virtual void Dispose()
{
_logger.Log(LogLevel.Debug, "Disposing client");
foreach (var client in ApiClients)
client.Dispose();
}
/// <summary>
/// Dispose
/// </summary>
public virtual void Dispose()
{
_logger.Log(LogLevel.Debug, "Disposing client");
foreach (var client in ApiClients)
client.Dispose();
}
}

View File

@ -3,24 +3,23 @@ using CryptoExchange.Net.Interfaces;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
namespace CryptoExchange.Net.Clients
{
/// <summary>
/// Base rest client
/// </summary>
public abstract class BaseRestClient : BaseClient, IRestClient
{
/// <inheritdoc />
public int TotalRequestsMade => ApiClients.OfType<RestApiClient>().Sum(s => s.TotalRequestsMade);
namespace CryptoExchange.Net.Clients;
/// <summary>
/// ctor
/// </summary>
/// <param name="loggerFactory">Logger factory</param>
/// <param name="name">The name of the API this client is for</param>
protected BaseRestClient(ILoggerFactory? loggerFactory, string name) : base(loggerFactory, name)
{
_logger = loggerFactory?.CreateLogger(name + ".RestClient") ?? NullLoggerFactory.Instance.CreateLogger(name);
}
/// <summary>
/// Base rest client
/// </summary>
public abstract class BaseRestClient : BaseClient, IRestClient
{
/// <inheritdoc />
public int TotalRequestsMade => ApiClients.OfType<RestApiClient>().Sum(s => s.TotalRequestsMade);
/// <summary>
/// ctor
/// </summary>
/// <param name="loggerFactory">Logger factory</param>
/// <param name="name">The name of the API this client is for</param>
protected BaseRestClient(ILoggerFactory? loggerFactory, string name) : base(loggerFactory, name)
{
_logger = loggerFactory?.CreateLogger(name + ".RestClient") ?? NullLoggerFactory.Instance.CreateLogger(name);
}
}

View File

@ -1,132 +1,130 @@
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Linq;
using CryptoExchange.Net.Interfaces;
using CryptoExchange.Net.Logging.Extensions;
using CryptoExchange.Net.Objects.Sockets;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
namespace CryptoExchange.Net.Clients
namespace CryptoExchange.Net.Clients;
/// <summary>
/// Base for socket client implementations
/// </summary>
public abstract class BaseSocketClient : BaseClient, ISocketClient
{
#region fields
/// <summary>
/// Base for socket client implementations
/// If client is disposing
/// </summary>
public abstract class BaseSocketClient : BaseClient, ISocketClient
protected bool _disposing;
/// <inheritdoc />
public int CurrentConnections => ApiClients.OfType<SocketApiClient>().Sum(c => c.CurrentConnections);
/// <inheritdoc />
public int CurrentSubscriptions => ApiClients.OfType<SocketApiClient>().Sum(s => s.CurrentSubscriptions);
/// <inheritdoc />
public double IncomingKbps => ApiClients.OfType<SocketApiClient>().Sum(s => s.IncomingKbps);
#endregion
/// <summary>
/// ctor
/// </summary>
/// <param name="loggerFactory">Logger factory</param>
/// <param name="name">The name of the exchange this client is for</param>
protected BaseSocketClient(ILoggerFactory? loggerFactory, string name) : base(loggerFactory, name)
{
#region fields
_logger = loggerFactory?.CreateLogger(name + ".SocketClient") ?? NullLoggerFactory.Instance.CreateLogger(name);
}
/// <summary>
/// If client is disposing
/// </summary>
protected bool _disposing;
/// <inheritdoc />
public int CurrentConnections => ApiClients.OfType<SocketApiClient>().Sum(c => c.CurrentConnections);
/// <inheritdoc />
public int CurrentSubscriptions => ApiClients.OfType<SocketApiClient>().Sum(s => s.CurrentSubscriptions);
/// <inheritdoc />
public double IncomingKbps => ApiClients.OfType<SocketApiClient>().Sum(s => s.IncomingKbps);
#endregion
/// <summary>
/// ctor
/// </summary>
/// <param name="loggerFactory">Logger factory</param>
/// <param name="name">The name of the exchange this client is for</param>
protected BaseSocketClient(ILoggerFactory? loggerFactory, string name) : base(loggerFactory, name)
/// <summary>
/// Unsubscribe an update subscription
/// </summary>
/// <param name="subscriptionId">The id of the subscription to unsubscribe</param>
/// <returns></returns>
public virtual async Task UnsubscribeAsync(int subscriptionId)
{
foreach (var socket in ApiClients.OfType<SocketApiClient>())
{
_logger = loggerFactory?.CreateLogger(name + ".SocketClient") ?? NullLoggerFactory.Instance.CreateLogger(name);
}
/// <summary>
/// Unsubscribe an update subscription
/// </summary>
/// <param name="subscriptionId">The id of the subscription to unsubscribe</param>
/// <returns></returns>
public virtual async Task UnsubscribeAsync(int subscriptionId)
{
foreach (var socket in ApiClients.OfType<SocketApiClient>())
{
var result = await socket.UnsubscribeAsync(subscriptionId).ConfigureAwait(false);
if (result)
break;
}
}
/// <summary>
/// Unsubscribe an update subscription
/// </summary>
/// <param name="subscription">The subscription to unsubscribe</param>
/// <returns></returns>
public virtual async Task UnsubscribeAsync(UpdateSubscription subscription)
{
if (subscription == null)
throw new ArgumentNullException(nameof(subscription));
_logger.UnsubscribingSubscription(subscription.SocketId, subscription.Id);
await subscription.CloseAsync().ConfigureAwait(false);
}
/// <summary>
/// Unsubscribe all subscriptions
/// </summary>
/// <returns></returns>
public virtual async Task UnsubscribeAllAsync()
{
var tasks = new List<Task>();
foreach (var client in ApiClients.OfType<SocketApiClient>())
tasks.Add(client.UnsubscribeAllAsync());
await Task.WhenAll(tasks.ToArray()).ConfigureAwait(false);
}
/// <summary>
/// Reconnect all connections
/// </summary>
/// <returns></returns>
public virtual async Task ReconnectAsync()
{
_logger.ReconnectingAllConnections(CurrentConnections);
var tasks = new List<Task>();
foreach (var client in ApiClients.OfType<SocketApiClient>())
{
tasks.Add(client.ReconnectAsync());
}
await Task.WhenAll(tasks.ToArray()).ConfigureAwait(false);
}
/// <summary>
/// Log the current state of connections and subscriptions
/// </summary>
public string GetSubscriptionsState()
{
var result = new StringBuilder();
foreach (var client in ApiClients.OfType<SocketApiClient>().Where(c => c.CurrentSubscriptions > 0))
{
result.AppendLine(client.GetSubscriptionsState());
}
return result.ToString();
}
/// <summary>
/// Returns the state of all socket api clients
/// </summary>
/// <returns></returns>
public List<SocketApiClient.SocketApiClientState> GetSocketApiClientStates()
{
var result = new List<SocketApiClient.SocketApiClientState>();
foreach (var client in ApiClients.OfType<SocketApiClient>())
{
result.Add(client.GetState());
}
return result;
var result = await socket.UnsubscribeAsync(subscriptionId).ConfigureAwait(false);
if (result)
break;
}
}
/// <summary>
/// Unsubscribe an update subscription
/// </summary>
/// <param name="subscription">The subscription to unsubscribe</param>
/// <returns></returns>
public virtual async Task UnsubscribeAsync(UpdateSubscription subscription)
{
if (subscription == null)
throw new ArgumentNullException(nameof(subscription));
_logger.UnsubscribingSubscription(subscription.SocketId, subscription.Id);
await subscription.CloseAsync().ConfigureAwait(false);
}
/// <summary>
/// Unsubscribe all subscriptions
/// </summary>
/// <returns></returns>
public virtual async Task UnsubscribeAllAsync()
{
var tasks = new List<Task>();
foreach (var client in ApiClients.OfType<SocketApiClient>())
tasks.Add(client.UnsubscribeAllAsync());
await Task.WhenAll(tasks.ToArray()).ConfigureAwait(false);
}
/// <summary>
/// Reconnect all connections
/// </summary>
/// <returns></returns>
public virtual async Task ReconnectAsync()
{
_logger.ReconnectingAllConnections(CurrentConnections);
var tasks = new List<Task>();
foreach (var client in ApiClients.OfType<SocketApiClient>())
{
tasks.Add(client.ReconnectAsync());
}
await Task.WhenAll(tasks.ToArray()).ConfigureAwait(false);
}
/// <summary>
/// Log the current state of connections and subscriptions
/// </summary>
public string GetSubscriptionsState()
{
var result = new StringBuilder();
foreach (var client in ApiClients.OfType<SocketApiClient>().Where(c => c.CurrentSubscriptions > 0))
{
result.AppendLine(client.GetSubscriptionsState());
}
return result.ToString();
}
/// <summary>
/// Returns the state of all socket api clients
/// </summary>
/// <returns></returns>
public List<SocketApiClient.SocketApiClientState> GetSocketApiClientStates()
{
var result = new List<SocketApiClient.SocketApiClientState>();
foreach (var client in ApiClients.OfType<SocketApiClient>())
{
result.Add(client.GetState());
}
return result;
}
}

View File

@ -1,67 +1,66 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Collections.Generic;
namespace CryptoExchange.Net.Clients
namespace CryptoExchange.Net.Clients;
/// <summary>
/// Base crypto client
/// </summary>
public class CryptoBaseClient : IDisposable
{
private readonly Dictionary<Type, object> _serviceCache = new Dictionary<Type, object>();
/// <summary>
/// Base crypto client
/// Service provider
/// </summary>
public class CryptoBaseClient : IDisposable
protected readonly IServiceProvider? _serviceProvider;
/// <summary>
/// ctor
/// </summary>
public CryptoBaseClient() { }
/// <summary>
/// ctor
/// </summary>
/// <param name="serviceProvider"></param>
public CryptoBaseClient(IServiceProvider serviceProvider)
{
private readonly Dictionary<Type, object> _serviceCache = new Dictionary<Type, object>();
_serviceProvider = serviceProvider;
_serviceCache = new Dictionary<Type, object>();
}
/// <summary>
/// Service provider
/// </summary>
protected readonly IServiceProvider? _serviceProvider;
/// <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;
/// <summary>
/// ctor
/// </summary>
public CryptoBaseClient() { }
/// <summary>
/// ctor
/// </summary>
/// <param name="serviceProvider"></param>
public CryptoBaseClient(IServiceProvider serviceProvider)
if (_serviceProvider == null)
{
_serviceProvider = serviceProvider;
_serviceCache = new Dictionary<Type, object>();
// Create with default options
var createResult = createFunc();
_serviceCache.Add(typeof(T), createResult!);
return createResult;
}
/// <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;
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;
}
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();
}
/// <summary>
/// Dispose
/// </summary>
public void Dispose()
{
_serviceCache.Clear();
}
}

View File

@ -1,27 +1,23 @@
using CryptoExchange.Net.Interfaces;
using Microsoft.Extensions.DependencyInjection;
using CryptoExchange.Net.Interfaces;
using System;
using System.Collections.Generic;
using System.Linq;
namespace CryptoExchange.Net.Clients
namespace CryptoExchange.Net.Clients;
/// <inheritdoc />
public class CryptoRestClient : CryptoBaseClient, ICryptoRestClient
{
/// <inheritdoc />
public class CryptoRestClient : CryptoBaseClient, ICryptoRestClient
/// <summary>
/// ctor
/// </summary>
public CryptoRestClient()
{
/// <summary>
/// ctor
/// </summary>
public CryptoRestClient()
{
}
}
/// <summary>
/// ctor
/// </summary>
/// <param name="serviceProvider"></param>
public CryptoRestClient(IServiceProvider serviceProvider) : base(serviceProvider)
{
}
/// <summary>
/// ctor
/// </summary>
/// <param name="serviceProvider"></param>
public CryptoRestClient(IServiceProvider serviceProvider) : base(serviceProvider)
{
}
}

View File

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

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,25 +1,24 @@
using System;
using System;
namespace CryptoExchange.Net.Converters
namespace CryptoExchange.Net.Converters;
/// <summary>
/// Mark property as an index in the array
/// </summary>
[AttributeUsage(AttributeTargets.Property)]
public class ArrayPropertyAttribute : Attribute
{
/// <summary>
/// Mark property as an index in the array
/// The index in the array
/// </summary>
[AttributeUsage(AttributeTargets.Property)]
public class ArrayPropertyAttribute : Attribute
{
/// <summary>
/// The index in the array
/// </summary>
public int Index { get; }
public int Index { get; }
/// <summary>
/// ctor
/// </summary>
/// <param name="index"></param>
public ArrayPropertyAttribute(int index)
{
Index = index;
}
/// <summary>
/// ctor
/// </summary>
/// <param name="index"></param>
public ArrayPropertyAttribute(int index)
{
Index = index;
}
}

View File

@ -1,31 +1,28 @@
using System;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Text;
using System.Text.Json.Serialization;
namespace CryptoExchange.Net.Converters
namespace CryptoExchange.Net.Converters;
/// <summary>
/// Caching for JsonSerializerContext instances
/// </summary>
public static class JsonSerializerContextCache
{
private static ConcurrentDictionary<Type, JsonSerializerContext> _cache = new ConcurrentDictionary<Type, JsonSerializerContext>();
/// <summary>
/// Caching for JsonSerializerContext instances
/// Get the instance of the provided type T. It will be created if it doesn't exist yet.
/// </summary>
public static class JsonSerializerContextCache
/// <typeparam name="T">Implementation type of the JsonSerializerContext</typeparam>
public static JsonSerializerContext GetOrCreate<T>() where T: JsonSerializerContext, new()
{
private static ConcurrentDictionary<Type, JsonSerializerContext> _cache = new ConcurrentDictionary<Type, JsonSerializerContext>();
var contextType = typeof(T);
if (_cache.TryGetValue(contextType, out var context))
return context;
/// <summary>
/// Get the instance of the provided type T. It will be created if it doesn't exist yet.
/// </summary>
/// <typeparam name="T">Implementation type of the JsonSerializerContext</typeparam>
public static JsonSerializerContext GetOrCreate<T>() where T: JsonSerializerContext, new()
{
var contextType = typeof(T);
if (_cache.TryGetValue(contextType, out var context))
return context;
var instance = new T();
_cache[contextType] = instance;
return instance;
}
var instance = new T();
_cache[contextType] = instance;
return instance;
}
}

View File

@ -1,49 +1,48 @@
namespace CryptoExchange.Net.Converters.MessageParsing
namespace CryptoExchange.Net.Converters.MessageParsing;
/// <summary>
/// Node accessor
/// </summary>
public readonly struct NodeAccessor
{
/// <summary>
/// Node accessor
/// Index
/// </summary>
public readonly struct NodeAccessor
public int? Index { get; }
/// <summary>
/// Property name
/// </summary>
public string? Property { get; }
/// <summary>
/// Type (0 = int, 1 = string, 2 = prop name)
/// </summary>
public int Type { get; }
private NodeAccessor(int? index, string? property, int type)
{
/// <summary>
/// Index
/// </summary>
public int? Index { get; }
/// <summary>
/// Property name
/// </summary>
public string? Property { get; }
/// <summary>
/// Type (0 = int, 1 = string, 2 = prop name)
/// </summary>
public int Type { get; }
private NodeAccessor(int? index, string? property, int type)
{
Index = index;
Property = property;
Type = type;
}
/// <summary>
/// Create an int node accessor
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
public static NodeAccessor Int(int value) { return new NodeAccessor(value, null, 0); }
/// <summary>
/// Create a string node accessor
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
public static NodeAccessor String(string value) { return new NodeAccessor(null, value, 1); }
/// <summary>
/// Create a property name node accessor
/// </summary>
/// <returns></returns>
public static NodeAccessor PropertyName() { return new NodeAccessor(null, null, 2); }
Index = index;
Property = property;
Type = type;
}
/// <summary>
/// Create an int node accessor
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
public static NodeAccessor Int(int value) { return new NodeAccessor(value, null, 0); }
/// <summary>
/// Create a string node accessor
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
public static NodeAccessor String(string value) { return new NodeAccessor(null, value, 1); }
/// <summary>
/// Create a property name node accessor
/// </summary>
/// <returns></returns>
public static NodeAccessor PropertyName() { return new NodeAccessor(null, null, 2); }
}

View File

@ -1,50 +1,49 @@
using System.Collections;
using System.Collections;
using System.Collections.Generic;
namespace CryptoExchange.Net.Converters.MessageParsing
namespace CryptoExchange.Net.Converters.MessageParsing;
/// <summary>
/// Message access definition
/// </summary>
public readonly struct MessagePath : IEnumerable<NodeAccessor>
{
/// <summary>
/// Message access definition
/// </summary>
public readonly struct MessagePath : IEnumerable<NodeAccessor>
private readonly List<NodeAccessor> _path;
internal void Add(NodeAccessor node)
{
private readonly List<NodeAccessor> _path;
_path.Add(node);
}
internal void Add(NodeAccessor node)
{
_path.Add(node);
}
/// <summary>
/// ctor
/// </summary>
public MessagePath()
{
_path = new List<NodeAccessor>();
}
/// <summary>
/// ctor
/// </summary>
public MessagePath()
{
_path = new List<NodeAccessor>();
}
/// <summary>
/// Create a new message path
/// </summary>
/// <returns></returns>
public static MessagePath Get()
{
return new MessagePath();
}
/// <summary>
/// Create a new message path
/// </summary>
/// <returns></returns>
public static MessagePath Get()
{
return new MessagePath();
}
/// <summary>
/// IEnumerable implementation
/// </summary>
/// <returns></returns>
public IEnumerator<NodeAccessor> GetEnumerator()
{
for (var i = 0; i < _path.Count; i++)
yield return _path[i];
}
/// <summary>
/// IEnumerable implementation
/// </summary>
/// <returns></returns>
public IEnumerator<NodeAccessor> GetEnumerator()
{
for (var i = 0; i < _path.Count; i++)
yield return _path[i];
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}

View File

@ -1,43 +1,42 @@
namespace CryptoExchange.Net.Converters.MessageParsing
namespace CryptoExchange.Net.Converters.MessageParsing;
/// <summary>
/// Message path extension methods
/// </summary>
public static class MessagePathExtension
{
/// <summary>
/// Message path extension methods
/// Add a string node accessor
/// </summary>
public static class MessagePathExtension
/// <param name="path"></param>
/// <param name="propName"></param>
/// <returns></returns>
public static MessagePath Property(this MessagePath path, string propName)
{
/// <summary>
/// Add a string node accessor
/// </summary>
/// <param name="path"></param>
/// <param name="propName"></param>
/// <returns></returns>
public static MessagePath Property(this MessagePath path, string propName)
{
path.Add(NodeAccessor.String(propName));
return path;
}
path.Add(NodeAccessor.String(propName));
return path;
}
/// <summary>
/// Add a property name node accessor
/// </summary>
/// <param name="path"></param>
/// <returns></returns>
public static MessagePath PropertyName(this MessagePath path)
{
path.Add(NodeAccessor.PropertyName());
return path;
}
/// <summary>
/// Add a property name node accessor
/// </summary>
/// <param name="path"></param>
/// <returns></returns>
public static MessagePath PropertyName(this MessagePath path)
{
path.Add(NodeAccessor.PropertyName());
return path;
}
/// <summary>
/// Add a int node accessor
/// </summary>
/// <param name="path"></param>
/// <param name="index"></param>
/// <returns></returns>
public static MessagePath Index(this MessagePath path, int index)
{
path.Add(NodeAccessor.Int(index));
return path;
}
/// <summary>
/// Add a int node accessor
/// </summary>
/// <param name="path"></param>
/// <param name="index"></param>
/// <returns></returns>
public static MessagePath Index(this MessagePath path, int index)
{
path.Add(NodeAccessor.Int(index));
return path;
}
}

View File

@ -1,21 +1,20 @@
namespace CryptoExchange.Net.Converters.MessageParsing
namespace CryptoExchange.Net.Converters.MessageParsing;
/// <summary>
/// Message node type
/// </summary>
public enum NodeType
{
/// <summary>
/// Message node type
/// Array node
/// </summary>
public enum NodeType
{
/// <summary>
/// Array node
/// </summary>
Array,
/// <summary>
/// Object node
/// </summary>
Object,
/// <summary>
/// Value node
/// </summary>
Value
}
Array,
/// <summary>
/// Object node
/// </summary>
Object,
/// <summary>
/// Value node
/// </summary>
Value
}

View File

@ -1,234 +1,232 @@
using System;
using System.Collections.Concurrent;
using System;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Text.Json.Serialization;
using System.Text.Json;
using CryptoExchange.Net.Attributes;
using System.Collections.Generic;
#if NET5_0_OR_GREATER
using System.Diagnostics.CodeAnalysis;
#endif
using System.Threading;
using System.Diagnostics;
namespace CryptoExchange.Net.Converters.SystemTextJson
{
/// <summary>
/// Converter for arrays to objects. Can deserialize data like [0.1, 0.2, "test"] to an object. Mapping is done by marking the class with [JsonConverter(typeof(ArrayConverter))] and the properties
/// with [ArrayProperty(x)] where x is the index of the property in the array
/// </summary>
namespace CryptoExchange.Net.Converters.SystemTextJson;
/// <summary>
/// Converter for arrays to objects. Can deserialize data like [0.1, 0.2, "test"] to an object. Mapping is done by marking the class with [JsonConverter(typeof(ArrayConverter))] and the properties
/// with [ArrayProperty(x)] where x is the index of the property in the array
/// </summary>
#if NET5_0_OR_GREATER
public class ArrayConverter<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] T> : JsonConverter<T> where T : new()
public class ArrayConverter<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] T> : JsonConverter<T> where T : new()
#else
public class ArrayConverter<T> : JsonConverter<T> where T : new()
public class ArrayConverter<T> : JsonConverter<T> where T : new()
#endif
{
private static readonly Lazy<List<ArrayPropertyInfo>> _typePropertyInfo = new Lazy<List<ArrayPropertyInfo>>(CacheTypeAttributes, LazyThreadSafetyMode.PublicationOnly);
/// <inheritdoc />
{
private static readonly Lazy<List<ArrayPropertyInfo>> _typePropertyInfo = new Lazy<List<ArrayPropertyInfo>>(CacheTypeAttributes, LazyThreadSafetyMode.PublicationOnly);
/// <inheritdoc />
#if NET5_0_OR_GREATER
[UnconditionalSuppressMessage("AssemblyLoadTrimming", "IL3050:RequiresUnreferencedCode", Justification = "JsonSerializerOptions provided here has TypeInfoResolver set")]
[UnconditionalSuppressMessage("AssemblyLoadTrimming", "IL2026:RequiresUnreferencedCode", Justification = "JsonSerializerOptions provided here has TypeInfoResolver set")]
[UnconditionalSuppressMessage("AssemblyLoadTrimming", "IL3050:RequiresUnreferencedCode", Justification = "JsonSerializerOptions provided here has TypeInfoResolver set")]
[UnconditionalSuppressMessage("AssemblyLoadTrimming", "IL2026:RequiresUnreferencedCode", Justification = "JsonSerializerOptions provided here has TypeInfoResolver set")]
#endif
public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options)
public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options)
{
if (value == null)
{
if (value == null)
writer.WriteNullValue();
return;
}
writer.WriteStartArray();
var ordered = _typePropertyInfo.Value.Where(x => x.ArrayProperty != null).OrderBy(p => p.ArrayProperty.Index);
var last = -1;
foreach (var prop in ordered)
{
if (prop.ArrayProperty.Index == last)
continue;
while (prop.ArrayProperty.Index != last + 1)
{
writer.WriteNullValue();
return;
last += 1;
}
writer.WriteStartArray();
last = prop.ArrayProperty.Index;
var ordered = _typePropertyInfo.Value.Where(x => x.ArrayProperty != null).OrderBy(p => p.ArrayProperty.Index);
var last = -1;
foreach (var prop in ordered)
var objValue = prop.PropertyInfo.GetValue(value);
if (objValue == null)
{
if (prop.ArrayProperty.Index == last)
continue;
while (prop.ArrayProperty.Index != last + 1)
{
writer.WriteNullValue();
last += 1;
}
last = prop.ArrayProperty.Index;
var objValue = prop.PropertyInfo.GetValue(value);
if (objValue == null)
{
writer.WriteNullValue();
continue;
}
JsonSerializerOptions? typeOptions = null;
if (prop.JsonConverter != null)
{
typeOptions = new JsonSerializerOptions
{
NumberHandling = JsonNumberHandling.AllowReadingFromString | JsonNumberHandling.AllowNamedFloatingPointLiterals,
PropertyNameCaseInsensitive = false,
TypeInfoResolver = options.TypeInfoResolver,
};
typeOptions.Converters.Add(prop.JsonConverter);
}
if (prop.JsonConverter == null && IsSimple(prop.PropertyInfo.PropertyType))
{
if (prop.TargetType == typeof(string))
writer.WriteStringValue(Convert.ToString(objValue, CultureInfo.InvariantCulture));
else if (prop.TargetType == typeof(bool))
writer.WriteBooleanValue((bool)objValue);
else
writer.WriteRawValue(Convert.ToString(objValue, CultureInfo.InvariantCulture)!);
}
else
{
JsonSerializer.Serialize(writer, objValue, typeOptions ?? options);
}
writer.WriteNullValue();
continue;
}
writer.WriteEndArray();
JsonSerializerOptions? typeOptions = null;
if (prop.JsonConverter != null)
{
typeOptions = new JsonSerializerOptions
{
NumberHandling = JsonNumberHandling.AllowReadingFromString | JsonNumberHandling.AllowNamedFloatingPointLiterals,
PropertyNameCaseInsensitive = false,
TypeInfoResolver = options.TypeInfoResolver,
};
typeOptions.Converters.Add(prop.JsonConverter);
}
if (prop.JsonConverter == null && IsSimple(prop.PropertyInfo.PropertyType))
{
if (prop.TargetType == typeof(string))
writer.WriteStringValue(Convert.ToString(objValue, CultureInfo.InvariantCulture));
else if (prop.TargetType == typeof(bool))
writer.WriteBooleanValue((bool)objValue);
else
writer.WriteRawValue(Convert.ToString(objValue, CultureInfo.InvariantCulture)!);
}
else
{
JsonSerializer.Serialize(writer, objValue, typeOptions ?? options);
}
}
/// <inheritdoc />
public override T? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
if (reader.TokenType == JsonTokenType.Null)
return default;
writer.WriteEndArray();
}
var result = new T();
return ParseObject(ref reader, result, options);
}
/// <inheritdoc />
public override T? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
if (reader.TokenType == JsonTokenType.Null)
return default;
var result = new T();
return ParseObject(ref reader, result, options);
}
#if NET5_0_OR_GREATER
[UnconditionalSuppressMessage("AssemblyLoadTrimming", "IL3050:RequiresUnreferencedCode", Justification = "JsonSerializerOptions provided here has TypeInfoResolver set")]
[UnconditionalSuppressMessage("AssemblyLoadTrimming", "IL2026:RequiresUnreferencedCode", Justification = "JsonSerializerOptions provided here has TypeInfoResolver set")]
private static T ParseObject(ref Utf8JsonReader reader, T result, JsonSerializerOptions options)
[UnconditionalSuppressMessage("AssemblyLoadTrimming", "IL3050:RequiresUnreferencedCode", Justification = "JsonSerializerOptions provided here has TypeInfoResolver set")]
[UnconditionalSuppressMessage("AssemblyLoadTrimming", "IL2026:RequiresUnreferencedCode", Justification = "JsonSerializerOptions provided here has TypeInfoResolver set")]
private static T ParseObject(ref Utf8JsonReader reader, T result, JsonSerializerOptions options)
#else
private static T ParseObject(ref Utf8JsonReader reader, T result, JsonSerializerOptions options)
private static T ParseObject(ref Utf8JsonReader reader, T result, JsonSerializerOptions options)
#endif
{
if (reader.TokenType != JsonTokenType.StartArray)
throw new Exception("Not an array");
int index = 0;
while (reader.Read())
{
if (reader.TokenType != JsonTokenType.StartArray)
throw new Exception("Not an array");
if (reader.TokenType == JsonTokenType.EndArray)
break;
int index = 0;
while (reader.Read())
var indexAttributes = _typePropertyInfo.Value.Where(a => a.ArrayProperty.Index == index);
if (!indexAttributes.Any())
{
if (reader.TokenType == JsonTokenType.EndArray)
break;
index++;
continue;
}
var indexAttributes = _typePropertyInfo.Value.Where(a => a.ArrayProperty.Index == index);
if (!indexAttributes.Any())
foreach (var attribute in indexAttributes)
{
var targetType = attribute.TargetType;
object? value = null;
if (attribute.JsonConverter != null)
{
index++;
continue;
}
foreach (var attribute in indexAttributes)
{
var targetType = attribute.TargetType;
object? value = null;
if (attribute.JsonConverter != null)
if (attribute.JsonSerializerOptions == null)
{
if (attribute.JsonSerializerOptions == null)
attribute.JsonSerializerOptions = new JsonSerializerOptions
{
attribute.JsonSerializerOptions = new JsonSerializerOptions
{
NumberHandling = JsonNumberHandling.AllowReadingFromString | JsonNumberHandling.AllowNamedFloatingPointLiterals,
PropertyNameCaseInsensitive = false,
Converters = { attribute.JsonConverter },
TypeInfoResolver = options.TypeInfoResolver,
};
}
var doc = JsonDocument.ParseValue(ref reader);
value = doc.Deserialize(attribute.PropertyInfo.PropertyType, attribute.JsonSerializerOptions);
}
else if (attribute.DefaultDeserialization)
{
value = JsonDocument.ParseValue(ref reader).Deserialize(options.GetTypeInfo(attribute.PropertyInfo.PropertyType));
}
else
{
value = reader.TokenType switch
{
JsonTokenType.Null => null,
JsonTokenType.False => false,
JsonTokenType.True => true,
JsonTokenType.String => reader.GetString(),
JsonTokenType.Number => reader.GetDecimal(),
JsonTokenType.StartObject => JsonSerializer.Deserialize(ref reader, attribute.TargetType, options),
_ => throw new NotImplementedException($"Array deserialization of type {reader.TokenType} not supported"),
NumberHandling = JsonNumberHandling.AllowReadingFromString | JsonNumberHandling.AllowNamedFloatingPointLiterals,
PropertyNameCaseInsensitive = false,
Converters = { attribute.JsonConverter },
TypeInfoResolver = options.TypeInfoResolver,
};
}
if (targetType.IsAssignableFrom(value?.GetType()))
attribute.PropertyInfo.SetValue(result, value);
else
attribute.PropertyInfo.SetValue(result, value == null ? null : Convert.ChangeType(value, targetType, CultureInfo.InvariantCulture));
var doc = JsonDocument.ParseValue(ref reader);
value = doc.Deserialize(attribute.PropertyInfo.PropertyType, attribute.JsonSerializerOptions);
}
else if (attribute.DefaultDeserialization)
{
value = JsonDocument.ParseValue(ref reader).Deserialize(options.GetTypeInfo(attribute.PropertyInfo.PropertyType));
}
else
{
value = reader.TokenType switch
{
JsonTokenType.Null => null,
JsonTokenType.False => false,
JsonTokenType.True => true,
JsonTokenType.String => reader.GetString(),
JsonTokenType.Number => reader.GetDecimal(),
JsonTokenType.StartObject => JsonSerializer.Deserialize(ref reader, attribute.TargetType, options),
_ => throw new NotImplementedException($"Array deserialization of type {reader.TokenType} not supported"),
};
}
index++;
if (targetType.IsAssignableFrom(value?.GetType()))
attribute.PropertyInfo.SetValue(result, value);
else
attribute.PropertyInfo.SetValue(result, value == null ? null : Convert.ChangeType(value, targetType, CultureInfo.InvariantCulture));
}
return result;
index++;
}
private static bool IsSimple(Type type)
return result;
}
private static bool IsSimple(Type type)
{
if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>))
{
if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>))
{
// nullable type, check if the nested type is simple.
return IsSimple(type.GetGenericArguments()[0]);
}
return type.IsPrimitive
|| type.IsEnum
|| type == typeof(string)
|| type == typeof(decimal);
// nullable type, check if the nested type is simple.
return IsSimple(type.GetGenericArguments()[0]);
}
return type.IsPrimitive
|| type.IsEnum
|| type == typeof(string)
|| type == typeof(decimal);
}
#if NET5_0_OR_GREATER
[UnconditionalSuppressMessage("AssemblyLoadTrimming", "IL3050:RequiresUnreferencedCode", Justification = "JsonSerializerOptions provided here has TypeInfoResolver set")]
[UnconditionalSuppressMessage("AssemblyLoadTrimming", "IL2026:RequiresUnreferencedCode", Justification = "JsonSerializerOptions provided here has TypeInfoResolver set")]
private static List<ArrayPropertyInfo> CacheTypeAttributes()
[UnconditionalSuppressMessage("AssemblyLoadTrimming", "IL3050:RequiresUnreferencedCode", Justification = "JsonSerializerOptions provided here has TypeInfoResolver set")]
[UnconditionalSuppressMessage("AssemblyLoadTrimming", "IL2026:RequiresUnreferencedCode", Justification = "JsonSerializerOptions provided here has TypeInfoResolver set")]
private static List<ArrayPropertyInfo> CacheTypeAttributes()
#else
private static List<ArrayPropertyInfo> CacheTypeAttributes()
private static List<ArrayPropertyInfo> CacheTypeAttributes()
#endif
{
var attributes = new List<ArrayPropertyInfo>();
var properties = typeof(T).GetProperties();
foreach (var property in properties)
{
var attributes = new List<ArrayPropertyInfo>();
var properties = typeof(T).GetProperties();
foreach (var property in properties)
var att = property.GetCustomAttribute<ArrayPropertyAttribute>();
if (att == null)
continue;
var targetType = Nullable.GetUnderlyingType(property.PropertyType) ?? property.PropertyType;
var converterType = property.GetCustomAttribute<JsonConverterAttribute>()?.ConverterType ?? targetType.GetCustomAttribute<JsonConverterAttribute>()?.ConverterType;
attributes.Add(new ArrayPropertyInfo
{
var att = property.GetCustomAttribute<ArrayPropertyAttribute>();
if (att == null)
continue;
var targetType = Nullable.GetUnderlyingType(property.PropertyType) ?? property.PropertyType;
var converterType = property.GetCustomAttribute<JsonConverterAttribute>()?.ConverterType ?? targetType.GetCustomAttribute<JsonConverterAttribute>()?.ConverterType;
attributes.Add(new ArrayPropertyInfo
{
ArrayProperty = att,
PropertyInfo = property,
DefaultDeserialization = property.GetCustomAttribute<CryptoExchange.Net.Attributes.JsonConversionAttribute>() != null,
JsonConverter = converterType == null ? null : (JsonConverter)Activator.CreateInstance(converterType)!,
TargetType = targetType
});
}
return attributes;
ArrayProperty = att,
PropertyInfo = property,
DefaultDeserialization = property.GetCustomAttribute<CryptoExchange.Net.Attributes.JsonConversionAttribute>() != null,
JsonConverter = converterType == null ? null : (JsonConverter)Activator.CreateInstance(converterType)!,
TargetType = targetType
});
}
private class ArrayPropertyInfo
{
public PropertyInfo PropertyInfo { get; set; } = null!;
public ArrayPropertyAttribute ArrayProperty { get; set; } = null!;
public JsonConverter? JsonConverter { get; set; }
public bool DefaultDeserialization { get; set; }
public Type TargetType { get; set; } = null!;
public JsonSerializerOptions? JsonSerializerOptions { get; set; } = null;
}
return attributes;
}
private class ArrayPropertyInfo
{
public PropertyInfo PropertyInfo { get; set; } = null!;
public ArrayPropertyAttribute ArrayProperty { get; set; } = null!;
public JsonConverter? JsonConverter { get; set; }
public bool DefaultDeserialization { get; set; }
public Type TargetType { get; set; } = null!;
public JsonSerializerOptions? JsonSerializerOptions { get; set; }
}
}

View File

@ -1,46 +1,45 @@
using System;
using System;
using System.Globalization;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace CryptoExchange.Net.Converters.SystemTextJson
{
/// <summary>
/// Decimal converter that handles overflowing decimal values (by setting it to decimal.MaxValue)
/// </summary>
public class BigDecimalConverter : JsonConverter<decimal>
{
/// <inheritdoc />
public override decimal Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
if (reader.TokenType == JsonTokenType.String)
{
try
{
return decimal.Parse(reader.GetString()!, NumberStyles.Float, CultureInfo.InvariantCulture);
}
catch(OverflowException)
{
// Value doesn't fit decimal, default to max value
return decimal.MaxValue;
}
}
namespace CryptoExchange.Net.Converters.SystemTextJson;
/// <summary>
/// Decimal converter that handles overflowing decimal values (by setting it to decimal.MaxValue)
/// </summary>
public class BigDecimalConverter : JsonConverter<decimal>
{
/// <inheritdoc />
public override decimal Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
if (reader.TokenType == JsonTokenType.String)
{
try
{
return reader.GetDecimal();
return decimal.Parse(reader.GetString()!, NumberStyles.Float, CultureInfo.InvariantCulture);
}
catch(FormatException)
catch(OverflowException)
{
// Format issue, assume value is too large
// Value doesn't fit decimal, default to max value
return decimal.MaxValue;
}
}
/// <inheritdoc />
public override void Write(Utf8JsonWriter writer, decimal value, JsonSerializerOptions options)
try
{
writer.WriteNumberValue(value);
return reader.GetDecimal();
}
catch(FormatException)
{
// Format issue, assume value is too large
return decimal.MaxValue;
}
}
/// <inheritdoc />
public override void Write(Utf8JsonWriter writer, decimal value, JsonSerializerOptions options)
{
writer.WriteNumberValue(value);
}
}

View File

@ -1,84 +1,83 @@
using System;
using System;
using System.Diagnostics;
using System.Runtime.Serialization;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace CryptoExchange.Net.Converters.SystemTextJson
namespace CryptoExchange.Net.Converters.SystemTextJson;
/// <summary>
/// Bool converter
/// </summary>
public class BoolConverter : JsonConverterFactory
{
/// <summary>
/// Bool converter
/// </summary>
public class BoolConverter : JsonConverterFactory
/// <inheritdoc />
public override bool CanConvert(Type typeToConvert)
{
/// <inheritdoc />
public override bool CanConvert(Type typeToConvert)
{
return typeToConvert == typeof(bool) || typeToConvert == typeof(bool?);
}
/// <inheritdoc />
public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options)
{
return typeToConvert == typeof(bool) ? new BoolConverterInner<bool>() : new BoolConverterInner<bool?>();
}
private class BoolConverterInner<T> : JsonConverter<T>
{
public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
=> (T)((object?)ReadBool(ref reader, typeToConvert, options) ?? default(T))!;
public bool? ReadBool(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
if (reader.TokenType == JsonTokenType.True)
return true;
if (reader.TokenType == JsonTokenType.False)
return false;
var value = reader.TokenType switch
{
JsonTokenType.String => reader.GetString(),
JsonTokenType.Number => reader.GetInt16().ToString(),
_ => null
};
value = value?.ToLowerInvariant().Trim();
if (string.IsNullOrEmpty(value))
{
if (typeToConvert == typeof(bool))
Trace.WriteLine($"{DateTime.Now:yyyy/MM/dd HH:mm:ss:fff} | Warning | Received null bool value, but property type is not a nullable bool");
return default;
}
switch (value)
{
case "true":
case "yes":
case "y":
case "1":
case "on":
return true;
case "false":
case "no":
case "n":
case "0":
case "off":
case "-1":
return false;
}
throw new SerializationException($"Can't convert bool value {value}");
}
public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options)
{
if (value is bool boolVal)
writer.WriteBooleanValue(boolVal);
else
writer.WriteNullValue();
}
}
return typeToConvert == typeof(bool) || typeToConvert == typeof(bool?);
}
/// <inheritdoc />
public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options)
{
return typeToConvert == typeof(bool) ? new BoolConverterInner<bool>() : new BoolConverterInner<bool?>();
}
private class BoolConverterInner<T> : JsonConverter<T>
{
public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
=> (T)((object?)ReadBool(ref reader, typeToConvert, options) ?? default(T))!;
public static bool? ReadBool(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
if (reader.TokenType == JsonTokenType.True)
return true;
if (reader.TokenType == JsonTokenType.False)
return false;
var value = reader.TokenType switch
{
JsonTokenType.String => reader.GetString(),
JsonTokenType.Number => reader.GetInt16().ToString(),
_ => null
};
value = value?.ToLowerInvariant().Trim();
if (string.IsNullOrEmpty(value))
{
if (typeToConvert == typeof(bool))
Trace.WriteLine($"{DateTime.Now:yyyy/MM/dd HH:mm:ss:fff} | Warning | Received null bool value, but property type is not a nullable bool");
return default;
}
switch (value)
{
case "true":
case "yes":
case "y":
case "1":
case "on":
return true;
case "false":
case "no":
case "n":
case "0":
case "off":
case "-1":
return false;
}
throw new SerializationException($"Can't convert bool value {value}");
}
public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options)
{
if (value is bool boolVal)
writer.WriteBooleanValue(boolVal);
else
writer.WriteNullValue();
}
}
}

View File

@ -1,37 +1,36 @@
using System;
using System.Collections.Generic;
using System;
#if NET5_0_OR_GREATER
using System.Diagnostics.CodeAnalysis;
#endif
using System.Linq;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace CryptoExchange.Net.Converters.SystemTextJson
{
/// <summary>
/// Converter for comma separated enum values
/// </summary>
namespace CryptoExchange.Net.Converters.SystemTextJson;
/// <summary>
/// Converter for comma separated enum values
/// </summary>
#if NET5_0_OR_GREATER
public class CommaSplitEnumConverter<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor | DynamicallyAccessedMemberTypes.PublicFields)] T> : JsonConverter<T[]> where T : struct, Enum
public class CommaSplitEnumConverter<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor | DynamicallyAccessedMemberTypes.PublicFields)] T> : JsonConverter<T[]> where T : struct, Enum
#else
public class CommaSplitEnumConverter<T> : JsonConverter<T[]> where T : struct, Enum
public class CommaSplitEnumConverter<T> : JsonConverter<T[]> where T : struct, Enum
#endif
{
/// <inheritdoc />
public override T[]? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
/// <inheritdoc />
public override T[]? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
var str = reader.GetString();
if (string.IsNullOrEmpty(str))
return [];
var str = reader.GetString();
if (string.IsNullOrEmpty(str))
return [];
return str!.Split(',').Select(x => (T)EnumConverter.ParseString<T>(x)!).ToArray() ?? [];
}
return str!.Split(',').Select(x => (T)EnumConverter.ParseString<T>(x)!).ToArray() ?? [];
}
/// <inheritdoc />
public override void Write(Utf8JsonWriter writer, T[] value, JsonSerializerOptions options)
{
writer.WriteStringValue(string.Join(",", value.Select(x => EnumConverter.GetString(x))));
}
/// <inheritdoc />
public override void Write(Utf8JsonWriter writer, T[] value, JsonSerializerOptions options)
{
writer.WriteStringValue(string.Join(",", value.Select(x => EnumConverter.GetString(x))));
}
}

View File

@ -1,242 +1,241 @@
using System;
using System;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace CryptoExchange.Net.Converters.SystemTextJson
namespace CryptoExchange.Net.Converters.SystemTextJson;
/// <summary>
/// Date time converter
/// </summary>
public class DateTimeConverter : JsonConverterFactory
{
/// <summary>
/// Date time converter
/// </summary>
public class DateTimeConverter : JsonConverterFactory
private static readonly DateTime _epoch = new(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
private const long _ticksPerSecond = TimeSpan.TicksPerMillisecond * 1000;
private const double _ticksPerMicrosecond = TimeSpan.TicksPerMillisecond / 1000d;
private const double _ticksPerNanosecond = TimeSpan.TicksPerMillisecond / 1000d / 1000;
/// <inheritdoc />
public override bool CanConvert(Type typeToConvert)
{
private static readonly DateTime _epoch = new(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
private const long _ticksPerSecond = TimeSpan.TicksPerMillisecond * 1000;
private const double _ticksPerMicrosecond = TimeSpan.TicksPerMillisecond / 1000d;
private const double _ticksPerNanosecond = TimeSpan.TicksPerMillisecond / 1000d / 1000;
/// <inheritdoc />
public override bool CanConvert(Type typeToConvert)
{
return typeToConvert == typeof(DateTime) || typeToConvert == typeof(DateTime?);
}
/// <inheritdoc />
public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options)
{
return typeToConvert == typeof(DateTime) ? new DateTimeConverterInner<DateTime>() : new DateTimeConverterInner<DateTime?>();
}
private class DateTimeConverterInner<T> : JsonConverter<T>
{
public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
=> (T)((object?)ReadDateTime(ref reader, typeToConvert, options) ?? default(T))!;
private DateTime? ReadDateTime(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
if (reader.TokenType == JsonTokenType.Null)
{
if (typeToConvert == typeof(DateTime))
Trace.WriteLine($"{DateTime.Now:yyyy/MM/dd HH:mm:ss:fff} | Warning | DateTime value of null, but property is not nullable");
return default;
}
if (reader.TokenType is JsonTokenType.Number)
{
var longValue = reader.GetDouble();
if (longValue == 0 || longValue < 0)
return default;
return ParseFromDouble(longValue);
}
else if (reader.TokenType is JsonTokenType.String)
{
var stringValue = reader.GetString();
if (string.IsNullOrWhiteSpace(stringValue)
|| stringValue == "-1"
|| stringValue == "0001-01-01T00:00:00Z"
|| double.TryParse(stringValue, out var doubleVal) && doubleVal == 0)
{
return default;
}
return ParseFromString(stringValue!);
}
else
{
return reader.GetDateTime();
}
}
public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options)
{
if (value == null)
{
writer.WriteNullValue();
}
else
{
var dtValue = (DateTime)(object)value;
if (dtValue == default)
writer.WriteStringValue(default(DateTime));
else
writer.WriteNumberValue((long)Math.Round((dtValue - new DateTime(1970, 1, 1)).TotalMilliseconds));
}
}
}
/// <summary>
/// Parse a long value to datetime
/// </summary>
/// <param name="longValue"></param>
/// <returns></returns>
public static DateTime ParseFromDouble(double longValue)
{
if (longValue < 19999999999)
return ConvertFromSeconds(longValue);
if (longValue < 19999999999999)
return ConvertFromMilliseconds(longValue);
if (longValue < 19999999999999999)
return ConvertFromMicroseconds(longValue);
return ConvertFromNanoseconds(longValue);
}
/// <summary>
/// Parse a string value to datetime
/// </summary>
/// <param name="stringValue"></param>
/// <returns></returns>
public static DateTime ParseFromString(string stringValue)
{
if (stringValue!.Length == 12 && stringValue.StartsWith("202"))
{
// Parse 202303261200 format
if (!int.TryParse(stringValue.Substring(0, 4), out var year)
|| !int.TryParse(stringValue.Substring(4, 2), out var month)
|| !int.TryParse(stringValue.Substring(6, 2), out var day)
|| !int.TryParse(stringValue.Substring(8, 2), out var hour)
|| !int.TryParse(stringValue.Substring(10, 2), out var minute))
{
Trace.WriteLine($"{DateTime.Now:yyyy/MM/dd HH:mm:ss:fff} | Warning | Unknown DateTime format: " + stringValue);
return default;
}
return new DateTime(year, month, day, hour, minute, 0, DateTimeKind.Utc);
}
if (stringValue.Length == 8)
{
// Parse 20211103 format
if (!int.TryParse(stringValue.Substring(0, 4), out var year)
|| !int.TryParse(stringValue.Substring(4, 2), out var month)
|| !int.TryParse(stringValue.Substring(6, 2), out var day))
{
Trace.WriteLine($"{DateTime.Now:yyyy/MM/dd HH:mm:ss:fff} | Warning | Unknown DateTime format: " + stringValue);
return default;
}
return new DateTime(year, month, day, 0, 0, 0, DateTimeKind.Utc);
}
if (stringValue.Length == 6)
{
// Parse 211103 format
if (!int.TryParse(stringValue.Substring(0, 2), out var year)
|| !int.TryParse(stringValue.Substring(2, 2), out var month)
|| !int.TryParse(stringValue.Substring(4, 2), out var day))
{
Trace.WriteLine("{DateTime.Now:yyyy/MM/dd HH:mm:ss:fff} | Warning | Unknown DateTime format: " + stringValue);
return default;
}
return new DateTime(year + 2000, month, day, 0, 0, 0, DateTimeKind.Utc);
}
if (double.TryParse(stringValue, NumberStyles.Float, CultureInfo.InvariantCulture, out var doubleValue))
{
// Parse 1637745563.000 format
if (doubleValue <= 0)
return default;
if (doubleValue < 19999999999)
return ConvertFromSeconds(doubleValue);
if (doubleValue < 19999999999999)
return ConvertFromMilliseconds((long)doubleValue);
if (doubleValue < 19999999999999999)
return ConvertFromMicroseconds((long)doubleValue);
return ConvertFromNanoseconds((long)doubleValue);
}
if (stringValue.Length == 10)
{
// Parse 2021-11-03 format
var values = stringValue.Split('-');
if (!int.TryParse(values[0], out var year)
|| !int.TryParse(values[1], out var month)
|| !int.TryParse(values[2], out var day))
{
Trace.WriteLine("{DateTime.Now:yyyy/MM/dd HH:mm:ss:fff} | Warning | Unknown DateTime format: " + stringValue);
return default;
}
return new DateTime(year, month, day, 0, 0, 0, DateTimeKind.Utc);
}
return DateTime.Parse(stringValue, CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal | DateTimeStyles.AssumeUniversal);
}
/// <summary>
/// Convert a seconds since epoch (01-01-1970) value to DateTime
/// </summary>
/// <param name="seconds"></param>
/// <returns></returns>
public static DateTime ConvertFromSeconds(double seconds) => _epoch.AddTicks((long)Math.Round(seconds * _ticksPerSecond));
/// <summary>
/// Convert a milliseconds since epoch (01-01-1970) value to DateTime
/// </summary>
/// <param name="milliseconds"></param>
/// <returns></returns>
public static DateTime ConvertFromMilliseconds(double milliseconds) => _epoch.AddTicks((long)Math.Round(milliseconds * TimeSpan.TicksPerMillisecond));
/// <summary>
/// Convert a microseconds since epoch (01-01-1970) value to DateTime
/// </summary>
/// <param name="microseconds"></param>
/// <returns></returns>
public static DateTime ConvertFromMicroseconds(double microseconds) => _epoch.AddTicks((long)Math.Round(microseconds * _ticksPerMicrosecond));
/// <summary>
/// Convert a nanoseconds since epoch (01-01-1970) value to DateTime
/// </summary>
/// <param name="nanoseconds"></param>
/// <returns></returns>
public static DateTime ConvertFromNanoseconds(double nanoseconds) => _epoch.AddTicks((long)Math.Round(nanoseconds * _ticksPerNanosecond));
/// <summary>
/// Convert a DateTime value to seconds since epoch (01-01-1970) value
/// </summary>
/// <param name="time"></param>
/// <returns></returns>
[return: NotNullIfNotNull("time")]
public static long? ConvertToSeconds(DateTime? time) => time == null ? null : (long)Math.Round((time.Value - _epoch).TotalSeconds);
/// <summary>
/// Convert a DateTime value to milliseconds since epoch (01-01-1970) value
/// </summary>
/// <param name="time"></param>
/// <returns></returns>
[return: NotNullIfNotNull("time")]
public static long? ConvertToMilliseconds(DateTime? time) => time == null ? null : (long)Math.Round((time.Value - _epoch).TotalMilliseconds);
/// <summary>
/// Convert a DateTime value to microseconds since epoch (01-01-1970) value
/// </summary>
/// <param name="time"></param>
/// <returns></returns>
[return: NotNullIfNotNull("time")]
public static long? ConvertToMicroseconds(DateTime? time) => time == null ? null : (long)Math.Round((time.Value - _epoch).Ticks / _ticksPerMicrosecond);
/// <summary>
/// Convert a DateTime value to nanoseconds since epoch (01-01-1970) value
/// </summary>
/// <param name="time"></param>
/// <returns></returns>
[return: NotNullIfNotNull("time")]
public static long? ConvertToNanoseconds(DateTime? time) => time == null ? null : (long)Math.Round((time.Value - _epoch).Ticks / _ticksPerNanosecond);
return typeToConvert == typeof(DateTime) || typeToConvert == typeof(DateTime?);
}
/// <inheritdoc />
public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options)
{
return typeToConvert == typeof(DateTime) ? new DateTimeConverterInner<DateTime>() : new DateTimeConverterInner<DateTime?>();
}
private class DateTimeConverterInner<T> : JsonConverter<T>
{
public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
=> (T)((object?)ReadDateTime(ref reader, typeToConvert, options) ?? default(T))!;
private static DateTime? ReadDateTime(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
if (reader.TokenType == JsonTokenType.Null)
{
if (typeToConvert == typeof(DateTime))
Trace.WriteLine($"{DateTime.Now:yyyy/MM/dd HH:mm:ss:fff} | Warning | DateTime value of null, but property is not nullable");
return default;
}
if (reader.TokenType is JsonTokenType.Number)
{
var longValue = reader.GetDouble();
if (longValue == 0 || longValue < 0)
return default;
return ParseFromDouble(longValue);
}
else if (reader.TokenType is JsonTokenType.String)
{
var stringValue = reader.GetString();
if (string.IsNullOrWhiteSpace(stringValue)
|| stringValue == "-1"
|| stringValue == "0001-01-01T00:00:00Z"
|| double.TryParse(stringValue, out var doubleVal) && doubleVal == 0)
{
return default;
}
return ParseFromString(stringValue!);
}
else
{
return reader.GetDateTime();
}
}
public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options)
{
if (value == null)
{
writer.WriteNullValue();
}
else
{
var dtValue = (DateTime)(object)value;
if (dtValue == default)
writer.WriteStringValue(default(DateTime));
else
writer.WriteNumberValue((long)Math.Round((dtValue - new DateTime(1970, 1, 1)).TotalMilliseconds));
}
}
}
/// <summary>
/// Parse a long value to datetime
/// </summary>
/// <param name="longValue"></param>
/// <returns></returns>
public static DateTime ParseFromDouble(double longValue)
{
if (longValue < 19999999999)
return ConvertFromSeconds(longValue);
if (longValue < 19999999999999)
return ConvertFromMilliseconds(longValue);
if (longValue < 19999999999999999)
return ConvertFromMicroseconds(longValue);
return ConvertFromNanoseconds(longValue);
}
/// <summary>
/// Parse a string value to datetime
/// </summary>
/// <param name="stringValue"></param>
/// <returns></returns>
public static DateTime ParseFromString(string stringValue)
{
if (stringValue!.Length == 12 && stringValue.StartsWith("202"))
{
// Parse 202303261200 format
if (!int.TryParse(stringValue.Substring(0, 4), out var year)
|| !int.TryParse(stringValue.Substring(4, 2), out var month)
|| !int.TryParse(stringValue.Substring(6, 2), out var day)
|| !int.TryParse(stringValue.Substring(8, 2), out var hour)
|| !int.TryParse(stringValue.Substring(10, 2), out var minute))
{
Trace.WriteLine($"{DateTime.Now:yyyy/MM/dd HH:mm:ss:fff} | Warning | Unknown DateTime format: " + stringValue);
return default;
}
return new DateTime(year, month, day, hour, minute, 0, DateTimeKind.Utc);
}
if (stringValue.Length == 8)
{
// Parse 20211103 format
if (!int.TryParse(stringValue.Substring(0, 4), out var year)
|| !int.TryParse(stringValue.Substring(4, 2), out var month)
|| !int.TryParse(stringValue.Substring(6, 2), out var day))
{
Trace.WriteLine($"{DateTime.Now:yyyy/MM/dd HH:mm:ss:fff} | Warning | Unknown DateTime format: " + stringValue);
return default;
}
return new DateTime(year, month, day, 0, 0, 0, DateTimeKind.Utc);
}
if (stringValue.Length == 6)
{
// Parse 211103 format
if (!int.TryParse(stringValue.Substring(0, 2), out var year)
|| !int.TryParse(stringValue.Substring(2, 2), out var month)
|| !int.TryParse(stringValue.Substring(4, 2), out var day))
{
Trace.WriteLine("{DateTime.Now:yyyy/MM/dd HH:mm:ss:fff} | Warning | Unknown DateTime format: " + stringValue);
return default;
}
return new DateTime(year + 2000, month, day, 0, 0, 0, DateTimeKind.Utc);
}
if (double.TryParse(stringValue, NumberStyles.Float, CultureInfo.InvariantCulture, out var doubleValue))
{
// Parse 1637745563.000 format
if (doubleValue <= 0)
return default;
if (doubleValue < 19999999999)
return ConvertFromSeconds(doubleValue);
if (doubleValue < 19999999999999)
return ConvertFromMilliseconds((long)doubleValue);
if (doubleValue < 19999999999999999)
return ConvertFromMicroseconds((long)doubleValue);
return ConvertFromNanoseconds((long)doubleValue);
}
if (stringValue.Length == 10)
{
// Parse 2021-11-03 format
var values = stringValue.Split('-');
if (!int.TryParse(values[0], out var year)
|| !int.TryParse(values[1], out var month)
|| !int.TryParse(values[2], out var day))
{
Trace.WriteLine("{DateTime.Now:yyyy/MM/dd HH:mm:ss:fff} | Warning | Unknown DateTime format: " + stringValue);
return default;
}
return new DateTime(year, month, day, 0, 0, 0, DateTimeKind.Utc);
}
return DateTime.Parse(stringValue, CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal | DateTimeStyles.AssumeUniversal);
}
/// <summary>
/// Convert a seconds since epoch (01-01-1970) value to DateTime
/// </summary>
/// <param name="seconds"></param>
/// <returns></returns>
public static DateTime ConvertFromSeconds(double seconds) => _epoch.AddTicks((long)Math.Round(seconds * _ticksPerSecond));
/// <summary>
/// Convert a milliseconds since epoch (01-01-1970) value to DateTime
/// </summary>
/// <param name="milliseconds"></param>
/// <returns></returns>
public static DateTime ConvertFromMilliseconds(double milliseconds) => _epoch.AddTicks((long)Math.Round(milliseconds * TimeSpan.TicksPerMillisecond));
/// <summary>
/// Convert a microseconds since epoch (01-01-1970) value to DateTime
/// </summary>
/// <param name="microseconds"></param>
/// <returns></returns>
public static DateTime ConvertFromMicroseconds(double microseconds) => _epoch.AddTicks((long)Math.Round(microseconds * _ticksPerMicrosecond));
/// <summary>
/// Convert a nanoseconds since epoch (01-01-1970) value to DateTime
/// </summary>
/// <param name="nanoseconds"></param>
/// <returns></returns>
public static DateTime ConvertFromNanoseconds(double nanoseconds) => _epoch.AddTicks((long)Math.Round(nanoseconds * _ticksPerNanosecond));
/// <summary>
/// Convert a DateTime value to seconds since epoch (01-01-1970) value
/// </summary>
/// <param name="time"></param>
/// <returns></returns>
[return: NotNullIfNotNull("time")]
public static long? ConvertToSeconds(DateTime? time) => time == null ? null : (long)Math.Round((time.Value - _epoch).TotalSeconds);
/// <summary>
/// Convert a DateTime value to milliseconds since epoch (01-01-1970) value
/// </summary>
/// <param name="time"></param>
/// <returns></returns>
[return: NotNullIfNotNull("time")]
public static long? ConvertToMilliseconds(DateTime? time) => time == null ? null : (long)Math.Round((time.Value - _epoch).TotalMilliseconds);
/// <summary>
/// Convert a DateTime value to microseconds since epoch (01-01-1970) value
/// </summary>
/// <param name="time"></param>
/// <returns></returns>
[return: NotNullIfNotNull("time")]
public static long? ConvertToMicroseconds(DateTime? time) => time == null ? null : (long)Math.Round((time.Value - _epoch).Ticks / _ticksPerMicrosecond);
/// <summary>
/// Convert a DateTime value to nanoseconds since epoch (01-01-1970) value
/// </summary>
/// <param name="time"></param>
/// <returns></returns>
[return: NotNullIfNotNull("time")]
public static long? ConvertToNanoseconds(DateTime? time) => time == null ? null : (long)Math.Round((time.Value - _epoch).Ticks / _ticksPerNanosecond);
}

View File

@ -1,45 +1,43 @@
using System;
using System.Globalization;
using System;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace CryptoExchange.Net.Converters.SystemTextJson
namespace CryptoExchange.Net.Converters.SystemTextJson;
/// <summary>
/// Decimal converter
/// </summary>
public class DecimalConverter : JsonConverter<decimal?>
{
/// <summary>
/// Decimal converter
/// </summary>
public class DecimalConverter : JsonConverter<decimal?>
/// <inheritdoc />
public override decimal? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
/// <inheritdoc />
public override decimal? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
if (reader.TokenType == JsonTokenType.Null)
return null;
if (reader.TokenType == JsonTokenType.String)
{
if (reader.TokenType == JsonTokenType.Null)
return null;
if (reader.TokenType == JsonTokenType.String)
{
var value = reader.GetString();
return ExchangeHelpers.ParseDecimal(value);
}
try
{
return reader.GetDecimal();
}
catch(FormatException)
{
// Format issue, assume value is too large
return decimal.MaxValue;
}
var value = reader.GetString();
return ExchangeHelpers.ParseDecimal(value);
}
/// <inheritdoc />
public override void Write(Utf8JsonWriter writer, decimal? value, JsonSerializerOptions options)
try
{
if (value == null)
writer.WriteNullValue();
else
writer.WriteNumberValue(value.Value);
return reader.GetDecimal();
}
catch(FormatException)
{
// Format issue, assume value is too large
return decimal.MaxValue;
}
}
/// <inheritdoc />
public override void Write(Utf8JsonWriter writer, decimal? value, JsonSerializerOptions options)
{
if (value == null)
writer.WriteNullValue();
else
writer.WriteNumberValue(value.Value);
}
}

View File

@ -1,23 +1,22 @@
using System;
using System;
using System.Globalization;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace CryptoExchange.Net.Converters.SystemTextJson
{
/// <summary>
/// Converter for serializing decimal values as string
/// </summary>
public class DecimalStringWriterConverter : JsonConverter<decimal>
{
/// <inheritdoc />
public override decimal Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
throw new NotImplementedException();
}
namespace CryptoExchange.Net.Converters.SystemTextJson;
/// <inheritdoc />
public override void Write(Utf8JsonWriter writer, decimal value, JsonSerializerOptions options)
=> writer.WriteStringValue(value.ToString(CultureInfo.InvariantCulture) ?? null);
/// <summary>
/// Converter for serializing decimal values as string
/// </summary>
public class DecimalStringWriterConverter : JsonConverter<decimal>
{
/// <inheritdoc />
public override decimal Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
throw new NotImplementedException();
}
/// <inheritdoc />
public override void Write(Utf8JsonWriter writer, decimal value, JsonSerializerOptions options)
=> writer.WriteStringValue(value.ToString(CultureInfo.InvariantCulture) ?? null);
}

View File

@ -1,4 +1,4 @@
using CryptoExchange.Net.Attributes;
using CryptoExchange.Net.Attributes;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
@ -9,281 +9,280 @@ using System.Reflection;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace CryptoExchange.Net.Converters.SystemTextJson
namespace CryptoExchange.Net.Converters.SystemTextJson;
/// <summary>
/// Static EnumConverter methods
/// </summary>
public static class EnumConverter
{
/// <summary>
/// Static EnumConverter methods
/// Get the enum value from a string
/// </summary>
public static class EnumConverter
{
/// <summary>
/// Get the enum value from a string
/// </summary>
/// <param name="value">String value</param>
/// <returns></returns>
/// <param name="value">String value</param>
/// <returns></returns>
#if NET5_0_OR_GREATER
public static T? ParseString<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor | DynamicallyAccessedMemberTypes.PublicFields)] T>(string value) where T : struct, Enum
public static T? ParseString<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor | DynamicallyAccessedMemberTypes.PublicFields)] T>(string value) where T : struct, Enum
#else
public static T? ParseString<T>(string value) where T : struct, Enum
public static T? ParseString<T>(string value) where T : struct, Enum
#endif
=> EnumConverter<T>.ParseString(value);
/// <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>
/// <param name="enumValue"></param>
/// <returns></returns>
#if NET5_0_OR_GREATER
public static string GetString<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor | DynamicallyAccessedMemberTypes.PublicFields)] T>(T enumValue) where T : struct, Enum
#else
public static string GetString<T>(T enumValue) where T : struct, Enum
#endif
=> EnumConverter<T>.GetString(enumValue);
/// <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>
/// <param name="enumValue"></param>
/// <returns></returns>
[return: NotNullIfNotNull("enumValue")]
#if NET5_0_OR_GREATER
public static string? GetString<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor | DynamicallyAccessedMemberTypes.PublicFields)] T>(T? enumValue) where T : struct, Enum
#else
public static string? GetString<T>(T? enumValue) where T : struct, Enum
#endif
=> EnumConverter<T>.GetString(enumValue);
}
=> EnumConverter<T>.ParseString(value);
/// <summary>
/// Converter for enum values. Enums entries should be noted with a MapAttribute to map the enum value to a string value
/// 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>
/// <param name="enumValue"></param>
/// <returns></returns>
#if NET5_0_OR_GREATER
public class EnumConverter<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor | DynamicallyAccessedMemberTypes.PublicFields)] T>
public static string GetString<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor | DynamicallyAccessedMemberTypes.PublicFields)] T>(T enumValue) where T : struct, Enum
#else
public class EnumConverter<T>
public static string GetString<T>(T enumValue) where T : struct, Enum
#endif
: JsonConverter<T>, INullableConverterFactory where T : struct, Enum
=> EnumConverter<T>.GetString(enumValue);
/// <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>
/// <param name="enumValue"></param>
/// <returns></returns>
[return: NotNullIfNotNull("enumValue")]
#if NET5_0_OR_GREATER
public static string? GetString<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor | DynamicallyAccessedMemberTypes.PublicFields)] T>(T? enumValue) where T : struct, Enum
#else
public static string? GetString<T>(T? enumValue) where T : struct, Enum
#endif
=> EnumConverter<T>.GetString(enumValue);
}
/// <summary>
/// Converter for enum values. Enums entries should be noted with a MapAttribute to map the enum value to a string value
/// </summary>
#if NET5_0_OR_GREATER
public class EnumConverter<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor | DynamicallyAccessedMemberTypes.PublicFields)] T>
#else
public class EnumConverter<T>
#endif
: JsonConverter<T>, INullableConverterFactory where T : struct, Enum
{
private static List<KeyValuePair<T, string>>? _mapping;
private NullableEnumConverter? _nullableEnumConverter;
private static ConcurrentBag<string> _unknownValuesWarned = new ConcurrentBag<string>();
internal class NullableEnumConverter : JsonConverter<T?>
{
private static List<KeyValuePair<T, string>>? _mapping = null;
private NullableEnumConverter? _nullableEnumConverter = null;
private readonly EnumConverter<T> _enumConverter;
private static ConcurrentBag<string> _unknownValuesWarned = new ConcurrentBag<string>();
internal class NullableEnumConverter : JsonConverter<T?>
public NullableEnumConverter(EnumConverter<T> enumConverter)
{
private readonly EnumConverter<T> _enumConverter;
public NullableEnumConverter(EnumConverter<T> enumConverter)
{
_enumConverter = enumConverter;
}
public override T? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
return _enumConverter.ReadNullable(ref reader, typeToConvert, options, out var isEmptyString, out var warn);
}
public override void Write(Utf8JsonWriter writer, T? value, JsonSerializerOptions options)
{
if (value == null)
{
writer.WriteNullValue();
}
else
{
_enumConverter.Write(writer, value.Value, options);
}
}
_enumConverter = enumConverter;
}
public override T? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
return EnumConverter<T>.ReadNullable(ref reader, typeToConvert, options, out var isEmptyString, out var warn);
}
/// <inheritdoc />
public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
public override void Write(Utf8JsonWriter writer, T? value, JsonSerializerOptions options)
{
var t = ReadNullable(ref reader, typeToConvert, options, out var isEmptyString, out var warn);
if (t == null)
if (value == null)
{
if (warn)
{
if (isEmptyString)
{
// We received an empty string and have no mapping for it, and the property isn't nullable
Trace.WriteLine($"{DateTime.Now:yyyy/MM/dd HH:mm:ss:fff} | Warning | Received empty string as enum value, but property type is not a nullable enum. EnumType: {typeof(T).Name}. If you think {typeof(T).Name} should be nullable please open an issue on the Github repo");
}
else
{
Trace.WriteLine($"{DateTime.Now:yyyy/MM/dd HH:mm:ss:fff} | Warning | Received null enum value, but property type is not a nullable enum. EnumType: {typeof(T).Name}. If you think {typeof(T).Name} should be nullable please open an issue on the Github repo");
}
}
return new T(); // return default value
writer.WriteNullValue();
}
else
{
return t.Value;
_enumConverter.Write(writer, value.Value, options);
}
}
}
private T? ReadNullable(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options, out bool isEmptyString, out bool warn)
/// <inheritdoc />
public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
var t = ReadNullable(ref reader, typeToConvert, options, out var isEmptyString, out var warn);
if (t == null)
{
isEmptyString = false;
warn = false;
var enumType = typeof(T);
if (_mapping == null)
_mapping = AddMapping();
var stringValue = reader.TokenType switch
if (warn)
{
JsonTokenType.String => reader.GetString(),
JsonTokenType.Number => reader.GetInt32().ToString(),
JsonTokenType.True => reader.GetBoolean().ToString(),
JsonTokenType.False => reader.GetBoolean().ToString(),
JsonTokenType.Null => null,
_ => throw new Exception("Invalid token type for enum deserialization: " + reader.TokenType)
};
if (string.IsNullOrEmpty(stringValue))
return null;
if (!GetValue(enumType, stringValue!, out var result))
{
if (string.IsNullOrWhiteSpace(stringValue))
if (isEmptyString)
{
isEmptyString = true;
// We received an empty string and have no mapping for it, and the property isn't nullable
Trace.WriteLine($"{DateTime.Now:yyyy/MM/dd HH:mm:ss:fff} | Warning | Received empty string as enum value, but property type is not a nullable enum. EnumType: {typeof(T).Name}. If you think {typeof(T).Name} should be nullable please open an issue on the Github repo");
}
else
{
// We received an enum value but weren't able to parse it.
if (!_unknownValuesWarned.Contains(stringValue))
{
warn = true;
_unknownValuesWarned.Add(stringValue!);
Trace.WriteLine($"{DateTime.Now:yyyy/MM/dd HH:mm:ss:fff} | Warning | Cannot map enum value. EnumType: {enumType.Name}, Value: {stringValue}, Known values: {string.Join(", ", _mapping.Select(m => m.Value))}. If you think {stringValue} should added please open an issue on the Github repo");
}
Trace.WriteLine($"{DateTime.Now:yyyy/MM/dd HH:mm:ss:fff} | Warning | Received null enum value, but property type is not a nullable enum. EnumType: {typeof(T).Name}. If you think {typeof(T).Name} should be nullable please open an issue on the Github repo");
}
return null;
}
return result;
return new T(); // return default value
}
/// <inheritdoc />
public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options)
else
{
var stringValue = GetString(value);
writer.WriteStringValue(stringValue);
return t.Value;
}
}
private static bool GetValue(Type objectType, string value, out T? result)
private static T? ReadNullable(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options, out bool isEmptyString, out bool warn)
{
isEmptyString = false;
warn = false;
var enumType = typeof(T);
if (_mapping == null)
_mapping = AddMapping();
var stringValue = reader.TokenType switch
{
if (_mapping != null)
JsonTokenType.String => reader.GetString(),
JsonTokenType.Number => reader.GetInt32().ToString(),
JsonTokenType.True => reader.GetBoolean().ToString(),
JsonTokenType.False => reader.GetBoolean().ToString(),
JsonTokenType.Null => null,
_ => throw new Exception("Invalid token type for enum deserialization: " + reader.TokenType)
};
if (string.IsNullOrEmpty(stringValue))
return null;
if (!GetValue(enumType, stringValue!, out var result))
{
if (string.IsNullOrWhiteSpace(stringValue))
{
// Check for exact match first, then if not found fallback to a case insensitive match
var mapping = _mapping.FirstOrDefault(kv => kv.Value.Equals(value, StringComparison.InvariantCulture));
if (mapping.Equals(default(KeyValuePair<T, string>)))
mapping = _mapping.FirstOrDefault(kv => kv.Value.Equals(value, StringComparison.InvariantCultureIgnoreCase));
if (!mapping.Equals(default(KeyValuePair<T, string>)))
isEmptyString = true;
}
else
{
// We received an enum value but weren't able to parse it.
if (!_unknownValuesWarned.Contains(stringValue))
{
result = mapping.Key;
return true;
warn = true;
_unknownValuesWarned.Add(stringValue!);
Trace.WriteLine($"{DateTime.Now:yyyy/MM/dd HH:mm:ss:fff} | Warning | Cannot map enum value. EnumType: {enumType.Name}, Value: {stringValue}, Known values: {string.Join(", ", _mapping.Select(m => m.Value))}. If you think {stringValue} should added please open an issue on the Github repo");
}
}
if (objectType.IsDefined(typeof(FlagsAttribute)))
{
var intValue = int.Parse(value);
result = (T)Enum.ToObject(objectType, intValue);
return true;
}
if (_unknownValuesWarned.Contains(value))
{
// Check if it is an known unknown value
// Done here to prevent lookup overhead for normal conversions, but prevent expensive exception throwing
result = default;
return false;
}
try
{
// If no explicit mapping is found try to parse string
result = (T)Enum.Parse(objectType, value, true);
return true;
}
catch (Exception)
{
result = default;
return false;
}
return null;
}
private static List<KeyValuePair<T, string>> AddMapping()
return result;
}
/// <inheritdoc />
public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options)
{
var stringValue = GetString(value);
writer.WriteStringValue(stringValue);
}
private static bool GetValue(Type objectType, string value, out T? result)
{
if (_mapping != null)
{
var mapping = new List<KeyValuePair<T, string>>();
var enumType = Nullable.GetUnderlyingType(typeof(T)) ?? typeof(T);
var enumMembers = enumType.GetFields();
foreach (var member in enumMembers)
{
var maps = member.GetCustomAttributes(typeof(MapAttribute), false);
foreach (MapAttribute attribute in maps)
{
foreach (var value in attribute.Values)
mapping.Add(new KeyValuePair<T, string>((T)Enum.Parse(enumType, member.Name), value));
}
}
_mapping = mapping;
return mapping;
}
/// <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>
/// <param name="enumValue"></param>
/// <returns></returns>
[return: NotNullIfNotNull("enumValue")]
public static string? GetString(T? enumValue)
{
if (_mapping == null)
_mapping = AddMapping();
return enumValue == null ? null : (_mapping.FirstOrDefault(v => v.Key.Equals(enumValue)).Value ?? enumValue.ToString());
}
/// <summary>
/// Get the enum value from a string
/// </summary>
/// <param name="value">String value</param>
/// <returns></returns>
public static T? ParseString(string value)
{
var type = Nullable.GetUnderlyingType(typeof(T)) ?? typeof(T);
if (_mapping == null)
_mapping = AddMapping();
// Check for exact match first, then if not found fallback to a case insensitive match
var mapping = _mapping.FirstOrDefault(kv => kv.Value.Equals(value, StringComparison.InvariantCulture));
if (mapping.Equals(default(KeyValuePair<T, string>)))
mapping = _mapping.FirstOrDefault(kv => kv.Value.Equals(value, StringComparison.InvariantCultureIgnoreCase));
if (!mapping.Equals(default(KeyValuePair<T, string>)))
return mapping.Key;
try
{
// If no explicit mapping is found try to parse string
return (T)Enum.Parse(type, value, true);
}
catch (Exception)
{
return default;
result = mapping.Key;
return true;
}
}
/// <inheritdoc />
public JsonConverter CreateNullableConverter()
if (objectType.IsDefined(typeof(FlagsAttribute)))
{
_nullableEnumConverter ??= new NullableEnumConverter(this);
return _nullableEnumConverter;
var intValue = int.Parse(value);
result = (T)Enum.ToObject(objectType, intValue);
return true;
}
if (_unknownValuesWarned.Contains(value))
{
// Check if it is an known unknown value
// Done here to prevent lookup overhead for normal conversions, but prevent expensive exception throwing
result = default;
return false;
}
try
{
// If no explicit mapping is found try to parse string
result = (T)Enum.Parse(objectType, value, true);
return true;
}
catch (Exception)
{
result = default;
return false;
}
}
private static List<KeyValuePair<T, string>> AddMapping()
{
var mapping = new List<KeyValuePair<T, string>>();
var enumType = Nullable.GetUnderlyingType(typeof(T)) ?? typeof(T);
var enumMembers = enumType.GetFields();
foreach (var member in enumMembers)
{
var maps = member.GetCustomAttributes(typeof(MapAttribute), false);
foreach (MapAttribute attribute in maps)
{
foreach (var value in attribute.Values)
mapping.Add(new KeyValuePair<T, string>((T)Enum.Parse(enumType, member.Name), value));
}
}
_mapping = mapping;
return mapping;
}
/// <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>
/// <param name="enumValue"></param>
/// <returns></returns>
[return: NotNullIfNotNull("enumValue")]
public static string? GetString(T? enumValue)
{
if (_mapping == null)
_mapping = AddMapping();
return enumValue == null ? null : (_mapping.FirstOrDefault(v => v.Key.Equals(enumValue)).Value ?? enumValue.ToString());
}
/// <summary>
/// Get the enum value from a string
/// </summary>
/// <param name="value">String value</param>
/// <returns></returns>
public static T? ParseString(string value)
{
var type = Nullable.GetUnderlyingType(typeof(T)) ?? typeof(T);
if (_mapping == null)
_mapping = AddMapping();
var mapping = _mapping.FirstOrDefault(kv => kv.Value.Equals(value, StringComparison.InvariantCulture));
if (mapping.Equals(default(KeyValuePair<T, string>)))
mapping = _mapping.FirstOrDefault(kv => kv.Value.Equals(value, StringComparison.InvariantCultureIgnoreCase));
if (!mapping.Equals(default(KeyValuePair<T, string>)))
return mapping.Key;
try
{
// If no explicit mapping is found try to parse string
return (T)Enum.Parse(type, value, true);
}
catch (Exception)
{
return default;
}
}
/// <inheritdoc />
public JsonConverter CreateNullableConverter()
{
_nullableEnumConverter ??= new NullableEnumConverter(this);
return _nullableEnumConverter;
}
}

View File

@ -1,23 +1,21 @@
using System;
using System.Globalization;
using System;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace CryptoExchange.Net.Converters.SystemTextJson
{
/// <summary>
/// Converter for serializing enum values as int
/// </summary>
public class EnumIntWriterConverter<T> : JsonConverter<T> where T: struct, Enum
{
/// <inheritdoc />
public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
throw new NotImplementedException();
}
namespace CryptoExchange.Net.Converters.SystemTextJson;
/// <inheritdoc />
public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options)
=> writer.WriteNumberValue((int)(object)value);
/// <summary>
/// Converter for serializing enum values as int
/// </summary>
public class EnumIntWriterConverter<T> : JsonConverter<T> where T: struct, Enum
{
/// <inheritdoc />
public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
throw new NotImplementedException();
}
/// <inheritdoc />
public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options)
=> writer.WriteNumberValue((int)(object)value);
}

View File

@ -1,9 +1,8 @@
using System.Text.Json.Serialization;
using System.Text.Json.Serialization;
namespace CryptoExchange.Net.Converters.SystemTextJson
namespace CryptoExchange.Net.Converters.SystemTextJson;
internal interface INullableConverterFactory
{
internal interface INullableConverterFactory
{
JsonConverter CreateNullableConverter();
}
JsonConverter CreateNullableConverter();
}

View File

@ -1,40 +1,39 @@
using System;
using System;
using System.Globalization;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace CryptoExchange.Net.Converters.SystemTextJson
namespace CryptoExchange.Net.Converters.SystemTextJson;
/// <summary>
/// Int converter
/// </summary>
public class IntConverter : JsonConverter<int?>
{
/// <summary>
/// Int converter
/// </summary>
public class IntConverter : JsonConverter<int?>
/// <inheritdoc />
public override int? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
/// <inheritdoc />
public override int? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
if (reader.TokenType == JsonTokenType.Null)
return null;
if (reader.TokenType == JsonTokenType.String)
{
if (reader.TokenType == JsonTokenType.Null)
var value = reader.GetString();
if (string.IsNullOrEmpty(value))
return null;
if (reader.TokenType == JsonTokenType.String)
{
var value = reader.GetString();
if (string.IsNullOrEmpty(value))
return null;
return int.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture);
}
return reader.GetInt32();
return int.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture);
}
/// <inheritdoc />
public override void Write(Utf8JsonWriter writer, int? value, JsonSerializerOptions options)
{
if (value == null)
writer.WriteNullValue();
else
writer.WriteNumberValue(value.Value);
}
return reader.GetInt32();
}
/// <inheritdoc />
public override void Write(Utf8JsonWriter writer, int? value, JsonSerializerOptions options)
{
if (value == null)
writer.WriteNullValue();
else
writer.WriteNumberValue(value.Value);
}
}

View File

@ -1,40 +1,39 @@
using System;
using System;
using System.Globalization;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace CryptoExchange.Net.Converters.SystemTextJson
namespace CryptoExchange.Net.Converters.SystemTextJson;
/// <summary>
/// Int converter
/// </summary>
public class LongConverter : JsonConverter<long?>
{
/// <summary>
/// Int converter
/// </summary>
public class LongConverter : JsonConverter<long?>
/// <inheritdoc />
public override long? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
/// <inheritdoc />
public override long? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
if (reader.TokenType == JsonTokenType.Null)
return null;
if (reader.TokenType == JsonTokenType.String)
{
if (reader.TokenType == JsonTokenType.Null)
var value = reader.GetString();
if (string.IsNullOrEmpty(value))
return null;
if (reader.TokenType == JsonTokenType.String)
{
var value = reader.GetString();
if (string.IsNullOrEmpty(value))
return null;
return long.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture);
}
return reader.GetInt64();
return long.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture);
}
/// <inheritdoc />
public override void Write(Utf8JsonWriter writer, long? value, JsonSerializerOptions options)
{
if (value == null)
writer.WriteNullValue();
else
writer.WriteNumberValue(value.Value);
}
return reader.GetInt64();
}
/// <inheritdoc />
public override void Write(Utf8JsonWriter writer, long? value, JsonSerializerOptions options)
{
if (value == null)
writer.WriteNullValue();
else
writer.WriteNumberValue(value.Value);
}
}

View File

@ -1,43 +1,40 @@
using System;
using System.Collections.Generic;
using System.Text;
using System;
using System.Text.Json.Serialization.Metadata;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace CryptoExchange.Net.Converters.SystemTextJson
namespace CryptoExchange.Net.Converters.SystemTextJson;
internal class NullableEnumConverterFactory : JsonConverterFactory
{
internal class NullableEnumConverterFactory : JsonConverterFactory
private readonly IJsonTypeInfoResolver _jsonTypeInfoResolver;
private static readonly JsonSerializerOptions _options = new JsonSerializerOptions();
public NullableEnumConverterFactory(IJsonTypeInfoResolver jsonTypeInfoResolver)
{
private readonly IJsonTypeInfoResolver _jsonTypeInfoResolver;
private static readonly JsonSerializerOptions _options = new JsonSerializerOptions();
_jsonTypeInfoResolver = jsonTypeInfoResolver;
}
public NullableEnumConverterFactory(IJsonTypeInfoResolver jsonTypeInfoResolver)
{
_jsonTypeInfoResolver = jsonTypeInfoResolver;
}
public override bool CanConvert(Type typeToConvert)
{
var b = Nullable.GetUnderlyingType(typeToConvert);
if (b == null)
return false;
public override bool CanConvert(Type typeToConvert)
{
var b = Nullable.GetUnderlyingType(typeToConvert);
if (b == null)
return false;
var typeInfo = _jsonTypeInfoResolver.GetTypeInfo(b, _options);
if (typeInfo == null)
return false;
var typeInfo = _jsonTypeInfoResolver.GetTypeInfo(b, _options);
if (typeInfo == null)
return false;
return typeInfo.Converter is INullableConverterFactory;
}
return typeInfo.Converter is INullableConverterFactory;
}
public override JsonConverter? CreateConverter(Type typeToConvert, JsonSerializerOptions options)
{
var b = Nullable.GetUnderlyingType(typeToConvert) ?? throw new ArgumentNullException($"Not nullable {typeToConvert.Name}");
var typeInfo = _jsonTypeInfoResolver.GetTypeInfo(b, _options) ?? throw new ArgumentNullException($"Can find type {typeToConvert.Name}");
if (typeInfo.Converter is not INullableConverterFactory nullConverterFactory)
throw new ArgumentNullException($"Can find type converter for {typeToConvert.Name}");
return nullConverterFactory.CreateNullableConverter();
}
public override JsonConverter? CreateConverter(Type typeToConvert, JsonSerializerOptions options)
{
var b = Nullable.GetUnderlyingType(typeToConvert) ?? throw new ArgumentNullException($"Not nullable {typeToConvert.Name}");
var typeInfo = _jsonTypeInfoResolver.GetTypeInfo(b, _options) ?? throw new ArgumentNullException($"Can find type {typeToConvert.Name}");
if (typeInfo.Converter is not INullableConverterFactory nullConverterFactory)
throw new ArgumentNullException($"Can find type converter for {typeToConvert.Name}");
return nullConverterFactory.CreateNullableConverter();
}
}

View File

@ -1,42 +1,41 @@
using System;
using System;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace CryptoExchange.Net.Converters.SystemTextJson
namespace CryptoExchange.Net.Converters.SystemTextJson;
/// <summary>
/// Read string or number as string
/// </summary>
public class NumberStringConverter : JsonConverter<string?>
{
/// <summary>
/// Read string or number as string
/// </summary>
public class NumberStringConverter : JsonConverter<string?>
/// <inheritdoc />
public override string? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
/// <inheritdoc />
public override string? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
if (reader.TokenType == JsonTokenType.Null)
return null;
if (reader.TokenType == JsonTokenType.Number)
{
if (reader.TokenType == JsonTokenType.Null)
return null;
if (reader.TryGetInt64(out var value))
return value.ToString();
if (reader.TokenType == JsonTokenType.Number)
{
if (reader.TryGetInt64(out var value))
return value.ToString();
return reader.GetDecimal().ToString();
}
try
{
return reader.GetString();
}
catch (Exception)
{
return null;
}
return reader.GetDecimal().ToString();
}
/// <inheritdoc />
public override void Write(Utf8JsonWriter writer, string? value, JsonSerializerOptions options)
try
{
writer.WriteStringValue(value);
return reader.GetString();
}
catch (Exception)
{
return null;
}
}
/// <inheritdoc />
public override void Write(Utf8JsonWriter writer, string? value, JsonSerializerOptions options)
{
writer.WriteStringValue(value);
}
}

View File

@ -1,43 +1,44 @@
using System;
using System;
using System.Text.Json.Serialization;
using System.Text.Json;
#if NET5_0_OR_GREATER
using System.Diagnostics.CodeAnalysis;
#endif
namespace CryptoExchange.Net.Converters.SystemTextJson
namespace CryptoExchange.Net.Converters.SystemTextJson;
/// <summary>
/// Converter for values which contain a nested json value
/// </summary>
public class ObjectStringConverter<T> : JsonConverter<T>
{
/// <summary>
/// Converter for values which contain a nested json value
/// </summary>
public class ObjectStringConverter<T> : JsonConverter<T>
/// <inheritdoc />
#if NET5_0_OR_GREATER
[UnconditionalSuppressMessage("AssemblyLoadTrimming", "IL3050:RequiresUnreferencedCode", Justification = "JsonSerializerOptions provided here has TypeInfoResolver set")]
[UnconditionalSuppressMessage("AssemblyLoadTrimming", "IL2026:RequiresUnreferencedCode", Justification = "JsonSerializerOptions provided here has TypeInfoResolver set")]
#endif
public override T? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
/// <inheritdoc />
if (reader.TokenType == JsonTokenType.Null)
return default;
var value = reader.GetString();
if (string.IsNullOrEmpty(value))
return default;
return JsonDocument.Parse(value!).Deserialize<T>(options);
}
/// <inheritdoc />
#if NET5_0_OR_GREATER
[UnconditionalSuppressMessage("AssemblyLoadTrimming", "IL3050:RequiresUnreferencedCode", Justification = "JsonSerializerOptions provided here has TypeInfoResolver set")]
[UnconditionalSuppressMessage("AssemblyLoadTrimming", "IL2026:RequiresUnreferencedCode", Justification = "JsonSerializerOptions provided here has TypeInfoResolver set")]
[UnconditionalSuppressMessage("AssemblyLoadTrimming", "IL3050:RequiresUnreferencedCode", Justification = "JsonSerializerOptions provided here has TypeInfoResolver set")]
[UnconditionalSuppressMessage("AssemblyLoadTrimming", "IL2026:RequiresUnreferencedCode", Justification = "JsonSerializerOptions provided here has TypeInfoResolver set")]
#endif
public override T? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
if (reader.TokenType == JsonTokenType.Null)
return default;
public override void Write(Utf8JsonWriter writer, T? value, JsonSerializerOptions options)
{
if (value is null)
writer.WriteStringValue("");
var value = reader.GetString();
if (string.IsNullOrEmpty(value))
return default;
return (T?)JsonDocument.Parse(value!).Deserialize(typeof(T), options);
}
/// <inheritdoc />
#if NET5_0_OR_GREATER
[UnconditionalSuppressMessage("AssemblyLoadTrimming", "IL3050:RequiresUnreferencedCode", Justification = "JsonSerializerOptions provided here has TypeInfoResolver set")]
[UnconditionalSuppressMessage("AssemblyLoadTrimming", "IL2026:RequiresUnreferencedCode", Justification = "JsonSerializerOptions provided here has TypeInfoResolver set")]
#endif
public override void Write(Utf8JsonWriter writer, T? value, JsonSerializerOptions options)
{
if (value is null)
writer.WriteStringValue("");
writer.WriteStringValue(JsonSerializer.Serialize(value, options));
}
writer.WriteStringValue(JsonSerializer.Serialize(value, options));
}
}

View File

@ -1,41 +1,40 @@
using System;
using System;
using System.Linq;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace CryptoExchange.Net.Converters.SystemTextJson
namespace CryptoExchange.Net.Converters.SystemTextJson;
/// <summary>
/// Replace a value on a string property
/// </summary>
public abstract class ReplaceConverter : JsonConverter<string>
{
private readonly (string ValueToReplace, string ValueToReplaceWith)[] _replacementSets;
/// <summary>
/// Replace a value on a string property
/// ctor
/// </summary>
public abstract class ReplaceConverter : JsonConverter<string>
public ReplaceConverter(params string[] replaceSets)
{
private readonly (string ValueToReplace, string ValueToReplaceWith)[] _replacementSets;
/// <summary>
/// ctor
/// </summary>
public ReplaceConverter(params string[] replaceSets)
_replacementSets = replaceSets.Select(x =>
{
_replacementSets = replaceSets.Select(x =>
{
var split = x.Split(new string[] { "->" }, StringSplitOptions.None);
if (split.Length != 2)
throw new ArgumentException("Invalid replacement config");
return (split[0], split[1]);
}).ToArray();
}
/// <inheritdoc />
public override string? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
var value = reader.GetString();
foreach (var set in _replacementSets)
value = value?.Replace(set.ValueToReplace, set.ValueToReplaceWith);
return value;
}
/// <inheritdoc />
public override void Write(Utf8JsonWriter writer, string value, JsonSerializerOptions options) => writer.WriteStringValue(value);
var split = x.Split(["->"], StringSplitOptions.None);
if (split.Length != 2)
throw new ArgumentException("Invalid replacement config");
return (split[0], split[1]);
}).ToArray();
}
/// <inheritdoc />
public override string? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
var value = reader.GetString();
foreach (var set in _replacementSets)
value = value?.Replace(set.ValueToReplace, set.ValueToReplaceWith);
return value;
}
/// <inheritdoc />
public override void Write(Utf8JsonWriter writer, string value, JsonSerializerOptions options) => writer.WriteStringValue(value);
}

View File

@ -1,23 +1,20 @@
using System;
using System.Collections.Generic;
using System.Text;
using System;
namespace CryptoExchange.Net.Converters.SystemTextJson
namespace CryptoExchange.Net.Converters.SystemTextJson;
/// <summary>
/// Attribute to mark a model as json serializable. Used for AOT compilation.
/// </summary>
[AttributeUsage(System.AttributeTargets.Class | AttributeTargets.Enum | System.AttributeTargets.Interface)]
public class SerializationModelAttribute : Attribute
{
/// <summary>
/// Attribute to mark a model as json serializable. Used for AOT compilation.
/// ctor
/// </summary>
[AttributeUsage(System.AttributeTargets.Class | AttributeTargets.Enum | System.AttributeTargets.Interface)]
public class SerializationModelAttribute : Attribute
{
/// <summary>
/// ctor
/// </summary>
public SerializationModelAttribute() { }
/// <summary>
/// ctor
/// </summary>
/// <param name="type"></param>
public SerializationModelAttribute(Type type) { }
}
public SerializationModelAttribute() { }
/// <summary>
/// ctor
/// </summary>
/// <param name="type"></param>
public SerializationModelAttribute(Type type) { }
}

View File

@ -1,47 +1,46 @@
using System.Collections.Concurrent;
using System.Collections.Concurrent;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace CryptoExchange.Net.Converters.SystemTextJson
namespace CryptoExchange.Net.Converters.SystemTextJson;
/// <summary>
/// Serializer options
/// </summary>
public static class SerializerOptions
{
private static readonly ConcurrentDictionary<JsonSerializerContext, JsonSerializerOptions> _cache = new ConcurrentDictionary<JsonSerializerContext, JsonSerializerOptions>();
/// <summary>
/// Serializer options
/// Get Json serializer settings which includes standard converters for DateTime, bool, enum and number types
/// </summary>
public static class SerializerOptions
public static JsonSerializerOptions WithConverters(JsonSerializerContext typeResolver, params JsonConverter[] additionalConverters)
{
private static readonly ConcurrentDictionary<JsonSerializerContext, JsonSerializerOptions> _cache = new ConcurrentDictionary<JsonSerializerContext, JsonSerializerOptions>();
/// <summary>
/// Get Json serializer settings which includes standard converters for DateTime, bool, enum and number types
/// </summary>
public static JsonSerializerOptions WithConverters(JsonSerializerContext typeResolver, params JsonConverter[] additionalConverters)
if (!_cache.TryGetValue(typeResolver, out var options))
{
if (!_cache.TryGetValue(typeResolver, out var options))
options = new JsonSerializerOptions
{
options = new JsonSerializerOptions
NumberHandling = JsonNumberHandling.AllowReadingFromString | JsonNumberHandling.AllowNamedFloatingPointLiterals,
PropertyNameCaseInsensitive = false,
Converters =
{
NumberHandling = JsonNumberHandling.AllowReadingFromString | JsonNumberHandling.AllowNamedFloatingPointLiterals,
PropertyNameCaseInsensitive = false,
Converters =
{
new DateTimeConverter(),
new BoolConverter(),
new DecimalConverter(),
new IntConverter(),
new LongConverter(),
new NullableEnumConverterFactory(typeResolver)
},
TypeInfoResolver = typeResolver,
};
new DateTimeConverter(),
new BoolConverter(),
new DecimalConverter(),
new IntConverter(),
new LongConverter(),
new NullableEnumConverterFactory(typeResolver)
},
TypeInfoResolver = typeResolver,
};
foreach (var converter in additionalConverters)
options.Converters.Add(converter);
foreach (var converter in additionalConverters)
options.Converters.Add(converter);
options.TypeInfoResolver = typeResolver;
_cache.TryAdd(typeResolver, options);
}
return options;
options.TypeInfoResolver = typeResolver;
_cache.TryAdd(typeResolver, options);
}
return options;
}
}

View File

@ -1,60 +1,57 @@
using CryptoExchange.Net.SharedApis;
using CryptoExchange.Net.SharedApis;
using System;
using System.Collections.Generic;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace CryptoExchange.Net.Converters.SystemTextJson
namespace CryptoExchange.Net.Converters.SystemTextJson;
internal class SharedQuantityConverter : SharedQuantityReferenceConverter<SharedQuantity> { }
internal class SharedOrderQuantityConverter : SharedQuantityReferenceConverter<SharedOrderQuantity> { }
internal class SharedQuantityReferenceConverter<T> : JsonConverter<T> where T: SharedQuantityReference, new()
{
internal class SharedQuantityConverter : SharedQuantityReferenceConverter<SharedQuantity> { }
internal class SharedOrderQuantityConverter : SharedQuantityReferenceConverter<SharedOrderQuantity> { }
internal class SharedQuantityReferenceConverter<T> : JsonConverter<T> where T: SharedQuantityReference, new()
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)
throw new Exception("");
if (reader.TokenType != JsonTokenType.StartArray)
throw new Exception("");
reader.Read(); // Start array
var baseQuantity = reader.TokenType == JsonTokenType.Null ? (decimal?)null : reader.GetDecimal();
reader.Read();
var quoteQuantity = reader.TokenType == JsonTokenType.Null ? (decimal?)null : reader.GetDecimal();
reader.Read();
var contractQuantity = reader.TokenType == JsonTokenType.Null ? (decimal?)null : reader.GetDecimal();
reader.Read();
reader.Read(); // Start array
var baseQuantity = reader.TokenType == JsonTokenType.Null ? (decimal?)null : reader.GetDecimal();
reader.Read();
var quoteQuantity = reader.TokenType == JsonTokenType.Null ? (decimal?)null : reader.GetDecimal();
reader.Read();
var contractQuantity = reader.TokenType == JsonTokenType.Null ? (decimal?)null : reader.GetDecimal();
reader.Read();
if (reader.TokenType != JsonTokenType.EndArray)
throw new Exception("");
if (reader.TokenType != JsonTokenType.EndArray)
throw new Exception("");
reader.Read(); // End array
reader.Read(); // End array
var result = new T();
result.QuantityInBaseAsset = baseQuantity;
result.QuantityInQuoteAsset = quoteQuantity;
result.QuantityInContracts = contractQuantity;
return result;
}
var result = new T();
result.QuantityInBaseAsset = baseQuantity;
result.QuantityInQuoteAsset = quoteQuantity;
result.QuantityInContracts = contractQuantity;
return result;
}
public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options)
{
writer.WriteStartArray();
if (value.QuantityInBaseAsset == null)
writer.WriteNullValue();
else
writer.WriteNumberValue(value.QuantityInBaseAsset.Value);
public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options)
{
writer.WriteStartArray();
if (value.QuantityInBaseAsset == null)
writer.WriteNullValue();
else
writer.WriteNumberValue(value.QuantityInBaseAsset.Value);
if (value.QuantityInQuoteAsset == null)
writer.WriteNullValue();
else
writer.WriteNumberValue(value.QuantityInQuoteAsset.Value);
if (value.QuantityInQuoteAsset == null)
writer.WriteNullValue();
else
writer.WriteNumberValue(value.QuantityInQuoteAsset.Value);
if (value.QuantityInContracts == null)
writer.WriteNullValue();
else
writer.WriteNumberValue(value.QuantityInContracts.Value);
writer.WriteEndArray();
}
if (value.QuantityInContracts == null)
writer.WriteNullValue();
else
writer.WriteNumberValue(value.QuantityInContracts.Value);
writer.WriteEndArray();
}
}

View File

@ -1,46 +1,43 @@
using CryptoExchange.Net.SharedApis;
using CryptoExchange.Net.SharedApis;
using System;
using System.Collections.Generic;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace CryptoExchange.Net.Converters.SystemTextJson
namespace CryptoExchange.Net.Converters.SystemTextJson;
internal class SharedSymbolConverter : JsonConverter<SharedSymbol>
{
internal class SharedSymbolConverter : JsonConverter<SharedSymbol>
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)
throw new Exception("");
if (reader.TokenType != JsonTokenType.StartArray)
throw new Exception("");
reader.Read(); // Start array
var tradingMode = (TradingMode)Enum.Parse(typeof(TradingMode), reader.GetString()!);
reader.Read();
var baseAsset = reader.GetString()!;
reader.Read();
var quoteAsset = reader.GetString()!;
reader.Read();
var timeStr = reader.GetString()!;
var deliverTime = string.IsNullOrEmpty(timeStr) ? (DateTime?)null : DateTime.Parse(timeStr);
reader.Read();
reader.Read(); // Start array
var tradingMode = (TradingMode)Enum.Parse(typeof(TradingMode), reader.GetString()!);
reader.Read();
var baseAsset = reader.GetString()!;
reader.Read();
var quoteAsset = reader.GetString()!;
reader.Read();
var timeStr = reader.GetString()!;
var deliverTime = string.IsNullOrEmpty(timeStr) ? (DateTime?)null : DateTime.Parse(timeStr);
reader.Read();
if (reader.TokenType != JsonTokenType.EndArray)
throw new Exception("");
if (reader.TokenType != JsonTokenType.EndArray)
throw new Exception("");
reader.Read(); // End array
reader.Read(); // End array
return new SharedSymbol(tradingMode, baseAsset, quoteAsset, deliverTime);
}
return new SharedSymbol(tradingMode, baseAsset, quoteAsset, deliverTime);
}
public override void Write(Utf8JsonWriter writer, SharedSymbol value, JsonSerializerOptions options)
{
writer.WriteStartArray();
writer.WriteStringValue(value.TradingMode.ToString());
writer.WriteStringValue(value.BaseAsset);
writer.WriteStringValue(value.QuoteAsset);
writer.WriteStringValue(value.DeliverTime?.ToString());
writer.WriteEndArray();
}
public override void Write(Utf8JsonWriter writer, SharedSymbol value, JsonSerializerOptions options)
{
writer.WriteStartArray();
writer.WriteStringValue(value.TradingMode.ToString());
writer.WriteStringValue(value.BaseAsset);
writer.WriteStringValue(value.QuoteAsset);
writer.WriteStringValue(value.DeliverTime?.ToString());
writer.WriteEndArray();
}
}

View File

@ -1,374 +1,376 @@
using CryptoExchange.Net.Converters.MessageParsing;
using CryptoExchange.Net.Converters.MessageParsing;
using CryptoExchange.Net.Interfaces;
using CryptoExchange.Net.Objects;
using System;
using System.Collections.Generic;
#if NET5_0_OR_GREATER
using System.Diagnostics.CodeAnalysis;
#endif
using System.IO;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
namespace CryptoExchange.Net.Converters.SystemTextJson
namespace CryptoExchange.Net.Converters.SystemTextJson;
/// <summary>
/// System.Text.Json message accessor
/// </summary>
public abstract class SystemTextJsonMessageAccessor : IMessageAccessor
{
/// <summary>
/// System.Text.Json message accessor
/// The JsonDocument loaded
/// </summary>
public abstract class SystemTextJsonMessageAccessor : IMessageAccessor
{
/// <summary>
/// The JsonDocument loaded
/// </summary>
protected JsonDocument? _document;
protected JsonDocument? _document;
private readonly JsonSerializerOptions? _customSerializerOptions;
private readonly JsonSerializerOptions? _customSerializerOptions;
/// <inheritdoc />
public bool IsValid { get; set; }
/// <inheritdoc />
public bool IsValid { get; set; }
/// <inheritdoc />
public abstract bool OriginalDataAvailable { get; }
/// <inheritdoc />
public abstract bool OriginalDataAvailable { get; }
/// <inheritdoc />
public object? Underlying => throw new NotImplementedException();
/// <summary>
/// ctor
/// </summary>
public SystemTextJsonMessageAccessor(JsonSerializerOptions options)
{
_customSerializerOptions = options;
}
/// <inheritdoc />
#if NET5_0_OR_GREATER
[UnconditionalSuppressMessage("AssemblyLoadTrimming", "IL2026:RequiresUnreferencedCode", Justification = "JsonSerializerOptions provided here has TypeInfoResolver set")]
[UnconditionalSuppressMessage("AssemblyLoadTrimming", "IL3050:RequiresUnreferencedCode", Justification = "JsonSerializerOptions provided here has TypeInfoResolver set")]
#endif
public CallResult<object> Deserialize(Type type, MessagePath? path = null)
{
if (!IsValid)
return new CallResult<object>(GetOriginalString());
if (_document == null)
throw new InvalidOperationException("No json document loaded");
try
{
var result = _document.Deserialize(type, _customSerializerOptions);
return new CallResult<object>(result!);
}
catch (JsonException ex)
{
var info = $"Json deserialization failed: {ex.Message}, Path: {ex.Path}, LineNumber: {ex.LineNumber}, LinePosition: {ex.BytePositionInLine}";
return new CallResult<object>(new DeserializeError(info, ex));
}
catch (Exception ex)
{
return new CallResult<object>(new DeserializeError($"Json deserialization failed: {ex.Message}", ex));
}
}
/// <inheritdoc />
#if NET5_0_OR_GREATER
[UnconditionalSuppressMessage("AssemblyLoadTrimming", "IL2026:RequiresUnreferencedCode", Justification = "JsonSerializerOptions provided here has TypeInfoResolver set")]
[UnconditionalSuppressMessage("AssemblyLoadTrimming", "IL3050:RequiresUnreferencedCode", Justification = "JsonSerializerOptions provided here has TypeInfoResolver set")]
#endif
public CallResult<T> Deserialize<T>(MessagePath? path = null)
{
if (_document == null)
throw new InvalidOperationException("No json document loaded");
try
{
var result = _document.Deserialize<T>(_customSerializerOptions);
return new CallResult<T>(result!);
}
catch (JsonException ex)
{
var info = $"Json deserialization failed: {ex.Message}, Path: {ex.Path}, LineNumber: {ex.LineNumber}, LinePosition: {ex.BytePositionInLine}";
return new CallResult<T>(new DeserializeError(info, ex));
}
catch (Exception ex)
{
return new CallResult<T>(new DeserializeError($"Json deserialization failed: {ex.Message}", ex));
}
}
/// <inheritdoc />
public NodeType? GetNodeType()
{
if (!IsValid)
throw new InvalidOperationException("Can't access json data on non-json message");
if (_document == null)
throw new InvalidOperationException("No json document loaded");
return _document.RootElement.ValueKind switch
{
JsonValueKind.Object => NodeType.Object,
JsonValueKind.Array => NodeType.Array,
_ => NodeType.Value
};
}
/// <inheritdoc />
public NodeType? GetNodeType(MessagePath path)
{
if (!IsValid)
throw new InvalidOperationException("Can't access json data on non-json message");
var node = GetPathNode(path);
if (!node.HasValue)
return null;
return node.Value.ValueKind switch
{
JsonValueKind.Object => NodeType.Object,
JsonValueKind.Array => NodeType.Array,
_ => NodeType.Value
};
}
/// <inheritdoc />
#if NET5_0_OR_GREATER
[UnconditionalSuppressMessage("AssemblyLoadTrimming", "IL2026:RequiresUnreferencedCode", Justification = "JsonSerializerOptions provided here has TypeInfoResolver set")]
[UnconditionalSuppressMessage("AssemblyLoadTrimming", "IL3050:RequiresUnreferencedCode", Justification = "JsonSerializerOptions provided here has TypeInfoResolver set")]
#endif
public T? GetValue<T>(MessagePath path)
{
if (!IsValid)
throw new InvalidOperationException("Can't access json data on non-json message");
var value = GetPathNode(path);
if (value == null)
return default;
if (value.Value.ValueKind == JsonValueKind.Object || value.Value.ValueKind == JsonValueKind.Array)
{
try
{
return value.Value.Deserialize<T>(_customSerializerOptions);
}
catch { }
return default;
}
if (typeof(T) == typeof(string))
{
if (value.Value.ValueKind == JsonValueKind.Number)
return (T)(object)value.Value.GetInt64().ToString();
}
return value.Value.Deserialize<T>(_customSerializerOptions);
}
/// <inheritdoc />
#if NET5_0_OR_GREATER
[UnconditionalSuppressMessage("AssemblyLoadTrimming", "IL2026:RequiresUnreferencedCode", Justification = "JsonSerializerOptions provided here has TypeInfoResolver set")]
[UnconditionalSuppressMessage("AssemblyLoadTrimming", "IL3050:RequiresUnreferencedCode", Justification = "JsonSerializerOptions provided here has TypeInfoResolver set")]
#endif
public T?[]? GetValues<T>(MessagePath path)
{
if (!IsValid)
throw new InvalidOperationException("Can't access json data on non-json message");
var value = GetPathNode(path);
if (value == null)
return default;
if (value.Value.ValueKind != JsonValueKind.Array)
return default;
return value.Value.Deserialize<T[]>(_customSerializerOptions)!;
}
private JsonElement? GetPathNode(MessagePath path)
{
if (!IsValid)
throw new InvalidOperationException("Can't access json data on non-json message");
if (_document == null)
throw new InvalidOperationException("No json document loaded");
JsonElement? currentToken = _document.RootElement;
foreach (var node in path)
{
if (node.Type == 0)
{
// Int value
var val = node.Index!.Value;
if (currentToken!.Value.ValueKind != JsonValueKind.Array || currentToken.Value.GetArrayLength() <= val)
return null;
currentToken = currentToken.Value[val];
}
else if (node.Type == 1)
{
// String value
if (currentToken!.Value.ValueKind != JsonValueKind.Object)
return null;
if (!currentToken.Value.TryGetProperty(node.Property!, out var token))
return null;
currentToken = token;
}
else
{
// Property name
if (currentToken!.Value.ValueKind != JsonValueKind.Object)
return null;
throw new NotImplementedException();
}
if (currentToken == null)
return null;
}
return currentToken;
}
/// <inheritdoc />
public abstract string GetOriginalString();
/// <inheritdoc />
public abstract void Clear();
}
/// <inheritdoc />
public object? Underlying => throw new NotImplementedException();
/// <summary>
/// System.Text.Json stream message accessor
/// ctor
/// </summary>
public class SystemTextJsonStreamMessageAccessor : SystemTextJsonMessageAccessor, IStreamMessageAccessor
public SystemTextJsonMessageAccessor(JsonSerializerOptions options)
{
private Stream? _stream;
_customSerializerOptions = options;
}
/// <inheritdoc />
public override bool OriginalDataAvailable => _stream?.CanSeek == true;
/// <inheritdoc />
#if NET5_0_OR_GREATER
[UnconditionalSuppressMessage("AssemblyLoadTrimming", "IL2026:RequiresUnreferencedCode", Justification = "JsonSerializerOptions provided here has TypeInfoResolver set")]
[UnconditionalSuppressMessage("AssemblyLoadTrimming", "IL3050:RequiresUnreferencedCode", Justification = "JsonSerializerOptions provided here has TypeInfoResolver set")]
#endif
public CallResult<object> Deserialize(Type type, MessagePath? path = null)
{
if (!IsValid)
return new CallResult<object>(GetOriginalString());
/// <summary>
/// ctor
/// </summary>
public SystemTextJsonStreamMessageAccessor(JsonSerializerOptions options): base(options)
if (_document == null)
throw new InvalidOperationException("No json document loaded");
try
{
var result = _document.Deserialize(type, _customSerializerOptions);
return new CallResult<object>(result!);
}
catch (JsonException ex)
{
var info = $"Json deserialization failed: {ex.Message}, Path: {ex.Path}, LineNumber: {ex.LineNumber}, LinePosition: {ex.BytePositionInLine}";
return new CallResult<object>(new DeserializeError(info, ex));
}
catch (Exception ex)
{
return new CallResult<object>(new DeserializeError($"Json deserialization failed: {ex.Message}", ex));
}
}
/// <inheritdoc />
#if NET5_0_OR_GREATER
[UnconditionalSuppressMessage("AssemblyLoadTrimming", "IL2026:RequiresUnreferencedCode", Justification = "JsonSerializerOptions provided here has TypeInfoResolver set")]
[UnconditionalSuppressMessage("AssemblyLoadTrimming", "IL3050:RequiresUnreferencedCode", Justification = "JsonSerializerOptions provided here has TypeInfoResolver set")]
#endif
public CallResult<T> Deserialize<T>(MessagePath? path = null)
{
if (_document == null)
throw new InvalidOperationException("No json document loaded");
try
{
var result = _document.Deserialize<T>(_customSerializerOptions);
return new CallResult<T>(result!);
}
catch (JsonException ex)
{
var info = $"Json deserialization failed: {ex.Message}, Path: {ex.Path}, LineNumber: {ex.LineNumber}, LinePosition: {ex.BytePositionInLine}";
return new CallResult<T>(new DeserializeError(info, ex));
}
catch (Exception ex)
{
return new CallResult<T>(new DeserializeError($"Json deserialization failed: {ex.Message}", ex));
}
}
/// <inheritdoc />
public NodeType? GetNodeType()
{
if (!IsValid)
throw new InvalidOperationException("Can't access json data on non-json message");
if (_document == null)
throw new InvalidOperationException("No json document loaded");
return _document.RootElement.ValueKind switch
{
JsonValueKind.Object => NodeType.Object,
JsonValueKind.Array => NodeType.Array,
_ => NodeType.Value
};
}
/// <inheritdoc />
public NodeType? GetNodeType(MessagePath path)
{
if (!IsValid)
throw new InvalidOperationException("Can't access json data on non-json message");
var node = GetPathNode(path);
if (!node.HasValue)
return null;
return node.Value.ValueKind switch
{
JsonValueKind.Object => NodeType.Object,
JsonValueKind.Array => NodeType.Array,
_ => NodeType.Value
};
}
/// <inheritdoc />
#if NET5_0_OR_GREATER
[UnconditionalSuppressMessage("AssemblyLoadTrimming", "IL2026:RequiresUnreferencedCode", Justification = "JsonSerializerOptions provided here has TypeInfoResolver set")]
[UnconditionalSuppressMessage("AssemblyLoadTrimming", "IL3050:RequiresUnreferencedCode", Justification = "JsonSerializerOptions provided here has TypeInfoResolver set")]
#endif
public T? GetValue<T>(MessagePath path)
{
if (!IsValid)
throw new InvalidOperationException("Can't access json data on non-json message");
var value = GetPathNode(path);
if (value == null)
return default;
if (value.Value.ValueKind == JsonValueKind.Object || value.Value.ValueKind == JsonValueKind.Array)
{
try
{
return value.Value.Deserialize<T>(_customSerializerOptions);
}
catch { }
return default;
}
/// <inheritdoc />
public async Task<CallResult> Read(Stream stream, bool bufferStream)
if (typeof(T) == typeof(string))
{
if (bufferStream && stream is not MemoryStream)
if (value.Value.ValueKind == JsonValueKind.Number)
return (T)(object)value.Value.GetInt64().ToString();
}
return value.Value.Deserialize<T>(_customSerializerOptions);
}
/// <inheritdoc />
#if NET5_0_OR_GREATER
[UnconditionalSuppressMessage("AssemblyLoadTrimming", "IL2026:RequiresUnreferencedCode", Justification = "JsonSerializerOptions provided here has TypeInfoResolver set")]
[UnconditionalSuppressMessage("AssemblyLoadTrimming", "IL3050:RequiresUnreferencedCode", Justification = "JsonSerializerOptions provided here has TypeInfoResolver set")]
#endif
public T?[]? GetValues<T>(MessagePath path)
{
if (!IsValid)
throw new InvalidOperationException("Can't access json data on non-json message");
var value = GetPathNode(path);
if (value == null)
return default;
if (value.Value.ValueKind != JsonValueKind.Array)
return default;
return value.Value.Deserialize<T[]>(_customSerializerOptions)!;
}
private JsonElement? GetPathNode(MessagePath path)
{
if (!IsValid)
throw new InvalidOperationException("Can't access json data on non-json message");
if (_document == null)
throw new InvalidOperationException("No json document loaded");
JsonElement? currentToken = _document.RootElement;
foreach (var node in path)
{
if (node.Type == 0)
{
// We need to be buffer the stream, and it's not currently a seekable stream, so copy it to a new memory stream
_stream = new MemoryStream();
stream.CopyTo(_stream);
_stream.Position = 0;
// Int value
var val = node.Index!.Value;
if (currentToken!.Value.ValueKind != JsonValueKind.Array || currentToken.Value.GetArrayLength() <= val)
return null;
currentToken = currentToken.Value[val];
}
else if (bufferStream)
else if (node.Type == 1)
{
// We need to buffer the stream, and the current stream is seekable, store as is
_stream = stream;
// String value
if (currentToken!.Value.ValueKind != JsonValueKind.Object)
return null;
if (!currentToken.Value.TryGetProperty(node.Property!, out var token))
return null;
currentToken = token;
}
else
{
// We don't need to buffer the stream, so don't bother keeping the reference
// Property name
if (currentToken!.Value.ValueKind != JsonValueKind.Object)
return null;
throw new NotImplementedException();
}
try
{
_document = await JsonDocument.ParseAsync(_stream ?? stream).ConfigureAwait(false);
IsValid = true;
return CallResult.SuccessResult;
}
catch (Exception ex)
{
// Not a json message
IsValid = false;
return new CallResult(new DeserializeError($"Json deserialization failed: {ex.Message}", ex));
}
}
/// <inheritdoc />
public override string GetOriginalString()
{
if (_stream is null)
throw new NullReferenceException("Stream not initialized");
_stream.Position = 0;
using var textReader = new StreamReader(_stream, Encoding.UTF8, false, 1024, true);
return textReader.ReadToEnd();
}
/// <inheritdoc />
public override void Clear()
{
_stream?.Dispose();
_stream = null;
_document?.Dispose();
_document = null;
if (currentToken == null)
return null;
}
return currentToken;
}
/// <inheritdoc />
public abstract string GetOriginalString();
/// <inheritdoc />
public abstract void Clear();
}
/// <summary>
/// System.Text.Json stream message accessor
/// </summary>
#pragma warning disable CA1001 // Types that own disposable fields should be disposable
public class SystemTextJsonStreamMessageAccessor : SystemTextJsonMessageAccessor, IStreamMessageAccessor
#pragma warning restore CA1001 // Types that own disposable fields should be disposable
{
private Stream? _stream;
/// <inheritdoc />
public override bool OriginalDataAvailable => _stream?.CanSeek == true;
/// <summary>
/// System.Text.Json byte message accessor
/// ctor
/// </summary>
public class SystemTextJsonByteMessageAccessor : SystemTextJsonMessageAccessor, IByteMessageAccessor
public SystemTextJsonStreamMessageAccessor(JsonSerializerOptions options): base(options)
{
private ReadOnlyMemory<byte> _bytes;
}
/// <summary>
/// ctor
/// </summary>
public SystemTextJsonByteMessageAccessor(JsonSerializerOptions options) : base(options)
/// <inheritdoc />
public async Task<CallResult> Read(Stream stream, bool bufferStream)
{
if (bufferStream && stream is not MemoryStream)
{
// We need to be buffer the stream, and it's not currently a seekable stream, so copy it to a new memory stream
_stream = new MemoryStream();
stream.CopyTo(_stream);
_stream.Position = 0;
}
else if (bufferStream)
{
// We need to buffer the stream, and the current stream is seekable, store as is
_stream = stream;
}
else
{
// We don't need to buffer the stream, so don't bother keeping the reference
}
/// <inheritdoc />
public CallResult Read(ReadOnlyMemory<byte> data)
try
{
_bytes = data;
try
{
var firstByte = data.Span[0];
if (firstByte != 0x7b && firstByte != 0x5b)
{
// Value doesn't start with `{` or `[`, prevent deserialization attempt as it's slow
IsValid = false;
return new CallResult(new DeserializeError("Not a json value"));
}
_document = JsonDocument.Parse(data);
IsValid = true;
return CallResult.SuccessResult;
}
catch (Exception ex)
{
// Not a json message
IsValid = false;
return new CallResult(new DeserializeError($"Json deserialization failed: {ex.Message}", ex));
}
_document = await JsonDocument.ParseAsync(_stream ?? stream).ConfigureAwait(false);
IsValid = true;
return CallResult.SuccessResult;
}
/// <inheritdoc />
public override string GetOriginalString() =>
// NetStandard 2.0 doesn't support GetString from a ReadonlySpan<byte>, so use ToArray there instead
#if NETSTANDARD2_0
Encoding.UTF8.GetString(_bytes.ToArray());
#else
Encoding.UTF8.GetString(_bytes.Span);
#endif
/// <inheritdoc />
public override bool OriginalDataAvailable => true;
/// <inheritdoc />
public override void Clear()
catch (Exception ex)
{
_bytes = null;
_document?.Dispose();
_document = null;
// Not a json message
IsValid = false;
return new CallResult(new DeserializeError($"Json deserialization failed: {ex.Message}", ex));
}
}
}
/// <inheritdoc />
public override string GetOriginalString()
{
if (_stream is null)
throw new NullReferenceException("Stream not initialized");
_stream.Position = 0;
using var textReader = new StreamReader(_stream, Encoding.UTF8, false, 1024, true);
return textReader.ReadToEnd();
}
/// <inheritdoc />
public override void Clear()
{
_stream?.Dispose();
_stream = null;
_document?.Dispose();
_document = null;
}
}
/// <summary>
/// System.Text.Json byte message accessor
/// </summary>
public class SystemTextJsonByteMessageAccessor : SystemTextJsonMessageAccessor, IByteMessageAccessor
{
private ReadOnlyMemory<byte> _bytes;
/// <summary>
/// ctor
/// </summary>
public SystemTextJsonByteMessageAccessor(JsonSerializerOptions options) : base(options)
{
}
/// <inheritdoc />
public CallResult Read(ReadOnlyMemory<byte> data)
{
_bytes = data;
try
{
var firstByte = data.Span[0];
if (firstByte != 0x7b && firstByte != 0x5b)
{
// Value doesn't start with `{` or `[`, prevent deserialization attempt as it's slow
IsValid = false;
return new CallResult(new DeserializeError("Not a json value"));
}
_document = JsonDocument.Parse(data);
IsValid = true;
return CallResult.SuccessResult;
}
catch (Exception ex)
{
// Not a json message
IsValid = false;
return new CallResult(new DeserializeError($"Json deserialization failed: {ex.Message}", ex));
}
}
/// <inheritdoc />
public override string GetOriginalString() =>
// NetStandard 2.0 doesn't support GetString from a ReadonlySpan<byte>, so use ToArray there instead
#if NETSTANDARD2_0
Encoding.UTF8.GetString(_bytes.ToArray());
#else
Encoding.UTF8.GetString(_bytes.Span);
#endif
/// <inheritdoc />
public override bool OriginalDataAvailable => true;
/// <inheritdoc />
public override void Clear()
{
_bytes = null;
_document?.Dispose();
_document = null;
}
}

View File

@ -1,29 +1,28 @@
using CryptoExchange.Net.Interfaces;
using System.Diagnostics.CodeAnalysis;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Text.Json.Serialization.Metadata;
namespace CryptoExchange.Net.Converters.SystemTextJson
{
/// <inheritdoc />
public class SystemTextJsonMessageSerializer : IStringMessageSerializer
{
private readonly JsonSerializerOptions _options;
/// <summary>
/// ctor
/// </summary>
public SystemTextJsonMessageSerializer(JsonSerializerOptions options)
{
_options = options;
}
/// <inheritdoc />
using CryptoExchange.Net.Interfaces;
#if NET5_0_OR_GREATER
[UnconditionalSuppressMessage("AssemblyLoadTrimming", "IL2026:RequiresUnreferencedCode", Justification = "Everything referenced in the loaded assembly is manually preserved, so it's safe")]
[UnconditionalSuppressMessage("AssemblyLoadTrimming", "IL3050:RequiresUnreferencedCode", Justification = "Everything referenced in the loaded assembly is manually preserved, so it's safe")]
using System.Diagnostics.CodeAnalysis;
#endif
public string Serialize<T>(T message) => JsonSerializer.Serialize(message, _options);
using System.Text.Json;
namespace CryptoExchange.Net.Converters.SystemTextJson;
/// <inheritdoc />
public class SystemTextJsonMessageSerializer : IStringMessageSerializer
{
private readonly JsonSerializerOptions _options;
/// <summary>
/// ctor
/// </summary>
public SystemTextJsonMessageSerializer(JsonSerializerOptions options)
{
_options = options;
}
/// <inheritdoc />
#if NET5_0_OR_GREATER
[UnconditionalSuppressMessage("AssemblyLoadTrimming", "IL2026:RequiresUnreferencedCode", Justification = "Everything referenced in the loaded assembly is manually preserved, so it's safe")]
[UnconditionalSuppressMessage("AssemblyLoadTrimming", "IL3050:RequiresUnreferencedCode", Justification = "Everything referenced in the loaded assembly is manually preserved, so it's safe")]
#endif
public string Serialize<T>(T message) => JsonSerializer.Serialize(message, _options);
}

View File

@ -24,6 +24,7 @@
<PackageLicenseExpression>MIT</PackageLicenseExpression>
</PropertyGroup>
<ItemGroup>
<None Include="C:\Projects\CryptoExchange.Net\CryptoExchange.Net\.editorconfig" />
<None Include="Icon\icon.png" Pack="true" PackagePath="\" />
<None Include="..\README.md" Pack="true" PackagePath="\" />
</ItemGroup>
@ -40,6 +41,12 @@
<PropertyGroup>
<DocumentationFile>CryptoExchange.Net.xml</DocumentationFile>
</PropertyGroup>
<PropertyGroup>
<EnableNETAnalyzers>true</EnableNETAnalyzers>
<AnalysisMode>Recommended</AnalysisMode>
<AnalysisModeGlobalization>None</AnalysisModeGlobalization>
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="ConfigureAwaitChecker.Analyzer" Version="5.0.0.1">
<PrivateAssets>all</PrivateAssets>
@ -58,4 +65,7 @@
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="9.0.6" />
<PackageReference Include="Microsoft.Extensions.Http" Version="9.0.6" />
</ItemGroup>
<ItemGroup>
<EditorConfigFiles Remove="C:\Projects\CryptoExchange.Net\CryptoExchange.Net\.editorconfig" />
</ItemGroup>
</Project>

View File

@ -1,389 +1,390 @@
using CryptoExchange.Net.Objects;
using CryptoExchange.Net.Objects;
using CryptoExchange.Net.SharedApis;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Runtime.CompilerServices;
#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER
using System.Security.Cryptography;
#endif
using System.Threading;
using System.Threading.Tasks;
namespace CryptoExchange.Net
namespace CryptoExchange.Net;
/// <summary>
/// General helpers functions
/// </summary>
public static class ExchangeHelpers
{
/// <summary>
/// General helpers functions
/// </summary>
public static class ExchangeHelpers
private const string _allowedRandomChars = "ABCDEFGHIJKLMONOPQRSTUVWXYZabcdefghijklmonopqrstuvwxyz0123456789";
private const string _allowedRandomHexChars = "0123456789ABCDEF";
private static readonly Dictionary<int, string> _monthSymbols = new Dictionary<int, string>()
{
private const string _allowedRandomChars = "ABCDEFGHIJKLMONOPQRSTUVWXYZabcdefghijklmonopqrstuvwxyz0123456789";
private const string _allowedRandomHexChars = "0123456789ABCDEF";
{ 1, "F" },
{ 2, "G" },
{ 3, "H" },
{ 4, "J" },
{ 5, "K" },
{ 6, "M" },
{ 7, "N" },
{ 8, "Q" },
{ 9, "U" },
{ 10, "V" },
{ 11, "X" },
{ 12, "Z" },
};
private static readonly Dictionary<int, string> _monthSymbols = new Dictionary<int, string>()
{
{ 1, "F" },
{ 2, "G" },
{ 3, "H" },
{ 4, "J" },
{ 5, "K" },
{ 6, "M" },
{ 7, "N" },
{ 8, "Q" },
{ 9, "U" },
{ 10, "V" },
{ 11, "X" },
{ 12, "Z" },
};
/// <summary>
/// The last used id, use NextId() to get the next id and up this
/// </summary>
private static int _lastId;
/// <summary>
/// The last used id, use NextId() to get the next id and up this
/// </summary>
private static int _lastId;
/// <summary>
/// Clamp a value between a min and max
/// </summary>
/// <param name="min"></param>
/// <param name="max"></param>
/// <param name="value"></param>
/// <returns></returns>
public static decimal ClampValue(decimal min, decimal max, decimal value)
{
value = Math.Min(max, value);
value = Math.Max(min, value);
return value;
}
/// <summary>
/// Clamp a value between a min and max
/// </summary>
/// <param name="min"></param>
/// <param name="max"></param>
/// <param name="value"></param>
/// <returns></returns>
public static decimal ClampValue(decimal min, decimal max, decimal value)
{
value = Math.Min(max, value);
value = Math.Max(min, value);
/// <summary>
/// Adjust a value to be between the min and max parameters and rounded to the closest step.
/// </summary>
/// <param name="min">The min value</param>
/// <param name="max">The max value</param>
/// <param name="step">The step size the value should be floored to. For example, value 2.548 with a step size of 0.01 will output 2.54</param>
/// <param name="roundingType">How to round</param>
/// <param name="value">The input value</param>
/// <returns></returns>
public static decimal AdjustValueStep(decimal min, decimal max, decimal? step, RoundingType roundingType, decimal value)
{
if(step == 0)
throw new ArgumentException($"0 not allowed for parameter {nameof(step)}, pass in null to ignore the step size", nameof(step));
value = Math.Min(max, value);
value = Math.Max(min, value);
if (step == null)
return value;
}
/// <summary>
/// Adjust a value to be between the min and max parameters and rounded to the closest step.
/// </summary>
/// <param name="min">The min value</param>
/// <param name="max">The max value</param>
/// <param name="step">The step size the value should be floored to. For example, value 2.548 with a step size of 0.01 will output 2.54</param>
/// <param name="roundingType">How to round</param>
/// <param name="value">The input value</param>
/// <returns></returns>
public static decimal AdjustValueStep(decimal min, decimal max, decimal? step, RoundingType roundingType, decimal value)
var offset = value % step.Value;
if(roundingType == RoundingType.Down)
{
if(step == 0)
throw new ArgumentException($"0 not allowed for parameter {nameof(step)}, pass in null to ignore the step size", nameof(step));
value = Math.Min(max, value);
value = Math.Max(min, value);
if (step == null)
return value;
var offset = value % step.Value;
if(roundingType == RoundingType.Down)
{
value -= offset;
}
else if(roundingType == RoundingType.Up)
{
if (offset != 0)
value += (step.Value - offset);
}
else
{
if (offset < step / 2)
value -= offset;
}
else if(roundingType == RoundingType.Up)
{
if (offset != 0)
value += (step.Value - offset);
}
else
{
if (offset < step / 2)
value -= offset;
else value += (step.Value - offset);
}
value = RoundDown(value, 8);
return value.Normalize();
else value += (step.Value - offset);
}
/// <summary>
/// Adjust a value to be between the min and max parameters and rounded to the closest precision.
/// </summary>
/// <param name="min">The min value</param>
/// <param name="max">The max value</param>
/// <param name="precision">The precision the value should be rounded to. For example, value 2.554215 with a precision of 5 will output 2.5542</param>
/// <param name="roundingType">How to round</param>
/// <param name="value">The input value</param>
/// <returns></returns>
public static decimal AdjustValuePrecision(decimal min, decimal max, int? precision, RoundingType roundingType, decimal value)
{
value = Math.Min(max, value);
value = Math.Max(min, value);
if (precision == null)
return value;
return RoundToSignificantDigits(value, precision.Value, roundingType);
}
/// <summary>
/// Apply the provided rules to the value
/// </summary>
/// <param name="value">Value to be adjusted</param>
/// <param name="decimals">Max decimal places</param>
/// <param name="valueStep">The value step for increase/decrease value</param>
/// <returns></returns>
public static decimal ApplyRules(
decimal value,
int? decimals = null,
decimal? valueStep = null)
{
if (valueStep.HasValue)
{
var offset = value % valueStep.Value;
if (offset != 0)
{
if (offset < valueStep.Value / 2)
value -= offset;
else value += (valueStep.Value - offset);
}
}
if (decimals.HasValue)
value = Math.Round(value, decimals.Value);
value = RoundDown(value, 8);
return value.Normalize();
}
/// <summary>
/// Adjust a value to be between the min and max parameters and rounded to the closest precision.
/// </summary>
/// <param name="min">The min value</param>
/// <param name="max">The max value</param>
/// <param name="precision">The precision the value should be rounded to. For example, value 2.554215 with a precision of 5 will output 2.5542</param>
/// <param name="roundingType">How to round</param>
/// <param name="value">The input value</param>
/// <returns></returns>
public static decimal AdjustValuePrecision(decimal min, decimal max, int? precision, RoundingType roundingType, decimal value)
{
value = Math.Min(max, value);
value = Math.Max(min, value);
if (precision == null)
return value;
}
/// <summary>
/// Round a value to have the provided total number of digits. For example, value 253.12332 with 5 digits would be 253.12
/// </summary>
/// <param name="value">The value to round</param>
/// <param name="digits">The total amount of digits (NOT decimal places) to round to</param>
/// <param name="roundingType">How to round</param>
/// <returns></returns>
public static decimal RoundToSignificantDigits(decimal value, int digits, RoundingType roundingType)
return RoundToSignificantDigits(value, precision.Value, roundingType);
}
/// <summary>
/// Apply the provided rules to the value
/// </summary>
/// <param name="value">Value to be adjusted</param>
/// <param name="decimals">Max decimal places</param>
/// <param name="valueStep">The value step for increase/decrease value</param>
/// <returns></returns>
public static decimal ApplyRules(
decimal value,
int? decimals = null,
decimal? valueStep = null)
{
if (valueStep.HasValue)
{
var val = (double)value;
if (value == 0)
return 0;
double scale = Math.Pow(10, Math.Floor(Math.Log10(Math.Abs(val))) + 1);
if(roundingType == RoundingType.Closest)
return (decimal)(scale * Math.Round(val / scale, digits));
else
return (decimal)(scale * (double)RoundDown((decimal)(val / scale), digits));
var offset = value % valueStep.Value;
if (offset != 0)
{
if (offset < valueStep.Value / 2)
value -= offset;
else value += (valueStep.Value - offset);
}
}
if (decimals.HasValue)
value = Math.Round(value, decimals.Value);
/// <summary>
/// Rounds a value down
/// </summary>
public static decimal RoundDown(decimal i, double decimalPlaces)
{
var power = Convert.ToDecimal(Math.Pow(10, decimalPlaces));
return Math.Floor(i * power) / power;
}
return value;
}
/// <summary>
/// Rounds a value up
/// </summary>
public static decimal RoundUp(decimal i, double decimalPlaces)
{
var power = Convert.ToDecimal(Math.Pow(10, decimalPlaces));
return Math.Ceiling(i * power) / power;
}
/// <summary>
/// Round a value to have the provided total number of digits. For example, value 253.12332 with 5 digits would be 253.12
/// </summary>
/// <param name="value">The value to round</param>
/// <param name="digits">The total amount of digits (NOT decimal places) to round to</param>
/// <param name="roundingType">How to round</param>
/// <returns></returns>
public static decimal RoundToSignificantDigits(decimal value, int digits, RoundingType roundingType)
{
var val = (double)value;
if (value == 0)
return 0;
/// <summary>
/// Strips any trailing zero's of a decimal value, useful when converting the value to string.
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
public static decimal Normalize(this decimal value)
{
return value / 1.000000000000000000000000000000000m;
}
double scale = Math.Pow(10, Math.Floor(Math.Log10(Math.Abs(val))) + 1);
if(roundingType == RoundingType.Closest)
return (decimal)(scale * Math.Round(val / scale, digits));
else
return (decimal)(scale * (double)RoundDown((decimal)(val / scale), digits));
}
/// <summary>
/// Generate a new unique id. The id is statically stored so it is guaranteed to be unique
/// </summary>
/// <returns></returns>
public static int NextId() => Interlocked.Increment(ref _lastId);
/// <summary>
/// Rounds a value down
/// </summary>
public static decimal RoundDown(decimal i, double decimalPlaces)
{
var power = Convert.ToDecimal(Math.Pow(10, decimalPlaces));
return Math.Floor(i * power) / power;
}
/// <summary>
/// Return the last unique id that was generated
/// </summary>
/// <returns></returns>
public static int LastId() => _lastId;
/// <summary>
/// Rounds a value up
/// </summary>
public static decimal RoundUp(decimal i, double decimalPlaces)
{
var power = Convert.ToDecimal(Math.Pow(10, decimalPlaces));
return Math.Ceiling(i * power) / power;
}
/// <summary>
/// Generate a random string of specified length
/// </summary>
/// <param name="length">Length of the random string</param>
/// <returns></returns>
public static string RandomString(int length)
{
var randomChars = new char[length];
/// <summary>
/// Strips any trailing zero's of a decimal value, useful when converting the value to string.
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
public static decimal Normalize(this decimal value)
{
return value / 1.000000000000000000000000000000000m;
}
/// <summary>
/// Generate a new unique id. The id is statically stored so it is guaranteed to be unique
/// </summary>
/// <returns></returns>
public static int NextId() => Interlocked.Increment(ref _lastId);
/// <summary>
/// Return the last unique id that was generated
/// </summary>
/// <returns></returns>
public static int LastId() => _lastId;
/// <summary>
/// Generate a random string of specified length
/// </summary>
/// <param name="length">Length of the random string</param>
/// <returns></returns>
public static string RandomString(int length)
{
var randomChars = new char[length];
#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER
for (int i = 0; i < length; i++)
randomChars[i] = _allowedRandomChars[RandomNumberGenerator.GetInt32(0, _allowedRandomChars.Length)];
for (int i = 0; i < length; i++)
randomChars[i] = _allowedRandomChars[RandomNumberGenerator.GetInt32(0, _allowedRandomChars.Length)];
#else
var random = new Random();
for (int i = 0; i < length; i++)
randomChars[i] = _allowedRandomChars[random.Next(0, _allowedRandomChars.Length)];
var random = new Random();
for (int i = 0; i < length; i++)
randomChars[i] = _allowedRandomChars[random.Next(0, _allowedRandomChars.Length)];
#endif
return new string(randomChars);
}
return new string(randomChars);
}
/// <summary>
/// Generate a random string of specified length
/// </summary>
/// <param name="length">Length of the random string</param>
/// <returns></returns>
public static string RandomHexString(int length)
{
/// <summary>
/// Generate a random string of specified length
/// </summary>
/// <param name="length">Length of the random string</param>
/// <returns></returns>
public static string RandomHexString(int length)
{
#if NET9_0_OR_GREATER
return "0x" + RandomNumberGenerator.GetHexString(length * 2);
return "0x" + RandomNumberGenerator.GetHexString(length * 2);
#else
var randomChars = new char[length * 2];
var random = new Random();
for (int i = 0; i < length * 2; i++)
randomChars[i] = _allowedRandomHexChars[random.Next(0, _allowedRandomHexChars.Length)];
return "0x" + new string(randomChars);
var randomChars = new char[length * 2];
var random = new Random();
for (int i = 0; i < length * 2; i++)
randomChars[i] = _allowedRandomHexChars[random.Next(0, _allowedRandomHexChars.Length)];
return "0x" + new string(randomChars);
#endif
}
}
/// <summary>
/// Generate a long value
/// </summary>
/// <param name="maxLength">Max character length</param>
/// <returns></returns>
public static long RandomLong(int maxLength)
{
/// <summary>
/// Generate a long value
/// </summary>
/// <param name="maxLength">Max character length</param>
/// <returns></returns>
public static long RandomLong(int maxLength)
{
#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER
var value = RandomNumberGenerator.GetInt32(0, int.MaxValue);
var value = RandomNumberGenerator.GetInt32(0, int.MaxValue);
#else
var random = new Random();
var value = random.Next(0, int.MaxValue);
var random = new Random();
var value = random.Next(0, int.MaxValue);
#endif
var val = value.ToString();
if (val.Length > maxLength)
return int.Parse(val.Substring(0, maxLength));
else
return value;
}
var val = value.ToString();
if (val.Length > maxLength)
return int.Parse(val.Substring(0, maxLength));
else
return value;
}
/// <summary>
/// Generate a random string of specified length
/// </summary>
/// <param name="source">The initial string</param>
/// <param name="totalLength">Total length of the resulting string</param>
/// <returns></returns>
public static string AppendRandomString(string source, int totalLength)
/// <summary>
/// Generate a random string of specified length
/// </summary>
/// <param name="source">The initial string</param>
/// <param name="totalLength">Total length of the resulting string</param>
/// <returns></returns>
public static string AppendRandomString(string source, int totalLength)
{
if (totalLength < source.Length)
throw new ArgumentException("Total length smaller than source string length", nameof(totalLength));
if (totalLength == source.Length)
return source;
return source + RandomString(totalLength - source.Length);
}
/// <summary>
/// Get the month representation for futures symbol based on the delivery month
/// </summary>
/// <param name="time">Delivery time</param>
/// <returns></returns>
public static string GetDeliveryMonthSymbol(DateTime time) => _monthSymbols[time.Month];
/// <summary>
/// Execute multiple requests to retrieve multiple pages of the result set
/// </summary>
/// <typeparam name="TResult">Type of the client</typeparam>
/// <typeparam name="TRequest">Type of the request</typeparam>
/// <param name="paginatedFunc">The func to execute with each request</param>
/// <param name="request">The request parameters</param>
/// <param name="ct">Cancellation token</param>
/// <returns></returns>
public static async IAsyncEnumerable<ExchangeWebResult<TResult[]>> ExecutePages<TResult, TRequest>(Func<TRequest, INextPageToken?, CancellationToken, Task<ExchangeWebResult<TResult[]>>> paginatedFunc, TRequest request, [EnumeratorCancellation]CancellationToken ct = default)
{
var result = new List<TResult>();
ExchangeWebResult<TResult[]> batch;
INextPageToken? nextPageToken = null;
while (true)
{
if (totalLength < source.Length)
throw new ArgumentException("Total length smaller than source string length", nameof(totalLength));
batch = await paginatedFunc(request, nextPageToken, ct).ConfigureAwait(false);
yield return batch;
if (!batch || ct.IsCancellationRequested)
break;
if (totalLength == source.Length)
return source;
return source + RandomString(totalLength - source.Length);
}
/// <summary>
/// Get the month representation for futures symbol based on the delivery month
/// </summary>
/// <param name="time">Delivery time</param>
/// <returns></returns>
public static string GetDeliveryMonthSymbol(DateTime time) => _monthSymbols[time.Month];
/// <summary>
/// Execute multiple requests to retrieve multiple pages of the result set
/// </summary>
/// <typeparam name="T">Type of the client</typeparam>
/// <typeparam name="U">Type of the request</typeparam>
/// <param name="paginatedFunc">The func to execute with each request</param>
/// <param name="request">The request parameters</param>
/// <param name="ct">Cancellation token</param>
/// <returns></returns>
public static async IAsyncEnumerable<ExchangeWebResult<T[]>> ExecutePages<T, U>(Func<U, INextPageToken?, CancellationToken, Task<ExchangeWebResult<T[]>>> paginatedFunc, U request, [EnumeratorCancellation]CancellationToken ct = default)
{
var result = new List<T>();
ExchangeWebResult<T[]> batch;
INextPageToken? nextPageToken = null;
while (true)
{
batch = await paginatedFunc(request, nextPageToken, ct).ConfigureAwait(false);
yield return batch;
if (!batch || ct.IsCancellationRequested)
break;
result.AddRange(batch.Data);
nextPageToken = batch.NextPageToken;
if (nextPageToken == null)
break;
}
}
/// <summary>
/// Apply the rules (price and quantity step size and decimals precision, min/max quantity) from the symbol to the quantity and price
/// </summary>
/// <param name="symbol">The symbol as retrieved from the exchange</param>
/// <param name="quantity">Quantity to trade</param>
/// <param name="price">Price to trade at</param>
/// <param name="adjustedQuantity">Quantity adjusted to match all trading rules</param>
/// <param name="adjustedPrice">Price adjusted to match all trading rules</param>
public static void ApplySymbolRules(SharedSpotSymbol symbol, decimal quantity, decimal? price, out decimal adjustedQuantity, out decimal? adjustedPrice)
{
adjustedPrice = price;
adjustedQuantity = quantity;
var minNotionalAdjust = false;
if (price != null)
{
adjustedPrice = AdjustValueStep(0, decimal.MaxValue, symbol.PriceStep, RoundingType.Down, price.Value);
adjustedPrice = symbol.PriceSignificantFigures.HasValue ? RoundToSignificantDigits(adjustedPrice.Value, symbol.PriceSignificantFigures.Value, RoundingType.Closest) : adjustedPrice;
adjustedPrice = symbol.PriceDecimals.HasValue ? RoundDown(price.Value, symbol.PriceDecimals.Value) : adjustedPrice;
if (adjustedPrice != 0 && adjustedPrice * quantity < symbol.MinNotionalValue)
{
adjustedQuantity = symbol.MinNotionalValue.Value / adjustedPrice.Value;
minNotionalAdjust = true;
}
}
adjustedQuantity = AdjustValueStep(symbol.MinTradeQuantity ?? 0, symbol.MaxTradeQuantity ?? decimal.MaxValue, symbol.QuantityStep, minNotionalAdjust ? RoundingType.Up : RoundingType.Down, adjustedQuantity);
adjustedQuantity = symbol.QuantityDecimals.HasValue ? (minNotionalAdjust ? RoundUp(adjustedQuantity, symbol.QuantityDecimals.Value) : RoundDown(adjustedQuantity, symbol.QuantityDecimals.Value)) : adjustedQuantity;
}
/// <summary>
/// Parse a decimal value from a string
/// </summary>
public static decimal? ParseDecimal(string? value)
{
// Value is null or empty is the most common case to return null so check before trying to parse
if (string.IsNullOrEmpty(value))
return null;
// Try parse, only fails for these reasons:
// 1. string is null or empty
// 2. value is larger or smaller than decimal max/min
// 3. unparsable format
if (decimal.TryParse(value, NumberStyles.Float, CultureInfo.InvariantCulture, out var decValue))
return decValue;
// Check for values which should be parsed to null
if (string.Equals("null", value, StringComparison.OrdinalIgnoreCase)
|| string.Equals("NaN", value, StringComparison.OrdinalIgnoreCase))
{
return null;
}
// Infinity value should be parsed to min/max value
if (string.Equals("Infinity", value, StringComparison.OrdinalIgnoreCase))
return decimal.MaxValue;
else if(string.Equals("-Infinity", value, StringComparison.OrdinalIgnoreCase))
return decimal.MinValue;
if (value!.Length > 27 && decimal.TryParse(value.Substring(0, 27), out var overflowValue))
{
// Not a valid decimal value and more than 27 chars, from which the first part can be parsed correctly.
// assume overflow
if (overflowValue < 0)
return decimal.MinValue;
else
return decimal.MaxValue;
}
// Unknown decimal format, return null
return null;
result.AddRange(batch.Data);
nextPageToken = batch.NextPageToken;
if (nextPageToken == null)
break;
}
}
/// <summary>
/// Apply the rules (price and quantity step size and decimals precision, min/max quantity) from the symbol to the quantity and price
/// </summary>
/// <param name="symbol">The symbol as retrieved from the exchange</param>
/// <param name="quantity">Quantity to trade</param>
/// <param name="price">Price to trade at</param>
/// <param name="adjustedQuantity">Quantity adjusted to match all trading rules</param>
/// <param name="adjustedPrice">Price adjusted to match all trading rules</param>
public static void ApplySymbolRules(SharedSpotSymbol symbol, decimal quantity, decimal? price, out decimal adjustedQuantity, out decimal? adjustedPrice)
{
adjustedPrice = price;
adjustedQuantity = quantity;
var minNotionalAdjust = false;
if (price != null)
{
adjustedPrice = AdjustValueStep(0, decimal.MaxValue, symbol.PriceStep, RoundingType.Down, price.Value);
adjustedPrice = symbol.PriceSignificantFigures.HasValue ? RoundToSignificantDigits(adjustedPrice.Value, symbol.PriceSignificantFigures.Value, RoundingType.Closest) : adjustedPrice;
adjustedPrice = symbol.PriceDecimals.HasValue ? RoundDown(price.Value, symbol.PriceDecimals.Value) : adjustedPrice;
if (adjustedPrice != 0 && adjustedPrice * quantity < symbol.MinNotionalValue)
{
adjustedQuantity = symbol.MinNotionalValue.Value / adjustedPrice.Value;
minNotionalAdjust = true;
}
}
adjustedQuantity = AdjustValueStep(symbol.MinTradeQuantity ?? 0, symbol.MaxTradeQuantity ?? decimal.MaxValue, symbol.QuantityStep, minNotionalAdjust ? RoundingType.Up : RoundingType.Down, adjustedQuantity);
adjustedQuantity = symbol.QuantityDecimals.HasValue ? (minNotionalAdjust ? RoundUp(adjustedQuantity, symbol.QuantityDecimals.Value) : RoundDown(adjustedQuantity, symbol.QuantityDecimals.Value)) : adjustedQuantity;
}
/// <summary>
/// Parse a decimal value from a string
/// </summary>
public static decimal? ParseDecimal(string? value)
{
// Value is null or empty is the most common case to return null so check before trying to parse
if (string.IsNullOrEmpty(value))
return null;
// Try parse, only fails for these reasons:
// 1. string is null or empty
// 2. value is larger or smaller than decimal max/min
// 3. unparsable format
if (decimal.TryParse(value, NumberStyles.Float, CultureInfo.InvariantCulture, out var decValue))
return decValue;
// Check for values which should be parsed to null
if (string.Equals("null", value, StringComparison.OrdinalIgnoreCase)
|| string.Equals("NaN", value, StringComparison.OrdinalIgnoreCase))
{
return null;
}
// Infinity value should be parsed to min/max value
if (string.Equals("Infinity", value, StringComparison.OrdinalIgnoreCase))
return decimal.MaxValue;
else if(string.Equals("-Infinity", value, StringComparison.OrdinalIgnoreCase))
return decimal.MinValue;
if (value!.Length > 27 && decimal.TryParse(value.Substring(0, 27), out var overflowValue))
{
// Not a valid decimal value and more than 27 chars, from which the first part can be parsed correctly.
// assume overflow
if (overflowValue < 0)
return decimal.MinValue;
else
return decimal.MaxValue;
}
// Unknown decimal format, return null
return null;
}
}

View File

@ -1,70 +1,68 @@
using CryptoExchange.Net.SharedApis;
using CryptoExchange.Net.SharedApis;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace CryptoExchange.Net
namespace CryptoExchange.Net;
/// <summary>
/// Cache for symbol parsing
/// </summary>
public static class ExchangeSymbolCache
{
private static ConcurrentDictionary<string, ExchangeInfo> _symbolInfos = new ConcurrentDictionary<string, ExchangeInfo>();
/// <summary>
/// Cache for symbol parsing
/// Update the cached symbol data for an exchange
/// </summary>
public static class ExchangeSymbolCache
/// <param name="topicId">Id for the provided data</param>
/// <param name="updateData">Symbol data</param>
public static void UpdateSymbolInfo(string topicId, SharedSpotSymbol[] updateData)
{
private static ConcurrentDictionary<string, ExchangeInfo> _symbolInfos = new ConcurrentDictionary<string, ExchangeInfo>();
/// <summary>
/// Update the cached symbol data for an exchange
/// </summary>
/// <param name="topicId">Id for the provided data</param>
/// <param name="updateData">Symbol data</param>
public static void UpdateSymbolInfo(string topicId, SharedSpotSymbol[] updateData)
if(!_symbolInfos.TryGetValue(topicId, out var exchangeInfo))
{
if(!_symbolInfos.TryGetValue(topicId, out var exchangeInfo))
{
exchangeInfo = new ExchangeInfo(DateTime.UtcNow, updateData.ToDictionary(x => x.Name, x => x.SharedSymbol));
_symbolInfos.TryAdd(topicId, exchangeInfo);
}
if (DateTime.UtcNow - exchangeInfo.UpdateTime < TimeSpan.FromMinutes(60))
return;
_symbolInfos[topicId] = new ExchangeInfo(DateTime.UtcNow, updateData.ToDictionary(x => x.Name, x => x.SharedSymbol));
exchangeInfo = new ExchangeInfo(DateTime.UtcNow, updateData.ToDictionary(x => x.Name, x => x.SharedSymbol));
_symbolInfos.TryAdd(topicId, exchangeInfo);
}
/// <summary>
/// Parse a symbol name to a SharedSymbol
/// </summary>
/// <param name="topicId">Id for the provided data</param>
/// <param name="symbolName">Symbol name</param>
public static SharedSymbol? ParseSymbol(string topicId, string? symbolName)
if (DateTime.UtcNow - exchangeInfo.UpdateTime < TimeSpan.FromMinutes(60))
return;
_symbolInfos[topicId] = new ExchangeInfo(DateTime.UtcNow, updateData.ToDictionary(x => x.Name, x => x.SharedSymbol));
}
/// <summary>
/// Parse a symbol name to a SharedSymbol
/// </summary>
/// <param name="topicId">Id for the provided data</param>
/// <param name="symbolName">Symbol name</param>
public static SharedSymbol? ParseSymbol(string topicId, string? symbolName)
{
if (symbolName == null)
return null;
if (!_symbolInfos.TryGetValue(topicId, out var exchangeInfo))
return null;
if (!exchangeInfo.Symbols.TryGetValue(symbolName, out var symbolInfo))
return null;
return new SharedSymbol(symbolInfo.TradingMode, symbolInfo.BaseAsset, symbolInfo.QuoteAsset, symbolName)
{
if (symbolName == null)
return null;
DeliverTime = symbolInfo.DeliverTime
};
}
if (!_symbolInfos.TryGetValue(topicId, out var exchangeInfo))
return null;
class ExchangeInfo
{
public DateTime UpdateTime { get; set; }
public Dictionary<string, SharedSymbol> Symbols { get; set; }
if (!exchangeInfo.Symbols.TryGetValue(symbolName, out var symbolInfo))
return null;
return new SharedSymbol(symbolInfo.TradingMode, symbolInfo.BaseAsset, symbolInfo.QuoteAsset, symbolName)
{
DeliverTime = symbolInfo.DeliverTime
};
}
class ExchangeInfo
public ExchangeInfo(DateTime updateTime, Dictionary<string, SharedSymbol> symbols)
{
public DateTime UpdateTime { get; set; }
public Dictionary<string, SharedSymbol> Symbols { get; set; }
public ExchangeInfo(DateTime updateTime, Dictionary<string, SharedSymbol> symbols)
{
UpdateTime = updateTime;
Symbols = symbols;
}
UpdateTime = updateTime;
Symbols = symbols;
}
}
}

View File

@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.IO.Compression;
using System.IO;
@ -10,515 +10,511 @@ using CryptoExchange.Net.Objects;
using System.Globalization;
using Microsoft.Extensions.DependencyInjection;
using CryptoExchange.Net.SharedApis;
using System.Text.Json.Serialization.Metadata;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace CryptoExchange.Net
namespace CryptoExchange.Net;
/// <summary>
/// Helper methods
/// </summary>
public static class ExtensionMethods
{
/// <summary>
/// Helper methods
/// Add a parameter
/// </summary>
public static class ExtensionMethods
/// <param name="parameters"></param>
/// <param name="key"></param>
/// <param name="value"></param>
public static void AddParameter(this Dictionary<string, object> parameters, string key, string value)
{
/// <summary>
/// Add a parameter
/// </summary>
/// <param name="parameters"></param>
/// <param name="key"></param>
/// <param name="value"></param>
public static void AddParameter(this Dictionary<string, object> parameters, string key, string value)
{
parameters.Add(key, value);
}
/// <summary>
/// Add a parameter
/// </summary>
/// <param name="parameters"></param>
/// <param name="key"></param>
/// <param name="value"></param>
public static void AddParameter(this Dictionary<string, object> parameters, string key, object value)
{
parameters.Add(key, value);
}
/// <summary>
/// Add an optional parameter. Not added if value is null
/// </summary>
/// <param name="parameters"></param>
/// <param name="key"></param>
/// <param name="value"></param>
public static void AddOptionalParameter(this Dictionary<string, object> parameters, string key, object? value)
{
if (value != null)
parameters.Add(key, value);
}
}
/// <summary>
/// Add a parameter
/// </summary>
/// <param name="parameters"></param>
/// <param name="key"></param>
/// <param name="value"></param>
public static void AddParameter(this Dictionary<string, object> parameters, string key, object value)
/// <summary>
/// Create a query string of the specified parameters
/// </summary>
/// <param name="parameters">The parameters to use</param>
/// <param name="urlEncodeValues">Whether or not the values should be url encoded</param>
/// <param name="serializationType">How to serialize array parameters</param>
/// <returns></returns>
public static string CreateParamString(this IDictionary<string, object> parameters, bool urlEncodeValues, ArrayParametersSerialization serializationType)
{
var uriString = string.Empty;
var arraysParameters = parameters.Where(p => p.Value.GetType().IsArray).ToList();
foreach (var arrayEntry in arraysParameters)
{
parameters.Add(key, value);
}
/// <summary>
/// Add an optional parameter. Not added if value is null
/// </summary>
/// <param name="parameters"></param>
/// <param name="key"></param>
/// <param name="value"></param>
public static void AddOptionalParameter(this Dictionary<string, object> parameters, string key, object? value)
{
if (value != null)
parameters.Add(key, value);
}
/// <summary>
/// Create a query string of the specified parameters
/// </summary>
/// <param name="parameters">The parameters to use</param>
/// <param name="urlEncodeValues">Whether or not the values should be url encoded</param>
/// <param name="serializationType">How to serialize array parameters</param>
/// <returns></returns>
public static string CreateParamString(this IDictionary<string, object> parameters, bool urlEncodeValues, ArrayParametersSerialization serializationType)
{
var uriString = string.Empty;
var arraysParameters = parameters.Where(p => p.Value.GetType().IsArray).ToList();
foreach (var arrayEntry in arraysParameters)
if (serializationType == ArrayParametersSerialization.Array)
{
if (serializationType == ArrayParametersSerialization.Array)
uriString += $"{string.Join("&", ((object[])(urlEncodeValues ? Uri.EscapeDataString(arrayEntry.Value.ToString()!) : arrayEntry.Value)).Select(v => $"{arrayEntry.Key}[]={string.Format(CultureInfo.InvariantCulture, "{0}", v)}"))}&";
}
else if (serializationType == ArrayParametersSerialization.MultipleValues)
{
var array = (Array)arrayEntry.Value;
uriString += string.Join("&", array.OfType<object>().Select(a => $"{arrayEntry.Key}={Uri.EscapeDataString(string.Format(CultureInfo.InvariantCulture, "{0}", a))}"));
uriString += "&";
}
else
{
var array = (Array)arrayEntry.Value;
uriString += $"{arrayEntry.Key}=[{string.Join(",", array.OfType<object>().Select(a => string.Format(CultureInfo.InvariantCulture, "{0}", a)))}]&";
}
}
uriString += $"{string.Join("&", parameters.Where(p => !p.Value.GetType().IsArray).Select(s => $"{s.Key}={(urlEncodeValues ? Uri.EscapeDataString(string.Format(CultureInfo.InvariantCulture, "{0}", s.Value)) : string.Format(CultureInfo.InvariantCulture, "{0}", s.Value))}"))}";
uriString = uriString.TrimEnd('&');
return uriString;
}
/// <summary>
/// Convert a dictionary to formdata string
/// </summary>
/// <param name="parameters"></param>
/// <returns></returns>
public static string ToFormData(this IDictionary<string, object> parameters)
{
var formData = HttpUtility.ParseQueryString(string.Empty);
foreach (var kvp in parameters)
{
if (kvp.Value is null)
continue;
if (kvp.Value.GetType().IsArray)
{
var array = (Array)kvp.Value;
foreach (var value in array)
formData.Add(kvp.Key, string.Format(CultureInfo.InvariantCulture, "{0}", value));
}
else
{
formData.Add(kvp.Key, string.Format(CultureInfo.InvariantCulture, "{0}", kvp.Value));
}
}
return formData.ToString()!;
}
/// <summary>
/// Validates an int is one of the allowed values
/// </summary>
/// <param name="value">Value of the int</param>
/// <param name="argumentName">Name of the parameter</param>
/// <param name="allowedValues">Allowed values</param>
public static void ValidateIntValues(this int value, string argumentName, params int[] allowedValues)
{
if (!allowedValues.Contains(value))
{
throw new ArgumentException(
$"{value} not allowed for parameter {argumentName}, allowed values: {string.Join(", ", allowedValues)}", argumentName);
}
}
/// <summary>
/// Validates an int is between two values
/// </summary>
/// <param name="value">The value of the int</param>
/// <param name="argumentName">Name of the parameter</param>
/// <param name="minValue">Min value</param>
/// <param name="maxValue">Max value</param>
public static void ValidateIntBetween(this int value, string argumentName, int minValue, int maxValue)
{
if (value < minValue || value > maxValue)
{
throw new ArgumentException(
$"{value} not allowed for parameter {argumentName}, min: {minValue}, max: {maxValue}", argumentName);
}
}
/// <summary>
/// Validates a string is not null or empty
/// </summary>
/// <param name="value">The value of the string</param>
/// <param name="argumentName">Name of the parameter</param>
public static void ValidateNotNull(this string value, string argumentName)
{
if (string.IsNullOrEmpty(value))
throw new ArgumentException($"No value provided for parameter {argumentName}", argumentName);
}
/// <summary>
/// Validates a string is null or not empty
/// </summary>
/// <param name="value"></param>
/// <param name="argumentName"></param>
public static void ValidateNullOrNotEmpty(this string value, string argumentName)
{
if (value != null && string.IsNullOrEmpty(value))
throw new ArgumentException($"No value provided for parameter {argumentName}", argumentName);
}
/// <summary>
/// Validates an object is not null
/// </summary>
/// <param name="value">The value of the object</param>
/// <param name="argumentName">Name of the parameter</param>
public static void ValidateNotNull(this object value, string argumentName)
{
if (value == null)
throw new ArgumentException($"No value provided for parameter {argumentName}", argumentName);
}
/// <summary>
/// Validates a list is not null or empty
/// </summary>
/// <param name="value">The value of the object</param>
/// <param name="argumentName">Name of the parameter</param>
public static void ValidateNotNull<T>(this IEnumerable<T> value, string argumentName)
{
if (value == null || !value.Any())
throw new ArgumentException($"No values provided for parameter {argumentName}", argumentName);
}
/// <summary>
/// Format a string to RFC3339/ISO8601 string
/// </summary>
/// <param name="dateTime"></param>
/// <returns></returns>
public static string ToRfc3339String(this DateTime dateTime)
{
return dateTime.ToString("yyyy-MM-dd'T'HH:mm:ss.fffzzz", DateTimeFormatInfo.InvariantInfo);
}
/// <summary>
/// Format an exception and inner exception to a readable string
/// </summary>
/// <param name="exception"></param>
/// <returns></returns>
public static string ToLogString(this Exception? exception)
{
var message = new StringBuilder();
var indent = 0;
while (exception != null)
{
for (var i = 0; i < indent; i++)
message.Append(' ');
message.Append(exception.GetType().Name);
message.Append(" - ");
message.AppendLine(exception.Message);
for (var i = 0; i < indent; i++)
message.Append(' ');
message.AppendLine(exception.StackTrace);
indent += 2;
exception = exception.InnerException;
}
return message.ToString();
}
/// <summary>
/// Append a base url with provided path
/// </summary>
/// <param name="url"></param>
/// <param name="path"></param>
/// <returns></returns>
public static string AppendPath(this string url, params string[] path)
{
if (!url.EndsWith("/"))
url += "/";
foreach (var item in path)
url += item.Trim('/') + "/";
return url.TrimEnd('/');
}
/// <summary>
/// Create a new uri with the provided parameters as query
/// </summary>
/// <param name="parameters"></param>
/// <param name="baseUri"></param>
/// <param name="arraySerialization"></param>
/// <returns></returns>
public static Uri SetParameters(this Uri baseUri, IDictionary<string, object> parameters, ArrayParametersSerialization arraySerialization)
{
var uriBuilder = new UriBuilder();
uriBuilder.Scheme = baseUri.Scheme;
uriBuilder.Host = baseUri.Host;
uriBuilder.Port = baseUri.Port;
uriBuilder.Path = baseUri.AbsolutePath;
var httpValueCollection = HttpUtility.ParseQueryString(string.Empty);
foreach (var parameter in parameters)
{
if (parameter.Value.GetType().IsArray)
{
if (arraySerialization == ArrayParametersSerialization.JsonArray)
{
uriString += $"{string.Join("&", ((object[])(urlEncodeValues ? Uri.EscapeDataString(arrayEntry.Value.ToString()!) : arrayEntry.Value)).Select(v => $"{arrayEntry.Key}[]={string.Format(CultureInfo.InvariantCulture, "{0}", v)}"))}&";
}
else if (serializationType == ArrayParametersSerialization.MultipleValues)
{
var array = (Array)arrayEntry.Value;
uriString += string.Join("&", array.OfType<object>().Select(a => $"{arrayEntry.Key}={Uri.EscapeDataString(string.Format(CultureInfo.InvariantCulture, "{0}", a))}"));
uriString += "&";
httpValueCollection.Add(parameter.Key, $"[{string.Join(",", (object[])parameter.Value)}]");
}
else
{
var array = (Array)arrayEntry.Value;
uriString += $"{arrayEntry.Key}=[{string.Join(",", array.OfType<object>().Select(a => string.Format(CultureInfo.InvariantCulture, "{0}", a)))}]&";
}
}
uriString += $"{string.Join("&", parameters.Where(p => !p.Value.GetType().IsArray).Select(s => $"{s.Key}={(urlEncodeValues ? Uri.EscapeDataString(string.Format(CultureInfo.InvariantCulture, "{0}", s.Value)) : string.Format(CultureInfo.InvariantCulture, "{0}", s.Value))}"))}";
uriString = uriString.TrimEnd('&');
return uriString;
}
/// <summary>
/// Convert a dictionary to formdata string
/// </summary>
/// <param name="parameters"></param>
/// <returns></returns>
public static string ToFormData(this IDictionary<string, object> parameters)
{
var formData = HttpUtility.ParseQueryString(string.Empty);
foreach (var kvp in parameters)
{
if (kvp.Value is null)
continue;
if (kvp.Value.GetType().IsArray)
{
var array = (Array)kvp.Value;
foreach (var value in array)
formData.Add(kvp.Key, string.Format(CultureInfo.InvariantCulture, "{0}", value));
}
else
{
formData.Add(kvp.Key, string.Format(CultureInfo.InvariantCulture, "{0}", kvp.Value));
}
}
return formData.ToString()!;
}
/// <summary>
/// Validates an int is one of the allowed values
/// </summary>
/// <param name="value">Value of the int</param>
/// <param name="argumentName">Name of the parameter</param>
/// <param name="allowedValues">Allowed values</param>
public static void ValidateIntValues(this int value, string argumentName, params int[] allowedValues)
{
if (!allowedValues.Contains(value))
{
throw new ArgumentException(
$"{value} not allowed for parameter {argumentName}, allowed values: {string.Join(", ", allowedValues)}", argumentName);
}
}
/// <summary>
/// Validates an int is between two values
/// </summary>
/// <param name="value">The value of the int</param>
/// <param name="argumentName">Name of the parameter</param>
/// <param name="minValue">Min value</param>
/// <param name="maxValue">Max value</param>
public static void ValidateIntBetween(this int value, string argumentName, int minValue, int maxValue)
{
if (value < minValue || value > maxValue)
{
throw new ArgumentException(
$"{value} not allowed for parameter {argumentName}, min: {minValue}, max: {maxValue}", argumentName);
}
}
/// <summary>
/// Validates a string is not null or empty
/// </summary>
/// <param name="value">The value of the string</param>
/// <param name="argumentName">Name of the parameter</param>
public static void ValidateNotNull(this string value, string argumentName)
{
if (string.IsNullOrEmpty(value))
throw new ArgumentException($"No value provided for parameter {argumentName}", argumentName);
}
/// <summary>
/// Validates a string is null or not empty
/// </summary>
/// <param name="value"></param>
/// <param name="argumentName"></param>
public static void ValidateNullOrNotEmpty(this string value, string argumentName)
{
if (value != null && string.IsNullOrEmpty(value))
throw new ArgumentException($"No value provided for parameter {argumentName}", argumentName);
}
/// <summary>
/// Validates an object is not null
/// </summary>
/// <param name="value">The value of the object</param>
/// <param name="argumentName">Name of the parameter</param>
public static void ValidateNotNull(this object value, string argumentName)
{
if (value == null)
throw new ArgumentException($"No value provided for parameter {argumentName}", argumentName);
}
/// <summary>
/// Validates a list is not null or empty
/// </summary>
/// <param name="value">The value of the object</param>
/// <param name="argumentName">Name of the parameter</param>
public static void ValidateNotNull<T>(this IEnumerable<T> value, string argumentName)
{
if (value == null || !value.Any())
throw new ArgumentException($"No values provided for parameter {argumentName}", argumentName);
}
/// <summary>
/// Format a string to RFC3339/ISO8601 string
/// </summary>
/// <param name="dateTime"></param>
/// <returns></returns>
public static string ToRfc3339String(this DateTime dateTime)
{
return dateTime.ToString("yyyy-MM-dd'T'HH:mm:ss.fffzzz", DateTimeFormatInfo.InvariantInfo);
}
/// <summary>
/// Format an exception and inner exception to a readable string
/// </summary>
/// <param name="exception"></param>
/// <returns></returns>
public static string ToLogString(this Exception? exception)
{
var message = new StringBuilder();
var indent = 0;
while (exception != null)
{
for (var i = 0; i < indent; i++)
message.Append(' ');
message.Append(exception.GetType().Name);
message.Append(" - ");
message.AppendLine(exception.Message);
for (var i = 0; i < indent; i++)
message.Append(' ');
message.AppendLine(exception.StackTrace);
indent += 2;
exception = exception.InnerException;
}
return message.ToString();
}
/// <summary>
/// Append a base url with provided path
/// </summary>
/// <param name="url"></param>
/// <param name="path"></param>
/// <returns></returns>
public static string AppendPath(this string url, params string[] path)
{
if (!url.EndsWith("/"))
url += "/";
foreach (var item in path)
url += item.Trim('/') + "/";
return url.TrimEnd('/');
}
/// <summary>
/// Create a new uri with the provided parameters as query
/// </summary>
/// <param name="parameters"></param>
/// <param name="baseUri"></param>
/// <param name="arraySerialization"></param>
/// <returns></returns>
public static Uri SetParameters(this Uri baseUri, IDictionary<string, object> parameters, ArrayParametersSerialization arraySerialization)
{
var uriBuilder = new UriBuilder();
uriBuilder.Scheme = baseUri.Scheme;
uriBuilder.Host = baseUri.Host;
uriBuilder.Port = baseUri.Port;
uriBuilder.Path = baseUri.AbsolutePath;
var httpValueCollection = HttpUtility.ParseQueryString(string.Empty);
foreach (var parameter in parameters)
{
if (parameter.Value.GetType().IsArray)
{
if (arraySerialization == ArrayParametersSerialization.JsonArray)
foreach (var item in (object[])parameter.Value)
{
httpValueCollection.Add(parameter.Key, $"[{string.Join(",", (object[])parameter.Value)}]");
}
else
{
foreach (var item in (object[])parameter.Value)
if (arraySerialization == ArrayParametersSerialization.Array)
{
if (arraySerialization == ArrayParametersSerialization.Array)
{
httpValueCollection.Add(parameter.Key + "[]", item.ToString());
}
else
{
httpValueCollection.Add(parameter.Key, item.ToString());
}
httpValueCollection.Add(parameter.Key + "[]", item.ToString());
}
else
{
httpValueCollection.Add(parameter.Key, item.ToString());
}
}
}
else
{
httpValueCollection.Add(parameter.Key, parameter.Value.ToString());
}
}
uriBuilder.Query = httpValueCollection.ToString();
return uriBuilder.Uri;
else
{
httpValueCollection.Add(parameter.Key, parameter.Value.ToString());
}
}
/// <summary>
/// Create a new uri with the provided parameters as query
/// </summary>
/// <param name="parameters"></param>
/// <param name="baseUri"></param>
/// <param name="arraySerialization"></param>
/// <returns></returns>
public static Uri SetParameters(this Uri baseUri, IOrderedEnumerable<KeyValuePair<string, object>> parameters, ArrayParametersSerialization arraySerialization)
uriBuilder.Query = httpValueCollection.ToString();
return uriBuilder.Uri;
}
/// <summary>
/// Create a new uri with the provided parameters as query
/// </summary>
/// <param name="parameters"></param>
/// <param name="baseUri"></param>
/// <param name="arraySerialization"></param>
/// <returns></returns>
public static Uri SetParameters(this Uri baseUri, IOrderedEnumerable<KeyValuePair<string, object>> parameters, ArrayParametersSerialization arraySerialization)
{
var uriBuilder = new UriBuilder();
uriBuilder.Scheme = baseUri.Scheme;
uriBuilder.Host = baseUri.Host;
uriBuilder.Port = baseUri.Port;
uriBuilder.Path = baseUri.AbsolutePath;
var httpValueCollection = HttpUtility.ParseQueryString(string.Empty);
foreach (var parameter in parameters)
{
var uriBuilder = new UriBuilder();
uriBuilder.Scheme = baseUri.Scheme;
uriBuilder.Host = baseUri.Host;
uriBuilder.Port = baseUri.Port;
uriBuilder.Path = baseUri.AbsolutePath;
var httpValueCollection = HttpUtility.ParseQueryString(string.Empty);
foreach (var parameter in parameters)
if (parameter.Value.GetType().IsArray)
{
if (parameter.Value.GetType().IsArray)
if (arraySerialization == ArrayParametersSerialization.JsonArray)
{
if (arraySerialization == ArrayParametersSerialization.JsonArray)
httpValueCollection.Add(parameter.Key, $"[{string.Join(",", (object[])parameter.Value)}]");
}
else
{
foreach (var item in (object[])parameter.Value)
{
httpValueCollection.Add(parameter.Key, $"[{string.Join(",", (object[])parameter.Value)}]");
}
else
{
foreach (var item in (object[])parameter.Value)
if (arraySerialization == ArrayParametersSerialization.Array)
{
if (arraySerialization == ArrayParametersSerialization.Array)
{
httpValueCollection.Add(parameter.Key + "[]", item.ToString());
}
else
{
httpValueCollection.Add(parameter.Key, item.ToString());
}
httpValueCollection.Add(parameter.Key + "[]", item.ToString());
}
else
{
httpValueCollection.Add(parameter.Key, item.ToString());
}
}
}
else
{
httpValueCollection.Add(parameter.Key, parameter.Value.ToString());
}
}
uriBuilder.Query = httpValueCollection.ToString();
return uriBuilder.Uri;
else
{
httpValueCollection.Add(parameter.Key, parameter.Value.ToString());
}
}
/// <summary>
/// Add parameter to URI
/// </summary>
/// <param name="uri"></param>
/// <param name="name"></param>
/// <param name="value"></param>
/// <returns></returns>
public static Uri AddQueryParameter(this Uri uri, string name, string value)
{
var httpValueCollection = HttpUtility.ParseQueryString(uri.Query);
uriBuilder.Query = httpValueCollection.ToString();
return uriBuilder.Uri;
}
httpValueCollection.Remove(name);
httpValueCollection.Add(name, value);
/// <summary>
/// Add parameter to URI
/// </summary>
/// <param name="uri"></param>
/// <param name="name"></param>
/// <param name="value"></param>
/// <returns></returns>
public static Uri AddQueryParameter(this Uri uri, string name, string value)
{
var httpValueCollection = HttpUtility.ParseQueryString(uri.Query);
var ub = new UriBuilder(uri);
ub.Query = httpValueCollection.ToString();
httpValueCollection.Remove(name);
httpValueCollection.Add(name, value);
return ub.Uri;
}
var ub = new UriBuilder(uri);
ub.Query = httpValueCollection.ToString();
/// <summary>
/// Decompress using GzipStream
/// </summary>
/// <param name="data"></param>
/// <returns></returns>
public static ReadOnlyMemory<byte> DecompressGzip(this ReadOnlyMemory<byte> data)
{
using var decompressedStream = new MemoryStream();
using var dataStream = MemoryMarshal.TryGetArray(data, out var arraySegment)
? new MemoryStream(arraySegment.Array!, arraySegment.Offset, arraySegment.Count)
: new MemoryStream(data.ToArray());
using var deflateStream = new GZipStream(new MemoryStream(data.ToArray()), CompressionMode.Decompress);
deflateStream.CopyTo(decompressedStream);
return new ReadOnlyMemory<byte>(decompressedStream.GetBuffer(), 0, (int)decompressedStream.Length);
}
return ub.Uri;
}
/// <summary>
/// Decompress using DeflateStream
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
public static ReadOnlyMemory<byte> Decompress(this ReadOnlyMemory<byte> input)
{
var output = new MemoryStream();
/// <summary>
/// Decompress using GzipStream
/// </summary>
/// <param name="data"></param>
/// <returns></returns>
public static ReadOnlyMemory<byte> DecompressGzip(this ReadOnlyMemory<byte> data)
{
using var decompressedStream = new MemoryStream();
using var dataStream = MemoryMarshal.TryGetArray(data, out var arraySegment)
? new MemoryStream(arraySegment.Array!, arraySegment.Offset, arraySegment.Count)
: new MemoryStream(data.ToArray());
using var deflateStream = new GZipStream(new MemoryStream(data.ToArray()), CompressionMode.Decompress);
deflateStream.CopyTo(decompressedStream);
return new ReadOnlyMemory<byte>(decompressedStream.GetBuffer(), 0, (int)decompressedStream.Length);
}
using (var compressStream = new MemoryStream(input.ToArray()))
using (var decompressor = new DeflateStream(compressStream, CompressionMode.Decompress))
decompressor.CopyTo(output);
/// <summary>
/// Decompress using DeflateStream
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
public static ReadOnlyMemory<byte> Decompress(this ReadOnlyMemory<byte> input)
{
var output = new MemoryStream();
output.Position = 0;
return new ReadOnlyMemory<byte>(output.GetBuffer(), 0, (int)output.Length);
}
using (var compressStream = new MemoryStream(input.ToArray()))
using (var decompressor = new DeflateStream(compressStream, CompressionMode.Decompress))
decompressor.CopyTo(output);
/// <summary>
/// Whether the trading mode is linear
/// </summary>
public static bool IsLinear(this TradingMode type) => type == TradingMode.PerpetualLinear || type == TradingMode.DeliveryLinear;
output.Position = 0;
return new ReadOnlyMemory<byte>(output.GetBuffer(), 0, (int)output.Length);
}
/// <summary>
/// Whether the trading mode is inverse
/// </summary>
public static bool IsInverse(this TradingMode type) => type == TradingMode.PerpetualInverse || type == TradingMode.DeliveryInverse;
/// <summary>
/// Whether the trading mode is perpetual
/// </summary>
public static bool IsPerpetual(this TradingMode type) => type == TradingMode.PerpetualInverse || type == TradingMode.PerpetualLinear;
/// <summary>
/// Whether the trading mode is linear
/// </summary>
public static bool IsLinear(this TradingMode type) => type == TradingMode.PerpetualLinear || type == TradingMode.DeliveryLinear;
/// <summary>
/// Whether the trading mode is delivery
/// </summary>
public static bool IsDelivery(this TradingMode type) => type == TradingMode.DeliveryInverse || type == TradingMode.DeliveryLinear;
/// <summary>
/// Whether the trading mode is inverse
/// </summary>
public static bool IsInverse(this TradingMode type) => type == TradingMode.PerpetualInverse || type == TradingMode.DeliveryInverse;
/// <summary>
/// Whether the trading mode is perpetual
/// </summary>
public static bool IsPerpetual(this TradingMode type) => type == TradingMode.PerpetualInverse || type == TradingMode.PerpetualLinear;
/// <summary>
/// Register rest client interfaces
/// </summary>
public static IServiceCollection RegisterSharedRestInterfaces<T>(this IServiceCollection services, Func<IServiceProvider, T> client)
{
if (typeof(IAssetsRestClient).IsAssignableFrom(typeof(T)))
services.AddTransient(x => (IAssetsRestClient)client(x)!);
if (typeof(IBalanceRestClient).IsAssignableFrom(typeof(T)))
services.AddTransient(x => (IBalanceRestClient)client(x)!);
if (typeof(IDepositRestClient).IsAssignableFrom(typeof(T)))
services.AddTransient(x => (IDepositRestClient)client(x)!);
if (typeof(IKlineRestClient).IsAssignableFrom(typeof(T)))
services.AddTransient(x => (IKlineRestClient)client(x)!);
if (typeof(IListenKeyRestClient).IsAssignableFrom(typeof(T)))
services.AddTransient(x => (IListenKeyRestClient)client(x)!);
if (typeof(IOrderBookRestClient).IsAssignableFrom(typeof(T)))
services.AddTransient(x => (IOrderBookRestClient)client(x)!);
if (typeof(IRecentTradeRestClient).IsAssignableFrom(typeof(T)))
services.AddTransient(x => (IRecentTradeRestClient)client(x)!);
if (typeof(ITradeHistoryRestClient).IsAssignableFrom(typeof(T)))
services.AddTransient(x => (ITradeHistoryRestClient)client(x)!);
if (typeof(IWithdrawalRestClient).IsAssignableFrom(typeof(T)))
services.AddTransient(x => (IWithdrawalRestClient)client(x)!);
if (typeof(IWithdrawRestClient).IsAssignableFrom(typeof(T)))
services.AddTransient(x => (IWithdrawRestClient)client(x)!);
if (typeof(IFeeRestClient).IsAssignableFrom(typeof(T)))
services.AddTransient(x => (IFeeRestClient)client(x)!);
if (typeof(IBookTickerRestClient).IsAssignableFrom(typeof(T)))
services.AddTransient(x => (IBookTickerRestClient)client(x)!);
/// <summary>
/// Whether the trading mode is delivery
/// </summary>
public static bool IsDelivery(this TradingMode type) => type == TradingMode.DeliveryInverse || type == TradingMode.DeliveryLinear;
if (typeof(ISpotOrderRestClient).IsAssignableFrom(typeof(T)))
services.AddTransient(x => (ISpotOrderRestClient)client(x)!);
if (typeof(ISpotSymbolRestClient).IsAssignableFrom(typeof(T)))
services.AddTransient(x => (ISpotSymbolRestClient)client(x)!);
if (typeof(ISpotTickerRestClient).IsAssignableFrom(typeof(T)))
services.AddTransient(x => (ISpotTickerRestClient)client(x)!);
if (typeof(ISpotTriggerOrderRestClient).IsAssignableFrom(typeof(T)))
services.AddTransient(x => (ISpotTriggerOrderRestClient)client(x)!);
if (typeof(ISpotOrderClientIdRestClient).IsAssignableFrom(typeof(T)))
services.AddTransient(x => (ISpotOrderClientIdRestClient)client(x)!);
/// <summary>
/// Register rest client interfaces
/// </summary>
public static IServiceCollection RegisterSharedRestInterfaces<T>(this IServiceCollection services, Func<IServiceProvider, T> client)
{
if (typeof(IAssetsRestClient).IsAssignableFrom(typeof(T)))
services.AddTransient(x => (IAssetsRestClient)client(x)!);
if (typeof(IBalanceRestClient).IsAssignableFrom(typeof(T)))
services.AddTransient(x => (IBalanceRestClient)client(x)!);
if (typeof(IDepositRestClient).IsAssignableFrom(typeof(T)))
services.AddTransient(x => (IDepositRestClient)client(x)!);
if (typeof(IKlineRestClient).IsAssignableFrom(typeof(T)))
services.AddTransient(x => (IKlineRestClient)client(x)!);
if (typeof(IListenKeyRestClient).IsAssignableFrom(typeof(T)))
services.AddTransient(x => (IListenKeyRestClient)client(x)!);
if (typeof(IOrderBookRestClient).IsAssignableFrom(typeof(T)))
services.AddTransient(x => (IOrderBookRestClient)client(x)!);
if (typeof(IRecentTradeRestClient).IsAssignableFrom(typeof(T)))
services.AddTransient(x => (IRecentTradeRestClient)client(x)!);
if (typeof(ITradeHistoryRestClient).IsAssignableFrom(typeof(T)))
services.AddTransient(x => (ITradeHistoryRestClient)client(x)!);
if (typeof(IWithdrawalRestClient).IsAssignableFrom(typeof(T)))
services.AddTransient(x => (IWithdrawalRestClient)client(x)!);
if (typeof(IWithdrawRestClient).IsAssignableFrom(typeof(T)))
services.AddTransient(x => (IWithdrawRestClient)client(x)!);
if (typeof(IFeeRestClient).IsAssignableFrom(typeof(T)))
services.AddTransient(x => (IFeeRestClient)client(x)!);
if (typeof(IBookTickerRestClient).IsAssignableFrom(typeof(T)))
services.AddTransient(x => (IBookTickerRestClient)client(x)!);
if (typeof(IFundingRateRestClient).IsAssignableFrom(typeof(T)))
services.AddTransient(x => (IFundingRateRestClient)client(x)!);
if (typeof(IFuturesOrderRestClient).IsAssignableFrom(typeof(T)))
services.AddTransient(x => (IFuturesOrderRestClient)client(x)!);
if (typeof(IFuturesSymbolRestClient).IsAssignableFrom(typeof(T)))
services.AddTransient(x => (IFuturesSymbolRestClient)client(x)!);
if (typeof(IFuturesTickerRestClient).IsAssignableFrom(typeof(T)))
services.AddTransient(x => (IFuturesTickerRestClient)client(x)!);
if (typeof(IIndexPriceKlineRestClient).IsAssignableFrom(typeof(T)))
services.AddTransient(x => (IIndexPriceKlineRestClient)client(x)!);
if (typeof(ILeverageRestClient).IsAssignableFrom(typeof(T)))
services.AddTransient(x => (ILeverageRestClient)client(x)!);
if (typeof(IMarkPriceKlineRestClient).IsAssignableFrom(typeof(T)))
services.AddTransient(x => (IMarkPriceKlineRestClient)client(x)!);
if (typeof(IOpenInterestRestClient).IsAssignableFrom(typeof(T)))
services.AddTransient(x => (IOpenInterestRestClient)client(x)!);
if (typeof(IPositionHistoryRestClient).IsAssignableFrom(typeof(T)))
services.AddTransient(x => (IPositionHistoryRestClient)client(x)!);
if (typeof(IPositionModeRestClient).IsAssignableFrom(typeof(T)))
services.AddTransient(x => (IPositionModeRestClient)client(x)!);
if (typeof(IFuturesTpSlRestClient).IsAssignableFrom(typeof(T)))
services.AddTransient(x => (IFuturesTpSlRestClient)client(x)!);
if (typeof(IFuturesTriggerOrderRestClient).IsAssignableFrom(typeof(T)))
services.AddTransient(x => (IFuturesTriggerOrderRestClient)client(x)!);
if (typeof(IFuturesOrderClientIdRestClient).IsAssignableFrom(typeof(T)))
services.AddTransient(x => (IFuturesOrderClientIdRestClient)client(x)!);
if (typeof(ISpotOrderRestClient).IsAssignableFrom(typeof(T)))
services.AddTransient(x => (ISpotOrderRestClient)client(x)!);
if (typeof(ISpotSymbolRestClient).IsAssignableFrom(typeof(T)))
services.AddTransient(x => (ISpotSymbolRestClient)client(x)!);
if (typeof(ISpotTickerRestClient).IsAssignableFrom(typeof(T)))
services.AddTransient(x => (ISpotTickerRestClient)client(x)!);
if (typeof(ISpotTriggerOrderRestClient).IsAssignableFrom(typeof(T)))
services.AddTransient(x => (ISpotTriggerOrderRestClient)client(x)!);
if (typeof(ISpotOrderClientIdRestClient).IsAssignableFrom(typeof(T)))
services.AddTransient(x => (ISpotOrderClientIdRestClient)client(x)!);
return services;
}
if (typeof(IFundingRateRestClient).IsAssignableFrom(typeof(T)))
services.AddTransient(x => (IFundingRateRestClient)client(x)!);
if (typeof(IFuturesOrderRestClient).IsAssignableFrom(typeof(T)))
services.AddTransient(x => (IFuturesOrderRestClient)client(x)!);
if (typeof(IFuturesSymbolRestClient).IsAssignableFrom(typeof(T)))
services.AddTransient(x => (IFuturesSymbolRestClient)client(x)!);
if (typeof(IFuturesTickerRestClient).IsAssignableFrom(typeof(T)))
services.AddTransient(x => (IFuturesTickerRestClient)client(x)!);
if (typeof(IIndexPriceKlineRestClient).IsAssignableFrom(typeof(T)))
services.AddTransient(x => (IIndexPriceKlineRestClient)client(x)!);
if (typeof(ILeverageRestClient).IsAssignableFrom(typeof(T)))
services.AddTransient(x => (ILeverageRestClient)client(x)!);
if (typeof(IMarkPriceKlineRestClient).IsAssignableFrom(typeof(T)))
services.AddTransient(x => (IMarkPriceKlineRestClient)client(x)!);
if (typeof(IOpenInterestRestClient).IsAssignableFrom(typeof(T)))
services.AddTransient(x => (IOpenInterestRestClient)client(x)!);
if (typeof(IPositionHistoryRestClient).IsAssignableFrom(typeof(T)))
services.AddTransient(x => (IPositionHistoryRestClient)client(x)!);
if (typeof(IPositionModeRestClient).IsAssignableFrom(typeof(T)))
services.AddTransient(x => (IPositionModeRestClient)client(x)!);
if (typeof(IFuturesTpSlRestClient).IsAssignableFrom(typeof(T)))
services.AddTransient(x => (IFuturesTpSlRestClient)client(x)!);
if (typeof(IFuturesTriggerOrderRestClient).IsAssignableFrom(typeof(T)))
services.AddTransient(x => (IFuturesTriggerOrderRestClient)client(x)!);
if (typeof(IFuturesOrderClientIdRestClient).IsAssignableFrom(typeof(T)))
services.AddTransient(x => (IFuturesOrderClientIdRestClient)client(x)!);
/// <summary>
/// Register socket client interfaces
/// </summary>
public static IServiceCollection RegisterSharedSocketInterfaces<T>(this IServiceCollection services, Func<IServiceProvider, T> client)
{
if (typeof(IBalanceSocketClient).IsAssignableFrom(typeof(T)))
services.AddTransient(x => (IBalanceSocketClient)client(x)!);
if (typeof(IBookTickerSocketClient).IsAssignableFrom(typeof(T)))
services.AddTransient(x => (IBookTickerSocketClient)client(x)!);
if (typeof(IKlineSocketClient).IsAssignableFrom(typeof(T)))
services.AddTransient(x => (IKlineSocketClient)client(x)!);
if (typeof(IOrderBookSocketClient).IsAssignableFrom(typeof(T)))
services.AddTransient(x => (IOrderBookSocketClient)client(x)!);
if (typeof(ITickerSocketClient).IsAssignableFrom(typeof(T)))
services.AddTransient(x => (ITickerSocketClient)client(x)!);
if (typeof(ITickersSocketClient).IsAssignableFrom(typeof(T)))
services.AddTransient(x => (ITickersSocketClient)client(x)!);
if (typeof(ITradeSocketClient).IsAssignableFrom(typeof(T)))
services.AddTransient(x => (ITradeSocketClient)client(x)!);
if (typeof(IUserTradeSocketClient).IsAssignableFrom(typeof(T)))
services.AddTransient(x => (IUserTradeSocketClient)client(x)!);
return services;
}
if (typeof(ISpotOrderSocketClient).IsAssignableFrom(typeof(T)))
services.AddTransient(x => (ISpotOrderSocketClient)client(x)!);
/// <summary>
/// Register socket client interfaces
/// </summary>
public static IServiceCollection RegisterSharedSocketInterfaces<T>(this IServiceCollection services, Func<IServiceProvider, T> client)
{
if (typeof(IBalanceSocketClient).IsAssignableFrom(typeof(T)))
services.AddTransient(x => (IBalanceSocketClient)client(x)!);
if (typeof(IBookTickerSocketClient).IsAssignableFrom(typeof(T)))
services.AddTransient(x => (IBookTickerSocketClient)client(x)!);
if (typeof(IKlineSocketClient).IsAssignableFrom(typeof(T)))
services.AddTransient(x => (IKlineSocketClient)client(x)!);
if (typeof(IOrderBookSocketClient).IsAssignableFrom(typeof(T)))
services.AddTransient(x => (IOrderBookSocketClient)client(x)!);
if (typeof(ITickerSocketClient).IsAssignableFrom(typeof(T)))
services.AddTransient(x => (ITickerSocketClient)client(x)!);
if (typeof(ITickersSocketClient).IsAssignableFrom(typeof(T)))
services.AddTransient(x => (ITickersSocketClient)client(x)!);
if (typeof(ITradeSocketClient).IsAssignableFrom(typeof(T)))
services.AddTransient(x => (ITradeSocketClient)client(x)!);
if (typeof(IUserTradeSocketClient).IsAssignableFrom(typeof(T)))
services.AddTransient(x => (IUserTradeSocketClient)client(x)!);
if (typeof(IFuturesOrderSocketClient).IsAssignableFrom(typeof(T)))
services.AddTransient(x => (IFuturesOrderSocketClient)client(x)!);
if (typeof(IPositionSocketClient).IsAssignableFrom(typeof(T)))
services.AddTransient(x => (IPositionSocketClient)client(x)!);
if (typeof(ISpotOrderSocketClient).IsAssignableFrom(typeof(T)))
services.AddTransient(x => (ISpotOrderSocketClient)client(x)!);
return services;
}
if (typeof(IFuturesOrderSocketClient).IsAssignableFrom(typeof(T)))
services.AddTransient(x => (IFuturesOrderSocketClient)client(x)!);
if (typeof(IPositionSocketClient).IsAssignableFrom(typeof(T)))
services.AddTransient(x => (IPositionSocketClient)client(x)!);
return services;
}
}

View File

@ -1,16 +1,15 @@
using System;
using System;
namespace CryptoExchange.Net.Interfaces
namespace CryptoExchange.Net.Interfaces;
/// <summary>
/// Time provider
/// </summary>
internal interface IAuthTimeProvider
{
/// <summary>
/// Time provider
/// Get current time
/// </summary>
internal interface IAuthTimeProvider
{
/// <summary>
/// Get current time
/// </summary>
/// <returns></returns>
DateTime GetTime();
}
/// <returns></returns>
DateTime GetTime();
}

View File

@ -1,48 +1,46 @@
using CryptoExchange.Net.Authentication;
using CryptoExchange.Net.Objects;
using CryptoExchange.Net.Authentication;
using CryptoExchange.Net.Objects.Options;
using CryptoExchange.Net.SharedApis;
using System;
namespace CryptoExchange.Net.Interfaces
namespace CryptoExchange.Net.Interfaces;
/// <summary>
/// Base api client
/// </summary>
public interface IBaseApiClient
{
/// <summary>
/// Base api client
/// Base address
/// </summary>
public interface IBaseApiClient
{
/// <summary>
/// Base address
/// </summary>
string BaseAddress { get; }
string BaseAddress { get; }
/// <summary>
/// Whether or not API credentials have been configured for this client. Does not check the credentials are actually valid.
/// </summary>
bool Authenticated { get; }
/// <summary>
/// Whether or not API credentials have been configured for this client. Does not check the credentials are actually valid.
/// </summary>
bool Authenticated { get; }
/// <summary>
/// Format a base and quote asset to an exchange accepted symbol
/// </summary>
/// <param name="baseAsset">The base asset</param>
/// <param name="quoteAsset">The quote asset</param>
/// <param name="tradingMode">The trading mode</param>
/// <param name="deliverDate">The deliver date for a delivery futures symbol</param>
/// <returns></returns>
string FormatSymbol(string baseAsset, string quoteAsset, TradingMode tradingMode, DateTime? deliverDate = null);
/// <summary>
/// Format a base and quote asset to an exchange accepted symbol
/// </summary>
/// <param name="baseAsset">The base asset</param>
/// <param name="quoteAsset">The quote asset</param>
/// <param name="tradingMode">The trading mode</param>
/// <param name="deliverDate">The deliver date for a delivery futures symbol</param>
/// <returns></returns>
string FormatSymbol(string baseAsset, string quoteAsset, TradingMode tradingMode, DateTime? deliverDate = null);
/// <summary>
/// Set the API credentials for this API client
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="credentials"></param>
void SetApiCredentials<T>(T credentials) where T : ApiCredentials;
/// <summary>
/// Set the API credentials for this API client
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="credentials"></param>
void SetApiCredentials<T>(T credentials) where T : ApiCredentials;
/// <summary>
/// Set new options. Note that when using a proxy this should be provided in the options even when already set before or it will be reset.
/// </summary>
/// <typeparam name="T">Api credentials type</typeparam>
/// <param name="options">Options to set</param>
void SetOptions<T>(UpdateOptions<T> options) where T : ApiCredentials;
}
/// <summary>
/// Set new options. Note that when using a proxy this should be provided in the options even when already set before or it will be reset.
/// </summary>
/// <typeparam name="T">Api credentials type</typeparam>
/// <param name="options">Options to set</param>
void SetOptions<T>(UpdateOptions<T> options) where T : ApiCredentials;
}

View File

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

View File

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

View File

@ -1,110 +1,110 @@
using CryptoExchange.Net.Converters.MessageParsing;
using CryptoExchange.Net.Converters.MessageParsing;
using CryptoExchange.Net.Objects;
using System;
using System.Collections.Generic;
#if NET5_0_OR_GREATER
using System.Diagnostics.CodeAnalysis;
#endif
using System.IO;
using System.Threading.Tasks;
namespace CryptoExchange.Net.Interfaces
namespace CryptoExchange.Net.Interfaces;
/// <summary>
/// Message accessor
/// </summary>
public interface IMessageAccessor
{
/// <summary>
/// Message accessor
/// Is this a valid message
/// </summary>
public interface IMessageAccessor
{
/// <summary>
/// Is this a valid message
/// </summary>
bool IsValid { get; }
/// <summary>
/// Is the original data available for retrieval
/// </summary>
bool OriginalDataAvailable { get; }
/// <summary>
/// The underlying data object
/// </summary>
object? Underlying { get; }
/// <summary>
/// Clear internal data structure
/// </summary>
void Clear();
/// <summary>
/// Get the type of node
/// </summary>
/// <returns></returns>
NodeType? GetNodeType();
/// <summary>
/// Get the type of node
/// </summary>
/// <param name="path">Access path</param>
/// <returns></returns>
NodeType? GetNodeType(MessagePath path);
/// <summary>
/// Get the value of a path
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="path"></param>
/// <returns></returns>
T? GetValue<T>(MessagePath path);
/// <summary>
/// Get the values of an array
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="path"></param>
/// <returns></returns>
T?[]? GetValues<T>(MessagePath path);
/// <summary>
/// Deserialize the message into this type
/// </summary>
/// <param name="type"></param>
/// <param name="path"></param>
/// <returns></returns>
bool IsValid { get; }
/// <summary>
/// Is the original data available for retrieval
/// </summary>
bool OriginalDataAvailable { get; }
/// <summary>
/// The underlying data object
/// </summary>
object? Underlying { get; }
/// <summary>
/// Clear internal data structure
/// </summary>
void Clear();
/// <summary>
/// Get the type of node
/// </summary>
/// <returns></returns>
NodeType? GetNodeType();
/// <summary>
/// Get the type of node
/// </summary>
/// <param name="path">Access path</param>
/// <returns></returns>
NodeType? GetNodeType(MessagePath path);
/// <summary>
/// Get the value of a path
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="path"></param>
/// <returns></returns>
T? GetValue<T>(MessagePath path);
/// <summary>
/// Get the values of an array
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="path"></param>
/// <returns></returns>
T?[]? GetValues<T>(MessagePath path);
/// <summary>
/// Deserialize the message into this type
/// </summary>
/// <param name="type"></param>
/// <param name="path"></param>
/// <returns></returns>
#if NET5_0_OR_GREATER
[UnconditionalSuppressMessage("AssemblyLoadTrimming", "IL2092:RequiresUnreferencedCode", Justification = "JsonSerializerOptions provided here has TypeInfoResolver set")]
[UnconditionalSuppressMessage("AssemblyLoadTrimming", "IL2095:RequiresUnreferencedCode", Justification = "JsonSerializerOptions provided here has TypeInfoResolver set")]
[UnconditionalSuppressMessage("AssemblyLoadTrimming", "IL2092:RequiresUnreferencedCode", Justification = "JsonSerializerOptions provided here has TypeInfoResolver set")]
[UnconditionalSuppressMessage("AssemblyLoadTrimming", "IL2095:RequiresUnreferencedCode", Justification = "JsonSerializerOptions provided here has TypeInfoResolver set")]
#endif
CallResult<object> Deserialize(Type type, MessagePath? path = null);
/// <summary>
/// Deserialize the message into this type
/// </summary>
/// <param name="path"></param>
/// <returns></returns>
CallResult<object> Deserialize(Type type, MessagePath? path = null);
/// <summary>
/// Deserialize the message into this type
/// </summary>
/// <param name="path"></param>
/// <returns></returns>
#if NET5_0_OR_GREATER
[UnconditionalSuppressMessage("AssemblyLoadTrimming", "IL2092:RequiresUnreferencedCode", Justification = "JsonSerializerOptions provided here has TypeInfoResolver set")]
[UnconditionalSuppressMessage("AssemblyLoadTrimming", "IL2095:RequiresUnreferencedCode", Justification = "JsonSerializerOptions provided here has TypeInfoResolver set")]
[UnconditionalSuppressMessage("AssemblyLoadTrimming", "IL2092:RequiresUnreferencedCode", Justification = "JsonSerializerOptions provided here has TypeInfoResolver set")]
[UnconditionalSuppressMessage("AssemblyLoadTrimming", "IL2095:RequiresUnreferencedCode", Justification = "JsonSerializerOptions provided here has TypeInfoResolver set")]
#endif
CallResult<T> Deserialize<T>(MessagePath? path = null);
/// <summary>
/// Get the original string value
/// </summary>
/// <returns></returns>
string GetOriginalString();
}
CallResult<T> Deserialize<T>(MessagePath? path = null);
/// <summary>
/// Stream message accessor
/// Get the original string value
/// </summary>
public interface IStreamMessageAccessor : IMessageAccessor
{
/// <summary>
/// Load a stream message
/// </summary>
/// <param name="stream"></param>
/// <param name="bufferStream"></param>
Task<CallResult> Read(Stream stream, bool bufferStream);
}
/// <summary>
/// Byte message accessor
/// </summary>
public interface IByteMessageAccessor : IMessageAccessor
{
/// <summary>
/// Load a data message
/// </summary>
/// <param name="data"></param>
CallResult Read(ReadOnlyMemory<byte> data);
}
/// <returns></returns>
string GetOriginalString();
}
/// <summary>
/// Stream message accessor
/// </summary>
public interface IStreamMessageAccessor : IMessageAccessor
{
/// <summary>
/// Load a stream message
/// </summary>
/// <param name="stream"></param>
/// <param name="bufferStream"></param>
Task<CallResult> Read(Stream stream, bool bufferStream);
}
/// <summary>
/// Byte message accessor
/// </summary>
public interface IByteMessageAccessor : IMessageAccessor
{
/// <summary>
/// Load a data message
/// </summary>
/// <param name="data"></param>
CallResult Read(ReadOnlyMemory<byte> data);
}

View File

@ -1,35 +1,33 @@
using CryptoExchange.Net.Objects;
using CryptoExchange.Net.Objects;
using CryptoExchange.Net.Objects.Sockets;
using CryptoExchange.Net.Sockets;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace CryptoExchange.Net.Interfaces
namespace CryptoExchange.Net.Interfaces;
/// <summary>
/// Message processor
/// </summary>
public interface IMessageProcessor
{
/// <summary>
/// Message processor
/// Id of the processor
/// </summary>
public interface IMessageProcessor
{
/// <summary>
/// Id of the processor
/// </summary>
public int Id { get; }
/// <summary>
/// The matcher for this listener
/// </summary>
public MessageMatcher MessageMatcher { get; }
/// <summary>
/// Handle a message
/// </summary>
Task<CallResult> Handle(SocketConnection connection, DataEvent<object> message, MessageHandlerLink matchedHandler);
/// <summary>
/// Deserialize a message into object of type
/// </summary>
/// <param name="accessor"></param>
/// <param name="type"></param>
/// <returns></returns>
CallResult<object> Deserialize(IMessageAccessor accessor, Type type);
}
public int Id { get; }
/// <summary>
/// The matcher for this listener
/// </summary>
public MessageMatcher MessageMatcher { get; }
/// <summary>
/// Handle a message
/// </summary>
Task<CallResult> Handle(SocketConnection connection, DataEvent<object> message, MessageHandlerLink matchedHandler);
/// <summary>
/// Deserialize a message into object of type
/// </summary>
/// <param name="accessor"></param>
/// <param name="type"></param>
/// <returns></returns>
CallResult<object> Deserialize(IMessageAccessor accessor, Type type);
}

View File

@ -1,37 +1,34 @@
using System.Diagnostics.CodeAnalysis;
namespace CryptoExchange.Net.Interfaces;
namespace CryptoExchange.Net.Interfaces
/// <summary>
/// Serializer interface
/// </summary>
public interface IMessageSerializer
{
}
/// <summary>
/// Serialize to byte array
/// </summary>
public interface IByteMessageSerializer: IMessageSerializer
{
/// <summary>
/// Serializer interface
/// Serialize an object to a string
/// </summary>
public interface IMessageSerializer
{
}
/// <summary>
/// Serialize to byte array
/// </summary>
public interface IByteMessageSerializer: IMessageSerializer
{
/// <summary>
/// Serialize an object to a string
/// </summary>
/// <param name="message"></param>
/// <returns></returns>
byte[] Serialize<T>(T message);
}
/// <summary>
/// Serialize to string
/// </summary>
public interface IStringMessageSerializer: IMessageSerializer
{
/// <summary>
/// Serialize an object to a string
/// </summary>
/// <param name="message"></param>
/// <returns></returns>
string Serialize<T>(T message);
}
/// <param name="message"></param>
/// <returns></returns>
byte[] Serialize<T>(T message);
}
/// <summary>
/// Serialize to string
/// </summary>
public interface IStringMessageSerializer: IMessageSerializer
{
/// <summary>
/// Serialize an object to a string
/// </summary>
/// <param name="message"></param>
/// <returns></returns>
string Serialize<T>(T message);
}

View File

@ -1,14 +1,13 @@
namespace CryptoExchange.Net.Interfaces
namespace CryptoExchange.Net.Interfaces;
/// <summary>
/// A provider for a nonce value used when signing requests
/// </summary>
public interface INonceProvider
{
/// <summary>
/// A provider for a nonce value used when signing requests
/// Get nonce value. Nonce value should be unique and incremental for each call
/// </summary>
public interface INonceProvider
{
/// <summary>
/// Get nonce value. Nonce value should be unique and incremental for each call
/// </summary>
/// <returns>Nonce value</returns>
long GetNonce();
}
/// <returns>Nonce value</returns>
long GetNonce();
}

View File

@ -1,35 +1,34 @@
using CryptoExchange.Net.Objects.Options;
using CryptoExchange.Net.Objects.Options;
using CryptoExchange.Net.SharedApis;
using System;
namespace CryptoExchange.Net.Interfaces
namespace CryptoExchange.Net.Interfaces;
/// <summary>
/// Factory for ISymbolOrderBook instances
/// </summary>
public interface IOrderBookFactory<TOptions> where TOptions : OrderBookOptions
{
/// <summary>
/// Factory for ISymbolOrderBook instances
/// Create a new order book by symbol name
/// </summary>
public interface IOrderBookFactory<TOptions> where TOptions : OrderBookOptions
{
/// <summary>
/// Create a new order book by symbol name
/// </summary>
/// <param name="symbol">Symbol name</param>
/// <param name="options">Options for the order book</param>
/// <returns></returns>
public ISymbolOrderBook Create(string symbol, Action<TOptions>? options = null);
/// <summary>
/// Create a new order book by base and quote asset names
/// </summary>
/// <param name="baseAsset">Base asset name</param>
/// <param name="quoteAsset">Quote asset name</param>
/// <param name="options">Options for the order book</param>
/// <returns></returns>
public ISymbolOrderBook Create(string baseAsset, string quoteAsset, Action<TOptions>? options = null);
/// <summary>
/// Create a new order book by base and quote asset names
/// </summary>
/// <param name="symbol">Symbol</param>
/// <param name="options">Options for the order book</param>
/// <returns></returns>
public ISymbolOrderBook Create(SharedSymbol symbol, Action<TOptions>? options = null);
}
/// <param name="symbol">Symbol name</param>
/// <param name="options">Options for the order book</param>
/// <returns></returns>
public ISymbolOrderBook Create(string symbol, Action<TOptions>? options = null);
/// <summary>
/// Create a new order book by base and quote asset names
/// </summary>
/// <param name="baseAsset">Base asset name</param>
/// <param name="quoteAsset">Quote asset name</param>
/// <param name="options">Options for the order book</param>
/// <returns></returns>
public ISymbolOrderBook Create(string baseAsset, string quoteAsset, Action<TOptions>? options = null);
/// <summary>
/// Create a new order book by base and quote asset names
/// </summary>
/// <param name="symbol">Symbol</param>
/// <param name="options">Options for the order book</param>
/// <returns></returns>
public ISymbolOrderBook Create(SharedSymbol symbol, Action<TOptions>? options = null);
}

View File

@ -4,25 +4,24 @@ using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
namespace CryptoExchange.Net.Interfaces
namespace CryptoExchange.Net.Interfaces;
/// <summary>
/// Rate limiter interface
/// </summary>
public interface IRateLimiter
{
/// <summary>
/// Rate limiter interface
/// Limit a request based on previous requests made
/// </summary>
public interface IRateLimiter
{
/// <summary>
/// Limit a request based on previous requests made
/// </summary>
/// <param name="log">The logger</param>
/// <param name="endpoint">The endpoint the request is for</param>
/// <param name="method">The Http request method</param>
/// <param name="signed">Whether the request is singed(private) or not</param>
/// <param name="apiKey">The api key making this request</param>
/// <param name="limitBehaviour">The limit behavior for when the limit is reached</param>
/// <param name="requestWeight">The weight of the request</param>
/// <param name="ct">Cancellation token to cancel waiting</param>
/// <returns>The time in milliseconds spend waiting</returns>
Task<CallResult<int>> LimitRequestAsync(ILogger log, string endpoint, HttpMethod method, bool signed, string? apiKey, RateLimitingBehaviour limitBehaviour, int requestWeight, CancellationToken ct);
}
/// <param name="log">The logger</param>
/// <param name="endpoint">The endpoint the request is for</param>
/// <param name="method">The Http request method</param>
/// <param name="signed">Whether the request is singed(private) or not</param>
/// <param name="apiKey">The api key making this request</param>
/// <param name="limitBehaviour">The limit behavior for when the limit is reached</param>
/// <param name="requestWeight">The weight of the request</param>
/// <param name="ct">Cancellation token to cancel waiting</param>
/// <returns>The time in milliseconds spend waiting</returns>
Task<CallResult<int>> LimitRequestAsync(ILogger log, string endpoint, HttpMethod method, bool signed, string? apiKey, RateLimitingBehaviour limitBehaviour, int requestWeight, CancellationToken ct);
}

View File

@ -1,66 +1,65 @@
using System;
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
namespace CryptoExchange.Net.Interfaces
namespace CryptoExchange.Net.Interfaces;
/// <summary>
/// Request interface
/// </summary>
public interface IRequest
{
/// <summary>
/// Request interface
/// Accept header
/// </summary>
public interface IRequest
{
/// <summary>
/// Accept header
/// </summary>
string Accept { set; }
/// <summary>
/// Content
/// </summary>
string? Content { get; }
/// <summary>
/// Method
/// </summary>
HttpMethod Method { get; set; }
/// <summary>
/// Uri
/// </summary>
Uri Uri { get; }
/// <summary>
/// internal request id for tracing
/// </summary>
int RequestId { get; }
/// <summary>
/// Set byte content
/// </summary>
/// <param name="data"></param>
void SetContent(byte[] data);
/// <summary>
/// Set string content
/// </summary>
/// <param name="data"></param>
/// <param name="contentType"></param>
void SetContent(string data, string contentType);
string Accept { set; }
/// <summary>
/// Content
/// </summary>
string? Content { get; }
/// <summary>
/// Method
/// </summary>
HttpMethod Method { get; set; }
/// <summary>
/// Uri
/// </summary>
Uri Uri { get; }
/// <summary>
/// internal request id for tracing
/// </summary>
int RequestId { get; }
/// <summary>
/// Set byte content
/// </summary>
/// <param name="data"></param>
void SetContent(byte[] data);
/// <summary>
/// Set string content
/// </summary>
/// <param name="data"></param>
/// <param name="contentType"></param>
void SetContent(string data, string contentType);
/// <summary>
/// Add a header to the request
/// </summary>
/// <param name="key"></param>
/// <param name="value"></param>
void AddHeader(string key, string value);
/// <summary>
/// Add a header to the request
/// </summary>
/// <param name="key"></param>
/// <param name="value"></param>
void AddHeader(string key, string value);
/// <summary>
/// Get all headers
/// </summary>
/// <returns></returns>
KeyValuePair<string, string[]>[] GetHeaders();
/// <summary>
/// Get all headers
/// </summary>
/// <returns></returns>
KeyValuePair<string, string[]>[] GetHeaders();
/// <summary>
/// Get the response
/// </summary>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task<IResponse> GetResponseAsync(CancellationToken cancellationToken);
}
/// <summary>
/// Get the response
/// </summary>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task<IResponse> GetResponseAsync(CancellationToken cancellationToken);
}

View File

@ -1,36 +1,35 @@
using CryptoExchange.Net.Objects;
using CryptoExchange.Net.Objects;
using System;
using System.Net.Http;
namespace CryptoExchange.Net.Interfaces
namespace CryptoExchange.Net.Interfaces;
/// <summary>
/// Request factory interface
/// </summary>
public interface IRequestFactory
{
/// <summary>
/// Request factory interface
/// Create a request for an uri
/// </summary>
public interface IRequestFactory
{
/// <summary>
/// Create a request for an uri
/// </summary>
/// <param name="method"></param>
/// <param name="uri"></param>
/// <param name="requestId"></param>
/// <returns></returns>
IRequest Create(HttpMethod method, Uri uri, int requestId);
/// <param name="method"></param>
/// <param name="uri"></param>
/// <param name="requestId"></param>
/// <returns></returns>
IRequest Create(HttpMethod method, Uri uri, int requestId);
/// <summary>
/// Configure the requests created by this factory
/// </summary>
/// <param name="requestTimeout">Request timeout to use</param>
/// <param name="httpClient">Optional shared http client instance</param>
/// <param name="proxy">Optional proxy to use when no http client is provided</param>
void Configure(ApiProxy? proxy, TimeSpan requestTimeout, HttpClient? httpClient = null);
/// <summary>
/// Configure the requests created by this factory
/// </summary>
/// <param name="requestTimeout">Request timeout to use</param>
/// <param name="httpClient">Optional shared http client instance</param>
/// <param name="proxy">Optional proxy to use when no http client is provided</param>
void Configure(ApiProxy? proxy, TimeSpan requestTimeout, HttpClient? httpClient = null);
/// <summary>
/// Update settings
/// </summary>
/// <param name="proxy">Proxy to use</param>
/// <param name="requestTimeout">Request timeout to use</param>
void UpdateSettings(ApiProxy? proxy, TimeSpan requestTimeout);
}
/// <summary>
/// Update settings
/// </summary>
/// <param name="proxy">Proxy to use</param>
/// <param name="requestTimeout">Request timeout to use</param>
void UpdateSettings(ApiProxy? proxy, TimeSpan requestTimeout);
}

View File

@ -1,44 +1,43 @@
using System.Collections.Generic;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Threading.Tasks;
namespace CryptoExchange.Net.Interfaces
namespace CryptoExchange.Net.Interfaces;
/// <summary>
/// Response object interface
/// </summary>
public interface IResponse
{
/// <summary>
/// Response object interface
/// The response status code
/// </summary>
public interface IResponse
{
/// <summary>
/// The response status code
/// </summary>
HttpStatusCode StatusCode { get; }
HttpStatusCode StatusCode { get; }
/// <summary>
/// Whether the status code indicates a success status
/// </summary>
bool IsSuccessStatusCode { get; }
/// <summary>
/// Whether the status code indicates a success status
/// </summary>
bool IsSuccessStatusCode { get; }
/// <summary>
/// The length of the response in bytes
/// </summary>
long? ContentLength { get; }
/// <summary>
/// The length of the response in bytes
/// </summary>
long? ContentLength { get; }
/// <summary>
/// The response headers
/// </summary>
KeyValuePair<string, string[]>[] ResponseHeaders { get; }
/// <summary>
/// The response headers
/// </summary>
KeyValuePair<string, string[]>[] ResponseHeaders { get; }
/// <summary>
/// Get the response stream
/// </summary>
/// <returns></returns>
Task<Stream> GetResponseStreamAsync();
/// <summary>
/// Get the response stream
/// </summary>
/// <returns></returns>
Task<Stream> GetResponseStreamAsync();
/// <summary>
/// Close the response
/// </summary>
void Close();
}
/// <summary>
/// Close the response
/// </summary>
void Close();
}

View File

@ -1,18 +1,17 @@
namespace CryptoExchange.Net.Interfaces
namespace CryptoExchange.Net.Interfaces;
/// <summary>
/// Base rest API client
/// </summary>
public interface IRestApiClient : IBaseApiClient
{
/// <summary>
/// Base rest API client
/// The factory for creating requests. Used for unit testing
/// </summary>
public interface IRestApiClient : IBaseApiClient
{
/// <summary>
/// The factory for creating requests. Used for unit testing
/// </summary>
IRequestFactory RequestFactory { get; set; }
IRequestFactory RequestFactory { get; set; }
/// <summary>
/// Total amount of requests made with this API client
/// </summary>
int TotalRequestsMade { get; set; }
}
/// <summary>
/// Total amount of requests made with this API client
/// </summary>
int TotalRequestsMade { get; set; }
}

View File

@ -1,26 +1,25 @@
using System;
using System;
using CryptoExchange.Net.Objects.Options;
namespace CryptoExchange.Net.Interfaces
namespace CryptoExchange.Net.Interfaces;
/// <summary>
/// Base class for rest API implementations
/// </summary>
public interface IRestClient: IDisposable
{
/// <summary>
/// Base class for rest API implementations
/// The options provided for this client
/// </summary>
public interface IRestClient: IDisposable
{
/// <summary>
/// The options provided for this client
/// </summary>
ExchangeOptions ClientOptions { get; }
ExchangeOptions ClientOptions { get; }
/// <summary>
/// The total amount of requests made with this client
/// </summary>
int TotalRequestsMade { get; }
/// <summary>
/// The total amount of requests made with this client
/// </summary>
int TotalRequestsMade { get; }
/// <summary>
/// The exchange name
/// </summary>
string Exchange { get; }
}
/// <summary>
/// The exchange name
/// </summary>
string Exchange { get; }
}

View File

@ -1,70 +1,69 @@
using CryptoExchange.Net.Objects;
using CryptoExchange.Net.Objects;
using CryptoExchange.Net.Objects.Options;
using CryptoExchange.Net.Objects.Sockets;
using System.Threading.Tasks;
namespace CryptoExchange.Net.Interfaces
namespace CryptoExchange.Net.Interfaces;
/// <summary>
/// Socket API client
/// </summary>
public interface ISocketApiClient: IBaseApiClient
{
/// <summary>
/// Socket API client
/// The current amount of socket connections on the API client
/// </summary>
public interface ISocketApiClient: IBaseApiClient
{
/// <summary>
/// The current amount of socket connections on the API client
/// </summary>
int CurrentConnections { get; }
/// <summary>
/// The current amount of subscriptions over all connections
/// </summary>
int CurrentSubscriptions { get; }
/// <summary>
/// Incoming data Kbps
/// </summary>
double IncomingKbps { get; }
/// <summary>
/// The factory for creating sockets. Used for unit testing
/// </summary>
IWebsocketFactory SocketFactory { get; set; }
/// <summary>
/// Current client options
/// </summary>
SocketExchangeOptions ClientOptions { get; }
/// <summary>
/// Current API options
/// </summary>
SocketApiOptions ApiOptions { get; }
/// <summary>
/// Log the current state of connections and subscriptions
/// </summary>
string GetSubscriptionsState(bool includeSubDetails = true);
/// <summary>
/// Reconnect all connections
/// </summary>
/// <returns></returns>
Task ReconnectAsync();
/// <summary>
/// Unsubscribe all subscriptions
/// </summary>
/// <returns></returns>
Task UnsubscribeAllAsync();
/// <summary>
/// Unsubscribe an update subscription
/// </summary>
/// <param name="subscriptionId">The id of the subscription to unsubscribe</param>
/// <returns></returns>
Task<bool> UnsubscribeAsync(int subscriptionId);
/// <summary>
/// Unsubscribe an update subscription
/// </summary>
/// <param name="subscription">The subscription to unsubscribe</param>
/// <returns></returns>
Task UnsubscribeAsync(UpdateSubscription subscription);
int CurrentConnections { get; }
/// <summary>
/// The current amount of subscriptions over all connections
/// </summary>
int CurrentSubscriptions { get; }
/// <summary>
/// Incoming data Kbps
/// </summary>
double IncomingKbps { get; }
/// <summary>
/// The factory for creating sockets. Used for unit testing
/// </summary>
IWebsocketFactory SocketFactory { get; set; }
/// <summary>
/// Current client options
/// </summary>
SocketExchangeOptions ClientOptions { get; }
/// <summary>
/// Current API options
/// </summary>
SocketApiOptions ApiOptions { get; }
/// <summary>
/// Log the current state of connections and subscriptions
/// </summary>
string GetSubscriptionsState(bool includeSubDetails = true);
/// <summary>
/// Reconnect all connections
/// </summary>
/// <returns></returns>
Task ReconnectAsync();
/// <summary>
/// Unsubscribe all subscriptions
/// </summary>
/// <returns></returns>
Task UnsubscribeAllAsync();
/// <summary>
/// Unsubscribe an update subscription
/// </summary>
/// <param name="subscriptionId">The id of the subscription to unsubscribe</param>
/// <returns></returns>
Task<bool> UnsubscribeAsync(int subscriptionId);
/// <summary>
/// Unsubscribe an update subscription
/// </summary>
/// <param name="subscription">The subscription to unsubscribe</param>
/// <returns></returns>
Task UnsubscribeAsync(UpdateSubscription subscription);
/// <summary>
/// Prepare connections which can subsequently be used for sending websocket requests. Note that this is not required. If not prepared it will be initialized at the first websocket request.
/// </summary>
/// <returns></returns>
Task<CallResult> PrepareConnectionsAsync();
}
/// <summary>
/// Prepare connections which can subsequently be used for sending websocket requests. Note that this is not required. If not prepared it will be initialized at the first websocket request.
/// </summary>
/// <returns></returns>
Task<CallResult> PrepareConnectionsAsync();
}

View File

@ -1,58 +1,57 @@
using System;
using System;
using System.Threading.Tasks;
using CryptoExchange.Net.Objects.Options;
using CryptoExchange.Net.Objects.Sockets;
namespace CryptoExchange.Net.Interfaces
namespace CryptoExchange.Net.Interfaces;
/// <summary>
/// Base class for socket API implementations
/// </summary>
public interface ISocketClient: IDisposable
{
/// <summary>
/// Base class for socket API implementations
/// The exchange name
/// </summary>
public interface ISocketClient: IDisposable
{
/// <summary>
/// The exchange name
/// </summary>
string Exchange { get; }
string Exchange { get; }
/// <summary>
/// The options provided for this client
/// </summary>
ExchangeOptions ClientOptions { get; }
/// <summary>
/// The options provided for this client
/// </summary>
ExchangeOptions ClientOptions { get; }
/// <summary>
/// Incoming kilobytes per second of data
/// </summary>
public double IncomingKbps { get; }
/// <summary>
/// Incoming kilobytes per second of data
/// </summary>
public double IncomingKbps { get; }
/// <summary>
/// The current amount of connections to the API from this client. A connection can have multiple subscriptions.
/// </summary>
public int CurrentConnections { get; }
/// <summary>
/// The current amount of subscriptions running from the client
/// </summary>
public int CurrentSubscriptions { get; }
/// <summary>
/// The current amount of connections to the API from this client. A connection can have multiple subscriptions.
/// </summary>
public int CurrentConnections { get; }
/// <summary>
/// The current amount of subscriptions running from the client
/// </summary>
public int CurrentSubscriptions { get; }
/// <summary>
/// Unsubscribe from a stream using the subscription id received when starting the subscription
/// </summary>
/// <param name="subscriptionId">The id of the subscription to unsubscribe</param>
/// <returns></returns>
Task UnsubscribeAsync(int subscriptionId);
/// <summary>
/// Unsubscribe from a stream using the subscription id received when starting the subscription
/// </summary>
/// <param name="subscriptionId">The id of the subscription to unsubscribe</param>
/// <returns></returns>
Task UnsubscribeAsync(int subscriptionId);
/// <summary>
/// Unsubscribe from a stream
/// </summary>
/// <param name="subscription">The subscription to unsubscribe</param>
/// <returns></returns>
Task UnsubscribeAsync(UpdateSubscription subscription);
/// <summary>
/// Unsubscribe from a stream
/// </summary>
/// <param name="subscription">The subscription to unsubscribe</param>
/// <returns></returns>
Task UnsubscribeAsync(UpdateSubscription subscription);
/// <summary>
/// Unsubscribe all subscriptions
/// </summary>
/// <returns></returns>
Task UnsubscribeAllAsync();
}
/// <summary>
/// Unsubscribe all subscriptions
/// </summary>
/// <returns></returns>
Task UnsubscribeAllAsync();
}

View File

@ -1,131 +1,129 @@
using System;
using System.Collections.Generic;
using System;
using System.Threading;
using System.Threading.Tasks;
using CryptoExchange.Net.Objects;
namespace CryptoExchange.Net.Interfaces
namespace CryptoExchange.Net.Interfaces;
/// <summary>
/// Interface for order book
/// </summary>
public interface ISymbolOrderBook
{
/// <summary>
/// Interface for order book
/// The exchange the book is for
/// </summary>
public interface ISymbolOrderBook
{
/// <summary>
/// The exchange the book is for
/// </summary>
string Exchange { get; }
string Exchange { get; }
/// <summary>
/// The Api the book is for
/// </summary>
string Api { get; }
/// <summary>
/// The Api the book is for
/// </summary>
string Api { get; }
/// <summary>
/// The status of the order book. Order book is up to date when the status is `Synced`
/// </summary>
OrderBookStatus Status { get; set; }
/// <summary>
/// The status of the order book. Order book is up to date when the status is `Synced`
/// </summary>
OrderBookStatus Status { get; set; }
/// <summary>
/// Last update identifier
/// </summary>
long LastSequenceNumber { get; }
/// <summary>
/// The symbol of the order book
/// </summary>
string Symbol { get; }
/// <summary>
/// Last update identifier
/// </summary>
long LastSequenceNumber { get; }
/// <summary>
/// The symbol of the order book
/// </summary>
string Symbol { get; }
/// <summary>
/// Event when the state changes
/// </summary>
event Action<OrderBookStatus, OrderBookStatus> OnStatusChange;
/// <summary>
/// Event when order book was updated. Be careful! It can generate a lot of events at high-liquidity markets
/// </summary>
event Action<(ISymbolOrderBookEntry[] Bids, ISymbolOrderBookEntry[] Asks)> OnOrderBookUpdate;
/// <summary>
/// Event when the BestBid or BestAsk changes ie a Pricing Tick
/// </summary>
event Action<(ISymbolOrderBookEntry BestBid, ISymbolOrderBookEntry BestAsk)> OnBestOffersChanged;
/// <summary>
/// Timestamp of the last update
/// </summary>
DateTime UpdateTime { get; }
/// <summary>
/// Event when the state changes
/// </summary>
event Action<OrderBookStatus, OrderBookStatus> OnStatusChange;
/// <summary>
/// Event when order book was updated. Be careful! It can generate a lot of events at high-liquidity markets
/// </summary>
event Action<(ISymbolOrderBookEntry[] Bids, ISymbolOrderBookEntry[] Asks)> OnOrderBookUpdate;
/// <summary>
/// Event when the BestBid or BestAsk changes ie a Pricing Tick
/// </summary>
event Action<(ISymbolOrderBookEntry BestBid, ISymbolOrderBookEntry BestAsk)> OnBestOffersChanged;
/// <summary>
/// Timestamp of the last update
/// </summary>
DateTime UpdateTime { get; }
/// <summary>
/// The number of asks in the book
/// </summary>
int AskCount { get; }
/// <summary>
/// The number of bids in the book
/// </summary>
int BidCount { get; }
/// <summary>
/// The number of asks in the book
/// </summary>
int AskCount { get; }
/// <summary>
/// The number of bids in the book
/// </summary>
int BidCount { get; }
/// <summary>
/// Get a snapshot of the book at this moment
/// </summary>
(ISymbolOrderBookEntry[] bids, ISymbolOrderBookEntry[] asks) Book { get; }
/// <summary>
/// Get a snapshot of the book at this moment
/// </summary>
(ISymbolOrderBookEntry[] bids, ISymbolOrderBookEntry[] asks) Book { get; }
/// <summary>
/// The list of asks
/// </summary>
ISymbolOrderBookEntry[] Asks { get; }
/// <summary>
/// The list of asks
/// </summary>
ISymbolOrderBookEntry[] Asks { get; }
/// <summary>
/// The list of bids
/// </summary>
ISymbolOrderBookEntry[] Bids { get; }
/// <summary>
/// The list of bids
/// </summary>
ISymbolOrderBookEntry[] Bids { get; }
/// <summary>
/// The best bid currently in the order book
/// </summary>
ISymbolOrderBookEntry BestBid { get; }
/// <summary>
/// The best bid currently in the order book
/// </summary>
ISymbolOrderBookEntry BestBid { get; }
/// <summary>
/// The best ask currently in the order book
/// </summary>
ISymbolOrderBookEntry BestAsk { get; }
/// <summary>
/// The best ask currently in the order book
/// </summary>
ISymbolOrderBookEntry BestAsk { get; }
/// <summary>
/// BestBid/BesAsk returned as a pair
/// </summary>
(ISymbolOrderBookEntry Bid, ISymbolOrderBookEntry Ask) BestOffers { get; }
/// <summary>
/// BestBid/BesAsk returned as a pair
/// </summary>
(ISymbolOrderBookEntry Bid, ISymbolOrderBookEntry Ask) BestOffers { get; }
/// <summary>
/// Start connecting and synchronizing the order book
/// </summary>
/// <param name="ct">A cancellation token to stop the order book when canceled</param>
/// <returns></returns>
Task<CallResult<bool>> StartAsync(CancellationToken? ct = null);
/// <summary>
/// Start connecting and synchronizing the order book
/// </summary>
/// <param name="ct">A cancellation token to stop the order book when canceled</param>
/// <returns></returns>
Task<CallResult<bool>> StartAsync(CancellationToken? ct = null);
/// <summary>
/// Stop syncing the order book
/// </summary>
/// <returns></returns>
Task StopAsync();
/// <summary>
/// Stop syncing the order book
/// </summary>
/// <returns></returns>
Task StopAsync();
/// <summary>
/// Get the average price that a market order would fill at at the current order book state. This is no guarantee that an order of that quantity would actually be filled
/// at that price since between this calculation and the order placement the book might have changed.
/// </summary>
/// <param name="quantity">The quantity in base asset to fill</param>
/// <param name="type">The type</param>
/// <returns>Average fill price</returns>
CallResult<decimal> CalculateAverageFillPrice(decimal quantity, OrderBookEntryType type);
/// <summary>
/// Get the average price that a market order would fill at at the current order book state. This is no guarantee that an order of that quantity would actually be filled
/// at that price since between this calculation and the order placement the book might have changed.
/// </summary>
/// <param name="quantity">The quantity in base asset to fill</param>
/// <param name="type">The type</param>
/// <returns>Average fill price</returns>
CallResult<decimal> CalculateAverageFillPrice(decimal quantity, OrderBookEntryType type);
/// <summary>
/// Get the amount of base asset which can be traded with the quote quantity when placing a market order at at the current order book state.
/// This is no guarantee that an order of that quantity would actually be fill the quantity returned by this since between this calculation and the order placement the book might have changed.
/// </summary>
/// <param name="quoteQuantity">The quantity in quote asset looking to trade</param>
/// <param name="type">The type</param>
/// <returns>Amount of base asset tradable with the specified amount of quote asset</returns>
CallResult<decimal> CalculateTradableAmount(decimal quoteQuantity, OrderBookEntryType type);
/// <summary>
/// Get the amount of base asset which can be traded with the quote quantity when placing a market order at at the current order book state.
/// This is no guarantee that an order of that quantity would actually be fill the quantity returned by this since between this calculation and the order placement the book might have changed.
/// </summary>
/// <param name="quoteQuantity">The quantity in quote asset looking to trade</param>
/// <param name="type">The type</param>
/// <returns>Amount of base asset tradable with the specified amount of quote asset</returns>
CallResult<decimal> CalculateTradableAmount(decimal quoteQuantity, OrderBookEntryType type);
/// <summary>
/// String representation of the top x entries
/// </summary>
/// <returns></returns>
string ToString(int rows);
}
/// <summary>
/// String representation of the top x entries
/// </summary>
/// <returns></returns>
string ToString(int rows);
}

View File

@ -1,28 +1,27 @@
namespace CryptoExchange.Net.Interfaces
namespace CryptoExchange.Net.Interfaces;
/// <summary>
/// Interface for order book entries
/// </summary>
public interface ISymbolOrderBookEntry
{
/// <summary>
/// Interface for order book entries
/// The quantity of the entry
/// </summary>
public interface ISymbolOrderBookEntry
{
/// <summary>
/// The quantity of the entry
/// </summary>
decimal Quantity { get; set; }
/// <summary>
/// The price of the entry
/// </summary>
decimal Price { get; set; }
}
decimal Quantity { get; set; }
/// <summary>
/// Interface for order book entries
/// The price of the entry
/// </summary>
public interface ISymbolOrderSequencedBookEntry: ISymbolOrderBookEntry
{
/// <summary>
/// Sequence of the update
/// </summary>
long Sequence { get; set; }
}
decimal Price { get; set; }
}
/// <summary>
/// Interface for order book entries
/// </summary>
public interface ISymbolOrderSequencedBookEntry: ISymbolOrderBookEntry
{
/// <summary>
/// Sequence of the update
/// </summary>
long Sequence { get; set; }
}

View File

@ -1,110 +1,109 @@
using CryptoExchange.Net.Objects;
using CryptoExchange.Net.Objects;
using System;
using System.Net.WebSockets;
using System.Threading;
using System.Threading.Tasks;
namespace CryptoExchange.Net.Interfaces
namespace CryptoExchange.Net.Interfaces;
/// <summary>
/// Websocket connection interface
/// </summary>
public interface IWebsocket: IDisposable
{
/// <summary>
/// Websocket connection interface
/// Websocket closed event
/// </summary>
public interface IWebsocket: IDisposable
{
/// <summary>
/// Websocket closed event
/// </summary>
event Func<Task> OnClose;
/// <summary>
/// Websocket message received event
/// </summary>
event Func<WebSocketMessageType, ReadOnlyMemory<byte>, Task> OnStreamMessage;
/// <summary>
/// Websocket sent event, RequestId as parameter
/// </summary>
event Func<int, Task> OnRequestSent;
/// <summary>
/// Websocket query was ratelimited and couldn't be send
/// </summary>
event Func<int, Task>? OnRequestRateLimited;
/// <summary>
/// Connection was ratelimited and couldn't be established
/// </summary>
event Func<Task>? OnConnectRateLimited;
/// <summary>
/// Websocket error event
/// </summary>
event Func<Exception, Task> OnError;
/// <summary>
/// Websocket opened event
/// </summary>
event Func<Task> OnOpen;
/// <summary>
/// Websocket has lost connection to the server and is attempting to reconnect
/// </summary>
event Func<Task> OnReconnecting;
/// <summary>
/// Websocket has reconnected to the server
/// </summary>
event Func<Task> OnReconnected;
/// <summary>
/// Get reconnection url
/// </summary>
Func<Task<Uri?>>? GetReconnectionUrl { get; set; }
event Func<Task> OnClose;
/// <summary>
/// Websocket message received event
/// </summary>
event Func<WebSocketMessageType, ReadOnlyMemory<byte>, Task> OnStreamMessage;
/// <summary>
/// Websocket sent event, RequestId as parameter
/// </summary>
event Func<int, Task> OnRequestSent;
/// <summary>
/// Websocket query was ratelimited and couldn't be send
/// </summary>
event Func<int, Task>? OnRequestRateLimited;
/// <summary>
/// Connection was ratelimited and couldn't be established
/// </summary>
event Func<Task>? OnConnectRateLimited;
/// <summary>
/// Websocket error event
/// </summary>
event Func<Exception, Task> OnError;
/// <summary>
/// Websocket opened event
/// </summary>
event Func<Task> OnOpen;
/// <summary>
/// Websocket has lost connection to the server and is attempting to reconnect
/// </summary>
event Func<Task> OnReconnecting;
/// <summary>
/// Websocket has reconnected to the server
/// </summary>
event Func<Task> OnReconnected;
/// <summary>
/// Get reconnection url
/// </summary>
Func<Task<Uri?>>? GetReconnectionUrl { get; set; }
/// <summary>
/// Unique id for this socket
/// </summary>
int Id { get; }
/// <summary>
/// The current kilobytes per second of data being received, averaged over the last 3 seconds
/// </summary>
double IncomingKbps { get; }
/// <summary>
/// The uri the socket connects to
/// </summary>
Uri Uri { get; }
/// <summary>
/// Whether the socket connection is closed
/// </summary>
bool IsClosed { get; }
/// <summary>
/// Whether the socket connection is open
/// </summary>
bool IsOpen { get; }
/// <summary>
/// Connect the socket
/// </summary>
/// <returns></returns>
Task<CallResult> ConnectAsync(CancellationToken ct);
/// <summary>
/// Send string data
/// </summary>
/// <param name="id"></param>
/// <param name="data"></param>
/// <param name="weight"></param>
bool Send(int id, string data, int weight);
/// <summary>
/// Send byte data
/// </summary>
/// <param name="id"></param>
/// <param name="data"></param>
/// <param name="weight"></param>
bool Send(int id, byte[] data, int weight);
/// <summary>
/// Reconnect the socket
/// </summary>
/// <returns></returns>
Task ReconnectAsync();
/// <summary>
/// Close the connection
/// </summary>
/// <returns></returns>
Task CloseAsync();
/// <summary>
/// Unique id for this socket
/// </summary>
int Id { get; }
/// <summary>
/// The current kilobytes per second of data being received, averaged over the last 3 seconds
/// </summary>
double IncomingKbps { get; }
/// <summary>
/// The uri the socket connects to
/// </summary>
Uri Uri { get; }
/// <summary>
/// Whether the socket connection is closed
/// </summary>
bool IsClosed { get; }
/// <summary>
/// Whether the socket connection is open
/// </summary>
bool IsOpen { get; }
/// <summary>
/// Connect the socket
/// </summary>
/// <returns></returns>
Task<CallResult> ConnectAsync(CancellationToken ct);
/// <summary>
/// Send string data
/// </summary>
/// <param name="id"></param>
/// <param name="data"></param>
/// <param name="weight"></param>
bool Send(int id, string data, int weight);
/// <summary>
/// Send byte data
/// </summary>
/// <param name="id"></param>
/// <param name="data"></param>
/// <param name="weight"></param>
bool Send(int id, byte[] data, int weight);
/// <summary>
/// Reconnect the socket
/// </summary>
/// <returns></returns>
Task ReconnectAsync();
/// <summary>
/// Close the connection
/// </summary>
/// <returns></returns>
Task CloseAsync();
/// <summary>
/// Update proxy setting
/// </summary>
void UpdateProxy(ApiProxy? proxy);
}
/// <summary>
/// Update proxy setting
/// </summary>
void UpdateProxy(ApiProxy? proxy);
}

View File

@ -1,19 +1,18 @@
using CryptoExchange.Net.Objects.Sockets;
using CryptoExchange.Net.Objects.Sockets;
using Microsoft.Extensions.Logging;
namespace CryptoExchange.Net.Interfaces
namespace CryptoExchange.Net.Interfaces;
/// <summary>
/// Websocket factory interface
/// </summary>
public interface IWebsocketFactory
{
/// <summary>
/// Websocket factory interface
/// Create a websocket for an url
/// </summary>
public interface IWebsocketFactory
{
/// <summary>
/// Create a websocket for an url
/// </summary>
/// <param name="logger">The logger</param>
/// <param name="parameters">The parameters to use for the connection</param>
/// <returns></returns>
IWebsocket CreateWebsocket(ILogger logger, WebSocketParameters parameters);
}
/// <param name="logger">The logger</param>
/// <param name="parameters">The parameters to use for the connection</param>
/// <returns></returns>
IWebsocket CreateWebsocket(ILogger logger, WebSocketParameters parameters);
}

View File

@ -1,47 +1,42 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace CryptoExchange.Net;
namespace CryptoExchange.Net
/// <summary>
/// Helpers for client libraries
/// </summary>
public static class LibraryHelpers
{
/// <summary>
/// Helpers for client libraries
/// Client order id separator
/// </summary>
public static class LibraryHelpers
public const string ClientOrderIdSeparator = "JK";
/// <summary>
/// Apply broker id to a client order id
/// </summary>
/// <param name="clientOrderId"></param>
/// <param name="brokerId"></param>
/// <param name="maxLength"></param>
/// <param name="allowValueAdjustment"></param>
/// <returns></returns>
public static string ApplyBrokerId(string? clientOrderId, string brokerId, int maxLength, bool allowValueAdjustment)
{
/// <summary>
/// Client order id separator
/// </summary>
public const string ClientOrderIdSeparator = "JK";
var reservedLength = brokerId.Length + ClientOrderIdSeparator.Length;
/// <summary>
/// Apply broker id to a client order id
/// </summary>
/// <param name="clientOrderId"></param>
/// <param name="brokerId"></param>
/// <param name="maxLength"></param>
/// <param name="allowValueAdjustment"></param>
/// <returns></returns>
public static string ApplyBrokerId(string? clientOrderId, string brokerId, int maxLength, bool allowValueAdjustment)
if ((clientOrderId?.Length + reservedLength) > maxLength)
return clientOrderId!;
if (!string.IsNullOrEmpty(clientOrderId))
{
var reservedLength = brokerId.Length + ClientOrderIdSeparator.Length;
if (allowValueAdjustment)
clientOrderId = brokerId + ClientOrderIdSeparator + clientOrderId;
if ((clientOrderId?.Length + reservedLength) > maxLength)
return clientOrderId!;
if (!string.IsNullOrEmpty(clientOrderId))
{
if (allowValueAdjustment)
clientOrderId = brokerId + ClientOrderIdSeparator + clientOrderId;
return clientOrderId!;
}
else
{
clientOrderId = ExchangeHelpers.AppendRandomString(brokerId + ClientOrderIdSeparator, maxLength);
}
return clientOrderId;
return clientOrderId!;
}
else
{
clientOrderId = ExchangeHelpers.AppendRandomString(brokerId + ClientOrderIdSeparator, maxLength);
}
return clientOrderId;
}
}

View File

@ -1,387 +1,386 @@
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging;
using System;
namespace CryptoExchange.Net.Logging.Extensions
{
namespace CryptoExchange.Net.Logging.Extensions;
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
public static class CryptoExchangeWebSocketClientLoggingExtension
public static class CryptoExchangeWebSocketClientLoggingExtension
{
private static readonly Action<ILogger, int, Exception?> _connecting;
private static readonly Action<ILogger, int, string, Exception?> _connectionFailed;
private static readonly Action<ILogger, int, Exception?> _connectingCanceled;
private static readonly Action<ILogger, int, Uri, Exception?> _connected;
private static readonly Action<ILogger, int, Exception?> _startingProcessing;
private static readonly Action<ILogger, int, Exception?> _finishedProcessing;
private static readonly Action<ILogger, int, Exception?> _attemptReconnect;
private static readonly Action<ILogger, int, Uri, Exception?> _setReconnectUri;
private static readonly Action<ILogger, int, int, int, Exception?> _addingBytesToSendBuffer;
private static readonly Action<ILogger, int, Exception?> _reconnectRequested;
private static readonly Action<ILogger, int, Exception?> _closeAsyncWaitingForExistingCloseTask;
private static readonly Action<ILogger, int, Exception?> _closeAsyncSocketNotOpen;
private static readonly Action<ILogger, int, Exception?> _closing;
private static readonly Action<ILogger, int, Exception?> _closed;
private static readonly Action<ILogger, int, Exception?> _disposing;
private static readonly Action<ILogger, int, Exception?> _disposed;
private static readonly Action<ILogger, int, int, int, Exception?> _sentBytes;
private static readonly Action<ILogger, int, string, Exception?> _sendLoopStoppedWithException;
private static readonly Action<ILogger, int, Exception?> _sendLoopFinished;
private static readonly Action<ILogger, int, string, string ,Exception?> _receivedCloseMessage;
private static readonly Action<ILogger, int, string, string ,Exception?> _receivedCloseConfirmation;
private static readonly Action<ILogger, int, int, Exception?> _receivedPartialMessage;
private static readonly Action<ILogger, int, int, Exception?> _receivedSingleMessage;
private static readonly Action<ILogger, int, long, Exception?> _reassembledMessage;
private static readonly Action<ILogger, int, long, Exception?> _discardIncompleteMessage;
private static readonly Action<ILogger, int, Exception?> _receiveLoopStoppedWithException;
private static readonly Action<ILogger, int, Exception?> _receiveLoopFinished;
private static readonly Action<ILogger, int, TimeSpan?, Exception?> _startingTaskForNoDataReceivedCheck;
private static readonly Action<ILogger, int, TimeSpan?, Exception?> _noDataReceiveTimeoutReconnect;
private static readonly Action<ILogger, int, string, string, Exception?> _socketProcessingStateChanged;
private static readonly Action<ILogger, int, Exception?> _socketPingTimeout;
static CryptoExchangeWebSocketClientLoggingExtension()
{
private static readonly Action<ILogger, int, Exception?> _connecting;
private static readonly Action<ILogger, int, string, Exception?> _connectionFailed;
private static readonly Action<ILogger, int, Exception?> _connectingCanceled;
private static readonly Action<ILogger, int, Uri, Exception?> _connected;
private static readonly Action<ILogger, int, Exception?> _startingProcessing;
private static readonly Action<ILogger, int, Exception?> _finishedProcessing;
private static readonly Action<ILogger, int, Exception?> _attemptReconnect;
private static readonly Action<ILogger, int, Uri, Exception?> _setReconnectUri;
private static readonly Action<ILogger, int, int, int, Exception?> _addingBytesToSendBuffer;
private static readonly Action<ILogger, int, Exception?> _reconnectRequested;
private static readonly Action<ILogger, int, Exception?> _closeAsyncWaitingForExistingCloseTask;
private static readonly Action<ILogger, int, Exception?> _closeAsyncSocketNotOpen;
private static readonly Action<ILogger, int, Exception?> _closing;
private static readonly Action<ILogger, int, Exception?> _closed;
private static readonly Action<ILogger, int, Exception?> _disposing;
private static readonly Action<ILogger, int, Exception?> _disposed;
private static readonly Action<ILogger, int, int, int, Exception?> _sentBytes;
private static readonly Action<ILogger, int, string, Exception?> _sendLoopStoppedWithException;
private static readonly Action<ILogger, int, Exception?> _sendLoopFinished;
private static readonly Action<ILogger, int, string, string ,Exception?> _receivedCloseMessage;
private static readonly Action<ILogger, int, string, string ,Exception?> _receivedCloseConfirmation;
private static readonly Action<ILogger, int, int, Exception?> _receivedPartialMessage;
private static readonly Action<ILogger, int, int, Exception?> _receivedSingleMessage;
private static readonly Action<ILogger, int, long, Exception?> _reassembledMessage;
private static readonly Action<ILogger, int, long, Exception?> _discardIncompleteMessage;
private static readonly Action<ILogger, int, Exception?> _receiveLoopStoppedWithException;
private static readonly Action<ILogger, int, Exception?> _receiveLoopFinished;
private static readonly Action<ILogger, int, TimeSpan?, Exception?> _startingTaskForNoDataReceivedCheck;
private static readonly Action<ILogger, int, TimeSpan?, Exception?> _noDataReceiveTimeoutReconnect;
private static readonly Action<ILogger, int, string, string, Exception?> _socketProcessingStateChanged;
private static readonly Action<ILogger, int, Exception?> _socketPingTimeout;
_connecting = LoggerMessage.Define<int>(
LogLevel.Debug,
new EventId(1000, "Connecting"),
"[Sckt {SocketId}] connecting");
static CryptoExchangeWebSocketClientLoggingExtension()
{
_connecting = LoggerMessage.Define<int>(
LogLevel.Debug,
new EventId(1000, "Connecting"),
"[Sckt {SocketId}] connecting");
_connectionFailed = LoggerMessage.Define<int, string>(
LogLevel.Error,
new EventId(1001, "ConnectionFailed"),
"[Sckt {SocketId}] connection failed: {ErrorMessage}");
_connectionFailed = LoggerMessage.Define<int, string>(
LogLevel.Error,
new EventId(1001, "ConnectionFailed"),
"[Sckt {SocketId}] connection failed: {ErrorMessage}");
_connected = LoggerMessage.Define<int, Uri?>(
LogLevel.Debug,
new EventId(1002, "Connected"),
"[Sckt {SocketId}] connected to {Uri}");
_connected = LoggerMessage.Define<int, Uri?>(
LogLevel.Debug,
new EventId(1002, "Connected"),
"[Sckt {SocketId}] connected to {Uri}");
_startingProcessing = LoggerMessage.Define<int>(
LogLevel.Debug,
new EventId(1003, "StartingProcessing"),
"[Sckt {SocketId}] starting processing tasks");
_startingProcessing = LoggerMessage.Define<int>(
LogLevel.Debug,
new EventId(1003, "StartingProcessing"),
"[Sckt {SocketId}] starting processing tasks");
_finishedProcessing = LoggerMessage.Define<int>(
LogLevel.Debug,
new EventId(1004, "FinishedProcessing"),
"[Sckt {SocketId}] processing tasks finished");
_finishedProcessing = LoggerMessage.Define<int>(
LogLevel.Debug,
new EventId(1004, "FinishedProcessing"),
"[Sckt {SocketId}] processing tasks finished");
_attemptReconnect = LoggerMessage.Define<int>(
LogLevel.Debug,
new EventId(1005, "AttemptReconnect"),
"[Sckt {SocketId}] attempting to reconnect");
_attemptReconnect = LoggerMessage.Define<int>(
LogLevel.Debug,
new EventId(1005, "AttemptReconnect"),
"[Sckt {SocketId}] attempting to reconnect");
_setReconnectUri = LoggerMessage.Define<int, Uri>(
LogLevel.Debug,
new EventId(1006, "SetReconnectUri"),
"[Sckt {SocketId}] reconnect URI set to {ReconnectUri}");
_setReconnectUri = LoggerMessage.Define<int, Uri>(
LogLevel.Debug,
new EventId(1006, "SetReconnectUri"),
"[Sckt {SocketId}] reconnect URI set to {ReconnectUri}");
_addingBytesToSendBuffer = LoggerMessage.Define<int, int, int>(
LogLevel.Trace,
new EventId(1007, "AddingBytesToSendBuffer"),
"[Sckt {SocketId}] [Req {RequestId}] adding {NumBytes} bytes to send buffer");
_addingBytesToSendBuffer = LoggerMessage.Define<int, int, int>(
LogLevel.Trace,
new EventId(1007, "AddingBytesToSendBuffer"),
"[Sckt {SocketId}] [Req {RequestId}] adding {NumBytes} bytes to send buffer");
_reconnectRequested = LoggerMessage.Define<int>(
LogLevel.Debug,
new EventId(1008, "ReconnectRequested"),
"[Sckt {SocketId}] reconnect requested");
_reconnectRequested = LoggerMessage.Define<int>(
LogLevel.Debug,
new EventId(1008, "ReconnectRequested"),
"[Sckt {SocketId}] reconnect requested");
_closeAsyncWaitingForExistingCloseTask = LoggerMessage.Define<int>(
LogLevel.Debug,
new EventId(1009, "CloseAsyncWaitForExistingCloseTask"),
"[Sckt {SocketId}] CloseAsync() waiting for existing close task");
_closeAsyncWaitingForExistingCloseTask = LoggerMessage.Define<int>(
LogLevel.Debug,
new EventId(1009, "CloseAsyncWaitForExistingCloseTask"),
"[Sckt {SocketId}] CloseAsync() waiting for existing close task");
_closeAsyncSocketNotOpen = LoggerMessage.Define<int>(
LogLevel.Debug,
new EventId(1010, "CloseAsyncSocketNotOpen"),
"[Sckt {SocketId}] CloseAsync() socket not open");
_closeAsyncSocketNotOpen = LoggerMessage.Define<int>(
LogLevel.Debug,
new EventId(1010, "CloseAsyncSocketNotOpen"),
"[Sckt {SocketId}] CloseAsync() socket not open");
_closing = LoggerMessage.Define<int>(
LogLevel.Debug,
new EventId(1011, "Closing"),
"[Sckt {SocketId}] closing");
_closing = LoggerMessage.Define<int>(
LogLevel.Debug,
new EventId(1011, "Closing"),
"[Sckt {SocketId}] closing");
_closed = LoggerMessage.Define<int>(
LogLevel.Debug,
new EventId(1012, "Closed"),
"[Sckt {SocketId}] closed");
_closed = LoggerMessage.Define<int>(
LogLevel.Debug,
new EventId(1012, "Closed"),
"[Sckt {SocketId}] closed");
_disposing = LoggerMessage.Define<int>(
LogLevel.Debug,
new EventId(1013, "Disposing"),
"[Sckt {SocketId}] disposing");
_disposing = LoggerMessage.Define<int>(
LogLevel.Debug,
new EventId(1013, "Disposing"),
"[Sckt {SocketId}] disposing");
_disposed = LoggerMessage.Define<int>(
LogLevel.Trace,
new EventId(1014, "Disposed"),
"[Sckt {SocketId}] disposed");
_disposed = LoggerMessage.Define<int>(
LogLevel.Trace,
new EventId(1014, "Disposed"),
"[Sckt {SocketId}] disposed");
_sentBytes = LoggerMessage.Define<int, int, int>(
LogLevel.Trace,
new EventId(1016, "SentBytes"),
"[Sckt {SocketId}] [Req {RequestId}] sent {NumBytes} bytes");
_sentBytes = LoggerMessage.Define<int, int, int>(
LogLevel.Trace,
new EventId(1016, "SentBytes"),
"[Sckt {SocketId}] [Req {RequestId}] sent {NumBytes} bytes");
_sendLoopStoppedWithException = LoggerMessage.Define<int, string>(
LogLevel.Warning,
new EventId(1017, "SendLoopStoppedWithException"),
"[Sckt {SocketId}] send loop stopped with exception: {ErrorMessage}");
_sendLoopStoppedWithException = LoggerMessage.Define<int, string>(
LogLevel.Warning,
new EventId(1017, "SendLoopStoppedWithException"),
"[Sckt {SocketId}] send loop stopped with exception: {ErrorMessage}");
_sendLoopFinished = LoggerMessage.Define<int>(
LogLevel.Debug,
new EventId(1018, "SendLoopFinished"),
"[Sckt {SocketId}] send loop finished");
_sendLoopFinished = LoggerMessage.Define<int>(
LogLevel.Debug,
new EventId(1018, "SendLoopFinished"),
"[Sckt {SocketId}] send loop finished");
_receivedCloseMessage = LoggerMessage.Define<int, string, string>(
LogLevel.Debug,
new EventId(1019, "ReceivedCloseMessage"),
"[Sckt {SocketId}] received `Close` message, CloseStatus: {CloseStatus}, CloseStatusDescription: {CloseStatusDescription}");
_receivedCloseMessage = LoggerMessage.Define<int, string, string>(
LogLevel.Debug,
new EventId(1019, "ReceivedCloseMessage"),
"[Sckt {SocketId}] received `Close` message, CloseStatus: {CloseStatus}, CloseStatusDescription: {CloseStatusDescription}");
_receivedPartialMessage = LoggerMessage.Define<int, int>(
LogLevel.Trace,
new EventId(1020, "ReceivedPartialMessage"),
"[Sckt {SocketId}] received {NumBytes} bytes in partial message");
_receivedPartialMessage = LoggerMessage.Define<int, int>(
LogLevel.Trace,
new EventId(1020, "ReceivedPartialMessage"),
"[Sckt {SocketId}] received {NumBytes} bytes in partial message");
_receivedSingleMessage = LoggerMessage.Define<int, int>(
LogLevel.Trace,
new EventId(1021, "ReceivedSingleMessage"),
"[Sckt {SocketId}] received {NumBytes} bytes in single message");
_receivedSingleMessage = LoggerMessage.Define<int, int>(
LogLevel.Trace,
new EventId(1021, "ReceivedSingleMessage"),
"[Sckt {SocketId}] received {NumBytes} bytes in single message");
_reassembledMessage = LoggerMessage.Define<int, long>(
LogLevel.Trace,
new EventId(1022, "ReassembledMessage"),
"[Sckt {SocketId}] reassembled message of {NumBytes} bytes");
_reassembledMessage = LoggerMessage.Define<int, long>(
LogLevel.Trace,
new EventId(1022, "ReassembledMessage"),
"[Sckt {SocketId}] reassembled message of {NumBytes} bytes");
_discardIncompleteMessage = LoggerMessage.Define<int, long>(
LogLevel.Trace,
new EventId(1023, "DiscardIncompleteMessage"),
"[Sckt {SocketId}] discarding incomplete message of {NumBytes} bytes");
_discardIncompleteMessage = LoggerMessage.Define<int, long>(
LogLevel.Trace,
new EventId(1023, "DiscardIncompleteMessage"),
"[Sckt {SocketId}] discarding incomplete message of {NumBytes} bytes");
_receiveLoopStoppedWithException = LoggerMessage.Define<int>(
LogLevel.Error,
new EventId(1024, "ReceiveLoopStoppedWithException"),
"[Sckt {SocketId}] receive loop stopped with exception");
_receiveLoopStoppedWithException = LoggerMessage.Define<int>(
LogLevel.Error,
new EventId(1024, "ReceiveLoopStoppedWithException"),
"[Sckt {SocketId}] receive loop stopped with exception");
_receiveLoopFinished = LoggerMessage.Define<int>(
LogLevel.Debug,
new EventId(1025, "ReceiveLoopFinished"),
"[Sckt {SocketId}] receive loop finished");
_receiveLoopFinished = LoggerMessage.Define<int>(
LogLevel.Debug,
new EventId(1025, "ReceiveLoopFinished"),
"[Sckt {SocketId}] receive loop finished");
_startingTaskForNoDataReceivedCheck = LoggerMessage.Define<int, TimeSpan?>(
LogLevel.Debug,
new EventId(1026, "StartingTaskForNoDataReceivedCheck"),
"[Sckt {SocketId}] starting task checking for no data received for {Timeout}");
_startingTaskForNoDataReceivedCheck = LoggerMessage.Define<int, TimeSpan?>(
LogLevel.Debug,
new EventId(1026, "StartingTaskForNoDataReceivedCheck"),
"[Sckt {SocketId}] starting task checking for no data received for {Timeout}");
_noDataReceiveTimeoutReconnect = LoggerMessage.Define<int, TimeSpan?>(
LogLevel.Warning,
new EventId(1027, "NoDataReceiveTimeoutReconnect"),
"[Sckt {SocketId}] no data received for {Timeout}, reconnecting socket");
_noDataReceiveTimeoutReconnect = LoggerMessage.Define<int, TimeSpan?>(
LogLevel.Warning,
new EventId(1027, "NoDataReceiveTimeoutReconnect"),
"[Sckt {SocketId}] no data received for {Timeout}, reconnecting socket");
_receivedCloseConfirmation = LoggerMessage.Define<int, string, string>(
LogLevel.Debug,
new EventId(1028, "ReceivedCloseMessage"),
"[Sckt {SocketId}] received `Close` message confirming our close request, CloseStatus: {CloseStatus}, CloseStatusDescription: {CloseStatusDescription}");
_receivedCloseConfirmation = LoggerMessage.Define<int, string, string>(
LogLevel.Debug,
new EventId(1028, "ReceivedCloseMessage"),
"[Sckt {SocketId}] received `Close` message confirming our close request, CloseStatus: {CloseStatus}, CloseStatusDescription: {CloseStatusDescription}");
_socketProcessingStateChanged = LoggerMessage.Define<int, string, string>(
LogLevel.Trace,
new EventId(1029, "SocketProcessingStateChanged"),
"[Sckt {Id}] processing state change: {PreviousState} -> {NewState}");
_socketProcessingStateChanged = LoggerMessage.Define<int, string, string>(
LogLevel.Trace,
new EventId(1029, "SocketProcessingStateChanged"),
"[Sckt {Id}] processing state change: {PreviousState} -> {NewState}");
_socketPingTimeout = LoggerMessage.Define<int>(
LogLevel.Warning,
new EventId(1030, "SocketPingTimeout"),
"[Sckt {Id}] ping frame timeout; reconnecting socket");
_socketPingTimeout = LoggerMessage.Define<int>(
LogLevel.Warning,
new EventId(1030, "SocketPingTimeout"),
"[Sckt {Id}] ping frame timeout; reconnecting socket");
_connectingCanceled = LoggerMessage.Define<int>(
LogLevel.Debug,
new EventId(1031, "ConnectingCanceled"),
"[Sckt {SocketId}] connecting canceled");
_connectingCanceled = LoggerMessage.Define<int>(
LogLevel.Debug,
new EventId(1031, "ConnectingCanceled"),
"[Sckt {SocketId}] connecting canceled");
}
}
public static void SocketConnecting(
this ILogger logger, int socketId)
{
_connecting(logger, socketId, null);
}
public static void SocketConnecting(
this ILogger logger, int socketId)
{
_connecting(logger, socketId, null);
}
public static void SocketConnectionFailed(
this ILogger logger, int socketId, string message, Exception e)
{
_connectionFailed(logger, socketId, message, e);
}
public static void SocketConnectionFailed(
this ILogger logger, int socketId, string message, Exception e)
{
_connectionFailed(logger, socketId, message, e);
}
public static void SocketConnected(
this ILogger logger, int socketId, Uri uri)
{
_connected(logger, socketId, uri, null);
}
public static void SocketConnected(
this ILogger logger, int socketId, Uri uri)
{
_connected(logger, socketId, uri, null);
}
public static void SocketStartingProcessing(
this ILogger logger, int socketId)
{
_startingProcessing(logger, socketId, null);
}
public static void SocketStartingProcessing(
this ILogger logger, int socketId)
{
_startingProcessing(logger, socketId, null);
}
public static void SocketFinishedProcessing(
this ILogger logger, int socketId)
{
_finishedProcessing(logger, socketId, null);
}
public static void SocketFinishedProcessing(
this ILogger logger, int socketId)
{
_finishedProcessing(logger, socketId, null);
}
public static void SocketAttemptReconnect(
this ILogger logger, int socketId)
{
_attemptReconnect(logger, socketId, null);
}
public static void SocketAttemptReconnect(
this ILogger logger, int socketId)
{
_attemptReconnect(logger, socketId, null);
}
public static void SocketSetReconnectUri(
this ILogger logger, int socketId, Uri uri)
{
_setReconnectUri(logger, socketId, uri, null);
}
public static void SocketSetReconnectUri(
this ILogger logger, int socketId, Uri uri)
{
_setReconnectUri(logger, socketId, uri, null);
}
public static void SocketAddingBytesToSendBuffer(
this ILogger logger, int socketId, int requestId, byte[] bytes)
{
_addingBytesToSendBuffer(logger, socketId, requestId, bytes.Length, null);
}
public static void SocketAddingBytesToSendBuffer(
this ILogger logger, int socketId, int requestId, byte[] bytes)
{
_addingBytesToSendBuffer(logger, socketId, requestId, bytes.Length, null);
}
public static void SocketReconnectRequested(
this ILogger logger, int socketId)
{
_reconnectRequested(logger, socketId, null);
}
public static void SocketReconnectRequested(
this ILogger logger, int socketId)
{
_reconnectRequested(logger, socketId, null);
}
public static void SocketCloseAsyncWaitingForExistingCloseTask(
this ILogger logger, int socketId)
{
_closeAsyncWaitingForExistingCloseTask(logger, socketId, null);
}
public static void SocketCloseAsyncWaitingForExistingCloseTask(
this ILogger logger, int socketId)
{
_closeAsyncWaitingForExistingCloseTask(logger, socketId, null);
}
public static void SocketCloseAsyncSocketNotOpen(
this ILogger logger, int socketId)
{
_closeAsyncSocketNotOpen(logger, socketId, null);
}
public static void SocketCloseAsyncSocketNotOpen(
this ILogger logger, int socketId)
{
_closeAsyncSocketNotOpen(logger, socketId, null);
}
public static void SocketClosing(
this ILogger logger, int socketId)
{
_closing(logger, socketId, null);
}
public static void SocketClosing(
this ILogger logger, int socketId)
{
_closing(logger, socketId, null);
}
public static void SocketClosed(
this ILogger logger, int socketId)
{
_closed(logger, socketId, null);
}
public static void SocketClosed(
this ILogger logger, int socketId)
{
_closed(logger, socketId, null);
}
public static void SocketDisposing(
this ILogger logger, int socketId)
{
_disposing(logger, socketId, null);
}
public static void SocketDisposing(
this ILogger logger, int socketId)
{
_disposing(logger, socketId, null);
}
public static void SocketDisposed(
this ILogger logger, int socketId)
{
_disposed(logger, socketId, null);
}
public static void SocketDisposed(
this ILogger logger, int socketId)
{
_disposed(logger, socketId, null);
}
public static void SocketSentBytes(
this ILogger logger, int socketId, int requestId, int numBytes)
{
_sentBytes(logger, socketId, requestId, numBytes, null);
}
public static void SocketSentBytes(
this ILogger logger, int socketId, int requestId, int numBytes)
{
_sentBytes(logger, socketId, requestId, numBytes, null);
}
public static void SocketSendLoopStoppedWithException(
this ILogger logger, int socketId, string message, Exception e)
{
_sendLoopStoppedWithException(logger, socketId, message, e);
}
public static void SocketSendLoopStoppedWithException(
this ILogger logger, int socketId, string message, Exception e)
{
_sendLoopStoppedWithException(logger, socketId, message, e);
}
public static void SocketSendLoopFinished(
this ILogger logger, int socketId)
{
_sendLoopFinished(logger, socketId, null);
}
public static void SocketSendLoopFinished(
this ILogger logger, int socketId)
{
_sendLoopFinished(logger, socketId, null);
}
public static void SocketReceivedCloseMessage(
this ILogger logger, int socketId, string webSocketCloseStatus, string closeStatusDescription)
{
_receivedCloseMessage(logger, socketId, webSocketCloseStatus, closeStatusDescription, null);
}
public static void SocketReceivedCloseMessage(
this ILogger logger, int socketId, string webSocketCloseStatus, string closeStatusDescription)
{
_receivedCloseMessage(logger, socketId, webSocketCloseStatus, closeStatusDescription, null);
}
public static void SocketReceivedCloseConfirmation(
this ILogger logger, int socketId, string webSocketCloseStatus, string closeStatusDescription)
{
_receivedCloseConfirmation(logger, socketId, webSocketCloseStatus, closeStatusDescription, null);
}
public static void SocketReceivedCloseConfirmation(
this ILogger logger, int socketId, string webSocketCloseStatus, string closeStatusDescription)
{
_receivedCloseConfirmation(logger, socketId, webSocketCloseStatus, closeStatusDescription, null);
}
public static void SocketReceivedPartialMessage(
this ILogger logger, int socketId, int countBytes)
{
_receivedPartialMessage(logger, socketId, countBytes, null);
}
public static void SocketReceivedPartialMessage(
this ILogger logger, int socketId, int countBytes)
{
_receivedPartialMessage(logger, socketId, countBytes, null);
}
public static void SocketReceivedSingleMessage(
this ILogger logger, int socketId, int countBytes)
{
_receivedSingleMessage(logger, socketId, countBytes, null);
}
public static void SocketReceivedSingleMessage(
this ILogger logger, int socketId, int countBytes)
{
_receivedSingleMessage(logger, socketId, countBytes, null);
}
public static void SocketReassembledMessage(
this ILogger logger, int socketId, long countBytes)
{
_reassembledMessage(logger, socketId, countBytes, null);
}
public static void SocketReassembledMessage(
this ILogger logger, int socketId, long countBytes)
{
_reassembledMessage(logger, socketId, countBytes, null);
}
public static void SocketDiscardIncompleteMessage(
this ILogger logger, int socketId, long countBytes)
{
_discardIncompleteMessage(logger, socketId, countBytes, null);
}
public static void SocketDiscardIncompleteMessage(
this ILogger logger, int socketId, long countBytes)
{
_discardIncompleteMessage(logger, socketId, countBytes, null);
}
public static void SocketReceiveLoopStoppedWithException(
this ILogger logger, int socketId, Exception e)
{
_receiveLoopStoppedWithException(logger, socketId, e);
}
public static void SocketReceiveLoopStoppedWithException(
this ILogger logger, int socketId, Exception e)
{
_receiveLoopStoppedWithException(logger, socketId, e);
}
public static void SocketReceiveLoopFinished(
this ILogger logger, int socketId)
{
_receiveLoopFinished(logger, socketId, null);
}
public static void SocketReceiveLoopFinished(
this ILogger logger, int socketId)
{
_receiveLoopFinished(logger, socketId, null);
}
public static void SocketStartingTaskForNoDataReceivedCheck(
this ILogger logger, int socketId, TimeSpan? timeSpan)
{
_startingTaskForNoDataReceivedCheck(logger, socketId, timeSpan, null);
}
public static void SocketStartingTaskForNoDataReceivedCheck(
this ILogger logger, int socketId, TimeSpan? timeSpan)
{
_startingTaskForNoDataReceivedCheck(logger, socketId, timeSpan, null);
}
public static void SocketNoDataReceiveTimoutReconnect(
this ILogger logger, int socketId, TimeSpan? timeSpan)
{
_noDataReceiveTimeoutReconnect(logger, socketId, timeSpan, null);
}
public static void SocketNoDataReceiveTimoutReconnect(
this ILogger logger, int socketId, TimeSpan? timeSpan)
{
_noDataReceiveTimeoutReconnect(logger, socketId, timeSpan, null);
}
public static void SocketProcessingStateChanged(
this ILogger logger, int socketId, string prevState, string newState)
{
_socketProcessingStateChanged(logger, socketId, prevState, newState, null);
}
public static void SocketProcessingStateChanged(
this ILogger logger, int socketId, string prevState, string newState)
{
_socketProcessingStateChanged(logger, socketId, prevState, newState, null);
}
public static void SocketPingTimeout(
this ILogger logger, int socketId)
{
_socketPingTimeout(logger, socketId, null);
}
public static void SocketPingTimeout(
this ILogger logger, int socketId)
{
_socketPingTimeout(logger, socketId, null);
}
public static void SocketConnectingCanceled(
this ILogger logger, int socketId)
{
_connectingCanceled(logger, socketId, null);
}
public static void SocketConnectingCanceled(
this ILogger logger, int socketId)
{
_connectingCanceled(logger, socketId, null);
}
}

View File

@ -1,79 +1,78 @@
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging;
using System;
namespace CryptoExchange.Net.Logging.Extensions
{
namespace CryptoExchange.Net.Logging.Extensions;
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
public static class RateLimitGateLoggingExtensions
public static class RateLimitGateLoggingExtensions
{
private static readonly Action<ILogger, int, string, string, string, Exception?> _rateLimitRequestFailed;
private static readonly Action<ILogger, int, string, string, Exception?> _rateLimitConnectionFailed;
private static readonly Action<ILogger, int, string, TimeSpan, string, string, Exception?> _rateLimitDelayingRequest;
private static readonly Action<ILogger, int, TimeSpan, string, string, Exception?> _rateLimitDelayingConnection;
private static readonly Action<ILogger, int, string, string, string, int, Exception?> _rateLimitAppliedRequest;
private static readonly Action<ILogger, int, string, string, int, Exception?> _rateLimitAppliedConnection;
static RateLimitGateLoggingExtensions()
{
private static readonly Action<ILogger, int, string, string, string, Exception?> _rateLimitRequestFailed;
private static readonly Action<ILogger, int, string, string, Exception?> _rateLimitConnectionFailed;
private static readonly Action<ILogger, int, string, TimeSpan, string, string, Exception?> _rateLimitDelayingRequest;
private static readonly Action<ILogger, int, TimeSpan, string, string, Exception?> _rateLimitDelayingConnection;
private static readonly Action<ILogger, int, string, string, string, int, Exception?> _rateLimitAppliedRequest;
private static readonly Action<ILogger, int, string, string, int, Exception?> _rateLimitAppliedConnection;
_rateLimitRequestFailed = LoggerMessage.Define<int, string, string, string>(
LogLevel.Warning,
new EventId(6000, "RateLimitRequestFailed"),
"[Req {Id}] Call to {Path} failed because of ratelimit guard {Guard}; {Limit}");
static RateLimitGateLoggingExtensions()
{
_rateLimitRequestFailed = LoggerMessage.Define<int, string, string, string>(
LogLevel.Warning,
new EventId(6000, "RateLimitRequestFailed"),
"[Req {Id}] Call to {Path} failed because of ratelimit guard {Guard}; {Limit}");
_rateLimitConnectionFailed = LoggerMessage.Define<int, string, string>(
LogLevel.Warning,
new EventId(6001, "RateLimitConnectionFailed"),
"[Sckt {Id}] Connection failed because of ratelimit guard {Guard}; {Limit}");
_rateLimitConnectionFailed = LoggerMessage.Define<int, string, string>(
LogLevel.Warning,
new EventId(6001, "RateLimitConnectionFailed"),
"[Sckt {Id}] Connection failed because of ratelimit guard {Guard}; {Limit}");
_rateLimitDelayingRequest = LoggerMessage.Define<int, string, TimeSpan, string, string>(
LogLevel.Warning,
new EventId(6002, "RateLimitDelayingRequest"),
"[Req {Id}] Delaying call to {Path} by {Delay} because of ratelimit guard {Guard}; {Limit}");
_rateLimitDelayingRequest = LoggerMessage.Define<int, string, TimeSpan, string, string>(
LogLevel.Warning,
new EventId(6002, "RateLimitDelayingRequest"),
"[Req {Id}] Delaying call to {Path} by {Delay} because of ratelimit guard {Guard}; {Limit}");
_rateLimitDelayingConnection = LoggerMessage.Define<int, TimeSpan, string, string>(
LogLevel.Warning,
new EventId(6003, "RateLimitDelayingConnection"),
"[Sckt {Id}] Delaying connection by {Delay} because of ratelimit guard {Guard}; {Limit}");
_rateLimitDelayingConnection = LoggerMessage.Define<int, TimeSpan, string, string>(
LogLevel.Warning,
new EventId(6003, "RateLimitDelayingConnection"),
"[Sckt {Id}] Delaying connection by {Delay} because of ratelimit guard {Guard}; {Limit}");
_rateLimitAppliedConnection = LoggerMessage.Define<int, string, string, int>(
LogLevel.Trace,
new EventId(6004, "RateLimitDelayingConnection"),
"[Sckt {Id}] Connection passed ratelimit guard {Guard}; {Limit}, New count: {Current}");
_rateLimitAppliedConnection = LoggerMessage.Define<int, string, string, int>(
LogLevel.Trace,
new EventId(6004, "RateLimitDelayingConnection"),
"[Sckt {Id}] Connection passed ratelimit guard {Guard}; {Limit}, New count: {Current}");
_rateLimitAppliedRequest = LoggerMessage.Define<int, string, string, string, int>(
LogLevel.Trace,
new EventId(6005, "RateLimitAppliedRequest"),
"[Req {Id}] Call to {Path} passed ratelimit guard {Guard}; {Limit}, New count: {Current}");
}
_rateLimitAppliedRequest = LoggerMessage.Define<int, string, string, string, int>(
LogLevel.Trace,
new EventId(6005, "RateLimitAppliedRequest"),
"[Req {Id}] Call to {Path} passed ratelimit guard {Guard}; {Limit}, New count: {Current}");
}
public static void RateLimitRequestFailed(this ILogger logger, int requestId, string path, string guard, string limit)
{
_rateLimitRequestFailed(logger, requestId, path, guard, limit, null);
}
public static void RateLimitRequestFailed(this ILogger logger, int requestId, string path, string guard, string limit)
{
_rateLimitRequestFailed(logger, requestId, path, guard, limit, null);
}
public static void RateLimitConnectionFailed(this ILogger logger, int connectionId, string guard, string limit)
{
_rateLimitConnectionFailed(logger, connectionId, guard, limit, null);
}
public static void RateLimitConnectionFailed(this ILogger logger, int connectionId, string guard, string limit)
{
_rateLimitConnectionFailed(logger, connectionId, guard, limit, null);
}
public static void RateLimitDelayingRequest(this ILogger logger, int requestId, string path, TimeSpan delay, string guard, string limit)
{
_rateLimitDelayingRequest(logger, requestId, path, delay, guard, limit, null);
}
public static void RateLimitDelayingRequest(this ILogger logger, int requestId, string path, TimeSpan delay, string guard, string limit)
{
_rateLimitDelayingRequest(logger, requestId, path, delay, guard, limit, null);
}
public static void RateLimitDelayingConnection(this ILogger logger, int connectionId, TimeSpan delay, string guard, string limit)
{
_rateLimitDelayingConnection(logger, connectionId, delay, guard, limit, null);
}
public static void RateLimitDelayingConnection(this ILogger logger, int connectionId, TimeSpan delay, string guard, string limit)
{
_rateLimitDelayingConnection(logger, connectionId, delay, guard, limit, null);
}
public static void RateLimitAppliedConnection(this ILogger logger, int connectionId, string guard, string limit, int current)
{
_rateLimitAppliedConnection(logger, connectionId, guard, limit, current, null);
}
public static void RateLimitAppliedConnection(this ILogger logger, int connectionId, string guard, string limit, int current)
{
_rateLimitAppliedConnection(logger, connectionId, guard, limit, current, null);
}
public static void RateLimitAppliedRequest(this ILogger logger, int requestIdId, string path, string guard, string limit, int current)
{
_rateLimitAppliedRequest(logger, requestIdId, path, guard, limit, current, null);
}
public static void RateLimitAppliedRequest(this ILogger logger, int requestIdId, string path, string guard, string limit, int current)
{
_rateLimitAppliedRequest(logger, requestIdId, path, guard, limit, current, null);
}
}

View File

@ -1,159 +1,158 @@
using CryptoExchange.Net.Objects;
using CryptoExchange.Net.Objects;
using Microsoft.Extensions.Logging;
using System;
using System.Net;
using System.Net.Http;
namespace CryptoExchange.Net.Logging.Extensions
{
namespace CryptoExchange.Net.Logging.Extensions;
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
public static class RestApiClientLoggingExtensions
public static class RestApiClientLoggingExtensions
{
private static readonly Action<ILogger, int?, int?, long, string?, string?, Exception?> _restApiErrorReceived;
private static readonly Action<ILogger, int?, int?, long, string?, Exception?> _restApiResponseReceived;
private static readonly Action<ILogger, int, string, Exception?> _restApiFailedToSyncTime;
private static readonly Action<ILogger, int, string, Exception?> _restApiNoApiCredentials;
private static readonly Action<ILogger, int, Uri, Exception?> _restApiCreatingRequest;
private static readonly Action<ILogger, int, HttpMethod, string, Uri, string, Exception?> _restApiSendingRequest;
private static readonly Action<ILogger, int, DateTime, Exception?> _restApiRateLimitRetry;
private static readonly Action<ILogger, int, DateTime, Exception?> _restApiRateLimitPauseUntil;
private static readonly Action<ILogger, int, RequestDefinition, string?, string, string, Exception?> _restApiSendRequest;
private static readonly Action<ILogger, string, Exception?> _restApiCheckingCache;
private static readonly Action<ILogger, string, Exception?> _restApiCacheHit;
private static readonly Action<ILogger, string, Exception?> _restApiCacheNotHit;
private static readonly Action<ILogger, int?, Exception?> _restApiCancellationRequested;
static RestApiClientLoggingExtensions()
{
private static readonly Action<ILogger, int?, int?, long, string?, string?, Exception?> _restApiErrorReceived;
private static readonly Action<ILogger, int?, int?, long, string?, Exception?> _restApiResponseReceived;
private static readonly Action<ILogger, int, string, Exception?> _restApiFailedToSyncTime;
private static readonly Action<ILogger, int, string, Exception?> _restApiNoApiCredentials;
private static readonly Action<ILogger, int, Uri, Exception?> _restApiCreatingRequest;
private static readonly Action<ILogger, int, HttpMethod, string, Uri, string, Exception?> _restApiSendingRequest;
private static readonly Action<ILogger, int, DateTime, Exception?> _restApiRateLimitRetry;
private static readonly Action<ILogger, int, DateTime, Exception?> _restApiRateLimitPauseUntil;
private static readonly Action<ILogger, int, RequestDefinition, string?, string, string, Exception?> _restApiSendRequest;
private static readonly Action<ILogger, string, Exception?> _restApiCheckingCache;
private static readonly Action<ILogger, string, Exception?> _restApiCacheHit;
private static readonly Action<ILogger, string, Exception?> _restApiCacheNotHit;
private static readonly Action<ILogger, int?, Exception?> _restApiCancellationRequested;
_restApiErrorReceived = LoggerMessage.Define<int?, int?, long, string?, string?>(
LogLevel.Warning,
new EventId(4000, "RestApiErrorReceived"),
"[Req {RequestId}] {ResponseStatusCode} - Error received in {ResponseTime}ms: {ErrorMessage}, Data: {OriginalData}");
static RestApiClientLoggingExtensions()
{
_restApiErrorReceived = LoggerMessage.Define<int?, int?, long, string?, string?>(
LogLevel.Warning,
new EventId(4000, "RestApiErrorReceived"),
"[Req {RequestId}] {ResponseStatusCode} - Error received in {ResponseTime}ms: {ErrorMessage}, Data: {OriginalData}");
_restApiResponseReceived = LoggerMessage.Define<int?, int?, long, string?>(
LogLevel.Debug,
new EventId(4001, "RestApiResponseReceived"),
"[Req {RequestId}] {ResponseStatusCode} - Response received in {ResponseTime}ms: {OriginalData}");
_restApiResponseReceived = LoggerMessage.Define<int?, int?, long, string?>(
LogLevel.Debug,
new EventId(4001, "RestApiResponseReceived"),
"[Req {RequestId}] {ResponseStatusCode} - Response received in {ResponseTime}ms: {OriginalData}");
_restApiFailedToSyncTime = LoggerMessage.Define<int, string>(
LogLevel.Debug,
new EventId(4002, "RestApiFailedToSyncTime"),
"[Req {RequestId}] Failed to sync time, aborting request: {ErrorMessage}");
_restApiFailedToSyncTime = LoggerMessage.Define<int, string>(
LogLevel.Debug,
new EventId(4002, "RestApiFailedToSyncTime"),
"[Req {RequestId}] Failed to sync time, aborting request: {ErrorMessage}");
_restApiNoApiCredentials = LoggerMessage.Define<int, string>(
LogLevel.Warning,
new EventId(4003, "RestApiNoApiCredentials"),
"[Req {RequestId}] Request {RestApiUri} failed because no ApiCredentials were provided");
_restApiNoApiCredentials = LoggerMessage.Define<int, string>(
LogLevel.Warning,
new EventId(4003, "RestApiNoApiCredentials"),
"[Req {RequestId}] Request {RestApiUri} failed because no ApiCredentials were provided");
_restApiCreatingRequest = LoggerMessage.Define<int, Uri>(
LogLevel.Information,
new EventId(4004, "RestApiCreatingRequest"),
"[Req {RequestId}] Creating request for {RestApiUri}");
_restApiCreatingRequest = LoggerMessage.Define<int, Uri>(
LogLevel.Information,
new EventId(4004, "RestApiCreatingRequest"),
"[Req {RequestId}] Creating request for {RestApiUri}");
_restApiSendingRequest = LoggerMessage.Define<int, HttpMethod, string, Uri, string>(
LogLevel.Trace,
new EventId(4005, "RestApiSendingRequest"),
"[Req {RequestId}] Sending {Method} {Signed} request to {RestApiUri}{Query}");
_restApiSendingRequest = LoggerMessage.Define<int, HttpMethod, string, Uri, string>(
LogLevel.Trace,
new EventId(4005, "RestApiSendingRequest"),
"[Req {RequestId}] Sending {Method} {Signed} request to {RestApiUri}{Query}");
_restApiRateLimitRetry = LoggerMessage.Define<int, DateTime>(
LogLevel.Warning,
new EventId(4006, "RestApiRateLimitRetry"),
"[Req {RequestId}] Received ratelimit error, retrying after {Timestamp}");
_restApiRateLimitRetry = LoggerMessage.Define<int, DateTime>(
LogLevel.Warning,
new EventId(4006, "RestApiRateLimitRetry"),
"[Req {RequestId}] Received ratelimit error, retrying after {Timestamp}");
_restApiRateLimitPauseUntil = LoggerMessage.Define<int, DateTime>(
LogLevel.Warning,
new EventId(4007, "RestApiRateLimitPauseUntil"),
"[Req {RequestId}] Ratelimit error from server, pausing requests until {Until}");
_restApiRateLimitPauseUntil = LoggerMessage.Define<int, DateTime>(
LogLevel.Warning,
new EventId(4007, "RestApiRateLimitPauseUntil"),
"[Req {RequestId}] Ratelimit error from server, pausing requests until {Until}");
_restApiSendRequest = LoggerMessage.Define<int, RequestDefinition, string?, string, string>(
LogLevel.Debug,
new EventId(4008, "RestApiSendRequest"),
"[Req {RequestId}] Sending {Definition} request with body {Body}, query parameters {Query} and headers {Headers}");
_restApiSendRequest = LoggerMessage.Define<int, RequestDefinition, string?, string, string>(
LogLevel.Debug,
new EventId(4008, "RestApiSendRequest"),
"[Req {RequestId}] Sending {Definition} request with body {Body}, query parameters {Query} and headers {Headers}");
_restApiCheckingCache = LoggerMessage.Define<string>(
LogLevel.Trace,
new EventId(4009, "RestApiCheckingCache"),
"Checking cache for key {Key}");
_restApiCheckingCache = LoggerMessage.Define<string>(
LogLevel.Trace,
new EventId(4009, "RestApiCheckingCache"),
"Checking cache for key {Key}");
_restApiCacheHit = LoggerMessage.Define<string>(
LogLevel.Trace,
new EventId(4010, "RestApiCacheHit"),
"Cache hit for key {Key}");
_restApiCacheHit = LoggerMessage.Define<string>(
LogLevel.Trace,
new EventId(4010, "RestApiCacheHit"),
"Cache hit for key {Key}");
_restApiCacheNotHit = LoggerMessage.Define<string>(
LogLevel.Trace,
new EventId(4011, "RestApiCacheNotHit"),
"Cache not hit for key {Key}");
_restApiCacheNotHit = LoggerMessage.Define<string>(
LogLevel.Trace,
new EventId(4011, "RestApiCacheNotHit"),
"Cache not hit for key {Key}");
_restApiCancellationRequested = LoggerMessage.Define<int?>(
LogLevel.Debug,
new EventId(4012, "RestApiCancellationRequested"),
"[Req {RequestId}] Request cancelled by user");
_restApiCancellationRequested = LoggerMessage.Define<int?>(
LogLevel.Debug,
new EventId(4012, "RestApiCancellationRequested"),
"[Req {RequestId}] Request cancelled by user");
}
}
public static void RestApiErrorReceived(this ILogger logger, int? requestId, HttpStatusCode? responseStatusCode, long responseTime, string? error, string? originalData, Exception? exception)
{
_restApiErrorReceived(logger, requestId, (int?)responseStatusCode, responseTime, error, originalData, exception);
}
public static void RestApiErrorReceived(this ILogger logger, int? requestId, HttpStatusCode? responseStatusCode, long responseTime, string? error, string? originalData, Exception? exception)
{
_restApiErrorReceived(logger, requestId, (int?)responseStatusCode, responseTime, error, originalData, exception);
}
public static void RestApiResponseReceived(this ILogger logger, int? requestId, HttpStatusCode? responseStatusCode, long responseTime, string? originalData)
{
_restApiResponseReceived(logger, requestId, (int?)responseStatusCode, responseTime, originalData, null);
}
public static void RestApiResponseReceived(this ILogger logger, int? requestId, HttpStatusCode? responseStatusCode, long responseTime, string? originalData)
{
_restApiResponseReceived(logger, requestId, (int?)responseStatusCode, responseTime, originalData, null);
}
public static void RestApiFailedToSyncTime(this ILogger logger, int requestId, string error)
{
_restApiFailedToSyncTime(logger, requestId, error, null);
}
public static void RestApiFailedToSyncTime(this ILogger logger, int requestId, string error)
{
_restApiFailedToSyncTime(logger, requestId, error, null);
}
public static void RestApiNoApiCredentials(this ILogger logger, int requestId, string uri)
{
_restApiNoApiCredentials(logger, requestId, uri, null);
}
public static void RestApiNoApiCredentials(this ILogger logger, int requestId, string uri)
{
_restApiNoApiCredentials(logger, requestId, uri, null);
}
public static void RestApiCreatingRequest(this ILogger logger, int requestId, Uri uri)
{
_restApiCreatingRequest(logger, requestId, uri, null);
}
public static void RestApiCreatingRequest(this ILogger logger, int requestId, Uri uri)
{
_restApiCreatingRequest(logger, requestId, uri, null);
}
public static void RestApiSendingRequest(this ILogger logger, int requestId, HttpMethod method, string signed, Uri uri, string paramString)
{
_restApiSendingRequest(logger, requestId, method, signed, uri, paramString, null);
}
public static void RestApiSendingRequest(this ILogger logger, int requestId, HttpMethod method, string signed, Uri uri, string paramString)
{
_restApiSendingRequest(logger, requestId, method, signed, uri, paramString, null);
}
public static void RestApiRateLimitRetry(this ILogger logger, int requestId, DateTime retryAfter)
{
_restApiRateLimitRetry(logger, requestId, retryAfter, null);
}
public static void RestApiRateLimitRetry(this ILogger logger, int requestId, DateTime retryAfter)
{
_restApiRateLimitRetry(logger, requestId, retryAfter, null);
}
public static void RestApiRateLimitPauseUntil(this ILogger logger, int requestId, DateTime retryAfter)
{
_restApiRateLimitPauseUntil(logger, requestId, retryAfter, null);
}
public static void RestApiRateLimitPauseUntil(this ILogger logger, int requestId, DateTime retryAfter)
{
_restApiRateLimitPauseUntil(logger, requestId, retryAfter, null);
}
public static void RestApiSendRequest(this ILogger logger, int requestId, RequestDefinition definition, string? body, string query, string headers)
{
_restApiSendRequest(logger, requestId, definition, body, query, headers, null);
}
public static void RestApiSendRequest(this ILogger logger, int requestId, RequestDefinition definition, string? body, string query, string headers)
{
_restApiSendRequest(logger, requestId, definition, body, query, headers, null);
}
public static void CheckingCache(this ILogger logger, string key)
{
_restApiCheckingCache(logger, key, null);
}
public static void CheckingCache(this ILogger logger, string key)
{
_restApiCheckingCache(logger, key, null);
}
public static void CacheHit(this ILogger logger, string key)
{
_restApiCacheHit(logger, key, null);
}
public static void CacheHit(this ILogger logger, string key)
{
_restApiCacheHit(logger, key, null);
}
public static void CacheNotHit(this ILogger logger, string key)
{
_restApiCacheNotHit(logger, key, null);
}
public static void RestApiCancellationRequested(this ILogger logger, int? requestId)
{
_restApiCancellationRequested(logger, requestId, null);
}
public static void CacheNotHit(this ILogger logger, string key)
{
_restApiCacheNotHit(logger, key, null);
}
public static void RestApiCancellationRequested(this ILogger logger, int? requestId)
{
_restApiCancellationRequested(logger, requestId, null);
}
}

View File

@ -1,200 +1,199 @@
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging;
using System;
namespace CryptoExchange.Net.Logging.Extensions
{
namespace CryptoExchange.Net.Logging.Extensions;
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
public static class SocketApiClientLoggingExtension
public static class SocketApiClientLoggingExtension
{
private static readonly Action<ILogger, int, Exception?> _failedToAddSubscriptionRetryOnDifferentConnection;
private static readonly Action<ILogger, int, Exception?> _hasBeenPausedCantSubscribeAtThisMoment;
private static readonly Action<ILogger, int, string?, Exception?> _failedToSubscribe;
private static readonly Action<ILogger, int, int, Exception?> _cancellationTokenSetClosingSubscription;
private static readonly Action<ILogger, int, int, Exception?> _subscriptionCompletedSuccessfully;
private static readonly Action<ILogger, int, Exception?> _hasBeenPausedCantSendQueryAtThisMoment;
private static readonly Action<ILogger, int, Exception?> _attemptingToAuthenticate;
private static readonly Action<ILogger, int, Exception?> _authenticationFailed;
private static readonly Action<ILogger, int, Exception?> _authenticated;
private static readonly Action<ILogger, string?, Exception?> _failedToDetermineConnectionUrl;
private static readonly Action<ILogger, string, Exception?> _connectionAddressSetTo;
private static readonly Action<ILogger, int, string, Exception?> _socketCreatedForAddress;
private static readonly Action<ILogger, int, Exception?> _unsubscribingAll;
private static readonly Action<ILogger, Exception?> _disposingSocketClient;
private static readonly Action<ILogger, int, int, Exception?> _unsubscribingSubscription;
private static readonly Action<ILogger, int, Exception?> _reconnectingAllConnections;
private static readonly Action<ILogger, DateTime, Exception?> _addingRetryAfterGuard;
static SocketApiClientLoggingExtension()
{
private static readonly Action<ILogger, int, Exception?> _failedToAddSubscriptionRetryOnDifferentConnection;
private static readonly Action<ILogger, int, Exception?> _hasBeenPausedCantSubscribeAtThisMoment;
private static readonly Action<ILogger, int, string?, Exception?> _failedToSubscribe;
private static readonly Action<ILogger, int, int, Exception?> _cancellationTokenSetClosingSubscription;
private static readonly Action<ILogger, int, int, Exception?> _subscriptionCompletedSuccessfully;
private static readonly Action<ILogger, int, Exception?> _hasBeenPausedCantSendQueryAtThisMoment;
private static readonly Action<ILogger, int, Exception?> _attemptingToAuthenticate;
private static readonly Action<ILogger, int, Exception?> _authenticationFailed;
private static readonly Action<ILogger, int, Exception?> _authenticated;
private static readonly Action<ILogger, string?, Exception?> _failedToDetermineConnectionUrl;
private static readonly Action<ILogger, string, Exception?> _connectionAddressSetTo;
private static readonly Action<ILogger, int, string, Exception?> _socketCreatedForAddress;
private static readonly Action<ILogger, int, Exception?> _unsubscribingAll;
private static readonly Action<ILogger, Exception?> _disposingSocketClient;
private static readonly Action<ILogger, int, int, Exception?> _unsubscribingSubscription;
private static readonly Action<ILogger, int, Exception?> _reconnectingAllConnections;
private static readonly Action<ILogger, DateTime, Exception?> _addingRetryAfterGuard;
_failedToAddSubscriptionRetryOnDifferentConnection = LoggerMessage.Define<int>(
LogLevel.Trace,
new EventId(3000, "FailedToAddSubscriptionRetryOnDifferentConnection"),
"[Sckt {SocketId}] failed to add subscription, retrying on different connection");
static SocketApiClientLoggingExtension()
{
_failedToAddSubscriptionRetryOnDifferentConnection = LoggerMessage.Define<int>(
LogLevel.Trace,
new EventId(3000, "FailedToAddSubscriptionRetryOnDifferentConnection"),
"[Sckt {SocketId}] failed to add subscription, retrying on different connection");
_hasBeenPausedCantSubscribeAtThisMoment = LoggerMessage.Define<int>(
LogLevel.Warning,
new EventId(3001, "HasBeenPausedCantSubscribeAtThisMoment"),
"[Sckt {SocketId}] has been paused, can't subscribe at this moment");
_hasBeenPausedCantSubscribeAtThisMoment = LoggerMessage.Define<int>(
LogLevel.Warning,
new EventId(3001, "HasBeenPausedCantSubscribeAtThisMoment"),
"[Sckt {SocketId}] has been paused, can't subscribe at this moment");
_failedToSubscribe = LoggerMessage.Define<int, string?>(
LogLevel.Warning,
new EventId(3002, "FailedToSubscribe"),
"[Sckt {SocketId}] failed to subscribe: {ErrorMessage}");
_failedToSubscribe = LoggerMessage.Define<int, string?>(
LogLevel.Warning,
new EventId(3002, "FailedToSubscribe"),
"[Sckt {SocketId}] failed to subscribe: {ErrorMessage}");
_cancellationTokenSetClosingSubscription = LoggerMessage.Define<int, int>(
LogLevel.Information,
new EventId(3003, "CancellationTokenSetClosingSubscription"),
"[Sckt {SocketId}] Cancellation token set, closing subscription {SubscriptionId}");
_cancellationTokenSetClosingSubscription = LoggerMessage.Define<int, int>(
LogLevel.Information,
new EventId(3003, "CancellationTokenSetClosingSubscription"),
"[Sckt {SocketId}] Cancellation token set, closing subscription {SubscriptionId}");
_subscriptionCompletedSuccessfully = LoggerMessage.Define<int, int>(
LogLevel.Information,
new EventId(3004, "SubscriptionCompletedSuccessfully"),
"[Sckt {SocketId}] subscription {SubscriptionId} completed successfully");
_subscriptionCompletedSuccessfully = LoggerMessage.Define<int, int>(
LogLevel.Information,
new EventId(3004, "SubscriptionCompletedSuccessfully"),
"[Sckt {SocketId}] subscription {SubscriptionId} completed successfully");
_hasBeenPausedCantSendQueryAtThisMoment = LoggerMessage.Define<int>(
LogLevel.Warning,
new EventId(3005, "HasBeenPausedCantSendQueryAtThisMoment"),
"[Sckt {SocketId}] has been paused, can't send query at this moment");
_hasBeenPausedCantSendQueryAtThisMoment = LoggerMessage.Define<int>(
LogLevel.Warning,
new EventId(3005, "HasBeenPausedCantSendQueryAtThisMoment"),
"[Sckt {SocketId}] has been paused, can't send query at this moment");
_attemptingToAuthenticate = LoggerMessage.Define<int>(
LogLevel.Debug,
new EventId(3006, "AttemptingToAuthenticate"),
"[Sckt {SocketId}] Attempting to authenticate");
_attemptingToAuthenticate = LoggerMessage.Define<int>(
LogLevel.Debug,
new EventId(3006, "AttemptingToAuthenticate"),
"[Sckt {SocketId}] Attempting to authenticate");
_authenticationFailed = LoggerMessage.Define<int>(
LogLevel.Warning,
new EventId(3007, "AuthenticationFailed"),
"[Sckt {SocketId}] authentication failed");
_authenticationFailed = LoggerMessage.Define<int>(
LogLevel.Warning,
new EventId(3007, "AuthenticationFailed"),
"[Sckt {SocketId}] authentication failed");
_authenticated = LoggerMessage.Define<int>(
LogLevel.Debug,
new EventId(3008, "Authenticated"),
"[Sckt {SocketId}] authenticated");
_authenticated = LoggerMessage.Define<int>(
LogLevel.Debug,
new EventId(3008, "Authenticated"),
"[Sckt {SocketId}] authenticated");
_failedToDetermineConnectionUrl = LoggerMessage.Define<string?>(
LogLevel.Warning,
new EventId(3009, "FailedToDetermineConnectionUrl"),
"Failed to determine connection url: {ErrorMessage}");
_failedToDetermineConnectionUrl = LoggerMessage.Define<string?>(
LogLevel.Warning,
new EventId(3009, "FailedToDetermineConnectionUrl"),
"Failed to determine connection url: {ErrorMessage}");
_connectionAddressSetTo = LoggerMessage.Define<string>(
LogLevel.Debug,
new EventId(3010, "ConnectionAddressSetTo"),
"Connection address set to {ConnectionAddress}");
_connectionAddressSetTo = LoggerMessage.Define<string>(
LogLevel.Debug,
new EventId(3010, "ConnectionAddressSetTo"),
"Connection address set to {ConnectionAddress}");
_socketCreatedForAddress = LoggerMessage.Define<int, string>(
LogLevel.Debug,
new EventId(3011, "SocketCreatedForAddress"),
"[Sckt {SocketId}] created for {Address}");
_socketCreatedForAddress = LoggerMessage.Define<int, string>(
LogLevel.Debug,
new EventId(3011, "SocketCreatedForAddress"),
"[Sckt {SocketId}] created for {Address}");
_unsubscribingAll = LoggerMessage.Define<int>(
LogLevel.Information,
new EventId(3013, "UnsubscribingAll"),
"Unsubscribing all {SubscriptionCount} subscriptions");
_unsubscribingAll = LoggerMessage.Define<int>(
LogLevel.Information,
new EventId(3013, "UnsubscribingAll"),
"Unsubscribing all {SubscriptionCount} subscriptions");
_disposingSocketClient = LoggerMessage.Define(
LogLevel.Debug,
new EventId(3015, "DisposingSocketClient"),
"Disposing socket client, closing all subscriptions");
_disposingSocketClient = LoggerMessage.Define(
LogLevel.Debug,
new EventId(3015, "DisposingSocketClient"),
"Disposing socket client, closing all subscriptions");
_unsubscribingSubscription = LoggerMessage.Define<int, int>(
LogLevel.Information,
new EventId(3016, "UnsubscribingSubscription"),
"[Sckt {SocketId}] Unsubscribing subscription {SubscriptionId}");
_unsubscribingSubscription = LoggerMessage.Define<int, int>(
LogLevel.Information,
new EventId(3016, "UnsubscribingSubscription"),
"[Sckt {SocketId}] Unsubscribing subscription {SubscriptionId}");
_reconnectingAllConnections = LoggerMessage.Define<int>(
LogLevel.Information,
new EventId(3017, "ReconnectingAll"),
"Reconnecting all {ConnectionCount} connections");
_reconnectingAllConnections = LoggerMessage.Define<int>(
LogLevel.Information,
new EventId(3017, "ReconnectingAll"),
"Reconnecting all {ConnectionCount} connections");
_addingRetryAfterGuard = LoggerMessage.Define<DateTime>(
LogLevel.Warning,
new EventId(3018, "AddRetryAfterGuard"),
"Adding RetryAfterGuard ({RetryAfter}) because the connection attempt was rate limited");
}
_addingRetryAfterGuard = LoggerMessage.Define<DateTime>(
LogLevel.Warning,
new EventId(3018, "AddRetryAfterGuard"),
"Adding RetryAfterGuard ({RetryAfter}) because the connection attempt was rate limited");
}
public static void FailedToAddSubscriptionRetryOnDifferentConnection(this ILogger logger, int socketId)
{
_failedToAddSubscriptionRetryOnDifferentConnection(logger, socketId, null);
}
public static void FailedToAddSubscriptionRetryOnDifferentConnection(this ILogger logger, int socketId)
{
_failedToAddSubscriptionRetryOnDifferentConnection(logger, socketId, null);
}
public static void HasBeenPausedCantSubscribeAtThisMoment(this ILogger logger, int socketId)
{
_hasBeenPausedCantSubscribeAtThisMoment(logger, socketId, null);
}
public static void HasBeenPausedCantSubscribeAtThisMoment(this ILogger logger, int socketId)
{
_hasBeenPausedCantSubscribeAtThisMoment(logger, socketId, null);
}
public static void FailedToSubscribe(this ILogger logger, int socketId, string? error)
{
_failedToSubscribe(logger, socketId, error, null);
}
public static void FailedToSubscribe(this ILogger logger, int socketId, string? error)
{
_failedToSubscribe(logger, socketId, error, null);
}
public static void CancellationTokenSetClosingSubscription(this ILogger logger, int socketId, int subscriptionId)
{
_cancellationTokenSetClosingSubscription(logger, socketId, subscriptionId, null);
}
public static void CancellationTokenSetClosingSubscription(this ILogger logger, int socketId, int subscriptionId)
{
_cancellationTokenSetClosingSubscription(logger, socketId, subscriptionId, null);
}
public static void SubscriptionCompletedSuccessfully(this ILogger logger, int socketId, int subscriptionId)
{
_subscriptionCompletedSuccessfully(logger, socketId, subscriptionId, null);
}
public static void SubscriptionCompletedSuccessfully(this ILogger logger, int socketId, int subscriptionId)
{
_subscriptionCompletedSuccessfully(logger, socketId, subscriptionId, null);
}
public static void HasBeenPausedCantSendQueryAtThisMoment(this ILogger logger, int socketId)
{
_hasBeenPausedCantSendQueryAtThisMoment(logger, socketId, null);
}
public static void HasBeenPausedCantSendQueryAtThisMoment(this ILogger logger, int socketId)
{
_hasBeenPausedCantSendQueryAtThisMoment(logger, socketId, null);
}
public static void AttemptingToAuthenticate(this ILogger logger, int socketId)
{
_attemptingToAuthenticate(logger, socketId, null);
}
public static void AttemptingToAuthenticate(this ILogger logger, int socketId)
{
_attemptingToAuthenticate(logger, socketId, null);
}
public static void AuthenticationFailed(this ILogger logger, int socketId)
{
_authenticationFailed(logger, socketId, null);
}
public static void AuthenticationFailed(this ILogger logger, int socketId)
{
_authenticationFailed(logger, socketId, null);
}
public static void Authenticated(this ILogger logger, int socketId)
{
_authenticated(logger, socketId, null);
}
public static void Authenticated(this ILogger logger, int socketId)
{
_authenticated(logger, socketId, null);
}
public static void FailedToDetermineConnectionUrl(this ILogger logger, string? error)
{
_failedToDetermineConnectionUrl(logger, error, null);
}
public static void FailedToDetermineConnectionUrl(this ILogger logger, string? error)
{
_failedToDetermineConnectionUrl(logger, error, null);
}
public static void ConnectionAddressSetTo(this ILogger logger, string connectionAddress)
{
_connectionAddressSetTo(logger, connectionAddress, null);
}
public static void ConnectionAddressSetTo(this ILogger logger, string connectionAddress)
{
_connectionAddressSetTo(logger, connectionAddress, null);
}
public static void SocketCreatedForAddress(this ILogger logger, int socketId, string address)
{
_socketCreatedForAddress(logger, socketId, address, null);
}
public static void SocketCreatedForAddress(this ILogger logger, int socketId, string address)
{
_socketCreatedForAddress(logger, socketId, address, null);
}
public static void UnsubscribingAll(this ILogger logger, int subscriptionCount)
{
_unsubscribingAll(logger, subscriptionCount, null);
}
public static void UnsubscribingAll(this ILogger logger, int subscriptionCount)
{
_unsubscribingAll(logger, subscriptionCount, null);
}
public static void DisposingSocketClient(this ILogger logger)
{
_disposingSocketClient(logger, null);
}
public static void DisposingSocketClient(this ILogger logger)
{
_disposingSocketClient(logger, null);
}
public static void UnsubscribingSubscription(this ILogger logger, int socketId, int subscriptionId)
{
_unsubscribingSubscription(logger, socketId, subscriptionId, null);
}
public static void UnsubscribingSubscription(this ILogger logger, int socketId, int subscriptionId)
{
_unsubscribingSubscription(logger, socketId, subscriptionId, null);
}
public static void ReconnectingAllConnections(this ILogger logger, int connectionCount)
{
_reconnectingAllConnections(logger, connectionCount, null);
}
public static void ReconnectingAllConnections(this ILogger logger, int connectionCount)
{
_reconnectingAllConnections(logger, connectionCount, null);
}
public static void AddingRetryAfterGuard(this ILogger logger, DateTime retryAfter)
{
_addingRetryAfterGuard(logger, retryAfter, null);
}
public static void AddingRetryAfterGuard(this ILogger logger, DateTime retryAfter)
{
_addingRetryAfterGuard(logger, retryAfter, null);
}
}

View File

@ -1,349 +1,348 @@
using System;
using System;
using System.Net.WebSockets;
using Microsoft.Extensions.Logging;
namespace CryptoExchange.Net.Logging.Extensions
{
namespace CryptoExchange.Net.Logging.Extensions;
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
public static class SocketConnectionLoggingExtension
public static class SocketConnectionLoggingExtension
{
private static readonly Action<ILogger, int, bool, Exception?> _activityPaused;
private static readonly Action<ILogger, int, Sockets.SocketConnection.SocketStatus, Sockets.SocketConnection.SocketStatus, Exception?> _socketStatusChanged;
private static readonly Action<ILogger, int, string?, Exception?> _failedReconnectProcessing;
private static readonly Action<ILogger, int, Exception?> _unknownExceptionWhileProcessingReconnection;
private static readonly Action<ILogger, int, WebSocketError, string?, Exception?> _webSocketErrorCodeAndDetails;
private static readonly Action<ILogger, int, string?, Exception?> _webSocketError;
private static readonly Action<ILogger, int, int, Exception?> _messageSentNotPending;
private static readonly Action<ILogger, int, string, Exception?> _receivedData;
private static readonly Action<ILogger, int, string, Exception?> _failedToParse;
private static readonly Action<ILogger, int, string, Exception?> _failedToEvaluateMessage;
private static readonly Action<ILogger, int, Exception?> _errorProcessingMessage;
private static readonly Action<ILogger, int, string, string, Exception?> _processorMatched;
private static readonly Action<ILogger, int, int, Exception?> _receivedMessageNotRecognized;
private static readonly Action<ILogger, int, string?, Exception?> _failedToDeserializeMessage;
private static readonly Action<ILogger, int, string, Exception?> _userMessageProcessingFailed;
private static readonly Action<ILogger, int, long, long, Exception?> _messageProcessed;
private static readonly Action<ILogger, int, int, Exception?> _closingSubscription;
private static readonly Action<ILogger, int, Exception?> _notUnsubscribingSubscriptionBecauseDuplicateRunning;
private static readonly Action<ILogger, int, Exception?> _alreadyClosing;
private static readonly Action<ILogger, int, Exception?> _closingNoMoreSubscriptions;
private static readonly Action<ILogger, int, int, int, Exception?> _addingNewSubscription;
private static readonly Action<ILogger, int, Exception?> _nothingToResubscribeCloseConnection;
private static readonly Action<ILogger, int, Exception?> _failedAuthenticationDisconnectAndReconnect;
private static readonly Action<ILogger, int, Exception?> _authenticationSucceeded;
private static readonly Action<ILogger, int, string?, Exception?> _failedRequestRevitalization;
private static readonly Action<ILogger, int, Exception?> _allSubscriptionResubscribed;
private static readonly Action<ILogger, int, int, Exception?> _subscriptionUnsubscribed;
private static readonly Action<ILogger, int, string, Exception?> _sendingPeriodic;
private static readonly Action<ILogger, int, string, string, Exception?> _periodicSendFailed;
private static readonly Action<ILogger, int, int, string, Exception?> _sendingData;
private static readonly Action<ILogger, int, string, string, Exception?> _receivedMessageNotMatchedToAnyListener;
private static readonly Action<ILogger, int, int, int, Exception?> _sendingByteData;
static SocketConnectionLoggingExtension()
{
private static readonly Action<ILogger, int, bool, Exception?> _activityPaused;
private static readonly Action<ILogger, int, Sockets.SocketConnection.SocketStatus, Sockets.SocketConnection.SocketStatus, Exception?> _socketStatusChanged;
private static readonly Action<ILogger, int, string?, Exception?> _failedReconnectProcessing;
private static readonly Action<ILogger, int, Exception?> _unknownExceptionWhileProcessingReconnection;
private static readonly Action<ILogger, int, WebSocketError, string?, Exception?> _webSocketErrorCodeAndDetails;
private static readonly Action<ILogger, int, string?, Exception?> _webSocketError;
private static readonly Action<ILogger, int, int, Exception?> _messageSentNotPending;
private static readonly Action<ILogger, int, string, Exception?> _receivedData;
private static readonly Action<ILogger, int, string, Exception?> _failedToParse;
private static readonly Action<ILogger, int, string, Exception?> _failedToEvaluateMessage;
private static readonly Action<ILogger, int, Exception?> _errorProcessingMessage;
private static readonly Action<ILogger, int, string, string, Exception?> _processorMatched;
private static readonly Action<ILogger, int, int, Exception?> _receivedMessageNotRecognized;
private static readonly Action<ILogger, int, string?, Exception?> _failedToDeserializeMessage;
private static readonly Action<ILogger, int, string, Exception?> _userMessageProcessingFailed;
private static readonly Action<ILogger, int, long, long, Exception?> _messageProcessed;
private static readonly Action<ILogger, int, int, Exception?> _closingSubscription;
private static readonly Action<ILogger, int, Exception?> _notUnsubscribingSubscriptionBecauseDuplicateRunning;
private static readonly Action<ILogger, int, Exception?> _alreadyClosing;
private static readonly Action<ILogger, int, Exception?> _closingNoMoreSubscriptions;
private static readonly Action<ILogger, int, int, int, Exception?> _addingNewSubscription;
private static readonly Action<ILogger, int, Exception?> _nothingToResubscribeCloseConnection;
private static readonly Action<ILogger, int, Exception?> _failedAuthenticationDisconnectAndReconnect;
private static readonly Action<ILogger, int, Exception?> _authenticationSucceeded;
private static readonly Action<ILogger, int, string?, Exception?> _failedRequestRevitalization;
private static readonly Action<ILogger, int, Exception?> _allSubscriptionResubscribed;
private static readonly Action<ILogger, int, int, Exception?> _subscriptionUnsubscribed;
private static readonly Action<ILogger, int, string, Exception?> _sendingPeriodic;
private static readonly Action<ILogger, int, string, string, Exception?> _periodicSendFailed;
private static readonly Action<ILogger, int, int, string, Exception?> _sendingData;
private static readonly Action<ILogger, int, string, string, Exception?> _receivedMessageNotMatchedToAnyListener;
private static readonly Action<ILogger, int, int, int, Exception?> _sendingByteData;
_activityPaused = LoggerMessage.Define<int, bool>(
LogLevel.Information,
new EventId(2000, "ActivityPaused"),
"[Sckt {SocketId}] paused activity: {Paused}");
static SocketConnectionLoggingExtension()
{
_activityPaused = LoggerMessage.Define<int, bool>(
LogLevel.Information,
new EventId(2000, "ActivityPaused"),
"[Sckt {SocketId}] paused activity: {Paused}");
_socketStatusChanged = LoggerMessage.Define<int, Sockets.SocketConnection.SocketStatus, Sockets.SocketConnection.SocketStatus>(
LogLevel.Debug,
new EventId(2001, "SocketStatusChanged"),
"[Sckt {SocketId}] status changed from {OldStatus} to {NewStatus}");
_socketStatusChanged = LoggerMessage.Define<int, Sockets.SocketConnection.SocketStatus, Sockets.SocketConnection.SocketStatus>(
LogLevel.Debug,
new EventId(2001, "SocketStatusChanged"),
"[Sckt {SocketId}] status changed from {OldStatus} to {NewStatus}");
_failedReconnectProcessing = LoggerMessage.Define<int, string?>(
LogLevel.Warning,
new EventId(2002, "FailedReconnectProcessing"),
"[Sckt {SocketId}] failed reconnect processing: {ErrorMessage}, reconnecting again");
_failedReconnectProcessing = LoggerMessage.Define<int, string?>(
LogLevel.Warning,
new EventId(2002, "FailedReconnectProcessing"),
"[Sckt {SocketId}] failed reconnect processing: {ErrorMessage}, reconnecting again");
_unknownExceptionWhileProcessingReconnection = LoggerMessage.Define<int>(
LogLevel.Warning,
new EventId(2003, "UnknownExceptionWhileProcessingReconnection"),
"[Sckt {SocketId}] Unknown exception while processing reconnection, reconnecting again");
_unknownExceptionWhileProcessingReconnection = LoggerMessage.Define<int>(
LogLevel.Warning,
new EventId(2003, "UnknownExceptionWhileProcessingReconnection"),
"[Sckt {SocketId}] Unknown exception while processing reconnection, reconnecting again");
_webSocketErrorCodeAndDetails = LoggerMessage.Define<int, WebSocketError, string?>(
LogLevel.Warning,
new EventId(2004, "WebSocketErrorCode"),
"[Sckt {SocketId}] error: Websocket error code {WebSocketErrorCode}, details: {Details}");
_webSocketErrorCodeAndDetails = LoggerMessage.Define<int, WebSocketError, string?>(
LogLevel.Warning,
new EventId(2004, "WebSocketErrorCode"),
"[Sckt {SocketId}] error: Websocket error code {WebSocketErrorCode}, details: {Details}");
_webSocketError = LoggerMessage.Define<int, string?>(
LogLevel.Warning,
new EventId(2005, "WebSocketError"),
"[Sckt {SocketId}] error: {ErrorMessage}");
_webSocketError = LoggerMessage.Define<int, string?>(
LogLevel.Warning,
new EventId(2005, "WebSocketError"),
"[Sckt {SocketId}] error: {ErrorMessage}");
_messageSentNotPending = LoggerMessage.Define<int, int>(
LogLevel.Debug,
new EventId(2006, "MessageSentNotPending"),
"[Sckt {SocketId}] [Req {RequestId}] message sent, but not pending");
_messageSentNotPending = LoggerMessage.Define<int, int>(
LogLevel.Debug,
new EventId(2006, "MessageSentNotPending"),
"[Sckt {SocketId}] [Req {RequestId}] message sent, but not pending");
_receivedData = LoggerMessage.Define<int, string>(
LogLevel.Trace,
new EventId(2007, "ReceivedData"),
"[Sckt {SocketId}] received {OriginalData}");
_receivedData = LoggerMessage.Define<int, string>(
LogLevel.Trace,
new EventId(2007, "ReceivedData"),
"[Sckt {SocketId}] received {OriginalData}");
_failedToEvaluateMessage = LoggerMessage.Define<int, string>(
LogLevel.Warning,
new EventId(2008, "FailedToEvaluateMessage"),
"[Sckt {SocketId}] failed to evaluate message. {OriginalData}");
_failedToEvaluateMessage = LoggerMessage.Define<int, string>(
LogLevel.Warning,
new EventId(2008, "FailedToEvaluateMessage"),
"[Sckt {SocketId}] failed to evaluate message. {OriginalData}");
_errorProcessingMessage = LoggerMessage.Define<int>(
LogLevel.Error,
new EventId(2009, "ErrorProcessingMessage"),
"[Sckt {SocketId}] error processing message");
_errorProcessingMessage = LoggerMessage.Define<int>(
LogLevel.Error,
new EventId(2009, "ErrorProcessingMessage"),
"[Sckt {SocketId}] error processing message");
_receivedMessageNotRecognized = LoggerMessage.Define<int, int>(
LogLevel.Warning,
new EventId(2011, "ReceivedMessageNotRecognized"),
"[Sckt {SocketId}] received message not recognized by handler {ProcessorId}");
_receivedMessageNotRecognized = LoggerMessage.Define<int, int>(
LogLevel.Warning,
new EventId(2011, "ReceivedMessageNotRecognized"),
"[Sckt {SocketId}] received message not recognized by handler {ProcessorId}");
_failedToDeserializeMessage = LoggerMessage.Define<int, string?>(
LogLevel.Warning,
new EventId(2012, "FailedToDeserializeMessage"),
"[Sckt {SocketId}] deserialization failed: {ErrorMessage}");
_failedToDeserializeMessage = LoggerMessage.Define<int, string?>(
LogLevel.Warning,
new EventId(2012, "FailedToDeserializeMessage"),
"[Sckt {SocketId}] deserialization failed: {ErrorMessage}");
_userMessageProcessingFailed = LoggerMessage.Define<int, string>(
LogLevel.Warning,
new EventId(2013, "UserMessageProcessingFailed"),
"[Sckt {SocketId}] user message processing failed: {ErrorMessage}");
_userMessageProcessingFailed = LoggerMessage.Define<int, string>(
LogLevel.Warning,
new EventId(2013, "UserMessageProcessingFailed"),
"[Sckt {SocketId}] user message processing failed: {ErrorMessage}");
_messageProcessed = LoggerMessage.Define<int, long, long>(
LogLevel.Trace,
new EventId(2014, "MessageProcessed"),
"[Sckt {SocketId}] message processed in {ProcessingTime}ms, {ParsingTime}ms parsing");
_messageProcessed = LoggerMessage.Define<int, long, long>(
LogLevel.Trace,
new EventId(2014, "MessageProcessed"),
"[Sckt {SocketId}] message processed in {ProcessingTime}ms, {ParsingTime}ms parsing");
_closingSubscription = LoggerMessage.Define<int, int>(
LogLevel.Debug,
new EventId(2015, "ClosingSubscription"),
"[Sckt {SocketId}] closing subscription {SubscriptionId}");
_closingSubscription = LoggerMessage.Define<int, int>(
LogLevel.Debug,
new EventId(2015, "ClosingSubscription"),
"[Sckt {SocketId}] closing subscription {SubscriptionId}");
_notUnsubscribingSubscriptionBecauseDuplicateRunning = LoggerMessage.Define<int>(
LogLevel.Debug,
new EventId(2016, "NotUnsubscribingSubscription"),
"[Sckt {SocketId}] not unsubscribing subscription as there is still a duplicate subscription running");
_notUnsubscribingSubscriptionBecauseDuplicateRunning = LoggerMessage.Define<int>(
LogLevel.Debug,
new EventId(2016, "NotUnsubscribingSubscription"),
"[Sckt {SocketId}] not unsubscribing subscription as there is still a duplicate subscription running");
_alreadyClosing = LoggerMessage.Define<int>(
LogLevel.Debug,
new EventId(2017, "AlreadyClosing"),
"[Sckt {SocketId}] already closing");
_alreadyClosing = LoggerMessage.Define<int>(
LogLevel.Debug,
new EventId(2017, "AlreadyClosing"),
"[Sckt {SocketId}] already closing");
_closingNoMoreSubscriptions = LoggerMessage.Define<int>(
LogLevel.Debug,
new EventId(2018, "ClosingNoMoreSubscriptions"),
"[Sckt {SocketId}] closing as there are no more subscriptions");
_closingNoMoreSubscriptions = LoggerMessage.Define<int>(
LogLevel.Debug,
new EventId(2018, "ClosingNoMoreSubscriptions"),
"[Sckt {SocketId}] closing as there are no more subscriptions");
_addingNewSubscription = LoggerMessage.Define<int, int, int>(
LogLevel.Debug,
new EventId(2019, "AddingNewSubscription"),
"[Sckt {SocketId}] adding new subscription with id {SubscriptionId}, total subscriptions on connection: {UserSubscriptionCount}");
_addingNewSubscription = LoggerMessage.Define<int, int, int>(
LogLevel.Debug,
new EventId(2019, "AddingNewSubscription"),
"[Sckt {SocketId}] adding new subscription with id {SubscriptionId}, total subscriptions on connection: {UserSubscriptionCount}");
_nothingToResubscribeCloseConnection = LoggerMessage.Define<int>(
LogLevel.Debug,
new EventId(2020, "NothingToResubscribe"),
"[Sckt {SocketId}] nothing to resubscribe, closing connection");
_nothingToResubscribeCloseConnection = LoggerMessage.Define<int>(
LogLevel.Debug,
new EventId(2020, "NothingToResubscribe"),
"[Sckt {SocketId}] nothing to resubscribe, closing connection");
_failedAuthenticationDisconnectAndReconnect = LoggerMessage.Define<int>(
LogLevel.Warning,
new EventId(2021, "FailedAuthentication"),
"[Sckt {SocketId}] authentication failed on reconnected socket. Disconnecting and reconnecting");
_failedAuthenticationDisconnectAndReconnect = LoggerMessage.Define<int>(
LogLevel.Warning,
new EventId(2021, "FailedAuthentication"),
"[Sckt {SocketId}] authentication failed on reconnected socket. Disconnecting and reconnecting");
_authenticationSucceeded = LoggerMessage.Define<int>(
LogLevel.Debug,
new EventId(2022, "AuthenticationSucceeded"),
"[Sckt {SocketId}] authentication succeeded on reconnected socket");
_authenticationSucceeded = LoggerMessage.Define<int>(
LogLevel.Debug,
new EventId(2022, "AuthenticationSucceeded"),
"[Sckt {SocketId}] authentication succeeded on reconnected socket");
_failedRequestRevitalization = LoggerMessage.Define<int, string?>(
LogLevel.Warning,
new EventId(2023, "FailedRequestRevitalization"),
"[Sckt {SocketId}] failed request revitalization: {ErrorMessage}");
_failedRequestRevitalization = LoggerMessage.Define<int, string?>(
LogLevel.Warning,
new EventId(2023, "FailedRequestRevitalization"),
"[Sckt {SocketId}] failed request revitalization: {ErrorMessage}");
_allSubscriptionResubscribed = LoggerMessage.Define<int>(
LogLevel.Debug,
new EventId(2024, "AllSubscriptionResubscribed"),
"[Sckt {SocketId}] all subscription successfully resubscribed on reconnected socket");
_allSubscriptionResubscribed = LoggerMessage.Define<int>(
LogLevel.Debug,
new EventId(2024, "AllSubscriptionResubscribed"),
"[Sckt {SocketId}] all subscription successfully resubscribed on reconnected socket");
_subscriptionUnsubscribed = LoggerMessage.Define<int, int>(
LogLevel.Information,
new EventId(2025, "SubscriptionUnsubscribed"),
"[Sckt {SocketId}] subscription {SubscriptionId} unsubscribed");
_subscriptionUnsubscribed = LoggerMessage.Define<int, int>(
LogLevel.Information,
new EventId(2025, "SubscriptionUnsubscribed"),
"[Sckt {SocketId}] subscription {SubscriptionId} unsubscribed");
_sendingPeriodic = LoggerMessage.Define<int, string>(
LogLevel.Trace,
new EventId(2026, "SendingPeriodic"),
"[Sckt {SocketId}] sending periodic {Identifier}");
_sendingPeriodic = LoggerMessage.Define<int, string>(
LogLevel.Trace,
new EventId(2026, "SendingPeriodic"),
"[Sckt {SocketId}] sending periodic {Identifier}");
_periodicSendFailed = LoggerMessage.Define<int, string, string>(
LogLevel.Warning,
new EventId(2027, "PeriodicSendFailed"),
"[Sckt {SocketId}] periodic send {Identifier} failed: {ErrorMessage}");
_periodicSendFailed = LoggerMessage.Define<int, string, string>(
LogLevel.Warning,
new EventId(2027, "PeriodicSendFailed"),
"[Sckt {SocketId}] periodic send {Identifier} failed: {ErrorMessage}");
_sendingData = LoggerMessage.Define<int, int, string>(
LogLevel.Trace,
new EventId(2028, "SendingData"),
"[Sckt {SocketId}] [Req {RequestId}] sending message: {Data}");
_sendingData = LoggerMessage.Define<int, int, string>(
LogLevel.Trace,
new EventId(2028, "SendingData"),
"[Sckt {SocketId}] [Req {RequestId}] sending message: {Data}");
_receivedMessageNotMatchedToAnyListener = LoggerMessage.Define<int, string, string>(
LogLevel.Warning,
new EventId(2029, "ReceivedMessageNotMatchedToAnyListener"),
"[Sckt {SocketId}] received message not matched to any listener. ListenId: {ListenId}, current listeners: [{ListenIds}]");
_receivedMessageNotMatchedToAnyListener = LoggerMessage.Define<int, string, string>(
LogLevel.Warning,
new EventId(2029, "ReceivedMessageNotMatchedToAnyListener"),
"[Sckt {SocketId}] received message not matched to any listener. ListenId: {ListenId}, current listeners: [{ListenIds}]");
_failedToParse = LoggerMessage.Define<int, string>(
LogLevel.Warning,
new EventId(2030, "FailedToParse"),
"[Sckt {SocketId}] failed to parse data: {Error}");
_failedToParse = LoggerMessage.Define<int, string>(
LogLevel.Warning,
new EventId(2030, "FailedToParse"),
"[Sckt {SocketId}] failed to parse data: {Error}");
_sendingByteData = LoggerMessage.Define<int, int, int>(
LogLevel.Trace,
new EventId(2031, "SendingByteData"),
"[Sckt {SocketId}] [Req {RequestId}] sending byte message of length: {Length}");
_sendingByteData = LoggerMessage.Define<int, int, int>(
LogLevel.Trace,
new EventId(2031, "SendingByteData"),
"[Sckt {SocketId}] [Req {RequestId}] sending byte message of length: {Length}");
_processorMatched = LoggerMessage.Define<int, string, string>(
LogLevel.Trace,
new EventId(2032, "ProcessorMatched"),
"[Sckt {SocketId}] listener '{ListenId}' matched to message with listener identifier {ListenerId}");
_processorMatched = LoggerMessage.Define<int, string, string>(
LogLevel.Trace,
new EventId(2032, "ProcessorMatched"),
"[Sckt {SocketId}] listener '{ListenId}' matched to message with listener identifier {ListenerId}");
}
}
public static void ActivityPaused(this ILogger logger, int socketId, bool paused)
{
_activityPaused(logger, socketId, paused, null);
}
public static void ActivityPaused(this ILogger logger, int socketId, bool paused)
{
_activityPaused(logger, socketId, paused, null);
}
public static void SocketStatusChanged(this ILogger logger, int socketId, Sockets.SocketConnection.SocketStatus oldStatus, Sockets.SocketConnection.SocketStatus newStatus)
{
_socketStatusChanged(logger, socketId, oldStatus, newStatus, null);
}
public static void SocketStatusChanged(this ILogger logger, int socketId, Sockets.SocketConnection.SocketStatus oldStatus, Sockets.SocketConnection.SocketStatus newStatus)
{
_socketStatusChanged(logger, socketId, oldStatus, newStatus, null);
}
public static void FailedReconnectProcessing(this ILogger logger, int socketId, string? error)
{
_failedReconnectProcessing(logger, socketId, error, null);
}
public static void FailedReconnectProcessing(this ILogger logger, int socketId, string? error)
{
_failedReconnectProcessing(logger, socketId, error, null);
}
public static void UnknownExceptionWhileProcessingReconnection(this ILogger logger, int socketId, Exception e)
{
_unknownExceptionWhileProcessingReconnection(logger, socketId, e);
}
public static void UnknownExceptionWhileProcessingReconnection(this ILogger logger, int socketId, Exception e)
{
_unknownExceptionWhileProcessingReconnection(logger, socketId, e);
}
public static void WebSocketErrorCodeAndDetails(this ILogger logger, int socketId, WebSocketError error, string? details, Exception e)
{
_webSocketErrorCodeAndDetails(logger, socketId, error, details, e);
}
public static void WebSocketErrorCodeAndDetails(this ILogger logger, int socketId, WebSocketError error, string? details, Exception e)
{
_webSocketErrorCodeAndDetails(logger, socketId, error, details, e);
}
public static void WebSocketError(this ILogger logger, int socketId, string? errorMessage, Exception e)
{
_webSocketError(logger, socketId, errorMessage, e);
}
public static void WebSocketError(this ILogger logger, int socketId, string? errorMessage, Exception e)
{
_webSocketError(logger, socketId, errorMessage, e);
}
public static void MessageSentNotPending(this ILogger logger, int socketId, int requestId)
{
_messageSentNotPending(logger, socketId, requestId, null);
}
public static void MessageSentNotPending(this ILogger logger, int socketId, int requestId)
{
_messageSentNotPending(logger, socketId, requestId, null);
}
public static void ReceivedData(this ILogger logger, int socketId, string originalData)
{
_receivedData(logger, socketId, originalData, null);
}
public static void ReceivedData(this ILogger logger, int socketId, string originalData)
{
_receivedData(logger, socketId, originalData, null);
}
public static void FailedToParse(this ILogger logger, int socketId, string error)
{
_failedToParse(logger, socketId, error, null);
}
public static void FailedToParse(this ILogger logger, int socketId, string error)
{
_failedToParse(logger, socketId, error, null);
}
public static void FailedToEvaluateMessage(this ILogger logger, int socketId, string originalData)
{
_failedToEvaluateMessage(logger, socketId, originalData, null);
}
public static void ErrorProcessingMessage(this ILogger logger, int socketId, Exception e)
{
_errorProcessingMessage(logger, socketId, e);
}
public static void ProcessorMatched(this ILogger logger, int socketId, string listener, string listenerId)
{
_processorMatched(logger, socketId, listener, listenerId, null);
}
public static void ReceivedMessageNotRecognized(this ILogger logger, int socketId, int id)
{
_receivedMessageNotRecognized(logger, socketId, id, null);
}
public static void FailedToDeserializeMessage(this ILogger logger, int socketId, string? errorMessage, Exception? ex)
{
_failedToDeserializeMessage(logger, socketId, errorMessage, ex);
}
public static void UserMessageProcessingFailed(this ILogger logger, int socketId, string errorMessage, Exception e)
{
_userMessageProcessingFailed(logger, socketId, errorMessage, e);
}
public static void MessageProcessed(this ILogger logger, int socketId, long processingTime, long parsingTime)
{
_messageProcessed(logger, socketId, processingTime, parsingTime, null);
}
public static void ClosingSubscription(this ILogger logger, int socketId, int subscriptionId)
{
_closingSubscription(logger, socketId, subscriptionId, null);
}
public static void NotUnsubscribingSubscriptionBecauseDuplicateRunning(this ILogger logger, int socketId)
{
_notUnsubscribingSubscriptionBecauseDuplicateRunning(logger, socketId, null);
}
public static void AlreadyClosing(this ILogger logger, int socketId)
{
_alreadyClosing(logger, socketId, null);
}
public static void ClosingNoMoreSubscriptions(this ILogger logger, int socketId)
{
_closingNoMoreSubscriptions(logger, socketId, null);
}
public static void AddingNewSubscription(this ILogger logger, int socketId, int subscriptionId, int userSubscriptionCount)
{
_addingNewSubscription(logger, socketId, subscriptionId, userSubscriptionCount, null);
}
public static void FailedToEvaluateMessage(this ILogger logger, int socketId, string originalData)
{
_failedToEvaluateMessage(logger, socketId, originalData, null);
}
public static void ErrorProcessingMessage(this ILogger logger, int socketId, Exception e)
{
_errorProcessingMessage(logger, socketId, e);
}
public static void ProcessorMatched(this ILogger logger, int socketId, string listener, string listenerId)
{
_processorMatched(logger, socketId, listener, listenerId, null);
}
public static void ReceivedMessageNotRecognized(this ILogger logger, int socketId, int id)
{
_receivedMessageNotRecognized(logger, socketId, id, null);
}
public static void FailedToDeserializeMessage(this ILogger logger, int socketId, string? errorMessage, Exception? ex)
{
_failedToDeserializeMessage(logger, socketId, errorMessage, ex);
}
public static void UserMessageProcessingFailed(this ILogger logger, int socketId, string errorMessage, Exception e)
{
_userMessageProcessingFailed(logger, socketId, errorMessage, e);
}
public static void MessageProcessed(this ILogger logger, int socketId, long processingTime, long parsingTime)
{
_messageProcessed(logger, socketId, processingTime, parsingTime, null);
}
public static void ClosingSubscription(this ILogger logger, int socketId, int subscriptionId)
{
_closingSubscription(logger, socketId, subscriptionId, null);
}
public static void NotUnsubscribingSubscriptionBecauseDuplicateRunning(this ILogger logger, int socketId)
{
_notUnsubscribingSubscriptionBecauseDuplicateRunning(logger, socketId, null);
}
public static void AlreadyClosing(this ILogger logger, int socketId)
{
_alreadyClosing(logger, socketId, null);
}
public static void ClosingNoMoreSubscriptions(this ILogger logger, int socketId)
{
_closingNoMoreSubscriptions(logger, socketId, null);
}
public static void AddingNewSubscription(this ILogger logger, int socketId, int subscriptionId, int userSubscriptionCount)
{
_addingNewSubscription(logger, socketId, subscriptionId, userSubscriptionCount, null);
}
public static void NothingToResubscribeCloseConnection(this ILogger logger, int socketId)
{
_nothingToResubscribeCloseConnection(logger, socketId, null);
}
public static void FailedAuthenticationDisconnectAndRecoonect(this ILogger logger, int socketId)
{
_failedAuthenticationDisconnectAndReconnect(logger, socketId, null);
}
public static void AuthenticationSucceeded(this ILogger logger, int socketId)
{
_authenticationSucceeded(logger, socketId, null);
}
public static void FailedRequestRevitalization(this ILogger logger, int socketId, string? errorMessage)
{
_failedRequestRevitalization(logger, socketId, errorMessage, null);
}
public static void AllSubscriptionResubscribed(this ILogger logger, int socketId)
{
_allSubscriptionResubscribed(logger, socketId, null);
}
public static void SubscriptionUnsubscribed(this ILogger logger, int socketId, int subscriptionId)
{
_subscriptionUnsubscribed(logger, socketId, subscriptionId, null);
}
public static void SendingPeriodic(this ILogger logger, int socketId, string identifier)
{
_sendingPeriodic(logger, socketId, identifier, null);
}
public static void PeriodicSendFailed(this ILogger logger, int socketId, string identifier, string errorMessage, Exception e)
{
_periodicSendFailed(logger, socketId, identifier, errorMessage, e);
}
public static void NothingToResubscribeCloseConnection(this ILogger logger, int socketId)
{
_nothingToResubscribeCloseConnection(logger, socketId, null);
}
public static void FailedAuthenticationDisconnectAndRecoonect(this ILogger logger, int socketId)
{
_failedAuthenticationDisconnectAndReconnect(logger, socketId, null);
}
public static void AuthenticationSucceeded(this ILogger logger, int socketId)
{
_authenticationSucceeded(logger, socketId, null);
}
public static void FailedRequestRevitalization(this ILogger logger, int socketId, string? errorMessage)
{
_failedRequestRevitalization(logger, socketId, errorMessage, null);
}
public static void AllSubscriptionResubscribed(this ILogger logger, int socketId)
{
_allSubscriptionResubscribed(logger, socketId, null);
}
public static void SubscriptionUnsubscribed(this ILogger logger, int socketId, int subscriptionId)
{
_subscriptionUnsubscribed(logger, socketId, subscriptionId, null);
}
public static void SendingPeriodic(this ILogger logger, int socketId, string identifier)
{
_sendingPeriodic(logger, socketId, identifier, null);
}
public static void PeriodicSendFailed(this ILogger logger, int socketId, string identifier, string errorMessage, Exception e)
{
_periodicSendFailed(logger, socketId, identifier, errorMessage, e);
}
public static void SendingData(this ILogger logger, int socketId, int requestId, string data)
{
_sendingData(logger, socketId, requestId, data, null);
}
public static void SendingData(this ILogger logger, int socketId, int requestId, string data)
{
_sendingData(logger, socketId, requestId, data, null);
}
public static void ReceivedMessageNotMatchedToAnyListener(this ILogger logger, int socketId, string listenId, string listenIds)
{
_receivedMessageNotMatchedToAnyListener(logger, socketId, listenId, listenIds, null);
}
public static void ReceivedMessageNotMatchedToAnyListener(this ILogger logger, int socketId, string listenId, string listenIds)
{
_receivedMessageNotMatchedToAnyListener(logger, socketId, listenId, listenIds, null);
}
public static void SendingByteData(this ILogger logger, int socketId, int requestId, int length)
{
_sendingByteData(logger, socketId, requestId, length, null);
}
public static void SendingByteData(this ILogger logger, int socketId, int requestId, int length)
{
_sendingByteData(logger, socketId, requestId, length, null);
}
}

View File

@ -1,237 +1,236 @@
using System;
using System;
using CryptoExchange.Net.Objects;
using Microsoft.Extensions.Logging;
namespace CryptoExchange.Net.Logging.Extensions
{
namespace CryptoExchange.Net.Logging.Extensions;
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
public static class SymbolOrderBookLoggingExtensions
public static class SymbolOrderBookLoggingExtensions
{
private static readonly Action<ILogger, string, string, OrderBookStatus, OrderBookStatus, Exception?> _orderBookStatusChanged;
private static readonly Action<ILogger, string, string, Exception?> _orderBookStarting;
private static readonly Action<ILogger, string, string, Exception?> _orderBookStoppedStarting;
private static readonly Action<ILogger, string, string, Exception?> _orderBookStopping;
private static readonly Action<ILogger, string, string, Exception?> _orderBookStopped;
private static readonly Action<ILogger, string, string, Exception?> _orderBookConnectionLost;
private static readonly Action<ILogger, string, string, Exception?> _orderBookDisconnected;
private static readonly Action<ILogger, string, string, int, Exception?> _orderBookProcessingBufferedUpdates;
private static readonly Action<ILogger, string, string, long, long, Exception?> _orderBookUpdateSkipped;
private static readonly Action<ILogger, string, string, Exception?> _orderBookOutOfSyncChecksum;
private static readonly Action<ILogger, string, string, Exception?> _orderBookResyncFailed;
private static readonly Action<ILogger, string, string, Exception?> _orderBookResyncing;
private static readonly Action<ILogger, string, string, Exception?> _orderBookResynced;
private static readonly Action<ILogger, string, string, Exception?> _orderBookMessageSkippedBecauseOfResubscribing;
private static readonly Action<ILogger, string, string, long, long, long, Exception?> _orderBookDataSet;
private static readonly Action<ILogger, string, string, long, long, long, long, Exception?> _orderBookUpdateBuffered;
private static readonly Action<ILogger, string, string, decimal, decimal, Exception?> _orderBookOutOfSyncDetected;
private static readonly Action<ILogger, string, string, Exception?> _orderBookReconnectingSocket;
private static readonly Action<ILogger, string, string, long, long, Exception?> _orderBookSkippedMessage;
private static readonly Action<ILogger, string, string, long, long, Exception?> _orderBookProcessedMessage;
private static readonly Action<ILogger, string, string, long, long, Exception?> _orderBookOutOfSync;
static SymbolOrderBookLoggingExtensions()
{
private static readonly Action<ILogger, string, string, OrderBookStatus, OrderBookStatus, Exception?> _orderBookStatusChanged;
private static readonly Action<ILogger, string, string, Exception?> _orderBookStarting;
private static readonly Action<ILogger, string, string, Exception?> _orderBookStoppedStarting;
private static readonly Action<ILogger, string, string, Exception?> _orderBookStopping;
private static readonly Action<ILogger, string, string, Exception?> _orderBookStopped;
private static readonly Action<ILogger, string, string, Exception?> _orderBookConnectionLost;
private static readonly Action<ILogger, string, string, Exception?> _orderBookDisconnected;
private static readonly Action<ILogger, string, string, int, Exception?> _orderBookProcessingBufferedUpdates;
private static readonly Action<ILogger, string, string, long, long, Exception?> _orderBookUpdateSkipped;
private static readonly Action<ILogger, string, string, Exception?> _orderBookOutOfSyncChecksum;
private static readonly Action<ILogger, string, string, Exception?> _orderBookResyncFailed;
private static readonly Action<ILogger, string, string, Exception?> _orderBookResyncing;
private static readonly Action<ILogger, string, string, Exception?> _orderBookResynced;
private static readonly Action<ILogger, string, string, Exception?> _orderBookMessageSkippedBecauseOfResubscribing;
private static readonly Action<ILogger, string, string, long, long, long, Exception?> _orderBookDataSet;
private static readonly Action<ILogger, string, string, long, long, long, long, Exception?> _orderBookUpdateBuffered;
private static readonly Action<ILogger, string, string, decimal, decimal, Exception?> _orderBookOutOfSyncDetected;
private static readonly Action<ILogger, string, string, Exception?> _orderBookReconnectingSocket;
private static readonly Action<ILogger, string, string, long, long, Exception?> _orderBookSkippedMessage;
private static readonly Action<ILogger, string, string, long, long, Exception?> _orderBookProcessedMessage;
private static readonly Action<ILogger, string, string, long, long, Exception?> _orderBookOutOfSync;
_orderBookStatusChanged = LoggerMessage.Define<string, string, OrderBookStatus, OrderBookStatus>(
LogLevel.Information,
new EventId(5000, "OrderBookStatusChanged"),
"{Api} order book {Symbol} status changed: {PreviousStatus} => {NewStatus}");
static SymbolOrderBookLoggingExtensions()
{
_orderBookStatusChanged = LoggerMessage.Define<string, string, OrderBookStatus, OrderBookStatus>(
LogLevel.Information,
new EventId(5000, "OrderBookStatusChanged"),
"{Api} order book {Symbol} status changed: {PreviousStatus} => {NewStatus}");
_orderBookStarting = LoggerMessage.Define<string, string>(
LogLevel.Debug,
new EventId(5001, "OrderBookStarting"),
"{Api} order book {Symbol} starting");
_orderBookStarting = LoggerMessage.Define<string, string>(
LogLevel.Debug,
new EventId(5001, "OrderBookStarting"),
"{Api} order book {Symbol} starting");
_orderBookStoppedStarting = LoggerMessage.Define<string, string>(
LogLevel.Debug,
new EventId(5002, "OrderBookStoppedStarting"),
"{Api} order book {Symbol} stopped while starting");
_orderBookStoppedStarting = LoggerMessage.Define<string, string>(
LogLevel.Debug,
new EventId(5002, "OrderBookStoppedStarting"),
"{Api} order book {Symbol} stopped while starting");
_orderBookConnectionLost = LoggerMessage.Define<string, string>(
LogLevel.Warning,
new EventId(5003, "OrderBookConnectionLost"),
"{Api} order book {Symbol} connection lost");
_orderBookConnectionLost = LoggerMessage.Define<string, string>(
LogLevel.Warning,
new EventId(5003, "OrderBookConnectionLost"),
"{Api} order book {Symbol} connection lost");
_orderBookDisconnected = LoggerMessage.Define<string, string>(
LogLevel.Debug,
new EventId(5004, "OrderBookDisconnected"),
"{Api} order book {Symbol} disconnected");
_orderBookDisconnected = LoggerMessage.Define<string, string>(
LogLevel.Debug,
new EventId(5004, "OrderBookDisconnected"),
"{Api} order book {Symbol} disconnected");
_orderBookStopping = LoggerMessage.Define<string, string>(
LogLevel.Debug,
new EventId(5005, "OrderBookStopping"),
"{Api} order book {Symbol} stopping");
_orderBookStopping = LoggerMessage.Define<string, string>(
LogLevel.Debug,
new EventId(5005, "OrderBookStopping"),
"{Api} order book {Symbol} stopping");
_orderBookStopped = LoggerMessage.Define<string, string>(
LogLevel.Trace,
new EventId(5006, "OrderBookStopped"),
"{Api} order book {Symbol} stopped");
_orderBookStopped = LoggerMessage.Define<string, string>(
LogLevel.Trace,
new EventId(5006, "OrderBookStopped"),
"{Api} order book {Symbol} stopped");
_orderBookProcessingBufferedUpdates = LoggerMessage.Define<string, string, int>(
LogLevel.Debug,
new EventId(5007, "OrderBookProcessingBufferedUpdates"),
"{Api} order book {Symbol} Processing {NumberBufferedUpdated} buffered updates");
_orderBookProcessingBufferedUpdates = LoggerMessage.Define<string, string, int>(
LogLevel.Debug,
new EventId(5007, "OrderBookProcessingBufferedUpdates"),
"{Api} order book {Symbol} Processing {NumberBufferedUpdated} buffered updates");
_orderBookUpdateSkipped = LoggerMessage.Define<string, string, long, long>(
LogLevel.Debug,
new EventId(5008, "OrderBookUpdateSkipped"),
"{Api} order book {Symbol} update skipped #{SequenceNumber}, currently at #{LastSequenceNumber}");
_orderBookUpdateSkipped = LoggerMessage.Define<string, string, long, long>(
LogLevel.Debug,
new EventId(5008, "OrderBookUpdateSkipped"),
"{Api} order book {Symbol} update skipped #{SequenceNumber}, currently at #{LastSequenceNumber}");
_orderBookOutOfSync = LoggerMessage.Define<string, string, long, long>(
LogLevel.Warning,
new EventId(5009, "OrderBookOutOfSync"),
"{Api} order book {Symbol} out of sync (expected {ExpectedSequenceNumber}, was {SequenceNumber}), reconnecting");
_orderBookOutOfSync = LoggerMessage.Define<string, string, long, long>(
LogLevel.Warning,
new EventId(5009, "OrderBookOutOfSync"),
"{Api} order book {Symbol} out of sync (expected {ExpectedSequenceNumber}, was {SequenceNumber}), reconnecting");
_orderBookResynced = LoggerMessage.Define<string, string>(
LogLevel.Information,
new EventId(5010, "OrderBookResynced"),
"{Api} order book {Symbol} successfully resynchronized");
_orderBookResynced = LoggerMessage.Define<string, string>(
LogLevel.Information,
new EventId(5010, "OrderBookResynced"),
"{Api} order book {Symbol} successfully resynchronized");
_orderBookMessageSkippedBecauseOfResubscribing = LoggerMessage.Define<string, string>(
LogLevel.Trace,
new EventId(5011, "OrderBookMessageSkippedResubscribing"),
"{Api} order book {Symbol} Skipping message because of resubscribing");
_orderBookMessageSkippedBecauseOfResubscribing = LoggerMessage.Define<string, string>(
LogLevel.Trace,
new EventId(5011, "OrderBookMessageSkippedResubscribing"),
"{Api} order book {Symbol} Skipping message because of resubscribing");
_orderBookDataSet = LoggerMessage.Define<string, string, long, long, long>(
LogLevel.Debug,
new EventId(5012, "OrderBookDataSet"),
"{Api} order book {Symbol} data set: {BidCount} bids, {AskCount} asks. #{EndUpdateId}");
_orderBookDataSet = LoggerMessage.Define<string, string, long, long, long>(
LogLevel.Debug,
new EventId(5012, "OrderBookDataSet"),
"{Api} order book {Symbol} data set: {BidCount} bids, {AskCount} asks. #{EndUpdateId}");
_orderBookUpdateBuffered = LoggerMessage.Define<string, string, long, long, long, long>(
LogLevel.Trace,
new EventId(5013, "OrderBookUpdateBuffered"),
"{Api} order book {Symbol} update buffered #{StartUpdateId}-#{EndUpdateId} [{AsksCount} asks, {BidsCount} bids]");
_orderBookUpdateBuffered = LoggerMessage.Define<string, string, long, long, long, long>(
LogLevel.Trace,
new EventId(5013, "OrderBookUpdateBuffered"),
"{Api} order book {Symbol} update buffered #{StartUpdateId}-#{EndUpdateId} [{AsksCount} asks, {BidsCount} bids]");
_orderBookOutOfSyncDetected = LoggerMessage.Define<string, string, decimal, decimal>(
LogLevel.Warning,
new EventId(5014, "OrderBookOutOfSyncDetected"),
"{Api} order book {Symbol} detected out of sync order book. First ask: {FirstAsk}, first bid: {FirstBid}. Resyncing");
_orderBookOutOfSyncDetected = LoggerMessage.Define<string, string, decimal, decimal>(
LogLevel.Warning,
new EventId(5014, "OrderBookOutOfSyncDetected"),
"{Api} order book {Symbol} detected out of sync order book. First ask: {FirstAsk}, first bid: {FirstBid}. Resyncing");
_orderBookReconnectingSocket = LoggerMessage.Define<string, string>(
LogLevel.Warning,
new EventId(5015, "OrderBookReconnectingSocket"),
"{Api} order book {Symbol} out of sync. Reconnecting socket");
_orderBookReconnectingSocket = LoggerMessage.Define<string, string>(
LogLevel.Warning,
new EventId(5015, "OrderBookReconnectingSocket"),
"{Api} order book {Symbol} out of sync. Reconnecting socket");
_orderBookResyncing = LoggerMessage.Define<string, string>(
LogLevel.Warning,
new EventId(5016, "OrderBookResyncing"),
"{Api} order book {Symbol} out of sync. Resyncing");
_orderBookResyncing = LoggerMessage.Define<string, string>(
LogLevel.Warning,
new EventId(5016, "OrderBookResyncing"),
"{Api} order book {Symbol} out of sync. Resyncing");
_orderBookResyncFailed = LoggerMessage.Define<string, string>(
LogLevel.Warning,
new EventId(5017, "OrderBookResyncFailed"),
"{Api} order book {Symbol} resync failed, reconnecting socket");
_orderBookResyncFailed = LoggerMessage.Define<string, string>(
LogLevel.Warning,
new EventId(5017, "OrderBookResyncFailed"),
"{Api} order book {Symbol} resync failed, reconnecting socket");
_orderBookSkippedMessage = LoggerMessage.Define<string, string, long, long>(
LogLevel.Trace,
new EventId(5018, "OrderBookSkippedMessage"),
"{Api} order book {Symbol} update skipped #{FirstUpdateId}-{LastUpdateId}");
_orderBookSkippedMessage = LoggerMessage.Define<string, string, long, long>(
LogLevel.Trace,
new EventId(5018, "OrderBookSkippedMessage"),
"{Api} order book {Symbol} update skipped #{FirstUpdateId}-{LastUpdateId}");
_orderBookProcessedMessage = LoggerMessage.Define<string, string, long, long>(
LogLevel.Trace,
new EventId(5019, "OrderBookProcessedMessage"),
"{Api} order book {Symbol} update processed #{FirstUpdateId}-{LastUpdateId}");
_orderBookProcessedMessage = LoggerMessage.Define<string, string, long, long>(
LogLevel.Trace,
new EventId(5019, "OrderBookProcessedMessage"),
"{Api} order book {Symbol} update processed #{FirstUpdateId}-{LastUpdateId}");
_orderBookOutOfSyncChecksum = LoggerMessage.Define<string, string>(
LogLevel.Warning,
new EventId(5020, "OrderBookOutOfSyncChecksum"),
"{Api} order book {Symbol} out of sync. Checksum mismatch, resyncing");
}
_orderBookOutOfSyncChecksum = LoggerMessage.Define<string, string>(
LogLevel.Warning,
new EventId(5020, "OrderBookOutOfSyncChecksum"),
"{Api} order book {Symbol} out of sync. Checksum mismatch, resyncing");
}
public static void OrderBookStatusChanged(this ILogger logger, string api, string symbol, OrderBookStatus previousStatus, OrderBookStatus newStatus)
{
_orderBookStatusChanged(logger, api, symbol, previousStatus, newStatus, null);
}
public static void OrderBookStarting(this ILogger logger, string api, string symbol)
{
_orderBookStarting(logger, api, symbol, null);
}
public static void OrderBookStoppedStarting(this ILogger logger, string api, string symbol)
{
_orderBookStoppedStarting(logger, api, symbol, null);
}
public static void OrderBookConnectionLost(this ILogger logger, string api, string symbol)
{
_orderBookConnectionLost(logger, api, symbol, null);
}
public static void OrderBookStatusChanged(this ILogger logger, string api, string symbol, OrderBookStatus previousStatus, OrderBookStatus newStatus)
{
_orderBookStatusChanged(logger, api, symbol, previousStatus, newStatus, null);
}
public static void OrderBookStarting(this ILogger logger, string api, string symbol)
{
_orderBookStarting(logger, api, symbol, null);
}
public static void OrderBookStoppedStarting(this ILogger logger, string api, string symbol)
{
_orderBookStoppedStarting(logger, api, symbol, null);
}
public static void OrderBookConnectionLost(this ILogger logger, string api, string symbol)
{
_orderBookConnectionLost(logger, api, symbol, null);
}
public static void OrderBookDisconnected(this ILogger logger, string api, string symbol)
{
_orderBookDisconnected(logger, api, symbol, null);
}
public static void OrderBookDisconnected(this ILogger logger, string api, string symbol)
{
_orderBookDisconnected(logger, api, symbol, null);
}
public static void OrderBookStopping(this ILogger logger, string api, string symbol)
{
_orderBookStopping(logger, api, symbol, null);
}
public static void OrderBookStopping(this ILogger logger, string api, string symbol)
{
_orderBookStopping(logger, api, symbol, null);
}
public static void OrderBookStopped(this ILogger logger, string api, string symbol)
{
_orderBookStopped(logger, api, symbol, null);
}
public static void OrderBookStopped(this ILogger logger, string api, string symbol)
{
_orderBookStopped(logger, api, symbol, null);
}
public static void OrderBookProcessingBufferedUpdates(this ILogger logger, string api, string symbol, int numberBufferedUpdated)
{
_orderBookProcessingBufferedUpdates(logger, api, symbol, numberBufferedUpdated, null);
}
public static void OrderBookProcessingBufferedUpdates(this ILogger logger, string api, string symbol, int numberBufferedUpdated)
{
_orderBookProcessingBufferedUpdates(logger, api, symbol, numberBufferedUpdated, null);
}
public static void OrderBookUpdateSkipped(this ILogger logger, string api, string symbol, long sequence, long lastSequenceNumber)
{
_orderBookUpdateSkipped(logger, api, symbol, sequence, lastSequenceNumber, null);
}
public static void OrderBookUpdateSkipped(this ILogger logger, string api, string symbol, long sequence, long lastSequenceNumber)
{
_orderBookUpdateSkipped(logger, api, symbol, sequence, lastSequenceNumber, null);
}
public static void OrderBookOutOfSync(this ILogger logger, string api, string symbol, long expectedSequenceNumber, long sequenceNumber)
{
_orderBookOutOfSync(logger, api, symbol, expectedSequenceNumber, sequenceNumber, null);
}
public static void OrderBookOutOfSync(this ILogger logger, string api, string symbol, long expectedSequenceNumber, long sequenceNumber)
{
_orderBookOutOfSync(logger, api, symbol, expectedSequenceNumber, sequenceNumber, null);
}
public static void OrderBookResynced(this ILogger logger, string api, string symbol)
{
_orderBookResynced(logger, api, symbol, null);
}
public static void OrderBookResynced(this ILogger logger, string api, string symbol)
{
_orderBookResynced(logger, api, symbol, null);
}
public static void OrderBookMessageSkippedResubscribing(this ILogger logger, string api, string symbol)
{
_orderBookMessageSkippedBecauseOfResubscribing(logger, api, symbol, null);
}
public static void OrderBookDataSet(this ILogger logger, string api, string symbol, long bidCount, long askCount, long endUpdateId)
{
_orderBookDataSet(logger, api, symbol, bidCount, askCount, endUpdateId, null);
}
public static void OrderBookUpdateBuffered(this ILogger logger, string api, string symbol, long startUpdateId, long endUpdateId, long asksCount, long bidsCount)
{
_orderBookUpdateBuffered(logger, api, symbol, startUpdateId, endUpdateId, asksCount, bidsCount, null);
}
public static void OrderBookOutOfSyncDetected(this ILogger logger, string api, string symbol, decimal firstAsk, decimal firstBid)
{
_orderBookOutOfSyncDetected(logger, api, symbol, firstAsk, firstBid, null);
}
public static void OrderBookMessageSkippedResubscribing(this ILogger logger, string api, string symbol)
{
_orderBookMessageSkippedBecauseOfResubscribing(logger, api, symbol, null);
}
public static void OrderBookDataSet(this ILogger logger, string api, string symbol, long bidCount, long askCount, long endUpdateId)
{
_orderBookDataSet(logger, api, symbol, bidCount, askCount, endUpdateId, null);
}
public static void OrderBookUpdateBuffered(this ILogger logger, string api, string symbol, long startUpdateId, long endUpdateId, long asksCount, long bidsCount)
{
_orderBookUpdateBuffered(logger, api, symbol, startUpdateId, endUpdateId, asksCount, bidsCount, null);
}
public static void OrderBookOutOfSyncDetected(this ILogger logger, string api, string symbol, decimal firstAsk, decimal firstBid)
{
_orderBookOutOfSyncDetected(logger, api, symbol, firstAsk, firstBid, null);
}
public static void OrderBookReconnectingSocket(this ILogger logger, string api, string symbol)
{
_orderBookReconnectingSocket(logger, api, symbol, null);
}
public static void OrderBookReconnectingSocket(this ILogger logger, string api, string symbol)
{
_orderBookReconnectingSocket(logger, api, symbol, null);
}
public static void OrderBookResyncing(this ILogger logger, string api, string symbol)
{
_orderBookResyncing(logger, api, symbol, null);
}
public static void OrderBookResyncFailed(this ILogger logger, string api, string symbol)
{
_orderBookResyncFailed(logger, api, symbol, null);
}
public static void OrderBookSkippedMessage(this ILogger logger, string api, string symbol, long firstUpdateId, long lastUpdateId)
{
_orderBookSkippedMessage(logger, api, symbol, firstUpdateId, lastUpdateId, null);
}
public static void OrderBookProcessedMessage(this ILogger logger, string api, string symbol, long firstUpdateId, long lastUpdateId)
{
_orderBookProcessedMessage(logger, api, symbol, firstUpdateId, lastUpdateId, null);
}
public static void OrderBookResyncing(this ILogger logger, string api, string symbol)
{
_orderBookResyncing(logger, api, symbol, null);
}
public static void OrderBookResyncFailed(this ILogger logger, string api, string symbol)
{
_orderBookResyncFailed(logger, api, symbol, null);
}
public static void OrderBookSkippedMessage(this ILogger logger, string api, string symbol, long firstUpdateId, long lastUpdateId)
{
_orderBookSkippedMessage(logger, api, symbol, firstUpdateId, lastUpdateId, null);
}
public static void OrderBookProcessedMessage(this ILogger logger, string api, string symbol, long firstUpdateId, long lastUpdateId)
{
_orderBookProcessedMessage(logger, api, symbol, firstUpdateId, lastUpdateId, null);
}
public static void OrderBookOutOfSyncChecksum(this ILogger logger, string api, string symbol)
{
_orderBookOutOfSyncChecksum(logger, api, symbol, null);
}
public static void OrderBookOutOfSyncChecksum(this ILogger logger, string api, string symbol)
{
_orderBookOutOfSyncChecksum(logger, api, symbol, null);
}
}

View File

@ -1,291 +1,290 @@
using System;
using System;
using CryptoExchange.Net.Objects;
using Microsoft.Extensions.Logging;
namespace CryptoExchange.Net.Logging.Extensions
{
namespace CryptoExchange.Net.Logging.Extensions;
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
public static class TrackerLoggingExtensions
public static class TrackerLoggingExtensions
{
private static readonly Action<ILogger, string, SyncStatus, SyncStatus, Exception?> _klineTrackerStatusChanged;
private static readonly Action<ILogger, string, Exception?> _klineTrackerStarting;
private static readonly Action<ILogger, string, string, Exception?> _klineTrackerStartFailed;
private static readonly Action<ILogger, string, Exception?> _klineTrackerStarted;
private static readonly Action<ILogger, string, Exception?> _klineTrackerStopping;
private static readonly Action<ILogger, string, Exception?> _klineTrackerStopped;
private static readonly Action<ILogger, string, DateTime, Exception?> _klineTrackerInitialDataSet;
private static readonly Action<ILogger, string, DateTime, Exception?> _klineTrackerKlineUpdated;
private static readonly Action<ILogger, string, DateTime, Exception?> _klineTrackerKlineAdded;
private static readonly Action<ILogger, string, Exception?> _klineTrackerConnectionLost;
private static readonly Action<ILogger, string, Exception?> _klineTrackerConnectionClosed;
private static readonly Action<ILogger, string, Exception?> _klineTrackerConnectionRestored;
private static readonly Action<ILogger, string, SyncStatus, SyncStatus, Exception?> _tradeTrackerStatusChanged;
private static readonly Action<ILogger, string, Exception?> _tradeTrackerStarting;
private static readonly Action<ILogger, string, string, Exception?> _tradeTrackerStartFailed;
private static readonly Action<ILogger, string, Exception?> _tradeTrackerStarted;
private static readonly Action<ILogger, string, Exception?> _tradeTrackerStopping;
private static readonly Action<ILogger, string, Exception?> _tradeTrackerStopped;
private static readonly Action<ILogger, string, int, long, Exception?> _tradeTrackerInitialDataSet;
private static readonly Action<ILogger, string, long, Exception?> _tradeTrackerPreSnapshotSkip;
private static readonly Action<ILogger, string, long, Exception?> _tradeTrackerPreSnapshotApplied;
private static readonly Action<ILogger, string, long, Exception?> _tradeTrackerTradeAdded;
private static readonly Action<ILogger, string, Exception?> _tradeTrackerConnectionLost;
private static readonly Action<ILogger, string, Exception?> _tradeTrackerConnectionClosed;
private static readonly Action<ILogger, string, Exception?> _tradeTrackerConnectionRestored;
static TrackerLoggingExtensions()
{
private static readonly Action<ILogger, string, SyncStatus, SyncStatus, Exception?> _klineTrackerStatusChanged;
private static readonly Action<ILogger, string, Exception?> _klineTrackerStarting;
private static readonly Action<ILogger, string, string, Exception?> _klineTrackerStartFailed;
private static readonly Action<ILogger, string, Exception?> _klineTrackerStarted;
private static readonly Action<ILogger, string, Exception?> _klineTrackerStopping;
private static readonly Action<ILogger, string, Exception?> _klineTrackerStopped;
private static readonly Action<ILogger, string, DateTime, Exception?> _klineTrackerInitialDataSet;
private static readonly Action<ILogger, string, DateTime, Exception?> _klineTrackerKlineUpdated;
private static readonly Action<ILogger, string, DateTime, Exception?> _klineTrackerKlineAdded;
private static readonly Action<ILogger, string, Exception?> _klineTrackerConnectionLost;
private static readonly Action<ILogger, string, Exception?> _klineTrackerConnectionClosed;
private static readonly Action<ILogger, string, Exception?> _klineTrackerConnectionRestored;
_klineTrackerStatusChanged = LoggerMessage.Define<string, SyncStatus, SyncStatus>(
LogLevel.Debug,
new EventId(6001, "KlineTrackerStatusChanged"),
"Kline tracker for {Symbol} status changed: {OldStatus} => {NewStatus}");
private static readonly Action<ILogger, string, SyncStatus, SyncStatus, Exception?> _tradeTrackerStatusChanged;
private static readonly Action<ILogger, string, Exception?> _tradeTrackerStarting;
private static readonly Action<ILogger, string, string, Exception?> _tradeTrackerStartFailed;
private static readonly Action<ILogger, string, Exception?> _tradeTrackerStarted;
private static readonly Action<ILogger, string, Exception?> _tradeTrackerStopping;
private static readonly Action<ILogger, string, Exception?> _tradeTrackerStopped;
private static readonly Action<ILogger, string, int, long, Exception?> _tradeTrackerInitialDataSet;
private static readonly Action<ILogger, string, long, Exception?> _tradeTrackerPreSnapshotSkip;
private static readonly Action<ILogger, string, long, Exception?> _tradeTrackerPreSnapshotApplied;
private static readonly Action<ILogger, string, long, Exception?> _tradeTrackerTradeAdded;
private static readonly Action<ILogger, string, Exception?> _tradeTrackerConnectionLost;
private static readonly Action<ILogger, string, Exception?> _tradeTrackerConnectionClosed;
private static readonly Action<ILogger, string, Exception?> _tradeTrackerConnectionRestored;
_klineTrackerStarting = LoggerMessage.Define<string>(
LogLevel.Debug,
new EventId(6002, "KlineTrackerStarting"),
"Kline tracker for {Symbol} starting");
static TrackerLoggingExtensions()
{
_klineTrackerStatusChanged = LoggerMessage.Define<string, SyncStatus, SyncStatus>(
LogLevel.Debug,
new EventId(6001, "KlineTrackerStatusChanged"),
"Kline tracker for {Symbol} status changed: {OldStatus} => {NewStatus}");
_klineTrackerStartFailed = LoggerMessage.Define<string, string>(
LogLevel.Warning,
new EventId(6003, "KlineTrackerStartFailed"),
"Kline tracker for {Symbol} failed to start: {Error}");
_klineTrackerStarting = LoggerMessage.Define<string>(
LogLevel.Debug,
new EventId(6002, "KlineTrackerStarting"),
"Kline tracker for {Symbol} starting");
_klineTrackerStarted = LoggerMessage.Define<string>(
LogLevel.Information,
new EventId(6004, "KlineTrackerStarted"),
"Kline tracker for {Symbol} started");
_klineTrackerStartFailed = LoggerMessage.Define<string, string>(
LogLevel.Warning,
new EventId(6003, "KlineTrackerStartFailed"),
"Kline tracker for {Symbol} failed to start: {Error}");
_klineTrackerStopping = LoggerMessage.Define<string>(
LogLevel.Debug,
new EventId(6005, "KlineTrackerStopping"),
"Kline tracker for {Symbol} stopping");
_klineTrackerStarted = LoggerMessage.Define<string>(
LogLevel.Information,
new EventId(6004, "KlineTrackerStarted"),
"Kline tracker for {Symbol} started");
_klineTrackerStopped = LoggerMessage.Define<string>(
LogLevel.Information,
new EventId(6006, "KlineTrackerStopped"),
"Kline tracker for {Symbol} stopped");
_klineTrackerStopping = LoggerMessage.Define<string>(
LogLevel.Debug,
new EventId(6005, "KlineTrackerStopping"),
"Kline tracker for {Symbol} stopping");
_klineTrackerInitialDataSet = LoggerMessage.Define<string, DateTime>(
LogLevel.Debug,
new EventId(6007, "KlineTrackerInitialDataSet"),
"Kline tracker for {Symbol} initial data set, last timestamp: {LastTime}");
_klineTrackerStopped = LoggerMessage.Define<string>(
LogLevel.Information,
new EventId(6006, "KlineTrackerStopped"),
"Kline tracker for {Symbol} stopped");
_klineTrackerKlineUpdated = LoggerMessage.Define<string, DateTime>(
LogLevel.Trace,
new EventId(6008, "KlineTrackerKlineUpdated"),
"Kline tracker for {Symbol} kline updated for open time: {LastTime}");
_klineTrackerInitialDataSet = LoggerMessage.Define<string, DateTime>(
LogLevel.Debug,
new EventId(6007, "KlineTrackerInitialDataSet"),
"Kline tracker for {Symbol} initial data set, last timestamp: {LastTime}");
_klineTrackerKlineAdded = LoggerMessage.Define<string, DateTime>(
LogLevel.Trace,
new EventId(6009, "KlineTrackerKlineAdded"),
"Kline tracker for {Symbol} new kline for open time: {LastTime}");
_klineTrackerKlineUpdated = LoggerMessage.Define<string, DateTime>(
LogLevel.Trace,
new EventId(6008, "KlineTrackerKlineUpdated"),
"Kline tracker for {Symbol} kline updated for open time: {LastTime}");
_klineTrackerConnectionLost = LoggerMessage.Define<string>(
LogLevel.Warning,
new EventId(6010, "KlineTrackerConnectionLost"),
"Kline tracker for {Symbol} connection lost");
_klineTrackerKlineAdded = LoggerMessage.Define<string, DateTime>(
LogLevel.Trace,
new EventId(6009, "KlineTrackerKlineAdded"),
"Kline tracker for {Symbol} new kline for open time: {LastTime}");
_klineTrackerConnectionClosed = LoggerMessage.Define<string>(
LogLevel.Warning,
new EventId(6011, "KlineTrackerConnectionClosed"),
"Kline tracker for {Symbol} disconnected");
_klineTrackerConnectionLost = LoggerMessage.Define<string>(
LogLevel.Warning,
new EventId(6010, "KlineTrackerConnectionLost"),
"Kline tracker for {Symbol} connection lost");
_klineTrackerConnectionRestored = LoggerMessage.Define<string>(
LogLevel.Information,
new EventId(6012, "KlineTrackerConnectionRestored"),
"Kline tracker for {Symbol} successfully resynchronized");
_klineTrackerConnectionClosed = LoggerMessage.Define<string>(
LogLevel.Warning,
new EventId(6011, "KlineTrackerConnectionClosed"),
"Kline tracker for {Symbol} disconnected");
_tradeTrackerStatusChanged = LoggerMessage.Define<string, SyncStatus, SyncStatus>(
LogLevel.Debug,
new EventId(6013, "KlineTrackerStatusChanged"),
"Trade tracker for {Symbol} status changed: {OldStatus} => {NewStatus}");
_klineTrackerConnectionRestored = LoggerMessage.Define<string>(
LogLevel.Information,
new EventId(6012, "KlineTrackerConnectionRestored"),
"Kline tracker for {Symbol} successfully resynchronized");
_tradeTrackerStarting = LoggerMessage.Define<string>(
LogLevel.Debug,
new EventId(6014, "KlineTrackerStarting"),
"Trade tracker for {Symbol} starting");
_tradeTrackerStatusChanged = LoggerMessage.Define<string, SyncStatus, SyncStatus>(
LogLevel.Debug,
new EventId(6013, "KlineTrackerStatusChanged"),
"Trade tracker for {Symbol} status changed: {OldStatus} => {NewStatus}");
_tradeTrackerStartFailed = LoggerMessage.Define<string, string>(
LogLevel.Warning,
new EventId(6015, "KlineTrackerStartFailed"),
"Trade tracker for {Symbol} failed to start: {Error}");
_tradeTrackerStarting = LoggerMessage.Define<string>(
LogLevel.Debug,
new EventId(6014, "KlineTrackerStarting"),
"Trade tracker for {Symbol} starting");
_tradeTrackerStarted = LoggerMessage.Define<string>(
LogLevel.Information,
new EventId(6016, "KlineTrackerStarted"),
"Trade tracker for {Symbol} started");
_tradeTrackerStartFailed = LoggerMessage.Define<string, string>(
LogLevel.Warning,
new EventId(6015, "KlineTrackerStartFailed"),
"Trade tracker for {Symbol} failed to start: {Error}");
_tradeTrackerStopping = LoggerMessage.Define<string>(
LogLevel.Debug,
new EventId(6017, "KlineTrackerStopping"),
"Trade tracker for {Symbol} stopping");
_tradeTrackerStarted = LoggerMessage.Define<string>(
LogLevel.Information,
new EventId(6016, "KlineTrackerStarted"),
"Trade tracker for {Symbol} started");
_tradeTrackerStopped = LoggerMessage.Define<string>(
LogLevel.Information,
new EventId(6018, "KlineTrackerStopped"),
"Trade tracker for {Symbol} stopped");
_tradeTrackerStopping = LoggerMessage.Define<string>(
LogLevel.Debug,
new EventId(6017, "KlineTrackerStopping"),
"Trade tracker for {Symbol} stopping");
_tradeTrackerInitialDataSet = LoggerMessage.Define<string, int, long>(
LogLevel.Debug,
new EventId(6019, "TradeTrackerInitialDataSet"),
"Trade tracker for {Symbol} snapshot set, Count: {Count}, Last id: {LastId}");
_tradeTrackerStopped = LoggerMessage.Define<string>(
LogLevel.Information,
new EventId(6018, "KlineTrackerStopped"),
"Trade tracker for {Symbol} stopped");
_tradeTrackerPreSnapshotSkip = LoggerMessage.Define<string, long>(
LogLevel.Trace,
new EventId(6020, "TradeTrackerPreSnapshotSkip"),
"Trade tracker for {Symbol} skipping {Id}, already in snapshot");
_tradeTrackerInitialDataSet = LoggerMessage.Define<string, int, long>(
LogLevel.Debug,
new EventId(6019, "TradeTrackerInitialDataSet"),
"Trade tracker for {Symbol} snapshot set, Count: {Count}, Last id: {LastId}");
_tradeTrackerPreSnapshotApplied = LoggerMessage.Define<string, long>(
LogLevel.Trace,
new EventId(6021, "TradeTrackerPreSnapshotApplied"),
"Trade tracker for {Symbol} adding {Id} from pre-snapshot");
_tradeTrackerPreSnapshotSkip = LoggerMessage.Define<string, long>(
LogLevel.Trace,
new EventId(6020, "TradeTrackerPreSnapshotSkip"),
"Trade tracker for {Symbol} skipping {Id}, already in snapshot");
_tradeTrackerTradeAdded = LoggerMessage.Define<string, long>(
LogLevel.Trace,
new EventId(6022, "TradeTrackerTradeAdded"),
"Trade tracker for {Symbol} adding trade {Id}");
_tradeTrackerPreSnapshotApplied = LoggerMessage.Define<string, long>(
LogLevel.Trace,
new EventId(6021, "TradeTrackerPreSnapshotApplied"),
"Trade tracker for {Symbol} adding {Id} from pre-snapshot");
_tradeTrackerConnectionLost = LoggerMessage.Define<string>(
LogLevel.Warning,
new EventId(6023, "TradeTrackerConnectionLost"),
"Trade tracker for {Symbol} connection lost");
_tradeTrackerTradeAdded = LoggerMessage.Define<string, long>(
LogLevel.Trace,
new EventId(6022, "TradeTrackerTradeAdded"),
"Trade tracker for {Symbol} adding trade {Id}");
_tradeTrackerConnectionClosed = LoggerMessage.Define<string>(
LogLevel.Warning,
new EventId(6024, "TradeTrackerConnectionClosed"),
"Trade tracker for {Symbol} disconnected");
_tradeTrackerConnectionLost = LoggerMessage.Define<string>(
LogLevel.Warning,
new EventId(6023, "TradeTrackerConnectionLost"),
"Trade tracker for {Symbol} connection lost");
_tradeTrackerConnectionRestored = LoggerMessage.Define<string>(
LogLevel.Information,
new EventId(6025, "TradeTrackerConnectionRestored"),
"Trade tracker for {Symbol} successfully resynchronized");
}
_tradeTrackerConnectionClosed = LoggerMessage.Define<string>(
LogLevel.Warning,
new EventId(6024, "TradeTrackerConnectionClosed"),
"Trade tracker for {Symbol} disconnected");
public static void KlineTrackerStatusChanged(this ILogger logger, string symbol, SyncStatus oldStatus, SyncStatus newStatus)
{
_klineTrackerStatusChanged(logger, symbol, oldStatus, newStatus, null);
}
_tradeTrackerConnectionRestored = LoggerMessage.Define<string>(
LogLevel.Information,
new EventId(6025, "TradeTrackerConnectionRestored"),
"Trade tracker for {Symbol} successfully resynchronized");
}
public static void KlineTrackerStarting(this ILogger logger, string symbol)
{
_klineTrackerStarting(logger, symbol, null);
}
public static void KlineTrackerStatusChanged(this ILogger logger, string symbol, SyncStatus oldStatus, SyncStatus newStatus)
{
_klineTrackerStatusChanged(logger, symbol, oldStatus, newStatus, null);
}
public static void KlineTrackerStartFailed(this ILogger logger, string symbol, string error, Exception? exception)
{
_klineTrackerStartFailed(logger, symbol, error, exception);
}
public static void KlineTrackerStarting(this ILogger logger, string symbol)
{
_klineTrackerStarting(logger, symbol, null);
}
public static void KlineTrackerStarted(this ILogger logger, string symbol)
{
_klineTrackerStarted(logger, symbol, null);
}
public static void KlineTrackerStartFailed(this ILogger logger, string symbol, string error, Exception? exception)
{
_klineTrackerStartFailed(logger, symbol, error, exception);
}
public static void KlineTrackerStopping(this ILogger logger, string symbol)
{
_klineTrackerStopping(logger, symbol, null);
}
public static void KlineTrackerStarted(this ILogger logger, string symbol)
{
_klineTrackerStarted(logger, symbol, null);
}
public static void KlineTrackerStopped(this ILogger logger, string symbol)
{
_klineTrackerStopped(logger, symbol, null);
}
public static void KlineTrackerStopping(this ILogger logger, string symbol)
{
_klineTrackerStopping(logger, symbol, null);
}
public static void KlineTrackerInitialDataSet(this ILogger logger, string symbol, DateTime lastTime)
{
_klineTrackerInitialDataSet(logger, symbol, lastTime, null);
}
public static void KlineTrackerStopped(this ILogger logger, string symbol)
{
_klineTrackerStopped(logger, symbol, null);
}
public static void KlineTrackerKlineUpdated(this ILogger logger, string symbol, DateTime lastTime)
{
_klineTrackerKlineUpdated(logger, symbol, lastTime, null);
}
public static void KlineTrackerInitialDataSet(this ILogger logger, string symbol, DateTime lastTime)
{
_klineTrackerInitialDataSet(logger, symbol, lastTime, null);
}
public static void KlineTrackerKlineAdded(this ILogger logger, string symbol, DateTime lastTime)
{
_klineTrackerKlineAdded(logger, symbol, lastTime, null);
}
public static void KlineTrackerKlineUpdated(this ILogger logger, string symbol, DateTime lastTime)
{
_klineTrackerKlineUpdated(logger, symbol, lastTime, null);
}
public static void KlineTrackerConnectionLost(this ILogger logger, string symbol)
{
_klineTrackerConnectionLost(logger, symbol, null);
}
public static void KlineTrackerKlineAdded(this ILogger logger, string symbol, DateTime lastTime)
{
_klineTrackerKlineAdded(logger, symbol, lastTime, null);
}
public static void KlineTrackerConnectionClosed(this ILogger logger, string symbol)
{
_klineTrackerConnectionClosed(logger, symbol, null);
}
public static void KlineTrackerConnectionLost(this ILogger logger, string symbol)
{
_klineTrackerConnectionLost(logger, symbol, null);
}
public static void KlineTrackerConnectionRestored(this ILogger logger, string symbol)
{
_klineTrackerConnectionRestored(logger, symbol, null);
}
public static void KlineTrackerConnectionClosed(this ILogger logger, string symbol)
{
_klineTrackerConnectionClosed(logger, symbol, null);
}
public static void TradeTrackerStatusChanged(this ILogger logger, string symbol, SyncStatus oldStatus, SyncStatus newStatus)
{
_tradeTrackerStatusChanged(logger, symbol, oldStatus, newStatus, null);
}
public static void KlineTrackerConnectionRestored(this ILogger logger, string symbol)
{
_klineTrackerConnectionRestored(logger, symbol, null);
}
public static void TradeTrackerStarting(this ILogger logger, string symbol)
{
_tradeTrackerStarting(logger, symbol, null);
}
public static void TradeTrackerStatusChanged(this ILogger logger, string symbol, SyncStatus oldStatus, SyncStatus newStatus)
{
_tradeTrackerStatusChanged(logger, symbol, oldStatus, newStatus, null);
}
public static void TradeTrackerStartFailed(this ILogger logger, string symbol, string error, Exception? ex)
{
_tradeTrackerStartFailed(logger, symbol, error, ex);
}
public static void TradeTrackerStarting(this ILogger logger, string symbol)
{
_tradeTrackerStarting(logger, symbol, null);
}
public static void TradeTrackerStarted(this ILogger logger, string symbol)
{
_tradeTrackerStarted(logger, symbol, null);
}
public static void TradeTrackerStartFailed(this ILogger logger, string symbol, string error, Exception? ex)
{
_tradeTrackerStartFailed(logger, symbol, error, ex);
}
public static void TradeTrackerStopping(this ILogger logger, string symbol)
{
_tradeTrackerStopping(logger, symbol, null);
}
public static void TradeTrackerStarted(this ILogger logger, string symbol)
{
_tradeTrackerStarted(logger, symbol, null);
}
public static void TradeTrackerStopped(this ILogger logger, string symbol)
{
_tradeTrackerStopped(logger, symbol, null);
}
public static void TradeTrackerStopping(this ILogger logger, string symbol)
{
_tradeTrackerStopping(logger, symbol, null);
}
public static void TradeTrackerInitialDataSet(this ILogger logger, string symbol, int count, long lastId)
{
_tradeTrackerInitialDataSet(logger, symbol, count, lastId, null);
}
public static void TradeTrackerStopped(this ILogger logger, string symbol)
{
_tradeTrackerStopped(logger, symbol, null);
}
public static void TradeTrackerPreSnapshotSkip(this ILogger logger, string symbol, long lastId)
{
_tradeTrackerPreSnapshotSkip(logger, symbol, lastId, null);
}
public static void TradeTrackerInitialDataSet(this ILogger logger, string symbol, int count, long lastId)
{
_tradeTrackerInitialDataSet(logger, symbol, count, lastId, null);
}
public static void TradeTrackerPreSnapshotApplied(this ILogger logger, string symbol, long lastId)
{
_tradeTrackerPreSnapshotApplied(logger, symbol, lastId, null);
}
public static void TradeTrackerPreSnapshotSkip(this ILogger logger, string symbol, long lastId)
{
_tradeTrackerPreSnapshotSkip(logger, symbol, lastId, null);
}
public static void TradeTrackerTradeAdded(this ILogger logger, string symbol, long lastId)
{
_tradeTrackerTradeAdded(logger, symbol, lastId, null);
}
public static void TradeTrackerPreSnapshotApplied(this ILogger logger, string symbol, long lastId)
{
_tradeTrackerPreSnapshotApplied(logger, symbol, lastId, null);
}
public static void TradeTrackerConnectionLost(this ILogger logger, string symbol)
{
_tradeTrackerConnectionLost(logger, symbol, null);
}
public static void TradeTrackerTradeAdded(this ILogger logger, string symbol, long lastId)
{
_tradeTrackerTradeAdded(logger, symbol, lastId, null);
}
public static void TradeTrackerConnectionClosed(this ILogger logger, string symbol)
{
_tradeTrackerConnectionClosed(logger, symbol, null);
}
public static void TradeTrackerConnectionLost(this ILogger logger, string symbol)
{
_tradeTrackerConnectionLost(logger, symbol, null);
}
public static void TradeTrackerConnectionClosed(this ILogger logger, string symbol)
{
_tradeTrackerConnectionClosed(logger, symbol, null);
}
public static void TradeTrackerConnectionRestored(this ILogger logger, string symbol)
{
_tradeTrackerConnectionRestored(logger, symbol, null);
}
public static void TradeTrackerConnectionRestored(this ILogger logger, string symbol)
{
_tradeTrackerConnectionRestored(logger, symbol, null);
}
}

View File

@ -1,42 +1,41 @@
namespace CryptoExchange.Net.Objects
namespace CryptoExchange.Net.Objects;
/// <summary>
/// Proxy info
/// </summary>
public class ApiProxy
{
/// <summary>
/// Proxy info
/// The host address of the proxy
/// </summary>
public class ApiProxy
public string Host { get; set; }
/// <summary>
/// The port of the proxy
/// </summary>
public int Port { get; set; }
/// <summary>
/// The login of the proxy
/// </summary>
public string? Login { get; set; }
/// <summary>
/// The password of the proxy
/// </summary>
public string? Password { get; set; }
/// <summary>
/// Create new settings for a proxy
/// </summary>
/// <param name="host">The proxy hostname/ip</param>
/// <param name="port">The proxy port</param>
/// <param name="login">The proxy login</param>
/// <param name="password">The proxy password</param>
public ApiProxy(string host, int port, string? login = null, string? password = null)
{
/// <summary>
/// The host address of the proxy
/// </summary>
public string Host { get; set; }
/// <summary>
/// The port of the proxy
/// </summary>
public int Port { get; set; }
/// <summary>
/// The login of the proxy
/// </summary>
public string? Login { get; set; }
/// <summary>
/// The password of the proxy
/// </summary>
public string? Password { get; set; }
/// <summary>
/// Create new settings for a proxy
/// </summary>
/// <param name="host">The proxy hostname/ip</param>
/// <param name="port">The proxy port</param>
/// <param name="login">The proxy login</param>
/// <param name="password">The proxy password</param>
public ApiProxy(string host, int port, string? login = null, string? password = null)
{
Host = host;
Port = port;
Login = login;
Password = password;
}
Host = host;
Port = port;
Login = login;
Password = password;
}
}

View File

@ -1,30 +1,25 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace CryptoExchange.Net.Objects;
namespace CryptoExchange.Net.Objects
/// <summary>
/// An alias used by the exchange for an asset commonly known by another name
/// </summary>
public class AssetAlias
{
/// <summary>
/// An alias used by the exchange for an asset commonly known by another name
/// The name of the asset on the exchange
/// </summary>
public class AssetAlias
{
/// <summary>
/// The name of the asset on the exchange
/// </summary>
public string ExchangeAssetName { get; set; }
/// <summary>
/// The name of the asset as it's commonly known
/// </summary>
public string CommonAssetName { get; set; }
public string ExchangeAssetName { get; set; }
/// <summary>
/// The name of the asset as it's commonly known
/// </summary>
public string CommonAssetName { get; set; }
/// <summary>
/// ctor
/// </summary>
public AssetAlias(string exchangeName, string commonName)
{
ExchangeAssetName = exchangeName;
CommonAssetName = commonName;
}
/// <summary>
/// ctor
/// </summary>
public AssetAlias(string exchangeName, string commonName)
{
ExchangeAssetName = exchangeName;
CommonAssetName = commonName;
}
}

View File

@ -1,34 +1,30 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace CryptoExchange.Net.Objects
namespace CryptoExchange.Net.Objects;
/// <summary>
/// Exchange configuration for asset aliases
/// </summary>
public class AssetAliasConfiguration
{
/// <summary>
/// Exchange configuration for asset aliases
/// Defined aliases
/// </summary>
public class AssetAliasConfiguration
{
/// <summary>
/// Defined aliases
/// </summary>
public AssetAlias[] Aliases { get; set; } = [];
public AssetAlias[] Aliases { get; set; } = [];
/// <summary>
/// Auto convert asset names when using the Shared interfaces. Defaults to true
/// </summary>
public bool AutoConvertEnabled { get; set; } = true;
/// <summary>
/// Auto convert asset names when using the Shared interfaces. Defaults to true
/// </summary>
public bool AutoConvertEnabled { get; set; } = true;
/// <summary>
/// Map the common name to an exchange name for an asset. If there is no alias the input name is returned
/// </summary>
public string CommonToExchangeName(string commonName) => !AutoConvertEnabled ? commonName : Aliases.SingleOrDefault(x => x.CommonAssetName == commonName)?.ExchangeAssetName ?? commonName;
/// <summary>
/// Map the common name to an exchange name for an asset. If there is no alias the input name is returned
/// </summary>
public string CommonToExchangeName(string commonName) => !AutoConvertEnabled ? commonName : Aliases.SingleOrDefault(x => x.CommonAssetName == commonName)?.ExchangeAssetName ?? commonName;
/// <summary>
/// Map the exchange name to a common name for an asset. If there is no alias the input name is returned
/// </summary>
public string ExchangeToCommonName(string exchangeName) => !AutoConvertEnabled ? exchangeName : Aliases.SingleOrDefault(x => x.ExchangeAssetName == exchangeName)?.CommonAssetName ?? exchangeName;
/// <summary>
/// Map the exchange name to a common name for an asset. If there is no alias the input name is returned
/// </summary>
public string ExchangeToCommonName(string exchangeName) => !AutoConvertEnabled ? exchangeName : Aliases.SingleOrDefault(x => x.ExchangeAssetName == exchangeName)?.CommonAssetName ?? exchangeName;
}
}

View File

@ -1,122 +1,123 @@
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace CryptoExchange.Net.Objects
namespace CryptoExchange.Net.Objects;
/// <summary>
/// Async auto reset based on Stephen Toub`s implementation
/// https://devblogs.microsoft.com/pfxteam/building-async-coordination-primitives-part-2-asyncautoresetevent/
/// </summary>
public class AsyncResetEvent : IDisposable
{
private static readonly Task<bool> _completed = Task.FromResult(true);
private Queue<TaskCompletionSource<bool>> _waits = new Queue<TaskCompletionSource<bool>>();
private bool _signaled;
private readonly bool _reset;
/// <summary>
/// Async auto reset based on Stephen Toub`s implementation
/// https://devblogs.microsoft.com/pfxteam/building-async-coordination-primitives-part-2-asyncautoresetevent/
/// New AsyncResetEvent
/// </summary>
public class AsyncResetEvent : IDisposable
/// <param name="initialState"></param>
/// <param name="reset"></param>
public AsyncResetEvent(bool initialState = false, bool reset = true)
{
private static readonly Task<bool> _completed = Task.FromResult(true);
private Queue<TaskCompletionSource<bool>> _waits = new Queue<TaskCompletionSource<bool>>();
private bool _signaled;
private readonly bool _reset;
_signaled = initialState;
_reset = reset;
}
/// <summary>
/// New AsyncResetEvent
/// </summary>
/// <param name="initialState"></param>
/// <param name="reset"></param>
public AsyncResetEvent(bool initialState = false, bool reset = true)
{
_signaled = initialState;
_reset = reset;
}
/// <summary>
/// Wait for the AutoResetEvent to be set
/// </summary>
/// <returns></returns>
public async Task<bool> WaitAsync(TimeSpan? timeout = null, CancellationToken ct = default)
{
CancellationTokenRegistration registration = default;
try
{
Task<bool> waiter = _completed;
lock (_waits)
{
if (_signaled)
{
if (_reset)
_signaled = false;
}
else if (!ct.IsCancellationRequested)
{
var tcs = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
if (timeout.HasValue)
{
var timeoutSource = new CancellationTokenSource(timeout.Value);
var cancellationSource = CancellationTokenSource.CreateLinkedTokenSource(timeoutSource.Token, ct);
ct = cancellationSource.Token;
}
registration = ct.Register(() =>
{
lock (_waits)
{
tcs.TrySetResult(false);
// Not the cleanest but it works
_waits = new Queue<TaskCompletionSource<bool>>(_waits.Where(i => i != tcs));
}
}, useSynchronizationContext: false);
_waits.Enqueue(tcs);
waiter = tcs.Task;
}
}
return await waiter.ConfigureAwait(false);
}
finally
{
registration.Dispose();
}
}
/// <summary>
/// Signal a waiter
/// </summary>
public void Set()
/// <summary>
/// Wait for the AutoResetEvent to be set
/// </summary>
/// <returns></returns>
public async Task<bool> WaitAsync(TimeSpan? timeout = null, CancellationToken ct = default)
{
CancellationTokenRegistration registration = default;
try
{
Task<bool> waiter = _completed;
lock (_waits)
{
if (!_reset)
if (_signaled)
{
// Act as ManualResetEvent. Once set keep it signaled and signal everyone who is waiting
_signaled = true;
while (_waits.Count > 0)
{
var toRelease = _waits.Dequeue();
toRelease.TrySetResult(true);
}
if (_reset)
_signaled = false;
}
else
else if (!ct.IsCancellationRequested)
{
// Act as AutoResetEvent. When set signal 1 waiter
if (_waits.Count > 0)
var tcs = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
if (timeout.HasValue)
{
var toRelease = _waits.Dequeue();
toRelease.TrySetResult(true);
var timeoutSource = new CancellationTokenSource(timeout.Value);
var cancellationSource = CancellationTokenSource.CreateLinkedTokenSource(timeoutSource.Token, ct);
ct = cancellationSource.Token;
}
else if (!_signaled)
_signaled = true;
registration = ct.Register(() =>
{
lock (_waits)
{
tcs.TrySetResult(false);
// Not the cleanest but it works
_waits = new Queue<TaskCompletionSource<bool>>(_waits.Where(i => i != tcs));
}
}, useSynchronizationContext: false);
_waits.Enqueue(tcs);
waiter = tcs.Task;
}
}
return await waiter.ConfigureAwait(false);
}
finally
{
registration.Dispose();
}
}
/// <summary>
/// Signal a waiter
/// </summary>
public void Set()
{
lock (_waits)
{
if (!_reset)
{
// Act as ManualResetEvent. Once set keep it signaled and signal everyone who is waiting
_signaled = true;
while (_waits.Count > 0)
{
var toRelease = _waits.Dequeue();
toRelease.TrySetResult(true);
}
}
else
{
// Act as AutoResetEvent. When set signal 1 waiter
if (_waits.Count > 0)
{
var toRelease = _waits.Dequeue();
toRelease.TrySetResult(true);
}
else if (!_signaled)
{
_signaled = true;
}
}
}
}
/// <summary>
/// Dispose
/// </summary>
public void Dispose()
{
_waits.Clear();
}
/// <summary>
/// Dispose
/// </summary>
public void Dispose()
{
_waits.Clear();
}
}

View File

@ -1,10 +1,9 @@
using CryptoExchange.Net.Interfaces;
using CryptoExchange.Net.Interfaces;
using System;
namespace CryptoExchange.Net.Objects
namespace CryptoExchange.Net.Objects;
internal class AuthTimeProvider : IAuthTimeProvider
{
internal class AuthTimeProvider : IAuthTimeProvider
{
public DateTime GetTime() => DateTime.UtcNow;
}
public DateTime GetTime() => DateTime.UtcNow;
}

View File

@ -1,58 +1,57 @@
using System;
using System;
using System.Collections.Generic;
namespace CryptoExchange.Net.Objects
namespace CryptoExchange.Net.Objects;
/// <summary>
/// Comparer for byte order
/// </summary>
public class ByteOrderComparer : IComparer<byte[]>
{
/// <summary>
/// Comparer for byte order
/// Compare function
/// </summary>
public class ByteOrderComparer : IComparer<byte[]>
/// <param name="x"></param>
/// <param name="y"></param>
/// <returns></returns>
public int Compare(byte[]? x, byte[]? y)
{
/// <summary>
/// Compare function
/// </summary>
/// <param name="x"></param>
/// <param name="y"></param>
/// <returns></returns>
public int Compare(byte[]? x, byte[]? y)
// Shortcuts: If both are null, they are the same.
if (x == null && y == null) return 0;
// If one is null and the other isn't, then the
// one that is null is "lesser".
if (x == null) return -1;
if (y == null) return 1;
// Both arrays are non-null. Find the shorter
// of the two lengths.
var bytesToCompare = Math.Min(x.Length, y.Length);
// Compare the bytes.
for (var index = 0; index < bytesToCompare; ++index)
{
// Shortcuts: If both are null, they are the same.
if (x == null && y == null) return 0;
// The x and y bytes.
var xByte = x[index];
var yByte = y[index];
// If one is null and the other isn't, then the
// one that is null is "lesser".
if (x == null) return -1;
if (y == null) return 1;
// Compare result.
var compareResult = Comparer<byte>.Default.Compare(xByte, yByte);
// Both arrays are non-null. Find the shorter
// of the two lengths.
var bytesToCompare = Math.Min(x.Length, y.Length);
// If not the same, then return the result of the
// comparison of the bytes, as they were the same
// up until now.
if (compareResult != 0) return compareResult;
// Compare the bytes.
for (var index = 0; index < bytesToCompare; ++index)
{
// The x and y bytes.
var xByte = x[index];
var yByte = y[index];
// Compare result.
var compareResult = Comparer<byte>.Default.Compare(xByte, yByte);
// If not the same, then return the result of the
// comparison of the bytes, as they were the same
// up until now.
if (compareResult != 0) return compareResult;
// They are the same, continue.
}
// The first n bytes are the same. Compare lengths.
// If the lengths are the same, the arrays
// are the same.
if (x.Length == y.Length) return 0;
// Compare lengths.
return x.Length < y.Length ? -1 : 1;
// They are the same, continue.
}
// The first n bytes are the same. Compare lengths.
// If the lengths are the same, the arrays
// are the same.
if (x.Length == y.Length) return 0;
// Compare lengths.
return x.Length < y.Length ? -1 : 1;
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,21 +1,20 @@
namespace CryptoExchange.Net.Objects
namespace CryptoExchange.Net.Objects;
/// <summary>
/// Constants
/// </summary>
public class Constants
{
/// <summary>
/// Constants
/// Json content type header
/// </summary>
public class Constants
{
/// <summary>
/// Json content type header
/// </summary>
public const string JsonContentHeader = "application/json";
/// <summary>
/// Form content type header
/// </summary>
public const string FormContentHeader = "application/x-www-form-urlencoded";
/// <summary>
/// Placeholder key for when request body should be set to the value of this KVP
/// </summary>
public const string BodyPlaceHolderKey = "_BODY_";
}
public const string JsonContentHeader = "application/json";
/// <summary>
/// Form content type header
/// </summary>
public const string FormContentHeader = "application/x-www-form-urlencoded";
/// <summary>
/// Placeholder key for when request body should be set to the value of this KVP
/// </summary>
public const string BodyPlaceHolderKey = "_BODY_";
}

View File

@ -1,270 +1,266 @@
using CryptoExchange.Net.Attributes;
namespace CryptoExchange.Net.Objects;
namespace CryptoExchange.Net.Objects
/// <summary>
/// What to do when a request would exceed the rate limit
/// </summary>
public enum RateLimitingBehaviour
{
/// <summary>
/// What to do when a request would exceed the rate limit
/// Fail the request
/// </summary>
public enum RateLimitingBehaviour
{
/// <summary>
/// Fail the request
/// </summary>
Fail,
/// <summary>
/// Wait till the request can be send
/// </summary>
Wait
}
Fail,
/// <summary>
/// What to do when a request would exceed the rate limit
/// Wait till the request can be send
/// </summary>
public enum RateLimitWindowType
{
/// <summary>
/// A sliding window
/// </summary>
Sliding,
/// <summary>
/// A fixed interval window
/// </summary>
Fixed,
/// <summary>
/// A fixed interval starting after the first request
/// </summary>
FixedAfterFirst,
/// <summary>
/// Decaying window
/// </summary>
Decay
}
/// <summary>
/// Where the parameters for a HttpMethod should be added in a request
/// </summary>
public enum HttpMethodParameterPosition
{
/// <summary>
/// Parameters in body
/// </summary>
InBody,
/// <summary>
/// Parameters in url
/// </summary>
InUri
}
/// <summary>
/// The format of the request body
/// </summary>
public enum RequestBodyFormat
{
/// <summary>
/// Form data
/// </summary>
FormData,
/// <summary>
/// Json
/// </summary>
Json
}
/// <summary>
/// Tracker sync status
/// </summary>
public enum SyncStatus
{
/// <summary>
/// Not connected
/// </summary>
Disconnected,
/// <summary>
/// Syncing, data connection is being made
/// </summary>
Syncing,
/// <summary>
/// The connection is active, but the full data backlog is not yet reached. For example, a tracker set to retain 10 minutes of data only has 8 minutes of data at this moment.
/// </summary>
PartiallySynced,
/// <summary>
/// Synced
/// </summary>
Synced,
/// <summary>
/// Disposed
/// </summary>
Disposed
}
/// <summary>
/// Status of the order book
/// </summary>
public enum OrderBookStatus
{
/// <summary>
/// Not connected
/// </summary>
Disconnected,
/// <summary>
/// Connecting
/// </summary>
Connecting,
/// <summary>
/// Reconnecting
/// </summary>
Reconnecting,
/// <summary>
/// Syncing data
/// </summary>
Syncing,
/// <summary>
/// Data synced, order book is up to date
/// </summary>
Synced,
/// <summary>
/// Disposing
/// </summary>
Disposing,
/// <summary>
/// Disposed
/// </summary>
Disposed
}
/// <summary>
/// Order book entry type
/// </summary>
public enum OrderBookEntryType
{
/// <summary>
/// Ask
/// </summary>
Ask,
/// <summary>
/// Bid
/// </summary>
Bid
}
/// <summary>
/// Define how array parameters should be send
/// </summary>
public enum ArrayParametersSerialization
#pragma warning disable CS1570 // XML comment has badly formed XML
{
/// <summary>
/// Send as key=value1&key=value2
/// </summary>
MultipleValues,
/// <summary>
/// Send as key[]=value1&key[]=value2
/// </summary>
Array,
/// <summary>
/// Send as key=[value1, value2]
/// </summary>
JsonArray
#pragma warning restore CS1570 // XML comment has badly formed XML
}
/// <summary>
/// How to round
/// </summary>
public enum RoundingType
{
/// <summary>
/// Round down (flooring)
/// </summary>
Down,
/// <summary>
/// Round to closest value
/// </summary>
Closest,
/// <summary>
/// Round up (ceil)
/// </summary>
Up
}
/// <summary>
/// Type of the update
/// </summary>
public enum SocketUpdateType
{
/// <summary>
/// A update
/// </summary>
Update,
/// <summary>
/// A snapshot, generally send at the start of the connection
/// </summary>
Snapshot
}
/// <summary>
/// Reconnect policy
/// </summary>
public enum ReconnectPolicy
{
/// <summary>
/// Reconnect is disabled
/// </summary>
Disabled,
/// <summary>
/// Fixed delay of `ReconnectInterval` between retries
/// </summary>
FixedDelay,
/// <summary>
/// Backoff policy of 2^`reconnectAttempt`, where `reconnectAttempt` has a max value of 5
/// </summary>
ExponentialBackoff
}
/// <summary>
/// The data source of the result
/// </summary>
public enum ResultDataSource
{
/// <summary>
/// From server
/// </summary>
Server,
/// <summary>
/// From cache
/// </summary>
Cache
}
/// <summary>
/// Type of exchange
/// </summary>
public enum ExchangeType
{
/// <summary>
/// Centralized
/// </summary>
CEX,
/// <summary>
/// Decentralized
/// </summary>
DEX
}
/// <summary>
/// Timeout behavior for queries
/// </summary>
public enum TimeoutBehavior
{
/// <summary>
/// Fail the request
/// </summary>
Fail,
/// <summary>
/// Mark the query as successful
/// </summary>
Succeed
}
Wait
}
/// <summary>
/// What to do when a request would exceed the rate limit
/// </summary>
public enum RateLimitWindowType
{
/// <summary>
/// A sliding window
/// </summary>
Sliding,
/// <summary>
/// A fixed interval window
/// </summary>
Fixed,
/// <summary>
/// A fixed interval starting after the first request
/// </summary>
FixedAfterFirst,
/// <summary>
/// Decaying window
/// </summary>
Decay
}
/// <summary>
/// Where the parameters for a HttpMethod should be added in a request
/// </summary>
public enum HttpMethodParameterPosition
{
/// <summary>
/// Parameters in body
/// </summary>
InBody,
/// <summary>
/// Parameters in url
/// </summary>
InUri
}
/// <summary>
/// The format of the request body
/// </summary>
public enum RequestBodyFormat
{
/// <summary>
/// Form data
/// </summary>
FormData,
/// <summary>
/// Json
/// </summary>
Json
}
/// <summary>
/// Tracker sync status
/// </summary>
public enum SyncStatus
{
/// <summary>
/// Not connected
/// </summary>
Disconnected,
/// <summary>
/// Syncing, data connection is being made
/// </summary>
Syncing,
/// <summary>
/// The connection is active, but the full data backlog is not yet reached. For example, a tracker set to retain 10 minutes of data only has 8 minutes of data at this moment.
/// </summary>
PartiallySynced,
/// <summary>
/// Synced
/// </summary>
Synced,
/// <summary>
/// Disposed
/// </summary>
Disposed
}
/// <summary>
/// Status of the order book
/// </summary>
public enum OrderBookStatus
{
/// <summary>
/// Not connected
/// </summary>
Disconnected,
/// <summary>
/// Connecting
/// </summary>
Connecting,
/// <summary>
/// Reconnecting
/// </summary>
Reconnecting,
/// <summary>
/// Syncing data
/// </summary>
Syncing,
/// <summary>
/// Data synced, order book is up to date
/// </summary>
Synced,
/// <summary>
/// Disposing
/// </summary>
Disposing,
/// <summary>
/// Disposed
/// </summary>
Disposed
}
/// <summary>
/// Order book entry type
/// </summary>
public enum OrderBookEntryType
{
/// <summary>
/// Ask
/// </summary>
Ask,
/// <summary>
/// Bid
/// </summary>
Bid
}
/// <summary>
/// Define how array parameters should be send
/// </summary>
public enum ArrayParametersSerialization
#pragma warning disable CS1570 // XML comment has badly formed XML
{
/// <summary>
/// Send as key=value1&key=value2
/// </summary>
MultipleValues,
/// <summary>
/// Send as key[]=value1&key[]=value2
/// </summary>
Array,
/// <summary>
/// Send as key=[value1, value2]
/// </summary>
JsonArray
#pragma warning restore CS1570 // XML comment has badly formed XML
}
/// <summary>
/// How to round
/// </summary>
public enum RoundingType
{
/// <summary>
/// Round down (flooring)
/// </summary>
Down,
/// <summary>
/// Round to closest value
/// </summary>
Closest,
/// <summary>
/// Round up (ceil)
/// </summary>
Up
}
/// <summary>
/// Type of the update
/// </summary>
public enum SocketUpdateType
{
/// <summary>
/// A update
/// </summary>
Update,
/// <summary>
/// A snapshot, generally send at the start of the connection
/// </summary>
Snapshot
}
/// <summary>
/// Reconnect policy
/// </summary>
public enum ReconnectPolicy
{
/// <summary>
/// Reconnect is disabled
/// </summary>
Disabled,
/// <summary>
/// Fixed delay of `ReconnectInterval` between retries
/// </summary>
FixedDelay,
/// <summary>
/// Backoff policy of 2^`reconnectAttempt`, where `reconnectAttempt` has a max value of 5
/// </summary>
ExponentialBackoff
}
/// <summary>
/// The data source of the result
/// </summary>
public enum ResultDataSource
{
/// <summary>
/// From server
/// </summary>
Server,
/// <summary>
/// From cache
/// </summary>
Cache
}
/// <summary>
/// Type of exchange
/// </summary>
public enum ExchangeType
{
/// <summary>
/// Centralized
/// </summary>
CEX,
/// <summary>
/// Decentralized
/// </summary>
DEX
}
/// <summary>
/// Timeout behavior for queries
/// </summary>
public enum TimeoutBehavior
{
/// <summary>
/// Fail the request
/// </summary>
Fail,
/// <summary>
/// Mark the query as successful
/// </summary>
Succeed
}

View File

@ -1,333 +1,331 @@
using CryptoExchange.Net.Objects.Errors;
using CryptoExchange.Net.Objects.Errors;
using System;
namespace CryptoExchange.Net.Objects
namespace CryptoExchange.Net.Objects;
/// <summary>
/// Base class for errors
/// </summary>
public abstract class Error
{
private int? _code;
/// <summary>
/// Base class for errors
/// The int error code the server returned; or the http status code int value if there was no error code.<br />
/// <br />
/// <i>Note:</i><br />
/// The <see cref="ErrorCode"/> property should be used for more generic error checking; it might contain a string error code if the server does not return an int code.
/// </summary>
public abstract class Error
{
private int? _code;
/// <summary>
/// The int error code the server returned; or the http status code int value if there was no error code.<br />
/// <br />
/// <i>Note:</i><br />
/// The <see cref="ErrorCode"/> property should be used for more generic error checking; it might contain a string error code if the server does not return an int code.
/// </summary>
public int? Code
{
get
{
if (_code.HasValue)
return _code;
return int.TryParse(ErrorCode, out var r) ? r : null;
}
set
{
_code = value;
}
}
/// <summary>
/// The error code returned by the server
/// </summary>
public string? ErrorCode { get; set; }
/// <summary>
/// The error description
/// </summary>
public string? ErrorDescription { get; set; }
/// <summary>
/// Error type
/// </summary>
public ErrorType ErrorType { get; set; }
/// <summary>
/// Whether the error is transient and can be retried
/// </summary>
public bool IsTransient { get; set; }
/// <summary>
/// The server message for the error that occurred
/// </summary>
public string? Message { get; set; }
/// <summary>
/// Underlying exception
/// </summary>
public Exception? Exception { get; set; }
/// <summary>
/// ctor
/// </summary>
protected Error(string? errorCode, ErrorInfo errorInfo, Exception? exception)
public int? Code
{
get
{
ErrorCode = errorCode;
ErrorType = errorInfo.ErrorType;
Message = errorInfo.Message;
ErrorDescription = errorInfo.ErrorDescription;
IsTransient = errorInfo.IsTransient;
Exception = exception;
}
if (_code.HasValue)
return _code;
/// <summary>
/// String representation
/// </summary>
/// <returns></returns>
public override string ToString()
return int.TryParse(ErrorCode, out var r) ? r : null;
}
set
{
return ErrorCode != null ? $"[{GetType().Name}.{ErrorType}] {ErrorCode}: {Message ?? ErrorDescription}" : $"[{GetType().Name}.{ErrorType}] {Message ?? ErrorDescription}";
_code = value;
}
}
/// <summary>
/// Cant reach server error
/// The error code returned by the server
/// </summary>
public class CantConnectError : Error
public string? ErrorCode { get; set; }
/// <summary>
/// The error description
/// </summary>
public string? ErrorDescription { get; set; }
/// <summary>
/// Error type
/// </summary>
public ErrorType ErrorType { get; set; }
/// <summary>
/// Whether the error is transient and can be retried
/// </summary>
public bool IsTransient { get; set; }
/// <summary>
/// The server message for the error that occurred
/// </summary>
public string? Message { get; set; }
/// <summary>
/// Underlying exception
/// </summary>
public Exception? Exception { get; set; }
/// <summary>
/// ctor
/// </summary>
protected Error(string? errorCode, ErrorInfo errorInfo, Exception? exception)
{
/// <summary>
/// Default error info
/// </summary>
protected static readonly ErrorInfo _errorInfo = new ErrorInfo(ErrorType.UnableToConnect, false, "Can't connect to the server");
/// <summary>
/// ctor
/// </summary>
public CantConnectError() : base(null, _errorInfo, null) { }
/// <summary>
/// ctor
/// </summary>
public CantConnectError(Exception? exception) : base(null, _errorInfo, exception) { }
/// <summary>
/// ctor
/// </summary>
protected CantConnectError(ErrorInfo info, Exception? exception) : base(null, info, exception) { }
ErrorCode = errorCode;
ErrorType = errorInfo.ErrorType;
Message = errorInfo.Message;
ErrorDescription = errorInfo.ErrorDescription;
IsTransient = errorInfo.IsTransient;
Exception = exception;
}
/// <summary>
/// No api credentials provided while trying to access a private endpoint
/// String representation
/// </summary>
public class NoApiCredentialsError : Error
/// <returns></returns>
public override string ToString()
{
/// <summary>
/// Default error info
/// </summary>
protected static readonly ErrorInfo _errorInfo = new ErrorInfo(ErrorType.MissingCredentials, false, "No credentials provided for private endpoint");
/// <summary>
/// ctor
/// </summary>
public NoApiCredentialsError() : base(null, _errorInfo, null) { }
/// <summary>
/// ctor
/// </summary>
protected NoApiCredentialsError(ErrorInfo info, Exception? exception) : base(null, info, exception) { }
}
/// <summary>
/// Error returned by the server
/// </summary>
public class ServerError : Error
{
/// <summary>
/// ctor
/// </summary>
public ServerError(ErrorInfo errorInfo, Exception? exception = null)
: base(null, errorInfo, exception) { }
/// <summary>
/// ctor
/// </summary>
public ServerError(int errorCode, ErrorInfo errorInfo, Exception? exception = null)
: this(errorCode.ToString(), errorInfo, exception) { }
/// <summary>
/// ctor
/// </summary>
public ServerError(string errorCode, ErrorInfo errorInfo, Exception? exception = null) : base(errorCode, errorInfo, exception) { }
}
/// <summary>
/// Web error returned by the server
/// </summary>
public class WebError : Error
{
/// <summary>
/// Default error info
/// </summary>
protected static readonly ErrorInfo _errorInfo = new ErrorInfo(ErrorType.NetworkError, true, "Failed to complete the request to the server due to a network error");
/// <summary>
/// ctor
/// </summary>
public WebError(string? message = null, Exception? exception = null) : base(null, _errorInfo with { Message = (message?.Length > 0 ? _errorInfo.Message + ": " + message : _errorInfo.Message) }, exception) { }
}
/// <summary>
/// Timeout error waiting for a response from the server
/// </summary>
public class TimeoutError : Error
{
/// <summary>
/// Default error info
/// </summary>
protected static readonly ErrorInfo _errorInfo = new ErrorInfo(ErrorType.Timeout, false, "Failed to receive a response from the server in time");
/// <summary>
/// ctor
/// </summary>
public TimeoutError(string? message = null, Exception? exception = null) : base(null, _errorInfo with { Message = (message?.Length > 0 ? _errorInfo.Message + ": " + message : _errorInfo.Message) }, exception) { }
}
/// <summary>
/// Error while deserializing data
/// </summary>
public class DeserializeError : Error
{
/// <summary>
/// Default error info
/// </summary>
protected static readonly ErrorInfo _errorInfo = new ErrorInfo(ErrorType.DeserializationFailed, false, "Failed to deserialize data");
/// <summary>
/// ctor
/// </summary>
public DeserializeError(string? message = null, Exception? exception = null) : base(null, _errorInfo with { Message = (message?.Length > 0 ? _errorInfo.Message + ": " + message : _errorInfo.Message) }, exception) { }
}
/// <summary>
/// An invalid parameter has been provided
/// </summary>
public class ArgumentError : Error
{
/// <summary>
/// Default error info for missing parameter
/// </summary>
protected static readonly ErrorInfo _missingInfo = new ErrorInfo(ErrorType.MissingParameter, false, "Missing parameter");
/// <summary>
/// Default error info for invalid parameter
/// </summary>
protected static readonly ErrorInfo _invalidInfo = new ErrorInfo(ErrorType.InvalidParameter, false, "Invalid parameter");
/// <summary>
/// ctor
/// </summary>
public static ArgumentError Missing(string parameterName, string? message = null) => new ArgumentError(_missingInfo with { Message = message == null ? $"{_missingInfo.Message} '{parameterName}'" : $"{_missingInfo.Message} '{parameterName}': {message}" }, null);
/// <summary>
/// ctor
/// </summary>
public static ArgumentError Invalid(string parameterName, string message) => new ArgumentError(_invalidInfo with { Message = $"{_invalidInfo.Message} '{parameterName}': {message}" }, null);
/// <summary>
/// ctor
/// </summary>
protected ArgumentError(ErrorInfo info, Exception? exception) : base(null, info, exception) { }
}
/// <summary>
/// Rate limit exceeded (client side)
/// </summary>
public abstract class BaseRateLimitError : Error
{
/// <summary>
/// When the request can be retried
/// </summary>
public DateTime? RetryAfter { get; set; }
/// <summary>
/// ctor
/// </summary>
protected BaseRateLimitError(ErrorInfo errorInfo, Exception? exception) : base(null, errorInfo, exception) { }
}
/// <summary>
/// Rate limit exceeded (client side)
/// </summary>
public class ClientRateLimitError : BaseRateLimitError
{
/// <summary>
/// Default error info
/// </summary>
protected static readonly ErrorInfo _errorInfo = new ErrorInfo(ErrorType.RateLimitRequest, false, "Client rate limit exceeded");
/// <summary>
/// ctor
/// </summary>
public ClientRateLimitError(string? message = null, Exception? exception = null) : base(_errorInfo with { Message = (message?.Length > 0 ? _errorInfo.Message + ": " + message : _errorInfo.Message) }, exception) { }
/// <summary>
/// ctor
/// </summary>
protected ClientRateLimitError(ErrorInfo info, Exception? exception) : base(info, exception) { }
}
/// <summary>
/// Rate limit exceeded (server side)
/// </summary>
public class ServerRateLimitError : BaseRateLimitError
{
/// <summary>
/// Default error info
/// </summary>
protected static readonly ErrorInfo _errorInfo = new ErrorInfo(ErrorType.RateLimitRequest, false, "Server rate limit exceeded");
/// <summary>
/// ctor
/// </summary>
public ServerRateLimitError(string? message = null, Exception? exception = null) : base(_errorInfo with { Message = (message?.Length > 0 ? _errorInfo.Message + ": " + message : _errorInfo.Message) }, exception) { }
/// <summary>
/// ctor
/// </summary>
protected ServerRateLimitError(ErrorInfo info, Exception? exception) : base(info, exception) { }
}
/// <summary>
/// Cancellation requested
/// </summary>
public class CancellationRequestedError : Error
{
/// <summary>
/// Default error info
/// </summary>
protected static readonly ErrorInfo _errorInfo = new ErrorInfo(ErrorType.CancellationRequested, false, "Cancellation requested");
/// <summary>
/// ctor
/// </summary>
public CancellationRequestedError(Exception? exception = null) : base(null, _errorInfo, null) { }
/// <summary>
/// ctor
/// </summary>
protected CancellationRequestedError(ErrorInfo info, Exception? exception) : base(null, info, exception) { }
}
/// <summary>
/// Invalid operation requested
/// </summary>
public class InvalidOperationError : Error
{
/// <summary>
/// Default error info
/// </summary>
protected static readonly ErrorInfo _errorInfo = new ErrorInfo(ErrorType.InvalidOperation, false, "Operation invalid");
/// <summary>
/// ctor
/// </summary>
public InvalidOperationError(string message) : base(null, _errorInfo with { Message = message }, null) { }
/// <summary>
/// ctor
/// </summary>
protected InvalidOperationError(ErrorInfo info, Exception? exception) : base(null, info, exception) { }
return ErrorCode != null ? $"[{GetType().Name}.{ErrorType}] {ErrorCode}: {Message ?? ErrorDescription}" : $"[{GetType().Name}.{ErrorType}] {Message ?? ErrorDescription}";
}
}
/// <summary>
/// Cant reach server error
/// </summary>
public class CantConnectError : Error
{
/// <summary>
/// Default error info
/// </summary>
protected static readonly ErrorInfo errorInfo = new ErrorInfo(ErrorType.UnableToConnect, false, "Can't connect to the server");
/// <summary>
/// ctor
/// </summary>
public CantConnectError() : base(null, errorInfo, null) { }
/// <summary>
/// ctor
/// </summary>
public CantConnectError(Exception? exception) : base(null, errorInfo, exception) { }
/// <summary>
/// ctor
/// </summary>
protected CantConnectError(ErrorInfo info, Exception? exception) : base(null, info, exception) { }
}
/// <summary>
/// No api credentials provided while trying to access a private endpoint
/// </summary>
public class NoApiCredentialsError : Error
{
/// <summary>
/// Default error info
/// </summary>
protected static readonly ErrorInfo errorInfo = new ErrorInfo(ErrorType.MissingCredentials, false, "No credentials provided for private endpoint");
/// <summary>
/// ctor
/// </summary>
public NoApiCredentialsError() : base(null, errorInfo, null) { }
/// <summary>
/// ctor
/// </summary>
protected NoApiCredentialsError(ErrorInfo info, Exception? exception) : base(null, info, exception) { }
}
/// <summary>
/// Error returned by the server
/// </summary>
public class ServerError : Error
{
/// <summary>
/// ctor
/// </summary>
public ServerError(ErrorInfo errorInfo, Exception? exception = null)
: base(null, errorInfo, exception) { }
/// <summary>
/// ctor
/// </summary>
public ServerError(int errorCode, ErrorInfo errorInfo, Exception? exception = null)
: this(errorCode.ToString(), errorInfo, exception) { }
/// <summary>
/// ctor
/// </summary>
public ServerError(string errorCode, ErrorInfo errorInfo, Exception? exception = null) : base(errorCode, errorInfo, exception) { }
}
/// <summary>
/// Web error returned by the server
/// </summary>
public class WebError : Error
{
/// <summary>
/// Default error info
/// </summary>
protected static readonly ErrorInfo errorInfo = new ErrorInfo(ErrorType.NetworkError, true, "Failed to complete the request to the server due to a network error");
/// <summary>
/// ctor
/// </summary>
public WebError(string? message = null, Exception? exception = null) : base(null, errorInfo with { Message = (message?.Length > 0 ? errorInfo.Message + ": " + message : errorInfo.Message) }, exception) { }
}
/// <summary>
/// Timeout error waiting for a response from the server
/// </summary>
public class TimeoutError : Error
{
/// <summary>
/// Default error info
/// </summary>
protected static readonly ErrorInfo errorInfo = new ErrorInfo(ErrorType.Timeout, false, "Failed to receive a response from the server in time");
/// <summary>
/// ctor
/// </summary>
public TimeoutError(string? message = null, Exception? exception = null) : base(null, errorInfo with { Message = (message?.Length > 0 ? errorInfo.Message + ": " + message : errorInfo.Message) }, exception) { }
}
/// <summary>
/// Error while deserializing data
/// </summary>
public class DeserializeError : Error
{
/// <summary>
/// Default error info
/// </summary>
protected static readonly ErrorInfo errorInfo = new ErrorInfo(ErrorType.DeserializationFailed, false, "Failed to deserialize data");
/// <summary>
/// ctor
/// </summary>
public DeserializeError(string? message = null, Exception? exception = null) : base(null, errorInfo with { Message = (message?.Length > 0 ? errorInfo.Message + ": " + message : errorInfo.Message) }, exception) { }
}
/// <summary>
/// An invalid parameter has been provided
/// </summary>
public class ArgumentError : Error
{
/// <summary>
/// Default error info for missing parameter
/// </summary>
protected static readonly ErrorInfo missingInfo = new ErrorInfo(ErrorType.MissingParameter, false, "Missing parameter");
/// <summary>
/// Default error info for invalid parameter
/// </summary>
protected static readonly ErrorInfo invalidInfo = new ErrorInfo(ErrorType.InvalidParameter, false, "Invalid parameter");
/// <summary>
/// ctor
/// </summary>
public static ArgumentError Missing(string parameterName, string? message = null) => new ArgumentError(missingInfo with { Message = message == null ? $"{missingInfo.Message} '{parameterName}'" : $"{missingInfo.Message} '{parameterName}': {message}" }, null);
/// <summary>
/// ctor
/// </summary>
public static ArgumentError Invalid(string parameterName, string message) => new ArgumentError(invalidInfo with { Message = $"{invalidInfo.Message} '{parameterName}': {message}" }, null);
/// <summary>
/// ctor
/// </summary>
protected ArgumentError(ErrorInfo info, Exception? exception) : base(null, info, exception) { }
}
/// <summary>
/// Rate limit exceeded (client side)
/// </summary>
public abstract class BaseRateLimitError : Error
{
/// <summary>
/// When the request can be retried
/// </summary>
public DateTime? RetryAfter { get; set; }
/// <summary>
/// ctor
/// </summary>
protected BaseRateLimitError(ErrorInfo errorInfo, Exception? exception) : base(null, errorInfo, exception) { }
}
/// <summary>
/// Rate limit exceeded (client side)
/// </summary>
public class ClientRateLimitError : BaseRateLimitError
{
/// <summary>
/// Default error info
/// </summary>
protected static readonly ErrorInfo errorInfo = new ErrorInfo(ErrorType.RateLimitRequest, false, "Client rate limit exceeded");
/// <summary>
/// ctor
/// </summary>
public ClientRateLimitError(string? message = null, Exception? exception = null) : base(errorInfo with { Message = (message?.Length > 0 ? errorInfo.Message + ": " + message : errorInfo.Message) }, exception) { }
/// <summary>
/// ctor
/// </summary>
protected ClientRateLimitError(ErrorInfo info, Exception? exception) : base(info, exception) { }
}
/// <summary>
/// Rate limit exceeded (server side)
/// </summary>
public class ServerRateLimitError : BaseRateLimitError
{
/// <summary>
/// Default error info
/// </summary>
protected static readonly ErrorInfo errorInfo = new ErrorInfo(ErrorType.RateLimitRequest, false, "Server rate limit exceeded");
/// <summary>
/// ctor
/// </summary>
public ServerRateLimitError(string? message = null, Exception? exception = null) : base(errorInfo with { Message = (message?.Length > 0 ? errorInfo.Message + ": " + message : errorInfo.Message) }, exception) { }
/// <summary>
/// ctor
/// </summary>
protected ServerRateLimitError(ErrorInfo info, Exception? exception) : base(info, exception) { }
}
/// <summary>
/// Cancellation requested
/// </summary>
public class CancellationRequestedError : Error
{
/// <summary>
/// Default error info
/// </summary>
protected static readonly ErrorInfo errorInfo = new ErrorInfo(ErrorType.CancellationRequested, false, "Cancellation requested");
/// <summary>
/// ctor
/// </summary>
public CancellationRequestedError(Exception? exception = null) : base(null, errorInfo, null) { }
/// <summary>
/// ctor
/// </summary>
protected CancellationRequestedError(ErrorInfo info, Exception? exception) : base(null, info, exception) { }
}
/// <summary>
/// Invalid operation requested
/// </summary>
public class InvalidOperationError : Error
{
/// <summary>
/// Default error info
/// </summary>
protected static readonly ErrorInfo errorInfo = new ErrorInfo(ErrorType.InvalidOperation, false, "Operation invalid");
/// <summary>
/// ctor
/// </summary>
public InvalidOperationError(string message) : base(null, errorInfo with { Message = message }, null) { }
/// <summary>
/// ctor
/// </summary>
protected InvalidOperationError(ErrorInfo info, Exception? exception) : base(null, info, exception) { }
}

View File

@ -1,40 +1,37 @@
using System;
using System.Collections.Generic;
using System.Text;
using System;
namespace CryptoExchange.Net.Objects.Errors
namespace CryptoExchange.Net.Objects.Errors;
/// <summary>
/// Error evaluator
/// </summary>
public class ErrorEvaluator
{
/// <summary>
/// Error evaluator
/// Error code
/// </summary>
public class ErrorEvaluator
public string[] ErrorCodes { get; set; }
/// <summary>
/// Evaluation callback for determining the error type
/// </summary>
public Func<string, string?, ErrorInfo> ErrorTypeEvaluator { get; set; }
/// <summary>
/// ctor
/// </summary>
public ErrorEvaluator(string errorCode, Func<string, string?, ErrorInfo> errorTypeEvaluator)
{
/// <summary>
/// Error code
/// </summary>
public string[] ErrorCodes { get; set; }
ErrorCodes = [errorCode];
ErrorTypeEvaluator = errorTypeEvaluator;
}
/// <summary>
/// Evaluation callback for determining the error type
/// </summary>
public Func<string, string?, ErrorInfo> ErrorTypeEvaluator { get; set; }
/// <summary>
/// ctor
/// </summary>
public ErrorEvaluator(string errorCode, Func<string, string?, ErrorInfo> errorTypeEvaluator)
{
ErrorCodes = [errorCode];
ErrorTypeEvaluator = errorTypeEvaluator;
}
/// <summary>
/// ctor
/// </summary>
public ErrorEvaluator(string[] errorCodes, Func<string, string?, ErrorInfo> errorTypeEvaluator)
{
ErrorCodes = errorCodes;
ErrorTypeEvaluator = errorTypeEvaluator;
}
/// <summary>
/// ctor
/// </summary>
public ErrorEvaluator(string[] errorCodes, Func<string, string?, ErrorInfo> errorTypeEvaluator)
{
ErrorCodes = errorCodes;
ErrorTypeEvaluator = errorTypeEvaluator;
}
}

View File

@ -1,58 +1,55 @@
using System;
namespace CryptoExchange.Net.Objects.Errors;
namespace CryptoExchange.Net.Objects.Errors
/// <summary>
/// Error info
/// </summary>
public record ErrorInfo
{
/// <summary>
/// Error info
/// Unknown error info
/// </summary>
public record ErrorInfo
public static ErrorInfo Unknown { get; } = new ErrorInfo(ErrorType.Unknown, false, "Unknown error", []);
/// <summary>
/// The server error code
/// </summary>
public string[] ErrorCodes { get; set; }
/// <summary>
/// Error description
/// </summary>
public string? ErrorDescription { get; set; }
/// <summary>
/// The error type
/// </summary>
public ErrorType ErrorType { get; set; }
/// <summary>
/// Whether the error is transient and can be retried
/// </summary>
public bool IsTransient { get; set; }
/// <summary>
/// Server response message
/// </summary>
public string? Message { get; set; }
/// <summary>
/// ctor
/// </summary>
public ErrorInfo(ErrorType errorType, string description)
{
/// <summary>
/// Unknown error info
/// </summary>
public static ErrorInfo Unknown { get; } = new ErrorInfo(ErrorType.Unknown, false, "Unknown error", []);
ErrorCodes = [];
ErrorType = errorType;
IsTransient = false;
ErrorDescription = description;
}
/// <summary>
/// The server error code
/// </summary>
public string[] ErrorCodes { get; set; }
/// <summary>
/// Error description
/// </summary>
public string? ErrorDescription { get; set; }
/// <summary>
/// The error type
/// </summary>
public ErrorType ErrorType { get; set; }
/// <summary>
/// Whether the error is transient and can be retried
/// </summary>
public bool IsTransient { get; set; }
/// <summary>
/// Server response message
/// </summary>
public string? Message { get; set; }
/// <summary>
/// ctor
/// </summary>
public ErrorInfo(ErrorType errorType, string description)
{
ErrorCodes = [];
ErrorType = errorType;
IsTransient = false;
ErrorDescription = description;
}
/// <summary>
/// ctor
/// </summary>
public ErrorInfo(ErrorType errorType, bool isTransient, string description, params string[] errorCodes)
{
ErrorCodes = errorCodes;
ErrorType = errorType;
IsTransient = isTransient;
ErrorDescription = description;
}
/// <summary>
/// ctor
/// </summary>
public ErrorInfo(ErrorType errorType, bool isTransient, string description, params string[] errorCodes)
{
ErrorCodes = errorCodes;
ErrorType = errorType;
IsTransient = isTransient;
ErrorDescription = description;
}
}

View File

@ -1,54 +1,51 @@
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace CryptoExchange.Net.Objects.Errors
namespace CryptoExchange.Net.Objects.Errors;
/// <summary>
/// Error mapping collection
/// </summary>
public class ErrorMapping
{
private Dictionary<string, ErrorEvaluator> _evaluators = new Dictionary<string, ErrorEvaluator>();
private Dictionary<string, ErrorInfo> _directMapping = new Dictionary<string, ErrorInfo>();
/// <summary>
/// Error mapping collection
/// ctor
/// </summary>
public class ErrorMapping
public ErrorMapping(ErrorInfo[] errorMappings, ErrorEvaluator[]? errorTypeEvaluators = null)
{
private Dictionary<string, ErrorEvaluator> _evaluators = new Dictionary<string, ErrorEvaluator>();
private Dictionary<string, ErrorInfo> _directMapping = new Dictionary<string, ErrorInfo>();
/// <summary>
/// ctor
/// </summary>
public ErrorMapping(ErrorInfo[] errorMappings, ErrorEvaluator[]? errorTypeEvaluators = null)
foreach (var item in errorMappings)
{
foreach (var item in errorMappings)
{
if (!item.ErrorCodes.Any())
throw new Exception("Error codes can't be null in error mapping");
if (item.ErrorCodes.Length == 0)
throw new Exception("Error codes can't be null in error mapping");
foreach(var code in item.ErrorCodes!)
_directMapping.Add(code, item);
}
if (errorTypeEvaluators == null)
return;
foreach (var item in errorTypeEvaluators)
{
foreach(var code in item.ErrorCodes)
_evaluators.Add(code, item);
}
foreach(var code in item.ErrorCodes!)
_directMapping.Add(code, item);
}
/// <summary>
/// Get error info for an error code
/// </summary>
public ErrorInfo GetErrorInfo(string code, string? message)
{
if (_directMapping.TryGetValue(code!, out var info))
return info with { Message = message };
if (_evaluators.TryGetValue(code!, out var eva))
return eva.ErrorTypeEvaluator.Invoke(code!, message) with { Message = message };
if (errorTypeEvaluators == null)
return;
return ErrorInfo.Unknown with { Message = message };
foreach (var item in errorTypeEvaluators)
{
foreach(var code in item.ErrorCodes)
_evaluators.Add(code, item);
}
}
/// <summary>
/// Get error info for an error code
/// </summary>
public ErrorInfo GetErrorInfo(string code, string? message)
{
if (_directMapping.TryGetValue(code!, out var info))
return info with { Message = message };
if (_evaluators.TryGetValue(code!, out var eva))
return eva.ErrorTypeEvaluator.Invoke(code!, message) with { Message = message };
return ErrorInfo.Unknown with { Message = message };
}
}

View File

@ -1,162 +1,157 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace CryptoExchange.Net.Objects.Errors;
namespace CryptoExchange.Net.Objects.Errors
/// <summary>
/// Type of error
/// </summary>
public enum ErrorType
{
#region Library errors
/// <summary>
/// Type of error
/// Failed to connect to server
/// </summary>
public enum ErrorType
{
#region Library errors
UnableToConnect,
/// <summary>
/// Failed to complete the request to the server
/// </summary>
NetworkError,
/// <summary>
/// No API credentials have been specified
/// </summary>
MissingCredentials,
/// <summary>
/// Invalid parameter value
/// </summary>
InvalidParameter,
/// <summary>
/// Missing parameter value
/// </summary>
MissingParameter,
/// <summary>
/// Cancellation requested by user
/// </summary>
CancellationRequested,
/// <summary>
/// Invalid operation requested
/// </summary>
InvalidOperation,
/// <summary>
/// Failed to deserialize data
/// </summary>
DeserializationFailed,
/// <summary>
/// Websocket is temporarily paused
/// </summary>
WebsocketPaused,
/// <summary>
/// Timeout while waiting for data from the order book subscription
/// </summary>
OrderBookTimeout,
/// <summary>
/// All orders failed for a multi-order operation
/// </summary>
AllOrdersFailed,
/// <summary>
/// Request timeout
/// </summary>
Timeout,
/// <summary>
/// Failed to connect to server
/// </summary>
UnableToConnect,
/// <summary>
/// Failed to complete the request to the server
/// </summary>
NetworkError,
/// <summary>
/// No API credentials have been specified
/// </summary>
MissingCredentials,
/// <summary>
/// Invalid parameter value
/// </summary>
InvalidParameter,
/// <summary>
/// Missing parameter value
/// </summary>
MissingParameter,
/// <summary>
/// Cancellation requested by user
/// </summary>
CancellationRequested,
/// <summary>
/// Invalid operation requested
/// </summary>
InvalidOperation,
/// <summary>
/// Failed to deserialize data
/// </summary>
DeserializationFailed,
/// <summary>
/// Websocket is temporarily paused
/// </summary>
WebsocketPaused,
/// <summary>
/// Timeout while waiting for data from the order book subscription
/// </summary>
OrderBookTimeout,
/// <summary>
/// All orders failed for a multi-order operation
/// </summary>
AllOrdersFailed,
/// <summary>
/// Request timeout
/// </summary>
Timeout,
#endregion
#endregion
#region Server errors
#region Server errors
/// <summary>
/// Unknown error
/// </summary>
Unknown,
/// <summary>
/// Not authorized or insufficient permissions
/// </summary>
Unauthorized,
/// <summary>
/// Request rate limit error, too many requests
/// </summary>
RateLimitRequest,
/// <summary>
/// Connection rate limit error, too many connections
/// </summary>
RateLimitConnection,
/// <summary>
/// Subscription rate limit error, too many subscriptions
/// </summary>
RateLimitSubscription,
/// <summary>
/// Order rate limit error, too many orders
/// </summary>
RateLimitOrder,
/// <summary>
/// Request timestamp invalid
/// </summary>
InvalidTimestamp,
/// <summary>
/// Unknown symbol
/// </summary>
UnknownSymbol,
/// <summary>
/// Unknown asset
/// </summary>
UnknownAsset,
/// <summary>
/// Unknown order
/// </summary>
UnknownOrder,
/// <summary>
/// Duplicate subscription
/// </summary>
DuplicateSubscription,
/// <summary>
/// Invalid quantity
/// </summary>
InvalidQuantity,
/// <summary>
/// Invalid price
/// </summary>
InvalidPrice,
/// <summary>
/// Parameter(s) for stop or tp/sl order invalid
/// </summary>
InvalidStopParameters,
/// <summary>
/// Not enough balance to execute request
/// </summary>
InsufficientBalance,
/// <summary>
/// Client order id already in use
/// </summary>
DuplicateClientOrderId,
/// <summary>
/// Symbol is not currently trading
/// </summary>
UnavailableSymbol,
/// <summary>
/// Order rejected due to order configuration such as order type or time in force restrictions
/// </summary>
RejectedOrderConfiguration,
/// <summary>
/// There is no open position
/// </summary>
NoPosition,
/// <summary>
/// Max position reached
/// </summary>
MaxPosition,
/// <summary>
/// Error in the internal system
/// </summary>
SystemError,
/// <summary>
/// The target object is not in the correct state for an operation
/// </summary>
IncorrectState,
/// <summary>
/// Risk management error
/// </summary>
RiskError
/// <summary>
/// Unknown error
/// </summary>
Unknown,
/// <summary>
/// Not authorized or insufficient permissions
/// </summary>
Unauthorized,
/// <summary>
/// Request rate limit error, too many requests
/// </summary>
RateLimitRequest,
/// <summary>
/// Connection rate limit error, too many connections
/// </summary>
RateLimitConnection,
/// <summary>
/// Subscription rate limit error, too many subscriptions
/// </summary>
RateLimitSubscription,
/// <summary>
/// Order rate limit error, too many orders
/// </summary>
RateLimitOrder,
/// <summary>
/// Request timestamp invalid
/// </summary>
InvalidTimestamp,
/// <summary>
/// Unknown symbol
/// </summary>
UnknownSymbol,
/// <summary>
/// Unknown asset
/// </summary>
UnknownAsset,
/// <summary>
/// Unknown order
/// </summary>
UnknownOrder,
/// <summary>
/// Duplicate subscription
/// </summary>
DuplicateSubscription,
/// <summary>
/// Invalid quantity
/// </summary>
InvalidQuantity,
/// <summary>
/// Invalid price
/// </summary>
InvalidPrice,
/// <summary>
/// Parameter(s) for stop or tp/sl order invalid
/// </summary>
InvalidStopParameters,
/// <summary>
/// Not enough balance to execute request
/// </summary>
InsufficientBalance,
/// <summary>
/// Client order id already in use
/// </summary>
DuplicateClientOrderId,
/// <summary>
/// Symbol is not currently trading
/// </summary>
UnavailableSymbol,
/// <summary>
/// Order rejected due to order configuration such as order type or time in force restrictions
/// </summary>
RejectedOrderConfiguration,
/// <summary>
/// There is no open position
/// </summary>
NoPosition,
/// <summary>
/// Max position reached
/// </summary>
MaxPosition,
/// <summary>
/// Error in the internal system
/// </summary>
SystemError,
/// <summary>
/// The target object is not in the correct state for an operation
/// </summary>
IncorrectState,
/// <summary>
/// Risk management error
/// </summary>
RiskError
#endregion
}
#endregion
}

View File

@ -1,20 +1,19 @@
using CryptoExchange.Net.Authentication;
using CryptoExchange.Net.Authentication;
namespace CryptoExchange.Net.Objects.Options
namespace CryptoExchange.Net.Objects.Options;
/// <summary>
/// Options for API usage
/// </summary>
public class ApiOptions
{
/// <summary>
/// Options for API usage
/// If true, the CallResult and DataEvent objects will also include the originally received json data in the OriginalData property
/// </summary>
public class ApiOptions
{
/// <summary>
/// If true, the CallResult and DataEvent objects will also include the originally received json data in the OriginalData property
/// </summary>
public bool? OutputOriginalData { get; set; }
public bool? OutputOriginalData { get; set; }
/// <summary>
/// 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>
/// The api credentials used for signing requests to this API. Overrides API credentials provided in the client options
/// </summary>
public ApiCredentials? ApiCredentials { get; set; }
}

View File

@ -1,46 +1,45 @@
using CryptoExchange.Net.Authentication;
using CryptoExchange.Net.Authentication;
using System;
namespace CryptoExchange.Net.Objects.Options
namespace CryptoExchange.Net.Objects.Options;
/// <summary>
/// Exchange options
/// </summary>
public class ExchangeOptions
{
/// <summary>
/// Exchange options
/// Proxy settings
/// </summary>
public class ExchangeOptions
public ApiProxy? Proxy { get; set; }
/// <summary>
/// If true, the CallResult and DataEvent objects will also include the originally received json data in the OriginalData property
/// </summary>
public bool OutputOriginalData { get; set; }
/// <summary>
/// The max time a request is allowed to take
/// </summary>
public TimeSpan RequestTimeout { get; set; } = TimeSpan.FromSeconds(20);
/// <summary>
/// The api credentials used for signing requests to this API.
/// </summary>
public ApiCredentials? ApiCredentials { get; set; }
/// <summary>
/// Whether or not client side rate limiting should be applied
/// </summary>
public bool RateLimiterEnabled { get; set; } = true;
/// <summary>
/// What should happen when a rate limit is reached
/// </summary>
public RateLimitingBehaviour RateLimitingBehaviour { get; set; } = RateLimitingBehaviour.Wait;
/// <inheritdoc />
public override string ToString()
{
/// <summary>
/// Proxy settings
/// </summary>
public ApiProxy? Proxy { get; set; }
/// <summary>
/// If true, the CallResult and DataEvent objects will also include the originally received json data in the OriginalData property
/// </summary>
public bool OutputOriginalData { get; set; } = false;
/// <summary>
/// The max time a request is allowed to take
/// </summary>
public TimeSpan RequestTimeout { get; set; } = TimeSpan.FromSeconds(20);
/// <summary>
/// The api credentials used for signing requests to this API.
/// </summary>
public ApiCredentials? ApiCredentials { get; set; }
/// <summary>
/// Whether or not client side rate limiting should be applied
/// </summary>
public bool RateLimiterEnabled { get; set; } = true;
/// <summary>
/// What should happen when a rate limit is reached
/// </summary>
public RateLimitingBehaviour RateLimitingBehaviour { get; set; } = RateLimitingBehaviour.Wait;
/// <inheritdoc />
public override string ToString()
{
return $"RequestTimeout: {RequestTimeout}, Proxy: {(Proxy == null ? "-" : "set")}, ApiCredentials: {(ApiCredentials == null ? "-" : "set")}";
}
return $"RequestTimeout: {RequestTimeout}, Proxy: {(Proxy == null ? "-" : "set")}, ApiCredentials: {(ApiCredentials == null ? "-" : "set")}";
}
}

View File

@ -1,58 +1,57 @@
using CryptoExchange.Net.Authentication;
using CryptoExchange.Net.Authentication;
using Microsoft.Extensions.DependencyInjection;
namespace CryptoExchange.Net.Objects.Options
namespace CryptoExchange.Net.Objects.Options;
/// <summary>
/// Library options
/// </summary>
/// <typeparam name="TRestOptions"></typeparam>
/// <typeparam name="TSocketOptions"></typeparam>
/// <typeparam name="TApiCredentials"></typeparam>
/// <typeparam name="TEnvironment"></typeparam>
public class LibraryOptions<TRestOptions, TSocketOptions, TApiCredentials, TEnvironment>
where TRestOptions: RestExchangeOptions, new()
where TSocketOptions: SocketExchangeOptions, new()
where TApiCredentials: ApiCredentials
where TEnvironment: TradeEnvironment
{
/// <summary>
/// Library options
/// Rest client options
/// </summary>
/// <typeparam name="TRestOptions"></typeparam>
/// <typeparam name="TSocketOptions"></typeparam>
/// <typeparam name="TApiCredentials"></typeparam>
/// <typeparam name="TEnvironment"></typeparam>
public class LibraryOptions<TRestOptions, TSocketOptions, TApiCredentials, TEnvironment>
where TRestOptions: RestExchangeOptions, new()
where TSocketOptions: SocketExchangeOptions, new()
where TApiCredentials: ApiCredentials
where TEnvironment: TradeEnvironment
public TRestOptions Rest { get; set; } = new TRestOptions();
/// <summary>
/// Socket client options
/// </summary>
public TSocketOptions Socket { get; set; } = new TSocketOptions();
/// <summary>
/// Trade environment. Contains info about URL's to use to connect to the API.
/// </summary>
public TEnvironment? Environment { get; set; }
/// <summary>
/// The api credentials used for signing requests.
/// </summary>
public TApiCredentials? ApiCredentials { get; set; }
/// <summary>
/// The DI service lifetime for the socket client
/// </summary>
public ServiceLifetime? SocketClientLifeTime { get; set; }
/// <summary>
/// Copy values from these options to the target options
/// </summary>
public T Set<T>(T targetOptions) where T: LibraryOptions<TRestOptions, TSocketOptions, TApiCredentials, TEnvironment>
{
/// <summary>
/// Rest client options
/// </summary>
public TRestOptions Rest { get; set; } = new TRestOptions();
targetOptions.ApiCredentials = (TApiCredentials?)ApiCredentials?.Copy();
targetOptions.Environment = Environment;
targetOptions.SocketClientLifeTime = SocketClientLifeTime;
targetOptions.Rest = Rest.Set(targetOptions.Rest);
targetOptions.Socket = Socket.Set(targetOptions.Socket);
/// <summary>
/// Socket client options
/// </summary>
public TSocketOptions Socket { get; set; } = new TSocketOptions();
/// <summary>
/// Trade environment. Contains info about URL's to use to connect to the API.
/// </summary>
public TEnvironment? Environment { get; set; }
/// <summary>
/// The api credentials used for signing requests.
/// </summary>
public TApiCredentials? ApiCredentials { get; set; }
/// <summary>
/// The DI service lifetime for the socket client
/// </summary>
public ServiceLifetime? SocketClientLifeTime { get; set; }
/// <summary>
/// Copy values from these options to the target options
/// </summary>
public T Set<T>(T targetOptions) where T: LibraryOptions<TRestOptions, TSocketOptions, TApiCredentials, TEnvironment>
{
targetOptions.ApiCredentials = (TApiCredentials?)ApiCredentials?.Copy();
targetOptions.Environment = Environment;
targetOptions.SocketClientLifeTime = SocketClientLifeTime;
targetOptions.Rest = Rest.Set(targetOptions.Rest);
targetOptions.Socket = Socket.Set(targetOptions.Socket);
return targetOptions;
}
return targetOptions;
}
}

View File

@ -1,26 +1,25 @@
namespace CryptoExchange.Net.Objects.Options
namespace CryptoExchange.Net.Objects.Options;
/// <summary>
/// Base for order book options
/// </summary>
public class OrderBookOptions
{
/// <summary>
/// Base for order book options
/// Whether or not checksum validation is enabled. Default is true, disabling will ignore checksum messages.
/// </summary>
public class OrderBookOptions
{
/// <summary>
/// Whether or not checksum validation is enabled. Default is true, disabling will ignore checksum messages.
/// </summary>
public bool ChecksumValidationEnabled { get; set; } = true;
public bool ChecksumValidationEnabled { get; set; } = true;
/// <summary>
/// Create a copy of this options
/// </summary>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
public T Copy<T>() where T : OrderBookOptions, new()
/// <summary>
/// Create a copy of this options
/// </summary>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
public T Copy<T>() where T : OrderBookOptions, new()
{
return new T
{
return new T
{
ChecksumValidationEnabled = ChecksumValidationEnabled,
};
}
ChecksumValidationEnabled = ChecksumValidationEnabled,
};
}
}

View File

@ -1,49 +1,48 @@
using CryptoExchange.Net.Authentication;
using CryptoExchange.Net.Authentication;
using System;
namespace CryptoExchange.Net.Objects.Options
namespace CryptoExchange.Net.Objects.Options;
/// <summary>
/// Http api options
/// </summary>
public class RestApiOptions : ApiOptions
{
/// <summary>
/// Http api options
/// Whether or not to automatically sync the local time with the server time
/// </summary>
public class RestApiOptions : ApiOptions
{
/// <summary>
/// Whether or not to automatically sync the local time with the server time
/// </summary>
public bool? AutoTimestamp { get; set; }
/// <summary>
/// How often the timestamp adjustment between client and server is recalculated. If you need a very small TimeSpan here you're probably better of syncing your server time more often
/// </summary>
public TimeSpan? TimestampRecalculationInterval { get; set; }
/// <summary>
/// Set the values of this options on the target options
/// </summary>
public T Set<T>(T item) where T : RestApiOptions, new()
{
item.ApiCredentials = ApiCredentials?.Copy();
item.OutputOriginalData = OutputOriginalData;
item.AutoTimestamp = AutoTimestamp;
item.TimestampRecalculationInterval = TimestampRecalculationInterval;
return item;
}
}
public bool? AutoTimestamp { get; set; }
/// <summary>
/// Http API options
/// How often the timestamp adjustment between client and server is recalculated. If you need a very small TimeSpan here you're probably better of syncing your server time more often
/// </summary>
/// <typeparam name="TApiCredentials"></typeparam>
public class RestApiOptions<TApiCredentials>: RestApiOptions where TApiCredentials: ApiCredentials
public TimeSpan? TimestampRecalculationInterval { get; set; }
/// <summary>
/// Set the values of this options on the target options
/// </summary>
public T Set<T>(T item) where T : RestApiOptions, new()
{
/// <summary>
/// The api credentials used for signing requests to this API.
/// </summary>
public new TApiCredentials? ApiCredentials
{
get => (TApiCredentials?)base.ApiCredentials;
set => base.ApiCredentials = value;
}
item.ApiCredentials = ApiCredentials?.Copy();
item.OutputOriginalData = OutputOriginalData;
item.AutoTimestamp = AutoTimestamp;
item.TimestampRecalculationInterval = TimestampRecalculationInterval;
return item;
}
}
/// <summary>
/// Http API options
/// </summary>
/// <typeparam name="TApiCredentials"></typeparam>
public class RestApiOptions<TApiCredentials>: RestApiOptions where TApiCredentials: ApiCredentials
{
/// <summary>
/// The api credentials used for signing requests to this API.
/// </summary>
public new TApiCredentials? ApiCredentials
{
get => (TApiCredentials?)base.ApiCredentials;
set => base.ApiCredentials = value;
}
}

View File

@ -1,91 +1,90 @@
using CryptoExchange.Net.Authentication;
using CryptoExchange.Net.Authentication;
using System;
namespace CryptoExchange.Net.Objects.Options
namespace CryptoExchange.Net.Objects.Options;
/// <summary>
/// Options for a rest exchange client
/// </summary>
public class RestExchangeOptions: ExchangeOptions
{
/// <summary>
/// Options for a rest exchange client
/// Whether or not to automatically sync the local time with the server time
/// </summary>
public class RestExchangeOptions: ExchangeOptions
{
/// <summary>
/// Whether or not to automatically sync the local time with the server time
/// </summary>
public bool AutoTimestamp { get; set; }
/// <summary>
/// How often the timestamp adjustment between client and server is recalculated. If you need a very small TimeSpan here you're probably better of syncing your server time more often
/// </summary>
public TimeSpan TimestampRecalculationInterval { get; set; } = TimeSpan.FromHours(1);
/// <summary>
/// Whether caching is enabled. Caching will only be applied to GET http requests. The lifetime of cached results can be determined by the `CachingMaxAge` option
/// </summary>
public bool CachingEnabled { get; set; } = false;
/// <summary>
/// The max age of a cached entry, only used when the `CachingEnabled` options is set to true. When a cached entry is older than the max age it will be discarded and a new server request will be done
/// </summary>
public TimeSpan CachingMaxAge { get; set; } = TimeSpan.FromSeconds(5);
/// <summary>
/// Set the values of this options on the target options
/// </summary>
public T Set<T>(T item) where T : RestExchangeOptions, new()
{
item.OutputOriginalData = OutputOriginalData;
item.AutoTimestamp = AutoTimestamp;
item.TimestampRecalculationInterval = TimestampRecalculationInterval;
item.ApiCredentials = ApiCredentials?.Copy();
item.Proxy = Proxy;
item.RequestTimeout = RequestTimeout;
item.RateLimiterEnabled = RateLimiterEnabled;
item.RateLimitingBehaviour = RateLimitingBehaviour;
item.CachingEnabled = CachingEnabled;
item.CachingMaxAge = CachingMaxAge;
return item;
}
}
public bool AutoTimestamp { get; set; }
/// <summary>
/// Options for a rest exchange client
/// How often the timestamp adjustment between client and server is recalculated. If you need a very small TimeSpan here you're probably better of syncing your server time more often
/// </summary>
/// <typeparam name="TEnvironment"></typeparam>
public class RestExchangeOptions<TEnvironment> : RestExchangeOptions where TEnvironment : TradeEnvironment
{
/// <summary>
/// Trade environment. Contains info about URL's to use to connect to the API. To swap environment select another environment for
/// the exchange's environment list or create a custom environment using either `[Exchange]Environment.CreateCustom()` or `[Exchange]Environment.[Environment]`, for example `KucoinEnvironment.TestNet` or `BinanceEnvironment.Live`
/// </summary>
#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
public TEnvironment Environment { get; set; }
#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
/// <summary>
/// Set the values of this options on the target options
/// </summary>
public new T Set<T>(T target) where T : RestExchangeOptions<TEnvironment>, new()
{
base.Set(target);
target.Environment = Environment;
return target;
}
}
public TimeSpan TimestampRecalculationInterval { get; set; } = TimeSpan.FromHours(1);
/// <summary>
/// Options for a rest exchange client
/// Whether caching is enabled. Caching will only be applied to GET http requests. The lifetime of cached results can be determined by the `CachingMaxAge` option
/// </summary>
/// <typeparam name="TEnvironment"></typeparam>
/// <typeparam name="TApiCredentials"></typeparam>
public class RestExchangeOptions<TEnvironment, TApiCredentials> : RestExchangeOptions<TEnvironment> where TEnvironment : TradeEnvironment where TApiCredentials : ApiCredentials
public bool CachingEnabled { get; set; }
/// <summary>
/// The max age of a cached entry, only used when the `CachingEnabled` options is set to true. When a cached entry is older than the max age it will be discarded and a new server request will be done
/// </summary>
public TimeSpan CachingMaxAge { get; set; } = TimeSpan.FromSeconds(5);
/// <summary>
/// Set the values of this options on the target options
/// </summary>
public T Set<T>(T item) where T : RestExchangeOptions, new()
{
/// <summary>
/// The api credentials used for signing requests to this API.
/// </summary>
public new TApiCredentials? ApiCredentials
{
get => (TApiCredentials?)base.ApiCredentials;
set => base.ApiCredentials = value;
}
item.OutputOriginalData = OutputOriginalData;
item.AutoTimestamp = AutoTimestamp;
item.TimestampRecalculationInterval = TimestampRecalculationInterval;
item.ApiCredentials = ApiCredentials?.Copy();
item.Proxy = Proxy;
item.RequestTimeout = RequestTimeout;
item.RateLimiterEnabled = RateLimiterEnabled;
item.RateLimitingBehaviour = RateLimitingBehaviour;
item.CachingEnabled = CachingEnabled;
item.CachingMaxAge = CachingMaxAge;
return item;
}
}
/// <summary>
/// Options for a rest exchange client
/// </summary>
/// <typeparam name="TEnvironment"></typeparam>
public class RestExchangeOptions<TEnvironment> : RestExchangeOptions where TEnvironment : TradeEnvironment
{
/// <summary>
/// Trade environment. Contains info about URL's to use to connect to the API. To swap environment select another environment for
/// the exchange's environment list or create a custom environment using either `[Exchange]Environment.CreateCustom()` or `[Exchange]Environment.[Environment]`, for example `KucoinEnvironment.TestNet` or `BinanceEnvironment.Live`
/// </summary>
#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
public TEnvironment Environment { get; set; }
#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
/// <summary>
/// Set the values of this options on the target options
/// </summary>
public new T Set<T>(T target) where T : RestExchangeOptions<TEnvironment>, new()
{
base.Set(target);
target.Environment = Environment;
return target;
}
}
/// <summary>
/// Options for a rest exchange client
/// </summary>
/// <typeparam name="TEnvironment"></typeparam>
/// <typeparam name="TApiCredentials"></typeparam>
public class RestExchangeOptions<TEnvironment, TApiCredentials> : RestExchangeOptions<TEnvironment> where TEnvironment : TradeEnvironment where TApiCredentials : ApiCredentials
{
/// <summary>
/// The api credentials used for signing requests to this API.
/// </summary>
public new TApiCredentials? ApiCredentials
{
get => (TApiCredentials?)base.ApiCredentials;
set => base.ApiCredentials = value;
}
}

Some files were not shown because too many files have changed in this diff Show More