@@ -1,10 +1,12 @@
1{-# LANGUAGE AllowAmbiguousTypes #-}
1{-# LANGUAGE AllowAmbiguousTypes #-}
2{-# LANGUAGE ConstraintKinds #-}
2{-# LANGUAGE ConstraintKinds #-}
3{-# LANGUAGE DataKinds #-}
4{-# LANGUAGE DataKinds #-}
4{-# LANGUAGE DeriveDataTypeable #-}
5{-# LANGUAGE DeriveDataTypeable #-}
5{-# LANGUAGE FlexibleContexts #-}
6{-# LANGUAGE FlexibleContexts #-}
6{-# LANGUAGE FlexibleInstances #-}
7{-# LANGUAGE FlexibleInstances #-}
7{-# LANGUAGE InstanceSigs #-}
8{-# LANGUAGE InstanceSigs #-}
9+{-# LANGUAGE LambdaCase #-}
8{-# LANGUAGE MultiParamTypeClasses #-}
10{-# LANGUAGE MultiParamTypeClasses #-}
9{-# LANGUAGE OverloadedStrings #-}
11{-# LANGUAGE OverloadedStrings #-}
10{-# LANGUAGE PolyKinds #-}
12{-# LANGUAGE PolyKinds #-}
@@ -34,14 +36,15 @@ module Servant.Server.Internal
34import Control.Monad
36import Control.Monad
35 (join, when)
37 (join, when)
36import Control.Monad.Trans
38import Control.Monad.Trans
37− (liftIO)
39+ (liftIO, lift)
38import Control.Monad.Trans.Resource
40import Control.Monad.Trans.Resource
39− (runResourceT)
41+ (runResourceT, ReleaseKey)
40import qualified Data.ByteString as B
43import qualified Data.ByteString as B
41import qualified Data.ByteString.Builder as BB
44import qualified Data.ByteString.Builder as BB
42import qualified Data.ByteString.Char8 as BC8
45import qualified Data.ByteString.Char8 as BC8
43import qualified Data.ByteString.Lazy as BL
46import qualified Data.ByteString.Lazy as BL
44−import Data.Constraint (Dict(..))
47+import Data.Constraint (Constraint, Dict(..))
45import Data.Either
48import Data.Either
46 (partitionEithers)
49 (partitionEithers)
47import Data.Maybe
50import Data.Maybe
@@ -56,14 +59,14 @@ import qualified Data.Text as T
56import Data.Typeable
59import Data.Typeable
57import GHC.Generics
60import GHC.Generics
58import GHC.TypeLits
61import GHC.TypeLits
59− (KnownNat, KnownSymbol, symbolVal)
62+ (KnownNat, KnownSymbol, TypeError, symbolVal)
60import qualified Network.HTTP.Media as NHM
63import qualified Network.HTTP.Media as NHM
61import Network.HTTP.Types hiding
64import Network.HTTP.Types hiding
62 (Header, ResponseHeaders)
65 (Header, ResponseHeaders)
63import Network.Socket
66import Network.Socket
64 (SockAddr)
67 (SockAddr)
65import Network.Wai
68import Network.Wai
66− (Application, Request, httpVersion, isSecure, lazyRequestBody,
69+ (Application, Request, Response, ResponseReceived, httpVersion, isSecure, lazyRequestBody,
67 queryString, remoteHost, getRequestBodyChunk, requestHeaders,
70 queryString, remoteHost, getRequestBodyChunk, requestHeaders,
68 requestMethod, responseLBS, responseStream, vault)
71 requestMethod, responseLBS, responseStream, vault)
69import Prelude ()
72import Prelude ()
@@ -73,10 +76,10 @@ import Servant.API
73 CaptureAll, Description, EmptyAPI, Fragment,
76 CaptureAll, Description, EmptyAPI, Fragment,
74 FramingRender (..), FramingUnrender (..), FromSourceIO (..),
77 FramingRender (..), FramingUnrender (..), FromSourceIO (..),
75 Header', If, IsSecure (..), NoContentVerb, QueryFlag,
78 Header', If, IsSecure (..), NoContentVerb, QueryFlag,
76− QueryParam', QueryParams, Raw, ReflectMethod (reflectMethod),
79+ QueryParam', QueryParams, Raw, RawM, ReflectMethod (reflectMethod),
77 RemoteHost, ReqBody', SBool (..), SBoolI (..), SourceIO,
80 RemoteHost, ReqBody', SBool (..), SBoolI (..), SourceIO,
78 Stream, StreamBody', Summary, ToSourceIO (..), Vault, Verb,
81 Stream, StreamBody', Summary, ToSourceIO (..), Vault, Verb,
79− WithNamedContext, NamedRoutes)
82+ WithNamedContext, WithResource, NamedRoutes)
80import Servant.API.Generic (GenericMode(..), ToServant, ToServantApi, GServantProduct, toServant, fromServant)
83import Servant.API.Generic (GenericMode(..), ToServant, ToServantApi, GServantProduct, toServant, fromServant)
81import Servant.API.ContentTypes
84import Servant.API.ContentTypes
82 (AcceptHeader (..), AllCTRender (..), AllCTUnrender (..),
85 (AcceptHeader (..), AllCTRender (..), AllCTUnrender (..),
@@ -90,9 +93,12 @@ import Servant.API.ResponseHeaders
90import Servant.API.Status
93import Servant.API.Status
91 (statusFromNat)
94 (statusFromNat)
92import qualified Servant.Types.SourceT as S
95import qualified Servant.Types.SourceT as S
96+import Servant.API.TypeErrors
93import Web.HttpApiData
97import Web.HttpApiData
94 (FromHttpApiData, parseHeader, parseQueryParam, parseUrlPiece,
98 (FromHttpApiData, parseHeader, parseQueryParam, parseUrlPiece,
95 parseUrlPieces)
99 parseUrlPieces)
97import Servant.Server.Internal.BasicAuth
103import Servant.Server.Internal.BasicAuth
98import Servant.Server.Internal.Context
104import Servant.Server.Internal.Context
@@ -111,6 +117,10 @@ import Servant.API.TypeLevel
111 (AtLeastOneFragment, FragmentUnique)
117 (AtLeastOneFragment, FragmentUnique)
113class HasServer api context where
119class HasServer api context where
120+ -- | The type of a server for this API, given a monad to run effects in.
122+ -- Note that the result kind is @*@, so it is /not/ a monad transformer, unlike
123+ -- what the @T@ in the name might suggest.
114 type ServerT api (m :: * -> *) :: *
124 type ServerT api (m :: * -> *) :: *
@@ -172,7 +182,7 @@ instance (HasServer a context, HasServer b context) => HasServer (a :<|> b) cont
172-- > server = getBook
182-- > server = getBook
173-- > where getBook :: Text -> Handler Book
183-- > where getBook :: Text -> Handler Book
174-- > getBook isbn = ...
184-- > getBook isbn = ...
175−instance (KnownSymbol capture, FromHttpApiData a
185+instance (KnownSymbol capture, FromHttpApiData a, Typeable a
176 , HasServer api context, SBoolI (FoldLenient mods)
186 , HasServer api context, SBoolI (FoldLenient mods)
177 , HasContextEntry (MkContextWithErrorFormatter context) ErrorFormatters
187 , HasContextEntry (MkContextWithErrorFormatter context) ErrorFormatters
@@ -184,7 +194,7 @@ instance (KnownSymbol capture, FromHttpApiData a
184 hoistServerWithContext _ pc nt s = hoistServerWithContext (Proxy :: Proxy api) pc nt . s
194 hoistServerWithContext _ pc nt s = hoistServerWithContext (Proxy :: Proxy api) pc nt . s
186 route Proxy context d =
196 route Proxy context d =
187− CaptureRouter $
197+ CaptureRouter [hint] $
188 route (Proxy :: Proxy api)
198 route (Proxy :: Proxy api)
190 (addCapture d $ \ txt -> withRequest $ \ request ->
200 (addCapture d $ \ txt -> withRequest $ \ request ->
@@ -196,6 +206,7 @@ instance (KnownSymbol capture, FromHttpApiData a
197 rep = typeRep (Proxy :: Proxy Capture')
207 rep = typeRep (Proxy :: Proxy Capture')
198 formatError = urlParseErrorFormatter $ getContextEntry (mkContextWithErrorFormatter context)
208 formatError = urlParseErrorFormatter $ getContextEntry (mkContextWithErrorFormatter context)
209+ hint = CaptureHint (T.pack $ symbolVal $ Proxy @capture) (typeRep (Proxy :: Proxy a))
200-- | If you use 'CaptureAll' in one of the endpoints for your API,
211-- | If you use 'CaptureAll' in one of the endpoints for your API,
201-- this automatically requires your server-side handler to be a
212-- this automatically requires your server-side handler to be a
@@ -214,7 +225,7 @@ instance (KnownSymbol capture, FromHttpApiData a
214-- > server = getSourceFile
225-- > server = getSourceFile
215-- > where getSourceFile :: [Text] -> Handler Book
226-- > where getSourceFile :: [Text] -> Handler Book
216-- > getSourceFile pathSegments = ...
227-- > getSourceFile pathSegments = ...
217−instance (KnownSymbol capture, FromHttpApiData a
228+instance (KnownSymbol capture, FromHttpApiData a, Typeable a
218 , HasServer api context
229 , HasServer api context
219 , HasContextEntry (MkContextWithErrorFormatter context) ErrorFormatters
230 , HasContextEntry (MkContextWithErrorFormatter context) ErrorFormatters
@@ -226,7 +237,7 @@ instance (KnownSymbol capture, FromHttpApiData a
226 hoistServerWithContext _ pc nt s = hoistServerWithContext (Proxy :: Proxy api) pc nt . s
237 hoistServerWithContext _ pc nt s = hoistServerWithContext (Proxy :: Proxy api) pc nt . s
228 route Proxy context d =
239 route Proxy context d =
229− CaptureAllRouter $
240+ CaptureAllRouter [hint] $
230 route (Proxy :: Proxy api)
241 route (Proxy :: Proxy api)
232 (addCapture d $ \ txts -> withRequest $ \ request ->
243 (addCapture d $ \ txts -> withRequest $ \ request ->
@@ -237,6 +248,43 @@ instance (KnownSymbol capture, FromHttpApiData a
238 rep = typeRep (Proxy :: Proxy CaptureAll)
249 rep = typeRep (Proxy :: Proxy CaptureAll)
239 formatError = urlParseErrorFormatter $ getContextEntry (mkContextWithErrorFormatter context)
250 formatError = urlParseErrorFormatter $ getContextEntry (mkContextWithErrorFormatter context)
251+ hint = CaptureHint (T.pack $ symbolVal $ Proxy @capture) (typeRep (Proxy :: Proxy [a]))
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.
259+-- The creation and destruction are done using a 'Data.Acquire.Acquire'
260+-- provided via server 'Context'.
264+-- > type MyApi = WithResource Handle :> "writeToFile" :> Post '[JSON] NoContent
266+-- > server :: Server MyApi
267+-- > server = writeToFile
268+-- > where writeToFile :: (ReleaseKey, Handle) -> Handler NoContent
269+-- > writeToFile (_, h) = hPutStrLn h "message"
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
275+instance (HasServer api ctx, HasContextEntry ctx (Acquire a))
276+ => HasServer (WithResource a :> api) ctx where
278+ type ServerT (WithResource a :> api) m = (ReleaseKey, a) -> ServerT api m
280+ hoistServerWithContext _ pc nt s = hoistServerWithContext (Proxy @api) pc nt . s
282+ route Proxy context d = route (Proxy @api) context (d `addParameterCheck` allocateResource)
284+ allocateResource :: DelayedIO (ReleaseKey, a)
285+ allocateResource = DelayedIO $ lift $ allocateAcquire (getContextEntry context)
241allowedMethodHead :: Method -> Request -> Bool
289allowedMethodHead :: Method -> Request -> Bool
242allowedMethodHead method request = method == methodGet && requestMethod request == methodHead
290allowedMethodHead method request = method == methodGet && requestMethod request == methodHead
@@ -605,6 +653,35 @@ instance HasServer Raw context where
605 Fail a -> respond $ Fail a
653 Fail a -> respond $ Fail a
606 FailFatal e -> respond $ FailFatal e
654 FailFatal e -> respond $ FailFatal e
656+-- | Just pass the request to the underlying application and serve its response.
660+-- > type MyApi = "images" :> Raw
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
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)) >>=
677+ Left e -> respond' $ FailFatal e
679+ Fail e -> respond' $ Fail e
680+ FailFatal e -> respond' $ FailFatal e
682+ hoistServerWithContext _ _ f srvM = \req respond -> f (srvM req respond)
608-- | If you use 'ReqBody' in one of the endpoints for your API,
685-- | If you use 'ReqBody' in one of the endpoints for your API,
609-- this automatically requires your server-side handler to be a function
686-- this automatically requires your server-side handler to be a function
610-- that takes an argument of the type specified by 'ReqBody'.
687-- that takes an argument of the type specified by 'ReqBody'.
@@ -814,38 +891,19 @@ instance (HasContextEntry context (NamedContext name subContext), HasServer subA
814 hoistServerWithContext _ _ nt s = hoistServerWithContext (Proxy :: Proxy subApi) (Proxy :: Proxy subContext) nt s
891 hoistServerWithContext _ _ nt s = hoistServerWithContext (Proxy :: Proxy subApi) (Proxy :: Proxy subContext) nt s
816-------------------------------------------------------------------------------
893-------------------------------------------------------------------------------
817−-- TypeError helpers
894+-- Custom type errors
818-------------------------------------------------------------------------------
895-------------------------------------------------------------------------------
820−-- | This instance catches mistakes when there are non-saturated
897+-- Erroring instance for 'HasServer' when a combinator is not fully applied
821−-- type applications on LHS of ':>'.
898+instance TypeError (PartialApplication
822−--
899+#if __GLASGOW_HASKELL__ >= 904
823−-- >>> serve (Proxy :: Proxy (Capture "foo" :> Get '[JSON] Int)) (error "...")
900+ @(Type -> [Type] -> Constraint)
825−-- ...Expected something of kind Symbol or *, got: k -> l on the LHS of ':>'.
826−-- ...Maybe you haven't applied enough arguments to
827−-- ...Capture' '[] "foo"
830−-- >>> undefined :: Server (Capture "foo" :> Get '[JSON] Int)
832−-- ...Expected something of kind Symbol or *, got: k -> l on the LHS of ':>'.
833−-- ...Maybe you haven't applied enough arguments to
834−-- ...Capture' '[] "foo"
837−instance TypeError (HasServerArrowKindError arr) => HasServer ((arr :: k -> l) :> api) context
902+ HasServer arr) => HasServer ((arr :: a -> b) :> sub) context
839− type ServerT (arr :> api) m = TypeError (HasServerArrowKindError arr)
904+ type ServerT (arr :> sub) _ = TypeError (PartialApplication (HasServer :: * -> [*] -> Constraint) arr)
840− -- it doesn't really matter what sub route we peak
905+ route = error "unreachable"
841− route _ _ _ = error "servant-server panic: impossible happened in HasServer (arr :> api)"
842− hoistServerWithContext _ _ _ = id
906+ hoistServerWithContext _ _ _ _ = error "unreachable"
844−-- Cannot have TypeError here, otherwise use of this symbol will error :)
845−type HasServerArrowKindError arr =
846− 'Text "Expected something of kind Symbol or *, got: k -> l on the LHS of ':>'."
847− ':$$: 'Text "Maybe you haven't applied enough arguments to"
850-- | This instance prevents from accidentally using '->' instead of ':>'
908-- | This instance prevents from accidentally using '->' instead of ':>'
@@ -880,6 +938,19 @@ type HasServerArrowTypeError a b =
880 ':$$: 'Text "and"
938 ':$$: 'Text "and"
881 ':$$: 'ShowType b
939 ':$$: 'ShowType b
941+-- Erroring instances for 'HasServer' for unknown API combinators
943+-- XXX: This omits the @context@ parameter, e.g.:
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)
950+ HasServer ty) => HasServer (ty :> sub) context
952+instance {-# OVERLAPPABLE #-} TypeError (NoInstanceFor (HasServer api context)) => HasServer api context
883-- | Ignore @'Fragment'@ in server handlers.
954-- | Ignore @'Fragment'@ in server handlers.
884-- See <https://ietf.org/rfc/rfc2616.html#section-15.1.3> for more details.
955-- See <https://ietf.org/rfc/rfc2616.html#section-15.1.3> for more details.