Skip to content

Commit f2c3c07

Browse files
authored
feat: Add support for SQS events in Amazon.Lambda.Annotations (#1758)
1 parent 3ef0bc9 commit f2c3c07

File tree

52 files changed

+2480
-306
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

52 files changed

+2480
-306
lines changed

Libraries/Amazon.Lambda.Annotations.slnf

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
"src\\Amazon.Lambda.Annotations\\Amazon.Lambda.Annotations.csproj",
88
"src\\Amazon.Lambda.Core\\Amazon.Lambda.Core.csproj",
99
"src\\Amazon.Lambda.RuntimeSupport\\Amazon.Lambda.RuntimeSupport.csproj",
10+
"src\\Amazon.Lambda.SQSEvents\\Amazon.Lambda.SQSEvents.csproj",
1011
"src\\Amazon.Lambda.Serialization.SystemTextJson\\Amazon.Lambda.Serialization.SystemTextJson.csproj",
1112
"test\\Amazon.Lambda.Annotations.SourceGenerators.Tests\\Amazon.Lambda.Annotations.SourceGenerators.Tests.csproj",
1213
"test\\TestExecutableServerlessApp\\TestExecutableServerlessApp.csproj",

Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Amazon.Lambda.Annotations.SourceGenerator.csproj

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,11 @@
44
<AssemblyVersion>1.4.0</AssemblyVersion>
55
<TargetFramework>netstandard2.0</TargetFramework>
66

7+
<!--This assembly needs to access internal methods inside the Amazon.Lambda.Annotations assembly.
8+
Both these assemblies need to be strongly signed for the InternalsVisibleTo attribute to take effect.-->
9+
<AssemblyOriginatorKeyFile>..\..\..\buildtools\public.snk</AssemblyOriginatorKeyFile>
10+
<SignAssembly>true</SignAssembly>
11+
712
<!-- This is required to allow copying all the dependencies to bin directory which can be copied after to nuget package based on nuspec -->
813
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
914
</PropertyGroup>

Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Diagnostics/AnalyzerReleases.Shipped.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,15 @@
11
; Shipped analyzer releases
22
; https://github.com/dotnet/roslyn-analyzers/blob/master/src/Microsoft.CodeAnalysis.Analyzers/ReleaseTrackingAnalyzers.Help.md
33

4+
## Release 1.5.0
5+
### New Rules
6+
7+
Rule ID | Category | Severity | Notes
8+
--------|----------|----------|-------
9+
AWSLambda0115 | AWSLambdaCSharpGenerator | Error | Invalid Usage of API Parameters
10+
AWSLambda0116 | AWSLambdaCSharpGenerator | Error | Invalid SQSEventAttribute encountered
11+
AWSLambda0117 | AWSLambdaCSharpGenerator | Error | Invalid Lambda Method Signature
12+
413
## Release 1.1.0
514
### New Rules
615

Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Diagnostics/DiagnosticDescriptors.cs

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ public static class DiagnosticDescriptors
103103
category: "AWSLambdaCSharpGenerator",
104104
DiagnosticSeverity.Error,
105105
isEnabledByDefault: true);
106-
106+
107107
public static readonly DiagnosticDescriptor ExecutableWithNoFunctions = new DiagnosticDescriptor(id: "AWSLambda0113",
108108
title: "Executable output with no LambdaFunction annotations",
109109
messageFormat: "Your project is configured to output an executable and generate a static Main method, but you have not configured any methods with the 'LambdaFunction' attribute",
@@ -117,5 +117,26 @@ public static class DiagnosticDescriptors
117117
category: "AWSLambdaCSharpGenerator",
118118
DiagnosticSeverity.Error,
119119
isEnabledByDefault: true);
120+
121+
public static readonly DiagnosticDescriptor ApiParametersOnNonApiFunction = new DiagnosticDescriptor(id: "AWSLambda0115",
122+
title: "Invalid Usage of API Parameters",
123+
messageFormat: "The Lambda function parameters are annotated with HTTP API attributes but the Lambda function itself is not annotated with an HTTP API attribute",
124+
category: "AWSLambdaCSharpGenerator",
125+
DiagnosticSeverity.Error,
126+
isEnabledByDefault: true);
127+
128+
public static readonly DiagnosticDescriptor InvalidSqsEventAttribute = new DiagnosticDescriptor(id: "AWSLambda0116",
129+
title: "Invalid SQSEventAttribute",
130+
messageFormat: "Invalid SQSEventAttribute encountered: {0}",
131+
category: "AWSLambdaCSharpGenerator",
132+
DiagnosticSeverity.Error,
133+
isEnabledByDefault: true);
134+
135+
public static readonly DiagnosticDescriptor InvalidLambdaMethodSignature = new DiagnosticDescriptor(id: "AWSLambda0117",
136+
title: "Invalid Lambda Method Signature",
137+
messageFormat: "Invalid Lambda method signature encountered: {0}",
138+
category: "AWSLambdaCSharpGenerator",
139+
DiagnosticSeverity.Error,
140+
isEnabledByDefault: true);
120141
}
121142
}

Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Generator.cs

