Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 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
Expand Up @@ -116,6 +116,7 @@ public void DeepCopy(SwaggerGeneratorOptions source, SwaggerGeneratorOptions tar
target.RequestBodyFilters = new List<IRequestBodyFilter>(source.RequestBodyFilters);
target.RequestBodyAsyncFilters = new List<IRequestBodyAsyncFilter>(source.RequestBodyAsyncFilters);
target.SecuritySchemesSelector = source.SecuritySchemesSelector;
target.PathGroupSelector = source.PathGroupSelector;
}

private TFilter GetOrCreateFilter<TFilter>(FilterDescriptor filterDescriptor)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,5 @@ static Microsoft.Extensions.DependencyInjection.SwaggerGenOptionsExtensions.Docu
static Microsoft.Extensions.DependencyInjection.SwaggerGenOptionsExtensions.OperationAsyncFilter<TFilter>(this Swashbuckle.AspNetCore.SwaggerGen.SwaggerGenOptions swaggerGenOptions, params object[] arguments) -> void
static Microsoft.Extensions.DependencyInjection.SwaggerGenOptionsExtensions.ParameterAsyncFilter<TFilter>(this Swashbuckle.AspNetCore.SwaggerGen.SwaggerGenOptions swaggerGenOptions, params object[] arguments) -> void
static Microsoft.Extensions.DependencyInjection.SwaggerGenOptionsExtensions.RequestBodyAsyncFilter<TFilter>(this Swashbuckle.AspNetCore.SwaggerGen.SwaggerGenOptions swaggerGenOptions, params object[] arguments) -> void
Swashbuckle.AspNetCore.SwaggerGen.SwaggerGeneratorOptions.PathGroupSelector.get -> System.Func<Microsoft.AspNetCore.Mvc.ApiExplorer.ApiDescription, string>
Swashbuckle.AspNetCore.SwaggerGen.SwaggerGeneratorOptions.PathGroupSelector.set -> void
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
using System;
using System.Linq;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Microsoft.AspNetCore.Mvc.ApiExplorer;
using Microsoft.AspNetCore.Mvc.Controllers;
Expand Down Expand Up @@ -41,7 +41,7 @@ public static IEnumerable<object> CustomAttributes(this ApiDescription apiDescri
.Union(methodInfo.DeclaringType.GetCustomAttributes(true));
}

return Enumerable.Empty<object>();
return [];
}

[Obsolete("Use TryGetMethodInfo() and CustomAttributes() instead")]
Expand All @@ -57,7 +57,7 @@ public static void GetAdditionalMetadata(this ApiDescription apiDescription,
return;
}

customAttributes = Enumerable.Empty<object>();
customAttributes = [];
}

