@@ -3,6 +3,7 @@
33{-# LANGUAGE UndecidableInstances #-}
44module Web.Tracking.Servant.Types where
55
6+import Web.Tracking
67import Web.Tracking
78
89import Control.Monad ((<=<))
@@ -10,113 +11,56 @@ import Control.Monad.IO.Class
1011import Data.ByteString (ByteString)
1112import Data.Kind
1213import Data.Proxy
13−import qualified Data.Sequence as Seq
14−import qualified Network.Wai as Wai
14+import Network.Wai
1515import Servant.API
16−import Servant.Client.Core
1716import Servant.Server
1817import Servant.Server.Internal
1918
20−-- | Combinator for your Servant API description. Prepend it to your API for greater enjoyment!
21−-- Example:
22−--
23−-- @
24−-- type MyAPI = RequestTracking :> "ping" :> Get '[JSON] Text
25−-- @
26−--
27−-- In addition to the API combinator, you also need to create a context handler that will
28−-- handler the tracking data (see 'ReqtrackInfo') and produce desired output, which in turn
29−-- will be passed to your API handlers.
30−-- The type of values passed to API handlers is controlled by @type instance TrackingData RequestTracking@,
31−-- which isn't set by default.
32−-- The server code should look something like this:
33−--
34−-- @
35−-- data TrackingArg = ...
36−--
37−-- type instance TrackingData RequestTracking = TrackingArg
38−--
39−-- trackingHandler :: ReqtrackingHandler (Maybe ReqtrackInfo) TrackingArg
40−-- trackingHandler = mkReqtrackingHandler $ \mrti ->
41−-- case mrti of
42−-- Just rti -> ... -- Do something with ReqtrackInfo, return TrackingArg
43−-- Nothing -> ...
44−--
45−-- @
19+-- | Types to use in you API description
4620data RequestTracking
4721
48−-- | See 'RequestTracking'
49−type family TrackingData a
22+type family TrackingData :: Type -> Type
5023
5124-- | Same as above, except no argument is passed to a handler.
5225data RequestTracking_
5326
54−-- | Servant context handler used for processing 'ReqtrackInfo' before passing the result to endpoint handlers.
55−-- Use 'mkReqtrackingHandler' in your code instead of the constructor.
56−newtype ReqtrackingHandler r o = ReqtrackingHandler { runReqtrackingHandler :: r -> Handler o
57− } deriving (Functor)
58−
59−instance Applicative (ReqtrackingHandler r) where
60− pure x = ReqtrackingHandler $ \_ -> pure x
61− f <*> v = ReqtrackingHandler $ \r -> runReqtrackingHandler f r <*> runReqtrackingHandler v r
62−
63−instance Monad (ReqtrackingHandler r) where
64− return = pure
65− f >>= g = ReqtrackingHandler $ \r -> do a <- runReqtrackingHandler f r
66− runReqtrackingHandler (g a) r
27+newtype ReqtrackingHandler o = ReqtrackingHandler { unReqtrackingHandler :: Maybe ReqtrackInfo -> Handler o
28+ } deriving (Functor)
6729
68−-- | Smart constructor to create 'ReqtrackingHandler'.
69−mkReqtrackingHandler :: (Maybe ReqtrackInfo -> Handler o) -> ReqtrackingHandler (Maybe ReqtrackInfo) o
30+mkReqtrackingHandler :: (Maybe ReqtrackInfo -> Handler o) -> ReqtrackingHandler o
7031mkReqtrackingHandler = ReqtrackingHandler
7132
7233instance forall (api :: Type) context.
7334 ( HasServer api context
74− , HasContextEntry context (ReqtrackingHandler (Maybe ReqtrackInfo) (TrackingData RequestTracking))
35+ , HasContextEntry context (ReqtrackingHandler (TrackingData RequestTracking))
7536 ) => HasServer (RequestTracking :> api) context where
7637 type ServerT (RequestTracking :> api) m = TrackingData RequestTracking -> ServerT api m
7738
7839 hoistServerWithContext _ pc nt s = hoistServerWithContext (Proxy :: Proxy api) pc nt . s
7940
8041 route _ context subserver = route (Proxy :: Proxy api) context (subserver `addHeaderCheck` withRequest trackingCheck)
81− where trackingCheck :: Wai.Request -> DelayedIO (TrackingData RequestTracking)
42+ where trackingCheck :: Request -> DelayedIO (TrackingData RequestTracking)
8243 trackingCheck = either delayedFailFatal return <=< liftIO . runHandler . trackingHandler
83− trackingHandler :: Wai.Request -> Handler (TrackingData RequestTracking)
44+ trackingHandler :: Request -> Handler (TrackingData RequestTracking)
8445 trackingHandler = let ReqtrackingHandler h = getContextEntry context
8546 in h . parseTrackingHeaders
8647
87−instance HasClient m api => HasClient m (RequestTracking :> api) where
88− type Client m (RequestTracking :> api) = Maybe ReqtrackInfo -> Client m api
89−
90− clientWithRoute pm Proxy req mrti = clientWithRoute pm (Proxy :: Proxy api) req'
91− where req' | Just rti <- mrti = req { requestHeaders = Seq.fromList (mkTrackingHeaders rti) <> requestHeaders req }
92− | otherwise = req
93−
94− hoistClientMonad pm Proxy f cl = \rti -> hoistClientMonad pm (Proxy :: Proxy api) f (cl rti)
95−
96−
9748instance forall (api :: Type) context.
9849 ( HasServer api context
99− , HasContextEntry context (ReqtrackingHandler (Maybe ReqtrackInfo) ())
50+ , HasContextEntry context (ReqtrackingHandler ())
10051 ) => HasServer (RequestTracking_ :> api) context where
10152 type ServerT (RequestTracking_ :> api) m = ServerT api m
10253
10354 hoistServerWithContext _ pc nt s = hoistServerWithContext (Proxy :: Proxy api) pc nt s
10455
10556 route _ context subserver = route (Proxy :: Proxy api) context (fmap const subserver `addHeaderCheck` withRequest trackingCheck)
106− where trackingCheck :: Wai.Request -> DelayedIO ()
57+ where trackingCheck :: Request -> DelayedIO ()
10758 trackingCheck = either delayedFailFatal (const $ return ()) <=< liftIO . runHandler . trackingHandler
108− trackingHandler :: Wai.Request -> Handler ()
59+ trackingHandler :: Request -> Handler ()
10960 trackingHandler = let ReqtrackingHandler h = getContextEntry context
11061 in h . parseTrackingHeaders
11162
112−instance HasClient m api => HasClient m (RequestTracking_ :> api) where
113− type Client m (RequestTracking_ :> api) = Client m api
114−
115− clientWithRoute pm Proxy = clientWithRoute pm (Proxy :: Proxy api)
116− hoistClientMonad pm Proxy f cl = hoistClientMonad pm (Proxy :: Proxy api) f cl
117−
11863
119−-- | Headers list to use with 'Headers'. Currently unused.
12064type ReqtrackHeaders = '[ Header RequestIdHdr ByteString
12165 , Header RequestLevelHdr Word
12266 , Header RequestSourceHdr ByteString