on e9ebb0f help sign in
servant/src/Servant/API/Capture.hs
109 lines 3.7 kB history raw
1{-# LANGUAGE PolyKinds #-}
2{-# LANGUAGE TypeFamilies #-}
3{-# LANGUAGE TypeOperators #-}
4{-# LANGUAGE FlexibleContexts #-}
5{-# LANGUAGE FlexibleInstances #-}
6{-# LANGUAGE ScopedTypeVariables #-}
7module Servant.API.Capture (Capture) where
9import Data.Proxy
10import Data.Text
11import GHC.TypeLits
12import Network.Wai
13import Servant.API.Sub
14import Servant.Client
15import Servant.Common.Req
16import Servant.Common.Text
17import Servant.Docs
18import Servant.Server
20-- | Capture a value from the request path under a certain type @a@.
21--
22-- Example:
23--
24-- > -- GET /books/:isbn
25-- > type MyApi = "books" :> Capture "isbn" Text :> Get Book
26data Capture sym a
28captured :: FromText a => proxy (Capture sym a) -> Text -> Maybe a
29captured _ = fromText
31-- | If you use 'Capture' in one of the endpoints for your API,
32-- this automatically requires your server-side handler to be a function
33-- that takes an argument of the type specified by the 'Capture'.
34-- This lets servant worry about getting it from the URL and turning
35-- it into a value of the type you specify.
36--
37-- You can control how it'll be converted from 'Text' to your type
38-- by simply providing an instance of 'FromText' for your type.
39--
40-- Example:
41--
42-- > type MyApi = "books" ':>' 'Capture' "isbn" 'Text' ':>' 'Get' Book
43-- >
44-- > server :: 'Server' MyApi
45-- > server = getBook
46-- > where getBook :: Text -> 'EitherT' (Int, String) IO Book
47-- > getBook isbn = ...
48instance (KnownSymbol capture, FromText a, HasServer sublayout)
49 => HasServer (Capture capture a :> sublayout) where
51 type Server (Capture capture a :> sublayout) =
52 a -> Server sublayout
54 route Proxy subserver request respond = case pathInfo request of
55 (first : rest)
56 -> case captured captureProxy first of
57 Nothing -> respond $ failWith NotFound
58 Just v -> route (Proxy :: Proxy sublayout) (subserver v) request{
59 pathInfo = rest
60 } respond
61 _ -> respond $ failWith NotFound
63 where captureProxy = Proxy :: Proxy (Capture capture a)
65-- | If you use a 'Capture' in one of your endpoints in your API,
66-- the corresponding querying function will automatically take
67-- an additional argument of the type specified by your 'Capture'.
68-- That function will take care of inserting a textual representation
69-- of this value at the right place in the request path.
70--
71-- You can control how values for this type are turned into
72-- text by specifying a 'ToText' instance for your type.
73--
74-- Example:
75--
76-- > type MyApi = "books" ':>' 'Capture' "isbn" 'Text' ':>' 'Get' Book
77-- >
78-- > myApi :: Proxy MyApi
79-- > myApi = Proxy
80-- >
81-- > getBook :: Text -> BaseUrl -> EitherT String IO Book
82-- > getBook = client myApi
83-- > -- then you can just use "getBook" to query that endpoint
84instance (KnownSymbol capture, ToText a, HasClient sublayout)
85 => HasClient (Capture capture a :> sublayout) where
87 type Client (Capture capture a :> sublayout) =
88 a -> Client sublayout
90 clientWithRoute Proxy req val =
91 clientWithRoute (Proxy :: Proxy sublayout) $
92 appendToPath p req
94 where p = unpack (toText val)
96-- | @"books" :> 'Capture' "isbn" Text@ will appear as
97-- @/books/:isbn@ in the docs
98instance (KnownSymbol sym, ToCapture (Capture sym a), HasDocs sublayout)
99 => HasDocs (Capture sym a :> sublayout) where
101 docsFor Proxy (endpoint, action) =
102 docsFor sublayoutP (endpoint', action')
104 where sublayoutP = Proxy :: Proxy sublayout
105 captureP = Proxy :: Proxy (Capture sym a)
107 action' = over captures (|> toCapture captureP) action
108 endpoint' = over path (\p -> p++"/:"++symbolVal symP) endpoint
109 symP = Proxy :: Proxy sym