Skip to content

Commit 86cf81f

Browse files
authored
feat: add api gateway emulator skeleton (#1902)
1 parent 0a9c885 commit 86cf81f

32 files changed

+1562
-132
lines changed
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
using System.Runtime.InteropServices;
2+
3+
namespace Spectre.Console.Cli;
4+
5+
/// <summary>
6+
/// Provides an abstract base class for asynchronous commands that support cancellation.
7+
/// </summary>
8+
/// <typeparam name="TSettings">The type of the settings used for the command.</typeparam>
9+
public abstract class CancellableAsyncCommand<TSettings> : AsyncCommand<TSettings> where TSettings : CommandSettings
10+
{
11+
/// <summary>
12+
/// Executes the command asynchronously, with support for cancellation.
13+
/// </summary>
14+
public abstract Task<int> ExecuteAsync(CommandContext context, TSettings settings, CancellationTokenSource cancellationTokenSource);
15+
16+
/// <summary>
17+
/// Executes the command asynchronously with built-in cancellation handling.
18+
/// </summary>
19+
public sealed override async Task<int> ExecuteAsync(CommandContext context, TSettings settings)
20+
{
21+
using var cancellationSource = new CancellationTokenSource();
22+
23+
using var sigInt = PosixSignalRegistration.Create(PosixSignal.SIGINT, onSignal);
24+
using var sigQuit = PosixSignalRegistration.Create(PosixSignal.SIGQUIT, onSignal);
25+
using var sigTerm = PosixSignalRegistration.Create(PosixSignal.SIGTERM, onSignal);
26+
27+
var cancellable = ExecuteAsync(context, settings, cancellationSource);
28+
return await cancellable;
29+
30+
void onSignal(PosixSignalContext context)
31+
{
32+
context.Cancel = true;
33+
cancellationSource.Cancel();
34+
}
35+
}
36+
}
Lines changed: 31 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
using System.ComponentModel;
21
using System.Diagnostics;
2+
using Amazon.Lambda.TestTool.Commands.Settings;
33
using Amazon.Lambda.TestTool.Extensions;
44
using Amazon.Lambda.TestTool.Models;
55
using Amazon.Lambda.TestTool.Processes;
@@ -8,49 +8,32 @@
88

99
namespace Amazon.Lambda.TestTool.Commands;
1010

11+
/// <summary>
12+
/// The default command of the application which is responsible for launching the Lambda Runtime API and the API Gateway Emulator.
13+
/// </summary>
1114
public sealed class RunCommand(
12-
IToolInteractiveService toolInteractiveService) : AsyncCommand<RunCommand.Settings>
15+
IToolInteractiveService toolInteractiveService) : CancellableAsyncCommand<RunCommandSettings>
1316
{
14-
public sealed class Settings : CommandSettings
15-
{
16-
[CommandOption("--host <HOST>")]
17-
[Description(
18-
"The hostname or IP address used for the test tool's web interface. Any host other than an explicit IP address or localhost (e.g. '*', '+' or 'example.com') binds to all public IPv4 and IPv6 addresses.")]
19-
[DefaultValue(Constants.DefaultHost)]
20-
public string Host { get; set; } = Constants.DefaultHost;
21-
22-
[CommandOption("-p|--port <PORT>")]
23-
[Description("The port number used for the test tool's web interface.")]
24-
[DefaultValue(Constants.DefaultPort)]
25-
public int Port { get; set; } = Constants.DefaultPort;
26-
27-
[CommandOption("--no-launch-window")]
28-
[Description("Disable auto launching the test tool's web interface in a browser.")]
29-
public bool NoLaunchWindow { get; set; }
30-
31-
[CommandOption("--pause-exit")]
32-
[Description("If set to true the test tool will pause waiting for a key input before exiting. The is useful when executing from an IDE so you can avoid having the output window immediately disappear after executing the Lambda code. The default value is true.")]
33-
public bool PauseExit { get; set; }
34-
35-
[CommandOption("--disable-logs")]
36-
[Description("Disables logging in the application")]
37-
public bool DisableLogs { get; set; }
38-
}
39-
40-
public override async Task<int> ExecuteAsync(CommandContext context, Settings settings)
17+
/// <summary>
18+
/// The method responsible for executing the <see cref="RunCommand"/>.
19+
/// </summary>
20+
public override async Task<int> ExecuteAsync(CommandContext context, RunCommandSettings settings, CancellationTokenSource cancellationTokenSource)
4121
{
4222
try
4323
{
44-
var process = TestToolProcess.Startup(settings);
45-
24+
var tasks = new List<Task>();
25+
26+
var testToolProcess = TestToolProcess.Startup(settings, cancellationTokenSource.Token);
27+
tasks.Add(testToolProcess.RunningTask);
28+
4629
if (!settings.NoLaunchWindow)
4730
{
4831
try
4932
{
5033
var info = new ProcessStartInfo
5134
{
5235
UseShellExecute = true,
53-
FileName = process.ServiceUrl
36+
FileName = testToolProcess.ServiceUrl
5437
};
5538
Process.Start(info);
5639
}
@@ -59,16 +42,23 @@ public override async Task<int> ExecuteAsync(CommandContext context, Settings se
5942
toolInteractiveService.WriteErrorLine($"Error launching browser: {e.Message}");
6043
}
6144
}
62-
63-
await process.RunningTask;
64-
45+
46+
if (settings.ApiGatewayEmulatorMode is not null)
47+
{
48+
var apiGatewayEmulatorProcess =
49+
ApiGatewayEmulatorProcess.Startup(settings, cancellationTokenSource.Token);
50+
tasks.Add(apiGatewayEmulatorProcess.RunningTask);
51+
}
52+
53+
await Task.WhenAny(tasks);
54+
6555
return CommandReturnCodes.Success;
6656
}
6757
catch (Exception e) when (e.IsExpectedException())
6858
{
6959
toolInteractiveService.WriteErrorLine(string.Empty);
7060
toolInteractiveService.WriteErrorLine(e.Message);
71-
61+
7262
return CommandReturnCodes.UserError;
7363
}
7464
catch (Exception e)
@@ -79,8 +69,12 @@ public override async Task<int> ExecuteAsync(CommandContext context, Settings se
7969
$"This is a bug.{Environment.NewLine}" +
8070
$"Please copy the stack trace below and file a bug at {Constants.LinkGithubRepo}. " +
8171
e.PrettyPrint());
82-
72+
8373
return CommandReturnCodes.UnhandledException;
8474
}
75+
finally
76+
{
77+
await cancellationTokenSource.CancelAsync();
78+
}
8579
}
8680
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
using Amazon.Lambda.TestTool.Models;
2+
using Spectre.Console.Cli;
3+
using System.ComponentModel;
4+
5+
namespace Amazon.Lambda.TestTool.Commands.Settings;
6+
7+
/// <summary>
8+
/// Represents the settings for configuring the <see cref="RunCommand"/>, which is the default command.
9+
/// </summary>
10+
public sealed class RunCommandSettings : CommandSettings
11+
{
12+
/// <summary>
13+
/// The hostname or IP address used for the test tool's web interface.
14+
/// Any host other than an explicit IP address or localhost (e.g. '*', '+' or 'example.com') binds to all public IPv4 and IPv6 addresses.
15+
/// </summary>
16+
[CommandOption("--host <HOST>")]
17+
[Description(
18+
"The hostname or IP address used for the test tool's web interface. Any host other than an explicit IP address or localhost (e.g. '*', '+' or 'example.com') binds to all public IPv4 and IPv6 addresses.")]
19+
[DefaultValue(Constants.DefaultHost)]
20+
public string Host { get; set; } = Constants.DefaultHost;
21+
22+
/// <summary>
23+
/// The port number used for the test tool's web interface.
24+
/// </summary>
25+
[CommandOption("-p|--port <PORT>")]
26+
[Description("The port number used for the test tool's web interface.")]
27+
[DefaultValue(Constants.DefaultLambdaRuntimeEmulatorPort)]
28+
public int Port { get; set; } = Constants.DefaultLambdaRuntimeEmulatorPort;
29+
30+
/// <summary>
31+
/// Disable auto launching the test tool's web interface in a browser.
32+
/// </summary>
33+
[CommandOption("--no-launch-window")]
34+
[Description("Disable auto launching the test tool's web interface in a browser.")]
35+
public bool NoLaunchWindow { get; set; }
36+
37+
/// <summary>
38+
/// If set to true the test tool will pause waiting for a key input before exiting.
39+
/// The is useful when executing from an IDE so you can avoid having the output window immediately disappear after executing the Lambda code.
40+
/// The default value is true.
41+
/// </summary>
42+
[CommandOption("--pause-exit")]
43+
[Description("If set to true the test tool will pause waiting for a key input before exiting. The is useful when executing from an IDE so you can avoid having the output window immediately disappear after executing the Lambda code. The default value is true.")]
44+
public bool PauseExit { get; set; }
45+
46+
/// <summary>
47+
/// Disables logging in the application
48+
/// </summary>
49+
[CommandOption("--disable-logs")]
50+
[Description("Disables logging in the application")]
51+
public bool DisableLogs { get; set; }
52+
53+
/// <summary>
54+
/// The API Gateway Emulator Mode specifies the format of the event that API Gateway sends to a Lambda integration,
55+
/// and how API Gateway interprets the response from Lambda.
56+
/// The available modes are: Rest, HttpV1, HttpV2.
57+
/// </summary>
58+
[CommandOption("--api-gateway-emulator <MODE>")]
59+
[Description(
60+
"The API Gateway Emulator Mode specifies the format of the event that API Gateway sends to a Lambda integration, and how API Gateway interprets the response from Lambda. " +
61+
"The available modes are: Rest, HttpV1, HttpV2.")]
62+
public ApiGatewayEmulatorMode? ApiGatewayEmulatorMode { get; set; }
63+
64+
/// <summary>
65+
/// The port number used for the test tool's API Gateway emulator.
66+
/// </summary>
67+
[CommandOption("--api-gateway-emulator-port <PORT>")]
68+
[Description("The port number used for the test tool's API Gateway emulator.")]
69+
[DefaultValue(Constants.DefaultApiGatewayEmulatorPort)]
70+
public int? ApiGatewayEmulatorPort { get; set; } = Constants.DefaultApiGatewayEmulatorPort;
71+
}

