Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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 @@ -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