Skip to content

Commit

Permalink
feat: Add wait policy to compiler (#105)
Browse files Browse the repository at this point in the history
  • Loading branch information
Mielek authored Feb 27, 2025
1 parent 9d67bf5 commit 87dd1c6
Show file tree
Hide file tree
Showing 5 changed files with 280 additions and 0 deletions.
13 changes: 13 additions & 0 deletions src/Authoring/IBackendContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -88,4 +88,17 @@ public interface IBackendContext : IHaveExpressionContext
/// <param name="config">Configuration of retry policy</param>
/// <param name="section">Child policies which should be retried</param>
void Retry(RetryConfig config, Action section);

/// <summary>
/// The wait policy executes its immediate child policies in parallel, and waits for either all or one of its immediate
/// child policies to complete before it completes.
/// The wait policy can have as its immediate child policies one or more of the following: send-request,
/// cache-lookup-value, and choose policies.
/// </summary>
/// <param name="section">Child policies which should be awaited</param>
/// <param name="waitFor">
/// Determines whether the wait policy waits for all immediate child policies to be completed or just one.
/// Policy expressions are allowed.
/// </param>
void Wait(Action section, string? waitFor = null);
}
13 changes: 13 additions & 0 deletions src/Authoring/IInboundContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -283,4 +283,17 @@ public interface IInboundContext : IHaveExpressionContext
/// <param name="config">Configuration of retry policy</param>
/// <param name="section">Child policies which should be retried</param>
void Retry(RetryConfig config, Action section);

/// <summary>
/// The wait policy executes its immediate child policies in parallel, and waits for either all or one of its immediate
/// child policies to complete before it completes.
/// The wait policy can have as its immediate child policies one or more of the following: send-request,
/// cache-lookup-value, and choose policies.
/// </summary>
/// <param name="section">Child policies which should be awaited</param>
/// <param name="waitFor">
/// Determines whether the wait policy waits for all immediate child policies to be completed or just one.
/// Policy expressions are allowed.
/// </param>
void Wait(Action section, string? waitFor = null);
}
13 changes: 13 additions & 0 deletions src/Authoring/IOutboundContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -161,4 +161,17 @@ public interface IOutboundContext : IHaveExpressionContext
/// <param name="config">Configuration of retry policy</param>
/// <param name="section">Child policies which should be retried</param>
void Retry(RetryConfig config, Action section);

/// <summary>
/// The wait policy executes its immediate child policies in parallel, and waits for either all or one of its immediate
/// child policies to complete before it completes.
/// The wait policy can have as its immediate child policies one or more of the following: send-request,
/// cache-lookup-value, and choose policies.
/// </summary>
/// <param name="section">Child policies which should be awaited</param>
/// <param name="waitFor">
/// Determines whether the wait policy waits for all immediate child policies to be completed or just one.
/// Policy expressions are allowed.
/// </param>
void Wait(Action section, string? waitFor = null);
}
68 changes: 68 additions & 0 deletions src/Core/Compiling/Policy/WaitCompiler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
using System.Xml.Linq;

using Azure.ApiManagement.PolicyToolkit.Authoring;
using Azure.ApiManagement.PolicyToolkit.Compiling.Diagnostics;
using Azure.ApiManagement.PolicyToolkit.Compiling.Syntax;

using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;

namespace Azure.ApiManagement.PolicyToolkit.Compiling.Policy;

public class WaitCompiler : IMethodPolicyHandler
{
private readonly Lazy<BlockCompiler> _blockCompiler;

public WaitCompiler(Lazy<BlockCompiler> blockCompiler)
{
_blockCompiler = blockCompiler;
}

public string MethodName { get; } = nameof(IInboundContext.Wait);

public void Handle(ICompilationContext context, InvocationExpressionSyntax node)
{
if (node.ArgumentList.Arguments.Count is > 2 or < 1)
{
context.Report(Diagnostic.Create(
CompilationErrors.ArgumentCountMissMatchForPolicy,
node.ArgumentList.GetLocation(),
"wait"));
return;
}

ExpressionSyntax childPoliciesLambdaExpression = node.ArgumentList.Arguments[0].Expression;
if (childPoliciesLambdaExpression is not LambdaExpressionSyntax lambda)
{
context.Report(Diagnostic.Create(
CompilationErrors.ValueShouldBe,
childPoliciesLambdaExpression.GetLocation(),
"wait",
nameof(LambdaExpressionSyntax)));
return;
}

if (lambda.Block is null)
{
context.Report(Diagnostic.Create(
CompilationErrors.NotSupportedStatement,
lambda.GetLocation(),
childPoliciesLambdaExpression.GetType().FullName
));
return;
}

XElement element = new("wait");

if (node.ArgumentList.Arguments.Count == 2)
{
string value = node.ArgumentList.Arguments[1].Expression.ProcessParameter(context);
element.Add(new XAttribute("for", value));
}

SubCompilationContext subContext = new(context, element);
_blockCompiler.Value.Compile(subContext, lambda.Block);

context.AddPolicy(element);
}
}
173 changes: 173 additions & 0 deletions test/Test.Core/Compiling/WaitTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
namespace Azure.ApiManagement.PolicyToolkit.Compiling;

