packages feed

valida (empty) → 0.1.0

raw patch · 15 files changed

+2047/−0 lines, 15 filesdep +basedep +smallcheckdep +tastysetup-changed

Dependencies added: base, smallcheck, tasty, tasty-hunit, tasty-quickcheck, tasty-smallcheck, valida

Files

+ ChangeLog.md view
@@ -0,0 +1,3 @@+# Changelog for valida
+
+## Unreleased changes
+ LICENSE view
@@ -0,0 +1,21 @@+MIT License
+
+Copyright (c) 2021 TotallyNotChase
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+ README.md view
@@ -0,0 +1,212 @@+# Valida
+Simple, elegant, applicative validation for product types - batteries included!
+
+Read the documentation on [hackage]().
+
+# Highlights
+* Minimal - *Zero dependencies* apart from `base`.
+* Batteries included - `ValidationRule` combinators for almost every scenario.
+* Validation without the boiler plate - a highly specialized usage of contravariant functors to conveniently model the common validation usecases, without extra boilerplate.
+
+# Quick Taste
+```hs
+import Data.List.NonEmpty (NonEmpty)
+
+import Valida
+
+data InputForm = InpForm
+  { inpName  :: String
+  , inpAge   :: Int
+  , inpEmail :: Maybe String
+  } deriving (Show)
+
+data ValidInput = ValidInput
+  { vInpName  :: String
+  , vInpAge   :: Int
+  , vInpEmail :: Maybe String
+  } deriving (Show)
+
+data FormErr
+  = InvalidNameLength
+  | InvalidAge
+  | NoAtCharInMail
+  | NoPeriodInMail
+  | InvalidEmailLength
+  deriving (Show)
+
+-- | Validator for each field in the input form - built using 'ValidationRule' combinators.
+inpFormValidator :: Validator (NonEmpty FormErr) InputForm ValidInput
+inpFormValidator = ValidInput
+    -- Name should be between 1 and 20 characters long
+    <$> inpName -?> lengthWithin (1, 20) InvalidNameLength
+    -- Age should be between 18 and 120
+    <*> inpAge -?> valueWithin (18, 120) InvalidAge
+    -- Email, if provided, should contain '@', and '.', and be atleast 5 characters long
+    <*> inpEmail -?> optionally (minLengthOf 5 InvalidEmailLength
+        <> mustContain '@' NoAtCharInMail
+        <> mustContain '.' NoPeriodInMail)
+
+goodInput :: InputForm
+goodInput = InpForm "John Doe" 42 Nothing
+
+badInput :: InputForm
+badInput = InpForm "John Doe" 17 (Just "@")
+
+main :: IO ()
+main = do
+    print (runForm inpFormValidator goodInput)
+    -- Prints- Success (ValidInput {vInpName = "John Doe", vInpAge = 42, vInpEmail = Nothing})
+    print (runForm inpFormValidator badInput)
+    -- Prints- Failure (InvalidAge :| [InvalidEmailLength])
+```
+
+You can also find more examples [here](./examples/Main.hs).
+
+# Quick Start
+The primary purpose of the `Validator` type is to validate each field in product types. To do this, you'll use `verify`.
+
+`verify` takes 2 inputs-
+* The "selector", which essentially just takes the product type as input, and returns the specific value of the specific field to validate.
+* The `ValidationRule`, which specifies the predicate the field must satisfy - as well as the error value to yield if it doesn't satisfy said predicate
+
+Let's validate a pair for example, the first field should be an int less than 10, the second field should be a non empty string. Then, the validator would look like-
+```hs
+pairValidator :: Validator (NonEmpty String) (Int, String) (Int, String)
+pairValidator = (,) <$> verify (failureIf (>=10) "NotLessThan10") fst <*> verify (notEmpty "EmptyString") snd
+```
+Or, if you prefer using operators - you can use `-?>`, which is a flipped version of `verify`.
+```hs
+pairValidator :: Validator (NonEmpty String) (Int, String) (Int, String)
+pairValidator = (,)
+    <$> fst -?> failureUnless (<10) "NotLessThan10"
+    <*> snd -?> notEmpty "EmptyString"
+```
+
+You can then run the validator on your input using `runValidator`-
+```hs
+>>> runValidator pairValidator (9, "foo")
+Success (9,"foo")
+>>> runValidator pairValidator (10, "")
+Failure ("NotLessThan10" :| ["EmptyString"])
+>>> runValidator pairValidator (5, "")
+Failure ("EmptyString" :| [])
+```
+
+This is the core concept for building the validators. You can use the primitive combinators (e.g `failureIf`, `failureUnless`) to build `ValidationRule`s directly from predicate functions, or you can choose one of the many derivate combinators (e.g `notEmpty`) to build `ValidationRule`s. Check out the `Valida.Combinators` module documentation to view all the included combinators.
+
+## Validators for non product types
+Although the primary purpose of `Valida` is building convenient validators for product types. Sometimes, you'll find yourself not needing to select on any field, but validating the input directly. In that case, you may find yourself using this pattern-
+```hs
+-- | Make sure int input is not even.
+intValidator :: Validator (NonEmpty String) Int Int
+intValidator = verify (failureIf even "Even") id
+```
+
+In these situations, instead of using `verify` with `id` as selector, you should use `validate` instead, which is the same as `flip verify id`-
+```hs
+intValidator :: Validator (NonEmpty String) Int Int
+intValidator = validate (failureIf even "Even")
+```
+
+## Combining multiple `ValidationRule`s
+Often, you'll find yourself in situations where you expect the input to satisfy *multiple* `ValidationRule`s, or situations where you expect the input to satisfy *at least one* of many `ValidationRule`s. This is where `andAlso`, and `orElse` come into play.
+
+### Combining multiple `ValidationRule`s with `andAlso`
+`andAlso` is the semigroup implementation of `ValidationRule`, and thus is the same as `<>`. Combining 2 rules with `<>` creates a new rule that is only satisfied when *both of the given rules are satisfied*. Otherwise, the very first (left most) failure value is returned - and the rest are not tried.
+
+The following rule only succeeds if the input is **odd**, *and* **not divisble by 3**.
+```hs
+rule :: ValidationRule (NonEmpty String) Int
+rule = failureIf even "IsEven" `andAlso` failureIf ((==0) . flip mod 3) "IsDivisbleBy3"
+```
+(OR)
+```hs
+rule :: ValidationRule (NonEmpty String) Int
+rule = failureIf even "IsEven" <> failureIf ((==0) . flip mod 3) "IsDivisbleBy3"
+```
+
+Usages-
+```hs
+>>> runValidator (validate rule) 5
+Success 5
+>>> runValidator (validate rule) 4
+Failure ("IsEven" :| [])
+>>> runValidator (validate rule) 15
+Failure ("IsDivisbleBy3" :| [])
+>>> runValidator (validate rule) 6
+Failure ("IsEven" :| [])
+```
+
+### Combining multiple `ValidationRule`s with `orElse`
+`orElse` also forms a semigroup, `</>` is aliased to `orElse`. Combining 2 rules with `</>` creates a new rule that is satisfied when *either of the given rules are satsified*. If all of them fail, the `Failure` values are accumulated.
+
+The following rule succeeds if the input is *either* **odd**, *or* **not divisble by 3**.
+```hs
+rule :: ValidationRule (NonEmpty String) Int
+rule = failureIf even "IsEven" `orElse` failureIf ((==0) . flip mod 3) "IsDivisbleBy3"
+```
+(OR)
+```hs
+rule :: ValidationRule (NonEmpty String) Int
+rule = failureIf even "IsEven" </> failureIf ((==0) . flip mod 3) "IsDivisbleBy3"
+```
+
+Usages-
+```hs
+>>> runValidator (validate rule) 5
+Success 5
+>>> runValidator (validate rule) 4
+Success 4
+>>> runValidator (validate rule) 15
+Success 15
+>>> runValidator (validate rule) 6
+Failure ("IsEven" :| ["IsDivisbleBy3"])
+```
+
+### Combining a foldable of `ValidationRule`s
+You can combine a foldable of `ValidationRule`s using `satisfyAll` and `satisfyAny`. `satisfyAll` folds using `andAlso`/`<>`, while `satisfyAny` folds using `orElse`/`</>`.
+
+## Ignoring errors
+Although, highly inadvisable and generally not useful in serious code, you may use alternative versions of `ValidationRule` combinators that use `()` (unit) as its error type so you don't have to supply error values. For example, `failureIf'` does not require an error value to be supplied. In case of failure, it simply yields `Failure ()`.
+```hs
+>>> runValidator (validate (failureIf' even)) 2
+Failure ()
+```
+
+## Re-assigning errors
+Using the `label`/`<?>` and `labelV`/`<??>` functions, you can use override the errors `ValidationRule`s and `Validator`s yield.
+
+For example, to re assign the error on a `ValidationRule`-
+```hs
+label "IsEven" (failureIf even "Foo")
+```
+(OR)
+```hs
+failureIf even "Foo" <?> "IsEven"
+```
+
+This is useful with `ValidationRule`s that use unit as their error type. You can create a `ValidationRule`, skip assigning an error to it - and label a specific error when you need it later.
+```hs
+label "IsEven" (failureIf' even)
+```
+
+Re-labeled `ValidationRule`s will yield the newly assigned error value when the rule is not satisfied.
+
+Similarly, `labelV` (or `<??>`) can be used to relabel the error value of an entire `Validator`.
+
+## Wait, couldn't this be done using contravariant functors?
+Yes! The concept of *keeping the input of a `Validator`* set to the same product type, but *letting it validate a specific field* of said input, can be generalized to contravariant functors. The `Validator` type looks like- `Validator e inp a`, to keep applicative composition working, the `inp` needs to stay the same - but each validator within said composition should also be able to *consume* a specific part of the `inp`. `ValidationRule` itself, is the typical example of a contravariant functor as well. It's essentially a specialized predicate function- `a -> Validation e ()`. The `verify` function simply combines these 2 *potentially generalizable* contravariant functors, into a very specialized usecase.
+
+I do think adding instances for actual generalized contravariant functors/profunctors for `ValidationRule` and `Validator`, *could* be more powerful, while also being able to provide the same specialized functions. However, I've refrained from doing so as I didn't want to pull in the extra dependencies. I think the separation of `ValidationRule` and `Validator`, combined with the provided functions, *should* be able to reliably, and elegantly model any scenario of building a validator. I have yet to find a usecase where the generalized contravariant instances *would be significantly useful*. But it could certainly be more idiomatic for haskell. I may consider creating a package that *does* go the contravariant/profunctor route though.
+
+# Comparison and Motivation
+The concept of the `Validation` data type used in this package isn't new. It's also used in the following packages-
+* [either](https://hackage.haskell.org/package/either)
+* [validation](https://hackage.haskell.org/package/validation)
+* [validation-selective](https://hackage.haskell.org/package/validation-selective)
+
+`Valida` aims to be a minimal in terms of dependencies, but batteries included in terms of API. It borrows many philosophies from `Data.Validation` (from `validation`) and `Validation` (from `validation-selective`), and aims to provide a convenient, minimal way to model the common usecases of them.
+
+The `verify` function, combined with the `ValidationRule` combinators, and the parsec-esque `Validator` aims to assist in easily modeling typical validation usecases without too much boilerplate. The core idea, really, is [contravariance](#wait-couldnt-this-be-done-using-contravariant-functors) - the typical usecases, especially when validating product types (the most common target of validation), simply showcases contravariant functors.
+
+In essence, the validation style itself, is designed to look like [forma](https://hackage.haskell.org/package/forma). Though the actual types, and core concepts are significantly different.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple
+main = defaultMain
+ src/Valida.hs view
@@ -0,0 +1,142 @@+{-# LANGUAGE Safe #-}
+
+{- |
+Module      : Valida
+Description : Simple applicative validation for product types, batteries included!
+Copyright   : (c) TotallyNotChase, 2021
+License     : MIT
+Maintainer  : totallynotchase42@gmail.com
+Stability   : Stable
+Portability : Portable
+
+This module exports the primary validator building functions. It also exports all of "Valida.Combinators".
+
+Refer to the hackage documentation for function reference and examples.
+You can also find examples in the README, and also in the github repo, within the examples directory.
+-}
+
+module Valida
+    ( Selector
+      -- * Primary data types
+    , Validation (..)
+    , ValidationRule
+    , Validator (runValidator)
+      -- * Functions for building Valida data types
+    , validate
+    , verify
+    , vrule
+    , (-?>)
+      -- * Reassigning errors
+    , label
+    , labelV
+    , (<?>)
+    , (<??>)
+      -- | Re-exports of "Valida.Combinators"
+    , module Valida.Combinators
+    , module Valida.ValidationUtils
+    ) where
+
+import Data.Bifunctor (Bifunctor (first))
+
+import Valida.Combinators
+import Valida.Validation      (Validation (..))
+import Valida.ValidationRule  (ValidationRule (..), vrule)
+import Valida.ValidationUtils
+import Valida.Validator       (Selector, Validator (..))
+
+{- | Build a validator from a 'ValidationRule' and a 'Selector'.
+
+The 'Validator` first runs given __selector__ on its input to obtain the validation target. Then, it runs the
+'ValidationRule' on the target.
+
+If validation is successful, the validation target is put into the 'Validation' result.
+
+==== __Examples__
+
+This is the primary function for building validators for your record types.
+To validate a pair, the most basic record type, such that the first element is a non empty string, and the second
+element is a number greater than 9, you can use:
+
+>>> let pairValidator = (,) <$> verify (notEmpty "EmptyString") fst <*> verify (failureIf (<10) "LessThan10") snd
+
+You can then run the validator on your input, using 'runValidator':
+>>> runValidator pairValidator ("foo", 12)
+Success ("foo",12)
+>>> runValidator pairValidator ("", 12)
+Failure ("EmptyString" :| [])
+>>> runValidator pairValidator ("foo", 9)
+Failure ("LessThan10" :| [])
+>>> runValidator pairValidator ("", 9)
+Failure ("EmptyString" :| ["LessThan10"])
+-}
+verify :: ValidationRule e b -> Selector a b -> Validator e a b
+verify (ValidationRule rule) selector = Validator $ \x -> selector x <$ rule (selector x)
+
+-- | A synonym for 'verify' with its arguments flipped.
+infix 5 -?>
+
+(-?>) :: Selector a b -> ValidationRule e b -> Validator e a b
+(-?>) = flip verify
+
+---------------------------------------------------------------------
+-- Reassigning corresponding error to 'ValidationRule'.
+---------------------------------------------------------------------
+
+{- | Relabel a 'ValidationRule' with a different error.
+
+Many combinators, like 'failureIf'' and 'failureUnless'', simply return the given error value
+within /NonEmpty/ upon failure. You can use 'label' to override this return value.
+
+==== __Examples__
+
+>>> let rule = label "NotEven" (failureUnless' even)
+>>> runValidator (validate rule) 1
+Failure "NotEven"
+
+>>> let rule = label "DefinitelyNotEven" (failureUnless even "NotEven")
+>>> runValidator (validate rule) 1
+Failure "DefinitelyNotEven"
+-}
+label :: e -> ValidationRule x a -> ValidationRule e a
+label err (ValidationRule rule) = vrule $ first (const err) . rule
+
+-- | A synonym for 'label' with its arguments flipped.
+infix 6 <?>
+
+(<?>) :: ValidationRule x a -> e -> ValidationRule e a
+(<?>) = flip label
+
+---------------------------------------------------------------------
+-- Reassigning corresponding error to 'Validator'.
+---------------------------------------------------------------------
+
+{- | Relabel a 'Validator' with a different error.
+
+==== __Examples__
+
+>>> let validator = labelV "NotEven" (validate (failureUnless' even))
+>>> runValidator validator 1
+Failure "NotEven"
+
+>>> let validator = labelV "DefinitelyNotEven" (validate (failureUnless even "NotEven"))
+>>> runValidator validator 1
+Failure "DefinitelyNotEven"
+-}
+labelV :: e -> Validator x inp a -> Validator e inp a
+labelV err (Validator v) = Validator $ first (const err) . v
+
+-- | A synonym for 'labelV' with its arguments flipped.
+infix 6 <??>
+
+(<??>) :: Validator x inp a -> e -> Validator e inp a
+(<??>) = flip labelV
+
+{- | Build a basic validator from a 'ValidationRule'.
+
+The 'Validator' runs the rule on its input. If validation is successful, the input is put into the 'Validation'
+result.
+
+prop> validate rule = verify rule id
+-}
+validate :: ValidationRule e a -> Validator e a a
+validate = (-?>) id
+ src/Valida/Combinators.hs view
@@ -0,0 +1,552 @@+{-# LANGUAGE Safe #-}
+
+{- |
+Module      : Valida.Combinators
+Description : Combinators and utilities for building and combining 'ValidationRule's.
+Copyright   : (c) TotallyNotChase, 2021
+License     : MIT
+Maintainer  : totallynotchase42@gmail.com
+Stability   : Stable
+Portability : Portable
+
+This module is re-exported by "Valida". You probably don't need to import this.
+
+This module exports the primitive, as well as utility, 'ValidationRule' combinators.
+As well as the 'orElse', 'andAlso', 'satisfyAny', and 'satisfyAll' functions, and some more utilities.
+-}
+
+module Valida.Combinators
+    ( -- * Primitive 'NonEmpty' combinators
+      failureIf
+    , failureUnless
+      -- * Primitive /Unit/ combinators
+    , failureIf'
+    , failureUnless'
+      -- * Negating 'ValidationRule'
+    , negateRule
+    , negateRule'
+      -- * Combining 'ValidationRule's
+    , andAlso
+    , falseRule
+    , orElse
+    , satisfyAll
+    , satisfyAny
+    , (</>)
+      -- * Common derivates of primitive 'NonEmpty' combinators
+    , atleastContains
+    , lengthAbove
+    , lengthBelow
+    , lengthWithin
+    , maxLengthOf
+    , maxValueOf
+    , minLengthOf
+    , minValueOf
+    , mustBe
+    , mustContain
+    , notEmpty
+    , ofLength
+    , onlyContains
+    , valueAbove
+    , valueBelow
+    , valueWithin
+      -- * Common derivates of primitive /Unit/ combinators
+    , atleastContains'
+    , lengthAbove'
+    , lengthBelow'
+    , lengthWithin'
+    , maxLengthOf'
+    , maxValueOf'
+    , minLengthOf'
+    , minValueOf'
+    , mustBe'
+    , mustContain'
+    , notEmpty'
+    , ofLength'
+    , onlyContains'
+    , valueAbove'
+    , valueBelow'
+    , valueWithin'
+      -- * Type specific 'ValidationRule's
+    , optionally
+    ) where
+
+import Control.Applicative (Applicative (liftA2))
+
+import Data.Bool          (bool)
+import Data.Foldable      (Foldable (fold))
+import Data.Ix            (Ix (inRange))
+import Data.List.NonEmpty (NonEmpty)
+
+import Valida.Utils          (neSingleton)
+import Valida.Validation     (Validation (..), validationConst)
+import Valida.ValidationRule (ValidationRule (..), vrule)
+
+---------------------------------------------------------------------
+-- Primitive 'NonEmpty' combinators
+---------------------------------------------------------------------
+
+{- | Build a rule that /fails/ with given error __if the given predicate succeeds__.
+
+prop> failureIf predc = failureUnless (not . predc)
+
+==== __Examples__
+
+>>> runValidator (validate (failureIf (>0) "Positive")) 5
+Failure ("Positive" :| [])
+>>> runValidator (validate (failureIf (>0) "Positive")) 0
+Success 0
+>>> runValidator (validate (failureIf (>0) "Positive")) (-1)
+Success (-1)
+-}
+failureIf :: (a -> Bool) -> e -> ValidationRule (NonEmpty e) a
+failureIf predc = predToRule (not . predc) . neSingleton
+
+{- | Build a rule that /fails/ with given error __unless the given predicate succeeds__.
+
+prop> failureUnless predc = failureIf (not . predc)
+
+==== __Examples__
+
+>>> runValidator (validate (failureUnless (>0) "NonPositive")) 5
+Success 5
+>>> runValidator (validate (failureUnless (>0) "NonPositive")) 0
+Failure ("NonPositive" :| [])
+>>> runValidator (validate (failureUnless (>0) "NonPositive")) (-1)
+Failure ("NonPositive" :| [])
+-}
+failureUnless :: (a -> Bool) -> e -> ValidationRule (NonEmpty e) a
+failureUnless predc = predToRule predc . neSingleton
+
+---------------------------------------------------------------------
+-- Primitive /Unit/ combinators
+---------------------------------------------------------------------
+
+{- | Like 'failureIf' but uses /Unit/ as the 'ValidationRule' error type.
+
+prop> failureIf' predc = failureUnless' (not . predc)
+prop> label (const (err :| [])) (failureIf' predc) = failureIf predc err
+
+==== __Examples__
+
+>>> runValidator (validate (failureIf' (>0))) 5
+Failure ()
+>>> runValidator (validate (failureIf' (>0))) 0
+Success 0
+>>> runValidator (validate (failureIf' (>0))) (-1)
+Success (-1)
+-}
+failureIf' :: (a -> Bool) -> ValidationRule () a
+failureIf' predc = predToRule (not . predc) ()
+
+{- | Like 'failureUnless' but uses /Unit/ as the 'ValidationRule' error type.
+
+prop> failureUnless' predc = failureIf' (not . predc)
+prop> label (const (err :| [])) (failureUnless' predc) = failureUnless predc err
+
+==== __Examples__
+
+>>> runValidator (validate (failureUnless' (>0))) 5
+Success 5
+>>> runValidator (validate (failureUnless' (>0))) 0
+Failure ()
+>>> runValidator (validate (failureUnless' (>0))) (-1)
+Failure ()
+-}
+failureUnless' :: (a -> Bool) -> ValidationRule () a
+failureUnless' = flip predToRule ()
+
+---------------------------------------------------------------------
+-- Common derivates of primitive 'NonEmpty' combinators
+---------------------------------------------------------------------
+
+{- | Build an equality rule for value.
+
+prop> mustBe x = failureUnless (==x)
+-}
+mustBe :: Eq a => a -> e -> ValidationRule (NonEmpty e) a
+mustBe x = failureUnless (==x)
+{-# INLINABLE mustBe #-}
+
+{- | Build an equality rule for length.
+
+prop> ofLength x = failureUnless ((==x) . length)
+-}
+ofLength :: Foldable t => Int -> e -> ValidationRule (NonEmpty e) (t a)
+ofLength n = failureUnless $ (==n) . length
+{-# INLINABLE ofLength #-}
+{-# SPECIALIZE ofLength :: Int -> e -> ValidationRule (NonEmpty e) [a] #-}
+
+{- | Build a minimum length (inclusive) rule.
+
+prop> minLengthOf x = failureUnless ((>=n) . length)
+-}
+minLengthOf :: Foldable t => Int -> e -> ValidationRule (NonEmpty e) (t a)
+minLengthOf n = failureUnless $ (>=n) . length
+{-# INLINABLE minLengthOf #-}
+{-# SPECIALIZE minLengthOf :: Int -> e -> ValidationRule (NonEmpty e) [a] #-}
+
+{- | Build a maximum length (inclusive) rule.
+
+prop> maxLengthOf n = failureUnless ((<=n) . length)
+-}
+maxLengthOf :: Foldable t => Int -> e -> ValidationRule (NonEmpty e) (t a)
+maxLengthOf n = failureUnless $ (<=n) . length
+{-# INLINABLE maxLengthOf #-}
+{-# SPECIALIZE maxLengthOf :: Int -> e -> ValidationRule (NonEmpty e) [a] #-}
+
+{- | Build a minimum length (inclusive) rule.
+
+prop> lengthAbove x = minLengthOf (x + 1)
+prop> lengthAbove x = failureUnless ((>n) . length)
+-}
+lengthAbove :: Foldable t => Int -> e -> ValidationRule (NonEmpty e) (t a)
+lengthAbove n = failureUnless $ (>n) . length
+{-# INLINABLE lengthAbove #-}
+{-# SPECIALIZE lengthAbove :: Int -> e -> ValidationRule (NonEmpty e) [a] #-}
+
+{- | Build a maximum length (inclusive) rule.
+
+prop> lengthBelow x = maxLengthOf (x - 1)
+prop> lengthBelow x = failureUnless ((<n) . length)
+-}
+lengthBelow :: Foldable t => Int -> e -> ValidationRule (NonEmpty e) (t a)
+lengthBelow n = failureUnless $ (<n) . length
+{-# INLINABLE lengthBelow #-}
+{-# SPECIALIZE lengthBelow :: Int -> e -> ValidationRule (NonEmpty e) [a] #-}
+
+{- | Build a maximum length rule.
+
+prop> notEmpty = minLengthOf 1
+prop> notEmpty = failureIf null
+-}
+notEmpty :: Foldable t => e -> ValidationRule (NonEmpty e) (t a)
+notEmpty = failureIf null
+{-# INLINABLE notEmpty #-}
+{-# SPECIALIZE notEmpty :: e -> ValidationRule (NonEmpty e) [a] #-}
+
+{- | Build an 'inRange' rule for length.
+
+prop> lengthWithin (min, max) = minLengthOf min `andAlso` maxLengthOf max
+prop> lengthWithin r = failureUnless (inRange r . length)
+-}
+lengthWithin :: Foldable t => (Int, Int) -> e -> ValidationRule (NonEmpty e) (t a)
+lengthWithin r = failureUnless $ inRange r . length
+{-# INLINABLE lengthWithin #-}
+{-# SPECIALIZE lengthWithin :: (Int, Int) -> e -> ValidationRule (NonEmpty e) [a] #-}
+
+{- | Build a minimum value (inclusive) rule.
+
+prop> minValueOf x = failureUnless (>=x)
+-}
+minValueOf :: Ord a => a -> e -> ValidationRule (NonEmpty e) a
+minValueOf x = failureUnless (>=x)
+{-# INLINABLE minValueOf #-}
+
+{- | Build a maximum value (inclusive) rule.
+
+prop> maxValueOf x = failureUnless (<=x)
+-}
+maxValueOf :: Ord a => a -> e -> ValidationRule (NonEmpty e) a
+maxValueOf x = failureUnless (<=x)
+{-# INLINABLE maxValueOf #-}
+
+{- | Build a minimum value (exclusive) rule.
+
+prop> valueAbove x = minValueOf (x + 1)
+prop> valueAbove x = failureUnless (>x)
+-}
+valueAbove :: Ord a => a -> e -> ValidationRule (NonEmpty e) a
+valueAbove n = failureUnless (>n)
+{-# INLINABLE valueAbove #-}
+
+{- | Build a maximum value (exclusive) rule.
+
+prop> valueBelow x = minValueOf (x - 1)
+prop> valueBelow x = failureUnless (<x)
+-}
+valueBelow :: Ord a => a -> e -> ValidationRule (NonEmpty e) a
+valueBelow n = failureUnless (<n)
+{-# INLINABLE valueBelow #-}
+
+{- | Build an 'inRange' rule for value.
+
+prop> valueWithin (min, max) = minValueOf min `andAlso` maxValueOf max
+prop> valueWithin (min, max) = failureUnless (\x -> m <= x && x <= n)
+-}
+valueWithin :: Ord a => (a, a) -> e -> ValidationRule (NonEmpty e) a
+valueWithin (m, n) = failureUnless $ \x -> m <= x && x <= n
+{-# INLINABLE valueWithin #-}
+{-# SPECIALIZE valueWithin :: (Int, Int) -> e -> ValidationRule (NonEmpty e) Int #-}
+
+{- | Build an 'all' rule.
+
+prop> onlyContains x = failureUnless (all x)
+-}
+onlyContains :: Foldable t => (a -> Bool) -> e -> ValidationRule (NonEmpty e) (t a)
+onlyContains x = failureUnless $ all x
+{-# INLINABLE onlyContains #-}
+{-# SPECIALIZE onlyContains :: (a -> Bool) -> e -> ValidationRule (NonEmpty e) [a] #-}
+
+{- | Build an 'any' rule.
+
+prop> atleastContains x = failureUnless (any x)
+-}
+atleastContains :: Foldable t => (a -> Bool) -> e -> ValidationRule (NonEmpty e) (t a)
+atleastContains x = failureUnless $ any x
+{-# INLINABLE atleastContains #-}
+{-# SPECIALIZE atleastContains :: (a -> Bool) -> e -> ValidationRule (NonEmpty e) [a] #-}
+
+{- | Build an 'elem' rule.
+
+prop> mustContain x = atleastContains (==x)
+
+prop> mustContain x = failureUnless (elem x)
+-}
+mustContain :: (Foldable t, Eq a) => a -> e -> ValidationRule (NonEmpty e) (t a)
+mustContain x = failureUnless $ elem x
+{-# INLINABLE mustContain #-}
+{-# SPECIALIZE mustContain :: Eq a => a -> e -> ValidationRule (NonEmpty e) [a] #-}
+
+---------------------------------------------------------------------
+-- Common derivates of primitive /Unit/ combinators
+---------------------------------------------------------------------
+
+-- | Like 'mustBe' but uses /Unit/ as the 'ValidationRule' error type.
+mustBe' :: Eq a => a -> ValidationRule () a
+mustBe' x = failureUnless' (==x)
+{-# INLINABLE mustBe' #-}
+
+-- | Like 'ofLength' but uses /Unit/ as the 'ValidationRule' error type.
+ofLength' :: Foldable t => Int -> ValidationRule () (t a)
+ofLength' n = failureUnless' $ (==n) . length
+{-# INLINABLE ofLength' #-}
+{-# SPECIALIZE ofLength' :: Int -> ValidationRule () [a] #-}
+
+-- | Like 'minLengthOf' but uses /Unit/ as the 'ValidationRule' error type.
+minLengthOf' :: Foldable t => Int -> ValidationRule () (t a)
+minLengthOf' n = failureUnless' $ (>=n) . length
+{-# INLINABLE minLengthOf' #-}
+{-# SPECIALIZE minLengthOf' :: Int -> ValidationRule () [a] #-}
+
+-- | Like 'maxLengthOf' but uses /Unit/ as the 'ValidationRule' error type.
+maxLengthOf' :: Foldable t => Int -> ValidationRule () (t a)
+maxLengthOf' n = failureUnless' $ (<=n) . length
+{-# INLINABLE maxLengthOf' #-}
+{-# SPECIALIZE maxLengthOf' :: Int -> ValidationRule () [a] #-}
+
+-- | Like 'lengthAbove' but uses /Unit/ as the 'ValidationRule' error type.
+lengthAbove' :: Foldable t => Int -> ValidationRule () (t a)
+lengthAbove' n = failureUnless' $ (>n) . length
+{-# INLINABLE lengthAbove' #-}
+{-# SPECIALIZE lengthAbove' :: Int -> ValidationRule () [a] #-}
+
+-- | Like 'lengthBelow' but uses /Unit/ as the 'ValidationRule' error type.
+lengthBelow' :: Foldable t => Int -> ValidationRule () (t a)
+lengthBelow' n = failureUnless' $ (<n) . length
+{-# INLINABLE lengthBelow' #-}
+{-# SPECIALIZE lengthBelow' :: Int -> ValidationRule () [a] #-}
+
+-- | Like 'notEmpty' but uses /Unit/ as the 'ValidationRule' error type.
+notEmpty' :: Foldable t => ValidationRule () (t a)
+notEmpty' = failureIf' null
+{-# INLINABLE notEmpty' #-}
+{-# SPECIALIZE notEmpty' :: ValidationRule () [a] #-}
+
+-- | Like 'lengthWithin' but uses /Unit/ as the 'ValidationRule' error type.
+lengthWithin' :: Foldable t => (Int, Int) -> ValidationRule () (t a)
+lengthWithin' r = failureUnless' $ inRange r . length
+{-# INLINABLE lengthWithin' #-}
+{-# SPECIALIZE ofLength' :: Int -> ValidationRule () [a] #-}
+
+-- | Like 'minValueOf' but uses /Unit/ as the 'ValidationRule' error type.
+minValueOf' :: Ord a => a -> ValidationRule () a
+minValueOf' x = failureUnless' (>=x)
+{-# INLINABLE minValueOf' #-}
+
+-- | Like 'maxValueOf' but uses /Unit/ as the 'ValidationRule' error type.
+maxValueOf' :: Ord a => a -> ValidationRule () a
+maxValueOf' x = failureUnless' (<=x)
+{-# INLINABLE maxValueOf' #-}
+
+-- | Like 'valueAbove' but uses /Unit/ as the 'ValidationRule' error type.
+valueAbove' :: Ord a => a -> ValidationRule () a
+valueAbove' n = failureUnless' (>n)
+{-# INLINABLE valueAbove' #-}
+
+-- | Like 'valueBelow' but uses /Unit/ as the 'ValidationRule' error type.
+valueBelow' :: Ord a => a -> ValidationRule () a
+valueBelow' n = failureUnless' (<n)
+{-# INLINABLE valueBelow' #-}
+
+-- | Like 'valueWithin' but uses /Unit/ as the 'ValidationRule' error type.
+valueWithin' :: Ord a => (a, a) -> ValidationRule () a
+valueWithin' (m, n) = failureUnless' $ \x -> m <= x && x <= n
+{-# INLINABLE valueWithin' #-}
+{-# SPECIALIZE valueWithin' :: (Int, Int) -> ValidationRule () Int #-}
+
+-- | Like 'onlyContains' but uses /Unit/ as the 'ValidationRule' error type.
+onlyContains' :: Foldable t => (a -> Bool) -> ValidationRule () (t a)
+onlyContains' x = failureUnless' $ all x
+{-# INLINABLE onlyContains' #-}
+{-# SPECIALIZE onlyContains' :: (a -> Bool) -> ValidationRule () [a] #-}
+
+-- | Like 'atleastContains' but uses /Unit/ as the 'ValidationRule' error type.
+atleastContains' :: Foldable t => (a -> Bool) -> ValidationRule () (t a)
+atleastContains' x = failureUnless' $ any x
+{-# INLINABLE atleastContains' #-}
+{-# SPECIALIZE atleastContains' :: (a -> Bool) -> ValidationRule () [a] #-}
+
+-- | Like 'mustContain' but uses /Unit/ as the 'ValidationRule' error type.
+mustContain' :: (Foldable t, Eq a) => a -> ValidationRule () (t a)
+mustContain' x = failureUnless' $ elem x
+{-# INLINABLE mustContain' #-}
+{-# SPECIALIZE mustContain' :: Eq a => a -> ValidationRule () [a] #-}
+
+---------------------------------------------------------------------
+-- Negating 'ValidationRule'
+---------------------------------------------------------------------
+
+{- | Build a rule that succeeds if given rule fails and vice versa.
+
+==== __Examples__
+
+>>> let rule = negateRule "NonPositive" (failureIf (>0) "Positive")
+>>> runValidator (validate rule) 5
+Success 5
+>>> runValidator (validate rule) 0
+Failure "NonPositive"
+>>> runValidator (validate rule) (-1)
+Failure "NonPositive"
+-}
+negateRule :: e -> ValidationRule e1 a -> ValidationRule e a
+negateRule err (ValidationRule rule) = vrule $ validationConst (Success ()) (Failure err) . rule
+
+-- | Like 'negateRule' but uses /Unit/ as the 'ValidationRule' error type.
+negateRule' :: ValidationRule e a -> ValidationRule () a
+negateRule' (ValidationRule rule) = vrule $ ($ ()) . validationConst Success Failure . rule
+
+---------------------------------------------------------------------
+-- Combining 'ValidationRule's
+---------------------------------------------------------------------
+
+-- | A synonym for 'orElse'. Satisfies associativity law and hence forms a semigroup.
+infixr 5 </>
+
+(</>) :: Semigroup e => ValidationRule e a -> ValidationRule e a -> ValidationRule e a
+ValidationRule rule1 </> ValidationRule rule2 = vrule $ liftA2 (<>) rule1 rule2
+{-# INLINABLE (</>)  #-}
+{-# SPECIALIZE (</>)
+    :: ValidationRule (NonEmpty err) a
+    -> ValidationRule (NonEmpty err) a
+    -> ValidationRule (NonEmpty err) a #-}
+{-# SPECIALIZE (</>) :: ValidationRule () a -> ValidationRule () a -> ValidationRule () a #-}
+{-# SPECIALIZE (</>) :: ValidationRule [err] a -> ValidationRule [err] a -> ValidationRule [err] a #-}
+
+{- | Build a rule that /succeeds/ if __either__ of the given rules succeed. If both fail, the errors are combined.
+
+prop> rule1 `orElse` (rule2 `orElse` rule3) = (rule1 `orElse` rule2) `orElse` rule3
+prop> falseRule e `orElse` rule = rule
+prop> rule `orElse` falseRule e = rule
+
+==== __Examples__
+
+>>> let rule = failureIf (>0) "Positive" `orElse` failureIf even "Even"
+>>> runValidator (validate rule) 5
+Success 5
+>>> runValidator (validate rule) 4
+Failure ("Positive" :| ["Even"])
+>>> runValidator (validate rule) 0
+Success 0
+>>> runValidator (validate rule) (-1)
+Success (-1)
+-}
+orElse :: Semigroup e => ValidationRule e a -> ValidationRule e a -> ValidationRule e a
+orElse = (</>)
+{-# INLINABLE orElse #-}
+
+{- | A 'ValidationRule' that always fails with supplied error. This is the identity of 'orElse' (i.e '(</>)').
+
+prop> falseRule `orElse` rule = rule
+prop> rule `orElse` falseRule = rule
+
+==== __Examples__
+
+>>> runValidator (validate falseRule) 42
+Failure ()
+-}
+falseRule :: Monoid e => ValidationRule e a
+falseRule = vrule $ const $ Failure mempty
+{-# INLINABLE falseRule #-}
+
+{- | Build a rule that /only succeeds/ if __both__ of the given rules succeed. The very first failure is yielded.
+
+This is the same as the semigroup operation (i.e '(<>)') on 'ValidationRule'.
+
+prop> rule1 `andAlso` (rule2 `andAlso` rule3) = (rule1 `andAlso` rule2) `andAlso` rule3
+prop> mempty `andAlso` rule = rule
+prop> rule `andAlso` mempty = rule
+
+==== __Examples__
+
+>>> let rule = failureIf (>0) "Positive" `andAlso` failureIf even "Even"
+>>> runValidator (validate rule) 5
+Failure ("Positive" :| [])
+>>> runValidator (validate rule) (-2)
+Failure ("Even" :| [])
+>>> runValidator (validate rule) (-1)
+Success (-1)
+-}
+andAlso :: ValidationRule e a -> ValidationRule e a -> ValidationRule e a
+andAlso = (<>)
+{-# INLINABLE andAlso #-}
+
+{- | Build a rule that /succeeds/ if __any__ of the given rules succeed. If all fail, the errors are combined.
+
+prop> satisfyAny = foldl1 orElse
+prop> satisfyAny = foldr1 orElse
+prop> satisfyAny = foldl orElse falseRule
+prop> satisfyAny = foldr orElse falseRule
+-}
+satisfyAny :: (Foldable t, Semigroup e) => t (ValidationRule e a) -> ValidationRule e a
+satisfyAny = foldr1 (</>)
+{-# INLINABLE satisfyAny #-}
+{-# SPECIALIZE satisfyAny :: [ValidationRule (NonEmpty err) a] -> ValidationRule (NonEmpty err) a #-}
+{-# SPECIALIZE satisfyAny :: [ValidationRule () a] -> ValidationRule () a #-}
+{-# SPECIALIZE satisfyAny :: [ValidationRule [err] a] -> ValidationRule [err] a #-}
+
+{- | Build a rule that /only succeeds/ if __all__ of the given rules succeed. The very first failure is yielded.
+
+prop> satisfyAll = fold
+prop> satisfyAll = foldl1 andAlso
+prop> satisfyAll = foldr1 andAlso
+prop> satisfyAll = foldl andAlso mempty
+prop> satisfyAll = foldr andAlso mempty
+-}
+satisfyAll :: Foldable t => t (ValidationRule e a) -> ValidationRule e a
+satisfyAll = fold
+{-# INLINABLE satisfyAll #-}
+{-# SPECIALIZE satisfyAll :: [ValidationRule e a] -> ValidationRule e a #-}
+
+---------------------------------------------------------------------
+-- Type specific 'ValidationRule's
+---------------------------------------------------------------------
+
+{- | Build a rule that runs given rule only if input is 'Just'.
+
+Yields 'Success' when input is 'Nothing.
+
+==== __Examples__
+
+>>> runValidator (validate (optionally (failureIf even "Even"))) (Just 5)
+Success (Just 5)
+>>> runValidator (validate (optionally (failureIf even "Even"))) (Just 6)
+Failure ("Even" :| [])
+>>> runValidator (validate (optionally (failureIf even "Even"))) Nothing
+Success Nothing
+-}
+optionally :: ValidationRule e a -> ValidationRule e (Maybe a)
+optionally (ValidationRule rule) = vrule $ maybe (Success ()) rule
+
+-- | Utility to convert a regular predicate function to a 'ValidationRule'. __INTERNAL__
+predToRule :: (a -> Bool) -> e -> ValidationRule e a
+predToRule predc err = vrule $ bool (Failure err) (Success ()) . predc
+ src/Valida/Utils.hs view
@@ -0,0 +1,10 @@+{-# LANGUAGE Safe #-}
+
+module Valida.Utils
+    ( neSingleton
+    ) where
+
+import Data.List.NonEmpty (NonEmpty ((:|)))
+
+neSingleton :: a -> NonEmpty a
+neSingleton = (:|[])
+ src/Valida/Validation.hs view
@@ -0,0 +1,178 @@+{-# LANGUAGE DeriveDataTypeable #-}
+{-# LANGUAGE DeriveGeneric      #-}
+{-# LANGUAGE Safe               #-}
+
+module Valida.Validation
+    ( Validation (..)
+    , validation
+    , validationConst
+    ) where
+
+import Data.Bifoldable    (Bifoldable (bifoldMap))
+import Data.Bifunctor     (Bifunctor (bimap))
+import Data.Bitraversable (Bitraversable)
+import Data.Data          (Data)
+import Data.List.NonEmpty (NonEmpty)
+import Data.Typeable      (Typeable)
+
+import GHC.Generics (Generic)
+
+-- | Like 'Either', but accumulates failures upon applicative composition.
+data Validation e a
+  -- | Represents a validation failure with an error.
+  = Failure e
+  -- | Represents a successful validation with the validated value.
+  | Success a
+  deriving (Eq, Ord, Read, Show, Data, Typeable, Generic)
+
+{- |
+
+[@fmap@] 'fmap' maps given function over a 'Success' value, does nothing on 'Failure' value.
+
+==== __Examples__
+
+>>> fmap (+1) (Success 2)
+Success 3
+>>> fmap (+1) (Failure "error")
+Failure "error"
+-}
+instance Functor (Validation e) where
+    fmap _ (Failure e) = Failure e
+    fmap f (Success a) = Success $ f a
+
+instance Bifunctor Validation where
+    bimap f g = validation (Failure . f) (Success . g)
+
+{- |
+[@pure@] 'pure' is a 'Success' value.
+[@(<*>)@] '(<*>)' behaves similar to 'Either', but accumulates failures instead of stopping.
+
+==== __Examples__
+
+>>> pure 2 :: Validation String Int
+Success 2
+>>> Success (+1) <*> Success 4
+Success 5
+>>> Success (+1) <*> Failure "error"
+Failure "error"
+>>> Failure ["err1"] <*> Failure ["err2"]
+Failure ["err1","err2"]
+-}
+instance Semigroup e => Applicative (Validation e) where
+    {-# SPECIALIZE instance Applicative (Validation (NonEmpty err)) #-}
+    {-# SPECIALIZE instance Applicative (Validation ()) #-}
+    {-# SPECIALIZE instance Applicative (Validation [err]) #-}
+    pure = Success
+    {-# INLINEABLE pure #-}
+    Success f <*> Success b = Success $ f b
+    Success _ <*> Failure e = Failure e
+    Failure e <*> Success _ = Failure e
+    Failure x <*> Failure y = Failure $ x <> y
+    {-# INLINEABLE (<*>) #-}
+
+{- |
+[@(<>)@] This behaves similar to the 'Either' semigroup. i.e Returns the first 'Success'. But also accumulates 'Failure's.
+
+==== __Examples__
+
+>>> Success 1 <> Success 2
+Success 1
+>>> Failure "error" <> Success 1
+Success 1
+>>> Success 2 <> Failure "error"
+Success 2
+>>> Failure ["err1"] <> Failure ["err2"]
+Failure ["err1","err2"]
+-}
+instance Semigroup e => Semigroup (Validation e a) where
+    {-# SPECIALIZE instance Semigroup (Validation (NonEmpty err) a) #-}
+    {-# SPECIALIZE instance Semigroup (Validation () a) #-}
+    {-# SPECIALIZE instance Semigroup (Validation [err] a) #-}
+    s@(Success _) <> _             = s
+    _             <> s@(Success _) = s
+    Failure x     <> Failure y     = Failure $ x <> y
+    {-# INLINEABLE (<>) #-}
+
+{- |
+
+[@foldMap@] 'foldMap' maps given function over a 'Success' value, returns 'mempty' for a 'Failure' value.
+
+==== __Examples__
+
+>>> foldMap (:[]) (Success 2)
+[2]
+>>> foldMap (:[]) (Failure "error")
+[]
+-}
+instance Foldable (Validation e) where
+    foldMap = validation (const mempty)
+
+{- |
+
+[@traverse@] In case of 'Success', 'traverse' applies given function to the inner value, and maps 'Success' over the result.
+In case of 'Failure', 'traverse' returns 'Failure', wrapped in minimal context of the corresponding type ('pure').
+
+==== __Examples__
+
+>>> traverse Just (Success 2)
+Just (Success 2)
+>>> traverse Just (Failure "error")
+Just (Failure "error")
+-}
+instance Traversable (Validation e) where
+    traverse f = validation (pure . Failure) (fmap Success . f)
+
+{- |
+
+[@bifoldMap@] 'bifoldMap' is the same as 'validation'.
+
+==== __Examples__
+
+'bifoldMap' (and its more generalized version, 'validation') can eliminate the need to pattern match on 'Validation'.
+
+>>> import Data.Bifoldable
+>>> bifoldMap reverse (:[]) (Success 'c' :: Validation String Char)
+"c"
+>>> bifoldMap reverse (:[]) (Failure "error" :: Validation String Char)
+"rorre"
+-}
+instance Bifoldable Validation where
+    bifoldMap = validation
+
+instance Bitraversable Validation
+
+{- | Case analysis for 'Validation', i.e catamorphism.
+
+In case of 'Failure e', apply the first function to e; in case of 'Success a', apply the second function to a.
+
+This is a more generalized version of the 'bifoldMap' implementation.
+
+==== __Examples__
+
+>>> validation (const Nothing) Just (Success 'c' :: Validation String Char)
+Just 'c'
+>>> validation (const Nothing) Just (Failure "error" :: Validation String Char)
+Nothing
+-}
+validation :: (e -> c) -> (a -> c) -> Validation e a -> c
+validation ef _ (Failure e) = ef e
+validation _ af (Success a) = af a
+
+{- | Case analysis for 'Validation', with replacer.
+
+This is similar to 'validation', but takes in replacers instead of functions.
+
+In case of 'Failure', return the first argument; otherwise, return the second argument.
+
+prop> validationConst e a = validation (const e) (const a)
+
+==== __Examples__
+
+>>> validation (const Nothing) Just (Success 'c' :: Validation String Char)
+Just 'c'
+>>> validation (const Nothing) Just (Failure "error" :: Validation String Char)
+Nothing
+-}
+validationConst :: p -> p -> Validation e a -> p
+validationConst e _ (Failure _) = e
+validationConst _ a _           = a
+ src/Valida/ValidationRule.hs view
@@ -0,0 +1,73 @@+{-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE Safe          #-}
+
+module Valida.ValidationRule
+    ( ValidationRule (..)
+    , vrule
+    ) where
+
+import Data.Typeable (Typeable)
+
+import GHC.Generics (Generic)
+
+import Valida.Validation (Validation (..))
+
+{- | The rule a Validator uses to run validation.
+
+Contains a function that accepts the target type and returns a `Validation` result.
+
+The type- __ValidationRule (NonEmpty String) Int__, designates a rule that verifies the validity of an __Int__, and
+uses a value of type __NonEmpty String__ to represent error, in case of failure.
+-}
+newtype ValidationRule e a
+  -- | Builds a 'ValidationRule' from a function to generate error and a validation predicate.
+  = ValidationRule
+  -- ^ The validation predicate.
+    (a -> Validation e ())
+  deriving (Typeable, Generic)
+
+{- |
+
+[@(<>)@] '(<>)' creates a new `ValidationRule` that only succeeds when both given rule succeed.
+Otherwise left-most failure is returned.
+
+==== __Examples__
+
+>>> runValidator (validate (failureIf even "IsEven" <> failureIf (>9) "GreaterThan9")) 5
+Success 5
+>>> runValidator (validate (failureIf even "IsEven" <> failureIf (>9) "GreaterThan9")) 4
+Failure ("IsEven" :| [])
+>>> runValidator (validate (failureIf even "IsEven" <> failureIf (>9) "GreaterThan9")) 15
+Failure ("GreaterThan9" :| [])
+>>> runValidator (validate (failureIf even "IsEven" <> failureIf (>9) "GreaterThan9")) 12
+Failure ("IsEven" :| [])
+-}
+instance Semigroup (ValidationRule e a) where
+    ValidationRule rl1 <> ValidationRule rl2 = ValidationRule
+        $ \x -> case (rl1 x, rl2 x) of
+            (f@(Failure _), _) -> f
+            (_, b)             -> b
+
+{- |
+
+[@mempty@] 'mempty' is a 'ValidationRule' that always succeeds.
+
+==== __Examples__
+
+>>> runValidator (validate mempty) 'a'
+Success 'a'
+-}
+instance Monoid (ValidationRule e a) where
+    mempty = ValidationRule $ const $ Success ()
+
+{- | Low level function to manually build a `ValidationRule`. You should use the combinators instead.
+
+==== __Examples__
+
+>>> runValidator (validate (vrule (\x -> if isDigit x then Success () else Failure "NotDigit"))) 'a'
+Failure "NotDigit"
+>>> runValidator (validate (vrule (\x -> if isDigit x then Success () else Failure "NotDigit"))) '5'
+Success '5'
+-}
+vrule :: (a -> Validation e ()) -> ValidationRule e a
+vrule = ValidationRule
+ src/Valida/ValidationUtils.hs view
@@ -0,0 +1,133 @@+{-# LANGUAGE Safe #-}
+
+module Valida.ValidationUtils
+    ( -- * Transformations between 'Either' and 'Validation'
+      fromEither
+    , toEither
+      -- * Utilities for working with 'Validation'
+    , failures
+    , fromFailure
+    , fromSuccess
+    , isFailure
+    , isSuccess
+    , partitionValidations
+    , successes
+    , validation
+    , validationConst
+    ) where
+
+import Valida.Validation (Validation (..), validation, validationConst)
+
+{- | Convert a 'Validation' to an 'Either'.
+
+Given, __Validation a b__-
+
+  * __Failure a__ is converted to __Left a__.
+  * __Success b__ is converted to __Right b__.
+
+==== __Examples__
+
+>>> toEither (Success 'c' :: Validation String Char)
+Right 'c'
+>>> toEither (Failure 42 :: Validation Int Char)
+Left 42
+-}
+toEither :: Validation a b -> Either a b
+toEither = validation Left Right
+
+{- | Convert a 'Either' to an 'Validation'.
+
+Given, __Either e a__-
+
+  * __Left e__ is converted to __Failure e__.
+  * __Right a__ is converted to __Success a__.
+
+==== __Examples__
+
+>>> fromEither (Right 'c' :: Either String Char)
+Success 'c'
+>>> fromEither (Left 42 :: Either Int Char)
+Failure 42
+-}
+fromEither :: Either e a -> Validation e a
+fromEither = either Failure Success
+
+-- | Return True if the given value is a Failure-value, False otherwise.
+isFailure :: Validation e a -> Bool
+isFailure (Failure _) = True
+isFailure _           = False
+
+-- | Return True if the given value is a Success-value, False otherwise.
+isSuccess :: Validation e a -> Bool
+isSuccess (Success _) = True
+isSuccess _           = False
+
+{- | Return the contents of a Failure-value or a default value otherwise.
+
+==== __Examples__
+
+>>> fromFailure 0 (Success 48 :: Validation Int Int)
+0
+>>> fromFailure 0 (Failure 27 :: Validation Int Int)
+27
+-}
+fromFailure :: e -> Validation e a -> e
+fromFailure _ (Failure e) = e
+fromFailure e _           = e
+
+{- | Return the contents of a Success-value or a default value otherwise.
+
+==== __Examples__
+
+>>> fromSuccess 0 (Success 48 :: Validation Int Int)
+48
+>>> fromSuccess 0 (Failure 27 :: Validation Int Int)
+0
+-}
+fromSuccess :: a -> Validation e a -> a
+fromSuccess _ (Success a) = a
+fromSuccess a _           = a
+
+{- | Extracts from a list of 'Validation' all the Failure elements, in order.
+
+==== __Examples__
+
+>>> failures ([Success 48, Failure "err1", Failure "err2", Success 2, Failure "err3"] :: [Validation String Int])
+["err1","err2","err3"]
+>>> failures ([Success 1, Success 2, Success 3] :: [Validation String Int])
+[]
+-}
+failures :: [Validation e a] -> [e]
+failures xs = [e | Failure e <- xs]
+{-# INLINABLE failures #-}
+
+{- | Extracts from a list of 'Validation' all the Success elements, in order.
+
+==== __Examples__
+
+>>> successes ([Success 1, Failure "err1", Failure "err2", Success 2, Failure "err3"] :: [Validation String Int])
+[1,2]
+>>> successes ([Failure "err1", Failure "err2", Failure "err3"] :: [Validation String Int])
+[]
+-}
+successes :: [Validation e a] -> [a]
+successes xs = [a | Success a <- xs]
+{-# INLINABLE successes #-}
+
+{- | Partitions a list of Either into two lists.
+
+All the Left elements are extracted, in order, to the first component of the output.
+Similarly the Right elements are extracted to the second component of the output.
+
+prop> partitionValidations xs = (failures xs, successes xs)
+
+==== __Examples__
+
+>>> partitionValidations ([Success 1, Failure "err1", Failure "err2", Success 2, Failure "err3"] :: [Validation String Int])
+(["err1","err2","err3"],[1,2])
+-}
+partitionValidations :: [Validation e a] -> ([e], [a])
+partitionValidations = foldr (validation failure success) ([],[])
+ where
+  failure a ~(l, r) = (a:l, r)
+  success a ~(l, r) = (l, a:r)
+ src/Valida/Validator.hs view
@@ -0,0 +1,95 @@+{-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE Safe          #-}
+
+module Valida.Validator
+    ( Selector
+    , Validator (..)
+    ) where
+
+import Data.List.NonEmpty (NonEmpty)
+import Data.Typeable      (Typeable)
+
+import GHC.Generics (Generic)
+
+import Valida.Validation (Validation (..))
+
+-- | Convenience alias for functions that "select" a record field.
+type Selector a b = a -> b
+
+-- | An applicative validator. Validates a predicate on an input when run and returns the 'Validation' result.
+newtype Validator e inp a = Validator { runValidator :: inp -> Validation e a }
+  deriving (Typeable, Generic)
+
+{- |
+
+[@fmap@] 'fmap' maps given function over the 'Validation' result by re-using 'fmap' on it.
+
+==== __Examples__
+
+>>> runValidator (fmap (+1) (validate $ failureIf (==2) "IsTwo")) 3
+Success 4
+>>> runValidator (fmap (+1) (validate $ failureIf (==2) "IsTwo")) 2
+Failure ("IsTwo" :| [])
+-}
+instance Functor (Validator e inp) where
+    fmap f (Validator v) = Validator $ fmap f . v
+
+{- |
+
+[@pure@] 'pure' creates a 'Validator' that always yields given value wrapped in 'Success', ignoring its input.
+
+[@(<*>)@] '(<*>)' runs 2 validators to obtain the 2 'Validation' results and combines them with '(<*>)'.
+This can be understood as-
+
+    @
+    (Validator ff) \<*\> (Validator v) = Validator (\\inp -> ff inp \<*\> v inp)
+    @
+
+    i.e Run __ff__ and __v__ on the input, and compose the 'Validation' results with '(<*>)'.
+
+==== __Examples__
+
+>>> runValidator (pure 5) 42
+Success 5
+>>> let v1 = validate (failureIf (==2) "IsTwo")
+>>> let v2 = validate (failureIf even "IsEven")
+>>> runValidator (const <$> v1 <*> v2) 5
+Success 5
+>>> runValidator (const <$> v1 <*> v2) 4
+Failure ("IsEven" :| [])
+>>> runValidator (const <$> v1 <*> v2) 2
+Failure ("IsTwo" :| ["IsEven"])
+-}
+instance Semigroup e => Applicative (Validator e inp) where
+    {-# SPECIALIZE instance Applicative (Validator (NonEmpty err) inp) #-}
+    {-# SPECIALIZE instance Applicative (Validator () inp) #-}
+    {-# SPECIALIZE instance Applicative (Validator [err] inp) #-}
+    pure = Validator . const . Success
+    {-# INLINEABLE pure #-}
+    Validator ff <*> Validator v = Validator $ \x -> ff x <*> v x
+    {-# INLINEABLE (<*>) #-}
+
+{- |
+
+[@(<>)@] '(<>)' applies input over both validator functions, and combines the 'Validation' results using '(<>)'.
+
+==== __Examples__
+
+This essentially reuses the '(<>)' impl of 'Validation'.
+i.e Returns the first 'Success'. But also accumulates 'Failure's.
+
+>>> let v1 = validate (failureIf (==2) "IsTwo")
+>>> let v2 = validate (failureIf even "IsEven")
+>>> runValidator (v1 <> v2) 5
+Success 5
+>>> runValidator (v1 <> v2) 4
+Success 4
+>>> runValidator (v1 <> v2) 2
+Failure ("IsTwo" :| ["IsEven"])
+-}
+instance Semigroup e => Semigroup (Validator e inp a) where
+    {-# SPECIALIZE instance Semigroup (Validator (NonEmpty err) inp a) #-}
+    {-# SPECIALIZE instance Semigroup (Validator () inp a) #-}
+    {-# SPECIALIZE instance Semigroup (Validator [err] inp a) #-}
+    Validator f <> Validator g = Validator $ f <> g
+    {-# INLINEABLE (<>) #-}
+ test/Gen.hs view
@@ -0,0 +1,48 @@+{-# LANGUAGE FlexibleInstances     #-}
+{-# LANGUAGE MultiParamTypeClasses #-}
+
+module Gen
+    ( NonEmptyLQ
+    , ValidationQ (..)
+    ) where
+
+import qualified Data.List.NonEmpty as NE
+
+import           Test.SmallCheck.Series (Serial (..), cons1, (\/))
+import           Test.Tasty.QuickCheck  (Arbitrary (arbitrary, shrink), oneof)
+import qualified Test.Tasty.QuickCheck  as QC
+
+
+import Valida (Validation (..))
+
+-- | Wrapper around 'Validation' with 'Arbitrary' and 'Serial' instances.
+newtype ValidationQ e a = ValidationQ (Validation e a)
+  deriving (Show)
+
+instance (Arbitrary e, Arbitrary a) => Arbitrary (ValidationQ e a) where
+    arbitrary = ValidationQ <$> oneof [Failure <$> arbitrary, Success <$> arbitrary]
+    shrink (ValidationQ (Failure e)) = ValidationQ . Failure <$> shrink e
+    shrink (ValidationQ (Success a)) = ValidationQ . Success <$> shrink a
+
+instance (Serial m e, Serial m a) => Serial m (ValidationQ e a) where
+    series = ValidationQ <$> cons1 Failure \/ cons1 Success
+
+-- | Type unifying the separate `NonEmpty` impls on quickcheck and smallcheck.
+data NonEmptyLQ a = NonEmptyListQ (QC.NonEmptyList a) | NonEmptyQ (NE.NonEmpty a)
+  deriving (Show)
+
+instance Arbitrary a => Arbitrary (NonEmptyLQ a) where
+    arbitrary = NonEmptyListQ <$> arbitrary
+    shrink (NonEmptyListQ l) = NonEmptyListQ <$> shrink l
+    shrink _                 = error "Should not try to shrink NonEmptyQ"
+
+instance Serial m a => Serial m (NonEmptyLQ a) where
+    series = cons1 NonEmptyQ
+
+instance Functor NonEmptyLQ where
+    fmap f (NonEmptyListQ l) = NonEmptyListQ $ f <$> l
+    fmap f (NonEmptyQ l)     = NonEmptyQ $ f <$> l
+
+instance Foldable NonEmptyLQ where
+    foldMap f (NonEmptyListQ l) = foldMap f $ QC.getNonEmpty l
+    foldMap f (NonEmptyQ l)     = foldMap f l
+ test/Spec.hs view
@@ -0,0 +1,506 @@+module Main
+    ( main
+    ) where
+
+import Data.Bool          (bool)
+import Data.Either        (isRight)
+import Data.Foldable      (Foldable (fold))
+import Data.List.NonEmpty (NonEmpty)
+import Data.Maybe         (isNothing)
+import Data.Monoid        (Sum)
+
+import           Test.Tasty            (TestTree, defaultMain, testGroup)
+import           Test.Tasty.HUnit      (testCase, (@?=))
+import qualified Test.Tasty.QuickCheck as QC
+import qualified Test.Tasty.SmallCheck as SC
+
+import Valida (Validation (..), ValidationRule, Validator (runValidator), failureIf, failureIf', failureUnless,
+               failureUnless', failures, falseRule, fromEither, label, negateRule, negateRule', partitionValidations,
+               satisfyAll, satisfyAny, successes, toEither, validate, validation, validationConst, vrule, (-?>), (</>),
+               (<?>), (<??>))
+
+import Gen   (NonEmptyLQ, ValidationQ (..))
+import Utils (neSingleton)
+
+-- | Helper to directly make a ValidationRule out of an error and predicate function.
+predToVRule :: e -> (a -> Bool) -> ValidationRule e a
+predToVRule err f = vrule $ bool (Failure err) (Success ()) . f
+
+-- | Helper to build a validator and run it on input at once.
+validatify :: ValidationRule e a -> a -> Validation e a
+validatify = runValidator . validate
+
+-- | Test the primitive non empty combinators.
+testPrimCombs :: [TestTree]
+testPrimCombs =
+  [ testCase "failureIf fails with expected error when predicate yields true" $ do
+      testValidator (failureIf $ (<7) . sum, "Sum is too small.", [1, 2] :: [Int], const . Failure . neSingleton)
+      testValidator (failureIf $ (<7) . sum, "Sum is too small.", [5, 1] :: [Int], const . Failure . neSingleton)
+      testValidator (failureIf (==0), "Equal to 0", 0 :: Int, const . Failure . neSingleton)
+      testValidator (failureIf null, "Is empty", [] :: [()], const . Failure . neSingleton)
+  , testCase "failureIf succeeds when predicate yields false" $ do
+      testValidator (failureIf (elem 'c'), "Has the letter 'c'", "foo", const Success)
+      testValidator (failureIf isNothing, "Is nothing", Just (), const Success)
+      testValidator (failureIf isRight, "Is right (Either)", Left () :: Either () (), const Success)
+      testValidator (failureIf or, "Has True", [False, False, False], const Success)
+  , testCase "failureUnless succeeds when predicate yields true" $ do
+      testValidator (failureUnless $ (<7) . sum, "Sum is too small.", [1, 2] :: [Int], const Success)
+      testValidator (failureUnless $ (<7) . sum, "Sum is too small.", [5, 1] :: [Int], const Success)
+      testValidator (failureUnless (==0), "Equal to 0", 0 :: Int, const Success)
+      testValidator (failureUnless null, "Is empty", [] :: [()], const Success)
+  , testCase "failureUnless fails with expected error when predicate yields false" $ do
+      testValidator (failureUnless (elem 'c'), "Has the letter 'c'", "foo", const . Failure . neSingleton)
+      testValidator (failureUnless isNothing, "Is nothing", Just (), const . Failure . neSingleton)
+      testValidator (failureUnless isRight, "Is right (Either)", Left () :: Either () (), const . Failure . neSingleton)
+      testValidator (failureUnless or, "Has True", [False, False, False], const . Failure . neSingleton)
+  ]
+  where
+    testValidator ~(rule, err, inp, expct) = rule err `validatify` inp @?= expct err inp
+
+-- | Test the primitive unit combinators.
+testPrimCombs' :: [TestTree]
+testPrimCombs' =
+  [ testCase "failureIf fails with expected error when predicate yields true" $ do
+      testValidator (failureIf' ((<7) . sum), [1, 2] :: [Int], const $ Failure ())
+      testValidator (failureIf' ((<7) . sum), [5, 1] :: [Int], const $ Failure ())
+      testValidator (failureIf' (==0), 0 :: Int, const $ Failure ())
+      testValidator (failureIf' null, [] :: [()], const $ Failure ())
+  , testCase "failueIf succeeds when predicate yields false" $ do
+      testValidator (failureIf' (elem 'c'), "foo", Success)
+      testValidator (failureIf' isNothing, Just (), Success)
+      testValidator (failureIf' isRight, Left () :: Either () (), Success)
+      testValidator (failureIf' or, [False, False, False], Success)
+  , testCase "failureUnless succeeds when predicate yields true" $ do
+      testValidator (failureUnless' ((<7) . sum), [1, 2] :: [Int], Success)
+      testValidator (failureUnless' ((<7) . sum), [5, 1] :: [Int], Success)
+      testValidator (failureUnless' (==0), 0 :: Int, Success)
+      testValidator (failureUnless' null, [] :: [()], Success)
+  , testCase "failureUnless fails with expected error when predicate yields false" $ do
+      testValidator (failureUnless' (elem 'c'), "foo", const $ Failure ())
+      testValidator (failureUnless' isNothing, Just (), const $ Failure ())
+      testValidator (failureUnless' isRight, Left () :: Either () (), const $ Failure ())
+      testValidator (failureUnless' or, [False, False, False], const $ Failure ())
+  ]
+  where
+    testValidator ~(rule, inp, expct) = rule `validatify` inp @?= expct inp
+
+-- | Test the primitive unit combinators with labeled errors.
+testPrimCombs'' :: [TestTree]
+testPrimCombs'' =
+  [ testCase "failureIf fails with expected error when predicate yields true" $ do
+      testValidator (failureIf' ((<7) . sum), "Sum is too small.", [1, 2] :: [Int], Failure)
+      testValidator (failureIf' ((<7) . sum), "Sum is too small.", [5, 1] :: [Int], Failure)
+      testValidator (failureIf' (==0), "Equal to 0", 0 :: Int, Failure)
+      testValidator (failureIf' null, "Is empty", [] :: [()], Failure)
+  , testCase "failureUnless fails with expected error when predicate yields false" $ do
+      testValidator (failureUnless' (elem 'c'), "Has the letter 'c'", "foo", Failure)
+      testValidator (failureUnless' isNothing, "Is nothing", Just (), Failure)
+      testValidator (failureUnless' isRight, "Is right (Either)", Left () :: Either () (), Failure)
+      testValidator (failureUnless' or, "Has True", [False, False, False], Failure)
+  ]
+  where
+    testValidator ~(rule, err, inp, expct) = (rule <?> err) `validatify` inp @?= expct err
+
+-- | Test multiple validators combined.
+testValidatorCollc :: [TestTree]
+testValidatorCollc =
+  [ testCase "Applicative validation fails if at least one validator fails" $ do
+      testValidator ((\x _ _ -> x) <$> validatorOf even <*> validatorOf (>0) <*> validatorOf (<9)
+        , 5 :: Int
+        , const $ Failure ()
+        )
+      testValidator (const <$> validatorOf ((<7) . sum) <*> validatorOf ((>9) . length)
+        , [1, 3, 7, 6] :: [Int]
+        , const $ Failure ()
+        )
+      testValidator ((,) <$> fst -?> failureUnless' (>7) <*> snd -?> failureUnless' (<9)
+        , (7, 7) :: (Int, Int)
+        , const $ Failure ()
+        )
+  , testCase "Applicative validation succeeds if all validators suceed" $ do
+      testValidator ((\x _ _ -> x) <$> validatorOf even <*> validatorOf (>0) <*> validatorOf (<9)
+        , 6 :: Int
+        , Success
+        )
+      testValidator (const <$> validatorOf ((<7) . sum) <*> validatorOf ((>9) . length)
+        , [1, 0, 0, 0, 0, 0, 0, 3, 2, 0] :: [Int]
+        , Success
+        )
+      testValidator ((,) <$> fst -?> failureUnless' (>7) <*> snd -?> failureUnless' (<5)
+        , (8, 3) :: (Int, Int)
+        , Success
+        )
+  ]
+  where
+    validatorOf f = validate $ predToVRule () f
+    testValidator ~(validator, inp, expct) = runValidator validator inp @?= expct inp
+
+-- | Test utility functions for Validation.
+testValidationUtils :: [TestTree]
+testValidationUtils =
+  [ QC.testProperty "(QC) fromEither . toEither = id"
+      (vIdentityTest :: ValidationQ String Int -> Bool)
+  , SC.testProperty "(SC) fromEither . toEither = id"
+      (vIdentityTest :: ValidationQ Char Char -> Bool)
+  , QC.testProperty "(QC) toEither . fromEither = id"
+      (eIdentityTest :: Either Int Char -> Bool)
+  , SC.testProperty "(SC) toEither . fromEither = id"
+      (eIdentityTest :: Either (Char, Char) Bool -> Bool)
+  , QC.testProperty "(QC) either f g (toEither v) = validation f g v"
+      (vCatamorphTest :: ValidationQ String Int -> (String -> Char) -> (Int -> Char) -> Bool)
+  , QC.testProperty "(QC) validation f g (fromEither v) = either f g v"
+      (eCatamorphTest :: Either String Int -> (String -> Char) -> (Int -> Char) -> Bool)
+  , QC.testProperty "(QC) either (const f) (const g) (toEither v) == validationConst f g v"
+      (vCatamorphCnstTest :: ValidationQ String Int -> Maybe Float -> Maybe Float -> Bool)
+  , SC.testProperty "(SC) either (const f) (const g) (toEither v) == validationConst f g v"
+      (vCatamorphCnstTest :: ValidationQ Bool Char -> String -> String -> Bool)
+  , QC.testProperty "(QC) validationConst f g (fromEither e) == either (const f) (const g) e"
+      (eCatamorphCnstTest :: Either String Int -> Maybe Float -> Maybe Float -> Bool)
+  , SC.testProperty "(SC) validationConst f g (fromEither e) == either (const f) (const g) e"
+      (eCatamorphCnstTest :: Either Bool Char -> String -> String -> Bool)
+  , QC.testProperty "(QC) validationConst f g v == validation (const f) (const g) v"
+      (vvCatamorphCnstTest :: ValidationQ String Int -> Maybe Float -> Maybe Float -> Bool)
+  , SC.testProperty "(SC) validationConst f g v == validation (const f) (const g) v"
+      (vvCatamorphCnstTest :: ValidationQ Bool Char -> String -> String -> Bool)
+  , QC.testProperty "(QC) partitionValidations xs = (failures xs, successes xs)"
+      (partitionTest :: [ValidationQ Int Char] -> Bool)
+  , SC.testProperty "(SC) partitionValidations xs = (failures xs, successes xs)"
+      (partitionTest :: [ValidationQ Bool (Maybe Char)] -> Bool)
+  ]
+  where
+    vIdentityTest :: (Eq e, Eq a) => ValidationQ e a -> Bool
+    vIdentityTest (ValidationQ v) = fromEither (toEither v) == v
+    eIdentityTest :: (Eq e, Eq a) => Either e a -> Bool
+    eIdentityTest e = toEither (fromEither e) == e
+    vCatamorphTest :: Eq c => ValidationQ e a -> (e -> c) -> (a -> c) -> Bool
+    vCatamorphTest (ValidationQ v) f g = either f g (toEither v) == validation f g v
+    eCatamorphTest :: Eq c => Either e a -> (e -> c) -> (a -> c) -> Bool
+    eCatamorphTest e f g = validation f g (fromEither e) == either f g e
+    vCatamorphCnstTest :: Eq c => ValidationQ e a -> c -> c -> Bool
+    vCatamorphCnstTest (ValidationQ v) f g = either (const f) (const g) (toEither v) == validationConst f g v
+    eCatamorphCnstTest :: Eq c => Either e a -> c -> c -> Bool
+    eCatamorphCnstTest e f g = validationConst f g (fromEither e) == either (const f) (const g) e
+    vvCatamorphCnstTest :: Eq c => ValidationQ e a -> c -> c -> Bool
+    vvCatamorphCnstTest (ValidationQ v) f g = validationConst f g v == validation (const f) (const g) v
+    partitionTest :: (Eq e, Eq a) => [ValidationQ e a] -> Bool
+    partitionTest vqs = let xs = [v | ValidationQ v <- vqs]
+        in partitionValidations xs == (failures xs, successes xs)
+
+-- | Test the label and labelV functions.
+testLabel :: [TestTree]
+testLabel =
+  [ QC.testProperty "(QC) Validator should yield relabeled ValidationRule error upon failure"
+      (labelTest :: Int -> Char -> String -> Bool)
+  , SC.testProperty "(SC) Validator should yield relabeled ValidationRule error upon failure"
+      (labelTest :: [Int] -> Bool -> Char -> Bool)
+  , QC.testProperty "(QC) Validator should yield relabeled Bool error upon failure"
+      (labelVTest :: Sum Int -> Maybe Char -> String -> Bool)
+  , SC.testProperty "(SC) Validator should yield relabeled Bool error upon failure"
+      (labelVTest :: [Int] -> Bool -> Char -> Bool)
+  ]
+  where
+    labelTest :: (Eq a, Eq e) => a -> e1 -> e -> Bool
+    labelTest inp err err' = (failureUnless (const False) err <?> err') `validatify` inp == Failure err'
+    labelVTest inp err err' = runValidator (validate (failureUnless (const False) err) <??> err') inp == Failure err'
+
+-- | Test validation errors being combined.
+testErrorPreservation :: [TestTree]
+testErrorPreservation =
+  [ SC.testProperty "(SC) All errors from all validators should be combined upon failure"
+      (helper :: (Bool, ()) -> NonEmpty [Bool] -> Bool)
+  ]
+  where
+    helper :: (Traversable t, Monoid e, Eq e, Eq (t inp)) => inp -> t e -> Bool
+    helper inp errs = runValidator
+        (traverse (\e -> validate $ label e $ failureUnless' (const False)) errs)
+        inp == Failure (fold errs)
+
+-- | Test the relation between NonEmpty and Unit combinators.
+testNEUnitRelation :: [TestTree]
+testNEUnitRelation =
+  [ QC.testProperty "(QC) failureIf p err = label (const (neSingleton err)) (failureIf' p)"
+      ((\predc inp err -> QC.classify (predc inp) "Significant"
+        $ QC.classify (not $ predc inp) "Trivial"
+        $ failureIf predc err `validatify` inp
+        == (failureIf' predc <?> neSingleton err) `validatify` inp
+       ) :: (Char -> Bool) -> Char -> String -> QC.Property
+      )
+  , SC.testProperty "(SC) failureIf p err = label (const (neSingleton err)) (failureIf' p)"
+      ((\predc inp err -> failureIf predc err `validatify` inp
+        == (failureIf' predc <?> neSingleton err) `validatify` inp
+       ) :: (Bool -> Bool) -> Bool -> Int -> Bool
+      )
+  , QC.testProperty "(QC) failureUnless p err = label (const (neSingleton err)) (failureUnless' p)"
+      ((\predc inp err -> QC.classify (not $ predc inp) "Significant"
+        $ QC.classify (predc inp) "Trivial"
+        $ failureUnless predc err `validatify` inp
+        == (failureUnless' predc <?> neSingleton err) `validatify` inp
+       ) :: (Char -> Bool) -> Char -> String -> QC.Property
+      )
+  , SC.testProperty "(SC) failureUnless p err = label (const (neSingleton err)) (failureUnless' p)"
+      ((\predc inp err -> failureUnless predc err `validatify` inp
+        == (failureUnless' predc <?> neSingleton err) `validatify` inp
+       ) :: (Bool -> Bool) -> Bool -> Int -> Bool
+      )
+  ]
+
+-- | Test the negateRule function.
+testNegateRule :: [TestTree]
+testNegateRule =
+  [ QC.testProperty "(QC) negateRule . negateRule = id (assuming errors are unchanged)"
+      (helper :: (String -> Bool, Int) -> String -> Bool)
+  , SC.testProperty "(SC) negateRule . negateRule = id (assuming errors are unchanged)"
+      (helper :: (Bool -> Bool, [Bool]) -> Bool -> Bool)
+  ]
+  where
+    helper :: (Eq a, Eq e) => (a -> Bool, e) -> a -> Bool
+    helper ~(predc, err) x = let rule = predToVRule err predc
+        in negateRule err (negateRule err rule) `validatify` x == rule `validatify` x
+        && negateRule' (negateRule' rule) `validatify` x == (rule <?> ()) `validatify` x
+
+-- | Test the relation between failureIf and failureUnless.
+testIfUnlessRelation :: [TestTree]
+testIfUnlessRelation =
+  [ QC.testProperty "(QC) failureIf' p = failureUnless' (not . p)"
+      (helper :: (String -> Bool) -> String -> Bool)
+  , SC.testProperty "(SC) failureIf' p = failureUnless' (not . p)"
+      (helper :: (Bool -> Bool) -> Bool -> Bool)
+  , QC.testProperty "(QC) failureIf p err = negateRule (neSingleton err) (failureUnless' p)"
+      (negateHelper :: (String -> Bool, String) -> String -> Bool)
+  , SC.testProperty "(SC) failureIf p err = negateRule (neSingleton err) (failureUnless' p)"
+      (negateHelper :: (Bool -> Bool, Char) -> Bool -> Bool)
+  , QC.testProperty "(QC) failureIf' p = negateRule' (failureUnless' p)"
+      (negateHelper' :: (String -> Bool) -> String -> Bool)
+  , SC.testProperty "(SC) failureIf' p = negateRule' (failureUnless' p)"
+      (negateHelper' :: (Bool -> Bool) -> Bool -> Bool)
+  ]
+  where
+    helper :: Eq a => (a -> Bool) -> a -> Bool
+    helper predc inp = failureIf' predc `validatify` inp
+        == failureUnless' (not . predc) `validatify` inp
+    negateHelper :: (Eq a, Eq e) => (a -> Bool, e) -> a -> Bool
+    negateHelper ~(predc, err) inp = failureIf predc err `validatify` inp
+        == negateRule (neSingleton err) (failureUnless' predc) `validatify` inp
+        && failureUnless predc err `validatify` inp
+        == negateRule (neSingleton err) (failureIf' predc) `validatify` inp
+    negateHelper' :: Eq a => (a -> Bool) -> a -> Bool
+    negateHelper' predc inp = failureIf' predc `validatify` inp
+        == negateRule' (failureUnless' predc) `validatify` inp
+        && failureUnless' predc `validatify` inp
+        == negateRule' (failureIf' predc) `validatify` inp
+
+-- | Test orElse properties.
+testOrElse :: [TestTree]
+testOrElse =
+  [ QC.testProperty "(QC) falseRule always fails"
+      (falseRuleTest :: String -> Bool)
+  , SC.testProperty "(SC) falseRule always fails"
+      (falseRuleTest :: Maybe Char -> Bool)
+  , QC.testProperty "(QC) Associativity: rule1 </> (rule2 </> rule3) = (rule1 </> rule2) </> rule3"
+      (asscTest :: (Char -> Bool, String) -> (Char -> Bool, String) -> (Char -> Bool, String) -> Char -> Bool)
+  , SC.testProperty "(SC) Associativity: rule1 </> (rule2 </> rule3) = (rule1 </> rule2) </> rule3"
+      (asscTest :: (Bool -> Bool, [()]) -> (Bool -> Bool, [()]) -> (Bool -> Bool, [()]) -> Bool -> Bool)
+  , QC.testProperty "(QC) Identity: rule </> falseRule = falseRule </> rule = rule"
+      (identTest :: (Int -> Bool, String) -> Int -> Bool)
+  , SC.testProperty "(SC) Identity: rule </> falseRule = falseRule </> rule = rule"
+      (identTest :: (Bool -> Bool, [Bool]) -> Bool -> Bool)
+  , QC.testProperty "(QC) Annihilator: rule </> mempty = mempty </> rule = mempty"
+      (annihilatorTest :: (Int -> Bool, String) -> Int -> Bool)
+  , SC.testProperty "(SC) Annihilator: rule </> mempty = mempty </> rule = mempty"
+      (annihilatorTest :: (Bool -> Bool, [Bool]) -> Bool -> Bool)
+  , QC.testProperty "(QC) Complement: rule </> (negateRule e rule) = (negateRule e rule) </> rule = Success"
+      (complmTest :: (Int -> Bool, String, String) -> Int -> Bool)
+  , SC.testProperty "(SC) Complement: rule </> (negateRule e rule) = (negateRule e rule) </> rule = Success"
+      (complmTest :: (Bool -> Bool, [Bool], [Bool]) -> Bool -> Bool)
+  ]
+  where
+    falseRuleTest :: Eq a => a -> Bool
+    falseRuleTest = (==Failure (mempty :: String)) . validatify falseRule
+    asscTest :: (Eq a, Eq e, Semigroup e) => (a -> Bool, e) -> (a -> Bool, e) -> (a -> Bool, e) -> a -> Bool
+    asscTest ~(f, err1) ~(g, err2) ~(h, err3) x =
+        let ~(rule1, rule2, rule3) = (predToVRule err1 f, predToVRule err2 g, predToVRule err3 h)
+        in  (rule1 </> (rule2 </> rule3)) `validatify` x == ((rule1 </> rule2) </> rule3) `validatify` x
+    identTest :: (Eq a, Eq e, Monoid e) => (a -> Bool, e) -> a -> Bool
+    identTest ~(f, err) x = let rule = predToVRule err f
+        in (rule </> falseRule) `validatify` x == rule `validatify` x
+        && (falseRule </> rule) `validatify` x == rule `validatify` x
+    annihilatorTest :: (Eq a, Eq e, Monoid e) => (a -> Bool, e) -> a -> Bool
+    annihilatorTest ~(f, err) x = let rule = predToVRule err f
+        in (rule </> mempty) `validatify` x == Success x
+        && (mempty </> rule) `validatify` x == Success x
+    complmTest :: (Eq a, Eq e, Semigroup e) => (a -> Bool, e, e) -> a -> Bool
+    complmTest ~(f, err, err') x =
+        let rule = predToVRule err f
+        in let complmRule = negateRule err' rule
+        in (rule </> complmRule) `validatify` x == Success x
+        && (complmRule </> rule) `validatify` x == Success x
+
+{-# ANN testAndAlso "HLint: ignore Monoid law, right identity" #-}
+{-# ANN testAndAlso "HLint: ignore Monoid law, left identity" #-}
+-- | Test andAlso properties.
+testAndAlso :: [TestTree]
+testAndAlso =
+  [ QC.testProperty "(QC) mempty always succeeds"
+      (memptyTest :: String -> Bool)
+  , SC.testProperty "(SC) mempty always succeeds"
+      (memptyTest :: Maybe Char -> Bool)
+  , QC.testProperty "(QC) Associativity: rule1 <> (rule2 <> rule3) = (rule1 <> rule2) <> rule3"
+      (asscTest :: (Char -> Bool, String) -> (Char -> Bool, String) -> (Char -> Bool, String) -> Char -> Bool)
+  , SC.testProperty "(SC) Associativity: rule1 <> (rule2 <> rule3) = (rule1 <> rule2) <> rule3"
+      (asscTest :: (Bool -> Bool, [()]) -> (Bool -> Bool, [()]) -> (Bool -> Bool, [()]) -> Bool -> Bool)
+  , QC.testProperty "(QC) Identity: rule <> mempty = mempty <> rule = rule"
+      (identTest :: (Int -> Bool, String) -> Int -> Bool)
+  , SC.testProperty "(SC) Identity: rule <> mempty = mempty <> rule = rule"
+      (identTest :: (Bool -> Bool, [Bool]) -> Bool -> Bool)
+  , QC.testProperty "(QC) Annihilator: rule <> falseRule = falseRule <> rule = falseRule"
+      (annihilatorTest :: (Int -> Bool, String) -> Int -> Bool)
+  , SC.testProperty "(SC) Annihilator: rule <> falseRule = falseRule <> rule = falseRule"
+      (annihilatorTest :: (Bool -> Bool, [Bool]) -> Bool -> Bool)
+  , QC.testProperty "(QC) Complement: rule <> (negateRule e rule) = (negateRule e rule) <> rule = Failure"
+      (complmTest :: (Int -> Bool, String, String) -> Int -> Bool)
+  , SC.testProperty "(SC) Complement: rule <> (negateRule e rule) = (negateRule e rule) <> rule = Failure"
+      (complmTest :: (Bool -> Bool, [Bool], [Bool]) -> Bool -> Bool)
+  , QC.testProperty "(QC) Idempotence: rule <> rule = rule"
+      (idempTest :: (Int -> Bool, String) -> Int -> Bool)
+  , SC.testProperty "(SC) Idempotence: rule <> rule = rule"
+      (idempTest :: (Bool -> Bool, [Bool]) -> Bool -> Bool)
+  ]
+  where
+    memptyTest :: Eq a => a -> Bool
+    memptyTest = (==) <$> Success `asTypeOf` const (Failure "") <*> validatify mempty
+    asscTest :: (Eq a, Eq e) => (a -> Bool, e) -> (a -> Bool, e) -> (a -> Bool, e) -> a -> Bool
+    asscTest ~(f, err1) (g, err2) (h, err3) x =
+        let ~(rule1, rule2, rule3) = (predToVRule err1 f, predToVRule err2 g, predToVRule err3 h)
+        in (rule1 <> (rule2 <> rule3)) `validatify` x == ((rule1 <> rule2) <> rule3) `validatify` x
+    identTest :: (Eq a, Eq e) => (a -> Bool, e) -> a -> Bool
+    identTest ~(f, err) x = let rule = predToVRule err f
+        in (rule <> mempty) `validatify` x == rule `validatify` x
+        && (mempty <> rule) `validatify` x == rule `validatify` x
+    annihilatorTest :: (Eq a, Eq e, Monoid e) => (a -> Bool, e) -> a -> Bool
+    annihilatorTest ~(f, err) x = let rule = predToVRule err f
+        in (rule <> falseRule) `validatify` x == Failure (if f x then mempty else err)
+        && (falseRule <> rule) `validatify` x == Failure mempty
+    complmTest :: (Eq a, Eq e) => (a -> Bool, e, e) -> a -> Bool
+    complmTest ~(f, err, err') x =
+        let rule = predToVRule err f
+        in let complmRule = negateRule err' rule
+        in (rule <> complmRule) `validatify` x == Failure (if f x then err' else err)
+        && (complmRule <> rule) `validatify` x == Failure (if f x then err' else err)
+    idempTest :: (Eq a, Eq e) => (a -> Bool, e) -> a -> Bool
+    idempTest ~(f, err) x = let rule = predToVRule err f
+        in (rule <> rule) `validatify` x == rule `validatify` x
+
+-- | Test the satisfyAny function.
+testSatisfyAny :: [TestTree]
+testSatisfyAny =
+  [ QC.testProperty "satisfyAny = foldl1 orElse"
+      (leftFold1Test :: NonEmptyLQ (Int -> Bool, Sum Int) -> Int -> Bool)
+  , SC.testProperty "satisfyAny = foldl1 orElse"
+      (leftFold1Test :: NonEmptyLQ (Bool -> Bool, Maybe [Bool]) -> Bool -> Bool)
+  , QC.testProperty "satisfyAny = foldr1 orElse"
+      (rightFold1Test :: NonEmptyLQ (Int -> Bool, Sum Int) -> Int -> Bool)
+  , SC.testProperty "satisfyAny = foldr1 orElse"
+      (rightFold1Test :: NonEmptyLQ (Bool -> Bool, Maybe [Bool]) -> Bool -> Bool)
+  , QC.testProperty "satisfyAny = foldl orElse falseRule"
+      (leftFoldTest :: NonEmptyLQ (Int -> Bool, Sum Int) -> Int -> Bool)
+  , SC.testProperty "satisfyAny = foldl orElse falseRule"
+      (leftFoldTest :: NonEmptyLQ (Bool -> Bool, Maybe [Bool]) -> Bool -> Bool)
+  , QC.testProperty "satisfyAny = foldr orElse falseRule"
+      (rightFoldTest :: NonEmptyLQ (Int -> Bool, Sum Int) -> Int -> Bool)
+  , SC.testProperty "satisfyAny = foldr orElse falseRule"
+      (rightFoldTest :: NonEmptyLQ (Bool -> Bool, Maybe [Bool]) -> Bool -> Bool)
+  ]
+  where
+    leftFold1Test :: (Eq a, Eq e, Semigroup e) => NonEmptyLQ (a -> Bool, e) -> a -> Bool
+    leftFold1Test predcs x = let rules = (\(f, err) -> predToVRule err f) <$> predcs
+        in satisfyAny rules `validatify` x
+        == foldl1 (</>) rules `validatify` x
+    rightFold1Test :: (Eq a, Eq e, Semigroup e) => NonEmptyLQ (a -> Bool, e) -> a -> Bool
+    rightFold1Test predcs x = let rules = (\(f, err) -> predToVRule err f) <$> predcs
+        in satisfyAny rules `validatify` x
+        == foldr1 (</>) rules `validatify` x
+    leftFoldTest :: (Eq a, Eq e, Monoid e) => NonEmptyLQ (a -> Bool, e) -> a -> Bool
+    leftFoldTest predcs x = let rules = (\(f, err) -> predToVRule err f) <$> predcs
+        in satisfyAny rules `validatify` x
+        == foldl (</>) falseRule rules `validatify` x
+    rightFoldTest :: (Eq a, Eq e, Monoid e) => NonEmptyLQ (a -> Bool, e) -> a -> Bool
+    rightFoldTest predcs x = let rules = (\(f, err) -> predToVRule err f) <$> predcs
+        in satisfyAny rules `validatify` x
+        == foldr (</>) falseRule rules `validatify` x
+
+{-# ANN testSatisfyAll "HLint: ignore Use mconcat" #-}
+-- | Test the satisfyAll function.
+testSatisfyAll :: [TestTree]
+testSatisfyAll =
+  [ QC.testProperty "satisfyAll = fold"
+      (foldTest :: NonEmptyLQ (Int -> Bool, Sum Int) -> Int -> Bool)
+  , SC.testProperty "satisfyAll = fold"
+      (foldTest :: NonEmptyLQ (Bool -> Bool, Maybe [Bool]) -> Bool -> Bool)
+  , QC.testProperty "satisfyAll = foldl1 andAlso"
+      (leftFold1Test :: NonEmptyLQ (Int -> Bool, Sum Int) -> Int -> Bool)
+  , SC.testProperty "satisfyAll = foldl1 andAlso"
+      (leftFold1Test :: NonEmptyLQ (Bool -> Bool, Maybe [Bool]) -> Bool -> Bool)
+  , QC.testProperty "satisfyAll = foldr1 andAlso"
+      (rightFold1Test :: NonEmptyLQ (Int -> Bool, Sum Int) -> Int -> Bool)
+  , SC.testProperty "satisfyAll = foldr1 andAlso"
+      (rightFold1Test :: NonEmptyLQ (Bool -> Bool, Maybe [Bool]) -> Bool -> Bool)
+  , QC.testProperty "satisfyAll = foldl andAlso mempty"
+      (leftFoldTest :: NonEmptyLQ (Int -> Bool, Sum Int) -> Int -> Bool)
+  , SC.testProperty "satisfyAll = foldl andAlso mempty"
+      (leftFoldTest :: NonEmptyLQ (Bool -> Bool, Maybe [Bool]) -> Bool -> Bool)
+  , QC.testProperty "satisfyAll = foldr andAlso mempty"
+      (rightFoldTest :: NonEmptyLQ (Int -> Bool, Sum Int) -> Int -> Bool)
+  , SC.testProperty "satisfyAll = foldr andAlso mempty"
+      (rightFoldTest :: NonEmptyLQ (Bool -> Bool, Maybe [Bool]) -> Bool -> Bool)
+  ]
+  where
+    foldTest :: (Eq a, Eq e) => NonEmptyLQ (a -> Bool, e) -> a -> Bool
+    foldTest predcs x = let rules = (\(f, err) -> predToVRule err f) <$> predcs
+        in satisfyAll rules `validatify` x
+        == fold rules `validatify` x
+    leftFold1Test :: (Eq a, Eq e) => NonEmptyLQ (a -> Bool, e) -> a -> Bool
+    leftFold1Test predcs x = let rules = (\(f, err) -> predToVRule err f) <$> predcs
+        in satisfyAll rules `validatify` x
+        == foldl1 (<>) rules `validatify` x
+    rightFold1Test :: (Eq a, Eq e) => NonEmptyLQ (a -> Bool, e) -> a -> Bool
+    rightFold1Test predcs x = let rules = (\(f, err) -> predToVRule err f) <$> predcs
+        in satisfyAll rules `validatify` x
+        == foldr1 (<>) rules `validatify` x
+    leftFoldTest :: (Eq a, Eq e) => NonEmptyLQ (a -> Bool, e) -> a -> Bool
+    leftFoldTest predcs x = let rules = (\(f, err) -> predToVRule err f) <$> predcs
+        in satisfyAll rules `validatify` x
+        == foldl (<>) mempty rules `validatify` x
+    rightFoldTest :: (Eq a, Eq e) => NonEmptyLQ (a -> Bool, e) -> a -> Bool
+    rightFoldTest predcs x = let rules = (\(f, err) -> predToVRule err f) <$> predcs
+        in satisfyAll rules `validatify` x
+        == foldr (<>) mempty rules `validatify` x
+
+-- | Test ValidationRule combinators.
+testCombs :: [TestTree]
+testCombs =
+  [ testGroup "Test primitive NonEmpty combinators" testPrimCombs
+  , testGroup "Test primitive Unit combinators" testPrimCombs'
+  , testGroup "Test primitive Unit combinators with labeled error" testPrimCombs''
+  , testGroup "Test relationship between primitive NonEmpty and Unit combinators" testNEUnitRelation
+  , testGroup "Test relationship between primitive 'if' and 'unless' combinators" testIfUnlessRelation
+  ]
+
+-- | Test functions that combine ValidationRules.
+testCombMix :: [TestTree]
+testCombMix =
+  [ testGroup "Test `orElse` function" testOrElse
+  , testGroup "Test `andAlso` function" testAndAlso
+  , testGroup "Test `satisfyAny` function" testSatisfyAny
+  , testGroup "Test `satisfyAll` function" testSatisfyAll
+  ]
+
+main :: IO ()
+main = defaultMain $ testGroup "Test suite"
+  [ testGroup "Test ValidationRule combinators" testCombs
+  , testGroup "Test negateRule function" testNegateRule
+  , testGroup "Test ValidationRule combining functions" testCombMix
+  , testGroup "Test validation of a collection of Validators" testValidatorCollc
+  , testGroup "Test Validation utilities" testValidationUtils
+  , testGroup "Test Validator relabeling" testLabel
+  , testGroup "Test Validator error grouping" testErrorPreservation
+  ]
+ test/Utils.hs view
@@ -0,0 +1,8 @@+module Utils
+    ( neSingleton
+    ) where
+
+import Data.List.NonEmpty (NonEmpty ((:|)))
+
+neSingleton :: a -> NonEmpty a
+neSingleton = (:|[])
+ valida.cabal view
@@ -0,0 +1,64 @@+cabal-version: 1.12
++-- This file has been generated from package.yaml by hpack version 0.34.4.+--+-- see: https://github.com/sol/hpack++name:           valida+version:        0.1.0+synopsis:       Simple applicative validation for product types, batteries included!+description:    Please see the README on GitHub at <https://github.com/TotallyNotChase/valida#readme>+category:       Validation, Data+homepage:       https://github.com/TotallyNotChase/valida#readme+bug-reports:    https://github.com/TotallyNotChase/valida/issues+author:         Chase+maintainer:     totallynotchase42@gmail.com+copyright:      TotallyNotChase+license:        MIT+license-file:   LICENSE+build-type:     Simple+extra-source-files:+    README.md+    ChangeLog.md++source-repository head+  type: git+  location: https://github.com/TotallyNotChase/valida++library+  exposed-modules:+      Valida+      Valida.Combinators+  other-modules:+      Valida.Utils+      Valida.Validation+      Valida.ValidationRule+      Valida.ValidationUtils+      Valida.Validator+      Paths_valida+  hs-source-dirs:+      src+  ghc-options: -Wall -Wcompat -Widentities -Wincomplete-uni-patterns -Wmissed-specialisations -Wmissing-export-lists -Wpartial-fields -Wredundant-constraints -O2+  build-depends:+      base >=4.13 && <5+  default-language: Haskell2010++test-suite valida-test+  type: exitcode-stdio-1.0+  main-is: Spec.hs+  other-modules:+      Gen+      Utils+      Paths_valida+  hs-source-dirs:+      test+  ghc-options: -Wall -Wcompat -Widentities -Wincomplete-uni-patterns -Wmissed-specialisations -Wmissing-export-lists -Wpartial-fields -Wredundant-constraints -threaded -rtsopts -with-rtsopts=-N+  build-depends:+      base >=4.13 && <5+    , smallcheck+    , tasty+    , tasty-hunit+    , tasty-quickcheck+    , tasty-smallcheck+    , valida+  default-language: Haskell2010