diff --git a/custom-runtime/src/main/java/com/networknt/aws/lambda/LambdaRuntimeMappers.java b/custom-runtime/src/main/java/com/networknt/aws/lambda/LambdaRuntimeMappers.java new file mode 100644 index 0000000..0cc8e8b --- /dev/null +++ b/custom-runtime/src/main/java/com/networknt/aws/lambda/LambdaRuntimeMappers.java @@ -0,0 +1,22 @@ +package com.networknt.aws.lambda; + +import com.networknt.jackson.JacksonDefaults; +import tools.jackson.databind.DeserializationFeature; +import tools.jackson.databind.ObjectMapper; + +/** + * Shared immutable mapper exclusively for AWS Lambda event payloads in the JVM and native runtimes. + * Intentionally accepts unknown event properties, making it more permissive than the framework mapper. + * Do not use it for configuration binding or validation. + */ +public final class LambdaRuntimeMappers { + private static final ObjectMapper MAPPER = JacksonDefaults.builder() + .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES).build(); + + private LambdaRuntimeMappers() { } + + /** Returns the event-only mapper, accepting future AWS event properties. */ + public static ObjectMapper mapper() { + return MAPPER; + } +} diff --git a/custom-runtime/src/main/java/com/networknt/aws/lambda/Runtime.java b/custom-runtime/src/main/java/com/networknt/aws/lambda/Runtime.java index 0b99f2d..3d7713b 100644 --- a/custom-runtime/src/main/java/com/networknt/aws/lambda/Runtime.java +++ b/custom-runtime/src/main/java/com/networknt/aws/lambda/Runtime.java @@ -3,7 +3,6 @@ import com.amazonaws.services.lambda.runtime.RequestHandler; import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent; import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent; -import com.networknt.jackson.JacksonDefaults; import com.networknt.utility.NioUtils; import java.io.IOException; import java.lang.reflect.Method; @@ -11,14 +10,12 @@ import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.net.HttpURLConnection; -import tools.jackson.databind.DeserializationFeature; import tools.jackson.databind.ObjectMapper; public class Runtime { private static final String REQUEST_ID_HEADER = "lambda-runtime-aws-request-id"; - private static final ObjectMapper OBJECT_MAPPER = JacksonDefaults.builder() - .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES).build(); + private static final ObjectMapper OBJECT_MAPPER = LambdaRuntimeMappers.mapper(); public static void main(String[] args) throws IOException { while(true) { diff --git a/custom-runtime/src/test/java/com/networknt/aws/lambda/Jackson3RuntimeCompatibilityTest.java b/custom-runtime/src/test/java/com/networknt/aws/lambda/Jackson3RuntimeCompatibilityTest.java index 387d202..1c60aa0 100644 --- a/custom-runtime/src/test/java/com/networknt/aws/lambda/Jackson3RuntimeCompatibilityTest.java +++ b/custom-runtime/src/test/java/com/networknt/aws/lambda/Jackson3RuntimeCompatibilityTest.java @@ -2,8 +2,9 @@ import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent; import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent; +import com.networknt.jackson.JacksonDefaults; import org.junit.jupiter.api.Test; -import tools.jackson.databind.ObjectMapper; +import tools.jackson.databind.DeserializationFeature; import java.util.List; import java.util.Map; @@ -14,15 +15,17 @@ import static org.junit.jupiter.api.Assertions.assertTrue; class Jackson3RuntimeCompatibilityTest { - private ObjectMapper runtimeMapper() throws Exception { - var field = Runtime.class.getDeclaredField("OBJECT_MAPPER"); - field.setAccessible(true); - return (ObjectMapper) field.get(null); + @Test + void runtimeAndFrameworkMappersHaveSeparateUnknownPropertyPolicies() { + var runtimeMapper = LambdaRuntimeMappers.mapper(); + var frameworkMapper = JacksonDefaults.builder().build(); + assertFalse(runtimeMapper.isEnabled(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)); + assertTrue(frameworkMapper.isEnabled(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)); } @Test void bindsAwsGatewayEventsWithoutJackson2Types() throws Exception { - var mapper = runtimeMapper(); + var mapper = LambdaRuntimeMappers.mapper(); var request = mapper.readValue("{\"path\":\"/pets\",\"httpMethod\":\"POST\"," + "\"body\":\"雪\",\"isBase64Encoded\":true,\"requestContext\":{\"requestId\":\"fixture\"}," + "\"multiValueHeaders\":{\"X-Test\":[\"one\",\"two\"]}}", APIGatewayProxyRequestEvent.class); @@ -40,12 +43,12 @@ void preservesTheMeasuredResponseWireShape() throws Exception { .withMultiValueHeaders(Map.of("Set-Cookie", List.of("a=1", "b=2"))); assertEquals("{\"statusCode\":201,\"headers\":{\"Content-Type\":\"application/json\"}," + "\"multiValueHeaders\":{\"Set-Cookie\":[\"a=1\",\"b=2\"]},\"body\":\"雪\\nbody\"," - + "\"isBase64Encoded\":true}", runtimeMapper().writeValueAsString(response)); + + "\"isBase64Encoded\":true}", LambdaRuntimeMappers.mapper().writeValueAsString(response)); } @Test void retainsBuilderBindingAndNullPrimitiveHandling() throws Exception { - var mapper = runtimeMapper(); + var mapper = LambdaRuntimeMappers.mapper(); var response = mapper.readValue("{\"code\":null,\"message\":\"fixture error\"}", DefaultResponse.class); assertEquals(0, response.getCode()); assertEquals("{\"code\":0,\"message\":\"fixture error\"}", mapper.writeValueAsString(response)); @@ -56,7 +59,7 @@ void retainsBuilderBindingAndNullPrimitiveHandling() throws Exception { @Test void retainsPermissiveRuntimeBinding() throws Exception { - var mapper = runtimeMapper(); + var mapper = LambdaRuntimeMappers.mapper(); var request = mapper.readValue("{\"futureField\":1,\"isBase64Encoded\":false} {}", APIGatewayProxyRequestEvent.class); assertNull(request.getPath()); assertFalse(request.getIsBase64Encoded());