[TestClass]
public class WaitTests
{
[TestMethod]
[DataRow(
"""
[Document]
public class PolicyDocument : IDocument
{
public void Inbound(IInboundContext context)
{
context.Wait(() =>
{
context.SendRequest(new SendRequestConfig {
ResponseVariableName = "variable"
});
});
}
public void Backend(IBackendContext context)
{
context.Wait(() =>
{
context.SendRequest(new SendRequestConfig {
ResponseVariableName = "variable"
});
});
}
public void Outbound(IOutboundContext context)
{
context.Wait(() =>
{
context.SendRequest(new SendRequestConfig {
ResponseVariableName = "variable"
});
});
}
}
""",
"""
<policies>
<inbound>
<wait>
<send-request response-variable-name="variable" />
</wait>
</inbound>
<backend>
<wait>
<send-request response-variable-name="variable" />
</wait>
</backend>
<outbound>
<wait>
<send-request response-variable-name="variable" />
</wait>
</outbound>
</policies>
""",
DisplayName = "Should compile wait policy in sections"
)]
[DataRow(
"""
[Document]
public class PolicyDocument : IDocument
{
public void Inbound(IInboundContext context)
{
context.Wait(() =>
{
context.SendRequest(new SendRequestConfig {
ResponseVariableName = "variable"
});
},
"any");
}
}
""",
"""
<policies>
<inbound>
<wait for="any">
<send-request response-variable-name="variable" />
</wait>
</inbound>
</policies>
""",
DisplayName = "Should compile wait policy with for attribute"
)]
[DataRow(
"""
[Document]
public class PolicyDocument : IDocument
{
public void Inbound(IInboundContext context)
{
context.Wait(() =>
{
context.SendRequest(new SendRequestConfig {
ResponseVariableName = "variable"
});
},
GetForAtt(context.ExpressionContext));
}
string GetForAtt(IExpressionContext context) => context.Variables.ContainsKey("any") ? "any" : "all";
}
""",
"""
<policies>
<inbound>
<wait for="@(context.Variables.ContainsKey("any") ? "any" : "all")">
<send-request response-variable-name="variable" />
</wait>
</inbound>
</policies>
""",
DisplayName = "Should compile wait policy with expression in for attribute"
)]
[DataRow(
"""
[Document]
public class PolicyDocument : IDocument
{
public void Inbound(IInboundContext context)
{
context.Wait(() =>
{
if(CacheCondition(context.ExpressionContext))
{
context.CacheLookupValue(new CacheLookupValueConfig {
Key = "key",
VariableName = "cache"
});
}
if(RequestCondition(context.ExpressionContext))
{
context.SendRequest(new SendRequestConfig {
ResponseVariableName = "request"
});
}
});
}
bool CacheCondition(IExpressionContext context) => !context.Variables.ContainsKey("cache");
bool RequestCondition(IExpressionContext context) => !context.Variables.ContainsKey("request");
}
""",
"""
<policies>
<inbound>
<wait>
<choose>
<when condition="@(!context.Variables.ContainsKey("cache"))">
<cache-lookup-value key="key" variable-name="cache" />
</when>
</choose>
<choose>
<when condition="@(!context.Variables.ContainsKey("request"))">
<send-request response-variable-name="request" />
</when>
</choose>
</wait>
</inbound>
</policies>
""",
DisplayName = "Should compile wait policy with send-request, cache-lookup-value and choose policies"
)]
public void ShouldCompileWaitPolicy(string code, string expectedXml)
{
code.CompileDocument().Should().BeSuccessful().And.DocumentEquivalentTo(expectedXml);
}
}

0 comments on commit 87dd1c6

Please sign in to comment.