Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
127 changes: 127 additions & 0 deletions .github/workflows/jackson3-source-build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
name: Jackson 3 source build
on:
pull_request:
workflow_dispatch:
inputs:
source_refs:
description: JSON map of prerequisite repositories to full commit SHAs
type: string
default: '{}'
push:
branches: [master]
permissions:
contents: read
actions: read
env:
# Reviewed master policy and separately qualified candidate; both pins are immutable.
JACKSON3_QUALIFIER_REVISION: f0d2100f972eec765b2b276e7ace38922394f0a6
JACKSON3_CORE_REVISION: 97b2359b92ed3e1d05cac04f2da8760aa954e49d
jobs:
test:
runs-on: ubuntu-latest
timeout-minutes: 90
steps:
- uses: actions/checkout@v4
with:
path: consumer
ref: ${{ github.event.pull_request.head.sha || github.sha }}
- uses: actions/setup-java@v4
with:
distribution: temurin
java-version: '25'
- uses: actions/checkout@v4
with:
repository: networknt/light-4j
ref: ${{ env.JACKSON3_QUALIFIER_REVISION }}
path: qualification-policy
- name: Select qualified core and resolve remaining prerequisites
id: prerequisites
env:
GH_TOKEN: ${{ github.token }}
SOURCE_REFS: ${{ inputs.source_refs || '{}' }}
shell: bash
run: |
jq -e --arg core "$JACKSON3_CORE_REVISION" --argjson expected '["http-client","json-overlay","light-4j","light-rest-4j","openapi-parser","yaml-rule"]' '.["light-4j"] == $core and keys == $expected and all(.[]; type == "string" and test("^[0-9a-f]{40}$"))' consumer/ci/jackson3-source-refs.json >/dev/null
source_refs=$(jq -c --argjson overrides "$SOURCE_REFS" '. * $overrides' consumer/ci/jackson3-source-refs.json)
python3 qualification-policy/scripts/resolve_jackson3_consumer_sources.py \
--core-sha "$JACKSON3_CORE_REVISION" \
--repositories '{"json-overlay":null,"openapi-parser":null,"light-rest-4j":null}' --source-refs "$source_refs" \
--report core-qualification.json --github-output "$GITHUB_OUTPUT"
- uses: actions/checkout@v4
with:
repository: networknt/yaml-rule
ref: ${{ steps.prerequisites.outputs.yaml_rule }}
path: sources/yaml-rule
- uses: actions/checkout@v4
with:
repository: networknt/http-client
ref: ${{ steps.prerequisites.outputs.http_client }}
path: sources/http-client
- uses: actions/checkout@v4
with:
repository: networknt/light-4j
ref: ${{ steps.prerequisites.outputs.light_4j }}
path: sources/light-4j
- uses: actions/checkout@v4
with:
repository: networknt/json-overlay
ref: ${{ steps.prerequisites.outputs.json_overlay }}
path: sources/json-overlay
- uses: actions/checkout@v4
with:
repository: networknt/openapi-parser
ref: ${{ steps.prerequisites.outputs.openapi_parser }}
path: sources/openapi-parser
- uses: actions/checkout@v4
with:
repository: networknt/light-rest-4j
ref: ${{ steps.prerequisites.outputs.light_rest_4j }}
path: sources/light-rest-4j
- name: Record source commits
shell: bash
run: |
for source in consumer sources/*; do
echo "$source $(git -C "$source" rev-parse HEAD)"
done | tee source-commits.txt
for source in sources/*; do
printf '{"key":"%s","value":"%s"}\n' "${source##*/}" "$(git -C "$source" rev-parse HEAD)"
done | jq -s from_entries | tee source-refs.json
- name: Build prerequisites and run full tests
shell: bash
env:
GH_TOKEN: ${{ github.token }}
JACKSON3_REUSE_CORE_QUALIFICATION: 1
JACKSON3_CORE_SOURCE: ${{ github.workspace }}/sources/light-4j
JACKSON3_CORE_QUALIFICATION_REPORT: ${{ github.workspace }}/core-qualification.json
JACKSON3_MAVEN_REPO: ${{ runner.temp }}/m2
JACKSON3_YAML_RULE: ${{ github.workspace }}/sources/yaml-rule
JACKSON3_HTTP_CLIENT: ${{ github.workspace }}/sources/http-client
run: |
set -euo pipefail
bash qualification-policy/scripts/verify-jackson3-bootstrap.sh 2>&1 | tee source-build.log
mvn -f sources/yaml-rule/pom.xml "-Dmaven.repo.local=$JACKSON3_MAVEN_REPO" clean install 2>&1 | tee -a source-build.log
mvn -f sources/json-overlay/pom.xml "-Dmaven.repo.local=$JACKSON3_MAVEN_REPO" clean install 2>&1 | tee -a source-build.log
mvn -f sources/openapi-parser/pom.xml "-Dmaven.repo.local=$JACKSON3_MAVEN_REPO" clean install 2>&1 | tee -a source-build.log
mvn -f sources/light-rest-4j/pom.xml "-Dmaven.repo.local=$JACKSON3_MAVEN_REPO" clean install 2>&1 | tee -a source-build.log
mvn -f consumer/pom.xml "-Dmaven.repo.local=$JACKSON3_MAVEN_REPO" clean install 2>&1 | tee -a source-build.log
- name: Audit resolved artifacts, including releases and Jackson boundaries
env:
SOURCE_MAVEN_REPO: ${{ runner.temp }}/m2
shell: bash
run: |
set -euo pipefail
mvn -B -Dstyle.color=never -f consumer/pom.xml "-Dmaven.repo.local=$SOURCE_MAVEN_REPO" dependency:tree 2>&1 | tee resolved-dependencies.txt
python3 sources/light-4j/scripts/jackson3_artifact_audit.py --repository "$SOURCE_MAVEN_REPO" --tree resolved-dependencies.txt --report artifact-audit.json --require-jackson3-only
- uses: actions/upload-artifact@v4
if: always()
with:
name: source-build-reports
path: |
source-build.log
source-commits.txt
source-refs.json
resolved-dependencies.txt
artifact-audit.json
core-qualification.json
**/target/surefire-reports/**
**/target/*smoke*.log
20 changes: 20 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,23 @@ This is a Lambda extension that will collect the metrics of the Lambda function
`env-config` uses SnakeYAML Engine with YAML 1.2 Core resolution. See the
[configuration migration notes](env-config/README.md) for scalar changes,
duplicate-key handling, and dependency compatibility before rollout.

## Jackson 3 migration

This source line requires Jackson 3.2.3, json-schema-validator 3.0.8 and the Jackson 3 light-4j/http-client prerequisites. Public mapper and JsonNode types use tools.jackson; annotations retain com.fasterxml.jackson.annotation. Build those prerequisites before this reactor. Do not mix published Jackson 2 snapshot binaries with this source line.

Mappers use the shared Jackson2Defaults profile and preserve existing per-mapper overrides. Mappers are immutable; Java time and Optional support are built into Jackson 3. Classic SnakeYAML compatibility pins remain until the stack-wide cleanup.

Local qualification covers JVM tests and sanitized API Gateway/event and validation parity. It does not qualify deployed AWS execution or GraalVM native images.

### Reproducible Jackson 3 source build

`.github/workflows/jackson3-source-build.yml` builds the prerequisite source stack in an isolated Maven repository, reuses independently qualified full-core test evidence, and runs the prerequisite and full consumer suites. It pins reviewed policy `f0d2100f972eec765b2b276e7ace38922394f0a6` and candidate `97b2359b92ed3e1d05cac04f2da8760aa954e49d`; yaml-rule/http-client are selected from that verified evidence. The complete prerequisite closure is pinned in `ci/jackson3-source-refs.json`, initialized from the successful source-build manifest, with reviewed updates expressed as full SHAs; no default-branch or migration-branch lookup is used. Explicit `source_refs` replay overrides are merged into that map and remain subject to core qualification. The workflow uploads `source-refs.json`, `source-commits.txt`, `core-qualification.json` and Surefire reports. Core registration and workflow-token acceptance are merged in light-4j #2825 and #2826.

The optional `source_refs` workflow-dispatch input replays recorded full SHAs. GitHub enables dispatch only after the workflow exists on the default branch; before merge, reproduce from the recorded source checkouts locally. A green local suite is separate from a successful remote source-build run.

### Exact-source CI qualification

Consumer CI requires retained successful full-reactor evidence for the actual checked-out light-4j SHA and the same yaml-rule/http-client source commits. Core PR CI explicitly checks out the PR head, not its merge ref. Replay dispatches cannot qualify consumers. The attempt-specific evidence artifact records the actual checkout, prerequisite SHAs and test totals. A later pending, failed or cancelled run does not invalidate earlier valid evidence. All API pages and retained attempts are considered; missing or expired evidence blocks reuse. `core-qualification.json` records the accepted run and artifact.

The full dependency ledger is audited across all groups. Every NetworkNT release and snapshot must be source-installed, apart from the explicitly SHA-256-pinned Jackson 3 validator release. Known Jackson 2 yaml-rule and chaos-monkey releases are rejected even with a local-install marker. Jackson 2 dependencies in other groups are reported; ready migrations reject them except shared annotations.
8 changes: 8 additions & 0 deletions ci/jackson3-source-refs.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"http-client": "5ecee29dbb08d813512f5379c2e1b902d553c5d4",
"json-overlay": "e309fd63f95317067c9b13a4bed7ac16727bfea5",
"light-4j": "97b2359b92ed3e1d05cac04f2da8760aa954e49d",
"light-rest-4j": "a31955945036d9108b7e9bb72167415cc9cfc287",
"openapi-parser": "9ae849056e49f7c796fe28bfe6093fbe4847cc5c",
"yaml-rule": "c237cc3a25720f43e57bf571e25d5fa06687aa3b"
}
8 changes: 8 additions & 0 deletions custom-runtime/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,14 @@
<description>A custom runtime for GraalVM native image to improve code start performance</description>

<dependencies>
<dependency>
<groupId>tools.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<dependency>
<groupId>com.networknt</groupId>
<artifactId>jackson-defaults</artifactId>
</dependency>
<dependency>
<groupId>com.networknt</groupId>
<artifactId>utility</artifactId>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package com.networknt.aws.lambda;

import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder;
import tools.jackson.databind.annotation.JsonDeserialize;
import tools.jackson.databind.annotation.JsonPOJOBuilder;

@JsonDeserialize(builder = DefaultResponse.Builder.class)
public class DefaultResponse {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package com.networknt.aws.lambda;

import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder;
import tools.jackson.databind.annotation.JsonDeserialize;
import tools.jackson.databind.annotation.JsonPOJOBuilder;

@JsonDeserialize(builder = InvocationResponse.Builder.class)
public class InvocationResponse {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,22 @@
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.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;

import com.networknt.jackson.JacksonDefaults;
import com.networknt.utility.NioUtils;
import java.io.IOException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.net.HttpURLConnection;

import com.networknt.utility.NioUtils;
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 = new ObjectMapper()
.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
private static final ObjectMapper OBJECT_MAPPER = JacksonDefaults.builder()
.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES).build();

public static void main(String[] args) throws IOException {
while(true) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package com.networknt.aws.lambda;

import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent;
import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent;
import org.junit.jupiter.api.Test;
import tools.jackson.databind.ObjectMapper;

import java.util.List;
import java.util.Map;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNull;
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 bindsAwsGatewayEventsWithoutJackson2Types() throws Exception {
var mapper = runtimeMapper();
var request = mapper.readValue("{\"path\":\"/pets\",\"httpMethod\":\"POST\","
+ "\"body\":\"雪\",\"isBase64Encoded\":true,\"requestContext\":{\"requestId\":\"fixture\"},"
+ "\"multiValueHeaders\":{\"X-Test\":[\"one\",\"two\"]}}", APIGatewayProxyRequestEvent.class);
assertEquals("/pets", request.getPath());
assertEquals("fixture", request.getRequestContext().getRequestId());
assertEquals(List.of("one", "two"), request.getMultiValueHeaders().get("X-Test"));
assertTrue(request.getIsBase64Encoded());
assertEquals(request, mapper.readValue(mapper.writeValueAsString(request), APIGatewayProxyRequestEvent.class));
}

@Test
void preservesTheMeasuredResponseWireShape() throws Exception {
var response = new APIGatewayProxyResponseEvent().withStatusCode(201).withBody("雪\nbody")
.withIsBase64Encoded(true).withHeaders(Map.of("Content-Type", "application/json"))
.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));
}

@Test
void retainsBuilderBindingAndNullPrimitiveHandling() throws Exception {
var mapper = runtimeMapper();
var response = mapper.readValue("{\"code\":null,\"message\":\"fixture error\"}", DefaultResponse.class);
assertEquals(0, response.getCode());
assertEquals("{\"code\":0,\"message\":\"fixture error\"}", mapper.writeValueAsString(response));
var invocation = mapper.readValue("{\"requestId\":\"fixture\",\"event\":{\"path\":\"/pets\"}}", InvocationResponse.class);
assertEquals("fixture", invocation.getRequestId());
assertEquals("/pets", invocation.getEvent().getPath());
}

@Test
void retainsPermissiveRuntimeBinding() throws Exception {
var mapper = runtimeMapper();
var request = mapper.readValue("{\"futureField\":1,\"isBase64Encoded\":false} {}", APIGatewayProxyRequestEvent.class);
assertNull(request.getPath());
assertFalse(request.getIsBase64Encoded());
}
}
7 changes: 6 additions & 1 deletion env-config/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@
<description>Environmental Config for Lambda for seldom changed properties</description>

<dependencies>
<dependency>
<groupId>com.networknt</groupId>
<artifactId>config</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.amazonaws</groupId>
<artifactId>aws-lambda-java-core</artifactId>
Expand All @@ -40,7 +45,7 @@
<artifactId>aws-lambda-java-events</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<groupId>tools.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<dependency>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
package com.networknt.aws.lambda;

import com.networknt.jackson.JacksonDefaults;

import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent;
import com.fasterxml.jackson.databind.ObjectMapper;
import tools.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;

import java.util.Map;

public class ConfigurationTest {
ObjectMapper objectMapper = new ObjectMapper();
ObjectMapper objectMapper = JacksonDefaults.mapper();
@Test
@Disabled
public void thatConfigurationValuesAreLoaded() throws Exception {
Expand Down
6 changes: 5 additions & 1 deletion lambda-interceptor/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@
<description>The interceptor to pre-process request and post-process response when light-proxy is used</description>

<dependencies>
<dependency>
<groupId>com.networknt</groupId>
<artifactId>jackson-defaults</artifactId>
</dependency>
<dependency>
<groupId>com.networknt</groupId>
<artifactId>slf4j-logback</artifactId>
Expand All @@ -44,7 +48,7 @@
<artifactId>aws-lambda-java-events</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<groupId>tools.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<dependency>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,13 @@
import com.amazonaws.services.lambda.runtime.LambdaLogger;
import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent;
import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.networknt.jackson.JacksonDefaults;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import tools.jackson.databind.ObjectMapper;

public class LightLambdaInterceptor {
private static ObjectMapper objectMapper = new ObjectMapper();
private static ObjectMapper objectMapper = JacksonDefaults.mapper();
private static final Logger logger = LoggerFactory.getLogger(LightLambdaInterceptor.class);

public APIGatewayProxyRequestEvent interceptRequest(final APIGatewayProxyRequestEvent input, final Context context) {
Expand Down
2 changes: 1 addition & 1 deletion lambda-invoker/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@
<artifactId>sts</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<groupId>tools.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<dependency>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package com.networknt.aws.lambda;

import com.fasterxml.jackson.core.type.TypeReference;
import tools.jackson.core.type.TypeReference;
import com.networknt.config.Config;
import com.networknt.config.ConfigException;
import com.networknt.config.schema.ConfigSchema;
Expand Down
2 changes: 1 addition & 1 deletion lambda-validator/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@
<artifactId>aws-lambda-java-events</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<groupId>tools.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<dependency>
Expand Down
Loading
Loading