v2.4.0
Introduction
This release adds support for several libraries and other minor features.
Main changes
Doobie & Skunk support
You now can integrate refined types to your database using Skunk or Doobie.
Doobie:
import doobie.*
import doobie.implicits.*
import io.github.iltotore.iron.*
import io.github.iltotore.iron.constraint.all.*
import io.github.iltotore.iron.doobie.given
opaque type CountryCode = Int :| Positive
object CountryCode extends RefinedTypeOps[Int, Positive, CountryCode]
opaque type CountryName = String :| Not[Blank]
object CountryName extends RefinedTypeOps[String, Not[Blank], CountryName]
opaque type Population = Int :| Positive
object Population extends RefinedTypeOps[Int, Positive, Population]
//Refined columns of a table
case class Country(code: CountryCode, name: CountryName, pop: Population)
//Interpolation with refined values
def biggerThan(minPop: Population) =
sql"""
select code, name, population, indepyear
from country
where population > $minPop
""".query[Country]
Skunk:
import skunk.*
import skunk.implicits.*
import skunk.codec.all.*
import io.github.iltotore.iron.*
import io.github.iltotore.iron.constraint.all.*
import io.github.iltotore.iron.skunk.*
import io.github.iltotore.iron.skunk.given
type Username = String :| Not[Blank]
// refining a codec at usage site
val a: Query[Void, Username] = sql"SELECT name FROM users".query(varchar.refined)
// defining a codec for a refined opaque type
opaque type PositiveInt = Int :| Positive
object PositiveInt extends RefinedTypeOps[Int, Positive, PositiveInt]:
given codec: Codec[PositiveInt] = int4.refined[Positive]
// defining a codec for a refined case class
final case class User(name: Username, age: PositiveInt)
given Codec[User] = (varchar.refined[Not[Blank]] *: PositiveInt.codec).to[User]
uPickle support
Iron (via iron-upickle
) now provides Writer
/Reader
instances for uPickle:
import upickle.default._
import io.github.iltotore.iron.*
import io.github.iltotore.iron.constraint.all.*
import io.github.iltotore.iron.upickle.given
opaque type Username = String :| Alphanumeric
object Username extends RefinedTypeOps[String, Alphanumeric, Username]
opaque type Age = Int :| Positive
object Age extends RefinedTypeOps[Int, Positive, Age]
case class User(name: Username, age: Age) derives ReadWriter
write(User("Iltotore", 19)) //{"name":"Iltotore","age":19}
read[User]("""{"name":"Iltotore","age":19}""") //User("Iltotore", 19)
read[User]("""{"name":"Iltotore","age":-19}""") //AbortException: Should be strictly positive
read[User]("""{"name":"Il_totore","age":19}""") //AbortException: Should be alphanumeric
Decline support
You can read refined values from command arguments using Decline and iron-decline
.
import cats.implicits.*
import com.monovore.decline.*
import io.github.iltotore.iron.*
import io.github.iltotore.iron.constraint.all.*
import io.github.iltotore.iron.decline.given
type Person = String :| Not[Blank]
opaque type PositiveInt <: Int = Int :| Positive
object PositiveInt extends RefinedTypeOps[Int, Positive, PositiveInt]
object HelloWorld extends CommandApp(
name = "hello-world",
header = "Says hello!",
main = {
// Defining an option for a constrainted type
val userOpt =
Opts.option[Person]("target", help = "Person to greet.")
.withDefault("world")
// Defining an option for a refined opaque type
val nOpt =
Opts.option[PositiveInt]("quiet", help = "Number of times message is printed.")
.withDefault(PositiveInt(1))
(userOpt, nOpt).mapN { (user, n) =>
(1 to n).map(_ => println(s"Hello $user!"))
}
}
)
New constraint aliases
Positive0
/Negative0
: equivalent toPositive
/Negative
but including 0 (aka non strict positivity/negativity)SemanticVersion
: ensure that a String respects Semantic Versioning format
Contributors
- @jnicoulaud-ledger: #192
- @Masynchin: #194
- @matwojcik: #198
- @rlemaitre: #196 and #199
- @rparree: #189
- @vbergeron: #184
Full Changelog: v2.3.0...v2.4.0