menshen 0.0.1 → 0.0.2
raw patch · 4 files changed
+98/−71 lines, 4 filesdep ~basePVP: major bump suggested
API removals or changes: PVP suggests a major version bump
Dependency ranges changed: base
API changes (from Hackage documentation)
- Data.Menshen: valify :: HasValid m => a -> Validator a -> m a
+ Data.Menshen: Invalid :: [ValidatorErr] -> VerifyResult a
+ Data.Menshen: Valid :: a -> VerifyResult a
+ Data.Menshen: ValidatorErr :: SomeException -> String -> String -> ValidatorErr
+ Data.Menshen: [exception] :: ValidatorErr -> SomeException
+ Data.Menshen: [field] :: ValidatorErr -> String
+ Data.Menshen: [message] :: ValidatorErr -> String
+ Data.Menshen: data ValidatorErr
+ Data.Menshen: data VerifyResult a
+ Data.Menshen: instance Data.Menshen.HasValid Data.Menshen.VerifyResult
+ Data.Menshen: instance GHC.Base.Applicative Data.Menshen.VerifyResult
+ Data.Menshen: instance GHC.Base.Functor Data.Menshen.VerifyResult
+ Data.Menshen: instance GHC.Base.Monad Data.Menshen.VerifyResult
+ Data.Menshen: instance GHC.Exception.Type.Exception Data.Menshen.ValidationException
+ Data.Menshen: instance GHC.Show.Show Data.Menshen.ValidatorErr
+ Data.Menshen: instance GHC.Show.Show a => GHC.Show.Show (Data.Menshen.VerifyResult a)
+ Data.Menshen: mark :: HasValid m => String -> m a -> m a
+ Data.Menshen: toErr :: HasI18n e => String -> e -> ValidatorErr
+ Data.Menshen: vcvt :: Validator' a -> Validator a
+ Data.Menshen: verify :: HasValid m => a -> Validator a -> m a
- Data.Menshen: (?) :: () => a -> (a -> b) -> b
+ Data.Menshen: (?) :: HasValid m => m a -> Validator a -> m a
- Data.Menshen: class HasI18n a
+ Data.Menshen: class Exception e => HasI18n e
- Data.Menshen: toI18n :: HasI18n a => a -> String
+ Data.Menshen: toI18n :: HasI18n e => e -> String
Files
- README.md +6/−8
- menshen.cabal +2/−2
- src/Data/Menshen.hs +81/−51
- test/Spec.hs +9/−10
README.md view
@@ -1,6 +1,6 @@ # menshen -[](https://hackage.haskell.org/package/menshen)+[](https://hackage.haskell.org/package/menshen) [](https://travis-ci.org/leptonyu/menshen) @@ -14,15 +14,13 @@ , age :: Int } deriving Show -valifyBody :: Validator Body-valifyBody = \ma -> do- Body{..} <- ma- Body- <$> name ?: pattern "^[a-z]{3,6}$"- <*> age ?: minInt 1 . maxInt 150+verifyBody :: Validator Body+verifyBody = vcvt $ Body{..} -> Body+ <$> name ?: mark "name" . pattern "^[a-z]{3,6}$"+ <*> age ?: mark "age" . minInt 1 . maxInt 150 makeBody :: String -> Int -> Either String Body-makeBody name age = Body{..} ?: valifyBody+makeBody name age = Body{..} ?: verifyBody main = do print $ makeBody "daniel" 15
menshen.cabal view
@@ -4,10 +4,10 @@ -- -- see: https://github.com/sol/hpack ----- hash: 8899e367732559bb8cbe2fc5e109b65a3d8d75f7f3abb404caffd690de0a2083+-- hash: 1289b725fb06666dc66eea5b71c5a5f42f91610dfb799c1a0752f23bf086d570 name: menshen-version: 0.0.1+version: 0.0.2 synopsis: Data Validation description: Data Validation inspired by JSR305 category: Web
src/Data/Menshen.hs view
@@ -1,8 +1,10 @@ {-# LANGUAGE CPP #-}+{-# LANGUAGE DeriveFunctor #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE RankNTypes #-}+{-# LANGUAGE RecordWildCards #-} {-# LANGUAGE TypeOperators #-} {-# LANGUAGE TypeSynonymInstances #-} -- |@@ -24,6 +26,8 @@ , Validator , ValidationException(..) , HasI18n(..)+ , ValidatorErr(..)+ , VerifyResult(..) -- * Validation Functions , HasValidSize(..) , notNull@@ -42,19 +46,21 @@ , email -- * Validation Operations , (?)- , valify+ , verify , (?:)+ , vcvt -- * Reexport Functions , (=~) ) where +import Control.Exception (Exception (..), SomeException) import Data.Scientific-import qualified Data.Text as TS-import qualified Data.Text.Lazy as TL+import qualified Data.Text as TS+import qualified Data.Text.Lazy as TL import Data.Word import Text.Regex.TDFA #if __GLASGOW_HASKELL__ > 708-import Data.Function ((&))+import Data.Function ((&)) #else infixl 1 & (&) :: a -> (a -> b) -> b@@ -63,17 +69,23 @@ -- | apply record validation to the value infixl 5 ?+(?) :: HasValid m => m a -> Validator a -> m a (?) = (&) -- | lift value a to validation context and check if it is valid. -- infixl 5 ?: (?:) :: HasValid m => a -> Validator a -> m a-(?:) = valify+(?:) = verify -- | Plan for i18n translate, now just for english.-class HasI18n a where- toI18n :: a -> String+class Exception e => HasI18n e where+ toI18n :: e -> String+ toErr :: String -> e -> ValidatorErr+ toErr field e =+ let message = toI18n e+ exception = toException e+ in ValidatorErr{..} -- | Validation Error Message data ValidationException@@ -101,6 +113,8 @@ | InvalidPattern String deriving Show +instance Exception ValidationException+ instance HasI18n ValidationException where toI18n ShouldBeTrue = "must be true" toI18n ShouldBeFalse = "must be false"@@ -125,38 +139,70 @@ toI18n (InvalidDigits i f) = "numeric value out of bounds (<" ++ show i ++ " digits>.<" ++ show f ++ " digits> expected)" toI18n (InvalidPattern r) = "must match " ++ r ++data ValidatorErr = ValidatorErr+ { exception :: SomeException+ , message :: String+ , field :: String+ } deriving Show+ -- | Define how invalid infomation passed to upper layer. class Monad m => HasValid m where invalid :: HasI18n a => a -> m b invalid = error . toI18n+ mark :: String -> m a -> m a+ mark _ = id instance HasValid (Either String) where invalid = Left . toI18n +data VerifyResult a = Invalid [ValidatorErr] | Valid a deriving (Show, Functor)++instance Applicative VerifyResult where+ pure = Valid+ (Invalid a) <*> (Invalid b) = Invalid (a ++ b)+ (Invalid a) <*> _ = Invalid a+ _ <*> (Invalid b) = Invalid b+ (Valid f) <*> (Valid b) = Valid (f b)++instance Monad VerifyResult where+ return = Valid+ (Valid a) >>= f = f a+ (Invalid a) >>= _ = Invalid a++instance HasValid VerifyResult where+ invalid e = Invalid [toErr "" e]+ mark name ma =+ let go err = if null (field err) then err { field = name } else err { field = field err ++ "." ++ name }+ in case ma of+ (Invalid x) -> Invalid $ go <$> x+ v -> v+ -- | Validator, use to define detailed validation check.-type Validator a = forall m. HasValid m => m a -> m a+type Validator a = forall m. HasValid m => m a -> m a+type Validator' a = forall m. HasValid m => a -> m a +vcvt :: Validator' a -> Validator a+vcvt f = (>>= f)+ -- | Length checker bundle class HasValidSize a where -- | Size validation size :: (Word64, Word64) -> Validator a- size (x,y) = \ma -> do- a <- ma+ size (x,y) = vcvt $ \a -> do let la = getLength a if la < x || la > y then invalid $ InvalidSize x y else return a -- | Assert not empty notEmpty :: Validator a- notEmpty = \ma -> do- a <- ma+ notEmpty = vcvt $ \a -> do if getLength a == 0 then invalid InvalidNotEmpty else return a -- | Assert not blank notBlank :: Validator a- notBlank = \ma -> do- a <- ma+ notBlank = vcvt $ \a -> do if getLength a == 0 then invalid InvalidNotBlank else return a@@ -175,8 +221,7 @@ -- | Regular expression validation pattern :: RegexLike Regex a => String -> Validator a-pattern p = \ma -> do- a <- ma+pattern p = vcvt $ \a -> do if a =~ p then return a else invalid $ InvalidPattern p @@ -185,108 +230,95 @@ -- | Email validation email :: RegexLike Regex a => Validator a-email = \ma -> do- a <- ma+email = vcvt $ \a -> do if a =~ emailPattern then return a else invalid InvalidEmail -- | Positive validation positive :: (Eq a, Num a) => Validator a-positive = \ma -> do- a <- ma+positive = vcvt $ \a -> do if a /= 0 && abs a - a == 0 then return a else invalid InvalidPositive -- | Positive or zero validation positiveOrZero :: (Eq a, Num a) => Validator a-positiveOrZero = \ma -> do- a <- ma+positiveOrZero = vcvt $ \a -> do if abs a - a == 0 then return a else invalid InvalidPositiveOrZero -- | Negative validation negative :: (Eq a, Num a) => Validator a-negative = \ma -> do- a <- ma+negative = vcvt $ \a -> do if a /= 0 && abs a + a == 0 then return a else invalid InvalidNegative -- | Negative or zero validation negativeOrZero :: (Eq a, Num a) => Validator a-negativeOrZero = \ma -> do- a <- ma+negativeOrZero = vcvt $ \a -> do if abs a + a == 0 then return a else invalid InvalidNegativeOrZero -- | Assert true assertTrue :: Validator Bool-assertTrue = \ma -> do- a <- ma+assertTrue = vcvt $ \a -> do if a then return a else invalid ShouldBeTrue -- | Assert false assertFalse :: Validator Bool-assertFalse = \ma -> do- a <- ma+assertFalse = vcvt $ \a -> do if not a then return a else invalid ShouldBeFalse -- | Assert not null notNull :: Validator (Maybe a)-notNull = \ma -> do- a <- ma+notNull = vcvt $ \a -> do case a of Just _ -> return a _ -> invalid ShouldNotNull -- | Assert null assertNull :: Validator (Maybe a)-assertNull = \ma -> do- a <- ma+assertNull = vcvt $ \a -> do case a of Just _ -> invalid ShouldNull _ -> return a -- | Maximum int validation maxInt :: Integral a => a -> Validator a-maxInt m = \ma -> do- a <- ma+maxInt m = vcvt $ \a -> do if a > m then invalid (InvalidMax $ toInteger m) else return a -- | Minimum int validation minInt :: Integral a => a -> Validator a-minInt m = \ma -> do- a <- ma+minInt m = vcvt $ \a -> do if a < m then invalid (InvalidMin $ toInteger m) else return a -- | Maximum decimal validation maxDecimal :: RealFloat a => a -> Validator a-maxDecimal m = \ma -> do- a <- ma+maxDecimal m = vcvt $ \a -> do if a > m then invalid (InvalidDecimalMax $ fromFloatDigits m) else return a -- | Minimum int validation minDecimal :: RealFloat a => a -> Validator a-minDecimal m = \ma -> do- a <- ma+minDecimal m = vcvt $ \a -> do if a < m then invalid (InvalidDecimalMin $ fromFloatDigits m) else return a -- | lift value a to validation context and check if it is valid.-valify :: HasValid m => a -> Validator a -> m a-valify a f = return a ? f+verify :: HasValid m => a -> Validator a -> m a+verify a f = f (return a) -- $use --@@ -302,15 +334,13 @@ -- > , age :: Int -- > } deriving Show -- >--- > valifyBody :: Validator Body--- > valifyBody = \ma -> do--- > Body{..} <- ma--- > Body--- > <$> name ?: pattern "^[a-z]{3,6}$"--- > <*> age ?: minInt 1 . maxInt 150+-- > verifyBody :: Validator Body+-- > verifyBody = vcvt $ \Body{..} -> Body+-- > <$> name ?: mark "name" . pattern "^[a-z]{3,6}$"+-- > <*> age ?: mark "age" . minInt 1 . maxInt 150 -- > -- > makeBody :: String -> Int -> Either String Body--- > makeBody name age = Body{..} ?: valifyBody+-- > makeBody name age = Body{..} ?: verifyBody -- > -- > main = do -- > print $ makeBody "daniel" 15
test/Spec.hs view
@@ -1,4 +1,5 @@ {-# LANGUAGE CPP #-}+{-# LANGUAGE NegativeLiterals #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RecordWildCards #-} module Main where@@ -50,18 +51,16 @@ instance FromJSON Body where parseJSON = withObject "Body" $ \v -> Body- <$> v .: "name" ? pattern "^[a-z]{3,6}$"- <*> v .: "age" ? minInt 1 . maxInt 150+ <$> v .: "name" ? mark "name" . pattern "^[a-z]{3,6}$"+ <*> v .: "age" ? mark "age" . minInt 1 . maxInt 150 -valifyBody :: Validator Body-valifyBody = \ma -> do- Body{..} <- ma- Body- <$> name ?: pattern "^[a-z]{3,6}$"- <*> age ?: minInt 1 . maxInt 150+verifyBody :: Validator Body+verifyBody = vcvt $ \Body{..} -> Body+ <$> name ?: mark "name" . pattern "^[a-z]{3,6}$"+ <*> age ?: mark "age" . minInt 1 . maxInt 150 makeBody :: String -> Int -> Either String Body-makeBody name age = Body{..} ?: valifyBody+makeBody name age = Body{..} ?: verifyBody specProperty = do context "bool" $ do@@ -111,7 +110,7 @@ it "notNull" $ do (notNullValue ? notNull) `shouldBe` notNullValue (notNullValue ? assertNull) `shouldSatisfy` isLeft- context "valify" $ do+ context "verify" $ do it "makeBody" $ do makeBody "daniel" 5 `shouldSatisfy` isRight makeBody "daniel" 200 `shouldSatisfy` isLeft