Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,11 @@
<ItemGroup>
<TargetPathWithTargetPlatformMoniker Include="$(PKGNewtonsoft_Json)\lib\netstandard2.0\Newtonsoft.Json.dll" IncludeRuntimeDependency="false" />
<TargetPathWithTargetPlatformMoniker Include="$(PKGYamlDotNet)\lib\netstandard2.0\YamlDotNet.dll" IncludeRuntimeDependency="false" />
<!-- The source generator loads Amazon.Lambda.Annotations at runtime to inspect attribute types.
Without this, the generator fails with FileNotFoundException in Release builds (CS8785).
Use absolute path via $(MSBuildProjectDirectory) so the path resolves correctly when
propagated to consuming projects through the project reference chain. -->
<TargetPathWithTargetPlatformMoniker Include="$(MSBuildProjectDirectory)\$(OutputPath)Amazon.Lambda.Annotations.dll" IncludeRuntimeDependency="false" />
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2026-02-26T12:06:37.456-05:00
REPORT RequestId: 49ccb130-93d1-4020-b13f-2046990f5e67	Duration: 985.80 ms	Billed Duration: 986 ms	Memory Size: 512 MB	Max Memory Used: 28 MB	
	
REPORT RequestId: 49ccb130-93d1-4020-b13f-2046990f5e67 Duration: 985.80 ms Billed Duration: 986 ms Memory Size: 512 MB Max Memory Used: 28 MB
	
2026-02-26T12:06:37.579-05:00
2026-02-26T17:06:37.557Z		fail	Amazon.Lambda.RuntimeSupport.ExceptionHandling.LambdaValidationException: Unable to load type 'TestServerlessApp.ComplexCalculator_Add_Generated' from assembly 'TestServerlessApp'.
	
2026-02-26T17:06:37.557Z fail Amazon.Lambda.RuntimeSupport.ExceptionHandling.LambdaValidationException: Unable to load type 'TestServerlessApp.ComplexCalculator_Add_Generated' from assembly 'TestServerlessApp'.
	
2026-02-26T12:06:37.579-05:00
at Amazon.Lambda.RuntimeSupport.Bootstrap.UserCodeLoader.Init(Action`1 customerLoggingAction) in /src/Repo/Libraries/src/Amazon.Lambda.RuntimeSupport/Bootstrap/UserCodeLoader.cs:line 115
	
at Amazon.Lambda.RuntimeSupport.Bootstrap.UserCodeLoader.Init(Action`1 customerLoggingAction) in /src/Repo/Libraries/src/Amazon.Lambda.RuntimeSupport/Bootstrap/UserCodeLoader.cs:line 115
	
2026-02-26T12:06:37.579-05:00
at Amazon.Lambda.RuntimeSupport.Bootstrap.UserCodeInitializer.InitializeAsync() in /src/Repo/Libraries/src/Amazon.Lambda.RuntimeSupport/Bootstrap/UserCodeInitializer.cs:line 46
	
at Amazon.Lambda.RuntimeSupport.Bootstrap.UserCodeInitializer.InitializeAsync() in /src/Repo/Libraries/src/Amazon.Lambda.RuntimeSupport/Bootstrap/UserCodeInitializer.cs:line 46
	
2026-02-26T12:06:37.579-05:00
at Amazon.Lambda.RuntimeSupport.LambdaBootstrap.InitializeAsync() in /src/Repo/Libraries/src/Amazon.Lambda.RuntimeSupport/Bootstrap/LambdaBootstrap.cs:line 210
	
at Amazon.Lambda.RuntimeSupport.LambdaBootstrap.InitializeAsync() in /src/Repo/Libraries/src/Amazon.Lambda.RuntimeSupport/Bootstrap/LambdaBootstrap.cs:line 210
	
2026-02-26T12:06:37.742-05:00
Unhandled exception. Amazon.Lambda.RuntimeSupport.ExceptionHandling.LambdaValidationException: Unable to load type 'TestServerlessApp.ComplexCalculator_Add_Generated' from assembly 'TestServerlessApp'.
	
