Skip to content

Commit

Permalink
feat: JsonRpc.Server can work with requests without params, JsonRpc.C…
Browse files Browse the repository at this point in the history
…lient has methods without params
  • Loading branch information
OlgaStarkova-Tochka committed Aug 22, 2024
1 parent 2c0dc73 commit d80a3f9
Show file tree
Hide file tree
Showing 14 changed files with 223 additions and 113 deletions.
15 changes: 14 additions & 1 deletion src/Tochka.JsonRpc.Client/IJsonRpcClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,20 @@ Task<ISingleJsonRpcResult<TResponse>> SendRequest<TParams, TResponse>(string req
Task<ISingleJsonRpcResult<TResponse>> SendRequest<TParams, TResponse>(IRpcId id, string method, TParams? parameters, CancellationToken cancellationToken)
where TParams : class
where TResponse : class;


/// <summary>
/// Send request to given url. Expects HTTP 200 with JSON-RPC response
/// </summary>
/// <param name="requestUrl">Relative path, appended to BaseAddress. Must not start with '/'</param>
/// <param name="id">JSON-RPC request id. Can be null</param>
/// <param name="method">JSON-RPC method</param>
/// <param name="cancellationToken"></param>
/// <returns>Result to be inspected for response data or errors</returns>
/// <exception cref="JsonRpcException">When HTTP status code is not 200, body is empty or deserialized as batch response</exception>
/// <exception cref="JsonException">When reading or deserializing JSON from body failed</exception>
/// <exception cref="System.ArgumentException">When requestUrl starts with '/'</exception>
Task<ISingleJsonRpcResult> SendRequest(string requestUrl, IRpcId id, string method, CancellationToken cancellationToken);

/// <summary>
/// Send batch of requests or notifications to given url. Expects HTTP 200 with batch JSON-RPC response if batch contains at least one request
/// </summary>
Expand Down
50 changes: 47 additions & 3 deletions src/Tochka.JsonRpc.Client/JsonRpcClientBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,14 @@ public async Task<ISingleJsonRpcResult<TResponse>> SendRequest<TParams, TRespons
var request = new Request<TParams>(id, method, parameters);
return await SendRequestInternal<TParams, TResponse>(null, request, cancellationToken);
}


/// <inheritdoc />
public async Task<ISingleJsonRpcResult> SendRequest(string requestUrl, IRpcId id, string method, CancellationToken cancellationToken)
{
var request = new Request(id, method);
return await SendRequestInternal(requestUrl, request, cancellationToken);
}

/// <inheritdoc />
public async Task<IBatchJsonRpcResult?> SendBatch(string requestUrl, IEnumerable<ICall> calls, CancellationToken cancellationToken) =>
await SendBatchInternal(requestUrl, calls, cancellationToken);
Expand Down Expand Up @@ -265,6 +272,24 @@ internal virtual async Task<ISingleJsonRpcResult> SendRequestInternal<TParams>(s
throw new JsonRpcException(message, context);
}
}

// internal virtual for mocking in tests
internal virtual async Task<ISingleJsonRpcResult> SendRequestInternal(string? requestUrl, Request request, CancellationToken cancellationToken)
{
var (context, contentString) = await PrepareInternalRequestContext(requestUrl, request, cancellationToken);
var responseWrapper = ParseBody(contentString);
switch (responseWrapper)
{
case SingleResponseWrapper singleResponseWrapper:
context.WithSingleResponse(singleResponseWrapper.Response);
Log.LogTrace("Request id [{requestId}]: success", request.Id);
return new SingleJsonRpcResult(context, HeadersJsonSerializerOptions, DataJsonSerializerOptions);
default:
var message = $"Expected single response, got [{responseWrapper}]";
Log.LogTrace("Request id [{requestId}] failed: {errorMessage}", request.Id, message);
throw new JsonRpcException(message, context);
}
}

// internal virtual for mocking in tests
internal virtual async Task<ISingleJsonRpcResult<TResponse>> SendRequestInternal<TParams, TResponse>(string? requestUrl, Request<TParams> request, CancellationToken cancellationToken)
Expand All @@ -285,6 +310,26 @@ internal virtual async Task<ISingleJsonRpcResult<TResponse>> SendRequestInternal
throw new JsonRpcException(message, context);
}
}

// internal virtual for mocking in tests
internal virtual async Task<ISingleJsonRpcResult<TResponse>> SendRequestInternal<TResponse>(string? requestUrl, Request request, CancellationToken cancellationToken)
where TResponse : class
{
var (context, contentString) = await PrepareInternalRequestContext(requestUrl, request, cancellationToken);
var responseWrapper = ParseBody(contentString);
switch (responseWrapper)
{
case SingleResponseWrapper singleResponseWrapper:
context.WithSingleResponse(singleResponseWrapper.Response);
Log.LogTrace("Request id [{requestId}]: success", request.Id);
return new SingleJsonRpcResult<TResponse>(context, HeadersJsonSerializerOptions,
DataJsonSerializerOptions);
default:
var message = $"Expected single response, got [{responseWrapper}]";
Log.LogTrace("Request id [{requestId}] failed: {errorMessage}", request.Id, message);
throw new JsonRpcException(message, context);
}
}

