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

Apply only a SchemaFilter to create the Description on the SwaggerUi #2943

Original file line number Diff line number Diff line change
Expand Up @@ -375,12 +375,21 @@ private OpenApiParameter GenerateParameter(
apiParameter.RouteInfo)
: new OpenApiSchema { Type = "string" };

var description = schema.Description;
if (string.IsNullOrEmpty(description)
&& !string.IsNullOrEmpty(schema?.Reference?.Id)
&& schemaRepository.Schemas.TryGetValue(schema.Reference.Id, out var openApiSchema))
{
description = openApiSchema.Description;
}

var parameter = new OpenApiParameter
{
Name = name,
In = location,
Required = isRequired,
Schema = schema
Schema = schema,
Description = description
};

var filterContext = new ParameterFilterContext(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ internal class FakeICompositeMetadataDetailsProvider : ICompositeMetadataDetails
{
public void CreateBindingMetadata(BindingMetadataProviderContext context)
{
throw new NotImplementedException();
context.BindingMetadata.IsBindingAllowed = true;
}

public void CreateDisplayMetadata(DisplayMetadataProviderContext context)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
using System;
using System.Linq;
using System.Text;
using Microsoft.OpenApi.Models;

namespace Swashbuckle.AspNetCore.SwaggerGen.Test.Fixtures
{
internal class TestEnumSchemaFilter : ISchemaFilter
{
public void Apply(OpenApiSchema schema, SchemaFilterContext context)
{
bool isEnumArgument = (context.Type?.GenericTypeArguments?.Length ?? 0) == 1 && context.Type.GenericTypeArguments.All(b => b.IsEnum);
var isEnumArray = context.Type.IsArray && context.Type.GetElementType().IsEnum;
if (context.Type.IsEnum || isEnumArgument || isEnumArray)
{
var enumType = (context.Type.IsEnum, isEnumArgument, isEnumArray) switch
{
(true, _, _) => context.Type,
(_, true, _) => context.Type.GenericTypeArguments.First(),
_ => context.Type.GetElementType()
};
StringBuilder stringBuilder = new("<p>Members:</p><ul>");
foreach (var enumValue in Enum.GetValues(enumType))
{
if (enumValue is Enum value)
{
stringBuilder.Append($"<li>{value} - {value:d}</li>");
}
}
schema.Description = stringBuilder.Append("</ul>").ToString();
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1723,15 +1723,68 @@ public void GetSwagger_Works_As_Expected_When_FromForm_Attribute_With_SwaggerIgn
Assert.Equal(new[] { nameof(SwaggerIngoreAnnotatedType.NotIgnoredString) }, mediaType.Encoding.Keys);
}

[Fact]
public void GetSwagger_Copies_Description_From_GeneratedSchema()
{
var propertyEnum = typeof(TypeWithDefaultAttributeOnEnum).GetProperty(nameof(TypeWithDefaultAttributeOnEnum.EnumWithDefault));
var modelMetadataForEnum = new DefaultModelMetadata(
new DefaultModelMetadataProvider(new FakeICompositeMetadataDetailsProvider()),
new FakeICompositeMetadataDetailsProvider(),
new DefaultMetadataDetails(ModelMetadataIdentity.ForProperty(propertyEnum, typeof(IntEnum), typeof(TypeWithDefaultAttributeOnEnum)), ModelAttributes.GetAttributesForProperty(typeof(TypeWithDefaultAttributeOnEnum), propertyEnum)));

var propertyEnumArray = typeof(TypeWithDefaultAttributeOnEnum).GetProperty(nameof(TypeWithDefaultAttributeOnEnum.EnumArrayWithDefault));
var modelMetadataForEnumArray = new DefaultModelMetadata(
new DefaultModelMetadataProvider(new FakeICompositeMetadataDetailsProvider()),
new FakeICompositeMetadataDetailsProvider(),
new DefaultMetadataDetails(ModelMetadataIdentity.ForProperty(propertyEnumArray, typeof(IntEnum[]), typeof(TypeWithDefaultAttributeOnEnum)), ModelAttributes.GetAttributesForProperty(typeof(TypeWithDefaultAttributeOnEnum), propertyEnumArray)));
var subject = Subject(
apiDescriptions:
[
ApiDescriptionFactory.Create<FakeController>(
c => nameof(c.ActionHavingFromFormAttributeWithSwaggerIgnore),
groupName: "v1",
httpMethod: "POST",
relativePath: "resource",
parameterDescriptions: new[]
{
new ApiParameterDescription
{
Name = nameof(TypeWithDefaultAttributeOnEnum.EnumWithDefault),
Source = BindingSource.Query,
Type = typeof(IntEnum),
ModelMetadata = modelMetadataForEnum
},
new ApiParameterDescription
{
Name = nameof(TypeWithDefaultAttributeOnEnum.EnumArrayWithDefault),
Source = BindingSource.Query,
Type = typeof(IntEnum[]),
ModelMetadata = modelMetadataForEnumArray
}
})
],
schemaFilters: [new TestEnumSchemaFilter()]
);
var document = subject.GetSwagger("v1");

var operation = document.Paths["/resource"].Operations[OperationType.Post];
Assert.NotEmpty(operation.Parameters);
Assert.Equal(nameof(TypeWithDefaultAttributeOnEnum.EnumWithDefault), operation.Parameters[0].Name);
Assert.Equal(document.Components.Schemas[nameof(IntEnum)].Description, operation.Parameters[0].Description);
Assert.Equal(nameof(TypeWithDefaultAttributeOnEnum.EnumArrayWithDefault), operation.Parameters[1].Name);
Assert.Equal(document.Components.Schemas[nameof(IntEnum)].Description, operation.Parameters[1].Description);
}

private static SwaggerGenerator Subject(
IEnumerable<ApiDescription> apiDescriptions,
SwaggerGeneratorOptions options = null,
IEnumerable<AuthenticationScheme> authenticationSchemes = null)
IEnumerable<AuthenticationScheme> authenticationSchemes = null,
List<ISchemaFilter> schemaFilters = null)
{
return new SwaggerGenerator(
options ?? DefaultOptions,
new FakeApiDescriptionGroupCollectionProvider(apiDescriptions),
new SchemaGenerator(new SchemaGeneratorOptions(), new JsonSerializerDataContractResolver(new JsonSerializerOptions())),
new SchemaGenerator(new SchemaGeneratorOptions() { SchemaFilters = schemaFilters ?? [] }, new JsonSerializerDataContractResolver(new JsonSerializerOptions())),
new FakeAuthenticationSchemeProvider(authenticationSchemes ?? Enumerable.Empty<AuthenticationScheme>())
);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
{
Info: {
Title: Test API,
Version: V1
},
Paths: {
/resource: {
Operations: {
Post: {
Tags: [
{
Name: Fake,
UnresolvedReference: false
}
],
Parameters: [
{
UnresolvedReference: false,
Name: EnumWithDefault,
In: Query,
Description: <p>Members:</p><ul><li>Value2 - 2</li><li>Value4 - 4</li><li>Value8 - 8</li></ul>,
Required: false,
Deprecated: false,
AllowEmptyValue: false,
Style: Form,
Explode: true,
AllowReserved: false,
Schema: {
ReadOnly: false,
WriteOnly: false,
AdditionalPropertiesAllowed: true,
Nullable: false,
Deprecated: false,
UnresolvedReference: false,
Reference: {
IsFragrament: false,
Type: Schema,
Id: IntEnum,
IsExternal: false,
IsLocal: true,
ReferenceV3: #/components/schemas/IntEnum,
ReferenceV2: #/definitions/IntEnum
}
}
},
{
UnresolvedReference: false,
Name: EnumArrayWithDefault,
In: Query,
Description: <p>Members:</p><ul><li>Value2 - 2</li><li>Value4 - 4</li><li>Value8 - 8</li></ul>,
Required: false,
Deprecated: false,
AllowEmptyValue: false,
Style: Form,
Explode: true,
AllowReserved: false,
Schema: {
Type: array,
Description: <p>Members:</p><ul><li>Value2 - 2</li><li>Value4 - 4</li><li>Value8 - 8</li></ul>,
Default: [
{
Value: 4
}
],
ReadOnly: false,
WriteOnly: false,
Items: {
ReadOnly: false,
WriteOnly: false,
AdditionalPropertiesAllowed: true,
Nullable: false,
Deprecated: false,
UnresolvedReference: false,
Reference: {
IsFragrament: false,
Type: Schema,
Id: IntEnum,
IsExternal: false,
IsLocal: true,
ReferenceV3: #/components/schemas/IntEnum,
ReferenceV2: #/definitions/IntEnum
}
},
AdditionalPropertiesAllowed: true,
Nullable: false,
Deprecated: false,
UnresolvedReference: false
}
}
],
Responses: {
200: {
Description: OK,
UnresolvedReference: false
}
},
Deprecated: false
}
},
UnresolvedReference: false
}
},
Components: {
Schemas: {
IntEnum: {
Type: integer,
Format: int32,
Description: <p>Members:</p><ul><li>Value2 - 2</li><li>Value4 - 4</li><li>Value8 - 8</li></ul>,
ReadOnly: false,
WriteOnly: false,
AdditionalPropertiesAllowed: true,
Enum: [
{
Value: 2
},
{
Value: 4
},
{
Value: 8
}
],
Nullable: false,
Deprecated: false,
UnresolvedReference: false
}
}
},
HashCode: 29A4A89ADE3B75E921A9EF9CE77F3E1517293B167A0B1513F55C57F3E84194193E8B24F875170DE0524C73CF5AB9DDCDF6556F1BDC2E6EA1F8171340F4C0F0B1
}
Original file line number Diff line number Diff line change
Expand Up @@ -1102,15 +1102,63 @@ public Task ActionHavingFromFormAttributeWithSwaggerIgnore()
return Verifier.Verify(document);
}

[Fact]
public Task GetSwagger_Copies_Description_From_GeneratedSchema()
{
var propertyEnum = typeof(TypeWithDefaultAttributeOnEnum).GetProperty(nameof(TypeWithDefaultAttributeOnEnum.EnumWithDefault));
var modelMetadataForEnum = new DefaultModelMetadata(
new DefaultModelMetadataProvider(new FakeICompositeMetadataDetailsProvider()),
new FakeICompositeMetadataDetailsProvider(),
new DefaultMetadataDetails(ModelMetadataIdentity.ForProperty(propertyEnum, typeof(IntEnum), typeof(TypeWithDefaultAttributeOnEnum)), ModelAttributes.GetAttributesForProperty(typeof(TypeWithDefaultAttributeOnEnum), propertyEnum)));

var propertyEnumArray = typeof(TypeWithDefaultAttributeOnEnum).GetProperty(nameof(TypeWithDefaultAttributeOnEnum.EnumArrayWithDefault));
var modelMetadataForEnumArray = new DefaultModelMetadata(
new DefaultModelMetadataProvider(new FakeICompositeMetadataDetailsProvider()),
new FakeICompositeMetadataDetailsProvider(),
new DefaultMetadataDetails(ModelMetadataIdentity.ForProperty(propertyEnumArray, typeof(IntEnum[]), typeof(TypeWithDefaultAttributeOnEnum)), ModelAttributes.GetAttributesForProperty(typeof(TypeWithDefaultAttributeOnEnum), propertyEnumArray)));
var subject = Subject(
apiDescriptions:
[
ApiDescriptionFactory.Create<FakeController>(
c => nameof(c.ActionHavingFromFormAttributeWithSwaggerIgnore),
groupName: "v1",
httpMethod: "POST",
relativePath: "resource",
parameterDescriptions: new[]
{
new ApiParameterDescription
{
Name = nameof(TypeWithDefaultAttributeOnEnum.EnumWithDefault),
Source = BindingSource.Query,
Type = typeof(IntEnum),
ModelMetadata = modelMetadataForEnum
},
new ApiParameterDescription
{
Name = nameof(TypeWithDefaultAttributeOnEnum.EnumArrayWithDefault),
Source = BindingSource.Query,
Type = typeof(IntEnum[]),
ModelMetadata = modelMetadataForEnumArray
}
})
],
schemaFilters: [new TestEnumSchemaFilter()]
);
var document = subject.GetSwagger("v1");

return Verifier.Verify(document);
}

private static SwaggerGenerator Subject(
IEnumerable<ApiDescription> apiDescriptions,
SwaggerGeneratorOptions options = null,
IEnumerable<AuthenticationScheme> authenticationSchemes = null)
IEnumerable<ApiDescription> apiDescriptions,
SwaggerGeneratorOptions options = null,
IEnumerable<AuthenticationScheme> authenticationSchemes = null,
List<ISchemaFilter> schemaFilters = null)
{
return new SwaggerGenerator(
options ?? DefaultOptions,
new FakeApiDescriptionGroupCollectionProvider(apiDescriptions),
new SchemaGenerator(new SchemaGeneratorOptions(), new JsonSerializerDataContractResolver(new JsonSerializerOptions())),
new SchemaGenerator(new SchemaGeneratorOptions() { SchemaFilters = schemaFilters ?? [] }, new JsonSerializerDataContractResolver(new JsonSerializerOptions())),
jgarciadelanoceda marked this conversation as resolved.
Show resolved Hide resolved
new FakeAuthenticationSchemeProvider(authenticationSchemes ?? Enumerable.Empty<AuthenticationScheme>())
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,7 @@ public class TypeWithDefaultAttributeOnEnum
{
[DefaultValue(IntEnum.Value4)]
public IntEnum EnumWithDefault { get; set; }
[DefaultValue(new IntEnum[] { IntEnum.Value4 })]
public IntEnum[] EnumArrayWithDefault { get; set; }
}
}