using Microsoft.AspNetCore.Mvc.ApiExplorer; using Microsoft.OpenApi.Models; using Swashbuckle.AspNetCore.SwaggerGen; namespace DocumentService.API.Configuration { /// /// Swagger document filter that merges operations with same path but different [Consumes] attributes. /// Ensures both multipart/form-data and application/json variants are visible in Swagger UI. /// public class DualInputDocumentFilter : IDocumentFilter { private readonly IApiDescriptionGroupCollectionProvider _apiDescriptionProvider; /// /// Initializes a new instance of the class. /// /// API description provider to access all endpoints public DualInputDocumentFilter(IApiDescriptionGroupCollectionProvider apiDescriptionProvider) { _apiDescriptionProvider = apiDescriptionProvider; } /// /// Applies the filter to merge operations with different content types. /// public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context) { var allApiDescriptions = _apiDescriptionProvider.ApiDescriptionGroups.Items .SelectMany(g => g.Items) .ToList(); // Group by path var groupedByPath = allApiDescriptions .GroupBy(x => "/" + x.RelativePath) .ToList(); foreach (var group in groupedByPath) { var path = group.Key; if (!swaggerDoc.Paths.ContainsKey(path)) continue; var pathItem = swaggerDoc.Paths[path]; // Find multipart and JSON variants var multipartDesc = group.FirstOrDefault(x => x.SupportedRequestFormats.Any(f => f.MediaType == "multipart/form-data")); var jsonDesc = group.FirstOrDefault(x => x.SupportedRequestFormats.Any(f => f.MediaType == "application/json")); // If we have both variants, merge them into single operation if (multipartDesc != null && jsonDesc != null) { var httpMethod = multipartDesc.HttpMethod?.ToLowerInvariant(); OperationType operationType; if (!Enum.TryParse(httpMethod, true, out operationType)) continue; if (!pathItem.Operations.ContainsKey(operationType)) continue; var operation = pathItem.Operations[operationType]; // Ensure RequestBody exists if (operation.RequestBody == null) { operation.RequestBody = new OpenApiRequestBody { Required = true, Content = new Dictionary() }; } // Add multipart/form-data if missing if (!operation.RequestBody.Content.ContainsKey("multipart/form-data")) { operation.RequestBody.Content.Add("multipart/form-data", new OpenApiMediaType { Schema = new OpenApiSchema { Type = "object", Properties = new Dictionary { ["file"] = new OpenApiSchema { Type = "string", Format = "binary", Description = "PDF file to upload" } }, Required = new HashSet { "file" } } }); } // Add application/json if missing if (!operation.RequestBody.Content.ContainsKey("application/json")) { operation.RequestBody.Content.Add("application/json", new OpenApiMediaType { Schema = new OpenApiSchema { Type = "object", Properties = new Dictionary { ["base64Pdf"] = new OpenApiSchema { Type = "string", Format = "byte", Description = "Base64-encoded PDF file content" } }, Required = new HashSet { "base64Pdf" } } }); } } } } } }