Skip to content

Commit

Permalink
Use schemas generated by Swashbuckle for built-in operations
Browse files Browse the repository at this point in the history
  • Loading branch information
captainsafia authored Jul 8, 2022
1 parent 0c5016c commit 96a7929
Show file tree
Hide file tree
Showing 2 changed files with 236 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -132,17 +132,11 @@ private IDictionary<OperationType, OpenApiOperation> GenerateOperations(

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
Original file line number Diff line number Diff line change
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

0 comments on commit 96a7929

Please sign in to comment.