Skip to main content
Version: v1.16.0

InlinedRefined Type - Inlined Custom Type

What is InlinedRefined?

InlinedRefined is Newtype + validation with explicit inline compile-time validation hooks.

It extends RefinedBase, so you still get runtime validation APIs such as:

  • from(a): Either[String, Type]
  • unsafeFrom(a): Type
  • value / unapply

In addition, InlinedRefined provides apply with inline validation through:

  • inlinedPredicate
  • inlinedExpectedValue

Import

import refined4s.*

Required Members

To define a custom InlinedRefined[A], implement all of these:

  1. inlinedExpectedValue: compile-time expected value message fragment.
  2. inlinedPredicate: compile-time predicate for apply.
  3. invalidReason: runtime error message for from and unsafeFrom.
  4. predicate: runtime predicate for from and unsafeFrom.
NOTE

Keep inlinedPredicate and predicate logically the same to avoid compile-time/runtime behavior mismatch.

Define an InlinedRefined Type

type NonEmptyName = NonEmptyName.Type
object NonEmptyName extends InlinedRefined[String] {

override inline def inlinedExpectedValue: String =
"a non-empty String"

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

override def invalidReason(a: String): String =
expectedMessage(inlinedExpectedValue)

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

Create Values

Compile-time Validation (apply)

NonEmptyName("Kevin")
// res1: Type = "Kevin"
NonEmptyName("")
// NonEmptyName("")
// ^^^^^^^^^^^^^^^^^
// Invalid value: [""]. It must be a non-empty String.

Runtime Validation (from)

val nameInput1 = "Kevin"
// nameInput1: String = "Kevin"
val nameInput2 = ""
// nameInput2: String = ""

NonEmptyName.from(nameInput1)
// res2: Either[String, Type] = Right(value = "Kevin")
NonEmptyName.from(nameInput2)
// res3: Either[String, Type] = Left(
// value = "Invalid value: []. It must be a non-empty String."
// )

Functional Runtime Handling

def renderName(input: String): String =
NonEmptyName.from(input).fold(
error => s"Invalid name: $error",
name => s"Valid name: ${name.value}"
)

renderName("Kevin")
// res4: String = "Valid name: Kevin"
renderName("")
// res5: String = "Invalid name: Invalid value: []. It must be a non-empty String."

Runtime Unsafe Validation (unsafeFrom)

danger

unsafeFrom may throw an exception if the input value is invalid. Prefer from in most cases.

NonEmptyName.unsafeFrom(nameInput2)
// java.lang.IllegalArgumentException: Invalid value: []. It must be a non-empty String.
// at refined4s.RefinedBase.unsafeFrom$$anonfun$1(RefinedBase.scala:27)
// at scala.util.Either.fold(Either.scala:198)
// at refined4s.RefinedBase.unsafeFrom(RefinedBase.scala:27)
// at refined4s.RefinedBase.unsafeFrom$(RefinedBase.scala:7)
// at repl.MdocSession$MdocApp0$NonEmptyName$.unsafeFrom(inlined-custom-type.md:20)
// at repl.MdocSession$MdocApp0$.$init$$$anonfun$1(inlined-custom-type.md:85)

Get Actual Value

val validName = NonEmptyName("Tom")
// validName: Type = "Tom"
validName.value
// res6: String = "Tom"

Pattern Matching

validName match {
case NonEmptyName(value) =>
println(s"Pattern matched name: $value")
}
// Pattern matched name: Tom