1
0
mirror of https://github.com/JKorf/CryptoExchange.Net synced 2025-09-04 14:42:09 +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> <IsPackable>false</IsPackable>
</PropertyGroup> </PropertyGroup>
<ItemGroup>
<None Include="..\CryptoExchange.Net\.editorconfig" Link=".editorconfig" />
</ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1"></PackageReference> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1"></PackageReference>
<PackageReference Include="Moq" Version="4.20.72" /> <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 namespace System.Runtime.CompilerServices;
{
internal static class IsExternalInit { } 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> /// <summary>
/// Map a enum entry to string values /// Values mapping to the enum entry
/// </summary> /// </summary>
[AttributeUsage(AttributeTargets.Field)] public string[] Values { get; set; }
public class MapAttribute : Attribute
{
/// <summary>
/// Values mapping to the enum entry
/// </summary>
public string[] Values { get; set; }
/// <summary> /// <summary>
/// ctor /// ctor
/// </summary> /// </summary>
/// <param name="maps"></param> /// <param name="maps"></param>
public MapAttribute(params string[] maps) public MapAttribute(params string[] maps)
{ {
Values = maps; Values = maps;
}
} }
} }

View File

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

View File

@ -1,21 +1,20 @@
namespace CryptoExchange.Net.Authentication namespace CryptoExchange.Net.Authentication;
/// <summary>
/// Credentials type
/// </summary>
public enum ApiCredentialsType
{ {
/// <summary> /// <summary>
/// Credentials type /// Hmac keys credentials
/// </summary> /// </summary>
public enum ApiCredentialsType Hmac,
{ /// <summary>
/// <summary> /// Rsa keys credentials in xml format
/// Hmac keys credentials /// </summary>
/// </summary> RsaXml,
Hmac, /// <summary>
/// <summary> /// Rsa keys credentials in pem/base64 format. Only available for .NetStandard 2.1 and up, use xml format for lower.
/// Rsa keys credentials in xml format /// </summary>
/// </summary> RsaPem
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.Converters.SystemTextJson;
using CryptoExchange.Net.Interfaces; using CryptoExchange.Net.Interfaces;
using CryptoExchange.Net.Objects; using CryptoExchange.Net.Objects;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.Net.Http;
using System.Security.Cryptography; using System.Security.Cryptography;
using System.Text; 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> /// <summary>
/// Base class for authentication providers /// Provided credentials
/// </summary> /// </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> _credentials = credentials;
/// Provided credentials _sBytes = Encoding.UTF8.GetBytes(credentials.Secret);
/// </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);
}
} }
/// <inheritdoc /> /// <summary>
public abstract class AuthenticationProvider<TApiCredentials> : AuthenticationProvider where TApiCredentials : ApiCredentials /// Authenticate a request
{ /// </summary>
/// <inheritdoc /> /// <param name="apiClient">The Api client sending the request</param>
protected new TApiCredentials _credentials => (TApiCredentials)base._credentials; /// <param name="requestConfig">The request configuration</param>
public abstract void ProcessRequest(RestApiClient apiClient, RestRequestConfiguration requestConfig);
/// <summary> /// <summary>
/// ctor /// SHA256 sign the data and return the bytes
/// </summary> /// </summary>
/// <param name="credentials"></param> /// <param name="data"></param>
protected AuthenticationProvider(TApiCredentials credentials) : base(credentials) /// <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> /// <summary>
/// Output string type /// Hex string
/// </summary> /// </summary>
public enum SignOutputType Hex,
{ /// <summary>
/// <summary> /// Base64 string
/// Hex string /// </summary>
/// </summary> Base64
Hex,
/// <summary>
/// Base64 string
/// </summary>
Base64
}
} }

View File

@ -1,53 +1,52 @@
using System; using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Linq; 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>(); var cacheItem = new CacheItem(DateTime.UtcNow, value);
private readonly object _lock = new object(); _cache.AddOrUpdate(key, cacheItem, (key, val1) => cacheItem);
}
/// <summary> /// <summary>
/// Add a new cache entry. Will override an existing entry if it already exists /// Get a cached value
/// </summary> /// </summary>
/// <param name="key">The key identifier</param> /// <param name="key">The key identifier</param>
/// <param name="value">Cache value</param> /// <param name="maxAge">The max age of the cached entry</param>
public void Add(string key, object value) /// <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); CacheTime = cacheTime;
_cache.AddOrUpdate(key, cacheItem, (key, val1) => cacheItem); Value = 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)
{
CacheTime = cacheTime;
Value = value;
}
} }
} }
} }

View File

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

View File

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

View File

@ -3,24 +3,23 @@ using CryptoExchange.Net.Interfaces;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Logging.Abstractions;
namespace CryptoExchange.Net.Clients 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);
/// <summary> /// <summary>
/// ctor /// Base rest client
/// </summary> /// </summary>
/// <param name="loggerFactory">Logger factory</param> public abstract class BaseRestClient : BaseClient, IRestClient
/// <param name="name">The name of the API this client is for</param> {
protected BaseRestClient(ILoggerFactory? loggerFactory, string name) : base(loggerFactory, name) /// <inheritdoc />
{ public int TotalRequestsMade => ApiClients.OfType<RestApiClient>().Sum(s => s.TotalRequestsMade);
_logger = loggerFactory?.CreateLogger(name + ".RestClient") ?? NullLoggerFactory.Instance.CreateLogger(name);
} /// <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.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Xml.Linq;
using CryptoExchange.Net.Interfaces; using CryptoExchange.Net.Interfaces;
using CryptoExchange.Net.Logging.Extensions; using CryptoExchange.Net.Logging.Extensions;
using CryptoExchange.Net.Objects.Sockets; using CryptoExchange.Net.Objects.Sockets;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions; 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> /// <summary>
/// Base for socket client implementations /// If client is disposing
/// </summary> /// </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> /// <summary>
/// If client is disposing /// Unsubscribe an update subscription
/// </summary> /// </summary>
protected bool _disposing; /// <param name="subscriptionId">The id of the subscription to unsubscribe</param>
/// <returns></returns>
/// <inheritdoc /> public virtual async Task UnsubscribeAsync(int subscriptionId)
public int CurrentConnections => ApiClients.OfType<SocketApiClient>().Sum(c => c.CurrentConnections); {
/// <inheritdoc /> foreach (var socket in ApiClients.OfType<SocketApiClient>())
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)
{ {
_logger = loggerFactory?.CreateLogger(name + ".SocketClient") ?? NullLoggerFactory.Instance.CreateLogger(name); var result = await socket.UnsubscribeAsync(subscriptionId).ConfigureAwait(false);
} if (result)
break;
/// <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;
} }
} }
/// <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;
using System.Collections.Generic; 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> /// <summary>
/// Base crypto client /// Service provider
/// </summary> /// </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> /// <summary>
/// Service provider /// Try get a client by type for the service collection
/// </summary> /// </summary>
protected readonly IServiceProvider? _serviceProvider; /// <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> if (_serviceProvider == null)
/// ctor
/// </summary>
public CryptoBaseClient() { }
/// <summary>
/// ctor
/// </summary>
/// <param name="serviceProvider"></param>
public CryptoBaseClient(IServiceProvider serviceProvider)
{ {
_serviceProvider = serviceProvider; // Create with default options
_serviceCache = new Dictionary<Type, object>(); var createResult = createFunc();
_serviceCache.Add(typeof(T), createResult!);
return createResult;
} }
/// <summary> var result = _serviceProvider.GetService<T>()
/// Try get a client by type for the service collection ?? 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");
/// </summary> _serviceCache.Add(type, result!);
/// <typeparam name="T"></typeparam> return result;
/// <returns></returns> }
public T TryGet<T>(Func<T> createFunc)
{
var type = typeof(T);
if (_serviceCache.TryGetValue(type, out var value))
return (T)value;
if (_serviceProvider == null) /// <summary>
{ /// Dispose
// Create with default options /// </summary>
var createResult = createFunc(); public void Dispose()
_serviceCache.Add(typeof(T), createResult!); {
return createResult; _serviceCache.Clear();
}
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();
}
} }
} }

View File

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

View File

@ -1,24 +1,23 @@
using CryptoExchange.Net.Interfaces; using CryptoExchange.Net.Interfaces;
using System; using System;
namespace CryptoExchange.Net.Clients namespace CryptoExchange.Net.Clients;
{
/// <inheritdoc />
public class CryptoSocketClient : CryptoBaseClient, ICryptoSocketClient
{
/// <summary>
/// ctor
/// </summary>
public CryptoSocketClient()
{
}
/// <summary> /// <inheritdoc />
/// ctor public class CryptoSocketClient : CryptoBaseClient, ICryptoSocketClient
/// </summary> {
/// <param name="serviceProvider"></param> /// <summary>
public CryptoSocketClient(IServiceProvider serviceProvider) : base(serviceProvider) /// 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> /// <summary>
/// Mark property as an index in the array /// The index in the array
/// </summary> /// </summary>
[AttributeUsage(AttributeTargets.Property)] public int Index { get; }
public class ArrayPropertyAttribute : Attribute
{
/// <summary>
/// The index in the array
/// </summary>
public int Index { get; }
/// <summary> /// <summary>
/// ctor /// ctor
/// </summary> /// </summary>
/// <param name="index"></param> /// <param name="index"></param>
public ArrayPropertyAttribute(int index) public ArrayPropertyAttribute(int index)
{ {
Index = index; Index = index;
}
} }
} }

View File

@ -1,31 +1,28 @@
using System; using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Text;
using System.Text.Json.Serialization; 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> /// <summary>
/// Caching for JsonSerializerContext instances /// Get the instance of the provided type T. It will be created if it doesn't exist yet.
/// </summary> /// </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> var instance = new T();
/// Get the instance of the provided type T. It will be created if it doesn't exist yet. _cache[contextType] = instance;
/// </summary> return instance;
/// <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;
}
} }
} }

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> /// <summary>
/// Node accessor /// Index
/// </summary> /// </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 = index;
/// Index Property = property;
/// </summary> Type = type;
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); }
} }
/// <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; 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> private readonly List<NodeAccessor> _path;
/// Message access definition
/// </summary> internal void Add(NodeAccessor node)
public readonly struct MessagePath : IEnumerable<NodeAccessor>
{ {
private readonly List<NodeAccessor> _path; _path.Add(node);
}
internal void Add(NodeAccessor node) /// <summary>
{ /// ctor
_path.Add(node); /// </summary>
} public MessagePath()
{
_path = new List<NodeAccessor>();
}
/// <summary> /// <summary>
/// ctor /// Create a new message path
/// </summary> /// </summary>
public MessagePath() /// <returns></returns>
{ public static MessagePath Get()
_path = new List<NodeAccessor>(); {
} return new MessagePath();
}
/// <summary> /// <summary>
/// Create a new message path /// IEnumerable implementation
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
public static MessagePath Get() public IEnumerator<NodeAccessor> GetEnumerator()
{ {
return new MessagePath(); for (var i = 0; i < _path.Count; i++)
} yield return _path[i];
}
/// <summary> IEnumerator IEnumerable.GetEnumerator()
/// IEnumerable implementation {
/// </summary> return GetEnumerator();
/// <returns></returns>
public IEnumerator<NodeAccessor> GetEnumerator()
{
for (var i = 0; i < _path.Count; i++)
yield return _path[i];
}
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> /// <summary>
/// Message path extension methods /// Add a string node accessor
/// </summary> /// </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> path.Add(NodeAccessor.String(propName));
/// Add a string node accessor return path;
/// </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;
}
/// <summary> /// <summary>
/// Add a property name node accessor /// Add a property name node accessor
/// </summary> /// </summary>
/// <param name="path"></param> /// <param name="path"></param>
/// <returns></returns> /// <returns></returns>
public static MessagePath PropertyName(this MessagePath path) public static MessagePath PropertyName(this MessagePath path)
{ {
path.Add(NodeAccessor.PropertyName()); path.Add(NodeAccessor.PropertyName());
return path; return path;
} }
/// <summary> /// <summary>
/// Add a int node accessor /// Add a int node accessor
/// </summary> /// </summary>
/// <param name="path"></param> /// <param name="path"></param>
/// <param name="index"></param> /// <param name="index"></param>
/// <returns></returns> /// <returns></returns>
public static MessagePath Index(this MessagePath path, int index) public static MessagePath Index(this MessagePath path, int index)
{ {
path.Add(NodeAccessor.Int(index)); path.Add(NodeAccessor.Int(index));
return path; 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> /// <summary>
/// Message node type /// Array node
/// </summary> /// </summary>
public enum NodeType Array,
{ /// <summary>
/// <summary> /// Object node
/// Array node /// </summary>
/// </summary> Object,
Array, /// <summary>
/// <summary> /// Value node
/// Object node /// </summary>
/// </summary> Value
Object,
/// <summary>
/// Value node
/// </summary>
Value
}
} }

View File

@ -1,234 +1,232 @@
using System; using System;
using System.Collections.Concurrent;
using System.Globalization; using System.Globalization;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
using System.Text.Json; using System.Text.Json;
using CryptoExchange.Net.Attributes;
using System.Collections.Generic; using System.Collections.Generic;
#if NET5_0_OR_GREATER
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
#endif
using System.Threading; using System.Threading;
using System.Diagnostics;
namespace CryptoExchange.Net.Converters.SystemTextJson namespace CryptoExchange.Net.Converters.SystemTextJson;
{
/// <summary> /// <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 /// 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 /// with [ArrayProperty(x)] where x is the index of the property in the array
/// </summary> /// </summary>
#if NET5_0_OR_GREATER #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 #else
public class ArrayConverter<T> : JsonConverter<T> where T : new() public class ArrayConverter<T> : JsonConverter<T> where T : new()
#endif #endif
{ {
private static readonly Lazy<List<ArrayPropertyInfo>> _typePropertyInfo = new Lazy<List<ArrayPropertyInfo>>(CacheTypeAttributes, LazyThreadSafetyMode.PublicationOnly); private static readonly Lazy<List<ArrayPropertyInfo>> _typePropertyInfo = new Lazy<List<ArrayPropertyInfo>>(CacheTypeAttributes, LazyThreadSafetyMode.PublicationOnly);
/// <inheritdoc /> /// <inheritdoc />
#if NET5_0_OR_GREATER #if NET5_0_OR_GREATER
[UnconditionalSuppressMessage("AssemblyLoadTrimming", "IL3050: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")] [UnconditionalSuppressMessage("AssemblyLoadTrimming", "IL2026:RequiresUnreferencedCode", Justification = "JsonSerializerOptions provided here has TypeInfoResolver set")]
#endif #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(); 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 objValue = prop.PropertyInfo.GetValue(value);
var last = -1; if (objValue == null)
foreach (var prop in ordered)
{ {
if (prop.ArrayProperty.Index == last) writer.WriteNullValue();
continue; 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.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 /> writer.WriteEndArray();
public override T? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) }
{
if (reader.TokenType == JsonTokenType.Null)
return default;
var result = new T(); /// <inheritdoc />
return ParseObject(ref reader, result, options); 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 #if NET5_0_OR_GREATER
[UnconditionalSuppressMessage("AssemblyLoadTrimming", "IL3050: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")] [UnconditionalSuppressMessage("AssemblyLoadTrimming", "IL2026:RequiresUnreferencedCode", Justification = "JsonSerializerOptions provided here has TypeInfoResolver set")]
private static T ParseObject(ref Utf8JsonReader reader, T result, JsonSerializerOptions options) private static T ParseObject(ref Utf8JsonReader reader, T result, JsonSerializerOptions options)
#else #else
private static T ParseObject(ref Utf8JsonReader reader, T result, JsonSerializerOptions options) private static T ParseObject(ref Utf8JsonReader reader, T result, JsonSerializerOptions options)
#endif #endif
{
if (reader.TokenType != JsonTokenType.StartArray)
throw new Exception("Not an array");
int index = 0;
while (reader.Read())
{ {
if (reader.TokenType != JsonTokenType.StartArray) if (reader.TokenType == JsonTokenType.EndArray)
throw new Exception("Not an array"); break;
int index = 0; var indexAttributes = _typePropertyInfo.Value.Where(a => a.ArrayProperty.Index == index);
while (reader.Read()) if (!indexAttributes.Any())
{ {
if (reader.TokenType == JsonTokenType.EndArray) index++;
break; continue;
}
var indexAttributes = _typePropertyInfo.Value.Where(a => a.ArrayProperty.Index == index); foreach (var attribute in indexAttributes)
if (!indexAttributes.Any()) {
var targetType = attribute.TargetType;
object? value = null;
if (attribute.JsonConverter != null)
{ {
index++; if (attribute.JsonSerializerOptions == null)
continue;
}
foreach (var attribute in indexAttributes)
{
var targetType = attribute.TargetType;
object? value = null;
if (attribute.JsonConverter != null)
{ {
if (attribute.JsonSerializerOptions == null) attribute.JsonSerializerOptions = new JsonSerializerOptions
{ {
attribute.JsonSerializerOptions = new JsonSerializerOptions NumberHandling = JsonNumberHandling.AllowReadingFromString | JsonNumberHandling.AllowNamedFloatingPointLiterals,
{ PropertyNameCaseInsensitive = false,
NumberHandling = JsonNumberHandling.AllowReadingFromString | JsonNumberHandling.AllowNamedFloatingPointLiterals, Converters = { attribute.JsonConverter },
PropertyNameCaseInsensitive = false, TypeInfoResolver = options.TypeInfoResolver,
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"),
}; };
} }
if (targetType.IsAssignableFrom(value?.GetType())) var doc = JsonDocument.ParseValue(ref reader);
attribute.PropertyInfo.SetValue(result, value); value = doc.Deserialize(attribute.PropertyInfo.PropertyType, attribute.JsonSerializerOptions);
else }
attribute.PropertyInfo.SetValue(result, value == null ? null : Convert.ChangeType(value, targetType, CultureInfo.InvariantCulture)); 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]);
// 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);
} }
return type.IsPrimitive
|| type.IsEnum
|| type == typeof(string)
|| type == typeof(decimal);
}
#if NET5_0_OR_GREATER #if NET5_0_OR_GREATER
[UnconditionalSuppressMessage("AssemblyLoadTrimming", "IL3050: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")] [UnconditionalSuppressMessage("AssemblyLoadTrimming", "IL2026:RequiresUnreferencedCode", Justification = "JsonSerializerOptions provided here has TypeInfoResolver set")]
private static List<ArrayPropertyInfo> CacheTypeAttributes() private static List<ArrayPropertyInfo> CacheTypeAttributes()
#else #else
private static List<ArrayPropertyInfo> CacheTypeAttributes() private static List<ArrayPropertyInfo> CacheTypeAttributes()
#endif #endif
{
var attributes = new List<ArrayPropertyInfo>();
var properties = typeof(T).GetProperties();
foreach (var property in properties)
{ {
var attributes = new List<ArrayPropertyInfo>(); var att = property.GetCustomAttribute<ArrayPropertyAttribute>();
var properties = typeof(T).GetProperties(); if (att == null)
foreach (var property in properties) 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>(); ArrayProperty = att,
if (att == null) PropertyInfo = property,
continue; DefaultDeserialization = property.GetCustomAttribute<CryptoExchange.Net.Attributes.JsonConversionAttribute>() != null,
JsonConverter = converterType == null ? null : (JsonConverter)Activator.CreateInstance(converterType)!,
var targetType = Nullable.GetUnderlyingType(property.PropertyType) ?? property.PropertyType; TargetType = targetType
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;
} }
private class ArrayPropertyInfo return attributes;
{ }
public PropertyInfo PropertyInfo { get; set; } = null!;
public ArrayPropertyAttribute ArrayProperty { get; set; } = null!; private class ArrayPropertyInfo
public JsonConverter? JsonConverter { get; set; } {
public bool DefaultDeserialization { get; set; } public PropertyInfo PropertyInfo { get; set; } = null!;
public Type TargetType { get; set; } = null!; public ArrayPropertyAttribute ArrayProperty { get; set; } = null!;
public JsonSerializerOptions? JsonSerializerOptions { 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.Globalization;
using System.Text.Json; using System.Text.Json;
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
namespace CryptoExchange.Net.Converters.SystemTextJson 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;
}
}
/// <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 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; return decimal.MaxValue;
} }
} }
/// <inheritdoc /> try
public override void Write(Utf8JsonWriter writer, decimal value, JsonSerializerOptions options)
{ {
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.Diagnostics;
using System.Runtime.Serialization; using System.Runtime.Serialization;
using System.Text.Json; using System.Text.Json;
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
namespace CryptoExchange.Net.Converters.SystemTextJson namespace CryptoExchange.Net.Converters.SystemTextJson;
/// <summary>
/// Bool converter
/// </summary>
public class BoolConverter : JsonConverterFactory
{ {
/// <summary> /// <inheritdoc />
/// Bool converter public override bool CanConvert(Type typeToConvert)
/// </summary>
public class BoolConverter : JsonConverterFactory
{ {
/// <inheritdoc /> return typeToConvert == typeof(bool) || typeToConvert == typeof(bool?);
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();
}
}
} }
/// <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;
using System.Collections.Generic; #if NET5_0_OR_GREATER
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
#endif
using System.Linq; using System.Linq;
using System.Text;
using System.Text.Json; using System.Text.Json;
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
namespace CryptoExchange.Net.Converters.SystemTextJson namespace CryptoExchange.Net.Converters.SystemTextJson;
{
/// <summary> /// <summary>
/// Converter for comma separated enum values /// Converter for comma separated enum values
/// </summary> /// </summary>
#if NET5_0_OR_GREATER #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 #else
public class CommaSplitEnumConverter<T> : JsonConverter<T[]> where T : struct, Enum public class CommaSplitEnumConverter<T> : JsonConverter<T[]> where T : struct, Enum
#endif #endif
{
/// <inheritdoc />
public override T[]? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{ {
/// <inheritdoc /> var str = reader.GetString();
public override T[]? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) 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 /> /// <inheritdoc />
public override void Write(Utf8JsonWriter writer, T[] value, JsonSerializerOptions options) public override void Write(Utf8JsonWriter writer, T[] value, JsonSerializerOptions options)
{ {
writer.WriteStringValue(string.Join(",", value.Select(x => EnumConverter.GetString(x)))); 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;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Globalization; using System.Globalization;
using System.Text.Json; using System.Text.Json;
using System.Text.Json.Serialization; 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> private static readonly DateTime _epoch = new(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
/// Date time converter private const long _ticksPerSecond = TimeSpan.TicksPerMillisecond * 1000;
/// </summary> private const double _ticksPerMicrosecond = TimeSpan.TicksPerMillisecond / 1000d;
public class DateTimeConverter : JsonConverterFactory 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); return typeToConvert == typeof(DateTime) || typeToConvert == typeof(DateTime?);
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);
} }
/// <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;
using System.Globalization;
using System.Text.Json; using System.Text.Json;
using System.Text.Json.Serialization; 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> /// <inheritdoc />
/// Decimal converter public override decimal? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
/// </summary>
public class DecimalConverter : JsonConverter<decimal?>
{ {
/// <inheritdoc /> if (reader.TokenType == JsonTokenType.Null)
public override decimal? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) return null;
if (reader.TokenType == JsonTokenType.String)
{ {
if (reader.TokenType == JsonTokenType.Null) var value = reader.GetString();
return null; return ExchangeHelpers.ParseDecimal(value);
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;
}
} }
/// <inheritdoc /> try
public override void Write(Utf8JsonWriter writer, decimal? value, JsonSerializerOptions options)
{ {
if (value == null) return reader.GetDecimal();
writer.WriteNullValue(); }
else catch(FormatException)
writer.WriteNumberValue(value.Value); {
// 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.Globalization;
using System.Text.Json; using System.Text.Json;
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
namespace CryptoExchange.Net.Converters.SystemTextJson 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();
}
/// <inheritdoc /> /// <summary>
public override void Write(Utf8JsonWriter writer, decimal value, JsonSerializerOptions options) /// Converter for serializing decimal values as string
=> writer.WriteStringValue(value.ToString(CultureInfo.InvariantCulture) ?? null); /// </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;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
@ -9,281 +9,280 @@ using System.Reflection;
using System.Text.Json; using System.Text.Json;
using System.Text.Json.Serialization; 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> /// <summary>
/// Static EnumConverter methods /// Get the enum value from a string
/// </summary> /// </summary>
public static class EnumConverter /// <param name="value">String value</param>
{ /// <returns></returns>
/// <summary>
/// Get the enum value from a string
/// </summary>
/// <param name="value">String value</param>
/// <returns></returns>
#if NET5_0_OR_GREATER #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 #else
public static T? ParseString<T>(string value) where T : struct, Enum public static T? ParseString<T>(string value) where T : struct, Enum
#endif #endif
=> EnumConverter<T>.ParseString(value); => 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);
}
/// <summary> /// <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> /// </summary>
/// <param name="enumValue"></param>
/// <returns></returns>
#if NET5_0_OR_GREATER #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 #else
public class EnumConverter<T> public static string GetString<T>(T enumValue) where T : struct, Enum
#endif #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 readonly EnumConverter<T> _enumConverter;
private NullableEnumConverter? _nullableEnumConverter = null;
private static ConcurrentBag<string> _unknownValuesWarned = new ConcurrentBag<string>(); public NullableEnumConverter(EnumConverter<T> enumConverter)
internal class NullableEnumConverter : JsonConverter<T?>
{ {
private readonly EnumConverter<T> _enumConverter; _enumConverter = enumConverter;
}
public NullableEnumConverter(EnumConverter<T> enumConverter) public override T? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{ {
_enumConverter = enumConverter; return EnumConverter<T>.ReadNullable(ref reader, typeToConvert, options, out var isEmptyString, out var warn);
}
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);
}
}
} }
/// <inheritdoc /> public override void Write(Utf8JsonWriter writer, T? value, JsonSerializerOptions options)
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 (value == null)
if (t == null)
{ {
if (warn) writer.WriteNullValue();
{
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
} }
else 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; if (warn)
warn = false;
var enumType = typeof(T);
if (_mapping == null)
_mapping = AddMapping();
var stringValue = reader.TokenType switch
{ {
JsonTokenType.String => reader.GetString(), if (isEmptyString)
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))
{ {
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 else
{ {
// We received an enum value but weren't able to parse it. 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");
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");
}
} }
return null;
} }
return result; return new T(); // return default value
} }
else
/// <inheritdoc />
public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options)
{ {
var stringValue = GetString(value); return t.Value;
writer.WriteStringValue(stringValue);
} }
}
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 isEmptyString = true;
var mapping = _mapping.FirstOrDefault(kv => kv.Value.Equals(value, StringComparison.InvariantCulture)); }
if (mapping.Equals(default(KeyValuePair<T, string>))) else
mapping = _mapping.FirstOrDefault(kv => kv.Value.Equals(value, StringComparison.InvariantCultureIgnoreCase)); {
// We received an enum value but weren't able to parse it.
if (!mapping.Equals(default(KeyValuePair<T, string>))) if (!_unknownValuesWarned.Contains(stringValue))
{ {
result = mapping.Key; warn = true;
return 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))) return null;
{
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() 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>>(); // Check for exact match first, then if not found fallback to a case insensitive match
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)); var mapping = _mapping.FirstOrDefault(kv => kv.Value.Equals(value, StringComparison.InvariantCulture));
if (mapping.Equals(default(KeyValuePair<T, string>))) if (mapping.Equals(default(KeyValuePair<T, string>)))
mapping = _mapping.FirstOrDefault(kv => kv.Value.Equals(value, StringComparison.InvariantCultureIgnoreCase)); mapping = _mapping.FirstOrDefault(kv => kv.Value.Equals(value, StringComparison.InvariantCultureIgnoreCase));
if (!mapping.Equals(default(KeyValuePair<T, string>))) if (!mapping.Equals(default(KeyValuePair<T, string>)))
return mapping.Key;
try
{ {
// If no explicit mapping is found try to parse string result = mapping.Key;
return (T)Enum.Parse(type, value, true); return true;
}
catch (Exception)
{
return default;
} }
} }
/// <inheritdoc /> if (objectType.IsDefined(typeof(FlagsAttribute)))
public JsonConverter CreateNullableConverter()
{ {
_nullableEnumConverter ??= new NullableEnumConverter(this); var intValue = int.Parse(value);
return _nullableEnumConverter; 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;
using System.Globalization;
using System.Text.Json; using System.Text.Json;
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
namespace CryptoExchange.Net.Converters.SystemTextJson 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();
}
/// <inheritdoc /> /// <summary>
public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options) /// Converter for serializing enum values as int
=> writer.WriteNumberValue((int)(object)value); /// </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.Globalization;
using System.Text.Json; using System.Text.Json;
using System.Text.Json.Serialization; 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> /// <inheritdoc />
/// Int converter public override int? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
/// </summary>
public class IntConverter : JsonConverter<int?>
{ {
/// <inheritdoc /> if (reader.TokenType == JsonTokenType.Null)
public override int? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) return null;
if (reader.TokenType == JsonTokenType.String)
{ {
if (reader.TokenType == JsonTokenType.Null) var value = reader.GetString();
if (string.IsNullOrEmpty(value))
return null; return null;
if (reader.TokenType == JsonTokenType.String) return int.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture);
{
var value = reader.GetString();
if (string.IsNullOrEmpty(value))
return null;
return int.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture);
}
return reader.GetInt32();
} }
/// <inheritdoc /> return reader.GetInt32();
public override void Write(Utf8JsonWriter writer, int? value, JsonSerializerOptions options) }
{
if (value == null) /// <inheritdoc />
writer.WriteNullValue(); public override void Write(Utf8JsonWriter writer, int? value, JsonSerializerOptions options)
else {
writer.WriteNumberValue(value.Value); 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.Globalization;
using System.Text.Json; using System.Text.Json;
using System.Text.Json.Serialization; 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> /// <inheritdoc />
/// Int converter public override long? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
/// </summary>
public class LongConverter : JsonConverter<long?>
{ {
/// <inheritdoc /> if (reader.TokenType == JsonTokenType.Null)
public override long? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) return null;
if (reader.TokenType == JsonTokenType.String)
{ {
if (reader.TokenType == JsonTokenType.Null) var value = reader.GetString();
if (string.IsNullOrEmpty(value))
return null; return null;
if (reader.TokenType == JsonTokenType.String) return long.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture);
{
var value = reader.GetString();
if (string.IsNullOrEmpty(value))
return null;
return long.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture);
}
return reader.GetInt64();
} }
/// <inheritdoc /> return reader.GetInt64();
public override void Write(Utf8JsonWriter writer, long? value, JsonSerializerOptions options) }
{
if (value == null) /// <inheritdoc />
writer.WriteNullValue(); public override void Write(Utf8JsonWriter writer, long? value, JsonSerializerOptions options)
else {
writer.WriteNumberValue(value.Value); if (value == null)
} writer.WriteNullValue();
else
writer.WriteNumberValue(value.Value);
} }
} }

