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.
*
*
* Validation
s are used to filter Or
s 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 Or
s 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)
*
*
*
* Validation
s can also be used to accumulate error using when
, a method that's made available by trait Accumulation
on
* accumualting Or
s (Or
s 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] extends Product with Serializable {
/**
* Ands this Validation
with another, passed, Validation
.
*
*
* The result of and-ing two Validations
is:
*
*
*
* Expression Result Pass && Pass
Pass
* Pass && Fail(right)
Fail(right)
* Fail(left) && Pass
Fail(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 Or
s.
* 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.
*/