Releases: kevin-lee/extras
v0.34.0
v0.33.0
0.33.0 - 2023-03-06
New Features
-
[
extras-circe
] AddrenameFields
forEncoder
to rename circeJson
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
forEncoder
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
] AddrenameFields
forDecoder
to rename circeJson
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
] AddrenameFields
forCodec
to rename circeJson
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
andStubToolsFx
(#329)
v0.32.0
v0.31.0
0.31.0 - 2023-02-15
New Feature
- Add
extras-testing-tools-effectie
withStubToolsFx
(#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
v0.29.0
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
to2.0.0-beta5
(#310)
v0.28.0
0.28.0 - 2023-01-25
Changes
-
[
extras-fs2-v2-text
][extras-fs2-v3-text
]: Replace the implicit param,Sync
, forutf8String
fromextras.fs2.text.syntax
withCompiler
(#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
0.27.0 - 2023-01-15
New Features
- [
extras-circe
]: Add an extension method to circeEncoder
to add more fields (#291)results inimport 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
There should be an easy way to add the{ "n": 1 }
name
field to have JSON likeFor instance,{ "n": 1, "name": "Blah" }
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 compileStream[F, Byte]
toF[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
0.26.0 - 2022-12-05
New Feature
-
[
extras-render-refined
] Addnewtype
+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
and2.7.0
=>2.8.0
- Cats Effect
2.5.4
=>2.5.5
- Cats Effect
3.2.9
=>3.3.14
- Scala
v0.25.0
0.25.0 - 2022-11-20
New Feature
extras-type-info
- [
extras-type-info
] Releaseextras-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.