Unhandled exception. Amazon.Lambda.RuntimeSupport.ExceptionHandling.LambdaValidationException: Unable to load type 'TestServerlessApp.ComplexCalculator_Add_Generated' from assembly 'TestServerlessApp'.
	
2026-02-26T12:06:37.742-05:00
at Amazon.Lambda.RuntimeSupport.Bootstrap.UserCodeLoader.Init(Action`1 customerLoggingAction) in /src/Repo/Libraries/src/Amazon.Lambda.RuntimeSupport/Bootstrap/UserCodeLoader.cs:line 115
	
at Amazon.Lambda.RuntimeSupport.Bootstrap.UserCodeLoader.Init(Action`1 customerLoggingAction) in /src/Repo/Libraries/src/Amazon.Lambda.RuntimeSupport/Bootstrap/UserCodeLoader.cs:line 115
	
2026-02-26T12:06:37.742-05:00
at Amazon.Lambda.RuntimeSupport.Bootstrap.UserCodeInitializer.InitializeAsync() in /src/Repo/Libraries/src/Amazon.Lambda.RuntimeSupport/Bootstrap/UserCodeInitializer.cs:line 46
	
at Amazon.Lambda.RuntimeSupport.Bootstrap.UserCodeInitializer.InitializeAsync() in /src/Repo/Libraries/src/Amazon.Lambda.RuntimeSupport/Bootstrap/UserCodeInitializer.cs:line 46
	
2026-02-26T12:06:37.742-05:00
at Amazon.Lambda.RuntimeSupport.LambdaBootstrap.InitializeAsync() in /src/Repo/Libraries/src/Amazon.Lambda.RuntimeSupport/Bootstrap/LambdaBootstrap.cs:line 210
	
at Amazon.Lambda.RuntimeSupport.LambdaBootstrap.InitializeAsync() in /src/Repo/Libraries/src/Amazon.Lambda.RuntimeSupport/Bootstrap/LambdaBootstrap.cs:line 210
	
2026-02-26T12:06:37.742-05:00
at Amazon.Lambda.RuntimeSupport.LambdaBootstrap.InitializeAsync() in /src/Repo/Libraries/src/Amazon.Lambda.RuntimeSupport/Bootstrap/LambdaBootstrap.cs:line 222
	
at Amazon.Lambda.RuntimeSupport.LambdaBootstrap.InitializeAsync() in /src/Repo/Libraries/src/Amazon.Lambda.RuntimeSupport/Bootstrap/LambdaBootstrap.cs:line 222
	
2026-02-26T12:06:37.742-05:00
at Amazon.Lambda.RuntimeSupport.LambdaBootstrap.RunAsync(CancellationToken cancellationToken) in /src/Repo/Libraries/src/Amazon.Lambda.RuntimeSupport/Bootstrap/LambdaBootstrap.cs:line 156
	
at Amazon.Lambda.RuntimeSupport.LambdaBootstrap.RunAsync(CancellationToken cancellationToken) in /src/Repo/Libraries/src/Amazon.Lambda.RuntimeSupport/Bootstrap/LambdaBootstrap.cs:line 156
	
2026-02-26T12:06:37.742-05:00
at Amazon.Lambda.RuntimeSupport.RuntimeSupportInitializer.RunLambdaBootstrap() in /src/Repo/Libraries/src/Amazon.Lambda.RuntimeSupport/RuntimeSupportInitializer.cs:line 63
	
at Amazon.Lambda.RuntimeSupport.RuntimeSupportInitializer.RunLambdaBootstrap() in /src/Repo/Libraries/src/Amazon.Lambda.RuntimeSupport/RuntimeSupportInitializer.cs:line 63
	
2026-02-26T12:06:37.742-05:00
at Amazon.Lambda.RuntimeSupport.Program.Main(String[] args) in /src/Repo/Libraries/src/Amazon.Lambda.RuntimeSupport/Program.cs:line 43
	
