git.iko.soy/typeable/request-track
on master help sign in
6 files changed+91−4
since splitsnapshots
unifiedsplit
README.org+18 request-track-servant/request-track-servant.cabal+9−1 request-track-servant/src/Web/Tracking/Servant.hs+4 request-track-servant/src/Web/Tracking/Servant/OpenAPI.hs+14 request-track-servant/src/Web/Tracking/Servant/Types.hs+41−2 stack.yaml+5−1
README.orgnew file+18
@@ -0,0 +1,18 @@
1+#+TITLE: Request tracking framework
2+#+AUTHOR: Nick <nick@typeable.io>
3+
4+* Mechanism
5+
6+See [[https://github.com/typeable/technical_notes/blob/master/Request%20Tracking/Tracking.org][this note]] for the mechanism description.
7+
8+* Lower-level API
9+
10+~request-track~ library provides WAI primitives to handle the passed headers. At the very least you would want to use ~echoMiddleware~ in order to send back the tracking headers in every HTTP reply.
11+
12+* Servant API
13+
14+~request-track-servant~ provides combinators to use with Servant. See the haddock documentation for more info.
15+
16+* Example server
17+
18+~example-server~ directory contains a simple HTTP server that uses all this stuff.
request-track-servant/request-track-servant.cabal+9−1
@@ -11,6 +11,11 @@ maintainer: nick@typeable.io
1111category: Web
1212build-type: Simple
1313
14+flag openapi
15+ description: Enable openapi instances
16+ default: False
17+ manual: True
18+
1419common warnings
1520 ghc-options: -Wall
1621
@@ -18,7 +23,6 @@ library
1823 import: warnings
1924 exposed-modules: Web.Tracking.Servant
2025 other-modules: Web.Tracking.Servant.Types
21− -- other-extensions:
2226 build-depends: base >=4.14.0.0 && < 5,
2327 bytestring >= 0.11,
2428 containers >= 0.6,
@@ -28,5 +32,9 @@ library
2832 servant-client-core >= 0.19,
2933 servant-server >= 0.19,
3034 wai >= 3.2
35+ if flag(openapi)
36+ other-modules: Web.Tracking.Servant.OpenAPI
37+ build-depends: servant-openapi3
38+ cpp-options: -DOPENAPI
3139 hs-source-dirs: src
3240 default-language: GHC2021
request-track-servant/src/Web/Tracking/Servant.hs+4
@@ -1,4 +1,8 @@
1+{-# LANGUAGE CPP #-}
12module Web.Tracking.Servant (module Export) where
23
34import Web.Tracking as Export (ReqtrackInfo(..))
45import Web.Tracking.Servant.Types as Export
6+#ifdef OPENAPI
7+import Web.Tracking.Servant.OpenAPI as Export ()
8+#endif
request-track-servant/src/Web/Tracking/Servant/OpenAPI.hsnew file+14
@@ -0,0 +1,14 @@
1+{-# OPTIONS_GHC -Wno-orphans #-}
2+module Web.Tracking.Servant.OpenAPI where
3+
4+import Web.Tracking.Servant.Types (RequestTracking, RequestTracking_)
5+
6+import Data.Proxy
7+import Servant.API
8+import Servant.OpenApi
9+
10+instance HasOpenApi api => HasOpenApi (RequestTracking :> api) where
11+ toOpenApi _ = toOpenApi (Proxy :: Proxy api)
12+
13+instance HasOpenApi api => HasOpenApi (RequestTracking_ :> api) where
14+ toOpenApi _ = toOpenApi (Proxy :: Proxy api)
request-track-servant/src/Web/Tracking/Servant/Types.hs+41−2
@@ -17,17 +17,55 @@ import Servant.Client.Core
1717import Servant.Server
1818import Servant.Server.Internal
1919
20−-- | 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+-- @
2146data RequestTracking
2247
48+-- | See 'RequestTracking'
2349type family TrackingData a
2450
2551-- | Same as above, except no argument is passed to a handler.
2652data RequestTracking_
2753
28−newtype ReqtrackingHandler r o = ReqtrackingHandler { unReqtrackingHandler :: r -> Handler o
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
2957 } deriving (Functor)
3058
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
67+
68+-- | Smart constructor to create 'ReqtrackingHandler'.
3169mkReqtrackingHandler :: (Maybe ReqtrackInfo -> Handler o) -> ReqtrackingHandler (Maybe ReqtrackInfo) o
3270mkReqtrackingHandler = ReqtrackingHandler
3371
@@ -78,6 +116,7 @@ instance HasClient m api => HasClient m (RequestTracking_ :> api) where
78116 hoistClientMonad pm Proxy f cl = hoistClientMonad pm (Proxy :: Proxy api) f cl
79117
80118
119+-- | Headers list to use with 'Headers'. Currently unused.
81120type ReqtrackHeaders = '[ Header RequestIdHdr ByteString
82121 , Header RequestLevelHdr Word
83122 , Header RequestSourceHdr ByteString
stack.yaml+5−1
@@ -1,4 +1,4 @@
1−resolver: lts-20.26
1+resolver: lts-21.6
22
33packages:
44- example-server
@@ -11,3 +11,7 @@ extra-deps:
1111
1212nix:
1313 packages: [ zlib ]
14+
15+flags:
16+ request-track-servant:
17+ openapi: true