Borer Support

This module provides refined types Encoder/Decoder instances for borer.

Dependency

SBT:

libraryDependencies += "io.github.iltotore" %% "iron-borer" % "version"

Mill:

ivy"io.github.iltotore::iron-borer:version"

Example Dependencies

SBT:

libraryDependencies ++= Seq(
  "io.bullet" %% "borer-core"       % "1.13.0",
  "io.bullet" %% "borer-derivation" % "1.13.0"
)

Mill:

ivy"io.bullet::borer-core::1.13.0"
ivy"io.bullet::borer-derivation::1.13.0"

How to use

This example shows how to integrate iron with borer.

After having added the above dependencies you enable the integration layer with this import:

import io.github.iltotore.iron.borer.given

With this in place all refined types T automatically have borer Encoder[T] and Decoder[T] instances available, as long as the respective Encoders and Decoders for the (unrefined) underlying types are already given.

If a refinement error is triggered during decoding because the decoded value doesn't match the refinement condition(s) decoding will fail with a Borer.Error.ValidationFailure.

Here is a simple example (which can also be found here):

import io.bullet.borer.{Codec, Json}
import io.bullet.borer.derivation.MapBasedCodecs.*
import io.github.iltotore.iron.constraint.all.*
import io.github.iltotore.iron.*
import io.github.iltotore.iron.borer.given // this enables borer <-> iron integration

type Username = (Alphanumeric & MinLength[3] & MaxLength[10]) DescribedAs
  "Username should be alphanumeric and have a length between 3 and 10"

type Password = (Match["[A-Za-z].*[0-9]|[0-9].*[A-Za-z]"] & MinLength[6] & MaxLength[20]) DescribedAs
  "Password must contain at least a letter, a digit and have a length between 6 and 20"

type Age = Greater[0] DescribedAs
  "Age should be strictly positive"

case class Account(
  name: String :| Username,
  password: String :| Password,
  age: Int :| Age
) derives Codec // relies on the MapBasedCodecs imported above

val account = Account(
  name = "matt",
  password = "bar123",
  age = 42
)

val okValidEncoding = """{"name":"matt","password":"bar123","age":42}"""
val invalidEncoding = """{"name":"matt","password":"bar","age":42}"""

val encoding = Json.encode(account).toUtf8String
assert(encoding == okValidEncoding)

val decoding = Json.decode(okValidEncoding.getBytes).to[Account].valueEither
assert(decoding == Right(account))

val decoding2 = Json.decode(invalidEncoding.getBytes).to[Account].valueEither
decoding2 match {
  case Left(e: Borer.Error.ValidationFailure[_]) =>
    // "Password must contain at least a letter, a digit and have a length between 6 and 20 (input position 26)"
    println(e.getMessage)
  case _ => throw new IllegalStateException
}