git.iko.soy/typeable/servant
on master help sign in
Merge pull request #753 from DaveCTurner/issue-753
1ccb0efOleg Grenrus
doc/tutorial/ApiType.lhs+24−2
@@ -321,11 +321,33 @@ data BasicAuth (realm :: Symbol) (userData :: *)
321Which is used like so:
321Which is used like so:
322
322
323``` haskell
323``` haskell
324−type ProtectedAPI12
324+type ProtectedAPI11
325 = UserAPI -- this is public
325 = UserAPI -- this is public
326 :<|> BasicAuth "my-realm" User :> UserAPI2 -- this is protected by auth
326 :<|> BasicAuth "my-realm" User :> UserAPI2 -- this is protected by auth
327```
327```
328
328
329+### Empty APIs
330+
331+Sometimes it is useful to be able to generalise an API over the type of some
332+part of it:
333+
334+``` haskell
335+type UserAPI12 innerAPI
336+ = UserAPI -- this is the fixed bit of the API
337+ :<|> "inner" :> innerAPI -- this lets us put various other APIs under /inner
338+```
339+
340+If there is a case where you do not have anything extra to serve, you can use
341+the `EmptyAPI` combinator to indicate this:
342+
343+``` haskell
344+type UserAPI12Alone = UserAPI12 EmptyAPI
345+```
346+
347+This also works well as a placeholder for unfinished parts of an API while it
348+is under development, for when you know that there should be _something_ there
349+but you don't yet know what. Think of it as similar to the unit type `()`.
350+
329### Interoperability with `wai`: `Raw`
351### Interoperability with `wai`: `Raw`
330
352
331Finally, we also include a combinator named `Raw` that provides an escape hatch
353Finally, we also include a combinator named `Raw` that provides an escape hatch
@@ -334,7 +356,7 @@ you want to plug a [wai `Application`](http://hackage.haskell.org/package/wai)
334into your webservice:
356into your webservice:
335
357
336``` haskell
358``` haskell
337−type UserAPI11 = "users" :> Get '[JSON] [User]
359+type UserAPI13 = "users" :> Get '[JSON] [User]
338 -- a /users endpoint
360 -- a /users endpoint
339
361
340 :<|> Raw
362 :<|> Raw
doc/tutorial/Client.lhs+13
@@ -93,6 +93,19 @@ position :<|> hello :<|> marketing = client api
93
93
94`client api` returns client functions for our _entire_ API, combined with `:<|>`, which we can pattern match on as above. You could say `client` "calculates" the correct type and number of client functions for the API type it is given (via a `Proxy`), as well as their implementations.
94`client api` returns client functions for our _entire_ API, combined with `:<|>`, which we can pattern match on as above. You could say `client` "calculates" the correct type and number of client functions for the API type it is given (via a `Proxy`), as well as their implementations.
95
95
96+If you have an `EmptyAPI` in your API, servant-client will hand you a value of
97+type `EmptyClient` in the corresponding slot, where `data EmptyClient =
98+EmptyClient`, as a way to indicate that you can't do anything useful with it.
99+
100+``` haskell ignore
101+type API' = API :<|> EmptyAPI
102+
103+api' :: Proxy API'
104+api' = Proxy
105+
106+(position' :<|> hello' :<|> marketing') :<|> EmptyClient = client api'
107+```
108+
96``` haskell ignore
109``` haskell ignore
97-- | URI scheme to use
110-- | URI scheme to use
98data Scheme =
111data Scheme =
doc/tutorial/Docs.lhs+2
@@ -89,6 +89,8 @@ instance ToSample Email where
89```
89```
90
90
91Types that are used as request or response bodies have to instantiate the `ToSample` typeclass which lets you specify one or more examples of values. `Capture`s and `QueryParam`s have to instantiate their respective `ToCapture` and `ToParam` classes and provide a name and some information about the concrete meaning of that argument, as illustrated in the code above.
91Types that are used as request or response bodies have to instantiate the `ToSample` typeclass which lets you specify one or more examples of values. `Capture`s and `QueryParam`s have to instantiate their respective `ToCapture` and `ToParam` classes and provide a name and some information about the concrete meaning of that argument, as illustrated in the code above.
92+The `EmptyAPI` combinator needs no special treatment as it generates no
93+documentation: an empty API has no endpoints to document.
92
94
93With all of this, we can derive docs for our API.
95With all of this, we can derive docs for our API.
94
96
doc/tutorial/Javascript.lhs+3
@@ -149,6 +149,9 @@ Why two different API types, proxies and servers though? Simply because we
149don't want to generate javascript functions for the `Raw` part of our API type,
149don't want to generate javascript functions for the `Raw` part of our API type,
150so we need a `Proxy` for our API type `API'` without its `Raw` endpoint.
150so we need a `Proxy` for our API type `API'` without its `Raw` endpoint.
151
151
152+The `EmptyAPI` combinator needs no special treatment as it generates no
153+Javascript functions: an empty API has no endpoints to access.
154+
152Very similarly to how one can derive haskell functions, we can derive the
155Very similarly to how one can derive haskell functions, we can derive the
153javascript with just a simple function call to `jsForAPI` from
156javascript with just a simple function call to `jsForAPI` from
154`Servant.JS`.
157`Servant.JS`.
doc/tutorial/Server.lhs+11
@@ -1020,6 +1020,17 @@ serverFor = error "..."
1020-- or the mailing list if you get stuck!
1020-- or the mailing list if you get stuck!
1021```
1021```
1022
1022
1023+When your API contains the `EmptyAPI` combinator, you'll want to use
1024+`emptyServer` in the corresponding slot for your server, which will simply fail
1025+with 404 whenever a request reaches it:
1026+
1027+``` haskell
1028+type CombinedAPI2 = API :<|> "empty" :> EmptyAPI
1029+
1030+server11 :: Server CombinedAPI2
1031+server11 = server3 :<|> emptyServer
1032+```
1033+
1023## Using another monad for your handlers
1034## Using another monad for your handlers
1024
1035
1025Remember how `Server` turns combinators for HTTP methods into `Handler`? Well, actually, there's more to that. `Server` is actually a
1036Remember how `Server` turns combinators for HTTP methods into `Handler`? Well, actually, there's more to that. `Server` is actually a
servant-client/src/Servant/Client.hs+18
@@ -24,6 +24,7 @@ module Servant.Client
24 , ClientEnv (ClientEnv)
24 , ClientEnv (ClientEnv)
25 , mkAuthenticateReq
25 , mkAuthenticateReq
26 , ServantError(..)
26 , ServantError(..)
27+ , EmptyClient(..)
27 , module Servant.Common.BaseUrl
28 , module Servant.Common.BaseUrl
28 ) where
29 ) where
29
30
@@ -88,6 +89,23 @@ instance (HasClient a, HasClient b) => HasClient (a :<|> b) where
88 clientWithRoute (Proxy :: Proxy a) req :<|>
89 clientWithRoute (Proxy :: Proxy a) req :<|>
89 clientWithRoute (Proxy :: Proxy b) req
90 clientWithRoute (Proxy :: Proxy b) req
90
91
92+-- | Singleton type representing a client for an empty API.
93+data EmptyClient = EmptyClient deriving (Eq, Show, Bounded, Enum)
94+
95+-- | The client for 'EmptyAPI' is simply 'EmptyClient'.
96+--
97+-- > type MyAPI = "books" :> Get '[JSON] [Book] -- GET /books
98+-- > :<|> "nothing" :> EmptyAPI
99+-- >
100+-- > myApi :: Proxy MyApi
101+-- > myApi = Proxy
102+-- >
103+-- > getAllBooks :: ClientM [Book]
104+-- > (getAllBooks :<|> EmptyClient) = client myApi
105+instance HasClient EmptyAPI where
106+ type Client EmptyAPI = EmptyClient
107+ clientWithRoute Proxy _ = EmptyClient
108+
91-- | If you use a 'Capture' in one of your endpoints in your API,
109-- | If you use a 'Capture' in one of your endpoints in your API,
92-- the corresponding querying function will automatically take
110-- the corresponding querying function will automatically take
93-- an additional argument of the type specified by your 'Capture'.
111-- an additional argument of the type specified by your 'Capture'.
servant-client/test/Servant/ClientSpec.hs+6−2
@@ -111,6 +111,8 @@ type Api =
111 Get '[JSON] (String, Maybe Int, Bool, [(String, [Rational])])
111 Get '[JSON] (String, Maybe Int, Bool, [(String, [Rational])])
112 :<|> "headers" :> Get '[JSON] (Headers TestHeaders Bool)
112 :<|> "headers" :> Get '[JSON] (Headers TestHeaders Bool)
113 :<|> "deleteContentType" :> DeleteNoContent '[JSON] NoContent
113 :<|> "deleteContentType" :> DeleteNoContent '[JSON] NoContent
114+ :<|> "empty" :> EmptyAPI
115+
114api :: Proxy Api
116api :: Proxy Api
115api = Proxy
117api = Proxy
116
118
@@ -130,6 +132,7 @@ getMultiple :: String -> Maybe Int -> Bool -> [(String, [Rational])]
130 -> SCR.ClientM (String, Maybe Int, Bool, [(String, [Rational])])
132 -> SCR.ClientM (String, Maybe Int, Bool, [(String, [Rational])])
131getRespHeaders :: SCR.ClientM (Headers TestHeaders Bool)
133getRespHeaders :: SCR.ClientM (Headers TestHeaders Bool)
132getDeleteContentType :: SCR.ClientM NoContent
134getDeleteContentType :: SCR.ClientM NoContent
135+
133getGet
136getGet
134 :<|> getDeleteEmpty
137 :<|> getDeleteEmpty
135 :<|> getCapture
138 :<|> getCapture
@@ -142,7 +145,8 @@ getGet
142 :<|> getRawFailure
145 :<|> getRawFailure
143 :<|> getMultiple
146 :<|> getMultiple
144 :<|> getRespHeaders
147 :<|> getRespHeaders
145− :<|> getDeleteContentType = client api
148+ :<|> getDeleteContentType
149+ :<|> EmptyClient = client api
146
150
147server :: Application
151server :: Application
148server = serve api (
152server = serve api (
@@ -162,7 +166,7 @@ server = serve api (
162 :<|> (\ a b c d -> return (a, b, c, d))
166 :<|> (\ a b c d -> return (a, b, c, d))
163 :<|> (return $ addHeader 1729 $ addHeader "eg2" True)
167 :<|> (return $ addHeader 1729 $ addHeader "eg2" True)
164 :<|> return NoContent
168 :<|> return NoContent
165− )
169+ :<|> emptyServer)
166
170
167
171
168type FailApi =
172type FailApi =
servant-docs/src/Servant/Docs/Internal.hs+4
@@ -683,6 +683,10 @@ instance OVERLAPPABLE_
683 p2 :: Proxy b
683 p2 :: Proxy b
684 p2 = Proxy
684 p2 = Proxy
685
685
686+-- | The generated docs for @'EmptyAPI'@ are empty.
687+instance HasDocs EmptyAPI where
688+ docsFor Proxy _ _ = emptyAPI
689+
686-- | @"books" :> 'Capture' "isbn" Text@ will appear as
690-- | @"books" :> 'Capture' "isbn" Text@ will appear as
687-- @/books/:isbn@ in the docs.
691-- @/books/:isbn@ in the docs.
688instance (KnownSymbol sym, ToCapture (Capture sym a), HasDocs api)
692instance (KnownSymbol sym, ToCapture (Capture sym a), HasDocs api)
servant-docs/test/Servant/DocsSpec.hs+4
@@ -104,6 +104,9 @@ spec = describe "Servant.Docs" $ do
104 it "contains request body samples" $
104 it "contains request body samples" $
105 md `shouldContain` "17"
105 md `shouldContain` "17"
106
106
107+ it "does not generate any docs mentioning the 'empty-api' path" $
108+ md `shouldNotContain` "empty-api"
109+
107
110
108-- * APIs
111-- * APIs
109
112
@@ -128,6 +131,7 @@ instance MimeRender PlainText Int where
128type TestApi1 = Get '[JSON, PlainText] (Headers '[Header "Location" String] Int)
131type TestApi1 = Get '[JSON, PlainText] (Headers '[Header "Location" String] Int)
129 :<|> ReqBody '[JSON] String :> Post '[JSON] Datatype1
132 :<|> ReqBody '[JSON] String :> Post '[JSON] Datatype1
130 :<|> Header "X-Test" Int :> Put '[JSON] Int
133 :<|> Header "X-Test" Int :> Put '[JSON] Int
134+ :<|> "empty-api" :> EmptyAPI
131
135
132data TT = TT1 | TT2 deriving (Show, Eq)
136data TT = TT1 | TT2 deriving (Show, Eq)
133data UT = UT1 | UT2 deriving (Show, Eq)
137data UT = UT1 | UT2 deriving (Show, Eq)
servant-foreign/src/Servant/Foreign/Internal.hs+10
@@ -187,6 +187,13 @@ instance (HasForeign lang ftype a, HasForeign lang ftype b)
187 foreignFor lang ftype (Proxy :: Proxy a) req
187 foreignFor lang ftype (Proxy :: Proxy a) req
188 :<|> foreignFor lang ftype (Proxy :: Proxy b) req
188 :<|> foreignFor lang ftype (Proxy :: Proxy b) req
189
189
190+data EmptyForeignAPI = EmptyForeignAPI
191+
192+instance HasForeign lang ftype EmptyAPI where
193+ type Foreign ftype EmptyAPI = EmptyForeignAPI
194+
195+ foreignFor Proxy Proxy Proxy _ = EmptyForeignAPI
196+
190instance (KnownSymbol sym, HasForeignType lang ftype t, HasForeign lang ftype api)
197instance (KnownSymbol sym, HasForeignType lang ftype t, HasForeign lang ftype api)
191 => HasForeign lang ftype (Capture sym t :> api) where
198 => HasForeign lang ftype (Capture sym t :> api) where
192 type Foreign ftype (Capture sym t :> api) = Foreign ftype api
199 type Foreign ftype (Capture sym t :> api) = Foreign ftype api
@@ -349,6 +356,9 @@ instance HasForeign lang ftype api
349class GenerateList ftype reqs where
356class GenerateList ftype reqs where
350 generateList :: reqs -> [Req ftype]
357 generateList :: reqs -> [Req ftype]
351
358
359+instance GenerateList ftype EmptyForeignAPI where
360+ generateList _ = []
361+
352instance GenerateList ftype (Req ftype) where
362instance GenerateList ftype (Req ftype) where
353 generateList r = [r]
363 generateList r = [r]
354
364
servant-foreign/test/Servant/ForeignSpec.hs+2−1
@@ -57,13 +57,14 @@ type TestApi
57 :<|> "test" :> QueryParams "params" Int :> ReqBody '[JSON] String :> Put '[JSON] NoContent
57 :<|> "test" :> QueryParams "params" Int :> ReqBody '[JSON] String :> Put '[JSON] NoContent
58 :<|> "test" :> Capture "id" Int :> Delete '[JSON] NoContent
58 :<|> "test" :> Capture "id" Int :> Delete '[JSON] NoContent
59 :<|> "test" :> CaptureAll "ids" Int :> Get '[JSON] [Int]
59 :<|> "test" :> CaptureAll "ids" Int :> Get '[JSON] [Int]
60+ :<|> "test" :> EmptyAPI
60
61
61testApi :: [Req String]
62testApi :: [Req String]
62testApi = listFromAPI (Proxy :: Proxy LangX) (Proxy :: Proxy String) (Proxy :: Proxy TestApi)
63testApi = listFromAPI (Proxy :: Proxy LangX) (Proxy :: Proxy String) (Proxy :: Proxy TestApi)
63
64
64listFromAPISpec :: Spec
65listFromAPISpec :: Spec
65listFromAPISpec = describe "listFromAPI" $ do
66listFromAPISpec = describe "listFromAPI" $ do
66− it "generates 4 endpoints for TestApi" $ do
67+ it "generates 5 endpoints for TestApi" $ do
67 length testApi `shouldBe` 5
68 length testApi `shouldBe` 5
68
69
69 let [getReq, postReq, putReq, deleteReq, captureAllReq] = testApi
70 let [getReq, postReq, putReq, deleteReq, captureAllReq] = testApi
servant-server/src/Servant/Server.hs+4−2
@@ -17,6 +17,8 @@ module Servant.Server
17 , -- * Handlers for all standard combinators
17 , -- * Handlers for all standard combinators
18 HasServer(..)
18 HasServer(..)
19 , Server
19 , Server
20+ , EmptyServer
21+ , emptyServer
20 , Handler (..)
22 , Handler (..)
21 , runHandler
23 , runHandler
22
24
@@ -219,8 +221,8 @@ layoutWithContext p context =
219--
221--
220-- >>> import Control.Monad.Reader
222-- >>> import Control.Monad.Reader
221-- >>> import qualified Control.Category as C
223-- >>> import qualified Control.Category as C
222−-- >>> type ReaderAPI = "ep1" :> Get '[JSON] Int :<|> "ep2" :> Get '[JSON] String :<|> Raw
224+-- >>> type ReaderAPI = "ep1" :> Get '[JSON] Int :<|> "ep2" :> Get '[JSON] String :<|> Raw :<|> EmptyAPI
223−-- >>> let readerServer = return 1797 :<|> ask :<|> Tagged (error "raw server") :: ServerT ReaderAPI (Reader String)
225+-- >>> let readerServer = return 1797 :<|> ask :<|> Tagged (error "raw server") :<|> emptyServer :: ServerT ReaderAPI (Reader String)
224-- >>> let nt = generalizeNat C.. (runReaderTNat "hi") :: Reader String :~> Handler
226-- >>> let nt = generalizeNat C.. (runReaderTNat "hi") :: Reader String :~> Handler
225-- >>> let mainServer = enter nt readerServer :: Server ReaderAPI
227-- >>> let mainServer = enter nt readerServer :: Server ReaderAPI
226--
228--
servant-server/src/Servant/Server/Internal.hs+21−2
@@ -1,6 +1,7 @@
1{-# LANGUAGE CPP #-}
1{-# LANGUAGE CPP #-}
2{-# LANGUAGE ConstraintKinds #-}
2{-# LANGUAGE ConstraintKinds #-}
3{-# LANGUAGE DataKinds #-}
3{-# LANGUAGE DataKinds #-}
4+{-# LANGUAGE DeriveDataTypeable #-}
4{-# LANGUAGE FlexibleContexts #-}
5{-# LANGUAGE FlexibleContexts #-}
5{-# LANGUAGE FlexibleInstances #-}
6{-# LANGUAGE FlexibleInstances #-}
6{-# LANGUAGE GeneralizedNewtypeDeriving #-}
7{-# LANGUAGE GeneralizedNewtypeDeriving #-}
@@ -32,7 +33,7 @@ import Data.Maybe (fromMaybe, mapMaybe)
32import Data.Either (partitionEithers)
33import Data.Either (partitionEithers)
33import Data.String (fromString)
34import Data.String (fromString)
34import Data.String.Conversions (cs, (<>))
35import Data.String.Conversions (cs, (<>))
35−import Data.Tagged (Tagged, untag)
36+import Data.Tagged (Tagged(..), untag)
36import qualified Data.Text as T
37import qualified Data.Text as T
37import Data.Typeable
38import Data.Typeable
38import GHC.TypeLits (KnownNat, KnownSymbol, natVal,
39import GHC.TypeLits (KnownNat, KnownSymbol, natVal,
@@ -52,7 +53,7 @@ import Web.HttpApiData (FromHttpApiData, parseHeader,
52 parseUrlPieceMaybe,
53 parseUrlPieceMaybe,
53 parseUrlPieces)
54 parseUrlPieces)
54import Servant.API ((:<|>) (..), (:>), BasicAuth, Capture,
55import Servant.API ((:<|>) (..), (:>), BasicAuth, Capture,
55− CaptureAll, Verb,
56+ CaptureAll, Verb, EmptyAPI,
56 ReflectMethod(reflectMethod),
57 ReflectMethod(reflectMethod),
57 IsSecure(..), Header, QueryFlag,
58 IsSecure(..), Header, QueryFlag,
58 QueryParam, QueryParams, Raw,
59 QueryParam, QueryParams, Raw,
@@ -532,6 +533,24 @@ instance HasServer api context => HasServer (HttpVersion :> api) context where
532 route Proxy context subserver =
533 route Proxy context subserver =
533 route (Proxy :: Proxy api) context (passToServer subserver httpVersion)
534 route (Proxy :: Proxy api) context (passToServer subserver httpVersion)
534
535
536+-- | Singleton type representing a server that serves an empty API.
537+data EmptyServer = EmptyServer deriving (Typeable, Eq, Show, Bounded, Enum)
538+
539+-- | Server for `EmptyAPI`
540+emptyServer :: ServerT EmptyAPI m
541+emptyServer = Tagged EmptyServer
542+
543+-- | The server for an `EmptyAPI` is `emptyAPIServer`.
544+--
545+-- > type MyApi = "nothing" :> EmptyApi
546+-- >
547+-- > server :: Server MyApi
548+-- > server = emptyAPIServer
549+instance HasServer EmptyAPI context where
550+ type ServerT EmptyAPI m = Tagged m EmptyServer
551+
552+ route Proxy _ _ = StaticRouter mempty mempty
553+
535-- | Basic Authentication
554-- | Basic Authentication
536instance ( KnownSymbol realm
555instance ( KnownSymbol realm
537 , HasServer api context
556 , HasServer api context
servant-server/test/Servant/ServerSpec.hs+7−2
@@ -42,14 +42,14 @@ import Servant.API ((:<|>) (..), (:>), AuthProtect,
42 Headers, HttpVersion,
42 Headers, HttpVersion,
43 IsSecure (..), JSON,
43 IsSecure (..), JSON,
44 NoContent (..), Patch, PlainText,
44 NoContent (..), Patch, PlainText,
45− Post, Put,
45+ Post, Put, EmptyAPI,
46 QueryFlag, QueryParam, QueryParams,
46 QueryFlag, QueryParam, QueryParams,
47 Raw, RemoteHost, ReqBody,
47 Raw, RemoteHost, ReqBody,
48 StdMethod (..), Verb, addHeader)
48 StdMethod (..), Verb, addHeader)
49import Servant.API.Internal.Test.ComprehensiveAPI
49import Servant.API.Internal.Test.ComprehensiveAPI
50import Servant.Server (Server, Handler, Tagged (..), err401, err403,
50import Servant.Server (Server, Handler, Tagged (..), err401, err403,
51 err404, serve, serveWithContext,
51 err404, serve, serveWithContext,
52− Context((:.), EmptyContext))
52+ Context((:.), EmptyContext), emptyServer)
53import Test.Hspec (Spec, context, describe, it,
53import Test.Hspec (Spec, context, describe, it,
54 shouldBe, shouldContain)
54 shouldBe, shouldContain)
55import qualified Test.Hspec.Wai as THW
55import qualified Test.Hspec.Wai as THW
@@ -609,6 +609,7 @@ type MiscCombinatorsAPI
609 = "version" :> HttpVersion :> Get '[JSON] String
609 = "version" :> HttpVersion :> Get '[JSON] String
610 :<|> "secure" :> IsSecure :> Get '[JSON] String
610 :<|> "secure" :> IsSecure :> Get '[JSON] String
611 :<|> "host" :> RemoteHost :> Get '[JSON] String
611 :<|> "host" :> RemoteHost :> Get '[JSON] String
612+ :<|> "empty" :> EmptyAPI
612
613
613miscApi :: Proxy MiscCombinatorsAPI
614miscApi :: Proxy MiscCombinatorsAPI
614miscApi = Proxy
615miscApi = Proxy
@@ -617,6 +618,7 @@ miscServ :: Server MiscCombinatorsAPI
617miscServ = versionHandler
618miscServ = versionHandler
618 :<|> secureHandler
619 :<|> secureHandler
619 :<|> hostHandler
620 :<|> hostHandler
621+ :<|> emptyServer
620
622
621 where versionHandler = return . show
623 where versionHandler = return . show
622 secureHandler Secure = return "secure"
624 secureHandler Secure = return "secure"
@@ -635,6 +637,9 @@ miscCombinatorSpec = with (return $ serve miscApi miscServ) $
635 it "Checks that hspec-wai issues request from 0.0.0.0" $
637 it "Checks that hspec-wai issues request from 0.0.0.0" $
636 go "/host" "\"0.0.0.0:0\""
638 go "/host" "\"0.0.0.0:0\""
637
639
640+ it "Doesn't serve anything from the empty API" $
641+ Test.Hspec.Wai.get "empty" `shouldRespondWith` 404
642+
638 where go path res = Test.Hspec.Wai.get path `shouldRespondWith` res
643 where go path res = Test.Hspec.Wai.get path `shouldRespondWith` res
639
644
640-- }}}
645-- }}}
servant/servant.cabal+1
@@ -36,6 +36,7 @@ library
36 Servant.API.BasicAuth
36 Servant.API.BasicAuth
37 Servant.API.Capture
37 Servant.API.Capture
38 Servant.API.ContentTypes
38 Servant.API.ContentTypes
39+ Servant.API.Empty
39 Servant.API.Experimental.Auth
40 Servant.API.Experimental.Auth
40 Servant.API.Header
41 Servant.API.Header
41 Servant.API.HttpVersion
42 Servant.API.HttpVersion
servant/src/Servant/API.hs+3
@@ -5,6 +5,8 @@ module Servant.API (
5 -- | Type-level combinator for expressing subrouting: @':>'@
5 -- | Type-level combinator for expressing subrouting: @':>'@
6 module Servant.API.Alternative,
6 module Servant.API.Alternative,
7 -- | Type-level combinator for alternative endpoints: @':<|>'@
7 -- | Type-level combinator for alternative endpoints: @':<|>'@
8+ module Servant.API.Empty,
9+ -- | Type-level combinator for an empty API: @'EmptyAPI'@
8
10
9 -- * Accessing information from the request
11 -- * Accessing information from the request
10 module Servant.API.Capture,
12 module Servant.API.Capture,
@@ -66,6 +68,7 @@ import Servant.API.ContentTypes (Accept (..), FormUrlEncoded,
66 MimeRender (..), NoContent (NoContent),
68 MimeRender (..), NoContent (NoContent),
67 MimeUnrender (..), OctetStream,
69 MimeUnrender (..), OctetStream,
68 PlainText)
70 PlainText)
71+import Servant.API.Empty (EmptyAPI (..))
69import Servant.API.Experimental.Auth (AuthProtect)
72import Servant.API.Experimental.Auth (AuthProtect)
70import Servant.API.Header (Header (..))
73import Servant.API.Header (Header (..))
71import Servant.API.HttpVersion (HttpVersion (..))
74import Servant.API.HttpVersion (HttpVersion (..))
servant/src/Servant/API/Empty.hsnew file+12
@@ -0,0 +1,12 @@
1+{-# LANGUAGE DeriveDataTypeable #-}
2+{-# OPTIONS_HADDOCK not-home #-}
3+module Servant.API.Empty(EmptyAPI(..)) where
4+
5+import Data.Typeable (Typeable)
6+import Prelude ()
7+import Prelude.Compat
8+
9+-- | An empty API: one which serves nothing. Morally speaking, this should be
10+-- the unit of ':<|>'. Implementors of interpretations of API types should
11+-- treat 'EmptyAPI' as close to the unit as possible.
12+data EmptyAPI = EmptyAPI deriving (Typeable, Eq, Show, Bounded, Enum)
servant/src/Servant/API/Internal/Test/ComprehensiveAPI.hs+2−1
@@ -37,7 +37,8 @@ type ComprehensiveAPIWithoutRaw =
37 Verb 'POST 204 '[JSON] NoContent :<|>
37 Verb 'POST 204 '[JSON] NoContent :<|>
38 Verb 'POST 204 '[JSON] Int :<|>
38 Verb 'POST 204 '[JSON] Int :<|>
39 WithNamedContext "foo" '[] GET :<|>
39 WithNamedContext "foo" '[] GET :<|>
40− CaptureAll "foo" Int :> GET
40+ CaptureAll "foo" Int :> GET :<|>
41+ EmptyAPI
41
42
42comprehensiveAPIWithoutRaw :: Proxy ComprehensiveAPIWithoutRaw
43comprehensiveAPIWithoutRaw :: Proxy ComprehensiveAPIWithoutRaw
43comprehensiveAPIWithoutRaw = Proxy
44comprehensiveAPIWithoutRaw = Proxy
servant/test/Servant/Utils/LinksSpec.hs+7
@@ -25,6 +25,7 @@ type TestApi =
25 :<|> "post" :> ReqBody '[JSON] 'True :> Post '[JSON] NoContent
25 :<|> "post" :> ReqBody '[JSON] 'True :> Post '[JSON] NoContent
26 :<|> "delete" :> Header "ponies" String :> Delete '[JSON] NoContent
26 :<|> "delete" :> Header "ponies" String :> Delete '[JSON] NoContent
27 :<|> "raw" :> Raw
27 :<|> "raw" :> Raw
28+ :<|> NoEndpoint
28
29
29
30
30apiLink :: (IsElem endpoint TestApi, HasLink endpoint)
31apiLink :: (IsElem endpoint TestApi, HasLink endpoint)
@@ -98,6 +99,11 @@ spec = describe "Servant.Utils.Links" $ do
98-- ...Could not deduce...
99-- ...Could not deduce...
99-- ...
100-- ...
100--
101--
102+-- >>> apiLink (Proxy :: Proxy NoEndpoint)
103+-- ...
104+-- ...No instance for...
105+-- ...
106+--
101-- sanity check
107-- sanity check
102-- >>> toUrlPiece $ apiLink (Proxy :: Proxy AllGood)
108-- >>> toUrlPiece $ apiLink (Proxy :: Proxy AllGood)
103-- "get"
109-- "get"
@@ -107,3 +113,4 @@ type WrongContentType = "get" :> Get '[OctetStream] NoContent
107type WrongMethod = "get" :> Post '[JSON] NoContent
113type WrongMethod = "get" :> Post '[JSON] NoContent
108type NotALink = "hello" :> ReqBody '[JSON] 'True :> Get '[JSON] Bool
114type NotALink = "hello" :> ReqBody '[JSON] 'True :> Get '[JSON] Bool
109type AllGood = "get" :> Get '[JSON] NoContent
115type AllGood = "get" :> Get '[JSON] NoContent
116+type NoEndpoint = "empty" :> EmptyAPI