1
0
mirror of https://github.com/JKorf/CryptoExchange.Net.git synced 2026-08-13 17:33:02 +00:00

Compare commits

..

24 Commits

Author SHA1 Message Date
JKorf 478cb537fa wip 2025-03-18 12:11:34 +01:00
Jkorf 6a82a7a411 wip 2025-03-18 11:59:59 +01:00
Jkorf f17d9032fb wip 2025-03-17 14:48:21 +01:00
Jkorf a8907c7ea5 wip 2025-03-14 15:10:17 +01:00
Jkorf f151b961a5 wip 2025-03-14 09:02:19 +01:00
Jkorf 932cc4864e Add ClientOrderId shared apis 2025-03-11 15:15:43 +01:00
Jkorf 9f6eb9f0d0 wip 2025-03-11 08:44:14 +01:00
Jkorf a0e2f78a6a Made ReplaceConverter abstract as it needs to be implemented in client libs, removed unsupported JsonConverterCtor attribute 2025-03-07 13:09:50 +01:00
Jkorf 142cba5cca Add GenerateClientOrderId to shared order clients 2025-03-06 16:13:20 +01:00
Jkorf 54aa6907f9 Some small fixes 2025-03-06 16:13:03 +01:00
Jkorf bf103ce9d1 Added ExchangeSymbolCache, added SharedSymbol property on all relevant response models 2025-03-06 16:12:48 +01:00
Jkorf ec1f469848 Added GenerateClientOrderId and ParseSymbol to ISharedClient 2025-03-05 16:12:32 +01:00
Jkorf c1dde521af Added OptionalExchangeParameters, UnsupportedOptionalParameters and Supported to shared EndpointOptions 2025-03-05 16:12:18 +01:00
Jkorf c070c425e7 Added MaxLeverage to shared FuturesSymbol 2025-03-05 16:11:36 +01:00
Jkorf 2f6889880a Restore CryptoRestClient 2025-03-05 16:11:09 +01:00
Jkorf 48d3e15f39 Added CallResult.SuccessResult as static object for use instead of new CallResult(null) 2025-03-05 15:03:09 +01:00
Jkorf 1999a27b41 Added Pass property to ApiCredentials 2025-03-05 14:47:13 +01:00
Jkorf e138d7d263 Removed Newtonsoft.Json dependency 2025-03-05 14:40:33 +01:00
Jkorf 0d4ab96e19 Removed old SendRequestAsync methods 2025-03-05 13:34:10 +01:00
Jkorf 7219441ec4 Updated IEnumerable responses to arrays Part 2 2025-03-05 13:25:15 +01:00
Jkorf 89c87b19e1 Updated IEnumerable responses to arrays 2025-03-05 13:13:55 +01:00
Jkorf 89bd091848 merge 2025-03-05 12:59:42 +01:00
Jkorf 6aff618770 Removed deprecated Common implementation 2025-03-05 11:52:33 +01:00
Jan Korf 355d111a55 Feature/aot (#232)
AOT support
2025-03-05 11:50:15 +01:00
385 changed files with 21508 additions and 24860 deletions
@@ -1,530 +0,0 @@
using CryptoExchange.Net.Converters.MessageParsing;
using CryptoExchange.Net.Interfaces;
using CryptoExchange.Net.Objects;
using ProtoBuf;
using ProtoBuf.Meta;
using System;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Runtime.InteropServices.ComTypes;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
namespace CryptoExchange.Net.Converters.Protobuf
{
/// <summary>
/// System.Text.Json message accessor
/// </summary>
#if NET5_0_OR_GREATER
public abstract class ProtobufMessageAccessor<
[DynamicallyAccessedMembers(
#if NET8_0_OR_GREATER
DynamicallyAccessedMemberTypes.NonPublicConstructors |
DynamicallyAccessedMemberTypes.PublicFields |
DynamicallyAccessedMemberTypes.NonPublicFields |
DynamicallyAccessedMemberTypes.NonPublicNestedTypes |
DynamicallyAccessedMemberTypes.PublicProperties |
DynamicallyAccessedMemberTypes.NonPublicProperties |
#endif
DynamicallyAccessedMemberTypes.PublicNestedTypes |
DynamicallyAccessedMemberTypes.NonPublicMethods |
DynamicallyAccessedMemberTypes.PublicMethods
)]
TIntermediateType> : IMessageAccessor
#else
public abstract class ProtobufMessageAccessor<TIntermediateType> : IMessageAccessor
#endif
{
/// <summary>
/// The intermediate deserialization object
/// </summary>
protected TIntermediateType? _intermediateType;
/// <summary>
/// Runtime type model
/// </summary>
protected RuntimeTypeModel _model;
/// <inheritdoc />
public bool IsValid { get; set; }
/// <inheritdoc />
public abstract bool OriginalDataAvailable { get; }
/// <inheritdoc />
public object? Underlying => _intermediateType;
/// <summary>
/// ctor
/// </summary>
public ProtobufMessageAccessor(RuntimeTypeModel model)
{
_model = model;
}
/// <inheritdoc />
public NodeType? GetNodeType()
{
throw new NotImplementedException();
}
/// <inheritdoc />
public NodeType? GetNodeType(MessagePath path)
{
if (_intermediateType == null)
throw new InvalidOperationException("Data not read");
object? value = _intermediateType;
foreach (var step in path)
{
if (value == null)
break;
if (step.Type == 0)
{
// array index
}
else if (step.Type == 1)
{
// property value
#pragma warning disable IL2075 // Type is already annotated
value = value.GetType().GetProperty(step.Property!)?.GetValue(value);
#pragma warning restore
}
else
{
// property name
}
}
if (value == null)
return null;
var valueType = value.GetType();
if (valueType.IsArray)
return NodeType.Array;
if (IsSimple(valueType))
return NodeType.Value;
return NodeType.Object;
}
private static bool IsSimple(Type type)
{
if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>))
{
// nullable type, check if the nested type is simple.
return IsSimple(type.GetGenericArguments()[0]);
}
return type.IsPrimitive
|| type.IsEnum
|| type == typeof(string)
|| type == typeof(decimal);
}
/// <inheritdoc />
#if NET5_0_OR_GREATER
[UnconditionalSuppressMessage("AssemblyLoadTrimming", "IL2075:RequiresUnreferencedCode", Justification = "JsonSerializerOptions provided here has TypeInfoResolver set")]
#endif
public T? GetValue<T>(MessagePath path)
{
if (_intermediateType == null)
throw new InvalidOperationException("Data not read");
object? value = _intermediateType;
foreach(var step in path)
{
if (value == null)
break;
if (step.Type == 0)
{
// array index
}
else if (step.Type == 1)
{
// property value
#pragma warning disable IL2075 // Type is already annotated
value = value.GetType().GetProperty(step.Property!)?.GetValue(value);
#pragma warning restore
}
else
{
// property name
}
}
return (T?)value;
}
/// <inheritdoc />
public T?[]? GetValues<T>(MessagePath path)
{
throw new NotImplementedException();
}
/// <inheritdoc />
public abstract string GetOriginalString();
/// <inheritdoc />
public abstract void Clear();
/// <inheritdoc />
#if NET5_0_OR_GREATER
[UnconditionalSuppressMessage("AssemblyLoadTrimming", "IL2092:RequiresUnreferencedCode", Justification = "JsonSerializerOptions provided here has TypeInfoResolver set")]
[UnconditionalSuppressMessage("AssemblyLoadTrimming", "IL2095:RequiresUnreferencedCode", Justification = "JsonSerializerOptions provided here has TypeInfoResolver set")]
#endif
public abstract CallResult<object> Deserialize(
#if NET5_0_OR_GREATER
[DynamicallyAccessedMembers(
#if NET8_0_OR_GREATER
DynamicallyAccessedMemberTypes.NonPublicConstructors |
DynamicallyAccessedMemberTypes.PublicFields |
DynamicallyAccessedMemberTypes.NonPublicFields |
DynamicallyAccessedMemberTypes.NonPublicNestedTypes |
DynamicallyAccessedMemberTypes.PublicProperties |
DynamicallyAccessedMemberTypes.NonPublicProperties |
DynamicallyAccessedMemberTypes.PublicConstructors |
#endif
DynamicallyAccessedMemberTypes.PublicNestedTypes |
DynamicallyAccessedMemberTypes.NonPublicMethods |
DynamicallyAccessedMemberTypes.PublicMethods
)]
#endif
Type type, MessagePath? path = null);
/// <inheritdoc />
#if NET5_0_OR_GREATER
[UnconditionalSuppressMessage("AssemblyLoadTrimming", "IL2092:RequiresUnreferencedCode", Justification = "JsonSerializerOptions provided here has TypeInfoResolver set")]
[UnconditionalSuppressMessage("AssemblyLoadTrimming", "IL2095:RequiresUnreferencedCode", Justification = "JsonSerializerOptions provided here has TypeInfoResolver set")]
#endif
public abstract CallResult<T> Deserialize<
#if NET5_0_OR_GREATER
[DynamicallyAccessedMembers(
#if NET8_0_OR_GREATER
DynamicallyAccessedMemberTypes.NonPublicConstructors |
DynamicallyAccessedMemberTypes.PublicFields |
DynamicallyAccessedMemberTypes.NonPublicFields |
DynamicallyAccessedMemberTypes.NonPublicNestedTypes |
DynamicallyAccessedMemberTypes.PublicProperties |
DynamicallyAccessedMemberTypes.NonPublicProperties |
DynamicallyAccessedMemberTypes.PublicConstructors |
#endif
DynamicallyAccessedMemberTypes.PublicNestedTypes |
DynamicallyAccessedMemberTypes.NonPublicMethods |
DynamicallyAccessedMemberTypes.PublicMethods
)]
#endif
T>(MessagePath? path = null);
}
/// <summary>
/// System.Text.Json stream message accessor
/// </summary>
public class ProtobufStreamMessageAccessor<
#if NET5_0_OR_GREATER
[DynamicallyAccessedMembers(
#if NET8_0_OR_GREATER
DynamicallyAccessedMemberTypes.NonPublicConstructors |
DynamicallyAccessedMemberTypes.PublicFields |
DynamicallyAccessedMemberTypes.NonPublicFields |
DynamicallyAccessedMemberTypes.NonPublicNestedTypes |
DynamicallyAccessedMemberTypes.PublicProperties |
DynamicallyAccessedMemberTypes.NonPublicProperties |
DynamicallyAccessedMemberTypes.PublicConstructors |
#endif
DynamicallyAccessedMemberTypes.PublicNestedTypes |
DynamicallyAccessedMemberTypes.NonPublicMethods |
DynamicallyAccessedMemberTypes.PublicMethods
)]
#endif
TIntermediate> : ProtobufMessageAccessor<TIntermediate>, IStreamMessageAccessor
{
private Stream? _stream;
/// <inheritdoc />
public override bool OriginalDataAvailable => _stream?.CanSeek == true;
/// <summary>
/// ctor
/// </summary>
public ProtobufStreamMessageAccessor(RuntimeTypeModel model) : base(model)
{
}
/// <inheritdoc />
#if NET5_0_OR_GREATER
[UnconditionalSuppressMessage("AssemblyLoadTrimming", "IL2092:RequiresUnreferencedCode", Justification = "JsonSerializerOptions provided here has TypeInfoResolver set")]
[UnconditionalSuppressMessage("AssemblyLoadTrimming", "IL2095:RequiresUnreferencedCode", Justification = "JsonSerializerOptions provided here has TypeInfoResolver set")]
#endif
public override CallResult<object> Deserialize(
#if NET5_0_OR_GREATER
[DynamicallyAccessedMembers(
#if NET8_0_OR_GREATER
DynamicallyAccessedMemberTypes.NonPublicConstructors |
DynamicallyAccessedMemberTypes.PublicFields |
DynamicallyAccessedMemberTypes.NonPublicFields |
DynamicallyAccessedMemberTypes.NonPublicNestedTypes |
DynamicallyAccessedMemberTypes.PublicProperties |
DynamicallyAccessedMemberTypes.NonPublicProperties |
DynamicallyAccessedMemberTypes.PublicConstructors |
#endif
DynamicallyAccessedMemberTypes.PublicNestedTypes |
DynamicallyAccessedMemberTypes.NonPublicMethods |
DynamicallyAccessedMemberTypes.PublicMethods
)]
#endif
Type type, MessagePath? path = null)
{
try
{
var result = _model.Deserialize(type, _stream);
return new CallResult<object>(result);
}
catch (Exception ex)
{
return new CallResult<object>(new DeserializeError("Protobuf deserialization failed: " + ex.Message, ex));
}
}
/// <inheritdoc />
#if NET5_0_OR_GREATER
[UnconditionalSuppressMessage("AssemblyLoadTrimming", "IL2092:RequiresUnreferencedCode", Justification = "JsonSerializerOptions provided here has TypeInfoResolver set")]
[UnconditionalSuppressMessage("AssemblyLoadTrimming", "IL2095:RequiresUnreferencedCode", Justification = "JsonSerializerOptions provided here has TypeInfoResolver set")]
#endif
public override CallResult<T> Deserialize<
#if NET5_0_OR_GREATER
[DynamicallyAccessedMembers(
#if NET8_0_OR_GREATER
DynamicallyAccessedMemberTypes.NonPublicConstructors |
DynamicallyAccessedMemberTypes.PublicFields |
DynamicallyAccessedMemberTypes.NonPublicFields |
DynamicallyAccessedMemberTypes.NonPublicNestedTypes |
DynamicallyAccessedMemberTypes.PublicProperties |
DynamicallyAccessedMemberTypes.NonPublicProperties |
DynamicallyAccessedMemberTypes.PublicConstructors |
#endif
DynamicallyAccessedMemberTypes.PublicNestedTypes |
DynamicallyAccessedMemberTypes.NonPublicMethods |
DynamicallyAccessedMemberTypes.PublicMethods
)]
#endif
T>(MessagePath? path = null)
{
try
{
var result = _model.Deserialize<T>(_stream);
return new CallResult<T>(result);
}
catch(Exception ex)
{
return new CallResult<T>(new DeserializeError("Protobuf deserialization failed: " + ex.Message, ex));
}
}
/// <inheritdoc />
public 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
{
_intermediateType = _model.Deserialize<TIntermediate>(_stream);
IsValid = true;
return Task.FromResult(CallResult.SuccessResult);
}
catch (Exception ex)
{
// Not a json message
IsValid = false;
return Task.FromResult(new CallResult(new DeserializeError("Protobuf 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;
_intermediateType = default;
}
}
/// <summary>
/// Protobuf byte message accessor
/// </summary>
public class ProtobufByteMessageAccessor<
#if NET5_0_OR_GREATER
[DynamicallyAccessedMembers(
#if NET8_0_OR_GREATER
DynamicallyAccessedMemberTypes.NonPublicConstructors |
DynamicallyAccessedMemberTypes.PublicFields |
DynamicallyAccessedMemberTypes.NonPublicFields |
DynamicallyAccessedMemberTypes.NonPublicNestedTypes |
DynamicallyAccessedMemberTypes.PublicProperties |
DynamicallyAccessedMemberTypes.NonPublicProperties |
DynamicallyAccessedMemberTypes.PublicConstructors |
#endif
DynamicallyAccessedMemberTypes.PublicNestedTypes |
DynamicallyAccessedMemberTypes.NonPublicMethods |
DynamicallyAccessedMemberTypes.PublicMethods
)]
#endif
TIntermediate> : ProtobufMessageAccessor<TIntermediate>, IByteMessageAccessor
{
private ReadOnlyMemory<byte> _bytes;
/// <summary>
/// ctor
/// </summary>
public ProtobufByteMessageAccessor(RuntimeTypeModel model) : base(model)
{
}
/// <inheritdoc />
#if NET5_0_OR_GREATER
[UnconditionalSuppressMessage("AssemblyLoadTrimming", "IL2092:RequiresUnreferencedCode", Justification = "JsonSerializerOptions provided here has TypeInfoResolver set")]
[UnconditionalSuppressMessage("AssemblyLoadTrimming", "IL2095:RequiresUnreferencedCode", Justification = "JsonSerializerOptions provided here has TypeInfoResolver set")]
#endif
public override CallResult<object> Deserialize(
#if NET5_0_OR_GREATER
[UnconditionalSuppressMessage("AssemblyLoadTrimming", "IL2092:RequiresUnreferencedCode", Justification = "JsonSerializerOptions provided here has TypeInfoResolver set")]
[UnconditionalSuppressMessage("AssemblyLoadTrimming", "IL2095:RequiresUnreferencedCode", Justification = "JsonSerializerOptions provided here has TypeInfoResolver set")]
[DynamicallyAccessedMembers(
#if NET8_0_OR_GREATER
DynamicallyAccessedMemberTypes.NonPublicConstructors |
DynamicallyAccessedMemberTypes.PublicFields |
DynamicallyAccessedMemberTypes.NonPublicFields |
DynamicallyAccessedMemberTypes.NonPublicNestedTypes |
DynamicallyAccessedMemberTypes.PublicProperties |
DynamicallyAccessedMemberTypes.NonPublicProperties |
DynamicallyAccessedMemberTypes.PublicConstructors |
#endif
DynamicallyAccessedMemberTypes.PublicNestedTypes |
DynamicallyAccessedMemberTypes.NonPublicMethods |
DynamicallyAccessedMemberTypes.PublicMethods
)]
#endif
Type type, MessagePath? path = null)
{
try
{
using var stream = new MemoryStream(_bytes.ToArray());
stream.Position = 0;
var result = _model.Deserialize(type, stream);
return new CallResult<object>(result);
}
catch (Exception ex)
{
return new CallResult<object>(new DeserializeError("Protobuf deserialization failed: " + ex.Message, ex));
}
}
/// <inheritdoc />
#if NET5_0_OR_GREATER
[UnconditionalSuppressMessage("AssemblyLoadTrimming", "IL2092:RequiresUnreferencedCode", Justification = "JsonSerializerOptions provided here has TypeInfoResolver set")]
[UnconditionalSuppressMessage("AssemblyLoadTrimming", "IL2095:RequiresUnreferencedCode", Justification = "JsonSerializerOptions provided here has TypeInfoResolver set")]
#endif
#if NET5_0_OR_GREATER
public override CallResult<T> Deserialize<
[UnconditionalSuppressMessage("AssemblyLoadTrimming", "IL2092:RequiresUnreferencedCode", Justification = "JsonSerializerOptions provided here has TypeInfoResolver set")]
[UnconditionalSuppressMessage("AssemblyLoadTrimming", "IL2095:RequiresUnreferencedCode", Justification = "JsonSerializerOptions provided here has TypeInfoResolver set")]
[DynamicallyAccessedMembers(
#if NET8_0_OR_GREATER
DynamicallyAccessedMemberTypes.NonPublicConstructors |
DynamicallyAccessedMemberTypes.PublicFields |
DynamicallyAccessedMemberTypes.NonPublicFields |
DynamicallyAccessedMemberTypes.NonPublicNestedTypes |
DynamicallyAccessedMemberTypes.PublicProperties |
DynamicallyAccessedMemberTypes.NonPublicProperties |
DynamicallyAccessedMemberTypes.PublicConstructors |
#endif
DynamicallyAccessedMemberTypes.PublicNestedTypes |
DynamicallyAccessedMemberTypes.NonPublicMethods |
DynamicallyAccessedMemberTypes.PublicMethods
)]
T>(MessagePath? path = null)
#else
public override CallResult<T> Deserialize<T>(MessagePath? path = null)
#endif
{
try
{
var result = _model.Deserialize<T>(_bytes);
return new CallResult<T>(result);
}
catch (Exception ex)
{
return new CallResult<T>(new DeserializeError("Protobuf deserialization failed: " + ex.Message, ex));
}
}
/// <inheritdoc />
public CallResult Read(ReadOnlyMemory<byte> data)
{
_bytes = data;
try
{
_intermediateType = _model.Deserialize<TIntermediate>(data);
IsValid = true;
return CallResult.SuccessResult;
}
catch (Exception ex)
{
// Not a json message
IsValid = false;
return new CallResult(new DeserializeError("Protobuf deserialization failed: " + ex.Message, ex));
}
}
/// <inheritdoc />
public override string GetOriginalString() =>
// NetStandard 2.0 doesn't support GetString from a ReadonlySpan<byte>, so use ToArray there instead
#if NETSTANDARD2_0
Encoding.UTF8.GetString(_bytes.ToArray());
#else
Encoding.UTF8.GetString(_bytes.Span);
#endif
/// <inheritdoc />
public override bool OriginalDataAvailable => true;
/// <inheritdoc />
public override void Clear()
{
_bytes = null;
_intermediateType = default;
}
}
}
@@ -1,53 +0,0 @@
using CryptoExchange.Net.Interfaces;
using ProtoBuf.Meta;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Reflection;
namespace CryptoExchange.Net.Converters.Protobuf
{
/// <inheritdoc />
public class ProtobufMessageSerializer : IByteMessageSerializer
{
private RuntimeTypeModel _model;
/// <summary>
/// ctor
/// </summary>
public ProtobufMessageSerializer(RuntimeTypeModel model)
{
_model = model;
}
/// <inheritdoc />
#if NET5_0_OR_GREATER
[UnconditionalSuppressMessage("AssemblyLoadTrimming", "IL2092:RequiresUnreferencedCode", Justification = "JsonSerializerOptions provided here has TypeInfoResolver set")]
[UnconditionalSuppressMessage("AssemblyLoadTrimming", "IL2095:RequiresUnreferencedCode", Justification = "JsonSerializerOptions provided here has TypeInfoResolver set")]
#endif
#if NET5_0_OR_GREATER
public byte[] Serialize<
[DynamicallyAccessedMembers(
#if NET8_0_OR_GREATER
DynamicallyAccessedMemberTypes.NonPublicConstructors |
DynamicallyAccessedMemberTypes.PublicFields |
DynamicallyAccessedMemberTypes.NonPublicFields |
DynamicallyAccessedMemberTypes.NonPublicNestedTypes |
DynamicallyAccessedMemberTypes.PublicProperties |
DynamicallyAccessedMemberTypes.NonPublicProperties |
DynamicallyAccessedMemberTypes.PublicConstructors |
#endif
DynamicallyAccessedMemberTypes.PublicNestedTypes |
DynamicallyAccessedMemberTypes.NonPublicMethods |
DynamicallyAccessedMemberTypes.PublicMethods
)]
T>(T message)
#else
public byte[] Serialize<T>(T message)
#endif
{
using var memoryStream = new MemoryStream();
_model.Serialize(memoryStream, message);
return memoryStream.ToArray();
}
}
}
@@ -1,47 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netstandard2.0;netstandard2.1;net8.0;net9.0</TargetFrameworks>
</PropertyGroup>
<PropertyGroup>
<PackageId>CryptoExchange.Net.Protobuf</PackageId>
<Authors>JKorf</Authors>
<Description>Protobuf support for CryptoExchange.Net</Description>
<PackageVersion>9.6.0</PackageVersion>
<AssemblyVersion>9.6.0</AssemblyVersion>
<FileVersion>9.6.0</FileVersion>
<PackageRequireLicenseAcceptance>false</PackageRequireLicenseAcceptance>
<PackageTags>CryptoExchange;CryptoExchange.Net</PackageTags>
<RepositoryType>git</RepositoryType>
<RepositoryUrl>https://github.com/JKorf/CryptoExchange.Net.git</RepositoryUrl>
<PackageProjectUrl>https://github.com/JKorf/CryptoExchange.Net/tree/master/CryptoExchange.Net.Protobuf</PackageProjectUrl>
<NeutralLanguage>en</NeutralLanguage>
<PackageReadmeFile>README.md</PackageReadmeFile>
<PackageIcon>icon.png</PackageIcon>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<PackageReleaseNotes>https://github.com/JKorf/CryptoExchange.Net?tab=readme-ov-file#release-notes</PackageReleaseNotes>
<Nullable>enable</Nullable>
<LangVersion>12.0</LangVersion>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
</PropertyGroup>
<ItemGroup>
<None Include="..\CryptoExchange.Net\Icon\icon.png" Pack="true" PackagePath="\" />
<None Include="README.md" Pack="true" PackagePath="\" />
</ItemGroup>
<PropertyGroup Label="AOT" Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'net7.0'))">
<IsAotCompatible>true</IsAotCompatible>
</PropertyGroup>
<PropertyGroup Label="Deterministic Build" Condition="'$(Configuration)' == 'Release'">
<PublishRepositoryUrl>true</PublishRepositoryUrl>
<IncludeSymbols>true</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
<EmbedUntrackedSources>true</EmbedUntrackedSources>
<ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
</PropertyGroup>
<PropertyGroup>
<DocumentationFile>CryptoExchange.Net.Protobuf.xml</DocumentationFile>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="CryptoExchange.Net" Version="9.6.0" />
<PackageReference Include="protobuf-net" Version="3.2.56" />
</ItemGroup>
</Project>
@@ -1,128 +0,0 @@
<?xml version="1.0"?>
<doc>
<assembly>
<name>CryptoExchange.Net.Protobuf</name>
</assembly>
<members>
<member name="T:CryptoExchange.Net.Converters.Protobuf.ProtobufMessageAccessor`1">
<summary>
System.Text.Json message accessor
</summary>
</member>
<member name="F:CryptoExchange.Net.Converters.Protobuf.ProtobufMessageAccessor`1._intermediateType">
<summary>
The intermediate deserialization object
</summary>
</member>
<member name="F:CryptoExchange.Net.Converters.Protobuf.ProtobufMessageAccessor`1._model">
<summary>
Runtime type model
</summary>
</member>
<member name="P:CryptoExchange.Net.Converters.Protobuf.ProtobufMessageAccessor`1.IsValid">
<inheritdoc />
</member>
<member name="P:CryptoExchange.Net.Converters.Protobuf.ProtobufMessageAccessor`1.OriginalDataAvailable">
<inheritdoc />
</member>
<member name="P:CryptoExchange.Net.Converters.Protobuf.ProtobufMessageAccessor`1.Underlying">
<inheritdoc />
</member>
<member name="M:CryptoExchange.Net.Converters.Protobuf.ProtobufMessageAccessor`1.#ctor(ProtoBuf.Meta.RuntimeTypeModel)">
<summary>
ctor
</summary>
</member>
<member name="M:CryptoExchange.Net.Converters.Protobuf.ProtobufMessageAccessor`1.GetNodeType">
<inheritdoc />
</member>
<member name="M:CryptoExchange.Net.Converters.Protobuf.ProtobufMessageAccessor`1.GetNodeType(CryptoExchange.Net.Converters.MessageParsing.MessagePath)">
<inheritdoc />
</member>
<member name="M:CryptoExchange.Net.Converters.Protobuf.ProtobufMessageAccessor`1.GetValue``1(CryptoExchange.Net.Converters.MessageParsing.MessagePath)">
<inheritdoc />
</member>
<member name="M:CryptoExchange.Net.Converters.Protobuf.ProtobufMessageAccessor`1.GetValues``1(CryptoExchange.Net.Converters.MessageParsing.MessagePath)">
<inheritdoc />
</member>
<member name="M:CryptoExchange.Net.Converters.Protobuf.ProtobufMessageAccessor`1.GetOriginalString">
<inheritdoc />
</member>
<member name="M:CryptoExchange.Net.Converters.Protobuf.ProtobufMessageAccessor`1.Clear">
<inheritdoc />
</member>
<member name="M:CryptoExchange.Net.Converters.Protobuf.ProtobufMessageAccessor`1.Deserialize(System.Type,System.Nullable{CryptoExchange.Net.Converters.MessageParsing.MessagePath})">
<inheritdoc />
</member>
<member name="M:CryptoExchange.Net.Converters.Protobuf.ProtobufMessageAccessor`1.Deserialize``1(System.Nullable{CryptoExchange.Net.Converters.MessageParsing.MessagePath})">
<inheritdoc />
</member>
<member name="T:CryptoExchange.Net.Converters.Protobuf.ProtobufStreamMessageAccessor`1">
<summary>
System.Text.Json stream message accessor
</summary>
</member>
<member name="P:CryptoExchange.Net.Converters.Protobuf.ProtobufStreamMessageAccessor`1.OriginalDataAvailable">
<inheritdoc />
</member>
<member name="M:CryptoExchange.Net.Converters.Protobuf.ProtobufStreamMessageAccessor`1.#ctor(ProtoBuf.Meta.RuntimeTypeModel)">
<summary>
ctor
</summary>
</member>
<member name="M:CryptoExchange.Net.Converters.Protobuf.ProtobufStreamMessageAccessor`1.Deserialize(System.Type,System.Nullable{CryptoExchange.Net.Converters.MessageParsing.MessagePath})">
<inheritdoc />
</member>
<member name="M:CryptoExchange.Net.Converters.Protobuf.ProtobufStreamMessageAccessor`1.Deserialize``1(System.Nullable{CryptoExchange.Net.Converters.MessageParsing.MessagePath})">
<inheritdoc />
</member>
<member name="M:CryptoExchange.Net.Converters.Protobuf.ProtobufStreamMessageAccessor`1.Read(System.IO.Stream,System.Boolean)">
<inheritdoc />
</member>
<member name="M:CryptoExchange.Net.Converters.Protobuf.ProtobufStreamMessageAccessor`1.GetOriginalString">
<inheritdoc />
</member>
<member name="M:CryptoExchange.Net.Converters.Protobuf.ProtobufStreamMessageAccessor`1.Clear">
<inheritdoc />
</member>
<member name="T:CryptoExchange.Net.Converters.Protobuf.ProtobufByteMessageAccessor`1">
<summary>
Protobuf byte message accessor
</summary>
</member>
<member name="M:CryptoExchange.Net.Converters.Protobuf.ProtobufByteMessageAccessor`1.#ctor(ProtoBuf.Meta.RuntimeTypeModel)">
<summary>
ctor
</summary>
</member>
<member name="M:CryptoExchange.Net.Converters.Protobuf.ProtobufByteMessageAccessor`1.Deserialize(System.Type,System.Nullable{CryptoExchange.Net.Converters.MessageParsing.MessagePath})">
<inheritdoc />
</member>
<member name="M:CryptoExchange.Net.Converters.Protobuf.ProtobufByteMessageAccessor`1.Deserialize``1(System.Nullable{CryptoExchange.Net.Converters.MessageParsing.MessagePath})">
<inheritdoc />
</member>
<member name="M:CryptoExchange.Net.Converters.Protobuf.ProtobufByteMessageAccessor`1.Read(System.ReadOnlyMemory{System.Byte})">
<inheritdoc />
</member>
<member name="M:CryptoExchange.Net.Converters.Protobuf.ProtobufByteMessageAccessor`1.GetOriginalString">
<inheritdoc />
</member>
<member name="P:CryptoExchange.Net.Converters.Protobuf.ProtobufByteMessageAccessor`1.OriginalDataAvailable">
<inheritdoc />
</member>
<member name="M:CryptoExchange.Net.Converters.Protobuf.ProtobufByteMessageAccessor`1.Clear">
<inheritdoc />
</member>
<member name="T:CryptoExchange.Net.Converters.Protobuf.ProtobufMessageSerializer">
<inheritdoc />
</member>
<member name="M:CryptoExchange.Net.Converters.Protobuf.ProtobufMessageSerializer.#ctor(ProtoBuf.Meta.RuntimeTypeModel)">
<summary>
ctor
</summary>
</member>
<member name="M:CryptoExchange.Net.Converters.Protobuf.ProtobufMessageSerializer.Serialize``1(``0)">
<inheritdoc />
</member>
</members>
</doc>
-22
View File
@@ -1,22 +0,0 @@
# ![.CryptoExchange.Net](https://github.com/JKorf/CryptoExchange.Net/blob/ffcb7db8ff597c2f14982d68464015a748815580/CryptoExchange.Net/Icon/icon.png) CryptoExchange.Net.Proto
[![.NET](https://img.shields.io/github/actions/workflow/status/JKorf/CryptoExchange.Net/dotnet.yml?style=for-the-badge)](https://github.com/JKorf/CryptoExchange.Net/actions/workflows/dotnet.yml) [![Nuget downloads](https://img.shields.io/nuget/dt/CryptoExchange.Net.Protobuf.svg?style=for-the-badge)](https://www.nuget.org/packages/CryptoExchange.Net.Protobuf) ![License](https://img.shields.io/github/license/JKorf/CryptoExchange.Net?style=for-the-badge)
Protobuf support for CryptoExchange.Net.
## Release notes
* Version 9.6.0 - 25 Aug 2025
* Updated CryptoExchange.Net version to 9.6.0
* Version 9.5.0 - 19 Aug 2025
* Updated CryptoExchange.Net version to 9.5.0
* Version 9.4.0 - 04 Aug 2025
* Updated CryptoExchange.Net to version 9.4.0, see https://github.com/JKorf/CryptoExchange.Net/releases/
* Updated protobuf-net package version to 3.2.56
* Version 9.3.0 - 23 Jul 2025
* Updated CryptoExchange.Net to version 9.3.0, see https://github.com/JKorf/CryptoExchange.Net/releases/
* Version 9.2.0 - 14 Jul 2025
* Initial release
+14 -15
View File
@@ -1,5 +1,4 @@
using CryptoExchange.Net.Objects; using CryptoExchange.Net.Objects;
using CryptoExchange.Net.Objects.Errors;
using NUnit.Framework; using NUnit.Framework;
using NUnit.Framework.Legacy; using NUnit.Framework.Legacy;
using System; using System;
@@ -17,9 +16,9 @@ namespace CryptoExchange.Net.UnitTests
[Test] [Test]
public void TestBasicErrorCallResult() public void TestBasicErrorCallResult()
{ {
var result = new CallResult(new ServerError("TestError", ErrorInfo.Unknown)); var result = new CallResult(new ServerError("TestError"));
ClassicAssert.AreSame(result.Error.ErrorCode, "TestError"); ClassicAssert.AreSame(result.Error.Message, "TestError");
ClassicAssert.IsFalse(result); ClassicAssert.IsFalse(result);
ClassicAssert.IsFalse(result.Success); ClassicAssert.IsFalse(result.Success);
} }
@@ -37,9 +36,9 @@ namespace CryptoExchange.Net.UnitTests
[Test] [Test]
public void TestCallResultError() public void TestCallResultError()
{ {
var result = new CallResult<object>(new ServerError("TestError", ErrorInfo.Unknown)); var result = new CallResult<object>(new ServerError("TestError"));
ClassicAssert.AreSame(result.Error.ErrorCode, "TestError"); ClassicAssert.AreSame(result.Error.Message, "TestError");
ClassicAssert.IsNull(result.Data); ClassicAssert.IsNull(result.Data);
ClassicAssert.IsFalse(result); ClassicAssert.IsFalse(result);
ClassicAssert.IsFalse(result.Success); ClassicAssert.IsFalse(result.Success);
@@ -72,11 +71,11 @@ namespace CryptoExchange.Net.UnitTests
[Test] [Test]
public void TestCallResultErrorAs() public void TestCallResultErrorAs()
{ {
var result = new CallResult<TestObjectResult>(new ServerError("TestError", ErrorInfo.Unknown)); var result = new CallResult<TestObjectResult>(new ServerError("TestError"));
var asResult = result.As<TestObject2>(default); var asResult = result.As<TestObject2>(default);
ClassicAssert.IsNotNull(asResult.Error); ClassicAssert.IsNotNull(asResult.Error);
ClassicAssert.AreSame(asResult.Error.ErrorCode, "TestError"); ClassicAssert.AreSame(asResult.Error.Message, "TestError");
ClassicAssert.IsNull(asResult.Data); ClassicAssert.IsNull(asResult.Data);
ClassicAssert.IsFalse(asResult); ClassicAssert.IsFalse(asResult);
ClassicAssert.IsFalse(asResult.Success); ClassicAssert.IsFalse(asResult.Success);
@@ -85,11 +84,11 @@ namespace CryptoExchange.Net.UnitTests
[Test] [Test]
public void TestCallResultErrorAsError() public void TestCallResultErrorAsError()
{ {
var result = new CallResult<TestObjectResult>(new ServerError("TestError", ErrorInfo.Unknown)); var result = new CallResult<TestObjectResult>(new ServerError("TestError"));
var asResult = result.AsError<TestObject2>(new ServerError("TestError2", ErrorInfo.Unknown)); var asResult = result.AsError<TestObject2>(new ServerError("TestError2"));
ClassicAssert.IsNotNull(asResult.Error); ClassicAssert.IsNotNull(asResult.Error);
ClassicAssert.AreSame(asResult.Error.ErrorCode, "TestError2"); ClassicAssert.AreSame(asResult.Error.Message, "TestError2");
ClassicAssert.IsNull(asResult.Data); ClassicAssert.IsNull(asResult.Data);
ClassicAssert.IsFalse(asResult); ClassicAssert.IsFalse(asResult);
ClassicAssert.IsFalse(asResult.Success); ClassicAssert.IsFalse(asResult.Success);
@@ -98,11 +97,11 @@ namespace CryptoExchange.Net.UnitTests
[Test] [Test]
public void TestWebCallResultErrorAsError() public void TestWebCallResultErrorAsError()
{ {
var result = new WebCallResult<TestObjectResult>(new ServerError("TestError", ErrorInfo.Unknown)); var result = new WebCallResult<TestObjectResult>(new ServerError("TestError"));
var asResult = result.AsError<TestObject2>(new ServerError("TestError2", ErrorInfo.Unknown)); var asResult = result.AsError<TestObject2>(new ServerError("TestError2"));
ClassicAssert.IsNotNull(asResult.Error); ClassicAssert.IsNotNull(asResult.Error);
ClassicAssert.AreSame(asResult.Error.ErrorCode, "TestError2"); ClassicAssert.AreSame(asResult.Error.Message, "TestError2");
ClassicAssert.IsNull(asResult.Data); ClassicAssert.IsNull(asResult.Data);
ClassicAssert.IsFalse(asResult); ClassicAssert.IsFalse(asResult);
ClassicAssert.IsFalse(asResult.Success); ClassicAssert.IsFalse(asResult.Success);
@@ -125,10 +124,10 @@ namespace CryptoExchange.Net.UnitTests
ResultDataSource.Server, ResultDataSource.Server,
new TestObjectResult(), new TestObjectResult(),
null); null);
var asResult = result.AsError<TestObject2>(new ServerError("TestError2", ErrorInfo.Unknown)); var asResult = result.AsError<TestObject2>(new ServerError("TestError2"));
ClassicAssert.IsNotNull(asResult.Error); ClassicAssert.IsNotNull(asResult.Error);
Assert.That(asResult.Error.ErrorCode == "TestError2"); Assert.That(asResult.Error.Message == "TestError2");
Assert.That(asResult.ResponseStatusCode == System.Net.HttpStatusCode.OK); Assert.That(asResult.ResponseStatusCode == System.Net.HttpStatusCode.OK);
Assert.That(asResult.ResponseTime == TimeSpan.FromSeconds(1)); Assert.That(asResult.ResponseTime == TimeSpan.FromSeconds(1));
Assert.That(asResult.RequestUrl == "https://test.com/api"); Assert.That(asResult.RequestUrl == "https://test.com/api");
@@ -6,14 +6,10 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<None Include="..\CryptoExchange.Net\.editorconfig" Link=".editorconfig" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0"></PackageReference>
</ItemGroup>
<ItemGroup>
<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" />
<PackageReference Include="NUnit" Version="4.3.2"></PackageReference> <PackageReference Include="NUnit" Version="4.2.2"></PackageReference>
<PackageReference Include="NUnit3TestAdapter" Version="5.0.0"></PackageReference> <PackageReference Include="NUnit3TestAdapter" Version="4.6.0"></PackageReference>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
@@ -80,6 +80,8 @@ namespace CryptoExchange.Net.UnitTests
ClassicAssert.IsFalse(result.Success); ClassicAssert.IsFalse(result.Success);
Assert.That(result.Error != null); Assert.That(result.Error != null);
Assert.That(result.Error is ServerError); Assert.That(result.Error is ServerError);
Assert.That(result.Error.Message.Contains("Invalid request"));
Assert.That(result.Error.Message.Contains("123"));
} }
[TestCase] [TestCase]
@@ -96,7 +98,7 @@ namespace CryptoExchange.Net.UnitTests
ClassicAssert.IsFalse(result.Success); ClassicAssert.IsFalse(result.Success);
Assert.That(result.Error != null); Assert.That(result.Error != null);
Assert.That(result.Error is ServerError); Assert.That(result.Error is ServerError);
Assert.That(result.Error.ErrorCode == "123"); Assert.That(result.Error.Code == 123);
Assert.That(result.Error.Message == "Invalid request"); Assert.That(result.Error.Message == "Invalid request");
} }
@@ -182,7 +184,7 @@ namespace CryptoExchange.Net.UnitTests
[TestCase("/sapi/test1", true)] [TestCase("/sapi/test1", true)]
[TestCase("/sapi/test2", true)] [TestCase("/sapi/test2", true)]
[TestCase("/api/test1", false)] [TestCase("/api/test1", false)]
[TestCase("sapi/test1", true)] [TestCase("sapi/test1", false)]
[TestCase("/sapi/", true)] [TestCase("/sapi/", true)]
public async Task PartialEndpointRateLimiterEndpoints(string endpoint, bool expectLimiting) public async Task PartialEndpointRateLimiterEndpoints(string endpoint, bool expectLimiting)
{ {
@@ -7,7 +7,6 @@ using System.Text.Json.Serialization;
using NUnit.Framework.Legacy; using NUnit.Framework.Legacy;
using CryptoExchange.Net.Converters; using CryptoExchange.Net.Converters;
using CryptoExchange.Net.Testing.Comparers; using CryptoExchange.Net.Testing.Comparers;
using CryptoExchange.Net.SharedApis;
namespace CryptoExchange.Net.UnitTests namespace CryptoExchange.Net.UnitTests
{ {
@@ -224,17 +223,13 @@ namespace CryptoExchange.Net.UnitTests
[TestCase(null, null)] [TestCase(null, null)]
[TestCase("", null)] [TestCase("", null)]
[TestCase("null", null)] [TestCase("null", null)]
[TestCase("nan", null)]
[TestCase("1E+2", 100)] [TestCase("1E+2", 100)]
[TestCase("1E-2", 0.01)] [TestCase("1E-2", 0.01)]
[TestCase("Infinity", 999)] // 999 is workaround for not being able to specify decimal.MinValue [TestCase("80228162514264337593543950335", -999)] // -999 is workaround for not being able to specify decimal.MaxValue
[TestCase("-Infinity", -999)] // -999 is workaround for not being able to specify decimal.MaxValue
[TestCase("80228162514264337593543950335", 999)] // 999 is workaround for not being able to specify decimal.MaxValue
[TestCase("-80228162514264337593543950335", -999)] // -999 is workaround for not being able to specify decimal.MaxValue
public void TestDecimalConverterString(string value, decimal? expected) public void TestDecimalConverterString(string value, decimal? expected)
{ {
var result = JsonSerializer.Deserialize<STJDecimalObject>("{ \"test\": \""+ value + "\"}"); var result = JsonSerializer.Deserialize<STJDecimalObject>("{ \"test\": \""+ value + "\"}");
Assert.That(result.Test, Is.EqualTo(expected == -999 ? decimal.MinValue : expected == 999 ? decimal.MaxValue: expected)); Assert.That(result.Test, Is.EqualTo(expected == -999 ? decimal.MaxValue : expected));
} }
[TestCase("1", 1)] [TestCase("1", 1)]
@@ -274,18 +269,9 @@ namespace CryptoExchange.Net.UnitTests
TestInternal = new Test TestInternal = new Test
{ {
Prop1 = 10 Prop1 = 10
}, }
Prop8 = new Test3
{
Prop31 = 5,
Prop32 = "101"
},
}; };
var options = new JsonSerializerOptions()
{
TypeInfoResolver = new SerializationContext()
};
var serialized = JsonSerializer.Serialize(data); var serialized = JsonSerializer.Serialize(data);
var deserialized = JsonSerializer.Deserialize<Test>(serialized); var deserialized = JsonSerializer.Deserialize<Test>(serialized);
@@ -300,42 +286,6 @@ namespace CryptoExchange.Net.UnitTests
Assert.That(deserialized.Prop6.Prop32, Is.EqualTo("789")); Assert.That(deserialized.Prop6.Prop32, Is.EqualTo("789"));
Assert.That(deserialized.Prop7, Is.EqualTo(TestEnum.Two)); Assert.That(deserialized.Prop7, Is.EqualTo(TestEnum.Two));
Assert.That(deserialized.TestInternal.Prop1, Is.EqualTo(10)); Assert.That(deserialized.TestInternal.Prop1, Is.EqualTo(10));
Assert.That(deserialized.Prop8.Prop31, Is.EqualTo(5));
Assert.That(deserialized.Prop8.Prop32, Is.EqualTo("101"));
}
[TestCase(TradingMode.Spot, "ETH", "USDT", null)]
[TestCase(TradingMode.PerpetualLinear, "ETH", "USDT", null)]
[TestCase(TradingMode.DeliveryLinear, "ETH", "USDT", 1748432430)]
public void TestSharedSymbolConversion(TradingMode tradingMode, string baseAsset, string quoteAsset, int? deliverTime)
{
DateTime? time = deliverTime == null ? null : DateTimeConverter.ParseFromDouble(deliverTime.Value);
var symbol = new SharedSymbol(tradingMode, baseAsset, quoteAsset, time);
var serialized = JsonSerializer.Serialize(symbol);
var restored = JsonSerializer.Deserialize<SharedSymbol>(serialized);
Assert.That(restored.TradingMode, Is.EqualTo(symbol.TradingMode));
Assert.That(restored.BaseAsset, Is.EqualTo(symbol.BaseAsset));
Assert.That(restored.QuoteAsset, Is.EqualTo(symbol.QuoteAsset));
Assert.That(restored.DeliverTime, Is.EqualTo(symbol.DeliverTime));
}
[TestCase(0.1, null, null)]
[TestCase(0.1, 0.1, null)]
[TestCase(0.1, 0.1, 0.1)]
[TestCase(null, 0.1, null)]
[TestCase(null, 0.1, 0.1)]
public void TestSharedQuantityConversion(double? baseQuantity, double? quoteQuantity, double? contractQuantity)
{
var symbol = new SharedOrderQuantity((decimal?)baseQuantity, (decimal?)quoteQuantity, (decimal?)contractQuantity);
var serialized = JsonSerializer.Serialize(symbol);
var restored = JsonSerializer.Deserialize<SharedOrderQuantity>(serialized);
Assert.That(restored.QuantityInBaseAsset, Is.EqualTo(symbol.QuantityInBaseAsset));
Assert.That(restored.QuantityInQuoteAsset, Is.EqualTo(symbol.QuantityInQuoteAsset));
Assert.That(restored.QuantityInContracts, Is.EqualTo(symbol.QuantityInContracts));
} }
} }
@@ -373,7 +323,7 @@ namespace CryptoExchange.Net.UnitTests
public bool Value { get; set; } public bool Value { get; set; }
} }
[JsonConverter(typeof(ArrayConverter<Test>))] [JsonConverter(typeof(ArrayConverter<Test, SerializationContext>))]
record Test record Test
{ {
[ArrayProperty(0)] [ArrayProperty(0)]
@@ -394,11 +344,9 @@ namespace CryptoExchange.Net.UnitTests
public TestEnum? Prop7 { get; set; } public TestEnum? Prop7 { get; set; }
[ArrayProperty(7)] [ArrayProperty(7)]
public Test TestInternal { get; set; } public Test TestInternal { get; set; }
[ArrayProperty(8), JsonConversion]
public Test3 Prop8 { get; set; }
} }
[JsonConverter(typeof(ArrayConverter<Test2>))] [JsonConverter(typeof(ArrayConverter<Test2, SerializationContext>))]
record Test2 record Test2
{ {
[ArrayProperty(0)] [ArrayProperty(0)]
@@ -1,5 +1,4 @@
using CryptoExchange.Net.Objects; using CryptoExchange.Net.Objects;
using CryptoExchange.Net.Objects.Errors;
using CryptoExchange.Net.Objects.Sockets; using CryptoExchange.Net.Objects.Sockets;
using CryptoExchange.Net.Sockets; using CryptoExchange.Net.Sockets;
using System; using System;
@@ -32,19 +31,21 @@ namespace CryptoExchange.Net.UnitTests.TestImplementations.Sockets
internal class TestChannelQuery : Query<SubResponse> internal class TestChannelQuery : Query<SubResponse>
{ {
public override HashSet<string> ListenerIdentifiers { get; set; }
public TestChannelQuery(string channel, string request, bool authenticated, int weight = 1) : base(request, authenticated, weight) public TestChannelQuery(string channel, string request, bool authenticated, int weight = 1) : base(request, authenticated, weight)
{ {
MessageMatcher = MessageMatcher.Create<SubResponse>(request + "-" + channel, HandleMessage); ListenerIdentifiers = new HashSet<string> { request + "-" + channel };
} }
public CallResult<SubResponse> HandleMessage(SocketConnection connection, DataEvent<SubResponse> message) public override CallResult<SubResponse> HandleMessage(SocketConnection connection, DataEvent<SubResponse> message)
{ {
if (!message.Data.Status.Equals("confirmed", StringComparison.OrdinalIgnoreCase)) if (!message.Data.Status.Equals("confirmed", StringComparison.OrdinalIgnoreCase))
{ {
return new CallResult<SubResponse>(new ServerError(ErrorInfo.Unknown with { Message = message.Data.Status })); return new CallResult<SubResponse>(new ServerError(message.Data.Status));
} }
return message.ToCallResult(); return base.HandleMessage(connection, message);
} }
} }
} }
@@ -9,9 +9,11 @@ namespace CryptoExchange.Net.UnitTests.TestImplementations.Sockets
{ {
internal class TestQuery : Query<object> internal class TestQuery : Query<object>
{ {
public override HashSet<string> ListenerIdentifiers { get; set; }
public TestQuery(string identifier, object request, bool authenticated, int weight = 1) : base(request, authenticated, weight) public TestQuery(string identifier, object request, bool authenticated, int weight = 1) : base(request, authenticated, weight)
{ {
MessageMatcher = MessageMatcher.Create<object>(identifier); ListenerIdentifiers = new HashSet<string> { identifier };
} }
} }
} }
@@ -15,20 +15,22 @@ namespace CryptoExchange.Net.UnitTests.TestImplementations.Sockets
{ {
private readonly Action<DataEvent<T>> _handler; private readonly Action<DataEvent<T>> _handler;
public override HashSet<string> ListenerIdentifiers { get; set; } = new HashSet<string> { "update-topic" };
public TestSubscription(ILogger logger, Action<DataEvent<T>> handler) : base(logger, false) public TestSubscription(ILogger logger, Action<DataEvent<T>> handler) : base(logger, false)
{ {
_handler = handler; _handler = handler;
MessageMatcher = MessageMatcher.Create<T>("update-topic", DoHandleMessage);
} }
public CallResult DoHandleMessage(SocketConnection connection, DataEvent<T> message) public override CallResult DoHandleMessage(SocketConnection connection, DataEvent<object> message)
{ {
_handler.Invoke(message); var data = (T)message.Data;
_handler.Invoke(message.As(data));
return new CallResult(null); return new CallResult(null);
} }
protected override Query GetSubQuery(SocketConnection connection) => new TestQuery("sub", new object(), false, 1); public override Type GetMessageType(IMessageAccessor message) => typeof(T);
protected override Query GetUnsubQuery(SocketConnection connection) => new TestQuery("unsub", new object(), false, 1); public override Query GetSubQuery(SocketConnection connection) => new TestQuery("sub", new object(), false, 1);
public override Query GetUnsubQuery() => new TestQuery("unsub", new object(), false, 1);
} }
} }
@@ -15,20 +15,24 @@ namespace CryptoExchange.Net.UnitTests.TestImplementations.Sockets
private readonly Action<DataEvent<T>> _handler; private readonly Action<DataEvent<T>> _handler;
private readonly string _channel; private readonly string _channel;
public override HashSet<string> ListenerIdentifiers { get; set; }
public TestSubscriptionWithResponseCheck(string channel, Action<DataEvent<T>> handler) : base(Mock.Of<ILogger>(), false) public TestSubscriptionWithResponseCheck(string channel, Action<DataEvent<T>> handler) : base(Mock.Of<ILogger>(), false)
{ {
MessageMatcher = MessageMatcher.Create<T>(channel, DoHandleMessage); ListenerIdentifiers = new HashSet<string>() { channel };
_handler = handler; _handler = handler;
_channel = channel; _channel = channel;
} }
public CallResult DoHandleMessage(SocketConnection connection, DataEvent<T> message) public override CallResult DoHandleMessage(SocketConnection connection, DataEvent<object> message)
{ {
_handler.Invoke(message); var data = (T)message.Data;
_handler.Invoke(message.As(data));
return new CallResult(null); return new CallResult(null);
} }
protected override Query GetSubQuery(SocketConnection connection) => new TestChannelQuery(_channel, "subscribe", false, 1); public override Type GetMessageType(IMessageAccessor message) => typeof(T);
protected override Query GetUnsubQuery(SocketConnection connection) => new TestChannelQuery(_channel, "unsubscribe", false, 1); public override Query GetSubQuery(SocketConnection connection) => new TestChannelQuery(_channel, "subscribe", false, 1);
public override Query GetUnsubQuery() => new TestChannelQuery(_channel, "unsubscribe", false, 1);
} }
} }
@@ -10,12 +10,10 @@ 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 CryptoExchange.Net.Objects.Errors;
using CryptoExchange.Net.Objects.Options; using CryptoExchange.Net.Objects.Options;
using CryptoExchange.Net.SharedApis; using CryptoExchange.Net.SharedApis;
using CryptoExchange.Net.UnitTests.TestImplementations; using CryptoExchange.Net.UnitTests.TestImplementations;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
namespace CryptoExchange.Net.UnitTests namespace CryptoExchange.Net.UnitTests
{ {
@@ -26,14 +24,12 @@ namespace CryptoExchange.Net.UnitTests
public TestBaseClient(): base(null, "Test") public TestBaseClient(): base(null, "Test")
{ {
var options = new TestClientOptions(); var options = new TestClientOptions();
_logger = NullLogger.Instance;
Initialize(options); Initialize(options);
SubClient = AddApiClient(new TestSubClient(options, new RestApiOptions())); SubClient = AddApiClient(new TestSubClient(options, new RestApiOptions()));
} }
public TestBaseClient(TestClientOptions exchangeOptions) : base(null, "Test") public TestBaseClient(TestClientOptions exchangeOptions) : base(null, "Test")
{ {
_logger = NullLogger.Instance;
Initialize(exchangeOptions); Initialize(exchangeOptions);
SubClient = AddApiClient(new TestSubClient(exchangeOptions, new RestApiOptions())); SubClient = AddApiClient(new TestSubClient(exchangeOptions, new RestApiOptions()));
} }
@@ -56,7 +52,7 @@ namespace CryptoExchange.Net.UnitTests
var accessor = CreateAccessor(); var accessor = CreateAccessor();
var valid = accessor.Read(stream, true).Result; var valid = accessor.Read(stream, true).Result;
if (!valid) if (!valid)
return new CallResult<T>(new ServerError(ErrorInfo.Unknown with { Message = data })); return new CallResult<T>(new ServerError(data));
var deserializeResult = accessor.Deserialize<T>(); var deserializeResult = accessor.Deserialize<T>();
return deserializeResult; return deserializeResult;
@@ -78,10 +74,10 @@ namespace CryptoExchange.Net.UnitTests
{ {
} }
public override void ProcessRequest(RestApiClient apiClient, RestRequestConfiguration requestConfig) public override void AuthenticateRequest(RestApiClient apiClient, Uri uri, HttpMethod method, ref IDictionary<string, object> uriParams, ref IDictionary<string, object> bodyParams, ref Dictionary<string, string> headers, bool auth, ArrayParametersSerialization arraySerialization, HttpMethodParameterPosition parameterPosition, RequestBodyFormat bodyFormat)
{ {
} }
public string GetKey() => _credentials.Key; public string GetKey() => _credentials.Key;
public string GetSecret() => _credentials.Secret; public string GetSecret() => _credentials.Secret;
} }
@@ -18,7 +18,6 @@ using Microsoft.Extensions.Options;
using System.Linq; using System.Linq;
using CryptoExchange.Net.Converters.SystemTextJson; using CryptoExchange.Net.Converters.SystemTextJson;
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
using CryptoExchange.Net.Objects.Errors;
namespace CryptoExchange.Net.UnitTests.TestImplementations namespace CryptoExchange.Net.UnitTests.TestImplementations
{ {
@@ -194,11 +193,11 @@ namespace CryptoExchange.Net.UnitTests.TestImplementations
return await SendAsync<T>("http://www.test.com", new RequestDefinition("/", HttpMethod.Get) { Weight = 0 }, null, ct); return await SendAsync<T>("http://www.test.com", new RequestDefinition("/", HttpMethod.Get) { Weight = 0 }, null, ct);
} }
protected override Error ParseErrorResponse(int httpStatusCode, KeyValuePair<string, string[]>[] responseHeaders, IMessageAccessor accessor, Exception exception) protected override Error ParseErrorResponse(int httpStatusCode, KeyValuePair<string, string[]>[] responseHeaders, IMessageAccessor accessor)
{ {
var errorData = accessor.Deserialize<TestError>(); var errorData = accessor.Deserialize<TestError>();
return new ServerError(errorData.Data.ErrorCode, GetErrorInfo(errorData.Data.ErrorCode, errorData.Data.ErrorMessage)); return new ServerError(errorData.Data.ErrorCode, errorData.Data.ErrorMessage);
} }
public override TimeSpan? GetTimeOffset() public override TimeSpan? GetTimeOffset()
@@ -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;
// }
//}
@@ -17,7 +17,6 @@ using CryptoExchange.Net.Testing.Implementations;
using CryptoExchange.Net.SharedApis; using CryptoExchange.Net.SharedApis;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using CryptoExchange.Net.Converters.SystemTextJson; using CryptoExchange.Net.Converters.SystemTextJson;
using System.Net.WebSockets;
namespace CryptoExchange.Net.UnitTests.TestImplementations namespace CryptoExchange.Net.UnitTests.TestImplementations
{ {
@@ -99,7 +98,7 @@ namespace CryptoExchange.Net.UnitTests.TestImplementations
} }
protected internal override IByteMessageAccessor CreateAccessor(WebSocketMessageType type) => new SystemTextJsonByteMessageAccessor(new System.Text.Json.JsonSerializerOptions()); protected internal override IByteMessageAccessor CreateAccessor() => new SystemTextJsonByteMessageAccessor(new System.Text.Json.JsonSerializerOptions());
protected internal override IMessageSerializer CreateSerializer() => new SystemTextJsonMessageSerializer(new System.Text.Json.JsonSerializerOptions()); protected internal override IMessageSerializer CreateSerializer() => new SystemTextJsonMessageSerializer(new System.Text.Json.JsonSerializerOptions());
/// <inheritdoc /> /// <inheritdoc />
@@ -115,12 +114,12 @@ namespace CryptoExchange.Net.UnitTests.TestImplementations
public CallResult ConnectSocketSub(SocketConnection sub) public CallResult ConnectSocketSub(SocketConnection sub)
{ {
return ConnectSocketAsync(sub, default).Result; return ConnectSocketAsync(sub).Result;
} }
public override string GetListenerIdentifier(IMessageAccessor message) public override string GetListenerIdentifier(IMessageAccessor message)
{ {
if (!message.IsValid) if (!message.IsJson)
{ {
return "topic"; return "topic";
} }
-6
View File
@@ -15,8 +15,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ConsoleClient", "Examples\C
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SharedClients", "Examples\SharedClients\SharedClients.csproj", "{988A87EF-EAEA-4313-A6CF-FA869813D5AB}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SharedClients", "Examples\SharedClients\SharedClients.csproj", "{988A87EF-EAEA-4313-A6CF-FA869813D5AB}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CryptoExchange.Net.Protobuf", "CryptoExchange.Net.Protobuf\CryptoExchange.Net.Protobuf.csproj", "{CC6A807A-9183-6F41-8EF1-8A70172B0E83}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU
@@ -43,10 +41,6 @@ Global
{988A87EF-EAEA-4313-A6CF-FA869813D5AB}.Debug|Any CPU.Build.0 = Debug|Any CPU {988A87EF-EAEA-4313-A6CF-FA869813D5AB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{988A87EF-EAEA-4313-A6CF-FA869813D5AB}.Release|Any CPU.ActiveCfg = Release|Any CPU {988A87EF-EAEA-4313-A6CF-FA869813D5AB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{988A87EF-EAEA-4313-A6CF-FA869813D5AB}.Release|Any CPU.Build.0 = Release|Any CPU {988A87EF-EAEA-4313-A6CF-FA869813D5AB}.Release|Any CPU.Build.0 = Release|Any CPU
{CC6A807A-9183-6F41-8EF1-8A70172B0E83}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{CC6A807A-9183-6F41-8EF1-8A70172B0E83}.Debug|Any CPU.Build.0 = Debug|Any CPU
{CC6A807A-9183-6F41-8EF1-8A70172B0E83}.Release|Any CPU.ActiveCfg = Release|Any CPU
{CC6A807A-9183-6F41-8EF1-8A70172B0E83}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE
-183
View File
@@ -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
+5 -4
View File
@@ -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
{
}
} }
+18 -17
View File
@@ -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,489 @@
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. Output parameters should include the providedParameters input
/// </summary>
/// <param name="apiClient">The Api client sending the request</param>
/// <param name="uri">The uri for the request</param>
/// <param name="method">The method of the request</param>
/// <param name="auth">If the requests should be authenticated</param>
/// <param name="arraySerialization">Array serialization type</param>
/// <param name="requestBodyFormat">The formatting of the request body</param>
/// <param name="uriParameters">Parameters that need to be in the Uri of the request. Should include the provided parameters if they should go in the uri</param>
/// <param name="bodyParameters">Parameters that need to be in the body of the request. Should include the provided parameters if they should go in the body</param>
/// <param name="headers">The headers that should be send with the request</param>
/// <param name="parameterPosition">The position where the providedParameters should go</param>
public abstract void AuthenticateRequest(
RestApiClient apiClient,
Uri uri,
HttpMethod method,
ref IDictionary<string, object>? uriParameters,
ref IDictionary<string, object>? bodyParameters,
ref Dictionary<string, string>? headers,
bool auth,
ArrayParametersSerialization arraySerialization,
HttpMethodParameterPosition parameterPosition,
RequestBodyFormat requestBodyFormat
);
/// <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 (parameters.Count == 1 && parameters.TryGetValue(Constants.BodyPlaceHolderKey, out object? value))
return serializer.Serialize(value);
else
return serializer.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
}
} }
+45 -43
View File
@@ -1,52 +1,54 @@
using System; using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
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);
}
/// <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)
{
_cache.TryGetValue(key, out CacheItem? value);
if (value == null)
return null;
if (DateTime.UtcNow - value.CacheTime > maxAge)
{
_cache.TryRemove(key, out _);
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;
}
} }
} }
} }
+89 -116
View File
@@ -1,140 +1,113 @@
using System; using System;
using CryptoExchange.Net.Authentication; using CryptoExchange.Net.Authentication;
using CryptoExchange.Net.Interfaces; using CryptoExchange.Net.Interfaces;
using CryptoExchange.Net.Objects.Errors; using CryptoExchange.Net.Objects;
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 => ApiOptions.ApiCredentials != null || ClientOptions.ApiCredentials != null;
/// </summary>
public ErrorInfo GetErrorInfo(int code, string? message = null) => GetErrorInfo(code.ToString(), message);
/// <summary> /// <summary>
/// Get error info for a response code /// Api options
/// </summary> /// </summary>
public ErrorInfo GetErrorInfo(string code, string? message = null) => ErrorMapping.GetErrorInfo(code.ToString(), message); public ApiOptions ApiOptions { get; }
/// <inheritdoc /> /// <summary>
public void SetApiCredentials<T>(T credentials) where T : ApiCredentials /// Client Options
{ /// </summary>
ApiCredentials = credentials?.Copy(); public ExchangeOptions ClientOptions { get; }
if (ApiCredentials != null)
AuthenticationProvider = CreateAuthenticationProvider(ApiCredentials);
}
/// <inheritdoc /> /// <summary>
public virtual void SetOptions<T>(UpdateOptions<T> options) where T : ApiCredentials /// ctor
{ /// </summary>
ClientOptions.Proxy = options.Proxy; /// <param name="logger">Logger</param>
ClientOptions.RequestTimeout = options.RequestTimeout ?? ClientOptions.RequestTimeout; /// <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;
ApiCredentials = options.ApiCredentials?.Copy() ?? ApiCredentials; ClientOptions = clientOptions;
if (ApiCredentials != null) ApiOptions = apiOptions;
AuthenticationProvider = CreateAuthenticationProvider(ApiCredentials); OutputOriginalData = outputOriginalData;
} BaseAddress = baseAddress;
/// <summary> if (apiCredentials != null)
/// Dispose AuthenticationProvider = CreateAuthenticationProvider(apiCredentials.Copy());
/// </summary> }
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
/// <summary> /// <summary>
/// Dispose /// Create an AuthenticationProvider implementation instance based on the provided credentials
/// </summary> /// </summary>
public virtual void Dispose(bool disposing) /// <param name="credentials"></param>
{ /// <returns></returns>
_disposing = true; protected abstract AuthenticationProvider CreateAuthenticationProvider(ApiCredentials credentials);
/// <inheritdoc />
public abstract string FormatSymbol(string baseAsset, string quoteAsset, TradingMode tradingMode, DateTime? deliverDate = null);
/// <inheritdoc />
public void SetApiCredentials<T>(T credentials) where T : ApiCredentials
{
ApiOptions.ApiCredentials = credentials;
if (credentials != null)
AuthenticationProvider = CreateAuthenticationProvider(credentials.Copy());
}
/// <inheritdoc />
public virtual void SetOptions<T>(UpdateOptions<T> options) where T : ApiCredentials
{
ClientOptions.Proxy = options.Proxy;
ClientOptions.RequestTimeout = options.RequestTimeout ?? ClientOptions.RequestTimeout;
ApiOptions.ApiCredentials = options.ApiCredentials ?? ClientOptions.ApiCredentials;
if (options.ApiCredentials != null)
AuthenticationProvider = CreateAuthenticationProvider(options.ApiCredentials.Copy());
}
/// <summary>
/// Dispose
/// </summary>
public virtual void Dispose()
{
_disposing = true;
}
} }
} }
+96 -106
View File
@@ -1,142 +1,132 @@
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; _logger = logger?.CreateLogger(exchange) ?? NullLoggerFactory.Instance.CreateLogger(exchange);
}
/// <summary> Exchange = exchange;
/// Initialize the client with the specified options }
/// </summary>
/// <param name="options"></param>
/// <exception cref="ArgumentNullException"></exception>
protected virtual void Initialize(ExchangeOptions options)
{
if (options == null)
throw new ArgumentNullException(nameof(options));
ClientOptions = options; /// <summary>
_logger.Log(LogLevel.Trace, "Client configuration: {Options}, CryptoExchange.Net: v{CryptoExchangeVersion}, {Exchange}.Net: v{ExchangeVersion}", options, CryptoExchangeLibVersion, Exchange, ExchangeLibVersion); /// Initialize the client with the specified options
} /// </summary>
/// <param name="options"></param>
/// <exception cref="ArgumentNullException"></exception>
protected virtual void Initialize(ExchangeOptions options)
{
if (options == null)
throw new ArgumentNullException(nameof(options));
/// <summary> ClientOptions = options;
/// Set the API credentials for this client. All Api clients in this client will use the new credentials, regardless of earlier set options. _logger.Log(LogLevel.Trace, $"Client configuration: {options}, CryptoExchange.Net: v{CryptoExchangeLibVersion}, {Exchange}.Net: v{ExchangeLibVersion}");
/// </summary> }
/// <param name="credentials">The credentials to set</param>
protected virtual void SetApiCredentials<T>(T credentials) where T : ApiCredentials
{
foreach (var apiClient in ApiClients)
apiClient.SetApiCredentials(credentials);
}
/// <summary> /// <summary>
/// Register an API client /// 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="apiClient">The client</param> /// <param name="credentials">The credentials to set</param>
protected T AddApiClient<T>(T apiClient) where T : BaseApiClient protected virtual void SetApiCredentials<T>(T credentials) where T : ApiCredentials
{ {
if (ClientOptions == null) foreach (var apiClient in ApiClients)
throw new InvalidOperationException("Client should have called Initialize before adding API clients"); apiClient.SetApiCredentials(credentials);
}
_logger.Log(LogLevel.Trace, " {ApiClient}, base address: {BaseAddress}", apiClient.GetType().Name, apiClient.BaseAddress); /// <summary>
ApiClients.Add(apiClient); /// Register an API client
return apiClient; /// </summary>
} /// <param name="apiClient">The client</param>
protected T AddApiClient<T>(T apiClient) where T : BaseApiClient
{
if (ClientOptions == null)
throw new InvalidOperationException("Client should have called Initialize before adding API clients");
/// <summary> _logger.Log(LogLevel.Trace, $" {apiClient.GetType().Name}, base address: {apiClient.BaseAddress}");
/// Apply the options delegate to a new options instance ApiClients.Add(apiClient);
/// </summary> return apiClient;
protected static T ApplyOptionsDelegate<T>(Action<T>? del) where T: new() }
{
var opts = new T();
del?.Invoke(opts);
return opts;
}
/// <summary> /// <summary>
/// Dispose /// Apply the options delegate to a new options instance
/// </summary> /// </summary>
public void Dispose() protected static T ApplyOptionsDelegate<T>(Action<T>? del) where T: new()
{ {
Dispose(true); var opts = new T();
GC.SuppressFinalize(this); del?.Invoke(opts);
} return opts;
}
/// <summary> /// <summary>
/// Dispose /// Dispose
/// </summary> /// </summary>
public virtual void Dispose(bool disposing) public virtual void Dispose()
{
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();
} }
} }
} }
+14 -15
View File
@@ -1,25 +1,24 @@
using System.Linq; using System.Linq;
using CryptoExchange.Net.Interfaces; using CryptoExchange.Net.Interfaces;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
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)
{
}
} }
} }
+104 -105
View File
@@ -1,4 +1,4 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
@@ -7,124 +7,123 @@ 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;
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="logger">Logger</param>
/// <param name="exchange">The name of the exchange this client is for</param>
protected BaseSocketClient(ILoggerFactory? logger, string exchange) : base(logger, exchange)
{ {
var result = await socket.UnsubscribeAsync(subscriptionId).ConfigureAwait(false);
if (result)
break;
}
}
/// <summary>
/// Unsubscribe an update subscription
/// </summary>
/// <param name="subscription">The subscription to unsubscribe</param>
/// <returns></returns>
public virtual async Task UnsubscribeAsync(UpdateSubscription subscription)
{
if (subscription == null)
throw new ArgumentNullException(nameof(subscription));
_logger.UnsubscribingSubscription(subscription.SocketId, subscription.Id);
await subscription.CloseAsync().ConfigureAwait(false);
}
/// <summary>
/// Unsubscribe all subscriptions
/// </summary>
/// <returns></returns>
public virtual async Task UnsubscribeAllAsync()
{
var tasks = new List<Task>();
foreach (var client in ApiClients.OfType<SocketApiClient>())
tasks.Add(client.UnsubscribeAllAsync());
await Task.WhenAll(tasks.ToArray()).ConfigureAwait(false);
}
/// <summary>
/// Reconnect all connections
/// </summary>
/// <returns></returns>
public virtual async Task ReconnectAsync()
{
_logger.ReconnectingAllConnections(CurrentConnections);
var tasks = new List<Task>();
foreach (var client in ApiClients.OfType<SocketApiClient>())
{
tasks.Add(client.ReconnectAsync());
} }
await Task.WhenAll(tasks.ToArray()).ConfigureAwait(false); /// <summary>
} /// 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;
}
} }
} }
+49 -60
View File
@@ -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);
}
} }
+20 -16
View File
@@ -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 +0,0 @@
using System;
using System.Collections.Concurrent;
using System.Text.Json.Serialization;
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>
/// Get the instance of the provided type T. It will be created if it doesn't exist yet.
/// </summary>
/// <typeparam name="T">Implementation type of the JsonSerializerContext</typeparam>
public static JsonSerializerContext GetOrCreate<T>() where T: JsonSerializerContext, new()
{
var contextType = typeof(T);
if (_cache.TryGetValue(contextType, out var context))
return context;
var instance = new T();
_cache[contextType] = instance;
return instance;
}
}
@@ -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,242 @@
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;
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, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] TContext> : JsonConverter<T> where T : new() where TContext: JsonSerializerContext
[UnconditionalSuppressMessage("AssemblyLoadTrimming", "IL2026:RequiresUnreferencedCode", Justification = "JsonSerializerOptions provided here has TypeInfoResolver set")] # else
#endif public class ArrayConverter<T, TContext> : JsonConverter<T> where T : new() where TContext: JsonSerializerContext
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
private static T ParseObject(ref Utf8JsonReader reader, T result, JsonSerializerOptions options)
#endif #endif
{ {
if (reader.TokenType != JsonTokenType.StartArray) private static readonly ConcurrentDictionary<Type, List<ArrayPropertyInfo>> _typeAttributesCache = new ConcurrentDictionary<Type, List<ArrayPropertyInfo>>();
throw new Exception("Not an array"); private static readonly ConcurrentDictionary<JsonConverter, JsonSerializerOptions> _converterOptionsCache = new ConcurrentDictionary<JsonConverter, JsonSerializerOptions>();
int index = 0; /// <inheritdoc />
while (reader.Read()) #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 (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 valueType = typeof(T);
if (!_typeAttributesCache.TryGetValue(valueType, out var typeAttributes))
typeAttributes = CacheTypeAttributes(valueType);
var ordered = typeAttributes.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 = (TContext)Activator.CreateInstance(typeof(TContext))!,
};
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 = Activator.CreateInstance(typeof(T))!;
return (T)ParseObject(ref reader, result, typeof(T), options);
}
private static bool IsSimple(Type type)
{
if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>))
{
// nullable type, check if the nested type is simple.
return IsSimple(type.GetGenericArguments()[0]);
}
return type.IsPrimitive
|| type.IsEnum
|| type == typeof(string)
|| type == typeof(decimal);
}
#if NET5_0_OR_GREATER
[UnconditionalSuppressMessage("AssemblyLoadTrimming", "IL3050:RequiresUnreferencedCode", Justification = "JsonSerializerOptions provided here has TypeInfoResolver set")]
[UnconditionalSuppressMessage("AssemblyLoadTrimming", "IL2026:RequiresUnreferencedCode", Justification = "JsonSerializerOptions provided here has TypeInfoResolver set")]
private static List<ArrayPropertyInfo> CacheTypeAttributes([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] Type type)
#else
private static List<ArrayPropertyInfo> CacheTypeAttributes(Type type)
#endif
{
var attributes = new List<ArrayPropertyInfo>();
var properties = type.GetProperties();
foreach (var property in properties)
{
var att = property.GetCustomAttribute<ArrayPropertyAttribute>();
if (att == null)
continue;
var targetType = Nullable.GetUnderlyingType(property.PropertyType) ?? property.PropertyType;
var converterType = property.GetCustomAttribute<JsonConverterAttribute>()?.ConverterType ?? targetType.GetCustomAttribute<JsonConverterAttribute>()?.ConverterType;
attributes.Add(new ArrayPropertyInfo
{
ArrayProperty = att,
PropertyInfo = property,
DefaultDeserialization = property.GetCustomAttribute<CryptoExchange.Net.Attributes.JsonConversionAttribute>() != null,
JsonConverter = converterType == null ? null : (JsonConverter)Activator.CreateInstance(converterType)!,
TargetType = targetType
});
}
_typeAttributesCache.TryAdd(type, attributes);
return attributes;
}
#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 object ParseObject(ref Utf8JsonReader reader, object result, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] Type objectType, JsonSerializerOptions options)
#else
private static object ParseObject(ref Utf8JsonReader reader, object result, Type objectType, JsonSerializerOptions options)
#endif
{
if (reader.TokenType != JsonTokenType.StartArray)
throw new Exception("Not an array");
if (!_typeAttributesCache.TryGetValue(objectType, out var attributes))
attributes = CacheTypeAttributes(objectType);
int index = 0;
while (reader.Read())
{
if (reader.TokenType == JsonTokenType.EndArray)
break;
var indexAttributes = attributes.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 (!_converterOptionsCache.TryGetValue(attribute.JsonConverter, out var newOptions))
{
newOptions = new JsonSerializerOptions
{
NumberHandling = JsonNumberHandling.AllowReadingFromString | JsonNumberHandling.AllowNamedFloatingPointLiterals,
PropertyNameCaseInsensitive = false,
Converters = { attribute.JsonConverter },
TypeInfoResolver = options.TypeInfoResolver,
};
_converterOptionsCache.TryAdd(attribute.JsonConverter, newOptions);
}
var doc = JsonDocument.ParseValue(ref reader);
value = doc.Deserialize(attribute.PropertyInfo.PropertyType, newOptions);
}
else if (attribute.DefaultDeserialization)
{
// Use default deserialization
value = JsonDocument.ParseValue(ref reader).Deserialize(attribute.PropertyInfo.PropertyType, SerializerOptions.WithConverters((TContext)Activator.CreateInstance(typeof(TContext))!));
}
else
{
value = reader.TokenType switch
{ {
NumberHandling = JsonNumberHandling.AllowReadingFromString | JsonNumberHandling.AllowNamedFloatingPointLiterals, JsonTokenType.Null => null,
PropertyNameCaseInsensitive = false, JsonTokenType.False => false,
Converters = { attribute.JsonConverter }, JsonTokenType.True => true,
TypeInfoResolver = options.TypeInfoResolver, 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 class ArrayPropertyInfo
}
private static bool IsSimple(Type type)
{
if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>))
{ {
// nullable type, check if the nested type is simple. public PropertyInfo PropertyInfo { get; set; } = null!;
return IsSimple(type.GetGenericArguments()[0]); public ArrayPropertyAttribute ArrayProperty { get; set; } = null!;
public JsonConverter? JsonConverter { get; set; }
public bool DefaultDeserialization { get; set; }
public Type TargetType { get; set; } = null!;
} }
return type.IsPrimitive
|| type.IsEnum
|| type == typeof(string)
|| type == typeof(decimal);
}
#if NET5_0_OR_GREATER
[UnconditionalSuppressMessage("AssemblyLoadTrimming", "IL3050:RequiresUnreferencedCode", Justification = "JsonSerializerOptions provided here has TypeInfoResolver set")]
[UnconditionalSuppressMessage("AssemblyLoadTrimming", "IL2026:RequiresUnreferencedCode", Justification = "JsonSerializerOptions provided here has TypeInfoResolver set")]
private static List<ArrayPropertyInfo> CacheTypeAttributes()
#else
private static List<ArrayPropertyInfo> CacheTypeAttributes()
#endif
{
var attributes = new List<ArrayPropertyInfo>();
var properties = typeof(T).GetProperties();
foreach (var property in properties)
{
var att = property.GetCustomAttribute<ArrayPropertyAttribute>();
if (att == null)
continue;
var targetType = Nullable.GetUnderlyingType(property.PropertyType) ?? property.PropertyType;
var converterType = property.GetCustomAttribute<JsonConverterAttribute>()?.ConverterType ?? targetType.GetCustomAttribute<JsonConverterAttribute>()?.ConverterType;
attributes.Add(new ArrayPropertyInfo
{
ArrayProperty = att,
PropertyInfo = property,
DefaultDeserialization = property.GetCustomAttribute<CryptoExchange.Net.Attributes.JsonConversionAttribute>() != null,
JsonConverter = converterType == null ? null : (JsonConverter)Activator.CreateInstance(converterType)!,
TargetType = targetType
});
}
return attributes;
}
private class ArrayPropertyInfo
{
public PropertyInfo PropertyInfo { get; set; } = null!;
public ArrayPropertyAttribute ArrayProperty { get; set; } = null!;
public JsonConverter? JsonConverter { get; set; }
public bool DefaultDeserialization { get; set; }
public Type TargetType { get; set; } = null!;
public JsonSerializerOptions? JsonSerializerOptions { get; set; }
} }
} }
@@ -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,33 @@
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 []; {
return (reader.GetString()?.Split(',').Select(x => (T)EnumConverter.ParseString<T>(x)!).ToArray() ?? []);
}
return str!.Split(',').Select(x => (T)EnumConverter.ParseString<T>(x)!).ToArray() ?? []; /// <inheritdoc />
} public override void Write(Utf8JsonWriter writer, T[] value, JsonSerializerOptions options)
{
/// <inheritdoc /> writer.WriteStringValue(string.Join(",", value.Select(x => EnumConverter.GetString(x))));
public override void Write(Utf8JsonWriter writer, T[] value, JsonSerializerOptions options) }
{
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,60 @@
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();
if (string.IsNullOrEmpty(value) || string.Equals("null", value, StringComparison.OrdinalIgnoreCase))
return null;
if (string.Equals("Infinity", value, StringComparison.Ordinal))
// Infinity returned by the server, default to max value
return decimal.MaxValue;
try
{
return decimal.Parse(value, NumberStyles.Float, CultureInfo.InvariantCulture);
}
catch(OverflowException)
{
// Value doesn't fit decimal, default to max value
return decimal.MaxValue;
}
}
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,99 +9,95 @@ 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;
internal class NullableEnumConverter : JsonConverter<T?>
{ {
if (warn) private readonly EnumConverter<T> _enumConverter;
public NullableEnumConverter(EnumConverter<T> enumConverter)
{
_enumConverter = enumConverter;
}
public override T? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
return _enumConverter.ReadNullable(ref reader, typeToConvert, options, out var isEmptyString);
}
public override void Write(Utf8JsonWriter writer, T? value, JsonSerializerOptions options)
{
if (value == null)
{
writer.WriteNullValue();
}
else
{
_enumConverter.Write(writer, value.Value, options);
}
}
}
/// <inheritdoc />
public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
var t = ReadNullable(ref reader, typeToConvert, options, out var isEmptyString);
if (t == null)
{ {
if (isEmptyString) if (isEmptyString)
{ {
@@ -112,177 +108,162 @@ public class EnumConverter<T>
{ {
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"); 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
return new T(); // return default value
}
else
{
return t.Value;
}
}
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;
} }
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)
{
isEmptyString = false;
var enumType = typeof(T);
if (_mapping == null)
_mapping = AddMapping();
var stringValue = reader.TokenType switch
{
JsonTokenType.String => reader.GetString(),
JsonTokenType.Number => reader.GetInt16().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))
{ {
warn = true; isEmptyString = true;
_unknownValuesWarned.Add(stringValue!); }
else
{
// We received an enum value but weren't able to parse it.
Trace.WriteLine($"{DateTime.Now:yyyy/MM/dd HH:mm:ss:fff} | Warning | Cannot map enum value. EnumType: {enumType.Name}, Value: {stringValue}, Known values: {string.Join(", ", _mapping.Select(m => m.Value))}. If you think {stringValue} should added please open an issue on the Github repo"); Trace.WriteLine($"{DateTime.Now:yyyy/MM/dd HH:mm:ss:fff} | Warning | 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;
}
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,42 @@
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)
{ {
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,
};
_cache.TryAdd(typeResolver, options);
}
foreach (var converter in additionalConverters) return options;
options.Converters.Add(converter);
options.TypeInfoResolver = typeResolver;
_cache.TryAdd(typeResolver, options);
} }
return options;
} }
} }
@@ -1,57 +0,0 @@
using CryptoExchange.Net.SharedApis;
using System;
using System.Text.Json;
using System.Text.Json.Serialization;
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)
{
if (reader.TokenType != JsonTokenType.StartArray)
throw new Exception("");
reader.Read(); // Start array
var baseQuantity = reader.TokenType == JsonTokenType.Null ? (decimal?)null : reader.GetDecimal();
reader.Read();
var quoteQuantity = reader.TokenType == JsonTokenType.Null ? (decimal?)null : reader.GetDecimal();
reader.Read();
var contractQuantity = reader.TokenType == JsonTokenType.Null ? (decimal?)null : reader.GetDecimal();
reader.Read();
if (reader.TokenType != JsonTokenType.EndArray)
throw new Exception("");
reader.Read(); // End array
var result = new T();
result.QuantityInBaseAsset = baseQuantity;
result.QuantityInQuoteAsset = quoteQuantity;
result.QuantityInContracts = contractQuantity;
return result;
}
public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options)
{
writer.WriteStartArray();
if (value.QuantityInBaseAsset == null)
writer.WriteNullValue();
else
writer.WriteNumberValue(value.QuantityInBaseAsset.Value);
if (value.QuantityInQuoteAsset == null)
writer.WriteNullValue();
else
writer.WriteNumberValue(value.QuantityInQuoteAsset.Value);
if (value.QuantityInContracts == null)
writer.WriteNullValue();
else
writer.WriteNumberValue(value.QuantityInContracts.Value);
writer.WriteEndArray();
}
}
@@ -1,43 +0,0 @@
using CryptoExchange.Net.SharedApis;
using System;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace CryptoExchange.Net.Converters.SystemTextJson;
internal class SharedSymbolConverter : JsonConverter<SharedSymbol>
{
public override SharedSymbol? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
if (reader.TokenType != JsonTokenType.StartArray)
throw new Exception("");
reader.Read(); // Start array
var tradingMode = (TradingMode)Enum.Parse(typeof(TradingMode), reader.GetString()!);
reader.Read();
var baseAsset = reader.GetString()!;
reader.Read();
var quoteAsset = reader.GetString()!;
reader.Read();
var timeStr = reader.GetString()!;
var deliverTime = string.IsNullOrEmpty(timeStr) ? (DateTime?)null : DateTime.Parse(timeStr);
reader.Read();
if (reader.TokenType != JsonTokenType.EndArray)
throw new Exception("");
reader.Read(); // End array
return new SharedSymbol(tradingMode, baseAsset, quoteAsset, deliverTime);
}
public override void Write(Utf8JsonWriter writer, SharedSymbol value, JsonSerializerOptions options)
{
writer.WriteStartArray();
writer.WriteStringValue(value.TradingMode.ToString());
writer.WriteStringValue(value.BaseAsset);
writer.WriteStringValue(value.QuoteAsset);
writer.WriteStringValue(value.DeliverTime?.ToString());
writer.WriteEndArray();
}
}
@@ -1,376 +1,376 @@
using CryptoExchange.Net.Converters.MessageParsing; using CryptoExchange.Net.Converters.MessageParsing;
using CryptoExchange.Net.Interfaces; using CryptoExchange.Net.Interfaces;
using CryptoExchange.Net.Objects; using CryptoExchange.Net.Objects;
using System; using System;
#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 IsJson { 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 (!IsJson)
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 = $"Deserialize JsonException: {ex.Message}, Path: {ex.Path}, LineNumber: {ex.LineNumber}, LinePosition: {ex.BytePositionInLine}";
return new CallResult<object>(new DeserializeError(info, OriginalDataAvailable ? GetOriginalString() : "[Data only available when OutputOriginal = true in client options]"));
}
catch (Exception ex)
{
var info = $"Deserialize unknown Exception: {ex.Message}";
return new CallResult<object>(new DeserializeError(info, OriginalDataAvailable ? GetOriginalString() : "[Data only available when OutputOriginal = true in client options]"));
} }
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 = $"Deserialize JsonException: {ex.Message}, Path: {ex.Path}, LineNumber: {ex.LineNumber}, LinePosition: {ex.BytePositionInLine}";
return new CallResult<T>(new DeserializeError(info, OriginalDataAvailable ? GetOriginalString() : "[Data only available when OutputOriginal = true in client options]"));
}
catch (Exception ex)
{
var info = $"Unknown exception: {ex.Message}";
return new CallResult<T>(new DeserializeError(info, OriginalDataAvailable ? GetOriginalString() : "[Data only available when OutputOriginal = true in client options]"));
}
}
if (value.Value.ValueKind != JsonValueKind.Array) /// <inheritdoc />
return default; public NodeType? GetNodeType()
{
if (!IsJson)
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 (!IsJson)
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 (!IsJson)
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 List<T?>? GetValues<T>(MessagePath path)
{
if (!IsJson)
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<List<T>>(_customSerializerOptions)!;
}
private JsonElement? GetPathNode(MessagePath path)
{
if (!IsJson)
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; IsJson = true;
return new CallResult(new DeserializeError("Not a json value")); return CallResult.SuccessResult;
} }
catch (Exception ex)
{
// Not a json message
IsJson = false;
return new CallResult(new ServerError("JsonError: " + ex.Message));
}
}
_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
IsJson = false;
return new CallResult(new ServerError("Not a json value"));
}
_document = JsonDocument.Parse(data);
IsJson = true;
return CallResult.SuccessResult;
}
catch (Exception ex)
{
// Not a json message
IsJson = false;
return new CallResult(new ServerError("JsonError: " + ex.Message));
}
}
/// <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 : IMessageSerializer
{
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);
}
} }
+18 -23
View File
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFrameworks>netstandard2.0;netstandard2.1;net8.0;net9.0</TargetFrameworks> <TargetFrameworks>netstandard2.0;netstandard2.1;net8.0;net9.0</TargetFrameworks>
</PropertyGroup> </PropertyGroup>
@@ -6,11 +6,11 @@
<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>8.8.0</PackageVersion>
<AssemblyVersion>9.6.0</AssemblyVersion> <AssemblyVersion>8.8.0</AssemblyVersion>
<FileVersion>9.6.0</FileVersion> <FileVersion>8.8.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</PackageTags>
<RepositoryType>git</RepositoryType> <RepositoryType>git</RepositoryType>
<RepositoryUrl>https://github.com/JKorf/CryptoExchange.Net.git</RepositoryUrl> <RepositoryUrl>https://github.com/JKorf/CryptoExchange.Net.git</RepositoryUrl>
<PackageProjectUrl>https://github.com/JKorf/CryptoExchange.Net</PackageProjectUrl> <PackageProjectUrl>https://github.com/JKorf/CryptoExchange.Net</PackageProjectUrl>
@@ -24,12 +24,11 @@
<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>
<PropertyGroup Label="AOT" Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'net7.0'))"> <PropertyGroup Label="AOT" Condition=" '$(TargetFramework)' == 'NET8_0' Or '$(TargetFramework)' == 'NET9_0' ">
<IsAotCompatible>true</IsAotCompatible> <IsAotCompatible>true</IsAotCompatible>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Label="Deterministic Build" Condition="'$(Configuration)' == 'Release'"> <PropertyGroup Label="Deterministic Build" Condition="'$(Configuration)' == 'Release'">
<PublishRepositoryUrl>true</PublishRepositoryUrl> <PublishRepositoryUrl>true</PublishRepositoryUrl>
@@ -38,15 +37,15 @@
<EmbedUntrackedSources>true</EmbedUntrackedSources> <EmbedUntrackedSources>true</EmbedUntrackedSources>
<ContinuousIntegrationBuild>true</ContinuousIntegrationBuild> <ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
</PropertyGroup> </PropertyGroup>
<ItemGroup Label="Deterministic Build" Condition="'$(Configuration)' == 'Release'">
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="8.0.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
<PropertyGroup> <PropertyGroup>
<DocumentationFile>CryptoExchange.Net.xml</DocumentationFile> <DocumentationFile>CryptoExchange.Net.xml</DocumentationFile>
</PropertyGroup> </PropertyGroup>
<PropertyGroup>
<EnableNETAnalyzers>true</EnableNETAnalyzers>
<AnalysisMode>Recommended</AnalysisMode>
<AnalysisModeGlobalization>None</AnalysisModeGlobalization>
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
</PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="ConfigureAwaitChecker.Analyzer" Version="5.0.0.1"> <PackageReference Include="ConfigureAwaitChecker.Analyzer" Version="5.0.0.1">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
@@ -58,14 +57,10 @@
</PackageReference> </PackageReference>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.Extensions.Logging" Version="9.0.6" /> <PackageReference Include="Microsoft.Extensions.Http" Version="9.0.0" />
<PackageReference Include="System.Text.Json" Version="9.0.6" /> <PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="9.0.0" />
</ItemGroup> <PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="9.0.0" />
<ItemGroup Label="Transitive Client Packages"> <PackageReference Include="System.Text.Json" Version="9.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="9.0.6" /> <PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="9.0.0" />
<PackageReference Include="Microsoft.Extensions.Http" Version="9.0.6" />
</ItemGroup>
<ItemGroup>
<EditorConfigFiles Remove="C:\Projects\CryptoExchange.Net\CryptoExchange.Net\.editorconfig" />
</ItemGroup> </ItemGroup>
</Project> </Project>
+291 -364
View File
@@ -1,390 +1,317 @@
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.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);
}
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)
value -= offset;
else value += (valueStep.Value - offset);
} }
} else if(roundingType == RoundingType.Up)
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="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);
yield return batch;
if (!batch || ct.IsCancellationRequested)
break;
result.AddRange(batch.Data);
nextPageToken = batch.NextPageToken;
if (nextPageToken == null)
break;
}
}
/// <summary>
/// Apply the rules (price and quantity step size and decimals precision, min/max quantity) from the symbol to the quantity and price
/// </summary>
/// <param name="symbol">The symbol as retrieved from the exchange</param>
/// <param name="quantity">Quantity to trade</param>
/// <param name="price">Price to trade at</param>
/// <param name="adjustedQuantity">Quantity adjusted to match all trading rules</param>
/// <param name="adjustedPrice">Price adjusted to match all trading rules</param>
public static void ApplySymbolRules(SharedSpotSymbol symbol, decimal quantity, decimal? price, out decimal adjustedQuantity, out decimal? adjustedPrice)
{
adjustedPrice = price;
adjustedQuantity = quantity;
var minNotionalAdjust = false;
if (price != null)
{
adjustedPrice = AdjustValueStep(0, decimal.MaxValue, symbol.PriceStep, RoundingType.Down, price.Value);
adjustedPrice = symbol.PriceSignificantFigures.HasValue ? RoundToSignificantDigits(adjustedPrice.Value, symbol.PriceSignificantFigures.Value, RoundingType.Closest) : adjustedPrice;
adjustedPrice = symbol.PriceDecimals.HasValue ? RoundDown(price.Value, symbol.PriceDecimals.Value) : adjustedPrice;
if (adjustedPrice != 0 && adjustedPrice * quantity < symbol.MinNotionalValue)
{ {
adjustedQuantity = symbol.MinNotionalValue.Value / adjustedPrice.Value; if (offset != 0)
minNotionalAdjust = true; value += (step.Value - offset);
} }
}
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 else
return decimal.MaxValue; {
if (offset < step / 2)
value -= offset;
else value += (step.Value - offset);
}
value = RoundDown(value, 8);
return value.Normalize();
} }
// Unknown decimal format, return null /// <summary>
return null; /// 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>
/// 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;
}
}
/// <summary>
/// Apply the rules (price and quantity step size and decimals precision, min/max quantity) from the symbol to the quantity and price
/// </summary>
/// <param name="symbol">The symbol as retrieved from the exchange</param>
/// <param name="quantity">Quantity to trade</param>
/// <param name="price">Price to trade at</param>
/// <param name="adjustedQuantity">Quantity adjusted to match all trading rules</param>
/// <param name="adjustedPrice">Price adjusted to match all trading rules</param>
public static void ApplySymbolRules(SharedSpotSymbol symbol, decimal quantity, decimal? price, out decimal adjustedQuantity, out decimal? adjustedPrice)
{
adjustedPrice = price;
adjustedQuantity = quantity;
var minNotionalAdjust = false;
if (price != null)
{
adjustedPrice = AdjustValueStep(0, decimal.MaxValue, symbol.PriceStep, RoundingType.Down, price.Value);
adjustedPrice = symbol.PriceSignificantFigures.HasValue ? RoundToSignificantDigits(adjustedPrice.Value, symbol.PriceSignificantFigures.Value, RoundingType.Closest) : adjustedPrice;
adjustedPrice = symbol.PriceDecimals.HasValue ? RoundDown(price.Value, symbol.PriceDecimals.Value) : adjustedPrice;
if (adjustedPrice != 0 && adjustedPrice * quantity < symbol.MinNotionalValue)
{
adjustedQuantity = symbol.MinNotionalValue.Value / adjustedPrice.Value;
minNotionalAdjust = true;
}
}
adjustedQuantity = AdjustValueStep(symbol.MinTradeQuantity ?? 0, symbol.MaxTradeQuantity ?? decimal.MaxValue, symbol.QuantityStep, minNotionalAdjust ? RoundingType.Up : RoundingType.Down, adjustedQuantity);
adjustedQuantity = symbol.QuantityDecimals.HasValue ? (minNotionalAdjust ? RoundUp(adjustedQuantity, symbol.QuantityDecimals.Value) : RoundDown(adjustedQuantity, symbol.QuantityDecimals.Value)) : adjustedQuantity;
}
} }
} }
+51 -49
View File
@@ -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 => new SharedSymbol(x.TradingMode, x.BaseAsset, x.QuoteAsset, (x as SharedFuturesSymbol)?.DeliveryTime) { SymbolName = x.Name }));
_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 => new SharedSymbol(x.TradingMode, x.BaseAsset, x.QuoteAsset, (x as SharedFuturesSymbol)?.DeliveryTime) { SymbolName = x.Name }));
} }
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;
}
} }
} }
} }
+448 -456
View File
@@ -1,4 +1,4 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO.Compression; using System.IO.Compression;
using System.IO; using System.IO;
@@ -10,511 +10,503 @@ 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(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(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)!);
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(IOrderBookRestClient).IsAssignableFrom(typeof(T)))
services.AddTransient(x => (IOrderBookRestClient)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();
}
} }
+36 -34
View File
@@ -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,101 @@
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;
#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 json message
bool OriginalDataAvailable { get; } /// </summary>
/// <summary> bool IsJson { 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> List<T?>? GetValues<T>(MessagePath path);
/// <returns></returns> /// <summary>
#if NET5_0_OR_GREATER /// Deserialize the message into this type
[UnconditionalSuppressMessage("AssemblyLoadTrimming", "IL2092:RequiresUnreferencedCode", Justification = "JsonSerializerOptions provided here has TypeInfoResolver set")] /// </summary>
[UnconditionalSuppressMessage("AssemblyLoadTrimming", "IL2095:RequiresUnreferencedCode", Justification = "JsonSerializerOptions provided here has TypeInfoResolver set")] /// <param name="type"></param>
#endif /// <param name="path"></param>
CallResult<object> Deserialize(Type type, MessagePath? path = null); /// <returns></returns>
/// <summary> CallResult<object> Deserialize(Type type, MessagePath? path = null);
/// Deserialize the message into this type /// <summary>
/// </summary> /// Deserialize the message into this type
/// <param name="path"></param> /// </summary>
/// <returns></returns> /// <param name="path"></param>
#if NET5_0_OR_GREATER /// <returns></returns>
[UnconditionalSuppressMessage("AssemblyLoadTrimming", "IL2092:RequiresUnreferencedCode", Justification = "JsonSerializerOptions provided here has TypeInfoResolver set")] CallResult<T> Deserialize<T>(MessagePath? path = null);
[UnconditionalSuppressMessage("AssemblyLoadTrimming", "IL2095:RequiresUnreferencedCode", Justification = "JsonSerializerOptions provided here has TypeInfoResolver set")]
#endif /// <summary>
CallResult<T> Deserialize<T>(MessagePath? path = null); /// 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,44 @@
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 identifiers for this processor
Task<CallResult> Handle(SocketConnection connection, DataEvent<object> message, MessageHandlerLink matchedHandler); /// </summary>
/// <summary> public HashSet<string> ListenerIdentifiers { get; }
/// Deserialize a message into object of type /// <summary>
/// </summary> /// Handle a message
/// <param name="accessor"></param> /// </summary>
/// <param name="type"></param> /// <param name="connection"></param>
/// <returns></returns> /// <param name="message"></param>
CallResult<object> Deserialize(IMessageAccessor accessor, Type type); /// <returns></returns>
Task<CallResult> Handle(SocketConnection connection, DataEvent<object> message);
/// <summary>
/// Get the type the message should be deserialized to
/// </summary>
/// <param name="messageAccessor"></param>
/// <returns></returns>
Type? GetMessageType(IMessageAccessor messageAccessor);
/// <summary>
/// Deserialize a message into object of type
/// </summary>
/// <param name="accessor"></param>
/// <param name="type"></param>
/// <returns></returns>
CallResult<object> Deserialize(IMessageAccessor accessor, Type type);
}
} }
@@ -1,34 +1,15 @@
namespace CryptoExchange.Net.Interfaces; namespace CryptoExchange.Net.Interfaces
/// <summary>
/// Serializer interface
/// </summary>
public interface IMessageSerializer
{
}
/// <summary>
/// Serialize to byte array
/// </summary>
public interface IByteMessageSerializer: IMessageSerializer
{ {
/// <summary> /// <summary>
/// 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 an object to a string
/// </summary>
/// <summary> /// <param name="message"></param>
/// Serialize to string /// <returns></returns>
/// </summary> string Serialize<T>(T message);
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);
}
} }
+18 -17
View File
@@ -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);
}
} }
+54 -53
View File
@@ -1,65 +1,66 @@
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> /// internal request id for tracing
/// <param name="data"></param> /// </summary>
void SetContent(byte[] data); int RequestId { get; }
/// <summary> /// <summary>
/// Set string content /// Set byte content
/// </summary> /// </summary>
/// <param name="data"></param> /// <param name="data"></param>
/// <param name="contentType"></param> void SetContent(byte[] data);
void SetContent(string data, string contentType); /// <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,36 @@
using CryptoExchange.Net.Objects; using CryptoExchange.Net.Objects;
using System; using System;
using System.Net.Http; using System.Net.Http;
namespace CryptoExchange.Net.Interfaces; namespace CryptoExchange.Net.Interfaces
/// <summary>
/// Request factory interface
/// </summary>
public interface IRequestFactory
{ {
/// <summary> /// <summary>
/// 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>
/// <param name="method"></param>
/// <param name="uri"></param>
/// <param name="requestId"></param>
/// <returns></returns>
IRequest Create(HttpMethod method, Uri uri, int requestId);
/// <summary> /// <summary>
/// Configure the requests created by this factory /// Configure the requests created by this factory
/// </summary> /// </summary>
/// <param name="requestTimeout">Request timeout to use</param> /// <param name="requestTimeout">Request timeout to use</param>
/// <param name="httpClient">Optional shared http client instance</param> /// <param name="httpClient">Optional shared http client instance</param>
/// <param name="proxy">Optional proxy to use when no http client is provided</param> /// <param name="proxy">Optional proxy to use when no http client is provided</param>
void Configure(ApiProxy? proxy, TimeSpan requestTimeout, HttpClient? httpClient = null); void Configure(ApiProxy? proxy, TimeSpan requestTimeout, HttpClient? httpClient = null);
/// <summary> /// <summary>
/// Update settings /// Update settings
/// </summary> /// </summary>
/// <param name="proxy">Proxy to use</param> /// <param name="proxy">Proxy to use</param>
/// <param name="requestTimeout">Request timeout to use</param> /// <param name="requestTimeout">Request timeout to use</param>
void UpdateSettings(ApiProxy? proxy, TimeSpan requestTimeout); void UpdateSettings(ApiProxy? proxy, TimeSpan requestTimeout);
}
} }
+31 -30
View File
@@ -1,43 +1,44 @@
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 /// Whether the status code indicates a success status
/// </summary> /// </summary>
bool IsSuccessStatusCode { get; } bool IsSuccessStatusCode { get; }
/// <summary> /// <summary>
/// The length of the response in bytes /// The length of the response in bytes
/// </summary> /// </summary>
long? ContentLength { get; } long? ContentLength { get; }
/// <summary> /// <summary>
/// The response headers /// The response headers
/// </summary> /// </summary>
KeyValuePair<string, string[]>[] ResponseHeaders { get; } KeyValuePair<string, string[]>[] ResponseHeaders { get; }
/// <summary> /// <summary>
/// Get the response stream /// Get the response stream
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
Task<Stream> GetResponseStreamAsync(); Task<Stream> GetResponseStreamAsync();
/// <summary> /// <summary>
/// Close the response /// Close the response
/// </summary> /// </summary>
void Close(); void Close();
}
} }
+13 -12
View File
@@ -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; }
}
} }
+18 -17
View File
@@ -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();
}
} }
+44 -43
View File
@@ -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();
}
} }
+107 -105
View File
@@ -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; }
}
} }
+92 -99
View File
@@ -1,109 +1,102 @@
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.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();
/// <summary> /// <summary>
/// Send string data /// Send 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 /// Reconnect the socket
/// </summary> /// </summary>
/// <param name="id"></param> /// <returns></returns>
/// <param name="data"></param> Task ReconnectAsync();
/// <param name="weight"></param> /// <summary>
bool Send(int id, byte[] data, int weight); /// Close the connection
/// <summary> /// </summary>
/// Reconnect the socket /// <returns></returns>
/// </summary> Task CloseAsync();
/// <returns></returns>
Task ReconnectAsync();
/// <summary>
/// Close the connection
/// </summary>
/// <returns></returns>
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);
}
} }
+36 -31
View File
@@ -1,42 +1,47 @@
namespace CryptoExchange.Net; using System;
using System.Collections.Generic;
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
{
clientOrderId = ExchangeHelpers.AppendRandomString(brokerId + ClientOrderIdSeparator, maxLength);
}
return clientOrderId; if (!string.IsNullOrEmpty(clientOrderId))
{
if (allowValueAdjustment)
clientOrderId = brokerId + ClientOrderIdSeparator + clientOrderId;
return clientOrderId!;
}
else
{
clientOrderId = ExchangeHelpers.AppendRandomString(brokerId + ClientOrderIdSeparator, maxLength);
}
return clientOrderId;
}
} }
} }
@@ -1,386 +1,374 @@
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, Uri, Exception?> _connected;
"[Sckt {SocketId}] connecting"); private static readonly Action<ILogger, int, Exception?> _startingProcessing;
private static readonly Action<ILogger, int, Exception?> _finishedProcessing;
private static readonly Action<ILogger, int, Exception?> _attemptReconnect;
private static readonly Action<ILogger, int, Uri, Exception?> _setReconnectUri;
private static readonly Action<ILogger, int, int, int, Exception?> _addingBytesToSendBuffer;
private static readonly Action<ILogger, int, Exception?> _reconnectRequested;
private static readonly Action<ILogger, int, Exception?> _closeAsyncWaitingForExistingCloseTask;
private static readonly Action<ILogger, int, Exception?> _closeAsyncSocketNotOpen;
private static readonly Action<ILogger, int, Exception?> _closing;
private static readonly Action<ILogger, int, Exception?> _closed;
private static readonly Action<ILogger, int, Exception?> _disposing;
private static readonly Action<ILogger, int, Exception?> _disposed;
private static readonly Action<ILogger, int, int, int, Exception?> _sentBytes;
private static readonly Action<ILogger, int, string, Exception?> _sendLoopStoppedWithException;
private static readonly Action<ILogger, int, Exception?> _sendLoopFinished;
private static readonly Action<ILogger, int, string, string ,Exception?> _receivedCloseMessage;
private static readonly Action<ILogger, int, string, string ,Exception?> _receivedCloseConfirmation;
private static readonly Action<ILogger, int, int, Exception?> _receivedPartialMessage;
private static readonly Action<ILogger, int, int, Exception?> _receivedSingleMessage;
private static readonly Action<ILogger, int, long, Exception?> _reassembledMessage;
private static readonly Action<ILogger, int, long, Exception?> _discardIncompleteMessage;
private static readonly Action<ILogger, int, Exception?> _receiveLoopStoppedWithException;
private static readonly Action<ILogger, int, Exception?> _receiveLoopFinished;
private static readonly Action<ILogger, int, TimeSpan?, Exception?> _startingTaskForNoDataReceivedCheck;
private static readonly Action<ILogger, int, TimeSpan?, Exception?> _noDataReceiveTimeoutReconnect;
private static readonly Action<ILogger, int, string, string, Exception?> _socketProcessingStateChanged;
private static readonly Action<ILogger, int, Exception?> _socketPingTimeout;
_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");
}
} public static void SocketConnecting(
this ILogger logger, int socketId)
{
_connecting(logger, socketId, null);
}
public static void SocketConnecting( public static void SocketConnectionFailed(
this ILogger logger, int socketId) this ILogger logger, int socketId, string message, Exception e)
{ {
_connecting(logger, socketId, null); _connectionFailed(logger, socketId, message, e);
} }
public static void SocketConnectionFailed( public static void SocketConnected(
this ILogger logger, int socketId, string message, Exception e) this ILogger logger, int socketId, Uri uri)
{ {
_connectionFailed(logger, socketId, message, e); _connected(logger, socketId, uri, null);
} }
public static void SocketConnected( public static void SocketStartingProcessing(
this ILogger logger, int socketId, Uri uri) this ILogger logger, int socketId)
{ {
_connected(logger, socketId, uri, null); _startingProcessing(logger, socketId, null);
} }
public static void SocketStartingProcessing( public static void SocketFinishedProcessing(
this ILogger logger, int socketId) this ILogger logger, int socketId)
{ {
_startingProcessing(logger, socketId, null); _finishedProcessing(logger, socketId, null);
} }
public static void SocketFinishedProcessing( public static void SocketAttemptReconnect(
this ILogger logger, int socketId) this ILogger logger, int socketId)
{ {
_finishedProcessing(logger, socketId, null); _attemptReconnect(logger, socketId, null);
} }
public static void SocketAttemptReconnect( public static void SocketSetReconnectUri(
this ILogger logger, int socketId) this ILogger logger, int socketId, Uri uri)
{ {
_attemptReconnect(logger, socketId, null); _setReconnectUri(logger, socketId, uri, null);
} }
public static void SocketSetReconnectUri( public static void SocketAddingBytesToSendBuffer(
this ILogger logger, int socketId, Uri uri) this ILogger logger, int socketId, int requestId, byte[] bytes)
{ {
_setReconnectUri(logger, socketId, uri, null); _addingBytesToSendBuffer(logger, socketId, requestId, bytes.Length, null);
} }
public static void SocketAddingBytesToSendBuffer( public static void SocketReconnectRequested(
this ILogger logger, int socketId, int requestId, byte[] bytes) this ILogger logger, int socketId)
{ {
_addingBytesToSendBuffer(logger, socketId, requestId, bytes.Length, null); _reconnectRequested(logger, socketId, null);
} }
public static void SocketReconnectRequested( public static void SocketCloseAsyncWaitingForExistingCloseTask(
this ILogger logger, int socketId) this ILogger logger, int socketId)
{ {
_reconnectRequested(logger, socketId, null); _closeAsyncWaitingForExistingCloseTask(logger, socketId, null);
} }
public static void SocketCloseAsyncWaitingForExistingCloseTask( public static void SocketCloseAsyncSocketNotOpen(
this ILogger logger, int socketId) this ILogger logger, int socketId)
{ {
_closeAsyncWaitingForExistingCloseTask(logger, socketId, null); _closeAsyncSocketNotOpen(logger, socketId, null);
} }
public static void SocketCloseAsyncSocketNotOpen( public static void SocketClosing(
this ILogger logger, int socketId) this ILogger logger, int socketId)
{ {
_closeAsyncSocketNotOpen(logger, socketId, null); _closing(logger, socketId, null);
} }
public static void SocketClosing( public static void SocketClosed(
this ILogger logger, int socketId) this ILogger logger, int socketId)
{ {
_closing(logger, socketId, null); _closed(logger, socketId, null);
} }
public static void SocketClosed( public static void SocketDisposing(
this ILogger logger, int socketId) this ILogger logger, int socketId)
{ {
_closed(logger, socketId, null); _disposing(logger, socketId, null);
} }
public static void SocketDisposing( public static void SocketDisposed(
this ILogger logger, int socketId) this ILogger logger, int socketId)
{ {
_disposing(logger, socketId, null); _disposed(logger, socketId, null);
} }
public static void SocketDisposed( public static void SocketSentBytes(
this ILogger logger, int socketId) this ILogger logger, int socketId, int requestId, int numBytes)
{ {
_disposed(logger, socketId, null); _sentBytes(logger, socketId, requestId, numBytes, null);
} }
public static void SocketSentBytes( public static void SocketSendLoopStoppedWithException(
this ILogger logger, int socketId, int requestId, int numBytes) this ILogger logger, int socketId, string message, Exception e)
{ {
_sentBytes(logger, socketId, requestId, numBytes, null); _sendLoopStoppedWithException(logger, socketId, message, e);
} }
public static void SocketSendLoopStoppedWithException( public static void SocketSendLoopFinished(
this ILogger logger, int socketId, string message, Exception e) this ILogger logger, int socketId)
{ {
_sendLoopStoppedWithException(logger, socketId, message, e); _sendLoopFinished(logger, socketId, null);
} }
public static void SocketSendLoopFinished( public static void SocketReceivedCloseMessage(
this ILogger logger, int socketId) this ILogger logger, int socketId, string webSocketCloseStatus, string closeStatusDescription)
{ {
_sendLoopFinished(logger, socketId, null); _receivedCloseMessage(logger, socketId, webSocketCloseStatus, closeStatusDescription, null);
} }
public static void SocketReceivedCloseMessage( public static void SocketReceivedCloseConfirmation(
this ILogger logger, int socketId, string webSocketCloseStatus, string closeStatusDescription) this ILogger logger, int socketId, string webSocketCloseStatus, string closeStatusDescription)
{ {
_receivedCloseMessage(logger, socketId, webSocketCloseStatus, closeStatusDescription, null); _receivedCloseConfirmation(logger, socketId, webSocketCloseStatus, closeStatusDescription, null);
} }
public static void SocketReceivedCloseConfirmation( public static void SocketReceivedPartialMessage(
this ILogger logger, int socketId, string webSocketCloseStatus, string closeStatusDescription) this ILogger logger, int socketId, int countBytes)
{ {
_receivedCloseConfirmation(logger, socketId, webSocketCloseStatus, closeStatusDescription, null); _receivedPartialMessage(logger, socketId, countBytes, null);
} }
public static void SocketReceivedPartialMessage( public static void SocketReceivedSingleMessage(
this ILogger logger, int socketId, int countBytes) this ILogger logger, int socketId, int countBytes)
{ {
_receivedPartialMessage(logger, socketId, countBytes, null); _receivedSingleMessage(logger, socketId, countBytes, null);
} }
public static void SocketReceivedSingleMessage( public static void SocketReassembledMessage(
this ILogger logger, int socketId, int countBytes) this ILogger logger, int socketId, long countBytes)
{ {
_receivedSingleMessage(logger, socketId, countBytes, null); _reassembledMessage(logger, socketId, countBytes, null);
} }
public static void SocketReassembledMessage( public static void SocketDiscardIncompleteMessage(
this ILogger logger, int socketId, long countBytes) this ILogger logger, int socketId, long countBytes)
{ {
_reassembledMessage(logger, socketId, countBytes, null); _discardIncompleteMessage(logger, socketId, countBytes, null);
} }
public static void SocketDiscardIncompleteMessage( public static void SocketReceiveLoopStoppedWithException(
this ILogger logger, int socketId, long countBytes) this ILogger logger, int socketId, Exception e)
{ {
_discardIncompleteMessage(logger, socketId, countBytes, null); _receiveLoopStoppedWithException(logger, socketId, e);
} }
public static void SocketReceiveLoopStoppedWithException( public static void SocketReceiveLoopFinished(
this ILogger logger, int socketId, Exception e) this ILogger logger, int socketId)
{ {
_receiveLoopStoppedWithException(logger, socketId, e); _receiveLoopFinished(logger, socketId, null);
} }
public static void SocketReceiveLoopFinished( public static void SocketStartingTaskForNoDataReceivedCheck(
this ILogger logger, int socketId) this ILogger logger, int socketId, TimeSpan? timeSpan)
{ {
_receiveLoopFinished(logger, socketId, null); _startingTaskForNoDataReceivedCheck(logger, socketId, timeSpan, null);
} }
public static void SocketStartingTaskForNoDataReceivedCheck( public static void SocketNoDataReceiveTimoutReconnect(
this ILogger logger, int socketId, TimeSpan? timeSpan) this ILogger logger, int socketId, TimeSpan? timeSpan)
{ {
_startingTaskForNoDataReceivedCheck(logger, socketId, timeSpan, null); _noDataReceiveTimeoutReconnect(logger, socketId, timeSpan, null);
} }
public static void SocketNoDataReceiveTimoutReconnect( public static void SocketProcessingStateChanged(
this ILogger logger, int socketId, TimeSpan? timeSpan) this ILogger logger, int socketId, string prevState, string newState)
{ {
_noDataReceiveTimeoutReconnect(logger, socketId, timeSpan, null); _socketProcessingStateChanged(logger, socketId, prevState, newState, null);
} }
public static void SocketProcessingStateChanged( public static void SocketPingTimeout(
this ILogger logger, int socketId, string prevState, string newState) this ILogger logger, int socketId)
{ {
_socketProcessingStateChanged(logger, socketId, prevState, newState, null); _socketPingTimeout(logger, socketId, null);
} }
public static void SocketPingTimeout(
this ILogger logger, int socketId)
{
_socketPingTimeout(logger, socketId, null);
}
public static void SocketConnectingCanceled(
this ILogger logger, int socketId)
{
_connectingCanceled(logger, socketId, null);
} }
} }
@@ -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?, 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?>(
"[Req {RequestId}] {ResponseStatusCode} - Response received in {ResponseTime}ms: {OriginalData}"); LogLevel.Warning,
new EventId(4000, "RestApiErrorReceived"),
"[Req {RequestId}] {ResponseStatusCode} - Error received in {ResponseTime}ms: {ErrorMessage}");
_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)
{ {
_restApiResponseReceived(logger, requestId, (int?)responseStatusCode, responseTime, originalData, null); _restApiErrorReceived(logger, requestId, (int?)responseStatusCode, responseTime, error, null);
} }
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,200 @@
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;
_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) public static void FailedToAddSubscriptionRetryOnDifferentConnection(this ILogger logger, int socketId)
{ {
_hasBeenPausedCantSubscribeAtThisMoment(logger, socketId, null); _failedToAddSubscriptionRetryOnDifferentConnection(logger, socketId, null);
} }
public static void FailedToSubscribe(this ILogger logger, int socketId, string? error) public static void HasBeenPausedCantSubscribeAtThisMoment(this ILogger logger, int socketId)
{ {
_failedToSubscribe(logger, socketId, error, null); _hasBeenPausedCantSubscribeAtThisMoment(logger, socketId, null);
} }
public static void CancellationTokenSetClosingSubscription(this ILogger logger, int socketId, int subscriptionId) public static void FailedToSubscribe(this ILogger logger, int socketId, string? error)
{ {
_cancellationTokenSetClosingSubscription(logger, socketId, subscriptionId, null); _failedToSubscribe(logger, socketId, error, null);
} }
public static void SubscriptionCompletedSuccessfully(this ILogger logger, int socketId, int subscriptionId) public static void CancellationTokenSetClosingSubscription(this ILogger logger, int socketId, int subscriptionId)
{ {
_subscriptionCompletedSuccessfully(logger, socketId, subscriptionId, null); _cancellationTokenSetClosingSubscription(logger, socketId, subscriptionId, null);
} }
public static void HasBeenPausedCantSendQueryAtThisMoment(this ILogger logger, int socketId) public static void SubscriptionCompletedSuccessfully(this ILogger logger, int socketId, int subscriptionId)
{ {
_hasBeenPausedCantSendQueryAtThisMoment(logger, socketId, null); _subscriptionCompletedSuccessfully(logger, socketId, subscriptionId, null);
} }
public static void AttemptingToAuthenticate(this ILogger logger, int socketId) public static void HasBeenPausedCantSendQueryAtThisMoment(this ILogger logger, int socketId)
{ {
_attemptingToAuthenticate(logger, socketId, null); _hasBeenPausedCantSendQueryAtThisMoment(logger, socketId, null);
} }
public static void AuthenticationFailed(this ILogger logger, int socketId) public static void AttemptingToAuthenticate(this ILogger logger, int socketId)
{ {
_authenticationFailed(logger, socketId, null); _attemptingToAuthenticate(logger, socketId, null);
} }
public static void Authenticated(this ILogger logger, int socketId) public static void AuthenticationFailed(this ILogger logger, int socketId)
{ {
_authenticated(logger, socketId, null); _authenticationFailed(logger, socketId, null);
} }
public static void FailedToDetermineConnectionUrl(this ILogger logger, string? error) public static void Authenticated(this ILogger logger, int socketId)
{ {
_failedToDetermineConnectionUrl(logger, error, null); _authenticated(logger, socketId, null);
} }
public static void ConnectionAddressSetTo(this ILogger logger, string connectionAddress) public static void FailedToDetermineConnectionUrl(this ILogger logger, string? error)
{ {
_connectionAddressSetTo(logger, connectionAddress, null); _failedToDetermineConnectionUrl(logger, error, null);
} }
public static void SocketCreatedForAddress(this ILogger logger, int socketId, string address) public static void ConnectionAddressSetTo(this ILogger logger, string connectionAddress)
{ {
_socketCreatedForAddress(logger, socketId, address, null); _connectionAddressSetTo(logger, connectionAddress, null);
} }
public static void UnsubscribingAll(this ILogger logger, int subscriptionCount) public static void SocketCreatedForAddress(this ILogger logger, int socketId, string address)
{ {
_unsubscribingAll(logger, subscriptionCount, null); _socketCreatedForAddress(logger, socketId, address, null);
} }
public static void DisposingSocketClient(this ILogger logger) public static void UnsubscribingAll(this ILogger logger, int subscriptionCount)
{ {
_disposingSocketClient(logger, null); _unsubscribingAll(logger, subscriptionCount, null);
} }
public static void UnsubscribingSubscription(this ILogger logger, int socketId, int subscriptionId) public static void DisposingSocketClient(this ILogger logger)
{ {
_unsubscribingSubscription(logger, socketId, subscriptionId, null); _disposingSocketClient(logger, null);
} }
public static void ReconnectingAllConnections(this ILogger logger, int connectionCount) public static void UnsubscribingSubscription(this ILogger logger, int socketId, int subscriptionId)
{ {
_reconnectingAllConnections(logger, connectionCount, null); _unsubscribingSubscription(logger, socketId, subscriptionId, null);
} }
public static void AddingRetryAfterGuard(this ILogger logger, DateTime retryAfter) public static void ReconnectingAllConnections(this ILogger logger, int connectionCount)
{ {
_addingRetryAfterGuard(logger, retryAfter, null); _reconnectingAllConnections(logger, connectionCount, null);
}
public static void AddingRetryAfterGuard(this ILogger logger, DateTime retryAfter)
{
_addingRetryAfterGuard(logger, retryAfter, null);
}
} }
} }
@@ -1,348 +1,325 @@
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?> _failedToEvaluateMessage;
private static readonly Action<ILogger, int, Exception?> _errorProcessingMessage;
private static readonly Action<ILogger, int, int, 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;
_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?>( _processorMatched = LoggerMessage.Define<int, int, string>(
LogLevel.Warning, LogLevel.Trace,
new EventId(2012, "FailedToDeserializeMessage"), new EventId(2010, "ProcessorMatched"),
"[Sckt {SocketId}] deserialization failed: {ErrorMessage}"); "[Sckt {SocketId}] {Count} processor(s) matched to message with listener identifier {ListenerId}");
_userMessageProcessingFailed = LoggerMessage.Define<int, string>( _receivedMessageNotRecognized = LoggerMessage.Define<int, int>(
LogLevel.Warning, LogLevel.Warning,
new EventId(2013, "UserMessageProcessingFailed"), new EventId(2011, "ReceivedMessageNotRecognized"),
"[Sckt {SocketId}] user message processing failed: {ErrorMessage}"); "[Sckt {SocketId}] received message not recognized by handler {ProcessorId}");
_messageProcessed = LoggerMessage.Define<int, long, long>( _failedToDeserializeMessage = LoggerMessage.Define<int, string?>(
LogLevel.Trace, LogLevel.Warning,
new EventId(2014, "MessageProcessed"), new EventId(2012, "FailedToDeserializeMessage"),
"[Sckt {SocketId}] message processed in {ProcessingTime}ms, {ParsingTime}ms parsing"); "[Sckt {SocketId}] deserialization failed: {ErrorMessage}");
_closingSubscription = LoggerMessage.Define<int, int>( _userMessageProcessingFailed = LoggerMessage.Define<int, string>(
LogLevel.Debug, LogLevel.Warning,
new EventId(2015, "ClosingSubscription"), new EventId(2013, "UserMessageProcessingFailed"),
"[Sckt {SocketId}] closing subscription {SubscriptionId}"); "[Sckt {SocketId}] user message processing failed: {ErrorMessage}");
_notUnsubscribingSubscriptionBecauseDuplicateRunning = LoggerMessage.Define<int>( _messageProcessed = LoggerMessage.Define<int, long, long>(
LogLevel.Debug, LogLevel.Trace,
new EventId(2016, "NotUnsubscribingSubscription"), new EventId(2014, "MessageProcessed"),
"[Sckt {SocketId}] not unsubscribing subscription as there is still a duplicate subscription running"); "[Sckt {SocketId}] message processed in {ProcessingTime}ms, {ParsingTime}ms parsing");
_alreadyClosing = LoggerMessage.Define<int>( _closingSubscription = LoggerMessage.Define<int, int>(
LogLevel.Debug, LogLevel.Debug,
new EventId(2017, "AlreadyClosing"), new EventId(2015, "ClosingSubscription"),
"[Sckt {SocketId}] already closing"); "[Sckt {SocketId}] closing subscription {SubscriptionId}");
_closingNoMoreSubscriptions = LoggerMessage.Define<int>( _notUnsubscribingSubscriptionBecauseDuplicateRunning = LoggerMessage.Define<int>(
LogLevel.Debug, LogLevel.Debug,
new EventId(2018, "ClosingNoMoreSubscriptions"), new EventId(2016, "NotUnsubscribingSubscription"),
"[Sckt {SocketId}] closing as there are no more subscriptions"); "[Sckt {SocketId}] not unsubscribing subscription as there is still a duplicate subscription running");
_addingNewSubscription = LoggerMessage.Define<int, int, int>( _alreadyClosing = LoggerMessage.Define<int>(
LogLevel.Debug, LogLevel.Debug,
new EventId(2019, "AddingNewSubscription"), new EventId(2017, "AlreadyClosing"),
"[Sckt {SocketId}] adding new subscription with id {SubscriptionId}, total subscriptions on connection: {UserSubscriptionCount}"); "[Sckt {SocketId}] already closing");
_nothingToResubscribeCloseConnection = LoggerMessage.Define<int>( _closingNoMoreSubscriptions = LoggerMessage.Define<int>(
LogLevel.Debug, LogLevel.Debug,
new EventId(2020, "NothingToResubscribe"), new EventId(2018, "ClosingNoMoreSubscriptions"),
"[Sckt {SocketId}] nothing to resubscribe, closing connection"); "[Sckt {SocketId}] closing as there are no more subscriptions");
_failedAuthenticationDisconnectAndReconnect = LoggerMessage.Define<int>( _addingNewSubscription = LoggerMessage.Define<int, int, int>(
LogLevel.Warning, LogLevel.Debug,
new EventId(2021, "FailedAuthentication"), new EventId(2019, "AddingNewSubscription"),
"[Sckt {SocketId}] authentication failed on reconnected socket. Disconnecting and reconnecting"); "[Sckt {SocketId}] adding new subscription with id {SubscriptionId}, total subscriptions on connection: {UserSubscriptionCount}");
_authenticationSucceeded = LoggerMessage.Define<int>( _nothingToResubscribeCloseConnection = LoggerMessage.Define<int>(
LogLevel.Debug, LogLevel.Debug,
new EventId(2022, "AuthenticationSucceeded"), new EventId(2020, "NothingToResubscribe"),
"[Sckt {SocketId}] authentication succeeded on reconnected socket"); "[Sckt {SocketId}] nothing to resubscribe, closing connection");
_failedRequestRevitalization = LoggerMessage.Define<int, string?>( _failedAuthenticationDisconnectAndReconnect = LoggerMessage.Define<int>(
LogLevel.Warning, LogLevel.Warning,
new EventId(2023, "FailedRequestRevitalization"), new EventId(2021, "FailedAuthentication"),
"[Sckt {SocketId}] failed request revitalization: {ErrorMessage}"); "[Sckt {SocketId}] authentication failed on reconnected socket. Disconnecting and reconnecting");
_allSubscriptionResubscribed = LoggerMessage.Define<int>( _authenticationSucceeded = LoggerMessage.Define<int>(
LogLevel.Debug, LogLevel.Debug,
new EventId(2024, "AllSubscriptionResubscribed"), new EventId(2022, "AuthenticationSucceeded"),
"[Sckt {SocketId}] all subscription successfully resubscribed on reconnected socket"); "[Sckt {SocketId}] authentication succeeded on reconnected socket");
_subscriptionUnsubscribed = LoggerMessage.Define<int, int>( _failedRequestRevitalization = LoggerMessage.Define<int, string?>(
LogLevel.Information, LogLevel.Warning,
new EventId(2025, "SubscriptionUnsubscribed"), new EventId(2023, "FailedRequestRevitalization"),
"[Sckt {SocketId}] subscription {SubscriptionId} unsubscribed"); "[Sckt {SocketId}] failed request revitalization: {ErrorMessage}");
_sendingPeriodic = LoggerMessage.Define<int, string>( _allSubscriptionResubscribed = LoggerMessage.Define<int>(
LogLevel.Trace, LogLevel.Debug,
new EventId(2026, "SendingPeriodic"), new EventId(2024, "AllSubscriptionResubscribed"),
"[Sckt {SocketId}] sending periodic {Identifier}"); "[Sckt {SocketId}] all subscription successfully resubscribed on reconnected socket");
_periodicSendFailed = LoggerMessage.Define<int, string, string>( _subscriptionUnsubscribed = LoggerMessage.Define<int, int>(
LogLevel.Warning, LogLevel.Information,
new EventId(2027, "PeriodicSendFailed"), new EventId(2025, "SubscriptionUnsubscribed"),
"[Sckt {SocketId}] periodic send {Identifier} failed: {ErrorMessage}"); "[Sckt {SocketId}] subscription {SubscriptionId} unsubscribed");
_sendingData = LoggerMessage.Define<int, int, string>( _sendingPeriodic = LoggerMessage.Define<int, string>(
LogLevel.Trace, LogLevel.Trace,
new EventId(2028, "SendingData"), new EventId(2026, "SendingPeriodic"),
"[Sckt {SocketId}] [Req {RequestId}] sending message: {Data}"); "[Sckt {SocketId}] sending periodic {Identifier}");
_receivedMessageNotMatchedToAnyListener = LoggerMessage.Define<int, string, string>( _periodicSendFailed = LoggerMessage.Define<int, string, string>(
LogLevel.Warning, LogLevel.Warning,
new EventId(2029, "ReceivedMessageNotMatchedToAnyListener"), new EventId(2027, "PeriodicSendFailed"),
"[Sckt {SocketId}] received message not matched to any listener. ListenId: {ListenId}, current listeners: [{ListenIds}]"); "[Sckt {SocketId}] periodic send {Identifier} failed: {ErrorMessage}");
_failedToParse = LoggerMessage.Define<int, string>( _sendingData = LoggerMessage.Define<int, int, string>(
LogLevel.Warning, LogLevel.Trace,
new EventId(2030, "FailedToParse"), new EventId(2028, "SendingData"),
"[Sckt {SocketId}] failed to parse data: {Error}"); "[Sckt {SocketId}] [Req {RequestId}] sending message: {Data}");
_sendingByteData = LoggerMessage.Define<int, int, int>( _receivedMessageNotMatchedToAnyListener = LoggerMessage.Define<int, string, string>(
LogLevel.Trace, LogLevel.Warning,
new EventId(2031, "SendingByteData"), new EventId(2029, "ReceivedMessageNotMatchedToAnyListener"),
"[Sckt {SocketId}] [Req {RequestId}] sending byte message of length: {Length}"); "[Sckt {SocketId}] received message not matched to any listener. ListenId: {ListenId}, current listeners: {ListenIds}");
}
_processorMatched = LoggerMessage.Define<int, string, string>( public static void ActivityPaused(this ILogger logger, int socketId, bool paused)
LogLevel.Trace, {
new EventId(2032, "ProcessorMatched"), _activityPaused(logger, socketId, paused, null);
"[Sckt {SocketId}] listener '{ListenId}' matched to message with listener identifier {ListenerId}"); }
} public static void SocketStatusChanged(this ILogger logger, int socketId, Sockets.SocketConnection.SocketStatus oldStatus, Sockets.SocketConnection.SocketStatus newStatus)
{
_socketStatusChanged(logger, socketId, oldStatus, newStatus, null);
}
public static void ActivityPaused(this ILogger logger, int socketId, bool paused) public static void FailedReconnectProcessing(this ILogger logger, int socketId, string? error)
{ {
_activityPaused(logger, socketId, paused, null); _failedReconnectProcessing(logger, socketId, error, null);
} }
public static void SocketStatusChanged(this ILogger logger, int socketId, Sockets.SocketConnection.SocketStatus oldStatus, Sockets.SocketConnection.SocketStatus newStatus) public static void UnknownExceptionWhileProcessingReconnection(this ILogger logger, int socketId, Exception e)
{ {
_socketStatusChanged(logger, socketId, oldStatus, newStatus, null); _unknownExceptionWhileProcessingReconnection(logger, socketId, e);
} }
public static void FailedReconnectProcessing(this ILogger logger, int socketId, string? error) public static void WebSocketErrorCodeAndDetails(this ILogger logger, int socketId, WebSocketError error, string? details, Exception e)
{ {
_failedReconnectProcessing(logger, socketId, error, null); _webSocketErrorCodeAndDetails(logger, socketId, error, details, e);
} }
public static void UnknownExceptionWhileProcessingReconnection(this ILogger logger, int socketId, Exception e) public static void WebSocketError(this ILogger logger, int socketId, string? errorMessage, Exception e)
{ {
_unknownExceptionWhileProcessingReconnection(logger, socketId, e); _webSocketError(logger, socketId, errorMessage, e);
} }
public static void WebSocketErrorCodeAndDetails(this ILogger logger, int socketId, WebSocketError error, string? details, Exception e) public static void MessageSentNotPending(this ILogger logger, int socketId, int requestId)
{ {
_webSocketErrorCodeAndDetails(logger, socketId, error, details, e); _messageSentNotPending(logger, socketId, requestId, null);
} }
public static void WebSocketError(this ILogger logger, int socketId, string? errorMessage, Exception e) public static void ReceivedData(this ILogger logger, int socketId, string originalData)
{ {
_webSocketError(logger, socketId, errorMessage, e); _receivedData(logger, socketId, originalData, null);
} }
public static void FailedToEvaluateMessage(this ILogger logger, int socketId, string originalData)
{
_failedToEvaluateMessage(logger, socketId, originalData, null);
}
public static void ErrorProcessingMessage(this ILogger logger, int socketId, Exception e)
{
_errorProcessingMessage(logger, socketId, e);
}
public static void ProcessorMatched(this ILogger logger, int socketId, int count, string listenerId)
{
_processorMatched(logger, socketId, count, 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)
{
_failedToDeserializeMessage(logger, socketId, errorMessage, null);
}
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 MessageSentNotPending(this ILogger logger, int socketId, int requestId) public static void NothingToResubscribeCloseConnection(this ILogger logger, int socketId)
{ {
_messageSentNotPending(logger, socketId, requestId, 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 ReceivedData(this ILogger logger, int socketId, string originalData) public static void SendingData(this ILogger logger, int socketId, int requestId, string data)
{ {
_receivedData(logger, socketId, originalData, null); _sendingData(logger, socketId, requestId, data, null);
} }
public static void FailedToParse(this ILogger logger, int socketId, string error) public static void ReceivedMessageNotMatchedToAnyListener(this ILogger logger, int socketId, string listenId, string listenIds)
{ {
_failedToParse(logger, socketId, error, null); _receivedMessageNotMatchedToAnyListener(logger, socketId, listenId, listenIds, null);
} }
public static void FailedToEvaluateMessage(this ILogger logger, int socketId, string originalData)
{
_failedToEvaluateMessage(logger, socketId, originalData, null);
}
public static void ErrorProcessingMessage(this ILogger logger, int socketId, Exception e)
{
_errorProcessingMessage(logger, socketId, e);
}
public static void ProcessorMatched(this ILogger logger, int socketId, string listener, string listenerId)
{
_processorMatched(logger, socketId, listener, listenerId, null);
}
public static void ReceivedMessageNotRecognized(this ILogger logger, int socketId, int id)
{
_receivedMessageNotRecognized(logger, socketId, id, null);
}
public static void FailedToDeserializeMessage(this ILogger logger, int socketId, string? errorMessage, Exception? ex)
{
_failedToDeserializeMessage(logger, socketId, errorMessage, ex);
}
public static void UserMessageProcessingFailed(this ILogger logger, int socketId, string errorMessage, Exception e)
{
_userMessageProcessingFailed(logger, socketId, errorMessage, e);
}
public static void MessageProcessed(this ILogger logger, int socketId, long processingTime, long parsingTime)
{
_messageProcessed(logger, socketId, processingTime, parsingTime, null);
}
public static void ClosingSubscription(this ILogger logger, int socketId, int subscriptionId)
{
_closingSubscription(logger, socketId, subscriptionId, null);
}
public static void NotUnsubscribingSubscriptionBecauseDuplicateRunning(this ILogger logger, int socketId)
{
_notUnsubscribingSubscriptionBecauseDuplicateRunning(logger, socketId, null);
}
public static void AlreadyClosing(this ILogger logger, int socketId)
{
_alreadyClosing(logger, socketId, null);
}
public static void ClosingNoMoreSubscriptions(this ILogger logger, int socketId)
{
_closingNoMoreSubscriptions(logger, socketId, null);
}
public static void AddingNewSubscription(this ILogger logger, int socketId, int subscriptionId, int userSubscriptionCount)
{
_addingNewSubscription(logger, socketId, subscriptionId, userSubscriptionCount, null);
}
public static void NothingToResubscribeCloseConnection(this ILogger logger, int socketId)
{
_nothingToResubscribeCloseConnection(logger, socketId, null);
}
public static void FailedAuthenticationDisconnectAndRecoonect(this ILogger logger, int socketId)
{
_failedAuthenticationDisconnectAndReconnect(logger, socketId, null);
}
public static void AuthenticationSucceeded(this ILogger logger, int socketId)
{
_authenticationSucceeded(logger, socketId, null);
}
public static void FailedRequestRevitalization(this ILogger logger, int socketId, string? errorMessage)
{
_failedRequestRevitalization(logger, socketId, errorMessage, null);
}
public static void AllSubscriptionResubscribed(this ILogger logger, int socketId)
{
_allSubscriptionResubscribed(logger, socketId, null);
}
public static void SubscriptionUnsubscribed(this ILogger logger, int socketId, int subscriptionId)
{
_subscriptionUnsubscribed(logger, socketId, subscriptionId, null);
}
public static void SendingPeriodic(this ILogger logger, int socketId, string identifier)
{
_sendingPeriodic(logger, socketId, identifier, null);
}
public static void PeriodicSendFailed(this ILogger logger, int socketId, string identifier, string errorMessage, Exception e)
{
_periodicSendFailed(logger, socketId, identifier, errorMessage, e);
}
public static void SendingData(this ILogger logger, int socketId, int requestId, string data)
{
_sendingData(logger, socketId, requestId, data, null);
}
public static void ReceivedMessageNotMatchedToAnyListener(this ILogger logger, int socketId, string listenId, string listenIds)
{
_receivedMessageNotMatchedToAnyListener(logger, socketId, listenId, listenIds, null);
}
public static void 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.Warning,
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)
{ {
_klineTrackerStopping(logger, symbol, null); _klineTrackerStartFailed(logger, symbol, error, null);
} }
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)
{ {
_tradeTrackerStopping(logger, symbol, null); _tradeTrackerStartFailed(logger, symbol, error, null);
} }
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);
}
} }
} }
+36 -35
View File
@@ -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;
}
} }
} }
-25
View File
@@ -1,25 +0,0 @@
namespace CryptoExchange.Net.Objects;
/// <summary>
/// An alias used by the exchange for an asset commonly known by another name
/// </summary>
public class AssetAlias
{
/// <summary>
/// The name of the asset on the exchange
/// </summary>
public string ExchangeAssetName { get; set; }
/// <summary>
/// The name of the asset as it's commonly known
/// </summary>
public string CommonAssetName { get; set; }
/// <summary>
/// ctor
/// </summary>
public AssetAlias(string exchangeName, string commonName)
{
ExchangeAssetName = exchangeName;
CommonAssetName = commonName;
}
}
@@ -1,30 +0,0 @@
using System.Linq;
namespace CryptoExchange.Net.Objects;
/// <summary>
/// Exchange configuration for asset aliases
/// </summary>
public class AssetAliasConfiguration
{
/// <summary>
/// Defined aliases
/// </summary>
public AssetAlias[] Aliases { get; set; } = [];
/// <summary>
/// Auto convert asset names when using the Shared interfaces. Defaults to true
/// </summary>
public bool AutoConvertEnabled { get; set; } = true;
/// <summary>
/// Map the common name to an exchange name for an asset. If there is no alias the input name is returned
/// </summary>
public string CommonToExchangeName(string commonName) => !AutoConvertEnabled ? commonName : Aliases.SingleOrDefault(x => x.CommonAssetName == commonName)?.ExchangeAssetName ?? commonName;
/// <summary>
/// Map the exchange name to a common name for an asset. If there is no alias the input name is returned
/// </summary>
public string ExchangeToCommonName(string exchangeName) => !AutoConvertEnabled ? exchangeName : Aliases.SingleOrDefault(x => x.ExchangeAssetName == exchangeName)?.CommonAssetName ?? exchangeName;
}

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