示例#1
0
    public async Task GetResponseMetadata_ReturnsValuesFromApiConventionMethodAttribute()
    {
        // Arrange
        var compilation = await GetResponseMetadataCompilation();

        var controller = compilation.GetTypeByMetadataName($"{Namespace}.{nameof(GetResponseMetadata_ControllerActionWithAttributes)}");
        var method     = (IMethodSymbol)controller.GetMembers(nameof(GetResponseMetadata_ControllerActionWithAttributes.GetResponseMetadata_ReturnsValuesFromApiConventionMethodAttribute)).First();

        Assert.True(ApiControllerSymbolCache.TryCreate(compilation, out var symbolCache));

        // Act
        var result = SymbolApiResponseMetadataProvider.GetDeclaredResponseMetadata(symbolCache, method);

        // Assert
        Assert.Collection(
            result,
            metadata =>
        {
            Assert.Equal(200, metadata.StatusCode);
            Assert.NotNull(metadata.Attribute);
        },
            metadata =>
        {
            Assert.Equal(404, metadata.StatusCode);
            Assert.NotNull(metadata.Attribute);
        },
            metadata =>
        {
            Assert.True(metadata.IsDefault);
        });
    }
    private void InitializeWorker(CompilationStartAnalysisContext compilationStartAnalysisContext, ApiControllerSymbolCache symbolCache)
    {
        compilationStartAnalysisContext.RegisterOperationAction(operationStartContext =>
        {
            var method = (IMethodSymbol)operationStartContext.ContainingSymbol;
            if (!ApiControllerFacts.IsApiControllerAction(symbolCache, method))
            {
                return;
            }

            var declaredResponseMetadata = SymbolApiResponseMetadataProvider.GetDeclaredResponseMetadata(symbolCache, method);
            var hasUnreadableStatusCodes = !ActualApiResponseMetadataFactory.TryGetActualResponseMetadata(symbolCache, (IMethodBodyOperation)operationStartContext.Operation, operationStartContext.CancellationToken, out var actualResponseMetadata);

            var hasUndocumentedStatusCodes = false;
            foreach (var actualMetadata in actualResponseMetadata)
            {
                var location = actualMetadata.ReturnOperation.ReturnedValue.Syntax.GetLocation();

                if (!DeclaredApiResponseMetadata.Contains(declaredResponseMetadata, actualMetadata))
                {
                    hasUndocumentedStatusCodes = true;
                    if (actualMetadata.IsDefaultResponse)
                    {
                        operationStartContext.ReportDiagnostic(Diagnostic.Create(
                                                                   ApiDiagnosticDescriptors.API1001_ActionReturnsUndocumentedSuccessResult,
                                                                   location));
                    }
                    else
                    {
                        operationStartContext.ReportDiagnostic(Diagnostic.Create(
                                                                   ApiDiagnosticDescriptors.API1000_ActionReturnsUndocumentedStatusCode,
                                                                   location,
                                                                   actualMetadata.StatusCode));
                    }
                }
            }

            if (hasUndocumentedStatusCodes || hasUnreadableStatusCodes)
            {
                // If we produced analyzer warnings about undocumented status codes, don't attempt to determine
                // if there are documented status codes that are missing from the method body.
                return;
            }

            for (var i = 0; i < declaredResponseMetadata.Count; i++)
            {
                var declaredMetadata = declaredResponseMetadata[i];
                if (!Contains(actualResponseMetadata, declaredMetadata))
                {
                    operationStartContext.ReportDiagnostic(Diagnostic.Create(
                                                               ApiDiagnosticDescriptors.API1002_ActionDoesNotReturnDocumentedStatusCode,
                                                               method.Locations[0],
                                                               declaredMetadata.StatusCode));
                }
            }
        }, OperationKind.MethodBody);
    }
示例#3
0
    private async Task GetStatusCodeTest(string actionName, int expected)
    {
        var compilation = await GetResponseMetadataCompilation();

        var controller = compilation.GetTypeByMetadataName($"{Namespace}.{nameof(GetResponseMetadata_ControllerActionWithAttributes)}");
        var method     = (IMethodSymbol)controller.GetMembers(actionName).First();
        var attribute  = method.GetAttributes().First();

        var statusCode = SymbolApiResponseMetadataProvider.GetStatusCode(attribute);

        Assert.Equal(expected, statusCode);
    }
