Skip to content
Scala library for boilerplate-free validation
Branch: master
Clone or download
rwalerow and krzemin Abstract over target M in asynchronous validators (#19)
* Main project compiling with abstract over Async Monad

* Make tests compile after abstracting over monad in async tests

* Trivial implementation of map2 for Future

* Proper implementation of App for scala Future

* Fix DerivedAsyncValidator passing down the FieldLabel

* Change implicitly[App[M]] to App[M]

* Fix code quality issue

* Remove duplicated test cases

* Adjust App[Future] implementation to scala 2.11.11

* Remove specific scala build version from the comment

* Add comap async validation test

* Add Base async Monad test for testing IO monads integration

* Add test for App[Future]

* Fix macro for handling passing of fields path

* Add scalaz IO monad integration and module

* Small error fixes

* Remove unuesed WordSpec from ScalazIntegrationSepc

* Test ValidationResult async cases

* Split BaseAsyncMonadSpec int two behviours

* Add CatsEffect integration spec

* AsyncValidatorAsyncOps extends AnyVal again

* Remove unnecesary executionContext passing

* AsyncValidatorSyncOps extends AnyVal again

* Remove async method compatibility breaking change

* Rename app instances

* Unnecesary val to method change

* Combine AsyncValidation spec and BaseAsyncMonadSpec

* Remove unnecesary test imports

* Remove dafault map implementation

* Add documentation regarding IO monads integration

* Rename App -> AppError, and AsyncValidator -> AsyncValidatorM

* Provide backward capability via type alias and object

* Add AsyncValidator object to dsl + remove unused import

* Move AsyncValiator object

* Fix permanently hidden error

* Remove unnecesary overrides, remove low priority and asyncF -> asyncM

* Use passed implicit appErrors and small fixes in

* Change F => M

* Refector AsyncValidationSpec

* Fix breaking dsl, syntax part changes

* Fix and rename app -> io
Latest commit 4d1490a Jul 20, 2018


Build Status Maven Central License

Octopus is a Scala library for boilerplate-free validation.

It defines Validator[T] type-class, provide composable DSL for defining validation rules for user-defined type and can automatically derive validators for case classes, tuples, sealed hierarchies and various standard Scala types by composing other defined or derived validators.


Let's consider example business domain.

case class UserId(id: Int) extends AnyVal

case class Email(address: String) extends AnyVal

case class PostalCode(code: String) extends AnyVal

case class Address(street: String,
                   postalCode: PostalCode,
                   city: String)

case class User(id: UserId,
                email: Email,
                address: Address)

Let's define validation rules as implicit type class instances.

// Usually you want to put them into companion objects
// or group them together in a module.

import octopus.dsl._

implicit val userIdValidator: Validator[UserId] = Validator[UserId]
  .rule( > 0, "must be positive number")

implicit val emailValidator: Validator[Email] = Validator[Email]
  .rule(_.address.nonEmpty, "must not be empty")
  .rule(_.address.contains("@"), "must contain @")
  .rule(_.address.split('@').last.contains("."), "must contain . after @")

implicit val potalCodeValidator: Validator[PostalCode] = Validator[PostalCode]
  .ruleVC((_: String).length == 5, "must be of length 5")
  .ruleVC((_: String).forall(_.isDigit), "must contain only digits")

implicit val addressValidator: Validator[Address] = Validator
  .derived[Address] // derives default validator for Address
  .rule(, (_: String).nonEmpty, "must not be empty")
  .rule(_.street, (_: String).nonEmpty, "must not be empty")

Then, having validator instances imported, we can validate our bigger case classes for free, without any additional boilerplate!

import octopus.syntax._

val user1 = User(
  Address("Love Street", PostalCode("12345"), "Los Angeles")

val user2 = User(
  Address("", PostalCode("qqqqqq"), "   ")

user1.isValid // : Boolean = true

user1.validate.toEither // : Either[octopus.ValidationError, User] = Right(user1)

user2.isValid // : Boolean = false

// : List[(String, String)] = List(
//     (id,must be positive number), 
//     (email,must contain . after @),
//     (address.postalCode,must be of length 5), 
//     (address.postalCode,must contain only digits),
//     (address.street,must not be empty)
// )

Getting started

Octopus is currently available for Scala 2.11, 2.12 and Scala.js.

To get started with SBT, add following line to your build.sbt:

libraryDependencies += "com.github.krzemin" %% "octopus" % "0.3.3"

Or if you are using Scala.js:

libraryDependencies += "com.github.krzemin" %%% "octopus" % "0.3.3"

Asynchronous validators

Sometimes validation rules are more complex in sense that they can't be decided locally by only looking at object value, but they require some external context like querying service or database. Therefore, Octopus has support for asynchronous predicates, that instead of T => Boolean, are defined in terms of T => Future[Boolean]. The same as with normal validation predicates, full derivation is also supported for asynchronous validators. Look at the example below to get better insight:

trait EmailService {
  def isEmailTaken(email: String): Future[Boolean]
  def doesDomainExists(email: String): Future[Boolean]

class AsyncValidators(emailService: EmailService) {

  implicit val emailAsyncValidator: AsyncValidator[Email] =
      .derived[Email] // (1)
      .async.ruleVC(emailService.isEmailTaken, "email is already taken by someone else") // (2)
      .async.rule(_.address, emailService.doesDomainExists, "domain does not exists") // (3)

val asyncValidators = new AsyncValidators(...)

import asyncValidators._ // (4)

Email("abc@xyz.qux").isValidAsync // Success(false): Future[Boolean]
  // Success(List(("", "must contain . after @"), ("", "domain does not exists"))): Future[List[(String, String)]

val user1 = User(
  Address("Love Street", PostalCode("12345"), "Los Angeles")

  .map(_.toFieldErrMapping) // Success(List("email", "email is already taken by someone else")): Future[List[(String, String)]


  • (1) we are requesting to derive usual validator for Email type
  • (2) by prepending rule with async keyword we can define validator rule with asynchronous predicate that lifts our validator to AsyncValidator[Email]
  • (3) we are adding next asynchronous validation rule
  • (4) we are importing instances for asynchronous validators into current scope so that later we can use .isValidAsync/.validateAsync extension methods.

Another asynchronous monad

At the time there exists possibility to use cats.IO or scalaz.IO monad for asynchronous validation rules. First of all proper dependency is required

libraryDependencies += "com.github.krzemin" %%% "octopus-scalaz-effect" % "0.3.3"
libraryDependencies += "com.github.krzemin" %%% "octopus-cats-effect" % "0.3.3"

Then with propper import asyncM method on Validator gives user the possibility to specify target monad:

  import octopus.async.cats._ // octopus.async.scalaz._
  import cats.effect.IO

  trait EmailService {
    def isEmailTaken(email: String): IO[Boolean]
    def doesDomainExists(email: String): IO[Boolean]

  implicit val emailAsyncValidator: AsyncValidatorM[IO, Email] =
      .asyncM[IO].ruleVC(emailService.isEmailTaken, "email is already taken by someone else") // (2)
      .async.rule(_.address, emailService.doesDomainExists, "domain does not exists") // (3)

  Email("abc@xyz.qux").isValidAsync // IO(false): IO[Boolean]

Integration with Cats / Scalaz

There are available additional modules that simplify integration with Cats and Scalaz validation types.


If you want to integrate with Cats, simply add following line to build.sbt:

libraryDependencies += "com.github.krzemin" %%% "octopus-cats" % "0.3.3"

Having this dependency on classpath, you can use

import octopus.syntax._
import octopus.cats._

user1.validate.toValidatedNel // : ValidatedNel[octopus.ValidationError, User] = Valid(user1)

user2.validate.toValidatedNel // : ValidatedNel[octopus.ValidationError, User] = Invalid(NonEmptyList(...))

See integration test suite for more information.


Alternatively, if you want similar integration with Scalaz, add following line to build.sbt:

libraryDependencies += "com.github.krzemin" %%% "octopus-scalaz" % "0.3.3"

See integration test suite for reference.


How it's different that Cats/Scalaz validation data types?

The main difference between Octopus and Cats/Scalaz validation types is an approach to composability of validations.

Cats/Scalaz validations are kind of disjunction types that hold successfully validated value or some validation error(s). They can be composed usually with simple combinators or applicative builder syntax. When having lot of case classes with many fields and you want to compose validators of their fields, you have to do this manually which results with rather lot amount of boilerplate code.

Octopus approach is a bit different. The Validator[T] type-class holds a function that will perform validation at some point of time. There are actually two levels of composability:

  • validation rules for single type - can be composed using provided DSL,
  • field validators that composes and create case class validator - that is achieved automatically by using type-class derivation mechanism; still you can override validation rules for certain types in local contexts.


Copyright 2016-2017 Piotr Krzemiński

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.

You can’t perform that action at this time.