diff --git a/AspNetCore.sln b/AspNetCore.sln index 3196580f8afa..c6e50d96b682 100644 --- a/AspNetCore.sln +++ b/AspNetCore.sln @@ -1650,10 +1650,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sandbox", "src\Grpc\JsonTra EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Grpc.JsonTranscoding", "src\Grpc\JsonTranscoding\src\Microsoft.AspNetCore.Grpc.JsonTranscoding\Microsoft.AspNetCore.Grpc.JsonTranscoding.csproj", "{109C702D-DACE-4F82-A490-15E5AFA94005}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Grpc.Swagger", "src\Grpc\JsonTranscoding\src\Microsoft.AspNetCore.Grpc.Swagger\Microsoft.AspNetCore.Grpc.Swagger.csproj", "{E3C5FAD2-8AB7-47C8-AAFD-8262551A5D11}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Grpc.Swagger.Tests", "src\Grpc\JsonTranscoding\test\Microsoft.AspNetCore.Grpc.Swagger.Tests\Microsoft.AspNetCore.Grpc.Swagger.Tests.csproj", "{90CF4DC6-AC53-459F-9EAB-623A11EADAA3}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Grpc.JsonTranscoding.Tests", "src\Grpc\JsonTranscoding\test\Microsoft.AspNetCore.Grpc.JsonTranscoding.Tests\Microsoft.AspNetCore.Grpc.JsonTranscoding.Tests.csproj", "{F18E97AE-3A3F-424D-8DC2-4D001A167F98}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Grpc.JsonTranscoding.IntegrationTests", "src\Grpc\JsonTranscoding\test\Microsoft.AspNetCore.Grpc.JsonTranscoding.IntegrationTests\Microsoft.AspNetCore.Grpc.JsonTranscoding.IntegrationTests.csproj", "{8C3E422A-F281-4B93-A567-88C7A1ED0412}" @@ -9953,38 +9949,6 @@ Global {109C702D-DACE-4F82-A490-15E5AFA94005}.Release|x64.Build.0 = Release|Any CPU {109C702D-DACE-4F82-A490-15E5AFA94005}.Release|x86.ActiveCfg = Release|Any CPU {109C702D-DACE-4F82-A490-15E5AFA94005}.Release|x86.Build.0 = Release|Any CPU - {E3C5FAD2-8AB7-47C8-AAFD-8262551A5D11}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {E3C5FAD2-8AB7-47C8-AAFD-8262551A5D11}.Debug|Any CPU.Build.0 = Debug|Any CPU - {E3C5FAD2-8AB7-47C8-AAFD-8262551A5D11}.Debug|arm64.ActiveCfg = Debug|Any CPU - {E3C5FAD2-8AB7-47C8-AAFD-8262551A5D11}.Debug|arm64.Build.0 = Debug|Any CPU - {E3C5FAD2-8AB7-47C8-AAFD-8262551A5D11}.Debug|x64.ActiveCfg = Debug|Any CPU - {E3C5FAD2-8AB7-47C8-AAFD-8262551A5D11}.Debug|x64.Build.0 = Debug|Any CPU - {E3C5FAD2-8AB7-47C8-AAFD-8262551A5D11}.Debug|x86.ActiveCfg = Debug|Any CPU - {E3C5FAD2-8AB7-47C8-AAFD-8262551A5D11}.Debug|x86.Build.0 = Debug|Any CPU - {E3C5FAD2-8AB7-47C8-AAFD-8262551A5D11}.Release|Any CPU.ActiveCfg = Release|Any CPU - {E3C5FAD2-8AB7-47C8-AAFD-8262551A5D11}.Release|Any CPU.Build.0 = Release|Any CPU - {E3C5FAD2-8AB7-47C8-AAFD-8262551A5D11}.Release|arm64.ActiveCfg = Release|Any CPU - {E3C5FAD2-8AB7-47C8-AAFD-8262551A5D11}.Release|arm64.Build.0 = Release|Any CPU - {E3C5FAD2-8AB7-47C8-AAFD-8262551A5D11}.Release|x64.ActiveCfg = Release|Any CPU - {E3C5FAD2-8AB7-47C8-AAFD-8262551A5D11}.Release|x64.Build.0 = Release|Any CPU - {E3C5FAD2-8AB7-47C8-AAFD-8262551A5D11}.Release|x86.ActiveCfg = Release|Any CPU - {E3C5FAD2-8AB7-47C8-AAFD-8262551A5D11}.Release|x86.Build.0 = Release|Any CPU - {90CF4DC6-AC53-459F-9EAB-623A11EADAA3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {90CF4DC6-AC53-459F-9EAB-623A11EADAA3}.Debug|Any CPU.Build.0 = Debug|Any CPU - {90CF4DC6-AC53-459F-9EAB-623A11EADAA3}.Debug|arm64.ActiveCfg = Debug|Any CPU - {90CF4DC6-AC53-459F-9EAB-623A11EADAA3}.Debug|arm64.Build.0 = Debug|Any CPU - {90CF4DC6-AC53-459F-9EAB-623A11EADAA3}.Debug|x64.ActiveCfg = Debug|Any CPU - {90CF4DC6-AC53-459F-9EAB-623A11EADAA3}.Debug|x64.Build.0 = Debug|Any CPU - {90CF4DC6-AC53-459F-9EAB-623A11EADAA3}.Debug|x86.ActiveCfg = Debug|Any CPU - {90CF4DC6-AC53-459F-9EAB-623A11EADAA3}.Debug|x86.Build.0 = Debug|Any CPU - {90CF4DC6-AC53-459F-9EAB-623A11EADAA3}.Release|Any CPU.ActiveCfg = Release|Any CPU - {90CF4DC6-AC53-459F-9EAB-623A11EADAA3}.Release|Any CPU.Build.0 = Release|Any CPU - {90CF4DC6-AC53-459F-9EAB-623A11EADAA3}.Release|arm64.ActiveCfg = Release|Any CPU - {90CF4DC6-AC53-459F-9EAB-623A11EADAA3}.Release|arm64.Build.0 = Release|Any CPU - {90CF4DC6-AC53-459F-9EAB-623A11EADAA3}.Release|x64.ActiveCfg = Release|Any CPU - {90CF4DC6-AC53-459F-9EAB-623A11EADAA3}.Release|x64.Build.0 = Release|Any CPU - {90CF4DC6-AC53-459F-9EAB-623A11EADAA3}.Release|x86.ActiveCfg = Release|Any CPU - {90CF4DC6-AC53-459F-9EAB-623A11EADAA3}.Release|x86.Build.0 = Release|Any CPU {F18E97AE-3A3F-424D-8DC2-4D001A167F98}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {F18E97AE-3A3F-424D-8DC2-4D001A167F98}.Debug|Any CPU.Build.0 = Debug|Any CPU {F18E97AE-3A3F-424D-8DC2-4D001A167F98}.Debug|arm64.ActiveCfg = Debug|Any CPU @@ -11570,8 +11534,6 @@ Global {2E28881D-A188-47AF-800A-B5877AD8C288} = {9A8AE587-A3DB-4211-8354-430C4CCBEB9B} {A53696E8-6065-41BA-84FB-E89E0DACFF6C} = {9A8AE587-A3DB-4211-8354-430C4CCBEB9B} {109C702D-DACE-4F82-A490-15E5AFA94005} = {151E6F9E-107B-4DDC-A2B1-95115801FD14} - {E3C5FAD2-8AB7-47C8-AAFD-8262551A5D11} = {151E6F9E-107B-4DDC-A2B1-95115801FD14} - {90CF4DC6-AC53-459F-9EAB-623A11EADAA3} = {B43BE3EB-9846-4484-88D8-05165202A0FC} {F18E97AE-3A3F-424D-8DC2-4D001A167F98} = {B43BE3EB-9846-4484-88D8-05165202A0FC} {8C3E422A-F281-4B93-A567-88C7A1ED0412} = {B43BE3EB-9846-4484-88D8-05165202A0FC} {EB14F068-AD55-4970-B9B4-1FBE33704243} = {5CDB8ABC-9DD0-4A9F-8948-EED5FFE89F67} diff --git a/eng/ProjectReferences.props b/eng/ProjectReferences.props index 002b4ffdc5e0..c62163d86db2 100644 --- a/eng/ProjectReferences.props +++ b/eng/ProjectReferences.props @@ -165,7 +165,6 @@ - diff --git a/eng/RequiresDelayedBuildProjects.props b/eng/RequiresDelayedBuildProjects.props index eee64beec6cd..d3cfd72b05d2 100644 --- a/eng/RequiresDelayedBuildProjects.props +++ b/eng/RequiresDelayedBuildProjects.props @@ -14,10 +14,8 @@ - - diff --git a/eng/ShippingAssemblies.props b/eng/ShippingAssemblies.props index bd06923b2454..1a6d2c8bfbea 100644 --- a/eng/ShippingAssemblies.props +++ b/eng/ShippingAssemblies.props @@ -158,7 +158,6 @@ - diff --git a/src/Grpc/Grpc.slnf b/src/Grpc/Grpc.slnf index dc2d96a23df3..e499126f1cd0 100644 --- a/src/Grpc/Grpc.slnf +++ b/src/Grpc/Grpc.slnf @@ -8,10 +8,8 @@ "src\\Grpc\\Interop\\test\\testassets\\InteropWebsite\\InteropWebsite.csproj", "src\\Grpc\\JsonTranscoding\\perf\\Microsoft.AspNetCore.Grpc.Microbenchmarks\\Microsoft.AspNetCore.Grpc.Microbenchmarks.csproj", "src\\Grpc\\JsonTranscoding\\src\\Microsoft.AspNetCore.Grpc.JsonTranscoding\\Microsoft.AspNetCore.Grpc.JsonTranscoding.csproj", - "src\\Grpc\\JsonTranscoding\\src\\Microsoft.AspNetCore.Grpc.Swagger\\Microsoft.AspNetCore.Grpc.Swagger.csproj", "src\\Grpc\\JsonTranscoding\\test\\Microsoft.AspNetCore.Grpc.JsonTranscoding.IntegrationTests\\Microsoft.AspNetCore.Grpc.JsonTranscoding.IntegrationTests.csproj", "src\\Grpc\\JsonTranscoding\\test\\Microsoft.AspNetCore.Grpc.JsonTranscoding.Tests\\Microsoft.AspNetCore.Grpc.JsonTranscoding.Tests.csproj", - "src\\Grpc\\JsonTranscoding\\test\\Microsoft.AspNetCore.Grpc.Swagger.Tests\\Microsoft.AspNetCore.Grpc.Swagger.Tests.csproj", "src\\Grpc\\JsonTranscoding\\test\\testassets\\IntegrationTestsWebsite\\IntegrationTestsWebsite.csproj", "src\\Grpc\\JsonTranscoding\\test\\testassets\\Sandbox\\Sandbox.csproj", "src\\Hosting\\Abstractions\\src\\Microsoft.AspNetCore.Hosting.Abstractions.csproj", diff --git a/src/Grpc/JsonTranscoding/src/Microsoft.AspNetCore.Grpc.Swagger/GrpcSwaggerGenOptionsExtensions.cs b/src/Grpc/JsonTranscoding/src/Microsoft.AspNetCore.Grpc.Swagger/GrpcSwaggerGenOptionsExtensions.cs deleted file mode 100644 index 8c98b75eec33..000000000000 --- a/src/Grpc/JsonTranscoding/src/Microsoft.AspNetCore.Grpc.Swagger/GrpcSwaggerGenOptionsExtensions.cs +++ /dev/null @@ -1,78 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Xml.XPath; -using Microsoft.AspNetCore.Grpc.Swagger.Internal.XmlComments; -using Swashbuckle.AspNetCore.SwaggerGen; - -namespace Microsoft.Extensions.DependencyInjection; - -/// -/// Extension methods for the gRPC JSON transcoding services. -/// -public static class GrpcSwaggerGenOptionsExtensions -{ - /// - /// Inject human-friendly descriptions for Operations, Parameters and Schemas based on XML Comment files - /// - /// - /// A factory method that returns XML Comments as an XPathDocument - public static void IncludeGrpcXmlComments( - this SwaggerGenOptions swaggerGenOptions, - Func xmlDocFactory) - { - swaggerGenOptions.IncludeGrpcXmlComments(xmlDocFactory, includeControllerXmlComments: false); - } - - /// - /// Inject human-friendly descriptions for Operations, Parameters and Schemas based on XML Comment files - /// - /// - /// A factory method that returns XML Comments as an XPathDocument - /// - /// Flag to indicate if controller XML comments (i.e. summary) should be used to assign Tag descriptions. - /// Don't set this flag if you're customizing the default tag for operations via TagActionsBy. - /// - public static void IncludeGrpcXmlComments( - this SwaggerGenOptions swaggerGenOptions, - Func xmlDocFactory, - bool includeControllerXmlComments) - { - var xmlDoc = xmlDocFactory(); - swaggerGenOptions.OperationFilter(xmlDoc); - - if (includeControllerXmlComments) - { - swaggerGenOptions.DocumentFilter(xmlDoc); - } - } - - /// - /// Inject human-friendly descriptions for Operations, Parameters and Schemas based on XML Comment files - /// - /// - /// An absolute path to the file that contains XML Comments - public static void IncludeGrpcXmlComments( - this SwaggerGenOptions swaggerGenOptions, - string filePath) - { - swaggerGenOptions.IncludeGrpcXmlComments(() => new XPathDocument(filePath)); - } - - /// - /// Inject human-friendly descriptions for Operations, Parameters and Schemas based on XML Comment files - /// - /// - /// An absolute path to the file that contains XML Comments - /// - /// Flag to indicate if controller XML comments (i.e. summary) should be used to assign Tag descriptions. - /// Don't set this flag if you're customizing the default tag for operations via TagActionsBy. - /// - public static void IncludeGrpcXmlComments( - this SwaggerGenOptions swaggerGenOptions, - string filePath, - bool includeControllerXmlComments) - { - swaggerGenOptions.IncludeGrpcXmlComments(() => new XPathDocument(filePath), includeControllerXmlComments); - } -} diff --git a/src/Grpc/JsonTranscoding/src/Microsoft.AspNetCore.Grpc.Swagger/GrpcSwaggerServiceExtensions.cs b/src/Grpc/JsonTranscoding/src/Microsoft.AspNetCore.Grpc.Swagger/GrpcSwaggerServiceExtensions.cs deleted file mode 100644 index 8fa484bd770f..000000000000 --- a/src/Grpc/JsonTranscoding/src/Microsoft.AspNetCore.Grpc.Swagger/GrpcSwaggerServiceExtensions.cs +++ /dev/null @@ -1,61 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Text.Json; -using Microsoft.AspNetCore.Grpc.Swagger.Internal; -using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.Abstractions; -using Microsoft.AspNetCore.Mvc.ApiExplorer; -using Microsoft.AspNetCore.Mvc.Infrastructure; -using Microsoft.Extensions.DependencyInjection.Extensions; -using Microsoft.Extensions.Options; -using Swashbuckle.AspNetCore.SwaggerGen; - -namespace Microsoft.Extensions.DependencyInjection; - -/// -/// Extension methods for the gRPC JSON transcoding services. -/// -public static class GrpcSwaggerServiceExtensions -{ - /// - /// Adds gRPC JSON transcoding services to the specified . - /// - /// The for adding services. - /// The so that additional calls can be chained. - public static IServiceCollection AddGrpcSwagger(this IServiceCollection services) - { - ArgumentNullException.ThrowIfNull(services); - - services.AddGrpc().AddJsonTranscoding(); - - services.TryAddEnumerable(ServiceDescriptor.Transient()); - - // Register default description provider in case MVC is not registered - services.TryAddSingleton(serviceProvider => - { - var actionDescriptorCollectionProvider = serviceProvider.GetService(); - var apiDescriptionProvider = serviceProvider.GetServices(); - - return new ApiDescriptionGroupCollectionProvider( - actionDescriptorCollectionProvider ?? new EmptyActionDescriptorCollectionProvider(), - apiDescriptionProvider); - }); - - // Add or replace contract resolver. - services.Replace(ServiceDescriptor.Transient(s => - { - var serializerOptions = s.GetService>()?.Value?.JsonSerializerOptions ?? new JsonSerializerOptions(); - var innerContractResolver = new JsonSerializerDataContractResolver(serializerOptions); - return new GrpcDataContractResolver(innerContractResolver); - })); - - return services; - } - - // Dummy type that is only used if MVC is not registered in the app - private sealed class EmptyActionDescriptorCollectionProvider : IActionDescriptorCollectionProvider - { - public ActionDescriptorCollection ActionDescriptors { get; } = new ActionDescriptorCollection(new List(), 1); - } -} diff --git a/src/Grpc/JsonTranscoding/src/Microsoft.AspNetCore.Grpc.Swagger/Internal/GrpcDataContractResolver.cs b/src/Grpc/JsonTranscoding/src/Microsoft.AspNetCore.Grpc.Swagger/Internal/GrpcDataContractResolver.cs deleted file mode 100644 index 2266e71b1582..000000000000 --- a/src/Grpc/JsonTranscoding/src/Microsoft.AspNetCore.Grpc.Swagger/Internal/GrpcDataContractResolver.cs +++ /dev/null @@ -1,161 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Diagnostics.CodeAnalysis; -using System.Linq; -using System.Reflection; -using Google.Protobuf; -using Google.Protobuf.Reflection; -using Google.Protobuf.WellKnownTypes; -using Grpc.Shared; -using Swashbuckle.AspNetCore.SwaggerGen; -using Type = System.Type; - -namespace Microsoft.AspNetCore.Grpc.Swagger.Internal; - -internal sealed class GrpcDataContractResolver : ISerializerDataContractResolver -{ - private readonly ISerializerDataContractResolver _innerContractResolver; - private readonly Dictionary _messageTypeMapping; - private readonly Dictionary _enumTypeMapping; - - public GrpcDataContractResolver(ISerializerDataContractResolver innerContractResolver) - { - _innerContractResolver = innerContractResolver; - _messageTypeMapping = new Dictionary(); - _enumTypeMapping = new Dictionary(); - } - - public DataContract GetDataContractForType(Type type) - { - if (!_messageTypeMapping.TryGetValue(type, out var messageDescriptor)) - { - if (typeof(IMessage).IsAssignableFrom(type)) - { - var property = type.GetProperty("Descriptor", BindingFlags.Public | BindingFlags.Static); - messageDescriptor = property?.GetValue(null) as MessageDescriptor; - - if (messageDescriptor == null) - { - throw new InvalidOperationException($"Couldn't resolve message descriptor for {type}."); - } - - _messageTypeMapping[type] = messageDescriptor; - } - } - - if (messageDescriptor != null) - { - return ConvertMessage(messageDescriptor); - } - - if (type.IsEnum) - { - if (_enumTypeMapping.TryGetValue(type, out var enumDescriptor)) - { - return DataContract.ForPrimitive(type, DataType.String, dataFormat: null, value => - { - var match = enumDescriptor.Values.SingleOrDefault(v => v.Number == (int)value); - var name = match?.Name ?? value.ToString(); - return @"""" + name + @""""; - }); - } - } - - return _innerContractResolver.GetDataContractForType(type); - } - - private bool TryCustomizeMessage(MessageDescriptor messageDescriptor, [NotNullWhen(true)] out DataContract? dataContract) - { - // The messages serialized here should be kept in sync with SericeDescriptionHelper.IsCustomType. - if (ServiceDescriptorHelpers.IsWellKnownType(messageDescriptor)) - { - if (ServiceDescriptorHelpers.IsWrapperType(messageDescriptor)) - { - var field = messageDescriptor.Fields[Int32Value.ValueFieldNumber]; - - dataContract = _innerContractResolver.GetDataContractForType(MessageDescriptorHelpers.ResolveFieldType(field)); - return true; - } - if (messageDescriptor.FullName == Timestamp.Descriptor.FullName || - messageDescriptor.FullName == Duration.Descriptor.FullName || - messageDescriptor.FullName == FieldMask.Descriptor.FullName) - { - dataContract = DataContract.ForPrimitive(messageDescriptor.ClrType, DataType.String, dataFormat: null); - return true; - } - if (messageDescriptor.FullName == Struct.Descriptor.FullName) - { - dataContract = DataContract.ForObject(messageDescriptor.ClrType, Array.Empty(), extensionDataType: typeof(Value)); - return true; - } - if (messageDescriptor.FullName == ListValue.Descriptor.FullName) - { - dataContract = DataContract.ForArray(messageDescriptor.ClrType, typeof(Value)); - return true; - } - if (messageDescriptor.FullName == Value.Descriptor.FullName) - { - dataContract = DataContract.ForPrimitive(messageDescriptor.ClrType, DataType.Unknown, dataFormat: null); - return true; - } - if (messageDescriptor.FullName == Any.Descriptor.FullName) - { - var anyProperties = new List - { - new DataProperty("@type", typeof(string), isRequired: true) - }; - dataContract = DataContract.ForObject(messageDescriptor.ClrType, anyProperties, extensionDataType: typeof(Value)); - return true; - } - } - - dataContract = null; - return false; - } - - private DataContract ConvertMessage(MessageDescriptor messageDescriptor) - { - if (TryCustomizeMessage(messageDescriptor, out var dataContract)) - { - return dataContract; - } - - var properties = new List(); - - foreach (var field in messageDescriptor.Fields.InFieldNumberOrder()) - { - // Enum type will later be used to call this contract resolver. - // Register the enum type so we know to resolve its names from the descriptor. - if (field.FieldType == FieldType.Enum) - { - _enumTypeMapping.TryAdd(field.EnumType.ClrType, field.EnumType); - } - - Type fieldType; - if (field.IsMap) - { - var mapFields = field.MessageType.Fields.InFieldNumberOrder(); - var valueType = MessageDescriptorHelpers.ResolveFieldType(mapFields[1]); - fieldType = typeof(IDictionary<,>).MakeGenericType(typeof(string), valueType); - } - else if (field.IsRepeated) - { - fieldType = typeof(IList<>).MakeGenericType(MessageDescriptorHelpers.ResolveFieldType(field)); - } - else - { - fieldType = MessageDescriptorHelpers.ResolveFieldType(field); - } - - var propertyName = ServiceDescriptorHelpers.FormatUnderscoreName(field.Name, pascalCase: true, preservePeriod: false); - var propertyInfo = messageDescriptor.ClrType.GetProperty(propertyName); - - properties.Add(new DataProperty(field.JsonName, fieldType, memberInfo: propertyInfo)); - } - - var schema = DataContract.ForObject(messageDescriptor.ClrType, properties: properties); - - return schema; - } -} diff --git a/src/Grpc/JsonTranscoding/src/Microsoft.AspNetCore.Grpc.Swagger/Internal/GrpcJsonTranscodingDescriptionProvider.cs b/src/Grpc/JsonTranscoding/src/Microsoft.AspNetCore.Grpc.Swagger/Internal/GrpcJsonTranscodingDescriptionProvider.cs deleted file mode 100644 index 270411f73924..000000000000 --- a/src/Grpc/JsonTranscoding/src/Microsoft.AspNetCore.Grpc.Swagger/Internal/GrpcJsonTranscodingDescriptionProvider.cs +++ /dev/null @@ -1,197 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Linq; -using System.Text; -using Google.Api; -using Google.Protobuf.Reflection; -using Grpc.AspNetCore.Server; -using Grpc.Shared; -using Microsoft.AspNetCore.Grpc.JsonTranscoding; -using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.Abstractions; -using Microsoft.AspNetCore.Mvc.ApiExplorer; -using Microsoft.AspNetCore.Mvc.Controllers; -using Microsoft.AspNetCore.Mvc.ModelBinding; -using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata; -using Microsoft.AspNetCore.Routing; - -namespace Microsoft.AspNetCore.Grpc.Swagger.Internal; - -internal sealed class GrpcJsonTranscodingDescriptionProvider : IApiDescriptionProvider -{ - private readonly EndpointDataSource _endpointDataSource; - - public GrpcJsonTranscodingDescriptionProvider(EndpointDataSource endpointDataSource) - { - _endpointDataSource = endpointDataSource; - } - - // Executes after ASP.NET Core - public int Order => -900; - - public void OnProvidersExecuting(ApiDescriptionProviderContext context) - { - var endpoints = _endpointDataSource.Endpoints; - - foreach (var endpoint in endpoints) - { - if (endpoint is RouteEndpoint routeEndpoint) - { - var grpcMetadata = endpoint.Metadata.GetMetadata(); - - if (grpcMetadata != null) - { - var httpRule = grpcMetadata.HttpRule; - var methodDescriptor = grpcMetadata.MethodDescriptor; - - if (ServiceDescriptorHelpers.TryResolvePattern(grpcMetadata.HttpRule, out var pattern, out var verb)) - { - var apiDescription = CreateApiDescription(routeEndpoint, httpRule, methodDescriptor, pattern, verb); - - context.Results.Add(apiDescription); - } - } - } - } - } - - private static ApiDescription CreateApiDescription(RouteEndpoint routeEndpoint, HttpRule httpRule, MethodDescriptor methodDescriptor, string pattern, string verb) - { - var apiDescription = new ApiDescription(); - apiDescription.HttpMethod = verb; - apiDescription.ActionDescriptor = new ActionDescriptor - { - RouteValues = new Dictionary - { - // Swagger uses this to group endpoints together. - // Group methods together using the service name. - ["controller"] = methodDescriptor.Service.Name - }, - EndpointMetadata = routeEndpoint.Metadata.ToList() - }; - apiDescription.SupportedRequestFormats.Add(new ApiRequestFormat { MediaType = "application/json" }); - apiDescription.SupportedResponseTypes.Add(new ApiResponseType - { - ApiResponseFormats = { new ApiResponseFormat { MediaType = "application/json" } }, - ModelMetadata = new GrpcModelMetadata(ModelMetadataIdentity.ForType(methodDescriptor.OutputType.ClrType)), - StatusCode = 200 - }); - apiDescription.SupportedResponseTypes.Add(new ApiResponseType - { - ApiResponseFormats = { new ApiResponseFormat { MediaType = "application/json" } }, - ModelMetadata = new GrpcModelMetadata(ModelMetadataIdentity.ForType(typeof(Google.Rpc.Status))), - IsDefaultResponse = true - }); - var explorerSettings = routeEndpoint.Metadata.GetMetadata(); - if (explorerSettings != null) - { - apiDescription.GroupName = explorerSettings.GroupName; - } - - var methodMetadata = routeEndpoint.Metadata.GetMetadata()!; - var httpRoutePattern = HttpRoutePattern.Parse(pattern); - var routeParameters = ServiceDescriptorHelpers.ResolveRouteParameterDescriptors(httpRoutePattern.Variables, methodDescriptor.InputType); - - apiDescription.RelativePath = ResolvePath(httpRoutePattern, routeParameters); - - foreach (var routeParameter in routeParameters) - { - var field = routeParameter.Value.DescriptorsPath.Last(); - var parameterName = ServiceDescriptorHelpers.FormatUnderscoreName(field.Name, pascalCase: true, preservePeriod: false); - var propertyInfo = field.ContainingType.ClrType.GetProperty(parameterName); - - // If from a property, create model as property to get its XML comments. - var identity = propertyInfo != null - ? ModelMetadataIdentity.ForProperty(propertyInfo, MessageDescriptorHelpers.ResolveFieldType(field), field.ContainingType.ClrType) - : ModelMetadataIdentity.ForType(MessageDescriptorHelpers.ResolveFieldType(field)); - - apiDescription.ParameterDescriptions.Add(new ApiParameterDescription - { - Name = routeParameter.Value.JsonPath, - ModelMetadata = new GrpcModelMetadata(identity), - Source = BindingSource.Path, - DefaultValue = string.Empty - }); - } - - var bodyDescriptor = ServiceDescriptorHelpers.ResolveBodyDescriptor(httpRule.Body, methodMetadata.ServiceType, methodDescriptor); - if (bodyDescriptor != null) - { - // If from a property, create model as property to get its XML comments. - var identity = bodyDescriptor.PropertyInfo != null - ? ModelMetadataIdentity.ForProperty(bodyDescriptor.PropertyInfo, bodyDescriptor.PropertyInfo.PropertyType, bodyDescriptor.PropertyInfo.DeclaringType!) - : ModelMetadataIdentity.ForType(bodyDescriptor.Descriptor.ClrType); - - // Or if from a parameter, create model as parameter to get its XML comments. - var parameterDescriptor = bodyDescriptor.ParameterInfo != null - ? new ControllerParameterDescriptor { ParameterInfo = bodyDescriptor.ParameterInfo } - : null; - - apiDescription.ParameterDescriptions.Add(new ApiParameterDescription - { - Name = "Input", - ModelMetadata = new GrpcModelMetadata(identity), - Source = BindingSource.Body, - ParameterDescriptor = parameterDescriptor! - }); - } - - var queryParameters = ServiceDescriptorHelpers.ResolveQueryParameterDescriptors(routeParameters, methodDescriptor, bodyDescriptor?.Descriptor, bodyDescriptor?.FieldDescriptor); - foreach (var queryDescription in queryParameters) - { - var fieldType = MessageDescriptorHelpers.ResolveFieldType(queryDescription.Value); - if (queryDescription.Value.IsRepeated) - { - fieldType = typeof(List<>).MakeGenericType(fieldType); - } - - apiDescription.ParameterDescriptions.Add(new ApiParameterDescription - { - Name = queryDescription.Key, - ModelMetadata = new GrpcModelMetadata(ModelMetadataIdentity.ForType(fieldType)), - Source = BindingSource.Query, - DefaultValue = string.Empty - }); - } - - return apiDescription; - } - - private static string ResolvePath(HttpRoutePattern httpRoutePattern, Dictionary routeParameters) - { - var sb = new StringBuilder(); - for (var i = 0; i < httpRoutePattern.Segments.Count; i++) - { - if (sb.Length > 0) - { - sb.Append('/'); - } - var routeParameter = routeParameters.SingleOrDefault(kvp => kvp.Value.RouteVariable.StartSegment == i).Value; - if (routeParameter != null) - { - sb.Append('{'); - sb.Append(routeParameter.JsonPath); - sb.Append('}'); - - // Skip segments if variable is multiple segment. - i = routeParameter.RouteVariable.EndSegment - 1; - } - else - { - sb.Append(httpRoutePattern.Segments[i]); - } - } - if (httpRoutePattern.Verb != null) - { - sb.Append(':'); - sb.Append(httpRoutePattern.Verb); - } - return sb.ToString(); - } - - public void OnProvidersExecuted(ApiDescriptionProviderContext context) - { - // no-op - } -} diff --git a/src/Grpc/JsonTranscoding/src/Microsoft.AspNetCore.Grpc.Swagger/Internal/GrpcModelMetadata.cs b/src/Grpc/JsonTranscoding/src/Microsoft.AspNetCore.Grpc.Swagger/Internal/GrpcModelMetadata.cs deleted file mode 100644 index 243d39fbde5c..000000000000 --- a/src/Grpc/JsonTranscoding/src/Microsoft.AspNetCore.Grpc.Swagger/Internal/GrpcModelMetadata.cs +++ /dev/null @@ -1,54 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using Microsoft.AspNetCore.Mvc.ModelBinding; -using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata; - -#nullable disable - -namespace Microsoft.AspNetCore.Grpc.Swagger.Internal; - -internal sealed class GrpcModelMetadata : ModelMetadata -{ - public GrpcModelMetadata(ModelMetadataIdentity identity) : base(identity) - { - IsBindingAllowed = true; - } - - public override IReadOnlyDictionary AdditionalValues { get; } - public override string BinderModelName { get; } - public override Type BinderType { get; } - public override BindingSource BindingSource { get; } - public override bool ConvertEmptyStringToNull { get; } - public override string DataTypeName { get; } - public override string Description { get; } - public override string DisplayFormatString { get; } - public override string DisplayName { get; } - public override string EditFormatString { get; } - public override ModelMetadata ElementMetadata { get; } - public override IEnumerable> EnumGroupedDisplayNamesAndValues { get; } - public override IReadOnlyDictionary EnumNamesAndValues { get; } - public override bool HasNonDefaultEditFormat { get; } - public override bool HideSurroundingHtml { get; } - public override bool HtmlEncode { get; } - public override bool IsBindingAllowed { get; } - public override bool IsBindingRequired { get; } - public override bool IsEnum { get; } - public override bool IsFlagsEnum { get; } - public override bool IsReadOnly { get; } - public override bool IsRequired { get; } - public override ModelBindingMessageProvider ModelBindingMessageProvider { get; } - public override string NullDisplayText { get; } - public override int Order { get; } - public override string Placeholder { get; } - public override ModelPropertyCollection Properties { get; } - public override IPropertyFilterProvider PropertyFilterProvider { get; } - public override Func PropertyGetter { get; } - public override Action PropertySetter { get; } - public override bool ShowForDisplay { get; } - public override bool ShowForEdit { get; } - public override string SimpleDisplayProperty { get; } - public override string TemplateHint { get; } - public override bool ValidateChildren { get; } - public override IReadOnlyList ValidatorMetadata { get; } -} diff --git a/src/Grpc/JsonTranscoding/src/Microsoft.AspNetCore.Grpc.Swagger/Internal/MessageDescriptorHelpers.cs b/src/Grpc/JsonTranscoding/src/Microsoft.AspNetCore.Grpc.Swagger/Internal/MessageDescriptorHelpers.cs deleted file mode 100644 index f62924c4a609..000000000000 --- a/src/Grpc/JsonTranscoding/src/Microsoft.AspNetCore.Grpc.Swagger/Internal/MessageDescriptorHelpers.cs +++ /dev/null @@ -1,52 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using Google.Protobuf.Reflection; - -namespace Microsoft.AspNetCore.Grpc.Swagger.Internal; - -internal static class MessageDescriptorHelpers -{ - public static Type ResolveFieldType(FieldDescriptor field) - { - switch (field.FieldType) - { - case FieldType.Double: - return typeof(double); - case FieldType.Float: - return typeof(float); - case FieldType.Int64: - return typeof(long); - case FieldType.UInt64: - return typeof(ulong); - case FieldType.Int32: - return typeof(int); - case FieldType.Fixed64: - return typeof(long); - case FieldType.Fixed32: - return typeof(int); - case FieldType.Bool: - return typeof(bool); - case FieldType.String: - return typeof(string); - case FieldType.Bytes: - return typeof(string); - case FieldType.UInt32: - return typeof(uint); - case FieldType.SFixed32: - return typeof(int); - case FieldType.SFixed64: - return typeof(long); - case FieldType.SInt32: - return typeof(int); - case FieldType.SInt64: - return typeof(long); - case FieldType.Enum: - return field.EnumType.ClrType; - case FieldType.Message: - return field.MessageType.ClrType; - default: - throw new InvalidOperationException("Unexpected field type: " + field.FieldType); - } - } -} diff --git a/src/Grpc/JsonTranscoding/src/Microsoft.AspNetCore.Grpc.Swagger/Internal/XmlComments/GrpcXmlCommentsDocumentFilter.cs b/src/Grpc/JsonTranscoding/src/Microsoft.AspNetCore.Grpc.Swagger/Internal/XmlComments/GrpcXmlCommentsDocumentFilter.cs deleted file mode 100644 index c374ed0be354..000000000000 --- a/src/Grpc/JsonTranscoding/src/Microsoft.AspNetCore.Grpc.Swagger/Internal/XmlComments/GrpcXmlCommentsDocumentFilter.cs +++ /dev/null @@ -1,79 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Globalization; -using System.Linq; -using System.Xml.XPath; -using Grpc.AspNetCore.Server; -using Microsoft.AspNetCore.Mvc.Abstractions; -using Microsoft.OpenApi.Models; -using Swashbuckle.AspNetCore.SwaggerGen; - -namespace Microsoft.AspNetCore.Grpc.Swagger.Internal.XmlComments; - -internal sealed class GrpcXmlCommentsDocumentFilter : IDocumentFilter -{ - private const string MemberXPath = "/doc/members/member[@name='{0}']"; - private const string SummaryTag = "summary"; - - private readonly XPathNavigator _xmlNavigator; - - public GrpcXmlCommentsDocumentFilter(XPathDocument xmlDoc) - { - _xmlNavigator = xmlDoc.CreateNavigator(); - } - - public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context) - { - // Get unique services - var nameAndServiceDescriptor = context.ApiDescriptions - .Select(apiDesc => apiDesc.ActionDescriptor) - .Where(actionDesc => actionDesc != null && (actionDesc.EndpointMetadata?.Any(m => m is GrpcMethodMetadata) ?? false)) - .GroupBy(actionDesc => actionDesc.RouteValues["controller"]!) - .Select(group => new KeyValuePair(group.Key, group.First())); - - foreach (var nameAndType in nameAndServiceDescriptor) - { - var grpcMethodMetadata = nameAndType.Value.EndpointMetadata.OfType().First(); - if (TryAdd(swaggerDoc, nameAndType, grpcMethodMetadata.ServiceType)) - { - continue; - } - - if (grpcMethodMetadata.ServiceType.BaseType?.DeclaringType is { } staticService) - { - if (TryAdd(swaggerDoc, nameAndType, staticService)) - { - continue; - } - } - } - } - - private bool TryAdd(OpenApiDocument swaggerDoc, KeyValuePair nameAndType, Type type) - { - var memberName = XmlCommentsNodeNameHelper.GetMemberNameForType(type); - var typeNode = _xmlNavigator.SelectSingleNode(string.Format(CultureInfo.InvariantCulture, MemberXPath, memberName)); - - if (typeNode != null) - { - var summaryNode = typeNode.SelectSingleNode(SummaryTag); - if (summaryNode != null) - { - if (swaggerDoc.Tags == null) - { - swaggerDoc.Tags = new List(); - } - - swaggerDoc.Tags.Add(new OpenApiTag - { - Name = nameAndType.Key, - Description = XmlCommentsTextHelper.Humanize(summaryNode.InnerXml) - }); - } - return true; - } - - return false; - } -} diff --git a/src/Grpc/JsonTranscoding/src/Microsoft.AspNetCore.Grpc.Swagger/Internal/XmlComments/GrpcXmlCommentsOperationFilter.cs b/src/Grpc/JsonTranscoding/src/Microsoft.AspNetCore.Grpc.Swagger/Internal/XmlComments/GrpcXmlCommentsOperationFilter.cs deleted file mode 100644 index e79eaa477b8b..000000000000 --- a/src/Grpc/JsonTranscoding/src/Microsoft.AspNetCore.Grpc.Swagger/Internal/XmlComments/GrpcXmlCommentsOperationFilter.cs +++ /dev/null @@ -1,111 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Linq; -using System.Reflection; -using System.Xml.XPath; -using Grpc.AspNetCore.Server; -using Microsoft.OpenApi.Models; -using Swashbuckle.AspNetCore.SwaggerGen; - -namespace Microsoft.AspNetCore.Grpc.Swagger.Internal.XmlComments; - -internal sealed class GrpcXmlCommentsOperationFilter : IOperationFilter -{ - private readonly XPathNavigator _xmlNavigator; - - public GrpcXmlCommentsOperationFilter(XPathDocument xmlDoc) - { - _xmlNavigator = xmlDoc.CreateNavigator(); - } - - public void Apply(OpenApiOperation operation, OperationFilterContext context) - { - var grpcMetadata = context.ApiDescription.ActionDescriptor.EndpointMetadata.OfType().FirstOrDefault(); - if (grpcMetadata == null) - { - return; - } - - var methodInfo = grpcMetadata.ServiceType.GetMethod(grpcMetadata.Method.Name); - if (methodInfo == null) - { - return; - } - - // If method is from a constructed generic type, look for comments from the generic type method - var targetMethod = methodInfo.DeclaringType!.IsConstructedGenericType - ? methodInfo.GetUnderlyingGenericTypeMethod() - : methodInfo; - - if (targetMethod == null) - { - return; - } - - // Base service never has response tags. - ApplyServiceTags(operation, targetMethod.DeclaringType!); - - if (TryApplyMethodTags(operation, targetMethod)) - { - return; - } - - if (targetMethod.IsVirtual && targetMethod.GetBaseDefinition() is { } baseMethod) - { - if (TryApplyMethodTags(operation, baseMethod)) - { - return; - } - } - } - - private void ApplyServiceTags(OpenApiOperation operation, Type controllerType) - { - var typeMemberName = XmlCommentsNodeNameHelper.GetMemberNameForType(controllerType); - var responseNodes = _xmlNavigator.Select($"/doc/members/member[@name='{typeMemberName}']/response"); - ApplyResponseTags(operation, responseNodes); - } - - private bool TryApplyMethodTags(OpenApiOperation operation, MethodInfo methodInfo) - { - var methodMemberName = XmlCommentsNodeNameHelper.GetMemberNameForMethod(methodInfo); - var methodNode = _xmlNavigator.SelectSingleNode($"/doc/members/member[@name='{methodMemberName}']"); - - if (methodNode == null) - { - return false; - } - - var summaryNode = methodNode.SelectSingleNode("summary"); - if (summaryNode != null) - { - operation.Summary = XmlCommentsTextHelper.Humanize(summaryNode.InnerXml); - } - - var remarksNode = methodNode.SelectSingleNode("remarks"); - if (remarksNode != null) - { - operation.Description = XmlCommentsTextHelper.Humanize(remarksNode.InnerXml); - } - - var responseNodes = methodNode.Select("response"); - ApplyResponseTags(operation, responseNodes); - - return true; - } - - private static void ApplyResponseTags(OpenApiOperation operation, XPathNodeIterator responseNodes) - { - while (responseNodes.MoveNext()) - { - var code = responseNodes.Current!.GetAttribute("code", ""); - if (!operation.Responses.TryGetValue(code, out var response)) - { - operation.Responses[code] = response = new OpenApiResponse(); - } - - response.Description = XmlCommentsTextHelper.Humanize(responseNodes.Current.InnerXml); - } - } -} diff --git a/src/Grpc/JsonTranscoding/src/Microsoft.AspNetCore.Grpc.Swagger/Microsoft.AspNetCore.Grpc.Swagger.csproj b/src/Grpc/JsonTranscoding/src/Microsoft.AspNetCore.Grpc.Swagger/Microsoft.AspNetCore.Grpc.Swagger.csproj deleted file mode 100644 index 0d0d5b3f381b..000000000000 --- a/src/Grpc/JsonTranscoding/src/Microsoft.AspNetCore.Grpc.Swagger/Microsoft.AspNetCore.Grpc.Swagger.csproj +++ /dev/null @@ -1,20 +0,0 @@ - - - Swagger for gRPC ASP.NET Core - gRPC RPC HTTP/2 REST Swagger OpenAPI - $(DefaultNetCoreTargetFramework) - $(ExperimentalVersionPrefix) - - - - - - - - - - - - - - diff --git a/src/Grpc/JsonTranscoding/src/Microsoft.AspNetCore.Grpc.Swagger/PublicAPI.Shipped.txt b/src/Grpc/JsonTranscoding/src/Microsoft.AspNetCore.Grpc.Swagger/PublicAPI.Shipped.txt deleted file mode 100644 index 29264107cc11..000000000000 --- a/src/Grpc/JsonTranscoding/src/Microsoft.AspNetCore.Grpc.Swagger/PublicAPI.Shipped.txt +++ /dev/null @@ -1,8 +0,0 @@ -#nullable enable -Microsoft.Extensions.DependencyInjection.GrpcSwaggerGenOptionsExtensions -Microsoft.Extensions.DependencyInjection.GrpcSwaggerServiceExtensions -static Microsoft.Extensions.DependencyInjection.GrpcSwaggerGenOptionsExtensions.IncludeGrpcXmlComments(this Swashbuckle.AspNetCore.SwaggerGen.SwaggerGenOptions! swaggerGenOptions, string! filePath) -> void -static Microsoft.Extensions.DependencyInjection.GrpcSwaggerGenOptionsExtensions.IncludeGrpcXmlComments(this Swashbuckle.AspNetCore.SwaggerGen.SwaggerGenOptions! swaggerGenOptions, string! filePath, bool includeControllerXmlComments) -> void -static Microsoft.Extensions.DependencyInjection.GrpcSwaggerGenOptionsExtensions.IncludeGrpcXmlComments(this Swashbuckle.AspNetCore.SwaggerGen.SwaggerGenOptions! swaggerGenOptions, System.Func! xmlDocFactory) -> void -static Microsoft.Extensions.DependencyInjection.GrpcSwaggerGenOptionsExtensions.IncludeGrpcXmlComments(this Swashbuckle.AspNetCore.SwaggerGen.SwaggerGenOptions! swaggerGenOptions, System.Func! xmlDocFactory, bool includeControllerXmlComments) -> void -static Microsoft.Extensions.DependencyInjection.GrpcSwaggerServiceExtensions.AddGrpcSwagger(this Microsoft.Extensions.DependencyInjection.IServiceCollection! services) -> Microsoft.Extensions.DependencyInjection.IServiceCollection! diff --git a/src/Grpc/JsonTranscoding/src/Microsoft.AspNetCore.Grpc.Swagger/PublicAPI.Unshipped.txt b/src/Grpc/JsonTranscoding/src/Microsoft.AspNetCore.Grpc.Swagger/PublicAPI.Unshipped.txt deleted file mode 100644 index 7dc5c58110bf..000000000000 --- a/src/Grpc/JsonTranscoding/src/Microsoft.AspNetCore.Grpc.Swagger/PublicAPI.Unshipped.txt +++ /dev/null @@ -1 +0,0 @@ -#nullable enable diff --git a/src/Grpc/JsonTranscoding/test/Microsoft.AspNetCore.Grpc.Swagger.Tests/Binding/BodyTests.cs b/src/Grpc/JsonTranscoding/test/Microsoft.AspNetCore.Grpc.Swagger.Tests/Binding/BodyTests.cs deleted file mode 100644 index 992c40007b04..000000000000 --- a/src/Grpc/JsonTranscoding/test/Microsoft.AspNetCore.Grpc.Swagger.Tests/Binding/BodyTests.cs +++ /dev/null @@ -1,82 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using Microsoft.AspNetCore.Grpc.Swagger.Tests.Infrastructure; -using Microsoft.AspNetCore.Grpc.Swagger.Tests.Services; -using Microsoft.OpenApi.Models; -using Xunit.Abstractions; - -namespace Microsoft.AspNetCore.Grpc.Swagger.Tests.Binding; - -public class BodyTests -{ - private readonly ITestOutputHelper _testOutputHelper; - - public BodyTests(ITestOutputHelper testOutputHelper) - { - _testOutputHelper = testOutputHelper; - } - - [Fact] - public void PostRepeated() - { - // Arrange & Act - var swagger = OpenApiTestHelpers.GetOpenApiDocument(_testOutputHelper); - - // Assert - var path = swagger.Paths["/v1/body1"]; - Assert.True(path.Operations.TryGetValue(OperationType.Post, out var operation)); - - var bodySchema = operation.RequestBody.Content["application/json"].Schema; - Assert.Null(bodySchema.Reference); - Assert.Equal("array", bodySchema.Type); - Assert.Equal("RequestBody", bodySchema.Items.Reference.Id); - - var messageSchema = swagger.ResolveReference(bodySchema.Items.Reference); - Assert.NotNull(messageSchema); - } - - [Fact] - public void PostMap() - { - // Arrange & Act - var swagger = OpenApiTestHelpers.GetOpenApiDocument(_testOutputHelper); - - // Assert - var path = swagger.Paths["/v1/body2"]; - Assert.True(path.Operations.TryGetValue(OperationType.Post, out var operation)); - - var bodySchema = operation.RequestBody.Content["application/json"].Schema; - Assert.Null(bodySchema.Reference); - Assert.Equal("object", bodySchema.Type); - Assert.Equal("integer", bodySchema.AdditionalProperties.Type); - } - - [Fact] - public void PostMessage() - { - // Arrange & Act - var swagger = OpenApiTestHelpers.GetOpenApiDocument(_testOutputHelper); - - // Assert - var path = swagger.Paths["/v1/body3"]; - Assert.True(path.Operations.TryGetValue(OperationType.Post, out var operation)); - - var bodySchema = operation.RequestBody.Content["application/json"].Schema; - Assert.Equal("RequestBody", bodySchema.Reference.Id); - } - - [Fact] - public void PostRoot() - { - // Arrange & Act - var swagger = OpenApiTestHelpers.GetOpenApiDocument(_testOutputHelper); - - // Assert - var path = swagger.Paths["/v1/body4"]; - Assert.True(path.Operations.TryGetValue(OperationType.Post, out var operation)); - - var bodySchema = operation.RequestBody.Content["application/json"].Schema; - Assert.Equal("RequestOne", bodySchema.Reference.Id); - } -} diff --git a/src/Grpc/JsonTranscoding/test/Microsoft.AspNetCore.Grpc.Swagger.Tests/Binding/ParametersTests.cs b/src/Grpc/JsonTranscoding/test/Microsoft.AspNetCore.Grpc.Swagger.Tests/Binding/ParametersTests.cs deleted file mode 100644 index 8a4db851175f..000000000000 --- a/src/Grpc/JsonTranscoding/test/Microsoft.AspNetCore.Grpc.Swagger.Tests/Binding/ParametersTests.cs +++ /dev/null @@ -1,186 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using Microsoft.AspNetCore.Grpc.Swagger.Tests.Infrastructure; -using Microsoft.AspNetCore.Grpc.Swagger.Tests.Services; -using Microsoft.OpenApi.Models; -using Xunit.Abstractions; - -namespace Microsoft.AspNetCore.Grpc.Swagger.Tests.Binding; - -public class ParametersTests -{ - private readonly ITestOutputHelper _testOutputHelper; - - public ParametersTests(ITestOutputHelper testOutputHelper) - { - _testOutputHelper = testOutputHelper; - } - - [Fact] - public void NoRouteOrBody_AllQueryFields() - { - // Arrange & Act - var swagger = OpenApiTestHelpers.GetOpenApiDocument(_testOutputHelper); - - // Assert - var path = swagger.Paths["/v1/parameters1"]; - Assert.True(path.Operations.TryGetValue(OperationType.Get, out var operation)); - Assert.Equal(2, operation.Parameters.Count); - Assert.Equal(ParameterLocation.Query, operation.Parameters[0].In); - Assert.Equal("parameterInt", operation.Parameters[0].Name); - Assert.Equal(ParameterLocation.Query, operation.Parameters[1].In); - Assert.Equal("parameterString", operation.Parameters[1].Name); - } - - [Fact] - public void RouteFields_FilterRouteQueryFields() - { - // Arrange & Act - var swagger = OpenApiTestHelpers.GetOpenApiDocument(_testOutputHelper); - - // Assert - var path = swagger.Paths["/v1/parameters2/{parameterInt}"]; - Assert.True(path.Operations.TryGetValue(OperationType.Get, out var operation)); - Assert.Equal(2, operation.Parameters.Count); - Assert.Equal(ParameterLocation.Path, operation.Parameters[0].In); - Assert.Equal("parameterInt", operation.Parameters[0].Name); - Assert.Equal(ParameterLocation.Query, operation.Parameters[1].In); - Assert.Equal("parameterString", operation.Parameters[1].Name); - } - - [Fact] - public void RouteAndBodyFields_FilterRouteAndBodyQueryFields() - { - // Arrange & Act - var swagger = OpenApiTestHelpers.GetOpenApiDocument(_testOutputHelper); - - // Assert - var path = swagger.Paths["/v1/parameters3/{parameterOne}"]; - Assert.True(path.Operations.TryGetValue(OperationType.Post, out var operation)); - Assert.Equal(3, operation.Parameters.Count); - Assert.Equal(ParameterLocation.Path, operation.Parameters[0].In); - Assert.Equal("parameterOne", operation.Parameters[0].Name); - Assert.Equal(ParameterLocation.Query, operation.Parameters[1].In); - Assert.Equal("parameterTwo", operation.Parameters[1].Name); - Assert.Equal(ParameterLocation.Query, operation.Parameters[2].In); - Assert.Equal("parameterThree", operation.Parameters[2].Name); - // body with one parameter - Assert.NotNull(operation.RequestBody); - Assert.Equal(1, swagger.Components.Schemas["RequestBody"].Properties.Count); - } - - [Fact] - public void CatchAllBody_NoQueryFields() - { - // Arrange & Act - var swagger = OpenApiTestHelpers.GetOpenApiDocument(_testOutputHelper); - - // Assert - var path = swagger.Paths["/v1/parameters4/{parameterTwo}"]; - Assert.True(path.Operations.TryGetValue(OperationType.Post, out var operation)); - Assert.Equal(1, operation.Parameters.Count); - Assert.Equal(ParameterLocation.Path, operation.Parameters[0].In); - Assert.Equal("parameterTwo", operation.Parameters[0].Name); - // body with four parameters - Assert.NotNull(operation.RequestBody); - Assert.Equal(4, swagger.Components.Schemas["RequestTwo"].Properties.Count); - } - - [Fact] - public void NoBodyComplexType_NestedQueryField() - { - // Arrange & Act - var swagger = OpenApiTestHelpers.GetOpenApiDocument(_testOutputHelper); - - // Assert - var path = swagger.Paths["/v1/parameters5/{parameterOne}"]; - Assert.True(path.Operations.TryGetValue(OperationType.Get, out var operation)); - Assert.Equal(4, operation.Parameters.Count); - Assert.Equal(ParameterLocation.Query, operation.Parameters[3].In); - Assert.Equal("parameterFour.requestBody", operation.Parameters[3].Name); - } - - [Fact] - public void RepeatedStringField_ArrayQueryField() - { - // Arrange & Act - var swagger = OpenApiTestHelpers.GetOpenApiDocument(_testOutputHelper); - - // Assert - var path = swagger.Paths["/v1/parameters6"]; - Assert.True(path.Operations.TryGetValue(OperationType.Get, out var operation)); - Assert.Equal(1, operation.Parameters.Count); - Assert.Equal(ParameterLocation.Query, operation.Parameters[0].In); - Assert.Equal("parameterOne", operation.Parameters[0].Name); - Assert.Equal("array", operation.Parameters[0].Schema.Type); - Assert.Equal("integer", operation.Parameters[0].Schema.Items.Type); - } - - [Fact] - public void MultipleRouteParameter_NestedFields_MissingFieldsAreQuery() - { - // Arrange & Act - var swagger = OpenApiTestHelpers.GetOpenApiDocument(_testOutputHelper); - - // Assert - var path = swagger.Paths["/v1/parameters7/{parameterOne.nestedParameterOne}/{parameterOne.nestedParameterTwo}"]; - Assert.True(path.Operations.TryGetValue(OperationType.Get, out var operation)); - Assert.Equal(5, operation.Parameters.Count); - Assert.Equal(ParameterLocation.Path, operation.Parameters[0].In); - Assert.Equal("parameterOne.nestedParameterOne", operation.Parameters[0].Name); - Assert.Equal(ParameterLocation.Path, operation.Parameters[1].In); - Assert.Equal("parameterOne.nestedParameterTwo", operation.Parameters[1].Name); - Assert.Equal(ParameterLocation.Query, operation.Parameters[2].In); - Assert.Equal("parameterOne.nestedParameterThree", operation.Parameters[2].Name); - Assert.Equal(ParameterLocation.Query, operation.Parameters[3].In); - Assert.Equal("parameterOne.nestedParameterFour", operation.Parameters[3].Name); - Assert.Equal(ParameterLocation.Query, operation.Parameters[4].In); - Assert.Equal("parameterTwo", operation.Parameters[4].Name); - } - - [Fact] - public void KnownTypes_AllQueryFields() - { - // Arrange & Act - var swagger = OpenApiTestHelpers.GetOpenApiDocument(_testOutputHelper); - - // Assert - var path = swagger.Paths["/v1/parameters9"]; - Assert.True(path.Operations.TryGetValue(OperationType.Get, out var operation)); - Assert.Equal(3, operation.Parameters.Count); - Assert.Equal(ParameterLocation.Query, operation.Parameters[0].In); - Assert.Equal("fieldMaskValue", operation.Parameters[0].Name); - Assert.Equal("string", operation.Parameters[0].Schema.Type); - Assert.Equal(ParameterLocation.Query, operation.Parameters[1].In); - Assert.Equal("stringValue", operation.Parameters[1].Name); - Assert.Equal("string", operation.Parameters[1].Schema.Type); - Assert.Equal(ParameterLocation.Query, operation.Parameters[2].In); - Assert.Equal("int32Value", operation.Parameters[2].Name); - Assert.Equal("integer", operation.Parameters[2].Schema.Type); - Assert.Equal("int32", operation.Parameters[2].Schema.Format); - } - - [Fact] - public void Verb() - { - // Arrange & Act - var swagger = OpenApiTestHelpers.GetOpenApiDocument(_testOutputHelper); - - // Assert - var path1 = swagger.Paths["/v1/parameters10/{parameterInt}:one"]; - AssertParams(path1); - var path2 = swagger.Paths["/v1/parameters10/{parameterInt}:two"]; - AssertParams(path2); - - static void AssertParams(OpenApiPathItem path) - { - Assert.True(path.Operations.TryGetValue(OperationType.Get, out var operation)); - Assert.Equal(2, operation.Parameters.Count); - Assert.Equal(ParameterLocation.Path, operation.Parameters[0].In); - Assert.Equal("parameterInt", operation.Parameters[0].Name); - Assert.Equal(ParameterLocation.Query, operation.Parameters[1].In); - Assert.Equal("parameterString", operation.Parameters[1].Name); - } - } -} diff --git a/src/Grpc/JsonTranscoding/test/Microsoft.AspNetCore.Grpc.Swagger.Tests/GrpcSwaggerServiceExtensionsTests.cs b/src/Grpc/JsonTranscoding/test/Microsoft.AspNetCore.Grpc.Swagger.Tests/GrpcSwaggerServiceExtensionsTests.cs deleted file mode 100644 index 6cffa4f6ed8d..000000000000 --- a/src/Grpc/JsonTranscoding/test/Microsoft.AspNetCore.Grpc.Swagger.Tests/GrpcSwaggerServiceExtensionsTests.cs +++ /dev/null @@ -1,99 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using Count; -using Greet; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Grpc.Swagger.Tests.Infrastructure; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.OpenApi.Models; -using Swashbuckle.AspNetCore.Swagger; - -namespace Microsoft.AspNetCore.Grpc.Swagger.Tests; - -public class GrpcSwaggerServiceExtensionsTests -{ - [Fact] - public void AddGrpcSwagger_GrpcServiceRegistered_ReturnSwaggerWithGrpcOperation() - { - // Arrange & Act - var services = new ServiceCollection(); - services.AddGrpcSwagger(); - services.AddSwaggerGen(c => - { - c.SwaggerDoc("v1", new OpenApiInfo { Title = "My API", Version = "v1" }); - }); - services.AddRouting(); - services.AddLogging(); - services.AddSingleton(); - var serviceProvider = services.BuildServiceProvider(); - var app = new ApplicationBuilder(serviceProvider); - - app.UseRouting(); - app.UseEndpoints(c => - { - c.MapGrpcService(); - }); - - var swaggerGenerator = serviceProvider.GetRequiredService(); - var swagger = swaggerGenerator.GetSwagger("v1"); - - // Assert - Assert.NotNull(swagger); - Assert.Single(swagger.Paths); - - var path = swagger.Paths["/v1/greeter/{name}"]; - Assert.True(path.Operations.TryGetValue(OperationType.Get, out var operation)); - Assert.Equal("OK", operation.Responses["200"].Description); - Assert.Equal("Error", operation.Responses["default"].Description); - } - - [Fact] - public void AddGrpcSwagger_GrpcServiceWithGroupName_FilteredByGroup() - { - // Arrange & Act - var services = new ServiceCollection(); - services.AddGrpcSwagger(); - services.AddSwaggerGen(c => - { - c.SwaggerDoc("v1", new OpenApiInfo { Title = "My API", Version = "v1" }); - c.SwaggerDoc("v2", new OpenApiInfo { Title = "My API", Version = "v2" }); - }); - services.AddRouting(); - services.AddLogging(); - services.AddSingleton(); - var serviceProvider = services.BuildServiceProvider(); - var app = new ApplicationBuilder(serviceProvider); - - app.UseRouting(); - app.UseEndpoints(c => - { - c.MapGrpcService(); - c.MapGrpcService(); - }); - - var swaggerGenerator = serviceProvider.GetRequiredService(); - - // Assert 1 - var swagger = swaggerGenerator.GetSwagger("v1"); - Assert.Single(swagger.Paths); - Assert.True(swagger.Paths["/v1/greeter/{name}"].Operations.ContainsKey(OperationType.Get)); - - // Assert 2 - swagger = swaggerGenerator.GetSwagger("v2"); - Assert.Equal(2, swagger.Paths.Count); - Assert.True(swagger.Paths["/v1/greeter/{name}"].Operations.ContainsKey(OperationType.Get)); - Assert.True(swagger.Paths["/v1/add/{value1}/{value2}"].Operations.ContainsKey(OperationType.Get)); - } - - private class GreeterService : Greeter.GreeterBase - { - } - - [ApiExplorerSettings(GroupName = "v2")] - private class CounterService : Counter.CounterBase - { - } -} diff --git a/src/Grpc/JsonTranscoding/test/Microsoft.AspNetCore.Grpc.Swagger.Tests/Infrastructure/OpenApiTestHelpers.cs b/src/Grpc/JsonTranscoding/test/Microsoft.AspNetCore.Grpc.Swagger.Tests/Infrastructure/OpenApiTestHelpers.cs deleted file mode 100644 index f17eff428e0f..000000000000 --- a/src/Grpc/JsonTranscoding/test/Microsoft.AspNetCore.Grpc.Swagger.Tests/Infrastructure/OpenApiTestHelpers.cs +++ /dev/null @@ -1,49 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.OpenApi.Models; -using Microsoft.OpenApi.Writers; -using Swashbuckle.AspNetCore.Swagger; -using Xunit.Abstractions; - -namespace Microsoft.AspNetCore.Grpc.Swagger.Tests.Infrastructure; - -internal static class OpenApiTestHelpers -{ - public static OpenApiDocument GetOpenApiDocument(ITestOutputHelper testOutputHelper) where TService : class - { - var services = new ServiceCollection(); - services.AddGrpcSwagger(); - services.AddSwaggerGen(c => - { - c.SwaggerDoc("v1", new OpenApiInfo { Title = "My API", Version = "v1" }); - - var filePath = Path.Combine(System.AppContext.BaseDirectory, "Microsoft.AspNetCore.Grpc.Swagger.Tests.xml"); - c.IncludeXmlComments(filePath); - c.IncludeGrpcXmlComments(filePath, includeControllerXmlComments: true); - }); - services.AddRouting(); - services.AddLogging(); - services.AddSingleton(); - var serviceProvider = services.BuildServiceProvider(); - var app = new ApplicationBuilder(serviceProvider); - - app.UseRouting(); - app.UseEndpoints(c => - { - c.MapGrpcService(); - }); - - var swaggerGenerator = serviceProvider.GetRequiredService(); - var swagger = swaggerGenerator.GetSwagger("v1"); - - using var outputString = new StringWriter(); - swagger.SerializeAsV3(new OpenApiJsonWriter(outputString)); - testOutputHelper.WriteLine(outputString.ToString()); - - return swagger; - } -} diff --git a/src/Grpc/JsonTranscoding/test/Microsoft.AspNetCore.Grpc.Swagger.Tests/Infrastructure/TestWebHostEnvironment.cs b/src/Grpc/JsonTranscoding/test/Microsoft.AspNetCore.Grpc.Swagger.Tests/Infrastructure/TestWebHostEnvironment.cs deleted file mode 100644 index ecb8a5e089d6..000000000000 --- a/src/Grpc/JsonTranscoding/test/Microsoft.AspNetCore.Grpc.Swagger.Tests/Infrastructure/TestWebHostEnvironment.cs +++ /dev/null @@ -1,17 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.FileProviders; - -namespace Microsoft.AspNetCore.Grpc.Swagger.Tests.Infrastructure; - -internal class TestWebHostEnvironment : IWebHostEnvironment -{ - public IFileProvider WebRootFileProvider { get; set; } - public string WebRootPath { get; set; } - public string ApplicationName { get; set; } - public IFileProvider ContentRootFileProvider { get; set; } - public string ContentRootPath { get; set; } - public string EnvironmentName { get; set; } -} diff --git a/src/Grpc/JsonTranscoding/test/Microsoft.AspNetCore.Grpc.Swagger.Tests/Microsoft.AspNetCore.Grpc.Swagger.Tests.csproj b/src/Grpc/JsonTranscoding/test/Microsoft.AspNetCore.Grpc.Swagger.Tests/Microsoft.AspNetCore.Grpc.Swagger.Tests.csproj deleted file mode 100644 index 41247608140b..000000000000 --- a/src/Grpc/JsonTranscoding/test/Microsoft.AspNetCore.Grpc.Swagger.Tests/Microsoft.AspNetCore.Grpc.Swagger.Tests.csproj +++ /dev/null @@ -1,19 +0,0 @@ - - - $(DefaultNetCoreTargetFramework) - true - $(NoWarn);CS1591 - - - - - - - - - - - - - - diff --git a/src/Grpc/JsonTranscoding/test/Microsoft.AspNetCore.Grpc.Swagger.Tests/Proto/body.proto b/src/Grpc/JsonTranscoding/test/Microsoft.AspNetCore.Grpc.Swagger.Tests/Proto/body.proto deleted file mode 100644 index 054b538ed271..000000000000 --- a/src/Grpc/JsonTranscoding/test/Microsoft.AspNetCore.Grpc.Swagger.Tests/Proto/body.proto +++ /dev/null @@ -1,54 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -syntax = "proto3"; - -package body; - -import "google/api/annotations.proto"; - -// Add go_package to keep protoc happy when testing generating OpenAPI from commandline. -option go_package = "github.com/dotnet/aspnetcore/swagger"; - -// HttpRule: https://cloud.google.com/endpoints/docs/grpc-service-config/reference/rpc/google.api#google.api.HttpRule - -service Body { - rpc DemoBodyOne (RequestOne) returns (BodyParamResponse) { - option (google.api.http) = { - post: "/v1/body1", - body: "parameter_one" - }; - } - rpc DemoBodyTwo (RequestOne) returns (BodyParamResponse) { - option (google.api.http) = { - post: "/v1/body2", - body: "parameter_two" - }; - } - rpc DemoBodyThree (RequestOne) returns (BodyParamResponse) { - option (google.api.http) = { - post: "/v1/body3", - body: "parameter_three" - }; - } - rpc DemoBodyFour (RequestOne) returns (BodyParamResponse) { - option (google.api.http) = { - post: "/v1/body4", - body: "*" - }; - } -} - -message RequestOne { - repeated RequestBody parameter_one = 1; - map parameter_two = 2; - RequestBody parameter_three = 3; -} - -message RequestBody { - string request_body = 1; -} - -message BodyParamResponse { - string message = 1; -} diff --git a/src/Grpc/JsonTranscoding/test/Microsoft.AspNetCore.Grpc.Swagger.Tests/Proto/counter.proto b/src/Grpc/JsonTranscoding/test/Microsoft.AspNetCore.Grpc.Swagger.Tests/Proto/counter.proto deleted file mode 100644 index 4e9bfc41f8c7..000000000000 --- a/src/Grpc/JsonTranscoding/test/Microsoft.AspNetCore.Grpc.Swagger.Tests/Proto/counter.proto +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -syntax = "proto3"; - -import "google/api/annotations.proto"; - -package count; - -service Counter { - rpc Add (CountRequest) returns (CountReply) { - option (google.api.http) = { - get: "/v1/add/{value1}/{value2}" - }; - } -} - -message CountRequest { - int32 value1 = 1; - int32 value2 = 2; -} - -message CountReply { - int32 result = 1; -} diff --git a/src/Grpc/JsonTranscoding/test/Microsoft.AspNetCore.Grpc.Swagger.Tests/Proto/greeter.proto b/src/Grpc/JsonTranscoding/test/Microsoft.AspNetCore.Grpc.Swagger.Tests/Proto/greeter.proto deleted file mode 100644 index 22295f3d9026..000000000000 --- a/src/Grpc/JsonTranscoding/test/Microsoft.AspNetCore.Grpc.Swagger.Tests/Proto/greeter.proto +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -syntax = "proto3"; - -import "google/api/annotations.proto"; - -package greet; - -// Greeter! -service Greeter { - // SayHello! - rpc SayHello (HelloRequest) returns (HelloReply) { - option (google.api.http) = { - get: "/v1/greeter/{name}" - }; - } -} - -// HelloRequest! -message HelloRequest { - // Name! - string name = 1; -} - -// HelloReply! -message HelloReply { - // Message! - string message = 1; -} diff --git a/src/Grpc/JsonTranscoding/test/Microsoft.AspNetCore.Grpc.Swagger.Tests/Proto/messages.proto b/src/Grpc/JsonTranscoding/test/Microsoft.AspNetCore.Grpc.Swagger.Tests/Proto/messages.proto deleted file mode 100644 index 9bd40f84a80b..000000000000 --- a/src/Grpc/JsonTranscoding/test/Microsoft.AspNetCore.Grpc.Swagger.Tests/Proto/messages.proto +++ /dev/null @@ -1,109 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -syntax = "proto3"; - -import "google/protobuf/wrappers.proto"; -import "google/protobuf/field_mask.proto"; - -package messages; - -message HelloRequest { - message SubMessage { - string subfield = 1; - repeated string subfields = 2; - } - message DataTypes { - enum NestedEnum { - NESTED_ENUM_UNSPECIFIED = 0; - FOO = 1; - BAR = 2; - BAZ = 3; - NEG = -1; // Intentionally negative. - } - message NestedMessage { - string subfield = 1; - } - int32 single_int32 = 1; - int64 single_int64 = 2; - uint32 single_uint32 = 3; - uint64 single_uint64 = 4; - sint32 single_sint32 = 5; - sint64 single_sint64 = 6; - fixed32 single_fixed32 = 7; - fixed64 single_fixed64 = 8; - sfixed32 single_sfixed32 = 9; - sfixed64 single_sfixed64 = 10; - float single_float = 11; - double single_double = 12; - bool single_bool = 13; - string single_string = 14; - bytes single_bytes = 15; - NestedEnum single_enum = 16; - NestedMessage single_message = 17; - } - message Wrappers { - google.protobuf.StringValue string_value = 1; - google.protobuf.Int32Value int32_value = 2; - google.protobuf.Int64Value int64_value = 3; - google.protobuf.FloatValue float_value = 4; - google.protobuf.DoubleValue double_value = 5; - google.protobuf.BoolValue bool_value = 6; - google.protobuf.UInt32Value uint32_value = 7; - google.protobuf.UInt64Value uint64_value = 8; - google.protobuf.BytesValue bytes_value = 9; - } - string name = 1; - SubMessage sub = 2; - DataTypes data = 3; - Wrappers wrappers = 4; - repeated string repeated_strings = 5; -} - -message HelloReply { - string message = 1; - repeated string values = 2; -} - -message StringWrapper { - google.protobuf.StringValue string_value = 1; -} - -message RecursiveMessage { - RecursiveMessage child = 1; -} - -message BytesMessage { - bytes bytes_value = 1; - google.protobuf.BytesValue bytes_nullable_value = 2; -} - -message EnumMessage { - enum NestedEnum { - NESTED_ENUM_UNSPECIFIED = 0; - FOO = 1; - BAR = 2; - BAZ = 3; - NEG = -1; // Intentionally negative. - } - NestedEnum enum_value = 1; -} - -message OneOfMessage { - oneof First { - string first_one = 1; - string first_two = 2; - } - oneof Second { - string second_one = 3; - string second_two = 4; - } -} - -message MapMessage { - map map_value = 1; -} - -message FieldMaskMessage { - google.protobuf.FieldMask field_mask_value = 1; -} diff --git a/src/Grpc/JsonTranscoding/test/Microsoft.AspNetCore.Grpc.Swagger.Tests/Proto/parameters.proto b/src/Grpc/JsonTranscoding/test/Microsoft.AspNetCore.Grpc.Swagger.Tests/Proto/parameters.proto deleted file mode 100644 index 6bb8de003e3c..000000000000 --- a/src/Grpc/JsonTranscoding/test/Microsoft.AspNetCore.Grpc.Swagger.Tests/Proto/parameters.proto +++ /dev/null @@ -1,138 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -syntax = "proto3"; - -package params; - -import "google/api/annotations.proto"; -import "google/protobuf/field_mask.proto"; -import "google/protobuf/wrappers.proto"; - -// Add go_package to keep protoc happy when testing generating OpenAPI from commandline. -option go_package = "github.com/dotnet/aspnetcore/swagger"; - -// HttpRule: https://cloud.google.com/endpoints/docs/grpc-service-config/reference/rpc/google.api#google.api.HttpRule - -service Parameters { - // parameter_int & parameter_string should be query parameters - rpc DemoParametersOne (RequestOne) returns (ParamResponse) { - option (google.api.http) = { - get: "/v1/parameters1" - }; - } - - // parameter_string should be query parameters - rpc DemoParametersTwo (RequestOne) returns (ParamResponse) { - option (google.api.http) = { - get: "/v1/parameters2/{parameter_int}" - }; - } - - // parameter_two & parameter_three should be query parameters - rpc DemoParametersThree (RequestTwo) returns (ParamResponse) { - option (google.api.http) = { - post: "/v1/parameters3/{parameter_one}" - body: "parameter_four" - }; - } - - // no query parameters - rpc DemoParametersFour (RequestTwo) returns (ParamResponse) { - option (google.api.http) = { - post: "/v1/parameters4/{parameter_two}" - body: "*" - }; - } - - // parameter_two & parameter_three & parameter_four should be query parameters - rpc DemoParametersFive (RequestTwo) returns (ParamResponse) { - option (google.api.http) = { - get: "/v1/parameters5/{parameter_one}" - }; - } - - // parameter_two & parameter_three & parameter_four should be query parameters - rpc DemoParametersSix (RequestThree) returns (ParamResponse) { - option (google.api.http) = { - get: "/v1/parameters6" - }; - } - - // parameter_two & parameter_one.nested_parameter_three & parameter_one.nested_parameter_four should be query parameters - rpc DemoParametersSeven (RequestFour) returns (ParamResponse) { - option (google.api.http) = { - get: "/v1/parameters7/{parameter_one.nested_parameter_one}/{parameter_one.nested_parameter_two}" - }; - } - - rpc DemoParametersEight (RequestFour) returns (ParamResponse) { - option (google.api.http) = { - get: "/v1/parameters8/{parameter_one.nested_parameter_one=messages1/*}/{parameter_one.nested_parameter_two=shelves/*/books/*}" - }; - } - - rpc DemoParametersNine (RequestFive) returns (ParamResponse) { - option (google.api.http) = { - get: "/v1/parameters9" - }; - } - - rpc DemoParametersTenOne (RequestOne) returns (ParamResponse) { - option (google.api.http) = { - get: "/v1/parameters10/{parameter_int}:one" - }; - } - - rpc DemoParametersTenTwo (RequestOne) returns (ParamResponse) { - option (google.api.http) = { - get: "/v1/parameters10/{parameter_int}:two" - }; - } -} - -message RequestOne { - int64 parameter_int = 1; - string parameter_string = 2; -} - -message RequestTwo { - int64 parameter_one = 1; - string parameter_two = 2; - int64 parameter_three = 3; - RequestBody parameter_four = 45; -} - -message RequestThree { - repeated int64 parameter_one = 1; - // repeated complex field not a valid query parameter - repeated RequestBody parameter_two = 2; - // map field not a valid query parameter - map parameter_three = 3; -} - -message RequestFour { - Nested parameter_one = 1; - string parameter_two = 2; - - message Nested { - int64 nested_parameter_one = 1; - string nested_parameter_two = 2; - int64 nested_parameter_three = 3; - repeated int64 nested_parameter_four = 4; - } -} - -message RequestFive { - google.protobuf.FieldMask field_mask_value = 1; - google.protobuf.StringValue string_value = 2; - google.protobuf.Int32Value int32_value = 3; -} - -message RequestBody { - string request_body = 1; -} - -message ParamResponse { - string message = 1; -} diff --git a/src/Grpc/JsonTranscoding/test/Microsoft.AspNetCore.Grpc.Swagger.Tests/Proto/xmldoc.proto b/src/Grpc/JsonTranscoding/test/Microsoft.AspNetCore.Grpc.Swagger.Tests/Proto/xmldoc.proto deleted file mode 100644 index 1b4b730d2497..000000000000 --- a/src/Grpc/JsonTranscoding/test/Microsoft.AspNetCore.Grpc.Swagger.Tests/Proto/xmldoc.proto +++ /dev/null @@ -1,64 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -syntax = "proto3"; - -import "google/api/annotations.proto"; - -package xmldoc; - -// XmlDoc! -service XmlDoc { - // BasicGet! - rpc BasicGet (StringRequest) returns (StringReply) { - option (google.api.http) = { - get: "/v1/greeter/{name}" - }; - } - // BodyRootPost! - rpc BodyRootPost (StringRequestWithDetail) returns (StringReply) { - option (google.api.http) = { - post: "/v1/greeter", - body: "*" - }; - } - // BodyPathPost! - rpc BodyPathPost (StringRequestWithDetail) returns (StringReply) { - option (google.api.http) = { - post: "/v1/greeter/{name}", - body: "detail" - }; - } - // BasicDelete! - rpc BasicDelete (StringRequest) returns (StringReply) { - option (google.api.http) = { - delete: "/v1/greeter/{name}" - }; - } -} - -// StringRequest! -message StringRequest { - // Name field! - string name = 1; -} - -// StringRequestWithDetail! -message StringRequestWithDetail { - // Name field! - string name = 1; - // Detail field! - StringRequestWithDetail.Detail detail = 2; - - // Detail! - message Detail { - // Age field! - int32 age = 1; - } -} - -// StringReply! -message StringReply { - // Message field! - string message = 1; -} diff --git a/src/Grpc/JsonTranscoding/test/Microsoft.AspNetCore.Grpc.Swagger.Tests/SchemaGeneratorIntegrationTests.cs b/src/Grpc/JsonTranscoding/test/Microsoft.AspNetCore.Grpc.Swagger.Tests/SchemaGeneratorIntegrationTests.cs deleted file mode 100644 index 7ade82418a35..000000000000 --- a/src/Grpc/JsonTranscoding/test/Microsoft.AspNetCore.Grpc.Swagger.Tests/SchemaGeneratorIntegrationTests.cs +++ /dev/null @@ -1,184 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Text.Json; -using Google.Protobuf.WellKnownTypes; -using Messages; -using Microsoft.AspNetCore.Grpc.Swagger.Internal; -using Microsoft.OpenApi.Any; -using Microsoft.OpenApi.Models; -using Swashbuckle.AspNetCore.SwaggerGen; - -namespace Microsoft.AspNetCore.Grpc.Swagger.Tests; - -public class SchemaGeneratorIntegrationTests -{ - private (OpenApiSchema Schema, SchemaRepository SchemaRepository) GenerateSchema(System.Type type) - { - var dataContractResolver = new GrpcDataContractResolver(new JsonSerializerDataContractResolver(new JsonSerializerOptions())); - var schemaGenerator = new SchemaGenerator(new SchemaGeneratorOptions(), dataContractResolver); - var schemaRepository = new SchemaRepository(); - - var schema = schemaGenerator.GenerateSchema(type, schemaRepository); - - return (schema, schemaRepository); - } - - [Fact] - public void GenerateSchema_EnumValue_ReturnSchema() - { - // Arrange & Act - var (schema, repository) = GenerateSchema(typeof(EnumMessage)); - - // Assert - schema = repository.Schemas[schema.Reference.Id]; - Assert.Equal("object", schema.Type); - Assert.Equal(1, schema.Properties.Count); - - var enumSchema = repository.Schemas[schema.Properties["enumValue"].Reference.Id]; - Assert.Equal("string", enumSchema.Type); - Assert.Equal(5, enumSchema.Enum.Count); - - var enumValues = enumSchema.Enum.Select(e => ((OpenApiString)e).Value).ToList(); - Assert.Contains("NEG", enumValues); - Assert.Contains("NESTED_ENUM_UNSPECIFIED", enumValues); - Assert.Contains("FOO", enumValues); - Assert.Contains("BAR", enumValues); - Assert.Contains("BAZ", enumValues); - } - - [Fact] - public void GenerateSchema_BasicMessage_ReturnSchema() - { - // Arrange & Act - var (schema, repository) = GenerateSchema(typeof(HelloReply)); - - // Assert - schema = repository.Schemas[schema.Reference.Id]; - Assert.Equal("object", schema.Type); - Assert.Equal(2, schema.Properties.Count); - Assert.Equal("string", schema.Properties["message"].Type); - var valuesSchema = schema.Properties["values"]; - Assert.Equal("array", valuesSchema.Type); - Assert.NotNull(valuesSchema.Items); - Assert.Equal("string", valuesSchema.Items.Type); - } - - [Fact] - public void GenerateSchema_RecursiveMessage_ReturnSchema() - { - // Arrange & Act - var (schema, repository) = GenerateSchema(typeof(RecursiveMessage)); - - // Assert - schema = repository.Schemas[schema.Reference.Id]; - Assert.Equal("object", schema.Type); - Assert.Equal(1, schema.Properties.Count); - Assert.Equal("RecursiveMessage", schema.Properties["child"].Reference.Id); - } - - [Fact] - public void GenerateSchema_BytesMessage_ReturnSchema() - { - // Arrange & Act - var (schema, repository) = GenerateSchema(typeof(BytesMessage)); - - // Assert - schema = repository.Schemas[schema.Reference.Id]; - Assert.Equal("object", schema.Type); - Assert.Equal(2, schema.Properties.Count); - Assert.Equal("string", schema.Properties["bytesValue"].Type); - Assert.Equal("string", schema.Properties["bytesNullableValue"].Type); - } - - [Fact] - public void GenerateSchema_ListValues_ReturnSchema() - { - // Arrange & Act - var (schema, _) = GenerateSchema(typeof(ListValue)); - - // Assert - Assert.Equal("array", schema.Type); - Assert.NotNull(schema.Items); - Assert.Null(schema.Items.Type); - } - - [Fact] - public void GenerateSchema_Struct_ReturnSchema() - { - // Arrange & Act - var (schema, repository) = GenerateSchema(typeof(Struct)); - - _ = repository.Schemas.Count; - - // Assert - Assert.Equal("Struct", schema.Reference.Id); - - var resolvedSchema = repository.Schemas[schema.Reference.Id]; - - Assert.Equal("object", resolvedSchema.Type); - Assert.Equal(0, resolvedSchema.Properties.Count); - Assert.NotNull(resolvedSchema.AdditionalProperties); - Assert.Null(resolvedSchema.AdditionalProperties.Type); - } - - [Fact] - public void GenerateSchema_Any_ReturnSchema() - { - // Arrange & Act - var (schema, repository) = GenerateSchema(typeof(Any)); - - // Assert - schema = repository.Schemas[schema.Reference.Id]; - Assert.Equal("object", schema.Type); - Assert.NotNull(schema.AdditionalProperties); - Assert.Null(schema.AdditionalProperties.Type); - Assert.Equal(1, schema.Properties.Count); - Assert.Equal("string", schema.Properties["@type"].Type); - } - - [Fact] - public void GenerateSchema_OneOf_ReturnSchema() - { - // Arrange & Act - var (schema, repository) = GenerateSchema(typeof(OneOfMessage)); - - // Assert - schema = repository.Schemas[schema.Reference.Id]; - Assert.Equal("object", schema.Type); - Assert.Equal(4, schema.Properties.Count); - Assert.Equal("string", schema.Properties["firstOne"].Type); - Assert.Equal("string", schema.Properties["firstTwo"].Type); - Assert.Equal("string", schema.Properties["secondOne"].Type); - Assert.Equal("string", schema.Properties["secondTwo"].Type); - Assert.Null(schema.AdditionalProperties); - } - - [Fact] - public void GenerateSchema_Map_ReturnSchema() - { - // Arrange & Act - var (schema, repository) = GenerateSchema(typeof(MapMessage)); - - // Assert - schema = repository.Schemas[schema.Reference.Id]; - Assert.Equal("object", schema.Type); - Assert.Equal(1, schema.Properties.Count); - Assert.Equal("object", schema.Properties["mapValue"].Type); - Assert.Equal("number", schema.Properties["mapValue"].AdditionalProperties.Type); - Assert.Equal("double", schema.Properties["mapValue"].AdditionalProperties.Format); - } - - [Fact] - public void GenerateSchema_FieldMask_ReturnSchema() - { - // Arrange & Act - var (schema, repository) = GenerateSchema(typeof(FieldMaskMessage)); - - // Assert - schema = repository.Schemas[schema.Reference.Id]; - Assert.Equal("object", schema.Type); - Assert.Equal(1, schema.Properties.Count); - Assert.Equal("string", schema.Properties["fieldMaskValue"].Type); - } -} diff --git a/src/Grpc/JsonTranscoding/test/Microsoft.AspNetCore.Grpc.Swagger.Tests/Services/BodyService.cs b/src/Grpc/JsonTranscoding/test/Microsoft.AspNetCore.Grpc.Swagger.Tests/Services/BodyService.cs deleted file mode 100644 index 7f8a88e6d975..000000000000 --- a/src/Grpc/JsonTranscoding/test/Microsoft.AspNetCore.Grpc.Swagger.Tests/Services/BodyService.cs +++ /dev/null @@ -1,10 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using Params; - -namespace Microsoft.AspNetCore.Grpc.Swagger.Tests.Services; - -public class BodyService : Body.Body.BodyBase -{ -} diff --git a/src/Grpc/JsonTranscoding/test/Microsoft.AspNetCore.Grpc.Swagger.Tests/Services/GreeterService.cs b/src/Grpc/JsonTranscoding/test/Microsoft.AspNetCore.Grpc.Swagger.Tests/Services/GreeterService.cs deleted file mode 100644 index c7a961fa154f..000000000000 --- a/src/Grpc/JsonTranscoding/test/Microsoft.AspNetCore.Grpc.Swagger.Tests/Services/GreeterService.cs +++ /dev/null @@ -1,24 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using Greet; -using Grpc.Core; -using Microsoft.Extensions.Logging; - -namespace Microsoft.AspNetCore.Grpc.Swagger.Tests.Services; - -public class GreeterService : Greeter.GreeterBase -{ - private readonly ILogger _logger; - - public GreeterService(ILoggerFactory loggerFactory) - { - _logger = loggerFactory.CreateLogger(); - } - - public override Task SayHello(HelloRequest request, ServerCallContext context) - { - _logger.LogInformation($"Sending hello to {request.Name}"); - return Task.FromResult(new HelloReply { Message = $"Hello {request.Name}" }); - } -} diff --git a/src/Grpc/JsonTranscoding/test/Microsoft.AspNetCore.Grpc.Swagger.Tests/Services/ParametersService.cs b/src/Grpc/JsonTranscoding/test/Microsoft.AspNetCore.Grpc.Swagger.Tests/Services/ParametersService.cs deleted file mode 100644 index ed09929a65d9..000000000000 --- a/src/Grpc/JsonTranscoding/test/Microsoft.AspNetCore.Grpc.Swagger.Tests/Services/ParametersService.cs +++ /dev/null @@ -1,30 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using Grpc.Core; -using Params; - -namespace Microsoft.AspNetCore.Grpc.Swagger.Tests.Services; - -public class ParametersService : Params.Parameters.ParametersBase -{ - public override Task DemoParametersOne(RequestOne requestId, ServerCallContext ctx) - { - return Task.FromResult(new ParamResponse { Message = "DemoParametersOne Response" }); - } - - public override Task DemoParametersTwo(RequestOne requestId, ServerCallContext ctx) - { - return Task.FromResult(new ParamResponse { Message = "DemoParametersTwo Response" }); - } - - public override Task DemoParametersThree(RequestTwo request, ServerCallContext ctx) - { - return Task.FromResult(new ParamResponse { Message = "DemoParametersThree Response " }); - } - - public override Task DemoParametersFour(RequestTwo request, ServerCallContext ctx) - { - return Task.FromResult(new ParamResponse { Message = "DemoParametersFour Response" }); - } -} diff --git a/src/Grpc/JsonTranscoding/test/Microsoft.AspNetCore.Grpc.Swagger.Tests/Services/XmlDocService.cs b/src/Grpc/JsonTranscoding/test/Microsoft.AspNetCore.Grpc.Swagger.Tests/Services/XmlDocService.cs deleted file mode 100644 index 3da58b7575e8..000000000000 --- a/src/Grpc/JsonTranscoding/test/Microsoft.AspNetCore.Grpc.Swagger.Tests/Services/XmlDocService.cs +++ /dev/null @@ -1,33 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using Grpc.Core; -using Microsoft.Extensions.Logging; -using Xmldoc; - -namespace Microsoft.AspNetCore.Grpc.Swagger.Tests.Services; - -public class XmlDocService : XmlDoc.XmlDocBase -{ - private readonly ILogger _logger; - - public XmlDocService(ILoggerFactory loggerFactory) - { - _logger = loggerFactory.CreateLogger(); - } - - public override Task BasicGet(StringRequest request, ServerCallContext context) - { - return base.BasicGet(request, context); - } - - public override Task BodyRootPost(StringRequestWithDetail request, ServerCallContext context) - { - return base.BodyRootPost(request, context); - } - - public override Task BodyPathPost(StringRequestWithDetail request, ServerCallContext context) - { - return base.BodyPathPost(request, context); - } -} diff --git a/src/Grpc/JsonTranscoding/test/Microsoft.AspNetCore.Grpc.Swagger.Tests/Services/XmlDocServiceWithComments.cs b/src/Grpc/JsonTranscoding/test/Microsoft.AspNetCore.Grpc.Swagger.Tests/Services/XmlDocServiceWithComments.cs deleted file mode 100644 index 642bc91bda18..000000000000 --- a/src/Grpc/JsonTranscoding/test/Microsoft.AspNetCore.Grpc.Swagger.Tests/Services/XmlDocServiceWithComments.cs +++ /dev/null @@ -1,48 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using Grpc.Core; -using Microsoft.Extensions.Logging; -using Xmldoc; - -namespace Microsoft.AspNetCore.Grpc.Swagger.Tests.Services; - -/// -/// XmlDocServiceWithComments XML comment! -/// -public class XmlDocServiceWithComments : XmlDoc.XmlDocBase -{ - private readonly ILogger _logger; - - public XmlDocServiceWithComments(ILoggerFactory loggerFactory) - { - _logger = loggerFactory.CreateLogger(); - } - - /// - /// BasicGet XML summary! - /// - /// - /// BasicGet XML remarks! - /// - /// Request XML comment! - /// - /// Returns the newly created item! - /// Not found! - /// Returns comment! - public override Task BasicGet(StringRequest request, ServerCallContext context) - { - return base.BasicGet(request, context); - } - - /// - /// BodyRootPost XML summary! - /// - /// Request XML param! - /// - /// - public override Task BodyRootPost(StringRequestWithDetail request, ServerCallContext context) - { - return base.BodyRootPost(request, context); - } -} diff --git a/src/Grpc/JsonTranscoding/test/Microsoft.AspNetCore.Grpc.Swagger.Tests/XmlComments/XmlCommentsDocumentFilterTests.cs b/src/Grpc/JsonTranscoding/test/Microsoft.AspNetCore.Grpc.Swagger.Tests/XmlComments/XmlCommentsDocumentFilterTests.cs deleted file mode 100644 index 6d9e0c3cc0d6..000000000000 --- a/src/Grpc/JsonTranscoding/test/Microsoft.AspNetCore.Grpc.Swagger.Tests/XmlComments/XmlCommentsDocumentFilterTests.cs +++ /dev/null @@ -1,72 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Xml.XPath; -using Grpc.AspNetCore.Server; -using Grpc.Core; -using Microsoft.AspNetCore.Grpc.Swagger.Internal.XmlComments; -using Microsoft.AspNetCore.Grpc.Swagger.Tests.Services; -using Microsoft.AspNetCore.Mvc.Abstractions; -using Microsoft.AspNetCore.Mvc.ApiExplorer; -using Microsoft.OpenApi.Models; -using Swashbuckle.AspNetCore.SwaggerGen; - -namespace Microsoft.AspNetCore.Grpc.Swagger.Tests.XmlComments; - -public class XmlCommentsDocumentFilterTests -{ - private class TestMethod : IMethod - { - public MethodType Type { get; } - public string ServiceName { get; } = "TestServiceName"; - public string Name { get; } = "TestName"; - public string FullName => ServiceName + "." + Name; - } - - [Theory] - [InlineData(typeof(XmlDocService), "XmlDoc!")] - [InlineData(typeof(XmlDocServiceWithComments), "XmlDocServiceWithComments XML comment!")] - public void Apply_SetsTagDescription_FromControllerSummaryTags(Type serviceType, string expectedDescription) - { - var document = new OpenApiDocument(); - var filterContext = new DocumentFilterContext( - new[] - { - CreateApiDescription(serviceType), - CreateApiDescription(serviceType) - }, - null, - null); - - Subject().Apply(document, filterContext); - - Assert.Equal(1, document.Tags.Count); - Assert.Equal(expectedDescription, document.Tags[0].Description); - - static ApiDescription CreateApiDescription(Type serviceType) - { - return new ApiDescription - { - ActionDescriptor = new ActionDescriptor - { - RouteValues = - { - ["controller"] = "greet.Greeter" - }, - EndpointMetadata = new List - { - new GrpcMethodMetadata(serviceType, new TestMethod()) - } - } - }; - } - } - - private GrpcXmlCommentsDocumentFilter Subject() - { - using (var xmlComments = File.OpenText($"{typeof(GreeterService).Assembly.GetName().Name}.xml")) - { - return new GrpcXmlCommentsDocumentFilter(new XPathDocument(xmlComments)); - } - } -} diff --git a/src/Grpc/JsonTranscoding/test/Microsoft.AspNetCore.Grpc.Swagger.Tests/XmlComments/XmlDocumentationIntegrationTests.cs b/src/Grpc/JsonTranscoding/test/Microsoft.AspNetCore.Grpc.Swagger.Tests/XmlComments/XmlDocumentationIntegrationTests.cs deleted file mode 100644 index 652c24b0c022..000000000000 --- a/src/Grpc/JsonTranscoding/test/Microsoft.AspNetCore.Grpc.Swagger.Tests/XmlComments/XmlDocumentationIntegrationTests.cs +++ /dev/null @@ -1,126 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using Greet; -using Microsoft.AspNetCore.Grpc.Swagger.Tests.Infrastructure; -using Microsoft.AspNetCore.Grpc.Swagger.Tests.Services; -using Microsoft.OpenApi.Models; -using Xunit.Abstractions; - -namespace Microsoft.AspNetCore.Grpc.Swagger.Tests.XmlComments; - -public class XmlDocumentationIntegrationTests -{ - private readonly ITestOutputHelper _testOutputHelper; - - public XmlDocumentationIntegrationTests(ITestOutputHelper testOutputHelper) - { - _testOutputHelper = testOutputHelper; - } - - [Fact] - public void ServiceDescription_ModelHasXmlDocs_UseXmlDocs() - { - // Arrange & Act - var swagger = OpenApiTestHelpers.GetOpenApiDocument(_testOutputHelper); - - // Assert - Assert.Equal("XmlDoc", swagger.Tags[0].Name); - Assert.Equal("XmlDocServiceWithComments XML comment!", swagger.Tags[0].Description); - } - - [Fact] - public void ServiceDescription_ModelDoesntHaveXmlDocs_UseProtoDocs() - { - // Arrange & Act - var swagger = OpenApiTestHelpers.GetOpenApiDocument(_testOutputHelper); - - // Assert - Assert.Equal("XmlDoc", swagger.Tags[0].Name); - Assert.Equal("XmlDoc!", swagger.Tags[0].Description); - } - - [Fact] - public void RouteParameter_UseProtoDocs() - { - // Arrange & Act - var swagger = OpenApiTestHelpers.GetOpenApiDocument(_testOutputHelper); - - // Assert - var path = swagger.Paths["/v1/greeter/{name}"]; - Assert.Equal("Name field!", path.Operations[OperationType.Get].Parameters[0].Description); - } - - [Fact] - public void MethodDescription_ModelHasXmlDocs_UseXmlDocs() - { - // Arrange & Act - var swagger = OpenApiTestHelpers.GetOpenApiDocument(_testOutputHelper); - - // Assert - var path = swagger.Paths["/v1/greeter/{name}"]; - Assert.Equal("BasicGet XML summary!", path.Operations[OperationType.Get].Summary); - Assert.Equal("BasicGet XML remarks!", path.Operations[OperationType.Get].Description); - } - - [Fact] - public void MethodDescription_ModelDoesntHaveXmlDocs_UseProtoDocs() - { - // Arrange & Act - var swagger = OpenApiTestHelpers.GetOpenApiDocument(_testOutputHelper); - - // Assert - var path = swagger.Paths["/v1/greeter/{name}"]; - Assert.Equal("BasicGet!", path.Operations[OperationType.Get].Summary); - Assert.Null(path.Operations[OperationType.Get].Description); - } - - [Fact] - public void RequestDescription_Root_ModelHasXmlDocs_UseXmlDocs() - { - // Arrange & Act - var swagger = OpenApiTestHelpers.GetOpenApiDocument(_testOutputHelper); - - // Assert - var path = swagger.Paths["/v1/greeter"]; - Assert.Equal("Request XML param!", path.Operations[OperationType.Post].RequestBody.Description); - } - - [Fact] - public void RequestDescription_Root_ModelDoesntHaveXmlDocs_Empty() - { - // Arrange & Act - var swagger = OpenApiTestHelpers.GetOpenApiDocument(_testOutputHelper); - - // Assert - var path = swagger.Paths["/v1/greeter"]; - Assert.Null(path.Operations[OperationType.Post].RequestBody.Description); - } - - [Fact] - public void RequestDescription_Nested_ProtoFieldDocs() - { - // Arrange & Act - var swagger = OpenApiTestHelpers.GetOpenApiDocument(_testOutputHelper); - - // Assert - var path = swagger.Paths["/v1/greeter/{name}"]; - Assert.Equal("Detail field!", path.Operations[OperationType.Post].RequestBody.Description); - } - - [Fact] - public void Message_UseProtoDocs() - { - // Arrange & Act - var swagger = OpenApiTestHelpers.GetOpenApiDocument(_testOutputHelper); - - // Assert - var helloReplyMessage = swagger.Components.Schemas["StringReply"]; - Assert.Equal("StringReply!", helloReplyMessage.Description); - Assert.Equal("Message field!", helloReplyMessage.Properties["message"].Description); - } - - private class GreeterService : Greeter.GreeterBase - { - } -} diff --git a/src/Grpc/JsonTranscoding/test/Microsoft.AspNetCore.Grpc.Swagger.Tests/google/api/annotations.proto b/src/Grpc/JsonTranscoding/test/Microsoft.AspNetCore.Grpc.Swagger.Tests/google/api/annotations.proto deleted file mode 100644 index 85c361b47fed..000000000000 --- a/src/Grpc/JsonTranscoding/test/Microsoft.AspNetCore.Grpc.Swagger.Tests/google/api/annotations.proto +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright (c) 2015, Google Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -syntax = "proto3"; - -package google.api; - -import "google/api/http.proto"; -import "google/protobuf/descriptor.proto"; - -option go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations"; -option java_multiple_files = true; -option java_outer_classname = "AnnotationsProto"; -option java_package = "com.google.api"; -option objc_class_prefix = "GAPI"; - -extend google.protobuf.MethodOptions { - // See `HttpRule`. - HttpRule http = 72295728; -} diff --git a/src/Grpc/JsonTranscoding/test/Microsoft.AspNetCore.Grpc.Swagger.Tests/google/api/http.proto b/src/Grpc/JsonTranscoding/test/Microsoft.AspNetCore.Grpc.Swagger.Tests/google/api/http.proto deleted file mode 100644 index b2977f514741..000000000000 --- a/src/Grpc/JsonTranscoding/test/Microsoft.AspNetCore.Grpc.Swagger.Tests/google/api/http.proto +++ /dev/null @@ -1,376 +0,0 @@ -// Copyright 2019 Google LLC. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -syntax = "proto3"; - -package google.api; - -option cc_enable_arenas = true; -option go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations"; -option java_multiple_files = true; -option java_outer_classname = "HttpProto"; -option java_package = "com.google.api"; -option objc_class_prefix = "GAPI"; - -// Defines the HTTP configuration for an API service. It contains a list of -// [HttpRule][google.api.HttpRule], each specifying the mapping of an RPC method -// to one or more HTTP REST API methods. -message Http { - // A list of HTTP configuration rules that apply to individual API methods. - // - // **NOTE:** All service configuration rules follow "last one wins" order. - repeated HttpRule rules = 1; - - // When set to true, URL path parameters will be fully URI-decoded except in - // cases of single segment matches in reserved expansion, where "%2F" will be - // left encoded. - // - // The default behavior is to not decode RFC 6570 reserved characters in multi - // segment matches. - bool fully_decode_reserved_expansion = 2; -} - -// # gRPC Transcoding -// -// gRPC Transcoding is a feature for mapping between a gRPC method and one or -// more HTTP REST endpoints. It allows developers to build a single API service -// that supports both gRPC APIs and REST APIs. Many systems, including [Google -// APIs](https://github.com/googleapis/googleapis), -// [Cloud Endpoints](https://cloud.google.com/endpoints), [gRPC -// Gateway](https://github.com/grpc-ecosystem/grpc-gateway), -// and [Envoy](https://github.com/envoyproxy/envoy) proxy support this feature -// and use it for large scale production services. -// -// `HttpRule` defines the schema of the gRPC/REST mapping. The mapping specifies -// how different portions of the gRPC request message are mapped to the URL -// path, URL query parameters, and HTTP request body. It also controls how the -// gRPC response message is mapped to the HTTP response body. `HttpRule` is -// typically specified as an `google.api.http` annotation on the gRPC method. -// -// Each mapping specifies a URL path template and an HTTP method. The path -// template may refer to one or more fields in the gRPC request message, as long -// as each field is a non-repeated field with a primitive (non-message) type. -// The path template controls how fields of the request message are mapped to -// the URL path. -// -// Example: -// -// service Messaging { -// rpc GetMessage(GetMessageRequest) returns (Message) { -// option (google.api.http) = { -// get: "/v1/{name=messages/*}" -// }; -// } -// } -// message GetMessageRequest { -// string name = 1; // Mapped to URL path. -// } -// message Message { -// string text = 1; // The resource content. -// } -// -// This enables an HTTP REST to gRPC mapping as below: -// -// HTTP | gRPC -// -----|----- -// `GET /v1/messages/123456` | `GetMessage(name: "messages/123456")` -// -// Any fields in the request message which are not bound by the path template -// automatically become HTTP query parameters if there is no HTTP request body. -// For example: -// -// service Messaging { -// rpc GetMessage(GetMessageRequest) returns (Message) { -// option (google.api.http) = { -// get:"/v1/messages/{message_id}" -// }; -// } -// } -// message GetMessageRequest { -// message SubMessage { -// string subfield = 1; -// } -// string message_id = 1; // Mapped to URL path. -// int64 revision = 2; // Mapped to URL query parameter `revision`. -// SubMessage sub = 3; // Mapped to URL query parameter `sub.subfield`. -// } -// -// This enables a HTTP JSON to RPC mapping as below: -// -// HTTP | gRPC -// -----|----- -// `GET /v1/messages/123456?revision=2&sub.subfield=foo` | -// `GetMessage(message_id: "123456" revision: 2 sub: SubMessage(subfield: -// "foo"))` -// -// Note that fields which are mapped to URL query parameters must have a -// primitive type or a repeated primitive type or a non-repeated message type. -// In the case of a repeated type, the parameter can be repeated in the URL -// as `...?param=A¶m=B`. In the case of a message type, each field of the -// message is mapped to a separate parameter, such as -// `...?foo.a=A&foo.b=B&foo.c=C`. -// -// For HTTP methods that allow a request body, the `body` field -// specifies the mapping. Consider a REST update method on the -// message resource collection: -// -// service Messaging { -// rpc UpdateMessage(UpdateMessageRequest) returns (Message) { -// option (google.api.http) = { -// patch: "/v1/messages/{message_id}" -// body: "message" -// }; -// } -// } -// message UpdateMessageRequest { -// string message_id = 1; // mapped to the URL -// Message message = 2; // mapped to the body -// } -// -// The following HTTP JSON to RPC mapping is enabled, where the -// representation of the JSON in the request body is determined by -// protos JSON encoding: -// -// HTTP | gRPC -// -----|----- -// `PATCH /v1/messages/123456 { "text": "Hi!" }` | `UpdateMessage(message_id: -// "123456" message { text: "Hi!" })` -// -// The special name `*` can be used in the body mapping to define that -// every field not bound by the path template should be mapped to the -// request body. This enables the following alternative definition of -// the update method: -// -// service Messaging { -// rpc UpdateMessage(Message) returns (Message) { -// option (google.api.http) = { -// patch: "/v1/messages/{message_id}" -// body: "*" -// }; -// } -// } -// message Message { -// string message_id = 1; -// string text = 2; -// } -// -// -// The following HTTP JSON to RPC mapping is enabled: -// -// HTTP | gRPC -// -----|----- -// `PATCH /v1/messages/123456 { "text": "Hi!" }` | `UpdateMessage(message_id: -// "123456" text: "Hi!")` -// -// Note that when using `*` in the body mapping, it is not possible to -// have HTTP parameters, as all fields not bound by the path end in -// the body. This makes this option more rarely used in practice when -// defining REST APIs. The common usage of `*` is in custom methods -// which don't use the URL at all for transferring data. -// -// It is possible to define multiple HTTP methods for one RPC by using -// the `additional_bindings` option. Example: -// -// service Messaging { -// rpc GetMessage(GetMessageRequest) returns (Message) { -// option (google.api.http) = { -// get: "/v1/messages/{message_id}" -// additional_bindings { -// get: "/v1/users/{user_id}/messages/{message_id}" -// } -// }; -// } -// } -// message GetMessageRequest { -// string message_id = 1; -// string user_id = 2; -// } -// -// This enables the following two alternative HTTP JSON to RPC mappings: -// -// HTTP | gRPC -// -----|----- -// `GET /v1/messages/123456` | `GetMessage(message_id: "123456")` -// `GET /v1/users/me/messages/123456` | `GetMessage(user_id: "me" message_id: -// "123456")` -// -// ## Rules for HTTP mapping -// -// 1. Leaf request fields (recursive expansion nested messages in the request -// message) are classified into three categories: -// - Fields referred by the path template. They are passed via the URL path. -// - Fields referred by the [HttpRule.body][google.api.HttpRule.body]. They are passed via the HTTP -// request body. -// - All other fields are passed via the URL query parameters, and the -// parameter name is the field path in the request message. A repeated -// field can be represented as multiple query parameters under the same -// name. -// 2. If [HttpRule.body][google.api.HttpRule.body] is "*", there is no URL query parameter, all fields -// are passed via URL path and HTTP request body. -// 3. If [HttpRule.body][google.api.HttpRule.body] is omitted, there is no HTTP request body, all -// fields are passed via URL path and URL query parameters. -// -// ### Path template syntax -// -// Template = "/" Segments [ Verb ] ; -// Segments = Segment { "/" Segment } ; -// Segment = "*" | "**" | LITERAL | Variable ; -// Variable = "{" FieldPath [ "=" Segments ] "}" ; -// FieldPath = IDENT { "." IDENT } ; -// Verb = ":" LITERAL ; -// -// The syntax `*` matches a single URL path segment. The syntax `**` matches -// zero or more URL path segments, which must be the last part of the URL path -// except the `Verb`. -// -// The syntax `Variable` matches part of the URL path as specified by its -// template. A variable template must not contain other variables. If a variable -// matches a single path segment, its template may be omitted, e.g. `{var}` -// is equivalent to `{var=*}`. -// -// The syntax `LITERAL` matches literal text in the URL path. If the `LITERAL` -// contains any reserved character, such characters should be percent-encoded -// before the matching. -// -// If a variable contains exactly one path segment, such as `"{var}"` or -// `"{var=*}"`, when such a variable is expanded into a URL path on the client -// side, all characters except `[-_.~0-9a-zA-Z]` are percent-encoded. The -// server side does the reverse decoding. Such variables show up in the -// [Discovery -// Document](https://developers.google.com/discovery/v1/reference/apis) as -// `{var}`. -// -// If a variable contains multiple path segments, such as `"{var=foo/*}"` -// or `"{var=**}"`, when such a variable is expanded into a URL path on the -// client side, all characters except `[-_.~/0-9a-zA-Z]` are percent-encoded. -// The server side does the reverse decoding, except "%2F" and "%2f" are left -// unchanged. Such variables show up in the -// [Discovery -// Document](https://developers.google.com/discovery/v1/reference/apis) as -// `{+var}`. -// -// ## Using gRPC API Service Configuration -// -// gRPC API Service Configuration (service config) is a configuration language -// for configuring a gRPC service to become a user-facing product. The -// service config is simply the YAML representation of the `google.api.Service` -// proto message. -// -// As an alternative to annotating your proto file, you can configure gRPC -// transcoding in your service config YAML files. You do this by specifying a -// `HttpRule` that maps the gRPC method to a REST endpoint, achieving the same -// effect as the proto annotation. This can be particularly useful if you -// have a proto that is reused in multiple services. Note that any transcoding -// specified in the service config will override any matching transcoding -// configuration in the proto. -// -// Example: -// -// http: -// rules: -// # Selects a gRPC method and applies HttpRule to it. -// - selector: example.v1.Messaging.GetMessage -// get: /v1/messages/{message_id}/{sub.subfield} -// -// ## Special notes -// -// When gRPC Transcoding is used to map a gRPC to JSON REST endpoints, the -// proto to JSON conversion must follow the [proto3 -// specification](https://developers.google.com/protocol-buffers/docs/proto3#json). -// -// While the single segment variable follows the semantics of -// [RFC 6570](https://tools.ietf.org/html/rfc6570) Section 3.2.2 Simple String -// Expansion, the multi segment variable **does not** follow RFC 6570 Section -// 3.2.3 Reserved Expansion. The reason is that the Reserved Expansion -// does not expand special characters like `?` and `#`, which would lead -// to invalid URLs. As the result, gRPC Transcoding uses a custom encoding -// for multi segment variables. -// -// The path variables **must not** refer to any repeated or mapped field, -// because client libraries are not capable of handling such variable expansion. -// -// The path variables **must not** capture the leading "/" character. The reason -// is that the most common use case "{var}" does not capture the leading "/" -// character. For consistency, all path variables must share the same behavior. -// -// Repeated message fields must not be mapped to URL query parameters, because -// no client library can support such complicated mapping. -// -// If an API needs to use a JSON array for request or response body, it can map -// the request or response body to a repeated field. However, some gRPC -// Transcoding implementations may not support this feature. -message HttpRule { - // Selects a method to which this rule applies. - // - // Refer to [selector][google.api.DocumentationRule.selector] for syntax details. - string selector = 1; - - // Determines the URL pattern is matched by this rules. This pattern can be - // used with any of the {get|put|post|delete|patch} methods. A custom method - // can be defined using the 'custom' field. - oneof pattern { - // Maps to HTTP GET. Used for listing and getting information about - // resources. - string get = 2; - - // Maps to HTTP PUT. Used for replacing a resource. - string put = 3; - - // Maps to HTTP POST. Used for creating a resource or performing an action. - string post = 4; - - // Maps to HTTP DELETE. Used for deleting a resource. - string delete = 5; - - // Maps to HTTP PATCH. Used for updating a resource. - string patch = 6; - - // The custom pattern is used for specifying an HTTP method that is not - // included in the `pattern` field, such as HEAD, or "*" to leave the - // HTTP method unspecified for this rule. The wild-card rule is useful - // for services that provide content to Web (HTML) clients. - CustomHttpPattern custom = 8; - } - - // The name of the request field whose value is mapped to the HTTP request - // body, or `*` for mapping all request fields not captured by the path - // pattern to the HTTP body, or omitted for not having any HTTP request body. - // - // NOTE: the referred field must be present at the top-level of the request - // message type. - string body = 7; - - // Optional. The name of the response field whose value is mapped to the HTTP - // response body. When omitted, the entire response message will be used - // as the HTTP response body. - // - // NOTE: The referred field must be present at the top-level of the response - // message type. - string response_body = 12; - - // Additional HTTP bindings for the selector. Nested bindings must - // not contain an `additional_bindings` field themselves (that is, - // the nesting may only be one level deep). - repeated HttpRule additional_bindings = 11; -} - -// A custom pattern is used for defining custom HTTP verb. -message CustomHttpPattern { - // The name of this custom HTTP verb. - string kind = 1; - - // The path matched by this custom verb. - string path = 2; -} diff --git a/src/Grpc/JsonTranscoding/test/testassets/Sandbox/Sandbox.csproj b/src/Grpc/JsonTranscoding/test/testassets/Sandbox/Sandbox.csproj index fa3e2095d4e1..b2ab977ec1d9 100644 --- a/src/Grpc/JsonTranscoding/test/testassets/Sandbox/Sandbox.csproj +++ b/src/Grpc/JsonTranscoding/test/testassets/Sandbox/Sandbox.csproj @@ -9,6 +9,5 @@ - diff --git a/src/Grpc/JsonTranscoding/test/testassets/Sandbox/Startup.cs b/src/Grpc/JsonTranscoding/test/testassets/Sandbox/Startup.cs index 52e10479fc58..6ad3bf3e55e8 100644 --- a/src/Grpc/JsonTranscoding/test/testassets/Sandbox/Startup.cs +++ b/src/Grpc/JsonTranscoding/test/testassets/Sandbox/Startup.cs @@ -1,8 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using Microsoft.OpenApi.Models; - namespace Server; public class Startup @@ -11,14 +9,6 @@ public void ConfigureServices(IServiceCollection services) { services.AddGrpc().AddJsonTranscoding(); services.AddMvc(); - - #region Secret - services.AddSwaggerGen(c => - { - c.SwaggerDoc("v1", new OpenApiInfo { Title = "My API", Version = "v1" }); - }); - services.AddGrpcSwagger(); - #endregion } public void Configure(IApplicationBuilder app, IWebHostEnvironment env) @@ -28,14 +18,6 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env) app.UseDeveloperExceptionPage(); } - #region Secret - app.UseSwagger(); - app.UseSwaggerUI(c => - { - c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1"); - }); - #endregion - app.UseRouting(); app.UseEndpoints(endpoints =>