git.iko.soy/typeable/servant
on master help sign in
Enter via natural transformations
commit74f4d2db145836d177ea283d278fe52ae68a4c52 authorJulian K. Arni <jkarni@gmail.com> dateSat 2 May 2015, 16:46 (11 years ago) parent978c890
6 files changed+166
.gitignore+1 servant-server/servant-server.cabal+4 servant-server/src/Servant/Server.hs+5 servant-server/src/Servant/Server/Internal/Enter.hs+96 servant-server/test/Servant/Server/Internal/EnterSpec.hs+60 servant/Setup
.gitignore+1
@@ -21,3 +21,4 @@ cabal.config
2121*.prof
2222*.aux
2323*.hp
24+Setup
servant-server/servant-server.cabal+4
@@ -37,6 +37,7 @@ library
3737 Servant.Server
3838 Servant.Server.Internal
3939 Servant.Server.Internal.ServantErr
40+ Servant.Server.Internal.Enter
4041 Servant.Utils.StaticFiles
4142 build-depends:
4243 base >= 4.7 && < 5
@@ -46,6 +47,8 @@ library
4647 , either >= 4.3 && < 4.4
4748 , http-types >= 0.8 && < 0.9
4849 , network-uri >= 2.6 && < 2.7
50+ , mtl >= 2 && < 3
51+ , mmorph >= 1
4952 , safe >= 0.3 && < 0.4
5053 , servant >= 0.2 && < 0.4
5154 , split >= 0.2 && < 0.3
@@ -101,6 +104,7 @@ test-suite spec
101104 , temporary
102105 , text
103106 , transformers
107+ , mtl
104108 , wai
105109 , wai-extra
106110 , warp
servant-server/src/Servant/Server.hs+5
@@ -16,6 +16,10 @@ module Servant.Server
1616 , Server
1717 , ServerT
1818
19+ -- * Enter
20+ -- Applying functions to all handlers
21+ , Enter(..)
22+
1923 -- * Default error type
2024 , ServantErr(..)
2125 -- ** 3XX
@@ -59,6 +63,7 @@ import Network.Wai (Application)
5963import Servant.API (Canonicalize, canonicalize)
6064import Servant.Server.Internal
6165import Servant.Server.Internal.ServantErr
66+import Servant.Server.Internal.Enter
6267
6368
6469-- * Implementing Servers
servant-server/src/Servant/Server/Internal/Enter.hsnew file+96
@@ -0,0 +1,96 @@
1+{-# LANGUAGE CPP #-}
2+{-# LANGUAGE DeriveDataTypeable #-}
3+{-# LANGUAGE FlexibleInstances #-}
4+{-# LANGUAGE FunctionalDependencies #-}
5+{-# LANGUAGE MultiParamTypeClasses #-}
6+{-# LANGUAGE OverlappingInstances #-}
7+{-# LANGUAGE RankNTypes #-}
8+{-# LANGUAGE ScopedTypeVariables #-}
9+{-# LANGUAGE TypeFamilies #-}
10+{-# LANGUAGE TypeOperators #-}
11+{-# LANGUAGE UndecidableInstances #-}
12+module Servant.Server.Internal.Enter where
13+
14+import Control.Applicative
15+import qualified Control.Category as C
16+#if MIN_VERSION_mtl(2,2,1)
17+import Control.Monad.Except
18+#endif
19+import Control.Monad.Identity
20+import Control.Monad.Morph
21+import Control.Monad.Reader
22+import qualified Control.Monad.State.Lazy as LState
23+import qualified Control.Monad.State.Strict as SState
24+import Control.Monad.Trans.Either
25+import qualified Control.Monad.Writer.Lazy as LWriter
26+import qualified Control.Monad.Writer.Strict as SWriter
27+import Data.Typeable
28+import Servant.API
29+
30+class Enter typ arg ret | typ arg -> ret, typ ret -> arg where
31+ enter :: arg -> typ -> ret
32+
33+-- ** Servant combinators
34+instance ( Enter typ1 arg1 ret1, Enter typ2 arg2 ret2
35+ , arg1 ~ arg2
36+ ) => Enter (typ1 :<|> typ2) arg1 (ret1 :<|> ret2) where
37+ enter e (a :<|> b) = enter e a :<|> enter e b
38+
39+instance (Enter b arg ret) => Enter (a -> b) arg (a -> ret) where
40+ enter arg f a = enter arg (f a)
41+
42+-- ** Useful instances
43+
44+newtype m :~> n = Nat { unNat :: forall a. m a -> n a} deriving Typeable
45+
46+instance C.Category (:~>) where
47+ id = Nat id
48+ Nat f . Nat g = Nat (f . g)
49+
50+instance Enter (m a) (m :~> n) (n a) where
51+ enter (Nat f) = f
52+
53+liftNat :: (MonadTrans t, Monad m) => m :~> t m
54+liftNat = Nat lift
55+
56+runReaderTNat :: r -> (ReaderT r m :~> m)
57+runReaderTNat a = Nat (`runReaderT` a)
58+
59+evalStateTLNat :: Monad m => s -> (LState.StateT s m :~> m)
60+evalStateTLNat a = Nat (`LState.evalStateT` a)
61+
62+evalStateTSNat :: Monad m => s -> (SState.StateT s m :~> m)
63+evalStateTSNat a = Nat (`SState.evalStateT` a)
64+
65+-- | Log the contents of `SWriter.WriterT` with the function provided as the
66+-- first argument, and return the value of the @WriterT@ computation
67+logWriterTSNat :: MonadIO m => (w -> IO ()) -> (SWriter.WriterT w m :~> m)
68+logWriterTSNat logger = Nat $ \x -> do
69+ (a, w) <- SWriter.runWriterT x
70+ liftIO $ logger w
71+ return a
72+
73+-- | Like `logWriterTSNat`, but for strict @WriterT@.
74+logWriterTLNat :: MonadIO m => (w -> IO ()) -> (LWriter.WriterT w m :~> m)
75+logWriterTLNat logger = Nat $ \x -> do
76+ (a, w) <- LWriter.runWriterT x
77+ liftIO $ logger w
78+ return a
79+
80+#if MIN_VERSION_mtl(2,2,1)
81+fromExceptT :: ExceptT e m :~> EitherT e m
82+fromExceptT = Nat $ \x -> EitherT $ runExceptT x
83+#endif
84+
85+-- | Like @mmorph@'s `hoist`.
86+hoistNat :: (MFunctor t, Monad m) => (m :~> n) -> (t m :~> t n)
87+hoistNat (Nat n) = Nat $ hoist n
88+
89+embedNat :: (MMonad t, Monad n) => (m :~> t n) -> (t m :~> t n)
90+embedNat (Nat n) = Nat $ embed n
91+
92+squashNat :: (Monad m, MMonad t) => t (t m) :~> t m
93+squashNat = Nat squash
94+
95+generalizeNat :: Applicative m => Identity :~> m
96+generalizeNat = Nat (pure . runIdentity)
servant-server/test/Servant/Server/Internal/EnterSpec.hsnew file+60
@@ -0,0 +1,60 @@
1+{-# LANGUAGE DataKinds #-}
2+{-# LANGUAGE OverloadedStrings #-}
3+{-# LANGUAGE TypeOperators #-}
4+module Servant.Server.Internal.EnterSpec where
5+
6+import qualified Control.Category as C
7+import Control.Monad.Reader
8+import Control.Monad.Trans.Either
9+import Data.Proxy
10+import Servant.API
11+import Servant.Server
12+import Servant.Server.Internal.Enter
13+
14+import Test.Hspec (Spec, describe, it)
15+import Test.Hspec.Wai (get, matchStatus, post,
16+ shouldRespondWith, with)
17+
18+spec :: Spec
19+spec = describe "module Servant.Server.Enter" $ do
20+ enterSpec
21+
22+type ReaderAPI = "int" :> Get '[JSON] Int
23+ :<|> "string" :> Post '[JSON] String
24+
25+type IdentityAPI = "bool" :> Get '[JSON] Bool
26+
27+type CombinedAPI = ReaderAPI :<|> IdentityAPI
28+
29+readerAPI :: Proxy ReaderAPI
30+readerAPI = Proxy
31+
32+combinedAPI :: Proxy CombinedAPI
33+combinedAPI = Proxy
34+
35+readerServer' :: ServerT ReaderAPI (Reader String)
36+readerServer' = return 1797 :<|> ask
37+
38+fReader :: Reader String :~> EitherT ServantErr IO
39+fReader = generalizeNat C.. (runReaderTNat "hi")
40+
41+readerServer :: Server ReaderAPI
42+readerServer = enter fReader readerServer'
43+
44+combinedReaderServer' :: ServerT CombinedAPI (Reader String)
45+combinedReaderServer' = readerServer' :<|> enter generalizeNat (return True)
46+
47+combinedReaderServer :: Server CombinedAPI
48+combinedReaderServer = enter fReader combinedReaderServer'
49+
50+enterSpec :: Spec
51+enterSpec = describe "Enter" $ do
52+ with (return (serve readerAPI readerServer)) $ do
53+
54+ it "allows running arbitrary monads" $ do
55+ get "int" `shouldRespondWith` "1797"
56+ post "string" "3" `shouldRespondWith` "\"hi\""{ matchStatus = 201 }
57+
58+ with (return (serve combinedAPI combinedReaderServer)) $ do
59+ it "allows combnation of enters" $ do
60+ get "bool" `shouldRespondWith` "true"
servant/Setupdeleted

Binary file not shown.