2014-10-25 01:27:39 +02:00
|
|
|
{-# LANGUAGE TypeFamilies #-}
|
|
|
|
{-# LANGUAGE OverloadedStrings #-}
|
2014-10-28 10:12:25 +01:00
|
|
|
|
|
|
|
-- | This module lets you implement 'Server's for defined APIs. You will
|
|
|
|
-- probably need 'serve' (and look at the 'HasServer' type family), but
|
|
|
|
-- 'toApplication' and 'route' are rather internals.
|
|
|
|
|
2014-10-25 01:27:39 +02:00
|
|
|
module Servant.Server where
|
|
|
|
|
2014-10-28 14:34:28 +01:00
|
|
|
import Data.Monoid
|
2014-10-25 01:27:39 +02:00
|
|
|
import Data.Proxy
|
|
|
|
import Network.HTTP.Types
|
|
|
|
import Network.Wai
|
|
|
|
|
|
|
|
-- * Implementing Servers
|
|
|
|
|
|
|
|
-- | 'serve' allows you to implement an API and produce a wai 'Application'.
|
|
|
|
serve :: HasServer layout => Proxy layout -> Server layout -> Application
|
|
|
|
serve p server = toApplication (route p server)
|
|
|
|
|
|
|
|
toApplication :: RoutingApplication -> Application
|
2014-10-27 11:24:20 +01:00
|
|
|
toApplication ra request respond = do
|
2014-10-28 14:34:28 +01:00
|
|
|
ra request (routingRespond . routeResult)
|
2014-10-27 11:24:20 +01:00
|
|
|
where
|
2014-10-28 14:34:28 +01:00
|
|
|
routingRespond :: Either RouteMismatch Response -> IO ResponseReceived
|
|
|
|
routingRespond (Left NotFound) =
|
2014-10-27 11:24:20 +01:00
|
|
|
respond $ responseLBS notFound404 [] "not found"
|
2014-10-28 14:34:28 +01:00
|
|
|
routingRespond (Left WrongMethod) =
|
|
|
|
respond $ responseLBS methodNotAllowed405 [] "method not allowed"
|
2014-10-28 16:50:42 +01:00
|
|
|
routingRespond (Left InvalidBody) =
|
|
|
|
respond $ responseLBS badRequest400 [] "Invalid JSON in request body"
|
2014-10-28 14:34:28 +01:00
|
|
|
routingRespond (Right response) =
|
2014-10-27 11:24:20 +01:00
|
|
|
respond response
|
2014-10-25 01:27:39 +02:00
|
|
|
|
2014-10-28 14:34:28 +01:00
|
|
|
-- * Route mismatch
|
|
|
|
data RouteMismatch =
|
|
|
|
NotFound -- ^ the usual "not found" error
|
|
|
|
| WrongMethod -- ^ a more informative "you just got the HTTP method wrong" error
|
2014-10-28 16:50:42 +01:00
|
|
|
| InvalidBody -- ^ an even more informative "you json request body wasn't valid" error
|
2014-10-28 14:34:28 +01:00
|
|
|
deriving (Eq, Show)
|
|
|
|
|
|
|
|
-- |
|
|
|
|
-- @
|
2014-10-28 16:50:42 +01:00
|
|
|
-- > mempty = NotFound
|
|
|
|
-- >
|
|
|
|
-- > NotFound `mappend` x = x
|
|
|
|
-- > WrongMethod `mappend` InvalidBody = InvalidBody
|
|
|
|
-- > WrongMethod `mappend` _ = WrongMethod
|
|
|
|
-- > InvalidBody `mappend` _ = InvalidBody
|
2014-10-28 14:34:28 +01:00
|
|
|
-- @
|
|
|
|
instance Monoid RouteMismatch where
|
|
|
|
mempty = NotFound
|
|
|
|
|
2014-10-28 16:50:42 +01:00
|
|
|
NotFound `mappend` x = x
|
|
|
|
WrongMethod `mappend` InvalidBody = InvalidBody
|
|
|
|
WrongMethod `mappend` _ = WrongMethod
|
|
|
|
InvalidBody `mappend` _ = InvalidBody
|
2014-10-28 14:34:28 +01:00
|
|
|
|
|
|
|
-- | A wrapper around @'Either' 'RouteMismatch' a@.
|
|
|
|
newtype RouteResult a =
|
|
|
|
RR { routeResult :: Either RouteMismatch a }
|
|
|
|
deriving (Eq, Show)
|
|
|
|
|
|
|
|
failWith :: RouteMismatch -> RouteResult a
|
|
|
|
failWith = RR . Left
|
|
|
|
|
|
|
|
succeedWith :: a -> RouteResult a
|
|
|
|
succeedWith = RR . Right
|
|
|
|
|
|
|
|
isMismatch :: RouteResult a -> Bool
|
|
|
|
isMismatch (RR (Left _)) = True
|
|
|
|
isMismatch _ = False
|
|
|
|
|
|
|
|
-- | If we get a `Right`, it has precedence over everything else.
|
|
|
|
--
|
|
|
|
-- This in particular means that if we could get several 'Right's,
|
|
|
|
-- only the first we encounter would be taken into account.
|
|
|
|
instance Monoid (RouteResult a) where
|
|
|
|
mempty = RR $ Left mempty
|
|
|
|
|
|
|
|
RR (Left x) `mappend` RR (Left y) = RR $ Left (x <> y)
|
|
|
|
RR (Left _) `mappend` RR (Right y) = RR $ Right y
|
|
|
|
r `mappend` _ = r
|
|
|
|
|
2014-10-25 01:27:39 +02:00
|
|
|
type RoutingApplication =
|
2014-10-28 10:42:49 +01:00
|
|
|
Request -- ^ the request, the field 'pathInfo' may be modified by url routing
|
2014-10-28 14:34:28 +01:00
|
|
|
-> (RouteResult Response -> IO ResponseReceived) -> IO ResponseReceived
|
2014-10-25 01:27:39 +02:00
|
|
|
|
|
|
|
class HasServer layout where
|
|
|
|
type Server layout :: *
|
|
|
|
route :: Proxy layout -> Server layout -> RoutingApplication
|