2{-# LANGUAGE GeneralizedNewtypeDeriving #-}
3{-# LANGUAGE OverloadedStrings #-}
4{-# LANGUAGE TemplateHaskell #-}
5{-# LANGUAGE TypeFamilies #-}
6{-# LANGUAGE UndecidableInstances #-}
8module Control.Monad.Validation
11 , runValidationTEither
28import Control.Applicative
29import Control.Lens hiding ((.=))
30import Control.Monad.Base
31import Control.Monad.Catch
32import Control.Monad.Except
33import Control.Monad.Fail
34import Control.Monad.State.Strict
35import Control.Monad.Trans.Control
37import Data.Foldable as F
40import Data.Map.Strict as M
43import Data.Vector as V
47-- | Collects all thrown warnings in 'StateT' and errors
48-- in 'ExceptT' into a single value using 'Monoid'.
49newtype ValidationT e m a = ValidationT
50 { unValidationT :: ExceptT e (StateT e m) a
51 } deriving ( Functor, Applicative, Monad, MonadThrow, MonadCatch
52 , MonadBase b, Alternative, MonadFix, MonadFail, Contravariant
53 , MonadIO, MonadPlus, MonadBaseControl b, MonadMask )
55instance MonadTrans (ValidationT e) where
56 lift = ValidationT . lift . lift
58-- | Map with 'Monoid' instance which 'mappend' its values
60-- This can be used as the `e` in `ValidationT e m a` to provide different
61-- sets of errors and warnings for different keys.
65-- [ MonoidMap $ M.fromList [(1, "foo"), (2, "hello, "), (3, "oh no")]
66-- , MonoidMap $ M.fromList [(1, "bar"), (2, "world")]
69-- MonoidMap (fromList [(1,"foobar"),(2,"hello, world"),(3,"oh no")])
70newtype MonoidMap k v = MonoidMap (Map k v)
71 deriving (Eq, Ord, Show, Arbitrary, Foldable)
75type instance IxValue (MonoidMap k v) = v
76type instance Index (MonoidMap k v) = k
77instance (Ord k) => Ixed (MonoidMap k v) where
78 ix key = _MonoidMap . ix key
79instance (Ord k) => At (MonoidMap k v) where
80 at key = _MonoidMap . at key
82#if MIN_VERSION_base(4,11,0)
83instance (Ord k, Semigroup v) => Semigroup (MonoidMap k v) where
87instance (Ord k, Monoid v) => Monoid (MonoidMap k v) where
88 mempty = MonoidMap M.empty
91instance (ToJSON k, ToJSON v) => ToJSON (MonoidMap k v) where
92 toJSON (MonoidMap m) = toJSON $ L.map toObj $ M.toList m
98instance (Ord k, FromJSON k, FromJSON v) => FromJSON (MonoidMap k v) where
99 parseJSON = withArray "MonoidMap" go
102 keyvals <- traverse fromObj arr
103 return $ MonoidMap $ M.fromList $ V.toList keyvals
104 fromObj objV = flip (withObject "element of MonoidMap") objV $ \obj -> do
106 val <- obj .: "value"
109#if MIN_VERSION_base(4,11,0)
110mmAppend :: (Ord k, Semigroup v) => MonoidMap k v -> MonoidMap k v -> MonoidMap k v
112mmAppend :: (Ord k, Monoid v) => MonoidMap k v -> MonoidMap k v -> MonoidMap k v
114mmAppend (MonoidMap a) (MonoidMap b) = MonoidMap $ M.unionWith (<>) a b
116-- | Convenient for 'vZoom' as first argument. Will prevent generation
117-- of map with 'mempty' values.
118mmSingleton :: (Eq v, Monoid v, Ord k) => k -> v -> MonoidMap k v
120 | v == mempty = mempty
121 | otherwise = MonoidMap . M.singleton k $ v
123-- | Sets given value to 'mempty'.
124setMempty :: (Monoid s) => ASetter' s a -> a -> s
125setMempty setter a = set setter a mempty
127neConcat :: Foldable f => (a -> a -> a) -> f a -> Maybe a
130 | otherwise = Just $ F.foldr1 f a
132-- | Returns the strings, concatanated with @", "@ if the list is not empty.
134-- Returns Nothing if the list is empty
136-- >>> textErrors ["foo", "bar"]
139-- >>> textErrors ["foo"]
144textErrors :: [Text] -> Maybe Text
145textErrors = neConcat (\a b -> a <> ", " <> b)
147-- | Returns 'mempty' instead of error if no warnings have occurred.
148-- Returns 'Nothing' as the second element of tuple if computation was
149-- interrupted by 'vError'.
151-- Returns all concatenated errors and warnings and the result if no
152-- errors have occurred (warnings could have occurred).
155-- runValidationT $ do
156-- vWarning ["warning1"]
158-- vWarning ["warning2"]
161-- (["error","warning1"],Nothing)
164-- runValidationT $ do
165-- vWarning ["warning1"]
166-- vWarning ["warning2"]
169-- (["warning1","warning2"],Just 8)
170runValidationT :: (Monoid e, Monad m) => ValidationT e m a -> m (e, Maybe a)
171runValidationT (ValidationT m) = do
172 (res, warnings) <- runStateT (runExceptT m) mempty
174 Left err -> (err <> warnings, Nothing)
175 Right a -> (warnings, Just a)
177-- | Like 'runValidationT' but doesn't return the result
178-- if any warning has occurred.
181-- runValidationTEither $ do
182-- vWarning ["warning1"]
184-- vWarning ["warning2"]
187-- Left ["error","warning1"]
190-- runValidationTEither $ do
191-- vWarning ["warning1"]
192-- vWarning ["warning2"]
195-- Left ["warning1","warning2"]
197 :: (Monoid e, Eq e, Monad m)
200runValidationTEither action = do
201 (err, res) <- runValidationT action
203 Just a | err == mempty -> Right a
206-- | Like 'runValidationTEither', but takes an error handler instead of
207-- returning errors and warnings.
210-- handleValidationT (\_ -> return 11) $ do
211-- vWarning ["warning1"]
213-- vWarning ["warning2"]
219-- handleValidationT (\_ -> return 11) $ do
220-- vWarning ["warning1"]
221-- vWarning ["warning2"]
226 :: (Monoid e, Monad m, Eq e)
230handleValidationT handler action =
231 runValidationTEither action >>= either handler return
233-- | Stops further execution and appends the given error.
234vError :: (Monad m) => e -> ValidationT e m a
235vError e = ValidationT $ throwError e
237-- | Does not stop further execution and appends the given warning.
238vWarning :: (Monad m, Monoid e) => e -> ValidationT e m ()
239vWarning e = ValidationT $ modify' (<> e)
241-- | Like 'vError' but allows you to use a setter to insert an error somewhere
242-- deeper into an empty ('mempty') "e" from "ValidationT e m x", which is then
243-- combined with all gathered warnings.
244vErrorL :: (Monad m, Monoid e) => ASetter' e a -> a -> ValidationT e m x
245vErrorL l a = vError $ setMempty l a
247-- | Like 'vWarning' but allows you to use a setter to insert an error somewhere
248-- deeper into an empty ('mempty') "e" from "ValidationT e m x", which is then
249-- combined with all gathered warnings.
250vWarningL :: (Monad m, Monoid e) => ASetter' e a -> a -> ValidationT e m ()
251vWarningL l a = vWarning $ setMempty l a
253-- | Allows you apply a transformation to the "e" in "ValidationT e m x".
256--runValidationT . vZoom (Data.Map.singleton "password errors") $ do
257-- vWarning ["warning1"]
259-- vWarning ["warning2"]
262-- (fromList [("password errors",["error","warning1"])],Nothing)
265-- runValidationT . vZoom (Data.Map.singleton "password errors") $ do
266-- vWarning ["warning1"]
267-- vWarning ["warning2"]
270-- (fromList [("password errors",["warning1","warning2"])],Just 8)
272 :: (Monad m, Monoid a, Monoid b)
277 (err, res) <- lift $ runValidationT action
279 Nothing -> vError $ up err
280 Just a -> vWarning (up err) $> a
282-- | Like 'vZoom' but takes a setter instead of a function.
284 :: (Monad m, Monoid a, Monoid b)
288vZoomL l = vZoom (setMempty l)
290-- | Turn any warnings the have occurred into errors.
292 :: (Monad m, Monoid a, Eq a)
296 err <- lift $ runValidationTEither m
297 either vError return err
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list