Skip to content

Releases: kevin-lee/extras

v0.34.0

07 Mar 16:42
c54721e
Compare
Choose a tag to compare

0.34.0 - 2023-03-08

Internal Housekeeping

  • Upgrade effectie to 2.0.0-beta8 (#341)

v0.33.0

06 Mar 07:41
b22ca75
Compare
Choose a tag to compare

0.33.0 - 2023-03-06

New Features

  • [extras-circe] Add renameFields for Encoder to rename circe Json fields (#331)

    final case class Something(a: Int, b: String, price: BigDecimal, c: Boolean)
    object Something {
      import extras.circe.codecs.encoder._
      implicit val somethingEncoder: Encoder[Something] =
        deriveEncoder[Something].renameFields(
          "a"  -> "productNumber",
          "b"  -> "name",
          "c" -> "inStock",
        )
    }
    
    import io.circe.syntax._
    Something(1, "Vibranium Shield", BidDecimal(9999999), false).asJson.spaces2
    {
      "id": 1,
      "name": "Vibranium Shield",
      "price": 9999999,
      "inStock": false
    }
    • [extras-circe] renameFields for Encoder should fail encoding when new name conflicts with the existing name (#335)
      final case class Something(n: Int, s: String, name: String)
      object Something {
      
        import extras.circe.codecs.encoder._
      
        implicit val somethingEncoder: Encoder[Something] =
          deriveEncoder[Something].renameFields(
            "n" -> "productNumber",
            "s" -> "name", // Something already has a field named "name"
          )
      }
      
      io.circe.syntax._
      Something(1, "some name", "another name").asJson
      // This fails with extras.circe.codecs.encoder.NamingConflictError
  • [extras-circe] Add renameFields for Decoder to rename circe Json fields (#333)

    final case class Something(a: Int, b: String, price: BigDecimal, c: Boolean)
    object Something {
      import extras.circe.codecs.decoder._
    
      implicit val somethingDecoder: Decoder[Something] =
        deriveDecoder[Something].renameFields(
          "a" -> "productNumber",
          "b" -> "name",
          "c" -> "inStock",
        )
    }
    val json = """{
      "productNumber": 1,
      "name": "Vibranium Shield",
      "price": 9999999,
      "inStock": false
    }"""
    
    import io.circe.parser._
    decode[Something](json)
    // Either[Error, Something] = Right(Something(a = 1, b = "Vibranium Shield", price = 9999999, c = false))
  • [extras-circe] Add renameFields for Codec to rename circe Json fields (#336)

    final case class Something(a: Int, b: String, price: BigDecimal, c: Boolean)
    object Something {
      import extras.circe.codecs.codec._
      implicit val somethingEncoder: Codec[Something] =
        deriveCodec[Something].renameFields(
          "a"  -> "productNumber",
          "b"  -> "name",
          "c" -> "inStock",
        )
    }
    
    val something = Something(1, "Vibranium Shield", BidDecimal(9999999), false)

    Encoding:

    import io.circe.syntax._
    something.asJson.spaces2
    {
      "id": 1,
      "name": "Vibranium Shield",
      "price": 9999999,
      "inStock": false
    }

    Decoding:

    val json = """{
      "productNumber": 1,
      "name": "Vibranium Shield",
      "price": 9999999,
      "inStock": false
    }"""
    
    import io.circe.parser._
    decode[Something](json)
    // Either[Error, Something] = Right(Something(a = 1, b = "Vibranium Shield", price = 9999999, c = false))

Improvement

  • Optimize StubToolsCats and StubToolsFx (#329)

v0.32.0

26 Feb 10:59
6789fbb
Compare
Choose a tag to compare

0.32.0 - 2023-02-26

Internal Housekeeping

  • Upgrade effectie to 2.0.0-beta7 (#322)
  • Upgrade sbt-plugins (#324)
    • sbt-scalafmt to 2.5.0
      • scalafmt to 3.7.2
    • sbt-scoverage to 2.0.7
    • sbt-mdoc to 2.3.7
    • sbt-tpolecat to 0.4.2
    • scalafix-rules to 0.2.15
    • sbt-scalajs to 1.13.0

v0.31.0

14 Feb 14:33
27c69c8
Compare
Choose a tag to compare

0.31.0 - 2023-02-15

New Feature

  • Add extras-testing-tools-effectie with StubToolsFx (#319)

extras-testing-tools-effectie

import cats._
import effectie.core._

trait TestType[F[*]] {
  def foo(n: Int): F[Int]
  def bar(n: Int): F[Int]
}

object TestTypeStub {
  def apply[F[*]: Fx: Monad](f4Foo: => Option[Int => Int], f4Bar: => Option[Int => F[Int]]): TestType[F] =
    new TestType[F] {

      override def foo(n: Int): F[Int] =
        StubToolsFx.stub(f4Foo).map(_(n))

      override def bar(n: Int): F[Int] =
        StubToolsFx.stub(f4Bar).flatMap(_(n))
    }
}

If the stub function is missing, it will be F containing MissingStubException saying where the missing stub is.

For example, the following code

import effectie.instances.ce2.fx._

val testType = TestTypeStub[IO](none, f.some)

testType.foo(n).catchNonFatalThrowable // IO[Either[Throwable, Int]]

results in IO[Either[Throwable, Int]] where Either is Left(MissingStubException) with the information like the following.

>> Missing Stub implementation at
>>   extras.testing.TestTypeStub$$anon$1.$anonfun$foo$1(TestType.scala:16)
>>   ---
>>   Details:
>>   extras.testing.TestTypeStub$$anon$1.$anonfun$foo$1(TestType.scala:16)
       at cats.ApplicativeError.fromOption(ApplicativeError.scala:318)
       at cats.ApplicativeError.fromOption$(ApplicativeError.scala:315)
       at cats.effect.IOLowPriorityInstances$IOEffect.fromOption(IO.scala:865)
       at extras.testing.StubToolsFx$StubToolsFxPartiallyApplied$.$anonfun$apply$1(StubToolsFx.scala:24)
       at cats.effect.internals.IORunLoop$.step(IORunLoop.scala:319)
       at cats.effect.IO.unsafeRunTimed(IO.scala:338)
       at cats.effect.IO.unsafeRunSync(IO.scala:256)
       at extras.testing.StubToolsFxSpec$.$anonfun$testStubIntToIntWithIOFailureCase$1(StubToolsFxSpec.scala:79)
       at extras.testing.StubToolsFxSpec$.$anonfun$testStubIntToIntWithIOFailureCase$1$adapted(StubToolsFxSpec.scala:71)
       at scala.Option.map(Option.scala:242)
       at hedgehog.core.PropertyT.$anonfun$map$1(PropertyT.scala:64)
       at scala.Option.map(Option.scala:242)
       at hedgehog.core.GenT.$anonfun$map$2(GenT.scala:12)
       at hedgehog.core.TreeImplicits1$$anon$1.map(Tree.scala:35)
       at hedgehog.core.TreeImplicits1$$anon$1.map(Tree.scala:33)
       at hedgehog.core.Tree.map(Tree.scala:16)
       at hedgehog.core.GenT.$anonfun$map$1(GenT.scala:12)
       at hedgehog.core.PropertyTReporting.loop$1(PropertyT.scala:205)
       at hedgehog.core.PropertyTReporting.report(PropertyT.scala:231)
       at hedgehog.core.PropertyTReporting.report$(PropertyT.scala:182)
       at hedgehog.package$$anon$2.report(package.scala:32)
       at hedgehog.PropertyTOps.check(Property.scala:38)
       at hedgehog.PropertyTOps.check$(Property.scala:37)
       at hedgehog.package$Property$.check(package.scala:19)
       at hedgehog.runner.Properties.$anonfun$main$1(Properties.scala:19)
       at hedgehog.runner.Properties.$anonfun$main$1$adapted(Properties.scala:18)
       at scala.collection.immutable.List.foreach(List.scala:333)
       at hedgehog.runner.Properties.main(Properties.scala:18)
       at extras.testing.StubToolsFxSpec.main(StubToolsFxSpec.scala)

v0.30.0

11 Feb 14:30
f597813
Compare
Choose a tag to compare

0.30.0 - 2023-02-12

Internal Housekeeping

  • Upgrade effectie to 2.0.0-beta6 (#314)

v0.29.0

10 Feb 16:36
0337410
Compare
Choose a tag to compare

0.29.0 - 2023-02-11

New Feature

  • Add [extras-testing-tools] and [extras-testing-tools-cats] with tools to use stub (#307)

extras-testing-tools

import extras.testing.StubTools

trait TestType {
  def foo(n: Int): Int
}

object TestTypeStub {
  def apply(f: => Option[Int => Int]): TestType = new TestType {
    override def foo(n: Int): Int =
      f.fold[Int](throw StubTools.missing)(_(n))
  }
}

If the stub function is missing, it will throw a MissingStubException saying where the missing stub is.
For example,

TestTypeStub(None)

results in

>> Missing Stub implementation at
>>   extras.testing.TestTypeStub$$anon$1.$anonfun$foo$1(TestType.scala:11)
>>   ---
>>   Details:
>>   extras.testing.TestTypeStub$$anon$1.$anonfun$foo$1(TestType.scala:11)
       at scala.Option.fold(Option.scala:263)
       at extras.testing.TestTypeStub$$anon$1.foo(TestType.scala:11)
       at extras.testing.StubToolsSpec$.$anonfun$testMissingMissingCase$2(StubToolsSpec.scala:33)
       ...

extras-testing-tools-cats

import cats._
import cats.syntax.all._
import extras.testing.StubToolsCats

trait TestType[F[*]] {
  def foo(n: Int): F[Int]
  def bar(n: Int): F[Int]
}

object TestTypeStub {
  def apply[F[*]: MonadThrow](f4Foo: => Option[Int => Int], f4Bar: => Option[Int => F[Int]]): TestType[F] =
    new TestType[F] {

      override def foo(n: Int): F[Int] =
        StubToolsCats.stub(f4Foo).map(_(n))

      override def bar(n: Int): F[Int] =
        StubToolsCats.stub(f4Bar).flatMap(_(n))
    }
}

If the stub function is missing, it will be F containing MissingStubException saying where the missing stub is.

For example, the following code

val testType = TestTypeStub[IO](none, f.some)

testType.foo(n).attempt // Either[Throwable, Int]

results in IO[Either[Throwable, Int]] where Either is Left(MissingStubException) with the information like the following.

>> Missing Stub implementation at
>>   extras.testing.TestTypeStub$$anon$1.$anonfun$foo$1(TestType.scala:16)
>>   ---
>>   Details:
>>   extras.testing.TestTypeStub$$anon$1.$anonfun$foo$1(TestType.scala:16)
       at cats.ApplicativeError.fromOption(ApplicativeError.scala:318)
       at cats.ApplicativeError.fromOption$(ApplicativeError.scala:315)
       at cats.effect.IOLowPriorityInstances$IOEffect.fromOption(IO.scala:865)
       at extras.testing.StubToolsCats$StubToolsCatsPartiallyApplied$.$anonfun$apply$1(StubToolsCats.scala:24)
       ...

Internal Housekeeping

  • Upgrade effectie to 2.0.0-beta5 (#310)

v0.28.0

24 Jan 17:39
59656df
Compare
Choose a tag to compare

0.28.0 - 2023-01-25

Changes

  • [extras-fs2-v2-text][extras-fs2-v3-text]: Replace the implicit param, Sync, for utf8String from extras.fs2.text.syntax with Compiler (#304)

    So the following method

    def utf8String(implicit sync: Sync[F]): F[String]

    has been changed to

    def utf8String(implicit compiler: Compiler[F, F]): F[String]

    fs2 v2: fs2.Stream.Compiler / fs2 v3: fs2.Compiler

v0.27.0

15 Jan 10:46
233acdf
Compare
Choose a tag to compare

0.27.0 - 2023-01-15

New Features

  • [extras-circe]: Add an extension method to circe Encoder to add more fields (#291)
    import io.circe._
    import io.circe.generic.semiauto._
    
    final case class Something(n: Int) {
      def name: String = "Blah"
    }
    object Something {
      implicit val somethingEncoder: Encoder[Something] = deriveEncoder
    }
    
    Something(1).asJson.spaces2
    results in
    {
      "n": 1
    }
    There should be an easy way to add the name field to have JSON like
    {
      "n": 1,
      "name": "Blah"
    }
    For instance,
    import extras.circe.codecs.encoder._
    
    object Something {
      implicit val somethingEncoder: Encoder[Something] =
        deriveEncoder.withFields { something =>
          List("name" -> something.name.asJson)
        }
    }
  • [extras-fs2-v2-text][extras-fs2-v3-text]: Add fs2 syntax to compile Stream[F, Byte] to F[String] (#295)
    import fs2.Stream
    import extras.fs2.text.syntax._
    
    val stream: Stream[F, Byte] = ...
    stream.utf8String // F[String]
    import org.http4s.Response
    import extras.fs2.text.syntax._
    
    val response: Response[F] = ...
    response.body.utf8String // F[String]

v0.26.0

05 Dec 11:46
520252c
Compare
Choose a tag to compare

0.26.0 - 2022-12-05

New Feature

  • [extras-render-refined] Add newtype + refined support (#277)

    import eu.timepit.refined.types.string.NonEmptyString
    import extras.render.Render
    import io.estatico.newtype.macros.newtype
    
    import extras.render.refined._
    
    @newtype case class Name(value: NonEmptyString)
    object Name {
    implicit val nameRender: Render[Name] = deriving
    }

    instead of

    @newtype case class Name(value: NonEmptyString)
    object Name {
      implicit val nameRender: Render[Name] = Render.render(_.value.value)
    }
  • [extras-doobie-tools] Add doobie tools to run SQL query (#283)

    • extras-doobie-tools-ce2
    • extras-doobie-tools-ce3

    This is meant to be used for testing.

    e.g.)

    DbTools.fetchSingleRow[F][Example](
      sql"""
        SELECT id, name, note
          FROM db_tools_test.example
      """
    )(transactor) // F[Option[Example]]

    DbTools.fetchMultipleRows[F][Example](
    sql"""
      SELECT id, name, note
        FROM db_tools_test.example
      """
    )(transactor) // F[List[Example]]

    DbTools.updateSingle[F](
      sql"""
        INSERT INTO db_tools_test.example (id, name, note) VALUES (${example.id}, ${example.name}, ${example.note})
      """
    )(transactor) // F[Int]
    

    // val examples: List[Example] = ???
    DbTools.updateMultiple[F][Example](
      "INSERT INTO db_tools_test.example (id, name, note) VALUES (?, ?, ?)"
    )(examples)(transactor) // F[Int]

    with

    implicit val writeExample: Write[Example] =
      Write[(Int, String, String)].contramap(example =>
        (example.id.value, example.name.value, example.note.value)
      )
    
    implicit val readExample: Read[Example] =
      Read[(Int, String, String)].map {
        case (id, name, note) =>
          Example(Example.Id(id), Example.Name(name), Example.Note(note))
      }

    Or in Scala 3,

    given readExample: Read[Example] =
      Read[(Int, String, String)].map {
        case (id, name, note) =>
          Example(Example.Id(id), Example.Name(name), Example.Note(note))
      }
    
    given writeExample: Write[Example] =
      Write[(Int, String, String)].contramap(example =>
        (example.id.value, example.name.value, example.note.value)
      )

Scala + Library Version Up

  • Bump Scala / bump libraries (#284)
    • Scala 2.12.13 => 2.12.17
    • Scala 2.13.6 => 2.13.10
    • Scala 3.0.0 => 3.1.3
    • Cats 2.6.1 and 2.7.0 => 2.8.0
    • Cats Effect 2.5.4 => 2.5.5
    • Cats Effect 3.2.9 => 3.3.14

v0.25.0

20 Nov 09:43
a925345
Compare
Choose a tag to compare

0.25.0 - 2022-11-20

New Feature

extras-type-info

  • [extras-type-info] Release extras-type-info (#273)
    sealed trait Aaa
    object Aaa {
      final case class Bbb(n: Int) extends Aaa
      case object Ccc extends Aaa
    
      def bbb(n: Int): Aaa = Bbb(n)
      def ccc: Aaa         = Ccc
    }
    
    final case class Something[A](a: A)
    
    Aaa.bbb(123).nestedClassName
    // Aaa.Bbb
    
    Aaa.ccc.nestedTypeName
    // Aaa.Ccc

It was supposed to be released in 0.24.0 but wasn't included by mistake.