@@ -3,7 +3,6 @@
33{-# LANGUAGE UndecidableInstances #-}
44module Web.Tracking.Servant.Types where
55
6−import Web.Tracking
76import Web.Tracking
87
98import Control.Monad ((<=<))
@@ -11,56 +10,113 @@ import Control.Monad.IO.Class
1110import Data.ByteString (ByteString)
1211import Data.Kind
1312import Data.Proxy
14−import Network.Wai
13+import qualified Data.Sequence as Seq
14+import qualified Network.Wai as Wai
1515import Servant.API
16+import Servant.Client.Core
1617import Servant.Server
1718import Servant.Server.Internal
1819
19−-- | Types to use in you API description
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+-- @
2046data RequestTracking
2147
22−type family TrackingData :: Type -> Type
48+-- | See 'RequestTracking'
49+type family TrackingData a
2350
2451-- | Same as above, except no argument is passed to a handler.
2552data RequestTracking_
2653
27−newtype ReqtrackingHandler o = ReqtrackingHandler { unReqtrackingHandler :: Maybe ReqtrackInfo -> Handler o
28− } deriving (Functor)
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
2967
30−mkReqtrackingHandler :: (Maybe ReqtrackInfo -> Handler o) -> ReqtrackingHandler o
68+-- | Smart constructor to create 'ReqtrackingHandler'.
69+mkReqtrackingHandler :: (Maybe ReqtrackInfo -> Handler o) -> ReqtrackingHandler (Maybe ReqtrackInfo) o
3170mkReqtrackingHandler = ReqtrackingHandler
3271
3372instance forall (api :: Type) context.
3473 ( HasServer api context
35− , HasContextEntry context (ReqtrackingHandler (TrackingData RequestTracking))
74+ , HasContextEntry context (ReqtrackingHandler (Maybe ReqtrackInfo) (TrackingData RequestTracking))
3675 ) => HasServer (RequestTracking :> api) context where
3776 type ServerT (RequestTracking :> api) m = TrackingData RequestTracking -> ServerT api m
3877
3978 hoistServerWithContext _ pc nt s = hoistServerWithContext (Proxy :: Proxy api) pc nt . s
4079
4180 route _ context subserver = route (Proxy :: Proxy api) context (subserver `addHeaderCheck` withRequest trackingCheck)
42− where trackingCheck :: Request -> DelayedIO (TrackingData RequestTracking)
81+ where trackingCheck :: Wai.Request -> DelayedIO (TrackingData RequestTracking)
4382 trackingCheck = either delayedFailFatal return <=< liftIO . runHandler . trackingHandler
44− trackingHandler :: Request -> Handler (TrackingData RequestTracking)
83+ trackingHandler :: Wai.Request -> Handler (TrackingData RequestTracking)
4584 trackingHandler = let ReqtrackingHandler h = getContextEntry context
4685 in h . parseTrackingHeaders
4786
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+
4897instance forall (api :: Type) context.
4998 ( HasServer api context
50− , HasContextEntry context (ReqtrackingHandler ())
99+ , HasContextEntry context (ReqtrackingHandler (Maybe ReqtrackInfo) ())
51100 ) => HasServer (RequestTracking_ :> api) context where
52101 type ServerT (RequestTracking_ :> api) m = ServerT api m
53102
54103 hoistServerWithContext _ pc nt s = hoistServerWithContext (Proxy :: Proxy api) pc nt s
55104
56105 route _ context subserver = route (Proxy :: Proxy api) context (fmap const subserver `addHeaderCheck` withRequest trackingCheck)
57− where trackingCheck :: Request -> DelayedIO ()
106+ where trackingCheck :: Wai.Request -> DelayedIO ()
58107 trackingCheck = either delayedFailFatal (const $ return ()) <=< liftIO . runHandler . trackingHandler
59− trackingHandler :: Request -> Handler ()
108+ trackingHandler :: Wai.Request -> Handler ()
60109 trackingHandler = let ReqtrackingHandler h = getContextEntry context
61110 in h . parseTrackingHeaders
62111
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+
63118
119+-- | Headers list to use with 'Headers'. Currently unused.
64120type ReqtrackHeaders = '[ Header RequestIdHdr ByteString
65121 , Header RequestLevelHdr Word
66122 , Header RequestSourceHdr ByteString