示例#4
0
    public async Task GetResponseMetadata_IgnoresProducesAttribute()
    {
        // Arrange
        var compilation = await GetResponseMetadataCompilation();

        var controller = compilation.GetTypeByMetadataName($"{Namespace}.{nameof(GetResponseMetadata_ControllerActionWithAttributes)}");
        var method     = (IMethodSymbol)controller.GetMembers(nameof(GetResponseMetadata_ControllerActionWithAttributes.ActionWithProducesAttribute)).First();

        Assert.True(ApiControllerSymbolCache.TryCreate(compilation, out var symbolCache));

        // Act
        var result = SymbolApiResponseMetadataProvider.GetDeclaredResponseMetadata(symbolCache, method);

        // Assert
        Assert.Collection(
            result,
            metadata => Assert.True(metadata.IsImplicit));
    }
示例#5
0
    public async Task GetErrorResponseType_ReturnsTypeDefinedAtAction()
    {
        // Arrange
        var compilation = await GetCompilation(nameof(GetErrorResponseType_ReturnsTypeDefinedAtAction));

        var expected = compilation.GetTypeByMetadataName(typeof(GetErrorResponseType_ReturnsTypeDefinedAtActionModel).FullName);

        var type   = compilation.GetTypeByMetadataName(typeof(GetErrorResponseType_ReturnsTypeDefinedAtActionController).FullName);
        var method = (IMethodSymbol)type.GetMembers("Action").First();

        Assert.True(ApiControllerSymbolCache.TryCreate(compilation, out var symbolCache));

        // Act
        var result = SymbolApiResponseMetadataProvider.GetErrorResponseType(symbolCache, method);

        // Assert
        Assert.Same(expected, result);
    }
示例#6
0
    public async Task GetErrorResponseType_ReturnsProblemDetails_IfNoAttributeIsDiscovered()
    {
        // Arrange
        var compilation = await GetCompilation(nameof(GetErrorResponseType_ReturnsProblemDetails_IfNoAttributeIsDiscovered));

        var expected = compilation.GetTypeByMetadataName(typeof(ProblemDetails).FullName);

        var type   = compilation.GetTypeByMetadataName(typeof(GetErrorResponseType_ReturnsProblemDetails_IfNoAttributeIsDiscoveredController).FullName);
        var method = (IMethodSymbol)type.GetMembers("Action").First();

        Assert.True(ApiControllerSymbolCache.TryCreate(compilation, out var symbolCache));

        // Act
        var result = SymbolApiResponseMetadataProvider.GetErrorResponseType(symbolCache, method);

        // Assert
        Assert.Same(expected, result);
    }
示例#7
0
    public async Task GetResponseMetadata_ReturnsEmptySequence_IfNoAttributesArePresent_ForPostAction()
    {
        // Arrange
        var compilation = await GetResponseMetadataCompilation();

        var controller = compilation.GetTypeByMetadataName($"{Namespace}.{nameof(GetResponseMetadata_ControllerWithoutConvention)}");
        var method     = (IMethodSymbol)controller.GetMembers(nameof(GetResponseMetadata_ControllerWithoutConvention.PostPerson)).First();

        Assert.True(ApiControllerSymbolCache.TryCreate(compilation, out var symbolCache));

        // Act
        var result = SymbolApiResponseMetadataProvider.GetDeclaredResponseMetadata(symbolCache, method);

        // Assert
        Assert.Collection(
            result,
            metadata => Assert.True(metadata.IsImplicit));
    }
示例#8
0
    public async Task GetDeclaredResponseMetadata_ApiConventionTypeAttributeOnBaseType_Works()
    {
        // Arrange
        var type        = typeof(GetDeclaredResponseMetadata_ApiConventionTypeAttributeOnBaseType);
        var compilation = await GetResponseMetadataCompilation();

        var controller = compilation.GetTypeByMetadataName(type.FullName);
        var method     = (IMethodSymbol)controller.GetMembers().First();

        Assert.True(ApiControllerSymbolCache.TryCreate(compilation, out var symbolCache));

        // Act
        var result = SymbolApiResponseMetadataProvider.GetDeclaredResponseMetadata(symbolCache, method);

        // Assert
        // We should expect 3 entries specified by DefaultApiConventions.Post
        Assert.Collection(
            result.OrderBy(r => r.StatusCode),
            metadata => Assert.True(metadata.IsDefault),
            metadata => Assert.Equal(201, metadata.StatusCode),
            metadata => Assert.Equal(400, metadata.StatusCode));
    }
示例#9
0
    private async Task GetResponseMetadata_WorksForInvalidOrUnsupportedAttributes(string typeName, string methodName)
    {
        // Arrange
        var compilation = await GetResponseMetadataCompilation();

        var controller = compilation.GetTypeByMetadataName($"{Namespace}.{typeName}");
        var method     = (IMethodSymbol)controller.GetMembers(methodName).First();

        Assert.True(ApiControllerSymbolCache.TryCreate(compilation, out var symbolCache));

        // Act
        var result = SymbolApiResponseMetadataProvider.GetDeclaredResponseMetadata(symbolCache, method);

        // Assert
        Assert.Collection(
            result,
            metadata =>
        {
            Assert.Equal(200, metadata.StatusCode);
            Assert.Same(method, metadata.AttributeSource);
        });
    }
