From 3820733b61d4c29ecd6c300350ca4a8a26c5356b Mon Sep 17 00:00:00 2001 From: Kevin Lee Date: Thu, 7 Dec 2023 23:50:21 +1100 Subject: [PATCH] Close #63 - [refined4s-cats] Add CatsEq, CatsShow and CatsEqShow to derive Eq and Show from the actual type's Eq and Show --- .../refined4s/cats/derivation/CatsEq.scala | 13 ++ .../cats/derivation/CatsEqShow.scala | 10 ++ .../refined4s/cats/derivation/CatsShow.scala | 13 ++ .../cats/derivation/CatsEqShowSpec.scala | 154 ++++++++++++++++++ .../cats/derivation/CatsEqSpec.scala | 120 ++++++++++++++ .../cats/derivation/CatsShowSpec.scala | 67 ++++++++ 6 files changed, 377 insertions(+) create mode 100644 modules/refined4s-cats/shared/src/main/scala/refined4s/cats/derivation/CatsEq.scala create mode 100644 modules/refined4s-cats/shared/src/main/scala/refined4s/cats/derivation/CatsEqShow.scala create mode 100644 modules/refined4s-cats/shared/src/main/scala/refined4s/cats/derivation/CatsShow.scala create mode 100644 modules/refined4s-cats/shared/src/test/scala/refined4s/cats/derivation/CatsEqShowSpec.scala create mode 100644 modules/refined4s-cats/shared/src/test/scala/refined4s/cats/derivation/CatsEqSpec.scala create mode 100644 modules/refined4s-cats/shared/src/test/scala/refined4s/cats/derivation/CatsShowSpec.scala diff --git a/modules/refined4s-cats/shared/src/main/scala/refined4s/cats/derivation/CatsEq.scala b/modules/refined4s-cats/shared/src/main/scala/refined4s/cats/derivation/CatsEq.scala new file mode 100644 index 0000000..0528328 --- /dev/null +++ b/modules/refined4s-cats/shared/src/main/scala/refined4s/cats/derivation/CatsEq.scala @@ -0,0 +1,13 @@ +package refined4s.cats.derivation + +import cats.Eq +import refined4s.* + +/** @author Kevin Lee + * @since 2023-12-07 + */ +trait CatsEq[A: Eq] { + self: NewtypeBase[A] => + + inline given derivedEq: Eq[Type] = deriving[Eq] +} diff --git a/modules/refined4s-cats/shared/src/main/scala/refined4s/cats/derivation/CatsEqShow.scala b/modules/refined4s-cats/shared/src/main/scala/refined4s/cats/derivation/CatsEqShow.scala new file mode 100644 index 0000000..4f4d0e3 --- /dev/null +++ b/modules/refined4s-cats/shared/src/main/scala/refined4s/cats/derivation/CatsEqShow.scala @@ -0,0 +1,10 @@ +package refined4s.cats.derivation + +import refined4s.NewtypeBase + +/** @author Kevin Lee + * @since 2023-12-07 + */ +trait CatsEqShow[A] extends CatsEq[A] with CatsShow[A] { + self: NewtypeBase[A] => +} diff --git a/modules/refined4s-cats/shared/src/main/scala/refined4s/cats/derivation/CatsShow.scala b/modules/refined4s-cats/shared/src/main/scala/refined4s/cats/derivation/CatsShow.scala new file mode 100644 index 0000000..58d8c51 --- /dev/null +++ b/modules/refined4s-cats/shared/src/main/scala/refined4s/cats/derivation/CatsShow.scala @@ -0,0 +1,13 @@ +package refined4s.cats.derivation + +import cats.Show +import refined4s.* + +/** @author Kevin Lee + * @since 2023-12-07 + */ +trait CatsShow[A: Show] { + self: NewtypeBase[A] => + + given derivedShow: Show[Type] = deriving[Show] +} diff --git a/modules/refined4s-cats/shared/src/test/scala/refined4s/cats/derivation/CatsEqShowSpec.scala b/modules/refined4s-cats/shared/src/test/scala/refined4s/cats/derivation/CatsEqShowSpec.scala new file mode 100644 index 0000000..ab1b403 --- /dev/null +++ b/modules/refined4s-cats/shared/src/test/scala/refined4s/cats/derivation/CatsEqShowSpec.scala @@ -0,0 +1,154 @@ +package refined4s.cats.derivation + +import cats.* +import cats.syntax.all.* +import hedgehog.* +import hedgehog.runner.* +import refined4s.* +import refined4s.cats.derivation.CatsShowSpec.{MyRefinedNewtype, MyRefinedType} + +/** @author Kevin Lee + * @since 2023-12-07 + */ +object CatsEqShowSpec extends Properties { + override def tests: List[Test] = List( + property("Given Newtype with CatsEq. Newtype[A](a) === Newtype[A](a) should return true", testEqNewtypeSame), + property( + "Given Newtype with CatsEq. Newtype[A](a) =!= Newtype[A](not a) should return true", + testEqNewtypeDifferent, + ), + property( + "Given Refined with CatsEq. Refined[A](a) === Refined[A](a) should return true", + testEqRefinedSame, + ), + property( + "Given Refined with CatsEq. Refined[A](a) =!= Refined[A](not a) should return true", + testEqRefinedDifferent, + ), + property( + "Given Newtype[Refined] with CatsEq. Newtype[Refined[A]](a) === Newtype[Refined[A]](a) should return true", + testEqNewtypeRefinedSame, + ), + property( + "Given Newtype[Refined] with CatsEq. Newtype[Refined[A]](a) =!= Newtype[Refined[A]](not a) should return true", + testEqNewtypeRefinedDifferent, + ), + property( + "Given Newtype with CatsShow. Newtype[A].show(a) should return the same String as Show[A].show(a)", + testShowNewtype, + ), + property( + "Given Refined with CatsShow. Refined[A].show(a) should return the same String as Show[A].show(a)", + testShowRefined, + ), + property( + "Given Newtype[Refined] with CatsShow. Newtype[Refined[A]].show(a) should return the same String as Show[A].show(a)", + testShowNewtypeRefined, + ), + ) + + def testEqNewtypeSame: Property = + for { + s <- Gen.string(Gen.unicode, Range.linear(0, 10)).log("s") + } yield { + val a = MyNewtype(s) + val b = a + Result.diffNamed("MyNewtype(value) === MyNewtype(value)", a, b)(_ === _) + } + + def testEqNewtypeDifferent: Property = + for { + s1 <- Gen.string(Gen.unicode, Range.linear(0, 10)).log("s1") + s2 <- Gen.string(Gen.unicode, Range.linear(0, 10)).map(s1 + "-" + _).log("s2") + } yield { + val a = MyNewtype(s1) + val b = MyNewtype(s2) + Result.diffNamed("MyNewtype(value) =!= MyNewtype(value)", a, b)(_ =!= _) + } + + def testEqRefinedSame: Property = + for { + s <- Gen.string(Gen.unicode, Range.linear(1, 10)).log("s") + } yield { + val a = MyRefinedType.unsafeFrom(s) + val b = a + Result.diffNamed(s"MyRefinedType(${a.value}) === MyRefinedType(${b.value})", a, b)(_ === _) + } + + def testEqRefinedDifferent: Property = + for { + s1 <- Gen.string(Gen.unicode, Range.linear(1, 10)).log("s1") + s2 <- Gen.string(Gen.unicode, Range.linear(1, 10)).map(s1 + "-" + _).log("s2") + } yield { + val a = MyRefinedType.unsafeFrom(s1) + val b = MyRefinedType.unsafeFrom(s2) + Result.diffNamed(s"MyRefinedType(${a.value}) =!= MyRefinedType(${b.value})", a, b)(_ =!= _) + } + + def testEqNewtypeRefinedSame: Property = + for { + s <- Gen.string(Gen.unicode, Range.linear(1, 10)).log("s") + } yield { + val a = MyRefinedNewtype(MyRefinedType.unsafeFrom(s)) + val b = a + Result.diffNamed( + s"MyRefinedNewtype(MyRefinedType(${a.value})) === MyRefinedNewtype(MyRefinedType(${b.value}))", + a, + b, + )(_ === _) + } + + def testEqNewtypeRefinedDifferent: Property = + for { + s1 <- Gen.string(Gen.unicode, Range.linear(1, 10)).log("s1") + s2 <- Gen.string(Gen.unicode, Range.linear(1, 10)).map(s1 + "-" + _).log("s2") + } yield { + val a = MyRefinedNewtype(MyRefinedType.unsafeFrom(s1)) + val b = MyRefinedNewtype(MyRefinedType.unsafeFrom(s2)) + Result.diffNamed( + s"MyRefinedNewtype(MyRefinedType(${a.value})) =!= MyRefinedNewtype(MyRefinedType(${b.value}))", + a, + b, + )(_ =!= _) + } + + def testShowNewtype: Property = + for { + s <- Gen.string(Gen.unicode, Range.linear(0, 10)).log("s") + } yield { + val a = MyNewtype(s) + a.show ==== Show[String].show(s) + } + + def testShowRefined: Property = + for { + s <- Gen.string(Gen.unicode, Range.linear(1, 10)).log("s") + } yield { + val a = MyRefinedType.unsafeFrom(s) + a.show ==== Show[String].show(s) + } + + def testShowNewtypeRefined: Property = + for { + s <- Gen.string(Gen.unicode, Range.linear(1, 10)).log("s") + } yield { + val a = MyRefinedNewtype(MyRefinedType.unsafeFrom(s)) + a.show ==== Show[String].show(s) + } + + type MyNewtype = MyNewtype.Type + object MyNewtype extends Newtype[String] with CatsEqShow[String] + + type MyRefinedType = MyRefinedType.Type + @SuppressWarnings(Array("org.wartremover.warts.Equals")) + object MyRefinedType extends Refined[String] with CatsEqShow[String] { + override inline def invalidReason(a: String): String = + "It has to be a non-empty String but got \"" + a + "\"" + + override inline def predicate(a: String): Boolean = a != "" + } + + type MyRefinedNewtype = MyRefinedNewtype.Type + object MyRefinedNewtype extends Newtype[MyRefinedType] with CatsEqShow[MyRefinedType] + +} diff --git a/modules/refined4s-cats/shared/src/test/scala/refined4s/cats/derivation/CatsEqSpec.scala b/modules/refined4s-cats/shared/src/test/scala/refined4s/cats/derivation/CatsEqSpec.scala new file mode 100644 index 0000000..c0811df --- /dev/null +++ b/modules/refined4s-cats/shared/src/test/scala/refined4s/cats/derivation/CatsEqSpec.scala @@ -0,0 +1,120 @@ +package refined4s.cats.derivation + +import cats.* +import cats.syntax.all.* +import hedgehog.* +import hedgehog.runner.* +import refined4s.* + +/** @author Kevin Lee + * @since 2023-12-07 + */ +object CatsEqSpec extends Properties { + override def tests: List[Test] = List( + property( + "Given Newtype with CatsEq. Newtype[A](a) === Newtype[A](a) should return true", + testEqNewtypeSame, + ), + property( + "Given Newtype with CatsEq. Newtype[A](a) =!= Newtype[A](not a) should return true", + testEqNewtypeDifferent, + ), + property( + "Given Refined with CatsEq. Refined[A](a) === Refined[A](a) should return true", + testEqRefinedSame, + ), + property( + "Given Refined with CatsEq. Refined[A](a) =!= Refined[A](not a) should return true", + testEqRefinedDifferent, + ), + property( + "Given Newtype[Refined] with CatsEq. Newtype[Refined[A]](a) === Newtype[Refined[A]](a) should return true", + testEqNewtypeRefinedSame, + ), + property( + "Given Newtype[Refined] with CatsEq. Newtype[Refined[A]](a) =!= Newtype[Refined[A]](not a) should return true", + testEqNewtypeRefinedDifferent, + ), + ) + + def testEqNewtypeSame: Property = + for { + s <- Gen.string(Gen.unicode, Range.linear(0, 10)).log("s") + } yield { + val a = MyNewtype(s) + val b = a + Result.diffNamed("MyNewtype(value) === MyNewtype(value)", a, b)(_ === _) + } + + def testEqNewtypeDifferent: Property = + for { + s1 <- Gen.string(Gen.unicode, Range.linear(0, 10)).log("s1") + s2 <- Gen.string(Gen.unicode, Range.linear(0, 10)).map(s1 + "-" + _).log("s2") + } yield { + val a = MyNewtype(s1) + val b = MyNewtype(s2) + Result.diffNamed("MyNewtype(value) =!= MyNewtype(value)", a, b)(_ =!= _) + } + + def testEqRefinedSame: Property = + for { + s <- Gen.string(Gen.unicode, Range.linear(1, 10)).log("s") + } yield { + val a = MyRefinedType.unsafeFrom(s) + val b = a + Result.diffNamed(s"MyRefinedType(${a.value}) === MyRefinedType(${b.value})", a, b)(_ === _) + } + + def testEqRefinedDifferent: Property = + for { + s1 <- Gen.string(Gen.unicode, Range.linear(1, 10)).log("s1") + s2 <- Gen.string(Gen.unicode, Range.linear(1, 10)).map(s1 + "-" + _).log("s2") + } yield { + val a = MyRefinedType.unsafeFrom(s1) + val b = MyRefinedType.unsafeFrom(s2) + Result.diffNamed(s"MyRefinedType(${a.value}) =!= MyRefinedType(${b.value})", a, b)(_ =!= _) + } + + def testEqNewtypeRefinedSame: Property = + for { + s <- Gen.string(Gen.unicode, Range.linear(1, 10)).log("s") + } yield { + val a = MyRefinedNewtype(MyRefinedType.unsafeFrom(s)) + val b = a + Result.diffNamed( + s"MyRefinedNewtype(MyRefinedType(${a.value})) === MyRefinedNewtype(MyRefinedType(${b.value}))", + a, + b, + )(_ === _) + } + + def testEqNewtypeRefinedDifferent: Property = + for { + s1 <- Gen.string(Gen.unicode, Range.linear(1, 10)).log("s1") + s2 <- Gen.string(Gen.unicode, Range.linear(1, 10)).map(s1 + "-" + _).log("s2") + } yield { + val a = MyRefinedNewtype(MyRefinedType.unsafeFrom(s1)) + val b = MyRefinedNewtype(MyRefinedType.unsafeFrom(s2)) + Result.diffNamed( + s"MyRefinedNewtype(MyRefinedType(${a.value})) =!= MyRefinedNewtype(MyRefinedType(${b.value}))", + a, + b, + )(_ =!= _) + } + + type MyNewtype = MyNewtype.Type + object MyNewtype extends Newtype[String] with CatsEq[String] + + type MyRefinedType = MyRefinedType.Type + @SuppressWarnings(Array("org.wartremover.warts.Equals")) + object MyRefinedType extends Refined[String] with CatsEq[String] { + override inline def invalidReason(a: String): String = + "It has to be a non-empty String but got \"" + a + "\"" + + override inline def predicate(a: String): Boolean = a != "" + } + + type MyRefinedNewtype = MyRefinedNewtype.Type + object MyRefinedNewtype extends Newtype[MyRefinedType] with CatsEq[MyRefinedType] + +} diff --git a/modules/refined4s-cats/shared/src/test/scala/refined4s/cats/derivation/CatsShowSpec.scala b/modules/refined4s-cats/shared/src/test/scala/refined4s/cats/derivation/CatsShowSpec.scala new file mode 100644 index 0000000..8fc1bc8 --- /dev/null +++ b/modules/refined4s-cats/shared/src/test/scala/refined4s/cats/derivation/CatsShowSpec.scala @@ -0,0 +1,67 @@ +package refined4s.cats.derivation + +import cats.* +import cats.syntax.all.* +import hedgehog.* +import hedgehog.runner.* +import refined4s.* + +/** @author Kevin Lee + * @since 2023-12-07 + */ +object CatsShowSpec extends Properties { + override def tests: List[Test] = List( + property( + "Given Newtype with CatsShow. Newtype[A].show(a) should return the same String as Show[A].show(a)", + testShowNewtype, + ), + property( + "Given Refined with CatsShow. Refined[A].show(a) should return the same String as Show[A].show(a)", + testShowRefined, + ), + property( + "Given Newtype[Refined] with CatsShow. Newtype[Refined[A]].show(a) should return the same String as Show[A].show(a)", + testShowNewtypeRefined, + ), + ) + + def testShowNewtype: Property = + for { + s <- Gen.string(Gen.unicode, Range.linear(0, 10)).log("s") + } yield { + val a = MyNewtype(s) + a.show ==== Show[String].show(s) + } + + def testShowRefined: Property = + for { + s <- Gen.string(Gen.unicode, Range.linear(1, 10)).log("s") + } yield { + val a = MyRefinedType.unsafeFrom(s) + a.show ==== Show[String].show(s) + } + + def testShowNewtypeRefined: Property = + for { + s <- Gen.string(Gen.unicode, Range.linear(1, 10)).log("s") + } yield { + val a = MyRefinedNewtype(MyRefinedType.unsafeFrom(s)) + a.show ==== Show[String].show(s) + } + + type MyNewtype = MyNewtype.Type + object MyNewtype extends Newtype[String] with CatsShow[String] + + type MyRefinedType = MyRefinedType.Type + @SuppressWarnings(Array("org.wartremover.warts.Equals")) + object MyRefinedType extends Refined[String] with CatsShow[String] { + override inline def invalidReason(a: String): String = + "It has to be a non-empty String but got \"" + a + "\"" + + override inline def predicate(a: String): Boolean = a != "" + } + + type MyRefinedNewtype = MyRefinedNewtype.Type + object MyRefinedNewtype extends Newtype[MyRefinedType] with CatsShow[MyRefinedType] + +}