From 04e6edfb74fcc5a803f2aad091b74b81c3f3605a Mon Sep 17 00:00:00 2001 From: PJ Fanning Date: Sun, 18 Jun 2023 12:34:53 +0100 Subject: [PATCH] add StreamWriteConstraints Update TokenFilterContext.java make stream write constraints accessible Update FilteringGeneratorDelegate.java police nesting depth in json generators Update JsonGeneratorImpl.java add test that should fail fix test test issue more tests revert changes in FilteringGeneratorDelegate Update FilteringGeneratorDelegate.java --- .../fasterxml/jackson/core/JsonFactory.java | 49 +++++- .../fasterxml/jackson/core/JsonGenerator.java | 9 + .../jackson/core/StreamWriteConstraints.java | 157 ++++++++++++++++++ .../fasterxml/jackson/core/TSFBuilder.java | 21 ++- .../jackson/core/TokenStreamFactory.java | 10 ++ .../core/filter/TokenFilterContext.java | 1 + .../fasterxml/jackson/core/io/IOContext.java | 37 ++++- .../jackson/core/json/JsonGeneratorImpl.java | 11 ++ .../jackson/core/json/JsonWriteContext.java | 2 + .../jackson/core/json/UTF8JsonGenerator.java | 5 + .../core/json/WriterBasedJsonGenerator.java | 5 + .../core/util/JsonGeneratorDelegate.java | 11 ++ .../jackson/core/write/UTF8GeneratorTest.java | 41 ++++- .../write/WriterBasedJsonGeneratorTest.java | 55 ++++++ 14 files changed, 407 insertions(+), 7 deletions(-) create mode 100644 src/main/java/com/fasterxml/jackson/core/StreamWriteConstraints.java create mode 100644 src/test/java/com/fasterxml/jackson/core/write/WriterBasedJsonGeneratorTest.java diff --git a/src/main/java/com/fasterxml/jackson/core/JsonFactory.java b/src/main/java/com/fasterxml/jackson/core/JsonFactory.java index a754660b25..066e1bb65e 100644 --- a/src/main/java/com/fasterxml/jackson/core/JsonFactory.java +++ b/src/main/java/com/fasterxml/jackson/core/JsonFactory.java @@ -279,6 +279,14 @@ public static int collectDefaults() { */ protected StreamReadConstraints _streamReadConstraints; + /** + * Write constraints to use for {@link JsonGenerator}s constructed using + * this factory. + * + * @since 2.16 + */ + protected StreamWriteConstraints _streamWriteConstraints; + /** * Optional helper object that may decorate input sources, to do * additional processing on input during parsing. @@ -339,6 +347,7 @@ public JsonFactory(ObjectCodec oc) { _objectCodec = oc; _quoteChar = DEFAULT_QUOTE_CHAR; _streamReadConstraints = StreamReadConstraints.defaults(); + _streamWriteConstraints = StreamWriteConstraints.defaults(); } /** @@ -361,6 +370,8 @@ protected JsonFactory(JsonFactory src, ObjectCodec codec) _outputDecorator = src._outputDecorator; _streamReadConstraints = src._streamReadConstraints == null ? StreamReadConstraints.defaults() : src._streamReadConstraints; + _streamWriteConstraints = src._streamWriteConstraints == null ? + StreamWriteConstraints.defaults() : src._streamWriteConstraints; // JSON-specific _characterEscapes = src._characterEscapes; @@ -387,6 +398,8 @@ public JsonFactory(JsonFactoryBuilder b) { _outputDecorator = b._outputDecorator; _streamReadConstraints = b._streamReadConstraints == null ? StreamReadConstraints.defaults() : b._streamReadConstraints; + _streamWriteConstraints = b._streamWriteConstraints == null ? + StreamWriteConstraints.defaults() : b._streamWriteConstraints; // JSON-specific _characterEscapes = b._characterEscapes; @@ -413,6 +426,8 @@ protected JsonFactory(TSFBuilder b, boolean bogus) { _outputDecorator = b._outputDecorator; _streamReadConstraints = b._streamReadConstraints == null ? StreamReadConstraints.defaults() : b._streamReadConstraints; + _streamWriteConstraints = b._streamWriteConstraints == null ? + StreamWriteConstraints.defaults() : b._streamWriteConstraints; // JSON-specific: need to assign even if not really used _characterEscapes = null; @@ -779,6 +794,11 @@ public StreamReadConstraints streamReadConstraints() { return _streamReadConstraints; } + @Override + public StreamWriteConstraints streamWriteConstraints() { + return _streamWriteConstraints; + } + /** * Method for overriding {@link StreamReadConstraints} defined for * this factory. @@ -799,6 +819,26 @@ public JsonFactory setStreamReadConstraints(StreamReadConstraints src) { return this; } + /** + * Method for overriding {@link StreamWriteConstraints} defined for + * this factory. + *

+ * NOTE: the preferred way to set constraints is by using + * {@link JsonFactoryBuilder#streamWriteConstraints}: this method is only + * provided to support older non-builder-based construction. + * In Jackson 3.x this method will not be available. + * + * @param swc Constraints + * + * @return This factory instance (to allow call chaining) + * + * @since 2.16 + */ + public JsonFactory setStreamWriteConstraints(StreamWriteConstraints swc) { + _streamWriteConstraints = Objects.requireNonNull(swc); + return this; + } + /* /********************************************************** /* Configuration, parser configuration @@ -2034,7 +2074,8 @@ protected IOContext _createContext(ContentReference contentRef, boolean resource if (contentRef == null) { contentRef = ContentReference.unknown(); } - return new IOContext(_streamReadConstraints, _getBufferRecycler(), contentRef, resourceManaged); + return new IOContext(_streamReadConstraints, _streamWriteConstraints, + _getBufferRecycler(), contentRef, resourceManaged); } /** @@ -2049,7 +2090,8 @@ protected IOContext _createContext(ContentReference contentRef, boolean resource */ @Deprecated // @since 2.13 protected IOContext _createContext(Object rawContentRef, boolean resourceManaged) { - return new IOContext(_streamReadConstraints, _getBufferRecycler(), + return new IOContext(_streamReadConstraints, _streamWriteConstraints, + _getBufferRecycler(), _createContentReference(rawContentRef), resourceManaged); } @@ -2067,7 +2109,8 @@ 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, _getBufferRecycler(), + return new IOContext(_streamReadConstraints, _streamWriteConstraints, + _getBufferRecycler(), _createContentReference(srcRef), false); } diff --git a/src/main/java/com/fasterxml/jackson/core/JsonGenerator.java b/src/main/java/com/fasterxml/jackson/core/JsonGenerator.java index 8137453ffc..d524939aef 100644 --- a/src/main/java/com/fasterxml/jackson/core/JsonGenerator.java +++ b/src/main/java/com/fasterxml/jackson/core/JsonGenerator.java @@ -341,6 +341,15 @@ protected JsonGenerator() { } */ public abstract ObjectCodec getCodec(); + /** + * Get the constraints to apply when performing streaming writes. + * + * @since 2.16 + */ + public StreamWriteConstraints streamWriteConstraints() { + return StreamWriteConstraints.defaults(); + } + /** * Accessor for finding out version of the bundle that provided this generator instance. * diff --git a/src/main/java/com/fasterxml/jackson/core/StreamWriteConstraints.java b/src/main/java/com/fasterxml/jackson/core/StreamWriteConstraints.java new file mode 100644 index 0000000000..83ac7ca66f --- /dev/null +++ b/src/main/java/com/fasterxml/jackson/core/StreamWriteConstraints.java @@ -0,0 +1,157 @@ +package com.fasterxml.jackson.core; + +import com.fasterxml.jackson.core.exc.StreamConstraintsException; + +/** + * The constraints to use for streaming writes: used to guard against problematic + * output by preventing processing of "too big" output constructs (values, + * structures). + * Constraints are registered with {@code TokenStreamFactory} (such as + * {@code JsonFactory}); if nothing explicitly specified, default + * constraints are used. + *

+ * Currently constrained aspects, with default settings, are: + *

+ * + * @since 2.16 + */ +public class StreamWriteConstraints + implements java.io.Serializable +{ + private static final long serialVersionUID = 1L; + + /** + * Default setting for maximum depth: see {@link Builder#maxNestingDepth(int)} for details. + */ + public static final int DEFAULT_MAX_DEPTH = 1000; + + protected final int _maxNestingDepth; + + private static final StreamWriteConstraints DEFAULT = + new StreamWriteConstraints(DEFAULT_MAX_DEPTH); + + public static final class Builder { + private int maxNestingDepth; + + /** + * Sets the maximum nesting depth. The depth is a count of objects and arrays that have not + * been closed, `{` and `[` respectively. + * + * @param maxNestingDepth the maximum depth + * + * @return this builder + * @throws IllegalArgumentException if the maxNestingDepth is set to a negative value + */ + public Builder maxNestingDepth(final int maxNestingDepth) { + if (maxNestingDepth < 0) { + throw new IllegalArgumentException("Cannot set maxNestingDepth to a negative value"); + } + this.maxNestingDepth = maxNestingDepth; + return this; + } + + Builder() { + this(DEFAULT_MAX_DEPTH); + } + + Builder(final int maxNestingDepth) { + this.maxNestingDepth = maxNestingDepth; + } + + Builder(StreamWriteConstraints src) { + maxNestingDepth = src._maxNestingDepth; + } + + public StreamWriteConstraints build() { + return new StreamWriteConstraints(maxNestingDepth); + } + } + + /* + /********************************************************************** + /* Life-cycle + /********************************************************************** + */ + + protected StreamWriteConstraints(final int maxNestingDepth) { + _maxNestingDepth = maxNestingDepth; + } + + public static Builder builder() { + return new Builder(); + } + + public static StreamWriteConstraints defaults() { + return DEFAULT; + } + + /** + * @return New {@link Builder} initialized with settings of this constraints + * instance + */ + public Builder rebuild() { + return new Builder(this); + } + + /* + /********************************************************************** + /* Accessors + /********************************************************************** + */ + + /** + * Accessor for maximum depth. + * see {@link Builder#maxNestingDepth(int)} for details. + * + * @return Maximum allowed depth + */ + public int getMaxNestingDepth() { + return _maxNestingDepth; + } + + /* + /********************************************************************** + /* Convenience methods for validation, document limits + /********************************************************************** + */ + + /** + * Convenience method that can be used to verify that the + * nesting depth does not exceed the maximum specified by this + * constraints object: if it does, a + * {@link StreamConstraintsException} + * is thrown. + * + * @param depth count of unclosed objects and arrays + * + * @throws StreamConstraintsException If depth exceeds maximum + */ + public void validateNestingDepth(int depth) throws StreamConstraintsException + { + if (depth > _maxNestingDepth) { + throw _constructException( + "Document nesting depth (%d) exceeds the maximum allowed (%d, from %s)", + depth, _maxNestingDepth, + _constrainRef("getMaxNestingDepth")); + } + } + + /* + /********************************************************************** + /* Error reporting + /********************************************************************** + */ + + // @since 2.16 + protected StreamConstraintsException _constructException(String msgTemplate, Object... args) throws StreamConstraintsException { + throw new StreamConstraintsException(String.format(msgTemplate, args)); + } + + // @since 2.16 + protected String _constrainRef(String method) { + return "`StreamWriteConstraints."+method+"()`"; + } +} diff --git a/src/main/java/com/fasterxml/jackson/core/TSFBuilder.java b/src/main/java/com/fasterxml/jackson/core/TSFBuilder.java index 757b627efe..d5462d96be 100644 --- a/src/main/java/com/fasterxml/jackson/core/TSFBuilder.java +++ b/src/main/java/com/fasterxml/jackson/core/TSFBuilder.java @@ -78,12 +78,19 @@ public abstract class TSFBuilderStreamWriteConstraints */ public IOContext(StreamReadConstraints src, BufferRecycler br, ContentReference contentRef, boolean managedResource) { _streamReadConstraints = (src == null) ? StreamReadConstraints.defaults() : src; + _streamWriteConstraints = StreamWriteConstraints.defaults(); _bufferRecycler = br; _contentReference = contentRef; _sourceRef = contentRef.getRawContent(); @@ -141,7 +168,7 @@ public IOContext(StreamReadConstraints src, BufferRecycler br, @Deprecated // since 2.15 public IOContext(BufferRecycler br, ContentReference contentRef, boolean managedResource) { - this(null, br, contentRef, managedResource); + this(null, null, br, contentRef, managedResource); } @Deprecated // since 2.13 @@ -157,6 +184,14 @@ public StreamReadConstraints streamReadConstraints() { return _streamReadConstraints; } + /** + * @return constraints for streaming writes + * @since 2.16 + */ + public StreamWriteConstraints streamWriteConstraints() { + return _streamWriteConstraints; + } + public void setEncoding(JsonEncoding enc) { _encoding = enc; } diff --git a/src/main/java/com/fasterxml/jackson/core/json/JsonGeneratorImpl.java b/src/main/java/com/fasterxml/jackson/core/json/JsonGeneratorImpl.java index 807a92fda8..da3c2de251 100644 --- a/src/main/java/com/fasterxml/jackson/core/json/JsonGeneratorImpl.java +++ b/src/main/java/com/fasterxml/jackson/core/json/JsonGeneratorImpl.java @@ -139,6 +139,17 @@ public Version version() { return VersionUtil.versionFor(getClass()); } + /* + /********************************************************************** + /* Constraints violation checking (2.16) + /********************************************************************** + */ + + @Override + public StreamWriteConstraints streamWriteConstraints() { + return _ioContext.streamWriteConstraints(); + } + /* /********************************************************** /* Overridden configuration methods diff --git a/src/main/java/com/fasterxml/jackson/core/json/JsonWriteContext.java b/src/main/java/com/fasterxml/jackson/core/json/JsonWriteContext.java index 87b1ef4ad0..12103e1cc1 100644 --- a/src/main/java/com/fasterxml/jackson/core/json/JsonWriteContext.java +++ b/src/main/java/com/fasterxml/jackson/core/json/JsonWriteContext.java @@ -69,6 +69,7 @@ protected JsonWriteContext(int type, JsonWriteContext parent, DupDetector dups) super(); _type = type; _parent = parent; + _nestingDepth = parent == null ? 0 : parent._nestingDepth + 1; _dups = dups; _index = -1; } @@ -79,6 +80,7 @@ protected JsonWriteContext(int type, JsonWriteContext parent, DupDetector dups, super(); _type = type; _parent = parent; + _nestingDepth = parent == null ? 0 : parent._nestingDepth + 1; _dups = dups; _index = -1; _currentValue = currValue; diff --git a/src/main/java/com/fasterxml/jackson/core/json/UTF8JsonGenerator.java b/src/main/java/com/fasterxml/jackson/core/json/UTF8JsonGenerator.java index 3966d52f82..553fb7be93 100644 --- a/src/main/java/com/fasterxml/jackson/core/json/UTF8JsonGenerator.java +++ b/src/main/java/com/fasterxml/jackson/core/json/UTF8JsonGenerator.java @@ -312,6 +312,7 @@ public final void writeStartArray() throws IOException { _verifyValueWrite("start an array"); _writeContext = _writeContext.createChildArrayContext(); + streamWriteConstraints().validateNestingDepth(_writeContext.getNestingDepth()); if (_cfgPrettyPrinter != null) { _cfgPrettyPrinter.writeStartArray(this); } else { @@ -327,6 +328,7 @@ public final void writeStartArray(Object currentValue) throws IOException { _verifyValueWrite("start an array"); _writeContext = _writeContext.createChildArrayContext(currentValue); + streamWriteConstraints().validateNestingDepth(_writeContext.getNestingDepth()); if (_cfgPrettyPrinter != null) { _cfgPrettyPrinter.writeStartArray(this); } else { @@ -342,6 +344,7 @@ public void writeStartArray(Object currentValue, int size) throws IOException { _verifyValueWrite("start an array"); _writeContext = _writeContext.createChildArrayContext(currentValue); + streamWriteConstraints().validateNestingDepth(_writeContext.getNestingDepth()); if (_cfgPrettyPrinter != null) { _cfgPrettyPrinter.writeStartArray(this); } else { @@ -374,6 +377,7 @@ public final void writeStartObject() throws IOException { _verifyValueWrite("start an object"); _writeContext = _writeContext.createChildObjectContext(); + streamWriteConstraints().validateNestingDepth(_writeContext.getNestingDepth()); if (_cfgPrettyPrinter != null) { _cfgPrettyPrinter.writeStartObject(this); } else { @@ -389,6 +393,7 @@ public void writeStartObject(Object forValue) throws IOException { _verifyValueWrite("start an object"); JsonWriteContext ctxt = _writeContext.createChildObjectContext(forValue); + streamWriteConstraints().validateNestingDepth(ctxt.getNestingDepth()); _writeContext = ctxt; if (_cfgPrettyPrinter != null) { _cfgPrettyPrinter.writeStartObject(this); diff --git a/src/main/java/com/fasterxml/jackson/core/json/WriterBasedJsonGenerator.java b/src/main/java/com/fasterxml/jackson/core/json/WriterBasedJsonGenerator.java index 01ccf3bfca..b1628c92b3 100644 --- a/src/main/java/com/fasterxml/jackson/core/json/WriterBasedJsonGenerator.java +++ b/src/main/java/com/fasterxml/jackson/core/json/WriterBasedJsonGenerator.java @@ -251,6 +251,7 @@ public void writeStartArray() throws IOException { _verifyValueWrite("start an array"); _writeContext = _writeContext.createChildArrayContext(); + streamWriteConstraints().validateNestingDepth(_writeContext.getNestingDepth()); if (_cfgPrettyPrinter != null) { _cfgPrettyPrinter.writeStartArray(this); } else { @@ -266,6 +267,7 @@ public void writeStartArray(Object currentValue) throws IOException { _verifyValueWrite("start an array"); _writeContext = _writeContext.createChildArrayContext(currentValue); + streamWriteConstraints().validateNestingDepth(_writeContext.getNestingDepth()); if (_cfgPrettyPrinter != null) { _cfgPrettyPrinter.writeStartArray(this); } else { @@ -281,6 +283,7 @@ public void writeStartArray(Object currentValue, int size) throws IOException { _verifyValueWrite("start an array"); _writeContext = _writeContext.createChildArrayContext(currentValue); + streamWriteConstraints().validateNestingDepth(_writeContext.getNestingDepth()); if (_cfgPrettyPrinter != null) { _cfgPrettyPrinter.writeStartArray(this); } else { @@ -313,6 +316,7 @@ public void writeStartObject() throws IOException { _verifyValueWrite("start an object"); _writeContext = _writeContext.createChildObjectContext(); + streamWriteConstraints().validateNestingDepth(_writeContext.getNestingDepth()); if (_cfgPrettyPrinter != null) { _cfgPrettyPrinter.writeStartObject(this); } else { @@ -328,6 +332,7 @@ public void writeStartObject(Object forValue) throws IOException { _verifyValueWrite("start an object"); JsonWriteContext ctxt = _writeContext.createChildObjectContext(forValue); + streamWriteConstraints().validateNestingDepth(_writeContext.getNestingDepth()); _writeContext = ctxt; if (_cfgPrettyPrinter != null) { _cfgPrettyPrinter.writeStartObject(this); diff --git a/src/main/java/com/fasterxml/jackson/core/util/JsonGeneratorDelegate.java b/src/main/java/com/fasterxml/jackson/core/util/JsonGeneratorDelegate.java index 85c8a0e519..5879063d62 100644 --- a/src/main/java/com/fasterxml/jackson/core/util/JsonGeneratorDelegate.java +++ b/src/main/java/com/fasterxml/jackson/core/util/JsonGeneratorDelegate.java @@ -186,6 +186,17 @@ public JsonGenerator setPrettyPrinter(PrettyPrinter pp) { public JsonGenerator setRootValueSeparator(SerializableString sep) { delegate.setRootValueSeparator(sep); return this; } + /* + /********************************************************************** + /* Constraints violation checking (2.16) + /********************************************************************** + */ + + @Override + public StreamWriteConstraints streamWriteConstraints() { + return delegate.streamWriteConstraints(); + } + /* /********************************************************************** /* Public API, write methods, structural diff --git a/src/test/java/com/fasterxml/jackson/core/write/UTF8GeneratorTest.java b/src/test/java/com/fasterxml/jackson/core/write/UTF8GeneratorTest.java index 469fe58325..1e83778040 100644 --- a/src/test/java/com/fasterxml/jackson/core/write/UTF8GeneratorTest.java +++ b/src/test/java/com/fasterxml/jackson/core/write/UTF8GeneratorTest.java @@ -3,6 +3,7 @@ import java.io.*; import com.fasterxml.jackson.core.*; +import com.fasterxml.jackson.core.exc.StreamConstraintsException; import com.fasterxml.jackson.core.filter.FilteringGeneratorDelegate; import com.fasterxml.jackson.core.filter.JsonPointerBasedFilter; import com.fasterxml.jackson.core.filter.TokenFilter.Inclusion; @@ -18,8 +19,7 @@ public class UTF8GeneratorTest extends BaseTest public void testUtf8Issue462() throws Exception { ByteArrayOutputStream bytes = new ByteArrayOutputStream(); - IOContext ioc = new IOContext(StreamReadConstraints.defaults(), - new BufferRecycler(), + IOContext ioc = new IOContext(new BufferRecycler(), ContentReference.rawReference(bytes), true); JsonGenerator gen = new UTF8JsonGenerator(ioc, 0, null, bytes, '"'); String str = "Natuurlijk is alles gelukt en weer een tevreden klant\uD83D\uDE04"; @@ -44,6 +44,43 @@ public void testUtf8Issue462() throws Exception p.close(); } + public void testNestingDepthWithSmallLimit() throws Exception + { + ByteArrayOutputStream bytes = new ByteArrayOutputStream(); + IOContext ioc = new IOContext(null, + StreamWriteConstraints.builder().maxNestingDepth(1).build(), + new BufferRecycler(), + ContentReference.rawReference(bytes), true); + try (JsonGenerator gen = new UTF8JsonGenerator(ioc, 0, null, bytes, '"')) { + gen.writeStartObject(); + gen.writeFieldName("array"); + gen.writeStartArray(); + fail("expected StreamConstraintsException"); + } catch (StreamConstraintsException sce) { + String expected = "Document nesting depth (2) exceeds the maximum allowed (1, from `StreamWriteConstraints.getMaxNestingDepth()`)"; + assertEquals(expected, sce.getMessage()); + } + } + + public void testNestingDepthWithSmallLimitNestedObject() throws Exception + { + ByteArrayOutputStream bytes = new ByteArrayOutputStream(); + IOContext ioc = new IOContext(null, + StreamWriteConstraints.builder().maxNestingDepth(1).build(), + new BufferRecycler(), + ContentReference.rawReference(bytes), true); + try (JsonGenerator gen = new UTF8JsonGenerator(ioc, 0, null, bytes, '"')) { + gen.writeStartObject(); + gen.writeFieldName("object"); + gen.writeStartObject(); + fail("expected StreamConstraintsException"); + } catch (StreamConstraintsException sce) { + String expected = "Document nesting depth (2) exceeds the maximum allowed (1, from `StreamWriteConstraints.getMaxNestingDepth()`)"; + assertEquals(expected, sce.getMessage()); + } + } + + // for [core#115] public void testSurrogatesWithRaw() throws Exception { diff --git a/src/test/java/com/fasterxml/jackson/core/write/WriterBasedJsonGeneratorTest.java b/src/test/java/com/fasterxml/jackson/core/write/WriterBasedJsonGeneratorTest.java new file mode 100644 index 0000000000..3f4eb63ec1 --- /dev/null +++ b/src/test/java/com/fasterxml/jackson/core/write/WriterBasedJsonGeneratorTest.java @@ -0,0 +1,55 @@ +package com.fasterxml.jackson.core.write; + +import com.fasterxml.jackson.core.BaseTest; +import com.fasterxml.jackson.core.JsonFactory; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.StreamWriteConstraints; +import com.fasterxml.jackson.core.exc.StreamConstraintsException; +import com.fasterxml.jackson.core.io.ContentReference; +import com.fasterxml.jackson.core.io.IOContext; +import com.fasterxml.jackson.core.json.WriterBasedJsonGenerator; +import com.fasterxml.jackson.core.util.BufferRecycler; + +import java.io.StringWriter; + +public class WriterBasedJsonGeneratorTest extends BaseTest +{ + private final JsonFactory JSON_F = new JsonFactory(); + + public void testNestingDepthWithSmallLimit() throws Exception + { + StringWriter sw = new StringWriter(); + IOContext ioc = new IOContext(null, + StreamWriteConstraints.builder().maxNestingDepth(1).build(), + new BufferRecycler(), + ContentReference.rawReference(sw), true); + try (JsonGenerator gen = new WriterBasedJsonGenerator(ioc, 0, null, sw, '"')) { + gen.writeStartObject(); + gen.writeFieldName("array"); + gen.writeStartArray(); + fail("expected StreamConstraintsException"); + } catch (StreamConstraintsException sce) { + String expected = "Document nesting depth (2) exceeds the maximum allowed (1, from `StreamWriteConstraints.getMaxNestingDepth()`)"; + assertEquals(expected, sce.getMessage()); + } + } + + public void testNestingDepthWithSmallLimitNestedObject() throws Exception + { + StringWriter sw = new StringWriter(); + IOContext ioc = new IOContext(null, + StreamWriteConstraints.builder().maxNestingDepth(1).build(), + new BufferRecycler(), + ContentReference.rawReference(sw), true); + try (JsonGenerator gen = new WriterBasedJsonGenerator(ioc, 0, null, sw, '"')) { + gen.writeStartObject(); + gen.writeFieldName("object"); + gen.writeStartObject(); + fail("expected StreamConstraintsException"); + } catch (StreamConstraintsException sce) { + String expected = "Document nesting depth (2) exceeds the maximum allowed (1, from `StreamWriteConstraints.getMaxNestingDepth()`)"; + assertEquals(expected, sce.getMessage()); + } + } + +}