@@ -1,17 +1,22 @@
1−{-# LANGUAGE CPP #-}
1+{-# LANGUAGE ConstraintKinds #-}
2−{-# LANGUAGE ConstraintKinds #-}
2+{-# LANGUAGE CPP #-}
3−{-# LANGUAGE DataKinds #-}
3+{-# LANGUAGE DataKinds #-}
4−{-# LANGUAGE DeriveGeneric #-}
4+{-# LANGUAGE DeriveGeneric #-}
5−{-# LANGUAGE FlexibleContexts #-}
5+{-# LANGUAGE FlexibleContexts #-}
6−{-# LANGUAGE FlexibleInstances #-}
6+{-# LANGUAGE FlexibleInstances #-}
7+{-# LANGUAGE FunctionalDependencies #-}
8+{-# LANGUAGE MultiParamTypeClasses #-}
7−{-# LANGUAGE OverloadedStrings #-}
9+{-# LANGUAGE OverloadedStrings #-}
8−{-# LANGUAGE PolyKinds #-}
10+{-# LANGUAGE PolyKinds #-}
9−{-# LANGUAGE ScopedTypeVariables #-}
11+{-# LANGUAGE ScopedTypeVariables #-}
10−{-# LANGUAGE TemplateHaskell #-}
12+{-# LANGUAGE TemplateHaskell #-}
11−{-# LANGUAGE TupleSections #-}
13+{-# LANGUAGE TupleSections #-}
12−{-# LANGUAGE TypeFamilies #-}
14+{-# LANGUAGE TypeFamilies #-}
13−{-# LANGUAGE TypeOperators #-}
15+{-# LANGUAGE TypeOperators #-}
14−{-# LANGUAGE UndecidableInstances #-}
16+{-# LANGUAGE UndecidableInstances #-}
17+#if !MIN_VERSION_base(4,8,0)
18+{-# LANGUAGE OverlappingInstances #-}
15module Servant.Docs.Internal where
20module Servant.Docs.Internal where
17#if !MIN_VERSION_base(4,8,0)
22#if !MIN_VERSION_base(4,8,0)
@@ -19,6 +24,7 @@ import Control.Applicative
20import Control.Lens
25import Control.Lens
21import Data.ByteString.Lazy.Char8 (ByteString)
26import Data.ByteString.Lazy.Char8 (ByteString)
27+import qualified Data.CaseInsensitive as CI
22import Data.Hashable
28import Data.Hashable
23import Data.HashMap.Strict (HashMap)
29import Data.HashMap.Strict (HashMap)
24import Data.List
30import Data.List
@@ -26,6 +32,7 @@ import Data.Maybe
26import Data.Monoid
32import Data.Monoid
27import Data.Ord (comparing)
33import Data.Ord (comparing)
28import Data.Proxy
34import Data.Proxy
35+import Data.ByteString.Conversion (ToByteString, toByteString)
29import Data.String.Conversions
36import Data.String.Conversions
30import Data.Text (Text, pack, unpack)
37import Data.Text (Text, pack, unpack)
31import GHC.Exts (Constraint)
38import GHC.Exts (Constraint)
@@ -33,11 +40,13 @@ import GHC.Generics
33import GHC.TypeLits
40import GHC.TypeLits
34import Servant.API
41import Servant.API
35import Servant.API.ContentTypes
42import Servant.API.ContentTypes
43+import Servant.API.ResponseHeaders
36import Servant.Utils.Links
44import Servant.Utils.Links
38import qualified Data.HashMap.Strict as HM
46import qualified Data.HashMap.Strict as HM
39import qualified Data.Text as T
47import qualified Data.Text as T
40import qualified Network.HTTP.Media as M
48import qualified Network.HTTP.Media as M
49+import qualified Network.HTTP.Types as HTTP
42-- | Supported HTTP request methods
51-- | Supported HTTP request methods
43data Method = DocDELETE -- ^ the DELETE method
52data Method = DocDELETE -- ^ the DELETE method
@@ -191,9 +200,10 @@ data ParamKind = Normal | List | Flag
191-- > λ> defResponse & respStatus .~ 204 & respBody .~ [("If everything goes well", "{ \"status\": \"ok\" }")]
200-- > λ> defResponse & respStatus .~ 204 & respBody .~ [("If everything goes well", "{ \"status\": \"ok\" }")]
192-- > Response {_respStatus = 204, _respTypes = [], _respBody = [("If everything goes well", "{ \"status\": \"ok\" }")]}
201-- > Response {_respStatus = 204, _respTypes = [], _respBody = [("If everything goes well", "{ \"status\": \"ok\" }")]}
193data Response = Response
202data Response = Response
194− { _respStatus :: Int
203+ { _respStatus :: Int
195− , _respTypes :: [M.MediaType]
204+ , _respTypes :: [M.MediaType]
196− , _respBody :: [(Text, M.MediaType, ByteString)]
205+ , _respBody :: [(Text, M.MediaType, ByteString)]
206+ , _respHeaders :: [HTTP.Header]
197 } deriving (Eq, Ord, Show)
207 } deriving (Eq, Ord, Show)
199-- | Default response: status code 200, no response body.
209-- | Default response: status code 200, no response body.
@@ -205,7 +215,12 @@ data Response = Response
205-- > λ> defResponse & respStatus .~ 204 & respBody .~ Just "[]"
215-- > λ> defResponse & respStatus .~ 204 & respBody .~ Just "[]"
206-- > Response {_respStatus = 204, _respBody = Just "[]"}
216-- > Response {_respStatus = 204, _respBody = Just "[]"}
207defResponse :: Response
217defResponse :: Response
208−defResponse = Response 200 [] []
218+defResponse = Response
210-- | A datatype that represents everything that can happen
225-- | A datatype that represents everything that can happen
211-- at an endpoint, with its lenses:
226-- at an endpoint, with its lenses:
@@ -371,34 +386,53 @@ class HasDocs layout where
371-- 'toSample': it lets you specify different responses along with
386-- 'toSample': it lets you specify different responses along with
372-- some context (as 'Text') that explains when you're supposed to
387-- some context (as 'Text') that explains when you're supposed to
373-- get the corresponding response.
388-- get the corresponding response.
374−class ToSample a where
389+class ToSample a b | a -> b where
375 {-# MINIMAL (toSample | toSamples) #-}
390 {-# MINIMAL (toSample | toSamples) #-}
376− toSample :: Maybe a
391+ toSample :: Proxy a -> Maybe b
377− toSample = snd <$> listToMaybe samples
392+ toSample _ = snd <$> listToMaybe samples
378− where samples = toSamples :: [(Text, a)]
393+ where samples = toSamples (Proxy :: Proxy a)
395+ toSamples :: Proxy a -> [(Text, b)]
396+ toSamples _ = maybe [] (return . ("",)) s
397+ where s = toSample (Proxy :: Proxy a)
399+instance ToSample a b => ToSample (Headers ls a) b where
400+ toSample _ = toSample (Proxy :: Proxy a)
401+ toSamples _ = toSamples (Proxy :: Proxy a)
380− toSamples :: [(Text, a)]
404+class AllHeaderSamples ls where
381− toSamples = maybe [] (return . ("",)) s
405+ allHeaderToSample :: Proxy ls -> [HTTP.Header]
382− where s = toSample :: Maybe a
406+
407+instance AllHeaderSamples '[] where
408+ allHeaderToSample _ = []
410+instance (ToByteString l, AllHeaderSamples ls, ToSample l l, KnownSymbol h)
411+ => AllHeaderSamples (Header h l ': ls) where
412+ allHeaderToSample _ = (mkHeader (toSample (Proxy :: Proxy l))) :
413+ allHeaderToSample (Proxy :: Proxy ls)
414+ where headerName = CI.mk . cs $ symbolVal (Proxy :: Proxy h)
415+ mkHeader (Just x) = (headerName, cs $ toByteString x)
416+ mkHeader Nothing = (headerName, "<no header sample provided>")
384-- | Synthesise a sample value of a type, encoded in the specified media types.
418-- | Synthesise a sample value of a type, encoded in the specified media types.
385sampleByteString
419sampleByteString
386− :: forall ctypes a. (ToSample a, IsNonEmpty ctypes, AllMimeRender ctypes a)
420+ :: forall ctypes a b. (ToSample a b, IsNonEmpty ctypes, AllMimeRender ctypes b)
387 => Proxy ctypes
421 => Proxy ctypes
388 -> Proxy a
422 -> Proxy a
389 -> [(M.MediaType, ByteString)]
423 -> [(M.MediaType, ByteString)]
390sampleByteString ctypes@Proxy Proxy =
424sampleByteString ctypes@Proxy Proxy =
391− maybe [] (allMimeRender ctypes) (toSample :: Maybe a)
425+ maybe [] (allMimeRender ctypes) $ toSample (Proxy :: Proxy a)
393-- | Synthesise a list of sample values of a particular type, encoded in the
427-- | Synthesise a list of sample values of a particular type, encoded in the
394-- specified media types.
428-- specified media types.
395sampleByteStrings
429sampleByteStrings
396− :: forall ctypes a. (ToSample a, IsNonEmpty ctypes, AllMimeRender ctypes a)
430+ :: forall ctypes a b. (ToSample a b, IsNonEmpty ctypes, AllMimeRender ctypes b)
397 => Proxy ctypes
431 => Proxy ctypes
398 -> Proxy a
432 -> Proxy a
399 -> [(Text, M.MediaType, ByteString)]
433 -> [(Text, M.MediaType, ByteString)]
400sampleByteStrings ctypes@Proxy Proxy =
434sampleByteStrings ctypes@Proxy Proxy =
401− let samples = toSamples :: [(Text, a)]
435+ let samples = toSamples (Proxy :: Proxy a)
402 enc (t, s) = uncurry (t,,) <$> allMimeRender ctypes s
436 enc (t, s) = uncurry (t,,) <$> allMimeRender ctypes s
403 in concatMap enc samples
437 in concatMap enc samples
@@ -580,6 +614,7 @@ markdown api = unlines $
580 "#### Response:" :
614 "#### Response:" :
582 ("- Status code " ++ show (resp ^. respStatus)) :
616 ("- Status code " ++ show (resp ^. respStatus)) :
617+ ("- Headers: " ++ show (resp ^. respHeaders)) :
584 formatTypes (resp ^. respTypes) ++
619 formatTypes (resp ^. respTypes) ++
@@ -630,7 +665,7 @@ instance HasDocs Delete where
630 action' = action & response.respBody .~ []
665 action' = action & response.respBody .~ []
631 & response.respStatus .~ 204
666 & response.respStatus .~ 204
633−instance (ToSample a, IsNonEmpty cts, AllMimeRender cts a, SupportedTypes cts)
668+instance (ToSample a b, IsNonEmpty cts, AllMimeRender cts b, SupportedTypes cts)
634 => HasDocs (Get cts a) where
669 => HasDocs (Get cts a) where
635 docsFor Proxy (endpoint, action) =
670 docsFor Proxy (endpoint, action) =
636 single endpoint' action'
671 single endpoint' action'
@@ -641,6 +676,20 @@ instance (ToSample a, IsNonEmpty cts, AllMimeRender cts a, SupportedTypes cts)
641 t = Proxy :: Proxy cts
676 t = Proxy :: Proxy cts
642 p = Proxy :: Proxy a
677 p = Proxy :: Proxy a
679+instance (ToSample a b, IsNonEmpty cts, AllMimeRender cts b, SupportedTypes cts
680+ , AllHeaderSamples ls , GetHeaders (HList ls) )
681+ => HasDocs (Get cts (Headers ls a)) where
682+ docsFor Proxy (endpoint, action) =
683+ single endpoint' action'
685+ where hdrs = allHeaderToSample (Proxy :: Proxy ls)
686+ endpoint' = endpoint & method .~ DocGET
687+ action' = action & response.respBody .~ sampleByteStrings t p
688+ & response.respTypes .~ supportedTypes t
689+ & response.respHeaders .~ hdrs
690+ t = Proxy :: Proxy cts
691+ p = Proxy :: Proxy a
644instance (KnownSymbol sym, HasDocs sublayout)
693instance (KnownSymbol sym, HasDocs sublayout)
645 => HasDocs (Header sym a :> sublayout) where
694 => HasDocs (Header sym a :> sublayout) where
646 docsFor Proxy (endpoint, action) =
695 docsFor Proxy (endpoint, action) =
@@ -650,7 +699,7 @@ instance (KnownSymbol sym, HasDocs sublayout)
650 action' = over headers (|> headername) action
699 action' = over headers (|> headername) action
651 headername = pack $ symbolVal (Proxy :: Proxy sym)
700 headername = pack $ symbolVal (Proxy :: Proxy sym)
653−instance (ToSample a, IsNonEmpty cts, AllMimeRender cts a, SupportedTypes cts)
702+instance (ToSample a b, IsNonEmpty cts, AllMimeRender cts b, SupportedTypes cts)
654 => HasDocs (Post cts a) where
703 => HasDocs (Post cts a) where
655 docsFor Proxy (endpoint, action) =
704 docsFor Proxy (endpoint, action) =
656 single endpoint' action'
705 single endpoint' action'
@@ -662,7 +711,22 @@ instance (ToSample a, IsNonEmpty cts, AllMimeRender cts a, SupportedTypes cts)
662 t = Proxy :: Proxy cts
711 t = Proxy :: Proxy cts
663 p = Proxy :: Proxy a
712 p = Proxy :: Proxy a
665−instance (ToSample a, IsNonEmpty cts, AllMimeRender cts a, SupportedTypes cts)
714+instance (ToSample a b, IsNonEmpty cts, AllMimeRender cts b, SupportedTypes cts
715+ , AllHeaderSamples ls , GetHeaders (HList ls) )
716+ => HasDocs (Post cts (Headers ls a)) where
717+ docsFor Proxy (endpoint, action) =
718+ single endpoint' action'
720+ where hdrs = allHeaderToSample (Proxy :: Proxy ls)
721+ endpoint' = endpoint & method .~ DocPOST
722+ action' = action & response.respBody .~ sampleByteStrings t p
723+ & response.respTypes .~ supportedTypes t
724+ & response.respStatus .~ 201
725+ & response.respHeaders .~ hdrs
726+ t = Proxy :: Proxy cts
727+ p = Proxy :: Proxy a
729+instance (ToSample a b, IsNonEmpty cts, AllMimeRender cts b, SupportedTypes cts)
666 => HasDocs (Put cts a) where
730 => HasDocs (Put cts a) where
667 docsFor Proxy (endpoint, action) =
731 docsFor Proxy (endpoint, action) =
668 single endpoint' action'
732 single endpoint' action'
@@ -674,6 +738,21 @@ instance (ToSample a, IsNonEmpty cts, AllMimeRender cts a, SupportedTypes cts)
674 t = Proxy :: Proxy cts
738 t = Proxy :: Proxy cts
675 p = Proxy :: Proxy a
739 p = Proxy :: Proxy a
741+instance (ToSample a b, IsNonEmpty cts, AllMimeRender cts b, SupportedTypes cts
742+ , AllHeaderSamples ls , GetHeaders (HList ls) )
743+ => HasDocs (Put cts (Headers ls a)) where
744+ docsFor Proxy (endpoint, action) =
745+ single endpoint' action'
747+ where hdrs = allHeaderToSample (Proxy :: Proxy ls)
748+ endpoint' = endpoint & method .~ DocPUT
749+ action' = action & response.respBody .~ sampleByteStrings t p
750+ & response.respTypes .~ supportedTypes t
751+ & response.respStatus .~ 200
752+ & response.respHeaders .~ hdrs
753+ t = Proxy :: Proxy cts
754+ p = Proxy :: Proxy a
677instance (KnownSymbol sym, ToParam (QueryParam sym a), HasDocs sublayout)
756instance (KnownSymbol sym, ToParam (QueryParam sym a), HasDocs sublayout)
678 => HasDocs (QueryParam sym a :> sublayout) where
757 => HasDocs (QueryParam sym a :> sublayout) where
@@ -756,7 +835,8 @@ instance HasDocs Raw where
756-- example data. However, there's no reason to believe that the instances of
835-- example data. However, there's no reason to believe that the instances of
757-- 'AllMimeUnrender' and 'AllMimeRender' actually agree (or to suppose that
836-- 'AllMimeUnrender' and 'AllMimeRender' actually agree (or to suppose that
758-- both are even defined) for any particular type.
837-- both are even defined) for any particular type.
759−instance (ToSample a, IsNonEmpty cts, AllMimeRender cts a, HasDocs sublayout, SupportedTypes cts)
838+instance (ToSample a b, IsNonEmpty cts, AllMimeRender cts b, HasDocs sublayout
839+ , SupportedTypes cts)
760 => HasDocs (ReqBody cts a :> sublayout) where
840 => HasDocs (ReqBody cts a :> sublayout) where
762 docsFor Proxy (endpoint, action) =
842 docsFor Proxy (endpoint, action) =