@@ -1,4 +1,3 @@
1−{-# LANGUAGE MultiWayIf #-}
2module Servant.QuickCheck.Internal.Predicates where
1module Servant.QuickCheck.Internal.Predicates where
4import Control.Monad
3import Control.Monad
@@ -10,15 +9,19 @@ import qualified Data.ByteString.Lazy as LBS
10import Data.CaseInsensitive (mk)
9import Data.CaseInsensitive (mk)
11import Data.Either (isRight)
10import Data.Either (isRight)
12import Data.List.Split (wordsBy)
11import Data.List.Split (wordsBy)
13−import Data.Maybe (isJust)
12+import Data.Maybe (fromMaybe, isJust, maybeToList)
14import Data.Monoid ((<>))
13import Data.Monoid ((<>))
15import Data.Text (Text)
14import Data.Text (Text)
16import GHC.Generics (Generic)
15import GHC.Generics (Generic)
17import Network.HTTP.Client (Manager, Request, Response, httpLbs,
16import Network.HTTP.Client (Manager, Request, Response, httpLbs,
17+ method, parseUrl, requestHeaders,
18− method, responseBody, responseHeaders,
18+ responseBody, responseHeaders,
19 responseStatus)
19 responseStatus)
20+import Network.HTTP.Media (matchAccept)
20import Network.HTTP.Types (methodGet, methodHead, parseMethod,
21import Network.HTTP.Types (methodGet, methodHead, parseMethod,
22+ renderStdMethod, status200, status201,
21− status401, renderStdMethod, status405, status500)
23+ status300, status401, status405,
23-- | [__Best Practice__]
26-- | [__Best Practice__]
@@ -72,16 +75,22 @@ onlyJsonObjects
73-- * 201 Created: <https://tools.ietf.org/html/rfc7231#section-6.3.2 RFC 7231 Section 6.3.2>
76-- * 201 Created: <https://tools.ietf.org/html/rfc7231#section-6.3.2 RFC 7231 Section 6.3.2>
74-- * Location header: <https://tools.ietf.org/html/rfc7231#section-7.1.2 RFC 7231 Section 7.1.2>
77-- * Location header: <https://tools.ietf.org/html/rfc7231#section-7.1.2 RFC 7231 Section 7.1.2>
75−{-createContainsValidLocation :: RequestPredicate Text Bool-}
78+createContainsValidLocation :: RequestPredicate Text Bool
76−{-createContainsValidLocation-}
79+createContainsValidLocation
77− {-= RequestPredicate-}
80+ = RequestPredicate
78− {-{ reqPredName = "createContainsValidLocation"-}
81+ { reqPredName = "createContainsValidLocation"
79− {-, reqResps = \req mg -> do-}
82+ , reqResps = \req mgr -> do
80− {-resp <- httpLbs mgr req-}
83+ resp <- httpLbs req mgr
81− {-if responseStatus resp == status201-}
84+ if responseStatus resp == status201
82− {-then case lookup "Location" $ responseHeaders resp of-}
85+ then case lookup "Location" $ responseHeaders resp of
83− {-Nothing -> return []-}
86+ Nothing -> return (False, [resp])
84− {-Just l -> if-}
87+ Just l -> case parseUrl $ SBSC.unpack l of
88+ Nothing -> return (False, [resp])
90+ resp2 <- httpLbs x mgr
91+ return (status2XX resp2, [resp, resp2])
92+ else return (True, [resp])
87getsHaveLastModifiedHeader :: ResponsePredicate Text Bool
96getsHaveLastModifiedHeader :: ResponsePredicate Text Bool
@@ -120,8 +129,9 @@ notAllowedContainsAllowHeader
120 go x = all (\y -> isRight $ parseMethod $ SBSC.pack y)
129 go x = all (\y -> isRight $ parseMethod $ SBSC.pack y)
121 $ wordsBy (`elem` (", " :: [Char])) (SBSC.unpack x)
130 $ wordsBy (`elem` (", " :: [Char])) (SBSC.unpack x)
123− {-
132+-- | [__RFC Compliance__]
124−-- | When a request contains an @Accept@ header, the server must either return
134+-- When a request contains an @Accept@ header, the server must either return
125-- content in one of the requested representations, or respond with @406 Not
135-- content in one of the requested representations, or respond with @406 Not
126-- Acceptable@.
136-- Acceptable@.
@@ -133,18 +143,18 @@ notAllowedContainsAllowHeader
133-- * @Accept@ header: <https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html RFC 2616 Section 14.1>
143-- * @Accept@ header: <https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html RFC 2616 Section 14.1>
134honoursAcceptHeader :: RequestPredicate b Bool
144honoursAcceptHeader :: RequestPredicate b Bool
135honoursAcceptHeader
145honoursAcceptHeader
136− = RequestPredicate name (ResponsePredicate name $ \req mgr -> do
146+ = RequestPredicate
137−
147+ { reqPredName = "honoursAcceptHeader"
148+ , reqResps = \req mgr -> do
138− resp <- httpLbs req mgr
149+ resp <- httpLbs req mgr
139− let scode = responseStatus resp
150+ let scode = responseStatus resp
140− sctype = maybeToList $ lookup "Content-Type" $ responseHeaders resp
151+ sctype = lookup "Content-Type" $ responseHeaders resp
141− sacc = fromMaybe "*/*" $ lookup "Accept" $ requestHeaders req
152+ sacc = fromMaybe "*/*" $ lookup "Accept" (requestHeaders req)
142− if 100 < scode && scode < 300
153+ if 100 < scode && scode < 300
143− then isJust matchAccept sacc sctype
154+ then return (isJust $ sctype >>= \x -> matchAccept x sacc, [resp])
144− else True)
155+ else return (True, [resp])
145− where name = "honoursAcceptHeader"
156+ }
148-- | [__Best Practice__]
158-- | [__Best Practice__]
150-- Whether or not a representation should be cached, it is good practice to
160-- Whether or not a representation should be cached, it is good practice to
@@ -179,7 +189,7 @@ headsHaveCacheControlHeader
179 , reqResps = \req mgr -> if method req == methodHead
189 , reqResps = \req mgr -> if method req == methodHead
181 resp <- httpLbs req mgr
191 resp <- httpLbs req mgr
182− let good = isJust $ lookup "Cache-Control" $ responseHeaders resp
192+ let good = hasValidHeader "Cache-Control" (const True) resp
183 return (good, [resp])
193 return (good, [resp])
184 else return (True, [])
194 else return (True, [])
@@ -334,3 +344,6 @@ hasValidHeader :: SBS.ByteString -> (SBS.ByteString -> Bool) -> Response b -> Bo
334hasValidHeader hdr p r = case lookup (mk hdr) (responseHeaders r) of
344hasValidHeader hdr p r = case lookup (mk hdr) (responseHeaders r) of
335 Nothing -> False
345 Nothing -> False
336 Just v -> p v
346 Just v -> p v
348+status2XX :: Response b -> Bool
349+status2XX r = status200 <= responseStatus r && responseStatus r < status300