-
Notifications
You must be signed in to change notification settings - Fork 494
Add [FunctionUrl] annotation attribute with CORS support and source generator #2324
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: dev
Are you sure you want to change the base?
Changes from all commits
403d00e
ef94a0e
a20a2b2
6d32835
bbcc0cc
ba8aadb
8830279
f38a6b0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| { | ||
| "Projects": [ | ||
| { | ||
| "Name": "Amazon.Lambda.Annotations", | ||
| "Type": "Minor", | ||
| "ChangelogMessages": [ | ||
| "Added [FunctionUrl] attribute for configuring Lambda functions with Function URL endpoints, including optional CORS support" | ||
| ] | ||
| } | ||
| ] | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,45 @@ | ||
| using System.Linq; | ||
| using Amazon.Lambda.Annotations.APIGateway; | ||
| using Microsoft.CodeAnalysis; | ||
|
|
||
| namespace Amazon.Lambda.Annotations.SourceGenerator.Models.Attributes | ||
| { | ||
| public static class FunctionUrlAttributeBuilder | ||
| { | ||
| public static FunctionUrlAttribute Build(AttributeData att) | ||
| { | ||
| var authType = att.NamedArguments.FirstOrDefault(arg => arg.Key == "AuthType").Value.Value; | ||
|
|
||
| var data = new FunctionUrlAttribute | ||
| { | ||
| AuthType = authType == null ? FunctionUrlAuthType.NONE : (FunctionUrlAuthType)authType | ||
| }; | ||
|
|
||
| var allowOrigins = att.NamedArguments.FirstOrDefault(arg => arg.Key == "AllowOrigins").Value; | ||
| if (!allowOrigins.IsNull) | ||
| data.AllowOrigins = allowOrigins.Values.Select(v => v.Value as string).ToArray(); | ||
|
|
||
| var allowMethods = att.NamedArguments.FirstOrDefault(arg => arg.Key == "AllowMethods").Value; | ||
| if (!allowMethods.IsNull) | ||
| data.AllowMethods = allowMethods.Values.Select(v => v.Value as string).ToArray(); | ||
|
|
||
| var allowHeaders = att.NamedArguments.FirstOrDefault(arg => arg.Key == "AllowHeaders").Value; | ||
| if (!allowHeaders.IsNull) | ||
| data.AllowHeaders = allowHeaders.Values.Select(v => v.Value as string).ToArray(); | ||
|
|
||
| var exposeHeaders = att.NamedArguments.FirstOrDefault(arg => arg.Key == "ExposeHeaders").Value; | ||
| if (!exposeHeaders.IsNull) | ||
| data.ExposeHeaders = exposeHeaders.Values.Select(v => v.Value as string).ToArray(); | ||
|
|
||
| var allowCredentials = att.NamedArguments.FirstOrDefault(arg => arg.Key == "AllowCredentials").Value.Value; | ||
| if (allowCredentials != null) | ||
| data.AllowCredentials = (bool)allowCredentials; | ||
|
|
||
| var maxAge = att.NamedArguments.FirstOrDefault(arg => arg.Key == "MaxAge").Value.Value; | ||
| if (maxAge != null) | ||
| data.MaxAge = (int)maxAge; | ||
|
|
||
| return data; | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,48 @@ | ||
| using System; | ||
|
|
||
| namespace Amazon.Lambda.Annotations.APIGateway | ||
| { | ||
| /// <summary> | ||
| /// Configures the Lambda function to be invoked via a Lambda Function URL. | ||
| /// </summary> | ||
| /// <remarks> | ||
| /// Function URLs use the same payload format as HTTP API v2 (APIGatewayHttpApiV2ProxyRequest/Response). | ||
| /// </remarks> | ||
| [AttributeUsage(AttributeTargets.Method)] | ||
| public class FunctionUrlAttribute : Attribute | ||
| { | ||
| /// <inheritdoc cref="FunctionUrlAuthType"/> | ||
| public FunctionUrlAuthType AuthType { get; set; } = FunctionUrlAuthType.NONE; | ||
|
|
||
| /// <summary> | ||
| /// The allowed origins for CORS requests. Example: new[] { "https://example.com" } | ||
| /// </summary> | ||
| public string[] AllowOrigins { get; set; } | ||
|
|
||
| /// <summary> | ||
| /// The allowed HTTP methods for CORS requests. Example: new[] { "GET", "POST" } | ||
| /// </summary> | ||
| public string[] AllowMethods { get; set; } | ||
|
|
||
| /// <summary> | ||
| /// The allowed headers for CORS requests. | ||
| /// </summary> | ||
| public string[] AllowHeaders { get; set; } | ||
|
|
||
| /// <summary> | ||
| /// Whether credentials are included in the CORS request. | ||
| /// </summary> | ||
| public bool AllowCredentials { get; set; } | ||
|
|
||
| /// <summary> | ||
| /// The expose headers for CORS responses. | ||
| /// </summary> | ||
| public string[] ExposeHeaders { get; set; } | ||
|
|
||
| /// <summary> | ||
| /// The maximum time in seconds that a browser can cache the CORS preflight response. | ||
| /// A value of 0 means the property is not set. | ||
| /// </summary> | ||
| public int MaxAge { get; set; } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,18 @@ | ||||||||||||||||||||||||||
| namespace Amazon.Lambda.Annotations.APIGateway | ||||||||||||||||||||||||||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. technically function url is completely different than api gateway but uses the same api gateway v2 response type, so i kept it in the same namespace for now |
||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||
| /// <summary> | ||||||||||||||||||||||||||
| /// The type of authentication for a Lambda Function URL. | ||||||||||||||||||||||||||
| /// </summary> | ||||||||||||||||||||||||||
| public enum FunctionUrlAuthType | ||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||
| /// <summary> | ||||||||||||||||||||||||||
| /// No authentication. Anyone with the Function URL can invoke the function. | ||||||||||||||||||||||||||
| /// </summary> | ||||||||||||||||||||||||||
| NONE, | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| /// <summary> | ||||||||||||||||||||||||||
| /// IAM authentication. Only authenticated IAM users and roles can invoke the function. | ||||||||||||||||||||||||||
| /// </summary> | ||||||||||||||||||||||||||
| AWS_IAM | ||||||||||||||||||||||||||
|
Comment on lines
+11
to
+16
|
||||||||||||||||||||||||||
| NONE, | |
| /// <summary> | |
| /// IAM authentication. Only authenticated IAM users and roles can invoke the function. | |
| /// </summary> | |
| AWS_IAM | |
| None, | |
| /// <summary> | |
| /// IAM authentication. Only authenticated IAM users and roles can invoke the function. | |
| /// </summary> | |
| AwsIam |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i think it should be all caps
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,98 @@ | ||
| // <auto-generated/> | ||
|
|
||
| using System; | ||
| using System.Linq; | ||
| using System.Collections.Generic; | ||
| using System.Text; | ||
| using System.Threading.Tasks; | ||
| using System.IO; | ||
| using Amazon.Lambda.Core; | ||
| using Amazon.Lambda.Annotations.APIGateway; | ||
|
|
||
| namespace TestServerlessApp | ||
| { | ||
| public class FunctionUrlExample_GetItems_Generated | ||
| { | ||
| private readonly FunctionUrlExample functionUrlExample; | ||
| private readonly Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer serializer; | ||
|
|
||
| /// <summary> | ||
| /// Default constructor. This constructor is used by Lambda to construct the instance. When invoked in a Lambda environment | ||
| /// the AWS credentials will come from the IAM role associated with the function and the AWS region will be set to the | ||
| /// region the Lambda function is executed in. | ||
| /// </summary> | ||
| public FunctionUrlExample_GetItems_Generated() | ||
| { | ||
| SetExecutionEnvironment(); | ||
| functionUrlExample = new FunctionUrlExample(); | ||
| serializer = new Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer(); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// The generated Lambda function handler for <see cref="GetItems(string, Amazon.Lambda.Core.ILambdaContext)"/> | ||
| /// </summary> | ||
| /// <param name="__request__">The Function URL request object that will be processed by the Lambda function handler.</param> | ||
| /// <param name="__context__">The ILambdaContext that provides methods for logging and describing the Lambda environment.</param> | ||
| /// <returns>Result of the Lambda function execution</returns> | ||
| public System.IO.Stream GetItems(Amazon.Lambda.APIGatewayEvents.APIGatewayHttpApiV2ProxyRequest __request__, Amazon.Lambda.Core.ILambdaContext __context__) | ||
| { | ||
| var validationErrors = new List<string>(); | ||
|
|
||
| var category = default(string); | ||
| if (__request__.QueryStringParameters?.ContainsKey("category") == true) | ||
| { | ||
| try | ||
| { | ||
| category = (string)Convert.ChangeType(__request__.QueryStringParameters["category"], typeof(string)); | ||
| } | ||
| catch (Exception e) when (e is InvalidCastException || e is FormatException || e is OverflowException || e is ArgumentException) | ||
| { | ||
| validationErrors.Add($"Value {__request__.QueryStringParameters["category"]} at 'category' failed to satisfy constraint: {e.Message}"); | ||
| } | ||
| } | ||
|
|
||
| // return 400 Bad Request if there exists a validation error | ||
| if (validationErrors.Any()) | ||
| { | ||
| var errorResult = new Amazon.Lambda.APIGatewayEvents.APIGatewayHttpApiV2ProxyResponse | ||
| { | ||
| Body = @$"{{""message"": ""{validationErrors.Count} validation error(s) detected: {string.Join(",", validationErrors)}""}}", | ||
| Headers = new Dictionary<string, string> | ||
| { | ||
| {"Content-Type", "application/json"}, | ||
| {"x-amzn-ErrorType", "ValidationException"} | ||
| }, | ||
| StatusCode = 400 | ||
| }; | ||
| var errorStream = new System.IO.MemoryStream(); | ||
| serializer.Serialize(errorResult, errorStream); | ||
| errorStream.Position = 0; | ||
| return errorStream; | ||
| } | ||
|
|
||
| var httpResults = functionUrlExample.GetItems(category, __context__); | ||
| HttpResultSerializationOptions.ProtocolFormat serializationFormat = HttpResultSerializationOptions.ProtocolFormat.HttpApi; | ||
| HttpResultSerializationOptions.ProtocolVersion serializationVersion = HttpResultSerializationOptions.ProtocolVersion.V2; | ||
| var serializationOptions = new HttpResultSerializationOptions { Format = serializationFormat, Version = serializationVersion, Serializer = serializer }; | ||
| var response = httpResults.Serialize(serializationOptions); | ||
| return response; | ||
| } | ||
|
|
||
| private static void SetExecutionEnvironment() | ||
| { | ||
| const string envName = "AWS_EXECUTION_ENV"; | ||
|
|
||
| var envValue = new StringBuilder(); | ||
|
|
||
| // If there is an existing execution environment variable add the annotations package as a suffix. | ||
| if(!string.IsNullOrEmpty(Environment.GetEnvironmentVariable(envName))) | ||
| { | ||
| envValue.Append($"{Environment.GetEnvironmentVariable(envName)}_"); | ||
| } | ||
|
|
||
| envValue.Append("lib/amazon-lambda-annotations#{ANNOTATIONS_ASSEMBLY_VERSION}"); | ||
|
|
||
| Environment.SetEnvironmentVariable(envName, envValue.ToString()); | ||
| } | ||
| } | ||
| } |
Uh oh!
There was an error while loading. Please reload this page.