org.scalactic.Validation.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
/**
* Represents the result of a validation, either the object Pass if the validation
* succeeded, else an instance of Fail containing an error value describing the validation failure.
*
*
* Validations are used to filter Ors in for expressions or filter method calls.
* For example, consider these methods:
*
*
*
* import org.scalactic._
*
* def isRound(i: Int): Validation[ErrorMessage] =
* if (i % 10 == 0) Pass else Fail(i + " was not a round number")
*
* def isDivBy3(i: Int): Validation[ErrorMessage] =
* if (i % 3 == 0) Pass else Fail(i + " was not divisible by 3")
*
*
*
* Because isRound and isDivBy3 take an Int and return a Validation[ErrorMessage], you
* can use them in filters in for expressions involving Ors of type Int Or ErrorMessage.
* Here's an example:
*
*
*
* for (i <- Good(3) if isRound(i) && isDivBy3(i)) yield i
* // Result: Bad(3 was not a round number)
*
*
*
* Validations can also be used to accumulate error using when, a method that's made available by trait Accumulation on
* accumualting Ors (Ors whose Bad type is an Every[T]). Here are some examples:
*
*
*
* import Accumulation._
*
* for (i <- Good(3) when (isRound, isDivBy3)) yield i
* // Result: Bad(One(3 was not a round number))
*
* for (i <- Good(4) when (isRound, isDivBy3)) yield i
* // Result: Bad(Many(4 was not a round number, 4 was not divisible by 3))
*
*
*
* Note: You can think of Validation as an “Option with attitude,” where Pass is
* a None that indicates validation success and Fail is a Some whose value describes
* the validation failure.
*
*
* @tparam E the type of error value describing a validation failure for this Validation
*/
sealed trait Validation[+E] {
/**
* Ands this Validation with another, passed, Validation.
*
*
* The result of and-ing two Validations is:
*
*
*
* Expression Result Pass && PassPass
* Pass && Fail(right)Fail(right)
* Fail(left) && PassFail(left)
* Fail(left) && Fail(right)Fail(left)
*
*
*
* As you can see in the above table, no attempt is made by && to accumulate errors, which in turn means that
* no constraint is placed on the E type (it need not be an Every). Instead, && short circuits
* and returns the first Fail it encounters. This makes it useful in filters in for expressions involving Ors.
* Here's an example:
*
*
*
* import org.scalactic._
*
* def isRound(i: Int): Validation[ErrorMessage] =
* if (i % 10 != 0) Fail(i + " was not a round number") else Pass
*
* def isDivBy3(i: Int): Validation[ErrorMessage] =
* if (i % 3 != 0) Fail(i + " was not divisible by 3") else Pass
*
* for (i <- Good(3) if isRound(i) && isDivBy3(i)) yield i
* // Result: Bad(3 was not a round number)
*
*
* @param other the other validation to and with this one
* @return the result of anding this Validation with the other, passed, Validation
*/
def &&[F >: E](other: => Validation[F]): Validation[F]
}
/**
* Indicates a validation succeeded.
*/
case object Pass extends Validation[Nothing] {
/**
* Ands this Validation with another, passed, Validation.
*
*
* The result of invoking this method will be the result of executing the passed other by-name value.
*
*
* @param other the other validation to and with this one
* @return the result of anding this Validation with the other, passed, Validation
*/
def &&[F](other: => Validation[F]): Validation[F] = other
}
/**
* Indicates a validation failed, describing the failure with a contained error value.
*
* @param error an error value describing the validation failure
* @tparam E the type of value describing a validation failure for this Fail
*/
case class Fail[E](error: E) extends Validation[E] {
/**
* Ands this Validation with another, passed, Validation.
*
*
* The result of invoking this method will be this same Fail object. It will not execute the passed by-name.
*
*
* @param other the other validation to and with this one
* @return the result of anding this Validation with the other, passed, Validation
*/
def &&[F >: E](other: => Validation[F]): Validation[F] = this
}
/*
OK, I can keep Validation simple like this, and it will only support &&, which
will short-circuit like && does, at least in the error message. And when I do Expectation, maybe I
could put it in Scalactic, and have an implicit conversion in the Expectation or Validation
companion object that goes from Expectation to Validation *if* the error type is String.
Though not sure that's really a good way to create error messages for non-programmers.
*/