1
0
mirror of https://github.com/JKorf/CryptoExchange.Net.git synced 2026-08-14 18:02:58 +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,7 +74,7 @@ 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)
{ {
} }
@@ -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
{
}
} }
@@ -1,13 +1,13 @@
using System; using System;
namespace CryptoExchange.Net.Attributes; namespace CryptoExchange.Net.Attributes
/// <summary>
/// Map a enum entry to string values
/// </summary>
[AttributeUsage(AttributeTargets.Field)]
public class MapAttribute : Attribute
{ {
/// <summary>
/// Map a enum entry to string values
/// </summary>
[AttributeUsage(AttributeTargets.Field)]
public class MapAttribute : Attribute
{
/// <summary> /// <summary>
/// Values mapping to the enum entry /// Values mapping to the enum entry
/// </summary> /// </summary>
@@ -21,4 +21,5 @@ public class MapAttribute : Attribute
{ {
Values = maps; Values = maps;
} }
}
} }
@@ -1,12 +1,15 @@
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>
/// Api credentials, used to sign requests accessing private endpoints
/// </summary>
public class ApiCredentials
{
/// <summary> /// <summary>
/// The api key / label to authenticate requests /// The api key / label to authenticate requests
/// </summary> /// </summary>
@@ -53,4 +56,5 @@ public class ApiCredentials
{ {
return new ApiCredentials(Key, Secret, Pass, CredentialType); return new ApiCredentials(Key, Secret, Pass, CredentialType);
} }
}
} }
@@ -1,10 +1,10 @@
namespace CryptoExchange.Net.Authentication; namespace CryptoExchange.Net.Authentication
/// <summary>
/// Credentials type
/// </summary>
public enum ApiCredentialsType
{ {
/// <summary>
/// Credentials type
/// </summary>
public enum ApiCredentialsType
{
/// <summary> /// <summary>
/// Hmac keys credentials /// Hmac keys credentials
/// </summary> /// </summary>
@@ -17,4 +17,5 @@ public enum ApiCredentialsType
/// Rsa keys credentials in pem/base64 format. Only available for .NetStandard 2.1 and up, use xml format for lower. /// Rsa keys credentials in pem/base64 format. Only available for .NetStandard 2.1 and up, use xml format for lower.
/// </summary> /// </summary>
RsaPem RsaPem
}
} }
@@ -1,20 +1,21 @@
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
{ {
/// <summary>
/// Base class for authentication providers
/// </summary>
public abstract class AuthenticationProvider
{
internal IAuthTimeProvider TimeProvider { get; set; } = new AuthTimeProvider(); internal IAuthTimeProvider TimeProvider { get; set; } = new AuthTimeProvider();
/// <summary> /// <summary>
@@ -50,11 +51,30 @@ public abstract class AuthenticationProvider
} }
/// <summary> /// <summary>
/// Authenticate a request /// Authenticate a request. Output parameters should include the providedParameters input
/// </summary> /// </summary>
/// <param name="apiClient">The Api client sending the request</param> /// <param name="apiClient">The Api client sending the request</param>
/// <param name="requestConfig">The request configuration</param> /// <param name="uri">The uri for the request</param>
public abstract void ProcessRequest(RestApiClient apiClient, RestRequestConfiguration requestConfig); /// <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> /// <summary>
/// SHA256 sign the data and return the bytes /// SHA256 sign the data and return the bytes
@@ -208,9 +228,7 @@ public abstract class AuthenticationProvider
/// <returns></returns> /// <returns></returns>
protected static string SignMD5(string data, SignOutputType? outputType = null) protected static string SignMD5(string data, SignOutputType? outputType = null)
{ {
#pragma warning disable CA5351
using var encryptor = MD5.Create(); using var encryptor = MD5.Create();
#pragma warning restore CA5351
var resultBytes = encryptor.ComputeHash(Encoding.UTF8.GetBytes(data)); var resultBytes = encryptor.ComputeHash(Encoding.UTF8.GetBytes(data));
return outputType == SignOutputType.Base64 ? BytesToBase64String(resultBytes) : BytesToHexString(resultBytes); return outputType == SignOutputType.Base64 ? BytesToBase64String(resultBytes) : BytesToHexString(resultBytes);
} }
@@ -223,9 +241,7 @@ public abstract class AuthenticationProvider
/// <returns></returns> /// <returns></returns>
protected static string SignMD5(byte[] data, SignOutputType? outputType = null) protected static string SignMD5(byte[] data, SignOutputType? outputType = null)
{ {
#pragma warning disable CA5351
using var encryptor = MD5.Create(); using var encryptor = MD5.Create();
#pragma warning restore CA5351
var resultBytes = encryptor.ComputeHash(data); var resultBytes = encryptor.ComputeHash(data);
return outputType == SignOutputType.Base64 ? BytesToBase64String(resultBytes) : BytesToHexString(resultBytes); return outputType == SignOutputType.Base64 ? BytesToBase64String(resultBytes) : BytesToHexString(resultBytes);
} }
@@ -237,9 +253,7 @@ public abstract class AuthenticationProvider
/// <returns></returns> /// <returns></returns>
protected static byte[] SignMD5Bytes(string data) protected static byte[] SignMD5Bytes(string data)
{ {
#pragma warning disable CA5351
using var encryptor = MD5.Create(); using var encryptor = MD5.Create();
#pragma warning restore CA5351
return encryptor.ComputeHash(Encoding.UTF8.GetBytes(data)); return encryptor.ComputeHash(Encoding.UTF8.GetBytes(data));
} }
@@ -451,25 +465,18 @@ public abstract class AuthenticationProvider
/// <returns></returns> /// <returns></returns>
protected static string GetSerializedBody(IMessageSerializer serializer, IDictionary<string, object> parameters) 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)) if (parameters.Count == 1 && parameters.TryGetValue(Constants.BodyPlaceHolderKey, out object? value))
return stringSerializer.Serialize(value); return serializer.Serialize(value);
else else
return stringSerializer.Serialize(parameters); return serializer.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 {
/// <inheritdoc />
protected new TApiCredentials _credentials => (TApiCredentials)base._credentials; protected new TApiCredentials _credentials => (TApiCredentials)base._credentials;
#pragma warning restore IDE1006 // Naming Styles
#pragma warning restore CA1707 // Naming Styles
/// <summary> /// <summary>
/// ctor /// ctor
@@ -478,4 +485,5 @@ public abstract class AuthenticationProvider<TApiCredentials> : AuthenticationPr
protected AuthenticationProvider(TApiCredentials credentials) : base(credentials) protected AuthenticationProvider(TApiCredentials credentials) : base(credentials)
{ {
} }
}
} }
@@ -1,10 +1,10 @@
namespace CryptoExchange.Net.Authentication; namespace CryptoExchange.Net.Authentication
/// <summary>
/// Output string type
/// </summary>
public enum SignOutputType
{ {
/// <summary>
/// Output string type
/// </summary>
public enum SignOutputType
{
/// <summary> /// <summary>
/// Hex string /// Hex string
/// </summary> /// </summary>
@@ -13,4 +13,5 @@ public enum SignOutputType
/// Base64 string /// Base64 string
/// </summary> /// </summary>
Base64 Base64
}
} }
+11 -9
View File
@@ -1,13 +1,11 @@
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
{ {
internal class MemoryCache
{
private readonly ConcurrentDictionary<string, CacheItem> _cache = new ConcurrentDictionary<string, CacheItem>(); private readonly ConcurrentDictionary<string, CacheItem> _cache = new ConcurrentDictionary<string, CacheItem>();
private readonly object _lock = new object();
/// <summary> /// <summary>
/// Add a new cache entry. Will override an existing entry if it already exists /// Add a new cache entry. Will override an existing entry if it already exists
@@ -28,13 +26,16 @@ internal class MemoryCache
/// <returns>Cached value if it was in cache</returns> /// <returns>Cached value if it was in cache</returns>
public object? Get(string key, TimeSpan maxAge) 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); _cache.TryGetValue(key, out CacheItem? value);
if (value == null) if (value == null)
return null; return null;
if (DateTime.UtcNow - value.CacheTime > maxAge)
{
_cache.TryRemove(key, out _);
return null;
}
return value.Value; return value.Value;
} }
@@ -49,4 +50,5 @@ internal class MemoryCache
Value = value; Value = value;
} }
} }
}
} }
+18 -45
View File
@@ -1,18 +1,18 @@
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>
/// Base API for all API clients
/// </summary>
public abstract class BaseApiClient : IDisposable, IBaseApiClient
{
/// <summary> /// <summary>
/// Logger /// Logger
/// </summary> /// </summary>
@@ -39,10 +39,7 @@ public abstract class BaseApiClient : IDisposable, IBaseApiClient
public bool OutputOriginalData { get; } public bool OutputOriginalData { get; }
/// <inheritdoc /> /// <inheritdoc />
public bool Authenticated => ApiCredentials != null; public bool Authenticated => ApiOptions.ApiCredentials != null || ClientOptions.ApiCredentials != null;
/// <inheritdoc />
public ApiCredentials? ApiCredentials { get; set; }
/// <summary> /// <summary>
/// Api options /// Api options
@@ -54,11 +51,6 @@ public abstract class BaseApiClient : IDisposable, IBaseApiClient
/// </summary> /// </summary>
public ExchangeOptions ClientOptions { get; } public ExchangeOptions ClientOptions { get; }
/// <summary>
/// Mapping of a response code to known error types
/// </summary>
protected internal virtual ErrorMapping ErrorMapping { get; } = new ErrorMapping([]);
/// <summary> /// <summary>
/// ctor /// ctor
/// </summary> /// </summary>
@@ -76,10 +68,9 @@ public abstract class BaseApiClient : IDisposable, IBaseApiClient
ApiOptions = apiOptions; ApiOptions = apiOptions;
OutputOriginalData = outputOriginalData; OutputOriginalData = outputOriginalData;
BaseAddress = baseAddress; BaseAddress = baseAddress;
ApiCredentials = apiCredentials?.Copy();
if (ApiCredentials != null) if (apiCredentials != null)
AuthenticationProvider = CreateAuthenticationProvider(ApiCredentials); AuthenticationProvider = CreateAuthenticationProvider(apiCredentials.Copy());
} }
/// <summary> /// <summary>
@@ -92,22 +83,12 @@ public abstract class BaseApiClient : IDisposable, IBaseApiClient
/// <inheritdoc /> /// <inheritdoc />
public abstract string FormatSymbol(string baseAsset, string quoteAsset, TradingMode tradingMode, DateTime? deliverDate = null); public abstract string FormatSymbol(string baseAsset, string quoteAsset, TradingMode tradingMode, DateTime? deliverDate = null);
/// <summary>
/// Get error info for a response code
/// </summary>
public ErrorInfo GetErrorInfo(int code, string? message = null) => GetErrorInfo(code.ToString(), message);
/// <summary>
/// Get error info for a response code
/// </summary>
public ErrorInfo GetErrorInfo(string code, string? message = null) => ErrorMapping.GetErrorInfo(code.ToString(), message);
/// <inheritdoc /> /// <inheritdoc />
public void SetApiCredentials<T>(T credentials) where T : ApiCredentials public void SetApiCredentials<T>(T credentials) where T : ApiCredentials
{ {
ApiCredentials = credentials?.Copy(); ApiOptions.ApiCredentials = credentials;
if (ApiCredentials != null) if (credentials != null)
AuthenticationProvider = CreateAuthenticationProvider(ApiCredentials); AuthenticationProvider = CreateAuthenticationProvider(credentials.Copy());
} }
/// <inheritdoc /> /// <inheritdoc />
@@ -116,25 +97,17 @@ public abstract class BaseApiClient : IDisposable, IBaseApiClient
ClientOptions.Proxy = options.Proxy; ClientOptions.Proxy = options.Proxy;
ClientOptions.RequestTimeout = options.RequestTimeout ?? ClientOptions.RequestTimeout; ClientOptions.RequestTimeout = options.RequestTimeout ?? ClientOptions.RequestTimeout;
ApiCredentials = options.ApiCredentials?.Copy() ?? ApiCredentials; ApiOptions.ApiCredentials = options.ApiCredentials ?? ClientOptions.ApiCredentials;
if (ApiCredentials != null) if (options.ApiCredentials != null)
AuthenticationProvider = CreateAuthenticationProvider(ApiCredentials); AuthenticationProvider = CreateAuthenticationProvider(options.ApiCredentials.Copy());
} }
/// <summary> /// <summary>
/// Dispose /// Dispose
/// </summary> /// </summary>
public void Dispose() public virtual void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
/// <summary>
/// Dispose
/// </summary>
public virtual void Dispose(bool disposing)
{ {
_disposing = true; _disposing = true;
} }
}
} }
+13 -23
View File
@@ -1,16 +1,17 @@
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>
/// 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 /// Version of the CryptoExchange.Net base library
/// </summary> /// </summary>
@@ -65,6 +66,8 @@ public abstract class BaseClient : IDisposable
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.
{ {
_logger = logger?.CreateLogger(exchange) ?? NullLoggerFactory.Instance.CreateLogger(exchange);
Exchange = exchange; Exchange = exchange;
} }
@@ -79,7 +82,7 @@ public abstract class BaseClient : IDisposable
throw new ArgumentNullException(nameof(options)); throw new ArgumentNullException(nameof(options));
ClientOptions = options; ClientOptions = options;
_logger.Log(LogLevel.Trace, "Client configuration: {Options}, CryptoExchange.Net: v{CryptoExchangeVersion}, {Exchange}.Net: v{ExchangeVersion}", options, CryptoExchangeLibVersion, Exchange, ExchangeLibVersion); _logger.Log(LogLevel.Trace, $"Client configuration: {options}, CryptoExchange.Net: v{CryptoExchangeLibVersion}, {Exchange}.Net: v{ExchangeLibVersion}");
} }
/// <summary> /// <summary>
@@ -101,7 +104,7 @@ public abstract class BaseClient : IDisposable
if (ClientOptions == null) if (ClientOptions == null)
throw new InvalidOperationException("Client should have called Initialize before adding API clients"); throw new InvalidOperationException("Client should have called Initialize before adding API clients");
_logger.Log(LogLevel.Trace, " {ApiClient}, base address: {BaseAddress}", apiClient.GetType().Name, apiClient.BaseAddress); _logger.Log(LogLevel.Trace, $" {apiClient.GetType().Name}, base address: {apiClient.BaseAddress}");
ApiClients.Add(apiClient); ApiClients.Add(apiClient);
return apiClient; return apiClient;
} }
@@ -119,24 +122,11 @@ public abstract class BaseClient : IDisposable
/// <summary> /// <summary>
/// Dispose /// Dispose
/// </summary> /// </summary>
public void Dispose() public virtual void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
/// <summary>
/// Dispose
/// </summary>
public virtual void Dispose(bool disposing)
{
if (disposing)
{ {
_logger.Log(LogLevel.Debug, "Disposing client"); _logger.Log(LogLevel.Debug, "Disposing client");
foreach (var client in ApiClients) foreach (var client in ApiClients)
client.Dispose(); client.Dispose();
} }
} }
} }
+7 -8
View File
@@ -1,15 +1,14 @@
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
{ {
/// <summary>
/// Base rest client
/// </summary>
public abstract class BaseRestClient : BaseClient, IRestClient
{
/// <inheritdoc /> /// <inheritdoc />
public int TotalRequestsMade => ApiClients.OfType<RestApiClient>().Sum(s => s.TotalRequestsMade); public int TotalRequestsMade => ApiClients.OfType<RestApiClient>().Sum(s => s.TotalRequestsMade);
@@ -20,6 +19,6 @@ public abstract class BaseRestClient : BaseClient, IRestClient
/// <param name="name">The name of the API this client is for</param> /// <param name="name">The name of the API this client is for</param>
protected BaseRestClient(ILoggerFactory? loggerFactory, string name) : base(loggerFactory, name) protected BaseRestClient(ILoggerFactory? loggerFactory, string name) : base(loggerFactory, name)
{ {
_logger = loggerFactory?.CreateLogger(name + ".RestClient") ?? NullLoggerFactory.Instance.CreateLogger(name); }
} }
} }
+11 -12
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,15 +7,14 @@ 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
{ {
/// <summary>
/// Base for socket client implementations
/// </summary>
public abstract class BaseSocketClient : BaseClient, ISocketClient
{
#region fields #region fields
/// <summary> /// <summary>
@@ -34,11 +33,10 @@ public abstract class BaseSocketClient : BaseClient, ISocketClient
/// <summary> /// <summary>
/// ctor /// ctor
/// </summary> /// </summary>
/// <param name="loggerFactory">Logger factory</param> /// <param name="logger">Logger</param>
/// <param name="name">The name of the exchange this client is for</param> /// <param name="exchange">The name of the exchange this client is for</param>
protected BaseSocketClient(ILoggerFactory? loggerFactory, string name) : base(loggerFactory, name) protected BaseSocketClient(ILoggerFactory? logger, string exchange) : base(logger, exchange)
{ {
_logger = loggerFactory?.CreateLogger(name + ".SocketClient") ?? NullLoggerFactory.Instance.CreateLogger(name);
} }
/// <summary> /// <summary>
@@ -127,4 +125,5 @@ public abstract class BaseSocketClient : BaseClient, ISocketClient
return result; return result;
} }
}
} }
+8 -19
View File
@@ -1,14 +1,14 @@
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
{ {
/// <summary>
/// Base crypto client
/// </summary>
public class CryptoBaseClient : IDisposable
{
private readonly Dictionary<Type, object> _serviceCache = new Dictionary<Type, object>(); private readonly Dictionary<Type, object> _serviceCache = new Dictionary<Type, object>();
/// <summary> /// <summary>
@@ -59,20 +59,9 @@ public class CryptoBaseClient : IDisposable
/// <summary> /// <summary>
/// Dispose /// Dispose
/// </summary> /// </summary>
public void Dispose(bool disposing) public void Dispose()
{
if (disposing)
{ {
_serviceCache.Clear(); _serviceCache.Clear();
} }
} }
/// <summary>
/// Dispose
/// </summary>
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
} }
@@ -1,11 +1,14 @@
using CryptoExchange.Net.Interfaces; using CryptoExchange.Net.Interfaces;
using Microsoft.Extensions.DependencyInjection;
using System; using System;
using System.Collections.Generic;
using System.Linq;
namespace CryptoExchange.Net.Clients; namespace CryptoExchange.Net.Clients
/// <inheritdoc />
public class CryptoRestClient : CryptoBaseClient, ICryptoRestClient
{ {
/// <inheritdoc />
public class CryptoRestClient : CryptoBaseClient, ICryptoRestClient
{
/// <summary> /// <summary>
/// ctor /// ctor
/// </summary> /// </summary>
@@ -20,4 +23,5 @@ public class CryptoRestClient : CryptoBaseClient, ICryptoRestClient
public CryptoRestClient(IServiceProvider serviceProvider) : base(serviceProvider) public CryptoRestClient(IServiceProvider serviceProvider) : base(serviceProvider)
{ {
} }
}
} }
@@ -1,11 +1,11 @@
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
{ {
/// <inheritdoc />
public class CryptoSocketClient : CryptoBaseClient, ICryptoSocketClient
{
/// <summary> /// <summary>
/// ctor /// ctor
/// </summary> /// </summary>
@@ -20,4 +20,5 @@ public class CryptoSocketClient : CryptoBaseClient, ICryptoSocketClient
public CryptoSocketClient(IServiceProvider serviceProvider) : base(serviceProvider) public CryptoSocketClient(IServiceProvider serviceProvider) : base(serviceProvider)
{ {
} }
}
} }
+80 -71
View File
@@ -11,20 +11,19 @@ using CryptoExchange.Net.Caching;
using CryptoExchange.Net.Interfaces; using CryptoExchange.Net.Interfaces;
using CryptoExchange.Net.Logging.Extensions; using CryptoExchange.Net.Logging.Extensions;
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.RateLimiting; using CryptoExchange.Net.RateLimiting;
using CryptoExchange.Net.RateLimiting.Interfaces; using CryptoExchange.Net.RateLimiting.Interfaces;
using CryptoExchange.Net.Requests; using CryptoExchange.Net.Requests;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
namespace CryptoExchange.Net.Clients; namespace CryptoExchange.Net.Clients
/// <summary>
/// Base rest API client for interacting with a REST API
/// </summary>
public abstract class RestApiClient : BaseApiClient, IRestApiClient
{ {
/// <summary>
/// Base rest API client for interacting with a REST API
/// </summary>
public abstract class RestApiClient : BaseApiClient, IRestApiClient
{
/// <inheritdoc /> /// <inheritdoc />
public IRequestFactory RequestFactory { get; set; } = new RequestFactory(); public IRequestFactory RequestFactory { get; set; } = new RequestFactory();
@@ -55,7 +54,7 @@ public abstract class RestApiClient : BaseApiClient, IRestApiClient
/// <summary> /// <summary>
/// Request headers to be sent with each request /// Request headers to be sent with each request
/// </summary> /// </summary>
protected Dictionary<string, string> StandardRequestHeaders { get; set; } = []; protected Dictionary<string, string>? StandardRequestHeaders { get; set; }
/// <summary> /// <summary>
/// Whether parameters need to be ordered /// Whether parameters need to be ordered
@@ -239,14 +238,13 @@ public abstract class RestApiClient : BaseApiClient, IRestApiClient
additionalHeaders); additionalHeaders);
_logger.RestApiSendRequest(request.RequestId, definition, request.Content, string.IsNullOrEmpty(request.Uri.Query) ? "-" : request.Uri.Query, string.Join(", ", request.GetHeaders().Select(h => h.Key + $"=[{string.Join(",", h.Value)}]"))); _logger.RestApiSendRequest(request.RequestId, definition, request.Content, string.IsNullOrEmpty(request.Uri.Query) ? "-" : request.Uri.Query, string.Join(", ", request.GetHeaders().Select(h => h.Key + $"=[{string.Join(",", h.Value)}]")));
TotalRequestsMade++; TotalRequestsMade++;
var result = await GetResponseAsync<T>(definition, request, definition.RateLimitGate, cancellationToken).ConfigureAwait(false); var result = await GetResponseAsync<T>(request, definition.RateLimitGate, cancellationToken).ConfigureAwait(false);
if (result.Error is not CancellationRequestedError) if (result.Error is not CancellationRequestedError)
{ {
var originalData = OutputOriginalData ? result.OriginalData : "[Data only available when OutputOriginal = true]";
if (!result) if (!result)
_logger.RestApiErrorReceived(result.RequestId, result.ResponseStatusCode, (long)Math.Floor(result.ResponseTime!.Value.TotalMilliseconds), result.Error?.ToString(), originalData, result.Error?.Exception); _logger.RestApiErrorReceived(result.RequestId, result.ResponseStatusCode, (long)Math.Floor(result.ResponseTime!.Value.TotalMilliseconds), result.Error?.ToString());
else else
_logger.RestApiResponseReceived(result.RequestId, result.ResponseStatusCode, (long)Math.Floor(result.ResponseTime!.Value.TotalMilliseconds), originalData); _logger.RestApiResponseReceived(result.RequestId, result.ResponseStatusCode, (long)Math.Floor(result.ResponseTime!.Value.TotalMilliseconds), OutputOriginalData ? result.OriginalData : "[Data only available when OutputOriginal = true]");
} }
else else
{ {
@@ -364,59 +362,75 @@ public abstract class RestApiClient : BaseApiClient, IRestApiClient
ParameterCollection? bodyParameters, ParameterCollection? bodyParameters,
Dictionary<string, string>? additionalHeaders) Dictionary<string, string>? additionalHeaders)
{ {
var requestConfiguration = new RestRequestConfiguration( var uriParams = uriParameters == null ? null : CreateParameterDictionary(uriParameters);
definition, var bodyParams = bodyParameters == null ? null : CreateParameterDictionary(bodyParameters);
baseAddress,
uriParameters == null ? new Dictionary<string, object>() : CreateParameterDictionary(uriParameters),
bodyParameters == null ? new Dictionary<string, object>() : CreateParameterDictionary(bodyParameters),
new Dictionary<string, string>(additionalHeaders ?? []),
definition.ArraySerialization ?? ArraySerialization,
definition.ParameterPosition ?? ParameterPositions[definition.Method],
definition.RequestBodyFormat ?? RequestBodyFormat);
var uri = new Uri(baseAddress.AppendPath(definition.Path));
var arraySerialization = definition.ArraySerialization ?? ArraySerialization;
var bodyFormat = definition.RequestBodyFormat ?? RequestBodyFormat;
var parameterPosition = definition.ParameterPosition ?? ParameterPositions[definition.Method];
Dictionary<string, string>? headers = null;
if (AuthenticationProvider != null)
{
try try
{ {
AuthenticationProvider?.ProcessRequest(this, requestConfiguration); AuthenticationProvider.AuthenticateRequest(
this,
uri,
definition.Method,
ref uriParams,
ref bodyParams,
ref headers,
definition.Authenticated,
arraySerialization,
parameterPosition,
bodyFormat
);
} }
catch (Exception ex) catch (Exception ex)
{ {
throw new Exception("Failed to authenticate request, make sure your API credentials are correct", ex); throw new Exception("Failed to authenticate request, make sure your API credentials are correct", ex);
} }
}
var queryString = requestConfiguration.GetQueryString(true); // Add the auth parameters to the uri, start with a new URI to be able to sort the parameters including the auth parameters
if (!string.IsNullOrEmpty(queryString) && !queryString.StartsWith("?")) if (uriParams != null)
queryString = $"?{queryString}"; uri = uri.SetParameters(uriParams, arraySerialization);
var uri = new Uri(baseAddress.AppendPath(definition.Path) + queryString);
var request = RequestFactory.Create(definition.Method, uri, requestId); var request = RequestFactory.Create(definition.Method, uri, requestId);
request.Accept = Constants.JsonContentHeader; request.Accept = Constants.JsonContentHeader;
foreach (var header in requestConfiguration.Headers) if (headers != null)
{
foreach (var header in headers)
request.AddHeader(header.Key, header.Value); request.AddHeader(header.Key, header.Value);
}
if (additionalHeaders != null)
{
foreach (var header in additionalHeaders)
request.AddHeader(header.Key, header.Value);
}
if (StandardRequestHeaders != null)
{
foreach (var header in StandardRequestHeaders) foreach (var header in StandardRequestHeaders)
{ {
// Only add it if it isn't overwritten // Only add it if it isn't overwritten
if (!requestConfiguration.Headers.ContainsKey(header.Key)) if (additionalHeaders?.ContainsKey(header.Key) != true)
request.AddHeader(header.Key, header.Value); request.AddHeader(header.Key, header.Value);
} }
if (requestConfiguration.ParameterPosition == HttpMethodParameterPosition.InBody)
{
var contentType = requestConfiguration.BodyFormat == RequestBodyFormat.Json ? Constants.JsonContentHeader : Constants.FormContentHeader;
var bodyContent = requestConfiguration.GetBodyContent();
if (bodyContent != null)
{
request.SetContent(bodyContent, contentType);
} }
else
if (parameterPosition == HttpMethodParameterPosition.InBody)
{ {
if (requestConfiguration.BodyParameters != null && requestConfiguration.BodyParameters.Count != 0) var contentType = bodyFormat == RequestBodyFormat.Json ? Constants.JsonContentHeader : Constants.FormContentHeader;
WriteParamBody(request, requestConfiguration.BodyParameters, contentType); if (bodyParams != null && bodyParams.Count != 0)
WriteParamBody(request, bodyParams, contentType);
else else
request.SetContent(RequestBodyEmptyContent, contentType); request.SetContent(RequestBodyEmptyContent, contentType);
} }
}
return request; return request;
} }
@@ -424,13 +438,11 @@ public abstract class RestApiClient : BaseApiClient, IRestApiClient
/// <summary> /// <summary>
/// Executes the request and returns the result deserialized into the type parameter class /// Executes the request and returns the result deserialized into the type parameter class
/// </summary> /// </summary>
/// <param name="requestDefinition">The request definition</param>
/// <param name="request">The request object to execute</param> /// <param name="request">The request object to execute</param>
/// <param name="gate">The ratelimit gate used</param> /// <param name="gate">The ratelimit gate used</param>
/// <param name="cancellationToken">Cancellation token</param> /// <param name="cancellationToken">Cancellation token</param>
/// <returns></returns> /// <returns></returns>
protected virtual async Task<WebCallResult<T>> GetResponseAsync<T>( protected virtual async Task<WebCallResult<T>> GetResponseAsync<T>(
RequestDefinition requestDefinition,
IRequest request, IRequest request,
IRateLimitGate? gate, IRateLimitGate? gate,
CancellationToken cancellationToken) CancellationToken cancellationToken)
@@ -450,10 +462,10 @@ public abstract class RestApiClient : BaseApiClient, IRestApiClient
var outputOriginalData = ApiOptions.OutputOriginalData ?? ClientOptions.OutputOriginalData; var outputOriginalData = ApiOptions.OutputOriginalData ?? ClientOptions.OutputOriginalData;
accessor = CreateAccessor(); accessor = CreateAccessor();
if (!response.IsSuccessStatusCode && !requestDefinition.TryParseOnNonSuccess) if (!response.IsSuccessStatusCode)
{ {
// Error response // Error response
var readResult = await accessor.Read(responseStream, true).ConfigureAwait(false); await accessor.Read(responseStream, true).ConfigureAwait(false);
Error error; Error error;
if (response.StatusCode == (HttpStatusCode)418 || response.StatusCode == (HttpStatusCode)429) if (response.StatusCode == (HttpStatusCode)418 || response.StatusCode == (HttpStatusCode)429)
@@ -469,7 +481,7 @@ public abstract class RestApiClient : BaseApiClient, IRestApiClient
} }
else else
{ {
error = ParseErrorResponse((int)response.StatusCode, response.ResponseHeaders, accessor, readResult.Error?.Exception); error = ParseErrorResponse((int)response.StatusCode, response.ResponseHeaders, accessor);
} }
if (error.Code == null || error.Code == 0) if (error.Code == null || error.Code == 0)
@@ -478,19 +490,20 @@ public abstract class RestApiClient : BaseApiClient, IRestApiClient
return new WebCallResult<T>(response.StatusCode, response.ResponseHeaders, sw.Elapsed, responseLength, OutputOriginalData ? accessor.GetOriginalString() : null, request.RequestId, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), ResultDataSource.Server, default, error!); return new WebCallResult<T>(response.StatusCode, response.ResponseHeaders, sw.Elapsed, responseLength, OutputOriginalData ? accessor.GetOriginalString() : null, request.RequestId, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), ResultDataSource.Server, default, error!);
} }
var valid = await accessor.Read(responseStream, outputOriginalData).ConfigureAwait(false);
if (typeof(T) == typeof(object)) if (typeof(T) == typeof(object))
// Success status code and expected empty response, assume it's correct // Success status code and expected empty response, assume it's correct
return new WebCallResult<T>(statusCode, headers, sw.Elapsed, 0, accessor.OriginalDataAvailable ? accessor.GetOriginalString() : "[Data only available when OutputOriginal = true in client options]", request.RequestId, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), ResultDataSource.Server, default, null); return new WebCallResult<T>(statusCode, headers, sw.Elapsed, 0, null, request.RequestId, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), ResultDataSource.Server, default, null);
var valid = await accessor.Read(responseStream, outputOriginalData).ConfigureAwait(false);
if (!valid) if (!valid)
{ {
// Invalid json // Invalid json
return new WebCallResult<T>(response.StatusCode, response.ResponseHeaders, sw.Elapsed, responseLength, OutputOriginalData ? accessor.GetOriginalString() : null, request.RequestId, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), ResultDataSource.Server, default, valid.Error); var error = new ServerError("Failed to parse response: " + valid.Error!.Message, accessor.OriginalDataAvailable ? accessor.GetOriginalString() : "[Data only available when OutputOriginal = true in client options]");
return new WebCallResult<T>(response.StatusCode, response.ResponseHeaders, sw.Elapsed, responseLength, OutputOriginalData ? accessor.GetOriginalString() : null, request.RequestId, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), ResultDataSource.Server, default, error);
} }
// Json response received // Json response received
var parsedError = TryParseError(requestDefinition, response.ResponseHeaders, accessor); var parsedError = TryParseError(response.ResponseHeaders, accessor);
if (parsedError != null) if (parsedError != null)
{ {
if (parsedError is ServerRateLimitError rateError) if (parsedError is ServerRateLimitError rateError)
@@ -512,22 +525,20 @@ public abstract class RestApiClient : BaseApiClient, IRestApiClient
catch (HttpRequestException requestException) catch (HttpRequestException requestException)
{ {
// Request exception, can't reach server for instance // Request exception, can't reach server for instance
var error = new WebError(requestException.Message, requestException); var exceptionInfo = requestException.ToLogString();
return new WebCallResult<T>(null, null, sw.Elapsed, null, null, request.RequestId, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), ResultDataSource.Server, default, error); return new WebCallResult<T>(null, null, sw.Elapsed, null, null, request.RequestId, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), ResultDataSource.Server, default, new WebError(exceptionInfo));
} }
catch (OperationCanceledException canceledException) catch (OperationCanceledException canceledException)
{ {
if (cancellationToken != default && canceledException.CancellationToken == cancellationToken) if (cancellationToken != default && canceledException.CancellationToken == cancellationToken)
{ {
// Cancellation token canceled by caller // Cancellation token canceled by caller
return new WebCallResult<T>(null, null, sw.Elapsed, null, null, request.RequestId, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), ResultDataSource.Server, default, new CancellationRequestedError(canceledException)); return new WebCallResult<T>(null, null, sw.Elapsed, null, null, request.RequestId, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), ResultDataSource.Server, default, new CancellationRequestedError());
} }
else else
{ {
// Request timed out // Request timed out
var error = new WebError($"Request timed out", exception: canceledException); return new WebCallResult<T>(null, null, sw.Elapsed, null, null, request.RequestId, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), ResultDataSource.Server, default, new WebError($"Request timed out"));
error.ErrorType = ErrorType.Timeout;
return new WebCallResult<T>(null, null, sw.Elapsed, null, null, request.RequestId, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), ResultDataSource.Server, default, error);
} }
} }
finally finally
@@ -543,11 +554,10 @@ public abstract class RestApiClient : BaseApiClient, IRestApiClient
/// This method will be called for each response to be able to check if the response is an error or not. /// This method will be called for each response to be able to check if the response is an error or not.
/// If the response is an error this method should return the parsed error, else it should return null /// If the response is an error this method should return the parsed error, else it should return null
/// </summary> /// </summary>
/// <param name="requestDefinition">Request definition</param>
/// <param name="accessor">Data accessor</param> /// <param name="accessor">Data accessor</param>
/// <param name="responseHeaders">The response headers</param> /// <param name="responseHeaders">The response headers</param>
/// <returns>Null if not an error, Error otherwise</returns> /// <returns>Null if not an error, Error otherwise</returns>
protected virtual Error? TryParseError(RequestDefinition requestDefinition, KeyValuePair<string, string[]>[] responseHeaders, IMessageAccessor accessor) => null; protected virtual Error? TryParseError(KeyValuePair<string, string[]>[] responseHeaders, IMessageAccessor accessor) => null;
/// <summary> /// <summary>
/// Can be used to indicate that a request should be retried. Defaults to false. Make sure to retry a max number of times (based on the the tries parameter) or the request will retry forever. /// Can be used to indicate that a request should be retried. Defaults to false. Make sure to retry a max number of times (based on the the tries parameter) or the request will retry forever.
@@ -593,16 +603,12 @@ public abstract class RestApiClient : BaseApiClient, IRestApiClient
{ {
if (contentType == Constants.JsonContentHeader) if (contentType == Constants.JsonContentHeader)
{ {
var serializer = CreateSerializer();
if (serializer is not IStringMessageSerializer stringSerializer)
throw new InvalidOperationException("Non-string message serializer can't get serialized request body");
// Write the parameters as json in the body // Write the parameters as json in the body
string stringData; string stringData;
if (parameters.Count == 1 && parameters.TryGetValue(Constants.BodyPlaceHolderKey, out object? value)) if (parameters.Count == 1 && parameters.TryGetValue(Constants.BodyPlaceHolderKey, out object? value))
stringData = stringSerializer.Serialize(value); stringData = CreateSerializer().Serialize(value);
else else
stringData = stringSerializer.Serialize(parameters); stringData = CreateSerializer().Serialize(parameters);
request.SetContent(stringData, contentType); request.SetContent(stringData, contentType);
} }
else if (contentType == Constants.FormContentHeader) else if (contentType == Constants.FormContentHeader)
@@ -619,11 +625,11 @@ public abstract class RestApiClient : BaseApiClient, IRestApiClient
/// <param name="httpStatusCode">The response status code</param> /// <param name="httpStatusCode">The response status code</param>
/// <param name="responseHeaders">The response headers</param> /// <param name="responseHeaders">The response headers</param>
/// <param name="accessor">Data accessor</param> /// <param name="accessor">Data accessor</param>
/// <param name="exception">Exception</param>
/// <returns></returns> /// <returns></returns>
protected virtual Error ParseErrorResponse(int httpStatusCode, KeyValuePair<string, string[]>[] responseHeaders, IMessageAccessor accessor, Exception? exception) protected virtual Error ParseErrorResponse(int httpStatusCode, KeyValuePair<string, string[]>[] responseHeaders, IMessageAccessor accessor)
{ {
return new ServerError(ErrorInfo.Unknown, exception); var message = accessor.OriginalDataAvailable ? accessor.GetOriginalString() : "[Error response content only available when OutputOriginal = true in client options]";
return new ServerError(message);
} }
/// <summary> /// <summary>
@@ -635,19 +641,21 @@ public abstract class RestApiClient : BaseApiClient, IRestApiClient
/// <returns></returns> /// <returns></returns>
protected virtual ServerRateLimitError ParseRateLimitResponse(int httpStatusCode, KeyValuePair<string, string[]>[] responseHeaders, IMessageAccessor accessor) protected virtual ServerRateLimitError ParseRateLimitResponse(int httpStatusCode, KeyValuePair<string, string[]>[] responseHeaders, IMessageAccessor accessor)
{ {
var message = accessor.OriginalDataAvailable ? accessor.GetOriginalString() : "[Error response content only available when OutputOriginal = true in client options]";
// Handle retry after header // Handle retry after header
var retryAfterHeader = responseHeaders.SingleOrDefault(r => r.Key.Equals("Retry-After", StringComparison.InvariantCultureIgnoreCase)); var retryAfterHeader = responseHeaders.SingleOrDefault(r => r.Key.Equals("Retry-After", StringComparison.InvariantCultureIgnoreCase));
if (!(retryAfterHeader.Value.Length > 0)) if (retryAfterHeader.Value?.Any() != true)
return new ServerRateLimitError(); return new ServerRateLimitError(message);
var value = retryAfterHeader.Value.First(); var value = retryAfterHeader.Value.First();
if (int.TryParse(value, out var seconds)) if (int.TryParse(value, out var seconds))
return new ServerRateLimitError() { RetryAfter = DateTime.UtcNow.AddSeconds(seconds) }; return new ServerRateLimitError(message) { RetryAfter = DateTime.UtcNow.AddSeconds(seconds) };
if (DateTime.TryParse(value, out var datetime)) if (DateTime.TryParse(value, out var datetime))
return new ServerRateLimitError() { RetryAfter = datetime }; return new ServerRateLimitError(message) { RetryAfter = datetime };
return new ServerRateLimitError(); return new ServerRateLimitError(message);
} }
/// <summary> /// <summary>
@@ -724,4 +732,5 @@ public abstract class RestApiClient : BaseApiClient, IRestApiClient
=> ClientOptions.CachingEnabled => ClientOptions.CachingEnabled
&& definition.Method == HttpMethod.Get && definition.Method == HttpMethod.Get
&& !definition.PreventCaching; && !definition.PreventCaching;
}
} }
+32 -60
View File
@@ -1,10 +1,8 @@
using CryptoExchange.Net.Interfaces; using CryptoExchange.Net.Interfaces;
using CryptoExchange.Net.Logging.Extensions; using CryptoExchange.Net.Logging.Extensions;
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.Objects.Sockets; using CryptoExchange.Net.Objects.Sockets;
using CryptoExchange.Net.RateLimiting;
using CryptoExchange.Net.RateLimiting.Interfaces; using CryptoExchange.Net.RateLimiting.Interfaces;
using CryptoExchange.Net.Sockets; using CryptoExchange.Net.Sockets;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
@@ -17,13 +15,13 @@ using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace CryptoExchange.Net.Clients; namespace CryptoExchange.Net.Clients
/// <summary>
/// Base socket API client for interaction with a websocket API
/// </summary>
public abstract class SocketApiClient : BaseApiClient, ISocketApiClient
{ {
/// <summary>
/// Base socket API client for interaction with a websocket API
/// </summary>
public abstract class SocketApiClient : BaseApiClient, ISocketApiClient
{
#region Fields #region Fields
/// <inheritdoc/> /// <inheritdoc/>
public IWebsocketFactory SocketFactory { get; set; } = new WebsocketFactory(); public IWebsocketFactory SocketFactory { get; set; } = new WebsocketFactory();
@@ -43,11 +41,6 @@ public abstract class SocketApiClient : BaseApiClient, ISocketApiClient
/// </summary> /// </summary>
protected TimeSpan KeepAliveInterval { get; set; } = TimeSpan.FromSeconds(10); protected TimeSpan KeepAliveInterval { get; set; } = TimeSpan.FromSeconds(10);
/// <summary>
/// Keep alive timeout for websocket connection
/// </summary>
protected TimeSpan KeepAliveTimeout { get; set; } = TimeSpan.FromSeconds(10);
/// <summary> /// <summary>
/// Handlers for data from the socket which doesn't need to be forwarded to the caller. Ping or welcome messages for example. /// Handlers for data from the socket which doesn't need to be forwarded to the caller. Ping or welcome messages for example.
/// </summary> /// </summary>
@@ -83,11 +76,6 @@ public abstract class SocketApiClient : BaseApiClient, ISocketApiClient
/// </summary> /// </summary>
protected bool AllowTopicsOnTheSameConnection { get; set; } = true; protected bool AllowTopicsOnTheSameConnection { get; set; } = true;
/// <summary>
/// Whether to continue processing and forward unparsable messages to handlers
/// </summary>
protected internal bool ProcessUnparsableMessages { get; set; }
/// <inheritdoc /> /// <inheritdoc />
public double IncomingKbps public double IncomingKbps
{ {
@@ -144,7 +132,7 @@ public abstract class SocketApiClient : BaseApiClient, ISocketApiClient
/// Create a message accessor instance /// Create a message accessor instance
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
protected internal abstract IByteMessageAccessor CreateAccessor(WebSocketMessageType messageType); protected internal abstract IByteMessageAccessor CreateAccessor();
/// <summary> /// <summary>
/// Create a serializer instance /// Create a serializer instance
@@ -217,9 +205,9 @@ public abstract class SocketApiClient : BaseApiClient, ISocketApiClient
{ {
await semaphoreSlim.WaitAsync(ct).ConfigureAwait(false); await semaphoreSlim.WaitAsync(ct).ConfigureAwait(false);
} }
catch (OperationCanceledException tce) catch (OperationCanceledException)
{ {
return new CallResult<UpdateSubscription>(new CancellationRequestedError(tce)); return new CallResult<UpdateSubscription>(new CancellationRequestedError());
} }
try try
@@ -250,7 +238,7 @@ public abstract class SocketApiClient : BaseApiClient, ISocketApiClient
var needsConnecting = !socketConnection.Connected; var needsConnecting = !socketConnection.Connected;
var connectResult = await ConnectIfNeededAsync(socketConnection, subscription.Authenticated, ct).ConfigureAwait(false); var connectResult = await ConnectIfNeededAsync(socketConnection, subscription.Authenticated).ConfigureAwait(false);
if (!connectResult) if (!connectResult)
return new CallResult<UpdateSubscription>(connectResult.Error!); return new CallResult<UpdateSubscription>(connectResult.Error!);
@@ -266,15 +254,15 @@ public abstract class SocketApiClient : BaseApiClient, ISocketApiClient
if (socketConnection.PausedActivity) if (socketConnection.PausedActivity)
{ {
_logger.HasBeenPausedCantSubscribeAtThisMoment(socketConnection.SocketId); _logger.HasBeenPausedCantSubscribeAtThisMoment(socketConnection.SocketId);
return new CallResult<UpdateSubscription>(new ServerError(new ErrorInfo(ErrorType.WebsocketPaused, "Socket is paused"))); return new CallResult<UpdateSubscription>(new ServerError("Socket is paused"));
} }
var waitEvent = new AsyncResetEvent(false); var waitEvent = new AsyncResetEvent(false);
var subQuery = subscription.CreateSubscriptionQuery(socketConnection); var subQuery = subscription.GetSubQuery(socketConnection);
if (subQuery != null) if (subQuery != null)
{ {
// Send the request and wait for answer // Send the request and wait for answer
var subResult = await socketConnection.SendAndWaitQueryAsync(subQuery, waitEvent, ct).ConfigureAwait(false); var subResult = await socketConnection.SendAndWaitQueryAsync(subQuery, waitEvent).ConfigureAwait(false);
if (!subResult) if (!subResult)
{ {
waitEvent?.Set(); waitEvent?.Set();
@@ -314,10 +302,11 @@ public abstract class SocketApiClient : BaseApiClient, ISocketApiClient
/// Send a query on a socket connection to the BaseAddress and wait for the response /// Send a query on a socket connection to the BaseAddress and wait for the response
/// </summary> /// </summary>
/// <typeparam name="THandlerResponse">Expected result type</typeparam> /// <typeparam name="THandlerResponse">Expected result type</typeparam>
/// <typeparam name="TServerResponse">The type returned to the caller</typeparam>
/// <param name="query">The query</param> /// <param name="query">The query</param>
/// <param name="ct">Cancellation token</param> /// <param name="ct">Cancellation token</param>
/// <returns></returns> /// <returns></returns>
protected virtual Task<CallResult<THandlerResponse>> QueryAsync<THandlerResponse>(Query<THandlerResponse> query, CancellationToken ct = default) protected virtual Task<CallResult<THandlerResponse>> QueryAsync<TServerResponse, THandlerResponse>(Query<TServerResponse, THandlerResponse> query, CancellationToken ct = default)
{ {
return QueryAsync(BaseAddress, query, ct); return QueryAsync(BaseAddress, query, ct);
} }
@@ -326,11 +315,12 @@ public abstract class SocketApiClient : BaseApiClient, ISocketApiClient
/// Send a query on a socket connection and wait for the response /// Send a query on a socket connection and wait for the response
/// </summary> /// </summary>
/// <typeparam name="THandlerResponse">Expected result type</typeparam> /// <typeparam name="THandlerResponse">Expected result type</typeparam>
/// <typeparam name="TServerResponse">The type returned to the caller</typeparam>
/// <param name="url">The url for the request</param> /// <param name="url">The url for the request</param>
/// <param name="query">The query</param> /// <param name="query">The query</param>
/// <param name="ct">Cancellation token</param> /// <param name="ct">Cancellation token</param>
/// <returns></returns> /// <returns></returns>
protected virtual async Task<CallResult<THandlerResponse>> QueryAsync<THandlerResponse>(string url, Query<THandlerResponse> query, CancellationToken ct = default) protected virtual async Task<CallResult<THandlerResponse>> QueryAsync<TServerResponse, THandlerResponse>(string url, Query<TServerResponse, THandlerResponse> query, CancellationToken ct = default)
{ {
if (_disposing) if (_disposing)
return new CallResult<THandlerResponse>(new InvalidOperationError("Client disposed, can't query")); return new CallResult<THandlerResponse>(new InvalidOperationError("Client disposed, can't query"));
@@ -340,13 +330,7 @@ public abstract class SocketApiClient : BaseApiClient, ISocketApiClient
SocketConnection socketConnection; SocketConnection socketConnection;
var released = false; var released = false;
await semaphoreSlim.WaitAsync().ConfigureAwait(false);
try
{
await semaphoreSlim.WaitAsync(ct).ConfigureAwait(false);
}
catch (OperationCanceledException) { }
try try
{ {
var socketResult = await GetSocketConnection(url, query.Authenticated, true).ConfigureAwait(false); var socketResult = await GetSocketConnection(url, query.Authenticated, true).ConfigureAwait(false);
@@ -362,7 +346,7 @@ public abstract class SocketApiClient : BaseApiClient, ISocketApiClient
released = true; released = true;
} }
var connectResult = await ConnectIfNeededAsync(socketConnection, query.Authenticated, ct).ConfigureAwait(false); var connectResult = await ConnectIfNeededAsync(socketConnection, query.Authenticated).ConfigureAwait(false);
if (!connectResult) if (!connectResult)
return new CallResult<THandlerResponse>(connectResult.Error!); return new CallResult<THandlerResponse>(connectResult.Error!);
} }
@@ -375,7 +359,7 @@ public abstract class SocketApiClient : BaseApiClient, ISocketApiClient
if (socketConnection.PausedActivity) if (socketConnection.PausedActivity)
{ {
_logger.HasBeenPausedCantSendQueryAtThisMoment(socketConnection.SocketId); _logger.HasBeenPausedCantSendQueryAtThisMoment(socketConnection.SocketId);
return new CallResult<THandlerResponse>(new ServerError(new ErrorInfo(ErrorType.WebsocketPaused, "Socket is paused"))); return new CallResult<THandlerResponse>(new ServerError("Socket is paused"));
} }
if (ct.IsCancellationRequested) if (ct.IsCancellationRequested)
@@ -389,25 +373,18 @@ public abstract class SocketApiClient : BaseApiClient, ISocketApiClient
/// </summary> /// </summary>
/// <param name="socket">The connection to check</param> /// <param name="socket">The connection to check</param>
/// <param name="authenticated">Whether the socket should authenticated</param> /// <param name="authenticated">Whether the socket should authenticated</param>
/// <param name="ct">Cancellation token</param>
/// <returns></returns> /// <returns></returns>
protected virtual async Task<CallResult> ConnectIfNeededAsync(SocketConnection socket, bool authenticated, CancellationToken ct) protected virtual async Task<CallResult> ConnectIfNeededAsync(SocketConnection socket, bool authenticated)
{ {
if (socket.Connected) if (socket.Connected)
return CallResult.SuccessResult; return CallResult.SuccessResult;
var connectResult = await ConnectSocketAsync(socket, ct).ConfigureAwait(false); var connectResult = await ConnectSocketAsync(socket).ConfigureAwait(false);
if (!connectResult) if (!connectResult)
return connectResult; return connectResult;
if (ClientOptions.DelayAfterConnect != TimeSpan.Zero) if (ClientOptions.DelayAfterConnect != TimeSpan.Zero)
{ await Task.Delay(ClientOptions.DelayAfterConnect).ConfigureAwait(false);
try
{
await Task.Delay(ClientOptions.DelayAfterConnect, ct).ConfigureAwait(false);
}
catch (OperationCanceledException) { }
}
if (!authenticated || socket.Authenticated) if (!authenticated || socket.Authenticated)
return CallResult.SuccessResult; return CallResult.SuccessResult;
@@ -583,12 +560,11 @@ public abstract class SocketApiClient : BaseApiClient, ISocketApiClient
/// </summary> /// </summary>
protected async virtual Task HandleConnectRateLimitedAsync() protected async virtual Task HandleConnectRateLimitedAsync()
{ {
if (ClientOptions.RateLimiterEnabled && ClientOptions.ConnectDelayAfterRateLimited.HasValue) if (ClientOptions.RateLimiterEnabled && RateLimiter is not null && ClientOptions.ConnectDelayAfterRateLimited is not null)
{ {
var retryAfter = DateTime.UtcNow.Add(ClientOptions.ConnectDelayAfterRateLimited.Value); var retryAfter = DateTime.UtcNow.Add(ClientOptions.ConnectDelayAfterRateLimited.Value);
_logger.AddingRetryAfterGuard(retryAfter); _logger.AddingRetryAfterGuard(retryAfter);
RateLimiter ??= new RateLimitGate("Connection"); await RateLimiter.SetRetryAfterGuardAsync(retryAfter, RateLimiting.RateLimitItemType.Connection).ConfigureAwait(false);
await RateLimiter.SetRetryAfterGuardAsync(retryAfter, RateLimitItemType.Connection).ConfigureAwait(false);
} }
} }
@@ -596,11 +572,10 @@ public abstract class SocketApiClient : BaseApiClient, ISocketApiClient
/// Connect a socket /// Connect a socket
/// </summary> /// </summary>
/// <param name="socketConnection">The socket to connect</param> /// <param name="socketConnection">The socket to connect</param>
/// <param name="ct">Cancellation token</param>
/// <returns></returns> /// <returns></returns>
protected virtual async Task<CallResult> ConnectSocketAsync(SocketConnection socketConnection, CancellationToken ct) protected virtual async Task<CallResult> ConnectSocketAsync(SocketConnection socketConnection)
{ {
var connectResult = await socketConnection.ConnectAsync(ct).ConfigureAwait(false); var connectResult = await socketConnection.ConnectAsync().ConfigureAwait(false);
if (connectResult) if (connectResult)
{ {
socketConnections.TryAdd(socketConnection.SocketId, socketConnection); socketConnections.TryAdd(socketConnection.SocketId, socketConnection);
@@ -620,7 +595,6 @@ public abstract class SocketApiClient : BaseApiClient, ISocketApiClient
=> new(new Uri(address), ClientOptions.ReconnectPolicy) => new(new Uri(address), ClientOptions.ReconnectPolicy)
{ {
KeepAliveInterval = KeepAliveInterval, KeepAliveInterval = KeepAliveInterval,
KeepAliveTimeout = KeepAliveTimeout,
ReconnectInterval = ClientOptions.ReconnectInterval, ReconnectInterval = ClientOptions.ReconnectInterval,
RateLimiter = ClientOptions.RateLimiterEnabled ? RateLimiter : null, RateLimiter = ClientOptions.RateLimiterEnabled ? RateLimiter : null,
RateLimitingBehavior = ClientOptions.RateLimitingBehaviour, RateLimitingBehavior = ClientOptions.RateLimitingBehaviour,
@@ -732,7 +706,7 @@ public abstract class SocketApiClient : BaseApiClient, ISocketApiClient
if (!socketResult) if (!socketResult)
return socketResult.AsDataless(); return socketResult.AsDataless();
var connectResult = await ConnectIfNeededAsync(socketResult.Data, item.Authenticated, default).ConfigureAwait(false); var connectResult = await ConnectIfNeededAsync(socketResult.Data, item.Authenticated).ConfigureAwait(false);
if (!connectResult) if (!connectResult)
return new CallResult(connectResult.Error!); return new CallResult(connectResult.Error!);
} }
@@ -827,7 +801,7 @@ public abstract class SocketApiClient : BaseApiClient, ISocketApiClient
sb.AppendLine($"\t\t\tId: {subState.Id}"); sb.AppendLine($"\t\t\tId: {subState.Id}");
sb.AppendLine($"\t\t\tConfirmed: {subState.Confirmed}"); sb.AppendLine($"\t\t\tConfirmed: {subState.Confirmed}");
sb.AppendLine($"\t\t\tInvocations: {subState.Invocations}"); sb.AppendLine($"\t\t\tInvocations: {subState.Invocations}");
sb.AppendLine($"\t\t\tIdentifiers: [{subState.ListenMatcher.ToString()}]"); sb.AppendLine($"\t\t\tIdentifiers: [{string.Join(",", subState.Identifiers)}]");
}); });
} }
}); });
@@ -839,11 +813,8 @@ public abstract class SocketApiClient : BaseApiClient, ISocketApiClient
/// <summary> /// <summary>
/// Dispose the client /// Dispose the client
/// </summary> /// </summary>
public override void Dispose(bool disposing) public override void Dispose()
{ {
if (disposing)
return;
_disposing = true; _disposing = true;
var tasks = new List<Task>(); var tasks = new List<Task>();
{ {
@@ -858,7 +829,7 @@ public abstract class SocketApiClient : BaseApiClient, ISocketApiClient
} }
semaphoreSlim?.Dispose(); semaphoreSlim?.Dispose();
base.Dispose(disposing); base.Dispose();
} }
/// <summary> /// <summary>
@@ -876,4 +847,5 @@ public abstract class SocketApiClient : BaseApiClient, ISocketApiClient
/// <param name="data"></param> /// <param name="data"></param>
/// <returns></returns> /// <returns></returns>
public virtual ReadOnlyMemory<byte> PreprocessStreamMessage(SocketConnection connection, WebSocketMessageType type, ReadOnlyMemory<byte> data) => data; public virtual ReadOnlyMemory<byte> PreprocessStreamMessage(SocketConnection connection, WebSocketMessageType type, ReadOnlyMemory<byte> data) => data;
}
} }
@@ -1,13 +1,13 @@
using System; using System;
namespace CryptoExchange.Net.Converters; namespace CryptoExchange.Net.Converters
/// <summary>
/// Mark property as an index in the array
/// </summary>
[AttributeUsage(AttributeTargets.Property)]
public class ArrayPropertyAttribute : Attribute
{ {
/// <summary>
/// Mark property as an index in the array
/// </summary>
[AttributeUsage(AttributeTargets.Property)]
public class ArrayPropertyAttribute : Attribute
{
/// <summary> /// <summary>
/// The index in the array /// The index in the array
/// </summary> /// </summary>
@@ -21,4 +21,5 @@ public class ArrayPropertyAttribute : Attribute
{ {
Index = 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,10 +1,10 @@
namespace CryptoExchange.Net.Converters.MessageParsing; namespace CryptoExchange.Net.Converters.MessageParsing
/// <summary>
/// Node accessor
/// </summary>
public readonly struct NodeAccessor
{ {
/// <summary>
/// Node accessor
/// </summary>
public readonly struct NodeAccessor
{
/// <summary> /// <summary>
/// Index /// Index
/// </summary> /// </summary>
@@ -45,4 +45,5 @@ public readonly struct NodeAccessor
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
public static NodeAccessor PropertyName() { return new NodeAccessor(null, null, 2); } public static NodeAccessor PropertyName() { return new NodeAccessor(null, null, 2); }
}
} }
@@ -1,13 +1,13 @@
using System.Collections; using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
namespace CryptoExchange.Net.Converters.MessageParsing; namespace CryptoExchange.Net.Converters.MessageParsing
/// <summary>
/// Message access definition
/// </summary>
public readonly struct MessagePath : IEnumerable<NodeAccessor>
{ {
/// <summary>
/// Message access definition
/// </summary>
public readonly struct MessagePath : IEnumerable<NodeAccessor>
{
private readonly List<NodeAccessor> _path; private readonly List<NodeAccessor> _path;
internal void Add(NodeAccessor node) internal void Add(NodeAccessor node)
@@ -46,4 +46,5 @@ public readonly struct MessagePath : IEnumerable<NodeAccessor>
{ {
return GetEnumerator(); return GetEnumerator();
} }
}
} }
@@ -1,10 +1,10 @@
namespace CryptoExchange.Net.Converters.MessageParsing; namespace CryptoExchange.Net.Converters.MessageParsing
/// <summary>
/// Message path extension methods
/// </summary>
public static class MessagePathExtension
{ {
/// <summary>
/// Message path extension methods
/// </summary>
public static class MessagePathExtension
{
/// <summary> /// <summary>
/// Add a string node accessor /// Add a string node accessor
/// </summary> /// </summary>
@@ -39,4 +39,5 @@ public static class MessagePathExtension
path.Add(NodeAccessor.Int(index)); path.Add(NodeAccessor.Int(index));
return path; return path;
} }
}
} }
@@ -1,10 +1,10 @@
namespace CryptoExchange.Net.Converters.MessageParsing; namespace CryptoExchange.Net.Converters.MessageParsing
/// <summary>
/// Message node type
/// </summary>
public enum NodeType
{ {
/// <summary>
/// Message node type
/// </summary>
public enum NodeType
{
/// <summary> /// <summary>
/// Array node /// Array node
/// </summary> /// </summary>
@@ -17,4 +17,5 @@ public enum NodeType
/// Value node /// Value node
/// </summary> /// </summary>
Value Value
}
} }
@@ -1,28 +1,28 @@
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
/// 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, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] TContext> : JsonConverter<T> where T : new() where TContext: JsonSerializerContext
# else
public class ArrayConverter<T, TContext> : JsonConverter<T> where T : new() where TContext: JsonSerializerContext
#endif
{
private static readonly ConcurrentDictionary<Type, List<ArrayPropertyInfo>> _typeAttributesCache = new ConcurrentDictionary<Type, List<ArrayPropertyInfo>>();
private static readonly ConcurrentDictionary<JsonConverter, JsonSerializerOptions> _converterOptionsCache = new ConcurrentDictionary<JsonConverter, JsonSerializerOptions>();
/// <inheritdoc /> /// <inheritdoc />
#if NET5_0_OR_GREATER #if NET5_0_OR_GREATER
@@ -39,7 +39,11 @@ public class ArrayConverter<T> : JsonConverter<T> where T : new()
writer.WriteStartArray(); writer.WriteStartArray();
var ordered = _typePropertyInfo.Value.Where(x => x.ArrayProperty != null).OrderBy(p => p.ArrayProperty.Index); 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; var last = -1;
foreach (var prop in ordered) foreach (var prop in ordered)
{ {
@@ -68,7 +72,7 @@ public class ArrayConverter<T> : JsonConverter<T> where T : new()
{ {
NumberHandling = JsonNumberHandling.AllowReadingFromString | JsonNumberHandling.AllowNamedFloatingPointLiterals, NumberHandling = JsonNumberHandling.AllowReadingFromString | JsonNumberHandling.AllowNamedFloatingPointLiterals,
PropertyNameCaseInsensitive = false, PropertyNameCaseInsensitive = false,
TypeInfoResolver = options.TypeInfoResolver, TypeInfoResolver = (TContext)Activator.CreateInstance(typeof(TContext))!,
}; };
typeOptions.Converters.Add(prop.JsonConverter); typeOptions.Converters.Add(prop.JsonConverter);
} }
@@ -97,29 +101,77 @@ public class ArrayConverter<T> : JsonConverter<T> where T : new()
if (reader.TokenType == JsonTokenType.Null) if (reader.TokenType == JsonTokenType.Null)
return default; return default;
var result = new T(); var result = Activator.CreateInstance(typeof(T))!;
return ParseObject(ref reader, result, options); 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 #if NET5_0_OR_GREATER
[UnconditionalSuppressMessage("AssemblyLoadTrimming", "IL3050:RequiresUnreferencedCode", Justification = "JsonSerializerOptions provided here has TypeInfoResolver set")] [UnconditionalSuppressMessage("AssemblyLoadTrimming", "IL3050:RequiresUnreferencedCode", Justification = "JsonSerializerOptions provided here has TypeInfoResolver set")]
[UnconditionalSuppressMessage("AssemblyLoadTrimming", "IL2026:RequiresUnreferencedCode", Justification = "JsonSerializerOptions provided here has TypeInfoResolver set")] [UnconditionalSuppressMessage("AssemblyLoadTrimming", "IL2026:RequiresUnreferencedCode", Justification = "JsonSerializerOptions provided here has TypeInfoResolver set")]
private static T ParseObject(ref Utf8JsonReader reader, T result, JsonSerializerOptions options) private static object ParseObject(ref Utf8JsonReader reader, object result, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] Type objectType, JsonSerializerOptions options)
#else #else
private static T ParseObject(ref Utf8JsonReader reader, T result, JsonSerializerOptions options) private static object ParseObject(ref Utf8JsonReader reader, object result, Type objectType, JsonSerializerOptions options)
#endif #endif
{ {
if (reader.TokenType != JsonTokenType.StartArray) if (reader.TokenType != JsonTokenType.StartArray)
throw new Exception("Not an array"); throw new Exception("Not an array");
if (!_typeAttributesCache.TryGetValue(objectType, out var attributes))
attributes = CacheTypeAttributes(objectType);
int index = 0; int index = 0;
while (reader.Read()) while (reader.Read())
{ {
if (reader.TokenType == JsonTokenType.EndArray) if (reader.TokenType == JsonTokenType.EndArray)
break; break;
var indexAttributes = _typePropertyInfo.Value.Where(a => a.ArrayProperty.Index == index); var indexAttributes = attributes.Where(a => a.ArrayProperty.Index == index);
if (!indexAttributes.Any()) if (!indexAttributes.Any())
{ {
index++; index++;
@@ -132,23 +184,25 @@ public class ArrayConverter<T> : JsonConverter<T> where T : new()
object? value = null; object? value = null;
if (attribute.JsonConverter != null) if (attribute.JsonConverter != null)
{ {
if (attribute.JsonSerializerOptions == null) if (!_converterOptionsCache.TryGetValue(attribute.JsonConverter, out var newOptions))
{ {
attribute.JsonSerializerOptions = new JsonSerializerOptions newOptions = new JsonSerializerOptions
{ {
NumberHandling = JsonNumberHandling.AllowReadingFromString | JsonNumberHandling.AllowNamedFloatingPointLiterals, NumberHandling = JsonNumberHandling.AllowReadingFromString | JsonNumberHandling.AllowNamedFloatingPointLiterals,
PropertyNameCaseInsensitive = false, PropertyNameCaseInsensitive = false,
Converters = { attribute.JsonConverter }, Converters = { attribute.JsonConverter },
TypeInfoResolver = options.TypeInfoResolver, TypeInfoResolver = options.TypeInfoResolver,
}; };
_converterOptionsCache.TryAdd(attribute.JsonConverter, newOptions);
} }
var doc = JsonDocument.ParseValue(ref reader); var doc = JsonDocument.ParseValue(ref reader);
value = doc.Deserialize(attribute.PropertyInfo.PropertyType, attribute.JsonSerializerOptions); value = doc.Deserialize(attribute.PropertyInfo.PropertyType, newOptions);
} }
else if (attribute.DefaultDeserialization) else if (attribute.DefaultDeserialization)
{ {
value = JsonDocument.ParseValue(ref reader).Deserialize(options.GetTypeInfo(attribute.PropertyInfo.PropertyType)); // Use default deserialization
value = JsonDocument.ParseValue(ref reader).Deserialize(attribute.PropertyInfo.PropertyType, SerializerOptions.WithConverters((TContext)Activator.CreateInstance(typeof(TContext))!));
} }
else else
{ {
@@ -176,50 +230,6 @@ public class ArrayConverter<T> : JsonConverter<T> where T : new()
return result; return result;
} }
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()
#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 private class ArrayPropertyInfo
{ {
public PropertyInfo PropertyInfo { get; set; } = null!; public PropertyInfo PropertyInfo { get; set; } = null!;
@@ -227,6 +237,6 @@ public class ArrayConverter<T> : JsonConverter<T> where T : new()
public JsonConverter? JsonConverter { get; set; } public JsonConverter? JsonConverter { get; set; }
public bool DefaultDeserialization { get; set; } public bool DefaultDeserialization { get; set; }
public Type TargetType { get; set; } = null!; public Type TargetType { get; set; } = null!;
public JsonSerializerOptions? JsonSerializerOptions { get; set; } }
} }
} }
@@ -1,15 +1,15 @@
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>
{ {
/// <summary>
/// Decimal converter that handles overflowing decimal values (by setting it to decimal.MaxValue)
/// </summary>
public class BigDecimalConverter : JsonConverter<decimal>
{
/// <inheritdoc /> /// <inheritdoc />
public override decimal Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) public override decimal Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{ {
@@ -42,4 +42,5 @@ public class BigDecimalConverter : JsonConverter<decimal>
{ {
writer.WriteNumberValue(value); writer.WriteNumberValue(value);
} }
}
} }
@@ -1,16 +1,16 @@
using System; using System;
using System.Diagnostics; using System.Diagnostics;
using System.Runtime.Serialization; using System.Runtime.Serialization;
using System.Text.Json; using System.Text.Json;
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
namespace CryptoExchange.Net.Converters.SystemTextJson; namespace CryptoExchange.Net.Converters.SystemTextJson
/// <summary>
/// Bool converter
/// </summary>
public class BoolConverter : JsonConverterFactory
{ {
/// <summary>
/// Bool converter
/// </summary>
public class BoolConverter : JsonConverterFactory
{
/// <inheritdoc /> /// <inheritdoc />
public override bool CanConvert(Type typeToConvert) public override bool CanConvert(Type typeToConvert)
{ {
@@ -28,7 +28,7 @@ public class BoolConverter : JsonConverterFactory
public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
=> (T)((object?)ReadBool(ref reader, typeToConvert, options) ?? default(T))!; => (T)((object?)ReadBool(ref reader, typeToConvert, options) ?? default(T))!;
public static bool? ReadBool(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) public bool? ReadBool(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{ {
if (reader.TokenType == JsonTokenType.True) if (reader.TokenType == JsonTokenType.True)
return true; return true;
@@ -80,4 +80,5 @@ public class BoolConverter : JsonConverterFactory
} }
} }
}
} }
@@ -1,31 +1,27 @@
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 /> /// <inheritdoc />
public override T[]? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) public override T[]? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{ {
var str = reader.GetString(); return (reader.GetString()?.Split(',').Select(x => (T)EnumConverter.ParseString<T>(x)!).ToArray() ?? []);
if (string.IsNullOrEmpty(str))
return [];
return str!.Split(',').Select(x => (T)EnumConverter.ParseString<T>(x)!).ToArray() ?? [];
} }
/// <inheritdoc /> /// <inheritdoc />
@@ -33,4 +29,5 @@ public class CommaSplitEnumConverter<T> : JsonConverter<T[]> where T : struct, E
{ {
writer.WriteStringValue(string.Join(",", value.Select(x => EnumConverter.GetString(x)))); writer.WriteStringValue(string.Join(",", value.Select(x => EnumConverter.GetString(x))));
} }
}
} }
@@ -1,17 +1,17 @@
using System; using System;
using System.Diagnostics; using System.Diagnostics;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Globalization; using System.Globalization;
using System.Text.Json; using System.Text.Json;
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
namespace CryptoExchange.Net.Converters.SystemTextJson; namespace CryptoExchange.Net.Converters.SystemTextJson
/// <summary>
/// Date time converter
/// </summary>
public class DateTimeConverter : JsonConverterFactory
{ {
/// <summary>
/// Date time converter
/// </summary>
public class DateTimeConverter : JsonConverterFactory
{
private static readonly DateTime _epoch = new(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); private static readonly DateTime _epoch = new(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
private const long _ticksPerSecond = TimeSpan.TicksPerMillisecond * 1000; private const long _ticksPerSecond = TimeSpan.TicksPerMillisecond * 1000;
private const double _ticksPerMicrosecond = TimeSpan.TicksPerMillisecond / 1000d; private const double _ticksPerMicrosecond = TimeSpan.TicksPerMillisecond / 1000d;
@@ -34,7 +34,7 @@ public class DateTimeConverter : JsonConverterFactory
public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
=> (T)((object?)ReadDateTime(ref reader, typeToConvert, options) ?? default(T))!; => (T)((object?)ReadDateTime(ref reader, typeToConvert, options) ?? default(T))!;
private static DateTime? ReadDateTime(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) private DateTime? ReadDateTime(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{ {
if (reader.TokenType == JsonTokenType.Null) if (reader.TokenType == JsonTokenType.Null)
{ {
@@ -238,4 +238,5 @@ public class DateTimeConverter : JsonConverterFactory
/// <returns></returns> /// <returns></returns>
[return: NotNullIfNotNull("time")] [return: NotNullIfNotNull("time")]
public static long? ConvertToNanoseconds(DateTime? time) => time == null ? null : (long)Math.Round((time.Value - _epoch).Ticks / _ticksPerNanosecond); public static long? ConvertToNanoseconds(DateTime? time) => time == null ? null : (long)Math.Round((time.Value - _epoch).Ticks / _ticksPerNanosecond);
}
} }
@@ -1,14 +1,15 @@
using System; using System;
using System.Globalization;
using System.Text.Json; using System.Text.Json;
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
namespace CryptoExchange.Net.Converters.SystemTextJson; namespace CryptoExchange.Net.Converters.SystemTextJson
/// <summary>
/// Decimal converter
/// </summary>
public class DecimalConverter : JsonConverter<decimal?>
{ {
/// <summary>
/// Decimal converter
/// </summary>
public class DecimalConverter : JsonConverter<decimal?>
{
/// <inheritdoc /> /// <inheritdoc />
public override decimal? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) public override decimal? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{ {
@@ -18,7 +19,22 @@ public class DecimalConverter : JsonConverter<decimal?>
if (reader.TokenType == JsonTokenType.String) if (reader.TokenType == JsonTokenType.String)
{ {
var value = reader.GetString(); var value = reader.GetString();
return ExchangeHelpers.ParseDecimal(value); 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 try
@@ -40,4 +56,5 @@ public class DecimalConverter : JsonConverter<decimal?>
else else
writer.WriteNumberValue(value.Value); writer.WriteNumberValue(value.Value);
} }
}
} }
@@ -1,15 +1,15 @@
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>
{ {
/// <summary>
/// Converter for serializing decimal values as string
/// </summary>
public class DecimalStringWriterConverter : JsonConverter<decimal>
{
/// <inheritdoc /> /// <inheritdoc />
public override decimal Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) public override decimal Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{ {
@@ -19,4 +19,5 @@ public class DecimalStringWriterConverter : JsonConverter<decimal>
/// <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,13 +9,13 @@ 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>
/// Static EnumConverter methods
/// </summary>
public static class EnumConverter
{
/// <summary> /// <summary>
/// Get the enum value from a string /// Get the enum value from a string
/// </summary> /// </summary>
@@ -52,22 +52,20 @@ public static class EnumConverter
public static string? GetString<T>(T? enumValue) where T : struct, Enum public static string? GetString<T>(T? enumValue) where T : struct, Enum
#endif #endif
=> EnumConverter<T>.GetString(enumValue); => EnumConverter<T>.GetString(enumValue);
} }
/// <summary> /// <summary>
/// Converter for enum values. Enums entries should be noted with a MapAttribute to map the enum value to a string value /// Converter for enum values. Enums entries should be noted with a MapAttribute to map the enum value to a string value
/// </summary> /// </summary>
#if NET5_0_OR_GREATER #if NET5_0_OR_GREATER
public class EnumConverter<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor | DynamicallyAccessedMemberTypes.PublicFields)] T> public class EnumConverter<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor | DynamicallyAccessedMemberTypes.PublicFields)] T>
#else #else
public class EnumConverter<T> public class EnumConverter<T>
#endif #endif
: JsonConverter<T>, INullableConverterFactory where T : struct, Enum : JsonConverter<T>, INullableConverterFactory where T : struct, Enum
{ {
private static List<KeyValuePair<T, string>>? _mapping; private static List<KeyValuePair<T, string>>? _mapping = null;
private NullableEnumConverter? _nullableEnumConverter; private NullableEnumConverter? _nullableEnumConverter = null;
private static ConcurrentBag<string> _unknownValuesWarned = new ConcurrentBag<string>();
internal class NullableEnumConverter : JsonConverter<T?> internal class NullableEnumConverter : JsonConverter<T?>
{ {
@@ -79,7 +77,7 @@ public class EnumConverter<T>
} }
public override T? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) public override T? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{ {
return EnumConverter<T>.ReadNullable(ref reader, typeToConvert, options, out var isEmptyString, out var warn); return _enumConverter.ReadNullable(ref reader, typeToConvert, options, out var isEmptyString);
} }
public override void Write(Utf8JsonWriter writer, T? value, JsonSerializerOptions options) public override void Write(Utf8JsonWriter writer, T? value, JsonSerializerOptions options)
@@ -98,10 +96,8 @@ public class EnumConverter<T>
/// <inheritdoc /> /// <inheritdoc />
public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{ {
var t = ReadNullable(ref reader, typeToConvert, options, out var isEmptyString, out var warn); var t = ReadNullable(ref reader, typeToConvert, options, out var isEmptyString);
if (t == null) if (t == null)
{
if (warn)
{ {
if (isEmptyString) if (isEmptyString)
{ {
@@ -112,8 +108,6 @@ 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 else
@@ -122,10 +116,9 @@ public class EnumConverter<T>
} }
} }
private static T? ReadNullable(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options, out bool isEmptyString, out bool warn) private T? ReadNullable(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options, out bool isEmptyString)
{ {
isEmptyString = false; isEmptyString = false;
warn = false;
var enumType = typeof(T); var enumType = typeof(T);
if (_mapping == null) if (_mapping == null)
_mapping = AddMapping(); _mapping = AddMapping();
@@ -133,7 +126,7 @@ public class EnumConverter<T>
var stringValue = reader.TokenType switch var stringValue = reader.TokenType switch
{ {
JsonTokenType.String => reader.GetString(), JsonTokenType.String => reader.GetString(),
JsonTokenType.Number => reader.GetInt32().ToString(), JsonTokenType.Number => reader.GetInt16().ToString(),
JsonTokenType.True => reader.GetBoolean().ToString(), JsonTokenType.True => reader.GetBoolean().ToString(),
JsonTokenType.False => reader.GetBoolean().ToString(), JsonTokenType.False => reader.GetBoolean().ToString(),
JsonTokenType.Null => null, JsonTokenType.Null => null,
@@ -152,13 +145,8 @@ public class EnumConverter<T>
else else
{ {
// We received an enum value but weren't able to parse it. // We received an enum value but weren't able to parse it.
if (!_unknownValuesWarned.Contains(stringValue))
{
warn = true;
_unknownValuesWarned.Add(stringValue!);
Trace.WriteLine($"{DateTime.Now:yyyy/MM/dd HH:mm:ss:fff} | Warning | Cannot map enum value. EnumType: {enumType.Name}, Value: {stringValue}, Known values: {string.Join(", ", _mapping.Select(m => m.Value))}. If you think {stringValue} should added please open an issue on the Github repo"); Trace.WriteLine($"{DateTime.Now:yyyy/MM/dd HH:mm:ss:fff} | Warning | 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 null;
} }
@@ -196,14 +184,6 @@ public class EnumConverter<T>
return true; 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 try
{ {
// If no explicit mapping is found try to parse string // If no explicit mapping is found try to parse string
@@ -285,4 +265,5 @@ public class EnumConverter<T>
_nullableEnumConverter ??= new NullableEnumConverter(this); _nullableEnumConverter ??= new NullableEnumConverter(this);
return _nullableEnumConverter; return _nullableEnumConverter;
} }
}
} }
@@ -1,14 +1,15 @@
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
{ {
/// <summary>
/// Converter for serializing enum values as int
/// </summary>
public class EnumIntWriterConverter<T> : JsonConverter<T> where T: struct, Enum
{
/// <inheritdoc /> /// <inheritdoc />
public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{ {
@@ -18,4 +19,5 @@ public class EnumIntWriterConverter<T> : JsonConverter<T> where T: struct, Enum
/// <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
{ {
internal interface INullableConverterFactory
{
JsonConverter CreateNullableConverter(); JsonConverter CreateNullableConverter();
}
} }
@@ -1,15 +1,15 @@
using System; using System;
using System.Globalization; using System.Globalization;
using System.Text.Json; using System.Text.Json;
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
namespace CryptoExchange.Net.Converters.SystemTextJson; namespace CryptoExchange.Net.Converters.SystemTextJson
/// <summary>
/// Int converter
/// </summary>
public class IntConverter : JsonConverter<int?>
{ {
/// <summary>
/// Int converter
/// </summary>
public class IntConverter : JsonConverter<int?>
{
/// <inheritdoc /> /// <inheritdoc />
public override int? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) public override int? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{ {
@@ -36,4 +36,5 @@ public class IntConverter : JsonConverter<int?>
else else
writer.WriteNumberValue(value.Value); writer.WriteNumberValue(value.Value);
} }
}
} }
@@ -1,15 +1,15 @@
using System; using System;
using System.Globalization; using System.Globalization;
using System.Text.Json; using System.Text.Json;
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
namespace CryptoExchange.Net.Converters.SystemTextJson; namespace CryptoExchange.Net.Converters.SystemTextJson
/// <summary>
/// Int converter
/// </summary>
public class LongConverter : JsonConverter<long?>
{ {
/// <summary>
/// Int converter
/// </summary>
public class LongConverter : JsonConverter<long?>
{
/// <inheritdoc /> /// <inheritdoc />
public override long? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) public override long? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{ {
@@ -36,4 +36,5 @@ public class LongConverter : JsonConverter<long?>
else else
writer.WriteNumberValue(value.Value); writer.WriteNumberValue(value.Value);
} }
}
} }
@@ -1,12 +1,14 @@
using System; using System;
using System.Collections.Generic;
using System.Text;
using System.Text.Json.Serialization.Metadata; using System.Text.Json.Serialization.Metadata;
using System.Text.Json; using System.Text.Json;
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
namespace CryptoExchange.Net.Converters.SystemTextJson; namespace CryptoExchange.Net.Converters.SystemTextJson
internal class NullableEnumConverterFactory : JsonConverterFactory
{ {
internal class NullableEnumConverterFactory : JsonConverterFactory
{
private readonly IJsonTypeInfoResolver _jsonTypeInfoResolver; private readonly IJsonTypeInfoResolver _jsonTypeInfoResolver;
private static readonly JsonSerializerOptions _options = new JsonSerializerOptions(); private static readonly JsonSerializerOptions _options = new JsonSerializerOptions();
@@ -37,4 +39,5 @@ internal class NullableEnumConverterFactory : JsonConverterFactory
return nullConverterFactory.CreateNullableConverter(); return nullConverterFactory.CreateNullableConverter();
} }
}
} }
@@ -1,14 +1,14 @@
using System; using System;
using System.Text.Json; using System.Text.Json;
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
namespace CryptoExchange.Net.Converters.SystemTextJson; namespace CryptoExchange.Net.Converters.SystemTextJson
/// <summary>
/// Read string or number as string
/// </summary>
public class NumberStringConverter : JsonConverter<string?>
{ {
/// <summary>
/// Read string or number as string
/// </summary>
public class NumberStringConverter : JsonConverter<string?>
{
/// <inheritdoc /> /// <inheritdoc />
public override string? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) public override string? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{ {
@@ -38,4 +38,5 @@ public class NumberStringConverter : JsonConverter<string?>
{ {
writer.WriteStringValue(value); writer.WriteStringValue(value);
} }
}
} }
@@ -1,17 +1,15 @@
using System; using System;
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
using System.Text.Json; using System.Text.Json;
#if NET5_0_OR_GREATER
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
#endif
namespace CryptoExchange.Net.Converters.SystemTextJson; namespace CryptoExchange.Net.Converters.SystemTextJson
/// <summary>
/// Converter for values which contain a nested json value
/// </summary>
public class ObjectStringConverter<T> : JsonConverter<T>
{ {
/// <summary>
/// Converter for values which contain a nested json value
/// </summary>
public class ObjectStringConverter<T> : JsonConverter<T>
{
/// <inheritdoc /> /// <inheritdoc />
#if NET5_0_OR_GREATER #if NET5_0_OR_GREATER
[UnconditionalSuppressMessage("AssemblyLoadTrimming", "IL3050:RequiresUnreferencedCode", Justification = "JsonSerializerOptions provided here has TypeInfoResolver set")] [UnconditionalSuppressMessage("AssemblyLoadTrimming", "IL3050:RequiresUnreferencedCode", Justification = "JsonSerializerOptions provided here has TypeInfoResolver set")]
@@ -26,7 +24,7 @@ public class ObjectStringConverter<T> : JsonConverter<T>
if (string.IsNullOrEmpty(value)) if (string.IsNullOrEmpty(value))
return default; return default;
return JsonDocument.Parse(value!).Deserialize<T>(options); return (T?)JsonDocument.Parse(value!).Deserialize(typeof(T), options);
} }
/// <inheritdoc /> /// <inheritdoc />
@@ -41,4 +39,5 @@ public class ObjectStringConverter<T> : JsonConverter<T>
writer.WriteStringValue(JsonSerializer.Serialize(value, options)); writer.WriteStringValue(JsonSerializer.Serialize(value, options));
} }
}
} }
@@ -1,15 +1,15 @@
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>
{ {
/// <summary>
/// Replace a value on a string property
/// </summary>
public abstract class ReplaceConverter : JsonConverter<string>
{
private readonly (string ValueToReplace, string ValueToReplaceWith)[] _replacementSets; private readonly (string ValueToReplace, string ValueToReplaceWith)[] _replacementSets;
/// <summary> /// <summary>
@@ -19,7 +19,7 @@ public abstract class ReplaceConverter : JsonConverter<string>
{ {
_replacementSets = replaceSets.Select(x => _replacementSets = replaceSets.Select(x =>
{ {
var split = x.Split(["->"], StringSplitOptions.None); var split = x.Split(new string[] { "->" }, StringSplitOptions.None);
if (split.Length != 2) if (split.Length != 2)
throw new ArgumentException("Invalid replacement config"); throw new ArgumentException("Invalid replacement config");
return (split[0], split[1]); return (split[0], split[1]);
@@ -37,4 +37,5 @@ public abstract class ReplaceConverter : JsonConverter<string>
/// <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,13 +1,15 @@
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>
/// 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 /// ctor
/// </summary> /// </summary>
@@ -17,4 +19,5 @@ public class SerializationModelAttribute : Attribute
/// </summary> /// </summary>
/// <param name="type"></param> /// <param name="type"></param>
public SerializationModelAttribute(Type type) { } public SerializationModelAttribute(Type type) { }
}
} }
@@ -1,20 +1,20 @@
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
{ {
/// <summary>
/// Serializer options
/// </summary>
public static class SerializerOptions
{
private static readonly ConcurrentDictionary<JsonSerializerContext, JsonSerializerOptions> _cache = new ConcurrentDictionary<JsonSerializerContext, JsonSerializerOptions>(); 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 /// Get Json serializer settings which includes standard converters for DateTime, bool, enum and number types
/// </summary> /// </summary>
public static JsonSerializerOptions WithConverters(JsonSerializerContext typeResolver, params JsonConverter[] additionalConverters) public static JsonSerializerOptions WithConverters(JsonSerializerContext typeResolver)
{ {
if (!_cache.TryGetValue(typeResolver, out var options)) if (!_cache.TryGetValue(typeResolver, out var options))
{ {
@@ -33,14 +33,10 @@ public static class SerializerOptions
}, },
TypeInfoResolver = typeResolver, TypeInfoResolver = typeResolver,
}; };
foreach (var converter in additionalConverters)
options.Converters.Add(converter);
options.TypeInfoResolver = typeResolver;
_cache.TryAdd(typeResolver, options); _cache.TryAdd(typeResolver, options);
} }
return 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,22 +1,21 @@
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>
/// System.Text.Json message accessor
/// </summary>
public abstract class SystemTextJsonMessageAccessor : IMessageAccessor
{
/// <summary> /// <summary>
/// The JsonDocument loaded /// The JsonDocument loaded
/// </summary> /// </summary>
@@ -25,7 +24,7 @@ public abstract class SystemTextJsonMessageAccessor : IMessageAccessor
private readonly JsonSerializerOptions? _customSerializerOptions; private readonly JsonSerializerOptions? _customSerializerOptions;
/// <inheritdoc /> /// <inheritdoc />
public bool IsValid { get; set; } public bool IsJson { get; set; }
/// <inheritdoc /> /// <inheritdoc />
public abstract bool OriginalDataAvailable { get; } public abstract bool OriginalDataAvailable { get; }
@@ -48,7 +47,7 @@ public abstract class SystemTextJsonMessageAccessor : IMessageAccessor
#endif #endif
public CallResult<object> Deserialize(Type type, MessagePath? path = null) public CallResult<object> Deserialize(Type type, MessagePath? path = null)
{ {
if (!IsValid) if (!IsJson)
return new CallResult<object>(GetOriginalString()); return new CallResult<object>(GetOriginalString());
if (_document == null) if (_document == null)
@@ -61,12 +60,13 @@ public abstract class SystemTextJsonMessageAccessor : IMessageAccessor
} }
catch (JsonException ex) catch (JsonException ex)
{ {
var info = $"Json deserialization failed: {ex.Message}, Path: {ex.Path}, LineNumber: {ex.LineNumber}, LinePosition: {ex.BytePositionInLine}"; var info = $"Deserialize JsonException: {ex.Message}, Path: {ex.Path}, LineNumber: {ex.LineNumber}, LinePosition: {ex.BytePositionInLine}";
return new CallResult<object>(new DeserializeError(info, ex)); return new CallResult<object>(new DeserializeError(info, OriginalDataAvailable ? GetOriginalString() : "[Data only available when OutputOriginal = true in client options]"));
} }
catch (Exception ex) catch (Exception ex)
{ {
return new CallResult<object>(new DeserializeError($"Json deserialization failed: {ex.Message}", 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]"));
} }
} }
@@ -87,19 +87,20 @@ public abstract class SystemTextJsonMessageAccessor : IMessageAccessor
} }
catch (JsonException ex) catch (JsonException ex)
{ {
var info = $"Json deserialization failed: {ex.Message}, Path: {ex.Path}, LineNumber: {ex.LineNumber}, LinePosition: {ex.BytePositionInLine}"; var info = $"Deserialize JsonException: {ex.Message}, Path: {ex.Path}, LineNumber: {ex.LineNumber}, LinePosition: {ex.BytePositionInLine}";
return new CallResult<T>(new DeserializeError(info, ex)); return new CallResult<T>(new DeserializeError(info, OriginalDataAvailable ? GetOriginalString() : "[Data only available when OutputOriginal = true in client options]"));
} }
catch (Exception ex) catch (Exception ex)
{ {
return new CallResult<T>(new DeserializeError($"Json deserialization failed: {ex.Message}", 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]"));
} }
} }
/// <inheritdoc /> /// <inheritdoc />
public NodeType? GetNodeType() public NodeType? GetNodeType()
{ {
if (!IsValid) if (!IsJson)
throw new InvalidOperationException("Can't access json data on non-json message"); throw new InvalidOperationException("Can't access json data on non-json message");
if (_document == null) if (_document == null)
@@ -116,7 +117,7 @@ public abstract class SystemTextJsonMessageAccessor : IMessageAccessor
/// <inheritdoc /> /// <inheritdoc />
public NodeType? GetNodeType(MessagePath path) public NodeType? GetNodeType(MessagePath path)
{ {
if (!IsValid) if (!IsJson)
throw new InvalidOperationException("Can't access json data on non-json message"); throw new InvalidOperationException("Can't access json data on non-json message");
var node = GetPathNode(path); var node = GetPathNode(path);
@@ -138,7 +139,7 @@ public abstract class SystemTextJsonMessageAccessor : IMessageAccessor
#endif #endif
public T? GetValue<T>(MessagePath path) public T? GetValue<T>(MessagePath path)
{ {
if (!IsValid) if (!IsJson)
throw new InvalidOperationException("Can't access json data on non-json message"); throw new InvalidOperationException("Can't access json data on non-json message");
var value = GetPathNode(path); var value = GetPathNode(path);
@@ -170,9 +171,9 @@ public abstract class SystemTextJsonMessageAccessor : IMessageAccessor
[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 List<T?>? GetValues<T>(MessagePath path)
{ {
if (!IsValid) if (!IsJson)
throw new InvalidOperationException("Can't access json data on non-json message"); throw new InvalidOperationException("Can't access json data on non-json message");
var value = GetPathNode(path); var value = GetPathNode(path);
@@ -182,12 +183,12 @@ public abstract class SystemTextJsonMessageAccessor : IMessageAccessor
if (value.Value.ValueKind != JsonValueKind.Array) if (value.Value.ValueKind != JsonValueKind.Array)
return default; return default;
return value.Value.Deserialize<T[]>(_customSerializerOptions)!; return value.Value.Deserialize<List<T>>(_customSerializerOptions)!;
} }
private JsonElement? GetPathNode(MessagePath path) private JsonElement? GetPathNode(MessagePath path)
{ {
if (!IsValid) if (!IsJson)
throw new InvalidOperationException("Can't access json data on non-json message"); throw new InvalidOperationException("Can't access json data on non-json message");
if (_document == null) if (_document == null)
@@ -236,15 +237,13 @@ public abstract class SystemTextJsonMessageAccessor : IMessageAccessor
/// <inheritdoc /> /// <inheritdoc />
public abstract void Clear(); public abstract void Clear();
} }
/// <summary> /// <summary>
/// System.Text.Json stream message accessor /// System.Text.Json stream message accessor
/// </summary> /// </summary>
#pragma warning disable CA1001 // Types that own disposable fields should be disposable public class SystemTextJsonStreamMessageAccessor : SystemTextJsonMessageAccessor, IStreamMessageAccessor
public class SystemTextJsonStreamMessageAccessor : SystemTextJsonMessageAccessor, IStreamMessageAccessor {
#pragma warning restore CA1001 // Types that own disposable fields should be disposable
{
private Stream? _stream; private Stream? _stream;
/// <inheritdoc /> /// <inheritdoc />
@@ -280,14 +279,14 @@ public class SystemTextJsonStreamMessageAccessor : SystemTextJsonMessageAccessor
try try
{ {
_document = await JsonDocument.ParseAsync(_stream ?? stream).ConfigureAwait(false); _document = await JsonDocument.ParseAsync(_stream ?? stream).ConfigureAwait(false);
IsValid = true; IsJson = true;
return CallResult.SuccessResult; return CallResult.SuccessResult;
} }
catch (Exception ex) catch (Exception ex)
{ {
// Not a json message // Not a json message
IsValid = false; IsJson = false;
return new CallResult(new DeserializeError($"Json deserialization failed: {ex.Message}", ex)); return new CallResult(new ServerError("JsonError: " + ex.Message));
} }
} }
@@ -311,13 +310,13 @@ public class SystemTextJsonStreamMessageAccessor : SystemTextJsonMessageAccessor
_document = null; _document = null;
} }
} }
/// <summary> /// <summary>
/// System.Text.Json byte message accessor /// System.Text.Json byte message accessor
/// </summary> /// </summary>
public class SystemTextJsonByteMessageAccessor : SystemTextJsonMessageAccessor, IByteMessageAccessor public class SystemTextJsonByteMessageAccessor : SystemTextJsonMessageAccessor, IByteMessageAccessor
{ {
private ReadOnlyMemory<byte> _bytes; private ReadOnlyMemory<byte> _bytes;
/// <summary> /// <summary>
@@ -338,19 +337,19 @@ public class SystemTextJsonByteMessageAccessor : SystemTextJsonMessageAccessor,
if (firstByte != 0x7b && firstByte != 0x5b) if (firstByte != 0x7b && firstByte != 0x5b)
{ {
// Value doesn't start with `{` or `[`, prevent deserialization attempt as it's slow // Value doesn't start with `{` or `[`, prevent deserialization attempt as it's slow
IsValid = false; IsJson = false;
return new CallResult(new DeserializeError("Not a json value")); return new CallResult(new ServerError("Not a json value"));
} }
_document = JsonDocument.Parse(data); _document = JsonDocument.Parse(data);
IsValid = true; IsJson = true;
return CallResult.SuccessResult; return CallResult.SuccessResult;
} }
catch (Exception ex) catch (Exception ex)
{ {
// Not a json message // Not a json message
IsValid = false; IsJson = false;
return new CallResult(new DeserializeError($"Json deserialization failed: {ex.Message}", ex)); return new CallResult(new ServerError("JsonError: " + ex.Message));
} }
} }
@@ -373,4 +372,5 @@ public class SystemTextJsonByteMessageAccessor : SystemTextJsonMessageAccessor,
_document?.Dispose(); _document?.Dispose();
_document = null; _document = null;
} }
}
} }
@@ -1,14 +1,14 @@
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
{ {
/// <inheritdoc />
public class SystemTextJsonMessageSerializer : IMessageSerializer
{
private readonly JsonSerializerOptions _options; private readonly JsonSerializerOptions _options;
/// <summary> /// <summary>
@@ -25,4 +25,5 @@ public class SystemTextJsonMessageSerializer : IStringMessageSerializer
[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);
}
} }
+17 -22
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,11 +24,10 @@
<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'">
@@ -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>
+12 -85
View File
@@ -1,22 +1,19 @@
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
{ {
/// <summary>
/// General helpers functions
/// </summary>
public static class ExchangeHelpers
{
private const string _allowedRandomChars = "ABCDEFGHIJKLMONOPQRSTUVWXYZabcdefghijklmonopqrstuvwxyz0123456789"; private const string _allowedRandomChars = "ABCDEFGHIJKLMONOPQRSTUVWXYZabcdefghijklmonopqrstuvwxyz0123456789";
private const string _allowedRandomHexChars = "0123456789ABCDEF"; private const string _allowedRandomHexChars = "0123456789ABCDEF";
@@ -115,34 +112,6 @@ public static class ExchangeHelpers
return RoundToSignificantDigits(value, precision.Value, roundingType); return RoundToSignificantDigits(value, precision.Value, roundingType);
} }
/// <summary>
/// Apply the provided rules to the value
/// </summary>
/// <param name="value">Value to be adjusted</param>
/// <param name="decimals">Max decimal places</param>
/// <param name="valueStep">The value step for increase/decrease value</param>
/// <returns></returns>
public static decimal ApplyRules(
decimal value,
int? decimals = null,
decimal? valueStep = null)
{
if (valueStep.HasValue)
{
var offset = value % valueStep.Value;
if (offset != 0)
{
if (offset < valueStep.Value / 2)
value -= offset;
else value += (valueStep.Value - offset);
}
}
if (decimals.HasValue)
value = Math.Round(value, decimals.Value);
return value;
}
/// <summary> /// <summary>
/// Round a value to have the provided total number of digits. For example, value 253.12332 with 5 digits would be 253.12 /// Round a value to have the provided total number of digits. For example, value 253.12332 with 5 digits would be 253.12
/// </summary> /// </summary>
@@ -289,16 +258,16 @@ public static class ExchangeHelpers
/// <summary> /// <summary>
/// Execute multiple requests to retrieve multiple pages of the result set /// Execute multiple requests to retrieve multiple pages of the result set
/// </summary> /// </summary>
/// <typeparam name="TResult">Type of the client</typeparam> /// <typeparam name="T">Type of the client</typeparam>
/// <typeparam name="TRequest">Type of the request</typeparam> /// <typeparam name="U">Type of the request</typeparam>
/// <param name="paginatedFunc">The func to execute with each request</param> /// <param name="paginatedFunc">The func to execute with each request</param>
/// <param name="request">The request parameters</param> /// <param name="request">The request parameters</param>
/// <param name="ct">Cancellation token</param> /// <param name="ct">Cancellation token</param>
/// <returns></returns> /// <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) 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<TResult>(); var result = new List<T>();
ExchangeWebResult<TResult[]> batch; ExchangeWebResult<T[]> batch;
INextPageToken? nextPageToken = null; INextPageToken? nextPageToken = null;
while (true) while (true)
{ {
@@ -344,47 +313,5 @@ public static class ExchangeHelpers
adjustedQuantity = symbol.QuantityDecimals.HasValue ? (minNotionalAdjust ? RoundUp(adjustedQuantity, symbol.QuantityDecimals.Value) : RoundDown(adjustedQuantity, symbol.QuantityDecimals.Value)) : adjustedQuantity; adjustedQuantity = symbol.QuantityDecimals.HasValue ? (minNotionalAdjust ? RoundUp(adjustedQuantity, symbol.QuantityDecimals.Value) : RoundDown(adjustedQuantity, symbol.QuantityDecimals.Value)) : adjustedQuantity;
} }
/// <summary>
/// Parse a decimal value from a string
/// </summary>
public static decimal? ParseDecimal(string? value)
{
// Value is null or empty is the most common case to return null so check before trying to parse
if (string.IsNullOrEmpty(value))
return null;
// Try parse, only fails for these reasons:
// 1. string is null or empty
// 2. value is larger or smaller than decimal max/min
// 3. unparsable format
if (decimal.TryParse(value, NumberStyles.Float, CultureInfo.InvariantCulture, out var decValue))
return decValue;
// Check for values which should be parsed to null
if (string.Equals("null", value, StringComparison.OrdinalIgnoreCase)
|| string.Equals("NaN", value, StringComparison.OrdinalIgnoreCase))
{
return null;
}
// Infinity value should be parsed to min/max value
if (string.Equals("Infinity", value, StringComparison.OrdinalIgnoreCase))
return decimal.MaxValue;
else if(string.Equals("-Infinity", value, StringComparison.OrdinalIgnoreCase))
return decimal.MinValue;
if (value!.Length > 27 && decimal.TryParse(value.Substring(0, 27), out var overflowValue))
{
// Not a valid decimal value and more than 27 chars, from which the first part can be parsed correctly.
// assume overflow
if (overflowValue < 0)
return decimal.MinValue;
else
return decimal.MaxValue;
}
// Unknown decimal format, return null
return null;
} }
} }
+11 -9
View File
@@ -1,16 +1,17 @@
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
{ {
/// <summary>
/// Cache for symbol parsing
/// </summary>
public static class ExchangeSymbolCache
{
private static ConcurrentDictionary<string, ExchangeInfo> _symbolInfos = new ConcurrentDictionary<string, ExchangeInfo>(); private static ConcurrentDictionary<string, ExchangeInfo> _symbolInfos = new ConcurrentDictionary<string, ExchangeInfo>();
/// <summary> /// <summary>
@@ -22,14 +23,14 @@ public static class ExchangeSymbolCache
{ {
if(!_symbolInfos.TryGetValue(topicId, out var exchangeInfo)) if(!_symbolInfos.TryGetValue(topicId, out var exchangeInfo))
{ {
exchangeInfo = new ExchangeInfo(DateTime.UtcNow, updateData.ToDictionary(x => x.Name, x => x.SharedSymbol)); 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); _symbolInfos.TryAdd(topicId, exchangeInfo);
} }
if (DateTime.UtcNow - exchangeInfo.UpdateTime < TimeSpan.FromMinutes(60)) if (DateTime.UtcNow - exchangeInfo.UpdateTime < TimeSpan.FromMinutes(60))
return; return;
_symbolInfos[topicId] = new ExchangeInfo(DateTime.UtcNow, updateData.ToDictionary(x => x.Name, x => x.SharedSymbol)); _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 }));
} }
/// <summary> /// <summary>
@@ -65,4 +66,5 @@ public static class ExchangeSymbolCache
Symbols = symbols; Symbols = symbols;
} }
} }
}
} }
+13 -21
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,14 +10,17 @@ 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>
/// Helper methods
/// </summary>
public static class ExtensionMethods
{
/// <summary> /// <summary>
/// Add a parameter /// Add a parameter
/// </summary> /// </summary>
@@ -440,8 +443,6 @@ public static class ExtensionMethods
services.AddTransient(x => (IWithdrawRestClient)client(x)!); services.AddTransient(x => (IWithdrawRestClient)client(x)!);
if (typeof(IFeeRestClient).IsAssignableFrom(typeof(T))) if (typeof(IFeeRestClient).IsAssignableFrom(typeof(T)))
services.AddTransient(x => (IFeeRestClient)client(x)!); services.AddTransient(x => (IFeeRestClient)client(x)!);
if (typeof(IBookTickerRestClient).IsAssignableFrom(typeof(T)))
services.AddTransient(x => (IBookTickerRestClient)client(x)!);
if (typeof(ISpotOrderRestClient).IsAssignableFrom(typeof(T))) if (typeof(ISpotOrderRestClient).IsAssignableFrom(typeof(T)))
services.AddTransient(x => (ISpotOrderRestClient)client(x)!); services.AddTransient(x => (ISpotOrderRestClient)client(x)!);
@@ -449,10 +450,6 @@ public static class ExtensionMethods
services.AddTransient(x => (ISpotSymbolRestClient)client(x)!); services.AddTransient(x => (ISpotSymbolRestClient)client(x)!);
if (typeof(ISpotTickerRestClient).IsAssignableFrom(typeof(T))) if (typeof(ISpotTickerRestClient).IsAssignableFrom(typeof(T)))
services.AddTransient(x => (ISpotTickerRestClient)client(x)!); 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))) if (typeof(IFundingRateRestClient).IsAssignableFrom(typeof(T)))
services.AddTransient(x => (IFundingRateRestClient)client(x)!); services.AddTransient(x => (IFundingRateRestClient)client(x)!);
@@ -474,12 +471,6 @@ public static class ExtensionMethods
services.AddTransient(x => (IPositionHistoryRestClient)client(x)!); services.AddTransient(x => (IPositionHistoryRestClient)client(x)!);
if (typeof(IPositionModeRestClient).IsAssignableFrom(typeof(T))) if (typeof(IPositionModeRestClient).IsAssignableFrom(typeof(T)))
services.AddTransient(x => (IPositionModeRestClient)client(x)!); services.AddTransient(x => (IPositionModeRestClient)client(x)!);
if (typeof(IFuturesTpSlRestClient).IsAssignableFrom(typeof(T)))
services.AddTransient(x => (IFuturesTpSlRestClient)client(x)!);
if (typeof(IFuturesTriggerOrderRestClient).IsAssignableFrom(typeof(T)))
services.AddTransient(x => (IFuturesTriggerOrderRestClient)client(x)!);
if (typeof(IFuturesOrderClientIdRestClient).IsAssignableFrom(typeof(T)))
services.AddTransient(x => (IFuturesOrderClientIdRestClient)client(x)!);
return services; return services;
} }
@@ -495,8 +486,8 @@ public static class ExtensionMethods
services.AddTransient(x => (IBookTickerSocketClient)client(x)!); services.AddTransient(x => (IBookTickerSocketClient)client(x)!);
if (typeof(IKlineSocketClient).IsAssignableFrom(typeof(T))) if (typeof(IKlineSocketClient).IsAssignableFrom(typeof(T)))
services.AddTransient(x => (IKlineSocketClient)client(x)!); services.AddTransient(x => (IKlineSocketClient)client(x)!);
if (typeof(IOrderBookSocketClient).IsAssignableFrom(typeof(T))) if (typeof(IOrderBookRestClient).IsAssignableFrom(typeof(T)))
services.AddTransient(x => (IOrderBookSocketClient)client(x)!); services.AddTransient(x => (IOrderBookRestClient)client(x)!);
if (typeof(ITickerSocketClient).IsAssignableFrom(typeof(T))) if (typeof(ITickerSocketClient).IsAssignableFrom(typeof(T)))
services.AddTransient(x => (ITickerSocketClient)client(x)!); services.AddTransient(x => (ITickerSocketClient)client(x)!);
if (typeof(ITickersSocketClient).IsAssignableFrom(typeof(T))) if (typeof(ITickersSocketClient).IsAssignableFrom(typeof(T)))
@@ -516,5 +507,6 @@ public static class ExtensionMethods
return services; 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>
/// Time provider
/// </summary>
internal interface IAuthTimeProvider
{
/// <summary> /// <summary>
/// Get current time /// Get current time
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
DateTime GetTime(); DateTime GetTime();
}
} }
@@ -1,15 +1,16 @@
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>
/// Base api client
/// </summary>
public interface IBaseApiClient
{
/// <summary> /// <summary>
/// Base address /// Base address
/// </summary> /// </summary>
@@ -43,4 +44,5 @@ public interface IBaseApiClient
/// <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>
/// Client for accessing REST API's for different exchanges
/// </summary>
public interface ICryptoRestClient
{
/// <summary> /// <summary>
/// Try get /// Try get
/// </summary> /// </summary>
/// <typeparam name="T"></typeparam> /// <typeparam name="T"></typeparam>
/// <returns></returns> /// <returns></returns>
T TryGet<T>(Func<T> createFunc); 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>
/// 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 /// Try get a client by type for the service collection
/// </summary> /// </summary>
/// <typeparam name="T"></typeparam> /// <typeparam name="T"></typeparam>
/// <returns></returns> /// <returns></returns>
T TryGet<T>(Func<T> createFunc); T TryGet<T>(Func<T> createFunc);
}
} }
@@ -1,23 +1,21 @@
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 this a json message
/// </summary>
bool IsJson { get; }
/// <summary> /// <summary>
/// Is the original data available for retrieval /// Is the original data available for retrieval
/// </summary> /// </summary>
@@ -54,27 +52,19 @@ public interface IMessageAccessor
/// <typeparam name="T"></typeparam> /// <typeparam name="T"></typeparam>
/// <param name="path"></param> /// <param name="path"></param>
/// <returns></returns> /// <returns></returns>
T?[]? GetValues<T>(MessagePath path); List<T?>? GetValues<T>(MessagePath path);
/// <summary> /// <summary>
/// Deserialize the message into this type /// Deserialize the message into this type
/// </summary> /// </summary>
/// <param name="type"></param> /// <param name="type"></param>
/// <param name="path"></param> /// <param name="path"></param>
/// <returns></returns> /// <returns></returns>
#if NET5_0_OR_GREATER
[UnconditionalSuppressMessage("AssemblyLoadTrimming", "IL2092:RequiresUnreferencedCode", Justification = "JsonSerializerOptions provided here has TypeInfoResolver set")]
[UnconditionalSuppressMessage("AssemblyLoadTrimming", "IL2095:RequiresUnreferencedCode", Justification = "JsonSerializerOptions provided here has TypeInfoResolver set")]
#endif
CallResult<object> Deserialize(Type type, MessagePath? path = null); CallResult<object> Deserialize(Type type, MessagePath? path = null);
/// <summary> /// <summary>
/// Deserialize the message into this type /// Deserialize the message into this type
/// </summary> /// </summary>
/// <param name="path"></param> /// <param name="path"></param>
/// <returns></returns> /// <returns></returns>
#if NET5_0_OR_GREATER
[UnconditionalSuppressMessage("AssemblyLoadTrimming", "IL2092:RequiresUnreferencedCode", Justification = "JsonSerializerOptions provided here has TypeInfoResolver set")]
[UnconditionalSuppressMessage("AssemblyLoadTrimming", "IL2095:RequiresUnreferencedCode", Justification = "JsonSerializerOptions provided here has TypeInfoResolver set")]
#endif
CallResult<T> Deserialize<T>(MessagePath? path = null); CallResult<T> Deserialize<T>(MessagePath? path = null);
/// <summary> /// <summary>
@@ -82,29 +72,30 @@ public interface IMessageAccessor
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
string GetOriginalString(); string GetOriginalString();
} }
/// <summary> /// <summary>
/// Stream message accessor /// Stream message accessor
/// </summary> /// </summary>
public interface IStreamMessageAccessor : IMessageAccessor public interface IStreamMessageAccessor : IMessageAccessor
{ {
/// <summary> /// <summary>
/// Load a stream message /// Load a stream message
/// </summary> /// </summary>
/// <param name="stream"></param> /// <param name="stream"></param>
/// <param name="bufferStream"></param> /// <param name="bufferStream"></param>
Task<CallResult> Read(Stream stream, bool bufferStream); Task<CallResult> Read(Stream stream, bool bufferStream);
} }
/// <summary> /// <summary>
/// Byte message accessor /// Byte message accessor
/// </summary> /// </summary>
public interface IByteMessageAccessor : IMessageAccessor public interface IByteMessageAccessor : IMessageAccessor
{ {
/// <summary> /// <summary>
/// Load a data message /// Load a data message
/// </summary> /// </summary>
/// <param name="data"></param> /// <param name="data"></param>
CallResult Read(ReadOnlyMemory<byte> data); CallResult Read(ReadOnlyMemory<byte> data);
}
} }
@@ -1,28 +1,38 @@
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>
/// Message processor
/// </summary>
public interface IMessageProcessor
{
/// <summary> /// <summary>
/// Id of the processor /// Id of the processor
/// </summary> /// </summary>
public int Id { get; } public int Id { get; }
/// <summary> /// <summary>
/// The matcher for this listener /// The identifiers for this processor
/// </summary> /// </summary>
public MessageMatcher MessageMatcher { get; } public HashSet<string> ListenerIdentifiers { get; }
/// <summary> /// <summary>
/// Handle a message /// Handle a message
/// </summary> /// </summary>
Task<CallResult> Handle(SocketConnection connection, DataEvent<object> message, MessageHandlerLink matchedHandler); /// <param name="connection"></param>
/// <param name="message"></param>
/// <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> /// <summary>
/// Deserialize a message into object of type /// Deserialize a message into object of type
/// </summary> /// </summary>
@@ -30,4 +40,5 @@ public interface IMessageProcessor
/// <param name="type"></param> /// <param name="type"></param>
/// <returns></returns> /// <returns></returns>
CallResult<object> Deserialize(IMessageAccessor accessor, Type type); 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 to string
/// </summary>
public interface IStringMessageSerializer: IMessageSerializer
{
/// <summary> /// <summary>
/// Serialize an object to a string /// Serialize an object to a string
/// </summary> /// </summary>
/// <param name="message"></param> /// <param name="message"></param>
/// <returns></returns> /// <returns></returns>
string Serialize<T>(T message); 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>
/// 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 /// Get nonce value. Nonce value should be unique and incremental for each call
/// </summary> /// </summary>
/// <returns>Nonce value</returns> /// <returns>Nonce value</returns>
long GetNonce(); long GetNonce();
}
} }
@@ -1,14 +1,14 @@
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>
/// Factory for ISymbolOrderBook instances
/// </summary>
public interface IOrderBookFactory<TOptions> where TOptions : OrderBookOptions
{
/// <summary> /// <summary>
/// Create a new order book by symbol name /// Create a new order book by symbol name
/// </summary> /// </summary>
@@ -31,4 +31,5 @@ public interface IOrderBookFactory<TOptions> where TOptions : OrderBookOptions
/// <param name="options">Options for the order book</param> /// <param name="options">Options for the order book</param>
/// <returns></returns> /// <returns></returns>
public ISymbolOrderBook Create(SharedSymbol symbol, Action<TOptions>? options = null); public ISymbolOrderBook Create(SharedSymbol symbol, Action<TOptions>? options = null);
}
} }
@@ -4,13 +4,13 @@ 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>
/// Rate limiter interface
/// </summary>
public interface IRateLimiter
{
/// <summary> /// <summary>
/// Limit a request based on previous requests made /// Limit a request based on previous requests made
/// </summary> /// </summary>
@@ -24,4 +24,5 @@ public interface IRateLimiter
/// <param name="ct">Cancellation token to cancel waiting</param> /// <param name="ct">Cancellation token to cancel waiting</param>
/// <returns>The time in milliseconds spend waiting</returns> /// <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); Task<CallResult<int>> LimitRequestAsync(ILogger log, string endpoint, HttpMethod method, bool signed, string? apiKey, RateLimitingBehaviour limitBehaviour, int requestWeight, CancellationToken ct);
}
} }
+8 -7
View File
@@ -1,16 +1,16 @@
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>
/// Request interface
/// </summary>
public interface IRequest
{
/// <summary> /// <summary>
/// Accept header /// Accept header
/// </summary> /// </summary>
@@ -62,4 +62,5 @@ public interface IRequest
/// <param name="cancellationToken"></param> /// <param name="cancellationToken"></param>
/// <returns></returns> /// <returns></returns>
Task<IResponse> GetResponseAsync(CancellationToken cancellationToken); Task<IResponse> GetResponseAsync(CancellationToken cancellationToken);
}
} }
@@ -1,14 +1,14 @@
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>
/// Request factory interface
/// </summary>
public interface IRequestFactory
{
/// <summary> /// <summary>
/// Create a request for an uri /// Create a request for an uri
/// </summary> /// </summary>
@@ -32,4 +32,5 @@ public interface IRequestFactory
/// <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);
}
} }
+8 -7
View File
@@ -1,15 +1,15 @@
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>
/// Response object interface
/// </summary>
public interface IResponse
{
/// <summary> /// <summary>
/// The response status code /// The response status code
/// </summary> /// </summary>
@@ -40,4 +40,5 @@ public interface IResponse
/// Close the response /// Close the response
/// </summary> /// </summary>
void Close(); void Close();
}
} }
@@ -1,10 +1,10 @@
namespace CryptoExchange.Net.Interfaces; namespace CryptoExchange.Net.Interfaces
/// <summary>
/// Base rest API client
/// </summary>
public interface IRestApiClient : IBaseApiClient
{ {
/// <summary>
/// Base rest API client
/// </summary>
public interface IRestApiClient : IBaseApiClient
{
/// <summary> /// <summary>
/// The factory for creating requests. Used for unit testing /// The factory for creating requests. Used for unit testing
/// </summary> /// </summary>
@@ -14,4 +14,5 @@ public interface IRestApiClient : IBaseApiClient
/// 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; }
}
} }
+8 -7
View File
@@ -1,13 +1,13 @@
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>
/// Base class for rest API implementations
/// </summary>
public interface IRestClient: IDisposable
{
/// <summary> /// <summary>
/// The options provided for this client /// The options provided for this client
/// </summary> /// </summary>
@@ -22,4 +22,5 @@ public interface IRestClient: IDisposable
/// The exchange name /// The exchange name
/// </summary> /// </summary>
string Exchange { get; } string Exchange { get; }
}
} }
@@ -1,15 +1,15 @@
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>
/// Socket API client
/// </summary>
public interface ISocketApiClient: IBaseApiClient
{
/// <summary> /// <summary>
/// The current amount of socket connections on the API client /// The current amount of socket connections on the API client
/// </summary> /// </summary>
@@ -66,4 +66,5 @@ public interface ISocketApiClient: IBaseApiClient
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
Task<CallResult> PrepareConnectionsAsync(); Task<CallResult> PrepareConnectionsAsync();
}
} }
@@ -1,15 +1,15 @@
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>
/// Base class for socket API implementations
/// </summary>
public interface ISocketClient: IDisposable
{
/// <summary> /// <summary>
/// The exchange name /// The exchange name
/// </summary> /// </summary>
@@ -54,4 +54,5 @@ public interface ISocketClient: IDisposable
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
Task UnsubscribeAllAsync(); Task UnsubscribeAllAsync();
}
} }
@@ -1,15 +1,16 @@
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>
/// Interface for order book
/// </summary>
public interface ISymbolOrderBook
{
/// <summary> /// <summary>
/// The exchange the book is for /// The exchange the book is for
/// </summary> /// </summary>
@@ -126,4 +127,5 @@ public interface ISymbolOrderBook
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
string ToString(int rows); string ToString(int rows);
}
} }
@@ -1,10 +1,10 @@
namespace CryptoExchange.Net.Interfaces; namespace CryptoExchange.Net.Interfaces
/// <summary>
/// Interface for order book entries
/// </summary>
public interface ISymbolOrderBookEntry
{ {
/// <summary>
/// Interface for order book entries
/// </summary>
public interface ISymbolOrderBookEntry
{
/// <summary> /// <summary>
/// The quantity of the entry /// The quantity of the entry
/// </summary> /// </summary>
@@ -13,15 +13,16 @@ public interface ISymbolOrderBookEntry
/// The price of the entry /// The price of the entry
/// </summary> /// </summary>
decimal Price { get; set; } decimal Price { get; set; }
} }
/// <summary> /// <summary>
/// Interface for order book entries /// Interface for order book entries
/// </summary> /// </summary>
public interface ISymbolOrderSequencedBookEntry: ISymbolOrderBookEntry public interface ISymbolOrderSequencedBookEntry: ISymbolOrderBookEntry
{ {
/// <summary> /// <summary>
/// Sequence of the update /// Sequence of the update
/// </summary> /// </summary>
long Sequence { get; set; } long Sequence { get; set; }
}
} }
+10 -17
View File
@@ -1,16 +1,15 @@
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>
/// Websocket connection interface
/// </summary>
public interface IWebsocket: IDisposable
{
/// <summary> /// <summary>
/// Websocket closed event /// Websocket closed event
/// </summary> /// </summary>
@@ -76,22 +75,15 @@ public interface IWebsocket: IDisposable
/// 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
/// </summary>
/// <param name="id"></param>
/// <param name="data"></param>
/// <param name="weight"></param>
bool Send(int id, byte[] data, int weight);
/// <summary>
/// Reconnect the socket /// Reconnect the socket
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
@@ -106,4 +98,5 @@ public interface IWebsocket: IDisposable
/// Update proxy setting /// Update proxy setting
/// </summary> /// </summary>
void UpdateProxy(ApiProxy? proxy); void UpdateProxy(ApiProxy? proxy);
}
} }
@@ -1,13 +1,13 @@
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>
/// Websocket factory interface
/// </summary>
public interface IWebsocketFactory
{
/// <summary> /// <summary>
/// Create a websocket for an url /// Create a websocket for an url
/// </summary> /// </summary>
@@ -15,4 +15,5 @@ public interface IWebsocketFactory
/// <param name="parameters">The parameters to use for the connection</param> /// <param name="parameters">The parameters to use for the connection</param>
/// <returns></returns> /// <returns></returns>
IWebsocket CreateWebsocket(ILogger logger, WebSocketParameters parameters); IWebsocket CreateWebsocket(ILogger logger, WebSocketParameters parameters);
}
} }
+10 -5
View File
@@ -1,10 +1,14 @@
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>
/// Helpers for client libraries
/// </summary>
public static class LibraryHelpers
{
/// <summary> /// <summary>
/// Client order id separator /// Client order id separator
/// </summary> /// </summary>
@@ -39,4 +43,5 @@ public static class LibraryHelpers
return clientOrderId; return clientOrderId;
} }
}
} }
@@ -1,14 +1,13 @@
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
{ {
#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; private static readonly Action<ILogger, int, Exception?> _connecting;
private static readonly Action<ILogger, int, string, Exception?> _connectionFailed; private static readonly Action<ILogger, int, string, Exception?> _connectionFailed;
private static readonly Action<ILogger, int, Exception?> _connectingCanceled;
private static readonly Action<ILogger, int, Uri, Exception?> _connected; private static readonly Action<ILogger, int, Uri, Exception?> _connected;
private static readonly Action<ILogger, int, Exception?> _startingProcessing; private static readonly Action<ILogger, int, Exception?> _startingProcessing;
private static readonly Action<ILogger, int, Exception?> _finishedProcessing; private static readonly Action<ILogger, int, Exception?> _finishedProcessing;
@@ -190,12 +189,6 @@ public static class CryptoExchangeWebSocketClientLoggingExtension
new EventId(1030, "SocketPingTimeout"), new EventId(1030, "SocketPingTimeout"),
"[Sckt {Id}] ping frame timeout; reconnecting socket"); "[Sckt {Id}] ping frame timeout; reconnecting socket");
_connectingCanceled = LoggerMessage.Define<int>(
LogLevel.Debug,
new EventId(1031, "ConnectingCanceled"),
"[Sckt {SocketId}] connecting canceled");
} }
public static void SocketConnecting( public static void SocketConnecting(
@@ -377,10 +370,5 @@ public static class CryptoExchangeWebSocketClientLoggingExtension
{ {
_socketPingTimeout(logger, socketId, null); _socketPingTimeout(logger, socketId, null);
} }
public static void SocketConnectingCanceled(
this ILogger logger, int socketId)
{
_connectingCanceled(logger, socketId, null);
} }
} }
@@ -1,11 +1,11 @@
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
{ {
#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; private static readonly Action<ILogger, int, string, string, string, Exception?> _rateLimitRequestFailed;
private static readonly Action<ILogger, int, string, string, Exception?> _rateLimitConnectionFailed; private static readonly Action<ILogger, int, string, string, Exception?> _rateLimitConnectionFailed;
private static readonly Action<ILogger, int, string, TimeSpan, string, string, Exception?> _rateLimitDelayingRequest; private static readonly Action<ILogger, int, string, TimeSpan, string, string, Exception?> _rateLimitDelayingRequest;
@@ -75,4 +75,5 @@ public static class RateLimitGateLoggingExtensions
{ {
_rateLimitAppliedRequest(logger, requestIdId, path, guard, limit, current, null); _rateLimitAppliedRequest(logger, requestIdId, path, guard, limit, current, null);
} }
}
} }
@@ -1,15 +1,15 @@
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
public static class RestApiClientLoggingExtensions
{
private static readonly Action<ILogger, int?, int?, long, string?, Exception?> _restApiErrorReceived;
private static readonly Action<ILogger, int?, int?, long, string?, Exception?> _restApiResponseReceived; private static readonly Action<ILogger, int?, int?, long, string?, Exception?> _restApiResponseReceived;
private static readonly Action<ILogger, int, string, Exception?> _restApiFailedToSyncTime; private static readonly Action<ILogger, int, string, Exception?> _restApiFailedToSyncTime;
private static readonly Action<ILogger, int, string, Exception?> _restApiNoApiCredentials; private static readonly Action<ILogger, int, string, Exception?> _restApiNoApiCredentials;
@@ -25,10 +25,10 @@ public static class RestApiClientLoggingExtensions
static RestApiClientLoggingExtensions() static RestApiClientLoggingExtensions()
{ {
_restApiErrorReceived = LoggerMessage.Define<int?, int?, long, string?, string?>( _restApiErrorReceived = LoggerMessage.Define<int?, int?, long, string?>(
LogLevel.Warning, LogLevel.Warning,
new EventId(4000, "RestApiErrorReceived"), new EventId(4000, "RestApiErrorReceived"),
"[Req {RequestId}] {ResponseStatusCode} - Error received in {ResponseTime}ms: {ErrorMessage}, Data: {OriginalData}"); "[Req {RequestId}] {ResponseStatusCode} - Error received in {ResponseTime}ms: {ErrorMessage}");
_restApiResponseReceived = LoggerMessage.Define<int?, int?, long, string?>( _restApiResponseReceived = LoggerMessage.Define<int?, int?, long, string?>(
LogLevel.Debug, LogLevel.Debug,
@@ -92,9 +92,9 @@ public static class RestApiClientLoggingExtensions
} }
public static void RestApiErrorReceived(this ILogger logger, int? requestId, HttpStatusCode? responseStatusCode, long responseTime, string? error, string? originalData, Exception? exception) public static void RestApiErrorReceived(this ILogger logger, int? requestId, HttpStatusCode? responseStatusCode, long responseTime, string? error)
{ {
_restApiErrorReceived(logger, requestId, (int?)responseStatusCode, responseTime, error, originalData, exception); _restApiErrorReceived(logger, requestId, (int?)responseStatusCode, responseTime, error, null);
} }
public static void RestApiResponseReceived(this ILogger logger, int? requestId, HttpStatusCode? responseStatusCode, long responseTime, string? originalData) public static void RestApiResponseReceived(this ILogger logger, int? requestId, HttpStatusCode? responseStatusCode, long responseTime, string? originalData)
@@ -155,4 +155,5 @@ public static class RestApiClientLoggingExtensions
{ {
_restApiCancellationRequested(logger, requestId, null); _restApiCancellationRequested(logger, requestId, null);
} }
}
} }
@@ -1,11 +1,11 @@
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
{ {
#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; private static readonly Action<ILogger, int, Exception?> _failedToAddSubscriptionRetryOnDifferentConnection;
private static readonly Action<ILogger, int, Exception?> _hasBeenPausedCantSubscribeAtThisMoment; private static readonly Action<ILogger, int, Exception?> _hasBeenPausedCantSubscribeAtThisMoment;
private static readonly Action<ILogger, int, string?, Exception?> _failedToSubscribe; private static readonly Action<ILogger, int, string?, Exception?> _failedToSubscribe;
@@ -196,4 +196,5 @@ public static class SocketApiClientLoggingExtension
{ {
_addingRetryAfterGuard(logger, retryAfter, null); _addingRetryAfterGuard(logger, retryAfter, null);
} }
}
} }
@@ -1,12 +1,12 @@
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
{ {
#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; private static readonly Action<ILogger, int, bool, Exception?> _activityPaused;
private static readonly Action<ILogger, int, Sockets.SocketConnection.SocketStatus, Sockets.SocketConnection.SocketStatus, Exception?> _socketStatusChanged; private static readonly Action<ILogger, int, Sockets.SocketConnection.SocketStatus, Sockets.SocketConnection.SocketStatus, Exception?> _socketStatusChanged;
private static readonly Action<ILogger, int, string?, Exception?> _failedReconnectProcessing; private static readonly Action<ILogger, int, string?, Exception?> _failedReconnectProcessing;
@@ -15,10 +15,9 @@ public static class SocketConnectionLoggingExtension
private static readonly Action<ILogger, int, string?, Exception?> _webSocketError; private static readonly Action<ILogger, int, string?, Exception?> _webSocketError;
private static readonly Action<ILogger, int, int, Exception?> _messageSentNotPending; 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?> _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, string, Exception?> _failedToEvaluateMessage;
private static readonly Action<ILogger, int, Exception?> _errorProcessingMessage; 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, string, Exception?> _processorMatched;
private static readonly Action<ILogger, int, int, Exception?> _receivedMessageNotRecognized; 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?> _failedToDeserializeMessage;
private static readonly Action<ILogger, int, string, Exception?> _userMessageProcessingFailed; private static readonly Action<ILogger, int, string, Exception?> _userMessageProcessingFailed;
@@ -38,7 +37,6 @@ public static class SocketConnectionLoggingExtension
private static readonly Action<ILogger, int, string, string, Exception?> _periodicSendFailed; 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, int, string, Exception?> _sendingData;
private static readonly Action<ILogger, int, string, string, Exception?> _receivedMessageNotMatchedToAnyListener; private static readonly Action<ILogger, int, string, string, Exception?> _receivedMessageNotMatchedToAnyListener;
private static readonly Action<ILogger, int, int, int, Exception?> _sendingByteData;
static SocketConnectionLoggingExtension() static SocketConnectionLoggingExtension()
{ {
@@ -92,6 +90,11 @@ public static class SocketConnectionLoggingExtension
new EventId(2009, "ErrorProcessingMessage"), new EventId(2009, "ErrorProcessingMessage"),
"[Sckt {SocketId}] error processing message"); "[Sckt {SocketId}] error processing message");
_processorMatched = LoggerMessage.Define<int, int, string>(
LogLevel.Trace,
new EventId(2010, "ProcessorMatched"),
"[Sckt {SocketId}] {Count} processor(s) matched to message with listener identifier {ListenerId}");
_receivedMessageNotRecognized = LoggerMessage.Define<int, int>( _receivedMessageNotRecognized = LoggerMessage.Define<int, int>(
LogLevel.Warning, LogLevel.Warning,
new EventId(2011, "ReceivedMessageNotRecognized"), new EventId(2011, "ReceivedMessageNotRecognized"),
@@ -185,23 +188,7 @@ public static class SocketConnectionLoggingExtension
_receivedMessageNotMatchedToAnyListener = LoggerMessage.Define<int, string, string>( _receivedMessageNotMatchedToAnyListener = LoggerMessage.Define<int, string, string>(
LogLevel.Warning, LogLevel.Warning,
new EventId(2029, "ReceivedMessageNotMatchedToAnyListener"), new EventId(2029, "ReceivedMessageNotMatchedToAnyListener"),
"[Sckt {SocketId}] received message not matched to any listener. ListenId: {ListenId}, current listeners: [{ListenIds}]"); "[Sckt {SocketId}] received message not matched to any listener. ListenId: {ListenId}, current listeners: {ListenIds}");
_failedToParse = LoggerMessage.Define<int, string>(
LogLevel.Warning,
new EventId(2030, "FailedToParse"),
"[Sckt {SocketId}] failed to parse data: {Error}");
_sendingByteData = LoggerMessage.Define<int, int, int>(
LogLevel.Trace,
new EventId(2031, "SendingByteData"),
"[Sckt {SocketId}] [Req {RequestId}] sending byte message of length: {Length}");
_processorMatched = LoggerMessage.Define<int, string, string>(
LogLevel.Trace,
new EventId(2032, "ProcessorMatched"),
"[Sckt {SocketId}] listener '{ListenId}' matched to message with listener identifier {ListenerId}");
} }
public static void ActivityPaused(this ILogger logger, int socketId, bool paused) public static void ActivityPaused(this ILogger logger, int socketId, bool paused)
@@ -243,12 +230,6 @@ public static class SocketConnectionLoggingExtension
{ {
_receivedData(logger, socketId, originalData, null); _receivedData(logger, socketId, originalData, null);
} }
public static void FailedToParse(this ILogger logger, int socketId, string error)
{
_failedToParse(logger, socketId, error, null);
}
public static void FailedToEvaluateMessage(this ILogger logger, int socketId, string originalData) public static void FailedToEvaluateMessage(this ILogger logger, int socketId, string originalData)
{ {
_failedToEvaluateMessage(logger, socketId, originalData, null); _failedToEvaluateMessage(logger, socketId, originalData, null);
@@ -257,17 +238,17 @@ public static class SocketConnectionLoggingExtension
{ {
_errorProcessingMessage(logger, socketId, e); _errorProcessingMessage(logger, socketId, e);
} }
public static void ProcessorMatched(this ILogger logger, int socketId, string listener, string listenerId) public static void ProcessorMatched(this ILogger logger, int socketId, int count, string listenerId)
{ {
_processorMatched(logger, socketId, listener, listenerId, null); _processorMatched(logger, socketId, count, listenerId, null);
} }
public static void ReceivedMessageNotRecognized(this ILogger logger, int socketId, int id) public static void ReceivedMessageNotRecognized(this ILogger logger, int socketId, int id)
{ {
_receivedMessageNotRecognized(logger, socketId, id, null); _receivedMessageNotRecognized(logger, socketId, id, null);
} }
public static void FailedToDeserializeMessage(this ILogger logger, int socketId, string? errorMessage, Exception? ex) public static void FailedToDeserializeMessage(this ILogger logger, int socketId, string? errorMessage)
{ {
_failedToDeserializeMessage(logger, socketId, errorMessage, ex); _failedToDeserializeMessage(logger, socketId, errorMessage, null);
} }
public static void UserMessageProcessingFailed(this ILogger logger, int socketId, string errorMessage, Exception e) public static void UserMessageProcessingFailed(this ILogger logger, int socketId, string errorMessage, Exception e)
{ {
@@ -340,9 +321,5 @@ public static class SocketConnectionLoggingExtension
{ {
_receivedMessageNotMatchedToAnyListener(logger, socketId, listenId, listenIds, null); _receivedMessageNotMatchedToAnyListener(logger, socketId, listenId, listenIds, null);
} }
public static void SendingByteData(this ILogger logger, int socketId, int requestId, int length)
{
_sendingByteData(logger, socketId, requestId, length, null);
} }
} }
@@ -1,13 +1,13 @@
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, OrderBookStatus, OrderBookStatus, Exception?> _orderBookStatusChanged;
private static readonly Action<ILogger, string, string, Exception?> _orderBookStarting; 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?> _orderBookStoppedStarting;
@@ -53,7 +53,7 @@ public static class SymbolOrderBookLoggingExtensions
"{Api} order book {Symbol} connection lost"); "{Api} order book {Symbol} connection lost");
_orderBookDisconnected = LoggerMessage.Define<string, string>( _orderBookDisconnected = LoggerMessage.Define<string, string>(
LogLevel.Debug, LogLevel.Warning,
new EventId(5004, "OrderBookDisconnected"), new EventId(5004, "OrderBookDisconnected"),
"{Api} order book {Symbol} disconnected"); "{Api} order book {Symbol} disconnected");
@@ -233,4 +233,5 @@ public static class SymbolOrderBookLoggingExtensions
{ {
_orderBookOutOfSyncChecksum(logger, api, symbol, null); _orderBookOutOfSyncChecksum(logger, api, symbol, null);
} }
}
} }
@@ -1,13 +1,13 @@
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, SyncStatus, SyncStatus, Exception?> _klineTrackerStatusChanged;
private static readonly Action<ILogger, string, Exception?> _klineTrackerStarting; private static readonly Action<ILogger, string, Exception?> _klineTrackerStarting;
private static readonly Action<ILogger, string, string, Exception?> _klineTrackerStartFailed; private static readonly Action<ILogger, string, string, Exception?> _klineTrackerStartFailed;
@@ -173,9 +173,9 @@ public static class TrackerLoggingExtensions
_klineTrackerStarting(logger, symbol, null); _klineTrackerStarting(logger, symbol, null);
} }
public static void KlineTrackerStartFailed(this ILogger logger, string symbol, string error, Exception? exception) public static void KlineTrackerStartFailed(this ILogger logger, string symbol, string error)
{ {
_klineTrackerStartFailed(logger, symbol, error, exception); _klineTrackerStartFailed(logger, symbol, error, null);
} }
public static void KlineTrackerStarted(this ILogger logger, string symbol) public static void KlineTrackerStarted(this ILogger logger, string symbol)
@@ -233,9 +233,9 @@ public static class TrackerLoggingExtensions
_tradeTrackerStarting(logger, symbol, null); _tradeTrackerStarting(logger, symbol, null);
} }
public static void TradeTrackerStartFailed(this ILogger logger, string symbol, string error, Exception? ex) public static void TradeTrackerStartFailed(this ILogger logger, string symbol, string error)
{ {
_tradeTrackerStartFailed(logger, symbol, error, ex); _tradeTrackerStartFailed(logger, symbol, error, null);
} }
public static void TradeTrackerStarted(this ILogger logger, string symbol) public static void TradeTrackerStarted(this ILogger logger, string symbol)
@@ -287,4 +287,5 @@ public static class TrackerLoggingExtensions
{ {
_tradeTrackerConnectionRestored(logger, symbol, null); _tradeTrackerConnectionRestored(logger, symbol, null);
} }
}
} }
+7 -6
View File
@@ -1,10 +1,10 @@
namespace CryptoExchange.Net.Objects; namespace CryptoExchange.Net.Objects
/// <summary>
/// Proxy info
/// </summary>
public class ApiProxy
{ {
/// <summary>
/// Proxy info
/// </summary>
public class ApiProxy
{
/// <summary> /// <summary>
/// The host address of the proxy /// The host address of the proxy
/// </summary> /// </summary>
@@ -38,4 +38,5 @@ public class ApiProxy
Login = login; Login = login;
Password = password; 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