@@ -7,12 +7,11 @@ import Data.Bifunctor (first)
7import qualified Data.ByteString as SBS
7import qualified Data.ByteString as SBS
8import qualified Data.ByteString.Char8 as SBSC
8import qualified Data.ByteString.Char8 as SBSC
9import qualified Data.ByteString.Lazy as LBS
9import qualified Data.ByteString.Lazy as LBS
10−import Data.CaseInsensitive (foldCase, foldedCase, mk)
10+import Data.CaseInsensitive (mk, foldedCase)
11import Data.Either (isRight)
11import Data.Either (isRight)
12import Data.List.Split (wordsBy)
12import Data.List.Split (wordsBy)
13import Data.Maybe (fromMaybe, isJust)
13import Data.Maybe (fromMaybe, isJust)
14−import Data.Semigroup (Semigroup (..))
14+import Data.Monoid ((<>))
15−import qualified Data.Text as T
16import Data.Time (UTCTime, defaultTimeLocale, parseTimeM,
15import Data.Time (UTCTime, defaultTimeLocale, parseTimeM,
17 rfc822DateFormat)
16 rfc822DateFormat)
18import GHC.Generics (Generic)
17import GHC.Generics (Generic)
@@ -43,7 +42,7 @@ import Servant.QuickCheck.Internal.ErrorTypes
43-- /Since 0.0.0.0/
42-- /Since 0.0.0.0/
44not500 :: ResponsePredicate
43not500 :: ResponsePredicate
45not500 = ResponsePredicate $ \resp ->
44not500 = ResponsePredicate $ \resp ->
46− when (responseStatus resp == status500) $ throw $ PredicateFailure "not500" Nothing resp
45+ when (responseStatus resp == status500) $ fail "not500"
48-- | [__Optional__]
47-- | [__Optional__]
@@ -84,13 +83,15 @@ notLongerThan maxAllowed
84-- /Since 0.0.0.0/
83-- /Since 0.0.0.0/
85onlyJsonObjects :: ResponsePredicate
84onlyJsonObjects :: ResponsePredicate
86onlyJsonObjects
85onlyJsonObjects
87− = ResponsePredicate (\resp -> do
86+ = ResponsePredicate (\resp -> case go resp of
87+ Nothing -> throw $ PredicateFailure "onlyJsonObjects" Nothing resp
88+ Just () -> return ())
88− case lookup "content-type" (first foldedCase <$> responseHeaders resp) of
91+ ctyp <- lookup "content-type" (first foldedCase <$> responseHeaders r)
90− Just ctype -> when ("application/json" `SBS.isPrefixOf` ctype) $ do
92+ when ("application/json" `SBS.isPrefixOf` ctyp) $ do
91− case (decode (responseBody resp) :: Maybe Object) of
93+ (_ :: Object) <- decode (responseBody r)
92− Nothing -> throw $ PredicateFailure "onlyJsonObjects" Nothing resp
93− Just _ -> return ())
94+ return ()
95-- | __Optional__
96-- | __Optional__
@@ -118,12 +119,12 @@ createContainsValidLocation
118 resp <- httpLbs req mgr
119 resp <- httpLbs req mgr
119 if responseStatus resp == status201
120 if responseStatus resp == status201
120 then case lookup "Location" $ responseHeaders resp of
121 then case lookup "Location" $ responseHeaders resp of
121− Nothing -> throw $ PredicateFailure n (Just req) resp
122+ Nothing -> fail n
122 Just l -> case parseRequest $ SBSC.unpack l of
123 Just l -> case parseRequest $ SBSC.unpack l of
123− Nothing -> throw $ PredicateFailure n (Just req) resp
124+ Nothing -> fail n
124 Just x -> do
125 Just x -> do
125 resp2 <- httpLbs x mgr
126 resp2 <- httpLbs x mgr
126− status2XX (Just req) resp2 n
127+ status2XX resp2 n
127 return [resp, resp2]
128 return [resp, resp2]
128 else return [resp]
129 else return [resp]
@@ -187,15 +188,14 @@ getsHaveLastModifiedHeader
187notAllowedContainsAllowHeader :: RequestPredicate
188notAllowedContainsAllowHeader :: RequestPredicate
188notAllowedContainsAllowHeader
189notAllowedContainsAllowHeader
189 = RequestPredicate $ \req mgr -> do
190 = RequestPredicate $ \req mgr -> do
190− let reqs = [ req { method = renderStdMethod m } | m <- [minBound .. maxBound]
191+ resp <- mapM (flip httpLbs mgr) $ [ req { method = renderStdMethod m }
192+ | m <- [minBound .. maxBound ]
191− , renderStdMethod m /= method req ]
193+ , renderStdMethod m /= method req ]
192− resp <- mapM (flip httpLbs mgr) reqs
194− case filter pred' (zip reqs resp) of
194+ case filter pred' resp of
195− (x:_) -> throw $ PredicateFailure "notAllowedContainsAllowHeader" (Just $ fst x) (snd x)
195+ (x:_) -> throw $ PredicateFailure "notAllowedContainsAllowHeader" (Just req) x
196 [] -> return resp
196 [] -> return resp
198− pred' (_, resp) = responseStatus resp == status405 && not (hasValidHeader "Allow" go resp)
198+ pred' resp = responseStatus resp == status405 && not (hasValidHeader "Allow" go resp)
200 go x = all (\y -> isRight $ parseMethod $ SBSC.pack y)
200 go x = all (\y -> isRight $ parseMethod $ SBSC.pack y)
201 $ wordsBy (`elem` (", " :: [Char])) (SBSC.unpack x)
201 $ wordsBy (`elem` (", " :: [Char])) (SBSC.unpack x)
@@ -225,7 +225,7 @@ honoursAcceptHeader
225 sacc = fromMaybe "*/*" $ lookup "Accept" (requestHeaders req)
225 sacc = fromMaybe "*/*" $ lookup "Accept" (requestHeaders req)
226 if status100 < scode && scode < status300
226 if status100 < scode && scode < status300
227 then if isJust $ sctype >>= \x -> matchAccept [x] sacc
227 then if isJust $ sctype >>= \x -> matchAccept [x] sacc
228− then throw $ PredicateFailure "honoursAcceptHeader" (Just req) resp
228+ then fail "honoursAcceptHeader"
229 else return [resp]
229 else return [resp]
230 else return [resp]
230 else return [resp]
@@ -336,29 +336,7 @@ unauthorizedContainsWWWAuthenticate
336 = ResponsePredicate $ \resp ->
336 = ResponsePredicate $ \resp ->
337 if responseStatus resp == status401
337 if responseStatus resp == status401
338 then unless (hasValidHeader "WWW-Authenticate" (const True) resp) $
338 then unless (hasValidHeader "WWW-Authenticate" (const True) resp) $
339− throw $ PredicateFailure "unauthorizedContainsWWWAuthenticate" Nothing resp
339+ fail "unauthorizedContainsWWWAuthenticate"
343−-- | [__RFC Compliance__]
345−-- [An HTML] document will start with exactly this string: <!DOCTYPE html>
347−-- This function checks that HTML documents (those with `Content-Type: text/html...`)
348−-- include a DOCTYPE declaration at the top. We do not enforce capital case for the string `DOCTYPE`.
352−-- * HTML5 Doctype: <https://tools.ietf.org/html/rfc7992#section-6.1 RFC 7992 Section 6.1>
354−htmlIncludesDoctype :: ResponsePredicate
356− = ResponsePredicate $ \resp ->
357− if hasValidHeader "Content-Type" (SBS.isPrefixOf . foldCase $ "text/html") resp
359− let htmlContent = foldCase . LBS.take 20 $ responseBody resp
360− unless (LBS.isPrefixOf (foldCase "<!doctype html>") htmlContent) $
361− throw $ PredicateFailure "htmlIncludesDoctype" Nothing resp
362 else return ()
340 else return ()
364-- * Predicate logic
342-- * Predicate logic
@@ -376,12 +354,9 @@ newtype ResponsePredicate = ResponsePredicate
376 { getResponsePredicate :: Response LBS.ByteString -> IO ()
354 { getResponsePredicate :: Response LBS.ByteString -> IO ()
377 } deriving (Generic)
355 } deriving (Generic)
379−instance Semigroup ResponsePredicate where
380− ResponsePredicate a <> ResponsePredicate b = ResponsePredicate $ \x -> a x >> b x
382instance Monoid ResponsePredicate where
357instance Monoid ResponsePredicate where
383 mempty = ResponsePredicate $ const $ return ()
358 mempty = ResponsePredicate $ const $ return ()
384− mappend = (<>)
359+ ResponsePredicate a `mappend` ResponsePredicate b = ResponsePredicate $ \x -> a x >> b x
386-- | A predicate that depends on both the request and the response.
361-- | A predicate that depends on both the request and the response.
@@ -393,11 +368,7 @@ newtype RequestPredicate = RequestPredicate
393-- TODO: This isn't actually a monoid
368-- TODO: This isn't actually a monoid
394instance Monoid RequestPredicate where
369instance Monoid RequestPredicate where
395 mempty = RequestPredicate (\r m -> httpLbs r m >>= \x -> return ([x]))
370 mempty = RequestPredicate (\r m -> httpLbs r m >>= \x -> return ([x]))
398−-- TODO: This isn't actually a monoid
399−instance Semigroup RequestPredicate where
400− RequestPredicate a <> RequestPredicate b = RequestPredicate $ \r mgr ->
371+ RequestPredicate a `mappend` RequestPredicate b = RequestPredicate $ \r mgr ->
401 liftM2 (<>) (a r mgr) (b r mgr)
372 liftM2 (<>) (a r mgr) (b r mgr)
403-- | A set of predicates. Construct one with 'mempty' and '<%>'.
374-- | A set of predicates. Construct one with 'mempty' and '<%>'.
@@ -406,13 +377,10 @@ data Predicates = Predicates
406 , responsePredicates :: ResponsePredicate
377 , responsePredicates :: ResponsePredicate
407 } deriving (Generic)
378 } deriving (Generic)
409−instance Semigroup Predicates where
410− a <> b = Predicates (requestPredicates a <> requestPredicates b)
411− (responsePredicates a <> responsePredicates b)
413instance Monoid Predicates where
380instance Monoid Predicates where
414 mempty = Predicates mempty mempty
381 mempty = Predicates mempty mempty
415− mappend = (<>)
382+ a `mappend` b = Predicates (requestPredicates a <> requestPredicates b)
383+ (responsePredicates a <> responsePredicates b)
417class JoinPreds a where
385class JoinPreds a where
418 joinPreds :: a -> Predicates -> Predicates
386 joinPreds :: a -> Predicates -> Predicates
@@ -454,8 +422,8 @@ isRFC822Date s
454 Nothing -> False
422 Nothing -> False
455 Just (_ :: UTCTime) -> True
423 Just (_ :: UTCTime) -> True
457−status2XX :: Monad m => Maybe Request -> Response LBS.ByteString -> T.Text -> m ()
425+status2XX :: Monad m => Response b -> String -> m ()
458−status2XX mreq resp t
426+status2XX r t
459− | status200 <= responseStatus resp && responseStatus resp < status300
427+ | status200 <= responseStatus r && responseStatus r < status300
460 = return ()
428 = return ()
461− | otherwise = throw $ PredicateFailure t mreq resp
429+ | otherwise = fail t