// internal virtual for mocking in tests
internal virtual async Task<IBatchJsonRpcResult?> SendBatchInternal(string? requestUrl, IEnumerable<ICall> calls, CancellationToken cancellationToken)
Expand Down Expand Up @@ -421,8 +466,7 @@ protected internal virtual HttpContent CreateHttpContent(object data)
protected internal virtual async Task<string> GetContent(HttpContent content, CancellationToken cancellationToken) =>
await content.ReadAsStringAsync(cancellationToken);

private async Task<(IJsonRpcCallContext, string)> PrepareInternalRequestContext<TParams>(string? requestUrl, Request<TParams> request, CancellationToken cancellationToken)
where TParams : class
private async Task<(IJsonRpcCallContext, string)> PrepareInternalRequestContext(string? requestUrl, ICall request, CancellationToken cancellationToken)
{
var context = CreateContext();
context.WithRequestUrl(requestUrl);
Expand Down
2 changes: 2 additions & 0 deletions src/Tochka.JsonRpc.Client/PublicAPI.Shipped.txt
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,8 @@ Tochka.JsonRpc.Client.IJsonRpcClient.SendRequest<TParams>(string! requestUrl, To
Tochka.JsonRpc.Client.IJsonRpcClient.SendRequest<TParams>(string! requestUrl, Tochka.JsonRpc.Common.Models.Request.Request<TParams!>! request, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task<Tochka.JsonRpc.Client.Models.SingleResult.ISingleJsonRpcResult!>!
Tochka.JsonRpc.Client.IJsonRpcClient.SendRequest<TParams>(Tochka.JsonRpc.Common.Models.Id.IRpcId! id, string! method, TParams? parameters, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task<Tochka.JsonRpc.Client.Models.SingleResult.ISingleJsonRpcResult!>!
Tochka.JsonRpc.Client.IJsonRpcClient.SendRequest<TParams>(Tochka.JsonRpc.Common.Models.Request.Request<TParams!>! request, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task<Tochka.JsonRpc.Client.Models.SingleResult.ISingleJsonRpcResult!>!
Tochka.JsonRpc.Client.IJsonRpcClient.SendRequest(string! requestUrl, Tochka.JsonRpc.Common.Models.Id.IRpcId! id, string! method, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task<Tochka.JsonRpc.Client.Models.SingleResult.ISingleJsonRpcResult!>!
Tochka.JsonRpc.Client.JsonRpcClientBase.SendRequest(string! requestUrl, Tochka.JsonRpc.Common.Models.Id.IRpcId! id, string! method, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task<Tochka.JsonRpc.Client.Models.SingleResult.ISingleJsonRpcResult!>!
Tochka.JsonRpc.Client.JsonRpcClientBase.SendBatch(string! requestUrl, System.Collections.Generic.IEnumerable<Tochka.JsonRpc.Common.Models.Request.ICall!>! calls, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task<Tochka.JsonRpc.Client.Models.BatchResult.IBatchJsonRpcResult?>!
Tochka.JsonRpc.Client.JsonRpcClientBase.SendBatch(System.Collections.Generic.IEnumerable<Tochka.JsonRpc.Common.Models.Request.ICall!>! calls, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task<Tochka.JsonRpc.Client.Models.BatchResult.IBatchJsonRpcResult?>!
Tochka.JsonRpc.Client.JsonRpcClientBase.SendBatch<TResponse>(string! requestUrl, System.Collections.Generic.IEnumerable<Tochka.JsonRpc.Common.Models.Request.ICall!>! calls, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task<Tochka.JsonRpc.Client.Models.BatchResult.IBatchJsonRpcResult<TResponse>?>!
Expand Down
24 changes: 24 additions & 0 deletions src/Tochka.JsonRpc.Common/Models/Request/Request.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,27 @@ public IUntypedCall WithSerializedParams(JsonSerializerOptions serializerOptions
return new UntypedRequest(Id, Method, serializedParams);
}
}

/// <inheritdoc />
/// <summary>
/// Request with typed params
/// </summary>
/// <param name="Id">Identifier established by the Client</param>
/// <param name="Method">Name of the method to be invoked</param>
/// <param name="Params">Parameter values to be used during the invocation of the method</param>

Check warning on line 41 in src/Tochka.JsonRpc.Common/Models/Request/Request.cs

View workflow job for this annotation

GitHub Actions / test

XML comment has a param tag for 'Params', but there is no parameter by that name
/// <param name="Jsonrpc">Version of the JSON-RPC protocol</param>
[PublicAPI]
[ExcludeFromCodeCoverage]
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);
}
}
9 changes: 9 additions & 0 deletions src/Tochka.JsonRpc.Common/PublicAPI.Shipped.txt
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,15 @@ Tochka.JsonRpc.Common.Models.Request.Request<TParams>.Params.get -> TParams?
Tochka.JsonRpc.Common.Models.Request.Request<TParams>.Params.init -> void
Tochka.JsonRpc.Common.Models.Request.Request<TParams>.Request(Tochka.JsonRpc.Common.Models.Id.IRpcId! Id, string! Method, TParams? Params, string! Jsonrpc = "2.0") -> void
Tochka.JsonRpc.Common.Models.Request.Request<TParams>.WithSerializedParams(System.Text.Json.JsonSerializerOptions! serializerOptions) -> Tochka.JsonRpc.Common.Models.Request.Untyped.IUntypedCall!
Tochka.JsonRpc.Common.Models.Request.Request
Tochka.JsonRpc.Common.Models.Request.Request.Id.get -> Tochka.JsonRpc.Common.Models.Id.IRpcId!
Tochka.JsonRpc.Common.Models.Request.Request.Id.init -> void
Tochka.JsonRpc.Common.Models.Request.Request.Jsonrpc.get -> string!
Tochka.JsonRpc.Common.Models.Request.Request.Jsonrpc.init -> void
Tochka.JsonRpc.Common.Models.Request.Request.Method.get -> string!
Tochka.JsonRpc.Common.Models.Request.Request.Method.init -> void
Tochka.JsonRpc.Common.Models.Request.Request.Request(Tochka.JsonRpc.Common.Models.Id.IRpcId! Id, string! Method, string! Jsonrpc = "2.0") -> void
Tochka.JsonRpc.Common.Models.Request.Request.WithSerializedParams(System.Text.Json.JsonSerializerOptions! serializerOptions) -> Tochka.JsonRpc.Common.Models.Request.Untyped.IUntypedCall!
Tochka.JsonRpc.Common.Models.Request.Untyped.IUntypedCall
Tochka.JsonRpc.Common.Models.Request.Untyped.UntypedNotification
Tochka.JsonRpc.Common.Models.Request.Untyped.UntypedNotification.UntypedNotification(string! Method, System.Text.Json.JsonDocument? Params, string! Jsonrpc = "2.0") -> void
Expand Down
4 changes: 4 additions & 0 deletions src/Tochka.JsonRpc.Common/Tochka.JsonRpc.Common.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -41,4 +41,8 @@
<PackageReference Include="Macross.Json.Extensions" Version="3.0.0" />
</ItemGroup>

<ItemGroup>
<None Remove="PublicAPI.Unshipped.txt" />
</ItemGroup>

</Project>
3 changes: 1 addition & 2 deletions src/Tochka.JsonRpc.Server/Binding/JsonRpcParamsParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,7 @@ public IParseResult Parse(JsonDocument rawCall, JsonDocument? parameters, JsonRp
{
JsonValueKind.Object => ParseObject(parameters!.RootElement, parameterMetadata.PropertyName, bindingStyle),
JsonValueKind.Array => ParseArray(parameters!.RootElement, parameterMetadata.Position, bindingStyle),
null when hasParamsNode => ParseNull(bindingStyle),
null => ParseNoParams(bindingStyle),
null => ParseNull(bindingStyle),
_ => new ErrorParseResult($"Unsupported root JSON value kind: [{jsonValueKind}]", string.Empty)
};
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,48 @@ public async Task SendRequest_SendRequestWithStringId_SerializeSuccessfully()
response.HasError().Should().BeFalse();
}

[Test]
public async Task SendRequest_SendRequestWithoutParamsSerializeSuccessfully()
{
var id = Guid.NewGuid().ToString();
var expectedRequestJson =
$$"""
{
"id": "{{id}}",
"method": "{{Method}}",
"params": null,
"jsonrpc": "2.0"
}
""".TrimAllLines();
var responseBody = JsonDocument.Parse($$"""
{
"id": "{{id}}",
"result": {},
"jsonrpc": "2.0"
}
""");

string actualContentType = null;
string actualRequestJson = null;
requestValidatorMock.Setup(static v => v.Validate(It.IsAny<HttpRequest>()))
.Callback<HttpRequest>(request =>
{
using var streamReader = new StreamReader(request.Body);
actualRequestJson = actualRequestJson = streamReader.ReadToEndAsync().Result.TrimAllLines();
actualContentType = request.ContentType;
});
responseProviderMock.Setup(static p => p.GetResponse())
.Returns(responseBody);

//var response = await camelCaseJsonRpcClient.SendRequest1(RequestUrl, new StringRpcId(id), Method, CancellationToken.None);

var response = await camelCaseJsonRpcClient.SendRequest(RequestUrl, new StringRpcId(id), Method, CancellationToken.None);

actualContentType.Should().Contain("application/json");
actualRequestJson.Should().Be(expectedRequestJson);
response.HasError().Should().BeFalse();
}

[Test]
public async Task SendRequest_SendRequestWithIntId_SerializeSuccessfully()
{
Expand Down
Loading

0 comments on commit d80a3f9

Please sign in to comment.