@@ -1,17 +1,22 @@
1−{-# LANGUAGE CPP #-}
2−{-# LANGUAGE ConstraintKinds #-}
3−{-# LANGUAGE DataKinds #-}
4−{-# LANGUAGE DeriveGeneric #-}
5−{-# LANGUAGE FlexibleContexts #-}
6−{-# LANGUAGE FlexibleInstances #-}
7−{-# LANGUAGE OverloadedStrings #-}
8−{-# LANGUAGE PolyKinds #-}
9−{-# LANGUAGE ScopedTypeVariables #-}
10−{-# LANGUAGE TemplateHaskell #-}
11−{-# LANGUAGE TupleSections #-}
12−{-# LANGUAGE TypeFamilies #-}
13−{-# LANGUAGE TypeOperators #-}
14−{-# LANGUAGE UndecidableInstances #-}
1+{-# LANGUAGE ConstraintKinds #-}
2+{-# LANGUAGE CPP #-}
3+{-# LANGUAGE DataKinds #-}
4+{-# LANGUAGE DeriveGeneric #-}
5+{-# LANGUAGE FlexibleContexts #-}
6+{-# LANGUAGE FlexibleInstances #-}
7+{-# LANGUAGE FunctionalDependencies #-}
8+{-# LANGUAGE MultiParamTypeClasses #-}
9+{-# LANGUAGE OverloadedStrings #-}
10+{-# LANGUAGE PolyKinds #-}
11+{-# LANGUAGE ScopedTypeVariables #-}
12+{-# LANGUAGE TemplateHaskell #-}
13+{-# LANGUAGE TupleSections #-}
14+{-# LANGUAGE TypeFamilies #-}
15+{-# LANGUAGE TypeOperators #-}
16+{-# LANGUAGE UndecidableInstances #-}
17+#if !MIN_VERSION_base(4,8,0)
18+{-# LANGUAGE OverlappingInstances #-}
19+#endif
1520module Servant.Docs.Internal where
1621
1722#if !MIN_VERSION_base(4,8,0)
@@ -19,6 +24,7 @@ import Control.Applicative
1924#endif
2025import Control.Lens
2126import Data.ByteString.Lazy.Char8 (ByteString)
27+import qualified Data.CaseInsensitive as CI
2228import Data.Hashable
2329import Data.HashMap.Strict (HashMap)
2430import Data.List
@@ -26,6 +32,7 @@ import Data.Maybe
2632import Data.Monoid
2733import Data.Ord (comparing)
2834import Data.Proxy
35+import Data.ByteString.Conversion (ToByteString, toByteString)
2936import Data.String.Conversions
3037import Data.Text (Text, pack, unpack)
3138import GHC.Exts (Constraint)
@@ -33,11 +40,13 @@ import GHC.Generics
3340import GHC.TypeLits
3441import Servant.API
3542import Servant.API.ContentTypes
43+import Servant.API.ResponseHeaders
3644import Servant.Utils.Links
3745
3846import qualified Data.HashMap.Strict as HM
3947import qualified Data.Text as T
4048import qualified Network.HTTP.Media as M
49+import qualified Network.HTTP.Types as HTTP
4150
4251-- | Supported HTTP request methods
4352data Method = DocDELETE -- ^ the DELETE method
@@ -191,9 +200,10 @@ data ParamKind = Normal | List | Flag
191200-- > λ> defResponse & respStatus .~ 204 & respBody .~ [("If everything goes well", "{ \"status\": \"ok\" }")]
192201-- > Response {_respStatus = 204, _respTypes = [], _respBody = [("If everything goes well", "{ \"status\": \"ok\" }")]}
193202data Response = Response
194− { _respStatus :: Int
195− , _respTypes :: [M.MediaType]
196− , _respBody :: [(Text, M.MediaType, ByteString)]
203+ { _respStatus :: Int
204+ , _respTypes :: [M.MediaType]
205+ , _respBody :: [(Text, M.MediaType, ByteString)]
206+ , _respHeaders :: [HTTP.Header]
197207 } deriving (Eq, Ord, Show)
198208
199209-- | Default response: status code 200, no response body.
@@ -205,7 +215,12 @@ data Response = Response
205215-- > λ> defResponse & respStatus .~ 204 & respBody .~ Just "[]"
206216-- > Response {_respStatus = 204, _respBody = Just "[]"}
207217defResponse :: Response
208−defResponse = Response 200 [] []
218+defResponse = Response
219+ { _respStatus = 200
220+ , _respTypes = []
221+ , _respBody = []
222+ , _respHeaders = []
223+ }
209224
210225-- | A datatype that represents everything that can happen
211226-- at an endpoint, with its lenses:
@@ -371,34 +386,53 @@ class HasDocs layout where
371386-- 'toSample': it lets you specify different responses along with
372387-- some context (as 'Text') that explains when you're supposed to
373388-- get the corresponding response.
374−class ToSample a where
389+class ToSample a b | a -> b where
375390 {-# MINIMAL (toSample | toSamples) #-}
376− toSample :: Maybe a
377− toSample = snd <$> listToMaybe samples
378− where samples = toSamples :: [(Text, a)]
391+ toSample :: Proxy a -> Maybe b
392+ toSample _ = snd <$> listToMaybe samples
393+ where samples = toSamples (Proxy :: Proxy a)
394+
395+ toSamples :: Proxy a -> [(Text, b)]
396+ toSamples _ = maybe [] (return . ("",)) s
397+ where s = toSample (Proxy :: Proxy a)
398+
399+instance ToSample a b => ToSample (Headers ls a) b where
400+ toSample _ = toSample (Proxy :: Proxy a)
401+ toSamples _ = toSamples (Proxy :: Proxy a)
402+
379403
380− toSamples :: [(Text, a)]
381− toSamples = maybe [] (return . ("",)) s
382− where s = toSample :: Maybe a
404+class AllHeaderSamples ls where
405+ allHeaderToSample :: Proxy ls -> [HTTP.Header]
406+
407+instance AllHeaderSamples '[] where
408+ allHeaderToSample _ = []
409+
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>")
383417
384418-- | Synthesise a sample value of a type, encoded in the specified media types.
385419sampleByteString
386− :: forall ctypes a. (ToSample a, IsNonEmpty ctypes, AllMimeRender ctypes a)
420+ :: forall ctypes a b. (ToSample a b, IsNonEmpty ctypes, AllMimeRender ctypes b)
387421 => Proxy ctypes
388422 -> Proxy a
389423 -> [(M.MediaType, ByteString)]
390424sampleByteString ctypes@Proxy Proxy =
391− maybe [] (allMimeRender ctypes) (toSample :: Maybe a)
425+ maybe [] (allMimeRender ctypes) $ toSample (Proxy :: Proxy a)
392426
393427-- | Synthesise a list of sample values of a particular type, encoded in the
394428-- specified media types.
395429sampleByteStrings
396− :: forall ctypes a. (ToSample a, IsNonEmpty ctypes, AllMimeRender ctypes a)
430+ :: forall ctypes a b. (ToSample a b, IsNonEmpty ctypes, AllMimeRender ctypes b)
397431 => Proxy ctypes
398432 -> Proxy a
399433 -> [(Text, M.MediaType, ByteString)]
400434sampleByteStrings ctypes@Proxy Proxy =
401− let samples = toSamples :: [(Text, a)]
435+ let samples = toSamples (Proxy :: Proxy a)
402436 enc (t, s) = uncurry (t,,) <$> allMimeRender ctypes s
403437 in concatMap enc samples
404438
@@ -580,6 +614,7 @@ markdown api = unlines $
580614 "#### Response:" :
581615 "" :
582616 ("- Status code " ++ show (resp ^. respStatus)) :
617+ ("- Headers: " ++ show (resp ^. respHeaders)) :
583618 "" :
584619 formatTypes (resp ^. respTypes) ++
585620 bodies
@@ -630,7 +665,7 @@ instance HasDocs Delete where
630665 action' = action & response.respBody .~ []
631666 & response.respStatus .~ 204
632667
633−instance (ToSample a, IsNonEmpty cts, AllMimeRender cts a, SupportedTypes cts)
668+instance (ToSample a b, IsNonEmpty cts, AllMimeRender cts b, SupportedTypes cts)
634669 => HasDocs (Get cts a) where
635670 docsFor Proxy (endpoint, action) =
636671 single endpoint' action'
@@ -641,6 +676,20 @@ instance (ToSample a, IsNonEmpty cts, AllMimeRender cts a, SupportedTypes cts)
641676 t = Proxy :: Proxy cts
642677 p = Proxy :: Proxy a
643678
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'
684+
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
692+
644693instance (KnownSymbol sym, HasDocs sublayout)
645694 => HasDocs (Header sym a :> sublayout) where
646695 docsFor Proxy (endpoint, action) =
@@ -650,7 +699,7 @@ instance (KnownSymbol sym, HasDocs sublayout)
650699 action' = over headers (|> headername) action
651700 headername = pack $ symbolVal (Proxy :: Proxy sym)
652701
653−instance (ToSample a, IsNonEmpty cts, AllMimeRender cts a, SupportedTypes cts)
702+instance (ToSample a b, IsNonEmpty cts, AllMimeRender cts b, SupportedTypes cts)
654703 => HasDocs (Post cts a) where
655704 docsFor Proxy (endpoint, action) =
656705 single endpoint' action'
@@ -662,7 +711,22 @@ instance (ToSample a, IsNonEmpty cts, AllMimeRender cts a, SupportedTypes cts)
662711 t = Proxy :: Proxy cts
663712 p = Proxy :: Proxy a
664713
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'
719+
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
728+
729+instance (ToSample a b, IsNonEmpty cts, AllMimeRender cts b, SupportedTypes cts)
666730 => HasDocs (Put cts a) where
667731 docsFor Proxy (endpoint, action) =
668732 single endpoint' action'
@@ -674,6 +738,21 @@ instance (ToSample a, IsNonEmpty cts, AllMimeRender cts a, SupportedTypes cts)
674738 t = Proxy :: Proxy cts
675739 p = Proxy :: Proxy a
676740
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'
746+
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
755+
677756instance (KnownSymbol sym, ToParam (QueryParam sym a), HasDocs sublayout)
678757 => HasDocs (QueryParam sym a :> sublayout) where
679758
@@ -756,7 +835,8 @@ instance HasDocs Raw where
756835-- example data. However, there's no reason to believe that the instances of
757836-- 'AllMimeUnrender' and 'AllMimeRender' actually agree (or to suppose that
758837-- 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)
760840 => HasDocs (ReqBody cts a :> sublayout) where
761841
762842 docsFor Proxy (endpoint, action) =