@@ -0,0 +1,153 @@
2+module Common.Validation where
4+import Control.Lens hiding ((.=))
5+import Control.Monad.Base
6+import Control.Monad.Catch
7+import Control.Monad.Except
8+import Control.Monad.State.Strict
9+import Control.Monad.Trans.Lift.Local
11+import Data.Foldable as F
13+import Data.Map.Strict as M
16+import Data.Vector as V
17+import Test.QuickCheck
19+-- | Collects all throwed "warnings" throwed through StateT and "errors" throwed
20+-- through ExceptT to single value using Monoid
21+-- FIXME: give more instances like HReaderT and MonadBaseControl/MonadMask
22+newtype ValidationT e m a = ValidationT
23+ { unValidationT :: ExceptT e (StateT e m) a
24+ } deriving ( Functor, Applicative, Monad, MonadThrow, MonadCatch
27+instance MonadTrans (ValidationT e) where
28+ lift = ValidationT . lift . lift
30+instance LiftLocal (ValidationT e) where
31+ liftLocal _ l f = ValidationT . mapExceptT (mapStateT $ l f) . unValidationT
33+-- | Map with 'Monoid' instance which 'mappend' its values
34+newtype MonoidMap k v = MonoidMap (Map k v)
35+ deriving (Eq, Ord, Show, Arbitrary)
37+makePrisms ''MonoidMap
39+type instance IxValue (MonoidMap k v) = v
40+type instance Index (MonoidMap k v) = k
41+instance (Ord k) => Ixed (MonoidMap k v) where
42+ ix key = _MonoidMap . ix key
43+instance (Ord k) => At (MonoidMap k v) where
44+ at key = _MonoidMap . at key
46+instance (Ord k, Monoid v) => Monoid (MonoidMap k v) where
47+ mempty = MonoidMap M.empty
48+ mappend (MonoidMap a) (MonoidMap b) =
49+ MonoidMap $ M.unionWith (<>) a b
51+instance (ToJSON k, ToJSON v) => ToJSON (MonoidMap k v) where
52+ toJSON (MonoidMap m) = toJSON $ L.map toObj $ M.toList m
54+ toObj (k, v) = object
58+instance (Ord k, FromJSON k, FromJSON v) => FromJSON (MonoidMap k v) where
59+ parseJSON v = withArray "MonoidMap" go v
62+ keyvals <- traverse fromObj arr
63+ return $ MonoidMap $ M.fromList $ V.toList keyvals
64+ fromObj objV = flip (withObject "element of MonoidMap") objV $ \obj -> do
66+ val <- obj .: "value"
69+-- | Convenient for 'vZoom' as first artument. Will prevent generation
70+-- of map with 'mempty' values
71+mmSingleton :: (Eq v, Monoid v, Ord k) => k -> v -> MonoidMap k v
72+mmSingleton k = memptyWrap mempty $ MonoidMap . M.singleton k
74+-- | Set given value to 'mempty'
75+setMempty :: (Monoid s) => ASetter' s a -> a -> s
76+setMempty setter a = set setter a mempty
78+memptyWrap :: (Eq a, Monoid a) => b -> (a -> b) -> a -> b
83+-- | If given container is not 'mempty', then use given function to
84+-- append all its elements and return 'Just' result
86+ :: (Foldable f, Eq (f a), Monoid a, Monoid (f a))
90+neConcat f = memptyWrap Nothing (Just . F.foldl' f mempty)
92+textErrors :: [Text] -> Maybe Text
93+textErrors = neConcat (\a b -> a <> ", " <> b)
95+-- | Returns `mempty` instead of error if no warnings was occured. So, your
96+-- error should have `Eq` instance to detect that any error was occured. Returns
97+-- Nothing for second element of tuple if compuration was interruped by 'vError'
98+runValidationT :: (Monoid e, Monad m) => ValidationT e m a -> m (e, Maybe a)
99+runValidationT (ValidationT m) = do
100+ (res, warnings) <- runStateT (runExceptT m) mempty
101+ return $ case res of
102+ Left err -> (err <> warnings, Nothing)
103+ Right a -> (warnings, Just a)
106+ :: (Monoid e, Eq e, Monad m)
107+ => ValidationT e m a
109+runValidationTEither action = do
110+ (err, res) <- runValidationT action
111+ return $ case res of
112+ Just a | err == mempty -> Right a
116+ :: (Monoid e, Monad m, Eq e)
118+ -> ValidationT e m a
120+handleValidationT handler action = do
121+ runValidationTEither action >>= either handler return
123+-- | Stops further execution of validation
124+vError :: (Monad m) => e -> ValidationT e m a
125+vError e = ValidationT $ throwError e
127+-- | Does not stop further execution, append warning to
128+vWarning :: (Monad m, Monoid e) => e -> ValidationT e m ()
129+vWarning e = ValidationT $ modify' (<> e)
131+vErrorL :: (Monad m, Monoid e) => ASetter' e a -> a -> ValidationT e m x
132+vErrorL l a = vError $ setMempty l a
134+vWarningL :: (Monad m, Monoid e) => ASetter' e a -> a -> ValidationT e m ()
135+vWarningL l a = vWarning $ setMempty l a
138+ :: (Monad m, Monoid a, Monoid b)
140+ -> ValidationT a m x
141+ -> ValidationT b m x
143+ (err, res) <- lift $ runValidationT action
145+ Nothing -> vError $ up err
146+ Just a -> vWarning (up err) *> return a
149+ :: (Monad m, Monoid a, Monoid b)
151+ -> ValidationT a m x
152+ -> ValidationT b m x
153+vZoomL l action = vZoom (setMempty l) action