Lines changed: 13 additions & 183 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,14 @@
22
using Amazon.Lambda.Annotations.SourceGenerator.Extensions;
33
using Amazon.Lambda.Annotations.SourceGenerator.FileIO;
44
using Amazon.Lambda.Annotations.SourceGenerator.Models;
5-
using Amazon.Lambda.Annotations.SourceGenerator.Models.Attributes;
65
using Amazon.Lambda.Annotations.SourceGenerator.Templates;
76
using Amazon.Lambda.Annotations.SourceGenerator.Writers;
87
using Microsoft.CodeAnalysis;
9-
using Microsoft.CodeAnalysis.CSharp.Syntax;
108
using Microsoft.CodeAnalysis.Text;
119
using System;
1210
using System.Collections.Generic;
13-
using System.Diagnostics;
1411
using System.Linq;
1512
using System.Text;
16-
using System.Text.RegularExpressions;
1713

1814
namespace Amazon.Lambda.Annotations.SourceGenerator
1915
{
@@ -41,12 +37,6 @@ public class Generator : ISourceGenerator
4137
"dotnet8"
4238
};
4339

44-
// Only allow alphanumeric characters
45-
private readonly Regex _resourceNameRegex = new Regex("^[a-zA-Z0-9]+$");
46-
47-
// Regex for the 'Name' property for API Gateway attributes - https://docs.aws.amazon.com/apigateway/latest/developerguide/request-response-data-mappings.html
48-
private readonly Regex _parameterAttributeNameRegex = new Regex("^[a-zA-Z0-9._$-]+$");
49-
5040
public Generator()
5141
{
5242
#if DEBUG
@@ -144,106 +134,46 @@ public void Execute(GeneratorExecutionContext context)
144134
}
145135
}
146136

147-
var configureMethodModel = semanticModelProvider.GetConfigureMethodModel(receiver.StartupClasses.FirstOrDefault());
137+
var configureMethodSymbol = semanticModelProvider.GetConfigureMethodModel(receiver.StartupClasses.FirstOrDefault());
148138

149139
var annotationReport = new AnnotationReport();
150140

151141
var templateHandler = new CloudFormationTemplateHandler(_fileManager, _directoryManager);
152142

153143
var lambdaModels = new List<LambdaFunctionModel>();
154144

155-
foreach (var lambdaMethod in receiver.LambdaMethods)
145+
foreach (var lambdaMethodDeclarationSyntax in receiver.LambdaMethods)
156146
{
157-
var lambdaMethodModel = semanticModelProvider.GetMethodSemanticModel(lambdaMethod);
147+
var lambdaMethodSymbol = semanticModelProvider.GetMethodSemanticModel(lambdaMethodDeclarationSyntax);
148+
var lambdaMethodLocation = lambdaMethodDeclarationSyntax.GetLocation();
158149

159-
if (!HasSerializerAttribute(context, lambdaMethodModel))
150+
var lambdaFunctionModel = LambdaFunctionModelBuilder.BuildAndValidate(lambdaMethodSymbol, lambdaMethodLocation, configureMethodSymbol, context, isExecutable, defaultRuntime, diagnosticReporter);
151+
if (!lambdaFunctionModel.IsValid)
160152
{
161-
diagnosticReporter.Report(Diagnostic.Create(DiagnosticDescriptors.MissingLambdaSerializer,
162-
lambdaMethod.GetLocation()));
163-
153+
// If the model is not valid then skip it from further processing
164154
foundFatalError = true;
165155
continue;
166156
}
167157

168-
// Check for necessary references
169-
if (lambdaMethodModel.HasAttribute(context, TypeFullNames.RestApiAttribute)
170-
|| lambdaMethodModel.HasAttribute(context, TypeFullNames.HttpApiAttribute))
171-
{
172-
// Check for arbitrary type from "Amazon.Lambda.APIGatewayEvents"
173-
if (context.Compilation.ReferencedAssemblyNames.FirstOrDefault(x => x.Name == "Amazon.Lambda.APIGatewayEvents") == null)
174-
{
175-
diagnosticReporter.Report(Diagnostic.Create(DiagnosticDescriptors.MissingDependencies,
176-
lambdaMethod.GetLocation(),
177-
"Amazon.Lambda.APIGatewayEvents"));
178-
179-
foundFatalError = true;
180-
continue;
181-
}
182-
}
183-
184-
var serializerInfo = GetSerializerInfoAttribute(context, lambdaMethodModel);
185-
186-
var model = LambdaFunctionModelBuilder.Build(lambdaMethodModel, configureMethodModel, context, isExecutable, serializerInfo, defaultRuntime);
187-
188-
// If there are more than one event, report them as errors
189-
if (model.LambdaMethod.Events.Count > 1)
190-
{
191-
foreach (var attribute in lambdaMethodModel.GetAttributes().Where(attribute => TypeFullNames.Events.Contains(attribute.AttributeClass.ToDisplayString())))
192-
{
193-
diagnosticReporter.Report(Diagnostic.Create(DiagnosticDescriptors.MultipleEventsNotSupported,
194-
Location.Create(attribute.ApplicationSyntaxReference.SyntaxTree, attribute.ApplicationSyntaxReference.Span),
195-
DiagnosticSeverity.Error));
196-
}
197-
198-
foundFatalError = true;
199-
// Skip multi-event lambda method from processing and check remaining lambda methods for diagnostics
200-
continue;
201-
}
202-
if(model.LambdaMethod.ReturnsIHttpResults && !model.LambdaMethod.Events.Contains(EventType.API))
203-
{
204-
diagnosticReporter.Report(Diagnostic.Create(DiagnosticDescriptors.HttpResultsOnNonApiFunction,
205-
Location.Create(lambdaMethod.SyntaxTree, lambdaMethod.Span),
206-
DiagnosticSeverity.Error));
207-
208-
foundFatalError = true;
209-
continue;
210-
}
211-
212-
if (!_resourceNameRegex.IsMatch(model.ResourceName))
213-
{
214-
diagnosticReporter.Report(Diagnostic.Create(DiagnosticDescriptors.InvalidResourceName,
215-
Location.Create(lambdaMethod.SyntaxTree, lambdaMethod.Span),
216-
DiagnosticSeverity.Error));
217-
218-
foundFatalError = true;
219-
continue;
220-
}
221-
222-
if (!AreLambdaMethodParametersValid(lambdaMethod, model, diagnosticReporter))
223-
{
224-
foundFatalError = true;
225-
continue;
226-
}
227-
228-
var template = new LambdaFunctionTemplate(model);
158+
var template = new LambdaFunctionTemplate(lambdaFunctionModel);
229159

230160
string sourceText;
231161
try
232162
{
233163
sourceText = template.TransformText().ToEnvironmentLineEndings();
234-
context.AddSource($"{model.GeneratedMethod.ContainingType.Name}.g.cs", SourceText.From(sourceText, Encoding.UTF8, SourceHashAlgorithm.Sha256));
164+
context.AddSource($"{lambdaFunctionModel.GeneratedMethod.ContainingType.Name}.g.cs", SourceText.From(sourceText, Encoding.UTF8, SourceHashAlgorithm.Sha256));
235165
}
236166
catch (Exception e) when (e is NotSupportedException || e is InvalidOperationException)
237167
{
238-
diagnosticReporter.Report(Diagnostic.Create(DiagnosticDescriptors.CodeGenerationFailed, Location.Create(lambdaMethod.SyntaxTree, lambdaMethod.Span), e.Message));
168+
diagnosticReporter.Report(Diagnostic.Create(DiagnosticDescriptors.CodeGenerationFailed, Location.Create(lambdaMethodDeclarationSyntax.SyntaxTree, lambdaMethodDeclarationSyntax.Span), e.Message));
239169
return;
240170
}
241171

242172
// report every generated file to build output
243-
diagnosticReporter.Report(Diagnostic.Create(DiagnosticDescriptors.CodeGeneration, Location.None, $"{model.GeneratedMethod.ContainingType.Name}.g.cs", sourceText));
173+
diagnosticReporter.Report(Diagnostic.Create(DiagnosticDescriptors.CodeGeneration, Location.None, $"{lambdaFunctionModel.GeneratedMethod.ContainingType.Name}.g.cs", sourceText));
244174

245-
lambdaModels.Add(model);
246-
annotationReport.LambdaFunctions.Add(model);
175+
lambdaModels.Add(lambdaFunctionModel);
176+
annotationReport.LambdaFunctions.Add(lambdaFunctionModel);
247177
}
248178

