diff --git a/.github/workflows/jackson3-source-build.yml b/.github/workflows/jackson3-source-build.yml new file mode 100644 index 00000000..56895886 --- /dev/null +++ b/.github/workflows/jackson3-source-build.yml @@ -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 diff --git a/README.md b/README.md index 1596b086..cc005a18 100644 --- a/README.md +++ b/README.md @@ -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. diff --git a/ci/jackson3-source-refs.json b/ci/jackson3-source-refs.json new file mode 100644 index 00000000..399f89a4 --- /dev/null +++ b/ci/jackson3-source-refs.json @@ -0,0 +1,8 @@ +{ + "http-client": "5ecee29dbb08d813512f5379c2e1b902d553c5d4", + "json-overlay": "e309fd63f95317067c9b13a4bed7ac16727bfea5", + "light-4j": "97b2359b92ed3e1d05cac04f2da8760aa954e49d", + "light-rest-4j": "a31955945036d9108b7e9bb72167415cc9cfc287", + "openapi-parser": "9ae849056e49f7c796fe28bfe6093fbe4847cc5c", + "yaml-rule": "c237cc3a25720f43e57bf571e25d5fa06687aa3b" +} diff --git a/custom-runtime/pom.xml b/custom-runtime/pom.xml index 4c1f60a2..962be8fe 100644 --- a/custom-runtime/pom.xml +++ b/custom-runtime/pom.xml @@ -31,6 +31,14 @@ A custom runtime for GraalVM native image to improve code start performance + + tools.jackson.core + jackson-databind + + + com.networknt + jackson-defaults + com.networknt utility diff --git a/custom-runtime/src/main/java/com/networknt/aws/lambda/DefaultResponse.java b/custom-runtime/src/main/java/com/networknt/aws/lambda/DefaultResponse.java index df11e128..5389aa7b 100644 --- a/custom-runtime/src/main/java/com/networknt/aws/lambda/DefaultResponse.java +++ b/custom-runtime/src/main/java/com/networknt/aws/lambda/DefaultResponse.java @@ -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 { diff --git a/custom-runtime/src/main/java/com/networknt/aws/lambda/InvocationResponse.java b/custom-runtime/src/main/java/com/networknt/aws/lambda/InvocationResponse.java index b3464d39..bcdccc24 100644 --- a/custom-runtime/src/main/java/com/networknt/aws/lambda/InvocationResponse.java +++ b/custom-runtime/src/main/java/com/networknt/aws/lambda/InvocationResponse.java @@ -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 { 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 b111f618..0b99f2d2 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,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) { 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 new file mode 100644 index 00000000..387d202a --- /dev/null +++ b/custom-runtime/src/test/java/com/networknt/aws/lambda/Jackson3RuntimeCompatibilityTest.java @@ -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()); + } +} diff --git a/env-config/pom.xml b/env-config/pom.xml index 5bda6212..6fea8960 100644 --- a/env-config/pom.xml +++ b/env-config/pom.xml @@ -31,6 +31,11 @@ Environmental Config for Lambda for seldom changed properties + + com.networknt + config + test + com.amazonaws aws-lambda-java-core @@ -40,7 +45,7 @@ aws-lambda-java-events - com.fasterxml.jackson.core + tools.jackson.core jackson-databind diff --git a/env-config/src/test/java/com/networknt/aws/lambda/ConfigurationTest.java b/env-config/src/test/java/com/networknt/aws/lambda/ConfigurationTest.java index a3c6f346..7cf40dd0 100644 --- a/env-config/src/test/java/com/networknt/aws/lambda/ConfigurationTest.java +++ b/env-config/src/test/java/com/networknt/aws/lambda/ConfigurationTest.java @@ -1,7 +1,9 @@ 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; @@ -9,7 +11,7 @@ import java.util.Map; public class ConfigurationTest { - ObjectMapper objectMapper = new ObjectMapper(); + ObjectMapper objectMapper = JacksonDefaults.mapper(); @Test @Disabled public void thatConfigurationValuesAreLoaded() throws Exception { diff --git a/lambda-interceptor/pom.xml b/lambda-interceptor/pom.xml index 05306a2c..e86716e5 100644 --- a/lambda-interceptor/pom.xml +++ b/lambda-interceptor/pom.xml @@ -31,6 +31,10 @@ The interceptor to pre-process request and post-process response when light-proxy is used + + com.networknt + jackson-defaults + com.networknt slf4j-logback @@ -44,7 +48,7 @@ aws-lambda-java-events - com.fasterxml.jackson.core + tools.jackson.core jackson-databind diff --git a/lambda-interceptor/src/main/java/com/networknt/aws/lambda/LightLambdaInterceptor.java b/lambda-interceptor/src/main/java/com/networknt/aws/lambda/LightLambdaInterceptor.java index 70be294d..ccf6b2be 100644 --- a/lambda-interceptor/src/main/java/com/networknt/aws/lambda/LightLambdaInterceptor.java +++ b/lambda-interceptor/src/main/java/com/networknt/aws/lambda/LightLambdaInterceptor.java @@ -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) { diff --git a/lambda-invoker/pom.xml b/lambda-invoker/pom.xml index 5ea049cc..76be8a04 100644 --- a/lambda-invoker/pom.xml +++ b/lambda-invoker/pom.xml @@ -77,7 +77,7 @@ sts - com.fasterxml.jackson.core + tools.jackson.core jackson-databind diff --git a/lambda-invoker/src/main/java/com/networknt/aws/lambda/LambdaInvokerConfig.java b/lambda-invoker/src/main/java/com/networknt/aws/lambda/LambdaInvokerConfig.java index f10a5819..d2f39cd0 100644 --- a/lambda-invoker/src/main/java/com/networknt/aws/lambda/LambdaInvokerConfig.java +++ b/lambda-invoker/src/main/java/com/networknt/aws/lambda/LambdaInvokerConfig.java @@ -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; diff --git a/lambda-validator/pom.xml b/lambda-validator/pom.xml index bebc2819..6b7c7d68 100644 --- a/lambda-validator/pom.xml +++ b/lambda-validator/pom.xml @@ -60,7 +60,7 @@ aws-lambda-java-events - com.fasterxml.jackson.core + tools.jackson.core jackson-databind diff --git a/lambda-validator/src/main/java/com/networknt/aws/lambda/LambdaSchemaValidator.java b/lambda-validator/src/main/java/com/networknt/aws/lambda/LambdaSchemaValidator.java index 0f341a91..f9274e23 100644 --- a/lambda-validator/src/main/java/com/networknt/aws/lambda/LambdaSchemaValidator.java +++ b/lambda-validator/src/main/java/com/networknt/aws/lambda/LambdaSchemaValidator.java @@ -2,7 +2,7 @@ import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent; import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent; -import com.fasterxml.jackson.core.JsonProcessingException; +import tools.jackson.core.JacksonException; import com.networknt.aws.lambda.validator.RequestValidator; import com.networknt.aws.lambda.validator.SchemaValidator; import com.networknt.config.Config; @@ -48,7 +48,7 @@ public LambdaSchemaValidator() { String openapiString = Config.getInstance().getMapper().writeValueAsString(openapi); if(logger.isTraceEnabled()) logger.trace("OpenApiMiddleware openapiString: " + openapiString); helper = new OpenApiHelper(openapiString); - } catch (JsonProcessingException e) { + } catch (JacksonException e) { logger.error("merge specification failed"); throw new RuntimeException("merge specification failed"); } diff --git a/lambda-validator/src/test/java/com/networknt/aws/lambda/Jackson3LambdaValidationRegressionTest.java b/lambda-validator/src/test/java/com/networknt/aws/lambda/Jackson3LambdaValidationRegressionTest.java new file mode 100644 index 00000000..6db1cf7b --- /dev/null +++ b/lambda-validator/src/test/java/com/networknt/aws/lambda/Jackson3LambdaValidationRegressionTest.java @@ -0,0 +1,44 @@ +package com.networknt.aws.lambda; + +import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent; +import com.networknt.config.Config; +import org.junit.jupiter.api.Test; + +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class Jackson3LambdaValidationRegressionTest { + @Test + void validatesAnEventBoundByTheFrameworkMapper() { + var request = Config.getInstance().getMapper().readValue( + "{\"path\":\"/pets\",\"httpMethod\":\"GET\",\"queryStringParameters\":{\"includeCode\":\"true\"}}", + APIGatewayProxyRequestEvent.class); + assertNull(new LambdaSchemaValidator().validateRequest(request)); + } + + @Test + void keepsMalformedBodyErrorsHandled() { + var request = new APIGatewayProxyRequestEvent().withPath("/pets").withHttpMethod("POST") + .withHeaders(Map.of("Content-Type", "application/json")).withBody("{"); + var response = new LambdaSchemaValidator().validateRequest(request); + assertNotNull(response); + assertEquals(400, response.getStatusCode()); + assertTrue(response.getBody().contains("ERR10015"), response.getBody()); + } + + @Test + void keepsValidationMessagesAndMethodErrorsStable() { + var validator = new LambdaSchemaValidator(); + var request = new APIGatewayProxyRequestEvent().withPath("/pets").withHttpMethod("GET") + .withQueryStringParameters(Map.of("includeCode", "wrong")); + var response = validator.validateRequest(request); + assertEquals(400, response.getStatusCode()); + assertTrue(response.getBody().contains("/includeCode: string found, boolean expected"), response.getBody()); + request.setHttpMethod("PATCH"); + assertEquals(405, validator.validateRequest(request).getStatusCode()); + } +} diff --git a/lambda-validator/src/test/java/com/networknt/aws/lambda/LambdaSchemaValidatorTest.java b/lambda-validator/src/test/java/com/networknt/aws/lambda/LambdaSchemaValidatorTest.java index bf590f3e..f1e4bded 100644 --- a/lambda-validator/src/test/java/com/networknt/aws/lambda/LambdaSchemaValidatorTest.java +++ b/lambda-validator/src/test/java/com/networknt/aws/lambda/LambdaSchemaValidatorTest.java @@ -1,9 +1,11 @@ package com.networknt.aws.lambda; +import com.networknt.jackson.JacksonDefaults; + import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent; import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent; import com.fasterxml.jackson.annotation.JsonIgnore; -import com.fasterxml.jackson.databind.ObjectMapper; +import tools.jackson.databind.ObjectMapper; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Disabled; @@ -13,7 +15,7 @@ import java.util.Map; public class LambdaSchemaValidatorTest { - ObjectMapper objectMapper = new ObjectMapper(); + ObjectMapper objectMapper = JacksonDefaults.mapper(); private final LambdaSchemaValidator validator = new LambdaSchemaValidator(); APIGatewayProxyRequestEvent requestEvent; diff --git a/pom.xml b/pom.xml index cc7bd09f..43c13982 100644 --- a/pom.xml +++ b/pom.xml @@ -99,7 +99,7 @@ ${java.version} UTF-8 2.4.2-SNAPSHOT - 2.22.3 + 3.2.3 2.0.20 1.4.0 3.16.1 @@ -116,7 +116,7 @@ 2.4.4.Final 3.27.7 2.23.0 - 2.0.8 + 3.0.8 3.12.0 1.7.0 3.6.0 @@ -139,6 +139,11 @@ + + com.networknt + jackson-defaults + ${version.light-4j} + org.junit junit-bom @@ -147,7 +152,7 @@ import - com.fasterxml.jackson + tools.jackson jackson-bom ${version.jackson} pom diff --git a/request-handler/pom.xml b/request-handler/pom.xml index 77f13f78..ea81c974 100644 --- a/request-handler/pom.xml +++ b/request-handler/pom.xml @@ -52,7 +52,7 @@ aws-lambda-java-events - com.fasterxml.jackson.core + tools.jackson.core jackson-databind diff --git a/schema-validator/pom.xml b/schema-validator/pom.xml index 83e0bc1a..30a73650 100644 --- a/schema-validator/pom.xml +++ b/schema-validator/pom.xml @@ -60,7 +60,7 @@ aws-lambda-java-events - com.fasterxml.jackson.core + tools.jackson.core jackson-databind diff --git a/schema-validator/src/main/java/com/networknt/aws/lambda/validator/RequestValidator.java b/schema-validator/src/main/java/com/networknt/aws/lambda/validator/RequestValidator.java index fc786188..f08202ef 100644 --- a/schema-validator/src/main/java/com/networknt/aws/lambda/validator/RequestValidator.java +++ b/schema-validator/src/main/java/com/networknt/aws/lambda/validator/RequestValidator.java @@ -1,8 +1,8 @@ package com.networknt.aws.lambda.validator; import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.node.TextNode; +import tools.jackson.databind.JsonNode; +import tools.jackson.databind.node.StringNode; import com.networknt.config.Config; import com.networknt.jsonoverlay.Overlay; import com.networknt.oas.model.Parameter; @@ -144,7 +144,7 @@ private Status validatePathParameters(final APIGatewayProxyRequestEvent requestE LOG.info("Path parameter cannot be decoded, it will be used directly"); } - return schemaValidator.validate(new TextNode(paramValue), Overlay.toJson((SchemaImpl)(parameter.get().getSchema())), paramName); + return schemaValidator.validate(new StringNode(paramValue), Overlay.toJson((SchemaImpl)(parameter.get().getSchema())), paramName); } } return status; @@ -179,7 +179,7 @@ private Status validateQueryParameter(final APIGatewayProxyRequestEvent requestE return new Status(VALIDATOR_REQUEST_PARAMETER_QUERY_MISSING, queryParameter.getName(), openApiOperation.getPathString().original()); } } else { - return schemaValidator.validate(new TextNode(queryParameterValue), Overlay.toJson((SchemaImpl)queryParameter.getSchema()), queryParameter.getName()); + return schemaValidator.validate(new StringNode(queryParameterValue), Overlay.toJson((SchemaImpl)queryParameter.getSchema()), queryParameter.getName()); } return null; } @@ -233,7 +233,7 @@ private Status validateHeader(final APIGatewayProxyRequestEvent requestEvent, return new Status(VALIDATOR_REQUEST_PARAMETER_HEADER_MISSING, headerParameter.getName(), openApiOperation.getPathString().original()); } } else { - return headerParameter.getSchema() != null ? schemaValidator.validate(new TextNode(headerValue.get()), Overlay.toJson((SchemaImpl)headerParameter.getSchema()), headerParameter.getName()) : null; + return headerParameter.getSchema() != null ? schemaValidator.validate(new StringNode(headerValue.get()), Overlay.toJson((SchemaImpl)headerParameter.getSchema()), headerParameter.getName()) : null; } return null; } @@ -249,7 +249,7 @@ private ValidationResult validateDeserializedValues(final APIGatewayProxyRequest if (null==deserializedValue) { validationResult.addSkipped(p); }else { - Status s = schemaValidator.validate(new TextNode(deserializedValue), Overlay.toJson((SchemaImpl)(p.getSchema())), p.getName()); + Status s = schemaValidator.validate(new StringNode(deserializedValue), Overlay.toJson((SchemaImpl)(p.getSchema())), p.getName()); validationResult.addStatus(s); } }); diff --git a/schema-validator/src/main/java/com/networknt/aws/lambda/validator/SchemaValidator.java b/schema-validator/src/main/java/com/networknt/aws/lambda/validator/SchemaValidator.java index a5e45217..52ed6493 100644 --- a/schema-validator/src/main/java/com/networknt/aws/lambda/validator/SchemaValidator.java +++ b/schema-validator/src/main/java/com/networknt/aws/lambda/validator/SchemaValidator.java @@ -16,8 +16,8 @@ package com.networknt.aws.lambda.validator; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.node.ObjectNode; +import tools.jackson.databind.JsonNode; +import tools.jackson.databind.node.ObjectNode; import com.networknt.jsonoverlay.Overlay; import com.networknt.oas.model.OpenApi3; import com.networknt.oas.model.impl.OpenApi3Impl; diff --git a/schema-validator/src/test/java/com/networknt/aws/lambda/validator/SchemaValidatorTest.java b/schema-validator/src/test/java/com/networknt/aws/lambda/validator/SchemaValidatorTest.java index 33abbbcf..a257a181 100644 --- a/schema-validator/src/test/java/com/networknt/aws/lambda/validator/SchemaValidatorTest.java +++ b/schema-validator/src/test/java/com/networknt/aws/lambda/validator/SchemaValidatorTest.java @@ -1,9 +1,11 @@ package com.networknt.aws.lambda.validator; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.node.NullNode; -import com.fasterxml.jackson.databind.node.TextNode; +import com.networknt.jackson.JacksonDefaults; + +import tools.jackson.databind.JsonNode; +import tools.jackson.databind.ObjectMapper; +import tools.jackson.databind.node.NullNode; +import tools.jackson.databind.node.StringNode; import com.networknt.status.Status; import org.junit.jupiter.api.Test; @@ -14,7 +16,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue; public class SchemaValidatorTest { - private static final ObjectMapper MAPPER = new ObjectMapper(); + private static final ObjectMapper MAPPER = JacksonDefaults.mapper(); @Test void shouldConstructWithoutAnApi() { @@ -26,8 +28,8 @@ void shouldKeepLooseAndStrictTypeProfilesSeparate() throws Exception { SchemaValidator validator = new SchemaValidator(); JsonNode schema = MAPPER.readTree("{\"type\":\"integer\"}"); - assertNull(validator.validate(new TextNode("42"), schema, true, false)); - assertNotNull(validator.validate(new TextNode("42"), schema, false, false)); + assertNull(validator.validate(new StringNode("42"), schema, true, false)); + assertNotNull(validator.validate(new StringNode("42"), schema, false, false)); } @Test @@ -56,7 +58,7 @@ void shouldAllowStandardNonDefaultDialects() throws Exception { SchemaValidator validator = new SchemaValidator(); JsonNode schema = MAPPER.readTree("{\"$schema\":\"http://json-schema.org/draft-07/schema#\",\"type\":\"integer\"}"); - Status status = validator.validate(new TextNode("invalid"), schema, false, false); + Status status = validator.validate(new StringNode("invalid"), schema, false, false); assertNotNull(status); assertEquals(SchemaValidator.VALIDATOR_SCHEMA, status.getCode()); @@ -67,7 +69,7 @@ void shouldFailClosedWhenSchemaCannotBeBuilt() throws Exception { SchemaValidator validator = new SchemaValidator(); JsonNode schema = MAPPER.readTree("{\"type\":\"string\",\"pattern\":\"[\"}"); - Status status = validator.validate(new TextNode("value"), schema, false, false); + Status status = validator.validate(new StringNode("value"), schema, false, false); assertNotNull(status); assertEquals(SchemaValidator.VALIDATOR_SCHEMA_INVALID_JSON, status.getCode()); diff --git a/scope-verifier/pom.xml b/scope-verifier/pom.xml index f32f475a..578cbdb6 100644 --- a/scope-verifier/pom.xml +++ b/scope-verifier/pom.xml @@ -44,7 +44,7 @@ aws-lambda-java-events - com.fasterxml.jackson.core + tools.jackson.core jackson-databind diff --git a/scope-verifier/src/test/java/com/networknt/aws/lambda/ScopeVerifierTest.java b/scope-verifier/src/test/java/com/networknt/aws/lambda/ScopeVerifierTest.java index 8c902480..e686d8dc 100644 --- a/scope-verifier/src/test/java/com/networknt/aws/lambda/ScopeVerifierTest.java +++ b/scope-verifier/src/test/java/com/networknt/aws/lambda/ScopeVerifierTest.java @@ -1,8 +1,10 @@ package com.networknt.aws.lambda; +import com.networknt.jackson.JacksonDefaults; + import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent; import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent; -import com.fasterxml.jackson.databind.ObjectMapper; +import tools.jackson.databind.ObjectMapper; import com.networknt.utility.StringUtils; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; @@ -11,7 +13,7 @@ import java.util.List; public class ScopeVerifierTest { - ObjectMapper objectMapper = new ObjectMapper(); + ObjectMapper objectMapper = JacksonDefaults.mapper(); @Test public void testSuccessRequest() { ScopeVerifier verifier = new ScopeVerifier();