packages feed

darcsden-1.1: src/DarcsDen/Validate.hs

module DarcsDen.Validate where

import Control.Monad.Trans
import Data.Char (isNumber)
import Data.Either (lefts)
import Snap.Core
import qualified Data.Map as M

import DarcsDen.State.Session
import DarcsDen.Util (fromBS, toBS)


data Valid = Predicate String (String -> Bool) String
           | PredicateOp String String (String -> String -> Bool) String
           | Not Valid
           | Or Valid Valid
           | And Valid Valid
           | If Valid (OK -> Valid)
           | IOPred String (IO Bool)

data Invalid = Invalid [Valid]
               deriving Show
data OK = OK (M.Map String String)
          deriving Show

type Result = Either Invalid OK

instance Show Valid where
    show = explain

-- Explain a validation
explain :: Valid -> String
explain (Predicate a _ e) = a ++ " must " ++ e
explain (PredicateOp a b _ e) = a ++ " and " ++ b ++ " must " ++ e
explain (Not v) = "not: " ++ explain v
explain (Or v x) = explain v ++ " or " ++ explain x
explain (And v x) = explain v ++ " and " ++ explain x
explain (If v _) = "if (" ++ explain v ++ ") (...)"
explain (IOPred e _) = e

-- Helpers
ok :: [(String, String)] -> Result
ok = Right . OK . M.fromList

invalid :: [Valid] -> Result
invalid = Left . Invalid

param :: String -> Snap (Maybe String)
param a = do
    p <- getParam (toBS a)
    return (fmap fromBS p)

-- Verify a validation
verify :: Valid -> Snap Result
verify v@(Predicate a p _) = do
    mp <- param a
    case mp of
        Just x | p x -> return $ ok [(a, x)]
        _ -> return $ invalid [v]
verify v@(PredicateOp a b p _) = do
    ma <- param a
    mb <- param b
    case (ma, mb) of
        (Just x, Just y) | p x y -> return $ ok [(a, x), (b, y)]
        _ -> return $ invalid [v]
verify v@(Not t) = do
    r <- verify t
    return (either (const (ok [])) (const (invalid [v])) r)
verify (Or a b) = do
    x <- verify a
    case x of
        Left _ -> verify b
        Right _ -> return x
verify (And a b) = do
    x <- verify a
    y <- verify b
    case [x, y] of
        [Right (OK ra), Right (OK rb)] ->
            return $ Right $ OK (ra `M.union` rb)
        other ->
            return $ invalid (concatMap (\(Invalid i) -> i) $ lefts other)
verify (If a b) = do
    x <- verify a
    case x of
        Right o@(OK r) -> do
            t <- verify (b o)
            case t of
                Left _ -> return t
                Right (OK ts) -> return $ Right $ OK (r `M.union` ts)
        _ -> return x
verify v@(IOPred _ p) = do
    r <- liftIO p
    if r
        then return $ ok []
        else return $ invalid [v]

-- Check a bunch of validations
check :: [Valid] -> Snap Result
check = check' (ok [])
    where
      check' acc [] = return acc
      check' (Left (Invalid is)) (t:ts)
          = do v <- verify t
               case v of
                 Left (Invalid i) -> check' (invalid (is ++ i)) ts
                 _ -> check' (invalid is) ts
      check' (Right (OK vs)) (t:ts)
          = do v <- verify t
               case v of
                 Right (OK r) -> check' (Right (OK (vs `M.union` r))) ts
                 _ -> check' v ts

-- Validators
nonEmpty :: String -> Valid
nonEmpty a = Predicate a (/= "") "not be empty"

isEmpty :: String -> Valid
isEmpty a = Predicate a (== "") "be empty"

numeric :: String -> Valid
numeric a = nonEmpty a `And` Predicate a (all isNumber) "be numeric"

equal :: String -> String -> Valid
equal a b = PredicateOp a b (==) "be the same"

validate :: [Valid] -> (OK -> Snap a) -> (Invalid -> Snap a) -> Snap a
validate ts p f = check ts >>= either f p

predicate :: String -> (String -> Bool) -> String -> Valid
predicate = Predicate

iff :: Valid -> (OK -> Valid) -> Valid
iff = If

onlyIf :: Valid -> Valid -> Valid
onlyIf p v = Or (Not p) v

io :: String -> IO Bool -> Valid
io = IOPred

-- Notifications
notify :: MonadIO m => (String -> Notification) -> Session -> [Valid] -> m Session
notify n s vs = do
    mapM_ (flip notice s) ns
    return (s { sNotifications = sNotifications s ++ ns })
  where
    ns = map (n . explain) vs