Skip to content

Commit

Permalink
Merge pull request #122 from tochka-public/feat/clientContext
Browse files Browse the repository at this point in the history
Feat/client context
  • Loading branch information
Rast1234 authored Feb 19, 2025
2 parents 42a534d + abc3183 commit 9bf233a
Show file tree
Hide file tree
Showing 76 changed files with 469 additions and 224 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/package.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ jobs:
- name: Setup .NET
uses: actions/setup-dotnet@v3
with:
dotnet-version: 7.0.x
dotnet-version: 8.0.x

- name: Checkout
uses: actions/checkout@v3
Expand All @@ -42,7 +42,7 @@ jobs:
- name: Setup .NET
uses: actions/setup-dotnet@v3
with:
dotnet-version: 7.0.x
dotnet-version: 8.0.x

- name: Checkout
uses: actions/checkout@v3
Expand Down
4 changes: 2 additions & 2 deletions src/Tochka.JsonRpc.ApiExplorer/JsonRpcDescriptionProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ public void OnProvidersExecuting(ApiDescriptionProviderContext context)
{
var existingDescriptions = context.Results
.Where(static x => x.ActionDescriptor.EndpointMetadata.Any(static m => m is JsonRpcControllerAttribute))
.ToArray();
.ToList();

foreach (var description in existingDescriptions)
{
Expand Down Expand Up @@ -90,7 +90,7 @@ private void WrapRequest(ApiDescription description, ControllerActionDescriptor
var parametersMetadata = actionDescriptor.EndpointMetadata.Get<JsonRpcActionParametersMetadata>() ?? new JsonRpcActionParametersMetadata();
var parametersToRemove = description.ParameterDescriptions
.Where(p => parametersMetadata.Parameters.ContainsKey(p.Name) || p.Source == BindingSource.Body)
.ToArray();
.ToList();
foreach (var parameterDescription in parametersToRemove)
{
description.ParameterDescriptions.Remove(parameterDescription);
Expand Down
42 changes: 20 additions & 22 deletions src/Tochka.JsonRpc.Client/JsonRpcClientBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
using Tochka.JsonRpc.Common;
using Tochka.JsonRpc.Common.Models.Id;
using Tochka.JsonRpc.Common.Models.Request;
using Tochka.JsonRpc.Common.Models.Request.Untyped;
using Tochka.JsonRpc.Common.Models.Response.Wrappers;

namespace Tochka.JsonRpc.Client;
Expand Down Expand Up @@ -336,7 +337,7 @@ internal virtual async Task SendNotificationInternal<TParams>(string? requestUrl
var data = notification.WithSerializedParams(DataJsonSerializerOptions);
context.WithSingle(data);
using var content = CreateHttpContent(data);
using var requestMessage = CreateRequestMessage(requestUrl, content, new[] { notification.Method });
using var requestMessage = CreateRequestMessage(requestUrl, content, [data]);
var httpResponseMessage = await Client.SendAsync(requestMessage, cancellationToken);
context.WithHttpResponse(httpResponseMessage);
}
Expand Down Expand Up @@ -424,19 +425,18 @@ internal virtual async Task<ISingleJsonRpcResult<TResponse>> SendRequestInternal
{
var context = CreateContext();
context.WithRequestUrl(requestUrl);
var data = calls.Select(x => x.WithSerializedParams(DataJsonSerializerOptions)).ToArray();
var data = calls.Select(x => x.WithSerializedParams(DataJsonSerializerOptions)).ToList();
context.WithBatch(data);
using var content = CreateHttpContent(data);
var methodNames = data.Select(static x => x.Method).ToArray();
using var request = CreateRequestMessage(requestUrl, content, methodNames);
using var request = CreateRequestMessage(requestUrl, content, data);
var httpResponseMessage = await Client.SendAsync(request, cancellationToken);
context.WithHttpResponse(httpResponseMessage);
if (context.ExpectedBatchResponseCount == 0)
{
// from specification:
// "If there are no Response objects contained within the Response array as it is to be sent to the client,
// the server MUST NOT return an empty Array and should return nothing at all."
Log.LogTrace("Batch count [{batchCount}] success: no response expected", data.Length);
Log.LogTrace("Batch count [{batchCount}] success: no response expected", data.Count);
return null;
}

Expand All @@ -447,18 +447,18 @@ internal virtual async Task<ISingleJsonRpcResult<TResponse>> SendRequestInternal
{
case BatchResponseWrapper batchResponseWrapper:
context.WithBatchResponse(batchResponseWrapper.Responses);
Log.LogTrace("Batch count [{batchCount}] success: response count [{responseCount}]", data.Length, batchResponseWrapper.Responses.Count);
Log.LogTrace("Batch count [{batchCount}] success: response count [{responseCount}]", data.Count, batchResponseWrapper.Responses.Count);
return new BatchJsonRpcResult(context, HeadersJsonSerializerOptions, DataJsonSerializerOptions);
case SingleResponseWrapper singleResponseWrapper:
// "If the batch rpc call itself fails to be recognized as an valid JSON or as an Array with at least one value,
// the response from the Server MUST be a single Response object."
context.WithSingleResponse(singleResponseWrapper.Response);
var message1 = $"Expected batch response, got single, id [{singleResponseWrapper.Response.Id}]";
Log.LogTrace("Batch count [{batchCount}] failed: {errorMessage}", data.Length, message1);
Log.LogTrace("Batch count [{batchCount}] failed: {errorMessage}", data.Count, message1);
throw new JsonRpcException(message1, context);
default:
var message2 = $"Expected batch response, got [{responseWrapper?.GetType().Name}]";
Log.LogTrace("Batch count [{batchCount}] failed: {errorMessage}", data.Length, message2);
Log.LogTrace("Batch count [{batchCount}] failed: {errorMessage}", data.Count, message2);
throw new JsonRpcException(message2, context);
}
}
Expand All @@ -468,19 +468,18 @@ internal virtual async Task<ISingleJsonRpcResult<TResponse>> SendRequestInternal
{
var context = CreateContext();
context.WithRequestUrl(requestUrl);
var data = calls.Select(x => x.WithSerializedParams(DataJsonSerializerOptions)).ToArray();
var data = calls.Select(x => x.WithSerializedParams(DataJsonSerializerOptions)).ToList();
context.WithBatch(data);
using var content = CreateHttpContent(data);
var methodNames = data.Select(static x => x.Method).ToArray();
using var request = CreateRequestMessage(requestUrl, content, methodNames);
using var request = CreateRequestMessage(requestUrl, content, data);
var httpResponseMessage = await Client.SendAsync(request, cancellationToken);
context.WithHttpResponse(httpResponseMessage);
if (context.ExpectedBatchResponseCount == 0)
{
// from specification:
// "If there are no Response objects contained within the Response array as it is to be sent to the client,
// the server MUST NOT return an empty Array and should return nothing at all."
Log.LogTrace("Batch count [{batchCount}] success: no response expected", data.Length);
Log.LogTrace("Batch count [{batchCount}] success: no response expected", data.Count);
return null;
}

Expand All @@ -491,18 +490,18 @@ internal virtual async Task<ISingleJsonRpcResult<TResponse>> SendRequestInternal
{
case BatchResponseWrapper batchResponseWrapper:
context.WithBatchResponse(batchResponseWrapper.Responses);
Log.LogTrace("Batch count [{batchCount}] success: response count [{responseCount}]", data.Length, batchResponseWrapper.Responses.Count);
Log.LogTrace("Batch count [{batchCount}] success: response count [{responseCount}]", data.Count, batchResponseWrapper.Responses.Count);
return new BatchJsonRpcResult<TResponse>(context, HeadersJsonSerializerOptions, DataJsonSerializerOptions);
case SingleResponseWrapper singleResponseWrapper:
// "If the batch rpc call itself fails to be recognized as an valid JSON or as an Array with at least one value,
// the response from the Server MUST be a single Response object."
context.WithSingleResponse(singleResponseWrapper.Response);
var message1 = $"Expected batch response, got single, id [{singleResponseWrapper.Response.Id}]";
Log.LogTrace("Batch count [{batchCount}] failed: {errorMessage}", data.Length, message1);
Log.LogTrace("Batch count [{batchCount}] failed: {errorMessage}", data.Count, message1);
throw new JsonRpcException(message1, context);
default:
var message2 = $"Expected batch response, got [{responseWrapper?.GetType().Name}]";
Log.LogTrace("Batch count [{batchCount}] failed: {errorMessage}", data.Length, message2);
Log.LogTrace("Batch count [{batchCount}] failed: {errorMessage}", data.Count, message2);
throw new JsonRpcException(message2, context);
}
}
Expand All @@ -512,17 +511,16 @@ internal virtual async Task<HttpResponseMessage> SendInternal(string? requestUrl
{
var data = call.WithSerializedParams(DataJsonSerializerOptions);
using var content = CreateHttpContent(data);
using var httpRequestMessage = CreateRequestMessage(requestUrl, content, new[] { call.Method });
using var httpRequestMessage = CreateRequestMessage(requestUrl, content, [data]);
return await Client.SendAsync(httpRequestMessage, cancellationToken);
}

// internal virtual for mocking in tests
internal virtual async Task<HttpResponseMessage> SendInternal(string? requestUrl, IEnumerable<ICall> calls, CancellationToken cancellationToken)
{
var data = calls.Select(x => x.WithSerializedParams(DataJsonSerializerOptions)).ToArray();
var data = calls.Select(x => x.WithSerializedParams(DataJsonSerializerOptions)).ToList();
using var content = CreateHttpContent(data);
var methodNames = data.Select(static x => x.Method).ToArray();
using var message = CreateRequestMessage(requestUrl, content, methodNames);
using var message = CreateRequestMessage(requestUrl, content, data);
return await Client.SendAsync(message, cancellationToken);
}

Expand Down Expand Up @@ -561,7 +559,7 @@ protected internal virtual async Task<string> GetContent(HttpContent content, Ca
var data = request.WithSerializedParams(DataJsonSerializerOptions);
context.WithSingle(data);
using var content = CreateHttpContent(data);
using var requestMessage = CreateRequestMessage(requestUrl, content, new[] { request.Method });
using var requestMessage = CreateRequestMessage(requestUrl, content, [data]);
var httpResponseMessage = await Client.SendAsync(requestMessage, cancellationToken);
context.WithHttpResponse(httpResponseMessage);
var contentString = await GetContent(httpResponseMessage.Content, cancellationToken);
Expand All @@ -579,14 +577,14 @@ private void InitializeClient(HttpClient client)
Log.LogTrace("Client initialized: user-agent {userAgent}", client.DefaultRequestHeaders.UserAgent);
}

private static HttpRequestMessage CreateRequestMessage(string? requestUrl, HttpContent content, string[] methodNames)
private static HttpRequestMessage CreateRequestMessage(string? requestUrl, HttpContent content, IReadOnlyList<IUntypedCall> calls)
{
var httpRequestMessage = new HttpRequestMessage(HttpMethod.Post, requestUrl)
{
Content = content
};

httpRequestMessage.Options.Set(new HttpRequestOptionsKey<string[]>(JsonRpcConstants.OutgoingHttpRequestOptionMethodNameKey), methodNames);
httpRequestMessage.Options.Set(JsonRpcConstants.JsonRpcClientCallsKey, calls);
return httpRequestMessage;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ namespace Tochka.JsonRpc.Client.Models.BatchResult;

/// <inheritdoc cref="IBatchJsonRpcResult" />
[PublicAPI]
public sealed class BatchJsonRpcResult : BatchJsonRpcResult<object>, IBatchJsonRpcResult
public sealed class BatchJsonRpcResult : BatchJsonRpcResult<object>,
IBatchJsonRpcResult
{
/// <summary></summary>
public BatchJsonRpcResult(IJsonRpcCallContext context, JsonSerializerOptions headersJsonSerializerOptions, JsonSerializerOptions dataJsonSerializerOptions)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ namespace Tochka.JsonRpc.Client.Models.SingleResult;

/// <inheritdoc cref="ISingleJsonRpcResult" />
[PublicAPI]
public sealed class SingleJsonRpcResult : SingleJsonRpcResult<object>, ISingleJsonRpcResult
public sealed class SingleJsonRpcResult : SingleJsonRpcResult<object>,
ISingleJsonRpcResult
{
/// <summary></summary>
public SingleJsonRpcResult(IJsonRpcCallContext context, JsonSerializerOptions headersJsonSerializerOptions, JsonSerializerOptions dataJsonSerializerOptions)
Expand Down
7 changes: 6 additions & 1 deletion src/Tochka.JsonRpc.Common/Converters/CallConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -64,5 +64,10 @@ private static PropertiesInfo CheckProperties(Utf8JsonReader propertyReader)
return new PropertiesInfo(hasId, hasMethod, hasVersion);
}

private record PropertiesInfo(bool HasId, bool HasMethod, bool HasVersion);
private record PropertiesInfo
(
bool HasId,
bool HasMethod,
bool HasVersion
);
}
8 changes: 7 additions & 1 deletion src/Tochka.JsonRpc.Common/Converters/ResponseConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -72,5 +72,11 @@ private static PropertiesInfo CheckProperties(Utf8JsonReader propertyReader)
return new PropertiesInfo(hasId, hasResult, hasError, hasVersion);
}

private record PropertiesInfo(bool HasId, bool HasResult, bool HasError, bool HasVersion);
private record PropertiesInfo
(
bool HasId,
bool HasResult,
bool HasError,
bool HasVersion
);
}
5 changes: 3 additions & 2 deletions src/Tochka.JsonRpc.Common/JsonRpcConstants.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System.Diagnostics.CodeAnalysis;
using JetBrains.Annotations;
using Tochka.JsonRpc.Common.Models.Request.Untyped;

namespace Tochka.JsonRpc.Common;

Expand Down Expand Up @@ -77,7 +78,7 @@ public static class JsonRpcConstants
public const int LogStringLimit = 5000;

/// <summary>
/// HttpRequest.Options item with string[], contains method name to be called. Multiple names for batch requests
/// HttpRequest.Options item, contains jsonrpc requests sent by client. Multiple items for batch requests
/// </summary>
public const string OutgoingHttpRequestOptionMethodNameKey = "tochka_outgoing_http_request_method_name";
public static readonly HttpRequestOptionsKey<IReadOnlyList<IUntypedCall>> JsonRpcClientCallsKey = new("tochka_jsonrpc_client_calls");
}
5 changes: 4 additions & 1 deletion src/Tochka.JsonRpc.Common/Models/Id/FloatNumberRpcId.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@ namespace Tochka.JsonRpc.Common.Models.Id;
/// <param name="Value">Actual id value</param>
[PublicAPI]
[ExcludeFromCodeCoverage]
public sealed record FloatNumberRpcId(double Value) : IRpcId
public sealed record FloatNumberRpcId
(
double Value
) : IRpcId
{
/// <inheritdoc />
public override string ToString() => Value.ToString(CultureInfo.InvariantCulture);
Expand Down
5 changes: 4 additions & 1 deletion src/Tochka.JsonRpc.Common/Models/Id/NumberRpcId.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,10 @@ namespace Tochka.JsonRpc.Common.Models.Id;
/// <param name="Value">Actual id value</param>
[PublicAPI]
[ExcludeFromCodeCoverage]
public sealed record NumberRpcId(long Value) : IRpcId
public sealed record NumberRpcId
(
long Value
) : IRpcId
{
/// <inheritdoc />
public override string ToString() => $"{Value}";
Expand Down
5 changes: 4 additions & 1 deletion src/Tochka.JsonRpc.Common/Models/Id/StringRpcId.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,10 @@ namespace Tochka.JsonRpc.Common.Models.Id;
/// <param name="Value">Actual id value</param>
[PublicAPI]
[ExcludeFromCodeCoverage]
public sealed record StringRpcId(string Value) : IRpcId
public sealed record StringRpcId
(
string Value
) : IRpcId
{
/// <inheritdoc />
public override string ToString() => $"\"{Value}\"";
Expand Down
7 changes: 6 additions & 1 deletion src/Tochka.JsonRpc.Common/Models/Request/Notification.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,12 @@ namespace Tochka.JsonRpc.Common.Models.Request;
/// <param name="Jsonrpc">Version of the JSON-RPC protocol</param>
[PublicAPI]
[ExcludeFromCodeCoverage]
public record Notification<TParams>(string Method, TParams? Params, string Jsonrpc = JsonRpcConstants.Version) : ICall<TParams>
public record Notification<TParams>
(
string Method,
TParams? Params,
string Jsonrpc = JsonRpcConstants.Version
) : ICall<TParams>
where TParams : class
{
/// <inheritdoc />
Expand Down
20 changes: 14 additions & 6 deletions src/Tochka.JsonRpc.Common/Models/Request/Request.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,13 @@ namespace Tochka.JsonRpc.Common.Models.Request;
/// <param name="Jsonrpc">Version of the JSON-RPC protocol</param>
[PublicAPI]
[ExcludeFromCodeCoverage]
public record Request<TParams>(IRpcId Id, string Method, TParams? Params, string Jsonrpc = JsonRpcConstants.Version) : ICall<TParams>
public record Request<TParams>
(
IRpcId Id,
string Method,
TParams? Params,
string Jsonrpc = JsonRpcConstants.Version
) : ICall<TParams>
where TParams : class
{
// required for autodoc metadata generation
Expand All @@ -41,16 +47,18 @@ public IUntypedCall WithSerializedParams(JsonSerializerOptions serializerOptions
/// <param name="Jsonrpc">Version of the JSON-RPC protocol</param>
[PublicAPI]
[ExcludeFromCodeCoverage]
public record Request(IRpcId Id, string Method, string Jsonrpc = JsonRpcConstants.Version) : ICall
public record Request
(
IRpcId Id,
string Method,
string Jsonrpc = JsonRpcConstants.Version
) : ICall
{
// required for autodoc metadata generation
internal Request() : this(null!)
{
}

/// <inheritdoc />
public IUntypedCall WithSerializedParams(JsonSerializerOptions serializerOptions)
{
return new UntypedRequest(Id, Method, null);
}
public IUntypedCall WithSerializedParams(JsonSerializerOptions serializerOptions) => new UntypedRequest(Id, Method, null);
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,11 @@ namespace Tochka.JsonRpc.Common.Models.Request.Untyped;
/// </summary>
[PublicAPI]
[ExcludeFromCodeCoverage]
public sealed record UntypedNotification(string Method, JsonDocument? Params, string Jsonrpc = JsonRpcConstants.Version)
: Notification<JsonDocument>(Method, Params, Jsonrpc), IUntypedCall;
public sealed record UntypedNotification
(
string Method,
JsonDocument? Params,
string Jsonrpc = JsonRpcConstants.Version
)
: Notification<JsonDocument>(Method, Params, Jsonrpc),
IUntypedCall;
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,12 @@ namespace Tochka.JsonRpc.Common.Models.Request.Untyped;
/// </summary>
[PublicAPI]
[ExcludeFromCodeCoverage]
public sealed record UntypedRequest(IRpcId Id, string Method, JsonDocument? Params, string Jsonrpc = JsonRpcConstants.Version)
: Request<JsonDocument>(Id, Method, Params, Jsonrpc), IUntypedCall;
public sealed record UntypedRequest
(
IRpcId Id,
string Method,
JsonDocument? Params,
string Jsonrpc = JsonRpcConstants.Version
)
: Request<JsonDocument>(Id, Method, Params, Jsonrpc),
IUntypedCall;
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,7 @@ namespace Tochka.JsonRpc.Common.Models.Request.Wrappers;
/// <param name="Calls">List of calls in batch request</param>
[PublicAPI]
[ExcludeFromCodeCoverage]
public sealed record BatchRequestWrapper(List<JsonDocument> Calls) : IRequestWrapper;
public sealed record BatchRequestWrapper
(
List<JsonDocument> Calls
) : IRequestWrapper;
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,7 @@ namespace Tochka.JsonRpc.Common.Models.Request.Wrappers;
/// <param name="Call">Single call from request</param>
[PublicAPI]
[ExcludeFromCodeCoverage]
public sealed record SingleRequestWrapper(JsonDocument Call) : IRequestWrapper;
public sealed record SingleRequestWrapper
(
JsonDocument Call
) : IRequestWrapper;
7 changes: 6 additions & 1 deletion src/Tochka.JsonRpc.Common/Models/Response/ErrorResponse.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,9 @@ namespace Tochka.JsonRpc.Common.Models.Response;
/// <typeparam name="TError">Type of error</typeparam>
[PublicAPI]
[ExcludeFromCodeCoverage]
public record ErrorResponse<TError>(IRpcId Id, Error<TError> Error, string Jsonrpc = JsonRpcConstants.Version) : IResponse;
public record ErrorResponse<TError>
(
IRpcId Id,
Error<TError> Error,
string Jsonrpc = JsonRpcConstants.Version
) : IResponse;
Loading

0 comments on commit 9bf233a

Please sign in to comment.