Skip to content

Commit

Permalink
Do not fill the RequestBody description with the first parameter of a…
Browse files Browse the repository at this point in the history
… FromForm request, and document the Properties instead
  • Loading branch information
Javier García de la Noceda Argüelles committed Sep 22, 2024
1 parent 8b0e896 commit 98b7950
Show file tree
Hide file tree
Showing 5 changed files with 190 additions and 52 deletions.
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
using Microsoft.OpenApi.Models;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Xml.XPath;
using Microsoft.OpenApi.Models;

namespace Swashbuckle.AspNetCore.SwaggerGen
{
Expand All @@ -13,97 +13,165 @@ public class XmlCommentsRequestBodyFilter : IRequestBodyFilter
public XmlCommentsRequestBodyFilter(XPathDocument xmlDoc) : this(XmlCommentsDocumentHelper.CreateMemberDictionary(xmlDoc))
{
}

internal XmlCommentsRequestBodyFilter(IReadOnlyDictionary<string, XPathNavigator> xmlDocMembers)
{
_xmlDocMembers = xmlDocMembers;
}

public void Apply(OpenApiRequestBody requestBody, RequestBodyFilterContext context)
{
var parameterDescription =
context.BodyParameterDescription ??
context.FormParameterDescriptions.FirstOrDefault((p) => p is not null);
var bodyParameterDescription = context.BodyParameterDescription;

if (parameterDescription is null)
if (bodyParameterDescription is not null)
{
return;
var propertyInfo = bodyParameterDescription.PropertyInfo();
if (propertyInfo is not null)
{
ApplyPropertyTagsForBody(requestBody, context, propertyInfo);
return;
}
var parameterInfo = bodyParameterDescription.ParameterInfo();
if (parameterInfo is not null)
{
ApplyParamTagsForBody(requestBody, context, parameterInfo);
return;
}
}

var propertyInfo = parameterDescription.PropertyInfo();
if (propertyInfo is not null)
else
{
ApplyPropertyTags(requestBody, context, propertyInfo);
return;
}
var numberOfFromForm = context.FormParameterDescriptions?.Count() ?? 0;
if (requestBody.Content?.Count is 0 || numberOfFromForm == 0)
{
return;
}

var parameterInfo = parameterDescription.ParameterInfo();
if (parameterInfo is not null)
{
ApplyParamTags(requestBody, context, parameterInfo);
foreach (var formParameter in context.FormParameterDescriptions)
{
if (formParameter.PropertyInfo() is not null || formParameter.Name is null)
{
continue;
}

var parameterFromForm = formParameter.ParameterInfo();
if (parameterFromForm is null)
{
continue;
}

foreach (var item in requestBody.Content.Values)
{
if ((item?.Schema?.Properties?.TryGetValue(formParameter.Name, out var value) ?? false)
|| (item?.Schema?.Properties?.TryGetValue(formParameter.Name.ToCamelCase(), out value) ?? false))
{
var (summary, example) = GetParamTags(parameterFromForm);
value.Description = summary;
if (!string.IsNullOrEmpty(example))
{
value.Example = XmlCommentsExampleHelper.Create(context.SchemaRepository, value, example);
}
}
}
}
}
}

private void ApplyPropertyTags(OpenApiRequestBody requestBody, RequestBodyFilterContext context, PropertyInfo propertyInfo)
private (string summary, string example) GetPropertyTags(PropertyInfo propertyInfo)
{
var propertyMemberName = XmlCommentsNodeNameHelper.GetMemberNameForFieldOrProperty(propertyInfo);
if (!_xmlDocMembers.TryGetValue(propertyMemberName, out var propertyNode))
{
return (null, null);
}

if (!_xmlDocMembers.TryGetValue(propertyMemberName, out var propertyNode)) return;

string summary = null;
var summaryNode = propertyNode.SelectFirstChild("summary");
if (summaryNode is not null)
{
requestBody.Description = XmlCommentsTextHelper.Humanize(summaryNode.InnerXml);
summary = XmlCommentsTextHelper.Humanize(summaryNode.InnerXml);
}

var exampleNode = propertyNode.SelectFirstChild("example");
if (exampleNode is null || requestBody.Content?.Count is 0)
if (exampleNode is null)
{
return;
return (summary, null);
}

return (summary, exampleNode.ToString());

}

private void ApplyPropertyTagsForBody(OpenApiRequestBody requestBody, RequestBodyFilterContext context, PropertyInfo propertyInfo)
{
var (summary, example) = GetPropertyTags(propertyInfo);

if (summary is not null)
{
requestBody.Description = summary;
}

var example = exampleNode.ToString();
if (requestBody.Content?.Count is 0)
{
return;
}

foreach (var mediaType in requestBody.Content.Values)
{
mediaType.Example = XmlCommentsExampleHelper.Create(context.SchemaRepository, mediaType.Schema, example);
}
}

private void ApplyParamTags(OpenApiRequestBody requestBody, RequestBodyFilterContext context, ParameterInfo parameterInfo)
private (string summary, string example) GetParamTags(ParameterInfo parameterInfo)
{
if (parameterInfo.Member is not MethodInfo methodInfo)
{
return;
return (null, null);
}

// 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 is null)
{
return;
return (null, null);
}

var methodMemberName = XmlCommentsNodeNameHelper.GetMemberNameForMethod(targetMethod);

if (!_xmlDocMembers.TryGetValue(methodMemberName, out var propertyNode)) return;

if (!_xmlDocMembers.TryGetValue(methodMemberName, out var propertyNode))
{
return (null, null);
}
var paramNode = propertyNode.SelectFirstChildWithAttribute("param", "name", parameterInfo.Name);

if (paramNode is not null)
if (paramNode is null)
{
return (null, null);
}

var summary = XmlCommentsTextHelper.Humanize(paramNode.InnerXml);
var example = paramNode.GetAttribute("example");

return (summary, example);

}

private void ApplyParamTagsForBody(OpenApiRequestBody requestBody, RequestBodyFilterContext context, ParameterInfo parameterInfo)
{
var (summary, example) = GetParamTags(parameterInfo);

if (summary is not null)
{
requestBody.Description = XmlCommentsTextHelper.Humanize(paramNode.InnerXml);
requestBody.Description = summary;
}

if (requestBody.Content?.Count is 0)
{
return;
}

var example = paramNode.GetAttribute("example");
if (!string.IsNullOrEmpty(example))
if (!string.IsNullOrEmpty(example))
{
foreach (var mediaType in requestBody.Content.Values)
{
foreach (var mediaType in requestBody.Content.Values)
{
mediaType.Example = XmlCommentsExampleHelper.Create(context.SchemaRepository, mediaType.Schema, example);
}
mediaType.Example = XmlCommentsExampleHelper.Create(context.SchemaRepository, mediaType.Schema, example);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -571,21 +571,34 @@
tags: [
FromFormParams
],
summary: Form parameters with description,
requestBody: {
content: {
application/x-www-form-urlencoded: {
schema: {
type: object,
properties: {
name: {
type: string
type: string,
description: Summary for Name,
example: MyName
},
phoneNumbers: {
type: array,
items: {
type: integer,
format: int32
}
},
description: Sumary for PhoneNumbers
},
formFile: {
type: string,
description: Description for file,
format: binary
},
text: {
type: string,
description: Description for Text
}
}
},
Expand All @@ -595,6 +608,12 @@
},
phoneNumbers: {
style: form
},
formFile: {
style: form
},
text: {
style: form
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -571,21 +571,34 @@
tags: [
FromFormParams
],
summary: Form parameters with description,
requestBody: {
content: {
application/x-www-form-urlencoded: {
schema: {
type: object,
properties: {
name: {
type: string
type: string,
description: Summary for Name,
example: MyName
},
phoneNumbers: {
type: array,
items: {
type: integer,
format: int32
}
},
description: Sumary for PhoneNumbers
},
formFile: {
type: string,
description: Description for file,
format: binary
},
text: {
type: string,
description: Description for Text
}
}
},
Expand All @@ -595,6 +608,12 @@
},
phoneNumbers: {
style: form
},
formFile: {
style: form
},
text: {
style: form
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using System.Xml.XPath;
using Microsoft.AspNetCore.Mvc.ApiExplorer;
using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.TestSupport;
using Xunit;
Expand Down Expand Up @@ -137,25 +138,39 @@ public void Apply_SetsDescription_ForParameterFromBody()
[Fact]
public void Apply_SetsDescription_ForParameterFromForm()
{
var parameterInfo = typeof(FakeControllerWithXmlComments)
.GetMethod(nameof(FakeControllerWithXmlComments.PostForm))
.GetParameters()[0];

var requestBody = new OpenApiRequestBody
{
Content = new Dictionary<string, OpenApiMediaType>
{
["multipart/form-data"] = new OpenApiMediaType { Schema = new OpenApiSchema { Type = "string" } }
["multipart/form-data"] = new OpenApiMediaType
{
Schema = new OpenApiSchema
{
Type = "string",
Properties = new Dictionary<string, OpenApiSchema>()
{
[parameterInfo.Name] = new()
}
},
}
}
};
var parameterInfo = typeof(FakeControllerWithXmlComments)
.GetMethod(nameof(FakeControllerWithXmlComments.PostForm))
.GetParameters()[0];

var bodyParameterDescription = new ApiParameterDescription
{
ParameterDescriptor = new ControllerParameterDescriptor { ParameterInfo = parameterInfo }
ParameterDescriptor = new ControllerParameterDescriptor { ParameterInfo = parameterInfo },
Name = parameterInfo.Name,
Source = BindingSource.Form
};
var filterContext = new RequestBodyFilterContext(null, [bodyParameterDescription], null, null);

Subject().Apply(requestBody, filterContext);

Assert.Equal("Parameter from form body", requestBody.Description);
Assert.Equal("Parameter from form body", requestBody.Content["multipart/form-data"].Schema.Properties[parameterInfo.Name].Description);
}

private static XmlCommentsRequestBodyFilter Subject()
Expand Down
Loading

0 comments on commit 98b7950

Please sign in to comment.