From dff351c8a93d015c1b1cd0d4bc93ba3c46ab099e Mon Sep 17 00:00:00 2001 From: Malhar Khimsaria <96malhar@gmail.com> Date: Thu, 19 Sep 2024 13:04:35 -0700 Subject: [PATCH] fix: Impose a 127 character limit on the Lambda function handler when the package type is set to zip Addresses https://github.com/aws/aws-lambda-dotnet/issues/1642 --- .../Diagnostics/AnalyzerReleases.Shipped.md | 7 ++++++ .../Diagnostics/DiagnosticDescriptors.cs | 7 ++++++ .../Validation/LambdaFunctionValidator.cs | 9 +++++++ .../ServerlessTemplates/sqsEvents.template | 22 ++++++++++------ .../SourceGeneratorTests.cs | 25 +++++++++++++++++++ .../ExceededMaximumHandlerLength.cs.error | 15 +++++++++++ .../InvalidSQSEvents.cs.error | 20 +++++++-------- .../SQSEventExamples/ValidSQSEvents.cs.txt | 4 +-- .../TestServerlessApp/serverless.template | 12 ++++----- 9 files changed, 95 insertions(+), 26 deletions(-) create mode 100644 Libraries/test/TestServerlessApp/ExceededMaximumHandlerLength.cs.error diff --git a/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Diagnostics/AnalyzerReleases.Shipped.md b/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Diagnostics/AnalyzerReleases.Shipped.md index f664c3c35..5e940e829 100644 --- a/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Diagnostics/AnalyzerReleases.Shipped.md +++ b/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Diagnostics/AnalyzerReleases.Shipped.md @@ -1,6 +1,13 @@ ; Shipped analyzer releases ; https://github.com/dotnet/roslyn-analyzers/blob/master/src/Microsoft.CodeAnalysis.Analyzers/ReleaseTrackingAnalyzers.Help.md +## Release 1.5.1 +### New Rules + +Rule ID | Category | Severity | Notes +--------|----------|----------|------- +AWSLambda0118 | AWSLambdaCSharpGenerator | Error | Maximum Handler Length Exceeded + ## Release 1.5.0 ### New Rules diff --git a/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Diagnostics/DiagnosticDescriptors.cs b/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Diagnostics/DiagnosticDescriptors.cs index fdf0813ec..b179fb02f 100644 --- a/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Diagnostics/DiagnosticDescriptors.cs +++ b/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Diagnostics/DiagnosticDescriptors.cs @@ -138,5 +138,12 @@ public static class DiagnosticDescriptors category: "AWSLambdaCSharpGenerator", DiagnosticSeverity.Error, isEnabledByDefault: true); + + public static readonly DiagnosticDescriptor MaximumHandlerLengthExceeded = new DiagnosticDescriptor(id: "AWSLambda0118", + title: "Maximum Handler Length Exceeded", + messageFormat: "The handler string '{0}' exceeds the maximum length of 127 characters. Please trim down your project namespace to stay within the character limit. Alternatively, you can switch to an image based deployment by setting the 'PackageType' to 'Image' to avoid the handler length restriction.", + category: "AWSLambdaCSharpGenerator", + DiagnosticSeverity.Error, + isEnabledByDefault: true); } } \ No newline at end of file diff --git a/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Validation/LambdaFunctionValidator.cs b/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Validation/LambdaFunctionValidator.cs index f8e31c0b0..8b305e7cf 100644 --- a/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Validation/LambdaFunctionValidator.cs +++ b/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Validation/LambdaFunctionValidator.cs @@ -28,6 +28,15 @@ internal static bool ValidateFunction(GeneratorExecutionContext context, IMethod diagnostics.Add(Diagnostic.Create(DiagnosticDescriptors.InvalidResourceName, methodLocation)); } + // Check the handler length does not exceed 127 characters when the package type is set to zip + // The official AWS docs state a 128 character limit on the Lambda handler. However, there is an open issue where the last character is stripped off + // when the handler is exactly 128 characters long. Hence, we are enforcing a 127 character limit. + // https://github.com/aws/aws-lambda-dotnet/issues/1642 + if (lambdaFunctionModel.PackageType == LambdaPackageType.Zip && lambdaFunctionModel.Handler.Length > 127) + { + diagnostics.Add(Diagnostic.Create(DiagnosticDescriptors.MaximumHandlerLengthExceeded, methodLocation, lambdaFunctionModel.Handler)); + } + // Check for Serializer attribute if (!lambdaMethodSymbol.ContainingAssembly.HasAttribute(context, TypeFullNames.LambdaSerializerAttribute)) { diff --git a/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/ServerlessTemplates/sqsEvents.template b/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/ServerlessTemplates/sqsEvents.template index 7d7f0591a..13a56ed27 100644 --- a/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/ServerlessTemplates/sqsEvents.template +++ b/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/ServerlessTemplates/sqsEvents.template @@ -35,15 +35,18 @@ } }, "Properties": { - "Runtime": "dotnet6", - "CodeUri": ".", "MemorySize": 512, "Timeout": 30, "Policies": [ "AWSLambdaBasicExecutionRole" ], - "PackageType": "Zip", - "Handler": "TestProject::TestServerlessApp.SQSEventExamples.ValidSQSEvents_ProcessMessages_Generated::ProcessMessages", + "PackageType": "Image", + "ImageUri": ".", + "ImageConfig": { + "Command": [ + "TestProject::TestServerlessApp.SQSEventExamples.ValidSQSEvents_ProcessMessages_Generated::ProcessMessages" + ] + }, "Events": { "queue1": { "Type": "SQS", @@ -109,15 +112,18 @@ } }, "Properties": { - "Runtime": "dotnet6", - "CodeUri": ".", "MemorySize": 512, "Timeout": 30, "Policies": [ "AWSLambdaBasicExecutionRole" ], - "PackageType": "Zip", - "Handler": "TestProject::TestServerlessApp.SQSEventExamples.ValidSQSEvents_ProcessMessagesWithBatchFailureReporting_Generated::ProcessMessagesWithBatchFailureReporting", + "PackageType": "Image", + "ImageUri": ".", + "ImageConfig": { + "Command": [ + "TestProject::TestServerlessApp.SQSEventExamples.ValidSQSEvents_ProcessMessagesWithBatchFailureReporting_Generated::ProcessMessagesWithBatchFailureReporting" + ] + }, "Events": { "queue3": { "Type": "SQS", diff --git a/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/SourceGeneratorTests.cs b/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/SourceGeneratorTests.cs index bcde6c26e..5940acf89 100644 --- a/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/SourceGeneratorTests.cs +++ b/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/SourceGeneratorTests.cs @@ -1330,6 +1330,31 @@ public async Task VerifyValidSQSEvents() }.RunAsync(); } + [Fact] + public async Task ExceededMaximumHandlerLength() + { + await new VerifyCS.Test + { + TestState = + { + Sources = + { + (Path.Combine("TestServerlessApp", "PlaceholderClass.cs"), await File.ReadAllTextAsync(Path.Combine("TestServerlessApp", "PlaceholderClass.cs"))), + (Path.Combine("TestServerlessApp", "ExceededMaximumHandlerLength.cs"), await File.ReadAllTextAsync(Path.Combine("TestServerlessApp", "ExceededMaximumHandlerLength.cs.error"))), + (Path.Combine("Amazon.Lambda.Annotations", "LambdaFunctionAttribute.cs"), await File.ReadAllTextAsync(Path.Combine("Amazon.Lambda.Annotations", "LambdaFunctionAttribute.cs"))), + (Path.Combine("TestServerlessApp", "AssemblyAttributes.cs"), await File.ReadAllTextAsync(Path.Combine("TestServerlessApp", "AssemblyAttributes.cs"))), + }, + ExpectedDiagnostics = + { + DiagnosticResult + .CompilerError("AWSLambda0118") + .WithSpan($"TestServerlessApp{Path.DirectorySeparatorChar}ExceededMaximumHandlerLength.cs", 9, 9, 13, 10) + .WithArguments("TestProject::TestServerlessApp.ExceededMaximumHandlerLength_SayHelloXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX_Generated::SayHelloXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX") + }, + } + }.RunAsync(); + } + public void Dispose() { File.Delete(Path.Combine("TestServerlessApp", "serverless.template")); diff --git a/Libraries/test/TestServerlessApp/ExceededMaximumHandlerLength.cs.error b/Libraries/test/TestServerlessApp/ExceededMaximumHandlerLength.cs.error new file mode 100644 index 000000000..706041dde --- /dev/null +++ b/Libraries/test/TestServerlessApp/ExceededMaximumHandlerLength.cs.error @@ -0,0 +1,15 @@ +using System; +using Amazon.Lambda.Annotations; + +namespace TestServerlessApp +{ + public class ExceededMaximumHandlerLength + { + // This fails because generated handler is longer than 127 characters + [LambdaFunction] + public string SayHelloXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX() + { + return "Hello, World!"; + } + } +} \ No newline at end of file diff --git a/Libraries/test/TestServerlessApp/SQSEventExamples/InvalidSQSEvents.cs.error b/Libraries/test/TestServerlessApp/SQSEventExamples/InvalidSQSEvents.cs.error index 0b90371a7..1603f121d 100644 --- a/Libraries/test/TestServerlessApp/SQSEventExamples/InvalidSQSEvents.cs.error +++ b/Libraries/test/TestServerlessApp/SQSEventExamples/InvalidSQSEvents.cs.error @@ -12,21 +12,21 @@ namespace TestServerlessApp.SQSEventExamples public class InvalidSQSEvents { - [LambdaFunction] + [LambdaFunction(PackageType = LambdaPackageType.Image)] [SQSEvent("@testQueue", BatchSize = 0, MaximumBatchingWindowInSeconds = 302, MaximumConcurrency = 1)] public void ProcessMessageWithInvalidSQSEventAttributes(SQSEvent evnt) { Console.WriteLine($"Event processed: {evnt}"); } - [LambdaFunction] + [LambdaFunction(PackageType = LambdaPackageType.Image)] [SQSEvent("@testQueue")] public void ProcessMessageWithInvalidParameters(SQSEvent evnt, bool invalidParameter1, int invalidParameter2) { Console.WriteLine($"Event processed: {evnt}"); } - [LambdaFunction] + [LambdaFunction(PackageType = LambdaPackageType.Image)] [SQSEvent("@testQueue")] public bool ProcessMessageWithInvalidReturnType(SQSEvent evnt) { @@ -34,7 +34,7 @@ namespace TestServerlessApp.SQSEventExamples return true; } - [LambdaFunction] + [LambdaFunction(PackageType = LambdaPackageType.Image)] [RestApi(LambdaHttpMethod.Get, "/")] [SQSEvent("@testQueue")] public void ProcessMessageWithMultipleEventTypes(SQSEvent evnt) @@ -42,42 +42,42 @@ namespace TestServerlessApp.SQSEventExamples Console.WriteLine($"Event processed: {evnt}"); } - [LambdaFunction] + [LambdaFunction(PackageType = LambdaPackageType.Image)] [SQSEvent("test-queue")] public void ProcessMessageWithInvalidQueueArn(SQSEvent evnt) { Console.WriteLine($"Event processed: {evnt}"); } - [LambdaFunction] + [LambdaFunction(PackageType = LambdaPackageType.Image)] [SQSEvent("@testQueue", ResourceName = "sqs-event-source")] public void ProcessMessageWithInvalidResourceName(SQSEvent evnt) { Console.WriteLine($"Event processed: {evnt}"); } - [LambdaFunction] + [LambdaFunction(PackageType = LambdaPackageType.Image)] [SQSEvent("@testQueue", ResourceName = "")] public void ProcessMessageWithEmptyResourceName(SQSEvent evnt) { Console.WriteLine($"Event processed: {evnt}"); } - [LambdaFunction] + [LambdaFunction(PackageType = LambdaPackageType.Image)] [SQSEvent("@testQueue", BatchSize = 100)] public void ProcessMessageWithMaximumBatchingWindowInSecondsNotSpecified(SQSEvent evnt) { Console.WriteLine($"Event processed: {evnt}"); } - [LambdaFunction] + [LambdaFunction(PackageType = LambdaPackageType.Image)] [SQSEvent("@testQueue", BatchSize = 100, MaximumBatchingWindowInSeconds = 0)] public void ProcessMessageWithMaximumBatchingWindowInSecondsLessThanOne(SQSEvent evnt) { Console.WriteLine($"Event processed: {evnt}"); } - [LambdaFunction] + [LambdaFunction(PackageType = LambdaPackageType.Image)] [SQSEvent("arn:aws:sqs:us-east-2:444455556666:test-queue.fifo", BatchSize = 100, MaximumBatchingWindowInSeconds = 5)] public void ProcessMessageWithFifoQueue(SQSEvent evnt) { diff --git a/Libraries/test/TestServerlessApp/SQSEventExamples/ValidSQSEvents.cs.txt b/Libraries/test/TestServerlessApp/SQSEventExamples/ValidSQSEvents.cs.txt index 28327c33f..791a6614a 100644 --- a/Libraries/test/TestServerlessApp/SQSEventExamples/ValidSQSEvents.cs.txt +++ b/Libraries/test/TestServerlessApp/SQSEventExamples/ValidSQSEvents.cs.txt @@ -12,7 +12,7 @@ namespace TestServerlessApp.SQSEventExamples public class ValidSQSEvents { - [LambdaFunction] + [LambdaFunction(PackageType = LambdaPackageType.Image)] [SQSEvent("arn:aws:sqs:us-east-2:444455556666:queue1", BatchSize = 50, MaximumBatchingWindowInSeconds = 2, MaximumConcurrency = 30, Filters = "My-Filter-1; My-Filter-2")] [SQSEvent("arn:aws:sqs:us-east-2:444455556666:queue2", MaximumBatchingWindowInSeconds = 5, Enabled = false)] [SQSEvent("arn:aws:sqs:us-east-2:444455556666:my-queue")] @@ -22,7 +22,7 @@ namespace TestServerlessApp.SQSEventExamples Console.WriteLine($"Event processed: {evnt}"); } - [LambdaFunction] + [LambdaFunction(PackageType = LambdaPackageType.Image)] [SQSEvent("arn:aws:sqs:us-east-2:444455556666:queue3")] public async Task ProcessMessagesWithBatchFailureReporting(SQSEvent evnt) { diff --git a/Libraries/test/TestServerlessApp/serverless.template b/Libraries/test/TestServerlessApp/serverless.template index 969ae9c9f..8f938cfe1 100644 --- a/Libraries/test/TestServerlessApp/serverless.template +++ b/Libraries/test/TestServerlessApp/serverless.template @@ -913,12 +913,6 @@ "TestQueueEvent": { "Type": "SQS", "Properties": { - "Queue": { - "Fn::GetAtt": [ - "TestQueue", - "Arn" - ] - }, "BatchSize": 50, "FilterCriteria": { "Filters": [ @@ -933,6 +927,12 @@ "MaximumBatchingWindowInSeconds": 5, "ScalingConfig": { "MaximumConcurrency": 5 + }, + "Queue": { + "Fn::GetAtt": [ + "TestQueue", + "Arn" + ] } } }