Tools/LambdaTestTool-v2/src/Amazon.Lambda.TestTool/Components/Pages/Home.razor

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
@using Amazon.Lambda.TestTool.Services
55
@using Amazon.Lambda.TestTool.Models
66
@using Amazon.Lambda.TestTool.SampleRequests;
7+
@using Amazon.Lambda.TestTool.Services.IO
78
@using Amazon.Lambda.TestTool.Utilities
89
@using Microsoft.AspNetCore.Http;
910

Tools/LambdaTestTool-v2/src/Amazon.Lambda.TestTool/Components/Pages/SaveRequestDialog.razor

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
@using Amazon.Lambda.TestTool.Commands
22
@using Amazon.Lambda.TestTool.SampleRequests;
33
@using Amazon.Lambda.TestTool.Services
4+
@using Amazon.Lambda.TestTool.Services.IO
45
@inject IModalService ModalService
56
@inject IDirectoryManager DirectoryManager
67

Lines changed: 74 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,94 @@
1+
using Amazon.Lambda.TestTool.Models;
2+
13
namespace Amazon.Lambda.TestTool;
24

5+
/// <summary>
6+
/// Provides constant values used across the application.
7+
/// </summary>
38
public abstract class Constants
49
{
10+
/// <summary>
11+
/// The name of the dotnet CLI tool
12+
/// </summary>
513
public const string ToolName = "lambda-test-tool";
6-
public const int DefaultPort = 5050;
14+
15+
/// <summary>
16+
/// The default port used by the Lambda Test Tool for the Lambda Runtime API and the Web Interface.
17+
/// </summary>
18+
public const int DefaultLambdaRuntimeEmulatorPort = 5050;
19+
20+
/// <summary>
21+
/// The default port used by the API Gateway Emulator.
22+
/// </summary>
23+
public const int DefaultApiGatewayEmulatorPort = 5051;
24+
25+
/// <summary>
26+
/// The default hostname used for the Lambda Test Tool.
27+
/// </summary>
728
public const string DefaultHost = "localhost";
829

30+
/// <summary>
31+
/// The default mode for the API Gateway Emulator.
32+
/// </summary>
33+
public const ApiGatewayEmulatorMode DefaultApiGatewayEmulatorMode = ApiGatewayEmulatorMode.HttpV2;
34+
35+
/// <summary>
36+
/// The prefix for environment variables used to configure the Lambda functions.
37+
/// </summary>
38+
public const string LambdaConfigEnvironmentVariablePrefix = "APIGATEWAY_EMULATOR_ROUTE_CONFIG";
39+
40+
/// <summary>
41+
/// The product name displayed for the Lambda Test Tool.
42+
/// </summary>
943
public const string ProductName = "AWS .NET Mock Lambda Test Tool";
1044

45+
/// <summary>
46+
/// The CSS style used for successful responses in the tool's UI.
47+
/// </summary>
1148
public const string ResponseSuccessStyle = "white-space: pre-wrap; height: min-content; font-size: 75%; color: black";
49+
50+
/// <summary>
51+
/// The CSS style used for error responses in the tool's UI.
52+
/// </summary>
1253
public const string ResponseErrorStyle = "white-space: pre-wrap; height: min-content; font-size: 75%; color: red";
1354

55+
/// <summary>
56+
/// The CSS style used for successful responses in the tool's UI when a size constraint is applied.
57+
/// </summary>
1458
public const string ResponseSuccessStyleSizeConstraint = "white-space: pre-wrap; height: 300px; font-size: 75%; color: black";
59+
60+
/// <summary>
61+
/// The CSS style used for error responses in the tool's UI when a size constraint is applied.
62+
/// </summary>
1563
public const string ResponseErrorStyleSizeConstraint = "white-space: pre-wrap; height: 300px; font-size: 75%; color: red";
1664

65+
/// <summary>
66+
/// The GitHub repository link for the AWS Lambda .NET repository.
67+
/// </summary>
1768
public const string LinkGithubRepo = "https://github.com/aws/aws-lambda-dotnet";
18-
public const string LinkGithubTestTool = "https://github.com/aws/aws-lambda-dotnet/tree/master/Tools/LambdaTestTool";
69+
70+
/// <summary>
71+
/// The GitHub link for the Lambda Test Tool.
72+
/// </summary>
73+
public const string LinkGithubTestTool = "https://github.com/aws/aws-lambda-dotnet/tree/master/Tools/LambdaTestTool-v2";
74+
75+
/// <summary>
76+
/// The GitHub link for the Lambda Test Tool's installation and running instructions.
77+
/// </summary>
1978
public const string LinkGithubTestToolInstallAndRun = "https://github.com/aws/aws-lambda-dotnet/tree/master/Tools/LambdaTestTool#installing-and-running";
79+
80+
/// <summary>
81+
/// The AWS Developer Guide link for Dead Letter Queues in AWS Lambda.
82+
/// </summary>
2083
public const string LinkDlqDeveloperGuide = "https://docs.aws.amazon.com/lambda/latest/dg/dlq.html";
84+
85+
/// <summary>
86+
/// The Microsoft documentation link for the <see cref="System.Runtime.Loader.AssemblyLoadContext"/> class.
87+
/// </summary>
2188
public const string LinkMsdnAssemblyLoadContext = "https://docs.microsoft.com/en-us/dotnet/api/system.runtime.loader.assemblyloadcontext";
22-
public const string LinkVsToolkitMarketplace = "https://marketplace.visualstudio.com/items?itemName=AmazonWebServices.AWSToolkitforVisualStudio2017";
89+
90+
/// <summary>
91+
/// The Visual Studio Marketplace link for the AWS Toolkit for Visual Studio.
92+
/// </summary>
93+
public const string LinkVsToolkitMarketplace = "https://marketplace.visualstudio.com/items?itemName=AmazonWebServices.AWSToolkitforVisualStudio2022";
2394
}

