Add [ScheduleEvent] annotation attribute and source generator support#2323
Add [ScheduleEvent] annotation attribute and source generator support#2323GarrettBeatty wants to merge 1 commit intodevfrom
Conversation
4dd8798 to
b1c1d26
Compare
- ScheduleEventAttribute with Schedule (rate/cron), ResourceName, Description, Input, Enabled - ScheduleEventAttributeBuilder for Roslyn AttributeData parsing - Source generator wiring (TypeFullNames, SyntaxReceiver, EventTypeBuilder, AttributeModelBuilder) - CloudFormationWriter ProcessScheduleAttribute (SAM Schedule event rule) - LambdaFunctionValidator ValidateScheduleEvents - DiagnosticDescriptors InvalidScheduleEventAttribute - ScheduleEventAttributeTests (attribute unit tests) - ScheduleEventsTests (CloudFormation writer tests) - E2E source generator snapshot tests - Integration test (ScheduleEventRule) - Sample function (ScheduledProcessing) - .autover change file - README documentation
b1c1d26 to
53f2edd
Compare
There was a problem hiding this comment.
Pull request overview
Adds first-class [ScheduleEvent] support to the Lambda Annotations framework so schedule-triggered Lambdas (rate/cron) can be declared in C# and emitted into the generated SAM/CloudFormation template.
Changes:
- Introduces
ScheduleEventAttribute(schedule expression + optional ResourceName/Description/Input/Enabled). - Extends the source generator to recognize/validate schedule events and emit
Type: Scheduleevent configuration in the template. - Adds unit, snapshot, and integration tests plus docs/examples for schedule events.
Reviewed changes
Copilot reviewed 26 out of 26 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| Libraries/test/TestServerlessApp/TestServerlessApp.csproj | Adds CloudWatchEvents project reference for schedule event sample app. |
| Libraries/test/TestServerlessApp/ScheduledProcessing.cs | Adds an example scheduled handler Lambda using [ScheduleEvent]. |
| Libraries/test/TestServerlessApp/ScheduleEventExamples/ValidScheduleEvents.cs.txt | Adds valid schedule event usages for generator snapshot tests (kept as .txt). |
| Libraries/test/TestServerlessApp.IntegrationTests/TestServerlessApp.IntegrationTests.csproj | Adds AWSSDK.CloudWatchEvents dependency for schedule integration testing. |
| Libraries/test/TestServerlessApp.IntegrationTests/ScheduleEventRule.cs | Adds integration test to validate the deployed EventBridge rule configuration. |
| Libraries/test/TestServerlessApp.IntegrationTests/IntegrationTestContextFixture.cs | Updates expected Lambda function count to include the new scheduled handler. |
| Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/WriterTests/ScheduleEventsTests.cs | Adds CloudFormation writer tests for schedule event emission and property syncing. |
| Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/SourceGeneratorTests.cs | Adds snapshot-based source generator test for valid schedule events. |
| Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/ServerlessTemplates/scheduleEvents.template | Adds expected generated serverless template snapshot for schedule events. |
| Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/Schedule/ValidScheduleEvents_ProcessScheduledEvent_Generated.g.cs | Adds expected generated handler snapshot (sync). |
| Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/Schedule/ValidScheduleEvents_ProcessScheduledEventAsync_Generated.g.cs | Adds expected generated handler snapshot (async). |
| Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/ScheduleEventAttributeTests.cs | Adds unit tests for ScheduleEventAttribute defaults/derivations/validation. |
| Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/CSharpSourceGeneratorVerifier.cs | Adds metadata reference for ScheduledEvent type in the Roslyn test harness. |
| Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Amazon.Lambda.Annotations.SourceGenerators.Tests.csproj | Adds CloudWatchEvents project reference for tests. |
| Libraries/src/Amazon.Lambda.Annotations/Schedule/ScheduleEventAttribute.cs | Introduces the new ScheduleEventAttribute implementation + validation. |
| Libraries/src/Amazon.Lambda.Annotations/README.md | Documents ScheduleEvent usage and updates supported-attributes list. |
| Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Writers/CloudFormationWriter.cs | Emits Schedule SAM event entries for [ScheduleEvent]. |
| Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Validation/LambdaFunctionValidator.cs | Adds dependency checks + signature/return/attribute validation for schedule handlers. |
| Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/TypeFullNames.cs | Adds full-name constants for ScheduleEventAttribute and ScheduledEvent. |
| Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/SyntaxReceiver.cs | Recognizes ScheduleEventAttribute during syntax collection. |
| Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Models/EventTypeBuilder.cs | Maps ScheduleEventAttribute to EventType.Schedule. |
| Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Models/Attributes/ScheduleEventAttributeBuilder.cs | Builds ScheduleEventAttribute models from Roslyn attribute data. |
| Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Models/Attributes/AttributeModelBuilder.cs | Routes ScheduleEventAttribute to the new builder/model type. |
| Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Diagnostics/DiagnosticDescriptors.cs | Adds a new diagnostic descriptor for invalid ScheduleEventAttribute usage. |
| Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Diagnostics/AnalyzerReleases.Unshipped.md | Records the new diagnostic ID in analyzer release notes. |
| .autover/changes/add-scheduleevent-annotation.json | Adds versioning/changelog entry for the new feature. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| public static readonly DiagnosticDescriptor InvalidScheduleEventAttribute = new DiagnosticDescriptor(id: "AWSLambda0139", | ||
| title: "Invalid ScheduleEventAttribute", | ||
| messageFormat: "Invalid ScheduleEventAttribute encountered: {0}", | ||
| category: "AWSLambdaCSharpGenerator", | ||
| DiagnosticSeverity.Error, | ||
| isEnabledByDefault: true); |
There was a problem hiding this comment.
This new diagnostic descriptor uses ID AWSLambda0139, but the PR description calls out a new diagnostic AWSLambda0132 for ScheduleEventAttribute validation. Please reconcile the diagnostic ID in code vs. the PR description (and any external docs) so consumers know which ID to expect.
| <ItemGroup> | ||
| <!-- AWSSDK.SecurityToken is needed at runtime for environments which uses assume-role operation for credentials --> | ||
| <PackageReference Include="AWSSDK.SecurityToken" Version="3.7.1.99" /> | ||
| <PackageReference Include="AWSSDK.CloudWatchEvents" Version="3.7.*" /> |
There was a problem hiding this comment.
The package reference uses a floating version (3.7.*), while other test projects in this repo pin exact AWSSDK package versions. Floating versions can make CI non-deterministic. Prefer an explicit AWSSDK.CloudWatchEvents version aligned with the other 3.7.x dependencies used by the integration tests.
| <PackageReference Include="AWSSDK.CloudWatchEvents" Version="3.7.*" /> | |
| <PackageReference Include="AWSSDK.CloudWatchEvents" Version="3.7.1.99" /> |
| var rulesResponse = await eventsClient.ListRulesAsync(new ListRulesRequest()); | ||
|
|
||
| // Find the rule targeting our function | ||
| var matchingRule = rulesResponse.Rules.FirstOrDefault(r => | ||
| r.Name.Contains("FiveMinuteSchedule") || r.Name.Contains("ScheduledHandler")); | ||
|
|
There was a problem hiding this comment.
ListRulesAsync is paginated and may not return the matching rule on the first page. Also, listing all rules in the account/region and then searching by substring can create false positives or flaky tests in shared accounts. Consider using pagination and narrowing the query (e.g., NamePrefix based on the stack name, or ListRuleNamesByTarget/ListTargetsByRule to ensure the rule actually targets the deployed function).
| var rulesResponse = await eventsClient.ListRulesAsync(new ListRulesRequest()); | |
| // Find the rule targeting our function | |
| var matchingRule = rulesResponse.Rules.FirstOrDefault(r => | |
| r.Name.Contains("FiveMinuteSchedule") || r.Name.Contains("ScheduledHandler")); | |
| Rule matchingRule = null; | |
| string nextToken = null; | |
| do | |
| { | |
| var rulesResponse = await eventsClient.ListRulesAsync(new ListRulesRequest | |
| { | |
| NextToken = nextToken | |
| }); | |
| foreach (var rule in rulesResponse.Rules.Where(r => | |
| string.Equals(r.ScheduleExpression, "rate(5 minutes)") && | |
| string.Equals(r.Description, "Runs every 5 minutes"))) | |
| { | |
| var targetsResponse = await eventsClient.ListTargetsByRuleAsync(new ListTargetsByRuleRequest | |
| { | |
| Rule = rule.Name | |
| }); | |
| if (targetsResponse.Targets.Any(t => t.Arn != null && t.Arn.Contains($":function:{lambdaFunctionName}"))) | |
| { | |
| matchingRule = rule; | |
| break; | |
| } | |
| } | |
| if (matchingRule != null) | |
| { | |
| break; | |
| } | |
| nextToken = rulesResponse.NextToken; | |
| } | |
| while (!string.IsNullOrEmpty(nextToken)); |
| var lambdaFunctionName = _fixture.LambdaFunctions.FirstOrDefault(x => string.Equals(x.LogicalId, "ScheduledHandler"))?.Name; | ||
| Assert.NotNull(lambdaFunctionName); |
There was a problem hiding this comment.
lambdaFunctionName is retrieved and asserted non-null, but it’s never used afterward. Either remove it (and just assert the function exists), or use it to scope the rule lookup (for example, by verifying the rule actually targets this function).
| /// <summary> | ||
| /// If set to false, the event source mapping will be disabled. Default value is true. | ||
| /// </summary> | ||
| public bool Enabled | ||
| { | ||
| get => enabled.GetValueOrDefault(); | ||
| set => enabled = value; | ||
| } | ||
| private bool? enabled { get; set; } | ||
| internal bool IsEnabledSet => enabled.HasValue; | ||
|
|
There was a problem hiding this comment.
Enabled is documented as defaulting to true, but the getter currently uses enabled.GetValueOrDefault() which returns false when the property was never set. This can lead to incorrect behavior for any tooling or user code that reads Enabled (and it also contradicts the XML doc). Consider returning enabled.GetValueOrDefault(true) (and keep using IsEnabledSet to decide whether to emit the property in the template).
| return resourceName; | ||
| } | ||
| // Generate a default resource name from the schedule expression | ||
| var sanitized = string.Join(string.Empty, Schedule.Where(char.IsLetterOrDigit)); |
There was a problem hiding this comment.
ResourceName derives a default name by enumerating Schedule.Where(...), but Schedule can be null (e.g., new ScheduleEventAttribute(null)), which would throw a NullReferenceException if ResourceName is accessed before validation short-circuits generation. Make the derivation null-safe (e.g., treat null as empty) so invalid attributes fail with diagnostics instead of potentially crashing the generator or user code.
| var sanitized = string.Join(string.Empty, Schedule.Where(char.IsLetterOrDigit)); | |
| var sanitized = string.Join(string.Empty, (Schedule ?? string.Empty).Where(char.IsLetterOrDigit)); |
| // Validate method parameters | ||
| var parameters = lambdaFunctionModel.LambdaMethod.Parameters; | ||
| if (parameters.Count > 2 || | ||
| (parameters.Count >= 1 && parameters[0].Type.FullName != TypeFullNames.ScheduledEvent) || | ||
| (parameters.Count == 2 && parameters[1].Type.FullName != TypeFullNames.ILambdaContext)) | ||
| { | ||
| var errorMessage = $"When using the {nameof(ScheduleEventAttribute)}, the Lambda method can accept at most 2 parameters. " + | ||
| $"The first parameter must be of type {TypeFullNames.ScheduledEvent}. " + | ||
| $"The second parameter is optional and must be of type {TypeFullNames.ILambdaContext}."; | ||
| diagnostics.Add(Diagnostic.Create(DiagnosticDescriptors.InvalidLambdaMethodSignature, methodLocation, errorMessage)); | ||
| } |
There was a problem hiding this comment.
The schedule-event method signature validation currently allows parameters.Count == 0. That’s inconsistent with the SQS/S3 validators (which require the first parameter) and with the PR description that says the first parameter must be ScheduledEvent. Update the condition to treat zero parameters as invalid and adjust the error message accordingly.
Summary
Adds
[ScheduleEvent]annotation attribute support to the Lambda Annotations framework, enabling developers to declaratively configure schedule-triggered Lambda functions directly in C# code using rate or cron expressions. The source generator automatically produces the corresponding SAM/CloudFormation template configuration at build time.User Experience
With this change, developers can write schedule-triggered Lambda functions like this:
The source generator will automatically generate the SAM template entry:
Attribute Properties
Schedulerate(...)orcron(...))ResourceNameDescriptionInputEnabledtrueCompile-Time Validation
The source generator validates at build time:
rate(orcron(ScheduledEvent, optional second parameter must beILambdaContextvoidorTaskAmazon.Lambda.CloudWatchEventsNuGet packageExample with all properties
What Changed
Annotation Attribute (
Amazon.Lambda.Annotations)ScheduleEventAttributeclass inAmazon.Lambda.Annotations.Schedulenamespace with configurable properties and built-in validationSource Generator (
Amazon.Lambda.Annotations.SourceGenerator)ScheduleEventAttributeBuilder— extracts attribute data from Roslyn syntax treeAttributeModelBuilder— recognizes and routes ScheduleEvent attributesEventTypeBuilder— maps toEventType.ScheduleSyntaxReceiver— registers ScheduleEvent as a recognized attributeTypeFullNames— adds Schedule type constantsLambdaFunctionValidator— validates method signatures, return types, dependencies, and attribute propertiesCloudFormationWriter.ProcessScheduleAttribute()— generates SAM template with Schedule expression, Description, Input, and EnabledAWSLambda0132for invalid ScheduleEventAttribute errorsTests
Related: DOTNET-8574