Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion example/source/ApiOperationPolicy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public void Inbound(IInboundContext context)
{
context.AuthenticationManagedIdentity(new ManagedIdentityAuthenticationConfig()
{
Resource = "https://management.azure.com/",
Resource = Constants.AzureManagementUrl,
});
}
}
Expand Down
9 changes: 9 additions & 0 deletions example/source/Constants.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

namespace Contoso.Apis;

public static class Constants
{
public const string AzureManagementUrl = "https://management.azure.com/";
}
66 changes: 61 additions & 5 deletions src/Core/Compiling/CompilerUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@
using System.Diagnostics.CodeAnalysis;
using System.Xml.Linq;

using Microsoft.Azure.ApiManagement.PolicyToolkit.Authoring;
using Microsoft.Azure.ApiManagement.PolicyToolkit.Compiling.Diagnostics;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;

namespace Microsoft.Azure.ApiManagement.PolicyToolkit.Compiling;
Expand All @@ -21,6 +21,8 @@ public static string ProcessParameter(this ExpressionSyntax expression, IDocumen
return syntax.Token.ValueText;
case InvocationExpressionSyntax syntax:
return FindCode(syntax, context);
case MemberAccessExpressionSyntax syntax:
return FindCode(syntax, context);
// case InterpolatedStringExpressionSyntax syntax:
// var interpolationParts = syntax.Contents.Select(c => c switch
// {
Expand All @@ -46,7 +48,12 @@ public static string ProcessParameter(this ExpressionSyntax expression, IDocumen

public static string FindCode(this InvocationExpressionSyntax syntax, IDocumentCompilationContext context)
{
if (syntax.Expression is not IdentifierNameSyntax identifierSyntax)
Compilation compilation = context.Compilation;
SemanticModel semanticModel = compilation.GetSemanticModel(syntax.SyntaxTree);
var symbolInfo = semanticModel.GetSymbolInfo(syntax.Expression);
var symbol = symbolInfo.Symbol ?? symbolInfo.CandidateSymbols.SingleOrDefault(s => s is IMethodSymbol);

if (symbol is not IMethodSymbol methodSymbol)
{
context.Report(Diagnostic.Create(
CompilationErrors.InvalidExpression,
Expand All @@ -55,10 +62,20 @@ public static string FindCode(this InvocationExpressionSyntax syntax, IDocumentC
return "";
}

var methodIdentifier = identifierSyntax.Identifier.ValueText;
var expressionMethod = context.SyntaxRoot.DescendantNodes()
var expressionMethod = methodSymbol.DeclaringSyntaxReferences
.Select(r => r.GetSyntax())
.OfType<MethodDeclarationSyntax>()
.First(m => m.Identifier.ValueText == methodIdentifier);
.FirstOrDefault();

if (expressionMethod is null)
{
context.Report(Diagnostic.Create(
CompilationErrors.CannotFindMethodCode,
syntax.GetLocation(),
methodSymbol.Name
));
return "";
}

expressionMethod = Normalize(expressionMethod);

Expand All @@ -76,6 +93,45 @@ public static string FindCode(this InvocationExpressionSyntax syntax, IDocumentC
}
}

public static string FindCode(this MemberAccessExpressionSyntax syntax, IDocumentCompilationContext context)
{
Compilation compilation = context.Compilation;
SemanticModel semanticModel = compilation.GetSemanticModel(syntax.SyntaxTree);
var symbolInfo = semanticModel.GetSymbolInfo(syntax);
var symbol = symbolInfo.Symbol ?? symbolInfo.CandidateSymbols.SingleOrDefault(s => s is IFieldSymbol);

if (symbol is not IFieldSymbol fieldSymbol)
{
context.Report(Diagnostic.Create(
CompilationErrors.InvalidConstantReference,
syntax.GetLocation()
));
return "";
}

if (!fieldSymbol.IsConst)
{
context.Report(Diagnostic.Create(
CompilationErrors.InvalidExpression,
syntax.GetLocation()
));
return "";
}

var value = fieldSymbol.ConstantValue?.ToString();
if (value is null)
{
context.Report(Diagnostic.Create(
CompilationErrors.IsNotAConstant,
syntax.GetLocation(),
fieldSymbol.Name
));
value = "";
}

return value;
}

public static InitializerValue Process(
this ObjectCreationExpressionSyntax creationSyntax,
IDocumentCompilationContext context)
Expand Down
33 changes: 33 additions & 0 deletions src/Core/Compiling/Diagnostics/CompilationErrors.cs
Original file line number Diff line number Diff line change
Expand Up @@ -215,4 +215,37 @@ public static class CompilationErrors
"Description.",
"TODO",
["APIM", "ApiManagement"]);

public readonly static DiagnosticDescriptor CannotFindMethodCode = new(
"APIM2009",
"Cannot find method code",
"Cannot find method code. Method '{0}' is not declared in the projects source code.",
"PolicyDocumentCompilation",
DiagnosticSeverity.Error,
isEnabledByDefault: true,
description: "Description.",
helpLinkUri: "TODO",
customTags: ["APIM", "ApiManagement"]);

public readonly static DiagnosticDescriptor InvalidConstantReference = new(
"APIM2010",
"Invalid constant reference for policy parameter",
"Argument should be an constant field reference",
"PolicyDocumentCompilation",
DiagnosticSeverity.Error,
isEnabledByDefault: true,
description: "Description.",
helpLinkUri: "TODO",
customTags: ["APIM", "ApiManagement"]);

public readonly static DiagnosticDescriptor IsNotAConstant = new(
"APIM2011",
"External value is not a constant",
"Field '{0}' should be a const for it to be inlined in the policy document",
"PolicyDocumentCompilation",
DiagnosticSeverity.Error,
isEnabledByDefault: true,
description: "Description.",
helpLinkUri: "TODO",
customTags: ["APIM", "ApiManagement"]);
}
27 changes: 16 additions & 11 deletions test/Test.Core/CompilerTestInitialize.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,24 +42,29 @@ public static void CompilerCleanup()
s_serviceProvider.Dispose();
}

public static IDocumentCompilationResult CompileDocument(this string document)
public static IDocumentCompilationResult CompileDocument(this string document) => document.CompileDocument([]);

public static IDocumentCompilationResult CompileDocument(this string document, params string[] separateDocuments)
{
var doc = $"""
using Microsoft.Azure.ApiManagement.PolicyToolkit.Authoring;
using Microsoft.Azure.ApiManagement.PolicyToolkit.Authoring.Expressions;
string[] docs = [document, ..separateDocuments];
var syntaxTrees = docs.Select(d =>
$"""
using Microsoft.Azure.ApiManagement.PolicyToolkit.Authoring;
using Microsoft.Azure.ApiManagement.PolicyToolkit.Authoring.Expressions;

namespace Test;
namespace Test;

{document}
""";
{d}
""")
.Select(d => CSharpSyntaxTree.ParseText(d))
.ToArray();

SyntaxTree syntaxTree = CSharpSyntaxTree.ParseText(doc);
var compilation = CSharpCompilation.Create(
Guid.NewGuid().ToString(),
syntaxTrees: [syntaxTree],
syntaxTrees: syntaxTrees,
references: References);
var semantics = compilation.GetSemanticModel(syntaxTree);
ClassDeclarationSyntax policy = syntaxTree
var semantics = compilation.GetSemanticModel(syntaxTrees[0]);
ClassDeclarationSyntax policy = syntaxTrees[0]
.GetRoot()
.DescendantNodes()
.OfType<ClassDeclarationSyntax>()
Expand Down
66 changes: 66 additions & 0 deletions test/Test.Core/ReferencingTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

namespace Test.Core;

[TestClass]
public class ReferencingTests
{
[TestMethod]
[DataRow(
"""
[Document]
public class PolicyDocument : IDocument
{
public void Inbound(IInboundContext context)
{
context.AuthenticationBasic("{{username}}", Expressions.Password(context.ExpressionContext));
}
}
""",
"""
public static class Expressions
{
public static string Password(IExpressionContext context) => context.Subscription.Key;
}
""",
"""
<policies>
<inbound>
<authentication-basic username="{{username}}" password="@(context.Subscription.Key)" />
</inbound>
</policies>
""",
DisplayName = "Should reference external expression code in policy document"
)]
[DataRow(
"""
[Document]
public class PolicyDocument : IDocument
{
public void Inbound(IInboundContext context)
{
context.AuthenticationBasic(Constants.Username, "{{password}}");
}
}
""",
"""
public static class Constants
{
public const string Username = "{{username}}";
}
""",
"""
<policies>
<inbound>
<authentication-basic username="{{username}}" password="{{password}}" />
</inbound>
</policies>
""",
DisplayName = "Should reference external constant in policy document"
)]
public void ShouldReference(string document, string externalCode, string expectedXml)
{
document.CompileDocument(externalCode).Should().BeSuccessful().And.DocumentEquivalentTo(expectedXml);
}
}
Loading