From 80160c6c5efee8c31fa528742e0da6e0c7fb9f18 Mon Sep 17 00:00:00 2001 From: David Geirola Date: Thu, 25 May 2023 11:01:28 +0200 Subject: [PATCH 1/2] WIP --- .../test/scala/cats/xml/XmlDataSuite.scala | 34 +++++++++++++++++++ .../test/scala/cats/xml/XmlNumberSuite.scala | 14 -------- .../xml/testing/VeryLongNumericString.scala | 15 ++++++++ 3 files changed, 49 insertions(+), 14 deletions(-) create mode 100644 core/src/test/scala/cats/xml/XmlDataSuite.scala create mode 100644 core/src/test/scala/cats/xml/testing/VeryLongNumericString.scala diff --git a/core/src/test/scala/cats/xml/XmlDataSuite.scala b/core/src/test/scala/cats/xml/XmlDataSuite.scala new file mode 100644 index 0000000..2ad2157 --- /dev/null +++ b/core/src/test/scala/cats/xml/XmlDataSuite.scala @@ -0,0 +1,34 @@ +package cats.xml + +import cats.xml.testing.VeryLongNumericString +import org.scalacheck.Arbitrary +import org.scalacheck.Prop.forAll + +import scala.reflect.ClassTag + +class XmlDataSuite extends munit.ScalaCheckSuite { + + testFromDataString[String] + testFromDataString[Char] + testFromDataString[Boolean] + testFromDataString[Int] + testFromDataString[Short] + testFromDataString[Long] + testFromDataString[Float] + testFromDataString[Double] + testFromDataString[BigInt] + testFromDataString[BigDecimal] + testFromDataString[VeryLongNumericString] + + private def testFromDataString[T: Arbitrary](implicit + c: ClassTag[T] + ): Unit = + property(s"Xml.fromDataString works with ${c.runtimeClass.getSimpleName}") { + forAll { (value: T) => + assertEquals( + obtained = Xml.fromDataString(value.toString).toString, + expected = value.toString + ) + } + } +} diff --git a/core/src/test/scala/cats/xml/XmlNumberSuite.scala b/core/src/test/scala/cats/xml/XmlNumberSuite.scala index 48ca58a..e69de29 100644 --- a/core/src/test/scala/cats/xml/XmlNumberSuite.scala +++ b/core/src/test/scala/cats/xml/XmlNumberSuite.scala @@ -1,14 +0,0 @@ -package cats.xml - -//TODO -class XmlNumberSuite extends munit.ScalaCheckSuite { - - test("") { - - Console.println(Xml.fromDataString("5340595900475325933418219074917").getClass) - assertEquals( - obtained = Xml.fromDataString("5340595900475325933418219074917"), - expected = Xml.ofString("5340595900475325933418219074917") - ) - } -} diff --git a/core/src/test/scala/cats/xml/testing/VeryLongNumericString.scala b/core/src/test/scala/cats/xml/testing/VeryLongNumericString.scala new file mode 100644 index 0000000..559ee99 --- /dev/null +++ b/core/src/test/scala/cats/xml/testing/VeryLongNumericString.scala @@ -0,0 +1,15 @@ +package cats.xml.testing + +import org.scalacheck.{Arbitrary, Gen} + +case class VeryLongNumericString private (str: String) extends AnyVal +object VeryLongNumericString { + + private[VeryLongNumericString] def apply(str: String): VeryLongNumericString = + new VeryLongNumericString(str) + + implicit val arb: Arbitrary[VeryLongNumericString] = + Arbitrary( + Gen.numStr.suchThat(_.length > 31).map(VeryLongNumericString(_)) + ) +} From 8331b94b1104b420028df7b7c025a30d57911754 Mon Sep 17 00:00:00 2001 From: David Geirola Date: Thu, 25 May 2023 11:01:28 +0200 Subject: [PATCH 2/2] WIP --- core/src/main/scala/cats/xml/Xml.scala | 23 +++++---- core/src/main/scala/cats/xml/xmlData.scala | 35 ++++++++++++- .../test/scala/cats/xml/XmlDataSuite.scala | 51 +++++++++++++++++-- 3 files changed, 94 insertions(+), 15 deletions(-) diff --git a/core/src/main/scala/cats/xml/Xml.scala b/core/src/main/scala/cats/xml/Xml.scala index f89393a..1b160fa 100644 --- a/core/src/main/scala/cats/xml/Xml.scala +++ b/core/src/main/scala/cats/xml/Xml.scala @@ -64,27 +64,32 @@ object Xml { def ofString(value: String): XmlString = XmlString(value) def ofChar(value: Char): XmlChar = XmlChar(value) def ofBoolean(value: Boolean): XmlBool = XmlBool(value) - def ofByte(value: Byte): XmlNumber = XmlLong(value.toLong) - def ofShort(value: Short): XmlNumber = XmlLong(value.toLong) - def ofInt(value: Int): XmlNumber = XmlLong(value.toLong) + def ofByte(value: Byte): XmlNumber = XmlByte(value) + def ofShort(value: Short): XmlNumber = XmlShort(value) + def ofInt(value: Int): XmlNumber = XmlInt(value) def ofLong(value: Long): XmlNumber = XmlLong(value) def ofFloat(value: Float): XmlNumber = XmlFloat(value) def ofDouble(value: Double): XmlNumber = XmlDouble(value) - def ofBigInt(value: BigInt): XmlNumber = XmlBigDecimal(BigDecimal(value)) + def ofBigInt(value: BigInt): XmlNumber = XmlBigInt(value) def ofBigDecimal(value: BigDecimal): XmlNumber = XmlBigDecimal(value) def ofArray[T <: XmlData](value: Array[T]): XmlArray[T] = XmlArray(value) def ofSeq[T <: XmlData: ClassTag](value: Seq[T]): XmlArray[T] = XmlArray(value.toArray) def ofValues[T <: XmlData: ClassTag](value: T*): XmlArray[T] = XmlArray(value.toArray) - def fromNumberString(str: String): Option[XmlNumber] = + def fromNumberString(str: String): Option[XmlNumber] = { Try(BigDecimal.exact(str)).toOption.map { - case db if db.isValidByte => ofLong(db.toByteExact.toLong) - case db if db.isValidShort => ofLong(db.toShortExact.toLong) - case db if db.isValidInt => ofLong(db.toIntExact.toLong) + case db if db.isValidByte => ofByte(db.toByteExact) + case db if db.isValidShort => ofShort(db.toShortExact) + case db if db.isValidInt => ofInt(db.toIntExact) case bd if bd.isValidLong => ofLong(bd.toLongExact) case db if db.isDecimalFloat => ofFloat(db.floatValue) case db if db.isDecimalDouble => ofDouble(db.doubleValue) - case bd => ofBigDecimal(bd) + case bd => + bd.toBigIntExact match { + case Some(bi) => ofBigInt(bi) + case None => ofBigDecimal(bd) + } } + } def fromDataString(value: String): XmlData = { fromNumberString(value) diff --git a/core/src/main/scala/cats/xml/xmlData.scala b/core/src/main/scala/cats/xml/xmlData.scala index bb12290..36ba08a 100644 --- a/core/src/main/scala/cats/xml/xmlData.scala +++ b/core/src/main/scala/cats/xml/xmlData.scala @@ -39,14 +39,21 @@ object XmlData { sealed trait XmlNumber extends XmlData with Serializable { final def toBigDecimal: Option[BigDecimal] = this match { + case XmlByte(value) => Some(BigDecimal(value.toInt)) + case XmlShort(value) => Some(BigDecimal(value.toInt)) + case XmlInt(value) => Some(BigDecimal(value)) case XmlLong(value) => Some(BigDecimal(value)) case XmlFloat(value) => Some(new JavaBigDecimal(java.lang.Float.toString(value))) case XmlDouble(value) => Some(JavaBigDecimal.valueOf(value)) + case XmlBigInt(value) => Some(BigDecimal(value)) case XmlBigDecimal(value) => Some(value) } final def toBigInt: Option[BigInt] = this match { - case XmlLong(value) => Some(BigInt(value)) + case XmlByte(value) => Some(BigInt(value.toInt)) + case XmlShort(value) => Some(BigInt(value.toInt)) + case XmlInt(value) => Some(BigInt(value)) + case XmlLong(value) => Some(BigInt(value)) case XmlFloat(value) => Option(new JavaBigDecimal(java.lang.Float.toString(value))) .filter(XmlNumber.bigDecimalIsWhole) @@ -55,20 +62,29 @@ object XmlData { Option(JavaBigDecimal.valueOf(value)) .filter(XmlNumber.bigDecimalIsWhole) .map(bd => new BigInt(bd.toBigInteger)) + case XmlBigInt(value) => Some(value) case XmlBigDecimal(value) => value.toBigIntExact } final def toDouble: Double = this match { + case XmlByte(value) => value.toDouble + case XmlShort(value) => value.toDouble + case XmlInt(value) => value.toDouble case XmlLong(value) => value.toDouble case XmlFloat(value) => new JavaBigDecimal(java.lang.Float.toString(value)).doubleValue case XmlDouble(value) => value + case XmlBigInt(value) => value.doubleValue case XmlBigDecimal(value) => value.doubleValue } final def toFloat: Float = this match { + case XmlByte(value) => value.toFloat + case XmlShort(value) => value.toFloat + case XmlInt(value) => value.toFloat case XmlLong(value) => value.toFloat case XmlFloat(value) => value case XmlDouble(value) => value.toFloat + case XmlBigInt(value) => value.floatValue case XmlBigDecimal(value) => value.floatValue } @@ -94,7 +110,10 @@ object XmlData { } final def toLong: Option[Long] = this match { - case XmlLong(value) => Some(value) + case XmlByte(value) => Some(value.toLong) + case XmlShort(value) => Some(value.toLong) + case XmlInt(value) => Some(value.toLong) + case XmlLong(value) => Some(value) case XmlFloat(value) => Option(new JavaBigDecimal(java.lang.Float.toString(value))) .filter(XmlNumber.bigDecimalIsValidLong) @@ -103,6 +122,7 @@ object XmlData { Option(JavaBigDecimal.valueOf(value)) .filter(XmlNumber.bigDecimalIsValidLong) .map(_.longValue) + case XmlBigInt(value) => Try(value.toLong).toOption case XmlBigDecimal(value) => Try(value.toLongExact).toOption } } @@ -120,9 +140,13 @@ object XmlData { ) <= 0 } + private[xml] final case class XmlByte(value: Byte) extends XmlNumber + private[xml] final case class XmlShort(value: Short) extends XmlNumber + private[xml] final case class XmlInt(value: Int) extends XmlNumber private[xml] final case class XmlLong(value: Long) extends XmlNumber private[xml] final case class XmlFloat(value: Float) extends XmlNumber private[xml] final case class XmlDouble(value: Double) extends XmlNumber + private[xml] final case class XmlBigInt(value: BigInt) extends XmlNumber private[xml] final case class XmlBigDecimal(value: BigDecimal) extends XmlNumber // ------------------------------------// @@ -132,14 +156,21 @@ object XmlData { case XmlChar(value) => value.toString case XmlBool(value) => value.toString case XmlArray(value) => value.mkString(",") + case XmlByte(value) => value.toString + case XmlShort(value) => value.toString + case XmlInt(value) => value.toString case XmlLong(value) => value.toString case XmlFloat(value) => value.toString case XmlDouble(value) => value.toString + case XmlBigInt(value) => value.toString case XmlBigDecimal(value) => value.toString } implicit val order: Order[XmlNumber] = { Order.from { + case (XmlByte(x), XmlByte(y)) => x.compareTo(y) + case (XmlShort(x), XmlShort(y)) => x.compareTo(y) + case (XmlInt(x), XmlInt(y)) => x.compareTo(y) case (XmlLong(x), XmlLong(y)) => x.compareTo(y) case (XmlDouble(x), XmlDouble(y)) => java.lang.Double.compare(x, y) case (XmlFloat(x), XmlFloat(y)) => java.lang.Float.compare(x, y) diff --git a/core/src/test/scala/cats/xml/XmlDataSuite.scala b/core/src/test/scala/cats/xml/XmlDataSuite.scala index 2ad2157..abf9daa 100644 --- a/core/src/test/scala/cats/xml/XmlDataSuite.scala +++ b/core/src/test/scala/cats/xml/XmlDataSuite.scala @@ -8,6 +8,18 @@ import scala.reflect.ClassTag class XmlDataSuite extends munit.ScalaCheckSuite { + // fromNumberString + testFromNumberString[Int]() + testFromNumberString[Short]() + testFromNumberString[Long]() + testFromNumberString[Float]() + testFromNumberString[Double]() + testFromNumberString[BigInt]() + testFromNumberString[BigDecimal]() + testFromNumberString[String](shouldFail = true) + testFromNumberString[VeryLongNumericString](shouldFail = true) + + // fromDataString testFromDataString[String] testFromDataString[Char] testFromDataString[Boolean] @@ -20,15 +32,46 @@ class XmlDataSuite extends munit.ScalaCheckSuite { testFromDataString[BigDecimal] testFromDataString[VeryLongNumericString] + private def testFromNumberString[T: Arbitrary](shouldFail: Boolean = false)(implicit + c: ClassTag[T] + ): Unit = + if (shouldFail) + property(s"Xml.fromNumberString return None with ${c.runtimeClass.getSimpleName}") { + forAll { (value: T) => + assertEquals( + obtained = Xml.fromNumberString(value.toString), + expected = None + ) + } + } + else + property(s"Xml.fromNumberString works with ${c.runtimeClass.getSimpleName}") { + forAll { (value: T) => + assertEquals( + Xml.fromNumberString(value.toString).get.toBigDecimal, + Some(BigDecimal(value.toString)) + ) + } + } + private def testFromDataString[T: Arbitrary](implicit c: ClassTag[T] ): Unit = property(s"Xml.fromDataString works with ${c.runtimeClass.getSimpleName}") { forAll { (value: T) => - assertEquals( - obtained = Xml.fromDataString(value.toString).toString, - expected = value.toString - ) + Xml.fromDataString(value.toString) match { + case number: XmlData.XmlNumber => + assertEquals( + number.toBigDecimal, + Some(BigDecimal(value.toString)) + ) + case other => + assertEquals( + obtained = other.toString, + expected = value.toString + ) + } + } } }