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
classor acase class - It should receive
List[String]orList[String] Refined NonEmptyto 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 compileThere 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.