Skip to content
Draft
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
11 changes: 11 additions & 0 deletions .autover/changes/durable-execution-annotations-integration.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"Projects": [
{
"Name": "Amazon.Lambda.Annotations",
"Type": "Minor",
"ChangelogMessages": [
"Add [DurableExecution] attribute and source generator support for durable execution functions. A method annotated with [LambdaFunction] and [DurableExecution] generates a handler wrapper that delegates to Amazon.Lambda.DurableExecution.DurableFunction.WrapAsync, and emits a DurableConfig block plus the lambda:CheckpointDurableExecution / lambda:GetDurableExecutionState IAM permissions in the generated CloudFormation/SAM template. Validates that durable functions are executable, Zip-packaged, and have the (TInput, IDurableContext) -> Task/Task<TOutput> signature (AWSLambda0140-AWSLambda0143). Preview."
]
}
]
}
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,10 @@
<Generator>TextTemplatingFilePreprocessor</Generator>
<LastGenOutput>AuthorizerInvoke.cs</LastGenOutput>
</None>
<None Update="Templates\DurableExecutionInvoke.tt">
<Generator>TextTemplatingFilePreprocessor</Generator>
<LastGenOutput>DurableExecutionInvoke.cs</LastGenOutput>
</None>
</ItemGroup>

<ItemGroup>
Expand Down Expand Up @@ -151,6 +155,11 @@
<AutoGen>True</AutoGen>
<DependentUpon>AuthorizerInvoke.tt</DependentUpon>
</Compile>
<Compile Update="Templates\DurableExecutionInvoke.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>DurableExecutionInvoke.tt</DependentUpon>
</Compile>
</ItemGroup>

<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,7 @@ AWSLambda0136 | AWSLambdaCSharpGenerator | Error | Invalid S3EventAttribute
AWSLambda0137 | AWSLambdaCSharpGenerator | Error | Invalid DynamoDBEventAttribute
AWSLambda0138 | AWSLambdaCSharpGenerator | Error | Invalid SNSEventAttribute
AWSLambda0139 | AWSLambdaCSharpGenerator | Error | Invalid ScheduleEventAttribute
AWSLambda0140 | AWSLambdaCSharpGenerator | Error | DurableExecution requires an executable project
AWSLambda0141 | AWSLambdaCSharpGenerator | Error | DurableExecution requires Zip packaging
AWSLambda0142 | AWSLambdaCSharpGenerator | Error | Invalid DurableExecution method signature
AWSLambda0143 | AWSLambdaCSharpGenerator | Info | DurableExecution function with explicit Role needs checkpoint permissions
Original file line number Diff line number Diff line change
Expand Up @@ -302,5 +302,33 @@ public static class DiagnosticDescriptors
category: "AWSLambdaCSharpGenerator",
DiagnosticSeverity.Error,
isEnabledByDefault: true);

public static readonly DiagnosticDescriptor DurableExecutionRequiresExecutable = new DiagnosticDescriptor(id: "AWSLambda0140",
title: "DurableExecution requires an executable project",
messageFormat: "A method annotated with [DurableExecution] requires the project to output an executable (set 'OutputType' to 'Exe' in the .csproj). Class library handlers are not supported for durable functions in preview.",
category: "AWSLambdaCSharpGenerator",
DiagnosticSeverity.Error,
isEnabledByDefault: true);

public static readonly DiagnosticDescriptor DurableExecutionZipOnly = new DiagnosticDescriptor(id: "AWSLambda0141",
title: "DurableExecution requires Zip packaging",
messageFormat: "A method annotated with [DurableExecution] requires PackageType to be Zip. Image (container) packaging is not supported for durable functions.",
category: "AWSLambdaCSharpGenerator",
DiagnosticSeverity.Error,
isEnabledByDefault: true);

public static readonly DiagnosticDescriptor DurableExecutionInvalidSignature = new DiagnosticDescriptor(id: "AWSLambda0142",
title: "Invalid DurableExecution method signature",
messageFormat: "A method annotated with [DurableExecution] must have the signature (TInput, Amazon.Lambda.DurableExecution.IDurableContext) returning Task or Task<TOutput>: {0}",
category: "AWSLambdaCSharpGenerator",
DiagnosticSeverity.Error,
isEnabledByDefault: true);

