Skip to content

Commit 0d237e5

Browse files
committed
Use full header type in response header instances
This is required to be able provide `Description` into headers, for example like this: ``` type PaginationTotalCountHeader = Header' '[ Description "Indicates to the client total count of items in collection" , Optional , Strict ] "Total-Count" Int ```
1 parent 02242e9 commit 0d237e5

File tree

4 files changed

+40
-20
lines changed

4 files changed

+40
-20
lines changed

servant/src/Servant/API/ResponseHeaders.hs

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ module Servant.API.ResponseHeaders
3737
import Control.DeepSeq
3838
(NFData (..))
3939
import Data.ByteString.Char8 as BS
40-
(ByteString, init, pack, unlines)
40+
(ByteString, pack)
4141
import qualified Data.CaseInsensitive as CI
4242
import qualified Data.List as L
4343
import Data.Proxy
@@ -52,7 +52,7 @@ import Web.HttpApiData
5252
import Prelude ()
5353
import Prelude.Compat
5454
import Servant.API.Header
55-
(Header)
55+
(Header, Header')
5656
import Servant.API.UVerb.Union
5757
import qualified Data.SOP.BasicFunctors as SOP
5858
import qualified Data.SOP.NS as SOP
@@ -81,19 +81,19 @@ instance NFData a => NFData (ResponseHeader sym a) where
8181

8282
data HList a where
8383
HNil :: HList '[]
84-
HCons :: ResponseHeader h x -> HList xs -> HList (Header h x ': xs)
84+
HCons :: ResponseHeader h x -> HList xs -> HList (Header' mods h x ': xs)
8585

8686
class NFDataHList xs where rnfHList :: HList xs -> ()
8787
instance NFDataHList '[] where rnfHList HNil = ()
88-
instance (y ~ Header h x, NFData x, NFDataHList xs) => NFDataHList (y ': xs) where
88+
instance (y ~ Header' mods h x, NFData x, NFDataHList xs) => NFDataHList (y ': xs) where
8989
rnfHList (HCons h xs) = rnf h `seq` rnfHList xs
9090

9191
instance NFDataHList xs => NFData (HList xs) where
9292
rnf = rnfHList
9393

9494
type family HeaderValMap (f :: * -> *) (xs :: [*]) where
9595
HeaderValMap f '[] = '[]
96-
HeaderValMap f (Header h x ': xs) = Header h (f x) ': HeaderValMap f xs
96+
HeaderValMap f (Header' mods h x ': xs) = Header' mods h (f x) ': HeaderValMap f xs
9797

9898

9999
class BuildHeadersTo hs where
@@ -105,7 +105,7 @@ instance {-# OVERLAPPING #-} BuildHeadersTo '[] where
105105
-- The current implementation does not manipulate HTTP header field lines in any way,
106106
-- like merging field lines with the same field name in a single line.
107107
instance {-# OVERLAPPABLE #-} ( FromHttpApiData v, BuildHeadersTo xs, KnownSymbol h )
108-
=> BuildHeadersTo (Header h v ': xs) where
108+
=> BuildHeadersTo (Header' mods h v ': xs) where
109109
buildHeadersTo headers = case L.find wantedHeader headers of
110110
Nothing -> MissingHeader `HCons` buildHeadersTo headers
111111
Just header@(_, val) -> case parseHeader val of
@@ -130,7 +130,7 @@ instance GetHeadersFromHList '[] where
130130
getHeadersFromHList _ = []
131131

132132
instance (KnownSymbol h, ToHttpApiData x, GetHeadersFromHList xs)
133-
=> GetHeadersFromHList (Header h x ': xs)
133+
=> GetHeadersFromHList (Header' mods h x ': xs)
134134
where
135135
getHeadersFromHList hdrs = case hdrs of
136136
Header val `HCons` rest -> (headerName , toHeader val) : getHeadersFromHList rest
@@ -151,42 +151,42 @@ instance GetHeaders' '[] where
151151
getHeaders' _ = []
152152

153153
instance (KnownSymbol h, GetHeadersFromHList rest, ToHttpApiData v)
154-
=> GetHeaders' (Header h v ': rest)
154+
=> GetHeaders' (Header' mods h v ': rest)
155155
where
156156
getHeaders' hs = getHeadersFromHList $ getHeadersHList hs
157157

158158
-- * Adding headers
159159

160160
-- We need all these fundeps to save type inference
161-
class AddHeader h v orig new
162-
| h v orig -> new, new -> h, new -> v, new -> orig where
161+
class AddHeader (mods :: [*]) h v orig new
162+
| mods h v orig -> new, new -> mods, new -> h, new -> v, new -> orig where
163163
addOptionalHeader :: ResponseHeader h v -> orig -> new -- ^ N.B.: The same header can't be added multiple times
164164

165165
-- In this instance, we add a Header on top of something that is already decorated with some headers
166166
instance {-# OVERLAPPING #-} ( KnownSymbol h, ToHttpApiData v )
167-
=> AddHeader h v (Headers (fst ': rest) a) (Headers (Header h v ': fst ': rest) a) where
167+
=> AddHeader mods h v (Headers (fst ': rest) a) (Headers (Header' mods h v ': fst ': rest) a) where
168168
addOptionalHeader hdr (Headers resp heads) = Headers resp (HCons hdr heads)
169169

170170
-- In this instance, 'a' parameter is decorated with a Header.
171-
instance {-# OVERLAPPABLE #-} ( KnownSymbol h, ToHttpApiData v , new ~ Headers '[Header h v] a)
172-
=> AddHeader h v a new where
171+
instance {-# OVERLAPPABLE #-} ( KnownSymbol h, ToHttpApiData v , new ~ Headers '[Header' mods h v] a)
172+
=> AddHeader mods h v a new where
173173
addOptionalHeader hdr resp = Headers resp (HCons hdr HNil)
174174

175175
-- Instances to decorate all responses in a 'Union' with headers. The functional
176176
-- dependencies force us to consider singleton lists as the base case in the
177177
-- recursion (it is impossible to determine h and v otherwise from old / new
178178
-- responses if the list is empty).
179-
instance (AddHeader h v old new) => AddHeader h v (Union '[old]) (Union '[new]) where
179+
instance (AddHeader mods h v old new) => AddHeader mods h v (Union '[old]) (Union '[new]) where
180180
addOptionalHeader hdr resp =
181181
SOP.Z $ SOP.I $ addOptionalHeader hdr $ SOP.unI $ SOP.unZ $ resp
182182

183183
instance
184-
( AddHeader h v old new, AddHeader h v (Union oldrest) (Union newrest)
184+
( AddHeader mods h v old new, AddHeader mods h v (Union oldrest) (Union newrest)
185185
-- This ensures that the remainder of the response list is _not_ empty
186186
-- It is necessary to prevent the two instances for union types from
187187
-- overlapping.
188188
, oldrest ~ (a ': as), newrest ~ (b ': bs))
189-
=> AddHeader h v (Union (old ': (a ': as))) (Union (new ': (b ': bs))) where
189+
=> AddHeader mods h v (Union (old ': (a ': as))) (Union (new ': (b ': bs))) where
190190
addOptionalHeader hdr resp = case resp of
191191
SOP.Z (SOP.I rHead) -> SOP.Z $ SOP.I $ addOptionalHeader hdr rHead
192192
SOP.S rOthers -> SOP.S $ addOptionalHeader hdr rOthers
@@ -211,21 +211,21 @@ instance
211211
-- Note that while in your handlers type annotations are not required, since
212212
-- the type can be inferred from the API type, in other cases you may find
213213
-- yourself needing to add annotations.
214-
addHeader :: AddHeader h v orig new => v -> orig -> new
214+
addHeader :: AddHeader mods h v orig new => v -> orig -> new
215215
addHeader = addOptionalHeader . Header
216216

217217
-- | Deliberately do not add a header to a value.
218218
--
219219
-- >>> let example1 = noHeader "hi" :: Headers '[Header "someheader" Int] String
220220
-- >>> getHeaders example1
221221
-- []
222-
noHeader :: AddHeader h v orig new => orig -> new
222+
noHeader :: AddHeader mods h v orig new => orig -> new
223223
noHeader = addOptionalHeader MissingHeader
224224

225225
class HasResponseHeader h a headers where
226226
hlistLookupHeader :: HList headers -> ResponseHeader h a
227227

228-
instance {-# OVERLAPPING #-} HasResponseHeader h a (Header h a ': rest) where
228+
instance {-# OVERLAPPING #-} HasResponseHeader h a (Header' mods h a ': rest) where
229229
hlistLookupHeader (HCons ha _) = ha
230230

231231
instance {-# OVERLAPPABLE #-} (HasResponseHeader h a rest) => HasResponseHeader h a (first ': rest) where

servant/src/Servant/API/TypeLevel.hs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ import Servant.API.Capture
5959
(Capture, CaptureAll)
6060
import Servant.API.Fragment
6161
import Servant.API.Header
62-
(Header)
62+
(Header, Header')
6363
import Servant.API.QueryParam
6464
(QueryFlag, QueryParam, QueryParams)
6565
import Servant.API.ReqBody
@@ -130,6 +130,7 @@ type family IsElem endpoint api :: Constraint where
130130
IsElem e (sa :<|> sb) = Or (IsElem e sa) (IsElem e sb)
131131
IsElem (e :> sa) (e :> sb) = IsElem sa sb
132132
IsElem sa (Header sym x :> sb) = IsElem sa sb
133+
IsElem sa (Header' mods sym x :> sb) = IsElem sa sb
133134
IsElem sa (ReqBody y x :> sb) = IsElem sa sb
134135
IsElem (CaptureAll z y :> sa) (CaptureAll x y :> sb)
135136
= IsElem sa sb

servant/stack.yaml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
resolver: lts-20.26
2+
3+
ghc-options:
4+
# locally install Haddocs for "everything": deps and the project
5+
$everything: -haddock
6+
# speed up project build via module-parallel compile in GHC itself
7+
$targets: -j

servant/stack.yaml.lock

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# This file was autogenerated by Stack.
2+
# You should not edit this file by hand.
3+
# For more information, please see the documentation at:
4+
# https://docs.haskellstack.org/en/stable/lock_files
5+
6+
packages: []
7+
snapshots:
8+
- completed:
9+
sha256: 5a59b2a405b3aba3c00188453be172b85893cab8ebc352b1ef58b0eae5d248a2
10+
size: 650475
11+
url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/20/26.yaml
12+
original: lts-20.26

0 commit comments

Comments
 (0)