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): Typevalue/unapply
In addition, InlinedRefined provides apply with inline validation through:
inlinedPredicateinlinedExpectedValue
Import
import refined4s.*
Required Members
To define a custom InlinedRefined[A], implement all of these:
inlinedExpectedValue: compile-time expected value message fragment.inlinedPredicate: compile-time predicate forapply.invalidReason: runtime error message forfromandunsafeFrom.predicate: runtime predicate forfromandunsafeFrom.
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