mirror of
https://github.com/JKorf/CryptoExchange.Net.git
synced 2026-08-12 17:03:10 +00:00
Compare commits
20 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| bcdcdbbd4e | |||
| 1ece13f5bc | |||
| da70ba6ec7 | |||
| a832f0e4d4 | |||
| 6ab4d005a0 | |||
| 18dc935038 | |||
| bb3a534f75 | |||
| 649ba370c6 | |||
| 51732c5ce6 | |||
| 0ba7b46680 | |||
| 94dfbb7b9e | |||
| b8b7512b35 | |||
| aba6b773ce | |||
| d88fb0d356 | |||
| b8c6d55156 | |||
| d9a5481db2 | |||
| 6a8bb42c0e | |||
| 2445f001ab | |||
| c84fa9ac32 | |||
| d44a11c44e |
@@ -6,9 +6,9 @@
|
|||||||
<PackageId>CryptoExchange.Net.Protobuf</PackageId>
|
<PackageId>CryptoExchange.Net.Protobuf</PackageId>
|
||||||
<Authors>JKorf</Authors>
|
<Authors>JKorf</Authors>
|
||||||
<Description>Protobuf support for CryptoExchange.Net</Description>
|
<Description>Protobuf support for CryptoExchange.Net</Description>
|
||||||
<PackageVersion>9.6.0</PackageVersion>
|
<PackageVersion>9.8.0</PackageVersion>
|
||||||
<AssemblyVersion>9.6.0</AssemblyVersion>
|
<AssemblyVersion>9.8.0</AssemblyVersion>
|
||||||
<FileVersion>9.6.0</FileVersion>
|
<FileVersion>9.8.0</FileVersion>
|
||||||
<PackageRequireLicenseAcceptance>false</PackageRequireLicenseAcceptance>
|
<PackageRequireLicenseAcceptance>false</PackageRequireLicenseAcceptance>
|
||||||
<PackageTags>CryptoExchange;CryptoExchange.Net</PackageTags>
|
<PackageTags>CryptoExchange;CryptoExchange.Net</PackageTags>
|
||||||
<RepositoryType>git</RepositoryType>
|
<RepositoryType>git</RepositoryType>
|
||||||
@@ -41,7 +41,7 @@
|
|||||||
<DocumentationFile>CryptoExchange.Net.Protobuf.xml</DocumentationFile>
|
<DocumentationFile>CryptoExchange.Net.Protobuf.xml</DocumentationFile>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="CryptoExchange.Net" Version="9.6.0" />
|
<PackageReference Include="CryptoExchange.Net" Version="9.8.0" />
|
||||||
<PackageReference Include="protobuf-net" Version="3.2.56" />
|
<PackageReference Include="protobuf-net" Version="3.2.56" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
@@ -5,6 +5,12 @@
|
|||||||
Protobuf support for CryptoExchange.Net.
|
Protobuf support for CryptoExchange.Net.
|
||||||
|
|
||||||
## Release notes
|
## Release notes
|
||||||
|
* Version 9.8.0 - 30 Sep 2025
|
||||||
|
* Updated CryptoExchange.Net version to 9.8.0, see https://github.com/JKorf/CryptoExchange.Net/releases/
|
||||||
|
|
||||||
|
* Version 9.7.0 - 01 Sep 2025
|
||||||
|
* Updated CryptoExchange.Net version to 9.7.0, see https://github.com/JKorf/CryptoExchange.Net/releases/
|
||||||
|
|
||||||
* Version 9.6.0 - 25 Aug 2025
|
* Version 9.6.0 - 25 Aug 2025
|
||||||
* Updated CryptoExchange.Net version to 9.6.0
|
* Updated CryptoExchange.Net version to 9.6.0
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ using NUnit.Framework.Legacy;
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Net;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
@@ -113,6 +114,7 @@ namespace CryptoExchange.Net.UnitTests
|
|||||||
{
|
{
|
||||||
var result = new WebCallResult<TestObjectResult>(
|
var result = new WebCallResult<TestObjectResult>(
|
||||||
System.Net.HttpStatusCode.OK,
|
System.Net.HttpStatusCode.OK,
|
||||||
|
HttpVersion.Version11,
|
||||||
new KeyValuePair<string, string[]>[0],
|
new KeyValuePair<string, string[]>[0],
|
||||||
TimeSpan.FromSeconds(1),
|
TimeSpan.FromSeconds(1),
|
||||||
null,
|
null,
|
||||||
@@ -143,6 +145,7 @@ namespace CryptoExchange.Net.UnitTests
|
|||||||
{
|
{
|
||||||
var result = new WebCallResult<TestObjectResult>(
|
var result = new WebCallResult<TestObjectResult>(
|
||||||
System.Net.HttpStatusCode.OK,
|
System.Net.HttpStatusCode.OK,
|
||||||
|
HttpVersion.Version11,
|
||||||
new KeyValuePair<string, string[]>[0],
|
new KeyValuePair<string, string[]>[0],
|
||||||
TimeSpan.FromSeconds(1),
|
TimeSpan.FromSeconds(1),
|
||||||
null,
|
null,
|
||||||
|
|||||||
@@ -5,10 +5,6 @@
|
|||||||
<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" />
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ namespace CryptoExchange.Net.UnitTests
|
|||||||
[TestCase(0.1, 1, 0.0001, RoundingType.Closest, 0.532, 0.532)]
|
[TestCase(0.1, 1, 0.0001, RoundingType.Closest, 0.532, 0.532)]
|
||||||
[TestCase(0.1, 1, 0.0001, RoundingType.Down, 0.5516592, 0.5516)]
|
[TestCase(0.1, 1, 0.0001, RoundingType.Down, 0.5516592, 0.5516)]
|
||||||
[TestCase(0.1, 1, 0.0001, RoundingType.Closest, 0.5516592, 0.5517)]
|
[TestCase(0.1, 1, 0.0001, RoundingType.Closest, 0.5516592, 0.5517)]
|
||||||
|
[TestCase(0, 1, 0.000000001, RoundingType.Closest, 0.0000097232, 0.000009723)]
|
||||||
public void AdjustValueStepTests(decimal min, decimal max, decimal? step, RoundingType roundingType, decimal input, decimal expected)
|
public void AdjustValueStepTests(decimal min, decimal max, decimal? step, RoundingType roundingType, decimal input, decimal expected)
|
||||||
{
|
{
|
||||||
var result = ExchangeHelpers.AdjustValueStep(min, max, step, roundingType, input);
|
var result = ExchangeHelpers.AdjustValueStep(min, max, step, roundingType, input);
|
||||||
|
|||||||
@@ -202,7 +202,7 @@ namespace CryptoExchange.Net.UnitTests
|
|||||||
await sub;
|
await sub;
|
||||||
|
|
||||||
// assert
|
// assert
|
||||||
ClassicAssert.IsFalse(client.SubClient.TestSubscription.Confirmed);
|
ClassicAssert.IsTrue(client.SubClient.TestSubscription.Status != SubscriptionStatus.Subscribed);
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestCase()]
|
[TestCase()]
|
||||||
@@ -225,7 +225,7 @@ namespace CryptoExchange.Net.UnitTests
|
|||||||
await sub;
|
await sub;
|
||||||
|
|
||||||
// assert
|
// assert
|
||||||
Assert.That(client.SubClient.TestSubscription.Confirmed);
|
Assert.That(client.SubClient.TestSubscription.Status == SubscriptionStatus.Subscribed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -60,8 +60,8 @@ namespace CryptoExchange.Net.UnitTests.TestImplementations
|
|||||||
request.Setup(c => c.GetHeaders()).Returns(() => headers.ToArray());
|
request.Setup(c => c.GetHeaders()).Returns(() => headers.ToArray());
|
||||||
|
|
||||||
var factory = Mock.Get(Api1.RequestFactory);
|
var factory = Mock.Get(Api1.RequestFactory);
|
||||||
factory.Setup(c => c.Create(It.IsAny<HttpMethod>(), It.IsAny<Uri>(), It.IsAny<int>()))
|
factory.Setup(c => c.Create(It.IsAny<Version>(), It.IsAny<HttpMethod>(), It.IsAny<Uri>(), It.IsAny<int>()))
|
||||||
.Callback<HttpMethod, Uri, int>((method, uri, id) =>
|
.Callback<Version, HttpMethod, Uri, int>((version, method, uri, id) =>
|
||||||
{
|
{
|
||||||
request.Setup(a => a.Uri).Returns(uri);
|
request.Setup(a => a.Uri).Returns(uri);
|
||||||
request.Setup(a => a.Method).Returns(method);
|
request.Setup(a => a.Method).Returns(method);
|
||||||
@@ -69,8 +69,8 @@ namespace CryptoExchange.Net.UnitTests.TestImplementations
|
|||||||
.Returns(request.Object);
|
.Returns(request.Object);
|
||||||
|
|
||||||
factory = Mock.Get(Api2.RequestFactory);
|
factory = Mock.Get(Api2.RequestFactory);
|
||||||
factory.Setup(c => c.Create(It.IsAny<HttpMethod>(), It.IsAny<Uri>(), It.IsAny<int>()))
|
factory.Setup(c => c.Create(It.IsAny<Version>(), It.IsAny<HttpMethod>(), It.IsAny<Uri>(), It.IsAny<int>()))
|
||||||
.Callback<HttpMethod, Uri, int>((method, uri, id) =>
|
.Callback<Version, HttpMethod, Uri, int>((version, method, uri, id) =>
|
||||||
{
|
{
|
||||||
request.Setup(a => a.Uri).Returns(uri);
|
request.Setup(a => a.Uri).Returns(uri);
|
||||||
request.Setup(a => a.Method).Returns(method);
|
request.Setup(a => a.Method).Returns(method);
|
||||||
@@ -90,12 +90,12 @@ namespace CryptoExchange.Net.UnitTests.TestImplementations
|
|||||||
request.Setup(c => c.GetResponseAsync(It.IsAny<CancellationToken>())).Throws(we);
|
request.Setup(c => c.GetResponseAsync(It.IsAny<CancellationToken>())).Throws(we);
|
||||||
|
|
||||||
var factory = Mock.Get(Api1.RequestFactory);
|
var factory = Mock.Get(Api1.RequestFactory);
|
||||||
factory.Setup(c => c.Create(It.IsAny<HttpMethod>(), It.IsAny<Uri>(), It.IsAny<int>()))
|
factory.Setup(c => c.Create(It.IsAny<Version>(), It.IsAny<HttpMethod>(), It.IsAny<Uri>(), It.IsAny<int>()))
|
||||||
.Returns(request.Object);
|
.Returns(request.Object);
|
||||||
|
|
||||||
|
|
||||||
factory = Mock.Get(Api2.RequestFactory);
|
factory = Mock.Get(Api2.RequestFactory);
|
||||||
factory.Setup(c => c.Create(It.IsAny<HttpMethod>(), It.IsAny<Uri>(), It.IsAny<int>()))
|
factory.Setup(c => c.Create(It.IsAny<Version>(), It.IsAny<HttpMethod>(), It.IsAny<Uri>(), It.IsAny<int>()))
|
||||||
.Returns(request.Object);
|
.Returns(request.Object);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -118,13 +118,13 @@ namespace CryptoExchange.Net.UnitTests.TestImplementations
|
|||||||
request.Setup(c => c.GetHeaders()).Returns(headers.ToArray());
|
request.Setup(c => c.GetHeaders()).Returns(headers.ToArray());
|
||||||
|
|
||||||
var factory = Mock.Get(Api1.RequestFactory);
|
var factory = Mock.Get(Api1.RequestFactory);
|
||||||
factory.Setup(c => c.Create(It.IsAny<HttpMethod>(), It.IsAny<Uri>(), It.IsAny<int>()))
|
factory.Setup(c => c.Create(It.IsAny<Version>(), It.IsAny<HttpMethod>(), It.IsAny<Uri>(), It.IsAny<int>()))
|
||||||
.Callback<HttpMethod, Uri, int>((method, uri, id) => request.Setup(a => a.Uri).Returns(uri))
|
.Callback<Version, HttpMethod, Uri, int>((version, method, uri, id) => request.Setup(a => a.Uri).Returns(uri))
|
||||||
.Returns(request.Object);
|
.Returns(request.Object);
|
||||||
|
|
||||||
factory = Mock.Get(Api2.RequestFactory);
|
factory = Mock.Get(Api2.RequestFactory);
|
||||||
factory.Setup(c => c.Create(It.IsAny<HttpMethod>(), It.IsAny<Uri>(), It.IsAny<int>()))
|
factory.Setup(c => c.Create(It.IsAny<Version>(), It.IsAny<HttpMethod>(), It.IsAny<Uri>(), It.IsAny<int>()))
|
||||||
.Callback<HttpMethod, Uri, int>((method, uri, id) => request.Setup(a => a.Uri).Returns(uri))
|
.Callback<Version, HttpMethod, Uri, int>((version, method, uri, id) => request.Setup(a => a.Uri).Returns(uri))
|
||||||
.Returns(request.Object);
|
.Returns(request.Object);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,132 @@
|
|||||||
|
//using System;
|
||||||
|
//using System.IO;
|
||||||
|
//using System.Net.WebSockets;
|
||||||
|
//using System.Security.Authentication;
|
||||||
|
//using System.Text;
|
||||||
|
//using System.Threading.Tasks;
|
||||||
|
//using CryptoExchange.Net.Interfaces;
|
||||||
|
//using CryptoExchange.Net.Objects;
|
||||||
|
|
||||||
|
//namespace CryptoExchange.Net.UnitTests.TestImplementations
|
||||||
|
//{
|
||||||
|
// public class TestSocket: IWebsocket
|
||||||
|
// {
|
||||||
|
// public bool CanConnect { get; set; }
|
||||||
|
// public bool Connected { get; set; }
|
||||||
|
|
||||||
|
// public event Func<Task> OnClose;
|
||||||
|
//#pragma warning disable 0067
|
||||||
|
// public event Func<Task> OnReconnected;
|
||||||
|
// public event Func<Task> OnReconnecting;
|
||||||
|
// public event Func<int, Task> OnRequestRateLimited;
|
||||||
|
//#pragma warning restore 0067
|
||||||
|
// public event Func<int, Task> OnRequestSent;
|
||||||
|
// public event Func<WebSocketMessageType, ReadOnlyMemory<byte>, Task> OnStreamMessage;
|
||||||
|
// public event Func<Exception, Task> OnError;
|
||||||
|
// public event Func<Task> OnOpen;
|
||||||
|
// public Func<Task<Uri>> GetReconnectionUrl { get; set; }
|
||||||
|
|
||||||
|
// public int Id { get; }
|
||||||
|
// public bool ShouldReconnect { get; set; }
|
||||||
|
// public TimeSpan Timeout { get; set; }
|
||||||
|
// public Func<string, string> DataInterpreterString { get; set; }
|
||||||
|
// public Func<byte[], string> DataInterpreterBytes { get; set; }
|
||||||
|
// public DateTime? DisconnectTime { get; set; }
|
||||||
|
// public string Url { get; }
|
||||||
|
// public bool IsClosed => !Connected;
|
||||||
|
// public bool IsOpen => Connected;
|
||||||
|
// public bool PingConnection { get; set; }
|
||||||
|
// public TimeSpan PingInterval { get; set; }
|
||||||
|
// public SslProtocols SSLProtocols { get; set; }
|
||||||
|
// public Encoding Encoding { get; set; }
|
||||||
|
|
||||||
|
// public int ConnectCalls { get; private set; }
|
||||||
|
// public bool Reconnecting { get; set; }
|
||||||
|
// public string Origin { get; set; }
|
||||||
|
// public int? RatelimitPerSecond { get; set; }
|
||||||
|
|
||||||
|
// public double IncomingKbps => throw new NotImplementedException();
|
||||||
|
|
||||||
|
// public Uri Uri => new Uri("");
|
||||||
|
|
||||||
|
// public TimeSpan KeepAliveInterval { get; set; }
|
||||||
|
|
||||||
|
// public static int lastId = 0;
|
||||||
|
// public static object lastIdLock = new object();
|
||||||
|
|
||||||
|
// public TestSocket()
|
||||||
|
// {
|
||||||
|
// lock (lastIdLock)
|
||||||
|
// {
|
||||||
|
// Id = lastId + 1;
|
||||||
|
// lastId++;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// public Task<CallResult> ConnectAsync()
|
||||||
|
// {
|
||||||
|
// Connected = CanConnect;
|
||||||
|
// ConnectCalls++;
|
||||||
|
// if (CanConnect)
|
||||||
|
// InvokeOpen();
|
||||||
|
// return Task.FromResult(CanConnect ? new CallResult(null) : new CallResult(new CantConnectError()));
|
||||||
|
// }
|
||||||
|
|
||||||
|
// public bool Send(int requestId, string data, int weight)
|
||||||
|
// {
|
||||||
|
// if(!Connected)
|
||||||
|
// throw new Exception("Socket not connected");
|
||||||
|
// OnRequestSent?.Invoke(requestId);
|
||||||
|
// return true;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// public void Reset()
|
||||||
|
// {
|
||||||
|
// }
|
||||||
|
|
||||||
|
// public Task CloseAsync()
|
||||||
|
// {
|
||||||
|
// Connected = false;
|
||||||
|
// DisconnectTime = DateTime.UtcNow;
|
||||||
|
// OnClose?.Invoke();
|
||||||
|
// return Task.FromResult(0);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// public void SetProxy(string host, int port)
|
||||||
|
// {
|
||||||
|
// throw new NotImplementedException();
|
||||||
|
// }
|
||||||
|
// public void Dispose()
|
||||||
|
// {
|
||||||
|
// }
|
||||||
|
|
||||||
|
// public void InvokeClose()
|
||||||
|
// {
|
||||||
|
// Connected = false;
|
||||||
|
// DisconnectTime = DateTime.UtcNow;
|
||||||
|
// Reconnecting = true;
|
||||||
|
// OnClose?.Invoke();
|
||||||
|
// }
|
||||||
|
|
||||||
|
// public void InvokeOpen()
|
||||||
|
// {
|
||||||
|
// OnOpen?.Invoke();
|
||||||
|
// }
|
||||||
|
|
||||||
|
// public void InvokeMessage(string data)
|
||||||
|
// {
|
||||||
|
// OnStreamMessage?.Invoke(WebSocketMessageType.Text, new ReadOnlyMemory<byte>(Encoding.UTF8.GetBytes(data))).Wait();
|
||||||
|
// }
|
||||||
|
|
||||||
|
// public void SetProxy(ApiProxy proxy)
|
||||||
|
// {
|
||||||
|
// throw new NotImplementedException();
|
||||||
|
// }
|
||||||
|
|
||||||
|
// public void InvokeError(Exception error)
|
||||||
|
// {
|
||||||
|
// OnError?.Invoke(error);
|
||||||
|
// }
|
||||||
|
// public Task ReconnectAsync() => Task.CompletedTask;
|
||||||
|
// }
|
||||||
|
//}
|
||||||
@@ -1,183 +0,0 @@
|
|||||||
root = true
|
|
||||||
|
|
||||||
[*]
|
|
||||||
|
|
||||||
# Indentation and spacing
|
|
||||||
indent_style = space
|
|
||||||
indent_size = 4
|
|
||||||
trim_trailing_whitespace = true
|
|
||||||
charset = utf-8
|
|
||||||
max_line_length = 140
|
|
||||||
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
|
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
[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 { }
|
||||||
|
}
|
||||||
@@ -1,11 +1,12 @@
|
|||||||
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
|
||||||
|
{
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,24 +1,25 @@
|
|||||||
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>
|
||||||
/// Values mapping to the enum entry
|
/// Map a enum entry to string values
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string[] Values { get; set; }
|
[AttributeUsage(AttributeTargets.Field)]
|
||||||
|
public class MapAttribute : Attribute
|
||||||
/// <summary>
|
|
||||||
/// ctor
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="maps"></param>
|
|
||||||
public MapAttribute(params string[] maps)
|
|
||||||
{
|
{
|
||||||
Values = maps;
|
/// <summary>
|
||||||
|
/// Values mapping to the enum entry
|
||||||
|
/// </summary>
|
||||||
|
public string[] Values { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// ctor
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="maps"></param>
|
||||||
|
public MapAttribute(params string[] maps)
|
||||||
|
{
|
||||||
|
Values = maps;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,56 +1,60 @@
|
|||||||
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>
|
||||||
/// The api key / label to authenticate requests
|
/// Api credentials, used to sign requests accessing private endpoints
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string Key { get; set; }
|
public class ApiCredentials
|
||||||
|
|
||||||
/// <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)
|
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(key) || string.IsNullOrEmpty(secret))
|
/// <summary>
|
||||||
throw new ArgumentException("Key and secret can't be null/empty");
|
/// The api key / label to authenticate requests
|
||||||
|
/// </summary>
|
||||||
|
public string Key { get; set; }
|
||||||
|
|
||||||
CredentialType = credentialType;
|
/// <summary>
|
||||||
Key = key;
|
/// The api secret or private key to authenticate requests
|
||||||
Secret = secret;
|
/// </summary>
|
||||||
Pass = pass;
|
public string Secret { get; set; }
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Copy the credentials
|
/// The api passphrase. Not needed on all exchanges
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns></returns>
|
public string? Pass { get; set; }
|
||||||
public virtual ApiCredentials Copy()
|
|
||||||
{
|
/// <summary>
|
||||||
return new ApiCredentials(Key, Secret, Pass, CredentialType);
|
/// Type of the credentials
|
||||||
|
/// </summary>
|
||||||
|
public ApiCredentialsType CredentialType { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create Api credentials providing an api key and secret for authentication
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="key">The api key / label used for identification</param>
|
||||||
|
/// <param name="secret">The api secret or private key used for signing</param>
|
||||||
|
/// <param name="pass">The api pass for the key. Not always needed</param>
|
||||||
|
/// <param name="credentialType">The type of credentials</param>
|
||||||
|
public ApiCredentials(string key, string secret, string? pass = null, ApiCredentialsType credentialType = ApiCredentialsType.Hmac)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(key) || string.IsNullOrEmpty(secret))
|
||||||
|
throw new ArgumentException("Key and secret can't be null/empty");
|
||||||
|
|
||||||
|
CredentialType = credentialType;
|
||||||
|
Key = key;
|
||||||
|
Secret = secret;
|
||||||
|
Pass = pass;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Copy the credentials
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public virtual ApiCredentials Copy()
|
||||||
|
{
|
||||||
|
return new ApiCredentials(Key, Secret, Pass, CredentialType);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,20 +1,21 @@
|
|||||||
namespace CryptoExchange.Net.Authentication;
|
namespace CryptoExchange.Net.Authentication
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Credentials type
|
|
||||||
/// </summary>
|
|
||||||
public enum ApiCredentialsType
|
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Hmac keys credentials
|
/// Credentials type
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Hmac,
|
public enum ApiCredentialsType
|
||||||
/// <summary>
|
{
|
||||||
/// Rsa keys credentials in xml format
|
/// <summary>
|
||||||
/// </summary>
|
/// Hmac keys credentials
|
||||||
RsaXml,
|
/// </summary>
|
||||||
/// <summary>
|
Hmac,
|
||||||
/// Rsa keys credentials in pem/base64 format. Only available for .NetStandard 2.1 and up, use xml format for lower.
|
/// <summary>
|
||||||
/// </summary>
|
/// Rsa keys credentials in xml format
|
||||||
RsaPem
|
/// </summary>
|
||||||
|
RsaXml,
|
||||||
|
/// <summary>
|
||||||
|
/// Rsa keys credentials in pem/base64 format. Only available for .NetStandard 2.1 and up, use xml format for lower.
|
||||||
|
/// </summary>
|
||||||
|
RsaPem
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,481 +1,473 @@
|
|||||||
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>
|
||||||
/// Provided credentials
|
/// Base class for authentication providers
|
||||||
/// </summary>
|
/// </summary>
|
||||||
protected internal readonly ApiCredentials _credentials;
|
public abstract class AuthenticationProvider
|
||||||
|
|
||||||
/// <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)
|
internal IAuthTimeProvider TimeProvider { get; set; } = new AuthTimeProvider();
|
||||||
throw new ArgumentException("ApiKey/Secret needed");
|
|
||||||
|
|
||||||
_credentials = credentials;
|
/// <summary>
|
||||||
_sBytes = Encoding.UTF8.GetBytes(credentials.Secret);
|
/// Provided credentials
|
||||||
}
|
/// </summary>
|
||||||
|
protected internal readonly ApiCredentials _credentials;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Authenticate a request
|
/// Byte representation of the secret
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="apiClient">The Api client sending the request</param>
|
protected byte[] _sBytes;
|
||||||
/// <param name="requestConfig">The request configuration</param>
|
|
||||||
public abstract void ProcessRequest(RestApiClient apiClient, RestRequestConfiguration requestConfig);
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// SHA256 sign the data and return the bytes
|
/// Get the API key of the current credentials
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="data"></param>
|
public string ApiKey => _credentials.Key!;
|
||||||
/// <returns></returns>
|
/// <summary>
|
||||||
protected static byte[] SignSHA256Bytes(string data)
|
/// Get the Passphrase of the current credentials
|
||||||
{
|
/// </summary>
|
||||||
using var encryptor = SHA256.Create();
|
public string? Pass => _credentials.Pass;
|
||||||
return encryptor.ComputeHash(Encoding.UTF8.GetBytes(data));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// SHA256 sign the data and return the bytes
|
/// ctor
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="data"></param>
|
/// <param name="credentials"></param>
|
||||||
/// <returns></returns>
|
protected AuthenticationProvider(ApiCredentials credentials)
|
||||||
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 (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
|
#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER
|
||||||
// Read from pem private key
|
// Read from pem private key
|
||||||
var key = _credentials.Secret!
|
var key = _credentials.Secret!
|
||||||
.Replace("\n", "")
|
.Replace("\n", "")
|
||||||
.Replace("-----BEGIN PRIVATE KEY-----", "")
|
.Replace("-----BEGIN PRIVATE KEY-----", "")
|
||||||
.Replace("-----END PRIVATE KEY-----", "")
|
.Replace("-----END PRIVATE KEY-----", "")
|
||||||
.Trim();
|
.Trim();
|
||||||
rsa.ImportPkcs8PrivateKey(Convert.FromBase64String(
|
rsa.ImportPkcs8PrivateKey(Convert.FromBase64String(
|
||||||
key)
|
key)
|
||||||
, out _);
|
, out _);
|
||||||
#else
|
#else
|
||||||
throw new Exception("Pem format not supported when running from .NetStandard2.0. Convert the private key to xml format.");
|
throw new Exception("Pem format not supported when running from .NetStandard2.0. Convert the private key to xml format.");
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
else if (_credentials.CredentialType == ApiCredentialsType.RsaXml)
|
else if (_credentials.CredentialType == ApiCredentialsType.RsaXml)
|
||||||
{
|
{
|
||||||
// Read from xml private key format
|
// Read from xml private key format
|
||||||
rsa.FromXmlString(_credentials.Secret!);
|
rsa.FromXmlString(_credentials.Secret!);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
throw new Exception("Invalid credentials type");
|
throw new Exception("Invalid credentials type");
|
||||||
|
}
|
||||||
|
|
||||||
|
return rsa;
|
||||||
}
|
}
|
||||||
|
|
||||||
return rsa;
|
/// <summary>
|
||||||
}
|
/// Convert byte array to hex string
|
||||||
|
/// </summary>
|
||||||
/// <summary>
|
/// <param name="buff"></param>
|
||||||
/// Convert byte array to hex string
|
/// <returns></returns>
|
||||||
/// </summary>
|
protected static string BytesToHexString(byte[] buff)
|
||||||
/// <param name="buff"></param>
|
{
|
||||||
/// <returns></returns>
|
|
||||||
protected static string BytesToHexString(byte[] buff)
|
|
||||||
{
|
|
||||||
#if NET9_0_OR_GREATER
|
#if NET9_0_OR_GREATER
|
||||||
return Convert.ToHexString(buff);
|
return Convert.ToHexString(buff);
|
||||||
#else
|
#else
|
||||||
var result = string.Empty;
|
var result = string.Empty;
|
||||||
foreach (var t in buff)
|
foreach (var t in buff)
|
||||||
result += t.ToString("X2");
|
result += t.ToString("X2");
|
||||||
return result;
|
return result;
|
||||||
#endif
|
#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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <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 />
|
/// <inheritdoc />
|
||||||
#pragma warning disable IDE1006 // Naming Styles
|
public abstract class AuthenticationProvider<TApiCredentials> : AuthenticationProvider where TApiCredentials : ApiCredentials
|
||||||
#pragma warning disable CA1707 // Naming Styles
|
|
||||||
protected new TApiCredentials _credentials => (TApiCredentials)base._credentials;
|
|
||||||
#pragma warning restore IDE1006 // Naming Styles
|
|
||||||
#pragma warning restore CA1707 // Naming Styles
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// ctor
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="credentials"></param>
|
|
||||||
protected AuthenticationProvider(TApiCredentials credentials) : base(credentials)
|
|
||||||
{
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected new TApiCredentials _credentials => (TApiCredentials)base._credentials;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// ctor
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="credentials"></param>
|
||||||
|
protected AuthenticationProvider(TApiCredentials credentials) : base(credentials)
|
||||||
|
{
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,17 @@
|
|||||||
namespace CryptoExchange.Net.Authentication;
|
namespace CryptoExchange.Net.Authentication
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Output string type
|
|
||||||
/// </summary>
|
|
||||||
public enum SignOutputType
|
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Hex string
|
/// Output string type
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Hex,
|
public enum SignOutputType
|
||||||
/// <summary>
|
{
|
||||||
/// Base64 string
|
/// <summary>
|
||||||
/// </summary>
|
/// Hex string
|
||||||
Base64
|
/// </summary>
|
||||||
|
Hex,
|
||||||
|
/// <summary>
|
||||||
|
/// Base64 string
|
||||||
|
/// </summary>
|
||||||
|
Base64
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,52 +1,53 @@
|
|||||||
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
|
|
||||||
{
|
{
|
||||||
private readonly ConcurrentDictionary<string, CacheItem> _cache = new ConcurrentDictionary<string, CacheItem>();
|
internal class MemoryCache
|
||||||
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)
|
|
||||||
{
|
{
|
||||||
var cacheItem = new CacheItem(DateTime.UtcNow, value);
|
private readonly ConcurrentDictionary<string, CacheItem> _cache = new ConcurrentDictionary<string, CacheItem>();
|
||||||
_cache.AddOrUpdate(key, cacheItem, (key, val1) => cacheItem);
|
private readonly object _lock = new object();
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Get a cached value
|
/// Add a new cache entry. Will override an existing entry if it already exists
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="key">The key identifier</param>
|
/// <param name="key">The key identifier</param>
|
||||||
/// <param name="maxAge">The max age of the cached entry</param>
|
/// <param name="value">Cache value</param>
|
||||||
/// <returns>Cached value if it was in cache</returns>
|
public void Add(string key, object value)
|
||||||
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;
|
var cacheItem = new CacheItem(DateTime.UtcNow, value);
|
||||||
Value = value;
|
_cache.AddOrUpdate(key, cacheItem, (key, val1) => cacheItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get a cached value
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="key">The key identifier</param>
|
||||||
|
/// <param name="maxAge">The max age of the cached entry</param>
|
||||||
|
/// <returns>Cached value if it was in cache</returns>
|
||||||
|
public object? Get(string key, TimeSpan maxAge)
|
||||||
|
{
|
||||||
|
foreach (var item in _cache.Where(x => DateTime.UtcNow - x.Value.CacheTime > maxAge).ToList())
|
||||||
|
_cache.TryRemove(item.Key, out _);
|
||||||
|
|
||||||
|
_cache.TryGetValue(key, out CacheItem? value);
|
||||||
|
if (value == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
return value.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private class CacheItem
|
||||||
|
{
|
||||||
|
public DateTime CacheTime { get; }
|
||||||
|
public object Value { get; }
|
||||||
|
|
||||||
|
public CacheItem(DateTime cacheTime, object value)
|
||||||
|
{
|
||||||
|
CacheTime = cacheTime;
|
||||||
|
Value = value;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,140 +1,134 @@
|
|||||||
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>
|
||||||
/// Logger
|
/// Base API for all API clients
|
||||||
/// </summary>
|
/// </summary>
|
||||||
protected ILogger _logger;
|
public abstract class BaseApiClient : IDisposable, IBaseApiClient
|
||||||
|
|
||||||
/// <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)
|
|
||||||
{
|
{
|
||||||
_logger = logger;
|
/// <summary>
|
||||||
|
/// Logger
|
||||||
|
/// </summary>
|
||||||
|
protected ILogger _logger;
|
||||||
|
|
||||||
ClientOptions = clientOptions;
|
/// <summary>
|
||||||
ApiOptions = apiOptions;
|
/// If we are disposing
|
||||||
OutputOriginalData = outputOriginalData;
|
/// </summary>
|
||||||
BaseAddress = baseAddress;
|
protected bool _disposing;
|
||||||
ApiCredentials = apiCredentials?.Copy();
|
|
||||||
|
|
||||||
if (ApiCredentials != null)
|
/// <summary>
|
||||||
AuthenticationProvider = CreateAuthenticationProvider(ApiCredentials);
|
/// The authentication provider for this API client. (null if no credentials are set)
|
||||||
}
|
/// </summary>
|
||||||
|
public AuthenticationProvider? AuthenticationProvider { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Create an AuthenticationProvider implementation instance based on the provided credentials
|
/// The environment this client communicates to
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="credentials"></param>
|
public string BaseAddress { get; }
|
||||||
/// <returns></returns>
|
|
||||||
protected abstract AuthenticationProvider CreateAuthenticationProvider(ApiCredentials credentials);
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <summary>
|
||||||
public abstract string FormatSymbol(string baseAsset, string quoteAsset, TradingMode tradingMode, DateTime? deliverDate = null);
|
/// Output the original string data along with the deserialized object
|
||||||
|
/// </summary>
|
||||||
|
public bool OutputOriginalData { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <inheritdoc />
|
||||||
/// Get error info for a response code
|
public bool Authenticated => ApiCredentials != null;
|
||||||
/// </summary>
|
|
||||||
public ErrorInfo GetErrorInfo(int code, string? message = null) => GetErrorInfo(code.ToString(), message);
|
|
||||||
|
|
||||||
/// <summary>
|
/// <inheritdoc />
|
||||||
/// Get error info for a response code
|
public ApiCredentials? ApiCredentials { get; set; }
|
||||||
/// </summary>
|
|
||||||
public ErrorInfo GetErrorInfo(string code, string? message = null) => ErrorMapping.GetErrorInfo(code.ToString(), message);
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <summary>
|
||||||
public void SetApiCredentials<T>(T credentials) where T : ApiCredentials
|
/// Api options
|
||||||
{
|
/// </summary>
|
||||||
ApiCredentials = credentials?.Copy();
|
public ApiOptions ApiOptions { get; }
|
||||||
if (ApiCredentials != null)
|
|
||||||
AuthenticationProvider = CreateAuthenticationProvider(ApiCredentials);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <summary>
|
||||||
public virtual void SetOptions<T>(UpdateOptions<T> options) where T : ApiCredentials
|
/// Client Options
|
||||||
{
|
/// </summary>
|
||||||
ClientOptions.Proxy = options.Proxy;
|
public ExchangeOptions ClientOptions { get; }
|
||||||
ClientOptions.RequestTimeout = options.RequestTimeout ?? ClientOptions.RequestTimeout;
|
|
||||||
|
|
||||||
ApiCredentials = options.ApiCredentials?.Copy() ?? ApiCredentials;
|
/// <summary>
|
||||||
if (ApiCredentials != null)
|
/// Mapping of a response code to known error types
|
||||||
AuthenticationProvider = CreateAuthenticationProvider(ApiCredentials);
|
/// </summary>
|
||||||
}
|
protected internal virtual ErrorMapping ErrorMapping { get; } = new ErrorMapping([]);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Dispose
|
/// ctor
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void Dispose()
|
/// <param name="logger">Logger</param>
|
||||||
{
|
/// <param name="outputOriginalData">Should data from this client include the original data in the call result</param>
|
||||||
Dispose(true);
|
/// <param name="baseAddress">Base address for this API client</param>
|
||||||
GC.SuppressFinalize(this);
|
/// <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;
|
||||||
|
|
||||||
/// <summary>
|
ClientOptions = clientOptions;
|
||||||
/// Dispose
|
ApiOptions = apiOptions;
|
||||||
/// </summary>
|
OutputOriginalData = outputOriginalData;
|
||||||
public virtual void Dispose(bool disposing)
|
BaseAddress = baseAddress;
|
||||||
{
|
ApiCredentials = apiCredentials?.Copy();
|
||||||
_disposing = true;
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,142 +1,130 @@
|
|||||||
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>
|
||||||
/// Version of the CryptoExchange.Net base library
|
/// The base for all clients, websocket client and rest client
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Version CryptoExchangeLibVersion { get; } = typeof(BaseClient).Assembly.GetName().Version!;
|
public abstract class BaseClient : IDisposable
|
||||||
|
{
|
||||||
|
/// <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)
|
|
||||||
{
|
{
|
||||||
if (_exchangeVersion == null)
|
lock(_versionLock)
|
||||||
_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{CryptoExchangeVersion}, {Exchange}.Net: v{ExchangeVersion}", options, CryptoExchangeLibVersion, Exchange, ExchangeLibVersion);
|
_logger.Log(LogLevel.Trace, $"Client configuration: {options}, CryptoExchange.Net: v{CryptoExchangeLibVersion}, {Exchange}.Net: v{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}, base address: {BaseAddress}", apiClient.GetType().Name, apiClient.BaseAddress);
|
_logger.Log(LogLevel.Trace, $" {apiClient.GetType().Name}, base address: {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 void Dispose()
|
public virtual void Dispose()
|
||||||
{
|
|
||||||
Dispose(true);
|
|
||||||
GC.SuppressFinalize(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Dispose
|
|
||||||
/// </summary>
|
|
||||||
public virtual void Dispose(bool disposing)
|
|
||||||
{
|
|
||||||
if (disposing)
|
|
||||||
{
|
{
|
||||||
_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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,23 +3,24 @@ 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)
|
|
||||||
{
|
{
|
||||||
_logger = loggerFactory?.CreateLogger(name + ".RestClient") ?? NullLoggerFactory.Instance.CreateLogger(name);
|
/// <inheritdoc />
|
||||||
|
public int TotalRequestsMade => ApiClients.OfType<RestApiClient>().Sum(s => s.TotalRequestsMade);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// ctor
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="loggerFactory">Logger factory</param>
|
||||||
|
/// <param name="name">The name of the API this client is for</param>
|
||||||
|
protected BaseRestClient(ILoggerFactory? loggerFactory, string name) : base(loggerFactory, name)
|
||||||
|
{
|
||||||
|
_logger = loggerFactory?.CreateLogger(name + ".RestClient") ?? NullLoggerFactory.Instance.CreateLogger(name);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,130 +1,132 @@
|
|||||||
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>
|
||||||
/// If client is disposing
|
/// Base for socket client implementations
|
||||||
/// </summary>
|
/// </summary>
|
||||||
protected bool _disposing;
|
public abstract class BaseSocketClient : BaseClient, ISocketClient
|
||||||
|
|
||||||
/// <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)
|
|
||||||
{
|
{
|
||||||
_logger = loggerFactory?.CreateLogger(name + ".SocketClient") ?? NullLoggerFactory.Instance.CreateLogger(name);
|
#region fields
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Unsubscribe an update subscription
|
/// If client is disposing
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="subscriptionId">The id of the subscription to unsubscribe</param>
|
protected bool _disposing;
|
||||||
/// <returns></returns>
|
|
||||||
public virtual async Task UnsubscribeAsync(int subscriptionId)
|
/// <inheritdoc />
|
||||||
{
|
public int CurrentConnections => ApiClients.OfType<SocketApiClient>().Sum(c => c.CurrentConnections);
|
||||||
foreach (var socket in ApiClients.OfType<SocketApiClient>())
|
/// <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)
|
||||||
{
|
{
|
||||||
var result = await socket.UnsubscribeAsync(subscriptionId).ConfigureAwait(false);
|
_logger = loggerFactory?.CreateLogger(name + ".SocketClient") ?? NullLoggerFactory.Instance.CreateLogger(name);
|
||||||
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>
|
||||||
}
|
/// Unsubscribe an update subscription
|
||||||
|
/// </summary>
|
||||||
/// <summary>
|
/// <param name="subscriptionId">The id of the subscription to unsubscribe</param>
|
||||||
/// Log the current state of connections and subscriptions
|
/// <returns></returns>
|
||||||
/// </summary>
|
public virtual async Task UnsubscribeAsync(int subscriptionId)
|
||||||
public string GetSubscriptionsState()
|
|
||||||
{
|
|
||||||
var result = new StringBuilder();
|
|
||||||
foreach (var client in ApiClients.OfType<SocketApiClient>().Where(c => c.CurrentSubscriptions > 0))
|
|
||||||
{
|
{
|
||||||
result.AppendLine(client.GetSubscriptionsState());
|
foreach (var socket in ApiClients.OfType<SocketApiClient>())
|
||||||
|
{
|
||||||
|
var result = await socket.UnsubscribeAsync(subscriptionId).ConfigureAwait(false);
|
||||||
|
if (result)
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return result.ToString();
|
/// <summary>
|
||||||
}
|
/// Unsubscribe an update subscription
|
||||||
|
/// </summary>
|
||||||
/// <summary>
|
/// <param name="subscription">The subscription to unsubscribe</param>
|
||||||
/// Returns the state of all socket api clients
|
/// <returns></returns>
|
||||||
/// </summary>
|
public virtual async Task UnsubscribeAsync(UpdateSubscription subscription)
|
||||||
/// <returns></returns>
|
|
||||||
public List<SocketApiClient.SocketApiClientState> GetSocketApiClientStates()
|
|
||||||
{
|
|
||||||
var result = new List<SocketApiClient.SocketApiClientState>();
|
|
||||||
foreach (var client in ApiClients.OfType<SocketApiClient>())
|
|
||||||
{
|
{
|
||||||
result.Add(client.GetState());
|
if (subscription == null)
|
||||||
|
throw new ArgumentNullException(nameof(subscription));
|
||||||
|
|
||||||
|
_logger.UnsubscribingSubscription(subscription.SocketId, subscription.Id);
|
||||||
|
await subscription.CloseAsync().ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
/// <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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,78 +1,67 @@
|
|||||||
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>
|
||||||
/// Service provider
|
/// Base crypto client
|
||||||
/// </summary>
|
/// </summary>
|
||||||
protected readonly IServiceProvider? _serviceProvider;
|
public class CryptoBaseClient : IDisposable
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// ctor
|
|
||||||
/// </summary>
|
|
||||||
public CryptoBaseClient() { }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// ctor
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="serviceProvider"></param>
|
|
||||||
public CryptoBaseClient(IServiceProvider serviceProvider)
|
|
||||||
{
|
{
|
||||||
_serviceProvider = serviceProvider;
|
private readonly Dictionary<Type, object> _serviceCache = new Dictionary<Type, object>();
|
||||||
_serviceCache = new Dictionary<Type, object>();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Try get a client by type for the service collection
|
/// Service provider
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <typeparam name="T"></typeparam>
|
protected readonly IServiceProvider? _serviceProvider;
|
||||||
/// <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>
|
||||||
|
/// ctor
|
||||||
|
/// </summary>
|
||||||
|
public CryptoBaseClient() { }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// ctor
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="serviceProvider"></param>
|
||||||
|
public CryptoBaseClient(IServiceProvider serviceProvider)
|
||||||
{
|
{
|
||||||
// Create with default options
|
_serviceProvider = serviceProvider;
|
||||||
var createResult = createFunc();
|
_serviceCache = new Dictionary<Type, object>();
|
||||||
_serviceCache.Add(typeof(T), createResult!);
|
|
||||||
return createResult;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var result = _serviceProvider.GetService<T>()
|
/// <summary>
|
||||||
?? 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");
|
/// Try get a client by type for the service collection
|
||||||
_serviceCache.Add(type, result!);
|
/// </summary>
|
||||||
return result;
|
/// <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)
|
||||||
/// Dispose
|
{
|
||||||
/// </summary>
|
// Create with default options
|
||||||
public void Dispose(bool disposing)
|
var createResult = createFunc();
|
||||||
{
|
_serviceCache.Add(typeof(T), createResult!);
|
||||||
if (disposing)
|
return createResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
var result = _serviceProvider.GetService<T>()
|
||||||
|
?? throw new InvalidOperationException($"No service was found for {typeof(T).Name}, make sure the exchange is registered in dependency injection with the `services.Add[Exchange]()` method");
|
||||||
|
_serviceCache.Add(type, result!);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Dispose
|
||||||
|
/// </summary>
|
||||||
|
public void Dispose()
|
||||||
{
|
{
|
||||||
_serviceCache.Clear();
|
_serviceCache.Clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Dispose
|
|
||||||
/// </summary>
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
Dispose(true);
|
|
||||||
GC.SuppressFinalize(this);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,23 +1,27 @@
|
|||||||
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
|
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <inheritdoc />
|
||||||
/// ctor
|
public class CryptoRestClient : CryptoBaseClient, ICryptoRestClient
|
||||||
/// </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)
|
||||||
{
|
{
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,23 +1,24 @@
|
|||||||
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>
|
/// <inheritdoc />
|
||||||
/// ctor
|
public class CryptoSocketClient : CryptoBaseClient, ICryptoSocketClient
|
||||||
/// </summary>
|
|
||||||
public CryptoSocketClient()
|
|
||||||
{
|
{
|
||||||
}
|
/// <summary>
|
||||||
|
/// ctor
|
||||||
|
/// </summary>
|
||||||
|
public CryptoSocketClient()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// ctor
|
/// ctor
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="serviceProvider"></param>
|
/// <param name="serviceProvider"></param>
|
||||||
public CryptoSocketClient(IServiceProvider serviceProvider) : base(serviceProvider)
|
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
@@ -1,24 +1,25 @@
|
|||||||
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>
|
||||||
/// The index in the array
|
/// Mark property as an index in the array
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int Index { get; }
|
[AttributeUsage(AttributeTargets.Property)]
|
||||||
|
public class ArrayPropertyAttribute : Attribute
|
||||||
/// <summary>
|
|
||||||
/// ctor
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="index"></param>
|
|
||||||
public ArrayPropertyAttribute(int index)
|
|
||||||
{
|
{
|
||||||
Index = index;
|
/// <summary>
|
||||||
|
/// The index in the array
|
||||||
|
/// </summary>
|
||||||
|
public int Index { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// ctor
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="index"></param>
|
||||||
|
public ArrayPropertyAttribute(int index)
|
||||||
|
{
|
||||||
|
Index = index;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,28 +1,31 @@
|
|||||||
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>
|
||||||
/// Get the instance of the provided type T. It will be created if it doesn't exist yet.
|
/// Caching for JsonSerializerContext instances
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <typeparam name="T">Implementation type of the JsonSerializerContext</typeparam>
|
public static class JsonSerializerContextCache
|
||||||
public static JsonSerializerContext GetOrCreate<T>() where T: JsonSerializerContext, new()
|
|
||||||
{
|
{
|
||||||
var contextType = typeof(T);
|
private static ConcurrentDictionary<Type, JsonSerializerContext> _cache = new ConcurrentDictionary<Type, JsonSerializerContext>();
|
||||||
if (_cache.TryGetValue(contextType, out var context))
|
|
||||||
return context;
|
|
||||||
|
|
||||||
var instance = new T();
|
/// <summary>
|
||||||
_cache[contextType] = instance;
|
/// Get the instance of the provided type T. It will be created if it doesn't exist yet.
|
||||||
return instance;
|
/// </summary>
|
||||||
|
/// <typeparam name="T">Implementation type of the JsonSerializerContext</typeparam>
|
||||||
|
public static JsonSerializerContext GetOrCreate<T>() where T: JsonSerializerContext, new()
|
||||||
|
{
|
||||||
|
var contextType = typeof(T);
|
||||||
|
if (_cache.TryGetValue(contextType, out var context))
|
||||||
|
return context;
|
||||||
|
|
||||||
|
var instance = new T();
|
||||||
|
_cache[contextType] = instance;
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,48 +1,49 @@
|
|||||||
namespace CryptoExchange.Net.Converters.MessageParsing;
|
namespace CryptoExchange.Net.Converters.MessageParsing
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Node accessor
|
|
||||||
/// </summary>
|
|
||||||
public readonly struct NodeAccessor
|
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Index
|
/// Node accessor
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int? Index { get; }
|
public readonly struct NodeAccessor
|
||||||
/// <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;
|
/// <summary>
|
||||||
Property = property;
|
/// Index
|
||||||
Type = type;
|
/// </summary>
|
||||||
|
public int? Index { get; }
|
||||||
|
/// <summary>
|
||||||
|
/// Property name
|
||||||
|
/// </summary>
|
||||||
|
public string? Property { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Type (0 = int, 1 = string, 2 = prop name)
|
||||||
|
/// </summary>
|
||||||
|
public int Type { get; }
|
||||||
|
|
||||||
|
private NodeAccessor(int? index, string? property, int type)
|
||||||
|
{
|
||||||
|
Index = index;
|
||||||
|
Property = property;
|
||||||
|
Type = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create an int node accessor
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static NodeAccessor Int(int value) { return new NodeAccessor(value, null, 0); }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create a string node accessor
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static NodeAccessor String(string value) { return new NodeAccessor(null, value, 1); }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create a property name node accessor
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static NodeAccessor PropertyName() { return new NodeAccessor(null, null, 2); }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <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); }
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,49 +1,50 @@
|
|||||||
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>
|
|
||||||
{
|
{
|
||||||
private readonly List<NodeAccessor> _path;
|
|
||||||
|
|
||||||
internal void Add(NodeAccessor node)
|
|
||||||
{
|
|
||||||
_path.Add(node);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// ctor
|
/// Message access definition
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public MessagePath()
|
public readonly struct MessagePath : IEnumerable<NodeAccessor>
|
||||||
{
|
{
|
||||||
_path = new List<NodeAccessor>();
|
private readonly List<NodeAccessor> _path;
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
internal void Add(NodeAccessor node)
|
||||||
/// Create a new message path
|
{
|
||||||
/// </summary>
|
_path.Add(node);
|
||||||
/// <returns></returns>
|
}
|
||||||
public static MessagePath Get()
|
|
||||||
{
|
|
||||||
return new MessagePath();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// IEnumerable implementation
|
/// ctor
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns></returns>
|
public MessagePath()
|
||||||
public IEnumerator<NodeAccessor> GetEnumerator()
|
{
|
||||||
{
|
_path = new List<NodeAccessor>();
|
||||||
for (var i = 0; i < _path.Count; i++)
|
}
|
||||||
yield return _path[i];
|
|
||||||
}
|
|
||||||
|
|
||||||
IEnumerator IEnumerable.GetEnumerator()
|
/// <summary>
|
||||||
{
|
/// Create a new message path
|
||||||
return GetEnumerator();
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static MessagePath Get()
|
||||||
|
{
|
||||||
|
return new MessagePath();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// IEnumerable implementation
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public IEnumerator<NodeAccessor> GetEnumerator()
|
||||||
|
{
|
||||||
|
for (var i = 0; i < _path.Count; i++)
|
||||||
|
yield return _path[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
IEnumerator IEnumerable.GetEnumerator()
|
||||||
|
{
|
||||||
|
return GetEnumerator();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,42 +1,43 @@
|
|||||||
namespace CryptoExchange.Net.Converters.MessageParsing;
|
namespace CryptoExchange.Net.Converters.MessageParsing
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Message path extension methods
|
|
||||||
/// </summary>
|
|
||||||
public static class MessagePathExtension
|
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Add a string node accessor
|
/// Message path extension methods
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="path"></param>
|
public static class MessagePathExtension
|
||||||
/// <param name="propName"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public static MessagePath Property(this MessagePath path, string propName)
|
|
||||||
{
|
{
|
||||||
path.Add(NodeAccessor.String(propName));
|
/// <summary>
|
||||||
return path;
|
/// Add a string node accessor
|
||||||
}
|
/// </summary>
|
||||||
|
/// <param name="path"></param>
|
||||||
|
/// <param name="propName"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static MessagePath Property(this MessagePath path, string propName)
|
||||||
|
{
|
||||||
|
path.Add(NodeAccessor.String(propName));
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
/// <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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,20 +1,21 @@
|
|||||||
namespace CryptoExchange.Net.Converters.MessageParsing;
|
namespace CryptoExchange.Net.Converters.MessageParsing
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Message node type
|
|
||||||
/// </summary>
|
|
||||||
public enum NodeType
|
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Array node
|
/// Message node type
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Array,
|
public enum NodeType
|
||||||
/// <summary>
|
{
|
||||||
/// Object node
|
/// <summary>
|
||||||
/// </summary>
|
/// Array node
|
||||||
Object,
|
/// </summary>
|
||||||
/// <summary>
|
Array,
|
||||||
/// Value node
|
/// <summary>
|
||||||
/// </summary>
|
/// Object node
|
||||||
Value
|
/// </summary>
|
||||||
|
Object,
|
||||||
|
/// <summary>
|
||||||
|
/// Value node
|
||||||
|
/// </summary>
|
||||||
|
Value
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,232 +1,234 @@
|
|||||||
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>
|
|
||||||
/// Converter for arrays to objects. Can deserialize data like [0.1, 0.2, "test"] to an object. Mapping is done by marking the class with [JsonConverter(typeof(ArrayConverter))] and the properties
|
|
||||||
/// with [ArrayProperty(x)] where x is the index of the property in the array
|
|
||||||
/// </summary>
|
|
||||||
#if NET5_0_OR_GREATER
|
|
||||||
public class ArrayConverter<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] T> : JsonConverter<T> where T : new()
|
|
||||||
#else
|
|
||||||
public class ArrayConverter<T> : JsonConverter<T> where T : new()
|
|
||||||
#endif
|
|
||||||
{
|
{
|
||||||
private static readonly Lazy<List<ArrayPropertyInfo>> _typePropertyInfo = new Lazy<List<ArrayPropertyInfo>>(CacheTypeAttributes, LazyThreadSafetyMode.PublicationOnly);
|
/// <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
|
||||||
/// <inheritdoc />
|
/// with [ArrayProperty(x)] where x is the index of the property in the array
|
||||||
|
/// </summary>
|
||||||
#if NET5_0_OR_GREATER
|
#if NET5_0_OR_GREATER
|
||||||
[UnconditionalSuppressMessage("AssemblyLoadTrimming", "IL3050:RequiresUnreferencedCode", Justification = "JsonSerializerOptions provided here has TypeInfoResolver set")]
|
public class ArrayConverter<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] T> : JsonConverter<T> where T : new()
|
||||||
[UnconditionalSuppressMessage("AssemblyLoadTrimming", "IL2026:RequiresUnreferencedCode", Justification = "JsonSerializerOptions provided here has TypeInfoResolver set")]
|
|
||||||
#endif
|
|
||||||
public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options)
|
|
||||||
{
|
|
||||||
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();
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public override T? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
|
||||||
{
|
|
||||||
if (reader.TokenType == JsonTokenType.Null)
|
|
||||||
return default;
|
|
||||||
|
|
||||||
var result = new T();
|
|
||||||
return ParseObject(ref reader, result, options);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#if NET5_0_OR_GREATER
|
|
||||||
[UnconditionalSuppressMessage("AssemblyLoadTrimming", "IL3050:RequiresUnreferencedCode", Justification = "JsonSerializerOptions provided here has TypeInfoResolver set")]
|
|
||||||
[UnconditionalSuppressMessage("AssemblyLoadTrimming", "IL2026:RequiresUnreferencedCode", Justification = "JsonSerializerOptions provided here has TypeInfoResolver set")]
|
|
||||||
private static T ParseObject(ref Utf8JsonReader reader, T result, JsonSerializerOptions options)
|
|
||||||
#else
|
#else
|
||||||
private static T ParseObject(ref Utf8JsonReader reader, T result, JsonSerializerOptions options)
|
public class ArrayConverter<T> : JsonConverter<T> where T : new()
|
||||||
#endif
|
#endif
|
||||||
{
|
{
|
||||||
if (reader.TokenType != JsonTokenType.StartArray)
|
private static readonly Lazy<List<ArrayPropertyInfo>> _typePropertyInfo = new Lazy<List<ArrayPropertyInfo>>(CacheTypeAttributes, LazyThreadSafetyMode.PublicationOnly);
|
||||||
throw new Exception("Not an array");
|
|
||||||
|
/// <inheritdoc />
|
||||||
int index = 0;
|
#if NET5_0_OR_GREATER
|
||||||
while (reader.Read())
|
[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 (reader.TokenType == JsonTokenType.EndArray)
|
if (value == null)
|
||||||
break;
|
|
||||||
|
|
||||||
var indexAttributes = _typePropertyInfo.Value.Where(a => a.ArrayProperty.Index == index);
|
|
||||||
if (!indexAttributes.Any())
|
|
||||||
{
|
{
|
||||||
index++;
|
writer.WriteNullValue();
|
||||||
continue;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var attribute in indexAttributes)
|
writer.WriteStartArray();
|
||||||
|
|
||||||
|
var ordered = _typePropertyInfo.Value.Where(x => x.ArrayProperty != null).OrderBy(p => p.ArrayProperty.Index);
|
||||||
|
var last = -1;
|
||||||
|
foreach (var prop in ordered)
|
||||||
{
|
{
|
||||||
var targetType = attribute.TargetType;
|
if (prop.ArrayProperty.Index == last)
|
||||||
object? value = null;
|
continue;
|
||||||
if (attribute.JsonConverter != null)
|
|
||||||
|
while (prop.ArrayProperty.Index != last + 1)
|
||||||
{
|
{
|
||||||
if (attribute.JsonSerializerOptions == null)
|
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
|
||||||
{
|
{
|
||||||
attribute.JsonSerializerOptions = 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();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override T? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||||
|
{
|
||||||
|
if (reader.TokenType == JsonTokenType.Null)
|
||||||
|
return default;
|
||||||
|
|
||||||
|
var result = new T();
|
||||||
|
return ParseObject(ref reader, result, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#if NET5_0_OR_GREATER
|
||||||
|
[UnconditionalSuppressMessage("AssemblyLoadTrimming", "IL3050:RequiresUnreferencedCode", Justification = "JsonSerializerOptions provided here has TypeInfoResolver set")]
|
||||||
|
[UnconditionalSuppressMessage("AssemblyLoadTrimming", "IL2026:RequiresUnreferencedCode", Justification = "JsonSerializerOptions provided here has TypeInfoResolver set")]
|
||||||
|
private static T ParseObject(ref Utf8JsonReader reader, T result, JsonSerializerOptions options)
|
||||||
|
#else
|
||||||
|
private static T ParseObject(ref Utf8JsonReader reader, T result, JsonSerializerOptions options)
|
||||||
|
#endif
|
||||||
|
{
|
||||||
|
if (reader.TokenType != JsonTokenType.StartArray)
|
||||||
|
throw new Exception("Not an array");
|
||||||
|
|
||||||
|
int index = 0;
|
||||||
|
while (reader.Read())
|
||||||
|
{
|
||||||
|
if (reader.TokenType == JsonTokenType.EndArray)
|
||||||
|
break;
|
||||||
|
|
||||||
|
var indexAttributes = _typePropertyInfo.Value.Where(a => a.ArrayProperty.Index == index);
|
||||||
|
if (!indexAttributes.Any())
|
||||||
|
{
|
||||||
|
index++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var attribute in indexAttributes)
|
||||||
|
{
|
||||||
|
var targetType = attribute.TargetType;
|
||||||
|
object? value = null;
|
||||||
|
if (attribute.JsonConverter != null)
|
||||||
|
{
|
||||||
|
if (attribute.JsonSerializerOptions == null)
|
||||||
{
|
{
|
||||||
NumberHandling = JsonNumberHandling.AllowReadingFromString | JsonNumberHandling.AllowNamedFloatingPointLiterals,
|
attribute.JsonSerializerOptions = new JsonSerializerOptions
|
||||||
PropertyNameCaseInsensitive = false,
|
{
|
||||||
Converters = { attribute.JsonConverter },
|
NumberHandling = JsonNumberHandling.AllowReadingFromString | JsonNumberHandling.AllowNamedFloatingPointLiterals,
|
||||||
TypeInfoResolver = options.TypeInfoResolver,
|
PropertyNameCaseInsensitive = false,
|
||||||
|
Converters = { attribute.JsonConverter },
|
||||||
|
TypeInfoResolver = options.TypeInfoResolver,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
var doc = JsonDocument.ParseValue(ref reader);
|
||||||
|
value = doc.Deserialize(attribute.PropertyInfo.PropertyType, attribute.JsonSerializerOptions);
|
||||||
|
}
|
||||||
|
else if (attribute.DefaultDeserialization)
|
||||||
|
{
|
||||||
|
value = JsonDocument.ParseValue(ref reader).Deserialize(options.GetTypeInfo(attribute.PropertyInfo.PropertyType));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
value = reader.TokenType switch
|
||||||
|
{
|
||||||
|
JsonTokenType.Null => null,
|
||||||
|
JsonTokenType.False => false,
|
||||||
|
JsonTokenType.True => true,
|
||||||
|
JsonTokenType.String => reader.GetString(),
|
||||||
|
JsonTokenType.Number => reader.GetDecimal(),
|
||||||
|
JsonTokenType.StartObject => JsonSerializer.Deserialize(ref reader, attribute.TargetType, options),
|
||||||
|
_ => throw new NotImplementedException($"Array deserialization of type {reader.TokenType} not supported"),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
var doc = JsonDocument.ParseValue(ref reader);
|
if (targetType.IsAssignableFrom(value?.GetType()))
|
||||||
value = doc.Deserialize(attribute.PropertyInfo.PropertyType, attribute.JsonSerializerOptions);
|
attribute.PropertyInfo.SetValue(result, value);
|
||||||
}
|
else
|
||||||
else if (attribute.DefaultDeserialization)
|
attribute.PropertyInfo.SetValue(result, value == null ? null : Convert.ChangeType(value, targetType, CultureInfo.InvariantCulture));
|
||||||
{
|
|
||||||
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()))
|
index++;
|
||||||
attribute.PropertyInfo.SetValue(result, value);
|
|
||||||
else
|
|
||||||
attribute.PropertyInfo.SetValue(result, value == null ? null : Convert.ChangeType(value, targetType, CultureInfo.InvariantCulture));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
index++;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
private static bool IsSimple(Type type)
|
||||||
}
|
|
||||||
|
|
||||||
private static bool IsSimple(Type type)
|
|
||||||
{
|
|
||||||
if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>))
|
|
||||||
{
|
{
|
||||||
// nullable type, check if the nested type is simple.
|
if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>))
|
||||||
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 att = property.GetCustomAttribute<ArrayPropertyAttribute>();
|
var attributes = new List<ArrayPropertyInfo>();
|
||||||
if (att == null)
|
var properties = typeof(T).GetProperties();
|
||||||
continue;
|
foreach (var property in properties)
|
||||||
|
|
||||||
var targetType = Nullable.GetUnderlyingType(property.PropertyType) ?? property.PropertyType;
|
|
||||||
var converterType = property.GetCustomAttribute<JsonConverterAttribute>()?.ConverterType ?? targetType.GetCustomAttribute<JsonConverterAttribute>()?.ConverterType;
|
|
||||||
attributes.Add(new ArrayPropertyInfo
|
|
||||||
{
|
{
|
||||||
ArrayProperty = att,
|
var att = property.GetCustomAttribute<ArrayPropertyAttribute>();
|
||||||
PropertyInfo = property,
|
if (att == null)
|
||||||
DefaultDeserialization = property.GetCustomAttribute<CryptoExchange.Net.Attributes.JsonConversionAttribute>() != null,
|
continue;
|
||||||
JsonConverter = converterType == null ? null : (JsonConverter)Activator.CreateInstance(converterType)!,
|
|
||||||
TargetType = targetType
|
var targetType = Nullable.GetUnderlyingType(property.PropertyType) ?? property.PropertyType;
|
||||||
});
|
var converterType = property.GetCustomAttribute<JsonConverterAttribute>()?.ConverterType ?? targetType.GetCustomAttribute<JsonConverterAttribute>()?.ConverterType;
|
||||||
|
attributes.Add(new ArrayPropertyInfo
|
||||||
|
{
|
||||||
|
ArrayProperty = att,
|
||||||
|
PropertyInfo = property,
|
||||||
|
DefaultDeserialization = property.GetCustomAttribute<CryptoExchange.Net.Attributes.JsonConversionAttribute>() != null,
|
||||||
|
JsonConverter = converterType == null ? null : (JsonConverter)Activator.CreateInstance(converterType)!,
|
||||||
|
TargetType = targetType
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return attributes;
|
||||||
}
|
}
|
||||||
|
|
||||||
return attributes;
|
private class ArrayPropertyInfo
|
||||||
}
|
{
|
||||||
|
public PropertyInfo PropertyInfo { get; set; } = null!;
|
||||||
private class ArrayPropertyInfo
|
public ArrayPropertyAttribute ArrayProperty { get; set; } = null!;
|
||||||
{
|
public JsonConverter? JsonConverter { get; set; }
|
||||||
public PropertyInfo PropertyInfo { get; set; } = null!;
|
public bool DefaultDeserialization { get; set; }
|
||||||
public ArrayPropertyAttribute ArrayProperty { get; set; } = null!;
|
public Type TargetType { get; set; } = null!;
|
||||||
public JsonConverter? JsonConverter { get; set; }
|
public JsonSerializerOptions? JsonSerializerOptions { get; set; } = null;
|
||||||
public bool DefaultDeserialization { get; set; }
|
}
|
||||||
public Type TargetType { get; set; } = null!;
|
|
||||||
public JsonSerializerOptions? JsonSerializerOptions { get; set; }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,45 +1,46 @@
|
|||||||
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 />
|
/// <summary>
|
||||||
public override decimal Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
/// Decimal converter that handles overflowing decimal values (by setting it to decimal.MaxValue)
|
||||||
|
/// </summary>
|
||||||
|
public class BigDecimalConverter : JsonConverter<decimal>
|
||||||
{
|
{
|
||||||
if (reader.TokenType == JsonTokenType.String)
|
/// <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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
return decimal.Parse(reader.GetString()!, NumberStyles.Float, CultureInfo.InvariantCulture);
|
return reader.GetDecimal();
|
||||||
}
|
}
|
||||||
catch(OverflowException)
|
catch(FormatException)
|
||||||
{
|
{
|
||||||
// Value doesn't fit decimal, default to max value
|
// Format issue, assume value is too large
|
||||||
return decimal.MaxValue;
|
return decimal.MaxValue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
/// <inheritdoc />
|
||||||
|
public override void Write(Utf8JsonWriter writer, decimal value, JsonSerializerOptions options)
|
||||||
{
|
{
|
||||||
return reader.GetDecimal();
|
writer.WriteNumberValue(value);
|
||||||
}
|
}
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,83 +1,84 @@
|
|||||||
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
|
|
||||||
{
|
{
|
||||||
/// <inheritdoc />
|
/// <summary>
|
||||||
public override bool CanConvert(Type typeToConvert)
|
/// Bool converter
|
||||||
|
/// </summary>
|
||||||
|
public class BoolConverter : JsonConverterFactory
|
||||||
{
|
{
|
||||||
return typeToConvert == typeof(bool) || typeToConvert == typeof(bool?);
|
/// <inheritdoc />
|
||||||
}
|
public override bool CanConvert(Type typeToConvert)
|
||||||
|
|
||||||
/// <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 typeToConvert == typeof(bool) || typeToConvert == typeof(bool?);
|
||||||
return true;
|
}
|
||||||
|
|
||||||
if (reader.TokenType == JsonTokenType.False)
|
/// <inheritdoc />
|
||||||
return false;
|
public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options)
|
||||||
|
{
|
||||||
|
return typeToConvert == typeof(bool) ? new BoolConverterInner<bool>() : new BoolConverterInner<bool?>();
|
||||||
|
}
|
||||||
|
|
||||||
var value = reader.TokenType switch
|
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)
|
||||||
{
|
{
|
||||||
JsonTokenType.String => reader.GetString(),
|
if (reader.TokenType == JsonTokenType.True)
|
||||||
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;
|
return true;
|
||||||
case "false":
|
|
||||||
case "no":
|
if (reader.TokenType == JsonTokenType.False)
|
||||||
case "n":
|
|
||||||
case "0":
|
|
||||||
case "off":
|
|
||||||
case "-1":
|
|
||||||
return 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}");
|
||||||
}
|
}
|
||||||
|
|
||||||
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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options)
|
|
||||||
{
|
|
||||||
if (value is bool boolVal)
|
|
||||||
writer.WriteBooleanValue(boolVal);
|
|
||||||
else
|
|
||||||
writer.WriteNullValue();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,36 +1,37 @@
|
|||||||
using System;
|
using System;
|
||||||
#if NET5_0_OR_GREATER
|
using System.Collections.Generic;
|
||||||
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)
|
|
||||||
{
|
{
|
||||||
var str = reader.GetString();
|
/// <inheritdoc />
|
||||||
if (string.IsNullOrEmpty(str))
|
public override T[]? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||||
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))));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,241 +1,242 @@
|
|||||||
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
|
|
||||||
{
|
{
|
||||||
private static readonly DateTime _epoch = new(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
|
/// <summary>
|
||||||
private const long _ticksPerSecond = TimeSpan.TicksPerMillisecond * 1000;
|
/// Date time converter
|
||||||
private const double _ticksPerMicrosecond = TimeSpan.TicksPerMillisecond / 1000d;
|
/// </summary>
|
||||||
private const double _ticksPerNanosecond = TimeSpan.TicksPerMillisecond / 1000d / 1000;
|
public class DateTimeConverter : JsonConverterFactory
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public override bool CanConvert(Type typeToConvert)
|
|
||||||
{
|
{
|
||||||
return typeToConvert == typeof(DateTime) || typeToConvert == typeof(DateTime?);
|
private static readonly DateTime _epoch = new(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
|
||||||
}
|
private const long _ticksPerSecond = TimeSpan.TicksPerMillisecond * 1000;
|
||||||
|
private const double _ticksPerMicrosecond = TimeSpan.TicksPerMillisecond / 1000d;
|
||||||
|
private const double _ticksPerNanosecond = TimeSpan.TicksPerMillisecond / 1000d / 1000;
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options)
|
public override bool CanConvert(Type typeToConvert)
|
||||||
{
|
|
||||||
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)
|
return typeToConvert == typeof(DateTime) || typeToConvert == typeof(DateTime?);
|
||||||
{
|
}
|
||||||
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)
|
/// <inheritdoc />
|
||||||
{
|
public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options)
|
||||||
var longValue = reader.GetDouble();
|
{
|
||||||
if (longValue == 0 || longValue < 0)
|
return typeToConvert == typeof(DateTime) ? new DateTimeConverterInner<DateTime>() : new DateTimeConverterInner<DateTime?>();
|
||||||
return default;
|
}
|
||||||
|
|
||||||
return ParseFromDouble(longValue);
|
private class DateTimeConverterInner<T> : JsonConverter<T>
|
||||||
}
|
{
|
||||||
else if (reader.TokenType is JsonTokenType.String)
|
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)
|
||||||
{
|
{
|
||||||
var stringValue = reader.GetString();
|
if (reader.TokenType == JsonTokenType.Null)
|
||||||
if (string.IsNullOrWhiteSpace(stringValue)
|
|
||||||
|| stringValue == "-1"
|
|
||||||
|| stringValue == "0001-01-01T00:00:00Z"
|
|
||||||
|| double.TryParse(stringValue, out var doubleVal) && doubleVal == 0)
|
|
||||||
{
|
{
|
||||||
|
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;
|
return default;
|
||||||
}
|
}
|
||||||
|
|
||||||
return ParseFromString(stringValue!);
|
if (reader.TokenType is JsonTokenType.Number)
|
||||||
}
|
{
|
||||||
else
|
var longValue = reader.GetDouble();
|
||||||
{
|
if (longValue == 0 || longValue < 0)
|
||||||
return reader.GetDateTime();
|
return default;
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options)
|
return ParseFromDouble(longValue);
|
||||||
{
|
}
|
||||||
if (value == null)
|
else if (reader.TokenType is JsonTokenType.String)
|
||||||
{
|
{
|
||||||
writer.WriteNullValue();
|
var stringValue = reader.GetString();
|
||||||
}
|
if (string.IsNullOrWhiteSpace(stringValue)
|
||||||
else
|
|| stringValue == "-1"
|
||||||
{
|
|| stringValue == "0001-01-01T00:00:00Z"
|
||||||
var dtValue = (DateTime)(object)value;
|
|| double.TryParse(stringValue, out var doubleVal) && doubleVal == 0)
|
||||||
if (dtValue == default)
|
{
|
||||||
writer.WriteStringValue(default(DateTime));
|
return default;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ParseFromString(stringValue!);
|
||||||
|
}
|
||||||
else
|
else
|
||||||
writer.WriteNumberValue((long)Math.Round((dtValue - new DateTime(1970, 1, 1)).TotalMilliseconds));
|
{
|
||||||
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <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);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,43 +1,45 @@
|
|||||||
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?>
|
|
||||||
{
|
{
|
||||||
/// <inheritdoc />
|
/// <summary>
|
||||||
public override decimal? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
/// Decimal converter
|
||||||
|
/// </summary>
|
||||||
|
public class DecimalConverter : JsonConverter<decimal?>
|
||||||
{
|
{
|
||||||
if (reader.TokenType == JsonTokenType.Null)
|
/// <inheritdoc />
|
||||||
return null;
|
public override decimal? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||||
|
|
||||||
if (reader.TokenType == JsonTokenType.String)
|
|
||||||
{
|
{
|
||||||
var value = reader.GetString();
|
if (reader.TokenType == JsonTokenType.Null)
|
||||||
return ExchangeHelpers.ParseDecimal(value);
|
return null;
|
||||||
|
|
||||||
|
if (reader.TokenType == JsonTokenType.String)
|
||||||
|
{
|
||||||
|
var value = reader.GetString();
|
||||||
|
return ExchangeHelpers.ParseDecimal(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return reader.GetDecimal();
|
||||||
|
}
|
||||||
|
catch(FormatException)
|
||||||
|
{
|
||||||
|
// Format issue, assume value is too large
|
||||||
|
return decimal.MaxValue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
/// <inheritdoc />
|
||||||
|
public override void Write(Utf8JsonWriter writer, decimal? value, JsonSerializerOptions options)
|
||||||
{
|
{
|
||||||
return reader.GetDecimal();
|
if (value == null)
|
||||||
|
writer.WriteNullValue();
|
||||||
|
else
|
||||||
|
writer.WriteNumberValue(value.Value);
|
||||||
}
|
}
|
||||||
catch(FormatException)
|
|
||||||
{
|
|
||||||
// Format issue, assume value is too large
|
|
||||||
return decimal.MaxValue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public override void Write(Utf8JsonWriter writer, decimal? value, JsonSerializerOptions options)
|
|
||||||
{
|
|
||||||
if (value == null)
|
|
||||||
writer.WriteNullValue();
|
|
||||||
else
|
|
||||||
writer.WriteNumberValue(value.Value);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,22 +1,23 @@
|
|||||||
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 />
|
/// <summary>
|
||||||
public override decimal Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
/// Converter for serializing decimal values as string
|
||||||
|
/// </summary>
|
||||||
|
public class DecimalStringWriterConverter : JsonConverter<decimal>
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
/// <inheritdoc />
|
||||||
}
|
public override decimal Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override void Write(Utf8JsonWriter writer, decimal value, JsonSerializerOptions options)
|
public override void Write(Utf8JsonWriter writer, decimal value, JsonSerializerOptions options)
|
||||||
=> writer.WriteStringValue(value.ToString(CultureInfo.InvariantCulture) ?? null);
|
=> writer.WriteStringValue(value.ToString(CultureInfo.InvariantCulture) ?? null);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,280 +9,288 @@ 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>
|
||||||
/// Get the enum value from a string
|
/// Static EnumConverter methods
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="value">String value</param>
|
public static class EnumConverter
|
||||||
/// <returns></returns>
|
|
||||||
#if NET5_0_OR_GREATER
|
|
||||||
public static T? ParseString<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor | DynamicallyAccessedMemberTypes.PublicFields)] T>(string value) where T : struct, Enum
|
|
||||||
#else
|
|
||||||
public static T? ParseString<T>(string value) where T : struct, Enum
|
|
||||||
#endif
|
|
||||||
=> EnumConverter<T>.ParseString(value);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Get the string value for an enum value using the MapAttribute mapping. When multiple values are mapped for a enum entry the first value will be returned
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="enumValue"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
#if NET5_0_OR_GREATER
|
|
||||||
public static string GetString<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor | DynamicallyAccessedMemberTypes.PublicFields)] T>(T enumValue) where T : struct, Enum
|
|
||||||
#else
|
|
||||||
public static string GetString<T>(T enumValue) where T : struct, Enum
|
|
||||||
#endif
|
|
||||||
=> EnumConverter<T>.GetString(enumValue);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Get the string value for an enum value using the MapAttribute mapping. When multiple values are mapped for a enum entry the first value will be returned
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="enumValue"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
[return: NotNullIfNotNull("enumValue")]
|
|
||||||
#if NET5_0_OR_GREATER
|
|
||||||
public static string? GetString<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor | DynamicallyAccessedMemberTypes.PublicFields)] T>(T? enumValue) where T : struct, Enum
|
|
||||||
#else
|
|
||||||
public static string? GetString<T>(T? enumValue) where T : struct, Enum
|
|
||||||
#endif
|
|
||||||
=> EnumConverter<T>.GetString(enumValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <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 readonly EnumConverter<T> _enumConverter;
|
/// <summary>
|
||||||
|
/// Get the enum value from a string
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value">String value</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
#if NET5_0_OR_GREATER
|
||||||
|
public static T? ParseString<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor | DynamicallyAccessedMemberTypes.PublicFields)] T>(string value) where T : struct, Enum
|
||||||
|
#else
|
||||||
|
public static T? ParseString<T>(string value) where T : struct, Enum
|
||||||
|
#endif
|
||||||
|
=> EnumConverter<T>.ParseString(value);
|
||||||
|
|
||||||
public NullableEnumConverter(EnumConverter<T> enumConverter)
|
/// <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
|
||||||
_enumConverter = enumConverter;
|
/// </summary>
|
||||||
}
|
/// <param name="enumValue"></param>
|
||||||
public override T? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
/// <returns></returns>
|
||||||
{
|
#if NET5_0_OR_GREATER
|
||||||
return EnumConverter<T>.ReadNullable(ref reader, typeToConvert, options, out var isEmptyString, out var warn);
|
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);
|
||||||
|
|
||||||
public override void Write(Utf8JsonWriter writer, T? value, JsonSerializerOptions options)
|
/// <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
|
||||||
if (value == null)
|
/// </summary>
|
||||||
{
|
/// <param name="enumValue"></param>
|
||||||
writer.WriteNullValue();
|
/// <returns></returns>
|
||||||
}
|
[return: NotNullIfNotNull("enumValue")]
|
||||||
else
|
#if NET5_0_OR_GREATER
|
||||||
{
|
public static string? GetString<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor | DynamicallyAccessedMemberTypes.PublicFields)] T>(T? enumValue) where T : struct, Enum
|
||||||
_enumConverter.Write(writer, value.Value, options);
|
#else
|
||||||
}
|
public static string? GetString<T>(T? enumValue) where T : struct, Enum
|
||||||
}
|
#endif
|
||||||
|
=> EnumConverter<T>.GetString(enumValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <summary>
|
||||||
public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
/// 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
|
||||||
{
|
{
|
||||||
var t = ReadNullable(ref reader, typeToConvert, options, out var isEmptyString, out var warn);
|
private static List<KeyValuePair<T, string>>? _mapping = null;
|
||||||
if (t == null)
|
private NullableEnumConverter? _nullableEnumConverter = null;
|
||||||
|
|
||||||
|
private static ConcurrentBag<string> _unknownValuesWarned = new ConcurrentBag<string>();
|
||||||
|
|
||||||
|
internal class NullableEnumConverter : JsonConverter<T?>
|
||||||
{
|
{
|
||||||
if (warn)
|
private readonly EnumConverter<T> _enumConverter;
|
||||||
|
|
||||||
|
public NullableEnumConverter(EnumConverter<T> enumConverter)
|
||||||
{
|
{
|
||||||
if (isEmptyString)
|
_enumConverter = enumConverter;
|
||||||
|
}
|
||||||
|
public override T? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||||
|
{
|
||||||
|
return _enumConverter.ReadNullable(ref reader, typeToConvert, options, out var isEmptyString, out var warn);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Write(Utf8JsonWriter writer, T? value, JsonSerializerOptions options)
|
||||||
|
{
|
||||||
|
if (value == null)
|
||||||
{
|
{
|
||||||
// We received an empty string and have no mapping for it, and the property isn't nullable
|
writer.WriteNullValue();
|
||||||
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
|
||||||
{
|
{
|
||||||
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");
|
_enumConverter.Write(writer, value.Value, options);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return new T(); // return default value
|
|
||||||
}
|
}
|
||||||
else
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||||
{
|
{
|
||||||
return t.Value;
|
var t = ReadNullable(ref reader, typeToConvert, options, out var isEmptyString, out var warn);
|
||||||
}
|
if (t == null)
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
{
|
|
||||||
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))
|
|
||||||
{
|
{
|
||||||
isEmptyString = true;
|
if (warn)
|
||||||
|
{
|
||||||
|
if (isEmptyString)
|
||||||
|
{
|
||||||
|
// We received an empty string and have no mapping for it, and the property isn't nullable
|
||||||
|
Trace.WriteLine($"{DateTime.Now:yyyy/MM/dd HH:mm:ss:fff} | Warning | Received empty string as enum value, but property type is not a nullable enum. EnumType: {typeof(T).Name}. If you think {typeof(T).Name} should be nullable please open an issue on the Github repo");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Trace.WriteLine($"{DateTime.Now:yyyy/MM/dd HH:mm:ss:fff} | Warning | Received null enum value, but property type is not a nullable enum. EnumType: {typeof(T).Name}. If you think {typeof(T).Name} should be nullable please open an issue on the Github repo");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new T(); // return default value
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// We received an enum value but weren't able to parse it.
|
return t.Value;
|
||||||
if (!_unknownValuesWarned.Contains(stringValue))
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private 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
|
||||||
|
{
|
||||||
|
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 (stringValue is null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
if (!GetValue(enumType, stringValue, out var result))
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(stringValue))
|
||||||
{
|
{
|
||||||
warn = true;
|
isEmptyString = 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");
|
else
|
||||||
|
{
|
||||||
|
// We received an enum value but weren't able to parse it.
|
||||||
|
if (!_unknownValuesWarned.Contains(stringValue))
|
||||||
|
{
|
||||||
|
warn = true;
|
||||||
|
_unknownValuesWarned.Add(stringValue!);
|
||||||
|
Trace.WriteLine($"{DateTime.Now:yyyy/MM/dd HH:mm:ss:fff} | Warning | Cannot map enum value. EnumType: {enumType.Name}, Value: {stringValue}, Known values: {string.Join(", ", _mapping.Select(m => m.Value))}. If you think {stringValue} should added please open an issue on the Github repo");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
// Check for exact match first, then if not found fallback to a case insensitive match
|
||||||
|
var mapping = _mapping.FirstOrDefault(kv => kv.Value.Equals(value, StringComparison.InvariantCulture));
|
||||||
|
if (mapping.Equals(default(KeyValuePair<T, string>)))
|
||||||
|
mapping = _mapping.FirstOrDefault(kv => kv.Value.Equals(value, StringComparison.InvariantCultureIgnoreCase));
|
||||||
|
|
||||||
|
if (!mapping.Equals(default(KeyValuePair<T, string>)))
|
||||||
|
{
|
||||||
|
result = mapping.Key;
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
if (objectType.IsDefined(typeof(FlagsAttribute)))
|
||||||
|
{
|
||||||
|
var intValue = int.Parse(value);
|
||||||
|
result = (T)Enum.ToObject(objectType, intValue);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_unknownValuesWarned.Contains(value))
|
||||||
|
{
|
||||||
|
// Check if it is an known unknown value
|
||||||
|
// Done here to prevent lookup overhead for normal conversions, but prevent expensive exception throwing
|
||||||
|
result = default;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (String.IsNullOrEmpty(value))
|
||||||
|
{
|
||||||
|
// An empty/null value will always fail when parsing, so just return here
|
||||||
|
result = default;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// If no explicit mapping is found try to parse string
|
||||||
|
result = (T)Enum.Parse(objectType, value, true);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
result = default;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
private static List<KeyValuePair<T, string>> AddMapping()
|
||||||
}
|
|
||||||
|
|
||||||
/// <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)
|
|
||||||
{
|
{
|
||||||
// Check for exact match first, then if not found fallback to a case insensitive match
|
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));
|
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
|
||||||
{
|
{
|
||||||
result = mapping.Key;
|
// If no explicit mapping is found try to parse string
|
||||||
return true;
|
return (T)Enum.Parse(type, value, true);
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
return default;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (objectType.IsDefined(typeof(FlagsAttribute)))
|
/// <inheritdoc />
|
||||||
|
public JsonConverter CreateNullableConverter()
|
||||||
{
|
{
|
||||||
var intValue = int.Parse(value);
|
_nullableEnumConverter ??= new NullableEnumConverter(this);
|
||||||
result = (T)Enum.ToObject(objectType, intValue);
|
return _nullableEnumConverter;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,21 +1,23 @@
|
|||||||
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 />
|
/// <summary>
|
||||||
public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
/// Converter for serializing enum values as int
|
||||||
|
/// </summary>
|
||||||
|
public class EnumIntWriterConverter<T> : JsonConverter<T> where T: struct, Enum
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
/// <inheritdoc />
|
||||||
}
|
public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options)
|
public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options)
|
||||||
=> writer.WriteNumberValue((int)(object)value);
|
=> writer.WriteNumberValue((int)(object)value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
using System.Text.Json.Serialization;
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
namespace CryptoExchange.Net.Converters.SystemTextJson;
|
namespace CryptoExchange.Net.Converters.SystemTextJson
|
||||||
|
|
||||||
internal interface INullableConverterFactory
|
|
||||||
{
|
{
|
||||||
JsonConverter CreateNullableConverter();
|
internal interface INullableConverterFactory
|
||||||
|
{
|
||||||
|
JsonConverter CreateNullableConverter();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,39 +1,40 @@
|
|||||||
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?>
|
|
||||||
{
|
{
|
||||||
/// <inheritdoc />
|
/// <summary>
|
||||||
public override int? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
/// Int converter
|
||||||
|
/// </summary>
|
||||||
|
public class IntConverter : JsonConverter<int?>
|
||||||
{
|
{
|
||||||
if (reader.TokenType == JsonTokenType.Null)
|
/// <inheritdoc />
|
||||||
return null;
|
public override int? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||||
|
|
||||||
if (reader.TokenType == JsonTokenType.String)
|
|
||||||
{
|
{
|
||||||
var value = reader.GetString();
|
if (reader.TokenType == JsonTokenType.Null)
|
||||||
if (string.IsNullOrEmpty(value))
|
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
return int.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture);
|
if (reader.TokenType == JsonTokenType.String)
|
||||||
|
{
|
||||||
|
var value = reader.GetString();
|
||||||
|
if (string.IsNullOrEmpty(value))
|
||||||
|
return null;
|
||||||
|
|
||||||
|
return int.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture);
|
||||||
|
}
|
||||||
|
|
||||||
|
return reader.GetInt32();
|
||||||
}
|
}
|
||||||
|
|
||||||
return reader.GetInt32();
|
/// <inheritdoc />
|
||||||
}
|
public override void Write(Utf8JsonWriter writer, int? value, JsonSerializerOptions options)
|
||||||
|
{
|
||||||
/// <inheritdoc />
|
if (value == null)
|
||||||
public override void Write(Utf8JsonWriter writer, int? value, JsonSerializerOptions options)
|
writer.WriteNullValue();
|
||||||
{
|
else
|
||||||
if (value == null)
|
writer.WriteNumberValue(value.Value);
|
||||||
writer.WriteNullValue();
|
}
|
||||||
else
|
|
||||||
writer.WriteNumberValue(value.Value);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,39 +1,40 @@
|
|||||||
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?>
|
|
||||||
{
|
{
|
||||||
/// <inheritdoc />
|
/// <summary>
|
||||||
public override long? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
/// Int converter
|
||||||
|
/// </summary>
|
||||||
|
public class LongConverter : JsonConverter<long?>
|
||||||
{
|
{
|
||||||
if (reader.TokenType == JsonTokenType.Null)
|
/// <inheritdoc />
|
||||||
return null;
|
public override long? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||||
|
|
||||||
if (reader.TokenType == JsonTokenType.String)
|
|
||||||
{
|
{
|
||||||
var value = reader.GetString();
|
if (reader.TokenType == JsonTokenType.Null)
|
||||||
if (string.IsNullOrEmpty(value))
|
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
return long.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture);
|
if (reader.TokenType == JsonTokenType.String)
|
||||||
|
{
|
||||||
|
var value = reader.GetString();
|
||||||
|
if (string.IsNullOrEmpty(value))
|
||||||
|
return null;
|
||||||
|
|
||||||
|
return long.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture);
|
||||||
|
}
|
||||||
|
|
||||||
|
return reader.GetInt64();
|
||||||
}
|
}
|
||||||
|
|
||||||
return reader.GetInt64();
|
/// <inheritdoc />
|
||||||
}
|
public override void Write(Utf8JsonWriter writer, long? value, JsonSerializerOptions options)
|
||||||
|
{
|
||||||
/// <inheritdoc />
|
if (value == null)
|
||||||
public override void Write(Utf8JsonWriter writer, long? value, JsonSerializerOptions options)
|
writer.WriteNullValue();
|
||||||
{
|
else
|
||||||
if (value == null)
|
writer.WriteNumberValue(value.Value);
|
||||||
writer.WriteNullValue();
|
}
|
||||||
else
|
|
||||||
writer.WriteNumberValue(value.Value);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,40 +1,43 @@
|
|||||||
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
|
|
||||||
{
|
{
|
||||||
private readonly IJsonTypeInfoResolver _jsonTypeInfoResolver;
|
internal class NullableEnumConverterFactory : JsonConverterFactory
|
||||||
private static readonly JsonSerializerOptions _options = new JsonSerializerOptions();
|
|
||||||
|
|
||||||
public NullableEnumConverterFactory(IJsonTypeInfoResolver jsonTypeInfoResolver)
|
|
||||||
{
|
{
|
||||||
_jsonTypeInfoResolver = jsonTypeInfoResolver;
|
private readonly IJsonTypeInfoResolver _jsonTypeInfoResolver;
|
||||||
}
|
private static readonly JsonSerializerOptions _options = new JsonSerializerOptions();
|
||||||
|
|
||||||
public override bool CanConvert(Type typeToConvert)
|
public NullableEnumConverterFactory(IJsonTypeInfoResolver jsonTypeInfoResolver)
|
||||||
{
|
{
|
||||||
var b = Nullable.GetUnderlyingType(typeToConvert);
|
_jsonTypeInfoResolver = jsonTypeInfoResolver;
|
||||||
if (b == null)
|
}
|
||||||
return false;
|
|
||||||
|
|
||||||
var typeInfo = _jsonTypeInfoResolver.GetTypeInfo(b, _options);
|
public override bool CanConvert(Type typeToConvert)
|
||||||
if (typeInfo == null)
|
{
|
||||||
return false;
|
var b = Nullable.GetUnderlyingType(typeToConvert);
|
||||||
|
if (b == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
return typeInfo.Converter is INullableConverterFactory;
|
var typeInfo = _jsonTypeInfoResolver.GetTypeInfo(b, _options);
|
||||||
}
|
if (typeInfo == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
public override JsonConverter? CreateConverter(Type typeToConvert, JsonSerializerOptions options)
|
return typeInfo.Converter is INullableConverterFactory;
|
||||||
{
|
}
|
||||||
var b = Nullable.GetUnderlyingType(typeToConvert) ?? throw new ArgumentNullException($"Not nullable {typeToConvert.Name}");
|
|
||||||
var typeInfo = _jsonTypeInfoResolver.GetTypeInfo(b, _options) ?? throw new ArgumentNullException($"Can find type {typeToConvert.Name}");
|
public override JsonConverter? CreateConverter(Type typeToConvert, JsonSerializerOptions options)
|
||||||
if (typeInfo.Converter is not INullableConverterFactory nullConverterFactory)
|
{
|
||||||
throw new ArgumentNullException($"Can find type converter for {typeToConvert.Name}");
|
var b = Nullable.GetUnderlyingType(typeToConvert) ?? throw new ArgumentNullException($"Not nullable {typeToConvert.Name}");
|
||||||
|
var typeInfo = _jsonTypeInfoResolver.GetTypeInfo(b, _options) ?? throw new ArgumentNullException($"Can find type {typeToConvert.Name}");
|
||||||
return nullConverterFactory.CreateNullableConverter();
|
if (typeInfo.Converter is not INullableConverterFactory nullConverterFactory)
|
||||||
|
throw new ArgumentNullException($"Can find type converter for {typeToConvert.Name}");
|
||||||
|
|
||||||
|
return nullConverterFactory.CreateNullableConverter();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,41 +1,42 @@
|
|||||||
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?>
|
|
||||||
{
|
{
|
||||||
/// <inheritdoc />
|
/// <summary>
|
||||||
public override string? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
/// Read string or number as string
|
||||||
|
/// </summary>
|
||||||
|
public class NumberStringConverter : JsonConverter<string?>
|
||||||
{
|
{
|
||||||
if (reader.TokenType == JsonTokenType.Null)
|
/// <inheritdoc />
|
||||||
return null;
|
public override string? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||||
|
|
||||||
if (reader.TokenType == JsonTokenType.Number)
|
|
||||||
{
|
{
|
||||||
if (reader.TryGetInt64(out var value))
|
if (reader.TokenType == JsonTokenType.Null)
|
||||||
return value.ToString();
|
return null;
|
||||||
|
|
||||||
return reader.GetDecimal().ToString();
|
if (reader.TokenType == JsonTokenType.Number)
|
||||||
|
{
|
||||||
|
if (reader.TryGetInt64(out var value))
|
||||||
|
return value.ToString();
|
||||||
|
|
||||||
|
return reader.GetDecimal().ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return reader.GetString();
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
/// <inheritdoc />
|
||||||
|
public override void Write(Utf8JsonWriter writer, string? value, JsonSerializerOptions options)
|
||||||
{
|
{
|
||||||
return reader.GetString();
|
writer.WriteStringValue(value);
|
||||||
}
|
}
|
||||||
catch (Exception)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public override void Write(Utf8JsonWriter writer, string? value, JsonSerializerOptions options)
|
|
||||||
{
|
|
||||||
writer.WriteStringValue(value);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,44 +1,43 @@
|
|||||||
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>
|
|
||||||
{
|
{
|
||||||
/// <inheritdoc />
|
/// <summary>
|
||||||
#if NET5_0_OR_GREATER
|
/// Converter for values which contain a nested json value
|
||||||
[UnconditionalSuppressMessage("AssemblyLoadTrimming", "IL3050:RequiresUnreferencedCode", Justification = "JsonSerializerOptions provided here has TypeInfoResolver set")]
|
/// </summary>
|
||||||
[UnconditionalSuppressMessage("AssemblyLoadTrimming", "IL2026:RequiresUnreferencedCode", Justification = "JsonSerializerOptions provided here has TypeInfoResolver set")]
|
public class ObjectStringConverter<T> : JsonConverter<T>
|
||||||
#endif
|
|
||||||
public override T? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
|
||||||
{
|
{
|
||||||
if (reader.TokenType == JsonTokenType.Null)
|
/// <inheritdoc />
|
||||||
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 void Write(Utf8JsonWriter writer, T? value, JsonSerializerOptions options)
|
public override T? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||||
{
|
{
|
||||||
if (value is null)
|
if (reader.TokenType == JsonTokenType.Null)
|
||||||
writer.WriteStringValue("");
|
return default;
|
||||||
|
|
||||||
writer.WriteStringValue(JsonSerializer.Serialize(value, options));
|
var value = reader.GetString();
|
||||||
|
if (string.IsNullOrEmpty(value))
|
||||||
|
return default;
|
||||||
|
|
||||||
|
return (T?)JsonDocument.Parse(value!).Deserialize(typeof(T), options);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
#if NET5_0_OR_GREATER
|
||||||
|
[UnconditionalSuppressMessage("AssemblyLoadTrimming", "IL3050:RequiresUnreferencedCode", Justification = "JsonSerializerOptions provided here has TypeInfoResolver set")]
|
||||||
|
[UnconditionalSuppressMessage("AssemblyLoadTrimming", "IL2026:RequiresUnreferencedCode", Justification = "JsonSerializerOptions provided here has TypeInfoResolver set")]
|
||||||
|
#endif
|
||||||
|
public override void Write(Utf8JsonWriter writer, T? value, JsonSerializerOptions options)
|
||||||
|
{
|
||||||
|
if (value is null)
|
||||||
|
writer.WriteStringValue("");
|
||||||
|
|
||||||
|
writer.WriteStringValue(JsonSerializer.Serialize(value, options));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,40 +1,41 @@
|
|||||||
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>
|
||||||
/// ctor
|
/// Replace a value on a string property
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public ReplaceConverter(params string[] replaceSets)
|
public abstract class ReplaceConverter : JsonConverter<string>
|
||||||
{
|
{
|
||||||
_replacementSets = replaceSets.Select(x =>
|
private readonly (string ValueToReplace, string ValueToReplaceWith)[] _replacementSets;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// ctor
|
||||||
|
/// </summary>
|
||||||
|
public ReplaceConverter(params string[] replaceSets)
|
||||||
{
|
{
|
||||||
var split = x.Split(["->"], StringSplitOptions.None);
|
_replacementSets = replaceSets.Select(x =>
|
||||||
if (split.Length != 2)
|
{
|
||||||
throw new ArgumentException("Invalid replacement config");
|
var split = x.Split(new string[] { "->" }, StringSplitOptions.None);
|
||||||
return (split[0], split[1]);
|
if (split.Length != 2)
|
||||||
}).ToArray();
|
throw new ArgumentException("Invalid replacement config");
|
||||||
}
|
return (split[0], split[1]);
|
||||||
|
}).ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override string? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
public override string? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||||
{
|
{
|
||||||
var value = reader.GetString();
|
var value = reader.GetString();
|
||||||
foreach (var set in _replacementSets)
|
foreach (var set in _replacementSets)
|
||||||
value = value?.Replace(set.ValueToReplace, set.ValueToReplaceWith);
|
value = value?.Replace(set.ValueToReplace, set.ValueToReplaceWith);
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override void Write(Utf8JsonWriter writer, string value, JsonSerializerOptions options) => writer.WriteStringValue(value);
|
public override void Write(Utf8JsonWriter writer, string value, JsonSerializerOptions options) => writer.WriteStringValue(value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,20 +1,23 @@
|
|||||||
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>
|
||||||
/// ctor
|
/// Attribute to mark a model as json serializable. Used for AOT compilation.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public SerializationModelAttribute() { }
|
[AttributeUsage(System.AttributeTargets.Class | AttributeTargets.Enum | System.AttributeTargets.Interface)]
|
||||||
/// <summary>
|
public class SerializationModelAttribute : Attribute
|
||||||
/// ctor
|
{
|
||||||
/// </summary>
|
/// <summary>
|
||||||
/// <param name="type"></param>
|
/// ctor
|
||||||
public SerializationModelAttribute(Type type) { }
|
/// </summary>
|
||||||
|
public SerializationModelAttribute() { }
|
||||||
|
/// <summary>
|
||||||
|
/// ctor
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="type"></param>
|
||||||
|
public SerializationModelAttribute(Type type) { }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,46 +1,47 @@
|
|||||||
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>
|
||||||
/// Get Json serializer settings which includes standard converters for DateTime, bool, enum and number types
|
/// Serializer options
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static JsonSerializerOptions WithConverters(JsonSerializerContext typeResolver, params JsonConverter[] additionalConverters)
|
public static class SerializerOptions
|
||||||
{
|
{
|
||||||
if (!_cache.TryGetValue(typeResolver, out var options))
|
private static readonly ConcurrentDictionary<JsonSerializerContext, JsonSerializerOptions> _cache = new ConcurrentDictionary<JsonSerializerContext, JsonSerializerOptions>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get Json serializer settings which includes standard converters for DateTime, bool, enum and number types
|
||||||
|
/// </summary>
|
||||||
|
public static JsonSerializerOptions WithConverters(JsonSerializerContext typeResolver, params JsonConverter[] additionalConverters)
|
||||||
{
|
{
|
||||||
options = new JsonSerializerOptions
|
if (!_cache.TryGetValue(typeResolver, out var options))
|
||||||
{
|
{
|
||||||
NumberHandling = JsonNumberHandling.AllowReadingFromString | JsonNumberHandling.AllowNamedFloatingPointLiterals,
|
options = new JsonSerializerOptions
|
||||||
PropertyNameCaseInsensitive = false,
|
|
||||||
Converters =
|
|
||||||
{
|
{
|
||||||
new DateTimeConverter(),
|
NumberHandling = JsonNumberHandling.AllowReadingFromString | JsonNumberHandling.AllowNamedFloatingPointLiterals,
|
||||||
new BoolConverter(),
|
PropertyNameCaseInsensitive = false,
|
||||||
new DecimalConverter(),
|
Converters =
|
||||||
new IntConverter(),
|
{
|
||||||
new LongConverter(),
|
new DateTimeConverter(),
|
||||||
new NullableEnumConverterFactory(typeResolver)
|
new BoolConverter(),
|
||||||
},
|
new DecimalConverter(),
|
||||||
TypeInfoResolver = typeResolver,
|
new IntConverter(),
|
||||||
};
|
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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,57 +1,60 @@
|
|||||||
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()
|
|
||||||
{
|
{
|
||||||
public override T? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
internal class SharedQuantityConverter : SharedQuantityReferenceConverter<SharedQuantity> { }
|
||||||
|
internal class SharedOrderQuantityConverter : SharedQuantityReferenceConverter<SharedOrderQuantity> { }
|
||||||
|
|
||||||
|
internal class SharedQuantityReferenceConverter<T> : JsonConverter<T> where T: SharedQuantityReference, new()
|
||||||
{
|
{
|
||||||
if (reader.TokenType != JsonTokenType.StartArray)
|
public override T? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||||
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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,43 +1,46 @@
|
|||||||
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>
|
|
||||||
{
|
{
|
||||||
public override SharedSymbol? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
internal class SharedSymbolConverter : JsonConverter<SharedSymbol>
|
||||||
{
|
{
|
||||||
if (reader.TokenType != JsonTokenType.StartArray)
|
public override SharedSymbol? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||||
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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,376 +1,374 @@
|
|||||||
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;
|
||||||
#if NET5_0_OR_GREATER
|
using System.Collections.Generic;
|
||||||
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>
|
||||||
/// The JsonDocument loaded
|
/// System.Text.Json message accessor
|
||||||
/// </summary>
|
/// </summary>
|
||||||
protected JsonDocument? _document;
|
public abstract class SystemTextJsonMessageAccessor : IMessageAccessor
|
||||||
|
|
||||||
private readonly JsonSerializerOptions? _customSerializerOptions;
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public bool IsValid { get; set; }
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public abstract bool OriginalDataAvailable { get; }
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public object? Underlying => throw new NotImplementedException();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// ctor
|
|
||||||
/// </summary>
|
|
||||||
public SystemTextJsonMessageAccessor(JsonSerializerOptions options)
|
|
||||||
{
|
{
|
||||||
_customSerializerOptions = options;
|
/// <summary>
|
||||||
}
|
/// The JsonDocument loaded
|
||||||
|
/// </summary>
|
||||||
|
protected JsonDocument? _document;
|
||||||
|
|
||||||
/// <inheritdoc />
|
private readonly JsonSerializerOptions? _customSerializerOptions;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public bool IsValid { get; set; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public abstract bool OriginalDataAvailable { get; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public object? Underlying => throw new NotImplementedException();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// ctor
|
||||||
|
/// </summary>
|
||||||
|
public SystemTextJsonMessageAccessor(JsonSerializerOptions options)
|
||||||
|
{
|
||||||
|
_customSerializerOptions = options;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
#if NET5_0_OR_GREATER
|
#if NET5_0_OR_GREATER
|
||||||
[UnconditionalSuppressMessage("AssemblyLoadTrimming", "IL2026:RequiresUnreferencedCode", Justification = "JsonSerializerOptions provided here has TypeInfoResolver set")]
|
[UnconditionalSuppressMessage("AssemblyLoadTrimming", "IL2026:RequiresUnreferencedCode", Justification = "JsonSerializerOptions provided here has TypeInfoResolver set")]
|
||||||
[UnconditionalSuppressMessage("AssemblyLoadTrimming", "IL3050:RequiresUnreferencedCode", Justification = "JsonSerializerOptions provided here has TypeInfoResolver set")]
|
[UnconditionalSuppressMessage("AssemblyLoadTrimming", "IL3050:RequiresUnreferencedCode", Justification = "JsonSerializerOptions provided here has TypeInfoResolver set")]
|
||||||
#endif
|
#endif
|
||||||
public CallResult<object> Deserialize(Type type, MessagePath? path = null)
|
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);
|
if (!IsValid)
|
||||||
return new CallResult<object>(result!);
|
return new CallResult<object>(GetOriginalString());
|
||||||
}
|
|
||||||
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 (_document == null)
|
||||||
#if NET5_0_OR_GREATER
|
throw new InvalidOperationException("No json document loaded");
|
||||||
[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
|
try
|
||||||
{
|
{
|
||||||
return value.Value.Deserialize<T>(_customSerializerOptions);
|
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));
|
||||||
}
|
}
|
||||||
catch { }
|
|
||||||
|
|
||||||
return default;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof(T) == typeof(string))
|
/// <inheritdoc />
|
||||||
{
|
|
||||||
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
|
#if NET5_0_OR_GREATER
|
||||||
[UnconditionalSuppressMessage("AssemblyLoadTrimming", "IL2026:RequiresUnreferencedCode", Justification = "JsonSerializerOptions provided here has TypeInfoResolver set")]
|
[UnconditionalSuppressMessage("AssemblyLoadTrimming", "IL2026:RequiresUnreferencedCode", Justification = "JsonSerializerOptions provided here has TypeInfoResolver set")]
|
||||||
[UnconditionalSuppressMessage("AssemblyLoadTrimming", "IL3050:RequiresUnreferencedCode", Justification = "JsonSerializerOptions provided here has TypeInfoResolver set")]
|
[UnconditionalSuppressMessage("AssemblyLoadTrimming", "IL3050:RequiresUnreferencedCode", Justification = "JsonSerializerOptions provided here has TypeInfoResolver set")]
|
||||||
#endif
|
#endif
|
||||||
public T?[]? GetValues<T>(MessagePath path)
|
public CallResult<T> Deserialize<T>(MessagePath? path = null)
|
||||||
{
|
{
|
||||||
if (!IsValid)
|
if (_document == null)
|
||||||
throw new InvalidOperationException("Can't access json data on non-json message");
|
throw new InvalidOperationException("No json document loaded");
|
||||||
|
|
||||||
var value = GetPathNode(path);
|
try
|
||||||
if (value == null)
|
{
|
||||||
return default;
|
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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (value.Value.ValueKind != JsonValueKind.Array)
|
/// <inheritdoc />
|
||||||
return default;
|
public NodeType? GetNodeType()
|
||||||
|
{
|
||||||
|
if (!IsValid)
|
||||||
|
throw new InvalidOperationException("Can't access json data on non-json message");
|
||||||
|
|
||||||
return value.Value.Deserialize<T[]>(_customSerializerOptions)!;
|
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();
|
||||||
}
|
}
|
||||||
|
|
||||||
private JsonElement? GetPathNode(MessagePath path)
|
/// <summary>
|
||||||
|
/// System.Text.Json stream message accessor
|
||||||
|
/// </summary>
|
||||||
|
public class SystemTextJsonStreamMessageAccessor : SystemTextJsonMessageAccessor, IStreamMessageAccessor
|
||||||
{
|
{
|
||||||
if (!IsValid)
|
private Stream? _stream;
|
||||||
throw new InvalidOperationException("Can't access json data on non-json message");
|
|
||||||
|
|
||||||
if (_document == null)
|
/// <inheritdoc />
|
||||||
throw new InvalidOperationException("No json document loaded");
|
public override bool OriginalDataAvailable => _stream?.CanSeek == true;
|
||||||
|
|
||||||
JsonElement? currentToken = _document.RootElement;
|
/// <summary>
|
||||||
foreach (var node in path)
|
/// ctor
|
||||||
|
/// </summary>
|
||||||
|
public SystemTextJsonStreamMessageAccessor(JsonSerializerOptions options): base(options)
|
||||||
{
|
{
|
||||||
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];
|
/// <inheritdoc />
|
||||||
|
public async Task<CallResult> Read(Stream stream, bool bufferStream)
|
||||||
|
{
|
||||||
|
if (bufferStream && stream is not MemoryStream)
|
||||||
|
{
|
||||||
|
// We need to be buffer the stream, and it's not currently a seekable stream, so copy it to a new memory stream
|
||||||
|
_stream = new MemoryStream();
|
||||||
|
stream.CopyTo(_stream);
|
||||||
|
_stream.Position = 0;
|
||||||
}
|
}
|
||||||
else if (node.Type == 1)
|
else if (bufferStream)
|
||||||
{
|
{
|
||||||
// String value
|
// We need to buffer the stream, and the current stream is seekable, store as is
|
||||||
if (currentToken!.Value.ValueKind != JsonValueKind.Object)
|
_stream = stream;
|
||||||
return null;
|
|
||||||
|
|
||||||
if (!currentToken.Value.TryGetProperty(node.Property!, out var token))
|
|
||||||
return null;
|
|
||||||
currentToken = token;
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Property name
|
// We don't need to buffer the stream, so don't bother keeping the reference
|
||||||
if (currentToken!.Value.ValueKind != JsonValueKind.Object)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currentToken == null)
|
try
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return currentToken;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public abstract string GetOriginalString();
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public abstract void Clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// System.Text.Json stream message accessor
|
|
||||||
/// </summary>
|
|
||||||
#pragma warning disable CA1001 // Types that own disposable fields should be disposable
|
|
||||||
public class SystemTextJsonStreamMessageAccessor : SystemTextJsonMessageAccessor, IStreamMessageAccessor
|
|
||||||
#pragma warning restore CA1001 // Types that own disposable fields should be disposable
|
|
||||||
{
|
|
||||||
private Stream? _stream;
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public override bool OriginalDataAvailable => _stream?.CanSeek == true;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// ctor
|
|
||||||
/// </summary>
|
|
||||||
public SystemTextJsonStreamMessageAccessor(JsonSerializerOptions options): base(options)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public async Task<CallResult> Read(Stream stream, bool bufferStream)
|
|
||||||
{
|
|
||||||
if (bufferStream && stream is not MemoryStream)
|
|
||||||
{
|
|
||||||
// We need to be buffer the stream, and it's not currently a seekable stream, so copy it to a new memory stream
|
|
||||||
_stream = new MemoryStream();
|
|
||||||
stream.CopyTo(_stream);
|
|
||||||
_stream.Position = 0;
|
|
||||||
}
|
|
||||||
else if (bufferStream)
|
|
||||||
{
|
|
||||||
// We need to buffer the stream, and the current stream is seekable, store as is
|
|
||||||
_stream = stream;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// We don't need to buffer the stream, so don't bother keeping the reference
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
_document = await JsonDocument.ParseAsync(_stream ?? stream).ConfigureAwait(false);
|
|
||||||
IsValid = true;
|
|
||||||
return CallResult.SuccessResult;
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
// Not a json message
|
|
||||||
IsValid = false;
|
|
||||||
return new CallResult(new DeserializeError($"Json deserialization failed: {ex.Message}", ex));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public override string GetOriginalString()
|
|
||||||
{
|
|
||||||
if (_stream is null)
|
|
||||||
throw new NullReferenceException("Stream not initialized");
|
|
||||||
|
|
||||||
_stream.Position = 0;
|
|
||||||
using var textReader = new StreamReader(_stream, Encoding.UTF8, false, 1024, true);
|
|
||||||
return textReader.ReadToEnd();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public override void Clear()
|
|
||||||
{
|
|
||||||
_stream?.Dispose();
|
|
||||||
_stream = null;
|
|
||||||
_document?.Dispose();
|
|
||||||
_document = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <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
|
_document = await JsonDocument.ParseAsync(_stream ?? stream).ConfigureAwait(false);
|
||||||
IsValid = false;
|
IsValid = true;
|
||||||
return new CallResult(new DeserializeError("Not a json value"));
|
return CallResult.SuccessResult;
|
||||||
}
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
// Not a json message
|
||||||
|
IsValid = false;
|
||||||
|
return new CallResult(new DeserializeError($"Json deserialization failed: {ex.Message}", ex));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
_document = JsonDocument.Parse(data);
|
/// <inheritdoc />
|
||||||
IsValid = true;
|
public override string GetOriginalString()
|
||||||
return CallResult.SuccessResult;
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
{
|
||||||
// Not a json message
|
if (_stream is null)
|
||||||
IsValid = false;
|
throw new NullReferenceException("Stream not initialized");
|
||||||
return new CallResult(new DeserializeError($"Json deserialization failed: {ex.Message}", ex));
|
|
||||||
|
_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;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <summary>
|
||||||
public override string GetOriginalString() =>
|
/// System.Text.Json byte message accessor
|
||||||
// NetStandard 2.0 doesn't support GetString from a ReadonlySpan<byte>, so use ToArray there instead
|
/// </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
|
#if NETSTANDARD2_0
|
||||||
Encoding.UTF8.GetString(_bytes.ToArray());
|
Encoding.UTF8.GetString(_bytes.ToArray());
|
||||||
#else
|
#else
|
||||||
Encoding.UTF8.GetString(_bytes.Span);
|
Encoding.UTF8.GetString(_bytes.Span);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override bool OriginalDataAvailable => true;
|
public override bool OriginalDataAvailable => true;
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override void Clear()
|
public override void Clear()
|
||||||
{
|
{
|
||||||
_bytes = null;
|
_bytes = null;
|
||||||
_document?.Dispose();
|
_document?.Dispose();
|
||||||
_document = null;
|
_document = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,28 +1,29 @@
|
|||||||
using CryptoExchange.Net.Interfaces;
|
using CryptoExchange.Net.Interfaces;
|
||||||
#if NET5_0_OR_GREATER
|
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
#endif
|
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
using System.Text.Json.Serialization.Metadata;
|
||||||
|
|
||||||
namespace CryptoExchange.Net.Converters.SystemTextJson;
|
namespace CryptoExchange.Net.Converters.SystemTextJson
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public class SystemTextJsonMessageSerializer : IStringMessageSerializer
|
|
||||||
{
|
{
|
||||||
private readonly JsonSerializerOptions _options;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// ctor
|
|
||||||
/// </summary>
|
|
||||||
public SystemTextJsonMessageSerializer(JsonSerializerOptions options)
|
|
||||||
{
|
|
||||||
_options = options;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <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")]
|
[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")]
|
[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);
|
public string Serialize<T>(T message) => JsonSerializer.Serialize(message, _options);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,9 +6,9 @@
|
|||||||
<PackageId>CryptoExchange.Net</PackageId>
|
<PackageId>CryptoExchange.Net</PackageId>
|
||||||
<Authors>JKorf</Authors>
|
<Authors>JKorf</Authors>
|
||||||
<Description>CryptoExchange.Net is a base library which is used to implement different cryptocurrency (exchange) API's. It provides a standardized way of implementing different API's, which results in a very similar experience for users of the API implementations.</Description>
|
<Description>CryptoExchange.Net is a base library which is used to implement different cryptocurrency (exchange) API's. It provides a standardized way of implementing different API's, which results in a very similar experience for users of the API implementations.</Description>
|
||||||
<PackageVersion>9.6.0</PackageVersion>
|
<PackageVersion>9.9.0</PackageVersion>
|
||||||
<AssemblyVersion>9.6.0</AssemblyVersion>
|
<AssemblyVersion>9.9.0</AssemblyVersion>
|
||||||
<FileVersion>9.6.0</FileVersion>
|
<FileVersion>9.9.0</FileVersion>
|
||||||
<PackageRequireLicenseAcceptance>false</PackageRequireLicenseAcceptance>
|
<PackageRequireLicenseAcceptance>false</PackageRequireLicenseAcceptance>
|
||||||
<PackageTags>OKX;OKX.Net;Mexc;Mexc.Net;Kucoin;Kucoin.Net;Kraken;Kraken.Net;Huobi;Huobi.Net;CoinEx;CoinEx.Net;Bybit;Bybit.Net;Bitget;Bitget.Net;Bitfinex;Bitfinex.Net;Binance;Binance.Net;CryptoCurrency;CryptoCurrency Exchange;CryptoExchange.Net</PackageTags>
|
<PackageTags>OKX;OKX.Net;Mexc;Mexc.Net;Kucoin;Kucoin.Net;Kraken;Kraken.Net;Huobi;Huobi.Net;CoinEx;CoinEx.Net;Bybit;Bybit.Net;Bitget;Bitget.Net;Bitfinex;Bitfinex.Net;Binance;Binance.Net;CryptoCurrency;CryptoCurrency Exchange;CryptoExchange.Net</PackageTags>
|
||||||
<RepositoryType>git</RepositoryType>
|
<RepositoryType>git</RepositoryType>
|
||||||
@@ -24,7 +24,6 @@
|
|||||||
<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>
|
||||||
@@ -41,12 +40,6 @@
|
|||||||
<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>
|
||||||
@@ -65,7 +58,4 @@
|
|||||||
<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>
|
||||||
@@ -1,390 +1,387 @@
|
|||||||
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
|
|
||||||
{
|
{
|
||||||
private const string _allowedRandomChars = "ABCDEFGHIJKLMONOPQRSTUVWXYZabcdefghijklmonopqrstuvwxyz0123456789";
|
|
||||||
private const string _allowedRandomHexChars = "0123456789ABCDEF";
|
|
||||||
|
|
||||||
private static readonly Dictionary<int, string> _monthSymbols = new Dictionary<int, string>()
|
|
||||||
{
|
|
||||||
{ 1, "F" },
|
|
||||||
{ 2, "G" },
|
|
||||||
{ 3, "H" },
|
|
||||||
{ 4, "J" },
|
|
||||||
{ 5, "K" },
|
|
||||||
{ 6, "M" },
|
|
||||||
{ 7, "N" },
|
|
||||||
{ 8, "Q" },
|
|
||||||
{ 9, "U" },
|
|
||||||
{ 10, "V" },
|
|
||||||
{ 11, "X" },
|
|
||||||
{ 12, "Z" },
|
|
||||||
};
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The last used id, use NextId() to get the next id and up this
|
/// General helpers functions
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private static int _lastId;
|
public static class ExchangeHelpers
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Clamp a value between a min and max
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="min"></param>
|
|
||||||
/// <param name="max"></param>
|
|
||||||
/// <param name="value"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public static decimal ClampValue(decimal min, decimal max, decimal value)
|
|
||||||
{
|
{
|
||||||
value = Math.Min(max, value);
|
private const string _allowedRandomChars = "ABCDEFGHIJKLMONOPQRSTUVWXYZabcdefghijklmonopqrstuvwxyz0123456789";
|
||||||
value = Math.Max(min, value);
|
private const string _allowedRandomHexChars = "0123456789ABCDEF";
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
private static readonly Dictionary<int, string> _monthSymbols = new Dictionary<int, string>()
|
||||||
/// Adjust a value to be between the min and max parameters and rounded to the closest step.
|
{
|
||||||
/// </summary>
|
{ 1, "F" },
|
||||||
/// <param name="min">The min value</param>
|
{ 2, "G" },
|
||||||
/// <param name="max">The max value</param>
|
{ 3, "H" },
|
||||||
/// <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>
|
{ 4, "J" },
|
||||||
/// <param name="roundingType">How to round</param>
|
{ 5, "K" },
|
||||||
/// <param name="value">The input value</param>
|
{ 6, "M" },
|
||||||
/// <returns></returns>
|
{ 7, "N" },
|
||||||
public static decimal AdjustValueStep(decimal min, decimal max, decimal? step, RoundingType roundingType, decimal value)
|
{ 8, "Q" },
|
||||||
{
|
{ 9, "U" },
|
||||||
if(step == 0)
|
{ 10, "V" },
|
||||||
throw new ArgumentException($"0 not allowed for parameter {nameof(step)}, pass in null to ignore the step size", nameof(step));
|
{ 11, "X" },
|
||||||
|
{ 12, "Z" },
|
||||||
|
};
|
||||||
|
|
||||||
value = Math.Min(max, value);
|
/// <summary>
|
||||||
value = Math.Max(min, value);
|
/// The last used id, use NextId() to get the next id and up this
|
||||||
if (step == null)
|
/// </summary>
|
||||||
|
private static int _lastId;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Clamp a value between a min and max
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="min"></param>
|
||||||
|
/// <param name="max"></param>
|
||||||
|
/// <param name="value"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static decimal ClampValue(decimal min, decimal max, decimal value)
|
||||||
|
{
|
||||||
|
value = Math.Min(max, value);
|
||||||
|
value = Math.Max(min, value);
|
||||||
return value;
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
var offset = value % step.Value;
|
/// <summary>
|
||||||
if(roundingType == RoundingType.Down)
|
/// Adjust a value to be between the min and max parameters and rounded to the closest step.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="min">The min value</param>
|
||||||
|
/// <param name="max">The max value</param>
|
||||||
|
/// <param name="step">The step size the value should be floored to. For example, value 2.548 with a step size of 0.01 will output 2.54</param>
|
||||||
|
/// <param name="roundingType">How to round</param>
|
||||||
|
/// <param name="value">The input value</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static decimal AdjustValueStep(decimal min, decimal max, decimal? step, RoundingType roundingType, decimal value)
|
||||||
{
|
{
|
||||||
value -= offset;
|
if(step == 0)
|
||||||
}
|
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);
|
||||||
if (offset != 0)
|
value = Math.Max(min, value);
|
||||||
value += (step.Value - offset);
|
if (step == null)
|
||||||
}
|
return value;
|
||||||
else
|
|
||||||
{
|
var offset = value % step.Value;
|
||||||
if (offset < step / 2)
|
if(roundingType == RoundingType.Down)
|
||||||
|
{
|
||||||
value -= offset;
|
value -= offset;
|
||||||
else value += (step.Value - offset);
|
}
|
||||||
}
|
else if(roundingType == RoundingType.Up)
|
||||||
|
|
||||||
value = RoundDown(value, 8);
|
|
||||||
|
|
||||||
return value.Normalize();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Adjust a value to be between the min and max parameters and rounded to the closest precision.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="min">The min value</param>
|
|
||||||
/// <param name="max">The max value</param>
|
|
||||||
/// <param name="precision">The precision the value should be rounded to. For example, value 2.554215 with a precision of 5 will output 2.5542</param>
|
|
||||||
/// <param name="roundingType">How to round</param>
|
|
||||||
/// <param name="value">The input value</param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public static decimal AdjustValuePrecision(decimal min, decimal max, int? precision, RoundingType roundingType, decimal value)
|
|
||||||
{
|
|
||||||
value = Math.Min(max, value);
|
|
||||||
value = Math.Max(min, value);
|
|
||||||
if (precision == null)
|
|
||||||
return value;
|
|
||||||
|
|
||||||
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)
|
if (offset != 0)
|
||||||
|
value += (step.Value - offset);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (offset < step / 2)
|
||||||
value -= offset;
|
value -= offset;
|
||||||
else value += (valueStep.Value - offset);
|
else value += (step.Value - offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return value.Normalize();
|
||||||
}
|
}
|
||||||
if (decimals.HasValue)
|
|
||||||
value = Math.Round(value, decimals.Value);
|
|
||||||
|
|
||||||
return value;
|
/// <summary>
|
||||||
}
|
/// Adjust a value to be between the min and max parameters and rounded to the closest precision.
|
||||||
|
/// </summary>
|
||||||
/// <summary>
|
/// <param name="min">The min value</param>
|
||||||
/// Round a value to have the provided total number of digits. For example, value 253.12332 with 5 digits would be 253.12
|
/// <param name="max">The max value</param>
|
||||||
/// </summary>
|
/// <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="value">The value to round</param>
|
/// <param name="roundingType">How to round</param>
|
||||||
/// <param name="digits">The total amount of digits (NOT decimal places) to round to</param>
|
/// <param name="value">The input value</param>
|
||||||
/// <param name="roundingType">How to round</param>
|
/// <returns></returns>
|
||||||
/// <returns></returns>
|
public static decimal AdjustValuePrecision(decimal min, decimal max, int? precision, RoundingType roundingType, decimal value)
|
||||||
public static decimal RoundToSignificantDigits(decimal value, int digits, RoundingType roundingType)
|
|
||||||
{
|
|
||||||
var val = (double)value;
|
|
||||||
if (value == 0)
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
double scale = Math.Pow(10, Math.Floor(Math.Log10(Math.Abs(val))) + 1);
|
|
||||||
if(roundingType == RoundingType.Closest)
|
|
||||||
return (decimal)(scale * Math.Round(val / scale, digits));
|
|
||||||
else
|
|
||||||
return (decimal)(scale * (double)RoundDown((decimal)(val / scale), digits));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Rounds a value down
|
|
||||||
/// </summary>
|
|
||||||
public static decimal RoundDown(decimal i, double decimalPlaces)
|
|
||||||
{
|
|
||||||
var power = Convert.ToDecimal(Math.Pow(10, decimalPlaces));
|
|
||||||
return Math.Floor(i * power) / power;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Rounds a value up
|
|
||||||
/// </summary>
|
|
||||||
public static decimal RoundUp(decimal i, double decimalPlaces)
|
|
||||||
{
|
|
||||||
var power = Convert.ToDecimal(Math.Pow(10, decimalPlaces));
|
|
||||||
return Math.Ceiling(i * power) / power;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Strips any trailing zero's of a decimal value, useful when converting the value to string.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="value"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public static decimal Normalize(this decimal value)
|
|
||||||
{
|
|
||||||
return value / 1.000000000000000000000000000000000m;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Generate a new unique id. The id is statically stored so it is guaranteed to be unique
|
|
||||||
/// </summary>
|
|
||||||
/// <returns></returns>
|
|
||||||
public static int NextId() => Interlocked.Increment(ref _lastId);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Return the last unique id that was generated
|
|
||||||
/// </summary>
|
|
||||||
/// <returns></returns>
|
|
||||||
public static int LastId() => _lastId;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Generate a random string of specified length
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="length">Length of the random string</param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public static string RandomString(int length)
|
|
||||||
{
|
|
||||||
var randomChars = new char[length];
|
|
||||||
|
|
||||||
#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER
|
|
||||||
for (int i = 0; i < length; i++)
|
|
||||||
randomChars[i] = _allowedRandomChars[RandomNumberGenerator.GetInt32(0, _allowedRandomChars.Length)];
|
|
||||||
#else
|
|
||||||
var random = new Random();
|
|
||||||
for (int i = 0; i < length; i++)
|
|
||||||
randomChars[i] = _allowedRandomChars[random.Next(0, _allowedRandomChars.Length)];
|
|
||||||
#endif
|
|
||||||
|
|
||||||
return new string(randomChars);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Generate a random string of specified length
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="length">Length of the random string</param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public static string RandomHexString(int length)
|
|
||||||
{
|
|
||||||
#if NET9_0_OR_GREATER
|
|
||||||
return "0x" + RandomNumberGenerator.GetHexString(length * 2);
|
|
||||||
#else
|
|
||||||
var randomChars = new char[length * 2];
|
|
||||||
var random = new Random();
|
|
||||||
for (int i = 0; i < length * 2; i++)
|
|
||||||
randomChars[i] = _allowedRandomHexChars[random.Next(0, _allowedRandomHexChars.Length)];
|
|
||||||
return "0x" + new string(randomChars);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Generate a long value
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="maxLength">Max character length</param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public static long RandomLong(int maxLength)
|
|
||||||
{
|
|
||||||
#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER
|
|
||||||
var value = RandomNumberGenerator.GetInt32(0, int.MaxValue);
|
|
||||||
#else
|
|
||||||
var random = new Random();
|
|
||||||
var value = random.Next(0, int.MaxValue);
|
|
||||||
#endif
|
|
||||||
var val = value.ToString();
|
|
||||||
if (val.Length > maxLength)
|
|
||||||
return int.Parse(val.Substring(0, maxLength));
|
|
||||||
else
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Generate a random string of specified length
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="source">The initial string</param>
|
|
||||||
/// <param name="totalLength">Total length of the resulting string</param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public static string AppendRandomString(string source, int totalLength)
|
|
||||||
{
|
|
||||||
if (totalLength < source.Length)
|
|
||||||
throw new ArgumentException("Total length smaller than source string length", nameof(totalLength));
|
|
||||||
|
|
||||||
if (totalLength == source.Length)
|
|
||||||
return source;
|
|
||||||
|
|
||||||
return source + RandomString(totalLength - source.Length);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Get the month representation for futures symbol based on the delivery month
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="time">Delivery time</param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public static string GetDeliveryMonthSymbol(DateTime time) => _monthSymbols[time.Month];
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Execute multiple requests to retrieve multiple pages of the result set
|
|
||||||
/// </summary>
|
|
||||||
/// <typeparam name="TResult">Type of the client</typeparam>
|
|
||||||
/// <typeparam name="TRequest">Type of the request</typeparam>
|
|
||||||
/// <param name="paginatedFunc">The func to execute with each request</param>
|
|
||||||
/// <param name="request">The request parameters</param>
|
|
||||||
/// <param name="ct">Cancellation token</param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public static async IAsyncEnumerable<ExchangeWebResult<TResult[]>> ExecutePages<TResult, TRequest>(Func<TRequest, INextPageToken?, CancellationToken, Task<ExchangeWebResult<TResult[]>>> paginatedFunc, TRequest request, [EnumeratorCancellation]CancellationToken ct = default)
|
|
||||||
{
|
|
||||||
var result = new List<TResult>();
|
|
||||||
ExchangeWebResult<TResult[]> batch;
|
|
||||||
INextPageToken? nextPageToken = null;
|
|
||||||
while (true)
|
|
||||||
{
|
{
|
||||||
batch = await paginatedFunc(request, nextPageToken, ct).ConfigureAwait(false);
|
value = Math.Min(max, value);
|
||||||
yield return batch;
|
value = Math.Max(min, value);
|
||||||
if (!batch || ct.IsCancellationRequested)
|
if (precision == null)
|
||||||
break;
|
return value;
|
||||||
|
|
||||||
result.AddRange(batch.Data);
|
return RoundToSignificantDigits(value, precision.Value, roundingType);
|
||||||
nextPageToken = batch.NextPageToken;
|
|
||||||
if (nextPageToken == null)
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Apply the rules (price and quantity step size and decimals precision, min/max quantity) from the symbol to the quantity and price
|
/// Apply the provided rules to the value
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="symbol">The symbol as retrieved from the exchange</param>
|
/// <param name="value">Value to be adjusted</param>
|
||||||
/// <param name="quantity">Quantity to trade</param>
|
/// <param name="decimals">Max decimal places</param>
|
||||||
/// <param name="price">Price to trade at</param>
|
/// <param name="valueStep">The value step for increase/decrease value</param>
|
||||||
/// <param name="adjustedQuantity">Quantity adjusted to match all trading rules</param>
|
/// <returns></returns>
|
||||||
/// <param name="adjustedPrice">Price adjusted to match all trading rules</param>
|
public static decimal ApplyRules(
|
||||||
public static void ApplySymbolRules(SharedSpotSymbol symbol, decimal quantity, decimal? price, out decimal adjustedQuantity, out decimal? adjustedPrice)
|
decimal value,
|
||||||
{
|
int? decimals = null,
|
||||||
adjustedPrice = price;
|
decimal? valueStep = null)
|
||||||
adjustedQuantity = quantity;
|
|
||||||
var minNotionalAdjust = false;
|
|
||||||
|
|
||||||
if (price != null)
|
|
||||||
{
|
{
|
||||||
adjustedPrice = AdjustValueStep(0, decimal.MaxValue, symbol.PriceStep, RoundingType.Down, price.Value);
|
if (valueStep.HasValue)
|
||||||
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;
|
var offset = value % valueStep.Value;
|
||||||
minNotionalAdjust = true;
|
if (offset != 0)
|
||||||
|
{
|
||||||
|
if (offset < valueStep.Value / 2)
|
||||||
|
value -= offset;
|
||||||
|
else value += (valueStep.Value - offset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (decimals.HasValue)
|
||||||
|
value = Math.Round(value, decimals.Value);
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Round a value to have the provided total number of digits. For example, value 253.12332 with 5 digits would be 253.12
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value">The value to round</param>
|
||||||
|
/// <param name="digits">The total amount of digits (NOT decimal places) to round to</param>
|
||||||
|
/// <param name="roundingType">How to round</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static decimal RoundToSignificantDigits(decimal value, int digits, RoundingType roundingType)
|
||||||
|
{
|
||||||
|
var val = (double)value;
|
||||||
|
if (value == 0)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
double scale = Math.Pow(10, Math.Floor(Math.Log10(Math.Abs(val))) + 1);
|
||||||
|
if(roundingType == RoundingType.Closest)
|
||||||
|
return (decimal)(scale * Math.Round(val / scale, digits));
|
||||||
|
else
|
||||||
|
return (decimal)(scale * (double)RoundDown((decimal)(val / scale), digits));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Rounds a value down
|
||||||
|
/// </summary>
|
||||||
|
public static decimal RoundDown(decimal i, double decimalPlaces)
|
||||||
|
{
|
||||||
|
var power = Convert.ToDecimal(Math.Pow(10, decimalPlaces));
|
||||||
|
return Math.Floor(i * power) / power;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Rounds a value up
|
||||||
|
/// </summary>
|
||||||
|
public static decimal RoundUp(decimal i, double decimalPlaces)
|
||||||
|
{
|
||||||
|
var power = Convert.ToDecimal(Math.Pow(10, decimalPlaces));
|
||||||
|
return Math.Ceiling(i * power) / power;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Strips any trailing zero's of a decimal value, useful when converting the value to string.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static decimal Normalize(this decimal value)
|
||||||
|
{
|
||||||
|
return value / 1.000000000000000000000000000000000m;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Generate a new unique id. The id is statically stored so it is guaranteed to be unique
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static int NextId() => Interlocked.Increment(ref _lastId);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Return the last unique id that was generated
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static int LastId() => _lastId;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Generate a random string of specified length
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="length">Length of the random string</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static string RandomString(int length)
|
||||||
|
{
|
||||||
|
var randomChars = new char[length];
|
||||||
|
|
||||||
|
#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER
|
||||||
|
for (int i = 0; i < length; i++)
|
||||||
|
randomChars[i] = _allowedRandomChars[RandomNumberGenerator.GetInt32(0, _allowedRandomChars.Length)];
|
||||||
|
#else
|
||||||
|
var random = new Random();
|
||||||
|
for (int i = 0; i < length; i++)
|
||||||
|
randomChars[i] = _allowedRandomChars[random.Next(0, _allowedRandomChars.Length)];
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return new string(randomChars);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Generate a random string of specified length
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="length">Length of the random string</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static string RandomHexString(int length)
|
||||||
|
{
|
||||||
|
#if NET9_0_OR_GREATER
|
||||||
|
return "0x" + RandomNumberGenerator.GetHexString(length * 2);
|
||||||
|
#else
|
||||||
|
var randomChars = new char[length * 2];
|
||||||
|
var random = new Random();
|
||||||
|
for (int i = 0; i < length * 2; i++)
|
||||||
|
randomChars[i] = _allowedRandomHexChars[random.Next(0, _allowedRandomHexChars.Length)];
|
||||||
|
return "0x" + new string(randomChars);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Generate a long value
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="maxLength">Max character length</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static long RandomLong(int maxLength)
|
||||||
|
{
|
||||||
|
#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER
|
||||||
|
var value = RandomNumberGenerator.GetInt32(0, int.MaxValue);
|
||||||
|
#else
|
||||||
|
var random = new Random();
|
||||||
|
var value = random.Next(0, int.MaxValue);
|
||||||
|
#endif
|
||||||
|
var val = value.ToString();
|
||||||
|
if (val.Length > maxLength)
|
||||||
|
return int.Parse(val.Substring(0, maxLength));
|
||||||
|
else
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Generate a random string of specified length
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="source">The initial string</param>
|
||||||
|
/// <param name="totalLength">Total length of the resulting string</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static string AppendRandomString(string source, int totalLength)
|
||||||
|
{
|
||||||
|
if (totalLength < source.Length)
|
||||||
|
throw new ArgumentException("Total length smaller than source string length", nameof(totalLength));
|
||||||
|
|
||||||
|
if (totalLength == source.Length)
|
||||||
|
return source;
|
||||||
|
|
||||||
|
return source + RandomString(totalLength - source.Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get the month representation for futures symbol based on the delivery month
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="time">Delivery time</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static string GetDeliveryMonthSymbol(DateTime time) => _monthSymbols[time.Month];
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Execute multiple requests to retrieve multiple pages of the result set
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
adjustedQuantity = AdjustValueStep(symbol.MinTradeQuantity ?? 0, symbol.MaxTradeQuantity ?? decimal.MaxValue, symbol.QuantityStep, minNotionalAdjust ? RoundingType.Up : RoundingType.Down, adjustedQuantity);
|
/// <summary>
|
||||||
adjustedQuantity = symbol.QuantityDecimals.HasValue ? (minNotionalAdjust ? RoundUp(adjustedQuantity, symbol.QuantityDecimals.Value) : RoundDown(adjustedQuantity, symbol.QuantityDecimals.Value)) : adjustedQuantity;
|
/// 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>
|
||||||
/// <summary>
|
/// <param name="price">Price to trade at</param>
|
||||||
/// Parse a decimal value from a string
|
/// <param name="adjustedQuantity">Quantity adjusted to match all trading rules</param>
|
||||||
/// </summary>
|
/// <param name="adjustedPrice">Price adjusted to match all trading rules</param>
|
||||||
public static decimal? ParseDecimal(string? value)
|
public static void ApplySymbolRules(SharedSpotSymbol symbol, decimal quantity, decimal? price, out decimal adjustedQuantity, out decimal? adjustedPrice)
|
||||||
{
|
|
||||||
// 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))
|
|
||||||
{
|
{
|
||||||
|
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;
|
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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,68 +1,70 @@
|
|||||||
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>
|
||||||
/// Update the cached symbol data for an exchange
|
/// Cache for symbol parsing
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="topicId">Id for the provided data</param>
|
public static class ExchangeSymbolCache
|
||||||
/// <param name="updateData">Symbol data</param>
|
|
||||||
public static void UpdateSymbolInfo(string topicId, SharedSpotSymbol[] updateData)
|
|
||||||
{
|
{
|
||||||
if(!_symbolInfos.TryGetValue(topicId, out var exchangeInfo))
|
private static ConcurrentDictionary<string, ExchangeInfo> _symbolInfos = new ConcurrentDictionary<string, ExchangeInfo>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Update the cached symbol data for an exchange
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="topicId">Id for the provided data</param>
|
||||||
|
/// <param name="updateData">Symbol data</param>
|
||||||
|
public static void UpdateSymbolInfo(string topicId, SharedSpotSymbol[] updateData)
|
||||||
{
|
{
|
||||||
exchangeInfo = new ExchangeInfo(DateTime.UtcNow, updateData.ToDictionary(x => x.Name, x => x.SharedSymbol));
|
if(!_symbolInfos.TryGetValue(topicId, out var exchangeInfo))
|
||||||
_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));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (DateTime.UtcNow - exchangeInfo.UpdateTime < TimeSpan.FromMinutes(60))
|
/// <summary>
|
||||||
return;
|
/// Parse a symbol name to a SharedSymbol
|
||||||
|
/// </summary>
|
||||||
_symbolInfos[topicId] = new ExchangeInfo(DateTime.UtcNow, updateData.ToDictionary(x => x.Name, x => x.SharedSymbol));
|
/// <param name="topicId">Id for the provided data</param>
|
||||||
}
|
/// <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)
|
|
||||||
{
|
{
|
||||||
DeliverTime = symbolInfo.DeliverTime
|
if (symbolName == null)
|
||||||
};
|
return null;
|
||||||
}
|
|
||||||
|
|
||||||
class ExchangeInfo
|
if (!_symbolInfos.TryGetValue(topicId, out var exchangeInfo))
|
||||||
{
|
return null;
|
||||||
public DateTime UpdateTime { get; set; }
|
|
||||||
public Dictionary<string, SharedSymbol> Symbols { get; set; }
|
|
||||||
|
|
||||||
public ExchangeInfo(DateTime updateTime, Dictionary<string, SharedSymbol> symbols)
|
if (!exchangeInfo.Symbols.TryGetValue(symbolName, out var symbolInfo))
|
||||||
|
return null;
|
||||||
|
|
||||||
|
return new SharedSymbol(symbolInfo.TradingMode, symbolInfo.BaseAsset, symbolInfo.QuoteAsset, symbolName)
|
||||||
|
{
|
||||||
|
DeliverTime = symbolInfo.DeliverTime
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
class ExchangeInfo
|
||||||
{
|
{
|
||||||
UpdateTime = updateTime;
|
public DateTime UpdateTime { get; set; }
|
||||||
Symbols = symbols;
|
public Dictionary<string, SharedSymbol> Symbols { get; set; }
|
||||||
|
|
||||||
|
public ExchangeInfo(DateTime updateTime, Dictionary<string, SharedSymbol> symbols)
|
||||||
|
{
|
||||||
|
UpdateTime = updateTime;
|
||||||
|
Symbols = symbols;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,511 +10,515 @@ 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>
|
||||||
/// Add a parameter
|
/// Helper methods
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="parameters"></param>
|
public static class ExtensionMethods
|
||||||
/// <param name="key"></param>
|
|
||||||
/// <param name="value"></param>
|
|
||||||
public static void AddParameter(this Dictionary<string, object> parameters, string key, string value)
|
|
||||||
{
|
{
|
||||||
parameters.Add(key, value);
|
/// <summary>
|
||||||
}
|
/// Add a parameter
|
||||||
|
/// </summary>
|
||||||
/// <summary>
|
/// <param name="parameters"></param>
|
||||||
/// Add a parameter
|
/// <param name="key"></param>
|
||||||
/// </summary>
|
/// <param name="value"></param>
|
||||||
/// <param name="parameters"></param>
|
public static void AddParameter(this Dictionary<string, object> parameters, string key, string value)
|
||||||
/// <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>
|
|
||||||
/// 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))}"))}";
|
/// <summary>
|
||||||
uriString = uriString.TrimEnd('&');
|
/// Add a parameter
|
||||||
return uriString;
|
/// </summary>
|
||||||
}
|
/// <param name="parameters"></param>
|
||||||
|
/// <param name="key"></param>
|
||||||
/// <summary>
|
/// <param name="value"></param>
|
||||||
/// Convert a dictionary to formdata string
|
public static void AddParameter(this Dictionary<string, object> parameters, string key, object value)
|
||||||
/// </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)
|
parameters.Add(key, value);
|
||||||
continue;
|
}
|
||||||
|
|
||||||
if (kvp.Value.GetType().IsArray)
|
/// <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)
|
||||||
{
|
{
|
||||||
var array = (Array)kvp.Value;
|
if (serializationType == ArrayParametersSerialization.Array)
|
||||||
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)}]");
|
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
|
else
|
||||||
{
|
{
|
||||||
foreach (var item in (object[])parameter.Value)
|
var array = (Array)arrayEntry.Value;
|
||||||
{
|
uriString += $"{arrayEntry.Key}=[{string.Join(",", array.OfType<object>().Select(a => string.Format(CultureInfo.InvariantCulture, "{0}", a)))}]&";
|
||||||
if (arraySerialization == ArrayParametersSerialization.Array)
|
|
||||||
{
|
|
||||||
httpValueCollection.Add(parameter.Key + "[]", item.ToString());
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
httpValueCollection.Add(parameter.Key, item.ToString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
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))}"))}";
|
||||||
httpValueCollection.Add(parameter.Key, parameter.Value.ToString());
|
uriString = uriString.TrimEnd('&');
|
||||||
}
|
return uriString;
|
||||||
}
|
}
|
||||||
|
|
||||||
uriBuilder.Query = httpValueCollection.ToString();
|
/// <summary>
|
||||||
return uriBuilder.Uri;
|
/// Convert a dictionary to formdata string
|
||||||
}
|
/// </summary>
|
||||||
|
/// <param name="parameters"></param>
|
||||||
/// <summary>
|
/// <returns></returns>
|
||||||
/// Create a new uri with the provided parameters as query
|
public static string ToFormData(this IDictionary<string, object> parameters)
|
||||||
/// </summary>
|
|
||||||
/// <param name="parameters"></param>
|
|
||||||
/// <param name="baseUri"></param>
|
|
||||||
/// <param name="arraySerialization"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public static Uri SetParameters(this Uri baseUri, IOrderedEnumerable<KeyValuePair<string, object>> parameters, ArrayParametersSerialization arraySerialization)
|
|
||||||
{
|
|
||||||
var uriBuilder = new UriBuilder();
|
|
||||||
uriBuilder.Scheme = baseUri.Scheme;
|
|
||||||
uriBuilder.Host = baseUri.Host;
|
|
||||||
uriBuilder.Port = baseUri.Port;
|
|
||||||
uriBuilder.Path = baseUri.AbsolutePath;
|
|
||||||
var httpValueCollection = HttpUtility.ParseQueryString(string.Empty);
|
|
||||||
foreach (var parameter in parameters)
|
|
||||||
{
|
{
|
||||||
if (parameter.Value.GetType().IsArray)
|
var formData = HttpUtility.ParseQueryString(string.Empty);
|
||||||
|
foreach (var kvp in parameters)
|
||||||
{
|
{
|
||||||
if (arraySerialization == ArrayParametersSerialization.JsonArray)
|
if (kvp.Value is null)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (kvp.Value.GetType().IsArray)
|
||||||
{
|
{
|
||||||
httpValueCollection.Add(parameter.Key, $"[{string.Join(",", (object[])parameter.Value)}]");
|
var array = (Array)kvp.Value;
|
||||||
|
foreach (var value in array)
|
||||||
|
formData.Add(kvp.Key, string.Format(CultureInfo.InvariantCulture, "{0}", value));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
foreach (var item in (object[])parameter.Value)
|
formData.Add(kvp.Key, string.Format(CultureInfo.InvariantCulture, "{0}", kvp.Value));
|
||||||
{
|
|
||||||
if (arraySerialization == ArrayParametersSerialization.Array)
|
|
||||||
{
|
|
||||||
httpValueCollection.Add(parameter.Key + "[]", item.ToString());
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
httpValueCollection.Add(parameter.Key, item.ToString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
|
||||||
|
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))
|
||||||
{
|
{
|
||||||
httpValueCollection.Add(parameter.Key, parameter.Value.ToString());
|
throw new ArgumentException(
|
||||||
|
$"{value} not allowed for parameter {argumentName}, allowed values: {string.Join(", ", allowedValues)}", argumentName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
uriBuilder.Query = httpValueCollection.ToString();
|
/// <summary>
|
||||||
return uriBuilder.Uri;
|
/// 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>
|
/// <summary>
|
||||||
/// Add parameter to URI
|
/// Validates a string is not null or empty
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="uri"></param>
|
/// <param name="value">The value of the string</param>
|
||||||
/// <param name="name"></param>
|
/// <param name="argumentName">Name of the parameter</param>
|
||||||
/// <param name="value"></param>
|
public static void ValidateNotNull(this string value, string argumentName)
|
||||||
/// <returns></returns>
|
{
|
||||||
public static Uri AddQueryParameter(this Uri uri, string name, string value)
|
if (string.IsNullOrEmpty(value))
|
||||||
{
|
throw new ArgumentException($"No value provided for parameter {argumentName}", argumentName);
|
||||||
var httpValueCollection = HttpUtility.ParseQueryString(uri.Query);
|
}
|
||||||
|
|
||||||
httpValueCollection.Remove(name);
|
/// <summary>
|
||||||
httpValueCollection.Add(name, value);
|
/// 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);
|
||||||
|
}
|
||||||
|
|
||||||
var ub = new UriBuilder(uri);
|
/// <summary>
|
||||||
ub.Query = httpValueCollection.ToString();
|
/// 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);
|
||||||
|
}
|
||||||
|
|
||||||
return ub.Uri;
|
/// <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>
|
/// <summary>
|
||||||
/// Decompress using GzipStream
|
/// Format a string to RFC3339/ISO8601 string
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="data"></param>
|
/// <param name="dateTime"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public static ReadOnlyMemory<byte> DecompressGzip(this ReadOnlyMemory<byte> data)
|
public static string ToRfc3339String(this DateTime dateTime)
|
||||||
{
|
{
|
||||||
using var decompressedStream = new MemoryStream();
|
return dateTime.ToString("yyyy-MM-dd'T'HH:mm:ss.fffzzz", DateTimeFormatInfo.InvariantInfo);
|
||||||
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
|
/// Format an exception and inner exception to a readable string
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="input"></param>
|
/// <param name="exception"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public static ReadOnlyMemory<byte> Decompress(this ReadOnlyMemory<byte> input)
|
public static string ToLogString(this Exception? exception)
|
||||||
{
|
{
|
||||||
var output = new MemoryStream();
|
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);
|
||||||
|
|
||||||
using (var compressStream = new MemoryStream(input.ToArray()))
|
indent += 2;
|
||||||
using (var decompressor = new DeflateStream(compressStream, CompressionMode.Decompress))
|
exception = exception.InnerException;
|
||||||
decompressor.CopyTo(output);
|
}
|
||||||
|
|
||||||
output.Position = 0;
|
return message.ToString();
|
||||||
return new ReadOnlyMemory<byte>(output.GetBuffer(), 0, (int)output.Length);
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Whether the trading mode is linear
|
/// Append a base url with provided path
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static bool IsLinear(this TradingMode type) => type == TradingMode.PerpetualLinear || type == TradingMode.DeliveryLinear;
|
/// <param name="url"></param>
|
||||||
|
/// <param name="path"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static string AppendPath(this string url, params string[] path)
|
||||||
|
{
|
||||||
|
if (!url.EndsWith("/"))
|
||||||
|
url += "/";
|
||||||
|
|
||||||
/// <summary>
|
foreach (var item in path)
|
||||||
/// Whether the trading mode is inverse
|
url += item.Trim('/') + "/";
|
||||||
/// </summary>
|
|
||||||
public static bool IsInverse(this TradingMode type) => type == TradingMode.PerpetualInverse || type == TradingMode.DeliveryInverse;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Whether the trading mode is perpetual
|
|
||||||
/// </summary>
|
|
||||||
public static bool IsPerpetual(this TradingMode type) => type == TradingMode.PerpetualInverse || type == TradingMode.PerpetualLinear;
|
|
||||||
|
|
||||||
/// <summary>
|
return url.TrimEnd('/');
|
||||||
/// Whether the trading mode is delivery
|
}
|
||||||
/// </summary>
|
|
||||||
public static bool IsDelivery(this TradingMode type) => type == TradingMode.DeliveryInverse || type == TradingMode.DeliveryLinear;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Register rest client interfaces
|
/// Create a new uri with the provided parameters as query
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static IServiceCollection RegisterSharedRestInterfaces<T>(this IServiceCollection services, Func<IServiceProvider, T> client)
|
/// <param name="parameters"></param>
|
||||||
{
|
/// <param name="baseUri"></param>
|
||||||
if (typeof(IAssetsRestClient).IsAssignableFrom(typeof(T)))
|
/// <param name="arraySerialization"></param>
|
||||||
services.AddTransient(x => (IAssetsRestClient)client(x)!);
|
/// <returns></returns>
|
||||||
if (typeof(IBalanceRestClient).IsAssignableFrom(typeof(T)))
|
public static Uri SetParameters(this Uri baseUri, IDictionary<string, object> parameters, ArrayParametersSerialization arraySerialization)
|
||||||
services.AddTransient(x => (IBalanceRestClient)client(x)!);
|
{
|
||||||
if (typeof(IDepositRestClient).IsAssignableFrom(typeof(T)))
|
var uriBuilder = new UriBuilder();
|
||||||
services.AddTransient(x => (IDepositRestClient)client(x)!);
|
uriBuilder.Scheme = baseUri.Scheme;
|
||||||
if (typeof(IKlineRestClient).IsAssignableFrom(typeof(T)))
|
uriBuilder.Host = baseUri.Host;
|
||||||
services.AddTransient(x => (IKlineRestClient)client(x)!);
|
uriBuilder.Port = baseUri.Port;
|
||||||
if (typeof(IListenKeyRestClient).IsAssignableFrom(typeof(T)))
|
uriBuilder.Path = baseUri.AbsolutePath;
|
||||||
services.AddTransient(x => (IListenKeyRestClient)client(x)!);
|
var httpValueCollection = HttpUtility.ParseQueryString(string.Empty);
|
||||||
if (typeof(IOrderBookRestClient).IsAssignableFrom(typeof(T)))
|
foreach (var parameter in parameters)
|
||||||
services.AddTransient(x => (IOrderBookRestClient)client(x)!);
|
{
|
||||||
if (typeof(IRecentTradeRestClient).IsAssignableFrom(typeof(T)))
|
if (parameter.Value.GetType().IsArray)
|
||||||
services.AddTransient(x => (IRecentTradeRestClient)client(x)!);
|
{
|
||||||
if (typeof(ITradeHistoryRestClient).IsAssignableFrom(typeof(T)))
|
if (arraySerialization == ArrayParametersSerialization.JsonArray)
|
||||||
services.AddTransient(x => (ITradeHistoryRestClient)client(x)!);
|
{
|
||||||
if (typeof(IWithdrawalRestClient).IsAssignableFrom(typeof(T)))
|
httpValueCollection.Add(parameter.Key, $"[{string.Join(",", (object[])parameter.Value)}]");
|
||||||
services.AddTransient(x => (IWithdrawalRestClient)client(x)!);
|
}
|
||||||
if (typeof(IWithdrawRestClient).IsAssignableFrom(typeof(T)))
|
else
|
||||||
services.AddTransient(x => (IWithdrawRestClient)client(x)!);
|
{
|
||||||
if (typeof(IFeeRestClient).IsAssignableFrom(typeof(T)))
|
foreach (var item in (object[])parameter.Value)
|
||||||
services.AddTransient(x => (IFeeRestClient)client(x)!);
|
{
|
||||||
if (typeof(IBookTickerRestClient).IsAssignableFrom(typeof(T)))
|
if (arraySerialization == ArrayParametersSerialization.Array)
|
||||||
services.AddTransient(x => (IBookTickerRestClient)client(x)!);
|
{
|
||||||
|
httpValueCollection.Add(parameter.Key + "[]", item.ToString());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
httpValueCollection.Add(parameter.Key, item.ToString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
httpValueCollection.Add(parameter.Key, parameter.Value.ToString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (typeof(ISpotOrderRestClient).IsAssignableFrom(typeof(T)))
|
uriBuilder.Query = httpValueCollection.ToString();
|
||||||
services.AddTransient(x => (ISpotOrderRestClient)client(x)!);
|
return uriBuilder.Uri;
|
||||||
if (typeof(ISpotSymbolRestClient).IsAssignableFrom(typeof(T)))
|
}
|
||||||
services.AddTransient(x => (ISpotSymbolRestClient)client(x)!);
|
|
||||||
if (typeof(ISpotTickerRestClient).IsAssignableFrom(typeof(T)))
|
|
||||||
services.AddTransient(x => (ISpotTickerRestClient)client(x)!);
|
|
||||||
if (typeof(ISpotTriggerOrderRestClient).IsAssignableFrom(typeof(T)))
|
|
||||||
services.AddTransient(x => (ISpotTriggerOrderRestClient)client(x)!);
|
|
||||||
if (typeof(ISpotOrderClientIdRestClient).IsAssignableFrom(typeof(T)))
|
|
||||||
services.AddTransient(x => (ISpotOrderClientIdRestClient)client(x)!);
|
|
||||||
|
|
||||||
if (typeof(IFundingRateRestClient).IsAssignableFrom(typeof(T)))
|
/// <summary>
|
||||||
services.AddTransient(x => (IFundingRateRestClient)client(x)!);
|
/// Create a new uri with the provided parameters as query
|
||||||
if (typeof(IFuturesOrderRestClient).IsAssignableFrom(typeof(T)))
|
/// </summary>
|
||||||
services.AddTransient(x => (IFuturesOrderRestClient)client(x)!);
|
/// <param name="parameters"></param>
|
||||||
if (typeof(IFuturesSymbolRestClient).IsAssignableFrom(typeof(T)))
|
/// <param name="baseUri"></param>
|
||||||
services.AddTransient(x => (IFuturesSymbolRestClient)client(x)!);
|
/// <param name="arraySerialization"></param>
|
||||||
if (typeof(IFuturesTickerRestClient).IsAssignableFrom(typeof(T)))
|
/// <returns></returns>
|
||||||
services.AddTransient(x => (IFuturesTickerRestClient)client(x)!);
|
public static Uri SetParameters(this Uri baseUri, IOrderedEnumerable<KeyValuePair<string, object>> parameters, ArrayParametersSerialization arraySerialization)
|
||||||
if (typeof(IIndexPriceKlineRestClient).IsAssignableFrom(typeof(T)))
|
{
|
||||||
services.AddTransient(x => (IIndexPriceKlineRestClient)client(x)!);
|
var uriBuilder = new UriBuilder();
|
||||||
if (typeof(ILeverageRestClient).IsAssignableFrom(typeof(T)))
|
uriBuilder.Scheme = baseUri.Scheme;
|
||||||
services.AddTransient(x => (ILeverageRestClient)client(x)!);
|
uriBuilder.Host = baseUri.Host;
|
||||||
if (typeof(IMarkPriceKlineRestClient).IsAssignableFrom(typeof(T)))
|
uriBuilder.Port = baseUri.Port;
|
||||||
services.AddTransient(x => (IMarkPriceKlineRestClient)client(x)!);
|
uriBuilder.Path = baseUri.AbsolutePath;
|
||||||
if (typeof(IOpenInterestRestClient).IsAssignableFrom(typeof(T)))
|
var httpValueCollection = HttpUtility.ParseQueryString(string.Empty);
|
||||||
services.AddTransient(x => (IOpenInterestRestClient)client(x)!);
|
foreach (var parameter in parameters)
|
||||||
if (typeof(IPositionHistoryRestClient).IsAssignableFrom(typeof(T)))
|
{
|
||||||
services.AddTransient(x => (IPositionHistoryRestClient)client(x)!);
|
if (parameter.Value.GetType().IsArray)
|
||||||
if (typeof(IPositionModeRestClient).IsAssignableFrom(typeof(T)))
|
{
|
||||||
services.AddTransient(x => (IPositionModeRestClient)client(x)!);
|
if (arraySerialization == ArrayParametersSerialization.JsonArray)
|
||||||
if (typeof(IFuturesTpSlRestClient).IsAssignableFrom(typeof(T)))
|
{
|
||||||
services.AddTransient(x => (IFuturesTpSlRestClient)client(x)!);
|
httpValueCollection.Add(parameter.Key, $"[{string.Join(",", (object[])parameter.Value)}]");
|
||||||
if (typeof(IFuturesTriggerOrderRestClient).IsAssignableFrom(typeof(T)))
|
}
|
||||||
services.AddTransient(x => (IFuturesTriggerOrderRestClient)client(x)!);
|
else
|
||||||
if (typeof(IFuturesOrderClientIdRestClient).IsAssignableFrom(typeof(T)))
|
{
|
||||||
services.AddTransient(x => (IFuturesOrderClientIdRestClient)client(x)!);
|
foreach (var item in (object[])parameter.Value)
|
||||||
|
{
|
||||||
|
if (arraySerialization == ArrayParametersSerialization.Array)
|
||||||
|
{
|
||||||
|
httpValueCollection.Add(parameter.Key + "[]", item.ToString());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
httpValueCollection.Add(parameter.Key, item.ToString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
httpValueCollection.Add(parameter.Key, parameter.Value.ToString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return services;
|
uriBuilder.Query = httpValueCollection.ToString();
|
||||||
}
|
return uriBuilder.Uri;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Register socket client interfaces
|
/// Add parameter to URI
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static IServiceCollection RegisterSharedSocketInterfaces<T>(this IServiceCollection services, Func<IServiceProvider, T> client)
|
/// <param name="uri"></param>
|
||||||
{
|
/// <param name="name"></param>
|
||||||
if (typeof(IBalanceSocketClient).IsAssignableFrom(typeof(T)))
|
/// <param name="value"></param>
|
||||||
services.AddTransient(x => (IBalanceSocketClient)client(x)!);
|
/// <returns></returns>
|
||||||
if (typeof(IBookTickerSocketClient).IsAssignableFrom(typeof(T)))
|
public static Uri AddQueryParameter(this Uri uri, string name, string value)
|
||||||
services.AddTransient(x => (IBookTickerSocketClient)client(x)!);
|
{
|
||||||
if (typeof(IKlineSocketClient).IsAssignableFrom(typeof(T)))
|
var httpValueCollection = HttpUtility.ParseQueryString(uri.Query);
|
||||||
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)))
|
httpValueCollection.Remove(name);
|
||||||
services.AddTransient(x => (ISpotOrderSocketClient)client(x)!);
|
httpValueCollection.Add(name, value);
|
||||||
|
|
||||||
if (typeof(IFuturesOrderSocketClient).IsAssignableFrom(typeof(T)))
|
var ub = new UriBuilder(uri);
|
||||||
services.AddTransient(x => (IFuturesOrderSocketClient)client(x)!);
|
ub.Query = httpValueCollection.ToString();
|
||||||
if (typeof(IPositionSocketClient).IsAssignableFrom(typeof(T)))
|
|
||||||
services.AddTransient(x => (IPositionSocketClient)client(x)!);
|
|
||||||
|
|
||||||
return services;
|
return ub.Uri;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Decompress using GzipStream
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="data"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static ReadOnlyMemory<byte> DecompressGzip(this ReadOnlyMemory<byte> data)
|
||||||
|
{
|
||||||
|
using var decompressedStream = new MemoryStream();
|
||||||
|
using var dataStream = MemoryMarshal.TryGetArray(data, out var arraySegment)
|
||||||
|
? new MemoryStream(arraySegment.Array!, arraySegment.Offset, arraySegment.Count)
|
||||||
|
: new MemoryStream(data.ToArray());
|
||||||
|
using var deflateStream = new GZipStream(new MemoryStream(data.ToArray()), CompressionMode.Decompress);
|
||||||
|
deflateStream.CopyTo(decompressedStream);
|
||||||
|
return new ReadOnlyMemory<byte>(decompressedStream.GetBuffer(), 0, (int)decompressedStream.Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Decompress using DeflateStream
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="input"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static ReadOnlyMemory<byte> Decompress(this ReadOnlyMemory<byte> input)
|
||||||
|
{
|
||||||
|
var output = new MemoryStream();
|
||||||
|
|
||||||
|
using (var compressStream = new MemoryStream(input.ToArray()))
|
||||||
|
using (var decompressor = new DeflateStream(compressStream, CompressionMode.Decompress))
|
||||||
|
decompressor.CopyTo(output);
|
||||||
|
|
||||||
|
output.Position = 0;
|
||||||
|
return new ReadOnlyMemory<byte>(output.GetBuffer(), 0, (int)output.Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether the trading mode is linear
|
||||||
|
/// </summary>
|
||||||
|
public static bool IsLinear(this TradingMode type) => type == TradingMode.PerpetualLinear || type == TradingMode.DeliveryLinear;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether the trading mode is inverse
|
||||||
|
/// </summary>
|
||||||
|
public static bool IsInverse(this TradingMode type) => type == TradingMode.PerpetualInverse || type == TradingMode.DeliveryInverse;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether the trading mode is perpetual
|
||||||
|
/// </summary>
|
||||||
|
public static bool IsPerpetual(this TradingMode type) => type == TradingMode.PerpetualInverse || type == TradingMode.PerpetualLinear;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether the trading mode is delivery
|
||||||
|
/// </summary>
|
||||||
|
public static bool IsDelivery(this TradingMode type) => type == TradingMode.DeliveryInverse || type == TradingMode.DeliveryLinear;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Register rest client interfaces
|
||||||
|
/// </summary>
|
||||||
|
public static IServiceCollection RegisterSharedRestInterfaces<T>(this IServiceCollection services, Func<IServiceProvider, T> client)
|
||||||
|
{
|
||||||
|
if (typeof(IAssetsRestClient).IsAssignableFrom(typeof(T)))
|
||||||
|
services.AddTransient(x => (IAssetsRestClient)client(x)!);
|
||||||
|
if (typeof(IBalanceRestClient).IsAssignableFrom(typeof(T)))
|
||||||
|
services.AddTransient(x => (IBalanceRestClient)client(x)!);
|
||||||
|
if (typeof(IDepositRestClient).IsAssignableFrom(typeof(T)))
|
||||||
|
services.AddTransient(x => (IDepositRestClient)client(x)!);
|
||||||
|
if (typeof(IKlineRestClient).IsAssignableFrom(typeof(T)))
|
||||||
|
services.AddTransient(x => (IKlineRestClient)client(x)!);
|
||||||
|
if (typeof(IListenKeyRestClient).IsAssignableFrom(typeof(T)))
|
||||||
|
services.AddTransient(x => (IListenKeyRestClient)client(x)!);
|
||||||
|
if (typeof(IOrderBookRestClient).IsAssignableFrom(typeof(T)))
|
||||||
|
services.AddTransient(x => (IOrderBookRestClient)client(x)!);
|
||||||
|
if (typeof(IRecentTradeRestClient).IsAssignableFrom(typeof(T)))
|
||||||
|
services.AddTransient(x => (IRecentTradeRestClient)client(x)!);
|
||||||
|
if (typeof(ITradeHistoryRestClient).IsAssignableFrom(typeof(T)))
|
||||||
|
services.AddTransient(x => (ITradeHistoryRestClient)client(x)!);
|
||||||
|
if (typeof(IWithdrawalRestClient).IsAssignableFrom(typeof(T)))
|
||||||
|
services.AddTransient(x => (IWithdrawalRestClient)client(x)!);
|
||||||
|
if (typeof(IWithdrawRestClient).IsAssignableFrom(typeof(T)))
|
||||||
|
services.AddTransient(x => (IWithdrawRestClient)client(x)!);
|
||||||
|
if (typeof(IFeeRestClient).IsAssignableFrom(typeof(T)))
|
||||||
|
services.AddTransient(x => (IFeeRestClient)client(x)!);
|
||||||
|
if (typeof(IBookTickerRestClient).IsAssignableFrom(typeof(T)))
|
||||||
|
services.AddTransient(x => (IBookTickerRestClient)client(x)!);
|
||||||
|
|
||||||
|
if (typeof(ISpotOrderRestClient).IsAssignableFrom(typeof(T)))
|
||||||
|
services.AddTransient(x => (ISpotOrderRestClient)client(x)!);
|
||||||
|
if (typeof(ISpotSymbolRestClient).IsAssignableFrom(typeof(T)))
|
||||||
|
services.AddTransient(x => (ISpotSymbolRestClient)client(x)!);
|
||||||
|
if (typeof(ISpotTickerRestClient).IsAssignableFrom(typeof(T)))
|
||||||
|
services.AddTransient(x => (ISpotTickerRestClient)client(x)!);
|
||||||
|
if (typeof(ISpotTriggerOrderRestClient).IsAssignableFrom(typeof(T)))
|
||||||
|
services.AddTransient(x => (ISpotTriggerOrderRestClient)client(x)!);
|
||||||
|
if (typeof(ISpotOrderClientIdRestClient).IsAssignableFrom(typeof(T)))
|
||||||
|
services.AddTransient(x => (ISpotOrderClientIdRestClient)client(x)!);
|
||||||
|
|
||||||
|
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)!);
|
||||||
|
|
||||||
|
return services;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Register socket client interfaces
|
||||||
|
/// </summary>
|
||||||
|
public static IServiceCollection RegisterSharedSocketInterfaces<T>(this IServiceCollection services, Func<IServiceProvider, T> client)
|
||||||
|
{
|
||||||
|
if (typeof(IBalanceSocketClient).IsAssignableFrom(typeof(T)))
|
||||||
|
services.AddTransient(x => (IBalanceSocketClient)client(x)!);
|
||||||
|
if (typeof(IBookTickerSocketClient).IsAssignableFrom(typeof(T)))
|
||||||
|
services.AddTransient(x => (IBookTickerSocketClient)client(x)!);
|
||||||
|
if (typeof(IKlineSocketClient).IsAssignableFrom(typeof(T)))
|
||||||
|
services.AddTransient(x => (IKlineSocketClient)client(x)!);
|
||||||
|
if (typeof(IOrderBookSocketClient).IsAssignableFrom(typeof(T)))
|
||||||
|
services.AddTransient(x => (IOrderBookSocketClient)client(x)!);
|
||||||
|
if (typeof(ITickerSocketClient).IsAssignableFrom(typeof(T)))
|
||||||
|
services.AddTransient(x => (ITickerSocketClient)client(x)!);
|
||||||
|
if (typeof(ITickersSocketClient).IsAssignableFrom(typeof(T)))
|
||||||
|
services.AddTransient(x => (ITickersSocketClient)client(x)!);
|
||||||
|
if (typeof(ITradeSocketClient).IsAssignableFrom(typeof(T)))
|
||||||
|
services.AddTransient(x => (ITradeSocketClient)client(x)!);
|
||||||
|
if (typeof(IUserTradeSocketClient).IsAssignableFrom(typeof(T)))
|
||||||
|
services.AddTransient(x => (IUserTradeSocketClient)client(x)!);
|
||||||
|
|
||||||
|
if (typeof(ISpotOrderSocketClient).IsAssignableFrom(typeof(T)))
|
||||||
|
services.AddTransient(x => (ISpotOrderSocketClient)client(x)!);
|
||||||
|
|
||||||
|
if (typeof(IFuturesOrderSocketClient).IsAssignableFrom(typeof(T)))
|
||||||
|
services.AddTransient(x => (IFuturesOrderSocketClient)client(x)!);
|
||||||
|
if (typeof(IPositionSocketClient).IsAssignableFrom(typeof(T)))
|
||||||
|
services.AddTransient(x => (IPositionSocketClient)client(x)!);
|
||||||
|
|
||||||
|
return services;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,15 +1,16 @@
|
|||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace CryptoExchange.Net.Interfaces;
|
namespace CryptoExchange.Net.Interfaces
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Time provider
|
|
||||||
/// </summary>
|
|
||||||
internal interface IAuthTimeProvider
|
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Get current time
|
/// Time provider
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns></returns>
|
internal interface IAuthTimeProvider
|
||||||
DateTime GetTime();
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Get current time
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
DateTime GetTime();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,46 +1,48 @@
|
|||||||
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 address
|
/// Base api client
|
||||||
/// </summary>
|
/// </summary>
|
||||||
string BaseAddress { get; }
|
public interface IBaseApiClient
|
||||||
|
{
|
||||||
|
/// <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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,16 +1,17 @@
|
|||||||
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>
|
||||||
/// Try get
|
/// Client for accessing REST API's for different exchanges
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <typeparam name="T"></typeparam>
|
public interface ICryptoRestClient
|
||||||
/// <returns></returns>
|
{
|
||||||
T TryGet<T>(Func<T> createFunc);
|
/// <summary>
|
||||||
|
/// Try get
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T"></typeparam>
|
||||||
|
/// <returns></returns>
|
||||||
|
T TryGet<T>(Func<T> createFunc);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,16 +1,17 @@
|
|||||||
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>
|
||||||
/// Try get a client by type for the service collection
|
/// Client for accessing Websocket API's for different exchanges
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <typeparam name="T"></typeparam>
|
public interface ICryptoSocketClient
|
||||||
/// <returns></returns>
|
{
|
||||||
T TryGet<T>(Func<T> createFunc);
|
/// <summary>
|
||||||
|
/// Try get a client by type for the service collection
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T"></typeparam>
|
||||||
|
/// <returns></returns>
|
||||||
|
T TryGet<T>(Func<T> createFunc);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
#if NET5_0_OR_GREATER
|
using System.Collections.Generic;
|
||||||
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>
|
||||||
/// Is this a valid message
|
/// Message accessor
|
||||||
/// </summary>
|
/// </summary>
|
||||||
bool IsValid { get; }
|
public interface IMessageAccessor
|
||||||
/// <summary>
|
{
|
||||||
/// Is the original data available for retrieval
|
/// <summary>
|
||||||
/// </summary>
|
/// Is this a valid message
|
||||||
bool OriginalDataAvailable { get; }
|
/// </summary>
|
||||||
/// <summary>
|
bool IsValid { get; }
|
||||||
/// The underlying data object
|
/// <summary>
|
||||||
/// </summary>
|
/// Is the original data available for retrieval
|
||||||
object? Underlying { get; }
|
/// </summary>
|
||||||
/// <summary>
|
bool OriginalDataAvailable { get; }
|
||||||
/// Clear internal data structure
|
/// <summary>
|
||||||
/// </summary>
|
/// The underlying data object
|
||||||
void Clear();
|
/// </summary>
|
||||||
/// <summary>
|
object? Underlying { get; }
|
||||||
/// Get the type of node
|
/// <summary>
|
||||||
/// </summary>
|
/// Clear internal data structure
|
||||||
/// <returns></returns>
|
/// </summary>
|
||||||
NodeType? GetNodeType();
|
void Clear();
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Get the type of node
|
/// Get the type of node
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="path">Access path</param>
|
/// <returns></returns>
|
||||||
/// <returns></returns>
|
NodeType? GetNodeType();
|
||||||
NodeType? GetNodeType(MessagePath path);
|
/// <summary>
|
||||||
/// <summary>
|
/// Get the type of node
|
||||||
/// Get the value of a path
|
/// </summary>
|
||||||
/// </summary>
|
/// <param name="path">Access path</param>
|
||||||
/// <typeparam name="T"></typeparam>
|
/// <returns></returns>
|
||||||
/// <param name="path"></param>
|
NodeType? GetNodeType(MessagePath path);
|
||||||
/// <returns></returns>
|
/// <summary>
|
||||||
T? GetValue<T>(MessagePath path);
|
/// Get the value of a path
|
||||||
/// <summary>
|
/// </summary>
|
||||||
/// Get the values of an array
|
/// <typeparam name="T"></typeparam>
|
||||||
/// </summary>
|
/// <param name="path"></param>
|
||||||
/// <typeparam name="T"></typeparam>
|
/// <returns></returns>
|
||||||
/// <param name="path"></param>
|
T? GetValue<T>(MessagePath path);
|
||||||
/// <returns></returns>
|
/// <summary>
|
||||||
T?[]? GetValues<T>(MessagePath path);
|
/// Get the values of an array
|
||||||
/// <summary>
|
/// </summary>
|
||||||
/// Deserialize the message into this type
|
/// <typeparam name="T"></typeparam>
|
||||||
/// </summary>
|
/// <param name="path"></param>
|
||||||
/// <param name="type"></param>
|
/// <returns></returns>
|
||||||
/// <param name="path"></param>
|
T?[]? GetValues<T>(MessagePath path);
|
||||||
/// <returns></returns>
|
/// <summary>
|
||||||
|
/// 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>
|
||||||
/// Get the original string value
|
/// Stream message accessor
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns></returns>
|
public interface IStreamMessageAccessor : IMessageAccessor
|
||||||
string GetOriginalString();
|
{
|
||||||
}
|
/// <summary>
|
||||||
|
/// Load a stream message
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="stream"></param>
|
||||||
|
/// <param name="bufferStream"></param>
|
||||||
|
Task<CallResult> Read(Stream stream, bool bufferStream);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Stream message accessor
|
|
||||||
/// </summary>
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,33 +1,35 @@
|
|||||||
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>
|
||||||
/// Id of the processor
|
/// Message processor
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int Id { get; }
|
public interface IMessageProcessor
|
||||||
/// <summary>
|
{
|
||||||
/// The matcher for this listener
|
/// <summary>
|
||||||
/// </summary>
|
/// Id of the processor
|
||||||
public MessageMatcher MessageMatcher { get; }
|
/// </summary>
|
||||||
/// <summary>
|
public int Id { get; }
|
||||||
/// Handle a message
|
/// <summary>
|
||||||
/// </summary>
|
/// The matcher for this listener
|
||||||
Task<CallResult> Handle(SocketConnection connection, DataEvent<object> message, MessageHandlerLink matchedHandler);
|
/// </summary>
|
||||||
/// <summary>
|
public MessageMatcher MessageMatcher { get; }
|
||||||
/// Deserialize a message into object of type
|
/// <summary>
|
||||||
/// </summary>
|
/// Handle a message
|
||||||
/// <param name="accessor"></param>
|
/// </summary>
|
||||||
/// <param name="type"></param>
|
Task<CallResult> Handle(SocketConnection connection, DataEvent<object> message, MessageHandlerLink matchedHandler);
|
||||||
/// <returns></returns>
|
/// <summary>
|
||||||
CallResult<object> Deserialize(IMessageAccessor accessor, Type type);
|
/// Deserialize a message into object of type
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="accessor"></param>
|
||||||
|
/// <param name="type"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
CallResult<object> Deserialize(IMessageAccessor accessor, Type type);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,34 +1,37 @@
|
|||||||
namespace CryptoExchange.Net.Interfaces;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
|
||||||
/// <summary>
|
namespace CryptoExchange.Net.Interfaces
|
||||||
/// Serializer interface
|
|
||||||
/// </summary>
|
|
||||||
public interface IMessageSerializer
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Serialize to byte array
|
|
||||||
/// </summary>
|
|
||||||
public interface IByteMessageSerializer: IMessageSerializer
|
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Serialize an object to a string
|
/// Serializer interface
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="message"></param>
|
public interface IMessageSerializer
|
||||||
/// <returns></returns>
|
{
|
||||||
byte[] Serialize<T>(T message);
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Serialize to string
|
|
||||||
/// </summary>
|
|
||||||
public interface IStringMessageSerializer: IMessageSerializer
|
|
||||||
{
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Serialize an object to a string
|
/// Serialize to byte array
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="message"></param>
|
public interface IByteMessageSerializer: IMessageSerializer
|
||||||
/// <returns></returns>
|
{
|
||||||
string Serialize<T>(T message);
|
/// <summary>
|
||||||
|
/// Serialize an object to a string
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="message"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
byte[] Serialize<T>(T message);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Serialize to string
|
||||||
|
/// </summary>
|
||||||
|
public interface IStringMessageSerializer: IMessageSerializer
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Serialize an object to a string
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="message"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
string Serialize<T>(T message);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
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>
|
||||||
/// Get nonce value. Nonce value should be unique and incremental for each call
|
/// A provider for a nonce value used when signing requests
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>Nonce value</returns>
|
public interface INonceProvider
|
||||||
long GetNonce();
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Get nonce value. Nonce value should be unique and incremental for each call
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>Nonce value</returns>
|
||||||
|
long GetNonce();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,34 +1,35 @@
|
|||||||
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>
|
||||||
/// Create a new order book by symbol name
|
/// Factory for ISymbolOrderBook instances
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="symbol">Symbol name</param>
|
public interface IOrderBookFactory<TOptions> where TOptions : OrderBookOptions
|
||||||
/// <param name="options">Options for the order book</param>
|
{
|
||||||
/// <returns></returns>
|
/// <summary>
|
||||||
public ISymbolOrderBook Create(string symbol, Action<TOptions>? options = null);
|
/// Create a new order book by symbol name
|
||||||
/// <summary>
|
/// </summary>
|
||||||
/// Create a new order book by base and quote asset names
|
/// <param name="symbol">Symbol name</param>
|
||||||
/// </summary>
|
/// <param name="options">Options for the order book</param>
|
||||||
/// <param name="baseAsset">Base asset name</param>
|
/// <returns></returns>
|
||||||
/// <param name="quoteAsset">Quote asset name</param>
|
public ISymbolOrderBook Create(string symbol, 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(string baseAsset, string quoteAsset, Action<TOptions>? options = null);
|
/// </summary>
|
||||||
/// <summary>
|
/// <param name="baseAsset">Base asset name</param>
|
||||||
/// Create a new order book by base and quote asset names
|
/// <param name="quoteAsset">Quote asset name</param>
|
||||||
/// </summary>
|
/// <param name="options">Options for the order book</param>
|
||||||
/// <param name="symbol">Symbol</param>
|
/// <returns></returns>
|
||||||
/// <param name="options">Options for the order book</param>
|
public ISymbolOrderBook Create(string baseAsset, string quoteAsset, Action<TOptions>? options = null);
|
||||||
/// <returns></returns>
|
/// <summary>
|
||||||
public ISymbolOrderBook Create(SharedSymbol symbol, Action<TOptions>? options = null);
|
/// Create a new order book by base and quote asset names
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="symbol">Symbol</param>
|
||||||
|
/// <param name="options">Options for the order book</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public ISymbolOrderBook Create(SharedSymbol symbol, Action<TOptions>? options = null);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,24 +4,25 @@ 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>
|
||||||
/// Limit a request based on previous requests made
|
/// Rate limiter interface
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="log">The logger</param>
|
public interface IRateLimiter
|
||||||
/// <param name="endpoint">The endpoint the request is for</param>
|
{
|
||||||
/// <param name="method">The Http request method</param>
|
/// <summary>
|
||||||
/// <param name="signed">Whether the request is singed(private) or not</param>
|
/// Limit a request based on previous requests made
|
||||||
/// <param name="apiKey">The api key making this request</param>
|
/// </summary>
|
||||||
/// <param name="limitBehaviour">The limit behavior for when the limit is reached</param>
|
/// <param name="log">The logger</param>
|
||||||
/// <param name="requestWeight">The weight of the request</param>
|
/// <param name="endpoint">The endpoint the request is for</param>
|
||||||
/// <param name="ct">Cancellation token to cancel waiting</param>
|
/// <param name="method">The Http request method</param>
|
||||||
/// <returns>The time in milliseconds spend waiting</returns>
|
/// <param name="signed">Whether the request is singed(private) or not</param>
|
||||||
Task<CallResult<int>> LimitRequestAsync(ILogger log, string endpoint, HttpMethod method, bool signed, string? apiKey, RateLimitingBehaviour limitBehaviour, int requestWeight, CancellationToken ct);
|
/// <param name="apiKey">The api key making this request</param>
|
||||||
|
/// <param name="limitBehaviour">The limit behavior for when the limit is reached</param>
|
||||||
|
/// <param name="requestWeight">The weight of the request</param>
|
||||||
|
/// <param name="ct">Cancellation token to cancel waiting</param>
|
||||||
|
/// <returns>The time in milliseconds spend waiting</returns>
|
||||||
|
Task<CallResult<int>> LimitRequestAsync(ILogger log, string endpoint, HttpMethod method, bool signed, string? apiKey, RateLimitingBehaviour limitBehaviour, int requestWeight, CancellationToken ct);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,65 +1,70 @@
|
|||||||
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>
|
||||||
/// Accept header
|
/// Request interface
|
||||||
/// </summary>
|
/// </summary>
|
||||||
string Accept { set; }
|
public interface IRequest
|
||||||
/// <summary>
|
{
|
||||||
/// Content
|
/// <summary>
|
||||||
/// </summary>
|
/// Accept header
|
||||||
string? Content { get; }
|
/// </summary>
|
||||||
/// <summary>
|
string Accept { set; }
|
||||||
/// Method
|
/// <summary>
|
||||||
/// </summary>
|
/// Content
|
||||||
HttpMethod Method { get; set; }
|
/// </summary>
|
||||||
/// <summary>
|
string? Content { get; }
|
||||||
/// Uri
|
/// <summary>
|
||||||
/// </summary>
|
/// Method
|
||||||
Uri Uri { get; }
|
/// </summary>
|
||||||
/// <summary>
|
HttpMethod Method { get; set; }
|
||||||
/// internal request id for tracing
|
/// <summary>
|
||||||
/// </summary>
|
/// Uri
|
||||||
int RequestId { get; }
|
/// </summary>
|
||||||
/// <summary>
|
Uri Uri { get; }
|
||||||
/// Set byte content
|
/// <summary>
|
||||||
/// </summary>
|
/// HTTP protocol version
|
||||||
/// <param name="data"></param>
|
/// </summary>
|
||||||
void SetContent(byte[] data);
|
Version HttpVersion { get; }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Set string content
|
/// internal request id for tracing
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="data"></param>
|
int RequestId { get; }
|
||||||
/// <param name="contentType"></param>
|
/// <summary>
|
||||||
void SetContent(string data, string contentType);
|
/// Set byte content
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="data"></param>
|
||||||
|
void SetContent(byte[] data);
|
||||||
|
/// <summary>
|
||||||
|
/// Set string content
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="data"></param>
|
||||||
|
/// <param name="contentType"></param>
|
||||||
|
void SetContent(string data, string contentType);
|
||||||
|
|
||||||
/// <summary>
|
/// <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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,35 +1,33 @@
|
|||||||
using CryptoExchange.Net.Objects;
|
using CryptoExchange.Net.Objects;
|
||||||
|
using CryptoExchange.Net.Objects.Options;
|
||||||
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>
|
||||||
/// Create a request for an uri
|
/// Request factory interface
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="method"></param>
|
public interface IRequestFactory
|
||||||
/// <param name="uri"></param>
|
{
|
||||||
/// <param name="requestId"></param>
|
/// <summary>
|
||||||
/// <returns></returns>
|
/// Create a request for an uri
|
||||||
IRequest Create(HttpMethod method, Uri uri, int requestId);
|
/// </summary>
|
||||||
|
IRequest Create(Version httpRequestVersion, 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="options">Rest client options</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>
|
void Configure(RestExchangeOptions options, 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);
|
/// <param name="httpKeepAliveInterval">Http client keep alive interval</param>
|
||||||
|
void UpdateSettings(ApiProxy? proxy, TimeSpan requestTimeout, TimeSpan? httpKeepAliveInterval);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,43 +1,50 @@
|
|||||||
|
using System;
|
||||||
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>
|
||||||
/// The response status code
|
/// Response object interface
|
||||||
/// </summary>
|
/// </summary>
|
||||||
HttpStatusCode StatusCode { get; }
|
public interface IResponse
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The response status code
|
||||||
|
/// </summary>
|
||||||
|
HttpStatusCode StatusCode { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Whether the status code indicates a success status
|
/// Http protocol version
|
||||||
/// </summary>
|
/// </summary>
|
||||||
bool IsSuccessStatusCode { get; }
|
Version HttpVersion { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The length of the response in bytes
|
/// Whether the status code indicates a success status
|
||||||
/// </summary>
|
/// </summary>
|
||||||
long? ContentLength { get; }
|
bool IsSuccessStatusCode { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The response headers
|
/// The length of the response in bytes
|
||||||
/// </summary>
|
/// </summary>
|
||||||
KeyValuePair<string, string[]>[] ResponseHeaders { get; }
|
long? ContentLength { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Get the response stream
|
/// The response headers
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns></returns>
|
KeyValuePair<string, string[]>[] ResponseHeaders { get; }
|
||||||
Task<Stream> GetResponseStreamAsync();
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Close the response
|
/// Get the response stream
|
||||||
/// </summary>
|
/// </summary>
|
||||||
void Close();
|
/// <returns></returns>
|
||||||
|
Task<Stream> GetResponseStreamAsync();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Close the response
|
||||||
|
/// </summary>
|
||||||
|
void Close();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,17 +1,18 @@
|
|||||||
namespace CryptoExchange.Net.Interfaces;
|
namespace CryptoExchange.Net.Interfaces
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Base rest API client
|
|
||||||
/// </summary>
|
|
||||||
public interface IRestApiClient : IBaseApiClient
|
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The factory for creating requests. Used for unit testing
|
/// Base rest API client
|
||||||
/// </summary>
|
/// </summary>
|
||||||
IRequestFactory RequestFactory { get; set; }
|
public interface IRestApiClient : IBaseApiClient
|
||||||
|
{
|
||||||
|
/// <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; }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,25 +1,26 @@
|
|||||||
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>
|
||||||
/// The options provided for this client
|
/// Base class for rest API implementations
|
||||||
/// </summary>
|
/// </summary>
|
||||||
ExchangeOptions ClientOptions { get; }
|
public interface IRestClient: IDisposable
|
||||||
|
{
|
||||||
|
/// <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; }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,69 +1,70 @@
|
|||||||
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>
|
||||||
/// The current amount of socket connections on the API client
|
/// Socket API client
|
||||||
/// </summary>
|
/// </summary>
|
||||||
int CurrentConnections { get; }
|
public interface ISocketApiClient: IBaseApiClient
|
||||||
/// <summary>
|
{
|
||||||
/// The current amount of subscriptions over all connections
|
/// <summary>
|
||||||
/// </summary>
|
/// The current amount of socket connections on the API client
|
||||||
int CurrentSubscriptions { get; }
|
/// </summary>
|
||||||
/// <summary>
|
int CurrentConnections { get; }
|
||||||
/// Incoming data Kbps
|
/// <summary>
|
||||||
/// </summary>
|
/// The current amount of subscriptions over all connections
|
||||||
double IncomingKbps { get; }
|
/// </summary>
|
||||||
/// <summary>
|
int CurrentSubscriptions { get; }
|
||||||
/// The factory for creating sockets. Used for unit testing
|
/// <summary>
|
||||||
/// </summary>
|
/// Incoming data Kbps
|
||||||
IWebsocketFactory SocketFactory { get; set; }
|
/// </summary>
|
||||||
/// <summary>
|
double IncomingKbps { get; }
|
||||||
/// Current client options
|
/// <summary>
|
||||||
/// </summary>
|
/// The factory for creating sockets. Used for unit testing
|
||||||
SocketExchangeOptions ClientOptions { get; }
|
/// </summary>
|
||||||
/// <summary>
|
IWebsocketFactory SocketFactory { get; set; }
|
||||||
/// Current API options
|
/// <summary>
|
||||||
/// </summary>
|
/// Current client options
|
||||||
SocketApiOptions ApiOptions { get; }
|
/// </summary>
|
||||||
/// <summary>
|
SocketExchangeOptions ClientOptions { get; }
|
||||||
/// Log the current state of connections and subscriptions
|
/// <summary>
|
||||||
/// </summary>
|
/// Current API options
|
||||||
string GetSubscriptionsState(bool includeSubDetails = true);
|
/// </summary>
|
||||||
/// <summary>
|
SocketApiOptions ApiOptions { get; }
|
||||||
/// Reconnect all connections
|
/// <summary>
|
||||||
/// </summary>
|
/// Log the current state of connections and subscriptions
|
||||||
/// <returns></returns>
|
/// </summary>
|
||||||
Task ReconnectAsync();
|
string GetSubscriptionsState(bool includeSubDetails = true);
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Unsubscribe all subscriptions
|
/// Reconnect all connections
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
Task UnsubscribeAllAsync();
|
Task ReconnectAsync();
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Unsubscribe an update subscription
|
/// Unsubscribe all subscriptions
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="subscriptionId">The id of the subscription to unsubscribe</param>
|
/// <returns></returns>
|
||||||
/// <returns></returns>
|
Task UnsubscribeAllAsync();
|
||||||
Task<bool> UnsubscribeAsync(int subscriptionId);
|
/// <summary>
|
||||||
/// <summary>
|
/// Unsubscribe an update subscription
|
||||||
/// Unsubscribe an update subscription
|
/// </summary>
|
||||||
/// </summary>
|
/// <param name="subscriptionId">The id of the subscription to unsubscribe</param>
|
||||||
/// <param name="subscription">The subscription to unsubscribe</param>
|
/// <returns></returns>
|
||||||
/// <returns></returns>
|
Task<bool> UnsubscribeAsync(int subscriptionId);
|
||||||
Task UnsubscribeAsync(UpdateSubscription subscription);
|
/// <summary>
|
||||||
|
/// 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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,57 +1,58 @@
|
|||||||
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>
|
||||||
/// The exchange name
|
/// Base class for socket API implementations
|
||||||
/// </summary>
|
/// </summary>
|
||||||
string Exchange { get; }
|
public interface ISocketClient: IDisposable
|
||||||
|
{
|
||||||
|
/// <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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,129 +1,131 @@
|
|||||||
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>
|
||||||
/// The exchange the book is for
|
/// Interface for order book
|
||||||
/// </summary>
|
/// </summary>
|
||||||
string Exchange { get; }
|
public interface ISymbolOrderBook
|
||||||
|
{
|
||||||
|
/// <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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,27 +1,28 @@
|
|||||||
namespace CryptoExchange.Net.Interfaces;
|
namespace CryptoExchange.Net.Interfaces
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Interface for order book entries
|
|
||||||
/// </summary>
|
|
||||||
public interface ISymbolOrderBookEntry
|
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The quantity of the entry
|
/// Interface for order book entries
|
||||||
/// </summary>
|
/// </summary>
|
||||||
decimal Quantity { get; set; }
|
public interface ISymbolOrderBookEntry
|
||||||
/// <summary>
|
{
|
||||||
/// The price of the entry
|
/// <summary>
|
||||||
/// </summary>
|
/// The quantity of the entry
|
||||||
decimal Price { get; set; }
|
/// </summary>
|
||||||
}
|
decimal Quantity { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// The price of the entry
|
||||||
|
/// </summary>
|
||||||
|
decimal Price { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Interface for order book entries
|
|
||||||
/// </summary>
|
|
||||||
public interface ISymbolOrderSequencedBookEntry: ISymbolOrderBookEntry
|
|
||||||
{
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sequence of the update
|
/// Interface for order book entries
|
||||||
/// </summary>
|
/// </summary>
|
||||||
long Sequence { get; set; }
|
public interface ISymbolOrderSequencedBookEntry: ISymbolOrderBookEntry
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Sequence of the update
|
||||||
|
/// </summary>
|
||||||
|
long Sequence { get; set; }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,45 @@
|
|||||||
|
using CryptoExchange.Net.SharedApis;
|
||||||
|
using CryptoExchange.Net.Trackers.Klines;
|
||||||
|
using CryptoExchange.Net.Trackers.Trades;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace CryptoExchange.Net.Interfaces
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Tracker factory
|
||||||
|
/// </summary>
|
||||||
|
public interface ITrackerFactory
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Whether the factory supports creating a KlineTracker instance for this symbol and interval
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="symbol">The symbol</param>
|
||||||
|
/// <param name="interval">The kline interval</param>
|
||||||
|
bool CanCreateKlineTracker(SharedSymbol symbol, SharedKlineInterval interval);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create a new kline tracker
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="symbol">The symbol</param>
|
||||||
|
/// <param name="interval">Kline interval</param>
|
||||||
|
/// <param name="limit">The max amount of klines to retain</param>
|
||||||
|
/// <param name="period">The max period the data should be retained</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
IKlineTracker CreateKlineTracker(SharedSymbol symbol, SharedKlineInterval interval, int? limit = null, TimeSpan? period = null);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether the factory supports creating a TradeTracker instance for this symbol
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="symbol">The symbol</param>
|
||||||
|
bool CanCreateTradeTracker(SharedSymbol symbol);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create a new trade tracker for a symbol
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="symbol">The symbol</param>
|
||||||
|
/// <param name="limit">The max amount of trades to retain</param>
|
||||||
|
/// <param name="period">The max period the data should be retained</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
ITradeTracker CreateTradeTracker(SharedSymbol symbol, int? limit = null, TimeSpan? period = null);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,109 +1,110 @@
|
|||||||
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 closed event
|
/// Websocket connection interface
|
||||||
/// </summary>
|
/// </summary>
|
||||||
event Func<Task> OnClose;
|
public interface IWebsocket: IDisposable
|
||||||
/// <summary>
|
{
|
||||||
/// Websocket message received event
|
/// <summary>
|
||||||
/// </summary>
|
/// Websocket closed event
|
||||||
event Func<WebSocketMessageType, ReadOnlyMemory<byte>, Task> OnStreamMessage;
|
/// </summary>
|
||||||
/// <summary>
|
event Func<Task> OnClose;
|
||||||
/// Websocket sent event, RequestId as parameter
|
/// <summary>
|
||||||
/// </summary>
|
/// Websocket message received event
|
||||||
event Func<int, Task> OnRequestSent;
|
/// </summary>
|
||||||
/// <summary>
|
event Func<WebSocketMessageType, ReadOnlyMemory<byte>, Task> OnStreamMessage;
|
||||||
/// Websocket query was ratelimited and couldn't be send
|
/// <summary>
|
||||||
/// </summary>
|
/// Websocket sent event, RequestId as parameter
|
||||||
event Func<int, Task>? OnRequestRateLimited;
|
/// </summary>
|
||||||
/// <summary>
|
event Func<int, Task> OnRequestSent;
|
||||||
/// Connection was ratelimited and couldn't be established
|
/// <summary>
|
||||||
/// </summary>
|
/// Websocket query was ratelimited and couldn't be send
|
||||||
event Func<Task>? OnConnectRateLimited;
|
/// </summary>
|
||||||
/// <summary>
|
event Func<int, Task>? OnRequestRateLimited;
|
||||||
/// Websocket error event
|
/// <summary>
|
||||||
/// </summary>
|
/// Connection was ratelimited and couldn't be established
|
||||||
event Func<Exception, Task> OnError;
|
/// </summary>
|
||||||
/// <summary>
|
event Func<Task>? OnConnectRateLimited;
|
||||||
/// Websocket opened event
|
/// <summary>
|
||||||
/// </summary>
|
/// Websocket error event
|
||||||
event Func<Task> OnOpen;
|
/// </summary>
|
||||||
/// <summary>
|
event Func<Exception, Task> OnError;
|
||||||
/// Websocket has lost connection to the server and is attempting to reconnect
|
/// <summary>
|
||||||
/// </summary>
|
/// Websocket opened event
|
||||||
event Func<Task> OnReconnecting;
|
/// </summary>
|
||||||
/// <summary>
|
event Func<Task> OnOpen;
|
||||||
/// Websocket has reconnected to the server
|
/// <summary>
|
||||||
/// </summary>
|
/// Websocket has lost connection to the server and is attempting to reconnect
|
||||||
event Func<Task> OnReconnected;
|
/// </summary>
|
||||||
/// <summary>
|
event Func<Task> OnReconnecting;
|
||||||
/// Get reconnection url
|
/// <summary>
|
||||||
/// </summary>
|
/// Websocket has reconnected to the server
|
||||||
Func<Task<Uri?>>? GetReconnectionUrl { get; set; }
|
/// </summary>
|
||||||
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,18 +1,19 @@
|
|||||||
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>
|
||||||
/// Create a websocket for an url
|
/// Websocket factory interface
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="logger">The logger</param>
|
public interface IWebsocketFactory
|
||||||
/// <param name="parameters">The parameters to use for the connection</param>
|
{
|
||||||
/// <returns></returns>
|
/// <summary>
|
||||||
IWebsocket CreateWebsocket(ILogger logger, WebSocketParameters parameters);
|
/// Create a websocket for an url
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="logger">The logger</param>
|
||||||
|
/// <param name="parameters">The parameters to use for the connection</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
IWebsocket CreateWebsocket(ILogger logger, WebSocketParameters parameters);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,42 +1,103 @@
|
|||||||
namespace CryptoExchange.Net;
|
using CryptoExchange.Net.Objects;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Net;
|
||||||
|
using System.Net.Http;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
/// <summary>
|
namespace CryptoExchange.Net
|
||||||
/// Helpers for client libraries
|
|
||||||
/// </summary>
|
|
||||||
public static class LibraryHelpers
|
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Client order id separator
|
/// Helpers for client libraries
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public const string ClientOrderIdSeparator = "JK";
|
public static class LibraryHelpers
|
||||||
|
|
||||||
/// <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)
|
|
||||||
{
|
{
|
||||||
var reservedLength = brokerId.Length + ClientOrderIdSeparator.Length;
|
/// <summary>
|
||||||
|
/// Client order id separator
|
||||||
|
/// </summary>
|
||||||
|
public const string ClientOrderIdSeparator = "JK";
|
||||||
|
|
||||||
if ((clientOrderId?.Length + reservedLength) > maxLength)
|
/// <summary>
|
||||||
return clientOrderId!;
|
/// Apply broker id to a client order id
|
||||||
|
/// </summary>
|
||||||
if (!string.IsNullOrEmpty(clientOrderId))
|
/// <param name="clientOrderId"></param>
|
||||||
|
/// <param name="brokerId"></param>
|
||||||
|
/// <param name="maxLength"></param>
|
||||||
|
/// <param name="allowValueAdjustment"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static string ApplyBrokerId(string? clientOrderId, string brokerId, int maxLength, bool allowValueAdjustment)
|
||||||
{
|
{
|
||||||
if (allowValueAdjustment)
|
var reservedLength = brokerId.Length + ClientOrderIdSeparator.Length;
|
||||||
clientOrderId = brokerId + ClientOrderIdSeparator + clientOrderId;
|
|
||||||
|
|
||||||
return clientOrderId!;
|
if ((clientOrderId?.Length + reservedLength) > maxLength)
|
||||||
}
|
return clientOrderId!;
|
||||||
else
|
|
||||||
{
|
if (!string.IsNullOrEmpty(clientOrderId))
|
||||||
clientOrderId = ExchangeHelpers.AppendRandomString(brokerId + ClientOrderIdSeparator, maxLength);
|
{
|
||||||
|
if (allowValueAdjustment)
|
||||||
|
clientOrderId = brokerId + ClientOrderIdSeparator + clientOrderId;
|
||||||
|
|
||||||
|
return clientOrderId!;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
clientOrderId = ExchangeHelpers.AppendRandomString(brokerId + ClientOrderIdSeparator, maxLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
return clientOrderId;
|
||||||
}
|
}
|
||||||
|
|
||||||
return clientOrderId;
|
/// <summary>
|
||||||
|
/// Create a new HttpMessageHandler instance
|
||||||
|
/// </summary>
|
||||||
|
public static HttpMessageHandler CreateHttpClientMessageHandler(ApiProxy? proxy, TimeSpan? keepAliveInterval)
|
||||||
|
{
|
||||||
|
#if NET5_0_OR_GREATER
|
||||||
|
var socketHandler = new SocketsHttpHandler();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (keepAliveInterval != null && keepAliveInterval != TimeSpan.Zero)
|
||||||
|
{
|
||||||
|
socketHandler.KeepAlivePingPolicy = HttpKeepAlivePingPolicy.Always;
|
||||||
|
socketHandler.KeepAlivePingDelay = keepAliveInterval.Value;
|
||||||
|
socketHandler.KeepAlivePingTimeout = TimeSpan.FromSeconds(10);
|
||||||
|
}
|
||||||
|
|
||||||
|
socketHandler.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate;
|
||||||
|
socketHandler.DefaultProxyCredentials = CredentialCache.DefaultCredentials;
|
||||||
|
}
|
||||||
|
catch (PlatformNotSupportedException) { }
|
||||||
|
catch (NotImplementedException) { } // Mono runtime throws NotImplementedException
|
||||||
|
|
||||||
|
if (proxy != null)
|
||||||
|
{
|
||||||
|
socketHandler.Proxy = new WebProxy
|
||||||
|
{
|
||||||
|
Address = new Uri($"{proxy.Host}:{proxy.Port}"),
|
||||||
|
Credentials = proxy.Password == null ? null : new NetworkCredential(proxy.Login, proxy.Password)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return socketHandler;
|
||||||
|
#else
|
||||||
|
var httpHandler = new HttpClientHandler();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
httpHandler.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate;
|
||||||
|
httpHandler.DefaultProxyCredentials = CredentialCache.DefaultCredentials;
|
||||||
|
}
|
||||||
|
catch (PlatformNotSupportedException) { }
|
||||||
|
catch (NotImplementedException) { } // Mono runtime throws NotImplementedException
|
||||||
|
|
||||||
|
if (proxy != null)
|
||||||
|
{
|
||||||
|
httpHandler.Proxy = new WebProxy
|
||||||
|
{
|
||||||
|
Address = new Uri($"{proxy.Host}:{proxy.Port}"),
|
||||||
|
Credentials = proxy.Password == null ? null : new NetworkCredential(proxy.Login, proxy.Password)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return httpHandler;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+318
-317
@@ -1,386 +1,387 @@
|
|||||||
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
|
|
||||||
public static class CryptoExchangeWebSocketClientLoggingExtension
|
|
||||||
{
|
{
|
||||||
private static readonly Action<ILogger, int, Exception?> _connecting;
|
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
|
||||||
private static readonly Action<ILogger, int, string, Exception?> _connectionFailed;
|
public static class CryptoExchangeWebSocketClientLoggingExtension
|
||||||
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()
|
|
||||||
{
|
{
|
||||||
_connecting = LoggerMessage.Define<int>(
|
private static readonly Action<ILogger, int, Exception?> _connecting;
|
||||||
LogLevel.Debug,
|
private static readonly Action<ILogger, int, string, Exception?> _connectionFailed;
|
||||||
new EventId(1000, "Connecting"),
|
private static readonly Action<ILogger, int, Exception?> _connectingCanceled;
|
||||||
"[Sckt {SocketId}] connecting");
|
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;
|
||||||
|
|
||||||
_connectionFailed = LoggerMessage.Define<int, string>(
|
static CryptoExchangeWebSocketClientLoggingExtension()
|
||||||
LogLevel.Error,
|
{
|
||||||
new EventId(1001, "ConnectionFailed"),
|
_connecting = LoggerMessage.Define<int>(
|
||||||
"[Sckt {SocketId}] connection failed: {ErrorMessage}");
|
LogLevel.Debug,
|
||||||
|
new EventId(1000, "Connecting"),
|
||||||
|
"[Sckt {SocketId}] connecting");
|
||||||
|
|
||||||
_connected = LoggerMessage.Define<int, Uri?>(
|
_connectionFailed = LoggerMessage.Define<int, string>(
|
||||||
LogLevel.Debug,
|
LogLevel.Error,
|
||||||
new EventId(1002, "Connected"),
|
new EventId(1001, "ConnectionFailed"),
|
||||||
"[Sckt {SocketId}] connected to {Uri}");
|
"[Sckt {SocketId}] connection failed: {ErrorMessage}");
|
||||||
|
|
||||||
_startingProcessing = LoggerMessage.Define<int>(
|
_connected = LoggerMessage.Define<int, Uri?>(
|
||||||
LogLevel.Debug,
|
LogLevel.Debug,
|
||||||
new EventId(1003, "StartingProcessing"),
|
new EventId(1002, "Connected"),
|
||||||
"[Sckt {SocketId}] starting processing tasks");
|
"[Sckt {SocketId}] connected to {Uri}");
|
||||||
|
|
||||||
_finishedProcessing = LoggerMessage.Define<int>(
|
_startingProcessing = LoggerMessage.Define<int>(
|
||||||
LogLevel.Debug,
|
LogLevel.Debug,
|
||||||
new EventId(1004, "FinishedProcessing"),
|
new EventId(1003, "StartingProcessing"),
|
||||||
"[Sckt {SocketId}] processing tasks finished");
|
"[Sckt {SocketId}] starting processing tasks");
|
||||||
|
|
||||||
_attemptReconnect = LoggerMessage.Define<int>(
|
_finishedProcessing = LoggerMessage.Define<int>(
|
||||||
LogLevel.Debug,
|
LogLevel.Debug,
|
||||||
new EventId(1005, "AttemptReconnect"),
|
new EventId(1004, "FinishedProcessing"),
|
||||||
"[Sckt {SocketId}] attempting to reconnect");
|
"[Sckt {SocketId}] processing tasks finished");
|
||||||
|
|
||||||
_setReconnectUri = LoggerMessage.Define<int, Uri>(
|
_attemptReconnect = LoggerMessage.Define<int>(
|
||||||
LogLevel.Debug,
|
LogLevel.Debug,
|
||||||
new EventId(1006, "SetReconnectUri"),
|
new EventId(1005, "AttemptReconnect"),
|
||||||
"[Sckt {SocketId}] reconnect URI set to {ReconnectUri}");
|
"[Sckt {SocketId}] attempting to reconnect");
|
||||||
|
|
||||||
_addingBytesToSendBuffer = LoggerMessage.Define<int, int, int>(
|
_setReconnectUri = LoggerMessage.Define<int, Uri>(
|
||||||
LogLevel.Trace,
|
LogLevel.Debug,
|
||||||
new EventId(1007, "AddingBytesToSendBuffer"),
|
new EventId(1006, "SetReconnectUri"),
|
||||||
"[Sckt {SocketId}] [Req {RequestId}] adding {NumBytes} bytes to send buffer");
|
"[Sckt {SocketId}] reconnect URI set to {ReconnectUri}");
|
||||||
|
|
||||||
_reconnectRequested = LoggerMessage.Define<int>(
|
_addingBytesToSendBuffer = LoggerMessage.Define<int, int, int>(
|
||||||
LogLevel.Debug,
|
LogLevel.Trace,
|
||||||
new EventId(1008, "ReconnectRequested"),
|
new EventId(1007, "AddingBytesToSendBuffer"),
|
||||||
"[Sckt {SocketId}] reconnect requested");
|
"[Sckt {SocketId}] [Req {RequestId}] adding {NumBytes} bytes to send buffer");
|
||||||
|
|
||||||
_closeAsyncWaitingForExistingCloseTask = LoggerMessage.Define<int>(
|
_reconnectRequested = LoggerMessage.Define<int>(
|
||||||
LogLevel.Debug,
|
LogLevel.Debug,
|
||||||
new EventId(1009, "CloseAsyncWaitForExistingCloseTask"),
|
new EventId(1008, "ReconnectRequested"),
|
||||||
"[Sckt {SocketId}] CloseAsync() waiting for existing close task");
|
"[Sckt {SocketId}] reconnect requested");
|
||||||
|
|
||||||
_closeAsyncSocketNotOpen = LoggerMessage.Define<int>(
|
_closeAsyncWaitingForExistingCloseTask = LoggerMessage.Define<int>(
|
||||||
LogLevel.Debug,
|
LogLevel.Debug,
|
||||||
new EventId(1010, "CloseAsyncSocketNotOpen"),
|
new EventId(1009, "CloseAsyncWaitForExistingCloseTask"),
|
||||||
"[Sckt {SocketId}] CloseAsync() socket not open");
|
"[Sckt {SocketId}] CloseAsync() waiting for existing close task");
|
||||||
|
|
||||||
_closing = LoggerMessage.Define<int>(
|
_closeAsyncSocketNotOpen = LoggerMessage.Define<int>(
|
||||||
LogLevel.Debug,
|
LogLevel.Debug,
|
||||||
new EventId(1011, "Closing"),
|
new EventId(1010, "CloseAsyncSocketNotOpen"),
|
||||||
"[Sckt {SocketId}] closing");
|
"[Sckt {SocketId}] CloseAsync() socket not open");
|
||||||
|
|
||||||
_closed = LoggerMessage.Define<int>(
|
_closing = LoggerMessage.Define<int>(
|
||||||
LogLevel.Debug,
|
LogLevel.Debug,
|
||||||
new EventId(1012, "Closed"),
|
new EventId(1011, "Closing"),
|
||||||
"[Sckt {SocketId}] closed");
|
"[Sckt {SocketId}] closing");
|
||||||
|
|
||||||
_disposing = LoggerMessage.Define<int>(
|
_closed = LoggerMessage.Define<int>(
|
||||||
LogLevel.Debug,
|
LogLevel.Debug,
|
||||||
new EventId(1013, "Disposing"),
|
new EventId(1012, "Closed"),
|
||||||
"[Sckt {SocketId}] disposing");
|
"[Sckt {SocketId}] closed");
|
||||||
|
|
||||||
_disposed = LoggerMessage.Define<int>(
|
_disposing = LoggerMessage.Define<int>(
|
||||||
LogLevel.Trace,
|
LogLevel.Debug,
|
||||||
new EventId(1014, "Disposed"),
|
new EventId(1013, "Disposing"),
|
||||||
"[Sckt {SocketId}] disposed");
|
"[Sckt {SocketId}] disposing");
|
||||||
|
|
||||||
_sentBytes = LoggerMessage.Define<int, int, int>(
|
_disposed = LoggerMessage.Define<int>(
|
||||||
LogLevel.Trace,
|
LogLevel.Trace,
|
||||||
new EventId(1016, "SentBytes"),
|
new EventId(1014, "Disposed"),
|
||||||
"[Sckt {SocketId}] [Req {RequestId}] sent {NumBytes} bytes");
|
"[Sckt {SocketId}] disposed");
|
||||||
|
|
||||||
_sendLoopStoppedWithException = LoggerMessage.Define<int, string>(
|
_sentBytes = LoggerMessage.Define<int, int, int>(
|
||||||
LogLevel.Warning,
|
LogLevel.Trace,
|
||||||
new EventId(1017, "SendLoopStoppedWithException"),
|
new EventId(1016, "SentBytes"),
|
||||||
"[Sckt {SocketId}] send loop stopped with exception: {ErrorMessage}");
|
"[Sckt {SocketId}] [Req {RequestId}] sent {NumBytes} bytes");
|
||||||
|
|
||||||
_sendLoopFinished = LoggerMessage.Define<int>(
|
_sendLoopStoppedWithException = LoggerMessage.Define<int, string>(
|
||||||
LogLevel.Debug,
|
LogLevel.Warning,
|
||||||
new EventId(1018, "SendLoopFinished"),
|
new EventId(1017, "SendLoopStoppedWithException"),
|
||||||
"[Sckt {SocketId}] send loop finished");
|
"[Sckt {SocketId}] send loop stopped with exception: {ErrorMessage}");
|
||||||
|
|
||||||
_receivedCloseMessage = LoggerMessage.Define<int, string, string>(
|
_sendLoopFinished = LoggerMessage.Define<int>(
|
||||||
LogLevel.Debug,
|
LogLevel.Debug,
|
||||||
new EventId(1019, "ReceivedCloseMessage"),
|
new EventId(1018, "SendLoopFinished"),
|
||||||
"[Sckt {SocketId}] received `Close` message, CloseStatus: {CloseStatus}, CloseStatusDescription: {CloseStatusDescription}");
|
"[Sckt {SocketId}] send loop finished");
|
||||||
|
|
||||||
_receivedPartialMessage = LoggerMessage.Define<int, int>(
|
_receivedCloseMessage = LoggerMessage.Define<int, string, string>(
|
||||||
LogLevel.Trace,
|
LogLevel.Debug,
|
||||||
new EventId(1020, "ReceivedPartialMessage"),
|
new EventId(1019, "ReceivedCloseMessage"),
|
||||||
"[Sckt {SocketId}] received {NumBytes} bytes in partial message");
|
"[Sckt {SocketId}] received `Close` message, CloseStatus: {CloseStatus}, CloseStatusDescription: {CloseStatusDescription}");
|
||||||
|
|
||||||
_receivedSingleMessage = LoggerMessage.Define<int, int>(
|
_receivedPartialMessage = LoggerMessage.Define<int, int>(
|
||||||
LogLevel.Trace,
|
LogLevel.Trace,
|
||||||
new EventId(1021, "ReceivedSingleMessage"),
|
new EventId(1020, "ReceivedPartialMessage"),
|
||||||
"[Sckt {SocketId}] received {NumBytes} bytes in single message");
|
"[Sckt {SocketId}] received {NumBytes} bytes in partial message");
|
||||||
|
|
||||||
_reassembledMessage = LoggerMessage.Define<int, long>(
|
_receivedSingleMessage = LoggerMessage.Define<int, int>(
|
||||||
LogLevel.Trace,
|
LogLevel.Trace,
|
||||||
new EventId(1022, "ReassembledMessage"),
|
new EventId(1021, "ReceivedSingleMessage"),
|
||||||
"[Sckt {SocketId}] reassembled message of {NumBytes} bytes");
|
"[Sckt {SocketId}] received {NumBytes} bytes in single message");
|
||||||
|
|
||||||
_discardIncompleteMessage = LoggerMessage.Define<int, long>(
|
_reassembledMessage = LoggerMessage.Define<int, long>(
|
||||||
LogLevel.Trace,
|
LogLevel.Trace,
|
||||||
new EventId(1023, "DiscardIncompleteMessage"),
|
new EventId(1022, "ReassembledMessage"),
|
||||||
"[Sckt {SocketId}] discarding incomplete message of {NumBytes} bytes");
|
"[Sckt {SocketId}] reassembled message of {NumBytes} bytes");
|
||||||
|
|
||||||
_receiveLoopStoppedWithException = LoggerMessage.Define<int>(
|
_discardIncompleteMessage = LoggerMessage.Define<int, long>(
|
||||||
LogLevel.Error,
|
LogLevel.Trace,
|
||||||
new EventId(1024, "ReceiveLoopStoppedWithException"),
|
new EventId(1023, "DiscardIncompleteMessage"),
|
||||||
"[Sckt {SocketId}] receive loop stopped with exception");
|
"[Sckt {SocketId}] discarding incomplete message of {NumBytes} bytes");
|
||||||
|
|
||||||
_receiveLoopFinished = LoggerMessage.Define<int>(
|
_receiveLoopStoppedWithException = LoggerMessage.Define<int>(
|
||||||
LogLevel.Debug,
|
LogLevel.Error,
|
||||||
new EventId(1025, "ReceiveLoopFinished"),
|
new EventId(1024, "ReceiveLoopStoppedWithException"),
|
||||||
"[Sckt {SocketId}] receive loop finished");
|
"[Sckt {SocketId}] receive loop stopped with exception");
|
||||||
|
|
||||||
_startingTaskForNoDataReceivedCheck = LoggerMessage.Define<int, TimeSpan?>(
|
_receiveLoopFinished = LoggerMessage.Define<int>(
|
||||||
LogLevel.Debug,
|
LogLevel.Debug,
|
||||||
new EventId(1026, "StartingTaskForNoDataReceivedCheck"),
|
new EventId(1025, "ReceiveLoopFinished"),
|
||||||
"[Sckt {SocketId}] starting task checking for no data received for {Timeout}");
|
"[Sckt {SocketId}] receive loop finished");
|
||||||
|
|
||||||
_noDataReceiveTimeoutReconnect = LoggerMessage.Define<int, TimeSpan?>(
|
_startingTaskForNoDataReceivedCheck = LoggerMessage.Define<int, TimeSpan?>(
|
||||||
LogLevel.Warning,
|
LogLevel.Debug,
|
||||||
new EventId(1027, "NoDataReceiveTimeoutReconnect"),
|
new EventId(1026, "StartingTaskForNoDataReceivedCheck"),
|
||||||
"[Sckt {SocketId}] no data received for {Timeout}, reconnecting socket");
|
"[Sckt {SocketId}] starting task checking for no data received for {Timeout}");
|
||||||
|
|
||||||
_receivedCloseConfirmation = LoggerMessage.Define<int, string, string>(
|
_noDataReceiveTimeoutReconnect = LoggerMessage.Define<int, TimeSpan?>(
|
||||||
LogLevel.Debug,
|
LogLevel.Warning,
|
||||||
new EventId(1028, "ReceivedCloseMessage"),
|
new EventId(1027, "NoDataReceiveTimeoutReconnect"),
|
||||||
"[Sckt {SocketId}] received `Close` message confirming our close request, CloseStatus: {CloseStatus}, CloseStatusDescription: {CloseStatusDescription}");
|
"[Sckt {SocketId}] no data received for {Timeout}, reconnecting socket");
|
||||||
|
|
||||||
_socketProcessingStateChanged = LoggerMessage.Define<int, string, string>(
|
_receivedCloseConfirmation = LoggerMessage.Define<int, string, string>(
|
||||||
LogLevel.Trace,
|
LogLevel.Debug,
|
||||||
new EventId(1029, "SocketProcessingStateChanged"),
|
new EventId(1028, "ReceivedCloseMessage"),
|
||||||
"[Sckt {Id}] processing state change: {PreviousState} -> {NewState}");
|
"[Sckt {SocketId}] received `Close` message confirming our close request, CloseStatus: {CloseStatus}, CloseStatusDescription: {CloseStatusDescription}");
|
||||||
|
|
||||||
_socketPingTimeout = LoggerMessage.Define<int>(
|
_socketProcessingStateChanged = LoggerMessage.Define<int, string, string>(
|
||||||
LogLevel.Warning,
|
LogLevel.Trace,
|
||||||
new EventId(1030, "SocketPingTimeout"),
|
new EventId(1029, "SocketProcessingStateChanged"),
|
||||||
"[Sckt {Id}] ping frame timeout; reconnecting socket");
|
"[Sckt {Id}] processing state change: {PreviousState} -> {NewState}");
|
||||||
|
|
||||||
_connectingCanceled = LoggerMessage.Define<int>(
|
_socketPingTimeout = LoggerMessage.Define<int>(
|
||||||
LogLevel.Debug,
|
LogLevel.Warning,
|
||||||
new EventId(1031, "ConnectingCanceled"),
|
new EventId(1030, "SocketPingTimeout"),
|
||||||
"[Sckt {SocketId}] connecting canceled");
|
"[Sckt {Id}] ping frame timeout; reconnecting socket");
|
||||||
|
|
||||||
|
_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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,78 +1,79 @@
|
|||||||
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
|
|
||||||
public static class RateLimitGateLoggingExtensions
|
|
||||||
{
|
{
|
||||||
private static readonly Action<ILogger, int, string, string, string, Exception?> _rateLimitRequestFailed;
|
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
|
||||||
private static readonly Action<ILogger, int, string, string, Exception?> _rateLimitConnectionFailed;
|
public static class RateLimitGateLoggingExtensions
|
||||||
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()
|
|
||||||
{
|
{
|
||||||
_rateLimitRequestFailed = LoggerMessage.Define<int, string, string, string>(
|
private static readonly Action<ILogger, int, string, string, string, Exception?> _rateLimitRequestFailed;
|
||||||
LogLevel.Warning,
|
private static readonly Action<ILogger, int, string, string, Exception?> _rateLimitConnectionFailed;
|
||||||
new EventId(6000, "RateLimitRequestFailed"),
|
private static readonly Action<ILogger, int, string, TimeSpan, string, string, Exception?> _rateLimitDelayingRequest;
|
||||||
"[Req {Id}] Call to {Path} failed because of ratelimit guard {Guard}; {Limit}");
|
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;
|
||||||
|
|
||||||
_rateLimitConnectionFailed = LoggerMessage.Define<int, string, string>(
|
static RateLimitGateLoggingExtensions()
|
||||||
LogLevel.Warning,
|
{
|
||||||
new EventId(6001, "RateLimitConnectionFailed"),
|
_rateLimitRequestFailed = LoggerMessage.Define<int, string, string, string>(
|
||||||
"[Sckt {Id}] Connection failed because of ratelimit guard {Guard}; {Limit}");
|
LogLevel.Warning,
|
||||||
|
new EventId(6000, "RateLimitRequestFailed"),
|
||||||
|
"[Req {Id}] Call to {Path} failed because of ratelimit guard {Guard}; {Limit}");
|
||||||
|
|
||||||
_rateLimitDelayingRequest = LoggerMessage.Define<int, string, TimeSpan, string, string>(
|
_rateLimitConnectionFailed = LoggerMessage.Define<int, string, string>(
|
||||||
LogLevel.Warning,
|
LogLevel.Warning,
|
||||||
new EventId(6002, "RateLimitDelayingRequest"),
|
new EventId(6001, "RateLimitConnectionFailed"),
|
||||||
"[Req {Id}] Delaying call to {Path} by {Delay} because of ratelimit guard {Guard}; {Limit}");
|
"[Sckt {Id}] Connection failed because of ratelimit guard {Guard}; {Limit}");
|
||||||
|
|
||||||
_rateLimitDelayingConnection = LoggerMessage.Define<int, TimeSpan, string, string>(
|
_rateLimitDelayingRequest = LoggerMessage.Define<int, string, TimeSpan, string, string>(
|
||||||
LogLevel.Warning,
|
LogLevel.Warning,
|
||||||
new EventId(6003, "RateLimitDelayingConnection"),
|
new EventId(6002, "RateLimitDelayingRequest"),
|
||||||
"[Sckt {Id}] Delaying connection by {Delay} because of ratelimit guard {Guard}; {Limit}");
|
"[Req {Id}] Delaying call to {Path} by {Delay} because of ratelimit guard {Guard}; {Limit}");
|
||||||
|
|
||||||
_rateLimitAppliedConnection = LoggerMessage.Define<int, string, string, int>(
|
_rateLimitDelayingConnection = LoggerMessage.Define<int, TimeSpan, string, string>(
|
||||||
LogLevel.Trace,
|
LogLevel.Warning,
|
||||||
new EventId(6004, "RateLimitDelayingConnection"),
|
new EventId(6003, "RateLimitDelayingConnection"),
|
||||||
"[Sckt {Id}] Connection passed ratelimit guard {Guard}; {Limit}, New count: {Current}");
|
"[Sckt {Id}] Delaying connection by {Delay} because of ratelimit guard {Guard}; {Limit}");
|
||||||
|
|
||||||
_rateLimitAppliedRequest = LoggerMessage.Define<int, string, string, string, int>(
|
_rateLimitAppliedConnection = LoggerMessage.Define<int, string, string, int>(
|
||||||
LogLevel.Trace,
|
LogLevel.Trace,
|
||||||
new EventId(6005, "RateLimitAppliedRequest"),
|
new EventId(6004, "RateLimitDelayingConnection"),
|
||||||
"[Req {Id}] Call to {Path} passed ratelimit guard {Guard}; {Limit}, New count: {Current}");
|
"[Sckt {Id}] Connection passed ratelimit guard {Guard}; {Limit}, New count: {Current}");
|
||||||
}
|
|
||||||
|
|
||||||
public static void RateLimitRequestFailed(this ILogger logger, int requestId, string path, string guard, string limit)
|
_rateLimitAppliedRequest = LoggerMessage.Define<int, string, string, string, int>(
|
||||||
{
|
LogLevel.Trace,
|
||||||
_rateLimitRequestFailed(logger, requestId, path, guard, limit, null);
|
new EventId(6005, "RateLimitAppliedRequest"),
|
||||||
}
|
"[Req {Id}] Call to {Path} passed ratelimit guard {Guard}; {Limit}, New count: {Current}");
|
||||||
|
}
|
||||||
|
|
||||||
public static void RateLimitConnectionFailed(this ILogger logger, int connectionId, string guard, string limit)
|
public static void RateLimitRequestFailed(this ILogger logger, int requestId, string path, string guard, string limit)
|
||||||
{
|
{
|
||||||
_rateLimitConnectionFailed(logger, connectionId, guard, limit, null);
|
_rateLimitRequestFailed(logger, requestId, path, guard, limit, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void RateLimitDelayingRequest(this ILogger logger, int requestId, string path, TimeSpan delay, string guard, string limit)
|
public static void RateLimitConnectionFailed(this ILogger logger, int connectionId, string guard, string limit)
|
||||||
{
|
{
|
||||||
_rateLimitDelayingRequest(logger, requestId, path, delay, guard, limit, null);
|
_rateLimitConnectionFailed(logger, connectionId, guard, limit, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void RateLimitDelayingConnection(this ILogger logger, int connectionId, TimeSpan delay, string guard, string limit)
|
public static void RateLimitDelayingRequest(this ILogger logger, int requestId, string path, TimeSpan delay, string guard, string limit)
|
||||||
{
|
{
|
||||||
_rateLimitDelayingConnection(logger, connectionId, delay, guard, limit, null);
|
_rateLimitDelayingRequest(logger, requestId, path, delay, guard, limit, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void RateLimitAppliedConnection(this ILogger logger, int connectionId, string guard, string limit, int current)
|
public static void RateLimitDelayingConnection(this ILogger logger, int connectionId, TimeSpan delay, string guard, string limit)
|
||||||
{
|
{
|
||||||
_rateLimitAppliedConnection(logger, connectionId, guard, limit, current, null);
|
_rateLimitDelayingConnection(logger, connectionId, delay, guard, limit, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void RateLimitAppliedRequest(this ILogger logger, int requestIdId, string path, string guard, string limit, int current)
|
public static void RateLimitAppliedConnection(this ILogger logger, int connectionId, string guard, string limit, int current)
|
||||||
{
|
{
|
||||||
_rateLimitAppliedRequest(logger, requestIdId, path, guard, limit, current, null);
|
_rateLimitAppliedConnection(logger, connectionId, guard, limit, current, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void RateLimitAppliedRequest(this ILogger logger, int requestIdId, string path, string guard, string limit, int current)
|
||||||
|
{
|
||||||
|
_rateLimitAppliedRequest(logger, requestIdId, path, guard, limit, current, null);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,158 +1,159 @@
|
|||||||
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
|
|
||||||
public static class RestApiClientLoggingExtensions
|
|
||||||
{
|
{
|
||||||
private static readonly Action<ILogger, int?, int?, long, string?, string?, Exception?> _restApiErrorReceived;
|
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
|
||||||
private static readonly Action<ILogger, int?, int?, long, string?, Exception?> _restApiResponseReceived;
|
public static class RestApiClientLoggingExtensions
|
||||||
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()
|
|
||||||
{
|
{
|
||||||
_restApiErrorReceived = LoggerMessage.Define<int?, int?, long, string?, string?>(
|
private static readonly Action<ILogger, int?, int?, long, string?, string?, Exception?> _restApiErrorReceived;
|
||||||
LogLevel.Warning,
|
private static readonly Action<ILogger, int?, int?, long, string?, Exception?> _restApiResponseReceived;
|
||||||
new EventId(4000, "RestApiErrorReceived"),
|
private static readonly Action<ILogger, int, string, Exception?> _restApiFailedToSyncTime;
|
||||||
"[Req {RequestId}] {ResponseStatusCode} - Error received in {ResponseTime}ms: {ErrorMessage}, Data: {OriginalData}");
|
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;
|
||||||
|
|
||||||
_restApiResponseReceived = LoggerMessage.Define<int?, int?, long, string?>(
|
static RestApiClientLoggingExtensions()
|
||||||
LogLevel.Debug,
|
{
|
||||||
new EventId(4001, "RestApiResponseReceived"),
|
_restApiErrorReceived = LoggerMessage.Define<int?, int?, long, string?, string?>(
|
||||||
"[Req {RequestId}] {ResponseStatusCode} - Response received in {ResponseTime}ms: {OriginalData}");
|
LogLevel.Warning,
|
||||||
|
new EventId(4000, "RestApiErrorReceived"),
|
||||||
|
"[Req {RequestId}] {ResponseStatusCode} - Error received in {ResponseTime}ms: {ErrorMessage}, Data: {OriginalData}");
|
||||||
|
|
||||||
_restApiFailedToSyncTime = LoggerMessage.Define<int, string>(
|
_restApiResponseReceived = LoggerMessage.Define<int?, int?, long, string?>(
|
||||||
LogLevel.Debug,
|
LogLevel.Debug,
|
||||||
new EventId(4002, "RestApiFailedToSyncTime"),
|
new EventId(4001, "RestApiResponseReceived"),
|
||||||
"[Req {RequestId}] Failed to sync time, aborting request: {ErrorMessage}");
|
"[Req {RequestId}] {ResponseStatusCode} - Response received in {ResponseTime}ms: {OriginalData}");
|
||||||
|
|
||||||
_restApiNoApiCredentials = LoggerMessage.Define<int, string>(
|
_restApiFailedToSyncTime = LoggerMessage.Define<int, string>(
|
||||||
LogLevel.Warning,
|
LogLevel.Debug,
|
||||||
new EventId(4003, "RestApiNoApiCredentials"),
|
new EventId(4002, "RestApiFailedToSyncTime"),
|
||||||
"[Req {RequestId}] Request {RestApiUri} failed because no ApiCredentials were provided");
|
"[Req {RequestId}] Failed to sync time, aborting request: {ErrorMessage}");
|
||||||
|
|
||||||
_restApiCreatingRequest = LoggerMessage.Define<int, Uri>(
|
_restApiNoApiCredentials = LoggerMessage.Define<int, string>(
|
||||||
LogLevel.Information,
|
LogLevel.Warning,
|
||||||
new EventId(4004, "RestApiCreatingRequest"),
|
new EventId(4003, "RestApiNoApiCredentials"),
|
||||||
"[Req {RequestId}] Creating request for {RestApiUri}");
|
"[Req {RequestId}] Request {RestApiUri} failed because no ApiCredentials were provided");
|
||||||
|
|
||||||
_restApiSendingRequest = LoggerMessage.Define<int, HttpMethod, string, Uri, string>(
|
_restApiCreatingRequest = LoggerMessage.Define<int, Uri>(
|
||||||
LogLevel.Trace,
|
LogLevel.Information,
|
||||||
new EventId(4005, "RestApiSendingRequest"),
|
new EventId(4004, "RestApiCreatingRequest"),
|
||||||
"[Req {RequestId}] Sending {Method} {Signed} request to {RestApiUri}{Query}");
|
"[Req {RequestId}] Creating request for {RestApiUri}");
|
||||||
|
|
||||||
_restApiRateLimitRetry = LoggerMessage.Define<int, DateTime>(
|
_restApiSendingRequest = LoggerMessage.Define<int, HttpMethod, string, Uri, string>(
|
||||||
LogLevel.Warning,
|
LogLevel.Trace,
|
||||||
new EventId(4006, "RestApiRateLimitRetry"),
|
new EventId(4005, "RestApiSendingRequest"),
|
||||||
"[Req {RequestId}] Received ratelimit error, retrying after {Timestamp}");
|
"[Req {RequestId}] Sending {Method} {Signed} request to {RestApiUri}{Query}");
|
||||||
|
|
||||||
_restApiRateLimitPauseUntil = LoggerMessage.Define<int, DateTime>(
|
_restApiRateLimitRetry = LoggerMessage.Define<int, DateTime>(
|
||||||
LogLevel.Warning,
|
LogLevel.Warning,
|
||||||
new EventId(4007, "RestApiRateLimitPauseUntil"),
|
new EventId(4006, "RestApiRateLimitRetry"),
|
||||||
"[Req {RequestId}] Ratelimit error from server, pausing requests until {Until}");
|
"[Req {RequestId}] Received ratelimit error, retrying after {Timestamp}");
|
||||||
|
|
||||||
_restApiSendRequest = LoggerMessage.Define<int, RequestDefinition, string?, string, string>(
|
_restApiRateLimitPauseUntil = LoggerMessage.Define<int, DateTime>(
|
||||||
LogLevel.Debug,
|
LogLevel.Warning,
|
||||||
new EventId(4008, "RestApiSendRequest"),
|
new EventId(4007, "RestApiRateLimitPauseUntil"),
|
||||||
"[Req {RequestId}] Sending {Definition} request with body {Body}, query parameters {Query} and headers {Headers}");
|
"[Req {RequestId}] Ratelimit error from server, pausing requests until {Until}");
|
||||||
|
|
||||||
_restApiCheckingCache = LoggerMessage.Define<string>(
|
_restApiSendRequest = LoggerMessage.Define<int, RequestDefinition, string?, string, string>(
|
||||||
LogLevel.Trace,
|
LogLevel.Debug,
|
||||||
new EventId(4009, "RestApiCheckingCache"),
|
new EventId(4008, "RestApiSendRequest"),
|
||||||
"Checking cache for key {Key}");
|
"[Req {RequestId}] Sending {Definition} request with body {Body}, query parameters {Query} and headers {Headers}");
|
||||||
|
|
||||||
_restApiCacheHit = LoggerMessage.Define<string>(
|
_restApiCheckingCache = LoggerMessage.Define<string>(
|
||||||
LogLevel.Trace,
|
LogLevel.Trace,
|
||||||
new EventId(4010, "RestApiCacheHit"),
|
new EventId(4009, "RestApiCheckingCache"),
|
||||||
"Cache hit for key {Key}");
|
"Checking cache for key {Key}");
|
||||||
|
|
||||||
_restApiCacheNotHit = LoggerMessage.Define<string>(
|
_restApiCacheHit = LoggerMessage.Define<string>(
|
||||||
LogLevel.Trace,
|
LogLevel.Trace,
|
||||||
new EventId(4011, "RestApiCacheNotHit"),
|
new EventId(4010, "RestApiCacheHit"),
|
||||||
"Cache not hit for key {Key}");
|
"Cache hit for key {Key}");
|
||||||
|
|
||||||
_restApiCancellationRequested = LoggerMessage.Define<int?>(
|
_restApiCacheNotHit = LoggerMessage.Define<string>(
|
||||||
LogLevel.Debug,
|
LogLevel.Trace,
|
||||||
new EventId(4012, "RestApiCancellationRequested"),
|
new EventId(4011, "RestApiCacheNotHit"),
|
||||||
"[Req {RequestId}] Request cancelled by user");
|
"Cache not hit for key {Key}");
|
||||||
|
|
||||||
}
|
_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 RestApiResponseReceived(this ILogger logger, int? requestId, HttpStatusCode? responseStatusCode, long responseTime, string? originalData)
|
public static void RestApiErrorReceived(this ILogger logger, int? requestId, HttpStatusCode? responseStatusCode, long responseTime, string? error, string? originalData, Exception? exception)
|
||||||
{
|
{
|
||||||
_restApiResponseReceived(logger, requestId, (int?)responseStatusCode, responseTime, originalData, null);
|
_restApiErrorReceived(logger, requestId, (int?)responseStatusCode, responseTime, error, originalData, exception);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void RestApiFailedToSyncTime(this ILogger logger, int requestId, string error)
|
public static void RestApiResponseReceived(this ILogger logger, int? requestId, HttpStatusCode? responseStatusCode, long responseTime, string? originalData)
|
||||||
{
|
{
|
||||||
_restApiFailedToSyncTime(logger, requestId, error, null);
|
_restApiResponseReceived(logger, requestId, (int?)responseStatusCode, responseTime, originalData, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void RestApiNoApiCredentials(this ILogger logger, int requestId, string uri)
|
public static void RestApiFailedToSyncTime(this ILogger logger, int requestId, string error)
|
||||||
{
|
{
|
||||||
_restApiNoApiCredentials(logger, requestId, uri, null);
|
_restApiFailedToSyncTime(logger, requestId, error, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void RestApiCreatingRequest(this ILogger logger, int requestId, Uri uri)
|
public static void RestApiNoApiCredentials(this ILogger logger, int requestId, string uri)
|
||||||
{
|
{
|
||||||
_restApiCreatingRequest(logger, requestId, uri, null);
|
_restApiNoApiCredentials(logger, requestId, uri, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void RestApiSendingRequest(this ILogger logger, int requestId, HttpMethod method, string signed, Uri uri, string paramString)
|
public static void RestApiCreatingRequest(this ILogger logger, int requestId, Uri uri)
|
||||||
{
|
{
|
||||||
_restApiSendingRequest(logger, requestId, method, signed, uri, paramString, null);
|
_restApiCreatingRequest(logger, requestId, uri, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void RestApiRateLimitRetry(this ILogger logger, int requestId, DateTime retryAfter)
|
public static void RestApiSendingRequest(this ILogger logger, int requestId, HttpMethod method, string signed, Uri uri, string paramString)
|
||||||
{
|
{
|
||||||
_restApiRateLimitRetry(logger, requestId, retryAfter, null);
|
_restApiSendingRequest(logger, requestId, method, signed, uri, paramString, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void RestApiRateLimitPauseUntil(this ILogger logger, int requestId, DateTime retryAfter)
|
public static void RestApiRateLimitRetry(this ILogger logger, int requestId, DateTime retryAfter)
|
||||||
{
|
{
|
||||||
_restApiRateLimitPauseUntil(logger, requestId, retryAfter, null);
|
_restApiRateLimitRetry(logger, requestId, retryAfter, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void RestApiSendRequest(this ILogger logger, int requestId, RequestDefinition definition, string? body, string query, string headers)
|
public static void RestApiRateLimitPauseUntil(this ILogger logger, int requestId, DateTime retryAfter)
|
||||||
{
|
{
|
||||||
_restApiSendRequest(logger, requestId, definition, body, query, headers, null);
|
_restApiRateLimitPauseUntil(logger, requestId, retryAfter, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void CheckingCache(this ILogger logger, string key)
|
public static void RestApiSendRequest(this ILogger logger, int requestId, RequestDefinition definition, string? body, string query, string headers)
|
||||||
{
|
{
|
||||||
_restApiCheckingCache(logger, key, null);
|
_restApiSendRequest(logger, requestId, definition, body, query, headers, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void CacheHit(this ILogger logger, string key)
|
public static void CheckingCache(this ILogger logger, string key)
|
||||||
{
|
{
|
||||||
_restApiCacheHit(logger, key, null);
|
_restApiCheckingCache(logger, key, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void CacheNotHit(this ILogger logger, string key)
|
public static void CacheHit(this ILogger logger, string key)
|
||||||
{
|
{
|
||||||
_restApiCacheNotHit(logger, key, null);
|
_restApiCacheHit(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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,199 +1,221 @@
|
|||||||
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
|
|
||||||
public static class SocketApiClientLoggingExtension
|
|
||||||
{
|
{
|
||||||
private static readonly Action<ILogger, int, Exception?> _failedToAddSubscriptionRetryOnDifferentConnection;
|
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
|
||||||
private static readonly Action<ILogger, int, Exception?> _hasBeenPausedCantSubscribeAtThisMoment;
|
public static class SocketApiClientLoggingExtension
|
||||||
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()
|
|
||||||
{
|
{
|
||||||
_failedToAddSubscriptionRetryOnDifferentConnection = LoggerMessage.Define<int>(
|
private static readonly Action<ILogger, int, Exception?> _failedToAddSubscriptionRetryOnDifferentConnection;
|
||||||
LogLevel.Trace,
|
private static readonly Action<ILogger, int, Exception?> _hasBeenPausedCantSubscribeAtThisMoment;
|
||||||
new EventId(3000, "FailedToAddSubscriptionRetryOnDifferentConnection"),
|
private static readonly Action<ILogger, int, string?, Exception?> _failedToSubscribe;
|
||||||
"[Sckt {SocketId}] failed to add subscription, retrying on different connection");
|
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;
|
||||||
|
private static readonly Action<ILogger, Exception?> _timeoutWaitingForReconnectingSocket;
|
||||||
|
private static readonly Action<ILogger, long, Exception?> _waitedForReconnectingSocket;
|
||||||
|
|
||||||
_hasBeenPausedCantSubscribeAtThisMoment = LoggerMessage.Define<int>(
|
static SocketApiClientLoggingExtension()
|
||||||
LogLevel.Warning,
|
{
|
||||||
new EventId(3001, "HasBeenPausedCantSubscribeAtThisMoment"),
|
_failedToAddSubscriptionRetryOnDifferentConnection = LoggerMessage.Define<int>(
|
||||||
"[Sckt {SocketId}] has been paused, can't subscribe at this moment");
|
LogLevel.Trace,
|
||||||
|
new EventId(3000, "FailedToAddSubscriptionRetryOnDifferentConnection"),
|
||||||
|
"[Sckt {SocketId}] failed to add subscription, retrying on different connection");
|
||||||
|
|
||||||
_failedToSubscribe = LoggerMessage.Define<int, string?>(
|
_hasBeenPausedCantSubscribeAtThisMoment = LoggerMessage.Define<int>(
|
||||||
LogLevel.Warning,
|
LogLevel.Warning,
|
||||||
new EventId(3002, "FailedToSubscribe"),
|
new EventId(3001, "HasBeenPausedCantSubscribeAtThisMoment"),
|
||||||
"[Sckt {SocketId}] failed to subscribe: {ErrorMessage}");
|
"[Sckt {SocketId}] has been paused, can't subscribe at this moment");
|
||||||
|
|
||||||
_cancellationTokenSetClosingSubscription = LoggerMessage.Define<int, int>(
|
_failedToSubscribe = LoggerMessage.Define<int, string?>(
|
||||||
LogLevel.Information,
|
LogLevel.Warning,
|
||||||
new EventId(3003, "CancellationTokenSetClosingSubscription"),
|
new EventId(3002, "FailedToSubscribe"),
|
||||||
"[Sckt {SocketId}] Cancellation token set, closing subscription {SubscriptionId}");
|
"[Sckt {SocketId}] failed to subscribe: {ErrorMessage}");
|
||||||
|
|
||||||
_subscriptionCompletedSuccessfully = LoggerMessage.Define<int, int>(
|
_cancellationTokenSetClosingSubscription = LoggerMessage.Define<int, int>(
|
||||||
LogLevel.Information,
|
LogLevel.Information,
|
||||||
new EventId(3004, "SubscriptionCompletedSuccessfully"),
|
new EventId(3003, "CancellationTokenSetClosingSubscription"),
|
||||||
"[Sckt {SocketId}] subscription {SubscriptionId} completed successfully");
|
"[Sckt {SocketId}] Cancellation token set, closing subscription {SubscriptionId}");
|
||||||
|
|
||||||
_hasBeenPausedCantSendQueryAtThisMoment = LoggerMessage.Define<int>(
|
_subscriptionCompletedSuccessfully = LoggerMessage.Define<int, int>(
|
||||||
LogLevel.Warning,
|
LogLevel.Information,
|
||||||
new EventId(3005, "HasBeenPausedCantSendQueryAtThisMoment"),
|
new EventId(3004, "SubscriptionCompletedSuccessfully"),
|
||||||
"[Sckt {SocketId}] has been paused, can't send query at this moment");
|
"[Sckt {SocketId}] subscription {SubscriptionId} completed successfully");
|
||||||
|
|
||||||
_attemptingToAuthenticate = LoggerMessage.Define<int>(
|
_hasBeenPausedCantSendQueryAtThisMoment = LoggerMessage.Define<int>(
|
||||||
LogLevel.Debug,
|
LogLevel.Warning,
|
||||||
new EventId(3006, "AttemptingToAuthenticate"),
|
new EventId(3005, "HasBeenPausedCantSendQueryAtThisMoment"),
|
||||||
"[Sckt {SocketId}] Attempting to authenticate");
|
"[Sckt {SocketId}] has been paused, can't send query at this moment");
|
||||||
|
|
||||||
_authenticationFailed = LoggerMessage.Define<int>(
|
_attemptingToAuthenticate = LoggerMessage.Define<int>(
|
||||||
LogLevel.Warning,
|
LogLevel.Debug,
|
||||||
new EventId(3007, "AuthenticationFailed"),
|
new EventId(3006, "AttemptingToAuthenticate"),
|
||||||
"[Sckt {SocketId}] authentication failed");
|
"[Sckt {SocketId}] Attempting to authenticate");
|
||||||
|
|
||||||
_authenticated = LoggerMessage.Define<int>(
|
_authenticationFailed = LoggerMessage.Define<int>(
|
||||||
LogLevel.Debug,
|
LogLevel.Warning,
|
||||||
new EventId(3008, "Authenticated"),
|
new EventId(3007, "AuthenticationFailed"),
|
||||||
"[Sckt {SocketId}] authenticated");
|
"[Sckt {SocketId}] authentication failed");
|
||||||
|
|
||||||
_failedToDetermineConnectionUrl = LoggerMessage.Define<string?>(
|
_authenticated = LoggerMessage.Define<int>(
|
||||||
LogLevel.Warning,
|
LogLevel.Debug,
|
||||||
new EventId(3009, "FailedToDetermineConnectionUrl"),
|
new EventId(3008, "Authenticated"),
|
||||||
"Failed to determine connection url: {ErrorMessage}");
|
"[Sckt {SocketId}] authenticated");
|
||||||
|
|
||||||
_connectionAddressSetTo = LoggerMessage.Define<string>(
|
_failedToDetermineConnectionUrl = LoggerMessage.Define<string?>(
|
||||||
LogLevel.Debug,
|
LogLevel.Warning,
|
||||||
new EventId(3010, "ConnectionAddressSetTo"),
|
new EventId(3009, "FailedToDetermineConnectionUrl"),
|
||||||
"Connection address set to {ConnectionAddress}");
|
"Failed to determine connection url: {ErrorMessage}");
|
||||||
|
|
||||||
_socketCreatedForAddress = LoggerMessage.Define<int, string>(
|
_connectionAddressSetTo = LoggerMessage.Define<string>(
|
||||||
LogLevel.Debug,
|
LogLevel.Debug,
|
||||||
new EventId(3011, "SocketCreatedForAddress"),
|
new EventId(3010, "ConnectionAddressSetTo"),
|
||||||
"[Sckt {SocketId}] created for {Address}");
|
"Connection address set to {ConnectionAddress}");
|
||||||
|
|
||||||
_unsubscribingAll = LoggerMessage.Define<int>(
|
_socketCreatedForAddress = LoggerMessage.Define<int, string>(
|
||||||
LogLevel.Information,
|
LogLevel.Debug,
|
||||||
new EventId(3013, "UnsubscribingAll"),
|
new EventId(3011, "SocketCreatedForAddress"),
|
||||||
"Unsubscribing all {SubscriptionCount} subscriptions");
|
"[Sckt {SocketId}] created for {Address}");
|
||||||
|
|
||||||
_disposingSocketClient = LoggerMessage.Define(
|
_unsubscribingAll = LoggerMessage.Define<int>(
|
||||||
LogLevel.Debug,
|
LogLevel.Information,
|
||||||
new EventId(3015, "DisposingSocketClient"),
|
new EventId(3013, "UnsubscribingAll"),
|
||||||
"Disposing socket client, closing all subscriptions");
|
"Unsubscribing all {SubscriptionCount} subscriptions");
|
||||||
|
|
||||||
_unsubscribingSubscription = LoggerMessage.Define<int, int>(
|
_disposingSocketClient = LoggerMessage.Define(
|
||||||
LogLevel.Information,
|
LogLevel.Debug,
|
||||||
new EventId(3016, "UnsubscribingSubscription"),
|
new EventId(3015, "DisposingSocketClient"),
|
||||||
"[Sckt {SocketId}] Unsubscribing subscription {SubscriptionId}");
|
"Disposing socket client, closing all subscriptions");
|
||||||
|
|
||||||
_reconnectingAllConnections = LoggerMessage.Define<int>(
|
_unsubscribingSubscription = LoggerMessage.Define<int, int>(
|
||||||
LogLevel.Information,
|
LogLevel.Information,
|
||||||
new EventId(3017, "ReconnectingAll"),
|
new EventId(3016, "UnsubscribingSubscription"),
|
||||||
"Reconnecting all {ConnectionCount} connections");
|
"[Sckt {SocketId}] Unsubscribing subscription {SubscriptionId}");
|
||||||
|
|
||||||
_addingRetryAfterGuard = LoggerMessage.Define<DateTime>(
|
_reconnectingAllConnections = LoggerMessage.Define<int>(
|
||||||
LogLevel.Warning,
|
LogLevel.Information,
|
||||||
new EventId(3018, "AddRetryAfterGuard"),
|
new EventId(3017, "ReconnectingAll"),
|
||||||
"Adding RetryAfterGuard ({RetryAfter}) because the connection attempt was rate limited");
|
"Reconnecting all {ConnectionCount} connections");
|
||||||
}
|
|
||||||
|
|
||||||
public static void FailedToAddSubscriptionRetryOnDifferentConnection(this ILogger logger, int socketId)
|
_addingRetryAfterGuard = LoggerMessage.Define<DateTime>(
|
||||||
{
|
LogLevel.Warning,
|
||||||
_failedToAddSubscriptionRetryOnDifferentConnection(logger, socketId, null);
|
new EventId(3018, "AddRetryAfterGuard"),
|
||||||
}
|
"Adding RetryAfterGuard ({RetryAfter}) because the connection attempt was rate limited");
|
||||||
|
|
||||||
public static void HasBeenPausedCantSubscribeAtThisMoment(this ILogger logger, int socketId)
|
_timeoutWaitingForReconnectingSocket = LoggerMessage.Define(
|
||||||
{
|
LogLevel.Debug,
|
||||||
_hasBeenPausedCantSubscribeAtThisMoment(logger, socketId, null);
|
new EventId(3019, "TimeoutWaitingForReconnectingSocket"),
|
||||||
}
|
"Timeout while waiting for existing socket reconnection, failing request");
|
||||||
|
|
||||||
public static void FailedToSubscribe(this ILogger logger, int socketId, string? error)
|
_waitedForReconnectingSocket = LoggerMessage.Define<long>(
|
||||||
{
|
LogLevel.Trace,
|
||||||
_failedToSubscribe(logger, socketId, error, null);
|
new EventId(3020, "WaitedForReconnectingSocket"),
|
||||||
}
|
"Waited for reconnecting socket for {Timespan}ms");
|
||||||
|
}
|
||||||
|
|
||||||
public static void CancellationTokenSetClosingSubscription(this ILogger logger, int socketId, int subscriptionId)
|
public static void FailedToAddSubscriptionRetryOnDifferentConnection(this ILogger logger, int socketId)
|
||||||
{
|
{
|
||||||
_cancellationTokenSetClosingSubscription(logger, socketId, subscriptionId, null);
|
_failedToAddSubscriptionRetryOnDifferentConnection(logger, socketId, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void SubscriptionCompletedSuccessfully(this ILogger logger, int socketId, int subscriptionId)
|
public static void HasBeenPausedCantSubscribeAtThisMoment(this ILogger logger, int socketId)
|
||||||
{
|
{
|
||||||
_subscriptionCompletedSuccessfully(logger, socketId, subscriptionId, null);
|
_hasBeenPausedCantSubscribeAtThisMoment(logger, socketId, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void HasBeenPausedCantSendQueryAtThisMoment(this ILogger logger, int socketId)
|
public static void FailedToSubscribe(this ILogger logger, int socketId, string? error)
|
||||||
{
|
{
|
||||||
_hasBeenPausedCantSendQueryAtThisMoment(logger, socketId, null);
|
_failedToSubscribe(logger, socketId, error, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void AttemptingToAuthenticate(this ILogger logger, int socketId)
|
public static void CancellationTokenSetClosingSubscription(this ILogger logger, int socketId, int subscriptionId)
|
||||||
{
|
{
|
||||||
_attemptingToAuthenticate(logger, socketId, null);
|
_cancellationTokenSetClosingSubscription(logger, socketId, subscriptionId, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void AuthenticationFailed(this ILogger logger, int socketId)
|
public static void SubscriptionCompletedSuccessfully(this ILogger logger, int socketId, int subscriptionId)
|
||||||
{
|
{
|
||||||
_authenticationFailed(logger, socketId, null);
|
_subscriptionCompletedSuccessfully(logger, socketId, subscriptionId, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void Authenticated(this ILogger logger, int socketId)
|
public static void HasBeenPausedCantSendQueryAtThisMoment(this ILogger logger, int socketId)
|
||||||
{
|
{
|
||||||
_authenticated(logger, socketId, null);
|
_hasBeenPausedCantSendQueryAtThisMoment(logger, socketId, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void FailedToDetermineConnectionUrl(this ILogger logger, string? error)
|
public static void AttemptingToAuthenticate(this ILogger logger, int socketId)
|
||||||
{
|
{
|
||||||
_failedToDetermineConnectionUrl(logger, error, null);
|
_attemptingToAuthenticate(logger, socketId, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void ConnectionAddressSetTo(this ILogger logger, string connectionAddress)
|
public static void AuthenticationFailed(this ILogger logger, int socketId)
|
||||||
{
|
{
|
||||||
_connectionAddressSetTo(logger, connectionAddress, null);
|
_authenticationFailed(logger, socketId, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void SocketCreatedForAddress(this ILogger logger, int socketId, string address)
|
public static void Authenticated(this ILogger logger, int socketId)
|
||||||
{
|
{
|
||||||
_socketCreatedForAddress(logger, socketId, address, null);
|
_authenticated(logger, socketId, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void UnsubscribingAll(this ILogger logger, int subscriptionCount)
|
public static void FailedToDetermineConnectionUrl(this ILogger logger, string? error)
|
||||||
{
|
{
|
||||||
_unsubscribingAll(logger, subscriptionCount, null);
|
_failedToDetermineConnectionUrl(logger, error, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void DisposingSocketClient(this ILogger logger)
|
public static void ConnectionAddressSetTo(this ILogger logger, string connectionAddress)
|
||||||
{
|
{
|
||||||
_disposingSocketClient(logger, null);
|
_connectionAddressSetTo(logger, connectionAddress, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void UnsubscribingSubscription(this ILogger logger, int socketId, int subscriptionId)
|
public static void SocketCreatedForAddress(this ILogger logger, int socketId, string address)
|
||||||
{
|
{
|
||||||
_unsubscribingSubscription(logger, socketId, subscriptionId, null);
|
_socketCreatedForAddress(logger, socketId, address, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void ReconnectingAllConnections(this ILogger logger, int connectionCount)
|
public static void UnsubscribingAll(this ILogger logger, int subscriptionCount)
|
||||||
{
|
{
|
||||||
_reconnectingAllConnections(logger, connectionCount, null);
|
_unsubscribingAll(logger, subscriptionCount, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void AddingRetryAfterGuard(this ILogger logger, DateTime retryAfter)
|
public static void DisposingSocketClient(this ILogger logger)
|
||||||
{
|
{
|
||||||
_addingRetryAfterGuard(logger, retryAfter, null);
|
_disposingSocketClient(logger, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void UnsubscribingSubscription(this ILogger logger, int socketId, int subscriptionId)
|
||||||
|
{
|
||||||
|
_unsubscribingSubscription(logger, socketId, subscriptionId, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void ReconnectingAllConnections(this ILogger logger, int connectionCount)
|
||||||
|
{
|
||||||
|
_reconnectingAllConnections(logger, connectionCount, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void AddingRetryAfterGuard(this ILogger logger, DateTime retryAfter)
|
||||||
|
{
|
||||||
|
_addingRetryAfterGuard(logger, retryAfter, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void TimeoutWaitingForReconnectingSocket(this ILogger logger)
|
||||||
|
{
|
||||||
|
_timeoutWaitingForReconnectingSocket(logger, null);
|
||||||
|
}
|
||||||
|
public static void WaitedForReconnectingSocket(this ILogger logger, long milliseconds)
|
||||||
|
{
|
||||||
|
_waitedForReconnectingSocket(logger, milliseconds, null);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,348 +1,349 @@
|
|||||||
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
|
|
||||||
public static class SocketConnectionLoggingExtension
|
|
||||||
{
|
{
|
||||||
private static readonly Action<ILogger, int, bool, Exception?> _activityPaused;
|
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
|
||||||
private static readonly Action<ILogger, int, Sockets.SocketConnection.SocketStatus, Sockets.SocketConnection.SocketStatus, Exception?> _socketStatusChanged;
|
public static class SocketConnectionLoggingExtension
|
||||||
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()
|
|
||||||
{
|
{
|
||||||
_activityPaused = LoggerMessage.Define<int, bool>(
|
private static readonly Action<ILogger, int, bool, Exception?> _activityPaused;
|
||||||
LogLevel.Information,
|
private static readonly Action<ILogger, int, Sockets.SocketConnection.SocketStatus, Sockets.SocketConnection.SocketStatus, Exception?> _socketStatusChanged;
|
||||||
new EventId(2000, "ActivityPaused"),
|
private static readonly Action<ILogger, int, string?, Exception?> _failedReconnectProcessing;
|
||||||
"[Sckt {SocketId}] paused activity: {Paused}");
|
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;
|
||||||
|
|
||||||
_socketStatusChanged = LoggerMessage.Define<int, Sockets.SocketConnection.SocketStatus, Sockets.SocketConnection.SocketStatus>(
|
static SocketConnectionLoggingExtension()
|
||||||
LogLevel.Debug,
|
{
|
||||||
new EventId(2001, "SocketStatusChanged"),
|
_activityPaused = LoggerMessage.Define<int, bool>(
|
||||||
"[Sckt {SocketId}] status changed from {OldStatus} to {NewStatus}");
|
LogLevel.Information,
|
||||||
|
new EventId(2000, "ActivityPaused"),
|
||||||
|
"[Sckt {SocketId}] paused activity: {Paused}");
|
||||||
|
|
||||||
_failedReconnectProcessing = LoggerMessage.Define<int, string?>(
|
_socketStatusChanged = LoggerMessage.Define<int, Sockets.SocketConnection.SocketStatus, Sockets.SocketConnection.SocketStatus>(
|
||||||
LogLevel.Warning,
|
LogLevel.Debug,
|
||||||
new EventId(2002, "FailedReconnectProcessing"),
|
new EventId(2001, "SocketStatusChanged"),
|
||||||
"[Sckt {SocketId}] failed reconnect processing: {ErrorMessage}, reconnecting again");
|
"[Sckt {SocketId}] status changed from {OldStatus} to {NewStatus}");
|
||||||
|
|
||||||
_unknownExceptionWhileProcessingReconnection = LoggerMessage.Define<int>(
|
_failedReconnectProcessing = LoggerMessage.Define<int, string?>(
|
||||||
LogLevel.Warning,
|
LogLevel.Warning,
|
||||||
new EventId(2003, "UnknownExceptionWhileProcessingReconnection"),
|
new EventId(2002, "FailedReconnectProcessing"),
|
||||||
"[Sckt {SocketId}] Unknown exception while processing reconnection, reconnecting again");
|
"[Sckt {SocketId}] failed reconnect processing: {ErrorMessage}, reconnecting again");
|
||||||
|
|
||||||
_webSocketErrorCodeAndDetails = LoggerMessage.Define<int, WebSocketError, string?>(
|
_unknownExceptionWhileProcessingReconnection = LoggerMessage.Define<int>(
|
||||||
LogLevel.Warning,
|
LogLevel.Warning,
|
||||||
new EventId(2004, "WebSocketErrorCode"),
|
new EventId(2003, "UnknownExceptionWhileProcessingReconnection"),
|
||||||
"[Sckt {SocketId}] error: Websocket error code {WebSocketErrorCode}, details: {Details}");
|
"[Sckt {SocketId}] Unknown exception while processing reconnection, reconnecting again");
|
||||||
|
|
||||||
_webSocketError = LoggerMessage.Define<int, string?>(
|
_webSocketErrorCodeAndDetails = LoggerMessage.Define<int, WebSocketError, string?>(
|
||||||
LogLevel.Warning,
|
LogLevel.Warning,
|
||||||
new EventId(2005, "WebSocketError"),
|
new EventId(2004, "WebSocketErrorCode"),
|
||||||
"[Sckt {SocketId}] error: {ErrorMessage}");
|
"[Sckt {SocketId}] error: Websocket error code {WebSocketErrorCode}, details: {Details}");
|
||||||
|
|
||||||
_messageSentNotPending = LoggerMessage.Define<int, int>(
|
_webSocketError = LoggerMessage.Define<int, string?>(
|
||||||
LogLevel.Debug,
|
LogLevel.Warning,
|
||||||
new EventId(2006, "MessageSentNotPending"),
|
new EventId(2005, "WebSocketError"),
|
||||||
"[Sckt {SocketId}] [Req {RequestId}] message sent, but not pending");
|
"[Sckt {SocketId}] error: {ErrorMessage}");
|
||||||
|
|
||||||
_receivedData = LoggerMessage.Define<int, string>(
|
_messageSentNotPending = LoggerMessage.Define<int, int>(
|
||||||
LogLevel.Trace,
|
LogLevel.Debug,
|
||||||
new EventId(2007, "ReceivedData"),
|
new EventId(2006, "MessageSentNotPending"),
|
||||||
"[Sckt {SocketId}] received {OriginalData}");
|
"[Sckt {SocketId}] [Req {RequestId}] message sent, but not pending");
|
||||||
|
|
||||||
_failedToEvaluateMessage = LoggerMessage.Define<int, string>(
|
_receivedData = LoggerMessage.Define<int, string>(
|
||||||
LogLevel.Warning,
|
LogLevel.Trace,
|
||||||
new EventId(2008, "FailedToEvaluateMessage"),
|
new EventId(2007, "ReceivedData"),
|
||||||
"[Sckt {SocketId}] failed to evaluate message. {OriginalData}");
|
"[Sckt {SocketId}] received {OriginalData}");
|
||||||
|
|
||||||
_errorProcessingMessage = LoggerMessage.Define<int>(
|
_failedToEvaluateMessage = LoggerMessage.Define<int, string>(
|
||||||
LogLevel.Error,
|
LogLevel.Warning,
|
||||||
new EventId(2009, "ErrorProcessingMessage"),
|
new EventId(2008, "FailedToEvaluateMessage"),
|
||||||
"[Sckt {SocketId}] error processing message");
|
"[Sckt {SocketId}] failed to evaluate message. {OriginalData}");
|
||||||
|
|
||||||
_receivedMessageNotRecognized = LoggerMessage.Define<int, int>(
|
_errorProcessingMessage = LoggerMessage.Define<int>(
|
||||||
LogLevel.Warning,
|
LogLevel.Error,
|
||||||
new EventId(2011, "ReceivedMessageNotRecognized"),
|
new EventId(2009, "ErrorProcessingMessage"),
|
||||||
"[Sckt {SocketId}] received message not recognized by handler {ProcessorId}");
|
"[Sckt {SocketId}] error processing message");
|
||||||
|
|
||||||
_failedToDeserializeMessage = LoggerMessage.Define<int, string?>(
|
_receivedMessageNotRecognized = LoggerMessage.Define<int, int>(
|
||||||
LogLevel.Warning,
|
LogLevel.Warning,
|
||||||
new EventId(2012, "FailedToDeserializeMessage"),
|
new EventId(2011, "ReceivedMessageNotRecognized"),
|
||||||
"[Sckt {SocketId}] deserialization failed: {ErrorMessage}");
|
"[Sckt {SocketId}] received message not recognized by handler {ProcessorId}");
|
||||||
|
|
||||||
_userMessageProcessingFailed = LoggerMessage.Define<int, string>(
|
_failedToDeserializeMessage = LoggerMessage.Define<int, string?>(
|
||||||
LogLevel.Warning,
|
LogLevel.Warning,
|
||||||
new EventId(2013, "UserMessageProcessingFailed"),
|
new EventId(2012, "FailedToDeserializeMessage"),
|
||||||
"[Sckt {SocketId}] user message processing failed: {ErrorMessage}");
|
"[Sckt {SocketId}] deserialization failed: {ErrorMessage}");
|
||||||
|
|
||||||
_messageProcessed = LoggerMessage.Define<int, long, long>(
|
_userMessageProcessingFailed = LoggerMessage.Define<int, string>(
|
||||||
LogLevel.Trace,
|
LogLevel.Warning,
|
||||||
new EventId(2014, "MessageProcessed"),
|
new EventId(2013, "UserMessageProcessingFailed"),
|
||||||
"[Sckt {SocketId}] message processed in {ProcessingTime}ms, {ParsingTime}ms parsing");
|
"[Sckt {SocketId}] user message processing failed: {ErrorMessage}");
|
||||||
|
|
||||||
_closingSubscription = LoggerMessage.Define<int, int>(
|
_messageProcessed = LoggerMessage.Define<int, long, long>(
|
||||||
LogLevel.Debug,
|
LogLevel.Trace,
|
||||||
new EventId(2015, "ClosingSubscription"),
|
new EventId(2014, "MessageProcessed"),
|
||||||
"[Sckt {SocketId}] closing subscription {SubscriptionId}");
|
"[Sckt {SocketId}] message processed in {ProcessingTime}ms, {ParsingTime}ms parsing");
|
||||||
|
|
||||||
_notUnsubscribingSubscriptionBecauseDuplicateRunning = LoggerMessage.Define<int>(
|
_closingSubscription = LoggerMessage.Define<int, int>(
|
||||||
LogLevel.Debug,
|
LogLevel.Debug,
|
||||||
new EventId(2016, "NotUnsubscribingSubscription"),
|
new EventId(2015, "ClosingSubscription"),
|
||||||
"[Sckt {SocketId}] not unsubscribing subscription as there is still a duplicate subscription running");
|
"[Sckt {SocketId}] closing subscription {SubscriptionId}");
|
||||||
|
|
||||||
_alreadyClosing = LoggerMessage.Define<int>(
|
_notUnsubscribingSubscriptionBecauseDuplicateRunning = LoggerMessage.Define<int>(
|
||||||
LogLevel.Debug,
|
LogLevel.Debug,
|
||||||
new EventId(2017, "AlreadyClosing"),
|
new EventId(2016, "NotUnsubscribingSubscription"),
|
||||||
"[Sckt {SocketId}] already closing");
|
"[Sckt {SocketId}] not unsubscribing subscription as there is still a duplicate subscription running");
|
||||||
|
|
||||||
_closingNoMoreSubscriptions = LoggerMessage.Define<int>(
|
_alreadyClosing = LoggerMessage.Define<int>(
|
||||||
LogLevel.Debug,
|
LogLevel.Debug,
|
||||||
new EventId(2018, "ClosingNoMoreSubscriptions"),
|
new EventId(2017, "AlreadyClosing"),
|
||||||
"[Sckt {SocketId}] closing as there are no more subscriptions");
|
"[Sckt {SocketId}] already closing");
|
||||||
|
|
||||||
_addingNewSubscription = LoggerMessage.Define<int, int, int>(
|
_closingNoMoreSubscriptions = LoggerMessage.Define<int>(
|
||||||
LogLevel.Debug,
|
LogLevel.Debug,
|
||||||
new EventId(2019, "AddingNewSubscription"),
|
new EventId(2018, "ClosingNoMoreSubscriptions"),
|
||||||
"[Sckt {SocketId}] adding new subscription with id {SubscriptionId}, total subscriptions on connection: {UserSubscriptionCount}");
|
"[Sckt {SocketId}] closing as there are no more subscriptions");
|
||||||
|
|
||||||
_nothingToResubscribeCloseConnection = LoggerMessage.Define<int>(
|
_addingNewSubscription = LoggerMessage.Define<int, int, int>(
|
||||||
LogLevel.Debug,
|
LogLevel.Debug,
|
||||||
new EventId(2020, "NothingToResubscribe"),
|
new EventId(2019, "AddingNewSubscription"),
|
||||||
"[Sckt {SocketId}] nothing to resubscribe, closing connection");
|
"[Sckt {SocketId}] adding new subscription with id {SubscriptionId}, total subscriptions on connection: {UserSubscriptionCount}");
|
||||||
|
|
||||||
_failedAuthenticationDisconnectAndReconnect = LoggerMessage.Define<int>(
|
_nothingToResubscribeCloseConnection = LoggerMessage.Define<int>(
|
||||||
LogLevel.Warning,
|
LogLevel.Debug,
|
||||||
new EventId(2021, "FailedAuthentication"),
|
new EventId(2020, "NothingToResubscribe"),
|
||||||
"[Sckt {SocketId}] authentication failed on reconnected socket. Disconnecting and reconnecting");
|
"[Sckt {SocketId}] nothing to resubscribe, closing connection");
|
||||||
|
|
||||||
_authenticationSucceeded = LoggerMessage.Define<int>(
|
_failedAuthenticationDisconnectAndReconnect = LoggerMessage.Define<int>(
|
||||||
LogLevel.Debug,
|
LogLevel.Warning,
|
||||||
new EventId(2022, "AuthenticationSucceeded"),
|
new EventId(2021, "FailedAuthentication"),
|
||||||
"[Sckt {SocketId}] authentication succeeded on reconnected socket");
|
"[Sckt {SocketId}] authentication failed on reconnected socket. Disconnecting and reconnecting");
|
||||||
|
|
||||||
_failedRequestRevitalization = LoggerMessage.Define<int, string?>(
|
_authenticationSucceeded = LoggerMessage.Define<int>(
|
||||||
LogLevel.Warning,
|
LogLevel.Debug,
|
||||||
new EventId(2023, "FailedRequestRevitalization"),
|
new EventId(2022, "AuthenticationSucceeded"),
|
||||||
"[Sckt {SocketId}] failed request revitalization: {ErrorMessage}");
|
"[Sckt {SocketId}] authentication succeeded on reconnected socket");
|
||||||
|
|
||||||
_allSubscriptionResubscribed = LoggerMessage.Define<int>(
|
_failedRequestRevitalization = LoggerMessage.Define<int, string?>(
|
||||||
LogLevel.Debug,
|
LogLevel.Warning,
|
||||||
new EventId(2024, "AllSubscriptionResubscribed"),
|
new EventId(2023, "FailedRequestRevitalization"),
|
||||||
"[Sckt {SocketId}] all subscription successfully resubscribed on reconnected socket");
|
"[Sckt {SocketId}] failed request revitalization: {ErrorMessage}");
|
||||||
|
|
||||||
_subscriptionUnsubscribed = LoggerMessage.Define<int, int>(
|
_allSubscriptionResubscribed = LoggerMessage.Define<int>(
|
||||||
LogLevel.Information,
|
LogLevel.Debug,
|
||||||
new EventId(2025, "SubscriptionUnsubscribed"),
|
new EventId(2024, "AllSubscriptionResubscribed"),
|
||||||
"[Sckt {SocketId}] subscription {SubscriptionId} unsubscribed");
|
"[Sckt {SocketId}] all subscription successfully resubscribed on reconnected socket");
|
||||||
|
|
||||||
_sendingPeriodic = LoggerMessage.Define<int, string>(
|
_subscriptionUnsubscribed = LoggerMessage.Define<int, int>(
|
||||||
LogLevel.Trace,
|
LogLevel.Information,
|
||||||
new EventId(2026, "SendingPeriodic"),
|
new EventId(2025, "SubscriptionUnsubscribed"),
|
||||||
"[Sckt {SocketId}] sending periodic {Identifier}");
|
"[Sckt {SocketId}] subscription {SubscriptionId} unsubscribed");
|
||||||
|
|
||||||
_periodicSendFailed = LoggerMessage.Define<int, string, string>(
|
_sendingPeriodic = LoggerMessage.Define<int, string>(
|
||||||
LogLevel.Warning,
|
LogLevel.Trace,
|
||||||
new EventId(2027, "PeriodicSendFailed"),
|
new EventId(2026, "SendingPeriodic"),
|
||||||
"[Sckt {SocketId}] periodic send {Identifier} failed: {ErrorMessage}");
|
"[Sckt {SocketId}] sending periodic {Identifier}");
|
||||||
|
|
||||||
_sendingData = LoggerMessage.Define<int, int, string>(
|
_periodicSendFailed = LoggerMessage.Define<int, string, string>(
|
||||||
LogLevel.Trace,
|
LogLevel.Warning,
|
||||||
new EventId(2028, "SendingData"),
|
new EventId(2027, "PeriodicSendFailed"),
|
||||||
"[Sckt {SocketId}] [Req {RequestId}] sending message: {Data}");
|
"[Sckt {SocketId}] periodic send {Identifier} failed: {ErrorMessage}");
|
||||||
|
|
||||||
_receivedMessageNotMatchedToAnyListener = LoggerMessage.Define<int, string, string>(
|
_sendingData = LoggerMessage.Define<int, int, string>(
|
||||||
LogLevel.Warning,
|
LogLevel.Trace,
|
||||||
new EventId(2029, "ReceivedMessageNotMatchedToAnyListener"),
|
new EventId(2028, "SendingData"),
|
||||||
"[Sckt {SocketId}] received message not matched to any listener. ListenId: {ListenId}, current listeners: [{ListenIds}]");
|
"[Sckt {SocketId}] [Req {RequestId}] sending message: {Data}");
|
||||||
|
|
||||||
_failedToParse = LoggerMessage.Define<int, string>(
|
_receivedMessageNotMatchedToAnyListener = LoggerMessage.Define<int, string, string>(
|
||||||
LogLevel.Warning,
|
LogLevel.Warning,
|
||||||
new EventId(2030, "FailedToParse"),
|
new EventId(2029, "ReceivedMessageNotMatchedToAnyListener"),
|
||||||
"[Sckt {SocketId}] failed to parse data: {Error}");
|
"[Sckt {SocketId}] received message not matched to any listener. ListenId: {ListenId}, current listeners: [{ListenIds}]");
|
||||||
|
|
||||||
_sendingByteData = LoggerMessage.Define<int, int, int>(
|
_failedToParse = LoggerMessage.Define<int, string>(
|
||||||
LogLevel.Trace,
|
LogLevel.Warning,
|
||||||
new EventId(2031, "SendingByteData"),
|
new EventId(2030, "FailedToParse"),
|
||||||
"[Sckt {SocketId}] [Req {RequestId}] sending byte message of length: {Length}");
|
"[Sckt {SocketId}] failed to parse data: {Error}");
|
||||||
|
|
||||||
_processorMatched = LoggerMessage.Define<int, string, string>(
|
_sendingByteData = LoggerMessage.Define<int, int, int>(
|
||||||
LogLevel.Trace,
|
LogLevel.Trace,
|
||||||
new EventId(2032, "ProcessorMatched"),
|
new EventId(2031, "SendingByteData"),
|
||||||
"[Sckt {SocketId}] listener '{ListenId}' matched to message with listener identifier {ListenerId}");
|
"[Sckt {SocketId}] [Req {RequestId}] sending byte message of length: {Length}");
|
||||||
|
|
||||||
}
|
_processorMatched = LoggerMessage.Define<int, string, string>(
|
||||||
|
LogLevel.Trace,
|
||||||
|
new EventId(2032, "ProcessorMatched"),
|
||||||
|
"[Sckt {SocketId}] listener '{ListenId}' matched to message with listener identifier {ListenerId}");
|
||||||
|
|
||||||
public static void ActivityPaused(this ILogger logger, int socketId, bool paused)
|
}
|
||||||
{
|
|
||||||
_activityPaused(logger, socketId, paused, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void SocketStatusChanged(this ILogger logger, int socketId, Sockets.SocketConnection.SocketStatus oldStatus, Sockets.SocketConnection.SocketStatus newStatus)
|
public static void ActivityPaused(this ILogger logger, int socketId, bool paused)
|
||||||
{
|
{
|
||||||
_socketStatusChanged(logger, socketId, oldStatus, newStatus, null);
|
_activityPaused(logger, socketId, paused, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void FailedReconnectProcessing(this ILogger logger, int socketId, string? error)
|
public static void SocketStatusChanged(this ILogger logger, int socketId, Sockets.SocketConnection.SocketStatus oldStatus, Sockets.SocketConnection.SocketStatus newStatus)
|
||||||
{
|
{
|
||||||
_failedReconnectProcessing(logger, socketId, error, null);
|
_socketStatusChanged(logger, socketId, oldStatus, newStatus, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void UnknownExceptionWhileProcessingReconnection(this ILogger logger, int socketId, Exception e)
|
public static void FailedReconnectProcessing(this ILogger logger, int socketId, string? error)
|
||||||
{
|
{
|
||||||
_unknownExceptionWhileProcessingReconnection(logger, socketId, e);
|
_failedReconnectProcessing(logger, socketId, error, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void WebSocketErrorCodeAndDetails(this ILogger logger, int socketId, WebSocketError error, string? details, Exception e)
|
public static void UnknownExceptionWhileProcessingReconnection(this ILogger logger, int socketId, Exception e)
|
||||||
{
|
{
|
||||||
_webSocketErrorCodeAndDetails(logger, socketId, error, details, e);
|
_unknownExceptionWhileProcessingReconnection(logger, socketId, e);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void WebSocketError(this ILogger logger, int socketId, string? errorMessage, Exception e)
|
public static void WebSocketErrorCodeAndDetails(this ILogger logger, int socketId, WebSocketError error, string? details, Exception e)
|
||||||
{
|
{
|
||||||
_webSocketError(logger, socketId, errorMessage, e);
|
_webSocketErrorCodeAndDetails(logger, socketId, error, details, e);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void MessageSentNotPending(this ILogger logger, int socketId, int requestId)
|
public static void WebSocketError(this ILogger logger, int socketId, string? errorMessage, Exception e)
|
||||||
{
|
{
|
||||||
_messageSentNotPending(logger, socketId, requestId, null);
|
_webSocketError(logger, socketId, errorMessage, e);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void ReceivedData(this ILogger logger, int socketId, string originalData)
|
public static void MessageSentNotPending(this ILogger logger, int socketId, int requestId)
|
||||||
{
|
{
|
||||||
_receivedData(logger, socketId, originalData, null);
|
_messageSentNotPending(logger, socketId, requestId, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void FailedToParse(this ILogger logger, int socketId, string error)
|
public static void ReceivedData(this ILogger logger, int socketId, string originalData)
|
||||||
{
|
{
|
||||||
_failedToParse(logger, socketId, error, null);
|
_receivedData(logger, socketId, originalData, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void FailedToEvaluateMessage(this ILogger logger, int socketId, string originalData)
|
public static void FailedToParse(this ILogger logger, int socketId, string error)
|
||||||
{
|
{
|
||||||
_failedToEvaluateMessage(logger, socketId, originalData, null);
|
_failedToParse(logger, socketId, error, null);
|
||||||
}
|
}
|
||||||
public static void ErrorProcessingMessage(this ILogger logger, int socketId, Exception e)
|
|
||||||
{
|
|
||||||
_errorProcessingMessage(logger, socketId, e);
|
|
||||||
}
|
|
||||||
public static void ProcessorMatched(this ILogger logger, int socketId, string listener, string listenerId)
|
|
||||||
{
|
|
||||||
_processorMatched(logger, socketId, listener, listenerId, null);
|
|
||||||
}
|
|
||||||
public static void ReceivedMessageNotRecognized(this ILogger logger, int socketId, int id)
|
|
||||||
{
|
|
||||||
_receivedMessageNotRecognized(logger, socketId, id, null);
|
|
||||||
}
|
|
||||||
public static void FailedToDeserializeMessage(this ILogger logger, int socketId, string? errorMessage, Exception? ex)
|
|
||||||
{
|
|
||||||
_failedToDeserializeMessage(logger, socketId, errorMessage, ex);
|
|
||||||
}
|
|
||||||
public static void UserMessageProcessingFailed(this ILogger logger, int socketId, string errorMessage, Exception e)
|
|
||||||
{
|
|
||||||
_userMessageProcessingFailed(logger, socketId, errorMessage, e);
|
|
||||||
}
|
|
||||||
public static void MessageProcessed(this ILogger logger, int socketId, long processingTime, long parsingTime)
|
|
||||||
{
|
|
||||||
_messageProcessed(logger, socketId, processingTime, parsingTime, null);
|
|
||||||
}
|
|
||||||
public static void ClosingSubscription(this ILogger logger, int socketId, int subscriptionId)
|
|
||||||
{
|
|
||||||
_closingSubscription(logger, socketId, subscriptionId, null);
|
|
||||||
}
|
|
||||||
public static void NotUnsubscribingSubscriptionBecauseDuplicateRunning(this ILogger logger, int socketId)
|
|
||||||
{
|
|
||||||
_notUnsubscribingSubscriptionBecauseDuplicateRunning(logger, socketId, null);
|
|
||||||
}
|
|
||||||
public static void AlreadyClosing(this ILogger logger, int socketId)
|
|
||||||
{
|
|
||||||
_alreadyClosing(logger, socketId, null);
|
|
||||||
}
|
|
||||||
public static void ClosingNoMoreSubscriptions(this ILogger logger, int socketId)
|
|
||||||
{
|
|
||||||
_closingNoMoreSubscriptions(logger, socketId, null);
|
|
||||||
}
|
|
||||||
public static void AddingNewSubscription(this ILogger logger, int socketId, int subscriptionId, int userSubscriptionCount)
|
|
||||||
{
|
|
||||||
_addingNewSubscription(logger, socketId, subscriptionId, userSubscriptionCount, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void NothingToResubscribeCloseConnection(this ILogger logger, int socketId)
|
public static void FailedToEvaluateMessage(this ILogger logger, int socketId, string originalData)
|
||||||
{
|
{
|
||||||
_nothingToResubscribeCloseConnection(logger, socketId, null);
|
_failedToEvaluateMessage(logger, socketId, originalData, null);
|
||||||
}
|
}
|
||||||
public static void FailedAuthenticationDisconnectAndRecoonect(this ILogger logger, int socketId)
|
public static void ErrorProcessingMessage(this ILogger logger, int socketId, Exception e)
|
||||||
{
|
{
|
||||||
_failedAuthenticationDisconnectAndReconnect(logger, socketId, null);
|
_errorProcessingMessage(logger, socketId, e);
|
||||||
}
|
}
|
||||||
public static void AuthenticationSucceeded(this ILogger logger, int socketId)
|
public static void ProcessorMatched(this ILogger logger, int socketId, string listener, string listenerId)
|
||||||
{
|
{
|
||||||
_authenticationSucceeded(logger, socketId, null);
|
_processorMatched(logger, socketId, listener, listenerId, null);
|
||||||
}
|
}
|
||||||
public static void FailedRequestRevitalization(this ILogger logger, int socketId, string? errorMessage)
|
public static void ReceivedMessageNotRecognized(this ILogger logger, int socketId, int id)
|
||||||
{
|
{
|
||||||
_failedRequestRevitalization(logger, socketId, errorMessage, null);
|
_receivedMessageNotRecognized(logger, socketId, id, null);
|
||||||
}
|
}
|
||||||
public static void AllSubscriptionResubscribed(this ILogger logger, int socketId)
|
public static void FailedToDeserializeMessage(this ILogger logger, int socketId, string? errorMessage, Exception? ex)
|
||||||
{
|
{
|
||||||
_allSubscriptionResubscribed(logger, socketId, null);
|
_failedToDeserializeMessage(logger, socketId, errorMessage, ex);
|
||||||
}
|
}
|
||||||
public static void SubscriptionUnsubscribed(this ILogger logger, int socketId, int subscriptionId)
|
public static void UserMessageProcessingFailed(this ILogger logger, int socketId, string errorMessage, Exception e)
|
||||||
{
|
{
|
||||||
_subscriptionUnsubscribed(logger, socketId, subscriptionId, null);
|
_userMessageProcessingFailed(logger, socketId, errorMessage, e);
|
||||||
}
|
}
|
||||||
public static void SendingPeriodic(this ILogger logger, int socketId, string identifier)
|
public static void MessageProcessed(this ILogger logger, int socketId, long processingTime, long parsingTime)
|
||||||
{
|
{
|
||||||
_sendingPeriodic(logger, socketId, identifier, null);
|
_messageProcessed(logger, socketId, processingTime, parsingTime, null);
|
||||||
}
|
}
|
||||||
public static void PeriodicSendFailed(this ILogger logger, int socketId, string identifier, string errorMessage, Exception e)
|
public static void ClosingSubscription(this ILogger logger, int socketId, int subscriptionId)
|
||||||
{
|
{
|
||||||
_periodicSendFailed(logger, socketId, identifier, errorMessage, e);
|
_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 SendingData(this ILogger logger, int socketId, int requestId, string data)
|
public static void NothingToResubscribeCloseConnection(this ILogger logger, int socketId)
|
||||||
{
|
{
|
||||||
_sendingData(logger, socketId, requestId, data, null);
|
_nothingToResubscribeCloseConnection(logger, socketId, null);
|
||||||
}
|
}
|
||||||
|
public static void FailedAuthenticationDisconnectAndRecoonect(this ILogger logger, int socketId)
|
||||||
|
{
|
||||||
|
_failedAuthenticationDisconnectAndReconnect(logger, socketId, null);
|
||||||
|
}
|
||||||
|
public static void AuthenticationSucceeded(this ILogger logger, int socketId)
|
||||||
|
{
|
||||||
|
_authenticationSucceeded(logger, socketId, null);
|
||||||
|
}
|
||||||
|
public static void FailedRequestRevitalization(this ILogger logger, int socketId, string? errorMessage)
|
||||||
|
{
|
||||||
|
_failedRequestRevitalization(logger, socketId, errorMessage, null);
|
||||||
|
}
|
||||||
|
public static void AllSubscriptionResubscribed(this ILogger logger, int socketId)
|
||||||
|
{
|
||||||
|
_allSubscriptionResubscribed(logger, socketId, null);
|
||||||
|
}
|
||||||
|
public static void SubscriptionUnsubscribed(this ILogger logger, int socketId, int subscriptionId)
|
||||||
|
{
|
||||||
|
_subscriptionUnsubscribed(logger, socketId, subscriptionId, null);
|
||||||
|
}
|
||||||
|
public static void SendingPeriodic(this ILogger logger, int socketId, string identifier)
|
||||||
|
{
|
||||||
|
_sendingPeriodic(logger, socketId, identifier, null);
|
||||||
|
}
|
||||||
|
public static void PeriodicSendFailed(this ILogger logger, int socketId, string identifier, string errorMessage, Exception e)
|
||||||
|
{
|
||||||
|
_periodicSendFailed(logger, socketId, identifier, errorMessage, e);
|
||||||
|
}
|
||||||
|
|
||||||
public static void ReceivedMessageNotMatchedToAnyListener(this ILogger logger, int socketId, string listenId, string listenIds)
|
public static void SendingData(this ILogger logger, int socketId, int requestId, string data)
|
||||||
{
|
{
|
||||||
_receivedMessageNotMatchedToAnyListener(logger, socketId, listenId, listenIds, null);
|
_sendingData(logger, socketId, requestId, data, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void SendingByteData(this ILogger logger, int socketId, int requestId, int length)
|
public static void ReceivedMessageNotMatchedToAnyListener(this ILogger logger, int socketId, string listenId, string listenIds)
|
||||||
{
|
{
|
||||||
_sendingByteData(logger, socketId, requestId, length, null);
|
_receivedMessageNotMatchedToAnyListener(logger, socketId, listenId, listenIds, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void SendingByteData(this ILogger logger, int socketId, int requestId, int length)
|
||||||
|
{
|
||||||
|
_sendingByteData(logger, socketId, requestId, length, null);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,236 +1,237 @@
|
|||||||
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()
|
|
||||||
{
|
{
|
||||||
_orderBookStatusChanged = LoggerMessage.Define<string, string, OrderBookStatus, OrderBookStatus>(
|
private static readonly Action<ILogger, string, string, OrderBookStatus, OrderBookStatus, Exception?> _orderBookStatusChanged;
|
||||||
LogLevel.Information,
|
private static readonly Action<ILogger, string, string, Exception?> _orderBookStarting;
|
||||||
new EventId(5000, "OrderBookStatusChanged"),
|
private static readonly Action<ILogger, string, string, Exception?> _orderBookStoppedStarting;
|
||||||
"{Api} order book {Symbol} status changed: {PreviousStatus} => {NewStatus}");
|
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;
|
||||||
|
|
||||||
_orderBookStarting = LoggerMessage.Define<string, string>(
|
static SymbolOrderBookLoggingExtensions()
|
||||||
LogLevel.Debug,
|
{
|
||||||
new EventId(5001, "OrderBookStarting"),
|
_orderBookStatusChanged = LoggerMessage.Define<string, string, OrderBookStatus, OrderBookStatus>(
|
||||||
"{Api} order book {Symbol} starting");
|
LogLevel.Information,
|
||||||
|
new EventId(5000, "OrderBookStatusChanged"),
|
||||||
|
"{Api} order book {Symbol} status changed: {PreviousStatus} => {NewStatus}");
|
||||||
|
|
||||||
_orderBookStoppedStarting = LoggerMessage.Define<string, string>(
|
_orderBookStarting = LoggerMessage.Define<string, string>(
|
||||||
LogLevel.Debug,
|
LogLevel.Debug,
|
||||||
new EventId(5002, "OrderBookStoppedStarting"),
|
new EventId(5001, "OrderBookStarting"),
|
||||||
"{Api} order book {Symbol} stopped while starting");
|
"{Api} order book {Symbol} starting");
|
||||||
|
|
||||||
_orderBookConnectionLost = LoggerMessage.Define<string, string>(
|
_orderBookStoppedStarting = LoggerMessage.Define<string, string>(
|
||||||
LogLevel.Warning,
|
LogLevel.Debug,
|
||||||
new EventId(5003, "OrderBookConnectionLost"),
|
new EventId(5002, "OrderBookStoppedStarting"),
|
||||||
"{Api} order book {Symbol} connection lost");
|
"{Api} order book {Symbol} stopped while starting");
|
||||||
|
|
||||||
_orderBookDisconnected = LoggerMessage.Define<string, string>(
|
_orderBookConnectionLost = LoggerMessage.Define<string, string>(
|
||||||
LogLevel.Debug,
|
LogLevel.Warning,
|
||||||
new EventId(5004, "OrderBookDisconnected"),
|
new EventId(5003, "OrderBookConnectionLost"),
|
||||||
"{Api} order book {Symbol} disconnected");
|
"{Api} order book {Symbol} connection lost");
|
||||||
|
|
||||||
_orderBookStopping = LoggerMessage.Define<string, string>(
|
_orderBookDisconnected = LoggerMessage.Define<string, string>(
|
||||||
LogLevel.Debug,
|
LogLevel.Debug,
|
||||||
new EventId(5005, "OrderBookStopping"),
|
new EventId(5004, "OrderBookDisconnected"),
|
||||||
"{Api} order book {Symbol} stopping");
|
"{Api} order book {Symbol} disconnected");
|
||||||
|
|
||||||
_orderBookStopped = LoggerMessage.Define<string, string>(
|
_orderBookStopping = LoggerMessage.Define<string, string>(
|
||||||
LogLevel.Trace,
|
LogLevel.Debug,
|
||||||
new EventId(5006, "OrderBookStopped"),
|
new EventId(5005, "OrderBookStopping"),
|
||||||
"{Api} order book {Symbol} stopped");
|
"{Api} order book {Symbol} stopping");
|
||||||
|
|
||||||
_orderBookProcessingBufferedUpdates = LoggerMessage.Define<string, string, int>(
|
_orderBookStopped = LoggerMessage.Define<string, string>(
|
||||||
LogLevel.Debug,
|
LogLevel.Trace,
|
||||||
new EventId(5007, "OrderBookProcessingBufferedUpdates"),
|
new EventId(5006, "OrderBookStopped"),
|
||||||
"{Api} order book {Symbol} Processing {NumberBufferedUpdated} buffered updates");
|
"{Api} order book {Symbol} stopped");
|
||||||
|
|
||||||
_orderBookUpdateSkipped = LoggerMessage.Define<string, string, long, long>(
|
_orderBookProcessingBufferedUpdates = LoggerMessage.Define<string, string, int>(
|
||||||
LogLevel.Debug,
|
LogLevel.Debug,
|
||||||
new EventId(5008, "OrderBookUpdateSkipped"),
|
new EventId(5007, "OrderBookProcessingBufferedUpdates"),
|
||||||
"{Api} order book {Symbol} update skipped #{SequenceNumber}, currently at #{LastSequenceNumber}");
|
"{Api} order book {Symbol} Processing {NumberBufferedUpdated} buffered updates");
|
||||||
|
|
||||||
_orderBookOutOfSync = LoggerMessage.Define<string, string, long, long>(
|
_orderBookUpdateSkipped = LoggerMessage.Define<string, string, long, long>(
|
||||||
LogLevel.Warning,
|
LogLevel.Debug,
|
||||||
new EventId(5009, "OrderBookOutOfSync"),
|
new EventId(5008, "OrderBookUpdateSkipped"),
|
||||||
"{Api} order book {Symbol} out of sync (expected {ExpectedSequenceNumber}, was {SequenceNumber}), reconnecting");
|
"{Api} order book {Symbol} update skipped #{SequenceNumber}, currently at #{LastSequenceNumber}");
|
||||||
|
|
||||||
_orderBookResynced = LoggerMessage.Define<string, string>(
|
_orderBookOutOfSync = LoggerMessage.Define<string, string, long, long>(
|
||||||
LogLevel.Information,
|
LogLevel.Warning,
|
||||||
new EventId(5010, "OrderBookResynced"),
|
new EventId(5009, "OrderBookOutOfSync"),
|
||||||
"{Api} order book {Symbol} successfully resynchronized");
|
"{Api} order book {Symbol} out of sync (expected {ExpectedSequenceNumber}, was {SequenceNumber}), reconnecting");
|
||||||
|
|
||||||
_orderBookMessageSkippedBecauseOfResubscribing = LoggerMessage.Define<string, string>(
|
_orderBookResynced = LoggerMessage.Define<string, string>(
|
||||||
LogLevel.Trace,
|
LogLevel.Information,
|
||||||
new EventId(5011, "OrderBookMessageSkippedResubscribing"),
|
new EventId(5010, "OrderBookResynced"),
|
||||||
"{Api} order book {Symbol} Skipping message because of resubscribing");
|
"{Api} order book {Symbol} successfully resynchronized");
|
||||||
|
|
||||||
_orderBookDataSet = LoggerMessage.Define<string, string, long, long, long>(
|
_orderBookMessageSkippedBecauseOfResubscribing = LoggerMessage.Define<string, string>(
|
||||||
LogLevel.Debug,
|
LogLevel.Trace,
|
||||||
new EventId(5012, "OrderBookDataSet"),
|
new EventId(5011, "OrderBookMessageSkippedResubscribing"),
|
||||||
"{Api} order book {Symbol} data set: {BidCount} bids, {AskCount} asks. #{EndUpdateId}");
|
"{Api} order book {Symbol} Skipping message because of resubscribing");
|
||||||
|
|
||||||
_orderBookUpdateBuffered = LoggerMessage.Define<string, string, long, long, long, long>(
|
_orderBookDataSet = LoggerMessage.Define<string, string, long, long, long>(
|
||||||
LogLevel.Trace,
|
LogLevel.Debug,
|
||||||
new EventId(5013, "OrderBookUpdateBuffered"),
|
new EventId(5012, "OrderBookDataSet"),
|
||||||
"{Api} order book {Symbol} update buffered #{StartUpdateId}-#{EndUpdateId} [{AsksCount} asks, {BidsCount} bids]");
|
"{Api} order book {Symbol} data set: {BidCount} bids, {AskCount} asks. #{EndUpdateId}");
|
||||||
|
|
||||||
_orderBookOutOfSyncDetected = LoggerMessage.Define<string, string, decimal, decimal>(
|
_orderBookUpdateBuffered = LoggerMessage.Define<string, string, long, long, long, long>(
|
||||||
LogLevel.Warning,
|
LogLevel.Trace,
|
||||||
new EventId(5014, "OrderBookOutOfSyncDetected"),
|
new EventId(5013, "OrderBookUpdateBuffered"),
|
||||||
"{Api} order book {Symbol} detected out of sync order book. First ask: {FirstAsk}, first bid: {FirstBid}. Resyncing");
|
"{Api} order book {Symbol} update buffered #{StartUpdateId}-#{EndUpdateId} [{AsksCount} asks, {BidsCount} bids]");
|
||||||
|
|
||||||
_orderBookReconnectingSocket = LoggerMessage.Define<string, string>(
|
_orderBookOutOfSyncDetected = LoggerMessage.Define<string, string, decimal, decimal>(
|
||||||
LogLevel.Warning,
|
LogLevel.Warning,
|
||||||
new EventId(5015, "OrderBookReconnectingSocket"),
|
new EventId(5014, "OrderBookOutOfSyncDetected"),
|
||||||
"{Api} order book {Symbol} out of sync. Reconnecting socket");
|
"{Api} order book {Symbol} detected out of sync order book. First ask: {FirstAsk}, first bid: {FirstBid}. Resyncing");
|
||||||
|
|
||||||
_orderBookResyncing = LoggerMessage.Define<string, string>(
|
_orderBookReconnectingSocket = LoggerMessage.Define<string, string>(
|
||||||
LogLevel.Warning,
|
LogLevel.Warning,
|
||||||
new EventId(5016, "OrderBookResyncing"),
|
new EventId(5015, "OrderBookReconnectingSocket"),
|
||||||
"{Api} order book {Symbol} out of sync. Resyncing");
|
"{Api} order book {Symbol} out of sync. Reconnecting socket");
|
||||||
|
|
||||||
_orderBookResyncFailed = LoggerMessage.Define<string, string>(
|
_orderBookResyncing = LoggerMessage.Define<string, string>(
|
||||||
LogLevel.Warning,
|
LogLevel.Warning,
|
||||||
new EventId(5017, "OrderBookResyncFailed"),
|
new EventId(5016, "OrderBookResyncing"),
|
||||||
"{Api} order book {Symbol} resync failed, reconnecting socket");
|
"{Api} order book {Symbol} out of sync. Resyncing");
|
||||||
|
|
||||||
_orderBookSkippedMessage = LoggerMessage.Define<string, string, long, long>(
|
_orderBookResyncFailed = LoggerMessage.Define<string, string>(
|
||||||
LogLevel.Trace,
|
LogLevel.Warning,
|
||||||
new EventId(5018, "OrderBookSkippedMessage"),
|
new EventId(5017, "OrderBookResyncFailed"),
|
||||||
"{Api} order book {Symbol} update skipped #{FirstUpdateId}-{LastUpdateId}");
|
"{Api} order book {Symbol} resync failed, reconnecting socket");
|
||||||
|
|
||||||
_orderBookProcessedMessage = LoggerMessage.Define<string, string, long, long>(
|
_orderBookSkippedMessage = LoggerMessage.Define<string, string, long, long>(
|
||||||
LogLevel.Trace,
|
LogLevel.Trace,
|
||||||
new EventId(5019, "OrderBookProcessedMessage"),
|
new EventId(5018, "OrderBookSkippedMessage"),
|
||||||
"{Api} order book {Symbol} update processed #{FirstUpdateId}-{LastUpdateId}");
|
"{Api} order book {Symbol} update skipped #{FirstUpdateId}-{LastUpdateId}");
|
||||||
|
|
||||||
_orderBookOutOfSyncChecksum = LoggerMessage.Define<string, string>(
|
_orderBookProcessedMessage = LoggerMessage.Define<string, string, long, long>(
|
||||||
LogLevel.Warning,
|
LogLevel.Trace,
|
||||||
new EventId(5020, "OrderBookOutOfSyncChecksum"),
|
new EventId(5019, "OrderBookProcessedMessage"),
|
||||||
"{Api} order book {Symbol} out of sync. Checksum mismatch, resyncing");
|
"{Api} order book {Symbol} update processed #{FirstUpdateId}-{LastUpdateId}");
|
||||||
}
|
|
||||||
|
|
||||||
public static void OrderBookStatusChanged(this ILogger logger, string api, string symbol, OrderBookStatus previousStatus, OrderBookStatus newStatus)
|
_orderBookOutOfSyncChecksum = LoggerMessage.Define<string, string>(
|
||||||
{
|
LogLevel.Warning,
|
||||||
_orderBookStatusChanged(logger, api, symbol, previousStatus, newStatus, null);
|
new EventId(5020, "OrderBookOutOfSyncChecksum"),
|
||||||
}
|
"{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 OrderBookDisconnected(this ILogger logger, string api, string symbol)
|
public static void OrderBookStatusChanged(this ILogger logger, string api, string symbol, OrderBookStatus previousStatus, OrderBookStatus newStatus)
|
||||||
{
|
{
|
||||||
_orderBookDisconnected(logger, api, symbol, null);
|
_orderBookStatusChanged(logger, api, symbol, previousStatus, newStatus, null);
|
||||||
}
|
}
|
||||||
|
public static void OrderBookStarting(this ILogger logger, string api, string symbol)
|
||||||
|
{
|
||||||
|
_orderBookStarting(logger, api, symbol, null);
|
||||||
|
}
|
||||||
|
public static void OrderBookStoppedStarting(this ILogger logger, string api, string symbol)
|
||||||
|
{
|
||||||
|
_orderBookStoppedStarting(logger, api, symbol, null);
|
||||||
|
}
|
||||||
|
public static void OrderBookConnectionLost(this ILogger logger, string api, string symbol)
|
||||||
|
{
|
||||||
|
_orderBookConnectionLost(logger, api, symbol, null);
|
||||||
|
}
|
||||||
|
|
||||||
public static void OrderBookStopping(this ILogger logger, string api, string symbol)
|
public static void OrderBookDisconnected(this ILogger logger, string api, string symbol)
|
||||||
{
|
{
|
||||||
_orderBookStopping(logger, api, symbol, null);
|
_orderBookDisconnected(logger, api, symbol, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void OrderBookStopped(this ILogger logger, string api, string symbol)
|
public static void OrderBookStopping(this ILogger logger, string api, string symbol)
|
||||||
{
|
{
|
||||||
_orderBookStopped(logger, api, symbol, null);
|
_orderBookStopping(logger, api, symbol, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void OrderBookProcessingBufferedUpdates(this ILogger logger, string api, string symbol, int numberBufferedUpdated)
|
public static void OrderBookStopped(this ILogger logger, string api, string symbol)
|
||||||
{
|
{
|
||||||
_orderBookProcessingBufferedUpdates(logger, api, symbol, numberBufferedUpdated, null);
|
_orderBookStopped(logger, api, symbol, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void OrderBookUpdateSkipped(this ILogger logger, string api, string symbol, long sequence, long lastSequenceNumber)
|
public static void OrderBookProcessingBufferedUpdates(this ILogger logger, string api, string symbol, int numberBufferedUpdated)
|
||||||
{
|
{
|
||||||
_orderBookUpdateSkipped(logger, api, symbol, sequence, lastSequenceNumber, null);
|
_orderBookProcessingBufferedUpdates(logger, api, symbol, numberBufferedUpdated, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void OrderBookOutOfSync(this ILogger logger, string api, string symbol, long expectedSequenceNumber, long sequenceNumber)
|
public static void OrderBookUpdateSkipped(this ILogger logger, string api, string symbol, long sequence, long lastSequenceNumber)
|
||||||
{
|
{
|
||||||
_orderBookOutOfSync(logger, api, symbol, expectedSequenceNumber, sequenceNumber, null);
|
_orderBookUpdateSkipped(logger, api, symbol, sequence, lastSequenceNumber, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void OrderBookResynced(this ILogger logger, string api, string symbol)
|
public static void OrderBookOutOfSync(this ILogger logger, string api, string symbol, long expectedSequenceNumber, long sequenceNumber)
|
||||||
{
|
{
|
||||||
_orderBookResynced(logger, api, symbol, null);
|
_orderBookOutOfSync(logger, api, symbol, expectedSequenceNumber, sequenceNumber, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void OrderBookMessageSkippedResubscribing(this ILogger logger, string api, string symbol)
|
public static void OrderBookResynced(this ILogger logger, string api, string symbol)
|
||||||
{
|
{
|
||||||
_orderBookMessageSkippedBecauseOfResubscribing(logger, api, symbol, null);
|
_orderBookResynced(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 OrderBookMessageSkippedResubscribing(this ILogger logger, string api, string symbol)
|
||||||
{
|
{
|
||||||
_orderBookReconnectingSocket(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 OrderBookResyncing(this ILogger logger, string api, string symbol)
|
public static void OrderBookReconnectingSocket(this ILogger logger, string api, string symbol)
|
||||||
{
|
{
|
||||||
_orderBookResyncing(logger, api, symbol, null);
|
_orderBookReconnectingSocket(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)
|
public static void OrderBookResyncing(this ILogger logger, string api, string symbol)
|
||||||
{
|
{
|
||||||
_orderBookOutOfSyncChecksum(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 OrderBookOutOfSyncChecksum(this ILogger logger, string api, string symbol)
|
||||||
|
{
|
||||||
|
_orderBookOutOfSyncChecksum(logger, api, symbol, null);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,290 +1,291 @@
|
|||||||
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()
|
|
||||||
{
|
{
|
||||||
_klineTrackerStatusChanged = LoggerMessage.Define<string, SyncStatus, SyncStatus>(
|
private static readonly Action<ILogger, string, SyncStatus, SyncStatus, Exception?> _klineTrackerStatusChanged;
|
||||||
LogLevel.Debug,
|
private static readonly Action<ILogger, string, Exception?> _klineTrackerStarting;
|
||||||
new EventId(6001, "KlineTrackerStatusChanged"),
|
private static readonly Action<ILogger, string, string, Exception?> _klineTrackerStartFailed;
|
||||||
"Kline tracker for {Symbol} status changed: {OldStatus} => {NewStatus}");
|
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;
|
||||||
|
|
||||||
_klineTrackerStarting = LoggerMessage.Define<string>(
|
private static readonly Action<ILogger, string, SyncStatus, SyncStatus, Exception?> _tradeTrackerStatusChanged;
|
||||||
LogLevel.Debug,
|
private static readonly Action<ILogger, string, Exception?> _tradeTrackerStarting;
|
||||||
new EventId(6002, "KlineTrackerStarting"),
|
private static readonly Action<ILogger, string, string, Exception?> _tradeTrackerStartFailed;
|
||||||
"Kline tracker for {Symbol} starting");
|
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;
|
||||||
|
|
||||||
_klineTrackerStartFailed = LoggerMessage.Define<string, string>(
|
static TrackerLoggingExtensions()
|
||||||
LogLevel.Warning,
|
{
|
||||||
new EventId(6003, "KlineTrackerStartFailed"),
|
_klineTrackerStatusChanged = LoggerMessage.Define<string, SyncStatus, SyncStatus>(
|
||||||
"Kline tracker for {Symbol} failed to start: {Error}");
|
LogLevel.Debug,
|
||||||
|
new EventId(6001, "KlineTrackerStatusChanged"),
|
||||||
|
"Kline tracker for {Symbol} status changed: {OldStatus} => {NewStatus}");
|
||||||
|
|
||||||
_klineTrackerStarted = LoggerMessage.Define<string>(
|
_klineTrackerStarting = LoggerMessage.Define<string>(
|
||||||
LogLevel.Information,
|
LogLevel.Debug,
|
||||||
new EventId(6004, "KlineTrackerStarted"),
|
new EventId(6002, "KlineTrackerStarting"),
|
||||||
"Kline tracker for {Symbol} started");
|
"Kline tracker for {Symbol} starting");
|
||||||
|
|
||||||
_klineTrackerStopping = LoggerMessage.Define<string>(
|
_klineTrackerStartFailed = LoggerMessage.Define<string, string>(
|
||||||
LogLevel.Debug,
|
LogLevel.Warning,
|
||||||
new EventId(6005, "KlineTrackerStopping"),
|
new EventId(6003, "KlineTrackerStartFailed"),
|
||||||
"Kline tracker for {Symbol} stopping");
|
"Kline tracker for {Symbol} failed to start: {Error}");
|
||||||
|
|
||||||
_klineTrackerStopped = LoggerMessage.Define<string>(
|
_klineTrackerStarted = LoggerMessage.Define<string>(
|
||||||
LogLevel.Information,
|
LogLevel.Information,
|
||||||
new EventId(6006, "KlineTrackerStopped"),
|
new EventId(6004, "KlineTrackerStarted"),
|
||||||
"Kline tracker for {Symbol} stopped");
|
"Kline tracker for {Symbol} started");
|
||||||
|
|
||||||
_klineTrackerInitialDataSet = LoggerMessage.Define<string, DateTime>(
|
_klineTrackerStopping = LoggerMessage.Define<string>(
|
||||||
LogLevel.Debug,
|
LogLevel.Debug,
|
||||||
new EventId(6007, "KlineTrackerInitialDataSet"),
|
new EventId(6005, "KlineTrackerStopping"),
|
||||||
"Kline tracker for {Symbol} initial data set, last timestamp: {LastTime}");
|
"Kline tracker for {Symbol} stopping");
|
||||||
|
|
||||||
_klineTrackerKlineUpdated = LoggerMessage.Define<string, DateTime>(
|
_klineTrackerStopped = LoggerMessage.Define<string>(
|
||||||
LogLevel.Trace,
|
LogLevel.Information,
|
||||||
new EventId(6008, "KlineTrackerKlineUpdated"),
|
new EventId(6006, "KlineTrackerStopped"),
|
||||||
"Kline tracker for {Symbol} kline updated for open time: {LastTime}");
|
"Kline tracker for {Symbol} stopped");
|
||||||
|
|
||||||
_klineTrackerKlineAdded = LoggerMessage.Define<string, DateTime>(
|
_klineTrackerInitialDataSet = LoggerMessage.Define<string, DateTime>(
|
||||||
LogLevel.Trace,
|
LogLevel.Debug,
|
||||||
new EventId(6009, "KlineTrackerKlineAdded"),
|
new EventId(6007, "KlineTrackerInitialDataSet"),
|
||||||
"Kline tracker for {Symbol} new kline for open time: {LastTime}");
|
"Kline tracker for {Symbol} initial data set, last timestamp: {LastTime}");
|
||||||
|
|
||||||
_klineTrackerConnectionLost = LoggerMessage.Define<string>(
|
_klineTrackerKlineUpdated = LoggerMessage.Define<string, DateTime>(
|
||||||
LogLevel.Warning,
|
LogLevel.Trace,
|
||||||
new EventId(6010, "KlineTrackerConnectionLost"),
|
new EventId(6008, "KlineTrackerKlineUpdated"),
|
||||||
"Kline tracker for {Symbol} connection lost");
|
"Kline tracker for {Symbol} kline updated for open time: {LastTime}");
|
||||||
|
|
||||||
_klineTrackerConnectionClosed = LoggerMessage.Define<string>(
|
_klineTrackerKlineAdded = LoggerMessage.Define<string, DateTime>(
|
||||||
LogLevel.Warning,
|
LogLevel.Trace,
|
||||||
new EventId(6011, "KlineTrackerConnectionClosed"),
|
new EventId(6009, "KlineTrackerKlineAdded"),
|
||||||
"Kline tracker for {Symbol} disconnected");
|
"Kline tracker for {Symbol} new kline for open time: {LastTime}");
|
||||||
|
|
||||||
_klineTrackerConnectionRestored = LoggerMessage.Define<string>(
|
_klineTrackerConnectionLost = LoggerMessage.Define<string>(
|
||||||
LogLevel.Information,
|
LogLevel.Warning,
|
||||||
new EventId(6012, "KlineTrackerConnectionRestored"),
|
new EventId(6010, "KlineTrackerConnectionLost"),
|
||||||
"Kline tracker for {Symbol} successfully resynchronized");
|
"Kline tracker for {Symbol} connection lost");
|
||||||
|
|
||||||
_tradeTrackerStatusChanged = LoggerMessage.Define<string, SyncStatus, SyncStatus>(
|
_klineTrackerConnectionClosed = LoggerMessage.Define<string>(
|
||||||
LogLevel.Debug,
|
LogLevel.Warning,
|
||||||
new EventId(6013, "KlineTrackerStatusChanged"),
|
new EventId(6011, "KlineTrackerConnectionClosed"),
|
||||||
"Trade tracker for {Symbol} status changed: {OldStatus} => {NewStatus}");
|
"Kline tracker for {Symbol} disconnected");
|
||||||
|
|
||||||
_tradeTrackerStarting = LoggerMessage.Define<string>(
|
_klineTrackerConnectionRestored = LoggerMessage.Define<string>(
|
||||||
LogLevel.Debug,
|
LogLevel.Information,
|
||||||
new EventId(6014, "KlineTrackerStarting"),
|
new EventId(6012, "KlineTrackerConnectionRestored"),
|
||||||
"Trade tracker for {Symbol} starting");
|
"Kline tracker for {Symbol} successfully resynchronized");
|
||||||
|
|
||||||
_tradeTrackerStartFailed = LoggerMessage.Define<string, string>(
|
_tradeTrackerStatusChanged = LoggerMessage.Define<string, SyncStatus, SyncStatus>(
|
||||||
LogLevel.Warning,
|
LogLevel.Debug,
|
||||||
new EventId(6015, "KlineTrackerStartFailed"),
|
new EventId(6013, "KlineTrackerStatusChanged"),
|
||||||
"Trade tracker for {Symbol} failed to start: {Error}");
|
"Trade tracker for {Symbol} status changed: {OldStatus} => {NewStatus}");
|
||||||
|
|
||||||
_tradeTrackerStarted = LoggerMessage.Define<string>(
|
_tradeTrackerStarting = LoggerMessage.Define<string>(
|
||||||
LogLevel.Information,
|
LogLevel.Debug,
|
||||||
new EventId(6016, "KlineTrackerStarted"),
|
new EventId(6014, "KlineTrackerStarting"),
|
||||||
"Trade tracker for {Symbol} started");
|
"Trade tracker for {Symbol} starting");
|
||||||
|
|
||||||
_tradeTrackerStopping = LoggerMessage.Define<string>(
|
_tradeTrackerStartFailed = LoggerMessage.Define<string, string>(
|
||||||
LogLevel.Debug,
|
LogLevel.Warning,
|
||||||
new EventId(6017, "KlineTrackerStopping"),
|
new EventId(6015, "KlineTrackerStartFailed"),
|
||||||
"Trade tracker for {Symbol} stopping");
|
"Trade tracker for {Symbol} failed to start: {Error}");
|
||||||
|
|
||||||
_tradeTrackerStopped = LoggerMessage.Define<string>(
|
_tradeTrackerStarted = LoggerMessage.Define<string>(
|
||||||
LogLevel.Information,
|
LogLevel.Information,
|
||||||
new EventId(6018, "KlineTrackerStopped"),
|
new EventId(6016, "KlineTrackerStarted"),
|
||||||
"Trade tracker for {Symbol} stopped");
|
"Trade tracker for {Symbol} started");
|
||||||
|
|
||||||
_tradeTrackerInitialDataSet = LoggerMessage.Define<string, int, long>(
|
_tradeTrackerStopping = LoggerMessage.Define<string>(
|
||||||
LogLevel.Debug,
|
LogLevel.Debug,
|
||||||
new EventId(6019, "TradeTrackerInitialDataSet"),
|
new EventId(6017, "KlineTrackerStopping"),
|
||||||
"Trade tracker for {Symbol} snapshot set, Count: {Count}, Last id: {LastId}");
|
"Trade tracker for {Symbol} stopping");
|
||||||
|
|
||||||
_tradeTrackerPreSnapshotSkip = LoggerMessage.Define<string, long>(
|
_tradeTrackerStopped = LoggerMessage.Define<string>(
|
||||||
LogLevel.Trace,
|
LogLevel.Information,
|
||||||
new EventId(6020, "TradeTrackerPreSnapshotSkip"),
|
new EventId(6018, "KlineTrackerStopped"),
|
||||||
"Trade tracker for {Symbol} skipping {Id}, already in snapshot");
|
"Trade tracker for {Symbol} stopped");
|
||||||
|
|
||||||
_tradeTrackerPreSnapshotApplied = LoggerMessage.Define<string, long>(
|
_tradeTrackerInitialDataSet = LoggerMessage.Define<string, int, long>(
|
||||||
LogLevel.Trace,
|
LogLevel.Debug,
|
||||||
new EventId(6021, "TradeTrackerPreSnapshotApplied"),
|
new EventId(6019, "TradeTrackerInitialDataSet"),
|
||||||
"Trade tracker for {Symbol} adding {Id} from pre-snapshot");
|
"Trade tracker for {Symbol} snapshot set, Count: {Count}, Last id: {LastId}");
|
||||||
|
|
||||||
_tradeTrackerTradeAdded = LoggerMessage.Define<string, long>(
|
_tradeTrackerPreSnapshotSkip = LoggerMessage.Define<string, long>(
|
||||||
LogLevel.Trace,
|
LogLevel.Trace,
|
||||||
new EventId(6022, "TradeTrackerTradeAdded"),
|
new EventId(6020, "TradeTrackerPreSnapshotSkip"),
|
||||||
"Trade tracker for {Symbol} adding trade {Id}");
|
"Trade tracker for {Symbol} skipping {Id}, already in snapshot");
|
||||||
|
|
||||||
_tradeTrackerConnectionLost = LoggerMessage.Define<string>(
|
_tradeTrackerPreSnapshotApplied = LoggerMessage.Define<string, long>(
|
||||||
LogLevel.Warning,
|
LogLevel.Trace,
|
||||||
new EventId(6023, "TradeTrackerConnectionLost"),
|
new EventId(6021, "TradeTrackerPreSnapshotApplied"),
|
||||||
"Trade tracker for {Symbol} connection lost");
|
"Trade tracker for {Symbol} adding {Id} from pre-snapshot");
|
||||||
|
|
||||||
_tradeTrackerConnectionClosed = LoggerMessage.Define<string>(
|
_tradeTrackerTradeAdded = LoggerMessage.Define<string, long>(
|
||||||
LogLevel.Warning,
|
LogLevel.Trace,
|
||||||
new EventId(6024, "TradeTrackerConnectionClosed"),
|
new EventId(6022, "TradeTrackerTradeAdded"),
|
||||||
"Trade tracker for {Symbol} disconnected");
|
"Trade tracker for {Symbol} adding trade {Id}");
|
||||||
|
|
||||||
_tradeTrackerConnectionRestored = LoggerMessage.Define<string>(
|
_tradeTrackerConnectionLost = LoggerMessage.Define<string>(
|
||||||
LogLevel.Information,
|
LogLevel.Warning,
|
||||||
new EventId(6025, "TradeTrackerConnectionRestored"),
|
new EventId(6023, "TradeTrackerConnectionLost"),
|
||||||
"Trade tracker for {Symbol} successfully resynchronized");
|
"Trade tracker for {Symbol} connection lost");
|
||||||
}
|
|
||||||
|
|
||||||
public static void KlineTrackerStatusChanged(this ILogger logger, string symbol, SyncStatus oldStatus, SyncStatus newStatus)
|
_tradeTrackerConnectionClosed = LoggerMessage.Define<string>(
|
||||||
{
|
LogLevel.Warning,
|
||||||
_klineTrackerStatusChanged(logger, symbol, oldStatus, newStatus, null);
|
new EventId(6024, "TradeTrackerConnectionClosed"),
|
||||||
}
|
"Trade tracker for {Symbol} disconnected");
|
||||||
|
|
||||||
public static void KlineTrackerStarting(this ILogger logger, string symbol)
|
_tradeTrackerConnectionRestored = LoggerMessage.Define<string>(
|
||||||
{
|
LogLevel.Information,
|
||||||
_klineTrackerStarting(logger, symbol, null);
|
new EventId(6025, "TradeTrackerConnectionRestored"),
|
||||||
}
|
"Trade tracker for {Symbol} successfully resynchronized");
|
||||||
|
}
|
||||||
|
|
||||||
public static void KlineTrackerStartFailed(this ILogger logger, string symbol, string error, Exception? exception)
|
public static void KlineTrackerStatusChanged(this ILogger logger, string symbol, SyncStatus oldStatus, SyncStatus newStatus)
|
||||||
{
|
{
|
||||||
_klineTrackerStartFailed(logger, symbol, error, exception);
|
_klineTrackerStatusChanged(logger, symbol, oldStatus, newStatus, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void KlineTrackerStarted(this ILogger logger, string symbol)
|
public static void KlineTrackerStarting(this ILogger logger, string symbol)
|
||||||
{
|
{
|
||||||
_klineTrackerStarted(logger, symbol, null);
|
_klineTrackerStarting(logger, symbol, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void KlineTrackerStopping(this ILogger logger, string symbol)
|
public static void KlineTrackerStartFailed(this ILogger logger, string symbol, string error, Exception? exception)
|
||||||
{
|
{
|
||||||
_klineTrackerStopping(logger, symbol, null);
|
_klineTrackerStartFailed(logger, symbol, error, exception);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void KlineTrackerStopped(this ILogger logger, string symbol)
|
public static void KlineTrackerStarted(this ILogger logger, string symbol)
|
||||||
{
|
{
|
||||||
_klineTrackerStopped(logger, symbol, null);
|
_klineTrackerStarted(logger, symbol, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void KlineTrackerInitialDataSet(this ILogger logger, string symbol, DateTime lastTime)
|
public static void KlineTrackerStopping(this ILogger logger, string symbol)
|
||||||
{
|
{
|
||||||
_klineTrackerInitialDataSet(logger, symbol, lastTime, null);
|
_klineTrackerStopping(logger, symbol, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void KlineTrackerKlineUpdated(this ILogger logger, string symbol, DateTime lastTime)
|
public static void KlineTrackerStopped(this ILogger logger, string symbol)
|
||||||
{
|
{
|
||||||
_klineTrackerKlineUpdated(logger, symbol, lastTime, null);
|
_klineTrackerStopped(logger, symbol, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void KlineTrackerKlineAdded(this ILogger logger, string symbol, DateTime lastTime)
|
public static void KlineTrackerInitialDataSet(this ILogger logger, string symbol, DateTime lastTime)
|
||||||
{
|
{
|
||||||
_klineTrackerKlineAdded(logger, symbol, lastTime, null);
|
_klineTrackerInitialDataSet(logger, symbol, lastTime, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void KlineTrackerConnectionLost(this ILogger logger, string symbol)
|
public static void KlineTrackerKlineUpdated(this ILogger logger, string symbol, DateTime lastTime)
|
||||||
{
|
{
|
||||||
_klineTrackerConnectionLost(logger, symbol, null);
|
_klineTrackerKlineUpdated(logger, symbol, lastTime, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void KlineTrackerConnectionClosed(this ILogger logger, string symbol)
|
public static void KlineTrackerKlineAdded(this ILogger logger, string symbol, DateTime lastTime)
|
||||||
{
|
{
|
||||||
_klineTrackerConnectionClosed(logger, symbol, null);
|
_klineTrackerKlineAdded(logger, symbol, lastTime, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void KlineTrackerConnectionRestored(this ILogger logger, string symbol)
|
public static void KlineTrackerConnectionLost(this ILogger logger, string symbol)
|
||||||
{
|
{
|
||||||
_klineTrackerConnectionRestored(logger, symbol, null);
|
_klineTrackerConnectionLost(logger, symbol, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void TradeTrackerStatusChanged(this ILogger logger, string symbol, SyncStatus oldStatus, SyncStatus newStatus)
|
public static void KlineTrackerConnectionClosed(this ILogger logger, string symbol)
|
||||||
{
|
{
|
||||||
_tradeTrackerStatusChanged(logger, symbol, oldStatus, newStatus, null);
|
_klineTrackerConnectionClosed(logger, symbol, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void TradeTrackerStarting(this ILogger logger, string symbol)
|
public static void KlineTrackerConnectionRestored(this ILogger logger, string symbol)
|
||||||
{
|
{
|
||||||
_tradeTrackerStarting(logger, symbol, null);
|
_klineTrackerConnectionRestored(logger, symbol, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void TradeTrackerStartFailed(this ILogger logger, string symbol, string error, Exception? ex)
|
public static void TradeTrackerStatusChanged(this ILogger logger, string symbol, SyncStatus oldStatus, SyncStatus newStatus)
|
||||||
{
|
{
|
||||||
_tradeTrackerStartFailed(logger, symbol, error, ex);
|
_tradeTrackerStatusChanged(logger, symbol, oldStatus, newStatus, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void TradeTrackerStarted(this ILogger logger, string symbol)
|
public static void TradeTrackerStarting(this ILogger logger, string symbol)
|
||||||
{
|
{
|
||||||
_tradeTrackerStarted(logger, symbol, null);
|
_tradeTrackerStarting(logger, symbol, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void TradeTrackerStopping(this ILogger logger, string symbol)
|
public static void TradeTrackerStartFailed(this ILogger logger, string symbol, string error, Exception? ex)
|
||||||
{
|
{
|
||||||
_tradeTrackerStopping(logger, symbol, null);
|
_tradeTrackerStartFailed(logger, symbol, error, ex);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void TradeTrackerStopped(this ILogger logger, string symbol)
|
public static void TradeTrackerStarted(this ILogger logger, string symbol)
|
||||||
{
|
{
|
||||||
_tradeTrackerStopped(logger, symbol, null);
|
_tradeTrackerStarted(logger, symbol, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void TradeTrackerInitialDataSet(this ILogger logger, string symbol, int count, long lastId)
|
public static void TradeTrackerStopping(this ILogger logger, string symbol)
|
||||||
{
|
{
|
||||||
_tradeTrackerInitialDataSet(logger, symbol, count, lastId, null);
|
_tradeTrackerStopping(logger, symbol, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void TradeTrackerPreSnapshotSkip(this ILogger logger, string symbol, long lastId)
|
public static void TradeTrackerStopped(this ILogger logger, string symbol)
|
||||||
{
|
{
|
||||||
_tradeTrackerPreSnapshotSkip(logger, symbol, lastId, null);
|
_tradeTrackerStopped(logger, symbol, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void TradeTrackerPreSnapshotApplied(this ILogger logger, string symbol, long lastId)
|
public static void TradeTrackerInitialDataSet(this ILogger logger, string symbol, int count, long lastId)
|
||||||
{
|
{
|
||||||
_tradeTrackerPreSnapshotApplied(logger, symbol, lastId, null);
|
_tradeTrackerInitialDataSet(logger, symbol, count, lastId, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void TradeTrackerTradeAdded(this ILogger logger, string symbol, long lastId)
|
public static void TradeTrackerPreSnapshotSkip(this ILogger logger, string symbol, long lastId)
|
||||||
{
|
{
|
||||||
_tradeTrackerTradeAdded(logger, symbol, lastId, null);
|
_tradeTrackerPreSnapshotSkip(logger, symbol, lastId, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void TradeTrackerConnectionLost(this ILogger logger, string symbol)
|
public static void TradeTrackerPreSnapshotApplied(this ILogger logger, string symbol, long lastId)
|
||||||
{
|
{
|
||||||
_tradeTrackerConnectionLost(logger, symbol, null);
|
_tradeTrackerPreSnapshotApplied(logger, symbol, lastId, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void TradeTrackerConnectionClosed(this ILogger logger, string symbol)
|
public static void TradeTrackerTradeAdded(this ILogger logger, string symbol, long lastId)
|
||||||
{
|
{
|
||||||
_tradeTrackerConnectionClosed(logger, symbol, null);
|
_tradeTrackerTradeAdded(logger, symbol, lastId, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void TradeTrackerConnectionRestored(this ILogger logger, string symbol)
|
public static void TradeTrackerConnectionLost(this ILogger logger, string symbol)
|
||||||
{
|
{
|
||||||
_tradeTrackerConnectionRestored(logger, symbol, null);
|
_tradeTrackerConnectionLost(logger, symbol, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void TradeTrackerConnectionClosed(this ILogger logger, string symbol)
|
||||||
|
{
|
||||||
|
_tradeTrackerConnectionClosed(logger, symbol, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void TradeTrackerConnectionRestored(this ILogger logger, string symbol)
|
||||||
|
{
|
||||||
|
_tradeTrackerConnectionRestored(logger, symbol, null);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,41 +1,42 @@
|
|||||||
namespace CryptoExchange.Net.Objects;
|
namespace CryptoExchange.Net.Objects
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Proxy info
|
|
||||||
/// </summary>
|
|
||||||
public class ApiProxy
|
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The host address of the proxy
|
/// Proxy info
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string Host { get; set; }
|
public class ApiProxy
|
||||||
/// <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;
|
/// <summary>
|
||||||
Port = port;
|
/// The host address of the proxy
|
||||||
Login = login;
|
/// </summary>
|
||||||
Password = password;
|
public string Host { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// The port of the proxy
|
||||||
|
/// </summary>
|
||||||
|
public int Port { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The login of the proxy
|
||||||
|
/// </summary>
|
||||||
|
public string? Login { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The password of the proxy
|
||||||
|
/// </summary>
|
||||||
|
public string? Password { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create new settings for a proxy
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="host">The proxy hostname/ip</param>
|
||||||
|
/// <param name="port">The proxy port</param>
|
||||||
|
/// <param name="login">The proxy login</param>
|
||||||
|
/// <param name="password">The proxy password</param>
|
||||||
|
public ApiProxy(string host, int port, string? login = null, string? password = null)
|
||||||
|
{
|
||||||
|
Host = host;
|
||||||
|
Port = port;
|
||||||
|
Login = login;
|
||||||
|
Password = password;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,25 +1,30 @@
|
|||||||
namespace CryptoExchange.Net.Objects;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
/// <summary>
|
namespace CryptoExchange.Net.Objects
|
||||||
/// An alias used by the exchange for an asset commonly known by another name
|
|
||||||
/// </summary>
|
|
||||||
public class AssetAlias
|
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The name of the asset on the exchange
|
/// An alias used by the exchange for an asset commonly known by another name
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string ExchangeAssetName { get; set; }
|
public class AssetAlias
|
||||||
/// <summary>
|
|
||||||
/// The name of the asset as it's commonly known
|
|
||||||
/// </summary>
|
|
||||||
public string CommonAssetName { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// ctor
|
|
||||||
/// </summary>
|
|
||||||
public AssetAlias(string exchangeName, string commonName)
|
|
||||||
{
|
{
|
||||||
ExchangeAssetName = exchangeName;
|
/// <summary>
|
||||||
CommonAssetName = commonName;
|
/// The name of the asset on the exchange
|
||||||
|
/// </summary>
|
||||||
|
public string ExchangeAssetName { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// The name of the asset as it's commonly known
|
||||||
|
/// </summary>
|
||||||
|
public string CommonAssetName { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// ctor
|
||||||
|
/// </summary>
|
||||||
|
public AssetAlias(string exchangeName, string commonName)
|
||||||
|
{
|
||||||
|
ExchangeAssetName = exchangeName;
|
||||||
|
CommonAssetName = commonName;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,30 +1,34 @@
|
|||||||
|
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>
|
||||||
/// Defined aliases
|
/// Exchange configuration for asset aliases
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public AssetAlias[] Aliases { get; set; } = [];
|
public class AssetAliasConfiguration
|
||||||
|
{
|
||||||
|
/// <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;
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,133 +1,120 @@
|
|||||||
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>
|
||||||
/// New AsyncResetEvent
|
/// Async auto reset based on Stephen Toub`s implementation
|
||||||
|
/// https://devblogs.microsoft.com/pfxteam/building-async-coordination-primitives-part-2-asyncautoresetevent/
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="initialState"></param>
|
public class AsyncResetEvent : IDisposable
|
||||||
/// <param name="reset"></param>
|
|
||||||
public AsyncResetEvent(bool initialState = false, bool reset = true)
|
|
||||||
{
|
{
|
||||||
_signaled = initialState;
|
private static readonly Task<bool> _completed = Task.FromResult(true);
|
||||||
_reset = reset;
|
private Queue<TaskCompletionSource<bool>> _waits = new Queue<TaskCompletionSource<bool>>();
|
||||||
}
|
private bool _signaled;
|
||||||
|
private readonly bool _reset;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Wait for the AutoResetEvent to be set
|
/// New AsyncResetEvent
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns></returns>
|
/// <param name="initialState"></param>
|
||||||
public async Task<bool> WaitAsync(TimeSpan? timeout = null, CancellationToken ct = default)
|
/// <param name="reset"></param>
|
||||||
{
|
public AsyncResetEvent(bool initialState = false, bool reset = true)
|
||||||
CancellationTokenRegistration registration = default;
|
{
|
||||||
try
|
_signaled = initialState;
|
||||||
|
_reset = reset;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Wait for the AutoResetEvent to be set
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public async Task<bool> WaitAsync(TimeSpan? timeout = null, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
CancellationTokenRegistration registration = default;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Task<bool> waiter = _completed;
|
||||||
|
lock (_waits)
|
||||||
|
{
|
||||||
|
if (_signaled)
|
||||||
|
{
|
||||||
|
if (_reset)
|
||||||
|
_signaled = false;
|
||||||
|
}
|
||||||
|
else if (!ct.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
var tcs = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||||
|
if (timeout.HasValue)
|
||||||
|
{
|
||||||
|
var timeoutSource = new CancellationTokenSource(timeout.Value);
|
||||||
|
var cancellationSource = CancellationTokenSource.CreateLinkedTokenSource(timeoutSource.Token, ct);
|
||||||
|
ct = cancellationSource.Token;
|
||||||
|
}
|
||||||
|
|
||||||
|
registration = ct.Register(() =>
|
||||||
|
{
|
||||||
|
lock (_waits)
|
||||||
|
{
|
||||||
|
tcs.TrySetResult(false);
|
||||||
|
|
||||||
|
// Not the cleanest but it works
|
||||||
|
_waits = new Queue<TaskCompletionSource<bool>>(_waits.Where(i => i != tcs));
|
||||||
|
}
|
||||||
|
}, useSynchronizationContext: false);
|
||||||
|
|
||||||
|
|
||||||
|
_waits.Enqueue(tcs);
|
||||||
|
waiter = tcs.Task;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return await waiter.ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
registration.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Signal a waiter
|
||||||
|
/// </summary>
|
||||||
|
public void Set()
|
||||||
{
|
{
|
||||||
Task<bool> waiter = _completed;
|
|
||||||
lock (_waits)
|
lock (_waits)
|
||||||
{
|
{
|
||||||
if (_signaled)
|
if (!_reset)
|
||||||
{
|
|
||||||
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()
|
|
||||||
{
|
|
||||||
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)
|
|
||||||
{
|
{
|
||||||
|
// Act as ManualResetEvent. Once set keep it signaled and signal everyone who is waiting
|
||||||
_signaled = true;
|
_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()
|
||||||
{
|
|
||||||
Dispose(true);
|
|
||||||
GC.SuppressFinalize(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Dispose
|
|
||||||
/// </summary>
|
|
||||||
public void Dispose(bool disposing)
|
|
||||||
{
|
|
||||||
if (disposing)
|
|
||||||
{
|
{
|
||||||
_waits.Clear();
|
_waits.Clear();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
using CryptoExchange.Net.Interfaces;
|
using CryptoExchange.Net.Interfaces;
|
||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace CryptoExchange.Net.Objects;
|
namespace CryptoExchange.Net.Objects
|
||||||
|
|
||||||
internal class AuthTimeProvider : IAuthTimeProvider
|
|
||||||
{
|
{
|
||||||
public DateTime GetTime() => DateTime.UtcNow;
|
internal class AuthTimeProvider : IAuthTimeProvider
|
||||||
|
{
|
||||||
|
public DateTime GetTime() => DateTime.UtcNow;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,57 +1,58 @@
|
|||||||
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>
|
||||||
/// Compare function
|
/// Comparer for byte order
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="x"></param>
|
public class ByteOrderComparer : IComparer<byte[]>
|
||||||
/// <param name="y"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public int Compare(byte[]? x, byte[]? y)
|
|
||||||
{
|
{
|
||||||
// Shortcuts: If both are null, they are the same.
|
/// <summary>
|
||||||
if (x == null && y == null) return 0;
|
/// Compare function
|
||||||
|
/// </summary>
|
||||||
// If one is null and the other isn't, then the
|
/// <param name="x"></param>
|
||||||
// one that is null is "lesser".
|
/// <param name="y"></param>
|
||||||
if (x == null) return -1;
|
/// <returns></returns>
|
||||||
if (y == null) return 1;
|
public int Compare(byte[]? x, byte[]? y)
|
||||||
|
|
||||||
// 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)
|
|
||||||
{
|
{
|
||||||
// The x and y bytes.
|
// Shortcuts: If both are null, they are the same.
|
||||||
var xByte = x[index];
|
if (x == null && y == null) return 0;
|
||||||
var yByte = y[index];
|
|
||||||
|
|
||||||
// Compare result.
|
// If one is null and the other isn't, then the
|
||||||
var compareResult = Comparer<byte>.Default.Compare(xByte, yByte);
|
// one that is null is "lesser".
|
||||||
|
if (x == null) return -1;
|
||||||
|
if (y == null) return 1;
|
||||||
|
|
||||||
// If not the same, then return the result of the
|
// Both arrays are non-null. Find the shorter
|
||||||
// comparison of the bytes, as they were the same
|
// of the two lengths.
|
||||||
// up until now.
|
var bytesToCompare = Math.Min(x.Length, y.Length);
|
||||||
if (compareResult != 0) return compareResult;
|
|
||||||
|
|
||||||
// They are the same, continue.
|
// Compare the bytes.
|
||||||
|
for (var index = 0; index < bytesToCompare; ++index)
|
||||||
|
{
|
||||||
|
// The x and y bytes.
|
||||||
|
var xByte = x[index];
|
||||||
|
var yByte = y[index];
|
||||||
|
|
||||||
|
// Compare result.
|
||||||
|
var compareResult = Comparer<byte>.Default.Compare(xByte, yByte);
|
||||||
|
|
||||||
|
// If not the same, then return the result of the
|
||||||
|
// comparison of the bytes, as they were the same
|
||||||
|
// up until now.
|
||||||
|
if (compareResult != 0) return compareResult;
|
||||||
|
|
||||||
|
// They are the same, continue.
|
||||||
|
}
|
||||||
|
|
||||||
|
// The first n bytes are the same. Compare lengths.
|
||||||
|
// If the lengths are the same, the arrays
|
||||||
|
// are the same.
|
||||||
|
if (x.Length == y.Length) return 0;
|
||||||
|
|
||||||
|
// Compare lengths.
|
||||||
|
return x.Length < y.Length ? -1 : 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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
@@ -1,20 +1,21 @@
|
|||||||
namespace CryptoExchange.Net.Objects;
|
namespace CryptoExchange.Net.Objects
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Constants
|
|
||||||
/// </summary>
|
|
||||||
public class Constants
|
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Json content type header
|
/// Constants
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public const string JsonContentHeader = "application/json";
|
public class Constants
|
||||||
/// <summary>
|
{
|
||||||
/// Form content type header
|
/// <summary>
|
||||||
/// </summary>
|
/// Json content type header
|
||||||
public const string FormContentHeader = "application/x-www-form-urlencoded";
|
/// </summary>
|
||||||
/// <summary>
|
public const string JsonContentHeader = "application/json";
|
||||||
/// Placeholder key for when request body should be set to the value of this KVP
|
/// <summary>
|
||||||
/// </summary>
|
/// Form content type header
|
||||||
public const string BodyPlaceHolderKey = "_BODY_";
|
/// </summary>
|
||||||
|
public const string FormContentHeader = "application/x-www-form-urlencoded";
|
||||||
|
/// <summary>
|
||||||
|
/// Placeholder key for when request body should be set to the value of this KVP
|
||||||
|
/// </summary>
|
||||||
|
public const string BodyPlaceHolderKey = "_BODY_";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+250
-219
@@ -1,266 +1,297 @@
|
|||||||
namespace CryptoExchange.Net.Objects;
|
using CryptoExchange.Net.Attributes;
|
||||||
|
|
||||||
/// <summary>
|
namespace CryptoExchange.Net.Objects
|
||||||
/// What to do when a request would exceed the rate limit
|
|
||||||
/// </summary>
|
|
||||||
public enum RateLimitingBehaviour
|
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Fail the request
|
/// What to do when a request would exceed the rate limit
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Fail,
|
public enum RateLimitingBehaviour
|
||||||
/// <summary>
|
{
|
||||||
/// Wait till the request can be send
|
/// <summary>
|
||||||
/// </summary>
|
/// Fail the request
|
||||||
Wait
|
/// </summary>
|
||||||
}
|
Fail,
|
||||||
|
/// <summary>
|
||||||
|
/// Wait till the request can be send
|
||||||
|
/// </summary>
|
||||||
|
Wait
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// What to do when a request would exceed the rate limit
|
|
||||||
/// </summary>
|
|
||||||
public enum RateLimitWindowType
|
|
||||||
{
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A sliding window
|
/// What to do when a request would exceed the rate limit
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Sliding,
|
public enum RateLimitWindowType
|
||||||
/// <summary>
|
{
|
||||||
/// A fixed interval window
|
/// <summary>
|
||||||
/// </summary>
|
/// A sliding window
|
||||||
Fixed,
|
/// </summary>
|
||||||
/// <summary>
|
Sliding,
|
||||||
/// A fixed interval starting after the first request
|
/// <summary>
|
||||||
/// </summary>
|
/// A fixed interval window
|
||||||
FixedAfterFirst,
|
/// </summary>
|
||||||
/// <summary>
|
Fixed,
|
||||||
/// Decaying window
|
/// <summary>
|
||||||
/// </summary>
|
/// A fixed interval starting after the first request
|
||||||
Decay
|
/// </summary>
|
||||||
}
|
FixedAfterFirst,
|
||||||
|
/// <summary>
|
||||||
|
/// Decaying window
|
||||||
|
/// </summary>
|
||||||
|
Decay
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Where the parameters for a HttpMethod should be added in a request
|
|
||||||
/// </summary>
|
|
||||||
public enum HttpMethodParameterPosition
|
|
||||||
{
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Parameters in body
|
/// Where the parameters for a HttpMethod should be added in a request
|
||||||
/// </summary>
|
/// </summary>
|
||||||
InBody,
|
public enum HttpMethodParameterPosition
|
||||||
/// <summary>
|
{
|
||||||
/// Parameters in url
|
/// <summary>
|
||||||
/// </summary>
|
/// Parameters in body
|
||||||
InUri
|
/// </summary>
|
||||||
}
|
InBody,
|
||||||
|
/// <summary>
|
||||||
|
/// Parameters in url
|
||||||
|
/// </summary>
|
||||||
|
InUri
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The format of the request body
|
|
||||||
/// </summary>
|
|
||||||
public enum RequestBodyFormat
|
|
||||||
{
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Form data
|
/// The format of the request body
|
||||||
/// </summary>
|
/// </summary>
|
||||||
FormData,
|
public enum RequestBodyFormat
|
||||||
/// <summary>
|
{
|
||||||
/// Json
|
/// <summary>
|
||||||
/// </summary>
|
/// Form data
|
||||||
Json
|
/// </summary>
|
||||||
}
|
FormData,
|
||||||
|
/// <summary>
|
||||||
|
/// Json
|
||||||
|
/// </summary>
|
||||||
|
Json
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Tracker sync status
|
|
||||||
/// </summary>
|
|
||||||
public enum SyncStatus
|
|
||||||
{
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Not connected
|
/// Tracker sync status
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Disconnected,
|
public enum SyncStatus
|
||||||
/// <summary>
|
{
|
||||||
/// Syncing, data connection is being made
|
/// <summary>
|
||||||
/// </summary>
|
/// Not connected
|
||||||
Syncing,
|
/// </summary>
|
||||||
/// <summary>
|
Disconnected,
|
||||||
/// 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>
|
/// Syncing, data connection is being made
|
||||||
PartiallySynced,
|
/// </summary>
|
||||||
/// <summary>
|
Syncing,
|
||||||
/// Synced
|
/// <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.
|
||||||
Synced,
|
/// </summary>
|
||||||
/// <summary>
|
PartiallySynced,
|
||||||
/// Disposed
|
/// <summary>
|
||||||
/// </summary>
|
/// Synced
|
||||||
Disposed
|
/// </summary>
|
||||||
}
|
Synced,
|
||||||
|
/// <summary>
|
||||||
|
/// Disposed
|
||||||
|
/// </summary>
|
||||||
|
Disposed
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Status of the order book
|
|
||||||
/// </summary>
|
|
||||||
public enum OrderBookStatus
|
|
||||||
{
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Not connected
|
/// Status of the order book
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Disconnected,
|
public enum OrderBookStatus
|
||||||
/// <summary>
|
{
|
||||||
/// Connecting
|
/// <summary>
|
||||||
/// </summary>
|
/// Not connected
|
||||||
Connecting,
|
/// </summary>
|
||||||
/// <summary>
|
Disconnected,
|
||||||
/// Reconnecting
|
/// <summary>
|
||||||
/// </summary>
|
/// Connecting
|
||||||
Reconnecting,
|
/// </summary>
|
||||||
/// <summary>
|
Connecting,
|
||||||
/// Syncing data
|
/// <summary>
|
||||||
/// </summary>
|
/// Reconnecting
|
||||||
Syncing,
|
/// </summary>
|
||||||
/// <summary>
|
Reconnecting,
|
||||||
/// Data synced, order book is up to date
|
/// <summary>
|
||||||
/// </summary>
|
/// Syncing data
|
||||||
Synced,
|
/// </summary>
|
||||||
/// <summary>
|
Syncing,
|
||||||
/// Disposing
|
/// <summary>
|
||||||
/// </summary>
|
/// Data synced, order book is up to date
|
||||||
Disposing,
|
/// </summary>
|
||||||
/// <summary>
|
Synced,
|
||||||
/// Disposed
|
/// <summary>
|
||||||
/// </summary>
|
/// Disposing
|
||||||
Disposed
|
/// </summary>
|
||||||
}
|
Disposing,
|
||||||
|
/// <summary>
|
||||||
|
/// Disposed
|
||||||
|
/// </summary>
|
||||||
|
Disposed
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Order book entry type
|
|
||||||
/// </summary>
|
|
||||||
public enum OrderBookEntryType
|
|
||||||
{
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Ask
|
/// Order book entry type
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Ask,
|
public enum OrderBookEntryType
|
||||||
/// <summary>
|
{
|
||||||
/// Bid
|
/// <summary>
|
||||||
/// </summary>
|
/// Ask
|
||||||
Bid
|
/// </summary>
|
||||||
}
|
Ask,
|
||||||
|
/// <summary>
|
||||||
|
/// Bid
|
||||||
|
/// </summary>
|
||||||
|
Bid
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Define how array parameters should be send
|
/// Define how array parameters should be send
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public enum ArrayParametersSerialization
|
public enum ArrayParametersSerialization
|
||||||
#pragma warning disable CS1570 // XML comment has badly formed XML
|
#pragma warning disable CS1570 // XML comment has badly formed XML
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Send as key=value1&key=value2
|
/// Send as key=value1&key=value2
|
||||||
/// </summary>
|
/// </summary>
|
||||||
MultipleValues,
|
MultipleValues,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Send as key[]=value1&key[]=value2
|
/// Send as key[]=value1&key[]=value2
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Array,
|
Array,
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Send as key=[value1, value2]
|
/// Send as key=[value1, value2]
|
||||||
/// </summary>
|
/// </summary>
|
||||||
JsonArray
|
JsonArray
|
||||||
#pragma warning restore CS1570 // XML comment has badly formed XML
|
#pragma warning restore CS1570 // XML comment has badly formed XML
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// How to round
|
|
||||||
/// </summary>
|
|
||||||
public enum RoundingType
|
|
||||||
{
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Round down (flooring)
|
/// How to round
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Down,
|
public enum RoundingType
|
||||||
/// <summary>
|
{
|
||||||
/// Round to closest value
|
/// <summary>
|
||||||
/// </summary>
|
/// Round down (flooring)
|
||||||
Closest,
|
/// </summary>
|
||||||
/// <summary>
|
Down,
|
||||||
/// Round up (ceil)
|
/// <summary>
|
||||||
/// </summary>
|
/// Round to closest value
|
||||||
Up
|
/// </summary>
|
||||||
}
|
Closest,
|
||||||
|
/// <summary>
|
||||||
|
/// Round up (ceil)
|
||||||
|
/// </summary>
|
||||||
|
Up
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Type of the update
|
|
||||||
/// </summary>
|
|
||||||
public enum SocketUpdateType
|
|
||||||
{
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A update
|
/// Type of the update
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Update,
|
public enum SocketUpdateType
|
||||||
/// <summary>
|
{
|
||||||
/// A snapshot, generally send at the start of the connection
|
/// <summary>
|
||||||
/// </summary>
|
/// A update
|
||||||
Snapshot
|
/// </summary>
|
||||||
}
|
Update,
|
||||||
|
/// <summary>
|
||||||
|
/// A snapshot, generally send at the start of the connection
|
||||||
|
/// </summary>
|
||||||
|
Snapshot
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Reconnect policy
|
|
||||||
/// </summary>
|
|
||||||
public enum ReconnectPolicy
|
|
||||||
{
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Reconnect is disabled
|
/// Reconnect policy
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Disabled,
|
public enum ReconnectPolicy
|
||||||
/// <summary>
|
{
|
||||||
/// Fixed delay of `ReconnectInterval` between retries
|
/// <summary>
|
||||||
/// </summary>
|
/// Reconnect is disabled
|
||||||
FixedDelay,
|
/// </summary>
|
||||||
/// <summary>
|
Disabled,
|
||||||
/// Backoff policy of 2^`reconnectAttempt`, where `reconnectAttempt` has a max value of 5
|
/// <summary>
|
||||||
/// </summary>
|
/// Fixed delay of `ReconnectInterval` between retries
|
||||||
ExponentialBackoff
|
/// </summary>
|
||||||
}
|
FixedDelay,
|
||||||
|
/// <summary>
|
||||||
|
/// Backoff policy of 2^`reconnectAttempt`, where `reconnectAttempt` has a max value of 5
|
||||||
|
/// </summary>
|
||||||
|
ExponentialBackoff
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The data source of the result
|
|
||||||
/// </summary>
|
|
||||||
public enum ResultDataSource
|
|
||||||
{
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// From server
|
/// The data source of the result
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Server,
|
public enum ResultDataSource
|
||||||
/// <summary>
|
{
|
||||||
/// From cache
|
/// <summary>
|
||||||
/// </summary>
|
/// From server
|
||||||
Cache
|
/// </summary>
|
||||||
}
|
Server,
|
||||||
|
/// <summary>
|
||||||
|
/// From cache
|
||||||
|
/// </summary>
|
||||||
|
Cache
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Type of exchange
|
|
||||||
/// </summary>
|
|
||||||
public enum ExchangeType
|
|
||||||
{
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Centralized
|
/// Type of exchange
|
||||||
/// </summary>
|
/// </summary>
|
||||||
CEX,
|
public enum ExchangeType
|
||||||
/// <summary>
|
{
|
||||||
/// Decentralized
|
/// <summary>
|
||||||
/// </summary>
|
/// Centralized
|
||||||
DEX
|
/// </summary>
|
||||||
}
|
CEX,
|
||||||
|
/// <summary>
|
||||||
|
/// Decentralized
|
||||||
|
/// </summary>
|
||||||
|
DEX
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Timeout behavior for queries
|
|
||||||
/// </summary>
|
|
||||||
public enum TimeoutBehavior
|
|
||||||
{
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Fail the request
|
/// Timeout behavior for queries
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Fail,
|
public enum TimeoutBehavior
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Fail the request
|
||||||
|
/// </summary>
|
||||||
|
Fail,
|
||||||
|
/// <summary>
|
||||||
|
/// Mark the query as successful
|
||||||
|
/// </summary>
|
||||||
|
Succeed
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Mark the query as successful
|
/// Subscription status
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Succeed
|
public enum SubscriptionStatus
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Pending, waiting before (re)subscription can be started
|
||||||
|
/// </summary>
|
||||||
|
Pending,
|
||||||
|
/// <summary>
|
||||||
|
/// Currently (re)subscribing, will start producing updates soon if subscription is successful
|
||||||
|
/// </summary>
|
||||||
|
Subscribing,
|
||||||
|
/// <summary>
|
||||||
|
/// Subscribed and listening to updates
|
||||||
|
/// </summary>
|
||||||
|
Subscribed,
|
||||||
|
/// <summary>
|
||||||
|
/// Subscription is being closed and will stop producing updates
|
||||||
|
/// </summary>
|
||||||
|
Closing,
|
||||||
|
/// <summary>
|
||||||
|
/// Subscription is closed and will no long produce updates
|
||||||
|
/// </summary>
|
||||||
|
Closed
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+312
-310
@@ -1,331 +1,333 @@
|
|||||||
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>
|
||||||
/// The int error code the server returned; or the http status code int value if there was no error code.<br />
|
/// Base class for errors
|
||||||
/// <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 int? Code
|
public abstract class Error
|
||||||
{
|
{
|
||||||
get
|
|
||||||
{
|
|
||||||
if (_code.HasValue)
|
|
||||||
return _code;
|
|
||||||
|
|
||||||
return int.TryParse(ErrorCode, out var r) ? r : null;
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
set
|
|
||||||
|
/// <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)
|
||||||
{
|
{
|
||||||
_code = value;
|
ErrorCode = errorCode;
|
||||||
|
ErrorType = errorInfo.ErrorType;
|
||||||
|
Message = errorInfo.Message;
|
||||||
|
ErrorDescription = errorInfo.ErrorDescription;
|
||||||
|
IsTransient = errorInfo.IsTransient;
|
||||||
|
Exception = exception;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// String representation
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return ErrorCode != null ? $"[{GetType().Name}.{ErrorType}] {ErrorCode}: {Message ?? ErrorDescription}" : $"[{GetType().Name}.{ErrorType}] {Message ?? ErrorDescription}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The error code returned by the server
|
/// Cant reach server error
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string? ErrorCode { get; set; }
|
public class CantConnectError : Error
|
||||||
/// <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;
|
/// <summary>
|
||||||
ErrorType = errorInfo.ErrorType;
|
/// Default error info
|
||||||
Message = errorInfo.Message;
|
/// </summary>
|
||||||
ErrorDescription = errorInfo.ErrorDescription;
|
protected static readonly ErrorInfo _errorInfo = new ErrorInfo(ErrorType.UnableToConnect, false, "Can't connect to the server");
|
||||||
IsTransient = errorInfo.IsTransient;
|
|
||||||
Exception = exception;
|
/// <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>
|
/// <summary>
|
||||||
/// String representation
|
/// No api credentials provided while trying to access a private endpoint
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns></returns>
|
public class NoApiCredentialsError : Error
|
||||||
public override string ToString()
|
|
||||||
{
|
{
|
||||||
return ErrorCode != null ? $"[{GetType().Name}.{ErrorType}] {ErrorCode}: {Message ?? ErrorDescription}" : $"[{GetType().Name}.{ErrorType}] {Message ?? ErrorDescription}";
|
/// <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) { }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <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) { }
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,37 +1,40 @@
|
|||||||
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 code
|
/// Error evaluator
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string[] ErrorCodes { get; set; }
|
public class ErrorEvaluator
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Evaluation callback for determining the error type
|
|
||||||
/// </summary>
|
|
||||||
public Func<string, string?, ErrorInfo> ErrorTypeEvaluator { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// ctor
|
|
||||||
/// </summary>
|
|
||||||
public ErrorEvaluator(string errorCode, Func<string, string?, ErrorInfo> errorTypeEvaluator)
|
|
||||||
{
|
{
|
||||||
ErrorCodes = [errorCode];
|
/// <summary>
|
||||||
ErrorTypeEvaluator = errorTypeEvaluator;
|
/// Error code
|
||||||
}
|
/// </summary>
|
||||||
|
public string[] ErrorCodes { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// ctor
|
/// Evaluation callback for determining the error type
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public ErrorEvaluator(string[] errorCodes, Func<string, string?, ErrorInfo> errorTypeEvaluator)
|
public Func<string, string?, ErrorInfo> ErrorTypeEvaluator { get; set; }
|
||||||
{
|
|
||||||
ErrorCodes = errorCodes;
|
/// <summary>
|
||||||
ErrorTypeEvaluator = errorTypeEvaluator;
|
/// ctor
|
||||||
|
/// </summary>
|
||||||
|
public ErrorEvaluator(string errorCode, Func<string, string?, ErrorInfo> errorTypeEvaluator)
|
||||||
|
{
|
||||||
|
ErrorCodes = [errorCode];
|
||||||
|
ErrorTypeEvaluator = errorTypeEvaluator;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// ctor
|
||||||
|
/// </summary>
|
||||||
|
public ErrorEvaluator(string[] errorCodes, Func<string, string?, ErrorInfo> errorTypeEvaluator)
|
||||||
|
{
|
||||||
|
ErrorCodes = errorCodes;
|
||||||
|
ErrorTypeEvaluator = errorTypeEvaluator;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,55 +1,58 @@
|
|||||||
namespace CryptoExchange.Net.Objects.Errors;
|
using System;
|
||||||
|
|
||||||
/// <summary>
|
namespace CryptoExchange.Net.Objects.Errors
|
||||||
/// Error info
|
|
||||||
/// </summary>
|
|
||||||
public record ErrorInfo
|
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Unknown error info
|
/// Error info
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static ErrorInfo Unknown { get; } = new ErrorInfo(ErrorType.Unknown, false, "Unknown error", []);
|
public record ErrorInfo
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The server error code
|
|
||||||
/// </summary>
|
|
||||||
public string[] ErrorCodes { get; set; }
|
|
||||||
/// <summary>
|
|
||||||
/// Error description
|
|
||||||
/// </summary>
|
|
||||||
public string? ErrorDescription { get; set; }
|
|
||||||
/// <summary>
|
|
||||||
/// The error type
|
|
||||||
/// </summary>
|
|
||||||
public ErrorType ErrorType { get; set; }
|
|
||||||
/// <summary>
|
|
||||||
/// Whether the error is transient and can be retried
|
|
||||||
/// </summary>
|
|
||||||
public bool IsTransient { get; set; }
|
|
||||||
/// <summary>
|
|
||||||
/// Server response message
|
|
||||||
/// </summary>
|
|
||||||
public string? Message { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// ctor
|
|
||||||
/// </summary>
|
|
||||||
public ErrorInfo(ErrorType errorType, string description)
|
|
||||||
{
|
{
|
||||||
ErrorCodes = [];
|
/// <summary>
|
||||||
ErrorType = errorType;
|
/// Unknown error info
|
||||||
IsTransient = false;
|
/// </summary>
|
||||||
ErrorDescription = description;
|
public static ErrorInfo Unknown { get; } = new ErrorInfo(ErrorType.Unknown, false, "Unknown error", []);
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// ctor
|
/// The server error code
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public ErrorInfo(ErrorType errorType, bool isTransient, string description, params string[] errorCodes)
|
public string[] ErrorCodes { get; set; }
|
||||||
{
|
/// <summary>
|
||||||
ErrorCodes = errorCodes;
|
/// Error description
|
||||||
ErrorType = errorType;
|
/// </summary>
|
||||||
IsTransient = isTransient;
|
public string? ErrorDescription { get; set; }
|
||||||
ErrorDescription = description;
|
/// <summary>
|
||||||
|
/// The error type
|
||||||
|
/// </summary>
|
||||||
|
public ErrorType ErrorType { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// Whether the error is transient and can be retried
|
||||||
|
/// </summary>
|
||||||
|
public bool IsTransient { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// Server response message
|
||||||
|
/// </summary>
|
||||||
|
public string? Message { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// ctor
|
||||||
|
/// </summary>
|
||||||
|
public ErrorInfo(ErrorType errorType, string description)
|
||||||
|
{
|
||||||
|
ErrorCodes = [];
|
||||||
|
ErrorType = errorType;
|
||||||
|
IsTransient = false;
|
||||||
|
ErrorDescription = description;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// ctor
|
||||||
|
/// </summary>
|
||||||
|
public ErrorInfo(ErrorType errorType, bool isTransient, string description, params string[] errorCodes)
|
||||||
|
{
|
||||||
|
ErrorCodes = errorCodes;
|
||||||
|
ErrorType = errorType;
|
||||||
|
IsTransient = isTransient;
|
||||||
|
ErrorDescription = description;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user