using System; using System.Collections.Generic; using System.Linq; using Amazon.Lambda.Annotations.APIGateway; using Amazon.Lambda.Annotations.SourceGenerator.Extensions; using Microsoft.CodeAnalysis; namespace Amazon.Lambda.Annotations.SourceGenerator.Models { /// /// builder. /// public static class GeneratedMethodModelBuilder { public static GeneratedMethodModel Build(IMethodSymbol lambdaMethodSymbol, IMethodSymbol configureMethodSymbol, LambdaMethodModel lambdaMethodModel, GeneratorExecutionContext context) { var model = new GeneratedMethodModel { Usings = BuildUsings(lambdaMethodModel, lambdaMethodSymbol, configureMethodSymbol, context), Parameters = BuildParameters(lambdaMethodSymbol, lambdaMethodModel, context), ReturnType = BuildResponseType(lambdaMethodSymbol, lambdaMethodModel, context), ContainingType = BuildContainingType(lambdaMethodSymbol), }; return model; } private static IList BuildUsings(LambdaMethodModel lambdaMethodModel, IMethodSymbol lambdaMethodSymbol, IMethodSymbol configureMethodSymbol, GeneratorExecutionContext context) { var namespaces = new List { "System", "System.Linq", "System.Collections.Generic", "System.Text" }; if (configureMethodSymbol != null) { namespaces.Add("Microsoft.Extensions.DependencyInjection"); } namespaces.Add("Amazon.Lambda.Core"); if(lambdaMethodModel.ReturnsIHttpResults) { namespaces.Add("Amazon.Lambda.Annotations.APIGateway"); } return namespaces; } private static TypeModel BuildResponseType(IMethodSymbol lambdaMethodSymbol, LambdaMethodModel lambdaMethodModel, GeneratorExecutionContext context) { var task = context.Compilation.GetTypeByMetadataName(TypeFullNames.Task1); if (lambdaMethodModel.ReturnsIHttpResults) { var typeStream = context.Compilation.GetTypeByMetadataName(TypeFullNames.Stream); if (lambdaMethodModel.ReturnsGenericTask) { var genericTask = task.Construct(typeStream); return TypeModelBuilder.Build(genericTask, context); } return TypeModelBuilder.Build(typeStream, context); } if (lambdaMethodSymbol.HasAttribute(context, TypeFullNames.RestApiAttribute)) { var symbol = lambdaMethodModel.ReturnsVoidOrGenericTask ? task.Construct(context.Compilation.GetTypeByMetadataName(TypeFullNames.APIGatewayProxyResponse)): context.Compilation.GetTypeByMetadataName(TypeFullNames.APIGatewayProxyResponse); return TypeModelBuilder.Build(symbol, context); } else if (lambdaMethodSymbol.HasAttribute(context, TypeFullNames.HttpApiAttribute)) { var version = GetHttpApiVersion(lambdaMethodSymbol, context); switch (version) { case HttpApiVersion.V1: { var symbol = lambdaMethodModel.ReturnsVoidOrGenericTask ? task.Construct(context.Compilation.GetTypeByMetadataName(TypeFullNames.APIGatewayProxyResponse)): context.Compilation.GetTypeByMetadataName(TypeFullNames.APIGatewayProxyResponse); return TypeModelBuilder.Build(symbol, context); } case HttpApiVersion.V2: { var symbol = lambdaMethodModel.ReturnsVoidOrGenericTask ? task.Construct(context.Compilation.GetTypeByMetadataName(TypeFullNames.APIGatewayHttpApiV2ProxyResponse)): context.Compilation.GetTypeByMetadataName(TypeFullNames.APIGatewayHttpApiV2ProxyResponse); return TypeModelBuilder.Build(symbol, context); } default: throw new ArgumentOutOfRangeException(); } } else { return lambdaMethodModel.ReturnType; } } private static HttpApiVersion GetHttpApiVersion(IMethodSymbol lambdaMethodSymbol, GeneratorExecutionContext context) { var httpApiAttribute = lambdaMethodSymbol.GetAttributeData(context, TypeFullNames.HttpApiAttribute); if (httpApiAttribute.ConstructorArguments.IsDefaultOrEmpty) { throw new InvalidOperationException($"{TypeFullNames.HttpApiAttribute} must have a constructor with parameter."); } var versionArgument = httpApiAttribute.NamedArguments.FirstOrDefault(arg => arg.Key == "Version").Value; if (versionArgument.Type == null) { return HttpApiVersion.V2; } if (!versionArgument.Type.Equals(context.Compilation.GetTypeByMetadataName(TypeFullNames.HttpApiVersion), SymbolEqualityComparer.Default)) { throw new InvalidOperationException($"Constructor parameter must be of type {TypeFullNames.HttpApiVersion}."); } if (versionArgument.Value == null) { throw new InvalidOperationException($"{versionArgument.Type} value cannot be null for {TypeFullNames.HttpApiAttribute}."); } return (HttpApiVersion)versionArgument.Value; } private static IList BuildParameters(IMethodSymbol lambdaMethodSymbol, LambdaMethodModel lambdaMethodModel, GeneratorExecutionContext context) { var parameters = new List(); var contextParameter = new ParameterModel { Name = "__context__", Type = new TypeModel { FullName = TypeFullNames.ILambdaContext } }; if (lambdaMethodSymbol.HasAttribute(context, TypeFullNames.RestApiAttribute)) { var symbol = context.Compilation.GetTypeByMetadataName(TypeFullNames.APIGatewayProxyRequest); var type = TypeModelBuilder.Build(symbol, context); var requestParameter = new ParameterModel { Name = "__request__", Type = type }; parameters.Add(requestParameter); parameters.Add(contextParameter); } else if (lambdaMethodSymbol.HasAttribute(context, TypeFullNames.HttpApiAttribute)) { var version = GetHttpApiVersion(lambdaMethodSymbol, context); TypeModel type; switch (version) { case HttpApiVersion.V1: { var symbol = context.Compilation.GetTypeByMetadataName(TypeFullNames.APIGatewayProxyRequest); type = TypeModelBuilder.Build(symbol, context); break; } case HttpApiVersion.V2: { var symbol = context.Compilation.GetTypeByMetadataName(TypeFullNames.APIGatewayHttpApiV2ProxyRequest); type = TypeModelBuilder.Build(symbol, context); break; } default: throw new ArgumentOutOfRangeException(); } var requestParameter = new ParameterModel { Name = "__request__", Type = type }; parameters.Add(requestParameter); parameters.Add(contextParameter); } else { // Lambda method with no event attribute are plain lambda functions, therefore, generated method will have // same parameter as original method except DI injected parameters foreach (var param in lambdaMethodModel.Parameters) { if (param.Attributes.Any(att => att.Type.FullName == TypeFullNames.FromServiceAttribute)) { continue; } // If the Lambda function is taking in the ILambdaContext object make sure in the generated wrapper code we // use the same system name for the ILambdaContext variable as all of the other places use. else if(param.Type.FullName == TypeFullNames.ILambdaContext) { param.Name = "__context__"; } parameters.Add(param); } } return parameters; } private static TypeModel BuildContainingType(IMethodSymbol lambdaMethodSymbol) { var name = $"{lambdaMethodSymbol.ContainingType.Name}_{lambdaMethodSymbol.Name}_Generated"; var fullName = $"{lambdaMethodSymbol.ContainingNamespace}.{name}"; var model = new TypeModel { Name = name, FullName = fullName, IsValueType = false }; return model; } } }