View File

@ -1,43 +1,40 @@
using System; using System;
using System.Collections.Generic;
using System.Text;
using System.Text.Json.Serialization.Metadata; using System.Text.Json.Serialization.Metadata;
using System.Text.Json; using System.Text.Json;
using System.Text.Json.Serialization; 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; _jsonTypeInfoResolver = jsonTypeInfoResolver;
private static readonly JsonSerializerOptions _options = new JsonSerializerOptions(); }
public NullableEnumConverterFactory(IJsonTypeInfoResolver jsonTypeInfoResolver) public override bool CanConvert(Type typeToConvert)
{ {
_jsonTypeInfoResolver = jsonTypeInfoResolver; var b = Nullable.GetUnderlyingType(typeToConvert);
} if (b == null)
return false;
public override bool CanConvert(Type typeToConvert) var typeInfo = _jsonTypeInfoResolver.GetTypeInfo(b, _options);
{ if (typeInfo == null)
var b = Nullable.GetUnderlyingType(typeToConvert); return false;
if (b == null)
return false;
var typeInfo = _jsonTypeInfoResolver.GetTypeInfo(b, _options); return typeInfo.Converter is INullableConverterFactory;
if (typeInfo == null) }
return false;
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}");
public override JsonConverter? CreateConverter(Type typeToConvert, JsonSerializerOptions options) var typeInfo = _jsonTypeInfoResolver.GetTypeInfo(b, _options) ?? throw new ArgumentNullException($"Can find type {typeToConvert.Name}");
{ if (typeInfo.Converter is not INullableConverterFactory nullConverterFactory)
var b = Nullable.GetUnderlyingType(typeToConvert) ?? throw new ArgumentNullException($"Not nullable {typeToConvert.Name}"); throw new ArgumentNullException($"Can find type converter for {typeToConvert.Name}");
var typeInfo = _jsonTypeInfoResolver.GetTypeInfo(b, _options) ?? throw new ArgumentNullException($"Can find type {typeToConvert.Name}");
if (typeInfo.Converter is not INullableConverterFactory nullConverterFactory) return nullConverterFactory.CreateNullableConverter();
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;
using System.Text.Json.Serialization; 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> /// <inheritdoc />
/// Read string or number as string public override string? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
/// </summary>
public class NumberStringConverter : JsonConverter<string?>
{ {
/// <inheritdoc /> if (reader.TokenType == JsonTokenType.Null)
public override string? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) return null;
if (reader.TokenType == JsonTokenType.Number)
{ {
if (reader.TokenType == JsonTokenType.Null) if (reader.TryGetInt64(out var value))
return null; return value.ToString();
if (reader.TokenType == JsonTokenType.Number) return reader.GetDecimal().ToString();
{
if (reader.TryGetInt64(out var value))
return value.ToString();
return reader.GetDecimal().ToString();
}
try
{
return reader.GetString();
}
catch (Exception)
{
return null;
}
} }
/// <inheritdoc /> try
public override void Write(Utf8JsonWriter writer, string? value, JsonSerializerOptions options)
{ {
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.Serialization;
using System.Text.Json; using System.Text.Json;
#if NET5_0_OR_GREATER
using System.Diagnostics.CodeAnalysis; 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> /// <inheritdoc />
/// Converter for values which contain a nested json value #if NET5_0_OR_GREATER
/// </summary> [UnconditionalSuppressMessage("AssemblyLoadTrimming", "IL3050:RequiresUnreferencedCode", Justification = "JsonSerializerOptions provided here has TypeInfoResolver set")]
public class ObjectStringConverter<T> : JsonConverter<T> [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 #if NET5_0_OR_GREATER
[UnconditionalSuppressMessage("AssemblyLoadTrimming", "IL3050: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")] [UnconditionalSuppressMessage("AssemblyLoadTrimming", "IL2026:RequiresUnreferencedCode", Justification = "JsonSerializerOptions provided here has TypeInfoResolver set")]
#endif #endif
public override T? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) public override void Write(Utf8JsonWriter writer, T? value, JsonSerializerOptions options)
{ {
if (reader.TokenType == JsonTokenType.Null) if (value is null)
return default; writer.WriteStringValue("");
var value = reader.GetString(); writer.WriteStringValue(JsonSerializer.Serialize(value, options));
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));
}
} }
} }

View File

@ -1,41 +1,40 @@
using System; using System;
using System.Linq; using System.Linq;
using System.Text.Json; using System.Text.Json;
using System.Text.Json.Serialization; 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> /// <summary>
/// Replace a value on a string property /// ctor
/// </summary> /// </summary>
public abstract class ReplaceConverter : JsonConverter<string> public ReplaceConverter(params string[] replaceSets)
{ {
private readonly (string ValueToReplace, string ValueToReplaceWith)[] _replacementSets; _replacementSets = replaceSets.Select(x =>
/// <summary>
/// ctor
/// </summary>
public ReplaceConverter(params string[] replaceSets)
{ {
_replacementSets = replaceSets.Select(x => var split = x.Split(["->"], StringSplitOptions.None);
{ if (split.Length != 2)
var split = x.Split(new string[] { "->" }, StringSplitOptions.None); throw new ArgumentException("Invalid replacement config");
if (split.Length != 2) return (split[0], split[1]);
throw new ArgumentException("Invalid replacement config"); }).ToArray();
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);
} }
/// <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;
using System.Collections.Generic;
using System.Text;
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> /// <summary>
/// Attribute to mark a model as json serializable. Used for AOT compilation. /// ctor
/// </summary> /// </summary>
[AttributeUsage(System.AttributeTargets.Class | AttributeTargets.Enum | System.AttributeTargets.Interface)] public SerializationModelAttribute() { }
public class SerializationModelAttribute : Attribute /// <summary>
{ /// ctor
/// <summary> /// </summary>
/// ctor /// <param name="type"></param>
/// </summary> 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;
using System.Text.Json.Serialization; 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> /// <summary>
/// Serializer options /// Get Json serializer settings which includes standard converters for DateTime, bool, enum and number types
/// </summary> /// </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>(); if (!_cache.TryGetValue(typeResolver, out var options))
/// <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)) options = new JsonSerializerOptions
{ {
options = new JsonSerializerOptions NumberHandling = JsonNumberHandling.AllowReadingFromString | JsonNumberHandling.AllowNamedFloatingPointLiterals,
PropertyNameCaseInsensitive = false,
Converters =
{ {
NumberHandling = JsonNumberHandling.AllowReadingFromString | JsonNumberHandling.AllowNamedFloatingPointLiterals, new DateTimeConverter(),
PropertyNameCaseInsensitive = false, new BoolConverter(),
Converters = new DecimalConverter(),
{ new IntConverter(),
new DateTimeConverter(), new LongConverter(),
new BoolConverter(), new NullableEnumConverterFactory(typeResolver)
new DecimalConverter(), },
new IntConverter(), TypeInfoResolver = typeResolver,
new LongConverter(), };
new NullableEnumConverterFactory(typeResolver)
},
TypeInfoResolver = typeResolver,
};
foreach (var converter in additionalConverters) foreach (var converter in additionalConverters)
options.Converters.Add(converter); options.Converters.Add(converter);
options.TypeInfoResolver = typeResolver; options.TypeInfoResolver = typeResolver;
_cache.TryAdd(typeResolver, options); _cache.TryAdd(typeResolver, options);
}
return options;
} }
return options;
} }
} }

View File

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

View File

@ -1,46 +1,43 @@
using CryptoExchange.Net.SharedApis; using CryptoExchange.Net.SharedApis;
using System; using System;
using System.Collections.Generic;
using System.Text;
using System.Text.Json; using System.Text.Json;
using System.Text.Json.Serialization; 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 reader.Read(); // Start array
var tradingMode = (TradingMode)Enum.Parse(typeof(TradingMode), reader.GetString()!); var tradingMode = (TradingMode)Enum.Parse(typeof(TradingMode), reader.GetString()!);
reader.Read(); reader.Read();
var baseAsset = reader.GetString()!; var baseAsset = reader.GetString()!;
reader.Read(); reader.Read();
var quoteAsset = reader.GetString()!; var quoteAsset = reader.GetString()!;
reader.Read(); reader.Read();
var timeStr = reader.GetString()!; var timeStr = reader.GetString()!;
var deliverTime = string.IsNullOrEmpty(timeStr) ? (DateTime?)null : DateTime.Parse(timeStr); var deliverTime = string.IsNullOrEmpty(timeStr) ? (DateTime?)null : DateTime.Parse(timeStr);
reader.Read(); reader.Read();
if (reader.TokenType != JsonTokenType.EndArray) if (reader.TokenType != JsonTokenType.EndArray)
throw new Exception(""); 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) public override void Write(Utf8JsonWriter writer, SharedSymbol value, JsonSerializerOptions options)
{ {
writer.WriteStartArray(); writer.WriteStartArray();
writer.WriteStringValue(value.TradingMode.ToString()); writer.WriteStringValue(value.TradingMode.ToString());
writer.WriteStringValue(value.BaseAsset); writer.WriteStringValue(value.BaseAsset);
writer.WriteStringValue(value.QuoteAsset); writer.WriteStringValue(value.QuoteAsset);
writer.WriteStringValue(value.DeliverTime?.ToString()); writer.WriteStringValue(value.DeliverTime?.ToString());
writer.WriteEndArray(); 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.Interfaces;
using CryptoExchange.Net.Objects; using CryptoExchange.Net.Objects;
using System; using System;
using System.Collections.Generic; #if NET5_0_OR_GREATER
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
#endif
using System.IO; using System.IO;
using System.Text; using System.Text;
using System.Text.Json; using System.Text.Json;
using System.Threading.Tasks; 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> /// <summary>
/// System.Text.Json message accessor /// The JsonDocument loaded
/// </summary> /// </summary>
public abstract class SystemTextJsonMessageAccessor : IMessageAccessor protected JsonDocument? _document;
{
/// <summary>
/// The JsonDocument loaded
/// </summary>
protected JsonDocument? _document;
private readonly JsonSerializerOptions? _customSerializerOptions; private readonly JsonSerializerOptions? _customSerializerOptions;
/// <inheritdoc /> /// <inheritdoc />
public bool IsValid { get; set; } public bool IsValid { get; set; }
/// <inheritdoc /> /// <inheritdoc />
public abstract bool OriginalDataAvailable { get; } public abstract bool OriginalDataAvailable { get; }
/// <inheritdoc /> /// <inheritdoc />
public object? Underlying => throw new NotImplementedException(); 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();
}
/// <summary> /// <summary>
/// System.Text.Json stream message accessor /// ctor
/// </summary> /// </summary>
public class SystemTextJsonStreamMessageAccessor : SystemTextJsonMessageAccessor, IStreamMessageAccessor public SystemTextJsonMessageAccessor(JsonSerializerOptions options)
{ {
private Stream? _stream; _customSerializerOptions = options;
}
/// <inheritdoc /> /// <inheritdoc />
public override bool OriginalDataAvailable => _stream?.CanSeek == true; #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> if (_document == null)
/// ctor throw new InvalidOperationException("No json document loaded");
/// </summary>
public SystemTextJsonStreamMessageAccessor(JsonSerializerOptions options): base(options) 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 /> if (typeof(T) == typeof(string))
public async Task<CallResult> Read(Stream stream, bool bufferStream)
{ {
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 // Int value
_stream = new MemoryStream(); var val = node.Index!.Value;
stream.CopyTo(_stream); if (currentToken!.Value.ValueKind != JsonValueKind.Array || currentToken.Value.GetArrayLength() <= val)
_stream.Position = 0; 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 // String value
_stream = stream; if (currentToken!.Value.ValueKind != JsonValueKind.Object)
return null;
if (!currentToken.Value.TryGetProperty(node.Property!, out var token))
return null;
currentToken = token;
} }
else 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 if (currentToken == null)
{ return null;
_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;
} }
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> /// <summary>
/// System.Text.Json byte message accessor /// ctor
/// </summary> /// </summary>
public class SystemTextJsonByteMessageAccessor : SystemTextJsonMessageAccessor, IByteMessageAccessor public SystemTextJsonStreamMessageAccessor(JsonSerializerOptions options): base(options)
{ {
private ReadOnlyMemory<byte> _bytes; }
/// <summary> /// <inheritdoc />
/// ctor public async Task<CallResult> Read(Stream stream, bool bufferStream)
/// </summary> {
public SystemTextJsonByteMessageAccessor(JsonSerializerOptions options) : base(options) 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 /> try
public CallResult Read(ReadOnlyMemory<byte> data)
{ {
_bytes = data; _document = await JsonDocument.ParseAsync(_stream ?? stream).ConfigureAwait(false);
IsValid = true;
try return CallResult.SuccessResult;
{
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));
}
} }
catch (Exception 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; // Not a json message
_document?.Dispose(); IsValid = false;
_document = null; 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 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 />
#if NET5_0_OR_GREATER #if NET5_0_OR_GREATER
[UnconditionalSuppressMessage("AssemblyLoadTrimming", "IL2026:RequiresUnreferencedCode", Justification = "Everything referenced in the loaded assembly is manually preserved, so it's safe")] using System.Diagnostics.CodeAnalysis;
[UnconditionalSuppressMessage("AssemblyLoadTrimming", "IL3050:RequiresUnreferencedCode", Justification = "Everything referenced in the loaded assembly is manually preserved, so it's safe")]
#endif #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> <PackageLicenseExpression>MIT</PackageLicenseExpression>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<None Include="C:\Projects\CryptoExchange.Net\CryptoExchange.Net\.editorconfig" />
<None Include="Icon\icon.png" Pack="true" PackagePath="\" /> <None Include="Icon\icon.png" Pack="true" PackagePath="\" />
<None Include="..\README.md" Pack="true" PackagePath="\" /> <None Include="..\README.md" Pack="true" PackagePath="\" />
</ItemGroup> </ItemGroup>
@ -40,6 +41,12 @@
<PropertyGroup> <PropertyGroup>
<DocumentationFile>CryptoExchange.Net.xml</DocumentationFile> <DocumentationFile>CryptoExchange.Net.xml</DocumentationFile>
</PropertyGroup> </PropertyGroup>
<PropertyGroup>
<EnableNETAnalyzers>true</EnableNETAnalyzers>
<AnalysisMode>Recommended</AnalysisMode>
<AnalysisModeGlobalization>None</AnalysisModeGlobalization>
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
</PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="ConfigureAwaitChecker.Analyzer" Version="5.0.0.1"> <PackageReference Include="ConfigureAwaitChecker.Analyzer" Version="5.0.0.1">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
@ -58,4 +65,7 @@
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="9.0.6" /> <PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="9.0.6" />
<PackageReference Include="Microsoft.Extensions.Http" Version="9.0.6" /> <PackageReference Include="Microsoft.Extensions.Http" Version="9.0.6" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<EditorConfigFiles Remove="C:\Projects\CryptoExchange.Net\CryptoExchange.Net\.editorconfig" />
</ItemGroup>
</Project> </Project>

View File

@ -1,389 +1,390 @@
using CryptoExchange.Net.Objects; using CryptoExchange.Net.Objects;
using CryptoExchange.Net.SharedApis; using CryptoExchange.Net.SharedApis;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER
using System.Security.Cryptography; using System.Security.Cryptography;
#endif
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace CryptoExchange.Net namespace CryptoExchange.Net;
/// <summary>
/// General helpers functions
/// </summary>
public static class ExchangeHelpers
{ {
/// <summary> private const string _allowedRandomChars = "ABCDEFGHIJKLMONOPQRSTUVWXYZabcdefghijklmonopqrstuvwxyz0123456789";
/// General helpers functions private const string _allowedRandomHexChars = "0123456789ABCDEF";
/// </summary>
public static class ExchangeHelpers private static readonly Dictionary<int, string> _monthSymbols = new Dictionary<int, string>()
{ {
private const string _allowedRandomChars = "ABCDEFGHIJKLMONOPQRSTUVWXYZabcdefghijklmonopqrstuvwxyz0123456789"; { 1, "F" },
private const string _allowedRandomHexChars = "0123456789ABCDEF"; { 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>() /// <summary>
{ /// The last used id, use NextId() to get the next id and up this
{ 1, "F" }, /// </summary>
{ 2, "G" }, private static int _lastId;
{ 3, "H" },
{ 4, "J" },
{ 5, "K" },
{ 6, "M" },
{ 7, "N" },
{ 8, "Q" },
{ 9, "U" },
{ 10, "V" },
{ 11, "X" },
{ 12, "Z" },
};
/// <summary> /// <summary>
/// The last used id, use NextId() to get the next id and up this /// Clamp a value between a min and max
/// </summary> /// </summary>
private static int _lastId; /// <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> /// <summary>
/// Clamp a value between a min and max /// Adjust a value to be between the min and max parameters and rounded to the closest step.
/// </summary> /// </summary>
/// <param name="min"></param> /// <param name="min">The min value</param>
/// <param name="max"></param> /// <param name="max">The max value</param>
/// <param name="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>
/// <returns></returns> /// <param name="roundingType">How to round</param>
public static decimal ClampValue(decimal min, decimal max, decimal value) /// <param name="value">The input value</param>
{ /// <returns></returns>
value = Math.Min(max, value); public static decimal AdjustValueStep(decimal min, decimal max, decimal? step, RoundingType roundingType, decimal value)
value = Math.Max(min, 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; return value;
}
/// <summary> var offset = value % step.Value;
/// Adjust a value to be between the min and max parameters and rounded to the closest step. if(roundingType == RoundingType.Down)
/// </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) value -= offset;
throw new ArgumentException($"0 not allowed for parameter {nameof(step)}, pass in null to ignore the step size", nameof(step)); }
else if(roundingType == RoundingType.Up)
value = Math.Min(max, value); {
value = Math.Max(min, value); if (offset != 0)
if (step == null) value += (step.Value - offset);
return value; }
else
var offset = value % step.Value; {
if(roundingType == RoundingType.Down) if (offset < step / 2)
{
value -= offset; value -= offset;
} else value += (step.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();
} }
/// <summary> value = RoundDown(value, 8);
/// Adjust a value to be between the min and max parameters and rounded to the closest precision.
/// </summary> return value.Normalize();
/// <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);
/// <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 value;
}
/// <summary> return RoundToSignificantDigits(value, precision.Value, roundingType);
/// 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> /// <summary>
/// <param name="digits">The total amount of digits (NOT decimal places) to round to</param> /// Apply the provided rules to the value
/// <param name="roundingType">How to round</param> /// </summary>
/// <returns></returns> /// <param name="value">Value to be adjusted</param>
public static decimal RoundToSignificantDigits(decimal value, int digits, RoundingType roundingType) /// <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; var offset = value % valueStep.Value;
if (value == 0) if (offset != 0)
return 0; {
if (offset < valueStep.Value / 2)
double scale = Math.Pow(10, Math.Floor(Math.Log10(Math.Abs(val))) + 1); value -= offset;
if(roundingType == RoundingType.Closest) else value += (valueStep.Value - offset);
return (decimal)(scale * Math.Round(val / scale, digits)); }
else
return (decimal)(scale * (double)RoundDown((decimal)(val / scale), digits));
} }
if (decimals.HasValue)
value = Math.Round(value, decimals.Value);
/// <summary> return value;
/// 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> /// <summary>
/// Rounds a value up /// Round a value to have the provided total number of digits. For example, value 253.12332 with 5 digits would be 253.12
/// </summary> /// </summary>
public static decimal RoundUp(decimal i, double decimalPlaces) /// <param name="value">The value to round</param>
{ /// <param name="digits">The total amount of digits (NOT decimal places) to round to</param>
var power = Convert.ToDecimal(Math.Pow(10, decimalPlaces)); /// <param name="roundingType">How to round</param>
return Math.Ceiling(i * power) / power; /// <returns></returns>
} public static decimal RoundToSignificantDigits(decimal value, int digits, RoundingType roundingType)
{
var val = (double)value;
if (value == 0)
return 0;
/// <summary> double scale = Math.Pow(10, Math.Floor(Math.Log10(Math.Abs(val))) + 1);
/// Strips any trailing zero's of a decimal value, useful when converting the value to string. if(roundingType == RoundingType.Closest)
/// </summary> return (decimal)(scale * Math.Round(val / scale, digits));
/// <param name="value"></param> else
/// <returns></returns> return (decimal)(scale * (double)RoundDown((decimal)(val / scale), digits));
public static decimal Normalize(this decimal value) }
{
return value / 1.000000000000000000000000000000000m;
}
/// <summary> /// <summary>
/// Generate a new unique id. The id is statically stored so it is guaranteed to be unique /// Rounds a value down
/// </summary> /// </summary>
/// <returns></returns> public static decimal RoundDown(decimal i, double decimalPlaces)
public static int NextId() => Interlocked.Increment(ref _lastId); {
var power = Convert.ToDecimal(Math.Pow(10, decimalPlaces));
return Math.Floor(i * power) / power;
}
/// <summary> /// <summary>
/// Return the last unique id that was generated /// Rounds a value up
/// </summary> /// </summary>
/// <returns></returns> public static decimal RoundUp(decimal i, double decimalPlaces)
public static int LastId() => _lastId; {
var power = Convert.ToDecimal(Math.Pow(10, decimalPlaces));
return Math.Ceiling(i * power) / power;
}
/// <summary> /// <summary>
/// Generate a random string of specified length /// Strips any trailing zero's of a decimal value, useful when converting the value to string.
/// </summary> /// </summary>
/// <param name="length">Length of the random string</param> /// <param name="value"></param>
/// <returns></returns> /// <returns></returns>
public static string RandomString(int length) public static decimal Normalize(this decimal value)
{ {
var randomChars = new char[length]; 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 #if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER
for (int i = 0; i < length; i++) for (int i = 0; i < length; i++)
randomChars[i] = _allowedRandomChars[RandomNumberGenerator.GetInt32(0, _allowedRandomChars.Length)]; randomChars[i] = _allowedRandomChars[RandomNumberGenerator.GetInt32(0, _allowedRandomChars.Length)];
#else #else
var random = new Random(); var random = new Random();
for (int i = 0; i < length; i++) for (int i = 0; i < length; i++)
randomChars[i] = _allowedRandomChars[random.Next(0, _allowedRandomChars.Length)]; randomChars[i] = _allowedRandomChars[random.Next(0, _allowedRandomChars.Length)];
#endif #endif
return new string(randomChars); return new string(randomChars);
} }
/// <summary> /// <summary>
/// Generate a random string of specified length /// Generate a random string of specified length
/// </summary> /// </summary>
/// <param name="length">Length of the random string</param> /// <param name="length">Length of the random string</param>
/// <returns></returns> /// <returns></returns>
public static string RandomHexString(int length) public static string RandomHexString(int length)
{ {
#if NET9_0_OR_GREATER #if NET9_0_OR_GREATER
return "0x" + RandomNumberGenerator.GetHexString(length * 2); return "0x" + RandomNumberGenerator.GetHexString(length * 2);
#else #else
var randomChars = new char[length * 2]; var randomChars = new char[length * 2];
var random = new Random(); var random = new Random();
for (int i = 0; i < length * 2; i++) for (int i = 0; i < length * 2; i++)
randomChars[i] = _allowedRandomHexChars[random.Next(0, _allowedRandomHexChars.Length)]; randomChars[i] = _allowedRandomHexChars[random.Next(0, _allowedRandomHexChars.Length)];
return "0x" + new string(randomChars); return "0x" + new string(randomChars);
#endif #endif
} }
/// <summary> /// <summary>
/// Generate a long value /// Generate a long value
/// </summary> /// </summary>
/// <param name="maxLength">Max character length</param> /// <param name="maxLength">Max character length</param>
/// <returns></returns> /// <returns></returns>
public static long RandomLong(int maxLength) public static long RandomLong(int maxLength)
{ {
#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER #if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER
var value = RandomNumberGenerator.GetInt32(0, int.MaxValue); var value = RandomNumberGenerator.GetInt32(0, int.MaxValue);
#else #else
var random = new Random(); var random = new Random();
var value = random.Next(0, int.MaxValue); var value = random.Next(0, int.MaxValue);
#endif #endif
var val = value.ToString(); var val = value.ToString();
if (val.Length > maxLength) if (val.Length > maxLength)
return int.Parse(val.Substring(0, maxLength)); return int.Parse(val.Substring(0, maxLength));
else else
return value; return value;
} }
/// <summary> /// <summary>
/// Generate a random string of specified length /// Generate a random string of specified length
/// </summary> /// </summary>
/// <param name="source">The initial string</param> /// <param name="source">The initial string</param>
/// <param name="totalLength">Total length of the resulting string</param> /// <param name="totalLength">Total length of the resulting string</param>
/// <returns></returns> /// <returns></returns>
public static string AppendRandomString(string source, int totalLength) 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) batch = await paginatedFunc(request, nextPageToken, ct).ConfigureAwait(false);
throw new ArgumentException("Total length smaller than source string length", nameof(totalLength)); yield return batch;
if (!batch || ct.IsCancellationRequested)
break;
if (totalLength == source.Length) result.AddRange(batch.Data);
return source; nextPageToken = batch.NextPageToken;
if (nextPageToken == null)
return source + RandomString(totalLength - source.Length); break;
}
/// <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;
} }
} }
/// <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;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; 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> /// <summary>
/// Cache for symbol parsing /// Update the cached symbol data for an exchange
/// </summary> /// </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>(); if(!_symbolInfos.TryGetValue(topicId, out var 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)) exchangeInfo = new ExchangeInfo(DateTime.UtcNow, updateData.ToDictionary(x => x.Name, x => x.SharedSymbol));
{ _symbolInfos.TryAdd(topicId, 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));
} }
/// <summary> if (DateTime.UtcNow - exchangeInfo.UpdateTime < TimeSpan.FromMinutes(60))
/// Parse a symbol name to a SharedSymbol return;
/// </summary>
/// <param name="topicId">Id for the provided data</param> _symbolInfos[topicId] = new ExchangeInfo(DateTime.UtcNow, updateData.ToDictionary(x => x.Name, x => x.SharedSymbol));
/// <param name="symbolName">Symbol name</param> }
public static SharedSymbol? ParseSymbol(string topicId, string? symbolName)
/// <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) DeliverTime = symbolInfo.DeliverTime
return null; };
}
if (!_symbolInfos.TryGetValue(topicId, out var exchangeInfo)) class ExchangeInfo
return null; {
public DateTime UpdateTime { get; set; }
public Dictionary<string, SharedSymbol> Symbols { get; set; }
if (!exchangeInfo.Symbols.TryGetValue(symbolName, out var symbolInfo)) public ExchangeInfo(DateTime updateTime, Dictionary<string, SharedSymbol> symbols)
return null;
return new SharedSymbol(symbolInfo.TradingMode, symbolInfo.BaseAsset, symbolInfo.QuoteAsset, symbolName)
{
DeliverTime = symbolInfo.DeliverTime
};
}
class ExchangeInfo
{ {
public DateTime UpdateTime { get; set; } UpdateTime = updateTime;
public Dictionary<string, SharedSymbol> Symbols { get; set; } Symbols = symbols;
public ExchangeInfo(DateTime updateTime, Dictionary<string, SharedSymbol> symbols)
{
UpdateTime = updateTime;
Symbols = symbols;
}
} }
} }
} }

