diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,20 @@
+Copyright (c) 2015 Alexander Thiemann
+
+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.
diff --git a/Setup.hs b/Setup.hs
new file mode 100644
--- /dev/null
+++ b/Setup.hs
@@ -0,0 +1,2 @@
+import Distribution.Simple
+main = defaultMain
diff --git a/src/Data/Validator.hs b/src/Data/Validator.hs
new file mode 100644
--- /dev/null
+++ b/src/Data/Validator.hs
@@ -0,0 +1,132 @@
+{-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE QuasiQuotes #-}
+module Data.Validator
+    ( -- * core monad and runners
+      ValidationM, ValidationT(..)
+    , runValidator, runValidatorT
+      -- * combinators
+    , (+>>)
+      -- * checks
+    , minLength, maxLength, lengthBetween, notEmpty
+    , largerThan, smallerThan, valueBetween
+    , matchesRegex
+    , conformsPred, conformsPredM
+      -- * helper classes and types
+    , HasLength(..), Stringable(..)
+    , Int64
+      -- * reexports
+    , re, mkRegexQQ, Regex
+    )
+where
+
+import Control.Applicative
+import Control.Monad
+import Control.Monad.Identity
+import Control.Monad.Trans
+import Control.Monad.Trans.Either
+import Data.Int
+import Data.Stringable hiding (length)
+import Text.Regex.PCRE.Heavy
+import qualified Data.Text as T
+import qualified Data.Text.Lazy as TL
+import qualified Data.ByteString as BS
+import qualified Data.ByteString.Lazy as BSL
+
+-- | The validation monad
+type ValidationM e = ValidationT e Identity
+
+-- | The validation monad transformer
+newtype ValidationT e m a
+    = ValidationT { unValidationT :: EitherT e m a }
+      deriving (Monad, Functor, Applicative, Alternative, MonadPlus, MonadTrans)
+
+-- | Run a validation on a type 'a'
+runValidator :: (a -> ValidationM e a) -> a -> Either e a
+runValidator a b = runIdentity $ runValidatorT a b
+
+-- | Run a validation on a type 'a'
+runValidatorT :: Monad m => (a -> ValidationT e m a) -> a -> m (Either e a)
+runValidatorT validationSteps input =
+    runEitherT $ unValidationT (validationSteps input)
+
+-- | All types that have a length, eg. 'String', '[a]', 'Vector a', etc.
+class HasLength a where
+    getLength :: a -> Int64
+
+instance HasLength [a] where
+    getLength = fromIntegral . length
+
+instance HasLength T.Text where
+    getLength = fromIntegral . T.length
+
+instance HasLength TL.Text where
+    getLength = TL.length
+
+instance HasLength BS.ByteString where
+    getLength = fromIntegral . BS.length
+
+instance HasLength BSL.ByteString where
+    getLength = BSL.length
+
+-- | Mark a custom check as failed
+checkFailed :: Monad m => e -> ValidationT e m a
+checkFailed = ValidationT . left
+{-# INLINE checkFailed #-}
+
+-- | Combine two checks
+(+>>) :: Monad m => (a -> ValidationT e m a) -> (a -> ValidationT e m a) -> a -> ValidationT e m a
+(+>>) m1 m2 a =
+    m1 a >>= m2
+{-# INLINE (+>>) #-}
+
+-- | Check that the value is at least N elements long
+minLength :: (Monad m, HasLength a) => Int64 -> e -> a -> ValidationT e m a
+minLength lowerBound e obj =
+    largerThan lowerBound e (getLength obj) >> return obj
+
+-- | Check that the value is at maxium N elements long
+maxLength :: (Monad m, HasLength a) => Int64 -> e -> a -> ValidationT e m a
+maxLength upperBound e obj =
+    smallerThan upperBound e (getLength obj) >> return obj
+
+-- | Check that the value's length is between N and M
+lengthBetween :: (Monad m, HasLength a) => Int64 -> Int64 -> e -> a -> ValidationT e m a
+lengthBetween lowerBound upperBound e obj =
+    valueBetween lowerBound upperBound e (getLength obj) >> return obj
+
+-- | Specialized minLength with N = 1
+notEmpty :: (Monad m, HasLength a) => e -> a -> ValidationT e m a
+notEmpty = minLength 1
+{-# INLINE notEmpty #-}
+
+-- | Check that a value is larger than N
+largerThan :: (Monad m, Ord a) => a -> e -> a -> ValidationT e m a
+largerThan lowerBound = conformsPred (>= lowerBound)
+
+-- | Check that a value is smaller than N
+smallerThan :: (Monad m, Ord a) => a -> e -> a -> ValidationT e m a
+smallerThan upperBound = conformsPred (<= upperBound)
+
+-- | Check that a value is between M and N
+valueBetween :: (Monad m, Ord a) => a -> a -> e -> a -> ValidationT e m a
+valueBetween lowerBound upperBound e obj =
+    (largerThan lowerBound e +>> smallerThan upperBound e) obj
+
+-- | Check that a value conforms a predicate
+conformsPred :: Monad m => (a -> Bool) -> e -> a -> ValidationT e m a
+conformsPred predicate e obj =
+    do unless (predicate obj) $ checkFailed e
+       return obj
+
+-- | Check that a value conforms a predicate
+conformsPredM :: Monad m => (a -> m Bool) -> e -> a -> ValidationT e m a
+conformsPredM predicate e obj =
+    do res <- lift $ predicate obj
+       unless res $ checkFailed e
+       return obj
+
+-- | Checks that a value matches a regular expression
+matchesRegex :: (Stringable a, Monad m) => Regex -> e -> a -> ValidationT e m a
+matchesRegex r = conformsPred (\obj -> obj =~ r)
diff --git a/validate-input.cabal b/validate-input.cabal
new file mode 100644
--- /dev/null
+++ b/validate-input.cabal
@@ -0,0 +1,34 @@
+name:                validate-input
+version:             0.1.0.0
+synopsis:            Input validation combinator library
+description:         A small Haskell combinator library that provides a simple way of validating user provided data structures.
+homepage:            https://github.com/agrafix/validate-input
+bug-reports:         https://github.com/agrafix/validate-input/issues
+license:             MIT
+license-file:        LICENSE
+author:              Alexander Thiemann <mail@athiemann.net>
+maintainer:          Alexander Thiemann <mail@athiemann.net>
+copyright:           (c) 2015 Alexander Thiemann
+category:            Web
+build-type:          Simple
+cabal-version:       >=1.10
+tested-with:
+    GHC == 7.8.3
+
+source-repository head
+  type: git
+  location: git://github.com/agrafix/validate-input.git
+
+library
+  exposed-modules:     Data.Validator
+  build-depends:
+                      base >=4.6 && <5,
+                      bytestring >=0.10,
+                      either >=4.3,
+                      mtl >=2.1,
+                      pcre-heavy >=0.2,
+                      stringable >=0.1,
+                      text >=1.2
+  hs-source-dirs:      src
+  default-language:    Haskell2010
+  ghc-options:         -auto-all -Wall -fno-warn-orphans
