Skip to content

Commit 19360f2

Browse files
committed
Phase 1: Add FunctionUrlAttribute with source generator wiring and CloudFormation FunctionUrlConfig generation
- New FunctionUrlAttribute class with AuthType property (NONE/AWS_IAM) - New FunctionUrlAuthType enum - Source generator detects FunctionUrlAttribute and maps to EventType.API - Generated wrapper uses HttpApi V2 request/response types (same payload format) - CloudFormationWriter emits FunctionUrlConfig on the function resource - Dependency validation checks for Amazon.Lambda.APIGatewayEvents - SyntaxReceiver detects missing [LambdaFunction] on [FunctionUrl] methods - 6 new unit tests for CloudFormation template generation (JSON + YAML)
1 parent 52d0de4 commit 19360f2

File tree

11 files changed

+205
-1
lines changed

11 files changed

+205
-1
lines changed

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,15 @@ public static AttributeModel Build(AttributeData att, GeneratorExecutionContext
9090
Type = TypeModelBuilder.Build(att.AttributeClass, context)
9191
};
9292
}
93+
else if (att.AttributeClass.Equals(context.Compilation.GetTypeByMetadataName(TypeFullNames.FunctionUrlAttribute), SymbolEqualityComparer.Default))
94+
{
95+
var data = FunctionUrlAttributeBuilder.Build(att);
96+
model = new AttributeModel<FunctionUrlAttribute>
97+
{
98+
Data = data,
99+
Type = TypeModelBuilder.Build(att.AttributeClass, context)
100+
};
101+
}
93102
else if (att.AttributeClass.Equals(context.Compilation.GetTypeByMetadataName(TypeFullNames.HttpApiAuthorizerAttribute), SymbolEqualityComparer.Default))
94103
{
95104
var data = HttpApiAuthorizerAttributeBuilder.Build(att);
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
using System.Linq;
2+
using Amazon.Lambda.Annotations.APIGateway;
3+
using Microsoft.CodeAnalysis;
4+
5+
namespace Amazon.Lambda.Annotations.SourceGenerator.Models.Attributes
6+
{
7+
public static class FunctionUrlAttributeBuilder
8+
{
9+
public static FunctionUrlAttribute Build(AttributeData att)
10+
{
11+
var authType = att.NamedArguments.FirstOrDefault(arg => arg.Key == "AuthType").Value.Value;
12+
13+
return new FunctionUrlAttribute
14+
{
15+
AuthType = authType == null ? FunctionUrlAuthType.NONE : (FunctionUrlAuthType)authType
16+
};
17+
}
18+
}
19+
}

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@ public static HashSet<EventType> Build(IMethodSymbol lambdaMethodSymbol,
1818
foreach (var attribute in lambdaMethodSymbol.GetAttributes())
1919
{
2020
if (attribute.AttributeClass.ToDisplayString() == TypeFullNames.RestApiAttribute
21-
|| attribute.AttributeClass.ToDisplayString() == TypeFullNames.HttpApiAttribute)
21+
|| attribute.AttributeClass.ToDisplayString() == TypeFullNames.HttpApiAttribute
22+
|| attribute.AttributeClass.ToDisplayString() == TypeFullNames.FunctionUrlAttribute)
2223
{
2324
events.Add(EventType.API);
2425
}

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

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,14 @@ private static TypeModel BuildResponseType(IMethodSymbol lambdaMethodSymbol,
130130
throw new ArgumentOutOfRangeException();
131131
}
132132
}
133+
else if (lambdaMethodSymbol.HasAttribute(context, TypeFullNames.FunctionUrlAttribute))
134+
{
135+
// Function URLs use the same payload format as HTTP API v2
136+
var symbol = lambdaMethodModel.ReturnsVoidOrGenericTask ?
137+
task.Construct(context.Compilation.GetTypeByMetadataName(TypeFullNames.APIGatewayHttpApiV2ProxyResponse)):
138+
context.Compilation.GetTypeByMetadataName(TypeFullNames.APIGatewayHttpApiV2ProxyResponse);
139+
return TypeModelBuilder.Build(symbol, context);
140+
}
133141
else
134142
{
135143
return lambdaMethodModel.ReturnType;
@@ -277,6 +285,20 @@ private static IList<ParameterModel> BuildParameters(IMethodSymbol lambdaMethodS
277285
parameters.Add(requestParameter);
278286
parameters.Add(contextParameter);
279287
}
288+
else if (lambdaMethodSymbol.HasAttribute(context, TypeFullNames.FunctionUrlAttribute))
289+
{
290+
// Function URLs use the same payload format as HTTP API v2
291+
var symbol = context.Compilation.GetTypeByMetadataName(TypeFullNames.APIGatewayHttpApiV2ProxyRequest);
292+
var type = TypeModelBuilder.Build(symbol, context);
293+
var requestParameter = new ParameterModel
294+
{
295+
Name = "__request__",
296+
Type = type,
297+
Documentation = "The Function URL request object that will be processed by the Lambda function handler."
298+
};
299+
parameters.Add(requestParameter);
300+
parameters.Add(contextParameter);
301+
}
280302
else
281303
{
282304
// Lambda method with no event attribute are plain lambda functions, therefore, generated method will have

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ internal class SyntaxReceiver : ISyntaxContextReceiver
2121
{ "RestApiAuthorizerAttribute", "RestApiAuthorizer" },
2222
{ "HttpApiAttribute", "HttpApi" },
2323
{ "RestApiAttribute", "RestApi" },
24+
{ "FunctionUrlAttribute", "FunctionUrl" },
2425
{ "SQSEventAttribute", "SQSEvent" }
2526
};
2627

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@ public static class TypeFullNames
3434
public const string FromRouteAttribute = "Amazon.Lambda.Annotations.APIGateway.FromRouteAttribute";
3535
public const string FromCustomAuthorizerAttribute = "Amazon.Lambda.Annotations.APIGateway.FromCustomAuthorizerAttribute";
3636

37+
public const string FunctionUrlAttribute = "Amazon.Lambda.Annotations.APIGateway.FunctionUrlAttribute";
38+
public const string FunctionUrlAuthType = "Amazon.Lambda.Annotations.APIGateway.FunctionUrlAuthType";
39+
3740
public const string HttpApiAuthorizerAttribute = "Amazon.Lambda.Annotations.APIGateway.HttpApiAuthorizerAttribute";
3841
public const string RestApiAuthorizerAttribute = "Amazon.Lambda.Annotations.APIGateway.RestApiAuthorizerAttribute";
3942

@@ -67,6 +70,7 @@ public static class TypeFullNames
6770
{
6871
RestApiAttribute,
6972
HttpApiAttribute,
73+
FunctionUrlAttribute,
7074
SQSEventAttribute
7175
};
7276
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ internal static bool ValidateDependencies(GeneratorExecutionContext context, IMe
6767
{
6868
// Check for references to "Amazon.Lambda.APIGatewayEvents" if the Lambda method is annotated with RestApi, HttpApi, or authorizer attributes.
6969
if (lambdaMethodSymbol.HasAttribute(context, TypeFullNames.RestApiAttribute) || lambdaMethodSymbol.HasAttribute(context, TypeFullNames.HttpApiAttribute)
70+
|| lambdaMethodSymbol.HasAttribute(context, TypeFullNames.FunctionUrlAttribute)
7071
|| lambdaMethodSymbol.HasAttribute(context, TypeFullNames.HttpApiAuthorizerAttribute) || lambdaMethodSymbol.HasAttribute(context, TypeFullNames.RestApiAuthorizerAttribute))
7172
{
7273
if (context.Compilation.ReferencedAssemblyNames.FirstOrDefault(x => x.Name == "Amazon.Lambda.APIGatewayEvents") == null)

Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Writers/CloudFormationWriter.cs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,9 @@ private void ProcessLambdaFunctionEventAttributes(ILambdaFunctionSerializable la
221221
eventName = ProcessSqsAttribute(lambdaFunction, sqsAttributeModel.Data, currentSyncedEventProperties);
222222
currentSyncedEvents.Add(eventName);
223223
break;
224+
case AttributeModel<FunctionUrlAttribute> functionUrlAttributeModel:
225+
ProcessFunctionUrlAttribute(lambdaFunction, functionUrlAttributeModel.Data);
226+
break;
224227
}
225228
}
226229

@@ -290,6 +293,17 @@ private string ProcessHttpApiAttribute(ILambdaFunctionSerializable lambdaFunctio
290293
return eventName;
291294
}
292295

296+
/// <summary>
297+
/// Writes the <see cref="FunctionUrlAttribute"/> configuration to the serverless template.
298+
/// Unlike HttpApi/RestApi, Function URLs are configured as a property on the function resource
299+
/// rather than as an event source.
300+
/// </summary>
301+
private void ProcessFunctionUrlAttribute(ILambdaFunctionSerializable lambdaFunction, FunctionUrlAttribute functionUrlAttribute)
302+
{
303+
var functionUrlConfigPath = $"Resources.{lambdaFunction.ResourceName}.Properties.FunctionUrlConfig";
304+
_templateWriter.SetToken($"{functionUrlConfigPath}.AuthType", functionUrlAttribute.AuthType.ToString());
305+
}
306+
293307
/// <summary>
294308
/// Processes all authorizers and writes them to the serverless template as inline authorizers within the API resources.
295309
/// AWS SAM expects authorizers to be defined within the Auth.Authorizers property of AWS::Serverless::HttpApi or AWS::Serverless::Api resources.
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
using System;
2+
3+
namespace Amazon.Lambda.Annotations.APIGateway
4+
{
5+
/// <summary>
6+
/// Configures the Lambda function to be invoked via a Lambda Function URL.
7+
/// </summary>
8+
/// <remarks>
9+
/// Function URLs use the same payload format as HTTP API v2 (APIGatewayHttpApiV2ProxyRequest/Response).
10+
/// </remarks>
11+
[AttributeUsage(AttributeTargets.Method)]
12+
public class FunctionUrlAttribute : Attribute
13+
{
14+
/// <inheritdoc cref="FunctionUrlAuthType"/>
15+
public FunctionUrlAuthType AuthType { get; set; } = FunctionUrlAuthType.NONE;
16+
}
17+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
namespace Amazon.Lambda.Annotations.APIGateway
2+
{
3+
/// <summary>
4+
/// The type of authentication for a Lambda Function URL.
5+
/// </summary>
6+
public enum FunctionUrlAuthType
7+
{
8+
/// <summary>
9+
/// No authentication. Anyone with the Function URL can invoke the function.
10+
/// </summary>
11+
NONE,
12+
13+
/// <summary>
14+
/// IAM authentication. Only authenticated IAM users and roles can invoke the function.
15+
/// </summary>
16+
AWS_IAM
17+
}
18+
}

0 commit comments

Comments
 (0)