StringNodeNumberLengthTest.java
package tools.jackson.databind.node;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.Optional;
import java.util.OptionalDouble;
import org.junit.jupiter.api.Test;
import tools.jackson.core.StreamReadConstraints;
import tools.jackson.core.exc.StreamConstraintsException;
import tools.jackson.databind.testutil.DatabindTestUtil;
import static org.junit.jupiter.api.Assertions.*;
/**
* Verifies that coercing a {@link StringNode} holding a "stringified" number to
* {@code BigInteger}/{@code BigDecimal}/{@code double}/{@code float} enforces the
* {@code StreamReadConstraints} number-length limit before the (super-linear) parse,
* the same way deserializers and (for scale) {@link DecimalNode} already do.
*
* @see <a href="https://github.com/FasterXML/jackson-databind/issues/6214">[databind#6214]</a>
*/
public class StringNodeNumberLengthTest extends DatabindTestUtil
{
private final static int MAX_LEN = StreamReadConstraints.defaults().getMaxNumberLength();
// A numeric String comfortably past the default limit (arrives as a JSON String,
// so it is bounded only by max-string-length, not max-number-length)
private final static int OVER_LEN = MAX_LEN + 100;
private final static String OVER_LONG_INT = "9".repeat(OVER_LEN);
private final static String OVER_LONG_DECIMAL = "1." + "9".repeat(OVER_LEN);
@Test
public void strictAccessorsRejectOverLongNumber() throws Exception
{
StringNode intNode = StringNode.valueOf(OVER_LONG_INT);
_verifyGuarded(() -> intNode.asBigInteger());
_verifyGuarded(() -> intNode.asDouble());
_verifyGuarded(() -> intNode.asFloat());
_verifyGuarded(() -> intNode.asDecimal());
StringNode decNode = StringNode.valueOf(OVER_LONG_DECIMAL);
_verifyGuarded(() -> decNode.asDouble());
_verifyGuarded(() -> decNode.asFloat());
_verifyGuarded(() -> decNode.asDecimal());
}
@Test
public void lenientAccessorsReturnDefaultForOverLongNumber() throws Exception
{
StringNode node = StringNode.valueOf(OVER_LONG_INT);
// default/Optional variants must NOT throw (same contract as any other
// non-convertible value): they return the default / empty instead
assertEquals(BigInteger.ONE, node.asBigInteger(BigInteger.ONE));
assertFalse(node.asBigIntegerOpt().isPresent());
assertEquals(-1.0, node.asDouble(-1.0));
assertFalse(node.asDoubleOpt().isPresent());
assertEquals(-1.0f, node.asFloat(-1.0f));
assertFalse(node.asFloatOpt().isPresent());
assertEquals(BigDecimal.ONE, node.asDecimal(BigDecimal.ONE));
assertFalse(node.asDecimalOpt().isPresent());
}
// Values within the limit must still coerce, unchanged
@Test
public void withinLimitStillCoerces() throws Exception
{
StringNode node = StringNode.valueOf("1234");
assertEquals(new BigInteger("1234"), node.asBigInteger());
assertEquals(new BigInteger("1234"), node.asBigInteger(BigInteger.ZERO));
assertEquals(Optional.of(new BigInteger("1234")), node.asBigIntegerOpt());
assertEquals(1234.0, node.asDouble());
assertEquals(OptionalDouble.of(1234.0), node.asDoubleOpt());
assertEquals(1234.0f, node.asFloat());
assertEquals(new BigDecimal("1234"), node.asDecimal());
// length exactly at the limit is still accepted
StringNode atLimit = StringNode.valueOf("9".repeat(MAX_LEN));
assertEquals(new BigInteger("9".repeat(MAX_LEN)), atLimit.asBigInteger());
}
private interface Coercion { Object convert(); }
private void _verifyGuarded(Coercion c) throws Exception
{
try {
c.convert();
fail("Should not pass: number length exceeds the configured maximum");
} catch (StreamConstraintsException e) {
verifyException(e, "Number value length");
verifyException(e, "exceeds the maximum allowed");
}
}
}