Adds exceptT based handler validation

master
Abhinav Sarkar 2017-09-30 17:56:53 +05:30
parent 9be24a1091
commit dbf198fe6b
2 changed files with 89 additions and 54 deletions

View File

@ -3,83 +3,86 @@ module SimpleService.Handler where
import Prelude import Prelude
import Control.Monad.Aff.Class (liftAff) import Control.Monad.Aff.Class (liftAff)
import Data.Either (Either(..)) import Control.Monad.Trans.Class (lift)
import Data.Bifunctor (lmap)
import Data.Either (Either)
import Data.Foldable (intercalate) import Data.Foldable (intercalate)
import Data.Foreign (renderForeignError) import Data.Foreign (ForeignError, renderForeignError)
import Data.Foreign.Class (encode) import Data.Foreign.Class (encode)
import Data.Foreign.NullOrUndefined (unNullOrUndefined) import Data.Foreign.NullOrUndefined (unNullOrUndefined)
import Data.Int (fromString) import Data.Int (fromString)
import Data.List.NonEmpty (toList)
import Data.List.Types (NonEmptyList)
import Data.Maybe (Maybe(..)) import Data.Maybe (Maybe(..))
import Data.Tuple (Tuple(..))
import Database.PostgreSQL as PG import Database.PostgreSQL as PG
import Node.Express.Handler (Handler) import Node.Express.Handler (Handler)
import Node.Express.Request (getBody, getRouteParam) import Node.Express.Request (getBody, getRouteParam)
import Node.Express.Response (end, sendJson, setStatus) import Node.Express.Response (end, sendJson, setStatus)
import SimpleService.Persistence as P import SimpleService.Persistence as P
import SimpleService.Validation as V
import SimpleService.Types import SimpleService.Types
getUser :: forall eff. PG.Pool -> Handler (postgreSQL :: PG.POSTGRESQL | eff) getUser :: forall eff. PG.Pool -> Handler (postgreSQL :: PG.POSTGRESQL | eff)
getUser pool = getRouteParam "id" >>= case _ of getUser pool = V.withValidation getUserId \userId ->
Nothing -> respond 422 { error: "User ID is required" } liftAff (PG.withConnection pool $ flip P.findUser userId) >>= case _ of
Just sUserId -> case fromString sUserId of Nothing -> respond 404 { error: "User not found with id: " <> show userId }
Nothing -> respond 422 { error: "User ID must be an integer: " <> sUserId } Just user -> respond 200 (encode user)
Just userId -> liftAff (PG.withConnection pool $ flip P.findUser userId) >>= case _ of
Nothing -> respond 404 { error: "User not found with id: " <> sUserId }
Just user -> respond 200 (encode user)
deleteUser :: forall eff. PG.Pool -> Handler (postgreSQL :: PG.POSTGRESQL | eff) deleteUser :: forall eff. PG.Pool -> Handler (postgreSQL :: PG.POSTGRESQL | eff)
deleteUser pool = getRouteParam "id" >>= case _ of deleteUser pool = V.withValidation getUserId \userId -> do
Nothing -> respond 422 { error: "User ID is required" } found <- liftAff $ PG.withConnection pool \conn -> PG.withTransaction conn do
Just sUserId -> case fromString sUserId of P.findUser conn userId >>= case _ of
Nothing -> respond 422 { error: "User ID must be an integer: " <> sUserId } Nothing -> pure false
Just userId -> do Just _ -> do
found <- liftAff $ PG.withConnection pool \conn -> PG.withTransaction conn do P.deleteUser conn userId
P.findUser conn userId >>= case _ of pure true
Nothing -> pure false if found
Just _ -> do then respondNoContent 204
P.deleteUser conn userId else respond 404 { error: "User not found with id: " <> show userId }
pure true
if found
then respondNoContent 204
else respond 404 { error: "User not found with id: " <> sUserId }
createUser :: forall eff. PG.Pool -> Handler (postgreSQL :: PG.POSTGRESQL | eff) createUser :: forall eff. PG.Pool -> Handler (postgreSQL :: PG.POSTGRESQL | eff)
createUser pool = getBody >>= case _ of createUser pool = V.withValidation getUser \user@(User _) -> do
Left errs -> respond 422 { error: intercalate ", " $ map renderForeignError errs} liftAff (PG.withConnection pool $ flip P.insertUser user)
Right u@(User user) -> respondNoContent 201
if user.id <= 0 where
then respond 422 { error: "User ID must be positive: " <> show user.id} getUser = lift getBody
else if user.name == "" >>= V.except <<< renderForeignErrors
then respond 422 { error: "User name must not be empty" } >>= V.exceptCond "User ID must be positive" (\(User user) -> user.id > 0)
else do >>= V.exceptCond "User name must not be empty" (\(User user) -> user.name /= "")
liftAff (PG.withConnection pool $ flip P.insertUser u)
respondNoContent 201
updateUser :: forall eff. PG.Pool -> Handler (postgreSQL :: PG.POSTGRESQL | eff) updateUser :: forall eff. PG.Pool -> Handler (postgreSQL :: PG.POSTGRESQL | eff)
updateUser pool = getRouteParam "id" >>= case _ of updateUser pool = V.withValidation (Tuple <$> getUserId <*> getUserPatch)
Nothing -> respond 422 { error: "User ID is required" } \(Tuple userId (UserPatch userPatch)) ->
Just sUserId -> case fromString sUserId of case unNullOrUndefined userPatch.name of
Nothing -> respond 422 { error: "User ID must be positive: " <> sUserId } Nothing -> respondNoContent 204
Just userId -> getBody >>= case _ of Just uName -> V.withValidation (getUserName uName) \userName -> do
Left errs -> respond 422 { error: intercalate ", " $ map renderForeignError errs} savedUser <- liftAff $ PG.withConnection pool \conn -> PG.withTransaction conn do
Right (UserPatch userPatch) -> case unNullOrUndefined userPatch.name of P.findUser conn userId >>= case _ of
Nothing -> respondNoContent 204 Nothing -> pure Nothing
Just userName -> if userName == "" Just (User user) -> do
then respond 422 { error: "User name must not be empty" } let user' = User (user { name = userName })
else do P.updateUser conn user'
savedUser <- liftAff $ PG.withConnection pool \conn -> PG.withTransaction conn do pure $ Just user'
P.findUser conn userId >>= case _ of case savedUser of
Nothing -> pure Nothing Nothing -> respond 404 { error: "User not found with id: " <> show userId }
Just (User user) -> do Just user -> respond 200 (encode user)
let user' = User (user { name = userName }) where
P.updateUser conn user' getUserPatch = lift getBody >>= V.except <<< renderForeignErrors
pure $ Just user' getUserName = V.exceptCond "User name must not be empty" (_ /= "")
case savedUser of
Nothing -> respond 404 { error: "User not found with id: " <> sUserId }
Just user -> respond 200 (encode user)
listUsers :: forall eff. PG.Pool -> Handler (postgreSQL :: PG.POSTGRESQL | eff) listUsers :: forall eff. PG.Pool -> Handler (postgreSQL :: PG.POSTGRESQL | eff)
listUsers pool = liftAff (PG.withConnection pool P.listUsers) >>= encode >>> respond 200 listUsers pool = liftAff (PG.withConnection pool P.listUsers) >>= encode >>> respond 200
getUserId :: forall eff. V.Validation eff Int
getUserId = lift (getRouteParam "id")
>>= V.exceptMaybe "User ID is required"
>>= fromString >>> V.exceptMaybe "User ID must be an integer"
>>= V.exceptCond "User ID must be positive" (_ > 0)
renderForeignErrors :: forall a. Either (NonEmptyList ForeignError) a -> Either String a
renderForeignErrors = lmap (toList >>> map renderForeignError >>> intercalate ", ")
respond :: forall eff a. Int -> a -> Handler eff respond :: forall eff a. Int -> a -> Handler eff
respond status body = do respond status body = do
setStatus status setStatus status

View File

@ -0,0 +1,32 @@
module SimpleService.Validation
(module MoreExports, module SimpleService.Validation) where
import Prelude
import Control.Monad.Except (ExceptT, except, runExceptT)
import Data.Either (Either(..))
import Data.Maybe (Maybe(..))
import Node.Express.Handler (HandlerM, Handler)
import Node.Express.Response (sendJson, setStatus)
import Node.Express.Types (EXPRESS)
import Control.Monad.Except (except) as MoreExports
type Validation eff a = ExceptT String (HandlerM (express :: EXPRESS | eff)) a
exceptMaybe :: forall e m a. Applicative m => e -> Maybe a -> ExceptT e m a
exceptMaybe e a = except $ case a of
Just x -> Right x
Nothing -> Left e
exceptCond :: forall e m a. Applicative m => e -> (a -> Boolean) -> a -> ExceptT e m a
exceptCond e cond a = except $ if cond a then Right a else Left e
withValidation :: forall eff a.
Validation eff a
-> (a -> Handler eff)
-> Handler eff
withValidation action handler = runExceptT action >>= case _ of
Left err -> do
setStatus 422
sendJson {error: err}
Right x -> handler x