internal static string RelativePathSansParameterConstraints(this ApiDescription apiDescription)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,9 +72,7 @@ public static PropertyInfo PropertyInfo(this ApiParameterDescription apiParamete
{
var modelMetadata = apiParameter.ModelMetadata;

return (modelMetadata?.ContainerType != null)
? modelMetadata.ContainerType.GetProperty(modelMetadata.PropertyName)
: null;
return modelMetadata?.ContainerType?.GetProperty(modelMetadata.PropertyName);
}

public static IEnumerable<object> CustomAttributes(this ApiParameterDescription apiParameter)
Expand Down Expand Up @@ -103,12 +101,12 @@ internal static void GetAdditionalMetadata(

internal static bool IsFromPath(this ApiParameterDescription apiParameter)
{
return (apiParameter.Source == BindingSource.Path);
return apiParameter.Source == BindingSource.Path;
}

internal static bool IsFromBody(this ApiParameterDescription apiParameter)
{
return (apiParameter.Source == BindingSource.Body);
return apiParameter.Source == BindingSource.Body;
}

internal static bool IsFromForm(this ApiParameterDescription apiParameter)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ public async Task<OpenApiDocument> GetSwaggerAsync(
swaggerDoc.Paths = await GeneratePathsAsync(filterContext.ApiDescriptions, filterContext.SchemaRepository);
swaggerDoc.Components.SecuritySchemes = await GetSecuritySchemesAsync();

// NOTE: Filter processing moved here so they may effect generated security schemes
// NOTE: Filter processing moved here so they may affect generated security schemes
foreach (var filter in _options.DocumentAsyncFilters)
{
await filter.ApplyAsync(swaggerDoc, filterContext, CancellationToken.None);
Expand All @@ -81,7 +81,7 @@ public OpenApiDocument GetSwagger(string documentName, string host = null, strin
swaggerDoc.Paths = GeneratePaths(filterContext.ApiDescriptions, filterContext.SchemaRepository);
swaggerDoc.Components.SecuritySchemes = GetSecuritySchemesAsync().Result;

// NOTE: Filter processing moved here so they may effect generated security schemes
// NOTE: Filter processing moved here so they may affect generated security schemes
foreach (var filter in _options.DocumentFilters)
{
filter.Apply(swaggerDoc, filterContext);
Expand Down Expand Up @@ -199,7 +199,7 @@ private async Task<OpenApiPaths> GeneratePathsAsync(
{
var apiDescriptionsByPath = apiDescriptions
.OrderBy(_options.SortKeySelector)
.GroupBy(apiDesc => apiDesc.RelativePathSansParameterConstraints());
.GroupBy(_options.PathGroupSelector);

var paths = new OpenApiPaths();
foreach (var group in apiDescriptionsByPath)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ public SwaggerGeneratorOptions()
OperationIdSelector = DefaultOperationIdSelector;
TagsSelector = DefaultTagsSelector;
SortKeySelector = DefaultSortKeySelector;
PathGroupSelector = DefaultPathGroupSelector;
SecuritySchemesSelector = null;
SchemaComparer = StringComparer.Ordinal;
Servers = new List<OpenApiServer>();
Expand Down Expand Up @@ -49,6 +50,8 @@ public SwaggerGeneratorOptions()

public Func<ApiDescription, string> SortKeySelector { get; set; }

public Func<ApiDescription, string> PathGroupSelector { get; set; }

public bool InferSecuritySchemes { get; set; }

public Func<IEnumerable<AuthenticationScheme>, IDictionary<string, OpenApiSecurityScheme>> SecuritySchemesSelector { get; set; }
Expand Down Expand Up @@ -120,5 +123,10 @@ private string DefaultSortKeySelector(ApiDescription apiDescription)
{
return TagsSelector(apiDescription).First();
}

private static string DefaultPathGroupSelector(ApiDescription apiDescription)
{
return apiDescription.RelativePathSansParameterConstraints();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public static void DeepCopy_Copies_All_Properties()

// If this assertion fails, it means that a new property has been added
// to SwaggerGeneratorOptions and ConfigureSwaggerGeneratorOptions.DeepCopy() needs to be updated
Assert.Equal(22, publicProperties.Length);
Assert.Equal(23, publicProperties.Length);
}

[Fact]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,9 @@ public void ActionWithIntParameterWithDefaultValue(int param = 1)
public void ActionWithIntParameterWithDefaultValueAttribute([DefaultValue(3)] int param)
{ }

public void ActionWithIntFromQueryParameter([FromQuery] int param)
{ }

public void ActionWithIntParameterWithRequiredAttribute([Required] int param)
{ }

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1143,6 +1143,36 @@ public void GetSwagger_ThrowsSwaggerGeneratorException_IfActionsHaveConflictingH
exception.Message);
}

[Fact]
public void GetSwagger_ThrowsSwaggerGeneratorException_IfActionsHaveConflictingHttpMethodAndPathWithDifferentParameters()
{
var subject = Subject(
apiDescriptions:
[
ApiDescriptionFactory.Create<FakeController>(
c => nameof(c.ActionWithNoParameters), groupName: "v1", httpMethod: "GET", relativePath: "resource"),

ApiDescriptionFactory.Create<FakeController>(
c => nameof(c.ActionWithIntFromQueryParameter), groupName: "v1", httpMethod: "GET", relativePath: "resource", new ApiParameterDescription[]
{
new()
{
Name = "id",
Source = BindingSource.Query,
}
}),
]
);

var exception = Assert.Throws<SwaggerGeneratorException>(() => subject.GetSwagger("v1"));
Assert.Equal(
"Conflicting method/path combination \"GET resource\" for actions - " +
"Swashbuckle.AspNetCore.SwaggerGen.Test.FakeController.ActionWithNoParameters (Swashbuckle.AspNetCore.SwaggerGen.Test)," +
"Swashbuckle.AspNetCore.SwaggerGen.Test.FakeController.ActionWithIntFromQueryParameter (Swashbuckle.AspNetCore.SwaggerGen.Test). " +
"Actions require a unique method/path combination for Swagger/OpenAPI 3.0. Use ConflictingActionsResolver as a workaround",
exception.Message);
}

[Fact]
public void GetSwagger_SupportsOption_IgnoreObsoleteActions()
{
Expand Down