示例#10
0
    public async Task GetResponseMetadata_ReturnsValueFromProducesResponseType_WhenStatusCodeIsSpecifiedInConstructorWithResponseType()
    {
        // Arrange
        var compilation = await GetResponseMetadataCompilation();

        var controller = compilation.GetTypeByMetadataName($"{Namespace}.{nameof(GetResponseMetadata_ControllerActionWithAttributes)}");
        var method     = (IMethodSymbol)controller.GetMembers(nameof(GetResponseMetadata_ControllerActionWithAttributes.ActionWithProducesResponseType_StatusCodeAndTypeInConstructor)).First();

        Assert.True(ApiControllerSymbolCache.TryCreate(compilation, out var symbolCache));

        // Act
        var result = SymbolApiResponseMetadataProvider.GetDeclaredResponseMetadata(symbolCache, method);

        // Assert
        Assert.Collection(
            result,
            metadata =>
        {
            Assert.Equal(202, metadata.StatusCode);
            Assert.NotNull(metadata.Attribute);
            Assert.Equal(method, metadata.AttributeSource);
        });
    }
示例#11
0
    protected override async Task <Document> GetChangedDocumentAsync(CancellationToken cancellationToken)
    {
        var nullableContext = await CreateCodeActionContext(cancellationToken).ConfigureAwait(false);

        if (nullableContext == null)
        {
            return(_document);
        }

        var context = nullableContext.Value;

        var declaredResponseMetadata = SymbolApiResponseMetadataProvider.GetDeclaredResponseMetadata(context.SymbolCache, context.Method);
        var errorResponseType        = SymbolApiResponseMetadataProvider.GetErrorResponseType(context.SymbolCache, context.Method);

        var results = CalculateStatusCodesToApply(context, declaredResponseMetadata);

        if (results.Count == 0)
        {
            return(_document);
        }

        var documentEditor = await DocumentEditor.CreateAsync(_document, cancellationToken).ConfigureAwait(false);

        var addUsingDirective = false;

        foreach (var item in results.OrderBy(s => s.statusCode))
        {
            var statusCode = item.statusCode;
            var returnType = item.typeSymbol;

            AttributeSyntax attributeSyntax;
            bool            addUsing;

            if (statusCode >= 400 && returnType != null && !SymbolEqualityComparer.Default.Equals(returnType, errorResponseType))
            {
                // If a returnType was discovered and is different from the errorResponseType, use it in the result.
                attributeSyntax = CreateProducesResponseTypeAttribute(context, statusCode, returnType, out addUsing);
            }
            else
            {
                attributeSyntax = CreateProducesResponseTypeAttribute(context, statusCode, out addUsing);
            }

            documentEditor.AddAttribute(context.MethodSyntax, attributeSyntax);
            addUsingDirective |= addUsing;
        }

        if (!declaredResponseMetadata.Any(m => m.IsDefault && SymbolEqualityComparer.Default.Equals(m.AttributeSource, context.Method)))
        {
            // Add a ProducesDefaultResponseTypeAttribute if the method does not already have one.
            documentEditor.AddAttribute(context.MethodSyntax, CreateProducesDefaultResponseTypeAttribute());
        }

        var apiConventionMethodAttribute = context.Method.GetAttributes(context.SymbolCache.ApiConventionMethodAttribute).FirstOrDefault();

        if (apiConventionMethodAttribute != null)
        {
            // Remove [ApiConventionMethodAttribute] declared on the method since it's no longer required
            var attributeSyntax = await apiConventionMethodAttribute
                                  .ApplicationSyntaxReference
                                  .GetSyntaxAsync(cancellationToken)
                                  .ConfigureAwait(false);

            documentEditor.RemoveNode(attributeSyntax);
        }

        var document = documentEditor.GetChangedDocument();

        var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);

        if (root is CompilationUnitSyntax compilationUnit && addUsingDirective)
        {
            const string @namespace = "Microsoft.AspNetCore.Http";

            var declaredUsings = new HashSet <string>(compilationUnit.Usings.Select(x => x.Name.ToString()));

            if (!declaredUsings.Contains(@namespace))
            {
                root = compilationUnit.AddUsings(SyntaxFactory.UsingDirective(SyntaxFactory.ParseName(@namespace)));
            }
        }

        return(document.WithSyntaxRoot(root));
    }