diff --git a/src/main/java/com/fasterxml/jackson/core/JsonFactory.java b/src/main/java/com/fasterxml/jackson/core/JsonFactory.java
index 3fa4240070..c02ab9254d 100644
--- a/src/main/java/com/fasterxml/jackson/core/JsonFactory.java
+++ b/src/main/java/com/fasterxml/jackson/core/JsonFactory.java
@@ -284,6 +284,14 @@ public static int collectDefaults() {
*/
protected StreamReadConstraints _streamReadConstraints;
+ /**
+ * Container for configuration values used when handling errorneous token inputs.
+ *
+ * @see ErrorReportConfiguration
+ * @since 2.16
+ */
+ protected ErrorReportConfiguration _errorReportConfiguration;
+
/**
* Write constraints to use for {@link JsonGenerator}s constructed using
* this factory.
@@ -361,6 +369,7 @@ public JsonFactory(ObjectCodec oc) {
_quoteChar = DEFAULT_QUOTE_CHAR;
_streamReadConstraints = StreamReadConstraints.defaults();
_streamWriteConstraints = StreamWriteConstraints.defaults();
+ _errorReportConfiguration = ErrorReportConfiguration.defaults();
_generatorDecorators = null;
}
@@ -385,6 +394,7 @@ protected JsonFactory(JsonFactory src, ObjectCodec codec)
_generatorDecorators = _copy(src._generatorDecorators);
_streamReadConstraints = Objects.requireNonNull(src._streamReadConstraints);
_streamWriteConstraints = Objects.requireNonNull(src._streamWriteConstraints);
+ _errorReportConfiguration = Objects.requireNonNull(src._errorReportConfiguration);
// JSON-specific
_characterEscapes = src._characterEscapes;
@@ -412,6 +422,7 @@ public JsonFactory(JsonFactoryBuilder b) {
_generatorDecorators = _copy(b._generatorDecorators);
_streamReadConstraints = Objects.requireNonNull(b._streamReadConstraints);
_streamWriteConstraints = Objects.requireNonNull(b._streamWriteConstraints);
+ _errorReportConfiguration = Objects.requireNonNull(b._errorReportConfiguration);
// JSON-specific
_characterEscapes = b._characterEscapes;
@@ -439,6 +450,7 @@ protected JsonFactory(TSFBuilder,?> b, boolean bogus) {
_generatorDecorators = _copy(b._generatorDecorators);
_streamReadConstraints = Objects.requireNonNull(b._streamReadConstraints);
_streamWriteConstraints = Objects.requireNonNull(b._streamWriteConstraints);
+ _errorReportConfiguration = Objects.requireNonNull(b._errorReportConfiguration);
// JSON-specific: need to assign even if not really used
_characterEscapes = null;
@@ -838,6 +850,26 @@ public JsonFactory setStreamReadConstraints(StreamReadConstraints src) {
return this;
}
+ /**
+ * Method for overriding {@link ErrorReportConfiguration} defined for
+ * this factory.
+ *
+ * NOTE: the preferred way to set constraints is by using
+ * {@link JsonFactoryBuilder#errorReportConfiguration}: this method is only
+ * provided to support older non-builder-based construction.
+ * In Jackson 3.x this method will not be available.
+ *
+ * @param src Configuration
+ *
+ * @return This factory instance (to allow call chaining)
+ *
+ * @since 2.16
+ */
+ public JsonFactory setErrorReportConfiguration(ErrorReportConfiguration src) {
+ _errorReportConfiguration = Objects.requireNonNull(src, "Cannot pass null ErrorReportConfiguration");;
+ return this;
+ }
+
/**
* Method for overriding {@link StreamWriteConstraints} defined for
* this factory.
@@ -2112,7 +2144,7 @@ protected IOContext _createContext(ContentReference contentRef, boolean resource
if (contentRef == null) {
contentRef = ContentReference.unknown();
}
- return new IOContext(_streamReadConstraints, _streamWriteConstraints,
+ return new IOContext(_streamReadConstraints, _streamWriteConstraints, _errorReportConfiguration,
_getBufferRecycler(), contentRef, resourceManaged);
}
@@ -2128,7 +2160,7 @@ protected IOContext _createContext(ContentReference contentRef, boolean resource
*/
@Deprecated // @since 2.13
protected IOContext _createContext(Object rawContentRef, boolean resourceManaged) {
- return new IOContext(_streamReadConstraints, _streamWriteConstraints,
+ return new IOContext(_streamReadConstraints, _streamWriteConstraints, _errorReportConfiguration,
_getBufferRecycler(),
_createContentReference(rawContentRef),
resourceManaged);
@@ -2147,7 +2179,7 @@ protected IOContext _createContext(Object rawContentRef, boolean resourceManaged
protected IOContext _createNonBlockingContext(Object srcRef) {
// [jackson-core#479]: allow recycling for non-blocking parser again
// now that access is thread-safe
- return new IOContext(_streamReadConstraints, _streamWriteConstraints,
+ return new IOContext(_streamReadConstraints, _streamWriteConstraints, _errorReportConfiguration,
_getBufferRecycler(),
_createContentReference(srcRef),
false);
@@ -2168,7 +2200,7 @@ protected IOContext _createNonBlockingContext(Object srcRef) {
protected ContentReference _createContentReference(Object contentAccessor) {
// 21-Mar-2021, tatu: For now assume "canHandleBinaryNatively()" is reliable
// indicator of textual vs binary format:
- return ContentReference.construct(!canHandleBinaryNatively(), contentAccessor);
+ return ContentReference.construct(!canHandleBinaryNatively(), contentAccessor, _errorReportConfiguration);
}
/**
@@ -2192,7 +2224,7 @@ protected ContentReference _createContentReference(Object contentAccessor,
// 21-Mar-2021, tatu: For now assume "canHandleBinaryNatively()" is reliable
// indicator of textual vs binary format:
return ContentReference.construct(!canHandleBinaryNatively(),
- contentAccessor, offset, length);
+ contentAccessor, offset, length, _errorReportConfiguration);
}
/*
diff --git a/src/main/java/com/fasterxml/jackson/core/JsonProcessingException.java b/src/main/java/com/fasterxml/jackson/core/JsonProcessingException.java
index e3364e9039..11d1cd88db 100644
--- a/src/main/java/com/fasterxml/jackson/core/JsonProcessingException.java
+++ b/src/main/java/com/fasterxml/jackson/core/JsonProcessingException.java
@@ -14,6 +14,8 @@
*
* Since Jackson 2.12 extends intermediate {@link JacksonException} type
* instead of directly extending {@link java.io.IOException}.
+ *
+ * Since Jackson 2.16, handles its content as configured using {@link com.fasterxml.jackson.core.ErrorReportConfiguration}.
*/
public class JsonProcessingException extends JacksonException
{
diff --git a/src/main/java/com/fasterxml/jackson/core/TSFBuilder.java b/src/main/java/com/fasterxml/jackson/core/TSFBuilder.java
index 02fe83b0eb..e3984daf8c 100644
--- a/src/main/java/com/fasterxml/jackson/core/TSFBuilder.java
+++ b/src/main/java/com/fasterxml/jackson/core/TSFBuilder.java
@@ -100,6 +100,13 @@ public abstract class TSFBuilder _generatorDecorators;
+ /**
+ * Optional {@link ErrorReportConfiguration} to use.
+ *
+ * @since 2.16
+ */
+ protected ErrorReportConfiguration _errorReportConfiguration;
+
/*
/**********************************************************************
/* Construction
@@ -120,6 +127,7 @@ protected TSFBuilder(JsonFactory base)
_outputDecorator = base._outputDecorator;
_streamReadConstraints = base._streamReadConstraints;
_streamWriteConstraints = base._streamWriteConstraints;
+ _errorReportConfiguration = base._errorReportConfiguration;
_generatorDecorators = _copy(base._generatorDecorators);
}
@@ -134,6 +142,7 @@ protected TSFBuilder(int factoryFeatures,
_outputDecorator = null;
_streamReadConstraints = StreamReadConstraints.defaults();
_streamWriteConstraints = StreamWriteConstraints.defaults();
+ _errorReportConfiguration = ErrorReportConfiguration.defaults();
_generatorDecorators = null;
}
@@ -324,6 +333,17 @@ public B streamReadConstraints(StreamReadConstraints streamReadConstraints) {
return _this();
}
+ /**
+ * Sets the configuration for error tokens.
+ *
+ * @param errorReportConfiguration configuration values used for handling errorneous token inputs.
+ * @return this factory
+ * @since 2.16
+ */
+ public B errorReportConfiguration(ErrorReportConfiguration errorReportConfiguration) {
+ _errorReportConfiguration = errorReportConfiguration;
+ return _this();
+ }
/**
* Sets the constraints for streaming writes.
*
diff --git a/src/test/java/com/fasterxml/jackson/core/ErrorReportConfigurationMaxRawContentLengthTest.java b/src/test/java/com/fasterxml/jackson/core/ErrorReportConfigurationMaxRawContentLengthTest.java
deleted file mode 100644
index 3afd9fd011..0000000000
--- a/src/test/java/com/fasterxml/jackson/core/ErrorReportConfigurationMaxRawContentLengthTest.java
+++ /dev/null
@@ -1,48 +0,0 @@
-package com.fasterxml.jackson.core;
-
-import com.fasterxml.jackson.core.io.ContentReference;
-
-/**
- * Unit tests for class {@link ErrorReportConfiguration#getMaxRawContentLength()}.
- */
-public class ErrorReportConfigurationMaxRawContentLengthTest extends BaseTest {
- /*
- /**********************************************************
- /* Unit Tests
- /**********************************************************
- */
-
- public void testBasicToStringErrorConfig() throws Exception {
- // Truncated result
- _verifyToString("abc", 2,
- "[Source: (String)\"ab\"[truncated 1 chars]; line: 1, column: 1]");
- // Exact length
- _verifyToString("abc", 3,
- "[Source: (String)\"abc\"; line: 1, column: 1]");
- // Enough length
- _verifyToString("abc", 4,
- "[Source: (String)\"abc\"; line: 1, column: 1]");
- }
-
- /*
- /**********************************************************
- /* Internal helper methods
- /**********************************************************
- */
-
- private void _verifyToString(String rawSrc, int rawContentLength, String expectedMessage) {
- ContentReference reference = _sourceRefWithErrorReportConfig(rawSrc, rawContentLength);
- String location = new JsonLocation(reference, 10L, 10L, 1, 1).toString();
- assertEquals(expectedMessage, location);
- }
-
- private ContentReference _sourceRefWithErrorReportConfig(String rawSrc, int rawContentLength) {
- return _sourceRef(rawSrc,
- ErrorReportConfiguration.builder().maxRawContentLength(rawContentLength).build());
- }
-
- private ContentReference _sourceRef(String rawSrc, ErrorReportConfiguration errorReportConfiguration) {
- return ContentReference.construct(true, rawSrc, 0, rawSrc.length(),errorReportConfiguration);
- }
-
-}
diff --git a/src/test/java/com/fasterxml/jackson/core/ErrorReportConfigurationTest.java b/src/test/java/com/fasterxml/jackson/core/ErrorReportConfigurationTest.java
index 4c48f02794..956d07d070 100644
--- a/src/test/java/com/fasterxml/jackson/core/ErrorReportConfigurationTest.java
+++ b/src/test/java/com/fasterxml/jackson/core/ErrorReportConfigurationTest.java
@@ -1,5 +1,8 @@
package com.fasterxml.jackson.core;
+import static org.assertj.core.api.Assertions.assertThat;
+import com.fasterxml.jackson.core.io.ContentReference;
+
/**
* Unit tests for class {@link ErrorReportConfiguration}.
*
@@ -8,6 +11,17 @@
public class ErrorReportConfigurationTest
extends BaseTest
{
+
+ /*
+ /**********************************************************
+ /* Unit Tests
+ /**********************************************************
+ */
+
+ private final int DEFAULT_CONTENT_LENGTH = ErrorReportConfiguration.DEFAULT_MAX_RAW_CONTENT_LENGTH;
+
+ private final int DEFAULT_ERROR_LENGTH = ErrorReportConfiguration.DEFAULT_MAX_ERROR_TOKEN_LENGTH;
+
private final ErrorReportConfiguration DEFAULTS = ErrorReportConfiguration.defaults();
public void testNormalBuild()
@@ -55,8 +69,8 @@ public void testInvalidMaxErrorTokenLength()
public void testDefaults()
{
// default value
- assertEquals(ErrorReportConfiguration.DEFAULT_MAX_ERROR_TOKEN_LENGTH, DEFAULTS.getMaxErrorTokenLength());
- assertEquals(ErrorReportConfiguration.DEFAULT_MAX_RAW_CONTENT_LENGTH, DEFAULTS.getMaxRawContentLength());
+ assertEquals(DEFAULT_ERROR_LENGTH, DEFAULTS.getMaxErrorTokenLength());
+ assertEquals(DEFAULT_CONTENT_LENGTH, DEFAULTS.getMaxRawContentLength());
// equals
assertEquals(ErrorReportConfiguration.defaults(), ErrorReportConfiguration.defaults());
@@ -69,8 +83,8 @@ public void testOverrideDefaultErrorReportConfiguration()
try {
ErrorReportConfiguration nullDefaults = ErrorReportConfiguration.defaults();
- assertEquals(ErrorReportConfiguration.DEFAULT_MAX_ERROR_TOKEN_LENGTH, nullDefaults.getMaxErrorTokenLength());
- assertEquals(ErrorReportConfiguration.DEFAULT_MAX_RAW_CONTENT_LENGTH, nullDefaults.getMaxRawContentLength());
+ assertEquals(DEFAULT_ERROR_LENGTH, nullDefaults.getMaxErrorTokenLength());
+ assertEquals(DEFAULT_CONTENT_LENGTH, nullDefaults.getMaxRawContentLength());
// (2) override with other value that actually changes default values
ErrorReportConfiguration.overrideDefaultErrorReportConfiguration(ErrorReportConfiguration.builder()
@@ -86,8 +100,8 @@ public void testOverrideDefaultErrorReportConfiguration()
// (3) revert back to default values
// IMPORTANT : make sure to revert back, otherwise other tests will be affected
ErrorReportConfiguration.overrideDefaultErrorReportConfiguration(ErrorReportConfiguration.builder()
- .maxErrorTokenLength(ErrorReportConfiguration.DEFAULT_MAX_ERROR_TOKEN_LENGTH)
- .maxRawContentLength(ErrorReportConfiguration.DEFAULT_MAX_RAW_CONTENT_LENGTH)
+ .maxErrorTokenLength(DEFAULT_ERROR_LENGTH)
+ .maxRawContentLength(DEFAULT_CONTENT_LENGTH)
.build());
}
}
@@ -113,4 +127,187 @@ public void testBuilderConstructorWithErrorReportConfiguration()
assertEquals(configA.getMaxErrorTokenLength(), configB.getMaxErrorTokenLength());
assertEquals(configA.getMaxRawContentLength(), configB.getMaxRawContentLength());
}
+
+ public void testWithJsonLocation() throws Exception
+ {
+ // Truncated result
+ _verifyJsonLocationToString("abc", 2, "\"ab\"[truncated 1 chars]");
+ // Exact length
+ _verifyJsonLocationToString("abc", 3, "\"abc\"");
+ // Enough length
+ _verifyJsonLocationToString("abc", 4, "\"abc\"");
+ }
+
+ public void testWithJsonFactory() throws Exception
+ {
+ // default
+ _verifyJsonProcessingExceptionSourceLength(500,
+ ErrorReportConfiguration.builder().build());
+ // default
+ _verifyJsonProcessingExceptionSourceLength(500,
+ ErrorReportConfiguration.defaults());
+ // shorter
+ _verifyJsonProcessingExceptionSourceLength(499,
+ ErrorReportConfiguration.builder()
+ .maxRawContentLength(DEFAULT_CONTENT_LENGTH - 1).build());
+ // longer
+ _verifyJsonProcessingExceptionSourceLength(501,
+ ErrorReportConfiguration.builder()
+ .maxRawContentLength(DEFAULT_CONTENT_LENGTH + 1).build());
+ // zero
+ _verifyJsonProcessingExceptionSourceLength(0,
+ ErrorReportConfiguration.builder()
+ .maxRawContentLength(0).build());
+ }
+
+ public void testExpectedTokenLengthWithConfigurations()
+ throws Exception
+ {
+ // default
+ _verifyErrorTokenLength(263,
+ ErrorReportConfiguration.builder().build());
+ // default
+ _verifyErrorTokenLength(263,
+ ErrorReportConfiguration.defaults());
+ // shorter
+ _verifyErrorTokenLength(63,
+ ErrorReportConfiguration.builder()
+ .maxErrorTokenLength(DEFAULT_ERROR_LENGTH - 200).build());
+ // longer
+ _verifyErrorTokenLength(463,
+ ErrorReportConfiguration.builder()
+ .maxErrorTokenLength(DEFAULT_ERROR_LENGTH + 200).build());
+ // zero
+ _verifyErrorTokenLength(9,
+ ErrorReportConfiguration.builder()
+ .maxErrorTokenLength(0).build());
+
+ // negative value fails
+ try {
+ _verifyErrorTokenLength(9,
+ ErrorReportConfiguration.builder()
+ .maxErrorTokenLength(-1).build());
+ } catch (IllegalArgumentException e) {
+ assertThat(e.getMessage())
+ .contains("Value of maxErrorTokenLength")
+ .contains("cannot be negative");
+ }
+ // null is not allowed, throws NPE
+ try {
+ _verifyErrorTokenLength(263,
+ null);
+ } catch (NullPointerException e) {
+ // no-op
+ }
+ }
+
+ public void testNonPositiveErrorTokenConfig()
+ {
+ // Zero should be ok
+ ErrorReportConfiguration.builder().maxErrorTokenLength(0).build();
+
+ // But not -1
+ try {
+ ErrorReportConfiguration.builder().maxErrorTokenLength(-1).build();
+ fail();
+ } catch (IllegalArgumentException e) {
+ assertThat(e.getMessage())
+ .contains("Value of maxErrorTokenLength")
+ .contains("cannot be negative");
+ }
+ }
+
+ public void testNullSetterThrowsException() {
+ try {
+ newStreamFactory().setErrorReportConfiguration(null);
+ fail();
+ } catch (NullPointerException npe) {
+ assertThat(npe).hasMessage("Cannot pass null ErrorReportConfiguration");
+ }
+ }
+
+ /*
+ /**********************************************************
+ /* Internal helper methods
+ /**********************************************************
+ */
+
+ private void _verifyJsonProcessingExceptionSourceLength(int expectedRawContentLength, ErrorReportConfiguration erc)
+ throws Exception
+ {
+ // Arrange
+ JsonFactory factory = streamFactoryBuilder()
+ .enable(StreamReadFeature.INCLUDE_SOURCE_IN_LOCATION)
+ .errorReportConfiguration(erc)
+ .build();
+ // Make JSON input too long so it can be cutoff
+ int tooLongContent = 50 * DEFAULT_CONTENT_LENGTH;
+ String inputWithDynamicLength = _buildBrokenJsonOfLength(tooLongContent);
+
+ // Act
+ try (JsonParser parser = factory.createParser(inputWithDynamicLength)) {
+ parser.nextToken();
+ parser.nextToken();
+ fail("Should not reach");
+ } catch (JsonProcessingException e) {
+
+ // Assert
+ String prefix = "(String)\"";
+ String suffix = "\"[truncated 12309 chars]";
+
+ // The length of the source description should be [ prefix + expected length + suffix ]
+ int expectedLength = prefix.length() + expectedRawContentLength + suffix.length();
+ int actualLength = e.getLocation().sourceDescription().length();
+
+ assertEquals(expectedLength, actualLength);
+ assertThat(e.getMessage())
+ .contains("Unrecognized token '")
+ .contains("was expecting (JSON");
+ }
+ }
+
+ private void _verifyJsonLocationToString(String rawSrc, int rawContentLength, String expectedMessage)
+ {
+ ErrorReportConfiguration erc = ErrorReportConfiguration.builder()
+ .maxRawContentLength(rawContentLength)
+ .build();
+ ContentReference reference = ContentReference.construct(true, rawSrc, 0, rawSrc.length(), erc);
+ assertEquals(
+ "[Source: (String)" + expectedMessage + "; line: 1, column: 1]",
+ new JsonLocation(reference, 10L, 10L, 1, 1).toString());
+ }
+
+ private void _verifyErrorTokenLength(int expectedTokenLen, ErrorReportConfiguration errorReportConfiguration)
+ throws Exception
+ {
+ JsonFactory jf3 = streamFactoryBuilder()
+ .errorReportConfiguration(errorReportConfiguration)
+ .build();
+ _testWithMaxErrorTokenLength(expectedTokenLen,
+ // creating arbitrary number so that token reaches max len, but not over-do it
+ 50 * DEFAULT_ERROR_LENGTH, jf3);
+ }
+
+ private void _testWithMaxErrorTokenLength(int expectedSize, int tokenLen, JsonFactory factory)
+ throws Exception
+ {
+ String inputWithDynamicLength = _buildBrokenJsonOfLength(tokenLen);
+ try (JsonParser parser = factory.createParser(inputWithDynamicLength)) {
+ parser.nextToken();
+ parser.nextToken();
+ } catch (JsonProcessingException e) {
+ assertThat(e.getLocation()._totalChars).isEqualTo(expectedSize);
+ assertThat(e.getMessage()).contains("Unrecognized token");
+ }
+ }
+
+ private String _buildBrokenJsonOfLength(int len)
+ {
+ StringBuilder sb = new StringBuilder("{\"key\":");
+ for (int i = 0; i < len; i++) {
+ sb.append("a");
+ }
+ sb.append("!}");
+ return sb.toString();
+ }
}