Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Do not fill the RequestBody description with the first parameter of a… #3076

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -21,90 +21,155 @@ internal XmlCommentsRequestBodyFilter(IReadOnlyDictionary<string, XPathNavigator

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);
}
else
{
var parameterInfo = bodyParameterDescription.ParameterInfo();
if (parameterInfo is not null)
{
ApplyParamTagsForBody(requestBody, context, parameterInfo);
}
}
}

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 is 0)
{
return;
}

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

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

foreach (var item in requestBody.Content.Values)
{
if (item?.Schema?.Properties is { } properties
&& (properties.TryGetValue(formParameter.Name, out var value) || properties.TryGetValue(formParameter.Name.ToCamelCase(), out value)))
{
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)

jgarciadelanoceda marked this conversation as resolved.
Show resolved Hide resolved
return (summary, exampleNode?.ToString());
}

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

if (summary is not null)
{
return;
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
jgarciadelanoceda marked this conversation as resolved.
Show resolved Hide resolved
? methodInfo.GetUnderlyingGenericTypeMethod()
: methodInfo;

if (targetMethod is null)
{
return;
return (null, null);
}

jgarciadelanoceda marked this conversation as resolved.
Show resolved Hide resolved
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);
jgarciadelanoceda marked this conversation as resolved.
Show resolved Hide resolved

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

var example = paramNode.GetAttribute("example");
if (!string.IsNullOrEmpty(example))
{
foreach (var mediaType in requestBody.Content.Values)
{
mediaType.Example = XmlCommentsExampleHelper.Create(context.SchemaRepository, mediaType.Schema, example);
}
}
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 = summary;
}

if (requestBody.Content?.Count is 0 || string.IsNullOrEmpty(example))
{
return;
}

foreach (var mediaType in requestBody.Content.Values)
{
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
18 changes: 17 additions & 1 deletion test/WebSites/Basic/Controllers/FromFormParamsController.cs
Original file line number Diff line number Diff line change
@@ -1,14 +1,23 @@
using System.Collections.Generic;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Swashbuckle.AspNetCore.Annotations;

namespace Basic.Controllers
{
public class FromFormParamsController
{
/// <summary>
/// Form parameters with description
/// </summary>
/// <param name="form">Description for whole object</param>
/// <param name="formFile">Description for file</param>
/// <param name="text">Description for Text</param>
/// <returns></returns>
/// <exception cref="System.NotImplementedException"></exception>
[HttpPost("registrations")]
[Consumes("application/x-www-form-urlencoded")]
public IActionResult PostForm([FromForm] RegistrationForm form)
public IActionResult PostForm([FromForm] RegistrationForm form, IFormFile formFile, [FromForm] string text)
{
throw new System.NotImplementedException();
}
Expand All @@ -22,8 +31,15 @@ public IActionResult PostFormWithIgnoredProperties([FromForm] RegistrationFormWi

public class RegistrationForm
{
/// <summary>
/// Summary for Name
/// </summary>
/// <example>MyName</example>
public string Name { get; set; }

/// <summary>
/// Sumary for PhoneNumbers
/// </summary>
public IEnumerable<int> PhoneNumbers { get; set; }
}

Expand Down