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

Use schemas generated by Swashbuckle for built-in operations #2441

Merged
merged 1 commit into from Jul 13, 2022
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -132,17 +132,11 @@ private OpenApiPaths GeneratePaths(IEnumerable<ApiDescription> apiDescriptions,

private OpenApiOperation GenerateOperation(ApiDescription apiDescription, SchemaRepository schemaRepository)
{
#if NET6_0_OR_GREATER
var metadata = apiDescription.ActionDescriptor?.EndpointMetadata;
var existingOperation = metadata?.OfType<OpenApiOperation>().SingleOrDefault();
if (existingOperation != null)
{
return existingOperation;
}
#endif
OpenApiOperation operation = GenerateOpenApiOperationFromMetadata(apiDescription, schemaRepository);

try
{
var operation = new OpenApiOperation
operation ??= new OpenApiOperation
{
Tags = GenerateOperationTags(apiDescription),
OperationId = _options.OperationIdSelector(apiDescription),
Expand All @@ -169,6 +163,72 @@ private OpenApiOperation GenerateOperation(ApiDescription apiDescription, Schema
}
}

private OpenApiOperation GenerateOpenApiOperationFromMetadata(ApiDescription apiDescription, SchemaRepository schemaRepository)
{
#if NET6_0_OR_GREATER
var metadata = apiDescription.ActionDescriptor?.EndpointMetadata;
var operation = metadata?.OfType<OpenApiOperation>().SingleOrDefault();

if (operation is null)
{
return null;
}

// Schemas will be generated via Swashbuckle by default.
foreach (var parameter in operation.Parameters)
{
var apiParameter = apiDescription.ParameterDescriptions.SingleOrDefault(desc => desc.Name == parameter.Name && !desc.IsFromBody() && !desc.IsFromForm());
if (apiParameter is not null)
{
parameter.Schema = GenerateSchema(
apiParameter.ModelMetadata.ModelType,
schemaRepository,
apiParameter.PropertyInfo(),
apiParameter.ParameterInfo(),
apiParameter.RouteInfo);
}
}

var requestContentTypes = operation.RequestBody?.Content?.Values;
if (requestContentTypes is not null)
{
foreach (var content in requestContentTypes)
{
var requestParameter = apiDescription.ParameterDescriptions.SingleOrDefault(desc => desc.IsFromBody() || desc.IsFromForm());
if (requestParameter is not null)
{
content.Schema = GenerateSchema(
requestParameter.ModelMetadata.ModelType,
schemaRepository,
requestParameter.PropertyInfo(),
requestParameter.ParameterInfo());
}
}
}

foreach (var kvp in operation.Responses)
{
var response = kvp.Value;
var responseModel = apiDescription.SupportedResponseTypes.SingleOrDefault(desc => desc.StatusCode.ToString() == kvp.Key);
if (responseModel is not null)
{
var responseContentTypes = response?.Content?.Values;
if (responseContentTypes is not null)
{
foreach (var content in responseContentTypes)
{
content.Schema = GenerateSchema(responseModel.Type, schemaRepository);
}
}
}
}

return operation;
#else
return null;
#endif
}

private IList<OpenApiTag> GenerateOperationTags(ApiDescription apiDescription)
{
return _options.TagsSelector(apiDescription)
Expand Down
Expand Up @@ -179,6 +179,173 @@ public void GetSwagger_UseProvidedOpenApiOperation_IfExistsInMetadata()
Assert.Equal("ParameterInMetadata", document.Paths["/resource"].Operations[OperationType.Post].Parameters[0].Name);
}

[Fact]
public void GetSwagger_GenerateProducesSchemas_ForProvidedOpenApiOperation()
{
var methodInfo = typeof(FakeController).GetMethod(nameof(FakeController.ActionWithProducesAttribute));
var actionDescriptor = new ActionDescriptor
{
EndpointMetadata = new List<object>()
{
new OpenApiOperation
{
OperationId = "OperationIdSetInMetadata",
Responses = new()
{
["200"] = new()
{
Content = new Dictionary<string, OpenApiMediaType>()
{
["application/someMediaType"] = new()
}
}
}
}
},
RouteValues = new Dictionary<string, string>
{
["controller"] = methodInfo.DeclaringType.Name.Replace("Controller", string.Empty)
}
};
var subject = Subject(
apiDescriptions: new[]
{
ApiDescriptionFactory.Create(
actionDescriptor,
methodInfo,
groupName: "v1",
httpMethod: "POST",
relativePath: "resource",
supportedResponseTypes: new[]
{
new ApiResponseType()
{
StatusCode = 200,
Type = typeof(TestDto)
}
}),
}
);

var document = subject.GetSwagger("v1");

Assert.Equal("OperationIdSetInMetadata", document.Paths["/resource"].Operations[OperationType.Post].OperationId);
var content = Assert.Single(document.Paths["/resource"].Operations[OperationType.Post].Responses["200"].Content);
Assert.Equal("application/someMediaType", content.Key);
Assert.Null(content.Value.Schema.Type);
Assert.NotNull(content.Value.Schema.Reference);
Assert.Equal("TestDto", content.Value.Schema.Reference.Id);
}

[Fact]
public void GetSwagger_GenerateConsumesSchemas_ForProvidedOpenApiOperation()
{
var methodInfo = typeof(FakeController).GetMethod(nameof(FakeController.ActionWithConsumesAttribute));
var actionDescriptor = new ActionDescriptor
{
EndpointMetadata = new List<object>()
{
new OpenApiOperation
{
OperationId = "OperationIdSetInMetadata",
RequestBody = new()
{
Content = new Dictionary<string, OpenApiMediaType>()
{
["application/someMediaType"] = new()
}
}
}
},
RouteValues = new Dictionary<string, string>
{
["controller"] = methodInfo.DeclaringType.Name.Replace("Controller", string.Empty)
}
};
var subject = Subject(
apiDescriptions: new[]
{
ApiDescriptionFactory.Create(
actionDescriptor,
methodInfo,
groupName: "v1",
httpMethod: "POST",
relativePath: "resource",
parameterDescriptions: new[]
{
new ApiParameterDescription()
{
Name = "param",
Source = BindingSource.Body,
ModelMetadata = ModelMetadataFactory.CreateForType(typeof(TestDto))
}
}),
}
);

var document = subject.GetSwagger("v1");

Assert.Equal("OperationIdSetInMetadata", document.Paths["/resource"].Operations[OperationType.Post].OperationId);
var content = Assert.Single(document.Paths["/resource"].Operations[OperationType.Post].RequestBody.Content);
Assert.Equal("application/someMediaType", content.Key);
Assert.Null(content.Value.Schema.Type);
Assert.NotNull(content.Value.Schema.Reference);
Assert.Equal("TestDto", content.Value.Schema.Reference.Id);
}

[Fact]
public void GetSwagger_GenerateParametersSchemas_ForProvidedOpenApiOperation()
{
var methodInfo = typeof(FakeController).GetMethod(nameof(FakeController.ActionWithParameter));
var actionDescriptor = new ActionDescriptor
{
EndpointMetadata = new List<object>()
{
new OpenApiOperation
{
OperationId = "OperationIdSetInMetadata",
Parameters = new List<OpenApiParameter>()
{
new OpenApiParameter
{
Name = "ParameterInMetadata"
}
}
}
},
RouteValues = new Dictionary<string, string>
{
["controller"] = methodInfo.DeclaringType.Name.Replace("Controller", string.Empty)
}
};
var subject = Subject(
apiDescriptions: new[]
{
ApiDescriptionFactory.Create(
actionDescriptor,
methodInfo,
groupName: "v1",
httpMethod: "POST",
relativePath: "resource",
parameterDescriptions: new[]
{
new ApiParameterDescription
{
Name = "ParameterInMetadata",
ModelMetadata = ModelMetadataFactory.CreateForType(typeof(string))
}
}),
}
);

var document = subject.GetSwagger("v1");

Assert.Equal("OperationIdSetInMetadata", document.Paths["/resource"].Operations[OperationType.Post].OperationId);
Assert.Equal("ParameterInMetadata", document.Paths["/resource"].Operations[OperationType.Post].Parameters[0].Name);
Assert.NotNull(document.Paths["/resource"].Operations[OperationType.Post].Parameters[0].Schema);
Assert.Equal("string", document.Paths["/resource"].Operations[OperationType.Post].Parameters[0].Schema.Type);
}

[Fact]
public void GetSwagger_SetsOperationIdToNull_IfActionHasNoEndpointMetadata()
{
Expand Down