Releases: Iltotore/iron
v2.6.0
Introduction
This release adds new "all" variants introduced in 2.5.0 as well as compile-time UX enhancements.
Main changes
First-order variants for Cats and ZIO
iron-cats
and iron-zio
now include "all" variants for ValidatedNec
/EitherNec
/Nel...
and Validation
.
opaque type Username = String :| Alphanumeric
object Username extends RefinedTypeOps[String, Alphanumeric, Username]
//Success(List("CoolSkeleton95", "Alice"): List[String :| Alphanumeric])
List("CookSkeleton95", "Alice").refineAllValidation[Alphanumeric]
/*
Failure(NonEmptyChunk(
InvalidValue("Il_totore", "Should be alphanumeric"),
InvalidValue(" ", "Should be alphanumeric")
))
*/
List("Il_totore", "CoolSkeleton95", " ", "Alice").refineAllValidation[Alphanumeric]
//Success(List("CoolSkeleton95", "Alice"): List[Username])
Username.validationAll(List("CookSkeleton95", "Alice"))
(Scastie)
More useful compile-time errors
A reason is now given when an error fails at compile-time:
val y: Int = ??? //Runtime value
val x: Int :| Greater[10] = y
[error] |-- Constraint Error --------------------------------------------------------
[error] |Cannot refine value at compile-time because the predicate cannot be evaluated.
[error] |This is likely because the condition or the input value isn't fully inlined.
[error] |
[error] |To test a constraint at runtime, use one of the `refine...` extension methods.
[error] |
[error] |Inlined input: y
[error] |Inlined condition: (y.>(10.0): scala.Boolean)
[error] |Message: Should be greater than 10
[error] |Reason: Some arguments of `>` are not inlined:
[error] |Arg 0:
[error] | Term not inlined: y
[error] |----------------------------------------------------------------------------
Better colors for compile-time errors
Instead of aqua, compile-time errors use magenta which is more readable in Scastie than the former. Expressions are now highlighted:
Configurable compile-time errors
Compile-time errors can now be tweaked with two options:
-Diron.color
to enable (true
)/disable (false
) compile-time messages colorations, including syntax highlighting-Diron.shortMessages
to display short summaries instead of full messages. Useful for Lens (such as Error Lens on VSCode or Inspection Lens on Intellij IDEA) users to have quick insights while coding.
Lens screenshot
In the following example, the flag -Diron.shortMessages=true
was added to BSP arguments.
Adopters
The company Clever Cloud and the Tessela project are now listed on the README as adopters.
Contributors
- @mcallisto: #232
- @zaxxel: #233 and #234
Full Changelog: v2.5.0...v2.6.0
v2.6.0-RC1
Introduction
This is the first release candidate for Iron v2.6.0
Main changes
- Add
refineAll
variants to Cats'Validated
/ZIO'sValidation
- Indicate the reason why a value cannot be evaluated at compile time
Note: the way of evaluating values at compile time has changed internally. It should not change the current behaviour of Iron at compile time. Please submit an issue if it did.
Full Changelog: v2.5.0...v2.6.0-RC1
v2.5.0
Introduction
This release adds support for several libraries and other minor features.
Main changes
First order types support
Iron now provides refinement methods for first order types:
List(1, 2, 3).refineAllOption[Positive] //Some(List(1, 2, 3)): List[Int :| Positive]
List(-1, 2, 3).refineAllOption[Positive] //None
Such methods exist for:
- Imperative (
refine
/refineUnsafe
) - Option
- Either
- "Further" equivalents
Borer support
Iron now supports Borer's codec derivation:
type Username = String :| DescribedAs[Not[Blank], "Username must not be blank"]
type Age = Int :| DescribedAs[Positive, "Age must be positive"]
case class User(name: String :| Not[Blank], age: Int :| Positive) derives Codec
Json.decode("""{"name":"hey","age":25}""".getBytes).to[User] //Right(...)
Json.decode("""{"name":"","age":25}""".getBytes).to[User] //Left(ValidationFailure("Username must not be blank"))
Other changes
Gen
/Arbitrary
instances for types such asList[Int :| Positive]
will not fail anymore, even for big sizes.refine
is now deprecated in favor ofrefineUnsafe
- Upgraded
doobie
dependency to1.0.0-RC5
- Minor changes to existing documentation
Adopters
Marss was added to the list of adopters.
Contributors
- @jprudent: #200 and #207
- @matwojcik: #204 and #206
- @rayandfz: #223
- @rlemaitre: #225
Full Changelog: v2.4.0...v2.5.0
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
v2.3.0
Introduction
This release brings ergonomic improvements for new types and other minor additions.
Main changes
Improved new types
Refactoring
RefinedTypeOps
has been refactored to be compatible with the future versions of Scala. RefinedTypeOps[T]
is now RefinedTypeOps.Transparent[T]
and RefinedTypeOpsImpl[A, C, T]
becomes RefinedTypeOps[A, C, T]
. Note that this introduces a minor compatibility break with 2.2.1.
It is recommended (but not mandatory) to use RefinedTypeOps.Transparent
for transparent type aliases. Opaque types should use the more verbose RefinedTypeOps[A, C, T]
.
For examples, this code written with Iron 2.2.1:
opaque type Temperature = Double :| Positive
object Temperature extends RefinedTypeOpsImpl[Double, Positive, Temperature]
type Age = Double :| Positive
object Age extends RefinedTypeOps[Age]
becomes in 2.3.0:
opaque type Temperature = Double :| Positive
object Temperature extends RefinedTypeOps[Double, Positive, Temperature]
type Age = Double :| Positive
object Age extends RefinedTypeOps.Transparent[Age]
This change brings several improvements:
- Compatibility with future versions of Scala. See scala/scala3#17984
- Improvements on
RefinedTypeOps
companion's given instances. They no longer are orphans and nogiven
import should be needed anymore.
New TypeTest
given instances
The RefinedTypeOps
trait (and IronType
) now brings TypeTest
instances. This is especially be useful in some typeclass derivation scenarios.
Runtime proxy for constraints
RuntimeConstraint[A, C]
is a proxy for Constraint[A, C]
usable at runtime. It significantly lowers the quantity of generated bytecode (by generating test
. Unlike Constraint
, a RuntimeConstraint
given instance does not require the method to be inline
:
//Compiles without `inline`
def refineOption[A, C](value: A)(using constraint: RuntimeConstraint[A, C]): Option[A :| C] =
Option.when(constraint.test(value))(value.asInstanceOf[A :| C])
refineOption[Int, Positive](5) //Some(5)
refineOption[Int, Positive](-5) //None
Note that RefinedTypeOps
also makes use of it by reusing the same instance of RuntimeConstraint
for companion object's methods (option
, either
, ...).
Ciris support
A new iron-ciris
module (JVM/SJS/SN) has been added, providing typeclass instances for Ciris. For example:
import cats.syntax.all.*
import ciris.*
import io.github.iltotore.iron.*
import io.github.iltotore.iron.constraint.all.*
import io.github.iltotore.iron.cats.given
import io.github.iltotore.iron.ciris.given
type Username = String :| (Not[Blank] & MaxLength[32])
type Password = String :| (Not[Blank] & MinLength[9])
case class DatabaseConfig(username: Username, password: Secret[Password])
val databaseConfig: ConfigValue[Effect, DatabaseConfig] = (
env("DB_USERNAME").as[Username],
env("DB_PASSWORD").as[Password].secret
).mapN(DatabaseConfig.apply)
Both IronType
and RefinedTypeOps
are supported.
Circe key support
iron-circe
now provides KeyEncoder
/KeyDecoder
instances given instances for refined types and new types.
Upgrade to Scala 3.3.1
Iron now uses Scala 3.3.1. The plan is to stick to the latest LTS version of Scala. See Scala's long-term compatibility plans.
This will require the consumers to update to Scala 3.3.1 or older to use Iron. The change should not break anything with Iron or other source code wrote in 3.2.x.
Adopters
Ledger was added to the list of adopters.
Contributors
- @ajaychandran: #179
- @kyri-petrou: #154, #175 and #176
- @rparree: #163
- @vbergeron: #182 and #183
- @Zelenya: #148
Full Changelog: v2.2.1...v2.3.0
v2.3.0-RC2
Introduction
This is the second release candidate for Iron v2.3.0.
Main changes (since 2.3.0-RC1)
- Support key encoder/decoder for Circe
- Add Ledger as an adopter to the README
Full Changelog: v2.3.0-RC1...v2.3.0-RC2
v2.3.0-RC1
Introduction
This is the first release candidate for Iron v2.3.0.
Main changes
- Upgrade to Scala 3.3.1. Consumers will have to upgrade to Scala 3.3.x (more details in future 2.3.0 changelog)
- Ciris support
- Add runtime proxy for constrants, allowing better performances, especially for derived typeclass instances.
- Refactor
RefinedTypeOps
. Small breaking change:RefinedTypeOpsImpl[A, C, T]
is nowRefinedTypeOps[A, C, T]
andRefinedTypeOps[T]
is nowRefinedTypeOps.Transparent[T]
- Improve ergonomics of
RefinedTypeOps
:- No given import needed anymore
- Easier typeclass derivation
- Default typeclass instances for
TypeTest
About Scala upgrade
Iron now uses Scala 3.3.1. The plan is to stick to the latest LTS version of Scala. See Scala's long-term compatibility plans.
This will require the consumers to update to Scala 3.3.1 or older to use Iron. More information in the 2.3.0 release changelog.
Full Changelog: v2.2.1...v2.3.0-RC1
v2.2.1
Introduction
This release fixes minor issues, including an implicit resolution bug.
Main changes
- Fix ambiguous Cats instances error when invoking
Eq
for a refined type - Fix wrong method "refined" mentionned in the error thrown when a value cannot be refined at compile-time.
Full Changelog: v2.2.0...v2.2.1
v2.2.1-RC1
Introduction
This is the first release candidate for Iron v2.2.1.
Main changes
- Fix ambiguous Cats instances error when invoking
Eq
for a refined type - Fix wrong method "refined" mentionned in the error thrown when a value cannot be refined at compile-time.
Full Changelog: v2.2.0...v2.2.1-RC1
v2.2.0
Introduction
This release brings new constraints and a way to create zero-overhead new types from refined ones.
Main changes
New types
This release adds the RefinedTypeOps
trait. It adds smart constructors for refined types and can be combined with type aliases to emulate no-cost new types:
type Temperature = Int :| Positive
object Temperature extends RefinedTypeOps[Temperature]
val temperature: Temperature = Temperature(100)
val maybeTemperature: Option[Temperature] = Temperature.option(100)
val temperatureOrError: Either[String, Temperature] = Temperature.either(100)
It can also be combined with Scala 3's opaque types to avoid mixing similar new types:
opaque type Temperature = Int :| Positive
object Temperature extends RefinedTypeOps[Temperature]
opaque type Moisture = Int :| Positive
object Moisture extends RefinedTypeOps[Moisture]
case class WeatherData(temperature: Temperature, moisture: Moisture)
val temperature = Temperature(100)
val moisture = Moisture(10)
WeatherData(temperature, moisture) //OK
WeatherData(moisture, temperature) //Compile-time error: type mismatch
Note: due to a compiler issue, interaction between opaque new types and multi-module projects has some frictions. See #131
Check the dedicated documentation page for further information.
BigInt and BigDecimal support
The io.github.iltotore.iron.constraint.numeric
package now has support for scala.math.BigInt
and scala.math.BigDecimal
.
Supported constraints are:
- Greater
- Less
- Multiple
- Divide
- Associated aliases (e.g GreaterEqual, LessEqual, Positive, Even, ...)
Note: like List
, Set
etc..., these constraints are runtime only. It will be fixed in Iron 3.0, see #147.
Better documentation
The documentation is now versioned. You can select the version you want in the upper-left corner like in Scala 3 API documentation. Extra dependencies and their version used for examples are now available on their page. See this page for example.
Contributors
Full Changelog: v2.2.0-RC3...v2.2.0