Currently Scala 2 only
The validations are useless if programmers ignore them. We need to reduce an amount of boilerplate to adopt a typesafe validations for mass usage.
breif is an automated constructor generator for the case classes with a refined fields. The purpose of this micro-library is to reduce an adoption cost of a refined types.
brief is just a wrapper around powerful Refined library by Frank S. Thomas.
It will help if:
- I want typesafe fields validation for case classes, but I don't want to write boilerplate to its validation manually;
- Something went wrong and I don't want to "fail fast" on the first invalid field. I want to get all validation errors together;
- I want to have a failed field name in an every error message.
libraryDependencies += "com.github.poslegm" %% "brief" % "0.0.1-RC1-M1"
// for Scala 2.13
scalacOptions += "-Ymacro-annotations"
// for Scala 2.12
libraryDependencies += compilerPlugin("org.scalamacros" % "paradise" % "2.1.1" cross CrossVersion.full)
Public API of brief consists of the only one macro annotation @Validation
!
It creates constructor for the case class with refined fields and accumulates
all validation errors to List[String]
.
import brief.annotations.Validation
import eu.timepit.refined._
import eu.timepit.refined.api.Refined
import eu.timepit.refined.numeric._
@Validation case class Test(a: Int, b: Int Refined Positive, c: Int Refined Negative)
// MACRO GENERATED CODE START
object Test {
def create(a: Int, b: Int, c: Int): Either[List[String] Refined NonEmpty, Test] =
brief.util.either.product(
brief.util.either.liftErrors(refineV[Positive](b)),
brief.util.either.liftErrors(refineV[Negative](c))
).map { case (b, c) =>
new Test(a = a, b = b, c = c)
}
}
// MACRO GENERATED CODE END
Test.create(1, 2, -3) // Right(Test(1, 2, -3))
Test.create(1, -2, 3) // Left(List("For field Test.b: Predicate failed: (-2 > 0).", "For field Test.c: Predicate failed: (3 < 0)."))
import brief.annotations.Validation
import eu.timepit.refined._
import eu.timepit.refined.api.{Refined, Validate}
val russianPhoneNumberRegex = "\\+7[0-9]{10}".r
final case class RussianPhoneNumber()
implicit def phoneNumber: Validate.Plain[String, RussianPhoneNumber] =
Validate.fromPredicate(
x => russianPhoneNumberRegex.matches(x),
x => s"($x has format +7XXXXXXXXXX)",
RussianPhoneNumber()
)
@Validation case class Call(
source: String Refined RussianPhoneNumber,
target: String Refined RussianPhoneNumber
)
Call.create("+71234567890", "+71112223344") // Right(Call(...))
It's possible to return your own exception from a create
method instead
of raw List[String]
with errors. Just define your error datatype with a
contract:
- It should be a
class
or acase class
- It should receive
List[String]
orList[String] Refined NonEmpty
to the constructor
And then pass it to the annotation type parameter as
@Validation[CustomError]
.
final case class CallValidationError(msgs: List[String])
extends Exception(s"call validation errors: ${msgs.mkString("; ")}")
with NoStackTrace
@Validation[CallValidationError] case class Call(
source: String Refined RussianPhoneNumber,
target: String Refined RussianPhoneNumber
)
Call.create("+71234567890", "+71112") // Left(CallValidationError(...))
case class
with a type parameters will not compile. Feel free to
fix it!
@Validation case class Test[T](a: T) // <- doesn't compile
There are constraints for type aliases. Feel free to fix it!
// predicate aliases are OK
type PaE = Positive And Even
@Validation case class Test(x: Int Refined PaE) // <- works good :)
// full type aliases aren't OK
type Pos = Int Refined Positive
@Validation case class Test(x: Pos) // <- will not work :(
Currently supported versions are 2.12 and 2.13. Scala 3 support not ready yet.