All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.scalactic.FutureSugar.scala Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2001-2013 Artima, Inc.
 *
 * 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
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * 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.
 */
package org.scalactic

import scala.concurrent.Future
import scala.concurrent.ExecutionContext
import exceptions.ValidationFailedException

/**
 * Trait providing an implicit class that adds a validating method to
 * Future, which takes one or more validation functions and returns either the
 * same Future if either the Future had already failed or its value
 * passes all the functions, or [[org.scalactic.exceptions.ValidationFailedException `ValidationFailedException`]] containing an error message
 * describing the first validation that failed.
 *
 * 

* Here's an example validation method, which passes if the given Int is evenly * divisible by 10 (i.e., the result will be [[org.scalactic.Pass Pass]]). If the value does not pass * this test, the result is a [[org.scalactic.Fail Fail]] containing a helpful error message string. *

* *
 * scala> import org.scalactic._
 * import org.scalactic._
 *
 * scala> import FutureSugar._
 * import org.scalactic.FutureSugar._
 *
 * scala> import scala.concurrent.Future
 * import scala.concurrent.Future
 *
 * scala> import scala.concurrent.ExecutionContext.Implicits.global
 * import scala.concurrent.ExecutionContext.Implicits.global
 *
 * scala> def isRound(i: Int): Validation[ErrorMessage] =
 *      |   if (i % 10 == 0) Pass else Fail(i + " was not a round number")
 * isRound: (i: Int)org.scalactic.Validation[org.scalactic.ErrorMessage]
 * 
* *

* Validation will be attempted on a successful Try. If the validation succeeds, the * resulting Future will be the same successful Future with the same value. (A * "validation" only transforms the Future if the validation fails, otherwise it is the * same Future. The only difference is its value has now been proven valid.) * In the following example, a successful Future[Int] with the value 100 * passes the validation (which checks whether 100 is evenly divisible by 10), therefore * the result of the validating call is the same successful Future * with the same value. *

* *
 * scala> val fut100 = Future(100)
 * fut100: scala.concurrent.Future[Int] = scala.concurrent.impl.Promise$DefaultPromise@67f9c9c6
 *
 * scala> fut100.value
 * res0: Option[scala.util.Try[Int]] = Some(Success(100))
 *
 * scala> val round100 = fut100.validating(isRound)
 * round100: scala.concurrent.Future[Int] = scala.concurrent.impl.Promise$DefaultPromise@1ac2f0d1
 *
 * scala> round100.value
 * res1: Option[scala.util.Try[Int]] = Some(Success(100))
 * 
* *

* If validation fails, the successful Future will be transformed into a failed one, with * a ValidationFailedException that contains the error message * returned by the validation function. In the following example, 42 fails the validation because it * is not evenly divisible by 10: *

* *
 * scala> val fut42 = Future(42)
 * fut42: scala.concurrent.Future[Int] = scala.concurrent.impl.Promise$DefaultPromise@19c6e4d1
 *
 * scala> fut42.value
 * res2: Option[scala.util.Try[Int]] = Some(Success(42))
 *
 * scala> val round42 = fut42.validating(isRound)
 * round42: scala.concurrent.Future[Int] = scala.concurrent.impl.Promise$DefaultPromise@b5175d
 *
 * scala> round42.value
 * res3: Option[scala.util.Try[Int]] = Some(Failure(org.scalactic.exceptions.ValidationFailedException: 42 was not a round number))
 * 
* *

* If validating is called on a failed Future, it just returns the same failed Future: *

* *
 * scala> val futEx = Future[Int] { throw new Exception("oops!") }
 * futEx: scala.concurrent.Future[Int] = scala.concurrent.impl.Promise$DefaultPromise@3ba0299c
 *
 * scala> futEx.value
 * res4: Option[scala.util.Try[Int]] = Some(Failure(java.lang.Exception: oops!))
 *
 * scala> val roundEx = futEx.validating(isRound)
 * roundEx: scala.concurrent.Future[Int] = scala.concurrent.impl.Promise$DefaultPromise@22bf1acf
 *
 * scala> roundEx.value
 * res5: Option[scala.util.Try[Int]] = Some(Failure(java.lang.Exception: oops!))
 * 
* *

* The validating method accepts one or more validation functions. If you * pass more than one, they will be tried in order up until the first failure, whose * error message will appear in the ValidationFailedException. In other words, * validating will short circuit at the first error and return that. It * will not accumulate errors. For example, the following validation will short circuit * after the isDivBy3 function fails: *

* *
 * scala> def isDivBy3(i: Int): Validation[ErrorMessage] =
 *      |   if (i % 3 == 0) Pass else Fail(i + " was not divisible by 3")
 * isDivBy3: (i: Int)org.scalactic.Validation[org.scalactic.ErrorMessage]
 *
 * scala> def isAnswerToLifeTheUniverseAndEverything(i: Int): Validation[ErrorMessage] =
 *      |   if (i == 42) Pass else Fail(i + " did not equal 42")
 * isAnswerToLifeTheUniverseAndEverything: (i: Int)org.scalactic.Validation[org.scalactic.ErrorMessage]
 *
 * scala> val futShort = fut100.validating(isRound, isDivBy3, isAnswerToLifeTheUniverseAndEverything)
 * futShort: scala.concurrent.Future[Int] = scala.concurrent.impl.Promise$DefaultPromise@30bb943e
 *
 * scala> futShort.value
 * res11: Option[scala.util.Try[Int]] = Some(Failure(org.scalactic.exceptions.ValidationFailedException: 100 was not divisible by 3))
 * 
*/ trait FutureSugar { /** * Implicit class that adds a validation method to * Future, which takes one or more functions that validate * the Future's value. * *

* See the main documentation for trait [[org.scalactic.FutureSugar `FutureSugar`]] for more detail and examples. *

* * @param theFuture the Future to which to add a validating method. */ implicit class Futureizer[T](theFuture: Future[T]) { /** * Validates a Future using the passed validation functions. * *

* See the main documentation for trait [[org.scalactic.FutureSugar `FutureSugar`]] for more detail and examples. *

* * @param first the first validation function to apply * @param rest the subsequent validation functions to apply, if any * @return a "validated" Future, either a Future with the same value, or * if validation failed, a failed Future containing a ValidationFailedException. */ def validating(first: T => Validation[ErrorMessage], rest: (T => Validation[ErrorMessage])*)(implicit executor: ExecutionContext): Future[T] = { theFuture.flatMap { (o: T) => TrySugar.passOrFirstFail(o, first :: rest.toList) match { case Pass => theFuture case Fail(errorMessage) => Future.failed(ValidationFailedException(errorMessage)) } } } } } /** * Companion object for FutureSugar enabling its members to be * imported as an alternative to mixing them in. */ object FutureSugar extends FutureSugar




© 2015 - 2024 Weber Informatics LLC | Privacy Policy