Last active
March 29, 2022 19:07
-
-
Save VincenzoCarlino/b7093d823a3d5f1e8795595c064a120f to your computer and use it in GitHub Desktop.
Schema validation
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
namespace TestApi; | |
using System.Reflection; | |
using Microsoft.AspNetCore.Mvc; | |
using Microsoft.AspNetCore.Mvc.ApiExplorer; | |
using Microsoft.AspNetCore.Mvc.Controllers; | |
using Microsoft.AspNetCore.Mvc.Infrastructure; | |
/* | |
* FOR INFORMATION PURPOSE ONLY!!! | |
* THIS CODE IS NOT READY FOR PRODUCTION. | |
*/ | |
public class SchemaValidator | |
{ | |
private const int MIN_SUCCESS_STATUS_CODE = 200; | |
private const int MAX_SUCCESS_STATUS_CODE = 299; | |
private readonly IApiDescriptionGroupCollectionProvider _apiDescriptionsProvider; | |
public SchemaValidator( | |
IApiDescriptionGroupCollectionProvider apiDescriptionsProvider | |
) | |
{ | |
_apiDescriptionsProvider = apiDescriptionsProvider; | |
} | |
public void ValidateControllerResponses() | |
{ | |
var invalidEndpoints = new List<string>(); | |
var applicableApiDescriptions = _apiDescriptionsProvider.ApiDescriptionGroups.Items | |
.SelectMany(group => group.Items); | |
foreach (var apiDescription in applicableApiDescriptions) | |
{ | |
if (apiDescription.ActionDescriptor is ControllerActionDescriptor controllerActionDescriptor) | |
{ | |
var methodReturnType = controllerActionDescriptor.MethodInfo.ReturnParameter | |
.ParameterType; | |
// Check if it is task | |
if ( | |
methodReturnType.IsGenericType && | |
methodReturnType.GetTypeInfo().GetGenericTypeDefinition() == typeof(Task<>) && | |
methodReturnType.GetTypeInfo().GetGenericArguments().Length > 0 | |
) | |
{ | |
methodReturnType = methodReturnType.GetGenericArguments().First(); | |
} | |
// Check if it is ActionResult | |
if ( | |
methodReturnType.IsGenericType && | |
methodReturnType.GetTypeInfo().GetGenericTypeDefinition() == typeof(ActionResult<>) | |
) | |
{ | |
methodReturnType = methodReturnType.GetGenericArguments().First(); | |
} | |
// Check if it is StrictJsonResult | |
if ( | |
methodReturnType.IsGenericType && | |
methodReturnType.GetTypeInfo().GetGenericTypeDefinition() == typeof(StrictJsonResult<>) | |
) | |
{ | |
methodReturnType = methodReturnType.GetGenericArguments().First(); | |
} | |
// Get list of unsupported response type (e.g. From [ProducesResponseType]) | |
var unsupportedSuccessResponseSystemTypes = GetSuccessSupportedResponsesTypes( | |
apiDescription | |
) | |
.Where(type => methodReturnType != type) | |
.ToList(); | |
foreach (var unsupportedType in unsupportedSuccessResponseSystemTypes) | |
{ | |
invalidEndpoints.Add($"[{apiDescription.HttpMethod} - {apiDescription.RelativePath}]. It should return: {unsupportedType} but instead it returns: {methodReturnType}"); | |
} | |
} | |
} | |
if (invalidEndpoints.Any()) | |
{ | |
throw new Exception($"Following endpoints are invalid: {string.Join("\n", invalidEndpoints)}"); | |
} | |
} | |
private IEnumerable<Type> GetSuccessSupportedResponsesTypes(ApiDescription apiDescription) | |
=> apiDescription.SupportedResponseTypes | |
.Where(supportedResponseType => | |
CheckIfSuccessResponseType(supportedResponseType) && | |
supportedResponseType.Type != typeof(void) // This annotation: [ProducesResponseType(200)] make supportedResponseType = System.Void | |
) | |
.Select(supportedResponseType => supportedResponseType.Type | |
.GetTypeInfo().ImplementedInterfaces.Contains(typeof(IConvertToActionResult)) | |
? supportedResponseType.Type.GetGenericArguments().First() | |
: supportedResponseType.Type | |
) | |
.DefaultIfEmpty(typeof(ActionResult)) // if list is empty, add ActionResult as default value | |
.ToList(); | |
// Check if response status is inside range of success statuses codes | |
private bool CheckIfSuccessResponseType(ApiResponseType responseType) | |
=> responseType.StatusCode is >= MIN_SUCCESS_STATUS_CODE and <= MAX_SUCCESS_STATUS_CODE; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment