Skip to content

Commit 249ce0f

Browse files
Add [SNSEvent] annotation attribute and source generator support (#2322)
* Add CODEOWNERS file * Add [SNSEvent] annotation attribute and source generator support - SNSEventAttribute with Topic, ResourceName, FilterPolicy, Enabled - SNSEventAttributeBuilder for Roslyn AttributeData parsing - Source generator wiring (TypeFullNames, SyntaxReceiver, EventTypeBuilder, AttributeModelBuilder) - CloudFormationWriter ProcessSNSAttribute (SAM SNS event subscription) - LambdaFunctionValidator ValidateSNSEvents - DiagnosticDescriptors InvalidSNSEventAttribute - SNSEventAttributeTests (attribute unit tests) - SNSEventsTests (CloudFormation writer tests) - E2E source generator snapshot tests - Integration test (SNSEventSubscription) - Sample function (SnsMessageProcessing) - .autover change file - README documentation pr comments fix tests fix tests fix tests * add header --------- Co-authored-by: AlexDaines <55813219+AlexDaines@users.noreply.github.com>
1 parent d7b8cb5 commit 249ce0f

32 files changed

Lines changed: 1200 additions & 15 deletions
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"Projects": [
3+
{
4+
"Name": "Amazon.Lambda.Annotations",
5+
"Type": "Minor",
6+
"ChangelogMessages": [
7+
"Added [SNSEvent] annotation attribute for declaratively configuring SNS topic-triggered Lambda functions with support for topic reference, filter policy, and enabled state."
8+
]
9+
}
10+
]
11+
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,4 @@ AWSLambda0133 | AWSLambdaCSharpGenerator | Error | ALB Listener Reference Not Fo
2121
AWSLambda0134 | AWSLambdaCSharpGenerator | Error | FromRoute not supported on ALB functions
2222
AWSLambda0135 | AWSLambdaCSharpGenerator | Error | Unmapped parameter on ALB function
2323
AWSLambda0136 | AWSLambdaCSharpGenerator | Error | Invalid S3EventAttribute
24+
AWSLambda0138 | AWSLambdaCSharpGenerator | Error | Invalid SNSEventAttribute

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -281,5 +281,12 @@ public static class DiagnosticDescriptors
281281
category: "AWSLambdaCSharpGenerator",
282282
DiagnosticSeverity.Error,
283283
isEnabledByDefault: true);
284+
285+
public static readonly DiagnosticDescriptor InvalidSnsEventAttribute = new DiagnosticDescriptor(id: "AWSLambda0138",
286+
title: "Invalid SNSEventAttribute",
287+
messageFormat: "Invalid SNSEventAttribute encountered: {0}",
288+
category: "AWSLambdaCSharpGenerator",
289+
DiagnosticSeverity.Error,
290+
isEnabledByDefault: true);
284291
}
285292
}

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,15 @@ public static AttributeModel Build(AttributeData att, GeneratorExecutionContext
113113
Type = TypeModelBuilder.Build(att.AttributeClass, context)
114114
};
115115
}
116+
else if (att.AttributeClass.Equals(context.Compilation.GetTypeByMetadataName(TypeFullNames.SNSEventAttribute), SymbolEqualityComparer.Default))
117+
{
118+
var data = SNSEventAttributeBuilder.Build(att);
119+
model = new AttributeModel<SNS.SNSEventAttribute>
120+
{
121+
Data = data,
122+
Type = TypeModelBuilder.Build(att.AttributeClass, context)
123+
};
124+
}
116125
else if (att.AttributeClass.Equals(context.Compilation.GetTypeByMetadataName(TypeFullNames.HttpApiAuthorizerAttribute), SymbolEqualityComparer.Default))
117126
{
118127
var data = HttpApiAuthorizerAttributeBuilder.Build(att);
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
using Amazon.Lambda.Annotations.SNS;
5+
using Microsoft.CodeAnalysis;
6+
using System;
7+
8+
namespace Amazon.Lambda.Annotations.SourceGenerator.Models.Attributes
9+
{
10+
/// <summary>
11+
/// Builder for <see cref="SNSEventAttribute"/>.
12+
/// </summary>
13+
public class SNSEventAttributeBuilder
14+
{
15+
public static SNSEventAttribute Build(AttributeData att)
16+
{
17+
if (att.ConstructorArguments.Length != 1)
18+
{
19+
throw new NotSupportedException($"{TypeFullNames.SNSEventAttribute} must have constructor with 1 argument.");
20+
}
21+
var topic = att.ConstructorArguments[0].Value as string;
22+
var data = new SNSEventAttribute(topic);
23+
24+
foreach (var pair in att.NamedArguments)
25+
{
26+
if (pair.Key == nameof(data.ResourceName) && pair.Value.Value is string resourceName)
27+
{
28+
data.ResourceName = resourceName;
29+
}
30+
else if (pair.Key == nameof(data.FilterPolicy) && pair.Value.Value is string filterPolicy)
31+
{
32+
data.FilterPolicy = filterPolicy;
33+
}
34+
else if (pair.Key == nameof(data.Enabled) && pair.Value.Value is bool enabled)
35+
{
36+
data.Enabled = enabled;
37+
}
38+
}
39+
40+
return data;
41+
}
42+
}
43+
}

Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Models/EventType.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ public enum EventType
99
API,
1010
S3,
1111
SQS,
12+
SNS,
1213
DynamoDB,
1314
Schedule,
1415
Authorizer,

Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Models/EventTypeBuilder.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,10 @@ public static HashSet<EventType> Build(IMethodSymbol lambdaMethodSymbol,
3434
{
3535
events.Add(EventType.S3);
3636
}
37+
else if (attribute.AttributeClass.ToDisplayString() == TypeFullNames.SNSEventAttribute)
38+
{
39+
events.Add(EventType.SNS);
40+
}
3741
else if (attribute.AttributeClass.ToDisplayString() == TypeFullNames.HttpApiAuthorizerAttribute
3842
|| attribute.AttributeClass.ToDisplayString() == TypeFullNames.RestApiAuthorizerAttribute)
3943
{

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@ internal class SyntaxReceiver : ISyntaxContextReceiver
2727
{ "FunctionUrlAttribute", "FunctionUrl" },
2828
{ "SQSEventAttribute", "SQSEvent" },
2929
{ "ALBApiAttribute", "ALBApi" },
30-
{ "S3EventAttribute", "S3Event" }
30+
{ "S3EventAttribute", "S3Event" },
31+
{ "SNSEventAttribute", "SNSEvent" }
3132
};
3233

3334
public List<MethodDeclarationSyntax> LambdaMethods { get; } = new List<MethodDeclarationSyntax>();

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,9 @@ public static class TypeFullNames
6262
public const string S3Event = "Amazon.Lambda.S3Events.S3Event";
6363
public const string S3EventAttribute = "Amazon.Lambda.Annotations.S3.S3EventAttribute";
6464

65+
public const string SNSEvent = "Amazon.Lambda.SNSEvents.SNSEvent";
66+
public const string SNSEventAttribute = "Amazon.Lambda.Annotations.SNS.SNSEventAttribute";
67+
6568
public const string LambdaSerializerAttribute = "Amazon.Lambda.Core.LambdaSerializerAttribute";
6669
public const string DefaultLambdaSerializer = "Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer";
6770

@@ -91,7 +94,8 @@ public static class TypeFullNames
9194
FunctionUrlAttribute,
9295
SQSEventAttribute,
9396
ALBApiAttribute,
94-
S3EventAttribute
97+
S3EventAttribute,
98+
SNSEventAttribute
9599
};
96100
}
97101
}

Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Validation/LambdaFunctionValidator.cs

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
using Amazon.Lambda.Annotations.SourceGenerator.Extensions;
88
using Amazon.Lambda.Annotations.SourceGenerator.Models;
99
using Amazon.Lambda.Annotations.SourceGenerator.Models.Attributes;
10+
using Amazon.Lambda.Annotations.SNS;
1011
using Amazon.Lambda.Annotations.SQS;
1112
using Microsoft.CodeAnalysis;
1213
using System.Collections.Generic;
@@ -64,6 +65,7 @@ internal static bool ValidateFunction(GeneratorExecutionContext context, IMethod
6465
// Validate Events
6566
ValidateApiGatewayEvents(lambdaFunctionModel, methodLocation, diagnostics);
6667
ValidateSqsEvents(lambdaFunctionModel, methodLocation, diagnostics);
68+
ValidateSnsEvents(lambdaFunctionModel, methodLocation, diagnostics);
6769
ValidateAlbEvents(lambdaFunctionModel, methodLocation, diagnostics);
6870
ValidateS3Events(lambdaFunctionModel, methodLocation, diagnostics);
6971

@@ -114,6 +116,16 @@ internal static bool ValidateDependencies(GeneratorExecutionContext context, IMe
114116
}
115117
}
116118

119+
// Check for references to "Amazon.Lambda.SNSEvents" if the Lambda method is annotated with SNSEvent attribute.
120+
if (lambdaMethodSymbol.HasAttribute(context, TypeFullNames.SNSEventAttribute))
121+
{
122+
if (context.Compilation.ReferencedAssemblyNames.FirstOrDefault(x => x.Name == "Amazon.Lambda.SNSEvents") == null)
123+
{
124+
diagnosticReporter.Report(Diagnostic.Create(DiagnosticDescriptors.MissingDependencies, methodLocation, "Amazon.Lambda.SNSEvents"));
125+
return false;
126+
}
127+
}
128+
117129
return true;
118130
}
119131

@@ -424,6 +436,45 @@ private static void ValidateS3Events(LambdaFunctionModel lambdaFunctionModel, Lo
424436
}
425437
}
426438

439+
private static void ValidateSnsEvents(LambdaFunctionModel lambdaFunctionModel, Location methodLocation, List<Diagnostic> diagnostics)
440+
{
441+
if (!lambdaFunctionModel.LambdaMethod.Events.Contains(EventType.SNS))
442+
{
443+
return;
444+
}
445+
446+
// Validate SNSEventAttributes
447+
foreach (var att in lambdaFunctionModel.Attributes)
448+
{
449+
if (att.Type.FullName != TypeFullNames.SNSEventAttribute)
450+
continue;
451+
452+
var snsEventAttribute = ((AttributeModel<SNSEventAttribute>)att).Data;
453+
var validationErrors = snsEventAttribute.Validate();
454+
validationErrors.ForEach(errorMessage => diagnostics.Add(Diagnostic.Create(DiagnosticDescriptors.InvalidSnsEventAttribute, methodLocation, errorMessage)));
455+
}
456+
457+
// Validate method parameters - When using SNSEventAttribute, the method signature must be (SNSEvent snsEvent) or (SNSEvent snsEvent, ILambdaContext context)
458+
var parameters = lambdaFunctionModel.LambdaMethod.Parameters;
459+
if (parameters.Count == 0 ||
460+
parameters.Count > 2 ||
461+
(parameters.Count == 1 && parameters[0].Type.FullName != TypeFullNames.SNSEvent) ||
462+
(parameters.Count == 2 && (parameters[0].Type.FullName != TypeFullNames.SNSEvent || parameters[1].Type.FullName != TypeFullNames.ILambdaContext)))
463+
{
464+
var errorMessage = $"When using the {nameof(SNSEventAttribute)}, the Lambda method can accept at most 2 parameters. " +
465+
$"The first parameter is required and must be of type {TypeFullNames.SNSEvent}. " +
466+
$"The second parameter is optional and must be of type {TypeFullNames.ILambdaContext}.";
467+
diagnostics.Add(Diagnostic.Create(DiagnosticDescriptors.InvalidLambdaMethodSignature, methodLocation, errorMessage));
468+
}
469+
470+
// Validate method return type - When using SNSEventAttribute, the return type must be either void or Task
471+
if (!lambdaFunctionModel.LambdaMethod.ReturnsVoid && !lambdaFunctionModel.LambdaMethod.ReturnsVoidTask)
472+
{
473+
var errorMessage = $"When using the {nameof(SNSEventAttribute)}, the Lambda method can return either void or {TypeFullNames.Task}";
474+
diagnostics.Add(Diagnostic.Create(DiagnosticDescriptors.InvalidLambdaMethodSignature, methodLocation, errorMessage));
475+
}
476+
}
477+
427478
private static bool ReportDiagnostics(DiagnosticReporter diagnosticReporter, List<Diagnostic> diagnostics)
428479
{
429480
var isValid = true;

0 commit comments

Comments
 (0)