at Amazon.Lambda.RuntimeSupport.Program.Main(String[] args) in /src/Repo/Libraries/src/Amazon.Lambda.RuntimeSupport/Program.cs:line 43
	
2026-02-26T12:06:37.742-05:00
at Amazon.Lambda.RuntimeSupport.Program.<Main>(String[] args)

@normj have you seen this issue at all when working with the annotations libraries integration test project. without this change i was seeing this problem where sometimes the release build wouldnt include the generated files (it would sometimes but not all the time). adding this line to here seemed to fix the issue but it doesnt feel right

</ItemGroup>
</Target>

Expand Down
Original file line number Diff line number Diff line change
@@ -1,2 +1,15 @@
; Unshipped analyzer release
; https://github.com/dotnet/roslyn-analyzers/blob/master/src/Microsoft.CodeAnalysis.Analyzers/ReleaseTrackingAnalyzers.Help.md

### New Rules

Rule ID | Category | Severity | Notes
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i added these to this file manually is that correct to do?

--------|----------|----------|-------
AWSLambda0120 | AWSLambdaCSharpGenerator | Error | Authorizer Name Required
AWSLambda0121 | AWSLambdaCSharpGenerator | Error | HTTP API Authorizer Not Found
AWSLambda0122 | AWSLambdaCSharpGenerator | Error | REST API Authorizer Not Found
AWSLambda0123 | AWSLambdaCSharpGenerator | Error | Authorizer Type Mismatch
AWSLambda0124 | AWSLambdaCSharpGenerator | Error | Authorizer Type Mismatch
AWSLambda0125 | AWSLambdaCSharpGenerator | Error | Duplicate Authorizer Name
AWSLambda0126 | AWSLambdaCSharpGenerator | Error | Invalid Payload Format Version
AWSLambda0127 | AWSLambdaCSharpGenerator | Error | Invalid Result TTL
Original file line number Diff line number Diff line change
Expand Up @@ -153,5 +153,70 @@ public static class DiagnosticDescriptors
category: "AWSLambdaCSharpGenerator",
DiagnosticSeverity.Error,
isEnabledByDefault: true);

// Authorizer diagnostics (ALA0019-ALA0027 per design document)
public static readonly DiagnosticDescriptor AuthorizerMissingName = new DiagnosticDescriptor(
id: "AWSLambda0120",
title: "Authorizer Name Required",
messageFormat: "The Name property is required on [{0}] attribute",
category: "AWSLambdaCSharpGenerator",
DiagnosticSeverity.Error,
isEnabledByDefault: true);

public static readonly DiagnosticDescriptor HttpApiAuthorizerNotFound = new DiagnosticDescriptor(
id: "AWSLambda0121",
title: "HTTP API Authorizer Not Found",
messageFormat: "Authorizer '{0}' referenced in [HttpApi] attribute does not exist. Add [HttpApiAuthorizer(Name = \"{0}\")] to an authorizer function.",
category: "AWSLambdaCSharpGenerator",
DiagnosticSeverity.Error,
isEnabledByDefault: true);

public static readonly DiagnosticDescriptor RestApiAuthorizerNotFound = new DiagnosticDescriptor(
id: "AWSLambda0122",
title: "REST API Authorizer Not Found",
messageFormat: "Authorizer '{0}' referenced in [RestApi] attribute does not exist. Add [RestApiAuthorizer(Name = \"{0}\")] to an authorizer function.",
category: "AWSLambdaCSharpGenerator",
DiagnosticSeverity.Error,
isEnabledByDefault: true);

public static readonly DiagnosticDescriptor HttpApiAuthorizerTypeMismatch = new DiagnosticDescriptor(
id: "AWSLambda0123",
title: "Authorizer Type Mismatch",
messageFormat: "Cannot use REST API authorizer '{0}' with [HttpApi] attribute. Use an [HttpApiAuthorizer] instead.",
category: "AWSLambdaCSharpGenerator",
DiagnosticSeverity.Error,
isEnabledByDefault: true);

