@@ -1,10 +1,12 @@
11{-# LANGUAGE AllowAmbiguousTypes #-}
22{-# LANGUAGE ConstraintKinds #-}
3+{-# LANGUAGE CPP #-}
34{-# LANGUAGE DataKinds #-}
45{-# LANGUAGE DeriveDataTypeable #-}
56{-# LANGUAGE FlexibleContexts #-}
67{-# LANGUAGE FlexibleInstances #-}
78{-# LANGUAGE InstanceSigs #-}
9+{-# LANGUAGE LambdaCase #-}
810{-# LANGUAGE MultiParamTypeClasses #-}
911{-# LANGUAGE OverloadedStrings #-}
1012{-# LANGUAGE PolyKinds #-}
@@ -34,14 +36,15 @@ module Servant.Server.Internal
3436import Control.Monad
3537 (join, when)
3638import Control.Monad.Trans
37− (liftIO)
39+ (liftIO, lift)
3840import Control.Monad.Trans.Resource
39− (runResourceT)
41+ (runResourceT, ReleaseKey)
42+import Data.Acquire
4043import qualified Data.ByteString as B
4144import qualified Data.ByteString.Builder as BB
4245import qualified Data.ByteString.Char8 as BC8
4346import qualified Data.ByteString.Lazy as BL
44−import Data.Constraint (Dict(..))
47+import Data.Constraint (Constraint, Dict(..))
4548import Data.Either
4649 (partitionEithers)
4750import Data.Maybe
@@ -56,14 +59,14 @@ import qualified Data.Text as T
5659import Data.Typeable
5760import GHC.Generics
5861import GHC.TypeLits
59− (KnownNat, KnownSymbol, natVal, symbolVal)
62+ (KnownNat, KnownSymbol, TypeError, symbolVal)
6063import qualified Network.HTTP.Media as NHM
6164import Network.HTTP.Types hiding
6265 (Header, ResponseHeaders)
6366import Network.Socket
6467 (SockAddr)
6568import Network.Wai
66− (Application, Request, httpVersion, isSecure, lazyRequestBody,
69+ (Application, Request, Response, ResponseReceived, httpVersion, isSecure, lazyRequestBody,
6770 queryString, remoteHost, getRequestBodyChunk, requestHeaders,
6871 requestMethod, responseLBS, responseStream, vault)
6972import Prelude ()
@@ -73,10 +76,10 @@ import Servant.API
7376 CaptureAll, Description, EmptyAPI, Fragment,
7477 FramingRender (..), FramingUnrender (..), FromSourceIO (..),
7578 Header', If, IsSecure (..), NoContentVerb, QueryFlag,
76− QueryParam', QueryParams, Raw, ReflectMethod (reflectMethod),
79+ QueryParam', QueryParams, Raw, RawM, ReflectMethod (reflectMethod),
7780 RemoteHost, ReqBody', SBool (..), SBoolI (..), SourceIO,
7881 Stream, StreamBody', Summary, ToSourceIO (..), Vault, Verb,
79− WithNamedContext, NamedRoutes)
82+ WithNamedContext, WithResource, NamedRoutes)
8083import Servant.API.Generic (GenericMode(..), ToServant, ToServantApi, GServantProduct, toServant, fromServant)
8184import Servant.API.ContentTypes
8285 (AcceptHeader (..), AllCTRender (..), AllCTUnrender (..),
@@ -87,10 +90,15 @@ import Servant.API.Modifiers
8790 unfoldRequestArgument)
8891import Servant.API.ResponseHeaders
8992 (GetHeaders, Headers, getHeaders, getResponse)
93+import Servant.API.Status
94+ (statusFromNat)
9095import qualified Servant.Types.SourceT as S
96+import Servant.API.TypeErrors
9197import Web.HttpApiData
9298 (FromHttpApiData, parseHeader, parseQueryParam, parseUrlPiece,
9399 parseUrlPieces)
100+import Data.Kind
101+ (Type)
94102
95103import Servant.Server.Internal.BasicAuth
96104import Servant.Server.Internal.Context
@@ -109,6 +117,10 @@ import Servant.API.TypeLevel
109117 (AtLeastOneFragment, FragmentUnique)
110118
111119class HasServer api context where
120+ -- | The type of a server for this API, given a monad to run effects in.
121+ --
122+ -- Note that the result kind is @*@, so it is /not/ a monad transformer, unlike
123+ -- what the @T@ in the name might suggest.
112124 type ServerT api (m :: * -> *) :: *
113125
114126 route ::
@@ -170,7 +182,7 @@ instance (HasServer a context, HasServer b context) => HasServer (a :<|> b) cont
170182-- > server = getBook
171183-- > where getBook :: Text -> Handler Book
172184-- > getBook isbn = ...
173−instance (KnownSymbol capture, FromHttpApiData a
185+instance (KnownSymbol capture, FromHttpApiData a, Typeable a
174186 , HasServer api context, SBoolI (FoldLenient mods)
175187 , HasContextEntry (MkContextWithErrorFormatter context) ErrorFormatters
176188 )
@@ -182,7 +194,7 @@ instance (KnownSymbol capture, FromHttpApiData a
182194 hoistServerWithContext _ pc nt s = hoistServerWithContext (Proxy :: Proxy api) pc nt . s
183195
184196 route Proxy context d =
185− CaptureRouter $
197+ CaptureRouter [hint] $
186198 route (Proxy :: Proxy api)
187199 context
188200 (addCapture d $ \ txt -> withRequest $ \ request ->
@@ -194,6 +206,7 @@ instance (KnownSymbol capture, FromHttpApiData a
194206 where
195207 rep = typeRep (Proxy :: Proxy Capture')
196208 formatError = urlParseErrorFormatter $ getContextEntry (mkContextWithErrorFormatter context)
209+ hint = CaptureHint (T.pack $ symbolVal $ Proxy @capture) (typeRep (Proxy :: Proxy a))
197210
198211-- | If you use 'CaptureAll' in one of the endpoints for your API,
199212-- this automatically requires your server-side handler to be a
@@ -212,7 +225,7 @@ instance (KnownSymbol capture, FromHttpApiData a
212225-- > server = getSourceFile
213226-- > where getSourceFile :: [Text] -> Handler Book
214227-- > getSourceFile pathSegments = ...
215−instance (KnownSymbol capture, FromHttpApiData a
228+instance (KnownSymbol capture, FromHttpApiData a, Typeable a
216229 , HasServer api context
217230 , HasContextEntry (MkContextWithErrorFormatter context) ErrorFormatters
218231 )
@@ -224,7 +237,7 @@ instance (KnownSymbol capture, FromHttpApiData a
224237 hoistServerWithContext _ pc nt s = hoistServerWithContext (Proxy :: Proxy api) pc nt . s
225238
226239 route Proxy context d =
227− CaptureAllRouter $
240+ CaptureAllRouter [hint] $
228241 route (Proxy :: Proxy api)
229242 context
230243 (addCapture d $ \ txts -> withRequest $ \ request ->
@@ -235,6 +248,43 @@ instance (KnownSymbol capture, FromHttpApiData a
235248 where
236249 rep = typeRep (Proxy :: Proxy CaptureAll)
237250 formatError = urlParseErrorFormatter $ getContextEntry (mkContextWithErrorFormatter context)
251+ hint = CaptureHint (T.pack $ symbolVal $ Proxy @capture) (typeRep (Proxy :: Proxy [a]))
252+
253+-- | If you use 'WithResource' in one of the endpoints for your API Servant
254+-- will provide the handler for this endpoint an argument of the specified type.
255+-- The lifespan of this resource will be automatically managed by Servant. This
256+-- resource will be created before the handler starts and it will be destoyed
257+-- after it ends. A new resource is created for each request to the endpoint.
258+
259+-- The creation and destruction are done using a 'Data.Acquire.Acquire'
260+-- provided via server 'Context'.
261+--
262+-- Example
263+--
264+-- > type MyApi = WithResource Handle :> "writeToFile" :> Post '[JSON] NoContent
265+-- >
266+-- > server :: Server MyApi
267+-- > server = writeToFile
268+-- > where writeToFile :: (ReleaseKey, Handle) -> Handler NoContent
269+-- > writeToFile (_, h) = hPutStrLn h "message"
270+--
271+-- In addition to the resource, the handler will also receive a 'ReleaseKey'
272+-- which can be used to deallocate the resource before the end of the request
273+-- if desired.
274+
275+instance (HasServer api ctx, HasContextEntry ctx (Acquire a))
276+ => HasServer (WithResource a :> api) ctx where
277+
278+ type ServerT (WithResource a :> api) m = (ReleaseKey, a) -> ServerT api m
279+
280+ hoistServerWithContext _ pc nt s = hoistServerWithContext (Proxy @api) pc nt . s
281+
282+ route Proxy context d = route (Proxy @api) context (d `addParameterCheck` allocateResource)
283+ where
284+ allocateResource :: DelayedIO (ReleaseKey, a)
285+ allocateResource = DelayedIO $ lift $ allocateAcquire (getContextEntry context)
286+
287+
238288
239289allowedMethodHead :: Method -> Request -> Bool
240290allowedMethodHead method request = method == methodGet && requestMethod request == methodHead
@@ -298,7 +348,7 @@ instance {-# OVERLAPPABLE #-}
298348
299349 route Proxy _ = methodRouter ([],) method (Proxy :: Proxy ctypes) status
300350 where method = reflectMethod (Proxy :: Proxy method)
301− status = toEnum . fromInteger $ natVal (Proxy :: Proxy status)
351+ status = statusFromNat (Proxy :: Proxy status)
302352
303353instance {-# OVERLAPPING #-}
304354 ( AllCTRender ctypes a, ReflectMethod method, KnownNat status
@@ -310,7 +360,7 @@ instance {-# OVERLAPPING #-}
310360
311361 route Proxy _ = methodRouter (\x -> (getHeaders x, getResponse x)) method (Proxy :: Proxy ctypes) status
312362 where method = reflectMethod (Proxy :: Proxy method)
313− status = toEnum . fromInteger $ natVal (Proxy :: Proxy status)
363+ status = statusFromNat (Proxy :: Proxy status)
314364
315365instance (ReflectMethod method) =>
316366 HasServer (NoContentVerb method) context where
@@ -331,7 +381,7 @@ instance {-# OVERLAPPABLE #-}
331381
332382 route Proxy _ = streamRouter ([],) method status (Proxy :: Proxy framing) (Proxy :: Proxy ctype)
333383 where method = reflectMethod (Proxy :: Proxy method)
334− status = toEnum . fromInteger $ natVal (Proxy :: Proxy status)
384+ status = statusFromNat (Proxy :: Proxy status)
335385
336386
337387instance {-# OVERLAPPING #-}
@@ -345,7 +395,7 @@ instance {-# OVERLAPPING #-}
345395
346396 route Proxy _ = streamRouter (\x -> (getHeaders x, getResponse x)) method status (Proxy :: Proxy framing) (Proxy :: Proxy ctype)
347397 where method = reflectMethod (Proxy :: Proxy method)
348− status = toEnum . fromInteger $ natVal (Proxy :: Proxy status)
398+ status = statusFromNat (Proxy :: Proxy status)
349399
350400
351401streamRouter :: forall ctype a c chunk env framing. (MimeRender ctype chunk, FramingRender framing, ToSourceIO chunk a) =>
@@ -603,6 +653,35 @@ instance HasServer Raw context where
603653 Fail a -> respond $ Fail a
604654 FailFatal e -> respond $ FailFatal e
605655
656+-- | Just pass the request to the underlying application and serve its response.
657+--
658+-- Example:
659+--
660+-- > type MyApi = "images" :> Raw
661+-- >
662+-- > server :: Server MyApi
663+-- > server = serveDirectory "/var/www/images"
664+instance HasServer RawM context where
665+ type ServerT RawM m = Request -> (Response -> IO ResponseReceived) -> m ResponseReceived
666+
667+ route
668+ :: Proxy RawM
669+ -> Context context
670+ -> Delayed env (Request -> (Response -> IO ResponseReceived) -> Handler ResponseReceived) -> Router env
671+ route _ _ handleDelayed = RawRouter $ \env request respond -> runResourceT $ do
672+ routeResult <- runDelayed handleDelayed env request
673+ let respond' = liftIO . respond
674+ liftIO $ case routeResult of
675+ Route handler -> runHandler (handler request (respond . Route)) >>=
676+ \case
677+ Left e -> respond' $ FailFatal e
678+ Right a -> pure a
679+ Fail e -> respond' $ Fail e
680+ FailFatal e -> respond' $ FailFatal e
681+
682+ hoistServerWithContext _ _ f srvM = \req respond -> f (srvM req respond)
683+
684+
606685-- | If you use 'ReqBody' in one of the endpoints for your API,
607686-- this automatically requires your server-side handler to be a function
608687-- that takes an argument of the type specified by 'ReqBody'.
@@ -812,38 +891,19 @@ instance (HasContextEntry context (NamedContext name subContext), HasServer subA
812891 hoistServerWithContext _ _ nt s = hoistServerWithContext (Proxy :: Proxy subApi) (Proxy :: Proxy subContext) nt s
813892
814893-------------------------------------------------------------------------------
815−-- TypeError helpers
894+-- Custom type errors
816895-------------------------------------------------------------------------------
817896
818−-- | This instance catches mistakes when there are non-saturated
819−-- type applications on LHS of ':>'.
820−--
821−-- >>> serve (Proxy :: Proxy (Capture "foo" :> Get '[JSON] Int)) (error "...")
822−-- ...
823−-- ...Expected something of kind Symbol or *, got: k -> l on the LHS of ':>'.
824−-- ...Maybe you haven't applied enough arguments to
825−-- ...Capture' '[] "foo"
826−-- ...
827−--
828−-- >>> undefined :: Server (Capture "foo" :> Get '[JSON] Int)
829−-- ...
830−-- ...Expected something of kind Symbol or *, got: k -> l on the LHS of ':>'.
831−-- ...Maybe you haven't applied enough arguments to
832−-- ...Capture' '[] "foo"
833−-- ...
834−--
835−instance TypeError (HasServerArrowKindError arr) => HasServer ((arr :: k -> l) :> api) context
897+-- Erroring instance for 'HasServer' when a combinator is not fully applied
898+instance TypeError (PartialApplication
899+#if __GLASGOW_HASKELL__ >= 904
900+ @(Type -> [Type] -> Constraint)
901+#endif
902+ HasServer arr) => HasServer ((arr :: a -> b) :> sub) context
836903 where
837− type ServerT (arr :> api) m = TypeError (HasServerArrowKindError arr)
838− -- it doesn't really matter what sub route we peak
839− route _ _ _ = error "servant-server panic: impossible happened in HasServer (arr :> api)"
840− hoistServerWithContext _ _ _ = id
841−
842−-- Cannot have TypeError here, otherwise use of this symbol will error :)
843−type HasServerArrowKindError arr =
844− 'Text "Expected something of kind Symbol or *, got: k -> l on the LHS of ':>'."
845− ':$$: 'Text "Maybe you haven't applied enough arguments to"
846− ':$$: 'ShowType arr
904+ type ServerT (arr :> sub) _ = TypeError (PartialApplication (HasServer :: * -> [*] -> Constraint) arr)
905+ route = error "unreachable"
906+ hoistServerWithContext _ _ _ _ = error "unreachable"
847907
848908-- | This instance prevents from accidentally using '->' instead of ':>'
849909--
@@ -878,6 +938,19 @@ type HasServerArrowTypeError a b =
878938 ':$$: 'Text "and"
879939 ':$$: 'ShowType b
880940
941+-- Erroring instances for 'HasServer' for unknown API combinators
942+
943+-- XXX: This omits the @context@ parameter, e.g.:
944+--
945+-- "There is no instance for HasServer (Bool :> …)". Do we care ?
946+instance {-# OVERLAPPABLE #-} TypeError (NoInstanceForSub
947+#if __GLASGOW_HASKELL__ >= 904
948+ @(Type -> [Type] -> Constraint)
949+#endif
950+ HasServer ty) => HasServer (ty :> sub) context
951+
952+instance {-# OVERLAPPABLE #-} TypeError (NoInstanceFor (HasServer api context)) => HasServer api context
953+
881954-- | Ignore @'Fragment'@ in server handlers.
882955-- See <https://ietf.org/rfc/rfc2616.html#section-15.1.3> for more details.
883956--