From 69a146f2f9904349cc7b01867846fd69af26e769 Mon Sep 17 00:00:00 2001 From: PJ Fanning Date: Thu, 3 Sep 2026 13:59:22 +0100 Subject: [PATCH] support "unlimited" for the Jackson stream-read constraints Motivation: Review on #3515 preferred an explicit keyword over -1 as a magic number for unlimited. Among the configuration listed in #3513, the Jackson read constraints max-document-length and max-token-count document -1 as meaning unlimited but only accept numbers. Modification: JacksonObjectMapperProvider in serialization-jackson and serialization-jackson3 reads "unlimited" as -1 for read.max-document-length and read.max-token-count, and the reference.conf defaults are written as `unlimited`. A negative number such as -1 is still accepted. Result: `max-document-length = unlimited` and `max-token-count = unlimited` work; the effective defaults are unchanged. Tests: - sbt "serialization-jackson/testOnly org.apache.pekko.serialization.jackson.*" - 129 passed - sbt "serialization-jackson3/testOnly org.apache.pekko.serialization.jackson3.*" - 127 passed - sbt "serialization-jackson/scalafmtCheckAll" "serialization-jackson3/scalafmtCheckAll" - clean References: Refs #3513, Refs #3515 --- .../src/main/resources/reference.conf | 8 ++++---- .../jackson/JacksonObjectMapperProvider.scala | 9 +++++++-- .../jackson/JacksonFactorySpec.scala | 15 +++++++++++++++ .../src/main/resources/reference.conf | 8 ++++---- .../jackson3/JacksonObjectMapperProvider.scala | 11 ++++++++--- .../jackson3/JacksonFactorySpec.scala | 15 +++++++++++++++ 6 files changed, 53 insertions(+), 13 deletions(-) diff --git a/serialization-jackson/src/main/resources/reference.conf b/serialization-jackson/src/main/resources/reference.conf index 5ad52a71100..687103b797b 100644 --- a/serialization-jackson/src/main/resources/reference.conf +++ b/serialization-jackson/src/main/resources/reference.conf @@ -56,10 +56,10 @@ pekko.serialization.jackson { max-number-length = 1000 max-string-length = 20000000 max-name-length = 50000 - # max-document-length of -1 means unlimited - max-document-length = -1 - # max-token-count of -1 means unlimited - max-token-count = -1 + # max-document-length of `unlimited` (or a negative number such as -1) means unlimited + max-document-length = unlimited + # max-token-count of `unlimited` (or a negative number such as -1) means unlimited + max-token-count = unlimited } write { diff --git a/serialization-jackson/src/main/scala/org/apache/pekko/serialization/jackson/JacksonObjectMapperProvider.scala b/serialization-jackson/src/main/scala/org/apache/pekko/serialization/jackson/JacksonObjectMapperProvider.scala index e2d665ef31e..688e4a3ad8a 100644 --- a/serialization-jackson/src/main/scala/org/apache/pekko/serialization/jackson/JacksonObjectMapperProvider.scala +++ b/serialization-jackson/src/main/scala/org/apache/pekko/serialization/jackson/JacksonObjectMapperProvider.scala @@ -82,6 +82,11 @@ object JacksonObjectMapperProvider extends ExtensionId[JacksonObjectMapperProvid baseConf } + // "unlimited" is accepted as a synonym for -1 in the constraints that treat -1 as no limit + private def getLongOrUnlimited(config: Config, path: String): Long = + if (config.getString(path) == "unlimited") -1L + else config.getLong(path) + private def createJsonFactory( bindingName: String, objectMapperFactory: JacksonObjectMapperFactory, @@ -93,8 +98,8 @@ object JacksonObjectMapperProvider extends ExtensionId[JacksonObjectMapperProvid .maxNumberLength(config.getInt("read.max-number-length")) .maxStringLength(config.getInt("read.max-string-length")) .maxNameLength(config.getInt("read.max-name-length")) - .maxDocumentLength(config.getLong("read.max-document-length")) - .maxTokenCount(config.getLong("read.max-token-count")) + .maxDocumentLength(getLongOrUnlimited(config, "read.max-document-length")) + .maxTokenCount(getLongOrUnlimited(config, "read.max-token-count")) .build() val streamWriteConstraints = StreamWriteConstraints.builder() diff --git a/serialization-jackson/src/test/scala/org/apache/pekko/serialization/jackson/JacksonFactorySpec.scala b/serialization-jackson/src/test/scala/org/apache/pekko/serialization/jackson/JacksonFactorySpec.scala index 9192ee4927a..4619f865691 100644 --- a/serialization-jackson/src/test/scala/org/apache/pekko/serialization/jackson/JacksonFactorySpec.scala +++ b/serialization-jackson/src/test/scala/org/apache/pekko/serialization/jackson/JacksonFactorySpec.scala @@ -68,6 +68,21 @@ class JacksonFactorySpec extends TestKit(ActorSystem("JacksonFactorySpec")) streamReadConstraints.getMaxTokenCount shouldEqual maxTokenCount } + "support unlimited as a StreamReadConstraints value" in { + val bindingName = "testJackson" + val config = ConfigFactory.parseString( + s"""pekko.serialization.jackson.read.max-document-length=unlimited + |pekko.serialization.jackson.read.max-token-count=unlimited + |""".stripMargin) + .withFallback(defaultConfig) + val jacksonConfig = JacksonObjectMapperProvider.configForBinding(bindingName, config) + val mapper = JacksonObjectMapperProvider.createObjectMapper( + bindingName, None, objectMapperFactory, jacksonConfig, dynamicAccess, None) + val streamReadConstraints = mapper.getFactory.streamReadConstraints() + streamReadConstraints.getMaxDocumentLength shouldEqual -1L + streamReadConstraints.getMaxTokenCount shouldEqual -1L + } + "support StreamWriteConstraints" in { val bindingName = "testJackson" val maxNestingDepth = 54321 diff --git a/serialization-jackson3/src/main/resources/reference.conf b/serialization-jackson3/src/main/resources/reference.conf index 0a94af43f24..d1f7be34ba5 100644 --- a/serialization-jackson3/src/main/resources/reference.conf +++ b/serialization-jackson3/src/main/resources/reference.conf @@ -53,10 +53,10 @@ pekko.serialization.jackson3 { max-number-length = 1000 max-string-length = 20000000 max-name-length = 50000 - # max-document-length of -1 means unlimited - max-document-length = -1 - # max-token-count of -1 means unlimited - max-token-count = -1 + # max-document-length of `unlimited` (or a negative number such as -1) means unlimited + max-document-length = unlimited + # max-token-count of `unlimited` (or a negative number such as -1) means unlimited + max-token-count = unlimited } write { diff --git a/serialization-jackson3/src/main/scala/org/apache/pekko/serialization/jackson3/JacksonObjectMapperProvider.scala b/serialization-jackson3/src/main/scala/org/apache/pekko/serialization/jackson3/JacksonObjectMapperProvider.scala index 7dbe434ab7a..acbbde5de30 100644 --- a/serialization-jackson3/src/main/scala/org/apache/pekko/serialization/jackson3/JacksonObjectMapperProvider.scala +++ b/serialization-jackson3/src/main/scala/org/apache/pekko/serialization/jackson3/JacksonObjectMapperProvider.scala @@ -75,6 +75,11 @@ object JacksonObjectMapperProvider extends ExtensionId[JacksonObjectMapperProvid baseConf } + // "unlimited" is accepted as a synonym for -1 in the constraints that treat -1 as no limit + private def getLongOrUnlimited(config: Config, path: String): Long = + if (config.getString(path) == "unlimited") -1L + else config.getLong(path) + private[pekko] def createJsonFactory( bindingName: String, objectMapperFactory: JacksonObjectMapperFactory, @@ -86,8 +91,8 @@ object JacksonObjectMapperProvider extends ExtensionId[JacksonObjectMapperProvid .maxNumberLength(config.getInt("read.max-number-length")) .maxStringLength(config.getInt("read.max-string-length")) .maxNameLength(config.getInt("read.max-name-length")) - .maxDocumentLength(config.getLong("read.max-document-length")) - .maxTokenCount(config.getLong("read.max-token-count")) + .maxDocumentLength(getLongOrUnlimited(config, "read.max-document-length")) + .maxTokenCount(getLongOrUnlimited(config, "read.max-token-count")) .build() val streamWriteConstraints = StreamWriteConstraints.builder() @@ -159,7 +164,7 @@ object JacksonObjectMapperProvider extends ExtensionId[JacksonObjectMapperProvid .maxNumberLength(config.getInt("read.max-number-length")) .maxStringLength(config.getInt("read.max-string-length")) .maxNameLength(config.getInt("read.max-name-length")) - .maxDocumentLength(config.getLong("read.max-document-length")) + .maxDocumentLength(getLongOrUnlimited(config, "read.max-document-length")) .build() val streamWriteConstraints = StreamWriteConstraints.builder() diff --git a/serialization-jackson3/src/test/scala/org/apache/pekko/serialization/jackson3/JacksonFactorySpec.scala b/serialization-jackson3/src/test/scala/org/apache/pekko/serialization/jackson3/JacksonFactorySpec.scala index 808c2973fac..8b332a37fb8 100644 --- a/serialization-jackson3/src/test/scala/org/apache/pekko/serialization/jackson3/JacksonFactorySpec.scala +++ b/serialization-jackson3/src/test/scala/org/apache/pekko/serialization/jackson3/JacksonFactorySpec.scala @@ -67,6 +67,21 @@ class JacksonFactorySpec extends TestKit(ActorSystem("JacksonFactorySpec")) streamReadConstraints.getMaxTokenCount shouldEqual maxTokenCount } + "support unlimited as a StreamReadConstraints value" in { + val bindingName = "testJackson" + val config = ConfigFactory.parseString( + s"""pekko.serialization.jackson3.read.max-document-length=unlimited + |pekko.serialization.jackson3.read.max-token-count=unlimited + |""".stripMargin) + .withFallback(defaultConfig) + val jacksonConfig = JacksonObjectMapperProvider.configForBinding(bindingName, config) + val factory = JacksonObjectMapperProvider.createJsonFactory( + bindingName, objectMapperFactory, jacksonConfig, None) + val streamReadConstraints = factory.streamReadConstraints() + streamReadConstraints.getMaxDocumentLength shouldEqual -1L + streamReadConstraints.getMaxTokenCount shouldEqual -1L + } + "support StreamWriteConstraints" in { val bindingName = "testJackson" val maxNestingDepth = 54321