public static readonly DiagnosticDescriptor RestApiAuthorizerTypeMismatch = new DiagnosticDescriptor(
id: "AWSLambda0124",
title: "Authorizer Type Mismatch",
messageFormat: "Cannot use HTTP API authorizer '{0}' with [RestApi] attribute. Use a [RestApiAuthorizer] instead.",
category: "AWSLambdaCSharpGenerator",
DiagnosticSeverity.Error,
isEnabledByDefault: true);

public static readonly DiagnosticDescriptor DuplicateAuthorizerName = new DiagnosticDescriptor(
id: "AWSLambda0125",
title: "Duplicate Authorizer Name",
messageFormat: "Duplicate authorizer name '{0}'. Authorizer names must be unique within the same API type.",
category: "AWSLambdaCSharpGenerator",
DiagnosticSeverity.Error,
isEnabledByDefault: true);

public static readonly DiagnosticDescriptor InvalidAuthorizerPayloadFormatVersion = new DiagnosticDescriptor(
id: "AWSLambda0126",
title: "Invalid Payload Format Version",
messageFormat: "Invalid PayloadFormatVersion '{0}'. Must be \"1.0\" or \"2.0\".",
category: "AWSLambdaCSharpGenerator",
DiagnosticSeverity.Error,
isEnabledByDefault: true);

public static readonly DiagnosticDescriptor InvalidAuthorizerResultTtl = new DiagnosticDescriptor(
id: "AWSLambda0127",
title: "Invalid Result TTL",
messageFormat: "Invalid ResultTtlInSeconds '{0}'. Must be between 0 and 3600.",
category: "AWSLambdaCSharpGenerator",
DiagnosticSeverity.Error,
isEnabledByDefault: true);
}
}
185 changes: 185 additions & 0 deletions Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Generator.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
using Amazon.Lambda.Annotations.APIGateway;
using Amazon.Lambda.Annotations.SourceGenerator.Diagnostics;
using Amazon.Lambda.Annotations.SourceGenerator.Extensions;
using Amazon.Lambda.Annotations.SourceGenerator.FileIO;
using Amazon.Lambda.Annotations.SourceGenerator.Models;
using Amazon.Lambda.Annotations.SourceGenerator.Models.Attributes;
using Amazon.Lambda.Annotations.SourceGenerator.Templates;
using Amazon.Lambda.Annotations.SourceGenerator.Writers;
using Microsoft.CodeAnalysis;
Expand Down Expand Up @@ -168,6 +170,13 @@ public void Execute(GeneratorExecutionContext context)
continue;
}

// Check for authorizer attributes on this Lambda function
var authorizerModel = ExtractAuthorizerModel(lambdaMethodSymbol, lambdaFunctionModel.ResourceName);
if (authorizerModel != null)
{
annotationReport.Authorizers.Add(authorizerModel);
}

var template = new LambdaFunctionTemplate(lambdaFunctionModel);

string sourceText;
Expand Down Expand Up @@ -206,6 +215,23 @@ public void Execute(GeneratorExecutionContext context)
context.AddSource("Program.g.cs", SourceText.From(executableAssembly.TransformText().ToEnvironmentLineEndings(), Encoding.UTF8, SourceHashAlgorithm.Sha256));
}

// Validate authorizer models and references before syncing CloudFormation template
foreach (var authorizerModel in annotationReport.Authorizers)
{
var attributeName = authorizerModel.AuthorizerType == AuthorizerType.HttpApi
? "HttpApiAuthorizer"
: "RestApiAuthorizer";
if (!ValidateAuthorizerModel(authorizerModel, attributeName, Location.None, diagnosticReporter))
{
foundFatalError = true;
}
}

if (!ValidateAuthorizerReferences(annotationReport, diagnosticReporter))
{
foundFatalError = true;
}