249179
if (isExecutable)
@@ -348,110 +278,10 @@ private static ExecutableAssembly GenerateExecutableAssemblySource(
348278
lambdaModels[0].LambdaMethod.ContainingNamespace);
349279
}
350280

351-
private bool HasSerializerAttribute(GeneratorExecutionContext context, IMethodSymbol methodModel)
352-
{
353-
return methodModel.ContainingAssembly.HasAttribute(context, TypeFullNames.LambdaSerializerAttribute);
354-
}
355-
356-
private LambdaSerializerInfo GetSerializerInfoAttribute(GeneratorExecutionContext context, IMethodSymbol methodModel)
357-
{
358-
var serializerString = DEFAULT_LAMBDA_SERIALIZER;
359-
360-
ISymbol symbol = null;
361-
362-
// First check if method has the Lambda Serializer.
363-
if (methodModel.HasAttribute(
364-
context,
365-
TypeFullNames.LambdaSerializerAttribute))
366-
{
367-
symbol = methodModel;
368-
}
369-
// Then check assembly
370-
else if (methodModel.ContainingAssembly.HasAttribute(
371-
context,
372-
TypeFullNames.LambdaSerializerAttribute))
373-
{
374-
symbol = methodModel.ContainingAssembly;
375-
}
376-
// Else return the default serializer.
377-
else
378-
{
379-
return new LambdaSerializerInfo(serializerString);
380-
}
381-
382-
var attribute = symbol.GetAttributes().FirstOrDefault(attr => attr.AttributeClass.Name == TypeFullNames.LambdaSerializerAttributeWithoutNamespace);
383-
384-
var serializerValue = attribute.ConstructorArguments.FirstOrDefault(kvp => kvp.Type.Name == nameof(Type)).Value;
385-
386-
if (serializerValue != null)
387-
{
388-
serializerString = serializerValue.ToString();
389-
}
390-
391-
return new LambdaSerializerInfo(serializerString);
392-
}
393-
394281
public void Initialize(GeneratorInitializationContext context)
395282
{
396283
// Register a syntax receiver that will be created for each generation pass
397284
context.RegisterForSyntaxNotifications(() => new SyntaxReceiver(_fileManager, _directoryManager));
398285
}
399-
400-
private bool AreLambdaMethodParametersValid(MethodDeclarationSyntax declarationSyntax, LambdaFunctionModel model, DiagnosticReporter diagnosticReporter)
401-
{
402-
var isValid = true;
403-
foreach (var parameter in model.LambdaMethod.Parameters)
404-
{
405-
if (parameter.Attributes.Any(att => att.Type.FullName == TypeFullNames.FromQueryAttribute))
406-
{
407-
var fromQueryAttribute = parameter.Attributes.First(att => att.Type.FullName == TypeFullNames.FromQueryAttribute) as AttributeModel<APIGateway.FromQueryAttribute>;
408-
// Use parameter name as key, if Name has not specified explicitly in the attribute definition.
409-
var parameterKey = fromQueryAttribute?.Data?.Name ?? parameter.Name;
410-
411-
if (!parameter.Type.IsPrimitiveType() && !parameter.Type.IsPrimitiveEnumerableType())
412-
{
413-
isValid = false;
414-
diagnosticReporter.Report(Diagnostic.Create(DiagnosticDescriptors.UnsupportedMethodParameterType,
415-
Location.Create(declarationSyntax.SyntaxTree, declarationSyntax.Span),
416-
parameterKey, parameter.Type.FullName));
417-
}
418-
}
419-
420-
foreach (var att in parameter.Attributes)
421-
{
422-
var parameterAttributeName = string.Empty;
423-
switch (att.Type.FullName)
424-
{
425-
case TypeFullNames.FromQueryAttribute:
426-
var fromQueryAttribute = (AttributeModel<APIGateway.FromQueryAttribute>)att;
427-
parameterAttributeName = fromQueryAttribute.Data.Name;
428-
break;
429-
430-
case TypeFullNames.FromRouteAttribute:
431-
var fromRouteAttribute = (AttributeModel<APIGateway.FromRouteAttribute>)att;
432-
parameterAttributeName = fromRouteAttribute.Data.Name;
433-
break;
434-
435-
case TypeFullNames.FromHeaderAttribute:
436-
var fromHeaderAttribute = (AttributeModel<APIGateway.FromHeaderAttribute>)att;
437-
parameterAttributeName = fromHeaderAttribute.Data.Name;
438-
break;
439-
440-
default:
441-
break;
442-
}
443-
444-
if (!string.IsNullOrEmpty(parameterAttributeName) && !_parameterAttributeNameRegex.IsMatch(parameterAttributeName))
445-
{
446-
isValid = false;
447-
diagnosticReporter.Report(Diagnostic.Create(DiagnosticDescriptors.InvalidParameterAttributeName,
448-
Location.Create(declarationSyntax.SyntaxTree, declarationSyntax.Span),
449-
parameterAttributeName, parameter.Name));
450-
}
451-
}
452-
}
453-
454-
return isValid;
455-
}
456286
}
457287
}

Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Models/Attributes/AttributeModelBuilder.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System;
22
using Amazon.Lambda.Annotations.APIGateway;
3+
using Amazon.Lambda.Annotations.SQS;
34
using Microsoft.CodeAnalysis;
45

56
namespace Amazon.Lambda.Annotations.SourceGenerator.Models.Attributes
@@ -71,6 +72,15 @@ public static AttributeModel Build(AttributeData att, GeneratorExecutionContext
7172
Type = TypeModelBuilder.Build(att.AttributeClass, context)
7273
};
7374
}
75+
else if (att.AttributeClass.Equals(context.Compilation.GetTypeByMetadataName(TypeFullNames.SQSEventAttribute), SymbolEqualityComparer.Default))
76+
{
77+
var data = SQSEventAttributeBuilder.Build(att);
78+
model = new AttributeModel<SQSEventAttribute>
79+
{
80+
Data = data,
81+
Type = TypeModelBuilder.Build(att.AttributeClass, context)
82+
};
83+
}
7484
else
7585
{
7686
model = new AttributeModel

0 commit comments

Comments
 (0)