org.scalactic.TrySugar.scala Maven / Gradle / Ivy
/*
* 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.util.Try
import scala.util.Failure
import annotation.tailrec
import exceptions.ValidationFailedException
/**
* Trait providing an implicit class that adds a toOr
method to
* Try
, which converts Success
to Good
,
* and Failure
to Bad
, as well as a validating
method,
* which takes one or more validation functions and returns either the
* same Try
if either the Try
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 TrySugar._
* import TrySugar._
*
* scala> import scala.util.Try
* import scala.util.Try
*
* 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 Try
will be the same successful Try
with the same value. (A
* "validation" only transforms the Try
if the validation fails, otherwise it is the
* same Try
. The only difference is its value has now been proven valid.)
* In the following example, a successful Try[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 Try
* with the same value.
*
*
*
* scala> val try100 = Try(100)
* try100: scala.util.Try[Int] = Success(100)
*
* scala> val round100 = try100.validating(isRound)
* round100: scala.util.Try[Int] = Success(100)
*
*
*
* If validation fails, the successful Try
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 try42 = Try(42)
* try42: scala.util.Try[Int] = Success(42)
*
* scala> val round42 = try42.validating(isRound)
* round42: scala.util.Try[Int] = Failure(org.scalactic.exceptions.ValidationFailedException: 42 was not a round number)
*
*
*
* If validating
is called on a failed Try
, it just returns the same failed Try
:
*
*
*
* scala> val tryEx = Try[Int] { throw new Exception("oops!") }
* tryEx: scala.util.Try[Int] = Failure(java.lang.Exception: oops!)
*
* scala> val roundEx = tryEx.validating(isRound)
* roundEx: scala.util.Try[Int] = 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> try100.validating(isRound, isDivBy3, isAnswerToLifeTheUniverseAndEverything)
* res0: scala.util.Try[Int] = Failure(org.scalactic.exceptions.ValidationFailedException: 100 was not divisible by 3)
*
*
*
* Here are some examples of the toOr
method:
*
*
*
* scala> try100.toOr
* res1: org.scalactic.Or[Int,Throwable] = Good(100)
*
* scala> tryEx.toOr
* res2: org.scalactic.Or[Int,Throwable] = Bad(java.lang.Exception: oops!)
*
*/
trait TrySugar {
/**
* Implicit class that adds a toOr
method to
* Try
, which converts Success
to Good
,
* and Failure
to Bad
, as well as a
* validation
method, which takes one or more functions that validate
* the Future
's value.
*
*
* See the main documentation for trait [[org.scalactic.TrySugar `TrySugar`]] for more detail and examples.
*
*
* @param theTry the Try
to which to add toOr
and validating
methods.
*/
implicit class Tryizer[T](theTry: Try[T]) {
/**
* Converts a Try
to an Or
, with
* Success
becoming Good
and
* Failure
becoming Bad
.
*/
def toOr: T Or Throwable = Or.from(theTry)
/**
* Validates a Try
using the passed validation functions.
*
*
* See the main documentation for trait [[org.scalactic.TrySugar `TrySugar`]] 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" Try
, either a Try
with the same value, or
* if validation failed, a failed Try
containing a ValidationFailedException
.
*/
def validating(hd: T => Validation[ErrorMessage], tl: (T => Validation[ErrorMessage])*): Try[T] = {
theTry.flatMap { (o: T) =>
TrySugar.passOrFirstFail(o, hd :: tl.toList) match {
case Pass => theTry
case Fail(errorMessage) => Failure(ValidationFailedException(errorMessage))
}
}
}
}
}
/**
* Companion object for TrySugar
enabling its members to be
* imported as an alternative to mixing them in.
*/
object TrySugar extends TrySugar {
@tailrec
private[scalactic] def passOrFirstFail[T, E](o: T, fs: List[T => Validation[E]]): Validation[E] = {
fs match {
case Nil => Pass
case head :: tail =>
head(o) match {
case Pass => passOrFirstFail(o, tail)
case firstFail => firstFail
}
}
}
}
/*
import org.scalactic._
import TrySugar._
import scala.util.Try
def isRound(i: Int): Validation[ErrorMessage] =
if (i % 10 == 0) Pass else Fail(i + " was not a round number")
val try100 = Try(100)
val round100 = try100.validating(isRound)
val try42 = Try(42)
val round42 = try42.validating(isRound)
val tryEx = Try[Int] { throw new Exception("oops!") }
val roundEx = tryEx.validating(isRound)
def isDivBy3(i: Int): Validation[ErrorMessage] =
if (i % 3 == 0) Pass else Fail(i + " was not divisible by 3")
def isAnswerToLifeTheUniverseAndEverything(i: Int): Validation[ErrorMessage] =
if (i == 42) Pass else Fail(i + "did not equal 42")
try100.validating(isRound, isDivBy3, isAnswerToLifeTheUniverseAndEverything)
*/