// Run the CloudFormation sync if any LambdaMethods exists. Also run if no LambdaMethods exists but there is a
// CloudFormation template in case orphaned functions in the template need to be removed.
// Both checks are required because if there is no template but there are LambdaMethods the CF template the template will be created.
Expand Down Expand Up @@ -296,5 +322,164 @@ public void Initialize(GeneratorInitializationContext context)
// Register a syntax receiver that will be created for each generation pass
context.RegisterForSyntaxNotifications(() => new SyntaxReceiver(_fileManager, _directoryManager));
}

/// <summary>
/// Extracts authorizer model from method symbol if it has HttpApiAuthorizer or RestApiAuthorizer attribute.
/// </summary>
/// <param name="methodSymbol">The method symbol to check for authorizer attributes</param>
/// <param name="lambdaResourceName">The CloudFormation resource name for the Lambda function</param>
/// <returns>AuthorizerModel if an authorizer attribute is found, null otherwise</returns>
private static AuthorizerModel ExtractAuthorizerModel(IMethodSymbol methodSymbol, string lambdaResourceName)
{
foreach (var attribute in methodSymbol.GetAttributes())
{
var attributeFullName = attribute.AttributeClass?.ToDisplayString();

if (attributeFullName == TypeFullNames.HttpApiAuthorizerAttribute)
{
return HttpApiAuthorizerAttributeBuilder.BuildModel(attribute, lambdaResourceName);
}

if (attributeFullName == TypeFullNames.RestApiAuthorizerAttribute)
{
return RestApiAuthorizerAttributeBuilder.BuildModel(attribute, lambdaResourceName);
}
}

return null;
}

/// <summary>
/// Validates an authorizer model.
/// </summary>
/// <param name="model">The authorizer model to validate</param>
/// <param name="attributeName">The name of the attribute for error messages</param>
/// <param name="methodLocation">The location of the method for diagnostic reporting</param>
/// <param name="diagnosticReporter">The diagnostic reporter for validation errors</param>
/// <returns>True if valid, false otherwise</returns>
private static bool ValidateAuthorizerModel(AuthorizerModel model, string attributeName, Location methodLocation, DiagnosticReporter diagnosticReporter)
{
var isValid = true;

// Validate Name is provided
if (string.IsNullOrEmpty(model.Name))
{
diagnosticReporter.Report(Diagnostic.Create(DiagnosticDescriptors.AuthorizerMissingName, methodLocation, attributeName));
isValid = false;
}

// Validate PayloadFormatVersion for HTTP API authorizers
if (model.AuthorizerType == AuthorizerType.HttpApi)
{
if (model.PayloadFormatVersion != "1.0" && model.PayloadFormatVersion != "2.0")
{
diagnosticReporter.Report(Diagnostic.Create(DiagnosticDescriptors.InvalidAuthorizerPayloadFormatVersion, methodLocation, model.PayloadFormatVersion));
isValid = false;
}
}

// Validate ResultTtlInSeconds
if (model.ResultTtlInSeconds < 0 || model.ResultTtlInSeconds > 3600)
{
diagnosticReporter.Report(Diagnostic.Create(DiagnosticDescriptors.InvalidAuthorizerResultTtl, methodLocation, model.ResultTtlInSeconds.ToString()));
isValid = false;
}

return isValid;
}

