diff --git a/.agents/skills/new-event-source/SKILL.md b/.agents/skills/new-event-source/SKILL.md new file mode 100644 index 000000000..cfed5914b --- /dev/null +++ b/.agents/skills/new-event-source/SKILL.md @@ -0,0 +1,194 @@ +--- +name: new-event-source +description: Add a new AWS event source attribute (e.g., Kinesis, Kafka, MQ) to the Lambda .NET Annotations framework, including the attribute class, source generator integration, CloudFormation writer, unit tests, writer tests, source generator tests, and integration tests +--- + +# Adding a New Event Source to Lambda Annotations + +This skill guides you through adding a complete new event source attribute to the AWS Lambda .NET Annotations framework. Use this when a user asks to add support for a new AWS event source like Kinesis, Kafka, MQ, etc. + +## Prerequisites + +Before starting, gather from the user: +1. **Service name** (e.g., "Kinesis", "Kafka", "MQ") +2. **Primary resource identifier** (e.g., stream ARN, topic ARN, broker ARN) +3. **CloudFormation event type string** (e.g., "Kinesis", "MSK", "MQ") +4. **Event class name** from the corresponding `Amazon.Lambda.*Events` NuGet package (e.g., `KinesisEvent`) +5. **Optional properties** the attribute should support (e.g., BatchSize, StartingPosition, Filters) +6. **Whether `@` references use `Fn::GetAtt` or `Ref`** — event source mappings use `Fn::GetAtt`, subscriptions use `Ref` + +## Reference Examples + +Read these files to understand existing patterns before creating new ones: +- **SNS (simplest, subscription-based)**: `Libraries/src/Amazon.Lambda.Annotations/SNS/SNSEventAttribute.cs` +- **SQS (event source mapping with batching)**: `Libraries/src/Amazon.Lambda.Annotations/SQS/SQSEventAttribute.cs` +- **DynamoDB (stream-based)**: `Libraries/src/Amazon.Lambda.Annotations/DynamoDB/DynamoDBEventAttribute.cs` +- **S3 (notification-based)**: `Libraries/src/Amazon.Lambda.Annotations/S3/S3EventAttribute.cs` + +## Steps + +### Step 1: Create the Event Attribute Class + +**Create**: `Libraries/src/Amazon.Lambda.Annotations/{ServiceName}/{ServiceName}EventAttribute.cs` + +Key patterns: +- Add copyright header: `// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.` + `// SPDX-License-Identifier: Apache-2.0` +- Inherit from `Attribute` with `[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]` (use `AllowMultiple = false` for event types where multiple triggers on the same function don't make sense, e.g., Schedule events) +- Constructor takes the primary resource identifier as a required `string` parameter +- All optional properties use nullable backing fields with `IsSet` internal properties +- Include auto-derived `ResourceName` property (strips `@` prefix or extracts name from ARN) +- Include `internal List Validate()` method with all validation rules +- Use `Regex("^[a-zA-Z0-9]+$")` for ResourceName validation + +### Step 2: Register Type Full Names + +**Modify**: `Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/TypeFullNames.cs` + +Add constants: +```csharp +public const string {ServiceName}EventAttribute = "Amazon.Lambda.Annotations.{ServiceName}.{ServiceName}EventAttribute"; +public const string {ServiceName}Event = "Amazon.Lambda.{ServiceName}Events.{ServiceName}Event"; +``` + +Also add to `EventType` enum if needed in `Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Models/EventType.cs`. + +### Step 3: Create the Attribute Builder + +**Create**: `Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Models/Attributes/{ServiceName}EventAttributeBuilder.cs` + +Extracts attribute data from Roslyn `AttributeData`. Use consistent `else if` chaining. Reference: `SNSEventAttributeBuilder.cs`. + +### Step 4: Register in AttributeModelBuilder + +**Modify**: `Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Models/Attributes/AttributeModelBuilder.cs` + +Add `else if` block for the new attribute type after the existing event attribute blocks. + +### Step 5: Register in EventTypeBuilder + +**Modify**: `Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Models/EventTypeBuilder.cs` + +Add `else if` block mapping the attribute to the `EventType` enum value. + +### Step 6: Add DiagnosticDescriptor + +**Modify**: `Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Diagnostics/DiagnosticDescriptors.cs` + +Add descriptor with the next available `AWSLambda0XXX` ID for invalid attribute validation errors. + +**Modify**: `Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Diagnostics/AnalyzerReleases.Unshipped.md` — add the new diagnostic ID. + +### Step 7: Add Validation in LambdaFunctionValidator + +**Modify**: `Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Validation/LambdaFunctionValidator.cs` + +1. Add `Validate{ServiceName}Events()` call in `ValidateFunction` method +2. Create private `Validate{ServiceName}Events()` method that validates: + - Attribute properties via `Validate()` method + - Method parameters (first must be event type, optional second is `ILambdaContext`) + - Return type (usually `void` or `Task`) + +### Step 8: Add Dependency Check + +**Modify**: `Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Validation/LambdaFunctionValidator.cs` + +In `ValidateDependencies`, add check for `Amazon.Lambda.{ServiceName}Events` NuGet package. + +### Step 9: Check SyntaxReceiver + +**Check**: `Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/SyntaxReceiver.cs` + +Add the new attribute name if the SyntaxReceiver filters by attribute name strings. + +### Step 10: Add CloudFormation Writer Logic + +**Modify**: `Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Writers/CloudFormationWriter.cs` + +1. Add `case AttributeModel<{ServiceName}EventAttribute>` in the event processing switch +2. Create `Process{ServiceName}Attribute()` method that writes CF template properties + - Event source mappings (SQS, DynamoDB, Kinesis): use `Fn::GetAtt` for `@` references + - Subscription events (SNS): use `Ref` for `@` references + - Track synced properties in metadata + +### Step 11: Create Attribute Unit Tests + +**Create**: `Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/{ServiceName}EventAttributeTests.cs` + +Cover: constructor, defaults, property tracking, ResourceName derivation, all validation paths. Reference: `SQSEventAttributeTests.cs`, `DynamoDBEventAttributeTests.cs`, `SNSEventAttributeTests.cs`. + +### Step 12: Create CloudFormation Writer Tests + +**Create**: `Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/WriterTests/{ServiceName}EventsTests.cs` + +This is a `partial class CloudFormationWriterTests`. Include tests for: +1. `Verify{ServiceName}EventAttributes_AreCorrectlyApplied` — Theory with JSON/YAML and property combinations +2. `Verify{ServiceName}EventProperties_AreSyncedCorrectly` — Synced properties update when attributes change +3. `SwitchBetweenArnAndRef_For{Resource}` — ARN to `@` reference switching +4. `Verify{Resource}CanBeSet_FromCloudFormationParameter` — CF Parameters handling +5. `VerifyManuallySet{ServiceName}EventProperties_ArePreserved` — Hand-edited template preservation + +Reference: `SQSEventsTests.cs`, `DynamoDBEventsTests.cs`, `SNSEventsTests.cs`. + +### Step 13: Create Valid Event Examples + Source Generator Test + +**Create**: `Libraries/test/TestServerlessApp/{ServiceName}EventExamples/Valid{ServiceName}Events.cs.txt` +**Create**: `Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/{ServiceName}/` (generated handler snapshots) +**Create**: `Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/ServerlessTemplates/{serviceName}Events.template` +**Modify**: `Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/SourceGeneratorTests.cs` — add `VerifyValid{ServiceName}Events()` test + +### Step 14: Create Invalid Event Examples + Source Generator Test + +**Create**: `Libraries/test/TestServerlessApp/{ServiceName}EventExamples/Invalid{ServiceName}Events.cs.error` + +Cover: invalid property values, invalid params, invalid return type, multiple events, invalid ARN, invalid resource name. + +**Modify**: `Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/SourceGeneratorTests.cs` — add `VerifyInvalid{ServiceName}Events_ThrowsCompilationErrors()` test with diagnostic assertions including line spans. + +### Step 15: Create Generated Code Snapshots + +**Create**: `Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/{ServiceName}/` + +Tip: Run the source generator once to get actual output, then use as snapshot. + +### Step 16: Create Integration Test + +**Create**: `Libraries/test/TestServerlessApp.IntegrationTests/{ServiceName}EventSourceMapping.cs` +**Modify**: `Libraries/test/TestServerlessApp.IntegrationTests/IntegrationTestContextFixture.cs` — resource lookup +**Modify**: `Libraries/test/TestServerlessApp.IntegrationTests/DeploymentScript.ps1` — if needed + +### Step 17: Update AnalyzerReleases.Unshipped.md + +**Modify**: `Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Diagnostics/AnalyzerReleases.Unshipped.md` + +## File Map Summary + +| Action | File Path | +|--------|-----------| +| Create | `src/Amazon.Lambda.Annotations/{ServiceName}/{ServiceName}EventAttribute.cs` | +| Modify | `src/Amazon.Lambda.Annotations.SourceGenerator/TypeFullNames.cs` | +| Create | `src/Amazon.Lambda.Annotations.SourceGenerator/Models/Attributes/{ServiceName}EventAttributeBuilder.cs` | +| Modify | `src/Amazon.Lambda.Annotations.SourceGenerator/Models/Attributes/AttributeModelBuilder.cs` | +| Modify | `src/Amazon.Lambda.Annotations.SourceGenerator/Models/EventTypeBuilder.cs` | +| Modify | `src/Amazon.Lambda.Annotations.SourceGenerator/Diagnostics/DiagnosticDescriptors.cs` | +| Modify | `src/Amazon.Lambda.Annotations.SourceGenerator/Validation/LambdaFunctionValidator.cs` | +| Modify | `src/Amazon.Lambda.Annotations.SourceGenerator/SyntaxReceiver.cs` | +| Modify | `src/Amazon.Lambda.Annotations.SourceGenerator/Writers/CloudFormationWriter.cs` | +| Modify | `src/Amazon.Lambda.Annotations.SourceGenerator/Diagnostics/AnalyzerReleases.Unshipped.md` | +| Create | `test/Amazon.Lambda.Annotations.SourceGenerators.Tests/{ServiceName}EventAttributeTests.cs` | +| Create | `test/Amazon.Lambda.Annotations.SourceGenerators.Tests/WriterTests/{ServiceName}EventsTests.cs` | +| Create | `test/TestServerlessApp/{ServiceName}EventExamples/Valid{ServiceName}Events.cs.txt` | +| Create | `test/TestServerlessApp/{ServiceName}EventExamples/Invalid{ServiceName}Events.cs.error` | +| Create | `test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/{ServiceName}/` | +| Create | `test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/ServerlessTemplates/{serviceName}Events.template` | +| Modify | `test/Amazon.Lambda.Annotations.SourceGenerators.Tests/SourceGeneratorTests.cs` | +| Create | `test/TestServerlessApp.IntegrationTests/{ServiceName}EventSourceMapping.cs` | +| Modify | `test/TestServerlessApp.IntegrationTests/IntegrationTestContextFixture.cs` | + +## Important Conventions + +- **Copyright header** on every new `.cs` file +- **Consistent `else if` chaining** in attribute builders (never `if` then `if` for the same loop) +- **Both JSON and YAML** template formats must be tested in writer tests +- **Invalid event test spans** must reference exact line numbers in the `.cs.error` file +- **`.cs.txt` extension** for valid test files (prevents deployment) +- **`.cs.error` extension** for invalid test files (prevents compilation) diff --git a/.autover/changes/add-dynamodbevent-annotation.json b/.autover/changes/add-dynamodbevent-annotation.json new file mode 100644 index 000000000..7a11ec17f --- /dev/null +++ b/.autover/changes/add-dynamodbevent-annotation.json @@ -0,0 +1,11 @@ +{ + "Projects": [ + { + "Name": "Amazon.Lambda.Annotations", + "Type": "Minor", + "ChangelogMessages": [ + "Added [DynamoDBEvent] annotation attribute for declaratively configuring DynamoDB stream-triggered Lambda functions with support for stream reference, batch size, starting position, batching window, filters, and enabled state." + ] + } + ] +} diff --git a/.autover/changes/add-scheduleevent-annotation.json b/.autover/changes/add-scheduleevent-annotation.json new file mode 100644 index 000000000..dc25a283f --- /dev/null +++ b/.autover/changes/add-scheduleevent-annotation.json @@ -0,0 +1,11 @@ +{ + "Projects": [ + { + "Name": "Amazon.Lambda.Annotations", + "Type": "Minor", + "ChangelogMessages": [ + "Added [ScheduleEvent] annotation attribute for declaratively configuring schedule-triggered Lambda functions with support for rate and cron expressions, description, input, and enabled state." + ] + } + ] +} diff --git a/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Diagnostics/AnalyzerReleases.Unshipped.md b/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Diagnostics/AnalyzerReleases.Unshipped.md index dc97dd7de..01f6e1693 100644 --- a/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Diagnostics/AnalyzerReleases.Unshipped.md +++ b/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Diagnostics/AnalyzerReleases.Unshipped.md @@ -21,4 +21,6 @@ AWSLambda0133 | AWSLambdaCSharpGenerator | Error | ALB Listener Reference Not Fo AWSLambda0134 | AWSLambdaCSharpGenerator | Error | FromRoute not supported on ALB functions AWSLambda0135 | AWSLambdaCSharpGenerator | Error | Unmapped parameter on ALB function AWSLambda0136 | AWSLambdaCSharpGenerator | Error | Invalid S3EventAttribute +AWSLambda0137 | AWSLambdaCSharpGenerator | Error | Invalid DynamoDBEventAttribute AWSLambda0138 | AWSLambdaCSharpGenerator | Error | Invalid SNSEventAttribute +AWSLambda0139 | AWSLambdaCSharpGenerator | Error | Invalid ScheduleEventAttribute diff --git a/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Diagnostics/DiagnosticDescriptors.cs b/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Diagnostics/DiagnosticDescriptors.cs index 569fd4116..6de39018b 100644 --- a/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Diagnostics/DiagnosticDescriptors.cs +++ b/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Diagnostics/DiagnosticDescriptors.cs @@ -282,11 +282,25 @@ public static class DiagnosticDescriptors DiagnosticSeverity.Error, isEnabledByDefault: true); + public static readonly DiagnosticDescriptor InvalidDynamoDBEventAttribute = new DiagnosticDescriptor(id: "AWSLambda0137", + title: "Invalid DynamoDBEventAttribute", + messageFormat: "Invalid DynamoDBEventAttribute encountered: {0}", + category: "AWSLambdaCSharpGenerator", + DiagnosticSeverity.Error, + isEnabledByDefault: true); + public static readonly DiagnosticDescriptor InvalidSnsEventAttribute = new DiagnosticDescriptor(id: "AWSLambda0138", title: "Invalid SNSEventAttribute", messageFormat: "Invalid SNSEventAttribute encountered: {0}", category: "AWSLambdaCSharpGenerator", DiagnosticSeverity.Error, isEnabledByDefault: true); + + public static readonly DiagnosticDescriptor InvalidScheduleEventAttribute = new DiagnosticDescriptor(id: "AWSLambda0139", + title: "Invalid ScheduleEventAttribute", + messageFormat: "Invalid ScheduleEventAttribute encountered: {0}", + category: "AWSLambdaCSharpGenerator", + DiagnosticSeverity.Error, + isEnabledByDefault: true); } } diff --git a/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Models/Attributes/AttributeModelBuilder.cs b/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Models/Attributes/AttributeModelBuilder.cs index add735fe5..4a8e08c0c 100644 --- a/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Models/Attributes/AttributeModelBuilder.cs +++ b/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Models/Attributes/AttributeModelBuilder.cs @@ -3,7 +3,10 @@ using System; using Amazon.Lambda.Annotations.ALB; +using Amazon.Lambda.Annotations.Schedule; using Amazon.Lambda.Annotations.APIGateway; +using Amazon.Lambda.Annotations.DynamoDB; +using Amazon.Lambda.Annotations.SNS; using Amazon.Lambda.Annotations.S3; using Amazon.Lambda.Annotations.SQS; using Microsoft.CodeAnalysis; @@ -113,6 +116,15 @@ public static AttributeModel Build(AttributeData att, GeneratorExecutionContext Type = TypeModelBuilder.Build(att.AttributeClass, context) }; } + else if (att.AttributeClass.Equals(context.Compilation.GetTypeByMetadataName(TypeFullNames.DynamoDBEventAttribute), SymbolEqualityComparer.Default)) + { + var data = DynamoDBEventAttributeBuilder.Build(att); + model = new AttributeModel + { + Data = data, + Type = TypeModelBuilder.Build(att.AttributeClass, context) + }; + } else if (att.AttributeClass.Equals(context.Compilation.GetTypeByMetadataName(TypeFullNames.SNSEventAttribute), SymbolEqualityComparer.Default)) { var data = SNSEventAttributeBuilder.Build(att); @@ -122,6 +134,15 @@ public static AttributeModel Build(AttributeData att, GeneratorExecutionContext Type = TypeModelBuilder.Build(att.AttributeClass, context) }; } + else if (att.AttributeClass.Equals(context.Compilation.GetTypeByMetadataName(TypeFullNames.ScheduleEventAttribute), SymbolEqualityComparer.Default)) + { + var data = ScheduleEventAttributeBuilder.Build(att); + model = new AttributeModel + { + 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); diff --git a/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Models/Attributes/DynamoDBEventAttributeBuilder.cs b/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Models/Attributes/DynamoDBEventAttributeBuilder.cs new file mode 100644 index 000000000..ce0d39c6c --- /dev/null +++ b/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Models/Attributes/DynamoDBEventAttributeBuilder.cs @@ -0,0 +1,55 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +using Amazon.Lambda.Annotations.DynamoDB; +using Microsoft.CodeAnalysis; +using System; + +namespace Amazon.Lambda.Annotations.SourceGenerator.Models.Attributes +{ + /// + /// Builder for . + /// + public class DynamoDBEventAttributeBuilder + { + public static DynamoDBEventAttribute Build(AttributeData att) + { + if (att.ConstructorArguments.Length != 1) + { + throw new NotSupportedException($"{TypeFullNames.DynamoDBEventAttribute} must have constructor with 1 argument."); + } + var stream = att.ConstructorArguments[0].Value as string; + var data = new DynamoDBEventAttribute(stream); + + foreach (var pair in att.NamedArguments) + { + if (pair.Key == nameof(data.ResourceName) && pair.Value.Value is string resourceName) + { + data.ResourceName = resourceName; + } + else if (pair.Key == nameof(data.BatchSize) && pair.Value.Value is uint batchSize) + { + data.BatchSize = batchSize; + } + else if (pair.Key == nameof(data.StartingPosition) && pair.Value.Value is string startingPosition) + { + data.StartingPosition = startingPosition; + } + else if (pair.Key == nameof(data.Enabled) && pair.Value.Value is bool enabled) + { + data.Enabled = enabled; + } + else if (pair.Key == nameof(data.MaximumBatchingWindowInSeconds) && pair.Value.Value is uint maximumBatchingWindowInSeconds) + { + data.MaximumBatchingWindowInSeconds = maximumBatchingWindowInSeconds; + } + else if (pair.Key == nameof(data.Filters) && pair.Value.Value is string filters) + { + data.Filters = filters; + } + } + + return data; + } + } +} diff --git a/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Models/Attributes/SQSEventAttributeBuilder.cs b/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Models/Attributes/SQSEventAttributeBuilder.cs index c58063c48..fa1202dc7 100644 --- a/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Models/Attributes/SQSEventAttributeBuilder.cs +++ b/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Models/Attributes/SQSEventAttributeBuilder.cs @@ -1,4 +1,7 @@ -using Amazon.Lambda.Annotations.SQS; +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +using Amazon.Lambda.Annotations.SQS; using Microsoft.CodeAnalysis; using System; @@ -24,7 +27,7 @@ public static SQSEventAttribute Build(AttributeData att) { data.ResourceName = resourceName; } - if (pair.Key == nameof(data.BatchSize) && pair.Value.Value is uint batchSize) + else if (pair.Key == nameof(data.BatchSize) && pair.Value.Value is uint batchSize) { data.BatchSize = batchSize; } diff --git a/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Models/Attributes/ScheduleEventAttributeBuilder.cs b/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Models/Attributes/ScheduleEventAttributeBuilder.cs new file mode 100644 index 000000000..91ba4403a --- /dev/null +++ b/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Models/Attributes/ScheduleEventAttributeBuilder.cs @@ -0,0 +1,47 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +using Amazon.Lambda.Annotations.Schedule; +using Microsoft.CodeAnalysis; +using System; + +namespace Amazon.Lambda.Annotations.SourceGenerator.Models.Attributes +{ + /// + /// Builder for . + /// + public class ScheduleEventAttributeBuilder + { + public static ScheduleEventAttribute Build(AttributeData att) + { + if (att.ConstructorArguments.Length != 1) + { + throw new NotSupportedException($"{TypeFullNames.ScheduleEventAttribute} must have constructor with 1 argument."); + } + var schedule = att.ConstructorArguments[0].Value as string; + var data = new ScheduleEventAttribute(schedule); + + foreach (var pair in att.NamedArguments) + { + if (pair.Key == nameof(data.ResourceName) && pair.Value.Value is string resourceName) + { + data.ResourceName = resourceName; + } + else if (pair.Key == nameof(data.Description) && pair.Value.Value is string description) + { + data.Description = description; + } + else if (pair.Key == nameof(data.Input) && pair.Value.Value is string input) + { + data.Input = input; + } + else if (pair.Key == nameof(data.Enabled) && pair.Value.Value is bool enabled) + { + data.Enabled = enabled; + } + } + + return data; + } + } +} diff --git a/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Models/EventTypeBuilder.cs b/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Models/EventTypeBuilder.cs index 960b83d97..4912a97a4 100644 --- a/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Models/EventTypeBuilder.cs +++ b/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Models/EventTypeBuilder.cs @@ -34,10 +34,18 @@ public static HashSet Build(IMethodSymbol lambdaMethodSymbol, { events.Add(EventType.S3); } + else if (attribute.AttributeClass.ToDisplayString() == TypeFullNames.DynamoDBEventAttribute) + { + events.Add(EventType.DynamoDB); + } else if (attribute.AttributeClass.ToDisplayString() == TypeFullNames.SNSEventAttribute) { events.Add(EventType.SNS); } + else if (attribute.AttributeClass.ToDisplayString() == TypeFullNames.ScheduleEventAttribute) + { + events.Add(EventType.Schedule); + } else if (attribute.AttributeClass.ToDisplayString() == TypeFullNames.HttpApiAuthorizerAttribute || attribute.AttributeClass.ToDisplayString() == TypeFullNames.RestApiAuthorizerAttribute) { @@ -52,4 +60,4 @@ public static HashSet Build(IMethodSymbol lambdaMethodSymbol, return events; } } -} \ No newline at end of file +} diff --git a/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/SyntaxReceiver.cs b/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/SyntaxReceiver.cs index e54d97b44..57c9adfa8 100644 --- a/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/SyntaxReceiver.cs +++ b/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/SyntaxReceiver.cs @@ -28,7 +28,9 @@ internal class SyntaxReceiver : ISyntaxContextReceiver { "SQSEventAttribute", "SQSEvent" }, { "ALBApiAttribute", "ALBApi" }, { "S3EventAttribute", "S3Event" }, - { "SNSEventAttribute", "SNSEvent" } + { "DynamoDBEventAttribute", "DynamoDBEvent" }, + { "SNSEventAttribute", "SNSEvent" }, + { "ScheduleEventAttribute", "ScheduleEvent" } }; public List LambdaMethods { get; } = new List(); diff --git a/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/TypeFullNames.cs b/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/TypeFullNames.cs index 40059193a..0e7e76a89 100644 --- a/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/TypeFullNames.cs +++ b/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/TypeFullNames.cs @@ -62,9 +62,15 @@ public static class TypeFullNames public const string S3Event = "Amazon.Lambda.S3Events.S3Event"; public const string S3EventAttribute = "Amazon.Lambda.Annotations.S3.S3EventAttribute"; + public const string DynamoDBEvent = "Amazon.Lambda.DynamoDBEvents.DynamoDBEvent"; + public const string DynamoDBEventAttribute = "Amazon.Lambda.Annotations.DynamoDB.DynamoDBEventAttribute"; + public const string SNSEvent = "Amazon.Lambda.SNSEvents.SNSEvent"; public const string SNSEventAttribute = "Amazon.Lambda.Annotations.SNS.SNSEventAttribute"; + public const string ScheduledEvent = "Amazon.Lambda.CloudWatchEvents.ScheduledEvents.ScheduledEvent"; + public const string ScheduleEventAttribute = "Amazon.Lambda.Annotations.Schedule.ScheduleEventAttribute"; + public const string LambdaSerializerAttribute = "Amazon.Lambda.Core.LambdaSerializerAttribute"; public const string DefaultLambdaSerializer = "Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer"; @@ -95,7 +101,9 @@ public static class TypeFullNames SQSEventAttribute, ALBApiAttribute, S3EventAttribute, - SNSEventAttribute + DynamoDBEventAttribute, + SNSEventAttribute, + ScheduleEventAttribute }; } } diff --git a/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Validation/LambdaFunctionValidator.cs b/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Validation/LambdaFunctionValidator.cs index 7193779ed..25e9d486d 100644 --- a/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Validation/LambdaFunctionValidator.cs +++ b/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Validation/LambdaFunctionValidator.cs @@ -7,7 +7,9 @@ using Amazon.Lambda.Annotations.SourceGenerator.Extensions; using Amazon.Lambda.Annotations.SourceGenerator.Models; using Amazon.Lambda.Annotations.SourceGenerator.Models.Attributes; +using Amazon.Lambda.Annotations.DynamoDB; using Amazon.Lambda.Annotations.SNS; +using Amazon.Lambda.Annotations.Schedule; using Amazon.Lambda.Annotations.SQS; using Microsoft.CodeAnalysis; using System.Collections.Generic; @@ -65,7 +67,9 @@ internal static bool ValidateFunction(GeneratorExecutionContext context, IMethod // Validate Events ValidateApiGatewayEvents(lambdaFunctionModel, methodLocation, diagnostics); ValidateSqsEvents(lambdaFunctionModel, methodLocation, diagnostics); + ValidateDynamoDBEvents(lambdaFunctionModel, methodLocation, diagnostics); ValidateSnsEvents(lambdaFunctionModel, methodLocation, diagnostics); + ValidateScheduleEvents(lambdaFunctionModel, methodLocation, diagnostics); ValidateAlbEvents(lambdaFunctionModel, methodLocation, diagnostics); ValidateS3Events(lambdaFunctionModel, methodLocation, diagnostics); @@ -116,6 +120,16 @@ internal static bool ValidateDependencies(GeneratorExecutionContext context, IMe } } + // Check for references to "Amazon.Lambda.DynamoDBEvents" if the Lambda method is annotated with DynamoDBEvent attribute. + if (lambdaMethodSymbol.HasAttribute(context, TypeFullNames.DynamoDBEventAttribute)) + { + if (context.Compilation.ReferencedAssemblyNames.FirstOrDefault(x => x.Name == "Amazon.Lambda.DynamoDBEvents") == null) + { + diagnosticReporter.Report(Diagnostic.Create(DiagnosticDescriptors.MissingDependencies, methodLocation, "Amazon.Lambda.DynamoDBEvents")); + return false; + } + } + // Check for references to "Amazon.Lambda.SNSEvents" if the Lambda method is annotated with SNSEvent attribute. if (lambdaMethodSymbol.HasAttribute(context, TypeFullNames.SNSEventAttribute)) { @@ -126,6 +140,16 @@ internal static bool ValidateDependencies(GeneratorExecutionContext context, IMe } } + // Check for references to "Amazon.Lambda.CloudWatchEvents" if the Lambda method is annotated with ScheduleEvent attribute. + if (lambdaMethodSymbol.HasAttribute(context, TypeFullNames.ScheduleEventAttribute)) + { + if (context.Compilation.ReferencedAssemblyNames.FirstOrDefault(x => x.Name == "Amazon.Lambda.CloudWatchEvents") == null) + { + diagnosticReporter.Report(Diagnostic.Create(DiagnosticDescriptors.MissingDependencies, methodLocation, "Amazon.Lambda.CloudWatchEvents")); + return false; + } + } + return true; } @@ -436,6 +460,45 @@ private static void ValidateS3Events(LambdaFunctionModel lambdaFunctionModel, Lo } } + private static void ValidateDynamoDBEvents(LambdaFunctionModel lambdaFunctionModel, Location methodLocation, List diagnostics) + { + if (!lambdaFunctionModel.LambdaMethod.Events.Contains(EventType.DynamoDB)) + { + return; + } + + // Validate DynamoDBEventAttributes + foreach (var att in lambdaFunctionModel.Attributes) + { + if (att.Type.FullName != TypeFullNames.DynamoDBEventAttribute) + continue; + + var dynamoDBEventAttribute = ((AttributeModel)att).Data; + var validationErrors = dynamoDBEventAttribute.Validate(); + validationErrors.ForEach(errorMessage => diagnostics.Add(Diagnostic.Create(DiagnosticDescriptors.InvalidDynamoDBEventAttribute, methodLocation, errorMessage))); + } + + // Validate method parameters - When using DynamoDBEventAttribute, the method signature must be (DynamoDBEvent evnt) or (DynamoDBEvent evnt, ILambdaContext context) + var parameters = lambdaFunctionModel.LambdaMethod.Parameters; + if (parameters.Count == 0 || + parameters.Count > 2 || + (parameters.Count == 1 && parameters[0].Type.FullName != TypeFullNames.DynamoDBEvent) || + (parameters.Count == 2 && (parameters[0].Type.FullName != TypeFullNames.DynamoDBEvent || parameters[1].Type.FullName != TypeFullNames.ILambdaContext))) + { + var errorMessage = $"When using the {nameof(DynamoDBEventAttribute)}, the Lambda method can accept at most 2 parameters. " + + $"The first parameter is required and must be of type {TypeFullNames.DynamoDBEvent}. " + + $"The second parameter is optional and must be of type {TypeFullNames.ILambdaContext}."; + diagnostics.Add(Diagnostic.Create(DiagnosticDescriptors.InvalidLambdaMethodSignature, methodLocation, errorMessage)); + } + + // Validate method return type - When using DynamoDBEventAttribute, the return type must be either void or Task + if (!lambdaFunctionModel.LambdaMethod.ReturnsVoid && !lambdaFunctionModel.LambdaMethod.ReturnsVoidTask) + { + var errorMessage = $"When using the {nameof(DynamoDBEventAttribute)}, the Lambda method can return either void or {TypeFullNames.Task}"; + diagnostics.Add(Diagnostic.Create(DiagnosticDescriptors.InvalidLambdaMethodSignature, methodLocation, errorMessage)); + } + } + private static void ValidateSnsEvents(LambdaFunctionModel lambdaFunctionModel, Location methodLocation, List diagnostics) { if (!lambdaFunctionModel.LambdaMethod.Events.Contains(EventType.SNS)) @@ -475,6 +538,44 @@ private static void ValidateSnsEvents(LambdaFunctionModel lambdaFunctionModel, L } } + private static void ValidateScheduleEvents(LambdaFunctionModel lambdaFunctionModel, Location methodLocation, List diagnostics) + { + if (!lambdaFunctionModel.LambdaMethod.Events.Contains(EventType.Schedule)) + { + return; + } + + foreach (var att in lambdaFunctionModel.Attributes) + { + if (att.Type.FullName != TypeFullNames.ScheduleEventAttribute) + continue; + + var scheduleEventAttribute = ((AttributeModel)att).Data; + var validationErrors = scheduleEventAttribute.Validate(); + validationErrors.ForEach(errorMessage => diagnostics.Add(Diagnostic.Create(DiagnosticDescriptors.InvalidScheduleEventAttribute, methodLocation, errorMessage))); + } + + // Validate method parameters - When using ScheduleEventAttribute, the method signature must be (ScheduledEvent evnt) or (ScheduledEvent evnt, ILambdaContext context) + var parameters = lambdaFunctionModel.LambdaMethod.Parameters; + if (parameters.Count == 0 || + parameters.Count > 2 || + (parameters.Count == 1 && parameters[0].Type.FullName != TypeFullNames.ScheduledEvent) || + (parameters.Count == 2 && (parameters[0].Type.FullName != TypeFullNames.ScheduledEvent || 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 is required and 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)); + } + + // Validate return type - must be void or Task + if (!lambdaFunctionModel.LambdaMethod.ReturnsVoid && !lambdaFunctionModel.LambdaMethod.ReturnsVoidTask) + { + var errorMessage = $"When using the {nameof(ScheduleEventAttribute)}, the Lambda method can return either void or {TypeFullNames.Task}"; + diagnostics.Add(Diagnostic.Create(DiagnosticDescriptors.InvalidLambdaMethodSignature, methodLocation, errorMessage)); + } + } + private static bool ReportDiagnostics(DiagnosticReporter diagnosticReporter, List diagnostics) { var isValid = true; diff --git a/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Writers/CloudFormationWriter.cs b/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Writers/CloudFormationWriter.cs index f34f2d8bb..eefa84546 100644 --- a/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Writers/CloudFormationWriter.cs +++ b/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Writers/CloudFormationWriter.cs @@ -2,11 +2,13 @@ // SPDX-License-Identifier: Apache-2.0 using Amazon.Lambda.Annotations.ALB; +using Amazon.Lambda.Annotations.DynamoDB; using Amazon.Lambda.Annotations.APIGateway; using Amazon.Lambda.Annotations.SourceGenerator.Diagnostics; using Amazon.Lambda.Annotations.SourceGenerator.FileIO; using Amazon.Lambda.Annotations.SourceGenerator.Models; using Amazon.Lambda.Annotations.SNS; +using Amazon.Lambda.Annotations.Schedule; using Amazon.Lambda.Annotations.SourceGenerator.Models.Attributes; using Amazon.Lambda.Annotations.S3; using Amazon.Lambda.Annotations.SQS; @@ -242,10 +244,18 @@ private void ProcessLambdaFunctionEventAttributes(ILambdaFunctionSerializable la _templateWriter.SetToken($"Resources.{lambdaFunction.ResourceName}.Metadata.SyncedFunctionUrlConfig", true); hasFunctionUrl = true; break; + case AttributeModel dynamoDBAttributeModel: + eventName = ProcessDynamoDBAttribute(lambdaFunction, dynamoDBAttributeModel.Data, currentSyncedEventProperties); + currentSyncedEvents.Add(eventName); + break; case AttributeModel snsAttributeModel: eventName = ProcessSnsAttribute(lambdaFunction, snsAttributeModel.Data, currentSyncedEventProperties); currentSyncedEvents.Add(eventName); break; + case AttributeModel scheduleAttributeModel: + eventName = ProcessScheduleAttribute(lambdaFunction, scheduleAttributeModel.Data, currentSyncedEventProperties); + currentSyncedEvents.Add(eventName); + break; } } @@ -678,6 +688,68 @@ private string ProcessSqsAttribute(ILambdaFunctionSerializable lambdaFunction, S return att.ResourceName; } + /// + /// Writes all properties associated with to the serverless template. + /// + private string ProcessDynamoDBAttribute(ILambdaFunctionSerializable lambdaFunction, DynamoDBEventAttribute att, Dictionary> syncedEventProperties) + { + var eventName = att.ResourceName; + var eventPath = $"Resources.{lambdaFunction.ResourceName}.Properties.Events.{eventName}"; + + _templateWriter.SetToken($"{eventPath}.Type", "DynamoDB"); + + // Stream + _templateWriter.RemoveToken($"{eventPath}.Properties.Stream"); + if (!att.Stream.StartsWith("@")) + { + SetEventProperty(syncedEventProperties, lambdaFunction.ResourceName, eventName, "Stream", att.Stream); + } + else + { + var resource = att.Stream.Substring(1); + if (_templateWriter.Exists($"{PARAMETERS}.{resource}")) + SetEventProperty(syncedEventProperties, lambdaFunction.ResourceName, eventName, $"Stream.{REF}", resource); + else + SetEventProperty(syncedEventProperties, lambdaFunction.ResourceName, eventName, $"Stream.{GET_ATTRIBUTE}", new List { resource, "StreamArn" }, TokenType.List); + } + + // StartingPosition + SetEventProperty(syncedEventProperties, lambdaFunction.ResourceName, eventName, "StartingPosition", att.StartingPosition); + + // BatchSize + if (att.IsBatchSizeSet) + { + SetEventProperty(syncedEventProperties, lambdaFunction.ResourceName, eventName, "BatchSize", att.BatchSize); + } + + // Enabled + if (att.IsEnabledSet) + { + SetEventProperty(syncedEventProperties, lambdaFunction.ResourceName, eventName, "Enabled", att.Enabled); + } + + // MaximumBatchingWindowInSeconds + if (att.IsMaximumBatchingWindowInSecondsSet) + { + SetEventProperty(syncedEventProperties, lambdaFunction.ResourceName, eventName, "MaximumBatchingWindowInSeconds", att.MaximumBatchingWindowInSeconds); + } + + // FilterCriteria + if (att.IsFiltersSet) + { + const char SEPERATOR = ';'; + var filters = att.Filters.Split(SEPERATOR).Select(x => x.Trim()).ToList(); + var filterList = new List>(); + foreach (var filter in filters) + { + filterList.Add(new Dictionary { { "Pattern", filter } }); + } + SetEventProperty(syncedEventProperties, lambdaFunction.ResourceName, eventName, "FilterCriteria.Filters", filterList, TokenType.List); + } + + return att.ResourceName; + } + /// /// Writes all properties associated with to the serverless template. /// @@ -715,6 +787,40 @@ private string ProcessSnsAttribute(ILambdaFunctionSerializable lambdaFunction, S return att.ResourceName; } + /// + /// Writes all properties associated with to the serverless template. + /// + private string ProcessScheduleAttribute(ILambdaFunctionSerializable lambdaFunction, ScheduleEventAttribute att, Dictionary> syncedEventProperties) + { + var eventName = att.ResourceName; + var eventPath = $"Resources.{lambdaFunction.ResourceName}.Properties.Events.{eventName}"; + + _templateWriter.SetToken($"{eventPath}.Type", "Schedule"); + + // Schedule expression + SetEventProperty(syncedEventProperties, lambdaFunction.ResourceName, eventName, "Schedule", att.Schedule); + + // Description + if (att.IsDescriptionSet) + { + SetEventProperty(syncedEventProperties, lambdaFunction.ResourceName, eventName, "Description", att.Description); + } + + // Input + if (att.IsInputSet) + { + SetEventProperty(syncedEventProperties, lambdaFunction.ResourceName, eventName, "Input", att.Input); + } + + // Enabled + if (att.IsEnabledSet) + { + SetEventProperty(syncedEventProperties, lambdaFunction.ResourceName, eventName, "Enabled", att.Enabled); + } + + return att.ResourceName; + } + /// /// Writes all properties associated with to the serverless template. /// diff --git a/Libraries/src/Amazon.Lambda.Annotations/DynamoDB/DynamoDBEventAttribute.cs b/Libraries/src/Amazon.Lambda.Annotations/DynamoDB/DynamoDBEventAttribute.cs new file mode 100644 index 000000000..d1801d483 --- /dev/null +++ b/Libraries/src/Amazon.Lambda.Annotations/DynamoDB/DynamoDBEventAttribute.cs @@ -0,0 +1,162 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.RegularExpressions; + +namespace Amazon.Lambda.Annotations.DynamoDB +{ + /// + /// This attribute defines the DynamoDB event source configuration for a Lambda function. + /// + [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] + public class DynamoDBEventAttribute : Attribute + { + private static readonly Regex _resourceNameRegex = new Regex("^[a-zA-Z0-9]+$"); + + /// + /// The DynamoDB stream that will act as the event trigger for the Lambda function. + /// This can either be the stream ARN or reference to the DynamoDB table resource that is already defined in the serverless template. + /// To reference a DynamoDB table resource in the serverless template, prefix the resource name with "@" symbol. + /// + public string Stream { get; set; } + + /// + /// The CloudFormation resource name for the DynamoDB event source mapping. + /// + public string ResourceName + { + get + { + if (IsResourceNameSet) + { + return resourceName; + } + + if (string.IsNullOrWhiteSpace(Stream)) + { + return string.Empty; + } + + if (Stream.StartsWith("@")) + { + return Stream.Length > 1 ? Stream.Substring(1) : string.Empty; + } + + // DynamoDB stream ARN format: arn:aws:dynamodb:region:account:table/TableName/stream/timestamp + var arnParts = Stream.Split('/'); + if (arnParts.Length >= 2) + { + var tableName = arnParts[1]; + return string.Join(string.Empty, tableName.Where(char.IsLetterOrDigit)); + } + return string.Join(string.Empty, Stream.Where(char.IsLetterOrDigit)); + } + set => resourceName = value; + } + + private string resourceName { get; set; } = null; + internal bool IsResourceNameSet => resourceName != null; + + /// + /// The maximum number of records in each batch that Lambda pulls from the stream. + /// Default value is 100. + /// + public uint BatchSize + { + get => batchSize.GetValueOrDefault(100); + set => batchSize = value; + } + private uint? batchSize { get; set; } + internal bool IsBatchSizeSet => batchSize.HasValue; + + /// + /// The position in the stream where Lambda starts reading. Valid values are TRIM_HORIZON and LATEST. + /// Default value is LATEST. + /// + public string StartingPosition { get; set; } = "LATEST"; + internal bool IsStartingPositionSet => true; + + /// + /// If set to false, the event source mapping will be disabled. Default value is true. + /// + public bool Enabled + { + get => enabled.GetValueOrDefault(true); + set => enabled = value; + } + private bool? enabled { get; set; } + internal bool IsEnabledSet => enabled.HasValue; + + /// + /// The maximum amount of time, in seconds, to gather records before invoking the function. + /// + public uint MaximumBatchingWindowInSeconds + { + get => maximumBatchingWindowInSeconds.GetValueOrDefault(); + set => maximumBatchingWindowInSeconds = value; + } + private uint? maximumBatchingWindowInSeconds { get; set; } + internal bool IsMaximumBatchingWindowInSecondsSet => maximumBatchingWindowInSeconds.HasValue; + + /// + /// A collection of semicolon (;) separated strings where each string denotes a filter pattern. + /// + public string Filters { get; set; } = null; + internal bool IsFiltersSet => Filters != null; + + /// + /// Creates an instance of the class. + /// + /// property + public DynamoDBEventAttribute(string stream) + { + Stream = stream; + } + + internal List Validate() + { + var validationErrors = new List(); + + if (IsBatchSizeSet && (BatchSize < 1 || BatchSize > 10000)) + { + validationErrors.Add($"{nameof(DynamoDBEventAttribute.BatchSize)} = {BatchSize}. It must be between 1 and 10000"); + } + if (IsMaximumBatchingWindowInSecondsSet && MaximumBatchingWindowInSeconds > 300) + { + validationErrors.Add($"{nameof(DynamoDBEventAttribute.MaximumBatchingWindowInSeconds)} = {MaximumBatchingWindowInSeconds}. It must be between 0 and 300"); + } + if (string.IsNullOrEmpty(StartingPosition)) + { + validationErrors.Add($"{nameof(DynamoDBEventAttribute.StartingPosition)} must not be null or empty. It must be either TRIM_HORIZON or LATEST"); + } + else if (StartingPosition != "TRIM_HORIZON" && StartingPosition != "LATEST") + { + validationErrors.Add($"{nameof(DynamoDBEventAttribute.StartingPosition)} = {StartingPosition}. It must be either TRIM_HORIZON or LATEST"); + } + if (string.IsNullOrWhiteSpace(Stream)) + { + validationErrors.Add($"{nameof(DynamoDBEventAttribute.Stream)} must not be null or empty"); + } + else if (Stream.StartsWith("@") && string.IsNullOrWhiteSpace(Stream.Substring(1))) + { + validationErrors.Add($"{nameof(DynamoDBEventAttribute.Stream)} = {Stream}. The '@' prefix must be followed by a non-empty resource or parameter name"); + } + else if (!Stream.StartsWith("@")) + { + if (!Stream.Contains(":dynamodb:") || !Stream.Contains("/stream/")) + { + validationErrors.Add($"{nameof(DynamoDBEventAttribute.Stream)} = {Stream}. The DynamoDB stream ARN is invalid"); + } + } + if (IsResourceNameSet && !_resourceNameRegex.IsMatch(ResourceName)) + { + validationErrors.Add($"{nameof(DynamoDBEventAttribute.ResourceName)} = {ResourceName}. It must only contain alphanumeric characters and must not be an empty string"); + } + + return validationErrors; + } + } +} diff --git a/Libraries/src/Amazon.Lambda.Annotations/SQS/SQSEventAttribute.cs b/Libraries/src/Amazon.Lambda.Annotations/SQS/SQSEventAttribute.cs index e3d72464f..8f2ca892e 100644 --- a/Libraries/src/Amazon.Lambda.Annotations/SQS/SQSEventAttribute.cs +++ b/Libraries/src/Amazon.Lambda.Annotations/SQS/SQSEventAttribute.cs @@ -1,4 +1,7 @@ -using System; +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +using System; using System.Collections.Generic; using System.Linq; using System.Text.RegularExpressions; diff --git a/Libraries/src/Amazon.Lambda.Annotations/Schedule/ScheduleEventAttribute.cs b/Libraries/src/Amazon.Lambda.Annotations/Schedule/ScheduleEventAttribute.cs new file mode 100644 index 000000000..197d3dfc5 --- /dev/null +++ b/Libraries/src/Amazon.Lambda.Annotations/Schedule/ScheduleEventAttribute.cs @@ -0,0 +1,99 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.RegularExpressions; + +namespace Amazon.Lambda.Annotations.Schedule +{ + /// + /// This attribute defines the Schedule event source configuration for a Lambda function. + /// + [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] + public class ScheduleEventAttribute : Attribute + { + private static readonly Regex _resourceNameRegex = new Regex("^[a-zA-Z0-9]+$"); + + /// + /// The schedule expression. Supports rate and cron expressions. + /// Examples: "rate(5 minutes)", "cron(0 12 * * ? *)" + /// + public string Schedule { get; set; } + + /// + /// The CloudFormation resource name for the schedule event. + /// + public string ResourceName + { + get + { + if (IsResourceNameSet) + { + return resourceName; + } + // Generate a default resource name from the schedule expression + var sanitized = string.Join(string.Empty, (Schedule ?? string.Empty).Where(char.IsLetterOrDigit)); + return sanitized.Length > 0 ? sanitized : "ScheduleEvent"; + } + set => resourceName = value; + } + + private string resourceName { get; set; } = null; + internal bool IsResourceNameSet => resourceName != null; + + /// + /// A description for the schedule rule. + /// + public string Description { get; set; } = null; + internal bool IsDescriptionSet => Description != null; + + /// + /// A JSON string to pass as input to the Lambda function. + /// + public string Input { get; set; } = null; + internal bool IsInputSet => Input != null; + + /// + /// If set to false, the event source mapping will be disabled. Default value is true. + /// + public bool Enabled + { + get => enabled.GetValueOrDefault(true); + set => enabled = value; + } + private bool? enabled { get; set; } + internal bool IsEnabledSet => enabled.HasValue; + + /// + /// Creates an instance of the class. + /// + /// property + public ScheduleEventAttribute(string schedule) + { + Schedule = schedule; + } + + internal List Validate() + { + var validationErrors = new List(); + + if (string.IsNullOrEmpty(Schedule)) + { + validationErrors.Add($"{nameof(ScheduleEventAttribute.Schedule)} must not be null or empty"); + } + else if (!Schedule.StartsWith("rate(") && !Schedule.StartsWith("cron(")) + { + validationErrors.Add($"{nameof(ScheduleEventAttribute.Schedule)} = {Schedule}. It must start with 'rate(' or 'cron('"); + } + + if (IsResourceNameSet && !_resourceNameRegex.IsMatch(ResourceName)) + { + validationErrors.Add($"{nameof(ScheduleEventAttribute.ResourceName)} = {ResourceName}. It must only contain alphanumeric characters and must not be an empty string"); + } + + return validationErrors; + } + } +} diff --git a/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Amazon.Lambda.Annotations.SourceGenerators.Tests.csproj b/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Amazon.Lambda.Annotations.SourceGenerators.Tests.csproj index a68fced1f..913413b42 100644 --- a/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Amazon.Lambda.Annotations.SourceGenerators.Tests.csproj +++ b/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Amazon.Lambda.Annotations.SourceGenerators.Tests.csproj @@ -208,7 +208,9 @@ + + - + + + diff --git a/Libraries/test/TestServerlessApp/DynamoDBEventExamples/InvalidDynamoDBEvents.cs.error b/Libraries/test/TestServerlessApp/DynamoDBEventExamples/InvalidDynamoDBEvents.cs.error new file mode 100644 index 000000000..243ddfb7a --- /dev/null +++ b/Libraries/test/TestServerlessApp/DynamoDBEventExamples/InvalidDynamoDBEvents.cs.error @@ -0,0 +1,68 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +using Amazon.Lambda.Annotations; +using Amazon.Lambda.Annotations.APIGateway; +using Amazon.Lambda.Annotations.DynamoDB; +using Amazon.Lambda.DynamoDBEvents; +using System; + +namespace TestServerlessApp.DynamoDBEventExamples +{ + // This file represents invalid usage of the DynamoDBEventAttribute. + // This file is sent as input to the source generator unit tests and we assert that compilation errors are thrown with the appropriate diagnostic message. + + public class InvalidDynamoDBEvents + { + [LambdaFunction(PackageType = LambdaPackageType.Image)] + [DynamoDBEvent("@testTable", BatchSize = 10001, MaximumBatchingWindowInSeconds = 301, StartingPosition = "INVALID")] + public void ProcessMessageWithInvalidDynamoDBEventAttributes(DynamoDBEvent evnt) + { + Console.WriteLine($"Event processed: {evnt}"); + } + + [LambdaFunction(PackageType = LambdaPackageType.Image)] + [DynamoDBEvent("@testTable")] + public void ProcessMessageWithInvalidParameters(DynamoDBEvent evnt, bool invalidParameter1, int invalidParameter2) + { + Console.WriteLine($"Event processed: {evnt}"); + } + + [LambdaFunction(PackageType = LambdaPackageType.Image)] + [DynamoDBEvent("@testTable")] + public bool ProcessMessageWithInvalidReturnType(DynamoDBEvent evnt) + { + Console.WriteLine($"Event processed: {evnt}"); + return true; + } + + [LambdaFunction(PackageType = LambdaPackageType.Image)] + [RestApi(LambdaHttpMethod.Get, "/")] + [DynamoDBEvent("@testTable")] + public void ProcessMessageWithMultipleEventTypes(DynamoDBEvent evnt) + { + Console.WriteLine($"Event processed: {evnt}"); + } + + [LambdaFunction(PackageType = LambdaPackageType.Image)] + [DynamoDBEvent("not-a-valid-arn")] + public void ProcessMessageWithInvalidStreamArn(DynamoDBEvent evnt) + { + Console.WriteLine($"Event processed: {evnt}"); + } + + [LambdaFunction(PackageType = LambdaPackageType.Image)] + [DynamoDBEvent("@testTable", ResourceName = "dynamo-event-source")] + public void ProcessMessageWithInvalidResourceName(DynamoDBEvent evnt) + { + Console.WriteLine($"Event processed: {evnt}"); + } + + [LambdaFunction(PackageType = LambdaPackageType.Image)] + [DynamoDBEvent("@testTable", ResourceName = "")] + public void ProcessMessageWithEmptyResourceName(DynamoDBEvent evnt) + { + Console.WriteLine($"Event processed: {evnt}"); + } + } +} diff --git a/Libraries/test/TestServerlessApp/DynamoDBEventExamples/ValidDynamoDBEvents.cs.txt b/Libraries/test/TestServerlessApp/DynamoDBEventExamples/ValidDynamoDBEvents.cs.txt new file mode 100644 index 000000000..c10996d5d --- /dev/null +++ b/Libraries/test/TestServerlessApp/DynamoDBEventExamples/ValidDynamoDBEvents.cs.txt @@ -0,0 +1,33 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +using Amazon.Lambda.Annotations; +using Amazon.Lambda.Annotations.DynamoDB; +using Amazon.Lambda.DynamoDBEvents; +using System; +using System.Threading.Tasks; + +namespace TestServerlessApp.DynamoDBEventExamples +{ + // This file represents valid usage of the DynamoDBEventAttribute. This is added as .txt file since we do not want to deploy these functions during our integration tests. + // This file is only sent as input to the source generator unit tests. + + public class ValidDynamoDBEvents + { + [LambdaFunction(PackageType = LambdaPackageType.Image)] + [DynamoDBEvent("arn:aws:dynamodb:us-east-2:444455556666:table/MyTable/stream/2024-01-01T00:00:00", BatchSize = 50, MaximumBatchingWindowInSeconds = 2, Filters = "My-Filter-1; My-Filter-2")] + [DynamoDBEvent("arn:aws:dynamodb:us-east-2:444455556666:table/MyTable2/stream/2024-01-01T00:00:00", StartingPosition = "TRIM_HORIZON", Enabled = false)] + [DynamoDBEvent("@testTable", ResourceName = "testTableEvent")] + public void ProcessMessages(DynamoDBEvent evnt) + { + Console.WriteLine($"Event processed: {evnt}"); + } + + [LambdaFunction(PackageType = LambdaPackageType.Image)] + [DynamoDBEvent("arn:aws:dynamodb:us-east-2:444455556666:table/MyTable/stream/2024-01-01T00:00:00")] + public async Task ProcessMessagesAsync(DynamoDBEvent evnt) + { + await Console.Out.WriteLineAsync($"Event processed: {evnt}"); + } + } +} diff --git a/Libraries/test/TestServerlessApp/DynamoDbStreamProcessing.cs b/Libraries/test/TestServerlessApp/DynamoDbStreamProcessing.cs new file mode 100644 index 000000000..29eacb3a2 --- /dev/null +++ b/Libraries/test/TestServerlessApp/DynamoDbStreamProcessing.cs @@ -0,0 +1,17 @@ +using Amazon.Lambda.Annotations; +using Amazon.Lambda.Annotations.DynamoDB; +using Amazon.Lambda.Core; +using Amazon.Lambda.DynamoDBEvents; + +namespace TestServerlessApp +{ + public class DynamoDbStreamProcessing + { + [LambdaFunction(ResourceName = "DynamoDBStreamHandler", Policies = "AWSLambdaDynamoDBExecutionRole", PackageType = LambdaPackageType.Image)] + [DynamoDBEvent("@TestTable", ResourceName = "TestTableStream", BatchSize = 100, StartingPosition = "TRIM_HORIZON")] + public void HandleStream(DynamoDBEvent evnt, ILambdaContext lambdaContext) + { + lambdaContext.Logger.Log($"Received {evnt.Records.Count} records"); + } + } +} diff --git a/Libraries/test/TestServerlessApp/SNSEventExamples/InvalidSNSEvents.cs.error b/Libraries/test/TestServerlessApp/SNSEventExamples/InvalidSNSEvents.cs.error new file mode 100644 index 000000000..639c31441 --- /dev/null +++ b/Libraries/test/TestServerlessApp/SNSEventExamples/InvalidSNSEvents.cs.error @@ -0,0 +1,61 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +using Amazon.Lambda.Annotations; +using Amazon.Lambda.Annotations.APIGateway; +using Amazon.Lambda.Annotations.SNS; +using Amazon.Lambda.SNSEvents; +using System; + +namespace TestServerlessApp.SNSEventExamples +{ + // This file represents invalid usage of the SNSEventAttribute. + // This file is sent as input to the source generator unit tests and we assert that compilation errors are thrown with the appropriate diagnostic message. + + public class InvalidSNSEvents + { + [LambdaFunction(PackageType = LambdaPackageType.Image)] + [SNSEvent("not-a-valid-arn")] + public void ProcessMessageWithInvalidTopicArn(SNSEvent evnt) + { + Console.WriteLine($"Event processed: {evnt}"); + } + + [LambdaFunction(PackageType = LambdaPackageType.Image)] + [SNSEvent("@testTopic")] + public void ProcessMessageWithInvalidParameters(SNSEvent evnt, bool invalidParameter1, int invalidParameter2) + { + Console.WriteLine($"Event processed: {evnt}"); + } + + [LambdaFunction(PackageType = LambdaPackageType.Image)] + [SNSEvent("@testTopic")] + public bool ProcessMessageWithInvalidReturnType(SNSEvent evnt) + { + Console.WriteLine($"Event processed: {evnt}"); + return true; + } + + [LambdaFunction(PackageType = LambdaPackageType.Image)] + [RestApi(LambdaHttpMethod.Get, "/")] + [SNSEvent("@testTopic")] + public void ProcessMessageWithMultipleEventTypes(SNSEvent evnt) + { + Console.WriteLine($"Event processed: {evnt}"); + } + + [LambdaFunction(PackageType = LambdaPackageType.Image)] + [SNSEvent("@testTopic", ResourceName = "sns-event-source")] + public void ProcessMessageWithInvalidResourceName(SNSEvent evnt) + { + Console.WriteLine($"Event processed: {evnt}"); + } + + [LambdaFunction(PackageType = LambdaPackageType.Image)] + [SNSEvent("@testTopic", ResourceName = "")] + public void ProcessMessageWithEmptyResourceName(SNSEvent evnt) + { + Console.WriteLine($"Event processed: {evnt}"); + } + } +} diff --git a/Libraries/test/TestServerlessApp/SQSEventExamples/InvalidSQSEvents.cs.error b/Libraries/test/TestServerlessApp/SQSEventExamples/InvalidSQSEvents.cs.error index 1603f121d..617c758f7 100644 --- a/Libraries/test/TestServerlessApp/SQSEventExamples/InvalidSQSEvents.cs.error +++ b/Libraries/test/TestServerlessApp/SQSEventExamples/InvalidSQSEvents.cs.error @@ -1,4 +1,7 @@ -using Amazon.Lambda.Annotations; +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +using Amazon.Lambda.Annotations; using Amazon.Lambda.Annotations.APIGateway; using Amazon.Lambda.Annotations.SQS; using Amazon.Lambda.SQSEvents; diff --git a/Libraries/test/TestServerlessApp/SQSEventExamples/ValidSQSEvents.cs.txt b/Libraries/test/TestServerlessApp/SQSEventExamples/ValidSQSEvents.cs.txt index 8bd12d68e..5c177b178 100644 --- a/Libraries/test/TestServerlessApp/SQSEventExamples/ValidSQSEvents.cs.txt +++ b/Libraries/test/TestServerlessApp/SQSEventExamples/ValidSQSEvents.cs.txt @@ -1,3 +1,6 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + using Amazon.Lambda.Annotations; using Amazon.Lambda.Annotations.SQS; using Amazon.Lambda.SQSEvents; diff --git a/Libraries/test/TestServerlessApp/ScheduleEventExamples/InvalidScheduleEvents.cs.error b/Libraries/test/TestServerlessApp/ScheduleEventExamples/InvalidScheduleEvents.cs.error new file mode 100644 index 000000000..4ed8897cc --- /dev/null +++ b/Libraries/test/TestServerlessApp/ScheduleEventExamples/InvalidScheduleEvents.cs.error @@ -0,0 +1,61 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +using Amazon.Lambda.Annotations; +using Amazon.Lambda.Annotations.APIGateway; +using Amazon.Lambda.Annotations.Schedule; +using Amazon.Lambda.CloudWatchEvents.ScheduledEvents; +using System; + +namespace TestServerlessApp.ScheduleEventExamples +{ + // This file represents invalid usage of the ScheduleEventAttribute. + // This file is sent as input to the source generator unit tests and we assert that compilation errors are thrown with the appropriate diagnostic message. + + public class InvalidScheduleEvents + { + [LambdaFunction(PackageType = LambdaPackageType.Image)] + [ScheduleEvent("every 5 minutes")] + public void ProcessMessageWithInvalidScheduleExpression(ScheduledEvent evnt) + { + Console.WriteLine($"Event processed: {evnt}"); + } + + [LambdaFunction(PackageType = LambdaPackageType.Image)] + [ScheduleEvent("rate(5 minutes)")] + public void ProcessMessageWithInvalidParameters(ScheduledEvent evnt, bool invalidParameter1, int invalidParameter2) + { + Console.WriteLine($"Event processed: {evnt}"); + } + + [LambdaFunction(PackageType = LambdaPackageType.Image)] + [ScheduleEvent("rate(5 minutes)")] + public bool ProcessMessageWithInvalidReturnType(ScheduledEvent evnt) + { + Console.WriteLine($"Event processed: {evnt}"); + return true; + } + + [LambdaFunction(PackageType = LambdaPackageType.Image)] + [RestApi(LambdaHttpMethod.Get, "/")] + [ScheduleEvent("rate(5 minutes)")] + public void ProcessMessageWithMultipleEventTypes(ScheduledEvent evnt) + { + Console.WriteLine($"Event processed: {evnt}"); + } + + [LambdaFunction(PackageType = LambdaPackageType.Image)] + [ScheduleEvent("rate(5 minutes)", ResourceName = "invalid-name!")] + public void ProcessMessageWithInvalidResourceName(ScheduledEvent evnt) + { + Console.WriteLine($"Event processed: {evnt}"); + } + + [LambdaFunction(PackageType = LambdaPackageType.Image)] + [ScheduleEvent("rate(5 minutes)", ResourceName = "")] + public void ProcessMessageWithEmptyResourceName(ScheduledEvent evnt) + { + Console.WriteLine($"Event processed: {evnt}"); + } + } +} diff --git a/Libraries/test/TestServerlessApp/ScheduleEventExamples/ValidScheduleEvents.cs.txt b/Libraries/test/TestServerlessApp/ScheduleEventExamples/ValidScheduleEvents.cs.txt new file mode 100644 index 000000000..179417209 --- /dev/null +++ b/Libraries/test/TestServerlessApp/ScheduleEventExamples/ValidScheduleEvents.cs.txt @@ -0,0 +1,28 @@ +using Amazon.Lambda.Annotations; +using Amazon.Lambda.Annotations.Schedule; +using Amazon.Lambda.CloudWatchEvents.ScheduledEvents; +using System; +using System.Threading.Tasks; + +namespace TestServerlessApp.ScheduleEventExamples +{ + // This file represents valid usage of the ScheduleEventAttribute. This is added as .txt file since we do not want to deploy these functions during our integration tests. + // This file is only sent as input to the source generator unit tests. + + public class ValidScheduleEvents + { + [LambdaFunction(PackageType = LambdaPackageType.Image)] + [ScheduleEvent("rate(5 minutes)", Description = "Runs every 5 minutes", Input = "{ \"key\": \"value\" }")] + public void ProcessScheduledEvent(ScheduledEvent evnt) + { + Console.WriteLine($"Event processed: {evnt}"); + } + + [LambdaFunction(PackageType = LambdaPackageType.Image)] + [ScheduleEvent("cron(0 12 * * ? *)", ResourceName = "DailyNoonSchedule")] + public async Task ProcessScheduledEventAsync(ScheduledEvent evnt) + { + await Console.Out.WriteLineAsync($"Event processed: {evnt}"); + } + } +} diff --git a/Libraries/test/TestServerlessApp/ScheduledProcessing.cs b/Libraries/test/TestServerlessApp/ScheduledProcessing.cs new file mode 100644 index 000000000..fd1079948 --- /dev/null +++ b/Libraries/test/TestServerlessApp/ScheduledProcessing.cs @@ -0,0 +1,20 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +using Amazon.Lambda.Annotations; +using Amazon.Lambda.Annotations.Schedule; +using Amazon.Lambda.CloudWatchEvents.ScheduledEvents; +using Amazon.Lambda.Core; + +namespace TestServerlessApp +{ + public class ScheduledProcessing + { + [LambdaFunction(ResourceName = "ScheduledHandler", Policies = "AWSLambdaBasicExecutionRole", PackageType = LambdaPackageType.Image)] + [ScheduleEvent("rate(5 minutes)", ResourceName = "FiveMinuteSchedule", Description = "Runs every 5 minutes")] + public void HandleSchedule(ScheduledEvent evnt, ILambdaContext lambdaContext) + { + lambdaContext.Logger.Log($"Scheduled event received at {evnt.Time}"); + } + } +} diff --git a/Libraries/test/TestServerlessApp/TestServerlessApp.csproj b/Libraries/test/TestServerlessApp/TestServerlessApp.csproj index 9875acee9..180082c47 100644 --- a/Libraries/test/TestServerlessApp/TestServerlessApp.csproj +++ b/Libraries/test/TestServerlessApp/TestServerlessApp.csproj @@ -27,7 +27,9 @@ + + diff --git a/Libraries/test/TestServerlessApp/aws-lambda-tools-defaults.json b/Libraries/test/TestServerlessApp/aws-lambda-tools-defaults.json index 03fe9926f..a4d211c58 100644 --- a/Libraries/test/TestServerlessApp/aws-lambda-tools-defaults.json +++ b/Libraries/test/TestServerlessApp/aws-lambda-tools-defaults.json @@ -13,7 +13,7 @@ "template": "serverless.template", "template-parameters": "", "docker-host-build-output-dir": "./bin/Release/lambda-publish", -"s3-bucket" : "test-serverless-app-784dfb1d", -"stack-name" : "test-serverless-app-784dfb1d", + "s3-bucket" : "test-serverless-app-872e667d", + "stack-name" : "test-serverless-app-872e667d", "function-architecture" : "x86_64" } diff --git a/Libraries/test/TestServerlessApp/serverless.template b/Libraries/test/TestServerlessApp/serverless.template index 65fcfee31..1def2f1e1 100644 --- a/Libraries/test/TestServerlessApp/serverless.template +++ b/Libraries/test/TestServerlessApp/serverless.template @@ -58,7 +58,7 @@ "Tool": "Amazon.Lambda.Annotations" } }, - "AuthNameFallbackTest": { + "TestServerlessAppCustomAuthorizerWithIHttpResultsExampleAuthorizerWithIHttpResultsGenerated": { "Type": "AWS::Serverless::Function", "Metadata": { "Tool": "Amazon.Lambda.Annotations", @@ -83,14 +83,14 @@ "ImageUri": ".", "ImageConfig": { "Command": [ - "TestServerlessApp::TestServerlessApp.AuthNameFallback_GetUserId_Generated::GetUserId" + "TestServerlessApp::TestServerlessApp.CustomAuthorizerWithIHttpResultsExample_AuthorizerWithIHttpResults_Generated::AuthorizerWithIHttpResults" ] }, "Events": { "RootGet": { "Type": "HttpApi", "Properties": { - "Path": "/api/authorizer-fallback", + "Path": "/authorizerihttpresults", "Method": "GET", "ApiId": { "Ref": "AnnotationsHttpApi" @@ -100,20 +100,11 @@ } } }, - "TestServerlessAppComplexCalculatorAddGenerated": { + "TestServerlessAppFunctionUrlExampleGetItemsGenerated": { "Type": "AWS::Serverless::Function", "Metadata": { "Tool": "Amazon.Lambda.Annotations", - "SyncedEvents": [ - "RootPost" - ], - "SyncedEventProperties": { - "RootPost": [ - "Path", - "Method", - "ApiId.Ref" - ] - } + "SyncedFunctionUrlConfig": true }, "Properties": { "MemorySize": 512, @@ -125,32 +116,23 @@ "ImageUri": ".", "ImageConfig": { "Command": [ - "TestServerlessApp::TestServerlessApp.ComplexCalculator_Add_Generated::Add" + "TestServerlessApp::TestServerlessApp.FunctionUrlExample_GetItems_Generated::GetItems" ] }, - "Events": { - "RootPost": { - "Type": "HttpApi", - "Properties": { - "Path": "/ComplexCalculator/Add", - "Method": "POST", - "ApiId": { - "Ref": "AnnotationsHttpApi" - } - } - } + "FunctionUrlConfig": { + "AuthType": "NONE" } } }, - "TestServerlessAppComplexCalculatorSubtractGenerated": { + "HttpApiAuthorizerTest": { "Type": "AWS::Serverless::Function", "Metadata": { "Tool": "Amazon.Lambda.Annotations", "SyncedEvents": [ - "RootPost" + "RootGet" ], "SyncedEventProperties": { - "RootPost": [ + "RootGet": [ "Path", "Method", "ApiId.Ref" @@ -167,15 +149,15 @@ "ImageUri": ".", "ImageConfig": { "Command": [ - "TestServerlessApp::TestServerlessApp.ComplexCalculator_Subtract_Generated::Subtract" + "TestServerlessApp::TestServerlessApp.CustomAuthorizerHttpApiExample_HttpApiAuthorizer_Generated::HttpApiAuthorizer" ] }, "Events": { - "RootPost": { + "RootGet": { "Type": "HttpApi", "Properties": { - "Path": "/ComplexCalculator/Subtract", - "Method": "POST", + "Path": "/api/authorizer", + "Method": "GET", "ApiId": { "Ref": "AnnotationsHttpApi" } @@ -184,18 +166,21 @@ } } }, - "HttpApiAuthorizerTest": { + "SQSMessageHandler": { "Type": "AWS::Serverless::Function", "Metadata": { "Tool": "Amazon.Lambda.Annotations", "SyncedEvents": [ - "RootGet" + "TestQueueEvent" ], "SyncedEventProperties": { - "RootGet": [ - "Path", - "Method", - "ApiId.Ref" + "TestQueueEvent": [ + "Queue.Fn::GetAtt", + "BatchSize", + "FilterCriteria.Filters", + "FunctionResponseTypes", + "MaximumBatchingWindowInSeconds", + "ScalingConfig.MaximumConcurrency" ] } }, @@ -203,29 +188,65 @@ "MemorySize": 512, "Timeout": 30, "Policies": [ - "AWSLambdaBasicExecutionRole" + "AWSLambdaSQSQueueExecutionRole" ], "PackageType": "Image", "ImageUri": ".", "ImageConfig": { "Command": [ - "TestServerlessApp::TestServerlessApp.CustomAuthorizerHttpApiExample_HttpApiAuthorizer_Generated::HttpApiAuthorizer" + "TestServerlessApp::TestServerlessApp.SqsMessageProcessing_HandleMessage_Generated::HandleMessage" ] }, "Events": { - "RootGet": { - "Type": "HttpApi", + "TestQueueEvent": { + "Type": "SQS", "Properties": { - "Path": "/api/authorizer", - "Method": "GET", - "ApiId": { - "Ref": "AnnotationsHttpApi" + "BatchSize": 50, + "FilterCriteria": { + "Filters": [ + { + "Pattern": "{ \"body\" : { \"RequestCode\" : [ \"BBBB\" ] } }" + } + ] + }, + "FunctionResponseTypes": [ + "ReportBatchItemFailures" + ], + "MaximumBatchingWindowInSeconds": 5, + "ScalingConfig": { + "MaximumConcurrency": 5 + }, + "Queue": { + "Fn::GetAtt": [ + "TestQueue", + "Arn" + ] } } } } } }, + "TestServerlessAppFromScratchNoSerializerAttributeReferenceToUpperGenerated": { + "Type": "AWS::Serverless::Function", + "Metadata": { + "Tool": "Amazon.Lambda.Annotations" + }, + "Properties": { + "MemorySize": 512, + "Timeout": 30, + "Policies": [ + "AWSLambdaBasicExecutionRole" + ], + "PackageType": "Image", + "ImageUri": ".", + "ImageConfig": { + "Command": [ + "TestServerlessApp::TestServerlessApp.FromScratch.NoSerializerAttributeReference_ToUpper_Generated::ToUpper" + ] + } + } + }, "HttpApiV1AuthorizerTest": { "Type": "AWS::Serverless::Function", "Metadata": { @@ -270,18 +291,17 @@ } } }, - "HttpApiNonString": { + "SNSMessageHandler": { "Type": "AWS::Serverless::Function", "Metadata": { "Tool": "Amazon.Lambda.Annotations", "SyncedEvents": [ - "RootGet" + "TestTopicEvent" ], "SyncedEventProperties": { - "RootGet": [ - "Path", - "Method", - "ApiId.Ref" + "TestTopicEvent": [ + "Topic.Ref", + "FilterPolicy" ] } }, @@ -295,37 +315,26 @@ "ImageUri": ".", "ImageConfig": { "Command": [ - "TestServerlessApp::TestServerlessApp.CustomAuthorizerNonStringExample_HttpApiWithNonString_Generated::HttpApiWithNonString" + "TestServerlessApp::TestServerlessApp.SnsMessageProcessing_HandleMessage_Generated::HandleMessage" ] }, "Events": { - "RootGet": { - "Type": "HttpApi", + "TestTopicEvent": { + "Type": "SNS", "Properties": { - "Path": "/api/authorizer-non-string", - "Method": "GET", - "ApiId": { - "Ref": "AnnotationsHttpApi" + "FilterPolicy": "{ \"store\": [\"example_corp\"] }", + "Topic": { + "Ref": "TestTopic" } } } } } }, - "RestAuthorizerTest": { + "TestServerlessAppVoidExampleVoidReturnGenerated": { "Type": "AWS::Serverless::Function", "Metadata": { - "Tool": "Amazon.Lambda.Annotations", - "SyncedEvents": [ - "RootGet" - ], - "SyncedEventProperties": { - "RootGet": [ - "Path", - "Method", - "RestApiId.Ref" - ] - } + "Tool": "Amazon.Lambda.Annotations" }, "Properties": { "MemorySize": 512, @@ -337,24 +346,12 @@ "ImageUri": ".", "ImageConfig": { "Command": [ - "TestServerlessApp::TestServerlessApp.CustomAuthorizerRestExample_RestAuthorizer_Generated::RestAuthorizer" + "TestServerlessApp::TestServerlessApp.VoidExample_VoidReturn_Generated::VoidReturn" ] - }, - "Events": { - "RootGet": { - "Type": "Api", - "Properties": { - "Path": "/rest/authorizer", - "Method": "GET", - "RestApiId": { - "Ref": "AnnotationsRestApi" - } - } - } } } }, - "TestServerlessAppCustomAuthorizerWithIHttpResultsExampleAuthorizerWithIHttpResultsGenerated": { + "RestAuthorizerTest": { "Type": "AWS::Serverless::Function", "Metadata": { "Tool": "Amazon.Lambda.Annotations", @@ -365,7 +362,7 @@ "RootGet": [ "Path", "Method", - "ApiId.Ref" + "RestApiId.Ref" ] } }, @@ -379,17 +376,17 @@ "ImageUri": ".", "ImageConfig": { "Command": [ - "TestServerlessApp::TestServerlessApp.CustomAuthorizerWithIHttpResultsExample_AuthorizerWithIHttpResults_Generated::AuthorizerWithIHttpResults" + "TestServerlessApp::TestServerlessApp.CustomAuthorizerRestExample_RestAuthorizer_Generated::RestAuthorizer" ] }, "Events": { "RootGet": { - "Type": "HttpApi", + "Type": "Api", "Properties": { - "Path": "/authorizerihttpresults", + "Path": "/rest/authorizer", "Method": "GET", - "ApiId": { - "Ref": "AnnotationsHttpApi" + "RestApiId": { + "Ref": "AnnotationsRestApi" } } } @@ -696,10 +693,20 @@ } } }, - "TestServerlessAppDynamicExampleDynamicReturnGenerated": { + "SimpleCalculatorAdd": { "Type": "AWS::Serverless::Function", "Metadata": { - "Tool": "Amazon.Lambda.Annotations" + "Tool": "Amazon.Lambda.Annotations", + "SyncedEvents": [ + "RootGet" + ], + "SyncedEventProperties": { + "RootGet": [ + "Path", + "Method", + "RestApiId.Ref" + ] + } }, "Properties": { "MemorySize": 512, @@ -711,15 +718,37 @@ "ImageUri": ".", "ImageConfig": { "Command": [ - "TestServerlessApp::TestServerlessApp.DynamicExample_DynamicReturn_Generated::DynamicReturn" + "TestServerlessApp::TestServerlessApp.SimpleCalculator_Add_Generated::Add" ] + }, + "Events": { + "RootGet": { + "Type": "Api", + "Properties": { + "Path": "/SimpleCalculator/Add", + "Method": "GET", + "RestApiId": { + "Ref": "AnnotationsRestApi" + } + } + } } } }, - "TestServerlessAppDynamicExampleDynamicInputGenerated": { + "SimpleCalculatorSubtract": { "Type": "AWS::Serverless::Function", "Metadata": { - "Tool": "Amazon.Lambda.Annotations" + "Tool": "Amazon.Lambda.Annotations", + "SyncedEvents": [ + "RootGet" + ], + "SyncedEventProperties": { + "RootGet": [ + "Path", + "Method", + "RestApiId.Ref" + ] + } }, "Properties": { "MemorySize": 512, @@ -731,12 +760,24 @@ "ImageUri": ".", "ImageConfig": { "Command": [ - "TestServerlessApp::TestServerlessApp.DynamicExample_DynamicInput_Generated::DynamicInput" + "TestServerlessApp::TestServerlessApp.SimpleCalculator_Subtract_Generated::Subtract" ] + }, + "Events": { + "RootGet": { + "Type": "Api", + "Properties": { + "Path": "/SimpleCalculator/Subtract", + "Method": "GET", + "RestApiId": { + "Ref": "AnnotationsRestApi" + } + } + } } } }, - "TestServerlessAppFromScratchNoApiGatewayEventsReferenceToUpperGenerated": { + "SimpleCalculatorMultiply": { "Type": "AWS::Serverless::Function", "Metadata": { "Tool": "Amazon.Lambda.Annotations", @@ -747,7 +788,7 @@ "RootGet": [ "Path", "Method", - "ApiId.Ref" + "RestApiId.Ref" ] } }, @@ -761,29 +802,39 @@ "ImageUri": ".", "ImageConfig": { "Command": [ - "TestServerlessApp::TestServerlessApp.FromScratch.NoApiGatewayEventsReference_ToUpper_Generated::ToUpper" + "TestServerlessApp::TestServerlessApp.SimpleCalculator_Multiply_Generated::Multiply" ] }, "Events": { "RootGet": { - "Type": "HttpApi", + "Type": "Api", "Properties": { - "Path": "/{text}", + "Path": "/SimpleCalculator/Multiply/{x}/{y}", "Method": "GET", - "ApiId": { - "Ref": "AnnotationsHttpApi" + "RestApiId": { + "Ref": "AnnotationsRestApi" } } } } } }, - "TestServerlessAppFromScratchNoSerializerAttributeReferenceToUpperGenerated": { + "SimpleCalculatorDivideAsync": { "Type": "AWS::Serverless::Function", "Metadata": { - "Tool": "Amazon.Lambda.Annotations" - }, - "Properties": { + "Tool": "Amazon.Lambda.Annotations", + "SyncedEvents": [ + "RootGet" + ], + "SyncedEventProperties": { + "RootGet": [ + "Path", + "Method", + "RestApiId.Ref" + ] + } + }, + "Properties": { "MemorySize": 512, "Timeout": 30, "Policies": [ @@ -793,16 +844,27 @@ "ImageUri": ".", "ImageConfig": { "Command": [ - "TestServerlessApp::TestServerlessApp.FromScratch.NoSerializerAttributeReference_ToUpper_Generated::ToUpper" + "TestServerlessApp::TestServerlessApp.SimpleCalculator_DivideAsync_Generated::DivideAsync" ] + }, + "Events": { + "RootGet": { + "Type": "Api", + "Properties": { + "Path": "/SimpleCalculator/DivideAsync/{x}/{y}", + "Method": "GET", + "RestApiId": { + "Ref": "AnnotationsRestApi" + } + } + } } } }, - "TestServerlessAppFunctionUrlExampleGetItemsGenerated": { + "PI": { "Type": "AWS::Serverless::Function", "Metadata": { - "Tool": "Amazon.Lambda.Annotations", - "SyncedFunctionUrlConfig": true + "Tool": "Amazon.Lambda.Annotations" }, "Properties": { "MemorySize": 512, @@ -814,32 +876,18 @@ "ImageUri": ".", "ImageConfig": { "Command": [ - "TestServerlessApp::TestServerlessApp.FunctionUrlExample_GetItems_Generated::GetItems" + "TestServerlessApp::TestServerlessApp.SimpleCalculator_Pi_Generated::Pi" ] - }, - "FunctionUrlConfig": { - "AuthType": "NONE" } } }, - "GreeterSayHello": { + "Random": { "Type": "AWS::Serverless::Function", "Metadata": { - "Tool": "Amazon.Lambda.Annotations", - "SyncedEvents": [ - "RootGet" - ], - "SyncedEventProperties": { - "RootGet": [ - "Path", - "Method", - "PayloadFormatVersion", - "ApiId.Ref" - ] - } + "Tool": "Amazon.Lambda.Annotations" }, "Properties": { - "MemorySize": 1024, + "MemorySize": 512, "Timeout": 30, "Policies": [ "AWSLambdaBasicExecutionRole" @@ -848,43 +896,19 @@ "ImageUri": ".", "ImageConfig": { "Command": [ - "TestServerlessApp::TestServerlessApp.Greeter_SayHello_Generated::SayHello" + "TestServerlessApp::TestServerlessApp.SimpleCalculator_Random_Generated::Random" ] - }, - "Events": { - "RootGet": { - "Type": "HttpApi", - "Properties": { - "Path": "/Greeter/SayHello", - "Method": "GET", - "PayloadFormatVersion": "1.0", - "ApiId": { - "Ref": "AnnotationsHttpApi" - } - } - } } } }, - "GreeterSayHelloAsync": { + "Randoms": { "Type": "AWS::Serverless::Function", "Metadata": { - "Tool": "Amazon.Lambda.Annotations", - "SyncedEvents": [ - "RootGet" - ], - "SyncedEventProperties": { - "RootGet": [ - "Path", - "Method", - "PayloadFormatVersion", - "ApiId.Ref" - ] - } + "Tool": "Amazon.Lambda.Annotations" }, "Properties": { "MemorySize": 512, - "Timeout": 50, + "Timeout": 30, "Policies": [ "AWSLambdaBasicExecutionRole" ], @@ -892,25 +916,12 @@ "ImageUri": ".", "ImageConfig": { "Command": [ - "TestServerlessApp::TestServerlessApp.Greeter_SayHelloAsync_Generated::SayHelloAsync" + "TestServerlessApp::TestServerlessApp.SimpleCalculator_Randoms_Generated::Randoms" ] - }, - "Events": { - "RootGet": { - "Type": "HttpApi", - "Properties": { - "Path": "/Greeter/SayHelloAsync", - "Method": "GET", - "PayloadFormatVersion": "1.0", - "ApiId": { - "Ref": "AnnotationsHttpApi" - } - } - } } } }, - "SimpleHttpApiAuth": { + "TestServerlessAppIntrinsicExampleHasIntrinsicGenerated": { "Type": "AWS::Serverless::Function", "Metadata": { "Tool": "Amazon.Lambda.Annotations" @@ -925,12 +936,12 @@ "ImageUri": ".", "ImageConfig": { "Command": [ - "TestServerlessApp::TestServerlessApp.IAuthorizerResultExample_SimpleHttpApiAuthorizer_Generated::SimpleHttpApiAuthorizer" + "TestServerlessApp::TestServerlessApp.IntrinsicExample_HasIntrinsic_Generated::HasIntrinsic" ] } } }, - "SimpleRestApiAuth": { + "SimpleHttpApiAuth": { "Type": "AWS::Serverless::Function", "Metadata": { "Tool": "Amazon.Lambda.Annotations" @@ -945,12 +956,12 @@ "ImageUri": ".", "ImageConfig": { "Command": [ - "TestServerlessApp::TestServerlessApp.IAuthorizerResultExample_SimpleRestApiAuthorizer_Generated::SimpleRestApiAuthorizer" + "TestServerlessApp::TestServerlessApp.IAuthorizerResultExample_SimpleHttpApiAuthorizer_Generated::SimpleHttpApiAuthorizer" ] } } }, - "TestServerlessAppIntrinsicExampleHasIntrinsicGenerated": { + "SimpleRestApiAuth": { "Type": "AWS::Serverless::Function", "Metadata": { "Tool": "Amazon.Lambda.Annotations" @@ -965,23 +976,23 @@ "ImageUri": ".", "ImageConfig": { "Command": [ - "TestServerlessApp::TestServerlessApp.IntrinsicExample_HasIntrinsic_Generated::HasIntrinsic" + "TestServerlessApp::TestServerlessApp.IAuthorizerResultExample_SimpleRestApiAuthorizer_Generated::SimpleRestApiAuthorizer" ] } } }, - "TestServerlessAppNullableReferenceTypeExampleNullableHeaderHttpApiGenerated": { + "DynamoDBStreamHandler": { "Type": "AWS::Serverless::Function", "Metadata": { "Tool": "Amazon.Lambda.Annotations", "SyncedEvents": [ - "RootGet" + "TestTableStream" ], "SyncedEventProperties": { - "RootGet": [ - "Path", - "Method", - "ApiId.Ref" + "TestTableStream": [ + "Stream.Fn::GetAtt", + "StartingPosition", + "BatchSize" ] } }, @@ -989,23 +1000,26 @@ "MemorySize": 512, "Timeout": 30, "Policies": [ - "AWSLambdaBasicExecutionRole" + "AWSLambdaDynamoDBExecutionRole" ], "PackageType": "Image", "ImageUri": ".", "ImageConfig": { "Command": [ - "TestServerlessApp::TestServerlessApp.NullableReferenceTypeExample_NullableHeaderHttpApi_Generated::NullableHeaderHttpApi" + "TestServerlessApp::TestServerlessApp.DynamoDbStreamProcessing_HandleStream_Generated::HandleStream" ] }, "Events": { - "RootGet": { - "Type": "HttpApi", + "TestTableStream": { + "Type": "DynamoDB", "Properties": { - "Path": "/nullableheaderhttpapi", - "Method": "GET", - "ApiId": { - "Ref": "AnnotationsHttpApi" + "StartingPosition": "TRIM_HORIZON", + "BatchSize": 100, + "Stream": { + "Fn::GetAtt": [ + "TestTable", + "StreamArn" + ] } } } @@ -1066,7 +1080,7 @@ } } }, - "SimpleCalculatorAdd": { + "GreeterSayHello": { "Type": "AWS::Serverless::Function", "Metadata": { "Tool": "Amazon.Lambda.Annotations", @@ -1077,12 +1091,13 @@ "RootGet": [ "Path", "Method", - "RestApiId.Ref" + "PayloadFormatVersion", + "ApiId.Ref" ] } }, "Properties": { - "MemorySize": 512, + "MemorySize": 1024, "Timeout": 30, "Policies": [ "AWSLambdaBasicExecutionRole" @@ -1091,24 +1106,25 @@ "ImageUri": ".", "ImageConfig": { "Command": [ - "TestServerlessApp::TestServerlessApp.SimpleCalculator_Add_Generated::Add" + "TestServerlessApp::TestServerlessApp.Greeter_SayHello_Generated::SayHello" ] }, "Events": { "RootGet": { - "Type": "Api", + "Type": "HttpApi", "Properties": { - "Path": "/SimpleCalculator/Add", + "Path": "/Greeter/SayHello", "Method": "GET", - "RestApiId": { - "Ref": "AnnotationsRestApi" + "PayloadFormatVersion": "1.0", + "ApiId": { + "Ref": "AnnotationsHttpApi" } } } } } }, - "SimpleCalculatorSubtract": { + "GreeterSayHelloAsync": { "Type": "AWS::Serverless::Function", "Metadata": { "Tool": "Amazon.Lambda.Annotations", @@ -1119,13 +1135,14 @@ "RootGet": [ "Path", "Method", - "RestApiId.Ref" + "PayloadFormatVersion", + "ApiId.Ref" ] } }, "Properties": { "MemorySize": 512, - "Timeout": 30, + "Timeout": 50, "Policies": [ "AWSLambdaBasicExecutionRole" ], @@ -1133,24 +1150,25 @@ "ImageUri": ".", "ImageConfig": { "Command": [ - "TestServerlessApp::TestServerlessApp.SimpleCalculator_Subtract_Generated::Subtract" + "TestServerlessApp::TestServerlessApp.Greeter_SayHelloAsync_Generated::SayHelloAsync" ] }, "Events": { "RootGet": { - "Type": "Api", + "Type": "HttpApi", "Properties": { - "Path": "/SimpleCalculator/Subtract", + "Path": "/Greeter/SayHelloAsync", "Method": "GET", - "RestApiId": { - "Ref": "AnnotationsRestApi" + "PayloadFormatVersion": "1.0", + "ApiId": { + "Ref": "AnnotationsHttpApi" } } } } } }, - "SimpleCalculatorMultiply": { + "HttpApiNonString": { "Type": "AWS::Serverless::Function", "Metadata": { "Tool": "Amazon.Lambda.Annotations", @@ -1161,7 +1179,7 @@ "RootGet": [ "Path", "Method", - "RestApiId.Ref" + "ApiId.Ref" ] } }, @@ -1175,24 +1193,44 @@ "ImageUri": ".", "ImageConfig": { "Command": [ - "TestServerlessApp::TestServerlessApp.SimpleCalculator_Multiply_Generated::Multiply" + "TestServerlessApp::TestServerlessApp.CustomAuthorizerNonStringExample_HttpApiWithNonString_Generated::HttpApiWithNonString" ] }, "Events": { "RootGet": { - "Type": "Api", + "Type": "HttpApi", "Properties": { - "Path": "/SimpleCalculator/Multiply/{x}/{y}", + "Path": "/api/authorizer-non-string", "Method": "GET", - "RestApiId": { - "Ref": "AnnotationsRestApi" + "ApiId": { + "Ref": "AnnotationsHttpApi" } } } } } }, - "SimpleCalculatorDivideAsync": { + "TestServerlessAppTaskExampleTaskReturnGenerated": { + "Type": "AWS::Serverless::Function", + "Metadata": { + "Tool": "Amazon.Lambda.Annotations" + }, + "Properties": { + "MemorySize": 512, + "Timeout": 30, + "Policies": [ + "AWSLambdaBasicExecutionRole" + ], + "PackageType": "Image", + "ImageUri": ".", + "ImageConfig": { + "Command": [ + "TestServerlessApp::TestServerlessApp.TaskExample_TaskReturn_Generated::TaskReturn" + ] + } + } + }, + "AuthNameFallbackTest": { "Type": "AWS::Serverless::Function", "Metadata": { "Tool": "Amazon.Lambda.Annotations", @@ -1203,7 +1241,7 @@ "RootGet": [ "Path", "Method", - "RestApiId.Ref" + "ApiId.Ref" ] } }, @@ -1217,24 +1255,24 @@ "ImageUri": ".", "ImageConfig": { "Command": [ - "TestServerlessApp::TestServerlessApp.SimpleCalculator_DivideAsync_Generated::DivideAsync" + "TestServerlessApp::TestServerlessApp.AuthNameFallback_GetUserId_Generated::GetUserId" ] }, "Events": { "RootGet": { - "Type": "Api", + "Type": "HttpApi", "Properties": { - "Path": "/SimpleCalculator/DivideAsync/{x}/{y}", + "Path": "/api/authorizer-fallback", "Method": "GET", - "RestApiId": { - "Ref": "AnnotationsRestApi" + "ApiId": { + "Ref": "AnnotationsHttpApi" } } } } } }, - "PI": { + "TestServerlessAppDynamicExampleDynamicReturnGenerated": { "Type": "AWS::Serverless::Function", "Metadata": { "Tool": "Amazon.Lambda.Annotations" @@ -1249,12 +1287,12 @@ "ImageUri": ".", "ImageConfig": { "Command": [ - "TestServerlessApp::TestServerlessApp.SimpleCalculator_Pi_Generated::Pi" + "TestServerlessApp::TestServerlessApp.DynamicExample_DynamicReturn_Generated::DynamicReturn" ] } } }, - "Random": { + "TestServerlessAppDynamicExampleDynamicInputGenerated": { "Type": "AWS::Serverless::Function", "Metadata": { "Tool": "Amazon.Lambda.Annotations" @@ -1269,12 +1307,12 @@ "ImageUri": ".", "ImageConfig": { "Command": [ - "TestServerlessApp::TestServerlessApp.SimpleCalculator_Random_Generated::Random" + "TestServerlessApp::TestServerlessApp.DynamicExample_DynamicInput_Generated::DynamicInput" ] } } }, - "Randoms": { + "ToUpper": { "Type": "AWS::Serverless::Function", "Metadata": { "Tool": "Amazon.Lambda.Annotations" @@ -1289,26 +1327,22 @@ "ImageUri": ".", "ImageConfig": { "Command": [ - "TestServerlessApp::TestServerlessApp.SimpleCalculator_Randoms_Generated::Randoms" + "TestServerlessApp::TestServerlessApp.Sub1.Functions_ToUpper_Generated::ToUpper" ] } } }, - "SQSMessageHandler": { + "ScheduledHandler": { "Type": "AWS::Serverless::Function", "Metadata": { "Tool": "Amazon.Lambda.Annotations", "SyncedEvents": [ - "TestQueueEvent" + "FiveMinuteSchedule" ], "SyncedEventProperties": { - "TestQueueEvent": [ - "Queue.Fn::GetAtt", - "BatchSize", - "FilterCriteria.Filters", - "FunctionResponseTypes", - "MaximumBatchingWindowInSeconds", - "ScalingConfig.MaximumConcurrency" + "FiveMinuteSchedule": [ + "Schedule", + "Description" ] } }, @@ -1316,49 +1350,40 @@ "MemorySize": 512, "Timeout": 30, "Policies": [ - "AWSLambdaSQSQueueExecutionRole" + "AWSLambdaBasicExecutionRole" ], "PackageType": "Image", "ImageUri": ".", "ImageConfig": { "Command": [ - "TestServerlessApp::TestServerlessApp.SqsMessageProcessing_HandleMessage_Generated::HandleMessage" + "TestServerlessApp::TestServerlessApp.ScheduledProcessing_HandleSchedule_Generated::HandleSchedule" ] }, "Events": { - "TestQueueEvent": { - "Type": "SQS", + "FiveMinuteSchedule": { + "Type": "Schedule", "Properties": { - "BatchSize": 50, - "FilterCriteria": { - "Filters": [ - { - "Pattern": "{ \"body\" : { \"RequestCode\" : [ \"BBBB\" ] } }" - } - ] - }, - "FunctionResponseTypes": [ - "ReportBatchItemFailures" - ], - "MaximumBatchingWindowInSeconds": 5, - "ScalingConfig": { - "MaximumConcurrency": 5 - }, - "Queue": { - "Fn::GetAtt": [ - "TestQueue", - "Arn" - ] - } + "Schedule": "rate(5 minutes)", + "Description": "Runs every 5 minutes" } } } } }, - "ToUpper": { + "TestServerlessAppNullableReferenceTypeExampleNullableHeaderHttpApiGenerated": { "Type": "AWS::Serverless::Function", "Metadata": { - "Tool": "Amazon.Lambda.Annotations" + "Tool": "Amazon.Lambda.Annotations", + "SyncedEvents": [ + "RootGet" + ], + "SyncedEventProperties": { + "RootGet": [ + "Path", + "Method", + "ApiId.Ref" + ] + } }, "Properties": { "MemorySize": 512, @@ -1370,15 +1395,37 @@ "ImageUri": ".", "ImageConfig": { "Command": [ - "TestServerlessApp::TestServerlessApp.Sub1.Functions_ToUpper_Generated::ToUpper" + "TestServerlessApp::TestServerlessApp.NullableReferenceTypeExample_NullableHeaderHttpApi_Generated::NullableHeaderHttpApi" ] + }, + "Events": { + "RootGet": { + "Type": "HttpApi", + "Properties": { + "Path": "/nullableheaderhttpapi", + "Method": "GET", + "ApiId": { + "Ref": "AnnotationsHttpApi" + } + } + } } } }, - "TestServerlessAppTaskExampleTaskReturnGenerated": { + "TestServerlessAppComplexCalculatorAddGenerated": { "Type": "AWS::Serverless::Function", "Metadata": { - "Tool": "Amazon.Lambda.Annotations" + "Tool": "Amazon.Lambda.Annotations", + "SyncedEvents": [ + "RootPost" + ], + "SyncedEventProperties": { + "RootPost": [ + "Path", + "Method", + "ApiId.Ref" + ] + } }, "Properties": { "MemorySize": 512, @@ -1390,15 +1437,37 @@ "ImageUri": ".", "ImageConfig": { "Command": [ - "TestServerlessApp::TestServerlessApp.TaskExample_TaskReturn_Generated::TaskReturn" + "TestServerlessApp::TestServerlessApp.ComplexCalculator_Add_Generated::Add" ] + }, + "Events": { + "RootPost": { + "Type": "HttpApi", + "Properties": { + "Path": "/ComplexCalculator/Add", + "Method": "POST", + "ApiId": { + "Ref": "AnnotationsHttpApi" + } + } + } } } }, - "TestServerlessAppVoidExampleVoidReturnGenerated": { + "TestServerlessAppComplexCalculatorSubtractGenerated": { "Type": "AWS::Serverless::Function", "Metadata": { - "Tool": "Amazon.Lambda.Annotations" + "Tool": "Amazon.Lambda.Annotations", + "SyncedEvents": [ + "RootPost" + ], + "SyncedEventProperties": { + "RootPost": [ + "Path", + "Method", + "ApiId.Ref" + ] + } }, "Properties": { "MemorySize": 512, @@ -1410,22 +1479,35 @@ "ImageUri": ".", "ImageConfig": { "Command": [ - "TestServerlessApp::TestServerlessApp.VoidExample_VoidReturn_Generated::VoidReturn" + "TestServerlessApp::TestServerlessApp.ComplexCalculator_Subtract_Generated::Subtract" ] + }, + "Events": { + "RootPost": { + "Type": "HttpApi", + "Properties": { + "Path": "/ComplexCalculator/Subtract", + "Method": "POST", + "ApiId": { + "Ref": "AnnotationsHttpApi" + } + } + } } } }, - "SNSMessageHandler": { + "TestServerlessAppFromScratchNoApiGatewayEventsReferenceToUpperGenerated": { "Type": "AWS::Serverless::Function", "Metadata": { "Tool": "Amazon.Lambda.Annotations", "SyncedEvents": [ - "TestTopicEvent" + "RootGet" ], "SyncedEventProperties": { - "TestTopicEvent": [ - "Topic.Ref", - "FilterPolicy" + "RootGet": [ + "Path", + "Method", + "ApiId.Ref" ] } }, @@ -1436,22 +1518,23 @@ "AWSLambdaBasicExecutionRole" ], "PackageType": "Image", + "ImageUri": ".", + "ImageConfig": { + "Command": [ + "TestServerlessApp::TestServerlessApp.FromScratch.NoApiGatewayEventsReference_ToUpper_Generated::ToUpper" + ] + }, "Events": { - "TestTopicEvent": { - "Type": "SNS", + "RootGet": { + "Type": "HttpApi", "Properties": { - "FilterPolicy": "{ \"store\": [\"example_corp\"] }", - "Topic": { - "Ref": "TestTopic" + "Path": "/{text}", + "Method": "GET", + "ApiId": { + "Ref": "AnnotationsHttpApi" } } } - }, - "ImageUri": ".", - "ImageConfig": { - "Command": [ - "TestServerlessApp::TestServerlessApp.SnsMessageProcessing_HandleMessage_Generated::HandleMessage" - ] } } }, @@ -1463,6 +1546,27 @@ }, "TestTopic": { "Type": "AWS::SNS::Topic" + }, + "TestTable": { + "Properties": { + "StreamSpecification": { + "StreamViewType": "NEW_AND_OLD_IMAGES" + }, + "AttributeDefinitions": [ + { + "AttributeName": "Id", + "AttributeType": "S" + } + ], + "BillingMode": "PAY_PER_REQUEST", + "KeySchema": [ + { + "AttributeName": "Id", + "KeyType": "HASH" + } + ] + }, + "Type": "AWS::DynamoDB::Table" } } -} \ No newline at end of file +}