mirror of
https://github.com/JKorf/CryptoExchange.Net.git
synced 2026-08-14 18:02:58 +00:00
Compare commits
24 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 478cb537fa | |||
| 6a82a7a411 | |||
| f17d9032fb | |||
| a8907c7ea5 | |||
| f151b961a5 | |||
| 932cc4864e | |||
| 9f6eb9f0d0 | |||
| a0e2f78a6a | |||
| 142cba5cca | |||
| 54aa6907f9 | |||
| bf103ce9d1 | |||
| ec1f469848 | |||
| c1dde521af | |||
| c070c425e7 | |||
| 2f6889880a | |||
| 48d3e15f39 | |||
| 1999a27b41 | |||
| e138d7d263 | |||
| 0d4ab96e19 | |||
| 7219441ec4 | |||
| 89c87b19e1 | |||
| 89bd091848 | |||
| 6aff618770 | |||
| 355d111a55 |
@@ -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>
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
#  CryptoExchange.Net.Proto
|
|
||||||
|
|
||||||
[](https://github.com/JKorf/CryptoExchange.Net/actions/workflows/dotnet.yml) [](https://www.nuget.org/packages/CryptoExchange.Net.Protobuf) 
|
|
||||||
|
|
||||||
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
|
|
||||||
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+9
-5
@@ -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";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -1,183 +0,0 @@
|
|||||||
root = true
|
|
||||||
|
|
||||||
[*]
|
|
||||||
|
|
||||||
# Indentation and spacing
|
|
||||||
indent_style = space
|
|
||||||
indent_size = 4
|
|
||||||
trim_trailing_whitespace = true
|
|
||||||
charset = utf-8
|
|
||||||
max_line_length = 140
|
|
||||||
insert_final_newline = true
|
|
||||||
|
|
||||||
# ReSharper code style properties
|
|
||||||
resharper_csharp_keep_existing_embedded_arrangement = false
|
|
||||||
resharper_csharp_place_accessorholder_attribute_on_same_line = false
|
|
||||||
resharper_csharp_wrap_after_declaration_lpar = true
|
|
||||||
resharper_csharp_wrap_parameters_style = chop_if_long
|
|
||||||
resharper_csharp_blank_lines_around_single_line_auto_property = 1
|
|
||||||
resharper_csharp_keep_blank_lines_in_declarations = 1
|
|
||||||
resharper_trailing_comma_in_multiline_lists = true
|
|
||||||
|
|
||||||
[*.cs]
|
|
||||||
|
|
||||||
indent_size = 4
|
|
||||||
|
|
||||||
# Code style conventions
|
|
||||||
dotnet_style_predefined_type_for_member_access = true:suggestion
|
|
||||||
dotnet_style_collection_initializer = true:suggestion
|
|
||||||
dotnet_style_object_initializer = true:suggestion
|
|
||||||
csharp_style_var_when_type_is_apparent = true:suggestion
|
|
||||||
csharp_style_expression_bodied_methods = true:suggestion
|
|
||||||
csharp_style_namespace_declarations = file_scoped:warning
|
|
||||||
dotnet_style_coalesce_expression = true:suggestion
|
|
||||||
dotnet_style_null_propagation = true:suggestion
|
|
||||||
dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion
|
|
||||||
dotnet_style_prefer_simplified_boolean_expressions = true:suggestion
|
|
||||||
csharp_prefer_braces = when_multiline:warning
|
|
||||||
|
|
||||||
# Analyzer preferences
|
|
||||||
dotnet_diagnostic.CA2007.severity = warning # Call ConfigureAwait on the awaited Task.
|
|
||||||
dotnet_code_quality.CA2007.exclude_async_void_methods = true
|
|
||||||
dotnet_code_quality.CA2007.output_kind = DynamicallyLinkedLibrary
|
|
||||||
|
|
||||||
dotnet_diagnostic.CA1000.severity = none # Do not declare static members on generic types
|
|
||||||
dotnet_diagnostic.CA1051.severity = none # Do not declare visible instance fields
|
|
||||||
dotnet_diagnostic.CA1510.severity = none # Use ArgumentNullException throw helper
|
|
||||||
dotnet_diagnostic.CA1720.severity = none # Identifiers should not contain type names
|
|
||||||
dotnet_diagnostic.CA1716.severity = none # Identifiers should not match keywords
|
|
||||||
dotnet_diagnostic.CA1835.severity = none # Use ArgumentNullException throw helper
|
|
||||||
dotnet_diagnostic.CA1846.severity = none # Prefer AsSpan over Substring
|
|
||||||
dotnet_diagnostic.CA1848.severity = none # Use the LoggerMessage delegates
|
|
||||||
dotnet_diagnostic.CA1850.severity = none # Prefer static HashData method over ComputeHash
|
|
||||||
dotnet_diagnostic.CA1866.severity = none # Use 'string.Method(char)' instead of 'string.Method(string)' for string with single char
|
|
||||||
dotnet_diagnostic.CA2201.severity = none # Do not raise reserved exception types
|
|
||||||
dotnet_diagnostic.CA2208.severity = none # Do not raise reserved exception types
|
|
||||||
dotnet_diagnostic.IDE0005.severity = warning # Using directive is unnecessary
|
|
||||||
|
|
||||||
[*.xml]
|
|
||||||
ij_xml_space_inside_empty_tag = true
|
|
||||||
[*.cs]
|
|
||||||
#### Naming styles ####
|
|
||||||
|
|
||||||
# Naming rules
|
|
||||||
|
|
||||||
dotnet_naming_rule.interface_should_be_begins_with_i.severity = warning
|
|
||||||
dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface
|
|
||||||
dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i
|
|
||||||
|
|
||||||
dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = warning
|
|
||||||
dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members
|
|
||||||
dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case
|
|
||||||
|
|
||||||
dotnet_naming_rule.private_or_internal_field_should_be_fields_start_with__.severity = warning
|
|
||||||
dotnet_naming_rule.private_or_internal_field_should_be_fields_start_with__.symbols = private_or_internal_field
|
|
||||||
dotnet_naming_rule.private_or_internal_field_should_be_fields_start_with__.style = fields_start_with__
|
|
||||||
|
|
||||||
# Symbol specifications
|
|
||||||
|
|
||||||
dotnet_naming_symbols.interface.applicable_kinds = interface
|
|
||||||
dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
|
|
||||||
dotnet_naming_symbols.interface.required_modifiers =
|
|
||||||
|
|
||||||
dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method
|
|
||||||
dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
|
|
||||||
dotnet_naming_symbols.non_field_members.required_modifiers =
|
|
||||||
|
|
||||||
dotnet_naming_symbols.private_or_internal_field.applicable_kinds = field
|
|
||||||
dotnet_naming_symbols.private_or_internal_field.applicable_accessibilities = internal, private, private_protected
|
|
||||||
dotnet_naming_symbols.private_or_internal_field.required_modifiers =
|
|
||||||
|
|
||||||
# Naming styles
|
|
||||||
|
|
||||||
dotnet_naming_style.begins_with_i.required_prefix = I
|
|
||||||
dotnet_naming_style.begins_with_i.required_suffix =
|
|
||||||
dotnet_naming_style.begins_with_i.word_separator =
|
|
||||||
dotnet_naming_style.begins_with_i.capitalization = pascal_case
|
|
||||||
|
|
||||||
dotnet_naming_style.pascal_case.required_prefix =
|
|
||||||
dotnet_naming_style.pascal_case.required_suffix =
|
|
||||||
dotnet_naming_style.pascal_case.word_separator =
|
|
||||||
dotnet_naming_style.pascal_case.capitalization = pascal_case
|
|
||||||
|
|
||||||
dotnet_naming_style.fields_start_with__.required_prefix = _
|
|
||||||
dotnet_naming_style.fields_start_with__.required_suffix =
|
|
||||||
dotnet_naming_style.fields_start_with__.word_separator =
|
|
||||||
dotnet_naming_style.fields_start_with__.capitalization = camel_case
|
|
||||||
csharp_indent_labels = one_less_than_current
|
|
||||||
csharp_using_directive_placement = outside_namespace:suggestion
|
|
||||||
csharp_prefer_simple_using_statement = true:suggestion
|
|
||||||
csharp_style_prefer_method_group_conversion = true:silent
|
|
||||||
csharp_style_prefer_top_level_statements = true:silent
|
|
||||||
csharp_style_prefer_primary_constructors = true:suggestion
|
|
||||||
csharp_prefer_system_threading_lock = true:suggestion
|
|
||||||
csharp_style_expression_bodied_constructors = false:silent
|
|
||||||
csharp_style_expression_bodied_operators = false:silent
|
|
||||||
csharp_style_expression_bodied_properties = true:suggestion
|
|
||||||
csharp_style_expression_bodied_indexers = true:suggestion
|
|
||||||
csharp_style_expression_bodied_accessors = true:suggestion
|
|
||||||
csharp_style_expression_bodied_lambdas = true:silent
|
|
||||||
csharp_style_expression_bodied_local_functions = true:silent
|
|
||||||
|
|
||||||
[*.vb]
|
|
||||||
#### Naming styles ####
|
|
||||||
|
|
||||||
# Naming rules
|
|
||||||
|
|
||||||
dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion
|
|
||||||
dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface
|
|
||||||
dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i
|
|
||||||
|
|
||||||
dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion
|
|
||||||
dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members
|
|
||||||
dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case
|
|
||||||
|
|
||||||
# Symbol specifications
|
|
||||||
|
|
||||||
dotnet_naming_symbols.interface.applicable_kinds = interface
|
|
||||||
dotnet_naming_symbols.interface.applicable_accessibilities = public, friend, private, protected, protected_friend, private_protected
|
|
||||||
dotnet_naming_symbols.interface.required_modifiers =
|
|
||||||
|
|
||||||
dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method
|
|
||||||
dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, friend, private, protected, protected_friend, private_protected
|
|
||||||
dotnet_naming_symbols.non_field_members.required_modifiers =
|
|
||||||
|
|
||||||
# Naming styles
|
|
||||||
|
|
||||||
dotnet_naming_style.begins_with_i.required_prefix = I
|
|
||||||
dotnet_naming_style.begins_with_i.required_suffix =
|
|
||||||
dotnet_naming_style.begins_with_i.word_separator =
|
|
||||||
dotnet_naming_style.begins_with_i.capitalization = pascal_case
|
|
||||||
|
|
||||||
dotnet_naming_style.pascal_case.required_prefix =
|
|
||||||
dotnet_naming_style.pascal_case.required_suffix =
|
|
||||||
dotnet_naming_style.pascal_case.word_separator =
|
|
||||||
dotnet_naming_style.pascal_case.capitalization = pascal_case
|
|
||||||
|
|
||||||
[*.{cs,vb}]
|
|
||||||
#### Naming styles ####
|
|
||||||
|
|
||||||
# Naming rules
|
|
||||||
|
|
||||||
dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion
|
|
||||||
dotnet_naming_rule.types_should_be_pascal_case.symbols = types
|
|
||||||
dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case
|
|
||||||
|
|
||||||
# Symbol specifications
|
|
||||||
|
|
||||||
dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum
|
|
||||||
dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
|
|
||||||
dotnet_naming_symbols.types.required_modifiers =
|
|
||||||
|
|
||||||
# Naming styles
|
|
||||||
|
|
||||||
dotnet_naming_style.pascal_case.required_prefix =
|
|
||||||
dotnet_naming_style.pascal_case.required_suffix =
|
|
||||||
dotnet_naming_style.pascal_case.word_separator =
|
|
||||||
dotnet_naming_style.pascal_case.capitalization = pascal_case
|
|
||||||
dotnet_style_operator_placement_when_wrapping = beginning_of_line
|
|
||||||
tab_width = 4
|
|
||||||
end_of_line = crlf
|
|
||||||
dotnet_style_coalesce_expression = true:suggestion
|
|
||||||
dotnet_style_null_propagation = true:suggestion
|
|
||||||
dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion
|
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("CryptoExchange.Net.UnitTests")]
|
[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("CryptoExchange.Net.UnitTests")]
|
||||||
|
|
||||||
namespace System.Runtime.CompilerServices;
|
namespace System.Runtime.CompilerServices
|
||||||
|
{
|
||||||
internal static class IsExternalInit { }
|
internal static class IsExternalInit { }
|
||||||
|
}
|
||||||
@@ -1,11 +1,12 @@
|
|||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace CryptoExchange.Net.Attributes;
|
namespace CryptoExchange.Net.Attributes
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Used for conversion in ArrayConverter
|
|
||||||
/// </summary>
|
|
||||||
[AttributeUsage(AttributeTargets.Property)]
|
|
||||||
public class JsonConversionAttribute: Attribute
|
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Used for conversion in ArrayConverter
|
||||||
|
/// </summary>
|
||||||
|
[AttributeUsage(AttributeTargets.Property)]
|
||||||
|
public class JsonConversionAttribute: Attribute
|
||||||
|
{
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
@@ -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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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; }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -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; }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-17
@@ -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);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
Reference in New Issue
Block a user