/// <summary>
/// Validates authorizer references in lambda functions.
/// </summary>
/// <param name="annotationReport">The annotation report containing all functions and authorizers</param>
/// <param name="diagnosticReporter">The diagnostic reporter for validation errors</param>
/// <returns>True if all authorizer references are valid, false otherwise</returns>
private static bool ValidateAuthorizerReferences(AnnotationReport annotationReport, DiagnosticReporter diagnosticReporter)
{
var isValid = true;

// Build lookups for authorizers by type
var httpApiAuthorizers = annotationReport.Authorizers
.Where(a => a.AuthorizerType == AuthorizerType.HttpApi)
.ToDictionary(a => a.Name, a => a);
var restApiAuthorizers = annotationReport.Authorizers
.Where(a => a.AuthorizerType == AuthorizerType.RestApi)
.ToDictionary(a => a.Name, a => a);

// Check for duplicate authorizer names within the same API type
var httpApiAuthorizerNames = annotationReport.Authorizers
.Where(a => a.AuthorizerType == AuthorizerType.HttpApi)
.GroupBy(a => a.Name)
.Where(g => g.Count() > 1)
.Select(g => g.Key);

foreach (var duplicateName in httpApiAuthorizerNames)
{
diagnosticReporter.Report(Diagnostic.Create(DiagnosticDescriptors.DuplicateAuthorizerName, Location.None, duplicateName));
isValid = false;
}

var restApiAuthorizerNames = annotationReport.Authorizers
.Where(a => a.AuthorizerType == AuthorizerType.RestApi)
.GroupBy(a => a.Name)
.Where(g => g.Count() > 1)
.Select(g => g.Key);

foreach (var duplicateName in restApiAuthorizerNames)
{
diagnosticReporter.Report(Diagnostic.Create(DiagnosticDescriptors.DuplicateAuthorizerName, Location.None, duplicateName));
isValid = false;
}

// Validate authorizer references in functions
foreach (var function in annotationReport.LambdaFunctions)
{
var authorizerName = function.Authorizer;
if (string.IsNullOrEmpty(authorizerName))
{
continue;
}

// Check if this function uses HttpApi or RestApi
var usesHttpApi = function.Attributes.Any(a => a is AttributeModel<HttpApiAttribute>);
var usesRestApi = function.Attributes.Any(a => a is AttributeModel<RestApiAttribute>);

if (usesHttpApi)
{
if (!httpApiAuthorizers.ContainsKey(authorizerName))
{
// Check if it exists as a REST API authorizer (type mismatch)
if (restApiAuthorizers.ContainsKey(authorizerName))
{
diagnosticReporter.Report(Diagnostic.Create(DiagnosticDescriptors.HttpApiAuthorizerTypeMismatch, Location.None, authorizerName));
}
else
{
diagnosticReporter.Report(Diagnostic.Create(DiagnosticDescriptors.HttpApiAuthorizerNotFound, Location.None, authorizerName));
}
isValid = false;
}
}

if (usesRestApi)
{
if (!restApiAuthorizers.ContainsKey(authorizerName))
{
// Check if it exists as an HTTP API authorizer (type mismatch)
if (httpApiAuthorizers.ContainsKey(authorizerName))
{
diagnosticReporter.Report(Diagnostic.Create(DiagnosticDescriptors.RestApiAuthorizerTypeMismatch, Location.None, authorizerName));
}
else
{
diagnosticReporter.Report(Diagnostic.Create(DiagnosticDescriptors.RestApiAuthorizerNotFound, Location.None, authorizerName));
}
isValid = false;
}
}
}

return isValid;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@ public class AnnotationReport
/// </summary>
public IList<ILambdaFunctionSerializable> LambdaFunctions { get; } = new List<ILambdaFunctionSerializable>();

/// <summary>
/// Collection of Lambda authorizers detected in the project
/// </summary>
public IList<AuthorizerModel> Authorizers { get; } = new List<AuthorizerModel>();

/// <summary>
/// Path to the CloudFormation template for the Lambda project
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,14 @@ public static HttpApiAttribute Build(AttributeData att)
var method = (LambdaHttpMethod)att.ConstructorArguments[0].Value;
var template = att.ConstructorArguments[1].Value as string;
var version = att.NamedArguments.FirstOrDefault(arg => arg.Key == "Version").Value.Value;
var authorizer = att.NamedArguments.FirstOrDefault(arg => arg.Key == "Authorizer").Value.Value as string;

var data = new HttpApiAttribute(method, template)
{
Version = version == null ? HttpApiVersion.V2 : (HttpApiVersion)version
Version = version == null ? HttpApiVersion.V2 : (HttpApiVersion)version,
Authorizer = authorizer
};
return data;
}
}
}
}
Loading
Loading