public static readonly DiagnosticDescriptor DurableExecutionExplicitRoleNeedsCheckpointPolicy = new DiagnosticDescriptor(id: "AWSLambda0143",
title: "DurableExecution function with explicit Role needs checkpoint permissions",
messageFormat: "The [DurableExecution] function uses an explicit Role, so the generator will not add the durable checkpoint policy. Attach the 'lambda:CheckpointDurableExecution' and 'lambda:GetDurableExecutionState' actions to the role manually.",
category: "AWSLambdaCSharpGenerator",
DiagnosticSeverity.Info,
isEnabledByDefault: true);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,15 @@ public static AttributeModel Build(AttributeData att, GeneratorExecutionContext
Type = TypeModelBuilder.Build(att.AttributeClass, context)
};
}
else if (att.AttributeClass.Equals(context.Compilation.GetTypeByMetadataName(TypeFullNames.DurableExecutionAttribute), SymbolEqualityComparer.Default))
{
var data = DurableExecutionAttributeBuilder.Build(att);
model = new AttributeModel<DurableExecutionAttribute>
{
Data = data,
Type = TypeModelBuilder.Build(att.AttributeClass, context)
};
}
else if (att.AttributeClass.Equals(context.Compilation.GetTypeByMetadataName(TypeFullNames.HttpApiAuthorizerAttribute), SymbolEqualityComparer.Default))
{
var data = HttpApiAuthorizerAttributeBuilder.Build(att);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

using Amazon.Lambda.Annotations;
using Microsoft.CodeAnalysis;

namespace Amazon.Lambda.Annotations.SourceGenerator.Models.Attributes
{
/// <summary>
/// Builder for <see cref="DurableExecutionAttribute"/>. Reads named arguments from the
/// <see cref="AttributeData"/>; assigning each property also sets its corresponding
/// <c>IsXxxSet</c> flag so unset values can be omitted from the generated template.
/// </summary>
public class DurableExecutionAttributeBuilder
{
public static DurableExecutionAttribute Build(AttributeData att)
{
var data = new DurableExecutionAttribute();

foreach (var pair in att.NamedArguments)
{
if (pair.Key == nameof(data.RetentionPeriodInDays) && pair.Value.Value is int retentionPeriodInDays)
{
data.RetentionPeriodInDays = retentionPeriodInDays;
}
else if (pair.Key == nameof(data.ExecutionTimeout) && pair.Value.Value is int executionTimeout)
{
data.ExecutionTimeout = executionTimeout;
}
}

return data;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ public enum EventType
DynamoDB,
Schedule,
Authorizer,
ALB
ALB,
DurableExecution
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@ public static HashSet<EventType> Build(IMethodSymbol lambdaMethodSymbol,
{
events.Add(EventType.Schedule);
}
else if (attribute.AttributeClass.ToDisplayString() == TypeFullNames.DurableExecutionAttribute)
{
events.Add(EventType.DurableExecution);
}
else if (attribute.AttributeClass.ToDisplayString() == TypeFullNames.HttpApiAuthorizerAttribute
|| attribute.AttributeClass.ToDisplayString() == TypeFullNames.RestApiAuthorizerAttribute)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,11 @@ private static IList<string> BuildUsings(LambdaMethodModel lambdaMethodModel,
namespaces.Add("Amazon.Lambda.Annotations.APIGateway");
}

if (lambdaMethodSymbol.HasAttribute(context, TypeFullNames.DurableExecutionAttribute))
{
namespaces.Add("Amazon.Lambda.DurableExecution");
}

return namespaces;
}

Expand All @@ -71,6 +76,15 @@ private static TypeModel BuildResponseType(IMethodSymbol lambdaMethodSymbol,
{
var task = context.Compilation.GetTypeByMetadataName(TypeFullNames.Task1);

// Durable functions always produce Task<DurableExecutionInvocationOutput>; the generated
// wrapper delegates to DurableFunction.WrapAsync, which returns that envelope.
if (lambdaMethodSymbol.HasAttribute(context, TypeFullNames.DurableExecutionAttribute))
{
var outputType = context.Compilation.GetTypeByMetadataName(TypeFullNames.DurableExecutionInvocationOutput);
var genericTask = task.Construct(outputType);
return TypeModelBuilder.Build(genericTask, context);
}

if (lambdaMethodModel.ReturnsIHttpResults)
{
var typeStream = context.Compilation.GetTypeByMetadataName(TypeFullNames.Stream);
Expand Down Expand Up @@ -217,7 +231,22 @@ private static IList<ParameterModel> BuildParameters(IMethodSymbol lambdaMethodS
Documentation = "The ILambdaContext that provides methods for logging and describing the Lambda environment."
};

if (lambdaMethodSymbol.HasAttribute(context, TypeFullNames.HttpApiAuthorizerAttribute))
// Durable functions receive the service envelope (DurableExecutionInvocationInput); the
// generated wrapper passes it straight to DurableFunction.WrapAsync along with the context.
if (lambdaMethodSymbol.HasAttribute(context, TypeFullNames.DurableExecutionAttribute))
{
var symbol = context.Compilation.GetTypeByMetadataName(TypeFullNames.DurableExecutionInvocationInput);
var type = TypeModelBuilder.Build(symbol, context);
var requestParameter = new ParameterModel
{
Name = "__request__",
Type = type,
Documentation = "The durable execution service envelope that will be processed by the Lambda function handler."
};
parameters.Add(requestParameter);
parameters.Add(contextParameter);
}
else if (lambdaMethodSymbol.HasAttribute(context, TypeFullNames.HttpApiAuthorizerAttribute))
{
// For HTTP API authorizer functions, the generated handler accepts the authorizer request type
var authorizerAttribute = lambdaMethodSymbol.GetAttributeData(context, TypeFullNames.HttpApiAuthorizerAttribute);
Expand Down
Loading
Loading