Skip to content

Instantly share code, notes, and snippets.

@debasishg
Created July 23, 2020 15:26
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save debasishg/0ab6bdf5e4bc8d4a1f2c0ab4a50f13e4 to your computer and use it in GitHub Desktop.
Save debasishg/0ab6bdf5e4bc8d4a1f2c0ab4a50f13e4 to your computer and use it in GitHub Desktop.
Incorporating compile time and runtime validation using refinement types in Scala
import cats.data.ValidatedNec
import cats.implicits._
import eu.timepit.refined._
import eu.timepit.refined.api.Refined
import eu.timepit.refined.auto._
import eu.timepit.refined.numeric._
import eu.timepit.refined.collection._
object person {
type ValidationResult[A] = ValidatedNec[String, A]
// compile time validation
type Age = Int Refined Positive
type Name = String Refined NonEmpty
// constructor that can be used for compile time type checks
case class Person private (name: Name, address: String, age: Age)
object Person {
// smart constructor for runtime validation
// that uses applicative style error accumulation
def person(
name: String,
address: String,
age: Int
): ValidationResult[Person] = {
(validateName(name), validateAge(age)).mapN { (n, a) =>
Person(n, address, a)
}
}
def validateName(name: String): ValidationResult[Name] =
refineV[NonEmpty](name).fold(_.invalidNec, _.validNec)
def validateAge(age: Int): ValidationResult[Age] =
refineV[Positive](age).fold(_.invalidNec, _.validNec)
}
}
@debasishg
Copy link
Author

Gabriel's (@volpegabriel87) version:

import cats.data.EitherNec
import cats.implicits._

import eu.timepit.refined._
import eu.timepit.refined.api._
import eu.timepit.refined.auto._
import eu.timepit.refined.numeric._
import eu.timepit.refined.collection._

object demo {
  
  // compile time validation
  type Age = Int Refined Positive // same as PosInt 
  object Age extends RefinedTypeOps.Numeric[Age, Int]

  type Name = String Refined NonEmpty // same as NonEmptyString
  object Name extends RefinedTypeOps[Name, String]

  // constructor that can be used for compile time type checks
  case class Person private (name: Name, address: String, age: Age)

  object Person {
    // smart constructor for runtime validation
    // that uses applicative style error accumulation
    def person(
        name: String,
        address: String,
        age: Int
    ): EitherNec[String, Person] = {
      (refineV[NonEmpty](name).toEitherNec, refineV[Positive](age).toEitherNec).parMapN { (n, a) =>
        Person(n, address, a)
      }
    }

    def person2(
        name: String,
        address: String,
        age: Int
    ): EitherNec[String, Person] = {
      (Name.from(name).toEitherNec, Age.from(age).toEitherNec).parMapN { (n, a) =>
        Person(n, address, a)
      }
    }

  }

}

println(demo.Person.person("", "foo", 0))

println(demo.Person.person2("", "foo", 0))

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment