Skip to content

Releases: Iltotore/iron

v2.6.0

21 Jun 10:56
Compare
Choose a tag to compare

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:

Screenshot

image

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.

image

Adopters

The company Clever Cloud and the Tessela project are now listed on the README as adopters.

Contributors

Full Changelog: v2.5.0...v2.6.0

v2.6.0-RC1

08 Jun 12:04
623835e
Compare
Choose a tag to compare
v2.6.0-RC1 Pre-release
Pre-release

Introduction

This is the first release candidate for Iron v2.6.0

Main changes

  • Add refineAll variants to Cats' Validated/ZIO's Validation
  • 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

03 Mar 10:05
8a88093
Compare
Choose a tag to compare

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 as List[Int :| Positive] will not fail anymore, even for big sizes.
  • refine is now deprecated in favor of refineUnsafe
  • Upgraded doobie dependency to 1.0.0-RC5
  • Minor changes to existing documentation

Adopters

Marss was added to the list of adopters.

Contributors

Full Changelog: v2.4.0...v2.5.0

v2.4.0

30 Dec 09:59
Compare
Choose a tag to compare

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 to Positive/Negative but including 0 (aka non strict positivity/negativity)
  • SemanticVersion: ensure that a String respects Semantic Versioning format

Contributors

Full Changelog: v2.3.0...v2.4.0

v2.3.0

01 Nov 08:10
0871457
Compare
Choose a tag to compare

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 no given 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

Scastie

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

Full Changelog: v2.2.1...v2.3.0

v2.3.0-RC2

21 Oct 07:31
6224b94
Compare
Choose a tag to compare
v2.3.0-RC2 Pre-release
Pre-release

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

11 Oct 12:55
5822995
Compare
Choose a tag to compare
v2.3.0-RC1 Pre-release
Pre-release

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 now RefinedTypeOps[A, C, T] and RefinedTypeOps[T] is now RefinedTypeOps.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

16 Aug 06:49
Compare
Choose a tag to compare

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

06 Aug 14:50
Compare
Choose a tag to compare
v2.2.1-RC1 Pre-release
Pre-release

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

26 Jul 08:59
Compare
Choose a tag to compare

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