From 22d1469854413089fe883cf1dadc2b66f7adb860 Mon Sep 17 00:00:00 2001 From: Norm Johanson Date: Wed, 11 Feb 2026 16:16:55 -0800 Subject: [PATCH 1/5] Task 1 --- .../Client/IResponseStream.cs | 85 +++++++++++++++++++ .../Client/ResponseStreamContext.cs | 44 ++++++++++ .../Client/StreamingConstants.cs | 48 +++++++++++ 3 files changed, 177 insertions(+) create mode 100644 Libraries/src/Amazon.Lambda.RuntimeSupport/Client/IResponseStream.cs create mode 100644 Libraries/src/Amazon.Lambda.RuntimeSupport/Client/ResponseStreamContext.cs create mode 100644 Libraries/src/Amazon.Lambda.RuntimeSupport/Client/StreamingConstants.cs diff --git a/Libraries/src/Amazon.Lambda.RuntimeSupport/Client/IResponseStream.cs b/Libraries/src/Amazon.Lambda.RuntimeSupport/Client/IResponseStream.cs new file mode 100644 index 000000000..6107dde16 --- /dev/null +++ b/Libraries/src/Amazon.Lambda.RuntimeSupport/Client/IResponseStream.cs @@ -0,0 +1,85 @@ +/* + * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace Amazon.Lambda.RuntimeSupport +{ + /// + /// Interface for writing streaming responses in AWS Lambda functions. + /// Obtained by calling ResponseStreamFactory.CreateStream() within a handler. + /// + public interface IResponseStream : IDisposable + { + /// + /// Asynchronously writes a byte array to the response stream. + /// + /// The byte array to write. + /// Optional cancellation token. + /// A task representing the asynchronous operation. + /// Thrown if the stream is already completed or an error has been reported. + /// Thrown if writing would exceed the 20 MiB limit. + Task WriteAsync(byte[] buffer, CancellationToken cancellationToken = default); + + /// + /// Asynchronously writes a portion of a byte array to the response stream. + /// + /// The byte array containing data to write. + /// The zero-based byte offset in buffer at which to begin copying bytes. + /// The number of bytes to write. + /// Optional cancellation token. + /// A task representing the asynchronous operation. + /// Thrown if the stream is already completed or an error has been reported. + /// Thrown if writing would exceed the 20 MiB limit. + Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken = default); + + /// + /// Asynchronously writes a memory buffer to the response stream. + /// + /// The memory buffer to write. + /// Optional cancellation token. + /// A task representing the asynchronous operation. + /// Thrown if the stream is already completed or an error has been reported. + /// Thrown if writing would exceed the 20 MiB limit. + Task WriteAsync(ReadOnlyMemory buffer, CancellationToken cancellationToken = default); + + /// + /// Reports an error that occurred during streaming. + /// This will send error information via HTTP trailing headers. + /// + /// The exception to report. + /// Optional cancellation token. + /// A task representing the asynchronous operation. + /// Thrown if the stream is already completed or an error has already been reported. + Task ReportErrorAsync(Exception exception, CancellationToken cancellationToken = default); + + /// + /// Gets the total number of bytes written to the stream so far. + /// + long BytesWritten { get; } + + /// + /// Gets whether the stream has been completed. + /// + bool IsCompleted { get; } + + /// + /// Gets whether an error has been reported. + /// + bool HasError { get; } + } +} diff --git a/Libraries/src/Amazon.Lambda.RuntimeSupport/Client/ResponseStreamContext.cs b/Libraries/src/Amazon.Lambda.RuntimeSupport/Client/ResponseStreamContext.cs new file mode 100644 index 000000000..fed7352a2 --- /dev/null +++ b/Libraries/src/Amazon.Lambda.RuntimeSupport/Client/ResponseStreamContext.cs @@ -0,0 +1,44 @@ +/* + * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +namespace Amazon.Lambda.RuntimeSupport +{ + /// + /// Internal context class used by ResponseStreamFactory to track per-invocation streaming state. + /// + internal class ResponseStreamContext + { + /// + /// The AWS request ID for the current invocation. + /// + public string AwsRequestId { get; set; } + + /// + /// Maximum allowed response size in bytes (20 MiB). + /// + public long MaxResponseSize { get; set; } + + /// + /// Whether CreateStream() has been called for this invocation. + /// + public bool StreamCreated { get; set; } + + /// + /// The IResponseStream instance if created. Typed as IResponseStream for now; + /// will be used with the concrete ResponseStream internally. + /// + public IResponseStream Stream { get; set; } + } +} diff --git a/Libraries/src/Amazon.Lambda.RuntimeSupport/Client/StreamingConstants.cs b/Libraries/src/Amazon.Lambda.RuntimeSupport/Client/StreamingConstants.cs new file mode 100644 index 000000000..7eeec86a2 --- /dev/null +++ b/Libraries/src/Amazon.Lambda.RuntimeSupport/Client/StreamingConstants.cs @@ -0,0 +1,48 @@ +/* + * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +namespace Amazon.Lambda.RuntimeSupport +{ + /// + /// Constants used for Lambda response streaming. + /// + internal static class StreamingConstants + { + /// + /// Maximum response size for Lambda streaming responses: 20 MiB. + /// + public const long MaxResponseSize = 20 * 1024 * 1024; + + /// + /// Header name for Lambda response mode. + /// + public const string ResponseModeHeader = "Lambda-Runtime-Function-Response-Mode"; + + /// + /// Value for streaming response mode. + /// + public const string StreamingResponseMode = "streaming"; + + /// + /// Trailer header name for error type. + /// + public const string ErrorTypeTrailer = "Lambda-Runtime-Function-Error-Type"; + + /// + /// Trailer header name for error body. + /// + public const string ErrorBodyTrailer = "Lambda-Runtime-Function-Error-Body"; + } +} From 5e0f8101f8b273ac475707f1737c59242cb940f3 Mon Sep 17 00:00:00 2001 From: Norm Johanson Date: Wed, 11 Feb 2026 16:24:58 -0800 Subject: [PATCH 2/5] Task 2 --- .../Client/ResponseStream.cs | 162 ++++++++++++++ .../Client/ResponseStreamContext.cs | 5 +- .../ResponseStreamTests.cs | 209 ++++++++++++++++++ 3 files changed, 373 insertions(+), 3 deletions(-) create mode 100644 Libraries/src/Amazon.Lambda.RuntimeSupport/Client/ResponseStream.cs create mode 100644 Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/ResponseStreamTests.cs diff --git a/Libraries/src/Amazon.Lambda.RuntimeSupport/Client/ResponseStream.cs b/Libraries/src/Amazon.Lambda.RuntimeSupport/Client/ResponseStream.cs new file mode 100644 index 000000000..1484d1f8d --- /dev/null +++ b/Libraries/src/Amazon.Lambda.RuntimeSupport/Client/ResponseStream.cs @@ -0,0 +1,162 @@ +/* + * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Amazon.Lambda.RuntimeSupport +{ + /// + /// Internal implementation of IResponseStream. + /// Buffers written data as chunks for HTTP chunked transfer encoding. + /// + internal class ResponseStream : IResponseStream + { + private readonly long _maxResponseSize; + private readonly List _chunks; + private long _bytesWritten; + private bool _isCompleted; + private bool _hasError; + private Exception _reportedError; + private readonly object _lock = new object(); + + public long BytesWritten => _bytesWritten; + public bool IsCompleted => _isCompleted; + public bool HasError => _hasError; + + internal IReadOnlyList Chunks + { + get + { + lock (_lock) + { + return _chunks.ToList(); + } + } + } + + internal Exception ReportedError => _reportedError; + + public ResponseStream(long maxResponseSize) + { + _maxResponseSize = maxResponseSize; + _chunks = new List(); + _bytesWritten = 0; + _isCompleted = false; + _hasError = false; + } + + public Task WriteAsync(byte[] buffer, CancellationToken cancellationToken = default) + { + if (buffer == null) + throw new ArgumentNullException(nameof(buffer)); + + return WriteAsync(buffer, 0, buffer.Length, cancellationToken); + } + + public Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken = default) + { + if (buffer == null) + throw new ArgumentNullException(nameof(buffer)); + if (offset < 0 || offset > buffer.Length) + throw new ArgumentOutOfRangeException(nameof(offset)); + if (count < 0 || offset + count > buffer.Length) + throw new ArgumentOutOfRangeException(nameof(count)); + + lock (_lock) + { + ThrowIfCompletedOrError(); + + if (_bytesWritten + count > _maxResponseSize) + { + throw new InvalidOperationException( + $"Writing {count} bytes would exceed the maximum response size of {_maxResponseSize} bytes (20 MiB). " + + $"Current size: {_bytesWritten} bytes."); + } + + var chunk = new byte[count]; + Array.Copy(buffer, offset, chunk, 0, count); + _chunks.Add(chunk); + _bytesWritten += count; + } + + return Task.CompletedTask; + } + + public Task WriteAsync(ReadOnlyMemory buffer, CancellationToken cancellationToken = default) + { + lock (_lock) + { + ThrowIfCompletedOrError(); + + if (_bytesWritten + buffer.Length > _maxResponseSize) + { + throw new InvalidOperationException( + $"Writing {buffer.Length} bytes would exceed the maximum response size of {_maxResponseSize} bytes (20 MiB). " + + $"Current size: {_bytesWritten} bytes."); + } + + var chunk = buffer.ToArray(); + _chunks.Add(chunk); + _bytesWritten += buffer.Length; + } + + return Task.CompletedTask; + } + + public Task ReportErrorAsync(Exception exception, CancellationToken cancellationToken = default) + { + if (exception == null) + throw new ArgumentNullException(nameof(exception)); + + lock (_lock) + { + if (_isCompleted) + throw new InvalidOperationException("Cannot report an error after the stream has been completed."); + if (_hasError) + throw new InvalidOperationException("An error has already been reported for this stream."); + + _hasError = true; + _reportedError = exception; + } + + return Task.CompletedTask; + } + + internal void MarkCompleted() + { + lock (_lock) + { + _isCompleted = true; + } + } + + private void ThrowIfCompletedOrError() + { + if (_isCompleted) + throw new InvalidOperationException("Cannot write to a completed stream."); + if (_hasError) + throw new InvalidOperationException("Cannot write to a stream after an error has been reported."); + } + + public void Dispose() + { + // Nothing to dispose - all data is in managed memory + } + } +} diff --git a/Libraries/src/Amazon.Lambda.RuntimeSupport/Client/ResponseStreamContext.cs b/Libraries/src/Amazon.Lambda.RuntimeSupport/Client/ResponseStreamContext.cs index fed7352a2..07df616e3 100644 --- a/Libraries/src/Amazon.Lambda.RuntimeSupport/Client/ResponseStreamContext.cs +++ b/Libraries/src/Amazon.Lambda.RuntimeSupport/Client/ResponseStreamContext.cs @@ -36,9 +36,8 @@ internal class ResponseStreamContext public bool StreamCreated { get; set; } /// - /// The IResponseStream instance if created. Typed as IResponseStream for now; - /// will be used with the concrete ResponseStream internally. + /// The ResponseStream instance if created. /// - public IResponseStream Stream { get; set; } + public ResponseStream Stream { get; set; } } } diff --git a/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/ResponseStreamTests.cs b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/ResponseStreamTests.cs new file mode 100644 index 000000000..7503277ca --- /dev/null +++ b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/ResponseStreamTests.cs @@ -0,0 +1,209 @@ +/* + * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +using System; +using System.Threading.Tasks; +using Xunit; + +namespace Amazon.Lambda.RuntimeSupport.UnitTests +{ + public class ResponseStreamTests + { + private const long MaxResponseSize = 20 * 1024 * 1024; // 20 MiB + + [Fact] + public void Constructor_InitializesStateCorrectly() + { + var stream = new ResponseStream(MaxResponseSize); + + Assert.Equal(0, stream.BytesWritten); + Assert.False(stream.IsCompleted); + Assert.False(stream.HasError); + Assert.Empty(stream.Chunks); + Assert.Null(stream.ReportedError); + } + + [Fact] + public async Task WriteAsync_ByteArray_BuffersDataCorrectly() + { + var stream = new ResponseStream(MaxResponseSize); + var data = new byte[] { 1, 2, 3, 4, 5 }; + + await stream.WriteAsync(data); + + Assert.Equal(5, stream.BytesWritten); + Assert.Single(stream.Chunks); + Assert.Equal(data, stream.Chunks[0]); + } + + [Fact] + public async Task WriteAsync_WithOffset_BuffersCorrectSlice() + { + var stream = new ResponseStream(MaxResponseSize); + var data = new byte[] { 0, 1, 2, 3, 0 }; + + await stream.WriteAsync(data, 1, 3); + + Assert.Equal(3, stream.BytesWritten); + Assert.Equal(new byte[] { 1, 2, 3 }, stream.Chunks[0]); + } + + [Fact] + public async Task WriteAsync_ReadOnlyMemory_BuffersDataCorrectly() + { + var stream = new ResponseStream(MaxResponseSize); + var data = new ReadOnlyMemory(new byte[] { 10, 20, 30 }); + + await stream.WriteAsync(data); + + Assert.Equal(3, stream.BytesWritten); + Assert.Equal(new byte[] { 10, 20, 30 }, stream.Chunks[0]); + } + + [Fact] + public async Task WriteAsync_MultipleWrites_AccumulatesBytesWritten() + { + var stream = new ResponseStream(MaxResponseSize); + + await stream.WriteAsync(new byte[100]); + await stream.WriteAsync(new byte[200]); + await stream.WriteAsync(new byte[300]); + + Assert.Equal(600, stream.BytesWritten); + Assert.Equal(3, stream.Chunks.Count); + } + + [Fact] + public async Task WriteAsync_CopiesData_AvoidingBufferReuseIssues() + { + var stream = new ResponseStream(MaxResponseSize); + var buffer = new byte[] { 1, 2, 3 }; + + await stream.WriteAsync(buffer); + buffer[0] = 99; // mutate original + + Assert.Equal(1, stream.Chunks[0][0]); // chunk should be unaffected + } + + /// + /// Property 6: Size Limit Enforcement - Writing beyond 20 MiB throws InvalidOperationException. + /// Validates: Requirements 3.6, 3.7 + /// + [Theory] + [InlineData(21 * 1024 * 1024)] // Single write exceeding limit + public async Task SizeLimit_SingleWriteExceedingLimit_Throws(int writeSize) + { + var stream = new ResponseStream(MaxResponseSize); + var data = new byte[writeSize]; + + await Assert.ThrowsAsync(() => stream.WriteAsync(data)); + } + + /// + /// Property 6: Size Limit Enforcement - Multiple writes exceeding 20 MiB throws. + /// Validates: Requirements 3.6, 3.7 + /// + [Fact] + public async Task SizeLimit_MultipleWritesExceedingLimit_Throws() + { + var stream = new ResponseStream(MaxResponseSize); + + await stream.WriteAsync(new byte[10 * 1024 * 1024]); + await Assert.ThrowsAsync( + () => stream.WriteAsync(new byte[11 * 1024 * 1024])); + } + + [Fact] + public async Task SizeLimit_ExactlyAtLimit_Succeeds() + { + var stream = new ResponseStream(MaxResponseSize); + var data = new byte[20 * 1024 * 1024]; + + await stream.WriteAsync(data); + + Assert.Equal(MaxResponseSize, stream.BytesWritten); + } + + /// + /// Property 19: Writes After Completion Rejected - Writes after completion throw InvalidOperationException. + /// Validates: Requirements 8.8 + /// + [Fact] + public async Task WriteAsync_AfterMarkCompleted_Throws() + { + var stream = new ResponseStream(MaxResponseSize); + await stream.WriteAsync(new byte[] { 1 }); + stream.MarkCompleted(); + + await Assert.ThrowsAsync( + () => stream.WriteAsync(new byte[] { 2 })); + } + + [Fact] + public async Task WriteAsync_AfterReportError_Throws() + { + var stream = new ResponseStream(MaxResponseSize); + await stream.WriteAsync(new byte[] { 1 }); + await stream.ReportErrorAsync(new Exception("test")); + + await Assert.ThrowsAsync( + () => stream.WriteAsync(new byte[] { 2 })); + } + + // --- Error handling tests (2.6) --- + + [Fact] + public async Task ReportErrorAsync_SetsErrorState() + { + var stream = new ResponseStream(MaxResponseSize); + var exception = new InvalidOperationException("something broke"); + + await stream.ReportErrorAsync(exception); + + Assert.True(stream.HasError); + Assert.Same(exception, stream.ReportedError); + } + + [Fact] + public async Task ReportErrorAsync_AfterCompleted_Throws() + { + var stream = new ResponseStream(MaxResponseSize); + stream.MarkCompleted(); + + await Assert.ThrowsAsync( + () => stream.ReportErrorAsync(new Exception("test"))); + } + + [Fact] + public async Task ReportErrorAsync_CalledTwice_Throws() + { + var stream = new ResponseStream(MaxResponseSize); + await stream.ReportErrorAsync(new Exception("first")); + + await Assert.ThrowsAsync( + () => stream.ReportErrorAsync(new Exception("second"))); + } + + [Fact] + public void MarkCompleted_SetsCompletionState() + { + var stream = new ResponseStream(MaxResponseSize); + + stream.MarkCompleted(); + + Assert.True(stream.IsCompleted); + } + } +} From 20e5ba8c50681295cf44cbcc63f5d96852d3ad98 Mon Sep 17 00:00:00 2001 From: Norm Johanson Date: Wed, 11 Feb 2026 16:31:17 -0800 Subject: [PATCH 3/5] Task 3 --- .../Client/ResponseStreamFactory.cs | 109 ++++++++++ .../ResponseStreamFactoryTests.cs | 188 ++++++++++++++++++ 2 files changed, 297 insertions(+) create mode 100644 Libraries/src/Amazon.Lambda.RuntimeSupport/Client/ResponseStreamFactory.cs create mode 100644 Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/ResponseStreamFactoryTests.cs diff --git a/Libraries/src/Amazon.Lambda.RuntimeSupport/Client/ResponseStreamFactory.cs b/Libraries/src/Amazon.Lambda.RuntimeSupport/Client/ResponseStreamFactory.cs new file mode 100644 index 000000000..9b60eacfd --- /dev/null +++ b/Libraries/src/Amazon.Lambda.RuntimeSupport/Client/ResponseStreamFactory.cs @@ -0,0 +1,109 @@ +/* + * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +using System; +using System.Threading; + +namespace Amazon.Lambda.RuntimeSupport +{ + /// + /// Factory for creating streaming responses in AWS Lambda functions. + /// Call CreateStream() within your handler to opt into response streaming for that invocation. + /// + public static class ResponseStreamFactory + { + // For on-demand mode (single invocation at a time) + private static ResponseStreamContext _onDemandContext; + + // For multi-concurrency mode (multiple concurrent invocations) + private static readonly AsyncLocal _asyncLocalContext = new AsyncLocal(); + + /// + /// Creates a streaming response for the current invocation. + /// Can only be called once per invocation. + /// + /// An IResponseStream for writing response data. + /// Thrown if called outside an invocation context. + /// Thrown if called more than once per invocation. + public static IResponseStream CreateStream() + { + var context = GetCurrentContext(); + + if (context == null) + { + throw new InvalidOperationException( + "ResponseStreamFactory.CreateStream() can only be called within a Lambda handler invocation."); + } + + if (context.StreamCreated) + { + throw new InvalidOperationException( + "ResponseStreamFactory.CreateStream() can only be called once per invocation."); + } + + var stream = new ResponseStream(context.MaxResponseSize); + context.Stream = stream; + context.StreamCreated = true; + + return stream; + } + + // Internal methods for LambdaBootstrap to manage state + + internal static void InitializeInvocation(string awsRequestId, long maxResponseSize, bool isMultiConcurrency) + { + var context = new ResponseStreamContext + { + AwsRequestId = awsRequestId, + MaxResponseSize = maxResponseSize, + StreamCreated = false, + Stream = null + }; + + if (isMultiConcurrency) + { + _asyncLocalContext.Value = context; + } + else + { + _onDemandContext = context; + } + } + + internal static ResponseStream GetStreamIfCreated(bool isMultiConcurrency) + { + var context = isMultiConcurrency ? _asyncLocalContext.Value : _onDemandContext; + return context?.Stream; + } + + internal static void CleanupInvocation(bool isMultiConcurrency) + { + if (isMultiConcurrency) + { + _asyncLocalContext.Value = null; + } + else + { + _onDemandContext = null; + } + } + + private static ResponseStreamContext GetCurrentContext() + { + // Check multi-concurrency first (AsyncLocal), then on-demand + return _asyncLocalContext.Value ?? _onDemandContext; + } + } +} diff --git a/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/ResponseStreamFactoryTests.cs b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/ResponseStreamFactoryTests.cs new file mode 100644 index 000000000..a4b0558af --- /dev/null +++ b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/ResponseStreamFactoryTests.cs @@ -0,0 +1,188 @@ +/* + * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +using System; +using System.Threading.Tasks; +using Xunit; + +namespace Amazon.Lambda.RuntimeSupport.UnitTests +{ + public class ResponseStreamFactoryTests : IDisposable + { + private const long MaxResponseSize = 20 * 1024 * 1024; + + public void Dispose() + { + // Clean up both modes to avoid test pollution + ResponseStreamFactory.CleanupInvocation(isMultiConcurrency: false); + ResponseStreamFactory.CleanupInvocation(isMultiConcurrency: true); + } + + // --- Task 3.3: CreateStream tests --- + + /// + /// Property 1: CreateStream Returns Valid Stream - on-demand mode. + /// Validates: Requirements 1.3, 2.2, 2.3 + /// + [Fact] + public void CreateStream_OnDemandMode_ReturnsValidStream() + { + ResponseStreamFactory.InitializeInvocation("req-1", MaxResponseSize, isMultiConcurrency: false); + + var stream = ResponseStreamFactory.CreateStream(); + + Assert.NotNull(stream); + Assert.IsAssignableFrom(stream); + } + + /// + /// Property 1: CreateStream Returns Valid Stream - multi-concurrency mode. + /// Validates: Requirements 1.3, 2.2, 2.3 + /// + [Fact] + public void CreateStream_MultiConcurrencyMode_ReturnsValidStream() + { + ResponseStreamFactory.InitializeInvocation("req-2", MaxResponseSize, isMultiConcurrency: true); + + var stream = ResponseStreamFactory.CreateStream(); + + Assert.NotNull(stream); + Assert.IsAssignableFrom(stream); + } + + /// + /// Property 4: Single Stream Per Invocation - calling CreateStream twice throws. + /// Validates: Requirements 2.5, 2.6 + /// + [Fact] + public void CreateStream_CalledTwice_ThrowsInvalidOperationException() + { + ResponseStreamFactory.InitializeInvocation("req-3", MaxResponseSize, isMultiConcurrency: false); + ResponseStreamFactory.CreateStream(); + + Assert.Throws(() => ResponseStreamFactory.CreateStream()); + } + + [Fact] + public void CreateStream_OutsideInvocationContext_ThrowsInvalidOperationException() + { + // No InitializeInvocation called + Assert.Throws(() => ResponseStreamFactory.CreateStream()); + } + + // --- Task 3.5: Internal methods tests --- + + [Fact] + public void InitializeInvocation_OnDemand_SetsUpContext() + { + ResponseStreamFactory.InitializeInvocation("req-4", MaxResponseSize, isMultiConcurrency: false); + + // GetStreamIfCreated should return null since CreateStream hasn't been called + Assert.Null(ResponseStreamFactory.GetStreamIfCreated(isMultiConcurrency: false)); + + // But CreateStream should work (proving context was set up) + var stream = ResponseStreamFactory.CreateStream(); + Assert.NotNull(stream); + } + + [Fact] + public void InitializeInvocation_MultiConcurrency_SetsUpContext() + { + ResponseStreamFactory.InitializeInvocation("req-5", MaxResponseSize, isMultiConcurrency: true); + + Assert.Null(ResponseStreamFactory.GetStreamIfCreated(isMultiConcurrency: true)); + + var stream = ResponseStreamFactory.CreateStream(); + Assert.NotNull(stream); + } + + [Fact] + public void GetStreamIfCreated_AfterCreateStream_ReturnsStream() + { + ResponseStreamFactory.InitializeInvocation("req-6", MaxResponseSize, isMultiConcurrency: false); + var created = ResponseStreamFactory.CreateStream(); + + var retrieved = ResponseStreamFactory.GetStreamIfCreated(isMultiConcurrency: false); + + Assert.NotNull(retrieved); + } + + [Fact] + public void GetStreamIfCreated_NoContext_ReturnsNull() + { + Assert.Null(ResponseStreamFactory.GetStreamIfCreated(isMultiConcurrency: false)); + } + + [Fact] + public void CleanupInvocation_ClearsState() + { + ResponseStreamFactory.InitializeInvocation("req-7", MaxResponseSize, isMultiConcurrency: false); + ResponseStreamFactory.CreateStream(); + + ResponseStreamFactory.CleanupInvocation(isMultiConcurrency: false); + + Assert.Null(ResponseStreamFactory.GetStreamIfCreated(isMultiConcurrency: false)); + Assert.Throws(() => ResponseStreamFactory.CreateStream()); + } + + /// + /// Property 16: State Isolation Between Invocations - state from one invocation doesn't leak to the next. + /// Validates: Requirements 6.5, 8.9 + /// + [Fact] + public void StateIsolation_SequentialInvocations_NoLeakage() + { + // First invocation - streaming + ResponseStreamFactory.InitializeInvocation("req-8a", MaxResponseSize, isMultiConcurrency: false); + var stream1 = ResponseStreamFactory.CreateStream(); + Assert.NotNull(stream1); + ResponseStreamFactory.CleanupInvocation(isMultiConcurrency: false); + + // Second invocation - should start fresh + ResponseStreamFactory.InitializeInvocation("req-8b", MaxResponseSize, isMultiConcurrency: false); + Assert.Null(ResponseStreamFactory.GetStreamIfCreated(isMultiConcurrency: false)); + + // Should be able to create a new stream + var stream2 = ResponseStreamFactory.CreateStream(); + Assert.NotNull(stream2); + ResponseStreamFactory.CleanupInvocation(isMultiConcurrency: false); + } + + /// + /// Property 16: State Isolation - multi-concurrency mode uses AsyncLocal. + /// Validates: Requirements 2.9, 2.10 + /// + [Fact] + public async Task StateIsolation_MultiConcurrency_UsesAsyncLocal() + { + // Initialize in multi-concurrency mode on main thread + ResponseStreamFactory.InitializeInvocation("req-9", MaxResponseSize, isMultiConcurrency: true); + var stream = ResponseStreamFactory.CreateStream(); + Assert.NotNull(stream); + + // A separate task should not see the main thread's context + // (AsyncLocal flows to child tasks, but a fresh Task.Run with new initialization should override) + bool childSawNull = false; + await Task.Run(() => + { + // Clean up the flowed context first + ResponseStreamFactory.CleanupInvocation(isMultiConcurrency: true); + childSawNull = ResponseStreamFactory.GetStreamIfCreated(isMultiConcurrency: true) == null; + }); + + Assert.True(childSawNull); + } + } +} From f63fec7a90cc51a62800c1f08f6146eee1662e22 Mon Sep 17 00:00:00 2001 From: Norm Johanson Date: Mon, 20 Apr 2026 15:41:20 -0700 Subject: [PATCH 4/5] Add support for Lambda Response Streaming (#2288) --- .../c27a62e6-91ca-4a59-9406-394866cdfa62.json | 18 + .../f0d5a912-bcfa-4244-96cb-ac3c847f877c.json | 27 + .github/CODEOWNERS | 1 + .gitignore | 2 + CHANGELOG.md | 25 + .../Images/net10/amd64/Dockerfile | 4 +- .../Images/net10/arm64/Dockerfile | 4 +- .../Images/net11/amd64/Dockerfile | 4 +- .../Images/net11/arm64/Dockerfile | 4 +- .../Images/net8/amd64/Dockerfile | 4 +- .../Images/net8/arm64/Dockerfile | 4 +- .../Images/net9/amd64/Dockerfile | 4 +- .../Images/net9/arm64/Dockerfile | 4 +- Libraries/Amazon.Lambda.Annotations.slnf | 5 +- Libraries/Amazon.Lambda.RuntimeSupport.slnf | 5 +- Libraries/Libraries.sln | 64 +- ....Lambda.Annotations.SourceGenerator.csproj | 3 +- .../Diagnostics/AnalyzerReleases.Unshipped.md | 6 + .../Diagnostics/DiagnosticDescriptors.cs | 46 ++ .../Extensions/ParameterListExtension.cs | 8 +- .../Attributes/ALBApiAttributeBuilder.cs | 68 ++ .../ALBFromHeaderAttributeBuilder.cs | 28 + .../ALBFromQueryAttributeBuilder.cs | 28 + .../Attributes/AttributeModelBuilder.cs | 74 +- .../Attributes/FunctionUrlAttributeBuilder.cs | 48 ++ .../Attributes/S3EventAttributeBuilder.cs | 37 + .../Attributes/SNSEventAttributeBuilder.cs | 43 ++ .../Models/EventType.cs | 4 +- .../Models/EventTypeBuilder.cs | 18 +- .../Models/GeneratedMethodModelBuilder.cs | 52 ++ .../Models/LambdaMethodModel.cs | 25 + .../SyntaxReceiver.cs | 13 +- .../Templates/ALBInvoke.cs | 420 +++++++++++ .../Templates/ALBInvoke.tt | 98 +++ .../Templates/ALBInvokeCode.cs | 20 + .../Templates/ALBSetupParameters.cs | 604 +++++++++++++++ .../Templates/ALBSetupParameters.tt | 303 ++++++++ .../Templates/ALBSetupParametersCode.cs | 19 + .../Templates/LambdaFunctionTemplate.cs | 6 + .../Templates/LambdaFunctionTemplate.tt | 6 + .../TypeFullNames.cs | 32 +- .../Validation/LambdaFunctionValidator.cs | 213 +++++- .../Writers/CloudFormationWriter.cs | 402 +++++++++- .../ALB/ALBApiAttribute.cs | 188 +++++ .../ALB/FromBodyAttribute.cs | 18 + .../ALB/FromHeaderAttribute.cs | 19 + .../ALB/FromQueryAttribute.cs | 19 + .../APIGateway/FunctionUrlAttribute.cs | 51 ++ .../APIGateway/FunctionUrlAuthType.cs | 21 + .../APIGateway/HttpApiAuthorizerAttribute.cs | 8 +- .../APIGateway/RestApiAuthorizerAttribute.cs | 2 +- .../Amazon.Lambda.Annotations.csproj | 3 +- .../src/Amazon.Lambda.Annotations/README.md | 354 ++++++++- .../S3/S3EventAttribute.cs | 129 ++++ .../SNS/SNSEventAttribute.cs | 116 +++ .../SQS/SQSEventAttribute.cs | 2 +- ...zon.Lambda.AspNetCoreServer.Hosting.csproj | 2 +- .../HostingOptions.cs | 15 + .../GetBeforeSnapshotRequestsCollector.cs | 2 - .../Internal/LambdaRuntimeSupportServer.cs | 45 +- .../ServiceCollectionExtensions.cs | 2 - .../APIGatewayHttpApiV2ProxyFunction.cs | 40 + .../APIGatewayProxyFunction.cs | 45 +- .../AbstractAspNetCoreFunction.cs | 218 +++++- .../Amazon.Lambda.AspNetCoreServer.csproj | 8 +- .../ApplicationLoadBalancerFunction.cs | 7 +- .../Internal/HttpRequestMessageConverter.cs | 8 - .../Internal/InvokeFeatures.cs | 11 +- .../Internal/StreamingResponseBodyFeature.cs | 250 +++++++ .../HttpResponseStreamPrelude.cs | 101 +++ .../ILambdaResponseStream.cs | 38 + .../ResponseStreaming/LambdaResponseStream.cs | 123 +++ .../LambdaResponseStreamFactory.cs | 72 ++ .../Amazon.Lambda.Logging.AspNetCore.csproj | 2 +- .../Amazon.Lambda.RuntimeSupport.csproj | 2 +- .../Bootstrap/LambdaBootstrap.cs | 57 +- .../RawStreamingHttpClient.cs | 296 ++++++++ .../ResponseStreaming/ResponseStream.cs | 261 +++++++ .../ResponseStreamContext.cs | 59 ++ .../ResponseStreamFactory.cs | 133 ++++ ...onseStreamLambdaCoreInitializerIsolated.cs | 61 ++ .../ResponseStreaming/StreamingConstants.cs | 43 ++ .../Client/RuntimeApiClient.cs | 29 + .../Helpers/ConsoleLoggerWriter.cs | 5 +- .../ALBApiAttributeTests.cs | 495 ++++++++++++ .../ALBApiModelTests.cs | 272 +++++++ ....Annotations.SourceGenerators.Tests.csproj | 3 + .../CSharpSourceGeneratorVerifier.cs | 4 + .../S3EventAttributeTests.cs | 448 +++++++++++ .../SNSEventAttributeTests.cs | 193 +++++ ...alidALBEvents_HandleRequest_Generated.g.cs | 59 ++ .../ALB/ValidALBEvents_Hello_Generated.g.cs | 59 ++ ...FunctionUrlExample_GetItems_Generated.g.cs | 98 +++ ...Events_ProcessMessagesAsync_Generated.g.cs | 57 ++ ...idSNSEvents_ProcessMessages_Generated.g.cs | 57 ++ .../ServerlessTemplates/albEvents.template | 203 +++++ .../functionUrlExample.template | 31 + .../ServerlessTemplates/snsEvents.template | 93 +++ .../SourceGeneratorTests.cs | 137 ++++ .../WriterTests/ALBEventsTests.cs | 430 +++++++++++ .../WriterTests/FunctionUrlTests.cs | 408 ++++++++++ .../WriterTests/S3EventsTests.cs | 168 +++++ .../WriterTests/SNSEventsTests.cs | 260 +++++++ .../AddAWSLambdaBeforeSnapshotRequestTests.cs | 2 - .../ResponseStreamingHostingTests.cs | 254 +++++++ .../ResponseStreamingPropertyTests.cs | 129 ++++ ...Amazon.Lambda.AspNetCoreServer.Test.csproj | 5 +- .../BuildStreamingPreludeTests.cs | 267 +++++++ .../ResponseStreamingPropertyTests.cs | 478 ++++++++++++ .../StreamingFunctionHandlerAsyncTests.cs | 703 ++++++++++++++++++ .../StreamingResponseBodyFeatureTests.cs | 286 +++++++ .../TestApiGatewayHttpApiV2Calls.cs | 5 - ...zon.Lambda.Logging.AspNetCore.Tests.csproj | 2 +- ...bda.RuntimeSupport.IntegrationTests.csproj | 19 +- .../ApiGatewayStreamingTests.cs | 447 +++++++++++ .../BaseCustomRuntimeTest.cs | 21 +- .../CustomRuntimeTests.cs | 2 +- .../Helpers/CommandLineWrapper.cs | 18 +- .../Helpers/LambdaToolsHelper.cs | 10 +- .../IntegrationTestCollection.cs | 4 +- .../IntegrationTestFixture.cs | 14 +- .../ResponseStreamingTests.cs | 133 ++++ .../HandlerTests.cs | 16 +- .../LambdaBootstrapTests.cs | 163 +++- .../LambdaResponseStreamingCoreTests.cs | 558 ++++++++++++++ .../RawStreamingHttpClientTests.cs | 495 ++++++++++++ .../ResponseStreamFactoryTests.cs | 284 +++++++ .../ResponseStreamTests.cs | 447 +++++++++++ .../RuntimeApiClientTests.cs | 211 ++++++ .../StreamingE2EWithMoq.cs | 545 ++++++++++++++ .../NoOpInternalRuntimeApiClient.cs | 60 ++ .../TestStreamingRuntimeApiClient.cs | 142 ++++ .../AspNetCoreStreamingApiGatewayTest.csproj | 15 + .../Program.cs | 133 ++++ .../aws-lambda-tools-defaults.json | 17 + .../serverless-functionurl.template | 38 + .../serverless-restapi.template | 87 +++ .../Function.cs | 56 ++ .../ResponseStreamingFunctionHandlers.csproj | 19 + .../aws-lambda-tools-defaults.json | 15 + .../CloudFormationHelper.cs | 22 +- .../IntegrationTests.Helpers/LambdaHelper.cs | 11 + .../test/IntegrationTests.Helpers/S3Helper.cs | 8 + .../serverless.template | 2 +- .../serverless.template | 2 +- .../TestMinimalAPIApp.csproj | 2 +- .../ALBIntegrationTestContextFixture.cs | 172 +++++ ...IntegrationTestContextFixtureCollection.cs | 12 + .../ALBTargetTests.cs | 81 ++ .../DeploymentScript.ps1 | 85 +++ ...tServerlessApp.ALB.IntegrationTests.csproj | 20 + .../TestServerlessApp.ALB/ALBFunctions.cs | 109 +++ .../AssemblyAttributes.cs | 6 + .../TestServerlessApp.ALB.csproj | 15 + .../aws-lambda-tools-defaults.json | 17 + .../TestServerlessApp.ALB/serverless.template | 542 ++++++++++++++ .../DeploymentScript.ps1 | 18 + .../FunctionUrlExample.cs | 103 +++ .../IntegrationTestContextFixture.cs | 30 +- .../S3EventNotification.cs | 53 ++ .../SNSEventSubscription.cs | 44 ++ .../TestServerlessApp.IntegrationTests.csproj | 1 + .../serverless.template | 2 +- .../ALBEventExamples/ValidALBEvents.cs.txt | 42 ++ .../TestServerlessApp/FunctionUrlExample.cs | 20 + .../S3EventExamples/S3EventProcessing.cs | 21 + .../S3EventExamples/ValidS3Events.cs.txt | 38 + .../SNSEventExamples/ValidSNSEvents.cs.txt | 32 + .../TestServerlessApp/SnsMessageProcessing.cs | 20 + .../TestServerlessApp.csproj | 2 + .../aws-lambda-tools-defaults.json | 8 +- .../TestServerlessApp/serverless.template | 132 +++- Libraries/test/TestWebApp/TestWebApp.csproj | 2 +- .../BaseApiGatewayTest.cs | 1 + .../SQSEventSourceTests.cs | 2 +- .../Helpers/TestHelpers.cs | 17 +- .../RuntimeApiTests.cs | 4 + ...Amazon.Lambda.TestTool.BlazorTester.csproj | 2 +- ...mbda.TestTool.BlazorTester10_0-pack.csproj | 2 +- ...Lambda.TestTool.BlazorTester80-pack.csproj | 2 +- ...Lambda.TestTool.BlazorTester90-pack.csproj | 2 +- .../Amazon.Lambda.TestTool/TestToolStartup.cs | 27 +- 182 files changed, 16743 insertions(+), 233 deletions(-) create mode 100644 .autover/changes/c27a62e6-91ca-4a59-9406-394866cdfa62.json create mode 100644 .autover/changes/f0d5a912-bcfa-4244-96cb-ac3c847f877c.json create mode 100644 .github/CODEOWNERS create mode 100644 Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Models/Attributes/ALBApiAttributeBuilder.cs create mode 100644 Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Models/Attributes/ALBFromHeaderAttributeBuilder.cs create mode 100644 Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Models/Attributes/ALBFromQueryAttributeBuilder.cs create mode 100644 Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Models/Attributes/FunctionUrlAttributeBuilder.cs create mode 100644 Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Models/Attributes/S3EventAttributeBuilder.cs create mode 100644 Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Models/Attributes/SNSEventAttributeBuilder.cs create mode 100644 Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Templates/ALBInvoke.cs create mode 100644 Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Templates/ALBInvoke.tt create mode 100644 Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Templates/ALBInvokeCode.cs create mode 100644 Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Templates/ALBSetupParameters.cs create mode 100644 Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Templates/ALBSetupParameters.tt create mode 100644 Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Templates/ALBSetupParametersCode.cs create mode 100644 Libraries/src/Amazon.Lambda.Annotations/ALB/ALBApiAttribute.cs create mode 100644 Libraries/src/Amazon.Lambda.Annotations/ALB/FromBodyAttribute.cs create mode 100644 Libraries/src/Amazon.Lambda.Annotations/ALB/FromHeaderAttribute.cs create mode 100644 Libraries/src/Amazon.Lambda.Annotations/ALB/FromQueryAttribute.cs create mode 100644 Libraries/src/Amazon.Lambda.Annotations/APIGateway/FunctionUrlAttribute.cs create mode 100644 Libraries/src/Amazon.Lambda.Annotations/APIGateway/FunctionUrlAuthType.cs create mode 100644 Libraries/src/Amazon.Lambda.Annotations/S3/S3EventAttribute.cs create mode 100644 Libraries/src/Amazon.Lambda.Annotations/SNS/SNSEventAttribute.cs create mode 100644 Libraries/src/Amazon.Lambda.AspNetCoreServer/Internal/StreamingResponseBodyFeature.cs create mode 100644 Libraries/src/Amazon.Lambda.Core/ResponseStreaming/HttpResponseStreamPrelude.cs create mode 100644 Libraries/src/Amazon.Lambda.Core/ResponseStreaming/ILambdaResponseStream.cs create mode 100644 Libraries/src/Amazon.Lambda.Core/ResponseStreaming/LambdaResponseStream.cs create mode 100644 Libraries/src/Amazon.Lambda.Core/ResponseStreaming/LambdaResponseStreamFactory.cs create mode 100644 Libraries/src/Amazon.Lambda.RuntimeSupport/Bootstrap/ResponseStreaming/RawStreamingHttpClient.cs create mode 100644 Libraries/src/Amazon.Lambda.RuntimeSupport/Bootstrap/ResponseStreaming/ResponseStream.cs create mode 100644 Libraries/src/Amazon.Lambda.RuntimeSupport/Bootstrap/ResponseStreaming/ResponseStreamContext.cs create mode 100644 Libraries/src/Amazon.Lambda.RuntimeSupport/Bootstrap/ResponseStreaming/ResponseStreamFactory.cs create mode 100644 Libraries/src/Amazon.Lambda.RuntimeSupport/Bootstrap/ResponseStreaming/ResponseStreamLambdaCoreInitializerIsolated.cs create mode 100644 Libraries/src/Amazon.Lambda.RuntimeSupport/Bootstrap/ResponseStreaming/StreamingConstants.cs create mode 100644 Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/ALBApiAttributeTests.cs create mode 100644 Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/ALBApiModelTests.cs create mode 100644 Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/S3EventAttributeTests.cs create mode 100644 Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/SNSEventAttributeTests.cs create mode 100644 Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/ALB/ValidALBEvents_HandleRequest_Generated.g.cs create mode 100644 Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/ALB/ValidALBEvents_Hello_Generated.g.cs create mode 100644 Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/FunctionUrlExample_GetItems_Generated.g.cs create mode 100644 Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/SNS/ValidSNSEvents_ProcessMessagesAsync_Generated.g.cs create mode 100644 Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/SNS/ValidSNSEvents_ProcessMessages_Generated.g.cs create mode 100644 Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/ServerlessTemplates/albEvents.template create mode 100644 Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/ServerlessTemplates/functionUrlExample.template create mode 100644 Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/ServerlessTemplates/snsEvents.template create mode 100644 Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/WriterTests/ALBEventsTests.cs create mode 100644 Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/WriterTests/FunctionUrlTests.cs create mode 100644 Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/WriterTests/S3EventsTests.cs create mode 100644 Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/WriterTests/SNSEventsTests.cs create mode 100644 Libraries/test/Amazon.Lambda.AspNetCoreServer.Hosting.Tests/ResponseStreamingHostingTests.cs create mode 100644 Libraries/test/Amazon.Lambda.AspNetCoreServer.Hosting.Tests/ResponseStreamingPropertyTests.cs create mode 100644 Libraries/test/Amazon.Lambda.AspNetCoreServer.Test/BuildStreamingPreludeTests.cs create mode 100644 Libraries/test/Amazon.Lambda.AspNetCoreServer.Test/ResponseStreamingPropertyTests.cs create mode 100644 Libraries/test/Amazon.Lambda.AspNetCoreServer.Test/StreamingFunctionHandlerAsyncTests.cs create mode 100644 Libraries/test/Amazon.Lambda.AspNetCoreServer.Test/StreamingResponseBodyFeatureTests.cs create mode 100644 Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.IntegrationTests/ApiGatewayStreamingTests.cs create mode 100644 Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.IntegrationTests/ResponseStreamingTests.cs create mode 100644 Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/LambdaResponseStreamingCoreTests.cs create mode 100644 Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/RawStreamingHttpClientTests.cs create mode 100644 Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/ResponseStreamFactoryTests.cs create mode 100644 Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/ResponseStreamTests.cs create mode 100644 Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/RuntimeApiClientTests.cs create mode 100644 Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/StreamingE2EWithMoq.cs create mode 100644 Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/TestHelpers/NoOpInternalRuntimeApiClient.cs create mode 100644 Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/TestHelpers/TestStreamingRuntimeApiClient.cs create mode 100644 Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/AspNetCoreStreamingApiGatewayTest/AspNetCoreStreamingApiGatewayTest.csproj create mode 100644 Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/AspNetCoreStreamingApiGatewayTest/Program.cs create mode 100644 Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/AspNetCoreStreamingApiGatewayTest/aws-lambda-tools-defaults.json create mode 100644 Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/AspNetCoreStreamingApiGatewayTest/serverless-functionurl.template create mode 100644 Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/AspNetCoreStreamingApiGatewayTest/serverless-restapi.template create mode 100644 Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/ResponseStreamingFunctionHandlers/Function.cs create mode 100644 Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/ResponseStreamingFunctionHandlers/ResponseStreamingFunctionHandlers.csproj create mode 100644 Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/ResponseStreamingFunctionHandlers/aws-lambda-tools-defaults.json create mode 100644 Libraries/test/TestServerlessApp.ALB.IntegrationTests/ALBIntegrationTestContextFixture.cs create mode 100644 Libraries/test/TestServerlessApp.ALB.IntegrationTests/ALBIntegrationTestContextFixtureCollection.cs create mode 100644 Libraries/test/TestServerlessApp.ALB.IntegrationTests/ALBTargetTests.cs create mode 100644 Libraries/test/TestServerlessApp.ALB.IntegrationTests/DeploymentScript.ps1 create mode 100644 Libraries/test/TestServerlessApp.ALB.IntegrationTests/TestServerlessApp.ALB.IntegrationTests.csproj create mode 100644 Libraries/test/TestServerlessApp.ALB/ALBFunctions.cs create mode 100644 Libraries/test/TestServerlessApp.ALB/AssemblyAttributes.cs create mode 100644 Libraries/test/TestServerlessApp.ALB/TestServerlessApp.ALB.csproj create mode 100644 Libraries/test/TestServerlessApp.ALB/aws-lambda-tools-defaults.json create mode 100644 Libraries/test/TestServerlessApp.ALB/serverless.template create mode 100644 Libraries/test/TestServerlessApp.IntegrationTests/FunctionUrlExample.cs create mode 100644 Libraries/test/TestServerlessApp.IntegrationTests/S3EventNotification.cs create mode 100644 Libraries/test/TestServerlessApp.IntegrationTests/SNSEventSubscription.cs create mode 100644 Libraries/test/TestServerlessApp/ALBEventExamples/ValidALBEvents.cs.txt create mode 100644 Libraries/test/TestServerlessApp/FunctionUrlExample.cs create mode 100644 Libraries/test/TestServerlessApp/S3EventExamples/S3EventProcessing.cs create mode 100644 Libraries/test/TestServerlessApp/S3EventExamples/ValidS3Events.cs.txt create mode 100644 Libraries/test/TestServerlessApp/SNSEventExamples/ValidSNSEvents.cs.txt create mode 100644 Libraries/test/TestServerlessApp/SnsMessageProcessing.cs diff --git a/.autover/changes/c27a62e6-91ca-4a59-9406-394866cdfa62.json b/.autover/changes/c27a62e6-91ca-4a59-9406-394866cdfa62.json new file mode 100644 index 000000000..39be8933f --- /dev/null +++ b/.autover/changes/c27a62e6-91ca-4a59-9406-394866cdfa62.json @@ -0,0 +1,18 @@ +{ + "Projects": [ + { + "Name": "Amazon.Lambda.RuntimeSupport", + "Type": "Minor", + "ChangelogMessages": [ + "(Preview) Add response streaming support" + ] + }, + { + "Name": "Amazon.Lambda.Core", + "Type": "Minor", + "ChangelogMessages": [ + "(Preview) Add response streaming support" + ] + } + ] +} diff --git a/.autover/changes/f0d5a912-bcfa-4244-96cb-ac3c847f877c.json b/.autover/changes/f0d5a912-bcfa-4244-96cb-ac3c847f877c.json new file mode 100644 index 000000000..f8dad2d13 --- /dev/null +++ b/.autover/changes/f0d5a912-bcfa-4244-96cb-ac3c847f877c.json @@ -0,0 +1,27 @@ +{ + "Projects": [ + { + "Name": "Amazon.Lambda.AspNetCoreServer", + "Type": "Major", + "ChangelogMessages": [ + "[Breaking] Update build targets from .NET 6 and 8 to .NET 8 and 10", + "[Preview] Add support for Lambda Response Streaming enabled by setting the EnableResponseStreaming property from the base class AbstractAspNetCoreFunction" + ] + }, + { + "Name": "Amazon.Lambda.AspNetCoreServer.Hosting", + "Type": "Major", + "ChangelogMessages": [ + "[Breaking] Update build targets from .NET 6 and 8 to .NET 8 and 10", + "[Preview] Add support for Lambda Response Streaming enabled by setting the EnableResponseStreaming property on the HostingOptions object passed into the AddAWSLambdaHosting method" + ] + }, + { + "Name": "Amazon.Lambda.Logging.AspNetCore", + "Type": "Major", + "ChangelogMessages": [ + "[Breaking] Update build targets from .NET 6 and 8 to .NET 8 and 10" + ] + } + ] +} \ No newline at end of file diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 000000000..62b77bced --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1 @@ +* @aws/aws-sdk-dotnet-team diff --git a/.gitignore b/.gitignore index f91715274..1caae6fe4 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,8 @@ *.suo *.user +**/.kiro/ + #################### # Build/Test folders #################### diff --git a/CHANGELOG.md b/CHANGELOG.md index 704ea1265..134cf9e72 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,28 @@ +## Release 2026-04-16 + +### Amazon.Lambda.Annotations (1.14.0) +* Added [SNSEvent] annotation attribute for declaratively configuring SNS topic-triggered Lambda functions with support for topic reference, filter policy, and enabled state. + +## Release 2026-04-14 + +### Amazon.Lambda.TestTool.BlazorTester (0.17.1) +* Minor fixes to improve the testability of the package +### Amazon.Lambda.RuntimeSupport (1.14.3) +* Minor fixes to improve the testability of the package +### Amazon.Lambda.Annotations (1.13.0) +* Added [FunctionUrl] attribute for configuring Lambda functions with Function URL endpoints, including optional CORS support + +## Release 2026-04-13 #2 + +### Amazon.Lambda.Annotations (1.12.0) +* treat warnings as errors and fix unshipped.md +* Added [S3Event] annotation attribute for declaratively configuring S3 event-triggered Lambda functions with support for bucket reference, event types, key prefix/suffix filters, and enabled state. + +## Release 2026-04-08 + +### Amazon.Lambda.Annotations (1.11.0) +* Added [ALBApi] attribute for configuring Lambda functions as targets behind an Application Load Balancer + ## Release 2026-03-27 ### Amazon.Lambda.Annotations (1.10.0) diff --git a/LambdaRuntimeDockerfiles/Images/net10/amd64/Dockerfile b/LambdaRuntimeDockerfiles/Images/net10/amd64/Dockerfile index 6b08ef4cb..a1d477249 100644 --- a/LambdaRuntimeDockerfiles/Images/net10/amd64/Dockerfile +++ b/LambdaRuntimeDockerfiles/Images/net10/amd64/Dockerfile @@ -1,7 +1,7 @@ # Based on Docker image from: https://github.com/dotnet/dotnet-docker/ -ARG ASPNET_VERSION=10.0.5 -ARG ASPNET_SHA512=7108ecdda8e2607fa80e2b45f1209d7af5301d53438b65d2269605b8415aebd49db23455d8dcd77d8fdccc904c9202b4834f9ca2e00e27a501d2006174d76cc4 +ARG ASPNET_VERSION=10.0.6 +ARG ASPNET_SHA512=89eeb16d1971dc0a854754a3bc4cebb637a959c889f56216af292580b76fbff01c329dd0933896b74ef6a2d4bc56b9c4bb605196d5c0520ca43027421d155365 ARG LAMBDA_RUNTIME_NAME=dotnet10 ARG AMAZON_LINUX=public.ecr.aws/lambda/provided:al2023 diff --git a/LambdaRuntimeDockerfiles/Images/net10/arm64/Dockerfile b/LambdaRuntimeDockerfiles/Images/net10/arm64/Dockerfile index 773f877f6..9d0134268 100644 --- a/LambdaRuntimeDockerfiles/Images/net10/arm64/Dockerfile +++ b/LambdaRuntimeDockerfiles/Images/net10/arm64/Dockerfile @@ -1,7 +1,7 @@ # Based on Docker image from: https://github.com/dotnet/dotnet-docker/ -ARG ASPNET_VERSION=10.0.5 -ARG ASPNET_SHA512=6cab3b81910ba3e6e118595a45948331f5d1506b42af0942f79ea3db6623e820557a1757973becb9afd3d6f8ead9e9a641667860f2a7fbbd598bcafa38f4739c +ARG ASPNET_VERSION=10.0.6 +ARG ASPNET_SHA512=8dee4e65a4a76e833867616df508a39c2f8a710269a2361e47acb73d09080e50405c1d84206327ff5df127a9f206451b0cedf1451d43144e440d9c4e350e9497 ARG LAMBDA_RUNTIME_NAME=dotnet10 ARG AMAZON_LINUX=public.ecr.aws/lambda/provided:al2023 diff --git a/LambdaRuntimeDockerfiles/Images/net11/amd64/Dockerfile b/LambdaRuntimeDockerfiles/Images/net11/amd64/Dockerfile index df5a89084..0630f9077 100644 --- a/LambdaRuntimeDockerfiles/Images/net11/amd64/Dockerfile +++ b/LambdaRuntimeDockerfiles/Images/net11/amd64/Dockerfile @@ -1,7 +1,7 @@ # Based on Docker image from: https://github.com/dotnet/dotnet-docker/ -ARG ASPNET_VERSION=11.0.0-preview.2.26159.112 -ARG ASPNET_SHA512=4c02fbb66bc4b7389e0f43c0ea96a3046954eb3c7ad06bf0f8d90997c2e603dd0a3929e2b29b9e8933cc76341fb5e62ebe5bcb83d9a31419bdd1195904ff5af6 +ARG ASPNET_VERSION=11.0.0-preview.3.26207.106 +ARG ASPNET_SHA512=61694de0e8f7ac4c5daa03a6a3edbf99241a9dae63d9c02153d91df9df45b3fdb2d5412030d6e52f52c27582d636ad916b54ff89a7578de7246b773bfd764c06 ARG LAMBDA_RUNTIME_NAME=dotnet11 ARG AMAZON_LINUX=public.ecr.aws/lambda/provided:al2023 diff --git a/LambdaRuntimeDockerfiles/Images/net11/arm64/Dockerfile b/LambdaRuntimeDockerfiles/Images/net11/arm64/Dockerfile index 9f150d25b..593a9db9a 100644 --- a/LambdaRuntimeDockerfiles/Images/net11/arm64/Dockerfile +++ b/LambdaRuntimeDockerfiles/Images/net11/arm64/Dockerfile @@ -1,7 +1,7 @@ # Based on Docker image from: https://github.com/dotnet/dotnet-docker/ -ARG ASPNET_VERSION=11.0.0-preview.2.26159.112 -ARG ASPNET_SHA512=711914b72530c8b6ba49e5077942893a52bf5508cc082b09ae04f5c72ae4a09dd78699ffcad16dd25a2fe43d586533f897dcdb5c82ab2982ff6b4ad6fdfe5a58 +ARG ASPNET_VERSION=11.0.0-preview.3.26207.106 +ARG ASPNET_SHA512=ef06c925082be5bc6ad0fec0ab33cfe4795f96d5ae253df930e7ccffa017643edc625c27fc2d2619e694f9bbf678e431ab9d614362aa616e8fc18c8e44d6c31c ARG LAMBDA_RUNTIME_NAME=dotnet11 ARG AMAZON_LINUX=public.ecr.aws/lambda/provided:al2023 diff --git a/LambdaRuntimeDockerfiles/Images/net8/amd64/Dockerfile b/LambdaRuntimeDockerfiles/Images/net8/amd64/Dockerfile index 3fa341885..f11a52f0b 100644 --- a/LambdaRuntimeDockerfiles/Images/net8/amd64/Dockerfile +++ b/LambdaRuntimeDockerfiles/Images/net8/amd64/Dockerfile @@ -1,7 +1,7 @@ # Based on Docker image from: https://github.com/dotnet/dotnet-docker/ -ARG ASPNET_VERSION=8.0.25 -ARG ASPNET_SHA512=ddb66ac366252ab382271241b3e53a75201d2c848c9ec870a27fb178a6db18e4d949b9896a3d8530d03d255f4fd51d635367bedda3d9f3c677cb596784dbcb9c +ARG ASPNET_VERSION=8.0.26 +ARG ASPNET_SHA512=2a5d39bfdb2d734fd765f806bf4be6138bc8d9b44f2f8aee3250ced56eaad4c0e4bf06141dec6ef2c6a46a8faf3d8ffd60b7361b687ff7d8b45179df35bb0149 ARG LAMBDA_RUNTIME_NAME=dotnet8 ARG AMAZON_LINUX=public.ecr.aws/lambda/provided:al2023 diff --git a/LambdaRuntimeDockerfiles/Images/net8/arm64/Dockerfile b/LambdaRuntimeDockerfiles/Images/net8/arm64/Dockerfile index 7180c4277..a21fb97fe 100644 --- a/LambdaRuntimeDockerfiles/Images/net8/arm64/Dockerfile +++ b/LambdaRuntimeDockerfiles/Images/net8/arm64/Dockerfile @@ -1,7 +1,7 @@ # Based on Docker image from: https://github.com/dotnet/dotnet-docker/ -ARG ASPNET_VERSION=8.0.25 -ARG ASPNET_SHA512=65d8b16bbef90c44daac906aaf92818f43a8482191dbf3f20bddcdd1ad6077d17b6178364bd08249501ddd3021a4c8f5f98a1c0360e126870db14cf06cd12727 +ARG ASPNET_VERSION=8.0.26 +ARG ASPNET_SHA512=93969f80c4bffa276a5538a5133e20ea8afd59b7f83f932cd0749ff37332f85ca3e3e9e90fb45ec8e2fd1a814df373b617565808823113e2911f5b55d018d19c ARG LAMBDA_RUNTIME_NAME=dotnet8 ARG AMAZON_LINUX=public.ecr.aws/lambda/provided:al2023 diff --git a/LambdaRuntimeDockerfiles/Images/net9/amd64/Dockerfile b/LambdaRuntimeDockerfiles/Images/net9/amd64/Dockerfile index 6fcff677b..0dbd59f2b 100644 --- a/LambdaRuntimeDockerfiles/Images/net9/amd64/Dockerfile +++ b/LambdaRuntimeDockerfiles/Images/net9/amd64/Dockerfile @@ -1,7 +1,7 @@ # Based on Docker image from: https://github.com/dotnet/dotnet-docker/ -ARG ASPNET_VERSION=9.0.14 -ARG ASPNET_SHA512=6d0947390f9ec316297f21a9ec022528a27205a44328f9417fb8675783edc56022e5e15ea65650c5e73cb73917c09c52305d3e85e4c12acd5e11074871bb0679 +ARG ASPNET_VERSION=9.0.15 +ARG ASPNET_SHA512=85d474b303fb8f1867125d9aa1ec39db5afff179b165287e45fd78b7ec51d414e5937871bda5948b8e750afe9c220afe6138eba7108090bfba1e5dad93d39c4e ARG LAMBDA_RUNTIME_NAME=dotnet9 ARG AMAZON_LINUX=public.ecr.aws/lambda/provided:al2023 diff --git a/LambdaRuntimeDockerfiles/Images/net9/arm64/Dockerfile b/LambdaRuntimeDockerfiles/Images/net9/arm64/Dockerfile index 2294235c1..da4fd7fd0 100644 --- a/LambdaRuntimeDockerfiles/Images/net9/arm64/Dockerfile +++ b/LambdaRuntimeDockerfiles/Images/net9/arm64/Dockerfile @@ -1,7 +1,7 @@ # Based on Docker image from: https://github.com/dotnet/dotnet-docker/ -ARG ASPNET_VERSION=9.0.14 -ARG ASPNET_SHA512=c5cd05971c9cba0c211ceb55e226cbfb62f172db8345fa5d4e476a4ccb3a4f61d6e51dc2b2d178eb55f3673ad2d61cf72ae649fb84ccf2c3dbbbd4acdbb78132 +ARG ASPNET_VERSION=9.0.15 +ARG ASPNET_SHA512=72b9f4b0649e5a3b874597c1b50dbbb246831e933455bbe34c3c53ffdd3016eca7d42090c6d785bb9dae78b9f34d37757df2b430f6309d811c08df6839ce00cf ARG LAMBDA_RUNTIME_NAME=dotnet9 ARG AMAZON_LINUX=public.ecr.aws/lambda/provided:al2023 diff --git a/Libraries/Amazon.Lambda.Annotations.slnf b/Libraries/Amazon.Lambda.Annotations.slnf index ecb4e01ee..d0bf67584 100644 --- a/Libraries/Amazon.Lambda.Annotations.slnf +++ b/Libraries/Amazon.Lambda.Annotations.slnf @@ -16,7 +16,10 @@ "test\\TestCustomAuthorizerApp.IntegrationTests\\TestCustomAuthorizerApp.IntegrationTests.csproj", "test\\TestServerlessApp.IntegrationTests\\TestServerlessApp.IntegrationTests.csproj", "test\\TestServerlessApp.NET8\\TestServerlessApp.NET8.csproj", - "test\\TestServerlessApp\\TestServerlessApp.csproj" + "src\\Amazon.Lambda.ApplicationLoadBalancerEvents\\Amazon.Lambda.ApplicationLoadBalancerEvents.csproj", + "test\\TestServerlessApp\\TestServerlessApp.csproj", + "test\\TestServerlessApp.ALB\\TestServerlessApp.ALB.csproj", + "test\\TestServerlessApp.ALB.IntegrationTests\\TestServerlessApp.ALB.IntegrationTests.csproj" ] } } diff --git a/Libraries/Amazon.Lambda.RuntimeSupport.slnf b/Libraries/Amazon.Lambda.RuntimeSupport.slnf index fb03ebc05..cd6d74977 100644 --- a/Libraries/Amazon.Lambda.RuntimeSupport.slnf +++ b/Libraries/Amazon.Lambda.RuntimeSupport.slnf @@ -14,12 +14,13 @@ "src\\SnapshotRestore.Registry\\SnapshotRestore.Registry.csproj", "test\\Amazon.Lambda.RuntimeSupport.Tests\\Amazon.Lambda.RuntimeSupport.IntegrationTests\\Amazon.Lambda.RuntimeSupport.IntegrationTests.csproj", "test\\Amazon.Lambda.RuntimeSupport.Tests\\Amazon.Lambda.RuntimeSupport.UnitTests\\Amazon.Lambda.RuntimeSupport.UnitTests.csproj", + "test\\Amazon.Lambda.RuntimeSupport.Tests\\AspNetCoreStreamingApiGatewayTest\\AspNetCoreStreamingApiGatewayTest.csproj", "test\\Amazon.Lambda.RuntimeSupport.Tests\\CustomRuntimeAspNetCoreMinimalApiCustomSerializerTest\\CustomRuntimeAspNetCoreMinimalApiCustomSerializerTest.csproj", "test\\Amazon.Lambda.RuntimeSupport.Tests\\CustomRuntimeAspNetCoreMinimalApiTest\\CustomRuntimeAspNetCoreMinimalApiTest.csproj", "test\\Amazon.Lambda.RuntimeSupport.Tests\\CustomRuntimeFunctionTest\\CustomRuntimeFunctionTest.csproj", - "test\\SnapshotRestore.Registry.Tests\\SnapshotRestore.Registry.Tests.csproj", "test\\HandlerTestNoSerializer\\HandlerTestNoSerializer.csproj", - "test\\HandlerTest\\HandlerTest.csproj" + "test\\HandlerTest\\HandlerTest.csproj", + "test\\SnapshotRestore.Registry.Tests\\SnapshotRestore.Registry.Tests.csproj" ] } } \ No newline at end of file diff --git a/Libraries/Libraries.sln b/Libraries/Libraries.sln index f3214606a..3fac89872 100644 --- a/Libraries/Libraries.sln +++ b/Libraries/Libraries.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.0.31717.71 +# Visual Studio Version 18 +VisualStudioVersion = 18.3.11512.155 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{AAB54E74-20B1-42ED-BC3D-CE9F7BC7FD12}" EndProject @@ -151,6 +151,14 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestCustomAuthorizerApp.Int EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestCustomAuthorizerApp", "test\TestCustomAuthorizerApp\TestCustomAuthorizerApp.csproj", "{3BFA4B73-BA61-4578-833B-C5B3A16EDA9E}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestServerlessApp.ALB", "test\TestServerlessApp.ALB\TestServerlessApp.ALB.csproj", "{8F7C617D-C611-4DC6-A07C-033F13C1835D}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestServerlessApp.ALB.IntegrationTests", "test\TestServerlessApp.ALB.IntegrationTests\TestServerlessApp.ALB.IntegrationTests.csproj", "{80594C21-C6EB-469E-83CC-68F9F661CA5E}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ResponseStreamingFunctionHandlers", "test\Amazon.Lambda.RuntimeSupport.Tests\ResponseStreamingFunctionHandlers\ResponseStreamingFunctionHandlers.csproj", "{E404A7AC-812B-BC03-CA76-02C0BC2BA7F9}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AspNetCoreStreamingApiGatewayTest", "test\Amazon.Lambda.RuntimeSupport.Tests\AspNetCoreStreamingApiGatewayTest\AspNetCoreStreamingApiGatewayTest.csproj", "{0768FA72-CF49-2B59-BC4C-E4CE579E5D93}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -941,6 +949,54 @@ Global {3BFA4B73-BA61-4578-833B-C5B3A16EDA9E}.Release|x64.Build.0 = Release|Any CPU {3BFA4B73-BA61-4578-833B-C5B3A16EDA9E}.Release|x86.ActiveCfg = Release|Any CPU {3BFA4B73-BA61-4578-833B-C5B3A16EDA9E}.Release|x86.Build.0 = Release|Any CPU + {8F7C617D-C611-4DC6-A07C-033F13C1835D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8F7C617D-C611-4DC6-A07C-033F13C1835D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8F7C617D-C611-4DC6-A07C-033F13C1835D}.Debug|x64.ActiveCfg = Debug|Any CPU + {8F7C617D-C611-4DC6-A07C-033F13C1835D}.Debug|x64.Build.0 = Debug|Any CPU + {8F7C617D-C611-4DC6-A07C-033F13C1835D}.Debug|x86.ActiveCfg = Debug|Any CPU + {8F7C617D-C611-4DC6-A07C-033F13C1835D}.Debug|x86.Build.0 = Debug|Any CPU + {8F7C617D-C611-4DC6-A07C-033F13C1835D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8F7C617D-C611-4DC6-A07C-033F13C1835D}.Release|Any CPU.Build.0 = Release|Any CPU + {8F7C617D-C611-4DC6-A07C-033F13C1835D}.Release|x64.ActiveCfg = Release|Any CPU + {8F7C617D-C611-4DC6-A07C-033F13C1835D}.Release|x64.Build.0 = Release|Any CPU + {8F7C617D-C611-4DC6-A07C-033F13C1835D}.Release|x86.ActiveCfg = Release|Any CPU + {8F7C617D-C611-4DC6-A07C-033F13C1835D}.Release|x86.Build.0 = Release|Any CPU + {80594C21-C6EB-469E-83CC-68F9F661CA5E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {80594C21-C6EB-469E-83CC-68F9F661CA5E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {80594C21-C6EB-469E-83CC-68F9F661CA5E}.Debug|x64.ActiveCfg = Debug|Any CPU + {80594C21-C6EB-469E-83CC-68F9F661CA5E}.Debug|x64.Build.0 = Debug|Any CPU + {80594C21-C6EB-469E-83CC-68F9F661CA5E}.Debug|x86.ActiveCfg = Debug|Any CPU + {80594C21-C6EB-469E-83CC-68F9F661CA5E}.Debug|x86.Build.0 = Debug|Any CPU + {80594C21-C6EB-469E-83CC-68F9F661CA5E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {80594C21-C6EB-469E-83CC-68F9F661CA5E}.Release|Any CPU.Build.0 = Release|Any CPU + {80594C21-C6EB-469E-83CC-68F9F661CA5E}.Release|x64.ActiveCfg = Release|Any CPU + {80594C21-C6EB-469E-83CC-68F9F661CA5E}.Release|x64.Build.0 = Release|Any CPU + {80594C21-C6EB-469E-83CC-68F9F661CA5E}.Release|x86.ActiveCfg = Release|Any CPU + {80594C21-C6EB-469E-83CC-68F9F661CA5E}.Release|x86.Build.0 = Release|Any CPU + {E404A7AC-812B-BC03-CA76-02C0BC2BA7F9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E404A7AC-812B-BC03-CA76-02C0BC2BA7F9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E404A7AC-812B-BC03-CA76-02C0BC2BA7F9}.Debug|x64.ActiveCfg = Debug|Any CPU + {E404A7AC-812B-BC03-CA76-02C0BC2BA7F9}.Debug|x64.Build.0 = Debug|Any CPU + {E404A7AC-812B-BC03-CA76-02C0BC2BA7F9}.Debug|x86.ActiveCfg = Debug|Any CPU + {E404A7AC-812B-BC03-CA76-02C0BC2BA7F9}.Debug|x86.Build.0 = Debug|Any CPU + {E404A7AC-812B-BC03-CA76-02C0BC2BA7F9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E404A7AC-812B-BC03-CA76-02C0BC2BA7F9}.Release|Any CPU.Build.0 = Release|Any CPU + {E404A7AC-812B-BC03-CA76-02C0BC2BA7F9}.Release|x64.ActiveCfg = Release|Any CPU + {E404A7AC-812B-BC03-CA76-02C0BC2BA7F9}.Release|x64.Build.0 = Release|Any CPU + {E404A7AC-812B-BC03-CA76-02C0BC2BA7F9}.Release|x86.ActiveCfg = Release|Any CPU + {E404A7AC-812B-BC03-CA76-02C0BC2BA7F9}.Release|x86.Build.0 = Release|Any CPU + {0768FA72-CF49-2B59-BC4C-E4CE579E5D93}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0768FA72-CF49-2B59-BC4C-E4CE579E5D93}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0768FA72-CF49-2B59-BC4C-E4CE579E5D93}.Debug|x64.ActiveCfg = Debug|Any CPU + {0768FA72-CF49-2B59-BC4C-E4CE579E5D93}.Debug|x64.Build.0 = Debug|Any CPU + {0768FA72-CF49-2B59-BC4C-E4CE579E5D93}.Debug|x86.ActiveCfg = Debug|Any CPU + {0768FA72-CF49-2B59-BC4C-E4CE579E5D93}.Debug|x86.Build.0 = Debug|Any CPU + {0768FA72-CF49-2B59-BC4C-E4CE579E5D93}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0768FA72-CF49-2B59-BC4C-E4CE579E5D93}.Release|Any CPU.Build.0 = Release|Any CPU + {0768FA72-CF49-2B59-BC4C-E4CE579E5D93}.Release|x64.ActiveCfg = Release|Any CPU + {0768FA72-CF49-2B59-BC4C-E4CE579E5D93}.Release|x64.Build.0 = Release|Any CPU + {0768FA72-CF49-2B59-BC4C-E4CE579E5D93}.Release|x86.ActiveCfg = Release|Any CPU + {0768FA72-CF49-2B59-BC4C-E4CE579E5D93}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -1015,6 +1071,10 @@ Global {8D03BDF3-7078-4B46-A3F1-C73BE6D6CE0D} = {1DE4EE60-45BA-4EF7-BE00-B9EB861E4C69} {8EEDD576-7FC4-4FAC-A5A2-F58562753A53} = {1DE4EE60-45BA-4EF7-BE00-B9EB861E4C69} {3BFA4B73-BA61-4578-833B-C5B3A16EDA9E} = {1DE4EE60-45BA-4EF7-BE00-B9EB861E4C69} + {8F7C617D-C611-4DC6-A07C-033F13C1835D} = {1DE4EE60-45BA-4EF7-BE00-B9EB861E4C69} + {80594C21-C6EB-469E-83CC-68F9F661CA5E} = {1DE4EE60-45BA-4EF7-BE00-B9EB861E4C69} + {E404A7AC-812B-BC03-CA76-02C0BC2BA7F9} = {B5BD0336-7D08-492C-8489-42C987E29B39} + {0768FA72-CF49-2B59-BC4C-E4CE579E5D93} = {B5BD0336-7D08-492C-8489-42C987E29B39} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {503678A4-B8D1-4486-8915-405A3E9CF0EB} diff --git a/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Amazon.Lambda.Annotations.SourceGenerator.csproj b/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Amazon.Lambda.Annotations.SourceGenerator.csproj index 3e2dd821e..925cb3715 100644 --- a/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Amazon.Lambda.Annotations.SourceGenerator.csproj +++ b/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Amazon.Lambda.Annotations.SourceGenerator.csproj @@ -20,7 +20,8 @@ true false - 1.10.0 + 1.14.0 + true diff --git a/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Diagnostics/AnalyzerReleases.Unshipped.md b/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Diagnostics/AnalyzerReleases.Unshipped.md index e9b44dd1e..dc97dd7de 100644 --- a/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Diagnostics/AnalyzerReleases.Unshipped.md +++ b/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Diagnostics/AnalyzerReleases.Unshipped.md @@ -16,3 +16,9 @@ AWSLambda0128 | AWSLambdaCSharpGenerator | Error | Authorizer Payload Version Mi AWSLambda0129 | AWSLambdaCSharpGenerator | Error | Missing LambdaFunction Attribute AWSLambda0130 | AWSLambdaCSharpGenerator | Error | Invalid return type IAuthorizerResult AWSLambda0131 | AWSLambdaCSharpGenerator | Error | FromBody not supported on Authorizer functions +AWSLambda0132 | AWSLambdaCSharpGenerator | Error | Invalid ALBApiAttribute +AWSLambda0133 | AWSLambdaCSharpGenerator | Error | ALB Listener Reference Not Found +AWSLambda0134 | AWSLambdaCSharpGenerator | Error | FromRoute not supported on ALB functions +AWSLambda0135 | AWSLambdaCSharpGenerator | Error | Unmapped parameter on ALB function +AWSLambda0136 | AWSLambdaCSharpGenerator | Error | Invalid S3EventAttribute +AWSLambda0138 | AWSLambdaCSharpGenerator | Error | Invalid SNSEventAttribute diff --git a/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Diagnostics/DiagnosticDescriptors.cs b/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Diagnostics/DiagnosticDescriptors.cs index 69c4f9428..569fd4116 100644 --- a/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Diagnostics/DiagnosticDescriptors.cs +++ b/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Diagnostics/DiagnosticDescriptors.cs @@ -242,5 +242,51 @@ public static class DiagnosticDescriptors category: "AWSLambdaCSharpGenerator", DiagnosticSeverity.Error, isEnabledByDefault: true); + + public static readonly DiagnosticDescriptor InvalidAlbApiAttribute = new DiagnosticDescriptor( + id: "AWSLambda0132", + title: "Invalid ALBApiAttribute", + messageFormat: "Invalid ALBApiAttribute encountered: {0}", + category: "AWSLambdaCSharpGenerator", + DiagnosticSeverity.Error, + isEnabledByDefault: true); + + public static readonly DiagnosticDescriptor AlbListenerReferenceNotFound = new DiagnosticDescriptor( + id: "AWSLambda0133", + title: "ALB Listener Reference Not Found", + messageFormat: "The ALBApi ListenerArn references '@{0}', but no resource or parameter named '{0}' was found in the CloudFormation template. Add the listener resource to the template or correct the reference name.", + category: "AWSLambdaCSharpGenerator", + DiagnosticSeverity.Error, + isEnabledByDefault: true); + + public static readonly DiagnosticDescriptor FromRouteNotSupportedOnAlb = new DiagnosticDescriptor( + id: "AWSLambda0134", + title: "FromRoute not supported on ALB functions", + messageFormat: "[FromRoute] is not supported on ALB functions. ALB does not support route path template parameters. Use [FromHeader], [FromQuery], or [FromBody] instead.", + category: "AWSLambdaCSharpGenerator", + DiagnosticSeverity.Error, + isEnabledByDefault: true); + + public static readonly DiagnosticDescriptor AlbUnmappedParameter = new DiagnosticDescriptor( + id: "AWSLambda0135", + title: "Unmapped parameter on ALB function", + messageFormat: "Parameter '{0}' on ALB function has no binding attribute. Use [FromHeader], [FromQuery], [FromBody], or [FromServices], or use the ApplicationLoadBalancerRequest or ILambdaContext types.", + category: "AWSLambdaCSharpGenerator", + DiagnosticSeverity.Error, + isEnabledByDefault: true); + + public static readonly DiagnosticDescriptor InvalidS3EventAttribute = new DiagnosticDescriptor(id: "AWSLambda0136", + title: "Invalid S3EventAttribute", + messageFormat: "Invalid S3EventAttribute encountered: {0}", + category: "AWSLambdaCSharpGenerator", + DiagnosticSeverity.Error, + isEnabledByDefault: true); + + public static readonly DiagnosticDescriptor InvalidSnsEventAttribute = new DiagnosticDescriptor(id: "AWSLambda0138", + title: "Invalid SNSEventAttribute", + messageFormat: "Invalid SNSEventAttribute encountered: {0}", + category: "AWSLambdaCSharpGenerator", + DiagnosticSeverity.Error, + isEnabledByDefault: true); } } diff --git a/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Extensions/ParameterListExtension.cs b/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Extensions/ParameterListExtension.cs index 5465f8323..9310019eb 100644 --- a/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Extensions/ParameterListExtension.cs +++ b/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Extensions/ParameterListExtension.cs @@ -17,6 +17,12 @@ public static bool HasConvertibleParameter(this IList parameters return false; } + // ALB request types are forwarded to lambda method if specified, there is no parameter conversion required. + if (TypeFullNames.ALBRequests.Contains(p.Type.FullName)) + { + return false; + } + // ILambdaContext is forwarded to lambda method if specified, there is no parameter conversion required. if (p.Type.FullName == TypeFullNames.ILambdaContext) { @@ -24,7 +30,7 @@ public static bool HasConvertibleParameter(this IList parameters } // Body parameter with target type as string doesn't require conversion because body is string by nature. - if (p.Attributes.Any(att => att.Type.FullName == TypeFullNames.FromBodyAttribute) && p.Type.IsString()) + if (p.Attributes.Any(att => att.Type.FullName == TypeFullNames.FromBodyAttribute || att.Type.FullName == TypeFullNames.ALBFromBodyAttribute) && p.Type.IsString()) { return false; } diff --git a/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Models/Attributes/ALBApiAttributeBuilder.cs b/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Models/Attributes/ALBApiAttributeBuilder.cs new file mode 100644 index 000000000..d64f64048 --- /dev/null +++ b/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Models/Attributes/ALBApiAttributeBuilder.cs @@ -0,0 +1,68 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +using Amazon.Lambda.Annotations.ALB; +using Microsoft.CodeAnalysis; +using System; +using System.Linq; + +namespace Amazon.Lambda.Annotations.SourceGenerator.Models.Attributes +{ + /// + /// Builder for . + /// + public class ALBApiAttributeBuilder + { + public static ALBApiAttribute Build(AttributeData att) + { + if (att.ConstructorArguments.Length != 3) + { + throw new NotSupportedException($"{TypeFullNames.ALBApiAttribute} must have constructor with 3 arguments."); + } + + var listenerArn = att.ConstructorArguments[0].Value as string; + var pathPattern = att.ConstructorArguments[1].Value as string; + var priority = (int)att.ConstructorArguments[2].Value; + + var data = new ALBApiAttribute(listenerArn, pathPattern, priority); + + foreach (var pair in att.NamedArguments) + { + if (pair.Key == nameof(data.MultiValueHeaders) && pair.Value.Value is bool multiValueHeaders) + { + data.MultiValueHeaders = multiValueHeaders; + } + else if (pair.Key == nameof(data.HostHeader) && pair.Value.Value is string hostHeader) + { + data.HostHeader = hostHeader; + } + else if (pair.Key == nameof(data.HttpMethod) && pair.Value.Value is string httpMethod) + { + data.HttpMethod = httpMethod; + } + else if (pair.Key == nameof(data.ResourceName) && pair.Value.Value is string resourceName) + { + data.ResourceName = resourceName; + } + else if (pair.Key == nameof(data.HttpHeaderConditionName) && pair.Value.Value is string httpHeaderConditionName) + { + data.HttpHeaderConditionName = httpHeaderConditionName; + } + else if (pair.Key == nameof(data.HttpHeaderConditionValues) && !pair.Value.IsNull) + { + data.HttpHeaderConditionValues = pair.Value.Values.Select(v => v.Value as string).ToArray(); + } + else if (pair.Key == nameof(data.QueryStringConditions) && !pair.Value.IsNull) + { + data.QueryStringConditions = pair.Value.Values.Select(v => v.Value as string).ToArray(); + } + else if (pair.Key == nameof(data.SourceIpConditions) && !pair.Value.IsNull) + { + data.SourceIpConditions = pair.Value.Values.Select(v => v.Value as string).ToArray(); + } + } + + return data; + } + } +} diff --git a/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Models/Attributes/ALBFromHeaderAttributeBuilder.cs b/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Models/Attributes/ALBFromHeaderAttributeBuilder.cs new file mode 100644 index 000000000..a0ca9aced --- /dev/null +++ b/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Models/Attributes/ALBFromHeaderAttributeBuilder.cs @@ -0,0 +1,28 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +using Amazon.Lambda.Annotations.ALB; +using Microsoft.CodeAnalysis; + +namespace Amazon.Lambda.Annotations.SourceGenerator.Models.Attributes +{ + /// + /// Builder for . + /// + public class ALBFromHeaderAttributeBuilder + { + public static ALB.FromHeaderAttribute Build(AttributeData att) + { + var data = new ALB.FromHeaderAttribute(); + foreach (var pair in att.NamedArguments) + { + if (pair.Key == nameof(data.Name) && pair.Value.Value is string value) + { + data.Name = value; + } + } + + return data; + } + } +} diff --git a/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Models/Attributes/ALBFromQueryAttributeBuilder.cs b/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Models/Attributes/ALBFromQueryAttributeBuilder.cs new file mode 100644 index 000000000..8fb7ce644 --- /dev/null +++ b/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Models/Attributes/ALBFromQueryAttributeBuilder.cs @@ -0,0 +1,28 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +using Amazon.Lambda.Annotations.ALB; +using Microsoft.CodeAnalysis; + +namespace Amazon.Lambda.Annotations.SourceGenerator.Models.Attributes +{ + /// + /// Builder for . + /// + public class ALBFromQueryAttributeBuilder + { + public static ALB.FromQueryAttribute Build(AttributeData att) + { + var data = new ALB.FromQueryAttribute(); + foreach (var pair in att.NamedArguments) + { + if (pair.Key == nameof(data.Name) && pair.Value.Value is string value) + { + data.Name = value; + } + } + + return data; + } + } +} diff --git a/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Models/Attributes/AttributeModelBuilder.cs b/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Models/Attributes/AttributeModelBuilder.cs index 328a29ac5..add735fe5 100644 --- a/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Models/Attributes/AttributeModelBuilder.cs +++ b/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Models/Attributes/AttributeModelBuilder.cs @@ -1,5 +1,10 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + using System; +using Amazon.Lambda.Annotations.ALB; using Amazon.Lambda.Annotations.APIGateway; +using Amazon.Lambda.Annotations.S3; using Amazon.Lambda.Annotations.SQS; using Microsoft.CodeAnalysis; @@ -30,7 +35,7 @@ public static AttributeModel Build(AttributeData att, GeneratorExecutionContext else if (att.AttributeClass.Equals(context.Compilation.GetTypeByMetadataName(TypeFullNames.FromQueryAttribute), SymbolEqualityComparer.Default)) { var data = FromQueryAttributeBuilder.Build(att); - model = new AttributeModel + model = new AttributeModel { Data = data, Type = TypeModelBuilder.Build(att.AttributeClass, context) @@ -39,7 +44,7 @@ public static AttributeModel Build(AttributeData att, GeneratorExecutionContext else if (att.AttributeClass.Equals(context.Compilation.GetTypeByMetadataName(TypeFullNames.FromHeaderAttribute), SymbolEqualityComparer.Default)) { var data = FromHeaderAttributeBuilder.Build(att); - model = new AttributeModel + model = new AttributeModel { Data = data, Type = TypeModelBuilder.Build(att.AttributeClass, context) @@ -90,6 +95,33 @@ public static AttributeModel Build(AttributeData att, GeneratorExecutionContext Type = TypeModelBuilder.Build(att.AttributeClass, context) }; } + else if (att.AttributeClass.Equals(context.Compilation.GetTypeByMetadataName(TypeFullNames.S3EventAttribute), SymbolEqualityComparer.Default)) + { + var data = S3EventAttributeBuilder.Build(att); + model = new AttributeModel + { + Data = data, + Type = TypeModelBuilder.Build(att.AttributeClass, context) + }; + } + else if (att.AttributeClass.Equals(context.Compilation.GetTypeByMetadataName(TypeFullNames.FunctionUrlAttribute), SymbolEqualityComparer.Default)) + { + var data = FunctionUrlAttributeBuilder.Build(att); + model = new AttributeModel + { + Data = data, + Type = TypeModelBuilder.Build(att.AttributeClass, context) + }; + } + else if (att.AttributeClass.Equals(context.Compilation.GetTypeByMetadataName(TypeFullNames.SNSEventAttribute), SymbolEqualityComparer.Default)) + { + var data = SNSEventAttributeBuilder.Build(att); + model = new AttributeModel + { + Data = data, + Type = TypeModelBuilder.Build(att.AttributeClass, context) + }; + } else if (att.AttributeClass.Equals(context.Compilation.GetTypeByMetadataName(TypeFullNames.HttpApiAuthorizerAttribute), SymbolEqualityComparer.Default)) { var data = HttpApiAuthorizerAttributeBuilder.Build(att); @@ -108,6 +140,42 @@ public static AttributeModel Build(AttributeData att, GeneratorExecutionContext Type = TypeModelBuilder.Build(att.AttributeClass, context) }; } + else if (att.AttributeClass.Equals(context.Compilation.GetTypeByMetadataName(TypeFullNames.ALBApiAttribute), SymbolEqualityComparer.Default)) + { + var data = ALBApiAttributeBuilder.Build(att); + model = new AttributeModel + { + Data = data, + Type = TypeModelBuilder.Build(att.AttributeClass, context) + }; + } + else if (att.AttributeClass.Equals(context.Compilation.GetTypeByMetadataName(TypeFullNames.ALBFromQueryAttribute), SymbolEqualityComparer.Default)) + { + var data = ALBFromQueryAttributeBuilder.Build(att); + model = new AttributeModel + { + Data = data, + Type = TypeModelBuilder.Build(att.AttributeClass, context) + }; + } + else if (att.AttributeClass.Equals(context.Compilation.GetTypeByMetadataName(TypeFullNames.ALBFromHeaderAttribute), SymbolEqualityComparer.Default)) + { + var data = ALBFromHeaderAttributeBuilder.Build(att); + model = new AttributeModel + { + Data = data, + Type = TypeModelBuilder.Build(att.AttributeClass, context) + }; + } + else if (att.AttributeClass.Equals(context.Compilation.GetTypeByMetadataName(TypeFullNames.ALBFromBodyAttribute), SymbolEqualityComparer.Default)) + { + var data = new ALB.FromBodyAttribute(); + model = new AttributeModel + { + Data = data, + Type = TypeModelBuilder.Build(att.AttributeClass, context) + }; + } else { model = new AttributeModel @@ -119,4 +187,4 @@ public static AttributeModel Build(AttributeData att, GeneratorExecutionContext return model; } } -} \ No newline at end of file +} diff --git a/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Models/Attributes/FunctionUrlAttributeBuilder.cs b/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Models/Attributes/FunctionUrlAttributeBuilder.cs new file mode 100644 index 000000000..48bb69ea8 --- /dev/null +++ b/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Models/Attributes/FunctionUrlAttributeBuilder.cs @@ -0,0 +1,48 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +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 => (LambdaHttpMethod)(int)v.Value).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; + } + } +} diff --git a/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Models/Attributes/S3EventAttributeBuilder.cs b/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Models/Attributes/S3EventAttributeBuilder.cs new file mode 100644 index 000000000..66070af74 --- /dev/null +++ b/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Models/Attributes/S3EventAttributeBuilder.cs @@ -0,0 +1,37 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +using Amazon.Lambda.Annotations.S3; +using Microsoft.CodeAnalysis; +using System; + +namespace Amazon.Lambda.Annotations.SourceGenerator.Models.Attributes +{ + public class S3EventAttributeBuilder + { + public static S3EventAttribute Build(AttributeData att) + { + if (att.ConstructorArguments.Length != 1) + throw new NotSupportedException($"{TypeFullNames.S3EventAttribute} must have constructor with 1 argument."); + + var bucket = att.ConstructorArguments[0].Value as string; + var data = new S3EventAttribute(bucket); + + foreach (var pair in att.NamedArguments) + { + if (pair.Key == nameof(data.ResourceName) && pair.Value.Value is string resourceName) + data.ResourceName = resourceName; + else if (pair.Key == nameof(data.Events) && pair.Value.Value is string events) + data.Events = events; + else if (pair.Key == nameof(data.FilterPrefix) && pair.Value.Value is string filterPrefix) + data.FilterPrefix = filterPrefix; + else if (pair.Key == nameof(data.FilterSuffix) && pair.Value.Value is string filterSuffix) + data.FilterSuffix = filterSuffix; + else if (pair.Key == nameof(data.Enabled) && pair.Value.Value is bool enabled) + data.Enabled = enabled; + } + + return data; + } + } +} diff --git a/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Models/Attributes/SNSEventAttributeBuilder.cs b/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Models/Attributes/SNSEventAttributeBuilder.cs new file mode 100644 index 000000000..9e9890334 --- /dev/null +++ b/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Models/Attributes/SNSEventAttributeBuilder.cs @@ -0,0 +1,43 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +using Amazon.Lambda.Annotations.SNS; +using Microsoft.CodeAnalysis; +using System; + +namespace Amazon.Lambda.Annotations.SourceGenerator.Models.Attributes +{ + /// + /// Builder for . + /// + public class SNSEventAttributeBuilder + { + public static SNSEventAttribute Build(AttributeData att) + { + if (att.ConstructorArguments.Length != 1) + { + throw new NotSupportedException($"{TypeFullNames.SNSEventAttribute} must have constructor with 1 argument."); + } + var topic = att.ConstructorArguments[0].Value as string; + var data = new SNSEventAttribute(topic); + + foreach (var pair in att.NamedArguments) + { + if (pair.Key == nameof(data.ResourceName) && pair.Value.Value is string resourceName) + { + data.ResourceName = resourceName; + } + else if (pair.Key == nameof(data.FilterPolicy) && pair.Value.Value is string filterPolicy) + { + data.FilterPolicy = filterPolicy; + } + else if (pair.Key == nameof(data.Enabled) && pair.Value.Value is bool enabled) + { + data.Enabled = enabled; + } + } + + return data; + } + } +} diff --git a/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Models/EventType.cs b/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Models/EventType.cs index d231967e3..15eea9db4 100644 --- a/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Models/EventType.cs +++ b/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Models/EventType.cs @@ -9,8 +9,10 @@ public enum EventType API, S3, SQS, + SNS, DynamoDB, Schedule, - Authorizer + Authorizer, + ALB } } \ No newline at end of file diff --git a/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Models/EventTypeBuilder.cs b/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Models/EventTypeBuilder.cs index 3f5775851..960b83d97 100644 --- a/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Models/EventTypeBuilder.cs +++ b/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Models/EventTypeBuilder.cs @@ -1,3 +1,6 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + using System; using System.Collections.Generic; using System.Linq; @@ -18,7 +21,8 @@ public static HashSet Build(IMethodSymbol lambdaMethodSymbol, foreach (var attribute in lambdaMethodSymbol.GetAttributes()) { if (attribute.AttributeClass.ToDisplayString() == TypeFullNames.RestApiAttribute - || attribute.AttributeClass.ToDisplayString() == TypeFullNames.HttpApiAttribute) + || attribute.AttributeClass.ToDisplayString() == TypeFullNames.HttpApiAttribute + || attribute.AttributeClass.ToDisplayString() == TypeFullNames.FunctionUrlAttribute) { events.Add(EventType.API); } @@ -26,11 +30,23 @@ public static HashSet Build(IMethodSymbol lambdaMethodSymbol, { events.Add(EventType.SQS); } + else if (attribute.AttributeClass.ToDisplayString() == TypeFullNames.S3EventAttribute) + { + events.Add(EventType.S3); + } + else if (attribute.AttributeClass.ToDisplayString() == TypeFullNames.SNSEventAttribute) + { + events.Add(EventType.SNS); + } else if (attribute.AttributeClass.ToDisplayString() == TypeFullNames.HttpApiAuthorizerAttribute || attribute.AttributeClass.ToDisplayString() == TypeFullNames.RestApiAuthorizerAttribute) { events.Add(EventType.Authorizer); } + else if (attribute.AttributeClass.ToDisplayString() == TypeFullNames.ALBApiAttribute) + { + events.Add(EventType.ALB); + } } return events; diff --git a/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Models/GeneratedMethodModelBuilder.cs b/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Models/GeneratedMethodModelBuilder.cs index decb864ee..e3c6a020e 100644 --- a/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Models/GeneratedMethodModelBuilder.cs +++ b/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Models/GeneratedMethodModelBuilder.cs @@ -1,3 +1,6 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + using System; using System.Collections.Generic; using System.Linq; @@ -130,6 +133,28 @@ private static TypeModel BuildResponseType(IMethodSymbol lambdaMethodSymbol, throw new ArgumentOutOfRangeException(); } } + else if (lambdaMethodSymbol.HasAttribute(context, TypeFullNames.ALBApiAttribute)) + { + // ALB functions return ApplicationLoadBalancerResponse + // If the user already returns ApplicationLoadBalancerResponse, pass through the return type. + // Otherwise, wrap in ApplicationLoadBalancerResponse. + if (lambdaMethodModel.ReturnsApplicationLoadBalancerResponse) + { + return lambdaMethodModel.ReturnType; + } + var symbol = lambdaMethodModel.ReturnsVoidOrGenericTask ? + task.Construct(context.Compilation.GetTypeByMetadataName(TypeFullNames.ApplicationLoadBalancerResponse)): + context.Compilation.GetTypeByMetadataName(TypeFullNames.ApplicationLoadBalancerResponse); + return TypeModelBuilder.Build(symbol, context); + } + else if (lambdaMethodSymbol.HasAttribute(context, TypeFullNames.FunctionUrlAttribute)) + { + // Function URLs use the same payload format as HTTP API v2 + var symbol = lambdaMethodModel.ReturnsVoidOrGenericTask ? + task.Construct(context.Compilation.GetTypeByMetadataName(TypeFullNames.APIGatewayHttpApiV2ProxyResponse)): + context.Compilation.GetTypeByMetadataName(TypeFullNames.APIGatewayHttpApiV2ProxyResponse); + return TypeModelBuilder.Build(symbol, context); + } else { return lambdaMethodModel.ReturnType; @@ -277,6 +302,33 @@ private static IList BuildParameters(IMethodSymbol lambdaMethodS parameters.Add(requestParameter); parameters.Add(contextParameter); } + else if (lambdaMethodSymbol.HasAttribute(context, TypeFullNames.ALBApiAttribute)) + { + var symbol = context.Compilation.GetTypeByMetadataName(TypeFullNames.ApplicationLoadBalancerRequest); + var type = TypeModelBuilder.Build(symbol, context); + var requestParameter = new ParameterModel + { + Name = "__request__", + Type = type, + Documentation = "The ALB request object that will be processed by the Lambda function handler." + }; + parameters.Add(requestParameter); + parameters.Add(contextParameter); + } + else if (lambdaMethodSymbol.HasAttribute(context, TypeFullNames.FunctionUrlAttribute)) + { + // Function URLs use the same payload format as HTTP API v2 + var symbol = context.Compilation.GetTypeByMetadataName(TypeFullNames.APIGatewayHttpApiV2ProxyRequest); + var type = TypeModelBuilder.Build(symbol, context); + var requestParameter = new ParameterModel + { + Name = "__request__", + Type = type, + Documentation = "The Function URL request object that will be processed by the Lambda function handler." + }; + parameters.Add(requestParameter); + parameters.Add(contextParameter); + } else { // Lambda method with no event attribute are plain lambda functions, therefore, generated method will have diff --git a/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Models/LambdaMethodModel.cs b/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Models/LambdaMethodModel.cs index df80c43e5..601e4d86e 100644 --- a/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Models/LambdaMethodModel.cs +++ b/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Models/LambdaMethodModel.cs @@ -89,6 +89,31 @@ public bool ReturnsIAuthorizerResult } } + /// + /// Returns true if the Lambda function returns either ApplicationLoadBalancerResponse or Task<ApplicationLoadBalancerResponse> + /// + public bool ReturnsApplicationLoadBalancerResponse + { + get + { + if (ReturnsVoid) + { + return false; + } + + if (ReturnType.FullName == TypeFullNames.ApplicationLoadBalancerResponse) + { + return true; + } + if (ReturnsGenericTask && ReturnType.TypeArguments.Count == 1 && ReturnType.TypeArguments[0].FullName == TypeFullNames.ApplicationLoadBalancerResponse) + { + return true; + } + + return false; + } + } + /// /// Returns true if the Lambda function returns either void, Task, SQSBatchResponse or Task /// diff --git a/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/SyntaxReceiver.cs b/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/SyntaxReceiver.cs index a5d7ce9ab..e54d97b44 100644 --- a/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/SyntaxReceiver.cs +++ b/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/SyntaxReceiver.cs @@ -1,4 +1,7 @@ -using System; +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +using System; using System.Collections.Generic; using System.Linq; using Amazon.Lambda.Annotations.SourceGenerator.FileIO; @@ -21,7 +24,11 @@ internal class SyntaxReceiver : ISyntaxContextReceiver { "RestApiAuthorizerAttribute", "RestApiAuthorizer" }, { "HttpApiAttribute", "HttpApi" }, { "RestApiAttribute", "RestApi" }, - { "SQSEventAttribute", "SQSEvent" } + { "FunctionUrlAttribute", "FunctionUrl" }, + { "SQSEventAttribute", "SQSEvent" }, + { "ALBApiAttribute", "ALBApi" }, + { "S3EventAttribute", "S3Event" }, + { "SNSEventAttribute", "SNSEvent" } }; public List LambdaMethods { get; } = new List(); @@ -120,4 +127,4 @@ public void OnVisitSyntaxNode(GeneratorSyntaxContext context) } } } -} \ No newline at end of file +} diff --git a/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Templates/ALBInvoke.cs b/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Templates/ALBInvoke.cs new file mode 100644 index 000000000..a09bec840 --- /dev/null +++ b/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Templates/ALBInvoke.cs @@ -0,0 +1,420 @@ +// ------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// ------------------------------------------------------------------------------ +namespace Amazon.Lambda.Annotations.SourceGenerator.Templates +{ + using System.Linq; + using System.Text; + using System.Collections.Generic; + using Amazon.Lambda.Annotations.SourceGenerator.Extensions; + using Amazon.Lambda.Annotations.SourceGenerator.Models; + using Amazon.Lambda.Annotations.SourceGenerator.Models.Attributes; + using System; + + /// + /// Class to produce the template output + /// + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TextTemplating", "18.0.0.0")] + public partial class ALBInvoke : ALBInvokeBase + { + /// + /// Create the template output + /// + public virtual string TransformText() + { + + if (_model.GeneratedMethod.ReturnType.FullName == _model.LambdaMethod.ReturnType.FullName) + { + // User already returns ApplicationLoadBalancerResponse (or Task), + // just pass through. + if (_model.LambdaMethod.ReturnsVoid) + { + + this.Write(" "); + this.Write(this.ToStringHelper.ToStringWithCulture(_model.LambdaMethod.ContainingType.Name.ToCamelCase())); + this.Write("."); + this.Write(this.ToStringHelper.ToStringWithCulture(_model.LambdaMethod.Name)); + this.Write("("); + this.Write(this.ToStringHelper.ToStringWithCulture(_parameterSignature)); + this.Write(");\r\n"); + + } + else if (_model.LambdaMethod.ReturnsVoidTask) + { + + this.Write(" await "); + this.Write(this.ToStringHelper.ToStringWithCulture(_model.LambdaMethod.ContainingType.Name.ToCamelCase())); + this.Write("."); + this.Write(this.ToStringHelper.ToStringWithCulture(_model.LambdaMethod.Name)); + this.Write("("); + this.Write(this.ToStringHelper.ToStringWithCulture(_parameterSignature)); + this.Write(");\r\n"); + + } + else + { + + this.Write(" var response = "); + this.Write(this.ToStringHelper.ToStringWithCulture(_model.LambdaMethod.ReturnsGenericTask ? "await " : "")); + this.Write(this.ToStringHelper.ToStringWithCulture(_model.LambdaMethod.ContainingType.Name.ToCamelCase())); + this.Write("."); + this.Write(this.ToStringHelper.ToStringWithCulture(_model.LambdaMethod.Name)); + this.Write("("); + this.Write(this.ToStringHelper.ToStringWithCulture(_parameterSignature)); + this.Write(");\r\n return response;\r\n"); + + } + } + else + { + // User returns a non-ALB type, we need to wrap in ApplicationLoadBalancerResponse + if (_model.LambdaMethod.ReturnsVoid) + { + + this.Write(" "); + this.Write(this.ToStringHelper.ToStringWithCulture(_model.LambdaMethod.ContainingType.Name.ToCamelCase())); + this.Write("."); + this.Write(this.ToStringHelper.ToStringWithCulture(_model.LambdaMethod.Name)); + this.Write("("); + this.Write(this.ToStringHelper.ToStringWithCulture(_parameterSignature)); + this.Write(");\r\n"); + + } + else if (_model.LambdaMethod.ReturnsVoidTask) + { + + this.Write(" await "); + this.Write(this.ToStringHelper.ToStringWithCulture(_model.LambdaMethod.ContainingType.Name.ToCamelCase())); + this.Write("."); + this.Write(this.ToStringHelper.ToStringWithCulture(_model.LambdaMethod.Name)); + this.Write("("); + this.Write(this.ToStringHelper.ToStringWithCulture(_parameterSignature)); + this.Write(");\r\n"); + + } + else + { + + this.Write(" var response = "); + this.Write(this.ToStringHelper.ToStringWithCulture(_model.LambdaMethod.ReturnsGenericTask ? "await " : "")); + this.Write(this.ToStringHelper.ToStringWithCulture(_model.LambdaMethod.ContainingType.Name.ToCamelCase())); + this.Write("."); + this.Write(this.ToStringHelper.ToStringWithCulture(_model.LambdaMethod.Name)); + this.Write("("); + this.Write(this.ToStringHelper.ToStringWithCulture(_parameterSignature)); + this.Write(");\r\n"); + + if (_model.LambdaMethod.ReturnType.IsValueType) + { + + this.Write("\r\n var body = response.ToString();\r\n"); + + } + else if (_model.LambdaMethod.ReturnType.IsString()) + { + // no action needed, response is already a string + } + else + { + + this.Write(" var memoryStream = new MemoryStream();\r\n" + + " serializer.Serialize(response, memoryStream);\r\n" + + " memoryStream.Position = 0;\r\n\r\n" + + " // convert stream to string\r\n" + + " StreamReader reader = new StreamReader( memoryStream );\r\n" + + " var body = reader.ReadToEnd();\r\n"); + + } + } + + this.Write("\r\n return new Amazon.Lambda.ApplicationLoadBalancerEvents.ApplicationLoadBalancerResponse\r\n {\r\n"); + + if (!_model.LambdaMethod.ReturnsVoid && !_model.LambdaMethod.ReturnsVoidTask) + { + + this.Write(" Body = "); + this.Write(this.ToStringHelper.ToStringWithCulture(_model.LambdaMethod.ReturnType.IsString() ? "response" : "body")); + this.Write(",\r\n Headers = new Dictionary\r\n {\r\n {\"Content-Type\", "); + this.Write(this.ToStringHelper.ToStringWithCulture(_model.LambdaMethod.ReturnType.IsString() ? "\"text/plain\"" : "\"application/json\"")); + this.Write("}\r\n },\r\n"); + + } + + this.Write(" StatusCode = 200\r\n };\r\n"); + + } + + return this.GenerationEnvironment.ToString(); + } + } + + #region Base class + /// + /// Base class for this transformation + /// + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TextTemplating", "18.0.0.0")] + public class ALBInvokeBase + { + #region Fields + private global::System.Text.StringBuilder generationEnvironmentField; + private global::System.CodeDom.Compiler.CompilerErrorCollection errorsField; + private global::System.Collections.Generic.List indentLengthsField; + private string currentIndentField = ""; + private bool endsWithNewline; + private global::System.Collections.Generic.IDictionary sessionField; + #endregion + #region Properties + /// + /// The string builder that generation-time code is using to assemble generated output + /// + public System.Text.StringBuilder GenerationEnvironment + { + get + { + if ((this.generationEnvironmentField == null)) + { + this.generationEnvironmentField = new global::System.Text.StringBuilder(); + } + return this.generationEnvironmentField; + } + set + { + this.generationEnvironmentField = value; + } + } + /// + /// The error collection for the generation process + /// + public System.CodeDom.Compiler.CompilerErrorCollection Errors + { + get + { + if ((this.errorsField == null)) + { + this.errorsField = new global::System.CodeDom.Compiler.CompilerErrorCollection(); + } + return this.errorsField; + } + } + /// + /// A list of the lengths of each indent that was added with PushIndent + /// + private System.Collections.Generic.List indentLengths + { + get + { + if ((this.indentLengthsField == null)) + { + this.indentLengthsField = new global::System.Collections.Generic.List(); + } + return this.indentLengthsField; + } + } + /// + /// Gets the current indent we use when adding lines to the output + /// + public string CurrentIndent + { + get + { + return this.currentIndentField; + } + } + /// + /// Current transformation session + /// + public virtual global::System.Collections.Generic.IDictionary Session + { + get + { + return this.sessionField; + } + set + { + this.sessionField = value; + } + } + #endregion + #region Transform-time helpers + /// + /// Write text directly into the generated output + /// + public void Write(string textToAppend) + { + if (string.IsNullOrEmpty(textToAppend)) + { + return; + } + if (((this.GenerationEnvironment.Length == 0) + || this.endsWithNewline)) + { + this.GenerationEnvironment.Append(this.currentIndentField); + this.endsWithNewline = false; + } + if (textToAppend.EndsWith(global::System.Environment.NewLine, global::System.StringComparison.CurrentCulture)) + { + this.endsWithNewline = true; + } + if ((this.currentIndentField.Length == 0)) + { + this.GenerationEnvironment.Append(textToAppend); + return; + } + textToAppend = textToAppend.Replace(global::System.Environment.NewLine, (global::System.Environment.NewLine + this.currentIndentField)); + if (this.endsWithNewline) + { + this.GenerationEnvironment.Append(textToAppend, 0, (textToAppend.Length - this.currentIndentField.Length)); + } + else + { + this.GenerationEnvironment.Append(textToAppend); + } + } + /// + /// Write text directly into the generated output + /// + public void WriteLine(string textToAppend) + { + this.Write(textToAppend); + this.GenerationEnvironment.AppendLine(); + this.endsWithNewline = true; + } + /// + /// Write formatted text directly into the generated output + /// + public void Write(string format, params object[] args) + { + this.Write(string.Format(global::System.Globalization.CultureInfo.CurrentCulture, format, args)); + } + /// + /// Write formatted text directly into the generated output + /// + public void WriteLine(string format, params object[] args) + { + this.WriteLine(string.Format(global::System.Globalization.CultureInfo.CurrentCulture, format, args)); + } + /// + /// Raise an error + /// + public void Error(string message) + { + System.CodeDom.Compiler.CompilerError error = new global::System.CodeDom.Compiler.CompilerError(); + error.ErrorText = message; + this.Errors.Add(error); + } + /// + /// Raise a warning + /// + public void Warning(string message) + { + System.CodeDom.Compiler.CompilerError error = new global::System.CodeDom.Compiler.CompilerError(); + error.ErrorText = message; + error.IsWarning = true; + this.Errors.Add(error); + } + /// + /// Increase the indent + /// + public void PushIndent(string indent) + { + if ((indent == null)) + { + throw new global::System.ArgumentNullException("indent"); + } + this.currentIndentField = (this.currentIndentField + indent); + this.indentLengths.Add(indent.Length); + } + /// + /// Remove the last indent that was added with PushIndent + /// + public string PopIndent() + { + string returnValue = ""; + if ((this.indentLengths.Count > 0)) + { + int indentLength = this.indentLengths[(this.indentLengths.Count - 1)]; + this.indentLengths.RemoveAt((this.indentLengths.Count - 1)); + if ((indentLength > 0)) + { + returnValue = this.currentIndentField.Substring((this.currentIndentField.Length - indentLength)); + this.currentIndentField = this.currentIndentField.Remove((this.currentIndentField.Length - indentLength)); + } + } + return returnValue; + } + /// + /// Remove any indentation + /// + public void ClearIndent() + { + this.indentLengths.Clear(); + this.currentIndentField = ""; + } + #endregion + #region ToString Helpers + /// + /// Utility class to produce culture-oriented representation of an object as a string. + /// + public class ToStringInstanceHelper + { + private System.IFormatProvider formatProviderField = global::System.Globalization.CultureInfo.InvariantCulture; + /// + /// Gets or sets format provider to be used by ToStringWithCulture method. + /// + public System.IFormatProvider FormatProvider + { + get + { + return this.formatProviderField ; + } + set + { + if ((value != null)) + { + this.formatProviderField = value; + } + } + } + /// + /// This is called from the compile/run appdomain to convert objects within an expression block to a string + /// + public string ToStringWithCulture(object objectToConvert) + { + if ((objectToConvert == null)) + { + throw new global::System.ArgumentNullException("objectToConvert"); + } + System.Type t = objectToConvert.GetType(); + System.Reflection.MethodInfo method = t.GetMethod("ToString", new System.Type[] { + typeof(System.IFormatProvider)}); + if ((method == null)) + { + return objectToConvert.ToString(); + } + else + { + return ((string)(method.Invoke(objectToConvert, new object[] { + this.formatProviderField }))); + } + } + } + private ToStringInstanceHelper toStringHelperField = new ToStringInstanceHelper(); + /// + /// Helper to produce culture-oriented representation of an object as a string + /// + public ToStringInstanceHelper ToStringHelper + { + get + { + return this.toStringHelperField; + } + } + #endregion + } + #endregion +} diff --git a/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Templates/ALBInvoke.tt b/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Templates/ALBInvoke.tt new file mode 100644 index 000000000..e4a8a32fb --- /dev/null +++ b/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Templates/ALBInvoke.tt @@ -0,0 +1,98 @@ +<#@ template language="C#" #> +<#@ assembly name="System.Core" #> +<#@ import namespace="System.Linq" #> +<#@ import namespace="System.Text" #> +<#@ import namespace="System.Collections.Generic" #> +<#@ import namespace="Amazon.Lambda.Annotations.SourceGenerator.Extensions" #> +<#@ import namespace="Amazon.Lambda.Annotations.SourceGenerator.Models" #> +<#@ import namespace="Amazon.Lambda.Annotations.SourceGenerator.Models.Attributes" #> +<# + if (_model.GeneratedMethod.ReturnType.FullName == _model.LambdaMethod.ReturnType.FullName) + { + // User already returns ApplicationLoadBalancerResponse (or Task), + // just pass through. + if (_model.LambdaMethod.ReturnsVoid) + { +#> + <#= _model.LambdaMethod.ContainingType.Name.ToCamelCase() #>.<#= _model.LambdaMethod.Name #>(<#= _parameterSignature #>); +<# + } + else if (_model.LambdaMethod.ReturnsVoidTask) + { +#> + await <#= _model.LambdaMethod.ContainingType.Name.ToCamelCase() #>.<#= _model.LambdaMethod.Name #>(<#= _parameterSignature #>); +<# + } + else + { +#> + var response = <#= _model.LambdaMethod.ReturnsGenericTask ? "await " : "" #><#= _model.LambdaMethod.ContainingType.Name.ToCamelCase() #>.<#= _model.LambdaMethod.Name #>(<#= _parameterSignature #>); + return response; +<# + } + } + else + { + // User returns a non-ALB type, we need to wrap in ApplicationLoadBalancerResponse + if (_model.LambdaMethod.ReturnsVoid) + { +#> + <#= _model.LambdaMethod.ContainingType.Name.ToCamelCase() #>.<#= _model.LambdaMethod.Name #>(<#= _parameterSignature #>); +<# + } + else if (_model.LambdaMethod.ReturnsVoidTask) + { +#> + await <#= _model.LambdaMethod.ContainingType.Name.ToCamelCase() #>.<#= _model.LambdaMethod.Name #>(<#= _parameterSignature #>); +<# + } + else + { +#> + var response = <#= _model.LambdaMethod.ReturnsGenericTask ? "await " : "" #><#= _model.LambdaMethod.ContainingType.Name.ToCamelCase() #>.<#= _model.LambdaMethod.Name #>(<#= _parameterSignature #>); +<# + if (_model.LambdaMethod.ReturnType.IsValueType) + { +#> + + var body = response.ToString(); +<# + } + else if (_model.LambdaMethod.ReturnType.IsString()) + { + // no action needed, response is already a string + } + else + { +#> + var memoryStream = new MemoryStream(); + serializer.Serialize(response, memoryStream); + memoryStream.Position = 0; + + // convert stream to string + StreamReader reader = new StreamReader( memoryStream ); + var body = reader.ReadToEnd(); +<# + } + } +#> + + return new Amazon.Lambda.ApplicationLoadBalancerEvents.ApplicationLoadBalancerResponse + { +<# + if (!_model.LambdaMethod.ReturnsVoid && !_model.LambdaMethod.ReturnsVoidTask) + { +#> + Body = <#= _model.LambdaMethod.ReturnType.IsString() ? "response" : "body" #>, + Headers = new Dictionary + { + {"Content-Type", <#= _model.LambdaMethod.ReturnType.IsString() ? "\"text/plain\"" : "\"application/json\"" #>} + }, +<# + } +#> + StatusCode = 200 + }; +<# + } +#> diff --git a/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Templates/ALBInvokeCode.cs b/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Templates/ALBInvokeCode.cs new file mode 100644 index 000000000..04076566c --- /dev/null +++ b/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Templates/ALBInvokeCode.cs @@ -0,0 +1,20 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +using Amazon.Lambda.Annotations.SourceGenerator.Models; + +namespace Amazon.Lambda.Annotations.SourceGenerator.Templates +{ + public partial class ALBInvoke + { + private readonly LambdaFunctionModel _model; + + public readonly string _parameterSignature; + + public ALBInvoke(LambdaFunctionModel model, string parameterSignature) + { + _model = model; + _parameterSignature = parameterSignature; + } + } +} diff --git a/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Templates/ALBSetupParameters.cs b/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Templates/ALBSetupParameters.cs new file mode 100644 index 000000000..a6ce865bc --- /dev/null +++ b/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Templates/ALBSetupParameters.cs @@ -0,0 +1,604 @@ +// ------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// ------------------------------------------------------------------------------ +namespace Amazon.Lambda.Annotations.SourceGenerator.Templates +{ + using System.Linq; + using System.Text; + using System.Collections.Generic; + using Amazon.Lambda.Annotations.SourceGenerator.Extensions; + using Amazon.Lambda.Annotations.SourceGenerator.Models; + using Amazon.Lambda.Annotations.SourceGenerator.Models.Attributes; + using System; + + /// + /// Class to produce the template output + /// + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TextTemplating", "18.0.0.0")] + public partial class ALBSetupParameters : ALBSetupParametersBase + { + /// + /// Create the template output + /// + public virtual string TransformText() + { + + ParameterSignature = string.Join(", ", _model.LambdaMethod.Parameters + .Select(p => + { + // Pass the same context parameter for ILambdaContext that comes from the generated method. + if (p.Type.FullName == TypeFullNames.ILambdaContext) + { + return "__context__"; + } + + // Pass the same request parameter for ALB Request Type that comes from the generated method. + if (TypeFullNames.ALBRequests.Contains(p.Type.FullName)) + { + return "__request__"; + } + + return p.Name; + })); + + var albApiAttribute = _model.LambdaMethod.Attributes.FirstOrDefault(att => att.Type.FullName == TypeFullNames.ALBApiAttribute) as AttributeModel; + + // Determine whether multi-value headers are enabled + var useMultiValue = albApiAttribute?.Data?.IsMultiValueHeadersSet == true && albApiAttribute.Data.MultiValueHeaders; + + if (_model.LambdaMethod.Parameters.HasConvertibleParameter()) + { + + this.Write(" var validationErrors = new List();\r\n\r\n"); + + } + + foreach (var parameter in _model.LambdaMethod.Parameters) + { + if (parameter.Type.FullName == TypeFullNames.ILambdaContext || TypeFullNames.ALBRequests.Contains(parameter.Type.FullName)) + { + // No action required for ILambdaContext and ALB RequestType, they are passed from the generated method parameter directly to the original method. + } + else if (parameter.Attributes.Any(att => att.Type.FullName == TypeFullNames.FromServiceAttribute)) + { + + this.Write(" var "); + this.Write(this.ToStringHelper.ToStringWithCulture(parameter.Name)); + this.Write(" = scope.ServiceProvider.GetRequiredService<"); + this.Write(this.ToStringHelper.ToStringWithCulture(parameter.Type.FullName)); + this.Write(">();\r\n"); + + } + else if (parameter.Attributes.Any(att => att.Type.FullName == TypeFullNames.ALBFromQueryAttribute)) + { + var fromQueryAttribute = parameter.Attributes.First(att => att.Type.FullName == TypeFullNames.ALBFromQueryAttribute) as AttributeModel; + + // Use parameter name as key, if Name has not specified explicitly in the attribute definition. + var parameterKey = fromQueryAttribute?.Data?.Name ?? parameter.Name; + + var queryStringParameters = useMultiValue ? "MultiValueQueryStringParameters" : "QueryStringParameters"; + + this.Write(" var "); + this.Write(this.ToStringHelper.ToStringWithCulture(parameter.Name)); + this.Write(" = default("); + this.Write(this.ToStringHelper.ToStringWithCulture(parameter.Type.FullName)); + this.Write(");\r\n"); + + if (parameter.Type.IsEnumerable && parameter.Type.IsGenericType) + { + if (useMultiValue) + { + // Multi-value mode: MultiValueQueryStringParameters is IDictionary> + if (parameter.Type.TypeArguments.Count != 1) + { + throw new NotSupportedException("Only one type argument is supported for generic types."); + } + + var typeArgument = parameter.Type.TypeArguments.First(); + this.Write(" if (__request__."); + this.Write(this.ToStringHelper.ToStringWithCulture(queryStringParameters)); + this.Write("?.ContainsKey(\""); + this.Write(this.ToStringHelper.ToStringWithCulture(parameterKey)); + this.Write("\") == true)\r\n {\r\n "); + this.Write(this.ToStringHelper.ToStringWithCulture(parameter.Name)); + this.Write(" = __request__."); + this.Write(this.ToStringHelper.ToStringWithCulture(queryStringParameters)); + this.Write("[\""); + this.Write(this.ToStringHelper.ToStringWithCulture(parameterKey)); + this.Write("\"]\r\n .Select(q =>\r\n {\r\n try\r\n {\r\n return ("); + this.Write(this.ToStringHelper.ToStringWithCulture(typeArgument.FullName)); + this.Write(")Convert.ChangeType(q, typeof("); + this.Write(this.ToStringHelper.ToStringWithCulture(typeArgument.FullNameWithoutAnnotations)); + this.Write("));\r\n }\r\n catch (Exception e) when (e is InvalidCastException || e is FormatException || e is OverflowException || e is ArgumentException)\r\n {\r\n validationErrors.Add($\"Value {q} at \'"); + this.Write(this.ToStringHelper.ToStringWithCulture(parameterKey)); + this.Write("\' failed to satisfy constraint: {e.Message}\");\r\n return default;\r\n }\r\n })\r\n .ToList();\r\n }\r\n\r\n"); + + } + else + { + // Single-value mode: QueryStringParameters is IDictionary + // Split by comma to support multiple values + if (parameter.Type.TypeArguments.Count != 1) + { + throw new NotSupportedException("Only one type argument is supported for generic types."); + } + + var typeArgument = parameter.Type.TypeArguments.First(); + this.Write(" if (__request__."); + this.Write(this.ToStringHelper.ToStringWithCulture(queryStringParameters)); + this.Write("?.ContainsKey(\""); + this.Write(this.ToStringHelper.ToStringWithCulture(parameterKey)); + this.Write("\") == true)\r\n {\r\n "); + this.Write(this.ToStringHelper.ToStringWithCulture(parameter.Name)); + this.Write(" = __request__."); + this.Write(this.ToStringHelper.ToStringWithCulture(queryStringParameters)); + this.Write("[\""); + this.Write(this.ToStringHelper.ToStringWithCulture(parameterKey)); + this.Write("\"].Split(\",\")\r\n .Select(q =>\r\n {\r\n try\r\n {\r\n return ("); + this.Write(this.ToStringHelper.ToStringWithCulture(typeArgument.FullName)); + this.Write(")Convert.ChangeType(q, typeof("); + this.Write(this.ToStringHelper.ToStringWithCulture(typeArgument.FullNameWithoutAnnotations)); + this.Write("));\r\n }\r\n catch (Exception e) when (e is InvalidCastException || e is FormatException || e is OverflowException || e is ArgumentException)\r\n {\r\n validationErrors.Add($\"Value {q} at \'"); + this.Write(this.ToStringHelper.ToStringWithCulture(parameterKey)); + this.Write("\' failed to satisfy constraint: {e.Message}\");\r\n return default;\r\n }\r\n })\r\n .ToList();\r\n }\r\n\r\n"); + + } + } + else + { + // Non-generic types are mapped directly to the target parameter. + this.Write(" if (__request__."); + this.Write(this.ToStringHelper.ToStringWithCulture(queryStringParameters)); + this.Write("?.ContainsKey(\""); + this.Write(this.ToStringHelper.ToStringWithCulture(parameterKey)); + this.Write("\") == true)\r\n {\r\n try\r\n {\r\n "); + this.Write(this.ToStringHelper.ToStringWithCulture(parameter.Name)); + this.Write(" = ("); + this.Write(this.ToStringHelper.ToStringWithCulture(parameter.Type.FullName)); + this.Write(")Convert.ChangeType(__request__."); + this.Write(this.ToStringHelper.ToStringWithCulture(queryStringParameters)); + this.Write("[\""); + this.Write(this.ToStringHelper.ToStringWithCulture(parameterKey)); + this.Write("\"], typeof("); + this.Write(this.ToStringHelper.ToStringWithCulture(parameter.Type.FullNameWithoutAnnotations)); + this.Write("));\r\n }\r\n catch (Exception e) when (e is InvalidCastException || e is FormatException || e is OverflowException || e is ArgumentException)\r\n {\r\n validationErrors.Add($\"Value {__request__."); + this.Write(this.ToStringHelper.ToStringWithCulture(queryStringParameters)); + this.Write("[\""); + this.Write(this.ToStringHelper.ToStringWithCulture(parameterKey)); + this.Write("\"]} at \'"); + this.Write(this.ToStringHelper.ToStringWithCulture(parameterKey)); + this.Write("\' failed to satisfy constraint: {e.Message}\");\r\n }\r\n }\r\n\r\n"); + + } + + } + else if (parameter.Attributes.Any(att => att.Type.FullName == TypeFullNames.ALBFromHeaderAttribute)) + { + var fromHeaderAttribute = + parameter.Attributes.First(att => att.Type.FullName == TypeFullNames.ALBFromHeaderAttribute) as + AttributeModel; + + // Use parameter name as key, if Name has not specified explicitly in the attribute definition. + var headerKey = fromHeaderAttribute?.Data?.Name ?? parameter.Name; + + var headers = useMultiValue ? "MultiValueHeaders" : "Headers"; + + this.Write(" var "); + this.Write(this.ToStringHelper.ToStringWithCulture(parameter.Name)); + this.Write(" = default("); + this.Write(this.ToStringHelper.ToStringWithCulture(parameter.Type.FullName)); + this.Write(");\r\n"); + + if (parameter.Type.IsEnumerable && parameter.Type.IsGenericType) + { + if (useMultiValue) + { + // Multi-value mode: MultiValueHeaders is IDictionary> + if (parameter.Type.TypeArguments.Count != 1) + { + throw new NotSupportedException("Only one type argument is supported for generic types."); + } + + var typeArgument = parameter.Type.TypeArguments.First(); + this.Write(" if (__request__."); + this.Write(this.ToStringHelper.ToStringWithCulture(headers)); + this.Write("?.Any(x => string.Equals(x.Key, \""); + this.Write(this.ToStringHelper.ToStringWithCulture(headerKey)); + this.Write("\", StringComparison.OrdinalIgnoreCase)) == true)\r\n {\r\n "); + this.Write(this.ToStringHelper.ToStringWithCulture(parameter.Name)); + this.Write(" = __request__."); + this.Write(this.ToStringHelper.ToStringWithCulture(headers)); + this.Write(".First(x => string.Equals(x.Key, \""); + this.Write(this.ToStringHelper.ToStringWithCulture(headerKey)); + this.Write("\", StringComparison.OrdinalIgnoreCase)).Value\r\n .Select(q =>\r\n {\r\n try\r\n {\r\n return ("); + this.Write(this.ToStringHelper.ToStringWithCulture(typeArgument.FullName)); + this.Write(")Convert.ChangeType(q, typeof("); + this.Write(this.ToStringHelper.ToStringWithCulture(typeArgument.FullNameWithoutAnnotations)); + this.Write("));\r\n }\r\n catch (Exception e) when (e is InvalidCastException || e is FormatException || e is OverflowException || e is ArgumentException)\r\n {\r\n validationErrors.Add($\"Value {q} at \'"); + this.Write(this.ToStringHelper.ToStringWithCulture(headerKey)); + this.Write("\' failed to satisfy constraint: {e.Message}\");\r\n return default;\r\n }\r\n })\r\n .ToList();\r\n }\r\n\r\n"); + + } + else + { + // Single-value mode: Headers is IDictionary + // Split by comma to support multiple values + if (parameter.Type.TypeArguments.Count != 1) + { + throw new NotSupportedException("Only one type argument is supported for generic types."); + } + + var typeArgument = parameter.Type.TypeArguments.First(); + this.Write(" if (__request__."); + this.Write(this.ToStringHelper.ToStringWithCulture(headers)); + this.Write("?.Any(x => string.Equals(x.Key, \""); + this.Write(this.ToStringHelper.ToStringWithCulture(headerKey)); + this.Write("\", StringComparison.OrdinalIgnoreCase)) == true)\r\n {\r\n "); + this.Write(this.ToStringHelper.ToStringWithCulture(parameter.Name)); + this.Write(" = __request__."); + this.Write(this.ToStringHelper.ToStringWithCulture(headers)); + this.Write(".First(x => string.Equals(x.Key, \""); + this.Write(this.ToStringHelper.ToStringWithCulture(headerKey)); + this.Write("\", StringComparison.OrdinalIgnoreCase)).Value.Split(\",\")\r\n .Select(q =>\r\n {\r\n try\r\n {\r\n return ("); + this.Write(this.ToStringHelper.ToStringWithCulture(typeArgument.FullName)); + this.Write(")Convert.ChangeType(q, typeof("); + this.Write(this.ToStringHelper.ToStringWithCulture(typeArgument.FullNameWithoutAnnotations)); + this.Write("));\r\n }\r\n catch (Exception e) when (e is InvalidCastException || e is FormatException || e is OverflowException || e is ArgumentException)\r\n {\r\n validationErrors.Add($\"Value {q} at \'"); + this.Write(this.ToStringHelper.ToStringWithCulture(headerKey)); + this.Write("\' failed to satisfy constraint: {e.Message}\");\r\n return default;\r\n }\r\n })\r\n .ToList();\r\n }\r\n\r\n"); + + } + } + else + { + // Non-generic types are mapped directly to the target parameter. + this.Write(" if (__request__."); + this.Write(this.ToStringHelper.ToStringWithCulture(headers)); + this.Write("?.Any(x => string.Equals(x.Key, \""); + this.Write(this.ToStringHelper.ToStringWithCulture(headerKey)); + this.Write("\", StringComparison.OrdinalIgnoreCase)) == true)\r\n {\r\n try\r\n {\r\n "); + this.Write(this.ToStringHelper.ToStringWithCulture(parameter.Name)); + this.Write(" = ("); + this.Write(this.ToStringHelper.ToStringWithCulture(parameter.Type.FullName)); + this.Write(")Convert.ChangeType(__request__."); + this.Write(this.ToStringHelper.ToStringWithCulture(headers)); + this.Write(".First(x => string.Equals(x.Key, \""); + this.Write(this.ToStringHelper.ToStringWithCulture(headerKey)); + this.Write("\", StringComparison.OrdinalIgnoreCase)).Value, typeof("); + this.Write(this.ToStringHelper.ToStringWithCulture(parameter.Type.FullNameWithoutAnnotations)); + this.Write("));\r\n }\r\n catch (Exception e) when (e is InvalidCastException || e is FormatException || e is OverflowException || e is ArgumentException)\r\n {\r\n validationErrors.Add($\"Value {__request__."); + this.Write(this.ToStringHelper.ToStringWithCulture(headers)); + this.Write(".First(x => string.Equals(x.Key, \""); + this.Write(this.ToStringHelper.ToStringWithCulture(headerKey)); + this.Write("\", StringComparison.OrdinalIgnoreCase)).Value} at \'"); + this.Write(this.ToStringHelper.ToStringWithCulture(headerKey)); + this.Write("\' failed to satisfy constraint: {e.Message}\");\r\n }\r\n }\r\n\r\n"); + + } + } + else if (parameter.Attributes.Any(att => att.Type.FullName == TypeFullNames.ALBFromBodyAttribute)) + { + // string parameter does not need to be de-serialized + if (parameter.Type.IsString()) + { + + this.Write(" var "); + this.Write(this.ToStringHelper.ToStringWithCulture(parameter.Name)); + this.Write(" = __request__.Body;\r\n\r\n"); + + } + else + { + + this.Write(" var "); + this.Write(this.ToStringHelper.ToStringWithCulture(parameter.Name)); + this.Write(" = default("); + this.Write(this.ToStringHelper.ToStringWithCulture(parameter.Type.FullName)); + this.Write(");\r\n try\r\n {\r\n // convert string to stream\r\n var byteArray = Encoding.UTF8.GetBytes(__request__.Body);\r\n var stream = new MemoryStream(byteArray);\r\n "); + this.Write(this.ToStringHelper.ToStringWithCulture(parameter.Name)); + this.Write(" = serializer.Deserialize<"); + this.Write(this.ToStringHelper.ToStringWithCulture(parameter.Type.FullName)); + this.Write(">(stream);\r\n }\r\n catch (Exception e)\r\n {\r\n validationErrors.Add($\"Value {__request__.Body} at \'body\' failed to satisfy constraint: {e.Message}\");\r\n }\r\n\r\n"); + + } + } + else + { + throw new NotSupportedException($"{parameter.Name} parameter of type {parameter.Type.FullName} passing is not supported for ALB functions. Use [FromHeader], [FromQuery], [FromBody], or [FromServices] attributes."); + } + } + + if (_model.LambdaMethod.Parameters.HasConvertibleParameter()) + { + + this.Write(" // return 400 Bad Request if there exists a validation error\r\n" + + " if (validationErrors.Any())\r\n" + + " {\r\n" + + " var errorResult = new Amazon.Lambda.ApplicationLoadBalancerEvents.ApplicationLoadBalancerResponse\r\n" + + " {\r\n" + + " Body = @$\"{{\"\"message\"\": \"\"{validationErrors.Count} validation error(s) detected: {string.Join(\",\", validationErrors)}\"\"}}\",\r\n" + + " Headers = new Dictionary\r\n" + + " {\r\n" + + " {\"Content-Type\", \"application/json\"}\r\n" + + " },\r\n" + + " StatusCode = 400\r\n" + + " };\r\n" + + " return errorResult;\r\n" + + " }\r\n\r\n"); + + } + + return this.GenerationEnvironment.ToString(); + } + } + + #region Base class + /// + /// Base class for this transformation + /// + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TextTemplating", "18.0.0.0")] + public class ALBSetupParametersBase + { + #region Fields + private global::System.Text.StringBuilder generationEnvironmentField; + private global::System.CodeDom.Compiler.CompilerErrorCollection errorsField; + private global::System.Collections.Generic.List indentLengthsField; + private string currentIndentField = ""; + private bool endsWithNewline; + private global::System.Collections.Generic.IDictionary sessionField; + #endregion + #region Properties + /// + /// The string builder that generation-time code is using to assemble generated output + /// + public System.Text.StringBuilder GenerationEnvironment + { + get + { + if ((this.generationEnvironmentField == null)) + { + this.generationEnvironmentField = new global::System.Text.StringBuilder(); + } + return this.generationEnvironmentField; + } + set + { + this.generationEnvironmentField = value; + } + } + /// + /// The error collection for the generation process + /// + public System.CodeDom.Compiler.CompilerErrorCollection Errors + { + get + { + if ((this.errorsField == null)) + { + this.errorsField = new global::System.CodeDom.Compiler.CompilerErrorCollection(); + } + return this.errorsField; + } + } + /// + /// A list of the lengths of each indent that was added with PushIndent + /// + private System.Collections.Generic.List indentLengths + { + get + { + if ((this.indentLengthsField == null)) + { + this.indentLengthsField = new global::System.Collections.Generic.List(); + } + return this.indentLengthsField; + } + } + /// + /// Gets the current indent we use when adding lines to the output + /// + public string CurrentIndent + { + get + { + return this.currentIndentField; + } + } + /// + /// Current transformation session + /// + public virtual global::System.Collections.Generic.IDictionary Session + { + get + { + return this.sessionField; + } + set + { + this.sessionField = value; + } + } + #endregion + #region Transform-time helpers + /// + /// Write text directly into the generated output + /// + public void Write(string textToAppend) + { + if (string.IsNullOrEmpty(textToAppend)) + { + return; + } + if (((this.GenerationEnvironment.Length == 0) + || this.endsWithNewline)) + { + this.GenerationEnvironment.Append(this.currentIndentField); + this.endsWithNewline = false; + } + if (textToAppend.EndsWith(global::System.Environment.NewLine, global::System.StringComparison.CurrentCulture)) + { + this.endsWithNewline = true; + } + if ((this.currentIndentField.Length == 0)) + { + this.GenerationEnvironment.Append(textToAppend); + return; + } + textToAppend = textToAppend.Replace(global::System.Environment.NewLine, (global::System.Environment.NewLine + this.currentIndentField)); + if (this.endsWithNewline) + { + this.GenerationEnvironment.Append(textToAppend, 0, (textToAppend.Length - this.currentIndentField.Length)); + } + else + { + this.GenerationEnvironment.Append(textToAppend); + } + } + /// + /// Write text directly into the generated output + /// + public void WriteLine(string textToAppend) + { + this.Write(textToAppend); + this.GenerationEnvironment.AppendLine(); + this.endsWithNewline = true; + } + /// + /// Write formatted text directly into the generated output + /// + public void Write(string format, params object[] args) + { + this.Write(string.Format(global::System.Globalization.CultureInfo.CurrentCulture, format, args)); + } + /// + /// Write formatted text directly into the generated output + /// + public void WriteLine(string format, params object[] args) + { + this.WriteLine(string.Format(global::System.Globalization.CultureInfo.CurrentCulture, format, args)); + } + /// + /// Raise an error + /// + public void Error(string message) + { + System.CodeDom.Compiler.CompilerError error = new global::System.CodeDom.Compiler.CompilerError(); + error.ErrorText = message; + this.Errors.Add(error); + } + /// + /// Raise a warning + /// + public void Warning(string message) + { + System.CodeDom.Compiler.CompilerError error = new global::System.CodeDom.Compiler.CompilerError(); + error.ErrorText = message; + error.IsWarning = true; + this.Errors.Add(error); + } + /// + /// Increase the indent + /// + public void PushIndent(string indent) + { + if ((indent == null)) + { + throw new global::System.ArgumentNullException("indent"); + } + this.currentIndentField = (this.currentIndentField + indent); + this.indentLengths.Add(indent.Length); + } + /// + /// Remove the last indent that was added with PushIndent + /// + public string PopIndent() + { + string returnValue = ""; + if ((this.indentLengths.Count > 0)) + { + int indentLength = this.indentLengths[(this.indentLengths.Count - 1)]; + this.indentLengths.RemoveAt((this.indentLengths.Count - 1)); + if ((indentLength > 0)) + { + returnValue = this.currentIndentField.Substring((this.currentIndentField.Length - indentLength)); + this.currentIndentField = this.currentIndentField.Remove((this.currentIndentField.Length - indentLength)); + } + } + return returnValue; + } + /// + /// Remove any indentation + /// + public void ClearIndent() + { + this.indentLengths.Clear(); + this.currentIndentField = ""; + } + #endregion + #region ToString Helpers + /// + /// Utility class to produce culture-oriented representation of an object as a string. + /// + public class ToStringInstanceHelper + { + private System.IFormatProvider formatProviderField = global::System.Globalization.CultureInfo.InvariantCulture; + /// + /// Gets or sets format provider to be used by ToStringWithCulture method. + /// + public System.IFormatProvider FormatProvider + { + get + { + return this.formatProviderField ; + } + set + { + if ((value != null)) + { + this.formatProviderField = value; + } + } + } + /// + /// This is called from the compile/run appdomain to convert objects within an expression block to a string + /// + public string ToStringWithCulture(object objectToConvert) + { + if ((objectToConvert == null)) + { + throw new global::System.ArgumentNullException("objectToConvert"); + } + System.Type t = objectToConvert.GetType(); + System.Reflection.MethodInfo method = t.GetMethod("ToString", new System.Type[] { + typeof(System.IFormatProvider)}); + if ((method == null)) + { + return objectToConvert.ToString(); + } + else + { + return ((string)(method.Invoke(objectToConvert, new object[] { + this.formatProviderField }))); + } + } + } + private ToStringInstanceHelper toStringHelperField = new ToStringInstanceHelper(); + /// + /// Helper to produce culture-oriented representation of an object as a string + /// + public ToStringInstanceHelper ToStringHelper + { + get + { + return this.toStringHelperField; + } + } + #endregion + } + #endregion +} diff --git a/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Templates/ALBSetupParameters.tt b/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Templates/ALBSetupParameters.tt new file mode 100644 index 000000000..c90cff8b6 --- /dev/null +++ b/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Templates/ALBSetupParameters.tt @@ -0,0 +1,303 @@ +<#@ template language="C#" #> +<#@ assembly name="System.Core" #> +<#@ import namespace="System.Linq" #> +<#@ import namespace="System.Text" #> +<#@ import namespace="System.Collections.Generic" #> +<#@ import namespace="Amazon.Lambda.Annotations.SourceGenerator.Extensions" #> +<#@ import namespace="Amazon.Lambda.Annotations.SourceGenerator.Models" #> +<#@ import namespace="Amazon.Lambda.Annotations.SourceGenerator.Models.Attributes" #> +<# + ParameterSignature = string.Join(", ", _model.LambdaMethod.Parameters + .Select(p => + { + // Pass the same context parameter for ILambdaContext that comes from the generated method. + if (p.Type.FullName == TypeFullNames.ILambdaContext) + { + return "__context__"; + } + + // Pass the same request parameter for ALB Request Type that comes from the generated method. + if (TypeFullNames.ALBRequests.Contains(p.Type.FullName)) + { + return "__request__"; + } + + return p.Name; + })); + + var albApiAttribute = _model.LambdaMethod.Attributes.FirstOrDefault(att => att.Type.FullName == TypeFullNames.ALBApiAttribute) as AttributeModel; + + // Determine whether multi-value headers are enabled + var useMultiValue = albApiAttribute?.Data?.IsMultiValueHeadersSet == true && albApiAttribute.Data.MultiValueHeaders; + + if (_model.LambdaMethod.Parameters.HasConvertibleParameter()) + { +#> + var validationErrors = new List(); + +<# + } + + foreach (var parameter in _model.LambdaMethod.Parameters) + { + if (parameter.Type.FullName == TypeFullNames.ILambdaContext || TypeFullNames.ALBRequests.Contains(parameter.Type.FullName)) + { + // No action required for ILambdaContext and ALB RequestType, they are passed from the generated method parameter directly to the original method. + } + else if (parameter.Attributes.Any(att => att.Type.FullName == TypeFullNames.FromServiceAttribute)) + { +#> + var <#= parameter.Name #> = scope.ServiceProvider.GetRequiredService<<#= parameter.Type.FullName #>>(); +<# + } + else if (parameter.Attributes.Any(att => att.Type.FullName == TypeFullNames.ALBFromQueryAttribute)) + { + var fromQueryAttribute = parameter.Attributes.First(att => att.Type.FullName == TypeFullNames.ALBFromQueryAttribute) as AttributeModel; + + // Use parameter name as key, if Name has not specified explicitly in the attribute definition. + var parameterKey = fromQueryAttribute?.Data?.Name ?? parameter.Name; + + var queryStringParameters = useMultiValue ? "MultiValueQueryStringParameters" : "QueryStringParameters"; + +#> + var <#= parameter.Name #> = default(<#= parameter.Type.FullName #>); +<# + + if (parameter.Type.IsEnumerable && parameter.Type.IsGenericType) + { + if (useMultiValue) + { + // Multi-value mode: MultiValueQueryStringParameters is IDictionary> + if (parameter.Type.TypeArguments.Count != 1) + { + throw new NotSupportedException("Only one type argument is supported for generic types."); + } + + var typeArgument = parameter.Type.TypeArguments.First(); +#> + if (__request__.<#= queryStringParameters #>?.ContainsKey("<#= parameterKey #>") == true) + { + <#= parameter.Name #> = __request__.<#= queryStringParameters #>["<#= parameterKey #>"] + .Select(q => + { + try + { + return (<#= typeArgument.FullName #>)Convert.ChangeType(q, typeof(<#= typeArgument.FullNameWithoutAnnotations #>)); + } + catch (Exception e) when (e is InvalidCastException || e is FormatException || e is OverflowException || e is ArgumentException) + { + validationErrors.Add($"Value {q} at '<#= parameterKey #>' failed to satisfy constraint: {e.Message}"); + return default; + } + }) + .ToList(); + } + +<# + } + else + { + // Single-value mode: QueryStringParameters is IDictionary + // Split by comma to support multiple values + if (parameter.Type.TypeArguments.Count != 1) + { + throw new NotSupportedException("Only one type argument is supported for generic types."); + } + + var typeArgument = parameter.Type.TypeArguments.First(); +#> + if (__request__.<#= queryStringParameters #>?.ContainsKey("<#= parameterKey #>") == true) + { + <#= parameter.Name #> = __request__.<#= queryStringParameters #>["<#= parameterKey #>"].Split(",") + .Select(q => + { + try + { + return (<#= typeArgument.FullName #>)Convert.ChangeType(q, typeof(<#= typeArgument.FullNameWithoutAnnotations #>)); + } + catch (Exception e) when (e is InvalidCastException || e is FormatException || e is OverflowException || e is ArgumentException) + { + validationErrors.Add($"Value {q} at '<#= parameterKey #>' failed to satisfy constraint: {e.Message}"); + return default; + } + }) + .ToList(); + } + +<# + } + } + else + { + // Non-generic types are mapped directly to the target parameter. +#> + if (__request__.<#= queryStringParameters #>?.ContainsKey("<#= parameterKey #>") == true) + { + try + { + <#= parameter.Name #> = (<#= parameter.Type.FullName #>)Convert.ChangeType(__request__.<#= queryStringParameters #>["<#= parameterKey #>"], typeof(<#= parameter.Type.FullNameWithoutAnnotations #>)); + } + catch (Exception e) when (e is InvalidCastException || e is FormatException || e is OverflowException || e is ArgumentException) + { + validationErrors.Add($"Value {__request__.<#= queryStringParameters #>["<#= parameterKey #>"]} at '<#= parameterKey #>' failed to satisfy constraint: {e.Message}"); + } + } + +<# + } + + } + else if (parameter.Attributes.Any(att => att.Type.FullName == TypeFullNames.ALBFromHeaderAttribute)) + { + var fromHeaderAttribute = + parameter.Attributes.First(att => att.Type.FullName == TypeFullNames.ALBFromHeaderAttribute) as + AttributeModel; + + // Use parameter name as key, if Name has not specified explicitly in the attribute definition. + var headerKey = fromHeaderAttribute?.Data?.Name ?? parameter.Name; + + var headers = useMultiValue ? "MultiValueHeaders" : "Headers"; + +#> + var <#= parameter.Name #> = default(<#= parameter.Type.FullName #>); +<# + + if (parameter.Type.IsEnumerable && parameter.Type.IsGenericType) + { + if (useMultiValue) + { + // Multi-value mode: MultiValueHeaders is IDictionary> + if (parameter.Type.TypeArguments.Count != 1) + { + throw new NotSupportedException("Only one type argument is supported for generic types."); + } + + var typeArgument = parameter.Type.TypeArguments.First(); +#> + if (__request__.<#= headers #>?.Any(x => string.Equals(x.Key, "<#= headerKey #>", StringComparison.OrdinalIgnoreCase)) == true) + { + <#= parameter.Name #> = __request__.<#= headers #>.First(x => string.Equals(x.Key, "<#= headerKey #>", StringComparison.OrdinalIgnoreCase)).Value + .Select(q => + { + try + { + return (<#= typeArgument.FullName #>)Convert.ChangeType(q, typeof(<#= typeArgument.FullNameWithoutAnnotations #>)); + } + catch (Exception e) when (e is InvalidCastException || e is FormatException || e is OverflowException || e is ArgumentException) + { + validationErrors.Add($"Value {q} at '<#= headerKey #>' failed to satisfy constraint: {e.Message}"); + return default; + } + }) + .ToList(); + } + +<# + } + else + { + // Single-value mode: Headers is IDictionary + // Split by comma to support multiple values + if (parameter.Type.TypeArguments.Count != 1) + { + throw new NotSupportedException("Only one type argument is supported for generic types."); + } + + var typeArgument = parameter.Type.TypeArguments.First(); +#> + if (__request__.<#= headers #>?.Any(x => string.Equals(x.Key, "<#= headerKey #>", StringComparison.OrdinalIgnoreCase)) == true) + { + <#= parameter.Name #> = __request__.<#= headers #>.First(x => string.Equals(x.Key, "<#= headerKey #>", StringComparison.OrdinalIgnoreCase)).Value.Split(",") + .Select(q => + { + try + { + return (<#= typeArgument.FullName #>)Convert.ChangeType(q, typeof(<#= typeArgument.FullNameWithoutAnnotations #>)); + } + catch (Exception e) when (e is InvalidCastException || e is FormatException || e is OverflowException || e is ArgumentException) + { + validationErrors.Add($"Value {q} at '<#= headerKey #>' failed to satisfy constraint: {e.Message}"); + return default; + } + }) + .ToList(); + } + +<# + } + } + else + { + // Non-generic types are mapped directly to the target parameter. +#> + if (__request__.<#= headers #>?.Any(x => string.Equals(x.Key, "<#= headerKey #>", StringComparison.OrdinalIgnoreCase)) == true) + { + try + { + <#= parameter.Name #> = (<#= parameter.Type.FullName #>)Convert.ChangeType(__request__.<#= headers #>.First(x => string.Equals(x.Key, "<#= headerKey #>", StringComparison.OrdinalIgnoreCase)).Value, typeof(<#= parameter.Type.FullNameWithoutAnnotations #>)); + } + catch (Exception e) when (e is InvalidCastException || e is FormatException || e is OverflowException || e is ArgumentException) + { + validationErrors.Add($"Value {__request__.<#= headers #>.First(x => string.Equals(x.Key, "<#= headerKey #>", StringComparison.OrdinalIgnoreCase)).Value} at '<#= headerKey #>' failed to satisfy constraint: {e.Message}"); + } + } + +<# + } + } + else if (parameter.Attributes.Any(att => att.Type.FullName == TypeFullNames.ALBFromBodyAttribute)) + { + // string parameter does not need to be de-serialized + if (parameter.Type.IsString()) + { + #> + var <#= parameter.Name #> = __request__.Body; + +<# + } + else + { + #> + var <#= parameter.Name #> = default(<#= parameter.Type.FullName #>); + try + { + // convert string to stream + var byteArray = Encoding.UTF8.GetBytes(__request__.Body); + var stream = new MemoryStream(byteArray); + <#= parameter.Name #> = serializer.Deserialize<<#= parameter.Type.FullName #>>(stream); + } + catch (Exception e) + { + validationErrors.Add($"Value {__request__.Body} at 'body' failed to satisfy constraint: {e.Message}"); + } + +<# + } + } + else + { + throw new NotSupportedException($"{parameter.Name} parameter of type {parameter.Type.FullName} passing is not supported for ALB functions. Use [FromHeader], [FromQuery], [FromBody], or [FromServices] attributes."); + } + } + + if (_model.LambdaMethod.Parameters.HasConvertibleParameter()) + { +#> + // return 400 Bad Request if there exists a validation error + if (validationErrors.Any()) + { + var errorResult = new Amazon.Lambda.ApplicationLoadBalancerEvents.ApplicationLoadBalancerResponse + { + Body = @$"{{""message"": ""{validationErrors.Count} validation error(s) detected: {string.Join(",", validationErrors)}""}}", + Headers = new Dictionary + { + {"Content-Type", "application/json"} + }, + StatusCode = 400 + }; + return errorResult; + } + +<# + } +#> diff --git a/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Templates/ALBSetupParametersCode.cs b/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Templates/ALBSetupParametersCode.cs new file mode 100644 index 000000000..678f28859 --- /dev/null +++ b/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Templates/ALBSetupParametersCode.cs @@ -0,0 +1,19 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +using Amazon.Lambda.Annotations.SourceGenerator.Models; + +namespace Amazon.Lambda.Annotations.SourceGenerator.Templates +{ + public partial class ALBSetupParameters + { + private readonly LambdaFunctionModel _model; + + public string ParameterSignature { get; set; } + + public ALBSetupParameters(LambdaFunctionModel model) + { + _model = model; + } + } +} diff --git a/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Templates/LambdaFunctionTemplate.cs b/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Templates/LambdaFunctionTemplate.cs index e2c2f957f..6e4a30347 100644 --- a/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Templates/LambdaFunctionTemplate.cs +++ b/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Templates/LambdaFunctionTemplate.cs @@ -188,6 +188,12 @@ public virtual string TransformText() this.Write(apiParameters.TransformText()); this.Write(new APIGatewayInvoke(_model, apiParameters.ParameterSignature).TransformText()); } + else if (_model.LambdaMethod.Events.Contains(EventType.ALB)) + { + var albParameters = new ALBSetupParameters(_model); + this.Write(albParameters.TransformText()); + this.Write(new ALBInvoke(_model, albParameters.ParameterSignature).TransformText()); + } else { this.Write(new NoEventMethodBody(_model).TransformText()); diff --git a/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Templates/LambdaFunctionTemplate.tt b/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Templates/LambdaFunctionTemplate.tt index bacf7daf0..aa3c3ab18 100644 --- a/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Templates/LambdaFunctionTemplate.tt +++ b/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Templates/LambdaFunctionTemplate.tt @@ -66,6 +66,12 @@ this.Write(new FieldsAndConstructor(_model).TransformText()); this.Write(apiParameters.TransformText()); this.Write(new APIGatewayInvoke(_model, apiParameters.ParameterSignature).TransformText()); } + else if (_model.LambdaMethod.Events.Contains(EventType.ALB)) + { + var albParameters = new ALBSetupParameters(_model); + this.Write(albParameters.TransformText()); + this.Write(new ALBInvoke(_model, albParameters.ParameterSignature).TransformText()); + } else { this.Write(new NoEventMethodBody(_model).TransformText()); diff --git a/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/TypeFullNames.cs b/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/TypeFullNames.cs index 6e15c2175..40059193a 100644 --- a/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/TypeFullNames.cs +++ b/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/TypeFullNames.cs @@ -1,3 +1,6 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + using System.Collections.Generic; namespace Amazon.Lambda.Annotations.SourceGenerator @@ -34,6 +37,9 @@ public static class TypeFullNames public const string FromRouteAttribute = "Amazon.Lambda.Annotations.APIGateway.FromRouteAttribute"; public const string FromCustomAuthorizerAttribute = "Amazon.Lambda.Annotations.APIGateway.FromCustomAuthorizerAttribute"; + public const string FunctionUrlAttribute = "Amazon.Lambda.Annotations.APIGateway.FunctionUrlAttribute"; + public const string FunctionUrlAuthType = "Amazon.Lambda.Annotations.APIGateway.FunctionUrlAuthType"; + public const string HttpApiAuthorizerAttribute = "Amazon.Lambda.Annotations.APIGateway.HttpApiAuthorizerAttribute"; public const string RestApiAuthorizerAttribute = "Amazon.Lambda.Annotations.APIGateway.RestApiAuthorizerAttribute"; @@ -46,6 +52,19 @@ public static class TypeFullNames public const string SQSBatchResponse = "Amazon.Lambda.SQSEvents.SQSBatchResponse"; public const string SQSEventAttribute = "Amazon.Lambda.Annotations.SQS.SQSEventAttribute"; + public const string ApplicationLoadBalancerRequest = "Amazon.Lambda.ApplicationLoadBalancerEvents.ApplicationLoadBalancerRequest"; + public const string ApplicationLoadBalancerResponse = "Amazon.Lambda.ApplicationLoadBalancerEvents.ApplicationLoadBalancerResponse"; + public const string ALBApiAttribute = "Amazon.Lambda.Annotations.ALB.ALBApiAttribute"; + public const string ALBFromQueryAttribute = "Amazon.Lambda.Annotations.ALB.FromQueryAttribute"; + public const string ALBFromHeaderAttribute = "Amazon.Lambda.Annotations.ALB.FromHeaderAttribute"; + public const string ALBFromBodyAttribute = "Amazon.Lambda.Annotations.ALB.FromBodyAttribute"; + + public const string S3Event = "Amazon.Lambda.S3Events.S3Event"; + public const string S3EventAttribute = "Amazon.Lambda.Annotations.S3.S3EventAttribute"; + + public const string SNSEvent = "Amazon.Lambda.SNSEvents.SNSEvent"; + public const string SNSEventAttribute = "Amazon.Lambda.Annotations.SNS.SNSEventAttribute"; + public const string LambdaSerializerAttribute = "Amazon.Lambda.Core.LambdaSerializerAttribute"; public const string DefaultLambdaSerializer = "Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer"; @@ -63,11 +82,20 @@ public static class TypeFullNames APIGatewayCustomAuthorizerRequest }; + public static HashSet ALBRequests = new HashSet + { + ApplicationLoadBalancerRequest + }; + public static HashSet Events = new HashSet { RestApiAttribute, HttpApiAttribute, - SQSEventAttribute + FunctionUrlAttribute, + SQSEventAttribute, + ALBApiAttribute, + S3EventAttribute, + SNSEventAttribute }; } -} \ No newline at end of file +} diff --git a/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Validation/LambdaFunctionValidator.cs b/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Validation/LambdaFunctionValidator.cs index 733124209..7193779ed 100644 --- a/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Validation/LambdaFunctionValidator.cs +++ b/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Validation/LambdaFunctionValidator.cs @@ -1,7 +1,13 @@ -using Amazon.Lambda.Annotations.SourceGenerator.Diagnostics; +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +using Amazon.Lambda.Annotations.ALB; +using Amazon.Lambda.Annotations.S3; +using Amazon.Lambda.Annotations.SourceGenerator.Diagnostics; using Amazon.Lambda.Annotations.SourceGenerator.Extensions; using Amazon.Lambda.Annotations.SourceGenerator.Models; using Amazon.Lambda.Annotations.SourceGenerator.Models.Attributes; +using Amazon.Lambda.Annotations.SNS; using Amazon.Lambda.Annotations.SQS; using Microsoft.CodeAnalysis; using System.Collections.Generic; @@ -59,6 +65,9 @@ internal static bool ValidateFunction(GeneratorExecutionContext context, IMethod // Validate Events ValidateApiGatewayEvents(lambdaFunctionModel, methodLocation, diagnostics); ValidateSqsEvents(lambdaFunctionModel, methodLocation, diagnostics); + ValidateSnsEvents(lambdaFunctionModel, methodLocation, diagnostics); + ValidateAlbEvents(lambdaFunctionModel, methodLocation, diagnostics); + ValidateS3Events(lambdaFunctionModel, methodLocation, diagnostics); return ReportDiagnostics(diagnosticReporter, diagnostics); } @@ -67,6 +76,7 @@ internal static bool ValidateDependencies(GeneratorExecutionContext context, IMe { // Check for references to "Amazon.Lambda.APIGatewayEvents" if the Lambda method is annotated with RestApi, HttpApi, or authorizer attributes. if (lambdaMethodSymbol.HasAttribute(context, TypeFullNames.RestApiAttribute) || lambdaMethodSymbol.HasAttribute(context, TypeFullNames.HttpApiAttribute) + || lambdaMethodSymbol.HasAttribute(context, TypeFullNames.FunctionUrlAttribute) || lambdaMethodSymbol.HasAttribute(context, TypeFullNames.HttpApiAuthorizerAttribute) || lambdaMethodSymbol.HasAttribute(context, TypeFullNames.RestApiAuthorizerAttribute)) { if (context.Compilation.ReferencedAssemblyNames.FirstOrDefault(x => x.Name == "Amazon.Lambda.APIGatewayEvents") == null) @@ -86,6 +96,36 @@ internal static bool ValidateDependencies(GeneratorExecutionContext context, IMe } } + // Check for references to "Amazon.Lambda.ApplicationLoadBalancerEvents" if the Lambda method is annotated with ALBApi attribute. + if (lambdaMethodSymbol.HasAttribute(context, TypeFullNames.ALBApiAttribute)) + { + if (context.Compilation.ReferencedAssemblyNames.FirstOrDefault(x => x.Name == "Amazon.Lambda.ApplicationLoadBalancerEvents") == null) + { + diagnosticReporter.Report(Diagnostic.Create(DiagnosticDescriptors.MissingDependencies, methodLocation, "Amazon.Lambda.ApplicationLoadBalancerEvents")); + return false; + } + } + + // Check for references to "Amazon.Lambda.S3Events" if the Lambda method is annotated with S3Event attribute. + if (lambdaMethodSymbol.HasAttribute(context, TypeFullNames.S3EventAttribute)) + { + if (context.Compilation.ReferencedAssemblyNames.FirstOrDefault(x => x.Name == "Amazon.Lambda.S3Events") == null) + { + diagnosticReporter.Report(Diagnostic.Create(DiagnosticDescriptors.MissingDependencies, methodLocation, "Amazon.Lambda.S3Events")); + return false; + } + } + + // Check for references to "Amazon.Lambda.SNSEvents" if the Lambda method is annotated with SNSEvent attribute. + if (lambdaMethodSymbol.HasAttribute(context, TypeFullNames.SNSEventAttribute)) + { + if (context.Compilation.ReferencedAssemblyNames.FirstOrDefault(x => x.Name == "Amazon.Lambda.SNSEvents") == null) + { + diagnosticReporter.Report(Diagnostic.Create(DiagnosticDescriptors.MissingDependencies, methodLocation, "Amazon.Lambda.SNSEvents")); + return false; + } + } + return true; } @@ -106,10 +146,12 @@ private static void ValidateApiGatewayEvents(LambdaFunctionModel lambdaFunctionM diagnostics.Add(Diagnostic.Create(DiagnosticDescriptors.AuthorizerResultOnNonAuthorizerFunction, methodLocation)); } - // If the method does not contain any API or Authorizer events, then it cannot have + // If the method does not contain any API, Authorizer, or ALB events, then it cannot have // parameters that are annotated with HTTP API attributes. // Authorizer functions also support FromHeader, FromQuery, FromRoute attributes. - if (!isApiEvent && !isAuthorizerEvent) + // ALB functions also support FromHeader, FromQuery, FromBody attributes. + var isAlbEvent = lambdaFunctionModel.LambdaMethod.Events.Contains(EventType.ALB); + if (!isApiEvent && !isAuthorizerEvent && !isAlbEvent) { foreach (var parameter in lambdaFunctionModel.LambdaMethod.Parameters) { @@ -268,6 +310,171 @@ private static void ValidateSqsEvents(LambdaFunctionModel lambdaFunctionModel, L } } + private static void ValidateAlbEvents(LambdaFunctionModel lambdaFunctionModel, Location methodLocation, List diagnostics) + { + // If the method does not contain any ALB events, then simply return early + if (!lambdaFunctionModel.LambdaMethod.Events.Contains(EventType.ALB)) + { + return; + } + + // Validate ALBApiAttributes + foreach (var att in lambdaFunctionModel.Attributes) + { + if (att.Type.FullName != TypeFullNames.ALBApiAttribute) + continue; + + var albApiAttribute = ((AttributeModel)att).Data; + var validationErrors = albApiAttribute.Validate(); + validationErrors.ForEach(errorMessage => diagnostics.Add(Diagnostic.Create(DiagnosticDescriptors.InvalidAlbApiAttribute, methodLocation, errorMessage))); + } + + // Validate method parameters + var parameters = lambdaFunctionModel.LambdaMethod.Parameters; + foreach (var parameter in parameters) + { + // [FromRoute] is not supported on ALB functions + if (parameter.Attributes.Any(att => att.Type.FullName == TypeFullNames.FromRouteAttribute)) + { + diagnostics.Add(Diagnostic.Create(DiagnosticDescriptors.FromRouteNotSupportedOnAlb, methodLocation)); + } + + // Validate [FromQuery] parameter types - only primitive types allowed + if (parameter.Attributes.Any(att => att.Type.FullName == TypeFullNames.ALBFromQueryAttribute)) + { + if (!parameter.Type.IsPrimitiveType() && !parameter.Type.IsPrimitiveEnumerableType()) + { + diagnostics.Add(Diagnostic.Create(DiagnosticDescriptors.UnsupportedMethodParameterType, methodLocation, parameter.Name, parameter.Type.FullName)); + } + } + + // Validate attribute names for FromQuery and FromHeader + foreach (var att in parameter.Attributes) + { + var parameterAttributeName = string.Empty; + switch (att.Type.FullName) + { + case TypeFullNames.ALBFromQueryAttribute: + if (att is AttributeModel albFromQueryAttribute) + parameterAttributeName = albFromQueryAttribute.Data.Name; + break; + + case TypeFullNames.ALBFromHeaderAttribute: + if (att is AttributeModel albFromHeaderAttribute) + parameterAttributeName = albFromHeaderAttribute.Data.Name; + break; + + default: + break; + } + + if (!string.IsNullOrEmpty(parameterAttributeName) && !_parameterAttributeNameRegex.IsMatch(parameterAttributeName)) + { + diagnostics.Add(Diagnostic.Create(DiagnosticDescriptors.InvalidParameterAttributeName, methodLocation, parameterAttributeName, parameter.Name)); + } + } + + // Validate that every parameter has a recognized binding + // Allowed: ILambdaContext, ApplicationLoadBalancerRequest, [FromServices], [FromQuery], [FromHeader], [FromBody] + if (parameter.Type.FullName != TypeFullNames.ILambdaContext && + !TypeFullNames.ALBRequests.Contains(parameter.Type.FullName) && + !parameter.Attributes.Any(att => + att.Type.FullName == TypeFullNames.FromServiceAttribute || + att.Type.FullName == TypeFullNames.ALBFromQueryAttribute || + att.Type.FullName == TypeFullNames.ALBFromHeaderAttribute || + att.Type.FullName == TypeFullNames.ALBFromBodyAttribute || + att.Type.FullName == TypeFullNames.FromRouteAttribute)) // FromRoute already has its own error + { + diagnostics.Add(Diagnostic.Create(DiagnosticDescriptors.AlbUnmappedParameter, methodLocation, parameter.Name)); + } + } + } + + private static void ValidateS3Events(LambdaFunctionModel lambdaFunctionModel, Location methodLocation, List diagnostics) + { + if (!lambdaFunctionModel.LambdaMethod.Events.Contains(EventType.S3)) + return; + + // Validate S3EventAttributes + var seenResourceNames = new HashSet(); + foreach (var att in lambdaFunctionModel.Attributes) + { + if (att.Type.FullName != TypeFullNames.S3EventAttribute) + continue; + + var s3EventAttribute = ((AttributeModel)att).Data; + var validationErrors = s3EventAttribute.Validate(); + validationErrors.ForEach(errorMessage => diagnostics.Add(Diagnostic.Create(DiagnosticDescriptors.InvalidS3EventAttribute, methodLocation, errorMessage))); + + // Check for duplicate resource names (only when ResourceName is safe to evaluate) + var derivedResourceName = s3EventAttribute.ResourceName; + if (!string.IsNullOrEmpty(derivedResourceName) && !seenResourceNames.Add(derivedResourceName)) + { + diagnostics.Add(Diagnostic.Create(DiagnosticDescriptors.InvalidS3EventAttribute, methodLocation, + $"Duplicate S3 event resource name '{derivedResourceName}'. Each [S3Event] attribute on the same method must have a unique ResourceName.")); + } + } + + // Validate method parameters - first param must be S3Event, optional second param ILambdaContext + var parameters = lambdaFunctionModel.LambdaMethod.Parameters; + if (parameters.Count == 0 || + parameters.Count > 2 || + (parameters.Count == 1 && parameters[0].Type.FullName != TypeFullNames.S3Event) || + (parameters.Count == 2 && (parameters[0].Type.FullName != TypeFullNames.S3Event || parameters[1].Type.FullName != TypeFullNames.ILambdaContext))) + { + var errorMessage = $"When using the {nameof(S3EventAttribute)}, the Lambda method can accept at most 2 parameters. " + + $"The first parameter is required and must be of type {TypeFullNames.S3Event}. " + + $"The second parameter is optional and must be of type {TypeFullNames.ILambdaContext}."; + diagnostics.Add(Diagnostic.Create(DiagnosticDescriptors.InvalidLambdaMethodSignature, methodLocation, errorMessage)); + } + + // Validate method return type - must be void or Task + if (!lambdaFunctionModel.LambdaMethod.ReturnsVoid && !lambdaFunctionModel.LambdaMethod.ReturnsVoidTask) + { + var errorMessage = $"When using the {nameof(S3EventAttribute)}, the Lambda method can return either void or {TypeFullNames.Task}"; + diagnostics.Add(Diagnostic.Create(DiagnosticDescriptors.InvalidLambdaMethodSignature, methodLocation, errorMessage)); + } + } + + private static void ValidateSnsEvents(LambdaFunctionModel lambdaFunctionModel, Location methodLocation, List diagnostics) + { + if (!lambdaFunctionModel.LambdaMethod.Events.Contains(EventType.SNS)) + { + return; + } + + // Validate SNSEventAttributes + foreach (var att in lambdaFunctionModel.Attributes) + { + if (att.Type.FullName != TypeFullNames.SNSEventAttribute) + continue; + + var snsEventAttribute = ((AttributeModel)att).Data; + var validationErrors = snsEventAttribute.Validate(); + validationErrors.ForEach(errorMessage => diagnostics.Add(Diagnostic.Create(DiagnosticDescriptors.InvalidSnsEventAttribute, methodLocation, errorMessage))); + } + + // Validate method parameters - When using SNSEventAttribute, the method signature must be (SNSEvent snsEvent) or (SNSEvent snsEvent, ILambdaContext context) + var parameters = lambdaFunctionModel.LambdaMethod.Parameters; + if (parameters.Count == 0 || + parameters.Count > 2 || + (parameters.Count == 1 && parameters[0].Type.FullName != TypeFullNames.SNSEvent) || + (parameters.Count == 2 && (parameters[0].Type.FullName != TypeFullNames.SNSEvent || parameters[1].Type.FullName != TypeFullNames.ILambdaContext))) + { + var errorMessage = $"When using the {nameof(SNSEventAttribute)}, the Lambda method can accept at most 2 parameters. " + + $"The first parameter is required and must be of type {TypeFullNames.SNSEvent}. " + + $"The second parameter is optional and must be of type {TypeFullNames.ILambdaContext}."; + diagnostics.Add(Diagnostic.Create(DiagnosticDescriptors.InvalidLambdaMethodSignature, methodLocation, errorMessage)); + } + + // Validate method return type - When using SNSEventAttribute, the return type must be either void or Task + if (!lambdaFunctionModel.LambdaMethod.ReturnsVoid && !lambdaFunctionModel.LambdaMethod.ReturnsVoidTask) + { + var errorMessage = $"When using the {nameof(SNSEventAttribute)}, the Lambda method can return either void or {TypeFullNames.Task}"; + diagnostics.Add(Diagnostic.Create(DiagnosticDescriptors.InvalidLambdaMethodSignature, methodLocation, errorMessage)); + } + } + private static bool ReportDiagnostics(DiagnosticReporter diagnosticReporter, List diagnostics) { var isValid = true; diff --git a/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Writers/CloudFormationWriter.cs b/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Writers/CloudFormationWriter.cs index a59aaf6d4..f34f2d8bb 100644 --- a/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Writers/CloudFormationWriter.cs +++ b/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Writers/CloudFormationWriter.cs @@ -1,8 +1,14 @@ -using Amazon.Lambda.Annotations.APIGateway; +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +using Amazon.Lambda.Annotations.ALB; +using Amazon.Lambda.Annotations.APIGateway; using Amazon.Lambda.Annotations.SourceGenerator.Diagnostics; using Amazon.Lambda.Annotations.SourceGenerator.FileIO; using Amazon.Lambda.Annotations.SourceGenerator.Models; +using Amazon.Lambda.Annotations.SNS; using Amazon.Lambda.Annotations.SourceGenerator.Models.Attributes; +using Amazon.Lambda.Annotations.S3; using Amazon.Lambda.Annotations.SQS; using Microsoft.CodeAnalysis; using System; @@ -203,6 +209,8 @@ private void ProcessLambdaFunctionEventAttributes(ILambdaFunctionSerializable la { var currentSyncedEvents = new List(); var currentSyncedEventProperties = new Dictionary>(); + var currentAlbResources = new List(); + var hasFunctionUrl = false; foreach (var attributeModel in lambdaFunction.Attributes) { @@ -221,10 +229,40 @@ private void ProcessLambdaFunctionEventAttributes(ILambdaFunctionSerializable la eventName = ProcessSqsAttribute(lambdaFunction, sqsAttributeModel.Data, currentSyncedEventProperties); currentSyncedEvents.Add(eventName); break; + case AttributeModel albAttributeModel: + var albResourceNames = ProcessAlbApiAttribute(lambdaFunction, albAttributeModel.Data); + currentAlbResources.AddRange(albResourceNames); + break; + case AttributeModel s3AttributeModel: + eventName = ProcessS3Attribute(lambdaFunction, s3AttributeModel.Data, currentSyncedEventProperties); + currentSyncedEvents.Add(eventName); + break; + case AttributeModel functionUrlAttributeModel: + ProcessFunctionUrlAttribute(lambdaFunction, functionUrlAttributeModel.Data); + _templateWriter.SetToken($"Resources.{lambdaFunction.ResourceName}.Metadata.SyncedFunctionUrlConfig", true); + hasFunctionUrl = true; + break; + case AttributeModel snsAttributeModel: + eventName = ProcessSnsAttribute(lambdaFunction, snsAttributeModel.Data, currentSyncedEventProperties); + currentSyncedEvents.Add(eventName); + break; + } + } + + // Remove FunctionUrlConfig only if it was previously created by Annotations (tracked via metadata). + // This preserves any manually-added FunctionUrlConfig that was not created by the source generator. + if (!hasFunctionUrl) + { + var syncedFunctionUrlConfigPath = $"Resources.{lambdaFunction.ResourceName}.Metadata.SyncedFunctionUrlConfig"; + if (_templateWriter.GetToken(syncedFunctionUrlConfigPath, false)) + { + _templateWriter.RemoveToken($"Resources.{lambdaFunction.ResourceName}.Properties.FunctionUrlConfig"); + _templateWriter.RemoveToken(syncedFunctionUrlConfigPath); } } SynchronizeEventsAndProperties(currentSyncedEvents, currentSyncedEventProperties, lambdaFunction); + SynchronizeAlbResources(currentAlbResources, lambdaFunction); } /// @@ -290,6 +328,50 @@ private string ProcessHttpApiAttribute(ILambdaFunctionSerializable lambdaFunctio return eventName; } + /// + /// Writes the configuration to the serverless template. + /// Unlike HttpApi/RestApi, Function URLs are configured as a property on the function resource + /// rather than as an event source. + /// + private void ProcessFunctionUrlAttribute(ILambdaFunctionSerializable lambdaFunction, FunctionUrlAttribute functionUrlAttribute) + { + var functionUrlConfigPath = $"Resources.{lambdaFunction.ResourceName}.Properties.FunctionUrlConfig"; + _templateWriter.SetToken($"{functionUrlConfigPath}.AuthType", functionUrlAttribute.AuthType.ToString()); + + // Always remove the existing Cors block first to clear any stale properties + // from a previous generation pass, then re-emit only the currently configured values. + var corsPath = $"{functionUrlConfigPath}.Cors"; + _templateWriter.RemoveToken(corsPath); + + var hasCors = functionUrlAttribute.AllowOrigins != null + || functionUrlAttribute.AllowMethods != null + || functionUrlAttribute.AllowHeaders != null + || functionUrlAttribute.ExposeHeaders != null + || functionUrlAttribute.AllowCredentials + || functionUrlAttribute.MaxAge > 0; + + if (hasCors) + { + if (functionUrlAttribute.AllowOrigins != null) + _templateWriter.SetToken($"{corsPath}.AllowOrigins", new List(functionUrlAttribute.AllowOrigins), TokenType.List); + + if (functionUrlAttribute.AllowMethods != null) + _templateWriter.SetToken($"{corsPath}.AllowMethods", functionUrlAttribute.AllowMethods.Select(m => m == LambdaHttpMethod.Any ? "*" : m.ToString().ToUpper()).ToList(), TokenType.List); + + if (functionUrlAttribute.AllowHeaders != null) + _templateWriter.SetToken($"{corsPath}.AllowHeaders", new List(functionUrlAttribute.AllowHeaders), TokenType.List); + + if (functionUrlAttribute.ExposeHeaders != null) + _templateWriter.SetToken($"{corsPath}.ExposeHeaders", new List(functionUrlAttribute.ExposeHeaders), TokenType.List); + + if (functionUrlAttribute.AllowCredentials) + _templateWriter.SetToken($"{corsPath}.AllowCredentials", true); + + if (functionUrlAttribute.MaxAge > 0) + _templateWriter.SetToken($"{corsPath}.MaxAge", functionUrlAttribute.MaxAge); + } + } + /// /// Processes all authorizers and writes them to the serverless template as inline authorizers within the API resources. /// AWS SAM expects authorizers to be defined within the Auth.Authorizers property of AWS::Serverless::HttpApi or AWS::Serverless::Api resources. @@ -597,8 +679,322 @@ private string ProcessSqsAttribute(ILambdaFunctionSerializable lambdaFunction, S } /// - /// Writes all properties associated with to the serverless template. + /// Writes all properties associated with to the serverless template. + /// + private string ProcessSnsAttribute(ILambdaFunctionSerializable lambdaFunction, SNSEventAttribute att, Dictionary> syncedEventProperties) + { + var eventName = att.ResourceName; + var eventPath = $"Resources.{lambdaFunction.ResourceName}.Properties.Events.{eventName}"; + + _templateWriter.SetToken($"{eventPath}.Type", "SNS"); + + // Topic - SNS topics use Ref to get the ARN + _templateWriter.RemoveToken($"{eventPath}.Properties.Topic"); + if (!att.Topic.StartsWith("@")) + { + SetEventProperty(syncedEventProperties, lambdaFunction.ResourceName, eventName, "Topic", att.Topic); + } + else + { + var topic = att.Topic.Substring(1); + SetEventProperty(syncedEventProperties, lambdaFunction.ResourceName, eventName, $"Topic.{REF}", topic); + } + + // FilterPolicy + if (att.IsFilterPolicySet) + { + SetEventProperty(syncedEventProperties, lambdaFunction.ResourceName, eventName, "FilterPolicy", att.FilterPolicy); + } + + // Enabled + if (att.IsEnabledSet) + { + SetEventProperty(syncedEventProperties, lambdaFunction.ResourceName, eventName, "Enabled", att.Enabled); + } + + return att.ResourceName; + } + + /// + /// Writes all properties associated with to the serverless template. /// + private string ProcessS3Attribute(ILambdaFunctionSerializable lambdaFunction, S3EventAttribute att, Dictionary> syncedEventProperties) + { + var eventName = att.ResourceName; + var eventPath = $"Resources.{lambdaFunction.ResourceName}.Properties.Events.{eventName}"; + + _templateWriter.SetToken($"{eventPath}.Type", "S3"); + + // Bucket - always a Ref since S3 events require the bucket resource in the same template (validated to start with "@") + var bucketName = att.Bucket.Substring(1); + _templateWriter.RemoveToken($"{eventPath}.Properties.Bucket"); + SetEventProperty(syncedEventProperties, lambdaFunction.ResourceName, eventName, $"Bucket.{REF}", bucketName); + + // Events - list of S3 event types (always written since S3 SAM events require it; uses default "s3:ObjectCreated:*" if not explicitly set) + { + var events = att.Events.Split(';').Select(x => x.Trim()).Where(x => !string.IsNullOrWhiteSpace(x)).ToList(); + SetEventProperty(syncedEventProperties, lambdaFunction.ResourceName, eventName, "Events", events, TokenType.List); + } + + // Filter - S3 key filter rules + if (att.IsFilterPrefixSet || att.IsFilterSuffixSet) + { + var rules = new List>(); + + if (att.IsFilterPrefixSet) + { + rules.Add(new Dictionary { { "Name", "prefix" }, { "Value", att.FilterPrefix } }); + } + + if (att.IsFilterSuffixSet) + { + rules.Add(new Dictionary { { "Name", "suffix" }, { "Value", att.FilterSuffix } }); + } + + SetEventProperty(syncedEventProperties, lambdaFunction.ResourceName, eventName, "Filter.S3Key.Rules", rules, TokenType.List); + } + + // Enabled + if (att.IsEnabledSet) + { + SetEventProperty(syncedEventProperties, lambdaFunction.ResourceName, eventName, "Enabled", att.Enabled); + } + + return att.ResourceName; + } + + /// + /// Generates CloudFormation resources for an Application Load Balancer target. + /// Unlike API Gateway events which map to SAM event types, ALB integration requires + /// generating standalone CloudFormation resources: a TargetGroup, a ListenerRule, and a Lambda Permission. + /// + /// List of the three generated CloudFormation resource names for tracking/synchronization. + private List ProcessAlbApiAttribute(ILambdaFunctionSerializable lambdaFunction, ALBApiAttribute att) + { + var baseName = att.IsResourceNameSet ? att.ResourceName : $"{lambdaFunction.ResourceName}ALB"; + var permissionName = $"{baseName}Permission"; + var targetGroupName = $"{baseName}TargetGroup"; + var listenerRuleName = $"{baseName}ListenerRule"; + + // 1. Lambda Permission - allows ELB to invoke the Lambda function + var permPath = $"Resources.{permissionName}"; + if (!_templateWriter.Exists(permPath) || + string.Equals(_templateWriter.GetToken($"{permPath}.Metadata.Tool", string.Empty), CREATION_TOOL, StringComparison.Ordinal)) + { + _templateWriter.SetToken($"{permPath}.Type", "AWS::Lambda::Permission"); + _templateWriter.SetToken($"{permPath}.Metadata.Tool", CREATION_TOOL); + _templateWriter.SetToken($"{permPath}.Properties.FunctionName.{GET_ATTRIBUTE}", new List { lambdaFunction.ResourceName, "Arn" }, TokenType.List); + _templateWriter.SetToken($"{permPath}.Properties.Action", "lambda:InvokeFunction"); + _templateWriter.SetToken($"{permPath}.Properties.Principal", "elasticloadbalancing.amazonaws.com"); + } + + // 2. Target Group - registers the Lambda function as a target + var tgPath = $"Resources.{targetGroupName}"; + if (!_templateWriter.Exists(tgPath) || + string.Equals(_templateWriter.GetToken($"{tgPath}.Metadata.Tool", string.Empty), CREATION_TOOL, StringComparison.Ordinal)) + { + _templateWriter.SetToken($"{tgPath}.Type", "AWS::ElasticLoadBalancingV2::TargetGroup"); + _templateWriter.SetToken($"{tgPath}.Metadata.Tool", CREATION_TOOL); + _templateWriter.SetToken($"{tgPath}.DependsOn", permissionName); + _templateWriter.SetToken($"{tgPath}.Properties.TargetType", "lambda"); + + // MultiValueHeaders must be set via TargetGroupAttributes, not as a top-level property. + // The CFN property "MultiValueHeadersEnabled" does not exist on AWS::ElasticLoadBalancingV2::TargetGroup. + if (att.MultiValueHeaders) + { + _templateWriter.SetToken($"{tgPath}.Properties.TargetGroupAttributes", + new List> + { + new Dictionary + { + { "Key", "lambda.multi_value_headers.enabled" }, + { "Value", "true" } + } + }, TokenType.List); + } + else + { + _templateWriter.RemoveToken($"{tgPath}.Properties.TargetGroupAttributes"); + } + + _templateWriter.SetToken($"{tgPath}.Properties.Targets", new List> + { + new Dictionary + { + { "Id", new Dictionary> { { GET_ATTRIBUTE, new List { lambdaFunction.ResourceName, "Arn" } } } } + } + }, TokenType.List); + } + + // 3. Listener Rule - routes traffic from the ALB listener to the target group + var rulePath = $"Resources.{listenerRuleName}"; + if (!_templateWriter.Exists(rulePath) || + string.Equals(_templateWriter.GetToken($"{rulePath}.Metadata.Tool", string.Empty), CREATION_TOOL, StringComparison.Ordinal)) + { + _templateWriter.SetToken($"{rulePath}.Type", "AWS::ElasticLoadBalancingV2::ListenerRule"); + _templateWriter.SetToken($"{rulePath}.Metadata.Tool", CREATION_TOOL); + + // ListenerArn - handle @reference vs literal ARN + _templateWriter.RemoveToken($"{rulePath}.Properties.ListenerArn"); + if (!string.IsNullOrEmpty(att.ListenerArn) && att.ListenerArn.StartsWith("@")) + { + var refName = att.ListenerArn.Substring(1); + _templateWriter.SetToken($"{rulePath}.Properties.ListenerArn.{REF}", refName); + + // Warn if the referenced resource/parameter doesn't exist in the template + if (!_templateWriter.Exists($"Resources.{refName}") && !_templateWriter.Exists($"{PARAMETERS}.{refName}")) + { + _diagnosticReporter.Report(Diagnostic.Create(DiagnosticDescriptors.AlbListenerReferenceNotFound, Location.None, refName)); + } + } + else + { + _templateWriter.SetToken($"{rulePath}.Properties.ListenerArn", att.ListenerArn); + } + + // Priority + _templateWriter.SetToken($"{rulePath}.Properties.Priority", att.Priority); + + // Conditions + var conditions = new List> + { + new Dictionary + { + { "Field", "path-pattern" }, + { "PathPatternConfig", new Dictionary + { + { "Values", new List { att.PathPattern } } + } + } + } + }; + if (!string.IsNullOrEmpty(att.HostHeader)) + { + conditions.Add(new Dictionary + { + { "Field", "host-header" }, + { "HostHeaderConfig", new Dictionary + { + { "Values", new List { att.HostHeader } } + } + } + }); + } + if (!string.IsNullOrEmpty(att.HttpMethod)) + { + conditions.Add(new Dictionary + { + { "Field", "http-request-method" }, + { "HttpRequestMethodConfig", new Dictionary + { + { "Values", new List { att.HttpMethod.ToUpper() } } + } + } + }); + } + if (!string.IsNullOrEmpty(att.HttpHeaderConditionName) && att.HttpHeaderConditionValues != null && att.HttpHeaderConditionValues.Length > 0) + { + conditions.Add(new Dictionary + { + { "Field", "http-header" }, + { "HttpHeaderConfig", new Dictionary + { + { "HttpHeaderName", att.HttpHeaderConditionName }, + { "Values", att.HttpHeaderConditionValues.ToList() } + } + } + }); + } + if (att.QueryStringConditions != null && att.QueryStringConditions.Length > 0) + { + var keyValuePairs = new List>(); + foreach (var entry in att.QueryStringConditions) + { + var separatorIndex = entry.IndexOf('='); + if (separatorIndex >= 0) + { + var key = entry.Substring(0, separatorIndex); + var value = entry.Substring(separatorIndex + 1); + var kvp = new Dictionary(); + if (!string.IsNullOrEmpty(key)) + { + kvp["Key"] = key; + } + kvp["Value"] = value; + keyValuePairs.Add(kvp); + } + } + if (keyValuePairs.Any()) + { + conditions.Add(new Dictionary + { + { "Field", "query-string" }, + { "QueryStringConfig", new Dictionary + { + { "Values", keyValuePairs } + } + } + }); + } + } + if (att.SourceIpConditions != null && att.SourceIpConditions.Length > 0) + { + conditions.Add(new Dictionary + { + { "Field", "source-ip" }, + { "SourceIpConfig", new Dictionary + { + { "Values", att.SourceIpConditions.ToList() } + } + } + }); + } + _templateWriter.SetToken($"{rulePath}.Properties.Conditions", conditions, TokenType.List); + + // Actions - forward to target group + _templateWriter.SetToken($"{rulePath}.Properties.Actions", new List> + { + new Dictionary + { + { "Type", "forward" }, + { "TargetGroupArn", new Dictionary { { REF, targetGroupName } } } + } + }, TokenType.List); + } + + return new List { permissionName, targetGroupName, listenerRuleName }; + } + + /// + /// Synchronizes ALB resources for a given Lambda function. ALB resources (Permission, TargetGroup, ListenerRule) + /// are standalone top-level CloudFormation resources, so they need separate tracking from SAM events. + /// Previously generated ALB resources that are no longer present in the current compilation are removed. + /// + private void SynchronizeAlbResources(List currentAlbResources, ILambdaFunctionSerializable lambdaFunction) + { + var syncedAlbResourcesPath = $"Resources.{lambdaFunction.ResourceName}.Metadata.SyncedAlbResources"; + + // Get previously synced ALB resources + var previousAlbResources = _templateWriter.GetToken>(syncedAlbResourcesPath, new List()); + + // Remove orphaned ALB resources + var orphanedAlbResources = previousAlbResources.Except(currentAlbResources).ToList(); + foreach (var resourceName in orphanedAlbResources) + { + var resourcePath = $"Resources.{resourceName}"; + // Only remove if it was created by this tool + if (_templateWriter.Exists(resourcePath) && + string.Equals(_templateWriter.GetToken($"{resourcePath}.Metadata.Tool", string.Empty), CREATION_TOOL, StringComparison.Ordinal)) + { + _templateWriter.RemoveToken(resourcePath); + } + } + + // Update synced ALB resources in the template metadata + _templateWriter.RemoveToken(syncedAlbResourcesPath); + if (currentAlbResources.Any()) + _templateWriter.SetToken(syncedAlbResourcesPath, currentAlbResources, TokenType.List); + } /// /// Writes the default values for the Lambda function's metadata and properties. @@ -893,4 +1289,4 @@ private void SynchronizeEventsAndProperties(List syncedEvents, Dictionar _templateWriter.SetToken(syncedEventPropertiesPath, syncedEventProperties, TokenType.KeyVal); } } -} \ No newline at end of file +} diff --git a/Libraries/src/Amazon.Lambda.Annotations/ALB/ALBApiAttribute.cs b/Libraries/src/Amazon.Lambda.Annotations/ALB/ALBApiAttribute.cs new file mode 100644 index 000000000..d73f4e365 --- /dev/null +++ b/Libraries/src/Amazon.Lambda.Annotations/ALB/ALBApiAttribute.cs @@ -0,0 +1,188 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.RegularExpressions; + +namespace Amazon.Lambda.Annotations.ALB +{ + /// + /// Configures the Lambda function to be called from an Application Load Balancer. + /// The source generator will create the necessary CloudFormation resources + /// (TargetGroup, ListenerRule, Lambda Permission) to wire the Lambda function + /// as a target behind the specified ALB listener. + /// + /// + /// The listener ARN (or template reference), path pattern, and priority are required. + /// See ALB Lambda documentation. + /// + [AttributeUsage(AttributeTargets.Method)] + public class ALBApiAttribute : Attribute + { + // Only allow alphanumeric characters for resource names + private static readonly Regex _resourceNameRegex = new Regex("^[a-zA-Z0-9]+$"); + + /// + /// The ARN of the existing ALB listener, or a "@ResourceName" reference to a + /// listener resource or parameter defined in the CloudFormation template. + /// To reference a resource in the serverless template, prefix the resource name with "@" symbol. + /// + public string ListenerArn { get; set; } + + /// + /// The path pattern condition for the ALB listener rule (e.g., "/api/orders/*"). + /// ALB supports wildcard path patterns using "*" and "?" characters. + /// + public string PathPattern { get; set; } + + /// + /// The priority of the ALB listener rule. Must be between 1 and 50000. + /// Lower numbers are evaluated first. Each rule on a listener must have a unique priority. + /// + public int Priority { get; set; } + + /// + /// Whether multi-value headers are enabled on the ALB target group. Default: false. + /// When true, the Lambda function should use MultiValueHeaders and + /// MultiValueQueryStringParameters on the request and response objects. + /// When false, use Headers and QueryStringParameters instead. + /// + public bool MultiValueHeaders + { + get => multiValueHeaders.GetValueOrDefault(); + set => multiValueHeaders = value; + } + private bool? multiValueHeaders { get; set; } + internal bool IsMultiValueHeadersSet => multiValueHeaders.HasValue; + + /// + /// Optional host header condition for the listener rule (e.g., "api.example.com"). + /// When specified, the rule will only match requests with this host header value. + /// + public string HostHeader { get; set; } + + /// + /// Optional HTTP method condition for the listener rule (e.g., "GET", "POST"). + /// When specified, the rule will only match requests with this HTTP method. + /// Leave null to match all HTTP methods. + /// + public string HttpMethod { get; set; } + + /// + /// Optional HTTP header name for an http-header listener rule condition (e.g., "X-Environment", "User-Agent"). + /// Must be used together with . + /// The header name is not case-sensitive. + /// + public string HttpHeaderConditionName { get; set; } + + /// + /// Optional HTTP header values for an http-header listener rule condition (e.g., new[] { "dev", "*Chrome*" }). + /// Supports wildcards (* and ?). Must be used together with . + /// Up to 3 match evaluations per condition. + /// + public string[] HttpHeaderConditionValues { get; set; } + + /// + /// Optional query string key/value pairs for a query-string listener rule condition. + /// Format: "key=value" pairs. Use "=value" (empty key) to match any key with that value. + /// Supports wildcards (* and ?). + /// Example: new[] { "version=v1", "=*example*" } + /// + public string[] QueryStringConditions { get; set; } + + /// + /// Optional source IP CIDR blocks for a source-ip listener rule condition. + /// Example: new[] { "192.0.2.0/24", "198.51.100.10/32" } + /// Supports both IPv4 and IPv6 addresses in CIDR format. + /// + public string[] SourceIpConditions { get; set; } + + /// + /// The CloudFormation resource name prefix for the generated ALB resources + /// (TargetGroup, ListenerRule, Permission). Defaults to "{LambdaResourceName}ALB". + /// Must only contain alphanumeric characters. + /// + public string ResourceName + { + get => resourceName; + set => resourceName = value; + } + private string resourceName { get; set; } + internal bool IsResourceNameSet => resourceName != null; + + /// + /// Creates an instance of the class. + /// + /// The ARN of the ALB listener, or a "@ResourceName" reference to a template resource. + /// The path pattern condition (e.g., "/api/orders/*"). + /// The listener rule priority (1-50000). + public ALBApiAttribute(string listenerArn, string pathPattern, int priority) + { + ListenerArn = listenerArn; + PathPattern = pathPattern; + Priority = priority; + } + + /// + /// Validates the attribute properties and returns a list of validation error messages. + /// + internal List Validate() + { + var validationErrors = new List(); + + if (string.IsNullOrEmpty(ListenerArn)) + { + validationErrors.Add($"{nameof(ListenerArn)} is required and cannot be empty."); + } + else if (!ListenerArn.StartsWith("@")) + { + // If it's not a template reference, validate it looks like an ARN + if (!ListenerArn.StartsWith("arn:")) + { + validationErrors.Add($"{nameof(ListenerArn)} = {ListenerArn}. It must be a valid ARN (starting with 'arn:') or a template reference (starting with '@')."); + } + } + + if (string.IsNullOrEmpty(PathPattern)) + { + validationErrors.Add($"{nameof(PathPattern)} is required and cannot be empty."); + } + + if (Priority < 1 || Priority > 50000) + { + validationErrors.Add($"{nameof(Priority)} = {Priority}. It must be between 1 and 50000."); + } + + if (IsResourceNameSet && !_resourceNameRegex.IsMatch(ResourceName)) + { + validationErrors.Add($"{nameof(ResourceName)} = {ResourceName}. It must only contain alphanumeric characters and must not be an empty string."); + } + + if (!string.IsNullOrEmpty(HttpMethod)) + { + var validMethods = new HashSet(StringComparer.OrdinalIgnoreCase) + { + "GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS" + }; + if (!validMethods.Contains(HttpMethod)) + { + validationErrors.Add($"{nameof(HttpMethod)} = {HttpMethod}. It must be a valid HTTP method (GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS)."); + } + } + + // Validate http-header condition: both name and values must be set together + if (!string.IsNullOrEmpty(HttpHeaderConditionName) && (HttpHeaderConditionValues == null || HttpHeaderConditionValues.Length == 0)) + { + validationErrors.Add($"{nameof(HttpHeaderConditionName)} is set to '{HttpHeaderConditionName}' but {nameof(HttpHeaderConditionValues)} is not set. Both must be specified together."); + } + if ((HttpHeaderConditionValues != null && HttpHeaderConditionValues.Length > 0) && string.IsNullOrEmpty(HttpHeaderConditionName)) + { + validationErrors.Add($"{nameof(HttpHeaderConditionValues)} is set but {nameof(HttpHeaderConditionName)} is not set. Both must be specified together."); + } + + return validationErrors; + } + } +} diff --git a/Libraries/src/Amazon.Lambda.Annotations/ALB/FromBodyAttribute.cs b/Libraries/src/Amazon.Lambda.Annotations/ALB/FromBodyAttribute.cs new file mode 100644 index 000000000..73e01adbe --- /dev/null +++ b/Libraries/src/Amazon.Lambda.Annotations/ALB/FromBodyAttribute.cs @@ -0,0 +1,18 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +using System; + +namespace Amazon.Lambda.Annotations.ALB +{ + /// + /// Maps this parameter to the HTTP request body from the ALB request + /// + /// + /// If the parameter is a complex type then the request body will be assumed to be JSON and deserialized into the type. + /// + [AttributeUsage(AttributeTargets.Parameter)] + public class FromBodyAttribute : Attribute + { + } +} diff --git a/Libraries/src/Amazon.Lambda.Annotations/ALB/FromHeaderAttribute.cs b/Libraries/src/Amazon.Lambda.Annotations/ALB/FromHeaderAttribute.cs new file mode 100644 index 000000000..1e8e07cd9 --- /dev/null +++ b/Libraries/src/Amazon.Lambda.Annotations/ALB/FromHeaderAttribute.cs @@ -0,0 +1,19 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +using System; + +namespace Amazon.Lambda.Annotations.ALB +{ + /// + /// Maps this parameter to an HTTP header value from the ALB request + /// + [AttributeUsage(AttributeTargets.Parameter)] + public class FromHeaderAttribute : Attribute, INamedAttribute + { + /// + /// Name of the header. If not specified, the parameter name is used. + /// + public string Name { get; set; } + } +} diff --git a/Libraries/src/Amazon.Lambda.Annotations/ALB/FromQueryAttribute.cs b/Libraries/src/Amazon.Lambda.Annotations/ALB/FromQueryAttribute.cs new file mode 100644 index 000000000..30d229386 --- /dev/null +++ b/Libraries/src/Amazon.Lambda.Annotations/ALB/FromQueryAttribute.cs @@ -0,0 +1,19 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +using System; + +namespace Amazon.Lambda.Annotations.ALB +{ + /// + /// Maps this parameter to a query string parameter from the ALB request + /// + [AttributeUsage(AttributeTargets.Parameter)] + public class FromQueryAttribute : Attribute, INamedAttribute + { + /// + /// Name of the query string parameter. If not specified, the parameter name is used. + /// + public string Name { get; set; } + } +} diff --git a/Libraries/src/Amazon.Lambda.Annotations/APIGateway/FunctionUrlAttribute.cs b/Libraries/src/Amazon.Lambda.Annotations/APIGateway/FunctionUrlAttribute.cs new file mode 100644 index 000000000..a92387762 --- /dev/null +++ b/Libraries/src/Amazon.Lambda.Annotations/APIGateway/FunctionUrlAttribute.cs @@ -0,0 +1,51 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +using System; + +namespace Amazon.Lambda.Annotations.APIGateway +{ + /// + /// Configures the Lambda function to be invoked via a Lambda Function URL. + /// + /// + /// Function URLs use the same payload format as HTTP API v2 (APIGatewayHttpApiV2ProxyRequest/Response). + /// + [AttributeUsage(AttributeTargets.Method)] + public class FunctionUrlAttribute : Attribute + { + /// + public FunctionUrlAuthType AuthType { get; set; } = FunctionUrlAuthType.NONE; + + /// + /// The allowed origins for CORS requests. Example: new[] { "https://example.com" } + /// + public string[] AllowOrigins { get; set; } + + /// + /// The allowed HTTP methods for CORS requests. Example: new[] { LambdaHttpMethod.Get, LambdaHttpMethod.Post } + /// + public LambdaHttpMethod[] AllowMethods { get; set; } + + /// + /// The allowed headers for CORS requests. + /// + public string[] AllowHeaders { get; set; } + + /// + /// Whether credentials are included in the CORS request. + /// + public bool AllowCredentials { get; set; } + + /// + /// The expose headers for CORS responses. + /// + public string[] ExposeHeaders { get; set; } + + /// + /// The maximum time in seconds that a browser can cache the CORS preflight response. + /// A value of 0 means the property is not set. + /// + public int MaxAge { get; set; } + } +} diff --git a/Libraries/src/Amazon.Lambda.Annotations/APIGateway/FunctionUrlAuthType.cs b/Libraries/src/Amazon.Lambda.Annotations/APIGateway/FunctionUrlAuthType.cs new file mode 100644 index 000000000..31a1c2397 --- /dev/null +++ b/Libraries/src/Amazon.Lambda.Annotations/APIGateway/FunctionUrlAuthType.cs @@ -0,0 +1,21 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +namespace Amazon.Lambda.Annotations.APIGateway +{ + /// + /// The type of authentication for a Lambda Function URL. + /// + public enum FunctionUrlAuthType + { + /// + /// No authentication. Anyone with the Function URL can invoke the function. + /// + NONE, + + /// + /// IAM authentication. Only authenticated IAM users and roles can invoke the function. + /// + AWS_IAM + } +} diff --git a/Libraries/src/Amazon.Lambda.Annotations/APIGateway/HttpApiAuthorizerAttribute.cs b/Libraries/src/Amazon.Lambda.Annotations/APIGateway/HttpApiAuthorizerAttribute.cs index 17b7d0bf7..725c2842a 100644 --- a/Libraries/src/Amazon.Lambda.Annotations/APIGateway/HttpApiAuthorizerAttribute.cs +++ b/Libraries/src/Amazon.Lambda.Annotations/APIGateway/HttpApiAuthorizerAttribute.cs @@ -8,8 +8,8 @@ namespace Amazon.Lambda.Annotations.APIGateway /// /// /// This attribute must be used in conjunction with the . - /// The authorizer function should return - /// when is true, or + /// The authorizer function should return APIGatewayCustomAuthorizerV2SimpleResponse + /// when is true, or APIGatewayCustomAuthorizerV2IamResponse /// when is false. /// /// @@ -45,8 +45,8 @@ public class HttpApiAuthorizerAttribute : Attribute /// Defaults to true for simpler implementation. /// /// - /// When true, the authorizer should return . - /// When false, the authorizer should return . + /// When true, the authorizer should return APIGatewayCustomAuthorizerV2SimpleResponse. + /// When false, the authorizer should return APIGatewayCustomAuthorizerV2IamResponse. /// public bool EnableSimpleResponses { get; set; } = true; diff --git a/Libraries/src/Amazon.Lambda.Annotations/APIGateway/RestApiAuthorizerAttribute.cs b/Libraries/src/Amazon.Lambda.Annotations/APIGateway/RestApiAuthorizerAttribute.cs index b578e0d97..d6db9fa04 100644 --- a/Libraries/src/Amazon.Lambda.Annotations/APIGateway/RestApiAuthorizerAttribute.cs +++ b/Libraries/src/Amazon.Lambda.Annotations/APIGateway/RestApiAuthorizerAttribute.cs @@ -26,7 +26,7 @@ public enum RestApiAuthorizerType /// /// /// This attribute must be used in conjunction with the . - /// The authorizer function should return . + /// The authorizer function should return APIGatewayCustomAuthorizerResponse. /// /// /// diff --git a/Libraries/src/Amazon.Lambda.Annotations/Amazon.Lambda.Annotations.csproj b/Libraries/src/Amazon.Lambda.Annotations/Amazon.Lambda.Annotations.csproj index 15da7004d..1c8dfe8c6 100644 --- a/Libraries/src/Amazon.Lambda.Annotations/Amazon.Lambda.Annotations.csproj +++ b/Libraries/src/Amazon.Lambda.Annotations/Amazon.Lambda.Annotations.csproj @@ -11,7 +11,8 @@ ..\..\..\buildtools\public.snk true - 1.10.0 + 1.14.0 + true diff --git a/Libraries/src/Amazon.Lambda.Annotations/README.md b/Libraries/src/Amazon.Lambda.Annotations/README.md index 75dfaac23..56cb7bbd7 100644 --- a/Libraries/src/Amazon.Lambda.Annotations/README.md +++ b/Libraries/src/Amazon.Lambda.Annotations/README.md @@ -19,6 +19,8 @@ Topics: - [Amazon API Gateway example](#amazon-api-gateway-example) - [Amazon S3 example](#amazon-s3-example) - [SQS Event Example](#sqs-event-example) + - [Application Load Balancer (ALB) Example](#application-load-balancer-alb-example) + - [Lambda Function URL Example](#lambda-function-url-example) - [Custom Lambda Authorizer Example](#custom-lambda-authorizer-example) - [HTTP API Authorizer](#http-api-authorizer) - [REST API Authorizer](#rest-api-authorizer) @@ -852,6 +854,348 @@ The following SQS event source mapping will be generated for the `SQSMessageHand } ``` +## Application Load Balancer (ALB) Example + +This example shows how to use the `ALBApi` attribute to configure a Lambda function as a target behind an [Application Load Balancer](https://docs.aws.amazon.com/elasticloadbalancing/latest/application/lambda-functions.html). Unlike API Gateway event attributes that map to SAM event types, the ALB integration generates standalone CloudFormation resources — a `TargetGroup`, a `ListenerRule`, and a `Lambda::Permission` — to wire the Lambda function to an existing ALB listener. + +The `ALBApi` attribute contains the following properties: + +| Property | Type | Required | Default | Description | +|---|---|---|---|---| +| `ListenerArn` | `string` | Yes | — | The ARN of the existing ALB listener, or a `@ResourceName` reference to a listener resource defined in the CloudFormation template. | +| `PathPattern` | `string` | Yes | — | The path pattern condition for the listener rule (e.g., `"/api/orders/*"`). Supports wildcard characters `*` and `?`. | +| `Priority` | `int` | Yes | — | The listener rule priority (1–50000). Lower numbers are evaluated first. Must be unique per listener. | +| `MultiValueHeaders` | `bool` | No | `false` | When `true`, enables multi-value headers on the target group. The function should then use `MultiValueHeaders` and `MultiValueQueryStringParameters` on request/response objects. | +| `HostHeader` | `string` | No | `null` | Optional host header condition (e.g., `"api.example.com"`). | +| `HttpMethod` | `string` | No | `null` | Optional HTTP method condition (e.g., `"GET"`, `"POST"`). Leave null to match all methods. | +| `ResourceName` | `string` | No | `"{LambdaResourceName}ALB"` | Custom CloudFormation resource name prefix for the generated resources. Must be alphanumeric. | + +The `ALBApi` attribute must be applied to a Lambda method along with the `LambdaFunction` attribute. + +The Lambda method must conform to the following rules when tagged with the `ALBApi` attribute: + +1. It must have at least 1 argument and can have at most 2 arguments. + - The first argument is required and must be of type `ApplicationLoadBalancerRequest` defined in the [Amazon.Lambda.ApplicationLoadBalancerEvents](https://github.com/aws/aws-lambda-dotnet/tree/master/Libraries/src/Amazon.Lambda.ApplicationLoadBalancerEvents) package. + - The second argument is optional and must be of type `ILambdaContext`. +2. The method return type must be `ApplicationLoadBalancerResponse` or `Task`. + +### Prerequisites + +Your CloudFormation template must include an existing ALB and listener. The `ALBApi` attribute references the listener — it does **not** create the ALB or listener for you. You can define them in the same template or reference one that already exists via its ARN. + +### Basic Example + +This example creates a simple hello endpoint behind an ALB listener that is defined elsewhere in the template: + +```csharp +using Amazon.Lambda.Annotations; +using Amazon.Lambda.Annotations.ALB; +using Amazon.Lambda.ApplicationLoadBalancerEvents; +using Amazon.Lambda.Core; +using System.Collections.Generic; + +public class ALBFunctions +{ + [LambdaFunction(ResourceName = "ALBHello", MemorySize = 256, Timeout = 15)] + [ALBApi("@ALBTestListener", "/hello", 1)] + public ApplicationLoadBalancerResponse Hello(ApplicationLoadBalancerRequest request, ILambdaContext context) + { + context.Logger.LogInformation($"Hello endpoint hit. Path: {request.Path}"); + + return new ApplicationLoadBalancerResponse + { + StatusCode = 200, + StatusDescription = "200 OK", + IsBase64Encoded = false, + Headers = new Dictionary + { + { "Content-Type", "application/json" } + }, + Body = $"{{\"message\": \"Hello from ALB Lambda!\", \"path\": \"{request.Path}\"}}" + }; + } +} +``` + +In the example above, `@ALBTestListener` references a listener resource called `ALBTestListener` defined in the same CloudFormation template. The `@` prefix tells the source generator to use a `Ref` intrinsic function instead of a literal ARN string. + +### Using a Literal Listener ARN + +If you want to reference an ALB listener in a different stack or one that was created outside of CloudFormation, use the full ARN: + +```csharp +[LambdaFunction(ResourceName = "ALBHandler")] +[ALBApi("arn:aws:elasticloadbalancing:us-east-1:123456789012:listener/app/my-alb/abc123/def456", "/api/*", 10)] +public ApplicationLoadBalancerResponse HandleRequest(ApplicationLoadBalancerRequest request, ILambdaContext context) +{ + return new ApplicationLoadBalancerResponse + { + StatusCode = 200, + Headers = new Dictionary { { "Content-Type", "application/json" } }, + Body = "{\"status\": \"ok\"}" + }; +} +``` + +### Advanced Example with All Options + +This example shows all optional properties including host header filtering, HTTP method filtering, multi-value headers, and a custom resource name: + +```csharp +[LambdaFunction(ResourceName = "ALBOrders")] +[ALBApi("@MyListener", "/api/orders/*", 5, + MultiValueHeaders = true, + HostHeader = "api.example.com", + HttpMethod = "POST", + ResourceName = "OrdersALB")] +public ApplicationLoadBalancerResponse CreateOrder(ApplicationLoadBalancerRequest request, ILambdaContext context) +{ + // When MultiValueHeaders is true, use MultiValueHeaders and MultiValueQueryStringParameters + var contentTypes = request.MultiValueHeaders?["content-type"]; + + return new ApplicationLoadBalancerResponse + { + StatusCode = 201, + StatusDescription = "201 Created", + MultiValueHeaders = new Dictionary> + { + { "Content-Type", new List { "application/json" } }, + { "X-Custom-Header", new List { "value1", "value2" } } + }, + Body = "{\"orderId\": \"12345\"}" + }; +} +``` + +### Generated CloudFormation Resources + +For each `ALBApi` attribute, the source generator creates three CloudFormation resources. Here is an example of the generated template for the basic hello endpoint: + +```json +"ALBHello": { + "Type": "AWS::Serverless::Function", + "Metadata": { + "Tool": "Amazon.Lambda.Annotations" + }, + "Properties": { + "Runtime": "dotnet8", + "CodeUri": ".", + "MemorySize": 512, + "Timeout": 15, + "Policies": ["AWSLambdaBasicExecutionRole"], + "PackageType": "Zip", + "Handler": "MyProject::MyNamespace.ALBFunctions_Hello_Generated::Hello" + } +}, +"ALBHelloALBPermission": { + "Type": "AWS::Lambda::Permission", + "Metadata": { "Tool": "Amazon.Lambda.Annotations" }, + "Properties": { + "FunctionName": { "Fn::GetAtt": ["ALBHello", "Arn"] }, + "Action": "lambda:InvokeFunction", + "Principal": "elasticloadbalancing.amazonaws.com" + } +}, +"ALBHelloALBTargetGroup": { + "Type": "AWS::ElasticLoadBalancingV2::TargetGroup", + "Metadata": { "Tool": "Amazon.Lambda.Annotations" }, + "DependsOn": "ALBHelloALBPermission", + "Properties": { + "TargetType": "lambda", + "Targets": [ + { "Id": { "Fn::GetAtt": ["ALBHello", "Arn"] } } + ] + } +}, +"ALBHelloALBListenerRule": { + "Type": "AWS::ElasticLoadBalancingV2::ListenerRule", + "Metadata": { "Tool": "Amazon.Lambda.Annotations" }, + "Properties": { + "ListenerArn": { "Ref": "ALBTestListener" }, + "Priority": 1, + "Conditions": [ + { "Field": "path-pattern", "Values": ["/hello"] } + ], + "Actions": [ + { "Type": "forward", "TargetGroupArn": { "Ref": "ALBHelloALBTargetGroup" } } + ] + } +} +``` + +When `MultiValueHeaders` is set to `true`, the target group will include a `TargetGroupAttributes` section: + +```json +"TargetGroupAttributes": [ + { "Key": "lambda.multi_value_headers.enabled", "Value": "true" } +] +``` + +When `HostHeader` or `HttpMethod` are specified, additional conditions are added to the listener rule: + +```json +"Conditions": [ + { "Field": "path-pattern", "Values": ["/api/orders/*"] }, + { "Field": "host-header", "Values": ["api.example.com"] }, + { "Field": "http-request-method", "Values": ["POST"] } +] +``` + +### Setting Up the ALB in the Template + +The `ALBApi` attribute requires an existing ALB listener. Here is a minimal example of the infrastructure resources you would add to your `serverless.template`: + +```json +{ + "MyVPC": { "Type": "AWS::EC2::VPC", "Properties": { "CidrBlock": "10.0.0.0/16" } }, + "MySubnet1": { "Type": "AWS::EC2::Subnet", "Properties": { "VpcId": { "Ref": "MyVPC" }, "CidrBlock": "10.0.1.0/24" } }, + "MySubnet2": { "Type": "AWS::EC2::Subnet", "Properties": { "VpcId": { "Ref": "MyVPC" }, "CidrBlock": "10.0.2.0/24" } }, + "MySecurityGroup": { "Type": "AWS::EC2::SecurityGroup", "Properties": { "GroupDescription": "ALB SG", "VpcId": { "Ref": "MyVPC" } } }, + "MyALB": { + "Type": "AWS::ElasticLoadBalancingV2::LoadBalancer", + "Properties": { + "Type": "application", + "Scheme": "internet-facing", + "Subnets": [{ "Ref": "MySubnet1" }, { "Ref": "MySubnet2" }], + "SecurityGroups": [{ "Ref": "MySecurityGroup" }] + } + }, + "MyListener": { + "Type": "AWS::ElasticLoadBalancingV2::Listener", + "Properties": { + "LoadBalancerArn": { "Ref": "MyALB" }, + "Port": 80, + "Protocol": "HTTP", + "DefaultActions": [{ "Type": "fixed-response", "FixedResponseConfig": { "StatusCode": "404" } }] + } + } +} +``` + +Then your Lambda function references `@MyListener` in the `ALBApi` attribute. + +## SNS Event Example +This example shows how to use the `SNSEvent` attribute to subscribe a Lambda function to an SNS topic. + +The `SNSEvent` attribute contains the following properties: +* **Topic** (Required) - The SNS topic ARN or a reference to an SNS topic resource prefixed with "@". +* **ResourceName** (Optional) - The CloudFormation resource name for the SNS event. +* **FilterPolicy** (Optional) - A JSON filter policy applied to the subscription. +* **Enabled** (Optional) - If false, the event source is disabled. Default is true. + +```csharp +[LambdaFunction(ResourceName = "SNSMessageHandler", Policies = "AWSLambdaSNSTopicExecutionRole")] +[SNSEvent("@TestTopic", ResourceName = "TestTopicEvent", FilterPolicy = "{ \"store\": [\"example_corp\"] }")] +public void HandleMessage(SNSEvent evnt, ILambdaContext lambdaContext) +{ + lambdaContext.Logger.Log($"Received {evnt.Records.Count} messages"); +} +``` + +## Lambda Function URL Example + +[Lambda Function URLs](https://docs.aws.amazon.com/lambda/latest/dg/lambda-urls.html) provide a dedicated HTTPS endpoint for your Lambda function without needing API Gateway or an Application Load Balancer. The `FunctionUrl` attribute configures the function to be invoked via a Function URL. Function URLs use the same payload format as HTTP API v2 (`APIGatewayHttpApiV2ProxyRequest`/`APIGatewayHttpApiV2ProxyResponse`). + +The `FunctionUrl` attribute contains the following properties: + +| Property | Type | Required | Default | Description | +|---|---|---|---|---| +| `AuthType` | `FunctionUrlAuthType` | No | `NONE` | The authentication type: `NONE` (public) or `AWS_IAM` (IAM-authenticated). | +| `AllowOrigins` | `string[]` | No | `null` | Allowed origins for CORS requests (e.g., `new[] { "https://example.com" }`). | +| `AllowMethods` | `LambdaHttpMethod[]` | No | `null` | Allowed HTTP methods for CORS requests, using the `LambdaHttpMethod` enum (e.g., `new[] { LambdaHttpMethod.Get, LambdaHttpMethod.Post }`). | +| `AllowHeaders` | `string[]` | No | `null` | Allowed headers for CORS requests. | +| `ExposeHeaders` | `string[]` | No | `null` | Headers to expose in CORS responses. | +| `AllowCredentials` | `bool` | No | `false` | Whether credentials are included in the CORS request. | +| `MaxAge` | `int` | No | `0` | Maximum time in seconds that a browser can cache the CORS preflight response. `0` means not set. | + +### Basic Example + +A simple function with a public Function URL (no authentication): + +```csharp +using Amazon.Lambda.Annotations; +using Amazon.Lambda.Annotations.APIGateway; +using Amazon.Lambda.Core; + +public class Functions +{ + [LambdaFunction(PackageType = LambdaPackageType.Image)] + [FunctionUrl(AuthType = FunctionUrlAuthType.NONE)] + public IHttpResult GetItems([FromQuery] string category, ILambdaContext context) + { + context.Logger.LogLine($"Getting items for category: {category}"); + return HttpResults.Ok(new { items = new[] { "item1", "item2" }, category }); + } +} +``` + +### With IAM Authentication + +Use `FunctionUrlAuthType.AWS_IAM` to require IAM authentication for the Function URL: + +```csharp +[LambdaFunction(PackageType = LambdaPackageType.Image)] +[FunctionUrl(AuthType = FunctionUrlAuthType.AWS_IAM)] +public IHttpResult SecureEndpoint(ILambdaContext context) +{ + return HttpResults.Ok(new { message = "This endpoint requires IAM auth" }); +} +``` + +### With CORS Configuration + +Configure CORS settings directly on the attribute. The `AllowMethods` property uses the type-safe `LambdaHttpMethod` enum, consistent with the `HttpApi` and `RestApi` attributes: + +```csharp +[LambdaFunction(PackageType = LambdaPackageType.Image)] +[FunctionUrl( + AuthType = FunctionUrlAuthType.NONE, + AllowOrigins = new[] { "https://example.com", "https://app.example.com" }, + AllowMethods = new[] { LambdaHttpMethod.Get, LambdaHttpMethod.Post }, + AllowHeaders = new[] { "Content-Type", "Authorization" }, + AllowCredentials = true, + MaxAge = 3600)] +public IHttpResult GetData([FromQuery] string id, ILambdaContext context) +{ + return HttpResults.Ok(new { id, data = "some data" }); +} +``` + +### Generated CloudFormation + +The source generator creates a `FunctionUrlConfig` property on the Lambda function resource (not a SAM event source). Here is an example with CORS: + +```json +"GetDataFunction": { + "Type": "AWS::Serverless::Function", + "Metadata": { + "Tool": "Amazon.Lambda.Annotations", + "SyncedFunctionUrlConfig": true + }, + "Properties": { + "PackageType": "Image", + "ImageUri": ".", + "ImageConfig": { + "Command": ["MyAssembly::MyNamespace.Functions_GetData_Generated::GetData"] + }, + "MemorySize": 512, + "Timeout": 30, + "FunctionUrlConfig": { + "AuthType": "NONE", + "Cors": { + "AllowOrigins": ["https://example.com", "https://app.example.com"], + "AllowMethods": ["GET", "POST"], + "AllowHeaders": ["Content-Type", "Authorization"], + "AllowCredentials": true, + "MaxAge": 3600 + } + } + } +} +``` + +> **Note:** Unlike `HttpApi` and `RestApi` which create SAM event sources, `FunctionUrl` configures the `FunctionUrlConfig` property directly on the function resource. If the `FunctionUrl` attribute is removed from the code, the source generator will automatically clean up the `FunctionUrlConfig` from the CloudFormation template. + ## Custom Lambda Authorizer Example Lambda Annotations supports defining custom Lambda authorizers using attributes. Custom authorizers let you control access to your API Gateway endpoints by running a Lambda function that validates tokens or request parameters before the target function is invoked. The source generator automatically wires up the authorizer resources and references in the CloudFormation template. @@ -1198,7 +1542,13 @@ parameter to the `LambdaFunction` must be the event object and the event source * RestApiAuthorizer * Marks a Lambda function as a REST API (API Gateway V1) custom authorizer. The authorizer name is automatically derived from the method name. Other functions reference it via `RestApi.Authorizer` using `nameof()`. Use the `Type` property to choose between `Token` and `Request` authorizer types. * SQSEvent - * Sets up event source mapping between the Lambda function and SQS queues. The SQS queue ARN is required to be set on the attribute. If users want to pass a reference to an existing SQS queue resource defined in their CloudFormation template, they can pass the SQS queue resource name prefixed with the '@' symbol. + * Sets up event source mapping between the Lambda function and SQS queues. The SQS queue ARN is required to be set on the attribute. If users want to pass a reference to an existing SQS queue resource defined in their CloudFormation template, they can pass the SQS queue resource name prefixed with the '@' symbol. +* SNSEvent + * Subscribes the Lambda function to an SNS topic. The topic ARN or resource reference (prefixed with '@') is required. +* ALBApi + * Configures the Lambda function to be called from an Application Load Balancer. The listener ARN (or `@ResourceName` template reference), path pattern, and priority are required. The source generator creates standalone CloudFormation resources (TargetGroup, ListenerRule, Lambda Permission) rather than SAM event types. +* FunctionUrl + * Configures the Lambda function to be invoked via a Lambda Function URL. Supports `AuthType` (`NONE` or `AWS_IAM`) and CORS configuration including `AllowMethods` (using the `LambdaHttpMethod` enum), `AllowOrigins`, `AllowHeaders`, `AllowCredentials`, and `MaxAge`. The source generator writes a `FunctionUrlConfig` property on the function resource rather than a SAM event source. ### Parameter Attributes @@ -1277,3 +1627,5 @@ The content type is determined using the following rules. ## Project References If API Gateway event attributes, such as `RestAPI` or `HttpAPI`, are being used then a package reference to `Amazon.Lambda.APIGatewayEvents` must be added to the project, otherwise the project will not compile. We do not include it by default in order to keep the `Amazon.Lambda.Annotations` library lightweight. + +Similarly, if the `ALBApi` attribute is being used then a package reference to `Amazon.Lambda.ApplicationLoadBalancerEvents` must be added to the project. This provides the `ApplicationLoadBalancerRequest` and `ApplicationLoadBalancerResponse` types used by ALB Lambda functions. diff --git a/Libraries/src/Amazon.Lambda.Annotations/S3/S3EventAttribute.cs b/Libraries/src/Amazon.Lambda.Annotations/S3/S3EventAttribute.cs new file mode 100644 index 000000000..13bc79095 --- /dev/null +++ b/Libraries/src/Amazon.Lambda.Annotations/S3/S3EventAttribute.cs @@ -0,0 +1,129 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +using System; +using System.Collections.Generic; +using System.Text.RegularExpressions; + +namespace Amazon.Lambda.Annotations.S3 +{ + /// + /// This attribute defines the S3 event source configuration for a Lambda function. + /// + [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] + public class S3EventAttribute : Attribute + { + private static readonly Regex _resourceNameRegex = new Regex("^[a-zA-Z0-9]+$"); + + /// + /// The S3 bucket that will act as the event trigger for the Lambda function. + /// This must be a reference to an S3 bucket resource defined in the serverless template, prefixed with "@". + /// + public string Bucket { get; set; } + + /// + /// The CloudFormation resource name for the S3 event. By default this is derived from the Bucket reference without the "@" prefix. + /// + public string ResourceName + { + get + { + if (IsResourceNameSet) + return resourceName; + if (!string.IsNullOrEmpty(Bucket) && Bucket.StartsWith("@")) + return Bucket.Substring(1); + return Bucket; + } + set => resourceName = value; + } + private string resourceName = null; + internal bool IsResourceNameSet => resourceName != null; + + /// + /// Semicolon-separated list of S3 event types. Default is 's3:ObjectCreated:*'. + /// + public string Events + { + get => events ?? "s3:ObjectCreated:*"; + set => events = value; + } + private string events = null; + internal bool IsEventsSet => events != null; + + /// + /// S3 key prefix filter for the event notification. + /// + public string FilterPrefix + { + get => filterPrefix; + set => filterPrefix = value; + } + private string filterPrefix = null; + internal bool IsFilterPrefixSet => filterPrefix != null; + + /// + /// S3 key suffix filter for the event notification. + /// + public string FilterSuffix + { + get => filterSuffix; + set => filterSuffix = value; + } + private string filterSuffix = null; + internal bool IsFilterSuffixSet => filterSuffix != null; + + /// + /// If set to false, the event source will be disabled. Default value is true. + /// + public bool Enabled + { + get => enabled.GetValueOrDefault(true); + set => enabled = value; + } + private bool? enabled; + internal bool IsEnabledSet => enabled.HasValue; + + /// + /// Creates an instance of the class. + /// + /// property + public S3EventAttribute(string bucket) + { + Bucket = bucket; + } + + internal List Validate() + { + var validationErrors = new List(); + + if (string.IsNullOrEmpty(Bucket)) + { + validationErrors.Add($"{nameof(S3EventAttribute.Bucket)} is required and must not be empty"); + } + else if (!Bucket.StartsWith("@")) + { + validationErrors.Add($"{nameof(S3EventAttribute.Bucket)} = {Bucket}. S3 event sources require a reference to an S3 bucket resource in the serverless template. Prefix the resource name with '@'"); + } + else + { + var bucketResourceName = Bucket.Substring(1); + if (!_resourceNameRegex.IsMatch(bucketResourceName)) + { + validationErrors.Add($"{nameof(S3EventAttribute.Bucket)} = {Bucket}. The referenced S3 bucket resource name must not be empty and must only contain alphanumeric characters after the '@' prefix"); + } + } + + if (IsResourceNameSet && !_resourceNameRegex.IsMatch(ResourceName)) + { + validationErrors.Add($"{nameof(S3EventAttribute.ResourceName)} = {ResourceName}. It must only contain alphanumeric characters and must not be an empty string"); + } + + if (string.IsNullOrEmpty(Events)) + { + validationErrors.Add($"{nameof(S3EventAttribute.Events)} must not be empty"); + } + + return validationErrors; + } + } +} diff --git a/Libraries/src/Amazon.Lambda.Annotations/SNS/SNSEventAttribute.cs b/Libraries/src/Amazon.Lambda.Annotations/SNS/SNSEventAttribute.cs new file mode 100644 index 000000000..044b26499 --- /dev/null +++ b/Libraries/src/Amazon.Lambda.Annotations/SNS/SNSEventAttribute.cs @@ -0,0 +1,116 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.RegularExpressions; + +namespace Amazon.Lambda.Annotations.SNS +{ + /// + /// This attribute defines the SNS event source configuration for a Lambda function. + /// + [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] + public class SNSEventAttribute : Attribute + { + private static readonly Regex _resourceNameRegex = new Regex("^[a-zA-Z0-9]+$"); + + /// + /// The SNS topic that will act as the event trigger for the Lambda function. + /// This can either be the topic ARN or reference to the SNS topic resource that is already defined in the serverless template. + /// To reference an SNS topic resource in the serverless template, prefix the resource name with "@" symbol. + /// + public string Topic { get; set; } + + /// + /// The CloudFormation resource name for the SNS event. By default this is set to the SNS topic name if the is set to an SNS topic ARN. + /// If is set to an existing CloudFormation resource, than that is used as the default value without the "@" prefix. + /// + public string ResourceName + { + get + { + if (IsResourceNameSet) + { + return resourceName; + } + if (string.IsNullOrEmpty(Topic)) + { + return string.Empty; + } + if (Topic.StartsWith("@")) + { + return Topic.Substring(1); + } + + var arnTokens = Topic.Split(new char[] { ':' }, 6); + if (arnTokens.Length < 6) + { + return Topic; + } + var topicName = arnTokens[5]; + var sanitizedTopicName = string.Join(string.Empty, topicName.Where(char.IsLetterOrDigit)); + return sanitizedTopicName; + } + set => resourceName = value; + } + + private string resourceName { get; set; } = null; + internal bool IsResourceNameSet => resourceName != null; + + /// + /// A JSON filter policy that is applied to the SNS subscription. + /// Only messages matching the filter policy will be delivered to the Lambda function. + /// + public string FilterPolicy { get; set; } = null; + internal bool IsFilterPolicySet => FilterPolicy != null; + + /// + /// If set to false, the event source will be disabled. + /// Default value is true. + /// + public bool Enabled + { + get => enabled.GetValueOrDefault(true); + set => enabled = value; + } + private bool? enabled { get; set; } + internal bool IsEnabledSet => enabled.HasValue; + + /// + /// Creates an instance of the class. + /// + /// property + public SNSEventAttribute(string topic) + { + Topic = topic; + } + + internal List Validate() + { + var validationErrors = new List(); + + if (string.IsNullOrEmpty(Topic)) + { + validationErrors.Add($"{nameof(SNSEventAttribute.Topic)} is required and must not be null or empty"); + return validationErrors; + } + + if (!Topic.StartsWith("@")) + { + var arnTokens = Topic.Split(new char[] { ':' }, 6); + if (arnTokens.Length != 6) + { + validationErrors.Add($"{nameof(SNSEventAttribute.Topic)} = {Topic}. The SNS topic ARN is invalid. The ARN format is 'arn::sns:::'"); + } + } + if (IsResourceNameSet && !_resourceNameRegex.IsMatch(ResourceName)) + { + validationErrors.Add($"{nameof(SNSEventAttribute.ResourceName)} = {ResourceName}. It must only contain alphanumeric characters and must not be an empty string"); + } + + return validationErrors; + } + } +} diff --git a/Libraries/src/Amazon.Lambda.Annotations/SQS/SQSEventAttribute.cs b/Libraries/src/Amazon.Lambda.Annotations/SQS/SQSEventAttribute.cs index 3358eee36..e3d72464f 100644 --- a/Libraries/src/Amazon.Lambda.Annotations/SQS/SQSEventAttribute.cs +++ b/Libraries/src/Amazon.Lambda.Annotations/SQS/SQSEventAttribute.cs @@ -60,7 +60,7 @@ public string ResourceName /// public bool Enabled { - get => enabled.GetValueOrDefault(); + get => enabled.GetValueOrDefault(true); set => enabled = value; } private bool? enabled { get; set; } diff --git a/Libraries/src/Amazon.Lambda.AspNetCoreServer.Hosting/Amazon.Lambda.AspNetCoreServer.Hosting.csproj b/Libraries/src/Amazon.Lambda.AspNetCoreServer.Hosting/Amazon.Lambda.AspNetCoreServer.Hosting.csproj index 2ed732314..a22fd248c 100644 --- a/Libraries/src/Amazon.Lambda.AspNetCoreServer.Hosting/Amazon.Lambda.AspNetCoreServer.Hosting.csproj +++ b/Libraries/src/Amazon.Lambda.AspNetCoreServer.Hosting/Amazon.Lambda.AspNetCoreServer.Hosting.csproj @@ -4,7 +4,7 @@ Package for running ASP.NET Core applications using the Minimal API style as a AWS Lambda function. - net6.0;net8.0 + net8.0;net10.0 enable enable 1.10.0 diff --git a/Libraries/src/Amazon.Lambda.AspNetCoreServer.Hosting/HostingOptions.cs b/Libraries/src/Amazon.Lambda.AspNetCoreServer.Hosting/HostingOptions.cs index d4fd7937c..d5d435e89 100644 --- a/Libraries/src/Amazon.Lambda.AspNetCoreServer.Hosting/HostingOptions.cs +++ b/Libraries/src/Amazon.Lambda.AspNetCoreServer.Hosting/HostingOptions.cs @@ -9,6 +9,12 @@ namespace Amazon.Lambda.AspNetCoreServer.Hosting; /// public class HostingOptions { + internal const string ParameterizedPreviewMessage = + "Response streaming is in preview till a new version of .NET Lambda runtime client that supports response streaming " + + "has been deployed to the .NET Lambda managed runtime. Till deployment has been made the feature can be used by deploying as an " + + "executable including the latest version of Amazon.Lambda.RuntimeSupport and setting the \"EnablePreviewFeatures\" in the Lambda " + + "project file to \"true\""; + /// /// The ILambdaSerializer used by Lambda to convert the incoming event JSON into the .NET event type and serialize the .NET response type /// back to JSON to return to Lambda. @@ -27,6 +33,15 @@ public class HostingOptions /// public bool IncludeUnhandledExceptionDetailInResponse { get; set; } = false; + /// + /// When true, the Lambda hosting server enables Lambda response streaming behavior + /// when invoking FunctionHandlerAsync. In streaming mode, + /// FunctionHandlerAsync writes directly to the Lambda response stream and + /// returns null. Requires net8.0 or later. + /// + [System.Runtime.Versioning.RequiresPreviewFeatures(ParameterizedPreviewMessage)] + public bool EnableResponseStreaming { get; set; } = false; + /// /// Callback invoked after request marshalling to customize the HTTP request feature. /// Receives the IHttpRequestFeature, Lambda request object, and ILambdaContext. diff --git a/Libraries/src/Amazon.Lambda.AspNetCoreServer.Hosting/Internal/GetBeforeSnapshotRequestsCollector.cs b/Libraries/src/Amazon.Lambda.AspNetCoreServer.Hosting/Internal/GetBeforeSnapshotRequestsCollector.cs index 8cbb12d8f..1d9ee854f 100644 --- a/Libraries/src/Amazon.Lambda.AspNetCoreServer.Hosting/Internal/GetBeforeSnapshotRequestsCollector.cs +++ b/Libraries/src/Amazon.Lambda.AspNetCoreServer.Hosting/Internal/GetBeforeSnapshotRequestsCollector.cs @@ -5,7 +5,6 @@ namespace Amazon.Lambda.AspNetCoreServer.Hosting.Internal; -#if NET8_0_OR_GREATER /// /// Helper class for storing Requests for /// @@ -14,4 +13,3 @@ internal class GetBeforeSnapshotRequestsCollector { public HttpRequestMessage? Request { get; set; } } -#endif diff --git a/Libraries/src/Amazon.Lambda.AspNetCoreServer.Hosting/Internal/LambdaRuntimeSupportServer.cs b/Libraries/src/Amazon.Lambda.AspNetCoreServer.Hosting/Internal/LambdaRuntimeSupportServer.cs index f50a37f7b..4218d463e 100644 --- a/Libraries/src/Amazon.Lambda.AspNetCoreServer.Hosting/Internal/LambdaRuntimeSupportServer.cs +++ b/Libraries/src/Amazon.Lambda.AspNetCoreServer.Hosting/Internal/LambdaRuntimeSupportServer.cs @@ -78,8 +78,13 @@ public APIGatewayHttpApiV2LambdaRuntimeSupportServer(IServiceProvider servicePro /// protected override HandlerWrapper CreateHandlerWrapper(IServiceProvider serviceProvider) { - var handler = new APIGatewayHttpApiV2MinimalApi(serviceProvider).FunctionHandlerAsync; - return HandlerWrapper.GetHandlerWrapper(handler, this.Serializer); + var handler = new APIGatewayHttpApiV2MinimalApi(serviceProvider); +#pragma warning disable CA2252 + var hostingOptions = serviceProvider.GetService(); + handler.EnableResponseStreaming = hostingOptions?.EnableResponseStreaming ?? false; +#pragma warning restore CA2252 + Func> bufferedHandler = handler.FunctionHandlerAsync; + return HandlerWrapper.GetHandlerWrapper(bufferedHandler, this.Serializer); } /// @@ -87,9 +92,7 @@ protected override HandlerWrapper CreateHandlerWrapper(IServiceProvider serviceP /// public class APIGatewayHttpApiV2MinimalApi : APIGatewayHttpApiV2ProxyFunction { - #if NET8_0_OR_GREATER private readonly IEnumerable _beforeSnapshotRequestsCollectors; - #endif private readonly HostingOptions? _hostingOptions; /// @@ -99,9 +102,7 @@ public class APIGatewayHttpApiV2MinimalApi : APIGatewayHttpApiV2ProxyFunction public APIGatewayHttpApiV2MinimalApi(IServiceProvider serviceProvider) : base(serviceProvider) { - #if NET8_0_OR_GREATER _beforeSnapshotRequestsCollectors = serviceProvider.GetServices(); - #endif // Retrieve HostingOptions from service provider (may be null for backward compatibility) _hostingOptions = serviceProvider.GetService(); @@ -127,14 +128,12 @@ public APIGatewayHttpApiV2MinimalApi(IServiceProvider serviceProvider) } } - #if NET8_0_OR_GREATER protected override IEnumerable GetBeforeSnapshotRequests() { foreach (var collector in _beforeSnapshotRequestsCollectors) if (collector.Request != null) yield return collector.Request; } - #endif protected override void PostMarshallRequestFeature(IHttpRequestFeature aspNetCoreRequestFeature, APIGatewayEvents.APIGatewayHttpApiV2ProxyRequest lambdaRequest, ILambdaContext lambdaContext) { @@ -208,8 +207,13 @@ public APIGatewayRestApiLambdaRuntimeSupportServer(IServiceProvider serviceProvi /// protected override HandlerWrapper CreateHandlerWrapper(IServiceProvider serviceProvider) { - var handler = new APIGatewayRestApiMinimalApi(serviceProvider).FunctionHandlerAsync; - return HandlerWrapper.GetHandlerWrapper(handler, this.Serializer); + var handler = new APIGatewayRestApiMinimalApi(serviceProvider); +#pragma warning disable CA2252 + var hostingOptions = serviceProvider.GetService(); + handler.EnableResponseStreaming = hostingOptions?.EnableResponseStreaming ?? false; +#pragma warning restore CA2252 + Func> bufferedHandler = handler.FunctionHandlerAsync; + return HandlerWrapper.GetHandlerWrapper(bufferedHandler, this.Serializer); } /// @@ -217,9 +221,7 @@ protected override HandlerWrapper CreateHandlerWrapper(IServiceProvider serviceP /// public class APIGatewayRestApiMinimalApi : APIGatewayProxyFunction { - #if NET8_0_OR_GREATER private readonly IEnumerable _beforeSnapshotRequestsCollectors; - #endif private readonly HostingOptions? _hostingOptions; /// @@ -229,9 +231,7 @@ public class APIGatewayRestApiMinimalApi : APIGatewayProxyFunction public APIGatewayRestApiMinimalApi(IServiceProvider serviceProvider) : base(serviceProvider) { - #if NET8_0_OR_GREATER _beforeSnapshotRequestsCollectors = serviceProvider.GetServices(); - #endif // Retrieve HostingOptions from service provider (may be null for backward compatibility) _hostingOptions = serviceProvider.GetService(); @@ -257,14 +257,12 @@ public APIGatewayRestApiMinimalApi(IServiceProvider serviceProvider) } } - #if NET8_0_OR_GREATER protected override IEnumerable GetBeforeSnapshotRequests() { foreach (var collector in _beforeSnapshotRequestsCollectors) if (collector.Request != null) yield return collector.Request; } - #endif protected override void PostMarshallRequestFeature(IHttpRequestFeature aspNetCoreRequestFeature, APIGatewayEvents.APIGatewayProxyRequest lambdaRequest, ILambdaContext lambdaContext) { @@ -338,8 +336,13 @@ public ApplicationLoadBalancerLambdaRuntimeSupportServer(IServiceProvider servic /// protected override HandlerWrapper CreateHandlerWrapper(IServiceProvider serviceProvider) { - var handler = new ApplicationLoadBalancerMinimalApi(serviceProvider).FunctionHandlerAsync; - return HandlerWrapper.GetHandlerWrapper(handler, this.Serializer); + var handler = new ApplicationLoadBalancerMinimalApi(serviceProvider); +#pragma warning disable CA2252 + var hostingOptions = serviceProvider.GetService(); + handler.EnableResponseStreaming = hostingOptions?.EnableResponseStreaming ?? false; +#pragma warning restore CA2252 + Func> bufferedHandler = handler.FunctionHandlerAsync; + return HandlerWrapper.GetHandlerWrapper(bufferedHandler, this.Serializer); } /// @@ -347,9 +350,7 @@ protected override HandlerWrapper CreateHandlerWrapper(IServiceProvider serviceP /// public class ApplicationLoadBalancerMinimalApi : ApplicationLoadBalancerFunction { - #if NET8_0_OR_GREATER private readonly IEnumerable _beforeSnapshotRequestsCollectors; - #endif private readonly HostingOptions? _hostingOptions; /// @@ -359,9 +360,7 @@ public class ApplicationLoadBalancerMinimalApi : ApplicationLoadBalancerFunction public ApplicationLoadBalancerMinimalApi(IServiceProvider serviceProvider) : base(serviceProvider) { - #if NET8_0_OR_GREATER _beforeSnapshotRequestsCollectors = serviceProvider.GetServices(); - #endif // Retrieve HostingOptions from service provider (may be null for backward compatibility) _hostingOptions = serviceProvider.GetService(); @@ -387,14 +386,12 @@ public ApplicationLoadBalancerMinimalApi(IServiceProvider serviceProvider) } } - #if NET8_0_OR_GREATER protected override IEnumerable GetBeforeSnapshotRequests() { foreach (var collector in _beforeSnapshotRequestsCollectors) if (collector.Request != null) yield return collector.Request; } - #endif protected override void PostMarshallRequestFeature(IHttpRequestFeature aspNetCoreRequestFeature, ApplicationLoadBalancerEvents.ApplicationLoadBalancerRequest lambdaRequest, ILambdaContext lambdaContext) { diff --git a/Libraries/src/Amazon.Lambda.AspNetCoreServer.Hosting/ServiceCollectionExtensions.cs b/Libraries/src/Amazon.Lambda.AspNetCoreServer.Hosting/ServiceCollectionExtensions.cs index aa952bc54..bd4089df0 100644 --- a/Libraries/src/Amazon.Lambda.AspNetCoreServer.Hosting/ServiceCollectionExtensions.cs +++ b/Libraries/src/Amazon.Lambda.AspNetCoreServer.Hosting/ServiceCollectionExtensions.cs @@ -88,7 +88,6 @@ public static IServiceCollection AddAWSLambdaHosting(this IServiceCollection ser return services; } - #if NET8_0_OR_GREATER /// /// Adds a > that will be used to invoke /// Routes in your lambda function in order to initialize the ASP.NET Core and Lambda pipelines @@ -142,7 +141,6 @@ public static IServiceCollection AddAWSLambdaBeforeSnapshotRequest(this IService return services; } - #endif private static bool TryLambdaSetup(IServiceCollection services, LambdaEventSource eventSource, Action? configure, out HostingOptions? hostingOptions) { diff --git a/Libraries/src/Amazon.Lambda.AspNetCoreServer/APIGatewayHttpApiV2ProxyFunction.cs b/Libraries/src/Amazon.Lambda.AspNetCoreServer/APIGatewayHttpApiV2ProxyFunction.cs index a7bcd519d..3026a1f67 100644 --- a/Libraries/src/Amazon.Lambda.AspNetCoreServer/APIGatewayHttpApiV2ProxyFunction.cs +++ b/Libraries/src/Amazon.Lambda.AspNetCoreServer/APIGatewayHttpApiV2ProxyFunction.cs @@ -54,6 +54,46 @@ private protected override void InternalCustomResponseExceptionHandling(APIGatew apiGatewayResponse.SetHeaderValues("ErrorType", ex.GetType().Name, false); } +#if NET8_0_OR_GREATER + /// + /// Override for HTTP API v2 to use single-value headers in the streaming prelude + /// instead of multiValueHeaders. API Gateway HTTP API v2 expects the headers + /// format; using multiValueHeaders causes a 500 Internal Server Error. + /// + [System.Runtime.Versioning.RequiresPreviewFeatures(ParameterizedPreviewMessage)] + protected override Amazon.Lambda.Core.ResponseStreaming.HttpResponseStreamPrelude BuildStreamingPrelude(IHttpResponseFeature responseFeature) + { + var prelude = new Amazon.Lambda.Core.ResponseStreaming.HttpResponseStreamPrelude + { + StatusCode = (System.Net.HttpStatusCode)(responseFeature.StatusCode != 0 ? responseFeature.StatusCode : 200) + }; + + foreach (var kvp in responseFeature.Headers) + { + if (string.Equals(kvp.Key, "Content-Length", StringComparison.OrdinalIgnoreCase) || + string.Equals(kvp.Key, "Transfer-Encoding", StringComparison.OrdinalIgnoreCase)) + { + continue; + } + + if (string.Equals(kvp.Key, "Set-Cookie", StringComparison.OrdinalIgnoreCase)) + { + foreach (var value in kvp.Value) + { + prelude.Cookies.Add(value); + } + } + else + { + // HTTP API v2 uses single-value headers. Join multiple values with ", ". + prelude.Headers[kvp.Key] = string.Join(", ", kvp.Value); + } + } + + return prelude; + } +#endif + /// /// Convert the JSON document received from API Gateway into the InvokeFeatures object. /// InvokeFeatures is then passed into IHttpApplication to create the ASP.NET Core request objects. diff --git a/Libraries/src/Amazon.Lambda.AspNetCoreServer/APIGatewayProxyFunction.cs b/Libraries/src/Amazon.Lambda.AspNetCoreServer/APIGatewayProxyFunction.cs index 841b3b1d5..9cbca5c39 100644 --- a/Libraries/src/Amazon.Lambda.AspNetCoreServer/APIGatewayProxyFunction.cs +++ b/Libraries/src/Amazon.Lambda.AspNetCoreServer/APIGatewayProxyFunction.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Net; @@ -104,6 +104,49 @@ private protected override void InternalCustomResponseExceptionHandling(APIGatew apiGatewayResponse.MultiValueHeaders["ErrorType"] = new List { ex.GetType().Name }; } + /// + /// Builds an from the current + /// ASP.NET Core response feature. The status code defaults to 200 when + /// is 0. Set-Cookie header values are moved to ; + /// all other headers are placed in . + /// + /// The ASP.NET Core response feature for the current invocation. + /// A populated . + [System.Runtime.Versioning.RequiresPreviewFeatures(ParameterizedPreviewMessage)] + protected override Amazon.Lambda.Core.ResponseStreaming.HttpResponseStreamPrelude BuildStreamingPrelude(IHttpResponseFeature responseFeature) + { + var prelude = new Amazon.Lambda.Core.ResponseStreaming.HttpResponseStreamPrelude + { + StatusCode = (System.Net.HttpStatusCode)(responseFeature.StatusCode != 0 ? responseFeature.StatusCode : 200) + }; + + foreach (var kvp in responseFeature.Headers) + { + // Skip hop-by-hop and framing headers that are meaningless for streaming + // responses. Content-Length conflicts with chunked transfer encoding and + // can cause API Gateway to reject the response with a 502. + if (string.Equals(kvp.Key, "Content-Length", StringComparison.OrdinalIgnoreCase) || + string.Equals(kvp.Key, "Transfer-Encoding", StringComparison.OrdinalIgnoreCase)) + { + continue; + } + + if (string.Equals(kvp.Key, "Set-Cookie", StringComparison.OrdinalIgnoreCase)) + { + foreach (var value in kvp.Value) + { + prelude.Cookies.Add(value); + } + } + else + { + prelude.MultiValueHeaders[kvp.Key] = kvp.Value.ToArray(); + } + } + + return prelude; + } + /// /// Convert the JSON document received from API Gateway into the InvokeFeatures object. diff --git a/Libraries/src/Amazon.Lambda.AspNetCoreServer/AbstractAspNetCoreFunction.cs b/Libraries/src/Amazon.Lambda.AspNetCoreServer/AbstractAspNetCoreFunction.cs index b24a9fd61..1b1e20092 100644 --- a/Libraries/src/Amazon.Lambda.AspNetCoreServer/AbstractAspNetCoreFunction.cs +++ b/Libraries/src/Amazon.Lambda.AspNetCoreServer/AbstractAspNetCoreFunction.cs @@ -24,6 +24,12 @@ namespace Amazon.Lambda.AspNetCoreServer /// public abstract class AbstractAspNetCoreFunction { + internal const string ParameterizedPreviewMessage = + "Response streaming is in preview till a new version of .NET Lambda runtime client that supports response streaming " + + "has been deployed to the .NET Lambda managed runtime. Till deployment has been made the feature can be used by deploying as an " + + "executable including the latest version of Amazon.Lambda.RuntimeSupport and setting the \"EnablePreviewFeatures\" in the Lambda " + + "project file to \"true\""; + /// /// Key to access the ILambdaContext object from the HttpContext.Items collection. /// @@ -194,6 +200,15 @@ public void RegisterResponseContentEncodingForContentEncoding(string contentEnco /// public bool IncludeUnhandledExceptionDetailInResponse { get; set; } + /// + /// When true, writes the response directly to a + /// instead of + /// buffering it and returning a typed response object (which will be null). + /// Requires net8.0 or later. + /// + [System.Runtime.Versioning.RequiresPreviewFeatures(ParameterizedPreviewMessage)] + public virtual bool EnableResponseStreaming { get; set; } = false; + /// /// Method to initialize the web builder before starting the web host. In a typical Web API this is similar to the main function. @@ -255,7 +270,6 @@ protected virtual IHostBuilder CreateHostBuilder() return builder; } - #if NET8_0_OR_GREATER /// /// Return one or more s that will be used to invoke /// Routes in your lambda function in order to initialize the ASP.NET Core and Lambda pipelines @@ -294,7 +308,6 @@ protected virtual IHostBuilder CreateHostBuilder() /// protected virtual IEnumerable GetBeforeSnapshotRequests() => Enumerable.Empty(); - #endif private protected bool IsStarted { @@ -306,8 +319,6 @@ private protected bool IsStarted private void AddRegisterBeforeSnapshot() { - #if NET8_0_OR_GREATER - Amazon.Lambda.Core.SnapshotRestore.RegisterBeforeSnapshot(async () => { var beforeSnapstartRequests = GetBeforeSnapshotRequests(); @@ -339,8 +350,6 @@ private void AddRegisterBeforeSnapshot() } } }); - - #endif } /// @@ -475,6 +484,14 @@ public virtual async Task FunctionHandlerAsync(TREQUEST request, ILam PostMarshallItemsFeatureFeature(itemFeatures, request, lambdaContext); } +#pragma warning disable CA2252 + if (EnableResponseStreaming) + { + await ExecuteStreamingRequestAsync(features, request, lambdaContext); + return default; + } +#pragma warning restore CA2252 + var scope = this._hostServices.CreateScope(); try { @@ -509,41 +526,7 @@ protected async Task ProcessRequest(ILambdaContext lambdaContext, obj { try { - await this._server.Application.ProcessRequestAsync(context); - } - catch (AggregateException agex) - { - ex = agex; - _logger.LogError(agex, $"Caught AggregateException: '{agex}'"); - var sb = new StringBuilder(); - foreach (var newEx in agex.InnerExceptions) - { - sb.AppendLine(this.ErrorReport(newEx)); - } - - _logger.LogError(sb.ToString()); - ((IHttpResponseFeature)features).StatusCode = 500; - } - catch (ReflectionTypeLoadException rex) - { - ex = rex; - _logger.LogError(rex, $"Caught ReflectionTypeLoadException: '{rex}'"); - var sb = new StringBuilder(); - foreach (var loaderException in rex.LoaderExceptions) - { - var fileNotFoundException = loaderException as FileNotFoundException; - if (fileNotFoundException != null && !string.IsNullOrEmpty(fileNotFoundException.FileName)) - { - sb.AppendLine($"Missing file: {fileNotFoundException.FileName}"); - } - else - { - sb.AppendLine(this.ErrorReport(loaderException)); - } - } - - _logger.LogError(sb.ToString()); - ((IHttpResponseFeature)features).StatusCode = 500; + await RunPipelineAsync(context, features); } catch (Exception e) { @@ -697,5 +680,158 @@ protected virtual void PostMarshallResponseFeature(IHttpResponseFeature aspNetCo /// /// protected abstract TRESPONSE MarshallResponse(IHttpResponseFeature responseFeatures, ILambdaContext lambdaContext, int statusCodeIfNotSet = 200); + + /// + /// Builds an from the current + /// ASP.NET Core response feature. + /// + /// The ASP.NET Core response feature for the current invocation. + /// A populated . + [System.Runtime.Versioning.RequiresPreviewFeatures(ParameterizedPreviewMessage)] + protected abstract Amazon.Lambda.Core.ResponseStreaming.HttpResponseStreamPrelude BuildStreamingPrelude(IHttpResponseFeature responseFeature); + + /// + /// Creates a for writing the streaming Lambda response. + /// The default implementation calls . + /// Subclasses may override this method to substitute a different stream (e.g. a + /// in unit tests). + /// + /// The HTTP response prelude containing status code and headers. + /// A writable for the response body. + [System.Runtime.Versioning.RequiresPreviewFeatures(ParameterizedPreviewMessage)] + protected virtual System.IO.Stream CreateLambdaResponseStream( + Amazon.Lambda.Core.ResponseStreaming.HttpResponseStreamPrelude prelude) + { + return Amazon.Lambda.Core.ResponseStreaming.LambdaResponseStreamFactory.CreateHttpStream(prelude); + } + + /// + /// Executes the streaming response path. Called by when + /// is true. Writes the response directly to a + /// . + /// + [System.Runtime.Versioning.RequiresPreviewFeatures(ParameterizedPreviewMessage)] + private async Task ExecuteStreamingRequestAsync(InvokeFeatures features, TREQUEST request, ILambdaContext lambdaContext) + { + var responseFeature = (IHttpResponseFeature)features; + System.IO.Stream lambdaStream = null; + bool streamOpened = false; + + async Task OpenStream() + { + var prelude = BuildStreamingPrelude(responseFeature); + _logger.LogDebug("Opening Lambda response stream with Status code {StatusCode}", prelude.StatusCode); + var stream = CreateLambdaResponseStream(prelude); + lambdaStream = stream; + streamOpened = true; + return stream; + } + + var streamingBodyFeature = new Internal.StreamingResponseBodyFeature(_logger, responseFeature, OpenStream); + features[typeof(IHttpResponseBodyFeature)] = streamingBodyFeature; + + var scope = this._hostServices.CreateScope(); + Exception pipelineException = null; + try + { + ((IServiceProvidersFeature)features).RequestServices = scope.ServiceProvider; + + var context = this.CreateContext(features); + try + { + try + { + await RunPipelineAsync(context, features); + await streamingBodyFeature.CompleteAsync(); + } + catch (Exception e) + { + pipelineException = e; + _logger.LogError(e, "Error in streaming request pipeline"); + + if (!streamOpened) + { + var errorPrelude = new Amazon.Lambda.Core.ResponseStreaming.HttpResponseStreamPrelude + { + StatusCode = System.Net.HttpStatusCode.InternalServerError + }; + var errorStream = CreateLambdaResponseStream(errorPrelude); + lambdaStream = errorStream; + streamOpened = true; + if (IncludeUnhandledExceptionDetailInResponse) + { + var errorBytes = System.Text.Encoding.UTF8.GetBytes(ErrorReport(e)); + await errorStream.WriteAsync(errorBytes, 0, errorBytes.Length); + } + } + else if (streamOpened) + { + _logger.LogError(e, $"Unhandled exception after response stream was opened: {ErrorReport(e)}"); + } + else + { + _logger.LogError(e, $"Unknown error responding to request: {ErrorReport(e)}"); + } + } + } + finally + { + if (lambdaStream != null) + { + lambdaStream.Dispose(); + } + + if (features.ResponseCompletedEvents != null) + { + await features.ResponseCompletedEvents.ExecuteAsync(); + } + + this._server.Application.DisposeContext(context, pipelineException); + } + } + finally + { + scope.Dispose(); + } + } + + /// + /// Invokes the ASP.NET Core pipeline for the given context, handling + /// and with + /// detailed logging. Any other exception is rethrown to the caller. + /// + private async Task RunPipelineAsync(object context, InvokeFeatures features) + { + try + { + await this._server.Application.ProcessRequestAsync(context); + } + catch (AggregateException agex) + { + _logger.LogError(agex, $"Caught AggregateException: '{agex}'"); + var sb = new StringBuilder(); + foreach (var newEx in agex.InnerExceptions) + sb.AppendLine(this.ErrorReport(newEx)); + _logger.LogError(sb.ToString()); + ((IHttpResponseFeature)features).StatusCode = 500; + throw; + } + catch (ReflectionTypeLoadException rex) + { + _logger.LogError(rex, $"Caught ReflectionTypeLoadException: '{rex}'"); + var sb = new StringBuilder(); + foreach (var loaderException in rex.LoaderExceptions) + { + var fileNotFoundException = loaderException as FileNotFoundException; + if (fileNotFoundException != null && !string.IsNullOrEmpty(fileNotFoundException.FileName)) + sb.AppendLine($"Missing file: {fileNotFoundException.FileName}"); + else + sb.AppendLine(this.ErrorReport(loaderException)); + } + _logger.LogError(sb.ToString()); + ((IHttpResponseFeature)features).StatusCode = 500; + throw; + } + } } } diff --git a/Libraries/src/Amazon.Lambda.AspNetCoreServer/Amazon.Lambda.AspNetCoreServer.csproj b/Libraries/src/Amazon.Lambda.AspNetCoreServer/Amazon.Lambda.AspNetCoreServer.csproj index 561616cd6..ea382d609 100644 --- a/Libraries/src/Amazon.Lambda.AspNetCoreServer/Amazon.Lambda.AspNetCoreServer.csproj +++ b/Libraries/src/Amazon.Lambda.AspNetCoreServer/Amazon.Lambda.AspNetCoreServer.csproj @@ -4,7 +4,7 @@ Amazon.Lambda.AspNetCoreServer makes it easy to run ASP.NET Core Web API applications as AWS Lambda functions. - net6.0;net8.0 + net8.0;net10.0 Amazon.Lambda.AspNetCoreServer 9.2.1 Amazon.Lambda.AspNetCoreServer @@ -27,7 +27,11 @@ - + + + + + diff --git a/Libraries/src/Amazon.Lambda.AspNetCoreServer/ApplicationLoadBalancerFunction.cs b/Libraries/src/Amazon.Lambda.AspNetCoreServer/ApplicationLoadBalancerFunction.cs index 3048284b2..f6f8c638e 100644 --- a/Libraries/src/Amazon.Lambda.AspNetCoreServer/ApplicationLoadBalancerFunction.cs +++ b/Libraries/src/Amazon.Lambda.AspNetCoreServer/ApplicationLoadBalancerFunction.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Linq; using System.Collections.Generic; using System.Text; @@ -14,6 +14,7 @@ using Microsoft.AspNetCore.Http.Features.Authentication; using Microsoft.Extensions.Primitives; using System.Globalization; +using Amazon.Lambda.Core.ResponseStreaming; namespace Amazon.Lambda.AspNetCoreServer { @@ -220,6 +221,10 @@ private protected override void InternalCustomResponseExceptionHandling(Applicat } } + /// + [System.Runtime.Versioning.RequiresPreviewFeatures(ParameterizedPreviewMessage)] + protected override HttpResponseStreamPrelude BuildStreamingPrelude(IHttpResponseFeature responseFeature) => throw new NotImplementedException(); + private string GetSingleHeaderValue(ApplicationLoadBalancerRequest request, string headerName) { if (this._multiHeaderValuesEnabled) diff --git a/Libraries/src/Amazon.Lambda.AspNetCoreServer/Internal/HttpRequestMessageConverter.cs b/Libraries/src/Amazon.Lambda.AspNetCoreServer/Internal/HttpRequestMessageConverter.cs index 285fb3898..016f3dabb 100644 --- a/Libraries/src/Amazon.Lambda.AspNetCoreServer/Internal/HttpRequestMessageConverter.cs +++ b/Libraries/src/Amazon.Lambda.AspNetCoreServer/Internal/HttpRequestMessageConverter.cs @@ -1,17 +1,10 @@ -#if NET8_0_OR_GREATER using System; -using System.Collections.Generic; using System.Linq; using System.Net.Http; -using System.Text; -using System.Text.Json; -using System.Text.Json.Serialization; using System.Threading.Tasks; using Amazon.Lambda.APIGatewayEvents; using Amazon.Lambda.ApplicationLoadBalancerEvents; -using Microsoft.AspNetCore.Identity.Data; using Microsoft.AspNetCore.WebUtilities; -using Microsoft.Extensions.Primitives; namespace Amazon.Lambda.AspNetCoreServer.Internal { @@ -118,4 +111,3 @@ private static async Task ReadContent(HttpRequestMessage r) } } } -#endif diff --git a/Libraries/src/Amazon.Lambda.AspNetCoreServer/Internal/InvokeFeatures.cs b/Libraries/src/Amazon.Lambda.AspNetCoreServer/Internal/InvokeFeatures.cs index 398817af2..987878311 100644 --- a/Libraries/src/Amazon.Lambda.AspNetCoreServer/Internal/InvokeFeatures.cs +++ b/Libraries/src/Amazon.Lambda.AspNetCoreServer/Internal/InvokeFeatures.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; @@ -28,13 +28,9 @@ public class InvokeFeatures : IFeatureCollection, IServiceProvidersFeature, ITlsConnectionFeature, IHttpRequestIdentifierFeature, - IHttpResponseBodyFeature - -#if NET6_0_OR_GREATER ,IHttpRequestBodyDetectionFeature ,IHttpActivityFeature -#endif /* , IHttpUpgradeFeature, @@ -54,11 +50,8 @@ public InvokeFeatures() this[typeof(ITlsConnectionFeature)] = this; this[typeof(IHttpResponseBodyFeature)] = this; this[typeof(IHttpRequestIdentifierFeature)] = this; - -#if NET6_0_OR_GREATER this[typeof(IHttpRequestBodyDetectionFeature)] = this; this[typeof(IHttpActivityFeature)] = this; -#endif } #region IFeatureCollection @@ -385,7 +378,6 @@ string IHttpRequestIdentifierFeature.TraceIdentifier #endregion -#if NET6_0_OR_GREATER bool IHttpRequestBodyDetectionFeature.CanHaveBody { get @@ -396,6 +388,5 @@ bool IHttpRequestBodyDetectionFeature.CanHaveBody } Activity IHttpActivityFeature.Activity { get; set; } -#endif } } diff --git a/Libraries/src/Amazon.Lambda.AspNetCoreServer/Internal/StreamingResponseBodyFeature.cs b/Libraries/src/Amazon.Lambda.AspNetCoreServer/Internal/StreamingResponseBodyFeature.cs new file mode 100644 index 000000000..03ec929a1 --- /dev/null +++ b/Libraries/src/Amazon.Lambda.AspNetCoreServer/Internal/StreamingResponseBodyFeature.cs @@ -0,0 +1,250 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +using System; +using System.IO; +using System.IO.Pipelines; +using System.Runtime.Versioning; +using System.Threading; +using System.Threading.Tasks; + +using Microsoft.AspNetCore.Http.Features; + +using Amazon.Lambda.Core.ResponseStreaming; +using Microsoft.Extensions.Logging; + +namespace Amazon.Lambda.AspNetCoreServer.Internal +{ + /// + /// An implementation that supports Lambda response streaming. + /// Uses a two-phase approach: bytes written before are buffered in a + /// ; after all writes go directly to the + /// obtained from the stream opener delegate. + /// + [RequiresPreviewFeatures(AbstractAspNetCoreFunction.ParameterizedPreviewMessage)] + internal class StreamingResponseBodyFeature : IHttpResponseBodyFeature + { + private readonly ILogger _logger; + private readonly IHttpResponseFeature _responseFeature; + private readonly Func> _streamOpener; + + private Stream _lambdaStream; // null until StartAsync completes + private MemoryStream _preStartBuffer; // accumulates bytes written before StartAsync + private bool _started; + private PipeWriter _pipeWriter; // lazily created; always wraps the live Stream + + /// + /// Initializes a new instance of . + /// + /// + /// + /// The for the current invocation. Used to fire + /// OnStarting callbacks when is called. + /// + /// + /// A delegate that, when invoked, builds the from + /// the response headers and calls + /// to obtain the . + /// + public StreamingResponseBodyFeature( + ILogger logger, + IHttpResponseFeature responseFeature, + Func> streamOpener) + { + _logger = logger; + _responseFeature = responseFeature ?? throw new ArgumentNullException(nameof(responseFeature)); + _streamOpener = streamOpener ?? throw new ArgumentNullException(nameof(streamOpener)); + } + + /// + /// Initializes a new instance without a logger (for use in tests). + /// + internal StreamingResponseBodyFeature( + IHttpResponseFeature responseFeature, + Func> streamOpener) + : this(null, responseFeature, streamOpener) { } + + /// + /// + /// Returns the once has been + /// called; otherwise returns a lazy-initialized that buffers + /// bytes until the stream is opened. + /// + public Stream Stream => _lambdaStream ?? (_preStartBuffer ??= new MemoryStream()); + + /// + /// + /// Returns a that calls on first + /// flush/write so that the Lambda stream is opened (and the HTTP prelude is sent) + /// as soon as the application first flushes, rather than waiting until the end. + /// + public PipeWriter Writer => _pipeWriter ??= new StartOnFlushPipeWriter(this); + + /// + /// + /// Fires all registered OnStarting callbacks, then calls the stream opener delegate + /// to obtain the , and finally flushes any bytes that + /// were buffered before this method was called. + /// + public async Task StartAsync(CancellationToken cancellationToken = default) + { + _logger?.LogInformation("Starting response streaming"); + + if (_started) + return; + + // Fire OnStarting callbacks registered on the response feature. + // InvokeFeatures (which implements IHttpResponseFeature) stores these in + // ResponseStartingEvents, which is internal to this assembly. + if (_responseFeature is InvokeFeatures invokeFeatures && + invokeFeatures.ResponseStartingEvents != null) + { + await invokeFeatures.ResponseStartingEvents.ExecuteAsync(); + } + + // Open the Lambda response stream (this writes the HTTP prelude). + _lambdaStream = await _streamOpener(); + + // Flush any bytes that were written before StartAsync was called. + if (_preStartBuffer != null && _preStartBuffer.Length > 0) + { + _preStartBuffer.Position = 0; + await _preStartBuffer.CopyToAsync(_lambdaStream, cancellationToken); + } + + _started = true; + } + + /// + public async Task CompleteAsync() + { + await StartAsync(); + + if (_pipeWriter != null) + { + await _pipeWriter.FlushAsync(); + } + } + + /// + /// No-op: the stream is already unbuffered once opened. + public void DisableBuffering() + { + // Intentional no-op per design: the Lambda response stream is already unbuffered. + } + + /// + /// + /// Calls to ensure the stream is open, then reads the specified + /// byte range from the file and writes it to the . + /// + public async Task SendFileAsync( + string path, + long offset, + long? count, + CancellationToken cancellationToken = default) + { + await StartAsync(cancellationToken); + + var fileInfo = new FileInfo(path); + if (offset < 0 || offset > fileInfo.Length) + throw new ArgumentOutOfRangeException(nameof(offset), offset, string.Empty); + if (count.HasValue && (count.Value < 0 || count.Value > fileInfo.Length - offset)) + throw new ArgumentOutOfRangeException(nameof(count), count, string.Empty); + + cancellationToken.ThrowIfCancellationRequested(); + + const int bufferSize = 1024 * 16; + var fileStream = new FileStream( + path, + FileMode.Open, + FileAccess.Read, + FileShare.ReadWrite, + bufferSize: bufferSize, + options: FileOptions.Asynchronous | FileOptions.SequentialScan); + + using (fileStream) + { + fileStream.Seek(offset, SeekOrigin.Begin); + await Utilities.CopyToAsync(fileStream, _lambdaStream, count, bufferSize, cancellationToken); + } + } + + // ----------------------------------------------------------------------- + // StartOnFlushPipeWriter + // + // A PipeWriter wrapper that ensures StartAsync is called (opening the Lambda + // stream and sending the HTTP prelude) the first time the application flushes + // or completes the writer — not just at the very end of the request. + // + // The inner PipeWriter is created lazily against the *live* Stream property + // so it always targets the correct underlying stream (Lambda stream after + // StartAsync, pre-start buffer before). + // ----------------------------------------------------------------------- + private sealed class StartOnFlushPipeWriter : PipeWriter + { + private readonly StreamingResponseBodyFeature _feature; + private PipeWriter _inner; + + // The inner writer must be recreated after StartAsync because Stream + // switches from _preStartBuffer to _lambdaStream at that point. + private PipeWriter Inner => _inner ??= PipeWriter.Create(_feature.Stream); + + public StartOnFlushPipeWriter(StreamingResponseBodyFeature feature) + { + _feature = feature; + } + + public override void Advance(int bytes) => Inner.Advance(bytes); + + public override bool CanGetUnflushedBytes => true; + + public override long UnflushedBytes => Inner.UnflushedBytes; + + public override Memory GetMemory(int sizeHint = 0) => Inner.GetMemory(sizeHint); + + public override Span GetSpan(int sizeHint = 0) => Inner.GetSpan(sizeHint); + + public override async ValueTask FlushAsync(CancellationToken cancellationToken = default) + { + if (!_feature._started) + { + // Flush buffered bytes into the pre-start buffer first, then open the stream. + var innerFlushResult = await Inner.FlushAsync(cancellationToken); + // Recreate inner writer against the Lambda stream after StartAsync. + _inner = null; + await _feature.StartAsync(cancellationToken); + + return innerFlushResult; + } + + return await Inner.FlushAsync(cancellationToken); + } + + public override async ValueTask CompleteAsync(Exception exception = null) + { + if (!_feature._started) + { + await Inner.FlushAsync(); + _inner = null; + await _feature.StartAsync(); + } + await Inner.CompleteAsync(exception); + } + + // Complete (sync) — mirror CompleteAsync behavior to ensure the response is started. + public override void Complete(Exception exception = null) + { + if (!_feature._started) + { + // Flush buffered bytes into the pre-start buffer, then open the stream. + Inner.FlushAsync().GetAwaiter().GetResult(); + _inner = null; + _feature.StartAsync().GetAwaiter().GetResult(); + } + + Inner.Complete(exception); + } + public override void CancelPendingFlush() => Inner.CancelPendingFlush(); + } + } +} diff --git a/Libraries/src/Amazon.Lambda.Core/ResponseStreaming/HttpResponseStreamPrelude.cs b/Libraries/src/Amazon.Lambda.Core/ResponseStreaming/HttpResponseStreamPrelude.cs new file mode 100644 index 000000000..1a10aa2dc --- /dev/null +++ b/Libraries/src/Amazon.Lambda.Core/ResponseStreaming/HttpResponseStreamPrelude.cs @@ -0,0 +1,101 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +#if NET8_0_OR_GREATER +using System; +using System.Collections.Generic; +using System.Net; +using System.Runtime.Versioning; +using System.Text.Json; + +namespace Amazon.Lambda.Core.ResponseStreaming +{ + /// + /// The HTTP response prelude to be sent as the first chunk of a streaming response when using . + /// When using Lambda response streaming with a Lambda Function URL or API Gateway, the response prelude is used to set the HTTP status code, + /// headers, and cookies for the response. The prelude must be sent as the first chunk of the response stream, followed by the response body chunks. + /// This allows you to set the status code and headers for the response before sending any of the response body. + /// + [RequiresPreviewFeatures(LambdaResponseStreamFactory.PreviewMessage)] + public class HttpResponseStreamPrelude + { + /// + /// The Http status code. + /// + public HttpStatusCode? StatusCode { get; set; } + + /// + /// The response headers. This collection supports setting single value for the same headers. When using + /// Lambda Function URLs as this event source this collection should be used. + /// + public IDictionary Headers { get; set; } = new Dictionary(); + + /// + /// The response headers. This collection supports setting multiple values for the same headers. When using + /// API Gateway REST APIs as this event source this collection should be used. + /// + public IDictionary> MultiValueHeaders { get; set; } = new Dictionary>(); + + /// + /// The list of cookies. This is used for Lambda Function URL responses, which support a separate "cookies" field in + /// the response JSON for setting cookies, rather than requiring cookies to be set via the "Set-Cookie" header. + /// + public IList Cookies { get; set; } = new List(); + + internal byte[] ToByteArray() + { + var bufferWriter = new System.Buffers.ArrayBufferWriter(); + using (var writer = new Utf8JsonWriter(bufferWriter)) + { + writer.WriteStartObject(); + + if (StatusCode.HasValue) + writer.WriteNumber("statusCode", (int)StatusCode); + + if (Headers?.Count > 0) + { + writer.WriteStartObject("headers"); + foreach (var header in Headers) + { + writer.WriteString(header.Key, header.Value); + } + writer.WriteEndObject(); + } + + if (MultiValueHeaders?.Count > 0) + { + writer.WriteStartObject("multiValueHeaders"); + foreach (var header in MultiValueHeaders) + { + writer.WriteStartArray(header.Key); + foreach (var value in header.Value) + { + writer.WriteStringValue(value); + } + writer.WriteEndArray(); + } + writer.WriteEndObject(); + } + + if (Cookies?.Count > 0) + { + writer.WriteStartArray("cookies"); + foreach (var cookie in Cookies) + { + writer.WriteStringValue(cookie); + } + writer.WriteEndArray(); + } + + writer.WriteEndObject(); + } + + if (string.Equals(Environment.GetEnvironmentVariable("LAMBDA_NET_SERIALIZER_DEBUG"), "true", StringComparison.OrdinalIgnoreCase)) + { + LambdaLogger.Log(LogLevel.Information, "HTTP Response Stream Prelude JSON: {Prelude}", System.Text.Encoding.UTF8.GetString(bufferWriter.WrittenSpan)); + } + + return bufferWriter.WrittenSpan.ToArray(); + } + } +} +#endif diff --git a/Libraries/src/Amazon.Lambda.Core/ResponseStreaming/ILambdaResponseStream.cs b/Libraries/src/Amazon.Lambda.Core/ResponseStreaming/ILambdaResponseStream.cs new file mode 100644 index 000000000..4b604cc58 --- /dev/null +++ b/Libraries/src/Amazon.Lambda.Core/ResponseStreaming/ILambdaResponseStream.cs @@ -0,0 +1,38 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +#if NET8_0_OR_GREATER +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace Amazon.Lambda.Core.ResponseStreaming +{ + /// + /// Interface for writing streaming responses in AWS Lambda functions. + /// Obtained by calling within a handler. + /// + internal interface ILambdaResponseStream : IDisposable + { + /// + /// Asynchronously writes a portion of a byte array to the response stream. + /// + /// The byte array containing data to write. + /// The zero-based byte offset in buffer at which to begin copying bytes. + /// The number of bytes to write. + /// Optional cancellation token. + /// A task representing the asynchronous operation. + /// Thrown if the stream is already completed or an error has been reported. + Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken = default); + + /// + /// Gets the total number of bytes written to the stream so far. + /// + long BytesWritten { get; } + + /// + /// Gets whether an error has been reported. + /// + bool HasError { get; } + } +} +#endif diff --git a/Libraries/src/Amazon.Lambda.Core/ResponseStreaming/LambdaResponseStream.cs b/Libraries/src/Amazon.Lambda.Core/ResponseStreaming/LambdaResponseStream.cs new file mode 100644 index 000000000..83ac446a4 --- /dev/null +++ b/Libraries/src/Amazon.Lambda.Core/ResponseStreaming/LambdaResponseStream.cs @@ -0,0 +1,123 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +#if NET8_0_OR_GREATER + +using System; +using System.IO; +using System.Runtime.Versioning; +using System.Threading; +using System.Threading.Tasks; + +namespace Amazon.Lambda.Core.ResponseStreaming +{ + /// + /// A write-only, non-seekable subclass that streams response data + /// to the Lambda Runtime API. Returned by . + /// Integrates with standard .NET stream consumers such as . + /// + [RequiresPreviewFeatures(LambdaResponseStreamFactory.PreviewMessage)] + public class LambdaResponseStream : Stream + { + private readonly ILambdaResponseStream _responseStream; + + internal LambdaResponseStream(ILambdaResponseStream responseStream) + { + _responseStream = responseStream; + } + + /// + /// The number of bytes written to the Lambda response stream so far. + /// + public long BytesWritten => _responseStream.BytesWritten; + + /// + /// Asynchronously writes a byte array to the response stream. + /// + /// The byte array to write. + /// Optional cancellation token. + /// A task representing the asynchronous operation. + /// Thrown if the stream is already completed or an error has been reported. + public async Task WriteAsync(byte[] buffer, CancellationToken cancellationToken = default) + { + if (buffer == null) + throw new ArgumentNullException(nameof(buffer)); + + await WriteAsync(buffer, 0, buffer.Length, cancellationToken); + } + + /// + /// Asynchronously writes a portion of a byte array to the response stream. + /// + /// The byte array containing data to write. + /// The zero-based byte offset in buffer at which to begin copying bytes. + /// The number of bytes to write. + /// Optional cancellation token. + /// A task representing the asynchronous operation. + /// Thrown if the stream is already completed or an error has been reported. + public override async Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken = default) + { + await _responseStream.WriteAsync(buffer, offset, count, cancellationToken); + } + + #region Noop Overrides + + /// Gets a value indicating whether the stream supports reading. Always false. + public override bool CanRead => false; + + /// Gets a value indicating whether the stream supports seeking. Always false. + public override bool CanSeek => false; + + /// Gets a value indicating whether the stream supports writing. Always true. + public override bool CanWrite => true; + + /// + /// Gets the total number of bytes written to the stream so far. + /// Equivalent to . + /// + public override long Length => BytesWritten; + + /// + /// Getting or setting the position is not supported. + /// + /// Always thrown. + public override long Position + { + get => throw new NotSupportedException($"{nameof(LambdaResponseStream)} does not support seeking."); + set => throw new NotSupportedException($"{nameof(LambdaResponseStream)} does not support seeking."); + } + + /// Not supported. + /// Always thrown. + public override long Seek(long offset, SeekOrigin origin) + => throw new NotImplementedException($"{nameof(LambdaResponseStream)} does not support seeking."); + + /// Not supported. + /// Always thrown. + public override int Read(byte[] buffer, int offset, int count) + => throw new NotImplementedException($"{nameof(LambdaResponseStream)} does not support reading."); + + /// Not supported. + /// Always thrown. + public override Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) + => throw new NotImplementedException($"{nameof(LambdaResponseStream)} does not support reading."); + + /// + /// Writes a sequence of bytes to the stream. Delegates to the async path synchronously. + /// Prefer to avoid blocking. + /// + public override void Write(byte[] buffer, int offset, int count) + => WriteAsync(buffer, offset, count, CancellationToken.None).GetAwaiter().GetResult(); + + /// + /// Flush is a no-op; data is sent to the Runtime API immediately on each write. + /// + public override void Flush() { } + + /// Not supported. + /// Always thrown. + public override void SetLength(long value) + => throw new NotSupportedException($"{nameof(LambdaResponseStream)} does not support SetLength."); + #endregion + } +} +#endif diff --git a/Libraries/src/Amazon.Lambda.Core/ResponseStreaming/LambdaResponseStreamFactory.cs b/Libraries/src/Amazon.Lambda.Core/ResponseStreaming/LambdaResponseStreamFactory.cs new file mode 100644 index 000000000..1b9e6d3b6 --- /dev/null +++ b/Libraries/src/Amazon.Lambda.Core/ResponseStreaming/LambdaResponseStreamFactory.cs @@ -0,0 +1,72 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +#if NET8_0_OR_GREATER +using System; +using System.IO; +using System.Runtime.Versioning; + +namespace Amazon.Lambda.Core.ResponseStreaming +{ + /// + /// Factory to create Lambda response streams for writing streaming responses in AWS Lambda functions. The created streams are write-only and non-seekable. + /// + [RequiresPreviewFeatures(LambdaResponseStreamFactory.PreviewMessage)] + public class LambdaResponseStreamFactory + { + internal const string PreviewMessage = + "Response streaming is in preview till a new version of .NET Lambda runtime client that supports response streaming " + + "has been deployed to the .NET Lambda managed runtime. Till deployment has been made the feature can be used by deploying as an " + + "executable including the latest version of Amazon.Lambda.RuntimeSupport and setting the \"EnablePreviewFeatures\" in the Lambda " + + "project file to \"true\""; + + internal const string UninitializedFactoryMessage = + "LambdaResponseStreamFactory is not initialized. This is caused by mismatch versions of Amazon.Lambda.Core and Amazon.Lambda.RuntimeSupport. " + + "Update both packages to the current version to address the issue."; + + private static Func _streamFactory; + + internal static void SetLambdaResponseStream(Func streamFactory) + { + _streamFactory = streamFactory ?? throw new ArgumentNullException(nameof(streamFactory)); + } + + /// + /// Creates a a subclass of that can be used to write streaming responses back to callers of the Lambda function. Once + /// a Lambda function creates a response stream all output must be returned by writing to the stream; the Lambda function's handler + /// return value will be ignored. The stream is write-only and non-seekable. + /// + /// + public static LambdaResponseStream CreateStream() + { + if (_streamFactory == null) + throw new InvalidOperationException(UninitializedFactoryMessage); + + var runtimeResponseStream = _streamFactory(Array.Empty()); + return new LambdaResponseStream(runtimeResponseStream); + } + + /// + /// Creates a a subclass of for writing streaming responses, with an HTTP response prelude containing status code and headers. This should be used for + /// Lambda functions using response streaming that are invoked via the Lambda Function URLs or API Gateway HTTP APIs, where the response format is expected to be an HTTP response. + /// The prelude will be serialized and sent as the first chunk of the response stream, and should contain any necessary HTTP status code and headers. + /// + /// Once a Lambda function creates a response stream all output must be returned by writing to the stream; the Lambda function's handler + /// return value will be ignored. The stream is write-only and non-seekable. + /// + /// + /// The HTTP response prelude including status code and headers. + /// + public static LambdaResponseStream CreateHttpStream(HttpResponseStreamPrelude prelude) + { + if (_streamFactory == null) + throw new InvalidOperationException(UninitializedFactoryMessage); + + if (prelude is null) + throw new ArgumentNullException(nameof(prelude)); + + var runtimeResponseStream = _streamFactory(prelude.ToByteArray()); + return new LambdaResponseStream(runtimeResponseStream); + } + } +} +#endif diff --git a/Libraries/src/Amazon.Lambda.Logging.AspNetCore/Amazon.Lambda.Logging.AspNetCore.csproj b/Libraries/src/Amazon.Lambda.Logging.AspNetCore/Amazon.Lambda.Logging.AspNetCore.csproj index 673a9ca30..7db23986c 100644 --- a/Libraries/src/Amazon.Lambda.Logging.AspNetCore/Amazon.Lambda.Logging.AspNetCore.csproj +++ b/Libraries/src/Amazon.Lambda.Logging.AspNetCore/Amazon.Lambda.Logging.AspNetCore.csproj @@ -4,7 +4,7 @@ Amazon Lambda .NET Core support - Logging ASP.NET Core package. - net6.0;net8.0 + net8.0;net10.0 Amazon.Lambda.Logging.AspNetCore 4.1.1 Amazon.Lambda.Logging.AspNetCore diff --git a/Libraries/src/Amazon.Lambda.RuntimeSupport/Amazon.Lambda.RuntimeSupport.csproj b/Libraries/src/Amazon.Lambda.RuntimeSupport/Amazon.Lambda.RuntimeSupport.csproj index b3bfb0488..6f8dabfa2 100644 --- a/Libraries/src/Amazon.Lambda.RuntimeSupport/Amazon.Lambda.RuntimeSupport.csproj +++ b/Libraries/src/Amazon.Lambda.RuntimeSupport/Amazon.Lambda.RuntimeSupport.csproj @@ -4,7 +4,7 @@ netstandard2.0;net6.0;net8.0;net9.0;net10.0;net11.0 - 1.14.2 + 1.14.3 Provides a bootstrap and Lambda Runtime API Client to help you to develop custom .NET Core Lambda Runtimes. Amazon.Lambda.RuntimeSupport Amazon.Lambda.RuntimeSupport diff --git a/Libraries/src/Amazon.Lambda.RuntimeSupport/Bootstrap/LambdaBootstrap.cs b/Libraries/src/Amazon.Lambda.RuntimeSupport/Bootstrap/LambdaBootstrap.cs index 0e00f3e7f..bb6198d9e 100644 --- a/Libraries/src/Amazon.Lambda.RuntimeSupport/Bootstrap/LambdaBootstrap.cs +++ b/Libraries/src/Amazon.Lambda.RuntimeSupport/Bootstrap/LambdaBootstrap.cs @@ -20,6 +20,7 @@ using System.Threading; using System.Threading.Tasks; using Amazon.Lambda.RuntimeSupport.Bootstrap; +using Amazon.Lambda.RuntimeSupport.Client.ResponseStreaming; using Amazon.Lambda.RuntimeSupport.Helpers; namespace Amazon.Lambda.RuntimeSupport @@ -225,6 +226,19 @@ internal LambdaBootstrap(HttpClient httpClient, LambdaBootstrapHandler handler, return; } #if NET8_0_OR_GREATER + + try + { + // Initalize in Amazon.Lambda.Core the factory for creating the response stream and related logic for supporting response streaming. + ResponseStreamLambdaCoreInitializerIsolated.InitializeCore(); + } + catch (TypeLoadException) + { + _logger.LogDebug("Failed to configure Amazon.Lambda.Core with factory to create response stream. This happens when the version of Amazon.Lambda.Core referenced by the Lambda function is out of date."); + } + + + // Check if Initialization type is SnapStart, and invoke the snapshot restore logic. if (_configuration.IsInitTypeSnapstart) { @@ -349,6 +363,7 @@ internal async Task InvokeOnceAsync(CancellationToken cancellationToken = defaul _logger.LogInformation("Starting InvokeOnceAsync"); var invocation = await Client.GetNextInvocationAsync(cancellationToken); + var isMultiConcurrency = Utils.IsUsingMultiConcurrency(_environmentVariables); Func processingFunc = async () => { @@ -358,6 +373,17 @@ internal async Task InvokeOnceAsync(CancellationToken cancellationToken = defaul SetInvocationTraceId(impl.RuntimeApiHeaders.TraceId); } + // Initialize ResponseStreamFactory — includes RuntimeApiClient reference + var runtimeApiClient = Client as RuntimeApiClient; + if (runtimeApiClient != null) + { + ResponseStreamFactory.InitializeInvocation( + invocation.LambdaContext.AwsRequestId, + isMultiConcurrency, + runtimeApiClient, + cancellationToken); + } + try { InvocationResponse response = null; @@ -372,15 +398,41 @@ internal async Task InvokeOnceAsync(CancellationToken cancellationToken = defaul catch (Exception exception) { WriteUnhandledExceptionToLog(exception); - await Client.ReportInvocationErrorAsync(invocation.LambdaContext.AwsRequestId, exception, cancellationToken); + + var responseStream = ResponseStreamFactory.GetStreamIfCreated(isMultiConcurrency); + if (responseStream != null) + { + responseStream.ReportError(exception); + } + else + { + await Client.ReportInvocationErrorAsync(invocation.LambdaContext.AwsRequestId, exception, cancellationToken); + } } finally { _logger.LogInformation("Finished invoking handler"); } - if (invokeSucceeded) + var streamIfCreated = ResponseStreamFactory.GetStreamIfCreated(isMultiConcurrency); + if (streamIfCreated != null) + { + streamIfCreated.MarkCompleted(); + + // If streaming was started, await the HTTP send task to ensure it completes + var sendTask = ResponseStreamFactory.GetSendTask(isMultiConcurrency); + if (sendTask != null) + { + // Wait for the streaming response to finish sending before allowing the next invocation to be processed. This ensures that responses are sent in the order the invocations were received. + await sendTask; + sendTask.Result.Dispose(); + } + + streamIfCreated.Dispose(); + } + else if (invokeSucceeded) { + // No streaming — send buffered response _logger.LogInformation("Starting sending response"); try { @@ -415,6 +467,7 @@ internal async Task InvokeOnceAsync(CancellationToken cancellationToken = defaul } finally { + ResponseStreamFactory.CleanupInvocation(isMultiConcurrency); invocation.Dispose(); } }; diff --git a/Libraries/src/Amazon.Lambda.RuntimeSupport/Bootstrap/ResponseStreaming/RawStreamingHttpClient.cs b/Libraries/src/Amazon.Lambda.RuntimeSupport/Bootstrap/ResponseStreaming/RawStreamingHttpClient.cs new file mode 100644 index 000000000..c944d104a --- /dev/null +++ b/Libraries/src/Amazon.Lambda.RuntimeSupport/Bootstrap/ResponseStreaming/RawStreamingHttpClient.cs @@ -0,0 +1,296 @@ +/* + * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +#if NET8_0_OR_GREATER +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Net.Sockets; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Amazon.Lambda.RuntimeSupport.Helpers; + +namespace Amazon.Lambda.RuntimeSupport.Client.ResponseStreaming +{ + /// + /// A raw HTTP/1.1 client for sending streaming responses to the Lambda Runtime API + /// with support for HTTP trailing headers (used for error reporting). + /// + /// .NET's HttpClient/SocketsHttpHandler does not support sending HTTP/1.1 trailing headers. + /// The Lambda Runtime API requires error information to be sent as trailing headers + /// (Lambda-Runtime-Function-Error-Type and Lambda-Runtime-Function-Error-Body) after + /// the chunked transfer encoding body. This class gives us full control over the + /// HTTP wire format to properly send those trailers. + /// + internal class RawStreamingHttpClient : IDisposable + { + private readonly string _host; + private readonly int _port; + private TcpClient _tcpClient; + internal Stream _networkStream; + private bool _disposed; + + private readonly InternalLogger _logger = InternalLogger.GetDefaultLogger(); + + public RawStreamingHttpClient(string hostAndPort) + { + var parts = hostAndPort.Split(':'); + if (parts.Length != 2) + throw new ArgumentException($"Invalid host and port format: {hostAndPort}. Expected format is 'host:port'"); + + _host = parts[0]; + _port = int.Parse(parts[1], CultureInfo.InvariantCulture); + } + + /// + /// Sends a streaming response to the Lambda Runtime API. + /// Connects via TCP, sends HTTP headers, then streams the response body + /// using chunked transfer encoding. When the response stream completes, + /// writes the chunked encoding terminator with optional trailing headers + /// for error reporting. + /// + /// The Lambda request ID. + /// The response stream that provides data and error state. + /// The User-Agent header value. + /// Cancellation token. + public async Task SendStreamingResponseAsync( + string awsRequestId, + ResponseStream responseStream, + string userAgent, + CancellationToken cancellationToken = default) + { + _tcpClient = new TcpClient(); + _tcpClient.NoDelay = true; + await _tcpClient.ConnectAsync(_host, _port, cancellationToken); + _networkStream = _tcpClient.GetStream(); + + // Send HTTP request line and headers + var path = $"/2018-06-01/runtime/invocation/{awsRequestId}/response"; + var headers = new StringBuilder(); + headers.Append($"POST {path} HTTP/1.1\r\n"); + headers.Append($"Host: {_host}:{_port}\r\n"); + headers.Append($"User-Agent: {userAgent}\r\n"); + headers.Append($"Content-Type: application/vnd.awslambda.http-integration-response\r\n"); + headers.Append($"{StreamingConstants.ResponseModeHeader}: {StreamingConstants.StreamingResponseMode}\r\n"); + headers.Append("Transfer-Encoding: chunked\r\n"); + headers.Append($"Trailer: {StreamingConstants.ErrorTypeTrailer}, {StreamingConstants.ErrorBodyTrailer}\r\n"); + headers.Append("\r\n"); + + var headerBytes = Encoding.ASCII.GetBytes(headers.ToString()); + await _networkStream.WriteAsync(headerBytes, cancellationToken); + await _networkStream.FlushAsync(cancellationToken); + + // Hand the network stream (wrapped in a chunked writer) to the ResponseStream + var chunkedWriter = new ChunkedStreamWriter(_networkStream); + await responseStream.SetHttpOutputStreamAsync(chunkedWriter, cancellationToken); + + _logger.LogInformation("In SendStreamingResponseAsync waiting for the underlying Lambda response stream to indicate it is complete."); + + // Wait for the handler to finish writing + await responseStream.WaitForCompletionAsync(cancellationToken); + + // Write the chunked encoding terminator with optional trailers + if (responseStream.HasError) + { + _logger.LogInformation("Adding response stream trailing error headers"); + await WriteTerminatorWithTrailersAsync(responseStream.ReportedError, cancellationToken); + } + else + { + // No error — write simple terminator: 0\r\n\r\n + var terminator = Encoding.ASCII.GetBytes("0\r\n\r\n"); + await _networkStream.WriteAsync(terminator, cancellationToken); + } + + await _networkStream.FlushAsync(cancellationToken); + + // Read and discard the HTTP response (we don't need it, but must consume it) + await ReadAndDiscardResponseAsync(cancellationToken); + } + + /// + /// Writes the chunked encoding terminator with HTTP trailing headers for error reporting. + /// Format: + /// 0\r\n + /// Lambda-Runtime-Function-Error-Type: errorType\r\n + /// Lambda-Runtime-Function-Error-Body: base64EncodedErrorBodyJson\r\n + /// \r\n + /// + /// The error body JSON is Base64-encoded because LambdaJsonExceptionWriter produces + /// pretty-printed multi-line JSON. HTTP trailer values cannot contain raw CR/LF characters + /// as they would break the HTTP framing — the Runtime API would see the first newline + /// inside the JSON as the end of the trailer and treat the rest as malformed data, + /// resulting in Runtime.TruncatedResponse instead of the actual error. + /// + internal async Task WriteTerminatorWithTrailersAsync(Exception exception, CancellationToken cancellationToken) + { + var exceptionInfo = ExceptionInfo.GetExceptionInfo(exception); + var errorBodyJson = LambdaJsonExceptionWriter.WriteJson(exceptionInfo); + var errorBodyBase64 = Convert.ToBase64String(Encoding.UTF8.GetBytes(errorBodyJson)); + + InternalLogger.GetDefaultLogger().LogInformation($"Writing trailing header {StreamingConstants.ErrorTypeTrailer} with error type {exceptionInfo.ErrorType}."); + var trailers = new StringBuilder(); + trailers.Append("0\r\n"); // zero-length chunk (end of body) + trailers.Append($"{StreamingConstants.ErrorTypeTrailer}: {exceptionInfo.ErrorType}\r\n"); + trailers.Append($"{StreamingConstants.ErrorBodyTrailer}: {errorBodyBase64}\r\n"); + trailers.Append("\r\n"); // end of trailers + + var trailerBytes = Encoding.UTF8.GetBytes(trailers.ToString()); + await _networkStream.WriteAsync(trailerBytes, cancellationToken); + } + + /// + /// Reads and discards the HTTP response from the Runtime API. + /// We need to consume the response to properly close the connection, + /// but we don't need to process it. + /// + internal async Task ReadAndDiscardResponseAsync(CancellationToken cancellationToken) + { + const string headerDelimiter = "\r\n\r\n"; + var buffer = new byte[4096]; + try + { + // Read until we get the full response. The Runtime API sends a short response. + var totalRead = 0; + var responseText = new StringBuilder(); + while (true) + { + var bytesRead = await _networkStream.ReadAsync(buffer, 0, buffer.Length, cancellationToken); + if (bytesRead == 0) + break; + + totalRead += bytesRead; + responseText.Append(Encoding.ASCII.GetString(buffer, 0, bytesRead)); + + // Check if we've received the complete response (ends with \r\n\r\n for headers, + // or we've read the content-length worth of body) + var text = responseText.ToString(); + if (text.Contains(headerDelimiter)) + { + // Find Content-Length to know if there's a body to read + var headerEnd = text.IndexOf(headerDelimiter, StringComparison.Ordinal); + var headers = text.Substring(0, headerEnd); + + var contentLengthMatch = System.Text.RegularExpressions.Regex.Match( + headers, @"Content-Length:\s*(\d+)", System.Text.RegularExpressions.RegexOptions.IgnoreCase); + + if (contentLengthMatch.Success) + { + var contentLength = int.Parse(contentLengthMatch.Groups[1].Value, CultureInfo.InvariantCulture); + var bodyStart = headerEnd + 4; // skip \r\n\r\n + var bodyRead = text.Length - bodyStart; + if (bodyRead >= contentLength) + break; + } + else + { + // No Content-Length, assume response is complete after headers + break; + } + } + + // 16KB is more than enough for the Runtime API response, so we can break here to avoid an infinite loop in case of malformed response + if (totalRead > 16384) + break; // Safety limit + } + } + catch (Exception ex) + { + // Log but don't throw — the streaming response was already sent + _logger.LogDebug($"Error reading Runtime API response: {ex.Message}"); + } + } + + public void Dispose() + { + if (!_disposed) + { + _networkStream?.Dispose(); + _tcpClient?.Dispose(); + _disposed = true; + } + } + } + + /// + /// A write-only Stream wrapper that writes data in HTTP/1.1 chunked transfer encoding format. + /// Each write produces a chunk: {size in hex}\r\n{data}\r\n + /// FlushAsync flushes the underlying network stream to ensure data is sent immediately. + /// The chunked encoding terminator (0\r\n...\r\n) is NOT written by this class — + /// it is handled by RawStreamingHttpClient to support trailing headers. + /// + internal class ChunkedStreamWriter : Stream + { + private readonly Stream _innerStream; + + public ChunkedStreamWriter(Stream innerStream) + { + _innerStream = innerStream ?? throw new ArgumentNullException(nameof(innerStream)); + } + + public override bool CanRead => false; + public override bool CanSeek => false; + public override bool CanWrite => true; + public override long Length => throw new NotSupportedException(); + public override long Position + { + get => throw new NotSupportedException(); + set => throw new NotSupportedException(); + } + + public override void Write(byte[] buffer, int offset, int count) + { + WriteAsync(buffer, offset, count, CancellationToken.None).GetAwaiter().GetResult(); + } + + public override async Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) + { + if (count == 0) return; + + // Write chunk header: size in hex + \r\n + var chunkHeader = Encoding.ASCII.GetBytes($"{count:X}\r\n"); + await _innerStream.WriteAsync(chunkHeader, 0, chunkHeader.Length, cancellationToken); + + // Write chunk data + await _innerStream.WriteAsync(buffer, offset, count, cancellationToken); + + // Write chunk trailer: \r\n + var crlf = Encoding.ASCII.GetBytes("\r\n"); + await _innerStream.WriteAsync(crlf, 0, crlf.Length, cancellationToken); + } + + public override async ValueTask WriteAsync(ReadOnlyMemory buffer, CancellationToken cancellationToken = default) + { + if (buffer.Length == 0) return; + + var chunkHeader = Encoding.ASCII.GetBytes($"{buffer.Length:X}\r\n"); + await _innerStream.WriteAsync(chunkHeader, cancellationToken); + await _innerStream.WriteAsync(buffer, cancellationToken); + await _innerStream.WriteAsync(Encoding.ASCII.GetBytes("\r\n"), cancellationToken); + } + + public override void Flush() => _innerStream.Flush(); + + public override Task FlushAsync(CancellationToken cancellationToken) => + _innerStream.FlushAsync(cancellationToken); + + public override int Read(byte[] buffer, int offset, int count) => throw new NotSupportedException(); + public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException(); + public override void SetLength(long value) => throw new NotSupportedException(); + } +} +#endif diff --git a/Libraries/src/Amazon.Lambda.RuntimeSupport/Bootstrap/ResponseStreaming/ResponseStream.cs b/Libraries/src/Amazon.Lambda.RuntimeSupport/Bootstrap/ResponseStreaming/ResponseStream.cs new file mode 100644 index 000000000..2df051b72 --- /dev/null +++ b/Libraries/src/Amazon.Lambda.RuntimeSupport/Bootstrap/ResponseStreaming/ResponseStream.cs @@ -0,0 +1,261 @@ +/* + * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +using System; +using System.Buffers; +using System.IO; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Amazon.Lambda.RuntimeSupport.Helpers; + +namespace Amazon.Lambda.RuntimeSupport.Client.ResponseStreaming +{ + /// + /// Represents the writable stream used by Lambda handlers to write response data for streaming invocations. + /// + internal class ResponseStream + { + private long _bytesWritten; + private bool _isCompleted; + private bool _hasError; + private Exception _reportedError; + private readonly object _lock = new object(); + + // The live HTTP output stream, set by RawStreamingHttpClient when sending the streaming response. + private Stream _httpOutputStream; + private bool _disposedValue; + + // The wait time is a sanity timeout to avoid waiting indefinitely if SetHttpOutputStreamAsync is not called or takes too long to call. + // Reality is that SetHttpOutputStreamAsync should be called very quickly after CreateStream, so this timeout is generous to avoid false positives but still protects against hanging indefinitely. + private readonly static TimeSpan _httpStreamWaitTimeout = TimeSpan.FromSeconds(30); + + private readonly SemaphoreSlim _httpStreamReady = new SemaphoreSlim(0, 1); + private readonly SemaphoreSlim _completionSignal = new SemaphoreSlim(0, 1); + + private static readonly byte[] PreludeDelimiter = new byte[8]; + + /// + /// The number of bytes written to the Lambda response stream so far. + /// + public long BytesWritten => _bytesWritten; + + /// + /// Gets a value indicating whether an error has occurred. + /// + public bool HasError => _hasError; + + private readonly byte[] _prelude; + + + private readonly InternalLogger _logger; + + + internal Exception ReportedError => _reportedError; + + internal ResponseStream(byte[] prelude) + { + _logger = InternalLogger.GetDefaultLogger(); + _prelude = prelude; + } + + /// + /// Called by RawStreamingHttpClient to provide the HTTP output stream (a ChunkedStreamWriter). + /// + internal async Task SetHttpOutputStreamAsync(Stream httpOutputStream, CancellationToken cancellationToken = default) + { + _httpOutputStream = httpOutputStream; + + // Write the prelude BEFORE releasing _httpStreamReady. This prevents a race + // where a handler WriteAsync that is already waiting on the semaphore could + // sneak in and write body data before the prelude, causing intermittent + // "Failed to parse prelude JSON" errors from API Gateway. + // + // Note: we intentionally do NOT check ThrowIfCompletedOrError() here. + // SetHttpOutputStreamAsync is infrastructure setup called by RawStreamingHttpClient, + // not a handler write. For fast-completing responses (e.g. Results.Json), + // LambdaBootstrap may call MarkCompleted() before the TCP connection is established + // and this method is called. The prelude still needs to be written to the wire + // so the response is properly framed. + if (_prelude?.Length > 0) + { + _logger.LogDebug("Writing prelude to HTTP stream."); + + var combinedLength = _prelude.Length + PreludeDelimiter.Length; + var combined = ArrayPool.Shared.Rent(combinedLength); + try + { + Buffer.BlockCopy(_prelude, 0, combined, 0, _prelude.Length); + Buffer.BlockCopy(PreludeDelimiter, 0, combined, _prelude.Length, PreludeDelimiter.Length); + + await _httpOutputStream.WriteAsync(combined, 0, combinedLength, cancellationToken); + await _httpOutputStream.FlushAsync(cancellationToken); + } + finally + { + ArrayPool.Shared.Return(combined); + } + } + + _httpStreamReady.Release(); + } + + /// + /// Called by RawStreamingHttpClient to wait until the handler + /// finishes writing (MarkCompleted or ReportError). + /// + internal async Task WaitForCompletionAsync(CancellationToken cancellationToken = default) + { + await _completionSignal.WaitAsync(cancellationToken); + } + + internal async Task WriteAsync(byte[] buffer, CancellationToken cancellationToken = default) + { + if (buffer == null) + throw new ArgumentNullException(nameof(buffer)); + await WriteAsync(buffer, 0, buffer.Length, cancellationToken); + } + + /// + /// Asynchronously writes a portion of a byte array to the response stream. + /// + /// The byte array containing data to write. + /// The zero-based byte offset in buffer at which to begin copying bytes. + /// The number of bytes to write. + /// Optional cancellation token. + /// A task representing the asynchronous operation. + /// Thrown if the stream is already completed or an error has been reported. + public async Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken = default) + { + if (buffer == null) + throw new ArgumentNullException(nameof(buffer)); + if (offset < 0 || offset > buffer.Length) + throw new ArgumentOutOfRangeException(nameof(offset)); + if (count < 0 || offset + count > buffer.Length) + throw new ArgumentOutOfRangeException(nameof(count)); + + // Wait for the HTTP stream to be ready (first write only blocks) + await _httpStreamReady.WaitAsync(_httpStreamWaitTimeout, cancellationToken); + try + { + _logger.LogDebug("Writing chunk to HTTP response stream."); + + lock (_lock) + { + // Only throw on error, not on completed. For buffered ASP.NET Core responses + // (e.g. Results.Json), the pipeline completes and LambdaBootstrap calls + // MarkCompleted() before the pre-start buffer has been flushed to the wire. + // The buffered data still needs to be written even after MarkCompleted. + if (_hasError) + throw new InvalidOperationException("Cannot write to a stream after an error has been reported."); + _bytesWritten += count; + } + + await _httpOutputStream.WriteAsync(buffer, offset, count, cancellationToken); + await _httpOutputStream.FlushAsync(cancellationToken); + } + finally + { + // Re-release so subsequent writes don't block + _httpStreamReady.Release(); + } + } + + /// + /// Reports an error that occurred during streaming. + /// This will send error information via HTTP trailing headers. + /// + /// The exception to report. + /// Thrown if the stream is already completed or an error has already been reported. + internal void ReportError(Exception exception) + { + if (exception == null) + throw new ArgumentNullException(nameof(exception)); + + lock (_lock) + { + if (_isCompleted) + throw new InvalidOperationException("Cannot report an error after the stream has been completed."); + if (_hasError) + throw new InvalidOperationException("An error has already been reported for this stream."); + + _hasError = true; + _reportedError = exception; + _isCompleted = true; + } + // Signal completion so RawStreamingHttpClient can write error trailers and finish + _completionSignal.Release(); + } + + internal void MarkCompleted() + { + bool shouldReleaseLock = false; + lock (_lock) + { + // Release lock if not already completed, otherwise do nothing (idempotent) + if (!_isCompleted) + { + shouldReleaseLock = true; + } + _isCompleted = true; + } + + if (shouldReleaseLock) + { + // Signal completion so RawStreamingHttpClient can write the final chunk and finish + _completionSignal.Release(); + } + } + + private void ThrowIfCompletedOrError() + { + if (_isCompleted) + throw new InvalidOperationException("Cannot write to a completed stream."); + if (_hasError) + throw new InvalidOperationException("Cannot write to a stream after an error has been reported."); + } + + /// + /// Disposes the stream. After calling Dispose, no further writes or error reports should be made. + /// + /// + protected virtual void Dispose(bool disposing) + { + if (!_disposedValue) + { + if (disposing) + { + try { _httpStreamReady.Release(); } catch (SemaphoreFullException) { /* Ignore if already released */ } + _httpStreamReady.Dispose(); + + try { _completionSignal.Release(); } catch (SemaphoreFullException) { /* Ignore if already released */ } + _completionSignal.Dispose(); + } + + _disposedValue = true; + } + } + + /// + /// Dispose of the stream. After calling Dispose, no further writes or error reports should be made. + /// + public void Dispose() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(disposing: true); + GC.SuppressFinalize(this); + } + } +} diff --git a/Libraries/src/Amazon.Lambda.RuntimeSupport/Bootstrap/ResponseStreaming/ResponseStreamContext.cs b/Libraries/src/Amazon.Lambda.RuntimeSupport/Bootstrap/ResponseStreaming/ResponseStreamContext.cs new file mode 100644 index 000000000..970c43138 --- /dev/null +++ b/Libraries/src/Amazon.Lambda.RuntimeSupport/Bootstrap/ResponseStreaming/ResponseStreamContext.cs @@ -0,0 +1,59 @@ +/* + * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +using System; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; + +namespace Amazon.Lambda.RuntimeSupport.Client.ResponseStreaming +{ + /// + /// Internal context class used by ResponseStreamFactory to track per-invocation streaming state. + /// + internal class ResponseStreamContext + { + /// + /// The AWS request ID for the current invocation. + /// + public string AwsRequestId { get; set; } + + /// + /// Whether CreateStream() has been called for this invocation. + /// + public bool StreamCreated { get; set; } + + /// + /// The ResponseStream instance if created. + /// + public ResponseStream Stream { get; set; } + + /// + /// The RuntimeApiClient used to start the streaming HTTP POST. + /// + public RuntimeApiClient RuntimeApiClient { get; set; } + + /// + /// Cancellation token for the current invocation. + /// + public CancellationToken CancellationToken { get; set; } + + /// + /// The Task representing the in-flight HTTP POST to the Runtime API. + /// Started when CreateStream() is called, completes when the stream is finalized. + /// + public Task SendTask { get; set; } + } +} diff --git a/Libraries/src/Amazon.Lambda.RuntimeSupport/Bootstrap/ResponseStreaming/ResponseStreamFactory.cs b/Libraries/src/Amazon.Lambda.RuntimeSupport/Bootstrap/ResponseStreaming/ResponseStreamFactory.cs new file mode 100644 index 000000000..27b34e8db --- /dev/null +++ b/Libraries/src/Amazon.Lambda.RuntimeSupport/Bootstrap/ResponseStreaming/ResponseStreamFactory.cs @@ -0,0 +1,133 @@ +/* + * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace Amazon.Lambda.RuntimeSupport.Client.ResponseStreaming +{ + /// + /// Factory for creating streaming responses in AWS Lambda functions. + /// Call CreateStream() within your handler to opt into response streaming for that invocation. + /// + internal static class ResponseStreamFactory + { + // For on-demand mode (single invocation at a time) + private static ResponseStreamContext _onDemandContext; + + // For multi-concurrency mode (multiple concurrent invocations) + private static readonly AsyncLocal _asyncLocalContext = new AsyncLocal(); + + /// + /// Creates a streaming response for the current invocation. + /// Can only be called once per invocation. + /// + /// + /// + /// Thrown if called outside an invocation context. + /// Thrown if called more than once per invocation. + public static ResponseStream CreateStream(byte[] prelude) + { +#if NET8_0_OR_GREATER + var context = GetCurrentContext(); + + if (context == null) + { + throw new InvalidOperationException( + "ResponseStreamFactory.CreateStream() can only be called within a Lambda handler invocation."); + } + + if (context.StreamCreated) + { + throw new InvalidOperationException( + "ResponseStreamFactory.CreateStream() can only be called once per invocation."); + } + + var lambdaStream = new ResponseStream(prelude); + context.Stream = lambdaStream; + context.StreamCreated = true; + + // Start the HTTP POST to the Runtime API. + // This runs concurrently — SerializeToStreamAsync will block + // until the handler finishes writing or reports an error. + context.SendTask = context.RuntimeApiClient.StartStreamingResponseAsync( + context.AwsRequestId, lambdaStream, context.CancellationToken); + + return lambdaStream; +#else + throw new NotImplementedException(); +#endif + } + + // Internal methods for LambdaBootstrap to manage state + + internal static void InitializeInvocation( + string awsRequestId, bool isMultiConcurrency, + RuntimeApiClient runtimeApiClient, CancellationToken cancellationToken) + { + var context = new ResponseStreamContext + { + AwsRequestId = awsRequestId, + StreamCreated = false, + Stream = null, + RuntimeApiClient = runtimeApiClient, + CancellationToken = cancellationToken + }; + + if (isMultiConcurrency) + { + _asyncLocalContext.Value = context; + } + else + { + _onDemandContext = context; + } + } + + internal static ResponseStream GetStreamIfCreated(bool isMultiConcurrency) + { + var context = isMultiConcurrency ? _asyncLocalContext.Value : _onDemandContext; + return context?.Stream; + } + + /// + /// Returns the Task for the in-flight HTTP send, or null if streaming wasn't started. + /// LambdaBootstrap awaits this after the handler returns to ensure the HTTP request completes. + /// + internal static Task GetSendTask(bool isMultiConcurrency) + { + var context = isMultiConcurrency ? _asyncLocalContext.Value : _onDemandContext; + return context?.SendTask; + } + + internal static void CleanupInvocation(bool isMultiConcurrency) + { + if (isMultiConcurrency) + { + _asyncLocalContext.Value = null; + } + else + { + _onDemandContext = null; + } + } + + private static ResponseStreamContext GetCurrentContext() + { + // Check multi-concurrency first (AsyncLocal), then on-demand + return _asyncLocalContext.Value ?? _onDemandContext; + } + } +} diff --git a/Libraries/src/Amazon.Lambda.RuntimeSupport/Bootstrap/ResponseStreaming/ResponseStreamLambdaCoreInitializerIsolated.cs b/Libraries/src/Amazon.Lambda.RuntimeSupport/Bootstrap/ResponseStreaming/ResponseStreamLambdaCoreInitializerIsolated.cs new file mode 100644 index 000000000..b86864480 --- /dev/null +++ b/Libraries/src/Amazon.Lambda.RuntimeSupport/Bootstrap/ResponseStreaming/ResponseStreamLambdaCoreInitializerIsolated.cs @@ -0,0 +1,61 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +#if NET8_0_OR_GREATER + +using System; +using System.Threading; +using System.Threading.Tasks; +using Amazon.Lambda.Core.ResponseStreaming; +using Amazon.Lambda.RuntimeSupport.Client.ResponseStreaming; +#pragma warning disable CA2252 +namespace Amazon.Lambda.RuntimeSupport +{ + /// + /// This class is used to connect the created by to Amazon.Lambda.Core with it's public interfaces. + /// The deployed Lambda function might be referencing an older version of Amazon.Lambda.Core that does not have the public interfaces for response streaming, + /// so this class is used to avoid a direct dependency on Amazon.Lambda.Core in the rest of the response streaming implementation. + /// + /// Any code referencing this class must wrap the code around a try/catch for to allow for the case where the Lambda function + /// is deployed with an older version of Amazon.Lambda.Core that does not have the response streaming interfaces. + /// + /// + internal class ResponseStreamLambdaCoreInitializerIsolated + { + /// + /// Initalize Amazon.Lambda.Core with a factory method for creating that wraps the internal implementation. + /// + internal static void InitializeCore() + { +#if !ANALYZER_UNIT_TESTS // This precompiler directive is used to avoid the unit tests from needing a dependency on Amazon.Lambda.Core. + Func factory = (byte[] prelude) => new ImplLambdaResponseStream(ResponseStreamFactory.CreateStream(prelude)); + LambdaResponseStreamFactory.SetLambdaResponseStream(factory); +#endif + } + + /// + /// Implements the interface by wrapping a . This is used to connect the internal response streaming implementation to the public interfaces in Amazon.Lambda.Core. + /// + internal class ImplLambdaResponseStream : ILambdaResponseStream + { + private readonly ResponseStream _innerStream; + + internal ImplLambdaResponseStream(ResponseStream innerStream) + { + _innerStream = innerStream; + } + + /// + public long BytesWritten => _innerStream.BytesWritten; + + /// + public bool HasError => _innerStream.HasError; + + /// + public void Dispose() => _innerStream.Dispose(); + + /// + public Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken = default) => _innerStream.WriteAsync(buffer, offset, count, cancellationToken); + } + } +} +#endif diff --git a/Libraries/src/Amazon.Lambda.RuntimeSupport/Bootstrap/ResponseStreaming/StreamingConstants.cs b/Libraries/src/Amazon.Lambda.RuntimeSupport/Bootstrap/ResponseStreaming/StreamingConstants.cs new file mode 100644 index 000000000..43ac607b7 --- /dev/null +++ b/Libraries/src/Amazon.Lambda.RuntimeSupport/Bootstrap/ResponseStreaming/StreamingConstants.cs @@ -0,0 +1,43 @@ +/* + * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +namespace Amazon.Lambda.RuntimeSupport.Client.ResponseStreaming +{ + /// + /// Constants used for Lambda response streaming. + /// + internal static class StreamingConstants + { + /// + /// Header name for Lambda response mode. + /// + public const string ResponseModeHeader = "Lambda-Runtime-Function-Response-Mode"; + + /// + /// Value for streaming response mode. + /// + public const string StreamingResponseMode = "streaming"; + + /// + /// Trailer header name for error type. + /// + public const string ErrorTypeTrailer = "Lambda-Runtime-Function-Error-Type"; + + /// + /// Trailer header name for error body. + /// + public const string ErrorBodyTrailer = "Lambda-Runtime-Function-Error-Body"; + } +} diff --git a/Libraries/src/Amazon.Lambda.RuntimeSupport/Client/RuntimeApiClient.cs b/Libraries/src/Amazon.Lambda.RuntimeSupport/Client/RuntimeApiClient.cs index daa9fff24..0cddfcd2a 100644 --- a/Libraries/src/Amazon.Lambda.RuntimeSupport/Client/RuntimeApiClient.cs +++ b/Libraries/src/Amazon.Lambda.RuntimeSupport/Client/RuntimeApiClient.cs @@ -20,6 +20,7 @@ using System.Threading; using System.Threading.Tasks; using Amazon.Lambda.RuntimeSupport.Bootstrap; +using Amazon.Lambda.RuntimeSupport.Client.ResponseStreaming; namespace Amazon.Lambda.RuntimeSupport { @@ -177,6 +178,34 @@ public Task ReportRestoreErrorAsync(Exception exception, String errorType = null #endif +#if NET8_0_OR_GREATER + /// + /// Start sending a streaming response to the Lambda Runtime API. + /// Uses a raw TCP connection with chunked transfer encoding to support HTTP/1.1 + /// trailing headers for error reporting, which .NET's HttpClient does not support. + /// The actual data is written by the handler via ResponseStream.WriteAsync, which flows + /// through a ChunkedStreamWriter to the TCP connection. + /// This Task completes when the stream is finalized (MarkCompleted or error). + /// + /// The ID of the function request being responded to. + /// The ResponseStream that will provide the streaming data. + /// The optional cancellation token to use. + /// A Task representing the in-flight HTTP POST. The returned IDisposable is the RawStreamingHttpClient that owns the TCP connection. + internal virtual async Task StartStreamingResponseAsync( + string awsRequestId, ResponseStream responseStream, CancellationToken cancellationToken = default) + { + if (awsRequestId == null) throw new ArgumentNullException(nameof(awsRequestId)); + if (responseStream == null) throw new ArgumentNullException(nameof(responseStream)); + + var userAgent = _httpClient.DefaultRequestHeaders.UserAgent.ToString(); + var rawClient = new RawStreamingHttpClient(LambdaEnvironment.RuntimeServerHostAndPort); + + await rawClient.SendStreamingResponseAsync(awsRequestId, responseStream, userAgent, cancellationToken); + + return rawClient; + } +#endif + /// /// Send a response to a function invocation to the Runtime API as an asynchronous operation. /// diff --git a/Libraries/src/Amazon.Lambda.RuntimeSupport/Helpers/ConsoleLoggerWriter.cs b/Libraries/src/Amazon.Lambda.RuntimeSupport/Helpers/ConsoleLoggerWriter.cs index 2caa708e3..a2417cbcc 100644 --- a/Libraries/src/Amazon.Lambda.RuntimeSupport/Helpers/ConsoleLoggerWriter.cs +++ b/Libraries/src/Amazon.Lambda.RuntimeSupport/Helpers/ConsoleLoggerWriter.cs @@ -227,6 +227,7 @@ public LogLevelLoggerWriter(IEnvironmentVariables environmentVariables) /// public LogLevelLoggerWriter(TextWriter stdOutWriter, TextWriter stdErrorWriter) { + _environmentVariables = new SystemEnvironmentVariables(); Initialize(stdOutWriter, stdErrorWriter); } @@ -325,7 +326,7 @@ public IRuntimeApiHeaders CurrentRuntimeApiHeaders { get { - if (Utils.IsUsingMultiConcurrency(_environmentVariables)) + if (_currentRuntimeApiHeadersStorage != null && Utils.IsUsingMultiConcurrency(_environmentVariables)) { return _currentRuntimeApiHeadersStorage.Value; } @@ -333,7 +334,7 @@ public IRuntimeApiHeaders CurrentRuntimeApiHeaders } set { - if (Utils.IsUsingMultiConcurrency(_environmentVariables)) + if (_currentRuntimeApiHeadersStorage != null && Utils.IsUsingMultiConcurrency(_environmentVariables)) { _currentRuntimeApiHeadersStorage.Value = value; } diff --git a/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/ALBApiAttributeTests.cs b/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/ALBApiAttributeTests.cs new file mode 100644 index 000000000..10cb530b1 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/ALBApiAttributeTests.cs @@ -0,0 +1,495 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +using Amazon.Lambda.Annotations.ALB; +using System.Linq; +using Xunit; + +namespace Amazon.Lambda.Annotations.SourceGenerators.Tests +{ + public class ALBApiAttributeTests + { + [Fact] + public void Constructor_SetsRequiredProperties() + { + // Arrange & Act + var attr = new ALBApiAttribute( + "arn:aws:elasticloadbalancing:us-east-1:123456789012:listener/app/my-alb/50dc6c495c0c9188/f2f7dc8efc522ab2", + "/api/orders/*", + 10); + + // Assert + Assert.Equal("arn:aws:elasticloadbalancing:us-east-1:123456789012:listener/app/my-alb/50dc6c495c0c9188/f2f7dc8efc522ab2", attr.ListenerArn); + Assert.Equal("/api/orders/*", attr.PathPattern); + Assert.Equal(10, attr.Priority); + } + + [Fact] + public void DefaultValues_AreCorrect() + { + // Arrange & Act + var attr = new ALBApiAttribute("arn:aws:elasticloadbalancing:us-east-1:123456789012:listener/app/my-alb/abc/def", "/hello", 1); + + // Assert + Assert.False(attr.MultiValueHeaders); + Assert.False(attr.IsMultiValueHeadersSet); + Assert.Null(attr.HostHeader); + Assert.Null(attr.HttpMethod); + Assert.Null(attr.ResourceName); + Assert.False(attr.IsResourceNameSet); + } + + [Fact] + public void MultiValueHeaders_WhenExplicitlySet_IsTracked() + { + var attr = new ALBApiAttribute("arn:aws:elasticloadbalancing:us-east-1:123456789012:listener/app/my-alb/abc/def", "/hello", 1); + + // Before setting + Assert.False(attr.IsMultiValueHeadersSet); + + // After setting to false explicitly + attr.MultiValueHeaders = false; + Assert.True(attr.IsMultiValueHeadersSet); + Assert.False(attr.MultiValueHeaders); + + // After setting to true + attr.MultiValueHeaders = true; + Assert.True(attr.IsMultiValueHeadersSet); + Assert.True(attr.MultiValueHeaders); + } + + [Fact] + public void ResourceName_WhenExplicitlySet_IsTracked() + { + var attr = new ALBApiAttribute("arn:aws:elasticloadbalancing:us-east-1:123456789012:listener/app/my-alb/abc/def", "/hello", 1); + + Assert.False(attr.IsResourceNameSet); + + attr.ResourceName = "MyCustomName"; + Assert.True(attr.IsResourceNameSet); + Assert.Equal("MyCustomName", attr.ResourceName); + } + + [Fact] + public void TemplateReference_IsAccepted() + { + var attr = new ALBApiAttribute("@MyALBListener", "/api/*", 5); + + Assert.Equal("@MyALBListener", attr.ListenerArn); + Assert.StartsWith("@", attr.ListenerArn); + } + + [Fact] + public void OptionalProperties_CanBeSet() + { + var attr = new ALBApiAttribute("@MyALBListener", "/api/*", 5) + { + HostHeader = "api.example.com", + HttpMethod = "GET", + MultiValueHeaders = true, + ResourceName = "MyALBTarget" + }; + + Assert.Equal("api.example.com", attr.HostHeader); + Assert.Equal("GET", attr.HttpMethod); + Assert.True(attr.MultiValueHeaders); + Assert.Equal("MyALBTarget", attr.ResourceName); + } + + // ===== Validation Tests ===== + + [Fact] + public void Validate_ValidArn_ReturnsNoErrors() + { + var attr = new ALBApiAttribute( + "arn:aws:elasticloadbalancing:us-east-1:123456789012:listener/app/my-alb/abc/def", + "/api/*", + 1); + + var errors = attr.Validate(); + Assert.Empty(errors); + } + + [Fact] + public void Validate_ValidTemplateReference_ReturnsNoErrors() + { + var attr = new ALBApiAttribute("@MyALBListener", "/api/*", 1); + + var errors = attr.Validate(); + Assert.Empty(errors); + } + + [Fact] + public void Validate_EmptyListenerArn_ReturnsError() + { + var attr = new ALBApiAttribute("", "/api/*", 1); + + var errors = attr.Validate(); + Assert.Single(errors); + Assert.Contains("ListenerArn", errors[0]); + Assert.Contains("required", errors[0]); + } + + [Fact] + public void Validate_NullListenerArn_ReturnsError() + { + var attr = new ALBApiAttribute(null, "/api/*", 1); + + var errors = attr.Validate(); + Assert.Single(errors); + Assert.Contains("ListenerArn", errors[0]); + } + + [Fact] + public void Validate_InvalidListenerArn_NotArnOrReference_ReturnsError() + { + var attr = new ALBApiAttribute("some-random-string", "/api/*", 1); + + var errors = attr.Validate(); + Assert.Single(errors); + Assert.Contains("ListenerArn", errors[0]); + Assert.Contains("arn:", errors[0]); + } + + [Fact] + public void Validate_EmptyPathPattern_ReturnsError() + { + var attr = new ALBApiAttribute("@MyListener", "", 1); + + var errors = attr.Validate(); + Assert.Single(errors); + Assert.Contains("PathPattern", errors[0]); + Assert.Contains("required", errors[0]); + } + + [Fact] + public void Validate_NullPathPattern_ReturnsError() + { + var attr = new ALBApiAttribute("@MyListener", null, 1); + + var errors = attr.Validate(); + Assert.Single(errors); + Assert.Contains("PathPattern", errors[0]); + } + + [Theory] + [InlineData(0)] + [InlineData(-1)] + [InlineData(50001)] + [InlineData(100000)] + public void Validate_InvalidPriority_ReturnsError(int priority) + { + var attr = new ALBApiAttribute("@MyListener", "/api/*", priority); + + var errors = attr.Validate(); + Assert.Single(errors); + Assert.Contains("Priority", errors[0]); + Assert.Contains("1 and 50000", errors[0]); + } + + [Theory] + [InlineData(1)] + [InlineData(50000)] + [InlineData(100)] + [InlineData(25000)] + public void Validate_ValidPriority_ReturnsNoErrors(int priority) + { + var attr = new ALBApiAttribute("@MyListener", "/api/*", priority); + + var errors = attr.Validate(); + Assert.Empty(errors); + } + + [Fact] + public void Validate_InvalidResourceName_ReturnsError() + { + var attr = new ALBApiAttribute("@MyListener", "/api/*", 1) + { + ResourceName = "invalid-name!" + }; + + var errors = attr.Validate(); + Assert.Single(errors); + Assert.Contains("ResourceName", errors[0]); + Assert.Contains("alphanumeric", errors[0]); + } + + [Fact] + public void Validate_ValidResourceName_ReturnsNoErrors() + { + var attr = new ALBApiAttribute("@MyListener", "/api/*", 1) + { + ResourceName = "MyValidResource123" + }; + + var errors = attr.Validate(); + Assert.Empty(errors); + } + + [Fact] + public void Validate_UnsetResourceName_ReturnsNoErrors() + { + // ResourceName not set should not produce validation errors + var attr = new ALBApiAttribute("@MyListener", "/api/*", 1); + + var errors = attr.Validate(); + Assert.Empty(errors); + Assert.False(attr.IsResourceNameSet); + } + + [Theory] + [InlineData("GET")] + [InlineData("POST")] + [InlineData("PUT")] + [InlineData("PATCH")] + [InlineData("DELETE")] + [InlineData("HEAD")] + [InlineData("OPTIONS")] + [InlineData("get")] + [InlineData("post")] + public void Validate_ValidHttpMethod_ReturnsNoErrors(string method) + { + var attr = new ALBApiAttribute("@MyListener", "/api/*", 1) + { + HttpMethod = method + }; + + var errors = attr.Validate(); + Assert.Empty(errors); + } + + [Fact] + public void Validate_InvalidHttpMethod_ReturnsError() + { + var attr = new ALBApiAttribute("@MyListener", "/api/*", 1) + { + HttpMethod = "INVALID" + }; + + var errors = attr.Validate(); + Assert.Single(errors); + Assert.Contains("HttpMethod", errors[0]); + } + + [Fact] + public void Validate_NullHttpMethod_ReturnsNoErrors() + { + var attr = new ALBApiAttribute("@MyListener", "/api/*", 1) + { + HttpMethod = null + }; + + var errors = attr.Validate(); + Assert.Empty(errors); + } + + [Fact] + public void Validate_MultipleErrors_ReturnsAll() + { + var attr = new ALBApiAttribute("", "", 0) + { + ResourceName = "invalid-name!", + HttpMethod = "INVALID" + }; + + var errors = attr.Validate(); + // Should have errors for: ListenerArn, PathPattern, Priority, ResourceName, HttpMethod + Assert.Equal(5, errors.Count); + Assert.Contains(errors, e => e.Contains("ListenerArn")); + Assert.Contains(errors, e => e.Contains("PathPattern")); + Assert.Contains(errors, e => e.Contains("Priority")); + Assert.Contains(errors, e => e.Contains("ResourceName")); + Assert.Contains(errors, e => e.Contains("HttpMethod")); + } + + [Fact] + public void Validate_AllValidWithOptionals_ReturnsNoErrors() + { + var attr = new ALBApiAttribute( + "arn:aws:elasticloadbalancing:us-east-1:123456789012:listener/app/my-alb/abc/def", + "/api/v1/products/*", + 42) + { + MultiValueHeaders = true, + HostHeader = "api.example.com", + HttpMethod = "POST", + ResourceName = "ProductsALB" + }; + + var errors = attr.Validate(); + Assert.Empty(errors); + } + + // ===== HTTP Header Condition Tests ===== + + [Fact] + public void HttpHeaderCondition_DefaultValues_AreNull() + { + var attr = new ALBApiAttribute("@MyListener", "/api/*", 1); + + Assert.Null(attr.HttpHeaderConditionName); + Assert.Null(attr.HttpHeaderConditionValues); + } + + [Fact] + public void HttpHeaderCondition_BothSet_ReturnsNoErrors() + { + var attr = new ALBApiAttribute("@MyListener", "/api/*", 1) + { + HttpHeaderConditionName = "X-Environment", + HttpHeaderConditionValues = new[] { "dev", "staging" } + }; + + var errors = attr.Validate(); + Assert.Empty(errors); + Assert.Equal("X-Environment", attr.HttpHeaderConditionName); + Assert.Equal(2, attr.HttpHeaderConditionValues.Length); + Assert.Equal("dev", attr.HttpHeaderConditionValues[0]); + Assert.Equal("staging", attr.HttpHeaderConditionValues[1]); + } + + [Fact] + public void HttpHeaderCondition_NameSetWithoutValues_ReturnsError() + { + var attr = new ALBApiAttribute("@MyListener", "/api/*", 1) + { + HttpHeaderConditionName = "X-Environment" + }; + + var errors = attr.Validate(); + Assert.Single(errors); + Assert.Contains("HttpHeaderConditionName", errors[0]); + Assert.Contains("HttpHeaderConditionValues", errors[0]); + } + + [Fact] + public void HttpHeaderCondition_ValuesSetWithoutName_ReturnsError() + { + var attr = new ALBApiAttribute("@MyListener", "/api/*", 1) + { + HttpHeaderConditionValues = new[] { "dev" } + }; + + var errors = attr.Validate(); + Assert.Single(errors); + Assert.Contains("HttpHeaderConditionValues", errors[0]); + Assert.Contains("HttpHeaderConditionName", errors[0]); + } + + [Fact] + public void HttpHeaderCondition_NameSetWithEmptyValues_ReturnsError() + { + var attr = new ALBApiAttribute("@MyListener", "/api/*", 1) + { + HttpHeaderConditionName = "User-Agent", + HttpHeaderConditionValues = new string[0] + }; + + var errors = attr.Validate(); + Assert.Single(errors); + Assert.Contains("HttpHeaderConditionName", errors[0]); + } + + [Fact] + public void HttpHeaderCondition_WithWildcards_ReturnsNoErrors() + { + var attr = new ALBApiAttribute("@MyListener", "/api/*", 1) + { + HttpHeaderConditionName = "User-Agent", + HttpHeaderConditionValues = new[] { "*Chrome*", "*Safari*" } + }; + + var errors = attr.Validate(); + Assert.Empty(errors); + } + + // ===== Query String Condition Tests ===== + + [Fact] + public void QueryStringConditions_DefaultValue_IsNull() + { + var attr = new ALBApiAttribute("@MyListener", "/api/*", 1); + Assert.Null(attr.QueryStringConditions); + } + + [Fact] + public void QueryStringConditions_WithKeyValuePairs_ReturnsNoErrors() + { + var attr = new ALBApiAttribute("@MyListener", "/api/*", 1) + { + QueryStringConditions = new[] { "version=v1", "=*example*" } + }; + + var errors = attr.Validate(); + Assert.Empty(errors); + Assert.Equal(2, attr.QueryStringConditions.Length); + Assert.Equal("version=v1", attr.QueryStringConditions[0]); + Assert.Equal("=*example*", attr.QueryStringConditions[1]); + } + + [Fact] + public void QueryStringConditions_WithSingleEntry_ReturnsNoErrors() + { + var attr = new ALBApiAttribute("@MyListener", "/api/*", 1) + { + QueryStringConditions = new[] { "env=prod" } + }; + + var errors = attr.Validate(); + Assert.Empty(errors); + } + + // ===== Source IP Condition Tests ===== + + [Fact] + public void SourceIpConditions_DefaultValue_IsNull() + { + var attr = new ALBApiAttribute("@MyListener", "/api/*", 1); + Assert.Null(attr.SourceIpConditions); + } + + [Fact] + public void SourceIpConditions_WithCidrBlocks_ReturnsNoErrors() + { + var attr = new ALBApiAttribute("@MyListener", "/api/*", 1) + { + SourceIpConditions = new[] { "192.0.2.0/24", "198.51.100.10/32" } + }; + + var errors = attr.Validate(); + Assert.Empty(errors); + Assert.Equal(2, attr.SourceIpConditions.Length); + } + + [Fact] + public void SourceIpConditions_WithIPv6_ReturnsNoErrors() + { + var attr = new ALBApiAttribute("@MyListener", "/api/*", 1) + { + SourceIpConditions = new[] { "2001:db8::/32" } + }; + + var errors = attr.Validate(); + Assert.Empty(errors); + } + + // ===== Combined Condition Tests ===== + + [Fact] + public void AllConditions_CanBeSetTogether_ReturnsNoErrors() + { + var attr = new ALBApiAttribute("@MyListener", "/api/*", 1) + { + HostHeader = "api.example.com", + HttpMethod = "POST", + HttpHeaderConditionName = "X-Environment", + HttpHeaderConditionValues = new[] { "dev" }, + QueryStringConditions = new[] { "version=v1" }, + SourceIpConditions = new[] { "10.0.0.0/8" } + }; + + var errors = attr.Validate(); + Assert.Empty(errors); + } + } +} diff --git a/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/ALBApiModelTests.cs b/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/ALBApiModelTests.cs new file mode 100644 index 000000000..f45d4a67a --- /dev/null +++ b/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/ALBApiModelTests.cs @@ -0,0 +1,272 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +using Amazon.Lambda.Annotations.SourceGenerator; +using Amazon.Lambda.Annotations.SourceGenerator.Diagnostics; +using Amazon.Lambda.Annotations.SourceGenerator.Extensions; +using Amazon.Lambda.Annotations.SourceGenerator.Models; +using Amazon.Lambda.Annotations.SourceGenerator.Models.Attributes; +using System.Collections.Generic; +using System.Linq; +using Xunit; + +namespace Amazon.Lambda.Annotations.SourceGenerators.Tests +{ + public class ALBApiModelTests + { + [Fact] + public void TypeFullNames_ContainsALBConstants() + { + Assert.Equal("Amazon.Lambda.ApplicationLoadBalancerEvents.ApplicationLoadBalancerRequest", TypeFullNames.ApplicationLoadBalancerRequest); + Assert.Equal("Amazon.Lambda.ApplicationLoadBalancerEvents.ApplicationLoadBalancerResponse", TypeFullNames.ApplicationLoadBalancerResponse); + Assert.Equal("Amazon.Lambda.Annotations.ALB.ALBApiAttribute", TypeFullNames.ALBApiAttribute); + } + + [Fact] + public void TypeFullNames_Events_ContainsALBApiAttribute() + { + Assert.Contains(TypeFullNames.ALBApiAttribute, TypeFullNames.Events); + } + + [Fact] + public void TypeFullNames_ALBRequests_ContainsLoadBalancerRequest() + { + Assert.Contains(TypeFullNames.ApplicationLoadBalancerRequest, TypeFullNames.ALBRequests); + Assert.Single(TypeFullNames.ALBRequests); + } + + [Fact] + public void EventType_HasALBValue() + { + // Verify the ALB enum value exists + var albEvent = EventType.ALB; + Assert.Equal(EventType.ALB, albEvent); + + // Verify it's distinct from other event types + Assert.NotEqual(EventType.API, albEvent); + Assert.NotEqual(EventType.SQS, albEvent); + } + + [Fact] + public void ALBApiAttributeBuilder_BuildsFromConstructorArgs() + { + // This tests the attribute builder by constructing an ALBApiAttribute directly + // (since we can't easily mock Roslyn AttributeData in unit tests, we test the attribute itself) + var attr = new Annotations.ALB.ALBApiAttribute("@MyListener", "/api/*", 5); + + Assert.Equal("@MyListener", attr.ListenerArn); + Assert.Equal("/api/*", attr.PathPattern); + Assert.Equal(5, attr.Priority); + } + + [Fact] + public void ALBApiAttributeBuilder_BuildsWithAllOptionalProperties() + { + var attr = new Annotations.ALB.ALBApiAttribute("arn:aws:elasticloadbalancing:us-east-1:123456789012:listener/app/my-alb/abc/def", "/api/v1/*", 10) + { + MultiValueHeaders = true, + HostHeader = "api.example.com", + HttpMethod = "POST", + ResourceName = "MyCustomALB" + }; + + Assert.Equal("arn:aws:elasticloadbalancing:us-east-1:123456789012:listener/app/my-alb/abc/def", attr.ListenerArn); + Assert.Equal("/api/v1/*", attr.PathPattern); + Assert.Equal(10, attr.Priority); + Assert.True(attr.MultiValueHeaders); + Assert.True(attr.IsMultiValueHeadersSet); + Assert.Equal("api.example.com", attr.HostHeader); + Assert.Equal("POST", attr.HttpMethod); + Assert.Equal("MyCustomALB", attr.ResourceName); + Assert.True(attr.IsResourceNameSet); + } + + [Fact] + public void LambdaMethodModel_ReturnsApplicationLoadBalancerResponse_WhenDirectReturn() + { + var model = new LambdaMethodModel + { + ReturnsVoid = false, + ReturnsGenericTask = false, + ReturnType = new TypeModel + { + FullName = TypeFullNames.ApplicationLoadBalancerResponse, + TypeArguments = new List() + } + }; + + Assert.True(model.ReturnsApplicationLoadBalancerResponse); + } + + [Fact] + public void LambdaMethodModel_ReturnsApplicationLoadBalancerResponse_WhenTaskReturn() + { + var model = new LambdaMethodModel + { + ReturnsVoid = false, + ReturnsGenericTask = true, + ReturnType = new TypeModel + { + FullName = "System.Threading.Tasks.Task`1", + TypeArguments = new List + { + new TypeModel { FullName = TypeFullNames.ApplicationLoadBalancerResponse } + } + } + }; + + Assert.True(model.ReturnsApplicationLoadBalancerResponse); + } + + [Fact] + public void LambdaMethodModel_ReturnsApplicationLoadBalancerResponse_FalseWhenVoid() + { + var model = new LambdaMethodModel + { + ReturnsVoid = true, + ReturnsGenericTask = false, + ReturnType = new TypeModel + { + FullName = "void", + TypeArguments = new List() + } + }; + + Assert.False(model.ReturnsApplicationLoadBalancerResponse); + } + + [Fact] + public void LambdaMethodModel_ReturnsApplicationLoadBalancerResponse_FalseWhenDifferentType() + { + var model = new LambdaMethodModel + { + ReturnsVoid = false, + ReturnsGenericTask = false, + ReturnType = new TypeModel + { + FullName = "System.String", + TypeArguments = new List() + } + }; + + Assert.False(model.ReturnsApplicationLoadBalancerResponse); + } + + [Fact] + public void ParameterListExtension_ALBRequest_IsNotConvertible() + { + // ApplicationLoadBalancerRequest parameters should be treated as pass-through + var parameters = new List + { + new ParameterModel + { + Name = "request", + Type = new TypeModel { FullName = TypeFullNames.ApplicationLoadBalancerRequest }, + Attributes = new List() + } + }; + + Assert.False(parameters.HasConvertibleParameter()); + } + + [Fact] + public void ParameterListExtension_FromQuery_IsConvertible() + { + // A [FromQuery] string parameter should be convertible + var parameters = new List + { + new ParameterModel + { + Name = "name", + Type = new TypeModel { FullName = "System.String" }, + Attributes = new List + { + new AttributeModel + { + Data = new Annotations.APIGateway.FromQueryAttribute(), + Type = new TypeModel { FullName = TypeFullNames.FromQueryAttribute } + } + } + } + }; + + Assert.True(parameters.HasConvertibleParameter()); + } + + [Fact] + public void ParameterListExtension_ILambdaContext_IsNotConvertible() + { + var parameters = new List + { + new ParameterModel + { + Name = "context", + Type = new TypeModel { FullName = TypeFullNames.ILambdaContext }, + Attributes = new List() + } + }; + + Assert.False(parameters.HasConvertibleParameter()); + } + + [Fact] + public void ParameterListExtension_FromBodyString_IsNotConvertible() + { + // A [FromBody] string parameter should NOT be convertible (string body is pass-through) + var parameters = new List + { + new ParameterModel + { + Name = "body", + Type = new TypeModel { FullName = "string" }, + Attributes = new List + { + new AttributeModel + { + Data = new Annotations.APIGateway.FromBodyAttribute(), + Type = new TypeModel { FullName = TypeFullNames.FromBodyAttribute } + } + } + } + }; + + Assert.False(parameters.HasConvertibleParameter()); + } + + [Fact] + public void DiagnosticDescriptors_FromRouteNotSupportedOnAlb_Exists() + { + Assert.Equal("AWSLambda0134", DiagnosticDescriptors.FromRouteNotSupportedOnAlb.Id); + Assert.Equal(Microsoft.CodeAnalysis.DiagnosticSeverity.Error, DiagnosticDescriptors.FromRouteNotSupportedOnAlb.DefaultSeverity); + } + + [Fact] + public void DiagnosticDescriptors_AlbUnmappedParameter_Exists() + { + Assert.Equal("AWSLambda0135", DiagnosticDescriptors.AlbUnmappedParameter.Id); + Assert.Equal(Microsoft.CodeAnalysis.DiagnosticSeverity.Error, DiagnosticDescriptors.AlbUnmappedParameter.DefaultSeverity); + } + + [Fact] + public void ALBFromQuery_ParameterName_DefaultsToParameterName() + { + // When Name is not set, ALB FromQueryAttribute should default to parameter name + var attr = new Annotations.ALB.FromQueryAttribute(); + Assert.Null(attr.Name); + } + + [Fact] + public void ALBFromQuery_ParameterName_UsesExplicitName() + { + var attr = new Annotations.ALB.FromQueryAttribute { Name = "custom_name" }; + Assert.Equal("custom_name", attr.Name); + } + + [Fact] + public void ALBFromHeader_ParameterName_UsesExplicitName() + { + var attr = new Annotations.ALB.FromHeaderAttribute { Name = "X-Custom-Header" }; + Assert.Equal("X-Custom-Header", attr.Name); + } + } +} diff --git a/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Amazon.Lambda.Annotations.SourceGenerators.Tests.csproj b/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Amazon.Lambda.Annotations.SourceGenerators.Tests.csproj index c8cc6f306..a68fced1f 100644 --- a/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Amazon.Lambda.Annotations.SourceGenerators.Tests.csproj +++ b/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Amazon.Lambda.Annotations.SourceGenerators.Tests.csproj @@ -208,6 +208,9 @@ + + + - + diff --git a/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.IntegrationTests/ApiGatewayStreamingTests.cs b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.IntegrationTests/ApiGatewayStreamingTests.cs new file mode 100644 index 000000000..4401612c7 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.IntegrationTests/ApiGatewayStreamingTests.cs @@ -0,0 +1,447 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +using System; +using System.IO; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Text; +using System.Text.Json; +using System.Threading.Tasks; +using Amazon.CloudFormation; +using Amazon.CloudFormation.Model; +using Amazon.Lambda.RuntimeSupport.IntegrationTests.Helpers; +using Xunit; +using Xunit.Abstractions; + +using InvalidOperationException = System.InvalidOperationException; + +namespace Amazon.Lambda.RuntimeSupport.IntegrationTests +{ + // ───────────────────────────────────────────────────────────────────────────── + // Shared test logic + // ───────────────────────────────────────────────────────────────────────────── + + /// + /// Base class containing all streaming integration test scenarios. + /// Subclasses provide the fixture for a specific deployment type + /// (API Gateway REST API or Lambda Function URL). + /// + public abstract class StreamingTestBase + { + private readonly StreamingFixture _fixture; + protected readonly ITestOutputHelper Output; + + protected StreamingTestBase(StreamingFixture fixture, ITestOutputHelper output) + { + _fixture = fixture; + Output = output; + _fixture.Initialize(output); + } + + [Fact] + public async Task RootEndpoint_ReturnsWelcomeMessage() + { + var apiUrl = await _fixture.GetApiUrlAsync(); + using var httpClient = new HttpClient(); + + var response = await httpClient.GetWithRetryAsync(apiUrl); + + Output.WriteLine($"Status: {response.StatusCode}"); + var body = await response.Content.ReadAsStringAsync(); + Output.WriteLine($"Body: {body}"); + + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + Assert.Contains("Welcome to ASP.NET Core streaming on Lambda", body); + } + + [Fact] + public async Task StreamingEndpoint_ReturnsAllLines() + { + var apiUrl = await _fixture.GetApiUrlAsync(); + using var httpClient = new HttpClient(); + + var response = await httpClient.GetWithRetryAsync($"{apiUrl}streaming-test"); + + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + var body = await response.Content.ReadAsStringAsync(); + Output.WriteLine($"Body length: {body.Length}"); + + Assert.Contains("Line 1", body); + Assert.Contains("Line 50", body); + Assert.Contains("Line 100", body); + } + + [Fact] + public async Task StreamingEndpoint_ContentTypeIsTextPlain() + { + var apiUrl = await _fixture.GetApiUrlAsync(); + using var httpClient = new HttpClient(); + + var response = await httpClient.GetWithRetryAsync($"{apiUrl}streaming-test"); + + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + Assert.Equal("text/plain", response.Content.Headers.ContentType?.MediaType); + } + + [Fact] + public async Task JsonEndpoint_ReturnsValidJson() + { + var apiUrl = await _fixture.GetApiUrlAsync(); + using var httpClient = new HttpClient(); + + var response = await httpClient.GetWithRetryAsync($"{apiUrl}json-response"); + + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + var body = await response.Content.ReadAsStringAsync(); + Output.WriteLine($"Body: {body}"); + + var doc = JsonDocument.Parse(body); + Assert.True(doc.RootElement.TryGetProperty("message", out var msg)); + Assert.Equal("Hello from streaming Lambda", msg.GetString()); + } + + [Fact] + public async Task StreamingErrorEndpoint_StreamIsTruncated() + { + var apiUrl = await _fixture.GetApiUrlAsync(); + using var httpClient = new HttpClient(); + + try + { + var response = await httpClient.GetWithRetryAsync($"{apiUrl}streaming-error"); + var body = await response.Content.ReadAsStringAsync(); + Output.WriteLine($"Status: {response.StatusCode}"); + Output.WriteLine($"Body: {body}"); + + if (response.StatusCode == HttpStatusCode.OK) + { + Assert.Contains("Line 1", body); + } + } + catch (HttpRequestException ex) + { + Output.WriteLine($"Expected error: {ex.Message}"); + } + } + + [Fact] + public async Task OnCompletedCallback_IsExecuted() + { + var apiUrl = await _fixture.GetApiUrlAsync(); + using var httpClient = new HttpClient(); + + var response = await httpClient.GetWithRetryAsync($"{apiUrl}oncompleted-test"); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + var body = await response.Content.ReadAsStringAsync(); + Output.WriteLine($"Body: {body}"); + Assert.Contains("OnCompleted callback registered", body); + + var verifyResponse = await httpClient.GetWithRetryAsync($"{apiUrl}oncompleted-verify"); + Assert.Equal(HttpStatusCode.OK, verifyResponse.StatusCode); + var verifyBody = await verifyResponse.Content.ReadAsStringAsync(); + Output.WriteLine($"Verify body: {verifyBody}"); + + var doc = JsonDocument.Parse(verifyBody); + Assert.True(doc.RootElement.GetProperty("onCompletedExecuted").GetBoolean(), + "OnCompleted callback should have been executed"); + } + + [Fact] + public async Task CustomHeaders_PassedThrough() + { + var apiUrl = await _fixture.GetApiUrlAsync(); + using var httpClient = new HttpClient(); + + var response = await httpClient.GetWithRetryAsync($"{apiUrl}custom-headers", HttpStatusCode.Created); + + Output.WriteLine($"Status: {response.StatusCode}"); + Assert.Equal(HttpStatusCode.Created, response.StatusCode); + + var body = await response.Content.ReadAsStringAsync(); + Assert.Contains("Custom headers response", body); + + Assert.True(response.Headers.Contains("X-Custom-Header"), "X-Custom-Header should be present"); + Assert.Equal("custom-value", response.Headers.GetValues("X-Custom-Header").First()); + Assert.True(response.Headers.Contains("X-Another-Header"), "X-Another-Header should be present"); + Assert.Equal("another-value", response.Headers.GetValues("X-Another-Header").First()); + } + + [Fact] + public async Task SetCookie_PassedThrough() + { + var apiUrl = await _fixture.GetApiUrlAsync(); + var handler = new HttpClientHandler { UseCookies = false }; + using var httpClient = new HttpClient(handler); + + var response = await httpClient.GetWithRetryAsync($"{apiUrl}set-cookie"); + + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + var body = await response.Content.ReadAsStringAsync(); + Output.WriteLine($"Body: {body}"); + Assert.Contains("Cookies set", body); + + Assert.True(response.Headers.Contains("Set-Cookie"), "Set-Cookie header should be present"); + var cookies = response.Headers.GetValues("Set-Cookie").ToList(); + Output.WriteLine($"Cookies: {string.Join("; ", cookies)}"); + Assert.True(cookies.Any(c => c.Contains("session=abc123")), "session cookie should be present"); + Assert.True(cookies.Any(c => c.Contains("theme=dark")), "theme cookie should be present"); + } + + [Fact] + public async Task PostWithBody_EchoesRequestBody() + { + var apiUrl = await _fixture.GetApiUrlAsync(); + using var httpClient = new HttpClient(); + + var content = new StringContent("Hello from integration test", Encoding.UTF8, "text/plain"); + var response = await httpClient.PostAsync($"{apiUrl}echo-body", content); + + Output.WriteLine($"Status: {response.StatusCode}"); + var body = await response.Content.ReadAsStringAsync(); + Output.WriteLine($"Body: {body}"); + + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + Assert.Contains("Echo: Hello from integration test", body); + } + } + + // ───────────────────────────────────────────────────────────────────────────── + // Concrete test classes + // ───────────────────────────────────────────────────────────────────────────── + + /// + /// Tests streaming through API Gateway REST API. + /// + public class RestApiStreamingTests : StreamingTestBase, IClassFixture + { + public RestApiStreamingTests(RestApiStreamingFixture fixture, ITestOutputHelper output) + : base(fixture, output) { } + } + + /// + /// Tests streaming through Lambda Function URL. + /// Function URL uses the same payload format as HTTP API v2. + /// + public class FunctionUrlStreamingTests : StreamingTestBase, IClassFixture + { + public FunctionUrlStreamingTests(FunctionUrlStreamingFixture fixture, ITestOutputHelper output) + : base(fixture, output) { } + } + + // ───────────────────────────────────────────────────────────────────────────── + // Fixtures + // ───────────────────────────────────────────────────────────────────────────── + + public class RestApiStreamingFixture : StreamingFixture + { + public RestApiStreamingFixture() + : base("serverless-restapi.template", "RestApi") { } + } + + public class FunctionUrlStreamingFixture : StreamingFixture + { + public FunctionUrlStreamingFixture() + : base("serverless-functionurl.template", "FunctionUrl") { } + } + + /// + /// Shared fixture that deploys the ASP.NET Core streaming test app to AWS using + /// "dotnet lambda deploy-serverless" and tears it down after tests complete. + /// Parameterized by template file and deployment type. + /// + public class StreamingFixture : IAsyncLifetime + { + private static readonly RegionEndpoint TestRegion = BaseCustomRuntimeTest.TestRegion; + + private readonly string _templateFile; + private readonly string _deploymentType; + private readonly string _stackName; + + private string _apiUrl; + private string _toolPath; + private string _testAppPath; + private bool _deployed; + private string _s3BucketName; + + private ITestOutputHelper _outputHelper; + + protected StreamingFixture(string templateFile, string deploymentType) + { + _templateFile = templateFile; + _deploymentType = deploymentType; + _stackName = $"IntegTest-Streaming-{deploymentType}-{DateTime.UtcNow.Ticks}"; + } + + public void Initialize(ITestOutputHelper outputHelper) + { + _outputHelper = outputHelper; + } + + public Task GetApiUrlAsync() + { + if (!_deployed) + { + throw new InvalidOperationException("Test infrastructure not deployed. InitializeAsync must complete first."); + } + return Task.FromResult(_apiUrl); + } + + public async Task InitializeAsync() + { + _toolPath = await LambdaToolsHelper.InstallLambdaTools(); + + _testAppPath = LambdaToolsHelper.GetTempTestAppDirectory( + "../../../../../../..", + "Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/AspNetCoreStreamingApiGatewayTest"); + + var lambdaToolPath = Path.Combine(_toolPath, "dotnet-lambda"); + _s3BucketName = await GetOrCreateDeploymentBucketAsync(); + await CommandLineWrapper.Run( + lambdaToolPath, + $"deploy-serverless --stack-name {_stackName} --template {_templateFile} --s3-bucket {_s3BucketName} --region {TestRegion.SystemName} --disable-interactive true", + _testAppPath, + _outputHelper); + + _apiUrl = await GetStackOutputAsync(_stackName, "ApiURL"); + if (!_apiUrl.EndsWith("/")) + { + _apiUrl += "/"; + } + + _deployed = true; + + await WaitForEndpointAsync(); + } + + public async Task DisposeAsync() + { + if (_deployed) + { + try + { + var lambdaToolPath = Path.Combine(_toolPath, "dotnet-lambda"); + await CommandLineWrapper.Run( + lambdaToolPath, + $"delete-serverless --stack-name {_stackName} --region {TestRegion.SystemName}", + _testAppPath, + _outputHelper); + + if (_s3BucketName != null) + { + using var s3Client = new Amazon.S3.AmazonS3Client(TestRegion); + try + { + await Amazon.S3.Util.AmazonS3Util.DeleteS3BucketWithObjectsAsync(s3Client, _s3BucketName); + } + catch (Exception ex) + { + Console.WriteLine($"Warning: Failed to delete S3 bucket {_s3BucketName}: {ex.Message}"); + } + } + } + catch (Exception ex) + { + Console.WriteLine($"Warning: Failed to delete stack {_stackName}: {ex.Message}"); + } + } + +#if !DEBUG + LambdaToolsHelper.CleanUp(_toolPath); + LambdaToolsHelper.CleanUp(_testAppPath); +#endif + } + + private async Task GetStackOutputAsync(string stackName, string outputKey) + { + using var cfnClient = new AmazonCloudFormationClient(TestRegion); + var response = await cfnClient.DescribeStacksAsync(new DescribeStacksRequest + { + StackName = stackName + }); + + var stack = response.Stacks.FirstOrDefault() + ?? throw new Exception($"Stack {stackName} not found"); + + var output = stack.Outputs.FirstOrDefault(o => o.OutputKey == outputKey) + ?? throw new Exception($"Output {outputKey} not found in stack {stackName}"); + + return output.OutputValue; + } + + private async Task GetOrCreateDeploymentBucketAsync() + { + using var stsClient = new Amazon.SecurityToken.AmazonSecurityTokenServiceClient(TestRegion); + var identity = await stsClient.GetCallerIdentityAsync(new Amazon.SecurityToken.Model.GetCallerIdentityRequest()); + var name = $"integ-test-streaming-{identity.Account}-{TestRegion.SystemName}"; + using var s3Client = new Amazon.S3.AmazonS3Client(TestRegion); + try + { + await s3Client.PutBucketAsync(new Amazon.S3.Model.PutBucketRequest + { + BucketName = name, + UseClientRegion = true + }); + } + catch (Amazon.S3.AmazonS3Exception ex) when (ex.ErrorCode == "BucketAlreadyOwnedByYou") + { + // Bucket already exists from a previous run — reuse it + } + + return name; + } + + private async Task WaitForEndpointAsync() + { + using var httpClient = new HttpClient(); + var maxRetries = 10; + for (var i = 0; i < maxRetries; i++) + { + try + { + var response = await httpClient.GetAsync(_apiUrl); + if (response.StatusCode != HttpStatusCode.InternalServerError) + { + return; + } + } + catch + { + // Ignore — endpoint may not be ready yet + } + await Task.Delay(TimeSpan.FromSeconds(5)); + } + } + } + + internal static class HttpClientExtension + { + public static async Task GetWithRetryAsync( + this HttpClient httpClient, string url, + HttpStatusCode expectedCode = HttpStatusCode.OK, + int maxRetries = 5, int delaySeconds = 5) + { + for (var i = 0; i < maxRetries; i++) + { + try + { + var response = await httpClient.GetAsync(url); + if (response.StatusCode == expectedCode) + { + return response; + } + } + catch + { + // Ignore and retry + } + await Task.Delay(TimeSpan.FromSeconds(delaySeconds)); + } + throw new Exception($"Failed to get expected status code {expectedCode} from {url} after {maxRetries} attempts"); + } + } +} diff --git a/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.IntegrationTests/BaseCustomRuntimeTest.cs b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.IntegrationTests/BaseCustomRuntimeTest.cs index c220a671e..314aa45c4 100644 --- a/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.IntegrationTests/BaseCustomRuntimeTest.cs +++ b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.IntegrationTests/BaseCustomRuntimeTest.cs @@ -17,7 +17,7 @@ public class BaseCustomRuntimeTest { public const int FUNCTION_MEMORY_MB = 512; - protected static readonly RegionEndpoint TestRegion = RegionEndpoint.USWest2; + public static readonly RegionEndpoint TestRegion = RegionEndpoint.USWest2; protected static readonly string LAMBDA_ASSUME_ROLE_POLICY = @" { @@ -63,7 +63,7 @@ protected BaseCustomRuntimeTest(IntegrationTestFixture fixture, string functionN /// /// /// - protected async Task CleanUpTestResources(AmazonS3Client s3Client, AmazonLambdaClient lambdaClient, + public async Task CleanUpTestResources(AmazonS3Client s3Client, AmazonLambdaClient lambdaClient, AmazonIdentityManagementServiceClient iamClient, bool roleAlreadyExisted) { await DeleteFunctionIfExistsAsync(lambdaClient); @@ -109,7 +109,7 @@ await iamClient.DetachRolePolicyAsync(new DetachRolePolicyRequest } } - protected async Task PrepareTestResources(IAmazonS3 s3Client, IAmazonLambda lambdaClient, + public async Task PrepareTestResources(IAmazonS3 s3Client, IAmazonLambda lambdaClient, AmazonIdentityManagementServiceClient iamClient) { var roleAlreadyExisted = await ValidateAndSetIamRoleArn(iamClient); @@ -288,7 +288,7 @@ protected async Task CreateFunctionAsync(IAmazonLambda lambdaClient, string buck Handler = Handler, MemorySize = FUNCTION_MEMORY_MB, Timeout = 30, - Runtime = Runtime.Dotnet6, + Runtime = Runtime.Dotnet10, Role = ExecutionRoleArn }; @@ -351,7 +351,16 @@ private string GetDeploymentZipPath() if (!File.Exists(deploymentZipFile)) { - throw new NoDeploymentPackageFoundException(); + var message = new StringBuilder(); + message.AppendLine($"Deployment package for {DeploymentPackageZipRelativePath} not found at expected path: {deploymentZipFile}"); + message.AppendLine("Available Test Bundles:"); + foreach (var kvp in _fixture.TestAppPaths) + { + message.AppendLine($"{kvp.Key}: {kvp.Value}"); + } + + + throw new NoDeploymentPackageFoundException(message.ToString()); } return deploymentZipFile; @@ -380,7 +389,9 @@ private static string FindUp(string path, string fileOrDirectoryName, bool combi protected class NoDeploymentPackageFoundException : Exception { + public NoDeploymentPackageFoundException() { } + public NoDeploymentPackageFoundException(string message) : base(message) { } } private ApplicationLogLevel ConvertRuntimeLogLevel(RuntimeLogLevel runtimeLogLevel) diff --git a/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.IntegrationTests/CustomRuntimeTests.cs b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.IntegrationTests/CustomRuntimeTests.cs index b548d5ba0..8ab008d66 100644 --- a/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.IntegrationTests/CustomRuntimeTests.cs +++ b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.IntegrationTests/CustomRuntimeTests.cs @@ -48,7 +48,7 @@ public async Task TestAllNET8HandlersAsync() public class CustomRuntimeTests : BaseCustomRuntimeTest { - public enum TargetFramework { NET6, NET8} + public enum TargetFramework { NET8 } private TargetFramework _targetFramework; diff --git a/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.IntegrationTests/Helpers/CommandLineWrapper.cs b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.IntegrationTests/Helpers/CommandLineWrapper.cs index aa8651eae..d68c73d32 100644 --- a/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.IntegrationTests/Helpers/CommandLineWrapper.cs +++ b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.IntegrationTests/Helpers/CommandLineWrapper.cs @@ -1,14 +1,16 @@ using System; using System.Diagnostics; +using System.Text; using System.Threading; using System.Threading.Tasks; using Xunit; +using Xunit.Abstractions; namespace Amazon.Lambda.RuntimeSupport.IntegrationTests.Helpers; public static class CommandLineWrapper { - public static async Task Run(string command, string arguments, string workingDirectory, CancellationToken cancellationToken = default) + public static async Task Run(string command, string arguments, string workingDirectory, ITestOutputHelper outputHelper, CancellationToken cancellationToken = default) { var processStartInfo = new ProcessStartInfo { @@ -31,6 +33,7 @@ public static async Task Run(string command, string arguments, string workingDir tcs.TrySetResult(true); }; + var output = new StringBuilder(); try { // Attach event handlers @@ -39,6 +42,7 @@ public static async Task Run(string command, string arguments, string workingDir if (!string.IsNullOrEmpty(args.Data)) { Console.WriteLine(args.Data); + output.AppendLine(args.Data); } }; @@ -47,6 +51,7 @@ public static async Task Run(string command, string arguments, string workingDir if (!string.IsNullOrEmpty(args.Data)) { Console.WriteLine(args.Data); + output.AppendLine(args.Data); } }; @@ -78,13 +83,20 @@ public static async Task Run(string command, string arguments, string workingDir catch (Exception ex) { Console.WriteLine("Exception: " + ex); + Console.WriteLine(output.ToString()); if (!process.HasExited) { process.Kill(); } } - + + if (process.ExitCode != 0 && outputHelper != null) + { + outputHelper.WriteLine($"Command '{command} {arguments}' failed."); + outputHelper.WriteLine(output.ToString()); + } + Assert.True(process.ExitCode == 0, $"Command '{command} {arguments}' failed."); } } -} \ No newline at end of file +} diff --git a/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.IntegrationTests/Helpers/LambdaToolsHelper.cs b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.IntegrationTests/Helpers/LambdaToolsHelper.cs index 42a02aac6..5f649d923 100644 --- a/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.IntegrationTests/Helpers/LambdaToolsHelper.cs +++ b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.IntegrationTests/Helpers/LambdaToolsHelper.cs @@ -10,6 +10,9 @@ public static class LambdaToolsHelper public static string GetTempTestAppDirectory(string workingDirectory, string testAppPath) { +#if DEBUG + return Path.GetFullPath(Path.Combine(workingDirectory, testAppPath)); +#else var customTestAppPath = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); Directory.CreateDirectory(customTestAppPath); @@ -17,6 +20,7 @@ public static string GetTempTestAppDirectory(string workingDirectory, string tes CopyDirectory(currentDir, customTestAppPath); return Path.Combine(customTestAppPath, testAppPath); +#endif } public static async Task InstallLambdaTools() @@ -26,7 +30,7 @@ public static async Task InstallLambdaTools() await CommandLineWrapper.Run( "dotnet", $"tool install Amazon.Lambda.Tools --tool-path {customToolPath}", - Directory.GetCurrentDirectory()); + Directory.GetCurrentDirectory(), null); return customToolPath; } @@ -36,7 +40,7 @@ public static async Task LambdaPackage(string toolPath, string framework, string await CommandLineWrapper.Run( lambdaToolPath, $"package -c Release --framework {framework} --function-architecture {FunctionArchitecture}", - workingDirectory); + workingDirectory, null); } public static void CleanUp(string toolPath) @@ -78,4 +82,4 @@ private static void CopyDirectory(DirectoryInfo dir, string destDirName) CopyDirectory(subDir, tempPath); } } -} \ No newline at end of file +} diff --git a/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.IntegrationTests/IntegrationTestCollection.cs b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.IntegrationTests/IntegrationTestCollection.cs index c9ce90e35..9b637b547 100644 --- a/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.IntegrationTests/IntegrationTestCollection.cs +++ b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.IntegrationTests/IntegrationTestCollection.cs @@ -3,7 +3,7 @@ namespace Amazon.Lambda.RuntimeSupport.IntegrationTests; [CollectionDefinition("Integration Tests")] -public class IntegrationTestCollection : ICollectionFixture +public class IntegrationTestCollection : ICollectionFixture, ICollectionFixture { -} \ No newline at end of file +} diff --git a/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.IntegrationTests/IntegrationTestFixture.cs b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.IntegrationTests/IntegrationTestFixture.cs index 89d62d61f..b8c71519e 100644 --- a/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.IntegrationTests/IntegrationTestFixture.cs +++ b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.IntegrationTests/IntegrationTestFixture.cs @@ -14,10 +14,11 @@ public class IntegrationTestFixture : IAsyncLifetime public async Task InitializeAsync() { + var toolPath = await LambdaToolsHelper.InstallLambdaTools(); + var testAppPath = LambdaToolsHelper.GetTempTestAppDirectory( "../../../../../../..", "Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/CustomRuntimeFunctionTest"); - var toolPath = await LambdaToolsHelper.InstallLambdaTools(); _tempPaths.AddRange([testAppPath, toolPath] ); await LambdaToolsHelper.LambdaPackage(toolPath, "net8.0", testAppPath); TestAppPaths[@"CustomRuntimeFunctionTest\bin\Release\net8.0\CustomRuntimeFunctionTest.zip"] = Path.Combine(testAppPath, @"bin\Release\net8.0\CustomRuntimeFunctionTest.zip"); @@ -25,7 +26,6 @@ public async Task InitializeAsync() testAppPath = LambdaToolsHelper.GetTempTestAppDirectory( "../../../../../../..", "Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/CustomRuntimeAspNetCoreMinimalApiTest"); - toolPath = await LambdaToolsHelper.InstallLambdaTools(); _tempPaths.AddRange([testAppPath, toolPath] ); await LambdaToolsHelper.LambdaPackage(toolPath, "net8.0", testAppPath); TestAppPaths[@"CustomRuntimeAspNetCoreMinimalApiTest\bin\Release\net8.0\CustomRuntimeAspNetCoreMinimalApiTest.zip"] = Path.Combine(testAppPath, @"bin\Release\net8.0\CustomRuntimeAspNetCoreMinimalApiTest.zip"); @@ -33,19 +33,27 @@ public async Task InitializeAsync() testAppPath = LambdaToolsHelper.GetTempTestAppDirectory( "../../../../../../..", "Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/CustomRuntimeAspNetCoreMinimalApiCustomSerializerTest"); - toolPath = await LambdaToolsHelper.InstallLambdaTools(); _tempPaths.AddRange([testAppPath, toolPath] ); await LambdaToolsHelper.LambdaPackage(toolPath, "net8.0", testAppPath); TestAppPaths[@"CustomRuntimeAspNetCoreMinimalApiCustomSerializerTest\bin\Release\net8.0\CustomRuntimeAspNetCoreMinimalApiCustomSerializerTest.zip"] = Path.Combine(testAppPath, @"bin\Release\net8.0\CustomRuntimeAspNetCoreMinimalApiCustomSerializerTest.zip"); + + testAppPath = LambdaToolsHelper.GetTempTestAppDirectory( + "../../../../../../..", + "Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/ResponseStreamingFunctionHandlers"); + _tempPaths.AddRange([testAppPath, toolPath]); + await LambdaToolsHelper.LambdaPackage(toolPath, "net10.0", testAppPath); + TestAppPaths[@"ResponseStreamingFunctionHandlers\bin\Release\net10.0\ResponseStreamingFunctionHandlers.zip"] = Path.Combine(testAppPath, "bin", "Release", "net10.0", "ResponseStreamingFunctionHandlers.zip"); } public Task DisposeAsync() { +#if !DEBUG foreach (var tempPath in _tempPaths) { LambdaToolsHelper.CleanUp(tempPath); } +#endif return Task.CompletedTask; } diff --git a/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.IntegrationTests/ResponseStreamingTests.cs b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.IntegrationTests/ResponseStreamingTests.cs new file mode 100644 index 000000000..006df6d15 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.IntegrationTests/ResponseStreamingTests.cs @@ -0,0 +1,133 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Amazon.IdentityManagement; +using Amazon.Lambda.Model; +using Amazon.Runtime.EventStreams; +using Amazon.S3; +using Xunit; + +namespace Amazon.Lambda.RuntimeSupport.IntegrationTests +{ + [Collection("Integration Tests")] + public class ResponseStreamingTests : BaseCustomRuntimeTest + { + private readonly static string s_functionName = "IntegTestResponseStreamingFunctionHandlers" + DateTime.Now.Ticks; + + private readonly ResponseStreamingTestsFixture _streamFixture; + + public ResponseStreamingTests(IntegrationTestFixture fixture, ResponseStreamingTestsFixture streamFixture) + : base(fixture, s_functionName, "ResponseStreamingFunctionHandlers.zip", @"ResponseStreamingFunctionHandlers\bin\Release\net10.0\ResponseStreamingFunctionHandlers.zip", "ResponseStreamingFunctionHandlers") + { + _streamFixture = streamFixture; + } + + [Fact] + public async Task SimpleFunctionHandler() + { + await _streamFixture.EnsureResourcesDeployedAsync(this); + + var evnts = await InvokeFunctionAsync(nameof(SimpleFunctionHandler)); + Assert.True(evnts.Any()); + + var content = GetCombinedStreamContent(evnts); + Assert.Equal("Hello, World!", content); + } + + [Fact] + public async Task StreamContentHandler() + { + await _streamFixture.EnsureResourcesDeployedAsync(this); + + var evnts = await InvokeFunctionAsync(nameof(StreamContentHandler)); + Assert.True(evnts.Length > 5); + + var content = GetCombinedStreamContent(evnts); + Assert.Contains("Line 9999", content); + Assert.EndsWith("Finish stream content\n", content); + } + + [Fact] + public async Task UnhandledExceptionHandler() + { + await _streamFixture.EnsureResourcesDeployedAsync(this); + + var evnts = await InvokeFunctionAsync(nameof(UnhandledExceptionHandler)); + Assert.True(evnts.Any()); + + var completeEvent = evnts.Last() as InvokeWithResponseStreamCompleteEvent; + Assert.Equal("InvalidOperationException", completeEvent.ErrorCode); + Assert.Contains("This is an unhandled exception", completeEvent.ErrorDetails); + Assert.Contains("stackTrace", completeEvent.ErrorDetails); + } + + private async Task InvokeFunctionAsync(string handlerScenario) + { + using var client = new AmazonLambdaClient(TestRegion); + + var request = new InvokeWithResponseStreamRequest + { + FunctionName = base.FunctionName, + Payload = new MemoryStream(System.Text.Encoding.UTF8.GetBytes($"\"{handlerScenario}\"")), + InvocationType = ResponseStreamingInvocationType.RequestResponse + }; + + var response = await client.InvokeWithResponseStreamAsync(request); + var evnts = response.EventStream.AsEnumerable().ToArray(); + return evnts; + } + + private string GetCombinedStreamContent(IEventStreamEvent[] events) + { + var sb = new StringBuilder(); + foreach (var evnt in events) + { + if (evnt is InvokeResponseStreamUpdate chunk) + { + var text = System.Text.Encoding.UTF8.GetString(chunk.Payload.ToArray()); + sb.Append(text); + } + } + return sb.ToString(); + } + } + + public class ResponseStreamingTestsFixture : IAsyncLifetime + { + private readonly AmazonLambdaClient _lambdaClient = new AmazonLambdaClient(BaseCustomRuntimeTest.TestRegion); + private readonly AmazonS3Client _s3Client = new AmazonS3Client(BaseCustomRuntimeTest.TestRegion); + private readonly AmazonIdentityManagementServiceClient _iamClient = new AmazonIdentityManagementServiceClient(BaseCustomRuntimeTest.TestRegion); + bool _resourcesCreated; + bool _roleAlreadyExisted; + + ResponseStreamingTests _tests; + + public async Task EnsureResourcesDeployedAsync(ResponseStreamingTests tests) + { + if (_resourcesCreated) + return; + + _tests = tests; + _roleAlreadyExisted = await _tests.PrepareTestResources(_s3Client, _lambdaClient, _iamClient); + + _resourcesCreated = true; + } + + public async Task DisposeAsync() + { + await _tests.CleanUpTestResources(_s3Client, _lambdaClient, _iamClient, _roleAlreadyExisted); + + _lambdaClient.Dispose(); + _s3Client.Dispose(); + _iamClient.Dispose(); + } + + public Task InitializeAsync() => Task.CompletedTask; + } +} diff --git a/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/HandlerTests.cs b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/HandlerTests.cs index 80f9d13d0..e71acddcd 100644 --- a/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/HandlerTests.cs +++ b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/HandlerTests.cs @@ -31,7 +31,7 @@ namespace Amazon.Lambda.RuntimeSupport.UnitTests { - [Collection("Bootstrap")] + [Collection("ResponseStreamFactory")] public class HandlerTests { private const string AggregateExceptionTestMarker = "AggregateExceptionTesting"; @@ -250,7 +250,7 @@ private async Task TestHandlerFailAsync(string handler, string expect var userCodeLoader = new UserCodeLoader(new SystemEnvironmentVariables(), handler, _internalLogger); var initializer = new UserCodeInitializer(userCodeLoader, _internalLogger); var handlerWrapper = HandlerWrapper.GetHandlerWrapper(userCodeLoader.Invoke); - var bootstrap = new LambdaBootstrap(handlerWrapper, initializer.InitializeAsync) + var bootstrap = new LambdaBootstrap(handlerWrapper.Handler, initializer.InitializeAsync, null, _environmentVariables) { Client = testRuntimeApiClient }; @@ -388,7 +388,9 @@ private async Task ExecHandlerAsync(string handler, string dataIn var userCodeLoader = new UserCodeLoader(new SystemEnvironmentVariables(), handler, _internalLogger); var handlerWrapper = HandlerWrapper.GetHandlerWrapper(userCodeLoader.Invoke); var initializer = new UserCodeInitializer(userCodeLoader, _internalLogger); - var bootstrap = new LambdaBootstrap(handlerWrapper, initializer.InitializeAsync) + // Pass null initializer to bootstrap so RunAsync won't re-invoke Init(), + // which would re-register AssemblyLoad event handlers and re-construct the invoke delegate. + var bootstrap = new LambdaBootstrap(handlerWrapper.Handler, null, null, _environmentVariables) { Client = testRuntimeApiClient }; @@ -403,7 +405,13 @@ private async Task ExecHandlerAsync(string handler, string dataIn Assert.DoesNotContain($"^^[{assertLoggedByInitialize}]^^", actionWriter.ToString()); } - await bootstrap.InitializeAsync(); + await initializer.InitializeAsync(); + + // Re-set logging actions after initialization in case Init's AssemblyLoad event + // handler overwrote them when loading Amazon.Lambda.Core as a handler dependency. + UserCodeLoader.SetCustomerLoggerLogAction(assembly, actionWriter.ToLoggingAction(), _internalLogger); + UserCodeLoader.SetCustomerLoggerLogAction(assembly, actionWriter.ToLoggingWithLevelAction(), _internalLogger); + UserCodeLoader.SetCustomerLoggerLogAction(assembly, actionWriter.ToLoggingWithLevelAndExceptionAction(), _internalLogger); if (assertLoggedByInitialize != null) { diff --git a/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/LambdaBootstrapTests.cs b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/LambdaBootstrapTests.cs index e1636ff16..76e924ac0 100644 --- a/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/LambdaBootstrapTests.cs +++ b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/LambdaBootstrapTests.cs @@ -14,12 +14,14 @@ */ using System; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Net.Http; using System.Text; using System.Threading.Tasks; using Xunit; +using Amazon.Lambda.RuntimeSupport.Client.ResponseStreaming; using Amazon.Lambda.RuntimeSupport.Bootstrap; using static Amazon.Lambda.RuntimeSupport.Bootstrap.Constants; @@ -29,6 +31,7 @@ namespace Amazon.Lambda.RuntimeSupport.UnitTests /// Tests to test LambdaBootstrap when it's constructed using its actual constructor. /// Tests of the static GetLambdaBootstrap methods can be found in LambdaBootstrapWrapperTests. /// + [Collection("ResponseStreamFactory")] public class LambdaBootstrapTests { readonly TestHandler _testFunction; @@ -165,7 +168,7 @@ public async Task TraceIdEnvironmentVariableIsSet() [Fact] public async Task HandlerThrowsException() { - using (var bootstrap = new LambdaBootstrap(_testFunction.BaseHandlerThrowsAsync, null)) + using (var bootstrap = new LambdaBootstrap(_testFunction.BaseHandlerThrowsAsync, null, null, _environmentVariables)) { bootstrap.Client = _testRuntimeApiClient; Assert.Null(_environmentVariables.GetEnvironmentVariable(LambdaEnvironment.EnvVarTraceId)); @@ -183,7 +186,7 @@ public async Task HandlerInputAndOutputWork() { const string testInput = "a MiXeD cAsE sTrInG"; - using (var bootstrap = new LambdaBootstrap(_testFunction.BaseHandlerToUpperAsync, null)) + using (var bootstrap = new LambdaBootstrap(_testFunction.BaseHandlerToUpperAsync, null, null, _environmentVariables)) { _testRuntimeApiClient.FunctionInput = Encoding.UTF8.GetBytes(testInput); bootstrap.Client = _testRuntimeApiClient; @@ -201,7 +204,7 @@ public async Task HandlerInputAndOutputWork() [Fact] public async Task HandlerReturnsNull() { - using (var bootstrap = new LambdaBootstrap(_testFunction.BaseHandlerReturnsNullAsync, null)) + using (var bootstrap = new LambdaBootstrap(_testFunction.BaseHandlerReturnsNullAsync, null, null, _environmentVariables)) { _testRuntimeApiClient.FunctionInput = new byte[0]; bootstrap.Client = _testRuntimeApiClient; @@ -283,5 +286,159 @@ public void IsCallPreJitTest() environmentVariables.SetEnvironmentVariable(ENVIRONMENT_VARIABLE_AWS_LAMBDA_INITIALIZATION_TYPE, AWS_LAMBDA_INITIALIZATION_TYPE_PC); Assert.True(UserCodeInit.IsCallPreJit(environmentVariables)); } + + // --- Streaming Integration Tests --- + + private TestStreamingRuntimeApiClient CreateStreamingClient() + { + var envVars = new TestEnvironmentVariables(); + var headers = new Dictionary> + { + { RuntimeApiHeaders.HeaderAwsRequestId, new List { "streaming-request-id" } }, + { RuntimeApiHeaders.HeaderInvokedFunctionArn, new List { "invoked_function_arn" } }, + { RuntimeApiHeaders.HeaderAwsTenantId, new List { "tenant_id" } } + }; + return new TestStreamingRuntimeApiClient(envVars, headers); + } + + /// + /// Property 2: CreateStream Enables Streaming Mode + /// When a handler calls ResponseStreamFactory.CreateStream(), the response is transmitted + /// using streaming mode. LambdaBootstrap awaits the send task. + /// **Validates: Requirements 1.4, 6.1, 6.2, 6.3, 6.4** + /// + [Fact] + public async Task StreamingMode_HandlerCallsCreateStream_SendTaskAwaited() + { + var streamingClient = CreateStreamingClient(); + + LambdaBootstrapHandler handler = async (invocation) => + { + var stream = ResponseStreamFactory.CreateStream(Array.Empty()); + await stream.WriteAsync(Encoding.UTF8.GetBytes("hello")); + return new InvocationResponse(Stream.Null, false); + }; + + using (var bootstrap = new LambdaBootstrap(handler, null)) + { + bootstrap.Client = streamingClient; + await bootstrap.InvokeOnceAsync(); + } + + Assert.True(streamingClient.StartStreamingResponseAsyncCalled); + Assert.False(streamingClient.SendResponseAsyncCalled); + } + + /// + /// Property 3: Default Mode Is Buffered + /// When a handler does not call ResponseStreamFactory.CreateStream(), the response + /// is transmitted using buffered mode via SendResponseAsync. + /// **Validates: Requirements 1.5, 7.2** + /// + [Fact] + public async Task BufferedMode_HandlerDoesNotCallCreateStream_UsesSendResponse() + { + var streamingClient = CreateStreamingClient(); + + LambdaBootstrapHandler handler = async (invocation) => + { + var outputStream = new MemoryStream(Encoding.UTF8.GetBytes("buffered response")); + return new InvocationResponse(outputStream); + }; + + using (var bootstrap = new LambdaBootstrap(handler, null)) + { + bootstrap.Client = streamingClient; + await bootstrap.InvokeOnceAsync(); + } + + Assert.False(streamingClient.StartStreamingResponseAsyncCalled); + Assert.True(streamingClient.SendResponseAsyncCalled); + } + + /// + /// Property 14: Exception After Writes Uses Trailers + /// When a handler throws an exception after writing data to an IResponseStream, + /// the error is reported via trailers (ReportErrorAsync) rather than standard error reporting. + /// **Validates: Requirements 5.6, 5.7** + /// + [Fact] + public async Task MidstreamError_ExceptionAfterWrites_ReportsViaTrailers() + { + var streamingClient = CreateStreamingClient(); + + LambdaBootstrapHandler handler = async (invocation) => + { + var stream = ResponseStreamFactory.CreateStream(Array.Empty()); + await stream.WriteAsync(Encoding.UTF8.GetBytes("partial data")); + throw new InvalidOperationException("midstream failure"); + }; + + using (var bootstrap = new LambdaBootstrap(handler, null)) + { + bootstrap.Client = streamingClient; + await bootstrap.InvokeOnceAsync(); + } + + // Error should be reported via trailers on the stream, not via standard error reporting + Assert.True(streamingClient.StartStreamingResponseAsyncCalled); + Assert.NotNull(streamingClient.LastStreamingResponseStream); + Assert.True(streamingClient.LastStreamingResponseStream.HasError); + Assert.False(streamingClient.ReportInvocationErrorAsyncExceptionCalled); + } + + /// + /// Property 15: Exception Before CreateStream Uses Standard Error + /// When a handler throws an exception before calling ResponseStreamFactory.CreateStream(), + /// the error is reported using the standard Lambda error reporting mechanism. + /// **Validates: Requirements 5.7, 7.1** + /// + [Fact] + public async Task PreStreamError_ExceptionBeforeCreateStream_UsesStandardErrorReporting() + { + var streamingClient = CreateStreamingClient(); + + LambdaBootstrapHandler handler = async (invocation) => + { + await Task.Yield(); + throw new InvalidOperationException("pre-stream failure"); + }; + + using (var bootstrap = new LambdaBootstrap(handler, null)) + { + bootstrap.Client = streamingClient; + await bootstrap.InvokeOnceAsync(); + } + + Assert.False(streamingClient.StartStreamingResponseAsyncCalled); + Assert.True(streamingClient.ReportInvocationErrorAsyncExceptionCalled); + } + + /// + /// State Isolation: ResponseStreamFactory state is cleared after each invocation. + /// **Validates: Requirements 6.5, 8.9** + /// + [Fact] + public async Task Cleanup_ResponseStreamFactoryStateCleared_AfterInvocation() + { + var streamingClient = CreateStreamingClient(); + + LambdaBootstrapHandler handler = async (invocation) => + { + var stream = ResponseStreamFactory.CreateStream(Array.Empty()); + await stream.WriteAsync(Encoding.UTF8.GetBytes("data")); + return new InvocationResponse(Stream.Null, false); + }; + + using (var bootstrap = new LambdaBootstrap(handler, null)) + { + bootstrap.Client = streamingClient; + await bootstrap.InvokeOnceAsync(); + } + + // After invocation, factory state should be cleaned up + Assert.Null(ResponseStreamFactory.GetStreamIfCreated(false)); + Assert.Null(ResponseStreamFactory.GetSendTask(false)); + } } } diff --git a/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/LambdaResponseStreamingCoreTests.cs b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/LambdaResponseStreamingCoreTests.cs new file mode 100644 index 000000000..0d5c20c86 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/LambdaResponseStreamingCoreTests.cs @@ -0,0 +1,558 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +#if NET8_0_OR_GREATER +#pragma warning disable CA2252 + +using System; +using System.Collections.Generic; +using System.IO; +using System.Net; +using System.Text; +using System.Text.Json; +using System.Threading; +using System.Threading.Tasks; +using Amazon.Lambda.Core.ResponseStreaming; +using Amazon.Lambda.RuntimeSupport.Client.ResponseStreaming; +using Xunit; + +namespace Amazon.Lambda.RuntimeSupport.UnitTests +{ + // ───────────────────────────────────────────────────────────────────────────── + // HttpResponseStreamPrelude.ToByteArray() tests + // ───────────────────────────────────────────────────────────────────────────── + + public class HttpResponseStreamPreludeTests + { + private static JsonDocument ParsePrelude(HttpResponseStreamPrelude prelude) + => JsonDocument.Parse(prelude.ToByteArray()); + + [Fact] + public void ToByteArray_EmptyPrelude_ProducesEmptyJsonObject() + { + var prelude = new HttpResponseStreamPrelude(); + var doc = ParsePrelude(prelude); + + Assert.Equal(JsonValueKind.Object, doc.RootElement.ValueKind); + // No properties should be present + Assert.False(doc.RootElement.TryGetProperty("statusCode", out _)); + Assert.False(doc.RootElement.TryGetProperty("headers", out _)); + Assert.False(doc.RootElement.TryGetProperty("multiValueHeaders", out _)); + Assert.False(doc.RootElement.TryGetProperty("cookies", out _)); + } + + [Fact] + public void ToByteArray_WithStatusCode_IncludesStatusCode() + { + var prelude = new HttpResponseStreamPrelude { StatusCode = HttpStatusCode.OK }; + var doc = ParsePrelude(prelude); + + Assert.True(doc.RootElement.TryGetProperty("statusCode", out var sc)); + Assert.Equal(200, sc.GetInt32()); + } + + [Fact] + public void ToByteArray_WithHeaders_IncludesHeaders() + { + var prelude = new HttpResponseStreamPrelude + { + Headers = new Dictionary + { + ["Content-Type"] = "application/json", + ["X-Custom"] = "value" + } + }; + var doc = ParsePrelude(prelude); + + Assert.True(doc.RootElement.TryGetProperty("headers", out var headers)); + Assert.Equal("application/json", headers.GetProperty("Content-Type").GetString()); + Assert.Equal("value", headers.GetProperty("X-Custom").GetString()); + } + + [Fact] + public void ToByteArray_WithMultiValueHeaders_IncludesMultiValueHeaders() + { + var prelude = new HttpResponseStreamPrelude + { + MultiValueHeaders = new Dictionary> + { + ["Set-Cookie"] = new List { "a=1", "b=2" } + } + }; + var doc = ParsePrelude(prelude); + + Assert.True(doc.RootElement.TryGetProperty("multiValueHeaders", out var mvh)); + var cookies = mvh.GetProperty("Set-Cookie"); + Assert.Equal(JsonValueKind.Array, cookies.ValueKind); + Assert.Equal(2, cookies.GetArrayLength()); + } + + [Fact] + public void ToByteArray_WithCookies_IncludesCookies() + { + var prelude = new HttpResponseStreamPrelude + { + Cookies = new List { "session=abc", "pref=dark" } + }; + var doc = ParsePrelude(prelude); + + Assert.True(doc.RootElement.TryGetProperty("cookies", out var cookies)); + Assert.Equal(JsonValueKind.Array, cookies.ValueKind); + Assert.Equal(2, cookies.GetArrayLength()); + Assert.Equal("session=abc", cookies[0].GetString()); + } + + [Fact] + public void ToByteArray_AllFieldsPopulated_ProducesCorrectJson() + { + var prelude = new HttpResponseStreamPrelude + { + StatusCode = HttpStatusCode.Created, + Headers = new Dictionary { ["X-Req"] = "1" }, + MultiValueHeaders = new Dictionary> { ["X-Multi"] = new List { "a", "b" } }, + Cookies = new List { "c=1" } + }; + var doc = ParsePrelude(prelude); + + Assert.Equal(201, doc.RootElement.GetProperty("statusCode").GetInt32()); + Assert.Equal("1", doc.RootElement.GetProperty("headers").GetProperty("X-Req").GetString()); + Assert.Equal(2, doc.RootElement.GetProperty("multiValueHeaders").GetProperty("X-Multi").GetArrayLength()); + Assert.Equal("c=1", doc.RootElement.GetProperty("cookies")[0].GetString()); + } + + [Fact] + public void ToByteArray_EmptyCollections_OmitsThoseFields() + { + var prelude = new HttpResponseStreamPrelude + { + StatusCode = HttpStatusCode.OK, + Headers = new Dictionary(), // empty — should be omitted + MultiValueHeaders = new Dictionary>(), // empty + Cookies = new List() // empty + }; + var doc = ParsePrelude(prelude); + + Assert.True(doc.RootElement.TryGetProperty("statusCode", out _)); + Assert.False(doc.RootElement.TryGetProperty("headers", out _)); + Assert.False(doc.RootElement.TryGetProperty("multiValueHeaders", out _)); + Assert.False(doc.RootElement.TryGetProperty("cookies", out _)); + } + + [Fact] + public void ToByteArray_ProducesValidUtf8() + { + var prelude = new HttpResponseStreamPrelude + { + StatusCode = HttpStatusCode.OK, + Headers = new Dictionary { ["Content-Type"] = "text/plain; charset=utf-8" } + }; + var bytes = prelude.ToByteArray(); + + // Should not throw + var text = Encoding.UTF8.GetString(bytes); + Assert.NotEmpty(text); + } + } + + // ───────────────────────────────────────────────────────────────────────────── + // LambdaResponseStream (Stream subclass) tests + // ───────────────────────────────────────────────────────────────────────────── + + public class LambdaResponseStreamTests + { + /// + /// Creates a LambdaResponseStream backed by a real ResponseStream wired to a MemoryStream. + /// + private static async Task<(LambdaResponseStream lambdaStream, MemoryStream httpOutput)> CreateWiredLambdaStream() + { + var inner = new ResponseStream(Array.Empty()); + var output = new MemoryStream(); + await inner.SetHttpOutputStreamAsync(output); + + var implStream = new ResponseStreamLambdaCoreInitializerIsolated.ImplLambdaResponseStream(inner); + var lambdaStream = new LambdaResponseStream(implStream); + return (lambdaStream, output); + } + + [Fact] + public void LambdaResponseStream_IsStreamSubclass() + { + var inner = new ResponseStream(Array.Empty()); + var impl = new ResponseStreamLambdaCoreInitializerIsolated.ImplLambdaResponseStream(inner); + var stream = new LambdaResponseStream(impl); + + Assert.IsAssignableFrom(stream); + } + + [Fact] + public void CanWrite_IsTrue() + { + var inner = new ResponseStream(Array.Empty()); + var impl = new ResponseStreamLambdaCoreInitializerIsolated.ImplLambdaResponseStream(inner); + var stream = new LambdaResponseStream(impl); + + Assert.True(stream.CanWrite); + } + + [Fact] + public void CanRead_IsFalse() + { + var inner = new ResponseStream(Array.Empty()); + var impl = new ResponseStreamLambdaCoreInitializerIsolated.ImplLambdaResponseStream(inner); + var stream = new LambdaResponseStream(impl); + + Assert.False(stream.CanRead); + } + + [Fact] + public void CanSeek_IsFalse() + { + var inner = new ResponseStream(Array.Empty()); + var impl = new ResponseStreamLambdaCoreInitializerIsolated.ImplLambdaResponseStream(inner); + var stream = new LambdaResponseStream(impl); + + Assert.False(stream.CanSeek); + } + + [Fact] + public void Read_ThrowsNotImplementedException() + { + var inner = new ResponseStream(Array.Empty()); + var impl = new ResponseStreamLambdaCoreInitializerIsolated.ImplLambdaResponseStream(inner); + var stream = new LambdaResponseStream(impl); + + Assert.Throws(() => stream.Read(new byte[1], 0, 1)); + } + + [Fact] + public void ReadAsync_ThrowsNotImplementedException() + { + var inner = new ResponseStream(Array.Empty()); + var impl = new ResponseStreamLambdaCoreInitializerIsolated.ImplLambdaResponseStream(inner); + var stream = new LambdaResponseStream(impl); + + // ReadAsync throws synchronously (not async) — capture the thrown task + var ex = Assert.Throws( + () => { var _ = stream.ReadAsync(new byte[1], 0, 1, CancellationToken.None); }); + Assert.NotNull(ex); + } + + [Fact] + public void Seek_ThrowsNotImplementedException() + { + var inner = new ResponseStream(Array.Empty()); + var impl = new ResponseStreamLambdaCoreInitializerIsolated.ImplLambdaResponseStream(inner); + var stream = new LambdaResponseStream(impl); + + Assert.Throws(() => stream.Seek(0, SeekOrigin.Begin)); + } + + [Fact] + public void Position_Get_ThrowsNotSupportedException() + { + var inner = new ResponseStream(Array.Empty()); + var impl = new ResponseStreamLambdaCoreInitializerIsolated.ImplLambdaResponseStream(inner); + var stream = new LambdaResponseStream(impl); + + Assert.Throws(() => _ = stream.Position); + } + + [Fact] + public void Position_Set_ThrowsNotSupportedException() + { + var inner = new ResponseStream(Array.Empty()); + var impl = new ResponseStreamLambdaCoreInitializerIsolated.ImplLambdaResponseStream(inner); + var stream = new LambdaResponseStream(impl); + + Assert.Throws(() => stream.Position = 0); + } + + [Fact] + public void SetLength_ThrowsNotSupportedException() + { + var inner = new ResponseStream(Array.Empty()); + var impl = new ResponseStreamLambdaCoreInitializerIsolated.ImplLambdaResponseStream(inner); + var stream = new LambdaResponseStream(impl); + + Assert.Throws(() => stream.SetLength(100)); + } + + [Fact] + public async Task WriteAsync_WritesRawBytesToHttpStream() + { + var (stream, output) = await CreateWiredLambdaStream(); + var data = Encoding.UTF8.GetBytes("hello streaming"); + + await stream.WriteAsync(data, 0, data.Length); + + Assert.Equal(data, output.ToArray()); + } + + [Fact] + public async Task Write_SyncOverload_WritesRawBytes() + { + var (stream, output) = await CreateWiredLambdaStream(); + var data = new byte[] { 1, 2, 3 }; + + stream.Write(data, 0, data.Length); + + Assert.Equal(data, output.ToArray()); + } + + [Fact] + public async Task Length_ReflectsBytesWritten() + { + var (stream, _) = await CreateWiredLambdaStream(); + var data = new byte[42]; + + await stream.WriteAsync(data, 0, data.Length); + + Assert.Equal(42, stream.Length); + Assert.Equal(42, stream.BytesWritten); + } + + [Fact] + public async Task Flush_IsNoOp() + { + var (stream, _) = await CreateWiredLambdaStream(); + // Should not throw + stream.Flush(); + } + + [Fact] + public async Task WriteAsync_ByteArrayOverload_WritesFullArray() + { + var (stream, output) = await CreateWiredLambdaStream(); + var data = new byte[] { 0xDE, 0xAD, 0xBE, 0xEF }; + + await stream.WriteAsync(data); + + Assert.Equal(data, output.ToArray()); + } + } + + // ───────────────────────────────────────────────────────────────────────────── + // ImplLambdaResponseStream (bridge class) tests + // ───────────────────────────────────────────────────────────────────────────── + + public class ImplLambdaResponseStreamTests + { + [Fact] + public async Task WriteAsync_DelegatesToInnerResponseStream() + { + var inner = new ResponseStream(Array.Empty()); + var output = new MemoryStream(); + await inner.SetHttpOutputStreamAsync(output); + + var impl = new ResponseStreamLambdaCoreInitializerIsolated.ImplLambdaResponseStream(inner); + var data = new byte[] { 1, 2, 3 }; + + await impl.WriteAsync(data, 0, data.Length); + + Assert.Equal(data, output.ToArray()); + } + + [Fact] + public async Task BytesWritten_ReflectsInnerStreamBytesWritten() + { + var inner = new ResponseStream(Array.Empty()); + var output = new MemoryStream(); + await inner.SetHttpOutputStreamAsync(output); + + var impl = new ResponseStreamLambdaCoreInitializerIsolated.ImplLambdaResponseStream(inner); + await impl.WriteAsync(new byte[7], 0, 7); + + Assert.Equal(7, impl.BytesWritten); + } + + [Fact] + public void HasError_InitiallyFalse() + { + var inner = new ResponseStream(Array.Empty()); + var impl = new ResponseStreamLambdaCoreInitializerIsolated.ImplLambdaResponseStream(inner); + + Assert.False(impl.HasError); + } + + [Fact] + public void HasError_TrueAfterReportError() + { + var inner = new ResponseStream(Array.Empty()); + inner.ReportError(new Exception("test")); + + var impl = new ResponseStreamLambdaCoreInitializerIsolated.ImplLambdaResponseStream(inner); + + Assert.True(impl.HasError); + } + + [Fact] + public void Dispose_DisposesInnerStream() + { + var inner = new ResponseStream(Array.Empty()); + var impl = new ResponseStreamLambdaCoreInitializerIsolated.ImplLambdaResponseStream(inner); + + // Should not throw + impl.Dispose(); + } + } + + // ───────────────────────────────────────────────────────────────────────────── + // LambdaResponseStreamFactory tests + // ───────────────────────────────────────────────────────────────────────────── + + [Collection("ResponseStreamFactory")] + public class LambdaResponseStreamFactoryTests : IDisposable + { + + public LambdaResponseStreamFactoryTests() + { + // Wire up the factory via the initializer (same as production bootstrap does) + ResponseStreamLambdaCoreInitializerIsolated.InitializeCore(); + } + + public void Dispose() + { + ResponseStreamFactory.CleanupInvocation(isMultiConcurrency: false); + } + + private void InitializeInvocation(string requestId = "test-req") + { + var envVars = new TestEnvironmentVariables(); + var client = new NoOpStreamingRuntimeApiClient(envVars); + ResponseStreamFactory.InitializeInvocation(requestId, false, client, CancellationToken.None); + } + + /// + /// Minimal RuntimeApiClient that accepts StartStreamingResponseAsync without real HTTP. + /// + private class NoOpStreamingRuntimeApiClient : RuntimeApiClient + { + public NoOpStreamingRuntimeApiClient(IEnvironmentVariables envVars) + : base(envVars, new TestHelpers.NoOpInternalRuntimeApiClient()) { } + + internal override async Task StartStreamingResponseAsync( + string awsRequestId, ResponseStream responseStream, CancellationToken cancellationToken = default) + { + // Provide the HTTP output stream so writes don't block + await responseStream.SetHttpOutputStreamAsync(new MemoryStream(), cancellationToken); + await responseStream.WaitForCompletionAsync(cancellationToken); + return new NoOpDisposable(); + } + } + + [Fact] + public void CreateStream_ReturnsLambdaResponseStream() + { + InitializeInvocation(); + + var stream = LambdaResponseStreamFactory.CreateStream(); + + Assert.NotNull(stream); + Assert.IsType(stream); + } + + [Fact] + public void CreateStream_ReturnsStreamSubclass() + { + InitializeInvocation(); + + var stream = LambdaResponseStreamFactory.CreateStream(); + + Assert.IsAssignableFrom(stream); + } + + [Fact] + public void CreateStream_ReturnedStream_IsWritable() + { + InitializeInvocation(); + + var stream = LambdaResponseStreamFactory.CreateStream(); + + Assert.True(stream.CanWrite); + } + + [Fact] + public void CreateStream_ReturnedStream_IsNotSeekable() + { + InitializeInvocation(); + + var stream = LambdaResponseStreamFactory.CreateStream(); + + Assert.False(stream.CanSeek); + } + + [Fact] + public void CreateStream_ReturnedStream_IsNotReadable() + { + InitializeInvocation(); + + var stream = LambdaResponseStreamFactory.CreateStream(); + + Assert.False(stream.CanRead); + } + + [Fact] + public void CreateHttpStream_WithPrelude_ReturnsLambdaResponseStream() + { + InitializeInvocation(); + + var prelude = new HttpResponseStreamPrelude { StatusCode = HttpStatusCode.OK }; + var stream = LambdaResponseStreamFactory.CreateHttpStream(prelude); + + Assert.NotNull(stream); + Assert.IsType(stream); + } + + [Fact] + public void CreateHttpStream_PassesSerializedPreludeToFactory() + { + // Capture the prelude bytes passed to the inner factory + byte[] capturedPrelude = null; + LambdaResponseStreamFactory.SetLambdaResponseStream(prelude => + { + capturedPrelude = prelude; + // Return a minimal stub that satisfies the interface + return new StubLambdaResponseStream(); + }); + + var httpPrelude = new HttpResponseStreamPrelude + { + StatusCode = HttpStatusCode.Created, + Headers = new Dictionary { ["X-Test"] = "1" } + }; + LambdaResponseStreamFactory.CreateHttpStream(httpPrelude); + + Assert.NotNull(capturedPrelude); + Assert.True(capturedPrelude.Length > 0); + + // Verify the bytes are valid JSON containing the status code + var doc = JsonDocument.Parse(capturedPrelude); + Assert.Equal(201, doc.RootElement.GetProperty("statusCode").GetInt32()); + } + + [Fact] + public void CreateStream_PassesEmptyPreludeToFactory() + { + byte[] capturedPrelude = null; + LambdaResponseStreamFactory.SetLambdaResponseStream(prelude => + { + capturedPrelude = prelude; + return new StubLambdaResponseStream(); + }); + + LambdaResponseStreamFactory.CreateStream(); + + Assert.NotNull(capturedPrelude); + Assert.Empty(capturedPrelude); + } + + private class StubLambdaResponseStream : ILambdaResponseStream + { + public long BytesWritten => 0; + public bool HasError => false; + public void Dispose() { } + public Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken = default) + => Task.CompletedTask; + } + } +} +#endif diff --git a/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/RawStreamingHttpClientTests.cs b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/RawStreamingHttpClientTests.cs new file mode 100644 index 000000000..57b94ce22 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/RawStreamingHttpClientTests.cs @@ -0,0 +1,495 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +#if NET8_0_OR_GREATER + +using System; +using System.IO; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Amazon.Lambda.RuntimeSupport.Client.ResponseStreaming; +using Xunit; + +namespace Amazon.Lambda.RuntimeSupport.UnitTests +{ + // ───────────────────────────────────────────────────────────────────────────── + // RawStreamingHttpClient tests + // ───────────────────────────────────────────────────────────────────────────── + + public class RawStreamingHttpClientTests + { + // --- Constructor / host parsing --- + + [Fact] + public void Constructor_HostAndPort_ParsedCorrectly() + { + using var client = new RawStreamingHttpClient("localhost:9001"); + // No exception means parsing succeeded. Fields are private but + // we verify indirectly via Dispose not throwing. + } + + [Fact] + public void Constructor_HighPort_ParsedCorrectly() + { + using var client = new RawStreamingHttpClient("127.0.0.1:65535"); + } + + // --- Dispose --- + + [Fact] + public void Dispose_CalledTwice_DoesNotThrow() + { + var client = new RawStreamingHttpClient("localhost:9001"); + client.Dispose(); + client.Dispose(); + } + + [Fact] + public void Dispose_WithoutConnect_DoesNotThrow() + { + var client = new RawStreamingHttpClient("localhost:9001"); + client.Dispose(); + } + } + + // ───────────────────────────────────────────────────────────────────────────── + // WriteTerminatorWithTrailersAsync tests + // ───────────────────────────────────────────────────────────────────────────── + + public class WriteTerminatorWithTrailersAsyncTests + { + private static (RawStreamingHttpClient client, MemoryStream output) CreateClientWithMemoryStream() + { + var client = new RawStreamingHttpClient("localhost:9001"); + var output = new MemoryStream(); + client._networkStream = output; + return (client, output); + } + + [Fact] + public async Task WriteTerminator_StartsWithZeroChunk() + { + var (client, output) = CreateClientWithMemoryStream(); + + await client.WriteTerminatorWithTrailersAsync( + new Exception("test"), CancellationToken.None); + + var written = Encoding.UTF8.GetString(output.ToArray()); + Assert.StartsWith("0\r\n", written); + } + + [Fact] + public async Task WriteTerminator_ContainsErrorTypeTrailer() + { + var (client, output) = CreateClientWithMemoryStream(); + + await client.WriteTerminatorWithTrailersAsync( + new InvalidOperationException("bad op"), CancellationToken.None); + + var written = Encoding.UTF8.GetString(output.ToArray()); + Assert.Contains($"{StreamingConstants.ErrorTypeTrailer}: InvalidOperationException\r\n", written); + } + + [Fact] + public async Task WriteTerminator_ContainsErrorBodyTrailerHeader() + { + var (client, output) = CreateClientWithMemoryStream(); + + await client.WriteTerminatorWithTrailersAsync( + new Exception("some error"), CancellationToken.None); + + var written = Encoding.UTF8.GetString(output.ToArray()); + Assert.Contains($"{StreamingConstants.ErrorBodyTrailer}: ", written); + } + + [Fact] + public async Task WriteTerminator_ErrorBodyIsBase64Encoded() + { + var (client, output) = CreateClientWithMemoryStream(); + const string errorMessage = "something broke"; + + await client.WriteTerminatorWithTrailersAsync( + new Exception(errorMessage), CancellationToken.None); + + var written = Encoding.UTF8.GetString(output.ToArray()); + + // Extract the Base64 value from the error body trailer + var prefix = $"{StreamingConstants.ErrorBodyTrailer}: "; + var start = written.IndexOf(prefix, StringComparison.Ordinal) + prefix.Length; + var end = written.IndexOf("\r\n", start, StringComparison.Ordinal); + var base64Value = written.Substring(start, end - start); + + // Should be valid Base64 + var decoded = Encoding.UTF8.GetString(Convert.FromBase64String(base64Value)); + Assert.Contains(errorMessage, decoded); + } + + [Fact] + public async Task WriteTerminator_ErrorBodyBase64ContainsNoNewlines() + { + var (client, output) = CreateClientWithMemoryStream(); + + // Use an exception with a stack trace that would produce multi-line JSON + Exception caughtException; + try { throw new InvalidOperationException("multi\nline\nerror"); } + catch (Exception ex) { caughtException = ex; } + + await client.WriteTerminatorWithTrailersAsync( + caughtException, CancellationToken.None); + + var written = Encoding.UTF8.GetString(output.ToArray()); + + // Extract just the error body trailer line + var prefix = $"{StreamingConstants.ErrorBodyTrailer}: "; + var start = written.IndexOf(prefix, StringComparison.Ordinal) + prefix.Length; + var end = written.IndexOf("\r\n", start, StringComparison.Ordinal); + var base64Value = written.Substring(start, end - start); + + // The Base64 value itself must not contain any newlines + Assert.DoesNotContain("\n", base64Value); + Assert.DoesNotContain("\r", base64Value); + } + + [Fact] + public async Task WriteTerminator_EndsWithEmptyLine() + { + var (client, output) = CreateClientWithMemoryStream(); + + await client.WriteTerminatorWithTrailersAsync( + new Exception("test"), CancellationToken.None); + + var written = Encoding.UTF8.GetString(output.ToArray()); + // Must end with \r\n\r\n — the last trailer line's \r\n plus the empty terminator line + Assert.EndsWith("\r\n\r\n", written); + } + + [Fact] + public async Task WriteTerminator_CorrectWireFormat() + { + var (client, output) = CreateClientWithMemoryStream(); + + await client.WriteTerminatorWithTrailersAsync( + new ArgumentException("bad arg"), CancellationToken.None); + + var written = Encoding.UTF8.GetString(output.ToArray()); + var lines = written.Split("\r\n"); + + // Line 0: "0" (zero-length chunk) + Assert.Equal("0", lines[0]); + // Line 1: error type trailer + Assert.StartsWith($"{StreamingConstants.ErrorTypeTrailer}: ", lines[1]); + // Line 2: error body trailer (Base64) + Assert.StartsWith($"{StreamingConstants.ErrorBodyTrailer}: ", lines[2]); + // Line 3: empty (end of trailers) + Assert.Equal("", lines[3]); + } + } + + // ───────────────────────────────────────────────────────────────────────────── + // ReadAndDiscardResponseAsync tests + // ───────────────────────────────────────────────────────────────────────────── + + public class ReadAndDiscardResponseAsyncTests + { + private static (RawStreamingHttpClient client, MemoryStream input) CreateClientWithResponse(string httpResponse) + { + var client = new RawStreamingHttpClient("localhost:9001"); + var input = new MemoryStream(Encoding.ASCII.GetBytes(httpResponse)); + client._networkStream = input; + return (client, input); + } + + [Fact] + public async Task ReadAndDiscard_HeadersOnly_CompletesSuccessfully() + { + var (client, _) = CreateClientWithResponse( + "HTTP/1.1 202 Accepted\r\nContent-Length: 0\r\n\r\n"); + + await client.ReadAndDiscardResponseAsync(CancellationToken.None); + // Should complete without error + } + + [Fact] + public async Task ReadAndDiscard_WithBody_ReadsFullBody() + { + var body = "OK"; + var (client, _) = CreateClientWithResponse( + $"HTTP/1.1 200 OK\r\nContent-Length: {body.Length}\r\n\r\n{body}"); + + await client.ReadAndDiscardResponseAsync(CancellationToken.None); + } + + [Fact] + public async Task ReadAndDiscard_NoContentLength_CompletesAfterHeaders() + { + var (client, _) = CreateClientWithResponse( + "HTTP/1.1 202 Accepted\r\n\r\n"); + + await client.ReadAndDiscardResponseAsync(CancellationToken.None); + } + + [Fact] + public async Task ReadAndDiscard_EmptyStream_CompletesSuccessfully() + { + var client = new RawStreamingHttpClient("localhost:9001"); + client._networkStream = new MemoryStream(Array.Empty()); + + await client.ReadAndDiscardResponseAsync(CancellationToken.None); + } + + [Fact] + public async Task ReadAndDiscard_PartialBody_WaitsForFullBody() + { + // Content-Length says 10 but we provide all 10 bytes + var body = "0123456789"; + var (client, _) = CreateClientWithResponse( + $"HTTP/1.1 200 OK\r\nContent-Length: 10\r\n\r\n{body}"); + + await client.ReadAndDiscardResponseAsync(CancellationToken.None); + } + + [Fact] + public async Task ReadAndDiscard_CancellationToken_Respected() + { + // Use a stream that blocks on read to test cancellation + var cts = new CancellationTokenSource(); + cts.Cancel(); + + var client = new RawStreamingHttpClient("localhost:9001"); + client._networkStream = new MemoryStream(Encoding.ASCII.GetBytes( + "HTTP/1.1 200 OK\r\nContent-Length: 100\r\n\r\n")); + + // Should not throw — ReadAndDiscardResponseAsync catches exceptions + await client.ReadAndDiscardResponseAsync(cts.Token); + } + } + + // ───────────────────────────────────────────────────────────────────────────── + // ChunkedStreamWriter tests + // ───────────────────────────────────────────────────────────────────────────── + + public class ChunkedStreamWriterTests + { + [Fact] + public void CanWrite_IsTrue() + { + using var inner = new MemoryStream(); + using var writer = new ChunkedStreamWriter(inner); + Assert.True(writer.CanWrite); + } + + [Fact] + public void CanRead_IsFalse() + { + using var inner = new MemoryStream(); + using var writer = new ChunkedStreamWriter(inner); + Assert.False(writer.CanRead); + } + + [Fact] + public void CanSeek_IsFalse() + { + using var inner = new MemoryStream(); + using var writer = new ChunkedStreamWriter(inner); + Assert.False(writer.CanSeek); + } + + [Fact] + public void Constructor_NullStream_ThrowsArgumentNullException() + { + Assert.Throws(() => new ChunkedStreamWriter(null)); + } + + [Fact] + public void Length_ThrowsNotSupportedException() + { + using var inner = new MemoryStream(); + using var writer = new ChunkedStreamWriter(inner); + Assert.Throws(() => writer.Length); + } + + [Fact] + public void Position_Get_ThrowsNotSupportedException() + { + using var inner = new MemoryStream(); + using var writer = new ChunkedStreamWriter(inner); + Assert.Throws(() => writer.Position); + } + + [Fact] + public void Position_Set_ThrowsNotSupportedException() + { + using var inner = new MemoryStream(); + using var writer = new ChunkedStreamWriter(inner); + Assert.Throws(() => writer.Position = 0); + } + + [Fact] + public void Read_ThrowsNotSupportedException() + { + using var inner = new MemoryStream(); + using var writer = new ChunkedStreamWriter(inner); + Assert.Throws(() => writer.Read(new byte[1], 0, 1)); + } + + [Fact] + public void Seek_ThrowsNotSupportedException() + { + using var inner = new MemoryStream(); + using var writer = new ChunkedStreamWriter(inner); + Assert.Throws(() => writer.Seek(0, SeekOrigin.Begin)); + } + + [Fact] + public void SetLength_ThrowsNotSupportedException() + { + using var inner = new MemoryStream(); + using var writer = new ChunkedStreamWriter(inner); + Assert.Throws(() => writer.SetLength(0)); + } + + [Fact] + public async Task WriteAsync_ByteArray_ProducesCorrectChunkFormat() + { + using var inner = new MemoryStream(); + using var writer = new ChunkedStreamWriter(inner); + + var data = Encoding.UTF8.GetBytes("Hello"); + await writer.WriteAsync(data, 0, data.Length); + + var output = Encoding.ASCII.GetString(inner.ToArray()); + // "Hello" is 5 bytes = 0x5 + Assert.Equal("5\r\nHello\r\n", output); + } + + [Fact] + public async Task WriteAsync_ReadOnlyMemory_ProducesCorrectChunkFormat() + { + using var inner = new MemoryStream(); + using var writer = new ChunkedStreamWriter(inner); + + var data = Encoding.UTF8.GetBytes("Hi"); + await writer.WriteAsync(new ReadOnlyMemory(data)); + + var output = Encoding.ASCII.GetString(inner.ToArray()); + Assert.Equal("2\r\nHi\r\n", output); + } + + [Fact] + public async Task WriteAsync_ZeroBytes_WritesNothing() + { + using var inner = new MemoryStream(); + using var writer = new ChunkedStreamWriter(inner); + + await writer.WriteAsync(Array.Empty(), 0, 0); + + Assert.Equal(0, inner.Length); + } + + [Fact] + public async Task WriteAsync_ReadOnlyMemory_ZeroBytes_WritesNothing() + { + using var inner = new MemoryStream(); + using var writer = new ChunkedStreamWriter(inner); + + await writer.WriteAsync(ReadOnlyMemory.Empty); + + Assert.Equal(0, inner.Length); + } + + [Fact] + public async Task WriteAsync_MultipleChunks_EachCorrectlyFormatted() + { + using var inner = new MemoryStream(); + using var writer = new ChunkedStreamWriter(inner); + + await writer.WriteAsync(Encoding.UTF8.GetBytes("AB"), 0, 2); + await writer.WriteAsync(Encoding.UTF8.GetBytes("CDE"), 0, 3); + + var output = Encoding.ASCII.GetString(inner.ToArray()); + Assert.Equal("2\r\nAB\r\n3\r\nCDE\r\n", output); + } + + [Fact] + public async Task WriteAsync_LargeChunk_HexSizeCorrect() + { + using var inner = new MemoryStream(); + using var writer = new ChunkedStreamWriter(inner); + + var data = new byte[256]; + Array.Fill(data, (byte)'X'); + await writer.WriteAsync(data, 0, data.Length); + + var output = Encoding.ASCII.GetString(inner.ToArray()); + // 256 = 0x100 + Assert.StartsWith("100\r\n", output); + Assert.EndsWith("\r\n", output); + } + + [Fact] + public async Task WriteAsync_WithOffset_WritesCorrectSlice() + { + using var inner = new MemoryStream(); + using var writer = new ChunkedStreamWriter(inner); + + var data = Encoding.UTF8.GetBytes("ABCDE"); + await writer.WriteAsync(data, 1, 3); // "BCD" + + var output = Encoding.ASCII.GetString(inner.ToArray()); + Assert.Equal("3\r\nBCD\r\n", output); + } + + [Fact] + public void Write_Sync_ProducesCorrectChunkFormat() + { + using var inner = new MemoryStream(); + using var writer = new ChunkedStreamWriter(inner); + + var data = Encoding.UTF8.GetBytes("OK"); + writer.Write(data, 0, data.Length); + + var output = Encoding.ASCII.GetString(inner.ToArray()); + Assert.Equal("2\r\nOK\r\n", output); + } + + [Fact] + public async Task FlushAsync_DelegatesToInnerStream() + { + var flushCalled = false; + var inner = new FlushTrackingStream(() => flushCalled = true); + using var writer = new ChunkedStreamWriter(inner); + + await writer.FlushAsync(CancellationToken.None); + + Assert.True(flushCalled); + } + + [Fact] + public void Flush_DelegatesToInnerStream() + { + var flushCalled = false; + var inner = new FlushTrackingStream(() => flushCalled = true); + using var writer = new ChunkedStreamWriter(inner); + + writer.Flush(); + + Assert.True(flushCalled); + } + + /// + /// A minimal writable stream that tracks Flush calls. + /// + private class FlushTrackingStream : MemoryStream + { + private readonly Action _onFlush; + public FlushTrackingStream(Action onFlush) => _onFlush = onFlush; + public override void Flush() { _onFlush(); base.Flush(); } + public override Task FlushAsync(CancellationToken cancellationToken) + { + _onFlush(); + return base.FlushAsync(cancellationToken); + } + } + } +} +#endif diff --git a/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/ResponseStreamFactoryTests.cs b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/ResponseStreamFactoryTests.cs new file mode 100644 index 000000000..cc9a19af2 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/ResponseStreamFactoryTests.cs @@ -0,0 +1,284 @@ +/* + * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +using System; +using System.Threading; +using System.Threading.Tasks; +using Amazon.Lambda.RuntimeSupport.Client.ResponseStreaming; +using Xunit; + +namespace Amazon.Lambda.RuntimeSupport.UnitTests +{ + [Collection("ResponseStreamFactory")] + public class ResponseStreamFactoryTests : IDisposable + { + private const long MaxResponseSize = 20 * 1024 * 1024; + + public void Dispose() + { + // Clean up both modes to avoid test pollution + ResponseStreamFactory.CleanupInvocation(isMultiConcurrency: false); + ResponseStreamFactory.CleanupInvocation(isMultiConcurrency: true); + } + + /// + /// A minimal RuntimeApiClient subclass for testing that overrides StartStreamingResponseAsync + /// to avoid real HTTP calls while tracking invocations. + /// + private class MockStreamingRuntimeApiClient : RuntimeApiClient + { + public bool StartStreamingCalled { get; private set; } + public string LastAwsRequestId { get; private set; } + public ResponseStream LastResponseStream { get; private set; } + public TaskCompletionSource SendTaskCompletion { get; } = new TaskCompletionSource(); + + public MockStreamingRuntimeApiClient() + : base(new TestEnvironmentVariables(), new TestHelpers.NoOpInternalRuntimeApiClient()) + { + } + + internal override async Task StartStreamingResponseAsync( + string awsRequestId, ResponseStream responseStream, CancellationToken cancellationToken = default) + { + StartStreamingCalled = true; + LastAwsRequestId = awsRequestId; + LastResponseStream = responseStream; + await SendTaskCompletion.Task; + return new NoOpDisposable(); + } + } + + private void InitializeWithMock(string requestId, bool isMultiConcurrency, MockStreamingRuntimeApiClient mockClient) + { + ResponseStreamFactory.InitializeInvocation( + requestId, isMultiConcurrency, + mockClient, CancellationToken.None); + } + + // --- Property 1: CreateStream Returns Valid Stream --- + + /// + /// Property 1: CreateStream Returns Valid Stream - on-demand mode. + /// Validates: Requirements 1.3, 2.2, 2.3 + /// + [Fact] + public void CreateStream_OnDemandMode_ReturnsValidStream() + { + var mock = new MockStreamingRuntimeApiClient(); + InitializeWithMock("req-1", isMultiConcurrency: false, mock); + + var stream = ResponseStreamFactory.CreateStream(Array.Empty()); + + Assert.NotNull(stream); + Assert.IsAssignableFrom(stream); + } + + /// + /// Property 1: CreateStream Returns Valid Stream - multi-concurrency mode. + /// Validates: Requirements 1.3, 2.2, 2.3 + /// + [Fact] + public void CreateStream_MultiConcurrencyMode_ReturnsValidStream() + { + var mock = new MockStreamingRuntimeApiClient(); + InitializeWithMock("req-2", isMultiConcurrency: true, mock); + + var stream = ResponseStreamFactory.CreateStream(Array.Empty()); + + Assert.NotNull(stream); + Assert.IsAssignableFrom(stream); + } + + // --- Property 4: Single Stream Per Invocation --- + + /// + /// Property 4: Single Stream Per Invocation - calling CreateStream twice throws. + /// Validates: Requirements 2.5, 2.6 + /// + [Fact] + public void CreateStream_CalledTwice_ThrowsInvalidOperationException() + { + var mock = new MockStreamingRuntimeApiClient(); + InitializeWithMock("req-3", isMultiConcurrency: false, mock); + ResponseStreamFactory.CreateStream(Array.Empty()); + + Assert.Throws(() => ResponseStreamFactory.CreateStream(Array.Empty())); + } + + [Fact] + public void CreateStream_OutsideInvocationContext_ThrowsInvalidOperationException() + { + // No InitializeInvocation called + Assert.Throws(() => ResponseStreamFactory.CreateStream(Array.Empty())); + } + + // --- CreateStream starts HTTP POST --- + + /// + /// Validates that CreateStream calls StartStreamingResponseAsync on the RuntimeApiClient. + /// Validates: Requirements 1.3, 1.4, 2.2, 2.3, 2.4 + /// + [Fact] + public void CreateStream_CallsStartStreamingResponseAsync() + { + var mock = new MockStreamingRuntimeApiClient(); + InitializeWithMock("req-start", isMultiConcurrency: false, mock); + + ResponseStreamFactory.CreateStream(Array.Empty()); + + Assert.True(mock.StartStreamingCalled); + Assert.Equal("req-start", mock.LastAwsRequestId); + Assert.NotNull(mock.LastResponseStream); + } + + // --- GetSendTask --- + + /// + /// Validates that GetSendTask returns the task from the HTTP POST. + /// Validates: Requirements 5.1, 7.3 + /// + [Fact] + public void GetSendTask_AfterCreateStream_ReturnsNonNullTask() + { + var mock = new MockStreamingRuntimeApiClient(); + InitializeWithMock("req-send", isMultiConcurrency: false, mock); + + ResponseStreamFactory.CreateStream(Array.Empty()); + + var sendTask = ResponseStreamFactory.GetSendTask(isMultiConcurrency: false); + Assert.NotNull(sendTask); + } + + [Fact] + public void GetSendTask_BeforeCreateStream_ReturnsNull() + { + var mock = new MockStreamingRuntimeApiClient(); + InitializeWithMock("req-nosend", isMultiConcurrency: false, mock); + + var sendTask = ResponseStreamFactory.GetSendTask(isMultiConcurrency: false); + Assert.Null(sendTask); + } + + [Fact] + public void GetSendTask_NoContext_ReturnsNull() + { + Assert.Null(ResponseStreamFactory.GetSendTask(isMultiConcurrency: false)); + } + + // --- Internal methods --- + + [Fact] + public void InitializeInvocation_OnDemand_SetsUpContext() + { + var mock = new MockStreamingRuntimeApiClient(); + InitializeWithMock("req-4", isMultiConcurrency: false, mock); + + Assert.Null(ResponseStreamFactory.GetStreamIfCreated(isMultiConcurrency: false)); + + var stream = ResponseStreamFactory.CreateStream(Array.Empty()); + Assert.NotNull(stream); + } + + [Fact] + public void InitializeInvocation_MultiConcurrency_SetsUpContext() + { + var mock = new MockStreamingRuntimeApiClient(); + InitializeWithMock("req-5", isMultiConcurrency: true, mock); + + Assert.Null(ResponseStreamFactory.GetStreamIfCreated(isMultiConcurrency: true)); + + var stream = ResponseStreamFactory.CreateStream(Array.Empty()); + Assert.NotNull(stream); + } + + [Fact] + public void GetStreamIfCreated_AfterCreateStream_ReturnsStream() + { + var mock = new MockStreamingRuntimeApiClient(); + InitializeWithMock("req-6", isMultiConcurrency: false, mock); + ResponseStreamFactory.CreateStream(Array.Empty()); + + var retrieved = ResponseStreamFactory.GetStreamIfCreated(isMultiConcurrency: false); + Assert.NotNull(retrieved); + } + + [Fact] + public void GetStreamIfCreated_NoContext_ReturnsNull() + { + Assert.Null(ResponseStreamFactory.GetStreamIfCreated(isMultiConcurrency: false)); + } + + [Fact] + public void CleanupInvocation_ClearsState() + { + var mock = new MockStreamingRuntimeApiClient(); + InitializeWithMock("req-7", isMultiConcurrency: false, mock); + ResponseStreamFactory.CreateStream(Array.Empty()); + + ResponseStreamFactory.CleanupInvocation(isMultiConcurrency: false); + + Assert.Null(ResponseStreamFactory.GetStreamIfCreated(isMultiConcurrency: false)); + Assert.Throws(() => ResponseStreamFactory.CreateStream(Array.Empty())); + } + + // --- Property 16: State Isolation Between Invocations --- + + /// + /// Property 16: State Isolation Between Invocations - state from one invocation doesn't leak to the next. + /// Validates: Requirements 6.5, 8.9 + /// + [Fact] + public void StateIsolation_SequentialInvocations_NoLeakage() + { + var mock = new MockStreamingRuntimeApiClient(); + + // First invocation - streaming + InitializeWithMock("req-8a", isMultiConcurrency: false, mock); + var stream1 = ResponseStreamFactory.CreateStream(Array.Empty()); + Assert.NotNull(stream1); + ResponseStreamFactory.CleanupInvocation(isMultiConcurrency: false); + + // Second invocation - should start fresh + InitializeWithMock("req-8b", isMultiConcurrency: false, mock); + Assert.Null(ResponseStreamFactory.GetStreamIfCreated(isMultiConcurrency: false)); + + var stream2 = ResponseStreamFactory.CreateStream(Array.Empty()); + Assert.NotNull(stream2); + ResponseStreamFactory.CleanupInvocation(isMultiConcurrency: false); + } + + /// + /// Property 16: State Isolation - multi-concurrency mode uses AsyncLocal. + /// Validates: Requirements 2.9, 2.10 + /// + [Fact] + public async Task StateIsolation_MultiConcurrency_UsesAsyncLocal() + { + var mock = new MockStreamingRuntimeApiClient(); + InitializeWithMock("req-9", isMultiConcurrency: true, mock); + var stream = ResponseStreamFactory.CreateStream(Array.Empty()); + Assert.NotNull(stream); + + bool childSawNull = false; + await Task.Run(() => + { + ResponseStreamFactory.CleanupInvocation(isMultiConcurrency: true); + childSawNull = ResponseStreamFactory.GetStreamIfCreated(isMultiConcurrency: true) == null; + }); + + Assert.True(childSawNull); + } + } +} diff --git a/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/ResponseStreamTests.cs b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/ResponseStreamTests.cs new file mode 100644 index 000000000..cd2c00fd2 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/ResponseStreamTests.cs @@ -0,0 +1,447 @@ +/* + * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +using System; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Amazon.Lambda.RuntimeSupport.Client.ResponseStreaming; +using Xunit; + +namespace Amazon.Lambda.RuntimeSupport.UnitTests +{ + public class ResponseStreamTests + { + /// + /// Helper: creates a ResponseStream and wires up a MemoryStream as the HTTP output stream. + /// Returns both so tests can inspect what was written. + /// + private static async Task<(ResponseStream stream, MemoryStream httpOutput)> CreateWiredStream() + { + var rs = new ResponseStream(Array.Empty()); + var output = new MemoryStream(); + await rs.SetHttpOutputStreamAsync(output); + return (rs, output); + } + + // ---- Basic state tests ---- + + [Fact] + public void Constructor_InitializesStateCorrectly() + { + var stream = new ResponseStream(Array.Empty()); + + Assert.Equal(0, stream.BytesWritten); + Assert.False(stream.HasError); + Assert.Null(stream.ReportedError); + } + + [Fact] + public async Task WriteAsync_WithOffset_WritesCorrectSlice() + { + var (stream, httpOutput) = await CreateWiredStream(); + var data = new byte[] { 0, 1, 2, 3, 0 }; + + await stream.WriteAsync(data, 1, 3); + + // Raw bytes {1,2,3} written directly — no chunked encoding + var expected = new byte[] { 1, 2, 3 }; + Assert.Equal(expected, httpOutput.ToArray()); + } + + [Fact] + public async Task WriteAsync_MultipleWrites_EachAppearsImmediately() + { + var (stream, httpOutput) = await CreateWiredStream(); + + var data = new byte[] { 0xAA }; + await stream.WriteAsync(data, 0, data.Length); + var afterFirst = httpOutput.ToArray().Length; + Assert.True(afterFirst > 0, "First chunk should be on the HTTP stream immediately after WriteAsync returns"); + + await stream.WriteAsync(new byte[] { 0xBB, 0xCC }, 0, 2); + var afterSecond = httpOutput.ToArray().Length; + Assert.True(afterSecond > afterFirst, "Second chunk should appear on the HTTP stream immediately"); + + Assert.Equal(3, stream.BytesWritten); + } + + [Fact] + public async Task WriteAsync_BlocksUntilSetHttpOutputStream() + { + var rs = new ResponseStream(Array.Empty()); + var httpOutput = new MemoryStream(); + var writeStarted = new ManualResetEventSlim(false); + var writeCompleted = new ManualResetEventSlim(false); + + // Start a write on a background thread — it should block + var writeTask = Task.Run(async () => + { + writeStarted.Set(); + await rs.WriteAsync(new byte[] { 1, 2, 3 }, 0, 3); + writeCompleted.Set(); + }); + + // Wait for the write to start, then verify it hasn't completed + writeStarted.Wait(TimeSpan.FromSeconds(2)); + await Task.Delay(100); // give it a moment + Assert.False(writeCompleted.IsSet, "WriteAsync should block until SetHttpOutputStream is called"); + + // Now provide the HTTP stream — the write should complete + await rs.SetHttpOutputStreamAsync(httpOutput); + await writeTask; + + Assert.True(writeCompleted.IsSet); + Assert.True(httpOutput.ToArray().Length > 0); + } + + [Fact] + public async Task MarkCompleted_ReleasesCompletionSignal() + { + var (stream, _) = await CreateWiredStream(); + + var waitTask = stream.WaitForCompletionAsync(); + Assert.False(waitTask.IsCompleted, "WaitForCompletionAsync should block before MarkCompleted"); + + stream.MarkCompleted(); + + // Should complete within a reasonable time + var completed = await Task.WhenAny(waitTask, Task.Delay(TimeSpan.FromSeconds(2))); + Assert.Same(waitTask, completed); + } + + [Fact] + public async Task ReportErrorAsync_ReleasesCompletionSignal() + { + var (stream, _) = await CreateWiredStream(); + + var waitTask = stream.WaitForCompletionAsync(); + Assert.False(waitTask.IsCompleted, "WaitForCompletionAsync should block before ReportErrorAsync"); + + stream.ReportError(new Exception("test error")); + + var completed = await Task.WhenAny(waitTask, Task.Delay(TimeSpan.FromSeconds(2))); + Assert.Same(waitTask, completed); + Assert.True(stream.HasError); + } + + [Fact] + public async Task WriteAsync_AfterMarkCompleted_StillSucceeds() + { + var (stream, output) = await CreateWiredStream(); + await stream.WriteAsync(new byte[] { 1 }, 0, 1); + stream.MarkCompleted(); + + // Writes after MarkCompleted are allowed — buffered ASP.NET Core responses + // (e.g. Results.Json) may flush pre-start buffer data after the pipeline + // completes and LambdaBootstrap calls MarkCompleted. + await stream.WriteAsync(new byte[] { 2 }, 0, 1); + + Assert.Equal(new byte[] { 1, 2 }, output.ToArray()); + } + + [Fact] + public async Task WriteAsync_AfterReportError_Throws() + { + var (stream, _) = await CreateWiredStream(); + await stream.WriteAsync(new byte[] { 1 }, 0, 1); + stream.ReportError(new Exception("test")); + + await Assert.ThrowsAsync( + () => stream.WriteAsync(new byte[] { 2 }, 0, 1)); + } + + [Fact] + public async Task ReportErrorAsync_SetsErrorState() + { + var stream = new ResponseStream(Array.Empty()); + var exception = new InvalidOperationException("something broke"); + + stream.ReportError(exception); + + Assert.True(stream.HasError); + Assert.Same(exception, stream.ReportedError); + } + + [Fact] + public async Task ReportErrorAsync_AfterCompleted_Throws() + { + var stream = new ResponseStream(Array.Empty()); + stream.MarkCompleted(); + + Assert.Throws( + () => stream.ReportError(new Exception("test"))); + } + + [Fact] + public async Task ReportErrorAsync_CalledTwice_Throws() + { + var stream = new ResponseStream(Array.Empty()); + stream.ReportError(new Exception("first")); + + Assert.Throws( + () => stream.ReportError(new Exception("second"))); + } + + [Fact] + public async Task WriteAsync_NullBuffer_ThrowsArgumentNull() + { + var (stream, _) = await CreateWiredStream(); + + await Assert.ThrowsAsync(() => stream.WriteAsync((byte[])null, 0, 0)); + } + + [Fact] + public async Task WriteAsync_NullBufferWithOffset_ThrowsArgumentNull() + { + var (stream, _) = await CreateWiredStream(); + + await Assert.ThrowsAsync(() => stream.WriteAsync(null, 0, 0)); + } + + [Fact] + public async Task ReportErrorAsync_NullException_ThrowsArgumentNull() + { + var stream = new ResponseStream(Array.Empty()); + + Assert.Throws(() => stream.ReportError(null)); + } + + [Fact] + public async Task Dispose_CalledTwice_DoesNotThrow() + { + var stream = new ResponseStream(Array.Empty()); + stream.Dispose(); + // Second dispose should be a no-op + stream.Dispose(); + } + + // ---- Prelude tests ---- + + [Fact] + public async Task SetHttpOutputStreamAsync_WithPrelude_WritesPreludeBeforeHandlerData() + { + var prelude = new byte[] { 0x01, 0x02, 0x03 }; + var rs = new ResponseStream(prelude); + var output = new MemoryStream(); + + await rs.SetHttpOutputStreamAsync(output); + + // Prelude bytes + 8-byte null delimiter should be written before any handler data + var written = output.ToArray(); + Assert.True(written.Length >= prelude.Length + 8, "Prelude + delimiter should be written"); + Assert.Equal(prelude, written[..prelude.Length]); + Assert.Equal(new byte[8], written[prelude.Length..(prelude.Length + 8)]); + } + + [Fact] + public async Task SetHttpOutputStreamAsync_WithEmptyPrelude_WritesNoPreludeBytes() + { + var rs = new ResponseStream(Array.Empty()); + var output = new MemoryStream(); + + await rs.SetHttpOutputStreamAsync(output); + + // Empty prelude — nothing written yet (handler hasn't written anything) + Assert.Empty(output.ToArray()); + } + + [Fact] + public async Task SetHttpOutputStreamAsync_WithPrelude_HandlerDataAppendsAfterDelimiter() + { + var prelude = new byte[] { 0xAA, 0xBB }; + var rs = new ResponseStream(prelude); + var output = new MemoryStream(); + + await rs.SetHttpOutputStreamAsync(output); + await rs.WriteAsync(new byte[] { 0xFF }, 0, 1); + + var written = output.ToArray(); + // Layout: [prelude][8 null bytes][handler data] + int expectedMinLength = prelude.Length + 8 + 1; + Assert.Equal(expectedMinLength, written.Length); + Assert.Equal(new byte[] { 0xFF }, written[^1..]); + } + + [Fact] + public async Task SetHttpOutputStreamAsync_NullPrelude_WritesNoPreludeBytes() + { + var rs = new ResponseStream(null); + var output = new MemoryStream(); + + await rs.SetHttpOutputStreamAsync(output); + + Assert.Empty(output.ToArray()); + } + + // ---- Prelude + delimiter single-chunk tests (via ChunkedStreamWriter) ---- + + [Fact] + public async Task SetHttpOutputStreamAsync_WithPrelude_ViaChunkedWriter_ProducesSingleChunk() + { + var preludeJson = Encoding.UTF8.GetBytes("{\"statusCode\":200}"); + var rs = new ResponseStream(preludeJson); + var rawOutput = new MemoryStream(); + var chunkedWriter = new ChunkedStreamWriter(rawOutput); + + await rs.SetHttpOutputStreamAsync(chunkedWriter); + + var wireBytes = Encoding.ASCII.GetString(rawOutput.ToArray()); + + // The prelude (18 bytes) + delimiter (8 bytes) = 26 bytes = 0x1A + // Should be exactly one chunk: "1A\r\n{prelude}{8 null bytes}\r\n" + var expectedDataLength = preludeJson.Length + 8; // 26 + var expectedHex = expectedDataLength.ToString("X"); + Assert.StartsWith($"{expectedHex}\r\n", wireBytes); + + // Verify there is only one chunk header (only one hex size prefix) + var chunkCount = 0; + var remaining = wireBytes; + while (remaining.Length > 0) + { + var crlfIndex = remaining.IndexOf("\r\n", StringComparison.Ordinal); + if (crlfIndex < 0) break; + var sizeStr = remaining.Substring(0, crlfIndex); + if (int.TryParse(sizeStr, System.Globalization.NumberStyles.HexNumber, null, out var chunkSize) && chunkSize >= 0) + { + chunkCount++; + // Skip past: hex\r\n{data}\r\n + remaining = remaining.Substring(crlfIndex + 2 + chunkSize + 2); + } + else + { + break; + } + } + Assert.Equal(1, chunkCount); + } + + [Fact] + public async Task SetHttpOutputStreamAsync_WithPrelude_ViaChunkedWriter_DelimiterImmediatelyFollowsPrelude() + { + var preludeJson = Encoding.UTF8.GetBytes("{\"statusCode\":201}"); + var rs = new ResponseStream(preludeJson); + var rawOutput = new MemoryStream(); + var chunkedWriter = new ChunkedStreamWriter(rawOutput); + + await rs.SetHttpOutputStreamAsync(chunkedWriter); + + // Parse the chunk to get the raw data payload + var wireBytes = rawOutput.ToArray(); + var wireStr = Encoding.ASCII.GetString(wireBytes); + var firstCrlf = wireStr.IndexOf("\r\n", StringComparison.Ordinal); + var dataStart = firstCrlf + 2; + var dataLength = preludeJson.Length + 8; + var chunkData = new byte[dataLength]; + Array.Copy(wireBytes, dataStart, chunkData, 0, dataLength); + + // First part should be the prelude JSON + Assert.Equal(preludeJson, chunkData[..preludeJson.Length]); + // Immediately followed by 8 null bytes (delimiter) + Assert.Equal(new byte[8], chunkData[preludeJson.Length..]); + } + + [Fact] + public async Task SetHttpOutputStreamAsync_WithPrelude_ViaChunkedWriter_HandlerDataInSeparateChunk() + { + var preludeJson = Encoding.UTF8.GetBytes("{\"statusCode\":200}"); + var rs = new ResponseStream(preludeJson); + var rawOutput = new MemoryStream(); + var chunkedWriter = new ChunkedStreamWriter(rawOutput); + + await rs.SetHttpOutputStreamAsync(chunkedWriter); + await rs.WriteAsync(Encoding.UTF8.GetBytes("body data"), 0, 9); + + var wireStr = Encoding.ASCII.GetString(rawOutput.ToArray()); + + // Should have exactly 2 chunks: one for prelude+delimiter, one for body + var chunkCount = 0; + var remaining = wireStr; + while (remaining.Length > 0) + { + var crlfIndex = remaining.IndexOf("\r\n", StringComparison.Ordinal); + if (crlfIndex < 0) break; + var sizeStr = remaining.Substring(0, crlfIndex); + if (int.TryParse(sizeStr, System.Globalization.NumberStyles.HexNumber, null, out var chunkSize) && chunkSize >= 0) + { + chunkCount++; + remaining = remaining.Substring(crlfIndex + 2 + chunkSize + 2); + } + else + { + break; + } + } + Assert.Equal(2, chunkCount); + } + + // ---- MarkCompleted idempotency ---- + + [Fact] + public async Task MarkCompleted_CalledTwice_DoesNotThrowOrDoubleRelease() + { + var (stream, _) = await CreateWiredStream(); + + stream.MarkCompleted(); + // Second call should be a no-op — semaphore should not be double-released + stream.MarkCompleted(); + + // WaitForCompletionAsync should complete exactly once without hanging + var waitTask = stream.WaitForCompletionAsync(); + var completed = await Task.WhenAny(waitTask, Task.Delay(TimeSpan.FromSeconds(2))); + Assert.Same(waitTask, completed); + } + + [Fact] + public async Task ReportError_ThenMarkCompleted_MarkCompletedIsNoOp() + { + var stream = new ResponseStream(Array.Empty()); + stream.ReportError(new Exception("error")); + + // MarkCompleted after ReportError should not throw and not double-release + stream.MarkCompleted(); + + // WaitForCompletionAsync should complete (released by ReportError) + var waitTask = stream.WaitForCompletionAsync(); + var completed = await Task.WhenAny(waitTask, Task.Delay(TimeSpan.FromSeconds(2))); + Assert.Same(waitTask, completed); + } + + // ---- BytesWritten tracking ---- + + [Fact] + public async Task BytesWritten_TracksAcrossMultipleWrites() + { + var (stream, _) = await CreateWiredStream(); + + await stream.WriteAsync(new byte[10], 0, 10); + await stream.WriteAsync(new byte[5], 0, 5); + + Assert.Equal(15, stream.BytesWritten); + } + + [Fact] + public async Task BytesWritten_ReflectsOffsetAndCount() + { + var (stream, _) = await CreateWiredStream(); + + await stream.WriteAsync(new byte[10], 2, 6); // only 6 bytes + + Assert.Equal(6, stream.BytesWritten); + } + } +} diff --git a/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/RuntimeApiClientTests.cs b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/RuntimeApiClientTests.cs new file mode 100644 index 000000000..71102ddf1 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/RuntimeApiClientTests.cs @@ -0,0 +1,211 @@ +/* + * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +using System; +using System.IO; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using Amazon.Lambda.RuntimeSupport.Client.ResponseStreaming; +using Xunit; + +namespace Amazon.Lambda.RuntimeSupport.UnitTests +{ + /// + /// Tests for RuntimeApiClient streaming and buffered behavior. + /// Validates Properties 7, 8, 10, 13, 18. + /// + public class RuntimeApiClientTests + { + private const long MaxResponseSize = 20 * 1024 * 1024; + + /// + /// Mock HttpMessageHandler that captures the request for header inspection. + /// It completes the ResponseStream and returns immediately without reading + /// the content body, avoiding the SerializeToStreamAsync blocking issue. + /// + private class MockHttpMessageHandler : HttpMessageHandler + { + public HttpRequestMessage CapturedRequest { get; private set; } + private readonly ResponseStream _responseStream; + + public MockHttpMessageHandler(ResponseStream responseStream) + { + _responseStream = responseStream; + } + + protected override Task SendAsync( + HttpRequestMessage request, CancellationToken cancellationToken) + { + CapturedRequest = request; + + return Task.FromResult(new HttpResponseMessage(HttpStatusCode.OK)); + } + } + + private static RuntimeApiClient CreateClientWithMockHandler( + ResponseStream stream, out MockHttpMessageHandler handler) + { + handler = new MockHttpMessageHandler(stream); + var httpClient = new HttpClient(handler); + var envVars = new TestEnvironmentVariables(); + envVars.SetEnvironmentVariable("AWS_LAMBDA_RUNTIME_API", "localhost:9001"); + return new RuntimeApiClient(envVars, httpClient); + } + + // --- Property 7: Streaming Response Mode Header --- + // Note: Properties 7, 8, 13 test the HttpClient-based streaming path which is only used on pre-NET8 targets. + // On NET8+, StartStreamingResponseAsync uses RawStreamingHttpClient (raw TCP) which doesn't go through HttpClient. + +#if !NET8_0_OR_GREATER + /// + /// Property 7: Streaming Response Mode Header + /// For any streaming response, the HTTP request should include + /// "Lambda-Runtime-Function-Response-Mode: streaming". + /// **Validates: Requirements 4.1** + /// + [Fact] + public async Task StartStreamingResponseAsync_IncludesStreamingResponseModeHeader() + { + var stream = new ResponseStream(Array.Empty()); + var client = CreateClientWithMockHandler(stream, out var handler); + + await client.StartStreamingResponseAsync("req-1", stream, CancellationToken.None); + + Assert.NotNull(handler.CapturedRequest); + Assert.True(handler.CapturedRequest.Headers.Contains(StreamingConstants.ResponseModeHeader)); + var values = handler.CapturedRequest.Headers.GetValues(StreamingConstants.ResponseModeHeader).ToList(); + Assert.Single(values); + Assert.Equal(StreamingConstants.StreamingResponseMode, values[0]); + } + + // --- Property 8: Chunked Transfer Encoding Header --- + + /// + /// Property 8: Chunked Transfer Encoding Header + /// For any streaming response, the HTTP request should include + /// "Transfer-Encoding: chunked". + /// **Validates: Requirements 4.2** + /// + [Fact] + public async Task StartStreamingResponseAsync_IncludesChunkedTransferEncodingHeader() + { + var stream = new ResponseStream(Array.Empty()); + var client = CreateClientWithMockHandler(stream, out var handler); + + await client.StartStreamingResponseAsync("req-2", stream, CancellationToken.None); + + Assert.NotNull(handler.CapturedRequest); + Assert.True(handler.CapturedRequest.Headers.TransferEncodingChunked); + } + + // --- Property 13: Trailer Declaration Header --- + + /// + /// Property 13: Trailer Declaration Header + /// For any streaming response, the HTTP request should include a "Trailer" header + /// declaring the error trailer headers upfront (since we cannot know at request + /// start whether an error will occur). + /// **Validates: Requirements 5.4** + /// + [Fact] + public async Task StartStreamingResponseAsync_DeclaresTrailerHeaderUpfront() + { + var stream = new ResponseStream(Array.Empty()); + var client = CreateClientWithMockHandler(stream, out var handler); + + await client.StartStreamingResponseAsync("req-3", stream, CancellationToken.None); + + Assert.NotNull(handler.CapturedRequest); + Assert.True(handler.CapturedRequest.Headers.Contains("Trailer")); + var trailerValue = string.Join(", ", handler.CapturedRequest.Headers.GetValues("Trailer")); + Assert.Contains(StreamingConstants.ErrorTypeTrailer, trailerValue); + Assert.Contains(StreamingConstants.ErrorBodyTrailer, trailerValue); + } +#endif + + // --- Property 10: Buffered Responses Exclude Streaming Headers --- + + /// + /// Mock HttpMessageHandler that captures the request for buffered response header inspection. + /// Returns an Accepted (202) response since that's what the InternalRuntimeApiClient expects. + /// + private class BufferedMockHttpMessageHandler : HttpMessageHandler + { + public HttpRequestMessage CapturedRequest { get; private set; } + + protected override Task SendAsync( + HttpRequestMessage request, CancellationToken cancellationToken) + { + CapturedRequest = request; + return Task.FromResult(new HttpResponseMessage(HttpStatusCode.Accepted)); + } + } + + /// + /// Property 10: Buffered Responses Exclude Streaming Headers + /// For any buffered response (where CreateStream was not called), the HTTP request + /// should not include "Lambda-Runtime-Function-Response-Mode" or + /// "Transfer-Encoding: chunked" or "Trailer" headers. + /// **Validates: Requirements 4.6** + /// + [Fact] + public async Task SendResponseAsync_BufferedResponse_ExcludesStreamingHeaders() + { + var bufferedHandler = new BufferedMockHttpMessageHandler(); + var httpClient = new HttpClient(bufferedHandler); + var envVars = new TestEnvironmentVariables(); + envVars.SetEnvironmentVariable("AWS_LAMBDA_RUNTIME_API", "localhost:9001"); + var client = new RuntimeApiClient(envVars, httpClient); + + var outputStream = new MemoryStream(new byte[] { 1, 2, 3 }); + await client.SendResponseAsync("req-buffered", outputStream, CancellationToken.None); + + Assert.NotNull(bufferedHandler.CapturedRequest); + // Buffered responses must not include streaming-specific headers + Assert.False(bufferedHandler.CapturedRequest.Headers.Contains(StreamingConstants.ResponseModeHeader), + "Buffered response should not include Lambda-Runtime-Function-Response-Mode header"); + Assert.NotEqual(true, bufferedHandler.CapturedRequest.Headers.TransferEncodingChunked); + Assert.False(bufferedHandler.CapturedRequest.Headers.Contains("Trailer"), + "Buffered response should not include Trailer header"); + } + + // --- Argument validation --- + +#if NET8_0_OR_GREATER + [Fact] + public async Task StartStreamingResponseAsync_NullRequestId_ThrowsArgumentNullException() + { + var stream = new ResponseStream(Array.Empty()); + var client = CreateClientWithMockHandler(stream, out _); + + await Assert.ThrowsAsync( + () => client.StartStreamingResponseAsync(null, stream, CancellationToken.None)); + } + + [Fact] + public async Task StartStreamingResponseAsync_NullResponseStream_ThrowsArgumentNullException() + { + var stream = new ResponseStream(Array.Empty()); + var client = CreateClientWithMockHandler(stream, out _); + + await Assert.ThrowsAsync( + () => client.StartStreamingResponseAsync("req-5", null, CancellationToken.None)); + } +#endif + } +} diff --git a/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/StreamingE2EWithMoq.cs b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/StreamingE2EWithMoq.cs new file mode 100644 index 000000000..f46c76f13 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/StreamingE2EWithMoq.cs @@ -0,0 +1,545 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.IO; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Amazon.Lambda.RuntimeSupport.Client.ResponseStreaming; +using Amazon.Lambda.RuntimeSupport.UnitTests.TestHelpers; +using Xunit; + +namespace Amazon.Lambda.RuntimeSupport.UnitTests +{ + [CollectionDefinition("ResponseStreamFactory")] + public class ResponseStreamFactoryCollection { } + + /// + /// End-to-end integration tests for the true-streaming architecture. + /// These tests exercise the full pipeline: LambdaBootstrap → ResponseStreamFactory → + /// ResponseStream → captured HTTP output stream. + /// + [Collection("ResponseStreamFactory")] + public class StreamingE2EWithMoq : IDisposable + { + public void Dispose() + { + ResponseStreamFactory.CleanupInvocation(isMultiConcurrency: false); + ResponseStreamFactory.CleanupInvocation(isMultiConcurrency: true); + } + + // ─── Helpers ──────────────────────────────────────────────────────────────── + + private static Dictionary> MakeHeaders(string requestId = "test-request-id") + => new Dictionary> + { + { RuntimeApiHeaders.HeaderAwsRequestId, new List { requestId } }, + { RuntimeApiHeaders.HeaderInvokedFunctionArn, new List { "arn:aws:lambda:us-east-1:123456789012:function:test" } }, + { RuntimeApiHeaders.HeaderAwsTenantId, new List { "tenant-id" } }, + { RuntimeApiHeaders.HeaderTraceId, new List { "trace-id" } }, + { RuntimeApiHeaders.HeaderDeadlineMs, new List { "9999999999999" } }, + }; + + /// + /// A capturing RuntimeApiClient that records the raw bytes written to the HTTP output stream + /// by SerializeToStreamAsync. + /// + private class CapturingStreamingRuntimeApiClient : RuntimeApiClient, IRuntimeApiClient + { + private readonly IEnvironmentVariables _envVars; + private readonly Dictionary> _headers; + + public bool StartStreamingCalled { get; private set; } + public bool SendResponseCalled { get; private set; } + public bool ReportInvocationErrorCalled { get; private set; } + public byte[] CapturedHttpBytes { get; private set; } + public ResponseStream LastResponseStream { get; private set; } + public Stream LastBufferedOutputStream { get; private set; } + + public new Amazon.Lambda.RuntimeSupport.Helpers.IConsoleLoggerWriter ConsoleLogger { get; } = new Helpers.LogLevelLoggerWriter(new SystemEnvironmentVariables()); + + public CapturingStreamingRuntimeApiClient( + IEnvironmentVariables envVars, + Dictionary> headers) + : base(envVars, new NoOpInternalRuntimeApiClient()) + { + _envVars = envVars; + _headers = headers; + } + + public new async Task GetNextInvocationAsync(CancellationToken cancellationToken = default) + { + _headers[RuntimeApiHeaders.HeaderTraceId] = new List { Guid.NewGuid().ToString() }; + var inputStream = new MemoryStream(new byte[0]); + return new InvocationRequest + { + InputStream = inputStream, + LambdaContext = new LambdaContext( + new RuntimeApiHeaders(_headers), + new LambdaEnvironment(_envVars), + new TestDateTimeHelper(), + new Helpers.SimpleLoggerWriter(_envVars)) + }; + } + + internal override async Task StartStreamingResponseAsync( + string awsRequestId, ResponseStream responseStream, CancellationToken cancellationToken = default) + { + StartStreamingCalled = true; + LastResponseStream = responseStream; + + // Use a real MemoryStream as the HTTP output stream so we capture actual bytes + var captureStream = new MemoryStream(); + await responseStream.SetHttpOutputStreamAsync(captureStream, cancellationToken); + + // Wait for the handler to finish writing (mirrors real RawStreamingHttpClient behavior) + await responseStream.WaitForCompletionAsync(cancellationToken); + CapturedHttpBytes = captureStream.ToArray(); + return new NoOpDisposable(); + } + + public new async Task SendResponseAsync(string awsRequestId, Stream outputStream, CancellationToken cancellationToken = default) + { + SendResponseCalled = true; + if (outputStream != null) + { + var ms = new MemoryStream(); + await outputStream.CopyToAsync(ms); + ms.Position = 0; + LastBufferedOutputStream = ms; + } + } + + public new Task ReportInvocationErrorAsync(string awsRequestId, Exception exception, CancellationToken cancellationToken = default) + { + ReportInvocationErrorCalled = true; + return Task.CompletedTask; + } + + public new Task ReportInitializationErrorAsync(Exception exception, string errorType = null, CancellationToken cancellationToken = default) + => Task.CompletedTask; + + public new Task ReportInitializationErrorAsync(string errorType, CancellationToken cancellationToken = default) + => Task.CompletedTask; + +#if NET8_0_OR_GREATER + public new Task RestoreNextInvocationAsync(CancellationToken cancellationToken = default) => Task.CompletedTask; + public new Task ReportRestoreErrorAsync(Exception exception, string errorType = null, CancellationToken cancellationToken = default) => Task.CompletedTask; +#endif + } + + private static CapturingStreamingRuntimeApiClient CreateClient(string requestId = "test-request-id") + => new CapturingStreamingRuntimeApiClient(new TestEnvironmentVariables(), MakeHeaders(requestId)); + + /// + /// End-to-end: all data is transmitted correctly (content round-trip). + /// Requirements: 3.2, 4.3, 10.1 + /// + [Fact] + public async Task Streaming_AllDataTransmitted_ContentRoundTrip() + { + var client = CreateClient(); + var payload = Encoding.UTF8.GetBytes("integration test payload"); + + LambdaBootstrapHandler handler = async (invocation) => + { + var stream = ResponseStreamFactory.CreateStream(Array.Empty()); + await stream.WriteAsync(payload); + return new InvocationResponse(Stream.Null, false); + }; + + using var bootstrap = new LambdaBootstrap(handler, null); + bootstrap.Client = client; + await bootstrap.InvokeOnceAsync(); + + var output = client.CapturedHttpBytes; + Assert.NotNull(output); + + var outputStr = Encoding.UTF8.GetString(output); + Assert.Contains("integration test payload", outputStr); + } + + /// + /// End-to-end: stream is finalized (final chunk written, BytesWritten matches). + /// Requirements: 3.2, 4.3, 10.1 + /// + [Fact] + public async Task Streaming_StreamFinalized_BytesWrittenMatchesPayload() + { + var client = CreateClient(); + var data = Encoding.UTF8.GetBytes("finalization check"); + + LambdaBootstrapHandler handler = async (invocation) => + { + var stream = ResponseStreamFactory.CreateStream(Array.Empty()); + await stream.WriteAsync(data); + return new InvocationResponse(Stream.Null, false); + }; + + using var bootstrap = new LambdaBootstrap(handler, null); + bootstrap.Client = client; + await bootstrap.InvokeOnceAsync(); + + Assert.NotNull(client.LastResponseStream); + Assert.Equal(data.Length, client.LastResponseStream.BytesWritten); + } + + // ─── 10.2 End-to-end buffered response ────────────────────────────────────── + + /// + /// End-to-end: handler does NOT call CreateStream — response goes via buffered path. + /// Verifies SendResponseAsync is called and streaming headers are absent. + /// Requirements: 1.5, 4.6, 9.4 + /// + [Fact] + public async Task Buffered_HandlerDoesNotCallCreateStream_UsesSendResponsePath() + { + var client = CreateClient(); + var responseBody = Encoding.UTF8.GetBytes("buffered response body"); + + LambdaBootstrapHandler handler = async (invocation) => + { + await Task.Yield(); + return new InvocationResponse(new MemoryStream(responseBody)); + }; + + using var bootstrap = new LambdaBootstrap(handler, null); + bootstrap.Client = client; + await bootstrap.InvokeOnceAsync(); + + Assert.False(client.StartStreamingCalled, "StartStreamingResponseAsync should NOT be called for buffered mode"); + Assert.True(client.SendResponseCalled, "SendResponseAsync should be called for buffered mode"); + Assert.Null(client.CapturedHttpBytes); + } + + /// + /// End-to-end: buffered response body is transmitted correctly. + /// Requirements: 1.5, 4.6, 9.4 + /// + [Fact] + public async Task Buffered_ResponseBodyTransmittedCorrectly() + { + var client = CreateClient(); + var responseBody = Encoding.UTF8.GetBytes("hello buffered world"); + + LambdaBootstrapHandler handler = async (invocation) => + { + await Task.Yield(); + return new InvocationResponse(new MemoryStream(responseBody)); + }; + + using var bootstrap = new LambdaBootstrap(handler, null); + bootstrap.Client = client; + await bootstrap.InvokeOnceAsync(); + + Assert.True(client.SendResponseCalled); + Assert.NotNull(client.LastBufferedOutputStream); + var received = new MemoryStream(); + await client.LastBufferedOutputStream.CopyToAsync(received); + Assert.Equal(responseBody, received.ToArray()); + } + + /// + /// End-to-end: midstream error sets error state on ResponseStream with exception details. + /// In production, RawStreamingHttpClient reads this state and writes trailing headers. + /// Requirements: 5.2, 5.3 + /// + [Fact] + public async Task MidstreamError_SetsErrorStateWithExceptionDetails() + { + var client = CreateClient(); + const string errorMessage = "something went wrong mid-stream"; + + LambdaBootstrapHandler handler = async (invocation) => + { + var stream = ResponseStreamFactory.CreateStream(Array.Empty()); + await stream.WriteAsync(Encoding.UTF8.GetBytes("some data")); + throw new InvalidOperationException(errorMessage); + }; + + using var bootstrap = new LambdaBootstrap(handler, null); + bootstrap.Client = client; + await bootstrap.InvokeOnceAsync(); + + Assert.True(client.StartStreamingCalled); + Assert.NotNull(client.LastResponseStream); + Assert.True(client.LastResponseStream.HasError); + Assert.NotNull(client.LastResponseStream.ReportedError); + Assert.IsType(client.LastResponseStream.ReportedError); + Assert.Equal(errorMessage, client.LastResponseStream.ReportedError.Message); + + // Verify the handler's data was still captured before the error + var output = Encoding.UTF8.GetString(client.CapturedHttpBytes); + Assert.Contains("some data", output); + } + + // ─── 10.4 Multi-concurrency ────────────────────────────────────────────────── + + /// + /// Multi-concurrency: concurrent invocations use AsyncLocal for state isolation. + /// Each invocation independently uses streaming or buffered mode without interference. + /// Requirements: 2.9, 6.5, 8.9 + /// + [Fact] + public async Task MultiConcurrency_ConcurrentInvocations_StateIsolated() + { + const int concurrency = 3; + var results = new ConcurrentDictionary(); + var barrier = new SemaphoreSlim(0, concurrency); + var allStarted = new SemaphoreSlim(0, concurrency); + + // Simulate concurrent invocations using AsyncLocal directly + var tasks = new List(); + for (int i = 0; i < concurrency; i++) + { + var requestId = $"req-{i}"; + var payload = $"payload-{i}"; + tasks.Add(Task.Run(async () => + { + var mockClient = new MockMultiConcurrencyStreamingClient(); + ResponseStreamFactory.InitializeInvocation( + requestId, + isMultiConcurrency: true, + mockClient, + CancellationToken.None); + + var stream = ResponseStreamFactory.CreateStream(Array.Empty()); + allStarted.Release(); + + // Wait until all tasks have started (to ensure true concurrency) + await barrier.WaitAsync(); + + await stream.WriteAsync(Encoding.UTF8.GetBytes(payload)); + stream.MarkCompleted(); + + // Verify this invocation's stream is still accessible + var retrieved = ResponseStreamFactory.GetStreamIfCreated(isMultiConcurrency: true); + results[requestId] = retrieved != null ? payload : "MISSING"; + + ResponseStreamFactory.CleanupInvocation(isMultiConcurrency: true); + })); + } + + // Wait for all tasks to start, then release the barrier + for (int i = 0; i < concurrency; i++) + await allStarted.WaitAsync(); + barrier.Release(concurrency); + + await Task.WhenAll(tasks); + + // Each invocation should have seen its own stream + Assert.Equal(concurrency, results.Count); + for (int i = 0; i < concurrency; i++) + Assert.Equal($"payload-{i}", results[$"req-{i}"]); + } + + /// + /// Multi-concurrency: streaming and buffered invocations can run concurrently without interference. + /// Requirements: 2.9, 6.5, 8.9 + /// + [Fact] + public async Task MultiConcurrency_StreamingAndBufferedMixedConcurrently_NoInterference() + { + var streamingResults = new ConcurrentBag(); + var bufferedResults = new ConcurrentBag(); + var barrier = new SemaphoreSlim(0, 4); + var allStarted = new SemaphoreSlim(0, 4); + + var tasks = new List(); + + // 2 streaming invocations + for (int i = 0; i < 2; i++) + { + var requestId = $"stream-{i}"; + tasks.Add(Task.Run(async () => + { + var mockClient = new MockMultiConcurrencyStreamingClient(); + ResponseStreamFactory.InitializeInvocation( + requestId, + isMultiConcurrency: true, mockClient, CancellationToken.None); + + var stream = ResponseStreamFactory.CreateStream(Array.Empty()); + allStarted.Release(); + await barrier.WaitAsync(); + + await stream.WriteAsync(Encoding.UTF8.GetBytes("streaming data")); + stream.MarkCompleted(); + + var retrieved = ResponseStreamFactory.GetStreamIfCreated(isMultiConcurrency: true); + streamingResults.Add(retrieved != null); + ResponseStreamFactory.CleanupInvocation(isMultiConcurrency: true); + })); + } + + // 2 buffered invocations (no CreateStream) + for (int i = 0; i < 2; i++) + { + var requestId = $"buffered-{i}"; + tasks.Add(Task.Run(async () => + { + var mockClient = new MockMultiConcurrencyStreamingClient(); + ResponseStreamFactory.InitializeInvocation( + requestId, + isMultiConcurrency: true, mockClient, CancellationToken.None); + + allStarted.Release(); + await barrier.WaitAsync(); + + // No CreateStream — buffered mode + var retrieved = ResponseStreamFactory.GetStreamIfCreated(isMultiConcurrency: true); + bufferedResults.Add(retrieved == null); // should be null (no stream created) + ResponseStreamFactory.CleanupInvocation(isMultiConcurrency: true); + })); + } + + for (int i = 0; i < 4; i++) + await allStarted.WaitAsync(); + barrier.Release(4); + + await Task.WhenAll(tasks); + + Assert.Equal(2, streamingResults.Count); + Assert.All(streamingResults, r => Assert.True(r, "Streaming invocation should have a stream")); + + Assert.Equal(2, bufferedResults.Count); + Assert.All(bufferedResults, r => Assert.True(r, "Buffered invocation should have no stream")); + } + + /// + /// Minimal mock RuntimeApiClient for multi-concurrency tests. + /// Accepts StartStreamingResponseAsync calls without real HTTP. + /// + private class MockMultiConcurrencyStreamingClient : RuntimeApiClient + { + public MockMultiConcurrencyStreamingClient() + : base(new TestEnvironmentVariables(), new NoOpInternalRuntimeApiClient()) { } + + internal override async Task StartStreamingResponseAsync( + string awsRequestId, ResponseStream responseStream, CancellationToken cancellationToken = default) + { + // Provide the HTTP output stream so writes don't block + await responseStream.SetHttpOutputStreamAsync(new MemoryStream()); + await responseStream.WaitForCompletionAsync(); + return new NoOpDisposable(); + } + } + + // ─── 10.5 Backward compatibility ──────────────────────────────────────────── + + /// + /// Backward compatibility: existing handler signatures (event + ILambdaContext) work without modification. + /// Requirements: 9.1, 9.2, 9.3 + /// + [Fact] + public async Task BackwardCompat_ExistingHandlerSignature_WorksUnchanged() + { + var client = CreateClient(); + bool handlerCalled = false; + + // Simulate a classic handler that returns a buffered response + LambdaBootstrapHandler handler = async (invocation) => + { + handlerCalled = true; + await Task.Yield(); + return new InvocationResponse(new MemoryStream(Encoding.UTF8.GetBytes("classic response"))); + }; + + using var bootstrap = new LambdaBootstrap(handler, null); + bootstrap.Client = client; + await bootstrap.InvokeOnceAsync(); + + Assert.True(handlerCalled); + Assert.True(client.SendResponseCalled); + Assert.False(client.StartStreamingCalled); + } + + /// + /// Backward compatibility: no regression in buffered response behavior — response body is correct. + /// Requirements: 9.4, 9.5 + /// + [Fact] + public async Task BackwardCompat_BufferedResponse_NoRegression() + { + var client = CreateClient(); + var expected = Encoding.UTF8.GetBytes("no regression here"); + + LambdaBootstrapHandler handler = async (invocation) => + { + await Task.Yield(); + return new InvocationResponse(new MemoryStream(expected)); + }; + + using var bootstrap = new LambdaBootstrap(handler, null); + bootstrap.Client = client; + await bootstrap.InvokeOnceAsync(); + + Assert.True(client.SendResponseCalled); + Assert.NotNull(client.LastBufferedOutputStream); + var received = new MemoryStream(); + await client.LastBufferedOutputStream.CopyToAsync(received); + Assert.Equal(expected, received.ToArray()); + } + + /// + /// Backward compatibility: handler that returns null OutputStream still works. + /// Requirements: 9.4 + /// + [Fact] + public async Task BackwardCompat_NullOutputStream_HandledGracefully() + { + var client = CreateClient(); + + LambdaBootstrapHandler handler = async (invocation) => + { + await Task.Yield(); + return new InvocationResponse(Stream.Null, false); + }; + + using var bootstrap = new LambdaBootstrap(handler, null); + bootstrap.Client = client; + + // Should not throw + await bootstrap.InvokeOnceAsync(); + + Assert.True(client.SendResponseCalled); + } + + /// + /// Backward compatibility: handler that throws before CreateStream uses standard error path. + /// Requirements: 9.5 + /// + [Fact] + public async Task BackwardCompat_HandlerThrows_StandardErrorReportingUsed() + { + var client = CreateClient(); + + LambdaBootstrapHandler handler = async (invocation) => + { + await Task.Yield(); + throw new Exception("classic handler error"); + }; + + using var bootstrap = new LambdaBootstrap(handler, null); + bootstrap.Client = client; + await bootstrap.InvokeOnceAsync(); + + Assert.True(client.ReportInvocationErrorCalled); + Assert.False(client.StartStreamingCalled); + } + } +} diff --git a/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/TestHelpers/NoOpInternalRuntimeApiClient.cs b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/TestHelpers/NoOpInternalRuntimeApiClient.cs new file mode 100644 index 000000000..9fa0434cd --- /dev/null +++ b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/TestHelpers/NoOpInternalRuntimeApiClient.cs @@ -0,0 +1,60 @@ +/* + * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +using System.Collections.Generic; +using System.IO; +using System.Threading; +using System.Threading.Tasks; + +namespace Amazon.Lambda.RuntimeSupport.UnitTests.TestHelpers +{ + /// + /// A no-op implementation of IInternalRuntimeApiClient for unit tests + /// that need to construct a RuntimeApiClient without real HTTP calls. + /// + internal class NoOpInternalRuntimeApiClient : IInternalRuntimeApiClient + { + private static readonly SwaggerResponse EmptyStatusResponse = + new SwaggerResponse(200, new Dictionary>(), new StatusResponse()); + + public Task> ErrorAsync( + string lambda_Runtime_Function_Error_Type, string errorJson, CancellationToken cancellationToken) + => Task.FromResult(EmptyStatusResponse); + + public Task> NextAsync(CancellationToken cancellationToken) + => Task.FromResult(new SwaggerResponse(200, new Dictionary>(), Stream.Null)); + + public Task> ResponseAsync(string awsRequestId, Stream outputStream) + => Task.FromResult(EmptyStatusResponse); + + public Task> ResponseAsync( + string awsRequestId, Stream outputStream, CancellationToken cancellationToken) + => Task.FromResult(EmptyStatusResponse); + + public Task> ErrorWithXRayCauseAsync( + string awsRequestId, string lambda_Runtime_Function_Error_Type, + string errorJson, string xrayCause, CancellationToken cancellationToken) + => Task.FromResult(EmptyStatusResponse); + +#if NET8_0_OR_GREATER + public Task> RestoreNextAsync(CancellationToken cancellationToken) + => Task.FromResult(new SwaggerResponse(200, new Dictionary>(), Stream.Null)); + + public Task> RestoreErrorAsync( + string lambda_Runtime_Function_Error_Type, string errorJson, CancellationToken cancellationToken) + => Task.FromResult(EmptyStatusResponse); +#endif + } +} diff --git a/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/TestHelpers/TestStreamingRuntimeApiClient.cs b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/TestHelpers/TestStreamingRuntimeApiClient.cs new file mode 100644 index 000000000..1cd6fa09e --- /dev/null +++ b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/TestHelpers/TestStreamingRuntimeApiClient.cs @@ -0,0 +1,142 @@ +/* + * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +using Amazon.Lambda.RuntimeSupport.Client.ResponseStreaming; +using Amazon.Lambda.RuntimeSupport.Helpers; +using Amazon.Lambda.RuntimeSupport.UnitTests.TestHelpers; +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace Amazon.Lambda.RuntimeSupport.UnitTests +{ + /// + /// A RuntimeApiClient subclass for testing LambdaBootstrap streaming integration. + /// Extends RuntimeApiClient so the (RuntimeApiClient)Client cast in LambdaBootstrap works. + /// Overrides StartStreamingResponseAsync to avoid real HTTP calls. + /// + internal class TestStreamingRuntimeApiClient : RuntimeApiClient, IRuntimeApiClient + { + private readonly IEnvironmentVariables _environmentVariables; + private readonly Dictionary> _headers; + + public new IConsoleLoggerWriter ConsoleLogger { get; } = new LogLevelLoggerWriter(new SystemEnvironmentVariables()); + + public TestStreamingRuntimeApiClient(IEnvironmentVariables environmentVariables, Dictionary> headers) + : base(environmentVariables, new NoOpInternalRuntimeApiClient()) + { + _environmentVariables = environmentVariables; + _headers = headers; + } + + // Tracking flags + public bool GetNextInvocationAsyncCalled { get; private set; } + public bool ReportInitializationErrorAsyncExceptionCalled { get; private set; } + public bool ReportInvocationErrorAsyncExceptionCalled { get; private set; } + public bool SendResponseAsyncCalled { get; private set; } + public bool StartStreamingResponseAsyncCalled { get; private set; } + + public string LastTraceId { get; private set; } + public byte[] FunctionInput { get; set; } + public Stream LastOutputStream { get; private set; } + public Exception LastRecordedException { get; private set; } + public ResponseStream LastStreamingResponseStream { get; private set; } + + public new async Task GetNextInvocationAsync(CancellationToken cancellationToken = default) + { + GetNextInvocationAsyncCalled = true; + + LastTraceId = Guid.NewGuid().ToString(); + _headers[RuntimeApiHeaders.HeaderTraceId] = new List() { LastTraceId }; + + var inputStream = new MemoryStream(FunctionInput == null ? new byte[0] : FunctionInput); + inputStream.Position = 0; + + return new InvocationRequest() + { + InputStream = inputStream, + LambdaContext = new LambdaContext( + new RuntimeApiHeaders(_headers), + new LambdaEnvironment(_environmentVariables), + new TestDateTimeHelper(), new SimpleLoggerWriter(_environmentVariables)) + }; + } + + public new Task ReportInitializationErrorAsync(Exception exception, String errorType = null, CancellationToken cancellationToken = default) + { + LastRecordedException = exception; + ReportInitializationErrorAsyncExceptionCalled = true; + return Task.CompletedTask; + } + + public new Task ReportInitializationErrorAsync(string errorType, CancellationToken cancellationToken = default) + { + return Task.CompletedTask; + } + + public new Task ReportInvocationErrorAsync(string awsRequestId, Exception exception, CancellationToken cancellationToken = default) + { + LastRecordedException = exception; + ReportInvocationErrorAsyncExceptionCalled = true; + return Task.CompletedTask; + } + + public new async Task SendResponseAsync(string awsRequestId, Stream outputStream, CancellationToken cancellationToken = default) + { + if (outputStream != null) + { + LastOutputStream = new MemoryStream((int)outputStream.Length); + outputStream.CopyTo(LastOutputStream); + LastOutputStream.Position = 0; + } + + SendResponseAsyncCalled = true; + } + + internal override async Task StartStreamingResponseAsync( + string awsRequestId, ResponseStream responseStream, CancellationToken cancellationToken = default) + { + StartStreamingResponseAsyncCalled = true; + LastStreamingResponseStream = responseStream; + + // Simulate the HTTP stream being available + await responseStream.SetHttpOutputStreamAsync(new MemoryStream(), cancellationToken); + + // Wait for the handler to finish writing (mirrors real SerializeToStreamAsync behavior) + await responseStream.WaitForCompletionAsync(); + + return new NoOpDisposable(); + } + +#if NET8_0_OR_GREATER + public new Task RestoreNextInvocationAsync(CancellationToken cancellationToken = default) + => Task.CompletedTask; + + public new Task ReportRestoreErrorAsync(Exception exception, String errorType = null, CancellationToken cancellationToken = default) + => Task.CompletedTask; +#endif + } + + /// + /// A no-op IDisposable for test overrides of StartStreamingResponseAsync. + /// + internal class NoOpDisposable : IDisposable + { + public void Dispose() { } + } +} diff --git a/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/AspNetCoreStreamingApiGatewayTest/AspNetCoreStreamingApiGatewayTest.csproj b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/AspNetCoreStreamingApiGatewayTest/AspNetCoreStreamingApiGatewayTest.csproj new file mode 100644 index 000000000..6c72dc80c --- /dev/null +++ b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/AspNetCoreStreamingApiGatewayTest/AspNetCoreStreamingApiGatewayTest.csproj @@ -0,0 +1,15 @@ + + + Exe + net10.0 + enable + enable + true + Lambda + true + true + + + + + diff --git a/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/AspNetCoreStreamingApiGatewayTest/Program.cs b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/AspNetCoreStreamingApiGatewayTest/Program.cs new file mode 100644 index 000000000..fbebed76b --- /dev/null +++ b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/AspNetCoreStreamingApiGatewayTest/Program.cs @@ -0,0 +1,133 @@ +#pragma warning disable CA2252 + +using Amazon.Lambda.AspNetCoreServer.Hosting; + +var builder = WebApplication.CreateBuilder(args); + +// Determine the event source from environment variable. +// RestApi for API Gateway REST API, HttpApi for Lambda Function URL +// (Function URL uses the same payload format as HTTP API v2). +var eventSourceType = Environment.GetEnvironmentVariable("LAMBDA_EVENT_SOURCE") ?? "RestApi"; +var eventSource = eventSourceType.Equals("HttpApi", StringComparison.OrdinalIgnoreCase) + ? LambdaEventSource.HttpApi + : LambdaEventSource.RestApi; + +builder.Services.AddAWSLambdaHosting(eventSource, options => +{ + options.EnableResponseStreaming = true; +}); + +var app = builder.Build(); + +app.MapGet("/", () => "Welcome to ASP.NET Core streaming on Lambda"); + +app.MapGet("/streaming-test", async (HttpContext context) => +{ + context.Response.ContentType = "text/plain"; + context.Response.StatusCode = 200; + + var stream = context.Response.BodyWriter.AsStream(); + using var writer = new StreamWriter(stream, leaveOpen: true); + + for (var i = 1; i <= 100; i++) + { + await writer.WriteLineAsync($"Line {i}"); + if (i % 10 == 0) + { + await writer.FlushAsync(); + } + } +}); + +app.MapGet("/streaming-error", async (HttpContext context) => +{ + context.Response.ContentType = "text/plain"; + context.Response.StatusCode = 200; + + var stream = context.Response.BodyWriter.AsStream(); + using var writer = new StreamWriter(stream, leaveOpen: true); + + for (var i = 1; i <= 10; i++) + { + await writer.WriteLineAsync($"Line {i}"); + } + await writer.FlushAsync(); + + throw new InvalidOperationException("Midstream error for testing"); +}); + +app.MapGet("/json-response", (HttpContext context) => +{ + return Results.Json(new { message = "Hello from streaming Lambda", timestamp = DateTime.UtcNow.ToString("o") }); +}); + +app.MapGet("/oncompleted-test", async (HttpContext context) => +{ + // Register an OnCompleted callback that writes a marker to a response header. + // Since headers are sent in the prelude before the body, we use a different approach: + // write a marker into the body from the OnCompleted callback via a shared flag. + var completedMarker = new CompletedMarker(); + context.Response.RegisterForDispose(completedMarker); + + context.Response.OnCompleted(async (state) => + { + var marker = (CompletedMarker)state; + marker.WasExecuted = true; + // Write to a static so the next request can verify it ran + CompletedMarkerStore.LastMarkerExecuted = true; + }, completedMarker); + + context.Response.ContentType = "text/plain"; + context.Response.StatusCode = 200; + + var stream = context.Response.BodyWriter.AsStream(); + using var writer = new StreamWriter(stream, leaveOpen: true); + await writer.WriteAsync("OnCompleted callback registered"); + await writer.FlushAsync(); +}); + +app.MapGet("/oncompleted-verify", (HttpContext context) => +{ + // Returns whether the OnCompleted callback from the previous request was executed + return Results.Json(new { onCompletedExecuted = CompletedMarkerStore.LastMarkerExecuted }); +}); + +app.MapGet("/custom-headers", (HttpContext context) => +{ + context.Response.StatusCode = 201; + context.Response.ContentType = "text/plain"; + context.Response.Headers["X-Custom-Header"] = "custom-value"; + context.Response.Headers["X-Another-Header"] = "another-value"; + return Results.Text("Custom headers response", "text/plain", statusCode: 201); +}); + +app.MapGet("/set-cookie", (HttpContext context) => +{ + context.Response.Cookies.Append("session", "abc123", new CookieOptions + { + Path = "/", + HttpOnly = true + }); + context.Response.Cookies.Append("theme", "dark"); + return Results.Text("Cookies set"); +}); + +app.MapPost("/echo-body", async (HttpContext context) => +{ + using var reader = new StreamReader(context.Request.Body); + var body = await reader.ReadToEndAsync(); + return Results.Text($"Echo: {body}"); +}); + +app.Run(); + +class CompletedMarker : IDisposable +{ + public bool WasExecuted { get; set; } + public void Dispose() { } +} + +static class CompletedMarkerStore +{ + public static bool LastMarkerExecuted { get; set; } +} diff --git a/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/AspNetCoreStreamingApiGatewayTest/aws-lambda-tools-defaults.json b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/AspNetCoreStreamingApiGatewayTest/aws-lambda-tools-defaults.json new file mode 100644 index 000000000..99fead300 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/AspNetCoreStreamingApiGatewayTest/aws-lambda-tools-defaults.json @@ -0,0 +1,17 @@ +{ + "Information": [ + "This file provides default values for the deployment wizard inside Visual Studio and the AWS Lambda commands added to the .NET Core CLI.", + "To learn more about the Lambda commands with the .NET Core CLI execute the following command at the command line in the project root directory.", + "dotnet lambda help" + ], + "profile": "", + "region": "", + "configuration": "Release", + "function-runtime": "dotnet10", + "function-memory-size": 512, + "function-timeout": 30, + "function-handler": "AspNetCoreStreamingApiGatewayTest", + "template": "serverless-restapi.template", + "template-parameters": "", + "s3-prefix": "AspNetCoreStreamingApiGatewayTest/" +} diff --git a/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/AspNetCoreStreamingApiGatewayTest/serverless-functionurl.template b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/AspNetCoreStreamingApiGatewayTest/serverless-functionurl.template new file mode 100644 index 000000000..496398dd1 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/AspNetCoreStreamingApiGatewayTest/serverless-functionurl.template @@ -0,0 +1,38 @@ +{ + "AWSTemplateFormatVersion": "2010-09-09", + "Transform": "AWS::Serverless-2016-10-31", + "Description": "Integration test for ASP.NET Core response streaming through Lambda Function URL.", + "Resources": { + "AspNetCoreFunction": { + "Type": "AWS::Serverless::Function", + "Properties": { + "Handler": "AspNetCoreStreamingApiGatewayTest", + "Runtime": "dotnet10", + "CodeUri": "", + "MemorySize": 512, + "Timeout": 30, + "Role": null, + "Policies": [ + "AWSLambda_FullAccess" + ], + "Environment": { + "Variables": { + "LAMBDA_EVENT_SOURCE": "HttpApi", + "LAMBDA_NET_SERIALIZER_DEBUG": "true", + "LAMBDA_RUNTIMESUPPORT_DEBUG": "true" + } + }, + "FunctionUrlConfig": { + "AuthType": "NONE", + "InvokeMode": "RESPONSE_STREAM" + } + } + } + }, + "Outputs": { + "ApiURL": { + "Description": "Lambda Function URL endpoint", + "Value": { "Fn::GetAtt": ["AspNetCoreFunctionUrl", "FunctionUrl"] } + } + } +} diff --git a/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/AspNetCoreStreamingApiGatewayTest/serverless-restapi.template b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/AspNetCoreStreamingApiGatewayTest/serverless-restapi.template new file mode 100644 index 000000000..f0c338b79 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/AspNetCoreStreamingApiGatewayTest/serverless-restapi.template @@ -0,0 +1,87 @@ +{ + "AWSTemplateFormatVersion": "2010-09-09", + "Transform": "AWS::Serverless-2016-10-31", + "Description": "Integration test for ASP.NET Core response streaming through API Gateway.", + "Resources": { + "StreamingApi": { + "Type": "AWS::Serverless::Api", + "Properties": { + "StageName": "prod", + "MethodSettings": [ + { + "ResourcePath": "/*", + "HttpMethod": "*" + } + ], + "DefinitionBody": { + "openapi": "3.0.1", + "info": { + "title": "ASP.NET Core Streaming Integration Test", + "version": "1.0" + }, + "paths": { + "/": { + "x-amazon-apigateway-any-method": { + "x-amazon-apigateway-integration": { + "type": "aws_proxy", + "httpMethod": "POST", + "payloadFormatVersion": "1.0", + "uri": { "Fn::Sub": "arn:aws:apigateway:${AWS::Region}:lambda:path/2021-11-15/functions/${AspNetCoreFunction.Arn}/response-streaming-invocations" }, + "responseTransferMode": "STREAM", + "timeoutInMillis": 29000 + } + } + }, + "/{proxy+}": { + "x-amazon-apigateway-any-method": { + "x-amazon-apigateway-integration": { + "type": "aws_proxy", + "httpMethod": "POST", + "payloadFormatVersion": "1.0", + "uri": { "Fn::Sub": "arn:aws:apigateway:${AWS::Region}:lambda:path/2021-11-15/functions/${AspNetCoreFunction.Arn}/response-streaming-invocations" }, + "responseTransferMode": "STREAM", + "timeoutInMillis": 29000 + } + } + } + } + } + } + }, + "AspNetCoreFunction": { + "Type": "AWS::Serverless::Function", + "Properties": { + "Handler": "AspNetCoreStreamingApiGatewayTest", + "Runtime": "dotnet10", + "CodeUri": "", + "MemorySize": 512, + "Timeout": 30, + "Role": null, + "Policies": [ + "AWSLambda_FullAccess" + ], + "Environment" : { + "Variables" : { + "LAMBDA_NET_SERIALIZER_DEBUG": "true", + "LAMBDA_RUNTIMESUPPORT_DEBUG": "true" + } + } + } + }, + "ApiPermission": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "FunctionName": { "Ref": "AspNetCoreFunction" }, + "Principal": "apigateway.amazonaws.com", + "SourceArn": { "Fn::Sub": "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${StreamingApi}/*/*/*" } + } + } + }, + "Outputs": { + "ApiURL": { + "Description": "API endpoint URL for Prod environment", + "Value": { "Fn::Sub": "https://${StreamingApi}.execute-api.${AWS::Region}.amazonaws.com/prod/" } + } + } +} diff --git a/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/ResponseStreamingFunctionHandlers/Function.cs b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/ResponseStreamingFunctionHandlers/Function.cs new file mode 100644 index 000000000..8c645ff5b --- /dev/null +++ b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/ResponseStreamingFunctionHandlers/Function.cs @@ -0,0 +1,56 @@ +#pragma warning disable CA2252 + +using Amazon.Lambda.Core; +using Amazon.Lambda.Core.ResponseStreaming; +using Amazon.Lambda.RuntimeSupport; +using Amazon.Lambda.Serialization.SystemTextJson; + +// The function handler that will be called for each Lambda event +var handler = async (string input, ILambdaContext context) => +{ + using var stream = LambdaResponseStreamFactory.CreateStream(); + + switch(input) + { + case $"{nameof(SimpleFunctionHandler)}": + await SimpleFunctionHandler(stream, context); + break; + case $"{nameof(StreamContentHandler)}": + await StreamContentHandler(stream, context); + break; + case $"{nameof(UnhandledExceptionHandler)}": + await UnhandledExceptionHandler(stream, context); + break; + default: + throw new ArgumentException($"Unknown handler scenario {input}"); + } +}; + +async Task SimpleFunctionHandler(Stream stream, ILambdaContext context) +{ + using var writer = new StreamWriter(stream); + await writer.WriteAsync("Hello, World!"); +} + +async Task StreamContentHandler(Stream stream, ILambdaContext context) +{ + using var writer = new StreamWriter(stream); + + await writer.WriteLineAsync("Starting stream content..."); + for(var i = 0; i < 10000; i++) + { + await writer.WriteLineAsync($"Line {i}"); + } + await writer.WriteLineAsync("Finish stream content"); +} + +async Task UnhandledExceptionHandler(Stream stream, ILambdaContext context) +{ + using var writer = new StreamWriter(stream); + await writer.WriteAsync("This method will fail"); + throw new InvalidOperationException("This is an unhandled exception"); +} + +await LambdaBootstrapBuilder.Create(handler, new DefaultLambdaJsonSerializer()) + .Build() + .RunAsync(); diff --git a/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/ResponseStreamingFunctionHandlers/ResponseStreamingFunctionHandlers.csproj b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/ResponseStreamingFunctionHandlers/ResponseStreamingFunctionHandlers.csproj new file mode 100644 index 000000000..fa81eaa17 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/ResponseStreamingFunctionHandlers/ResponseStreamingFunctionHandlers.csproj @@ -0,0 +1,19 @@ + + + Exe + net10.0 + enable + enable + true + Lambda + + true + + true + + + + + + + \ No newline at end of file diff --git a/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/ResponseStreamingFunctionHandlers/aws-lambda-tools-defaults.json b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/ResponseStreamingFunctionHandlers/aws-lambda-tools-defaults.json new file mode 100644 index 000000000..3042c3978 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/ResponseStreamingFunctionHandlers/aws-lambda-tools-defaults.json @@ -0,0 +1,15 @@ +{ + "Information": [ + "This file provides default values for the deployment wizard inside Visual Studio and the AWS Lambda commands added to the .NET Core CLI.", + "To learn more about the Lambda commands with the .NET Core CLI execute the following command at the command line in the project root directory.", + "dotnet lambda help", + "All the command line options for the Lambda command can be specified in this file." + ], + "profile": "default", + "region": "us-west-2", + "configuration": "Release", + "function-runtime": "dotnet10", + "function-memory-size": 512, + "function-timeout": 30, + "function-handler": "ResponseStreamingFunctionHandlers" +} \ No newline at end of file diff --git a/Libraries/test/IntegrationTests.Helpers/CloudFormationHelper.cs b/Libraries/test/IntegrationTests.Helpers/CloudFormationHelper.cs index 43f4005c1..76a1c2439 100644 --- a/Libraries/test/IntegrationTests.Helpers/CloudFormationHelper.cs +++ b/Libraries/test/IntegrationTests.Helpers/CloudFormationHelper.cs @@ -60,15 +60,19 @@ public async Task DeleteStackAsync(string stackName) { try { - var response = await _cloudFormationClient.DescribeStackResourcesAsync( - new DescribeStackResourcesRequest { StackName = stackName }); - - Console.WriteLine($"[CloudFormationHelper] Stack '{stackName}' has {response.StackResources.Count} resources: " + - string.Join(", ", response.StackResources.Select(r => $"{r.LogicalResourceId}={r.PhysicalResourceId} ({r.ResourceStatus})"))); - - var physicalId = response.StackResources - .FirstOrDefault(r => string.Equals(r.LogicalResourceId, logicalResourceId)) - ?.PhysicalResourceId; + // Use DescribeStackResource (singular) to query a specific resource by logical ID. + // DescribeStackResources (plural) returns at most 100 resources without pagination, + // which causes resources to be silently missed in large stacks (>100 resources). + var response = await _cloudFormationClient.DescribeStackResourceAsync( + new DescribeStackResourceRequest + { + StackName = stackName, + LogicalResourceId = logicalResourceId + }); + + var physicalId = response.StackResourceDetail?.PhysicalResourceId; + + Console.WriteLine($"[CloudFormationHelper] Resource '{logicalResourceId}' in stack '{stackName}': PhysicalId={physicalId}, Status={response.StackResourceDetail?.ResourceStatus}"); if (physicalId == null) { diff --git a/Libraries/test/IntegrationTests.Helpers/LambdaHelper.cs b/Libraries/test/IntegrationTests.Helpers/LambdaHelper.cs index 6436c7c7b..591eb5e06 100644 --- a/Libraries/test/IntegrationTests.Helpers/LambdaHelper.cs +++ b/Libraries/test/IntegrationTests.Helpers/LambdaHelper.cs @@ -1,3 +1,6 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + using System.Collections.Generic; using System.Threading.Tasks; using Amazon.Lambda; @@ -55,6 +58,14 @@ public async Task ListEventSourceMappingsAsync( }); } + public async Task GetFunctionUrlConfigAsync(string functionName) + { + return await _lambdaClient.GetFunctionUrlConfigAsync(new GetFunctionUrlConfigRequest + { + FunctionName = functionName + }); + } + public async Task WaitTillNotPending(List functions) { foreach (var function in functions) diff --git a/Libraries/test/IntegrationTests.Helpers/S3Helper.cs b/Libraries/test/IntegrationTests.Helpers/S3Helper.cs index e3c09eefd..9620732b7 100644 --- a/Libraries/test/IntegrationTests.Helpers/S3Helper.cs +++ b/Libraries/test/IntegrationTests.Helpers/S3Helper.cs @@ -33,5 +33,13 @@ public async Task BucketExistsAsync(string bucketName) var response = await _s3Client.ListBucketsAsync(new ListBucketsRequest()); return response.Buckets.Any(x => x.BucketName.Equals(bucketName)); } + + public async Task GetBucketNotificationAsync(string bucketName) + { + return await _s3Client.GetBucketNotificationAsync(new GetBucketNotificationRequest + { + BucketName = bucketName + }); + } } } diff --git a/Libraries/test/TestCustomAuthorizerApp/serverless.template b/Libraries/test/TestCustomAuthorizerApp/serverless.template index 41e03a2f9..ebce2bc30 100644 --- a/Libraries/test/TestCustomAuthorizerApp/serverless.template +++ b/Libraries/test/TestCustomAuthorizerApp/serverless.template @@ -1,7 +1,7 @@ { "AWSTemplateFormatVersion": "2010-09-09", "Transform": "AWS::Serverless-2016-10-31", - "Description": "This template is partially managed by Amazon.Lambda.Annotations (v1.10.0.0).", + "Description": "This template is partially managed by Amazon.Lambda.Annotations (v1.14.0.0).", "Resources": { "AnnotationsHttpApi": { "Type": "AWS::Serverless::HttpApi", diff --git a/Libraries/test/TestExecutableServerlessApp/serverless.template b/Libraries/test/TestExecutableServerlessApp/serverless.template index 3092da266..efa6636d9 100644 --- a/Libraries/test/TestExecutableServerlessApp/serverless.template +++ b/Libraries/test/TestExecutableServerlessApp/serverless.template @@ -1,7 +1,7 @@ { "AWSTemplateFormatVersion": "2010-09-09", "Transform": "AWS::Serverless-2016-10-31", - "Description": "An AWS Serverless Application. This template is partially managed by Amazon.Lambda.Annotations (v1.10.0.0).", + "Description": "An AWS Serverless Application. This template is partially managed by Amazon.Lambda.Annotations (v1.14.0.0).", "Parameters": { "ArchitectureTypeParameter": { "Type": "String", diff --git a/Libraries/test/TestMinimalAPIApp/TestMinimalAPIApp.csproj b/Libraries/test/TestMinimalAPIApp/TestMinimalAPIApp.csproj index 60080ae84..94bb22bb2 100644 --- a/Libraries/test/TestMinimalAPIApp/TestMinimalAPIApp.csproj +++ b/Libraries/test/TestMinimalAPIApp/TestMinimalAPIApp.csproj @@ -1,7 +1,7 @@  - net6.0 + net10.0 enable enable diff --git a/Libraries/test/TestServerlessApp.ALB.IntegrationTests/ALBIntegrationTestContextFixture.cs b/Libraries/test/TestServerlessApp.ALB.IntegrationTests/ALBIntegrationTestContextFixture.cs new file mode 100644 index 000000000..40c70f7be --- /dev/null +++ b/Libraries/test/TestServerlessApp.ALB.IntegrationTests/ALBIntegrationTestContextFixture.cs @@ -0,0 +1,172 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +using System; +using System.IO; +using System.Linq; +using System.Net.Http; +using System.Threading.Tasks; +using Amazon.CloudFormation; +using Amazon.ElasticLoadBalancingV2; +using Amazon.ElasticLoadBalancingV2.Model; +using Amazon.Lambda; +using Amazon.S3; +using IntegrationTests.Helpers; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Xunit; + +namespace TestServerlessApp.ALB.IntegrationTests +{ + public class ALBIntegrationTestContextFixture : IAsyncLifetime + { + private readonly CloudFormationHelper _cloudFormationHelper; + private readonly S3Helper _s3Helper; + + private string _stackName; + private string _bucketName; + + public readonly AmazonElasticLoadBalancingV2Client ELBv2Client; + public readonly LambdaHelper LambdaHelper; + public readonly HttpClient HttpClient; + + public string ALBDnsName; + public string LoadBalancerArn; + + public ALBIntegrationTestContextFixture() + { + _cloudFormationHelper = new CloudFormationHelper(new AmazonCloudFormationClient(Amazon.RegionEndpoint.USWest2)); + _s3Helper = new S3Helper(new AmazonS3Client(Amazon.RegionEndpoint.USWest2)); + LambdaHelper = new LambdaHelper(new AmazonLambdaClient(Amazon.RegionEndpoint.USWest2)); + ELBv2Client = new AmazonElasticLoadBalancingV2Client(Amazon.RegionEndpoint.USWest2); + HttpClient = new HttpClient(); + } + + public async Task InitializeAsync() + { + var scriptPath = Path.Combine("..", "..", "..", "DeploymentScript.ps1"); + Console.WriteLine($"[ALB IntegrationTest] Running deployment script: {scriptPath}"); + await CommandLineWrapper.RunAsync($"pwsh {scriptPath}"); + Console.WriteLine("[ALB IntegrationTest] Deployment script completed successfully."); + + _stackName = GetConfigValue("stack-name"); + _bucketName = GetConfigValue("s3-bucket"); + Console.WriteLine($"[ALB IntegrationTest] Stack name: '{_stackName}', Bucket name: '{_bucketName}'"); + Assert.False(string.IsNullOrEmpty(_stackName), "Stack name should not be empty"); + Assert.False(string.IsNullOrEmpty(_bucketName), "Bucket name should not be empty"); + + // Check stack status + var stackStatus = await _cloudFormationHelper.GetStackStatusAsync(_stackName); + Console.WriteLine($"[ALB IntegrationTest] Stack status: {stackStatus}"); + Assert.NotNull(stackStatus); + Assert.Equal(StackStatus.CREATE_COMPLETE, stackStatus); + + // Get ALB DNS name from stack outputs + ALBDnsName = await _cloudFormationHelper.GetOutputValueAsync(_stackName, "ALBDnsName"); + Console.WriteLine($"[ALB IntegrationTest] ALB DNS Name: {ALBDnsName}"); + Assert.False(string.IsNullOrEmpty(ALBDnsName), "ALB DNS Name should not be empty"); + + // Resolve the LoadBalancerArn from DNS name for scoped queries + var lbResponse = await ELBv2Client.DescribeLoadBalancersAsync(new DescribeLoadBalancersRequest()); + var loadBalancer = lbResponse.LoadBalancers.FirstOrDefault(lb => lb.DNSName == ALBDnsName); + if (loadBalancer != null) + { + LoadBalancerArn = loadBalancer.LoadBalancerArn; + Console.WriteLine($"[ALB IntegrationTest] LoadBalancer ARN: {LoadBalancerArn}"); + } + + // Wait for Lambda targets to become healthy by polling target health + Console.WriteLine("[ALB IntegrationTest] Waiting for targets to become healthy..."); + await WaitForTargetsHealthy(timeoutSeconds: 120, pollIntervalSeconds: 10); + } + + /// + /// Polls ALB target group health until at least one target is healthy or the timeout is reached. + /// + private async Task WaitForTargetsHealthy(int timeoutSeconds, int pollIntervalSeconds) + { + var deadline = DateTime.UtcNow.AddSeconds(timeoutSeconds); + + while (DateTime.UtcNow < deadline) + { + try + { + if (!string.IsNullOrEmpty(LoadBalancerArn)) + { + var tgResponse = await ELBv2Client.DescribeTargetGroupsAsync(new DescribeTargetGroupsRequest + { + LoadBalancerArn = LoadBalancerArn + }); + + var lambdaTgs = tgResponse.TargetGroups.Where(tg => tg.TargetType == TargetTypeEnum.Lambda).ToList(); + if (lambdaTgs.Count >= 2) + { + var allHealthy = true; + foreach (var tg in lambdaTgs) + { + var healthResponse = await ELBv2Client.DescribeTargetHealthAsync(new DescribeTargetHealthRequest + { + TargetGroupArn = tg.TargetGroupArn + }); + if (!healthResponse.TargetHealthDescriptions.Any(t => t.TargetHealth.State == TargetHealthStateEnum.Healthy)) + { + allHealthy = false; + break; + } + } + + if (allHealthy) + { + Console.WriteLine("[ALB IntegrationTest] All targets are healthy."); + return; + } + } + } + } + catch (Exception ex) + { + Console.WriteLine($"[ALB IntegrationTest] Polling error (will retry): {ex.Message}"); + } + + Console.WriteLine($"[ALB IntegrationTest] Targets not yet healthy, retrying in {pollIntervalSeconds}s..."); + await Task.Delay(pollIntervalSeconds * 1000); + } + + Console.WriteLine("[ALB IntegrationTest] Warning: Timed out waiting for targets to become healthy. Proceeding anyway."); + } + + public async Task DisposeAsync() + { + if (!string.IsNullOrEmpty(_stackName)) + { + Console.WriteLine($"[ALB IntegrationTest] Cleaning up stack '{_stackName}'..."); + await _cloudFormationHelper.DeleteStackAsync(_stackName); + Assert.True(await _cloudFormationHelper.IsDeletedAsync(_stackName), + $"The stack '{_stackName}' still exists and will have to be manually deleted."); + } + + if (!string.IsNullOrEmpty(_bucketName)) + { + Console.WriteLine($"[ALB IntegrationTest] Cleaning up bucket '{_bucketName}'..."); + await _s3Helper.DeleteBucketAsync(_bucketName); + Assert.False(await _s3Helper.BucketExistsAsync(_bucketName), + $"The bucket '{_bucketName}' still exists and will have to be manually deleted."); + } + + // Reset aws-lambda-tools-defaults.json to original values + var filePath = Path.Combine("..", "..", "..", "..", "TestServerlessApp.ALB", "aws-lambda-tools-defaults.json"); + var token = JObject.Parse(await File.ReadAllTextAsync(filePath)); + token["s3-bucket"] = "test-serverless-app-alb"; + token["stack-name"] = "test-serverless-app-alb"; + token["function-architecture"] = "x86_64"; + await File.WriteAllTextAsync(filePath, token.ToString(Formatting.Indented)); + } + + private string GetConfigValue(string key) + { + var filePath = Path.Combine("..", "..", "..", "..", "TestServerlessApp.ALB", "aws-lambda-tools-defaults.json"); + var token = JObject.Parse(File.ReadAllText(filePath))[key]; + return token?.ToObject(); + } + } +} diff --git a/Libraries/test/TestServerlessApp.ALB.IntegrationTests/ALBIntegrationTestContextFixtureCollection.cs b/Libraries/test/TestServerlessApp.ALB.IntegrationTests/ALBIntegrationTestContextFixtureCollection.cs new file mode 100644 index 000000000..696048ab4 --- /dev/null +++ b/Libraries/test/TestServerlessApp.ALB.IntegrationTests/ALBIntegrationTestContextFixtureCollection.cs @@ -0,0 +1,12 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +using Xunit; + +namespace TestServerlessApp.ALB.IntegrationTests +{ + [CollectionDefinition("ALB Integration Tests")] + public class ALBIntegrationTestContextFixtureCollection : ICollectionFixture + { + } +} diff --git a/Libraries/test/TestServerlessApp.ALB.IntegrationTests/ALBTargetTests.cs b/Libraries/test/TestServerlessApp.ALB.IntegrationTests/ALBTargetTests.cs new file mode 100644 index 000000000..8e90c1a79 --- /dev/null +++ b/Libraries/test/TestServerlessApp.ALB.IntegrationTests/ALBTargetTests.cs @@ -0,0 +1,81 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +using System.Linq; +using System.Net; +using System.Threading.Tasks; +using Amazon.ElasticLoadBalancingV2; +using Amazon.ElasticLoadBalancingV2.Model; +using Xunit; + +namespace TestServerlessApp.ALB.IntegrationTests +{ + [Collection("ALB Integration Tests")] + public class ALBTargetTests + { + private readonly ALBIntegrationTestContextFixture _fixture; + + public ALBTargetTests(ALBIntegrationTestContextFixture fixture) + { + _fixture = fixture; + } + + [Fact] + public async Task InvokeHelloEndpoint_ReturnsSuccessWithBody() + { + // ACT + var response = await _fixture.HttpClient.GetAsync($"http://{_fixture.ALBDnsName}/hello"); + + // ASSERT + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + var body = await response.Content.ReadAsStringAsync(); + Assert.Contains("Hello from ALB Lambda!", body); + Assert.Contains("/hello", body); + } + + [Fact] + public async Task InvokeHealthEndpoint_ReturnsHealthy() + { + // ACT + var response = await _fixture.HttpClient.GetAsync($"http://{_fixture.ALBDnsName}/health"); + + // ASSERT + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + var body = await response.Content.ReadAsStringAsync(); + Assert.Contains("healthy", body); + } + + [Fact] + public async Task InvokeUnknownPath_Returns404FromDefaultAction() + { + // ACT - The ALB default action returns 404 for unmatched paths + var response = await _fixture.HttpClient.GetAsync($"http://{_fixture.ALBDnsName}/unknown-path"); + + // ASSERT + Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); + var body = await response.Content.ReadAsStringAsync(); + Assert.Contains("Not Found", body); + } + + [Fact] + public async Task VerifyTargetGroupsExist() + { + // ACT - Describe only the target groups associated with this stack's load balancer + Assert.False(string.IsNullOrEmpty(_fixture.LoadBalancerArn), + "LoadBalancerArn should have been resolved during test initialization"); + + var describeResponse = await _fixture.ELBv2Client.DescribeTargetGroupsAsync(new DescribeTargetGroupsRequest + { + LoadBalancerArn = _fixture.LoadBalancerArn + }); + + var albTargetGroups = describeResponse.TargetGroups + .Where(tg => tg.TargetType == TargetTypeEnum.Lambda) + .ToList(); + + // ASSERT - At least our Lambda target groups should exist for this ALB + Assert.True(albTargetGroups.Count >= 2, + $"Expected at least 2 Lambda target groups for ALB '{_fixture.ALBDnsName}', found {albTargetGroups.Count}"); + } + } +} diff --git a/Libraries/test/TestServerlessApp.ALB.IntegrationTests/DeploymentScript.ps1 b/Libraries/test/TestServerlessApp.ALB.IntegrationTests/DeploymentScript.ps1 new file mode 100644 index 000000000..f74ee365f --- /dev/null +++ b/Libraries/test/TestServerlessApp.ALB.IntegrationTests/DeploymentScript.ps1 @@ -0,0 +1,85 @@ +$ErrorActionPreference = 'Stop' + +function Get-Architecture { + $arch = [System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture + if ($arch -eq "Arm64" || $arch -eq "Arm") { + return "arm64" + } + + if ($arch -eq "X64" || $arch -eq "X86") { + return "x86_64" + } + + throw "Unsupported architecture: $arch" +} + +try +{ + Push-Location $PSScriptRoot + $guid = New-Guid + $suffix = $guid.ToString().Split('-') | Select-Object -First 1 + $identifier = "test-alb-app-" + $suffix + cd ..\TestServerlessApp.ALB + + $arch = Get-Architecture + + # Replace bucket name in aws-lambda-tools-defaults.json + $line = Get-Content .\aws-lambda-tools-defaults.json | Select-String s3-bucket | Select-Object -ExpandProperty Line + $content = Get-Content .\aws-lambda-tools-defaults.json + $content | ForEach-Object {$_ -replace $line, "`"s3-bucket`" : `"$identifier`","} | Set-Content .\aws-lambda-tools-defaults.json + + # Replace stack name in aws-lambda-tools-defaults.json + $line = Get-Content .\aws-lambda-tools-defaults.json | Select-String stack-name | Select-Object -ExpandProperty Line + $content = Get-Content .\aws-lambda-tools-defaults.json + $content | ForEach-Object {$_ -replace $line, "`"stack-name`" : `"$identifier`","} | Set-Content .\aws-lambda-tools-defaults.json + + # Replace function-architecture in aws-lambda-tools-defaults.json + $line = Get-Content .\aws-lambda-tools-defaults.json | Select-String function-architecture | Select-Object -ExpandProperty Line + $content = Get-Content .\aws-lambda-tools-defaults.json + $content | ForEach-Object {$_ -replace $line, "`"function-architecture`" : `"$arch`""} | Set-Content .\aws-lambda-tools-defaults.json + + # Extract region + $json = Get-Content .\aws-lambda-tools-defaults.json | Out-String | ConvertFrom-Json + $region = $json.region + + dotnet tool install -g Amazon.Lambda.Tools + Write-Host "Creating S3 Bucket $identifier" + + if(![string]::IsNullOrEmpty($region)) + { + aws s3 mb s3://$identifier --region $region + } + else + { + aws s3 mb s3://$identifier + } + + if (!$?) + { + throw "Failed to create the following bucket: $identifier" + } + + dotnet restore + Write-Host "Creating CloudFormation Stack $identifier, Architecture $arch" + dotnet lambda deploy-serverless + if (!$?) + { + Write-Host "Deployment failed. Fetching CloudFormation stack events for debugging..." + try { + $events = aws cloudformation describe-stack-events --stack-name $identifier --query "StackEvents[?ResourceStatus=='CREATE_FAILED' || ResourceStatus=='UPDATE_FAILED']" --output json 2>&1 + if ($events) { + Write-Host "CloudFormation failed events:" + Write-Host $events + } + } + catch { + Write-Host "Could not fetch CloudFormation events: $_" + } + + throw "Failed to create the following CloudFormation stack: $identifier" + } +} +finally +{ + Pop-Location +} diff --git a/Libraries/test/TestServerlessApp.ALB.IntegrationTests/TestServerlessApp.ALB.IntegrationTests.csproj b/Libraries/test/TestServerlessApp.ALB.IntegrationTests/TestServerlessApp.ALB.IntegrationTests.csproj new file mode 100644 index 000000000..ceb0564e5 --- /dev/null +++ b/Libraries/test/TestServerlessApp.ALB.IntegrationTests/TestServerlessApp.ALB.IntegrationTests.csproj @@ -0,0 +1,20 @@ + + + net6.0 + false + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + diff --git a/Libraries/test/TestServerlessApp.ALB/ALBFunctions.cs b/Libraries/test/TestServerlessApp.ALB/ALBFunctions.cs new file mode 100644 index 000000000..325a130e1 --- /dev/null +++ b/Libraries/test/TestServerlessApp.ALB/ALBFunctions.cs @@ -0,0 +1,109 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +using Amazon.Lambda.Annotations; +using Amazon.Lambda.Annotations.ALB; +using Amazon.Lambda.ApplicationLoadBalancerEvents; +using Amazon.Lambda.Core; +using System.Collections.Generic; + +namespace TestServerlessApp.ALB +{ + public class ALBFunctions + { + /// + /// Hello endpoint - returns a greeting message with the request path. + /// Uses the raw ApplicationLoadBalancerRequest (pass-through mode). + /// + [LambdaFunction(ResourceName = "ALBHello", MemorySize = 256, Timeout = 15)] + [ALBApi("@ALBTestListener", "/hello", 1)] + public ApplicationLoadBalancerResponse Hello(ApplicationLoadBalancerRequest request, ILambdaContext context) + { + context.Logger.LogInformation($"Hello endpoint hit. Path: {request.Path}"); + + return new ApplicationLoadBalancerResponse + { + StatusCode = 200, + StatusDescription = "200 OK", + IsBase64Encoded = false, + Headers = new Dictionary + { + { "Content-Type", "application/json" } + }, + Body = $"{{\"message\": \"Hello from ALB Lambda!\", \"path\": \"{request.Path}\"}}" + }; + } + + /// + /// Health check endpoint for ALB target group health checks. + /// Uses the raw ApplicationLoadBalancerRequest (pass-through mode). + /// + [LambdaFunction(ResourceName = "ALBHealth", MemorySize = 128, Timeout = 5)] + [ALBApi("@ALBTestListener", "/health", 2)] + public ApplicationLoadBalancerResponse Health(ApplicationLoadBalancerRequest request, ILambdaContext context) + { + return new ApplicationLoadBalancerResponse + { + StatusCode = 200, + StatusDescription = "200 OK", + IsBase64Encoded = false, + Headers = new Dictionary + { + { "Content-Type", "application/json" } + }, + Body = "{\"status\": \"healthy\"}" + }; + } + + /// + /// Greeting endpoint that uses FromQuery and FromHeader parameter binding. + /// Demonstrates ALB functions with any number of parameters using FromX attributes. + /// + [LambdaFunction(ResourceName = "ALBGreeting", MemorySize = 256, Timeout = 15)] + [ALBApi("@ALBTestListener", "/greeting", 3)] + public ApplicationLoadBalancerResponse Greeting( + [FromQuery] string name, + [FromHeader(Name = "X-Custom-Header")] string customHeader, + ILambdaContext context) + { + context.Logger.LogInformation($"Greeting endpoint hit. Name: {name}, Header: {customHeader}"); + + return new ApplicationLoadBalancerResponse + { + StatusCode = 200, + StatusDescription = "200 OK", + IsBase64Encoded = false, + Headers = new Dictionary + { + { "Content-Type", "application/json" } + }, + Body = $"{{\"message\": \"Hello {name}!\", \"customHeader\": \"{customHeader}\"}}" + }; + } + + /// + /// Endpoint that uses FromBody to deserialize JSON request body. + /// Demonstrates ALB function with body deserialization. + /// + [LambdaFunction(ResourceName = "ALBCreateItem", MemorySize = 256, Timeout = 15)] + [ALBApi("@ALBTestListener", "/items", 4, HttpMethod = "POST")] + public ApplicationLoadBalancerResponse CreateItem( + [FromBody] string body, + ILambdaContext context) + { + context.Logger.LogInformation($"CreateItem endpoint hit. Body: {body}"); + + return new ApplicationLoadBalancerResponse + { + StatusCode = 201, + StatusDescription = "201 Created", + IsBase64Encoded = false, + Headers = new Dictionary + { + { "Content-Type", "application/json" } + }, + Body = $"{{\"created\": true, \"body\": \"{body}\"}}" + }; + } + } +} diff --git a/Libraries/test/TestServerlessApp.ALB/AssemblyAttributes.cs b/Libraries/test/TestServerlessApp.ALB/AssemblyAttributes.cs new file mode 100644 index 000000000..a0821fb25 --- /dev/null +++ b/Libraries/test/TestServerlessApp.ALB/AssemblyAttributes.cs @@ -0,0 +1,6 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +using Amazon.Lambda.Core; + +[assembly: LambdaSerializer(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))] diff --git a/Libraries/test/TestServerlessApp.ALB/TestServerlessApp.ALB.csproj b/Libraries/test/TestServerlessApp.ALB/TestServerlessApp.ALB.csproj new file mode 100644 index 000000000..9f9e5630c --- /dev/null +++ b/Libraries/test/TestServerlessApp.ALB/TestServerlessApp.ALB.csproj @@ -0,0 +1,15 @@ + + + net6.0 + true + Lambda + true + + + + + + + + + diff --git a/Libraries/test/TestServerlessApp.ALB/aws-lambda-tools-defaults.json b/Libraries/test/TestServerlessApp.ALB/aws-lambda-tools-defaults.json new file mode 100644 index 000000000..49464b51c --- /dev/null +++ b/Libraries/test/TestServerlessApp.ALB/aws-lambda-tools-defaults.json @@ -0,0 +1,17 @@ +{ + "Information": [ + "This file provides default values for the deployment wizard inside Visual Studio and the AWS Lambda commands added to the .NET Core CLI.", + "To learn more about the Lambda commands with the .NET Core CLI execute the following command at the command line in the project root directory.", + "dotnet lambda help", + "All the command line options for the Lambda command can be specified in this file." + ], + "profile": "", + "region": "us-west-2", + "configuration": "Release", + "framework": "net6.0", +"s3-bucket" : "test-alb-app-dce31eae", +"stack-name" : "test-alb-app-dce31eae", + "template": "serverless.template", + "template-parameters": "", +"function-architecture" : "x86_64" +} diff --git a/Libraries/test/TestServerlessApp.ALB/serverless.template b/Libraries/test/TestServerlessApp.ALB/serverless.template new file mode 100644 index 000000000..a8992babb --- /dev/null +++ b/Libraries/test/TestServerlessApp.ALB/serverless.template @@ -0,0 +1,542 @@ +{ + "AWSTemplateFormatVersion": "2010-09-09", + "Transform": "AWS::Serverless-2016-10-31", + "Description": "ALB Integration Test Stack - VPC and ALB infrastructure for testing Lambda ALB annotations This template is partially managed by Amazon.Lambda.Annotations (v1.14.0.0).", + "Resources": { + "ALBTestVPC": { + "Type": "AWS::EC2::VPC", + "Properties": { + "CidrBlock": "10.0.0.0/16", + "EnableDnsSupport": true, + "EnableDnsHostnames": true, + "Tags": [ + { + "Key": "Name", + "Value": "ALB-Integration-Test-VPC" + } + ] + } + }, + "ALBTestInternetGateway": { + "Type": "AWS::EC2::InternetGateway" + }, + "ALBTestGatewayAttachment": { + "Type": "AWS::EC2::VPCGatewayAttachment", + "Properties": { + "VpcId": { + "Ref": "ALBTestVPC" + }, + "InternetGatewayId": { + "Ref": "ALBTestInternetGateway" + } + } + }, + "ALBTestRouteTable": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "ALBTestVPC" + } + } + }, + "ALBTestRoute": { + "Type": "AWS::EC2::Route", + "DependsOn": "ALBTestGatewayAttachment", + "Properties": { + "RouteTableId": { + "Ref": "ALBTestRouteTable" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "GatewayId": { + "Ref": "ALBTestInternetGateway" + } + } + }, + "ALBTestSubnet1": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "VpcId": { + "Ref": "ALBTestVPC" + }, + "CidrBlock": "10.0.1.0/24", + "AvailabilityZone": { + "Fn::Select": [ + 0, + { + "Fn::GetAZs": "" + } + ] + }, + "MapPublicIpOnLaunch": true + } + }, + "ALBTestSubnet2": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "VpcId": { + "Ref": "ALBTestVPC" + }, + "CidrBlock": "10.0.2.0/24", + "AvailabilityZone": { + "Fn::Select": [ + 1, + { + "Fn::GetAZs": "" + } + ] + }, + "MapPublicIpOnLaunch": true + } + }, + "ALBTestSubnet1RouteTableAssociation": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "SubnetId": { + "Ref": "ALBTestSubnet1" + }, + "RouteTableId": { + "Ref": "ALBTestRouteTable" + } + } + }, + "ALBTestSubnet2RouteTableAssociation": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "SubnetId": { + "Ref": "ALBTestSubnet2" + }, + "RouteTableId": { + "Ref": "ALBTestRouteTable" + } + } + }, + "ALBTestSecurityGroup": { + "Type": "AWS::EC2::SecurityGroup", + "Properties": { + "GroupDescription": "ALB Integration Test Security Group", + "VpcId": { + "Ref": "ALBTestVPC" + }, + "SecurityGroupIngress": [ + { + "IpProtocol": "tcp", + "FromPort": 80, + "ToPort": 80, + "CidrIp": "0.0.0.0/0" + } + ] + } + }, + "ALBTestLoadBalancer": { + "Type": "AWS::ElasticLoadBalancingV2::LoadBalancer", + "Properties": { + "Type": "application", + "Scheme": "internet-facing", + "Subnets": [ + { + "Ref": "ALBTestSubnet1" + }, + { + "Ref": "ALBTestSubnet2" + } + ], + "SecurityGroups": [ + { + "Ref": "ALBTestSecurityGroup" + } + ] + } + }, + "ALBTestListener": { + "Type": "AWS::ElasticLoadBalancingV2::Listener", + "Properties": { + "LoadBalancerArn": { + "Ref": "ALBTestLoadBalancer" + }, + "Port": 80, + "Protocol": "HTTP", + "DefaultActions": [ + { + "Type": "fixed-response", + "FixedResponseConfig": { + "StatusCode": "404", + "ContentType": "application/json", + "MessageBody": "{\"error\": \"Not Found\"}" + } + } + ] + } + }, + "ALBHello": { + "Type": "AWS::Serverless::Function", + "Metadata": { + "Tool": "Amazon.Lambda.Annotations", + "SyncedAlbResources": [ + "ALBHelloALBPermission", + "ALBHelloALBTargetGroup", + "ALBHelloALBListenerRule" + ] + }, + "Properties": { + "Runtime": "dotnet6", + "CodeUri": ".", + "MemorySize": 256, + "Timeout": 15, + "Policies": [ + "AWSLambdaBasicExecutionRole" + ], + "PackageType": "Zip", + "Handler": "TestServerlessApp.ALB::TestServerlessApp.ALB.ALBFunctions_Hello_Generated::Hello" + } + }, + "ALBHelloALBPermission": { + "Type": "AWS::Lambda::Permission", + "Metadata": { + "Tool": "Amazon.Lambda.Annotations" + }, + "Properties": { + "FunctionName": { + "Fn::GetAtt": [ + "ALBHello", + "Arn" + ] + }, + "Action": "lambda:InvokeFunction", + "Principal": "elasticloadbalancing.amazonaws.com" + } + }, + "ALBHelloALBTargetGroup": { + "Type": "AWS::ElasticLoadBalancingV2::TargetGroup", + "Metadata": { + "Tool": "Amazon.Lambda.Annotations" + }, + "DependsOn": "ALBHelloALBPermission", + "Properties": { + "TargetType": "lambda", + "Targets": [ + { + "Id": { + "Fn::GetAtt": [ + "ALBHello", + "Arn" + ] + } + } + ] + } + }, + "ALBHelloALBListenerRule": { + "Type": "AWS::ElasticLoadBalancingV2::ListenerRule", + "Metadata": { + "Tool": "Amazon.Lambda.Annotations" + }, + "Properties": { + "Priority": 1, + "Conditions": [ + { + "Field": "path-pattern", + "PathPatternConfig": { + "Values": [ + "/hello" + ] + } + } + ], + "Actions": [ + { + "Type": "forward", + "TargetGroupArn": { + "Ref": "ALBHelloALBTargetGroup" + } + } + ], + "ListenerArn": { + "Ref": "ALBTestListener" + } + } + }, + "ALBHealth": { + "Type": "AWS::Serverless::Function", + "Metadata": { + "Tool": "Amazon.Lambda.Annotations", + "SyncedAlbResources": [ + "ALBHealthALBPermission", + "ALBHealthALBTargetGroup", + "ALBHealthALBListenerRule" + ] + }, + "Properties": { + "Runtime": "dotnet6", + "CodeUri": ".", + "MemorySize": 128, + "Timeout": 5, + "Policies": [ + "AWSLambdaBasicExecutionRole" + ], + "PackageType": "Zip", + "Handler": "TestServerlessApp.ALB::TestServerlessApp.ALB.ALBFunctions_Health_Generated::Health" + } + }, + "ALBHealthALBPermission": { + "Type": "AWS::Lambda::Permission", + "Metadata": { + "Tool": "Amazon.Lambda.Annotations" + }, + "Properties": { + "FunctionName": { + "Fn::GetAtt": [ + "ALBHealth", + "Arn" + ] + }, + "Action": "lambda:InvokeFunction", + "Principal": "elasticloadbalancing.amazonaws.com" + } + }, + "ALBHealthALBTargetGroup": { + "Type": "AWS::ElasticLoadBalancingV2::TargetGroup", + "Metadata": { + "Tool": "Amazon.Lambda.Annotations" + }, + "DependsOn": "ALBHealthALBPermission", + "Properties": { + "TargetType": "lambda", + "Targets": [ + { + "Id": { + "Fn::GetAtt": [ + "ALBHealth", + "Arn" + ] + } + } + ] + } + }, + "ALBHealthALBListenerRule": { + "Type": "AWS::ElasticLoadBalancingV2::ListenerRule", + "Metadata": { + "Tool": "Amazon.Lambda.Annotations" + }, + "Properties": { + "Priority": 2, + "Conditions": [ + { + "Field": "path-pattern", + "PathPatternConfig": { + "Values": [ + "/health" + ] + } + } + ], + "Actions": [ + { + "Type": "forward", + "TargetGroupArn": { + "Ref": "ALBHealthALBTargetGroup" + } + } + ], + "ListenerArn": { + "Ref": "ALBTestListener" + } + } + }, + "ALBGreeting": { + "Type": "AWS::Serverless::Function", + "Metadata": { + "Tool": "Amazon.Lambda.Annotations", + "SyncedAlbResources": [ + "ALBGreetingALBPermission", + "ALBGreetingALBTargetGroup", + "ALBGreetingALBListenerRule" + ] + }, + "Properties": { + "Runtime": "dotnet6", + "CodeUri": ".", + "MemorySize": 256, + "Timeout": 15, + "Policies": [ + "AWSLambdaBasicExecutionRole" + ], + "PackageType": "Zip", + "Handler": "TestServerlessApp.ALB::TestServerlessApp.ALB.ALBFunctions_Greeting_Generated::Greeting" + } + }, + "ALBGreetingALBPermission": { + "Type": "AWS::Lambda::Permission", + "Metadata": { + "Tool": "Amazon.Lambda.Annotations" + }, + "Properties": { + "FunctionName": { + "Fn::GetAtt": [ + "ALBGreeting", + "Arn" + ] + }, + "Action": "lambda:InvokeFunction", + "Principal": "elasticloadbalancing.amazonaws.com" + } + }, + "ALBGreetingALBTargetGroup": { + "Type": "AWS::ElasticLoadBalancingV2::TargetGroup", + "Metadata": { + "Tool": "Amazon.Lambda.Annotations" + }, + "DependsOn": "ALBGreetingALBPermission", + "Properties": { + "TargetType": "lambda", + "Targets": [ + { + "Id": { + "Fn::GetAtt": [ + "ALBGreeting", + "Arn" + ] + } + } + ] + } + }, + "ALBGreetingALBListenerRule": { + "Type": "AWS::ElasticLoadBalancingV2::ListenerRule", + "Metadata": { + "Tool": "Amazon.Lambda.Annotations" + }, + "Properties": { + "Priority": 3, + "Conditions": [ + { + "Field": "path-pattern", + "PathPatternConfig": { + "Values": [ + "/greeting" + ] + } + } + ], + "Actions": [ + { + "Type": "forward", + "TargetGroupArn": { + "Ref": "ALBGreetingALBTargetGroup" + } + } + ], + "ListenerArn": { + "Ref": "ALBTestListener" + } + } + }, + "ALBCreateItem": { + "Type": "AWS::Serverless::Function", + "Metadata": { + "Tool": "Amazon.Lambda.Annotations", + "SyncedAlbResources": [ + "ALBCreateItemALBPermission", + "ALBCreateItemALBTargetGroup", + "ALBCreateItemALBListenerRule" + ] + }, + "Properties": { + "Runtime": "dotnet6", + "CodeUri": ".", + "MemorySize": 256, + "Timeout": 15, + "Policies": [ + "AWSLambdaBasicExecutionRole" + ], + "PackageType": "Zip", + "Handler": "TestServerlessApp.ALB::TestServerlessApp.ALB.ALBFunctions_CreateItem_Generated::CreateItem" + } + }, + "ALBCreateItemALBPermission": { + "Type": "AWS::Lambda::Permission", + "Metadata": { + "Tool": "Amazon.Lambda.Annotations" + }, + "Properties": { + "FunctionName": { + "Fn::GetAtt": [ + "ALBCreateItem", + "Arn" + ] + }, + "Action": "lambda:InvokeFunction", + "Principal": "elasticloadbalancing.amazonaws.com" + } + }, + "ALBCreateItemALBTargetGroup": { + "Type": "AWS::ElasticLoadBalancingV2::TargetGroup", + "Metadata": { + "Tool": "Amazon.Lambda.Annotations" + }, + "DependsOn": "ALBCreateItemALBPermission", + "Properties": { + "TargetType": "lambda", + "Targets": [ + { + "Id": { + "Fn::GetAtt": [ + "ALBCreateItem", + "Arn" + ] + } + } + ] + } + }, + "ALBCreateItemALBListenerRule": { + "Type": "AWS::ElasticLoadBalancingV2::ListenerRule", + "Metadata": { + "Tool": "Amazon.Lambda.Annotations" + }, + "Properties": { + "Priority": 4, + "Conditions": [ + { + "Field": "path-pattern", + "PathPatternConfig": { + "Values": [ + "/items" + ] + } + }, + { + "Field": "http-request-method", + "HttpRequestMethodConfig": { + "Values": [ + "POST" + ] + } + } + ], + "Actions": [ + { + "Type": "forward", + "TargetGroupArn": { + "Ref": "ALBCreateItemALBTargetGroup" + } + } + ], + "ListenerArn": { + "Ref": "ALBTestListener" + } + } + } + }, + "Outputs": { + "ALBDnsName": { + "Value": { + "Fn::GetAtt": [ + "ALBTestLoadBalancer", + "DNSName" + ] + }, + "Description": "ALB DNS Name for integration testing" + } + } +} \ No newline at end of file diff --git a/Libraries/test/TestServerlessApp.IntegrationTests/DeploymentScript.ps1 b/Libraries/test/TestServerlessApp.IntegrationTests/DeploymentScript.ps1 index 7a5cd5644..c8a2147d1 100644 --- a/Libraries/test/TestServerlessApp.IntegrationTests/DeploymentScript.ps1 +++ b/Libraries/test/TestServerlessApp.IntegrationTests/DeploymentScript.ps1 @@ -68,6 +68,24 @@ try Write-Host "Added TestQueue resource to serverless.template" } + # Add TestS3Bucket resource to serverless.template for S3 event integration testing + # The source generator creates a Ref to TestS3Bucket but doesn't define the resource itself + $template = Get-Content $templatePath | Out-String | ConvertFrom-Json + if (-not $template.Resources.PSObject.Properties['TestS3Bucket']) { + $template.Resources | Add-Member -NotePropertyName "TestS3Bucket" -NotePropertyValue @{ Type = "AWS::S3::Bucket" } -Force + $template | ConvertTo-Json -Depth 100 | Set-Content $templatePath + Write-Host "Added TestS3Bucket resource to serverless.template" + } + + # Add TestTopic resource to serverless.template for SNS event integration testing + # The source generator creates a Ref to TestTopic but doesn't define the resource itself + $template = Get-Content $templatePath | Out-String | ConvertFrom-Json + if (-not $template.Resources.PSObject.Properties['TestTopic']) { + $template.Resources | Add-Member -NotePropertyName "TestTopic" -NotePropertyValue @{ Type = "AWS::SNS::Topic" } -Force + $template | ConvertTo-Json -Depth 100 | Set-Content $templatePath + Write-Host "Added TestTopic resource to serverless.template" + } + dotnet restore Write-Host "Creating CloudFormation Stack $identifier, Architecture $arch, Runtime $runtime" dotnet lambda deploy-serverless --template-parameters "ArchitectureTypeParameter=$arch" diff --git a/Libraries/test/TestServerlessApp.IntegrationTests/FunctionUrlExample.cs b/Libraries/test/TestServerlessApp.IntegrationTests/FunctionUrlExample.cs new file mode 100644 index 000000000..b3f97929b --- /dev/null +++ b/Libraries/test/TestServerlessApp.IntegrationTests/FunctionUrlExample.cs @@ -0,0 +1,103 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +using System; +using System.Linq; +using System.Net.Http; +using System.Threading.Tasks; +using Newtonsoft.Json.Linq; +using Xunit; + +namespace TestServerlessApp.IntegrationTests +{ + [Collection("Integration Tests")] + public class FunctionUrlExample + { + private readonly IntegrationTestContextFixture _fixture; + + public FunctionUrlExample(IntegrationTestContextFixture fixture) + { + _fixture = fixture; + } + + [Fact] + public async Task GetItems_WithCategory_ReturnsOkWithItems() + { + Assert.False(string.IsNullOrEmpty(_fixture.FunctionUrlPrefix), "FunctionUrlPrefix should not be empty. The Function URL was not discovered during setup."); + + var response = await GetWithRetryAsync($"{_fixture.FunctionUrlPrefix}?category=electronics"); + response.EnsureSuccessStatusCode(); + + var content = await response.Content.ReadAsStringAsync(); + var json = JObject.Parse(content); + + Assert.Equal("electronics", json["category"]?.ToString()); + Assert.NotNull(json["items"]); + var items = json["items"].ToObject(); + Assert.Equal(2, items.Length); + Assert.Contains("item1", items); + Assert.Contains("item2", items); + } + + [Fact] + public async Task GetItems_LogsToCloudWatch() + { + Assert.False(string.IsNullOrEmpty(_fixture.FunctionUrlPrefix), "FunctionUrlPrefix should not be empty. The Function URL was not discovered during setup."); + + var response = await GetWithRetryAsync($"{_fixture.FunctionUrlPrefix}?category=books"); + response.EnsureSuccessStatusCode(); + + var lambdaFunctionName = _fixture.LambdaFunctions + .FirstOrDefault(x => string.Equals(x.LogicalId, "TestServerlessAppFunctionUrlExampleGetItemsGenerated"))?.Name; + Assert.False(string.IsNullOrEmpty(lambdaFunctionName)); + + var logGroupName = _fixture.CloudWatchHelper.GetLogGroupName(lambdaFunctionName); + Assert.True( + await _fixture.CloudWatchHelper.MessageExistsInRecentLogEventsAsync("Getting items for category: books", logGroupName, logGroupName), + "Expected log message not found in CloudWatch logs"); + } + + [Fact] + public async Task VerifyFunctionUrlConfig_HasNoneAuthType() + { + var lambdaFunctionName = _fixture.LambdaFunctions + .FirstOrDefault(x => string.Equals(x.LogicalId, "TestServerlessAppFunctionUrlExampleGetItemsGenerated"))?.Name; + Assert.False(string.IsNullOrEmpty(lambdaFunctionName)); + + var functionUrlConfig = await _fixture.LambdaHelper.GetFunctionUrlConfigAsync(lambdaFunctionName); + Assert.NotNull(functionUrlConfig); + Assert.Equal("NONE", functionUrlConfig.AuthType.Value); + Assert.False(string.IsNullOrEmpty(functionUrlConfig.FunctionUrl), "Function URL should not be empty"); + Assert.Contains(".lambda-url.", functionUrlConfig.FunctionUrl); + } + + private async Task GetWithRetryAsync(string url) + { + const int maxAttempts = 10; + HttpResponseMessage response = null; + + for (var attempt = 0; attempt < maxAttempts; attempt++) + { + await Task.Delay(attempt * 1000); + try + { + response = await _fixture.HttpClient.GetAsync(url); + + // If we get a 403 Forbidden, it may be an eventual consistency issue + // with the Function URL permissions propagating. + if (response.StatusCode == System.Net.HttpStatusCode.Forbidden) + continue; + + break; + } + catch + { + if (attempt + 1 == maxAttempts) + throw; + } + } + + return response; + } + } +} diff --git a/Libraries/test/TestServerlessApp.IntegrationTests/IntegrationTestContextFixture.cs b/Libraries/test/TestServerlessApp.IntegrationTests/IntegrationTestContextFixture.cs index fb689cd7c..d2c1817cd 100644 --- a/Libraries/test/TestServerlessApp.IntegrationTests/IntegrationTestContextFixture.cs +++ b/Libraries/test/TestServerlessApp.IntegrationTests/IntegrationTestContextFixture.cs @@ -1,3 +1,6 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + using System; using System.Collections.Generic; using System.IO; @@ -25,17 +28,22 @@ public class IntegrationTestContextFixture : IAsyncLifetime public readonly LambdaHelper LambdaHelper; public readonly CloudWatchHelper CloudWatchHelper; + public readonly S3Helper S3HelperInstance; public readonly HttpClient HttpClient; public string RestApiUrlPrefix; public string HttpApiUrlPrefix; + public string FunctionUrlPrefix; + public string TestTopicARN; public string TestQueueARN; + public string TestS3BucketName; public List LambdaFunctions; public IntegrationTestContextFixture() { _cloudFormationHelper = new CloudFormationHelper(new AmazonCloudFormationClient(Amazon.RegionEndpoint.USWest2)); _s3Helper = new S3Helper(new AmazonS3Client(Amazon.RegionEndpoint.USWest2)); + S3HelperInstance = _s3Helper; LambdaHelper = new LambdaHelper(new AmazonLambdaClient(Amazon.RegionEndpoint.USWest2)); CloudWatchHelper = new CloudWatchHelper(new AmazonCloudWatchLogsClient(Amazon.RegionEndpoint.USWest2)); HttpClient = new HttpClient(); @@ -77,16 +85,36 @@ public async Task InitializeAsync() Console.WriteLine($"[IntegrationTest] TestQueue URL: {queueUrl}"); Assert.False(string.IsNullOrEmpty(queueUrl), $"CloudFormation resource 'TestQueue' was not found in stack '{_stackName}'."); TestQueueARN = ConvertSqsUrlToArn(queueUrl); + + // Get the SNS test topic ARN (physical ID is the ARN for SNS topics) + TestTopicARN = await _cloudFormationHelper.GetResourcePhysicalIdAsync(_stackName, "TestTopic"); + Console.WriteLine($"[IntegrationTest] TestTopic ARN: {TestTopicARN}"); + + // Get the S3 bucket name from the physical resource ID + TestS3BucketName = await _cloudFormationHelper.GetResourcePhysicalIdAsync(_stackName, "TestS3Bucket"); + Console.WriteLine($"[IntegrationTest] TestS3Bucket: {TestS3BucketName}"); + Assert.False(string.IsNullOrEmpty(TestS3BucketName), $"CloudFormation resource 'TestS3Bucket' was not found in stack '{_stackName}'."); + LambdaFunctions = await LambdaHelper.FilterByCloudFormationStackAsync(_stackName); Console.WriteLine($"[IntegrationTest] Found {LambdaFunctions.Count} Lambda functions: {string.Join(", ", LambdaFunctions.Select(f => f.Name ?? "(null)"))}"); Assert.True(await _s3Helper.BucketExistsAsync(_bucketName), $"S3 bucket {_bucketName} should exist"); - Assert.Equal(36, LambdaFunctions.Count); + Assert.Equal(39, LambdaFunctions.Count); Assert.False(string.IsNullOrEmpty(RestApiUrlPrefix), "RestApiUrlPrefix should not be empty"); Assert.False(string.IsNullOrEmpty(HttpApiUrlPrefix), "HttpApiUrlPrefix should not be empty"); await LambdaHelper.WaitTillNotPending(LambdaFunctions.Where(x => x.Name != null).Select(x => x.Name).ToList()); + // Discover the Function URL for the FunctionUrlExample function + var functionUrlLambdaName = LambdaFunctions + .FirstOrDefault(x => string.Equals(x.LogicalId, "TestServerlessAppFunctionUrlExampleGetItemsGenerated"))?.Name; + if (!string.IsNullOrEmpty(functionUrlLambdaName)) + { + var functionUrlConfig = await LambdaHelper.GetFunctionUrlConfigAsync(functionUrlLambdaName); + FunctionUrlPrefix = functionUrlConfig.FunctionUrl.TrimEnd('/'); + Console.WriteLine($"[IntegrationTest] FunctionUrlPrefix: {FunctionUrlPrefix}"); + } + // Wait an additional 10 seconds for any other eventually consistency state to finish up. await Task.Delay(10000); } diff --git a/Libraries/test/TestServerlessApp.IntegrationTests/S3EventNotification.cs b/Libraries/test/TestServerlessApp.IntegrationTests/S3EventNotification.cs new file mode 100644 index 000000000..d9758ae00 --- /dev/null +++ b/Libraries/test/TestServerlessApp.IntegrationTests/S3EventNotification.cs @@ -0,0 +1,53 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +using System.Linq; +using System.Threading.Tasks; +using Amazon.S3; +using Xunit; + +namespace TestServerlessApp.IntegrationTests +{ + [Collection("Integration Tests")] + public class S3EventNotification + { + private readonly IntegrationTestContextFixture _fixture; + + public S3EventNotification(IntegrationTestContextFixture fixture) + { + _fixture = fixture; + } + + [Fact] + public async Task VerifyS3EventNotificationConfiguration() + { + // Verify the Lambda function exists in the stack + var lambdaFunction = _fixture.LambdaFunctions + .FirstOrDefault(x => string.Equals(x.LogicalId, "S3EventHandler")); + Assert.NotNull(lambdaFunction); + Assert.NotNull(lambdaFunction.Name); + + // Verify S3 bucket notification is configured correctly + var notificationConfig = await _fixture.S3HelperInstance + .GetBucketNotificationAsync(_fixture.TestS3BucketName); + + var lambdaConfigs = notificationConfig.LambdaFunctionConfigurations; + Assert.Single(lambdaConfigs); + + var config = lambdaConfigs.First(); + + // Verify the notification points to the correct Lambda function ARN + Assert.Contains(lambdaFunction.Name, config.FunctionArn); + + // Verify the event type is s3:ObjectCreated:* + Assert.Single(config.Events); + Assert.Equal(EventType.ObjectCreatedAll, config.Events.First()); + + // Verify the suffix filter is .json + var filterRules = config.Filter.S3KeyFilter.FilterRules; + Assert.Single(filterRules); + var suffixRule = filterRules.First(r => string.Equals(r.Name, "suffix", System.StringComparison.OrdinalIgnoreCase)); + Assert.Equal(".json", suffixRule.Value); + } + } +} diff --git a/Libraries/test/TestServerlessApp.IntegrationTests/SNSEventSubscription.cs b/Libraries/test/TestServerlessApp.IntegrationTests/SNSEventSubscription.cs new file mode 100644 index 000000000..075a5162b --- /dev/null +++ b/Libraries/test/TestServerlessApp.IntegrationTests/SNSEventSubscription.cs @@ -0,0 +1,44 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +using System.Linq; +using System.Threading.Tasks; +using Amazon.SimpleNotificationService; +using Xunit; + +namespace TestServerlessApp.IntegrationTests +{ + [Collection("Integration Tests")] + public class SNSEventSubscription + { + private readonly IntegrationTestContextFixture _fixture; + + public SNSEventSubscription(IntegrationTestContextFixture fixture) + { + _fixture = fixture; + } + + [Fact] + public async Task VerifySNSSubscriptionConfiguration() + { + var lambdaFunctionName = _fixture.LambdaFunctions.FirstOrDefault(x => string.Equals(x.LogicalId, "SNSMessageHandler"))?.Name; + Assert.NotNull(lambdaFunctionName); + + var testTopicArn = _fixture.TestTopicARN; + Assert.False(string.IsNullOrEmpty(testTopicArn), "TestTopic ARN should not be empty"); + + var snsClient = new AmazonSimpleNotificationServiceClient(Amazon.RegionEndpoint.USWest2); + var subscriptions = await snsClient.ListSubscriptionsByTopicAsync(testTopicArn); + + // Find the Lambda subscription + var lambdaSub = subscriptions.Subscriptions.FirstOrDefault(s => + s.Protocol == "lambda" && s.Endpoint.Contains(lambdaFunctionName)); + Assert.NotNull(lambdaSub); + + // Verify filter policy + var attrs = await snsClient.GetSubscriptionAttributesAsync(lambdaSub.SubscriptionArn); + Assert.True(attrs.Attributes.ContainsKey("FilterPolicy")); + Assert.Contains("example_corp", attrs.Attributes["FilterPolicy"]); + } + } +} diff --git a/Libraries/test/TestServerlessApp.IntegrationTests/TestServerlessApp.IntegrationTests.csproj b/Libraries/test/TestServerlessApp.IntegrationTests/TestServerlessApp.IntegrationTests.csproj index 5f0728358..38aa5bd22 100644 --- a/Libraries/test/TestServerlessApp.IntegrationTests/TestServerlessApp.IntegrationTests.csproj +++ b/Libraries/test/TestServerlessApp.IntegrationTests/TestServerlessApp.IntegrationTests.csproj @@ -8,6 +8,7 @@ + diff --git a/Libraries/test/TestServerlessApp.NET8/serverless.template b/Libraries/test/TestServerlessApp.NET8/serverless.template index c139ace2b..ee3617fb0 100644 --- a/Libraries/test/TestServerlessApp.NET8/serverless.template +++ b/Libraries/test/TestServerlessApp.NET8/serverless.template @@ -1,7 +1,7 @@ { "AWSTemplateFormatVersion": "2010-09-09", "Transform": "AWS::Serverless-2016-10-31", - "Description": "This template is partially managed by Amazon.Lambda.Annotations (v1.10.0.0).", + "Description": "This template is partially managed by Amazon.Lambda.Annotations (v1.14.0.0).", "Resources": { "TestServerlessAppNET8FunctionsToUpperGenerated": { "Type": "AWS::Serverless::Function", diff --git a/Libraries/test/TestServerlessApp/ALBEventExamples/ValidALBEvents.cs.txt b/Libraries/test/TestServerlessApp/ALBEventExamples/ValidALBEvents.cs.txt new file mode 100644 index 000000000..1becbac0e --- /dev/null +++ b/Libraries/test/TestServerlessApp/ALBEventExamples/ValidALBEvents.cs.txt @@ -0,0 +1,42 @@ +using Amazon.Lambda.Annotations; +using Amazon.Lambda.Annotations.ALB; +using Amazon.Lambda.ApplicationLoadBalancerEvents; +using Amazon.Lambda.Core; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace TestServerlessApp.ALBEventExamples +{ + // This file represents valid usage of the ALBApiAttribute. This is added as .txt file since we do not want to deploy these functions during our integration tests. + // This file is only sent as input to the source generator unit tests. + // Refer to VerifyValidALBEvents unit test. + + public class ValidALBEvents + { + [LambdaFunction(ResourceName = "ALBHelloWorld")] + [ALBApi("arn:aws:elasticloadbalancing:us-east-1:123456789012:listener/app/my-alb/abc/def", "/hello", 1)] + public ApplicationLoadBalancerResponse Hello(ApplicationLoadBalancerRequest request, ILambdaContext context) + { + return new ApplicationLoadBalancerResponse + { + StatusCode = 200, + StatusDescription = "200 OK", + Headers = new Dictionary { { "Content-Type", "text/plain" } }, + Body = "Hello from ALB Lambda!" + }; + } + + [LambdaFunction(ResourceName = "ALBWithOptions")] + [ALBApi("@MyALBListener", "/api/*", 5, MultiValueHeaders = true, HostHeader = "api.example.com", HttpMethod = "POST")] + public async Task HandleRequest(ApplicationLoadBalancerRequest request, ILambdaContext context) + { + await Task.CompletedTask; + return new ApplicationLoadBalancerResponse + { + StatusCode = 200, + StatusDescription = "200 OK", + Body = "OK" + }; + } + } +} diff --git a/Libraries/test/TestServerlessApp/FunctionUrlExample.cs b/Libraries/test/TestServerlessApp/FunctionUrlExample.cs new file mode 100644 index 000000000..4909c768e --- /dev/null +++ b/Libraries/test/TestServerlessApp/FunctionUrlExample.cs @@ -0,0 +1,20 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +using Amazon.Lambda.Annotations; +using Amazon.Lambda.Annotations.APIGateway; +using Amazon.Lambda.Core; + +namespace TestServerlessApp +{ + public class FunctionUrlExample + { + [LambdaFunction(PackageType = LambdaPackageType.Image)] + [FunctionUrl(AuthType = FunctionUrlAuthType.NONE)] + public IHttpResult GetItems([FromQuery] string category, ILambdaContext context) + { + context.Logger.LogLine($"Getting items for category: {category}"); + return HttpResults.Ok(new { items = new[] { "item1", "item2" }, category }); + } + } +} diff --git a/Libraries/test/TestServerlessApp/S3EventExamples/S3EventProcessing.cs b/Libraries/test/TestServerlessApp/S3EventExamples/S3EventProcessing.cs new file mode 100644 index 000000000..0ee914c76 --- /dev/null +++ b/Libraries/test/TestServerlessApp/S3EventExamples/S3EventProcessing.cs @@ -0,0 +1,21 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +using Amazon.Lambda.Annotations; +using Amazon.Lambda.Annotations.S3; +using Amazon.Lambda.Core; +using Amazon.Lambda.S3Events; +using System; + +namespace TestServerlessApp.S3EventExamples +{ + public class S3EventProcessing + { + [LambdaFunction(ResourceName = "S3EventHandler", Policies = "AWSLambdaBasicExecutionRole,AmazonS3ReadOnlyAccess", PackageType = LambdaPackageType.Image)] + [S3Event("@TestS3Bucket", Events = "s3:ObjectCreated:*", FilterSuffix = ".json")] + public void ProcessS3Event(S3Event evnt) + { + Console.WriteLine($"Received S3 event with {evnt.Records.Count} records"); + } + } +} diff --git a/Libraries/test/TestServerlessApp/S3EventExamples/ValidS3Events.cs.txt b/Libraries/test/TestServerlessApp/S3EventExamples/ValidS3Events.cs.txt new file mode 100644 index 000000000..9a5da1f8e --- /dev/null +++ b/Libraries/test/TestServerlessApp/S3EventExamples/ValidS3Events.cs.txt @@ -0,0 +1,38 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +using Amazon.Lambda.Annotations; +using Amazon.Lambda.Annotations.S3; +using Amazon.Lambda.S3Events; +using System; +using System.Threading.Tasks; + +namespace TestServerlessApp.S3EventExamples +{ + // This file represents valid usage of the S3EventAttribute. This is added as .txt file since we do not want to deploy these functions during our integration tests. + // This file is only sent as input to the source generator unit tests. + + public class ValidS3Events + { + [LambdaFunction(PackageType = LambdaPackageType.Image)] + [S3Event("@MyBucket")] + public void ProcessS3Event(S3Event evnt) + { + Console.WriteLine($"Event processed: {evnt}"); + } + + [LambdaFunction(PackageType = LambdaPackageType.Image)] + [S3Event("@MyBucket", Events = "s3:ObjectCreated:*;s3:ObjectRemoved:*", FilterPrefix = "uploads/", FilterSuffix = ".jpg")] + public async Task ProcessS3EventWithFilters(S3Event evnt) + { + await Console.Out.WriteLineAsync($"Event processed: {evnt}"); + } + + [LambdaFunction(PackageType = LambdaPackageType.Image)] + [S3Event("@ImageBucket", ResourceName = "ImageBucketEvent", Enabled = false)] + public void ProcessS3EventDisabled(S3Event evnt) + { + Console.WriteLine($"Event processed: {evnt}"); + } + } +} diff --git a/Libraries/test/TestServerlessApp/SNSEventExamples/ValidSNSEvents.cs.txt b/Libraries/test/TestServerlessApp/SNSEventExamples/ValidSNSEvents.cs.txt new file mode 100644 index 000000000..9c42ebc70 --- /dev/null +++ b/Libraries/test/TestServerlessApp/SNSEventExamples/ValidSNSEvents.cs.txt @@ -0,0 +1,32 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +using Amazon.Lambda.Annotations; +using Amazon.Lambda.Annotations.SNS; +using Amazon.Lambda.SNSEvents; +using System; +using System.Threading.Tasks; + +namespace TestServerlessApp.SNSEventExamples +{ + // This file represents valid usage of the SNSEventAttribute. This is added as .txt file since we do not want to deploy these functions during our integration tests. + // This file is only sent as input to the source generator unit tests. + + public class ValidSNSEvents + { + [LambdaFunction(PackageType = LambdaPackageType.Image)] + [SNSEvent("arn:aws:sns:us-east-2:444455556666:MyTopic", FilterPolicy = "{ \"store\": [\"example_corp\"] }")] + [SNSEvent("@testTopic", ResourceName = "testTopicEvent")] + public void ProcessMessages(SNSEvent evnt) + { + Console.WriteLine($"Event processed: {evnt}"); + } + + [LambdaFunction(PackageType = LambdaPackageType.Image)] + [SNSEvent("arn:aws:sns:us-east-2:444455556666:MyTopic")] + public async Task ProcessMessagesAsync(SNSEvent evnt) + { + await Console.Out.WriteLineAsync($"Event processed: {evnt}"); + } + } +} diff --git a/Libraries/test/TestServerlessApp/SnsMessageProcessing.cs b/Libraries/test/TestServerlessApp/SnsMessageProcessing.cs new file mode 100644 index 000000000..91004a5b9 --- /dev/null +++ b/Libraries/test/TestServerlessApp/SnsMessageProcessing.cs @@ -0,0 +1,20 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +using Amazon.Lambda.Annotations; +using Amazon.Lambda.Annotations.SNS; +using Amazon.Lambda.Core; +using Amazon.Lambda.SNSEvents; + +namespace TestServerlessApp +{ + public class SnsMessageProcessing + { + [LambdaFunction(ResourceName = "SNSMessageHandler", Policies = "AWSLambdaBasicExecutionRole", PackageType = LambdaPackageType.Image)] + [SNSEvent("@TestTopic", ResourceName = "TestTopicEvent", FilterPolicy = "{ \"store\": [\"example_corp\"] }")] + public void HandleMessage(SNSEvent evnt, ILambdaContext lambdaContext) + { + lambdaContext.Logger.Log($"Received {evnt.Records.Count} messages"); + } + } +} diff --git a/Libraries/test/TestServerlessApp/TestServerlessApp.csproj b/Libraries/test/TestServerlessApp/TestServerlessApp.csproj index 921e3d372..9875acee9 100644 --- a/Libraries/test/TestServerlessApp/TestServerlessApp.csproj +++ b/Libraries/test/TestServerlessApp/TestServerlessApp.csproj @@ -27,6 +27,8 @@ + + diff --git a/Libraries/test/TestServerlessApp/aws-lambda-tools-defaults.json b/Libraries/test/TestServerlessApp/aws-lambda-tools-defaults.json index 0b96350ff..03fe9926f 100644 --- a/Libraries/test/TestServerlessApp/aws-lambda-tools-defaults.json +++ b/Libraries/test/TestServerlessApp/aws-lambda-tools-defaults.json @@ -13,7 +13,7 @@ "template": "serverless.template", "template-parameters": "", "docker-host-build-output-dir": "./bin/Release/lambda-publish", - "s3-bucket": "test-serverless-app", - "stack-name": "test-serverless-app", - "function-architecture": "x86_64" -} \ No newline at end of file +"s3-bucket" : "test-serverless-app-784dfb1d", +"stack-name" : "test-serverless-app-784dfb1d", +"function-architecture" : "x86_64" +} diff --git a/Libraries/test/TestServerlessApp/serverless.template b/Libraries/test/TestServerlessApp/serverless.template index a0bf929eb..65fcfee31 100644 --- a/Libraries/test/TestServerlessApp/serverless.template +++ b/Libraries/test/TestServerlessApp/serverless.template @@ -1,11 +1,8 @@ { "AWSTemplateFormatVersion": "2010-09-09", "Transform": "AWS::Serverless-2016-10-31", - "Description": "This template is partially managed by Amazon.Lambda.Annotations (v1.10.0.0).", + "Description": "This template is partially managed by Amazon.Lambda.Annotations (v1.14.0.0).", "Resources": { - "TestQueue": { - "Type": "AWS::SQS::Queue" - }, "AnnotationsHttpApi": { "Type": "AWS::Serverless::HttpApi", "Metadata": { @@ -801,6 +798,30 @@ } } }, + "TestServerlessAppFunctionUrlExampleGetItemsGenerated": { + "Type": "AWS::Serverless::Function", + "Metadata": { + "Tool": "Amazon.Lambda.Annotations", + "SyncedFunctionUrlConfig": true + }, + "Properties": { + "MemorySize": 512, + "Timeout": 30, + "Policies": [ + "AWSLambdaBasicExecutionRole" + ], + "PackageType": "Image", + "ImageUri": ".", + "ImageConfig": { + "Command": [ + "TestServerlessApp::TestServerlessApp.FunctionUrlExample_GetItems_Generated::GetItems" + ] + }, + "FunctionUrlConfig": { + "AuthType": "NONE" + } + } + }, "GreeterSayHello": { "Type": "AWS::Serverless::Function", "Metadata": { @@ -991,6 +1012,60 @@ } } }, + "S3EventHandler": { + "Type": "AWS::Serverless::Function", + "Metadata": { + "Tool": "Amazon.Lambda.Annotations", + "SyncedEvents": [ + "TestS3Bucket" + ], + "SyncedEventProperties": { + "TestS3Bucket": [ + "Bucket.Ref", + "Events", + "Filter.S3Key.Rules" + ] + } + }, + "Properties": { + "MemorySize": 512, + "Timeout": 30, + "Policies": [ + "AWSLambdaBasicExecutionRole", + "AmazonS3ReadOnlyAccess" + ], + "PackageType": "Image", + "ImageUri": ".", + "ImageConfig": { + "Command": [ + "TestServerlessApp::TestServerlessApp.S3EventExamples.S3EventProcessing_ProcessS3Event_Generated::ProcessS3Event" + ] + }, + "Events": { + "TestS3Bucket": { + "Type": "S3", + "Properties": { + "Events": [ + "s3:ObjectCreated:*" + ], + "Filter": { + "S3Key": { + "Rules": [ + { + "Name": "suffix", + "Value": ".json" + } + ] + } + }, + "Bucket": { + "Ref": "TestS3Bucket" + } + } + } + } + } + }, "SimpleCalculatorAdd": { "Type": "AWS::Serverless::Function", "Metadata": { @@ -1339,6 +1414,55 @@ ] } } + }, + "SNSMessageHandler": { + "Type": "AWS::Serverless::Function", + "Metadata": { + "Tool": "Amazon.Lambda.Annotations", + "SyncedEvents": [ + "TestTopicEvent" + ], + "SyncedEventProperties": { + "TestTopicEvent": [ + "Topic.Ref", + "FilterPolicy" + ] + } + }, + "Properties": { + "MemorySize": 512, + "Timeout": 30, + "Policies": [ + "AWSLambdaBasicExecutionRole" + ], + "PackageType": "Image", + "Events": { + "TestTopicEvent": { + "Type": "SNS", + "Properties": { + "FilterPolicy": "{ \"store\": [\"example_corp\"] }", + "Topic": { + "Ref": "TestTopic" + } + } + } + }, + "ImageUri": ".", + "ImageConfig": { + "Command": [ + "TestServerlessApp::TestServerlessApp.SnsMessageProcessing_HandleMessage_Generated::HandleMessage" + ] + } + } + }, + "TestQueue": { + "Type": "AWS::SQS::Queue" + }, + "TestS3Bucket": { + "Type": "AWS::S3::Bucket" + }, + "TestTopic": { + "Type": "AWS::SNS::Topic" } } } \ No newline at end of file diff --git a/Libraries/test/TestWebApp/TestWebApp.csproj b/Libraries/test/TestWebApp/TestWebApp.csproj index e5607beb2..37a8b67fa 100644 --- a/Libraries/test/TestWebApp/TestWebApp.csproj +++ b/Libraries/test/TestWebApp/TestWebApp.csproj @@ -1,7 +1,7 @@  - net6.0;net8.0 + net8.0;net10.0 true TestWebApp Exe diff --git a/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.IntegrationTests/BaseApiGatewayTest.cs b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.IntegrationTests/BaseApiGatewayTest.cs index f277ffa2a..c9998141c 100644 --- a/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.IntegrationTests/BaseApiGatewayTest.cs +++ b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.IntegrationTests/BaseApiGatewayTest.cs @@ -48,6 +48,7 @@ protected async Task CleanupAsync() CancellationTokenSource.Dispose(); CancellationTokenSource = new CancellationTokenSource(); } + Environment.SetEnvironmentVariable("APIGATEWAY_EMULATOR_ROUTE_CONFIG", null); } protected async Task StartTestToolProcessAsync(ApiGatewayEmulatorMode apiGatewayMode, string routeName, int lambdaPort, int apiGatewayPort, CancellationTokenSource cancellationTokenSource, string httpMethod = "POST") diff --git a/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.IntegrationTests/SQSEventSourceTests.cs b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.IntegrationTests/SQSEventSourceTests.cs index fc4aa1882..f02ee3abe 100644 --- a/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.IntegrationTests/SQSEventSourceTests.cs +++ b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.IntegrationTests/SQSEventSourceTests.cs @@ -183,7 +183,7 @@ public async Task ProcessMessagesFromMultipleEventSources() await sqsClient.SendMessageAsync(queueUrl2, "MessageFromQueue2"); var startTime = DateTime.UtcNow; - while (listOfProcessedMessages.Count == 0 && DateTime.UtcNow < startTime.AddMinutes(2)) + while (listOfProcessedMessages.Count < 2 && DateTime.UtcNow < startTime.AddMinutes(2)) { Assert.False(lambdaTask.IsFaulted, "Lambda function failed: " + lambdaTask.Exception?.ToString()); await Task.Delay(500); diff --git a/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.Tests.Common/Helpers/TestHelpers.cs b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.Tests.Common/Helpers/TestHelpers.cs index 79c618d48..9def24fe2 100644 --- a/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.Tests.Common/Helpers/TestHelpers.cs +++ b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.Tests.Common/Helpers/TestHelpers.cs @@ -1,6 +1,9 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 +using System.Net; +using System.Net.Sockets; + namespace Amazon.Lambda.TestTool.Tests.Common.Helpers; public static class TestHelpers @@ -39,16 +42,22 @@ public static async Task SendRequest(string url) } } - private static int _maxLambdaRuntimePort = 6000; - private static int _maxApiGatewayPort = 9000; + private static int GetFreePort() + { + var listener = new TcpListener(IPAddress.Loopback, 0); + listener.Start(); + var port = ((IPEndPoint)listener.LocalEndpoint).Port; + listener.Stop(); + return port; + } public static int GetNextLambdaRuntimePort() { - return Interlocked.Increment(ref _maxLambdaRuntimePort); + return GetFreePort(); } public static int GetNextApiGatewayPort() { - return Interlocked.Increment(ref _maxApiGatewayPort); + return GetFreePort(); } } diff --git a/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/RuntimeApiTests.cs b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/RuntimeApiTests.cs index 998460411..cb2afa01a 100644 --- a/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/RuntimeApiTests.cs +++ b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/RuntimeApiTests.cs @@ -39,6 +39,8 @@ public async Task AddEventToDataStore() var testToolProcess = TestToolProcess.Startup(options, cancellationTokenSource.Token); try { + Assert.True(await TestHelpers.WaitForApiToStartAsync($"{testToolProcess.ServiceUrl}/lambda-runtime-api/healthcheck")); + var lambdaClient = ConstructLambdaServiceClient(testToolProcess.ServiceUrl); var invokeFunction = new InvokeRequest { @@ -92,6 +94,8 @@ public async Task InvokeRequestResponse() var testToolProcess = TestToolProcess.Startup(options, cancellationTokenSource.Token); try { + Assert.True(await TestHelpers.WaitForApiToStartAsync($"{testToolProcess.ServiceUrl}/lambda-runtime-api/healthcheck")); + var handler = (string input, ILambdaContext context) => { Thread.Sleep(1000); // Add a sleep to prove the LambdaRuntimeApi waited for the completion. diff --git a/Tools/LambdaTestTool/src/Amazon.Lambda.TestTool.BlazorTester/Amazon.Lambda.TestTool.BlazorTester.csproj b/Tools/LambdaTestTool/src/Amazon.Lambda.TestTool.BlazorTester/Amazon.Lambda.TestTool.BlazorTester.csproj index 878c49507..345380f68 100644 --- a/Tools/LambdaTestTool/src/Amazon.Lambda.TestTool.BlazorTester/Amazon.Lambda.TestTool.BlazorTester.csproj +++ b/Tools/LambdaTestTool/src/Amazon.Lambda.TestTool.BlazorTester/Amazon.Lambda.TestTool.BlazorTester.csproj @@ -6,7 +6,7 @@ Exe A tool to help debug and test your .NET Core AWS Lambda functions locally. Latest - 0.17.0 + 0.17.1 AWS .NET Lambda Test Tool Apache 2 AWS;Amazon;Lambda diff --git a/Tools/LambdaTestTool/src/Amazon.Lambda.TestTool.BlazorTester/Amazon.Lambda.TestTool.BlazorTester10_0-pack.csproj b/Tools/LambdaTestTool/src/Amazon.Lambda.TestTool.BlazorTester/Amazon.Lambda.TestTool.BlazorTester10_0-pack.csproj index f18756bce..55fbe0b02 100644 --- a/Tools/LambdaTestTool/src/Amazon.Lambda.TestTool.BlazorTester/Amazon.Lambda.TestTool.BlazorTester10_0-pack.csproj +++ b/Tools/LambdaTestTool/src/Amazon.Lambda.TestTool.BlazorTester/Amazon.Lambda.TestTool.BlazorTester10_0-pack.csproj @@ -5,7 +5,7 @@ Exe A tool to help debug and test your .NET 10.0 AWS Lambda functions locally. - 0.17.0 + 0.17.1 AWS .NET Lambda Test Tool Apache 2 AWS;Amazon;Lambda diff --git a/Tools/LambdaTestTool/src/Amazon.Lambda.TestTool.BlazorTester/Amazon.Lambda.TestTool.BlazorTester80-pack.csproj b/Tools/LambdaTestTool/src/Amazon.Lambda.TestTool.BlazorTester/Amazon.Lambda.TestTool.BlazorTester80-pack.csproj index 45afdfeb1..422742f07 100644 --- a/Tools/LambdaTestTool/src/Amazon.Lambda.TestTool.BlazorTester/Amazon.Lambda.TestTool.BlazorTester80-pack.csproj +++ b/Tools/LambdaTestTool/src/Amazon.Lambda.TestTool.BlazorTester/Amazon.Lambda.TestTool.BlazorTester80-pack.csproj @@ -5,7 +5,7 @@ Exe A tool to help debug and test your .NET 8.0 AWS Lambda functions locally. - 0.17.0 + 0.17.1 AWS .NET Lambda Test Tool Apache 2 AWS;Amazon;Lambda diff --git a/Tools/LambdaTestTool/src/Amazon.Lambda.TestTool.BlazorTester/Amazon.Lambda.TestTool.BlazorTester90-pack.csproj b/Tools/LambdaTestTool/src/Amazon.Lambda.TestTool.BlazorTester/Amazon.Lambda.TestTool.BlazorTester90-pack.csproj index e4a7cc450..d656e40be 100644 --- a/Tools/LambdaTestTool/src/Amazon.Lambda.TestTool.BlazorTester/Amazon.Lambda.TestTool.BlazorTester90-pack.csproj +++ b/Tools/LambdaTestTool/src/Amazon.Lambda.TestTool.BlazorTester/Amazon.Lambda.TestTool.BlazorTester90-pack.csproj @@ -5,7 +5,7 @@ Exe A tool to help debug and test your .NET 9.0 AWS Lambda functions locally. - 0.17.0 + 0.17.1 AWS .NET Lambda Test Tool Apache 2 AWS;Amazon;Lambda diff --git a/Tools/LambdaTestTool/src/Amazon.Lambda.TestTool/TestToolStartup.cs b/Tools/LambdaTestTool/src/Amazon.Lambda.TestTool/TestToolStartup.cs index 9693d64e4..87353be34 100644 --- a/Tools/LambdaTestTool/src/Amazon.Lambda.TestTool/TestToolStartup.cs +++ b/Tools/LambdaTestTool/src/Amazon.Lambda.TestTool/TestToolStartup.cs @@ -10,7 +10,6 @@ namespace Amazon.Lambda.TestTool { public class TestToolStartup { - private static bool shouldDisableLogs; public class RunConfiguration { @@ -37,7 +36,7 @@ public static void Startup(string productName, Action try { var commandOptions = CommandLineOptions.Parse(args); - shouldDisableLogs = Utils.ShouldDisableLogs(commandOptions); + var shouldDisableLogs = Utils.ShouldDisableLogs(commandOptions); if (!shouldDisableLogs) Utils.PrintToolTitle(productName); @@ -76,7 +75,7 @@ public static void Startup(string productName, Action if (commandOptions.NoUI) { - ExecuteWithNoUi(localLambdaOptions, commandOptions, lambdaAssemblyDirectory, runConfiguration); + ExecuteWithNoUi(localLambdaOptions, commandOptions, lambdaAssemblyDirectory, runConfiguration, shouldDisableLogs); } else { @@ -118,16 +117,16 @@ public static void Startup(string productName, Action } - public static void ExecuteWithNoUi(LocalLambdaOptions localLambdaOptions, CommandLineOptions commandOptions, string lambdaAssemblyDirectory, RunConfiguration runConfiguration) + public static void ExecuteWithNoUi(LocalLambdaOptions localLambdaOptions, CommandLineOptions commandOptions, string lambdaAssemblyDirectory, RunConfiguration runConfiguration, bool shouldDisableLogs) { if (!shouldDisableLogs) runConfiguration.OutputWriter.WriteLine("Executing Lambda function without web interface"); var lambdaProjectDirectory = Utils.FindLambdaProjectDirectory(lambdaAssemblyDirectory); - string configFile = DetermineConfigFile(commandOptions, lambdaAssemblyDirectory: lambdaAssemblyDirectory, lambdaProjectDirectory: lambdaProjectDirectory); - LambdaConfigInfo configInfo = LoadLambdaConfigInfo(configFile, commandOptions, lambdaAssemblyDirectory: lambdaAssemblyDirectory, lambdaProjectDirectory: lambdaProjectDirectory, runConfiguration); - LambdaFunction lambdaFunction = LoadLambdaFunction(configInfo, localLambdaOptions, commandOptions, lambdaAssemblyDirectory: lambdaAssemblyDirectory, lambdaProjectDirectory: lambdaProjectDirectory, runConfiguration); + string configFile = DetermineConfigFile(commandOptions, lambdaAssemblyDirectory: lambdaAssemblyDirectory, lambdaProjectDirectory: lambdaProjectDirectory, shouldDisableLogs: shouldDisableLogs); + LambdaConfigInfo configInfo = LoadLambdaConfigInfo(configFile, commandOptions, lambdaAssemblyDirectory: lambdaAssemblyDirectory, lambdaProjectDirectory: lambdaProjectDirectory, runConfiguration, shouldDisableLogs: shouldDisableLogs); + LambdaFunction lambdaFunction = LoadLambdaFunction(configInfo, localLambdaOptions, commandOptions, lambdaAssemblyDirectory: lambdaAssemblyDirectory, lambdaProjectDirectory: lambdaProjectDirectory, runConfiguration, shouldDisableLogs: shouldDisableLogs); - string payload = DeterminePayload(localLambdaOptions, commandOptions, lambdaAssemblyDirectory: lambdaAssemblyDirectory, lambdaProjectDirectory: lambdaProjectDirectory, runConfiguration); + string payload = DeterminePayload(localLambdaOptions, commandOptions, lambdaAssemblyDirectory: lambdaAssemblyDirectory, lambdaProjectDirectory: lambdaProjectDirectory, runConfiguration, shouldDisableLogs: shouldDisableLogs); var awsProfile = commandOptions.AWSProfile ?? configInfo.AWSProfile; if (!string.IsNullOrEmpty(awsProfile)) @@ -166,7 +165,7 @@ public static void ExecuteWithNoUi(LocalLambdaOptions localLambdaOptions, Comman Function = lambdaFunction }; - ExecuteRequest(request, localLambdaOptions, runConfiguration); + ExecuteRequest(request, localLambdaOptions, runConfiguration, shouldDisableLogs); if (runConfiguration.Mode == RunConfiguration.RunMode.Normal && commandOptions.PauseExit) @@ -176,7 +175,7 @@ public static void ExecuteWithNoUi(LocalLambdaOptions localLambdaOptions, Comman } } - private static string DetermineConfigFile(CommandLineOptions commandOptions, string lambdaAssemblyDirectory, string lambdaProjectDirectory) + private static string DetermineConfigFile(CommandLineOptions commandOptions, string lambdaAssemblyDirectory, string lambdaProjectDirectory, bool shouldDisableLogs) { string configFile = null; if (string.IsNullOrEmpty(commandOptions.ConfigFile)) @@ -199,7 +198,7 @@ private static string DetermineConfigFile(CommandLineOptions commandOptions, str return configFile; } - private static LambdaConfigInfo LoadLambdaConfigInfo(string configFile, CommandLineOptions commandOptions, string lambdaAssemblyDirectory, string lambdaProjectDirectory, RunConfiguration runConfiguration) + private static LambdaConfigInfo LoadLambdaConfigInfo(string configFile, CommandLineOptions commandOptions, string lambdaAssemblyDirectory, string lambdaProjectDirectory, RunConfiguration runConfiguration, bool shouldDisableLogs) { LambdaConfigInfo configInfo; if (configFile != null) @@ -226,7 +225,7 @@ private static LambdaConfigInfo LoadLambdaConfigInfo(string configFile, CommandL return configInfo; } - private static LambdaFunction LoadLambdaFunction(LambdaConfigInfo configInfo, LocalLambdaOptions localLambdaOptions, CommandLineOptions commandOptions, string lambdaAssemblyDirectory, string lambdaProjectDirectory, RunConfiguration runConfiguration) + private static LambdaFunction LoadLambdaFunction(LambdaConfigInfo configInfo, LocalLambdaOptions localLambdaOptions, CommandLineOptions commandOptions, string lambdaAssemblyDirectory, string lambdaProjectDirectory, RunConfiguration runConfiguration, bool shouldDisableLogs) { // If no function handler was explicitly set and there is only one function defined in the config file then assume the user wants to debug that function. var functionHandler = commandOptions.FunctionHandler; @@ -264,7 +263,7 @@ private static LambdaFunction LoadLambdaFunction(LambdaConfigInfo configInfo, Lo return lambdaFunction; } - private static string DeterminePayload(LocalLambdaOptions localLambdaOptions, CommandLineOptions commandOptions, string lambdaAssemblyDirectory, string lambdaProjectDirectory, RunConfiguration runConfiguration) + private static string DeterminePayload(LocalLambdaOptions localLambdaOptions, CommandLineOptions commandOptions, string lambdaAssemblyDirectory, string lambdaProjectDirectory, RunConfiguration runConfiguration, bool shouldDisableLogs) { var payload = commandOptions.Payload; @@ -346,7 +345,7 @@ private static string DeterminePayload(LocalLambdaOptions localLambdaOptions, Co return payload; } - private static void ExecuteRequest(ExecutionRequest request, LocalLambdaOptions localLambdaOptions, RunConfiguration runConfiguration) + private static void ExecuteRequest(ExecutionRequest request, LocalLambdaOptions localLambdaOptions, RunConfiguration runConfiguration, bool shouldDisableLogs) { try { From 277095118d8831f56201b73a83be79c53fab9514 Mon Sep 17 00:00:00 2001 From: Norm Johanson Date: Wed, 22 Apr 2026 00:43:04 -0700 Subject: [PATCH 5/5] Remove out of support build targets and fix all compiler warnings. --- .../218d1da2-1942-431e-b210-0ec9229cdb51.json | 223 +++ Libraries/Libraries.sln | 34 +- .../APIGatewayCustomAuthorizerContext.cs | 30 +- ...APIGatewayCustomAuthorizerContextOutput.cs | 8 +- .../APIGatewayCustomAuthorizerPolicy.cs | 14 - .../APIGatewayCustomAuthorizerResponse.cs | 18 +- ...APIGatewayCustomAuthorizerV2IamResponse.cs | 8 +- ...GatewayCustomAuthorizerV2SimpleResponse.cs | 6 +- .../APIGatewayHttpApiV2ProxyResponse.cs | 32 +- .../APIGatewayProxyResponse.cs | 22 +- .../Amazon.Lambda.APIGatewayEvents.csproj | 6 +- ....Lambda.Annotations.SourceGenerator.csproj | 9 +- .../Generator.cs | 4 +- .../Templates/APIGatewaySetupParameters.cs | 10 +- .../Templates/APIGatewaySetupParameters.tt | 10 +- .../Templates/AuthorizerSetupParameters.cs | 8 +- .../Templates/AuthorizerSetupParameters.tt | 8 +- .../APIGateway/HttpApiAuthorizerAttribute.cs | 2 +- .../APIGateway/HttpResults.cs | 2 +- .../APIGateway/RestApiAuthorizerAttribute.cs | 2 +- .../Amazon.Lambda.Annotations.csproj | 10 +- .../Amazon.Lambda.AppSyncEvents.csproj | 2 +- ...ambda.ApplicationLoadBalancerEvents.csproj | 4 +- .../ApplicationLoadBalancerResponse.cs | 14 +- ...zon.Lambda.AspNetCoreServer.Hosting.csproj | 2 +- .../HostingOptions.cs | 2 +- .../Internal/LambdaRuntimeSupportServer.cs | 27 +- .../ServiceCollectionExtensions.cs | 10 +- .../APIGatewayHttpApiV2ProxyFunction.cs | 7 +- .../APIGatewayProxyFunction.cs | 4 +- .../AbstractAspNetCoreFunction.cs | 49 +- .../Amazon.Lambda.AspNetCoreServer.csproj | 4 +- .../ApplicationLoadBalancerFunction.cs | 13 +- .../Internal/HttpRequestMessageConverter.cs | 3 + .../Internal/InvokeFeatures.cs | 31 +- .../Internal/LambdaServer.cs | 9 +- .../Internal/Utilities.cs | 6 +- .../Amazon.Lambda.CloudWatchEvents.csproj | 4 +- .../CloudWatchEvent.cs | 4 +- .../S3Events/S3Object.cs | 2 - .../S3Events/S3ObjectCreate.cs | 2 - .../S3Events/S3ObjectDelete.cs | 4 - .../S3Events/S3ObjectEventDetails.cs | 2 - .../S3Events/S3ObjectRestore.cs | 4 - .../TranslateParallelDataStateChange.cs | 2 +- .../Amazon.Lambda.CloudWatchLogsEvents.csproj | 4 +- .../CloudWatchLogsEvents.cs | 8 +- .../AccessTokenGeneration.cs | 8 - .../Amazon.Lambda.CognitoEvents.csproj | 4 +- .../ChallengeResultElement.cs | 8 +- .../ClaimOverrideDetails.cs | 8 +- .../ClaimsAndScopeOverrideDetails.cs | 6 - .../CognitoCreateAuthChallengeRequest.cs | 10 +- .../CognitoCreateAuthChallengeResponse.cs | 8 +- .../CognitoCustomEmailSenderRequest.cs | 6 +- .../CognitoCustomMessageRequest.cs | 8 +- .../CognitoCustomMessageResponse.cs | 8 +- .../CognitoCustomSmsSenderRequest.cs | 6 +- .../CognitoDefineAuthChallengeRequest.cs | 8 +- .../CognitoDefineAuthChallengeResponse.cs | 8 +- .../CognitoMigrateUserRequest.cs | 10 +- .../CognitoMigrateUserResponse.cs | 10 - .../CognitoPostAuthenticationRequest.cs | 8 +- .../CognitoPostConfirmationRequest.cs | 4 +- .../CognitoPreAuthenticationRequest.cs | 6 +- .../CognitoPreSignupRequest.cs | 6 +- .../CognitoPreSignupResponse.cs | 8 +- .../CognitoPreTokenGenerationRequest.cs | 6 +- .../CognitoPreTokenGenerationResponse.cs | 4 +- .../CognitoPreTokenGenerationV2Request.cs | 8 +- .../CognitoPreTokenGenerationV2Response.cs | 4 +- .../CognitoTriggerCallerContext.cs | 6 +- .../CognitoTriggerEvent.cs | 20 +- .../CognitoTriggerRequest.cs | 4 +- .../CognitoVerifyAuthChallengeRequest.cs | 10 +- .../CognitoVerifyAuthChallengeResponse.cs | 4 +- .../GroupConfiguration.cs | 8 +- .../IdTokenGeneration.cs | 4 - .../Amazon.Lambda.ConfigEvents.csproj | 4 +- .../Amazon.Lambda.ConnectEvents.csproj | 4 +- .../Amazon.Lambda.Core.csproj | 4 +- .../src/Amazon.Lambda.Core/ILambdaLogger.cs | 4 +- .../src/Amazon.Lambda.Core/LambdaLogger.cs | 2 +- .../LambdaSerializerAttribute.cs | 2 +- .../src/Amazon.Lambda.Core/SnapshotRestore.cs | 2 +- ...Lambda.DynamoDBEvents.SDK.Convertor.csproj | 2 +- .../Amazon.Lambda.DynamoDBEvents.csproj | 4 +- .../DictionaryLongToStringJsonConverter.cs | 20 +- .../DynamoDBTimeWindowEvent.cs | 4 - .../DynamoDBTimeWindowResponse.cs | 10 +- .../ExtensionMethods.cs | 12 +- .../StreamsEventResponse.cs | 8 +- .../Amazon.Lambda.KafkaEvents.csproj | 4 +- ...mazon.Lambda.KinesisAnalyticsEvents.csproj | 4 +- ...nalyticsFirehoseInputPreprocessingEvent.cs | 12 +- ...esisAnalyticsInputPreprocessingResponse.cs | 12 +- .../KinesisAnalyticsOutputDeliveryEvent.cs | 8 +- .../KinesisAnalyticsOutputDeliveryResponse.cs | 8 +- ...AnalyticsStreamsInputPreprocessingEvent.cs | 12 +- .../Amazon.Lambda.KinesisEvents.csproj | 4 +- .../DictionaryLongToStringJsonConverter.cs | 20 +- .../KinesisTimeWindowEvent.cs | 5 - .../KinesisTimeWindowResponse.cs | 11 +- .../Properties/AssemblyInfo.cs | 3 +- .../StreamsEventResponse.cs | 8 +- ...Amazon.Lambda.KinesisFirehoseEvents.csproj | 4 +- .../KinesisFirehoseEvent.cs | 10 +- .../KinesisFirehoseResponse.cs | 17 +- .../Amazon.Lambda.LexEvents.csproj | 4 +- .../LexActiveContext.cs | 14 +- .../LexRecentIntentSummaryViewType.cs | 30 +- .../Amazon.Lambda.LexEvents/LexResponse.cs | 48 +- .../Amazon.Lambda.LexV2Events.csproj | 4 +- .../Amazon.Lambda.Logging.AspNetCore.csproj | 2 +- .../Amazon.Lambda.MQEvents.csproj | 4 +- .../Amazon.Lambda.PowerShellHost.csproj | 12 +- .../PowerShellFunctionHost.cs | 124 +- .../Amazon.Lambda.RuntimeSupport.csproj | 4 +- .../Bootstrap/InvokeDelegateBuilder.cs | 14 - .../Bootstrap/LambdaBootstrap.cs | 27 +- .../Bootstrap/LambdaBootstrapBuilder.cs | 2 +- .../RawStreamingHttpClient.cs | 3 - .../ResponseStreaming/ResponseStream.cs | 20 +- .../ResponseStreamFactory.cs | 4 - ...onseStreamLambdaCoreInitializerIsolated.cs | 2 - .../Bootstrap/UserCodeInit.cs | 6 +- .../Bootstrap/UserCodeLoader.cs | 2 - .../Bootstrap/UserCodeValidator.cs | 2 - .../Client/IRuntimeApiClient.cs | 6 +- .../Client/InternalClientAdapted.cs | 45 - .../Client/RuntimeApiClient.cs | 14 - .../Context/LambdaBootstrapConfiguration.cs | 4 - .../Context/LambdaConsoleLogger.cs | 4 +- .../ExceptionHandling/StackFrameInfo.cs | 4 +- .../Helpers/ConsoleLoggerWriter.cs | 68 - .../Logging/AbstractLogMessageFormatter.cs | 2 - .../Logging/DefaultLogMessageFormatter.cs | 3 - .../Helpers/Logging/ILogMessageFormatter.cs | 3 - .../Logging/JsonLogMessageFormatter.cs | 2 - .../Helpers/Logging/MessageProperty.cs | 26 +- .../Helpers/Logging/MessageState.cs | 2 - ...tartHelperCopySnapshotCallbacksIsolated.cs | 4 +- ...perInitializeWithSnapstartIsolatedAsync.cs | 4 +- .../Helpers/Utils.cs | 7 - .../Amazon.Lambda.RuntimeSupport/Program.cs | 6 - .../RuntimeSupportInitializer.cs | 4 +- .../Amazon.Lambda.S3Events.csproj | 4 +- .../S3ObjectLambdaEvent.cs | 4 +- .../Amazon.Lambda.SNSEvents.csproj | 2 +- .../Amazon.Lambda.SQSEvents.csproj | 2 +- .../SQSBatchResponse.cs | 6 +- .../Amazon.Lambda.Serialization.Json.csproj | 2 +- .../AwsResolver.cs | 18 +- .../JsonSerializer.cs | 2 +- .../AbstractLambdaJsonSerializer.cs | 10 +- ...Lambda.Serialization.SystemTextJson.csproj | 2 +- .../CamelCaseLambdaJsonSerializer.cs | 8 +- .../Converters/ConstantClassConverter.cs | 4 +- .../DefaultLambdaJsonSerializer.cs | 12 +- .../LambdaJsonSerializer.cs | 12 +- .../SourceGeneratorLambdaJsonSerializer.cs | 10 +- .../Actions/IReceiptAction.cs | 10 +- .../Amazon.Lambda.SimpleEmailEvents.csproj | 2 +- .../SimpleEmailEvent.cs | 16 +- .../Amazon.Lambda.TestUtilities.csproj | 2 +- .../RestoreHooksRegistry.cs | 10 +- ....Annotations.SourceGenerators.Tests.csproj | 14 +- .../CSharpSourceGeneratorVerifier.cs | 69 +- .../FindTemplateTests.cs | 8 +- .../AuthNameFallback_GetUserId_Generated.g.cs | 4 +- ...tion_SimpleHttpApiAuthorize_Generated.g.cs | 2 +- ...tion_SimpleRestApiAuthorize_Generated.g.cs | 2 +- ...piExample_HttpApiAuthorizer_Generated.g.cs | 6 +- ...Example_HttpApiV1Authorizer_Generated.g.cs | 4 +- ...xample_HttpApiWithNonString_Generated.g.cs | 12 +- ...rRestExample_RestAuthorizer_Generated.g.cs | 6 +- ..._AuthorizerWithIHttpResults_Generated.g.cs | 4 +- ...ple_SimpleHttpApiAuthorizer_Generated.g.cs | 2 +- ...ple_SimpleRestApiAuthorizer_Generated.g.cs | 2 +- ...nction_GetHttpApiV1UserInfo_Generated.g.cs | 12 +- ...ctedFunction_GetIHttpResult_Generated.g.cs | 8 +- ...nction_GetNonStringUserInfo_Generated.g.cs | 12 +- ...tedFunction_GetRestUserInfo_Generated.g.cs | 12 +- ...on_GetSimpleHttpApiUserInfo_Generated.g.cs | 12 +- ...on_GetSimpleRestApiUserInfo_Generated.g.cs | 12 +- ...otectedFunction_GetUserInfo_Generated.g.cs | 12 +- .../ServerlessTemplates/albEvents.template | 16 +- .../customAuthorizerApp.template | 28 +- .../dynamoDBEvents.template | 16 +- .../hostbuild.serverless.template | 2 +- .../ServerlessTemplates/snsEvents.template | 4 +- .../ServerlessTemplates/sqsEvents.template | 14 +- .../SourceGeneratorTests.cs | 91 +- ...mbda.AspNetCoreServer.Hosting.Tests.csproj | 13 +- .../HostingOptionsTests.cs | 4 +- ...Amazon.Lambda.AspNetCoreServer.Test.csproj | 11 +- .../ResponseStreamingPropertyTests.cs | 15 +- .../StreamingFunctionHandlerAsyncTests.cs | 2 +- .../TestApiGatewayHttpApiV2Calls.cs | 54 +- .../TestApplicationLoadBalancerCalls.cs | 38 +- .../TestCallingWebAPI.cs | 74 +- .../TestMinimalAPI.cs | 19 +- .../Amazon.Lambda.Core.Tests.csproj | 11 +- ....DynamoDBEvents.SDK.Convertor.Tests.csproj | 9 +- .../DynamodbIdentityConvertorTests.cs | 2 +- .../DynamodbStreamRecordConvertorTests.cs | 2 +- ...zon.Lambda.Logging.AspNetCore.Tests.csproj | 16 +- .../LoggingTests.cs | 10 +- ...bda.RuntimeSupport.IntegrationTests.csproj | 12 +- .../ApiGatewayStreamingTests.cs | 81 +- .../BaseCustomRuntimeTest.cs | 14 +- ...spNetCoreMinimalApiCustomSerializerTest.cs | 6 +- .../CustomRuntimeAspNetCoreMinimalApiTest.cs | 6 +- .../CustomRuntimeTests.cs | 26 +- .../IntegrationTestFixture.cs | 12 +- .../ResponseStreamingTests.cs | 2 +- ...zon.Lambda.RuntimeSupport.UnitTests.csproj | 6 +- .../Common.cs | 6 +- .../HandlerTests.cs | 3 +- .../HandlerWrapperTests.cs | 7 +- .../LambdaBootstrapTests.cs | 12 +- .../LambdaContextTests.cs | 4 +- .../LambdaResponseStreamingCoreTests.cs | 4 +- .../LogMessageFormatterTests.cs | 2 +- .../NativeAOTTests.cs | 4 +- .../RawStreamingHttpClientTests.cs | 2 - .../ResponseStreamFactoryTests.cs | 2 +- .../RuntimeApiClientTests.cs | 2 - .../SnapstartTests.cs | 18 +- .../StreamingE2EWithMoq.cs | 82 +- .../NoOpInternalRuntimeApiClient.cs | 2 - .../TestMultiConcurrencyRuntimeApiClient.cs | 2 +- .../TestHelpers/TestRuntimeApiClient.cs | 4 +- .../TestStreamingRuntimeApiClient.cs | 4 +- .../Program.cs | 41 - .../Controllers/LoggerTestController.cs | 4 +- ...tCoreMinimalApiCustomSerializerTest.csproj | 7 +- .../aws-lambda-tools-defaults.json | 4 +- .../Controllers/LoggerTestController.cs | 4 +- ...stomRuntimeAspNetCoreMinimalApiTest.csproj | 7 +- .../aws-lambda-tools-defaults.json | 20 +- .../CustomRuntimeFunctionTest.csproj | 9 +- .../aws-lambda-tools-defaults.json | 20 +- .../EventsTests.NET6/EventsTests.NET6.csproj | 74 - .../SourceGeneratorSerializerTests.cs | 342 ---- .../EventsTests.NET8/EventsTests.NET8.csproj | 1 - .../SourceGeneratorSerializerTests.cs | 342 ++++ .../TestResponseCasing.cs | 4 +- .../EventsTests.NETCore31.csproj | 71 - .../DynamoDBEventJsonTests.cs | 12 +- .../test/EventsTests.Shared/EventTests.cs | 1692 ++++++++--------- .../CloudFormationHelper.cs | 2 +- .../IntegrationTests.Helpers.csproj | 10 +- .../IntegrationTests.Helpers/LambdaHelper.cs | 17 +- .../PowerShellTests/ExceptionHandlingTests.cs | 4 +- .../PowerShellTests/PowerShellTests.csproj | 6 +- .../test/PowerShellTests/ScriptInvokeTests.cs | 2 +- .../SnapshotRestore.Registry.Tests.csproj | 6 +- ...IntegrationTestContextFixtureCollection.cs | 2 +- .../NonStringAuthorizerTests.cs | 4 +- ...ustomAuthorizerApp.IntegrationTests.csproj | 14 +- .../TestCustomAuthorizerApp.csproj | 2 +- .../serverless.template | 28 +- .../src/Function/serverless.template | 28 +- .../NullableReferenceTypeExample.cs | 4 +- .../TestExecutableServerlessApp.csproj | 4 +- .../serverless.template | 288 +-- .../test/TestFunction/TestFunction.csproj | 2 +- .../FSharpJsonSerializer.csproj | 2 +- .../TestFunctionFSharp.fsproj | 1 - ...tServerlessApp.ALB.IntegrationTests.csproj | 15 +- .../TestServerlessApp.ALB.csproj | 2 +- .../TestServerlessApp.ALB/serverless.template | 8 +- .../Greeter.cs | 6 +- .../TestServerlessApp.IntegrationTests.csproj | 16 +- Libraries/test/TestServerlessApp/Dockerfile | 2 +- .../NullableReferenceTypeExample.cs | 2 +- .../TestServerlessApp/SimpleCalculator.cs | 4 +- .../TestServerlessApp.csproj | 6 +- .../aws-lambda-tools-defaults.json | 10 +- .../TestServerlessApp/serverless.template | 930 ++++----- .../Controllers/BinaryContentController.cs | 2 +- .../Controllers/RawQueryStringController.cs | 4 +- .../Controllers/RedirectTestController.cs | 4 +- .../RequestServicesExampleController.cs | 4 +- .../Controllers/ResourcePathController.cs | 4 +- .../Controllers/TraceTestsController.cs | 4 +- .../TestWebApp/HttpApiV2LambdaFunction.cs | 2 - Libraries/test/TestWebApp/Program.cs | 5 +- Libraries/test/TestWebApp/Startup.cs | 54 +- Libraries/test/TestWebApp/TestWebApp.csproj | 4 - Libraries/test/TestWebApp/serverless.template | 4 +- .../Amazon.Lambda.TestTool.csproj | 2 +- .../Commands/RunCommand.cs | 2 +- .../Extensions/HttpContextExtensions.cs | 1 + .../Models/EventContainer.cs | 2 +- .../Services/RuntimeApiDataStore.cs | 3 +- .../Utilities/HttpRequestUtility.cs | 1 + .../InvokeResponseExtensionsTests.cs | 2 + .../PackagingTests.cs | 2 +- .../ApiGatewayEmulatorProcessTests.cs | 2 +- .../RuntimeApiTests.cs | 2 +- .../Utilities/DirectoryHelpers.cs | 2 +- ...Amazon.Lambda.TestTool.BlazorTester.csproj | 7 +- ...mbda.TestTool.BlazorTester10_0-pack.csproj | 1 + ...Lambda.TestTool.BlazorTester80-pack.csproj | 1 + ...Lambda.TestTool.BlazorTester90-pack.csproj | 1 + .../Amazon.Lambda.TestTool.csproj | 13 +- buildtools/common.props | 15 +- 309 files changed, 3119 insertions(+), 3916 deletions(-) create mode 100644 .autover/changes/218d1da2-1942-431e-b210-0ec9229cdb51.json delete mode 100644 Libraries/test/EventsTests.NET6/EventsTests.NET6.csproj delete mode 100644 Libraries/test/EventsTests.NET6/SourceGeneratorSerializerTests.cs create mode 100644 Libraries/test/EventsTests.NET8/SourceGeneratorSerializerTests.cs rename Libraries/test/{EventsTests.NETCore31 => EventsTests.NET8}/TestResponseCasing.cs (98%) delete mode 100644 Libraries/test/EventsTests.NETCore31/EventsTests.NETCore31.csproj diff --git a/.autover/changes/218d1da2-1942-431e-b210-0ec9229cdb51.json b/.autover/changes/218d1da2-1942-431e-b210-0ec9229cdb51.json new file mode 100644 index 000000000..1e48a49fe --- /dev/null +++ b/.autover/changes/218d1da2-1942-431e-b210-0ec9229cdb51.json @@ -0,0 +1,223 @@ +{ + "Projects": [ + { + "Name": "Amazon.Lambda.RuntimeSupport", + "Type": "Major", + "ChangelogMessages": [ + "Remove .NET Standard 2.0, .NET Core 3.1 and .NET 6 build targets" + ] + }, + { + "Name": "Amazon.Lambda.Annotations", + "Type": "Major", + "ChangelogMessages": [ + "Update Build targets from .NET Standard 2.0, .NET 6 and .NET 8 to .NET Standard 2.0, .NET 8 and .NET 10" + ] + }, + { + "Name": "Amazon.Lambda.APIGatewayEvents", + "Type": "Major", + "ChangelogMessages": [ + "Update Build targets from .NET Standard 2.0, .NET Core 3.1 and .NET 8 to .NET 8 and .NET 10" + ] + }, + { + "Name": "Amazon.Lambda.ApplicationLoadBalancerEvents", + "Type": "Major", + "ChangelogMessages": [ + "Update Build targets from .NET Standard 2.0, .NET Core 3.1 and .NET 8 to .NET 8 and .NET 10" + ] + }, + { + "Name": "Amazon.Lambda.AppSyncEvents", + "Type": "Major", + "ChangelogMessages": [ + "Update Build targets from .NET 8 to .NET 8 and .NET 10" + ] + }, + { + "Name": "Amazon.Lambda.AspNetCoreServer.Hosting", + "Type": "Major", + "ChangelogMessages": [ + "Update Build targets from .NET 6 and .NET 8 to .NET 8 and .NET 10" + ] + }, + { + "Name": "Amazon.Lambda.AspNetCoreServer", + "Type": "Major", + "ChangelogMessages": [ + "Update Build targets from .NET 6 and .NET 8 to .NET 8 and .NET 10" + ] + }, + { + "Name": "Amazon.Lambda.CloudWatchEvents", + "Type": "Major", + "ChangelogMessages": [ + "Update Build targets from .NET Standard 2.0, .NET Core 3.1 and .NET 8 to .NET 8 and .NET 10" + ] + }, + { + "Name": "Amazon.Lambda.CloudWatchLogsEvents", + "Type": "Major", + "ChangelogMessages": [ + "Update Build targets from .NET Standard 2.0, .NET Core 3.1 and .NET 8 to .NET 8 and .NET 10" + ] + }, + { + "Name": "Amazon.Lambda.CognitoEvents", + "Type": "Major", + "ChangelogMessages": [ + "Update Build targets from .NET Standard 2.0, .NET Core 3.1 and .NET 8 to .NET 8 and .NET 10" + ] + }, + { + "Name": "Amazon.Lambda.ConfigEvents", + "Type": "Major", + "ChangelogMessages": [ + "Update Build targets from .NET Standard 2.0 and .NET 8 to .NET 8 and .NET 10" + ] + }, + { + "Name": "Amazon.Lambda.ConnectEvents", + "Type": "Major", + "ChangelogMessages": [ + "Update Build targets from .NET Standard 2.0 and .NET 8 to .NET 8 and .NET 10" + ] + }, + { + "Name": "Amazon.Lambda.Core", + "Type": "Major", + "ChangelogMessages": [ + "Update Build targets from .NET Standard 2.0, .NET 6 and .NET 8 to .NET Standard 2.0, .NET 8 and .NET 10" + ] + }, + { + "Name": "Amazon.Lambda.DynamoDBEvents.SDK.Convertor", + "Type": "Major", + "ChangelogMessages": [ + "Update Build targets from .NET 8 to .NET 8 and .NET 10" + ] + }, + { + "Name": "Amazon.Lambda.DynamoDBEvents", + "Type": "Major", + "ChangelogMessages": [ + "Update Build targets from .NET Core 3.1 and .NET 8 to .NET 8 and .NET 10" + ] + }, + { + "Name": "Amazon.Lambda.KafkaEvents", + "Type": "Major", + "ChangelogMessages": [ + "Update Build targets from .NET Standard 2.0 and .NET 8 to .NET Standard 2.0, .NET 8 and .NET 10" + ] + }, + { + "Name": "Amazon.Lambda.KinesisAnalyticsEvents", + "Type": "Major", + "ChangelogMessages": [ + "Update Build targets from .NET Standard 2.0, .NET Core 3.1 and .NET 8 to .NET 8 and .NET 10" + ] + }, + { + "Name": "Amazon.Lambda.KinesisEvents", + "Type": "Major", + "ChangelogMessages": [ + "Update Build targets from .NET Standard 2.0, .NET Core 3.1 and .NET 8 to .NET 8 and .NET 10" + ] + }, + { + "Name": "Amazon.Lambda.KinesisFirehoseEvents", + "Type": "Major", + "ChangelogMessages": [ + "Update Build targets from .NET Standard 2.0, .NET Core 3.1 and .NET 8 to .NET 8 and .NET 10" + ] + }, + { + "Name": "Amazon.Lambda.LexEvents", + "Type": "Major", + "ChangelogMessages": [ + "Update Build targets from .NET Standard 2.0, .NET Core 3.1 and .NET 8 to .NET 8 and .NET 10" + ] + }, + { + "Name": "Amazon.Lambda.LexV2Events", + "Type": "Major", + "ChangelogMessages": [ + "Update Build targets from .NET Core 3.1 and .NET 8 to .NET 8 and .NET 10" + ] + }, + { + "Name": "Amazon.Lambda.Logging.AspNetCore", + "Type": "Major", + "ChangelogMessages": [ + "Update Build targets from .NET 6 and .NET 8 to .NET 8 and .NET 10" + ] + }, + { + "Name": "Amazon.Lambda.MQEvents", + "Type": "Major", + "ChangelogMessages": [ + "Update Build targets from .NET Standard 2.0, .NET Core 3.1 and .NET 8 to .NET 8 and .NET 10" + ] + }, + { + "Name": "Amazon.Lambda.PowerShellHost", + "Type": "Major", + "ChangelogMessages": [ + "Update Build targets from .NET 6 and .NET 8 to .NET 8 and .NET 10", + "Update Microsoft.PowerShell.SDK package dependency to version 7.4.14", + "Update System.Security.Cryptography.Xml package dependency to version 8.0.3" + ] + }, + { + "Name": "Amazon.Lambda.S3Events", + "Type": "Major", + "ChangelogMessages": [ + "Update Build targets from .NET Core 3.1 and .NET 8 to .NET 8 and .NET 10" + ] + }, + { + "Name": "Amazon.Lambda.Serialization.Json", + "Type": "Major", + "ChangelogMessages": [ + "Update Build targets from .NET Standard 2.0 to .NET 8 and .NET 10" + ] + }, + { + "Name": "Amazon.Lambda.Serialization.SystemTextJson", + "Type": "Major", + "ChangelogMessages": [ + "Update Build targets from .NET Core 3.1, .NET 6 and .NET 8 to .NET 8 and .NET 10" + ] + }, + { + "Name": "Amazon.Lambda.SimpleEmailEvents", + "Type": "Major", + "ChangelogMessages": [ + "Update Build targets from .NET Standard 2.0 and .NET 8 to .NET 8 and .NET 10" + ] + }, + { + "Name": "Amazon.Lambda.SNSEvents", + "Type": "Major", + "ChangelogMessages": [ + "Update Build targets from .NET Standard 2.0 and .NET 8 to .NET 8 and .NET 10" + ] + }, + { + "Name": "Amazon.Lambda.SQSEvents", + "Type": "Major", + "ChangelogMessages": [ + "Update Build targets from .NET Standard 2.0, .NET Core 3.1 and .NET 8 to .NET 8 and .NET 10" + ] + }, + { + "Name": "Amazon.Lambda.TestUtilities", + "Type": "Major", + "ChangelogMessages": [ + "Update Build targets from .NET 6 and .NET 8 to .NET 8 and .NET 10" + ] + } + ] +} \ No newline at end of file diff --git a/Libraries/Libraries.sln b/Libraries/Libraries.sln index 3fac89872..e42c40045 100644 --- a/Libraries/Libraries.sln +++ b/Libraries/Libraries.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 18 -VisualStudioVersion = 18.3.11512.155 +VisualStudioVersion = 18.5.11709.299 stable MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{AAB54E74-20B1-42ED-BC3D-CE9F7BC7FD12}" EndProject @@ -95,8 +95,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Amazon.Lambda.Serialization EndProject Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "EventsTests.Shared", "test\EventsTests.Shared\EventsTests.Shared.shproj", "{A2CB78BB-E54F-48CA-BBFB-9553D27EF23D}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EventsTests.NETCore31", "test\EventsTests.NETCore31\EventsTests.NETCore31.csproj", "{44E9D925-B61D-4234-97B7-61424C963BA6}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HandlerTest", "test\HandlerTest\HandlerTest.csproj", "{E88231E0-B249-49AE-B764-DB6C9615F6CA}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HandlerTestNoSerializer", "test\HandlerTestNoSerializer\HandlerTestNoSerializer.csproj", "{9736E38B-B67F-42BD-882E-CE9C8AEE1BC4}" @@ -115,8 +113,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Amazon.Lambda.AspNetCoreSer EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CustomRuntimeAspNetCoreMinimalApiTest", "test\Amazon.Lambda.RuntimeSupport.Tests\CustomRuntimeAspNetCoreMinimalApiTest\CustomRuntimeAspNetCoreMinimalApiTest.csproj", "{2FFBE745-B7D5-4E44-B76D-88A0C2402FEB}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EventsTests.NET6", "test\EventsTests.NET6\EventsTests.NET6.csproj", "{C1BB30D2-3237-4CFC-BA93-627471148EC2}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Amazon.Lambda.KafkaEvents", "src\Amazon.Lambda.KafkaEvents\Amazon.Lambda.KafkaEvents.csproj", "{982A26C7-A5D1-4783-A7F8-F2B28AA2459E}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestMinimalAPIApp", "test\TestMinimalAPIApp\TestMinimalAPIApp.csproj", "{8AB1CBD7-2D08-492F-9C09-3E754364046C}" @@ -613,18 +609,6 @@ Global {AA6BA0B8-D61E-49E7-BC1B-19410E25F005}.Release|x64.Build.0 = Release|Any CPU {AA6BA0B8-D61E-49E7-BC1B-19410E25F005}.Release|x86.ActiveCfg = Release|Any CPU {AA6BA0B8-D61E-49E7-BC1B-19410E25F005}.Release|x86.Build.0 = Release|Any CPU - {44E9D925-B61D-4234-97B7-61424C963BA6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {44E9D925-B61D-4234-97B7-61424C963BA6}.Debug|Any CPU.Build.0 = Debug|Any CPU - {44E9D925-B61D-4234-97B7-61424C963BA6}.Debug|x64.ActiveCfg = Debug|Any CPU - {44E9D925-B61D-4234-97B7-61424C963BA6}.Debug|x64.Build.0 = Debug|Any CPU - {44E9D925-B61D-4234-97B7-61424C963BA6}.Debug|x86.ActiveCfg = Debug|Any CPU - {44E9D925-B61D-4234-97B7-61424C963BA6}.Debug|x86.Build.0 = Debug|Any CPU - {44E9D925-B61D-4234-97B7-61424C963BA6}.Release|Any CPU.ActiveCfg = Release|Any CPU - {44E9D925-B61D-4234-97B7-61424C963BA6}.Release|Any CPU.Build.0 = Release|Any CPU - {44E9D925-B61D-4234-97B7-61424C963BA6}.Release|x64.ActiveCfg = Release|Any CPU - {44E9D925-B61D-4234-97B7-61424C963BA6}.Release|x64.Build.0 = Release|Any CPU - {44E9D925-B61D-4234-97B7-61424C963BA6}.Release|x86.ActiveCfg = Release|Any CPU - {44E9D925-B61D-4234-97B7-61424C963BA6}.Release|x86.Build.0 = Release|Any CPU {E88231E0-B249-49AE-B764-DB6C9615F6CA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {E88231E0-B249-49AE-B764-DB6C9615F6CA}.Debug|Any CPU.Build.0 = Debug|Any CPU {E88231E0-B249-49AE-B764-DB6C9615F6CA}.Debug|x64.ActiveCfg = Debug|Any CPU @@ -733,18 +717,6 @@ Global {2FFBE745-B7D5-4E44-B76D-88A0C2402FEB}.Release|x64.Build.0 = Release|Any CPU {2FFBE745-B7D5-4E44-B76D-88A0C2402FEB}.Release|x86.ActiveCfg = Release|Any CPU {2FFBE745-B7D5-4E44-B76D-88A0C2402FEB}.Release|x86.Build.0 = Release|Any CPU - {C1BB30D2-3237-4CFC-BA93-627471148EC2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C1BB30D2-3237-4CFC-BA93-627471148EC2}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C1BB30D2-3237-4CFC-BA93-627471148EC2}.Debug|x64.ActiveCfg = Debug|Any CPU - {C1BB30D2-3237-4CFC-BA93-627471148EC2}.Debug|x64.Build.0 = Debug|Any CPU - {C1BB30D2-3237-4CFC-BA93-627471148EC2}.Debug|x86.ActiveCfg = Debug|Any CPU - {C1BB30D2-3237-4CFC-BA93-627471148EC2}.Debug|x86.Build.0 = Debug|Any CPU - {C1BB30D2-3237-4CFC-BA93-627471148EC2}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C1BB30D2-3237-4CFC-BA93-627471148EC2}.Release|Any CPU.Build.0 = Release|Any CPU - {C1BB30D2-3237-4CFC-BA93-627471148EC2}.Release|x64.ActiveCfg = Release|Any CPU - {C1BB30D2-3237-4CFC-BA93-627471148EC2}.Release|x64.Build.0 = Release|Any CPU - {C1BB30D2-3237-4CFC-BA93-627471148EC2}.Release|x86.ActiveCfg = Release|Any CPU - {C1BB30D2-3237-4CFC-BA93-627471148EC2}.Release|x86.Build.0 = Release|Any CPU {982A26C7-A5D1-4783-A7F8-F2B28AA2459E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {982A26C7-A5D1-4783-A7F8-F2B28AA2459E}.Debug|Any CPU.Build.0 = Debug|Any CPU {982A26C7-A5D1-4783-A7F8-F2B28AA2459E}.Debug|x64.ActiveCfg = Debug|Any CPU @@ -1043,7 +1015,6 @@ Global {10E47FE4-8620-4933-A14D-E33F25CA557A} = {B5BD0336-7D08-492C-8489-42C987E29B39} {AA6BA0B8-D61E-49E7-BC1B-19410E25F005} = {AAB54E74-20B1-42ED-BC3D-CE9F7BC7FD12} {A2CB78BB-E54F-48CA-BBFB-9553D27EF23D} = {1DE4EE60-45BA-4EF7-BE00-B9EB861E4C69} - {44E9D925-B61D-4234-97B7-61424C963BA6} = {1DE4EE60-45BA-4EF7-BE00-B9EB861E4C69} {E88231E0-B249-49AE-B764-DB6C9615F6CA} = {B5BD0336-7D08-492C-8489-42C987E29B39} {9736E38B-B67F-42BD-882E-CE9C8AEE1BC4} = {B5BD0336-7D08-492C-8489-42C987E29B39} {3D322CAB-0DDD-4C84-B3ED-0862F244AF5C} = {1DE4EE60-45BA-4EF7-BE00-B9EB861E4C69} @@ -1053,7 +1024,6 @@ Global {2D956162-04BE-402E-9487-AE785AA14DE4} = {1DE4EE60-45BA-4EF7-BE00-B9EB861E4C69} {02908C6F-FBDF-4949-B039-0F4632265B90} = {AAB54E74-20B1-42ED-BC3D-CE9F7BC7FD12} {2FFBE745-B7D5-4E44-B76D-88A0C2402FEB} = {B5BD0336-7D08-492C-8489-42C987E29B39} - {C1BB30D2-3237-4CFC-BA93-627471148EC2} = {1DE4EE60-45BA-4EF7-BE00-B9EB861E4C69} {982A26C7-A5D1-4783-A7F8-F2B28AA2459E} = {AAB54E74-20B1-42ED-BC3D-CE9F7BC7FD12} {8AB1CBD7-2D08-492F-9C09-3E754364046C} = {1DE4EE60-45BA-4EF7-BE00-B9EB861E4C69} {BF85932E-2DFF-41CD-8090-A672468B8FBB} = {AAB54E74-20B1-42ED-BC3D-CE9F7BC7FD12} @@ -1081,8 +1051,6 @@ Global EndGlobalSection GlobalSection(SharedMSBuildProjectFiles) = preSolution test\EventsTests.Shared\EventsTests.Shared.projitems*{1fb22337-5d88-4ce7-adff-ffd89204f0e9}*SharedItemsImports = 5 - test\EventsTests.Shared\EventsTests.Shared.projitems*{44e9d925-b61d-4234-97b7-61424c963ba6}*SharedItemsImports = 5 test\EventsTests.Shared\EventsTests.Shared.projitems*{a2cb78bb-e54f-48ca-bbfb-9553d27ef23d}*SharedItemsImports = 13 - test\EventsTests.Shared\EventsTests.Shared.projitems*{c1bb30d2-3237-4cfc-ba93-627471148ec2}*SharedItemsImports = 5 EndGlobalSection EndGlobal diff --git a/Libraries/src/Amazon.Lambda.APIGatewayEvents/APIGatewayCustomAuthorizerContext.cs b/Libraries/src/Amazon.Lambda.APIGatewayEvents/APIGatewayCustomAuthorizerContext.cs index 15ed85503..0ed160ccb 100644 --- a/Libraries/src/Amazon.Lambda.APIGatewayEvents/APIGatewayCustomAuthorizerContext.cs +++ b/Libraries/src/Amazon.Lambda.APIGatewayEvents/APIGatewayCustomAuthorizerContext.cs @@ -1,14 +1,10 @@ -namespace Amazon.Lambda.APIGatewayEvents +namespace Amazon.Lambda.APIGatewayEvents { using System; using System.Collections.Generic; using System.Runtime.Serialization; -#if NETSTANDARD_2_0 - using Newtonsoft.Json.Linq; -#else using System.Text.Json; -#endif /// @@ -26,7 +22,7 @@ public string PrincipalId get { object value; - if (this.TryGetValue("principalId", out value)) + if (TryGetValue("principalId", out value)) return value.ToString(); return null; } @@ -45,7 +41,7 @@ public string StringKey get { object value; - if (this.TryGetValue("stringKey", out value)) + if (TryGetValue("stringKey", out value)) return value.ToString(); return null; } @@ -64,7 +60,7 @@ public int? NumKey get { object value; - if (this.TryGetValue("numKey", out value)) + if (TryGetValue("numKey", out value)) { int i; if (int.TryParse(value?.ToString(), out i)) @@ -90,7 +86,7 @@ public bool? BoolKey get { object value; - if (this.TryGetValue("boolKey", out value)) + if (TryGetValue("boolKey", out value)) { bool b; if(bool.TryParse(value?.ToString(), out b)) @@ -120,19 +116,8 @@ public Dictionary Claims _claims = new Dictionary(); object value; - if(this.TryGetValue("claims", out value)) + if(TryGetValue("claims", out value)) { -#if NETSTANDARD_2_0 - JObject jsonClaims = value as JObject; - if (jsonClaims != null) - { - foreach (JProperty property in jsonClaims.Properties()) - { - _claims[property.Name] = property.Value?.ToString(); - - } - } -#else if(value is JsonElement jsonClaims) { foreach(JsonProperty property in jsonClaims.EnumerateObject()) @@ -143,7 +128,6 @@ public Dictionary Claims } } } -#endif } } @@ -151,7 +135,7 @@ public Dictionary Claims } set { - this._claims = value; + _claims = value; } } } diff --git a/Libraries/src/Amazon.Lambda.APIGatewayEvents/APIGatewayCustomAuthorizerContextOutput.cs b/Libraries/src/Amazon.Lambda.APIGatewayEvents/APIGatewayCustomAuthorizerContextOutput.cs index 2f5acf092..1f905d839 100644 --- a/Libraries/src/Amazon.Lambda.APIGatewayEvents/APIGatewayCustomAuthorizerContextOutput.cs +++ b/Libraries/src/Amazon.Lambda.APIGatewayEvents/APIGatewayCustomAuthorizerContextOutput.cs @@ -1,4 +1,4 @@ -namespace Amazon.Lambda.APIGatewayEvents +namespace Amazon.Lambda.APIGatewayEvents { using System; using System.Collections.Generic; @@ -19,7 +19,7 @@ public string StringKey get { object value; - if (this.TryGetValue("stringKey", out value)) + if (TryGetValue("stringKey", out value)) return value.ToString(); return null; } @@ -38,7 +38,7 @@ public int? NumKey get { object value; - if (this.TryGetValue("numKey", out value)) + if (TryGetValue("numKey", out value)) { int i; if (int.TryParse(value?.ToString(), out i)) @@ -64,7 +64,7 @@ public bool? BoolKey get { object value; - if (this.TryGetValue("boolKey", out value)) + if (TryGetValue("boolKey", out value)) { bool b; if (bool.TryParse(value?.ToString(), out b)) diff --git a/Libraries/src/Amazon.Lambda.APIGatewayEvents/APIGatewayCustomAuthorizerPolicy.cs b/Libraries/src/Amazon.Lambda.APIGatewayEvents/APIGatewayCustomAuthorizerPolicy.cs index 59fd940f3..887d05c0e 100644 --- a/Libraries/src/Amazon.Lambda.APIGatewayEvents/APIGatewayCustomAuthorizerPolicy.cs +++ b/Libraries/src/Amazon.Lambda.APIGatewayEvents/APIGatewayCustomAuthorizerPolicy.cs @@ -10,17 +10,13 @@ public class APIGatewayCustomAuthorizerPolicy /// /// Gets or sets the IAM API version. /// -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("Version")] -#endif public string Version { get; set; } = "2012-10-17"; /// /// Gets or sets a list of IAM policy statements to apply. /// -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("Statement")] -#endif public List Statement { get; set; } = new List(); /// @@ -31,42 +27,32 @@ public class IAMPolicyStatement /// /// Gets or sets the effect the statement has. /// -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("Effect")] -#endif public string Effect { get; set; } = "Allow"; /// /// Gets or sets the action/s the statement has. /// -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("Action")] -#endif public HashSet Action { get; set; } /// /// Gets or sets the resources the statement applies to. /// -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("Resource")] -#endif public HashSet Resource { get; set; } /// /// Gets or sets the resources the statement does not apply to. /// -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("NotResource")] -#endif public HashSet NotResource { get; set; } /// /// Gets or sets the conditions for when a policy is in effect. /// https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements_condition.html /// -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("Condition")] -#endif public IDictionary> Condition { get; set; } } } diff --git a/Libraries/src/Amazon.Lambda.APIGatewayEvents/APIGatewayCustomAuthorizerResponse.cs b/Libraries/src/Amazon.Lambda.APIGatewayEvents/APIGatewayCustomAuthorizerResponse.cs index e2209f2e0..83e8a4a6d 100644 --- a/Libraries/src/Amazon.Lambda.APIGatewayEvents/APIGatewayCustomAuthorizerResponse.cs +++ b/Libraries/src/Amazon.Lambda.APIGatewayEvents/APIGatewayCustomAuthorizerResponse.cs @@ -1,4 +1,4 @@ -namespace Amazon.Lambda.APIGatewayEvents +namespace Amazon.Lambda.APIGatewayEvents { using System.Runtime.Serialization; @@ -12,36 +12,28 @@ public class APIGatewayCustomAuthorizerResponse /// Gets or sets the ID of the principal. /// [DataMember(Name = "principalId")] -#if NETCOREAPP3_1_OR_GREATER - [System.Text.Json.Serialization.JsonPropertyName("principalId")] -#endif + [System.Text.Json.Serialization.JsonPropertyName("principalId")] public string PrincipalID { get; set; } /// /// Gets or sets the policy document. /// [DataMember(Name = "policyDocument")] -#if NETCOREAPP3_1_OR_GREATER - [System.Text.Json.Serialization.JsonPropertyName("policyDocument")] -#endif + [System.Text.Json.Serialization.JsonPropertyName("policyDocument")] public APIGatewayCustomAuthorizerPolicy PolicyDocument { get; set; } = new APIGatewayCustomAuthorizerPolicy(); /// /// Gets or sets the property. /// [DataMember(Name = "context")] -#if NETCOREAPP3_1_OR_GREATER - [System.Text.Json.Serialization.JsonPropertyName("context")] -#endif + [System.Text.Json.Serialization.JsonPropertyName("context")] public APIGatewayCustomAuthorizerContextOutput Context { get; set; } /// /// Gets or sets the usageIdentifierKey. /// [DataMember(Name = "usageIdentifierKey")] -#if NETCOREAPP3_1_OR_GREATER - [System.Text.Json.Serialization.JsonPropertyName("usageIdentifierKey")] -#endif + [System.Text.Json.Serialization.JsonPropertyName("usageIdentifierKey")] public string UsageIdentifierKey { get; set; } } } diff --git a/Libraries/src/Amazon.Lambda.APIGatewayEvents/APIGatewayCustomAuthorizerV2IamResponse.cs b/Libraries/src/Amazon.Lambda.APIGatewayEvents/APIGatewayCustomAuthorizerV2IamResponse.cs index 20d0fe56a..96c74ab22 100644 --- a/Libraries/src/Amazon.Lambda.APIGatewayEvents/APIGatewayCustomAuthorizerV2IamResponse.cs +++ b/Libraries/src/Amazon.Lambda.APIGatewayEvents/APIGatewayCustomAuthorizerV2IamResponse.cs @@ -1,4 +1,4 @@ -namespace Amazon.Lambda.APIGatewayEvents +namespace Amazon.Lambda.APIGatewayEvents { using System.Collections.Generic; using System.Runtime.Serialization; @@ -14,27 +14,21 @@ public class APIGatewayCustomAuthorizerV2IamResponse /// Gets or sets the ID of the principal. /// [DataMember(Name = "principalId")] -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("principalId")] -#endif public string PrincipalID { get; set; } /// /// Gets or sets the policy document. /// [DataMember(Name = "policyDocument")] -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("policyDocument")] -#endif public APIGatewayCustomAuthorizerPolicy PolicyDocument { get; set; } = new APIGatewayCustomAuthorizerPolicy(); /// /// Gets or sets the property. /// [DataMember(Name = "context")] -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("context")] -#endif public Dictionary Context { get; set; } } } diff --git a/Libraries/src/Amazon.Lambda.APIGatewayEvents/APIGatewayCustomAuthorizerV2SimpleResponse.cs b/Libraries/src/Amazon.Lambda.APIGatewayEvents/APIGatewayCustomAuthorizerV2SimpleResponse.cs index a64561833..14100785b 100644 --- a/Libraries/src/Amazon.Lambda.APIGatewayEvents/APIGatewayCustomAuthorizerV2SimpleResponse.cs +++ b/Libraries/src/Amazon.Lambda.APIGatewayEvents/APIGatewayCustomAuthorizerV2SimpleResponse.cs @@ -1,4 +1,4 @@ -namespace Amazon.Lambda.APIGatewayEvents +namespace Amazon.Lambda.APIGatewayEvents { using System.Collections.Generic; using System.Runtime.Serialization; @@ -14,18 +14,14 @@ public class APIGatewayCustomAuthorizerV2SimpleResponse /// Gets or sets authorization result. /// [DataMember(Name = "isAuthorized")] -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("isAuthorized")] -#endif public bool IsAuthorized { get; set; } /// /// Gets or sets the property. /// [DataMember(Name = "context")] -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("context")] -#endif public Dictionary Context { get; set; } } } diff --git a/Libraries/src/Amazon.Lambda.APIGatewayEvents/APIGatewayHttpApiV2ProxyResponse.cs b/Libraries/src/Amazon.Lambda.APIGatewayEvents/APIGatewayHttpApiV2ProxyResponse.cs index 6e7d1185d..151439a4d 100644 --- a/Libraries/src/Amazon.Lambda.APIGatewayEvents/APIGatewayHttpApiV2ProxyResponse.cs +++ b/Libraries/src/Amazon.Lambda.APIGatewayEvents/APIGatewayHttpApiV2ProxyResponse.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Text; @@ -17,18 +17,14 @@ public class APIGatewayHttpApiV2ProxyResponse /// The HTTP status code for the request /// [DataMember(Name = "statusCode")] -#if NETCOREAPP3_1_OR_GREATER - [System.Text.Json.Serialization.JsonPropertyName("statusCode")] -#endif + [System.Text.Json.Serialization.JsonPropertyName("statusCode")] public int StatusCode { get; set; } /// /// The Http headers returned in the response. Multiple header values set for the the same header should be separate by a comma. /// [DataMember(Name = "headers")] -#if NETCOREAPP3_1_OR_GREATER - [System.Text.Json.Serialization.JsonPropertyName("headers")] -#endif + [System.Text.Json.Serialization.JsonPropertyName("headers")] public IDictionary Headers { get; set; } /// @@ -50,16 +46,16 @@ public void SetHeaderValues(string headerName, string value, bool append) /// If true it will append the values to the existing value in the Headers collection. public void SetHeaderValues(string headerName, IEnumerable values, bool append) { - if (this.Headers == null) - this.Headers = new Dictionary(); + if (Headers == null) + Headers = new Dictionary(); - if(this.Headers.ContainsKey(headerName) && append) + if(Headers.ContainsKey(headerName) && append) { - this.Headers[headerName] = this.Headers[headerName] + "," + string.Join(",", values); + Headers[headerName] = Headers[headerName] + "," + string.Join(",", values); } else { - this.Headers[headerName] = string.Join(",", values); + Headers[headerName] = string.Join(",", values); } } @@ -67,27 +63,21 @@ public void SetHeaderValues(string headerName, IEnumerable values, bool /// The cookies returned in the response. /// [DataMember(Name = "cookies")] -#if NETCOREAPP3_1_OR_GREATER - [System.Text.Json.Serialization.JsonPropertyName("cookies")] -#endif + [System.Text.Json.Serialization.JsonPropertyName("cookies")] public string[] Cookies { get; set; } /// /// The response body /// [DataMember(Name = "body")] -#if NETCOREAPP3_1_OR_GREATER - [System.Text.Json.Serialization.JsonPropertyName("body")] -#endif + [System.Text.Json.Serialization.JsonPropertyName("body")] public string Body { get; set; } /// /// Flag indicating whether the body should be treated as a base64-encoded string /// [DataMember(Name = "isBase64Encoded")] -#if NETCOREAPP3_1_OR_GREATER - [System.Text.Json.Serialization.JsonPropertyName("isBase64Encoded")] -#endif + [System.Text.Json.Serialization.JsonPropertyName("isBase64Encoded")] public bool IsBase64Encoded { get; set; } } } diff --git a/Libraries/src/Amazon.Lambda.APIGatewayEvents/APIGatewayProxyResponse.cs b/Libraries/src/Amazon.Lambda.APIGatewayEvents/APIGatewayProxyResponse.cs index 48191a601..8ef412b4e 100644 --- a/Libraries/src/Amazon.Lambda.APIGatewayEvents/APIGatewayProxyResponse.cs +++ b/Libraries/src/Amazon.Lambda.APIGatewayEvents/APIGatewayProxyResponse.cs @@ -1,4 +1,4 @@ -namespace Amazon.Lambda.APIGatewayEvents +namespace Amazon.Lambda.APIGatewayEvents { using System.Collections.Generic; using System.Runtime.Serialization; @@ -14,9 +14,7 @@ public class APIGatewayProxyResponse /// The HTTP status code for the request /// [DataMember(Name = "statusCode")] -#if NETCOREAPP3_1_OR_GREATER - [System.Text.Json.Serialization.JsonPropertyName("statusCode")] -#endif + [System.Text.Json.Serialization.JsonPropertyName("statusCode")] public int StatusCode { get; set; } /// @@ -25,9 +23,7 @@ public class APIGatewayProxyResponse /// before returning back the headers to the caller. /// [DataMember(Name = "headers")] -#if NETCOREAPP3_1_OR_GREATER - [System.Text.Json.Serialization.JsonPropertyName("headers")] -#endif + [System.Text.Json.Serialization.JsonPropertyName("headers")] public IDictionary Headers { get; set; } /// @@ -36,27 +32,21 @@ public class APIGatewayProxyResponse /// before returning back the headers to the caller. /// [DataMember(Name = "multiValueHeaders")] -#if NETCOREAPP3_1_OR_GREATER - [System.Text.Json.Serialization.JsonPropertyName("multiValueHeaders")] -#endif + [System.Text.Json.Serialization.JsonPropertyName("multiValueHeaders")] public IDictionary> MultiValueHeaders { get; set; } /// /// The response body /// [DataMember(Name = "body")] -#if NETCOREAPP3_1_OR_GREATER - [System.Text.Json.Serialization.JsonPropertyName("body")] -#endif + [System.Text.Json.Serialization.JsonPropertyName("body")] public string Body { get; set; } /// /// Flag indicating whether the body should be treated as a base64-encoded string /// [DataMember(Name = "isBase64Encoded")] -#if NETCOREAPP3_1_OR_GREATER - [System.Text.Json.Serialization.JsonPropertyName("isBase64Encoded")] -#endif + [System.Text.Json.Serialization.JsonPropertyName("isBase64Encoded")] public bool IsBase64Encoded { get; set; } } } diff --git a/Libraries/src/Amazon.Lambda.APIGatewayEvents/Amazon.Lambda.APIGatewayEvents.csproj b/Libraries/src/Amazon.Lambda.APIGatewayEvents/Amazon.Lambda.APIGatewayEvents.csproj index 0472bb651..fb3580bfa 100644 --- a/Libraries/src/Amazon.Lambda.APIGatewayEvents/Amazon.Lambda.APIGatewayEvents.csproj +++ b/Libraries/src/Amazon.Lambda.APIGatewayEvents/Amazon.Lambda.APIGatewayEvents.csproj @@ -3,7 +3,7 @@ - netstandard2.0;netcoreapp3.1;net8.0 + $(DefaultPackageTargets) Amazon Lambda .NET Core support - API Gateway package. Amazon.Lambda.APIGatewayEvents 2.7.3 @@ -19,10 +19,6 @@ --> false - - - NETSTANDARD_2_0 - diff --git a/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Amazon.Lambda.Annotations.SourceGenerator.csproj b/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Amazon.Lambda.Annotations.SourceGenerator.csproj index 925cb3715..2d2558841 100644 --- a/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Amazon.Lambda.Annotations.SourceGenerator.csproj +++ b/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Amazon.Lambda.Annotations.SourceGenerator.csproj @@ -1,7 +1,8 @@ - netstandard2.0;net6.0;net8.0 + + netstandard2.0;net8.0;net10.0 Amazon Web Services AWS Amazon Lambda @@ -34,11 +35,11 @@ - - - + + + diff --git a/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Generator.cs b/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Generator.cs index c35030190..2b92d958c 100644 --- a/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Generator.cs +++ b/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Generator.cs @@ -26,14 +26,12 @@ public class Generator : ISourceGenerator /// internal static readonly Dictionary _targetFrameworksToRuntimes = new Dictionary(2) { - { "net6.0", "dotnet6" }, { "net8.0", "dotnet8" }, { "net10.0", "dotnet10" } }; internal static readonly List _allowedRuntimeValues = new List(4) { - "dotnet6", "provided.al2", "provided.al2023", "dotnet8", @@ -102,7 +100,7 @@ public void Execute(GeneratorExecutionContext context) var globalPropertiesAttribute = assemblyAttributes .FirstOrDefault(attr => attr.AttributeClass.Name == nameof(LambdaGlobalPropertiesAttribute)); - var defaultRuntime = "dotnet6"; + var defaultRuntime = "dotnet10"; // Try to determine the target framework from the source generator context if (context.AnalyzerConfigOptions.GlobalOptions.TryGetValue("build_property.TargetFramework", out var targetFramework)) diff --git a/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Templates/APIGatewaySetupParameters.cs b/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Templates/APIGatewaySetupParameters.cs index b8c54a105..179eafb48 100644 --- a/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Templates/APIGatewaySetupParameters.cs +++ b/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Templates/APIGatewaySetupParameters.cs @@ -1,4 +1,4 @@ -// ------------------------------------------------------------------------------ +// ------------------------------------------------------------------------------ // // This code was generated by a tool. // Runtime Version: 18.0.0.0 @@ -668,7 +668,7 @@ public virtual string TransformText() this.Write("\") == false)\r\n {\r\n"); #line 267 "C:\dev\repos\aws-lambda-dotnet\Libraries\src\Amazon.Lambda.Annotations.SourceGenerator\Templates\APIGatewaySetupParameters.tt" - this.Write(this.ToStringHelper.ToStringWithCulture("#if NET6_0_OR_GREATER")); + this.Write(this.ToStringHelper.ToStringWithCulture("#if NET8_0_OR_GREATER")); #line default #line hidden @@ -798,7 +798,7 @@ public virtual string TransformText() "\n {\r\n"); #line 307 "C:\dev\repos\aws-lambda-dotnet\Libraries\src\Amazon.Lambda.Annotations.SourceGenerator\Templates\APIGatewaySetupParameters.tt" - this.Write(this.ToStringHelper.ToStringWithCulture("#if NET6_0_OR_GREATER")); + this.Write(this.ToStringHelper.ToStringWithCulture("#if NET8_0_OR_GREATER")); #line default #line hidden @@ -918,7 +918,7 @@ public virtual string TransformText() this.Write("\") == false)\r\n {\r\n"); #line 348 "C:\dev\repos\aws-lambda-dotnet\Libraries\src\Amazon.Lambda.Annotations.SourceGenerator\Templates\APIGatewaySetupParameters.tt" - this.Write(this.ToStringHelper.ToStringWithCulture("#if NET6_0_OR_GREATER")); + this.Write(this.ToStringHelper.ToStringWithCulture("#if NET8_0_OR_GREATER")); #line default #line hidden @@ -1048,7 +1048,7 @@ public virtual string TransformText() "\n {\r\n"); #line 388 "C:\dev\repos\aws-lambda-dotnet\Libraries\src\Amazon.Lambda.Annotations.SourceGenerator\Templates\APIGatewaySetupParameters.tt" - this.Write(this.ToStringHelper.ToStringWithCulture("#if NET6_0_OR_GREATER")); + this.Write(this.ToStringHelper.ToStringWithCulture("#if NET8_0_OR_GREATER")); #line default #line hidden diff --git a/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Templates/APIGatewaySetupParameters.tt b/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Templates/APIGatewaySetupParameters.tt index 53d6b47e8..8ee0aaaf1 100644 --- a/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Templates/APIGatewaySetupParameters.tt +++ b/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Templates/APIGatewaySetupParameters.tt @@ -1,4 +1,4 @@ -<#@ template language="C#" #> +<#@ template language="C#" #> <#@ assembly name="System.Core" #> <#@ import namespace="System.Linq" #> <#@ import namespace="System.Text" #> @@ -264,7 +264,7 @@ var <#= parameter.Name #> = default(<#= parameter.Type.FullName #>); if (__request__.RequestContext?.Authorizer == null || __request__.RequestContext?.Authorizer.ContainsKey("<#= authKey #>") == false) { -<#= "#if NET6_0_OR_GREATER" #> +<#= "#if NET8_0_OR_GREATER" #> __context__.Logger.LogDebug("Authorizer attribute '<#= authKey #>' was missing, returning unauthorized."); <#= "#else" #> __context__.Logger.Log("Authorizer attribute '<#= authKey #>' was missing, returning unauthorized."); @@ -304,7 +304,7 @@ } catch (Exception e) when (e is InvalidCastException || e is FormatException || e is OverflowException || e is ArgumentException) { -<#= "#if NET6_0_OR_GREATER" #> +<#= "#if NET8_0_OR_GREATER" #> __context__.Logger.LogError(e, "Failed to convert authorizer attribute '<#= authKey #>', returning unauthorized."); <#= "#else" #> __context__.Logger.Log("Failed to convert authorizer attribute '<#= authKey #>', returning unauthorized. Exception: " + e.ToString()); @@ -345,7 +345,7 @@ var <#= parameter.Name #> = default(<#= parameter.Type.FullName #>); if (__request__.RequestContext?.Authorizer?.Lambda == null || __request__.RequestContext?.Authorizer?.Lambda.ContainsKey("<#= authKey #>") == false) { -<#= "#if NET6_0_OR_GREATER" #> +<#= "#if NET8_0_OR_GREATER" #> __context__.Logger.LogDebug("Authorizer attribute '<#= authKey #>' was missing, returning unauthorized."); <#= "#else" #> __context__.Logger.Log("Authorizer attribute '<#= authKey #>' was missing, returning unauthorized."); @@ -385,7 +385,7 @@ } catch (Exception e) when (e is InvalidCastException || e is FormatException || e is OverflowException || e is ArgumentException) { -<#= "#if NET6_0_OR_GREATER" #> +<#= "#if NET8_0_OR_GREATER" #> __context__.Logger.LogError(e, "Failed to convert authorizer attribute '<#= authKey #>', returning unauthorized."); <#= "#else" #> __context__.Logger.Log("Failed to convert authorizer attribute '<#= authKey #>', returning unauthorized. Exception: " + e.ToString()); diff --git a/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Templates/AuthorizerSetupParameters.cs b/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Templates/AuthorizerSetupParameters.cs index d43c7c770..d61abbaaf 100644 --- a/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Templates/AuthorizerSetupParameters.cs +++ b/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Templates/AuthorizerSetupParameters.cs @@ -157,7 +157,7 @@ public virtual string TransformText() "eption)\r\n {\r\n"); #line 79 "C:\dev\repos\aws-lambda-dotnet\Libraries\src\Amazon.Lambda.Annotations.SourceGenerator\Templates\AuthorizerSetupParameters.tt" - this.Write(this.ToStringHelper.ToStringWithCulture("#if NET6_0_OR_GREATER")); + this.Write(this.ToStringHelper.ToStringWithCulture("#if NET8_0_OR_GREATER")); #line default #line hidden @@ -244,7 +244,7 @@ public virtual string TransformText() "eption)\r\n {\r\n"); #line 101 "C:\dev\repos\aws-lambda-dotnet\Libraries\src\Amazon.Lambda.Annotations.SourceGenerator\Templates\AuthorizerSetupParameters.tt" - this.Write(this.ToStringHelper.ToStringWithCulture("#if NET6_0_OR_GREATER")); + this.Write(this.ToStringHelper.ToStringWithCulture("#if NET8_0_OR_GREATER")); #line default #line hidden @@ -353,7 +353,7 @@ public virtual string TransformText() "eption)\r\n {\r\n"); #line 128 "C:\dev\repos\aws-lambda-dotnet\Libraries\src\Amazon.Lambda.Annotations.SourceGenerator\Templates\AuthorizerSetupParameters.tt" - this.Write(this.ToStringHelper.ToStringWithCulture("#if NET6_0_OR_GREATER")); + this.Write(this.ToStringHelper.ToStringWithCulture("#if NET8_0_OR_GREATER")); #line default #line hidden @@ -455,7 +455,7 @@ public virtual string TransformText() "eption)\r\n {\r\n"); #line 152 "C:\dev\repos\aws-lambda-dotnet\Libraries\src\Amazon.Lambda.Annotations.SourceGenerator\Templates\AuthorizerSetupParameters.tt" - this.Write(this.ToStringHelper.ToStringWithCulture("#if NET6_0_OR_GREATER")); + this.Write(this.ToStringHelper.ToStringWithCulture("#if NET8_0_OR_GREATER")); #line default #line hidden diff --git a/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Templates/AuthorizerSetupParameters.tt b/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Templates/AuthorizerSetupParameters.tt index 7d555a6e8..9745212f9 100644 --- a/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Templates/AuthorizerSetupParameters.tt +++ b/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Templates/AuthorizerSetupParameters.tt @@ -76,7 +76,7 @@ } catch (Exception e) when (e is InvalidCastException || e is FormatException || e is OverflowException || e is ArgumentException) { -<#= "#if NET6_0_OR_GREATER" #> +<#= "#if NET8_0_OR_GREATER" #> __context__.Logger.LogError(e, "Failed to extract authorization token."); <#= "#else" #> __context__.Logger.Log("Failed to extract authorization token. Exception: " + e.ToString()); @@ -98,7 +98,7 @@ } catch (Exception e) when (e is InvalidCastException || e is FormatException || e is OverflowException || e is ArgumentException) { -<#= "#if NET6_0_OR_GREATER" #> +<#= "#if NET8_0_OR_GREATER" #> __context__.Logger.LogError(e, "Failed to extract header '<#= headerKey #>'."); <#= "#else" #> __context__.Logger.Log("Failed to extract header '<#= headerKey #>'. Exception: " + e.ToString()); @@ -125,7 +125,7 @@ } catch (Exception e) when (e is InvalidCastException || e is FormatException || e is OverflowException || e is ArgumentException) { -<#= "#if NET6_0_OR_GREATER" #> +<#= "#if NET8_0_OR_GREATER" #> __context__.Logger.LogError(e, "Failed to extract query parameter '<#= parameterKey #>'."); <#= "#else" #> __context__.Logger.Log("Failed to extract query parameter '<#= parameterKey #>'. Exception: " + e.ToString()); @@ -149,7 +149,7 @@ } catch (Exception e) when (e is InvalidCastException || e is FormatException || e is OverflowException || e is ArgumentException) { -<#= "#if NET6_0_OR_GREATER" #> +<#= "#if NET8_0_OR_GREATER" #> __context__.Logger.LogError(e, "Failed to extract route parameter '<#= routeKey #>'."); <#= "#else" #> __context__.Logger.Log("Failed to extract route parameter '<#= routeKey #>'. Exception: " + e.ToString()); diff --git a/Libraries/src/Amazon.Lambda.Annotations/APIGateway/HttpApiAuthorizerAttribute.cs b/Libraries/src/Amazon.Lambda.Annotations/APIGateway/HttpApiAuthorizerAttribute.cs index 725c2842a..743fbb614 100644 --- a/Libraries/src/Amazon.Lambda.Annotations/APIGateway/HttpApiAuthorizerAttribute.cs +++ b/Libraries/src/Amazon.Lambda.Annotations/APIGateway/HttpApiAuthorizerAttribute.cs @@ -62,4 +62,4 @@ public class HttpApiAuthorizerAttribute : Attribute /// public int ResultTtlInSeconds { get; set; } = 0; } -} \ No newline at end of file +} diff --git a/Libraries/src/Amazon.Lambda.Annotations/APIGateway/HttpResults.cs b/Libraries/src/Amazon.Lambda.Annotations/APIGateway/HttpResults.cs index 539da4546..ece87ff63 100644 --- a/Libraries/src/Amazon.Lambda.Annotations/APIGateway/HttpResults.cs +++ b/Libraries/src/Amazon.Lambda.Annotations/APIGateway/HttpResults.cs @@ -5,7 +5,7 @@ using System.Net; using Amazon.Lambda.Core; -#if NET6_0_OR_GREATER +#if !NETSTANDARD2_0 using System.Buffers; using System.Text.Json; using System.Text.Json.Serialization; diff --git a/Libraries/src/Amazon.Lambda.Annotations/APIGateway/RestApiAuthorizerAttribute.cs b/Libraries/src/Amazon.Lambda.Annotations/APIGateway/RestApiAuthorizerAttribute.cs index d6db9fa04..935597261 100644 --- a/Libraries/src/Amazon.Lambda.Annotations/APIGateway/RestApiAuthorizerAttribute.cs +++ b/Libraries/src/Amazon.Lambda.Annotations/APIGateway/RestApiAuthorizerAttribute.cs @@ -69,4 +69,4 @@ public class RestApiAuthorizerAttribute : Attribute /// public int ResultTtlInSeconds { get; set; } = 0; } -} \ No newline at end of file +} diff --git a/Libraries/src/Amazon.Lambda.Annotations/Amazon.Lambda.Annotations.csproj b/Libraries/src/Amazon.Lambda.Annotations/Amazon.Lambda.Annotations.csproj index 1c8dfe8c6..a50ae0d09 100644 --- a/Libraries/src/Amazon.Lambda.Annotations/Amazon.Lambda.Annotations.csproj +++ b/Libraries/src/Amazon.Lambda.Annotations/Amazon.Lambda.Annotations.csproj @@ -2,7 +2,13 @@ Amazon.Lambda.Annotations - netstandard2.0;net6.0;net8.0 + + netstandard2.0;net8.0;net10.0 true false @@ -15,7 +21,7 @@ true - + IL2026,IL2067,IL2075,IL3050 true true diff --git a/Libraries/src/Amazon.Lambda.AppSyncEvents/Amazon.Lambda.AppSyncEvents.csproj b/Libraries/src/Amazon.Lambda.AppSyncEvents/Amazon.Lambda.AppSyncEvents.csproj index 6fd4746ca..953e86fda 100644 --- a/Libraries/src/Amazon.Lambda.AppSyncEvents/Amazon.Lambda.AppSyncEvents.csproj +++ b/Libraries/src/Amazon.Lambda.AppSyncEvents/Amazon.Lambda.AppSyncEvents.csproj @@ -4,7 +4,7 @@ Amazon Lambda .NET support - AWS AppSync package. - net8.0 + $(DefaultPackageTargets) Amazon.Lambda.AppSyncEvents 1.0.0 Amazon.Lambda.AppSyncEvents diff --git a/Libraries/src/Amazon.Lambda.ApplicationLoadBalancerEvents/Amazon.Lambda.ApplicationLoadBalancerEvents.csproj b/Libraries/src/Amazon.Lambda.ApplicationLoadBalancerEvents/Amazon.Lambda.ApplicationLoadBalancerEvents.csproj index c24491ec6..97050e8d2 100644 --- a/Libraries/src/Amazon.Lambda.ApplicationLoadBalancerEvents/Amazon.Lambda.ApplicationLoadBalancerEvents.csproj +++ b/Libraries/src/Amazon.Lambda.ApplicationLoadBalancerEvents/Amazon.Lambda.ApplicationLoadBalancerEvents.csproj @@ -3,7 +3,7 @@ - netstandard2.0;netcoreapp3.1;net8.0 + $(DefaultPackageTargets) Amazon Lambda .NET Core support - Application Load Balancer package. Amazon.Lambda.ApplicationLoadBalancerEvents 2.2.1 @@ -18,7 +18,7 @@ false - + IL2026,IL2067,IL2075 true true diff --git a/Libraries/src/Amazon.Lambda.ApplicationLoadBalancerEvents/ApplicationLoadBalancerResponse.cs b/Libraries/src/Amazon.Lambda.ApplicationLoadBalancerEvents/ApplicationLoadBalancerResponse.cs index 70b200fc8..39e9a0888 100644 --- a/Libraries/src/Amazon.Lambda.ApplicationLoadBalancerEvents/ApplicationLoadBalancerResponse.cs +++ b/Libraries/src/Amazon.Lambda.ApplicationLoadBalancerEvents/ApplicationLoadBalancerResponse.cs @@ -14,18 +14,14 @@ public class ApplicationLoadBalancerResponse /// The HTTP status code for the request /// [DataMember(Name = "statusCode")] -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("statusCode")] -#endif public int StatusCode { get; set; } /// /// The HTTP status description for the request /// [DataMember(Name = "statusDescription")] -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("statusDescription")] -#endif public string StatusDescription { get; set; } /// @@ -33,9 +29,7 @@ public class ApplicationLoadBalancerResponse /// Note: Use this property when "Multi value headers" is disabled on ELB Target Group. /// [DataMember(Name = "headers")] -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("headers")] -#endif public IDictionary Headers { get; set; } /// @@ -43,27 +37,21 @@ public class ApplicationLoadBalancerResponse /// Note: Use this property when "Multi value headers" is enabled on ELB Target Group. /// [DataMember(Name = "multiValueHeaders")] -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("multiValueHeaders")] -#endif public IDictionary> MultiValueHeaders { get; set; } /// /// The response body /// [DataMember(Name = "body")] -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("body")] -#endif public string Body { get; set; } /// /// Flag indicating whether the body should be treated as a base64-encoded string /// [DataMember(Name = "isBase64Encoded")] -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("isBase64Encoded")] -#endif public bool IsBase64Encoded { get; set; } } -} \ No newline at end of file +} diff --git a/Libraries/src/Amazon.Lambda.AspNetCoreServer.Hosting/Amazon.Lambda.AspNetCoreServer.Hosting.csproj b/Libraries/src/Amazon.Lambda.AspNetCoreServer.Hosting/Amazon.Lambda.AspNetCoreServer.Hosting.csproj index a22fd248c..6fbcf5c8b 100644 --- a/Libraries/src/Amazon.Lambda.AspNetCoreServer.Hosting/Amazon.Lambda.AspNetCoreServer.Hosting.csproj +++ b/Libraries/src/Amazon.Lambda.AspNetCoreServer.Hosting/Amazon.Lambda.AspNetCoreServer.Hosting.csproj @@ -4,7 +4,7 @@ Package for running ASP.NET Core applications using the Minimal API style as a AWS Lambda function. - net8.0;net10.0 + $(DefaultPackageTargets) enable enable 1.10.0 diff --git a/Libraries/src/Amazon.Lambda.AspNetCoreServer.Hosting/HostingOptions.cs b/Libraries/src/Amazon.Lambda.AspNetCoreServer.Hosting/HostingOptions.cs index d5d435e89..cf7ef34ba 100644 --- a/Libraries/src/Amazon.Lambda.AspNetCoreServer.Hosting/HostingOptions.cs +++ b/Libraries/src/Amazon.Lambda.AspNetCoreServer.Hosting/HostingOptions.cs @@ -19,7 +19,7 @@ public class HostingOptions /// The ILambdaSerializer used by Lambda to convert the incoming event JSON into the .NET event type and serialize the .NET response type /// back to JSON to return to Lambda. /// - public ILambdaSerializer Serializer { get; set; } + public ILambdaSerializer? Serializer { get; set; } /// /// The default response content encoding to use when no explicit content type or content encoding mapping is registered. diff --git a/Libraries/src/Amazon.Lambda.AspNetCoreServer.Hosting/Internal/LambdaRuntimeSupportServer.cs b/Libraries/src/Amazon.Lambda.AspNetCoreServer.Hosting/Internal/LambdaRuntimeSupportServer.cs index 4218d463e..c19229a4a 100644 --- a/Libraries/src/Amazon.Lambda.AspNetCoreServer.Hosting/Internal/LambdaRuntimeSupportServer.cs +++ b/Libraries/src/Amazon.Lambda.AspNetCoreServer.Hosting/Internal/LambdaRuntimeSupportServer.cs @@ -84,7 +84,7 @@ protected override HandlerWrapper CreateHandlerWrapper(IServiceProvider serviceP handler.EnableResponseStreaming = hostingOptions?.EnableResponseStreaming ?? false; #pragma warning restore CA2252 Func> bufferedHandler = handler.FunctionHandlerAsync; - return HandlerWrapper.GetHandlerWrapper(bufferedHandler, this.Serializer); + return HandlerWrapper.GetHandlerWrapper(bufferedHandler, Serializer); } /// @@ -128,6 +128,7 @@ public APIGatewayHttpApiV2MinimalApi(IServiceProvider serviceProvider) } } + /// protected override IEnumerable GetBeforeSnapshotRequests() { foreach (var collector in _beforeSnapshotRequestsCollectors) @@ -135,6 +136,7 @@ protected override IEnumerable GetBeforeSnapshotRequests() yield return collector.Request; } + /// protected override void PostMarshallRequestFeature(IHttpRequestFeature aspNetCoreRequestFeature, APIGatewayEvents.APIGatewayHttpApiV2ProxyRequest lambdaRequest, ILambdaContext lambdaContext) { base.PostMarshallRequestFeature(aspNetCoreRequestFeature, lambdaRequest, lambdaContext); @@ -143,6 +145,7 @@ protected override void PostMarshallRequestFeature(IHttpRequestFeature aspNetCor _hostingOptions?.PostMarshallRequestFeature?.Invoke(aspNetCoreRequestFeature, lambdaRequest, lambdaContext); } + /// protected override void PostMarshallResponseFeature(IHttpResponseFeature aspNetCoreResponseFeature, APIGatewayEvents.APIGatewayHttpApiV2ProxyResponse lambdaResponse, ILambdaContext lambdaContext) { base.PostMarshallResponseFeature(aspNetCoreResponseFeature, lambdaResponse, lambdaContext); @@ -151,6 +154,7 @@ protected override void PostMarshallResponseFeature(IHttpResponseFeature aspNetC _hostingOptions?.PostMarshallResponseFeature?.Invoke(aspNetCoreResponseFeature, lambdaResponse, lambdaContext); } + /// protected override void PostMarshallConnectionFeature(IHttpConnectionFeature aspNetCoreConnectionFeature, APIGatewayEvents.APIGatewayHttpApiV2ProxyRequest lambdaRequest, ILambdaContext lambdaContext) { base.PostMarshallConnectionFeature(aspNetCoreConnectionFeature, lambdaRequest, lambdaContext); @@ -159,6 +163,7 @@ protected override void PostMarshallConnectionFeature(IHttpConnectionFeature asp _hostingOptions?.PostMarshallConnectionFeature?.Invoke(aspNetCoreConnectionFeature, lambdaRequest, lambdaContext); } + /// protected override void PostMarshallHttpAuthenticationFeature(IHttpAuthenticationFeature aspNetCoreHttpAuthenticationFeature, APIGatewayEvents.APIGatewayHttpApiV2ProxyRequest lambdaRequest, ILambdaContext lambdaContext) { base.PostMarshallHttpAuthenticationFeature(aspNetCoreHttpAuthenticationFeature, lambdaRequest, lambdaContext); @@ -167,6 +172,7 @@ protected override void PostMarshallHttpAuthenticationFeature(IHttpAuthenticatio _hostingOptions?.PostMarshallHttpAuthenticationFeature?.Invoke(aspNetCoreHttpAuthenticationFeature, lambdaRequest, lambdaContext); } + /// protected override void PostMarshallTlsConnectionFeature(ITlsConnectionFeature aspNetCoreConnectionFeature, APIGatewayEvents.APIGatewayHttpApiV2ProxyRequest lambdaRequest, ILambdaContext lambdaContext) { base.PostMarshallTlsConnectionFeature(aspNetCoreConnectionFeature, lambdaRequest, lambdaContext); @@ -175,6 +181,7 @@ protected override void PostMarshallTlsConnectionFeature(ITlsConnectionFeature a _hostingOptions?.PostMarshallTlsConnectionFeature?.Invoke(aspNetCoreConnectionFeature, lambdaRequest, lambdaContext); } + /// protected override void PostMarshallItemsFeatureFeature(IItemsFeature aspNetCoreItemFeature, APIGatewayEvents.APIGatewayHttpApiV2ProxyRequest lambdaRequest, ILambdaContext lambdaContext) { base.PostMarshallItemsFeatureFeature(aspNetCoreItemFeature, lambdaRequest, lambdaContext); @@ -213,7 +220,7 @@ protected override HandlerWrapper CreateHandlerWrapper(IServiceProvider serviceP handler.EnableResponseStreaming = hostingOptions?.EnableResponseStreaming ?? false; #pragma warning restore CA2252 Func> bufferedHandler = handler.FunctionHandlerAsync; - return HandlerWrapper.GetHandlerWrapper(bufferedHandler, this.Serializer); + return HandlerWrapper.GetHandlerWrapper(bufferedHandler, Serializer); } /// @@ -257,6 +264,7 @@ public APIGatewayRestApiMinimalApi(IServiceProvider serviceProvider) } } + /// protected override IEnumerable GetBeforeSnapshotRequests() { foreach (var collector in _beforeSnapshotRequestsCollectors) @@ -264,6 +272,7 @@ protected override IEnumerable GetBeforeSnapshotRequests() yield return collector.Request; } + /// protected override void PostMarshallRequestFeature(IHttpRequestFeature aspNetCoreRequestFeature, APIGatewayEvents.APIGatewayProxyRequest lambdaRequest, ILambdaContext lambdaContext) { base.PostMarshallRequestFeature(aspNetCoreRequestFeature, lambdaRequest, lambdaContext); @@ -272,6 +281,7 @@ protected override void PostMarshallRequestFeature(IHttpRequestFeature aspNetCor _hostingOptions?.PostMarshallRequestFeature?.Invoke(aspNetCoreRequestFeature, lambdaRequest, lambdaContext); } + /// protected override void PostMarshallResponseFeature(IHttpResponseFeature aspNetCoreResponseFeature, APIGatewayEvents.APIGatewayProxyResponse lambdaResponse, ILambdaContext lambdaContext) { base.PostMarshallResponseFeature(aspNetCoreResponseFeature, lambdaResponse, lambdaContext); @@ -280,6 +290,7 @@ protected override void PostMarshallResponseFeature(IHttpResponseFeature aspNetC _hostingOptions?.PostMarshallResponseFeature?.Invoke(aspNetCoreResponseFeature, lambdaResponse, lambdaContext); } + /// protected override void PostMarshallConnectionFeature(IHttpConnectionFeature aspNetCoreConnectionFeature, APIGatewayEvents.APIGatewayProxyRequest lambdaRequest, ILambdaContext lambdaContext) { base.PostMarshallConnectionFeature(aspNetCoreConnectionFeature, lambdaRequest, lambdaContext); @@ -288,6 +299,7 @@ protected override void PostMarshallConnectionFeature(IHttpConnectionFeature asp _hostingOptions?.PostMarshallConnectionFeature?.Invoke(aspNetCoreConnectionFeature, lambdaRequest, lambdaContext); } + /// protected override void PostMarshallHttpAuthenticationFeature(IHttpAuthenticationFeature aspNetCoreHttpAuthenticationFeature, APIGatewayEvents.APIGatewayProxyRequest lambdaRequest, ILambdaContext lambdaContext) { base.PostMarshallHttpAuthenticationFeature(aspNetCoreHttpAuthenticationFeature, lambdaRequest, lambdaContext); @@ -296,6 +308,7 @@ protected override void PostMarshallHttpAuthenticationFeature(IHttpAuthenticatio _hostingOptions?.PostMarshallHttpAuthenticationFeature?.Invoke(aspNetCoreHttpAuthenticationFeature, lambdaRequest, lambdaContext); } + /// protected override void PostMarshallTlsConnectionFeature(ITlsConnectionFeature aspNetCoreConnectionFeature, APIGatewayEvents.APIGatewayProxyRequest lambdaRequest, ILambdaContext lambdaContext) { base.PostMarshallTlsConnectionFeature(aspNetCoreConnectionFeature, lambdaRequest, lambdaContext); @@ -304,6 +317,7 @@ protected override void PostMarshallTlsConnectionFeature(ITlsConnectionFeature a _hostingOptions?.PostMarshallTlsConnectionFeature?.Invoke(aspNetCoreConnectionFeature, lambdaRequest, lambdaContext); } + /// protected override void PostMarshallItemsFeatureFeature(IItemsFeature aspNetCoreItemFeature, APIGatewayEvents.APIGatewayProxyRequest lambdaRequest, ILambdaContext lambdaContext) { base.PostMarshallItemsFeatureFeature(aspNetCoreItemFeature, lambdaRequest, lambdaContext); @@ -342,7 +356,7 @@ protected override HandlerWrapper CreateHandlerWrapper(IServiceProvider serviceP handler.EnableResponseStreaming = hostingOptions?.EnableResponseStreaming ?? false; #pragma warning restore CA2252 Func> bufferedHandler = handler.FunctionHandlerAsync; - return HandlerWrapper.GetHandlerWrapper(bufferedHandler, this.Serializer); + return HandlerWrapper.GetHandlerWrapper(bufferedHandler, Serializer); } /// @@ -386,6 +400,7 @@ public ApplicationLoadBalancerMinimalApi(IServiceProvider serviceProvider) } } + /// protected override IEnumerable GetBeforeSnapshotRequests() { foreach (var collector in _beforeSnapshotRequestsCollectors) @@ -393,6 +408,7 @@ protected override IEnumerable GetBeforeSnapshotRequests() yield return collector.Request; } + /// protected override void PostMarshallRequestFeature(IHttpRequestFeature aspNetCoreRequestFeature, ApplicationLoadBalancerEvents.ApplicationLoadBalancerRequest lambdaRequest, ILambdaContext lambdaContext) { base.PostMarshallRequestFeature(aspNetCoreRequestFeature, lambdaRequest, lambdaContext); @@ -401,6 +417,7 @@ protected override void PostMarshallRequestFeature(IHttpRequestFeature aspNetCor _hostingOptions?.PostMarshallRequestFeature?.Invoke(aspNetCoreRequestFeature, lambdaRequest, lambdaContext); } + /// protected override void PostMarshallResponseFeature(IHttpResponseFeature aspNetCoreResponseFeature, ApplicationLoadBalancerEvents.ApplicationLoadBalancerResponse lambdaResponse, ILambdaContext lambdaContext) { base.PostMarshallResponseFeature(aspNetCoreResponseFeature, lambdaResponse, lambdaContext); @@ -409,6 +426,7 @@ protected override void PostMarshallResponseFeature(IHttpResponseFeature aspNetC _hostingOptions?.PostMarshallResponseFeature?.Invoke(aspNetCoreResponseFeature, lambdaResponse, lambdaContext); } + /// protected override void PostMarshallConnectionFeature(IHttpConnectionFeature aspNetCoreConnectionFeature, ApplicationLoadBalancerEvents.ApplicationLoadBalancerRequest lambdaRequest, ILambdaContext lambdaContext) { base.PostMarshallConnectionFeature(aspNetCoreConnectionFeature, lambdaRequest, lambdaContext); @@ -417,6 +435,7 @@ protected override void PostMarshallConnectionFeature(IHttpConnectionFeature asp _hostingOptions?.PostMarshallConnectionFeature?.Invoke(aspNetCoreConnectionFeature, lambdaRequest, lambdaContext); } + /// protected override void PostMarshallHttpAuthenticationFeature(IHttpAuthenticationFeature aspNetCoreHttpAuthenticationFeature, ApplicationLoadBalancerEvents.ApplicationLoadBalancerRequest lambdaRequest, ILambdaContext lambdaContext) { base.PostMarshallHttpAuthenticationFeature(aspNetCoreHttpAuthenticationFeature, lambdaRequest, lambdaContext); @@ -425,6 +444,7 @@ protected override void PostMarshallHttpAuthenticationFeature(IHttpAuthenticatio _hostingOptions?.PostMarshallHttpAuthenticationFeature?.Invoke(aspNetCoreHttpAuthenticationFeature, lambdaRequest, lambdaContext); } + /// protected override void PostMarshallTlsConnectionFeature(ITlsConnectionFeature aspNetCoreConnectionFeature, ApplicationLoadBalancerEvents.ApplicationLoadBalancerRequest lambdaRequest, ILambdaContext lambdaContext) { base.PostMarshallTlsConnectionFeature(aspNetCoreConnectionFeature, lambdaRequest, lambdaContext); @@ -433,6 +453,7 @@ protected override void PostMarshallTlsConnectionFeature(ITlsConnectionFeature a _hostingOptions?.PostMarshallTlsConnectionFeature?.Invoke(aspNetCoreConnectionFeature, lambdaRequest, lambdaContext); } + /// protected override void PostMarshallItemsFeatureFeature(IItemsFeature aspNetCoreItemFeature, ApplicationLoadBalancerEvents.ApplicationLoadBalancerRequest lambdaRequest, ILambdaContext lambdaContext) { base.PostMarshallItemsFeatureFeature(aspNetCoreItemFeature, lambdaRequest, lambdaContext); diff --git a/Libraries/src/Amazon.Lambda.AspNetCoreServer.Hosting/ServiceCollectionExtensions.cs b/Libraries/src/Amazon.Lambda.AspNetCoreServer.Hosting/ServiceCollectionExtensions.cs index bd4089df0..ed8d8ccf1 100644 --- a/Libraries/src/Amazon.Lambda.AspNetCoreServer.Hosting/ServiceCollectionExtensions.cs +++ b/Libraries/src/Amazon.Lambda.AspNetCoreServer.Hosting/ServiceCollectionExtensions.cs @@ -82,7 +82,11 @@ public static IServiceCollection AddAWSLambdaHosting(this IServiceCollection ser { if(TryLambdaSetup(services, eventSource, configure, out var hostingOptions)) { - services.TryAddSingleton(serializer ?? hostingOptions!.Serializer); + var localSerializer = serializer ?? hostingOptions!.Serializer; + if (localSerializer == null) + throw new ArgumentNullException(nameof(serializer)); + + services.TryAddSingleton(localSerializer); } return services; @@ -91,7 +95,7 @@ public static IServiceCollection AddAWSLambdaHosting(this IServiceCollection ser /// /// Adds a > that will be used to invoke /// Routes in your lambda function in order to initialize the ASP.NET Core and Lambda pipelines - /// during . This improves the performance gains + /// during . This improves the performance gains /// offered by SnapStart. /// /// must have a relative @@ -104,7 +108,7 @@ public static IServiceCollection AddAWSLambdaHosting(this IServiceCollection ser /// When the function handler is called as part of SnapStart warm up, the instance will use a /// mock , which will not be fully populated. /// - /// This method automatically registers with . + /// This method automatically registers with . /// /// This method can be called multiple times to register additional urls. /// diff --git a/Libraries/src/Amazon.Lambda.AspNetCoreServer/APIGatewayHttpApiV2ProxyFunction.cs b/Libraries/src/Amazon.Lambda.AspNetCoreServer/APIGatewayHttpApiV2ProxyFunction.cs index 3026a1f67..819306c30 100644 --- a/Libraries/src/Amazon.Lambda.AspNetCoreServer/APIGatewayHttpApiV2ProxyFunction.cs +++ b/Libraries/src/Amazon.Lambda.AspNetCoreServer/APIGatewayHttpApiV2ProxyFunction.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Net; @@ -54,7 +54,6 @@ private protected override void InternalCustomResponseExceptionHandling(APIGatew apiGatewayResponse.SetHeaderValues("ErrorType", ex.GetType().Name, false); } -#if NET8_0_OR_GREATER /// /// Override for HTTP API v2 to use single-value headers in the streaming prelude /// instead of multiValueHeaders. API Gateway HTTP API v2 expects the headers @@ -92,7 +91,6 @@ protected override Amazon.Lambda.Core.ResponseStreaming.HttpResponseStreamPrelud return prelude; } -#endif /// /// Convert the JSON document received from API Gateway into the InvokeFeatures object. @@ -287,6 +285,8 @@ protected override APIGatewayHttpApiV2ProxyResponse MarshallResponse(IHttpRespon response.Headers["Content-Type"] = null; } +// Disabled in case the user's ASP.NET Core application is still using the older API that set the body on the response feature instead of the new API that sets the body on the HttpResponse object. +#pragma warning disable CS0618 if (responseFeatures.Body != null) { // Figure out how we should treat the response content, check encoding first to see if body is compressed, then check content type @@ -299,6 +299,7 @@ protected override APIGatewayHttpApiV2ProxyResponse MarshallResponse(IHttpRespon (response.Body, response.IsBase64Encoded) = Utilities.ConvertAspNetCoreBodyToLambdaBody(responseFeatures.Body, rcEncoding); } +#pragma warning restore CS0618 PostMarshallResponseFeature(responseFeatures, response, lambdaContext); diff --git a/Libraries/src/Amazon.Lambda.AspNetCoreServer/APIGatewayProxyFunction.cs b/Libraries/src/Amazon.Lambda.AspNetCoreServer/APIGatewayProxyFunction.cs index 9cbca5c39..7f2a8b3b7 100644 --- a/Libraries/src/Amazon.Lambda.AspNetCoreServer/APIGatewayProxyFunction.cs +++ b/Libraries/src/Amazon.Lambda.AspNetCoreServer/APIGatewayProxyFunction.cs @@ -359,6 +359,8 @@ protected override APIGatewayProxyResponse MarshallResponse(IHttpResponseFeature response.MultiValueHeaders["Content-Type"] = new List() { null }; } +// Disabled in case the user's ASP.NET Core application is still using the older API that set the body on the response feature instead of the new API that sets the body on the HttpResponse object. +#pragma warning disable CS0618 if (responseFeatures.Body != null) { // Figure out how we should treat the response content, check encoding first to see if body is compressed, then check content type @@ -371,7 +373,7 @@ protected override APIGatewayProxyResponse MarshallResponse(IHttpResponseFeature (response.Body, response.IsBase64Encoded) = Utilities.ConvertAspNetCoreBodyToLambdaBody(responseFeatures.Body, rcEncoding); } - +#pragma warning restore CS0618 PostMarshallResponseFeature(responseFeatures, response, lambdaContext); _logger.LogDebug($"Response Base 64 Encoded: {response.IsBase64Encoded}"); diff --git a/Libraries/src/Amazon.Lambda.AspNetCoreServer/AbstractAspNetCoreFunction.cs b/Libraries/src/Amazon.Lambda.AspNetCoreServer/AbstractAspNetCoreFunction.cs index 1b1e20092..51c930333 100644 --- a/Libraries/src/Amazon.Lambda.AspNetCoreServer/AbstractAspNetCoreFunction.cs +++ b/Libraries/src/Amazon.Lambda.AspNetCoreServer/AbstractAspNetCoreFunction.cs @@ -126,8 +126,8 @@ protected AbstractAspNetCoreFunction(StartupMode startupMode) protected AbstractAspNetCoreFunction(IServiceProvider hostedServices) { _hostServices = hostedServices; - _server = this._hostServices.GetService(typeof(Microsoft.AspNetCore.Hosting.Server.IServer)) as LambdaServer; - _logger = ActivatorUtilities.CreateInstance>>(this._hostServices); + _server = _hostServices.GetService(typeof(Microsoft.AspNetCore.Hosting.Server.IServer)) as LambdaServer; + _logger = ActivatorUtilities.CreateInstance>>(_hostServices); AddRegisterBeforeSnapshot(); } @@ -367,16 +367,16 @@ protected void Start() PostCreateHost(host); host.Start(); - this._hostServices = host.Services; + _hostServices = host.Services; - _server = this._hostServices.GetService(typeof(Microsoft.AspNetCore.Hosting.Server.IServer)) as LambdaServer; + _server = _hostServices.GetService(typeof(Microsoft.AspNetCore.Hosting.Server.IServer)) as LambdaServer; if (_server == null) { throw new Exception("Failed to find the Lambda implementation for the IServer interface in the IServiceProvider for the Host. This happens if UseLambdaServer was " + "not called when constructing the IWebHostBuilder. If CreateHostBuilder was overridden it is recommended that ConfigureWebHostLambdaDefaults should be used " + "instead of ConfigureWebHostDefaults to make sure the property Lambda services are registered."); } - _logger = ActivatorUtilities.CreateInstance>>(this._hostServices); + _logger = ActivatorUtilities.CreateInstance>>(_hostServices); AddRegisterBeforeSnapshot(); } @@ -487,18 +487,18 @@ public virtual async Task FunctionHandlerAsync(TREQUEST request, ILam #pragma warning disable CA2252 if (EnableResponseStreaming) { - await ExecuteStreamingRequestAsync(features, request, lambdaContext); + await ExecuteStreamingRequestAsync(features); return default; } #pragma warning restore CA2252 - var scope = this._hostServices.CreateScope(); + var scope = _hostServices.CreateScope(); try { ((IServiceProvidersFeature)features).RequestServices = scope.ServiceProvider; - var context = this.CreateContext(features); - var response = await this.ProcessRequest(lambdaContext, context, features); + var context = CreateContext(features); + var response = await ProcessRequest(lambdaContext, context, features); return response; } @@ -516,7 +516,7 @@ public virtual async Task FunctionHandlerAsync(TREQUEST request, ILam /// An instance. /// /// If specified, an unhandled exception will be rethrown for custom error handling. - /// Ensure that the error handling code calls 'this.MarshallResponse(features, 500);' after handling the error to return a the typed Lambda object to the user. + /// Ensure that the error handling code calls 'MarshallResponse(features, 500);' after handling the error to return a the typed Lambda object to the user. /// protected async Task ProcessRequest(ILambdaContext lambdaContext, object context, InvokeFeatures features, bool rethrowUnhandledError = false) { @@ -532,7 +532,7 @@ protected async Task ProcessRequest(ILambdaContext lambdaContext, obj { ex = e; if (rethrowUnhandledError) throw; - _logger.LogError(e, $"Unknown error responding to request: {this.ErrorReport(e)}"); + _logger.LogError(e, $"Unknown error responding to request: {ErrorReport(e)}"); ((IHttpResponseFeature)features).StatusCode = 500; } @@ -540,7 +540,7 @@ protected async Task ProcessRequest(ILambdaContext lambdaContext, obj { await features.ResponseStartingEvents.ExecuteAsync(); } - var response = this.MarshallResponse(features, lambdaContext, defaultStatusCode); + var response = MarshallResponse(features, lambdaContext, defaultStatusCode); if (ex != null && IncludeUnhandledExceptionDetailInResponse) { @@ -556,7 +556,7 @@ protected async Task ProcessRequest(ILambdaContext lambdaContext, obj } finally { - this._server.Application.DisposeContext(context, ex); + _server.Application.DisposeContext(context, ex); } } @@ -566,15 +566,6 @@ private protected virtual void InternalCustomResponseExceptionHandling(TRESPONSE } - /// - /// This method is called after the IWebHost is created from the IWebHostBuilder and the services have been configured. The - /// WebHost hasn't been started yet. - /// - /// - protected virtual void PostCreateWebHost(IWebHost webHost) - { - - } /// /// This method is called after the IHost is created from the IHostBuilder and the services have been configured. The @@ -711,7 +702,7 @@ protected virtual System.IO.Stream CreateLambdaResponseStream( /// . /// [System.Runtime.Versioning.RequiresPreviewFeatures(ParameterizedPreviewMessage)] - private async Task ExecuteStreamingRequestAsync(InvokeFeatures features, TREQUEST request, ILambdaContext lambdaContext) + private async Task ExecuteStreamingRequestAsync(InvokeFeatures features) { var responseFeature = (IHttpResponseFeature)features; System.IO.Stream lambdaStream = null; @@ -730,13 +721,13 @@ private async Task ExecuteStreamingRequestAsync(InvokeFeatures features, TREQUES var streamingBodyFeature = new Internal.StreamingResponseBodyFeature(_logger, responseFeature, OpenStream); features[typeof(IHttpResponseBodyFeature)] = streamingBodyFeature; - var scope = this._hostServices.CreateScope(); + var scope = _hostServices.CreateScope(); Exception pipelineException = null; try { ((IServiceProvidersFeature)features).RequestServices = scope.ServiceProvider; - var context = this.CreateContext(features); + var context = CreateContext(features); try { try @@ -786,7 +777,7 @@ private async Task ExecuteStreamingRequestAsync(InvokeFeatures features, TREQUES await features.ResponseCompletedEvents.ExecuteAsync(); } - this._server.Application.DisposeContext(context, pipelineException); + _server.Application.DisposeContext(context, pipelineException); } } finally @@ -804,14 +795,14 @@ private async Task RunPipelineAsync(object context, InvokeFeatures features) { try { - await this._server.Application.ProcessRequestAsync(context); + await _server.Application.ProcessRequestAsync(context); } catch (AggregateException agex) { _logger.LogError(agex, $"Caught AggregateException: '{agex}'"); var sb = new StringBuilder(); foreach (var newEx in agex.InnerExceptions) - sb.AppendLine(this.ErrorReport(newEx)); + sb.AppendLine(ErrorReport(newEx)); _logger.LogError(sb.ToString()); ((IHttpResponseFeature)features).StatusCode = 500; throw; @@ -826,7 +817,7 @@ private async Task RunPipelineAsync(object context, InvokeFeatures features) if (fileNotFoundException != null && !string.IsNullOrEmpty(fileNotFoundException.FileName)) sb.AppendLine($"Missing file: {fileNotFoundException.FileName}"); else - sb.AppendLine(this.ErrorReport(loaderException)); + sb.AppendLine(ErrorReport(loaderException)); } _logger.LogError(sb.ToString()); ((IHttpResponseFeature)features).StatusCode = 500; diff --git a/Libraries/src/Amazon.Lambda.AspNetCoreServer/Amazon.Lambda.AspNetCoreServer.csproj b/Libraries/src/Amazon.Lambda.AspNetCoreServer/Amazon.Lambda.AspNetCoreServer.csproj index ea382d609..1104dfdf8 100644 --- a/Libraries/src/Amazon.Lambda.AspNetCoreServer/Amazon.Lambda.AspNetCoreServer.csproj +++ b/Libraries/src/Amazon.Lambda.AspNetCoreServer/Amazon.Lambda.AspNetCoreServer.csproj @@ -4,7 +4,7 @@ Amazon.Lambda.AspNetCoreServer makes it easy to run ASP.NET Core Web API applications as AWS Lambda functions. - net8.0;net10.0 + $(DefaultPackageTargets) Amazon.Lambda.AspNetCoreServer 9.2.1 Amazon.Lambda.AspNetCoreServer @@ -27,7 +27,7 @@ - + diff --git a/Libraries/src/Amazon.Lambda.AspNetCoreServer/ApplicationLoadBalancerFunction.cs b/Libraries/src/Amazon.Lambda.AspNetCoreServer/ApplicationLoadBalancerFunction.cs index f6f8c638e..2bb08c711 100644 --- a/Libraries/src/Amazon.Lambda.AspNetCoreServer/ApplicationLoadBalancerFunction.cs +++ b/Libraries/src/Amazon.Lambda.AspNetCoreServer/ApplicationLoadBalancerFunction.cs @@ -77,7 +77,7 @@ protected override void MarshallRequest(InvokeFeatures features, ApplicationLoad // marshalling the response to know whether to fill in the the Headers or MultiValueHeaders collection. // Since a Lambda function compute environment is only one processing one event at a time it is safe to store // this as a member variable. - this._multiHeaderValuesEnabled = lambdaRequest.MultiValueHeaders != null; + _multiHeaderValuesEnabled = lambdaRequest.MultiValueHeaders != null; { var requestFeatures = (IHttpRequestFeature)features; @@ -160,14 +160,14 @@ protected override ApplicationLoadBalancerResponse MarshallResponse(IHttpRespons if (responseFeatures.Headers != null) { - if (this._multiHeaderValuesEnabled) + if (_multiHeaderValuesEnabled) response.MultiValueHeaders = new Dictionary>(); else response.Headers = new Dictionary(); foreach (var kvp in responseFeatures.Headers) { - if (this._multiHeaderValuesEnabled) + if (_multiHeaderValuesEnabled) { response.MultiValueHeaders[kvp.Key] = kvp.Value.ToList(); } @@ -188,6 +188,8 @@ protected override ApplicationLoadBalancerResponse MarshallResponse(IHttpRespons } } +// Disabled in case the user's ASP.NET Core application is still using the older API that set the body on the response feature instead of the new API that sets the body on the HttpResponse object. +#pragma warning disable CS0618 if (responseFeatures.Body != null) { // Figure out how we should treat the response content, check encoding first to see if body is compressed, then check content type @@ -199,6 +201,7 @@ protected override ApplicationLoadBalancerResponse MarshallResponse(IHttpRespons (response.Body, response.IsBase64Encoded) = Utilities.ConvertAspNetCoreBodyToLambdaBody(responseFeatures.Body, rcEncoding); } +#pragma warning restore CS0618 PostMarshallResponseFeature(responseFeatures, response, lambdaContext); @@ -211,7 +214,7 @@ private protected override void InternalCustomResponseExceptionHandling(Applicat { var errorName = ex.GetType().Name; - if (this._multiHeaderValuesEnabled) + if (_multiHeaderValuesEnabled) { lambdaResponse.MultiValueHeaders.Add(new KeyValuePair>("ErrorType", new List { errorName })); } @@ -227,7 +230,7 @@ private protected override void InternalCustomResponseExceptionHandling(Applicat private string GetSingleHeaderValue(ApplicationLoadBalancerRequest request, string headerName) { - if (this._multiHeaderValuesEnabled) + if (_multiHeaderValuesEnabled) { var kvp = request.MultiValueHeaders.FirstOrDefault(x => string.Equals(x.Key, headerName, StringComparison.OrdinalIgnoreCase)); if (!kvp.Equals(default(KeyValuePair>))) diff --git a/Libraries/src/Amazon.Lambda.AspNetCoreServer/Internal/HttpRequestMessageConverter.cs b/Libraries/src/Amazon.Lambda.AspNetCoreServer/Internal/HttpRequestMessageConverter.cs index 016f3dabb..3f0b9a99b 100644 --- a/Libraries/src/Amazon.Lambda.AspNetCoreServer/Internal/HttpRequestMessageConverter.cs +++ b/Libraries/src/Amazon.Lambda.AspNetCoreServer/Internal/HttpRequestMessageConverter.cs @@ -6,6 +6,9 @@ using Amazon.Lambda.ApplicationLoadBalancerEvents; using Microsoft.AspNetCore.WebUtilities; +#pragma warning disable CS1591 // Since this class is treated as internal, we can ignore the missing XML comments for public members. + + namespace Amazon.Lambda.AspNetCoreServer.Internal { /// diff --git a/Libraries/src/Amazon.Lambda.AspNetCoreServer/Internal/InvokeFeatures.cs b/Libraries/src/Amazon.Lambda.AspNetCoreServer/Internal/InvokeFeatures.cs index 987878311..ceea34d12 100644 --- a/Libraries/src/Amazon.Lambda.AspNetCoreServer/Internal/InvokeFeatures.cs +++ b/Libraries/src/Amazon.Lambda.AspNetCoreServer/Internal/InvokeFeatures.cs @@ -112,7 +112,7 @@ public TFeature Get() public IEnumerator> GetEnumerator() { - return this._features.GetEnumerator(); + return _features.GetEnumerator(); } public void Set(TFeature instance) @@ -120,12 +120,12 @@ public void Set(TFeature instance) if (instance == null) return; - this._features[typeof(TFeature)] = instance; + _features[typeof(TFeature)] = instance; } IEnumerator IEnumerable.GetEnumerator() { - return this._features.GetEnumerator(); + return _features.GetEnumerator(); } #endregion @@ -192,27 +192,27 @@ Stream IHttpResponseFeature.Body void IHttpResponseFeature.OnStarting(Func callback, object state) { if (ResponseStartingEvents == null) - this.ResponseStartingEvents = new EventCallbacks(); + ResponseStartingEvents = new EventCallbacks(); - this.ResponseStartingEvents.Add(callback, state); + ResponseStartingEvents.Add(callback, state); } internal EventCallbacks ResponseCompletedEvents { get; private set; } void IHttpResponseFeature.OnCompleted(Func callback, object state) { - if (this.ResponseCompletedEvents == null) - this.ResponseCompletedEvents = new EventCallbacks(); + if (ResponseCompletedEvents == null) + ResponseCompletedEvents = new EventCallbacks(); - this.ResponseCompletedEvents.Add(callback, state); + ResponseCompletedEvents.Add(callback, state); } internal class EventCallbacks { - List _callbacks = new List(); + readonly List _callbacks = new List(); internal void Add(Func callback, object state) { - this._callbacks.Add(new EventCallback(callback, state)); + _callbacks.Add(new EventCallback(callback, state)); } internal async Task ExecuteAsync() @@ -227,8 +227,8 @@ internal class EventCallback { internal EventCallback(Func callback, object state) { - this.Callback = callback; - this.State = state; + Callback = callback; + State = state; } Func Callback { get; } @@ -236,7 +236,7 @@ internal EventCallback(Func callback, object state) internal Task ExecuteAsync() { - var task = Callback(this.State); + var task = Callback(State); return task; } } @@ -245,7 +245,10 @@ internal Task ExecuteAsync() #endregion #region IHttpResponseBodyFeature +// Disabled in case the user's ASP.NET Core application is still using the older API that set the body on the response feature instead of the new API that sets the body on the HttpResponse object. +#pragma warning disable CS0618 Stream IHttpResponseBodyFeature.Stream => ((IHttpResponseFeature)this).Body; +#pragma warning restore CS0618 private PipeWriter _pipeWriter; @@ -373,7 +376,7 @@ string IHttpRequestIdentifierFeature.TraceIdentifier _traceIdentifier = (new Microsoft.AspNetCore.Http.Features.HttpRequestIdentifierFeature()).TraceIdentifier; return _traceIdentifier; } - set { this._traceIdentifier = value; } + set { _traceIdentifier = value; } } #endregion diff --git a/Libraries/src/Amazon.Lambda.AspNetCoreServer/Internal/LambdaServer.cs b/Libraries/src/Amazon.Lambda.AspNetCoreServer/Internal/LambdaServer.cs index b6c218dfe..257936a76 100644 --- a/Libraries/src/Amazon.Lambda.AspNetCoreServer/Internal/LambdaServer.cs +++ b/Libraries/src/Amazon.Lambda.AspNetCoreServer/Internal/LambdaServer.cs @@ -1,13 +1,12 @@ -using System; -using System.Collections.Generic; -using System.Linq; +using System; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Hosting.Server; -using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Features; +#pragma warning disable CS1591 // Since this class is treated as internal, we can ignore the missing XML comments for public members. + namespace Amazon.Lambda.AspNetCoreServer.Internal { /// @@ -28,7 +27,7 @@ public void Dispose() public virtual Task StartAsync(IHttpApplication application, CancellationToken cancellationToken) { - this.Application = new ApplicationWrapper(application); + Application = new ApplicationWrapper(application); return Task.CompletedTask; } diff --git a/Libraries/src/Amazon.Lambda.AspNetCoreServer/Internal/Utilities.cs b/Libraries/src/Amazon.Lambda.AspNetCoreServer/Internal/Utilities.cs index 43fac1a96..6e96840c8 100644 --- a/Libraries/src/Amazon.Lambda.AspNetCoreServer/Internal/Utilities.cs +++ b/Libraries/src/Amazon.Lambda.AspNetCoreServer/Internal/Utilities.cs @@ -1,4 +1,4 @@ -using Microsoft.AspNetCore.Hosting.Server; +using Microsoft.AspNetCore.Hosting.Server; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Primitives; @@ -275,7 +275,11 @@ internal static X509Certificate2 GetX509Certificate2FromPem(string clientCertPem // Remove "-----BEGIN CERTIFICATE-----\n" and "-----END CERTIFICATE-----" clientCertPem = clientCertPem.Substring(28, clientCertPem.Length - 53); +#if NET10_0_OR_GREATER + return X509CertificateLoader.LoadCertificate(Convert.FromBase64String(clientCertPem)); +#else return new X509Certificate2(Convert.FromBase64String(clientCertPem)); +#endif } } } diff --git a/Libraries/src/Amazon.Lambda.CloudWatchEvents/Amazon.Lambda.CloudWatchEvents.csproj b/Libraries/src/Amazon.Lambda.CloudWatchEvents/Amazon.Lambda.CloudWatchEvents.csproj index f64435895..466ce78f8 100644 --- a/Libraries/src/Amazon.Lambda.CloudWatchEvents/Amazon.Lambda.CloudWatchEvents.csproj +++ b/Libraries/src/Amazon.Lambda.CloudWatchEvents/Amazon.Lambda.CloudWatchEvents.csproj @@ -3,7 +3,7 @@ - netstandard2.0;netcoreapp3.1;net8.0 + $(DefaultPackageTargets) Amazon Lambda .NET Core support - CloudWatchEvents package. Amazon.Lambda.CloudWatchEvents 4.4.1 @@ -12,7 +12,7 @@ AWS;Amazon;Lambda - + IL2026,IL2067,IL2075 true true diff --git a/Libraries/src/Amazon.Lambda.CloudWatchEvents/CloudWatchEvent.cs b/Libraries/src/Amazon.Lambda.CloudWatchEvents/CloudWatchEvent.cs index 6f5259717..2128a980f 100644 --- a/Libraries/src/Amazon.Lambda.CloudWatchEvents/CloudWatchEvent.cs +++ b/Libraries/src/Amazon.Lambda.CloudWatchEvents/CloudWatchEvent.cs @@ -1,4 +1,4 @@ -namespace Amazon.Lambda.CloudWatchEvents +namespace Amazon.Lambda.CloudWatchEvents { using System; using System.Collections.Generic; @@ -40,9 +40,7 @@ public class CloudWatchEvent /// For example, ScheduledEvent will be null /// For example, ECSEvent could be "ECS Container Instance State Change" or "ECS Task State Change" /// -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("detail-type")] -#endif public string DetailType { get; set; } /// diff --git a/Libraries/src/Amazon.Lambda.CloudWatchEvents/S3Events/S3Object.cs b/Libraries/src/Amazon.Lambda.CloudWatchEvents/S3Events/S3Object.cs index 2e9abecee..2941ac5f0 100644 --- a/Libraries/src/Amazon.Lambda.CloudWatchEvents/S3Events/S3Object.cs +++ b/Libraries/src/Amazon.Lambda.CloudWatchEvents/S3Events/S3Object.cs @@ -30,9 +30,7 @@ public class S3Object /// The version ID of the object. /// [DataMember(Name = "version-id")] -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("version-id")] -#endif public string VersionId { get; set; } /// diff --git a/Libraries/src/Amazon.Lambda.CloudWatchEvents/S3Events/S3ObjectCreate.cs b/Libraries/src/Amazon.Lambda.CloudWatchEvents/S3Events/S3ObjectCreate.cs index 8f691bafe..cde7304ba 100644 --- a/Libraries/src/Amazon.Lambda.CloudWatchEvents/S3Events/S3ObjectCreate.cs +++ b/Libraries/src/Amazon.Lambda.CloudWatchEvents/S3Events/S3ObjectCreate.cs @@ -12,9 +12,7 @@ public class S3ObjectCreate : S3ObjectEventDetails /// The source IP of the API request. /// [DataMember(Name = "source-ip-address")] -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("source-ip-address")] -#endif public string SourceIpAddress { get; set; } /// diff --git a/Libraries/src/Amazon.Lambda.CloudWatchEvents/S3Events/S3ObjectDelete.cs b/Libraries/src/Amazon.Lambda.CloudWatchEvents/S3Events/S3ObjectDelete.cs index db8febc6a..99c39c8e6 100644 --- a/Libraries/src/Amazon.Lambda.CloudWatchEvents/S3Events/S3ObjectDelete.cs +++ b/Libraries/src/Amazon.Lambda.CloudWatchEvents/S3Events/S3ObjectDelete.cs @@ -12,9 +12,7 @@ public class S3ObjectDelete : S3ObjectEventDetails /// The source IP of the API request. /// [DataMember(Name = "source-ip-address")] -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("source-ip-address")] -#endif public string SourceIpAddress { get; set; } /// @@ -27,9 +25,7 @@ public class S3ObjectDelete : S3ObjectEventDetails /// The type of object deletion event. /// [DataMember(Name = "deletion-type")] -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("deletion-type")] -#endif public string DeletionType { get; set; } } } diff --git a/Libraries/src/Amazon.Lambda.CloudWatchEvents/S3Events/S3ObjectEventDetails.cs b/Libraries/src/Amazon.Lambda.CloudWatchEvents/S3Events/S3ObjectEventDetails.cs index 83352dfd8..66ac3edf1 100644 --- a/Libraries/src/Amazon.Lambda.CloudWatchEvents/S3Events/S3ObjectEventDetails.cs +++ b/Libraries/src/Amazon.Lambda.CloudWatchEvents/S3Events/S3ObjectEventDetails.cs @@ -30,9 +30,7 @@ public class S3ObjectEventDetails /// The ID of the API request. /// [DataMember(Name = "request-id")] -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("request-id")] -#endif public string RequestId { get; set; } /// diff --git a/Libraries/src/Amazon.Lambda.CloudWatchEvents/S3Events/S3ObjectRestore.cs b/Libraries/src/Amazon.Lambda.CloudWatchEvents/S3Events/S3ObjectRestore.cs index 27719f200..7a16bdc9a 100644 --- a/Libraries/src/Amazon.Lambda.CloudWatchEvents/S3Events/S3ObjectRestore.cs +++ b/Libraries/src/Amazon.Lambda.CloudWatchEvents/S3Events/S3ObjectRestore.cs @@ -12,18 +12,14 @@ public class S3ObjectRestore : S3ObjectEventDetails /// The time when the temporary copy of the object will be deleted from S3. /// [DataMember(Name = "restore-expiry-time")] -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("restore-expiry-time")] -#endif public string RestoreExpiryTime { get; set; } /// /// The storage class of the object being restored. /// [DataMember(Name = "source-storage-class")] -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("source-storage-class")] -#endif public string SourceStorageClass { get; set; } } } diff --git a/Libraries/src/Amazon.Lambda.CloudWatchEvents/TranslateEvents/TranslateParallelDataStateChange.cs b/Libraries/src/Amazon.Lambda.CloudWatchEvents/TranslateEvents/TranslateParallelDataStateChange.cs index 4edb431f0..8f8ea6e49 100644 --- a/Libraries/src/Amazon.Lambda.CloudWatchEvents/TranslateEvents/TranslateParallelDataStateChange.cs +++ b/Libraries/src/Amazon.Lambda.CloudWatchEvents/TranslateEvents/TranslateParallelDataStateChange.cs @@ -4,7 +4,7 @@ namespace Amazon.Lambda.CloudWatchEvents.TranslateEvents { /// /// This class represents the details of a Translate Parallel Data State Change - // for CreateParallelData and UpdateParallelData events sent via EventBridge. + /// for CreateParallelData and UpdateParallelData events sent via EventBridge. /// For more see - https://docs.aws.amazon.com/translate/latest/dg/monitoring-with-eventbridge.html /// public class TranslateParallelDataStateChange diff --git a/Libraries/src/Amazon.Lambda.CloudWatchLogsEvents/Amazon.Lambda.CloudWatchLogsEvents.csproj b/Libraries/src/Amazon.Lambda.CloudWatchLogsEvents/Amazon.Lambda.CloudWatchLogsEvents.csproj index ded1aba67..24d68cc12 100644 --- a/Libraries/src/Amazon.Lambda.CloudWatchLogsEvents/Amazon.Lambda.CloudWatchLogsEvents.csproj +++ b/Libraries/src/Amazon.Lambda.CloudWatchLogsEvents/Amazon.Lambda.CloudWatchLogsEvents.csproj @@ -2,7 +2,7 @@ Amazon Lambda .NET Core support - CloudWatchLogsEvents package. - netstandard2.0;netcoreapp3.1;net8.0 + $(DefaultPackageTargets) Amazon.Lambda.CloudWatchLogsEvents 2.2.1 Amazon.Lambda.CloudWatchLogsEvents @@ -10,7 +10,7 @@ AWS;Amazon;Lambda - + IL2026,IL2067,IL2075 true true diff --git a/Libraries/src/Amazon.Lambda.CloudWatchLogsEvents/CloudWatchLogsEvents.cs b/Libraries/src/Amazon.Lambda.CloudWatchLogsEvents/CloudWatchLogsEvents.cs index c25b39884..88d27ef89 100644 --- a/Libraries/src/Amazon.Lambda.CloudWatchLogsEvents/CloudWatchLogsEvents.cs +++ b/Libraries/src/Amazon.Lambda.CloudWatchLogsEvents/CloudWatchLogsEvents.cs @@ -30,9 +30,7 @@ public class Log /// The data that are base64 encoded and gziped messages in LogStreams. /// [DataMember(Name = "data", IsRequired = false)] -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("data")] -#endif public string EncodedData { get; set; } /// @@ -40,10 +38,10 @@ public class Log /// public string DecodeData() { - if (string.IsNullOrEmpty(this.EncodedData)) - return this.EncodedData; + if (string.IsNullOrEmpty(EncodedData)) + return EncodedData; - var bytes = Convert.FromBase64String(this.EncodedData); + var bytes = Convert.FromBase64String(EncodedData); var uncompressedStream = new MemoryStream(); using (var stream = new GZipStream(new MemoryStream(bytes), CompressionMode.Decompress)) diff --git a/Libraries/src/Amazon.Lambda.CognitoEvents/AccessTokenGeneration.cs b/Libraries/src/Amazon.Lambda.CognitoEvents/AccessTokenGeneration.cs index daea448eb..77749a623 100644 --- a/Libraries/src/Amazon.Lambda.CognitoEvents/AccessTokenGeneration.cs +++ b/Libraries/src/Amazon.Lambda.CognitoEvents/AccessTokenGeneration.cs @@ -14,18 +14,14 @@ public class AccessTokenGeneration /// groupOverrideDetails instead. /// [DataMember(Name = "claimsToAddOrOverride")] -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("claimsToAddOrOverride")] -# endif public Dictionary ClaimsToAddOrOverride { get; set; } = new Dictionary(); /// /// A list that contains claims to be suppressed from the identity token. /// [DataMember(Name = "claimsToSuppress")] -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("claimsToSuppress")] -# endif public List ClaimsToSuppress { get; set; } = new List(); /// @@ -33,18 +29,14 @@ public class AccessTokenGeneration /// add scope values that contain one or more blank-space characters. /// [DataMember(Name = "scopesToAdd")] -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("scopesToAdd")] -# endif public List ScopesToAdd { get; set; } = new List(); /// /// A list of OAuth 2.0 scopes that you want to remove from the scope claim in your user's access token. /// [DataMember(Name = "scopesToSuppress")] -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("scopesToSuppress")] -# endif public List ScopesToSuppress { get; set; } = new List(); } } diff --git a/Libraries/src/Amazon.Lambda.CognitoEvents/Amazon.Lambda.CognitoEvents.csproj b/Libraries/src/Amazon.Lambda.CognitoEvents/Amazon.Lambda.CognitoEvents.csproj index f8c17ffe9..451b898b8 100644 --- a/Libraries/src/Amazon.Lambda.CognitoEvents/Amazon.Lambda.CognitoEvents.csproj +++ b/Libraries/src/Amazon.Lambda.CognitoEvents/Amazon.Lambda.CognitoEvents.csproj @@ -4,7 +4,7 @@ Amazon Lambda .NET Core support - CognitoEvents package. - netstandard2.0;netcoreapp3.1;net8.0 + $(DefaultPackageTargets) Amazon.Lambda.CognitoEvents 4.0.1 Amazon.Lambda.CognitoEvents @@ -12,7 +12,7 @@ AWS;Amazon;Lambda - + IL2026,IL2067,IL2075 true true diff --git a/Libraries/src/Amazon.Lambda.CognitoEvents/ChallengeResultElement.cs b/Libraries/src/Amazon.Lambda.CognitoEvents/ChallengeResultElement.cs index a67025566..ba8f18eaf 100644 --- a/Libraries/src/Amazon.Lambda.CognitoEvents/ChallengeResultElement.cs +++ b/Libraries/src/Amazon.Lambda.CognitoEvents/ChallengeResultElement.cs @@ -1,4 +1,4 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; namespace Amazon.Lambda.CognitoEvents { @@ -12,27 +12,21 @@ public class ChallengeResultElement /// The challenge type.One of: CUSTOM_CHALLENGE, SRP_A, PASSWORD_VERIFIER, SMS_MFA, DEVICE_SRP_AUTH, DEVICE_PASSWORD_VERIFIER, or ADMIN_NO_SRP_AUTH. /// [DataMember(Name = "challengeName")] -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("challengeName")] -# endif public string ChallengeName { get; set; } /// /// Set to true if the user successfully completed the challenge, or false otherwise. /// [DataMember(Name = "challengeResult")] -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("challengeResult")] -# endif public bool ChallengeResult { get; set; } /// /// Your name for the custom challenge.Used only if challengeName is CUSTOM_CHALLENGE. /// [DataMember(Name = "challengeMetadata")] -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("challengeMetadata")] -# endif public string ChallengeMetadata { get; set; } } } diff --git a/Libraries/src/Amazon.Lambda.CognitoEvents/ClaimOverrideDetails.cs b/Libraries/src/Amazon.Lambda.CognitoEvents/ClaimOverrideDetails.cs index 190146102..2b99ac2e9 100644 --- a/Libraries/src/Amazon.Lambda.CognitoEvents/ClaimOverrideDetails.cs +++ b/Libraries/src/Amazon.Lambda.CognitoEvents/ClaimOverrideDetails.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Runtime.Serialization; namespace Amazon.Lambda.CognitoEvents @@ -13,27 +13,21 @@ public class ClaimOverrideDetails /// A map of one or more key-value pairs of claims to add or override. For group related claims, use groupOverrideDetails instead. /// [DataMember(Name = "claimsToAddOrOverride")] -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("claimsToAddOrOverride")] -# endif public Dictionary ClaimsToAddOrOverride { get; set; } = new Dictionary(); /// /// A list that contains claims to be suppressed from the identity token. /// [DataMember(Name = "claimsToSuppress")] -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("claimsToSuppress")] -# endif public List ClaimsToSuppress { get; set; } = new List(); /// /// The output object containing the current group configuration. It includes groupsToOverride, iamRolesToOverride, and preferredRole. /// [DataMember(Name = "groupOverrideDetails")] -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("groupOverrideDetails")] -# endif public GroupConfiguration GroupOverrideDetails { get; set; } = new GroupConfiguration(); } } diff --git a/Libraries/src/Amazon.Lambda.CognitoEvents/ClaimsAndScopeOverrideDetails.cs b/Libraries/src/Amazon.Lambda.CognitoEvents/ClaimsAndScopeOverrideDetails.cs index 83b483e37..f94125410 100644 --- a/Libraries/src/Amazon.Lambda.CognitoEvents/ClaimsAndScopeOverrideDetails.cs +++ b/Libraries/src/Amazon.Lambda.CognitoEvents/ClaimsAndScopeOverrideDetails.cs @@ -12,27 +12,21 @@ public class ClaimsAndScopeOverrideDetails /// The claims that you want to override, add, or suppress in your user’s ID token. /// [DataMember(Name = "idTokenGeneration")] -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("idTokenGeneration")] -# endif public IdTokenGeneration IdTokenGeneration { get; set; } = new IdTokenGeneration(); /// /// The claims and scopes that you want to override, add, or suppress in your user’s access token. /// [DataMember(Name = "accessTokenGeneration")] -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("accessTokenGeneration")] -# endif public AccessTokenGeneration AccessTokenGeneration { get; set; } = new AccessTokenGeneration(); /// /// The output object containing the current group configuration. It includes groupsToOverride, iamRolesToOverride, and preferredRole. /// [DataMember(Name = "groupOverrideDetails")] -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("groupOverrideDetails")] -# endif public GroupConfiguration GroupOverrideDetails { get; set; } = new GroupConfiguration(); } } diff --git a/Libraries/src/Amazon.Lambda.CognitoEvents/CognitoCreateAuthChallengeRequest.cs b/Libraries/src/Amazon.Lambda.CognitoEvents/CognitoCreateAuthChallengeRequest.cs index fc3937e45..e706fedb0 100644 --- a/Libraries/src/Amazon.Lambda.CognitoEvents/CognitoCreateAuthChallengeRequest.cs +++ b/Libraries/src/Amazon.Lambda.CognitoEvents/CognitoCreateAuthChallengeRequest.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Runtime.Serialization; namespace Amazon.Lambda.CognitoEvents @@ -12,36 +12,28 @@ public class CognitoCreateAuthChallengeRequest : CognitoTriggerRequest /// One or more key-value pairs that you can provide as custom input to the Lambda function that you specify for the pre sign-up trigger. You can pass this data to your Lambda function by using the ClientMetadata parameter in the following API actions: AdminCreateUser, AdminRespondToAuthChallenge, ForgotPassword, and SignUp. /// [DataMember(Name = "clientMetadata")] -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("clientMetadata")] -# endif public Dictionary ClientMetadata { get; set; } = new Dictionary(); /// /// The name of the new challenge. /// [DataMember(Name = "challengeName")] -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("challengeName")] -#endif public string ChallengeName { get; set; } /// /// an array of ChallengeResult elements /// [DataMember(Name = "session")] -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("session")] -#endif public List Session { get; set; } = new List(); /// /// A Boolean that is populated when PreventUserExistenceErrors is set to ENABLED for your user pool client. A value of true means that the user id (user name, email address, etc.) did not match any existing users. When PreventUserExistenceErrors is set to ENABLED, the service will not report back to the app that the user does not exist. The recommended best practice is for your Lambda functions to maintain the same user experience including latency so the caller cannot detect different behavior when the user exists or doesn’t exist. /// [DataMember(Name = "userNotFound")] -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("userNotFound")] -#endif public bool UserNotFound { get; set; } } } diff --git a/Libraries/src/Amazon.Lambda.CognitoEvents/CognitoCreateAuthChallengeResponse.cs b/Libraries/src/Amazon.Lambda.CognitoEvents/CognitoCreateAuthChallengeResponse.cs index 2af64454b..6ec61af73 100644 --- a/Libraries/src/Amazon.Lambda.CognitoEvents/CognitoCreateAuthChallengeResponse.cs +++ b/Libraries/src/Amazon.Lambda.CognitoEvents/CognitoCreateAuthChallengeResponse.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Runtime.Serialization; namespace Amazon.Lambda.CognitoEvents @@ -12,27 +12,21 @@ public class CognitoCreateAuthChallengeResponse : CognitoTriggerResponse /// One or more key-value pairs for the client app to use in the challenge to be presented to the user.This parameter should contain all of the necessary information to accurately present the challenge to the user. /// [DataMember(Name = "publicChallengeParameters")] -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("publicChallengeParameters")] -#endif public Dictionary PublicChallengeParameters { get; set; } = new Dictionary(); /// /// This parameter is only used by the Verify Auth Challenge Response Lambda trigger. This parameter should contain all of the information that is required to validate the user's response to the challenge. In other words, the publicChallengeParameters parameter contains the question that is presented to the user and privateChallengeParameters contains the valid answers for the question. /// [DataMember(Name = "privateChallengeParameters")] -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("privateChallengeParameters")] -#endif public Dictionary PrivateChallengeParameters { get; set; } = new Dictionary(); /// /// Your name for the custom challenge, if this is a custom challenge. /// [DataMember(Name = "challengeMetadata")] -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("challengeMetadata")] -#endif public string ChallengeMetadata { get; set; } } } diff --git a/Libraries/src/Amazon.Lambda.CognitoEvents/CognitoCustomEmailSenderRequest.cs b/Libraries/src/Amazon.Lambda.CognitoEvents/CognitoCustomEmailSenderRequest.cs index 73deb6935..f2c52e240 100644 --- a/Libraries/src/Amazon.Lambda.CognitoEvents/CognitoCustomEmailSenderRequest.cs +++ b/Libraries/src/Amazon.Lambda.CognitoEvents/CognitoCustomEmailSenderRequest.cs @@ -1,4 +1,4 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; namespace Amazon.Lambda.CognitoEvents { @@ -11,18 +11,14 @@ public class CognitoCustomEmailSenderRequest : CognitoTriggerRequest /// The type of sender request. /// [DataMember(Name = "type")] -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("type")] -# endif public string Type { get; set; } /// /// The encrypted temporary authorization code. /// [DataMember(Name = "code")] -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("code")] -#endif public string Code { get; set; } } } diff --git a/Libraries/src/Amazon.Lambda.CognitoEvents/CognitoCustomMessageRequest.cs b/Libraries/src/Amazon.Lambda.CognitoEvents/CognitoCustomMessageRequest.cs index 7730c5d92..d276c837f 100644 --- a/Libraries/src/Amazon.Lambda.CognitoEvents/CognitoCustomMessageRequest.cs +++ b/Libraries/src/Amazon.Lambda.CognitoEvents/CognitoCustomMessageRequest.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Runtime.Serialization; namespace Amazon.Lambda.CognitoEvents @@ -12,27 +12,21 @@ public class CognitoCustomMessageRequest : CognitoTriggerRequest /// A string for you to use as the placeholder for the verification code in the custom message. /// [DataMember(Name = "codeParameter")] -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("codeParameter")] -#endif public string CodeParameter { get; set; } /// /// The username parameter. It is a required request parameter for the admin create user flow. /// [DataMember(Name = "usernameParameter")] -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("usernameParameter")] -#endif public string UsernameParameter { get; set; } /// /// One or more key-value pairs that you can provide as custom input to the Lambda function that you specify for the pre sign-up trigger. You can pass this data to your Lambda function by using the ClientMetadata parameter in the following API actions: AdminCreateUser, AdminRespondToAuthChallenge, ForgotPassword, and SignUp. /// [DataMember(Name = "clientMetadata")] -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("clientMetadata")] -#endif public Dictionary ClientMetadata { get; set; } = new Dictionary(); } } diff --git a/Libraries/src/Amazon.Lambda.CognitoEvents/CognitoCustomMessageResponse.cs b/Libraries/src/Amazon.Lambda.CognitoEvents/CognitoCustomMessageResponse.cs index 5dc25737c..979bdeb51 100644 --- a/Libraries/src/Amazon.Lambda.CognitoEvents/CognitoCustomMessageResponse.cs +++ b/Libraries/src/Amazon.Lambda.CognitoEvents/CognitoCustomMessageResponse.cs @@ -1,4 +1,4 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; namespace Amazon.Lambda.CognitoEvents { @@ -11,27 +11,21 @@ public class CognitoCustomMessageResponse : CognitoTriggerResponse /// The custom SMS message to be sent to your users. Must include the codeParameter value received in the request. /// [DataMember(Name = "smsMessage")] -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("smsMessage")] -#endif public string SmsMessage { get; set; } /// /// The custom email message to be sent to your users. Must include the codeParameter value received in the request. /// [DataMember(Name = "emailMessage")] -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("emailMessage")] -#endif public string EmailMessage { get; set; } /// /// The subject line for the custom message. /// [DataMember(Name = "emailSubject")] -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("emailSubject")] -#endif public string EmailSubject { get; set; } } } diff --git a/Libraries/src/Amazon.Lambda.CognitoEvents/CognitoCustomSmsSenderRequest.cs b/Libraries/src/Amazon.Lambda.CognitoEvents/CognitoCustomSmsSenderRequest.cs index 64c830c00..6ef4b1dcd 100644 --- a/Libraries/src/Amazon.Lambda.CognitoEvents/CognitoCustomSmsSenderRequest.cs +++ b/Libraries/src/Amazon.Lambda.CognitoEvents/CognitoCustomSmsSenderRequest.cs @@ -1,4 +1,4 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; namespace Amazon.Lambda.CognitoEvents { @@ -11,18 +11,14 @@ public class CognitoCustomSmsSenderRequest : CognitoTriggerRequest /// The type of sender request. /// [DataMember(Name = "type")] -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("type")] -#endif public string Type { get; set; } /// /// The encrypted temporary authorization code. /// [DataMember(Name = "code")] -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("code")] -#endif public string Code { get; set; } } } diff --git a/Libraries/src/Amazon.Lambda.CognitoEvents/CognitoDefineAuthChallengeRequest.cs b/Libraries/src/Amazon.Lambda.CognitoEvents/CognitoDefineAuthChallengeRequest.cs index 7cacf29ae..357840f8b 100644 --- a/Libraries/src/Amazon.Lambda.CognitoEvents/CognitoDefineAuthChallengeRequest.cs +++ b/Libraries/src/Amazon.Lambda.CognitoEvents/CognitoDefineAuthChallengeRequest.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Runtime.Serialization; namespace Amazon.Lambda.CognitoEvents @@ -12,27 +12,21 @@ public class CognitoDefineAuthChallengeRequest : CognitoTriggerRequest /// One or more key-value pairs that you can provide as custom input to the Lambda function that you specify for the pre sign-up trigger. You can pass this data to your Lambda function by using the ClientMetadata parameter in the following API actions: AdminCreateUser, AdminRespondToAuthChallenge, ForgotPassword, and SignUp. /// [DataMember(Name = "clientMetadata")] -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("clientMetadata")] -# endif public Dictionary ClientMetadata { get; set; } = new Dictionary(); /// /// an array of ChallengeResult elements /// [DataMember(Name = "session")] -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("session")] -# endif public List Session { get; set; } = new List(); /// /// A Boolean that is populated when PreventUserExistenceErrors is set to ENABLED for your user pool client. A value of true means that the user id (user name, email address, etc.) did not match any existing users. When PreventUserExistenceErrors is set to ENABLED, the service will not report back to the app that the user does not exist. The recommended best practice is for your Lambda functions to maintain the same user experience including latency so the caller cannot detect different behavior when the user exists or doesn’t exist. /// [DataMember(Name = "userNotFound")] -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("userNotFound")] -#endif public bool UserNotFound { get; set; } } } diff --git a/Libraries/src/Amazon.Lambda.CognitoEvents/CognitoDefineAuthChallengeResponse.cs b/Libraries/src/Amazon.Lambda.CognitoEvents/CognitoDefineAuthChallengeResponse.cs index 4ab868f7c..5f9057013 100644 --- a/Libraries/src/Amazon.Lambda.CognitoEvents/CognitoDefineAuthChallengeResponse.cs +++ b/Libraries/src/Amazon.Lambda.CognitoEvents/CognitoDefineAuthChallengeResponse.cs @@ -1,4 +1,4 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; namespace Amazon.Lambda.CognitoEvents { @@ -11,27 +11,21 @@ public class CognitoDefineAuthChallengeResponse : CognitoTriggerResponse /// A string containing the name of the next challenge. If you want to present a new challenge to your user, specify the challenge name here. /// [DataMember(Name = "challengeName")] -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("challengeName")] -#endif public string ChallengeName { get; set; } /// /// Set to true if you determine that the user has been sufficiently authenticated by completing the challenges, or false otherwise. /// [DataMember(Name = "issueTokens")] -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("issueTokens")] -#endif public bool? IssueTokens { get; set; } /// /// Set to true if you want to terminate the current authentication process, or false otherwise. /// [DataMember(Name = "failAuthentication")] -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("failAuthentication")] -#endif public bool? FailAuthentication { get; set; } } } diff --git a/Libraries/src/Amazon.Lambda.CognitoEvents/CognitoMigrateUserRequest.cs b/Libraries/src/Amazon.Lambda.CognitoEvents/CognitoMigrateUserRequest.cs index 2570ac473..dd0c4ec9d 100644 --- a/Libraries/src/Amazon.Lambda.CognitoEvents/CognitoMigrateUserRequest.cs +++ b/Libraries/src/Amazon.Lambda.CognitoEvents/CognitoMigrateUserRequest.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Runtime.Serialization; namespace Amazon.Lambda.CognitoEvents @@ -12,36 +12,28 @@ public class CognitoMigrateUserRequest : CognitoTriggerRequest /// The username entered by the user. /// [DataMember(Name = "userName")] -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("userName")] -#endif public string UserName { get; set; } /// /// The password entered by the user for sign-in. It is not set in the forgot-password flow. /// [DataMember(Name = "password")] -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("password")] -#endif public string Password { get; set; } /// /// One or more name-value pairs containing the validation data in the request to register a user. The validation data is set and then passed from the client in the request to register a user. You can pass this data to your Lambda function by using the ClientMetadata parameter in the InitiateAuth and AdminInitiateAuth API actions. /// [DataMember(Name = "validationData")] -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("validationData")] -#endif public Dictionary ValidationData { get; set; } = new Dictionary(); /// /// One or more key-value pairs that you can provide as custom input to the Lambda function that you specify for the pre sign-up trigger. You can pass this data to your Lambda function by using the ClientMetadata parameter in the following API actions: AdminCreateUser, AdminRespondToAuthChallenge, ForgotPassword, and SignUp. /// [DataMember(Name = "clientMetadata")] -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("clientMetadata")] -#endif public Dictionary ClientMetadata { get; set; } = new Dictionary(); } } diff --git a/Libraries/src/Amazon.Lambda.CognitoEvents/CognitoMigrateUserResponse.cs b/Libraries/src/Amazon.Lambda.CognitoEvents/CognitoMigrateUserResponse.cs index 7babcd417..267a5f405 100644 --- a/Libraries/src/Amazon.Lambda.CognitoEvents/CognitoMigrateUserResponse.cs +++ b/Libraries/src/Amazon.Lambda.CognitoEvents/CognitoMigrateUserResponse.cs @@ -12,45 +12,35 @@ public class CognitoMigrateUserResponse : CognitoTriggerResponse /// It must contain one or more name-value pairs representing user attributes to be stored in the user profile in your user pool. You can include both standard and custom user attributes. Custom attributes require the custom: prefix to distinguish them from standard attributes. /// [DataMember(Name = "userAttributes")] -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("userAttributes")] -#endif public Dictionary UserAttributes { get; set; } = new Dictionary(); /// /// During sign-in, this attribute can be set to CONFIRMED, or not set, to auto-confirm your users and allow them to sign-in with their previous passwords. This is the simplest experience for the user. /// [DataMember(Name = "finalUserStatus")] -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("finalUserStatus")] -#endif public string FinalUserStatus { get; set; } /// /// This attribute can be set to "SUPPRESS" to suppress the welcome message usually sent by Amazon Cognito to new users. If this attribute is not returned, the welcome message will be sent. /// [DataMember(Name = "messageAction")] -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("messageAction")] -#endif public string MessageAction { get; set; } /// /// This attribute can be set to "EMAIL" to send the welcome message by email, or "SMS" to send the welcome message by SMS. If this attribute is not returned, the welcome message will be sent by SMS. /// [DataMember(Name = "desiredDeliveryMediums")] -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("desiredDeliveryMediums")] -#endif public List DesiredDeliveryMediums { get; set; } = new List(); /// /// If this parameter is set to "true" and the phone number or email address specified in the UserAttributes parameter already exists as an alias with a different user, the API call will migrate the alias from the previous user to the newly created user. The previous user will no longer be able to log in using that alias. /// [DataMember(Name = "forceAliasCreation")] -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("forceAliasCreation")] -#endif public bool? ForceAliasCreation { get; set; } } } diff --git a/Libraries/src/Amazon.Lambda.CognitoEvents/CognitoPostAuthenticationRequest.cs b/Libraries/src/Amazon.Lambda.CognitoEvents/CognitoPostAuthenticationRequest.cs index 96e95d5cf..87aff4ac5 100644 --- a/Libraries/src/Amazon.Lambda.CognitoEvents/CognitoPostAuthenticationRequest.cs +++ b/Libraries/src/Amazon.Lambda.CognitoEvents/CognitoPostAuthenticationRequest.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Runtime.Serialization; namespace Amazon.Lambda.CognitoEvents @@ -13,27 +13,21 @@ public class CognitoPostAuthenticationRequest : CognitoTriggerRequest /// One or more name-value pairs containing the validation data in the request to register a user. The validation data is set and then passed from the client in the request to register a user. You can pass this data to your Lambda function by using the ClientMetadata parameter in the InitiateAuth and AdminInitiateAuth API actions. /// [DataMember(Name = "validationData")] -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("validationData")] -# endif public Dictionary ValidationData { get; set; } = new Dictionary(); /// /// This flag indicates if the user has signed in on a new device. It is set only if the remembered devices value of the user pool is set to Always or User Opt-In. /// [DataMember(Name = "newDeviceUsed")] -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("newDeviceUsed")] -#endif public bool NewDevicedUsed { get; set; } /// /// One or more key-value pairs that you can provide as custom input to the Lambda function that you specify for the pre sign-up trigger. You can pass this data to your Lambda function by using the ClientMetadata parameter in the following API actions: AdminCreateUser, AdminRespondToAuthChallenge, ForgotPassword, and SignUp. /// [DataMember(Name = "clientMetadata")] -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("clientMetadata")] -# endif public Dictionary ClientMetadata { get; set; } = new Dictionary(); } } diff --git a/Libraries/src/Amazon.Lambda.CognitoEvents/CognitoPostConfirmationRequest.cs b/Libraries/src/Amazon.Lambda.CognitoEvents/CognitoPostConfirmationRequest.cs index 8446bc599..97486f0e5 100644 --- a/Libraries/src/Amazon.Lambda.CognitoEvents/CognitoPostConfirmationRequest.cs +++ b/Libraries/src/Amazon.Lambda.CognitoEvents/CognitoPostConfirmationRequest.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Runtime.Serialization; namespace Amazon.Lambda.CognitoEvents @@ -13,9 +13,7 @@ public class CognitoPostConfirmationRequest : CognitoTriggerRequest /// One or more key-value pairs that you can provide as custom input to the Lambda function that you specify for the pre sign-up trigger. You can pass this data to your Lambda function by using the ClientMetadata parameter in the following API actions: AdminCreateUser, AdminRespondToAuthChallenge, ForgotPassword, and SignUp. /// [DataMember(Name = "clientMetadata")] -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("clientMetadata")] -#endif public Dictionary ClientMetadata { get; set; } = new Dictionary(); } } diff --git a/Libraries/src/Amazon.Lambda.CognitoEvents/CognitoPreAuthenticationRequest.cs b/Libraries/src/Amazon.Lambda.CognitoEvents/CognitoPreAuthenticationRequest.cs index 3db08a010..bd1a0153c 100644 --- a/Libraries/src/Amazon.Lambda.CognitoEvents/CognitoPreAuthenticationRequest.cs +++ b/Libraries/src/Amazon.Lambda.CognitoEvents/CognitoPreAuthenticationRequest.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Runtime.Serialization; namespace Amazon.Lambda.CognitoEvents @@ -13,18 +13,14 @@ public class CognitoPreAuthenticationRequest : CognitoTriggerRequest /// One or more name-value pairs containing the validation data in the request to register a user. The validation data is set and then passed from the client in the request to register a user. You can pass this data to your Lambda function by using the ClientMetadata parameter in the InitiateAuth and AdminInitiateAuth API actions. /// [DataMember(Name = "validationData")] -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("validationData")] -# endif public Dictionary ValidationData { get; set; } = new Dictionary(); /// /// This boolean is populated when PreventUserExistenceErrors is set to ENABLED for your User Pool client. /// [DataMember(Name = "userNotFound")] -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("userNotFound")] -#endif public bool UserNotFound { get; set; } } } diff --git a/Libraries/src/Amazon.Lambda.CognitoEvents/CognitoPreSignupRequest.cs b/Libraries/src/Amazon.Lambda.CognitoEvents/CognitoPreSignupRequest.cs index 3df29217a..891038b9c 100644 --- a/Libraries/src/Amazon.Lambda.CognitoEvents/CognitoPreSignupRequest.cs +++ b/Libraries/src/Amazon.Lambda.CognitoEvents/CognitoPreSignupRequest.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Runtime.Serialization; namespace Amazon.Lambda.CognitoEvents @@ -13,18 +13,14 @@ public class CognitoPreSignupRequest : CognitoTriggerRequest /// One or more name-value pairs containing the validation data in the request to register a user. The validation data is set and then passed from the client in the request to register a user. You can pass this data to your Lambda function by using the ClientMetadata parameter in the InitiateAuth and AdminInitiateAuth API actions. /// [DataMember(Name = "validationData")] -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("validationData")] -#endif public Dictionary ValidationData { get; set; } = new Dictionary(); /// /// One or more key-value pairs that you can provide as custom input to the Lambda function that you specify for the pre sign-up trigger. You can pass this data to your Lambda function by using the ClientMetadata parameter in the following API actions: AdminCreateUser, AdminRespondToAuthChallenge, ForgotPassword, and SignUp. /// [DataMember(Name = "clientMetadata")] -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("clientMetadata")] -#endif public Dictionary ClientMetadata { get; set; } = new Dictionary(); } } diff --git a/Libraries/src/Amazon.Lambda.CognitoEvents/CognitoPreSignupResponse.cs b/Libraries/src/Amazon.Lambda.CognitoEvents/CognitoPreSignupResponse.cs index d7ac1b6a9..acc5733fc 100644 --- a/Libraries/src/Amazon.Lambda.CognitoEvents/CognitoPreSignupResponse.cs +++ b/Libraries/src/Amazon.Lambda.CognitoEvents/CognitoPreSignupResponse.cs @@ -1,4 +1,4 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; namespace Amazon.Lambda.CognitoEvents { @@ -12,27 +12,21 @@ public class CognitoPreSignupResponse : CognitoTriggerResponse /// Set to true to auto-confirm the user, or false otherwise. /// [DataMember(Name = "autoConfirmUser")] -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("autoConfirmUser")] -#endif public bool AutoConfirmUser { get; set; } /// /// Set to true to set as verified the email of a user who is signing up, or false otherwise. If autoVerifyEmail is set to true, the email attribute must have a valid, non-null value. Otherwise an error will occur and the user will not be able to complete sign-up. /// [DataMember(Name = "autoVerifyPhone")] -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("autoVerifyPhone")] -#endif public bool AutoVerifyPhone { get; set; } /// /// Set to true to set as verified the phone number of a user who is signing up, or false otherwise. If autoVerifyPhone is set to true, the phone_number attribute must have a valid, non-null value. Otherwise an error will occur and the user will not be able to complete sign-up. /// [DataMember(Name = "autoVerifyEmail")] -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("autoVerifyEmail")] -#endif public bool AutoVerifyEmail { get; set; } } } diff --git a/Libraries/src/Amazon.Lambda.CognitoEvents/CognitoPreTokenGenerationRequest.cs b/Libraries/src/Amazon.Lambda.CognitoEvents/CognitoPreTokenGenerationRequest.cs index 78c1395bb..6d4ae42f6 100644 --- a/Libraries/src/Amazon.Lambda.CognitoEvents/CognitoPreTokenGenerationRequest.cs +++ b/Libraries/src/Amazon.Lambda.CognitoEvents/CognitoPreTokenGenerationRequest.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Runtime.Serialization; namespace Amazon.Lambda.CognitoEvents @@ -12,18 +12,14 @@ public class CognitoPreTokenGenerationRequest : CognitoTriggerRequest /// The input object containing the current group configuration. It includes groupsToOverride, iamRolesToOverride, and preferredRole. /// [DataMember(Name = "groupConfiguration")] -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("groupConfiguration")] -# endif public GroupConfiguration GroupConfiguration { get; set; } = new GroupConfiguration(); /// /// One or more key-value pairs that you can provide as custom input to the Lambda function that you specify for the pre sign-up trigger. You can pass this data to your Lambda function by using the ClientMetadata parameter in the following API actions: AdminVerifyUser, AdminRespondToAuthChallenge, ForgotPassword, and SignUp. /// [DataMember(Name = "clientMetadata")] -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("clientMetadata")] -# endif public Dictionary ClientMetadata { get; set; } = new Dictionary(); } } diff --git a/Libraries/src/Amazon.Lambda.CognitoEvents/CognitoPreTokenGenerationResponse.cs b/Libraries/src/Amazon.Lambda.CognitoEvents/CognitoPreTokenGenerationResponse.cs index 819f39603..3a8adacd2 100644 --- a/Libraries/src/Amazon.Lambda.CognitoEvents/CognitoPreTokenGenerationResponse.cs +++ b/Libraries/src/Amazon.Lambda.CognitoEvents/CognitoPreTokenGenerationResponse.cs @@ -1,4 +1,4 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; namespace Amazon.Lambda.CognitoEvents { @@ -11,9 +11,7 @@ public class CognitoPreTokenGenerationResponse : CognitoTriggerResponse /// Pre token generation response parameters /// [DataMember(Name = "claimsOverrideDetails")] -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("claimsOverrideDetails")] -# endif public ClaimOverrideDetails ClaimsOverrideDetails { get; set; } = new ClaimOverrideDetails(); } } diff --git a/Libraries/src/Amazon.Lambda.CognitoEvents/CognitoPreTokenGenerationV2Request.cs b/Libraries/src/Amazon.Lambda.CognitoEvents/CognitoPreTokenGenerationV2Request.cs index 3ef60f7ed..1ba3be186 100644 --- a/Libraries/src/Amazon.Lambda.CognitoEvents/CognitoPreTokenGenerationV2Request.cs +++ b/Libraries/src/Amazon.Lambda.CognitoEvents/CognitoPreTokenGenerationV2Request.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Runtime.Serialization; namespace Amazon.Lambda.CognitoEvents @@ -12,27 +12,21 @@ public class CognitoPreTokenGenerationV2Request : CognitoTriggerRequest /// The input object containing the current group configuration. It includes groupsToOverride, iamRolesToOverride, and preferredRole. /// [DataMember(Name = "groupConfiguration")] -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("groupConfiguration")] -# endif public GroupConfiguration GroupConfiguration { get; set; } = new GroupConfiguration(); /// /// One or more key-value pairs that you can provide as custom input to the Lambda function that you specify for the pre sign-up trigger. You can pass this data to your Lambda function by using the ClientMetadata parameter in the following API actions: AdminVerifyUser, AdminRespondToAuthChallenge, ForgotPassword, and SignUp. /// [DataMember(Name = "clientMetadata")] -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("clientMetadata")] -# endif public Dictionary ClientMetadata { get; set; } = new Dictionary(); /// /// A list that contains the OAuth 2.0 user scopes. /// [DataMember(Name = "scopes")] -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("scopes")] -# endif public List Scopes { get; set; } = new List(); } } diff --git a/Libraries/src/Amazon.Lambda.CognitoEvents/CognitoPreTokenGenerationV2Response.cs b/Libraries/src/Amazon.Lambda.CognitoEvents/CognitoPreTokenGenerationV2Response.cs index 981a1aa83..2f4e8e40f 100644 --- a/Libraries/src/Amazon.Lambda.CognitoEvents/CognitoPreTokenGenerationV2Response.cs +++ b/Libraries/src/Amazon.Lambda.CognitoEvents/CognitoPreTokenGenerationV2Response.cs @@ -1,4 +1,4 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; namespace Amazon.Lambda.CognitoEvents { @@ -11,9 +11,7 @@ public class CognitoPreTokenGenerationV2Response : CognitoTriggerResponse /// A container for all elements in a V2_0 trigger event. /// [DataMember(Name = "claimsAndScopeOverrideDetails")] -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("claimsAndScopeOverrideDetails")] -# endif public ClaimsAndScopeOverrideDetails ClaimsAndScopeOverrideDetails { get; set; } = new ClaimsAndScopeOverrideDetails(); } } diff --git a/Libraries/src/Amazon.Lambda.CognitoEvents/CognitoTriggerCallerContext.cs b/Libraries/src/Amazon.Lambda.CognitoEvents/CognitoTriggerCallerContext.cs index a9f73bb70..4030a529b 100644 --- a/Libraries/src/Amazon.Lambda.CognitoEvents/CognitoTriggerCallerContext.cs +++ b/Libraries/src/Amazon.Lambda.CognitoEvents/CognitoTriggerCallerContext.cs @@ -1,4 +1,4 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; namespace Amazon.Lambda.CognitoEvents { @@ -12,18 +12,14 @@ public class CognitoTriggerCallerContext /// The AWS SDK version number. /// [DataMember(Name = "awsSdkVersion")] -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("awsSdkVersion")] -#endif public string AwsSdkVersion { get; set; } /// /// The ID of the client associated with the user pool. /// [DataMember(Name = "clientId")] -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("clientId")] -#endif public string ClientId { get; set; } } diff --git a/Libraries/src/Amazon.Lambda.CognitoEvents/CognitoTriggerEvent.cs b/Libraries/src/Amazon.Lambda.CognitoEvents/CognitoTriggerEvent.cs index ff57fc458..997a66175 100644 --- a/Libraries/src/Amazon.Lambda.CognitoEvents/CognitoTriggerEvent.cs +++ b/Libraries/src/Amazon.Lambda.CognitoEvents/CognitoTriggerEvent.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Runtime.Serialization; namespace Amazon.Lambda.CognitoEvents @@ -16,72 +16,56 @@ public abstract class CognitoTriggerEvent /// The version number of your Lambda function. /// [DataMember(Name = "version")] -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("version")] -#endif public string Version { get; set; } /// /// The AWS Region, as an AWSRegion instance. /// [DataMember(Name = "region")] -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("region")] -#endif public string Region { get; set; } /// /// The user pool ID for the user pool. /// [DataMember(Name = "userPoolId")] -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("userPoolId")] -#endif public string UserPoolId { get; set; } /// /// The username of the current user. /// [DataMember(Name = "userName")] -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("userName")] -#endif public string UserName { get; set; } /// /// The caller context /// [DataMember(Name = "callerContext")] -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("callerContext")] -#endif public CognitoTriggerCallerContext CallerContext { get; set; } = new CognitoTriggerCallerContext(); /// /// The name of the event that triggered the Lambda function.For a description of each triggerSource see User pool Lambda trigger sources. /// [DataMember(Name = "triggerSource")] -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("triggerSource")] -#endif public string TriggerSource { get; set; } /// /// The request from the Amazon Cognito service /// [DataMember(Name = "request")] -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("request")] -#endif public TRequest Request { get; set; } = new TRequest(); /// /// The response from your Lambda trigger.The return parameters in the response depend on the triggering event. /// [DataMember(Name = "response")] -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("response")] -#endif public TResponse Response { get; set; } = new TResponse(); } -} \ No newline at end of file +} diff --git a/Libraries/src/Amazon.Lambda.CognitoEvents/CognitoTriggerRequest.cs b/Libraries/src/Amazon.Lambda.CognitoEvents/CognitoTriggerRequest.cs index 11f8749b8..ce5fe46f2 100644 --- a/Libraries/src/Amazon.Lambda.CognitoEvents/CognitoTriggerRequest.cs +++ b/Libraries/src/Amazon.Lambda.CognitoEvents/CognitoTriggerRequest.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Runtime.Serialization; namespace Amazon.Lambda.CognitoEvents @@ -13,9 +13,7 @@ public abstract class CognitoTriggerRequest /// One or more pairs of user attribute names and values.Each pair is in the form "name": "value". /// [DataMember(Name = "userAttributes")] -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("userAttributes")] -#endif public Dictionary UserAttributes { get; set; } = new Dictionary(); } } diff --git a/Libraries/src/Amazon.Lambda.CognitoEvents/CognitoVerifyAuthChallengeRequest.cs b/Libraries/src/Amazon.Lambda.CognitoEvents/CognitoVerifyAuthChallengeRequest.cs index c03f15b02..c099e04d0 100644 --- a/Libraries/src/Amazon.Lambda.CognitoEvents/CognitoVerifyAuthChallengeRequest.cs +++ b/Libraries/src/Amazon.Lambda.CognitoEvents/CognitoVerifyAuthChallengeRequest.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Runtime.Serialization; namespace Amazon.Lambda.CognitoEvents @@ -12,36 +12,28 @@ public class CognitoVerifyAuthChallengeRequest : CognitoTriggerRequest /// This parameter comes from the Create Auth Challenge trigger, and is compared against a user’s challengeAnswer to determine whether the user passed the challenge. /// [DataMember(Name = "privateChallengeParameters")] -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("privateChallengeParameters")] -# endif public Dictionary PrivateChallengeParameters { get; set; } = new Dictionary(); /// /// This parameter comes from the Create Auth Challenge trigger, and is compared against a user’s challengeAnswer to determine whether the user passed the challenge. /// [DataMember(Name = "challengeAnswer")] -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("challengeAnswer")] -# endif public string ChallengeAnswer { get; set; } = string.Empty; /// /// One or more key-value pairs that you can provide as custom input to the Lambda function that you specify for the pre sign-up trigger. You can pass this data to your Lambda function by using the ClientMetadata parameter in the following API actions: AdminVerifyUser, AdminRespondToAuthChallenge, ForgotPassword, and SignUp. /// [DataMember(Name = "clientMetadata")] -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("clientMetadata")] -# endif public Dictionary ClientMetadata { get; set; } /// /// This boolean is populated when PreventUserExistenceErrors is set to ENABLED for your User Pool client. /// [DataMember(Name = "userNotFound")] -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("userNotFound")] -# endif public bool UserNotFound { get; set; } } } diff --git a/Libraries/src/Amazon.Lambda.CognitoEvents/CognitoVerifyAuthChallengeResponse.cs b/Libraries/src/Amazon.Lambda.CognitoEvents/CognitoVerifyAuthChallengeResponse.cs index 4b1d3e527..af147aad5 100644 --- a/Libraries/src/Amazon.Lambda.CognitoEvents/CognitoVerifyAuthChallengeResponse.cs +++ b/Libraries/src/Amazon.Lambda.CognitoEvents/CognitoVerifyAuthChallengeResponse.cs @@ -1,4 +1,4 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; namespace Amazon.Lambda.CognitoEvents { @@ -11,9 +11,7 @@ public class CognitoVerifyAuthChallengeResponse : CognitoTriggerResponse /// Set to true if the user has successfully completed the challenge, or false otherwise. /// [DataMember(Name = "answerCorrect")] -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("answerCorrect")] -#endif public bool? AnswerCorrect { get; set; } } } diff --git a/Libraries/src/Amazon.Lambda.CognitoEvents/GroupConfiguration.cs b/Libraries/src/Amazon.Lambda.CognitoEvents/GroupConfiguration.cs index f8a261db0..2e0d4f132 100644 --- a/Libraries/src/Amazon.Lambda.CognitoEvents/GroupConfiguration.cs +++ b/Libraries/src/Amazon.Lambda.CognitoEvents/GroupConfiguration.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Runtime.Serialization; namespace Amazon.Lambda.CognitoEvents @@ -13,27 +13,21 @@ public class GroupConfiguration /// A list of the group names that are associated with the user that the identity token is issued for. /// [DataMember(Name = "groupsToOverride")] -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("groupsToOverride")] -# endif public List GroupsToOverride { get; set; } = new List(); /// /// A list of the current IAM roles associated with these groups. /// [DataMember(Name = "iamRolesToOverride")] -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("iamRolesToOverride")] -# endif public List IamRolesToOverride { get; set; } = new List(); /// /// A string indicating the preferred IAM role. /// [DataMember(Name = "preferredRole")] -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("preferredRole")] -# endif public string PreferredRole { get; set; } } } diff --git a/Libraries/src/Amazon.Lambda.CognitoEvents/IdTokenGeneration.cs b/Libraries/src/Amazon.Lambda.CognitoEvents/IdTokenGeneration.cs index 296458a70..cc14e502e 100644 --- a/Libraries/src/Amazon.Lambda.CognitoEvents/IdTokenGeneration.cs +++ b/Libraries/src/Amazon.Lambda.CognitoEvents/IdTokenGeneration.cs @@ -13,18 +13,14 @@ public class IdTokenGeneration /// A map of one or more key-value pairs of claims to add or override. For group related claims, use groupOverrideDetails instead. /// [DataMember(Name = "claimsToAddOrOverride")] -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("claimsToAddOrOverride")] -# endif public Dictionary ClaimsToAddOrOverride { get; set; } = new Dictionary(); /// /// A list that contains claims to be suppressed from the identity token. /// [DataMember(Name = "claimsToSuppress")] -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("claimsToSuppress")] -# endif public List ClaimsToSuppress { get; set; } = new List(); } } diff --git a/Libraries/src/Amazon.Lambda.ConfigEvents/Amazon.Lambda.ConfigEvents.csproj b/Libraries/src/Amazon.Lambda.ConfigEvents/Amazon.Lambda.ConfigEvents.csproj index 61478e5bb..2d1e3ca7b 100644 --- a/Libraries/src/Amazon.Lambda.ConfigEvents/Amazon.Lambda.ConfigEvents.csproj +++ b/Libraries/src/Amazon.Lambda.ConfigEvents/Amazon.Lambda.ConfigEvents.csproj @@ -3,7 +3,7 @@ - netstandard2.0;net8.0 + $(DefaultPackageTargets) Amazon Lambda .NET Core support - ConfigEvents package. Amazon.Lambda.ConfigEvents 2.1.1 @@ -12,7 +12,7 @@ AWS;Amazon;Lambda - + IL2026,IL2067,IL2075 true true diff --git a/Libraries/src/Amazon.Lambda.ConnectEvents/Amazon.Lambda.ConnectEvents.csproj b/Libraries/src/Amazon.Lambda.ConnectEvents/Amazon.Lambda.ConnectEvents.csproj index 1a21daefb..30bb04e9a 100644 --- a/Libraries/src/Amazon.Lambda.ConnectEvents/Amazon.Lambda.ConnectEvents.csproj +++ b/Libraries/src/Amazon.Lambda.ConnectEvents/Amazon.Lambda.ConnectEvents.csproj @@ -4,7 +4,7 @@ Amazon Lambda .NET Core support - Amazon Connect package. - netstandard2.0;net8.0 + $(DefaultPackageTargets) Amazon.Lambda.ConnectEvents 1.1.1 Amazon.Lambda.ConnectEvents @@ -12,7 +12,7 @@ AWS;Amazon;Lambda;Connect - + IL2026,IL2067,IL2075 true true diff --git a/Libraries/src/Amazon.Lambda.Core/Amazon.Lambda.Core.csproj b/Libraries/src/Amazon.Lambda.Core/Amazon.Lambda.Core.csproj index 580096c1f..7b73ebd00 100644 --- a/Libraries/src/Amazon.Lambda.Core/Amazon.Lambda.Core.csproj +++ b/Libraries/src/Amazon.Lambda.Core/Amazon.Lambda.Core.csproj @@ -3,7 +3,7 @@ - netstandard2.0;net6.0;net8.0 + netstandard2.0;$(DefaultPackageTargets) Amazon Lambda .NET Core support - Core package. Amazon.Lambda.Core 2.8.1 @@ -29,7 +29,7 @@ - + IL2026,IL2067,IL2075 true true diff --git a/Libraries/src/Amazon.Lambda.Core/ILambdaLogger.cs b/Libraries/src/Amazon.Lambda.Core/ILambdaLogger.cs index 9d4fdeeb0..327cc5015 100644 --- a/Libraries/src/Amazon.Lambda.Core/ILambdaLogger.cs +++ b/Libraries/src/Amazon.Lambda.Core/ILambdaLogger.cs @@ -3,7 +3,7 @@ namespace Amazon.Lambda.Core { -#if NET6_0_OR_GREATER +#if NET8_0_OR_GREATER /// /// Log Level for logging messages /// @@ -62,7 +62,7 @@ public interface ILambdaLogger /// void LogLine(string message); -#if NET6_0_OR_GREATER +#if NET8_0_OR_GREATER /// /// Log message categorized by the given log level diff --git a/Libraries/src/Amazon.Lambda.Core/LambdaLogger.cs b/Libraries/src/Amazon.Lambda.Core/LambdaLogger.cs index 8e74a380b..8968e34b5 100644 --- a/Libraries/src/Amazon.Lambda.Core/LambdaLogger.cs +++ b/Libraries/src/Amazon.Lambda.Core/LambdaLogger.cs @@ -36,7 +36,7 @@ public static void Log(string message) _loggingAction(message); } -#if NET6_0_OR_GREATER +#if NET8_0_OR_GREATER // The name of this field must not change or be readonly because Amazon.Lambda.RuntimeSupport will use reflection to replace the // value with an Action that directs the logging into its logging system. diff --git a/Libraries/src/Amazon.Lambda.Core/LambdaSerializerAttribute.cs b/Libraries/src/Amazon.Lambda.Core/LambdaSerializerAttribute.cs index ca751cbad..56c18167e 100644 --- a/Libraries/src/Amazon.Lambda.Core/LambdaSerializerAttribute.cs +++ b/Libraries/src/Amazon.Lambda.Core/LambdaSerializerAttribute.cs @@ -26,7 +26,7 @@ public sealed class LambdaSerializerAttribute : System.Attribute /// public LambdaSerializerAttribute(Type serializerType) { - this.SerializerType = serializerType; + SerializerType = serializerType; } } diff --git a/Libraries/src/Amazon.Lambda.Core/SnapshotRestore.cs b/Libraries/src/Amazon.Lambda.Core/SnapshotRestore.cs index 87854bf01..bebf326d4 100644 --- a/Libraries/src/Amazon.Lambda.Core/SnapshotRestore.cs +++ b/Libraries/src/Amazon.Lambda.Core/SnapshotRestore.cs @@ -56,4 +56,4 @@ public static void RegisterAfterRestore(Func afterRestoreAction) } } #endif -} \ No newline at end of file +} diff --git a/Libraries/src/Amazon.Lambda.DynamoDBEvents.SDK.Convertor/Amazon.Lambda.DynamoDBEvents.SDK.Convertor.csproj b/Libraries/src/Amazon.Lambda.DynamoDBEvents.SDK.Convertor/Amazon.Lambda.DynamoDBEvents.SDK.Convertor.csproj index 512a580cd..76f84a84e 100644 --- a/Libraries/src/Amazon.Lambda.DynamoDBEvents.SDK.Convertor/Amazon.Lambda.DynamoDBEvents.SDK.Convertor.csproj +++ b/Libraries/src/Amazon.Lambda.DynamoDBEvents.SDK.Convertor/Amazon.Lambda.DynamoDBEvents.SDK.Convertor.csproj @@ -4,7 +4,7 @@ Amazon Lambda .NET Core support - DynamoDBEvents SDK Convertor package. - net8.0 + $(DefaultPackageTargets) Amazon.Lambda.DynamoDBEvents.SDK.Convertor Amazon.Lambda.DynamoDBEvents.SDK.Convertor Amazon.Lambda.DynamoDBEvents.SDK.Convertor diff --git a/Libraries/src/Amazon.Lambda.DynamoDBEvents/Amazon.Lambda.DynamoDBEvents.csproj b/Libraries/src/Amazon.Lambda.DynamoDBEvents/Amazon.Lambda.DynamoDBEvents.csproj index 38f8f76bf..90e7586f9 100644 --- a/Libraries/src/Amazon.Lambda.DynamoDBEvents/Amazon.Lambda.DynamoDBEvents.csproj +++ b/Libraries/src/Amazon.Lambda.DynamoDBEvents/Amazon.Lambda.DynamoDBEvents.csproj @@ -3,7 +3,7 @@ - netcoreapp3.1;net8.0 + $(DefaultPackageTargets) Amazon Lambda .NET Core support - DynamoDBEvents package. Amazon.Lambda.DynamoDBEvents 3.1.2 @@ -20,7 +20,7 @@ - + IL2026,IL2067,IL2075 true true diff --git a/Libraries/src/Amazon.Lambda.DynamoDBEvents/Converters/DictionaryLongToStringJsonConverter.cs b/Libraries/src/Amazon.Lambda.DynamoDBEvents/Converters/DictionaryLongToStringJsonConverter.cs index 7d68b7ff9..b33fdcc63 100644 --- a/Libraries/src/Amazon.Lambda.DynamoDBEvents/Converters/DictionaryLongToStringJsonConverter.cs +++ b/Libraries/src/Amazon.Lambda.DynamoDBEvents/Converters/DictionaryLongToStringJsonConverter.cs @@ -1,12 +1,16 @@ -using System; +using System; using System.Collections.Generic; using System.Text.Json; using System.Text.Json.Serialization; namespace Amazon.Lambda.DynamoDBEvents.Converters { + /// + /// JSON converter to convert a JSON object with string keys and long values to a Dictionary<string, string> where the long values are converted to strings. + /// public class DictionaryLongToStringJsonConverter : JsonConverter> { + /// public override Dictionary Read(ref Utf8JsonReader reader, Type type, JsonSerializerOptions options) { if (reader.TokenType != JsonTokenType.StartObject) @@ -38,25 +42,21 @@ public override Dictionary Read(ref Utf8JsonReader reader, Type // Get the value. reader.Read(); - var keyValue = ExtractValue(ref reader, options); + var keyValue = ExtractValue(ref reader); dictionary.Add(propertyName, keyValue); } return dictionary; } + /// public override void Write(Utf8JsonWriter writer, Dictionary value, JsonSerializerOptions options) { -#if NET8_0_OR_GREATER // For .NET 8+ use source generation for serialization to be trimming complaint JsonSerializer.Serialize(writer, value, typeof(Dictionary), new DictionaryStringStringJsonSerializerContext(options)); -#else - // Use the built-in serializer, because it can handle dictionaries with string keys. - JsonSerializer.Serialize(writer, value, options); -#endif } - private string ExtractValue(ref Utf8JsonReader reader, JsonSerializerOptions options) + private string ExtractValue(ref Utf8JsonReader reader) { switch (reader.TokenType) { @@ -76,7 +76,6 @@ private string ExtractValue(ref Utf8JsonReader reader, JsonSerializerOptions opt } -#if NET8_0_OR_GREATER /// /// Context used for writing converter /// @@ -85,5 +84,4 @@ public partial class DictionaryStringStringJsonSerializerContext : JsonSerialize { } -#endif -} \ No newline at end of file +} diff --git a/Libraries/src/Amazon.Lambda.DynamoDBEvents/DynamoDBTimeWindowEvent.cs b/Libraries/src/Amazon.Lambda.DynamoDBEvents/DynamoDBTimeWindowEvent.cs index 97e5644e8..7ff8a51db 100644 --- a/Libraries/src/Amazon.Lambda.DynamoDBEvents/DynamoDBTimeWindowEvent.cs +++ b/Libraries/src/Amazon.Lambda.DynamoDBEvents/DynamoDBTimeWindowEvent.cs @@ -3,10 +3,8 @@ namespace Amazon.Lambda.DynamoDBEvents using System; using System.Collections.Generic; -#if NETCOREAPP3_1_OR_GREATER using Amazon.Lambda.DynamoDBEvents.Converters; using System.Text.Json.Serialization; -#endif /// /// Represents an Amazon DynamodDB event when using time windows. @@ -22,9 +20,7 @@ public class DynamoDBTimeWindowEvent : DynamoDBEvent /// /// State being built up to this invoke in the time window. /// -#if NETCOREAPP3_1_OR_GREATER [JsonConverter(typeof(DictionaryLongToStringJsonConverter))] -#endif public Dictionary State { get; set; } /// diff --git a/Libraries/src/Amazon.Lambda.DynamoDBEvents/DynamoDBTimeWindowResponse.cs b/Libraries/src/Amazon.Lambda.DynamoDBEvents/DynamoDBTimeWindowResponse.cs index 73a14ab7e..2361e38f9 100644 --- a/Libraries/src/Amazon.Lambda.DynamoDBEvents/DynamoDBTimeWindowResponse.cs +++ b/Libraries/src/Amazon.Lambda.DynamoDBEvents/DynamoDBTimeWindowResponse.cs @@ -1,13 +1,11 @@ -namespace Amazon.Lambda.DynamoDBEvents +namespace Amazon.Lambda.DynamoDBEvents { using System; using System.Collections.Generic; using System.Runtime.Serialization; -#if NETCOREAPP3_1_OR_GREATER using Amazon.Lambda.DynamoDBEvents.Converters; using System.Text.Json.Serialization; -#endif /// /// Response type to return a new state for the time window and to report batch item failures. @@ -19,10 +17,8 @@ public class DynamoDBTimeWindowResponse /// New state after processing a batch of records. /// [DataMember(Name = "state")] -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("state")] [JsonConverter(typeof(DictionaryLongToStringJsonConverter))] -#endif public Dictionary State { get; set; } /// @@ -30,9 +26,7 @@ public class DynamoDBTimeWindowResponse /// Returning the first record which failed would retry all remaining records from the batch. /// [DataMember(Name = "batchItemFailures")] -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("batchItemFailures")] -#endif public IList BatchItemFailures { get; set; } /// @@ -45,9 +39,7 @@ public class BatchItemFailure /// Sequence number of the record which failed processing. /// [DataMember(Name = "itemIdentifier")] -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("itemIdentifier")] -#endif public string ItemIdentifier { get; set; } } } diff --git a/Libraries/src/Amazon.Lambda.DynamoDBEvents/ExtensionMethods.cs b/Libraries/src/Amazon.Lambda.DynamoDBEvents/ExtensionMethods.cs index 29158f4ef..59911feb8 100644 --- a/Libraries/src/Amazon.Lambda.DynamoDBEvents/ExtensionMethods.cs +++ b/Libraries/src/Amazon.Lambda.DynamoDBEvents/ExtensionMethods.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.IO; using System.Text; using System.Text.Json; @@ -88,12 +88,7 @@ private static void WriteJsonValue(Utf8JsonWriter writer, AttributeValue attribu } else if (attribute.N != null) { -#if NETCOREAPP3_1 // WriteRawValue was added in .NET 6, but we need to write out Number values without quotes - using var document = JsonDocument.Parse(attribute.N); - document.WriteTo(writer); -#else writer.WriteRawValue(attribute.N); -#endif } else if (attribute.B != null) { @@ -134,12 +129,7 @@ private static void WriteJsonValue(Utf8JsonWriter writer, AttributeValue attribu writer.WriteStartArray(); foreach (var item in attribute.NS) { -#if NETCOREAPP3_1 // WriteRawValue was added in .NET 6, but we need to write out Number values without quotes - using var document = JsonDocument.Parse(item); - document.WriteTo(writer); -#else writer.WriteRawValue(item); -#endif } writer.WriteEndArray(); } diff --git a/Libraries/src/Amazon.Lambda.DynamoDBEvents/StreamsEventResponse.cs b/Libraries/src/Amazon.Lambda.DynamoDBEvents/StreamsEventResponse.cs index cb5c3a754..0a8337ee3 100644 --- a/Libraries/src/Amazon.Lambda.DynamoDBEvents/StreamsEventResponse.cs +++ b/Libraries/src/Amazon.Lambda.DynamoDBEvents/StreamsEventResponse.cs @@ -1,4 +1,4 @@ -namespace Amazon.Lambda.DynamoDBEvents +namespace Amazon.Lambda.DynamoDBEvents { using System.Collections.Generic; using System.Runtime.Serialization; @@ -14,9 +14,7 @@ public class StreamsEventResponse /// A list of records which failed processing. Returning the first record which failed would retry all remaining records from the batch. /// [DataMember(Name = "batchItemFailures", EmitDefaultValue = false)] -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("batchItemFailures")] -#endif public IList BatchItemFailures { get; set; } /// @@ -29,10 +27,8 @@ public class BatchItemFailure /// Sequence number of the record which failed processing. /// [DataMember(Name = "itemIdentifier", EmitDefaultValue = false)] -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("itemIdentifier")] -#endif public string ItemIdentifier { get; set; } } } -} \ No newline at end of file +} diff --git a/Libraries/src/Amazon.Lambda.KafkaEvents/Amazon.Lambda.KafkaEvents.csproj b/Libraries/src/Amazon.Lambda.KafkaEvents/Amazon.Lambda.KafkaEvents.csproj index 325133e2a..72d30e488 100644 --- a/Libraries/src/Amazon.Lambda.KafkaEvents/Amazon.Lambda.KafkaEvents.csproj +++ b/Libraries/src/Amazon.Lambda.KafkaEvents/Amazon.Lambda.KafkaEvents.csproj @@ -3,7 +3,7 @@ - netstandard2.0;net8.0 + $(DefaultPackageTargets) Amazon Lambda .NET Core support - KafkaEvents package. Amazon.Lambda.KafkaEvents 2.1.1 @@ -12,7 +12,7 @@ AWS;Amazon;Lambda;Kafka - + IL2026,IL2067,IL2075 true true diff --git a/Libraries/src/Amazon.Lambda.KinesisAnalyticsEvents/Amazon.Lambda.KinesisAnalyticsEvents.csproj b/Libraries/src/Amazon.Lambda.KinesisAnalyticsEvents/Amazon.Lambda.KinesisAnalyticsEvents.csproj index 449ba7879..25940930f 100644 --- a/Libraries/src/Amazon.Lambda.KinesisAnalyticsEvents/Amazon.Lambda.KinesisAnalyticsEvents.csproj +++ b/Libraries/src/Amazon.Lambda.KinesisAnalyticsEvents/Amazon.Lambda.KinesisAnalyticsEvents.csproj @@ -3,7 +3,7 @@ - netstandard2.0;netcoreapp3.1;net8.0 + $(DefaultPackageTargets) Amazon Lambda .NET Core support - Amazon Kinesis Analytics package. Amazon.Lambda.KinesisAnalyticsEvents 2.3.1 @@ -12,7 +12,7 @@ AWS;Amazon;Lambda;KinesisAnalytics - + IL2026,IL2067,IL2075 true true diff --git a/Libraries/src/Amazon.Lambda.KinesisAnalyticsEvents/KinesisAnalyticsFirehoseInputPreprocessingEvent.cs b/Libraries/src/Amazon.Lambda.KinesisAnalyticsEvents/KinesisAnalyticsFirehoseInputPreprocessingEvent.cs index 9108aa3cd..04307b32c 100644 --- a/Libraries/src/Amazon.Lambda.KinesisAnalyticsEvents/KinesisAnalyticsFirehoseInputPreprocessingEvent.cs +++ b/Libraries/src/Amazon.Lambda.KinesisAnalyticsEvents/KinesisAnalyticsFirehoseInputPreprocessingEvent.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Runtime.Serialization; @@ -69,9 +69,7 @@ public class FirehoseRecord /// The record metadata. /// [DataMember(Name = "kinesisFirehoseRecordMetadata")] -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("kinesisFirehoseRecordMetadata")] -#endif public KinesisFirehoseRecordMetadata RecordMetadata { get; set; } /// @@ -84,9 +82,7 @@ public class KinesisFirehoseRecordMetadata /// The approximate time the record was sent to Kinesis Firehose. /// [IgnoreDataMember] -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonIgnore] -#endif public DateTime ApproximateArrivalTimestamp { get @@ -100,9 +96,7 @@ public DateTime ApproximateArrivalTimestamp /// The approximate time the record was sent to Kinesis Firehose in epoch. /// [DataMember(Name = "approximateArrivalTimestamp")] -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("approximateArrivalTimestamp")] -#endif public long ApproximateArrivalEpoch { get; set; } } @@ -114,9 +108,7 @@ public DateTime ApproximateArrivalTimestamp /// The base64 encoded data. /// [DataMember(Name = "data")] -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("data")] -#endif public string Base64EncodedData { get; set; } /// @@ -125,7 +117,7 @@ public DateTime ApproximateArrivalTimestamp /// public string DecodeData() { - var decodedData = System.Text.Encoding.UTF8.GetString(Convert.FromBase64String(this.Base64EncodedData)); + var decodedData = System.Text.Encoding.UTF8.GetString(Convert.FromBase64String(Base64EncodedData)); return decodedData; } } diff --git a/Libraries/src/Amazon.Lambda.KinesisAnalyticsEvents/KinesisAnalyticsInputPreprocessingResponse.cs b/Libraries/src/Amazon.Lambda.KinesisAnalyticsEvents/KinesisAnalyticsInputPreprocessingResponse.cs index 7d99991f4..a2cd08035 100644 --- a/Libraries/src/Amazon.Lambda.KinesisAnalyticsEvents/KinesisAnalyticsInputPreprocessingResponse.cs +++ b/Libraries/src/Amazon.Lambda.KinesisAnalyticsEvents/KinesisAnalyticsInputPreprocessingResponse.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Runtime.Serialization; @@ -33,9 +33,7 @@ public class KinesisAnalyticsInputPreprocessingResponse /// The records. /// [DataMember(Name = "records")] -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("records")] -#endif public IList Records { get; set; } /// @@ -51,9 +49,7 @@ public class Record /// The record identifier. /// [DataMember(Name = "recordId")] -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("recordId")] -#endif public string RecordId { get; set; } /// @@ -63,9 +59,7 @@ public class Record /// The result. /// [DataMember(Name = "result")] -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("result")] -#endif public string Result { get; set; } /// @@ -75,9 +69,7 @@ public class Record /// The base64 encoded data. /// [DataMember(Name = "data")] -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("data")] -#endif public string Base64EncodedData { get; set; } /// @@ -86,7 +78,7 @@ public class Record /// The data. public void EncodeData(string data) { - this.Base64EncodedData = Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes(data)); + Base64EncodedData = Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes(data)); } } } diff --git a/Libraries/src/Amazon.Lambda.KinesisAnalyticsEvents/KinesisAnalyticsOutputDeliveryEvent.cs b/Libraries/src/Amazon.Lambda.KinesisAnalyticsEvents/KinesisAnalyticsOutputDeliveryEvent.cs index 36b6364aa..1646be331 100644 --- a/Libraries/src/Amazon.Lambda.KinesisAnalyticsEvents/KinesisAnalyticsOutputDeliveryEvent.cs +++ b/Libraries/src/Amazon.Lambda.KinesisAnalyticsEvents/KinesisAnalyticsOutputDeliveryEvent.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Runtime.Serialization; @@ -85,9 +85,7 @@ public class LambdaDeliveryRecordMetadata /// The base64 encoded data. /// [DataMember(Name = "data")] -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("data")] -#endif public string Base64EncodedData { get; set; } /// @@ -96,9 +94,9 @@ public class LambdaDeliveryRecordMetadata /// public string DecodeData() { - var decodedData = System.Text.Encoding.UTF8.GetString(Convert.FromBase64String(this.Base64EncodedData)); + var decodedData = System.Text.Encoding.UTF8.GetString(Convert.FromBase64String(Base64EncodedData)); return decodedData; } } } -} \ No newline at end of file +} diff --git a/Libraries/src/Amazon.Lambda.KinesisAnalyticsEvents/KinesisAnalyticsOutputDeliveryResponse.cs b/Libraries/src/Amazon.Lambda.KinesisAnalyticsEvents/KinesisAnalyticsOutputDeliveryResponse.cs index b844df0b1..28b233310 100644 --- a/Libraries/src/Amazon.Lambda.KinesisAnalyticsEvents/KinesisAnalyticsOutputDeliveryResponse.cs +++ b/Libraries/src/Amazon.Lambda.KinesisAnalyticsEvents/KinesisAnalyticsOutputDeliveryResponse.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Runtime.Serialization; @@ -28,9 +28,7 @@ public class KinesisAnalyticsOutputDeliveryResponse /// The records. /// [DataMember(Name = "records")] -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("records")] -#endif public IList Records { get; set; } /// @@ -46,9 +44,7 @@ public class Record /// The record identifier. /// [DataMember(Name = "recordId")] -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("recordId")] -#endif public string RecordId { get; set; } /// @@ -58,9 +54,7 @@ public class Record /// The result. /// [DataMember(Name = "result")] -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("result")] -#endif public string Result { get; set; } } } diff --git a/Libraries/src/Amazon.Lambda.KinesisAnalyticsEvents/KinesisAnalyticsStreamsInputPreprocessingEvent.cs b/Libraries/src/Amazon.Lambda.KinesisAnalyticsEvents/KinesisAnalyticsStreamsInputPreprocessingEvent.cs index 2aab44a5e..d590cf5aa 100644 --- a/Libraries/src/Amazon.Lambda.KinesisAnalyticsEvents/KinesisAnalyticsStreamsInputPreprocessingEvent.cs +++ b/Libraries/src/Amazon.Lambda.KinesisAnalyticsEvents/KinesisAnalyticsStreamsInputPreprocessingEvent.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Runtime.Serialization; @@ -68,9 +68,7 @@ public class StreamsRecord /// The record metadata. /// [DataMember(Name = "kinesisStreamRecordMetadata")] -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("kinesisStreamRecordMetadata")] -#endif public KinesisStreamRecordMetadata RecordMetadata { get; set; } /// @@ -101,9 +99,7 @@ public class KinesisStreamRecordMetadata /// The approximate time the record was sent to Kinesis Steam. /// [IgnoreDataMember] -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonIgnore] -#endif public DateTime ApproximateArrivalTimestamp { get @@ -117,9 +113,7 @@ public DateTime ApproximateArrivalTimestamp /// The approximate time the record was sent to Kinesis stream in epoch. /// [DataMember(Name = "approximateArrivalTimestamp")] -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("approximateArrivalTimestamp")] -#endif public long ApproximateArrivalEpoch { get; set; } /// @@ -140,9 +134,7 @@ public DateTime ApproximateArrivalTimestamp /// The base64 encoded data. /// [DataMember(Name = "data")] -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("data")] -#endif public string Base64EncodedData { get; set; } /// @@ -151,7 +143,7 @@ public DateTime ApproximateArrivalTimestamp /// public string DecodeData() { - var decodedData = System.Text.Encoding.UTF8.GetString(Convert.FromBase64String(this.Base64EncodedData)); + var decodedData = System.Text.Encoding.UTF8.GetString(Convert.FromBase64String(Base64EncodedData)); return decodedData; } } diff --git a/Libraries/src/Amazon.Lambda.KinesisEvents/Amazon.Lambda.KinesisEvents.csproj b/Libraries/src/Amazon.Lambda.KinesisEvents/Amazon.Lambda.KinesisEvents.csproj index a10527f8b..bf39b0213 100644 --- a/Libraries/src/Amazon.Lambda.KinesisEvents/Amazon.Lambda.KinesisEvents.csproj +++ b/Libraries/src/Amazon.Lambda.KinesisEvents/Amazon.Lambda.KinesisEvents.csproj @@ -3,7 +3,7 @@ - netstandard2.0;netcoreapp3.1;net8.0 + $(DefaultPackageTargets) Amazon Lambda .NET Core support - KinesisEvents package. Amazon.Lambda.KinesisEvents 3.0.2 @@ -25,7 +25,7 @@ - + IL2026,IL2067,IL2075 true true diff --git a/Libraries/src/Amazon.Lambda.KinesisEvents/Converters/DictionaryLongToStringJsonConverter.cs b/Libraries/src/Amazon.Lambda.KinesisEvents/Converters/DictionaryLongToStringJsonConverter.cs index 73d39b5a4..bc50d5019 100644 --- a/Libraries/src/Amazon.Lambda.KinesisEvents/Converters/DictionaryLongToStringJsonConverter.cs +++ b/Libraries/src/Amazon.Lambda.KinesisEvents/Converters/DictionaryLongToStringJsonConverter.cs @@ -1,12 +1,16 @@ -using System; +using System; using System.Collections.Generic; using System.Text.Json; using System.Text.Json.Serialization; namespace Amazon.Lambda.KinesisEvents.Converters { + /// + /// JSON converter to convert a JSON object with string keys and long values to a Dictionary<string, string> where the long values are converted to strings. + /// public class DictionaryLongToStringJsonConverter : JsonConverter> { + /// public override Dictionary Read(ref Utf8JsonReader reader, Type type, JsonSerializerOptions options) { if (reader.TokenType != JsonTokenType.StartObject) @@ -38,25 +42,21 @@ public override Dictionary Read(ref Utf8JsonReader reader, Type // Get the value. reader.Read(); - var keyValue = ExtractValue(ref reader, options); + var keyValue = ExtractValue(ref reader); dictionary.Add(propertyName, keyValue); } return dictionary; } + /// public override void Write(Utf8JsonWriter writer, Dictionary value, JsonSerializerOptions options) { -#if NET8_0_OR_GREATER // For .NET 8+ use source generation for serialization to be trimming complaint JsonSerializer.Serialize(writer, value, typeof(Dictionary), new DictionaryStringStringJsonSerializerContext(options)); -#else - // Use the built-in serializer, because it can handle dictionaries with string keys. - JsonSerializer.Serialize(writer, value, options); -#endif } - private string ExtractValue(ref Utf8JsonReader reader, JsonSerializerOptions options) + private string ExtractValue(ref Utf8JsonReader reader) { switch (reader.TokenType) { @@ -74,7 +74,6 @@ private string ExtractValue(ref Utf8JsonReader reader, JsonSerializerOptions opt } } -#if NET8_0_OR_GREATER /// /// Context used for writing converter /// @@ -83,5 +82,4 @@ public partial class DictionaryStringStringJsonSerializerContext : JsonSerialize { } -#endif -} \ No newline at end of file +} diff --git a/Libraries/src/Amazon.Lambda.KinesisEvents/KinesisTimeWindowEvent.cs b/Libraries/src/Amazon.Lambda.KinesisEvents/KinesisTimeWindowEvent.cs index d0a55658d..a60227f1e 100644 --- a/Libraries/src/Amazon.Lambda.KinesisEvents/KinesisTimeWindowEvent.cs +++ b/Libraries/src/Amazon.Lambda.KinesisEvents/KinesisTimeWindowEvent.cs @@ -2,11 +2,8 @@ namespace Amazon.Lambda.KinesisEvents { using System; using System.Collections.Generic; - -#if NETCOREAPP3_1_OR_GREATER using Amazon.Lambda.KinesisEvents.Converters; using System.Text.Json.Serialization; -#endif /// /// Represents an Amazon Kinesis event when using time windows. @@ -22,9 +19,7 @@ public class KinesisTimeWindowEvent : KinesisEvent /// /// State being built up to this invoke in the time window. /// -#if NETCOREAPP3_1_OR_GREATER [JsonConverter(typeof(DictionaryLongToStringJsonConverter))] -#endif public Dictionary State { get; set; } /// diff --git a/Libraries/src/Amazon.Lambda.KinesisEvents/KinesisTimeWindowResponse.cs b/Libraries/src/Amazon.Lambda.KinesisEvents/KinesisTimeWindowResponse.cs index 7454f7ab0..1a746431d 100644 --- a/Libraries/src/Amazon.Lambda.KinesisEvents/KinesisTimeWindowResponse.cs +++ b/Libraries/src/Amazon.Lambda.KinesisEvents/KinesisTimeWindowResponse.cs @@ -1,13 +1,10 @@ -namespace Amazon.Lambda.KinesisEvents +namespace Amazon.Lambda.KinesisEvents { using System; using System.Collections.Generic; using System.Runtime.Serialization; - -#if NETCOREAPP3_1_OR_GREATER using Amazon.Lambda.KinesisEvents.Converters; using System.Text.Json.Serialization; -#endif /// /// Response type to return a new state for the time window and to report batch item failures. @@ -19,10 +16,8 @@ public class KinesisTimeWindowResponse /// New state after processing a batch of records. /// [DataMember(Name = "state")] -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("state")] [JsonConverter(typeof(DictionaryLongToStringJsonConverter))] -#endif public Dictionary State { get; set; } /// @@ -30,9 +25,7 @@ public class KinesisTimeWindowResponse /// Returning the first record which failed would retry all remaining records from the batch. /// [DataMember(Name = "batchItemFailures")] -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("batchItemFailures")] -#endif public IList BatchItemFailures { get; set; } /// @@ -45,9 +38,7 @@ public class BatchItemFailure /// Sequence number of the record which failed processing. /// [DataMember(Name = "itemIdentifier")] -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("itemIdentifier")] -#endif public string ItemIdentifier { get; set; } } } diff --git a/Libraries/src/Amazon.Lambda.KinesisEvents/Properties/AssemblyInfo.cs b/Libraries/src/Amazon.Lambda.KinesisEvents/Properties/AssemblyInfo.cs index 62c7092c5..cb6f89240 100644 --- a/Libraries/src/Amazon.Lambda.KinesisEvents/Properties/AssemblyInfo.cs +++ b/Libraries/src/Amazon.Lambda.KinesisEvents/Properties/AssemblyInfo.cs @@ -8,6 +8,5 @@ [assembly: AssemblyCompany("Amazon.com, Inc")] [assembly: AssemblyCopyright("Copyright 2009-2017 Amazon.com, Inc. or its affiliates. All Rights Reserved.")] [assembly: ComVisible(false)] -[assembly: System.CLSCompliant(true)] [assembly: AssemblyVersion("1.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] \ No newline at end of file +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Libraries/src/Amazon.Lambda.KinesisEvents/StreamsEventResponse.cs b/Libraries/src/Amazon.Lambda.KinesisEvents/StreamsEventResponse.cs index a6dfc7c0c..046a1a08a 100644 --- a/Libraries/src/Amazon.Lambda.KinesisEvents/StreamsEventResponse.cs +++ b/Libraries/src/Amazon.Lambda.KinesisEvents/StreamsEventResponse.cs @@ -1,4 +1,4 @@ -namespace Amazon.Lambda.KinesisEvents +namespace Amazon.Lambda.KinesisEvents { using System.Collections.Generic; using System.Runtime.Serialization; @@ -14,9 +14,7 @@ public class StreamsEventResponse /// A list of records which failed processing. Returning the first record which failed would retry all remaining records from the batch. /// [DataMember(Name = "batchItemFailures", EmitDefaultValue = false)] -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("batchItemFailures")] -#endif public IList BatchItemFailures { get; set; } /// @@ -29,10 +27,8 @@ public class BatchItemFailure /// Sequence number of the record which failed processing. /// [DataMember(Name = "itemIdentifier", EmitDefaultValue = false)] -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("itemIdentifier")] -#endif public string ItemIdentifier { get; set; } } } -} \ No newline at end of file +} diff --git a/Libraries/src/Amazon.Lambda.KinesisFirehoseEvents/Amazon.Lambda.KinesisFirehoseEvents.csproj b/Libraries/src/Amazon.Lambda.KinesisFirehoseEvents/Amazon.Lambda.KinesisFirehoseEvents.csproj index 572b86b78..9dd1e395c 100644 --- a/Libraries/src/Amazon.Lambda.KinesisFirehoseEvents/Amazon.Lambda.KinesisFirehoseEvents.csproj +++ b/Libraries/src/Amazon.Lambda.KinesisFirehoseEvents/Amazon.Lambda.KinesisFirehoseEvents.csproj @@ -2,7 +2,7 @@ - netstandard2.0;netcoreapp3.1;net8.0 + $(DefaultPackageTargets) Amazon Lambda .NET Core support - Amazon Kinesis Firehose package. Amazon.Lambda.KinesisFirehoseEvents 2.3.1 @@ -11,7 +11,7 @@ AWS;Amazon;Lambda;KinesisFirehose - + IL2026,IL2067,IL2075 true true diff --git a/Libraries/src/Amazon.Lambda.KinesisFirehoseEvents/KinesisFirehoseEvent.cs b/Libraries/src/Amazon.Lambda.KinesisFirehoseEvents/KinesisFirehoseEvent.cs index f70b228cb..065822ae4 100644 --- a/Libraries/src/Amazon.Lambda.KinesisFirehoseEvents/KinesisFirehoseEvent.cs +++ b/Libraries/src/Amazon.Lambda.KinesisFirehoseEvents/KinesisFirehoseEvent.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Runtime.Serialization; @@ -51,18 +51,14 @@ public class FirehoseRecord /// The approximate time the record was sent to Kinesis Firehose as a Unix epoch. /// [DataMember(Name = "approximateArrivalTimestamp")] -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("approximateArrivalTimestamp")] -#endif public long ApproximateArrivalEpoch { get; set; } /// /// The approximate time the record was sent to Kinesis Firehose. /// [IgnoreDataMember] -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonIgnore()] -#endif public DateTime ApproximateArrivalTimestamp { get @@ -76,9 +72,7 @@ public DateTime ApproximateArrivalTimestamp /// The data sent through as a Kinesis Firehose record. The data is sent to the Lambda function base64 encoded. /// [DataMember(Name = "data")] -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("data")] -#endif public string Base64EncodedData { get; set; } /// @@ -87,7 +81,7 @@ public DateTime ApproximateArrivalTimestamp /// public string DecodeData() { - var decodedData = Encoding.UTF8.GetString(Convert.FromBase64String(this.Base64EncodedData)); + var decodedData = Encoding.UTF8.GetString(Convert.FromBase64String(Base64EncodedData)); return decodedData; } } diff --git a/Libraries/src/Amazon.Lambda.KinesisFirehoseEvents/KinesisFirehoseResponse.cs b/Libraries/src/Amazon.Lambda.KinesisFirehoseEvents/KinesisFirehoseResponse.cs index d9aecea80..0b99c9cb1 100644 --- a/Libraries/src/Amazon.Lambda.KinesisFirehoseEvents/KinesisFirehoseResponse.cs +++ b/Libraries/src/Amazon.Lambda.KinesisFirehoseEvents/KinesisFirehoseResponse.cs @@ -1,6 +1,5 @@ -using System; +using System; using System.Collections.Generic; -using System.Linq; using System.Runtime.Serialization; using System.Text; @@ -32,9 +31,7 @@ public class KinesisFirehoseResponse /// The transformed records from the KinesisFirehoseEvent. /// [DataMember(Name = "records")] -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("records")] -#endif public IList Records { get; set; } /// @@ -49,9 +46,7 @@ public class FirehoseRecord ///transformed record is treated as a data transformation failure. /// [DataMember(Name = "recordId")] -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("recordId")] -#endif public string RecordId { get; set; } /// @@ -78,27 +73,21 @@ public class FirehoseRecord /// /// [DataMember(Name = "result")] -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("result")] -#endif public string Result { get; set; } /// /// The transformed data payload, after base64-encoding. /// [DataMember(Name = "data")] -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("data")] -#endif public string Base64EncodedData { get; set; } /// /// The response record metadata. /// [DataMember(Name = "metadata")] -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("metadata")] -#endif public FirehoseResponseRecordMetadata Metadata { get; set; } @@ -108,7 +97,7 @@ public class FirehoseRecord /// The data to be base64 encoded. public void EncodeData(string data) { - this.Base64EncodedData = Convert.ToBase64String(Encoding.UTF8.GetBytes(data)); + Base64EncodedData = Convert.ToBase64String(Encoding.UTF8.GetBytes(data)); } } @@ -123,9 +112,7 @@ public class FirehoseResponseRecordMetadata /// https://docs.aws.amazon.com/firehose/latest/dev/dynamic-partitioning.html /// [DataMember(Name = "partitionKeys")] -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("partitionKeys")] -#endif public Dictionary PartitionKeys { get; set; } } } diff --git a/Libraries/src/Amazon.Lambda.LexEvents/Amazon.Lambda.LexEvents.csproj b/Libraries/src/Amazon.Lambda.LexEvents/Amazon.Lambda.LexEvents.csproj index 06c138cea..af574bc3d 100644 --- a/Libraries/src/Amazon.Lambda.LexEvents/Amazon.Lambda.LexEvents.csproj +++ b/Libraries/src/Amazon.Lambda.LexEvents/Amazon.Lambda.LexEvents.csproj @@ -4,7 +4,7 @@ Amazon Lambda .NET Core support - Amazon Lex package. - netstandard2.0;netcoreapp3.1;net8.0 + $(DefaultPackageTargets) Amazon.Lambda.LexEvents 3.1.1 Amazon.Lambda.LexEvents @@ -12,7 +12,7 @@ AWS;Amazon;Lambda;Lex - + IL2026,IL2067,IL2075 true true diff --git a/Libraries/src/Amazon.Lambda.LexEvents/LexActiveContext.cs b/Libraries/src/Amazon.Lambda.LexEvents/LexActiveContext.cs index 25d1f6248..4b2c175a0 100644 --- a/Libraries/src/Amazon.Lambda.LexEvents/LexActiveContext.cs +++ b/Libraries/src/Amazon.Lambda.LexEvents/LexActiveContext.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Runtime.Serialization; namespace Amazon.Lambda.LexEvents @@ -13,27 +13,21 @@ public class LexActiveContext /// The length of time or number of turns in the conversation with the user that the context remains active. /// [DataMember(Name = "timeToLive", EmitDefaultValue = false)] -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("timeToLive")] -#endif public TimeToLive TimeToLive { get; set; } /// /// The name of the context. /// [DataMember(Name = "name", EmitDefaultValue = false)] -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("name")] -#endif public string Name { get; set; } /// /// A list of key/value pairs the contains the name and value of the slots from the intent that activated the context. /// [DataMember(Name = "parameters", EmitDefaultValue = false)] -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("parameters")] -#endif public IDictionary Parameters { get; set; } } @@ -47,18 +41,14 @@ public class TimeToLive /// The length of time that the context remains active. /// [DataMember(Name = "timeToLiveInSeconds", EmitDefaultValue = false)] -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("timeToLiveInSeconds")] -#endif public int TimeToLiveInSeconds { get; set; } /// /// The number of turns in the conversation with the user that the context remains active. /// [DataMember(Name = "turnsToLive", EmitDefaultValue = false)] -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("turnsToLive")] -#endif public int TurnsToLive { get; set; } } -} \ No newline at end of file +} diff --git a/Libraries/src/Amazon.Lambda.LexEvents/LexRecentIntentSummaryViewType.cs b/Libraries/src/Amazon.Lambda.LexEvents/LexRecentIntentSummaryViewType.cs index 3bf075087..aa3b6870b 100644 --- a/Libraries/src/Amazon.Lambda.LexEvents/LexRecentIntentSummaryViewType.cs +++ b/Libraries/src/Amazon.Lambda.LexEvents/LexRecentIntentSummaryViewType.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Runtime.Serialization; namespace Amazon.Lambda.LexEvents @@ -13,63 +13,49 @@ public class LexRecentIntentSummaryViewType /// Gets and sets the IntentName /// [DataMember(Name = "intentName", EmitDefaultValue = false)] -#if NETCOREAPP3_1_OR_GREATER - [System.Text.Json.Serialization.JsonPropertyName("intentName")] -#endif + [System.Text.Json.Serialization.JsonPropertyName("intentName")] public string IntentName { get; set; } /// /// Gets and sets the CheckpointLabel /// [DataMember(Name = "checkpointLabel", EmitDefaultValue = false)] -#if NETCOREAPP3_1_OR_GREATER - [System.Text.Json.Serialization.JsonPropertyName("checkpointLabel")] -#endif + [System.Text.Json.Serialization.JsonPropertyName("checkpointLabel")] public string CheckpointLabel { get; set; } /// /// Gets and sets the Slots /// [DataMember(Name = "slots", EmitDefaultValue = false)] -#if NETCOREAPP3_1_OR_GREATER - [System.Text.Json.Serialization.JsonPropertyName("slots")] -#endif + [System.Text.Json.Serialization.JsonPropertyName("slots")] public IDictionary Slots { get; set; } /// /// Gets and sets the ConfirmationStatus /// [DataMember(Name = "confirmationStatus", EmitDefaultValue = false)] -#if NETCOREAPP3_1_OR_GREATER - [System.Text.Json.Serialization.JsonPropertyName("confirmationStatus")] -#endif + [System.Text.Json.Serialization.JsonPropertyName("confirmationStatus")] public string ConfirmationStatus { get; set; } /// /// Gets and sets the DialogActionType /// [DataMember(Name = "dialogActionType", EmitDefaultValue = false)] -#if NETCOREAPP3_1_OR_GREATER - [System.Text.Json.Serialization.JsonPropertyName("dialogActionType")] -#endif + [System.Text.Json.Serialization.JsonPropertyName("dialogActionType")] public string DialogActionType { get; set; } /// /// Gets and sets the FulfillmentState /// [DataMember(Name = "fulfillmentState", EmitDefaultValue = false)] -#if NETCOREAPP3_1_OR_GREATER - [System.Text.Json.Serialization.JsonPropertyName("fulfillmentState")] -#endif + [System.Text.Json.Serialization.JsonPropertyName("fulfillmentState")] public string FulfillmentState { get; set; } /// /// Gets and sets the SlotToElicit /// [DataMember(Name = "slotToElicit", EmitDefaultValue = false)] -#if NETCOREAPP3_1_OR_GREATER - [System.Text.Json.Serialization.JsonPropertyName("slotToElicit")] -#endif + [System.Text.Json.Serialization.JsonPropertyName("slotToElicit")] public string SlotToElicit { get; set; } } } diff --git a/Libraries/src/Amazon.Lambda.LexEvents/LexResponse.cs b/Libraries/src/Amazon.Lambda.LexEvents/LexResponse.cs index 1d049c323..c1023adeb 100644 --- a/Libraries/src/Amazon.Lambda.LexEvents/LexResponse.cs +++ b/Libraries/src/Amazon.Lambda.LexEvents/LexResponse.cs @@ -1,4 +1,4 @@ -namespace Amazon.Lambda.LexEvents +namespace Amazon.Lambda.LexEvents { using System; using System.Collections.Generic; @@ -15,9 +15,7 @@ public class LexResponse /// Application-specific session attributes. This is an optional field. /// [DataMember(Name = "sessionAttributes", EmitDefaultValue=false)] -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("sessionAttributes")] -#endif public IDictionary SessionAttributes { get; set; } /// @@ -26,9 +24,7 @@ public class LexResponse /// after Amazon Lex returns a response to the client. /// \ [DataMember(Name = "dialogAction", EmitDefaultValue=false)] -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("dialogAction")] -#endif public LexDialogAction DialogAction { get; set; } /// @@ -36,18 +32,14 @@ public class LexResponse /// For example, you can include a context to make one or more intents that have that context as an input eligible for recognition in the next turn of the conversation. /// [DataMember(Name = "activeContexts", EmitDefaultValue=false)] -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("activeContexts")] -#endif public IList ActiveContexts { get; set; } /// /// If included, sets values for one or more recent intents. You can include information for up to three intents. /// [DataMember(Name = "recentIntentSummaryView", EmitDefaultValue = false)] -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("recentIntentSummaryView")] -#endif public IList RecentIntentSummaryView { get; set; } /// @@ -60,63 +52,49 @@ public class LexDialogAction /// The type of action for Lex to take with the response from the Lambda function. /// [DataMember(Name = "type", EmitDefaultValue=false)] -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("type")] -#endif public string Type { get; set; } /// /// The state of the fullfillment. "Fulfilled" or "Failed" /// [DataMember(Name = "fulfillmentState", EmitDefaultValue=false)] -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("fulfillmentState")] -#endif public string FulfillmentState { get; set; } /// /// The message to be sent to the user. /// [DataMember(Name = "message", EmitDefaultValue=false)] -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("message")] -#endif public LexMessage Message { get; set; } /// /// The intent name you want to confirm or elicit. /// [DataMember(Name = "intentName", EmitDefaultValue=false)] -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("intentName")] -#endif public string IntentName { get; set; } /// /// The values for all of the slots when response is of type "Delegate". /// [DataMember(Name = "slots", EmitDefaultValue=false)] -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("slots")] -#endif public IDictionary Slots { get; set; } /// /// The slot to elicit when the Type is "ElicitSlot" /// [DataMember(Name = "slotToElicit", EmitDefaultValue=false)] -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("slotToElicit")] -#endif public string SlotToElicit { get; set; } /// /// The response card provides information back to the bot to display for the user. /// [DataMember(Name = "responseCard", EmitDefaultValue=false)] -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("responseCard")] -#endif public LexResponseCard ResponseCard { get; set; } } @@ -130,18 +108,14 @@ public class LexMessage /// The content type of the message. PlainText or SSML /// [DataMember(Name = "contentType", EmitDefaultValue=false)] -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("contentType")] -#endif public string ContentType { get; set; } /// /// The message to be asked to the user by the bot. /// [DataMember(Name = "content", EmitDefaultValue=false)] -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("content")] -#endif public string Content { get; set; } } @@ -155,27 +129,21 @@ public class LexResponseCard /// The version of the response card. /// [DataMember(Name = "version", EmitDefaultValue=false)] -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("version")] -#endif public int? Version { get; set; } /// /// The content type of the response card. The default is "application/vnd.amazonaws.card.generic". /// [DataMember(Name = "contentType", EmitDefaultValue=false)] -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("contentType")] -#endif public string ContentType { get; set; } = "application/vnd.amazonaws.card.generic"; /// /// The list of attachments sent back with the response card. /// [DataMember(Name = "genericAttachments", EmitDefaultValue=false)] -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("genericAttachments")] -#endif public IList GenericAttachments { get; set; } } @@ -189,45 +157,35 @@ public class LexGenericAttachments /// The card's title. /// [DataMember(Name = "title", EmitDefaultValue=false)] -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("title")] -#endif public string Title { get; set; } /// /// The card's sub title. /// [DataMember(Name = "subTitle", EmitDefaultValue=false)] -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("subTitle")] -#endif public string SubTitle { get; set; } /// /// URL to an image to be shown. /// [DataMember(Name = "imageUrl", EmitDefaultValue=false)] -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("imageUrl")] -#endif public string ImageUrl { get; set; } /// /// URL of the attachment to be associated with the card. /// [DataMember(Name = "attachmentLinkUrl", EmitDefaultValue=false)] -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("attachmentLinkUrl")] -#endif public string AttachmentLinkUrl { get; set; } /// /// The list of buttons to be displayed with the response card. /// [DataMember(Name = "buttons", EmitDefaultValue=false)] -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("buttons")] -#endif public IList Buttons { get; set; } } @@ -241,18 +199,14 @@ public class LexButton /// The text for the button. /// [DataMember(Name = "text", EmitDefaultValue=false)] -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("text")] -#endif public string Text { get; set; } /// /// The value of the button sent back to the server. /// [DataMember(Name = "value", EmitDefaultValue=false)] -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("value")] -#endif public string Value { get; set; } } } diff --git a/Libraries/src/Amazon.Lambda.LexV2Events/Amazon.Lambda.LexV2Events.csproj b/Libraries/src/Amazon.Lambda.LexV2Events/Amazon.Lambda.LexV2Events.csproj index e7b0035a8..06df6f2e1 100644 --- a/Libraries/src/Amazon.Lambda.LexV2Events/Amazon.Lambda.LexV2Events.csproj +++ b/Libraries/src/Amazon.Lambda.LexV2Events/Amazon.Lambda.LexV2Events.csproj @@ -4,7 +4,7 @@ Amazon Lambda .NET Core support - Amazon Lex V2 package. - netcoreapp3.1;net8.0 + $(DefaultPackageTargets) Amazon.Lambda.LexV2Events 1.1.1 Amazon.Lambda.LexV2Events @@ -12,7 +12,7 @@ AWS;Amazon;Lambda;Lex;LexV2 - + IL2026,IL2067,IL2075 true true diff --git a/Libraries/src/Amazon.Lambda.Logging.AspNetCore/Amazon.Lambda.Logging.AspNetCore.csproj b/Libraries/src/Amazon.Lambda.Logging.AspNetCore/Amazon.Lambda.Logging.AspNetCore.csproj index 7db23986c..0d3435401 100644 --- a/Libraries/src/Amazon.Lambda.Logging.AspNetCore/Amazon.Lambda.Logging.AspNetCore.csproj +++ b/Libraries/src/Amazon.Lambda.Logging.AspNetCore/Amazon.Lambda.Logging.AspNetCore.csproj @@ -4,7 +4,7 @@ Amazon Lambda .NET Core support - Logging ASP.NET Core package. - net8.0;net10.0 + $(DefaultPackageTargets) Amazon.Lambda.Logging.AspNetCore 4.1.1 Amazon.Lambda.Logging.AspNetCore diff --git a/Libraries/src/Amazon.Lambda.MQEvents/Amazon.Lambda.MQEvents.csproj b/Libraries/src/Amazon.Lambda.MQEvents/Amazon.Lambda.MQEvents.csproj index fb0a42580..ddfa17c01 100644 --- a/Libraries/src/Amazon.Lambda.MQEvents/Amazon.Lambda.MQEvents.csproj +++ b/Libraries/src/Amazon.Lambda.MQEvents/Amazon.Lambda.MQEvents.csproj @@ -3,7 +3,7 @@ - netstandard2.0;netcoreapp3.1;net8.0 + $(DefaultPackageTargets) Amazon Lambda .NET Core support - MQEvents package. Amazon.Lambda.MQEvents 2.1.1 @@ -13,7 +13,7 @@ Amazon.Lambda.MQEvents - + IL2026,IL2067,IL2075 true true diff --git a/Libraries/src/Amazon.Lambda.PowerShellHost/Amazon.Lambda.PowerShellHost.csproj b/Libraries/src/Amazon.Lambda.PowerShellHost/Amazon.Lambda.PowerShellHost.csproj index 0e025dc33..1c382012d 100644 --- a/Libraries/src/Amazon.Lambda.PowerShellHost/Amazon.Lambda.PowerShellHost.csproj +++ b/Libraries/src/Amazon.Lambda.PowerShellHost/Amazon.Lambda.PowerShellHost.csproj @@ -3,7 +3,7 @@ - net6.0;net8.0 + $(DefaultPackageTargets) AWS Lambda PowerShell Host. Amazon.Lambda.PowerShellHost 3.0.3 @@ -12,11 +12,13 @@ AWS;Amazon;Lambda;PowerShell - - - - + + + + + + diff --git a/Libraries/src/Amazon.Lambda.PowerShellHost/PowerShellFunctionHost.cs b/Libraries/src/Amazon.Lambda.PowerShellHost/PowerShellFunctionHost.cs index ae358459c..52012c1ca 100644 --- a/Libraries/src/Amazon.Lambda.PowerShellHost/PowerShellFunctionHost.cs +++ b/Libraries/src/Amazon.Lambda.PowerShellHost/PowerShellFunctionHost.cs @@ -1,4 +1,4 @@ -using Amazon.Lambda.Core; +using Amazon.Lambda.Core; using System; using System.IO; using System.Linq; @@ -61,27 +61,27 @@ protected PowerShellFunctionHost() { var state = InitialSessionState.CreateDefault(); state.ExecutionPolicy = Microsoft.PowerShell.ExecutionPolicy.Unrestricted; - this._ps = PowerShell.Create(state); + _ps = PowerShell.Create(state); } else { - this._ps = PowerShell.Create(); + _ps = PowerShell.Create(); } - this.SetupStreamHandlers(); - this.LoadModules(); + SetupStreamHandlers(); + LoadModules(); // Can be true if there was an exception importing modules packaged with the function. - if(this._lastException != null) + if(_lastException != null) { - Console.WriteLine(this._constructorLoggingBuffer.ToString()); - throw this._lastException; + Console.WriteLine(_constructorLoggingBuffer.ToString()); + throw _lastException; } - this.PowerShellFunctionName = Environment.GetEnvironmentVariable(POWERSHELL_FUNCTION_ENV); - if(!string.IsNullOrEmpty(this.PowerShellFunctionName)) + PowerShellFunctionName = Environment.GetEnvironmentVariable(POWERSHELL_FUNCTION_ENV); + if(!string.IsNullOrEmpty(PowerShellFunctionName)) { - this._constructorLoggingBuffer.AppendLine($"Configured to call function {this.PowerShellFunctionName} from the PowerShell script."); + _constructorLoggingBuffer.AppendLine($"Configured to call function {PowerShellFunctionName} from the PowerShell script."); } } @@ -93,7 +93,7 @@ protected PowerShellFunctionHost() protected PowerShellFunctionHost(string powerShellScriptFileName) : this() { - this._powerShellScriptFileName = powerShellScriptFileName; + _powerShellScriptFileName = powerShellScriptFileName; } /// @@ -104,19 +104,19 @@ protected PowerShellFunctionHost(string powerShellScriptFileName) /// public Stream ExecuteFunction(Stream inputStream, ILambdaContext context) { - this._lastException = null; + _lastException = null; - if (this._runFirstTimeInitialization) + if (_runFirstTimeInitialization) { - this._logger = context.Logger; + _logger = context.Logger; - if (this._constructorLoggingBuffer?.Length > 0) + if (_constructorLoggingBuffer?.Length > 0) { - context.Logger.Log(this._constructorLoggingBuffer.ToString()); - this._constructorLoggingBuffer = null; + context.Logger.Log(_constructorLoggingBuffer.ToString()); + _constructorLoggingBuffer = null; } - this._runFirstTimeInitialization = false; + _runFirstTimeInitialization = false; } string inputString; @@ -125,16 +125,16 @@ public Stream ExecuteFunction(Stream inputStream, ILambdaContext context) inputString = reader.ReadToEnd(); } - var result = this.BeginInvoke(inputString, context); - this.WaitPowerShellExecution(result); + var result = BeginInvoke(inputString, context); + WaitPowerShellExecution(result); - if (this._lastException != null || this._ps.InvocationStateInfo.State == PSInvocationState.Failed) + if (_lastException != null || _ps.InvocationStateInfo.State == PSInvocationState.Failed) { - var exception = this._exceptionManager.DetermineExceptionToThrow(this._lastException ?? this._ps.InvocationStateInfo.Reason); + var exception = _exceptionManager.DetermineExceptionToThrow(_lastException ?? _ps.InvocationStateInfo.Reason); throw exception; } - return new MemoryStream(Encoding.UTF8.GetBytes(this.GetExecutionOutput())); + return new MemoryStream(Encoding.UTF8.GetBytes(GetExecutionOutput())); } /// @@ -146,14 +146,14 @@ public Stream ExecuteFunction(Stream inputStream, ILambdaContext context) private IAsyncResult BeginInvoke(string input, ILambdaContext context) { // Clear all previous PowerShell executions, variables and outputs - this._ps.Commands?.Clear(); - this._ps.Streams.Verbose?.Clear(); - this._ps.Streams.Debug?.Clear(); - this._ps.Streams.Information?.Clear(); - this._ps.Streams.Warning?.Clear(); - this._ps.Streams.Error?.Clear(); - this._ps.Runspace?.ResetRunspaceState(); - this._output.Clear(); + _ps.Commands?.Clear(); + _ps.Streams.Verbose?.Clear(); + _ps.Streams.Debug?.Clear(); + _ps.Streams.Information?.Clear(); + _ps.Streams.Warning?.Clear(); + _ps.Streams.Error?.Clear(); + _ps.Runspace?.ResetRunspaceState(); + _output.Clear(); var providedScript = LoadScript(input, context); @@ -187,18 +187,18 @@ private IAsyncResult BeginInvoke(string input, ILambdaContext context) executingScript += providedScript; - if (!string.IsNullOrEmpty(this.PowerShellFunctionName)) + if (!string.IsNullOrEmpty(PowerShellFunctionName)) { - executingScript += $"{Environment.NewLine}{this.PowerShellFunctionName} $LambdaInput $LambdaContext{Environment.NewLine}"; + executingScript += $"{Environment.NewLine}{PowerShellFunctionName} $LambdaInput $LambdaContext{Environment.NewLine}"; } - this._ps.AddScript(executingScript); - this._ps.AddParameter("LambdaInputString", input); - this._ps.AddParameter("LambdaContext", context); + _ps.AddScript(executingScript); + _ps.AddParameter("LambdaInputString", input); + _ps.AddParameter("LambdaContext", context); - return this._ps.BeginInvoke(null, this._output); + return _ps.BeginInvoke(null, _output); } /// @@ -210,23 +210,23 @@ private IAsyncResult BeginInvoke(string input, ILambdaContext context) protected virtual string LoadScript(string input, ILambdaContext context) { // Check to see if the file contents have already been read. - if(this._powerShellScriptFileContent != null) + if(_powerShellScriptFileContent != null) { - return this._powerShellScriptFileContent; + return _powerShellScriptFileContent; } - if(string.IsNullOrEmpty(this._powerShellScriptFileName)) + if(string.IsNullOrEmpty(_powerShellScriptFileName)) { throw new LambdaPowerShellException("No PowerShell script specified to be executed. Either specify a script in the constructor or override the LoadScript method."); } - if(!File.Exists(this._powerShellScriptFileName)) + if(!File.Exists(_powerShellScriptFileName)) { - throw new LambdaPowerShellException($"Failed to find PowerShell script {this._powerShellScriptFileName}. Make sure the script is included with the package bundle."); + throw new LambdaPowerShellException($"Failed to find PowerShell script {_powerShellScriptFileName}. Make sure the script is included with the package bundle."); } - this._powerShellScriptFileContent = File.ReadAllText(this._powerShellScriptFileName); + _powerShellScriptFileContent = File.ReadAllText(_powerShellScriptFileName); - return this._powerShellScriptFileContent; + return _powerShellScriptFileContent; } /// @@ -245,7 +245,7 @@ private void WaitPowerShellExecution(IAsyncResult result) /// private string GetExecutionOutput() { - var responseObject = this._output?.LastOrDefault(); + var responseObject = _output?.LastOrDefault(); if (responseObject == null) { return string.Empty; @@ -255,8 +255,8 @@ private string GetExecutionOutput() return baseObj; } - this._ps.Commands?.Clear(); - this._ps.Runspace?.ResetRunspaceState(); + _ps.Commands?.Clear(); + _ps.Runspace?.ResetRunspaceState(); string executingScript = @" Param( @@ -266,16 +266,16 @@ private string GetExecutionOutput() ConvertTo-Json $Response "; - this._ps.AddScript(executingScript); - this._ps.AddParameter("Response", responseObject); - var marshalled = this._ps.Invoke(); + _ps.AddScript(executingScript); + _ps.AddParameter("Response", responseObject); + var marshalled = _ps.Invoke(); return marshalled.FirstOrDefault()?.BaseObject as string; } private void SetupStreamHandlers() { - this._output = new PSDataCollection(); + _output = new PSDataCollection(); Func> _loggerFactory = (prefix) => { @@ -288,17 +288,17 @@ private void SetupStreamHandlers() var errorRecord = e?.ItemAdded as ErrorRecord; if (errorRecord?.Exception != null) { - this._lastException = errorRecord.Exception; + _lastException = errorRecord.Exception; } }; return handler; }; - this._ps.Streams.Verbose.DataAdding += _loggerFactory("Verbose"); - this._ps.Streams.Debug.DataAdding += _loggerFactory("Debug"); - this._ps.Streams.Information.DataAdding += _loggerFactory("Information"); - this._ps.Streams.Warning.DataAdding += _loggerFactory("Warning"); - this._ps.Streams.Error.DataAdding += _loggerFactory("Error"); + _ps.Streams.Verbose.DataAdding += _loggerFactory("Verbose"); + _ps.Streams.Debug.DataAdding += _loggerFactory("Debug"); + _ps.Streams.Information.DataAdding += _loggerFactory("Information"); + _ps.Streams.Warning.DataAdding += _loggerFactory("Warning"); + _ps.Streams.Error.DataAdding += _loggerFactory("Error"); } private void LogMessage(string prefix, string message) @@ -313,13 +313,13 @@ private void LogMessage(string prefix, string message) message = $"[{prefix}] - {message}"; } - if (this._logger != null) + if (_logger != null) { - this._logger.LogLine(message); + _logger.LogLine(message); } else { - this._constructorLoggingBuffer.AppendLine(message); + _constructorLoggingBuffer.AppendLine(message); } } @@ -357,7 +357,7 @@ private void LoadModules() } _constructorLoggingBuffer.AppendLine($"Importing module {psd1Path}"); - var result = this._ps.AddScript($"Import-Module \"{psd1Path}\"").BeginInvoke(); + var result = _ps.AddScript($"Import-Module \"{psd1Path}\"").BeginInvoke(); WaitPowerShellExecution(result); } } diff --git a/Libraries/src/Amazon.Lambda.RuntimeSupport/Amazon.Lambda.RuntimeSupport.csproj b/Libraries/src/Amazon.Lambda.RuntimeSupport/Amazon.Lambda.RuntimeSupport.csproj index 0929eb357..d6bb73f1f 100644 --- a/Libraries/src/Amazon.Lambda.RuntimeSupport/Amazon.Lambda.RuntimeSupport.csproj +++ b/Libraries/src/Amazon.Lambda.RuntimeSupport/Amazon.Lambda.RuntimeSupport.csproj @@ -3,7 +3,7 @@ - netstandard2.0;net6.0;net8.0;net9.0;net10.0 + net8.0;net9.0;net10.0 1.14.3 Provides a bootstrap and Lambda Runtime API Client to help you to develop custom .NET Core Lambda Runtimes. Amazon.Lambda.RuntimeSupport @@ -35,7 +35,7 @@ $(DefineConstants);ExecutableOutputType - + true true diff --git a/Libraries/src/Amazon.Lambda.RuntimeSupport/Bootstrap/InvokeDelegateBuilder.cs b/Libraries/src/Amazon.Lambda.RuntimeSupport/Bootstrap/InvokeDelegateBuilder.cs index 316f2ee78..022a8d3ac 100644 --- a/Libraries/src/Amazon.Lambda.RuntimeSupport/Bootstrap/InvokeDelegateBuilder.cs +++ b/Libraries/src/Amazon.Lambda.RuntimeSupport/Bootstrap/InvokeDelegateBuilder.cs @@ -29,9 +29,7 @@ namespace Amazon.Lambda.RuntimeSupport.Bootstrap /// /// Builds user delegate from the handler information. /// -#if NET8_0_OR_GREATER [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("InvokeDelegateBuilder does not support trimming and is meant to be used in class library based Lambda functions.")] -#endif internal class InvokeDelegateBuilder { private readonly InternalLogger _logger; @@ -65,9 +63,7 @@ public InvokeDelegateBuilder(InternalLogger logger, HandlerInfo handler, MethodI /// Instance of lambda input & output serializer. /// If true forces more .NET code to get loaded during startup for jitting. /// Action delegate pointing to customer's handler. -#if NET8_0_OR_GREATER [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("ConstructInvokeDelegate does not support trimming and is meant to be used in class library based Lambda functions.")] -#endif public Action ConstructInvokeDelegate(object customerObject, object customerSerializerInstance, bool isPreJit) { var inStreamParameter = Expression.Parameter(Types.StreamType, "inStream"); @@ -120,9 +116,7 @@ public Action ConstructInvokeDelegate(object cus /// Type of context passed for the invocation. /// Expression that deserializes incoming stream to the customer method inputs or null if customer method takes no input. /// Thrown when customer method inputs don't meet lambda requirements. -#if NET8_0_OR_GREATER [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("BuildInputExpressionOrNull does not support trimming and is meant to be used in class library based Lambda functions.")] -#endif private Expression BuildInputExpressionOrNull(object customerSerializerInstance, Expression inStreamParameter, out Type iLambdaContextType) { Type inputType = null; @@ -201,9 +195,7 @@ private static Expression BuildContextExpressionOrNull(Type iLambdaContextType, /// Input expression that defines customer input. /// Context expression that defines context passed for the invocation. /// Expression that unwraps customer object. -#if NET8_0_OR_GREATER [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("CreateHandlerCallExpression does not support trimming and is meant to be used in class library based Lambda functions.")] -#endif private Expression CreateHandlerCallExpression(object customerObject, Expression inputExpression, Expression contextExpression) { @@ -262,9 +254,7 @@ private Expression CreateHandlerCallExpression(object customerObject, Expression /// Expression that defines customer output. /// Expression that defines customer handler call. /// Expression that serializes customer method output to outgoing stream. -#if NET8_0_OR_GREATER [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("CreateOutputExpression does not support trimming and is meant to be used in class library based Lambda functions.")] -#endif private Expression CreateOutputExpression(object customerSerializerInstance, Expression outStreamParameter, Expression handlerCallExpression) { var outputType = _customerMethodInfo.ReturnType; @@ -320,9 +310,7 @@ private static Type GetTaskTSubclassOrNull(Type type) /// Expression that defines customer output. /// Expression that serializes returned object to output stream. /// Thrown when customer input is serializable & serializer instance is null. -#if NET8_0_OR_GREATER [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("CreateSerializeExpression does not support trimming and is meant to be used in class library based Lambda functions.")] -#endif private Expression CreateSerializeExpression(object customerSerializerInstance, Type dataType, Expression customerObject, Expression outStreamParameter) { // generic types, null for String and Stream converters @@ -377,9 +365,7 @@ private Expression CreateSerializeExpression(object customerSerializerInstance, /// Input expression that defines customer input. /// Expression that deserializes incoming data to customer method input. /// Thrown when customer serializer doesn't match with expected serializer definition -#if NET8_0_OR_GREATER [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("CreateDeserializeExpression does not support trimming and is meant to be used in class library based Lambda functions.")] -#endif private Expression CreateDeserializeExpression(object customerSerializerInstance, Type dataType, Expression inStream) { // generic types, null for String and Stream converters diff --git a/Libraries/src/Amazon.Lambda.RuntimeSupport/Bootstrap/LambdaBootstrap.cs b/Libraries/src/Amazon.Lambda.RuntimeSupport/Bootstrap/LambdaBootstrap.cs index bb6198d9e..da5367cab 100644 --- a/Libraries/src/Amazon.Lambda.RuntimeSupport/Bootstrap/LambdaBootstrap.cs +++ b/Libraries/src/Amazon.Lambda.RuntimeSupport/Bootstrap/LambdaBootstrap.cs @@ -195,16 +195,11 @@ internal LambdaBootstrap(HttpClient httpClient, LambdaBootstrapHandler handler, /// /// /// A Task that represents the operation. -#if NET8_0_OR_GREATER [System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026", Justification = "Unreferenced code paths are excluded when RuntimeFeature.IsDynamicCodeSupported is false.")] -#endif - public async Task RunAsync(CancellationToken cancellationToken = default(CancellationToken)) { -#if NET8_0_OR_GREATER AdjustMemorySettings(); -#endif if (_configuration.IsCallPreJit) { @@ -225,7 +220,7 @@ internal LambdaBootstrap(HttpClient httpClient, LambdaBootstrapHandler handler, { return; } -#if NET8_0_OR_GREATER + try { @@ -276,7 +271,6 @@ internal LambdaBootstrap(HttpClient httpClient, LambdaBootstrapHandler handler, return; }; } -#endif var processingTasksCount = Utils.DetermineProcessingTaskCount(_environmentVariables, Environment.ProcessorCount); _logger.LogInformation($"Using {processingTasksCount} tasks for invoke processing loops"); @@ -348,12 +342,12 @@ internal async Task InitializeAsync() { WriteUnhandledExceptionToLog(exception); await Client.ReportInitializationErrorAsync(exception); -#if NET8_0_OR_GREATER + if (_configuration.IsInitTypeSnapstart) { System.Environment.Exit(1); // This needs to be non-zero for Lambda Sandbox to know that Runtime client encountered an exception } -#endif + throw; } } @@ -406,8 +400,8 @@ internal async Task InvokeOnceAsync(CancellationToken cancellationToken = defaul } else { - await Client.ReportInvocationErrorAsync(invocation.LambdaContext.AwsRequestId, exception, cancellationToken); - } + await Client.ReportInvocationErrorAsync(invocation.LambdaContext.AwsRequestId, exception, cancellationToken); + } } finally { @@ -540,7 +534,6 @@ public static HttpClient ConstructHttpClient() } var amazonLambdaRuntimeSupport = typeof(LambdaBootstrap).Assembly.GetName().Version; -#if NET6_0_OR_GREATER // Create the SocketsHttpHandler directly to avoid spending cold start time creating the wrapper HttpClientHandler var handler = new SocketsHttpHandler { @@ -553,24 +546,15 @@ public static HttpClient ConstructHttpClient() : $"aws-lambda-dotnet/{dotnetRuntimeVersion}-{amazonLambdaRuntimeSupport}"; var client = new HttpClient(handler); -#else - var userAgentString = $"aws-lambda-dotnet/{dotnetRuntimeVersion}-{amazonLambdaRuntimeSupport}"; - var client = new HttpClient(); -#endif client.DefaultRequestHeaders.Add("User-Agent", userAgentString); return client; } private void WriteUnhandledExceptionToLog(Exception exception) { -#if NET6_0_OR_GREATER Client.ConsoleLogger.FormattedWriteLine(Amazon.Lambda.RuntimeSupport.Helpers.LogLevelLoggerWriter.LogLevel.Error.ToString(), exception, null); -#else - Console.Error.WriteLine(exception); -#endif } -#if NET8_0_OR_GREATER /// /// The .NET runtime does not recognize the memory limits placed by Lambda via Lambda's cgroups. This method is run during startup to inform the /// .NET runtime the max memory configured for Lambda function. The max memory can be determined using the AWS_LAMBDA_FUNCTION_MEMORY_SIZE environment variable @@ -614,7 +598,6 @@ private void AdjustMemorySettings() _logger.LogError(ex, "Failed to communicate to the .NET runtime the amount of memory configured for the Lambda function via the AWS_LAMBDA_FUNCTION_MEMORY_SIZE environment variable."); } } -#endif #region IDisposable Support private bool disposedValue = false; // To detect redundant calls diff --git a/Libraries/src/Amazon.Lambda.RuntimeSupport/Bootstrap/LambdaBootstrapBuilder.cs b/Libraries/src/Amazon.Lambda.RuntimeSupport/Bootstrap/LambdaBootstrapBuilder.cs index b24d76b4c..fff7710ca 100644 --- a/Libraries/src/Amazon.Lambda.RuntimeSupport/Bootstrap/LambdaBootstrapBuilder.cs +++ b/Libraries/src/Amazon.Lambda.RuntimeSupport/Bootstrap/LambdaBootstrapBuilder.cs @@ -36,7 +36,7 @@ public class LambdaBootstrapBuilder private LambdaBootstrapBuilder(HandlerWrapper handlerWrapper) { - this._handlerWrapper = handlerWrapper; + _handlerWrapper = handlerWrapper; } /// diff --git a/Libraries/src/Amazon.Lambda.RuntimeSupport/Bootstrap/ResponseStreaming/RawStreamingHttpClient.cs b/Libraries/src/Amazon.Lambda.RuntimeSupport/Bootstrap/ResponseStreaming/RawStreamingHttpClient.cs index c944d104a..11f2f1a49 100644 --- a/Libraries/src/Amazon.Lambda.RuntimeSupport/Bootstrap/ResponseStreaming/RawStreamingHttpClient.cs +++ b/Libraries/src/Amazon.Lambda.RuntimeSupport/Bootstrap/ResponseStreaming/RawStreamingHttpClient.cs @@ -13,9 +13,7 @@ * permissions and limitations under the License. */ -#if NET8_0_OR_GREATER using System; -using System.Collections.Generic; using System.Globalization; using System.IO; using System.Net.Sockets; @@ -293,4 +291,3 @@ public override Task FlushAsync(CancellationToken cancellationToken) => public override void SetLength(long value) => throw new NotSupportedException(); } } -#endif diff --git a/Libraries/src/Amazon.Lambda.RuntimeSupport/Bootstrap/ResponseStreaming/ResponseStream.cs b/Libraries/src/Amazon.Lambda.RuntimeSupport/Bootstrap/ResponseStreaming/ResponseStream.cs index 2df051b72..8109e9253 100644 --- a/Libraries/src/Amazon.Lambda.RuntimeSupport/Bootstrap/ResponseStreaming/ResponseStream.cs +++ b/Libraries/src/Amazon.Lambda.RuntimeSupport/Bootstrap/ResponseStreaming/ResponseStream.cs @@ -36,7 +36,7 @@ internal class ResponseStream // The live HTTP output stream, set by RawStreamingHttpClient when sending the streaming response. private Stream _httpOutputStream; - private bool _disposedValue; + private int _disposedFlag; // The wait time is a sanity timeout to avoid waiting indefinitely if SetHttpOutputStreamAsync is not called or takes too long to call. // Reality is that SetHttpOutputStreamAsync should be called very quickly after CreateStream, so this timeout is generous to avoid false positives but still protects against hanging indefinitely. @@ -233,18 +233,16 @@ private void ThrowIfCompletedOrError() /// protected virtual void Dispose(bool disposing) { - if (!_disposedValue) - { - if (disposing) - { - try { _httpStreamReady.Release(); } catch (SemaphoreFullException) { /* Ignore if already released */ } - _httpStreamReady.Dispose(); + if (Interlocked.Exchange(ref _disposedFlag, 1) != 0) + return; - try { _completionSignal.Release(); } catch (SemaphoreFullException) { /* Ignore if already released */ } - _completionSignal.Dispose(); - } + if (disposing) + { + try { _httpStreamReady.Release(); } catch (SemaphoreFullException) { /* Ignore if already released */ } + _httpStreamReady.Dispose(); - _disposedValue = true; + try { _completionSignal.Release(); } catch (SemaphoreFullException) { /* Ignore if already released */ } + _completionSignal.Dispose(); } } diff --git a/Libraries/src/Amazon.Lambda.RuntimeSupport/Bootstrap/ResponseStreaming/ResponseStreamFactory.cs b/Libraries/src/Amazon.Lambda.RuntimeSupport/Bootstrap/ResponseStreaming/ResponseStreamFactory.cs index 27b34e8db..0170ddb27 100644 --- a/Libraries/src/Amazon.Lambda.RuntimeSupport/Bootstrap/ResponseStreaming/ResponseStreamFactory.cs +++ b/Libraries/src/Amazon.Lambda.RuntimeSupport/Bootstrap/ResponseStreaming/ResponseStreamFactory.cs @@ -40,7 +40,6 @@ internal static class ResponseStreamFactory /// Thrown if called more than once per invocation. public static ResponseStream CreateStream(byte[] prelude) { -#if NET8_0_OR_GREATER var context = GetCurrentContext(); if (context == null) @@ -66,9 +65,6 @@ public static ResponseStream CreateStream(byte[] prelude) context.AwsRequestId, lambdaStream, context.CancellationToken); return lambdaStream; -#else - throw new NotImplementedException(); -#endif } // Internal methods for LambdaBootstrap to manage state diff --git a/Libraries/src/Amazon.Lambda.RuntimeSupport/Bootstrap/ResponseStreaming/ResponseStreamLambdaCoreInitializerIsolated.cs b/Libraries/src/Amazon.Lambda.RuntimeSupport/Bootstrap/ResponseStreaming/ResponseStreamLambdaCoreInitializerIsolated.cs index b86864480..2cb46e3ce 100644 --- a/Libraries/src/Amazon.Lambda.RuntimeSupport/Bootstrap/ResponseStreaming/ResponseStreamLambdaCoreInitializerIsolated.cs +++ b/Libraries/src/Amazon.Lambda.RuntimeSupport/Bootstrap/ResponseStreaming/ResponseStreamLambdaCoreInitializerIsolated.cs @@ -1,6 +1,5 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -#if NET8_0_OR_GREATER using System; using System.Threading; @@ -58,4 +57,3 @@ internal ImplLambdaResponseStream(ResponseStream innerStream) } } } -#endif diff --git a/Libraries/src/Amazon.Lambda.RuntimeSupport/Bootstrap/UserCodeInit.cs b/Libraries/src/Amazon.Lambda.RuntimeSupport/Bootstrap/UserCodeInit.cs index b59ba67f5..ece57f72f 100644 --- a/Libraries/src/Amazon.Lambda.RuntimeSupport/Bootstrap/UserCodeInit.cs +++ b/Libraries/src/Amazon.Lambda.RuntimeSupport/Bootstrap/UserCodeInit.cs @@ -31,14 +31,12 @@ internal class UserCodeInit { public static bool IsCallPreJit(IEnvironmentVariables environmentVariables) { -#if NET6_0_OR_GREATER // If we are running in an AOT environment, there is no point in doing any prejit optmization // and will most likely cause errors using APIs that are not supported in AOT. if(Utils.IsRunningNativeAot()) { return false; } -#endif string awsLambdaDotNetPreJitStr = environmentVariables.GetEnvironmentVariable(ENVIRONMENT_VARIABLE_AWS_LAMBDA_DOTNET_PREJIT); string awsLambdaInitTypeStr = environmentVariables.GetEnvironmentVariable(ENVIRONMENT_VARIABLE_AWS_LAMBDA_INITIALIZATION_TYPE); @@ -140,9 +138,7 @@ public static void LoadStringCultureInfo(IEnvironmentVariables environmentVariab } } -#if NET8_0_OR_GREATER - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("PreJitAssembly is not used for Native AOT")] -#endif + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("PreJitAssembly is not used for Native AOT")] public static void PreJitAssembly(Assembly a) { // Storage to ensure not loading the same assembly twice and optimize calls to GetAssemblies() diff --git a/Libraries/src/Amazon.Lambda.RuntimeSupport/Bootstrap/UserCodeLoader.cs b/Libraries/src/Amazon.Lambda.RuntimeSupport/Bootstrap/UserCodeLoader.cs index 340648405..84b3d7aa4 100644 --- a/Libraries/src/Amazon.Lambda.RuntimeSupport/Bootstrap/UserCodeLoader.cs +++ b/Libraries/src/Amazon.Lambda.RuntimeSupport/Bootstrap/UserCodeLoader.cs @@ -28,9 +28,7 @@ namespace Amazon.Lambda.RuntimeSupport.Bootstrap /// /// Loads user code and prepares to invoke it. /// -#if NET8_0_OR_GREATER [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("UserCodeLoader does not support trimming and is meant to be used in class library based Lambda functions.")] -#endif internal class UserCodeLoader { private const string UserInvokeException = "An exception occurred while invoking customer handler."; diff --git a/Libraries/src/Amazon.Lambda.RuntimeSupport/Bootstrap/UserCodeValidator.cs b/Libraries/src/Amazon.Lambda.RuntimeSupport/Bootstrap/UserCodeValidator.cs index 5f6100770..2684e5498 100644 --- a/Libraries/src/Amazon.Lambda.RuntimeSupport/Bootstrap/UserCodeValidator.cs +++ b/Libraries/src/Amazon.Lambda.RuntimeSupport/Bootstrap/UserCodeValidator.cs @@ -21,9 +21,7 @@ namespace Amazon.Lambda.RuntimeSupport.Bootstrap { -#if NET8_0_OR_GREATER [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("UserCodeValidator does not support trimming and is meant to be used in class library based Lambda functions.")] -#endif internal static class UserCodeValidator { /// diff --git a/Libraries/src/Amazon.Lambda.RuntimeSupport/Client/IRuntimeApiClient.cs b/Libraries/src/Amazon.Lambda.RuntimeSupport/Client/IRuntimeApiClient.cs index 8dbb34257..c43a949c9 100644 --- a/Libraries/src/Amazon.Lambda.RuntimeSupport/Client/IRuntimeApiClient.cs +++ b/Libraries/src/Amazon.Lambda.RuntimeSupport/Client/IRuntimeApiClient.cs @@ -1,4 +1,4 @@ -/* +/* * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). @@ -66,7 +66,6 @@ public interface IRuntimeApiClient /// A Task representing the asynchronous operation. Task ReportInvocationErrorAsync(string awsRequestId, Exception exception, CancellationToken cancellationToken = default); -#if NET8_0_OR_GREATER /// /// Triggers the snapshot to be taken, and then after resume, restores the lambda /// context from the Runtime API as an asynchronous operation when SnapStart is enabled. @@ -83,7 +82,6 @@ public interface IRuntimeApiClient /// The optional cancellation token to use. /// A Task representing the asynchronous operation. Task ReportRestoreErrorAsync(Exception exception, String errorType = null, CancellationToken cancellationToken = default); -#endif /// /// Send a response to a function invocation to the Runtime API as an asynchronous operation. @@ -94,4 +92,4 @@ public interface IRuntimeApiClient /// Task SendResponseAsync(string awsRequestId, Stream outputStream, CancellationToken cancellationToken = default); } -} \ No newline at end of file +} diff --git a/Libraries/src/Amazon.Lambda.RuntimeSupport/Client/InternalClientAdapted.cs b/Libraries/src/Amazon.Lambda.RuntimeSupport/Client/InternalClientAdapted.cs index 0ea7fdbbd..aeeb7ac4b 100644 --- a/Libraries/src/Amazon.Lambda.RuntimeSupport/Client/InternalClientAdapted.cs +++ b/Libraries/src/Amazon.Lambda.RuntimeSupport/Client/InternalClientAdapted.cs @@ -34,7 +34,6 @@ internal partial interface IInternalRuntimeApiClient Task> ErrorAsync(string lambda_Runtime_Function_Error_Type, string errorJson, CancellationToken cancellationToken); -#if NET8_0_OR_GREATER /// /// Triggers the snapshot to be taken, and then after resume, restores the lambda /// context from the Runtime API as an asynchronous operation when SnapStart is enabled. @@ -45,7 +44,6 @@ internal partial interface IInternalRuntimeApiClient Task> RestoreErrorAsync(string lambda_Runtime_Function_Error_Type, string errorJson, CancellationToken cancellationToken); -#endif /// Runtime makes this HTTP request when it is ready to receive and process a new invoke. /// This is an iterator-style blocking API call. Response contains event JSON document, specific to the invoking service. @@ -82,16 +80,12 @@ Task> RestoreErrorAsync(string lambda_Runtime_Fu internal partial class InternalRuntimeApiClient : IInternalRuntimeApiClient { -#if NET6_0_OR_GREATER - [JsonSerializable(typeof(StatusResponse))] [JsonSerializable(typeof(ErrorResponse))] public partial class RuntimeApiSerializationContext : JsonSerializerContext { } -#endif - private const int MAX_HEADER_SIZE_BYTES = 1024 * 1024; private const string ErrorContentType = "application/vnd.aws.lambda.error+json"; @@ -160,11 +154,7 @@ private async System.Threading.Tasks.Task> Error var result_ = default(StatusResponse); try { -#if NET6_0_OR_GREATER result_ = JsonSerializer.Deserialize(responseData_, RuntimeApiSerializationContext.Default.StatusResponse); -#else - result_ = JsonSerializer.Deserialize(responseData_); -#endif return new SwaggerResponse((int)response_.StatusCode, headers_, result_); } catch (System.Exception exception_) @@ -179,11 +169,7 @@ private async System.Threading.Tasks.Task> Error var result_ = default(ErrorResponse); try { -#if NET6_0_OR_GREATER result_ = JsonSerializer.Deserialize(responseData_, RuntimeApiSerializationContext.Default.ErrorResponse); -#else - result_ = JsonSerializer.Deserialize(responseData_); -#endif } catch (System.Exception exception_) { @@ -227,7 +213,6 @@ private async System.Threading.Tasks.Task> Error return NextAsync("/runtime/invocation/next", cancellationToken); } -#if NET8_0_OR_GREATER /// /// Restores the lambda context from the Runtime API as an asynchronous operation when SnapStart is enabled /// @@ -247,8 +232,6 @@ public async Task> RestoreErrorAsync(string lamb return await ErrorAsync(lambda_Runtime_Function_Error_Type, errorJson, "/runtime/restore/error", cancellationToken); } -#endif - /// Runtime makes this HTTP request when it is ready to receive and process a new invoke. /// This is an iterator-style blocking API call. Response contains event JSON document, specific to the invoking service. @@ -289,11 +272,7 @@ public async Task> RestoreErrorAsync(string lamb var result_ = default(ErrorResponse); try { -#if NET6_0_OR_GREATER result_ = JsonSerializer.Deserialize(responseData_, RuntimeApiSerializationContext.Default.ErrorResponse); -#else - result_ = JsonSerializer.Deserialize(responseData_); -#endif } catch (System.Exception exception_) { @@ -389,11 +368,7 @@ public async System.Threading.Tasks.Task> Respon var result_ = default(ErrorResponse); try { -#if NET6_0_OR_GREATER result_ = JsonSerializer.Deserialize(responseData_, RuntimeApiSerializationContext.Default.ErrorResponse); -#else - result_ = JsonSerializer.Deserialize(responseData_); -#endif } catch (System.Exception exception_) { @@ -408,11 +383,7 @@ public async System.Threading.Tasks.Task> Respon var result_ = default(ErrorResponse); try { -#if NET6_0_OR_GREATER result_ = JsonSerializer.Deserialize(responseData_, RuntimeApiSerializationContext.Default.ErrorResponse); -#else - result_ = JsonSerializer.Deserialize(responseData_); -#endif } catch (System.Exception exception_) { @@ -427,11 +398,7 @@ public async System.Threading.Tasks.Task> Respon var result_ = default(ErrorResponse); try { -#if NET6_0_OR_GREATER result_ = JsonSerializer.Deserialize(responseData_, RuntimeApiSerializationContext.Default.ErrorResponse); -#else - result_ = JsonSerializer.Deserialize(responseData_); -#endif } catch (System.Exception exception_) { @@ -540,11 +507,7 @@ public async System.Threading.Tasks.Task> ErrorW var result_ = default(StatusResponse); try { -#if NET6_0_OR_GREATER result_ = JsonSerializer.Deserialize(responseData_, RuntimeApiSerializationContext.Default.StatusResponse); -#else - result_ = JsonSerializer.Deserialize(responseData_); -#endif return new SwaggerResponse((int)response_.StatusCode, headers_, result_); } catch (System.Exception exception_) @@ -559,11 +522,7 @@ public async System.Threading.Tasks.Task> ErrorW var result_ = default(ErrorResponse); try { -#if NET6_0_OR_GREATER result_ = JsonSerializer.Deserialize(responseData_, RuntimeApiSerializationContext.Default.ErrorResponse); -#else - result_ = JsonSerializer.Deserialize(responseData_); -#endif } catch (System.Exception exception_) { @@ -578,11 +537,7 @@ public async System.Threading.Tasks.Task> ErrorW var result_ = default(ErrorResponse); try { -#if NET6_0_OR_GREATER result_ = JsonSerializer.Deserialize(responseData_, RuntimeApiSerializationContext.Default.ErrorResponse); -#else - result_ = JsonSerializer.Deserialize(responseData_); -#endif } catch (System.Exception exception_) { diff --git a/Libraries/src/Amazon.Lambda.RuntimeSupport/Client/RuntimeApiClient.cs b/Libraries/src/Amazon.Lambda.RuntimeSupport/Client/RuntimeApiClient.cs index 0cddfcd2a..39cc7d055 100644 --- a/Libraries/src/Amazon.Lambda.RuntimeSupport/Client/RuntimeApiClient.cs +++ b/Libraries/src/Amazon.Lambda.RuntimeSupport/Client/RuntimeApiClient.cs @@ -32,11 +32,7 @@ public class RuntimeApiClient : IRuntimeApiClient private readonly HttpClient _httpClient; private readonly IInternalRuntimeApiClient _internalClient; -#if NET6_0_OR_GREATER private readonly IConsoleLoggerWriter _consoleLoggerRedirector; -#else - private readonly IConsoleLoggerWriter _consoleLoggerRedirector; -#endif internal Func ExceptionConverter { get; set; } internal LambdaEnvironment LambdaEnvironment { get; set; } @@ -55,11 +51,7 @@ public RuntimeApiClient(HttpClient httpClient) internal RuntimeApiClient(IEnvironmentVariables environmentVariables, HttpClient httpClient, LambdaBootstrapOptions lambdaBootstrapOptions = null) { -#if NET6_0_OR_GREATER _consoleLoggerRedirector = new LogLevelLoggerWriter(environmentVariables); -#else - _consoleLoggerRedirector = new SimpleLoggerWriter(environmentVariables); -#endif ExceptionConverter = ExceptionInfo.GetExceptionInfo; _httpClient = httpClient; @@ -148,8 +140,6 @@ public Task ReportInvocationErrorAsync(string awsRequestId, Exception exception, return _internalClient.ErrorWithXRayCauseAsync(awsRequestId, exceptionInfo.ErrorType, exceptionInfoJson, exceptionInfoXRayJson, cancellationToken); } -#if NET8_0_OR_GREATER - /// /// Triggers the snapshot to be taken, and then after resume, restores the lambda /// context from the Runtime API as an asynchronous operation when SnapStart is enabled. @@ -175,10 +165,7 @@ public Task ReportRestoreErrorAsync(Exception exception, String errorType = null return _internalClient.RestoreErrorAsync(errorType, LambdaJsonExceptionWriter.WriteJson(ExceptionInfo.GetExceptionInfo(exception)), cancellationToken); } -#endif - -#if NET8_0_OR_GREATER /// /// Start sending a streaming response to the Lambda Runtime API. /// Uses a raw TCP connection with chunked transfer encoding to support HTTP/1.1 @@ -204,7 +191,6 @@ internal virtual async Task StartStreamingResponseAsync( return rawClient; } -#endif /// /// Send a response to a function invocation to the Runtime API as an asynchronous operation. diff --git a/Libraries/src/Amazon.Lambda.RuntimeSupport/Context/LambdaBootstrapConfiguration.cs b/Libraries/src/Amazon.Lambda.RuntimeSupport/Context/LambdaBootstrapConfiguration.cs index 96e913312..cdf5e5435 100644 --- a/Libraries/src/Amazon.Lambda.RuntimeSupport/Context/LambdaBootstrapConfiguration.cs +++ b/Libraries/src/Amazon.Lambda.RuntimeSupport/Context/LambdaBootstrapConfiguration.cs @@ -21,16 +21,12 @@ internal LambdaBootstrapConfiguration(bool isCallPreJit, bool isInitTypeSnapstar internal static LambdaBootstrapConfiguration GetDefaultConfiguration(IEnvironmentVariables environmentVariables) { bool isCallPreJit = UserCodeInit.IsCallPreJit(environmentVariables); -#if NET8_0_OR_GREATER bool isInitTypeSnapstart = string.Equals( environmentVariables.GetEnvironmentVariable(Constants.ENVIRONMENT_VARIABLE_AWS_LAMBDA_INITIALIZATION_TYPE), Constants.AWS_LAMBDA_INITIALIZATION_TYPE_SNAP_START); return new LambdaBootstrapConfiguration(isCallPreJit, isInitTypeSnapstart); -#else - return new LambdaBootstrapConfiguration(isCallPreJit, false); -#endif } } } diff --git a/Libraries/src/Amazon.Lambda.RuntimeSupport/Context/LambdaConsoleLogger.cs b/Libraries/src/Amazon.Lambda.RuntimeSupport/Context/LambdaConsoleLogger.cs index e0faac022..133d20bab 100644 --- a/Libraries/src/Amazon.Lambda.RuntimeSupport/Context/LambdaConsoleLogger.cs +++ b/Libraries/src/Amazon.Lambda.RuntimeSupport/Context/LambdaConsoleLogger.cs @@ -1,4 +1,4 @@ -/* +/* * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). @@ -40,7 +40,6 @@ public void LogLine(string message) public string CurrentAwsRequestId { get; set; } -#if NET6_0_OR_GREATER public void Log(string level, string message) { _consoleLoggerRedirector.FormattedWriteLine(level, message); @@ -55,6 +54,5 @@ public void Log(string level, Exception exception, string message, params object { _consoleLoggerRedirector.FormattedWriteLine(level, exception, message, args); } -#endif } } diff --git a/Libraries/src/Amazon.Lambda.RuntimeSupport/ExceptionHandling/StackFrameInfo.cs b/Libraries/src/Amazon.Lambda.RuntimeSupport/ExceptionHandling/StackFrameInfo.cs index a5c4b49fc..0eebd721c 100644 --- a/Libraries/src/Amazon.Lambda.RuntimeSupport/ExceptionHandling/StackFrameInfo.cs +++ b/Libraries/src/Amazon.Lambda.RuntimeSupport/ExceptionHandling/StackFrameInfo.cs @@ -1,4 +1,4 @@ -/* +/* * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). @@ -27,10 +27,8 @@ public StackFrameInfo(string path, int line, string label) Label = label; } -#if NET8_0_OR_GREATER [System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026", Justification = "Constructor has defensive code in place in case the method for the stack frame has been trimmed.")] -#endif public StackFrameInfo(StackFrame stackFrame) { Path = stackFrame.GetFileName(); diff --git a/Libraries/src/Amazon.Lambda.RuntimeSupport/Helpers/ConsoleLoggerWriter.cs b/Libraries/src/Amazon.Lambda.RuntimeSupport/Helpers/ConsoleLoggerWriter.cs index a2417cbcc..3dfb43730 100644 --- a/Libraries/src/Amazon.Lambda.RuntimeSupport/Helpers/ConsoleLoggerWriter.cs +++ b/Libraries/src/Amazon.Lambda.RuntimeSupport/Helpers/ConsoleLoggerWriter.cs @@ -22,9 +22,7 @@ using System.Threading.Tasks; -#if NET6_0_OR_GREATER using Amazon.Lambda.RuntimeSupport.Helpers.Logging; -#endif namespace Amazon.Lambda.RuntimeSupport.Helpers { @@ -63,71 +61,6 @@ public interface IConsoleLoggerWriter void FormattedWriteLine(string level, Exception exception, string message, params object[] args); } - /// - /// Simple logger to maintain compatibility with versions of .NET before .NET 6 - /// - public class SimpleLoggerWriter : IConsoleLoggerWriter - { - readonly TextWriter _writer; - - /// - /// Default Constructor - /// - public SimpleLoggerWriter(IEnvironmentVariables environmentVariables) - { - // Look to see if Lambda's telemetry log file descriptor is available. If so use that for logging. - // This will make sure multiline log messages use a single CloudWatch Logs record. - var fileDescriptorLogId = environmentVariables.GetEnvironmentVariable(Constants.ENVIRONMENT_VARIABLE_TELEMETRY_LOG_FD); - if (fileDescriptorLogId != null) - { - try - { - _writer = FileDescriptorLogFactory.GetWriter(environmentVariables, fileDescriptorLogId); - InternalLogger.GetDefaultLogger().LogInformation("Using file descriptor stream writer for logging"); - } - catch (Exception ex) - { - _writer = Console.Out; - InternalLogger.GetDefaultLogger().LogError(ex, "Error creating file descriptor log stream writer. Fallback to stdout."); - } - } - else - { - _writer = Console.Out; - InternalLogger.GetDefaultLogger().LogInformation("Using stdout for logging"); - } - } - - /// - public void SetRuntimeHeaders(IRuntimeApiHeaders runtimeApiHeaders) - { - } - - /// - public void FormattedWriteLine(string message) - { - _writer.WriteLine(message); - } - - /// - public void FormattedWriteLine(string level, string message, params object[] args) - { - _writer.WriteLine(message); - } - - /// - public void FormattedWriteLine(string level, Exception exception, string message, params object[] args) - { - _writer.WriteLine(message); - if (exception != null) - { - _writer.WriteLine(exception.ToString()); - } - } - } - -#if NET6_0_OR_GREATER - /// /// Formats log messages with time, request id, log level and message /// @@ -598,5 +531,4 @@ public override string NewLine #endregion } } -#endif } diff --git a/Libraries/src/Amazon.Lambda.RuntimeSupport/Helpers/Logging/AbstractLogMessageFormatter.cs b/Libraries/src/Amazon.Lambda.RuntimeSupport/Helpers/Logging/AbstractLogMessageFormatter.cs index 75535970f..e18a57c15 100644 --- a/Libraries/src/Amazon.Lambda.RuntimeSupport/Helpers/Logging/AbstractLogMessageFormatter.cs +++ b/Libraries/src/Amazon.Lambda.RuntimeSupport/Helpers/Logging/AbstractLogMessageFormatter.cs @@ -1,4 +1,3 @@ -#if NET6_0_OR_GREATER using System; using System.Collections.Concurrent; using System.Collections.Generic; @@ -285,4 +284,3 @@ public bool UsingPositionalArguments(IReadOnlyList messagePrope } } } -#endif diff --git a/Libraries/src/Amazon.Lambda.RuntimeSupport/Helpers/Logging/DefaultLogMessageFormatter.cs b/Libraries/src/Amazon.Lambda.RuntimeSupport/Helpers/Logging/DefaultLogMessageFormatter.cs index 13bdca8ad..f03c65eff 100644 --- a/Libraries/src/Amazon.Lambda.RuntimeSupport/Helpers/Logging/DefaultLogMessageFormatter.cs +++ b/Libraries/src/Amazon.Lambda.RuntimeSupport/Helpers/Logging/DefaultLogMessageFormatter.cs @@ -1,5 +1,3 @@ -#if NET6_0_OR_GREATER - using System.Text; namespace Amazon.Lambda.RuntimeSupport.Helpers.Logging @@ -120,4 +118,3 @@ private string ConvertLogLevelToLabel(LogLevelLoggerWriter.LogLevel level) } } } -#endif \ No newline at end of file diff --git a/Libraries/src/Amazon.Lambda.RuntimeSupport/Helpers/Logging/ILogMessageFormatter.cs b/Libraries/src/Amazon.Lambda.RuntimeSupport/Helpers/Logging/ILogMessageFormatter.cs index 732fe7f3d..44ffb3060 100644 --- a/Libraries/src/Amazon.Lambda.RuntimeSupport/Helpers/Logging/ILogMessageFormatter.cs +++ b/Libraries/src/Amazon.Lambda.RuntimeSupport/Helpers/Logging/ILogMessageFormatter.cs @@ -1,5 +1,3 @@ -#if NET6_0_OR_GREATER - namespace Amazon.Lambda.RuntimeSupport.Helpers.Logging { /// @@ -16,4 +14,3 @@ public interface ILogMessageFormatter string FormatMessage(MessageState state); } } -#endif \ No newline at end of file diff --git a/Libraries/src/Amazon.Lambda.RuntimeSupport/Helpers/Logging/JsonLogMessageFormatter.cs b/Libraries/src/Amazon.Lambda.RuntimeSupport/Helpers/Logging/JsonLogMessageFormatter.cs index cfe46c563..4c2dcbb5b 100644 --- a/Libraries/src/Amazon.Lambda.RuntimeSupport/Helpers/Logging/JsonLogMessageFormatter.cs +++ b/Libraries/src/Amazon.Lambda.RuntimeSupport/Helpers/Logging/JsonLogMessageFormatter.cs @@ -1,4 +1,3 @@ -#if NET6_0_OR_GREATER using System; using System.Buffers; using System.Collections; @@ -315,4 +314,3 @@ private void FormatJsonValue(Utf8JsonWriter writer, object value, string formatA private static string ToInvariantString(object obj) => Convert.ToString(obj, CultureInfo.InvariantCulture); } } -#endif diff --git a/Libraries/src/Amazon.Lambda.RuntimeSupport/Helpers/Logging/MessageProperty.cs b/Libraries/src/Amazon.Lambda.RuntimeSupport/Helpers/Logging/MessageProperty.cs index 1d463163a..9f4f77284 100644 --- a/Libraries/src/Amazon.Lambda.RuntimeSupport/Helpers/Logging/MessageProperty.cs +++ b/Libraries/src/Amazon.Lambda.RuntimeSupport/Helpers/Logging/MessageProperty.cs @@ -1,4 +1,3 @@ -#if NET6_0_OR_GREATER using System; using System.Collections; using System.Globalization; @@ -25,29 +24,29 @@ public MessageProperty(ReadOnlySpan messageToken) // messageToken format is: // : - this.MessageToken = "{" + messageToken.ToString() + "}"; + MessageToken = "{" + messageToken.ToString() + "}"; - this.FormatDirective = Directive.Default; + FormatDirective = Directive.Default; if (messageToken[0] == '@') { - this.FormatDirective = Directive.JsonSerialization; + FormatDirective = Directive.JsonSerialization; messageToken = messageToken.Slice(1); } var idxOfDelimeter = messageToken.IndexOfAny(PARAM_FORMAT_DELIMITERS); if (idxOfDelimeter < 0) { - this.Name = messageToken.ToString().Trim(); - this.FormatArgument = null; + Name = messageToken.ToString().Trim(); + FormatArgument = null; } else { - this.Name = messageToken.Slice(0, idxOfDelimeter).ToString().Trim(); - this.FormatArgument = messageToken.Slice(idxOfDelimeter + 1).ToString().Trim(); - if(this.FormatArgument == string.Empty) + Name = messageToken.Slice(0, idxOfDelimeter).ToString().Trim(); + FormatArgument = messageToken.Slice(idxOfDelimeter + 1).ToString().Trim(); + if(FormatArgument == string.Empty) { - this.FormatArgument = null; + FormatArgument = null; } } } @@ -108,11 +107,11 @@ public string FormatForMessage(object value) } if (value == null || value is IList || value is IDictionary) { - return this.MessageToken; + return MessageToken; } - if(!string.IsNullOrEmpty(this.FormatArgument)) + if(!string.IsNullOrEmpty(FormatArgument)) { - return ApplyFormatArgument(value, this.FormatArgument); + return ApplyFormatArgument(value, FormatArgument); } if(value is DateTime dt) { @@ -188,4 +187,3 @@ public static string FormatByteArray(ReadOnlySpan bytes) } } } -#endif \ No newline at end of file diff --git a/Libraries/src/Amazon.Lambda.RuntimeSupport/Helpers/Logging/MessageState.cs b/Libraries/src/Amazon.Lambda.RuntimeSupport/Helpers/Logging/MessageState.cs index 444aea46a..f59fe79d1 100644 --- a/Libraries/src/Amazon.Lambda.RuntimeSupport/Helpers/Logging/MessageState.cs +++ b/Libraries/src/Amazon.Lambda.RuntimeSupport/Helpers/Logging/MessageState.cs @@ -1,4 +1,3 @@ -#if NET6_0_OR_GREATER using System; namespace Amazon.Lambda.RuntimeSupport.Helpers.Logging @@ -51,4 +50,3 @@ public class MessageState public Exception Exception { get; set; } } } -#endif diff --git a/Libraries/src/Amazon.Lambda.RuntimeSupport/Helpers/SnapstartHelperCopySnapshotCallbacksIsolated.cs b/Libraries/src/Amazon.Lambda.RuntimeSupport/Helpers/SnapstartHelperCopySnapshotCallbacksIsolated.cs index 740b29b0d..c3d6fcd11 100644 --- a/Libraries/src/Amazon.Lambda.RuntimeSupport/Helpers/SnapstartHelperCopySnapshotCallbacksIsolated.cs +++ b/Libraries/src/Amazon.Lambda.RuntimeSupport/Helpers/SnapstartHelperCopySnapshotCallbacksIsolated.cs @@ -1,10 +1,9 @@ -using System; +using System; using System.Collections.Generic; using System.Text; namespace Amazon.Lambda.RuntimeSupport.Helpers { -#if NET8_0_OR_GREATER internal static class SnapstartHelperCopySnapshotCallbacksIsolated { internal static object CopySnapshotCallbacks() @@ -17,5 +16,4 @@ internal static object CopySnapshotCallbacks() return restoreHooksRegistry; } } -#endif } diff --git a/Libraries/src/Amazon.Lambda.RuntimeSupport/Helpers/SnapstartHelperInitializeWithSnapstartIsolatedAsync.cs b/Libraries/src/Amazon.Lambda.RuntimeSupport/Helpers/SnapstartHelperInitializeWithSnapstartIsolatedAsync.cs index e0874f50b..83d343f76 100644 --- a/Libraries/src/Amazon.Lambda.RuntimeSupport/Helpers/SnapstartHelperInitializeWithSnapstartIsolatedAsync.cs +++ b/Libraries/src/Amazon.Lambda.RuntimeSupport/Helpers/SnapstartHelperInitializeWithSnapstartIsolatedAsync.cs @@ -1,10 +1,9 @@ -using System; +using System; using System.Threading.Tasks; using Amazon.Lambda.RuntimeSupport.Bootstrap; namespace Amazon.Lambda.RuntimeSupport.Helpers { -#if NET8_0_OR_GREATER /// /// Anywhere this class is used in RuntimeSupport it should be wrapped around a try/catch block catching TypeLoadException. /// If the version of Amazon.Lambda.Core in the deployment bundle is out of date the type that is accessing SnapshotRestore @@ -50,5 +49,4 @@ internal static async Task InitializeWithSnapstartAsync(IRuntimeApiClient return true; } } -#endif } diff --git a/Libraries/src/Amazon.Lambda.RuntimeSupport/Helpers/Utils.cs b/Libraries/src/Amazon.Lambda.RuntimeSupport/Helpers/Utils.cs index e23c0e588..c0472d065 100644 --- a/Libraries/src/Amazon.Lambda.RuntimeSupport/Helpers/Utils.cs +++ b/Libraries/src/Amazon.Lambda.RuntimeSupport/Helpers/Utils.cs @@ -24,12 +24,7 @@ internal static class Utils public static bool IsRunningNativeAot() { // If dynamic code is not supported we are most likely running in an AOT environment. -#if NET6_0_OR_GREATER return !RuntimeFeature.IsDynamicCodeSupported; -#else - return false; -#endif - } /// @@ -73,10 +68,8 @@ internal static int DetermineProcessingTaskCount(IEnvironmentVariables environme /// If the AWS .NET SDK is not found then null is returned. /// /// -#if NET8_0_OR_GREATER [System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "Loading the type is okay to fail if the user is not using the AWS SDK for .NET or it is an old version. If they are using an SDK with the SDKTaskContext the SDK has the attributes to avoid the Set method being trimmed.")] -#endif internal static Action FindAWSSDKTraceIdSetter(IEnvironmentVariables environmentVariables) { if (!Utils.IsUsingMultiConcurrency(environmentVariables)) diff --git a/Libraries/src/Amazon.Lambda.RuntimeSupport/Program.cs b/Libraries/src/Amazon.Lambda.RuntimeSupport/Program.cs index d51dc0ea7..2c94fbd5d 100644 --- a/Libraries/src/Amazon.Lambda.RuntimeSupport/Program.cs +++ b/Libraries/src/Amazon.Lambda.RuntimeSupport/Program.cs @@ -29,16 +29,12 @@ class Program // the Main exists in the Lambda class library mode which will never be used for Native AOT. #pragma warning disable IL2123 #if ExecutableOutputType -#if NET8_0_OR_GREATER [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode( "The Main entry point is used in the managed runtime which loads Lambda functions as a class library. " + "The class library mode does not support Native AOT and trimming.")] -#endif private static async Task Main(string[] args) { -#if NET8_0_OR_GREATER AssemblyLoadContext.Default.Resolving += ResolveSnapshotRestoreAssembly; -#endif if (args.Length == 0) { throw new ArgumentException("The function handler was not provided via command line arguments.", nameof(args)); @@ -53,7 +49,6 @@ private static async Task Main(string[] args) #endif #pragma warning restore IL2123 -#if NET8_0_OR_GREATER [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("This code is only exercised in the class library programming model. Native AOT will not use this code path.")] private static System.Reflection.Assembly ResolveSnapshotRestoreAssembly(AssemblyLoadContext assemblyContext, System.Reflection.AssemblyName assemblyName) { @@ -66,7 +61,6 @@ private static System.Reflection.Assembly ResolveSnapshotRestoreAssembly(Assembl return null; } -#endif /// /// Parse the command line args to create a object diff --git a/Libraries/src/Amazon.Lambda.RuntimeSupport/RuntimeSupportInitializer.cs b/Libraries/src/Amazon.Lambda.RuntimeSupport/RuntimeSupportInitializer.cs index b7f36cc31..02fcfc2c2 100644 --- a/Libraries/src/Amazon.Lambda.RuntimeSupport/RuntimeSupportInitializer.cs +++ b/Libraries/src/Amazon.Lambda.RuntimeSupport/RuntimeSupportInitializer.cs @@ -23,9 +23,7 @@ namespace Amazon.Lambda.RuntimeSupport /// /// RuntimeSupportInitializer class responsible for initializing the UserCodeLoader and LambdaBootstrap given a function handler. /// -#if NET8_0_OR_GREATER - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("RuntimeSupportInitializer does not support trimming and is meant to be used in class library based Lambda functions.")] -#endif + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("RuntimeSupportInitializer does not support trimming and is meant to be used in class library based Lambda functions.")] public class RuntimeSupportInitializer { private readonly string _handler; diff --git a/Libraries/src/Amazon.Lambda.S3Events/Amazon.Lambda.S3Events.csproj b/Libraries/src/Amazon.Lambda.S3Events/Amazon.Lambda.S3Events.csproj index b8c6f3ecb..5a399089b 100644 --- a/Libraries/src/Amazon.Lambda.S3Events/Amazon.Lambda.S3Events.csproj +++ b/Libraries/src/Amazon.Lambda.S3Events/Amazon.Lambda.S3Events.csproj @@ -4,7 +4,7 @@ Amazon Lambda .NET Core support - S3Events package. - netcoreapp3.1;net8.0 + $(DefaultPackageTargets) Amazon.Lambda.S3Events 3.1.2 Amazon.Lambda.S3Events @@ -12,7 +12,7 @@ AWS;Amazon;Lambda - + IL2026,IL2067,IL2075 true true diff --git a/Libraries/src/Amazon.Lambda.S3Events/S3ObjectLambdaEvent.cs b/Libraries/src/Amazon.Lambda.S3Events/S3ObjectLambdaEvent.cs index d6d7a0ca2..a03b5e89f 100644 --- a/Libraries/src/Amazon.Lambda.S3Events/S3ObjectLambdaEvent.cs +++ b/Libraries/src/Amazon.Lambda.S3Events/S3ObjectLambdaEvent.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Text; @@ -15,9 +15,7 @@ public class S3ObjectLambdaEvent /// /// The Amazon S3 request ID for this request. We recommend that you log this value to help with debugging. /// -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("xAmzRequestId")] -#endif public string XAmzRequestId { get; set; } /// diff --git a/Libraries/src/Amazon.Lambda.SNSEvents/Amazon.Lambda.SNSEvents.csproj b/Libraries/src/Amazon.Lambda.SNSEvents/Amazon.Lambda.SNSEvents.csproj index da157bbad..a7e25ee0f 100644 --- a/Libraries/src/Amazon.Lambda.SNSEvents/Amazon.Lambda.SNSEvents.csproj +++ b/Libraries/src/Amazon.Lambda.SNSEvents/Amazon.Lambda.SNSEvents.csproj @@ -4,7 +4,7 @@ Amazon Lambda .NET Core support - SNSEvents package. - netstandard2.0;net8.0 + $(DefaultPackageTargets) Amazon.Lambda.SNSEvents 2.1.1 Amazon.Lambda.SNSEvents diff --git a/Libraries/src/Amazon.Lambda.SQSEvents/Amazon.Lambda.SQSEvents.csproj b/Libraries/src/Amazon.Lambda.SQSEvents/Amazon.Lambda.SQSEvents.csproj index 6683a8462..4b6e37c16 100644 --- a/Libraries/src/Amazon.Lambda.SQSEvents/Amazon.Lambda.SQSEvents.csproj +++ b/Libraries/src/Amazon.Lambda.SQSEvents/Amazon.Lambda.SQSEvents.csproj @@ -4,7 +4,7 @@ Amazon Lambda .NET Core support - SQSEvents package. - netstandard2.0;netcoreapp3.1;net8.0 + $(DefaultPackageTargets) Amazon.Lambda.SQSEvents 2.2.1 Amazon.Lambda.SQSEvents diff --git a/Libraries/src/Amazon.Lambda.SQSEvents/SQSBatchResponse.cs b/Libraries/src/Amazon.Lambda.SQSEvents/SQSBatchResponse.cs index 35241b21f..a58f3cb6a 100644 --- a/Libraries/src/Amazon.Lambda.SQSEvents/SQSBatchResponse.cs +++ b/Libraries/src/Amazon.Lambda.SQSEvents/SQSBatchResponse.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Runtime.Serialization; namespace Amazon.Lambda.SQSEvents @@ -30,9 +30,7 @@ public SQSBatchResponse(List batchItemFailures) /// Gets or sets the message failures within the batch failures /// [DataMember(Name = "batchItemFailures")] -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("batchItemFailures")] -#endif public List BatchItemFailures { get; set; } /// @@ -45,9 +43,7 @@ public class BatchItemFailure /// MessageId that failed processing /// [DataMember(Name = "itemIdentifier")] -#if NETCOREAPP3_1_OR_GREATER [System.Text.Json.Serialization.JsonPropertyName("itemIdentifier")] -#endif public string ItemIdentifier { get; set; } } } diff --git a/Libraries/src/Amazon.Lambda.Serialization.Json/Amazon.Lambda.Serialization.Json.csproj b/Libraries/src/Amazon.Lambda.Serialization.Json/Amazon.Lambda.Serialization.Json.csproj index 6c03c035a..f41dc960a 100644 --- a/Libraries/src/Amazon.Lambda.Serialization.Json/Amazon.Lambda.Serialization.Json.csproj +++ b/Libraries/src/Amazon.Lambda.Serialization.Json/Amazon.Lambda.Serialization.Json.csproj @@ -4,7 +4,7 @@ Amazon Lambda .NET Core support - Serialization.Json package. - netstandard2.0 + $(DefaultPackageTargets) Amazon.Lambda.Serialization.Json Amazon.Lambda.Serialization.Json Amazon.Lambda.Serialization.Json diff --git a/Libraries/src/Amazon.Lambda.Serialization.Json/AwsResolver.cs b/Libraries/src/Amazon.Lambda.Serialization.Json/AwsResolver.cs index 056b954db..3d1853a79 100644 --- a/Libraries/src/Amazon.Lambda.Serialization.Json/AwsResolver.cs +++ b/Libraries/src/Amazon.Lambda.Serialization.Json/AwsResolver.cs @@ -78,11 +78,11 @@ protected override IList CreateProperties(Type type, MemberSeriali { if (property.PropertyName.Equals("Data", StringComparison.Ordinal)) { - property.MemberConverter = StreamDataConverter; + property.Converter = StreamDataConverter; } else if (property.PropertyName.Equals("ApproximateArrivalTimestamp", StringComparison.Ordinal)) { - property.MemberConverter = DateTimeConverter; + property.Converter = DateTimeConverter; } } } @@ -95,7 +95,7 @@ protected override IList CreateProperties(Type type, MemberSeriali { if (property.PropertyName.Equals("ApproximateCreationDateTime", StringComparison.Ordinal)) { - property.MemberConverter = DateTimeConverter; + property.Converter = DateTimeConverter; } } } @@ -108,11 +108,11 @@ protected override IList CreateProperties(Type type, MemberSeriali { if (property.PropertyName.Equals("B", StringComparison.Ordinal)) { - property.MemberConverter = StreamDataConverter; + property.Converter = StreamDataConverter; } else if (property.PropertyName.Equals("BS", StringComparison.Ordinal)) { - property.MemberConverter = StreamListDataConverter; + property.Converter = StreamListDataConverter; } } } @@ -122,11 +122,11 @@ protected override IList CreateProperties(Type type, MemberSeriali { if (property.PropertyName.Equals("BinaryValue", StringComparison.Ordinal)) { - property.MemberConverter = StreamDataConverter; + property.Converter = StreamDataConverter; } else if (property.PropertyName.Equals("BinaryListValues", StringComparison.Ordinal)) { - property.MemberConverter = StreamListDataConverter; + property.Converter = StreamListDataConverter; } } } @@ -149,7 +149,7 @@ protected override IList CreateProperties(Type type, MemberSeriali { if (property.PropertyName.Equals("Value", StringComparison.Ordinal)) { - property.MemberConverter = StreamDataConverter; + property.Converter = StreamDataConverter; } } } @@ -157,4 +157,4 @@ protected override IList CreateProperties(Type type, MemberSeriali return properties; } } -} \ No newline at end of file +} diff --git a/Libraries/src/Amazon.Lambda.Serialization.Json/JsonSerializer.cs b/Libraries/src/Amazon.Lambda.Serialization.Json/JsonSerializer.cs index 0555e2f9c..6386a6e1e 100644 --- a/Libraries/src/Amazon.Lambda.Serialization.Json/JsonSerializer.cs +++ b/Libraries/src/Amazon.Lambda.Serialization.Json/JsonSerializer.cs @@ -57,7 +57,7 @@ public JsonSerializer(Action customizeSerializerSettings if (string.Equals(Environment.GetEnvironmentVariable(DEBUG_ENVIRONMENT_VARIABLE_NAME), "true", StringComparison.OrdinalIgnoreCase)) { - this.debug = true; + debug = true; } } diff --git a/Libraries/src/Amazon.Lambda.Serialization.SystemTextJson/AbstractLambdaJsonSerializer.cs b/Libraries/src/Amazon.Lambda.Serialization.SystemTextJson/AbstractLambdaJsonSerializer.cs index 2656c535b..c3ba049d7 100644 --- a/Libraries/src/Amazon.Lambda.Serialization.SystemTextJson/AbstractLambdaJsonSerializer.cs +++ b/Libraries/src/Amazon.Lambda.Serialization.SystemTextJson/AbstractLambdaJsonSerializer.cs @@ -1,4 +1,4 @@ -using Amazon.Lambda.Serialization.SystemTextJson.Converters; +using Amazon.Lambda.Serialization.SystemTextJson.Converters; using System; using System.Collections.Generic; using System.IO; @@ -32,9 +32,9 @@ protected AbstractLambdaJsonSerializer(Action jsonWriterCusto { Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping }; - jsonWriterCustomizer?.Invoke(this.WriterOptions); + jsonWriterCustomizer?.Invoke(WriterOptions); - this._debug = string.Equals(Environment.GetEnvironmentVariable(DEBUG_ENVIRONMENT_VARIABLE_NAME), "true", + _debug = string.Equals(Environment.GetEnvironmentVariable(DEBUG_ENVIRONMENT_VARIABLE_NAME), "true", StringComparison.OrdinalIgnoreCase); } @@ -130,11 +130,7 @@ protected virtual JsonSerializerOptions CreateDefaultJsonSerializationOptions() { var serializer = new JsonSerializerOptions() { -#if NET6_0_OR_GREATER DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, -#else - IgnoreNullValues = true, -#endif PropertyNameCaseInsensitive = true, PropertyNamingPolicy = new AwsNamingPolicy(), Converters = diff --git a/Libraries/src/Amazon.Lambda.Serialization.SystemTextJson/Amazon.Lambda.Serialization.SystemTextJson.csproj b/Libraries/src/Amazon.Lambda.Serialization.SystemTextJson/Amazon.Lambda.Serialization.SystemTextJson.csproj index d3160d1ee..d49abd0b0 100644 --- a/Libraries/src/Amazon.Lambda.Serialization.SystemTextJson/Amazon.Lambda.Serialization.SystemTextJson.csproj +++ b/Libraries/src/Amazon.Lambda.Serialization.SystemTextJson/Amazon.Lambda.Serialization.SystemTextJson.csproj @@ -3,7 +3,7 @@ - netcoreapp3.1;net6;net8.0 + $(DefaultPackageTargets) Amazon Lambda .NET Core support - Serialization.Json with System.Text.Json. Amazon.Lambda.Serialization.SystemTextJson Amazon.Lambda.Serialization.SystemTextJson diff --git a/Libraries/src/Amazon.Lambda.Serialization.SystemTextJson/CamelCaseLambdaJsonSerializer.cs b/Libraries/src/Amazon.Lambda.Serialization.SystemTextJson/CamelCaseLambdaJsonSerializer.cs index 54099b876..bf0f741b4 100644 --- a/Libraries/src/Amazon.Lambda.Serialization.SystemTextJson/CamelCaseLambdaJsonSerializer.cs +++ b/Libraries/src/Amazon.Lambda.Serialization.SystemTextJson/CamelCaseLambdaJsonSerializer.cs @@ -14,10 +14,8 @@ namespace Amazon.Lambda.Serialization.SystemTextJson /// in from Lambda and being sent back to Lambda will be logged. /// /// -#if NET8_0_OR_GREATER - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("CamelCaseLambdaJsonSerializer does not support trimming. " + - "For trimmed Lambda functions SourceGeneratorLambdaJsonSerializer passing in JsonSerializerContext should be used instead.")] -#endif + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("CamelCaseLambdaJsonSerializer does not support trimming. " + + "For trimmed Lambda functions SourceGeneratorLambdaJsonSerializer passing in JsonSerializerContext should be used instead.")] public class CamelCaseLambdaJsonSerializer : DefaultLambdaJsonSerializer { /// @@ -34,4 +32,4 @@ private static void ConfigureJsonSerializerOptions(JsonSerializerOptions options options.PropertyNamingPolicy = new AwsNamingPolicy(JsonNamingPolicy.CamelCase); } } -} \ No newline at end of file +} diff --git a/Libraries/src/Amazon.Lambda.Serialization.SystemTextJson/Converters/ConstantClassConverter.cs b/Libraries/src/Amazon.Lambda.Serialization.SystemTextJson/Converters/ConstantClassConverter.cs index 2053e2e65..44358eaba 100644 --- a/Libraries/src/Amazon.Lambda.Serialization.SystemTextJson/Converters/ConstantClassConverter.cs +++ b/Libraries/src/Amazon.Lambda.Serialization.SystemTextJson/Converters/ConstantClassConverter.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Text.Json; using System.Text.Json.Serialization; using System.Threading; @@ -41,10 +41,8 @@ public override bool CanConvert(Type typeToConvert) /// /// /// -#if NET8_0_OR_GREATER [System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessage("ReflectionAnalysis", "IL2067", Justification = "Constant classes are only used in the DynamoDB event referencing the SDK. S3 originally referenced the SDK but has been rewritten to no longer reference the SDK or ConstantClass. Suppressing this trim warning because we have marked the DynamoDB event with the RequiresUnreferencedCode attribute.")] -#endif public override object Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { var value = reader.GetString(); diff --git a/Libraries/src/Amazon.Lambda.Serialization.SystemTextJson/DefaultLambdaJsonSerializer.cs b/Libraries/src/Amazon.Lambda.Serialization.SystemTextJson/DefaultLambdaJsonSerializer.cs index c63c8189c..a5107de13 100644 --- a/Libraries/src/Amazon.Lambda.Serialization.SystemTextJson/DefaultLambdaJsonSerializer.cs +++ b/Libraries/src/Amazon.Lambda.Serialization.SystemTextJson/DefaultLambdaJsonSerializer.cs @@ -17,10 +17,8 @@ namespace Amazon.Lambda.Serialization.SystemTextJson /// in from Lambda and being sent back to Lambda will be logged. /// /// -#if NET8_0_OR_GREATER - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("DefaultLambdaJsonSerializer does not support trimming. " + - "For trimmed Lambda functions SourceGeneratorLambdaJsonSerializer passing in JsonSerializerContext should be used instead.")] -#endif + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("DefaultLambdaJsonSerializer does not support trimming. " + + "For trimmed Lambda functions SourceGeneratorLambdaJsonSerializer passing in JsonSerializerContext should be used instead.")] public class DefaultLambdaJsonSerializer : AbstractLambdaJsonSerializer, ILambdaSerializer { /// @@ -59,8 +57,8 @@ public DefaultLambdaJsonSerializer(Action customizer, Act { SerializerOptions = CreateDefaultJsonSerializationOptions(); - customizer?.Invoke(this.SerializerOptions); - jsonWriterCustomizer?.Invoke(this.WriterOptions); + customizer?.Invoke(SerializerOptions); + jsonWriterCustomizer?.Invoke(WriterOptions); } /// @@ -75,4 +73,4 @@ protected override T InternalDeserialize(byte[] utf8Json) return JsonSerializer.Deserialize(utf8Json, SerializerOptions); } } -} \ No newline at end of file +} diff --git a/Libraries/src/Amazon.Lambda.Serialization.SystemTextJson/LambdaJsonSerializer.cs b/Libraries/src/Amazon.Lambda.Serialization.SystemTextJson/LambdaJsonSerializer.cs index dae777b54..f405c2f39 100644 --- a/Libraries/src/Amazon.Lambda.Serialization.SystemTextJson/LambdaJsonSerializer.cs +++ b/Libraries/src/Amazon.Lambda.Serialization.SystemTextJson/LambdaJsonSerializer.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.IO; using System.Text; @@ -23,10 +23,8 @@ namespace Amazon.Lambda.Serialization.SystemTextJson /// /// [Obsolete("This serializer is obsolete because it uses inconsistent name casing when serializing to JSON. Lambda functions should use the DefaultLambdaJsonSerializer type.")] -#if NET8_0_OR_GREATER [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("LambdaJsonSerializer does not support trimming. " + "For trimmed Lambda functions SourceGeneratorLambdaJsonSerializer passing in JsonSerializerContext should be used instead.")] -#endif public class LambdaJsonSerializer : ILambdaSerializer { private const string DEBUG_ENVIRONMENT_VARIABLE_NAME = "LAMBDA_NET_SERIALIZER_DEBUG"; @@ -58,7 +56,7 @@ public LambdaJsonSerializer() if (string.Equals(Environment.GetEnvironmentVariable(DEBUG_ENVIRONMENT_VARIABLE_NAME), "true", StringComparison.OrdinalIgnoreCase)) { - this._debug = true; + _debug = true; } } @@ -70,7 +68,7 @@ public LambdaJsonSerializer() public LambdaJsonSerializer(Action customizer) : this() { - customizer?.Invoke(this._options); + customizer?.Invoke(_options); } /// @@ -82,7 +80,7 @@ public LambdaJsonSerializer(Action customizer) public LambdaJsonSerializer(Action customizer, Action jsonWriterCustomizer) : this(customizer) { - jsonWriterCustomizer?.Invoke(this.WriterOptions); + jsonWriterCustomizer?.Invoke(WriterOptions); } /// @@ -176,4 +174,4 @@ public T Deserialize(Stream requestStream) } } } -} \ No newline at end of file +} diff --git a/Libraries/src/Amazon.Lambda.Serialization.SystemTextJson/SourceGeneratorLambdaJsonSerializer.cs b/Libraries/src/Amazon.Lambda.Serialization.SystemTextJson/SourceGeneratorLambdaJsonSerializer.cs index 350fa4fab..34fef180a 100644 --- a/Libraries/src/Amazon.Lambda.Serialization.SystemTextJson/SourceGeneratorLambdaJsonSerializer.cs +++ b/Libraries/src/Amazon.Lambda.Serialization.SystemTextJson/SourceGeneratorLambdaJsonSerializer.cs @@ -1,12 +1,9 @@ -#if NET6_0_OR_GREATER using System; using System.Diagnostics.CodeAnalysis; -using System.Reflection; using System.Text.Json; using System.Text.Json.Serialization; using System.Text.Json.Serialization.Metadata; using Amazon.Lambda.Core; -using Amazon.Lambda.Serialization.SystemTextJson.Converters; namespace Amazon.Lambda.Serialization.SystemTextJson { @@ -77,7 +74,7 @@ public SourceGeneratorLambdaJsonSerializer(Action customi : base(jsonWriterCustomizer) { SerializerOptions = CreateDefaultJsonSerializationOptions(); - customizer?.Invoke(this.SerializerOptions); + customizer?.Invoke(SerializerOptions); var constructor = typeof(TSGContext).GetConstructor(new Type[] { typeof(JsonSerializerOptions) }); if(constructor == null) @@ -85,7 +82,7 @@ public SourceGeneratorLambdaJsonSerializer(Action customi throw new ApplicationException($"The serializer {typeof(TSGContext).FullName} is missing a constructor that takes in JsonSerializerOptions object"); } - _jsonSerializerContext = constructor.Invoke(new object[] { this.SerializerOptions }) as TSGContext; + _jsonSerializerContext = constructor.Invoke(new object[] { SerializerOptions }) as TSGContext; } /// @@ -102,7 +99,7 @@ public SourceGeneratorLambdaJsonSerializer(TSGContext jsonSerializerContext, Act : base(jsonWriterCustomizer) { SerializerOptions = CreateDefaultJsonSerializationOptions(); - customizer?.Invoke(this.SerializerOptions); + customizer?.Invoke(SerializerOptions); _jsonSerializerContext = jsonSerializerContext; } @@ -133,4 +130,3 @@ protected override T InternalDeserialize(byte[] utf8Json) } } } -#endif diff --git a/Libraries/src/Amazon.Lambda.SimpleEmailEvents/Actions/IReceiptAction.cs b/Libraries/src/Amazon.Lambda.SimpleEmailEvents/Actions/IReceiptAction.cs index 4b2d9598a..cb3db7949 100644 --- a/Libraries/src/Amazon.Lambda.SimpleEmailEvents/Actions/IReceiptAction.cs +++ b/Libraries/src/Amazon.Lambda.SimpleEmailEvents/Actions/IReceiptAction.cs @@ -1,7 +1,13 @@ -namespace Amazon.Lambda.SimpleEmailEvents.Actions +namespace Amazon.Lambda.SimpleEmailEvents.Actions { + /// + /// Represents an action that can be performed on a receipt. + /// public interface IReceiptAction { + /// + /// Gets or sets the type identifier associated with the current instance. + /// string Type { get; set; } } -} \ No newline at end of file +} diff --git a/Libraries/src/Amazon.Lambda.SimpleEmailEvents/Amazon.Lambda.SimpleEmailEvents.csproj b/Libraries/src/Amazon.Lambda.SimpleEmailEvents/Amazon.Lambda.SimpleEmailEvents.csproj index 7b677f43b..10c0acc87 100644 --- a/Libraries/src/Amazon.Lambda.SimpleEmailEvents/Amazon.Lambda.SimpleEmailEvents.csproj +++ b/Libraries/src/Amazon.Lambda.SimpleEmailEvents/Amazon.Lambda.SimpleEmailEvents.csproj @@ -4,7 +4,7 @@ Amazon Lambda .NET Core support - SimpleEmailEvents package. - netstandard2.0;net8.0 + $(DefaultPackageTargets) Amazon.Lambda.SimpleEmailEvents 3.1.1 Amazon.Lambda.SimpleEmailEvents diff --git a/Libraries/src/Amazon.Lambda.SimpleEmailEvents/SimpleEmailEvent.cs b/Libraries/src/Amazon.Lambda.SimpleEmailEvents/SimpleEmailEvent.cs index d84817bf4..7da9951ca 100644 --- a/Libraries/src/Amazon.Lambda.SimpleEmailEvents/SimpleEmailEvent.cs +++ b/Libraries/src/Amazon.Lambda.SimpleEmailEvents/SimpleEmailEvent.cs @@ -1,4 +1,4 @@ -using Amazon.Lambda.SimpleEmailEvents.Actions; +using Amazon.Lambda.SimpleEmailEvents.Actions; using System; using System.Collections.Generic; @@ -18,7 +18,7 @@ public class SimpleEmailEvent where TReceiptAction : IReceiptAct /// /// An SES record. /// - public class SimpleEmailRecord where TReceiptAction : IReceiptAction + public class SimpleEmailRecord where TChild : IReceiptAction { /// /// The event version. @@ -33,7 +33,7 @@ public class SimpleEmailRecord where TReceiptAction : IReceiptAc /// /// The SES message. /// - public SimpleEmailService Ses { get; set; } + public SimpleEmailService Ses { get; set; } } /// @@ -48,7 +48,7 @@ public class SimpleEmailRecord : SimpleEmailRecord /// /// An SES message record. /// - public class SimpleEmailService where TReceiptAction : IReceiptAction + public class SimpleEmailService where TChild : IReceiptAction { /// /// The mail data for the SES message. @@ -58,7 +58,7 @@ public class SimpleEmailService where TReceiptAction : IReceiptA /// /// The receipt data for the SES message. /// - public SimpleEmailReceipt Receipt { get; set; } + public SimpleEmailReceipt Receipt { get; set; } } /// @@ -105,8 +105,8 @@ public class SimpleEmailMessage /// /// The receipt data for the SES message. /// - /// The type of action being received in this receipt - public class SimpleEmailReceipt where TReceiptAction : IReceiptAction + /// The type of action being received in this receipt + public class SimpleEmailReceipt where TChild : IReceiptAction { /// /// The recipients of the message. @@ -146,7 +146,7 @@ public class SimpleEmailReceipt where TReceiptAction : IReceiptA /// /// The action of the message (i.e, which lambda was invoked, where it was stored in S3, etc) /// - public TReceiptAction Action { get; set; } + public TChild Action { get; set; } /// /// How long this incoming message took to process. diff --git a/Libraries/src/Amazon.Lambda.TestUtilities/Amazon.Lambda.TestUtilities.csproj b/Libraries/src/Amazon.Lambda.TestUtilities/Amazon.Lambda.TestUtilities.csproj index 3a9a7e7dd..dff7a8801 100644 --- a/Libraries/src/Amazon.Lambda.TestUtilities/Amazon.Lambda.TestUtilities.csproj +++ b/Libraries/src/Amazon.Lambda.TestUtilities/Amazon.Lambda.TestUtilities.csproj @@ -4,7 +4,7 @@ Amazon.Lambda.TestUtilties includes stub implementations of interfaces defined in Amazon.Lambda.Core and helper methods. - net6.0;net8.0 + $(DefaultPackageTargets) Amazon.Lambda.TestUtilities 3.0.1 Amazon.Lambda.TestUtilities diff --git a/Libraries/src/SnapshotRestore.Registry/RestoreHooksRegistry.cs b/Libraries/src/SnapshotRestore.Registry/RestoreHooksRegistry.cs index e150643f8..bc725dc72 100644 --- a/Libraries/src/SnapshotRestore.Registry/RestoreHooksRegistry.cs +++ b/Libraries/src/SnapshotRestore.Registry/RestoreHooksRegistry.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Concurrent; using System.Threading.Tasks; @@ -10,10 +10,10 @@ namespace SnapshotRestore.Registry; /// public class RestoreHooksRegistry { - private ConcurrentStack> _beforeSnapshotRegistry = new(); - private ConcurrentQueue> _afterRestoreRegistry = new(); + private readonly ConcurrentStack> _beforeSnapshotRegistry = new(); + private readonly ConcurrentQueue> _afterRestoreRegistry = new(); - private Action _logger; + private readonly Action _logger; /// /// Creates an instance of RestoreHooksRegistry. @@ -74,4 +74,4 @@ public async Task InvokeAfterRestoreCallbacks() } } } -} \ No newline at end of file +} diff --git a/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Amazon.Lambda.Annotations.SourceGenerators.Tests.csproj b/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Amazon.Lambda.Annotations.SourceGenerators.Tests.csproj index e99dce51a..de15bd96c 100644 --- a/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Amazon.Lambda.Annotations.SourceGenerators.Tests.csproj +++ b/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Amazon.Lambda.Annotations.SourceGenerators.Tests.csproj @@ -1,7 +1,7 @@  - net6.0 + net8.0;net10.0 true latest @@ -15,8 +15,6 @@ - - all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -24,10 +22,10 @@ - - - - + + + + @@ -216,7 +214,7 @@ We need to force using the .NET Standard 2.0 version because the source generator test framework will complain about using newer versions of System.Runtime then it can handle. This is not an issue in a end user scenario. --> - + diff --git a/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/CSharpSourceGeneratorVerifier.cs b/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/CSharpSourceGeneratorVerifier.cs index 75417bee1..f09f7838b 100644 --- a/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/CSharpSourceGeneratorVerifier.cs +++ b/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/CSharpSourceGeneratorVerifier.cs @@ -27,30 +27,41 @@ namespace Amazon.Lambda.Annotations.SourceGenerators.Tests public static class CSharpSourceGeneratorVerifier where TSourceGenerator : ISourceGenerator, new() { - public class Test : CSharpSourceGeneratorTest + public class Test : CSharpSourceGeneratorTest { public enum ReferencesMode {All, NoApiGatewayEvents} - public enum TargetFramework { Net60, Net80 } + public enum TargetFramework { Net8_0, Net10_0 } private ImmutableArray PreprocessorSymbols { get; set; } = ImmutableArray.Empty; - public Test(ReferencesMode referencesMode = ReferencesMode.All, TargetFramework targetFramework = TargetFramework.Net60) + public Test(ReferencesMode referencesMode = ReferencesMode.All, TargetFramework targetFramework = TargetFramework.Net10_0) { PreprocessorSymbols = ImmutableArray.Create("ANALYZER_UNIT_TESTS"); + var assemblyResolver = (Type t) => + { + var path = t.Assembly.Location; + if (targetFramework == TargetFramework.Net8_0 && path.Contains("net10.0")) + path = path.Replace("net10.0", "net8.0"); + + return path; + }; + if (referencesMode == ReferencesMode.NoApiGatewayEvents) { SolutionTransforms.Add((solution, projectId) => { - return solution.AddMetadataReference(projectId, MetadataReference.CreateFromFile(typeof(ILambdaContext).Assembly.Location)) - .AddMetadataReference(projectId, MetadataReference.CreateFromFile(typeof(IServiceCollection).Assembly.Location)) - .AddMetadataReference(projectId, MetadataReference.CreateFromFile(typeof(ServiceProvider).Assembly.Location)) - .AddMetadataReference(projectId, MetadataReference.CreateFromFile(typeof(RestApiAttribute).Assembly.Location)) - .AddMetadataReference(projectId, MetadataReference.CreateFromFile(typeof(DefaultLambdaJsonSerializer).Assembly.Location)) - .AddMetadataReference(projectId, MetadataReference.CreateFromFile(typeof(HostApplicationBuilder).Assembly.Location)) - .AddMetadataReference(projectId, MetadataReference.CreateFromFile(typeof(IHost).Assembly.Location)) - .AddMetadataReference(projectId, MetadataReference.CreateFromFile(typeof(LambdaBootstrapBuilder).Assembly.Location)); + return solution + .AddMetadataReference(projectId, MetadataReference.CreateFromFile(assemblyResolver(typeof(ILambdaContext)))) + .AddMetadataReference(projectId, MetadataReference.CreateFromFile(assemblyResolver(typeof(IServiceCollection)))) + .AddMetadataReference(projectId, MetadataReference.CreateFromFile(assemblyResolver(typeof(ServiceProvider)))) + .AddMetadataReference(projectId, MetadataReference.CreateFromFile(assemblyResolver(typeof(RestApiAttribute)))) + .AddMetadataReference(projectId, MetadataReference.CreateFromFile(assemblyResolver(typeof(DefaultLambdaJsonSerializer)))) + .AddMetadataReference(projectId, MetadataReference.CreateFromFile(assemblyResolver(typeof(HostApplicationBuilder)))) + .AddMetadataReference(projectId, MetadataReference.CreateFromFile(assemblyResolver(typeof(IHost)))) + .AddMetadataReference(projectId, MetadataReference.CreateFromFile(assemblyResolver(typeof(SnapshotRestore.Registry.RestoreHooksRegistry)))) + .AddMetadataReference(projectId, MetadataReference.CreateFromFile(assemblyResolver(typeof(LambdaBootstrapBuilder)))); }); } @@ -58,24 +69,26 @@ public Test(ReferencesMode referencesMode = ReferencesMode.All, TargetFramework { SolutionTransforms.Add((solution, projectId) => { - return solution.AddMetadataReference(projectId, MetadataReference.CreateFromFile(typeof(ILambdaContext).Assembly.Location)) - .AddMetadataReference(projectId, MetadataReference.CreateFromFile(typeof(APIGatewayProxyRequest).Assembly.Location)) - .AddMetadataReference(projectId, MetadataReference.CreateFromFile(typeof(DynamoDBEvent).Assembly.Location)) - .AddMetadataReference(projectId, MetadataReference.CreateFromFile(typeof(SNSEvent).Assembly.Location)) - .AddMetadataReference(projectId, MetadataReference.CreateFromFile(typeof(SQSEvent).Assembly.Location)) - .AddMetadataReference(projectId, MetadataReference.CreateFromFile(typeof(ApplicationLoadBalancerRequest).Assembly.Location)) - .AddMetadataReference(projectId, MetadataReference.CreateFromFile(typeof(IServiceCollection).Assembly.Location)) - .AddMetadataReference(projectId, MetadataReference.CreateFromFile(typeof(ServiceProvider).Assembly.Location)) - .AddMetadataReference(projectId, MetadataReference.CreateFromFile(typeof(RestApiAttribute).Assembly.Location)) - .AddMetadataReference(projectId, MetadataReference.CreateFromFile(typeof(DefaultLambdaJsonSerializer).Assembly.Location)) - .AddMetadataReference(projectId, MetadataReference.CreateFromFile(typeof(HostApplicationBuilder).Assembly.Location)) - .AddMetadataReference(projectId, MetadataReference.CreateFromFile(typeof(IHost).Assembly.Location)) - .AddMetadataReference(projectId, MetadataReference.CreateFromFile(typeof(LambdaBootstrapBuilder).Assembly.Location)); + return solution + .AddMetadataReference(projectId, MetadataReference.CreateFromFile(assemblyResolver(typeof(ILambdaContext)))) + .AddMetadataReference(projectId, MetadataReference.CreateFromFile(assemblyResolver(typeof(APIGatewayProxyRequest)))) + .AddMetadataReference(projectId, MetadataReference.CreateFromFile(assemblyResolver(typeof(DynamoDBEvent)))) + .AddMetadataReference(projectId, MetadataReference.CreateFromFile(assemblyResolver(typeof(SNSEvent)))) + .AddMetadataReference(projectId, MetadataReference.CreateFromFile(assemblyResolver(typeof(SQSEvent)))) + .AddMetadataReference(projectId, MetadataReference.CreateFromFile(assemblyResolver(typeof(ApplicationLoadBalancerRequest)))) + .AddMetadataReference(projectId, MetadataReference.CreateFromFile(assemblyResolver(typeof(IServiceCollection)))) + .AddMetadataReference(projectId, MetadataReference.CreateFromFile(assemblyResolver(typeof(ServiceProvider)))) + .AddMetadataReference(projectId, MetadataReference.CreateFromFile(assemblyResolver(typeof(RestApiAttribute)))) + .AddMetadataReference(projectId, MetadataReference.CreateFromFile(assemblyResolver(typeof(DefaultLambdaJsonSerializer)))) + .AddMetadataReference(projectId, MetadataReference.CreateFromFile(assemblyResolver(typeof(HostApplicationBuilder)))) + .AddMetadataReference(projectId, MetadataReference.CreateFromFile(assemblyResolver(typeof(IHost)))) + .AddMetadataReference(projectId, MetadataReference.CreateFromFile(assemblyResolver(typeof(SnapshotRestore.Registry.RestoreHooksRegistry)))) + .AddMetadataReference(projectId, MetadataReference.CreateFromFile(assemblyResolver(typeof(LambdaBootstrapBuilder)))); }); } // Set up the target framework moniker and reference assemblies - if (targetFramework == TargetFramework.Net60) + if (targetFramework == TargetFramework.Net10_0) { SolutionTransforms.Add((solution, projectId) => { @@ -84,13 +97,13 @@ public Test(ReferencesMode referencesMode = ReferencesMode.All, TargetFramework "TargetFrameworkConfig.editorconfig", SourceText.From(""" is_global = true - build_property.TargetFramework = net6.0 + build_property.TargetFramework = net10.0 """), filePath: "/TargetFrameworkConfig.editorconfig"); }); - ReferenceAssemblies = ReferenceAssemblies.Net.Net60; + ReferenceAssemblies = new ReferenceAssemblies("net10.0", new PackageIdentity("Microsoft.NETCore.App.Ref", "10.0.0"), Path.Combine("ref", "net10.0")); } - else if (targetFramework == TargetFramework.Net80) + else if (targetFramework == TargetFramework.Net8_0) { SolutionTransforms.Add((solution, projectId) => { diff --git a/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/CloudFormationTemplateHandlerTests/FindTemplateTests.cs b/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/CloudFormationTemplateHandlerTests/FindTemplateTests.cs index 0a1e33627..ee710606b 100644 --- a/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/CloudFormationTemplateHandlerTests/FindTemplateTests.cs +++ b/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/CloudFormationTemplateHandlerTests/FindTemplateTests.cs @@ -1,4 +1,4 @@ -using System.IO; +using System.IO; using Amazon.Lambda.Annotations.SourceGenerator; using Amazon.Lambda.Annotations.SourceGenerator.FileIO; using Xunit; @@ -39,7 +39,7 @@ public void FindTemplate_FromDefaultConfigFile() 'profile': 'default', 'region': 'us-west-2', 'configuration': 'Release', - 'framework': 'netcoreapp3.1', + 'framework': 'net10.0', 's3-prefix': 'AWSServerless1/', 'template': 'serverless.template', 'template-parameters': '' @@ -75,7 +75,7 @@ public void FindTemplate_DefaultConfigFileDoesNotHaveTemplateProperty() 'profile': 'default', 'region': 'us-west-2', 'configuration': 'Release', - 'framework': 'netcoreapp3.1', + 'framework': 'net10.0', 's3-prefix': 'AWSServerless1/' }"; @@ -121,4 +121,4 @@ public void FindTemplate_DefaultConfigFile_Template_Is_AboveProjectRoot() Assert.True(File.Exists(templatePath)); } } -} \ No newline at end of file +} diff --git a/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/AuthNameFallback_GetUserId_Generated.g.cs b/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/AuthNameFallback_GetUserId_Generated.g.cs index 36f99cbfe..138c0d461 100644 --- a/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/AuthNameFallback_GetUserId_Generated.g.cs +++ b/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/AuthNameFallback_GetUserId_Generated.g.cs @@ -40,7 +40,7 @@ public AuthNameFallback_GetUserId_Generated() var userId = default(string); if (__request__.RequestContext?.Authorizer?.Lambda == null || __request__.RequestContext?.Authorizer?.Lambda.ContainsKey("userId") == false) { -#if NET6_0_OR_GREATER +#if NET8_0_OR_GREATER __context__.Logger.LogDebug("Authorizer attribute 'userId' was missing, returning unauthorized."); #else __context__.Logger.Log("Authorizer attribute 'userId' was missing, returning unauthorized."); @@ -64,7 +64,7 @@ public AuthNameFallback_GetUserId_Generated() } catch (Exception e) when (e is InvalidCastException || e is FormatException || e is OverflowException || e is ArgumentException) { -#if NET6_0_OR_GREATER +#if NET8_0_OR_GREATER __context__.Logger.LogError(e, "Failed to convert authorizer attribute 'userId', returning unauthorized."); #else __context__.Logger.Log("Failed to convert authorizer attribute 'userId', returning unauthorized. Exception: " + e.ToString()); diff --git a/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/AuthorizerFunction_SimpleHttpApiAuthorize_Generated.g.cs b/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/AuthorizerFunction_SimpleHttpApiAuthorize_Generated.g.cs index e5dc0f74d..cb60c9ae3 100644 --- a/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/AuthorizerFunction_SimpleHttpApiAuthorize_Generated.g.cs +++ b/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/AuthorizerFunction_SimpleHttpApiAuthorize_Generated.g.cs @@ -45,7 +45,7 @@ public System.IO.Stream SimpleHttpApiAuthorize(Amazon.Lambda.APIGatewayEvents.AP } catch (Exception e) when (e is InvalidCastException || e is FormatException || e is OverflowException || e is ArgumentException) { -#if NET6_0_OR_GREATER +#if NET8_0_OR_GREATER __context__.Logger.LogError(e, "Failed to extract header 'Authorization'."); #else __context__.Logger.Log("Failed to extract header 'Authorization'. Exception: " + e.ToString()); diff --git a/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/AuthorizerFunction_SimpleRestApiAuthorize_Generated.g.cs b/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/AuthorizerFunction_SimpleRestApiAuthorize_Generated.g.cs index 05b9f6e71..f030e1938 100644 --- a/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/AuthorizerFunction_SimpleRestApiAuthorize_Generated.g.cs +++ b/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/AuthorizerFunction_SimpleRestApiAuthorize_Generated.g.cs @@ -45,7 +45,7 @@ public System.IO.Stream SimpleRestApiAuthorize(Amazon.Lambda.APIGatewayEvents.AP } catch (Exception e) when (e is InvalidCastException || e is FormatException || e is OverflowException || e is ArgumentException) { -#if NET6_0_OR_GREATER +#if NET8_0_OR_GREATER __context__.Logger.LogError(e, "Failed to extract authorization token."); #else __context__.Logger.Log("Failed to extract authorization token. Exception: " + e.ToString()); diff --git a/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/CustomAuthorizerHttpApiExample_HttpApiAuthorizer_Generated.g.cs b/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/CustomAuthorizerHttpApiExample_HttpApiAuthorizer_Generated.g.cs index 62bdd521f..888fe077b 100644 --- a/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/CustomAuthorizerHttpApiExample_HttpApiAuthorizer_Generated.g.cs +++ b/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/CustomAuthorizerHttpApiExample_HttpApiAuthorizer_Generated.g.cs @@ -1,4 +1,4 @@ -// +// using System; using System.Linq; @@ -40,7 +40,7 @@ public CustomAuthorizerHttpApiExample_HttpApiAuthorizer_Generated() var authorizerValue = default(string); if (__request__.RequestContext?.Authorizer?.Lambda == null || __request__.RequestContext?.Authorizer?.Lambda.ContainsKey("authKey") == false) { -#if NET6_0_OR_GREATER +#if NET8_0_OR_GREATER __context__.Logger.LogDebug("Authorizer attribute 'authKey' was missing, returning unauthorized."); #else __context__.Logger.Log("Authorizer attribute 'authKey' was missing, returning unauthorized."); @@ -64,7 +64,7 @@ public CustomAuthorizerHttpApiExample_HttpApiAuthorizer_Generated() } catch (Exception e) when (e is InvalidCastException || e is FormatException || e is OverflowException || e is ArgumentException) { -#if NET6_0_OR_GREATER +#if NET8_0_OR_GREATER __context__.Logger.LogError(e, "Failed to convert authorizer attribute 'authKey', returning unauthorized."); #else __context__.Logger.Log("Failed to convert authorizer attribute 'authKey', returning unauthorized. Exception: " + e.ToString()); diff --git a/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/CustomAuthorizerHttpApiV1Example_HttpApiV1Authorizer_Generated.g.cs b/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/CustomAuthorizerHttpApiV1Example_HttpApiV1Authorizer_Generated.g.cs index c7cc8eb9f..9b3e474a4 100644 --- a/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/CustomAuthorizerHttpApiV1Example_HttpApiV1Authorizer_Generated.g.cs +++ b/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/CustomAuthorizerHttpApiV1Example_HttpApiV1Authorizer_Generated.g.cs @@ -40,7 +40,7 @@ public CustomAuthorizerHttpApiV1Example_HttpApiV1Authorizer_Generated() var authorizerValue = default(string); if (__request__.RequestContext?.Authorizer == null || __request__.RequestContext?.Authorizer.ContainsKey("authKey") == false) { -#if NET6_0_OR_GREATER +#if NET8_0_OR_GREATER __context__.Logger.LogDebug("Authorizer attribute 'authKey' was missing, returning unauthorized."); #else __context__.Logger.Log("Authorizer attribute 'authKey' was missing, returning unauthorized."); @@ -64,7 +64,7 @@ public CustomAuthorizerHttpApiV1Example_HttpApiV1Authorizer_Generated() } catch (Exception e) when (e is InvalidCastException || e is FormatException || e is OverflowException || e is ArgumentException) { -#if NET6_0_OR_GREATER +#if NET8_0_OR_GREATER __context__.Logger.LogError(e, "Failed to convert authorizer attribute 'authKey', returning unauthorized."); #else __context__.Logger.Log("Failed to convert authorizer attribute 'authKey', returning unauthorized. Exception: " + e.ToString()); diff --git a/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/CustomAuthorizerNonStringExample_HttpApiWithNonString_Generated.g.cs b/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/CustomAuthorizerNonStringExample_HttpApiWithNonString_Generated.g.cs index 2847aae15..0614accf0 100644 --- a/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/CustomAuthorizerNonStringExample_HttpApiWithNonString_Generated.g.cs +++ b/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/CustomAuthorizerNonStringExample_HttpApiWithNonString_Generated.g.cs @@ -40,7 +40,7 @@ public CustomAuthorizerNonStringExample_HttpApiWithNonString_Generated() var userId = default(int); if (__request__.RequestContext?.Authorizer?.Lambda == null || __request__.RequestContext?.Authorizer?.Lambda.ContainsKey("userId") == false) { -#if NET6_0_OR_GREATER +#if NET8_0_OR_GREATER __context__.Logger.LogDebug("Authorizer attribute 'userId' was missing, returning unauthorized."); #else __context__.Logger.Log("Authorizer attribute 'userId' was missing, returning unauthorized."); @@ -64,7 +64,7 @@ public CustomAuthorizerNonStringExample_HttpApiWithNonString_Generated() } catch (Exception e) when (e is InvalidCastException || e is FormatException || e is OverflowException || e is ArgumentException) { -#if NET6_0_OR_GREATER +#if NET8_0_OR_GREATER __context__.Logger.LogError(e, "Failed to convert authorizer attribute 'userId', returning unauthorized."); #else __context__.Logger.Log("Failed to convert authorizer attribute 'userId', returning unauthorized. Exception: " + e.ToString()); @@ -84,7 +84,7 @@ public CustomAuthorizerNonStringExample_HttpApiWithNonString_Generated() var isAdmin = default(bool); if (__request__.RequestContext?.Authorizer?.Lambda == null || __request__.RequestContext?.Authorizer?.Lambda.ContainsKey("isAdmin") == false) { -#if NET6_0_OR_GREATER +#if NET8_0_OR_GREATER __context__.Logger.LogDebug("Authorizer attribute 'isAdmin' was missing, returning unauthorized."); #else __context__.Logger.Log("Authorizer attribute 'isAdmin' was missing, returning unauthorized."); @@ -108,7 +108,7 @@ public CustomAuthorizerNonStringExample_HttpApiWithNonString_Generated() } catch (Exception e) when (e is InvalidCastException || e is FormatException || e is OverflowException || e is ArgumentException) { -#if NET6_0_OR_GREATER +#if NET8_0_OR_GREATER __context__.Logger.LogError(e, "Failed to convert authorizer attribute 'isAdmin', returning unauthorized."); #else __context__.Logger.Log("Failed to convert authorizer attribute 'isAdmin', returning unauthorized. Exception: " + e.ToString()); @@ -128,7 +128,7 @@ public CustomAuthorizerNonStringExample_HttpApiWithNonString_Generated() var score = default(double); if (__request__.RequestContext?.Authorizer?.Lambda == null || __request__.RequestContext?.Authorizer?.Lambda.ContainsKey("score") == false) { -#if NET6_0_OR_GREATER +#if NET8_0_OR_GREATER __context__.Logger.LogDebug("Authorizer attribute 'score' was missing, returning unauthorized."); #else __context__.Logger.Log("Authorizer attribute 'score' was missing, returning unauthorized."); @@ -152,7 +152,7 @@ public CustomAuthorizerNonStringExample_HttpApiWithNonString_Generated() } catch (Exception e) when (e is InvalidCastException || e is FormatException || e is OverflowException || e is ArgumentException) { -#if NET6_0_OR_GREATER +#if NET8_0_OR_GREATER __context__.Logger.LogError(e, "Failed to convert authorizer attribute 'score', returning unauthorized."); #else __context__.Logger.Log("Failed to convert authorizer attribute 'score', returning unauthorized. Exception: " + e.ToString()); diff --git a/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/CustomAuthorizerRestExample_RestAuthorizer_Generated.g.cs b/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/CustomAuthorizerRestExample_RestAuthorizer_Generated.g.cs index 29c7b4ada..98c620ef8 100644 --- a/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/CustomAuthorizerRestExample_RestAuthorizer_Generated.g.cs +++ b/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/CustomAuthorizerRestExample_RestAuthorizer_Generated.g.cs @@ -1,4 +1,4 @@ -// +// using System; using System.Linq; @@ -40,7 +40,7 @@ public CustomAuthorizerRestExample_RestAuthorizer_Generated() var authorizerValue = default(string); if (__request__.RequestContext?.Authorizer == null || __request__.RequestContext?.Authorizer.ContainsKey("theAuthKey") == false) { -#if NET6_0_OR_GREATER +#if NET8_0_OR_GREATER __context__.Logger.LogDebug("Authorizer attribute 'theAuthKey' was missing, returning unauthorized."); #else __context__.Logger.Log("Authorizer attribute 'theAuthKey' was missing, returning unauthorized."); @@ -64,7 +64,7 @@ public CustomAuthorizerRestExample_RestAuthorizer_Generated() } catch (Exception e) when (e is InvalidCastException || e is FormatException || e is OverflowException || e is ArgumentException) { -#if NET6_0_OR_GREATER +#if NET8_0_OR_GREATER __context__.Logger.LogError(e, "Failed to convert authorizer attribute 'theAuthKey', returning unauthorized."); #else __context__.Logger.Log("Failed to convert authorizer attribute 'theAuthKey', returning unauthorized. Exception: " + e.ToString()); diff --git a/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/CustomAuthorizerWithIHttpResultsExample_AuthorizerWithIHttpResults_Generated.g.cs b/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/CustomAuthorizerWithIHttpResultsExample_AuthorizerWithIHttpResults_Generated.g.cs index 2a99d1b8d..4e0adf6fc 100644 --- a/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/CustomAuthorizerWithIHttpResultsExample_AuthorizerWithIHttpResults_Generated.g.cs +++ b/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/CustomAuthorizerWithIHttpResultsExample_AuthorizerWithIHttpResults_Generated.g.cs @@ -41,7 +41,7 @@ public System.IO.Stream AuthorizerWithIHttpResults(Amazon.Lambda.APIGatewayEvent var userId = default(string); if (__request__.RequestContext?.Authorizer?.Lambda == null || __request__.RequestContext?.Authorizer?.Lambda.ContainsKey("userId") == false) { -#if NET6_0_OR_GREATER +#if NET8_0_OR_GREATER __context__.Logger.LogDebug("Authorizer attribute 'userId' was missing, returning unauthorized."); #else __context__.Logger.Log("Authorizer attribute 'userId' was missing, returning unauthorized."); @@ -68,7 +68,7 @@ public System.IO.Stream AuthorizerWithIHttpResults(Amazon.Lambda.APIGatewayEvent } catch (Exception e) when (e is InvalidCastException || e is FormatException || e is OverflowException || e is ArgumentException) { -#if NET6_0_OR_GREATER +#if NET8_0_OR_GREATER __context__.Logger.LogError(e, "Failed to convert authorizer attribute 'userId', returning unauthorized."); #else __context__.Logger.Log("Failed to convert authorizer attribute 'userId', returning unauthorized. Exception: " + e.ToString()); diff --git a/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/IAuthorizerResultExample_SimpleHttpApiAuthorizer_Generated.g.cs b/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/IAuthorizerResultExample_SimpleHttpApiAuthorizer_Generated.g.cs index 274071032..515fae050 100644 --- a/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/IAuthorizerResultExample_SimpleHttpApiAuthorizer_Generated.g.cs +++ b/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/IAuthorizerResultExample_SimpleHttpApiAuthorizer_Generated.g.cs @@ -45,7 +45,7 @@ public System.IO.Stream SimpleHttpApiAuthorizer(Amazon.Lambda.APIGatewayEvents.A } catch (Exception e) when (e is InvalidCastException || e is FormatException || e is OverflowException || e is ArgumentException) { -#if NET6_0_OR_GREATER +#if NET8_0_OR_GREATER __context__.Logger.LogError(e, "Failed to extract header 'Authorization'."); #else __context__.Logger.Log("Failed to extract header 'Authorization'. Exception: " + e.ToString()); diff --git a/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/IAuthorizerResultExample_SimpleRestApiAuthorizer_Generated.g.cs b/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/IAuthorizerResultExample_SimpleRestApiAuthorizer_Generated.g.cs index 015545556..3d70b27f6 100644 --- a/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/IAuthorizerResultExample_SimpleRestApiAuthorizer_Generated.g.cs +++ b/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/IAuthorizerResultExample_SimpleRestApiAuthorizer_Generated.g.cs @@ -45,7 +45,7 @@ public System.IO.Stream SimpleRestApiAuthorizer(Amazon.Lambda.APIGatewayEvents.A } catch (Exception e) when (e is InvalidCastException || e is FormatException || e is OverflowException || e is ArgumentException) { -#if NET6_0_OR_GREATER +#if NET8_0_OR_GREATER __context__.Logger.LogError(e, "Failed to extract authorization token."); #else __context__.Logger.Log("Failed to extract authorization token. Exception: " + e.ToString()); diff --git a/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/ProtectedFunction_GetHttpApiV1UserInfo_Generated.g.cs b/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/ProtectedFunction_GetHttpApiV1UserInfo_Generated.g.cs index 62c34f770..cf0912e40 100644 --- a/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/ProtectedFunction_GetHttpApiV1UserInfo_Generated.g.cs +++ b/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/ProtectedFunction_GetHttpApiV1UserInfo_Generated.g.cs @@ -40,7 +40,7 @@ public Amazon.Lambda.APIGatewayEvents.APIGatewayProxyResponse GetHttpApiV1UserIn var userId = default(string); if (__request__.RequestContext?.Authorizer == null || __request__.RequestContext?.Authorizer.ContainsKey("userId") == false) { -#if NET6_0_OR_GREATER +#if NET8_0_OR_GREATER __context__.Logger.LogDebug("Authorizer attribute 'userId' was missing, returning unauthorized."); #else __context__.Logger.Log("Authorizer attribute 'userId' was missing, returning unauthorized."); @@ -64,7 +64,7 @@ public Amazon.Lambda.APIGatewayEvents.APIGatewayProxyResponse GetHttpApiV1UserIn } catch (Exception e) when (e is InvalidCastException || e is FormatException || e is OverflowException || e is ArgumentException) { -#if NET6_0_OR_GREATER +#if NET8_0_OR_GREATER __context__.Logger.LogError(e, "Failed to convert authorizer attribute 'userId', returning unauthorized."); #else __context__.Logger.Log("Failed to convert authorizer attribute 'userId', returning unauthorized. Exception: " + e.ToString()); @@ -84,7 +84,7 @@ public Amazon.Lambda.APIGatewayEvents.APIGatewayProxyResponse GetHttpApiV1UserIn var email = default(string); if (__request__.RequestContext?.Authorizer == null || __request__.RequestContext?.Authorizer.ContainsKey("email") == false) { -#if NET6_0_OR_GREATER +#if NET8_0_OR_GREATER __context__.Logger.LogDebug("Authorizer attribute 'email' was missing, returning unauthorized."); #else __context__.Logger.Log("Authorizer attribute 'email' was missing, returning unauthorized."); @@ -108,7 +108,7 @@ public Amazon.Lambda.APIGatewayEvents.APIGatewayProxyResponse GetHttpApiV1UserIn } catch (Exception e) when (e is InvalidCastException || e is FormatException || e is OverflowException || e is ArgumentException) { -#if NET6_0_OR_GREATER +#if NET8_0_OR_GREATER __context__.Logger.LogError(e, "Failed to convert authorizer attribute 'email', returning unauthorized."); #else __context__.Logger.Log("Failed to convert authorizer attribute 'email', returning unauthorized. Exception: " + e.ToString()); @@ -128,7 +128,7 @@ public Amazon.Lambda.APIGatewayEvents.APIGatewayProxyResponse GetHttpApiV1UserIn var tenantId = default(string); if (__request__.RequestContext?.Authorizer == null || __request__.RequestContext?.Authorizer.ContainsKey("tenantId") == false) { -#if NET6_0_OR_GREATER +#if NET8_0_OR_GREATER __context__.Logger.LogDebug("Authorizer attribute 'tenantId' was missing, returning unauthorized."); #else __context__.Logger.Log("Authorizer attribute 'tenantId' was missing, returning unauthorized."); @@ -152,7 +152,7 @@ public Amazon.Lambda.APIGatewayEvents.APIGatewayProxyResponse GetHttpApiV1UserIn } catch (Exception e) when (e is InvalidCastException || e is FormatException || e is OverflowException || e is ArgumentException) { -#if NET6_0_OR_GREATER +#if NET8_0_OR_GREATER __context__.Logger.LogError(e, "Failed to convert authorizer attribute 'tenantId', returning unauthorized."); #else __context__.Logger.Log("Failed to convert authorizer attribute 'tenantId', returning unauthorized. Exception: " + e.ToString()); diff --git a/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/ProtectedFunction_GetIHttpResult_Generated.g.cs b/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/ProtectedFunction_GetIHttpResult_Generated.g.cs index b969dd378..133a2cb65 100644 --- a/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/ProtectedFunction_GetIHttpResult_Generated.g.cs +++ b/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/ProtectedFunction_GetIHttpResult_Generated.g.cs @@ -41,7 +41,7 @@ public System.IO.Stream GetIHttpResult(Amazon.Lambda.APIGatewayEvents.APIGateway var userId = default(string); if (__request__.RequestContext?.Authorizer?.Lambda == null || __request__.RequestContext?.Authorizer?.Lambda.ContainsKey("userId") == false) { -#if NET6_0_OR_GREATER +#if NET8_0_OR_GREATER __context__.Logger.LogDebug("Authorizer attribute 'userId' was missing, returning unauthorized."); #else __context__.Logger.Log("Authorizer attribute 'userId' was missing, returning unauthorized."); @@ -68,7 +68,7 @@ public System.IO.Stream GetIHttpResult(Amazon.Lambda.APIGatewayEvents.APIGateway } catch (Exception e) when (e is InvalidCastException || e is FormatException || e is OverflowException || e is ArgumentException) { -#if NET6_0_OR_GREATER +#if NET8_0_OR_GREATER __context__.Logger.LogError(e, "Failed to convert authorizer attribute 'userId', returning unauthorized."); #else __context__.Logger.Log("Failed to convert authorizer attribute 'userId', returning unauthorized. Exception: " + e.ToString()); @@ -91,7 +91,7 @@ public System.IO.Stream GetIHttpResult(Amazon.Lambda.APIGatewayEvents.APIGateway var email = default(string); if (__request__.RequestContext?.Authorizer?.Lambda == null || __request__.RequestContext?.Authorizer?.Lambda.ContainsKey("email") == false) { -#if NET6_0_OR_GREATER +#if NET8_0_OR_GREATER __context__.Logger.LogDebug("Authorizer attribute 'email' was missing, returning unauthorized."); #else __context__.Logger.Log("Authorizer attribute 'email' was missing, returning unauthorized."); @@ -118,7 +118,7 @@ public System.IO.Stream GetIHttpResult(Amazon.Lambda.APIGatewayEvents.APIGateway } catch (Exception e) when (e is InvalidCastException || e is FormatException || e is OverflowException || e is ArgumentException) { -#if NET6_0_OR_GREATER +#if NET8_0_OR_GREATER __context__.Logger.LogError(e, "Failed to convert authorizer attribute 'email', returning unauthorized."); #else __context__.Logger.Log("Failed to convert authorizer attribute 'email', returning unauthorized. Exception: " + e.ToString()); diff --git a/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/ProtectedFunction_GetNonStringUserInfo_Generated.g.cs b/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/ProtectedFunction_GetNonStringUserInfo_Generated.g.cs index 10301f09c..fc339b197 100644 --- a/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/ProtectedFunction_GetNonStringUserInfo_Generated.g.cs +++ b/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/ProtectedFunction_GetNonStringUserInfo_Generated.g.cs @@ -40,7 +40,7 @@ public Amazon.Lambda.APIGatewayEvents.APIGatewayHttpApiV2ProxyResponse GetNonStr var tenantId = default(int); if (__request__.RequestContext?.Authorizer?.Lambda == null || __request__.RequestContext?.Authorizer?.Lambda.ContainsKey("numericTenantId") == false) { -#if NET6_0_OR_GREATER +#if NET8_0_OR_GREATER __context__.Logger.LogDebug("Authorizer attribute 'numericTenantId' was missing, returning unauthorized."); #else __context__.Logger.Log("Authorizer attribute 'numericTenantId' was missing, returning unauthorized."); @@ -64,7 +64,7 @@ public Amazon.Lambda.APIGatewayEvents.APIGatewayHttpApiV2ProxyResponse GetNonStr } catch (Exception e) when (e is InvalidCastException || e is FormatException || e is OverflowException || e is ArgumentException) { -#if NET6_0_OR_GREATER +#if NET8_0_OR_GREATER __context__.Logger.LogError(e, "Failed to convert authorizer attribute 'numericTenantId', returning unauthorized."); #else __context__.Logger.Log("Failed to convert authorizer attribute 'numericTenantId', returning unauthorized. Exception: " + e.ToString()); @@ -84,7 +84,7 @@ public Amazon.Lambda.APIGatewayEvents.APIGatewayHttpApiV2ProxyResponse GetNonStr var isAdmin = default(bool); if (__request__.RequestContext?.Authorizer?.Lambda == null || __request__.RequestContext?.Authorizer?.Lambda.ContainsKey("isAdmin") == false) { -#if NET6_0_OR_GREATER +#if NET8_0_OR_GREATER __context__.Logger.LogDebug("Authorizer attribute 'isAdmin' was missing, returning unauthorized."); #else __context__.Logger.Log("Authorizer attribute 'isAdmin' was missing, returning unauthorized."); @@ -108,7 +108,7 @@ public Amazon.Lambda.APIGatewayEvents.APIGatewayHttpApiV2ProxyResponse GetNonStr } catch (Exception e) when (e is InvalidCastException || e is FormatException || e is OverflowException || e is ArgumentException) { -#if NET6_0_OR_GREATER +#if NET8_0_OR_GREATER __context__.Logger.LogError(e, "Failed to convert authorizer attribute 'isAdmin', returning unauthorized."); #else __context__.Logger.Log("Failed to convert authorizer attribute 'isAdmin', returning unauthorized. Exception: " + e.ToString()); @@ -128,7 +128,7 @@ public Amazon.Lambda.APIGatewayEvents.APIGatewayHttpApiV2ProxyResponse GetNonStr var score = default(double); if (__request__.RequestContext?.Authorizer?.Lambda == null || __request__.RequestContext?.Authorizer?.Lambda.ContainsKey("score") == false) { -#if NET6_0_OR_GREATER +#if NET8_0_OR_GREATER __context__.Logger.LogDebug("Authorizer attribute 'score' was missing, returning unauthorized."); #else __context__.Logger.Log("Authorizer attribute 'score' was missing, returning unauthorized."); @@ -152,7 +152,7 @@ public Amazon.Lambda.APIGatewayEvents.APIGatewayHttpApiV2ProxyResponse GetNonStr } catch (Exception e) when (e is InvalidCastException || e is FormatException || e is OverflowException || e is ArgumentException) { -#if NET6_0_OR_GREATER +#if NET8_0_OR_GREATER __context__.Logger.LogError(e, "Failed to convert authorizer attribute 'score', returning unauthorized."); #else __context__.Logger.Log("Failed to convert authorizer attribute 'score', returning unauthorized. Exception: " + e.ToString()); diff --git a/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/ProtectedFunction_GetRestUserInfo_Generated.g.cs b/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/ProtectedFunction_GetRestUserInfo_Generated.g.cs index 0ae3d3107..fcf4fd4ea 100644 --- a/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/ProtectedFunction_GetRestUserInfo_Generated.g.cs +++ b/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/ProtectedFunction_GetRestUserInfo_Generated.g.cs @@ -40,7 +40,7 @@ public Amazon.Lambda.APIGatewayEvents.APIGatewayProxyResponse GetRestUserInfo(Am var userId = default(string); if (__request__.RequestContext?.Authorizer == null || __request__.RequestContext?.Authorizer.ContainsKey("userId") == false) { -#if NET6_0_OR_GREATER +#if NET8_0_OR_GREATER __context__.Logger.LogDebug("Authorizer attribute 'userId' was missing, returning unauthorized."); #else __context__.Logger.Log("Authorizer attribute 'userId' was missing, returning unauthorized."); @@ -64,7 +64,7 @@ public Amazon.Lambda.APIGatewayEvents.APIGatewayProxyResponse GetRestUserInfo(Am } catch (Exception e) when (e is InvalidCastException || e is FormatException || e is OverflowException || e is ArgumentException) { -#if NET6_0_OR_GREATER +#if NET8_0_OR_GREATER __context__.Logger.LogError(e, "Failed to convert authorizer attribute 'userId', returning unauthorized."); #else __context__.Logger.Log("Failed to convert authorizer attribute 'userId', returning unauthorized. Exception: " + e.ToString()); @@ -84,7 +84,7 @@ public Amazon.Lambda.APIGatewayEvents.APIGatewayProxyResponse GetRestUserInfo(Am var email = default(string); if (__request__.RequestContext?.Authorizer == null || __request__.RequestContext?.Authorizer.ContainsKey("email") == false) { -#if NET6_0_OR_GREATER +#if NET8_0_OR_GREATER __context__.Logger.LogDebug("Authorizer attribute 'email' was missing, returning unauthorized."); #else __context__.Logger.Log("Authorizer attribute 'email' was missing, returning unauthorized."); @@ -108,7 +108,7 @@ public Amazon.Lambda.APIGatewayEvents.APIGatewayProxyResponse GetRestUserInfo(Am } catch (Exception e) when (e is InvalidCastException || e is FormatException || e is OverflowException || e is ArgumentException) { -#if NET6_0_OR_GREATER +#if NET8_0_OR_GREATER __context__.Logger.LogError(e, "Failed to convert authorizer attribute 'email', returning unauthorized."); #else __context__.Logger.Log("Failed to convert authorizer attribute 'email', returning unauthorized. Exception: " + e.ToString()); @@ -128,7 +128,7 @@ public Amazon.Lambda.APIGatewayEvents.APIGatewayProxyResponse GetRestUserInfo(Am var tenantId = default(string); if (__request__.RequestContext?.Authorizer == null || __request__.RequestContext?.Authorizer.ContainsKey("tenantId") == false) { -#if NET6_0_OR_GREATER +#if NET8_0_OR_GREATER __context__.Logger.LogDebug("Authorizer attribute 'tenantId' was missing, returning unauthorized."); #else __context__.Logger.Log("Authorizer attribute 'tenantId' was missing, returning unauthorized."); @@ -152,7 +152,7 @@ public Amazon.Lambda.APIGatewayEvents.APIGatewayProxyResponse GetRestUserInfo(Am } catch (Exception e) when (e is InvalidCastException || e is FormatException || e is OverflowException || e is ArgumentException) { -#if NET6_0_OR_GREATER +#if NET8_0_OR_GREATER __context__.Logger.LogError(e, "Failed to convert authorizer attribute 'tenantId', returning unauthorized."); #else __context__.Logger.Log("Failed to convert authorizer attribute 'tenantId', returning unauthorized. Exception: " + e.ToString()); diff --git a/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/ProtectedFunction_GetSimpleHttpApiUserInfo_Generated.g.cs b/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/ProtectedFunction_GetSimpleHttpApiUserInfo_Generated.g.cs index 784c96531..be1099135 100644 --- a/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/ProtectedFunction_GetSimpleHttpApiUserInfo_Generated.g.cs +++ b/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/ProtectedFunction_GetSimpleHttpApiUserInfo_Generated.g.cs @@ -40,7 +40,7 @@ public Amazon.Lambda.APIGatewayEvents.APIGatewayHttpApiV2ProxyResponse GetSimple var userId = default(string); if (__request__.RequestContext?.Authorizer?.Lambda == null || __request__.RequestContext?.Authorizer?.Lambda.ContainsKey("userId") == false) { -#if NET6_0_OR_GREATER +#if NET8_0_OR_GREATER __context__.Logger.LogDebug("Authorizer attribute 'userId' was missing, returning unauthorized."); #else __context__.Logger.Log("Authorizer attribute 'userId' was missing, returning unauthorized."); @@ -64,7 +64,7 @@ public Amazon.Lambda.APIGatewayEvents.APIGatewayHttpApiV2ProxyResponse GetSimple } catch (Exception e) when (e is InvalidCastException || e is FormatException || e is OverflowException || e is ArgumentException) { -#if NET6_0_OR_GREATER +#if NET8_0_OR_GREATER __context__.Logger.LogError(e, "Failed to convert authorizer attribute 'userId', returning unauthorized."); #else __context__.Logger.Log("Failed to convert authorizer attribute 'userId', returning unauthorized. Exception: " + e.ToString()); @@ -84,7 +84,7 @@ public Amazon.Lambda.APIGatewayEvents.APIGatewayHttpApiV2ProxyResponse GetSimple var email = default(string); if (__request__.RequestContext?.Authorizer?.Lambda == null || __request__.RequestContext?.Authorizer?.Lambda.ContainsKey("email") == false) { -#if NET6_0_OR_GREATER +#if NET8_0_OR_GREATER __context__.Logger.LogDebug("Authorizer attribute 'email' was missing, returning unauthorized."); #else __context__.Logger.Log("Authorizer attribute 'email' was missing, returning unauthorized."); @@ -108,7 +108,7 @@ public Amazon.Lambda.APIGatewayEvents.APIGatewayHttpApiV2ProxyResponse GetSimple } catch (Exception e) when (e is InvalidCastException || e is FormatException || e is OverflowException || e is ArgumentException) { -#if NET6_0_OR_GREATER +#if NET8_0_OR_GREATER __context__.Logger.LogError(e, "Failed to convert authorizer attribute 'email', returning unauthorized."); #else __context__.Logger.Log("Failed to convert authorizer attribute 'email', returning unauthorized. Exception: " + e.ToString()); @@ -128,7 +128,7 @@ public Amazon.Lambda.APIGatewayEvents.APIGatewayHttpApiV2ProxyResponse GetSimple var tenantId = default(string); if (__request__.RequestContext?.Authorizer?.Lambda == null || __request__.RequestContext?.Authorizer?.Lambda.ContainsKey("tenantId") == false) { -#if NET6_0_OR_GREATER +#if NET8_0_OR_GREATER __context__.Logger.LogDebug("Authorizer attribute 'tenantId' was missing, returning unauthorized."); #else __context__.Logger.Log("Authorizer attribute 'tenantId' was missing, returning unauthorized."); @@ -152,7 +152,7 @@ public Amazon.Lambda.APIGatewayEvents.APIGatewayHttpApiV2ProxyResponse GetSimple } catch (Exception e) when (e is InvalidCastException || e is FormatException || e is OverflowException || e is ArgumentException) { -#if NET6_0_OR_GREATER +#if NET8_0_OR_GREATER __context__.Logger.LogError(e, "Failed to convert authorizer attribute 'tenantId', returning unauthorized."); #else __context__.Logger.Log("Failed to convert authorizer attribute 'tenantId', returning unauthorized. Exception: " + e.ToString()); diff --git a/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/ProtectedFunction_GetSimpleRestApiUserInfo_Generated.g.cs b/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/ProtectedFunction_GetSimpleRestApiUserInfo_Generated.g.cs index f81b75d86..6ac1d1b4a 100644 --- a/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/ProtectedFunction_GetSimpleRestApiUserInfo_Generated.g.cs +++ b/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/ProtectedFunction_GetSimpleRestApiUserInfo_Generated.g.cs @@ -40,7 +40,7 @@ public Amazon.Lambda.APIGatewayEvents.APIGatewayProxyResponse GetSimpleRestApiUs var userId = default(string); if (__request__.RequestContext?.Authorizer == null || __request__.RequestContext?.Authorizer.ContainsKey("userId") == false) { -#if NET6_0_OR_GREATER +#if NET8_0_OR_GREATER __context__.Logger.LogDebug("Authorizer attribute 'userId' was missing, returning unauthorized."); #else __context__.Logger.Log("Authorizer attribute 'userId' was missing, returning unauthorized."); @@ -64,7 +64,7 @@ public Amazon.Lambda.APIGatewayEvents.APIGatewayProxyResponse GetSimpleRestApiUs } catch (Exception e) when (e is InvalidCastException || e is FormatException || e is OverflowException || e is ArgumentException) { -#if NET6_0_OR_GREATER +#if NET8_0_OR_GREATER __context__.Logger.LogError(e, "Failed to convert authorizer attribute 'userId', returning unauthorized."); #else __context__.Logger.Log("Failed to convert authorizer attribute 'userId', returning unauthorized. Exception: " + e.ToString()); @@ -84,7 +84,7 @@ public Amazon.Lambda.APIGatewayEvents.APIGatewayProxyResponse GetSimpleRestApiUs var email = default(string); if (__request__.RequestContext?.Authorizer == null || __request__.RequestContext?.Authorizer.ContainsKey("email") == false) { -#if NET6_0_OR_GREATER +#if NET8_0_OR_GREATER __context__.Logger.LogDebug("Authorizer attribute 'email' was missing, returning unauthorized."); #else __context__.Logger.Log("Authorizer attribute 'email' was missing, returning unauthorized."); @@ -108,7 +108,7 @@ public Amazon.Lambda.APIGatewayEvents.APIGatewayProxyResponse GetSimpleRestApiUs } catch (Exception e) when (e is InvalidCastException || e is FormatException || e is OverflowException || e is ArgumentException) { -#if NET6_0_OR_GREATER +#if NET8_0_OR_GREATER __context__.Logger.LogError(e, "Failed to convert authorizer attribute 'email', returning unauthorized."); #else __context__.Logger.Log("Failed to convert authorizer attribute 'email', returning unauthorized. Exception: " + e.ToString()); @@ -128,7 +128,7 @@ public Amazon.Lambda.APIGatewayEvents.APIGatewayProxyResponse GetSimpleRestApiUs var tenantId = default(string); if (__request__.RequestContext?.Authorizer == null || __request__.RequestContext?.Authorizer.ContainsKey("tenantId") == false) { -#if NET6_0_OR_GREATER +#if NET8_0_OR_GREATER __context__.Logger.LogDebug("Authorizer attribute 'tenantId' was missing, returning unauthorized."); #else __context__.Logger.Log("Authorizer attribute 'tenantId' was missing, returning unauthorized."); @@ -152,7 +152,7 @@ public Amazon.Lambda.APIGatewayEvents.APIGatewayProxyResponse GetSimpleRestApiUs } catch (Exception e) when (e is InvalidCastException || e is FormatException || e is OverflowException || e is ArgumentException) { -#if NET6_0_OR_GREATER +#if NET8_0_OR_GREATER __context__.Logger.LogError(e, "Failed to convert authorizer attribute 'tenantId', returning unauthorized."); #else __context__.Logger.Log("Failed to convert authorizer attribute 'tenantId', returning unauthorized. Exception: " + e.ToString()); diff --git a/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/ProtectedFunction_GetUserInfo_Generated.g.cs b/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/ProtectedFunction_GetUserInfo_Generated.g.cs index 469a54036..3cc64ec37 100644 --- a/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/ProtectedFunction_GetUserInfo_Generated.g.cs +++ b/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/ProtectedFunction_GetUserInfo_Generated.g.cs @@ -40,7 +40,7 @@ public Amazon.Lambda.APIGatewayEvents.APIGatewayHttpApiV2ProxyResponse GetUserIn var userId = default(string); if (__request__.RequestContext?.Authorizer?.Lambda == null || __request__.RequestContext?.Authorizer?.Lambda.ContainsKey("userId") == false) { -#if NET6_0_OR_GREATER +#if NET8_0_OR_GREATER __context__.Logger.LogDebug("Authorizer attribute 'userId' was missing, returning unauthorized."); #else __context__.Logger.Log("Authorizer attribute 'userId' was missing, returning unauthorized."); @@ -64,7 +64,7 @@ public Amazon.Lambda.APIGatewayEvents.APIGatewayHttpApiV2ProxyResponse GetUserIn } catch (Exception e) when (e is InvalidCastException || e is FormatException || e is OverflowException || e is ArgumentException) { -#if NET6_0_OR_GREATER +#if NET8_0_OR_GREATER __context__.Logger.LogError(e, "Failed to convert authorizer attribute 'userId', returning unauthorized."); #else __context__.Logger.Log("Failed to convert authorizer attribute 'userId', returning unauthorized. Exception: " + e.ToString()); @@ -84,7 +84,7 @@ public Amazon.Lambda.APIGatewayEvents.APIGatewayHttpApiV2ProxyResponse GetUserIn var email = default(string); if (__request__.RequestContext?.Authorizer?.Lambda == null || __request__.RequestContext?.Authorizer?.Lambda.ContainsKey("email") == false) { -#if NET6_0_OR_GREATER +#if NET8_0_OR_GREATER __context__.Logger.LogDebug("Authorizer attribute 'email' was missing, returning unauthorized."); #else __context__.Logger.Log("Authorizer attribute 'email' was missing, returning unauthorized."); @@ -108,7 +108,7 @@ public Amazon.Lambda.APIGatewayEvents.APIGatewayHttpApiV2ProxyResponse GetUserIn } catch (Exception e) when (e is InvalidCastException || e is FormatException || e is OverflowException || e is ArgumentException) { -#if NET6_0_OR_GREATER +#if NET8_0_OR_GREATER __context__.Logger.LogError(e, "Failed to convert authorizer attribute 'email', returning unauthorized."); #else __context__.Logger.Log("Failed to convert authorizer attribute 'email', returning unauthorized. Exception: " + e.ToString()); @@ -128,7 +128,7 @@ public Amazon.Lambda.APIGatewayEvents.APIGatewayHttpApiV2ProxyResponse GetUserIn var tenantId = default(string); if (__request__.RequestContext?.Authorizer?.Lambda == null || __request__.RequestContext?.Authorizer?.Lambda.ContainsKey("tenantId") == false) { -#if NET6_0_OR_GREATER +#if NET8_0_OR_GREATER __context__.Logger.LogDebug("Authorizer attribute 'tenantId' was missing, returning unauthorized."); #else __context__.Logger.Log("Authorizer attribute 'tenantId' was missing, returning unauthorized."); @@ -152,7 +152,7 @@ public Amazon.Lambda.APIGatewayEvents.APIGatewayHttpApiV2ProxyResponse GetUserIn } catch (Exception e) when (e is InvalidCastException || e is FormatException || e is OverflowException || e is ArgumentException) { -#if NET6_0_OR_GREATER +#if NET8_0_OR_GREATER __context__.Logger.LogError(e, "Failed to convert authorizer attribute 'tenantId', returning unauthorized."); #else __context__.Logger.Log("Failed to convert authorizer attribute 'tenantId', returning unauthorized. Exception: " + e.ToString()); diff --git a/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/ServerlessTemplates/albEvents.template b/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/ServerlessTemplates/albEvents.template index 43133fd00..6d5d4dac4 100644 --- a/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/ServerlessTemplates/albEvents.template +++ b/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/ServerlessTemplates/albEvents.template @@ -14,7 +14,7 @@ ] }, "Properties": { - "Runtime": "dotnet6", + "Runtime": "dotnet10", "CodeUri": ".", "MemorySize": 512, "Timeout": 30, @@ -67,7 +67,6 @@ "Tool": "Amazon.Lambda.Annotations" }, "Properties": { - "ListenerArn": "arn:aws:elasticloadbalancing:us-east-1:123456789012:listener/app/my-alb/abc/def", "Priority": 1, "Conditions": [ { @@ -86,7 +85,8 @@ "Ref": "ALBHelloWorldALBTargetGroup" } } - ] + ], + "ListenerArn": "arn:aws:elasticloadbalancing:us-east-1:123456789012:listener/app/my-alb/abc/def" } }, "ALBWithOptions": { @@ -100,7 +100,7 @@ ] }, "Properties": { - "Runtime": "dotnet6", + "Runtime": "dotnet10", "CodeUri": ".", "MemorySize": 512, "Timeout": 30, @@ -159,9 +159,6 @@ "Tool": "Amazon.Lambda.Annotations" }, "Properties": { - "ListenerArn": { - "Ref": "MyALBListener" - }, "Priority": 5, "Conditions": [ { @@ -196,7 +193,10 @@ "Ref": "ALBWithOptionsALBTargetGroup" } } - ] + ], + "ListenerArn": { + "Ref": "MyALBListener" + } } } } diff --git a/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/ServerlessTemplates/customAuthorizerApp.template b/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/ServerlessTemplates/customAuthorizerApp.template index 76ca3e862..7298d7d01 100644 --- a/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/ServerlessTemplates/customAuthorizerApp.template +++ b/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/ServerlessTemplates/customAuthorizerApp.template @@ -111,7 +111,7 @@ "Tool": "Amazon.Lambda.Annotations" }, "Properties": { - "Runtime": "dotnet6", + "Runtime": "dotnet10", "CodeUri": ".", "MemorySize": 512, "Timeout": 30, @@ -128,7 +128,7 @@ "Tool": "Amazon.Lambda.Annotations" }, "Properties": { - "Runtime": "dotnet6", + "Runtime": "dotnet10", "CodeUri": ".", "MemorySize": 512, "Timeout": 30, @@ -145,7 +145,7 @@ "Tool": "Amazon.Lambda.Annotations" }, "Properties": { - "Runtime": "dotnet6", + "Runtime": "dotnet10", "CodeUri": ".", "MemorySize": 512, "Timeout": 30, @@ -162,7 +162,7 @@ "Tool": "Amazon.Lambda.Annotations" }, "Properties": { - "Runtime": "dotnet6", + "Runtime": "dotnet10", "CodeUri": ".", "MemorySize": 512, "Timeout": 30, @@ -179,7 +179,7 @@ "Tool": "Amazon.Lambda.Annotations" }, "Properties": { - "Runtime": "dotnet6", + "Runtime": "dotnet10", "CodeUri": ".", "MemorySize": 512, "Timeout": 30, @@ -207,7 +207,7 @@ } }, "Properties": { - "Runtime": "dotnet6", + "Runtime": "dotnet10", "CodeUri": ".", "MemorySize": 512, "Timeout": 30, @@ -250,7 +250,7 @@ } }, "Properties": { - "Runtime": "dotnet6", + "Runtime": "dotnet10", "CodeUri": ".", "MemorySize": 512, "Timeout": 30, @@ -292,7 +292,7 @@ } }, "Properties": { - "Runtime": "dotnet6", + "Runtime": "dotnet10", "CodeUri": ".", "MemorySize": 512, "Timeout": 30, @@ -332,7 +332,7 @@ } }, "Properties": { - "Runtime": "dotnet6", + "Runtime": "dotnet10", "CodeUri": ".", "MemorySize": 512, "Timeout": 30, @@ -376,7 +376,7 @@ } }, "Properties": { - "Runtime": "dotnet6", + "Runtime": "dotnet10", "CodeUri": ".", "MemorySize": 512, "Timeout": 30, @@ -420,7 +420,7 @@ } }, "Properties": { - "Runtime": "dotnet6", + "Runtime": "dotnet10", "CodeUri": ".", "MemorySize": 512, "Timeout": 30, @@ -463,7 +463,7 @@ } }, "Properties": { - "Runtime": "dotnet6", + "Runtime": "dotnet10", "CodeUri": ".", "MemorySize": 512, "Timeout": 30, @@ -506,7 +506,7 @@ } }, "Properties": { - "Runtime": "dotnet6", + "Runtime": "dotnet10", "CodeUri": ".", "MemorySize": 512, "Timeout": 30, @@ -549,7 +549,7 @@ } }, "Properties": { - "Runtime": "dotnet6", + "Runtime": "dotnet10", "CodeUri": ".", "MemorySize": 512, "Timeout": 30, diff --git a/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/ServerlessTemplates/dynamoDBEvents.template b/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/ServerlessTemplates/dynamoDBEvents.template index 5fbcf8d48..d5d5c8234 100644 --- a/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/ServerlessTemplates/dynamoDBEvents.template +++ b/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/ServerlessTemplates/dynamoDBEvents.template @@ -48,7 +48,6 @@ "MyTable": { "Type": "DynamoDB", "Properties": { - "Stream": "arn:aws:dynamodb:us-east-2:444455556666:table/MyTable/stream/2024-01-01T00:00:00", "StartingPosition": "LATEST", "BatchSize": 50, "MaximumBatchingWindowInSeconds": 2, @@ -61,27 +60,28 @@ "Pattern": "My-Filter-2" } ] - } + }, + "Stream": "arn:aws:dynamodb:us-east-2:444455556666:table/MyTable/stream/2024-01-01T00:00:00" } }, "MyTable2": { "Type": "DynamoDB", "Properties": { - "Stream": "arn:aws:dynamodb:us-east-2:444455556666:table/MyTable2/stream/2024-01-01T00:00:00", "StartingPosition": "TRIM_HORIZON", - "Enabled": false + "Enabled": false, + "Stream": "arn:aws:dynamodb:us-east-2:444455556666:table/MyTable2/stream/2024-01-01T00:00:00" } }, "testTableEvent": { "Type": "DynamoDB", "Properties": { + "StartingPosition": "LATEST", "Stream": { "Fn::GetAtt": [ "testTable", "StreamArn" ] - }, - "StartingPosition": "LATEST" + } } } } @@ -118,8 +118,8 @@ "MyTable": { "Type": "DynamoDB", "Properties": { - "Stream": "arn:aws:dynamodb:us-east-2:444455556666:table/MyTable/stream/2024-01-01T00:00:00", - "StartingPosition": "LATEST" + "StartingPosition": "LATEST", + "Stream": "arn:aws:dynamodb:us-east-2:444455556666:table/MyTable/stream/2024-01-01T00:00:00" } } } diff --git a/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/ServerlessTemplates/hostbuild.serverless.template b/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/ServerlessTemplates/hostbuild.serverless.template index 94e4d7da9..e1e14bf92 100644 --- a/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/ServerlessTemplates/hostbuild.serverless.template +++ b/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/ServerlessTemplates/hostbuild.serverless.template @@ -18,7 +18,7 @@ } }, "Properties": { - "Runtime": "dotnet6", + "Runtime": "dotnet10", "CodeUri": ".", "MemorySize": 512, "Timeout": 30, diff --git a/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/ServerlessTemplates/snsEvents.template b/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/ServerlessTemplates/snsEvents.template index e8f100def..857bd3fd4 100644 --- a/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/ServerlessTemplates/snsEvents.template +++ b/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/ServerlessTemplates/snsEvents.template @@ -38,8 +38,8 @@ "MyTopic": { "Type": "SNS", "Properties": { - "Topic": "arn:aws:sns:us-east-2:444455556666:MyTopic", - "FilterPolicy": "{ \"store\": [\"example_corp\"] }" + "FilterPolicy": "{ \"store\": [\"example_corp\"] }", + "Topic": "arn:aws:sns:us-east-2:444455556666:MyTopic" } }, "testTopicEvent": { diff --git a/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/ServerlessTemplates/sqsEvents.template b/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/ServerlessTemplates/sqsEvents.template index 7e2d1bfa2..3f004e48d 100644 --- a/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/ServerlessTemplates/sqsEvents.template +++ b/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/ServerlessTemplates/sqsEvents.template @@ -51,7 +51,6 @@ "queue1": { "Type": "SQS", "Properties": { - "Queue": "arn:aws:sqs:us-east-2:444455556666:queue1", "BatchSize": 50, "FilterCriteria": { "Filters": [ @@ -66,15 +65,16 @@ "MaximumBatchingWindowInSeconds": 2, "ScalingConfig": { "MaximumConcurrency": 30 - } + }, + "Queue": "arn:aws:sqs:us-east-2:444455556666:queue1" } }, "queue2": { "Type": "SQS", "Properties": { - "Queue": "arn:aws:sqs:us-east-2:444455556666:queue2", "Enabled": false, - "MaximumBatchingWindowInSeconds": 5 + "MaximumBatchingWindowInSeconds": 5, + "Queue": "arn:aws:sqs:us-east-2:444455556666:queue2" } }, "myqueue": { @@ -164,14 +164,14 @@ "queue3": { "Type": "SQS", "Properties": { - "Queue": "arn:aws:sqs:us-east-2:444455556666:queue3", "FunctionResponseTypes": [ "ReportBatchItemFailures" - ] + ], + "Queue": "arn:aws:sqs:us-east-2:444455556666:queue3" } } } } } } -} \ No newline at end of file +} diff --git a/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/SourceGeneratorTests.cs b/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/SourceGeneratorTests.cs index a377eb5a7..49d17a9da 100644 --- a/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/SourceGeneratorTests.cs +++ b/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/SourceGeneratorTests.cs @@ -267,7 +267,7 @@ public async Task TestInvalidGlobalRuntime_ShouldError() }, ExpectedDiagnostics = { - new DiagnosticResult("AWSLambda0112", DiagnosticSeverity.Error).WithMessage("The runtime selected in the Amazon.Lambda.Annotations.LambdaGlobalPropertiesAttribute is not a supported value. The valid values are: dotnet6, provided.al2, provided.al2023, dotnet8, dotnet10"), + new DiagnosticResult("AWSLambda0112", DiagnosticSeverity.Error).WithMessage("The runtime selected in the Amazon.Lambda.Annotations.LambdaGlobalPropertiesAttribute is not a supported value. The valid values are: provided.al2, provided.al2023, dotnet8, dotnet10"), } } }; @@ -279,6 +279,8 @@ public async Task TestInvalidGlobalRuntime_ShouldError() test.TestState.Sources.Add((file, await File.ReadAllTextAsync(file))); } + test.TestState.ExpectedDiagnostics.AddRange(GetExpectedRuntimeSupportDiagnostics()); + await test.RunAsync(); } @@ -375,6 +377,8 @@ public async Task VerifyExecutableAssemblyWithZipAndHandler() test.TestState.Sources.Add((file, await File.ReadAllTextAsync(file))); } + test.TestState.ExpectedDiagnostics.AddRange(GetExpectedRuntimeSupportDiagnostics()); + await test.RunAsync(); } @@ -434,6 +438,8 @@ public async Task VerifyExecutableAssembly() test.TestState.Sources.Add((file, await File.ReadAllTextAsync(file))); } + test.TestState.ExpectedDiagnostics.AddRange(GetExpectedRuntimeSupportDiagnostics()); + await test.RunAsync(); } @@ -490,6 +496,8 @@ public async Task VerifyExecutableAssemblyWithParameterlessConstructor() test.TestState.Sources.Add((file, await File.ReadAllTextAsync(file))); } + test.TestState.ExpectedDiagnostics.AddRange(GetExpectedRuntimeSupportDiagnostics()); + await test.RunAsync(); } @@ -546,6 +554,8 @@ public async Task VerifyExecutableAssemblyWithParameterlessConstructorAndRespons test.TestState.Sources.Add((file, await File.ReadAllTextAsync(file))); } + test.TestState.ExpectedDiagnostics.AddRange(GetExpectedRuntimeSupportDiagnostics()); + await test.RunAsync(); } @@ -649,6 +659,8 @@ public async Task VerifyExecutableAssemblyWithMultipleHandler() test.TestState.Sources.Add((file, await File.ReadAllTextAsync(file))); } + test.TestState.ExpectedDiagnostics.AddRange(GetExpectedRuntimeSupportDiagnostics()); + await test.RunAsync(); } @@ -712,6 +724,8 @@ public async Task VerifySourceGeneratorSerializerWithHttpResultsBody() test.TestState.Sources.Add((file, content)); } + test.TestState.ExpectedDiagnostics.AddRange(GetExpectedRuntimeSupportDiagnostics()); + await test.RunAsync(); } @@ -947,9 +961,6 @@ public async Task CustomizeResponses() new DiagnosticResult("AWSLambda0103", DiagnosticSeverity.Info).WithArguments("CustomizeResponseExamples_NotFoundResponseWithHeaderV1Async_Generated.g.cs", expectedNotFoundResponseWithHeaderV1AsyncGenerated), new DiagnosticResult("AWSLambda0103", DiagnosticSeverity.Info).WithArguments("CustomizeResponseExamples_OkResponseWithCustomSerializer_Generated.g.cs", expectedOkResponseWithCustomSerializerGenerated), - // The test framework doesn't appear to also execute the System.Text.Json source generator so Annotations generated code relying on the generated System.Text.Json code does not exist - // so we get compile errors. In an real world scenario they are both run and the applicaton compiles correctly. - DiagnosticResult.CompilerError("CS0117").WithSpan($"TestServerlessApp{Path.DirectorySeparatorChar}CustomizeResponseExamples.cs", 99, 65, 99, 79).WithArguments("System.Text.Json.JsonNamingPolicy", "SnakeCaseUpper"), new DiagnosticResult("AWSLambda0103", DiagnosticSeverity.Info).WithArguments($"TestServerlessApp{Path.DirectorySeparatorChar}serverless.template", expectedTemplateContent) } @@ -1181,7 +1192,7 @@ public async Task ToUpper_Net8() var expectedFunctionContent = await ReadSnapshotContent(Path.Combine("Snapshots", "Functions_ToUpper_Generated_NET8.g.cs")); var expectedTemplateContent = await ReadSnapshotContent(Path.Combine("Snapshots", "ServerlessTemplates", "net8.template")); - await new VerifyCS.Test(targetFramework: VerifyCS.Test.TargetFramework.Net80) + await new VerifyCS.Test(targetFramework: VerifyCS.Test.TargetFramework.Net8_0) { TestState = { @@ -1200,7 +1211,7 @@ public async Task ToUpper_Net8() ExpectedDiagnostics = { new DiagnosticResult("AWSLambda0103", DiagnosticSeverity.Info).WithArguments("Functions_ToUpper_Generated.g.cs", expectedFunctionContent), - new DiagnosticResult("AWSLambda0103", DiagnosticSeverity.Info).WithArguments($"TestServerlessApp.NET8{Path.DirectorySeparatorChar}serverless.template", expectedTemplateContent) + new DiagnosticResult("AWSLambda0103", DiagnosticSeverity.Info).WithArguments($"TestServerlessApp.NET8{Path.DirectorySeparatorChar}serverless.template", expectedTemplateContent), } } }.RunAsync(); @@ -1327,16 +1338,16 @@ public async Task VerifyValidSQSEvents() ExpectedDiagnostics = { new DiagnosticResult("AWSLambda0103", DiagnosticSeverity.Info) - .WithArguments("ValidSQSEvents_ProcessMessages_Generated.g.cs", validSqsEventsProcessMessagesGeneratedContent), + .WithArguments($"TestServerlessApp{Path.DirectorySeparatorChar}serverless.template", expectedTemplateContent), new DiagnosticResult("AWSLambda0103", DiagnosticSeverity.Info) - .WithArguments("ValidSQSEvents_ProcessMessagesWithReservedParameterName_Generated.g.cs", validSqsEventsProcessMessagesWithReservedParameterNameGeneratedContent), + .WithArguments("ValidSQSEvents_ProcessMessagesWithBatchFailureReporting_Generated.g.cs", validSqsEventsProcessMessagesWithBatchFailureReportingGeneratedContent), new DiagnosticResult("AWSLambda0103", DiagnosticSeverity.Info) - .WithArguments("ValidSQSEvents_ProcessMessagesWithBatchFailureReporting_Generated.g.cs", validSqsEventsProcessMessagesWithBatchFailureReportingGeneratedContent), + .WithArguments("ValidSQSEvents_ProcessMessagesWithReservedParameterName_Generated.g.cs", validSqsEventsProcessMessagesWithReservedParameterNameGeneratedContent), new DiagnosticResult("AWSLambda0103", DiagnosticSeverity.Info) - .WithArguments($"TestServerlessApp{Path.DirectorySeparatorChar}serverless.template", expectedTemplateContent) + .WithArguments("ValidSQSEvents_ProcessMessages_Generated.g.cs", validSqsEventsProcessMessagesGeneratedContent) } } }.RunAsync(); @@ -1377,13 +1388,13 @@ public async Task VerifyValidALBEvents() ExpectedDiagnostics = { new DiagnosticResult("AWSLambda0103", DiagnosticSeverity.Info) - .WithArguments("ValidALBEvents_Hello_Generated.g.cs", validALBEventsHelloGeneratedContent), + .WithArguments($"TestServerlessApp{Path.DirectorySeparatorChar}serverless.template", expectedTemplateContent), new DiagnosticResult("AWSLambda0103", DiagnosticSeverity.Info) .WithArguments("ValidALBEvents_HandleRequest_Generated.g.cs", validALBEventsHandleRequestGeneratedContent), new DiagnosticResult("AWSLambda0103", DiagnosticSeverity.Info) - .WithArguments($"TestServerlessApp{Path.DirectorySeparatorChar}serverless.template", expectedTemplateContent), + .WithArguments("ValidALBEvents_Hello_Generated.g.cs", validALBEventsHelloGeneratedContent), new DiagnosticResult("AWSLambda0133", DiagnosticSeverity.Error) } @@ -1492,13 +1503,13 @@ public async Task VerifyValidDynamoDBEvents() ExpectedDiagnostics = { new DiagnosticResult("AWSLambda0103", DiagnosticSeverity.Info) - .WithArguments("ValidDynamoDBEvents_ProcessMessages_Generated.g.cs", validDynamoDBEventsProcessMessagesGeneratedContent), + .WithArguments($"TestServerlessApp{Path.DirectorySeparatorChar}serverless.template", expectedTemplateContent), new DiagnosticResult("AWSLambda0103", DiagnosticSeverity.Info) .WithArguments("ValidDynamoDBEvents_ProcessMessagesAsync_Generated.g.cs", validDynamoDBEventsProcessMessagesAsyncGeneratedContent), new DiagnosticResult("AWSLambda0103", DiagnosticSeverity.Info) - .WithArguments($"TestServerlessApp{Path.DirectorySeparatorChar}serverless.template", expectedTemplateContent) + .WithArguments("ValidDynamoDBEvents_ProcessMessages_Generated.g.cs", validDynamoDBEventsProcessMessagesGeneratedContent) } } }.RunAsync(); @@ -1593,13 +1604,13 @@ public async Task VerifyValidSNSEvents() ExpectedDiagnostics = { new DiagnosticResult("AWSLambda0103", DiagnosticSeverity.Info) - .WithArguments("ValidSNSEvents_ProcessMessages_Generated.g.cs", validSNSEventsProcessMessagesGeneratedContent), + .WithArguments($"TestServerlessApp{Path.DirectorySeparatorChar}serverless.template", expectedTemplateContent), new DiagnosticResult("AWSLambda0103", DiagnosticSeverity.Info) .WithArguments("ValidSNSEvents_ProcessMessagesAsync_Generated.g.cs", validSNSEventsProcessMessagesAsyncGeneratedContent), new DiagnosticResult("AWSLambda0103", DiagnosticSeverity.Info) - .WithArguments($"TestServerlessApp{Path.DirectorySeparatorChar}serverless.template", expectedTemplateContent) + .WithArguments("ValidSNSEvents_ProcessMessages_Generated.g.cs", validSNSEventsProcessMessagesGeneratedContent) } } }.RunAsync(); @@ -1697,7 +1708,6 @@ public async Task CustomAuthorizerRestTest() new DiagnosticResult("AWSLambda0103", DiagnosticSeverity.Info).WithArguments("CustomAuthorizerRestExample_RestAuthorizer_Generated.g.cs", expectedRestAuthorizerGenerated), new DiagnosticResult("AWSLambda0103", DiagnosticSeverity.Info).WithArguments($"TestServerlessApp{Path.DirectorySeparatorChar}serverless.template", expectedTemplateContent) }, - ReferenceAssemblies = ReferenceAssemblies.Net.Net60 } }.RunAsync(); @@ -1736,7 +1746,6 @@ public async Task CustomAuthorizerHttpApiTest() new DiagnosticResult("AWSLambda0103", DiagnosticSeverity.Info).WithArguments("CustomAuthorizerHttpApiExample_HttpApiAuthorizer_Generated.g.cs", expectedRestAuthorizerGenerated), new DiagnosticResult("AWSLambda0103", DiagnosticSeverity.Info).WithArguments($"TestServerlessApp{Path.DirectorySeparatorChar}serverless.template", expectedTemplateContent) }, - ReferenceAssemblies = ReferenceAssemblies.Net.Net60 } }.RunAsync(); @@ -1780,7 +1789,6 @@ public async Task CustomAuthorizerAttributeParameterNameFallbackTest() new DiagnosticResult("AWSLambda0103", DiagnosticSeverity.Info).WithArguments("AuthNameFallback_GetUserId_Generated.g.cs", expectedAuthorizerGenerated), new DiagnosticResult("AWSLambda0103", DiagnosticSeverity.Info).WithArguments($"TestServerlessApp{Path.DirectorySeparatorChar}serverless.template", expectedTemplateContent) }, - ReferenceAssemblies = ReferenceAssemblies.Net.Net60 } }.RunAsync(); @@ -1819,7 +1827,6 @@ public async Task CustomAuthorizerHttpApiV1Test() new DiagnosticResult("AWSLambda0103", DiagnosticSeverity.Info).WithArguments("CustomAuthorizerHttpApiV1Example_HttpApiV1Authorizer_Generated.g.cs", expectedHttpApiV1AuthorizerGenerated), new DiagnosticResult("AWSLambda0103", DiagnosticSeverity.Info).WithArguments($"TestServerlessApp{Path.DirectorySeparatorChar}serverless.template", expectedTemplateContent) }, - ReferenceAssemblies = ReferenceAssemblies.Net.Net60 } }.RunAsync(); @@ -1858,7 +1865,6 @@ public async Task CustomAuthorizerWithIHttpResultsTest() new DiagnosticResult("AWSLambda0103", DiagnosticSeverity.Info).WithArguments("CustomAuthorizerWithIHttpResultsExample_AuthorizerWithIHttpResults_Generated.g.cs", expectedAuthorizerGenerated), new DiagnosticResult("AWSLambda0103", DiagnosticSeverity.Info).WithArguments($"TestServerlessApp{Path.DirectorySeparatorChar}serverless.template", expectedTemplateContent) }, - ReferenceAssemblies = ReferenceAssemblies.Net.Net60 } }.RunAsync(); @@ -1902,7 +1908,6 @@ public async Task CustomAuthorizerNonStringTest() new DiagnosticResult("AWSLambda0103", DiagnosticSeverity.Info).WithArguments("CustomAuthorizerNonStringExample_HttpApiWithNonString_Generated.g.cs", expectedAuthorizerGenerated), new DiagnosticResult("AWSLambda0103", DiagnosticSeverity.Info).WithArguments($"TestServerlessApp{Path.DirectorySeparatorChar}serverless.template", expectedTemplateContent) }, - ReferenceAssemblies = ReferenceAssemblies.Net.Net60 } }.RunAsync(); @@ -2042,7 +2047,6 @@ public async Task CustomAuthorizerAppAuthorizerDefinitionsTest() new DiagnosticResult("AWSLambda0103", DiagnosticSeverity.Info).WithArguments("ProtectedFunction_HealthCheck_Generated.g.cs", expectedHealthCheckGenerated), new DiagnosticResult("AWSLambda0103", DiagnosticSeverity.Info).WithArguments($"TestCustomAuthorizerApp{Path.DirectorySeparatorChar}serverless.template", expectedTemplateContent), }, - ReferenceAssemblies = ReferenceAssemblies.Net.Net60 } }.RunAsync(); @@ -2126,7 +2130,6 @@ public async Task IAuthorizerResultHttpApiTest() new DiagnosticResult("AWSLambda0103", DiagnosticSeverity.Info).WithArguments("IAuthorizerResultExample_SimpleRestApiAuthorizer_Generated.g.cs", expectedRestApiGenerated), new DiagnosticResult("AWSLambda0103", DiagnosticSeverity.Info).WithArguments($"TestServerlessApp{Path.DirectorySeparatorChar}serverless.template", expectedTemplateContent), }, - ReferenceAssemblies = ReferenceAssemblies.Net.Net60 } }.RunAsync(); @@ -2189,14 +2192,50 @@ private async static Task ReadSnapshotContent(string snapshotPath, bool return content.ToEnvironmentLineEndings().ApplyReplacements(); } - private static string InvalidAssemblyAttributeString = "using Amazon.Lambda.Annotations;" + + private readonly static string InvalidAssemblyAttributeString = "using Amazon.Lambda.Annotations;" + "using Amazon.Lambda.Core;" + "[assembly: LambdaSerializer(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))]" + "[assembly: LambdaGlobalProperties(GenerateMain = true, Runtime = \"notavalidruntime\")]"; - private static string NullAssemblyAttributeString = "using Amazon.Lambda.Annotations;" + + private readonly static string NullAssemblyAttributeString = "using Amazon.Lambda.Annotations;" + "using Amazon.Lambda.Core;" + "[assembly: LambdaSerializer(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))]" + "[assembly: LambdaGlobalProperties(Runtime = null)]"; + + /// + /// Returns expected compiler diagnostics for RuntimeSupport source files included in test compilations. + /// In .NET 10, JsonSerializerContext has new abstract members and a required constructor parameter that + /// the System.Text.Json source generator would normally implement. Since only the Lambda Annotations + /// source generator runs in tests, these produce expected compiler errors. + /// + private static DiagnosticResult[] GetExpectedRuntimeSupportDiagnostics() + { + var runtimeApiContext = "Amazon.Lambda.RuntimeSupport.InternalRuntimeApiClient.RuntimeApiSerializationContext"; + var clientFile = $"Amazon.Lambda.RuntimeSupport{Path.DirectorySeparatorChar}Client{Path.DirectorySeparatorChar}InternalClientAdapted.cs"; + var snapFile = $"Amazon.Lambda.RuntimeSupport{Path.DirectorySeparatorChar}Helpers{Path.DirectorySeparatorChar}SnapstartHelperCopySnapshotCallbacksIsolated.cs"; + + return new[] + { + // These are here because the System.Text.Json source generator isn't included in test compilations, so these members aren't generated. + DiagnosticResult.CompilerError("CS0534").WithSpan(clientFile, 85, 30, 85, 60).WithArguments(runtimeApiContext, "System.Text.Json.Serialization.JsonSerializerContext.GeneratedSerializerOptions.get"), + DiagnosticResult.CompilerError("CS0534").WithSpan(clientFile, 85, 30, 85, 60).WithArguments(runtimeApiContext, "System.Text.Json.Serialization.JsonSerializerContext.GetTypeInfo(System.Type)"), + DiagnosticResult.CompilerError("CS7036").WithSpan(clientFile, 85, 30, 85, 60).WithArguments("options", "System.Text.Json.Serialization.JsonSerializerContext.JsonSerializerContext(System.Text.Json.JsonSerializerOptions?)"), + DiagnosticResult.CompilerError("CS0117").WithSpan(clientFile, 157, 136, 157, 143).WithArguments(runtimeApiContext, "Default"), + DiagnosticResult.CompilerError("CS0117").WithSpan(clientFile, 172, 135, 172, 142).WithArguments(runtimeApiContext, "Default"), + DiagnosticResult.CompilerError("CS0117").WithSpan(clientFile, 275, 131, 275, 138).WithArguments(runtimeApiContext, "Default"), + DiagnosticResult.CompilerError("CS0117").WithSpan(clientFile, 371, 135, 371, 142).WithArguments(runtimeApiContext, "Default"), + DiagnosticResult.CompilerError("CS0117").WithSpan(clientFile, 386, 135, 386, 142).WithArguments(runtimeApiContext, "Default"), + DiagnosticResult.CompilerError("CS0117").WithSpan(clientFile, 401, 135, 401, 142).WithArguments(runtimeApiContext, "Default"), + DiagnosticResult.CompilerError("CS0117").WithSpan(clientFile, 510, 136, 510, 143).WithArguments(runtimeApiContext, "Default"), + DiagnosticResult.CompilerError("CS0117").WithSpan(clientFile, 525, 135, 525, 142).WithArguments(runtimeApiContext, "Default"), + DiagnosticResult.CompilerError("CS0117").WithSpan(clientFile, 540, 135, 540, 142).WithArguments(runtimeApiContext, "Default"), + + + // These are here because the internalvisibleto attribute isn't included in test compilations, so these types are inaccessible. + DiagnosticResult.CompilerError("CS0117").WithSpan(snapFile, 13, 34, 13, 71).WithArguments("Amazon.Lambda.Core.SnapshotRestore", "CopyBeforeSnapshotCallbacksToRegistry"), + DiagnosticResult.CompilerError("CS0117").WithSpan(snapFile, 14, 34, 14, 69).WithArguments("Amazon.Lambda.Core.SnapshotRestore", "CopyAfterRestoreCallbacksToRegistry"), + DiagnosticResult.CompilerError("CS0122").WithSpan($"Amazon.Lambda.RuntimeSupport{Path.DirectorySeparatorChar}Bootstrap{Path.DirectorySeparatorChar}ResponseStreaming{Path.DirectorySeparatorChar}ResponseStreamLambdaCoreInitializerIsolated.cs", 37, 51, 37, 72).WithArguments("Amazon.Lambda.Core.ResponseStreaming.ILambdaResponseStream"), + }; + } } } diff --git a/Libraries/test/Amazon.Lambda.AspNetCoreServer.Hosting.Tests/Amazon.Lambda.AspNetCoreServer.Hosting.Tests.csproj b/Libraries/test/Amazon.Lambda.AspNetCoreServer.Hosting.Tests/Amazon.Lambda.AspNetCoreServer.Hosting.Tests.csproj index 276bdd5c7..705fdabd5 100644 --- a/Libraries/test/Amazon.Lambda.AspNetCoreServer.Hosting.Tests/Amazon.Lambda.AspNetCoreServer.Hosting.Tests.csproj +++ b/Libraries/test/Amazon.Lambda.AspNetCoreServer.Hosting.Tests/Amazon.Lambda.AspNetCoreServer.Hosting.Tests.csproj @@ -3,20 +3,23 @@ - net8.0 + net8.0;net10.0 enable enable true false false false - 1701;1702;1705;CS0618 + 1701;1702;1705;CS0618;CS1591 - - - + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/Libraries/test/Amazon.Lambda.AspNetCoreServer.Hosting.Tests/HostingOptionsTests.cs b/Libraries/test/Amazon.Lambda.AspNetCoreServer.Hosting.Tests/HostingOptionsTests.cs index 580095a2e..9214e9194 100644 --- a/Libraries/test/Amazon.Lambda.AspNetCoreServer.Hosting.Tests/HostingOptionsTests.cs +++ b/Libraries/test/Amazon.Lambda.AspNetCoreServer.Hosting.Tests/HostingOptionsTests.cs @@ -68,7 +68,7 @@ public void RegisterResponseContentEncodingForContentType_NullContentType_Ignore var options = new HostingOptions(); // Act - options.RegisterResponseContentEncodingForContentType(null, ResponseContentEncoding.Base64); + options.RegisterResponseContentEncodingForContentType(null!, ResponseContentEncoding.Base64); // Assert Assert.Empty(options.ContentTypeEncodings); @@ -144,7 +144,7 @@ public void RegisterResponseContentEncodingForContentEncoding_NullContentEncodin var options = new HostingOptions(); // Act - options.RegisterResponseContentEncodingForContentEncoding(null, ResponseContentEncoding.Base64); + options.RegisterResponseContentEncodingForContentEncoding(null!, ResponseContentEncoding.Base64); // Assert Assert.Empty(options.ContentEncodingEncodings); diff --git a/Libraries/test/Amazon.Lambda.AspNetCoreServer.Test/Amazon.Lambda.AspNetCoreServer.Test.csproj b/Libraries/test/Amazon.Lambda.AspNetCoreServer.Test/Amazon.Lambda.AspNetCoreServer.Test.csproj index 6d2a4d012..30a0c672e 100644 --- a/Libraries/test/Amazon.Lambda.AspNetCoreServer.Test/Amazon.Lambda.AspNetCoreServer.Test.csproj +++ b/Libraries/test/Amazon.Lambda.AspNetCoreServer.Test/Amazon.Lambda.AspNetCoreServer.Test.csproj @@ -11,7 +11,7 @@ false false false - 1701;1702;1705;CS0618 + 1701;1702;1705;CS0618;CS1591 @@ -49,9 +49,12 @@ - - - + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/Libraries/test/Amazon.Lambda.AspNetCoreServer.Test/ResponseStreamingPropertyTests.cs b/Libraries/test/Amazon.Lambda.AspNetCoreServer.Test/ResponseStreamingPropertyTests.cs index d10149f07..05c6bed87 100644 --- a/Libraries/test/Amazon.Lambda.AspNetCoreServer.Test/ResponseStreamingPropertyTests.cs +++ b/Libraries/test/Amazon.Lambda.AspNetCoreServer.Test/ResponseStreamingPropertyTests.cs @@ -107,17 +107,17 @@ public static IEnumerable RequestMarshallingCases() => [Theory] [MemberData(nameof(RequestMarshallingCases))] - public void Property1_RequestMarshalling_IdenticalInStreamingAndBufferedModes( + public async Task Property1_RequestMarshalling_IdenticalInStreamingAndBufferedModes( string method, string path, Dictionary headers, string body) { var function = new PropertyTestStreamingFunction(); var context = new TestLambdaContext(); // Warm up so the host is started - function.FunctionHandlerAsync(MakeRequest(), context).GetAwaiter().GetResult(); + await function.FunctionHandlerAsync(MakeRequest(), context); var request = MakeRequest(method, path, headers, body); - function.FunctionHandlerAsync(request, context).GetAwaiter().GetResult(); + await function.FunctionHandlerAsync(request, context); var streamingReq = (IHttpRequestFeature)function.CapturedFeatures; var bufferedFeatures = new InvokeFeatures(); @@ -151,7 +151,7 @@ public static IEnumerable BufferedModeCases() => [Theory] [MemberData(nameof(BufferedModeCases))] - public void Property2_BufferedMode_Unaffected( + public async Task Property2_BufferedMode_Unaffected( string method, string path, Dictionary headers, string body) { // Use a fresh function with streaming OFF @@ -159,8 +159,7 @@ public void Property2_BufferedMode_Unaffected( function.EnableResponseStreaming = false; var context = new TestLambdaContext(); - var response = function.FunctionHandlerAsync(MakeRequest(method, path, headers, body), context) - .GetAwaiter().GetResult(); + var response = await function.FunctionHandlerAsync(MakeRequest(method, path, headers, body), context); Assert.NotNull(response); Assert.True(function.MarshallResponseCalled, "MarshallResponse must be called in buffered mode"); @@ -355,7 +354,7 @@ public async Task Property7_SendFileAsync_WritesCorrectByteRange( [InlineData(2)] [InlineData(3)] [InlineData(5)] - public void Property8_OnCompletedCallbacks_FireAfterStreamClose(int cbCount) + public async Task Property8_OnCompletedCallbacks_FireAfterStreamClose(int cbCount) { int sequenceCounter = 0; var completedSequences = new List(); @@ -370,7 +369,7 @@ public void Property8_OnCompletedCallbacks_FireAfterStreamClose(int cbCount) var context = new TestLambdaContext(); var request = MakeRequest(); - function.FunctionHandlerAsync(request, context).GetAwaiter().GetResult(); + await function.FunctionHandlerAsync(request, context); Assert.Equal(cbCount, completedSequences.Count); Assert.True(streamClosedSequence >= 0, "Stream was never closed"); diff --git a/Libraries/test/Amazon.Lambda.AspNetCoreServer.Test/StreamingFunctionHandlerAsyncTests.cs b/Libraries/test/Amazon.Lambda.AspNetCoreServer.Test/StreamingFunctionHandlerAsyncTests.cs index b006ec11f..6ba2a6291 100644 --- a/Libraries/test/Amazon.Lambda.AspNetCoreServer.Test/StreamingFunctionHandlerAsyncTests.cs +++ b/Libraries/test/Amazon.Lambda.AspNetCoreServer.Test/StreamingFunctionHandlerAsyncTests.cs @@ -570,7 +570,7 @@ async Task OpenStream() var streamingBodyFeature = new StreamingResponseBodyFeature(_logger, responseFeature, OpenStream); features[typeof(IHttpResponseBodyFeature)] = streamingBodyFeature; - var scope = this._hostServices.CreateScope(); + var scope = _hostServices.CreateScope(); Exception pipelineException = null; try { diff --git a/Libraries/test/Amazon.Lambda.AspNetCoreServer.Test/TestApiGatewayHttpApiV2Calls.cs b/Libraries/test/Amazon.Lambda.AspNetCoreServer.Test/TestApiGatewayHttpApiV2Calls.cs index e6a80d38c..b28f82cc2 100644 --- a/Libraries/test/Amazon.Lambda.AspNetCoreServer.Test/TestApiGatewayHttpApiV2Calls.cs +++ b/Libraries/test/Amazon.Lambda.AspNetCoreServer.Test/TestApiGatewayHttpApiV2Calls.cs @@ -29,7 +29,7 @@ public async Task TestValuesGetAllFromBetaStage() { var context = new TestLambdaContext(); - var response = await this.InvokeAPIGatewayRequest(context, "values-get-all-httpapi-v2-with-stage.json"); + var response = await InvokeAPIGatewayRequest(context, "values-get-all-httpapi-v2-with-stage.json"); Assert.Equal(200, response.StatusCode); Assert.Equal("[\"value1\",\"value2\"]", response.Body); @@ -42,7 +42,7 @@ public async Task TestValuesGetAllFromBetaStage() [Fact] public async Task TestGetBinaryContent() { - var response = await this.InvokeAPIGatewayRequest("values-get-binary-httpapi-v2-request.json"); + var response = await InvokeAPIGatewayRequest("values-get-binary-httpapi-v2-request.json"); Assert.Equal((int)HttpStatusCode.OK, response.StatusCode); @@ -65,7 +65,7 @@ public async Task TestGetBinaryContent() [Fact] public async Task TestEncodePlusInResourcePath() { - var response = await this.InvokeAPIGatewayRequest("encode-plus-in-resource-path-httpapi-v2.json"); + var response = await InvokeAPIGatewayRequest("encode-plus-in-resource-path-httpapi-v2.json"); Assert.Equal(200, response.StatusCode); @@ -76,7 +76,7 @@ public async Task TestEncodePlusInResourcePath() [Fact] public async Task TestGetQueryStringValueMV() { - var response = await this.InvokeAPIGatewayRequest("values-get-querystring-httpapi-v2-mv-request.json"); + var response = await InvokeAPIGatewayRequest("values-get-querystring-httpapi-v2-mv-request.json"); Assert.Equal("value1,value2", response.Body); Assert.True(response.Headers.ContainsKey("Content-Type")); @@ -86,7 +86,7 @@ public async Task TestGetQueryStringValueMV() [Fact] public async Task TestGetEncodingQueryStringGateway() { - var response = await this.InvokeAPIGatewayRequest("values-get-querystring-httpapi-v2-encoding-request.json"); + var response = await InvokeAPIGatewayRequest("values-get-querystring-httpapi-v2-encoding-request.json"); var results = JsonConvert.DeserializeObject(response.Body); Assert.Equal("http://www.google.com", results.Url); Assert.Equal(DateTimeOffset.Parse("2019-03-12T16:06:06.549817+00:00"), results.TestDateTimeOffset); @@ -98,7 +98,7 @@ public async Task TestGetEncodingQueryStringGateway() [Fact] public async Task TestPutWithBody() { - var response = await this.InvokeAPIGatewayRequest("values-put-withbody-httpapi-v2-request.json"); + var response = await InvokeAPIGatewayRequest("values-put-withbody-httpapi-v2-request.json"); Assert.Equal(200, response.StatusCode); Assert.Equal("Agent, Smith", response.Body); @@ -109,7 +109,7 @@ public async Task TestPutWithBody() [Fact] public async Task TestDefaultResponseErrorCode() { - var response = await this.InvokeAPIGatewayRequest("values-get-error-httpapi-v2-request.json"); + var response = await InvokeAPIGatewayRequest("values-get-error-httpapi-v2-request.json"); Assert.Equal(500, response.StatusCode); Assert.Equal(string.Empty, response.Body); @@ -122,7 +122,7 @@ public async Task TestDefaultResponseErrorCode() [InlineData("values-get-typeloaderror-httpapi-v2-request.json", "ReflectionTypeLoadException", false)] public async Task TestEnhancedExceptions(string requestFileName, string expectedExceptionType, bool configureApiToReturnExceptionDetail) { - var response = await this.InvokeAPIGatewayRequest(requestFileName, configureApiToReturnExceptionDetail); + var response = await InvokeAPIGatewayRequest(requestFileName, configureApiToReturnExceptionDetail); Assert.Equal(500, response.StatusCode); Assert.Equal(string.Empty, response.Body); @@ -140,7 +140,7 @@ public async Task TestEnhancedExceptions(string requestFileName, string expected [Fact] public async Task TestGettingSwaggerDefinition() { - var response = await this.InvokeAPIGatewayRequest("swagger-get-httpapi-v2-request.json"); + var response = await InvokeAPIGatewayRequest("swagger-get-httpapi-v2-request.json"); Assert.Equal(200, response.StatusCode); Assert.True(response.Body.Length > 0); @@ -150,7 +150,7 @@ public async Task TestGettingSwaggerDefinition() [Fact] public async Task TestEncodeSpaceInResourcePath() { - var response = await this.InvokeAPIGatewayRequest("encode-space-in-resource-path-httpapi-v2.json"); + var response = await InvokeAPIGatewayRequest("encode-space-in-resource-path-httpapi-v2.json"); Assert.Equal(200, response.StatusCode); Assert.Equal("value=tmh/file name.xml", response.Body); @@ -161,12 +161,12 @@ public async Task TestEncodeSpaceInResourcePath() public async Task TestEncodeSlashInResourcePath() { var requestStr = GetRequestContent("encode-slash-in-resource-path-httpapi-v2.json"); - var response = await this.InvokeAPIGatewayRequestWithContent(new TestLambdaContext(), requestStr); + var response = await InvokeAPIGatewayRequestWithContent(new TestLambdaContext(), requestStr); Assert.Equal(200, response.StatusCode); Assert.Equal("{\"only\":\"a%2Fb\"}", response.Body); - response = await this.InvokeAPIGatewayRequestWithContent(new TestLambdaContext(), requestStr.Replace("a%2Fb", "a/b")); + response = await InvokeAPIGatewayRequestWithContent(new TestLambdaContext(), requestStr.Replace("a%2Fb", "a/b")); Assert.Equal(200, response.StatusCode); Assert.Equal("{\"first\":\"a\",\"second\":\"b\"}", response.Body); @@ -175,7 +175,7 @@ public async Task TestEncodeSlashInResourcePath() [Fact] public async Task TestTrailingSlashInPath() { - var response = await this.InvokeAPIGatewayRequest("trailing-slash-in-path-httpapi-v2.json"); + var response = await InvokeAPIGatewayRequest("trailing-slash-in-path-httpapi-v2.json"); Assert.Equal(200, response.StatusCode); @@ -191,7 +191,7 @@ public async Task TestTrailingSlashInPath() [InlineData("rawtarget-escaped-slash-in-path-httpapi-v2.json", "/foo%2Fbar")] public async Task TestRawTarget(string requestFileName, string expectedRawTarget) { - var response = await this.InvokeAPIGatewayRequest(requestFileName); + var response = await InvokeAPIGatewayRequest(requestFileName); Assert.Equal(200, response.StatusCode); @@ -202,7 +202,7 @@ public async Task TestRawTarget(string requestFileName, string expectedRawTarget [Fact] public async Task TestAuthTestAccess() { - var response = await this.InvokeAPIGatewayRequest("authtest-access-request-httpapi-v2.json"); + var response = await InvokeAPIGatewayRequest("authtest-access-request-httpapi-v2.json"); Assert.Equal(200, response.StatusCode); Assert.Equal("You Have Access", response.Body); @@ -211,7 +211,7 @@ public async Task TestAuthTestAccess() [Fact] public async Task TestAuthTestNoAccess() { - var response = await this.InvokeAPIGatewayRequest("authtest-noaccess-request-httpapi-v2.json"); + var response = await InvokeAPIGatewayRequest("authtest-noaccess-request-httpapi-v2.json"); Assert.NotEqual(200, response.StatusCode); } @@ -219,7 +219,7 @@ public async Task TestAuthTestNoAccess() [Fact] public async Task TestAuthMTls() { - var response = await this.InvokeAPIGatewayRequest("mtls-request-httpapi-v2.json"); + var response = await InvokeAPIGatewayRequest("mtls-request-httpapi-v2.json"); Assert.Equal(200, response.StatusCode); Assert.Equal("O=Internet Widgits Pty Ltd, S=Some-State, C=AU", response.Body); } @@ -227,7 +227,7 @@ public async Task TestAuthMTls() [Fact] public async Task TestReturningCookie() { - var response = await this.InvokeAPIGatewayRequest("cookies-get-returned-httpapi-v2-request.json"); + var response = await InvokeAPIGatewayRequest("cookies-get-returned-httpapi-v2-request.json"); Assert.Collection(response.Cookies, actual => Assert.StartsWith("TestCookie=TestValue", actual)); @@ -236,7 +236,7 @@ public async Task TestReturningCookie() [Fact] public async Task TestReturningMultipleCookies() { - var response = await this.InvokeAPIGatewayRequest("cookies-get-multiple-returned-httpapi-v2-request.json"); + var response = await InvokeAPIGatewayRequest("cookies-get-multiple-returned-httpapi-v2-request.json"); Assert.Collection(response.Cookies.OrderBy(s => s), actual => Assert.StartsWith("TestCookie1=TestValue1", actual), @@ -246,7 +246,7 @@ public async Task TestReturningMultipleCookies() [Fact] public async Task TestSingleCookie() { - var response = await this.InvokeAPIGatewayRequest("cookies-get-single-httpapi-v2-request.json"); + var response = await InvokeAPIGatewayRequest("cookies-get-single-httpapi-v2-request.json"); Assert.Equal("TestValue", response.Body); } @@ -254,7 +254,7 @@ public async Task TestSingleCookie() [Fact] public async Task TestMultipleCookie() { - var response = await this.InvokeAPIGatewayRequest("cookies-get-multiple-httpapi-v2-request.json"); + var response = await InvokeAPIGatewayRequest("cookies-get-multiple-httpapi-v2-request.json"); Assert.Equal("TestValue3", response.Body); } @@ -265,15 +265,15 @@ public async Task TestTraceIdSetFromLambdaContext() try { Environment.SetEnvironmentVariable("_X_AMZN_TRACE_ID", "MyTraceId-1"); - var response = await this.InvokeAPIGatewayRequest("traceid-get-httpapi-v2-request.json"); + var response = await InvokeAPIGatewayRequest("traceid-get-httpapi-v2-request.json"); Assert.Equal("MyTraceId-1", response.Body); Environment.SetEnvironmentVariable("_X_AMZN_TRACE_ID", "MyTraceId-2"); - response = await this.InvokeAPIGatewayRequest("traceid-get-httpapi-v2-request.json"); + response = await InvokeAPIGatewayRequest("traceid-get-httpapi-v2-request.json"); Assert.Equal("MyTraceId-2", response.Body); Environment.SetEnvironmentVariable("_X_AMZN_TRACE_ID", null); - response = await this.InvokeAPIGatewayRequest("traceid-get-httpapi-v2-request.json"); + response = await InvokeAPIGatewayRequest("traceid-get-httpapi-v2-request.json"); Assert.True(!string.IsNullOrEmpty(response.Body) && !string.Equals(response.Body, "MyTraceId-2")); } finally @@ -333,7 +333,7 @@ private async Task InvokeAPIGatewayRequestWith private string GetRequestContent(string fileName) { - var filePath = Path.Combine(Path.GetDirectoryName(this.GetType().GetTypeInfo().Assembly.Location), fileName); + var filePath = Path.Combine(Path.GetDirectoryName(GetType().GetTypeInfo().Assembly.Location), fileName); var requestStr = File.ReadAllText(filePath); return requestStr; } @@ -341,8 +341,8 @@ private string GetRequestContent(string fileName) public class EnvironmentVariableHelper : IDisposable { - private string _name; - private string? _oldValue; + private readonly string _name; + private readonly string _oldValue; public EnvironmentVariableHelper(string name, string value) { _name = name; diff --git a/Libraries/test/Amazon.Lambda.AspNetCoreServer.Test/TestApplicationLoadBalancerCalls.cs b/Libraries/test/Amazon.Lambda.AspNetCoreServer.Test/TestApplicationLoadBalancerCalls.cs index ed2272045..5a76d8b54 100644 --- a/Libraries/test/Amazon.Lambda.AspNetCoreServer.Test/TestApplicationLoadBalancerCalls.cs +++ b/Libraries/test/Amazon.Lambda.AspNetCoreServer.Test/TestApplicationLoadBalancerCalls.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.IO; using System.IO.Compression; using System.Linq; @@ -25,7 +25,7 @@ public async Task TestGetAllValues() { var context = new TestLambdaContext(); - var response = await this.InvokeApplicationLoadBalancerRequest(context, "values-get-all-alb-request.json"); + var response = await InvokeApplicationLoadBalancerRequest(context, "values-get-all-alb-request.json"); Assert.Equal(200, response.StatusCode); Assert.Equal("[\"value1\",\"value2\"]", response.Body); @@ -38,7 +38,7 @@ public async Task TestGetAllValues() [Fact] public async Task TestGetQueryStringValue() { - var response = await this.InvokeApplicationLoadBalancerRequest("values-get-querystring-alb-request.json"); + var response = await InvokeApplicationLoadBalancerRequest("values-get-querystring-alb-request.json"); Assert.Equal("Lewis, Meriwether", response.Body); Assert.True(response.Headers.ContainsKey("Content-Type")); @@ -48,7 +48,7 @@ public async Task TestGetQueryStringValue() [Fact] public async Task TestGetNoQueryStringAlb() { - var response = await this.InvokeApplicationLoadBalancerRequest("values-get-no-querystring-alb-request.json"); + var response = await InvokeApplicationLoadBalancerRequest("values-get-no-querystring-alb-request.json"); Assert.Equal(string.Empty, response.Body); Assert.True(response.Headers.ContainsKey("Content-Type")); @@ -58,14 +58,14 @@ public async Task TestGetNoQueryStringAlb() [Fact] public async Task TestGetNoQueryStringAlbMv() { - var response = await this.InvokeApplicationLoadBalancerRequest("values-get-no-querystring-alb-mv-request.json"); + var response = await InvokeApplicationLoadBalancerRequest("values-get-no-querystring-alb-mv-request.json"); Assert.Equal(string.Empty, response.Body); } [Fact] public async Task TestGetEncodingQueryStringAlb() { - var response = await this.InvokeApplicationLoadBalancerRequest("values-get-querystring-alb-encoding-request.json"); + var response = await InvokeApplicationLoadBalancerRequest("values-get-querystring-alb-encoding-request.json"); var results = JsonConvert.DeserializeObject(response.Body); Assert.Equal("http://www.gooogle.com", results.Url); Assert.Equal(DateTimeOffset.Parse("2019-03-12T16:06:06.549817+00:00"), results.TestDateTimeOffset); @@ -77,7 +77,7 @@ public async Task TestGetEncodingQueryStringAlb() [Fact] public async Task TestGetEncodingQueryStringAlbMv() { - var response = await this.InvokeApplicationLoadBalancerRequest("values-get-querystring-alb-mv-encoding-request.json"); + var response = await InvokeApplicationLoadBalancerRequest("values-get-querystring-alb-mv-encoding-request.json"); var results = JsonConvert.DeserializeObject(response.Body); Assert.Equal("http://www.gooogle.com", results.Url); Assert.Equal(DateTimeOffset.Parse("2019-03-12T16:06:06.549817+00:00"), results.TestDateTimeOffset); @@ -89,7 +89,7 @@ public async Task TestGetEncodingQueryStringAlbMv() [Fact] public async Task TestGetQueryStringValueMV() { - var response = await this.InvokeApplicationLoadBalancerRequest("values-get-querystring-alb-mv-request.json"); + var response = await InvokeApplicationLoadBalancerRequest("values-get-querystring-alb-mv-request.json"); Assert.Equal("value1,value2", response.Body); Assert.True(response.MultiValueHeaders.ContainsKey("Content-Type")); @@ -99,7 +99,7 @@ public async Task TestGetQueryStringValueMV() [Fact] public async Task TestPutWithBody() { - var response = await this.InvokeApplicationLoadBalancerRequest("values-put-withbody-alb-request.json"); + var response = await InvokeApplicationLoadBalancerRequest("values-put-withbody-alb-request.json"); Assert.Equal(200, response.StatusCode); Assert.Equal("Agent, Smith", response.Body); @@ -110,7 +110,7 @@ public async Task TestPutWithBody() [Fact] public async Task TestPutWithBodyMV() { - var response = await this.InvokeApplicationLoadBalancerRequest("values-put-withbody-alb-mv-request.json"); + var response = await InvokeApplicationLoadBalancerRequest("values-put-withbody-alb-mv-request.json"); Assert.Equal(200, response.StatusCode); Assert.Equal("Agent, Smith", response.Body); @@ -121,7 +121,7 @@ public async Task TestPutWithBodyMV() [Fact] public async Task TestGetSingleValue() { - var response = await this.InvokeApplicationLoadBalancerRequest("values-get-single-alb-request.json"); + var response = await InvokeApplicationLoadBalancerRequest("values-get-single-alb-request.json"); Assert.Equal("value=5", response.Body); Assert.True(response.Headers.ContainsKey("Content-Type")); @@ -131,7 +131,7 @@ public async Task TestGetSingleValue() [Fact] public async Task TestGetBinaryContent() { - var response = await this.InvokeApplicationLoadBalancerRequest("values-get-binary-alb-request.json"); + var response = await InvokeApplicationLoadBalancerRequest("values-get-binary-alb-request.json"); Assert.Equal((int)HttpStatusCode.OK, response.StatusCode); @@ -154,7 +154,7 @@ public async Task TestGetBinaryContent() [Fact] public async Task TestPutBinaryContent() { - var response = await this.InvokeApplicationLoadBalancerRequest("values-put-binary-alb-request.json"); + var response = await InvokeApplicationLoadBalancerRequest("values-put-binary-alb-request.json"); Assert.Equal((int)HttpStatusCode.OK, response.StatusCode); @@ -167,7 +167,7 @@ public async Task TestPutBinaryContent() [Fact] public async Task TestHealthCheck() { - var response = await this.InvokeApplicationLoadBalancerRequest("alb-healthcheck.json"); + var response = await InvokeApplicationLoadBalancerRequest("alb-healthcheck.json"); Assert.Equal(200, response.StatusCode); Assert.Equal("[\"value1\",\"value2\"]", response.Body); @@ -178,14 +178,14 @@ public async Task TestHealthCheck() [Fact] public async Task TestContentLengthWithContent() { - var response = await this.InvokeApplicationLoadBalancerRequest("check-content-length-withcontent-alb.json"); + var response = await InvokeApplicationLoadBalancerRequest("check-content-length-withcontent-alb.json"); Assert.Equal("Request content length: 17", response.Body.Trim()); } [Fact] public async Task TestContentLengthNoContent() { - var response = await this.InvokeApplicationLoadBalancerRequest("check-content-length-nocontent-alb.json"); + var response = await InvokeApplicationLoadBalancerRequest("check-content-length-nocontent-alb.json"); Assert.Equal("Request content length: 0", response.Body.Trim()); } @@ -194,7 +194,7 @@ public async Task TestGetCompressResponse() { var context = new TestLambdaContext(); - var response = await this.InvokeApplicationLoadBalancerRequest(context, "compressresponse-get-alb-request.json"); + var response = await InvokeApplicationLoadBalancerRequest(context, "compressresponse-get-alb-request.json"); Assert.Equal(200, response.StatusCode); @@ -227,7 +227,7 @@ public async Task TestGetCompressResponse() [InlineData("rawtarget-escaped-slash-in-path-alb.json", "/foo%2Fbar")] public async Task TestRawTarget(string requestFileName, string expectedRawTarget) { - var response = await this.InvokeApplicationLoadBalancerRequest(requestFileName); + var response = await InvokeApplicationLoadBalancerRequest(requestFileName); Assert.Equal(200, response.StatusCode); @@ -243,7 +243,7 @@ private async Task InvokeApplicationLoadBalance private async Task InvokeApplicationLoadBalancerRequest(TestLambdaContext context, string fileName) { var lambdaFunction = new ALBLambdaFunction(); - var filePath = Path.Combine(Path.GetDirectoryName(this.GetType().GetTypeInfo().Assembly.Location), fileName); + var filePath = Path.Combine(Path.GetDirectoryName(GetType().GetTypeInfo().Assembly.Location), fileName); var requestStr = File.ReadAllText(filePath); var request = JsonConvert.DeserializeObject(requestStr); return await lambdaFunction.FunctionHandlerAsync(request, context); diff --git a/Libraries/test/Amazon.Lambda.AspNetCoreServer.Test/TestCallingWebAPI.cs b/Libraries/test/Amazon.Lambda.AspNetCoreServer.Test/TestCallingWebAPI.cs index 042b2e46b..9743da7db 100644 --- a/Libraries/test/Amazon.Lambda.AspNetCoreServer.Test/TestCallingWebAPI.cs +++ b/Libraries/test/Amazon.Lambda.AspNetCoreServer.Test/TestCallingWebAPI.cs @@ -33,7 +33,7 @@ public async Task TestHttpApiGetAllValues() { var context = new TestLambdaContext(); - var response = await this.InvokeAPIGatewayRequest(context, "values-get-all-httpapi-request.json"); + var response = await InvokeAPIGatewayRequest(context, "values-get-all-httpapi-request.json"); Assert.Equal(200, response.StatusCode); Assert.Equal("[\"value1\",\"value2\"]", response.Body); @@ -49,7 +49,7 @@ public async Task TestGetAllValues() { var context = new TestLambdaContext(); - var response = await this.InvokeAPIGatewayRequest(context, "values-get-all-apigateway-request.json"); + var response = await InvokeAPIGatewayRequest(context, "values-get-all-apigateway-request.json"); Assert.Equal(200, response.StatusCode); Assert.Equal("[\"value1\",\"value2\"]", response.Body); @@ -62,7 +62,7 @@ public async Task TestGetAllValues() [Fact] public async Task TestGetAllValuesWithCustomPath() { - var response = await this.InvokeAPIGatewayRequest("values-get-different-proxypath-apigateway-request.json"); + var response = await InvokeAPIGatewayRequest("values-get-different-proxypath-apigateway-request.json"); Assert.Equal(200, response.StatusCode); Assert.Equal("[\"value1\",\"value2\"]", response.Body); @@ -73,7 +73,7 @@ public async Task TestGetAllValuesWithCustomPath() [Fact] public async Task TestGetSingleValue() { - var response = await this.InvokeAPIGatewayRequest("values-get-single-apigateway-request.json"); + var response = await InvokeAPIGatewayRequest("values-get-single-apigateway-request.json"); Assert.Equal("value=5", response.Body); Assert.True(response.MultiValueHeaders.ContainsKey("Content-Type")); @@ -83,7 +83,7 @@ public async Task TestGetSingleValue() [Fact] public async Task TestGetQueryStringValue() { - var response = await this.InvokeAPIGatewayRequest("values-get-querystring-apigateway-request.json"); + var response = await InvokeAPIGatewayRequest("values-get-querystring-apigateway-request.json"); Assert.Equal("Lewis, Meriwether", response.Body); Assert.True(response.MultiValueHeaders.ContainsKey("Content-Type")); @@ -93,7 +93,7 @@ public async Task TestGetQueryStringValue() [Fact] public async Task TestGetNoQueryStringApiGateway() { - var response = await this.InvokeAPIGatewayRequest("values-get-no-querystring-apigateway-request.json"); + var response = await InvokeAPIGatewayRequest("values-get-no-querystring-apigateway-request.json"); Assert.Equal(string.Empty, response.Body); Assert.True(response.MultiValueHeaders.ContainsKey("Content-Type")); @@ -103,7 +103,7 @@ public async Task TestGetNoQueryStringApiGateway() [Fact] public async Task TestGetEncodingQueryStringGateway() { - var response = await this.InvokeAPIGatewayRequest("values-get-querystring-apigateway-encoding-request.json"); + var response = await InvokeAPIGatewayRequest("values-get-querystring-apigateway-encoding-request.json"); var results = JsonSerializer.Deserialize(response.Body, new JsonSerializerOptions { PropertyNameCaseInsensitive = true @@ -118,7 +118,7 @@ public async Task TestGetEncodingQueryStringGateway() [Fact] public async Task TestPutWithBody() { - var response = await this.InvokeAPIGatewayRequest("values-put-withbody-apigateway-request.json"); + var response = await InvokeAPIGatewayRequest("values-put-withbody-apigateway-request.json"); Assert.Equal(200, response.StatusCode); Assert.Equal("Agent, Smith", response.Body); @@ -129,7 +129,7 @@ public async Task TestPutWithBody() [Fact] public async Task TestPutNoBody() { - var response = await this.InvokeAPIGatewayRequest("values-put-no-body-request.json"); + var response = await InvokeAPIGatewayRequest("values-put-no-body-request.json"); Assert.Equal(string.Empty, response.Body); Assert.Equal(202, response.StatusCode); @@ -138,7 +138,7 @@ public async Task TestPutNoBody() [Fact] public async Task TestDefaultResponseErrorCode() { - var response = await this.InvokeAPIGatewayRequest("values-get-error-apigateway-request.json"); + var response = await InvokeAPIGatewayRequest("values-get-error-apigateway-request.json"); Assert.Equal(500, response.StatusCode); Assert.Equal(string.Empty, response.Body); @@ -151,7 +151,7 @@ public async Task TestDefaultResponseErrorCode() [InlineData("values-get-typeloaderror-apigateway-request.json", "ReflectionTypeLoadException", false)] public async Task TestEnhancedExceptions(string requestFileName, string expectedExceptionType, bool configureApiToReturnExceptionDetail) { - var response = await this.InvokeAPIGatewayRequest(requestFileName, configureApiToReturnExceptionDetail); + var response = await InvokeAPIGatewayRequest(requestFileName, configureApiToReturnExceptionDetail); Assert.Equal(500, response.StatusCode); Assert.Equal(string.Empty, response.Body); @@ -169,7 +169,7 @@ public async Task TestEnhancedExceptions(string requestFileName, string expected [Fact] public async Task TestGettingSwaggerDefinition() { - var response = await this.InvokeAPIGatewayRequest("swagger-get-apigateway-request.json"); + var response = await InvokeAPIGatewayRequest("swagger-get-apigateway-request.json"); Assert.Equal(200, response.StatusCode); Assert.True(response.Body.Length > 0); @@ -232,7 +232,7 @@ public void TestCustomAuthorizerSerialization() [Fact] public async Task TestGetBinaryContent() { - var response = await this.InvokeAPIGatewayRequest("values-get-binary-apigateway-request.json"); + var response = await InvokeAPIGatewayRequest("values-get-binary-apigateway-request.json"); Assert.Equal((int) HttpStatusCode.OK, response.StatusCode); @@ -255,7 +255,7 @@ public async Task TestGetBinaryContent() [Fact] public async Task TestEncodePlusInResourcePath() { - var response = await this.InvokeAPIGatewayRequest("encode-plus-in-resource-path.json"); + var response = await InvokeAPIGatewayRequest("encode-plus-in-resource-path.json"); Assert.Equal(200, response.StatusCode); @@ -267,7 +267,7 @@ public async Task TestEncodePlusInResourcePath() public async Task TestEncodeSpaceInResourcePath() { var requestStr = GetRequestContent("encode-space-in-resource-path.json"); - var response = await this.InvokeAPIGatewayRequest("encode-space-in-resource-path.json"); + var response = await InvokeAPIGatewayRequest("encode-space-in-resource-path.json"); Assert.Equal(200, response.StatusCode); Assert.Equal("value=tmh/file name.xml", response.Body); @@ -278,12 +278,12 @@ public async Task TestEncodeSpaceInResourcePath() public async Task TestEncodeSlashInResourcePath() { var requestStr = GetRequestContent("encode-slash-in-resource-path.json"); - var response = await this.InvokeAPIGatewayRequestWithContent(new TestLambdaContext(), requestStr); + var response = await InvokeAPIGatewayRequestWithContent(new TestLambdaContext(), requestStr); Assert.Equal(200, response.StatusCode); Assert.Equal("{\"only\":\"a%2Fb\"}", response.Body); - response = await this.InvokeAPIGatewayRequestWithContent(new TestLambdaContext(), requestStr.Replace("a%2Fb", "a/b")); + response = await InvokeAPIGatewayRequestWithContent(new TestLambdaContext(), requestStr.Replace("a%2Fb", "a/b")); Assert.Equal(200, response.StatusCode); Assert.Equal("{\"first\":\"a\",\"second\":\"b\"}", response.Body); @@ -292,7 +292,7 @@ public async Task TestEncodeSlashInResourcePath() [Fact] public async Task TestAdditionalPathParametersInProxyPath() { - var response = await this.InvokeAPIGatewayRequest("additional-path-parameters-in-proxy-path.json"); + var response = await InvokeAPIGatewayRequest("additional-path-parameters-in-proxy-path.json"); Assert.Equal(200, response.StatusCode); var root = JsonSerializer.Deserialize(response.Body); @@ -302,7 +302,7 @@ public async Task TestAdditionalPathParametersInProxyPath() [Fact] public async Task TestAdditionalPathParametersInNonProxyPath() { - var response = await this.InvokeAPIGatewayRequest("additional-path-parameters-in-non-proxy-path.json"); + var response = await InvokeAPIGatewayRequest("additional-path-parameters-in-non-proxy-path.json"); Assert.Equal(200, response.StatusCode); var root = JsonSerializer.Deserialize(response.Body); @@ -312,7 +312,7 @@ public async Task TestAdditionalPathParametersInNonProxyPath() [Fact] public async Task TestSpaceInResourcePathAndQueryString() { - var response = await this.InvokeAPIGatewayRequest("encode-space-in-resource-path-and-query.json"); + var response = await InvokeAPIGatewayRequest("encode-space-in-resource-path-and-query.json"); Assert.Equal(200, response.StatusCode); @@ -326,7 +326,7 @@ public async Task TestSpaceInResourcePathAndQueryString() [Fact] public async Task TestTrailingSlashInPath() { - var response = await this.InvokeAPIGatewayRequest("trailing-slash-in-path.json"); + var response = await InvokeAPIGatewayRequest("trailing-slash-in-path.json"); Assert.Equal(200, response.StatusCode); @@ -342,7 +342,7 @@ public async Task TestTrailingSlashInPath() [InlineData("rawtarget-escaped-slash-in-path.json", "/foo%2Fbar")] public async Task TestRawTarget(string requestFileName, string expectedRawTarget) { - var response = await this.InvokeAPIGatewayRequest(requestFileName); + var response = await InvokeAPIGatewayRequest(requestFileName); Assert.Equal(200, response.StatusCode); @@ -353,7 +353,7 @@ public async Task TestRawTarget(string requestFileName, string expectedRawTarget [Fact] public async Task TestAuthTestAccess() { - var response = await this.InvokeAPIGatewayRequest("authtest-access-request.json"); + var response = await InvokeAPIGatewayRequest("authtest-access-request.json"); Assert.Equal(200, response.StatusCode); Assert.Equal("You Have Access", response.Body); @@ -362,7 +362,7 @@ public async Task TestAuthTestAccess() [Fact] public async Task TestAuthMTls() { - var response = await this.InvokeAPIGatewayRequest("mtls-request.json"); + var response = await InvokeAPIGatewayRequest("mtls-request.json"); Assert.Equal(200, response.StatusCode); Assert.Equal("O=Internet Widgits Pty Ltd, S=Some-State, C=AU", response.Body); } @@ -370,7 +370,7 @@ public async Task TestAuthMTls() [Fact] public async Task TestAuthMTlsWithTrailingNewLine() { - var response = await this.InvokeAPIGatewayRequest("mtls-request-trailing-newline.json"); + var response = await InvokeAPIGatewayRequest("mtls-request-trailing-newline.json"); Assert.Equal(200, response.StatusCode); Assert.Equal("O=Internet Widgits Pty Ltd, S=Some-State, C=AU", response.Body); } @@ -379,7 +379,7 @@ public async Task TestAuthMTlsWithTrailingNewLine() public async Task TestAuthTestAccess_CustomLambdaAuthorizerClaims() { var response = - await this.InvokeAPIGatewayRequest("authtest-access-request-custom-lambda-authorizer-output.json"); + await InvokeAPIGatewayRequest("authtest-access-request-custom-lambda-authorizer-output.json"); Assert.Equal(200, response.StatusCode); Assert.Equal("You Have Access", response.Body); @@ -388,7 +388,7 @@ public async Task TestAuthTestAccess_CustomLambdaAuthorizerClaims() [Fact] public async Task TestAuthTestNoAccess() { - var response = await this.InvokeAPIGatewayRequest("authtest-noaccess-request.json"); + var response = await InvokeAPIGatewayRequest("authtest-noaccess-request.json"); Assert.NotEqual(200, response.StatusCode); } @@ -397,7 +397,7 @@ public async Task TestAuthTestNoAccess() [Fact] public async Task TestMissingResourceInRequest() { - var response = await this.InvokeAPIGatewayRequest("missing-resource-request.json"); + var response = await InvokeAPIGatewayRequest("missing-resource-request.json"); Assert.Equal(200, response.StatusCode); Assert.True(response.Body.Length > 0); @@ -408,22 +408,22 @@ public async Task TestMissingResourceInRequest() [Fact] public async Task TestDeleteNoContentContentType() { - var response = await this.InvokeAPIGatewayRequest("values-delete-no-content-type-apigateway-request.json"); + var response = await InvokeAPIGatewayRequest("values-delete-no-content-type-apigateway-request.json"); Assert.Equal(200, response.StatusCode); Assert.True(response.Body.Length == 0); - Assert.Equal(1, response.MultiValueHeaders["Content-Type"].Count); + Assert.Single(response.MultiValueHeaders["Content-Type"]); Assert.Null(response.MultiValueHeaders["Content-Type"][0]); } [Fact] public async Task TestRedirectNoContentType() { - var response = await this.InvokeAPIGatewayRequest("redirect-apigateway-request.json"); + var response = await InvokeAPIGatewayRequest("redirect-apigateway-request.json"); Assert.Equal(302, response.StatusCode); Assert.True(response.Body.Length == 0); - Assert.Equal(1, response.MultiValueHeaders["Content-Type"].Count); + Assert.Single(response.MultiValueHeaders["Content-Type"]); Assert.Null(response.MultiValueHeaders["Content-Type"][0]); Assert.Equal("redirecttarget", response.MultiValueHeaders["Location"][0]); @@ -432,14 +432,14 @@ public async Task TestRedirectNoContentType() [Fact] public async Task TestContentLengthWithContent() { - var response = await this.InvokeAPIGatewayRequest("check-content-length-withcontent-apigateway.json"); + var response = await InvokeAPIGatewayRequest("check-content-length-withcontent-apigateway.json"); Assert.Equal("Request content length: 17", response.Body.Trim()); } [Fact] public async Task TestContentLengthNoContent() { - var response = await this.InvokeAPIGatewayRequest("check-content-length-nocontent-apigateway.json"); + var response = await InvokeAPIGatewayRequest("check-content-length-nocontent-apigateway.json"); Assert.Equal("Request content length: 0", response.Body.Trim()); } @@ -448,7 +448,7 @@ public async Task TestGetCompressResponse() { var context = new TestLambdaContext(); - var response = await this.InvokeAPIGatewayRequest(context, "compressresponse-get-apigateway-request.json"); + var response = await InvokeAPIGatewayRequest(context, "compressresponse-get-apigateway-request.json"); Assert.Equal(200, response.StatusCode); @@ -476,7 +476,7 @@ public async Task TestGetCompressResponse() public async Task TestRequestServicesAreAvailable() { var requestStr = GetRequestContent("requestservices-get-apigateway-request.json"); - var response = await this.InvokeAPIGatewayRequestWithContent(new TestLambdaContext(), requestStr); + var response = await InvokeAPIGatewayRequestWithContent(new TestLambdaContext(), requestStr); Assert.Equal(200, response.StatusCode); Assert.Equal("Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngineScope", response.Body); @@ -540,7 +540,7 @@ private async Task InvokeAPIGatewayRequestWithContent(T private string GetRequestContent(string fileName) { - var filePath = Path.Combine(Path.GetDirectoryName(this.GetType().GetTypeInfo().Assembly.Location), fileName); + var filePath = Path.Combine(Path.GetDirectoryName(GetType().GetTypeInfo().Assembly.Location), fileName); var requestStr = File.ReadAllText(filePath); return requestStr; } diff --git a/Libraries/test/Amazon.Lambda.AspNetCoreServer.Test/TestMinimalAPI.cs b/Libraries/test/Amazon.Lambda.AspNetCoreServer.Test/TestMinimalAPI.cs index 385856acd..22709a1e3 100644 --- a/Libraries/test/Amazon.Lambda.AspNetCoreServer.Test/TestMinimalAPI.cs +++ b/Libraries/test/Amazon.Lambda.AspNetCoreServer.Test/TestMinimalAPI.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; @@ -17,11 +17,11 @@ namespace Amazon.Lambda.AspNetCoreServer.Test { public class TestMinimalAPI : IClassFixture { - TestMinimalAPIAppFixture _fixture; + readonly TestMinimalAPIAppFixture _fixture; public TestMinimalAPI(TestMinimalAPI.TestMinimalAPIAppFixture fixture) { - this._fixture = fixture; + _fixture = fixture; } [Fact] @@ -34,7 +34,7 @@ public void TestMapPostComplexType() public class TestMinimalAPIAppFixture : IDisposable { - object lock_process = new object(); + readonly object lock_process = new object(); public TestMinimalAPIAppFixture() { } @@ -46,7 +46,7 @@ public void Dispose() public T ExecuteRequest(string eventFilePath) { - var requestFilePath = Path.Combine(Path.GetDirectoryName(this.GetType().GetTypeInfo().Assembly.Location), eventFilePath); + var requestFilePath = Path.Combine(Path.GetDirectoryName(GetType().GetTypeInfo().Assembly.Location), eventFilePath); var responseFilePath = Path.GetTempFileName(); var comamndArgument = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? $"/c" : $"-c"; @@ -61,6 +61,11 @@ public T ExecuteRequest(string eventFilePath) using var process = Process.Start(processStartInfo); process.WaitForExit(15000); + if (process.ExitCode != 0) + { + throw new Exception("Process failed with exit code: " + process.ExitCode); + } + if(!File.Exists(responseFilePath)) { throw new Exception("No response file found"); @@ -77,7 +82,7 @@ public T ExecuteRequest(string eventFilePath) private string GetTestAppDirectory() { - var path = this.GetType().GetTypeInfo().Assembly.Location; + var path = GetType().GetTypeInfo().Assembly.Location; while(!string.Equals(new DirectoryInfo(path).Name, "test")) { path = Directory.GetParent(path).FullName; @@ -102,7 +107,7 @@ private string GetSystemShell() return RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "cmd.exe" : "/bin/sh"; } - private bool TryGetEnvironmentVariable(string variable, out string? value) + private bool TryGetEnvironmentVariable(string variable, out string value) { value = Environment.GetEnvironmentVariable(variable); return !string.IsNullOrEmpty(value); diff --git a/Libraries/test/Amazon.Lambda.Core.Tests/Amazon.Lambda.Core.Tests.csproj b/Libraries/test/Amazon.Lambda.Core.Tests/Amazon.Lambda.Core.Tests.csproj index 755b6b95e..09c512fb6 100644 --- a/Libraries/test/Amazon.Lambda.Core.Tests/Amazon.Lambda.Core.Tests.csproj +++ b/Libraries/test/Amazon.Lambda.Core.Tests/Amazon.Lambda.Core.Tests.csproj @@ -15,10 +15,13 @@ - - - - + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + diff --git a/Libraries/test/Amazon.Lambda.DynamoDBEvents.SDK.Convertor.Tests/Amazon.Lambda.DynamoDBEvents.SDK.Convertor.Tests.csproj b/Libraries/test/Amazon.Lambda.DynamoDBEvents.SDK.Convertor.Tests/Amazon.Lambda.DynamoDBEvents.SDK.Convertor.Tests.csproj index a6d081721..24216ee51 100644 --- a/Libraries/test/Amazon.Lambda.DynamoDBEvents.SDK.Convertor.Tests/Amazon.Lambda.DynamoDBEvents.SDK.Convertor.Tests.csproj +++ b/Libraries/test/Amazon.Lambda.DynamoDBEvents.SDK.Convertor.Tests/Amazon.Lambda.DynamoDBEvents.SDK.Convertor.Tests.csproj @@ -10,9 +10,12 @@ - - - + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/Libraries/test/Amazon.Lambda.DynamoDBEvents.SDK.Convertor.Tests/DynamodbIdentityConvertorTests.cs b/Libraries/test/Amazon.Lambda.DynamoDBEvents.SDK.Convertor.Tests/DynamodbIdentityConvertorTests.cs index ee5557878..2120900cc 100644 --- a/Libraries/test/Amazon.Lambda.DynamoDBEvents.SDK.Convertor.Tests/DynamodbIdentityConvertorTests.cs +++ b/Libraries/test/Amazon.Lambda.DynamoDBEvents.SDK.Convertor.Tests/DynamodbIdentityConvertorTests.cs @@ -6,7 +6,7 @@ public class DynamodbIdentityConvertorTests public void ConvertToSdkIdentity_NullLambdaIdentity_ReturnsNull() { // Arrange - DynamoDBEvent.Identity lambdaIdentity = null; + DynamoDBEvent.Identity? lambdaIdentity = null; // Act var result = lambdaIdentity.ConvertToSdkIdentity(); diff --git a/Libraries/test/Amazon.Lambda.DynamoDBEvents.SDK.Convertor.Tests/DynamodbStreamRecordConvertorTests.cs b/Libraries/test/Amazon.Lambda.DynamoDBEvents.SDK.Convertor.Tests/DynamodbStreamRecordConvertorTests.cs index a56deb0dd..ff52dd3ba 100644 --- a/Libraries/test/Amazon.Lambda.DynamoDBEvents.SDK.Convertor.Tests/DynamodbStreamRecordConvertorTests.cs +++ b/Libraries/test/Amazon.Lambda.DynamoDBEvents.SDK.Convertor.Tests/DynamodbStreamRecordConvertorTests.cs @@ -6,7 +6,7 @@ public class DynamodbStreamRecordConvertorTests public void ConvertToSdkStreamRecord_NullLambdaStreamRecord_ReturnsNull() { // Arrange - DynamoDBEvent.StreamRecord lambdaStreamRecord = null; + DynamoDBEvent.StreamRecord? lambdaStreamRecord = null; // Act var result = lambdaStreamRecord.ConvertToSdkStreamRecord(); diff --git a/Libraries/test/Amazon.Lambda.Logging.AspNetCore.Tests/Amazon.Lambda.Logging.AspNetCore.Tests.csproj b/Libraries/test/Amazon.Lambda.Logging.AspNetCore.Tests/Amazon.Lambda.Logging.AspNetCore.Tests.csproj index 148cb7155..cf907724c 100644 --- a/Libraries/test/Amazon.Lambda.Logging.AspNetCore.Tests/Amazon.Lambda.Logging.AspNetCore.Tests.csproj +++ b/Libraries/test/Amazon.Lambda.Logging.AspNetCore.Tests/Amazon.Lambda.Logging.AspNetCore.Tests.csproj @@ -1,7 +1,7 @@  - net10.0 + net8.0;net10.0 Amazon.Lambda.Logging.AspNetCore.Tests Amazon.Lambda.Logging.AspNetCore.Tests true @@ -34,16 +34,16 @@ - - - + + + all runtime; build; native; contentfiles; analyzers - - - - + + + + diff --git a/Libraries/test/Amazon.Lambda.Logging.AspNetCore.Tests/LoggingTests.cs b/Libraries/test/Amazon.Lambda.Logging.AspNetCore.Tests/LoggingTests.cs index 241237e00..e9997cdf3 100644 --- a/Libraries/test/Amazon.Lambda.Logging.AspNetCore.Tests/LoggingTests.cs +++ b/Libraries/test/Amazon.Lambda.Logging.AspNetCore.Tests/LoggingTests.cs @@ -18,7 +18,7 @@ public class LoggingTests private const string SHOULD_NOT_APPEAR = "TextThatShouldNotAppear"; private const string SHOULD_APPEAR_EVENT = "EventThatShouldAppear"; private const string SHOULD_APPEAR_EXCEPTION = "ExceptionThatShouldAppear"; - private static string APPSETTINGS_DIR = Directory.GetCurrentDirectory(); + private static readonly string APPSETTINGS_DIR = Directory.GetCurrentDirectory(); private static readonly Func GET_SHOULD_APPEAR_EVENT = (id) => new EventId(451, SHOULD_APPEAR_EVENT + id); private static readonly EventId SHOULD_NOT_APPEAR_EVENT = new EventId(333, "EventThatShoulNotdAppear"); private static readonly Func GET_SHOULD_APPEAR_EXCEPTION = (id) => new Exception(SHOULD_APPEAR_EXCEPTION + id); @@ -436,7 +436,6 @@ public void TestLoggingWithTypeCategories() // act var httpClientLogger = loggerFactory.CreateLogger(); - var authMngrLogger = loggerFactory.CreateLogger(); var arrayLogger = loggerFactory.CreateLogger(); httpClientLogger.LogTrace(SHOULD_NOT_APPEAR); @@ -446,13 +445,6 @@ public void TestLoggingWithTypeCategories() httpClientLogger.LogError(SHOULD_APPEAR); httpClientLogger.LogCritical(SHOULD_APPEAR); - authMngrLogger.LogTrace(SHOULD_NOT_APPEAR); - authMngrLogger.LogDebug(SHOULD_NOT_APPEAR); - authMngrLogger.LogInformation(SHOULD_APPEAR); - authMngrLogger.LogWarning(SHOULD_APPEAR); - authMngrLogger.LogError(SHOULD_APPEAR); - authMngrLogger.LogCritical(SHOULD_APPEAR); - arrayLogger.LogTrace(SHOULD_NOT_APPEAR); arrayLogger.LogDebug(SHOULD_NOT_APPEAR); arrayLogger.LogInformation(SHOULD_NOT_APPEAR); diff --git a/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.IntegrationTests/Amazon.Lambda.RuntimeSupport.IntegrationTests.csproj b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.IntegrationTests/Amazon.Lambda.RuntimeSupport.IntegrationTests.csproj index 7a4aeb901..336d524ab 100644 --- a/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.IntegrationTests/Amazon.Lambda.RuntimeSupport.IntegrationTests.csproj +++ b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.IntegrationTests/Amazon.Lambda.RuntimeSupport.IntegrationTests.csproj @@ -19,11 +19,11 @@ - - - - - + + + + + all @@ -32,7 +32,7 @@ - + diff --git a/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.IntegrationTests/ApiGatewayStreamingTests.cs b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.IntegrationTests/ApiGatewayStreamingTests.cs index 4401612c7..d357ee50f 100644 --- a/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.IntegrationTests/ApiGatewayStreamingTests.cs +++ b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.IntegrationTests/ApiGatewayStreamingTests.cs @@ -128,28 +128,6 @@ public async Task StreamingErrorEndpoint_StreamIsTruncated() } } - [Fact] - public async Task OnCompletedCallback_IsExecuted() - { - var apiUrl = await _fixture.GetApiUrlAsync(); - using var httpClient = new HttpClient(); - - var response = await httpClient.GetWithRetryAsync($"{apiUrl}oncompleted-test"); - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - var body = await response.Content.ReadAsStringAsync(); - Output.WriteLine($"Body: {body}"); - Assert.Contains("OnCompleted callback registered", body); - - var verifyResponse = await httpClient.GetWithRetryAsync($"{apiUrl}oncompleted-verify"); - Assert.Equal(HttpStatusCode.OK, verifyResponse.StatusCode); - var verifyBody = await verifyResponse.Content.ReadAsStringAsync(); - Output.WriteLine($"Verify body: {verifyBody}"); - - var doc = JsonDocument.Parse(verifyBody); - Assert.True(doc.RootElement.GetProperty("onCompletedExecuted").GetBoolean(), - "OnCompleted callback should have been executed"); - } - [Fact] public async Task CustomHeaders_PassedThrough() { @@ -198,8 +176,9 @@ public async Task PostWithBody_EchoesRequestBody() var apiUrl = await _fixture.GetApiUrlAsync(); using var httpClient = new HttpClient(); - var content = new StringContent("Hello from integration test", Encoding.UTF8, "text/plain"); - var response = await httpClient.PostAsync($"{apiUrl}echo-body", content); + var response = await httpClient.PostWithRetryAsync( + $"{apiUrl}echo-body", + new StringContent("Hello from integration test", Encoding.UTF8, "text/plain")); Output.WriteLine($"Status: {response.StatusCode}"); var body = await response.Content.ReadAsStringAsync(); @@ -217,6 +196,7 @@ public async Task PostWithBody_EchoesRequestBody() /// /// Tests streaming through API Gateway REST API. /// + [Collection("Integration Tests")] public class RestApiStreamingTests : StreamingTestBase, IClassFixture { public RestApiStreamingTests(RestApiStreamingFixture fixture, ITestOutputHelper output) @@ -227,6 +207,8 @@ public RestApiStreamingTests(RestApiStreamingFixture fixture, ITestOutputHelper /// Tests streaming through Lambda Function URL. /// Function URL uses the same payload format as HTTP API v2. /// + [Collection("Integration Tests")] + public class FunctionUrlStreamingTests : StreamingTestBase, IClassFixture { public FunctionUrlStreamingTests(FunctionUrlStreamingFixture fixture, ITestOutputHelper output) @@ -398,7 +380,7 @@ await s3Client.PutBucketAsync(new Amazon.S3.Model.PutBucketRequest private async Task WaitForEndpointAsync() { using var httpClient = new HttpClient(); - var maxRetries = 10; + var maxRetries = 20; for (var i = 0; i < maxRetries; i++) { try @@ -424,6 +406,15 @@ public static async Task GetWithRetryAsync( this HttpClient httpClient, string url, HttpStatusCode expectedCode = HttpStatusCode.OK, int maxRetries = 5, int delaySeconds = 5) + { + return await GetWithRetryAsync(httpClient, url, expectedCode, null, maxRetries, delaySeconds); + } + + public static async Task GetWithRetryAsync( + this HttpClient httpClient, string url, + HttpStatusCode expectedCode, + Func> contentValidator, + int maxRetries = 5, int delaySeconds = 5) { for (var i = 0; i < maxRetries; i++) { @@ -431,6 +422,33 @@ public static async Task GetWithRetryAsync( { var response = await httpClient.GetAsync(url); if (response.StatusCode == expectedCode) + { + if (contentValidator == null || await contentValidator(response)) + { + return response; + } + } + } + catch + { + // Ignore and retry + } + await Task.Delay(TimeSpan.FromSeconds(delaySeconds)); + } + throw new Exception($"Failed to get expected response from {url} after {maxRetries} attempts"); + } + + public static async Task PostWithRetryAsync( + this HttpClient httpClient, string url, HttpContent content, + HttpStatusCode expectedCode = HttpStatusCode.OK, + int maxRetries = 5, int delaySeconds = 5) + { + for (var i = 0; i < maxRetries; i++) + { + try + { + var response = await httpClient.PostAsync(url, content); + if (response.StatusCode == expectedCode) { return response; } @@ -439,9 +457,22 @@ public static async Task GetWithRetryAsync( { // Ignore and retry } + // HttpContent can only be consumed once; create fresh content for retries + content = await CloneHttpContentAsync(content); await Task.Delay(TimeSpan.FromSeconds(delaySeconds)); } throw new Exception($"Failed to get expected status code {expectedCode} from {url} after {maxRetries} attempts"); } + + private static async Task CloneHttpContentAsync(HttpContent original) + { + var bytes = await original.ReadAsByteArrayAsync(); + var clone = new ByteArrayContent(bytes); + if (original.Headers.ContentType != null) + { + clone.Headers.ContentType = original.Headers.ContentType; + } + return clone; + } } } diff --git a/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.IntegrationTests/BaseCustomRuntimeTest.cs b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.IntegrationTests/BaseCustomRuntimeTest.cs index 314aa45c4..148b52649 100644 --- a/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.IntegrationTests/BaseCustomRuntimeTest.cs +++ b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.IntegrationTests/BaseCustomRuntimeTest.cs @@ -15,6 +15,8 @@ namespace Amazon.Lambda.RuntimeSupport.IntegrationTests { public class BaseCustomRuntimeTest { + protected readonly Runtime _providedRuntime = Runtime.ProvidedAl2023; + public const int FUNCTION_MEMORY_MB = 512; public static readonly RegionEndpoint TestRegion = RegionEndpoint.USWest2; @@ -44,7 +46,7 @@ public class BaseCustomRuntimeTest protected string ExecutionRoleArn { get; set; } private const string TestsProjectDirectoryName = "Amazon.Lambda.RuntimeSupport.Tests"; - private IntegrationTestFixture _fixture; + private readonly IntegrationTestFixture _fixture; protected BaseCustomRuntimeTest(IntegrationTestFixture fixture, string functionName, string deploymentZipKey, string deploymentPackageZipRelativePath, string handler) { @@ -110,13 +112,13 @@ await iamClient.DetachRolePolicyAsync(new DetachRolePolicyRequest } public async Task PrepareTestResources(IAmazonS3 s3Client, IAmazonLambda lambdaClient, - AmazonIdentityManagementServiceClient iamClient) + AmazonIdentityManagementServiceClient iamClient, Runtime runtime) { var roleAlreadyExisted = await ValidateAndSetIamRoleArn(iamClient); var testBucketName = TestBucketRoot + Guid.NewGuid().ToString(); await CreateBucketWithDeploymentZipAsync(s3Client, testBucketName); - await CreateFunctionAsync(lambdaClient, testBucketName); + await CreateFunctionAsync(lambdaClient, testBucketName, runtime); return roleAlreadyExisted; } @@ -273,7 +275,7 @@ private async Task WaitForFunctionToBeReady(IAmazonLambda lambdaClient) await Task.Delay(1000); } - protected async Task CreateFunctionAsync(IAmazonLambda lambdaClient, string bucketName) + protected async Task CreateFunctionAsync(IAmazonLambda lambdaClient, string bucketName, Runtime runtime) { await DeleteFunctionIfExistsAsync(lambdaClient); @@ -288,7 +290,7 @@ protected async Task CreateFunctionAsync(IAmazonLambda lambdaClient, string buck Handler = Handler, MemorySize = FUNCTION_MEMORY_MB, Timeout = 30, - Runtime = Runtime.Dotnet10, + Runtime = runtime, Role = ExecutionRoleArn }; @@ -357,7 +359,7 @@ private string GetDeploymentZipPath() foreach (var kvp in _fixture.TestAppPaths) { message.AppendLine($"{kvp.Key}: {kvp.Value}"); - } + } throw new NoDeploymentPackageFoundException(message.ToString()); diff --git a/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.IntegrationTests/CustomRuntimeAspNetCoreMinimalApiCustomSerializerTest.cs b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.IntegrationTests/CustomRuntimeAspNetCoreMinimalApiCustomSerializerTest.cs index d8bb17103..c2d585b0a 100644 --- a/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.IntegrationTests/CustomRuntimeAspNetCoreMinimalApiCustomSerializerTest.cs +++ b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.IntegrationTests/CustomRuntimeAspNetCoreMinimalApiCustomSerializerTest.cs @@ -22,7 +22,7 @@ namespace Amazon.Lambda.RuntimeSupport.IntegrationTests public class CustomRuntimeAspNetCoreMinimalApiCustomSerializerTest : BaseCustomRuntimeTest { public CustomRuntimeAspNetCoreMinimalApiCustomSerializerTest(IntegrationTestFixture fixture) - : base(fixture, "CustomRuntimeMinimalApiCustomSerializerTest-" + DateTime.Now.Ticks, "CustomRuntimeAspNetCoreMinimalApiCustomSerializerTest.zip", @"CustomRuntimeAspNetCoreMinimalApiCustomSerializerTest\bin\Release\net8.0\CustomRuntimeAspNetCoreMinimalApiCustomSerializerTest.zip", "bootstrap") + : base(fixture, "CustomRuntimeMinimalApiCustomSerializerTest-" + DateTime.Now.Ticks, "CustomRuntimeAspNetCoreMinimalApiCustomSerializerTest.zip", @"CustomRuntimeAspNetCoreMinimalApiCustomSerializerTest\bin\Release\net10.0\CustomRuntimeAspNetCoreMinimalApiCustomSerializerTest.zip", "bootstrap") { } @@ -39,7 +39,7 @@ public async Task TestMinimalApi() try { - roleAlreadyExisted = await PrepareTestResources(s3Client, lambdaClient, iamClient); + roleAlreadyExisted = await PrepareTestResources(s3Client, lambdaClient, iamClient, _providedRuntime); await InvokeSuccessToWeatherForecastController(lambdaClient); } catch (NoDeploymentPackageFoundException) @@ -69,7 +69,7 @@ public async Task TestThreadingLogging() try { - roleAlreadyExisted = await PrepareTestResources(s3Client, lambdaClient, iamClient); + roleAlreadyExisted = await PrepareTestResources(s3Client, lambdaClient, iamClient, _providedRuntime); await InvokeLoggerTestController(lambdaClient); } catch (NoDeploymentPackageFoundException) diff --git a/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.IntegrationTests/CustomRuntimeAspNetCoreMinimalApiTest.cs b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.IntegrationTests/CustomRuntimeAspNetCoreMinimalApiTest.cs index 53fd92608..e3f73416b 100644 --- a/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.IntegrationTests/CustomRuntimeAspNetCoreMinimalApiTest.cs +++ b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.IntegrationTests/CustomRuntimeAspNetCoreMinimalApiTest.cs @@ -22,7 +22,7 @@ namespace Amazon.Lambda.RuntimeSupport.IntegrationTests public class CustomRuntimeAspNetCoreMinimalApiTest : BaseCustomRuntimeTest { public CustomRuntimeAspNetCoreMinimalApiTest(IntegrationTestFixture fixture) - : base(fixture, "CustomRuntimeAspNetCoreMinimalApiTest-" + DateTime.Now.Ticks, "CustomRuntimeAspNetCoreMinimalApiTest.zip", @"CustomRuntimeAspNetCoreMinimalApiTest\bin\Release\net8.0\CustomRuntimeAspNetCoreMinimalApiTest.zip", "bootstrap") + : base(fixture, "CustomRuntimeAspNetCoreMinimalApiTest-" + DateTime.Now.Ticks, "CustomRuntimeAspNetCoreMinimalApiTest.zip", @"CustomRuntimeAspNetCoreMinimalApiTest\bin\Release\net10.0\CustomRuntimeAspNetCoreMinimalApiTest.zip", "bootstrap") { } @@ -39,7 +39,7 @@ public async Task TestMinimalApi() try { - roleAlreadyExisted = await PrepareTestResources(s3Client, lambdaClient, iamClient); + roleAlreadyExisted = await PrepareTestResources(s3Client, lambdaClient, iamClient, _providedRuntime); await InvokeSuccessToWeatherForecastController(lambdaClient); } catch (NoDeploymentPackageFoundException) @@ -69,7 +69,7 @@ public async Task TestThreadingLogging() try { - roleAlreadyExisted = await PrepareTestResources(s3Client, lambdaClient, iamClient); + roleAlreadyExisted = await PrepareTestResources(s3Client, lambdaClient, iamClient, _providedRuntime); await InvokeLoggerTestController(lambdaClient); } catch (NoDeploymentPackageFoundException) diff --git a/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.IntegrationTests/CustomRuntimeTests.cs b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.IntegrationTests/CustomRuntimeTests.cs index 8ab008d66..70a0b77e8 100644 --- a/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.IntegrationTests/CustomRuntimeTests.cs +++ b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.IntegrationTests/CustomRuntimeTests.cs @@ -32,15 +32,15 @@ namespace Amazon.Lambda.RuntimeSupport.IntegrationTests { [Collection("Integration Tests")] - public class CustomRuntimeNET8Tests : CustomRuntimeTests + public class CustomRuntimeNET10Tests : CustomRuntimeTests { - public CustomRuntimeNET8Tests(IntegrationTestFixture fixture) - : base(fixture, "CustomRuntimeNET8FunctionTest-" + DateTime.Now.Ticks, "CustomRuntimeFunctionTest.zip", @"CustomRuntimeFunctionTest\bin\Release\net8.0\CustomRuntimeFunctionTest.zip", "CustomRuntimeFunctionTest", TargetFramework.NET8) + public CustomRuntimeNET10Tests(IntegrationTestFixture fixture) + : base(fixture, "CustomRuntimeNET10FunctionTest-" + DateTime.Now.Ticks, "CustomRuntimeFunctionTest.zip", @"CustomRuntimeFunctionTest\bin\Release\net10.0\CustomRuntimeFunctionTest.zip", "CustomRuntimeFunctionTest", TargetFramework.NET10) { } [Fact] - public async Task TestAllNET8HandlersAsync() + public async Task TestAllNET10HandlersAsync() { await base.TestAllHandlersAsync(); } @@ -48,9 +48,9 @@ public async Task TestAllNET8HandlersAsync() public class CustomRuntimeTests : BaseCustomRuntimeTest { - public enum TargetFramework { NET8 } + public enum TargetFramework { NET8, NET10} - private TargetFramework _targetFramework; + private readonly TargetFramework _targetFramework; public CustomRuntimeTests(IntegrationTestFixture fixture, string functionName, string deploymentZipKey, string deploymentPackageZipRelativePath, string handler, TargetFramework targetFramework) : base(fixture, functionName, deploymentZipKey, deploymentPackageZipRelativePath, handler) @@ -69,15 +69,11 @@ protected virtual async Task TestAllHandlersAsync() try { - roleAlreadyExisted = await PrepareTestResources(s3Client, lambdaClient, iamClient); - - // .NET API to address setting memory constraint was added for .NET 8 - if (_targetFramework == TargetFramework.NET8) - { - await RunMaxHeapMemoryCheck(lambdaClient, "GetTotalAvailableMemoryBytes"); - await RunWithoutMaxHeapMemoryCheck(lambdaClient, "GetTotalAvailableMemoryBytes"); - await RunMaxHeapMemoryCheckWithCustomMemorySettings(lambdaClient, "GetTotalAvailableMemoryBytes"); - } + roleAlreadyExisted = await PrepareTestResources(s3Client, lambdaClient, iamClient, _providedRuntime); + + await RunMaxHeapMemoryCheck(lambdaClient, "GetTotalAvailableMemoryBytes"); + await RunWithoutMaxHeapMemoryCheck(lambdaClient, "GetTotalAvailableMemoryBytes"); + await RunMaxHeapMemoryCheckWithCustomMemorySettings(lambdaClient, "GetTotalAvailableMemoryBytes"); await RunTestExceptionAsync(lambdaClient, "ExceptionNonAsciiCharacterUnwrappedAsync", "", "Exception", "Unhandled exception with non ASCII character: ♂"); await RunTestSuccessAsync(lambdaClient, "UnintendedDisposeTest", "not-used", "UnintendedDisposeTest-SUCCESS"); diff --git a/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.IntegrationTests/IntegrationTestFixture.cs b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.IntegrationTests/IntegrationTestFixture.cs index b8c71519e..e86a37e2d 100644 --- a/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.IntegrationTests/IntegrationTestFixture.cs +++ b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.IntegrationTests/IntegrationTestFixture.cs @@ -20,22 +20,22 @@ public async Task InitializeAsync() "../../../../../../..", "Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/CustomRuntimeFunctionTest"); _tempPaths.AddRange([testAppPath, toolPath] ); - await LambdaToolsHelper.LambdaPackage(toolPath, "net8.0", testAppPath); - TestAppPaths[@"CustomRuntimeFunctionTest\bin\Release\net8.0\CustomRuntimeFunctionTest.zip"] = Path.Combine(testAppPath, @"bin\Release\net8.0\CustomRuntimeFunctionTest.zip"); + await LambdaToolsHelper.LambdaPackage(toolPath, "net10.0", testAppPath); + TestAppPaths[@"CustomRuntimeFunctionTest\bin\Release\net10.0\CustomRuntimeFunctionTest.zip"] = Path.Combine(testAppPath, @"bin\Release\net10.0\CustomRuntimeFunctionTest.zip"); testAppPath = LambdaToolsHelper.GetTempTestAppDirectory( "../../../../../../..", "Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/CustomRuntimeAspNetCoreMinimalApiTest"); _tempPaths.AddRange([testAppPath, toolPath] ); - await LambdaToolsHelper.LambdaPackage(toolPath, "net8.0", testAppPath); - TestAppPaths[@"CustomRuntimeAspNetCoreMinimalApiTest\bin\Release\net8.0\CustomRuntimeAspNetCoreMinimalApiTest.zip"] = Path.Combine(testAppPath, @"bin\Release\net8.0\CustomRuntimeAspNetCoreMinimalApiTest.zip"); + await LambdaToolsHelper.LambdaPackage(toolPath, "net10.0", testAppPath); + TestAppPaths[@"CustomRuntimeAspNetCoreMinimalApiTest\bin\Release\net10.0\CustomRuntimeAspNetCoreMinimalApiTest.zip"] = Path.Combine(testAppPath, @"bin\Release\net10.0\CustomRuntimeAspNetCoreMinimalApiTest.zip"); testAppPath = LambdaToolsHelper.GetTempTestAppDirectory( "../../../../../../..", "Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/CustomRuntimeAspNetCoreMinimalApiCustomSerializerTest"); _tempPaths.AddRange([testAppPath, toolPath] ); - await LambdaToolsHelper.LambdaPackage(toolPath, "net8.0", testAppPath); - TestAppPaths[@"CustomRuntimeAspNetCoreMinimalApiCustomSerializerTest\bin\Release\net8.0\CustomRuntimeAspNetCoreMinimalApiCustomSerializerTest.zip"] = Path.Combine(testAppPath, @"bin\Release\net8.0\CustomRuntimeAspNetCoreMinimalApiCustomSerializerTest.zip"); + await LambdaToolsHelper.LambdaPackage(toolPath, "net10.0", testAppPath); + TestAppPaths[@"CustomRuntimeAspNetCoreMinimalApiCustomSerializerTest\bin\Release\net10.0\CustomRuntimeAspNetCoreMinimalApiCustomSerializerTest.zip"] = Path.Combine(testAppPath, @"bin\Release\net10.0\CustomRuntimeAspNetCoreMinimalApiCustomSerializerTest.zip"); testAppPath = LambdaToolsHelper.GetTempTestAppDirectory( "../../../../../../..", diff --git a/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.IntegrationTests/ResponseStreamingTests.cs b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.IntegrationTests/ResponseStreamingTests.cs index 006df6d15..30e14832d 100644 --- a/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.IntegrationTests/ResponseStreamingTests.cs +++ b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.IntegrationTests/ResponseStreamingTests.cs @@ -114,7 +114,7 @@ public async Task EnsureResourcesDeployedAsync(ResponseStreamingTests tests) return; _tests = tests; - _roleAlreadyExisted = await _tests.PrepareTestResources(_s3Client, _lambdaClient, _iamClient); + _roleAlreadyExisted = await _tests.PrepareTestResources(_s3Client, _lambdaClient, _iamClient, Runtime.Dotnet10); _resourcesCreated = true; } diff --git a/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/Amazon.Lambda.RuntimeSupport.UnitTests.csproj b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/Amazon.Lambda.RuntimeSupport.UnitTests.csproj index cc96b0a0b..4b55471cb 100644 --- a/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/Amazon.Lambda.RuntimeSupport.UnitTests.csproj +++ b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/Amazon.Lambda.RuntimeSupport.UnitTests.csproj @@ -9,12 +9,12 @@ - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/Common.cs b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/Common.cs index 010d7c37f..eacaeb43d 100644 --- a/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/Common.cs +++ b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/Common.cs @@ -1,4 +1,4 @@ -/* +/* * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). @@ -55,7 +55,7 @@ public static void CheckException(Exception e, string expectedPartialMessage) { if (!FindMatchingExceptionMessage(e, expectedPartialMessage)) { - Assert.True(false, $"Unable to match up expected message '{expectedPartialMessage}' in exception: {GetAllMessages(e)}"); + Assert.Fail($"Unable to match up expected message '{expectedPartialMessage}' in exception: {GetAllMessages(e)}"); } } @@ -89,4 +89,4 @@ public static string GetAllMessages(Exception e) } } } -} \ No newline at end of file +} diff --git a/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/HandlerTests.cs b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/HandlerTests.cs index e71acddcd..dacd01f87 100644 --- a/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/HandlerTests.cs +++ b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/HandlerTests.cs @@ -31,7 +31,7 @@ namespace Amazon.Lambda.RuntimeSupport.UnitTests { - [Collection("ResponseStreamFactory")] + [Collection("RuntimeSupportStateCheck")] public class HandlerTests { private const string AggregateExceptionTestMarker = "AggregateExceptionTesting"; @@ -285,6 +285,7 @@ await Record.ExceptionAsync(async () => private async Task InvokeAsync(LambdaBootstrap bootstrap, string dataIn, TestRuntimeApiClient testRuntimeApiClient) { testRuntimeApiClient.FunctionInput = dataIn != null ? Encoding.UTF8.GetBytes(dataIn) : new byte[0]; + testRuntimeApiClient.LastOutputStream = null; using (var cancellationTokenSource = new CancellationTokenSource()) { diff --git a/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/HandlerWrapperTests.cs b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/HandlerWrapperTests.cs index 88e93834f..fb4aae2e1 100644 --- a/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/HandlerWrapperTests.cs +++ b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/HandlerWrapperTests.cs @@ -23,6 +23,7 @@ namespace Amazon.Lambda.RuntimeSupport.UnitTests { + [Collection("RuntimeSupportStateCheck")] public class HandlerWrapperTests { private static readonly JsonSerializer Serializer = new JsonSerializer(); @@ -652,7 +653,7 @@ public async Task TestOutputStreamReuse(bool onDemand) var invocation1 = new InvocationRequest { InputStream = new MemoryStream(UTF8Encoding.UTF8.GetBytes("\"Hello\"")), - LambdaContext = new LambdaContext(_runtimeApiHeaders, _lambdaEnvironment, new Helpers.SimpleLoggerWriter(new SystemEnvironmentVariables())) + LambdaContext = new LambdaContext(_runtimeApiHeaders, _lambdaEnvironment, new Helpers.LogLevelLoggerWriter(new SystemEnvironmentVariables())) }; var invocationResponse1 = await handlerWrapper.Handler(invocation1); @@ -660,7 +661,7 @@ public async Task TestOutputStreamReuse(bool onDemand) var invocation2 = new InvocationRequest { InputStream = new MemoryStream(UTF8Encoding.UTF8.GetBytes("\"World\"")), - LambdaContext = new LambdaContext(_runtimeApiHeaders, _lambdaEnvironment, new Helpers.SimpleLoggerWriter(new SystemEnvironmentVariables())) + LambdaContext = new LambdaContext(_runtimeApiHeaders, _lambdaEnvironment, new Helpers.LogLevelLoggerWriter(new SystemEnvironmentVariables())) }; var invocationResponse2 = await handlerWrapper.Handler(invocation2); @@ -684,7 +685,7 @@ private async Task TestHandlerWrapper(HandlerWrapper handlerWrapper, byte[] inpu var invocation = new InvocationRequest { InputStream = new MemoryStream(input ?? new byte[0]), - LambdaContext = new LambdaContext(_runtimeApiHeaders, _lambdaEnvironment, new Helpers.SimpleLoggerWriter(new SystemEnvironmentVariables())) + LambdaContext = new LambdaContext(_runtimeApiHeaders, _lambdaEnvironment, new Helpers.LogLevelLoggerWriter(new SystemEnvironmentVariables())) }; var invocationResponse = await handlerWrapper.Handler(invocation); diff --git a/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/LambdaBootstrapTests.cs b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/LambdaBootstrapTests.cs index 76e924ac0..d2b1a1556 100644 --- a/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/LambdaBootstrapTests.cs +++ b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/LambdaBootstrapTests.cs @@ -31,7 +31,7 @@ namespace Amazon.Lambda.RuntimeSupport.UnitTests /// Tests to test LambdaBootstrap when it's constructed using its actual constructor. /// Tests of the static GetLambdaBootstrap methods can be found in LambdaBootstrapWrapperTests. /// - [Collection("ResponseStreamFactory")] + [Collection("RuntimeSupportStateCheck")] public class LambdaBootstrapTests { readonly TestHandler _testFunction; @@ -319,7 +319,7 @@ public async Task StreamingMode_HandlerCallsCreateStream_SendTaskAwaited() return new InvocationResponse(Stream.Null, false); }; - using (var bootstrap = new LambdaBootstrap(handler, null)) + using (var bootstrap = new LambdaBootstrap(handler, null, null, new TestEnvironmentVariables())) { bootstrap.Client = streamingClient; await bootstrap.InvokeOnceAsync(); @@ -346,7 +346,7 @@ public async Task BufferedMode_HandlerDoesNotCallCreateStream_UsesSendResponse() return new InvocationResponse(outputStream); }; - using (var bootstrap = new LambdaBootstrap(handler, null)) + using (var bootstrap = new LambdaBootstrap(handler, null, null, new TestEnvironmentVariables())) { bootstrap.Client = streamingClient; await bootstrap.InvokeOnceAsync(); @@ -374,7 +374,7 @@ public async Task MidstreamError_ExceptionAfterWrites_ReportsViaTrailers() throw new InvalidOperationException("midstream failure"); }; - using (var bootstrap = new LambdaBootstrap(handler, null)) + using (var bootstrap = new LambdaBootstrap(handler, null, null, new TestEnvironmentVariables())) { bootstrap.Client = streamingClient; await bootstrap.InvokeOnceAsync(); @@ -404,7 +404,7 @@ public async Task PreStreamError_ExceptionBeforeCreateStream_UsesStandardErrorRe throw new InvalidOperationException("pre-stream failure"); }; - using (var bootstrap = new LambdaBootstrap(handler, null)) + using (var bootstrap = new LambdaBootstrap(handler, null, null, new TestEnvironmentVariables())) { bootstrap.Client = streamingClient; await bootstrap.InvokeOnceAsync(); @@ -430,7 +430,7 @@ public async Task Cleanup_ResponseStreamFactoryStateCleared_AfterInvocation() return new InvocationResponse(Stream.Null, false); }; - using (var bootstrap = new LambdaBootstrap(handler, null)) + using (var bootstrap = new LambdaBootstrap(handler, null, null, new TestEnvironmentVariables())) { bootstrap.Client = streamingClient; await bootstrap.InvokeOnceAsync(); diff --git a/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/LambdaContextTests.cs b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/LambdaContextTests.cs index d8964b912..56cf83819 100644 --- a/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/LambdaContextTests.cs +++ b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/LambdaContextTests.cs @@ -31,7 +31,7 @@ public void RemainingTimeIsPositive() var runtimeApiHeaders = new RuntimeApiHeaders(headers); var lambdaEnvironment = new LambdaEnvironment(_environmentVariables); - var context = new LambdaContext(runtimeApiHeaders, lambdaEnvironment, new Helpers.SimpleLoggerWriter(new SystemEnvironmentVariables())); + var context = new LambdaContext(runtimeApiHeaders, lambdaEnvironment, new Helpers.LogLevelLoggerWriter(new SystemEnvironmentVariables())); Assert.True(context.RemainingTime >= TimeSpan.Zero, $"Remaining time is not a positive value: {context.RemainingTime}"); } @@ -49,7 +49,7 @@ public void RuntimeApiHeadersAddedToContext() var runtimeApiHeaders = new RuntimeApiHeaders(headers); var lambdaEnvironment = new LambdaEnvironment(_environmentVariables); - var context = new LambdaContext(runtimeApiHeaders, lambdaEnvironment, new Helpers.SimpleLoggerWriter(new SystemEnvironmentVariables())); + var context = new LambdaContext(runtimeApiHeaders, lambdaEnvironment, new Helpers.LogLevelLoggerWriter(new SystemEnvironmentVariables())); Assert.Equal("request-generated-id", context.AwsRequestId); Assert.Equal("my-function-arn", context.InvokedFunctionArn); diff --git a/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/LambdaResponseStreamingCoreTests.cs b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/LambdaResponseStreamingCoreTests.cs index 0d5c20c86..5da3d5e8b 100644 --- a/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/LambdaResponseStreamingCoreTests.cs +++ b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/LambdaResponseStreamingCoreTests.cs @@ -1,6 +1,5 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -#if NET8_0_OR_GREATER #pragma warning disable CA2252 using System; @@ -399,7 +398,7 @@ public void Dispose_DisposesInnerStream() // LambdaResponseStreamFactory tests // ───────────────────────────────────────────────────────────────────────────── - [Collection("ResponseStreamFactory")] + [Collection("RuntimeSupportStateCheck")] public class LambdaResponseStreamFactoryTests : IDisposable { @@ -555,4 +554,3 @@ public Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken c } } } -#endif diff --git a/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/LogMessageFormatterTests.cs b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/LogMessageFormatterTests.cs index 4cc5b57c5..98c83ad78 100644 --- a/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/LogMessageFormatterTests.cs +++ b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/LogMessageFormatterTests.cs @@ -47,7 +47,7 @@ public void ParseLogMessageWithOpenBracketAndNoClosing() var formatter = new JsonLogMessageFormatter(); var properties = formatter.ParseProperties("{hello} before { after"); - Assert.Equal(1, properties.Count); + Assert.Single(properties); Assert.Equal("hello", properties[0].Name); Assert.Equal(MessageProperty.Directive.Default, properties[0].FormatDirective); diff --git a/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/NativeAOTTests.cs b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/NativeAOTTests.cs index ae7f7aaf7..313abdbc1 100644 --- a/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/NativeAOTTests.cs +++ b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/NativeAOTTests.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Diagnostics; using System.Text; using System.Reflection; @@ -7,7 +7,6 @@ using Xunit.Abstractions; using System.IO; -#if NET8_0_OR_GREATER namespace Amazon.Lambda.RuntimeSupport.UnitTests { public class NativeAOTTests @@ -97,4 +96,3 @@ private string FindProject(string projectName) } } } -#endif \ No newline at end of file diff --git a/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/RawStreamingHttpClientTests.cs b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/RawStreamingHttpClientTests.cs index 57b94ce22..34af19450 100644 --- a/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/RawStreamingHttpClientTests.cs +++ b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/RawStreamingHttpClientTests.cs @@ -1,6 +1,5 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -#if NET8_0_OR_GREATER using System; using System.IO; @@ -492,4 +491,3 @@ public override Task FlushAsync(CancellationToken cancellationToken) } } } -#endif diff --git a/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/ResponseStreamFactoryTests.cs b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/ResponseStreamFactoryTests.cs index cc9a19af2..0b49eb27a 100644 --- a/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/ResponseStreamFactoryTests.cs +++ b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/ResponseStreamFactoryTests.cs @@ -21,7 +21,7 @@ namespace Amazon.Lambda.RuntimeSupport.UnitTests { - [Collection("ResponseStreamFactory")] + [Collection("RuntimeSupportStateCheck")] public class ResponseStreamFactoryTests : IDisposable { private const long MaxResponseSize = 20 * 1024 * 1024; diff --git a/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/RuntimeApiClientTests.cs b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/RuntimeApiClientTests.cs index 71102ddf1..a6ce7d892 100644 --- a/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/RuntimeApiClientTests.cs +++ b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/RuntimeApiClientTests.cs @@ -186,7 +186,6 @@ public async Task SendResponseAsync_BufferedResponse_ExcludesStreamingHeaders() // --- Argument validation --- -#if NET8_0_OR_GREATER [Fact] public async Task StartStreamingResponseAsync_NullRequestId_ThrowsArgumentNullException() { @@ -206,6 +205,5 @@ public async Task StartStreamingResponseAsync_NullResponseStream_ThrowsArgumentN await Assert.ThrowsAsync( () => client.StartStreamingResponseAsync("req-5", null, CancellationToken.None)); } -#endif } } diff --git a/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/SnapstartTests.cs b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/SnapstartTests.cs index aaedf943a..a10f8229c 100644 --- a/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/SnapstartTests.cs +++ b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/SnapstartTests.cs @@ -7,10 +7,10 @@ namespace Amazon.Lambda.RuntimeSupport.UnitTests; public class SnapstartTests { - TestHandler _testFunction; - TestInitializer _testInitializer; - TestRuntimeApiClient _testRuntimeApiClient; - TestEnvironmentVariables _environmentVariables; + readonly TestHandler _testFunction; + readonly TestInitializer _testInitializer; + readonly TestRuntimeApiClient _testRuntimeApiClient; + readonly TestEnvironmentVariables _environmentVariables; public SnapstartTests() { @@ -30,7 +30,7 @@ public SnapstartTests() } [Fact] - public async void VerifyRestoreNextIsCalledWhenSnapstartIsEnabled() + public async Task VerifyRestoreNextIsCalledWhenSnapstartIsEnabled() { using var bootstrap = new LambdaBootstrap(_testFunction.BaseHandlerAsync, _testInitializer.InitializeTrueAsync, configuration: new LambdaBootstrapConfiguration(false, true)); @@ -40,7 +40,7 @@ public async void VerifyRestoreNextIsCalledWhenSnapstartIsEnabled() } [Fact] - public async void VerifyRestoreNextIsNotCalledWhenSnapstartIsDisabled() + public async Task VerifyRestoreNextIsNotCalledWhenSnapstartIsDisabled() { using var bootstrap = new LambdaBootstrap(_testFunction.BaseHandlerAsync, _testInitializer.InitializeTrueAsync, configuration: new LambdaBootstrapConfiguration(false, false)); @@ -52,7 +52,7 @@ public async void VerifyRestoreNextIsNotCalledWhenSnapstartIsDisabled() [Fact] - public async void VerifyInitializeErrorIsCalledWhenExceptionInBeforeSnapshotCallables() + public async Task VerifyInitializeErrorIsCalledWhenExceptionInBeforeSnapshotCallables() { using var bootstrap = new LambdaBootstrap(_testFunction.BaseHandlerAsync, _testInitializer.InitializeTrueAsync, configuration: new LambdaBootstrapConfiguration(false, true)); @@ -65,7 +65,7 @@ public async void VerifyInitializeErrorIsCalledWhenExceptionInBeforeSnapshotCall } [Fact] - public async void VerifyRestoreErrorIsCalledWhenExceptionInAfterRestoreCallables() + public async Task VerifyRestoreErrorIsCalledWhenExceptionInAfterRestoreCallables() { using (var bootstrap = new LambdaBootstrap(_testFunction.BaseHandlerAsync, _testInitializer.InitializeTrueAsync, new LambdaBootstrapConfiguration(false, true))) @@ -77,4 +77,4 @@ public async void VerifyRestoreErrorIsCalledWhenExceptionInAfterRestoreCallables Assert.True(_testRuntimeApiClient.ReportRestoreErrorAsyncCalled); } } -} \ No newline at end of file +} diff --git a/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/StreamingE2EWithMoq.cs b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/StreamingE2EWithMoq.cs index f46c76f13..6f6d6492c 100644 --- a/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/StreamingE2EWithMoq.cs +++ b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/StreamingE2EWithMoq.cs @@ -26,15 +26,15 @@ namespace Amazon.Lambda.RuntimeSupport.UnitTests { - [CollectionDefinition("ResponseStreamFactory")] - public class ResponseStreamFactoryCollection { } + [CollectionDefinition("RuntimeSupportStateCheck")] + public class RuntimeSupportStateCheckCollection { } /// /// End-to-end integration tests for the true-streaming architecture. /// These tests exercise the full pipeline: LambdaBootstrap → ResponseStreamFactory → /// ResponseStream → captured HTTP output stream. /// - [Collection("ResponseStreamFactory")] + [Collection("RuntimeSupportStateCheck")] public class StreamingE2EWithMoq : IDisposable { public void Dispose() @@ -43,8 +43,6 @@ public void Dispose() ResponseStreamFactory.CleanupInvocation(isMultiConcurrency: true); } - // ─── Helpers ──────────────────────────────────────────────────────────────── - private static Dictionary> MakeHeaders(string requestId = "test-request-id") => new Dictionary> { @@ -70,6 +68,8 @@ private class CapturingStreamingRuntimeApiClient : RuntimeApiClient, IRuntimeApi public byte[] CapturedHttpBytes { get; private set; } public ResponseStream LastResponseStream { get; private set; } public Stream LastBufferedOutputStream { get; private set; } + public Action OnStreamingReady { get; set; } + public MemoryStream CapturedOutputStream { get; private set; } public new Amazon.Lambda.RuntimeSupport.Helpers.IConsoleLoggerWriter ConsoleLogger { get; } = new Helpers.LogLevelLoggerWriter(new SystemEnvironmentVariables()); @@ -93,7 +93,7 @@ public CapturingStreamingRuntimeApiClient( new RuntimeApiHeaders(_headers), new LambdaEnvironment(_envVars), new TestDateTimeHelper(), - new Helpers.SimpleLoggerWriter(_envVars)) + new Helpers.LogLevelLoggerWriter(_envVars)) }; } @@ -105,9 +105,11 @@ internal override async Task StartStreamingResponseAsync( // Use a real MemoryStream as the HTTP output stream so we capture actual bytes var captureStream = new MemoryStream(); + CapturedOutputStream = captureStream; await responseStream.SetHttpOutputStreamAsync(captureStream, cancellationToken); // Wait for the handler to finish writing (mirrors real RawStreamingHttpClient behavior) + OnStreamingReady?.Invoke(); await responseStream.WaitForCompletionAsync(cancellationToken); CapturedHttpBytes = captureStream.ToArray(); return new NoOpDisposable(); @@ -137,10 +139,8 @@ internal override async Task StartStreamingResponseAsync( public new Task ReportInitializationErrorAsync(string errorType, CancellationToken cancellationToken = default) => Task.CompletedTask; -#if NET8_0_OR_GREATER public new Task RestoreNextInvocationAsync(CancellationToken cancellationToken = default) => Task.CompletedTask; public new Task ReportRestoreErrorAsync(Exception exception, string errorType = null, CancellationToken cancellationToken = default) => Task.CompletedTask; -#endif } private static CapturingStreamingRuntimeApiClient CreateClient(string requestId = "test-request-id") @@ -148,7 +148,6 @@ private static CapturingStreamingRuntimeApiClient CreateClient(string requestId /// /// End-to-end: all data is transmitted correctly (content round-trip). - /// Requirements: 3.2, 4.3, 10.1 /// [Fact] public async Task Streaming_AllDataTransmitted_ContentRoundTrip() @@ -163,7 +162,7 @@ public async Task Streaming_AllDataTransmitted_ContentRoundTrip() return new InvocationResponse(Stream.Null, false); }; - using var bootstrap = new LambdaBootstrap(handler, null); + using var bootstrap = new LambdaBootstrap(handler, null, null, new TestEnvironmentVariables()); bootstrap.Client = client; await bootstrap.InvokeOnceAsync(); @@ -176,7 +175,6 @@ public async Task Streaming_AllDataTransmitted_ContentRoundTrip() /// /// End-to-end: stream is finalized (final chunk written, BytesWritten matches). - /// Requirements: 3.2, 4.3, 10.1 /// [Fact] public async Task Streaming_StreamFinalized_BytesWrittenMatchesPayload() @@ -191,7 +189,7 @@ public async Task Streaming_StreamFinalized_BytesWrittenMatchesPayload() return new InvocationResponse(Stream.Null, false); }; - using var bootstrap = new LambdaBootstrap(handler, null); + using var bootstrap = new LambdaBootstrap(handler, null, null, new TestEnvironmentVariables()); bootstrap.Client = client; await bootstrap.InvokeOnceAsync(); @@ -199,12 +197,9 @@ public async Task Streaming_StreamFinalized_BytesWrittenMatchesPayload() Assert.Equal(data.Length, client.LastResponseStream.BytesWritten); } - // ─── 10.2 End-to-end buffered response ────────────────────────────────────── - /// /// End-to-end: handler does NOT call CreateStream — response goes via buffered path. /// Verifies SendResponseAsync is called and streaming headers are absent. - /// Requirements: 1.5, 4.6, 9.4 /// [Fact] public async Task Buffered_HandlerDoesNotCallCreateStream_UsesSendResponsePath() @@ -218,7 +213,7 @@ public async Task Buffered_HandlerDoesNotCallCreateStream_UsesSendResponsePath() return new InvocationResponse(new MemoryStream(responseBody)); }; - using var bootstrap = new LambdaBootstrap(handler, null); + using var bootstrap = new LambdaBootstrap(handler, null, null, new TestEnvironmentVariables()); bootstrap.Client = client; await bootstrap.InvokeOnceAsync(); @@ -229,7 +224,6 @@ public async Task Buffered_HandlerDoesNotCallCreateStream_UsesSendResponsePath() /// /// End-to-end: buffered response body is transmitted correctly. - /// Requirements: 1.5, 4.6, 9.4 /// [Fact] public async Task Buffered_ResponseBodyTransmittedCorrectly() @@ -243,7 +237,7 @@ public async Task Buffered_ResponseBodyTransmittedCorrectly() return new InvocationResponse(new MemoryStream(responseBody)); }; - using var bootstrap = new LambdaBootstrap(handler, null); + using var bootstrap = new LambdaBootstrap(handler, null, null, new TestEnvironmentVariables()); bootstrap.Client = client; await bootstrap.InvokeOnceAsync(); @@ -254,46 +248,9 @@ public async Task Buffered_ResponseBodyTransmittedCorrectly() Assert.Equal(responseBody, received.ToArray()); } - /// - /// End-to-end: midstream error sets error state on ResponseStream with exception details. - /// In production, RawStreamingHttpClient reads this state and writes trailing headers. - /// Requirements: 5.2, 5.3 - /// - [Fact] - public async Task MidstreamError_SetsErrorStateWithExceptionDetails() - { - var client = CreateClient(); - const string errorMessage = "something went wrong mid-stream"; - - LambdaBootstrapHandler handler = async (invocation) => - { - var stream = ResponseStreamFactory.CreateStream(Array.Empty()); - await stream.WriteAsync(Encoding.UTF8.GetBytes("some data")); - throw new InvalidOperationException(errorMessage); - }; - - using var bootstrap = new LambdaBootstrap(handler, null); - bootstrap.Client = client; - await bootstrap.InvokeOnceAsync(); - - Assert.True(client.StartStreamingCalled); - Assert.NotNull(client.LastResponseStream); - Assert.True(client.LastResponseStream.HasError); - Assert.NotNull(client.LastResponseStream.ReportedError); - Assert.IsType(client.LastResponseStream.ReportedError); - Assert.Equal(errorMessage, client.LastResponseStream.ReportedError.Message); - - // Verify the handler's data was still captured before the error - var output = Encoding.UTF8.GetString(client.CapturedHttpBytes); - Assert.Contains("some data", output); - } - - // ─── 10.4 Multi-concurrency ────────────────────────────────────────────────── - /// /// Multi-concurrency: concurrent invocations use AsyncLocal for state isolation. /// Each invocation independently uses streaming or buffered mode without interference. - /// Requirements: 2.9, 6.5, 8.9 /// [Fact] public async Task MultiConcurrency_ConcurrentInvocations_StateIsolated() @@ -350,7 +307,6 @@ public async Task MultiConcurrency_ConcurrentInvocations_StateIsolated() /// /// Multi-concurrency: streaming and buffered invocations can run concurrently without interference. - /// Requirements: 2.9, 6.5, 8.9 /// [Fact] public async Task MultiConcurrency_StreamingAndBufferedMixedConcurrently_NoInterference() @@ -439,11 +395,8 @@ internal override async Task StartStreamingResponseAsync( } } - // ─── 10.5 Backward compatibility ──────────────────────────────────────────── - /// /// Backward compatibility: existing handler signatures (event + ILambdaContext) work without modification. - /// Requirements: 9.1, 9.2, 9.3 /// [Fact] public async Task BackwardCompat_ExistingHandlerSignature_WorksUnchanged() @@ -459,7 +412,7 @@ public async Task BackwardCompat_ExistingHandlerSignature_WorksUnchanged() return new InvocationResponse(new MemoryStream(Encoding.UTF8.GetBytes("classic response"))); }; - using var bootstrap = new LambdaBootstrap(handler, null); + using var bootstrap = new LambdaBootstrap(handler, null, null, new TestEnvironmentVariables()); bootstrap.Client = client; await bootstrap.InvokeOnceAsync(); @@ -470,7 +423,6 @@ public async Task BackwardCompat_ExistingHandlerSignature_WorksUnchanged() /// /// Backward compatibility: no regression in buffered response behavior — response body is correct. - /// Requirements: 9.4, 9.5 /// [Fact] public async Task BackwardCompat_BufferedResponse_NoRegression() @@ -484,7 +436,7 @@ public async Task BackwardCompat_BufferedResponse_NoRegression() return new InvocationResponse(new MemoryStream(expected)); }; - using var bootstrap = new LambdaBootstrap(handler, null); + using var bootstrap = new LambdaBootstrap(handler, null, null, new TestEnvironmentVariables()); bootstrap.Client = client; await bootstrap.InvokeOnceAsync(); @@ -497,7 +449,6 @@ public async Task BackwardCompat_BufferedResponse_NoRegression() /// /// Backward compatibility: handler that returns null OutputStream still works. - /// Requirements: 9.4 /// [Fact] public async Task BackwardCompat_NullOutputStream_HandledGracefully() @@ -510,7 +461,7 @@ public async Task BackwardCompat_NullOutputStream_HandledGracefully() return new InvocationResponse(Stream.Null, false); }; - using var bootstrap = new LambdaBootstrap(handler, null); + using var bootstrap = new LambdaBootstrap(handler, null, null, new TestEnvironmentVariables()); bootstrap.Client = client; // Should not throw @@ -521,7 +472,6 @@ public async Task BackwardCompat_NullOutputStream_HandledGracefully() /// /// Backward compatibility: handler that throws before CreateStream uses standard error path. - /// Requirements: 9.5 /// [Fact] public async Task BackwardCompat_HandlerThrows_StandardErrorReportingUsed() @@ -534,7 +484,7 @@ public async Task BackwardCompat_HandlerThrows_StandardErrorReportingUsed() throw new Exception("classic handler error"); }; - using var bootstrap = new LambdaBootstrap(handler, null); + using var bootstrap = new LambdaBootstrap(handler, null, null, new TestEnvironmentVariables()); bootstrap.Client = client; await bootstrap.InvokeOnceAsync(); diff --git a/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/TestHelpers/NoOpInternalRuntimeApiClient.cs b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/TestHelpers/NoOpInternalRuntimeApiClient.cs index 9fa0434cd..c73a0382c 100644 --- a/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/TestHelpers/NoOpInternalRuntimeApiClient.cs +++ b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/TestHelpers/NoOpInternalRuntimeApiClient.cs @@ -48,13 +48,11 @@ public Task> ErrorWithXRayCauseAsync( string errorJson, string xrayCause, CancellationToken cancellationToken) => Task.FromResult(EmptyStatusResponse); -#if NET8_0_OR_GREATER public Task> RestoreNextAsync(CancellationToken cancellationToken) => Task.FromResult(new SwaggerResponse(200, new Dictionary>(), Stream.Null)); public Task> RestoreErrorAsync( string lambda_Runtime_Function_Error_Type, string errorJson, CancellationToken cancellationToken) => Task.FromResult(EmptyStatusResponse); -#endif } } diff --git a/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/TestHelpers/TestMultiConcurrencyRuntimeApiClient.cs b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/TestHelpers/TestMultiConcurrencyRuntimeApiClient.cs index f7c85dd15..198c83170 100644 --- a/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/TestHelpers/TestMultiConcurrencyRuntimeApiClient.cs +++ b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/TestHelpers/TestMultiConcurrencyRuntimeApiClient.cs @@ -78,7 +78,7 @@ public async Task GetNextInvocationAsync(CancellationToken ca LambdaContext = new LambdaContext( new RuntimeApiHeaders(data.Headers), new LambdaEnvironment(_environmentVariables), - new TestDateTimeHelper(), new Helpers.SimpleLoggerWriter(_environmentVariables)) + new TestDateTimeHelper(), new Helpers.LogLevelLoggerWriter(_environmentVariables)) }; } diff --git a/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/TestHelpers/TestRuntimeApiClient.cs b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/TestHelpers/TestRuntimeApiClient.cs index 50bfa7254..b2ef549bc 100644 --- a/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/TestHelpers/TestRuntimeApiClient.cs +++ b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/TestHelpers/TestRuntimeApiClient.cs @@ -50,7 +50,7 @@ public TestRuntimeApiClient(IEnvironmentVariables environmentVariables, Dictiona public string LastTraceId { get; private set; } public byte[] FunctionInput { get; set; } - public Stream LastOutputStream { get; private set; } + public Stream LastOutputStream { get; internal set; } public Exception LastRecordedException { get; private set; } public void VerifyOutput(string expectedOutput) @@ -99,7 +99,7 @@ public Task GetNextInvocationAsync(CancellationToken cancella LambdaContext = new LambdaContext( new RuntimeApiHeaders(_headers), new LambdaEnvironment(_environmentVariables), - new TestDateTimeHelper(), new Helpers.SimpleLoggerWriter(_environmentVariables)) + new TestDateTimeHelper(), ConsoleLogger) }); } diff --git a/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/TestHelpers/TestStreamingRuntimeApiClient.cs b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/TestHelpers/TestStreamingRuntimeApiClient.cs index 1cd6fa09e..7b70c8071 100644 --- a/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/TestHelpers/TestStreamingRuntimeApiClient.cs +++ b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/TestHelpers/TestStreamingRuntimeApiClient.cs @@ -73,7 +73,7 @@ public TestStreamingRuntimeApiClient(IEnvironmentVariables environmentVariables, LambdaContext = new LambdaContext( new RuntimeApiHeaders(_headers), new LambdaEnvironment(_environmentVariables), - new TestDateTimeHelper(), new SimpleLoggerWriter(_environmentVariables)) + new TestDateTimeHelper(), ConsoleLogger) }; } @@ -123,13 +123,11 @@ internal override async Task StartStreamingResponseAsync( return new NoOpDisposable(); } -#if NET8_0_OR_GREATER public new Task RestoreNextInvocationAsync(CancellationToken cancellationToken = default) => Task.CompletedTask; public new Task ReportRestoreErrorAsync(Exception exception, String errorType = null, CancellationToken cancellationToken = default) => Task.CompletedTask; -#endif } /// diff --git a/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/AspNetCoreStreamingApiGatewayTest/Program.cs b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/AspNetCoreStreamingApiGatewayTest/Program.cs index fbebed76b..43f23a628 100644 --- a/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/AspNetCoreStreamingApiGatewayTest/Program.cs +++ b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/AspNetCoreStreamingApiGatewayTest/Program.cs @@ -61,37 +61,6 @@ return Results.Json(new { message = "Hello from streaming Lambda", timestamp = DateTime.UtcNow.ToString("o") }); }); -app.MapGet("/oncompleted-test", async (HttpContext context) => -{ - // Register an OnCompleted callback that writes a marker to a response header. - // Since headers are sent in the prelude before the body, we use a different approach: - // write a marker into the body from the OnCompleted callback via a shared flag. - var completedMarker = new CompletedMarker(); - context.Response.RegisterForDispose(completedMarker); - - context.Response.OnCompleted(async (state) => - { - var marker = (CompletedMarker)state; - marker.WasExecuted = true; - // Write to a static so the next request can verify it ran - CompletedMarkerStore.LastMarkerExecuted = true; - }, completedMarker); - - context.Response.ContentType = "text/plain"; - context.Response.StatusCode = 200; - - var stream = context.Response.BodyWriter.AsStream(); - using var writer = new StreamWriter(stream, leaveOpen: true); - await writer.WriteAsync("OnCompleted callback registered"); - await writer.FlushAsync(); -}); - -app.MapGet("/oncompleted-verify", (HttpContext context) => -{ - // Returns whether the OnCompleted callback from the previous request was executed - return Results.Json(new { onCompletedExecuted = CompletedMarkerStore.LastMarkerExecuted }); -}); - app.MapGet("/custom-headers", (HttpContext context) => { context.Response.StatusCode = 201; @@ -121,13 +90,3 @@ app.Run(); -class CompletedMarker : IDisposable -{ - public bool WasExecuted { get; set; } - public void Dispose() { } -} - -static class CompletedMarkerStore -{ - public static bool LastMarkerExecuted { get; set; } -} diff --git a/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/CustomRuntimeAspNetCoreMinimalApiCustomSerializerTest/Controllers/LoggerTestController.cs b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/CustomRuntimeAspNetCoreMinimalApiCustomSerializerTest/Controllers/LoggerTestController.cs index 86b482fff..5e8e6f7b2 100644 --- a/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/CustomRuntimeAspNetCoreMinimalApiCustomSerializerTest/Controllers/LoggerTestController.cs +++ b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/CustomRuntimeAspNetCoreMinimalApiCustomSerializerTest/Controllers/LoggerTestController.cs @@ -1,4 +1,4 @@ -using Amazon.Lambda.Core; +using Amazon.Lambda.Core; using Microsoft.AspNetCore.Mvc; namespace CustomRuntimeAspNetCoreMinimalApiCustomSerializerTest.Controllers @@ -10,7 +10,7 @@ public class LoggerTestController : ControllerBase [HttpGet()] public long Get() { - var lambdaContext = this.HttpContext.Items["LambdaContext"] as ILambdaContext; + var lambdaContext = HttpContext.Items["LambdaContext"] as ILambdaContext; const int maxLogs = 10000; long actualLogsWritten = 0; diff --git a/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/CustomRuntimeAspNetCoreMinimalApiCustomSerializerTest/CustomRuntimeAspNetCoreMinimalApiCustomSerializerTest.csproj b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/CustomRuntimeAspNetCoreMinimalApiCustomSerializerTest/CustomRuntimeAspNetCoreMinimalApiCustomSerializerTest.csproj index 849e21461..ab7cc9865 100644 --- a/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/CustomRuntimeAspNetCoreMinimalApiCustomSerializerTest/CustomRuntimeAspNetCoreMinimalApiCustomSerializerTest.csproj +++ b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/CustomRuntimeAspNetCoreMinimalApiCustomSerializerTest/CustomRuntimeAspNetCoreMinimalApiCustomSerializerTest.csproj @@ -1,10 +1,11 @@  - net8.0 + net10.0 enable enable - bootstrap + bootstrap + true @@ -16,7 +17,7 @@ - + diff --git a/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/CustomRuntimeAspNetCoreMinimalApiCustomSerializerTest/aws-lambda-tools-defaults.json b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/CustomRuntimeAspNetCoreMinimalApiCustomSerializerTest/aws-lambda-tools-defaults.json index 1502f4b16..7aebdc2ad 100644 --- a/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/CustomRuntimeAspNetCoreMinimalApiCustomSerializerTest/aws-lambda-tools-defaults.json +++ b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/CustomRuntimeAspNetCoreMinimalApiCustomSerializerTest/aws-lambda-tools-defaults.json @@ -1,9 +1,9 @@ -{ +{ "profile": "default", "region": "us-west-2", "configuration": "Release", "msbuild-parameters": "--self-contained true", - "function-runtime": "provided.al2", + "function-runtime": "provided.al2023", "function-memory-size": 256, "function-timeout": 30, "function-handler": "not-used", diff --git a/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/CustomRuntimeAspNetCoreMinimalApiTest/Controllers/LoggerTestController.cs b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/CustomRuntimeAspNetCoreMinimalApiTest/Controllers/LoggerTestController.cs index 558a25e7f..57378eaa9 100644 --- a/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/CustomRuntimeAspNetCoreMinimalApiTest/Controllers/LoggerTestController.cs +++ b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/CustomRuntimeAspNetCoreMinimalApiTest/Controllers/LoggerTestController.cs @@ -1,4 +1,4 @@ -using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc; using Amazon.Lambda.Core; namespace CustomRuntimeAspNetCoreMinimalApiTest.Controllers @@ -10,7 +10,7 @@ public class LoggerTestController : ControllerBase [HttpGet()] public long Get() { - var lambdaContext = this.HttpContext.Items["LambdaContext"] as ILambdaContext; + var lambdaContext = HttpContext.Items["LambdaContext"] as ILambdaContext; const int maxLogs = 10000; long actualLogsWritten = 0; diff --git a/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/CustomRuntimeAspNetCoreMinimalApiTest/CustomRuntimeAspNetCoreMinimalApiTest.csproj b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/CustomRuntimeAspNetCoreMinimalApiTest/CustomRuntimeAspNetCoreMinimalApiTest.csproj index 849e21461..67af2cee7 100644 --- a/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/CustomRuntimeAspNetCoreMinimalApiTest/CustomRuntimeAspNetCoreMinimalApiTest.csproj +++ b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/CustomRuntimeAspNetCoreMinimalApiTest/CustomRuntimeAspNetCoreMinimalApiTest.csproj @@ -1,10 +1,11 @@  - net8.0 + net10.0 enable enable - bootstrap + bootstrap + true @@ -16,7 +17,7 @@ - + diff --git a/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/CustomRuntimeAspNetCoreMinimalApiTest/aws-lambda-tools-defaults.json b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/CustomRuntimeAspNetCoreMinimalApiTest/aws-lambda-tools-defaults.json index ba42343b7..b1b12f46f 100644 --- a/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/CustomRuntimeAspNetCoreMinimalApiTest/aws-lambda-tools-defaults.json +++ b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/CustomRuntimeAspNetCoreMinimalApiTest/aws-lambda-tools-defaults.json @@ -1,11 +1,11 @@ -{ - "profile": "default", - "region": "us-west-2", - "configuration": "Release", - "msbuild-parameters": "--self-contained true", - "function-runtime": "provided.al2", - "function-memory-size": 256, - "function-timeout": 30, - "function-handler": "not-used", - "function-name": "CustomRuntimeAspNetCoreMinimalApiTest" +{ + "profile": "default", + "region": "us-west-2", + "configuration": "Release", + "msbuild-parameters": "--self-contained true", + "function-runtime": "provided.al2023", + "function-memory-size": 256, + "function-timeout": 30, + "function-handler": "not-used", + "function-name": "CustomRuntimeAspNetCoreMinimalApiTest" } diff --git a/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/CustomRuntimeFunctionTest/CustomRuntimeFunctionTest.csproj b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/CustomRuntimeFunctionTest/CustomRuntimeFunctionTest.csproj index e38db03d4..91deb26de 100644 --- a/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/CustomRuntimeFunctionTest/CustomRuntimeFunctionTest.csproj +++ b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/CustomRuntimeFunctionTest/CustomRuntimeFunctionTest.csproj @@ -2,9 +2,10 @@ Exe - net6.0;net8.0 + net10.0 IDE0060 + true @@ -20,8 +21,8 @@ - - - + + + diff --git a/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/CustomRuntimeFunctionTest/aws-lambda-tools-defaults.json b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/CustomRuntimeFunctionTest/aws-lambda-tools-defaults.json index 1e44f44ba..e48ac315f 100644 --- a/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/CustomRuntimeFunctionTest/aws-lambda-tools-defaults.json +++ b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/CustomRuntimeFunctionTest/aws-lambda-tools-defaults.json @@ -1,11 +1,11 @@ -{ - "profile": "default", - "region": "us-west-2", - "configuration": "Release", - "msbuild-parameters": "--self-contained true", - "function-runtime": "provided", - "function-memory-size": 256, - "function-timeout": 30, - "function-handler": "PingAsync", - "function-name": "CustomRuntimeFunctionTest" +{ + "profile": "default", + "region": "us-west-2", + "configuration": "Release", + "msbuild-parameters": "--self-contained true", + "function-runtime": "provided.al2023", + "function-memory-size": 256, + "function-timeout": 30, + "function-handler": "PingAsync", + "function-name": "CustomRuntimeFunctionTest" } diff --git a/Libraries/test/EventsTests.NET6/EventsTests.NET6.csproj b/Libraries/test/EventsTests.NET6/EventsTests.NET6.csproj deleted file mode 100644 index afae3cb55..000000000 --- a/Libraries/test/EventsTests.NET6/EventsTests.NET6.csproj +++ /dev/null @@ -1,74 +0,0 @@ - - - - net6.0 - EventsTests31 - true - EventsTests.NET6 - latest - - - - - - - PreserveNewest - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - - - - - - diff --git a/Libraries/test/EventsTests.NET6/SourceGeneratorSerializerTests.cs b/Libraries/test/EventsTests.NET6/SourceGeneratorSerializerTests.cs deleted file mode 100644 index 548556933..000000000 --- a/Libraries/test/EventsTests.NET6/SourceGeneratorSerializerTests.cs +++ /dev/null @@ -1,342 +0,0 @@ -using System.IO; -using System.Text.Json.Serialization; - -using Xunit; - -using Amazon.Lambda.Serialization.SystemTextJson; -using Amazon.Lambda.APIGatewayEvents; -using System; -using Amazon.Lambda.Core; -using System.Text; -using System.Text.Json; -using Amazon.Lambda.S3Events; -using Amazon.Lambda.CloudWatchEvents.BatchEvents; - -namespace EventsTests.NET6 -{ - [JsonSerializable(typeof(APIGatewayHttpApiV2ProxyRequest))] - [JsonSerializable(typeof(APIGatewayHttpApiV2ProxyResponse))] - internal partial class HttpApiJsonSerializationContext : JsonSerializerContext - { - - } - - [JsonSerializable(typeof(S3ObjectLambdaEvent))] - internal partial class S3ObjectLambdaSerializationContext : JsonSerializerContext - { - - } - - [JsonSerializable(typeof(BatchJobStateChangeEvent))] - internal partial class BatchJobStateChangeEventSerializationContext : JsonSerializerContext - { - - } - - public class SourceGeneratorSerializerTests - { - [Theory] - [InlineData(typeof(SourceGeneratorLambdaJsonSerializer))] - public void HttpApiV2Format(Type serializerType) - { - var serializer = Activator.CreateInstance(serializerType) as ILambdaSerializer; - using (var fileStream = LoadJsonTestFile("http-api-v2-request.json")) - { - var request = serializer.Deserialize(fileStream); - - Assert.Equal("2.0", request.Version); - Assert.Equal("$default", request.RouteKey); - Assert.Equal("/my/path", request.RawPath); - Assert.Equal("parameter1=value1¶meter1=value2¶meter2=value", request.RawQueryString); - - Assert.Equal(2, request.Cookies.Length); - Assert.Equal("cookie1", request.Cookies[0]); - Assert.Equal("cookie2", request.Cookies[1]); - - Assert.Equal(2, request.QueryStringParameters.Count); - Assert.Equal("value1,value2", request.QueryStringParameters["parameter1"]); - Assert.Equal("value", request.QueryStringParameters["parameter2"]); - - Assert.Equal("Hello from Lambda", request.Body); - Assert.True(request.IsBase64Encoded); - - Assert.Equal(2, request.StageVariables.Count); - Assert.Equal("value1", request.StageVariables["stageVariable1"]); - Assert.Equal("value2", request.StageVariables["stageVariable2"]); - - Assert.Equal(1, request.PathParameters.Count); - Assert.Equal("value1", request.PathParameters["parameter1"]); - - var rc = request.RequestContext; - Assert.NotNull(rc); - Assert.Equal("123456789012", rc.AccountId); - Assert.Equal("api-id", rc.ApiId); - Assert.Equal("id.execute-api.us-east-1.amazonaws.com", rc.DomainName); - Assert.Equal("domain-id", rc.DomainPrefix); - Assert.Equal("request-id", rc.RequestId); - Assert.Equal("route-id", rc.RouteId); - Assert.Equal("$default-route", rc.RouteKey); - Assert.Equal("$default-stage", rc.Stage); - Assert.Equal("12/Mar/2020:19:03:58 +0000", rc.Time); - Assert.Equal(1583348638390, rc.TimeEpoch); - - var clientCert = request.RequestContext.Authentication.ClientCert; - Assert.Equal("CERT_CONTENT", clientCert.ClientCertPem); - Assert.Equal("www.example.com", clientCert.SubjectDN); - Assert.Equal("Example issuer", clientCert.IssuerDN); - Assert.Equal("a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1", clientCert.SerialNumber); - - Assert.Equal("May 28 12:30:02 2019 GMT", clientCert.Validity.NotBefore); - Assert.Equal("Aug 5 09:36:04 2021 GMT", clientCert.Validity.NotAfter); - - var auth = rc.Authorizer; - Assert.NotNull(auth); - Assert.Equal(2, auth.Jwt.Claims.Count); - Assert.Equal("value1", auth.Jwt.Claims["claim1"]); - Assert.Equal("value2", auth.Jwt.Claims["claim2"]); - Assert.Equal(2, auth.Jwt.Scopes.Length); - Assert.Equal("scope1", auth.Jwt.Scopes[0]); - Assert.Equal("scope2", auth.Jwt.Scopes[1]); - - var http = rc.Http; - Assert.NotNull(http); - Assert.Equal("POST", http.Method); - Assert.Equal("/my/path", http.Path); - Assert.Equal("HTTP/1.1", http.Protocol); - Assert.Equal("IP", http.SourceIp); - Assert.Equal("agent", http.UserAgent); - } - } - - [Theory] - [InlineData(typeof(SourceGeneratorLambdaJsonSerializer))] - public void S3ObjectLambdaEventTest(Type serializerType) - { - var serializer = Activator.CreateInstance(serializerType) as ILambdaSerializer; - using (var fileStream = LoadJsonTestFile("s3-object-lambda-event.json")) - { - var s3Event = serializer.Deserialize(fileStream); - - Assert.Equal("requestId", s3Event.XAmzRequestId); - Assert.Equal("https://my-s3-ap-111122223333.s3-accesspoint.us-east-1.amazonaws.com/example?X-Amz-Security-Token=", s3Event.GetObjectContext.InputS3Url); - Assert.Equal("io-use1-001", s3Event.GetObjectContext.OutputRoute); - Assert.Equal("OutputToken", s3Event.GetObjectContext.OutputToken); - - Assert.Equal("arn:aws:s3-object-lambda:us-east-1:111122223333:accesspoint/example-object-lambda-ap", s3Event.Configuration.AccessPointArn); - Assert.Equal("arn:aws:s3:us-east-1:111122223333:accesspoint/example-ap", s3Event.Configuration.SupportingAccessPointArn); - Assert.Equal("{}", s3Event.Configuration.Payload); - - Assert.Equal("https://object-lambda-111122223333.s3-object-lambda.us-east-1.amazonaws.com/example", s3Event.UserRequest.Url); - Assert.Equal("object-lambda-111122223333.s3-object-lambda.us-east-1.amazonaws.com", s3Event.UserRequest.Headers["Host"]); - - Assert.Equal("AssumedRole", s3Event.UserIdentity.Type); - Assert.Equal("principalId", s3Event.UserIdentity.PrincipalId); - Assert.Equal("arn:aws:sts::111122223333:assumed-role/Admin/example", s3Event.UserIdentity.Arn); - Assert.Equal("111122223333", s3Event.UserIdentity.AccountId); - Assert.Equal("accessKeyId", s3Event.UserIdentity.AccessKeyId); - - Assert.Equal("false", s3Event.UserIdentity.SessionContext.Attributes.MfaAuthenticated); - Assert.Equal("Wed Mar 10 23:41:52 UTC 2021", s3Event.UserIdentity.SessionContext.Attributes.CreationDate); - - Assert.Equal("Role", s3Event.UserIdentity.SessionContext.SessionIssuer.Type); - Assert.Equal("principalId", s3Event.UserIdentity.SessionContext.SessionIssuer.PrincipalId); - Assert.Equal("arn:aws:iam::111122223333:role/Admin", s3Event.UserIdentity.SessionContext.SessionIssuer.Arn); - Assert.Equal("111122223333", s3Event.UserIdentity.SessionContext.SessionIssuer.AccountId); - Assert.Equal("Admin", s3Event.UserIdentity.SessionContext.SessionIssuer.UserName); - - Assert.Equal("1.00", s3Event.ProtocolVersion); - } - } - - [Theory] - [InlineData(typeof(SourceGeneratorLambdaJsonSerializer))] - - public void BatchJobStateChangeEventTest(Type serializerType) - { - var serializer = Activator.CreateInstance(serializerType) as ILambdaSerializer; - using (var fileStream = LoadJsonTestFile("batch-job-state-change-event.json")) - { - var jobStateChangeEvent = serializer.Deserialize(fileStream); - - Assert.Equal(jobStateChangeEvent.Version, "0"); - Assert.Equal(jobStateChangeEvent.Id, "c8f9c4b5-76e5-d76a-f980-7011e206042b"); - Assert.Equal(jobStateChangeEvent.DetailType, "Batch Job State Change"); - Assert.Equal(jobStateChangeEvent.Source, "aws.batch"); - Assert.Equal(jobStateChangeEvent.Account, "aws_account_id"); - Assert.Equal(jobStateChangeEvent.Time.ToUniversalTime(), DateTime.Parse("2017-10-23T17:56:03Z").ToUniversalTime()); - Assert.Equal(jobStateChangeEvent.Region, "us-east-1"); - Assert.Equal(jobStateChangeEvent.Resources.Count, 1); - Assert.Equal(jobStateChangeEvent.Resources[0], "arn:aws:batch:us-east-1:aws_account_id:job/4c7599ae-0a82-49aa-ba5a-4727fcce14a8"); - Assert.IsType(typeof(Job), jobStateChangeEvent.Detail); - Assert.Equal(jobStateChangeEvent.Detail.JobName, "event-test"); - Assert.Equal(jobStateChangeEvent.Detail.JobId, "4c7599ae-0a82-49aa-ba5a-4727fcce14a8"); - Assert.Equal(jobStateChangeEvent.Detail.JobQueue, "arn:aws:batch:us-east-1:aws_account_id:job-queue/HighPriority"); - Assert.Equal(jobStateChangeEvent.Detail.Status, "RUNNABLE"); - Assert.Equal(jobStateChangeEvent.Detail.Attempts.Count, 0); - Assert.Equal(jobStateChangeEvent.Detail.CreatedAt, 1508781340401); - Assert.Equal(jobStateChangeEvent.Detail.RetryStrategy.Attempts, 1); - Assert.Equal(jobStateChangeEvent.Detail.RetryStrategy.EvaluateOnExit.Count, 1); - Assert.Equal(jobStateChangeEvent.Detail.RetryStrategy.EvaluateOnExit[0].Action, "EXIT"); - Assert.Equal(jobStateChangeEvent.Detail.RetryStrategy.EvaluateOnExit[0].OnExitCode, "*"); - Assert.Equal(jobStateChangeEvent.Detail.RetryStrategy.EvaluateOnExit[0].OnReason, "*"); - Assert.Equal(jobStateChangeEvent.Detail.RetryStrategy.EvaluateOnExit[0].OnStatusReason, "*"); - Assert.Equal(jobStateChangeEvent.Detail.DependsOn.Count, 0); - Assert.Equal(jobStateChangeEvent.Detail.JobDefinition, "arn:aws:batch:us-east-1:aws_account_id:job-definition/first-run-job-definition:1"); - Assert.Equal(jobStateChangeEvent.Detail.Parameters.Count, 1); - Assert.Equal(jobStateChangeEvent.Detail.Parameters["test"], "abc"); - Assert.Equal(jobStateChangeEvent.Detail.Container.Image, "busybox"); - Assert.NotNull(jobStateChangeEvent.Detail.Container.ResourceRequirements); - Assert.Equal(jobStateChangeEvent.Detail.Container.ResourceRequirements.Count, 2); - Assert.Equal(jobStateChangeEvent.Detail.Container.ResourceRequirements[0].Type, "MEMORY"); - Assert.Equal(jobStateChangeEvent.Detail.Container.ResourceRequirements[0].Value, "2000"); - Assert.Equal(jobStateChangeEvent.Detail.Container.ResourceRequirements[1].Type, "VCPU"); - Assert.Equal(jobStateChangeEvent.Detail.Container.ResourceRequirements[1].Value, "2"); - Assert.Equal(jobStateChangeEvent.Detail.Container.Vcpus, 2); - Assert.Equal(jobStateChangeEvent.Detail.Container.Memory, 2000); - Assert.Equal(jobStateChangeEvent.Detail.Container.Command.Count, 2); - Assert.Equal(jobStateChangeEvent.Detail.Container.Command[0], "echo"); - Assert.Equal(jobStateChangeEvent.Detail.Container.Command[1], "'hello world'"); - Assert.Equal(jobStateChangeEvent.Detail.Container.Volumes.Count, 2); - Assert.Equal(jobStateChangeEvent.Detail.Container.Volumes[0].Name, "myhostsource"); - Assert.Equal(jobStateChangeEvent.Detail.Container.Volumes[0].Host.SourcePath, "/data"); - Assert.Equal(jobStateChangeEvent.Detail.Container.Volumes[1].Name, "efs"); - Assert.Equal(jobStateChangeEvent.Detail.Container.Volumes[1].EfsVolumeConfiguration.AuthorizationConfig.AccessPointId, "fsap-XXXXXXXXXXXXXXXXX"); - Assert.Equal(jobStateChangeEvent.Detail.Container.Volumes[1].EfsVolumeConfiguration.AuthorizationConfig.Iam, "ENABLED"); - Assert.Equal(jobStateChangeEvent.Detail.Container.Volumes[1].EfsVolumeConfiguration.FileSystemId, "fs-XXXXXXXXX"); - Assert.Equal(jobStateChangeEvent.Detail.Container.Volumes[1].EfsVolumeConfiguration.RootDirectory, "/"); - Assert.Equal(jobStateChangeEvent.Detail.Container.Volumes[1].EfsVolumeConfiguration.TransitEncryption, "ENABLED"); - Assert.Equal(jobStateChangeEvent.Detail.Container.Volumes[1].EfsVolumeConfiguration.TransitEncryptionPort, 12345); - Assert.NotNull(jobStateChangeEvent.Detail.Container.Environment); - Assert.Equal(jobStateChangeEvent.Detail.Container.Environment.Count, 1); - Assert.Equal(jobStateChangeEvent.Detail.Container.Environment[0].Name, "MANAGED_BY_AWS"); - Assert.Equal(jobStateChangeEvent.Detail.Container.Environment[0].Value, "STARTED_BY_STEP_FUNCTIONS"); - Assert.Equal(jobStateChangeEvent.Detail.Container.MountPoints.Count, 2); - Assert.Equal(jobStateChangeEvent.Detail.Container.MountPoints[0].ContainerPath, "/data"); - Assert.Equal(jobStateChangeEvent.Detail.Container.MountPoints[0].ReadOnly, true); - Assert.Equal(jobStateChangeEvent.Detail.Container.MountPoints[0].SourceVolume, "myhostsource"); - Assert.Equal(jobStateChangeEvent.Detail.Container.MountPoints[1].ContainerPath, "/mount/efs"); - Assert.Equal(jobStateChangeEvent.Detail.Container.MountPoints[1].SourceVolume, "efs"); - Assert.Equal(jobStateChangeEvent.Detail.Container.Ulimits.Count, 1); - Assert.Equal(jobStateChangeEvent.Detail.Container.Ulimits[0].HardLimit, 2048); - Assert.Equal(jobStateChangeEvent.Detail.Container.Ulimits[0].Name, "nofile"); - Assert.Equal(jobStateChangeEvent.Detail.Container.Ulimits[0].SoftLimit, 2048); - Assert.NotNull(jobStateChangeEvent.Detail.Container.LinuxParameters); - Assert.Equal(jobStateChangeEvent.Detail.Container.LinuxParameters.Devices.Count, 1); - Assert.Equal(jobStateChangeEvent.Detail.Container.LinuxParameters.Devices[0].ContainerPath, "/dev/sda"); - Assert.Equal(jobStateChangeEvent.Detail.Container.LinuxParameters.Devices[0].HostPath, "/dev/xvdc"); - Assert.Equal(jobStateChangeEvent.Detail.Container.LinuxParameters.Devices[0].Permissions.Count, 1); - Assert.Equal(jobStateChangeEvent.Detail.Container.LinuxParameters.Devices[0].Permissions[0], "MKNOD"); - Assert.Equal(jobStateChangeEvent.Detail.Container.LinuxParameters.InitProcessEnabled, true); - Assert.Equal(jobStateChangeEvent.Detail.Container.LinuxParameters.SharedMemorySize, 64); - Assert.Equal(jobStateChangeEvent.Detail.Container.LinuxParameters.MaxSwap, 1024); - Assert.Equal(jobStateChangeEvent.Detail.Container.LinuxParameters.Swappiness, 55); - Assert.Equal(jobStateChangeEvent.Detail.Container.LinuxParameters.Tmpfs.Count, 1); - Assert.Equal(jobStateChangeEvent.Detail.Container.LinuxParameters.Tmpfs[0].ContainerPath, "/run"); - Assert.Equal(jobStateChangeEvent.Detail.Container.LinuxParameters.Tmpfs[0].Size, 65536); - Assert.Equal(jobStateChangeEvent.Detail.Container.LinuxParameters.Tmpfs[0].MountOptions.Count, 2); - Assert.Equal(jobStateChangeEvent.Detail.Container.LinuxParameters.Tmpfs[0].MountOptions[0], "noexec"); - Assert.Equal(jobStateChangeEvent.Detail.Container.LinuxParameters.Tmpfs[0].MountOptions[1], "nosuid"); - Assert.NotNull(jobStateChangeEvent.Detail.Container.LogConfiguration); - Assert.Equal(jobStateChangeEvent.Detail.Container.LogConfiguration.LogDriver, "json-file"); - Assert.Equal(jobStateChangeEvent.Detail.Container.LogConfiguration.Options.Count, 2); - Assert.Equal(jobStateChangeEvent.Detail.Container.LogConfiguration.Options["max-size"], "10m"); - Assert.Equal(jobStateChangeEvent.Detail.Container.LogConfiguration.Options["max-file"], "3"); - Assert.Equal(jobStateChangeEvent.Detail.Container.LogConfiguration.SecretOptions.Count, 1); - Assert.Equal(jobStateChangeEvent.Detail.Container.LogConfiguration.SecretOptions[0].Name, "apikey"); - Assert.Equal(jobStateChangeEvent.Detail.Container.LogConfiguration.SecretOptions[0].ValueFrom, "ddApiKey"); - Assert.Equal(jobStateChangeEvent.Detail.Container.Secrets.Count, 1); - Assert.Equal(jobStateChangeEvent.Detail.Container.Secrets[0].Name, "DATABASE_PASSWORD"); - Assert.Equal(jobStateChangeEvent.Detail.Container.Secrets[0].ValueFrom, "arn:aws:ssm:us-east-1:awsExampleAccountID:parameter/awsExampleParameter"); - Assert.NotNull(jobStateChangeEvent.Detail.Container.NetworkConfiguration); - Assert.Equal(jobStateChangeEvent.Detail.Container.NetworkConfiguration.AssignPublicIp, "ENABLED"); - Assert.NotNull(jobStateChangeEvent.Detail.Container.FargatePlatformConfiguration); - Assert.Equal(jobStateChangeEvent.Detail.Container.FargatePlatformConfiguration.PlatformVersion, "LATEST"); - Assert.NotNull(jobStateChangeEvent.Detail.NodeProperties); - Assert.Equal(jobStateChangeEvent.Detail.NodeProperties.MainNode, 0); - Assert.Equal(jobStateChangeEvent.Detail.NodeProperties.NumNodes, 0); - Assert.Equal(jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties.Count, 1); - Assert.Equal(jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].TargetNodes, "0:1"); - Assert.NotNull(jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container); - Assert.Equal(jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.Image, "busybox"); - Assert.Equal(jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.ResourceRequirements.Count, 2); - Assert.Equal(jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.ResourceRequirements[0].Type, "MEMORY"); - Assert.Equal(jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.ResourceRequirements[0].Value, "2000"); - Assert.Equal(jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.ResourceRequirements[1].Type, "VCPU"); - Assert.Equal(jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.ResourceRequirements[1].Value, "2"); - Assert.Equal(jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.Vcpus, 2); - Assert.Equal(jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.Memory, 2000); - Assert.Equal(jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.Command.Count, 2); - Assert.Equal(jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.Command[0], "echo"); - Assert.Equal(jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.Command[1], "'hello world'"); - Assert.Equal(jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.Volumes.Count, 2); - Assert.Equal(jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.Volumes[0].Name, "myhostsource"); - Assert.Equal(jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.Volumes[0].Host.SourcePath, "/data"); - Assert.Equal(jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.Volumes[1].Name, "efs"); - Assert.Equal(jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.Volumes[1].EfsVolumeConfiguration.AuthorizationConfig.AccessPointId, "fsap-XXXXXXXXXXXXXXXXX"); - Assert.Equal(jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.Volumes[1].EfsVolumeConfiguration.AuthorizationConfig.Iam, "ENABLED"); - Assert.Equal(jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.Volumes[1].EfsVolumeConfiguration.FileSystemId, "fs-XXXXXXXXX"); - Assert.Equal(jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.Volumes[1].EfsVolumeConfiguration.RootDirectory, "/"); - Assert.Equal(jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.Volumes[1].EfsVolumeConfiguration.TransitEncryption, "ENABLED"); - Assert.Equal(jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.Volumes[1].EfsVolumeConfiguration.TransitEncryptionPort, 12345); - Assert.Equal(jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.Environment.Count, 1); - Assert.Equal(jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.Environment[0].Name, "MANAGED_BY_AWS"); - Assert.Equal(jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.Environment[0].Value, "STARTED_BY_STEP_FUNCTIONS"); - Assert.Equal(jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.MountPoints.Count, 2); - Assert.Equal(jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.MountPoints[0].ContainerPath, "/data"); - Assert.Equal(jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.MountPoints[0].ReadOnly, true); - Assert.Equal(jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.MountPoints[0].SourceVolume, "myhostsource"); - Assert.Equal(jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.MountPoints[1].ContainerPath, "/mount/efs"); - Assert.Equal(jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.MountPoints[1].SourceVolume, "efs"); - Assert.Equal(jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.Ulimits.Count, 1); - Assert.Equal(jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.Ulimits[0].HardLimit, 2048); - Assert.Equal(jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.Ulimits[0].Name, "nofile"); - Assert.Equal(jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.Ulimits[0].SoftLimit, 2048); - Assert.Equal(jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.ExecutionRoleArn, "arn:aws:iam::awsExampleAccountID:role/awsExampleRoleName"); - Assert.Equal(jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.InstanceType, "p3.2xlarge"); - Assert.Equal(jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.User, "testuser"); - Assert.Equal(jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.JobRoleArn, "arn:aws:iam::awsExampleAccountID:role/awsExampleRoleName"); - Assert.Equal(jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.LinuxParameters.Devices.Count, 1); - Assert.Equal(jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.LinuxParameters.Devices[0].HostPath, "/dev/xvdc"); - Assert.Equal(jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.LinuxParameters.Devices[0].ContainerPath, "/dev/sda"); - Assert.Equal(jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.LinuxParameters.Devices[0].Permissions.Count, 1); - Assert.Equal(jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.LinuxParameters.Devices[0].Permissions[0], "MKNOD"); - Assert.Equal(jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.LinuxParameters.InitProcessEnabled, true); - Assert.Equal(jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.LinuxParameters.SharedMemorySize, 64); - Assert.Equal(jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.LinuxParameters.MaxSwap, 1024); - Assert.Equal(jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.LinuxParameters.Swappiness, 55); - Assert.Equal(jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.LinuxParameters.Tmpfs.Count, 1); - Assert.Equal(jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.LinuxParameters.Tmpfs[0].ContainerPath, "/run"); - Assert.Equal(jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.LinuxParameters.Tmpfs[0].Size, 65536); - Assert.Equal(jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.LinuxParameters.Tmpfs[0].MountOptions.Count, 2); - Assert.Equal(jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.LinuxParameters.Tmpfs[0].MountOptions[0], "noexec"); - Assert.Equal(jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.LinuxParameters.Tmpfs[0].MountOptions[1], "nosuid"); - Assert.Equal(jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.LogConfiguration.LogDriver, "awslogs"); - Assert.Equal(jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.LogConfiguration.Options["awslogs-group"], "awslogs-wordpress"); - Assert.Equal(jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.LogConfiguration.Options["awslogs-stream-prefix"], "awslogs-example"); - Assert.Equal(jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.LogConfiguration.SecretOptions.Count, 1); - Assert.Equal(jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.LogConfiguration.SecretOptions[0].Name, "apikey"); - Assert.Equal(jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.LogConfiguration.SecretOptions[0].ValueFrom, "ddApiKey"); - Assert.Equal(jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.Secrets.Count, 1); - Assert.Equal(jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.Secrets[0].Name, "DATABASE_PASSWORD"); - Assert.Equal(jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.Secrets[0].ValueFrom, "arn:aws:ssm:us-east-1:awsExampleAccountID:parameter/awsExampleParameter"); - Assert.Equal(jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.NetworkConfiguration.AssignPublicIp, "DISABLED"); - Assert.Equal(jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.FargatePlatformConfiguration.PlatformVersion, "LATEST"); - Assert.Equal(jobStateChangeEvent.Detail.PropagateTags, true); - Assert.Equal(jobStateChangeEvent.Detail.Timeout.AttemptDurationSeconds, 90); - Assert.Equal(jobStateChangeEvent.Detail.Tags.Count, 3); - Assert.Equal(jobStateChangeEvent.Detail.Tags["Service"], "Batch"); - Assert.Equal(jobStateChangeEvent.Detail.Tags["Name"], "JobDefinitionTag"); - Assert.Equal(jobStateChangeEvent.Detail.Tags["Expected"], "MergeTag"); - Assert.Equal(jobStateChangeEvent.Detail.PlatformCapabilities.Count, 1); - Assert.Equal(jobStateChangeEvent.Detail.PlatformCapabilities[0], "FARGATE"); - } - } - - - public MemoryStream LoadJsonTestFile(string filename) - { - var json = File.ReadAllText(filename); - return new MemoryStream(UTF8Encoding.UTF8.GetBytes(json)); - } - } -} diff --git a/Libraries/test/EventsTests.NET8/EventsTests.NET8.csproj b/Libraries/test/EventsTests.NET8/EventsTests.NET8.csproj index a004ee0e9..b6f09235b 100644 --- a/Libraries/test/EventsTests.NET8/EventsTests.NET8.csproj +++ b/Libraries/test/EventsTests.NET8/EventsTests.NET8.csproj @@ -14,7 +14,6 @@ PreserveNewest - diff --git a/Libraries/test/EventsTests.NET8/SourceGeneratorSerializerTests.cs b/Libraries/test/EventsTests.NET8/SourceGeneratorSerializerTests.cs new file mode 100644 index 000000000..21f268eb8 --- /dev/null +++ b/Libraries/test/EventsTests.NET8/SourceGeneratorSerializerTests.cs @@ -0,0 +1,342 @@ +using System.IO; +using System.Text.Json.Serialization; + +using Xunit; + +using Amazon.Lambda.Serialization.SystemTextJson; +using Amazon.Lambda.APIGatewayEvents; +using System; +using Amazon.Lambda.Core; +using System.Text; +using System.Text.Json; +using Amazon.Lambda.S3Events; +using Amazon.Lambda.CloudWatchEvents.BatchEvents; + +namespace EventsTests.NET8 +{ + [JsonSerializable(typeof(APIGatewayHttpApiV2ProxyRequest))] + [JsonSerializable(typeof(APIGatewayHttpApiV2ProxyResponse))] + internal partial class HttpApiJsonSerializationContext : JsonSerializerContext + { + + } + + [JsonSerializable(typeof(S3ObjectLambdaEvent))] + internal partial class S3ObjectLambdaSerializationContext : JsonSerializerContext + { + + } + + [JsonSerializable(typeof(BatchJobStateChangeEvent))] + internal partial class BatchJobStateChangeEventSerializationContext : JsonSerializerContext + { + + } + + public class SourceGeneratorSerializerTests + { + [Theory] + [InlineData(typeof(SourceGeneratorLambdaJsonSerializer))] + public void HttpApiV2Format(Type serializerType) + { + var serializer = Activator.CreateInstance(serializerType) as ILambdaSerializer; + using (var fileStream = LoadJsonTestFile("http-api-v2-request.json")) + { + var request = serializer.Deserialize(fileStream); + + Assert.Equal("2.0", request.Version); + Assert.Equal("$default", request.RouteKey); + Assert.Equal("/my/path", request.RawPath); + Assert.Equal("parameter1=value1¶meter1=value2¶meter2=value", request.RawQueryString); + + Assert.Equal(2, request.Cookies.Length); + Assert.Equal("cookie1", request.Cookies[0]); + Assert.Equal("cookie2", request.Cookies[1]); + + Assert.Equal(2, request.QueryStringParameters.Count); + Assert.Equal("value1,value2", request.QueryStringParameters["parameter1"]); + Assert.Equal("value", request.QueryStringParameters["parameter2"]); + + Assert.Equal("Hello from Lambda", request.Body); + Assert.True(request.IsBase64Encoded); + + Assert.Equal(2, request.StageVariables.Count); + Assert.Equal("value1", request.StageVariables["stageVariable1"]); + Assert.Equal("value2", request.StageVariables["stageVariable2"]); + + Assert.Single(request.PathParameters); + Assert.Equal("value1", request.PathParameters["parameter1"]); + + var rc = request.RequestContext; + Assert.NotNull(rc); + Assert.Equal("123456789012", rc.AccountId); + Assert.Equal("api-id", rc.ApiId); + Assert.Equal("id.execute-api.us-east-1.amazonaws.com", rc.DomainName); + Assert.Equal("domain-id", rc.DomainPrefix); + Assert.Equal("request-id", rc.RequestId); + Assert.Equal("route-id", rc.RouteId); + Assert.Equal("$default-route", rc.RouteKey); + Assert.Equal("$default-stage", rc.Stage); + Assert.Equal("12/Mar/2020:19:03:58 +0000", rc.Time); + Assert.Equal(1583348638390, rc.TimeEpoch); + + var clientCert = request.RequestContext.Authentication.ClientCert; + Assert.Equal("CERT_CONTENT", clientCert.ClientCertPem); + Assert.Equal("www.example.com", clientCert.SubjectDN); + Assert.Equal("Example issuer", clientCert.IssuerDN); + Assert.Equal("a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1", clientCert.SerialNumber); + + Assert.Equal("May 28 12:30:02 2019 GMT", clientCert.Validity.NotBefore); + Assert.Equal("Aug 5 09:36:04 2021 GMT", clientCert.Validity.NotAfter); + + var auth = rc.Authorizer; + Assert.NotNull(auth); + Assert.Equal(2, auth.Jwt.Claims.Count); + Assert.Equal("value1", auth.Jwt.Claims["claim1"]); + Assert.Equal("value2", auth.Jwt.Claims["claim2"]); + Assert.Equal(2, auth.Jwt.Scopes.Length); + Assert.Equal("scope1", auth.Jwt.Scopes[0]); + Assert.Equal("scope2", auth.Jwt.Scopes[1]); + + var http = rc.Http; + Assert.NotNull(http); + Assert.Equal("POST", http.Method); + Assert.Equal("/my/path", http.Path); + Assert.Equal("HTTP/1.1", http.Protocol); + Assert.Equal("IP", http.SourceIp); + Assert.Equal("agent", http.UserAgent); + } + } + + [Theory] + [InlineData(typeof(SourceGeneratorLambdaJsonSerializer))] + public void S3ObjectLambdaEventTest(Type serializerType) + { + var serializer = Activator.CreateInstance(serializerType) as ILambdaSerializer; + using (var fileStream = LoadJsonTestFile("s3-object-lambda-event.json")) + { + var s3Event = serializer.Deserialize(fileStream); + + Assert.Equal("requestId", s3Event.XAmzRequestId); + Assert.Equal("https://my-s3-ap-111122223333.s3-accesspoint.us-east-1.amazonaws.com/example?X-Amz-Security-Token=", s3Event.GetObjectContext.InputS3Url); + Assert.Equal("io-use1-001", s3Event.GetObjectContext.OutputRoute); + Assert.Equal("OutputToken", s3Event.GetObjectContext.OutputToken); + + Assert.Equal("arn:aws:s3-object-lambda:us-east-1:111122223333:accesspoint/example-object-lambda-ap", s3Event.Configuration.AccessPointArn); + Assert.Equal("arn:aws:s3:us-east-1:111122223333:accesspoint/example-ap", s3Event.Configuration.SupportingAccessPointArn); + Assert.Equal("{}", s3Event.Configuration.Payload); + + Assert.Equal("https://object-lambda-111122223333.s3-object-lambda.us-east-1.amazonaws.com/example", s3Event.UserRequest.Url); + Assert.Equal("object-lambda-111122223333.s3-object-lambda.us-east-1.amazonaws.com", s3Event.UserRequest.Headers["Host"]); + + Assert.Equal("AssumedRole", s3Event.UserIdentity.Type); + Assert.Equal("principalId", s3Event.UserIdentity.PrincipalId); + Assert.Equal("arn:aws:sts::111122223333:assumed-role/Admin/example", s3Event.UserIdentity.Arn); + Assert.Equal("111122223333", s3Event.UserIdentity.AccountId); + Assert.Equal("accessKeyId", s3Event.UserIdentity.AccessKeyId); + + Assert.Equal("false", s3Event.UserIdentity.SessionContext.Attributes.MfaAuthenticated); + Assert.Equal("Wed Mar 10 23:41:52 UTC 2021", s3Event.UserIdentity.SessionContext.Attributes.CreationDate); + + Assert.Equal("Role", s3Event.UserIdentity.SessionContext.SessionIssuer.Type); + Assert.Equal("principalId", s3Event.UserIdentity.SessionContext.SessionIssuer.PrincipalId); + Assert.Equal("arn:aws:iam::111122223333:role/Admin", s3Event.UserIdentity.SessionContext.SessionIssuer.Arn); + Assert.Equal("111122223333", s3Event.UserIdentity.SessionContext.SessionIssuer.AccountId); + Assert.Equal("Admin", s3Event.UserIdentity.SessionContext.SessionIssuer.UserName); + + Assert.Equal("1.00", s3Event.ProtocolVersion); + } + } + + [Theory] + [InlineData(typeof(SourceGeneratorLambdaJsonSerializer))] + + public void BatchJobStateChangeEventTest(Type serializerType) + { + var serializer = Activator.CreateInstance(serializerType) as ILambdaSerializer; + using (var fileStream = LoadJsonTestFile("batch-job-state-change-event.json")) + { + var jobStateChangeEvent = serializer.Deserialize(fileStream); + + Assert.Equal("0", jobStateChangeEvent.Version); + Assert.Equal("c8f9c4b5-76e5-d76a-f980-7011e206042b", jobStateChangeEvent.Id); + Assert.Equal("Batch Job State Change", jobStateChangeEvent.DetailType); + Assert.Equal("aws.batch", jobStateChangeEvent.Source); + Assert.Equal("aws_account_id", jobStateChangeEvent.Account); + Assert.Equal(DateTime.Parse("2017-10-23T17:56:03Z").ToUniversalTime(), jobStateChangeEvent.Time.ToUniversalTime()); + Assert.Equal("us-east-1", jobStateChangeEvent.Region); + Assert.Single(jobStateChangeEvent.Resources); + Assert.Equal("arn:aws:batch:us-east-1:aws_account_id:job/4c7599ae-0a82-49aa-ba5a-4727fcce14a8", jobStateChangeEvent.Resources[0]); + Assert.IsType(jobStateChangeEvent.Detail); + Assert.Equal("event-test", jobStateChangeEvent.Detail.JobName); + Assert.Equal("4c7599ae-0a82-49aa-ba5a-4727fcce14a8", jobStateChangeEvent.Detail.JobId); + Assert.Equal("arn:aws:batch:us-east-1:aws_account_id:job-queue/HighPriority", jobStateChangeEvent.Detail.JobQueue); + Assert.Equal("RUNNABLE", jobStateChangeEvent.Detail.Status); + Assert.Empty(jobStateChangeEvent.Detail.Attempts); + Assert.Equal(1508781340401, jobStateChangeEvent.Detail.CreatedAt); + Assert.Equal(1, jobStateChangeEvent.Detail.RetryStrategy.Attempts); + Assert.Single(jobStateChangeEvent.Detail.RetryStrategy.EvaluateOnExit); + Assert.Equal("EXIT", jobStateChangeEvent.Detail.RetryStrategy.EvaluateOnExit[0].Action); + Assert.Equal("*", jobStateChangeEvent.Detail.RetryStrategy.EvaluateOnExit[0].OnExitCode); + Assert.Equal("*", jobStateChangeEvent.Detail.RetryStrategy.EvaluateOnExit[0].OnReason); + Assert.Equal("*", jobStateChangeEvent.Detail.RetryStrategy.EvaluateOnExit[0].OnStatusReason); + Assert.Empty(jobStateChangeEvent.Detail.DependsOn); + Assert.Equal("arn:aws:batch:us-east-1:aws_account_id:job-definition/first-run-job-definition:1", jobStateChangeEvent.Detail.JobDefinition); + Assert.Single(jobStateChangeEvent.Detail.Parameters); + Assert.Equal("abc", jobStateChangeEvent.Detail.Parameters["test"]); + Assert.Equal("busybox", jobStateChangeEvent.Detail.Container.Image); + Assert.NotNull(jobStateChangeEvent.Detail.Container.ResourceRequirements); + Assert.Equal(2, jobStateChangeEvent.Detail.Container.ResourceRequirements.Count); + Assert.Equal("MEMORY", jobStateChangeEvent.Detail.Container.ResourceRequirements[0].Type); + Assert.Equal("2000", jobStateChangeEvent.Detail.Container.ResourceRequirements[0].Value); + Assert.Equal("VCPU", jobStateChangeEvent.Detail.Container.ResourceRequirements[1].Type); + Assert.Equal("2", jobStateChangeEvent.Detail.Container.ResourceRequirements[1].Value); + Assert.Equal(2, jobStateChangeEvent.Detail.Container.Vcpus); + Assert.Equal(2000, jobStateChangeEvent.Detail.Container.Memory); + Assert.Equal(2, jobStateChangeEvent.Detail.Container.Command.Count); + Assert.Equal("echo", jobStateChangeEvent.Detail.Container.Command[0]); + Assert.Equal("'hello world'", jobStateChangeEvent.Detail.Container.Command[1]); + Assert.Equal(2, jobStateChangeEvent.Detail.Container.Volumes.Count); + Assert.Equal("myhostsource", jobStateChangeEvent.Detail.Container.Volumes[0].Name); + Assert.Equal("/data", jobStateChangeEvent.Detail.Container.Volumes[0].Host.SourcePath); + Assert.Equal("efs", jobStateChangeEvent.Detail.Container.Volumes[1].Name); + Assert.Equal("fsap-XXXXXXXXXXXXXXXXX", jobStateChangeEvent.Detail.Container.Volumes[1].EfsVolumeConfiguration.AuthorizationConfig.AccessPointId); + Assert.Equal("ENABLED", jobStateChangeEvent.Detail.Container.Volumes[1].EfsVolumeConfiguration.AuthorizationConfig.Iam); + Assert.Equal("fs-XXXXXXXXX", jobStateChangeEvent.Detail.Container.Volumes[1].EfsVolumeConfiguration.FileSystemId); + Assert.Equal("/", jobStateChangeEvent.Detail.Container.Volumes[1].EfsVolumeConfiguration.RootDirectory); + Assert.Equal("ENABLED", jobStateChangeEvent.Detail.Container.Volumes[1].EfsVolumeConfiguration.TransitEncryption); + Assert.Equal(12345, jobStateChangeEvent.Detail.Container.Volumes[1].EfsVolumeConfiguration.TransitEncryptionPort); + Assert.NotNull(jobStateChangeEvent.Detail.Container.Environment); + Assert.Single(jobStateChangeEvent.Detail.Container.Environment); + Assert.Equal("MANAGED_BY_AWS", jobStateChangeEvent.Detail.Container.Environment[0].Name); + Assert.Equal("STARTED_BY_STEP_FUNCTIONS", jobStateChangeEvent.Detail.Container.Environment[0].Value); + Assert.Equal(2, jobStateChangeEvent.Detail.Container.MountPoints.Count); + Assert.Equal("/data", jobStateChangeEvent.Detail.Container.MountPoints[0].ContainerPath); + Assert.True(jobStateChangeEvent.Detail.Container.MountPoints[0].ReadOnly); + Assert.Equal("myhostsource", jobStateChangeEvent.Detail.Container.MountPoints[0].SourceVolume); + Assert.Equal("/mount/efs", jobStateChangeEvent.Detail.Container.MountPoints[1].ContainerPath); + Assert.Equal("efs", jobStateChangeEvent.Detail.Container.MountPoints[1].SourceVolume); + Assert.Single(jobStateChangeEvent.Detail.Container.Ulimits); + Assert.Equal(2048, jobStateChangeEvent.Detail.Container.Ulimits[0].HardLimit); + Assert.Equal("nofile", jobStateChangeEvent.Detail.Container.Ulimits[0].Name); + Assert.Equal(2048, jobStateChangeEvent.Detail.Container.Ulimits[0].SoftLimit); + Assert.NotNull(jobStateChangeEvent.Detail.Container.LinuxParameters); + Assert.Single(jobStateChangeEvent.Detail.Container.LinuxParameters.Devices); + Assert.Equal("/dev/sda", jobStateChangeEvent.Detail.Container.LinuxParameters.Devices[0].ContainerPath); + Assert.Equal("/dev/xvdc", jobStateChangeEvent.Detail.Container.LinuxParameters.Devices[0].HostPath); + Assert.Single(jobStateChangeEvent.Detail.Container.LinuxParameters.Devices[0].Permissions); + Assert.Equal("MKNOD", jobStateChangeEvent.Detail.Container.LinuxParameters.Devices[0].Permissions[0]); + Assert.True(jobStateChangeEvent.Detail.Container.LinuxParameters.InitProcessEnabled); + Assert.Equal(64, jobStateChangeEvent.Detail.Container.LinuxParameters.SharedMemorySize); + Assert.Equal(1024, jobStateChangeEvent.Detail.Container.LinuxParameters.MaxSwap); + Assert.Equal(55, jobStateChangeEvent.Detail.Container.LinuxParameters.Swappiness); + Assert.Single(jobStateChangeEvent.Detail.Container.LinuxParameters.Tmpfs); + Assert.Equal("/run", jobStateChangeEvent.Detail.Container.LinuxParameters.Tmpfs[0].ContainerPath); + Assert.Equal(65536, jobStateChangeEvent.Detail.Container.LinuxParameters.Tmpfs[0].Size); + Assert.Equal(2, jobStateChangeEvent.Detail.Container.LinuxParameters.Tmpfs[0].MountOptions.Count); + Assert.Equal("noexec", jobStateChangeEvent.Detail.Container.LinuxParameters.Tmpfs[0].MountOptions[0]); + Assert.Equal("nosuid", jobStateChangeEvent.Detail.Container.LinuxParameters.Tmpfs[0].MountOptions[1]); + Assert.NotNull(jobStateChangeEvent.Detail.Container.LogConfiguration); + Assert.Equal("json-file", jobStateChangeEvent.Detail.Container.LogConfiguration.LogDriver); + Assert.Equal(2, jobStateChangeEvent.Detail.Container.LogConfiguration.Options.Count); + Assert.Equal("10m", jobStateChangeEvent.Detail.Container.LogConfiguration.Options["max-size"]); + Assert.Equal("3", jobStateChangeEvent.Detail.Container.LogConfiguration.Options["max-file"]); + Assert.Single(jobStateChangeEvent.Detail.Container.LogConfiguration.SecretOptions); + Assert.Equal("apikey", jobStateChangeEvent.Detail.Container.LogConfiguration.SecretOptions[0].Name); + Assert.Equal("ddApiKey", jobStateChangeEvent.Detail.Container.LogConfiguration.SecretOptions[0].ValueFrom); + Assert.Single(jobStateChangeEvent.Detail.Container.Secrets); + Assert.Equal("DATABASE_PASSWORD", jobStateChangeEvent.Detail.Container.Secrets[0].Name); + Assert.Equal("arn:aws:ssm:us-east-1:awsExampleAccountID:parameter/awsExampleParameter", jobStateChangeEvent.Detail.Container.Secrets[0].ValueFrom); + Assert.NotNull(jobStateChangeEvent.Detail.Container.NetworkConfiguration); + Assert.Equal("ENABLED", jobStateChangeEvent.Detail.Container.NetworkConfiguration.AssignPublicIp); + Assert.NotNull(jobStateChangeEvent.Detail.Container.FargatePlatformConfiguration); + Assert.Equal("LATEST", jobStateChangeEvent.Detail.Container.FargatePlatformConfiguration.PlatformVersion); + Assert.NotNull(jobStateChangeEvent.Detail.NodeProperties); + Assert.Equal(0, jobStateChangeEvent.Detail.NodeProperties.MainNode); + Assert.Equal(0, jobStateChangeEvent.Detail.NodeProperties.NumNodes); + Assert.Single(jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties); + Assert.Equal("0:1", jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].TargetNodes); + Assert.NotNull(jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container); + Assert.Equal("busybox", jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.Image); + Assert.Equal(2, jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.ResourceRequirements.Count); + Assert.Equal("MEMORY", jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.ResourceRequirements[0].Type); + Assert.Equal("2000", jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.ResourceRequirements[0].Value); + Assert.Equal("VCPU", jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.ResourceRequirements[1].Type); + Assert.Equal("2", jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.ResourceRequirements[1].Value); + Assert.Equal(2, jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.Vcpus); + Assert.Equal(2000, jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.Memory); + Assert.Equal(2, jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.Command.Count); + Assert.Equal("echo", jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.Command[0]); + Assert.Equal("'hello world'", jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.Command[1]); + Assert.Equal(2, jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.Volumes.Count); + Assert.Equal("myhostsource", jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.Volumes[0].Name); + Assert.Equal("/data", jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.Volumes[0].Host.SourcePath); + Assert.Equal("efs", jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.Volumes[1].Name); + Assert.Equal("fsap-XXXXXXXXXXXXXXXXX", jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.Volumes[1].EfsVolumeConfiguration.AuthorizationConfig.AccessPointId); + Assert.Equal("ENABLED", jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.Volumes[1].EfsVolumeConfiguration.AuthorizationConfig.Iam); + Assert.Equal("fs-XXXXXXXXX", jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.Volumes[1].EfsVolumeConfiguration.FileSystemId); + Assert.Equal("/", jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.Volumes[1].EfsVolumeConfiguration.RootDirectory); + Assert.Equal("ENABLED", jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.Volumes[1].EfsVolumeConfiguration.TransitEncryption); + Assert.Equal(12345, jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.Volumes[1].EfsVolumeConfiguration.TransitEncryptionPort); + Assert.Single(jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.Environment); + Assert.Equal("MANAGED_BY_AWS", jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.Environment[0].Name); + Assert.Equal("STARTED_BY_STEP_FUNCTIONS", jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.Environment[0].Value); + Assert.Equal(2, jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.MountPoints.Count); + Assert.Equal("/data", jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.MountPoints[0].ContainerPath); + Assert.True(jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.MountPoints[0].ReadOnly); + Assert.Equal("myhostsource", jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.MountPoints[0].SourceVolume); + Assert.Equal("/mount/efs", jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.MountPoints[1].ContainerPath); + Assert.Equal("efs", jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.MountPoints[1].SourceVolume); + Assert.Single(jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.Ulimits); + Assert.Equal(2048, jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.Ulimits[0].HardLimit); + Assert.Equal("nofile", jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.Ulimits[0].Name); + Assert.Equal(2048, jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.Ulimits[0].SoftLimit); + Assert.Equal("arn:aws:iam::awsExampleAccountID:role/awsExampleRoleName", jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.ExecutionRoleArn); + Assert.Equal("p3.2xlarge", jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.InstanceType); + Assert.Equal("testuser", jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.User); + Assert.Equal("arn:aws:iam::awsExampleAccountID:role/awsExampleRoleName", jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.JobRoleArn); + Assert.Single(jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.LinuxParameters.Devices); + Assert.Equal("/dev/xvdc", jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.LinuxParameters.Devices[0].HostPath); + Assert.Equal("/dev/sda", jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.LinuxParameters.Devices[0].ContainerPath); + Assert.Single(jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.LinuxParameters.Devices[0].Permissions); + Assert.Equal("MKNOD", jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.LinuxParameters.Devices[0].Permissions[0]); + Assert.True(jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.LinuxParameters.InitProcessEnabled); + Assert.Equal(64, jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.LinuxParameters.SharedMemorySize); + Assert.Equal(1024, jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.LinuxParameters.MaxSwap); + Assert.Equal(55, jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.LinuxParameters.Swappiness); + Assert.Single(jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.LinuxParameters.Tmpfs); + Assert.Equal("/run", jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.LinuxParameters.Tmpfs[0].ContainerPath); + Assert.Equal(65536, jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.LinuxParameters.Tmpfs[0].Size); + Assert.Equal(2, jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.LinuxParameters.Tmpfs[0].MountOptions.Count); + Assert.Equal("noexec", jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.LinuxParameters.Tmpfs[0].MountOptions[0]); + Assert.Equal("nosuid", jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.LinuxParameters.Tmpfs[0].MountOptions[1]); + Assert.Equal("awslogs", jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.LogConfiguration.LogDriver); + Assert.Equal("awslogs-wordpress", jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.LogConfiguration.Options["awslogs-group"]); + Assert.Equal("awslogs-example", jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.LogConfiguration.Options["awslogs-stream-prefix"]); + Assert.Single(jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.LogConfiguration.SecretOptions); + Assert.Equal("apikey", jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.LogConfiguration.SecretOptions[0].Name); + Assert.Equal("ddApiKey", jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.LogConfiguration.SecretOptions[0].ValueFrom); + Assert.Single(jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.Secrets); + Assert.Equal("DATABASE_PASSWORD", jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.Secrets[0].Name); + Assert.Equal("arn:aws:ssm:us-east-1:awsExampleAccountID:parameter/awsExampleParameter", jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.Secrets[0].ValueFrom); + Assert.Equal("DISABLED", jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.NetworkConfiguration.AssignPublicIp); + Assert.Equal("LATEST", jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.FargatePlatformConfiguration.PlatformVersion); + Assert.True(jobStateChangeEvent.Detail.PropagateTags); + Assert.Equal(90, jobStateChangeEvent.Detail.Timeout.AttemptDurationSeconds); + Assert.Equal(3, jobStateChangeEvent.Detail.Tags.Count); + Assert.Equal("Batch", jobStateChangeEvent.Detail.Tags["Service"]); + Assert.Equal("JobDefinitionTag", jobStateChangeEvent.Detail.Tags["Name"]); + Assert.Equal("MergeTag", jobStateChangeEvent.Detail.Tags["Expected"]); + Assert.Single(jobStateChangeEvent.Detail.PlatformCapabilities); + Assert.Equal("FARGATE", jobStateChangeEvent.Detail.PlatformCapabilities[0]); + } + } + + + public MemoryStream LoadJsonTestFile(string filename) + { + var json = File.ReadAllText(filename); + return new MemoryStream(UTF8Encoding.UTF8.GetBytes(json)); + } + } +} diff --git a/Libraries/test/EventsTests.NETCore31/TestResponseCasing.cs b/Libraries/test/EventsTests.NET8/TestResponseCasing.cs similarity index 98% rename from Libraries/test/EventsTests.NETCore31/TestResponseCasing.cs rename to Libraries/test/EventsTests.NET8/TestResponseCasing.cs index f6385bc86..d14b5ec47 100644 --- a/Libraries/test/EventsTests.NETCore31/TestResponseCasing.cs +++ b/Libraries/test/EventsTests.NET8/TestResponseCasing.cs @@ -5,7 +5,7 @@ using Amazon.Lambda.Serialization.SystemTextJson; using Newtonsoft.Json.Linq; -namespace EventsTests31 +namespace EventsTests.NET8 { public class TestResponseCasing { @@ -52,4 +52,4 @@ public class DummyResponse public string BingBong { get; set; } } } -} \ No newline at end of file +} diff --git a/Libraries/test/EventsTests.NETCore31/EventsTests.NETCore31.csproj b/Libraries/test/EventsTests.NETCore31/EventsTests.NETCore31.csproj deleted file mode 100644 index 25fedbb49..000000000 --- a/Libraries/test/EventsTests.NETCore31/EventsTests.NETCore31.csproj +++ /dev/null @@ -1,71 +0,0 @@ - - - - netcoreapp3.1 - EventsTests31 - true - win7-x64;win7-x86 - EventsTests31 - latest - - - - - - PreserveNewest - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Libraries/test/EventsTests.Shared/DynamoDBEventJsonTests.cs b/Libraries/test/EventsTests.Shared/DynamoDBEventJsonTests.cs index 39338fe17..ee3d251ac 100644 --- a/Libraries/test/EventsTests.Shared/DynamoDBEventJsonTests.cs +++ b/Libraries/test/EventsTests.Shared/DynamoDBEventJsonTests.cs @@ -1,4 +1,4 @@ -using Amazon.DynamoDBv2.DocumentModel; +using Amazon.DynamoDBv2.DocumentModel; using Amazon.Lambda.DynamoDBEvents; using System.Collections.Generic; using System.IO; @@ -223,7 +223,7 @@ public void Map_ToDocument() Assert.NotNull(document["Map"].AsDocument()); Assert.Equal("string", document["Map"].AsDocument()["string"].AsString()); Assert.Equal(123.45, document["Map"].AsDocument()["number"].AsDouble()); - Assert.Equal(false, document["Map"].AsDocument()["boolean"].AsBoolean()); + Assert.False(document["Map"].AsDocument()["boolean"].AsBoolean()); } [Fact] @@ -357,9 +357,9 @@ public void StringSet_ToDocument() var hashSet = document["StringSet"].AsHashSetOfString(); Assert.NotNull(hashSet); Assert.Equal(3, hashSet.Count); - Assert.True(hashSet.Contains("Black")); - Assert.True(hashSet.Contains("Green")); - Assert.True(hashSet.Contains("Red")); + Assert.Contains("Black", hashSet); + Assert.Contains("Green", hashSet); + Assert.Contains("Red", hashSet); } [Fact] @@ -492,7 +492,7 @@ public void NoAttributes_ToDocument() var json = evnt.Records[0].Dynamodb.NewImage.ToJson(); var document = Document.FromJson(json); - Assert.Equal(0, document.Count); + Assert.Empty(document); } } } diff --git a/Libraries/test/EventsTests.Shared/EventTests.cs b/Libraries/test/EventsTests.Shared/EventTests.cs index 8fa6a6a54..4ca0d39c9 100644 --- a/Libraries/test/EventsTests.Shared/EventTests.cs +++ b/Libraries/test/EventsTests.Shared/EventTests.cs @@ -1,43 +1,43 @@ #pragma warning disable 618 +using Amazon.Lambda.APIGatewayEvents; +using Amazon.Lambda.ApplicationLoadBalancerEvents; +using Amazon.Lambda.CloudWatchEvents.BatchEvents; +using Amazon.Lambda.CloudWatchEvents.ECSEvents; +using Amazon.Lambda.CloudWatchEvents.S3Events; +using Amazon.Lambda.CloudWatchEvents.ScheduledEvents; +using Amazon.Lambda.CloudWatchEvents.TranscribeEvents; +using Amazon.Lambda.CloudWatchEvents.TranslateEvents; +using Amazon.Lambda.CloudWatchLogsEvents; +using Amazon.Lambda.CognitoEvents; +using Amazon.Lambda.ConfigEvents; +using Amazon.Lambda.ConnectEvents; +using Amazon.Lambda.Core; +using Amazon.Lambda.DynamoDBEvents; +using Amazon.Lambda.KafkaEvents; +using Amazon.Lambda.KinesisAnalyticsEvents; +using Amazon.Lambda.KinesisEvents; +using Amazon.Lambda.KinesisFirehoseEvents; +using Amazon.Lambda.LexEvents; +using Amazon.Lambda.LexV2Events; +using Amazon.Lambda.MQEvents; +using Amazon.Lambda.S3Events; +using Amazon.Lambda.Serialization.Json; +using Amazon.Lambda.SimpleEmailEvents; +using Amazon.Lambda.SNSEvents; +using Amazon.Lambda.SQSEvents; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Newtonsoft.Json.Serialization; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using Xunit; +using JsonSerializer = Amazon.Lambda.Serialization.Json.JsonSerializer; + namespace Amazon.Lambda.Tests { - using Amazon.Lambda.APIGatewayEvents; - using Amazon.Lambda.ApplicationLoadBalancerEvents; - using Amazon.Lambda.CloudWatchEvents.BatchEvents; - using Amazon.Lambda.CloudWatchEvents.ECSEvents; - using Amazon.Lambda.CloudWatchEvents.S3Events; - using Amazon.Lambda.CloudWatchEvents.ScheduledEvents; - using Amazon.Lambda.CloudWatchEvents.TranscribeEvents; - using Amazon.Lambda.CloudWatchEvents.TranslateEvents; - using Amazon.Lambda.CloudWatchLogsEvents; - using Amazon.Lambda.CognitoEvents; - using Amazon.Lambda.ConfigEvents; - using Amazon.Lambda.ConnectEvents; - using Amazon.Lambda.Core; - using Amazon.Lambda.DynamoDBEvents; - using Amazon.Lambda.KafkaEvents; - using Amazon.Lambda.KinesisAnalyticsEvents; - using Amazon.Lambda.KinesisEvents; - using Amazon.Lambda.KinesisFirehoseEvents; - using Amazon.Lambda.LexEvents; - using Amazon.Lambda.LexV2Events; - using Amazon.Lambda.MQEvents; - using Amazon.Lambda.S3Events; - using Amazon.Lambda.Serialization.Json; - using Amazon.Lambda.SimpleEmailEvents; - using Amazon.Lambda.SNSEvents; - using Amazon.Lambda.SQSEvents; - using Amazon.Runtime.Internal.Transform; - using Newtonsoft.Json; - using Newtonsoft.Json.Linq; - using Newtonsoft.Json.Serialization; - using System; - using System.Collections.Generic; - using System.IO; - using System.Linq; - using System.Text; - using Xunit; - using JsonSerializer = Amazon.Lambda.Serialization.Json.JsonSerializer; public class EventTest { @@ -52,7 +52,7 @@ public MemoryStream LoadJsonTestFile(string filename) public string SerializeJson(ILambdaSerializer serializer, T response) { string serializedJson; - using (MemoryStream stream = new MemoryStream()) + using (var stream = new MemoryStream()) { serializer.Serialize(response, stream); @@ -64,10 +64,8 @@ public string SerializeJson(ILambdaSerializer serializer, T response) [Theory] [InlineData(typeof(JsonSerializer))] -#if NETCOREAPP3_1_OR_GREATER [InlineData(typeof(Amazon.Lambda.Serialization.SystemTextJson.LambdaJsonSerializer))] [InlineData(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))] -#endif public void HttpApiV2Format(Type serializerType) { var serializer = Activator.CreateInstance(serializerType) as ILambdaSerializer; @@ -95,7 +93,7 @@ public void HttpApiV2Format(Type serializerType) Assert.Equal("value1", request.StageVariables["stageVariable1"]); Assert.Equal("value2", request.StageVariables["stageVariable2"]); - Assert.Equal(1, request.PathParameters.Count); + Assert.Single(request.PathParameters); Assert.Equal("value1", request.PathParameters["parameter1"]); var rc = request.RequestContext; @@ -141,10 +139,8 @@ public void HttpApiV2Format(Type serializerType) [Theory] [InlineData(typeof(JsonSerializer))] -#if NETCOREAPP3_1_OR_GREATER [InlineData(typeof(Amazon.Lambda.Serialization.SystemTextJson.LambdaJsonSerializer))] [InlineData(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))] -#endif public void HttpApiV2FormatLambdaAuthorizer(Type serializerType) { var serializer = Activator.CreateInstance(serializerType) as ILambdaSerializer; @@ -157,10 +153,8 @@ public void HttpApiV2FormatLambdaAuthorizer(Type serializerType) [Theory] [InlineData(typeof(JsonSerializer))] -#if NETCOREAPP3_1_OR_GREATER [InlineData(typeof(Amazon.Lambda.Serialization.SystemTextJson.LambdaJsonSerializer))] [InlineData(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))] -#endif public void HttpApiV2FormatIAMAuthorizer(Type serializerType) { var serializer = Activator.CreateInstance(serializerType) as ILambdaSerializer; @@ -201,10 +195,8 @@ public void SetHeadersToHttpApiV2Response() [Theory] [InlineData(typeof(JsonSerializer))] -#if NETCOREAPP3_1_OR_GREATER [InlineData(typeof(Amazon.Lambda.Serialization.SystemTextJson.LambdaJsonSerializer))] [InlineData(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))] -#endif public void S3ObjectLambdaEventTest(Type serializerType) { var serializer = Activator.CreateInstance(serializerType) as ILambdaSerializer; @@ -245,10 +237,8 @@ public void S3ObjectLambdaEventTest(Type serializerType) [Theory] [InlineData(typeof(JsonSerializer))] -#if NETCOREAPP3_1_OR_GREATER [InlineData(typeof(Amazon.Lambda.Serialization.SystemTextJson.LambdaJsonSerializer))] [InlineData(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))] -#endif public void S3PutTest(Type serializerType) { var serializer = Activator.CreateInstance(serializerType) as ILambdaSerializer; @@ -256,25 +246,25 @@ public void S3PutTest(Type serializerType) { var s3Event = serializer.Deserialize(fileStream); - Assert.Equal(s3Event.Records.Count, 2); + Assert.Equal(2, s3Event.Records.Count); var record = s3Event.Records[0]; - Assert.Equal(record.EventVersion, "2.0"); - Assert.Equal(record.EventTime.ToUniversalTime(), DateTime.Parse("1970-01-01T00:00:00.000Z").ToUniversalTime()); - Assert.Equal(record.RequestParameters.SourceIPAddress, "127.0.0.1"); - Assert.Equal(record.S3.ConfigurationId, "testConfigRule"); - Assert.Equal(record.S3.Object.ETag, "0123456789abcdef0123456789abcdef"); - Assert.Equal(record.S3.Object.Key, "HappyFace.jpg"); - Assert.Equal(record.S3.Object.Size, 1024); - Assert.Equal(record.S3.Bucket.Arn, "arn:aws:s3:::mybucket"); - Assert.Equal(record.S3.Bucket.Name, "sourcebucket"); - Assert.Equal(record.S3.Bucket.OwnerIdentity.PrincipalId, "EXAMPLE"); - Assert.Equal(record.S3.S3SchemaVersion, "1.0"); - Assert.Equal(record.ResponseElements.XAmzId2, "EXAMPLE123/5678abcdefghijklambdaisawesome/mnopqrstuvwxyzABCDEFGH"); - Assert.Equal(record.ResponseElements.XAmzRequestId, "EXAMPLE123456789"); - Assert.Equal(record.AwsRegion, "us-east-1"); - Assert.Equal(record.EventName, "ObjectCreated:Put"); - Assert.Equal(record.UserIdentity.PrincipalId, "EXAMPLE"); - Assert.Equal(record.EventSource, "aws:s3"); + Assert.Equal("2.0", record.EventVersion); + Assert.Equal(DateTime.Parse("1970-01-01T00:00:00.000Z").ToUniversalTime(), record.EventTime.ToUniversalTime()); + Assert.Equal("127.0.0.1", record.RequestParameters.SourceIPAddress); + Assert.Equal("testConfigRule", record.S3.ConfigurationId); + Assert.Equal("0123456789abcdef0123456789abcdef", record.S3.Object.ETag); + Assert.Equal("HappyFace.jpg", record.S3.Object.Key); + Assert.Equal(1024, record.S3.Object.Size); + Assert.Equal("arn:aws:s3:::mybucket", record.S3.Bucket.Arn); + Assert.Equal("sourcebucket", record.S3.Bucket.Name); + Assert.Equal("EXAMPLE", record.S3.Bucket.OwnerIdentity.PrincipalId); + Assert.Equal("1.0", record.S3.S3SchemaVersion); + Assert.Equal("EXAMPLE123/5678abcdefghijklambdaisawesome/mnopqrstuvwxyzABCDEFGH", record.ResponseElements.XAmzId2); + Assert.Equal("EXAMPLE123456789", record.ResponseElements.XAmzRequestId); + Assert.Equal("us-east-1", record.AwsRegion); + Assert.Equal("ObjectCreated:Put", record.EventName); + Assert.Equal("EXAMPLE", record.UserIdentity.PrincipalId); + Assert.Equal("aws:s3", record.EventSource); // In the events file the key is New+File.jpg simulating the key being url encoded. Assert.Equal("New File.jpg", s3Event.Records[1].S3.Object.KeyDecoded); @@ -294,38 +284,32 @@ private void Handle(S3Event s3Event) [Theory] [InlineData(typeof(JsonSerializer))] -#if NETCOREAPP3_1_OR_GREATER [InlineData(typeof(Amazon.Lambda.Serialization.SystemTextJson.LambdaJsonSerializer))] [InlineData(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))] -#endif public void KinesisTest(Type serializerType) { var serializer = Activator.CreateInstance(serializerType) as ILambdaSerializer; using (var fileStream = LoadJsonTestFile("kinesis-event.json")) { var kinesisEvent = serializer.Deserialize(fileStream); - Assert.Equal(kinesisEvent.Records.Count, 2); + Assert.Equal(2, kinesisEvent.Records.Count); var record = kinesisEvent.Records[0]; - Assert.Equal(record.EventId, "shardId-000000000000:49568167373333333333333333333333333333333333333333333333"); - Assert.Equal(record.EventVersion, "1.0"); - Assert.Equal(record.Kinesis.PartitionKey, "s1"); + Assert.Equal("shardId-000000000000:49568167373333333333333333333333333333333333333333333333", record.EventId); + Assert.Equal("1.0", record.EventVersion); + Assert.Equal("s1", record.Kinesis.PartitionKey); var dataBytes = record.Kinesis.Data.ToArray(); - Assert.Equal(Convert.ToBase64String(dataBytes), "SGVsbG8gV29ybGQ="); - Assert.Equal(Encoding.UTF8.GetString(dataBytes), "Hello World"); - Assert.Equal(record.Kinesis.KinesisSchemaVersion, "1.0"); - Assert.Equal(record.Kinesis.SequenceNumber, "49568167373333333333333333333333333333333333333333333333"); - Assert.Equal(record.InvokeIdentityArn, "arn:aws:iam::123456789012:role/LambdaRole"); - Assert.Equal(record.EventName, "aws:kinesis:record"); - Assert.Equal(record.EventSourceARN, "arn:aws:kinesis:us-east-1:123456789012:stream/simple-stream"); - Assert.Equal(record.EventSource, "aws:kinesis"); - Assert.Equal(record.AwsRegion, "us-east-1"); -#if NET8_0_OR_GREATER + Assert.Equal("SGVsbG8gV29ybGQ=", Convert.ToBase64String(dataBytes)); + Assert.Equal("Hello World", Encoding.UTF8.GetString(dataBytes)); + Assert.Equal("1.0", record.Kinesis.KinesisSchemaVersion); + Assert.Equal("49568167373333333333333333333333333333333333333333333333", record.Kinesis.SequenceNumber); + Assert.Equal("arn:aws:iam::123456789012:role/LambdaRole", record.InvokeIdentityArn); + Assert.Equal("aws:kinesis:record", record.EventName); + Assert.Equal("arn:aws:kinesis:us-east-1:123456789012:stream/simple-stream", record.EventSourceARN); + Assert.Equal("aws:kinesis", record.EventSource); + Assert.Equal("us-east-1", record.AwsRegion); // Starting with .NET 7 the precision of the underlying AddSeconds method was changed. // https://learn.microsoft.com/en-us/dotnet/core/compatibility/core-libraries/7.0/datetime-add-precision Assert.Equal(636162383234769999, record.Kinesis.ApproximateArrivalTimestamp.Value.ToUniversalTime().Ticks); -#else - Assert.Equal(636162383234770000, record.Kinesis.ApproximateArrivalTimestamp.Value.ToUniversalTime().Ticks); -#endif Handle(kinesisEvent); } @@ -345,10 +329,8 @@ private void Handle(KinesisEvent kinesisEvent) [Theory] [InlineData(typeof(JsonSerializer))] -#if NETCOREAPP3_1_OR_GREATER [InlineData(typeof(Amazon.Lambda.Serialization.SystemTextJson.LambdaJsonSerializer))] [InlineData(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))] -#endif public void KinesisBatchItemFailuresTest(Type serializerType) { var serializer = Activator.CreateInstance(serializerType) as ILambdaSerializer; @@ -356,10 +338,10 @@ public void KinesisBatchItemFailuresTest(Type serializerType) { var kinesisStreamsEventResponse = serializer.Deserialize(fileStream); - Assert.Equal(1, kinesisStreamsEventResponse.BatchItemFailures.Count); + Assert.Single(kinesisStreamsEventResponse.BatchItemFailures); Assert.Equal("1405400000000002063282832", kinesisStreamsEventResponse.BatchItemFailures[0].ItemIdentifier); - MemoryStream ms = new MemoryStream(); + var ms = new MemoryStream(); serializer.Serialize(kinesisStreamsEventResponse, ms); ms.Position = 0; var json = new StreamReader(ms).ReadToEnd(); @@ -372,10 +354,8 @@ public void KinesisBatchItemFailuresTest(Type serializerType) [Theory] [InlineData(typeof(JsonSerializer))] -#if NETCOREAPP3_1_OR_GREATER [InlineData(typeof(Amazon.Lambda.Serialization.SystemTextJson.LambdaJsonSerializer))] [InlineData(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))] -#endif public void KinesisTimeWindowTest(Type serializerType) { var serializer = Activator.CreateInstance(serializerType) as ILambdaSerializer; @@ -383,35 +363,35 @@ public void KinesisTimeWindowTest(Type serializerType) { var kinesisTimeWindowEvent = serializer.Deserialize(fileStream); - Assert.Equal(kinesisTimeWindowEvent.ShardId, "shardId-000000000006"); - Assert.Equal(kinesisTimeWindowEvent.EventSourceARN, "arn:aws:kinesis:us-east-1:123456789012:stream/lambda-stream"); + Assert.Equal("shardId-000000000006", kinesisTimeWindowEvent.ShardId); + Assert.Equal("arn:aws:kinesis:us-east-1:123456789012:stream/lambda-stream", kinesisTimeWindowEvent.EventSourceARN); Assert.False(kinesisTimeWindowEvent.IsFinalInvokeForWindow); Assert.False(kinesisTimeWindowEvent.IsWindowTerminatedEarly); - Assert.Equal(kinesisTimeWindowEvent.State.Count, 2); + Assert.Equal(2, kinesisTimeWindowEvent.State.Count); Assert.True(kinesisTimeWindowEvent.State.ContainsKey("1")); - Assert.Equal(kinesisTimeWindowEvent.State["1"], "282"); + Assert.Equal("282", kinesisTimeWindowEvent.State["1"]); Assert.True(kinesisTimeWindowEvent.State.ContainsKey("2")); - Assert.Equal(kinesisTimeWindowEvent.State["2"], "715"); + Assert.Equal("715", kinesisTimeWindowEvent.State["2"]); Assert.NotNull(kinesisTimeWindowEvent.Window); Assert.Equal(637430942400000000, kinesisTimeWindowEvent.Window.Start.Ticks); Assert.Equal(637430943600000000, kinesisTimeWindowEvent.Window.End.Ticks); - Assert.Equal(kinesisTimeWindowEvent.Records.Count, 1); + Assert.Single(kinesisTimeWindowEvent.Records); var record = kinesisTimeWindowEvent.Records[0]; - Assert.Equal(record.EventId, "shardId-000000000006:49590338271490256608559692538361571095921575989136588898"); - Assert.Equal(record.EventName, "aws:kinesis:record"); - Assert.Equal(record.EventVersion, "1.0"); - Assert.Equal(record.EventSource, "aws:kinesis"); - Assert.Equal(record.InvokeIdentityArn, "arn:aws:iam::123456789012:role/lambda-kinesis-role"); - Assert.Equal(record.AwsRegion, "us-east-1"); - Assert.Equal(record.EventSourceARN, "arn:aws:kinesis:us-east-1:123456789012:stream/lambda-stream"); - - Assert.Equal(record.Kinesis.KinesisSchemaVersion, "1.0"); - Assert.Equal(record.Kinesis.PartitionKey, "1"); - Assert.Equal(record.Kinesis.SequenceNumber, "49590338271490256608559692538361571095921575989136588898"); + Assert.Equal("shardId-000000000006:49590338271490256608559692538361571095921575989136588898", record.EventId); + Assert.Equal("aws:kinesis:record", record.EventName); + Assert.Equal("1.0", record.EventVersion); + Assert.Equal("aws:kinesis", record.EventSource); + Assert.Equal("arn:aws:iam::123456789012:role/lambda-kinesis-role", record.InvokeIdentityArn); + Assert.Equal("us-east-1", record.AwsRegion); + Assert.Equal("arn:aws:kinesis:us-east-1:123456789012:stream/lambda-stream", record.EventSourceARN); + + Assert.Equal("1.0", record.Kinesis.KinesisSchemaVersion); + Assert.Equal("1", record.Kinesis.PartitionKey); + Assert.Equal("49590338271490256608559692538361571095921575989136588898", record.Kinesis.SequenceNumber); var dataBytes = record.Kinesis.Data.ToArray(); - Assert.Equal(Convert.ToBase64String(dataBytes), "SGVsbG8sIHRoaXMgaXMgYSB0ZXN0Lg=="); - Assert.Equal(Encoding.UTF8.GetString(dataBytes), "Hello, this is a test."); + Assert.Equal("SGVsbG8sIHRoaXMgaXMgYSB0ZXN0Lg==", Convert.ToBase64String(dataBytes)); + Assert.Equal("Hello, this is a test.", Encoding.UTF8.GetString(dataBytes)); Assert.Equal(637430942750000000, record.Kinesis.ApproximateArrivalTimestamp.Value.ToUniversalTime().Ticks); Handle(kinesisTimeWindowEvent); @@ -432,10 +412,8 @@ private void Handle(KinesisTimeWindowEvent kinesisTimeWindowEvent) [Theory] [InlineData(typeof(JsonSerializer))] -#if NETCOREAPP3_1_OR_GREATER [InlineData(typeof(Amazon.Lambda.Serialization.SystemTextJson.LambdaJsonSerializer))] [InlineData(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))] -#endif public void KinesisTimeWindowResponseTest(Type serializerType) { var serializer = Activator.CreateInstance(serializerType) as ILambdaSerializer; @@ -443,38 +421,36 @@ public void KinesisTimeWindowResponseTest(Type serializerType) { var kinesisTimeWindowResponse = serializer.Deserialize(fileStream); - Assert.Equal(kinesisTimeWindowResponse.State.Count, 2); + Assert.Equal(2, kinesisTimeWindowResponse.State.Count); Assert.True(kinesisTimeWindowResponse.State.ContainsKey("1")); - Assert.Equal(kinesisTimeWindowResponse.State["1"], "282"); + Assert.Equal("282", kinesisTimeWindowResponse.State["1"]); Assert.True(kinesisTimeWindowResponse.State.ContainsKey("2")); - Assert.Equal(kinesisTimeWindowResponse.State["2"], "715"); - Assert.Equal(kinesisTimeWindowResponse.BatchItemFailures.Count, 0); + Assert.Equal("715", kinesisTimeWindowResponse.State["2"]); + Assert.Empty(kinesisTimeWindowResponse.BatchItemFailures); } } [Theory] [InlineData(typeof(JsonSerializer))] -#if NETCOREAPP3_1_OR_GREATER [InlineData(typeof(Amazon.Lambda.Serialization.SystemTextJson.LambdaJsonSerializer))] [InlineData(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))] -#endif public void DynamoDbUpdateTest(Type serializerType) { var serializer = Activator.CreateInstance(serializerType) as ILambdaSerializer; Stream json = LoadJsonTestFile("dynamodb-event.json"); var dynamodbEvent = serializer.Deserialize(json); - Assert.Equal(dynamodbEvent.Records.Count, 2); + Assert.Equal(2, dynamodbEvent.Records.Count); var record = dynamodbEvent.Records[0]; - Assert.Equal(record.EventID, "f07f8ca4b0b26cb9c4e5e77e69f274ee"); - Assert.Equal(record.EventVersion, "1.1"); - Assert.Equal(record.Dynamodb.Keys.Count, 2); - Assert.Equal(record.Dynamodb.Keys["key"].S, "binary"); - Assert.Equal(record.Dynamodb.Keys["val"].S, "data"); + Assert.Equal("f07f8ca4b0b26cb9c4e5e77e69f274ee", record.EventID); + Assert.Equal("1.1", record.EventVersion); + Assert.Equal(2, record.Dynamodb.Keys.Count); + Assert.Equal("binary", record.Dynamodb.Keys["key"].S); + Assert.Equal("data", record.Dynamodb.Keys["val"].S); Assert.Null(record.UserIdentity); Assert.Null(record.Dynamodb.OldImage); - Assert.Equal(record.Dynamodb.NewImage["val"].S, "data"); - Assert.Equal(record.Dynamodb.NewImage["key"].S, "binary"); + Assert.Equal("data", record.Dynamodb.NewImage["val"].S); + Assert.Equal("binary", record.Dynamodb.NewImage["key"].S); Assert.Null(record.Dynamodb.NewImage["key"].BOOL); Assert.Null(record.Dynamodb.NewImage["key"].L); Assert.Null(record.Dynamodb.NewImage["key"].M); @@ -482,26 +458,26 @@ public void DynamoDbUpdateTest(Type serializerType) Assert.Null(record.Dynamodb.NewImage["key"].NS); Assert.Null(record.Dynamodb.NewImage["key"].NULL); Assert.Null(record.Dynamodb.NewImage["key"].SS); - Assert.Equal(MemoryStreamToBase64String(record.Dynamodb.NewImage["asdf1"].B), "AAEqQQ=="); - Assert.Equal(record.Dynamodb.NewImage["asdf2"].BS.Count, 2); - Assert.Equal(MemoryStreamToBase64String(record.Dynamodb.NewImage["asdf2"].BS[0]), "AAEqQQ=="); - Assert.Equal(MemoryStreamToBase64String(record.Dynamodb.NewImage["asdf2"].BS[1]), "QSoBAA=="); - Assert.Equal(record.Dynamodb.StreamViewType, "NEW_AND_OLD_IMAGES"); - Assert.Equal(record.Dynamodb.SequenceNumber, "1405400000000002063282832"); - Assert.Equal(record.Dynamodb.SizeBytes, 54); - Assert.Equal(record.AwsRegion, "us-east-1"); - Assert.Equal(record.EventName, "INSERT"); - Assert.Equal(record.EventSourceArn, "arn:aws:dynamodb:us-east-1:123456789012:table/Example-Table/stream/2016-12-01T00:00:00.000"); - Assert.Equal(record.EventSource, "aws:dynamodb"); + Assert.Equal("AAEqQQ==", MemoryStreamToBase64String(record.Dynamodb.NewImage["asdf1"].B)); + Assert.Equal(2, record.Dynamodb.NewImage["asdf2"].BS.Count); + Assert.Equal("AAEqQQ==", MemoryStreamToBase64String(record.Dynamodb.NewImage["asdf2"].BS[0])); + Assert.Equal("QSoBAA==", MemoryStreamToBase64String(record.Dynamodb.NewImage["asdf2"].BS[1])); + Assert.Equal("NEW_AND_OLD_IMAGES", record.Dynamodb.StreamViewType); + Assert.Equal("1405400000000002063282832", record.Dynamodb.SequenceNumber); + Assert.Equal(54, record.Dynamodb.SizeBytes); + Assert.Equal("us-east-1", record.AwsRegion); + Assert.Equal("INSERT", record.EventName); + Assert.Equal("arn:aws:dynamodb:us-east-1:123456789012:table/Example-Table/stream/2016-12-01T00:00:00.000", record.EventSourceArn); + Assert.Equal("aws:dynamodb", record.EventSource); var recordDateTime = record.Dynamodb.ApproximateCreationDateTime; - Assert.Equal(recordDateTime.Ticks, 636162388200000000); + Assert.Equal(636162388200000000, recordDateTime.Ticks); var topLevelList = record.Dynamodb.NewImage["misc1"].L; - Assert.Equal(0, topLevelList.Count); + Assert.Empty(topLevelList); var nestedMap = record.Dynamodb.NewImage["misc2"].M; Assert.NotNull(nestedMap); - Assert.Equal(0, nestedMap["ItemsEmpty"].L.Count); + Assert.Empty(nestedMap["ItemsEmpty"].L); Assert.Equal(3, nestedMap["ItemsNonEmpty"].L.Count); Assert.False(nestedMap["ItemBoolean"].BOOL); Assert.True(nestedMap["ItemNull"].NULL); @@ -527,27 +503,25 @@ public void DynamoDbUpdateTest(Type serializerType) [Theory] [InlineData(typeof(JsonSerializer))] -#if NETCOREAPP3_1_OR_GREATER [InlineData(typeof(Amazon.Lambda.Serialization.SystemTextJson.LambdaJsonSerializer))] [InlineData(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))] -#endif public void DynamoDbWithMillisecondsTest(Type serializerType) { var serializer = Activator.CreateInstance(serializerType) as ILambdaSerializer; Stream json = LoadJsonTestFile("dynamodb-with-ms-event.json"); var dynamodbEvent = serializer.Deserialize(json); - Assert.Equal(dynamodbEvent.Records.Count, 2); + Assert.Equal(2, dynamodbEvent.Records.Count); var record = dynamodbEvent.Records[0]; - Assert.Equal(record.EventID, "f07f8ca4b0b26cb9c4e5e77e69f274ee"); - Assert.Equal(record.EventVersion, "1.1"); - Assert.Equal(record.Dynamodb.Keys.Count, 2); - Assert.Equal(record.Dynamodb.Keys["key"].S, "binary"); - Assert.Equal(record.Dynamodb.Keys["val"].S, "data"); + Assert.Equal("f07f8ca4b0b26cb9c4e5e77e69f274ee", record.EventID); + Assert.Equal("1.1", record.EventVersion); + Assert.Equal(2, record.Dynamodb.Keys.Count); + Assert.Equal("binary", record.Dynamodb.Keys["key"].S); + Assert.Equal("data", record.Dynamodb.Keys["val"].S); Assert.Null(record.UserIdentity); Assert.Null(record.Dynamodb.OldImage); - Assert.Equal(record.Dynamodb.NewImage["val"].S, "data"); - Assert.Equal(record.Dynamodb.NewImage["key"].S, "binary"); + Assert.Equal("data", record.Dynamodb.NewImage["val"].S); + Assert.Equal("binary", record.Dynamodb.NewImage["key"].S); Assert.Null(record.Dynamodb.NewImage["key"].BOOL); Assert.Null(record.Dynamodb.NewImage["key"].L); Assert.Null(record.Dynamodb.NewImage["key"].M); @@ -555,26 +529,26 @@ public void DynamoDbWithMillisecondsTest(Type serializerType) Assert.Null(record.Dynamodb.NewImage["key"].NS); Assert.Null(record.Dynamodb.NewImage["key"].NULL); Assert.Null(record.Dynamodb.NewImage["key"].SS); - Assert.Equal(MemoryStreamToBase64String(record.Dynamodb.NewImage["asdf1"].B), "AAEqQQ=="); - Assert.Equal(record.Dynamodb.NewImage["asdf2"].BS.Count, 2); - Assert.Equal(MemoryStreamToBase64String(record.Dynamodb.NewImage["asdf2"].BS[0]), "AAEqQQ=="); - Assert.Equal(MemoryStreamToBase64String(record.Dynamodb.NewImage["asdf2"].BS[1]), "QSoBAA=="); - Assert.Equal(record.Dynamodb.StreamViewType, "NEW_AND_OLD_IMAGES"); - Assert.Equal(record.Dynamodb.SequenceNumber, "1405400000000002063282832"); - Assert.Equal(record.Dynamodb.SizeBytes, 54); - Assert.Equal(record.AwsRegion, "us-east-1"); - Assert.Equal(record.EventName, "INSERT"); - Assert.Equal(record.EventSourceArn, "arn:aws:dynamodb:us-east-1:123456789012:table/Example-Table/stream/2016-12-01T00:00:00.000"); - Assert.Equal(record.EventSource, "aws:dynamodb"); + Assert.Equal("AAEqQQ==", MemoryStreamToBase64String(record.Dynamodb.NewImage["asdf1"].B)); + Assert.Equal(2, record.Dynamodb.NewImage["asdf2"].BS.Count); + Assert.Equal("AAEqQQ==", MemoryStreamToBase64String(record.Dynamodb.NewImage["asdf2"].BS[0])); + Assert.Equal("QSoBAA==", MemoryStreamToBase64String(record.Dynamodb.NewImage["asdf2"].BS[1])); + Assert.Equal("NEW_AND_OLD_IMAGES", record.Dynamodb.StreamViewType); + Assert.Equal("1405400000000002063282832", record.Dynamodb.SequenceNumber); + Assert.Equal(54, record.Dynamodb.SizeBytes); + Assert.Equal("us-east-1", record.AwsRegion); + Assert.Equal("INSERT", record.EventName); + Assert.Equal("arn:aws:dynamodb:us-east-1:123456789012:table/Example-Table/stream/2016-12-01T00:00:00.000", record.EventSourceArn); + Assert.Equal("aws:dynamodb", record.EventSource); var recordDateTime = record.Dynamodb.ApproximateCreationDateTime; - Assert.Equal(recordDateTime.Ticks, 636162388200000000); + Assert.Equal(636162388200000000, recordDateTime.Ticks); var topLevelList = record.Dynamodb.NewImage["misc1"].L; - Assert.Equal(0, topLevelList.Count); + Assert.Empty(topLevelList); var nestedMap = record.Dynamodb.NewImage["misc2"].M; Assert.NotNull(nestedMap); - Assert.Equal(0, nestedMap["ItemsEmpty"].L.Count); + Assert.Empty(nestedMap["ItemsEmpty"].L); Assert.Equal(3, nestedMap["ItemsNonEmpty"].L.Count); Assert.False(nestedMap["ItemBoolean"].BOOL); Assert.True(nestedMap["ItemNull"].NULL); @@ -600,10 +574,8 @@ public void DynamoDbWithMillisecondsTest(Type serializerType) [Theory] [InlineData(typeof(JsonSerializer))] -#if NETCOREAPP3_1_OR_GREATER [InlineData(typeof(Amazon.Lambda.Serialization.SystemTextJson.LambdaJsonSerializer))] [InlineData(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))] -#endif public void DynamoDbBatchItemFailuresTest(Type serializerType) { var serializer = Activator.CreateInstance(serializerType) as ILambdaSerializer; @@ -611,10 +583,10 @@ public void DynamoDbBatchItemFailuresTest(Type serializerType) { var dynamoDbStreamsEventResponse = serializer.Deserialize(fileStream); - Assert.Equal(1, dynamoDbStreamsEventResponse.BatchItemFailures.Count); + Assert.Single(dynamoDbStreamsEventResponse.BatchItemFailures); Assert.Equal("1405400000000002063282832", dynamoDbStreamsEventResponse.BatchItemFailures[0].ItemIdentifier); - MemoryStream ms = new MemoryStream(); + var ms = new MemoryStream(); serializer.Serialize(dynamoDbStreamsEventResponse, ms); ms.Position = 0; var json = new StreamReader(ms).ReadToEnd(); @@ -638,10 +610,8 @@ private static void Handle(DynamoDBEvent ddbEvent) [Theory] [InlineData(typeof(JsonSerializer))] -#if NETCOREAPP3_1_OR_GREATER [InlineData(typeof(Amazon.Lambda.Serialization.SystemTextJson.LambdaJsonSerializer))] [InlineData(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))] -#endif public void DynamoDBTimeWindowTest(Type serializerType) { var serializer = Activator.CreateInstance(serializerType) as ILambdaSerializer; @@ -649,80 +619,78 @@ public void DynamoDBTimeWindowTest(Type serializerType) { var dynamoDBTimeWindowEvent = serializer.Deserialize(fileStream); - Assert.Equal(dynamoDBTimeWindowEvent.ShardId, "shard123456789"); - Assert.Equal(dynamoDBTimeWindowEvent.EventSourceArn, "stream-ARN"); + Assert.Equal("shard123456789", dynamoDBTimeWindowEvent.ShardId); + Assert.Equal("stream-ARN", dynamoDBTimeWindowEvent.EventSourceArn); Assert.False(dynamoDBTimeWindowEvent.IsFinalInvokeForWindow); Assert.False(dynamoDBTimeWindowEvent.IsWindowTerminatedEarly); - Assert.Equal(dynamoDBTimeWindowEvent.State.Count, 1); + Assert.Single(dynamoDBTimeWindowEvent.State); Assert.True(dynamoDBTimeWindowEvent.State.ContainsKey("1")); - Assert.Equal(dynamoDBTimeWindowEvent.State["1"], "state1"); + Assert.Equal("state1", dynamoDBTimeWindowEvent.State["1"]); Assert.NotNull(dynamoDBTimeWindowEvent.Window); Assert.Equal(637317252000000000, dynamoDBTimeWindowEvent.Window.Start.Ticks); Assert.Equal(637317255000000000, dynamoDBTimeWindowEvent.Window.End.Ticks); - Assert.Equal(dynamoDBTimeWindowEvent.Records.Count, 3); + Assert.Equal(3, dynamoDBTimeWindowEvent.Records.Count); var record1 = dynamoDBTimeWindowEvent.Records[0]; - Assert.Equal(record1.EventID, "1"); - Assert.Equal(record1.EventName, "INSERT"); - Assert.Equal(record1.EventVersion, "1.0"); - Assert.Equal(record1.EventSource, "aws:dynamodb"); - Assert.Equal(record1.AwsRegion, "us-east-1"); - Assert.Equal(record1.EventSourceArn, "stream-ARN"); - Assert.Equal(record1.Dynamodb.Keys.Count, 1); - Assert.Equal(record1.Dynamodb.Keys["Id"].N, "101"); - Assert.Equal(record1.Dynamodb.SequenceNumber, "111"); - Assert.Equal(record1.Dynamodb.SizeBytes, 26); - Assert.Equal(record1.Dynamodb.StreamViewType, "NEW_IMAGE"); - Assert.Equal(record1.Dynamodb.NewImage.Count, 2); - Assert.Equal(record1.Dynamodb.NewImage["Message"].S, "New item!"); - Assert.Equal(record1.Dynamodb.NewImage["Id"].N, "101"); + Assert.Equal("1", record1.EventID); + Assert.Equal("INSERT", record1.EventName); + Assert.Equal("1.0", record1.EventVersion); + Assert.Equal("aws:dynamodb", record1.EventSource); + Assert.Equal("us-east-1", record1.AwsRegion); + Assert.Equal("stream-ARN", record1.EventSourceArn); + Assert.Single(record1.Dynamodb.Keys); + Assert.Equal("101", record1.Dynamodb.Keys["Id"].N); + Assert.Equal("111", record1.Dynamodb.SequenceNumber); + Assert.Equal(26, record1.Dynamodb.SizeBytes); + Assert.Equal("NEW_IMAGE", record1.Dynamodb.StreamViewType); + Assert.Equal(2, record1.Dynamodb.NewImage.Count); + Assert.Equal("New item!", record1.Dynamodb.NewImage["Message"].S); + Assert.Equal("101", record1.Dynamodb.NewImage["Id"].N); Assert.Null(record1.Dynamodb.OldImage); var record2 = dynamoDBTimeWindowEvent.Records[1]; - Assert.Equal(record2.EventID, "2"); - Assert.Equal(record2.EventName, "MODIFY"); - Assert.Equal(record2.EventVersion, "1.0"); - Assert.Equal(record2.EventSource, "aws:dynamodb"); - Assert.Equal(record2.AwsRegion, "us-east-1"); - Assert.Equal(record2.EventSourceArn, "stream-ARN"); - Assert.Equal(record2.Dynamodb.Keys.Count, 1); - Assert.Equal(record2.Dynamodb.Keys["Id"].N, "101"); - Assert.Equal(record2.Dynamodb.SequenceNumber, "222"); - Assert.Equal(record2.Dynamodb.SizeBytes, 59); - Assert.Equal(record2.Dynamodb.StreamViewType, "NEW_AND_OLD_IMAGES"); - Assert.Equal(record2.Dynamodb.NewImage.Count, 2); - Assert.Equal(record2.Dynamodb.NewImage["Message"].S, "This item has changed"); - Assert.Equal(record2.Dynamodb.NewImage["Id"].N, "101"); - Assert.Equal(record2.Dynamodb.OldImage.Count, 2); - Assert.Equal(record2.Dynamodb.OldImage["Message"].S, "New item!"); - Assert.Equal(record2.Dynamodb.OldImage["Id"].N, "101"); + Assert.Equal("2", record2.EventID); + Assert.Equal("MODIFY", record2.EventName); + Assert.Equal("1.0", record2.EventVersion); + Assert.Equal("aws:dynamodb", record2.EventSource); + Assert.Equal("us-east-1", record2.AwsRegion); + Assert.Equal("stream-ARN", record2.EventSourceArn); + Assert.Single(record2.Dynamodb.Keys); + Assert.Equal("101", record2.Dynamodb.Keys["Id"].N); + Assert.Equal("222", record2.Dynamodb.SequenceNumber); + Assert.Equal(59, record2.Dynamodb.SizeBytes); + Assert.Equal("NEW_AND_OLD_IMAGES", record2.Dynamodb.StreamViewType); + Assert.Equal(2, record2.Dynamodb.NewImage.Count); + Assert.Equal("This item has changed", record2.Dynamodb.NewImage["Message"].S); + Assert.Equal("101", record2.Dynamodb.NewImage["Id"].N); + Assert.Equal(2, record2.Dynamodb.OldImage.Count); + Assert.Equal("New item!", record2.Dynamodb.OldImage["Message"].S); + Assert.Equal("101", record2.Dynamodb.OldImage["Id"].N); var record3 = dynamoDBTimeWindowEvent.Records[2]; - Assert.Equal(record3.EventID, "3"); - Assert.Equal(record3.EventName, "REMOVE"); - Assert.Equal(record3.EventVersion, "1.0"); - Assert.Equal(record3.EventSource, "aws:dynamodb"); - Assert.Equal(record3.AwsRegion, "us-east-1"); - Assert.Equal(record3.EventSourceArn, "stream-ARN"); - Assert.Equal(record3.Dynamodb.Keys.Count, 1); - Assert.Equal(record3.Dynamodb.Keys["Id"].N, "101"); - Assert.Equal(record3.Dynamodb.SequenceNumber, "333"); - Assert.Equal(record3.Dynamodb.SizeBytes, 38); - Assert.Equal(record3.Dynamodb.StreamViewType, "NEW_AND_OLD_IMAGES"); + Assert.Equal("3", record3.EventID); + Assert.Equal("REMOVE", record3.EventName); + Assert.Equal("1.0", record3.EventVersion); + Assert.Equal("aws:dynamodb", record3.EventSource); + Assert.Equal("us-east-1", record3.AwsRegion); + Assert.Equal("stream-ARN", record3.EventSourceArn); + Assert.Single(record3.Dynamodb.Keys); + Assert.Equal("101", record3.Dynamodb.Keys["Id"].N); + Assert.Equal("333", record3.Dynamodb.SequenceNumber); + Assert.Equal(38, record3.Dynamodb.SizeBytes); + Assert.Equal("NEW_AND_OLD_IMAGES", record3.Dynamodb.StreamViewType); Assert.Null(record3.Dynamodb.NewImage); - Assert.Equal(record3.Dynamodb.OldImage.Count, 2); - Assert.Equal(record3.Dynamodb.OldImage["Message"].S, "This item has changed"); - Assert.Equal(record3.Dynamodb.OldImage["Id"].N, "101"); + Assert.Equal(2, record3.Dynamodb.OldImage.Count); + Assert.Equal("This item has changed", record3.Dynamodb.OldImage["Message"].S); + Assert.Equal("101", record3.Dynamodb.OldImage["Id"].N); } } [Theory] [InlineData(typeof(JsonSerializer))] -#if NETCOREAPP3_1_OR_GREATER [InlineData(typeof(Amazon.Lambda.Serialization.SystemTextJson.LambdaJsonSerializer))] [InlineData(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))] -#endif public void DynamoDBTimeWindowResponseTest(Type serializerType) { var serializer = Activator.CreateInstance(serializerType) as ILambdaSerializer; @@ -730,38 +698,36 @@ public void DynamoDBTimeWindowResponseTest(Type serializerType) { var dynamoDBTimeWindowResponse = serializer.Deserialize(fileStream); - Assert.Equal(dynamoDBTimeWindowResponse.State.Count, 2); + Assert.Equal(2, dynamoDBTimeWindowResponse.State.Count); Assert.True(dynamoDBTimeWindowResponse.State.ContainsKey("1")); - Assert.Equal(dynamoDBTimeWindowResponse.State["1"], "282"); + Assert.Equal("282", dynamoDBTimeWindowResponse.State["1"]); Assert.True(dynamoDBTimeWindowResponse.State.ContainsKey("2")); - Assert.Equal(dynamoDBTimeWindowResponse.State["2"], "715"); - Assert.Equal(dynamoDBTimeWindowResponse.BatchItemFailures.Count, 0); + Assert.Equal("715", dynamoDBTimeWindowResponse.State["2"]); + Assert.Empty(dynamoDBTimeWindowResponse.BatchItemFailures); } } [Theory] [InlineData(typeof(JsonSerializer))] -#if NETCOREAPP3_1_OR_GREATER [InlineData(typeof(Amazon.Lambda.Serialization.SystemTextJson.LambdaJsonSerializer))] [InlineData(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))] -#endif public void CognitoTest(Type serializerType) { var serializer = Activator.CreateInstance(serializerType) as ILambdaSerializer; using (var fileStream = LoadJsonTestFile("cognito-event.json")) { var cognitoEvent = serializer.Deserialize(fileStream); - Assert.Equal(cognitoEvent.Version, 2); - Assert.Equal(cognitoEvent.EventType, "SyncTrigger"); - Assert.Equal(cognitoEvent.Region, "us-east-1"); - Assert.Equal(cognitoEvent.DatasetName, "datasetName"); - Assert.Equal(cognitoEvent.IdentityPoolId, "identityPoolId"); - Assert.Equal(cognitoEvent.IdentityId, "identityId"); - Assert.Equal(cognitoEvent.DatasetRecords.Count, 1); + Assert.Equal(2, cognitoEvent.Version); + Assert.Equal("SyncTrigger", cognitoEvent.EventType); + Assert.Equal("us-east-1", cognitoEvent.Region); + Assert.Equal("datasetName", cognitoEvent.DatasetName); + Assert.Equal("identityPoolId", cognitoEvent.IdentityPoolId); + Assert.Equal("identityId", cognitoEvent.IdentityId); + Assert.Single(cognitoEvent.DatasetRecords); Assert.True(cognitoEvent.DatasetRecords.ContainsKey("SampleKey1")); - Assert.Equal(cognitoEvent.DatasetRecords["SampleKey1"].NewValue, "newValue1"); - Assert.Equal(cognitoEvent.DatasetRecords["SampleKey1"].OldValue, "oldValue1"); - Assert.Equal(cognitoEvent.DatasetRecords["SampleKey1"].Op, "replace"); + Assert.Equal("newValue1", cognitoEvent.DatasetRecords["SampleKey1"].NewValue); + Assert.Equal("oldValue1", cognitoEvent.DatasetRecords["SampleKey1"].OldValue); + Assert.Equal("replace", cognitoEvent.DatasetRecords["SampleKey1"].Op); Handle(cognitoEvent); } @@ -780,10 +746,8 @@ private static void Handle(CognitoEvent cognitoEvent) [Theory] [InlineData(typeof(JsonSerializer))] -#if NETCOREAPP3_1_OR_GREATER [InlineData(typeof(Amazon.Lambda.Serialization.SystemTextJson.LambdaJsonSerializer))] [InlineData(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))] -#endif public void CognitoPreSignUpEventTest(Type serializerType) { var serializer = Activator.CreateInstance(serializerType) as ILambdaSerializer; @@ -809,7 +773,7 @@ public void CognitoPreSignUpEventTest(Type serializerType) Assert.True(cognitoPreSignupEvent.Response.AutoVerifyPhone); Assert.True(cognitoPreSignupEvent.Response.AutoVerifyEmail); - MemoryStream ms = new MemoryStream(); + var ms = new MemoryStream(); serializer.Serialize(cognitoPreSignupEvent, ms); ms.Position = 0; var json = new StreamReader(ms).ReadToEnd(); @@ -822,10 +786,8 @@ public void CognitoPreSignUpEventTest(Type serializerType) [Theory] [InlineData(typeof(JsonSerializer))] -#if NETCOREAPP3_1_OR_GREATER [InlineData(typeof(Amazon.Lambda.Serialization.SystemTextJson.LambdaJsonSerializer))] [InlineData(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))] -#endif public void CognitoPostConfirmationEventTest(Type serializerType) { var serializer = Activator.CreateInstance(serializerType) as ILambdaSerializer; @@ -841,7 +803,7 @@ public void CognitoPostConfirmationEventTest(Type serializerType) Assert.Equal("metadata_2", cognitoPostConfirmationEvent.Request.ClientMetadata.ToArray()[1].Key); Assert.Equal("metadata_value_2", cognitoPostConfirmationEvent.Request.ClientMetadata.ToArray()[1].Value); - MemoryStream ms = new MemoryStream(); + var ms = new MemoryStream(); serializer.Serialize(cognitoPostConfirmationEvent, ms); ms.Position = 0; var json = new StreamReader(ms).ReadToEnd(); @@ -854,10 +816,8 @@ public void CognitoPostConfirmationEventTest(Type serializerType) [Theory] [InlineData(typeof(JsonSerializer))] -#if NETCOREAPP3_1_OR_GREATER [InlineData(typeof(Amazon.Lambda.Serialization.SystemTextJson.LambdaJsonSerializer))] [InlineData(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))] -#endif public void CognitoPreAuthenticationEventTest(Type serializerType) { var serializer = Activator.CreateInstance(serializerType) as ILambdaSerializer; @@ -875,7 +835,7 @@ public void CognitoPreAuthenticationEventTest(Type serializerType) Assert.True(cognitoPreAuthenticationEvent.Request.UserNotFound); - MemoryStream ms = new MemoryStream(); + var ms = new MemoryStream(); serializer.Serialize(cognitoPreAuthenticationEvent, ms); ms.Position = 0; var json = new StreamReader(ms).ReadToEnd(); @@ -888,10 +848,8 @@ public void CognitoPreAuthenticationEventTest(Type serializerType) [Theory] [InlineData(typeof(JsonSerializer))] -#if NETCOREAPP3_1_OR_GREATER [InlineData(typeof(Amazon.Lambda.Serialization.SystemTextJson.LambdaJsonSerializer))] [InlineData(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))] -#endif public void CognitoPostAuthenticationEventTest(Type serializerType) { var serializer = Activator.CreateInstance(serializerType) as ILambdaSerializer; @@ -928,10 +886,8 @@ public void CognitoPostAuthenticationEventTest(Type serializerType) [Theory] [InlineData(typeof(JsonSerializer))] -#if NETCOREAPP3_1_OR_GREATER [InlineData(typeof(Amazon.Lambda.Serialization.SystemTextJson.LambdaJsonSerializer))] [InlineData(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))] -#endif public void CognitoDefineAuthChallengeEventTest(Type serializerType) { var serializer = Activator.CreateInstance(serializerType) as ILambdaSerializer; @@ -966,7 +922,7 @@ public void CognitoDefineAuthChallengeEventTest(Type serializerType) Assert.True(cognitoDefineAuthChallengeEvent.Response.IssueTokens); Assert.True(cognitoDefineAuthChallengeEvent.Response.FailAuthentication); - MemoryStream ms = new MemoryStream(); + var ms = new MemoryStream(); serializer.Serialize(cognitoDefineAuthChallengeEvent, ms); ms.Position = 0; var json = new StreamReader(ms).ReadToEnd(); @@ -979,10 +935,8 @@ public void CognitoDefineAuthChallengeEventTest(Type serializerType) [Theory] [InlineData(typeof(JsonSerializer))] -#if NETCOREAPP3_1_OR_GREATER [InlineData(typeof(Amazon.Lambda.Serialization.SystemTextJson.LambdaJsonSerializer))] [InlineData(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))] -#endif public void CognitoDefineAuthChallengeEventWithNullValuesTest(Type serializerType) { var serializer = Activator.CreateInstance(serializerType) as ILambdaSerializer; @@ -1020,10 +974,8 @@ public void CognitoDefineAuthChallengeEventWithNullValuesTest(Type serializerTyp [Theory] [InlineData(typeof(JsonSerializer))] -#if NETCOREAPP3_1_OR_GREATER [InlineData(typeof(Amazon.Lambda.Serialization.SystemTextJson.LambdaJsonSerializer))] [InlineData(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))] -#endif public void CognitoCreateAuthChallengeEventTest(Type serializerType) { var serializer = Activator.CreateInstance(serializerType) as ILambdaSerializer; @@ -1070,7 +1022,7 @@ public void CognitoCreateAuthChallengeEventTest(Type serializerType) Assert.Equal("challenge", cognitoCreateAuthChallengeEvent.Response.ChallengeMetadata); - MemoryStream ms = new MemoryStream(); + var ms = new MemoryStream(); serializer.Serialize(cognitoCreateAuthChallengeEvent, ms); ms.Position = 0; var json = new StreamReader(ms).ReadToEnd(); @@ -1083,10 +1035,8 @@ public void CognitoCreateAuthChallengeEventTest(Type serializerType) [Theory] [InlineData(typeof(JsonSerializer))] -#if NETCOREAPP3_1_OR_GREATER [InlineData(typeof(Amazon.Lambda.Serialization.SystemTextJson.LambdaJsonSerializer))] [InlineData(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))] -#endif public void CognitoVerifyAuthChallengeEventTest(Type serializerType) { var serializer = Activator.CreateInstance(serializerType) as ILambdaSerializer; @@ -1115,7 +1065,7 @@ public void CognitoVerifyAuthChallengeEventTest(Type serializerType) Assert.True(cognitoVerifyAuthChallengeEvent.Response.AnswerCorrect); - MemoryStream ms = new MemoryStream(); + var ms = new MemoryStream(); serializer.Serialize(cognitoVerifyAuthChallengeEvent, ms); ms.Position = 0; var json = new StreamReader(ms).ReadToEnd(); @@ -1129,10 +1079,8 @@ public void CognitoVerifyAuthChallengeEventTest(Type serializerType) [Theory] [InlineData(typeof(JsonSerializer))] -#if NETCOREAPP3_1_OR_GREATER [InlineData(typeof(Amazon.Lambda.Serialization.SystemTextJson.LambdaJsonSerializer))] [InlineData(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))] -#endif public void CognitoVerifyAuthChallengeEventWithNullValuesTest(Type serializerType) { var serializer = Activator.CreateInstance(serializerType) as ILambdaSerializer; @@ -1160,10 +1108,8 @@ public void CognitoVerifyAuthChallengeEventWithNullValuesTest(Type serializerTyp [Theory] [InlineData(typeof(JsonSerializer))] -#if NETCOREAPP3_1_OR_GREATER [InlineData(typeof(Amazon.Lambda.Serialization.SystemTextJson.LambdaJsonSerializer))] [InlineData(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))] -#endif public void CognitoPreTokenGenerationEventTest(Type serializerType) { var serializer = Activator.CreateInstance(serializerType) as ILambdaSerializer; @@ -1209,7 +1155,7 @@ public void CognitoPreTokenGenerationEventTest(Type serializerType) Assert.Equal("role", cognitoPreTokenGenerationEvent.Response.ClaimsOverrideDetails.GroupOverrideDetails.PreferredRole); - MemoryStream ms = new MemoryStream(); + var ms = new MemoryStream(); serializer.Serialize(cognitoPreTokenGenerationEvent, ms); ms.Position = 0; var json = new StreamReader(ms).ReadToEnd(); @@ -1222,10 +1168,8 @@ public void CognitoPreTokenGenerationEventTest(Type serializerType) [Theory] [InlineData(typeof(JsonSerializer))] -#if NETCOREAPP3_1_OR_GREATER [InlineData(typeof(Amazon.Lambda.Serialization.SystemTextJson.LambdaJsonSerializer))] [InlineData(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))] -#endif public void CognitoPreTokenGenerationV2EventTest(Type serializerType) { var serializer = Activator.CreateInstance(serializerType) as ILambdaSerializer; @@ -1301,7 +1245,7 @@ public void CognitoPreTokenGenerationV2EventTest(Type serializerType) Assert.Equal("role", cognitoPreTokenGenerationV2Event.Response.ClaimsAndScopeOverrideDetails.GroupOverrideDetails.PreferredRole); - MemoryStream ms = new MemoryStream(); + var ms = new MemoryStream(); serializer.Serialize(cognitoPreTokenGenerationV2Event, ms); ms.Position = 0; var json = new StreamReader(ms).ReadToEnd(); @@ -1314,10 +1258,8 @@ public void CognitoPreTokenGenerationV2EventTest(Type serializerType) [Theory] [InlineData(typeof(JsonSerializer))] -#if NETCOREAPP3_1_OR_GREATER [InlineData(typeof(Amazon.Lambda.Serialization.SystemTextJson.LambdaJsonSerializer))] [InlineData(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))] -#endif public void CognitoMigrateUserEventTest(Type serializerType) { var serializer = Activator.CreateInstance(serializerType) as ILambdaSerializer; @@ -1358,7 +1300,7 @@ public void CognitoMigrateUserEventTest(Type serializerType) Assert.True(cognitoMigrateUserEvent.Response.ForceAliasCreation); - MemoryStream ms = new MemoryStream(); + var ms = new MemoryStream(); serializer.Serialize(cognitoMigrateUserEvent, ms); ms.Position = 0; var json = new StreamReader(ms).ReadToEnd(); @@ -1371,10 +1313,8 @@ public void CognitoMigrateUserEventTest(Type serializerType) [Theory] [InlineData(typeof(JsonSerializer))] -#if NETCOREAPP3_1_OR_GREATER [InlineData(typeof(Amazon.Lambda.Serialization.SystemTextJson.LambdaJsonSerializer))] [InlineData(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))] -#endif public void CognitoCustomMessageEventTest(Type serializerType) { var serializer = Activator.CreateInstance(serializerType) as ILambdaSerializer; @@ -1397,7 +1337,7 @@ public void CognitoCustomMessageEventTest(Type serializerType) Assert.Equal("email", cognitoCustomMessageEvent.Response.EmailMessage); Assert.Equal("subject", cognitoCustomMessageEvent.Response.EmailSubject); - MemoryStream ms = new MemoryStream(); + var ms = new MemoryStream(); serializer.Serialize(cognitoCustomMessageEvent, ms); ms.Position = 0; var json = new StreamReader(ms).ReadToEnd(); @@ -1410,10 +1350,8 @@ public void CognitoCustomMessageEventTest(Type serializerType) [Theory] [InlineData(typeof(JsonSerializer))] -#if NETCOREAPP3_1_OR_GREATER [InlineData(typeof(Amazon.Lambda.Serialization.SystemTextJson.LambdaJsonSerializer))] [InlineData(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))] -#endif public void CognitoCustomEmailSenderEventTest(Type serializerType) { var serializer = Activator.CreateInstance(serializerType) as ILambdaSerializer; @@ -1426,7 +1364,7 @@ public void CognitoCustomEmailSenderEventTest(Type serializerType) Assert.Equal("code", cognitoCustomEmailSenderEvent.Request.Code); Assert.Equal("type", cognitoCustomEmailSenderEvent.Request.Type); - MemoryStream ms = new MemoryStream(); + var ms = new MemoryStream(); serializer.Serialize(cognitoCustomEmailSenderEvent, ms); ms.Position = 0; var json = new StreamReader(ms).ReadToEnd(); @@ -1439,10 +1377,8 @@ public void CognitoCustomEmailSenderEventTest(Type serializerType) [Theory] [InlineData(typeof(JsonSerializer))] -#if NETCOREAPP3_1_OR_GREATER [InlineData(typeof(Amazon.Lambda.Serialization.SystemTextJson.LambdaJsonSerializer))] [InlineData(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))] -#endif public void CognitoCustomSmsSenderEventTest(Type serializerType) { var serializer = Activator.CreateInstance(serializerType) as ILambdaSerializer; @@ -1489,30 +1425,28 @@ private static void AssertBaseClass(CognitoTriggerEvent(fileStream); - Assert.Equal(configEvent.ConfigRuleId, "config-rule-0123456"); - Assert.Equal(configEvent.Version, "1.0"); - Assert.Equal(configEvent.ConfigRuleName, "periodic-config-rule"); - Assert.Equal(configEvent.ConfigRuleArn, "arn:aws:config:us-east-1:012345678912:config-rule/config-rule-0123456"); - Assert.Equal(configEvent.InvokingEvent, ConfigInvokingEvent); - Assert.Equal(configEvent.ResultToken, "myResultToken"); - Assert.Equal(configEvent.EventLeftScope, false); - Assert.Equal(configEvent.RuleParameters, "{\"\":\"\"}"); - Assert.Equal(configEvent.ExecutionRoleArn, "arn:aws:iam::012345678912:role/config-role"); - Assert.Equal(configEvent.AccountId, "012345678912"); + Assert.Equal("config-rule-0123456", configEvent.ConfigRuleId); + Assert.Equal("1.0", configEvent.Version); + Assert.Equal("periodic-config-rule", configEvent.ConfigRuleName); + Assert.Equal("arn:aws:config:us-east-1:012345678912:config-rule/config-rule-0123456", configEvent.ConfigRuleArn); + Assert.Equal(ConfigInvokingEvent, configEvent.InvokingEvent); + Assert.Equal("myResultToken", configEvent.ResultToken); + Assert.False(configEvent.EventLeftScope); + Assert.Equal("{\"\":\"\"}", configEvent.RuleParameters); + Assert.Equal("arn:aws:iam::012345678912:role/config-role", configEvent.ExecutionRoleArn); + Assert.Equal("012345678912", configEvent.AccountId); Handle(configEvent); } @@ -1527,50 +1461,46 @@ private static void Handle(ConfigEvent configEvent) [Theory] [InlineData(typeof(JsonSerializer))] -#if NETCOREAPP3_1_OR_GREATER [InlineData(typeof(Amazon.Lambda.Serialization.SystemTextJson.LambdaJsonSerializer))] [InlineData(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))] -#endif public void ConnectContactFlowTest(Type serializerType) { var serializer = Activator.CreateInstance(serializerType) as ILambdaSerializer; using (var fileStream = LoadJsonTestFile("connect-contactflow-event.json")) { var contactFlowEvent = serializer.Deserialize(fileStream); - Assert.Equal(contactFlowEvent.Name, "ContactFlowEvent"); + Assert.Equal("ContactFlowEvent", contactFlowEvent.Name); Assert.NotNull(contactFlowEvent.Details); Assert.NotNull(contactFlowEvent.Details.ContactData); Assert.NotNull(contactFlowEvent.Details.ContactData.Attributes); - Assert.Equal(contactFlowEvent.Details.ContactData.Attributes.Count, 0); - Assert.Equal(contactFlowEvent.Details.ContactData.Channel, "VOICE"); - Assert.Equal(contactFlowEvent.Details.ContactData.ContactId, "4a573372-1f28-4e26-b97b-XXXXXXXXXXX"); + Assert.Empty(contactFlowEvent.Details.ContactData.Attributes); + Assert.Equal("VOICE", contactFlowEvent.Details.ContactData.Channel); + Assert.Equal("4a573372-1f28-4e26-b97b-XXXXXXXXXXX", contactFlowEvent.Details.ContactData.ContactId); Assert.NotNull(contactFlowEvent.Details.ContactData.CustomerEndpoint); - Assert.Equal(contactFlowEvent.Details.ContactData.CustomerEndpoint.Address, "+1234567890"); - Assert.Equal(contactFlowEvent.Details.ContactData.CustomerEndpoint.Type, "TELEPHONE_NUMBER"); - Assert.Equal(contactFlowEvent.Details.ContactData.InitialContactId, "4a573372-1f28-4e26-b97b-XXXXXXXXXXX"); - Assert.Equal(contactFlowEvent.Details.ContactData.InitiationMethod, "INBOUND | OUTBOUND | TRANSFER | CALLBACK"); - Assert.Equal(contactFlowEvent.Details.ContactData.InstanceARN, "arn:aws:connect:aws-region:1234567890:instance/c8c0e68d-2200-4265-82c0-XXXXXXXXXX"); - Assert.Equal(contactFlowEvent.Details.ContactData.PreviousContactId, "4a573372-1f28-4e26-b97b-XXXXXXXXXXX"); + Assert.Equal("+1234567890", contactFlowEvent.Details.ContactData.CustomerEndpoint.Address); + Assert.Equal("TELEPHONE_NUMBER", contactFlowEvent.Details.ContactData.CustomerEndpoint.Type); + Assert.Equal("4a573372-1f28-4e26-b97b-XXXXXXXXXXX", contactFlowEvent.Details.ContactData.InitialContactId); + Assert.Equal("INBOUND | OUTBOUND | TRANSFER | CALLBACK", contactFlowEvent.Details.ContactData.InitiationMethod); + Assert.Equal("arn:aws:connect:aws-region:1234567890:instance/c8c0e68d-2200-4265-82c0-XXXXXXXXXX", contactFlowEvent.Details.ContactData.InstanceARN); + Assert.Equal("4a573372-1f28-4e26-b97b-XXXXXXXXXXX", contactFlowEvent.Details.ContactData.PreviousContactId); Assert.NotNull(contactFlowEvent.Details.ContactData.Queue); - Assert.Equal(contactFlowEvent.Details.ContactData.Queue.Arn, "arn:aws:connect:eu-west-2:111111111111:instance/cccccccc-bbbb-dddd-eeee-ffffffffffff/queue/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"); - Assert.Equal(contactFlowEvent.Details.ContactData.Queue.Name, "PasswordReset"); + Assert.Equal("arn:aws:connect:eu-west-2:111111111111:instance/cccccccc-bbbb-dddd-eeee-ffffffffffff/queue/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee", contactFlowEvent.Details.ContactData.Queue.Arn); + Assert.Equal("PasswordReset", contactFlowEvent.Details.ContactData.Queue.Name); Assert.NotNull(contactFlowEvent.Details.ContactData.SystemEndpoint); - Assert.Equal(contactFlowEvent.Details.ContactData.SystemEndpoint.Address, "+1234567890"); - Assert.Equal(contactFlowEvent.Details.ContactData.SystemEndpoint.Type, "TELEPHONE_NUMBER"); + Assert.Equal("+1234567890", contactFlowEvent.Details.ContactData.SystemEndpoint.Address); + Assert.Equal("TELEPHONE_NUMBER", contactFlowEvent.Details.ContactData.SystemEndpoint.Type); Assert.NotNull(contactFlowEvent.Details.Parameters); - Assert.Equal(contactFlowEvent.Details.Parameters.Count, 1); - Assert.Equal(contactFlowEvent.Details.Parameters["sentAttributeKey"], "sentAttributeValue"); + Assert.Single(contactFlowEvent.Details.Parameters); + Assert.Equal("sentAttributeValue", contactFlowEvent.Details.Parameters["sentAttributeKey"]); } } [Theory] [InlineData(typeof(JsonSerializer))] -#if NETCOREAPP3_1_OR_GREATER [InlineData(typeof(Amazon.Lambda.Serialization.SystemTextJson.LambdaJsonSerializer))] [InlineData(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))] -#endif public void SimpleEmailTest(Type serializerType) { var serializer = Activator.CreateInstance(serializerType) as ILambdaSerializer; @@ -1578,67 +1508,65 @@ public void SimpleEmailTest(Type serializerType) { var sesEvent = serializer.Deserialize>(fileStream); - Assert.Equal(sesEvent.Records.Count, 1); + Assert.Single(sesEvent.Records); var record = sesEvent.Records[0]; - Assert.Equal(record.EventVersion, "1.0"); - Assert.Equal(record.EventSource, "aws:ses"); - - Assert.Equal(record.Ses.Mail.CommonHeaders.From.Count, 1); - Assert.Equal(record.Ses.Mail.CommonHeaders.From[0], "Amazon Web Services "); - Assert.Equal(record.Ses.Mail.CommonHeaders.To.Count, 1); - Assert.Equal(record.Ses.Mail.CommonHeaders.To[0], "lambda@amazon.com"); - Assert.Equal(record.Ses.Mail.CommonHeaders.ReturnPath, "aws@amazon.com"); - Assert.Equal(record.Ses.Mail.CommonHeaders.MessageId, ""); - Assert.Equal(record.Ses.Mail.CommonHeaders.Date, "Mon, 5 Dec 2016 18:40:08 -0800"); - Assert.Equal(record.Ses.Mail.CommonHeaders.Subject, "Test Subject"); - Assert.Equal(record.Ses.Mail.Source, "aws@amazon.com"); - Assert.Equal(record.Ses.Mail.Timestamp.ToUniversalTime(), DateTime.Parse("2016-12-06T02:40:08.000Z").ToUniversalTime()); - Assert.Equal(record.Ses.Mail.Destination.Count, 1); - Assert.Equal(record.Ses.Mail.Destination[0], "lambda@amazon.com"); - Assert.Equal(record.Ses.Mail.Headers.Count, 10); - Assert.Equal(record.Ses.Mail.Headers[0].Name, "Return-Path"); - Assert.Equal(record.Ses.Mail.Headers[0].Value, ""); - Assert.Equal(record.Ses.Mail.Headers[1].Name, "Received"); - Assert.Equal(record.Ses.Mail.Headers[1].Value, "from mx.amazon.com (mx.amazon.com [127.0.0.1]) by inbound-smtp.us-east-1.amazonaws.com with SMTP id 6n4thuhcbhpfiuf25gshf70rss364fuejrvmqko1 for lambda@amazon.com; Tue, 06 Dec 2016 02:40:10 +0000 (UTC)"); - Assert.Equal(record.Ses.Mail.Headers[2].Name, "DKIM-Signature"); - Assert.Equal(record.Ses.Mail.Headers[2].Value, "v=1; a=rsa-sha256; c=relaxed/relaxed; d=iatn.net; s=amazon; h=mime-version:from:date:message-id:subject:to; bh=chlJxa/vZ11+0O9lf4tKDM/CcPjup2nhhdITm+hSf3c=; b=SsoNPK0wX7umtWnw8pln3YSib+E09XO99d704QdSc1TR1HxM0OTti/UaFxVD4e5b0+okBqo3rgVeWgNZ0sWZEUhBaZwSL3kTd/nHkcPexeV0XZqEgms1vmbg75F6vlz9igWflO3GbXyTRBNMM0gUXKU/686hpVW6aryEIfM/rLY="); - Assert.Equal(record.Ses.Mail.Headers[3].Name, "MIME-Version"); - Assert.Equal(record.Ses.Mail.Headers[3].Value, "1.0"); - Assert.Equal(record.Ses.Mail.Headers[4].Name, "From"); - Assert.Equal(record.Ses.Mail.Headers[4].Value, "Amazon Web Services "); - Assert.Equal(record.Ses.Mail.Headers[5].Name, "Date"); - Assert.Equal(record.Ses.Mail.Headers[5].Value, "Mon, 5 Dec 2016 18:40:08 -0800"); - Assert.Equal(record.Ses.Mail.Headers[6].Name, "Message-ID"); - Assert.Equal(record.Ses.Mail.Headers[6].Value, ""); - Assert.Equal(record.Ses.Mail.Headers[7].Name, "Subject"); - Assert.Equal(record.Ses.Mail.Headers[7].Value, "Test Subject"); - Assert.Equal(record.Ses.Mail.Headers[8].Name, "To"); - Assert.Equal(record.Ses.Mail.Headers[8].Value, "lambda@amazon.com"); - Assert.Equal(record.Ses.Mail.Headers[9].Name, "Content-Type"); - Assert.Equal(record.Ses.Mail.Headers[9].Value, "multipart/alternative; boundary=94eb2c0742269658b10542f452a9"); - Assert.Equal(record.Ses.Mail.HeadersTruncated, false); - Assert.Equal(record.Ses.Mail.MessageId, "6n4thuhcbhpfiuf25gshf70rss364fuejrvmqko1"); - - Assert.Equal(record.Ses.Receipt.Recipients.Count, 1); - Assert.Equal(record.Ses.Receipt.Recipients[0], "lambda@amazon.com"); - Assert.Equal(record.Ses.Receipt.Timestamp.ToUniversalTime(), DateTime.Parse("2016-12-06T02:40:08.000Z").ToUniversalTime()); - Assert.Equal(record.Ses.Receipt.SpamVerdict.Status, "PASS"); - Assert.Equal(record.Ses.Receipt.DKIMVerdict.Status, "PASS"); - Assert.Equal(record.Ses.Receipt.SPFVerdict.Status, "PASS"); - Assert.Equal(record.Ses.Receipt.VirusVerdict.Status, "PASS"); - Assert.Equal(record.Ses.Receipt.DMARCVerdict.Status, "PASS"); - Assert.Equal(record.Ses.Receipt.ProcessingTimeMillis, 574); + Assert.Equal("1.0", record.EventVersion); + Assert.Equal("aws:ses", record.EventSource); + + Assert.Single(record.Ses.Mail.CommonHeaders.From); + Assert.Equal("Amazon Web Services ", record.Ses.Mail.CommonHeaders.From[0]); + Assert.Single(record.Ses.Mail.CommonHeaders.To); + Assert.Equal("lambda@amazon.com", record.Ses.Mail.CommonHeaders.To[0]); + Assert.Equal("aws@amazon.com", record.Ses.Mail.CommonHeaders.ReturnPath); + Assert.Equal("", record.Ses.Mail.CommonHeaders.MessageId); + Assert.Equal("Mon, 5 Dec 2016 18:40:08 -0800", record.Ses.Mail.CommonHeaders.Date); + Assert.Equal("Test Subject", record.Ses.Mail.CommonHeaders.Subject); + Assert.Equal("aws@amazon.com", record.Ses.Mail.Source); + Assert.Equal(DateTime.Parse("2016-12-06T02:40:08.000Z").ToUniversalTime(), record.Ses.Mail.Timestamp.ToUniversalTime()); + Assert.Single(record.Ses.Mail.Destination); + Assert.Equal("lambda@amazon.com", record.Ses.Mail.Destination[0]); + Assert.Equal(10, record.Ses.Mail.Headers.Count); + Assert.Equal("Return-Path", record.Ses.Mail.Headers[0].Name); + Assert.Equal("", record.Ses.Mail.Headers[0].Value); + Assert.Equal("Received", record.Ses.Mail.Headers[1].Name); + Assert.Equal("from mx.amazon.com (mx.amazon.com [127.0.0.1]) by inbound-smtp.us-east-1.amazonaws.com with SMTP id 6n4thuhcbhpfiuf25gshf70rss364fuejrvmqko1 for lambda@amazon.com; Tue, 06 Dec 2016 02:40:10 +0000 (UTC)", record.Ses.Mail.Headers[1].Value); + Assert.Equal("DKIM-Signature", record.Ses.Mail.Headers[2].Name); + Assert.Equal("v=1; a=rsa-sha256; c=relaxed/relaxed; d=iatn.net; s=amazon; h=mime-version:from:date:message-id:subject:to; bh=chlJxa/vZ11+0O9lf4tKDM/CcPjup2nhhdITm+hSf3c=; b=SsoNPK0wX7umtWnw8pln3YSib+E09XO99d704QdSc1TR1HxM0OTti/UaFxVD4e5b0+okBqo3rgVeWgNZ0sWZEUhBaZwSL3kTd/nHkcPexeV0XZqEgms1vmbg75F6vlz9igWflO3GbXyTRBNMM0gUXKU/686hpVW6aryEIfM/rLY=", record.Ses.Mail.Headers[2].Value); + Assert.Equal("MIME-Version", record.Ses.Mail.Headers[3].Name); + Assert.Equal("1.0", record.Ses.Mail.Headers[3].Value); + Assert.Equal("From", record.Ses.Mail.Headers[4].Name); + Assert.Equal("Amazon Web Services ", record.Ses.Mail.Headers[4].Value); + Assert.Equal("Date", record.Ses.Mail.Headers[5].Name); + Assert.Equal("Mon, 5 Dec 2016 18:40:08 -0800", record.Ses.Mail.Headers[5].Value); + Assert.Equal("Message-ID", record.Ses.Mail.Headers[6].Name); + Assert.Equal("", record.Ses.Mail.Headers[6].Value); + Assert.Equal("Subject", record.Ses.Mail.Headers[7].Name); + Assert.Equal("Test Subject", record.Ses.Mail.Headers[7].Value); + Assert.Equal("To", record.Ses.Mail.Headers[8].Name); + Assert.Equal("lambda@amazon.com", record.Ses.Mail.Headers[8].Value); + Assert.Equal("Content-Type", record.Ses.Mail.Headers[9].Name); + Assert.Equal("multipart/alternative; boundary=94eb2c0742269658b10542f452a9", record.Ses.Mail.Headers[9].Value); + Assert.False(record.Ses.Mail.HeadersTruncated); + Assert.Equal("6n4thuhcbhpfiuf25gshf70rss364fuejrvmqko1", record.Ses.Mail.MessageId); + + Assert.Single(record.Ses.Receipt.Recipients); + Assert.Equal("lambda@amazon.com", record.Ses.Receipt.Recipients[0]); + Assert.Equal(DateTime.Parse("2016-12-06T02:40:08.000Z").ToUniversalTime(), record.Ses.Receipt.Timestamp.ToUniversalTime()); + Assert.Equal("PASS", record.Ses.Receipt.SpamVerdict.Status); + Assert.Equal("PASS", record.Ses.Receipt.DKIMVerdict.Status); + Assert.Equal("PASS", record.Ses.Receipt.SPFVerdict.Status); + Assert.Equal("PASS", record.Ses.Receipt.VirusVerdict.Status); + Assert.Equal("PASS", record.Ses.Receipt.DMARCVerdict.Status); + Assert.Equal(574, record.Ses.Receipt.ProcessingTimeMillis); Handle(sesEvent); } } [Theory] [InlineData(typeof(JsonSerializer))] -#if NETCOREAPP3_1_OR_GREATER [InlineData(typeof(Amazon.Lambda.Serialization.SystemTextJson.LambdaJsonSerializer))] [InlineData(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))] -#endif public void SimpleEmailLambdaActionTest(Type serializerType) { var serializer = Activator.CreateInstance(serializerType) as ILambdaSerializer; @@ -1646,22 +1574,20 @@ public void SimpleEmailLambdaActionTest(Type serializerType) { var sesEvent = serializer.Deserialize>(fileStream); - Assert.Equal(sesEvent.Records.Count, 1); + Assert.Single(sesEvent.Records); var record = sesEvent.Records[0]; - Assert.Equal(record.Ses.Receipt.Action.Type, "Lambda"); - Assert.Equal(record.Ses.Receipt.Action.InvocationType, "Event"); - Assert.Equal(record.Ses.Receipt.Action.FunctionArn, "arn:aws:lambda:us-east-1:000000000000:function:my-ses-lambda-function"); + Assert.Equal("Lambda", record.Ses.Receipt.Action.Type); + Assert.Equal("Event", record.Ses.Receipt.Action.InvocationType); + Assert.Equal("arn:aws:lambda:us-east-1:000000000000:function:my-ses-lambda-function", record.Ses.Receipt.Action.FunctionArn); Handle(sesEvent); } } [Theory] [InlineData(typeof(JsonSerializer))] -#if NETCOREAPP3_1_OR_GREATER [InlineData(typeof(Amazon.Lambda.Serialization.SystemTextJson.LambdaJsonSerializer))] [InlineData(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))] -#endif public void SimpleEmailS3ActionTest(Type serializerType) { var serializer = Activator.CreateInstance(serializerType) as ILambdaSerializer; @@ -1669,14 +1595,14 @@ public void SimpleEmailS3ActionTest(Type serializerType) { var sesEvent = serializer.Deserialize>(fileStream); - Assert.Equal(sesEvent.Records.Count, 1); + Assert.Single(sesEvent.Records); var record = sesEvent.Records[0]; - Assert.Equal(record.Ses.Receipt.Action.Type, "S3"); - Assert.Equal(record.Ses.Receipt.Action.TopicArn, "arn:aws:sns:eu-west-1:123456789:ses-email-received"); - Assert.Equal(record.Ses.Receipt.Action.BucketName, "my-ses-inbox"); - Assert.Equal(record.Ses.Receipt.Action.ObjectKeyPrefix, "important"); - Assert.Equal(record.Ses.Receipt.Action.ObjectKey, "important/fiddlyfaddlyhiddlyhoodly"); + Assert.Equal("S3", record.Ses.Receipt.Action.Type); + Assert.Equal("arn:aws:sns:eu-west-1:123456789:ses-email-received", record.Ses.Receipt.Action.TopicArn); + Assert.Equal("my-ses-inbox", record.Ses.Receipt.Action.BucketName); + Assert.Equal("important", record.Ses.Receipt.Action.ObjectKeyPrefix); + Assert.Equal("important/fiddlyfaddlyhiddlyhoodly", record.Ses.Receipt.Action.ObjectKey); Handle(sesEvent); } @@ -1693,10 +1619,8 @@ private static void Handle(SimpleEmailEvent sesE [Theory] [InlineData(typeof(JsonSerializer))] -#if NETCOREAPP3_1_OR_GREATER [InlineData(typeof(Amazon.Lambda.Serialization.SystemTextJson.LambdaJsonSerializer))] [InlineData(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))] -#endif public void SNSTest(Type serializerType) { var serializer = Activator.CreateInstance(serializerType) as ILambdaSerializer; @@ -1704,27 +1628,27 @@ public void SNSTest(Type serializerType) { var snsEvent = serializer.Deserialize(fileStream); - Assert.Equal(snsEvent.Records.Count, 1); + Assert.Single(snsEvent.Records); var record = snsEvent.Records[0]; - Assert.Equal(record.EventVersion, "1.0"); - Assert.Equal(record.EventSubscriptionArn, "arn:aws:sns:EXAMPLE"); - Assert.Equal(record.EventSource, "aws:sns"); - Assert.Equal(record.Sns.SignatureVersion, "1"); - Assert.Equal(record.Sns.Timestamp.ToUniversalTime(), DateTime.Parse("1970-01-01T00:00:00.000Z").ToUniversalTime()); - Assert.Equal(record.Sns.Signature, "EXAMPLE"); - Assert.Equal(record.Sns.SigningCertUrl, "EXAMPLE"); - Assert.Equal(record.Sns.MessageId, "95df01b4-ee98-5cb9-9903-4c221d41eb5e"); - Assert.Equal(record.Sns.Message, "Hello from SNS!"); + Assert.Equal("1.0", record.EventVersion); + Assert.Equal("arn:aws:sns:EXAMPLE", record.EventSubscriptionArn); + Assert.Equal("aws:sns", record.EventSource); + Assert.Equal("1", record.Sns.SignatureVersion); + Assert.Equal(DateTime.Parse("1970-01-01T00:00:00.000Z").ToUniversalTime(), record.Sns.Timestamp.ToUniversalTime()); + Assert.Equal("EXAMPLE", record.Sns.Signature); + Assert.Equal("EXAMPLE", record.Sns.SigningCertUrl); + Assert.Equal("95df01b4-ee98-5cb9-9903-4c221d41eb5e", record.Sns.MessageId); + Assert.Equal("Hello from SNS!", record.Sns.Message); Assert.True(record.Sns.MessageAttributes.ContainsKey("Test")); - Assert.Equal(record.Sns.MessageAttributes["Test"].Type, "String"); - Assert.Equal(record.Sns.MessageAttributes["Test"].Value, "TestString"); + Assert.Equal("String", record.Sns.MessageAttributes["Test"].Type); + Assert.Equal("TestString", record.Sns.MessageAttributes["Test"].Value); Assert.True(record.Sns.MessageAttributes.ContainsKey("TestBinary")); - Assert.Equal(record.Sns.MessageAttributes["TestBinary"].Type, "Binary"); - Assert.Equal(record.Sns.MessageAttributes["TestBinary"].Value, "TestBinary"); - Assert.Equal(record.Sns.Type, "Notification"); - Assert.Equal(record.Sns.UnsubscribeUrl, "EXAMPLE"); - Assert.Equal(record.Sns.TopicArn, "arn:aws:sns:EXAMPLE"); - Assert.Equal(record.Sns.Subject, "TestInvoke"); + Assert.Equal("Binary", record.Sns.MessageAttributes["TestBinary"].Type); + Assert.Equal("TestBinary", record.Sns.MessageAttributes["TestBinary"].Value); + Assert.Equal("Notification", record.Sns.Type); + Assert.Equal("EXAMPLE", record.Sns.UnsubscribeUrl); + Assert.Equal("arn:aws:sns:EXAMPLE", record.Sns.TopicArn); + Assert.Equal("TestInvoke", record.Sns.Subject); Handle(snsEvent); } @@ -1741,10 +1665,8 @@ private static void Handle(SNSEvent snsEvent) [Theory] [InlineData(typeof(JsonSerializer))] -#if NETCOREAPP3_1_OR_GREATER [InlineData(typeof(Amazon.Lambda.Serialization.SystemTextJson.LambdaJsonSerializer))] [InlineData(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))] -#endif public void SQSTest(Type serializerType) { var serializer = Activator.CreateInstance(serializerType) as ILambdaSerializer; @@ -1752,7 +1674,7 @@ public void SQSTest(Type serializerType) { var sqsEvent = serializer.Deserialize(fileStream); - Assert.Equal(sqsEvent.Records.Count, 1); + Assert.Single(sqsEvent.Records); var record = sqsEvent.Records[0]; Assert.Equal("MessageID", record.MessageId); Assert.Equal("MessageReceiptHandle", record.ReceiptHandle); @@ -1811,10 +1733,8 @@ private static void Handle(SQSEvent sqsEvent) [Theory] [InlineData(typeof(JsonSerializer))] -#if NETCOREAPP3_1_OR_GREATER [InlineData(typeof(Amazon.Lambda.Serialization.SystemTextJson.LambdaJsonSerializer))] [InlineData(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))] -#endif public void SQSBatchResponseTest(Type serializerType) { var serializer = Activator.CreateInstance(serializerType) as ILambdaSerializer; @@ -1822,7 +1742,7 @@ public void SQSBatchResponseTest(Type serializerType) { var sqsBatchResponse = serializer.Deserialize(fileStream); - Assert.Equal(sqsBatchResponse.BatchItemFailures.Count, 2); + Assert.Equal(2, sqsBatchResponse.BatchItemFailures.Count); { var item1 = sqsBatchResponse.BatchItemFailures[0]; Assert.NotNull(item1); @@ -1848,10 +1768,8 @@ public void SQSBatchResponseTest(Type serializerType) [Theory] [InlineData(typeof(JsonSerializer))] -#if NETCOREAPP3_1_OR_GREATER [InlineData(typeof(Amazon.Lambda.Serialization.SystemTextJson.LambdaJsonSerializer))] [InlineData(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))] -#endif public void APIGatewayProxyRequestTest(Type serializerType) { var serializer = Activator.CreateInstance(serializerType) as ILambdaSerializer; @@ -1859,48 +1777,48 @@ public void APIGatewayProxyRequestTest(Type serializerType) { var proxyEvent = serializer.Deserialize(fileStream); - Assert.Equal(proxyEvent.Resource, "/{proxy+}"); - Assert.Equal(proxyEvent.Path, "/hello/world"); - Assert.Equal(proxyEvent.HttpMethod, "POST"); - Assert.Equal(proxyEvent.Body, "{\r\n\t\"a\": 1\r\n}"); + Assert.Equal("/{proxy+}", proxyEvent.Resource); + Assert.Equal("/hello/world", proxyEvent.Path); + Assert.Equal("POST", proxyEvent.HttpMethod); + Assert.Equal("{\r\n\t\"a\": 1\r\n}", proxyEvent.Body); var headers = proxyEvent.Headers; - Assert.Equal(headers["Accept"], "*/*"); - Assert.Equal(headers["Accept-Encoding"], "gzip, deflate"); - Assert.Equal(headers["cache-control"], "no-cache"); - Assert.Equal(headers["CloudFront-Forwarded-Proto"], "https"); + Assert.Equal("*/*", headers["Accept"]); + Assert.Equal("gzip, deflate", headers["Accept-Encoding"]); + Assert.Equal("no-cache", headers["cache-control"]); + Assert.Equal("https", headers["CloudFront-Forwarded-Proto"]); var queryStringParameters = proxyEvent.QueryStringParameters; - Assert.Equal(queryStringParameters["name"], "me"); + Assert.Equal("me", queryStringParameters["name"]); var pathParameters = proxyEvent.PathParameters; - Assert.Equal(pathParameters["proxy"], "hello/world"); + Assert.Equal("hello/world", pathParameters["proxy"]); var stageVariables = proxyEvent.StageVariables; - Assert.Equal(stageVariables["stageVariableName"], "stageVariableValue"); + Assert.Equal("stageVariableValue", stageVariables["stageVariableName"]); var requestContext = proxyEvent.RequestContext; - Assert.Equal(requestContext.AccountId, "12345678912"); - Assert.Equal(requestContext.ResourceId, "roq9wj"); - Assert.Equal(requestContext.Stage, "testStage"); - Assert.Equal(requestContext.RequestId, "deef4878-7910-11e6-8f14-25afc3e9ae33"); - Assert.Equal(requestContext.ConnectionId, "d034bc98-beed-4fdf-9e85-11bfc15bf734"); - Assert.Equal(requestContext.DomainName, "somerandomdomain.net"); + Assert.Equal("12345678912", requestContext.AccountId); + Assert.Equal("roq9wj", requestContext.ResourceId); + Assert.Equal("testStage", requestContext.Stage); + Assert.Equal("deef4878-7910-11e6-8f14-25afc3e9ae33", requestContext.RequestId); + Assert.Equal("d034bc98-beed-4fdf-9e85-11bfc15bf734", requestContext.ConnectionId); + Assert.Equal("somerandomdomain.net", requestContext.DomainName); Assert.Equal(1519166937665, requestContext.RequestTimeEpoch); Assert.Equal("20/Feb/2018:22:48:57 +0000", requestContext.RequestTime); var identity = requestContext.Identity; - Assert.Equal(identity.CognitoIdentityPoolId, "theCognitoIdentityPoolId"); - Assert.Equal(identity.AccountId, "theAccountId"); - Assert.Equal(identity.CognitoIdentityId, "theCognitoIdentityId"); - Assert.Equal(identity.Caller, "theCaller"); - Assert.Equal(identity.ApiKey, "theApiKey"); - Assert.Equal(identity.SourceIp, "192.168.196.186"); - Assert.Equal(identity.CognitoAuthenticationType, "theCognitoAuthenticationType"); - Assert.Equal(identity.CognitoAuthenticationProvider, "theCognitoAuthenticationProvider"); - Assert.Equal(identity.UserArn, "theUserArn"); - Assert.Equal(identity.UserAgent, "PostmanRuntime/2.4.5"); - Assert.Equal(identity.User, "theUser"); + Assert.Equal("theCognitoIdentityPoolId", identity.CognitoIdentityPoolId); + Assert.Equal("theAccountId", identity.AccountId); + Assert.Equal("theCognitoIdentityId", identity.CognitoIdentityId); + Assert.Equal("theCaller", identity.Caller); + Assert.Equal("theApiKey", identity.ApiKey); + Assert.Equal("192.168.196.186", identity.SourceIp); + Assert.Equal("theCognitoAuthenticationType", identity.CognitoAuthenticationType); + Assert.Equal("theCognitoAuthenticationProvider", identity.CognitoAuthenticationProvider); + Assert.Equal("theUserArn", identity.UserArn); + Assert.Equal("PostmanRuntime/2.4.5", identity.UserAgent); + Assert.Equal("theUser", identity.User); Assert.Equal("IAM_user_access_key", identity.AccessKey); var clientCert = identity.ClientCert; @@ -1932,10 +1850,8 @@ private static APIGatewayProxyResponse Handle(APIGatewayProxyRequest apigProxyEv [Theory] [InlineData(typeof(JsonSerializer))] -#if NETCOREAPP3_1_OR_GREATER [InlineData(typeof(Amazon.Lambda.Serialization.SystemTextJson.LambdaJsonSerializer))] [InlineData(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))] -#endif public void APIGatewayProxyResponseTest(Type serializerType) { var serializer = Activator.CreateInstance(serializerType) as ILambdaSerializer; @@ -1955,28 +1871,28 @@ public void APIGatewayProxyResponseTest(Type serializerType) serializedJson = Encoding.UTF8.GetString(stream.ToArray()); } - JObject root = Newtonsoft.Json.JsonConvert.DeserializeObject(serializedJson) as JObject; - Assert.Equal(root["statusCode"], 200); - Assert.Equal(root["body"], "theBody"); + var root = Newtonsoft.Json.JsonConvert.DeserializeObject(serializedJson) as JObject; + Assert.Equal(200, root["statusCode"]); + Assert.Equal("theBody", root["body"]); Assert.NotNull(root["headers"]); var headers = root["headers"] as JObject; - Assert.Equal(headers["Header1"], "Value1"); - Assert.Equal(headers["Header2"], "Value2"); + Assert.Equal("Value1", headers["Header1"]); + Assert.Equal("Value2", headers["Header2"]); } [Theory] [InlineData(typeof(JsonSerializer))] -#if NETCOREAPP3_1_OR_GREATER [InlineData(typeof(Amazon.Lambda.Serialization.SystemTextJson.LambdaJsonSerializer))] [InlineData(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))] -#endif public void APIGatewayAuthorizerResponseTest(Type serializerType) { var serializer = Activator.CreateInstance(serializerType) as ILambdaSerializer; - var context = new APIGatewayCustomAuthorizerContextOutput(); - context["field1"] = "value1"; - context["field2"] = "value2"; + var context = new APIGatewayCustomAuthorizerContextOutput + { + ["field1"] = "value1", + ["field2"] = "value2" + }; var response = new APIGatewayCustomAuthorizerResponse { @@ -1990,7 +1906,7 @@ public void APIGatewayAuthorizerResponseTest(Type serializerType) { new APIGatewayCustomAuthorizerPolicy.IAMPolicyStatement { - Action = new HashSet{ "execute-api:Invoke" }, + Action = ["execute-api:Invoke"], Effect = "Allow", Resource = new HashSet{ "*" } } @@ -2007,7 +1923,7 @@ public void APIGatewayAuthorizerResponseTest(Type serializerType) serializedJson = Encoding.UTF8.GetString(stream.ToArray()); } - JObject root = Newtonsoft.Json.JsonConvert.DeserializeObject(serializedJson) as JObject; + var root = Newtonsoft.Json.JsonConvert.DeserializeObject(serializedJson) as JObject; Assert.Equal("prin1", root["principalId"]); Assert.Equal("usageKey", root["usageIdentifierKey"]); @@ -2023,16 +1939,16 @@ public void APIGatewayAuthorizerResponseTest(Type serializerType) [Theory] [InlineData(typeof(JsonSerializer))] -#if NETCOREAPP3_1_OR_GREATER [InlineData(typeof(Amazon.Lambda.Serialization.SystemTextJson.LambdaJsonSerializer))] [InlineData(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))] -#endif public void APIGatewayAuthorizerWithSimpleIAMConditionResponseTest(Type serializerType) { var serializer = Activator.CreateInstance(serializerType) as ILambdaSerializer; - var context = new APIGatewayCustomAuthorizerContextOutput(); - context["field1"] = "value1"; - context["field2"] = "value2"; + var context = new APIGatewayCustomAuthorizerContextOutput + { + ["field1"] = "value1", + ["field2"] = "value2" + }; var response = new APIGatewayCustomAuthorizerResponse { @@ -2042,8 +1958,8 @@ public void APIGatewayAuthorizerWithSimpleIAMConditionResponseTest(Type serializ PolicyDocument = new APIGatewayCustomAuthorizerPolicy { Version = "2012-10-17", - Statement = new List - { + Statement = + [ new APIGatewayCustomAuthorizerPolicy.IAMPolicyStatement { Action = new HashSet{ "execute-api:Invoke" }, @@ -2058,7 +1974,7 @@ public void APIGatewayAuthorizerWithSimpleIAMConditionResponseTest(Type serializ } } } - } + ] } }; @@ -2071,7 +1987,7 @@ public void APIGatewayAuthorizerWithSimpleIAMConditionResponseTest(Type serializ serializedJson = Encoding.UTF8.GetString(stream.ToArray()); } - JObject root = Newtonsoft.Json.JsonConvert.DeserializeObject(serializedJson) as JObject; + var root = Newtonsoft.Json.JsonConvert.DeserializeObject(serializedJson) as JObject; Assert.Equal("prin1", root["principalId"]); Assert.Equal("usageKey", root["usageIdentifierKey"]); @@ -2087,16 +2003,16 @@ public void APIGatewayAuthorizerWithSimpleIAMConditionResponseTest(Type serializ [Theory] [InlineData(typeof(JsonSerializer))] -#if NETCOREAPP3_1_OR_GREATER [InlineData(typeof(Amazon.Lambda.Serialization.SystemTextJson.LambdaJsonSerializer))] [InlineData(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))] -#endif public void APIGatewayAuthorizerWithMultiValueIAMConditionResponseTest(Type serializerType) { var serializer = Activator.CreateInstance(serializerType) as ILambdaSerializer; - var context = new APIGatewayCustomAuthorizerContextOutput(); - context["field1"] = "value1"; - context["field2"] = "value2"; + var context = new APIGatewayCustomAuthorizerContextOutput + { + ["field1"] = "value1", + ["field2"] = "value2" + }; var response = new APIGatewayCustomAuthorizerResponse { @@ -2106,8 +2022,8 @@ public void APIGatewayAuthorizerWithMultiValueIAMConditionResponseTest(Type seri PolicyDocument = new APIGatewayCustomAuthorizerPolicy { Version = "2012-10-17", - Statement = new List - { + Statement = + [ new APIGatewayCustomAuthorizerPolicy.IAMPolicyStatement { Action = new HashSet{ "execute-api:Invoke" }, @@ -2132,7 +2048,7 @@ public void APIGatewayAuthorizerWithMultiValueIAMConditionResponseTest(Type seri } } } - } + ] } }; @@ -2145,7 +2061,7 @@ public void APIGatewayAuthorizerWithMultiValueIAMConditionResponseTest(Type seri serializedJson = Encoding.UTF8.GetString(stream.ToArray()); } - JObject root = Newtonsoft.Json.JsonConvert.DeserializeObject(serializedJson) as JObject; + var root = Newtonsoft.Json.JsonConvert.DeserializeObject(serializedJson) as JObject; Assert.Equal("prin1", root["principalId"]); Assert.Equal("usageKey", root["usageIdentifierKey"]); @@ -2170,16 +2086,16 @@ public void APIGatewayAuthorizerWithMultiValueIAMConditionResponseTest(Type seri [Theory] [InlineData(typeof(JsonSerializer))] -#if NETCOREAPP3_1_OR_GREATER [InlineData(typeof(Amazon.Lambda.Serialization.SystemTextJson.LambdaJsonSerializer))] [InlineData(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))] -#endif public void APIGatewayAuthorizerResponseNotResourceTest(Type serializerType) { var serializer = Activator.CreateInstance(serializerType) as ILambdaSerializer; - var context = new APIGatewayCustomAuthorizerContextOutput(); - context["field1"] = "value1"; - context["field2"] = "value2"; + var context = new APIGatewayCustomAuthorizerContextOutput + { + ["field1"] = "value1", + ["field2"] = "value2" + }; var response = new APIGatewayCustomAuthorizerResponse { @@ -2189,19 +2105,19 @@ public void APIGatewayAuthorizerResponseNotResourceTest(Type serializerType) PolicyDocument = new APIGatewayCustomAuthorizerPolicy { Version = "2012-10-17", - Statement = new List - { + Statement = + [ new APIGatewayCustomAuthorizerPolicy.IAMPolicyStatement { Action = new HashSet{ "execute-api:Invoke" }, Effect = "Deny", - NotResource = new HashSet - { + NotResource = + [ "arn:aws:execute-api:us-east-1:1234567890:abcdef1234/Prod/GET/resource1", "arn:aws:execute-api:us-east-1:1234567890:abcdef1234/Prod/GET/resource2" - } + ] } - } + ] } }; @@ -2214,7 +2130,7 @@ public void APIGatewayAuthorizerResponseNotResourceTest(Type serializerType) serializedJson = Encoding.UTF8.GetString(stream.ToArray()); } - JObject root = Newtonsoft.Json.JsonConvert.DeserializeObject(serializedJson) as JObject; + var root = Newtonsoft.Json.JsonConvert.DeserializeObject(serializedJson) as JObject; Assert.Equal("prin1", root["principalId"]); Assert.Equal("usageKey", root["usageIdentifierKey"]); @@ -2236,10 +2152,8 @@ public void APIGatewayAuthorizerResponseNotResourceTest(Type serializerType) [Theory] [InlineData(typeof(JsonSerializer))] -#if NETCOREAPP3_1_OR_GREATER [InlineData(typeof(Amazon.Lambda.Serialization.SystemTextJson.LambdaJsonSerializer))] [InlineData(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))] -#endif public void WebSocketApiConnectTest(Type serializerType) { var serializer = Activator.CreateInstance(serializerType) as ILambdaSerializer; @@ -2253,63 +2167,61 @@ public void WebSocketApiConnectTest(Type serializerType) Assert.Null(proxyEvent.Body); var headers = proxyEvent.Headers; - Assert.Equal(headers["HeaderAuth1"], "headerValue1"); - Assert.Equal(headers["Host"], "lg10ltpf4f.execute-api.us-east-2.amazonaws.com"); - Assert.Equal(headers["Sec-WebSocket-Extensions"], "permessage-deflate; client_max_window_bits"); - Assert.Equal(headers["Sec-WebSocket-Key"], "BvlrrFKoKAPDYOlwBcGKWw=="); - Assert.Equal(headers["Sec-WebSocket-Version"], "13"); - Assert.Equal(headers["X-Amzn-Trace-Id"], "Root=1-625d9ad1-37a5d33a61dd9be33ae3a247"); - Assert.Equal(headers["X-Forwarded-For"], "52.95.4.0"); - Assert.Equal(headers["X-Forwarded-Port"], "443"); - Assert.Equal(headers["X-Forwarded-Proto"], "https"); + Assert.Equal("headerValue1", headers["HeaderAuth1"]); + Assert.Equal("lg10ltpf4f.execute-api.us-east-2.amazonaws.com", headers["Host"]); + Assert.Equal("permessage-deflate; client_max_window_bits", headers["Sec-WebSocket-Extensions"]); + Assert.Equal("BvlrrFKoKAPDYOlwBcGKWw==", headers["Sec-WebSocket-Key"]); + Assert.Equal("13", headers["Sec-WebSocket-Version"]); + Assert.Equal("Root=1-625d9ad1-37a5d33a61dd9be33ae3a247", headers["X-Amzn-Trace-Id"]); + Assert.Equal("52.95.4.0", headers["X-Forwarded-For"]); + Assert.Equal("443", headers["X-Forwarded-Port"]); + Assert.Equal("https", headers["X-Forwarded-Proto"]); var multiValueHeaders = proxyEvent.MultiValueHeaders; - Assert.Equal(multiValueHeaders["HeaderAuth1"].Count, 1); - Assert.Equal(multiValueHeaders["HeaderAuth1"][0], "headerValue1"); - Assert.Equal(multiValueHeaders["Host"].Count, 1); - Assert.Equal(multiValueHeaders["Host"][0], "lg10ltpf4f.execute-api.us-east-2.amazonaws.com"); - Assert.Equal(multiValueHeaders["Sec-WebSocket-Extensions"].Count, 1); - Assert.Equal(multiValueHeaders["Sec-WebSocket-Extensions"][0], "permessage-deflate; client_max_window_bits"); - Assert.Equal(multiValueHeaders["Sec-WebSocket-Key"].Count, 1); - Assert.Equal(multiValueHeaders["Sec-WebSocket-Key"][0], "BvlrrFKoKAPDYOlwBcGKWw=="); - Assert.Equal(multiValueHeaders["Sec-WebSocket-Version"].Count, 1); - Assert.Equal(multiValueHeaders["Sec-WebSocket-Version"][0], "13"); - Assert.Equal(multiValueHeaders["X-Amzn-Trace-Id"].Count, 1); - Assert.Equal(multiValueHeaders["X-Amzn-Trace-Id"][0], "Root=1-625d9ad1-37a5d33a61dd9be33ae3a247"); - Assert.Equal(multiValueHeaders["X-Forwarded-For"].Count, 1); - Assert.Equal(multiValueHeaders["X-Forwarded-For"][0], "52.95.4.0"); - Assert.Equal(multiValueHeaders["X-Forwarded-Port"].Count, 1); - Assert.Equal(multiValueHeaders["X-Forwarded-Port"][0], "443"); - Assert.Equal(multiValueHeaders["X-Forwarded-Proto"].Count, 1); - Assert.Equal(multiValueHeaders["X-Forwarded-Proto"][0], "https"); + Assert.Single(multiValueHeaders["HeaderAuth1"]); + Assert.Equal("headerValue1", multiValueHeaders["HeaderAuth1"][0]); + Assert.Single(multiValueHeaders["Host"]); + Assert.Equal("lg10ltpf4f.execute-api.us-east-2.amazonaws.com", multiValueHeaders["Host"][0]); + Assert.Single(multiValueHeaders["Sec-WebSocket-Extensions"]); + Assert.Equal("permessage-deflate; client_max_window_bits", multiValueHeaders["Sec-WebSocket-Extensions"][0]); + Assert.Single(multiValueHeaders["Sec-WebSocket-Key"]); + Assert.Equal("BvlrrFKoKAPDYOlwBcGKWw==", multiValueHeaders["Sec-WebSocket-Key"][0]); + Assert.Single(multiValueHeaders["Sec-WebSocket-Version"]); + Assert.Equal("13", multiValueHeaders["Sec-WebSocket-Version"][0]); + Assert.Single(multiValueHeaders["X-Amzn-Trace-Id"]); + Assert.Equal("Root=1-625d9ad1-37a5d33a61dd9be33ae3a247", multiValueHeaders["X-Amzn-Trace-Id"][0]); + Assert.Single(multiValueHeaders["X-Forwarded-For"]); + Assert.Equal("52.95.4.0", multiValueHeaders["X-Forwarded-For"][0]); + Assert.Single(multiValueHeaders["X-Forwarded-Port"]); + Assert.Equal("443", multiValueHeaders["X-Forwarded-Port"][0]); + Assert.Single(multiValueHeaders["X-Forwarded-Proto"]); + Assert.Equal("https", multiValueHeaders["X-Forwarded-Proto"][0]); var requestContext = proxyEvent.RequestContext; - Assert.Equal(requestContext.RouteKey, "$connect"); - Assert.Equal(requestContext.EventType, "CONNECT"); - Assert.Equal(requestContext.ExtendedRequestId, "QyUg1HJgCYcFvbw="); - Assert.Equal(requestContext.RequestTime, "18/Apr/2022:17:07:29 +0000"); - Assert.Equal(requestContext.MessageDirection, "IN"); - Assert.Equal(requestContext.Stage, "production"); - Assert.Equal(requestContext.ConnectedAt, 1650301649973); - Assert.Equal(requestContext.RequestTimeEpoch, 1650301649973); - Assert.Equal(requestContext.RequestId, "QyUg1HJgCYcFvbw="); - Assert.Equal(requestContext.DomainName, "lg10ltpf4f.execute-api.us-east-2.amazonaws.com"); - Assert.Equal(requestContext.ConnectionId, "QyUg1czHCYcCHXw="); - Assert.Equal(requestContext.ApiId, "lg10ltpf4f"); + Assert.Equal("$connect", requestContext.RouteKey); + Assert.Equal("CONNECT", requestContext.EventType); + Assert.Equal("QyUg1HJgCYcFvbw=", requestContext.ExtendedRequestId); + Assert.Equal("18/Apr/2022:17:07:29 +0000", requestContext.RequestTime); + Assert.Equal("IN", requestContext.MessageDirection); + Assert.Equal("production", requestContext.Stage); + Assert.Equal(1650301649973, requestContext.ConnectedAt); + Assert.Equal(1650301649973, requestContext.RequestTimeEpoch); + Assert.Equal("QyUg1HJgCYcFvbw=", requestContext.RequestId); + Assert.Equal("lg10ltpf4f.execute-api.us-east-2.amazonaws.com", requestContext.DomainName); + Assert.Equal("QyUg1czHCYcCHXw=", requestContext.ConnectionId); + Assert.Equal("lg10ltpf4f", requestContext.ApiId); Assert.False(proxyEvent.IsBase64Encoded); var identity = requestContext.Identity; - Assert.Equal(identity.SourceIp, "52.95.4.0"); + Assert.Equal("52.95.4.0", identity.SourceIp); } } [Theory] [InlineData(typeof(JsonSerializer))] -#if NETCOREAPP3_1_OR_GREATER [InlineData(typeof(Amazon.Lambda.Serialization.SystemTextJson.LambdaJsonSerializer))] [InlineData(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))] -#endif public void ApplicationLoadBalancerRequestSingleValueTest(Type serializerType) { var serializer = Activator.CreateInstance(serializerType) as ILambdaSerializer; @@ -2317,9 +2229,9 @@ public void ApplicationLoadBalancerRequestSingleValueTest(Type serializerType) { var evnt = serializer.Deserialize(fileStream); - Assert.Equal(evnt.Path, "/"); - Assert.Equal(evnt.HttpMethod, "GET"); - Assert.Equal(evnt.Body, "not really base64"); + Assert.Equal("/", evnt.Path); + Assert.Equal("GET", evnt.HttpMethod); + Assert.Equal("not really base64", evnt.Body); Assert.True(evnt.IsBase64Encoded); Assert.Equal(2, evnt.QueryStringParameters.Count); @@ -2331,17 +2243,15 @@ public void ApplicationLoadBalancerRequestSingleValueTest(Type serializerType) var requestContext = evnt.RequestContext; - Assert.Equal(requestContext.Elb.TargetGroupArn, "arn:aws:elasticloadbalancing:region:123456789012:targetgroup/my-target-group/6d0ecf831eec9f09"); + Assert.Equal("arn:aws:elasticloadbalancing:region:123456789012:targetgroup/my-target-group/6d0ecf831eec9f09", requestContext.Elb.TargetGroupArn); } } [Theory] [InlineData(typeof(JsonSerializer))] -#if NETCOREAPP3_1_OR_GREATER [InlineData(typeof(Amazon.Lambda.Serialization.SystemTextJson.LambdaJsonSerializer))] [InlineData(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))] -#endif public void ApplicationLoadBalancerRequestMultiValueTest(Type serializerType) { var serializer = Activator.CreateInstance(serializerType) as ILambdaSerializer; @@ -2349,9 +2259,9 @@ public void ApplicationLoadBalancerRequestMultiValueTest(Type serializerType) { var evnt = serializer.Deserialize(fileStream); - Assert.Equal(evnt.Path, "/"); - Assert.Equal(evnt.HttpMethod, "GET"); - Assert.Equal(evnt.Body, "not really base64"); + Assert.Equal("/", evnt.Path); + Assert.Equal("GET", evnt.HttpMethod); + Assert.Equal("not really base64", evnt.Body); Assert.True(evnt.IsBase64Encoded); Assert.Equal(2, evnt.MultiValueQueryStringParameters.Count); @@ -2372,17 +2282,15 @@ public void ApplicationLoadBalancerRequestMultiValueTest(Type serializerType) var requestContext = evnt.RequestContext; - Assert.Equal(requestContext.Elb.TargetGroupArn, "arn:aws:elasticloadbalancing:region:123456789012:targetgroup/my-target-group/6d0ecf831eec9f09"); + Assert.Equal("arn:aws:elasticloadbalancing:region:123456789012:targetgroup/my-target-group/6d0ecf831eec9f09", requestContext.Elb.TargetGroupArn); } } [Theory] [InlineData(typeof(JsonSerializer))] -#if NETCOREAPP3_1_OR_GREATER [InlineData(typeof(Amazon.Lambda.Serialization.SystemTextJson.LambdaJsonSerializer))] [InlineData(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))] -#endif public void ApplicationLoadBalancerSingleHeaderResponseTest(Type serializerType) { var serializer = Activator.CreateInstance(serializerType) as ILambdaSerializer; @@ -2409,7 +2317,7 @@ public void ApplicationLoadBalancerSingleHeaderResponseTest(Type serializerType) serializedJson = Encoding.UTF8.GetString(stream.ToArray()); } - JObject root = Newtonsoft.Json.JsonConvert.DeserializeObject(serializedJson) as JObject; + var root = Newtonsoft.Json.JsonConvert.DeserializeObject(serializedJson) as JObject; Assert.Equal("h1-value1", root["headers"]["Head1"]); Assert.Equal("h2-value1", root["headers"]["Head2"]); @@ -2422,10 +2330,8 @@ public void ApplicationLoadBalancerSingleHeaderResponseTest(Type serializerType) [Theory] [InlineData(typeof(JsonSerializer))] -#if NETCOREAPP3_1_OR_GREATER [InlineData(typeof(Amazon.Lambda.Serialization.SystemTextJson.LambdaJsonSerializer))] [InlineData(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))] -#endif public void ApplicationLoadBalancerMultiHeaderResponseTest(Type serializerType) { var serializer = Activator.CreateInstance(serializerType) as ILambdaSerializer; @@ -2453,7 +2359,7 @@ public void ApplicationLoadBalancerMultiHeaderResponseTest(Type serializerType) JObject root = Newtonsoft.Json.JsonConvert.DeserializeObject(serializedJson) as JObject; - Assert.Equal(1, root["multiValueHeaders"]["Head1"].Count()); + Assert.Single(root["multiValueHeaders"]["Head1"]); Assert.Equal("h1-value1", root["multiValueHeaders"]["Head1"].First()); Assert.Equal(2, root["multiValueHeaders"]["Head2"].Count()); @@ -2469,10 +2375,8 @@ public void ApplicationLoadBalancerMultiHeaderResponseTest(Type serializerType) [Theory] [InlineData(typeof(JsonSerializer))] -#if NETCOREAPP3_1_OR_GREATER [InlineData(typeof(Amazon.Lambda.Serialization.SystemTextJson.LambdaJsonSerializer))] [InlineData(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))] -#endif public void LexEvent(Type serializerType) { var serializer = Activator.CreateInstance(serializerType) as ILambdaSerializer; @@ -2536,10 +2440,8 @@ public void LexEvent(Type serializerType) [Theory] [InlineData(typeof(JsonSerializer))] -#if NETCOREAPP3_1_OR_GREATER [InlineData(typeof(Amazon.Lambda.Serialization.SystemTextJson.LambdaJsonSerializer))] [InlineData(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))] -#endif public void LexResponse(Type serializerType) { var serializer = Activator.CreateInstance(serializerType) as ILambdaSerializer; @@ -2562,12 +2464,12 @@ public void LexResponse(Type serializerType) Assert.Equal("slot-name", lexResponse.DialogAction.SlotToElicit); Assert.Equal(3, lexResponse.DialogAction.ResponseCard.Version); Assert.Equal("application/vnd.amazonaws.card.generic", lexResponse.DialogAction.ResponseCard.ContentType); - Assert.Equal(1, lexResponse.DialogAction.ResponseCard.GenericAttachments.Count); + Assert.Single(lexResponse.DialogAction.ResponseCard.GenericAttachments); Assert.Equal("card-title", lexResponse.DialogAction.ResponseCard.GenericAttachments[0].Title); Assert.Equal("card-sub-title", lexResponse.DialogAction.ResponseCard.GenericAttachments[0].SubTitle); Assert.Equal("URL of the image to be shown", lexResponse.DialogAction.ResponseCard.GenericAttachments[0].ImageUrl); Assert.Equal("URL of the attachment to be associated with the card", lexResponse.DialogAction.ResponseCard.GenericAttachments[0].AttachmentLinkUrl); - Assert.Equal(1, lexResponse.DialogAction.ResponseCard.GenericAttachments[0].Buttons.Count); + Assert.Single(lexResponse.DialogAction.ResponseCard.GenericAttachments[0].Buttons); Assert.Equal("button-text", lexResponse.DialogAction.ResponseCard.GenericAttachments[0].Buttons[0].Text); Assert.Equal("value sent to server on button click", lexResponse.DialogAction.ResponseCard.GenericAttachments[0].Buttons[0].Value); @@ -2624,13 +2526,13 @@ public void LexV2Event(Type serializerType) Assert.Equal("List", lexV2Event.Interpretations[0].Intent.Slots["ActionType"].Shape); Assert.Equal("Action Value", lexV2Event.Interpretations[0].Intent.Slots["ActionType"].Value.OriginalValue); Assert.Equal("Action Value", lexV2Event.Interpretations[0].Intent.Slots["ActionType"].Value.InterpretedValue); - Assert.Equal(1, lexV2Event.Interpretations[0].Intent.Slots["ActionType"].Value.ResolvedValues.Count); + Assert.Single(lexV2Event.Interpretations[0].Intent.Slots["ActionType"].Value.ResolvedValues); Assert.Equal("action value", lexV2Event.Interpretations[0].Intent.Slots["ActionType"].Value.ResolvedValues[0]); - Assert.Equal(1, lexV2Event.Interpretations[0].Intent.Slots["ActionType"].Values.Count); + Assert.Single(lexV2Event.Interpretations[0].Intent.Slots["ActionType"].Values); Assert.Equal("Scalar", lexV2Event.Interpretations[0].Intent.Slots["ActionType"].Values[0].Shape); Assert.Equal("Action Value", lexV2Event.Interpretations[0].Intent.Slots["ActionType"].Values[0].Value.OriginalValue); Assert.Equal("Action Value", lexV2Event.Interpretations[0].Intent.Slots["ActionType"].Values[0].Value.InterpretedValue); - Assert.Equal(1, lexV2Event.Interpretations[0].Intent.Slots["ActionType"].Values[0].Value.ResolvedValues.Count); + Assert.Single(lexV2Event.Interpretations[0].Intent.Slots["ActionType"].Values[0].Value.ResolvedValues); Assert.Equal("action value", lexV2Event.Interpretations[0].Intent.Slots["ActionType"].Values[0].Value.ResolvedValues[0]); Assert.Null(lexV2Event.Interpretations[0].Intent.Slots["ActionType"].Values[0].Values); Assert.Null(lexV2Event.Interpretations[0].Intent.Slots["ActionDate"]); @@ -2644,20 +2546,20 @@ public void LexV2Event(Type serializerType) Assert.Equal(0.5, lexV2Event.Interpretations[0].SentimentResponse.SentimentScore.Neutral); Assert.Equal(0.9, lexV2Event.Interpretations[0].SentimentResponse.SentimentScore.Positive); Assert.Equal("FallbackIntent", lexV2Event.Interpretations[1].Intent.Name); - Assert.Equal(0, lexV2Event.Interpretations[1].Intent.Slots.Count); + Assert.Empty(lexV2Event.Interpretations[1].Intent.Slots); Assert.Equal("ActionDate", lexV2Event.ProposedNextState.DialogAction.SlotToElicit); Assert.Equal("ConfirmIntent", lexV2Event.ProposedNextState.DialogAction.Type); Assert.Equal("NextIntent", lexV2Event.ProposedNextState.Intent.Name); Assert.Equal("None", lexV2Event.ProposedNextState.Intent.ConfirmationState); - Assert.Equal(0, lexV2Event.ProposedNextState.Intent.Slots.Count); + Assert.Empty(lexV2Event.ProposedNextState.Intent.Slots); Assert.Equal("Waiting", lexV2Event.ProposedNextState.Intent.State); Assert.Equal(2, lexV2Event.RequestAttributes.Count); Assert.Equal("value1", lexV2Event.RequestAttributes["key1"]); Assert.Equal("value2", lexV2Event.RequestAttributes["key2"]); - Assert.Equal(1, lexV2Event.SessionState.ActiveContexts.Count); + Assert.Single(lexV2Event.SessionState.ActiveContexts); Assert.Equal(2, lexV2Event.SessionState.ActiveContexts[0].ContextAttributes.Count); Assert.Equal("contextattributevalue1", lexV2Event.SessionState.ActiveContexts[0].ContextAttributes["contextattribute1"]); Assert.Equal("contextattributevalue2", lexV2Event.SessionState.ActiveContexts[0].ContextAttributes["contextattribute2"]); @@ -2671,13 +2573,13 @@ public void LexV2Event(Type serializerType) Assert.Equal("List", lexV2Event.SessionState.Intent.Slots["ActionType"].Shape); Assert.Equal("Action Value", lexV2Event.SessionState.Intent.Slots["ActionType"].Value.OriginalValue); Assert.Equal("Action Value", lexV2Event.SessionState.Intent.Slots["ActionType"].Value.InterpretedValue); - Assert.Equal(1, lexV2Event.SessionState.Intent.Slots["ActionType"].Value.ResolvedValues.Count); + Assert.Single(lexV2Event.SessionState.Intent.Slots["ActionType"].Value.ResolvedValues); Assert.Equal("action value", lexV2Event.SessionState.Intent.Slots["ActionType"].Value.ResolvedValues[0]); - Assert.Equal(1, lexV2Event.SessionState.Intent.Slots["ActionType"].Values.Count); + Assert.Single(lexV2Event.SessionState.Intent.Slots["ActionType"].Values); Assert.Equal("Scalar", lexV2Event.SessionState.Intent.Slots["ActionType"].Values[0].Shape); Assert.Equal("Action Value", lexV2Event.SessionState.Intent.Slots["ActionType"].Values[0].Value.OriginalValue); Assert.Equal("Action Value", lexV2Event.SessionState.Intent.Slots["ActionType"].Values[0].Value.InterpretedValue); - Assert.Equal(1, lexV2Event.SessionState.Intent.Slots["ActionType"].Values[0].Value.ResolvedValues.Count); + Assert.Single(lexV2Event.SessionState.Intent.Slots["ActionType"].Values[0].Value.ResolvedValues); Assert.Equal("action value", lexV2Event.SessionState.Intent.Slots["ActionType"].Values[0].Value.ResolvedValues[0]); Assert.Null(lexV2Event.SessionState.Intent.Slots["ActionType"].Values[0].Values); Assert.Null(lexV2Event.SessionState.Intent.Slots["ActionDate"]); @@ -2685,8 +2587,8 @@ public void LexV2Event(Type serializerType) Assert.Equal("InProgress", lexV2Event.SessionState.Intent.State); Assert.Equal("None", lexV2Event.SessionState.Intent.ConfirmationState); Assert.Equal("85f22c97-b5d3-4a74-9e3d-95446768ecaa", lexV2Event.SessionState.OriginatingRequestId); - Assert.Equal(1, lexV2Event.SessionState.RuntimeHints.SlotHints.Count); - Assert.Equal(1, lexV2Event.SessionState.RuntimeHints.SlotHints["hint1"].Count); + Assert.Single(lexV2Event.SessionState.RuntimeHints.SlotHints); + Assert.Single(lexV2Event.SessionState.RuntimeHints.SlotHints["hint1"]); Assert.Equal(2, lexV2Event.SessionState.RuntimeHints.SlotHints["hint1"]["detail1"].RuntimeHintValues.Count); Assert.Equal("hintvalue1_1", lexV2Event.SessionState.RuntimeHints.SlotHints["hint1"]["detail1"].RuntimeHintValues[0].Phrase); Assert.Equal("hintvalue1_2", lexV2Event.SessionState.RuntimeHints.SlotHints["hint1"]["detail1"].RuntimeHintValues[1].Phrase); @@ -2694,21 +2596,21 @@ public void LexV2Event(Type serializerType) Assert.Equal("sessionvalue1", lexV2Event.SessionState.SessionAttributes["sessionattribute1"]); Assert.Equal("sessionvalue2", lexV2Event.SessionState.SessionAttributes["sessionattribute2"]); - Assert.Equal(1, lexV2Event.Transcriptions.Count); + Assert.Single(lexV2Event.Transcriptions); Assert.Equal("testtranscription", lexV2Event.Transcriptions[0].Transcription); Assert.Equal(0.8, lexV2Event.Transcriptions[0].TranscriptionConfidence); Assert.Equal("TestAction", lexV2Event.Transcriptions[0].ResolvedContext.Intent); - Assert.Equal(1, lexV2Event.Transcriptions[0].ResolvedSlots.Count); + Assert.Single(lexV2Event.Transcriptions[0].ResolvedSlots); Assert.Equal("List", lexV2Event.Transcriptions[0].ResolvedSlots["ActionType"].Shape); Assert.Equal("Action Value", lexV2Event.Transcriptions[0].ResolvedSlots["ActionType"].Value.OriginalValue); Assert.Equal("Action Value", lexV2Event.Transcriptions[0].ResolvedSlots["ActionType"].Value.InterpretedValue); - Assert.Equal(1, lexV2Event.Transcriptions[0].ResolvedSlots["ActionType"].Value.ResolvedValues.Count); + Assert.Single(lexV2Event.Transcriptions[0].ResolvedSlots["ActionType"].Value.ResolvedValues); Assert.Equal("action value", lexV2Event.Transcriptions[0].ResolvedSlots["ActionType"].Value.ResolvedValues[0]); - Assert.Equal(1, lexV2Event.Transcriptions[0].ResolvedSlots["ActionType"].Values.Count); + Assert.Single(lexV2Event.Transcriptions[0].ResolvedSlots["ActionType"].Values); Assert.Equal("Scalar", lexV2Event.Transcriptions[0].ResolvedSlots["ActionType"].Values[0].Shape); Assert.Equal("Action Value", lexV2Event.Transcriptions[0].ResolvedSlots["ActionType"].Values[0].Value.OriginalValue); Assert.Equal("Action Value", lexV2Event.Transcriptions[0].ResolvedSlots["ActionType"].Values[0].Value.InterpretedValue); - Assert.Equal(1, lexV2Event.Transcriptions[0].ResolvedSlots["ActionType"].Values[0].Value.ResolvedValues.Count); + Assert.Single(lexV2Event.Transcriptions[0].ResolvedSlots["ActionType"].Values[0].Value.ResolvedValues); Assert.Equal("action value", lexV2Event.Transcriptions[0].ResolvedSlots["ActionType"].Values[0].Value.ResolvedValues[0]); Assert.Null(lexV2Event.Transcriptions[0].ResolvedSlots["ActionType"].Values[0].Values); } @@ -2725,16 +2627,16 @@ public void LexV2Response(Type serializerType) { var lexV2Response = serializer.Deserialize(fileStream); - Assert.Equal(1, lexV2Response.Messages.Count); + Assert.Single(lexV2Response.Messages); Assert.Equal("Test Content", lexV2Response.Messages[0].Content); Assert.Equal("ImageResponseCard", lexV2Response.Messages[0].ContentType); - Assert.Equal(1, lexV2Response.Messages[0].ImageResponseCard.Buttons.Count); + Assert.Single(lexV2Response.Messages[0].ImageResponseCard.Buttons); Assert.Equal("Take Action", lexV2Response.Messages[0].ImageResponseCard.Buttons[0].Text); Assert.Equal("takeaction", lexV2Response.Messages[0].ImageResponseCard.Buttons[0].Value); Assert.Equal("http://somedomain.com/testimage.png", lexV2Response.Messages[0].ImageResponseCard.ImageUrl); Assert.Equal("Click button to take action", lexV2Response.Messages[0].ImageResponseCard.Subtitle); Assert.Equal("Take Action", lexV2Response.Messages[0].ImageResponseCard.Title); - Assert.Equal(1, lexV2Response.SessionState.ActiveContexts.Count); + Assert.Single(lexV2Response.SessionState.ActiveContexts); Assert.Equal(2, lexV2Response.SessionState.ActiveContexts[0].ContextAttributes.Count); Assert.Equal("contextattributevalue1", lexV2Response.SessionState.ActiveContexts[0].ContextAttributes["contextattribute1"]); Assert.Equal("contextattributevalue2", lexV2Response.SessionState.ActiveContexts[0].ContextAttributes["contextattribute2"]); @@ -2748,13 +2650,13 @@ public void LexV2Response(Type serializerType) Assert.Equal("List", lexV2Response.SessionState.Intent.Slots["ActionType"].Shape); Assert.Equal("Action Value", lexV2Response.SessionState.Intent.Slots["ActionType"].Value.OriginalValue); Assert.Equal("Action Value", lexV2Response.SessionState.Intent.Slots["ActionType"].Value.InterpretedValue); - Assert.Equal(1, lexV2Response.SessionState.Intent.Slots["ActionType"].Value.ResolvedValues.Count); + Assert.Single(lexV2Response.SessionState.Intent.Slots["ActionType"].Value.ResolvedValues); Assert.Equal("action value", lexV2Response.SessionState.Intent.Slots["ActionType"].Value.ResolvedValues[0]); - Assert.Equal(1, lexV2Response.SessionState.Intent.Slots["ActionType"].Values.Count); + Assert.Single(lexV2Response.SessionState.Intent.Slots["ActionType"].Values); Assert.Equal("Scalar", lexV2Response.SessionState.Intent.Slots["ActionType"].Values[0].Shape); Assert.Equal("Action Value", lexV2Response.SessionState.Intent.Slots["ActionType"].Values[0].Value.OriginalValue); Assert.Equal("Action Value", lexV2Response.SessionState.Intent.Slots["ActionType"].Values[0].Value.InterpretedValue); - Assert.Equal(1, lexV2Response.SessionState.Intent.Slots["ActionType"].Values[0].Value.ResolvedValues.Count); + Assert.Single(lexV2Response.SessionState.Intent.Slots["ActionType"].Values[0].Value.ResolvedValues); Assert.Equal("action value", lexV2Response.SessionState.Intent.Slots["ActionType"].Values[0].Value.ResolvedValues[0]); Assert.Null(lexV2Response.SessionState.Intent.Slots["ActionType"].Values[0].Values); Assert.Null(lexV2Response.SessionState.Intent.Slots["ActionDate"]); @@ -2762,8 +2664,8 @@ public void LexV2Response(Type serializerType) Assert.Equal("InProgress", lexV2Response.SessionState.Intent.State); Assert.Equal("None", lexV2Response.SessionState.Intent.ConfirmationState); Assert.Equal("85f22c97-b5d3-4a74-9e3d-95446768ecaa", lexV2Response.SessionState.OriginatingRequestId); - Assert.Equal(1, lexV2Response.SessionState.RuntimeHints.SlotHints.Count); - Assert.Equal(1, lexV2Response.SessionState.RuntimeHints.SlotHints["hint1"].Count); + Assert.Single(lexV2Response.SessionState.RuntimeHints.SlotHints); + Assert.Single(lexV2Response.SessionState.RuntimeHints.SlotHints["hint1"]); Assert.Equal(2, lexV2Response.SessionState.RuntimeHints.SlotHints["hint1"]["detail1"].RuntimeHintValues.Count); Assert.Equal("hintvalue1_1", lexV2Response.SessionState.RuntimeHints.SlotHints["hint1"]["detail1"].RuntimeHintValues[0].Phrase); Assert.Equal("hintvalue1_2", lexV2Response.SessionState.RuntimeHints.SlotHints["hint1"]["detail1"].RuntimeHintValues[1].Phrase); @@ -2774,7 +2676,7 @@ public void LexV2Response(Type serializerType) Assert.Equal("value1", lexV2Response.RequestAttributes["key1"]); Assert.Equal("value2", lexV2Response.RequestAttributes["key2"]); - MemoryStream ms = new MemoryStream(); + var ms = new MemoryStream(); serializer.Serialize(lexV2Response, ms); ms.Position = 0; var json = new StreamReader(ms).ReadToEnd(); @@ -2785,14 +2687,10 @@ public void LexV2Response(Type serializerType) } } - // Test is temporary disabled due to a bug in .NET 8 RC2 - // https://github.com/dotnet/runtime/issues/93903 -#if !NET8_0 [Theory] [InlineData(typeof(JsonSerializer))] [InlineData(typeof(Amazon.Lambda.Serialization.SystemTextJson.LambdaJsonSerializer))] [InlineData(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))] -#endif public void KinesisFirehoseEvent(Type serializerType) { var serializer = Activator.CreateInstance(serializerType) as ILambdaSerializer; @@ -2802,7 +2700,7 @@ public void KinesisFirehoseEvent(Type serializerType) Assert.Equal("00540a87-5050-496a-84e4-e7d92bbaf5e2", kinesisEvent.InvocationId); Assert.Equal("arn:aws:firehose:us-east-1:AAAAAAAAAAAA:deliverystream/lambda-test", kinesisEvent.DeliveryStreamArn); Assert.Equal("us-east-1", kinesisEvent.Region); - Assert.Equal(1, kinesisEvent.Records.Count); + Assert.Single(kinesisEvent.Records); Assert.Equal("49572672223665514422805246926656954630972486059535892482", kinesisEvent.Records[0].RecordId); Assert.Equal("aGVsbG8gd29ybGQ=", kinesisEvent.Records[0].Base64EncodedData); @@ -2813,10 +2711,8 @@ public void KinesisFirehoseEvent(Type serializerType) [Theory] [InlineData(typeof(JsonSerializer))] -#if NETCOREAPP3_1_OR_GREATER [InlineData(typeof(Amazon.Lambda.Serialization.SystemTextJson.LambdaJsonSerializer))] [InlineData(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))] -#endif public void KinesisFirehoseResponseTest(Type serializerType) { var serializer = Activator.CreateInstance(serializerType) as ILambdaSerializer; @@ -2824,7 +2720,7 @@ public void KinesisFirehoseResponseTest(Type serializerType) { var kinesisResponse = serializer.Deserialize(fileStream); - Assert.Equal(1, kinesisResponse.Records.Count); + Assert.Single(kinesisResponse.Records); Assert.Equal("49572672223665514422805246926656954630972486059535892482", kinesisResponse.Records[0].RecordId); Assert.Equal(KinesisFirehoseResponse.TRANSFORMED_STATE_OK, kinesisResponse.Records[0].Result); Assert.Equal("SEVMTE8gV09STEQ=", kinesisResponse.Records[0].Base64EncodedData); @@ -2846,10 +2742,8 @@ public void KinesisFirehoseResponseTest(Type serializerType) [Theory] [InlineData(typeof(JsonSerializer))] -#if NETCOREAPP3_1_OR_GREATER [InlineData(typeof(Amazon.Lambda.Serialization.SystemTextJson.LambdaJsonSerializer))] [InlineData(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))] -#endif public void KinesisAnalyticsOutputDeliveryEvent(Type serializerType) { var serializer = Activator.CreateInstance(serializerType) as ILambdaSerializer; @@ -2866,10 +2760,8 @@ public void KinesisAnalyticsOutputDeliveryEvent(Type serializerType) [Theory] [InlineData(typeof(JsonSerializer))] -#if NETCOREAPP3_1_OR_GREATER [InlineData(typeof(Amazon.Lambda.Serialization.SystemTextJson.LambdaJsonSerializer))] [InlineData(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))] -#endif public void KinesisAnalyticsOutputDeliveryResponseTest(Type serializerType) { var serializer = Activator.CreateInstance(serializerType) as ILambdaSerializer; @@ -2877,7 +2769,7 @@ public void KinesisAnalyticsOutputDeliveryResponseTest(Type serializerType) { var kinesisAnalyticsResponse = serializer.Deserialize(fileStream); - Assert.Equal(1, kinesisAnalyticsResponse.Records.Count); + Assert.Single(kinesisAnalyticsResponse.Records); Assert.Equal("49572672223665514422805246926656954630972486059535892482", kinesisAnalyticsResponse.Records[0].RecordId); Assert.Equal(KinesisAnalyticsOutputDeliveryResponse.OK, kinesisAnalyticsResponse.Records[0].Result); @@ -2892,14 +2784,10 @@ public void KinesisAnalyticsOutputDeliveryResponseTest(Type serializerType) } } - // Test is temporary disabled due to a bug in .NET 8 RC2 - // https://github.com/dotnet/runtime/issues/93903 -#if !NET8_0 [Theory] [InlineData(typeof(JsonSerializer))] [InlineData(typeof(Amazon.Lambda.Serialization.SystemTextJson.LambdaJsonSerializer))] [InlineData(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))] -#endif public void KinesisAnalyticsInputProcessingEventTest(Type serializerType) { var serializer = Activator.CreateInstance(serializerType) as ILambdaSerializer; @@ -2909,7 +2797,7 @@ public void KinesisAnalyticsInputProcessingEventTest(Type serializerType) Assert.Equal("00540a87-5050-496a-84e4-e7d92bbaf5e2", kinesisAnalyticsEvent.InvocationId); Assert.Equal("arn:aws:kinesis:us-east-1:AAAAAAAAAAAA:stream/lambda-test", kinesisAnalyticsEvent.StreamArn); Assert.Equal("arn:aws:kinesisanalytics:us-east-1:12345678911:application/lambda-test", kinesisAnalyticsEvent.ApplicationArn); - Assert.Equal(1, kinesisAnalyticsEvent.Records.Count); + Assert.Single(kinesisAnalyticsEvent.Records); Assert.Equal("49572672223665514422805246926656954630972486059535892482", kinesisAnalyticsEvent.Records[0].RecordId); Assert.Equal("aGVsbG8gd29ybGQ=", kinesisAnalyticsEvent.Records[0].Base64EncodedData); @@ -2918,10 +2806,8 @@ public void KinesisAnalyticsInputProcessingEventTest(Type serializerType) [Theory] [InlineData(typeof(JsonSerializer))] -#if NETCOREAPP3_1_OR_GREATER [InlineData(typeof(Amazon.Lambda.Serialization.SystemTextJson.LambdaJsonSerializer))] [InlineData(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))] -#endif public void KinesisAnalyticsInputProcessingResponseTest(Type serializerType) { var serializer = Activator.CreateInstance(serializerType) as ILambdaSerializer; @@ -2929,7 +2815,7 @@ public void KinesisAnalyticsInputProcessingResponseTest(Type serializerType) { var kinesisAnalyticsResponse = serializer.Deserialize(fileStream); - Assert.Equal(1, kinesisAnalyticsResponse.Records.Count); + Assert.Single(kinesisAnalyticsResponse.Records); Assert.Equal("49572672223665514422805246926656954630972486059535892482", kinesisAnalyticsResponse.Records[0].RecordId); Assert.Equal(KinesisAnalyticsInputPreprocessingResponse.OK, kinesisAnalyticsResponse.Records[0].Result); Assert.Equal("SEVMTE8gV09STEQ=", kinesisAnalyticsResponse.Records[0].Base64EncodedData); @@ -2948,14 +2834,10 @@ public void KinesisAnalyticsInputProcessingResponseTest(Type serializerType) } } - // Test is temporary disabled due to a bug in .NET 8 RC2 - // https://github.com/dotnet/runtime/issues/93903 -#if !NET8_0 [Theory] [InlineData(typeof(JsonSerializer))] [InlineData(typeof(Amazon.Lambda.Serialization.SystemTextJson.LambdaJsonSerializer))] [InlineData(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))] -#endif public void KinesisAnalyticsStreamsInputProcessingEventTest(Type serializerType) { var serializer = Activator.CreateInstance(serializerType) as ILambdaSerializer; @@ -2965,7 +2847,7 @@ public void KinesisAnalyticsStreamsInputProcessingEventTest(Type serializerType) Assert.Equal("00540a87-5050-496a-84e4-e7d92bbaf5e2", kinesisAnalyticsEvent.InvocationId); Assert.Equal("arn:aws:kinesis:us-east-1:AAAAAAAAAAAA:stream/lambda-test", kinesisAnalyticsEvent.StreamArn); Assert.Equal("arn:aws:kinesisanalytics:us-east-1:12345678911:application/lambda-test", kinesisAnalyticsEvent.ApplicationArn); - Assert.Equal(1, kinesisAnalyticsEvent.Records.Count); + Assert.Single(kinesisAnalyticsEvent.Records); Assert.Equal("49572672223665514422805246926656954630972486059535892482", kinesisAnalyticsEvent.Records[0].RecordId); Assert.Equal("aGVsbG8gd29ybGQ=", kinesisAnalyticsEvent.Records[0].Base64EncodedData); @@ -2978,14 +2860,10 @@ public void KinesisAnalyticsStreamsInputProcessingEventTest(Type serializerType) } } - // Test is temporary disabled due to a bug in .NET 8 RC2 - // https://github.com/dotnet/runtime/issues/93903 -#if !NET8_0 [Theory] [InlineData(typeof(JsonSerializer))] [InlineData(typeof(Amazon.Lambda.Serialization.SystemTextJson.LambdaJsonSerializer))] [InlineData(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))] -#endif public void KinesisAnalyticsFirehoseInputProcessingEventTest(Type serializerType) { var serializer = Activator.CreateInstance(serializerType) as ILambdaSerializer; @@ -2995,7 +2873,7 @@ public void KinesisAnalyticsFirehoseInputProcessingEventTest(Type serializerType Assert.Equal("00540a87-5050-496a-84e4-e7d92bbaf5e2", kinesisAnalyticsEvent.InvocationId); Assert.Equal("arn:aws:firehose:us-east-1:AAAAAAAAAAAA:deliverystream/lambda-test", kinesisAnalyticsEvent.StreamArn); Assert.Equal("arn:aws:kinesisanalytics:us-east-1:12345678911:application/lambda-test", kinesisAnalyticsEvent.ApplicationArn); - Assert.Equal(1, kinesisAnalyticsEvent.Records.Count); + Assert.Single(kinesisAnalyticsEvent.Records); Assert.Equal("49572672223665514422805246926656954630972486059535892482", kinesisAnalyticsEvent.Records[0].RecordId); Assert.Equal("aGVsbG8gd29ybGQ=", kinesisAnalyticsEvent.Records[0].Base64EncodedData); @@ -3007,10 +2885,8 @@ public void KinesisAnalyticsFirehoseInputProcessingEventTest(Type serializerType [Theory] [InlineData(typeof(JsonSerializer))] -#if NETCOREAPP3_1_OR_GREATER [InlineData(typeof(Amazon.Lambda.Serialization.SystemTextJson.LambdaJsonSerializer))] [InlineData(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))] -#endif public void CloudWatchLogEvent(Type serializerType) { var serializer = Activator.CreateInstance(serializerType) as ILambdaSerializer; @@ -3037,10 +2913,8 @@ private string MemoryStreamToBase64String(MemoryStream ms) [Theory] [InlineData(typeof(JsonSerializer))] -#if NETCOREAPP3_1_OR_GREATER [InlineData(typeof(Amazon.Lambda.Serialization.SystemTextJson.LambdaJsonSerializer))] [InlineData(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))] -#endif public void BatchJobStateChangeEventTest(Type serializerType) { var serializer = Activator.CreateInstance(serializerType) as ILambdaSerializer; @@ -3048,177 +2922,177 @@ public void BatchJobStateChangeEventTest(Type serializerType) { var jobStateChangeEvent = serializer.Deserialize(fileStream); - Assert.Equal(jobStateChangeEvent.Version, "0"); - Assert.Equal(jobStateChangeEvent.Id, "c8f9c4b5-76e5-d76a-f980-7011e206042b"); - Assert.Equal(jobStateChangeEvent.DetailType, "Batch Job State Change"); - Assert.Equal(jobStateChangeEvent.Source, "aws.batch"); - Assert.Equal(jobStateChangeEvent.Account, "aws_account_id"); - Assert.Equal(jobStateChangeEvent.Time.ToUniversalTime(), DateTime.Parse("2017-10-23T17:56:03Z").ToUniversalTime()); - Assert.Equal(jobStateChangeEvent.Region, "us-east-1"); - Assert.Equal(jobStateChangeEvent.Resources.Count, 1); - Assert.Equal(jobStateChangeEvent.Resources[0], "arn:aws:batch:us-east-1:aws_account_id:job/4c7599ae-0a82-49aa-ba5a-4727fcce14a8"); - Assert.IsType(typeof(Job), jobStateChangeEvent.Detail); - Assert.Equal(jobStateChangeEvent.Detail.JobName, "event-test"); - Assert.Equal(jobStateChangeEvent.Detail.JobId, "4c7599ae-0a82-49aa-ba5a-4727fcce14a8"); - Assert.Equal(jobStateChangeEvent.Detail.JobQueue, "arn:aws:batch:us-east-1:aws_account_id:job-queue/HighPriority"); - Assert.Equal(jobStateChangeEvent.Detail.Status, "RUNNABLE"); - Assert.Equal(jobStateChangeEvent.Detail.Attempts.Count, 0); - Assert.Equal(jobStateChangeEvent.Detail.CreatedAt, 1508781340401); - Assert.Equal(jobStateChangeEvent.Detail.RetryStrategy.Attempts, 1); - Assert.Equal(jobStateChangeEvent.Detail.RetryStrategy.EvaluateOnExit.Count, 1); - Assert.Equal(jobStateChangeEvent.Detail.RetryStrategy.EvaluateOnExit[0].Action, "EXIT"); - Assert.Equal(jobStateChangeEvent.Detail.RetryStrategy.EvaluateOnExit[0].OnExitCode, "*"); - Assert.Equal(jobStateChangeEvent.Detail.RetryStrategy.EvaluateOnExit[0].OnReason, "*"); - Assert.Equal(jobStateChangeEvent.Detail.RetryStrategy.EvaluateOnExit[0].OnStatusReason, "*"); - Assert.Equal(jobStateChangeEvent.Detail.DependsOn.Count, 0); - Assert.Equal(jobStateChangeEvent.Detail.JobDefinition, "arn:aws:batch:us-east-1:aws_account_id:job-definition/first-run-job-definition:1"); - Assert.Equal(jobStateChangeEvent.Detail.Parameters.Count, 1); - Assert.Equal(jobStateChangeEvent.Detail.Parameters["test"], "abc"); - Assert.Equal(jobStateChangeEvent.Detail.Container.Image, "busybox"); + Assert.Equal("0", jobStateChangeEvent.Version); + Assert.Equal("c8f9c4b5-76e5-d76a-f980-7011e206042b", jobStateChangeEvent.Id); + Assert.Equal("Batch Job State Change", jobStateChangeEvent.DetailType); + Assert.Equal("aws.batch", jobStateChangeEvent.Source); + Assert.Equal("aws_account_id", jobStateChangeEvent.Account); + Assert.Equal(DateTime.Parse("2017-10-23T17:56:03Z").ToUniversalTime(), jobStateChangeEvent.Time.ToUniversalTime()); + Assert.Equal("us-east-1", jobStateChangeEvent.Region); + Assert.Single(jobStateChangeEvent.Resources); + Assert.Equal("arn:aws:batch:us-east-1:aws_account_id:job/4c7599ae-0a82-49aa-ba5a-4727fcce14a8", jobStateChangeEvent.Resources[0]); + Assert.IsType(jobStateChangeEvent.Detail); + Assert.Equal("event-test", jobStateChangeEvent.Detail.JobName); + Assert.Equal("4c7599ae-0a82-49aa-ba5a-4727fcce14a8", jobStateChangeEvent.Detail.JobId); + Assert.Equal("arn:aws:batch:us-east-1:aws_account_id:job-queue/HighPriority", jobStateChangeEvent.Detail.JobQueue); + Assert.Equal("RUNNABLE", jobStateChangeEvent.Detail.Status); + Assert.Empty(jobStateChangeEvent.Detail.Attempts); + Assert.Equal(1508781340401, jobStateChangeEvent.Detail.CreatedAt); + Assert.Equal(1, jobStateChangeEvent.Detail.RetryStrategy.Attempts); + Assert.Single(jobStateChangeEvent.Detail.RetryStrategy.EvaluateOnExit); + Assert.Equal("EXIT", jobStateChangeEvent.Detail.RetryStrategy.EvaluateOnExit[0].Action); + Assert.Equal("*", jobStateChangeEvent.Detail.RetryStrategy.EvaluateOnExit[0].OnExitCode); + Assert.Equal("*", jobStateChangeEvent.Detail.RetryStrategy.EvaluateOnExit[0].OnReason); + Assert.Equal("*", jobStateChangeEvent.Detail.RetryStrategy.EvaluateOnExit[0].OnStatusReason); + Assert.Empty(jobStateChangeEvent.Detail.DependsOn); + Assert.Equal("arn:aws:batch:us-east-1:aws_account_id:job-definition/first-run-job-definition:1", jobStateChangeEvent.Detail.JobDefinition); + Assert.Single(jobStateChangeEvent.Detail.Parameters); + Assert.Equal("abc", jobStateChangeEvent.Detail.Parameters["test"]); + Assert.Equal("busybox", jobStateChangeEvent.Detail.Container.Image); Assert.NotNull(jobStateChangeEvent.Detail.Container.ResourceRequirements); - Assert.Equal(jobStateChangeEvent.Detail.Container.ResourceRequirements.Count, 2); - Assert.Equal(jobStateChangeEvent.Detail.Container.ResourceRequirements[0].Type, "MEMORY"); - Assert.Equal(jobStateChangeEvent.Detail.Container.ResourceRequirements[0].Value, "2000"); - Assert.Equal(jobStateChangeEvent.Detail.Container.ResourceRequirements[1].Type, "VCPU"); - Assert.Equal(jobStateChangeEvent.Detail.Container.ResourceRequirements[1].Value, "2"); - Assert.Equal(jobStateChangeEvent.Detail.Container.Vcpus, 2); - Assert.Equal(jobStateChangeEvent.Detail.Container.Memory, 2000); - Assert.Equal(jobStateChangeEvent.Detail.Container.Command.Count, 2); - Assert.Equal(jobStateChangeEvent.Detail.Container.Command[0], "echo"); - Assert.Equal(jobStateChangeEvent.Detail.Container.Command[1], "'hello world'"); - Assert.Equal(jobStateChangeEvent.Detail.Container.Volumes.Count, 2); - Assert.Equal(jobStateChangeEvent.Detail.Container.Volumes[0].Name, "myhostsource"); - Assert.Equal(jobStateChangeEvent.Detail.Container.Volumes[0].Host.SourcePath, "/data"); - Assert.Equal(jobStateChangeEvent.Detail.Container.Volumes[1].Name, "efs"); - Assert.Equal(jobStateChangeEvent.Detail.Container.Volumes[1].EfsVolumeConfiguration.AuthorizationConfig.AccessPointId, "fsap-XXXXXXXXXXXXXXXXX"); - Assert.Equal(jobStateChangeEvent.Detail.Container.Volumes[1].EfsVolumeConfiguration.AuthorizationConfig.Iam, "ENABLED"); - Assert.Equal(jobStateChangeEvent.Detail.Container.Volumes[1].EfsVolumeConfiguration.FileSystemId, "fs-XXXXXXXXX"); - Assert.Equal(jobStateChangeEvent.Detail.Container.Volumes[1].EfsVolumeConfiguration.RootDirectory, "/"); - Assert.Equal(jobStateChangeEvent.Detail.Container.Volumes[1].EfsVolumeConfiguration.TransitEncryption, "ENABLED"); - Assert.Equal(jobStateChangeEvent.Detail.Container.Volumes[1].EfsVolumeConfiguration.TransitEncryptionPort, 12345); + Assert.Equal(2, jobStateChangeEvent.Detail.Container.ResourceRequirements.Count); + Assert.Equal("MEMORY", jobStateChangeEvent.Detail.Container.ResourceRequirements[0].Type); + Assert.Equal("2000", jobStateChangeEvent.Detail.Container.ResourceRequirements[0].Value); + Assert.Equal("VCPU", jobStateChangeEvent.Detail.Container.ResourceRequirements[1].Type); + Assert.Equal("2", jobStateChangeEvent.Detail.Container.ResourceRequirements[1].Value); + Assert.Equal(2, jobStateChangeEvent.Detail.Container.Vcpus); + Assert.Equal(2000, jobStateChangeEvent.Detail.Container.Memory); + Assert.Equal(2, jobStateChangeEvent.Detail.Container.Command.Count); + Assert.Equal("echo", jobStateChangeEvent.Detail.Container.Command[0]); + Assert.Equal("'hello world'", jobStateChangeEvent.Detail.Container.Command[1]); + Assert.Equal(2, jobStateChangeEvent.Detail.Container.Volumes.Count); + Assert.Equal("myhostsource", jobStateChangeEvent.Detail.Container.Volumes[0].Name); + Assert.Equal("/data", jobStateChangeEvent.Detail.Container.Volumes[0].Host.SourcePath); + Assert.Equal("efs", jobStateChangeEvent.Detail.Container.Volumes[1].Name); + Assert.Equal("fsap-XXXXXXXXXXXXXXXXX", jobStateChangeEvent.Detail.Container.Volumes[1].EfsVolumeConfiguration.AuthorizationConfig.AccessPointId); + Assert.Equal("ENABLED", jobStateChangeEvent.Detail.Container.Volumes[1].EfsVolumeConfiguration.AuthorizationConfig.Iam); + Assert.Equal("fs-XXXXXXXXX", jobStateChangeEvent.Detail.Container.Volumes[1].EfsVolumeConfiguration.FileSystemId); + Assert.Equal("/", jobStateChangeEvent.Detail.Container.Volumes[1].EfsVolumeConfiguration.RootDirectory); + Assert.Equal("ENABLED", jobStateChangeEvent.Detail.Container.Volumes[1].EfsVolumeConfiguration.TransitEncryption); + Assert.Equal(12345, jobStateChangeEvent.Detail.Container.Volumes[1].EfsVolumeConfiguration.TransitEncryptionPort); Assert.NotNull(jobStateChangeEvent.Detail.Container.Environment); - Assert.Equal(jobStateChangeEvent.Detail.Container.Environment.Count, 1); - Assert.Equal(jobStateChangeEvent.Detail.Container.Environment[0].Name, "MANAGED_BY_AWS"); - Assert.Equal(jobStateChangeEvent.Detail.Container.Environment[0].Value, "STARTED_BY_STEP_FUNCTIONS"); - Assert.Equal(jobStateChangeEvent.Detail.Container.MountPoints.Count, 2); - Assert.Equal(jobStateChangeEvent.Detail.Container.MountPoints[0].ContainerPath, "/data"); - Assert.Equal(jobStateChangeEvent.Detail.Container.MountPoints[0].ReadOnly, true); - Assert.Equal(jobStateChangeEvent.Detail.Container.MountPoints[0].SourceVolume, "myhostsource"); - Assert.Equal(jobStateChangeEvent.Detail.Container.MountPoints[1].ContainerPath, "/mount/efs"); - Assert.Equal(jobStateChangeEvent.Detail.Container.MountPoints[1].SourceVolume, "efs"); - Assert.Equal(jobStateChangeEvent.Detail.Container.Ulimits.Count, 1); - Assert.Equal(jobStateChangeEvent.Detail.Container.Ulimits[0].HardLimit, 2048); - Assert.Equal(jobStateChangeEvent.Detail.Container.Ulimits[0].Name, "nofile"); - Assert.Equal(jobStateChangeEvent.Detail.Container.Ulimits[0].SoftLimit, 2048); + Assert.Single(jobStateChangeEvent.Detail.Container.Environment); + Assert.Equal("MANAGED_BY_AWS", jobStateChangeEvent.Detail.Container.Environment[0].Name); + Assert.Equal("STARTED_BY_STEP_FUNCTIONS", jobStateChangeEvent.Detail.Container.Environment[0].Value); + Assert.Equal(2, jobStateChangeEvent.Detail.Container.MountPoints.Count); + Assert.Equal("/data", jobStateChangeEvent.Detail.Container.MountPoints[0].ContainerPath); + Assert.True(jobStateChangeEvent.Detail.Container.MountPoints[0].ReadOnly); + Assert.Equal("myhostsource", jobStateChangeEvent.Detail.Container.MountPoints[0].SourceVolume); + Assert.Equal("/mount/efs", jobStateChangeEvent.Detail.Container.MountPoints[1].ContainerPath); + Assert.Equal("efs", jobStateChangeEvent.Detail.Container.MountPoints[1].SourceVolume); + Assert.Single(jobStateChangeEvent.Detail.Container.Ulimits); + Assert.Equal(2048, jobStateChangeEvent.Detail.Container.Ulimits[0].HardLimit); + Assert.Equal("nofile", jobStateChangeEvent.Detail.Container.Ulimits[0].Name); + Assert.Equal(2048, jobStateChangeEvent.Detail.Container.Ulimits[0].SoftLimit); Assert.NotNull(jobStateChangeEvent.Detail.Container.LinuxParameters); - Assert.Equal(jobStateChangeEvent.Detail.Container.LinuxParameters.Devices.Count, 1); - Assert.Equal(jobStateChangeEvent.Detail.Container.LinuxParameters.Devices[0].ContainerPath, "/dev/sda"); - Assert.Equal(jobStateChangeEvent.Detail.Container.LinuxParameters.Devices[0].HostPath, "/dev/xvdc"); - Assert.Equal(jobStateChangeEvent.Detail.Container.LinuxParameters.Devices[0].Permissions.Count, 1); - Assert.Equal(jobStateChangeEvent.Detail.Container.LinuxParameters.Devices[0].Permissions[0], "MKNOD"); - Assert.Equal(jobStateChangeEvent.Detail.Container.LinuxParameters.InitProcessEnabled, true); - Assert.Equal(jobStateChangeEvent.Detail.Container.LinuxParameters.SharedMemorySize, 64); - Assert.Equal(jobStateChangeEvent.Detail.Container.LinuxParameters.MaxSwap, 1024); - Assert.Equal(jobStateChangeEvent.Detail.Container.LinuxParameters.Swappiness, 55); - Assert.Equal(jobStateChangeEvent.Detail.Container.LinuxParameters.Tmpfs.Count, 1); - Assert.Equal(jobStateChangeEvent.Detail.Container.LinuxParameters.Tmpfs[0].ContainerPath, "/run"); - Assert.Equal(jobStateChangeEvent.Detail.Container.LinuxParameters.Tmpfs[0].Size, 65536); - Assert.Equal(jobStateChangeEvent.Detail.Container.LinuxParameters.Tmpfs[0].MountOptions.Count, 2); - Assert.Equal(jobStateChangeEvent.Detail.Container.LinuxParameters.Tmpfs[0].MountOptions[0], "noexec"); - Assert.Equal(jobStateChangeEvent.Detail.Container.LinuxParameters.Tmpfs[0].MountOptions[1], "nosuid"); + Assert.Single(jobStateChangeEvent.Detail.Container.LinuxParameters.Devices); + Assert.Equal("/dev/sda", jobStateChangeEvent.Detail.Container.LinuxParameters.Devices[0].ContainerPath); + Assert.Equal("/dev/xvdc", jobStateChangeEvent.Detail.Container.LinuxParameters.Devices[0].HostPath); + Assert.Single(jobStateChangeEvent.Detail.Container.LinuxParameters.Devices[0].Permissions); + Assert.Equal("MKNOD", jobStateChangeEvent.Detail.Container.LinuxParameters.Devices[0].Permissions[0]); + Assert.True(jobStateChangeEvent.Detail.Container.LinuxParameters.InitProcessEnabled); + Assert.Equal(64, jobStateChangeEvent.Detail.Container.LinuxParameters.SharedMemorySize); + Assert.Equal(1024, jobStateChangeEvent.Detail.Container.LinuxParameters.MaxSwap); + Assert.Equal(55, jobStateChangeEvent.Detail.Container.LinuxParameters.Swappiness); + Assert.Single(jobStateChangeEvent.Detail.Container.LinuxParameters.Tmpfs); + Assert.Equal("/run", jobStateChangeEvent.Detail.Container.LinuxParameters.Tmpfs[0].ContainerPath); + Assert.Equal(65536, jobStateChangeEvent.Detail.Container.LinuxParameters.Tmpfs[0].Size); + Assert.Equal(2, jobStateChangeEvent.Detail.Container.LinuxParameters.Tmpfs[0].MountOptions.Count); + Assert.Equal("noexec", jobStateChangeEvent.Detail.Container.LinuxParameters.Tmpfs[0].MountOptions[0]); + Assert.Equal("nosuid", jobStateChangeEvent.Detail.Container.LinuxParameters.Tmpfs[0].MountOptions[1]); Assert.NotNull(jobStateChangeEvent.Detail.Container.LogConfiguration); - Assert.Equal(jobStateChangeEvent.Detail.Container.LogConfiguration.LogDriver, "json-file"); - Assert.Equal(jobStateChangeEvent.Detail.Container.LogConfiguration.Options.Count, 2); - Assert.Equal(jobStateChangeEvent.Detail.Container.LogConfiguration.Options["max-size"], "10m"); - Assert.Equal(jobStateChangeEvent.Detail.Container.LogConfiguration.Options["max-file"], "3"); - Assert.Equal(jobStateChangeEvent.Detail.Container.LogConfiguration.SecretOptions.Count, 1); - Assert.Equal(jobStateChangeEvent.Detail.Container.LogConfiguration.SecretOptions[0].Name, "apikey"); - Assert.Equal(jobStateChangeEvent.Detail.Container.LogConfiguration.SecretOptions[0].ValueFrom, "ddApiKey"); - Assert.Equal(jobStateChangeEvent.Detail.Container.Secrets.Count, 1); - Assert.Equal(jobStateChangeEvent.Detail.Container.Secrets[0].Name, "DATABASE_PASSWORD"); - Assert.Equal(jobStateChangeEvent.Detail.Container.Secrets[0].ValueFrom, "arn:aws:ssm:us-east-1:awsExampleAccountID:parameter/awsExampleParameter"); + Assert.Equal("json-file", jobStateChangeEvent.Detail.Container.LogConfiguration.LogDriver); + Assert.Equal(2, jobStateChangeEvent.Detail.Container.LogConfiguration.Options.Count); + Assert.Equal("10m", jobStateChangeEvent.Detail.Container.LogConfiguration.Options["max-size"]); + Assert.Equal("3", jobStateChangeEvent.Detail.Container.LogConfiguration.Options["max-file"]); + Assert.Single(jobStateChangeEvent.Detail.Container.LogConfiguration.SecretOptions); + Assert.Equal("apikey", jobStateChangeEvent.Detail.Container.LogConfiguration.SecretOptions[0].Name); + Assert.Equal("ddApiKey", jobStateChangeEvent.Detail.Container.LogConfiguration.SecretOptions[0].ValueFrom); + Assert.Single(jobStateChangeEvent.Detail.Container.Secrets); + Assert.Equal("DATABASE_PASSWORD", jobStateChangeEvent.Detail.Container.Secrets[0].Name); + Assert.Equal("arn:aws:ssm:us-east-1:awsExampleAccountID:parameter/awsExampleParameter", jobStateChangeEvent.Detail.Container.Secrets[0].ValueFrom); Assert.NotNull(jobStateChangeEvent.Detail.Container.NetworkConfiguration); - Assert.Equal(jobStateChangeEvent.Detail.Container.NetworkConfiguration.AssignPublicIp, "ENABLED"); + Assert.Equal("ENABLED", jobStateChangeEvent.Detail.Container.NetworkConfiguration.AssignPublicIp); Assert.NotNull(jobStateChangeEvent.Detail.Container.FargatePlatformConfiguration); - Assert.Equal(jobStateChangeEvent.Detail.Container.FargatePlatformConfiguration.PlatformVersion, "LATEST"); + Assert.Equal("LATEST", jobStateChangeEvent.Detail.Container.FargatePlatformConfiguration.PlatformVersion); Assert.NotNull(jobStateChangeEvent.Detail.NodeProperties); - Assert.Equal(jobStateChangeEvent.Detail.NodeProperties.MainNode, 0); - Assert.Equal(jobStateChangeEvent.Detail.NodeProperties.NumNodes, 0); - Assert.Equal(jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties.Count, 1); - Assert.Equal(jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].TargetNodes, "0:1"); + Assert.Equal(0, jobStateChangeEvent.Detail.NodeProperties.MainNode); + Assert.Equal(0, jobStateChangeEvent.Detail.NodeProperties.NumNodes); + Assert.Single(jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties); + Assert.Equal("0:1", jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].TargetNodes); Assert.NotNull(jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container); - Assert.Equal(jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.Image, "busybox"); - Assert.Equal(jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.ResourceRequirements.Count, 2); - Assert.Equal(jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.ResourceRequirements[0].Type, "MEMORY"); - Assert.Equal(jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.ResourceRequirements[0].Value, "2000"); - Assert.Equal(jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.ResourceRequirements[1].Type, "VCPU"); - Assert.Equal(jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.ResourceRequirements[1].Value, "2"); - Assert.Equal(jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.Vcpus, 2); - Assert.Equal(jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.Memory, 2000); - Assert.Equal(jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.Command.Count, 2); - Assert.Equal(jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.Command[0], "echo"); - Assert.Equal(jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.Command[1], "'hello world'"); - Assert.Equal(jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.Volumes.Count, 2); - Assert.Equal(jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.Volumes[0].Name, "myhostsource"); - Assert.Equal(jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.Volumes[0].Host.SourcePath, "/data"); - Assert.Equal(jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.Volumes[1].Name, "efs"); - Assert.Equal(jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.Volumes[1].EfsVolumeConfiguration.AuthorizationConfig.AccessPointId, "fsap-XXXXXXXXXXXXXXXXX"); - Assert.Equal(jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.Volumes[1].EfsVolumeConfiguration.AuthorizationConfig.Iam, "ENABLED"); - Assert.Equal(jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.Volumes[1].EfsVolumeConfiguration.FileSystemId, "fs-XXXXXXXXX"); - Assert.Equal(jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.Volumes[1].EfsVolumeConfiguration.RootDirectory, "/"); - Assert.Equal(jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.Volumes[1].EfsVolumeConfiguration.TransitEncryption, "ENABLED"); - Assert.Equal(jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.Volumes[1].EfsVolumeConfiguration.TransitEncryptionPort, 12345); - Assert.Equal(jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.Environment.Count, 1); - Assert.Equal(jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.Environment[0].Name, "MANAGED_BY_AWS"); - Assert.Equal(jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.Environment[0].Value, "STARTED_BY_STEP_FUNCTIONS"); - Assert.Equal(jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.MountPoints.Count, 2); - Assert.Equal(jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.MountPoints[0].ContainerPath, "/data"); - Assert.Equal(jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.MountPoints[0].ReadOnly, true); - Assert.Equal(jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.MountPoints[0].SourceVolume, "myhostsource"); - Assert.Equal(jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.MountPoints[1].ContainerPath, "/mount/efs"); - Assert.Equal(jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.MountPoints[1].SourceVolume, "efs"); - Assert.Equal(jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.Ulimits.Count, 1); - Assert.Equal(jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.Ulimits[0].HardLimit, 2048); - Assert.Equal(jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.Ulimits[0].Name, "nofile"); - Assert.Equal(jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.Ulimits[0].SoftLimit, 2048); - Assert.Equal(jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.ExecutionRoleArn, "arn:aws:iam::awsExampleAccountID:role/awsExampleRoleName"); - Assert.Equal(jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.InstanceType, "p3.2xlarge"); - Assert.Equal(jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.User, "testuser"); - Assert.Equal(jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.JobRoleArn, "arn:aws:iam::awsExampleAccountID:role/awsExampleRoleName"); - Assert.Equal(jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.LinuxParameters.Devices.Count, 1); - Assert.Equal(jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.LinuxParameters.Devices[0].HostPath, "/dev/xvdc"); - Assert.Equal(jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.LinuxParameters.Devices[0].ContainerPath, "/dev/sda"); - Assert.Equal(jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.LinuxParameters.Devices[0].Permissions.Count, 1); - Assert.Equal(jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.LinuxParameters.Devices[0].Permissions[0], "MKNOD"); - Assert.Equal(jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.LinuxParameters.InitProcessEnabled, true); - Assert.Equal(jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.LinuxParameters.SharedMemorySize, 64); - Assert.Equal(jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.LinuxParameters.MaxSwap, 1024); - Assert.Equal(jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.LinuxParameters.Swappiness, 55); - Assert.Equal(jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.LinuxParameters.Tmpfs.Count, 1); - Assert.Equal(jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.LinuxParameters.Tmpfs[0].ContainerPath, "/run"); - Assert.Equal(jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.LinuxParameters.Tmpfs[0].Size, 65536); - Assert.Equal(jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.LinuxParameters.Tmpfs[0].MountOptions.Count, 2); - Assert.Equal(jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.LinuxParameters.Tmpfs[0].MountOptions[0], "noexec"); - Assert.Equal(jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.LinuxParameters.Tmpfs[0].MountOptions[1], "nosuid"); - Assert.Equal(jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.LogConfiguration.LogDriver, "awslogs"); - Assert.Equal(jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.LogConfiguration.Options["awslogs-group"], "awslogs-wordpress"); - Assert.Equal(jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.LogConfiguration.Options["awslogs-stream-prefix"], "awslogs-example"); - Assert.Equal(jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.LogConfiguration.SecretOptions.Count, 1); - Assert.Equal(jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.LogConfiguration.SecretOptions[0].Name, "apikey"); - Assert.Equal(jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.LogConfiguration.SecretOptions[0].ValueFrom, "ddApiKey"); - Assert.Equal(jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.Secrets.Count, 1); - Assert.Equal(jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.Secrets[0].Name, "DATABASE_PASSWORD"); - Assert.Equal(jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.Secrets[0].ValueFrom, "arn:aws:ssm:us-east-1:awsExampleAccountID:parameter/awsExampleParameter"); - Assert.Equal(jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.NetworkConfiguration.AssignPublicIp, "DISABLED"); - Assert.Equal(jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.FargatePlatformConfiguration.PlatformVersion, "LATEST"); - Assert.Equal(jobStateChangeEvent.Detail.PropagateTags, true); - Assert.Equal(jobStateChangeEvent.Detail.Timeout.AttemptDurationSeconds, 90); - Assert.Equal(jobStateChangeEvent.Detail.Tags.Count, 3); - Assert.Equal(jobStateChangeEvent.Detail.Tags["Service"], "Batch"); - Assert.Equal(jobStateChangeEvent.Detail.Tags["Name"], "JobDefinitionTag"); - Assert.Equal(jobStateChangeEvent.Detail.Tags["Expected"], "MergeTag"); - Assert.Equal(jobStateChangeEvent.Detail.PlatformCapabilities.Count, 1); - Assert.Equal(jobStateChangeEvent.Detail.PlatformCapabilities[0], "FARGATE"); + Assert.Equal("busybox", jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.Image); + Assert.Equal(2, jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.ResourceRequirements.Count); + Assert.Equal("MEMORY", jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.ResourceRequirements[0].Type); + Assert.Equal("2000", jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.ResourceRequirements[0].Value); + Assert.Equal("VCPU", jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.ResourceRequirements[1].Type); + Assert.Equal("2", jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.ResourceRequirements[1].Value); + Assert.Equal(2, jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.Vcpus); + Assert.Equal(2000, jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.Memory); + Assert.Equal(2, jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.Command.Count); + Assert.Equal("echo", jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.Command[0]); + Assert.Equal("'hello world'", jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.Command[1]); + Assert.Equal(2, jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.Volumes.Count); + Assert.Equal("myhostsource", jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.Volumes[0].Name); + Assert.Equal("/data", jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.Volumes[0].Host.SourcePath); + Assert.Equal("efs", jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.Volumes[1].Name); + Assert.Equal("fsap-XXXXXXXXXXXXXXXXX", jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.Volumes[1].EfsVolumeConfiguration.AuthorizationConfig.AccessPointId); + Assert.Equal("ENABLED", jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.Volumes[1].EfsVolumeConfiguration.AuthorizationConfig.Iam); + Assert.Equal("fs-XXXXXXXXX", jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.Volumes[1].EfsVolumeConfiguration.FileSystemId); + Assert.Equal("/", jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.Volumes[1].EfsVolumeConfiguration.RootDirectory); + Assert.Equal("ENABLED", jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.Volumes[1].EfsVolumeConfiguration.TransitEncryption); + Assert.Equal(12345, jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.Volumes[1].EfsVolumeConfiguration.TransitEncryptionPort); + Assert.Single(jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.Environment); + Assert.Equal("MANAGED_BY_AWS", jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.Environment[0].Name); + Assert.Equal("STARTED_BY_STEP_FUNCTIONS", jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.Environment[0].Value); + Assert.Equal(2, jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.MountPoints.Count); + Assert.Equal("/data", jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.MountPoints[0].ContainerPath); + Assert.True(jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.MountPoints[0].ReadOnly); + Assert.Equal("myhostsource", jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.MountPoints[0].SourceVolume); + Assert.Equal("/mount/efs", jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.MountPoints[1].ContainerPath); + Assert.Equal("efs", jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.MountPoints[1].SourceVolume); + Assert.Single(jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.Ulimits); + Assert.Equal(2048, jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.Ulimits[0].HardLimit); + Assert.Equal("nofile", jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.Ulimits[0].Name); + Assert.Equal(2048, jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.Ulimits[0].SoftLimit); + Assert.Equal("arn:aws:iam::awsExampleAccountID:role/awsExampleRoleName", jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.ExecutionRoleArn); + Assert.Equal("p3.2xlarge", jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.InstanceType); + Assert.Equal("testuser", jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.User); + Assert.Equal("arn:aws:iam::awsExampleAccountID:role/awsExampleRoleName", jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.JobRoleArn); + Assert.Single(jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.LinuxParameters.Devices); + Assert.Equal("/dev/xvdc", jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.LinuxParameters.Devices[0].HostPath); + Assert.Equal("/dev/sda", jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.LinuxParameters.Devices[0].ContainerPath); + Assert.Single(jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.LinuxParameters.Devices[0].Permissions); + Assert.Equal("MKNOD", jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.LinuxParameters.Devices[0].Permissions[0]); + Assert.True(jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.LinuxParameters.InitProcessEnabled); + Assert.Equal(64, jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.LinuxParameters.SharedMemorySize); + Assert.Equal(1024, jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.LinuxParameters.MaxSwap); + Assert.Equal(55, jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.LinuxParameters.Swappiness); + Assert.Single(jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.LinuxParameters.Tmpfs); + Assert.Equal("/run", jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.LinuxParameters.Tmpfs[0].ContainerPath); + Assert.Equal(65536, jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.LinuxParameters.Tmpfs[0].Size); + Assert.Equal(2, jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.LinuxParameters.Tmpfs[0].MountOptions.Count); + Assert.Equal("noexec", jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.LinuxParameters.Tmpfs[0].MountOptions[0]); + Assert.Equal("nosuid", jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.LinuxParameters.Tmpfs[0].MountOptions[1]); + Assert.Equal("awslogs", jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.LogConfiguration.LogDriver); + Assert.Equal("awslogs-wordpress", jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.LogConfiguration.Options["awslogs-group"]); + Assert.Equal("awslogs-example", jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.LogConfiguration.Options["awslogs-stream-prefix"]); + Assert.Single(jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.LogConfiguration.SecretOptions); + Assert.Equal("apikey", jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.LogConfiguration.SecretOptions[0].Name); + Assert.Equal("ddApiKey", jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.LogConfiguration.SecretOptions[0].ValueFrom); + Assert.Single(jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.Secrets); + Assert.Equal("DATABASE_PASSWORD", jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.Secrets[0].Name); + Assert.Equal("arn:aws:ssm:us-east-1:awsExampleAccountID:parameter/awsExampleParameter", jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.Secrets[0].ValueFrom); + Assert.Equal("DISABLED", jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.NetworkConfiguration.AssignPublicIp); + Assert.Equal("LATEST", jobStateChangeEvent.Detail.NodeProperties.NodeRangeProperties[0].Container.FargatePlatformConfiguration.PlatformVersion); + Assert.True(jobStateChangeEvent.Detail.PropagateTags); + Assert.Equal(90, jobStateChangeEvent.Detail.Timeout.AttemptDurationSeconds); + Assert.Equal(3, jobStateChangeEvent.Detail.Tags.Count); + Assert.Equal("Batch", jobStateChangeEvent.Detail.Tags["Service"]); + Assert.Equal("JobDefinitionTag", jobStateChangeEvent.Detail.Tags["Name"]); + Assert.Equal("MergeTag", jobStateChangeEvent.Detail.Tags["Expected"]); + Assert.Single(jobStateChangeEvent.Detail.PlatformCapabilities); + Assert.Equal("FARGATE", jobStateChangeEvent.Detail.PlatformCapabilities[0]); Handle(jobStateChangeEvent); } @@ -3231,26 +3105,24 @@ private void Handle(BatchJobStateChangeEvent jobStateChangeEvent) [Theory] [InlineData(typeof(JsonSerializer))] -#if NETCOREAPP3_1_OR_GREATER [InlineData(typeof(Amazon.Lambda.Serialization.SystemTextJson.LambdaJsonSerializer))] [InlineData(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))] -#endif public void ScheduledEventTest(Type serializerType) { var serializer = Activator.CreateInstance(serializerType) as ILambdaSerializer; using (var fileStream = LoadJsonTestFile("scheduled-event.json")) { var scheduledEvent = serializer.Deserialize(fileStream); - Assert.Equal(scheduledEvent.Version, "0"); - Assert.Equal(scheduledEvent.Id, "cdc73f9d-aea9-11e3-9d5a-835b769c0d9c"); - Assert.Equal(scheduledEvent.DetailType, "Scheduled Event"); - Assert.Equal(scheduledEvent.Source, "aws.events"); - Assert.Equal(scheduledEvent.Account, "123456789012"); - Assert.Equal(scheduledEvent.Time.ToUniversalTime(), DateTime.Parse("1970-01-01T00:00:00Z").ToUniversalTime()); - Assert.Equal(scheduledEvent.Region, "us-east-1"); - Assert.Equal(scheduledEvent.Resources.Count, 1); - Assert.Equal(scheduledEvent.Resources[0], "arn:aws:events:us-east-1:123456789012:rule/my-schedule"); - Assert.IsType(typeof(Detail), scheduledEvent.Detail); + Assert.Equal("0", scheduledEvent.Version); + Assert.Equal("cdc73f9d-aea9-11e3-9d5a-835b769c0d9c", scheduledEvent.Id); + Assert.Equal("Scheduled Event", scheduledEvent.DetailType); + Assert.Equal("aws.events", scheduledEvent.Source); + Assert.Equal("123456789012", scheduledEvent.Account); + Assert.Equal(DateTime.Parse("1970-01-01T00:00:00Z").ToUniversalTime(), scheduledEvent.Time.ToUniversalTime()); + Assert.Equal("us-east-1", scheduledEvent.Region); + Assert.Single(scheduledEvent.Resources); + Assert.Equal("arn:aws:events:us-east-1:123456789012:rule/my-schedule", scheduledEvent.Resources[0]); + Assert.IsType(scheduledEvent.Detail); Handle(scheduledEvent); } @@ -3263,10 +3135,8 @@ private void Handle(ScheduledEvent scheduledEvent) [Theory] [InlineData(typeof(JsonSerializer))] -#if NETCOREAPP3_1_OR_GREATER [InlineData(typeof(Amazon.Lambda.Serialization.SystemTextJson.LambdaJsonSerializer))] [InlineData(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))] -#endif public void ECSContainerInstanceStateChangeEventTest(Type serializerType) { var serializer = Activator.CreateInstance(serializerType) as ILambdaSerializer; @@ -3274,38 +3144,38 @@ public void ECSContainerInstanceStateChangeEventTest(Type serializerType) { var ecsEvent = serializer.Deserialize(fileStream); - Assert.Equal(ecsEvent.Version, "0"); - Assert.Equal(ecsEvent.Id, "8952ba83-7be2-4ab5-9c32-6687532d15a2"); - Assert.Equal(ecsEvent.DetailType, "ECS Container Instance State Change"); - Assert.Equal(ecsEvent.Source, "aws.ecs"); - Assert.Equal(ecsEvent.Account, "111122223333"); - Assert.Equal(ecsEvent.Time.ToUniversalTime(), DateTime.Parse("2016-12-06T16:41:06Z").ToUniversalTime()); - Assert.Equal(ecsEvent.Region, "us-east-1"); - Assert.Equal(ecsEvent.Resources.Count, 1); - Assert.Equal(ecsEvent.Resources[0], "arn:aws:ecs:us-east-1:111122223333:container-instance/b54a2a04-046f-4331-9d74-3f6d7f6ca315"); - Assert.IsType(typeof(ContainerInstance), ecsEvent.Detail); - Assert.Equal(ecsEvent.Detail.AgentConnected, true); - Assert.Equal(ecsEvent.Detail.Attributes.Count, 14); - Assert.Equal(ecsEvent.Detail.Attributes[0].Name, "com.amazonaws.ecs.capability.logging-driver.syslog"); - Assert.Equal(ecsEvent.Detail.ClusterArn, "arn:aws:ecs:us-east-1:111122223333:cluster/default"); - Assert.Equal(ecsEvent.Detail.ContainerInstanceArn, "arn:aws:ecs:us-east-1:111122223333:container-instance/b54a2a04-046f-4331-9d74-3f6d7f6ca315"); - Assert.Equal(ecsEvent.Detail.Ec2InstanceId, "i-f3a8506b"); - Assert.Equal(ecsEvent.Detail.RegisteredResources.Count, 4); - Assert.Equal(ecsEvent.Detail.RegisteredResources[0].Name, "CPU"); - Assert.Equal(ecsEvent.Detail.RegisteredResources[0].Type, "INTEGER"); - Assert.Equal(ecsEvent.Detail.RegisteredResources[0].IntegerValue, 2048); - Assert.Equal(ecsEvent.Detail.RegisteredResources[2].StringSetValue[0], "22"); - Assert.Equal(ecsEvent.Detail.RemainingResources.Count, 4); - Assert.Equal(ecsEvent.Detail.RemainingResources[0].Name, "CPU"); - Assert.Equal(ecsEvent.Detail.RemainingResources[0].Type, "INTEGER"); - Assert.Equal(ecsEvent.Detail.RemainingResources[0].IntegerValue, 1988); - Assert.Equal(ecsEvent.Detail.RemainingResources[2].StringSetValue[0], "22"); - Assert.Equal(ecsEvent.Detail.Status, "ACTIVE"); - Assert.Equal(ecsEvent.Detail.Version, 14801); - Assert.Equal(ecsEvent.Detail.VersionInfo.AgentHash, "aebcbca"); - Assert.Equal(ecsEvent.Detail.VersionInfo.AgentVersion, "1.13.0"); - Assert.Equal(ecsEvent.Detail.VersionInfo.DockerVersion, "DockerVersion: 1.11.2"); - Assert.Equal(ecsEvent.Detail.UpdatedAt.ToUniversalTime(), DateTime.Parse("2016-12-06T16:41:06.991Z").ToUniversalTime()); + Assert.Equal("0", ecsEvent.Version); + Assert.Equal("8952ba83-7be2-4ab5-9c32-6687532d15a2", ecsEvent.Id); + Assert.Equal("ECS Container Instance State Change", ecsEvent.DetailType); + Assert.Equal("aws.ecs", ecsEvent.Source); + Assert.Equal("111122223333", ecsEvent.Account); + Assert.Equal(DateTime.Parse("2016-12-06T16:41:06Z").ToUniversalTime(), ecsEvent.Time.ToUniversalTime()); + Assert.Equal("us-east-1", ecsEvent.Region); + Assert.Single(ecsEvent.Resources); + Assert.Equal("arn:aws:ecs:us-east-1:111122223333:container-instance/b54a2a04-046f-4331-9d74-3f6d7f6ca315", ecsEvent.Resources[0]); + Assert.IsType(ecsEvent.Detail); + Assert.True(ecsEvent.Detail.AgentConnected); + Assert.Equal(14, ecsEvent.Detail.Attributes.Count); + Assert.Equal("com.amazonaws.ecs.capability.logging-driver.syslog", ecsEvent.Detail.Attributes[0].Name); + Assert.Equal("arn:aws:ecs:us-east-1:111122223333:cluster/default", ecsEvent.Detail.ClusterArn); + Assert.Equal("arn:aws:ecs:us-east-1:111122223333:container-instance/b54a2a04-046f-4331-9d74-3f6d7f6ca315", ecsEvent.Detail.ContainerInstanceArn); + Assert.Equal("i-f3a8506b", ecsEvent.Detail.Ec2InstanceId); + Assert.Equal(4, ecsEvent.Detail.RegisteredResources.Count); + Assert.Equal("CPU", ecsEvent.Detail.RegisteredResources[0].Name); + Assert.Equal("INTEGER", ecsEvent.Detail.RegisteredResources[0].Type); + Assert.Equal(2048, ecsEvent.Detail.RegisteredResources[0].IntegerValue); + Assert.Equal("22", ecsEvent.Detail.RegisteredResources[2].StringSetValue[0]); + Assert.Equal(4, ecsEvent.Detail.RemainingResources.Count); + Assert.Equal("CPU", ecsEvent.Detail.RemainingResources[0].Name); + Assert.Equal("INTEGER", ecsEvent.Detail.RemainingResources[0].Type); + Assert.Equal(1988, ecsEvent.Detail.RemainingResources[0].IntegerValue); + Assert.Equal("22", ecsEvent.Detail.RemainingResources[2].StringSetValue[0]); + Assert.Equal("ACTIVE", ecsEvent.Detail.Status); + Assert.Equal(14801, ecsEvent.Detail.Version); + Assert.Equal("aebcbca", ecsEvent.Detail.VersionInfo.AgentHash); + Assert.Equal("1.13.0", ecsEvent.Detail.VersionInfo.AgentVersion); + Assert.Equal("DockerVersion: 1.11.2", ecsEvent.Detail.VersionInfo.DockerVersion); + Assert.Equal(DateTime.Parse("2016-12-06T16:41:06.991Z").ToUniversalTime(), ecsEvent.Detail.UpdatedAt.ToUniversalTime()); Handle(ecsEvent); } @@ -3314,10 +3184,8 @@ public void ECSContainerInstanceStateChangeEventTest(Type serializerType) [Theory] [InlineData(typeof(JsonSerializer))] -#if NETCOREAPP3_1_OR_GREATER [InlineData(typeof(Amazon.Lambda.Serialization.SystemTextJson.LambdaJsonSerializer))] [InlineData(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))] -#endif public void ECSTaskStateChangeEventTest(Type serializerType) { var serializer = Activator.CreateInstance(serializerType) as ILambdaSerializer; @@ -3325,78 +3193,78 @@ public void ECSTaskStateChangeEventTest(Type serializerType) { var ecsEvent = serializer.Deserialize(fileStream); - Assert.Equal(ecsEvent.Version, "0"); - Assert.Equal(ecsEvent.Id, "3317b2af-7005-947d-b652-f55e762e571a"); - Assert.Equal(ecsEvent.DetailType, "ECS Task State Change"); - Assert.Equal(ecsEvent.Source, "aws.ecs"); - Assert.Equal(ecsEvent.Account, "111122223333"); - Assert.Equal(ecsEvent.Time.ToUniversalTime(), DateTime.Parse("2020-01-23T17:57:58Z").ToUniversalTime()); - Assert.Equal(ecsEvent.Region, "us-west-2"); + Assert.Equal("0", ecsEvent.Version); + Assert.Equal("3317b2af-7005-947d-b652-f55e762e571a", ecsEvent.Id); + Assert.Equal("ECS Task State Change", ecsEvent.DetailType); + Assert.Equal("aws.ecs", ecsEvent.Source); + Assert.Equal("111122223333", ecsEvent.Account); + Assert.Equal(DateTime.Parse("2020-01-23T17:57:58Z").ToUniversalTime(), ecsEvent.Time.ToUniversalTime()); + Assert.Equal("us-west-2", ecsEvent.Region); Assert.NotNull(ecsEvent.Resources); - Assert.Equal(ecsEvent.Resources.Count, 1); - Assert.Equal(ecsEvent.Resources[0], "arn:aws:ecs:us-west-2:111122223333:task/FargateCluster/c13b4cb40f1f4fe4a2971f76ae5a47ad"); + Assert.Single(ecsEvent.Resources); + Assert.Equal("arn:aws:ecs:us-west-2:111122223333:task/FargateCluster/c13b4cb40f1f4fe4a2971f76ae5a47ad", ecsEvent.Resources[0]); Assert.NotNull(ecsEvent.Detail); - Assert.IsType(typeof(Task), ecsEvent.Detail); + Assert.IsType(ecsEvent.Detail); Assert.NotNull(ecsEvent.Detail.Attachments); - Assert.Equal(ecsEvent.Detail.Attachments.Count, 1); - Assert.Equal(ecsEvent.Detail.Attachments[0].Id, "1789bcae-ddfb-4d10-8ebe-8ac87ddba5b8"); - Assert.Equal(ecsEvent.Detail.Attachments[0].Type, "eni"); - Assert.Equal(ecsEvent.Detail.Attachments[0].Status, "ATTACHED"); + Assert.Single(ecsEvent.Detail.Attachments); + Assert.Equal("1789bcae-ddfb-4d10-8ebe-8ac87ddba5b8", ecsEvent.Detail.Attachments[0].Id); + Assert.Equal("eni", ecsEvent.Detail.Attachments[0].Type); + Assert.Equal("ATTACHED", ecsEvent.Detail.Attachments[0].Status); Assert.NotNull(ecsEvent.Detail.Attachments[0].Details); - Assert.Equal(ecsEvent.Detail.Attachments[0].Details.Count, 4); - Assert.Equal(ecsEvent.Detail.Attachments[0].Details[0].Name, "subnetId"); - Assert.Equal(ecsEvent.Detail.Attachments[0].Details[0].Value, "subnet-abcd1234"); - Assert.Equal(ecsEvent.Detail.Attachments[0].Details[1].Name, "networkInterfaceId"); - Assert.Equal(ecsEvent.Detail.Attachments[0].Details[1].Value, "eni-abcd1234"); - Assert.Equal(ecsEvent.Detail.Attachments[0].Details[2].Name, "macAddress"); - Assert.Equal(ecsEvent.Detail.Attachments[0].Details[2].Value, "0a:98:eb:a7:29:ba"); - Assert.Equal(ecsEvent.Detail.Attachments[0].Details[3].Name, "privateIPv4Address"); - Assert.Equal(ecsEvent.Detail.Attachments[0].Details[3].Value, "10.0.0.139"); - - Assert.Equal(ecsEvent.Detail.AvailabilityZone, "us-west-2c"); - Assert.Equal(ecsEvent.Detail.ClusterArn, "arn:aws:ecs:us-west-2:111122223333:cluster/FargateCluster"); + Assert.Equal(4, ecsEvent.Detail.Attachments[0].Details.Count); + Assert.Equal("subnetId", ecsEvent.Detail.Attachments[0].Details[0].Name); + Assert.Equal("subnet-abcd1234", ecsEvent.Detail.Attachments[0].Details[0].Value); + Assert.Equal("networkInterfaceId", ecsEvent.Detail.Attachments[0].Details[1].Name); + Assert.Equal("eni-abcd1234", ecsEvent.Detail.Attachments[0].Details[1].Value); + Assert.Equal("macAddress", ecsEvent.Detail.Attachments[0].Details[2].Name); + Assert.Equal("0a:98:eb:a7:29:ba", ecsEvent.Detail.Attachments[0].Details[2].Value); + Assert.Equal("privateIPv4Address", ecsEvent.Detail.Attachments[0].Details[3].Name); + Assert.Equal("10.0.0.139", ecsEvent.Detail.Attachments[0].Details[3].Value); + + Assert.Equal("us-west-2c", ecsEvent.Detail.AvailabilityZone); + Assert.Equal("arn:aws:ecs:us-west-2:111122223333:cluster/FargateCluster", ecsEvent.Detail.ClusterArn); Assert.NotNull(ecsEvent.Detail.Containers); - Assert.Equal(ecsEvent.Detail.Containers.Count, 1); - Assert.Equal(ecsEvent.Detail.Containers[0].ContainerArn, "arn:aws:ecs:us-west-2:111122223333:container/cf159fd6-3e3f-4a9e-84f9-66cbe726af01"); - Assert.Equal(ecsEvent.Detail.Containers[0].ExitCode, 0); - Assert.Equal(ecsEvent.Detail.Containers[0].LastStatus, "RUNNING"); - Assert.Equal(ecsEvent.Detail.Containers[0].Name, "FargateApp"); - Assert.Equal(ecsEvent.Detail.Containers[0].Image, "111122223333.dkr.ecr.us-west-2.amazonaws.com/hello-repository:latest"); - Assert.Equal(ecsEvent.Detail.Containers[0].ImageDigest, "sha256:74b2c688c700ec95a93e478cdb959737c148df3fbf5ea706abe0318726e885e6"); - Assert.Equal(ecsEvent.Detail.Containers[0].RuntimeId, "ad64cbc71c7fb31c55507ec24c9f77947132b03d48d9961115cf24f3b7307e1e"); - Assert.Equal(ecsEvent.Detail.Containers[0].TaskArn, "arn:aws:ecs:us-west-2:111122223333:task/FargateCluster/c13b4cb40f1f4fe4a2971f76ae5a47ad"); + Assert.Single(ecsEvent.Detail.Containers); + Assert.Equal("arn:aws:ecs:us-west-2:111122223333:container/cf159fd6-3e3f-4a9e-84f9-66cbe726af01", ecsEvent.Detail.Containers[0].ContainerArn); + Assert.Equal(0, ecsEvent.Detail.Containers[0].ExitCode); + Assert.Equal("RUNNING", ecsEvent.Detail.Containers[0].LastStatus); + Assert.Equal("FargateApp", ecsEvent.Detail.Containers[0].Name); + Assert.Equal("111122223333.dkr.ecr.us-west-2.amazonaws.com/hello-repository:latest", ecsEvent.Detail.Containers[0].Image); + Assert.Equal("sha256:74b2c688c700ec95a93e478cdb959737c148df3fbf5ea706abe0318726e885e6", ecsEvent.Detail.Containers[0].ImageDigest); + Assert.Equal("ad64cbc71c7fb31c55507ec24c9f77947132b03d48d9961115cf24f3b7307e1e", ecsEvent.Detail.Containers[0].RuntimeId); + Assert.Equal("arn:aws:ecs:us-west-2:111122223333:task/FargateCluster/c13b4cb40f1f4fe4a2971f76ae5a47ad", ecsEvent.Detail.Containers[0].TaskArn); Assert.NotNull(ecsEvent.Detail.Containers[0].NetworkInterfaces); - Assert.Equal(ecsEvent.Detail.Containers[0].NetworkInterfaces.Count, 1); - Assert.Equal(ecsEvent.Detail.Containers[0].NetworkInterfaces[0].AttachmentId, "1789bcae-ddfb-4d10-8ebe-8ac87ddba5b8"); - Assert.Equal(ecsEvent.Detail.Containers[0].NetworkInterfaces[0].PrivateIpv4Address, "10.0.0.139"); - Assert.Equal(ecsEvent.Detail.Containers[0].Cpu, "0"); - - Assert.Equal(ecsEvent.Detail.CreatedAt.ToUniversalTime(), DateTime.Parse("2020-01-23T17:57:34.402Z").ToUniversalTime()); - Assert.Equal(ecsEvent.Detail.LaunchType, "FARGATE"); - Assert.Equal(ecsEvent.Detail.Cpu, "256"); - Assert.Equal(ecsEvent.Detail.Memory, "512"); - Assert.Equal(ecsEvent.Detail.DesiredStatus, "RUNNING"); - Assert.Equal(ecsEvent.Detail.Group, "family:sample-fargate"); - Assert.Equal(ecsEvent.Detail.LastStatus, "RUNNING"); - - Assert.Equal(ecsEvent.Detail.Overrides.ContainerOverrides.Count, 1); - Assert.Equal(ecsEvent.Detail.Overrides.ContainerOverrides[0].Name, "FargateApp"); - Assert.Equal(ecsEvent.Detail.Overrides.ContainerOverrides[0].Environment.Count, 1); - Assert.Equal(ecsEvent.Detail.Overrides.ContainerOverrides[0].Environment[0].Name, "testname"); - Assert.Equal(ecsEvent.Detail.Overrides.ContainerOverrides[0].Environment[0].Value, "testvalue"); - - Assert.Equal(ecsEvent.Detail.Connectivity, "CONNECTED"); - Assert.Equal(ecsEvent.Detail.ConnectivityAt.ToUniversalTime(), DateTime.Parse("2020-01-23T17:57:38.453Z").ToUniversalTime()); - Assert.Equal(ecsEvent.Detail.PullStartedAt.ToUniversalTime(), DateTime.Parse("2020-01-23T17:57:52.103Z").ToUniversalTime()); - Assert.Equal(ecsEvent.Detail.StartedAt.ToUniversalTime(), DateTime.Parse("2020-01-23T17:57:58.103Z").ToUniversalTime()); - Assert.Equal(ecsEvent.Detail.PullStoppedAt.ToUniversalTime(), DateTime.Parse("2020-01-23T17:57:55.103Z").ToUniversalTime()); - Assert.Equal(ecsEvent.Detail.UpdatedAt.ToUniversalTime(), DateTime.Parse("2020-01-23T17:57:58.103Z").ToUniversalTime()); - Assert.Equal(ecsEvent.Detail.TaskArn, "arn:aws:ecs:us-west-2:111122223333:task/FargateCluster/c13b4cb40f1f4fe4a2971f76ae5a47ad"); - Assert.Equal(ecsEvent.Detail.TaskDefinitionArn, "arn:aws:ecs:us-west-2:111122223333:task-definition/sample-fargate:1"); - Assert.Equal(ecsEvent.Detail.Version, 4); - Assert.Equal(ecsEvent.Detail.PlatformVersion, "1.3.0"); + Assert.Single(ecsEvent.Detail.Containers[0].NetworkInterfaces); + Assert.Equal("1789bcae-ddfb-4d10-8ebe-8ac87ddba5b8", ecsEvent.Detail.Containers[0].NetworkInterfaces[0].AttachmentId); + Assert.Equal("10.0.0.139", ecsEvent.Detail.Containers[0].NetworkInterfaces[0].PrivateIpv4Address); + Assert.Equal("0", ecsEvent.Detail.Containers[0].Cpu); + + Assert.Equal(DateTime.Parse("2020-01-23T17:57:34.402Z").ToUniversalTime(), ecsEvent.Detail.CreatedAt.ToUniversalTime()); + Assert.Equal("FARGATE", ecsEvent.Detail.LaunchType); + Assert.Equal("256", ecsEvent.Detail.Cpu); + Assert.Equal("512", ecsEvent.Detail.Memory); + Assert.Equal("RUNNING", ecsEvent.Detail.DesiredStatus); + Assert.Equal("family:sample-fargate", ecsEvent.Detail.Group); + Assert.Equal("RUNNING", ecsEvent.Detail.LastStatus); + + Assert.Single(ecsEvent.Detail.Overrides.ContainerOverrides); + Assert.Equal("FargateApp", ecsEvent.Detail.Overrides.ContainerOverrides[0].Name); + Assert.Single(ecsEvent.Detail.Overrides.ContainerOverrides[0].Environment); + Assert.Equal("testname", ecsEvent.Detail.Overrides.ContainerOverrides[0].Environment[0].Name); + Assert.Equal("testvalue", ecsEvent.Detail.Overrides.ContainerOverrides[0].Environment[0].Value); + + Assert.Equal("CONNECTED", ecsEvent.Detail.Connectivity); + Assert.Equal(DateTime.Parse("2020-01-23T17:57:38.453Z").ToUniversalTime(), ecsEvent.Detail.ConnectivityAt.ToUniversalTime()); + Assert.Equal(DateTime.Parse("2020-01-23T17:57:52.103Z").ToUniversalTime(), ecsEvent.Detail.PullStartedAt.ToUniversalTime()); + Assert.Equal(DateTime.Parse("2020-01-23T17:57:58.103Z").ToUniversalTime(), ecsEvent.Detail.StartedAt.ToUniversalTime()); + Assert.Equal(DateTime.Parse("2020-01-23T17:57:55.103Z").ToUniversalTime(), ecsEvent.Detail.PullStoppedAt.ToUniversalTime()); + Assert.Equal(DateTime.Parse("2020-01-23T17:57:58.103Z").ToUniversalTime(), ecsEvent.Detail.UpdatedAt.ToUniversalTime()); + Assert.Equal("arn:aws:ecs:us-west-2:111122223333:task/FargateCluster/c13b4cb40f1f4fe4a2971f76ae5a47ad", ecsEvent.Detail.TaskArn); + Assert.Equal("arn:aws:ecs:us-west-2:111122223333:task-definition/sample-fargate:1", ecsEvent.Detail.TaskDefinitionArn); + Assert.Equal(4, ecsEvent.Detail.Version); + Assert.Equal("1.3.0", ecsEvent.Detail.PlatformVersion); Handle(ecsEvent); } @@ -3414,10 +3282,8 @@ private void Handle(ECSTaskStateChangeEvent ecsEvent) [Theory] [InlineData(typeof(JsonSerializer))] -#if NETCOREAPP3_1_OR_GREATER [InlineData(typeof(Amazon.Lambda.Serialization.SystemTextJson.LambdaJsonSerializer))] [InlineData(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))] -#endif public void KafkaEventTest(Type serializerType) { var serializer = Activator.CreateInstance(serializerType) as ILambdaSerializer; @@ -3425,40 +3291,38 @@ public void KafkaEventTest(Type serializerType) { var kafkaEvent = serializer.Deserialize(fileStream); Assert.NotNull(kafkaEvent); - Assert.Equal(kafkaEvent.EventSource, "aws:kafka"); - Assert.Equal(kafkaEvent.EventSourceArn, "arn:aws:kafka:us-east-1:123456789012:cluster/vpc-3432434/4834-3547-3455-9872-7929"); - Assert.Equal(kafkaEvent.BootstrapServers, "b-2.demo-cluster-1.a1bcde.c1.kafka.us-east-1.amazonaws.com:9092,b-1.demo-cluster-1.a1bcde.c1.kafka.us-east-1.amazonaws.com:9092"); + Assert.Equal("aws:kafka", kafkaEvent.EventSource); + Assert.Equal("arn:aws:kafka:us-east-1:123456789012:cluster/vpc-3432434/4834-3547-3455-9872-7929", kafkaEvent.EventSourceArn); + Assert.Equal("b-2.demo-cluster-1.a1bcde.c1.kafka.us-east-1.amazonaws.com:9092,b-1.demo-cluster-1.a1bcde.c1.kafka.us-east-1.amazonaws.com:9092", kafkaEvent.BootstrapServers); Assert.NotNull(kafkaEvent.Records); - Assert.Equal(kafkaEvent.Records.Count, 1); + Assert.Single(kafkaEvent.Records); var record = kafkaEvent.Records.FirstOrDefault(); - Assert.NotNull(record); - Assert.Equal(record.Key, "mytopic-0"); + Assert.Equal("mytopic-0", record.Key); - Assert.Equal(record.Value.Count, 1); + Assert.Single(record.Value); var eventRecord = record.Value.FirstOrDefault(); - Assert.Equal(eventRecord.Topic, "mytopic"); - Assert.Equal(eventRecord.Partition, 12); - Assert.Equal(eventRecord.Offset, 3043205); - Assert.Equal(eventRecord.Timestamp, 1545084650987); - Assert.Equal(eventRecord.TimestampType, "CREATE_TIME"); + Assert.Equal("mytopic", eventRecord.Topic); + Assert.Equal(12, eventRecord.Partition); + Assert.Equal(3043205, eventRecord.Offset); + Assert.Equal(1545084650987, eventRecord.Timestamp); + Assert.Equal("CREATE_TIME", eventRecord.TimestampType); - Assert.Equal(new StreamReader(eventRecord.Value).ReadToEnd(), "Hello, this is a test."); + Assert.Equal("Hello, this is a test.", new StreamReader(eventRecord.Value).ReadToEnd()); - Assert.Equal(eventRecord.Headers.Count, 8); + Assert.Equal(8, eventRecord.Headers.Count); var eventRecordHeader = eventRecord.Headers.FirstOrDefault(); Assert.NotNull(eventRecordHeader); - Assert.Equal(eventRecordHeader.Count, 1); + Assert.Single(eventRecordHeader); var eventRecordHeaderValue = eventRecordHeader.FirstOrDefault(); - Assert.NotNull(eventRecordHeaderValue); - Assert.Equal(eventRecordHeaderValue.Key, "headerKey"); + Assert.Equal("headerKey", eventRecordHeaderValue.Key); // Convert sbyte[] to byte[] array. var tempHeaderValueByteArray = new byte[eventRecordHeaderValue.Value.Length]; Buffer.BlockCopy(eventRecordHeaderValue.Value, 0, tempHeaderValueByteArray, 0, tempHeaderValueByteArray.Length); - Assert.Equal(Encoding.UTF8.GetString(tempHeaderValueByteArray), "headerValue"); + Assert.Equal("headerValue", Encoding.UTF8.GetString(tempHeaderValueByteArray)); Handle(kafkaEvent); } @@ -3479,10 +3343,8 @@ private void Handle(KafkaEvent kafkaEvent) [Theory] [InlineData(typeof(JsonSerializer))] -#if NETCOREAPP3_1_OR_GREATER [InlineData(typeof(Amazon.Lambda.Serialization.SystemTextJson.LambdaJsonSerializer))] [InlineData(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))] -#endif public void ActiveMQEventTest(Type serializerType) { var serializer = Activator.CreateInstance(serializerType) as ILambdaSerializer; @@ -3522,10 +3384,8 @@ public void ActiveMQEventTest(Type serializerType) [Theory] [InlineData(typeof(JsonSerializer))] -#if NETCOREAPP3_1_OR_GREATER [InlineData(typeof(Amazon.Lambda.Serialization.SystemTextJson.LambdaJsonSerializer))] [InlineData(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))] -#endif public void RabbitMQEventTest(Type serializerType) { var serializer = Activator.CreateInstance(serializerType) as ILambdaSerializer; @@ -3536,7 +3396,7 @@ public void RabbitMQEventTest(Type serializerType) Assert.Equal("aws:rmq", rabbitmqEvent.EventSource); Assert.Equal("arn:aws:mq:us-west-2:112556298976:broker:pizzaBroker:b-9bcfa592-423a-4942-879d-eb284b418fc8", rabbitmqEvent.EventSourceArn); - Assert.Equal(1, rabbitmqEvent.RmqMessagesByQueue.Count); + Assert.Single(rabbitmqEvent.RmqMessagesByQueue); Assert.Equal(2, rabbitmqEvent.RmqMessagesByQueue["pizzaQueue::/"].Count); var firstMessage = rabbitmqEvent.RmqMessagesByQueue["pizzaQueue::/"][0]; @@ -3563,7 +3423,7 @@ public void RabbitMQEventTest(Type serializerType) Assert.NotNull(secondMessage.BasicProperties); Assert.Null(secondMessage.BasicProperties.ContentType); Assert.Null(secondMessage.BasicProperties.ContentEncoding); - Assert.Equal(0, secondMessage.BasicProperties.Headers.Count); + Assert.Empty(secondMessage.BasicProperties.Headers); Assert.Equal(1, secondMessage.BasicProperties.DeliveryMode); Assert.Null(secondMessage.BasicProperties.Priority); Assert.Null(secondMessage.BasicProperties.CorrelationId); @@ -3583,10 +3443,8 @@ public void RabbitMQEventTest(Type serializerType) [Theory] [InlineData(typeof(JsonSerializer))] -#if NETCOREAPP3_1_OR_GREATER [InlineData(typeof(Amazon.Lambda.Serialization.SystemTextJson.LambdaJsonSerializer))] [InlineData(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))] -#endif public void APIGatewayCustomAuthorizerV2Request(Type serializerType) { var serializer = Activator.CreateInstance(serializerType) as ILambdaSerializer; @@ -3655,10 +3513,8 @@ public void APIGatewayCustomAuthorizerV2Request(Type serializerType) [Theory] [InlineData(typeof(JsonSerializer))] -#if NETCOREAPP3_1_OR_GREATER [InlineData(typeof(Amazon.Lambda.Serialization.SystemTextJson.LambdaJsonSerializer))] [InlineData(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))] -#endif public void APIGatewayCustomAuthorizerV2SimpleResponse(Type serializerType) { var response = new APIGatewayCustomAuthorizerV2SimpleResponse @@ -3680,10 +3536,8 @@ public void APIGatewayCustomAuthorizerV2SimpleResponse(Type serializerType) [Theory] [InlineData(typeof(JsonSerializer))] -#if NETCOREAPP3_1_OR_GREATER [InlineData(typeof(Amazon.Lambda.Serialization.SystemTextJson.LambdaJsonSerializer))] [InlineData(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))] -#endif public void APIGatewayCustomAuthorizerV2IamResponse(Type serializerType) { var response = new APIGatewayCustomAuthorizerV2IamResponse @@ -3695,7 +3549,7 @@ public void APIGatewayCustomAuthorizerV2IamResponse(Type serializerType) { new APIGatewayCustomAuthorizerPolicy.IAMPolicyStatement { - Action = new HashSet { "execute-api:Invoke" }, + Action = ["execute-api:Invoke"], Effect = "Allow", Resource = new HashSet{ "arn:aws:execute-api:{regionId}:{accountId}:{apiId}/{stage}/{httpVerb}/[{resource}/[{child-resources}]]" } } @@ -3757,10 +3611,8 @@ public void SerializeWithCamelCaseNamingStrategyCanDeserializeBothCamelAndPascal [Theory] [InlineData(typeof(JsonSerializer))] -#if NETCOREAPP3_1_OR_GREATER [InlineData(typeof(Amazon.Lambda.Serialization.SystemTextJson.LambdaJsonSerializer))] [InlineData(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))] -#endif public void CloudWatchEventsS3ObjectCreate(Type serializerType) { var serializer = Activator.CreateInstance(serializerType) as ILambdaSerializer; @@ -3787,10 +3639,8 @@ public void CloudWatchEventsS3ObjectCreate(Type serializerType) [Theory] [InlineData(typeof(JsonSerializer))] -#if NETCOREAPP3_1_OR_GREATER [InlineData(typeof(Amazon.Lambda.Serialization.SystemTextJson.LambdaJsonSerializer))] [InlineData(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))] -#endif public void CloudWatchEventsS3ObjectDelete(Type serializerType) { var serializer = Activator.CreateInstance(serializerType) as ILambdaSerializer; @@ -3817,10 +3667,8 @@ public void CloudWatchEventsS3ObjectDelete(Type serializerType) [Theory] [InlineData(typeof(JsonSerializer))] -#if NETCOREAPP3_1_OR_GREATER [InlineData(typeof(Amazon.Lambda.Serialization.SystemTextJson.LambdaJsonSerializer))] [InlineData(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))] -#endif public void CloudWatchEventsS3ObjectRestore(Type serializerType) { var serializer = Activator.CreateInstance(serializerType) as ILambdaSerializer; @@ -3846,10 +3694,8 @@ public void CloudWatchEventsS3ObjectRestore(Type serializerType) [Theory] [InlineData(typeof(JsonSerializer))] -#if NETCOREAPP3_1_OR_GREATER [InlineData(typeof(Amazon.Lambda.Serialization.SystemTextJson.LambdaJsonSerializer))] [InlineData(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))] -#endif public void CloudWatchTranscribeJobStateChangeCompleted(Type serializerType) { var serializer = Activator.CreateInstance(serializerType) as ILambdaSerializer; @@ -3868,10 +3714,8 @@ public void CloudWatchTranscribeJobStateChangeCompleted(Type serializerType) [Theory] [InlineData(typeof(JsonSerializer))] -#if NETCOREAPP3_1_OR_GREATER [InlineData(typeof(Amazon.Lambda.Serialization.SystemTextJson.LambdaJsonSerializer))] [InlineData(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))] -#endif public void CloudWatchTranscribeJobStateChangeFailed(Type serializerType) { var serializer = Activator.CreateInstance(serializerType) as ILambdaSerializer; @@ -3890,10 +3734,8 @@ public void CloudWatchTranscribeJobStateChangeFailed(Type serializerType) [Theory] [InlineData(typeof(JsonSerializer))] -#if NETCOREAPP3_1_OR_GREATER [InlineData(typeof(Amazon.Lambda.Serialization.SystemTextJson.LambdaJsonSerializer))] [InlineData(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))] -#endif public void CloudWatchTranslateTextTranslationJobStateChange(Type serializerType) { var serializer = Activator.CreateInstance(serializerType) as ILambdaSerializer; @@ -3911,10 +3753,8 @@ public void CloudWatchTranslateTextTranslationJobStateChange(Type serializerType [Theory] [InlineData(typeof(JsonSerializer))] -#if NETCOREAPP3_1_OR_GREATER [InlineData(typeof(Amazon.Lambda.Serialization.SystemTextJson.LambdaJsonSerializer))] [InlineData(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))] -#endif public void CloudWatchTranslateParallelDataStateChangeCreate(Type serializerType) { var serializer = Activator.CreateInstance(serializerType) as ILambdaSerializer; @@ -3933,10 +3773,8 @@ public void CloudWatchTranslateParallelDataStateChangeCreate(Type serializerType [Theory] [InlineData(typeof(JsonSerializer))] -#if NETCOREAPP3_1_OR_GREATER [InlineData(typeof(Amazon.Lambda.Serialization.SystemTextJson.LambdaJsonSerializer))] [InlineData(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))] -#endif public void CloudWatchTranslateParallelDataStateChangeUpdate(Type serializerType) { var serializer = Activator.CreateInstance(serializerType) as ILambdaSerializer; @@ -3967,7 +3805,7 @@ public void TestJsonIncludeNullValueSerializer() SomeOtherValue = null }; - MemoryStream ms = new MemoryStream(); + var ms = new MemoryStream(); serializer.Serialize(response, ms); ms.Position = 0; var json = new StreamReader(ms).ReadToEnd(); diff --git a/Libraries/test/IntegrationTests.Helpers/CloudFormationHelper.cs b/Libraries/test/IntegrationTests.Helpers/CloudFormationHelper.cs index 76a1c2439..00d5f5773 100644 --- a/Libraries/test/IntegrationTests.Helpers/CloudFormationHelper.cs +++ b/Libraries/test/IntegrationTests.Helpers/CloudFormationHelper.cs @@ -18,7 +18,7 @@ public CloudFormationHelper(IAmazonCloudFormation cloudFormationClient) public async Task GetStackStatusAsync(string stackName) { var stack = await GetStackAsync(stackName); - return stack?.StackStatus; + return stack?.StackStatus ?? StackStatus.CREATE_FAILED; } public async Task IsDeletedAsync(string stackName) diff --git a/Libraries/test/IntegrationTests.Helpers/IntegrationTests.Helpers.csproj b/Libraries/test/IntegrationTests.Helpers/IntegrationTests.Helpers.csproj index c1f9c663b..6ac30f29c 100644 --- a/Libraries/test/IntegrationTests.Helpers/IntegrationTests.Helpers.csproj +++ b/Libraries/test/IntegrationTests.Helpers/IntegrationTests.Helpers.csproj @@ -1,16 +1,16 @@ - net6.0 + net8.0 disable enable - - - - + + + + diff --git a/Libraries/test/IntegrationTests.Helpers/LambdaHelper.cs b/Libraries/test/IntegrationTests.Helpers/LambdaHelper.cs index 591eb5e06..2a6d70f6c 100644 --- a/Libraries/test/IntegrationTests.Helpers/LambdaHelper.cs +++ b/Libraries/test/IntegrationTests.Helpers/LambdaHelper.cs @@ -72,14 +72,21 @@ public async Task WaitTillNotPending(List functions) { while (true) { - var response = await _lambdaClient.GetFunctionConfigurationAsync(new GetFunctionConfigurationRequest { FunctionName = function }); - if (response.State == State.Pending) + try { - await Task.Delay(1000); + var response = await _lambdaClient.GetFunctionConfigurationAsync(new GetFunctionConfigurationRequest { FunctionName = function }); + if (response.State == State.Pending) + { + await Task.Delay(1000); + } + else + { + break; + } } - else + catch(TooManyRequestsException) { - break; + await Task.Delay(10000); } } } diff --git a/Libraries/test/PowerShellTests/ExceptionHandlingTests.cs b/Libraries/test/PowerShellTests/ExceptionHandlingTests.cs index 2adc059a3..b4e7221c8 100644 --- a/Libraries/test/PowerShellTests/ExceptionHandlingTests.cs +++ b/Libraries/test/PowerShellTests/ExceptionHandlingTests.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.IO; using System.Text; @@ -115,7 +115,7 @@ private void ExceptionValidator(PowerShellScriptsAsFunctions.Function function, } Assert.NotNull(foundException); - Assert.True(foundException.GetType().Name.EndsWith(exceptionType)); + Assert.EndsWith(exceptionType, foundException.GetType().Name); if(message != null) { diff --git a/Libraries/test/PowerShellTests/PowerShellTests.csproj b/Libraries/test/PowerShellTests/PowerShellTests.csproj index c97458cf5..8d38a5470 100644 --- a/Libraries/test/PowerShellTests/PowerShellTests.csproj +++ b/Libraries/test/PowerShellTests/PowerShellTests.csproj @@ -13,12 +13,12 @@ - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/Libraries/test/PowerShellTests/ScriptInvokeTests.cs b/Libraries/test/PowerShellTests/ScriptInvokeTests.cs index 2fa5e0e09..4e65f2cbf 100644 --- a/Libraries/test/PowerShellTests/ScriptInvokeTests.cs +++ b/Libraries/test/PowerShellTests/ScriptInvokeTests.cs @@ -123,7 +123,7 @@ public void UseAWSPowerShellCmdLetTest() Assert.Contains("AWS Lambda", resultString); } -#if NETCOREAPP3_1_OR_GREATER +#if NET8_0_OR_GREATER [Fact] public void ForObjectParallelTest() { diff --git a/Libraries/test/SnapshotRestore.Registry.Tests/SnapshotRestore.Registry.Tests.csproj b/Libraries/test/SnapshotRestore.Registry.Tests/SnapshotRestore.Registry.Tests.csproj index dd41e38a4..9d3345666 100644 --- a/Libraries/test/SnapshotRestore.Registry.Tests/SnapshotRestore.Registry.Tests.csproj +++ b/Libraries/test/SnapshotRestore.Registry.Tests/SnapshotRestore.Registry.Tests.csproj @@ -7,12 +7,12 @@ - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/Libraries/test/TestCustomAuthorizerApp.IntegrationTests/IntegrationTestContextFixtureCollection.cs b/Libraries/test/TestCustomAuthorizerApp.IntegrationTests/IntegrationTestContextFixtureCollection.cs index f90a2401b..dd673e7b9 100644 --- a/Libraries/test/TestCustomAuthorizerApp.IntegrationTests/IntegrationTestContextFixtureCollection.cs +++ b/Libraries/test/TestCustomAuthorizerApp.IntegrationTests/IntegrationTestContextFixtureCollection.cs @@ -2,7 +2,7 @@ namespace TestCustomAuthorizerApp.IntegrationTests; -[CollectionDefinition("Integration Tests")] +[CollectionDefinition("Integration Tests", DisableParallelization = true)] public class IntegrationTestContextFixtureCollection : ICollectionFixture { // This class has no code, and is never created. Its purpose is simply diff --git a/Libraries/test/TestCustomAuthorizerApp.IntegrationTests/NonStringAuthorizerTests.cs b/Libraries/test/TestCustomAuthorizerApp.IntegrationTests/NonStringAuthorizerTests.cs index 7b58cfd64..0d25145dd 100644 --- a/Libraries/test/TestCustomAuthorizerApp.IntegrationTests/NonStringAuthorizerTests.cs +++ b/Libraries/test/TestCustomAuthorizerApp.IntegrationTests/NonStringAuthorizerTests.cs @@ -105,7 +105,7 @@ public async Task NonStringUserInfo_IntValueIsCorrectType() // Verify TenantId is returned as a number, not a string var tenantIdToken = json["TenantId"]; Assert.NotNull(tenantIdToken); - Assert.Equal(JTokenType.Integer, tenantIdToken.Type); + Assert.Equal(JTokenType.Integer, tenantIdToken!.Type); } /// @@ -129,6 +129,6 @@ public async Task NonStringUserInfo_BoolValueIsCorrectType() // Verify IsAdmin is returned as a boolean, not a string var isAdminToken = json["IsAdmin"]; Assert.NotNull(isAdminToken); - Assert.Equal(JTokenType.Boolean, isAdminToken.Type); + Assert.Equal(JTokenType.Boolean, isAdminToken!.Type); } } diff --git a/Libraries/test/TestCustomAuthorizerApp.IntegrationTests/TestCustomAuthorizerApp.IntegrationTests.csproj b/Libraries/test/TestCustomAuthorizerApp.IntegrationTests/TestCustomAuthorizerApp.IntegrationTests.csproj index 9271cfe61..02540483e 100644 --- a/Libraries/test/TestCustomAuthorizerApp.IntegrationTests/TestCustomAuthorizerApp.IntegrationTests.csproj +++ b/Libraries/test/TestCustomAuthorizerApp.IntegrationTests/TestCustomAuthorizerApp.IntegrationTests.csproj @@ -1,7 +1,7 @@ - + - net8.0 + net8.0;net10.0 enable enable Library @@ -9,11 +9,11 @@ - - - - - + + + + + all runtime; build; native; contentfiles; analyzers diff --git a/Libraries/test/TestCustomAuthorizerApp/TestCustomAuthorizerApp.csproj b/Libraries/test/TestCustomAuthorizerApp/TestCustomAuthorizerApp.csproj index 6abb25b6c..1b49e1c0e 100644 --- a/Libraries/test/TestCustomAuthorizerApp/TestCustomAuthorizerApp.csproj +++ b/Libraries/test/TestCustomAuthorizerApp/TestCustomAuthorizerApp.csproj @@ -1,6 +1,6 @@ - net6.0 + net10.0 enable enable true diff --git a/Libraries/test/TestCustomAuthorizerApp/serverless.template b/Libraries/test/TestCustomAuthorizerApp/serverless.template index ebce2bc30..e4c7c9b2a 100644 --- a/Libraries/test/TestCustomAuthorizerApp/serverless.template +++ b/Libraries/test/TestCustomAuthorizerApp/serverless.template @@ -111,7 +111,7 @@ "Tool": "Amazon.Lambda.Annotations" }, "Properties": { - "Runtime": "dotnet6", + "Runtime": "dotnet10", "CodeUri": ".", "MemorySize": 512, "Timeout": 30, @@ -128,7 +128,7 @@ "Tool": "Amazon.Lambda.Annotations" }, "Properties": { - "Runtime": "dotnet6", + "Runtime": "dotnet10", "CodeUri": ".", "MemorySize": 512, "Timeout": 30, @@ -145,7 +145,7 @@ "Tool": "Amazon.Lambda.Annotations" }, "Properties": { - "Runtime": "dotnet6", + "Runtime": "dotnet10", "CodeUri": ".", "MemorySize": 512, "Timeout": 30, @@ -162,7 +162,7 @@ "Tool": "Amazon.Lambda.Annotations" }, "Properties": { - "Runtime": "dotnet6", + "Runtime": "dotnet10", "CodeUri": ".", "MemorySize": 512, "Timeout": 30, @@ -179,7 +179,7 @@ "Tool": "Amazon.Lambda.Annotations" }, "Properties": { - "Runtime": "dotnet6", + "Runtime": "dotnet10", "CodeUri": ".", "MemorySize": 512, "Timeout": 30, @@ -207,7 +207,7 @@ } }, "Properties": { - "Runtime": "dotnet6", + "Runtime": "dotnet10", "CodeUri": ".", "MemorySize": 512, "Timeout": 30, @@ -250,7 +250,7 @@ } }, "Properties": { - "Runtime": "dotnet6", + "Runtime": "dotnet10", "CodeUri": ".", "MemorySize": 512, "Timeout": 30, @@ -292,7 +292,7 @@ } }, "Properties": { - "Runtime": "dotnet6", + "Runtime": "dotnet10", "CodeUri": ".", "MemorySize": 512, "Timeout": 30, @@ -332,7 +332,7 @@ } }, "Properties": { - "Runtime": "dotnet6", + "Runtime": "dotnet10", "CodeUri": ".", "MemorySize": 512, "Timeout": 30, @@ -376,7 +376,7 @@ } }, "Properties": { - "Runtime": "dotnet6", + "Runtime": "dotnet10", "CodeUri": ".", "MemorySize": 512, "Timeout": 30, @@ -420,7 +420,7 @@ } }, "Properties": { - "Runtime": "dotnet6", + "Runtime": "dotnet10", "CodeUri": ".", "MemorySize": 512, "Timeout": 30, @@ -463,7 +463,7 @@ } }, "Properties": { - "Runtime": "dotnet6", + "Runtime": "dotnet10", "CodeUri": ".", "MemorySize": 512, "Timeout": 30, @@ -506,7 +506,7 @@ } }, "Properties": { - "Runtime": "dotnet6", + "Runtime": "dotnet10", "CodeUri": ".", "MemorySize": 512, "Timeout": 30, @@ -549,7 +549,7 @@ } }, "Properties": { - "Runtime": "dotnet6", + "Runtime": "dotnet10", "CodeUri": ".", "MemorySize": 512, "Timeout": 30, diff --git a/Libraries/test/TestCustomAuthorizerApp/src/Function/serverless.template b/Libraries/test/TestCustomAuthorizerApp/src/Function/serverless.template index ebce2bc30..e4c7c9b2a 100644 --- a/Libraries/test/TestCustomAuthorizerApp/src/Function/serverless.template +++ b/Libraries/test/TestCustomAuthorizerApp/src/Function/serverless.template @@ -111,7 +111,7 @@ "Tool": "Amazon.Lambda.Annotations" }, "Properties": { - "Runtime": "dotnet6", + "Runtime": "dotnet10", "CodeUri": ".", "MemorySize": 512, "Timeout": 30, @@ -128,7 +128,7 @@ "Tool": "Amazon.Lambda.Annotations" }, "Properties": { - "Runtime": "dotnet6", + "Runtime": "dotnet10", "CodeUri": ".", "MemorySize": 512, "Timeout": 30, @@ -145,7 +145,7 @@ "Tool": "Amazon.Lambda.Annotations" }, "Properties": { - "Runtime": "dotnet6", + "Runtime": "dotnet10", "CodeUri": ".", "MemorySize": 512, "Timeout": 30, @@ -162,7 +162,7 @@ "Tool": "Amazon.Lambda.Annotations" }, "Properties": { - "Runtime": "dotnet6", + "Runtime": "dotnet10", "CodeUri": ".", "MemorySize": 512, "Timeout": 30, @@ -179,7 +179,7 @@ "Tool": "Amazon.Lambda.Annotations" }, "Properties": { - "Runtime": "dotnet6", + "Runtime": "dotnet10", "CodeUri": ".", "MemorySize": 512, "Timeout": 30, @@ -207,7 +207,7 @@ } }, "Properties": { - "Runtime": "dotnet6", + "Runtime": "dotnet10", "CodeUri": ".", "MemorySize": 512, "Timeout": 30, @@ -250,7 +250,7 @@ } }, "Properties": { - "Runtime": "dotnet6", + "Runtime": "dotnet10", "CodeUri": ".", "MemorySize": 512, "Timeout": 30, @@ -292,7 +292,7 @@ } }, "Properties": { - "Runtime": "dotnet6", + "Runtime": "dotnet10", "CodeUri": ".", "MemorySize": 512, "Timeout": 30, @@ -332,7 +332,7 @@ } }, "Properties": { - "Runtime": "dotnet6", + "Runtime": "dotnet10", "CodeUri": ".", "MemorySize": 512, "Timeout": 30, @@ -376,7 +376,7 @@ } }, "Properties": { - "Runtime": "dotnet6", + "Runtime": "dotnet10", "CodeUri": ".", "MemorySize": 512, "Timeout": 30, @@ -420,7 +420,7 @@ } }, "Properties": { - "Runtime": "dotnet6", + "Runtime": "dotnet10", "CodeUri": ".", "MemorySize": 512, "Timeout": 30, @@ -463,7 +463,7 @@ } }, "Properties": { - "Runtime": "dotnet6", + "Runtime": "dotnet10", "CodeUri": ".", "MemorySize": 512, "Timeout": 30, @@ -506,7 +506,7 @@ } }, "Properties": { - "Runtime": "dotnet6", + "Runtime": "dotnet10", "CodeUri": ".", "MemorySize": 512, "Timeout": 30, @@ -549,7 +549,7 @@ } }, "Properties": { - "Runtime": "dotnet6", + "Runtime": "dotnet10", "CodeUri": ".", "MemorySize": 512, "Timeout": 30, diff --git a/Libraries/test/TestExecutableServerlessApp/NullableReferenceTypeExample.cs b/Libraries/test/TestExecutableServerlessApp/NullableReferenceTypeExample.cs index 9b930176d..a50a6c57a 100644 --- a/Libraries/test/TestExecutableServerlessApp/NullableReferenceTypeExample.cs +++ b/Libraries/test/TestExecutableServerlessApp/NullableReferenceTypeExample.cs @@ -1,4 +1,4 @@ -using Amazon.Lambda.Annotations; +using Amazon.Lambda.Annotations; using Amazon.Lambda.Annotations.APIGateway; using Amazon.Lambda.Core; @@ -8,7 +8,7 @@ public class NullableReferenceTypeExample { [LambdaFunction(PackageType = LambdaPackageType.Image)] [HttpApi(LambdaHttpMethod.Get, "/nullableheaderhttpapi")] - public void NullableHeaderHttpApi([FromHeader(Name = "MyHeader")] string? text, ILambdaContext context) + public void NullableHeaderHttpApi([FromHeader(Name = "MyHeader")] string text, ILambdaContext context) { context.Logger.LogLine(text); } diff --git a/Libraries/test/TestExecutableServerlessApp/TestExecutableServerlessApp.csproj b/Libraries/test/TestExecutableServerlessApp/TestExecutableServerlessApp.csproj index 0fa9708b4..ba6eba8e9 100644 --- a/Libraries/test/TestExecutableServerlessApp/TestExecutableServerlessApp.csproj +++ b/Libraries/test/TestExecutableServerlessApp/TestExecutableServerlessApp.csproj @@ -1,7 +1,7 @@  exe - net6.0 + net10.0 true Lambda @@ -19,7 +19,7 @@ - + diff --git a/Libraries/test/TestExecutableServerlessApp/serverless.template b/Libraries/test/TestExecutableServerlessApp/serverless.template index 84558771c..b4c51121d 100644 --- a/Libraries/test/TestExecutableServerlessApp/serverless.template +++ b/Libraries/test/TestExecutableServerlessApp/serverless.template @@ -22,7 +22,7 @@ } }, "Resources": { - "TestServerlessAppVoidExampleVoidReturnGenerated": { + "TestServerlessAppTaskExampleTaskReturnGenerated": { "Type": "AWS::Serverless::Function", "Metadata": { "Tool": "Amazon.Lambda.Annotations" @@ -42,37 +42,56 @@ }, "Environment": { "Variables": { - "ANNOTATIONS_HANDLER": "VoidReturn" + "ANNOTATIONS_HANDLER": "TaskReturn" } } } }, - "TestServerlessAppTaskExampleTaskReturnGenerated": { + "TestServerlessAppParameterlessMethodsNoParameterGenerated": { "Type": "AWS::Serverless::Function", "Metadata": { "Tool": "Amazon.Lambda.Annotations" }, "Properties": { + "Runtime": "provided.al2", + "CodeUri": ".", "MemorySize": 512, "Timeout": 30, "Policies": [ "AWSLambdaBasicExecutionRole" ], - "PackageType": "Image", - "ImageUri": ".", - "ImageConfig": { - "Command": [ - "TestExecutableServerlessApp" - ] - }, + "PackageType": "Zip", + "Handler": "TestExecutableServerlessApp", "Environment": { "Variables": { - "ANNOTATIONS_HANDLER": "TaskReturn" + "ANNOTATIONS_HANDLER": "NoParameter" } } } }, - "TestServerlessAppCustomizeResponseExamplesOkResponseWithHeaderGenerated": { + "ToLower": { + "Type": "AWS::Serverless::Function", + "Metadata": { + "Tool": "Amazon.Lambda.Annotations" + }, + "Properties": { + "Runtime": "provided.al2", + "CodeUri": ".", + "MemorySize": 512, + "Timeout": 30, + "Policies": [ + "AWSLambdaBasicExecutionRole" + ], + "PackageType": "Zip", + "Handler": "TestExecutableServerlessApp", + "Environment": { + "Variables": { + "ANNOTATIONS_HANDLER": "ToLower" + } + } + } + }, + "TestServerlessAppNullableReferenceTypeExampleNullableHeaderHttpApiGenerated": { "Type": "AWS::Serverless::Function", "Metadata": { "Tool": "Amazon.Lambda.Annotations", @@ -101,33 +120,49 @@ }, "Environment": { "Variables": { - "ANNOTATIONS_HANDLER": "OkResponseWithHeader" + "ANNOTATIONS_HANDLER": "NullableHeaderHttpApi" } }, "Events": { "RootGet": { - "Type": "Api", + "Type": "HttpApi", "Properties": { - "Path": "/okresponsewithheader/{x}", + "Path": "/nullableheaderhttpapi", "Method": "GET" } } } } }, - "TestServerlessAppCustomizeResponseExamplesOkResponseWithHeaderAsyncGenerated": { + "TestServerlessAppDynamicExampleDynamicReturnGenerated": { "Type": "AWS::Serverless::Function", "Metadata": { - "Tool": "Amazon.Lambda.Annotations", - "SyncedEvents": [ - "RootGet" + "Tool": "Amazon.Lambda.Annotations" + }, + "Properties": { + "MemorySize": 512, + "Timeout": 30, + "Policies": [ + "AWSLambdaBasicExecutionRole" ], - "SyncedEventProperties": { - "RootGet": [ - "Path", - "Method" + "PackageType": "Image", + "ImageUri": ".", + "ImageConfig": { + "Command": [ + "TestExecutableServerlessApp" ] + }, + "Environment": { + "Variables": { + "ANNOTATIONS_HANDLER": "DynamicReturn" + } } + } + }, + "TestServerlessAppDynamicExampleDynamicInputGenerated": { + "Type": "AWS::Serverless::Function", + "Metadata": { + "Tool": "Amazon.Lambda.Annotations" }, "Properties": { "MemorySize": 512, @@ -144,21 +179,37 @@ }, "Environment": { "Variables": { - "ANNOTATIONS_HANDLER": "OkResponseWithHeaderAsync" + "ANNOTATIONS_HANDLER": "DynamicInput" } + } + } + }, + "ToUpper": { + "Type": "AWS::Serverless::Function", + "Metadata": { + "Tool": "Amazon.Lambda.Annotations" + }, + "Properties": { + "MemorySize": 512, + "Timeout": 30, + "Policies": [ + "AWSLambdaBasicExecutionRole" + ], + "PackageType": "Image", + "ImageUri": ".", + "ImageConfig": { + "Command": [ + "TestExecutableServerlessApp" + ] }, - "Events": { - "RootGet": { - "Type": "Api", - "Properties": { - "Path": "/okresponsewithheaderasync/{x}", - "Method": "GET" - } + "Environment": { + "Variables": { + "ANNOTATIONS_HANDLER": "ToUpper" } } } }, - "TestServerlessAppCustomizeResponseExamplesNotFoundResponseWithHeaderV2Generated": { + "TestServerlessAppCustomizeResponseExamplesOkResponseWithHeaderGenerated": { "Type": "AWS::Serverless::Function", "Metadata": { "Tool": "Amazon.Lambda.Annotations", @@ -187,21 +238,21 @@ }, "Environment": { "Variables": { - "ANNOTATIONS_HANDLER": "NotFoundResponseWithHeaderV2" + "ANNOTATIONS_HANDLER": "OkResponseWithHeader" } }, "Events": { "RootGet": { - "Type": "HttpApi", + "Type": "Api", "Properties": { - "Path": "/notfoundwithheaderv2/{x}", + "Path": "/okresponsewithheader/{x}", "Method": "GET" } } } } }, - "TestServerlessAppCustomizeResponseExamplesNotFoundResponseWithHeaderV2AsyncGenerated": { + "TestServerlessAppCustomizeResponseExamplesOkResponseWithHeaderAsyncGenerated": { "Type": "AWS::Serverless::Function", "Metadata": { "Tool": "Amazon.Lambda.Annotations", @@ -230,21 +281,21 @@ }, "Environment": { "Variables": { - "ANNOTATIONS_HANDLER": "NotFoundResponseWithHeaderV2Async" + "ANNOTATIONS_HANDLER": "OkResponseWithHeaderAsync" } }, "Events": { "RootGet": { - "Type": "HttpApi", + "Type": "Api", "Properties": { - "Path": "/notfoundwithheaderv2async/{x}", + "Path": "/okresponsewithheaderasync/{x}", "Method": "GET" } } } } }, - "TestServerlessAppCustomizeResponseExamplesNotFoundResponseWithHeaderV1Generated": { + "TestServerlessAppCustomizeResponseExamplesNotFoundResponseWithHeaderV2Generated": { "Type": "AWS::Serverless::Function", "Metadata": { "Tool": "Amazon.Lambda.Annotations", @@ -254,8 +305,7 @@ "SyncedEventProperties": { "RootGet": [ "Path", - "Method", - "PayloadFormatVersion" + "Method" ] } }, @@ -274,22 +324,21 @@ }, "Environment": { "Variables": { - "ANNOTATIONS_HANDLER": "NotFoundResponseWithHeaderV1" + "ANNOTATIONS_HANDLER": "NotFoundResponseWithHeaderV2" } }, "Events": { "RootGet": { "Type": "HttpApi", "Properties": { - "Path": "/notfoundwithheaderv1/{x}", - "Method": "GET", - "PayloadFormatVersion": "1.0" + "Path": "/notfoundwithheaderv2/{x}", + "Method": "GET" } } } } }, - "TestServerlessAppCustomizeResponseExamplesNotFoundResponseWithHeaderV1AsyncGenerated": { + "TestServerlessAppCustomizeResponseExamplesNotFoundResponseWithHeaderV2AsyncGenerated": { "Type": "AWS::Serverless::Function", "Metadata": { "Tool": "Amazon.Lambda.Annotations", @@ -299,8 +348,7 @@ "SyncedEventProperties": { "RootGet": [ "Path", - "Method", - "PayloadFormatVersion" + "Method" ] } }, @@ -319,66 +367,21 @@ }, "Environment": { "Variables": { - "ANNOTATIONS_HANDLER": "NotFoundResponseWithHeaderV1Async" + "ANNOTATIONS_HANDLER": "NotFoundResponseWithHeaderV2Async" } }, "Events": { "RootGet": { "Type": "HttpApi", "Properties": { - "Path": "/notfoundwithheaderv1async/{x}", - "Method": "GET", - "PayloadFormatVersion": "1.0" + "Path": "/notfoundwithheaderv2async/{x}", + "Method": "GET" } } } } }, - "TestServerlessAppParameterlessMethodsNoParameterGenerated": { - "Type": "AWS::Serverless::Function", - "Metadata": { - "Tool": "Amazon.Lambda.Annotations" - }, - "Properties": { - "Runtime": "provided.al2", - "CodeUri": ".", - "MemorySize": 512, - "Timeout": 30, - "Policies": [ - "AWSLambdaBasicExecutionRole" - ], - "PackageType": "Zip", - "Handler": "TestExecutableServerlessApp", - "Environment": { - "Variables": { - "ANNOTATIONS_HANDLER": "NoParameter" - } - } - } - }, - "TestServerlessAppParameterlessMethodWithResponseNoParameterWithResponseGenerated": { - "Type": "AWS::Serverless::Function", - "Metadata": { - "Tool": "Amazon.Lambda.Annotations" - }, - "Properties": { - "Runtime": "provided.al2", - "CodeUri": ".", - "MemorySize": 512, - "Timeout": 30, - "Policies": [ - "AWSLambdaBasicExecutionRole" - ], - "PackageType": "Zip", - "Handler": "TestExecutableServerlessApp", - "Environment": { - "Variables": { - "ANNOTATIONS_HANDLER": "NoParameterWithResponse" - } - } - } - }, - "TestServerlessAppNullableReferenceTypeExampleNullableHeaderHttpApiGenerated": { + "TestServerlessAppCustomizeResponseExamplesNotFoundResponseWithHeaderV1Generated": { "Type": "AWS::Serverless::Function", "Metadata": { "Tool": "Amazon.Lambda.Annotations", @@ -388,7 +391,8 @@ "SyncedEventProperties": { "RootGet": [ "Path", - "Method" + "Method", + "PayloadFormatVersion" ] } }, @@ -407,21 +411,22 @@ }, "Environment": { "Variables": { - "ANNOTATIONS_HANDLER": "NullableHeaderHttpApi" + "ANNOTATIONS_HANDLER": "NotFoundResponseWithHeaderV1" } }, "Events": { "RootGet": { "Type": "HttpApi", "Properties": { - "Path": "/nullableheaderhttpapi", - "Method": "GET" + "Path": "/notfoundwithheaderv1/{x}", + "Method": "GET", + "PayloadFormatVersion": "1.0" } } } } }, - "TestExecutableServerlessAppSourceGenerationSerializationExampleGetPersonGenerated": { + "TestServerlessAppCustomizeResponseExamplesNotFoundResponseWithHeaderV1AsyncGenerated": { "Type": "AWS::Serverless::Function", "Metadata": { "Tool": "Amazon.Lambda.Annotations", @@ -431,7 +436,8 @@ "SyncedEventProperties": { "RootGet": [ "Path", - "Method" + "Method", + "PayloadFormatVersion" ] } }, @@ -450,42 +456,21 @@ }, "Environment": { "Variables": { - "ANNOTATIONS_HANDLER": "GetPerson" + "ANNOTATIONS_HANDLER": "NotFoundResponseWithHeaderV1Async" } }, "Events": { "RootGet": { - "Type": "Api", + "Type": "HttpApi", "Properties": { - "Path": "/", - "Method": "GET" + "Path": "/notfoundwithheaderv1async/{x}", + "Method": "GET", + "PayloadFormatVersion": "1.0" } } } } }, - "ToLower": { - "Type": "AWS::Serverless::Function", - "Metadata": { - "Tool": "Amazon.Lambda.Annotations" - }, - "Properties": { - "Runtime": "provided.al2", - "CodeUri": ".", - "MemorySize": 512, - "Timeout": 30, - "Policies": [ - "AWSLambdaBasicExecutionRole" - ], - "PackageType": "Zip", - "Handler": "TestExecutableServerlessApp", - "Environment": { - "Variables": { - "ANNOTATIONS_HANDLER": "ToLower" - } - } - } - }, "GreeterSayHello": { "Type": "AWS::Serverless::Function", "Metadata": { @@ -576,32 +561,29 @@ } } }, - "TestServerlessAppDynamicExampleDynamicReturnGenerated": { + "TestServerlessAppParameterlessMethodWithResponseNoParameterWithResponseGenerated": { "Type": "AWS::Serverless::Function", "Metadata": { "Tool": "Amazon.Lambda.Annotations" }, "Properties": { + "Runtime": "provided.al2", + "CodeUri": ".", "MemorySize": 512, "Timeout": 30, "Policies": [ "AWSLambdaBasicExecutionRole" ], - "PackageType": "Image", - "ImageUri": ".", - "ImageConfig": { - "Command": [ - "TestExecutableServerlessApp" - ] - }, + "PackageType": "Zip", + "Handler": "TestExecutableServerlessApp", "Environment": { "Variables": { - "ANNOTATIONS_HANDLER": "DynamicReturn" + "ANNOTATIONS_HANDLER": "NoParameterWithResponse" } } } }, - "TestServerlessAppDynamicExampleDynamicInputGenerated": { + "TestServerlessAppVoidExampleVoidReturnGenerated": { "Type": "AWS::Serverless::Function", "Metadata": { "Tool": "Amazon.Lambda.Annotations" @@ -621,7 +603,7 @@ }, "Environment": { "Variables": { - "ANNOTATIONS_HANDLER": "DynamicInput" + "ANNOTATIONS_HANDLER": "VoidReturn" } } } @@ -651,10 +633,19 @@ } } }, - "ToUpper": { + "TestExecutableServerlessAppSourceGenerationSerializationExampleGetPersonGenerated": { "Type": "AWS::Serverless::Function", "Metadata": { - "Tool": "Amazon.Lambda.Annotations" + "Tool": "Amazon.Lambda.Annotations", + "SyncedEvents": [ + "RootGet" + ], + "SyncedEventProperties": { + "RootGet": [ + "Path", + "Method" + ] + } }, "Properties": { "MemorySize": 512, @@ -671,7 +662,16 @@ }, "Environment": { "Variables": { - "ANNOTATIONS_HANDLER": "ToUpper" + "ANNOTATIONS_HANDLER": "GetPerson" + } + }, + "Events": { + "RootGet": { + "Type": "Api", + "Properties": { + "Path": "/", + "Method": "GET" + } } } } diff --git a/Libraries/test/TestFunction/TestFunction.csproj b/Libraries/test/TestFunction/TestFunction.csproj index 98b0093d3..d15b5ec17 100644 --- a/Libraries/test/TestFunction/TestFunction.csproj +++ b/Libraries/test/TestFunction/TestFunction.csproj @@ -1,7 +1,7 @@  - netcoreapp3.1 + net10.0 TestFunction Library TestFunction diff --git a/Libraries/test/TestFunctionFSharp/FSharpJsonSerializer/FSharpJsonSerializer.csproj b/Libraries/test/TestFunctionFSharp/FSharpJsonSerializer/FSharpJsonSerializer.csproj index 2f2a4d5c5..3371f242e 100644 --- a/Libraries/test/TestFunctionFSharp/FSharpJsonSerializer/FSharpJsonSerializer.csproj +++ b/Libraries/test/TestFunctionFSharp/FSharpJsonSerializer/FSharpJsonSerializer.csproj @@ -1,7 +1,7 @@  - netstandard2.0 + net8.0;net10.0 FSharpJsonSerializer Library FSharpJsonSerializer diff --git a/Libraries/test/TestFunctionFSharp/TestFunctionFSharp/TestFunctionFSharp.fsproj b/Libraries/test/TestFunctionFSharp/TestFunctionFSharp/TestFunctionFSharp.fsproj index 094d04fd7..693b53bd5 100644 --- a/Libraries/test/TestFunctionFSharp/TestFunctionFSharp/TestFunctionFSharp.fsproj +++ b/Libraries/test/TestFunctionFSharp/TestFunctionFSharp/TestFunctionFSharp.fsproj @@ -18,7 +18,6 @@ - diff --git a/Libraries/test/TestServerlessApp.ALB.IntegrationTests/TestServerlessApp.ALB.IntegrationTests.csproj b/Libraries/test/TestServerlessApp.ALB.IntegrationTests/TestServerlessApp.ALB.IntegrationTests.csproj index ceb0564e5..b9537f341 100644 --- a/Libraries/test/TestServerlessApp.ALB.IntegrationTests/TestServerlessApp.ALB.IntegrationTests.csproj +++ b/Libraries/test/TestServerlessApp.ALB.IntegrationTests/TestServerlessApp.ALB.IntegrationTests.csproj @@ -1,18 +1,19 @@ - net6.0 + net10.0 false - - - + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - - - + + + diff --git a/Libraries/test/TestServerlessApp.ALB/TestServerlessApp.ALB.csproj b/Libraries/test/TestServerlessApp.ALB/TestServerlessApp.ALB.csproj index 9f9e5630c..cbbc51785 100644 --- a/Libraries/test/TestServerlessApp.ALB/TestServerlessApp.ALB.csproj +++ b/Libraries/test/TestServerlessApp.ALB/TestServerlessApp.ALB.csproj @@ -1,6 +1,6 @@ - net6.0 + net10.0 true Lambda true diff --git a/Libraries/test/TestServerlessApp.ALB/serverless.template b/Libraries/test/TestServerlessApp.ALB/serverless.template index 5050f5265..3efd1aa0c 100644 --- a/Libraries/test/TestServerlessApp.ALB/serverless.template +++ b/Libraries/test/TestServerlessApp.ALB/serverless.template @@ -450,7 +450,7 @@ ] }, "Properties": { - "Runtime": "dotnet6", + "Runtime": "dotnet10", "CodeUri": ".", "MemorySize": 256, "Timeout": 15, @@ -472,7 +472,7 @@ ] }, "Properties": { - "Runtime": "dotnet6", + "Runtime": "dotnet10", "CodeUri": ".", "MemorySize": 128, "Timeout": 5, @@ -494,7 +494,7 @@ ] }, "Properties": { - "Runtime": "dotnet6", + "Runtime": "dotnet10", "CodeUri": ".", "MemorySize": 256, "Timeout": 15, @@ -516,7 +516,7 @@ ] }, "Properties": { - "Runtime": "dotnet6", + "Runtime": "dotnet10", "CodeUri": ".", "MemorySize": 256, "Timeout": 15, diff --git a/Libraries/test/TestServerlessApp.IntegrationTests/Greeter.cs b/Libraries/test/TestServerlessApp.IntegrationTests/Greeter.cs index 8f114d80c..395ebfc29 100644 --- a/Libraries/test/TestServerlessApp.IntegrationTests/Greeter.cs +++ b/Libraries/test/TestServerlessApp.IntegrationTests/Greeter.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Net.Http; @@ -38,7 +38,7 @@ public async Task SayHelloAsync_FromHeader_LogsToCloudWatch() RequestUri = new Uri($"{_fixture.HttpApiUrlPrefix}/Greeter/SayHelloAsync"), Headers = {{ "names", new List{"Alice", "Bob"}}} }; - var response = _fixture.HttpClient.SendAsync(httpRequestMessage).Result; + var response = await _fixture.HttpClient.SendAsync(httpRequestMessage); response.EnsureSuccessStatusCode(); var lambdaFunctionName = _fixture.LambdaFunctions.FirstOrDefault(x => string.Equals(x.LogicalId, "GreeterSayHelloAsync"))?.Name; Assert.False(string.IsNullOrEmpty(lambdaFunctionName)); @@ -46,4 +46,4 @@ public async Task SayHelloAsync_FromHeader_LogsToCloudWatch() Assert.True(await _fixture.CloudWatchHelper.MessageExistsInRecentLogEventsAsync("Hello Alice, Bob", logGroupName, logGroupName)); } } -} \ No newline at end of file +} diff --git a/Libraries/test/TestServerlessApp.IntegrationTests/TestServerlessApp.IntegrationTests.csproj b/Libraries/test/TestServerlessApp.IntegrationTests/TestServerlessApp.IntegrationTests.csproj index 0c882dda8..442a91751 100644 --- a/Libraries/test/TestServerlessApp.IntegrationTests/TestServerlessApp.IntegrationTests.csproj +++ b/Libraries/test/TestServerlessApp.IntegrationTests/TestServerlessApp.IntegrationTests.csproj @@ -1,19 +1,19 @@ - net6.0 + net10.0 Library - - - - - - - + + + + + + + all runtime; build; native; contentfiles; analyzers diff --git a/Libraries/test/TestServerlessApp/Dockerfile b/Libraries/test/TestServerlessApp/Dockerfile index 3f1c870e6..775bedb52 100644 --- a/Libraries/test/TestServerlessApp/Dockerfile +++ b/Libraries/test/TestServerlessApp/Dockerfile @@ -1,4 +1,4 @@ -FROM public.ecr.aws/lambda/dotnet:6 +FROM public.ecr.aws/lambda/dotnet:10 WORKDIR /var/task diff --git a/Libraries/test/TestServerlessApp/NullableReferenceTypeExample.cs b/Libraries/test/TestServerlessApp/NullableReferenceTypeExample.cs index 9b930176d..ad165ce86 100644 --- a/Libraries/test/TestServerlessApp/NullableReferenceTypeExample.cs +++ b/Libraries/test/TestServerlessApp/NullableReferenceTypeExample.cs @@ -1,4 +1,4 @@ -using Amazon.Lambda.Annotations; +using Amazon.Lambda.Annotations; using Amazon.Lambda.Annotations.APIGateway; using Amazon.Lambda.Core; diff --git a/Libraries/test/TestServerlessApp/SimpleCalculator.cs b/Libraries/test/TestServerlessApp/SimpleCalculator.cs index 8dabd1f49..0c703ab47 100644 --- a/Libraries/test/TestServerlessApp/SimpleCalculator.cs +++ b/Libraries/test/TestServerlessApp/SimpleCalculator.cs @@ -19,7 +19,7 @@ public class SimpleCalculator /// public SimpleCalculator(ISimpleCalculatorService simpleCalculatorService) { - this._simpleCalculatorService = simpleCalculatorService; + _simpleCalculatorService = simpleCalculatorService; } [LambdaFunction(ResourceName = "SimpleCalculatorAdd", PackageType = LambdaPackageType.Image)] @@ -90,4 +90,4 @@ public class RandomsInput public int MaxValue { get; set; } } } -} \ No newline at end of file +} diff --git a/Libraries/test/TestServerlessApp/TestServerlessApp.csproj b/Libraries/test/TestServerlessApp/TestServerlessApp.csproj index a9ce36791..f5f918665 100644 --- a/Libraries/test/TestServerlessApp/TestServerlessApp.csproj +++ b/Libraries/test/TestServerlessApp/TestServerlessApp.csproj @@ -1,10 +1,11 @@  - net6.0 + net10.0 true Lambda true + CS8632;CS8669 @@ -19,8 +20,7 @@ - - + diff --git a/Libraries/test/TestServerlessApp/aws-lambda-tools-defaults.json b/Libraries/test/TestServerlessApp/aws-lambda-tools-defaults.json index 03fe9926f..eca7ad5fb 100644 --- a/Libraries/test/TestServerlessApp/aws-lambda-tools-defaults.json +++ b/Libraries/test/TestServerlessApp/aws-lambda-tools-defaults.json @@ -8,12 +8,12 @@ "profile": "default", "region": "us-west-2", "configuration": "Release", - "framework": "net6.0", + "framework": "net10.0", "s3-prefix": "TestServerlessApp/", "template": "serverless.template", "template-parameters": "", "docker-host-build-output-dir": "./bin/Release/lambda-publish", -"s3-bucket" : "test-serverless-app-784dfb1d", -"stack-name" : "test-serverless-app-784dfb1d", -"function-architecture" : "x86_64" -} + "s3-bucket": "test-serverless-app", + "stack-name": "test-serverless-app", + "function-architecture": "x86_64" +} \ No newline at end of file diff --git a/Libraries/test/TestServerlessApp/serverless.template b/Libraries/test/TestServerlessApp/serverless.template index fae491e28..094e17ac8 100644 --- a/Libraries/test/TestServerlessApp/serverless.template +++ b/Libraries/test/TestServerlessApp/serverless.template @@ -3,6 +3,36 @@ "Transform": "AWS::Serverless-2016-10-31", "Description": "This template is partially managed by Amazon.Lambda.Annotations (v1.14.0.0).", "Resources": { + "TestQueue": { + "Type": "AWS::SQS::Queue" + }, + "TestS3Bucket": { + "Type": "AWS::S3::Bucket" + }, + "TestTopic": { + "Type": "AWS::SNS::Topic" + }, + "TestTable": { + "Type": "AWS::DynamoDB::Table", + "Properties": { + "AttributeDefinitions": [ + { + "AttributeName": "Id", + "AttributeType": "S" + } + ], + "KeySchema": [ + { + "KeyType": "HASH", + "AttributeName": "Id" + } + ], + "BillingMode": "PAY_PER_REQUEST", + "StreamSpecification": { + "StreamViewType": "NEW_AND_OLD_IMAGES" + } + } + }, "AnnotationsHttpApi": { "Type": "AWS::Serverless::HttpApi", "Metadata": { @@ -58,7 +88,7 @@ "Tool": "Amazon.Lambda.Annotations" } }, - "AuthNameFallbackTest": { + "HttpApiAuthorizerTest": { "Type": "AWS::Serverless::Function", "Metadata": { "Tool": "Amazon.Lambda.Annotations", @@ -83,14 +113,14 @@ "ImageUri": ".", "ImageConfig": { "Command": [ - "TestServerlessApp::TestServerlessApp.AuthNameFallback_GetUserId_Generated::GetUserId" + "TestServerlessApp::TestServerlessApp.CustomAuthorizerHttpApiExample_HttpApiAuthorizer_Generated::HttpApiAuthorizer" ] }, "Events": { "RootGet": { "Type": "HttpApi", "Properties": { - "Path": "/api/authorizer-fallback", + "Path": "/api/authorizer", "Method": "GET", "ApiId": { "Ref": "AnnotationsHttpApi" @@ -100,18 +130,21 @@ } } }, - "TestServerlessAppComplexCalculatorAddGenerated": { + "SQSMessageHandler": { "Type": "AWS::Serverless::Function", "Metadata": { "Tool": "Amazon.Lambda.Annotations", "SyncedEvents": [ - "RootPost" + "TestQueueEvent" ], "SyncedEventProperties": { - "RootPost": [ - "Path", - "Method", - "ApiId.Ref" + "TestQueueEvent": [ + "Queue.Fn::GetAtt", + "BatchSize", + "FilterCriteria.Filters", + "FunctionResponseTypes", + "MaximumBatchingWindowInSeconds", + "ScalingConfig.MaximumConcurrency" ] } }, @@ -119,38 +152,54 @@ "MemorySize": 512, "Timeout": 30, "Policies": [ - "AWSLambdaBasicExecutionRole" + "AWSLambdaSQSQueueExecutionRole" ], "PackageType": "Image", "ImageUri": ".", "ImageConfig": { "Command": [ - "TestServerlessApp::TestServerlessApp.ComplexCalculator_Add_Generated::Add" + "TestServerlessApp::TestServerlessApp.SqsMessageProcessing_HandleMessage_Generated::HandleMessage" ] }, "Events": { - "RootPost": { - "Type": "HttpApi", + "TestQueueEvent": { + "Type": "SQS", "Properties": { - "Path": "/ComplexCalculator/Add", - "Method": "POST", - "ApiId": { - "Ref": "AnnotationsHttpApi" + "BatchSize": 50, + "FilterCriteria": { + "Filters": [ + { + "Pattern": "{ \"body\" : { \"RequestCode\" : [ \"BBBB\" ] } }" + } + ] + }, + "FunctionResponseTypes": [ + "ReportBatchItemFailures" + ], + "MaximumBatchingWindowInSeconds": 5, + "ScalingConfig": { + "MaximumConcurrency": 5 + }, + "Queue": { + "Fn::GetAtt": [ + "TestQueue", + "Arn" + ] } } } } } }, - "TestServerlessAppComplexCalculatorSubtractGenerated": { + "AuthNameFallbackTest": { "Type": "AWS::Serverless::Function", "Metadata": { "Tool": "Amazon.Lambda.Annotations", "SyncedEvents": [ - "RootPost" + "RootGet" ], "SyncedEventProperties": { - "RootPost": [ + "RootGet": [ "Path", "Method", "ApiId.Ref" @@ -167,15 +216,15 @@ "ImageUri": ".", "ImageConfig": { "Command": [ - "TestServerlessApp::TestServerlessApp.ComplexCalculator_Subtract_Generated::Subtract" + "TestServerlessApp::TestServerlessApp.AuthNameFallback_GetUserId_Generated::GetUserId" ] }, "Events": { - "RootPost": { + "RootGet": { "Type": "HttpApi", "Properties": { - "Path": "/ComplexCalculator/Subtract", - "Method": "POST", + "Path": "/api/authorizer-fallback", + "Method": "GET", "ApiId": { "Ref": "AnnotationsHttpApi" } @@ -184,18 +233,17 @@ } } }, - "HttpApiAuthorizerTest": { + "SNSMessageHandler": { "Type": "AWS::Serverless::Function", "Metadata": { "Tool": "Amazon.Lambda.Annotations", "SyncedEvents": [ - "RootGet" + "TestTopicEvent" ], "SyncedEventProperties": { - "RootGet": [ - "Path", - "Method", - "ApiId.Ref" + "TestTopicEvent": [ + "Topic.Ref", + "FilterPolicy" ] } }, @@ -209,24 +257,23 @@ "ImageUri": ".", "ImageConfig": { "Command": [ - "TestServerlessApp::TestServerlessApp.CustomAuthorizerHttpApiExample_HttpApiAuthorizer_Generated::HttpApiAuthorizer" + "TestServerlessApp::TestServerlessApp.SnsMessageProcessing_HandleMessage_Generated::HandleMessage" ] }, "Events": { - "RootGet": { - "Type": "HttpApi", + "TestTopicEvent": { + "Type": "SNS", "Properties": { - "Path": "/api/authorizer", - "Method": "GET", - "ApiId": { - "Ref": "AnnotationsHttpApi" + "FilterPolicy": "{ \"store\": [\"example_corp\"] }", + "Topic": { + "Ref": "TestTopic" } } } } } }, - "HttpApiV1AuthorizerTest": { + "TestServerlessAppNullableReferenceTypeExampleNullableHeaderHttpApiGenerated": { "Type": "AWS::Serverless::Function", "Metadata": { "Tool": "Amazon.Lambda.Annotations", @@ -237,7 +284,6 @@ "RootGet": [ "Path", "Method", - "PayloadFormatVersion", "ApiId.Ref" ] } @@ -252,16 +298,15 @@ "ImageUri": ".", "ImageConfig": { "Command": [ - "TestServerlessApp::TestServerlessApp.CustomAuthorizerHttpApiV1Example_HttpApiV1Authorizer_Generated::HttpApiV1Authorizer" + "TestServerlessApp::TestServerlessApp.NullableReferenceTypeExample_NullableHeaderHttpApi_Generated::NullableHeaderHttpApi" ] }, "Events": { "RootGet": { "Type": "HttpApi", "Properties": { - "Path": "/api/authorizer-v1", + "Path": "/nullableheaderhttpapi", "Method": "GET", - "PayloadFormatVersion": "1.0", "ApiId": { "Ref": "AnnotationsHttpApi" } @@ -270,20 +315,10 @@ } } }, - "HttpApiNonString": { + "ToUpper": { "Type": "AWS::Serverless::Function", "Metadata": { - "Tool": "Amazon.Lambda.Annotations", - "SyncedEvents": [ - "RootGet" - ], - "SyncedEventProperties": { - "RootGet": [ - "Path", - "Method", - "ApiId.Ref" - ] - } + "Tool": "Amazon.Lambda.Annotations" }, "Properties": { "MemorySize": 512, @@ -295,35 +330,23 @@ "ImageUri": ".", "ImageConfig": { "Command": [ - "TestServerlessApp::TestServerlessApp.CustomAuthorizerNonStringExample_HttpApiWithNonString_Generated::HttpApiWithNonString" + "TestServerlessApp::TestServerlessApp.Sub1.Functions_ToUpper_Generated::ToUpper" ] - }, - "Events": { - "RootGet": { - "Type": "HttpApi", - "Properties": { - "Path": "/api/authorizer-non-string", - "Method": "GET", - "ApiId": { - "Ref": "AnnotationsHttpApi" - } - } - } } } }, - "RestAuthorizerTest": { + "TestServerlessAppComplexCalculatorAddGenerated": { "Type": "AWS::Serverless::Function", "Metadata": { "Tool": "Amazon.Lambda.Annotations", "SyncedEvents": [ - "RootGet" + "RootPost" ], "SyncedEventProperties": { - "RootGet": [ + "RootPost": [ "Path", "Method", - "RestApiId.Ref" + "ApiId.Ref" ] } }, @@ -337,32 +360,32 @@ "ImageUri": ".", "ImageConfig": { "Command": [ - "TestServerlessApp::TestServerlessApp.CustomAuthorizerRestExample_RestAuthorizer_Generated::RestAuthorizer" + "TestServerlessApp::TestServerlessApp.ComplexCalculator_Add_Generated::Add" ] }, "Events": { - "RootGet": { - "Type": "Api", + "RootPost": { + "Type": "HttpApi", "Properties": { - "Path": "/rest/authorizer", - "Method": "GET", - "RestApiId": { - "Ref": "AnnotationsRestApi" + "Path": "/ComplexCalculator/Add", + "Method": "POST", + "ApiId": { + "Ref": "AnnotationsHttpApi" } } } } } }, - "TestServerlessAppCustomAuthorizerWithIHttpResultsExampleAuthorizerWithIHttpResultsGenerated": { + "TestServerlessAppComplexCalculatorSubtractGenerated": { "Type": "AWS::Serverless::Function", "Metadata": { "Tool": "Amazon.Lambda.Annotations", "SyncedEvents": [ - "RootGet" + "RootPost" ], "SyncedEventProperties": { - "RootGet": [ + "RootPost": [ "Path", "Method", "ApiId.Ref" @@ -379,15 +402,15 @@ "ImageUri": ".", "ImageConfig": { "Command": [ - "TestServerlessApp::TestServerlessApp.CustomAuthorizerWithIHttpResultsExample_AuthorizerWithIHttpResults_Generated::AuthorizerWithIHttpResults" + "TestServerlessApp::TestServerlessApp.ComplexCalculator_Subtract_Generated::Subtract" ] }, "Events": { - "RootGet": { + "RootPost": { "Type": "HttpApi", "Properties": { - "Path": "/authorizerihttpresults", - "Method": "GET", + "Path": "/ComplexCalculator/Subtract", + "Method": "POST", "ApiId": { "Ref": "AnnotationsHttpApi" } @@ -396,20 +419,11 @@ } } }, - "TestServerlessAppCustomizeResponseExamplesOkResponseWithHeaderGenerated": { + "TestServerlessAppFunctionUrlExampleGetItemsGenerated": { "Type": "AWS::Serverless::Function", "Metadata": { "Tool": "Amazon.Lambda.Annotations", - "SyncedEvents": [ - "RootGet" - ], - "SyncedEventProperties": { - "RootGet": [ - "Path", - "Method", - "RestApiId.Ref" - ] - } + "SyncedFunctionUrlConfig": true }, "Properties": { "MemorySize": 512, @@ -421,37 +435,18 @@ "ImageUri": ".", "ImageConfig": { "Command": [ - "TestServerlessApp::TestServerlessApp.CustomizeResponseExamples_OkResponseWithHeader_Generated::OkResponseWithHeader" + "TestServerlessApp::TestServerlessApp.FunctionUrlExample_GetItems_Generated::GetItems" ] }, - "Events": { - "RootGet": { - "Type": "Api", - "Properties": { - "Path": "/okresponsewithheader/{x}", - "Method": "GET", - "RestApiId": { - "Ref": "AnnotationsRestApi" - } - } - } + "FunctionUrlConfig": { + "AuthType": "NONE" } } }, - "TestServerlessAppCustomizeResponseExamplesOkResponseWithHeaderAsyncGenerated": { + "TestServerlessAppIntrinsicExampleHasIntrinsicGenerated": { "Type": "AWS::Serverless::Function", "Metadata": { - "Tool": "Amazon.Lambda.Annotations", - "SyncedEvents": [ - "RootGet" - ], - "SyncedEventProperties": { - "RootGet": [ - "Path", - "Method", - "RestApiId.Ref" - ] - } + "Tool": "Amazon.Lambda.Annotations" }, "Properties": { "MemorySize": 512, @@ -463,37 +458,35 @@ "ImageUri": ".", "ImageConfig": { "Command": [ - "TestServerlessApp::TestServerlessApp.CustomizeResponseExamples_OkResponseWithHeaderAsync_Generated::OkResponseWithHeaderAsync" + "TestServerlessApp::TestServerlessApp.IntrinsicExample_HasIntrinsic_Generated::HasIntrinsic" ] - }, - "Events": { - "RootGet": { - "Type": "Api", - "Properties": { - "Path": "/okresponsewithheaderasync/{x}", - "Method": "GET", - "RestApiId": { - "Ref": "AnnotationsRestApi" - } - } - } } } }, - "TestServerlessAppCustomizeResponseExamplesNotFoundResponseWithHeaderV2Generated": { + "TestServerlessAppDynamicExampleDynamicReturnGenerated": { "Type": "AWS::Serverless::Function", "Metadata": { - "Tool": "Amazon.Lambda.Annotations", - "SyncedEvents": [ - "RootGet" - ], - "SyncedEventProperties": { - "RootGet": [ - "Path", - "Method", - "ApiId.Ref" + "Tool": "Amazon.Lambda.Annotations" + }, + "Properties": { + "MemorySize": 512, + "Timeout": 30, + "Policies": [ + "AWSLambdaBasicExecutionRole" + ], + "PackageType": "Image", + "ImageUri": ".", + "ImageConfig": { + "Command": [ + "TestServerlessApp::TestServerlessApp.DynamicExample_DynamicReturn_Generated::DynamicReturn" ] } + } + }, + "TestServerlessAppDynamicExampleDynamicInputGenerated": { + "Type": "AWS::Serverless::Function", + "Metadata": { + "Tool": "Amazon.Lambda.Annotations" }, "Properties": { "MemorySize": 512, @@ -505,37 +498,35 @@ "ImageUri": ".", "ImageConfig": { "Command": [ - "TestServerlessApp::TestServerlessApp.CustomizeResponseExamples_NotFoundResponseWithHeaderV2_Generated::NotFoundResponseWithHeaderV2" + "TestServerlessApp::TestServerlessApp.DynamicExample_DynamicInput_Generated::DynamicInput" ] - }, - "Events": { - "RootGet": { - "Type": "HttpApi", - "Properties": { - "Path": "/notfoundwithheaderv2/{x}", - "Method": "GET", - "ApiId": { - "Ref": "AnnotationsHttpApi" - } - } - } } } }, - "TestServerlessAppCustomizeResponseExamplesNotFoundResponseWithHeaderV2AsyncGenerated": { + "TestServerlessAppFromScratchNoSerializerAttributeReferenceToUpperGenerated": { "Type": "AWS::Serverless::Function", "Metadata": { - "Tool": "Amazon.Lambda.Annotations", - "SyncedEvents": [ - "RootGet" + "Tool": "Amazon.Lambda.Annotations" + }, + "Properties": { + "MemorySize": 512, + "Timeout": 30, + "Policies": [ + "AWSLambdaBasicExecutionRole" ], - "SyncedEventProperties": { - "RootGet": [ - "Path", - "Method", - "ApiId.Ref" + "PackageType": "Image", + "ImageUri": ".", + "ImageConfig": { + "Command": [ + "TestServerlessApp::TestServerlessApp.FromScratch.NoSerializerAttributeReference_ToUpper_Generated::ToUpper" ] } + } + }, + "TestServerlessAppTaskExampleTaskReturnGenerated": { + "Type": "AWS::Serverless::Function", + "Metadata": { + "Tool": "Amazon.Lambda.Annotations" }, "Properties": { "MemorySize": 512, @@ -547,38 +538,35 @@ "ImageUri": ".", "ImageConfig": { "Command": [ - "TestServerlessApp::TestServerlessApp.CustomizeResponseExamples_NotFoundResponseWithHeaderV2Async_Generated::NotFoundResponseWithHeaderV2Async" + "TestServerlessApp::TestServerlessApp.TaskExample_TaskReturn_Generated::TaskReturn" ] - }, - "Events": { - "RootGet": { - "Type": "HttpApi", - "Properties": { - "Path": "/notfoundwithheaderv2async/{x}", - "Method": "GET", - "ApiId": { - "Ref": "AnnotationsHttpApi" - } - } - } } } }, - "TestServerlessAppCustomizeResponseExamplesNotFoundResponseWithHeaderV1Generated": { + "SimpleHttpApiAuth": { "Type": "AWS::Serverless::Function", "Metadata": { - "Tool": "Amazon.Lambda.Annotations", - "SyncedEvents": [ - "RootGet" + "Tool": "Amazon.Lambda.Annotations" + }, + "Properties": { + "MemorySize": 512, + "Timeout": 30, + "Policies": [ + "AWSLambdaBasicExecutionRole" ], - "SyncedEventProperties": { - "RootGet": [ - "Path", - "Method", - "PayloadFormatVersion", - "ApiId.Ref" + "PackageType": "Image", + "ImageUri": ".", + "ImageConfig": { + "Command": [ + "TestServerlessApp::TestServerlessApp.IAuthorizerResultExample_SimpleHttpApiAuthorizer_Generated::SimpleHttpApiAuthorizer" ] } + } + }, + "SimpleRestApiAuth": { + "Type": "AWS::Serverless::Function", + "Metadata": { + "Tool": "Amazon.Lambda.Annotations" }, "Properties": { "MemorySize": 512, @@ -590,25 +578,12 @@ "ImageUri": ".", "ImageConfig": { "Command": [ - "TestServerlessApp::TestServerlessApp.CustomizeResponseExamples_NotFoundResponseWithHeaderV1_Generated::NotFoundResponseWithHeaderV1" + "TestServerlessApp::TestServerlessApp.IAuthorizerResultExample_SimpleRestApiAuthorizer_Generated::SimpleRestApiAuthorizer" ] - }, - "Events": { - "RootGet": { - "Type": "HttpApi", - "Properties": { - "Path": "/notfoundwithheaderv1/{x}", - "Method": "GET", - "PayloadFormatVersion": "1.0", - "ApiId": { - "Ref": "AnnotationsHttpApi" - } - } - } } } }, - "TestServerlessAppCustomizeResponseExamplesNotFoundResponseWithHeaderV1AsyncGenerated": { + "HttpApiV1AuthorizerTest": { "Type": "AWS::Serverless::Function", "Metadata": { "Tool": "Amazon.Lambda.Annotations", @@ -634,14 +609,14 @@ "ImageUri": ".", "ImageConfig": { "Command": [ - "TestServerlessApp::TestServerlessApp.CustomizeResponseExamples_NotFoundResponseWithHeaderV1Async_Generated::NotFoundResponseWithHeaderV1Async" + "TestServerlessApp::TestServerlessApp.CustomAuthorizerHttpApiV1Example_HttpApiV1Authorizer_Generated::HttpApiV1Authorizer" ] }, "Events": { "RootGet": { "Type": "HttpApi", "Properties": { - "Path": "/notfoundwithheaderv1async/{x}", + "Path": "/api/authorizer-v1", "Method": "GET", "PayloadFormatVersion": "1.0", "ApiId": { @@ -652,7 +627,7 @@ } } }, - "TestServerlessAppCustomizeResponseExamplesOkResponseWithCustomSerializerGenerated": { + "TestServerlessAppCustomizeResponseExamplesOkResponseWithHeaderGenerated": { "Type": "AWS::Serverless::Function", "Metadata": { "Tool": "Amazon.Lambda.Annotations", @@ -663,8 +638,7 @@ "RootGet": [ "Path", "Method", - "PayloadFormatVersion", - "ApiId.Ref" + "RestApiId.Ref" ] } }, @@ -678,48 +652,37 @@ "ImageUri": ".", "ImageConfig": { "Command": [ - "TestServerlessApp::TestServerlessApp.CustomizeResponseExamples_OkResponseWithCustomSerializer_Generated::OkResponseWithCustomSerializer" + "TestServerlessApp::TestServerlessApp.CustomizeResponseExamples_OkResponseWithHeader_Generated::OkResponseWithHeader" ] }, "Events": { "RootGet": { - "Type": "HttpApi", + "Type": "Api", "Properties": { - "Path": "/okresponsewithcustomserializerasync/{firstName}/{lastName}", + "Path": "/okresponsewithheader/{x}", "Method": "GET", - "PayloadFormatVersion": "1.0", - "ApiId": { - "Ref": "AnnotationsHttpApi" + "RestApiId": { + "Ref": "AnnotationsRestApi" } } } } } }, - "TestServerlessAppDynamicExampleDynamicReturnGenerated": { + "TestServerlessAppCustomizeResponseExamplesOkResponseWithHeaderAsyncGenerated": { "Type": "AWS::Serverless::Function", "Metadata": { - "Tool": "Amazon.Lambda.Annotations" - }, - "Properties": { - "MemorySize": 512, - "Timeout": 30, - "Policies": [ - "AWSLambdaBasicExecutionRole" + "Tool": "Amazon.Lambda.Annotations", + "SyncedEvents": [ + "RootGet" ], - "PackageType": "Image", - "ImageUri": ".", - "ImageConfig": { - "Command": [ - "TestServerlessApp::TestServerlessApp.DynamicExample_DynamicReturn_Generated::DynamicReturn" + "SyncedEventProperties": { + "RootGet": [ + "Path", + "Method", + "RestApiId.Ref" ] } - } - }, - "TestServerlessAppDynamicExampleDynamicInputGenerated": { - "Type": "AWS::Serverless::Function", - "Metadata": { - "Tool": "Amazon.Lambda.Annotations" }, "Properties": { "MemorySize": 512, @@ -731,12 +694,24 @@ "ImageUri": ".", "ImageConfig": { "Command": [ - "TestServerlessApp::TestServerlessApp.DynamicExample_DynamicInput_Generated::DynamicInput" + "TestServerlessApp::TestServerlessApp.CustomizeResponseExamples_OkResponseWithHeaderAsync_Generated::OkResponseWithHeaderAsync" ] + }, + "Events": { + "RootGet": { + "Type": "Api", + "Properties": { + "Path": "/okresponsewithheaderasync/{x}", + "Method": "GET", + "RestApiId": { + "Ref": "AnnotationsRestApi" + } + } + } } } }, - "TestServerlessAppFromScratchNoApiGatewayEventsReferenceToUpperGenerated": { + "TestServerlessAppCustomizeResponseExamplesNotFoundResponseWithHeaderV2Generated": { "Type": "AWS::Serverless::Function", "Metadata": { "Tool": "Amazon.Lambda.Annotations", @@ -761,14 +736,14 @@ "ImageUri": ".", "ImageConfig": { "Command": [ - "TestServerlessApp::TestServerlessApp.FromScratch.NoApiGatewayEventsReference_ToUpper_Generated::ToUpper" + "TestServerlessApp::TestServerlessApp.CustomizeResponseExamples_NotFoundResponseWithHeaderV2_Generated::NotFoundResponseWithHeaderV2" ] }, "Events": { "RootGet": { "Type": "HttpApi", "Properties": { - "Path": "/{text}", + "Path": "/notfoundwithheaderv2/{x}", "Method": "GET", "ApiId": { "Ref": "AnnotationsHttpApi" @@ -778,31 +753,20 @@ } } }, - "TestServerlessAppFromScratchNoSerializerAttributeReferenceToUpperGenerated": { + "TestServerlessAppCustomizeResponseExamplesNotFoundResponseWithHeaderV2AsyncGenerated": { "Type": "AWS::Serverless::Function", "Metadata": { - "Tool": "Amazon.Lambda.Annotations" - }, - "Properties": { - "MemorySize": 512, - "Timeout": 30, - "Policies": [ - "AWSLambdaBasicExecutionRole" + "Tool": "Amazon.Lambda.Annotations", + "SyncedEvents": [ + "RootGet" ], - "PackageType": "Image", - "ImageUri": ".", - "ImageConfig": { - "Command": [ - "TestServerlessApp::TestServerlessApp.FromScratch.NoSerializerAttributeReference_ToUpper_Generated::ToUpper" + "SyncedEventProperties": { + "RootGet": [ + "Path", + "Method", + "ApiId.Ref" ] } - } - }, - "TestServerlessAppFunctionUrlExampleGetItemsGenerated": { - "Type": "AWS::Serverless::Function", - "Metadata": { - "Tool": "Amazon.Lambda.Annotations", - "SyncedFunctionUrlConfig": true }, "Properties": { "MemorySize": 512, @@ -814,15 +778,24 @@ "ImageUri": ".", "ImageConfig": { "Command": [ - "TestServerlessApp::TestServerlessApp.FunctionUrlExample_GetItems_Generated::GetItems" + "TestServerlessApp::TestServerlessApp.CustomizeResponseExamples_NotFoundResponseWithHeaderV2Async_Generated::NotFoundResponseWithHeaderV2Async" ] }, - "FunctionUrlConfig": { - "AuthType": "NONE" + "Events": { + "RootGet": { + "Type": "HttpApi", + "Properties": { + "Path": "/notfoundwithheaderv2async/{x}", + "Method": "GET", + "ApiId": { + "Ref": "AnnotationsHttpApi" + } + } + } } } }, - "GreeterSayHello": { + "TestServerlessAppCustomizeResponseExamplesNotFoundResponseWithHeaderV1Generated": { "Type": "AWS::Serverless::Function", "Metadata": { "Tool": "Amazon.Lambda.Annotations", @@ -839,7 +812,7 @@ } }, "Properties": { - "MemorySize": 1024, + "MemorySize": 512, "Timeout": 30, "Policies": [ "AWSLambdaBasicExecutionRole" @@ -848,14 +821,14 @@ "ImageUri": ".", "ImageConfig": { "Command": [ - "TestServerlessApp::TestServerlessApp.Greeter_SayHello_Generated::SayHello" + "TestServerlessApp::TestServerlessApp.CustomizeResponseExamples_NotFoundResponseWithHeaderV1_Generated::NotFoundResponseWithHeaderV1" ] }, "Events": { "RootGet": { "Type": "HttpApi", "Properties": { - "Path": "/Greeter/SayHello", + "Path": "/notfoundwithheaderv1/{x}", "Method": "GET", "PayloadFormatVersion": "1.0", "ApiId": { @@ -866,7 +839,7 @@ } } }, - "GreeterSayHelloAsync": { + "TestServerlessAppCustomizeResponseExamplesNotFoundResponseWithHeaderV1AsyncGenerated": { "Type": "AWS::Serverless::Function", "Metadata": { "Tool": "Amazon.Lambda.Annotations", @@ -884,7 +857,7 @@ }, "Properties": { "MemorySize": 512, - "Timeout": 50, + "Timeout": 30, "Policies": [ "AWSLambdaBasicExecutionRole" ], @@ -892,14 +865,14 @@ "ImageUri": ".", "ImageConfig": { "Command": [ - "TestServerlessApp::TestServerlessApp.Greeter_SayHelloAsync_Generated::SayHelloAsync" + "TestServerlessApp::TestServerlessApp.CustomizeResponseExamples_NotFoundResponseWithHeaderV1Async_Generated::NotFoundResponseWithHeaderV1Async" ] }, "Events": { "RootGet": { "Type": "HttpApi", "Properties": { - "Path": "/Greeter/SayHelloAsync", + "Path": "/notfoundwithheaderv1async/{x}", "Method": "GET", "PayloadFormatVersion": "1.0", "ApiId": { @@ -910,67 +883,7 @@ } } }, - "SimpleHttpApiAuth": { - "Type": "AWS::Serverless::Function", - "Metadata": { - "Tool": "Amazon.Lambda.Annotations" - }, - "Properties": { - "MemorySize": 512, - "Timeout": 30, - "Policies": [ - "AWSLambdaBasicExecutionRole" - ], - "PackageType": "Image", - "ImageUri": ".", - "ImageConfig": { - "Command": [ - "TestServerlessApp::TestServerlessApp.IAuthorizerResultExample_SimpleHttpApiAuthorizer_Generated::SimpleHttpApiAuthorizer" - ] - } - } - }, - "SimpleRestApiAuth": { - "Type": "AWS::Serverless::Function", - "Metadata": { - "Tool": "Amazon.Lambda.Annotations" - }, - "Properties": { - "MemorySize": 512, - "Timeout": 30, - "Policies": [ - "AWSLambdaBasicExecutionRole" - ], - "PackageType": "Image", - "ImageUri": ".", - "ImageConfig": { - "Command": [ - "TestServerlessApp::TestServerlessApp.IAuthorizerResultExample_SimpleRestApiAuthorizer_Generated::SimpleRestApiAuthorizer" - ] - } - } - }, - "TestServerlessAppIntrinsicExampleHasIntrinsicGenerated": { - "Type": "AWS::Serverless::Function", - "Metadata": { - "Tool": "Amazon.Lambda.Annotations" - }, - "Properties": { - "MemorySize": 512, - "Timeout": 30, - "Policies": [ - "AWSLambdaBasicExecutionRole" - ], - "PackageType": "Image", - "ImageUri": ".", - "ImageConfig": { - "Command": [ - "TestServerlessApp::TestServerlessApp.IntrinsicExample_HasIntrinsic_Generated::HasIntrinsic" - ] - } - } - }, - "TestServerlessAppNullableReferenceTypeExampleNullableHeaderHttpApiGenerated": { + "TestServerlessAppCustomizeResponseExamplesOkResponseWithCustomSerializerGenerated": { "Type": "AWS::Serverless::Function", "Metadata": { "Tool": "Amazon.Lambda.Annotations", @@ -981,6 +894,7 @@ "RootGet": [ "Path", "Method", + "PayloadFormatVersion", "ApiId.Ref" ] } @@ -995,15 +909,16 @@ "ImageUri": ".", "ImageConfig": { "Command": [ - "TestServerlessApp::TestServerlessApp.NullableReferenceTypeExample_NullableHeaderHttpApi_Generated::NullableHeaderHttpApi" + "TestServerlessApp::TestServerlessApp.CustomizeResponseExamples_OkResponseWithCustomSerializer_Generated::OkResponseWithCustomSerializer" ] }, "Events": { "RootGet": { "Type": "HttpApi", "Properties": { - "Path": "/nullableheaderhttpapi", + "Path": "/okresponsewithcustomserializerasync/{firstName}/{lastName}", "Method": "GET", + "PayloadFormatVersion": "1.0", "ApiId": { "Ref": "AnnotationsHttpApi" } @@ -1012,57 +927,23 @@ } } }, - "S3EventHandler": { + "TestServerlessAppVoidExampleVoidReturnGenerated": { "Type": "AWS::Serverless::Function", "Metadata": { - "Tool": "Amazon.Lambda.Annotations", - "SyncedEvents": [ - "TestS3Bucket" - ], - "SyncedEventProperties": { - "TestS3Bucket": [ - "Bucket.Ref", - "Events", - "Filter.S3Key.Rules" - ] - } + "Tool": "Amazon.Lambda.Annotations" }, "Properties": { "MemorySize": 512, "Timeout": 30, "Policies": [ - "AWSLambdaBasicExecutionRole", - "AmazonS3ReadOnlyAccess" + "AWSLambdaBasicExecutionRole" ], "PackageType": "Image", "ImageUri": ".", "ImageConfig": { "Command": [ - "TestServerlessApp::TestServerlessApp.S3EventExamples.S3EventProcessing_ProcessS3Event_Generated::ProcessS3Event" + "TestServerlessApp::TestServerlessApp.VoidExample_VoidReturn_Generated::VoidReturn" ] - }, - "Events": { - "TestS3Bucket": { - "Type": "S3", - "Properties": { - "Events": [ - "s3:ObjectCreated:*" - ], - "Filter": { - "S3Key": { - "Rules": [ - { - "Name": "suffix", - "Value": ".json" - } - ] - } - }, - "Bucket": { - "Ref": "TestS3Bucket" - } - } - } } } }, @@ -1294,21 +1175,18 @@ } } }, - "SQSMessageHandler": { + "HttpApiNonString": { "Type": "AWS::Serverless::Function", "Metadata": { "Tool": "Amazon.Lambda.Annotations", "SyncedEvents": [ - "TestQueueEvent" + "RootGet" ], "SyncedEventProperties": { - "TestQueueEvent": [ - "Queue.Fn::GetAtt", - "BatchSize", - "FilterCriteria.Filters", - "FunctionResponseTypes", - "MaximumBatchingWindowInSeconds", - "ScalingConfig.MaximumConcurrency" + "RootGet": [ + "Path", + "Method", + "ApiId.Ref" ] } }, @@ -1316,69 +1194,43 @@ "MemorySize": 512, "Timeout": 30, "Policies": [ - "AWSLambdaSQSQueueExecutionRole" + "AWSLambdaBasicExecutionRole" ], "PackageType": "Image", "ImageUri": ".", "ImageConfig": { "Command": [ - "TestServerlessApp::TestServerlessApp.SqsMessageProcessing_HandleMessage_Generated::HandleMessage" + "TestServerlessApp::TestServerlessApp.CustomAuthorizerNonStringExample_HttpApiWithNonString_Generated::HttpApiWithNonString" ] }, "Events": { - "TestQueueEvent": { - "Type": "SQS", + "RootGet": { + "Type": "HttpApi", "Properties": { - "BatchSize": 50, - "FilterCriteria": { - "Filters": [ - { - "Pattern": "{ \"body\" : { \"RequestCode\" : [ \"BBBB\" ] } }" - } - ] - }, - "FunctionResponseTypes": [ - "ReportBatchItemFailures" - ], - "MaximumBatchingWindowInSeconds": 5, - "ScalingConfig": { - "MaximumConcurrency": 5 - }, - "Queue": { - "Fn::GetAtt": [ - "TestQueue", - "Arn" - ] + "Path": "/api/authorizer-non-string", + "Method": "GET", + "ApiId": { + "Ref": "AnnotationsHttpApi" } } } } } }, - "ToUpper": { + "TestServerlessAppFromScratchNoApiGatewayEventsReferenceToUpperGenerated": { "Type": "AWS::Serverless::Function", "Metadata": { - "Tool": "Amazon.Lambda.Annotations" - }, - "Properties": { - "MemorySize": 512, - "Timeout": 30, - "Policies": [ - "AWSLambdaBasicExecutionRole" + "Tool": "Amazon.Lambda.Annotations", + "SyncedEvents": [ + "RootGet" ], - "PackageType": "Image", - "ImageUri": ".", - "ImageConfig": { - "Command": [ - "TestServerlessApp::TestServerlessApp.Sub1.Functions_ToUpper_Generated::ToUpper" + "SyncedEventProperties": { + "RootGet": [ + "Path", + "Method", + "ApiId.Ref" ] } - } - }, - "TestServerlessAppTaskExampleTaskReturnGenerated": { - "Type": "AWS::Serverless::Function", - "Metadata": { - "Tool": "Amazon.Lambda.Annotations" }, "Properties": { "MemorySize": 512, @@ -1390,18 +1242,41 @@ "ImageUri": ".", "ImageConfig": { "Command": [ - "TestServerlessApp::TestServerlessApp.TaskExample_TaskReturn_Generated::TaskReturn" + "TestServerlessApp::TestServerlessApp.FromScratch.NoApiGatewayEventsReference_ToUpper_Generated::ToUpper" ] + }, + "Events": { + "RootGet": { + "Type": "HttpApi", + "Properties": { + "Path": "/{text}", + "Method": "GET", + "ApiId": { + "Ref": "AnnotationsHttpApi" + } + } + } } } }, - "TestServerlessAppVoidExampleVoidReturnGenerated": { + "GreeterSayHello": { "Type": "AWS::Serverless::Function", "Metadata": { - "Tool": "Amazon.Lambda.Annotations" + "Tool": "Amazon.Lambda.Annotations", + "SyncedEvents": [ + "RootGet" + ], + "SyncedEventProperties": { + "RootGet": [ + "Path", + "Method", + "PayloadFormatVersion", + "ApiId.Ref" + ] + } }, "Properties": { - "MemorySize": 512, + "MemorySize": 1024, "Timeout": 30, "Policies": [ "AWSLambdaBasicExecutionRole" @@ -1410,60 +1285,68 @@ "ImageUri": ".", "ImageConfig": { "Command": [ - "TestServerlessApp::TestServerlessApp.VoidExample_VoidReturn_Generated::VoidReturn" + "TestServerlessApp::TestServerlessApp.Greeter_SayHello_Generated::SayHello" ] + }, + "Events": { + "RootGet": { + "Type": "HttpApi", + "Properties": { + "Path": "/Greeter/SayHello", + "Method": "GET", + "PayloadFormatVersion": "1.0", + "ApiId": { + "Ref": "AnnotationsHttpApi" + } + } + } } } }, - "SNSMessageHandler": { + "GreeterSayHelloAsync": { "Type": "AWS::Serverless::Function", "Metadata": { "Tool": "Amazon.Lambda.Annotations", "SyncedEvents": [ - "TestTopicEvent" + "RootGet" ], "SyncedEventProperties": { - "TestTopicEvent": [ - "Topic.Ref", - "FilterPolicy" + "RootGet": [ + "Path", + "Method", + "PayloadFormatVersion", + "ApiId.Ref" ] } }, "Properties": { "MemorySize": 512, - "Timeout": 30, + "Timeout": 50, "Policies": [ "AWSLambdaBasicExecutionRole" ], "PackageType": "Image", + "ImageUri": ".", + "ImageConfig": { + "Command": [ + "TestServerlessApp::TestServerlessApp.Greeter_SayHelloAsync_Generated::SayHelloAsync" + ] + }, "Events": { - "TestTopicEvent": { - "Type": "SNS", + "RootGet": { + "Type": "HttpApi", "Properties": { - "FilterPolicy": "{ \"store\": [\"example_corp\"] }", - "Topic": { - "Ref": "TestTopic" + "Path": "/Greeter/SayHelloAsync", + "Method": "GET", + "PayloadFormatVersion": "1.0", + "ApiId": { + "Ref": "AnnotationsHttpApi" } } } - }, - "ImageUri": ".", - "ImageConfig": { - "Command": [ - "TestServerlessApp::TestServerlessApp.SnsMessageProcessing_HandleMessage_Generated::HandleMessage" - ] } } }, - "TestQueue": { - "Type": "AWS::SQS::Queue" - }, - "TestS3Bucket": { - "Type": "AWS::S3::Bucket" - }, - "TestTopic": { - "Type": "AWS::SNS::Topic" - }, "DynamoDBStreamHandler": { "Type": "AWS::Serverless::Function", "Metadata": { @@ -1509,24 +1392,141 @@ } } }, - "TestTable": { - "Type": "AWS::DynamoDB::Table", + "TestServerlessAppCustomAuthorizerWithIHttpResultsExampleAuthorizerWithIHttpResultsGenerated": { + "Type": "AWS::Serverless::Function", + "Metadata": { + "Tool": "Amazon.Lambda.Annotations", + "SyncedEvents": [ + "RootGet" + ], + "SyncedEventProperties": { + "RootGet": [ + "Path", + "Method", + "ApiId.Ref" + ] + } + }, "Properties": { - "AttributeDefinitions": [ - { - "AttributeName": "Id", - "AttributeType": "S" + "MemorySize": 512, + "Timeout": 30, + "Policies": [ + "AWSLambdaBasicExecutionRole" + ], + "PackageType": "Image", + "ImageUri": ".", + "ImageConfig": { + "Command": [ + "TestServerlessApp::TestServerlessApp.CustomAuthorizerWithIHttpResultsExample_AuthorizerWithIHttpResults_Generated::AuthorizerWithIHttpResults" + ] + }, + "Events": { + "RootGet": { + "Type": "HttpApi", + "Properties": { + "Path": "/authorizerihttpresults", + "Method": "GET", + "ApiId": { + "Ref": "AnnotationsHttpApi" + } + } } + } + } + }, + "S3EventHandler": { + "Type": "AWS::Serverless::Function", + "Metadata": { + "Tool": "Amazon.Lambda.Annotations", + "SyncedEvents": [ + "TestS3Bucket" ], - "KeySchema": [ - { - "KeyType": "HASH", - "AttributeName": "Id" + "SyncedEventProperties": { + "TestS3Bucket": [ + "Bucket.Ref", + "Events", + "Filter.S3Key.Rules" + ] + } + }, + "Properties": { + "MemorySize": 512, + "Timeout": 30, + "Policies": [ + "AWSLambdaBasicExecutionRole", + "AmazonS3ReadOnlyAccess" + ], + "PackageType": "Image", + "ImageUri": ".", + "ImageConfig": { + "Command": [ + "TestServerlessApp::TestServerlessApp.S3EventExamples.S3EventProcessing_ProcessS3Event_Generated::ProcessS3Event" + ] + }, + "Events": { + "TestS3Bucket": { + "Type": "S3", + "Properties": { + "Events": [ + "s3:ObjectCreated:*" + ], + "Filter": { + "S3Key": { + "Rules": [ + { + "Name": "suffix", + "Value": ".json" + } + ] + } + }, + "Bucket": { + "Ref": "TestS3Bucket" + } + } } + } + } + }, + "RestAuthorizerTest": { + "Type": "AWS::Serverless::Function", + "Metadata": { + "Tool": "Amazon.Lambda.Annotations", + "SyncedEvents": [ + "RootGet" ], - "BillingMode": "PAY_PER_REQUEST", - "StreamSpecification": { - "StreamViewType": "NEW_AND_OLD_IMAGES" + "SyncedEventProperties": { + "RootGet": [ + "Path", + "Method", + "RestApiId.Ref" + ] + } + }, + "Properties": { + "MemorySize": 512, + "Timeout": 30, + "Policies": [ + "AWSLambdaBasicExecutionRole" + ], + "PackageType": "Image", + "ImageUri": ".", + "ImageConfig": { + "Command": [ + "TestServerlessApp::TestServerlessApp.CustomAuthorizerRestExample_RestAuthorizer_Generated::RestAuthorizer" + ] + }, + "Events": { + "RootGet": { + "Type": "Api", + "Properties": { + "Path": "/rest/authorizer", + "Method": "GET", + "RestApiId": { + "Ref": "AnnotationsRestApi" + } + } + } } } } diff --git a/Libraries/test/TestWebApp/Controllers/BinaryContentController.cs b/Libraries/test/TestWebApp/Controllers/BinaryContentController.cs index 6b3c3a195..5aab5d9e2 100644 --- a/Libraries/test/TestWebApp/Controllers/BinaryContentController.cs +++ b/Libraries/test/TestWebApp/Controllers/BinaryContentController.cs @@ -21,7 +21,7 @@ public IActionResult Get([FromQuery] string firstName, [FromQuery] string lastNa [HttpPut] public string Put() { - using (var reader = new StreamReader(this.HttpContext.Request.Body)) + using (var reader = new StreamReader(HttpContext.Request.Body)) { return reader.ReadToEnd(); } diff --git a/Libraries/test/TestWebApp/Controllers/RawQueryStringController.cs b/Libraries/test/TestWebApp/Controllers/RawQueryStringController.cs index 52e083bf9..456006851 100644 --- a/Libraries/test/TestWebApp/Controllers/RawQueryStringController.cs +++ b/Libraries/test/TestWebApp/Controllers/RawQueryStringController.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; @@ -12,7 +12,7 @@ public class RawQueryStringController : Controller [HttpGet] public string Get() { - return this.Request.QueryString.ToString(); + return Request.QueryString.ToString(); } [HttpGet] diff --git a/Libraries/test/TestWebApp/Controllers/RedirectTestController.cs b/Libraries/test/TestWebApp/Controllers/RedirectTestController.cs index 3a5f4b0ae..ca5239eb7 100644 --- a/Libraries/test/TestWebApp/Controllers/RedirectTestController.cs +++ b/Libraries/test/TestWebApp/Controllers/RedirectTestController.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; @@ -14,7 +14,7 @@ public class RedirectTestController : Controller [HttpGet] public ActionResult Get() { - return this.Redirect("redirecttarget"); + return Redirect("redirecttarget"); } } diff --git a/Libraries/test/TestWebApp/Controllers/RequestServicesExampleController.cs b/Libraries/test/TestWebApp/Controllers/RequestServicesExampleController.cs index e193aec5d..7412cf97a 100644 --- a/Libraries/test/TestWebApp/Controllers/RequestServicesExampleController.cs +++ b/Libraries/test/TestWebApp/Controllers/RequestServicesExampleController.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; @@ -12,7 +12,7 @@ public class RequestServicesExampleController : ControllerBase [HttpGet] public string Get() { - return this.HttpContext.RequestServices?.GetType().FullName; + return HttpContext.RequestServices?.GetType().FullName; } } } diff --git a/Libraries/test/TestWebApp/Controllers/ResourcePathController.cs b/Libraries/test/TestWebApp/Controllers/ResourcePathController.cs index 082f14863..0fc682a15 100644 --- a/Libraries/test/TestWebApp/Controllers/ResourcePathController.cs +++ b/Libraries/test/TestWebApp/Controllers/ResourcePathController.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; @@ -27,7 +27,7 @@ public string Get(string id) [HttpGet] public string GetString(string value) { - var path = this.HttpContext.Request.Path; + var path = HttpContext.Request.Path; return "value=" + value; } diff --git a/Libraries/test/TestWebApp/Controllers/TraceTestsController.cs b/Libraries/test/TestWebApp/Controllers/TraceTestsController.cs index 970d484a9..268d57ddd 100644 --- a/Libraries/test/TestWebApp/Controllers/TraceTestsController.cs +++ b/Libraries/test/TestWebApp/Controllers/TraceTestsController.cs @@ -1,4 +1,4 @@ -using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc; namespace TestWebApp.Controllers { @@ -8,7 +8,7 @@ public class TraceTestsController : Controller [HttpGet] public string GetTraceId() { - return this.HttpContext.TraceIdentifier; + return HttpContext.TraceIdentifier; } } } diff --git a/Libraries/test/TestWebApp/HttpApiV2LambdaFunction.cs b/Libraries/test/TestWebApp/HttpApiV2LambdaFunction.cs index fa525b113..b8d88ccb1 100644 --- a/Libraries/test/TestWebApp/HttpApiV2LambdaFunction.cs +++ b/Libraries/test/TestWebApp/HttpApiV2LambdaFunction.cs @@ -6,11 +6,9 @@ namespace TestWebApp { public class HttpV2LambdaFunction : APIGatewayHttpApiV2ProxyFunction { -#if NET8_0_OR_GREATER protected override IEnumerable GetBeforeSnapshotRequests() => [ new HttpRequestMessage(HttpMethod.Get, "api/SnapStart") ]; -#endif } } diff --git a/Libraries/test/TestWebApp/Program.cs b/Libraries/test/TestWebApp/Program.cs index b728f4386..9c6421e35 100644 --- a/Libraries/test/TestWebApp/Program.cs +++ b/Libraries/test/TestWebApp/Program.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.IO; using System.Linq; @@ -12,13 +12,14 @@ public class Program { public static void Main(string[] args) { +#pragma warning disable ASPDEPR008,ASPDEPR004 var host = new WebHostBuilder() .UseKestrel() .UseContentRoot(Directory.GetCurrentDirectory()) .UseIISIntegration() .UseStartup() .Build(); - +#pragma warning restore ASPDEPR008,ASPDEPR004 host.Run(); } } diff --git a/Libraries/test/TestWebApp/Startup.cs b/Libraries/test/TestWebApp/Startup.cs index 055fbb7a9..1d0422c30 100644 --- a/Libraries/test/TestWebApp/Startup.cs +++ b/Libraries/test/TestWebApp/Startup.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; @@ -8,22 +8,15 @@ using Microsoft.AspNetCore.ResponseCompression; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using System.Runtime.Serialization.Json; using System.IO; using Microsoft.AspNetCore.Http.Features; - -#if NETCOREAPP_2_1 -using Newtonsoft.Json.Linq; -using Swashbuckle.AspNetCore.Swagger; -#else using System.Text.Json; -#endif namespace TestWebApp { public class Startup { +#pragma warning disable CS0618 public Startup(IHostingEnvironment env) { var builder = new ConfigurationBuilder() @@ -34,6 +27,7 @@ public Startup(IHostingEnvironment env) builder.AddEnvironmentVariables(); Configuration = builder.Build(); } +#pragma warning restore CS0618 public IConfigurationRoot Configuration { get; } @@ -52,28 +46,16 @@ public void ConfigureServices(IServiceCollection services) options.MimeTypes = new string[] { "application/json-compress" }; }); -#if NETCOREAPP_2_1 - services.AddSwaggerGen(c => - { - c.SwaggerDoc("v1", new Info { Title = "My API", Version = "v1" }); - }); - - // Add framework services. - services.AddApplicationInsightsTelemetry(Configuration); - - services.AddMvc(); -#elif NETCOREAPP3_1_OR_GREATER services.AddControllers(); -#endif } +#pragma warning disable CS0618 // This method gets called by the runtime. Use this method to configure the HTTP request pipeline public void Configure(IApplicationBuilder app, IHostingEnvironment env) { app.UseMiddleware(); app.UseResponseCompression(); -#if NETCOREAPP3_1_OR_GREATER app.UseRouting(); app.UseAuthorization(); @@ -82,35 +64,10 @@ public void Configure(IApplicationBuilder app, IHostingEnvironment env) { endpoints.MapControllers(); }); -#else - app.UseSwagger(); - - app.UseMvc(); -#endif app.Run(async (context) => { var rawTarget = context.Features.Get()?.RawTarget; -#if NETCOREAPP_2_1 - var root = new JObject(); - root["Path"] = new JValue(context.Request.Path); - root["PathBase"] = new JValue(context.Request.PathBase); - root["RawTarget"] = new JValue(rawTarget); - - var query = new JObject(); - foreach(var queryKey in context.Request.Query.Keys) - { - var variables = new JArray(); - foreach(var v in context.Request.Query[queryKey]) - { - variables.Add(new JValue(v)); - } - query[queryKey] = variables; - } - root["QueryVariables"] = query; - - var body = root.ToString(); -#else var stream = new MemoryStream(); var writer = new Utf8JsonWriter(stream); writer.WriteStartObject(); @@ -132,10 +89,11 @@ public void Configure(IApplicationBuilder app, IHostingEnvironment env) writer.Dispose(); stream.Position = 0; var body = new StreamReader(stream).ReadToEnd(); -#endif + context.Response.Headers["Content-Type"] = "application/json"; await context.Response.WriteAsync(body); }); } +#pragma warning restore CS0618 } } diff --git a/Libraries/test/TestWebApp/TestWebApp.csproj b/Libraries/test/TestWebApp/TestWebApp.csproj index 37a8b67fa..ebc1b93db 100644 --- a/Libraries/test/TestWebApp/TestWebApp.csproj +++ b/Libraries/test/TestWebApp/TestWebApp.csproj @@ -11,9 +11,5 @@ - - - - diff --git a/Libraries/test/TestWebApp/serverless.template b/Libraries/test/TestWebApp/serverless.template index 1b9693422..b753ebf38 100644 --- a/Libraries/test/TestWebApp/serverless.template +++ b/Libraries/test/TestWebApp/serverless.template @@ -9,7 +9,7 @@ "Type" : "AWS::Serverless::Function", "Properties": { "Handler": "TestWebApp::TestWebApp.LambdaFunction::FunctionHandlerAsync", - "Runtime": "dotnetcore2.0", + "Runtime": "dotnet10", "CodeUri": "", "Description": "Default function", "MemorySize": 256, @@ -30,4 +30,4 @@ }, "Outputs" : { } -} \ No newline at end of file +} diff --git a/Tools/LambdaTestTool-v2/src/Amazon.Lambda.TestTool/Amazon.Lambda.TestTool.csproj b/Tools/LambdaTestTool-v2/src/Amazon.Lambda.TestTool/Amazon.Lambda.TestTool.csproj index 9711bce5d..eba399cbd 100644 --- a/Tools/LambdaTestTool-v2/src/Amazon.Lambda.TestTool/Amazon.Lambda.TestTool.csproj +++ b/Tools/LambdaTestTool-v2/src/Amazon.Lambda.TestTool/Amazon.Lambda.TestTool.csproj @@ -16,7 +16,7 @@ Amazon.Lambda.TestTool dotnet-lambda-test-tool 0.13.0 - NU5100 + NU5100;NU5048;CS1591 Major README.md diff --git a/Tools/LambdaTestTool-v2/src/Amazon.Lambda.TestTool/Commands/RunCommand.cs b/Tools/LambdaTestTool-v2/src/Amazon.Lambda.TestTool/Commands/RunCommand.cs index 036611cd5..f65d7bad1 100644 --- a/Tools/LambdaTestTool-v2/src/Amazon.Lambda.TestTool/Commands/RunCommand.cs +++ b/Tools/LambdaTestTool-v2/src/Amazon.Lambda.TestTool/Commands/RunCommand.cs @@ -28,7 +28,7 @@ public sealed class RunCommand( /// /// Task for the Lambda Runtime API. /// - public Task LambdRuntimeApiTask { get; private set; } + public Task? LambdRuntimeApiTask { get; private set; } /// /// The method responsible for executing the . diff --git a/Tools/LambdaTestTool-v2/src/Amazon.Lambda.TestTool/Extensions/HttpContextExtensions.cs b/Tools/LambdaTestTool-v2/src/Amazon.Lambda.TestTool/Extensions/HttpContextExtensions.cs index b0e585f7d..f7e95614f 100644 --- a/Tools/LambdaTestTool-v2/src/Amazon.Lambda.TestTool/Extensions/HttpContextExtensions.cs +++ b/Tools/LambdaTestTool-v2/src/Amazon.Lambda.TestTool/Extensions/HttpContextExtensions.cs @@ -124,6 +124,7 @@ public static async Task ToApiGatewayHttpV2Requ /// /// The to be translated. /// The configuration of the API Gateway route, including the HTTP method, path, and other metadata. + /// /// An object representing the translated request. public static async Task ToApiGatewayRequest( this HttpContext context, diff --git a/Tools/LambdaTestTool-v2/src/Amazon.Lambda.TestTool/Models/EventContainer.cs b/Tools/LambdaTestTool-v2/src/Amazon.Lambda.TestTool/Models/EventContainer.cs index 366cb2a17..694f134cb 100644 --- a/Tools/LambdaTestTool-v2/src/Amazon.Lambda.TestTool/Models/EventContainer.cs +++ b/Tools/LambdaTestTool-v2/src/Amazon.Lambda.TestTool/Models/EventContainer.cs @@ -68,7 +68,7 @@ public void ReportSuccessResponse(string response) _dataStore.RaiseStateChanged(); } - public void ReportErrorResponse(string errorType, string errorBody) + public void ReportErrorResponse(string errorType, string? errorBody) { LastUpdated = DateTime.Now; ErrorType = errorType; diff --git a/Tools/LambdaTestTool-v2/src/Amazon.Lambda.TestTool/Services/RuntimeApiDataStore.cs b/Tools/LambdaTestTool-v2/src/Amazon.Lambda.TestTool/Services/RuntimeApiDataStore.cs index 1329c6e35..7a41f039b 100644 --- a/Tools/LambdaTestTool-v2/src/Amazon.Lambda.TestTool/Services/RuntimeApiDataStore.cs +++ b/Tools/LambdaTestTool-v2/src/Amazon.Lambda.TestTool/Services/RuntimeApiDataStore.cs @@ -72,7 +72,8 @@ public interface IRuntimeApiDataStore /// notification from the Lambda function. /// /// - /// + /// + /// void ReportError(string awsRequestId, string errorType, string errorBody); } diff --git a/Tools/LambdaTestTool-v2/src/Amazon.Lambda.TestTool/Utilities/HttpRequestUtility.cs b/Tools/LambdaTestTool-v2/src/Amazon.Lambda.TestTool/Utilities/HttpRequestUtility.cs index fce9494cc..2d1f9b261 100644 --- a/Tools/LambdaTestTool-v2/src/Amazon.Lambda.TestTool/Utilities/HttpRequestUtility.cs +++ b/Tools/LambdaTestTool-v2/src/Amazon.Lambda.TestTool/Utilities/HttpRequestUtility.cs @@ -145,6 +145,7 @@ public static (IDictionary, IDictionary>) /// A string representing a random request ID in the format used by API Gateway for HTTP APIs. /// /// The generated ID is a 145character string consisting of lowercase letters and numbers, followed by an equals sign. + /// public static string GenerateRequestId() { return $"{Guid.NewGuid().ToString("N").Substring(0, 8)}{Guid.NewGuid().ToString("N").Substring(0, 7)}="; diff --git a/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Extensions/InvokeResponseExtensionsTests.cs b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Extensions/InvokeResponseExtensionsTests.cs index f07f70a47..24d75acf3 100644 --- a/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Extensions/InvokeResponseExtensionsTests.cs +++ b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Extensions/InvokeResponseExtensionsTests.cs @@ -124,6 +124,8 @@ await _helper.VerifyApiGatewayResponseAsync( /// This test ensures that our ToApiGatewayHttpApiV2ProxyResponse method /// correctly replicates this observed behavior, rather than the documented behavior. /// + /// + /// [Theory] [InlineData("Invalid_JSON_Partial_Object", "{\"name\": \"John Doe\", \"age\":", "{\"name\": \"John Doe\", \"age\":")] // Invalid JSON (partial object) [InlineData("Valid_JSON_Object", "{\"name\": \"John Doe\", \"age\": 30}", "{\"name\": \"John Doe\", \"age\": 30}")] // Valid JSON object without statusCode diff --git a/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/PackagingTests.cs b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/PackagingTests.cs index d2051615e..0b3dc7cdf 100644 --- a/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/PackagingTests.cs +++ b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/PackagingTests.cs @@ -89,7 +89,7 @@ public void VerifyPackageContentsHasStaticAssets() public void VerifyPackageContentsHasRuntimeSupport() { var projectPath = Path.Combine(_workingDirectory, "Tools", "LambdaTestTool-v2", "src", "Amazon.Lambda.TestTool", "Amazon.Lambda.TestTool.csproj"); - var expectedFrameworks = new string[] { "net6.0", "net8.0", "net9.0", "net10.0" }; + var expectedFrameworks = new string[] { "net8.0", "net9.0", "net10.0" }; _output.WriteLine("Packing TestTool..."); var packProcess = new Process { diff --git a/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Processes/ApiGatewayEmulatorProcessTests.cs b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Processes/ApiGatewayEmulatorProcessTests.cs index fe6a6d790..4d7b03094 100644 --- a/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Processes/ApiGatewayEmulatorProcessTests.cs +++ b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Processes/ApiGatewayEmulatorProcessTests.cs @@ -12,7 +12,7 @@ namespace Amazon.Lambda.TestTool.UnitTests.Processes; -public class ApiGatewayEmulatorProcessTests(ITestOutputHelper testOutputHelper) +public class ApiGatewayEmulatorProcessTests { [Theory] [InlineData(ApiGatewayEmulatorMode.Rest, HttpStatusCode.Forbidden, "{\"message\":\"Missing Authentication Token\"}")] diff --git a/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/RuntimeApiTests.cs b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/RuntimeApiTests.cs index cb2afa01a..2b6bc20a8 100644 --- a/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/RuntimeApiTests.cs +++ b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/RuntimeApiTests.cs @@ -19,7 +19,7 @@ namespace Amazon.Lambda.TestTool.UnitTests; -public class RuntimeApiTests(ITestOutputHelper testOutputHelper) +public class RuntimeApiTests { #if DEBUG [Fact] diff --git a/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Utilities/DirectoryHelpers.cs b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Utilities/DirectoryHelpers.cs index 9303cd590..1c22bc4ce 100644 --- a/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Utilities/DirectoryHelpers.cs +++ b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Utilities/DirectoryHelpers.cs @@ -40,7 +40,7 @@ public static void CleanUp(string directory) } /// - /// + /// https://docs.microsoft.com/en-us/dotnet/standard/io/how-to-copy-directories /// private static void CopyDirectory(DirectoryInfo dir, string destDirName) { diff --git a/Tools/LambdaTestTool/src/Amazon.Lambda.TestTool.BlazorTester/Amazon.Lambda.TestTool.BlazorTester.csproj b/Tools/LambdaTestTool/src/Amazon.Lambda.TestTool.BlazorTester/Amazon.Lambda.TestTool.BlazorTester.csproj index 345380f68..964a49cb6 100644 --- a/Tools/LambdaTestTool/src/Amazon.Lambda.TestTool.BlazorTester/Amazon.Lambda.TestTool.BlazorTester.csproj +++ b/Tools/LambdaTestTool/src/Amazon.Lambda.TestTool.BlazorTester/Amazon.Lambda.TestTool.BlazorTester.csproj @@ -1,7 +1,7 @@ - + Exe A tool to help debug and test your .NET Core AWS Lambda functions locally. @@ -13,8 +13,9 @@ 1701;1702;1591;1587;3021;NU5100;CS1591 true net8.0;net9.0;net10.0 - - + false + + diff --git a/Tools/LambdaTestTool/src/Amazon.Lambda.TestTool.BlazorTester/Amazon.Lambda.TestTool.BlazorTester10_0-pack.csproj b/Tools/LambdaTestTool/src/Amazon.Lambda.TestTool.BlazorTester/Amazon.Lambda.TestTool.BlazorTester10_0-pack.csproj index 55fbe0b02..87bdbdc91 100644 --- a/Tools/LambdaTestTool/src/Amazon.Lambda.TestTool.BlazorTester/Amazon.Lambda.TestTool.BlazorTester10_0-pack.csproj +++ b/Tools/LambdaTestTool/src/Amazon.Lambda.TestTool.BlazorTester/Amazon.Lambda.TestTool.BlazorTester10_0-pack.csproj @@ -14,6 +14,7 @@ true true Amazon.Lambda.TestTool-10.0 + false Amazon.Lambda.TestTool.BlazorTester Amazon.Lambda.TestTool.BlazorTester true diff --git a/Tools/LambdaTestTool/src/Amazon.Lambda.TestTool.BlazorTester/Amazon.Lambda.TestTool.BlazorTester80-pack.csproj b/Tools/LambdaTestTool/src/Amazon.Lambda.TestTool.BlazorTester/Amazon.Lambda.TestTool.BlazorTester80-pack.csproj index 422742f07..237dfc764 100644 --- a/Tools/LambdaTestTool/src/Amazon.Lambda.TestTool.BlazorTester/Amazon.Lambda.TestTool.BlazorTester80-pack.csproj +++ b/Tools/LambdaTestTool/src/Amazon.Lambda.TestTool.BlazorTester/Amazon.Lambda.TestTool.BlazorTester80-pack.csproj @@ -14,6 +14,7 @@ true true Amazon.Lambda.TestTool-8.0 + false Amazon.Lambda.TestTool.BlazorTester Amazon.Lambda.TestTool.BlazorTester true diff --git a/Tools/LambdaTestTool/src/Amazon.Lambda.TestTool.BlazorTester/Amazon.Lambda.TestTool.BlazorTester90-pack.csproj b/Tools/LambdaTestTool/src/Amazon.Lambda.TestTool.BlazorTester/Amazon.Lambda.TestTool.BlazorTester90-pack.csproj index d656e40be..3b133525e 100644 --- a/Tools/LambdaTestTool/src/Amazon.Lambda.TestTool.BlazorTester/Amazon.Lambda.TestTool.BlazorTester90-pack.csproj +++ b/Tools/LambdaTestTool/src/Amazon.Lambda.TestTool.BlazorTester/Amazon.Lambda.TestTool.BlazorTester90-pack.csproj @@ -14,6 +14,7 @@ true true Amazon.Lambda.TestTool-9.0 + false Amazon.Lambda.TestTool.BlazorTester Amazon.Lambda.TestTool.BlazorTester true diff --git a/Tools/LambdaTestTool/src/Amazon.Lambda.TestTool/Amazon.Lambda.TestTool.csproj b/Tools/LambdaTestTool/src/Amazon.Lambda.TestTool/Amazon.Lambda.TestTool.csproj index 7467b5896..2b1d75130 100644 --- a/Tools/LambdaTestTool/src/Amazon.Lambda.TestTool/Amazon.Lambda.TestTool.csproj +++ b/Tools/LambdaTestTool/src/Amazon.Lambda.TestTool/Amazon.Lambda.TestTool.csproj @@ -1,11 +1,12 @@  - + net8.0;net9.0;net10.0 Common code for the AWS .NET Core Lambda Mock Test Tool. 1701;1702;1591;1587;3021;NU5100;CS1591 + false @@ -19,10 +20,10 @@ - + net8.0 - + net9.0 @@ -35,7 +36,7 @@ - + @@ -46,6 +47,6 @@ - - + + diff --git a/buildtools/common.props b/buildtools/common.props index 70fbc9abb..a7a261fe8 100644 --- a/buildtools/common.props +++ b/buildtools/common.props @@ -1,21 +1,21 @@ - $(MSBuildThisFileDirectory)/public.snk true - Amazon Web Services - + + net8.0;net10.0 + Library - false + $(NoWarn);CA1822;NU5048 + true true - + https://sdk-for-net.amazonwebservices.com/images/AWSLogo128x128.png https://github.com/aws/aws-lambda-dotnet Apache-2.0 - false false false @@ -23,6 +23,5 @@ false false false - - \ No newline at end of file +