View File

@ -1,4 +1,4 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO.Compression; using System.IO.Compression;
using System.IO; using System.IO;
@ -10,515 +10,511 @@ using CryptoExchange.Net.Objects;
using System.Globalization; using System.Globalization;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using CryptoExchange.Net.SharedApis; 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> /// <summary>
/// Helper methods /// Add a parameter
/// </summary> /// </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> parameters.Add(key, value);
/// Add a parameter }
/// </summary>
/// <param name="parameters"></param> /// <summary>
/// <param name="key"></param> /// Add a parameter
/// <param name="value"></param> /// </summary>
public static void AddParameter(this Dictionary<string, object> parameters, string key, string value) /// <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); parameters.Add(key, value);
} }
/// <summary> /// <summary>
/// Add a parameter /// Create a query string of the specified parameters
/// </summary> /// </summary>
/// <param name="parameters"></param> /// <param name="parameters">The parameters to use</param>
/// <param name="key"></param> /// <param name="urlEncodeValues">Whether or not the values should be url encoded</param>
/// <param name="value"></param> /// <param name="serializationType">How to serialize array parameters</param>
public static void AddParameter(this Dictionary<string, object> parameters, string key, object value) /// <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); if (serializationType == ArrayParametersSerialization.Array)
}
/// <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) 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)}"))}&"; httpValueCollection.Add(parameter.Key, $"[{string.Join(",", (object[])parameter.Value)}]");
}
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 else
{ {
var array = (Array)arrayEntry.Value; foreach (var item in (object[])parameter.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)
{ {
httpValueCollection.Add(parameter.Key, $"[{string.Join(",", (object[])parameter.Value)}]"); if (arraySerialization == ArrayParametersSerialization.Array)
}
else
{
foreach (var item in (object[])parameter.Value)
{ {
if (arraySerialization == ArrayParametersSerialization.Array) httpValueCollection.Add(parameter.Key + "[]", item.ToString());
{ }
httpValueCollection.Add(parameter.Key + "[]", item.ToString()); else
} {
else httpValueCollection.Add(parameter.Key, item.ToString());
{
httpValueCollection.Add(parameter.Key, item.ToString());
}
} }
} }
} }
else
{
httpValueCollection.Add(parameter.Key, parameter.Value.ToString());
}
} }
else
uriBuilder.Query = httpValueCollection.ToString(); {
return uriBuilder.Uri; httpValueCollection.Add(parameter.Key, parameter.Value.ToString());
}
} }
/// <summary> uriBuilder.Query = httpValueCollection.ToString();
/// Create a new uri with the provided parameters as query return uriBuilder.Uri;
/// </summary> }
/// <param name="parameters"></param>
/// <param name="baseUri"></param> /// <summary>
/// <param name="arraySerialization"></param> /// Create a new uri with the provided parameters as query
/// <returns></returns> /// </summary>
public static Uri SetParameters(this Uri baseUri, IOrderedEnumerable<KeyValuePair<string, object>> parameters, ArrayParametersSerialization arraySerialization) /// <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(); if (parameter.Value.GetType().IsArray)
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)
{ {
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)}]"); if (arraySerialization == ArrayParametersSerialization.Array)
}
else
{
foreach (var item in (object[])parameter.Value)
{ {
if (arraySerialization == ArrayParametersSerialization.Array) httpValueCollection.Add(parameter.Key + "[]", item.ToString());
{ }
httpValueCollection.Add(parameter.Key + "[]", item.ToString()); else
} {
else httpValueCollection.Add(parameter.Key, item.ToString());
{
httpValueCollection.Add(parameter.Key, item.ToString());
}
} }
} }
} }
else
{
httpValueCollection.Add(parameter.Key, parameter.Value.ToString());
}
} }
else
uriBuilder.Query = httpValueCollection.ToString(); {
return uriBuilder.Uri; httpValueCollection.Add(parameter.Key, parameter.Value.ToString());
}
} }
/// <summary> uriBuilder.Query = httpValueCollection.ToString();
/// Add parameter to URI return uriBuilder.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);
httpValueCollection.Remove(name); /// <summary>
httpValueCollection.Add(name, value); /// 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); httpValueCollection.Remove(name);
ub.Query = httpValueCollection.ToString(); httpValueCollection.Add(name, value);
return ub.Uri; var ub = new UriBuilder(uri);
} ub.Query = httpValueCollection.ToString();
/// <summary> return ub.Uri;
/// 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);
}
/// <summary> /// <summary>
/// Decompress using DeflateStream /// Decompress using GzipStream
/// </summary> /// </summary>
/// <param name="input"></param> /// <param name="data"></param>
/// <returns></returns> /// <returns></returns>
public static ReadOnlyMemory<byte> Decompress(this ReadOnlyMemory<byte> input) public static ReadOnlyMemory<byte> DecompressGzip(this ReadOnlyMemory<byte> data)
{ {
var output = new MemoryStream(); 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())) /// <summary>
using (var decompressor = new DeflateStream(compressStream, CompressionMode.Decompress)) /// Decompress using DeflateStream
decompressor.CopyTo(output); /// </summary>
/// <param name="input"></param>
/// <returns></returns>
public static ReadOnlyMemory<byte> Decompress(this ReadOnlyMemory<byte> input)
{
var output = new MemoryStream();
output.Position = 0; using (var compressStream = new MemoryStream(input.ToArray()))
return new ReadOnlyMemory<byte>(output.GetBuffer(), 0, (int)output.Length); using (var decompressor = new DeflateStream(compressStream, CompressionMode.Decompress))
} decompressor.CopyTo(output);
/// <summary> output.Position = 0;
/// Whether the trading mode is linear return new ReadOnlyMemory<byte>(output.GetBuffer(), 0, (int)output.Length);
/// </summary> }
public static bool IsLinear(this TradingMode type) => type == TradingMode.PerpetualLinear || type == TradingMode.DeliveryLinear;
/// <summary> /// <summary>
/// Whether the trading mode is inverse /// Whether the trading mode is linear
/// </summary> /// </summary>
public static bool IsInverse(this TradingMode type) => type == TradingMode.PerpetualInverse || type == TradingMode.DeliveryInverse; public static bool IsLinear(this TradingMode type) => type == TradingMode.PerpetualLinear || type == TradingMode.DeliveryLinear;
/// <summary>
/// Whether the trading mode is perpetual
/// </summary>
public static bool IsPerpetual(this TradingMode type) => type == TradingMode.PerpetualInverse || type == TradingMode.PerpetualLinear;
/// <summary> /// <summary>
/// Whether the trading mode is delivery /// Whether the trading mode is inverse
/// </summary> /// </summary>
public static bool IsDelivery(this TradingMode type) => type == TradingMode.DeliveryInverse || type == TradingMode.DeliveryLinear; 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> /// <summary>
/// Register rest client interfaces /// Whether the trading mode is delivery
/// </summary> /// </summary>
public static IServiceCollection RegisterSharedRestInterfaces<T>(this IServiceCollection services, Func<IServiceProvider, T> client) public static bool IsDelivery(this TradingMode type) => type == TradingMode.DeliveryInverse || type == TradingMode.DeliveryLinear;
{
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(ISpotOrderRestClient).IsAssignableFrom(typeof(T))) /// <summary>
services.AddTransient(x => (ISpotOrderRestClient)client(x)!); /// Register rest client interfaces
if (typeof(ISpotSymbolRestClient).IsAssignableFrom(typeof(T))) /// </summary>
services.AddTransient(x => (ISpotSymbolRestClient)client(x)!); public static IServiceCollection RegisterSharedRestInterfaces<T>(this IServiceCollection services, Func<IServiceProvider, T> client)
if (typeof(ISpotTickerRestClient).IsAssignableFrom(typeof(T))) {
services.AddTransient(x => (ISpotTickerRestClient)client(x)!); if (typeof(IAssetsRestClient).IsAssignableFrom(typeof(T)))
if (typeof(ISpotTriggerOrderRestClient).IsAssignableFrom(typeof(T))) services.AddTransient(x => (IAssetsRestClient)client(x)!);
services.AddTransient(x => (ISpotTriggerOrderRestClient)client(x)!); if (typeof(IBalanceRestClient).IsAssignableFrom(typeof(T)))
if (typeof(ISpotOrderClientIdRestClient).IsAssignableFrom(typeof(T))) services.AddTransient(x => (IBalanceRestClient)client(x)!);
services.AddTransient(x => (ISpotOrderClientIdRestClient)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))) if (typeof(ISpotOrderRestClient).IsAssignableFrom(typeof(T)))
services.AddTransient(x => (IFundingRateRestClient)client(x)!); services.AddTransient(x => (ISpotOrderRestClient)client(x)!);
if (typeof(IFuturesOrderRestClient).IsAssignableFrom(typeof(T))) if (typeof(ISpotSymbolRestClient).IsAssignableFrom(typeof(T)))
services.AddTransient(x => (IFuturesOrderRestClient)client(x)!); services.AddTransient(x => (ISpotSymbolRestClient)client(x)!);
if (typeof(IFuturesSymbolRestClient).IsAssignableFrom(typeof(T))) if (typeof(ISpotTickerRestClient).IsAssignableFrom(typeof(T)))
services.AddTransient(x => (IFuturesSymbolRestClient)client(x)!); services.AddTransient(x => (ISpotTickerRestClient)client(x)!);
if (typeof(IFuturesTickerRestClient).IsAssignableFrom(typeof(T))) if (typeof(ISpotTriggerOrderRestClient).IsAssignableFrom(typeof(T)))
services.AddTransient(x => (IFuturesTickerRestClient)client(x)!); services.AddTransient(x => (ISpotTriggerOrderRestClient)client(x)!);
if (typeof(IIndexPriceKlineRestClient).IsAssignableFrom(typeof(T))) if (typeof(ISpotOrderClientIdRestClient).IsAssignableFrom(typeof(T)))
services.AddTransient(x => (IIndexPriceKlineRestClient)client(x)!); services.AddTransient(x => (ISpotOrderClientIdRestClient)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)!);
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> return services;
/// 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(ISpotOrderSocketClient).IsAssignableFrom(typeof(T))) /// <summary>
services.AddTransient(x => (ISpotOrderSocketClient)client(x)!); /// 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))) if (typeof(ISpotOrderSocketClient).IsAssignableFrom(typeof(T)))
services.AddTransient(x => (IFuturesOrderSocketClient)client(x)!); services.AddTransient(x => (ISpotOrderSocketClient)client(x)!);
if (typeof(IPositionSocketClient).IsAssignableFrom(typeof(T)))
services.AddTransient(x => (IPositionSocketClient)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> /// <summary>
/// Time provider /// Get current time
/// </summary> /// </summary>
internal interface IAuthTimeProvider /// <returns></returns>
{ DateTime GetTime();
/// <summary>
/// Get current time
/// </summary>
/// <returns></returns>
DateTime GetTime();
}
} }

View File

