From 4a298ec6475eb8aec2fe1fd503e384ae03c5ff36 Mon Sep 17 00:00:00 2001 From: Andriy Plokhotnyuk Date: Tue, 11 Feb 2025 16:34:27 +0100 Subject: [PATCH] Fix returning of wrong value or throwing of exceptions during decoding of floating point numbers with extreme exponents (#1303) --- .../zio/json/internal/UnsafeNumbers.scala | 36 ++++++---- .../zio/json/internal/UnsafeNumbers.scala | 36 ++++++---- .../zio/json/internal/UnsafeNumbers.scala | 36 ++++++---- .../src/test/scala/zio/json/DecoderSpec.scala | 17 +++++ .../zio/json/internal/SafeNumbersSpec.scala | 69 +++++++++++-------- 5 files changed, 127 insertions(+), 67 deletions(-) diff --git a/zio-json/js/src/main/scala/zio/json/internal/UnsafeNumbers.scala b/zio-json/js/src/main/scala/zio/json/internal/UnsafeNumbers.scala index 70bb1b77..d4a3d626 100644 --- a/zio-json/js/src/main/scala/zio/json/internal/UnsafeNumbers.scala +++ b/zio-json/js/src/main/scala/zio/json/internal/UnsafeNumbers.scala @@ -195,7 +195,6 @@ object UnsafeNumbers { } } } - if ((hiM10 eq null) && loDigits == 0) throw UnsafeNumber if ((current | 0x20) == 'e') { current = in.readChar().toInt val negateExp = current == '-' @@ -213,15 +212,20 @@ object UnsafeNumbers { } ) throw UnsafeNumber } - if (negateExp) e10 += exp - else if (exp != -2147483648) e10 -= exp + if (negateExp) { + e10 += exp + if (e10 > 0) throw UnsafeNumber + } else if (exp != -2147483648) e10 -= exp else throw UnsafeNumber } if (consume && current != -1) throw UnsafeNumber if (hiM10 eq null) { + if (loDigits == 0) throw UnsafeNumber if (negate) loM10 = -loM10 return java.math.BigDecimal.valueOf(loM10, -e10) } + val scale = loDigits + e10 + if (((loDigits ^ scale) & (e10 ^ scale)) < 0) throw UnsafeNumber toBigDecimal(hiM10, loM10, loDigits, e10, max_bits, negate) } @@ -235,12 +239,10 @@ object UnsafeNumbers { ): java.math.BigDecimal = { var loM10 = lo if (negate) loM10 = -loM10 - val bd = - if (loDigits != 0) java.math.BigDecimal.valueOf(loM10, -e10) - else java.math.BigDecimal.ZERO + val bd = java.math.BigDecimal.valueOf(loM10, -e10) if (hi eq null) return bd - var hiM10 = hi val scale = loDigits + e10 + var hiM10 = hi if (scale != 0) hiM10 = hiM10.scaleByPowerOfTen(scale) hiM10 = hiM10.add(bd) if (hiM10.unscaledValue.bitLength >= max_bits) throw UnsafeNumber @@ -300,7 +302,6 @@ object UnsafeNumbers { } } } - if ((hiM10 eq null) && loDigits == 0) throw UnsafeNumber if ((current | 0x20) == 'e') { current = in.readChar().toInt val negateExp = current == '-' @@ -318,12 +319,15 @@ object UnsafeNumbers { } ) throw UnsafeNumber } - if (negateExp) e10 += exp - else if (exp != -2147483648) e10 -= exp + if (negateExp) { + e10 += exp + if (e10 > 0) throw UnsafeNumber + } else if (exp != -2147483648) e10 -= exp else throw UnsafeNumber } if (consume && current != -1) throw UnsafeNumber if (hiM10 eq null) { + if (loDigits == 0) throw UnsafeNumber var x = if (e10 == 0) loM10.toFloat else { @@ -336,6 +340,8 @@ object UnsafeNumbers { if (negate) x = -x return x } + val scale = loDigits + e10 + if (((loDigits ^ scale) & (e10 ^ scale)) < 0) throw UnsafeNumber toBigDecimal(hiM10, loM10, loDigits, e10, max_bits, negate).floatValue } @@ -425,7 +431,6 @@ object UnsafeNumbers { } } } - if ((hiM10 eq null) && loDigits == 0) throw UnsafeNumber if ((current | 0x20) == 'e') { current = in.readChar().toInt val negateExp = current == '-' @@ -443,12 +448,15 @@ object UnsafeNumbers { } ) throw UnsafeNumber } - if (negateExp) e10 += exp - else if (exp != -2147483648) e10 -= exp + if (negateExp) { + e10 += exp + if (e10 > 0) throw UnsafeNumber + } else if (exp != -2147483648) e10 -= exp else throw UnsafeNumber } if (consume && current != -1) throw UnsafeNumber if (hiM10 eq null) { + if (loDigits == 0) throw UnsafeNumber var x = if (e10 == 0) loM10.toDouble else { @@ -465,6 +473,8 @@ object UnsafeNumbers { if (negate) x = -x return x } + val scale = loDigits + e10 + if (((loDigits ^ scale) & (e10 ^ scale)) < 0) throw UnsafeNumber toBigDecimal(hiM10, loM10, loDigits, e10, max_bits, negate).doubleValue } diff --git a/zio-json/jvm/src/main/scala/zio/json/internal/UnsafeNumbers.scala b/zio-json/jvm/src/main/scala/zio/json/internal/UnsafeNumbers.scala index ce269ae7..3f263624 100644 --- a/zio-json/jvm/src/main/scala/zio/json/internal/UnsafeNumbers.scala +++ b/zio-json/jvm/src/main/scala/zio/json/internal/UnsafeNumbers.scala @@ -195,7 +195,6 @@ object UnsafeNumbers { } } } - if ((hiM10 eq null) && loDigits == 0) throw UnsafeNumber if ((current | 0x20) == 'e') { current = in.readChar().toInt val negateExp = current == '-' @@ -213,15 +212,20 @@ object UnsafeNumbers { } ) throw UnsafeNumber } - if (negateExp) e10 += exp - else if (exp != -2147483648) e10 -= exp + if (negateExp) { + e10 += exp + if (e10 > 0) throw UnsafeNumber + } else if (exp != -2147483648) e10 -= exp else throw UnsafeNumber } if (consume && current != -1) throw UnsafeNumber if (hiM10 eq null) { + if (loDigits == 0) throw UnsafeNumber if (negate) loM10 = -loM10 return java.math.BigDecimal.valueOf(loM10, -e10) } + val scale = loDigits + e10 + if (((loDigits ^ scale) & (e10 ^ scale)) < 0) throw UnsafeNumber toBigDecimal(hiM10, loM10, loDigits, e10, max_bits, negate) } @@ -235,12 +239,10 @@ object UnsafeNumbers { ): java.math.BigDecimal = { var loM10 = lo if (negate) loM10 = -loM10 - val bd = - if (loDigits != 0) java.math.BigDecimal.valueOf(loM10, -e10) - else java.math.BigDecimal.ZERO + val bd = java.math.BigDecimal.valueOf(loM10, -e10) if (hi eq null) return bd - var hiM10 = hi val scale = loDigits + e10 + var hiM10 = hi if (scale != 0) hiM10 = hiM10.scaleByPowerOfTen(scale) hiM10 = hiM10.add(bd) if (hiM10.unscaledValue.bitLength >= max_bits) throw UnsafeNumber @@ -300,7 +302,6 @@ object UnsafeNumbers { } } } - if ((hiM10 eq null) && loDigits == 0) throw UnsafeNumber if ((current | 0x20) == 'e') { current = in.readChar().toInt val negateExp = current == '-' @@ -318,12 +319,15 @@ object UnsafeNumbers { } ) throw UnsafeNumber } - if (negateExp) e10 += exp - else if (exp != -2147483648) e10 -= exp + if (negateExp) { + e10 += exp + if (e10 > 0) throw UnsafeNumber + } else if (exp != -2147483648) e10 -= exp else throw UnsafeNumber } if (consume && current != -1) throw UnsafeNumber if (hiM10 eq null) { + if (loDigits == 0) throw UnsafeNumber var x = if (e10 == 0) loM10.toFloat else { @@ -336,6 +340,8 @@ object UnsafeNumbers { if (negate) x = -x return x } + val scale = loDigits + e10 + if (((loDigits ^ scale) & (e10 ^ scale)) < 0) throw UnsafeNumber toBigDecimal(hiM10, loM10, loDigits, e10, max_bits, negate).floatValue } @@ -425,7 +431,6 @@ object UnsafeNumbers { } } } - if ((hiM10 eq null) && loDigits == 0) throw UnsafeNumber if ((current | 0x20) == 'e') { current = in.readChar().toInt val negateExp = current == '-' @@ -443,12 +448,15 @@ object UnsafeNumbers { } ) throw UnsafeNumber } - if (negateExp) e10 += exp - else if (exp != -2147483648) e10 -= exp + if (negateExp) { + e10 += exp + if (e10 > 0) throw UnsafeNumber + } else if (exp != -2147483648) e10 -= exp else throw UnsafeNumber } if (consume && current != -1) throw UnsafeNumber if (hiM10 eq null) { + if (loDigits == 0) throw UnsafeNumber var x = if (e10 == 0) loM10.toDouble else { @@ -465,6 +473,8 @@ object UnsafeNumbers { if (negate) x = -x return x } + val scale = loDigits + e10 + if (((loDigits ^ scale) & (e10 ^ scale)) < 0) throw UnsafeNumber toBigDecimal(hiM10, loM10, loDigits, e10, max_bits, negate).doubleValue } diff --git a/zio-json/native/src/main/scala/zio/json/internal/UnsafeNumbers.scala b/zio-json/native/src/main/scala/zio/json/internal/UnsafeNumbers.scala index ce269ae7..3f263624 100644 --- a/zio-json/native/src/main/scala/zio/json/internal/UnsafeNumbers.scala +++ b/zio-json/native/src/main/scala/zio/json/internal/UnsafeNumbers.scala @@ -195,7 +195,6 @@ object UnsafeNumbers { } } } - if ((hiM10 eq null) && loDigits == 0) throw UnsafeNumber if ((current | 0x20) == 'e') { current = in.readChar().toInt val negateExp = current == '-' @@ -213,15 +212,20 @@ object UnsafeNumbers { } ) throw UnsafeNumber } - if (negateExp) e10 += exp - else if (exp != -2147483648) e10 -= exp + if (negateExp) { + e10 += exp + if (e10 > 0) throw UnsafeNumber + } else if (exp != -2147483648) e10 -= exp else throw UnsafeNumber } if (consume && current != -1) throw UnsafeNumber if (hiM10 eq null) { + if (loDigits == 0) throw UnsafeNumber if (negate) loM10 = -loM10 return java.math.BigDecimal.valueOf(loM10, -e10) } + val scale = loDigits + e10 + if (((loDigits ^ scale) & (e10 ^ scale)) < 0) throw UnsafeNumber toBigDecimal(hiM10, loM10, loDigits, e10, max_bits, negate) } @@ -235,12 +239,10 @@ object UnsafeNumbers { ): java.math.BigDecimal = { var loM10 = lo if (negate) loM10 = -loM10 - val bd = - if (loDigits != 0) java.math.BigDecimal.valueOf(loM10, -e10) - else java.math.BigDecimal.ZERO + val bd = java.math.BigDecimal.valueOf(loM10, -e10) if (hi eq null) return bd - var hiM10 = hi val scale = loDigits + e10 + var hiM10 = hi if (scale != 0) hiM10 = hiM10.scaleByPowerOfTen(scale) hiM10 = hiM10.add(bd) if (hiM10.unscaledValue.bitLength >= max_bits) throw UnsafeNumber @@ -300,7 +302,6 @@ object UnsafeNumbers { } } } - if ((hiM10 eq null) && loDigits == 0) throw UnsafeNumber if ((current | 0x20) == 'e') { current = in.readChar().toInt val negateExp = current == '-' @@ -318,12 +319,15 @@ object UnsafeNumbers { } ) throw UnsafeNumber } - if (negateExp) e10 += exp - else if (exp != -2147483648) e10 -= exp + if (negateExp) { + e10 += exp + if (e10 > 0) throw UnsafeNumber + } else if (exp != -2147483648) e10 -= exp else throw UnsafeNumber } if (consume && current != -1) throw UnsafeNumber if (hiM10 eq null) { + if (loDigits == 0) throw UnsafeNumber var x = if (e10 == 0) loM10.toFloat else { @@ -336,6 +340,8 @@ object UnsafeNumbers { if (negate) x = -x return x } + val scale = loDigits + e10 + if (((loDigits ^ scale) & (e10 ^ scale)) < 0) throw UnsafeNumber toBigDecimal(hiM10, loM10, loDigits, e10, max_bits, negate).floatValue } @@ -425,7 +431,6 @@ object UnsafeNumbers { } } } - if ((hiM10 eq null) && loDigits == 0) throw UnsafeNumber if ((current | 0x20) == 'e') { current = in.readChar().toInt val negateExp = current == '-' @@ -443,12 +448,15 @@ object UnsafeNumbers { } ) throw UnsafeNumber } - if (negateExp) e10 += exp - else if (exp != -2147483648) e10 -= exp + if (negateExp) { + e10 += exp + if (e10 > 0) throw UnsafeNumber + } else if (exp != -2147483648) e10 -= exp else throw UnsafeNumber } if (consume && current != -1) throw UnsafeNumber if (hiM10 eq null) { + if (loDigits == 0) throw UnsafeNumber var x = if (e10 == 0) loM10.toDouble else { @@ -465,6 +473,8 @@ object UnsafeNumbers { if (negate) x = -x return x } + val scale = loDigits + e10 + if (((loDigits ^ scale) & (e10 ^ scale)) < 0) throw UnsafeNumber toBigDecimal(hiM10, loM10, loDigits, e10, max_bits, negate).doubleValue } diff --git a/zio-json/shared/src/test/scala/zio/json/DecoderSpec.scala b/zio-json/shared/src/test/scala/zio/json/DecoderSpec.scala index 7fbbc4f1..0754c72b 100644 --- a/zio-json/shared/src/test/scala/zio/json/DecoderSpec.scala +++ b/zio-json/shared/src/test/scala/zio/json/DecoderSpec.scala @@ -83,6 +83,9 @@ object DecoderSpec extends ZIOSpecDefault { assert("-1.234567e9".fromJson[Float])(isRight(equalTo(-1.234567e9f))) && assert("1.234567e9".fromJson[Float])(isRight(equalTo(1.234567e9f))) && assert("\"-1.234567e9\"".fromJson[Float])(isRight(equalTo(-1.234567e9f))) && + assert("-1.23456789012345678901e-2147483648".fromJson[Float])(isLeft(equalTo("(expected a Float)"))) && + assert("123456789012345678901e+2147483647".fromJson[Float])(isLeft(equalTo("(expected a Float)"))) && + assert("-123456789012345678901e+2147483647".fromJson[Float])(isLeft(equalTo("(expected a Float)"))) && assert("\"Infinity\"".fromJson[Float])(isRight(equalTo(Float.PositiveInfinity))) && assert("\"+Infinity\"".fromJson[Float])(isRight(equalTo(Float.PositiveInfinity))) && assert("\"-Infinity\"".fromJson[Float])(isRight(equalTo(Float.NegativeInfinity))) && @@ -92,6 +95,9 @@ object DecoderSpec extends ZIOSpecDefault { test("double") { assert("-1.23456789012345e9".fromJson[Double])(isRight(equalTo(-1.23456789012345e9))) && assert("\"-1.23456789012345e9\"".fromJson[Double])(isRight(equalTo(-1.23456789012345e9))) && + assert("-1.23456789012345678901e-2147483648".fromJson[Double])(isLeft(equalTo("(expected a Double)"))) && + assert("123456789012345678901e+2147483647".fromJson[Double])(isLeft(equalTo("(expected a Double)"))) && + assert("-123456789012345678901e+2147483647".fromJson[Double])(isLeft(equalTo("(expected a Double)"))) && assert("\"Infinity\"".fromJson[Double])(isRight(equalTo(Double.PositiveInfinity))) && assert("\"+Infinity\"".fromJson[Double])(isRight(equalTo(Double.PositiveInfinity))) && assert("\"-Infinity\"".fromJson[Double])(isRight(equalTo(Double.NegativeInfinity))) && @@ -120,6 +126,17 @@ object DecoderSpec extends ZIOSpecDefault { isLeft(equalTo("(expected a BigDecimal with 256-bit mantissa)")) ) }, + test("BigDecimal exponent too large") { + assert("1.23456789012345678901e-2147483648".fromJson[BigDecimal])( + isLeft(equalTo("(expected a BigDecimal with 256-bit mantissa)")) + ) && + assert("123456789012345678901e+2147483647".fromJson[BigDecimal])( + isLeft(equalTo("(expected a BigDecimal with 256-bit mantissa)")) + ) && + assert("-123456789012345678901e+2147483647".fromJson[BigDecimal])( + isLeft(equalTo("(expected a BigDecimal with 256-bit mantissa)")) + ) + }, test("BigInteger") { assert("170141183460469231731687303715884105728".fromJson[BigInteger])( isRight(equalTo(new BigInteger("170141183460469231731687303715884105728"))) diff --git a/zio-json/shared/src/test/scala/zio/json/internal/SafeNumbersSpec.scala b/zio-json/shared/src/test/scala/zio/json/internal/SafeNumbersSpec.scala index 078126fa..cf013d2f 100644 --- a/zio-json/shared/src/test/scala/zio/json/internal/SafeNumbersSpec.scala +++ b/zio-json/shared/src/test/scala/zio/json/internal/SafeNumbersSpec.scala @@ -10,7 +10,7 @@ object SafeNumbersSpec extends ZIOSpecDefault { suite("SafeNumbers")( suite("BigDecimal")( test("valid big decimals") { - check(genBigDecimal)(i => assert(SafeNumbers.bigDecimal(i.toString, 2048))(isSome(equalTo(i)))) + check(genBigDecimal)(x => assert(SafeNumbers.bigDecimal(x.toString))(isSome(equalTo(x)))) }, test("invalid big decimals") { val invalidBigDecimalEdgeCases = List( @@ -80,7 +80,7 @@ object SafeNumbersSpec extends ZIOSpecDefault { check(Gen.fromIterable(inputs))(s => assert(SafeNumbers.bigInteger(s))(isNone)) }, test("valid big Integer") { - check(genBigInteger)(i => assert(SafeNumbers.bigInteger(i.toString, 2048))(isSome(equalTo(i)))) + check(genBigInteger)(x => assert(SafeNumbers.bigInteger(x.toString, 2048))(isSome(equalTo(x)))) }, test("invalid BigInteger") { check(genAlphaLowerString)(s => assert(SafeNumbers.bigInteger(s))(isNone)) @@ -88,33 +88,35 @@ object SafeNumbersSpec extends ZIOSpecDefault { ), suite("Byte")( test("valid Byte") { - check(Gen.byte(Byte.MinValue, Byte.MaxValue)) { b => - assert(SafeNumbers.byte(b.toString))(equalTo(ByteSome(b))) + check(Gen.byte(Byte.MinValue, Byte.MaxValue)) { x => + val r = SafeNumbers.byte(x.toString) + assert(r)(equalTo(ByteSome(x))) && assert(r.isEmpty)(equalTo(false)) } }, test("invalid Byte (numbers)") { - check(Gen.int.filter(i => i < Byte.MinValue || i > Byte.MaxValue)) { b => - assert(SafeNumbers.byte(b.toString))(equalTo(ByteNone)) + check(Gen.int.filter(x => x < Byte.MinValue || x > Byte.MaxValue)) { x => + assert(SafeNumbers.byte(x.toString))(equalTo(ByteNone)) } }, test("invalid Byte (text)") { - check(genAlphaLowerString)(b => assert(SafeNumbers.byte(b.toString))(equalTo(ByteNone))) + check(genAlphaLowerString)(s => assert(SafeNumbers.byte(s).isEmpty)(equalTo(true))) } ), suite("Double")( test("valid") { - check(Gen.double.filterNot(_.isNaN)) { d => - assert(SafeNumbers.double(d.toString))(equalTo(DoubleSome(d))) + check(Gen.double.filterNot(_.isNaN)) { x => + val r = SafeNumbers.double(x.toString) + assert(r)(equalTo(DoubleSome(x))) && assert(r.isEmpty)(equalTo(false)) } }, test("valid (from Int)") { - check(Gen.int)(i => assert(SafeNumbers.double(i.toString))(equalTo(DoubleSome(i.toDouble)))) + check(Gen.int)(x => assert(SafeNumbers.double(x.toString))(equalTo(DoubleSome(x.toDouble)))) }, test("valid (from Long)") { - check(Gen.long)(i => assert(SafeNumbers.double(i.toString))(equalTo(DoubleSome(i.toDouble)))) + check(Gen.long)(x => assert(SafeNumbers.double(x.toString))(equalTo(DoubleSome(x.toDouble)))) }, test("valid (from BigDecimal)") { - check(genBigDecimal)(i => assert(SafeNumbers.double(i.toString))(equalTo(DoubleSome(i.doubleValue)))) + check(genBigDecimal)(x => assert(SafeNumbers.double(x.toString))(equalTo(DoubleSome(x.doubleValue)))) }, test("invalid edge cases") { val inputs = List( @@ -133,7 +135,7 @@ object SafeNumbersSpec extends ZIOSpecDefault { "0." + "9" * 99 ) - check(Gen.fromIterable(inputs))(i => assert(SafeNumbers.double(i))(equalTo(DoubleNone))) + check(Gen.fromIterable(inputs))(s => assert(SafeNumbers.double(s))(equalTo(DoubleNone))) }, test("valid edge cases") { val inputs = List( @@ -185,22 +187,25 @@ object SafeNumbersSpec extends ZIOSpecDefault { assert(SafeNumbers.double("-Infinity"))(not(equalTo(DoubleNone))) }, test("invalid doubles (text)") { - check(genAlphaLowerString)(s => assert(SafeNumbers.double(s))(equalTo(DoubleNone))) + check(genAlphaLowerString)(s => assert(SafeNumbers.double(s).isEmpty)(equalTo(true))) } ), suite("Float")( test("valid") { - check(Gen.float.filterNot(_.isNaN))(d => assert(SafeNumbers.float(d.toString))(equalTo(FloatSome(d)))) + check(Gen.float.filterNot(_.isNaN)) { x => + val r = SafeNumbers.float(x.toString) + assert(r)(equalTo(FloatSome(x))) && assert(r.isEmpty)(equalTo(false)) + } }, test("large mantissa") { // https://github.com/zio/zio-json/issues/221 assert(SafeNumbers.float("1.199999988079071"))(equalTo(FloatSome(1.1999999f))) } @@ jvmOnly, test("valid (from Int)") { - check(Gen.int)(i => assert(SafeNumbers.float(i.toString))(equalTo(FloatSome(i.toFloat)))) + check(Gen.int)(x => assert(SafeNumbers.float(x.toString))(equalTo(FloatSome(x.toFloat)))) }, test("valid (from Long)") { - check(Gen.long)(i => assert(SafeNumbers.float(i.toString))(equalTo(FloatSome(i.toFloat)))) + check(Gen.long)(x => assert(SafeNumbers.float(x.toString))(equalTo(FloatSome(x.toFloat)))) }, test("invalid edge cases") { val inputs = List( @@ -218,7 +223,7 @@ object SafeNumbersSpec extends ZIOSpecDefault { "0." + "9" * 99 ) - check(Gen.fromIterable(inputs))(i => assert(SafeNumbers.float(i))(equalTo(FloatNone))) + check(Gen.fromIterable(inputs))(s => assert(SafeNumbers.float(s))(equalTo(FloatNone))) }, test("valid edge cases") { val inputs = List( @@ -272,15 +277,15 @@ object SafeNumbersSpec extends ZIOSpecDefault { } }, test("valid (from Double)") { - check(Gen.double.filterNot(_.isNaN)) { d => - assert(SafeNumbers.float(d.toString))(equalTo(FloatSome(d.toFloat))) + check(Gen.double.filterNot(_.isNaN)) { x => + assert(SafeNumbers.float(x.toString))(equalTo(FloatSome(x.toFloat))) } }, test("valid (from BigDecimal)") { check(genBigDecimal)(i => assert(SafeNumbers.float(i.toString))(equalTo(FloatSome(i.floatValue)))) }, test("invalid float (text)") { - check(genAlphaLowerString)(s => assert(SafeNumbers.float(s))(equalTo(FloatNone))) + check(genAlphaLowerString)(s => assert(SafeNumbers.float(s).isEmpty)(equalTo(true))) } ), suite("Int")( @@ -290,7 +295,10 @@ object SafeNumbersSpec extends ZIOSpecDefault { check(Gen.fromIterable(input))(x => assert(SafeNumbers.int(x))(equalTo(IntSome(x.toInt)))) }, test("valid") { - check(Gen.int)(d => assert(SafeNumbers.int(d.toString))(equalTo(IntSome(d)))) + check(Gen.int) { x => + val r = SafeNumbers.int(x.toString) + assert(r)(equalTo(IntSome(x))) && assert(r.isEmpty)(equalTo(false)) + } }, test("invalid (edge cases)") { val input = List( @@ -334,21 +342,26 @@ object SafeNumbersSpec extends ZIOSpecDefault { check(Gen.fromIterable(input))(x => assert(SafeNumbers.long(x))(equalTo(LongNone))) }, test("valid") { - check(Gen.long)(d => assert(SafeNumbers.long(d.toString))(equalTo(LongSome(d)))) + check(Gen.long) { x => + val r = SafeNumbers.long(x.toString) + assert(r)(equalTo(LongSome(x))) && assert(r.isEmpty)(equalTo(false)) + } }, test("invalid (out of range)") { - val outOfRange = genBigInteger - .filter(_.bitLength > 63) + val outOfRange = genBigInteger.filter(_.bitLength > 63) check(outOfRange)(x => assert(SafeNumbers.long(x.toString))(equalTo(LongNone))) }, test("invalid (text)") { - check(genAlphaLowerString)(s => assert(SafeNumbers.long(s))(equalTo(LongNone))) + check(genAlphaLowerString)(s => assert(SafeNumbers.long(s).isEmpty)(equalTo(true))) } ), suite("Short")( test("valid") { - check(Gen.short)(d => assert(SafeNumbers.short(d.toString))(equalTo(ShortSome(d)))) + check(Gen.short) { x => + val r = SafeNumbers.short(x.toString) + assert(r)(equalTo(ShortSome(x))) && assert(r.isEmpty)(equalTo(false)) + } }, test("invalid (out of range)") { check(Gen.int.filter(i => i < Short.MinValue || i > Short.MaxValue))(d => @@ -356,7 +369,7 @@ object SafeNumbersSpec extends ZIOSpecDefault { ) }, test("invalid (text)") { - check(genAlphaLowerString)(s => assert(SafeNumbers.short(s))(equalTo(ShortNone))) + check(genAlphaLowerString)(s => assert(SafeNumbers.short(s).isEmpty)(equalTo(true))) } ) )