diff --git a/.autover/changes/6e13a012-1f93-4e55-90b5-d2dd480d086c.json b/.autover/changes/6e13a012-1f93-4e55-90b5-d2dd480d086c.json
new file mode 100644
index 000000000..306989ddb
--- /dev/null
+++ b/.autover/changes/6e13a012-1f93-4e55-90b5-d2dd480d086c.json
@@ -0,0 +1,25 @@
+{
+ "Projects": [
+ {
+ "Name": "Amazon.Lambda.Core",
+ "Type": "Minor",
+ "ChangelogMessages": [
+ "Add preview ILambdaSerializer Serializer property to ILambdaContext (default-implemented to null on net8.0+) so user code can access the serializer registered with the runtime. Marked [Experimental(\"AWSLAMBDA001\")]; class-library mode requires an updated managed Lambda runtime to populate this property. The Experimental flag will be removed in a follow-up release once the managed runtime is deployed."
+ ]
+ },
+ {
+ "Name": "Amazon.Lambda.RuntimeSupport",
+ "Type": "Minor",
+ "ChangelogMessages": [
+ "Propagate the registered ILambdaSerializer to the per-invocation ILambdaContext.Serializer. Surfaces the new preview ILambdaContext.Serializer (AWSLAMBDA001); the Experimental flag will be removed in a follow-up release once the managed runtime is deployed."
+ ]
+ },
+ {
+ "Name": "Amazon.Lambda.TestUtilities",
+ "Type": "Minor",
+ "ChangelogMessages": [
+ "Add Serializer setter to TestLambdaContext to mirror the new preview ILambdaContext.Serializer property. Marked [Experimental(\"AWSLAMBDA001\")]; the Experimental flag will be removed in a follow-up release once the managed runtime is deployed."
+ ]
+ }
+ ]
+}
diff --git a/Libraries/src/Amazon.Lambda.Core/ILambdaContext.cs b/Libraries/src/Amazon.Lambda.Core/ILambdaContext.cs
index 81f290408..21f7a54f6 100644
--- a/Libraries/src/Amazon.Lambda.Core/ILambdaContext.cs
+++ b/Libraries/src/Amazon.Lambda.Core/ILambdaContext.cs
@@ -1,6 +1,7 @@
namespace Amazon.Lambda.Core
{
using System;
+ using System.Diagnostics.CodeAnalysis;
///
/// Object that allows you to access useful information available within
@@ -95,6 +96,25 @@ public interface ILambdaContext
/// The trace id generated by Lambda for distributed tracing across AWS services.
///
string TraceId { get { return string.Empty; } }
+
+ ///
+ /// The the Lambda function registered with the
+ /// runtime — either the instance passed to
+ /// LambdaBootstrapBuilder.Create(handler, serializer) /
+ /// HandlerWrapper.GetHandlerWrapper(handler, serializer), or the type set
+ /// via [assembly: LambdaSerializer(typeof(...))] in class-library mode.
+ /// User code can reuse it for ad-hoc (de)serialization without re-instantiating.
+ /// Can be null when the function did not register a serializer (e.g., raw-stream
+ /// handlers).
+ ///
+ ///
+ /// Preview API. Class-library mode requires an updated managed
+ /// Lambda runtime to populate this property; until that ships, the value will
+ /// be null when running in class-library mode. The
+ /// is applied to surface this caveat at the call site.
+ ///
+ [Experimental("AWSLAMBDA001")]
+ ILambdaSerializer Serializer { get { return null; } }
#endif
}
}
diff --git a/Libraries/src/Amazon.Lambda.RuntimeSupport/Bootstrap/HandlerWrapper.cs b/Libraries/src/Amazon.Lambda.RuntimeSupport/Bootstrap/HandlerWrapper.cs
index e3a74d04b..1981d5509 100644
--- a/Libraries/src/Amazon.Lambda.RuntimeSupport/Bootstrap/HandlerWrapper.cs
+++ b/Libraries/src/Amazon.Lambda.RuntimeSupport/Bootstrap/HandlerWrapper.cs
@@ -36,6 +36,14 @@ public class HandlerWrapper : IDisposable
///
public LambdaBootstrapHandler Handler { get; private set; }
+ ///
+ /// The serializer registered with the wrapper, if any. Surfaced so the
+ /// runtime bootstrap can attach it to the per-invocation
+ /// , allowing user code to reuse it.
+ /// Null for handlers that don't take a typed input/output.
+ ///
+ internal ILambdaSerializer Serializer { get; set; }
+
private HandlerWrapper(LambdaBootstrapHandler handler)
{
Handler = handler;
@@ -121,7 +129,7 @@ public static HandlerWrapper GetHandlerWrapper(Func handle
TInput input = serializer.Deserialize(invocation.InputStream);
await handler(input);
return EmptyInvocationResponse;
- });
+ }) { Serializer = serializer };
}
///
@@ -171,7 +179,7 @@ public static HandlerWrapper GetHandlerWrapper(Func(invocation.InputStream);
await handler(input, invocation.LambdaContext);
return EmptyInvocationResponse;
- });
+ }) { Serializer = serializer };
}
///
@@ -218,7 +226,7 @@ public static HandlerWrapper GetHandlerWrapper(Func
{
TInput input = serializer.Deserialize(invocation.InputStream);
return new InvocationResponse(await handler(input));
- });
+ }) { Serializer = serializer };
}
///
@@ -265,7 +273,7 @@ public static HandlerWrapper GetHandlerWrapper(Func(invocation.InputStream);
return new InvocationResponse(await handler(input, invocation.LambdaContext));
- });
+ }) { Serializer = serializer };
}
///
@@ -278,7 +286,7 @@ public static HandlerWrapper GetHandlerWrapper(FuncA HandlerWrapper
public static HandlerWrapper GetHandlerWrapper(Func> handler, ILambdaSerializer serializer)
{
- var handlerWrapper = new HandlerWrapper();
+ var handlerWrapper = new HandlerWrapper { Serializer = serializer };
handlerWrapper.Handler = async (invocation) =>
{
TOutput output = await handler();
@@ -300,7 +308,7 @@ public static HandlerWrapper GetHandlerWrapper(Func> hand
/// A HandlerWrapper
public static HandlerWrapper GetHandlerWrapper(Func> handler, ILambdaSerializer serializer)
{
- var handlerWrapper = new HandlerWrapper();
+ var handlerWrapper = new HandlerWrapper { Serializer = serializer };
handlerWrapper.Handler = async (invocation) =>
{
TOutput output = await handler(invocation.InputStream);
@@ -322,7 +330,7 @@ public static HandlerWrapper GetHandlerWrapper(FuncA HandlerWrapper
public static HandlerWrapper GetHandlerWrapper(Func> handler, ILambdaSerializer serializer)
{
- var handlerWrapper = new HandlerWrapper();
+ var handlerWrapper = new HandlerWrapper { Serializer = serializer };
handlerWrapper.Handler = async (invocation) =>
{
TInput input = serializer.Deserialize(invocation.InputStream);
@@ -345,7 +353,7 @@ public static HandlerWrapper GetHandlerWrapper(FuncA HandlerWrapper
public static HandlerWrapper GetHandlerWrapper(Func> handler, ILambdaSerializer serializer)
{
- var handlerWrapper = new HandlerWrapper();
+ var handlerWrapper = new HandlerWrapper { Serializer = serializer };
handlerWrapper.Handler = async (invocation) =>
{
TOutput output = await handler(invocation.LambdaContext);
@@ -367,7 +375,7 @@ public static HandlerWrapper GetHandlerWrapper(FuncA HandlerWrapper
public static HandlerWrapper GetHandlerWrapper(Func> handler, ILambdaSerializer serializer)
{
- var handlerWrapper = new HandlerWrapper();
+ var handlerWrapper = new HandlerWrapper { Serializer = serializer };
handlerWrapper.Handler = async (invocation) =>
{
TOutput output = await handler(invocation.InputStream, invocation.LambdaContext);
@@ -389,7 +397,7 @@ public static HandlerWrapper GetHandlerWrapper(FuncA HandlerWrapper
public static HandlerWrapper GetHandlerWrapper(Func> handler, ILambdaSerializer serializer)
{
- var handlerWrapper = new HandlerWrapper();
+ var handlerWrapper = new HandlerWrapper { Serializer = serializer };
handlerWrapper.Handler = async (invocation) =>
{
TInput input = serializer.Deserialize(invocation.InputStream);
@@ -449,7 +457,7 @@ public static HandlerWrapper GetHandlerWrapper(Action handler, I
TInput input = serializer.Deserialize(invocation.InputStream);
handler(input);
return Task.FromResult(EmptyInvocationResponse);
- });
+ }) { Serializer = serializer };
}
///
@@ -499,7 +507,7 @@ public static HandlerWrapper GetHandlerWrapper(Action(invocation.InputStream);
handler(input, invocation.LambdaContext);
return Task.FromResult(EmptyInvocationResponse);
- });
+ }) { Serializer = serializer };
}
///
@@ -546,7 +554,7 @@ public static HandlerWrapper GetHandlerWrapper(Func hand
{
TInput input = serializer.Deserialize(invocation.InputStream);
return Task.FromResult(new InvocationResponse(handler(input)));
- });
+ }) { Serializer = serializer };
}
///
@@ -593,7 +601,7 @@ public static HandlerWrapper GetHandlerWrapper(Func(invocation.InputStream);
return Task.FromResult(new InvocationResponse(handler(input, invocation.LambdaContext)));
- });
+ }) { Serializer = serializer };
}
///
@@ -606,7 +614,7 @@ public static HandlerWrapper GetHandlerWrapper(FuncA HandlerWrapper
public static HandlerWrapper GetHandlerWrapper(Func handler, ILambdaSerializer serializer)
{
- var handlerWrapper = new HandlerWrapper();
+ var handlerWrapper = new HandlerWrapper { Serializer = serializer };
handlerWrapper.Handler = (invocation) =>
{
TOutput output = handler();
@@ -628,7 +636,7 @@ public static HandlerWrapper GetHandlerWrapper(Func handler, I
/// A HandlerWrapper
public static HandlerWrapper GetHandlerWrapper(Func handler, ILambdaSerializer serializer)
{
- var handlerWrapper = new HandlerWrapper();
+ var handlerWrapper = new HandlerWrapper { Serializer = serializer };
handlerWrapper.Handler = (invocation) =>
{
TOutput output = handler(invocation.InputStream);
@@ -650,7 +658,7 @@ public static HandlerWrapper GetHandlerWrapper(Func ha
/// A HandlerWrapper
public static HandlerWrapper GetHandlerWrapper(Func handler, ILambdaSerializer serializer)
{
- var handlerWrapper = new HandlerWrapper();
+ var handlerWrapper = new HandlerWrapper { Serializer = serializer };
handlerWrapper.Handler = (invocation) =>
{
TInput input = serializer.Deserialize(invocation.InputStream);
@@ -673,7 +681,7 @@ public static HandlerWrapper GetHandlerWrapper(FuncA HandlerWrapper
public static HandlerWrapper GetHandlerWrapper(Func handler, ILambdaSerializer serializer)
{
- var handlerWrapper = new HandlerWrapper();
+ var handlerWrapper = new HandlerWrapper { Serializer = serializer };
handlerWrapper.Handler = (invocation) =>
{
TOutput output = handler(invocation.LambdaContext);
@@ -695,7 +703,7 @@ public static HandlerWrapper GetHandlerWrapper(FuncA HandlerWrapper
public static HandlerWrapper GetHandlerWrapper(Func handler, ILambdaSerializer serializer)
{
- var handlerWrapper = new HandlerWrapper();
+ var handlerWrapper = new HandlerWrapper { Serializer = serializer };
handlerWrapper.Handler = (invocation) =>
{
TOutput output = handler(invocation.InputStream, invocation.LambdaContext);
@@ -717,7 +725,7 @@ public static HandlerWrapper GetHandlerWrapper(FuncA HandlerWrapper
public static HandlerWrapper GetHandlerWrapper(Func handler, ILambdaSerializer serializer)
{
- var handlerWrapper = new HandlerWrapper();
+ var handlerWrapper = new HandlerWrapper { Serializer = serializer };
handlerWrapper.Handler = (invocation) =>
{
TInput input = serializer.Deserialize(invocation.InputStream);
diff --git a/Libraries/src/Amazon.Lambda.RuntimeSupport/Bootstrap/LambdaBootstrap.cs b/Libraries/src/Amazon.Lambda.RuntimeSupport/Bootstrap/LambdaBootstrap.cs
index da5367cab..130ddf1d4 100644
--- a/Libraries/src/Amazon.Lambda.RuntimeSupport/Bootstrap/LambdaBootstrap.cs
+++ b/Libraries/src/Amazon.Lambda.RuntimeSupport/Bootstrap/LambdaBootstrap.cs
@@ -52,6 +52,10 @@ public class LambdaBootstrap : IDisposable
private readonly LambdaBootstrapInitializer _initializer;
private readonly LambdaBootstrapHandler _handler;
+ // Mutable so RuntimeSupportInitializer (class-library mode) can set this after
+ // UserCodeLoader.Init resolves [assembly: LambdaSerializer]. Read on every
+ // invocation to populate ILambdaContext.Serializer.
+ private Amazon.Lambda.Core.ILambdaSerializer _serializer;
private readonly bool _ownsHttpClient;
private readonly InternalLogger _logger = InternalLogger.GetDefaultLogger();
@@ -62,6 +66,18 @@ public class LambdaBootstrap : IDisposable
internal IRuntimeApiClient Client { get; set; }
+ ///
+ /// Set the serializer to surface on
+ /// for each invocation. Used by to plumb the
+ /// serializer constructed from [assembly: LambdaSerializer] after
+ /// has initialized. Setter is internal — public
+ /// callers register the serializer via instead.
+ ///
+ internal void SetSerializer(Amazon.Lambda.Core.ILambdaSerializer serializer)
+ {
+ _serializer = serializer;
+ }
+
///
/// Create a LambdaBootstrap that will call the given initializer and handler.
@@ -101,7 +117,7 @@ public LambdaBootstrap(LambdaBootstrapHandler handler, LambdaBootstrapOptions la
/// Delegate called to initialize the Lambda function. If not provided the initialization step is skipped.
///
public LambdaBootstrap(HandlerWrapper handlerWrapper, LambdaBootstrapInitializer initializer = null)
- : this(handlerWrapper.Handler, initializer)
+ : this(ConstructHttpClient(), handlerWrapper.Handler, initializer, ownsHttpClient: true, serializer: handlerWrapper.Serializer)
{ }
///
@@ -111,7 +127,7 @@ public LambdaBootstrap(HandlerWrapper handlerWrapper, LambdaBootstrapInitializer
/// Lambda bootstrap configuration options.
/// Delegate called to initialize the Lambda function. If not provided the initialization step is skipped.
public LambdaBootstrap(HandlerWrapper handlerWrapper, LambdaBootstrapOptions lambdaBootstrapOptions, LambdaBootstrapInitializer initializer = null)
- : this(handlerWrapper.Handler, lambdaBootstrapOptions, initializer)
+ : this(ConstructHttpClient(), handlerWrapper.Handler, initializer, ownsHttpClient: true, lambdaBootstrapOptions: lambdaBootstrapOptions, serializer: handlerWrapper.Serializer)
{ }
///
@@ -122,7 +138,7 @@ public LambdaBootstrap(HandlerWrapper handlerWrapper, LambdaBootstrapOptions lam
/// Delegate called to initialize the Lambda function. If not provided the initialization step is skipped.
///
public LambdaBootstrap(HttpClient httpClient, HandlerWrapper handlerWrapper, LambdaBootstrapInitializer initializer = null)
- : this(httpClient, handlerWrapper.Handler, initializer, ownsHttpClient: false)
+ : this(httpClient, handlerWrapper.Handler, initializer, ownsHttpClient: false, serializer: handlerWrapper.Serializer)
{ }
///
@@ -133,7 +149,7 @@ public LambdaBootstrap(HttpClient httpClient, HandlerWrapper handlerWrapper, Lam
/// Lambda bootstrap configuration options.
/// Delegate called to initialize the Lambda function. If not provided the initialization step is skipped.
public LambdaBootstrap(HttpClient httpClient, HandlerWrapper handlerWrapper, LambdaBootstrapOptions lambdaBootstrapOptions, LambdaBootstrapInitializer initializer = null)
- : this(httpClient, handlerWrapper.Handler, initializer, ownsHttpClient: false, lambdaBootstrapOptions: lambdaBootstrapOptions)
+ : this(httpClient, handlerWrapper.Handler, initializer, ownsHttpClient: false, lambdaBootstrapOptions: lambdaBootstrapOptions, serializer: handlerWrapper.Serializer)
{ }
///
@@ -170,7 +186,8 @@ internal LambdaBootstrap(LambdaBootstrapHandler handler, LambdaBootstrapInitiali
/// Get configuration to check if Invoke is with Pre JIT or SnapStart enabled
/// Lambda bootstrap configuration options.
///
- internal LambdaBootstrap(HttpClient httpClient, LambdaBootstrapHandler handler, LambdaBootstrapInitializer initializer, bool ownsHttpClient, LambdaBootstrapConfiguration configuration = null, LambdaBootstrapOptions lambdaBootstrapOptions = null, IEnvironmentVariables environmentVariables = null)
+ /// The Lambda serializer to expose on the per-invocation . May be null.
+ internal LambdaBootstrap(HttpClient httpClient, LambdaBootstrapHandler handler, LambdaBootstrapInitializer initializer, bool ownsHttpClient, LambdaBootstrapConfiguration configuration = null, LambdaBootstrapOptions lambdaBootstrapOptions = null, IEnvironmentVariables environmentVariables = null, Amazon.Lambda.Core.ILambdaSerializer serializer = null)
{
if (ownsHttpClient && httpClient == null)
{
@@ -179,6 +196,7 @@ internal LambdaBootstrap(HttpClient httpClient, LambdaBootstrapHandler handler,
_httpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient));
_handler = handler ?? throw new ArgumentNullException(nameof(handler));
+ _serializer = serializer;
_ownsHttpClient = ownsHttpClient;
_initializer = initializer;
_httpClient.Timeout = RuntimeApiHttpTimeout;
@@ -365,6 +383,7 @@ internal async Task InvokeOnceAsync(CancellationToken cancellationToken = defaul
{
Client.ConsoleLogger.SetRuntimeHeaders(impl.RuntimeApiHeaders);
SetInvocationTraceId(impl.RuntimeApiHeaders.TraceId);
+ SetSerializerOnContext(impl);
}
// Initialize ResponseStreamFactory — includes RuntimeApiClient reference
@@ -481,6 +500,16 @@ internal async Task InvokeOnceAsync(CancellationToken cancellationToken = defaul
}
}
+ private void SetSerializerOnContext(LambdaContext context)
+ {
+ // No serializer was registered with this bootstrap (raw-stream handler, or
+ // the user constructed LambdaBootstrap with a LambdaBootstrapHandler directly).
+ // Nothing to surface — leave context.Serializer null.
+ if (_serializer == null) return;
+
+ context.Serializer = _serializer;
+ }
+
volatile bool _disableTraceProvider = false;
private void SetInvocationTraceId(string traceId)
{
diff --git a/Libraries/src/Amazon.Lambda.RuntimeSupport/Bootstrap/UserCodeLoader.cs b/Libraries/src/Amazon.Lambda.RuntimeSupport/Bootstrap/UserCodeLoader.cs
index 84b3d7aa4..5c079a7d8 100644
--- a/Libraries/src/Amazon.Lambda.RuntimeSupport/Bootstrap/UserCodeLoader.cs
+++ b/Libraries/src/Amazon.Lambda.RuntimeSupport/Bootstrap/UserCodeLoader.cs
@@ -46,6 +46,17 @@ internal class UserCodeLoader
private Action _invokeDelegate;
internal MethodInfo CustomerMethodInfo { get; private set; }
+ ///
+ /// The serializer instance constructed from the customer's
+ /// [LambdaSerializer(typeof(...))] attribute (if any). Populated by
+ /// . Typed as here because the value is
+ /// produced via reflection in and validated
+ /// against the loaded ILambdaSerializer interface there;
+ /// casts it back to ILambdaSerializer
+ /// before handing it to .
+ ///
+ internal object CustomerSerializerInstance { get; private set; }
+
///
/// Initializes UserCodeLoader with a given handler and internal logger.
///
@@ -129,6 +140,7 @@ public void Init(Action customerLoggingAction)
var customerObject = GetCustomerObject(customerType);
var customerSerializerInstance = GetSerializerObject(customerAssembly);
+ CustomerSerializerInstance = customerSerializerInstance;
_logger.LogDebug($"UCL : Constructing invoke delegate");
var isPreJit = UserCodeInit.IsCallPreJit(_environmentVariables);
diff --git a/Libraries/src/Amazon.Lambda.RuntimeSupport/Context/LambdaContext.cs b/Libraries/src/Amazon.Lambda.RuntimeSupport/Context/LambdaContext.cs
index cca6f4e5d..fea4a6bd2 100644
--- a/Libraries/src/Amazon.Lambda.RuntimeSupport/Context/LambdaContext.cs
+++ b/Libraries/src/Amazon.Lambda.RuntimeSupport/Context/LambdaContext.cs
@@ -77,6 +77,13 @@ public LambdaContext(RuntimeApiHeaders runtimeApiHeaders, LambdaEnvironment lamb
public string TenantId => _runtimeApiHeaders.TenantId;
+ ///
+ /// The serializer the Lambda function registered with the runtime, surfaced via
+ /// . Assigned per-invocation by
+ /// .
+ ///
+ public ILambdaSerializer Serializer { get; internal set; }
+
internal IRuntimeApiHeaders RuntimeApiHeaders => _runtimeApiHeaders;
}
}
diff --git a/Libraries/src/Amazon.Lambda.RuntimeSupport/RuntimeSupportInitializer.cs b/Libraries/src/Amazon.Lambda.RuntimeSupport/RuntimeSupportInitializer.cs
index 02fcfc2c2..0de36f58e 100644
--- a/Libraries/src/Amazon.Lambda.RuntimeSupport/RuntimeSupportInitializer.cs
+++ b/Libraries/src/Amazon.Lambda.RuntimeSupport/RuntimeSupportInitializer.cs
@@ -63,11 +63,27 @@ public async Task RunLambdaBootstrap()
var environmentVariables = new SystemEnvironmentVariables();
var userCodeLoader = new UserCodeLoader(environmentVariables, _handler, _logger);
var initializer = new UserCodeInitializer(userCodeLoader, _logger);
+ // Pre-declare so the wrapped initializer can reference it. The closure runs
+ // later (inside bootstrap.RunAsync) by which time bootstrap is assigned.
+ LambdaBootstrap bootstrap = null;
+ // Wrap init to plumb the serializer ([assembly: LambdaSerializer]) onto the
+ // bootstrap right after UserCodeLoader resolves it. The bootstrap then
+ // surfaces it on ILambdaContext.Serializer for every invocation via the
+ // Isolated shim.
+ LambdaBootstrapInitializer wrappedInit = async () =>
+ {
+ var initResult = await initializer.InitializeAsync();
+ if (initResult)
+ {
+ bootstrap.SetSerializer(userCodeLoader.CustomerSerializerInstance as Amazon.Lambda.Core.ILambdaSerializer);
+ }
+ return initResult;
+ };
using (var handlerWrapper = HandlerWrapper.GetHandlerWrapper(userCodeLoader.Invoke))
- using (var bootstrap = new LambdaBootstrap(
+ using (bootstrap = new LambdaBootstrap(
httpClient: null,
handler: handlerWrapper.Handler,
- initializer: initializer.InitializeAsync,
+ initializer: wrappedInit,
ownsHttpClient: true,
lambdaBootstrapOptions: _lambdaBootstrapOptions,
environmentVariables: environmentVariables))
diff --git a/Libraries/src/Amazon.Lambda.TestUtilities/TestLambdaContext.cs b/Libraries/src/Amazon.Lambda.TestUtilities/TestLambdaContext.cs
index e3a47d308..07723b13c 100644
--- a/Libraries/src/Amazon.Lambda.TestUtilities/TestLambdaContext.cs
+++ b/Libraries/src/Amazon.Lambda.TestUtilities/TestLambdaContext.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Threading.Tasks;
@@ -80,5 +81,17 @@ public class TestLambdaContext : ILambdaContext
/// The trace id generated by Lambda for distributed tracing across AWS services.
///
public string TraceId { get; set; }
+
+ ///
+ /// The Lambda serializer registered for the current invocation. Tests can set this
+ /// to mirror the serializer that the Lambda runtime support library would attach
+ /// in production.
+ ///
+ ///
+ /// Preview API. Mirrors the experimental ILambdaContext.Serializer
+ /// surface so test code opts in via the same AWSLAMBDA001 diagnostic.
+ ///
+ [Experimental("AWSLAMBDA001")]
+ public ILambdaSerializer Serializer { get; set; }
}
}
diff --git a/Libraries/test/Amazon.Lambda.Core.Tests/TestLambdaContextSerializerTest.cs b/Libraries/test/Amazon.Lambda.Core.Tests/TestLambdaContextSerializerTest.cs
new file mode 100644
index 000000000..c806609ee
--- /dev/null
+++ b/Libraries/test/Amazon.Lambda.Core.Tests/TestLambdaContextSerializerTest.cs
@@ -0,0 +1,37 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+#pragma warning disable AWSLAMBDA001 // ILambdaContext.Serializer is preview; this is the test that proves it works.
+using System.IO;
+using Amazon.Lambda.Core;
+using Amazon.Lambda.TestUtilities;
+using Xunit;
+
+namespace Amazon.Lambda.Tests
+{
+ public class TestLambdaContextSerializerTest
+ {
+ [Fact]
+ public void Serializer_DefaultsToNull()
+ {
+ var context = new TestLambdaContext();
+
+ Assert.Null(context.Serializer);
+ }
+
+ [Fact]
+ public void Serializer_RoundTripsThroughTestContext()
+ {
+ var stub = new StubSerializer();
+ var context = new TestLambdaContext { Serializer = stub };
+
+ ILambdaContext asInterface = context;
+ Assert.Same(stub, asInterface.Serializer);
+ }
+
+ private sealed class StubSerializer : ILambdaSerializer
+ {
+ public T Deserialize(Stream requestStream) => default;
+ public void Serialize(T response, Stream responseStream) { }
+ }
+ }
+}
diff --git a/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/LambdaContextSerializerTests.cs b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/LambdaContextSerializerTests.cs
new file mode 100644
index 000000000..8b62c25ef
--- /dev/null
+++ b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/LambdaContextSerializerTests.cs
@@ -0,0 +1,224 @@
+/*
+ * 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.
+ */
+#pragma warning disable AWSLAMBDA001 // ILambdaContext.Serializer is preview; this is the test that proves it works.
+using Amazon.Lambda.Core;
+using Amazon.Lambda.RuntimeSupport.Bootstrap;
+using Amazon.Lambda.RuntimeSupport.Helpers;
+using Amazon.Lambda.Serialization.Json;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Threading.Tasks;
+using Xunit;
+
+namespace Amazon.Lambda.RuntimeSupport.UnitTests
+{
+ ///
+ /// Verifies that the serializer registered with a /
+ /// is exposed on the per-invocation
+ /// via .
+ ///
+ public class LambdaContextSerializerTests
+ {
+ private static readonly JsonSerializer SharedSerializer = new JsonSerializer();
+
+ private readonly TestEnvironmentVariables _environmentVariables;
+ private readonly LambdaEnvironment _lambdaEnvironment;
+ private readonly RuntimeApiHeaders _runtimeApiHeaders;
+ private readonly Dictionary> _headers;
+
+ public LambdaContextSerializerTests()
+ {
+ _environmentVariables = new TestEnvironmentVariables();
+ _lambdaEnvironment = new LambdaEnvironment(_environmentVariables);
+
+ _headers = new Dictionary>
+ {
+ [RuntimeApiHeaders.HeaderAwsRequestId] = new[] { "request-id" },
+ [RuntimeApiHeaders.HeaderInvokedFunctionArn] = new[] { "invoked-function-arn" }
+ };
+ _runtimeApiHeaders = new RuntimeApiHeaders(_headers);
+ }
+
+ [Fact]
+ public void LambdaContext_Serializer_DefaultsToNull()
+ {
+ var context = new LambdaContext(_runtimeApiHeaders, _lambdaEnvironment, new LogLevelLoggerWriter(new SystemEnvironmentVariables()));
+
+ Assert.Null(context.Serializer);
+ }
+
+ [Fact]
+ public void HandlerWrapper_PocoInOut_ExposesSerializer()
+ {
+ using var handlerWrapper = HandlerWrapper.GetHandlerWrapper(
+ input => Task.FromResult(new PocoOutput()),
+ SharedSerializer);
+
+ Assert.Same(SharedSerializer, handlerWrapper.Serializer);
+ }
+
+ [Fact]
+ public void HandlerWrapper_RawStreamOverloads_HaveNullSerializer()
+ {
+ using var handlerWrapper = HandlerWrapper.GetHandlerWrapper(
+ (Func>)((input) => Task.FromResult(new MemoryStream())));
+
+ Assert.Null(handlerWrapper.Serializer);
+ }
+
+ [Fact]
+ public void HandlerWrapper_SerializerOverloadFamilies_PropagateSerializer()
+ {
+ // One sample per overload family (Func/Action × Task/non-Task × in/out × ILambdaContext) —
+ // they share the same field-assignment line. This guards against future overloads being
+ // added without setting Serializer, but only spot-checks each family rather than every
+ // overload signature.
+ using (var w = HandlerWrapper.GetHandlerWrapper((input) => Task.CompletedTask, SharedSerializer))
+ Assert.Same(SharedSerializer, w.Serializer);
+
+ using (var w = HandlerWrapper.GetHandlerWrapper((input, ctx) => Task.CompletedTask, SharedSerializer))
+ Assert.Same(SharedSerializer, w.Serializer);
+
+ using (var w = HandlerWrapper.GetHandlerWrapper(
+ (Func>)((input) => Task.FromResult(new MemoryStream())), SharedSerializer))
+ Assert.Same(SharedSerializer, w.Serializer);
+
+ using (var w = HandlerWrapper.GetHandlerWrapper(() => Task.FromResult(new PocoOutput()), SharedSerializer))
+ Assert.Same(SharedSerializer, w.Serializer);
+
+ using (var w = HandlerWrapper.GetHandlerWrapper(
+ (input, ctx) => Task.FromResult(new PocoOutput()), SharedSerializer))
+ Assert.Same(SharedSerializer, w.Serializer);
+
+ using (var w = HandlerWrapper.GetHandlerWrapper((Action)(input => { }), SharedSerializer))
+ Assert.Same(SharedSerializer, w.Serializer);
+
+ using (var w = HandlerWrapper.GetHandlerWrapper((Func)(() => new PocoOutput()), SharedSerializer))
+ Assert.Same(SharedSerializer, w.Serializer);
+ }
+
+ [Fact]
+ public async Task LambdaBootstrap_InvokeOnce_SetsSerializerOnContext()
+ {
+ // End-to-end: a HandlerWrapper-backed bootstrap invokes once against a test
+ // RuntimeApiClient. The user's handler reads context.Serializer mid-invocation
+ // and must see the registered instance — proving SetSerializerOnContext fires
+ // during the invoke loop.
+ ILambdaSerializer observed = null;
+ using var handlerWrapper = HandlerWrapper.GetHandlerWrapper(
+ (input, ctx) =>
+ {
+ observed = ctx.Serializer;
+ return Task.FromResult(new PocoOutput());
+ },
+ SharedSerializer);
+
+ using var bootstrap = new LambdaBootstrap(handlerWrapper);
+ var testClient = new TestRuntimeApiClient(_environmentVariables, _headers)
+ {
+ FunctionInput = SerializeToBytes(new PocoInput { InputInt = 1, InputString = "x" })
+ };
+ bootstrap.Client = testClient;
+
+ await bootstrap.InvokeOnceAsync();
+
+ Assert.Same(SharedSerializer, observed);
+ }
+
+ [Fact]
+ public async Task LambdaBootstrap_InvokeOnce_RawStreamHandler_LeavesSerializerNull()
+ {
+ // Raw-stream handlers don't register a serializer — context.Serializer must
+ // stay null even after the invoke loop runs.
+ ILambdaSerializer observed = SharedSerializer; // start non-null to prove it's set to null
+ using var handlerWrapper = HandlerWrapper.GetHandlerWrapper(
+ (Func)((input, ctx) =>
+ {
+ observed = ctx.Serializer;
+ return Task.CompletedTask;
+ }));
+
+ using var bootstrap = new LambdaBootstrap(handlerWrapper);
+ var testClient = new TestRuntimeApiClient(_environmentVariables, _headers);
+ bootstrap.Client = testClient;
+
+ await bootstrap.InvokeOnceAsync();
+
+ Assert.Null(observed);
+ }
+
+ [Fact]
+ public void UserCodeLoader_Init_PopulatesCustomerSerializerFromAssemblyAttribute()
+ {
+ // Class-library mode: [assembly: LambdaSerializer(typeof(JsonSerializer))] on the
+ // HandlerTest assembly should make UserCodeLoader.Init resolve a JsonSerializer
+ // instance. This is what RuntimeSupportInitializer reads and pushes onto
+ // LambdaBootstrap.SetSerializer in production.
+ var ucl = new UserCodeLoader(
+ new SystemEnvironmentVariables(),
+ "HandlerTest::HandlerTest.CustomerType::ZeroInZeroOut",
+ InternalLogger.NoOpLogger);
+
+ ucl.Init(message => { });
+
+ Assert.NotNull(ucl.CustomerSerializerInstance);
+ Assert.IsType(ucl.CustomerSerializerInstance);
+ }
+
+ [Fact]
+ public async Task LambdaBootstrap_SetSerializer_FlowsAssemblySerializerToContext()
+ {
+ // End-to-end class-library wiring: the value UserCodeLoader.Init resolves from
+ // [assembly: LambdaSerializer] is what RuntimeSupportInitializer pushes onto the
+ // bootstrap via SetSerializer, after which the invoke loop must surface it on
+ // ILambdaContext.Serializer for every invocation.
+ var ucl = new UserCodeLoader(
+ new SystemEnvironmentVariables(),
+ "HandlerTest::HandlerTest.CustomerType::ZeroInZeroOut",
+ InternalLogger.NoOpLogger);
+ ucl.Init(message => { });
+ var assemblySerializer = (ILambdaSerializer)ucl.CustomerSerializerInstance;
+
+ ILambdaSerializer observed = null;
+ using var handlerWrapper = HandlerWrapper.GetHandlerWrapper(
+ (input, ctx) =>
+ {
+ observed = ctx.Serializer;
+ return Task.FromResult(new PocoOutput());
+ },
+ SharedSerializer);
+
+ using var bootstrap = new LambdaBootstrap(handlerWrapper);
+ bootstrap.SetSerializer(assemblySerializer);
+ var testClient = new TestRuntimeApiClient(_environmentVariables, _headers)
+ {
+ FunctionInput = SerializeToBytes(new PocoInput { InputInt = 1, InputString = "x" })
+ };
+ bootstrap.Client = testClient;
+
+ await bootstrap.InvokeOnceAsync();
+
+ Assert.Same(assemblySerializer, observed);
+ }
+
+ private static byte[] SerializeToBytes(T value)
+ {
+ using var ms = new MemoryStream();
+ SharedSerializer.Serialize(value, ms);
+ return ms.ToArray();
+ }
+ }
+}