diff --git a/src/Tochka.JsonRpc.OpenRpc/Services/OpenRpcDocumentGenerator.cs b/src/Tochka.JsonRpc.OpenRpc/Services/OpenRpcDocumentGenerator.cs index e65c095..275c095 100644 --- a/src/Tochka.JsonRpc.OpenRpc/Services/OpenRpcDocumentGenerator.cs +++ b/src/Tochka.JsonRpc.OpenRpc/Services/OpenRpcDocumentGenerator.cs @@ -50,7 +50,7 @@ public Models.OpenRpc Generate(OpenRpcInfo info, string documentName, Uri host) var tags = GetControllersTags(); return new(info) { - Servers = GetServers(host, serverOptions.RoutePrefix.Value), + Servers = GetServers(host), Methods = GetMethods(documentName, host, tags), Components = new() { @@ -60,6 +60,33 @@ public Models.OpenRpc Generate(OpenRpcInfo info, string documentName, Uri host) }; } + // internal virtual for mocking in tests + internal virtual List GetServers(Uri host) + { + var apiDescriptions = apiDescriptionsProvider.ApiDescriptionGroups.Items + .SelectMany(static g => g.Items) + .Where(d => !openRpcOptions.IgnoreObsoleteActions || !IsObsoleteTransitive(d)) + .Where(static d => d.ActionDescriptor.EndpointMetadata.Any(static m => m is JsonRpcControllerAttribute)) + .ToArray(); + + List servers = []; + foreach (var apiDescription in apiDescriptions) + { + var actionDescriptor = apiDescription.ActionDescriptor as ControllerActionDescriptor; + var controllerSummary = actionDescriptor?.ControllerTypeInfo.GetXmlDocsSummary(); + var route = apiDescription.RelativePath?.Split('#').First(); + + if (string.IsNullOrWhiteSpace(route) || $"/{route}" == serverOptions.RoutePrefix) + { + continue; + } + + servers.AddRange(GetServers(host, route, string.IsNullOrEmpty(controllerSummary) ? null : controllerSummary)); + } + + return servers; + } + internal virtual Dictionary GetControllersTags() { var tags = apiDescriptionsProvider.ApiDescriptionGroups.Items @@ -72,10 +99,13 @@ internal virtual Dictionary GetControllersTags() } // internal virtual for mocking in tests - internal virtual List GetServers(Uri host, string? route) + internal virtual List GetServers(Uri host, string? route, string? serverSummary) { var uriBuilder = new UriBuilder(host) { Path = route }; - var server = new OpenRpcServer(openRpcOptions.DefaultServerName, uriBuilder.Uri); + var server = new OpenRpcServer(openRpcOptions.DefaultServerName, uriBuilder.Uri) + { + Summary = serverSummary + }; return new List { @@ -178,13 +208,15 @@ internal virtual OpenRpcContentDescriptor GetResultContentDescriptor(ApiDescript internal virtual List? GetMethodServers(ApiDescription apiDescription, Uri host) { var route = apiDescription.RelativePath?.Split('#').First(); + var actionDescriptor = apiDescription.ActionDescriptor as ControllerActionDescriptor; + var controllerSummary = actionDescriptor?.ControllerTypeInfo.GetXmlDocsSummary(); if (string.IsNullOrWhiteSpace(route) || $"/{route}" == serverOptions.RoutePrefix) { return null; } - return GetServers(host, route); + return GetServers(host, route, string.IsNullOrEmpty(controllerSummary) ? null : controllerSummary); } // internal virtual for mocking in tests diff --git a/src/tests/Tochka.JsonRpc.OpenRpc.Tests/Services/OpenRpcDocumentGeneratorTests.cs b/src/tests/Tochka.JsonRpc.OpenRpc.Tests/Services/OpenRpcDocumentGeneratorTests.cs index 55b45eb..c621f0c 100644 --- a/src/tests/Tochka.JsonRpc.OpenRpc.Tests/Services/OpenRpcDocumentGeneratorTests.cs +++ b/src/tests/Tochka.JsonRpc.OpenRpc.Tests/Services/OpenRpcDocumentGeneratorTests.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Reflection; using System.Text.Json; using FluentAssertions; using JetBrains.Annotations; @@ -20,6 +21,7 @@ using Tochka.JsonRpc.Common.Models.Response; using Tochka.JsonRpc.OpenRpc.Models; using Tochka.JsonRpc.OpenRpc.Services; +using Tochka.JsonRpc.Server; using Tochka.JsonRpc.Server.Attributes; using Tochka.JsonRpc.Server.Metadata; using Tochka.JsonRpc.Server.Serialization; @@ -64,7 +66,15 @@ public void Generate_UseArgsAndInternalMethods() var methods = new List(); var schemas = new Dictionary(); var tags = new Dictionary(); - documentGeneratorMock.Setup(g => g.GetServers(host, serverOptions.RoutePrefix)) + var apiDescription = GetValidDescription(controllerName: "ValidJsonRpc"); + apiDescriptionsProviderMock.Setup(static p => p.ApiDescriptionGroups) + .Returns(new ApiDescriptionGroupCollection(new List + { + new(null, new[] { apiDescription }), + }, + 0)) + .Verifiable(); + documentGeneratorMock.Setup(g => g.GetServers(host, serverOptions.RoutePrefix, null)) .Returns(servers) .Verifiable(); documentGeneratorMock.Setup(g => g.GetMethods(DocumentName, host, tags)) @@ -90,7 +100,7 @@ public void Generate_UseArgsAndInternalMethods() } }; result.Should().BeEquivalentTo(expected); - documentGeneratorMock.Verify(); + apiDescriptionsProviderMock.Verify(); } [Test] @@ -121,6 +131,55 @@ public void GetControllersTagsReturnsTagsWithControllerName() documentGeneratorMock.Verify(); } + [Test] + public void GetServersReturnsAllEndpoints() + { + var apiDescription1 = GetValidDescription(); + var apiDescription2 = GetValidDescription(); + var path = "default/path"; + serverOptions.RoutePrefix = "/"; + apiDescription1.RelativePath = $"{path}#{MethodName}"; + apiDescription2.RelativePath = $"{path}#{MethodName}"; + + apiDescriptionsProviderMock.Setup(static p => p.ApiDescriptionGroups) + .Returns(new ApiDescriptionGroupCollection(new List + { + new(null, new[] { apiDescription1 }), + new(null, new[] { apiDescription2 }), + }, + 0)) + .Verifiable(); + + var result = documentGeneratorMock.Object.GetServers(new Uri(Host)); + + apiDescriptionsProviderMock.Verify(); + documentGeneratorMock.Verify(); + result.Should().HaveCount(2); + } + + [Test] + public void GetServersPathAndRoutePrefixSameReturnsEmptyServers() + { + var apiDescription = GetValidDescription(); + var path = "default/path"; + serverOptions.RoutePrefix = "/default/path"; + apiDescription.RelativePath = $"{path}#{MethodName}"; + + apiDescriptionsProviderMock.Setup(static p => p.ApiDescriptionGroups) + .Returns(new ApiDescriptionGroupCollection(new List + { + new(null, new[] { apiDescription }), + }, + 0)) + .Verifiable(); + + var result = documentGeneratorMock.Object.GetServers(new Uri(Host)); + + apiDescriptionsProviderMock.Verify(); + documentGeneratorMock.Verify(); + result.Should().BeEmpty(); + } + [Test] public void GetMethodTagsReturnNewRefSchemaTag() { @@ -178,7 +237,7 @@ public void GetServers_ReturnsOneServerWithDefaultNameAndUrl() var host = new Uri(Host); var route = "route"; - var result = documentGeneratorMock.Object.GetServers(host, route); + var result = documentGeneratorMock.Object.GetServers(host, route, null); var expected = new OpenRpcServer(openRpcOptions.DefaultServerName, new Uri($"{Host}/{route}")); result.Should().HaveCount(1); @@ -767,9 +826,10 @@ public void GetMethodServers_RelativePathIsDifferent_ReturnServerWithThisRoute() var description = GetValidDescription(); var host = new Uri(Host); var path = "different/path"; - var servers = new List { new("name", new Uri($"{Host}{path}")) }; + var server = new OpenRpcServer("JSON-RPC", new Uri($"{Host}/{path}")); + var servers = new List { server }; description.RelativePath = $"{path}#{MethodName}"; - documentGeneratorMock.Setup(g => g.GetServers(host, path)) + documentGeneratorMock.Setup(g => g.GetServers(host, path, null)) .Returns(servers) .Verifiable(); @@ -923,7 +983,8 @@ public void CombineBindingStyles_BothObjectAndArray_ReturnEither() new JsonRpcControllerAttribute() }, ControllerName = controllerName, - MethodInfo = action?.Method ?? ((Action) ValidMethod).Method + MethodInfo = action?.Method ?? ((Action) ValidMethod).Method, + ControllerTypeInfo = new TypeDelegator(typeof(ValidJsonRpcController)) }, Properties = { @@ -940,6 +1001,13 @@ private static void ValidMethod() { } + private class ValidJsonRpcController : JsonRpcControllerBase + { + public void ValidJsonRpcMethod() + { + } + } + /// summary /// description private static void MethodWithDocs()