Tools/LambdaTestTool-v2/src/Amazon.Lambda.TestTool/Extensions/ExceptionExtensions.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22

33
namespace Amazon.Lambda.TestTool.Extensions;
44

5+
/// <summary>
6+
/// A class that contains extension methods for the <see cref="Exception"/> class.
7+
/// </summary>
58
public static class ExceptionExtensions
69
{
710
/// <summary>
@@ -11,6 +14,9 @@ public static class ExceptionExtensions
1114
public static bool IsExpectedException(this Exception e) =>
1215
e is TestToolException;
1316

17+
/// <summary>
18+
/// Prints an exception in a user-friendly way.
19+
/// </summary>
1420
public static string PrettyPrint(this Exception? e)
1521
{
1622
if (null == e)
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,31 @@
11
using Amazon.Lambda.TestTool.Services;
2+
using Amazon.Lambda.TestTool.Services.IO;
23
using Microsoft.Extensions.DependencyInjection.Extensions;
34

45
namespace Amazon.Lambda.TestTool.Extensions;
56

7+
/// <summary>
8+
/// A class that contains extension methods for the <see cref="IServiceCollection"/> interface.
9+
/// </summary>
610
public static class ServiceCollectionExtensions
711
{
12+
/// <summary>
13+
/// Adds a set of services for the .NET CLI portion of this application.
14+
/// </summary>
815
public static void AddCustomServices(this IServiceCollection serviceCollection,
916
ServiceLifetime lifetime = ServiceLifetime.Singleton)
1017
{
1118
serviceCollection.TryAdd(new ServiceDescriptor(typeof(IToolInteractiveService), typeof(ConsoleInteractiveService), lifetime));
1219
serviceCollection.TryAdd(new ServiceDescriptor(typeof(IDirectoryManager), typeof(DirectoryManager), lifetime));
20+
}
21+
22+
/// <summary>
23+
/// Adds a set of services for the API Gateway emulator portion of this application.
24+
/// </summary>
25+
public static void AddApiGatewayEmulatorServices(this IServiceCollection serviceCollection,
26+
ServiceLifetime lifetime = ServiceLifetime.Singleton)
27+
{
28+
serviceCollection.TryAdd(new ServiceDescriptor(typeof(IApiGatewayRouteConfigService), typeof(ApiGatewayRouteConfigService), lifetime));
29+
serviceCollection.TryAdd(new ServiceDescriptor(typeof(IEnvironmentManager), typeof(EnvironmentManager), lifetime));
1330
}
1431
}

0 commit comments

Comments
 (0)