Skip to main content

Custom Type

Import

import refined4s.*

Define Refined Type

type RefinedTypeName = RefinedTypeName.Type
object RefinedTypeName extends Refined[ActualType] {
override inline def invalidReason(a: ActualType): String =
expectedMessage("something with blah blah")

override inline def predicate(a: ActualType): Boolean =
// validation logic here
}

e.g.)

type MyString = MyString.Type
object MyString extends Refined[String] {
override inline def invalidReason(a: String): String =
expectedMessage("a non-empty String")

override inline def predicate(a: String): Boolean =
a != ""
}

Create Value

Given the following Refined type,

type Month = Month.Type
object Month extends Refined[Int] {
override inline def invalidReason(a: Int): String =
expectedMessage("Int between 1 and 12 (1 - 12)")

override inline def predicate(a: Int): Boolean =
a >= 1 && a <= 12
}

With Compile-time Validation

If the actual value is a constant value meaning that the actual value is known in compile-time (e.g. number, Boolean, String literals), Refined provides a compile-time validation with its apply method.

Valid cases

Month(1)
// res1: Type = 1
Month(12)
// res2: Type = 12

Invalid cases

Month(0)
// Month(0)
// ^^^^^^^^
// Invalid value: [0]. It must be Int between 1 and 12 (1 - 12)

Month(13)
// Month(13)
// ^^^^^^^^^
// Invalid value: [13]. It must be Int between 1 and 12 (1 - 12)

With Runtime Validation

Valid cases

val monthInput1 = 1
// monthInput1: Int = 1
val monthInput2 = 12
// monthInput2: Int = 12

Month.from(monthInput1)
// res3: Either[String, Type] = Right(value = 1)
Month.from(monthInput2)
// res4: Either[String, Type] = Right(value = 12)

Invalid cases

val invalidMonthInput1 = 0
// invalidMonthInput1: Int = 0
val invalidMonthInput2 = 13
// invalidMonthInput2: Int = 13

Month.from(invalidMonthInput1)
// res5: Either[String, Type] = Left(
// value = "Invalid value: [0]. It must be Int between 1 and 12 (1 - 12)"
// )
Month.from(invalidMonthInput2)
// res6: Either[String, Type] = Left(
// value = "Invalid value: [13]. It must be Int between 1 and 12 (1 - 12)"
// )

Get Actual Value

To get the actual value you can simply use the value method.

val month = Month(1)
// month: Type = 1
month.value
// res7: Int = 1

Pattern Matching

For pattern matching, Refined has built-in unapply so you can simply do

month match {
case Month(value) =>
println(s"Pattern matched value: $value")
}
// Pattern matched value: 1