@ -1,48 +1,46 @@
using CryptoExchange.Net.Authentication; using CryptoExchange.Net.Authentication;
using CryptoExchange.Net.Objects;
using CryptoExchange.Net.Objects.Options; using CryptoExchange.Net.Objects.Options;
using CryptoExchange.Net.SharedApis; using CryptoExchange.Net.SharedApis;
using System; using System;
namespace CryptoExchange.Net.Interfaces namespace CryptoExchange.Net.Interfaces;
/// <summary>
/// Base api client
/// </summary>
public interface IBaseApiClient
{ {
/// <summary> /// <summary>
/// Base api client /// Base address
/// </summary> /// </summary>
public interface IBaseApiClient string BaseAddress { get; }
{
/// <summary>
/// Base address
/// </summary>
string BaseAddress { get; }
/// <summary> /// <summary>
/// Whether or not API credentials have been configured for this client. Does not check the credentials are actually valid. /// Whether or not API credentials have been configured for this client. Does not check the credentials are actually valid.
/// </summary> /// </summary>
bool Authenticated { get; } bool Authenticated { get; }
/// <summary> /// <summary>
/// Format a base and quote asset to an exchange accepted symbol /// Format a base and quote asset to an exchange accepted symbol
/// </summary> /// </summary>
/// <param name="baseAsset">The base asset</param> /// <param name="baseAsset">The base asset</param>
/// <param name="quoteAsset">The quote asset</param> /// <param name="quoteAsset">The quote asset</param>
/// <param name="tradingMode">The trading mode</param> /// <param name="tradingMode">The trading mode</param>
/// <param name="deliverDate">The deliver date for a delivery futures symbol</param> /// <param name="deliverDate">The deliver date for a delivery futures symbol</param>
/// <returns></returns> /// <returns></returns>
string FormatSymbol(string baseAsset, string quoteAsset, TradingMode tradingMode, DateTime? deliverDate = null); string FormatSymbol(string baseAsset, string quoteAsset, TradingMode tradingMode, DateTime? deliverDate = null);
/// <summary> /// <summary>
/// Set the API credentials for this API client /// Set the API credentials for this API client
/// </summary> /// </summary>
/// <typeparam name="T"></typeparam> /// <typeparam name="T"></typeparam>
/// <param name="credentials"></param> /// <param name="credentials"></param>
void SetApiCredentials<T>(T credentials) where T : ApiCredentials; void SetApiCredentials<T>(T credentials) where T : ApiCredentials;
/// <summary> /// <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. /// 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> /// </summary>
/// <typeparam name="T">Api credentials type</typeparam> /// <typeparam name="T">Api credentials type</typeparam>
/// <param name="options">Options to set</param> /// <param name="options">Options to set</param>
void SetOptions<T>(UpdateOptions<T> options) where T : ApiCredentials; 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> /// <summary>
/// Client for accessing REST API's for different exchanges /// Try get
/// </summary> /// </summary>
public interface ICryptoRestClient /// <typeparam name="T"></typeparam>
{ /// <returns></returns>
/// <summary> T TryGet<T>(Func<T> createFunc);
/// Try get
/// </summary>
/// <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> /// <summary>
/// Client for accessing Websocket API's for different exchanges /// Try get a client by type for the service collection
/// </summary> /// </summary>
public interface ICryptoSocketClient /// <typeparam name="T"></typeparam>
{ /// <returns></returns>
/// <summary> T TryGet<T>(Func<T> createFunc);
/// Try get a client by type for the service collection
/// </summary>
/// <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 CryptoExchange.Net.Objects;
using System; using System;
using System.Collections.Generic; #if NET5_0_OR_GREATER
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
#endif
using System.IO; using System.IO;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace CryptoExchange.Net.Interfaces namespace CryptoExchange.Net.Interfaces;
/// <summary>
/// Message accessor
/// </summary>
public interface IMessageAccessor
{ {
/// <summary> /// <summary>
/// Message accessor /// Is this a valid message
/// </summary> /// </summary>
public interface IMessageAccessor bool IsValid { get; }
{ /// <summary>
/// <summary> /// Is the original data available for retrieval
/// Is this a valid message /// </summary>
/// </summary> bool OriginalDataAvailable { get; }
bool IsValid { get; } /// <summary>
/// <summary> /// The underlying data object
/// Is the original data available for retrieval /// </summary>
/// </summary> object? Underlying { get; }
bool OriginalDataAvailable { get; } /// <summary>
/// <summary> /// Clear internal data structure
/// The underlying data object /// </summary>
/// </summary> void Clear();
object? Underlying { get; } /// <summary>
/// <summary> /// Get the type of node
/// Clear internal data structure /// </summary>
/// </summary> /// <returns></returns>
void Clear(); NodeType? GetNodeType();
/// <summary> /// <summary>
/// Get the type of node /// Get the type of node
/// </summary> /// </summary>
/// <returns></returns> /// <param name="path">Access path</param>
NodeType? GetNodeType(); /// <returns></returns>
/// <summary> NodeType? GetNodeType(MessagePath path);
/// Get the type of node /// <summary>
/// </summary> /// Get the value of a path
/// <param name="path">Access path</param> /// </summary>
/// <returns></returns> /// <typeparam name="T"></typeparam>
NodeType? GetNodeType(MessagePath path); /// <param name="path"></param>
/// <summary> /// <returns></returns>
/// Get the value of a path T? GetValue<T>(MessagePath path);
/// </summary> /// <summary>
/// <typeparam name="T"></typeparam> /// Get the values of an array
/// <param name="path"></param> /// </summary>
/// <returns></returns> /// <typeparam name="T"></typeparam>
T? GetValue<T>(MessagePath path); /// <param name="path"></param>
/// <summary> /// <returns></returns>
/// Get the values of an array T?[]? GetValues<T>(MessagePath path);
/// </summary> /// <summary>
/// <typeparam name="T"></typeparam> /// Deserialize the message into this type
/// <param name="path"></param> /// </summary>
/// <returns></returns> /// <param name="type"></param>
T?[]? GetValues<T>(MessagePath path); /// <param name="path"></param>
/// <summary> /// <returns></returns>
/// Deserialize the message into this type
/// </summary>
/// <param name="type"></param>
/// <param name="path"></param>
/// <returns></returns>
#if NET5_0_OR_GREATER #if NET5_0_OR_GREATER
[UnconditionalSuppressMessage("AssemblyLoadTrimming", "IL2092: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")] [UnconditionalSuppressMessage("AssemblyLoadTrimming", "IL2095:RequiresUnreferencedCode", Justification = "JsonSerializerOptions provided here has TypeInfoResolver set")]
#endif #endif
CallResult<object> Deserialize(Type type, MessagePath? path = null); CallResult<object> Deserialize(Type type, MessagePath? path = null);
/// <summary> /// <summary>
/// Deserialize the message into this type /// Deserialize the message into this type
/// </summary> /// </summary>
/// <param name="path"></param> /// <param name="path"></param>
/// <returns></returns> /// <returns></returns>
#if NET5_0_OR_GREATER #if NET5_0_OR_GREATER
[UnconditionalSuppressMessage("AssemblyLoadTrimming", "IL2092: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")] [UnconditionalSuppressMessage("AssemblyLoadTrimming", "IL2095:RequiresUnreferencedCode", Justification = "JsonSerializerOptions provided here has TypeInfoResolver set")]
#endif #endif
CallResult<T> Deserialize<T>(MessagePath? path = null); CallResult<T> Deserialize<T>(MessagePath? path = null);
/// <summary>
/// Get the original string value
/// </summary>
/// <returns></returns>
string GetOriginalString();
}
/// <summary> /// <summary>
/// Stream message accessor /// Get the original string value
/// </summary> /// </summary>
public interface IStreamMessageAccessor : IMessageAccessor /// <returns></returns>
{ string GetOriginalString();
/// <summary> }
/// Load a stream message
/// </summary> /// <summary>
/// <param name="stream"></param> /// Stream message accessor
/// <param name="bufferStream"></param> /// </summary>
Task<CallResult> Read(Stream stream, bool bufferStream); public interface IStreamMessageAccessor : IMessageAccessor
} {
/// <summary>
/// <summary> /// Load a stream message
/// Byte message accessor /// </summary>
/// </summary> /// <param name="stream"></param>
public interface IByteMessageAccessor : IMessageAccessor /// <param name="bufferStream"></param>
{ Task<CallResult> Read(Stream stream, bool bufferStream);
/// <summary> }
/// Load a data message
/// </summary> /// <summary>
/// <param name="data"></param> /// Byte message accessor
CallResult Read(ReadOnlyMemory<byte> data); /// </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.Objects.Sockets;
using CryptoExchange.Net.Sockets; using CryptoExchange.Net.Sockets;
using System; using System;
using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace CryptoExchange.Net.Interfaces namespace CryptoExchange.Net.Interfaces;
/// <summary>
/// Message processor
/// </summary>
public interface IMessageProcessor
{ {
/// <summary> /// <summary>
/// Message processor /// Id of the processor
/// </summary> /// </summary>
public interface IMessageProcessor public int Id { get; }
{ /// <summary>
/// <summary> /// The matcher for this listener
/// Id of the processor /// </summary>
/// </summary> public MessageMatcher MessageMatcher { get; }
public int Id { get; } /// <summary>
/// <summary> /// Handle a message
/// The matcher for this listener /// </summary>
/// </summary> Task<CallResult> Handle(SocketConnection connection, DataEvent<object> message, MessageHandlerLink matchedHandler);
public MessageMatcher MessageMatcher { get; } /// <summary>
/// <summary> /// Deserialize a message into object of type
/// Handle a message /// </summary>
/// </summary> /// <param name="accessor"></param>
Task<CallResult> Handle(SocketConnection connection, DataEvent<object> message, MessageHandlerLink matchedHandler); /// <param name="type"></param>
/// <summary> /// <returns></returns>
/// Deserialize a message into object of type CallResult<object> Deserialize(IMessageAccessor accessor, Type 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> /// <summary>
/// Serializer interface /// Serialize an object to a string
/// </summary> /// </summary>
public interface IMessageSerializer /// <param name="message"></param>
{ /// <returns></returns>
} byte[] Serialize<T>(T message);
}
/// <summary>
/// Serialize to byte array /// <summary>
/// </summary> /// Serialize to string
public interface IByteMessageSerializer: IMessageSerializer /// </summary>
{ public interface IStringMessageSerializer: IMessageSerializer
/// <summary> {
/// Serialize an object to a string /// <summary>
/// </summary> /// Serialize an object to a string
/// <param name="message"></param> /// </summary>
/// <returns></returns> /// <param name="message"></param>
byte[] Serialize<T>(T message); /// <returns></returns>
} string 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> /// <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> /// </summary>
public interface INonceProvider /// <returns>Nonce value</returns>
{ long GetNonce();
/// <summary>
/// Get nonce value. Nonce value should be unique and incremental for each call
/// </summary>
/// <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 CryptoExchange.Net.SharedApis;
using System; using System;
namespace CryptoExchange.Net.Interfaces namespace CryptoExchange.Net.Interfaces;
/// <summary>
/// Factory for ISymbolOrderBook instances
/// </summary>
public interface IOrderBookFactory<TOptions> where TOptions : OrderBookOptions
{ {
/// <summary> /// <summary>
/// Factory for ISymbolOrderBook instances /// Create a new order book by symbol name
/// </summary> /// </summary>
public interface IOrderBookFactory<TOptions> where TOptions : OrderBookOptions /// <param name="symbol">Symbol name</param>
{ /// <param name="options">Options for the order book</param>
/// <summary> /// <returns></returns>
/// Create a new order book by symbol name public ISymbolOrderBook Create(string symbol, Action<TOptions>? options = null);
/// </summary> /// <summary>
/// <param name="symbol">Symbol name</param> /// Create a new order book by base and quote asset names
/// <param name="options">Options for the order book</param> /// </summary>
/// <returns></returns> /// <param name="baseAsset">Base asset name</param>
public ISymbolOrderBook Create(string symbol, Action<TOptions>? options = null); /// <param name="quoteAsset">Quote asset name</param>
/// <summary> /// <param name="options">Options for the order book</param>
/// Create a new order book by base and quote asset names /// <returns></returns>
/// </summary> public ISymbolOrderBook Create(string baseAsset, string quoteAsset, Action<TOptions>? options = null);
/// <param name="baseAsset">Base asset name</param> /// <summary>
/// <param name="quoteAsset">Quote asset name</param> /// Create a new order book by base and quote asset names
/// <param name="options">Options for the order book</param> /// </summary>
/// <returns></returns> /// <param name="symbol">Symbol</param>
public ISymbolOrderBook Create(string baseAsset, string quoteAsset, Action<TOptions>? options = null); /// <param name="options">Options for the order book</param>
/// <summary> /// <returns></returns>
/// Create a new order book by base and quote asset names public ISymbolOrderBook Create(SharedSymbol symbol, Action<TOptions>? options = null);
/// </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;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace CryptoExchange.Net.Interfaces namespace CryptoExchange.Net.Interfaces;
/// <summary>
/// Rate limiter interface
/// </summary>
public interface IRateLimiter
{ {
/// <summary> /// <summary>
/// Rate limiter interface /// Limit a request based on previous requests made
/// </summary> /// </summary>
public interface IRateLimiter /// <param name="log">The logger</param>
{ /// <param name="endpoint">The endpoint the request is for</param>
/// <summary> /// <param name="method">The Http request method</param>
/// Limit a request based on previous requests made /// <param name="signed">Whether the request is singed(private) or not</param>
/// </summary> /// <param name="apiKey">The api key making this request</param>
/// <param name="log">The logger</param> /// <param name="limitBehaviour">The limit behavior for when the limit is reached</param>
/// <param name="endpoint">The endpoint the request is for</param> /// <param name="requestWeight">The weight of the request</param>
/// <param name="method">The Http request method</param> /// <param name="ct">Cancellation token to cancel waiting</param>
/// <param name="signed">Whether the request is singed(private) or not</param> /// <returns>The time in milliseconds spend waiting</returns>
/// <param name="apiKey">The api key making this request</param> Task<CallResult<int>> LimitRequestAsync(ILogger log, string endpoint, HttpMethod method, bool signed, string? apiKey, RateLimitingBehaviour limitBehaviour, int requestWeight, CancellationToken ct);
/// <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.Collections.Generic;
using System.Net.Http; using System.Net.Http;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace CryptoExchange.Net.Interfaces namespace CryptoExchange.Net.Interfaces;
/// <summary>
/// Request interface
/// </summary>
public interface IRequest
{ {
/// <summary> /// <summary>
/// Request interface /// Accept header
/// </summary> /// </summary>
public interface IRequest string Accept { set; }
{ /// <summary>
/// <summary> /// Content
/// Accept header /// </summary>
/// </summary> string? Content { get; }
string Accept { set; } /// <summary>
/// <summary> /// Method
/// Content /// </summary>
/// </summary> HttpMethod Method { get; set; }
string? Content { get; } /// <summary>
/// <summary> /// Uri
/// Method /// </summary>
/// </summary> Uri Uri { get; }
HttpMethod Method { get; set; } /// <summary>
/// <summary> /// internal request id for tracing
/// Uri /// </summary>
/// </summary> int RequestId { get; }
Uri Uri { get; } /// <summary>
/// <summary> /// Set byte content
/// internal request id for tracing /// </summary>
/// </summary> /// <param name="data"></param>
int RequestId { get; } void SetContent(byte[] data);
/// <summary> /// <summary>
/// Set byte content /// Set string content
/// </summary> /// </summary>
/// <param name="data"></param> /// <param name="data"></param>
void SetContent(byte[] data); /// <param name="contentType"></param>
/// <summary> void SetContent(string data, string contentType);
/// Set string content
/// </summary>
/// <param name="data"></param>
/// <param name="contentType"></param>
void SetContent(string data, string contentType);
/// <summary> /// <summary>
/// Add a header to the request /// Add a header to the request
/// </summary> /// </summary>
/// <param name="key"></param> /// <param name="key"></param>
/// <param name="value"></param> /// <param name="value"></param>
void AddHeader(string key, string value); void AddHeader(string key, string value);
/// <summary> /// <summary>
/// Get all headers /// Get all headers
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
KeyValuePair<string, string[]>[] GetHeaders(); KeyValuePair<string, string[]>[] GetHeaders();
/// <summary> /// <summary>
/// Get the response /// Get the response
/// </summary> /// </summary>
/// <param name="cancellationToken"></param> /// <param name="cancellationToken"></param>
/// <returns></returns> /// <returns></returns>
Task<IResponse> GetResponseAsync(CancellationToken cancellationToken); Task<IResponse> GetResponseAsync(CancellationToken cancellationToken);
}
} }

View File

@ -1,36 +1,35 @@
using CryptoExchange.Net.Objects; using CryptoExchange.Net.Objects;
using System; using System;
using System.Net.Http; using System.Net.Http;
namespace CryptoExchange.Net.Interfaces namespace CryptoExchange.Net.Interfaces;
/// <summary>
/// Request factory interface
/// </summary>
public interface IRequestFactory
{ {
/// <summary> /// <summary>
/// Request factory interface /// Create a request for an uri
/// </summary> /// </summary>
public interface IRequestFactory /// <param name="method"></param>
{ /// <param name="uri"></param>
/// <summary> /// <param name="requestId"></param>
/// Create a request for an uri /// <returns></returns>
/// </summary> 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> /// <summary>
/// Configure the requests created by this factory /// Configure the requests created by this factory
/// </summary> /// </summary>
/// <param name="requestTimeout">Request timeout to use</param> /// <param name="requestTimeout">Request timeout to use</param>
/// <param name="httpClient">Optional shared http client instance</param> /// <param name="httpClient">Optional shared http client instance</param>
/// <param name="proxy">Optional proxy to use when no http client is provided</param> /// <param name="proxy">Optional proxy to use when no http client is provided</param>
void Configure(ApiProxy? proxy, TimeSpan requestTimeout, HttpClient? httpClient = null); void Configure(ApiProxy? proxy, TimeSpan requestTimeout, HttpClient? httpClient = null);
/// <summary> /// <summary>
/// Update settings /// Update settings
/// </summary> /// </summary>
/// <param name="proxy">Proxy to use</param> /// <param name="proxy">Proxy to use</param>
/// <param name="requestTimeout">Request timeout to use</param> /// <param name="requestTimeout">Request timeout to use</param>
void UpdateSettings(ApiProxy? proxy, TimeSpan requestTimeout); 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.IO;
using System.Net; using System.Net;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace CryptoExchange.Net.Interfaces namespace CryptoExchange.Net.Interfaces;
/// <summary>
/// Response object interface
/// </summary>
public interface IResponse
{ {
/// <summary> /// <summary>
/// Response object interface /// The response status code
/// </summary> /// </summary>
public interface IResponse HttpStatusCode StatusCode { get; }
{
/// <summary>
/// The response status code
/// </summary>
HttpStatusCode StatusCode { get; }
/// <summary> /// <summary>
/// Whether the status code indicates a success status /// Whether the status code indicates a success status
/// </summary> /// </summary>
bool IsSuccessStatusCode { get; } bool IsSuccessStatusCode { get; }
/// <summary> /// <summary>
/// The length of the response in bytes /// The length of the response in bytes
/// </summary> /// </summary>
long? ContentLength { get; } long? ContentLength { get; }
/// <summary> /// <summary>
/// The response headers /// The response headers
/// </summary> /// </summary>
KeyValuePair<string, string[]>[] ResponseHeaders { get; } KeyValuePair<string, string[]>[] ResponseHeaders { get; }
/// <summary> /// <summary>
/// Get the response stream /// Get the response stream
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
Task<Stream> GetResponseStreamAsync(); Task<Stream> GetResponseStreamAsync();
/// <summary> /// <summary>
/// Close the response /// Close the response
/// </summary> /// </summary>
void Close(); 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> /// <summary>
/// Base rest API client /// The factory for creating requests. Used for unit testing
/// </summary> /// </summary>
public interface IRestApiClient : IBaseApiClient IRequestFactory RequestFactory { get; set; }
{
/// <summary>
/// The factory for creating requests. Used for unit testing
/// </summary>
IRequestFactory RequestFactory { get; set; }
/// <summary> /// <summary>
/// Total amount of requests made with this API client /// Total amount of requests made with this API client
/// </summary> /// </summary>
int TotalRequestsMade { get; set; } int TotalRequestsMade { get; set; }
}
} }

View File

@ -1,26 +1,25 @@
using System; using System;
using CryptoExchange.Net.Objects.Options; 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> /// <summary>
/// Base class for rest API implementations /// The options provided for this client
/// </summary> /// </summary>
public interface IRestClient: IDisposable ExchangeOptions ClientOptions { get; }
{
/// <summary>
/// The options provided for this client
/// </summary>
ExchangeOptions ClientOptions { get; }
/// <summary> /// <summary>
/// The total amount of requests made with this client /// The total amount of requests made with this client
/// </summary> /// </summary>
int TotalRequestsMade { get; } int TotalRequestsMade { get; }
/// <summary> /// <summary>
/// The exchange name /// The exchange name
/// </summary> /// </summary>
string Exchange { get; } 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.Options;
using CryptoExchange.Net.Objects.Sockets; using CryptoExchange.Net.Objects.Sockets;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace CryptoExchange.Net.Interfaces namespace CryptoExchange.Net.Interfaces;
/// <summary>
/// Socket API client
/// </summary>
public interface ISocketApiClient: IBaseApiClient
{ {
/// <summary> /// <summary>
/// Socket API client /// The current amount of socket connections on the API client
/// </summary> /// </summary>
public interface ISocketApiClient: IBaseApiClient int CurrentConnections { get; }
{ /// <summary>
/// <summary> /// The current amount of subscriptions over all connections
/// The current amount of socket connections on the API client /// </summary>
/// </summary> int CurrentSubscriptions { get; }
int CurrentConnections { get; } /// <summary>
/// <summary> /// Incoming data Kbps
/// The current amount of subscriptions over all connections /// </summary>
/// </summary> double IncomingKbps { get; }
int CurrentSubscriptions { get; } /// <summary>
/// <summary> /// The factory for creating sockets. Used for unit testing
/// Incoming data Kbps /// </summary>
/// </summary> IWebsocketFactory SocketFactory { get; set; }
double IncomingKbps { get; } /// <summary>
/// <summary> /// Current client options
/// The factory for creating sockets. Used for unit testing /// </summary>
/// </summary> SocketExchangeOptions ClientOptions { get; }
IWebsocketFactory SocketFactory { get; set; } /// <summary>
/// <summary> /// Current API options
/// Current client options /// </summary>
/// </summary> SocketApiOptions ApiOptions { get; }
SocketExchangeOptions ClientOptions { get; } /// <summary>
/// <summary> /// Log the current state of connections and subscriptions
/// Current API options /// </summary>
/// </summary> string GetSubscriptionsState(bool includeSubDetails = true);
SocketApiOptions ApiOptions { get; } /// <summary>
/// <summary> /// Reconnect all connections
/// Log the current state of connections and subscriptions /// </summary>
/// </summary> /// <returns></returns>
string GetSubscriptionsState(bool includeSubDetails = true); Task ReconnectAsync();
/// <summary> /// <summary>
/// Reconnect all connections /// Unsubscribe all subscriptions
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
Task ReconnectAsync(); Task UnsubscribeAllAsync();
/// <summary> /// <summary>
/// Unsubscribe all subscriptions /// Unsubscribe an update subscription
/// </summary> /// </summary>
/// <returns></returns> /// <param name="subscriptionId">The id of the subscription to unsubscribe</param>
Task UnsubscribeAllAsync(); /// <returns></returns>
/// <summary> Task<bool> UnsubscribeAsync(int subscriptionId);
/// Unsubscribe an update subscription /// <summary>
/// </summary> /// Unsubscribe an update subscription
/// <param name="subscriptionId">The id of the subscription to unsubscribe</param> /// </summary>
/// <returns></returns> /// <param name="subscription">The subscription to unsubscribe</param>
Task<bool> UnsubscribeAsync(int subscriptionId); /// <returns></returns>
/// <summary> Task UnsubscribeAsync(UpdateSubscription subscription);
/// Unsubscribe an update subscription
/// </summary>
/// <param name="subscription">The subscription to unsubscribe</param>
/// <returns></returns>
Task UnsubscribeAsync(UpdateSubscription subscription);
/// <summary> /// <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. /// 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> /// </summary>
/// <returns></returns> /// <returns></returns>
Task<CallResult> PrepareConnectionsAsync(); Task<CallResult> PrepareConnectionsAsync();
}
} }

View File

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

View File

@ -1,131 +1,129 @@
using System; using System;
using System.Collections.Generic;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using CryptoExchange.Net.Objects; using CryptoExchange.Net.Objects;
namespace CryptoExchange.Net.Interfaces namespace CryptoExchange.Net.Interfaces;
/// <summary>
/// Interface for order book
/// </summary>
public interface ISymbolOrderBook
{ {
/// <summary> /// <summary>
/// Interface for order book /// The exchange the book is for
/// </summary> /// </summary>
public interface ISymbolOrderBook string Exchange { get; }
{
/// <summary>
/// The exchange the book is for
/// </summary>
string Exchange { get; }
/// <summary> /// <summary>
/// The Api the book is for /// The Api the book is for
/// </summary> /// </summary>
string Api { get; } string Api { get; }
/// <summary> /// <summary>
/// The status of the order book. Order book is up to date when the status is `Synced` /// The status of the order book. Order book is up to date when the status is `Synced`
/// </summary> /// </summary>
OrderBookStatus Status { get; set; } OrderBookStatus Status { get; set; }
/// <summary> /// <summary>
/// Last update identifier /// Last update identifier
/// </summary> /// </summary>
long LastSequenceNumber { get; } long LastSequenceNumber { get; }
/// <summary> /// <summary>
/// The symbol of the order book /// The symbol of the order book
/// </summary> /// </summary>
string Symbol { get; } string Symbol { get; }
/// <summary> /// <summary>
/// Event when the state changes /// Event when the state changes
/// </summary> /// </summary>
event Action<OrderBookStatus, OrderBookStatus> OnStatusChange; event Action<OrderBookStatus, OrderBookStatus> OnStatusChange;
/// <summary> /// <summary>
/// Event when order book was updated. Be careful! It can generate a lot of events at high-liquidity markets /// Event when order book was updated. Be careful! It can generate a lot of events at high-liquidity markets
/// </summary> /// </summary>
event Action<(ISymbolOrderBookEntry[] Bids, ISymbolOrderBookEntry[] Asks)> OnOrderBookUpdate; event Action<(ISymbolOrderBookEntry[] Bids, ISymbolOrderBookEntry[] Asks)> OnOrderBookUpdate;
/// <summary> /// <summary>
/// Event when the BestBid or BestAsk changes ie a Pricing Tick /// Event when the BestBid or BestAsk changes ie a Pricing Tick
/// </summary> /// </summary>
event Action<(ISymbolOrderBookEntry BestBid, ISymbolOrderBookEntry BestAsk)> OnBestOffersChanged; event Action<(ISymbolOrderBookEntry BestBid, ISymbolOrderBookEntry BestAsk)> OnBestOffersChanged;
/// <summary> /// <summary>
/// Timestamp of the last update /// Timestamp of the last update
/// </summary> /// </summary>
DateTime UpdateTime { get; } DateTime UpdateTime { get; }
/// <summary> /// <summary>
/// The number of asks in the book /// The number of asks in the book
/// </summary> /// </summary>
int AskCount { get; } int AskCount { get; }
/// <summary> /// <summary>
/// The number of bids in the book /// The number of bids in the book
/// </summary> /// </summary>
int BidCount { get; } int BidCount { get; }
/// <summary> /// <summary>
/// Get a snapshot of the book at this moment /// Get a snapshot of the book at this moment
/// </summary> /// </summary>
(ISymbolOrderBookEntry[] bids, ISymbolOrderBookEntry[] asks) Book { get; } (ISymbolOrderBookEntry[] bids, ISymbolOrderBookEntry[] asks) Book { get; }
/// <summary> /// <summary>
/// The list of asks /// The list of asks
/// </summary> /// </summary>
ISymbolOrderBookEntry[] Asks { get; } ISymbolOrderBookEntry[] Asks { get; }
/// <summary> /// <summary>
/// The list of bids /// The list of bids
/// </summary> /// </summary>
ISymbolOrderBookEntry[] Bids { get; } ISymbolOrderBookEntry[] Bids { get; }
/// <summary> /// <summary>
/// The best bid currently in the order book /// The best bid currently in the order book
/// </summary> /// </summary>
ISymbolOrderBookEntry BestBid { get; } ISymbolOrderBookEntry BestBid { get; }
/// <summary> /// <summary>
/// The best ask currently in the order book /// The best ask currently in the order book
/// </summary> /// </summary>
ISymbolOrderBookEntry BestAsk { get; } ISymbolOrderBookEntry BestAsk { get; }
/// <summary> /// <summary>
/// BestBid/BesAsk returned as a pair /// BestBid/BesAsk returned as a pair
/// </summary> /// </summary>
(ISymbolOrderBookEntry Bid, ISymbolOrderBookEntry Ask) BestOffers { get; } (ISymbolOrderBookEntry Bid, ISymbolOrderBookEntry Ask) BestOffers { get; }
/// <summary> /// <summary>
/// Start connecting and synchronizing the order book /// Start connecting and synchronizing the order book
/// </summary> /// </summary>
/// <param name="ct">A cancellation token to stop the order book when canceled</param> /// <param name="ct">A cancellation token to stop the order book when canceled</param>
/// <returns></returns> /// <returns></returns>
Task<CallResult<bool>> StartAsync(CancellationToken? ct = null); Task<CallResult<bool>> StartAsync(CancellationToken? ct = null);
/// <summary> /// <summary>
/// Stop syncing the order book /// Stop syncing the order book
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
Task StopAsync(); Task StopAsync();
/// <summary> /// <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 /// 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. /// at that price since between this calculation and the order placement the book might have changed.
/// </summary> /// </summary>
/// <param name="quantity">The quantity in base asset to fill</param> /// <param name="quantity">The quantity in base asset to fill</param>
/// <param name="type">The type</param> /// <param name="type">The type</param>
/// <returns>Average fill price</returns> /// <returns>Average fill price</returns>
CallResult<decimal> CalculateAverageFillPrice(decimal quantity, OrderBookEntryType type); CallResult<decimal> CalculateAverageFillPrice(decimal quantity, OrderBookEntryType type);
/// <summary> /// <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. /// 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. /// 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> /// </summary>
/// <param name="quoteQuantity">The quantity in quote asset looking to trade</param> /// <param name="quoteQuantity">The quantity in quote asset looking to trade</param>
/// <param name="type">The type</param> /// <param name="type">The type</param>
/// <returns>Amount of base asset tradable with the specified amount of quote asset</returns> /// <returns>Amount of base asset tradable with the specified amount of quote asset</returns>
CallResult<decimal> CalculateTradableAmount(decimal quoteQuantity, OrderBookEntryType type); CallResult<decimal> CalculateTradableAmount(decimal quoteQuantity, OrderBookEntryType type);
/// <summary> /// <summary>
/// String representation of the top x entries /// String representation of the top x entries
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
string ToString(int rows); 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> /// <summary>
/// Interface for order book entries /// The quantity of the entry
/// </summary> /// </summary>
public interface ISymbolOrderBookEntry decimal Quantity { get; set; }
{
/// <summary>
/// The quantity of the entry
/// </summary>
decimal Quantity { get; set; }
/// <summary>
/// The price of the entry
/// </summary>
decimal Price { get; set; }
}
/// <summary> /// <summary>
/// Interface for order book entries /// The price of the entry
/// </summary> /// </summary>
public interface ISymbolOrderSequencedBookEntry: ISymbolOrderBookEntry decimal Price { get; set; }
{ }
/// <summary>
/// Sequence of the update /// <summary>
/// </summary> /// Interface for order book entries
long Sequence { get; set; } /// </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;
using System.Net.WebSockets; using System.Net.WebSockets;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace CryptoExchange.Net.Interfaces namespace CryptoExchange.Net.Interfaces;
/// <summary>
/// Websocket connection interface
/// </summary>
public interface IWebsocket: IDisposable
{ {
/// <summary> /// <summary>
/// Websocket connection interface /// Websocket closed event
/// </summary> /// </summary>
public interface IWebsocket: IDisposable event Func<Task> OnClose;
{ /// <summary>
/// <summary> /// Websocket message received event
/// Websocket closed event /// </summary>
/// </summary> event Func<WebSocketMessageType, ReadOnlyMemory<byte>, Task> OnStreamMessage;
event Func<Task> OnClose; /// <summary>
/// <summary> /// Websocket sent event, RequestId as parameter
/// Websocket message received event /// </summary>
/// </summary> event Func<int, Task> OnRequestSent;
event Func<WebSocketMessageType, ReadOnlyMemory<byte>, Task> OnStreamMessage; /// <summary>
/// <summary> /// Websocket query was ratelimited and couldn't be send
/// Websocket sent event, RequestId as parameter /// </summary>
/// </summary> event Func<int, Task>? OnRequestRateLimited;
event Func<int, Task> OnRequestSent; /// <summary>
/// <summary> /// Connection was ratelimited and couldn't be established
/// Websocket query was ratelimited and couldn't be send /// </summary>
/// </summary> event Func<Task>? OnConnectRateLimited;
event Func<int, Task>? OnRequestRateLimited; /// <summary>
/// <summary> /// Websocket error event
/// Connection was ratelimited and couldn't be established /// </summary>
/// </summary> event Func<Exception, Task> OnError;
event Func<Task>? OnConnectRateLimited; /// <summary>
/// <summary> /// Websocket opened event
/// Websocket error event /// </summary>
/// </summary> event Func<Task> OnOpen;
event Func<Exception, Task> OnError; /// <summary>
/// <summary> /// Websocket has lost connection to the server and is attempting to reconnect
/// Websocket opened event /// </summary>
/// </summary> event Func<Task> OnReconnecting;
event Func<Task> OnOpen; /// <summary>
/// <summary> /// Websocket has reconnected to the server
/// Websocket has lost connection to the server and is attempting to reconnect /// </summary>
/// </summary> event Func<Task> OnReconnected;
event Func<Task> OnReconnecting; /// <summary>
/// <summary> /// Get reconnection url
/// Websocket has reconnected to the server /// </summary>
/// </summary> Func<Task<Uri?>>? GetReconnectionUrl { get; set; }
event Func<Task> OnReconnected;
/// <summary>
/// Get reconnection url
/// </summary>
Func<Task<Uri?>>? GetReconnectionUrl { get; set; }
/// <summary> /// <summary>
/// Unique id for this socket /// Unique id for this socket
/// </summary> /// </summary>
int Id { get; } int Id { get; }
/// <summary> /// <summary>
/// The current kilobytes per second of data being received, averaged over the last 3 seconds /// The current kilobytes per second of data being received, averaged over the last 3 seconds
/// </summary> /// </summary>
double IncomingKbps { get; } double IncomingKbps { get; }
/// <summary> /// <summary>
/// The uri the socket connects to /// The uri the socket connects to
/// </summary> /// </summary>
Uri Uri { get; } Uri Uri { get; }
/// <summary> /// <summary>
/// Whether the socket connection is closed /// Whether the socket connection is closed
/// </summary> /// </summary>
bool IsClosed { get; } bool IsClosed { get; }
/// <summary> /// <summary>
/// Whether the socket connection is open /// Whether the socket connection is open
/// </summary> /// </summary>
bool IsOpen { get; } bool IsOpen { get; }
/// <summary> /// <summary>
/// Connect the socket /// Connect the socket
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
Task<CallResult> ConnectAsync(CancellationToken ct); Task<CallResult> ConnectAsync(CancellationToken ct);
/// <summary> /// <summary>
/// Send string data /// Send string data
/// </summary> /// </summary>
/// <param name="id"></param> /// <param name="id"></param>
/// <param name="data"></param> /// <param name="data"></param>
/// <param name="weight"></param> /// <param name="weight"></param>
bool Send(int id, string data, int weight); bool Send(int id, string data, int weight);
/// <summary> /// <summary>
/// Send byte data /// Send byte data
/// </summary> /// </summary>
/// <param name="id"></param> /// <param name="id"></param>
/// <param name="data"></param> /// <param name="data"></param>
/// <param name="weight"></param> /// <param name="weight"></param>
bool Send(int id, byte[] data, int weight); bool Send(int id, byte[] data, int weight);
/// <summary> /// <summary>
/// Reconnect the socket /// Reconnect the socket
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
Task ReconnectAsync(); Task ReconnectAsync();
/// <summary> /// <summary>
/// Close the connection /// Close the connection
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
Task CloseAsync(); Task CloseAsync();
/// <summary> /// <summary>
/// Update proxy setting /// Update proxy setting
/// </summary> /// </summary>
void UpdateProxy(ApiProxy? proxy); void UpdateProxy(ApiProxy? proxy);
}
} }

View File

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

View File

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

View File

@ -1,79 +1,78 @@
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using System; 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 #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; _rateLimitRequestFailed = LoggerMessage.Define<int, string, string, string>(
private static readonly Action<ILogger, int, string, string, Exception?> _rateLimitConnectionFailed; LogLevel.Warning,
private static readonly Action<ILogger, int, string, TimeSpan, string, string, Exception?> _rateLimitDelayingRequest; new EventId(6000, "RateLimitRequestFailed"),
private static readonly Action<ILogger, int, TimeSpan, string, string, Exception?> _rateLimitDelayingConnection; "[Req {Id}] Call to {Path} failed because of ratelimit guard {Guard}; {Limit}");
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() _rateLimitConnectionFailed = LoggerMessage.Define<int, string, string>(
{ LogLevel.Warning,
_rateLimitRequestFailed = LoggerMessage.Define<int, string, string, string>( new EventId(6001, "RateLimitConnectionFailed"),
LogLevel.Warning, "[Sckt {Id}] Connection failed because of ratelimit guard {Guard}; {Limit}");
new EventId(6000, "RateLimitRequestFailed"),
"[Req {Id}] Call to {Path} failed because of ratelimit guard {Guard}; {Limit}");
_rateLimitConnectionFailed = LoggerMessage.Define<int, string, string>( _rateLimitDelayingRequest = LoggerMessage.Define<int, string, TimeSpan, string, string>(
LogLevel.Warning, LogLevel.Warning,
new EventId(6001, "RateLimitConnectionFailed"), new EventId(6002, "RateLimitDelayingRequest"),
"[Sckt {Id}] Connection failed because of ratelimit guard {Guard}; {Limit}"); "[Req {Id}] Delaying call to {Path} by {Delay} because of ratelimit guard {Guard}; {Limit}");
_rateLimitDelayingRequest = LoggerMessage.Define<int, string, TimeSpan, string, string>( _rateLimitDelayingConnection = LoggerMessage.Define<int, TimeSpan, string, string>(
LogLevel.Warning, LogLevel.Warning,
new EventId(6002, "RateLimitDelayingRequest"), new EventId(6003, "RateLimitDelayingConnection"),
"[Req {Id}] Delaying call to {Path} by {Delay} because of ratelimit guard {Guard}; {Limit}"); "[Sckt {Id}] Delaying connection by {Delay} because of ratelimit guard {Guard}; {Limit}");
_rateLimitDelayingConnection = LoggerMessage.Define<int, TimeSpan, string, string>( _rateLimitAppliedConnection = LoggerMessage.Define<int, string, string, int>(
LogLevel.Warning, LogLevel.Trace,
new EventId(6003, "RateLimitDelayingConnection"), new EventId(6004, "RateLimitDelayingConnection"),
"[Sckt {Id}] Delaying connection by {Delay} because of ratelimit guard {Guard}; {Limit}"); "[Sckt {Id}] Connection passed ratelimit guard {Guard}; {Limit}, New count: {Current}");
_rateLimitAppliedConnection = LoggerMessage.Define<int, string, string, int>( _rateLimitAppliedRequest = LoggerMessage.Define<int, string, string, string, int>(
LogLevel.Trace, LogLevel.Trace,
new EventId(6004, "RateLimitDelayingConnection"), new EventId(6005, "RateLimitAppliedRequest"),
"[Sckt {Id}] Connection passed ratelimit guard {Guard}; {Limit}, New count: {Current}"); "[Req {Id}] Call to {Path} passed ratelimit guard {Guard}; {Limit}, New count: {Current}");
}
_rateLimitAppliedRequest = LoggerMessage.Define<int, string, string, string, int>( public static void RateLimitRequestFailed(this ILogger logger, int requestId, string path, string guard, string limit)
LogLevel.Trace, {
new EventId(6005, "RateLimitAppliedRequest"), _rateLimitRequestFailed(logger, requestId, path, guard, limit, null);
"[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) public static void RateLimitConnectionFailed(this ILogger logger, int connectionId, string guard, string limit)
{ {
_rateLimitRequestFailed(logger, requestId, path, guard, limit, null); _rateLimitConnectionFailed(logger, connectionId, guard, limit, null);
} }
public static void RateLimitConnectionFailed(this ILogger logger, int connectionId, string guard, string limit) public static void RateLimitDelayingRequest(this ILogger logger, int requestId, string path, TimeSpan delay, string guard, string limit)
{ {
_rateLimitConnectionFailed(logger, connectionId, guard, limit, null); _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) public static void RateLimitDelayingConnection(this ILogger logger, int connectionId, TimeSpan delay, string guard, string limit)
{ {
_rateLimitDelayingRequest(logger, requestId, path, delay, guard, limit, null); _rateLimitDelayingConnection(logger, connectionId, delay, guard, limit, null);
} }
public static void RateLimitDelayingConnection(this ILogger logger, int connectionId, TimeSpan delay, string guard, string limit) public static void RateLimitAppliedConnection(this ILogger logger, int connectionId, string guard, string limit, int current)
{ {
_rateLimitDelayingConnection(logger, connectionId, delay, guard, limit, null); _rateLimitAppliedConnection(logger, connectionId, guard, limit, current, null);
} }
public static void RateLimitAppliedConnection(this ILogger logger, int connectionId, string guard, string limit, int current) public static void RateLimitAppliedRequest(this ILogger logger, int requestIdId, string path, string guard, string limit, int current)
{ {
_rateLimitAppliedConnection(logger, connectionId, guard, limit, current, null); _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 Microsoft.Extensions.Logging;
using System; using System;
using System.Net; using System.Net;
using System.Net.Http; 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 #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; _restApiErrorReceived = LoggerMessage.Define<int?, int?, long, string?, string?>(
private static readonly Action<ILogger, int?, int?, long, string?, Exception?> _restApiResponseReceived; LogLevel.Warning,
private static readonly Action<ILogger, int, string, Exception?> _restApiFailedToSyncTime; new EventId(4000, "RestApiErrorReceived"),
private static readonly Action<ILogger, int, string, Exception?> _restApiNoApiCredentials; "[Req {RequestId}] {ResponseStatusCode} - Error received in {ResponseTime}ms: {ErrorMessage}, Data: {OriginalData}");
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() _restApiResponseReceived = LoggerMessage.Define<int?, int?, long, string?>(
{ LogLevel.Debug,
_restApiErrorReceived = LoggerMessage.Define<int?, int?, long, string?, string?>( new EventId(4001, "RestApiResponseReceived"),
LogLevel.Warning, "[Req {RequestId}] {ResponseStatusCode} - Response received in {ResponseTime}ms: {OriginalData}");
new EventId(4000, "RestApiErrorReceived"),
"[Req {RequestId}] {ResponseStatusCode} - Error received in {ResponseTime}ms: {ErrorMessage}, Data: {OriginalData}");
_restApiResponseReceived = LoggerMessage.Define<int?, int?, long, string?>( _restApiFailedToSyncTime = LoggerMessage.Define<int, string>(
LogLevel.Debug, LogLevel.Debug,
new EventId(4001, "RestApiResponseReceived"), new EventId(4002, "RestApiFailedToSyncTime"),
"[Req {RequestId}] {ResponseStatusCode} - Response received in {ResponseTime}ms: {OriginalData}"); "[Req {RequestId}] Failed to sync time, aborting request: {ErrorMessage}");
_restApiFailedToSyncTime = LoggerMessage.Define<int, string>( _restApiNoApiCredentials = LoggerMessage.Define<int, string>(
LogLevel.Debug, LogLevel.Warning,
new EventId(4002, "RestApiFailedToSyncTime"), new EventId(4003, "RestApiNoApiCredentials"),
"[Req {RequestId}] Failed to sync time, aborting request: {ErrorMessage}"); "[Req {RequestId}] Request {RestApiUri} failed because no ApiCredentials were provided");
_restApiNoApiCredentials = LoggerMessage.Define<int, string>( _restApiCreatingRequest = LoggerMessage.Define<int, Uri>(
LogLevel.Warning, LogLevel.Information,
new EventId(4003, "RestApiNoApiCredentials"), new EventId(4004, "RestApiCreatingRequest"),
"[Req {RequestId}] Request {RestApiUri} failed because no ApiCredentials were provided"); "[Req {RequestId}] Creating request for {RestApiUri}");
_restApiCreatingRequest = LoggerMessage.Define<int, Uri>( _restApiSendingRequest = LoggerMessage.Define<int, HttpMethod, string, Uri, string>(
LogLevel.Information, LogLevel.Trace,
new EventId(4004, "RestApiCreatingRequest"), new EventId(4005, "RestApiSendingRequest"),
"[Req {RequestId}] Creating request for {RestApiUri}"); "[Req {RequestId}] Sending {Method} {Signed} request to {RestApiUri}{Query}");
_restApiSendingRequest = LoggerMessage.Define<int, HttpMethod, string, Uri, string>( _restApiRateLimitRetry = LoggerMessage.Define<int, DateTime>(
LogLevel.Trace, LogLevel.Warning,
new EventId(4005, "RestApiSendingRequest"), new EventId(4006, "RestApiRateLimitRetry"),
"[Req {RequestId}] Sending {Method} {Signed} request to {RestApiUri}{Query}"); "[Req {RequestId}] Received ratelimit error, retrying after {Timestamp}");
_restApiRateLimitRetry = LoggerMessage.Define<int, DateTime>( _restApiRateLimitPauseUntil = LoggerMessage.Define<int, DateTime>(
LogLevel.Warning, LogLevel.Warning,
new EventId(4006, "RestApiRateLimitRetry"), new EventId(4007, "RestApiRateLimitPauseUntil"),
"[Req {RequestId}] Received ratelimit error, retrying after {Timestamp}"); "[Req {RequestId}] Ratelimit error from server, pausing requests until {Until}");
_restApiRateLimitPauseUntil = LoggerMessage.Define<int, DateTime>( _restApiSendRequest = LoggerMessage.Define<int, RequestDefinition, string?, string, string>(
LogLevel.Warning, LogLevel.Debug,
new EventId(4007, "RestApiRateLimitPauseUntil"), new EventId(4008, "RestApiSendRequest"),
"[Req {RequestId}] Ratelimit error from server, pausing requests until {Until}"); "[Req {RequestId}] Sending {Definition} request with body {Body}, query parameters {Query} and headers {Headers}");
_restApiSendRequest = LoggerMessage.Define<int, RequestDefinition, string?, string, string>( _restApiCheckingCache = LoggerMessage.Define<string>(
LogLevel.Debug, LogLevel.Trace,
new EventId(4008, "RestApiSendRequest"), new EventId(4009, "RestApiCheckingCache"),
"[Req {RequestId}] Sending {Definition} request with body {Body}, query parameters {Query} and headers {Headers}"); "Checking cache for key {Key}");
_restApiCheckingCache = LoggerMessage.Define<string>( _restApiCacheHit = LoggerMessage.Define<string>(
LogLevel.Trace, LogLevel.Trace,
new EventId(4009, "RestApiCheckingCache"), new EventId(4010, "RestApiCacheHit"),
"Checking cache for key {Key}"); "Cache hit for key {Key}");
_restApiCacheHit = LoggerMessage.Define<string>( _restApiCacheNotHit = LoggerMessage.Define<string>(
LogLevel.Trace, LogLevel.Trace,
new EventId(4010, "RestApiCacheHit"), new EventId(4011, "RestApiCacheNotHit"),
"Cache hit for key {Key}"); "Cache not hit for key {Key}");
_restApiCacheNotHit = LoggerMessage.Define<string>( _restApiCancellationRequested = LoggerMessage.Define<int?>(
LogLevel.Trace, LogLevel.Debug,
new EventId(4011, "RestApiCacheNotHit"), new EventId(4012, "RestApiCancellationRequested"),
"Cache not hit for key {Key}"); "[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) public static void RestApiResponseReceived(this ILogger logger, int? requestId, HttpStatusCode? responseStatusCode, long responseTime, string? originalData)
{ {
_restApiErrorReceived(logger, requestId, (int?)responseStatusCode, responseTime, error, originalData, exception); _restApiResponseReceived(logger, requestId, (int?)responseStatusCode, responseTime, originalData, null);
} }
public static void RestApiResponseReceived(this ILogger logger, int? requestId, HttpStatusCode? responseStatusCode, long responseTime, string? originalData) public static void RestApiFailedToSyncTime(this ILogger logger, int requestId, string error)
{ {
_restApiResponseReceived(logger, requestId, (int?)responseStatusCode, responseTime, originalData, null); _restApiFailedToSyncTime(logger, requestId, error, null);
} }
public static void RestApiFailedToSyncTime(this ILogger logger, int requestId, string error) public static void RestApiNoApiCredentials(this ILogger logger, int requestId, string uri)
{ {
_restApiFailedToSyncTime(logger, requestId, error, null); _restApiNoApiCredentials(logger, requestId, uri, null);
} }
public static void RestApiNoApiCredentials(this ILogger logger, int requestId, string uri) public static void RestApiCreatingRequest(this ILogger logger, int requestId, Uri uri)
{ {
_restApiNoApiCredentials(logger, requestId, uri, null); _restApiCreatingRequest(logger, requestId, uri, null);
} }
public static void RestApiCreatingRequest(this ILogger logger, int requestId, Uri uri) public static void RestApiSendingRequest(this ILogger logger, int requestId, HttpMethod method, string signed, Uri uri, string paramString)
{ {
_restApiCreatingRequest(logger, requestId, uri, null); _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) public static void RestApiRateLimitRetry(this ILogger logger, int requestId, DateTime retryAfter)
{ {
_restApiSendingRequest(logger, requestId, method, signed, uri, paramString, null); _restApiRateLimitRetry(logger, requestId, retryAfter, null);
} }
public static void RestApiRateLimitRetry(this ILogger logger, int requestId, DateTime retryAfter) public static void RestApiRateLimitPauseUntil(this ILogger logger, int requestId, DateTime retryAfter)
{ {
_restApiRateLimitRetry(logger, requestId, retryAfter, null); _restApiRateLimitPauseUntil(logger, requestId, retryAfter, null);
} }
public static void RestApiRateLimitPauseUntil(this ILogger logger, int requestId, DateTime retryAfter) public static void RestApiSendRequest(this ILogger logger, int requestId, RequestDefinition definition, string? body, string query, string headers)
{ {
_restApiRateLimitPauseUntil(logger, requestId, retryAfter, null); _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) public static void CheckingCache(this ILogger logger, string key)
{ {
_restApiSendRequest(logger, requestId, definition, body, query, headers, null); _restApiCheckingCache(logger, key, null);
} }
public static void CheckingCache(this ILogger logger, string key) public static void CacheHit(this ILogger logger, string key)
{ {
_restApiCheckingCache(logger, key, null); _restApiCacheHit(logger, key, null);
} }
public static void CacheHit(this ILogger logger, string key) public static void CacheNotHit(this ILogger logger, string key)
{ {
_restApiCacheHit(logger, key, null); _restApiCacheNotHit(logger, key, null);
} }
public static void RestApiCancellationRequested(this ILogger logger, int? requestId)
public static void CacheNotHit(this ILogger logger, string key) {
{ _restApiCancellationRequested(logger, requestId, null);
_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; 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 #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; _failedToAddSubscriptionRetryOnDifferentConnection = LoggerMessage.Define<int>(
private static readonly Action<ILogger, int, Exception?> _hasBeenPausedCantSubscribeAtThisMoment; LogLevel.Trace,
private static readonly Action<ILogger, int, string?, Exception?> _failedToSubscribe; new EventId(3000, "FailedToAddSubscriptionRetryOnDifferentConnection"),
private static readonly Action<ILogger, int, int, Exception?> _cancellationTokenSetClosingSubscription; "[Sckt {SocketId}] failed to add subscription, retrying on different connection");
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() _hasBeenPausedCantSubscribeAtThisMoment = LoggerMessage.Define<int>(
{ LogLevel.Warning,
_failedToAddSubscriptionRetryOnDifferentConnection = LoggerMessage.Define<int>( new EventId(3001, "HasBeenPausedCantSubscribeAtThisMoment"),
LogLevel.Trace, "[Sckt {SocketId}] has been paused, can't subscribe at this moment");
new EventId(3000, "FailedToAddSubscriptionRetryOnDifferentConnection"),
"[Sckt {SocketId}] failed to add subscription, retrying on different connection");
_hasBeenPausedCantSubscribeAtThisMoment = LoggerMessage.Define<int>( _failedToSubscribe = LoggerMessage.Define<int, string?>(
LogLevel.Warning, LogLevel.Warning,
new EventId(3001, "HasBeenPausedCantSubscribeAtThisMoment"), new EventId(3002, "FailedToSubscribe"),
"[Sckt {SocketId}] has been paused, can't subscribe at this moment"); "[Sckt {SocketId}] failed to subscribe: {ErrorMessage}");
_failedToSubscribe = LoggerMessage.Define<int, string?>( _cancellationTokenSetClosingSubscription = LoggerMessage.Define<int, int>(
LogLevel.Warning, LogLevel.Information,
new EventId(3002, "FailedToSubscribe"), new EventId(3003, "CancellationTokenSetClosingSubscription"),
"[Sckt {SocketId}] failed to subscribe: {ErrorMessage}"); "[Sckt {SocketId}] Cancellation token set, closing subscription {SubscriptionId}");
_cancellationTokenSetClosingSubscription = LoggerMessage.Define<int, int>( _subscriptionCompletedSuccessfully = LoggerMessage.Define<int, int>(
LogLevel.Information, LogLevel.Information,
new EventId(3003, "CancellationTokenSetClosingSubscription"), new EventId(3004, "SubscriptionCompletedSuccessfully"),
"[Sckt {SocketId}] Cancellation token set, closing subscription {SubscriptionId}"); "[Sckt {SocketId}] subscription {SubscriptionId} completed successfully");
_subscriptionCompletedSuccessfully = LoggerMessage.Define<int, int>( _hasBeenPausedCantSendQueryAtThisMoment = LoggerMessage.Define<int>(
LogLevel.Information, LogLevel.Warning,
new EventId(3004, "SubscriptionCompletedSuccessfully"), new EventId(3005, "HasBeenPausedCantSendQueryAtThisMoment"),
"[Sckt {SocketId}] subscription {SubscriptionId} completed successfully"); "[Sckt {SocketId}] has been paused, can't send query at this moment");
_hasBeenPausedCantSendQueryAtThisMoment = LoggerMessage.Define<int>( _attemptingToAuthenticate = LoggerMessage.Define<int>(
LogLevel.Warning, LogLevel.Debug,
new EventId(3005, "HasBeenPausedCantSendQueryAtThisMoment"), new EventId(3006, "AttemptingToAuthenticate"),
"[Sckt {SocketId}] has been paused, can't send query at this moment"); "[Sckt {SocketId}] Attempting to authenticate");
_attemptingToAuthenticate = LoggerMessage.Define<int>( _authenticationFailed = LoggerMessage.Define<int>(
LogLevel.Debug, LogLevel.Warning,
new EventId(3006, "AttemptingToAuthenticate"), new EventId(3007, "AuthenticationFailed"),
"[Sckt {SocketId}] Attempting to authenticate"); "[Sckt {SocketId}] authentication failed");
_authenticationFailed = LoggerMessage.Define<int>( _authenticated = LoggerMessage.Define<int>(
LogLevel.Warning, LogLevel.Debug,
new EventId(3007, "AuthenticationFailed"), new EventId(3008, "Authenticated"),
"[Sckt {SocketId}] authentication failed"); "[Sckt {SocketId}] authenticated");
_authenticated = LoggerMessage.Define<int>( _failedToDetermineConnectionUrl = LoggerMessage.Define<string?>(
LogLevel.Debug, LogLevel.Warning,
new EventId(3008, "Authenticated"), new EventId(3009, "FailedToDetermineConnectionUrl"),
"[Sckt {SocketId}] authenticated"); "Failed to determine connection url: {ErrorMessage}");
_failedToDetermineConnectionUrl = LoggerMessage.Define<string?>( _connectionAddressSetTo = LoggerMessage.Define<string>(
LogLevel.Warning, LogLevel.Debug,
new EventId(3009, "FailedToDetermineConnectionUrl"), new EventId(3010, "ConnectionAddressSetTo"),
"Failed to determine connection url: {ErrorMessage}"); "Connection address set to {ConnectionAddress}");
_connectionAddressSetTo = LoggerMessage.Define<string>( _socketCreatedForAddress = LoggerMessage.Define<int, string>(
LogLevel.Debug, LogLevel.Debug,
new EventId(3010, "ConnectionAddressSetTo"), new EventId(3011, "SocketCreatedForAddress"),
"Connection address set to {ConnectionAddress}"); "[Sckt {SocketId}] created for {Address}");
_socketCreatedForAddress = LoggerMessage.Define<int, string>( _unsubscribingAll = LoggerMessage.Define<int>(
LogLevel.Debug, LogLevel.Information,
new EventId(3011, "SocketCreatedForAddress"), new EventId(3013, "UnsubscribingAll"),
"[Sckt {SocketId}] created for {Address}"); "Unsubscribing all {SubscriptionCount} subscriptions");
_unsubscribingAll = LoggerMessage.Define<int>( _disposingSocketClient = LoggerMessage.Define(
LogLevel.Information, LogLevel.Debug,
new EventId(3013, "UnsubscribingAll"), new EventId(3015, "DisposingSocketClient"),
"Unsubscribing all {SubscriptionCount} subscriptions"); "Disposing socket client, closing all subscriptions");
_disposingSocketClient = LoggerMessage.Define( _unsubscribingSubscription = LoggerMessage.Define<int, int>(
LogLevel.Debug, LogLevel.Information,
new EventId(3015, "DisposingSocketClient"), new EventId(3016, "UnsubscribingSubscription"),
"Disposing socket client, closing all subscriptions"); "[Sckt {SocketId}] Unsubscribing subscription {SubscriptionId}");
_unsubscribingSubscription = LoggerMessage.Define<int, int>( _reconnectingAllConnections = LoggerMessage.Define<int>(
LogLevel.Information, LogLevel.Information,
new EventId(3016, "UnsubscribingSubscription"), new EventId(3017, "ReconnectingAll"),
"[Sckt {SocketId}] Unsubscribing subscription {SubscriptionId}"); "Reconnecting all {ConnectionCount} connections");
_reconnectingAllConnections = LoggerMessage.Define<int>( _addingRetryAfterGuard = LoggerMessage.Define<DateTime>(
LogLevel.Information, LogLevel.Warning,
new EventId(3017, "ReconnectingAll"), new EventId(3018, "AddRetryAfterGuard"),
"Reconnecting all {ConnectionCount} connections"); "Adding RetryAfterGuard ({RetryAfter}) because the connection attempt was rate limited");
}
_addingRetryAfterGuard = LoggerMessage.Define<DateTime>( public static void FailedToAddSubscriptionRetryOnDifferentConnection(this ILogger logger, int socketId)
LogLevel.Warning, {
new EventId(3018, "AddRetryAfterGuard"), _failedToAddSubscriptionRetryOnDifferentConnection(logger, socketId, null);
"Adding RetryAfterGuard ({RetryAfter}) because the connection attempt was rate limited"); }
}
public static void FailedToAddSubscriptionRetryOnDifferentConnection(this ILogger logger, int socketId) public static void HasBeenPausedCantSubscribeAtThisMoment(this ILogger logger, int socketId)
{ {
_failedToAddSubscriptionRetryOnDifferentConnection(logger, socketId, null); _hasBeenPausedCantSubscribeAtThisMoment(logger, socketId, null);
} }
public static void HasBeenPausedCantSubscribeAtThisMoment(this ILogger logger, int socketId) public static void FailedToSubscribe(this ILogger logger, int socketId, string? error)
{ {
_hasBeenPausedCantSubscribeAtThisMoment(logger, socketId, null); _failedToSubscribe(logger, socketId, error, null);
} }
public static void FailedToSubscribe(this ILogger logger, int socketId, string? error) public static void CancellationTokenSetClosingSubscription(this ILogger logger, int socketId, int subscriptionId)
{ {
_failedToSubscribe(logger, socketId, error, null); _cancellationTokenSetClosingSubscription(logger, socketId, subscriptionId, null);
} }
public static void CancellationTokenSetClosingSubscription(this ILogger logger, int socketId, int subscriptionId) public static void SubscriptionCompletedSuccessfully(this ILogger logger, int socketId, int subscriptionId)
{ {
_cancellationTokenSetClosingSubscription(logger, socketId, subscriptionId, null); _subscriptionCompletedSuccessfully(logger, socketId, subscriptionId, null);
} }
public static void SubscriptionCompletedSuccessfully(this ILogger logger, int socketId, int subscriptionId) public static void HasBeenPausedCantSendQueryAtThisMoment(this ILogger logger, int socketId)
{ {
_subscriptionCompletedSuccessfully(logger, socketId, subscriptionId, null); _hasBeenPausedCantSendQueryAtThisMoment(logger, socketId, null);
} }
public static void HasBeenPausedCantSendQueryAtThisMoment(this ILogger logger, int socketId) public static void AttemptingToAuthenticate(this ILogger logger, int socketId)
{ {
_hasBeenPausedCantSendQueryAtThisMoment(logger, socketId, null); _attemptingToAuthenticate(logger, socketId, null);
} }
public static void AttemptingToAuthenticate(this ILogger logger, int socketId) public static void AuthenticationFailed(this ILogger logger, int socketId)
{ {
_attemptingToAuthenticate(logger, socketId, null); _authenticationFailed(logger, socketId, null);
} }
public static void AuthenticationFailed(this ILogger logger, int socketId) public static void Authenticated(this ILogger logger, int socketId)
{ {
_authenticationFailed(logger, socketId, null); _authenticated(logger, socketId, null);
} }
public static void Authenticated(this ILogger logger, int socketId) public static void FailedToDetermineConnectionUrl(this ILogger logger, string? error)
{ {
_authenticated(logger, socketId, null); _failedToDetermineConnectionUrl(logger, error, null);
} }
public static void FailedToDetermineConnectionUrl(this ILogger logger, string? error) public static void ConnectionAddressSetTo(this ILogger logger, string connectionAddress)
{ {
_failedToDetermineConnectionUrl(logger, error, null); _connectionAddressSetTo(logger, connectionAddress, null);
} }
public static void ConnectionAddressSetTo(this ILogger logger, string connectionAddress) public static void SocketCreatedForAddress(this ILogger logger, int socketId, string address)
{ {
_connectionAddressSetTo(logger, connectionAddress, null); _socketCreatedForAddress(logger, socketId, address, null);
} }
public static void SocketCreatedForAddress(this ILogger logger, int socketId, string address) public static void UnsubscribingAll(this ILogger logger, int subscriptionCount)
{ {
_socketCreatedForAddress(logger, socketId, address, null); _unsubscribingAll(logger, subscriptionCount, null);
} }
public static void UnsubscribingAll(this ILogger logger, int subscriptionCount) public static void DisposingSocketClient(this ILogger logger)
{ {
_unsubscribingAll(logger, subscriptionCount, null); _disposingSocketClient(logger, null);
} }
public static void DisposingSocketClient(this ILogger logger) public static void UnsubscribingSubscription(this ILogger logger, int socketId, int subscriptionId)
{ {
_disposingSocketClient(logger, null); _unsubscribingSubscription(logger, socketId, subscriptionId, null);
} }
public static void UnsubscribingSubscription(this ILogger logger, int socketId, int subscriptionId) public static void ReconnectingAllConnections(this ILogger logger, int connectionCount)
{ {
_unsubscribingSubscription(logger, socketId, subscriptionId, null); _reconnectingAllConnections(logger, connectionCount, null);
} }
public static void ReconnectingAllConnections(this ILogger logger, int connectionCount) public static void AddingRetryAfterGuard(this ILogger logger, DateTime retryAfter)
{ {
_reconnectingAllConnections(logger, connectionCount, null); _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 System.Net.WebSockets;
using Microsoft.Extensions.Logging; 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 #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; _activityPaused = LoggerMessage.Define<int, bool>(
private static readonly Action<ILogger, int, Sockets.SocketConnection.SocketStatus, Sockets.SocketConnection.SocketStatus, Exception?> _socketStatusChanged; LogLevel.Information,
private static readonly Action<ILogger, int, string?, Exception?> _failedReconnectProcessing; new EventId(2000, "ActivityPaused"),
private static readonly Action<ILogger, int, Exception?> _unknownExceptionWhileProcessingReconnection; "[Sckt {SocketId}] paused activity: {Paused}");
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() _socketStatusChanged = LoggerMessage.Define<int, Sockets.SocketConnection.SocketStatus, Sockets.SocketConnection.SocketStatus>(
{ LogLevel.Debug,
_activityPaused = LoggerMessage.Define<int, bool>( new EventId(2001, "SocketStatusChanged"),
LogLevel.Information, "[Sckt {SocketId}] status changed from {OldStatus} to {NewStatus}");
new EventId(2000, "ActivityPaused"),
"[Sckt {SocketId}] paused activity: {Paused}");
_socketStatusChanged = LoggerMessage.Define<int, Sockets.SocketConnection.SocketStatus, Sockets.SocketConnection.SocketStatus>( _failedReconnectProcessing = LoggerMessage.Define<int, string?>(
LogLevel.Debug, LogLevel.Warning,
new EventId(2001, "SocketStatusChanged"), new EventId(2002, "FailedReconnectProcessing"),
"[Sckt {SocketId}] status changed from {OldStatus} to {NewStatus}"); "[Sckt {SocketId}] failed reconnect processing: {ErrorMessage}, reconnecting again");
_failedReconnectProcessing = LoggerMessage.Define<int, string?>( _unknownExceptionWhileProcessingReconnection = LoggerMessage.Define<int>(
LogLevel.Warning, LogLevel.Warning,
new EventId(2002, "FailedReconnectProcessing"), new EventId(2003, "UnknownExceptionWhileProcessingReconnection"),
"[Sckt {SocketId}] failed reconnect processing: {ErrorMessage}, reconnecting again"); "[Sckt {SocketId}] Unknown exception while processing reconnection, reconnecting again");
_unknownExceptionWhileProcessingReconnection = LoggerMessage.Define<int>( _webSocketErrorCodeAndDetails = LoggerMessage.Define<int, WebSocketError, string?>(
LogLevel.Warning, LogLevel.Warning,
new EventId(2003, "UnknownExceptionWhileProcessingReconnection"), new EventId(2004, "WebSocketErrorCode"),
"[Sckt {SocketId}] Unknown exception while processing reconnection, reconnecting again"); "[Sckt {SocketId}] error: Websocket error code {WebSocketErrorCode}, details: {Details}");
_webSocketErrorCodeAndDetails = LoggerMessage.Define<int, WebSocketError, string?>( _webSocketError = LoggerMessage.Define<int, string?>(
LogLevel.Warning, LogLevel.Warning,
new EventId(2004, "WebSocketErrorCode"), new EventId(2005, "WebSocketError"),
"[Sckt {SocketId}] error: Websocket error code {WebSocketErrorCode}, details: {Details}"); "[Sckt {SocketId}] error: {ErrorMessage}");
_webSocketError = LoggerMessage.Define<int, string?>( _messageSentNotPending = LoggerMessage.Define<int, int>(
LogLevel.Warning, LogLevel.Debug,
new EventId(2005, "WebSocketError"), new EventId(2006, "MessageSentNotPending"),
"[Sckt {SocketId}] error: {ErrorMessage}"); "[Sckt {SocketId}] [Req {RequestId}] message sent, but not pending");
_messageSentNotPending = LoggerMessage.Define<int, int>( _receivedData = LoggerMessage.Define<int, string>(
LogLevel.Debug, LogLevel.Trace,
new EventId(2006, "MessageSentNotPending"), new EventId(2007, "ReceivedData"),
"[Sckt {SocketId}] [Req {RequestId}] message sent, but not pending"); "[Sckt {SocketId}] received {OriginalData}");
_receivedData = LoggerMessage.Define<int, string>( _failedToEvaluateMessage = LoggerMessage.Define<int, string>(
LogLevel.Trace, LogLevel.Warning,
new EventId(2007, "ReceivedData"), new EventId(2008, "FailedToEvaluateMessage"),
"[Sckt {SocketId}] received {OriginalData}"); "[Sckt {SocketId}] failed to evaluate message. {OriginalData}");
_failedToEvaluateMessage = LoggerMessage.Define<int, string>( _errorProcessingMessage = LoggerMessage.Define<int>(
LogLevel.Warning, LogLevel.Error,
new EventId(2008, "FailedToEvaluateMessage"), new EventId(2009, "ErrorProcessingMessage"),
"[Sckt {SocketId}] failed to evaluate message. {OriginalData}"); "[Sckt {SocketId}] error processing message");
_errorProcessingMessage = LoggerMessage.Define<int>( _receivedMessageNotRecognized = LoggerMessage.Define<int, int>(
LogLevel.Error, LogLevel.Warning,
new EventId(2009, "ErrorProcessingMessage"), new EventId(2011, "ReceivedMessageNotRecognized"),
"[Sckt {SocketId}] error processing message"); "[Sckt {SocketId}] received message not recognized by handler {ProcessorId}");
_receivedMessageNotRecognized = LoggerMessage.Define<int, int>( _failedToDeserializeMessage = LoggerMessage.Define<int, string?>(
LogLevel.Warning, LogLevel.Warning,
new EventId(2011, "ReceivedMessageNotRecognized"), new EventId(2012, "FailedToDeserializeMessage"),
"[Sckt {SocketId}] received message not recognized by handler {ProcessorId}"); "[Sckt {SocketId}] deserialization failed: {ErrorMessage}");
_failedToDeserializeMessage = LoggerMessage.Define<int, string?>( _userMessageProcessingFailed = LoggerMessage.Define<int, string>(
LogLevel.Warning, LogLevel.Warning,
new EventId(2012, "FailedToDeserializeMessage"), new EventId(2013, "UserMessageProcessingFailed"),
"[Sckt {SocketId}] deserialization failed: {ErrorMessage}"); "[Sckt {SocketId}] user message processing failed: {ErrorMessage}");
_userMessageProcessingFailed = LoggerMessage.Define<int, string>( _messageProcessed = LoggerMessage.Define<int, long, long>(
LogLevel.Warning, LogLevel.Trace,
new EventId(2013, "UserMessageProcessingFailed"), new EventId(2014, "MessageProcessed"),
"[Sckt {SocketId}] user message processing failed: {ErrorMessage}"); "[Sckt {SocketId}] message processed in {ProcessingTime}ms, {ParsingTime}ms parsing");
_messageProcessed = LoggerMessage.Define<int, long, long>( _closingSubscription = LoggerMessage.Define<int, int>(
LogLevel.Trace, LogLevel.Debug,
new EventId(2014, "MessageProcessed"), new EventId(2015, "ClosingSubscription"),
"[Sckt {SocketId}] message processed in {ProcessingTime}ms, {ParsingTime}ms parsing"); "[Sckt {SocketId}] closing subscription {SubscriptionId}");
_closingSubscription = LoggerMessage.Define<int, int>( _notUnsubscribingSubscriptionBecauseDuplicateRunning = LoggerMessage.Define<int>(
LogLevel.Debug, LogLevel.Debug,
new EventId(2015, "ClosingSubscription"), new EventId(2016, "NotUnsubscribingSubscription"),
"[Sckt {SocketId}] closing subscription {SubscriptionId}"); "[Sckt {SocketId}] not unsubscribing subscription as there is still a duplicate subscription running");
_notUnsubscribingSubscriptionBecauseDuplicateRunning = LoggerMessage.Define<int>( _alreadyClosing = LoggerMessage.Define<int>(
LogLevel.Debug, LogLevel.Debug,
new EventId(2016, "NotUnsubscribingSubscription"), new EventId(2017, "AlreadyClosing"),
"[Sckt {SocketId}] not unsubscribing subscription as there is still a duplicate subscription running"); "[Sckt {SocketId}] already closing");
_alreadyClosing = LoggerMessage.Define<int>( _closingNoMoreSubscriptions = LoggerMessage.Define<int>(
LogLevel.Debug, LogLevel.Debug,
new EventId(2017, "AlreadyClosing"), new EventId(2018, "ClosingNoMoreSubscriptions"),
"[Sckt {SocketId}] already closing"); "[Sckt {SocketId}] closing as there are no more subscriptions");
_closingNoMoreSubscriptions = LoggerMessage.Define<int>( _addingNewSubscription = LoggerMessage.Define<int, int, int>(
LogLevel.Debug, LogLevel.Debug,
new EventId(2018, "ClosingNoMoreSubscriptions"), new EventId(2019, "AddingNewSubscription"),
"[Sckt {SocketId}] closing as there are no more subscriptions"); "[Sckt {SocketId}] adding new subscription with id {SubscriptionId}, total subscriptions on connection: {UserSubscriptionCount}");
_addingNewSubscription = LoggerMessage.Define<int, int, int>( _nothingToResubscribeCloseConnection = LoggerMessage.Define<int>(
LogLevel.Debug, LogLevel.Debug,
new EventId(2019, "AddingNewSubscription"), new EventId(2020, "NothingToResubscribe"),
"[Sckt {SocketId}] adding new subscription with id {SubscriptionId}, total subscriptions on connection: {UserSubscriptionCount}"); "[Sckt {SocketId}] nothing to resubscribe, closing connection");
_nothingToResubscribeCloseConnection = LoggerMessage.Define<int>( _failedAuthenticationDisconnectAndReconnect = LoggerMessage.Define<int>(
LogLevel.Debug, LogLevel.Warning,
new EventId(2020, "NothingToResubscribe"), new EventId(2021, "FailedAuthentication"),
"[Sckt {SocketId}] nothing to resubscribe, closing connection"); "[Sckt {SocketId}] authentication failed on reconnected socket. Disconnecting and reconnecting");
_failedAuthenticationDisconnectAndReconnect = LoggerMessage.Define<int>( _authenticationSucceeded = LoggerMessage.Define<int>(
LogLevel.Warning, LogLevel.Debug,
new EventId(2021, "FailedAuthentication"), new EventId(2022, "AuthenticationSucceeded"),
"[Sckt {SocketId}] authentication failed on reconnected socket. Disconnecting and reconnecting"); "[Sckt {SocketId}] authentication succeeded on reconnected socket");
_authenticationSucceeded = LoggerMessage.Define<int>( _failedRequestRevitalization = LoggerMessage.Define<int, string?>(
LogLevel.Debug, LogLevel.Warning,
new EventId(2022, "AuthenticationSucceeded"), new EventId(2023, "FailedRequestRevitalization"),
"[Sckt {SocketId}] authentication succeeded on reconnected socket"); "[Sckt {SocketId}] failed request revitalization: {ErrorMessage}");
_failedRequestRevitalization = LoggerMessage.Define<int, string?>( _allSubscriptionResubscribed = LoggerMessage.Define<int>(
LogLevel.Warning, LogLevel.Debug,
new EventId(2023, "FailedRequestRevitalization"), new EventId(2024, "AllSubscriptionResubscribed"),
"[Sckt {SocketId}] failed request revitalization: {ErrorMessage}"); "[Sckt {SocketId}] all subscription successfully resubscribed on reconnected socket");
_allSubscriptionResubscribed = LoggerMessage.Define<int>( _subscriptionUnsubscribed = LoggerMessage.Define<int, int>(
LogLevel.Debug, LogLevel.Information,
new EventId(2024, "AllSubscriptionResubscribed"), new EventId(2025, "SubscriptionUnsubscribed"),
"[Sckt {SocketId}] all subscription successfully resubscribed on reconnected socket"); "[Sckt {SocketId}] subscription {SubscriptionId} unsubscribed");
_subscriptionUnsubscribed = LoggerMessage.Define<int, int>( _sendingPeriodic = LoggerMessage.Define<int, string>(
LogLevel.Information, LogLevel.Trace,
new EventId(2025, "SubscriptionUnsubscribed"), new EventId(2026, "SendingPeriodic"),
"[Sckt {SocketId}] subscription {SubscriptionId} unsubscribed"); "[Sckt {SocketId}] sending periodic {Identifier}");
_sendingPeriodic = LoggerMessage.Define<int, string>( _periodicSendFailed = LoggerMessage.Define<int, string, string>(
LogLevel.Trace, LogLevel.Warning,
new EventId(2026, "SendingPeriodic"), new EventId(2027, "PeriodicSendFailed"),
"[Sckt {SocketId}] sending periodic {Identifier}"); "[Sckt {SocketId}] periodic send {Identifier} failed: {ErrorMessage}");
_periodicSendFailed = LoggerMessage.Define<int, string, string>( _sendingData = LoggerMessage.Define<int, int, string>(
LogLevel.Warning, LogLevel.Trace,
new EventId(2027, "PeriodicSendFailed"), new EventId(2028, "SendingData"),
"[Sckt {SocketId}] periodic send {Identifier} failed: {ErrorMessage}"); "[Sckt {SocketId}] [Req {RequestId}] sending message: {Data}");
_sendingData = LoggerMessage.Define<int, int, string>( _receivedMessageNotMatchedToAnyListener = LoggerMessage.Define<int, string, string>(
LogLevel.Trace, LogLevel.Warning,
new EventId(2028, "SendingData"), new EventId(2029, "ReceivedMessageNotMatchedToAnyListener"),
"[Sckt {SocketId}] [Req {RequestId}] sending message: {Data}"); "[Sckt {SocketId}] received message not matched to any listener. ListenId: {ListenId}, current listeners: [{ListenIds}]");
_receivedMessageNotMatchedToAnyListener = LoggerMessage.Define<int, string, string>( _failedToParse = LoggerMessage.Define<int, string>(
LogLevel.Warning, LogLevel.Warning,
new EventId(2029, "ReceivedMessageNotMatchedToAnyListener"), new EventId(2030, "FailedToParse"),
"[Sckt {SocketId}] received message not matched to any listener. ListenId: {ListenId}, current listeners: [{ListenIds}]"); "[Sckt {SocketId}] failed to parse data: {Error}");
_failedToParse = LoggerMessage.Define<int, string>( _sendingByteData = LoggerMessage.Define<int, int, int>(
LogLevel.Warning, LogLevel.Trace,
new EventId(2030, "FailedToParse"), new EventId(2031, "SendingByteData"),
"[Sckt {SocketId}] failed to parse data: {Error}"); "[Sckt {SocketId}] [Req {RequestId}] sending byte message of length: {Length}");
_sendingByteData = LoggerMessage.Define<int, int, int>( _processorMatched = LoggerMessage.Define<int, string, string>(
LogLevel.Trace, LogLevel.Trace,
new EventId(2031, "SendingByteData"), new EventId(2032, "ProcessorMatched"),
"[Sckt {SocketId}] [Req {RequestId}] sending byte message of length: {Length}"); "[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) public static void SocketStatusChanged(this ILogger logger, int socketId, Sockets.SocketConnection.SocketStatus oldStatus, Sockets.SocketConnection.SocketStatus newStatus)
{ {
_activityPaused(logger, socketId, paused, null); _socketStatusChanged(logger, socketId, oldStatus, newStatus, null);
} }
public static void SocketStatusChanged(this ILogger logger, int socketId, Sockets.SocketConnection.SocketStatus oldStatus, Sockets.SocketConnection.SocketStatus newStatus) public static void FailedReconnectProcessing(this ILogger logger, int socketId, string? error)
{ {
_socketStatusChanged(logger, socketId, oldStatus, newStatus, null); _failedReconnectProcessing(logger, socketId, error, null);
} }
public static void FailedReconnectProcessing(this ILogger logger, int socketId, string? error) public static void UnknownExceptionWhileProcessingReconnection(this ILogger logger, int socketId, Exception e)
{ {
_failedReconnectProcessing(logger, socketId, error, null); _unknownExceptionWhileProcessingReconnection(logger, socketId, e);
} }
public static void UnknownExceptionWhileProcessingReconnection(this ILogger logger, int socketId, Exception e) public static void WebSocketErrorCodeAndDetails(this ILogger logger, int socketId, WebSocketError error, string? details, Exception e)
{ {
_unknownExceptionWhileProcessingReconnection(logger, socketId, e); _webSocketErrorCodeAndDetails(logger, socketId, error, details, e);
} }
public static void WebSocketErrorCodeAndDetails(this ILogger logger, int socketId, WebSocketError error, string? details, Exception e) public static void WebSocketError(this ILogger logger, int socketId, string? errorMessage, Exception e)
{ {
_webSocketErrorCodeAndDetails(logger, socketId, error, details, e); _webSocketError(logger, socketId, errorMessage, e);
} }
public static void WebSocketError(this ILogger logger, int socketId, string? errorMessage, Exception e) public static void MessageSentNotPending(this ILogger logger, int socketId, int requestId)
{ {
_webSocketError(logger, socketId, errorMessage, e); _messageSentNotPending(logger, socketId, requestId, null);
} }
public static void MessageSentNotPending(this ILogger logger, int socketId, int requestId) public static void ReceivedData(this ILogger logger, int socketId, string originalData)
{ {
_messageSentNotPending(logger, socketId, requestId, null); _receivedData(logger, socketId, originalData, null);
} }
public static void ReceivedData(this ILogger logger, int socketId, string originalData) public static void FailedToParse(this ILogger logger, int socketId, string error)
{ {
_receivedData(logger, socketId, originalData, null); _failedToParse(logger, socketId, error, null);
} }
public static void FailedToParse(this ILogger logger, int socketId, string error) public static void FailedToEvaluateMessage(this ILogger logger, int socketId, string originalData)
{ {
_failedToParse(logger, socketId, error, null); _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) public static void NothingToResubscribeCloseConnection(this ILogger logger, int socketId)
{ {
_failedToEvaluateMessage(logger, socketId, originalData, null); _nothingToResubscribeCloseConnection(logger, socketId, null);
} }
public static void ErrorProcessingMessage(this ILogger logger, int socketId, Exception e) public static void FailedAuthenticationDisconnectAndRecoonect(this ILogger logger, int socketId)
{ {
_errorProcessingMessage(logger, socketId, e); _failedAuthenticationDisconnectAndReconnect(logger, socketId, null);
} }
public static void ProcessorMatched(this ILogger logger, int socketId, string listener, string listenerId) public static void AuthenticationSucceeded(this ILogger logger, int socketId)
{ {
_processorMatched(logger, socketId, listener, listenerId, null); _authenticationSucceeded(logger, socketId, null);
} }
public static void ReceivedMessageNotRecognized(this ILogger logger, int socketId, int id) public static void FailedRequestRevitalization(this ILogger logger, int socketId, string? errorMessage)
{ {
_receivedMessageNotRecognized(logger, socketId, id, null); _failedRequestRevitalization(logger, socketId, errorMessage, null);
} }
public static void FailedToDeserializeMessage(this ILogger logger, int socketId, string? errorMessage, Exception? ex) public static void AllSubscriptionResubscribed(this ILogger logger, int socketId)
{ {
_failedToDeserializeMessage(logger, socketId, errorMessage, ex); _allSubscriptionResubscribed(logger, socketId, null);
} }
public static void UserMessageProcessingFailed(this ILogger logger, int socketId, string errorMessage, Exception e) public static void SubscriptionUnsubscribed(this ILogger logger, int socketId, int subscriptionId)
{ {
_userMessageProcessingFailed(logger, socketId, errorMessage, e); _subscriptionUnsubscribed(logger, socketId, subscriptionId, null);
} }
public static void MessageProcessed(this ILogger logger, int socketId, long processingTime, long parsingTime) public static void SendingPeriodic(this ILogger logger, int socketId, string identifier)
{ {
_messageProcessed(logger, socketId, processingTime, parsingTime, null); _sendingPeriodic(logger, socketId, identifier, null);
} }
public static void ClosingSubscription(this ILogger logger, int socketId, int subscriptionId) public static void PeriodicSendFailed(this ILogger logger, int socketId, string identifier, string errorMessage, Exception e)
{ {
_closingSubscription(logger, socketId, subscriptionId, null); _periodicSendFailed(logger, socketId, identifier, errorMessage, e);
} }
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) public static void SendingData(this ILogger logger, int socketId, int requestId, string data)
{ {
_nothingToResubscribeCloseConnection(logger, socketId, null); _sendingData(logger, socketId, requestId, data, 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) public static void ReceivedMessageNotMatchedToAnyListener(this ILogger logger, int socketId, string listenId, string listenIds)
{ {
_sendingData(logger, socketId, requestId, data, null); _receivedMessageNotMatchedToAnyListener(logger, socketId, listenId, listenIds, null);
} }
public static void ReceivedMessageNotMatchedToAnyListener(this ILogger logger, int socketId, string listenId, string listenIds) public static void SendingByteData(this ILogger logger, int socketId, int requestId, int length)
{ {
_receivedMessageNotMatchedToAnyListener(logger, socketId, listenId, listenIds, null); _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 CryptoExchange.Net.Objects;
using Microsoft.Extensions.Logging; 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 #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; _orderBookStatusChanged = LoggerMessage.Define<string, string, OrderBookStatus, OrderBookStatus>(
private static readonly Action<ILogger, string, string, Exception?> _orderBookStarting; LogLevel.Information,
private static readonly Action<ILogger, string, string, Exception?> _orderBookStoppedStarting; new EventId(5000, "OrderBookStatusChanged"),
private static readonly Action<ILogger, string, string, Exception?> _orderBookStopping; "{Api} order book {Symbol} status changed: {PreviousStatus} => {NewStatus}");
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() _orderBookStarting = LoggerMessage.Define<string, string>(
{ LogLevel.Debug,
_orderBookStatusChanged = LoggerMessage.Define<string, string, OrderBookStatus, OrderBookStatus>( new EventId(5001, "OrderBookStarting"),
LogLevel.Information, "{Api} order book {Symbol} starting");
new EventId(5000, "OrderBookStatusChanged"),
"{Api} order book {Symbol} status changed: {PreviousStatus} => {NewStatus}");
_orderBookStarting = LoggerMessage.Define<string, string>( _orderBookStoppedStarting = LoggerMessage.Define<string, string>(
LogLevel.Debug, LogLevel.Debug,
new EventId(5001, "OrderBookStarting"), new EventId(5002, "OrderBookStoppedStarting"),
"{Api} order book {Symbol} starting"); "{Api} order book {Symbol} stopped while starting");
_orderBookStoppedStarting = LoggerMessage.Define<string, string>( _orderBookConnectionLost = LoggerMessage.Define<string, string>(
LogLevel.Debug, LogLevel.Warning,
new EventId(5002, "OrderBookStoppedStarting"), new EventId(5003, "OrderBookConnectionLost"),
"{Api} order book {Symbol} stopped while starting"); "{Api} order book {Symbol} connection lost");
_orderBookConnectionLost = LoggerMessage.Define<string, string>( _orderBookDisconnected = LoggerMessage.Define<string, string>(
LogLevel.Warning, LogLevel.Debug,
new EventId(5003, "OrderBookConnectionLost"), new EventId(5004, "OrderBookDisconnected"),
"{Api} order book {Symbol} connection lost"); "{Api} order book {Symbol} disconnected");
_orderBookDisconnected = LoggerMessage.Define<string, string>( _orderBookStopping = LoggerMessage.Define<string, string>(
LogLevel.Debug, LogLevel.Debug,
new EventId(5004, "OrderBookDisconnected"), new EventId(5005, "OrderBookStopping"),
"{Api} order book {Symbol} disconnected"); "{Api} order book {Symbol} stopping");
_orderBookStopping = LoggerMessage.Define<string, string>( _orderBookStopped = LoggerMessage.Define<string, string>(
LogLevel.Debug, LogLevel.Trace,
new EventId(5005, "OrderBookStopping"), new EventId(5006, "OrderBookStopped"),
"{Api} order book {Symbol} stopping"); "{Api} order book {Symbol} stopped");
_orderBookStopped = LoggerMessage.Define<string, string>( _orderBookProcessingBufferedUpdates = LoggerMessage.Define<string, string, int>(
LogLevel.Trace, LogLevel.Debug,
new EventId(5006, "OrderBookStopped"), new EventId(5007, "OrderBookProcessingBufferedUpdates"),
"{Api} order book {Symbol} stopped"); "{Api} order book {Symbol} Processing {NumberBufferedUpdated} buffered updates");
_orderBookProcessingBufferedUpdates = LoggerMessage.Define<string, string, int>( _orderBookUpdateSkipped = LoggerMessage.Define<string, string, long, long>(
LogLevel.Debug, LogLevel.Debug,
new EventId(5007, "OrderBookProcessingBufferedUpdates"), new EventId(5008, "OrderBookUpdateSkipped"),
"{Api} order book {Symbol} Processing {NumberBufferedUpdated} buffered updates"); "{Api} order book {Symbol} update skipped #{SequenceNumber}, currently at #{LastSequenceNumber}");
_orderBookUpdateSkipped = LoggerMessage.Define<string, string, long, long>( _orderBookOutOfSync = LoggerMessage.Define<string, string, long, long>(
LogLevel.Debug, LogLevel.Warning,
new EventId(5008, "OrderBookUpdateSkipped"), new EventId(5009, "OrderBookOutOfSync"),
"{Api} order book {Symbol} update skipped #{SequenceNumber}, currently at #{LastSequenceNumber}"); "{Api} order book {Symbol} out of sync (expected {ExpectedSequenceNumber}, was {SequenceNumber}), reconnecting");
_orderBookOutOfSync = LoggerMessage.Define<string, string, long, long>( _orderBookResynced = LoggerMessage.Define<string, string>(
LogLevel.Warning, LogLevel.Information,
new EventId(5009, "OrderBookOutOfSync"), new EventId(5010, "OrderBookResynced"),
"{Api} order book {Symbol} out of sync (expected {ExpectedSequenceNumber}, was {SequenceNumber}), reconnecting"); "{Api} order book {Symbol} successfully resynchronized");
_orderBookResynced = LoggerMessage.Define<string, string>( _orderBookMessageSkippedBecauseOfResubscribing = LoggerMessage.Define<string, string>(
LogLevel.Information, LogLevel.Trace,
new EventId(5010, "OrderBookResynced"), new EventId(5011, "OrderBookMessageSkippedResubscribing"),
"{Api} order book {Symbol} successfully resynchronized"); "{Api} order book {Symbol} Skipping message because of resubscribing");
_orderBookMessageSkippedBecauseOfResubscribing = LoggerMessage.Define<string, string>( _orderBookDataSet = LoggerMessage.Define<string, string, long, long, long>(
LogLevel.Trace, LogLevel.Debug,
new EventId(5011, "OrderBookMessageSkippedResubscribing"), new EventId(5012, "OrderBookDataSet"),
"{Api} order book {Symbol} Skipping message because of resubscribing"); "{Api} order book {Symbol} data set: {BidCount} bids, {AskCount} asks. #{EndUpdateId}");
_orderBookDataSet = LoggerMessage.Define<string, string, long, long, long>( _orderBookUpdateBuffered = LoggerMessage.Define<string, string, long, long, long, long>(
LogLevel.Debug, LogLevel.Trace,
new EventId(5012, "OrderBookDataSet"), new EventId(5013, "OrderBookUpdateBuffered"),
"{Api} order book {Symbol} data set: {BidCount} bids, {AskCount} asks. #{EndUpdateId}"); "{Api} order book {Symbol} update buffered #{StartUpdateId}-#{EndUpdateId} [{AsksCount} asks, {BidsCount} bids]");
_orderBookUpdateBuffered = LoggerMessage.Define<string, string, long, long, long, long>( _orderBookOutOfSyncDetected = LoggerMessage.Define<string, string, decimal, decimal>(
LogLevel.Trace, LogLevel.Warning,
new EventId(5013, "OrderBookUpdateBuffered"), new EventId(5014, "OrderBookOutOfSyncDetected"),
"{Api} order book {Symbol} update buffered #{StartUpdateId}-#{EndUpdateId} [{AsksCount} asks, {BidsCount} bids]"); "{Api} order book {Symbol} detected out of sync order book. First ask: {FirstAsk}, first bid: {FirstBid}. Resyncing");
_orderBookOutOfSyncDetected = LoggerMessage.Define<string, string, decimal, decimal>( _orderBookReconnectingSocket = LoggerMessage.Define<string, string>(
LogLevel.Warning, LogLevel.Warning,
new EventId(5014, "OrderBookOutOfSyncDetected"), new EventId(5015, "OrderBookReconnectingSocket"),
"{Api} order book {Symbol} detected out of sync order book. First ask: {FirstAsk}, first bid: {FirstBid}. Resyncing"); "{Api} order book {Symbol} out of sync. Reconnecting socket");
_orderBookReconnectingSocket = LoggerMessage.Define<string, string>( _orderBookResyncing = LoggerMessage.Define<string, string>(
LogLevel.Warning, LogLevel.Warning,
new EventId(5015, "OrderBookReconnectingSocket"), new EventId(5016, "OrderBookResyncing"),
"{Api} order book {Symbol} out of sync. Reconnecting socket"); "{Api} order book {Symbol} out of sync. Resyncing");
_orderBookResyncing = LoggerMessage.Define<string, string>( _orderBookResyncFailed = LoggerMessage.Define<string, string>(
LogLevel.Warning, LogLevel.Warning,
new EventId(5016, "OrderBookResyncing"), new EventId(5017, "OrderBookResyncFailed"),
"{Api} order book {Symbol} out of sync. Resyncing"); "{Api} order book {Symbol} resync failed, reconnecting socket");
_orderBookResyncFailed = LoggerMessage.Define<string, string>( _orderBookSkippedMessage = LoggerMessage.Define<string, string, long, long>(
LogLevel.Warning, LogLevel.Trace,
new EventId(5017, "OrderBookResyncFailed"), new EventId(5018, "OrderBookSkippedMessage"),
"{Api} order book {Symbol} resync failed, reconnecting socket"); "{Api} order book {Symbol} update skipped #{FirstUpdateId}-{LastUpdateId}");
_orderBookSkippedMessage = LoggerMessage.Define<string, string, long, long>( _orderBookProcessedMessage = LoggerMessage.Define<string, string, long, long>(
LogLevel.Trace, LogLevel.Trace,
new EventId(5018, "OrderBookSkippedMessage"), new EventId(5019, "OrderBookProcessedMessage"),
"{Api} order book {Symbol} update skipped #{FirstUpdateId}-{LastUpdateId}"); "{Api} order book {Symbol} update processed #{FirstUpdateId}-{LastUpdateId}");
_orderBookProcessedMessage = LoggerMessage.Define<string, string, long, long>( _orderBookOutOfSyncChecksum = LoggerMessage.Define<string, string>(
LogLevel.Trace, LogLevel.Warning,
new EventId(5019, "OrderBookProcessedMessage"), new EventId(5020, "OrderBookOutOfSyncChecksum"),
"{Api} order book {Symbol} update processed #{FirstUpdateId}-{LastUpdateId}"); "{Api} order book {Symbol} out of sync. Checksum mismatch, resyncing");
}
_orderBookOutOfSyncChecksum = LoggerMessage.Define<string, string>( public static void OrderBookStatusChanged(this ILogger logger, string api, string symbol, OrderBookStatus previousStatus, OrderBookStatus newStatus)
LogLevel.Warning, {
new EventId(5020, "OrderBookOutOfSyncChecksum"), _orderBookStatusChanged(logger, api, symbol, previousStatus, newStatus, null);
"{Api} order book {Symbol} out of sync. Checksum mismatch, resyncing"); }
} 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) public static void OrderBookDisconnected(this ILogger logger, string api, string symbol)
{ {
_orderBookStatusChanged(logger, api, symbol, previousStatus, newStatus, null); _orderBookDisconnected(logger, api, symbol, 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) public static void OrderBookStopping(this ILogger logger, string api, string symbol)
{ {
_orderBookDisconnected(logger, api, symbol, null); _orderBookStopping(logger, api, symbol, null);
} }
public static void OrderBookStopping(this ILogger logger, string api, string symbol) public static void OrderBookStopped(this ILogger logger, string api, string symbol)
{ {
_orderBookStopping(logger, api, symbol, null); _orderBookStopped(logger, api, symbol, null);
} }
public static void OrderBookStopped(this ILogger logger, string api, string symbol) public static void OrderBookProcessingBufferedUpdates(this ILogger logger, string api, string symbol, int numberBufferedUpdated)
{ {
_orderBookStopped(logger, api, symbol, null); _orderBookProcessingBufferedUpdates(logger, api, symbol, numberBufferedUpdated, null);
} }
public static void OrderBookProcessingBufferedUpdates(this ILogger logger, string api, string symbol, int numberBufferedUpdated) public static void OrderBookUpdateSkipped(this ILogger logger, string api, string symbol, long sequence, long lastSequenceNumber)
{ {
_orderBookProcessingBufferedUpdates(logger, api, symbol, numberBufferedUpdated, null); _orderBookUpdateSkipped(logger, api, symbol, sequence, lastSequenceNumber, null);
} }
public static void OrderBookUpdateSkipped(this ILogger logger, string api, string symbol, long sequence, long lastSequenceNumber) public static void OrderBookOutOfSync(this ILogger logger, string api, string symbol, long expectedSequenceNumber, long sequenceNumber)
{ {
_orderBookUpdateSkipped(logger, api, symbol, sequence, lastSequenceNumber, null); _orderBookOutOfSync(logger, api, symbol, expectedSequenceNumber, sequenceNumber, null);
} }
public static void OrderBookOutOfSync(this ILogger logger, string api, string symbol, long expectedSequenceNumber, long sequenceNumber) public static void OrderBookResynced(this ILogger logger, string api, string symbol)
{ {
_orderBookOutOfSync(logger, api, symbol, expectedSequenceNumber, sequenceNumber, null); _orderBookResynced(logger, api, symbol, null);
} }
public static void OrderBookResynced(this ILogger logger, string api, string symbol) public static void OrderBookMessageSkippedResubscribing(this ILogger logger, string api, string symbol)
{ {
_orderBookResynced(logger, api, symbol, null); _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) public static void OrderBookReconnectingSocket(this ILogger logger, string api, string symbol)
{ {
_orderBookMessageSkippedBecauseOfResubscribing(logger, api, symbol, null); _orderBookReconnectingSocket(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) public static void OrderBookResyncing(this ILogger logger, string api, string symbol)
{ {
_orderBookReconnectingSocket(logger, api, symbol, null); _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) public static void OrderBookOutOfSyncChecksum(this ILogger logger, string api, string symbol)
{ {
_orderBookResyncing(logger, api, symbol, null); _orderBookOutOfSyncChecksum(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);
}
} }
} }

View File

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

View File

@ -1,42 +1,41 @@
namespace CryptoExchange.Net.Objects namespace CryptoExchange.Net.Objects;
/// <summary>
/// Proxy info
/// </summary>
public class ApiProxy
{ {
/// <summary> /// <summary>
/// Proxy info /// The host address of the proxy
/// </summary> /// </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> Host = host;
/// The host address of the proxy Port = port;
/// </summary> Login = login;
public string Host { get; set; } Password = password;
/// <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;
}
} }
} }

View File

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

View File

@ -1,34 +1,30 @@
using System;
using System.Collections.Generic;
using System.Linq; 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> /// <summary>
/// Exchange configuration for asset aliases /// Defined aliases
/// </summary> /// </summary>
public class AssetAliasConfiguration public AssetAlias[] Aliases { get; set; } = [];
{
/// <summary>
/// Defined aliases
/// </summary>
public AssetAlias[] Aliases { get; set; } = [];
/// <summary> /// <summary>
/// Auto convert asset names when using the Shared interfaces. Defaults to true /// Auto convert asset names when using the Shared interfaces. Defaults to true
/// </summary> /// </summary>
public bool AutoConvertEnabled { get; set; } = true; public bool AutoConvertEnabled { get; set; } = true;
/// <summary> /// <summary>
/// Map the common name to an exchange name for an asset. If there is no alias the input name is returned /// Map the common name to an exchange name for an asset. If there is no alias the input name is returned
/// </summary> /// </summary>
public string CommonToExchangeName(string commonName) => !AutoConvertEnabled ? commonName : Aliases.SingleOrDefault(x => x.CommonAssetName == commonName)?.ExchangeAssetName ?? commonName; public string CommonToExchangeName(string commonName) => !AutoConvertEnabled ? commonName : Aliases.SingleOrDefault(x => x.CommonAssetName == commonName)?.ExchangeAssetName ?? commonName;
/// <summary> /// <summary>
/// Map the exchange name to a common name for an asset. If there is no alias the input name is returned /// Map the exchange name to a common name for an asset. If there is no alias the input name is returned
/// </summary> /// </summary>
public string ExchangeToCommonName(string exchangeName) => !AutoConvertEnabled ? exchangeName : Aliases.SingleOrDefault(x => x.ExchangeAssetName == exchangeName)?.CommonAssetName ?? exchangeName; 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.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; 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> /// <summary>
/// Async auto reset based on Stephen Toub`s implementation /// New AsyncResetEvent
/// https://devblogs.microsoft.com/pfxteam/building-async-coordination-primitives-part-2-asyncautoresetevent/
/// </summary> /// </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); _signaled = initialState;
private Queue<TaskCompletionSource<bool>> _waits = new Queue<TaskCompletionSource<bool>>(); _reset = reset;
private bool _signaled; }
private readonly bool _reset;
/// <summary> /// <summary>
/// New AsyncResetEvent /// Wait for the AutoResetEvent to be set
/// </summary> /// </summary>
/// <param name="initialState"></param> /// <returns></returns>
/// <param name="reset"></param> public async Task<bool> WaitAsync(TimeSpan? timeout = null, CancellationToken ct = default)
public AsyncResetEvent(bool initialState = false, bool reset = true) {
{ CancellationTokenRegistration registration = default;
_signaled = initialState; try
_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()
{ {
Task<bool> waiter = _completed;
lock (_waits) lock (_waits)
{ {
if (!_reset) if (_signaled)
{ {
// Act as ManualResetEvent. Once set keep it signaled and signal everyone who is waiting if (_reset)
_signaled = true; _signaled = false;
while (_waits.Count > 0)
{
var toRelease = _waits.Dequeue();
toRelease.TrySetResult(true);
}
} }
else else if (!ct.IsCancellationRequested)
{ {
// Act as AutoResetEvent. When set signal 1 waiter var tcs = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
if (_waits.Count > 0) if (timeout.HasValue)
{ {
var toRelease = _waits.Dequeue(); var timeoutSource = new CancellationTokenSource(timeout.Value);
toRelease.TrySetResult(true); 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> /// <summary>
/// Dispose /// Dispose
/// </summary> /// </summary>
public void Dispose() public void Dispose()
{ {
_waits.Clear(); _waits.Clear();
}
} }
} }

View File

@ -1,10 +1,9 @@
using CryptoExchange.Net.Interfaces; using CryptoExchange.Net.Interfaces;
using System; 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; using System.Collections.Generic;
namespace CryptoExchange.Net.Objects namespace CryptoExchange.Net.Objects;
/// <summary>
/// Comparer for byte order
/// </summary>
public class ByteOrderComparer : IComparer<byte[]>
{ {
/// <summary> /// <summary>
/// Comparer for byte order /// Compare function
/// </summary> /// </summary>
public class ByteOrderComparer : IComparer<byte[]> /// <param name="x"></param>
/// <param name="y"></param>
/// <returns></returns>
public int Compare(byte[]? x, byte[]? y)
{ {
/// <summary> // Shortcuts: If both are null, they are the same.
/// Compare function if (x == null && y == null) return 0;
/// </summary>
/// <param name="x"></param> // If one is null and the other isn't, then the
/// <param name="y"></param> // one that is null is "lesser".
/// <returns></returns> if (x == null) return -1;
public int Compare(byte[]? x, byte[]? y) 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. // The x and y bytes.
if (x == null && y == null) return 0; var xByte = x[index];
var yByte = y[index];
// If one is null and the other isn't, then the // Compare result.
// one that is null is "lesser". var compareResult = Comparer<byte>.Default.Compare(xByte, yByte);
if (x == null) return -1;
if (y == null) return 1;
// Both arrays are non-null. Find the shorter // If not the same, then return the result of the
// of the two lengths. // comparison of the bytes, as they were the same
var bytesToCompare = Math.Min(x.Length, y.Length); // up until now.
if (compareResult != 0) return compareResult;
// Compare the bytes. // They are the same, continue.
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;
} }
// 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> /// <summary>
/// Constants /// Json content type header
/// </summary> /// </summary>
public class Constants public const string JsonContentHeader = "application/json";
{ /// <summary>
/// <summary> /// Form content type header
/// Json content type header /// </summary>
/// </summary> public const string FormContentHeader = "application/x-www-form-urlencoded";
public const string JsonContentHeader = "application/json"; /// <summary>
/// <summary> /// Placeholder key for when request body should be set to the value of this KVP
/// Form content type header /// </summary>
/// </summary> public const string BodyPlaceHolderKey = "_BODY_";
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> /// <summary>
/// What to do when a request would exceed the rate limit /// Fail the request
/// </summary> /// </summary>
public enum RateLimitingBehaviour Fail,
{
/// <summary>
/// Fail the request
/// </summary>
Fail,
/// <summary>
/// Wait till the request can be send
/// </summary>
Wait
}
/// <summary> /// <summary>
/// What to do when a request would exceed the rate limit /// Wait till the request can be send
/// </summary> /// </summary>
public enum RateLimitWindowType Wait
{ }
/// <summary>
/// A sliding window /// <summary>
/// </summary> /// What to do when a request would exceed the rate limit
Sliding, /// </summary>
/// <summary> public enum RateLimitWindowType
/// A fixed interval window {
/// </summary> /// <summary>
Fixed, /// A sliding window
/// <summary> /// </summary>
/// A fixed interval starting after the first request Sliding,
/// </summary> /// <summary>
FixedAfterFirst, /// A fixed interval window
/// <summary> /// </summary>
/// Decaying window Fixed,
/// </summary> /// <summary>
Decay /// A fixed interval starting after the first request
} /// </summary>
FixedAfterFirst,
/// <summary> /// <summary>
/// Where the parameters for a HttpMethod should be added in a request /// Decaying window
/// </summary> /// </summary>
public enum HttpMethodParameterPosition Decay
{ }
/// <summary>
/// Parameters in body /// <summary>
/// </summary> /// Where the parameters for a HttpMethod should be added in a request
InBody, /// </summary>
/// <summary> public enum HttpMethodParameterPosition
/// Parameters in url {
/// </summary> /// <summary>
InUri /// Parameters in body
} /// </summary>
InBody,
/// <summary> /// <summary>
/// The format of the request body /// Parameters in url
/// </summary> /// </summary>
public enum RequestBodyFormat InUri
{ }
/// <summary>
/// Form data /// <summary>
/// </summary> /// The format of the request body
FormData, /// </summary>
/// <summary> public enum RequestBodyFormat
/// Json {
/// </summary> /// <summary>
Json /// Form data
} /// </summary>
FormData,
/// <summary> /// <summary>
/// Tracker sync status /// Json
/// </summary> /// </summary>
public enum SyncStatus Json
{ }
/// <summary>
/// Not connected /// <summary>
/// </summary> /// Tracker sync status
Disconnected, /// </summary>
/// <summary> public enum SyncStatus
/// Syncing, data connection is being made {
/// </summary> /// <summary>
Syncing, /// Not connected
/// <summary> /// </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. Disconnected,
/// </summary> /// <summary>
PartiallySynced, /// Syncing, data connection is being made
/// <summary> /// </summary>
/// Synced Syncing,
/// </summary> /// <summary>
Synced, /// 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> /// </summary>
/// Disposed PartiallySynced,
/// </summary> /// <summary>
Disposed /// Synced
} /// </summary>
Synced,
/// <summary> /// <summary>
/// Status of the order book /// Disposed
/// </summary> /// </summary>
public enum OrderBookStatus Disposed
{ }
/// <summary>
/// Not connected /// <summary>
/// </summary> /// Status of the order book
Disconnected, /// </summary>
/// <summary> public enum OrderBookStatus
/// Connecting {
/// </summary> /// <summary>
Connecting, /// Not connected
/// <summary> /// </summary>
/// Reconnecting Disconnected,
/// </summary> /// <summary>
Reconnecting, /// Connecting
/// <summary> /// </summary>
/// Syncing data Connecting,
/// </summary> /// <summary>
Syncing, /// Reconnecting
/// <summary> /// </summary>
/// Data synced, order book is up to date Reconnecting,
/// </summary> /// <summary>
Synced, /// Syncing data
/// <summary> /// </summary>
/// Disposing Syncing,
/// </summary> /// <summary>
Disposing, /// Data synced, order book is up to date
/// <summary> /// </summary>
/// Disposed Synced,
/// </summary> /// <summary>
Disposed /// Disposing
} /// </summary>
Disposing,
/// <summary> /// <summary>
/// Order book entry type /// Disposed
/// </summary> /// </summary>
public enum OrderBookEntryType Disposed
{ }
/// <summary>
/// Ask /// <summary>
/// </summary> /// Order book entry type
Ask, /// </summary>
/// <summary> public enum OrderBookEntryType
/// Bid {
/// </summary> /// <summary>
Bid /// Ask
} /// </summary>
Ask,
/// <summary> /// <summary>
/// Define how array parameters should be send /// Bid
/// </summary> /// </summary>
public enum ArrayParametersSerialization Bid
#pragma warning disable CS1570 // XML comment has badly formed XML }
{
/// <summary> /// <summary>
/// Send as key=value1&key=value2 /// Define how array parameters should be send
/// </summary> /// </summary>
MultipleValues, public enum ArrayParametersSerialization
#pragma warning disable CS1570 // XML comment has badly formed XML
/// <summary> {
/// Send as key[]=value1&key[]=value2 /// <summary>
/// </summary> /// Send as key=value1&key=value2
Array, /// </summary>
/// <summary> MultipleValues,
/// Send as key=[value1, value2]
/// </summary> /// <summary>
JsonArray /// Send as key[]=value1&key[]=value2
#pragma warning restore CS1570 // XML comment has badly formed XML /// </summary>
} Array,
/// <summary>
/// <summary> /// Send as key=[value1, value2]
/// How to round /// </summary>
/// </summary> JsonArray
public enum RoundingType #pragma warning restore CS1570 // XML comment has badly formed XML
{ }
/// <summary>
/// Round down (flooring) /// <summary>
/// </summary> /// How to round
Down, /// </summary>
/// <summary> public enum RoundingType
/// Round to closest value {
/// </summary> /// <summary>
Closest, /// Round down (flooring)
/// <summary> /// </summary>
/// Round up (ceil) Down,
/// </summary> /// <summary>
Up /// Round to closest value
} /// </summary>
Closest,
/// <summary> /// <summary>
/// Type of the update /// Round up (ceil)
/// </summary> /// </summary>
public enum SocketUpdateType Up
{ }
/// <summary>
/// A update /// <summary>
/// </summary> /// Type of the update
Update, /// </summary>
/// <summary> public enum SocketUpdateType
/// A snapshot, generally send at the start of the connection {
/// </summary> /// <summary>
Snapshot /// A update
} /// </summary>
Update,
/// <summary> /// <summary>
/// Reconnect policy /// A snapshot, generally send at the start of the connection
/// </summary> /// </summary>
public enum ReconnectPolicy Snapshot
{ }
/// <summary>
/// Reconnect is disabled /// <summary>
/// </summary> /// Reconnect policy
Disabled, /// </summary>
/// <summary> public enum ReconnectPolicy
/// Fixed delay of `ReconnectInterval` between retries {
/// </summary> /// <summary>
FixedDelay, /// Reconnect is disabled
/// <summary> /// </summary>
/// Backoff policy of 2^`reconnectAttempt`, where `reconnectAttempt` has a max value of 5 Disabled,
/// </summary> /// <summary>
ExponentialBackoff /// Fixed delay of `ReconnectInterval` between retries
} /// </summary>
FixedDelay,
/// <summary> /// <summary>
/// The data source of the result /// Backoff policy of 2^`reconnectAttempt`, where `reconnectAttempt` has a max value of 5
/// </summary> /// </summary>
public enum ResultDataSource ExponentialBackoff
{ }
/// <summary>
/// From server /// <summary>
/// </summary> /// The data source of the result
Server, /// </summary>
/// <summary> public enum ResultDataSource
/// From cache {
/// </summary> /// <summary>
Cache /// From server
} /// </summary>
Server,
/// <summary> /// <summary>
/// Type of exchange /// From cache
/// </summary> /// </summary>
public enum ExchangeType Cache
{ }
/// <summary>
/// Centralized /// <summary>
/// </summary> /// Type of exchange
CEX, /// </summary>
/// <summary> public enum ExchangeType
/// Decentralized {
/// </summary> /// <summary>
DEX /// Centralized
} /// </summary>
CEX,
/// <summary> /// <summary>
/// Timeout behavior for queries /// Decentralized
/// </summary> /// </summary>
public enum TimeoutBehavior DEX
{ }
/// <summary>
/// Fail the request /// <summary>
/// </summary> /// Timeout behavior for queries
Fail, /// </summary>
/// <summary> public enum TimeoutBehavior
/// Mark the query as successful {
/// </summary> /// <summary>
Succeed /// 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; using System;
namespace CryptoExchange.Net.Objects namespace CryptoExchange.Net.Objects;
/// <summary>
/// Base class for errors
/// </summary>
public abstract class Error
{ {
private int? _code;
/// <summary> /// <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> /// </summary>
public abstract class Error public int? Code
{ {
get
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)
{ {
ErrorCode = errorCode; if (_code.HasValue)
ErrorType = errorInfo.ErrorType; return _code;
Message = errorInfo.Message;
ErrorDescription = errorInfo.ErrorDescription;
IsTransient = errorInfo.IsTransient;
Exception = exception;
}
/// <summary> return int.TryParse(ErrorCode, out var r) ? r : null;
/// String representation }
/// </summary> set
/// <returns></returns>
public override string ToString()
{ {
return ErrorCode != null ? $"[{GetType().Name}.{ErrorType}] {ErrorCode}: {Message ?? ErrorDescription}" : $"[{GetType().Name}.{ErrorType}] {Message ?? ErrorDescription}"; _code = value;
} }
} }
/// <summary> /// <summary>
/// Cant reach server error /// The error code returned by the server
/// </summary> /// </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> ErrorCode = errorCode;
/// Default error info ErrorType = errorInfo.ErrorType;
/// </summary> Message = errorInfo.Message;
protected static readonly ErrorInfo _errorInfo = new ErrorInfo(ErrorType.UnableToConnect, false, "Can't connect to the server"); ErrorDescription = errorInfo.ErrorDescription;
IsTransient = errorInfo.IsTransient;
/// <summary> Exception = exception;
/// 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> /// <summary>
/// No api credentials provided while trying to access a private endpoint /// String representation
/// </summary> /// </summary>
public class NoApiCredentialsError : Error /// <returns></returns>
public override string ToString()
{ {
/// <summary> return ErrorCode != null ? $"[{GetType().Name}.{ErrorType}] {ErrorCode}: {Message ?? ErrorDescription}" : $"[{GetType().Name}.{ErrorType}] {Message ?? ErrorDescription}";
/// 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) { }
} }
} }
/// <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;
using System.Collections.Generic;
using System.Text;
namespace CryptoExchange.Net.Objects.Errors namespace CryptoExchange.Net.Objects.Errors;
/// <summary>
/// Error evaluator
/// </summary>
public class ErrorEvaluator
{ {
/// <summary> /// <summary>
/// Error evaluator /// Error code
/// </summary> /// </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> ErrorCodes = [errorCode];
/// Error code ErrorTypeEvaluator = errorTypeEvaluator;
/// </summary> }
public string[] ErrorCodes { get; set; }
/// <summary> /// <summary>
/// Evaluation callback for determining the error type /// ctor
/// </summary> /// </summary>
public Func<string, string?, ErrorInfo> ErrorTypeEvaluator { get; set; } public ErrorEvaluator(string[] errorCodes, Func<string, string?, ErrorInfo> errorTypeEvaluator)
{
/// <summary> ErrorCodes = errorCodes;
/// ctor ErrorTypeEvaluator = errorTypeEvaluator;
/// </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;
}
} }
} }

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> /// <summary>
/// Error info /// Unknown error info
/// </summary> /// </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> ErrorCodes = [];
/// Unknown error info ErrorType = errorType;
/// </summary> IsTransient = false;
public static ErrorInfo Unknown { get; } = new ErrorInfo(ErrorType.Unknown, false, "Unknown error", []); ErrorDescription = description;
}
/// <summary> /// <summary>
/// The server error code /// ctor
/// </summary> /// </summary>
public string[] ErrorCodes { get; set; } public ErrorInfo(ErrorType errorType, bool isTransient, string description, params string[] errorCodes)
/// <summary> {
/// Error description ErrorCodes = errorCodes;
/// </summary> ErrorType = errorType;
public string? ErrorDescription { get; set; } IsTransient = isTransient;
/// <summary> ErrorDescription = description;
/// 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;
}
} }
} }

View File

@ -1,54 +1,51 @@
using System; using System;
using System.Collections.Generic; 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> /// <summary>
/// Error mapping collection /// ctor
/// </summary> /// </summary>
public class ErrorMapping public ErrorMapping(ErrorInfo[] errorMappings, ErrorEvaluator[]? errorTypeEvaluators = null)
{ {
private Dictionary<string, ErrorEvaluator> _evaluators = new Dictionary<string, ErrorEvaluator>(); foreach (var item in errorMappings)
private Dictionary<string, ErrorInfo> _directMapping = new Dictionary<string, ErrorInfo>();
/// <summary>
/// ctor
/// </summary>
public ErrorMapping(ErrorInfo[] errorMappings, ErrorEvaluator[]? errorTypeEvaluators = null)
{ {
foreach (var item in errorMappings) if (item.ErrorCodes.Length == 0)
{ throw new Exception("Error codes can't be null in error mapping");
if (!item.ErrorCodes.Any())
throw new Exception("Error codes can't be null in error mapping");
foreach(var code in item.ErrorCodes!) foreach(var code in item.ErrorCodes!)
_directMapping.Add(code, item); _directMapping.Add(code, item);
}
if (errorTypeEvaluators == null)
return;
foreach (var item in errorTypeEvaluators)
{
foreach(var code in item.ErrorCodes)
_evaluators.Add(code, item);
}
} }
/// <summary> if (errorTypeEvaluators == null)
/// Get error info for an error code return;
/// </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 }; 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; namespace CryptoExchange.Net.Objects.Errors;
using System.Collections.Generic;
using System.Text;
namespace CryptoExchange.Net.Objects.Errors /// <summary>
/// Type of error
/// </summary>
public enum ErrorType
{ {
#region Library errors
/// <summary> /// <summary>
/// Type of error /// Failed to connect to server
/// </summary> /// </summary>
public enum ErrorType UnableToConnect,
{ /// <summary>
#region Library errors /// 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> #endregion
/// 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 #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> #endregion
/// 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
}
} }

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> /// <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> /// </summary>
public class ApiOptions public bool? OutputOriginalData { 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> /// <summary>
/// The api credentials used for signing requests to this API. Overrides API credentials provided in the client options /// The api credentials used for signing requests to this API. Overrides API credentials provided in the client options
/// </summary> /// </summary>
public ApiCredentials? ApiCredentials { get; set; } public ApiCredentials? ApiCredentials { get; set; }
}
} }

View File

@ -1,46 +1,45 @@
using CryptoExchange.Net.Authentication; using CryptoExchange.Net.Authentication;
using System; using System;
namespace CryptoExchange.Net.Objects.Options namespace CryptoExchange.Net.Objects.Options;
/// <summary>
/// Exchange options
/// </summary>
public class ExchangeOptions
{ {
/// <summary> /// <summary>
/// Exchange options /// Proxy settings
/// </summary> /// </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> return $"RequestTimeout: {RequestTimeout}, Proxy: {(Proxy == null ? "-" : "set")}, ApiCredentials: {(ApiCredentials == null ? "-" : "set")}";
/// 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")}";
}
} }
} }

View File

@ -1,58 +1,57 @@
using CryptoExchange.Net.Authentication; using CryptoExchange.Net.Authentication;
using Microsoft.Extensions.DependencyInjection; 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> /// <summary>
/// Library options /// Rest client options
/// </summary> /// </summary>
/// <typeparam name="TRestOptions"></typeparam> public TRestOptions Rest { get; set; } = new TRestOptions();
/// <typeparam name="TSocketOptions"></typeparam>
/// <typeparam name="TApiCredentials"></typeparam> /// <summary>
/// <typeparam name="TEnvironment"></typeparam> /// Socket client options
public class LibraryOptions<TRestOptions, TSocketOptions, TApiCredentials, TEnvironment> /// </summary>
where TRestOptions: RestExchangeOptions, new() public TSocketOptions Socket { get; set; } = new TSocketOptions();
where TSocketOptions: SocketExchangeOptions, new()
where TApiCredentials: ApiCredentials /// <summary>
where TEnvironment: TradeEnvironment /// 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> targetOptions.ApiCredentials = (TApiCredentials?)ApiCredentials?.Copy();
/// Rest client options targetOptions.Environment = Environment;
/// </summary> targetOptions.SocketClientLifeTime = SocketClientLifeTime;
public TRestOptions Rest { get; set; } = new TRestOptions(); targetOptions.Rest = Rest.Set(targetOptions.Rest);
targetOptions.Socket = Socket.Set(targetOptions.Socket);
/// <summary> return targetOptions;
/// 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;
}
} }
} }

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> /// <summary>
/// Base for order book options /// Whether or not checksum validation is enabled. Default is true, disabling will ignore checksum messages.
/// </summary> /// </summary>
public class OrderBookOptions public bool ChecksumValidationEnabled { get; set; } = true;
{
/// <summary>
/// Whether or not checksum validation is enabled. Default is true, disabling will ignore checksum messages.
/// </summary>
public bool ChecksumValidationEnabled { get; set; } = true;
/// <summary> /// <summary>
/// Create a copy of this options /// Create a copy of this options
/// </summary> /// </summary>
/// <typeparam name="T"></typeparam> /// <typeparam name="T"></typeparam>
/// <returns></returns> /// <returns></returns>
public T Copy<T>() where T : OrderBookOptions, new() 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; using System;
namespace CryptoExchange.Net.Objects.Options namespace CryptoExchange.Net.Objects.Options;
/// <summary>
/// Http api options
/// </summary>
public class RestApiOptions : ApiOptions
{ {
/// <summary> /// <summary>
/// Http api options /// Whether or not to automatically sync the local time with the server time
/// </summary> /// </summary>
public class RestApiOptions : ApiOptions public bool? AutoTimestamp { get; set; }
{
/// <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;
}
}
/// <summary> /// <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> /// </summary>
/// <typeparam name="TApiCredentials"></typeparam> public TimeSpan? TimestampRecalculationInterval { get; set; }
public class RestApiOptions<TApiCredentials>: RestApiOptions where TApiCredentials: ApiCredentials
/// <summary>
/// Set the values of this options on the target options
/// </summary>
public T Set<T>(T item) where T : RestApiOptions, new()
{ {
/// <summary> item.ApiCredentials = ApiCredentials?.Copy();
/// The api credentials used for signing requests to this API. item.OutputOriginalData = OutputOriginalData;
/// </summary> item.AutoTimestamp = AutoTimestamp;
public new TApiCredentials? ApiCredentials item.TimestampRecalculationInterval = TimestampRecalculationInterval;
{ return item;
get => (TApiCredentials?)base.ApiCredentials; }
set => base.ApiCredentials = value; }
}
/// <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; 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> /// <summary>
/// Options for a rest exchange client /// Whether or not to automatically sync the local time with the server time
/// </summary> /// </summary>
public class RestExchangeOptions: ExchangeOptions public bool AutoTimestamp { get; set; }
{
/// <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;
}
}
/// <summary> /// <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> /// </summary>
/// <typeparam name="TEnvironment"></typeparam> public TimeSpan TimestampRecalculationInterval { get; set; } = TimeSpan.FromHours(1);
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> /// <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> /// </summary>
/// <typeparam name="TEnvironment"></typeparam> public bool CachingEnabled { get; set; }
/// <typeparam name="TApiCredentials"></typeparam>
public class RestExchangeOptions<TEnvironment, TApiCredentials> : RestExchangeOptions<TEnvironment> where TEnvironment : TradeEnvironment where TApiCredentials : ApiCredentials /// <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> item.OutputOriginalData = OutputOriginalData;
/// The api credentials used for signing requests to this API. item.AutoTimestamp = AutoTimestamp;
/// </summary> item.TimestampRecalculationInterval = TimestampRecalculationInterval;
public new TApiCredentials? ApiCredentials item.ApiCredentials = ApiCredentials?.Copy();
{ item.Proxy = Proxy;
get => (TApiCredentials?)base.ApiCredentials; item.RequestTimeout = RequestTimeout;
set => base.ApiCredentials = value; 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